use crate::error::Result;
use crate::mkv::attachments::MAX_ATTACHMENT_FIELD;
use crate::mkv::info::DEFAULT_TIMESTAMP_SCALE;
use crate::mkv::mux::bytes::invalid;
pub(crate) const APP_NAME: &str = concat!("containr ", env!("CARGO_PKG_VERSION"));
pub const DEFAULT_CLUSTER_DURATION_MS: u32 = 5000;
pub const MAX_CLUSTER_DURATION_MS: u32 = 3_600_000;
pub const DEFAULT_CLUSTER_BYTES: usize = 5 << 20;
pub const MIN_CLUSTER_BYTES: usize = 1 << 10;
pub const MAX_CLUSTER_BYTES: usize = 1 << 30;
pub const DEFAULT_MAX_BUFFER: usize = 64 << 20;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum DocType {
Matroska,
WebM,
}
impl DocType {
pub(crate) const fn value(self) -> &'static str {
match self {
Self::Matroska => "matroska",
Self::WebM => "webm",
}
}
}
pub(crate) fn webm_allows(codec_id: &str) -> bool {
matches!(
codec_id,
"V_VP8"
| "V_VP9"
| "V_AV1"
| "A_OPUS"
| "A_VORBIS"
| "S_TEXT/WEBVTT"
| "D_WEBVTT/SUBTITLES"
| "D_WEBVTT/CAPTIONS"
| "D_WEBVTT/DESCRIPTIONS"
| "D_WEBVTT/METADATA"
)
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct MkvOptions {
pub doc_type: DocType,
pub timestamp_scale: u64,
pub cluster_duration_ms: u32,
pub cluster_bytes: usize,
pub max_buffer: usize,
pub cues: bool,
pub seekable: bool,
pub title: Option<String>,
pub writing_app: String,
}
impl Default for MkvOptions {
fn default() -> Self {
Self {
doc_type: DocType::Matroska,
timestamp_scale: DEFAULT_TIMESTAMP_SCALE,
cluster_duration_ms: DEFAULT_CLUSTER_DURATION_MS,
cluster_bytes: DEFAULT_CLUSTER_BYTES,
max_buffer: DEFAULT_MAX_BUFFER,
cues: true,
seekable: true,
title: None,
writing_app: APP_NAME.to_owned(),
}
}
}
impl MkvOptions {
pub(crate) fn check(&self) -> Result<()> {
if self.timestamp_scale == 0 || self.timestamp_scale > u64::from(u32::MAX) {
return Err(invalid(format!(
"`MkvOptions::timestamp_scale` is {}. Use 1 to {} nanoseconds per tick",
self.timestamp_scale,
u32::MAX
)));
}
if self.cluster_duration_ms == 0 || self.cluster_duration_ms > MAX_CLUSTER_DURATION_MS {
return Err(invalid(format!(
"`MkvOptions::cluster_duration_ms` is {}. Use 1 to {MAX_CLUSTER_DURATION_MS}",
self.cluster_duration_ms
)));
}
if !(MIN_CLUSTER_BYTES..=MAX_CLUSTER_BYTES).contains(&self.cluster_bytes) {
return Err(invalid(format!(
"`MkvOptions::cluster_bytes` is {}. Use {MIN_CLUSTER_BYTES} to {MAX_CLUSTER_BYTES}",
self.cluster_bytes
)));
}
if self.max_buffer < self.cluster_bytes {
return Err(invalid(format!(
"`MkvOptions::max_buffer` is {}, below `cluster_bytes` {}. Use at least \
`cluster_bytes`",
self.max_buffer, self.cluster_bytes
)));
}
let title = self.title.as_deref().unwrap_or_default();
for (field, value) in [("title", title), ("writing_app", self.writing_app.as_str())] {
if value.len() as u64 > MAX_ATTACHMENT_FIELD {
return Err(invalid(format!(
"`MkvOptions::{field}` holds {} bytes, above the limit of \
{MAX_ATTACHMENT_FIELD}. Use a shorter text",
value.len()
)));
}
}
Ok(())
}
pub(crate) fn cluster_ticks(&self) -> i64 {
let ticks = u128::from(self.cluster_duration_ms) * 1_000_000
/ u128::from(self.timestamp_scale.max(1));
i64::try_from(ticks).unwrap_or(i64::MAX).max(1)
}
}