#![forbid(unsafe_code)]
#![allow(clippy::cast_possible_truncation)]
#![allow(clippy::cast_sign_loss)]
use crate::demux::matroska::ebml::element_id;
#[derive(Debug)]
pub struct ClusterWriter {
pub timecode: i64,
pub position: u64,
max_duration: i64,
max_size: usize,
current_size: usize,
last_timecode: i64,
block_count: usize,
}
impl ClusterWriter {
#[must_use]
pub const fn new(timecode: i64, position: u64, max_duration: i64, max_size: usize) -> Self {
Self {
timecode,
position,
max_duration,
max_size,
current_size: 0,
last_timecode: timecode,
block_count: 0,
}
}
#[must_use]
pub fn should_start_new(&self, new_timecode: i64, block_size: usize) -> bool {
let duration = new_timecode - self.timecode;
if duration > self.max_duration {
return true;
}
let estimated_block_size = block_size + 10; if self.current_size + estimated_block_size > self.max_size {
return true;
}
false
}
pub fn add_block(&mut self, timecode: i64, size: usize) {
self.current_size += size + 10; self.last_timecode = timecode;
self.block_count += 1;
}
#[must_use]
pub fn duration(&self) -> i64 {
self.last_timecode - self.timecode
}
#[must_use]
pub const fn block_count(&self) -> usize {
self.block_count
}
#[must_use]
pub const fn current_size(&self) -> usize {
self.current_size
}
}
#[must_use]
#[allow(dead_code)]
pub fn build_simple_block(
track_num: u64,
timecode: i16,
data: &[u8],
keyframe: bool,
invisible: bool,
discardable: bool,
) -> Vec<u8> {
let mut block = Vec::with_capacity(data.len() + 10);
block.extend(encode_track_vint(track_num));
block.extend(&timecode.to_be_bytes());
let mut flags: u8 = 0;
if keyframe {
flags |= 0x80;
}
if invisible {
flags |= 0x08;
}
if discardable {
flags |= 0x01;
}
block.push(flags);
block.extend_from_slice(data);
block
}
#[must_use]
#[allow(dead_code)]
pub fn build_block_group(
track_num: u64,
timecode: i16,
data: &[u8],
duration: Option<i64>,
reference_block: Option<i16>,
) -> Vec<u8> {
let mut content = Vec::new();
let mut block = Vec::with_capacity(data.len() + 10);
block.extend(encode_track_vint(track_num));
block.extend(&timecode.to_be_bytes());
block.push(0x00); block.extend_from_slice(data);
content.extend(encode_element_id(element_id::BLOCK));
content.extend(encode_vint_size(block.len() as u64));
content.extend(block);
if let Some(dur) = duration {
content.extend(encode_element_id(element_id::BLOCK_DURATION));
let dur_bytes = encode_uint(dur as u64);
content.extend(encode_vint_size(dur_bytes.len() as u64));
content.extend(dur_bytes);
}
if let Some(ref_block) = reference_block {
content.extend(encode_element_id(element_id::REFERENCE_BLOCK));
let ref_bytes = encode_sint(i64::from(ref_block));
content.extend(encode_vint_size(ref_bytes.len() as u64));
content.extend(ref_bytes);
}
let mut result = Vec::new();
result.extend(encode_element_id(element_id::BLOCK_GROUP));
result.extend(encode_vint_size(content.len() as u64));
result.extend(content);
result
}
fn encode_track_vint(track_num: u64) -> Vec<u8> {
if track_num < 0x80 {
vec![0x80 | track_num as u8]
} else if track_num < 0x4000 {
vec![0x40 | (track_num >> 8) as u8, track_num as u8]
} else if track_num < 0x20_0000 {
vec![
0x20 | (track_num >> 16) as u8,
(track_num >> 8) as u8,
track_num as u8,
]
} else {
vec![
0x10 | (track_num >> 24) as u8,
(track_num >> 16) as u8,
(track_num >> 8) as u8,
track_num as u8,
]
}
}
fn encode_element_id(id: u32) -> Vec<u8> {
if id <= 0x7F {
vec![id as u8]
} else if id <= 0x3FFF {
vec![(id >> 8) as u8, id as u8]
} else if id <= 0x1F_FFFF {
vec![(id >> 16) as u8, (id >> 8) as u8, id as u8]
} else {
vec![
(id >> 24) as u8,
(id >> 16) as u8,
(id >> 8) as u8,
id as u8,
]
}
}
fn encode_vint_size(size: u64) -> Vec<u8> {
if size < 0x7F {
vec![0x80 | size as u8]
} else if size < 0x3FFF {
vec![0x40 | (size >> 8) as u8, size as u8]
} else if size < 0x1F_FFFF {
vec![0x20 | (size >> 16) as u8, (size >> 8) as u8, size as u8]
} else if size < 0x0FFF_FFFF {
vec![
0x10 | (size >> 24) as u8,
(size >> 16) as u8,
(size >> 8) as u8,
size as u8,
]
} else {
vec![
0x01,
(size >> 48) as u8,
(size >> 40) as u8,
(size >> 32) as u8,
(size >> 24) as u8,
(size >> 16) as u8,
(size >> 8) as u8,
size as u8,
]
}
}
fn encode_uint(value: u64) -> Vec<u8> {
if value == 0 {
vec![0]
} else if value <= 0xFF {
vec![value as u8]
} else if value <= 0xFFFF {
vec![(value >> 8) as u8, value as u8]
} else if value <= 0xFF_FFFF {
vec![(value >> 16) as u8, (value >> 8) as u8, value as u8]
} else if value <= 0xFFFF_FFFF {
vec![
(value >> 24) as u8,
(value >> 16) as u8,
(value >> 8) as u8,
value as u8,
]
} else {
vec![
(value >> 56) as u8,
(value >> 48) as u8,
(value >> 40) as u8,
(value >> 32) as u8,
(value >> 24) as u8,
(value >> 16) as u8,
(value >> 8) as u8,
value as u8,
]
}
}
fn encode_sint(value: i64) -> Vec<u8> {
if (-0x40..0x40).contains(&value) {
vec![value as u8]
} else if (-0x2000..0x2000).contains(&value) {
vec![(value >> 8) as u8, value as u8]
} else if (-0x10_0000..0x10_0000).contains(&value) {
vec![(value >> 16) as u8, (value >> 8) as u8, value as u8]
} else if (-0x0800_0000..0x0800_0000).contains(&value) {
vec![
(value >> 24) as u8,
(value >> 16) as u8,
(value >> 8) as u8,
value as u8,
]
} else {
vec![
(value >> 56) as u8,
(value >> 48) as u8,
(value >> 40) as u8,
(value >> 32) as u8,
(value >> 24) as u8,
(value >> 16) as u8,
(value >> 8) as u8,
value as u8,
]
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cluster_writer_new() {
let writer = ClusterWriter::new(1000, 0, 5000, 5 * 1024 * 1024);
assert_eq!(writer.timecode, 1000);
assert_eq!(writer.position, 0);
assert_eq!(writer.block_count(), 0);
}
#[test]
fn test_cluster_writer_add_block() {
let mut writer = ClusterWriter::new(0, 0, 5000, 5 * 1024 * 1024);
writer.add_block(100, 1000);
assert_eq!(writer.block_count(), 1);
assert_eq!(writer.last_timecode, 100);
}
#[test]
fn test_cluster_writer_duration() {
let mut writer = ClusterWriter::new(0, 0, 5000, 5 * 1024 * 1024);
writer.add_block(1000, 100);
assert_eq!(writer.duration(), 1000);
}
#[test]
fn test_should_start_new_duration() {
let writer = ClusterWriter::new(0, 0, 5000, 5 * 1024 * 1024);
assert!(!writer.should_start_new(4999, 100));
assert!(writer.should_start_new(5001, 100));
}
#[test]
fn test_should_start_new_size() {
let writer = ClusterWriter::new(0, 0, 5000, 1000);
assert!(!writer.should_start_new(100, 500));
assert!(writer.should_start_new(100, 1000));
}
#[test]
fn test_build_simple_block() {
let block = build_simple_block(1, 0, &[1, 2, 3, 4], true, false, false);
assert!(!block.is_empty());
assert!(block.len() >= 8);
assert_eq!(block[0], 0x81); assert_eq!(block[3], 0x80); }
#[test]
fn test_build_simple_block_flags() {
let keyframe = build_simple_block(1, 0, &[], true, false, false);
assert_eq!(keyframe[3], 0x80);
let invisible = build_simple_block(1, 0, &[], false, true, false);
assert_eq!(invisible[3], 0x08);
let discardable = build_simple_block(1, 0, &[], false, false, true);
assert_eq!(discardable[3], 0x01);
let all_flags = build_simple_block(1, 0, &[], true, true, true);
assert_eq!(all_flags[3], 0x89);
}
#[test]
fn test_build_block_group() {
let group = build_block_group(1, 100, &[1, 2, 3], Some(500), None);
assert!(!group.is_empty());
assert!(group.len() > 10); }
#[test]
fn test_encode_track_vint() {
assert_eq!(encode_track_vint(1), vec![0x81]);
assert_eq!(encode_track_vint(127), vec![0xFF]);
assert_eq!(encode_track_vint(128), vec![0x40, 0x80]);
}
#[test]
fn test_encode_uint() {
assert_eq!(encode_uint(0), vec![0]);
assert_eq!(encode_uint(255), vec![255]);
assert_eq!(encode_uint(256), vec![1, 0]);
}
#[test]
fn test_encode_sint() {
assert_eq!(encode_sint(0), vec![0]);
assert_eq!(encode_sint(-1), vec![0xFF]);
assert_eq!(encode_sint(63), vec![63]);
assert_eq!(encode_sint(-64), vec![0xC0]);
}
}