use broadcast_common::impl_spec_display;
use transmux::dash_parse::MAX_FORMAT_WIDTH;
const IDENT_RESERVED: &str = "RepresentationID";
const IDENT_NUMBER: &str = "Number";
const IDENT_TIME: &str = "Time";
const IDENT_BANDWIDTH: &str = "Bandwidth";
const IDENT_SUB_NUMBER: &str = "SubNumber";
const TAG_ZERO_FLAG: char = '0';
const TAG_CONVERSION: char = 'd';
const DELIMITER: char = '$';
const TAG_START: char = '%';
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[non_exhaustive]
pub struct TemplateUse {
pub representation_id: bool,
pub number: bool,
pub time: bool,
pub bandwidth: bool,
pub sub_number: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum TemplateFault {
UnterminatedIdentifier,
UnknownIdentifier,
FormatOnRepresentationId,
BadFormatTag,
WidthTooLarge,
NumberAndTime,
MissingNumberOrTime,
DynamicIdentifierInInitialization,
}
impl TemplateFault {
#[must_use]
pub fn name(&self) -> &'static str {
match self {
Self::UnterminatedIdentifier => "unterminated_identifier",
Self::UnknownIdentifier => "unknown_identifier",
Self::FormatOnRepresentationId => "format_on_representation_id",
Self::BadFormatTag => "bad_format_tag",
Self::WidthTooLarge => "width_too_large",
Self::NumberAndTime => "number_and_time",
Self::MissingNumberOrTime => "missing_number_or_time",
Self::DynamicIdentifierInInitialization => "dynamic_identifier_in_initialization",
}
}
}
impl_spec_display!(TemplateFault);
fn check_tag(tag: &str) -> Result<(), TemplateFault> {
let inner = tag
.strip_suffix(TAG_CONVERSION)
.ok_or(TemplateFault::BadFormatTag)?;
let digits = inner
.strip_prefix(TAG_ZERO_FLAG)
.ok_or(TemplateFault::BadFormatTag)?;
if !digits.bytes().all(|b| b.is_ascii_digit()) {
return Err(TemplateFault::BadFormatTag);
}
if !digits.is_empty() {
let width: usize = digits.parse().map_err(|_| TemplateFault::WidthTooLarge)?;
if width > MAX_FORMAT_WIDTH {
return Err(TemplateFault::WidthTooLarge);
}
}
Ok(())
}
pub fn scan(template: &str) -> Result<TemplateUse, TemplateFault> {
let mut used = TemplateUse::default();
let mut rest = template;
while let Some(at) = rest.find(DELIMITER) {
let tail = &rest[at + 1..];
if let Some(after) = tail.strip_prefix(DELIMITER) {
rest = after;
continue;
}
let end = tail
.find(DELIMITER)
.ok_or(TemplateFault::UnterminatedIdentifier)?;
let ident = &tail[..end];
let (name, tag) = match ident.split_once(TAG_START) {
Some((n, t)) => (n, Some(format!("{TAG_START}{t}"))),
None => (ident, None),
};
match name {
IDENT_RESERVED => {
if tag.is_some() {
return Err(TemplateFault::FormatOnRepresentationId);
}
used.representation_id = true;
}
IDENT_NUMBER | IDENT_TIME | IDENT_BANDWIDTH | IDENT_SUB_NUMBER => {
if let Some(t) = &tag {
check_tag(&t[TAG_START.len_utf8()..])?;
}
match name {
IDENT_NUMBER => used.number = true,
IDENT_TIME => used.time = true,
IDENT_BANDWIDTH => used.bandwidth = true,
_ => used.sub_number = true,
}
}
_ => return Err(TemplateFault::UnknownIdentifier),
}
rest = &tail[end + 1..];
}
Ok(used)
}
pub fn scan_media(template: &str) -> Result<TemplateUse, TemplateFault> {
let used = scan(template)?;
if used.number && used.time {
return Err(TemplateFault::NumberAndTime);
}
if !used.number && !used.time {
return Err(TemplateFault::MissingNumberOrTime);
}
Ok(used)
}
pub fn scan_initialization(template: &str) -> Result<TemplateUse, TemplateFault> {
let used = scan(template)?;
if used.number || used.time {
return Err(TemplateFault::DynamicIdentifierInInitialization);
}
Ok(used)
}
#[must_use]
pub fn substitute(
template: &str,
representation_id: &str,
number: Option<u64>,
time: Option<u64>,
bandwidth: u64,
) -> String {
transmux::SegmentTemplate::resolve(template, representation_id, number, time, Some(bandwidth))
}