mirror of
https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs.git
synced 2024-12-22 18:16:28 +00:00
closedcaption: Port from nom to winnow
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1475>
This commit is contained in:
parent
f7ffa13543
commit
5df7c01cb5
7 changed files with 598 additions and 736 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -2255,7 +2255,6 @@ dependencies = [
|
|||
"gstreamer-base",
|
||||
"gstreamer-check",
|
||||
"gstreamer-video",
|
||||
"nom",
|
||||
"once_cell",
|
||||
"pango",
|
||||
"pangocairo",
|
||||
|
@ -2264,6 +2263,7 @@ dependencies = [
|
|||
"serde",
|
||||
"serde_json",
|
||||
"uuid",
|
||||
"winnow 0.6.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -10,7 +10,6 @@ repository.workspace = true
|
|||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
nom = "7.0"
|
||||
either = "1"
|
||||
uuid = { version = "1.0", features = ["v4"] }
|
||||
chrono = "0.4.23"
|
||||
|
@ -26,6 +25,7 @@ once_cell.workspace = true
|
|||
gst = { workspace = true, features = ["v1_16"]}
|
||||
gst-base = { workspace = true, features = ["v1_16"]}
|
||||
gst-video = { workspace = true, features = ["v1_16"]}
|
||||
winnow = "0.6"
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "1"
|
||||
|
|
|
@ -139,7 +139,10 @@ fn parse_timecode_rate(
|
|||
|
||||
impl State {
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn line(&mut self, drain: bool) -> Result<Option<MccLine>, (&[u8], nom::error::Error<&[u8]>)> {
|
||||
fn line(
|
||||
&mut self,
|
||||
drain: bool,
|
||||
) -> Result<Option<MccLine>, (&[u8], winnow::error::ContextError)> {
|
||||
let line = if self.replay_last_line {
|
||||
self.replay_last_line = false;
|
||||
&self.last_raw_line
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
use either::Either;
|
||||
|
||||
use crate::parser_utils::{digits, digits_range, end_of_line, timecode, TimeCode};
|
||||
use nom::IResult;
|
||||
use winnow::{error::StrContext, PResult, Parser};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum MccLine<'a> {
|
||||
|
@ -37,110 +37,84 @@ pub struct MccParser {
|
|||
}
|
||||
|
||||
/// Parser for the MCC header
|
||||
fn header(s: &[u8]) -> IResult<&[u8], MccLine> {
|
||||
use nom::branch::alt;
|
||||
use nom::bytes::complete::tag;
|
||||
use nom::combinator::{map, opt};
|
||||
use nom::error::context;
|
||||
use nom::sequence::tuple;
|
||||
fn header<'a>(s: &mut &'a [u8]) -> PResult<MccLine<'a>> {
|
||||
use winnow::combinator::{alt, opt};
|
||||
use winnow::token::literal;
|
||||
|
||||
context(
|
||||
"invalid header",
|
||||
map(
|
||||
tuple((
|
||||
opt(tag(&[0xEFu8, 0xBBu8, 0xBFu8][..])),
|
||||
tag("File Format=MacCaption_MCC V"),
|
||||
alt((tag("1.0"), tag("2.0"))),
|
||||
end_of_line,
|
||||
)),
|
||||
|_| MccLine::Header,
|
||||
),
|
||||
)(s)
|
||||
(
|
||||
opt(literal(&[0xEFu8, 0xBBu8, 0xBFu8][..])),
|
||||
literal("File Format=MacCaption_MCC V"),
|
||||
alt((literal("1.0"), literal("2.0"))),
|
||||
end_of_line,
|
||||
)
|
||||
.map(|_| MccLine::Header)
|
||||
.context(StrContext::Label("invalid header"))
|
||||
.parse_next(s)
|
||||
}
|
||||
|
||||
/// Parser for an MCC comment, i.e. a line starting with `//`. We don't return the actual comment
|
||||
/// text as it's irrelevant for us.
|
||||
fn comment(s: &[u8]) -> IResult<&[u8], MccLine> {
|
||||
use nom::bytes::complete::tag;
|
||||
use nom::combinator::{map, rest};
|
||||
use nom::error::context;
|
||||
use nom::sequence::tuple;
|
||||
fn comment<'a>(s: &mut &'a [u8]) -> PResult<MccLine<'a>> {
|
||||
use winnow::combinator::rest;
|
||||
use winnow::token::literal;
|
||||
|
||||
context(
|
||||
"invalid comment",
|
||||
map(tuple((tag("//"), rest)), |_| MccLine::Comment),
|
||||
)(s)
|
||||
(literal("//"), rest)
|
||||
.map(|_| MccLine::Comment)
|
||||
.context(StrContext::Label("invalid comment"))
|
||||
.parse_next(s)
|
||||
}
|
||||
|
||||
/// Parser for the MCC UUID line.
|
||||
fn uuid(s: &[u8]) -> IResult<&[u8], MccLine> {
|
||||
use nom::bytes::complete::{tag, take_while1};
|
||||
use nom::combinator::map;
|
||||
use nom::error::context;
|
||||
use nom::sequence::tuple;
|
||||
fn uuid<'a>(s: &mut &'a [u8]) -> PResult<MccLine<'a>> {
|
||||
use winnow::token::{literal, take_while};
|
||||
|
||||
context(
|
||||
"invalid uuid",
|
||||
map(
|
||||
tuple((
|
||||
tag("UUID="),
|
||||
take_while1(|b| b != b'\n' && b != b'\r'),
|
||||
end_of_line,
|
||||
)),
|
||||
|(_, uuid, _)| MccLine::Uuid(uuid),
|
||||
),
|
||||
)(s)
|
||||
(
|
||||
literal("UUID="),
|
||||
take_while(1.., |b| b != b'\n' && b != b'\r'),
|
||||
end_of_line,
|
||||
)
|
||||
.map(|(_, uuid, _)| MccLine::Uuid(uuid))
|
||||
.context(StrContext::Label("invalid uuid"))
|
||||
.parse_next(s)
|
||||
}
|
||||
|
||||
/// Parser for the MCC Time Code Rate line.
|
||||
fn time_code_rate(s: &[u8]) -> IResult<&[u8], MccLine> {
|
||||
use nom::bytes::complete::tag;
|
||||
use nom::combinator::{map, opt};
|
||||
use nom::error::context;
|
||||
use nom::sequence::tuple;
|
||||
fn time_code_rate<'a>(s: &mut &'a [u8]) -> PResult<MccLine<'a>> {
|
||||
use winnow::combinator::opt;
|
||||
use winnow::token::literal;
|
||||
|
||||
context(
|
||||
"invalid timecode rate",
|
||||
map(
|
||||
tuple((
|
||||
tag("Time Code Rate="),
|
||||
digits_range(1..256),
|
||||
opt(tag("DF")),
|
||||
end_of_line,
|
||||
)),
|
||||
|(_, v, df, _)| MccLine::TimeCodeRate(v as u8, df.is_some()),
|
||||
),
|
||||
)(s)
|
||||
(
|
||||
literal("Time Code Rate="),
|
||||
digits_range(1..256),
|
||||
opt(literal("DF")),
|
||||
end_of_line,
|
||||
)
|
||||
.map(|(_, v, df, _)| MccLine::TimeCodeRate(v as u8, df.is_some()))
|
||||
.context(StrContext::Label("invalid timecode rate"))
|
||||
.parse_next(s)
|
||||
}
|
||||
|
||||
/// Parser for generic MCC metadata lines in the form `key=value`.
|
||||
fn metadata(s: &[u8]) -> IResult<&[u8], MccLine> {
|
||||
use nom::bytes::complete::take_while1;
|
||||
use nom::character::complete::char;
|
||||
use nom::combinator::map;
|
||||
use nom::error::context;
|
||||
use nom::sequence::tuple;
|
||||
fn metadata<'a>(s: &mut &'a [u8]) -> PResult<MccLine<'a>> {
|
||||
use winnow::token::{one_of, take_while};
|
||||
|
||||
context(
|
||||
"invalid metadata",
|
||||
map(
|
||||
tuple((
|
||||
take_while1(|b| b != b'='),
|
||||
char('='),
|
||||
take_while1(|b| b != b'\n' && b != b'\r'),
|
||||
end_of_line,
|
||||
)),
|
||||
|(name, _, value, _)| MccLine::Metadata(name, value),
|
||||
),
|
||||
)(s)
|
||||
(
|
||||
take_while(1.., |b| b != b'='),
|
||||
one_of('='),
|
||||
take_while(1.., |b| b != b'\n' && b != b'\r'),
|
||||
end_of_line,
|
||||
)
|
||||
.map(|(name, _, value, _)| MccLine::Metadata(name, value))
|
||||
.context(StrContext::Label("invalid metadata"))
|
||||
.parse_next(s)
|
||||
}
|
||||
|
||||
/// Parser that accepts only an empty line
|
||||
fn empty_line(s: &[u8]) -> IResult<&[u8], MccLine> {
|
||||
use nom::combinator::map;
|
||||
use nom::error::context;
|
||||
|
||||
context("invalid empty line", map(end_of_line, |_| MccLine::Empty))(s)
|
||||
fn empty_line<'a>(s: &mut &'a [u8]) -> PResult<MccLine<'a>> {
|
||||
end_of_line
|
||||
.map(|_| MccLine::Empty)
|
||||
.context(StrContext::Label("invalid empty line"))
|
||||
.parse_next(s)
|
||||
}
|
||||
|
||||
/// A single MCC payload item. This is ASCII hex encoded bytes plus some single-character
|
||||
|
@ -148,152 +122,145 @@ fn empty_line(s: &[u8]) -> IResult<&[u8], MccLine> {
|
|||
///
|
||||
/// It returns an `Either` of the single hex encoded byte or the short-cut byte sequence as a
|
||||
/// static byte slice.
|
||||
fn mcc_payload_item(s: &[u8]) -> IResult<&[u8], Either<u8, &'static [u8]>> {
|
||||
use nom::branch::alt;
|
||||
use nom::bytes::complete::tag;
|
||||
use nom::bytes::complete::take_while_m_n;
|
||||
use nom::character::is_hex_digit;
|
||||
use nom::combinator::map;
|
||||
use nom::error::context;
|
||||
fn mcc_payload_item(s: &mut &[u8]) -> PResult<Either<u8, &'static [u8]>> {
|
||||
use winnow::combinator::alt;
|
||||
use winnow::stream::AsChar;
|
||||
use winnow::token::{literal, take_while};
|
||||
|
||||
context(
|
||||
"invalid payload item",
|
||||
alt((
|
||||
map(tag("G"), |_| Either::Right([0xfa, 0x00, 0x00].as_ref())),
|
||||
map(tag("H"), |_| {
|
||||
Either::Right([0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00].as_ref())
|
||||
}),
|
||||
map(tag("I"), |_| {
|
||||
Either::Right([0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00].as_ref())
|
||||
}),
|
||||
map(tag("J"), |_| {
|
||||
Either::Right(
|
||||
[
|
||||
0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00,
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
}),
|
||||
map(tag("K"), |_| {
|
||||
Either::Right(
|
||||
[
|
||||
0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00,
|
||||
0xfa, 0x00, 0x00,
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
}),
|
||||
map(tag("L"), |_| {
|
||||
Either::Right(
|
||||
[
|
||||
0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00,
|
||||
0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00,
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
}),
|
||||
map(tag("M"), |_| {
|
||||
Either::Right(
|
||||
[
|
||||
0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00,
|
||||
0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00,
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
}),
|
||||
map(tag("N"), |_| {
|
||||
Either::Right(
|
||||
[
|
||||
0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00,
|
||||
0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00,
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
}),
|
||||
map(tag("O"), |_| {
|
||||
Either::Right(
|
||||
[
|
||||
0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00,
|
||||
0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00,
|
||||
0xfa, 0x00, 0x00,
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
}),
|
||||
map(tag("P"), |_| Either::Right([0xfb, 0x80, 0x80].as_ref())),
|
||||
map(tag("Q"), |_| Either::Right([0xfc, 0x80, 0x80].as_ref())),
|
||||
map(tag("R"), |_| Either::Right([0xfd, 0x80, 0x80].as_ref())),
|
||||
map(tag("S"), |_| Either::Right([0x96, 0x69].as_ref())),
|
||||
map(tag("T"), |_| Either::Right([0x61, 0x01].as_ref())),
|
||||
map(tag("U"), |_| Either::Right([0xe1, 0x00, 0x00].as_ref())),
|
||||
map(tag("Z"), |_| Either::Right([0x00].as_ref())),
|
||||
map(take_while_m_n(2, 2, is_hex_digit), |s: &[u8]| {
|
||||
let hex_to_u8 = |v: u8| match v {
|
||||
v if v.is_ascii_digit() => v - b'0',
|
||||
v if (b'A'..=b'F').contains(&v) => 10 + v - b'A',
|
||||
v if (b'a'..=b'f').contains(&v) => 10 + v - b'a',
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let val = (hex_to_u8(s[0]) << 4) | hex_to_u8(s[1]);
|
||||
Either::Left(val)
|
||||
}),
|
||||
)),
|
||||
)(s)
|
||||
alt((
|
||||
literal("G").map(|_| Either::Right([0xfa, 0x00, 0x00].as_ref())),
|
||||
literal("H").map(|_| Either::Right([0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00].as_ref())),
|
||||
literal("I").map(|_| {
|
||||
Either::Right([0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00].as_ref())
|
||||
}),
|
||||
literal("J").map(|_| {
|
||||
Either::Right(
|
||||
[
|
||||
0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00,
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
}),
|
||||
literal("K").map(|_| {
|
||||
Either::Right(
|
||||
[
|
||||
0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa,
|
||||
0x00, 0x00,
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
}),
|
||||
literal("L").map(|_| {
|
||||
Either::Right(
|
||||
[
|
||||
0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa,
|
||||
0x00, 0x00, 0xfa, 0x00, 0x00,
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
}),
|
||||
literal("M").map(|_| {
|
||||
Either::Right(
|
||||
[
|
||||
0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa,
|
||||
0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00,
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
}),
|
||||
literal("N").map(|_| {
|
||||
Either::Right(
|
||||
[
|
||||
0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa,
|
||||
0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00,
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
}),
|
||||
literal("O").map(|_| {
|
||||
Either::Right(
|
||||
[
|
||||
0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa,
|
||||
0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00,
|
||||
0x00,
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
}),
|
||||
literal("P").map(|_| Either::Right([0xfb, 0x80, 0x80].as_ref())),
|
||||
literal("Q").map(|_| Either::Right([0xfc, 0x80, 0x80].as_ref())),
|
||||
literal("R").map(|_| Either::Right([0xfd, 0x80, 0x80].as_ref())),
|
||||
literal("S").map(|_| Either::Right([0x96, 0x69].as_ref())),
|
||||
literal("T").map(|_| Either::Right([0x61, 0x01].as_ref())),
|
||||
literal("U").map(|_| Either::Right([0xe1, 0x00, 0x00].as_ref())),
|
||||
literal("Z").map(|_| Either::Right([0x00].as_ref())),
|
||||
take_while(2..=2, AsChar::is_hex_digit).map(|s: &[u8]| {
|
||||
let hex_to_u8 = |v: u8| match v {
|
||||
v if v.is_ascii_digit() => v - b'0',
|
||||
v if (b'A'..=b'F').contains(&v) => 10 + v - b'A',
|
||||
v if (b'a'..=b'f').contains(&v) => 10 + v - b'a',
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let val = (hex_to_u8(s[0]) << 4) | hex_to_u8(s[1]);
|
||||
Either::Left(val)
|
||||
}),
|
||||
))
|
||||
.context(StrContext::Label("invalid payload item"))
|
||||
.parse_next(s)
|
||||
}
|
||||
|
||||
/// Parser for the whole MCC payload with conversion to the underlying byte values.
|
||||
fn mcc_payload(s: &[u8]) -> IResult<&[u8], Vec<u8>> {
|
||||
use nom::error::context;
|
||||
use nom::multi::fold_many1;
|
||||
fn mcc_payload(s: &mut &[u8]) -> PResult<Vec<u8>> {
|
||||
use winnow::combinator::repeat;
|
||||
|
||||
context(
|
||||
"invalid MCC payload",
|
||||
fold_many1(mcc_payload_item, Vec::new, |mut acc: Vec<_>, item| {
|
||||
repeat(1.., mcc_payload_item)
|
||||
.fold(Vec::new, |mut acc: Vec<_>, item| {
|
||||
match item {
|
||||
Either::Left(val) => acc.push(val),
|
||||
Either::Right(vals) => acc.extend_from_slice(vals),
|
||||
}
|
||||
acc
|
||||
}),
|
||||
)(s)
|
||||
})
|
||||
.context(StrContext::Label("invalid MCC payload"))
|
||||
.parse_next(s)
|
||||
}
|
||||
|
||||
/// Parser for a MCC caption line in the form `timecode\tpayload`.
|
||||
fn caption(parse_payload: bool) -> impl FnMut(&[u8]) -> IResult<&[u8], MccLine> {
|
||||
use nom::bytes::complete::take_while;
|
||||
use nom::character::complete::{char, one_of};
|
||||
use nom::combinator::{map, opt};
|
||||
use nom::error::context;
|
||||
use nom::sequence::tuple;
|
||||
fn caption<'a>(
|
||||
parse_payload: bool,
|
||||
) -> impl Parser<&'a [u8], MccLine<'a>, winnow::error::ContextError> {
|
||||
use winnow::combinator::opt;
|
||||
use winnow::token::{one_of, take_while};
|
||||
|
||||
fn parse(parse_payload: bool) -> impl FnMut(&[u8]) -> IResult<&[u8], Option<Vec<u8>>> {
|
||||
move |s| {
|
||||
fn parse<'a>(
|
||||
parse_payload: bool,
|
||||
) -> impl Parser<&'a [u8], Option<Vec<u8>>, winnow::error::ContextError> {
|
||||
move |s: &mut &'a [u8]| {
|
||||
if parse_payload {
|
||||
map(mcc_payload, Some)(s)
|
||||
mcc_payload.map(Some).parse_next(s)
|
||||
} else {
|
||||
map(take_while(|b| b != b'\n' && b != b'\r'), |_| None)(s)
|
||||
take_while(0.., |b| b != b'\n' && b != b'\r')
|
||||
.map(|_| None)
|
||||
.parse_next(s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
move |s: &[u8]| {
|
||||
context(
|
||||
"invalid MCC caption",
|
||||
map(
|
||||
tuple((
|
||||
timecode,
|
||||
opt(tuple((
|
||||
char('.'),
|
||||
one_of("01"),
|
||||
opt(tuple((char(','), digits))),
|
||||
))),
|
||||
char('\t'),
|
||||
parse(parse_payload),
|
||||
end_of_line,
|
||||
)),
|
||||
|(tc, _, _, value, _)| MccLine::Caption(tc, value),
|
||||
),
|
||||
)(s)
|
||||
move |s: &mut &'a [u8]| {
|
||||
(
|
||||
timecode,
|
||||
opt((
|
||||
one_of('.'),
|
||||
one_of(|c| b"01".contains(&c)),
|
||||
opt((one_of(','), digits)),
|
||||
)),
|
||||
one_of('\t'),
|
||||
parse(parse_payload),
|
||||
end_of_line,
|
||||
)
|
||||
.map(|(tc, _, _, value, _)| MccLine::Caption(tc, value))
|
||||
.context(StrContext::Label("invalid MCC caption"))
|
||||
.parse_next(s)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -317,60 +284,64 @@ impl MccParser {
|
|||
|
||||
pub fn parse_line<'a>(
|
||||
&mut self,
|
||||
line: &'a [u8],
|
||||
mut line: &'a [u8],
|
||||
parse_payload: bool,
|
||||
) -> Result<MccLine<'a>, nom::error::Error<&'a [u8]>> {
|
||||
use nom::branch::alt;
|
||||
) -> Result<MccLine<'a>, winnow::error::ContextError> {
|
||||
use winnow::combinator::alt;
|
||||
|
||||
match self.state {
|
||||
State::Header => header(line)
|
||||
State::Header => header(&mut line)
|
||||
.map(|v| {
|
||||
self.state = State::EmptyAfterHeader;
|
||||
v.1
|
||||
v
|
||||
})
|
||||
.map_err(|err| match err {
|
||||
nom::Err::Incomplete(_) => unreachable!(),
|
||||
nom::Err::Error(e) | nom::Err::Failure(e) => e,
|
||||
winnow::error::ErrMode::Incomplete(_) => unreachable!(),
|
||||
winnow::error::ErrMode::Backtrack(e) | winnow::error::ErrMode::Cut(e) => e,
|
||||
}),
|
||||
State::EmptyAfterHeader => empty_line(line)
|
||||
State::EmptyAfterHeader => empty_line(&mut line)
|
||||
.map(|v| {
|
||||
self.state = State::Comments;
|
||||
v.1
|
||||
v
|
||||
})
|
||||
.map_err(|err| match err {
|
||||
nom::Err::Incomplete(_) => unreachable!(),
|
||||
nom::Err::Error(e) | nom::Err::Failure(e) => e,
|
||||
winnow::error::ErrMode::Incomplete(_) => unreachable!(),
|
||||
winnow::error::ErrMode::Backtrack(e) | winnow::error::ErrMode::Cut(e) => e,
|
||||
}),
|
||||
State::Comments => alt((empty_line, comment))(line)
|
||||
State::Comments => alt((empty_line, comment))
|
||||
.parse_next(&mut line)
|
||||
.map(|v| {
|
||||
if v.1 == MccLine::Empty {
|
||||
if v == MccLine::Empty {
|
||||
self.state = State::Metadata;
|
||||
}
|
||||
|
||||
v.1
|
||||
v
|
||||
})
|
||||
.map_err(|err| match err {
|
||||
nom::Err::Incomplete(_) => unreachable!(),
|
||||
nom::Err::Error(e) | nom::Err::Failure(e) => e,
|
||||
winnow::error::ErrMode::Incomplete(_) => unreachable!(),
|
||||
winnow::error::ErrMode::Backtrack(e) | winnow::error::ErrMode::Cut(e) => e,
|
||||
}),
|
||||
State::Metadata => alt((empty_line, uuid, time_code_rate, metadata))(line)
|
||||
State::Metadata => alt((empty_line, uuid, time_code_rate, metadata))
|
||||
.parse_next(&mut line)
|
||||
.map(|v| {
|
||||
if v.1 == MccLine::Empty {
|
||||
if v == MccLine::Empty {
|
||||
self.state = State::Captions;
|
||||
}
|
||||
|
||||
v.1
|
||||
v
|
||||
})
|
||||
.map_err(|err| match err {
|
||||
nom::Err::Incomplete(_) => unreachable!(),
|
||||
nom::Err::Error(e) | nom::Err::Failure(e) => e,
|
||||
}),
|
||||
State::Captions => caption(parse_payload)(line)
|
||||
.map(|v| v.1)
|
||||
.map_err(|err| match err {
|
||||
nom::Err::Incomplete(_) => unreachable!(),
|
||||
nom::Err::Error(e) | nom::Err::Failure(e) => e,
|
||||
winnow::error::ErrMode::Incomplete(_) => unreachable!(),
|
||||
winnow::error::ErrMode::Backtrack(e) | winnow::error::ErrMode::Cut(e) => e,
|
||||
}),
|
||||
State::Captions => {
|
||||
caption(parse_payload)
|
||||
.parse_next(&mut line)
|
||||
.map_err(|err| match err {
|
||||
winnow::error::ErrMode::Incomplete(_) => unreachable!(),
|
||||
winnow::error::ErrMode::Backtrack(e) | winnow::error::ErrMode::Cut(e) => e,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -381,293 +352,221 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_header() {
|
||||
assert_eq!(
|
||||
header(b"File Format=MacCaption_MCC V1.0".as_ref()),
|
||||
Ok((b"".as_ref(), MccLine::Header))
|
||||
);
|
||||
let mut input = b"File Format=MacCaption_MCC V1.0".as_slice();
|
||||
assert_eq!(header(&mut input), Ok(MccLine::Header));
|
||||
assert!(input.is_empty());
|
||||
|
||||
assert_eq!(
|
||||
header(b"File Format=MacCaption_MCC V1.0\n".as_ref()),
|
||||
Ok((b"".as_ref(), MccLine::Header))
|
||||
);
|
||||
let mut input = b"File Format=MacCaption_MCC V1.0\n".as_slice();
|
||||
assert_eq!(header(&mut input), Ok(MccLine::Header));
|
||||
assert!(input.is_empty());
|
||||
|
||||
assert_eq!(
|
||||
header(b"File Format=MacCaption_MCC V1.0\r\n".as_ref()),
|
||||
Ok((b"".as_ref(), MccLine::Header))
|
||||
);
|
||||
let mut input = b"File Format=MacCaption_MCC V1.0\r\n".as_slice();
|
||||
assert_eq!(header(&mut input), Ok(MccLine::Header));
|
||||
assert!(input.is_empty());
|
||||
|
||||
assert_eq!(
|
||||
header(b"File Format=MacCaption_MCC V2.0\r\n".as_ref()),
|
||||
Ok((b"".as_ref(), MccLine::Header))
|
||||
);
|
||||
let mut input = b"File Format=MacCaption_MCC V2.0\r\n".as_slice();
|
||||
assert_eq!(header(&mut input), Ok(MccLine::Header));
|
||||
assert!(input.is_empty());
|
||||
|
||||
assert_eq!(
|
||||
header(b"File Format=MacCaption_MCC V1.1".as_ref()),
|
||||
Err(nom::Err::Error(nom::error::Error::new(
|
||||
b"1.1".as_ref(),
|
||||
nom::error::ErrorKind::Tag
|
||||
))),
|
||||
);
|
||||
let mut input = b"File Format=MacCaption_MCC V1.1".as_slice();
|
||||
assert!(header(&mut input).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_line() {
|
||||
assert_eq!(empty_line(b"".as_ref()), Ok((b"".as_ref(), MccLine::Empty)));
|
||||
let mut input = b"".as_slice();
|
||||
assert_eq!(empty_line(&mut input), Ok(MccLine::Empty));
|
||||
assert!(input.is_empty());
|
||||
|
||||
assert_eq!(
|
||||
empty_line(b"\n".as_ref()),
|
||||
Ok((b"".as_ref(), MccLine::Empty))
|
||||
);
|
||||
let mut input = b"\n".as_slice();
|
||||
assert_eq!(empty_line(&mut input), Ok(MccLine::Empty));
|
||||
assert!(input.is_empty());
|
||||
|
||||
assert_eq!(
|
||||
empty_line(b"\r\n".as_ref()),
|
||||
Ok((b"".as_ref(), MccLine::Empty))
|
||||
);
|
||||
let mut input = b"\r\n".as_slice();
|
||||
assert_eq!(empty_line(&mut input), Ok(MccLine::Empty));
|
||||
assert!(input.is_empty());
|
||||
|
||||
assert_eq!(
|
||||
empty_line(b" \r\n".as_ref()),
|
||||
Err(nom::Err::Error(nom::error::Error::new(
|
||||
b" \r\n".as_ref(),
|
||||
nom::error::ErrorKind::Eof
|
||||
))),
|
||||
);
|
||||
let mut input = b" \r\n".as_slice();
|
||||
assert!(empty_line(&mut input).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_comment() {
|
||||
assert_eq!(
|
||||
comment(b"// blabla".as_ref()),
|
||||
Ok((b"".as_ref(), MccLine::Comment))
|
||||
);
|
||||
let mut input = b"// blabla".as_slice();
|
||||
assert_eq!(comment(&mut input), Ok(MccLine::Comment));
|
||||
assert!(input.is_empty());
|
||||
|
||||
assert_eq!(
|
||||
comment(b"//\n".as_ref()),
|
||||
Ok((b"".as_ref(), MccLine::Comment))
|
||||
);
|
||||
let mut input = b"//\n".as_slice();
|
||||
assert_eq!(comment(&mut input), Ok(MccLine::Comment));
|
||||
assert!(input.is_empty());
|
||||
|
||||
assert_eq!(
|
||||
comment(b"//".as_ref()),
|
||||
Ok((b"".as_ref(), MccLine::Comment))
|
||||
);
|
||||
let mut input = b"//".as_slice();
|
||||
assert_eq!(comment(&mut input), Ok(MccLine::Comment));
|
||||
assert!(input.is_empty());
|
||||
|
||||
assert_eq!(
|
||||
comment(b" //".as_ref()),
|
||||
Err(nom::Err::Error(nom::error::Error::new(
|
||||
b" //".as_ref(),
|
||||
nom::error::ErrorKind::Tag
|
||||
))),
|
||||
);
|
||||
let mut input = b" //".as_slice();
|
||||
assert!(comment(&mut input).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_uuid() {
|
||||
assert_eq!(
|
||||
uuid(b"UUID=1234".as_ref()),
|
||||
Ok((b"".as_ref(), MccLine::Uuid(b"1234".as_ref())))
|
||||
);
|
||||
let mut input = b"UUID=1234".as_slice();
|
||||
assert_eq!(uuid(&mut input), Ok(MccLine::Uuid(b"1234".as_ref())));
|
||||
assert!(input.is_empty());
|
||||
|
||||
assert_eq!(
|
||||
uuid(b"UUID=1234\n".as_ref()),
|
||||
Ok((b"".as_ref(), MccLine::Uuid(b"1234".as_ref())))
|
||||
);
|
||||
let mut input = b"UUID=1234\n".as_slice();
|
||||
assert_eq!(uuid(&mut input), Ok(MccLine::Uuid(b"1234".as_ref())));
|
||||
assert!(input.is_empty());
|
||||
|
||||
assert_eq!(
|
||||
uuid(b"UUID=1234\r\n".as_ref()),
|
||||
Ok((b"".as_ref(), MccLine::Uuid(b"1234".as_ref())))
|
||||
);
|
||||
let mut input = b"UUID=1234\r\n".as_slice();
|
||||
assert_eq!(uuid(&mut input), Ok(MccLine::Uuid(b"1234".as_ref())));
|
||||
assert!(input.is_empty());
|
||||
|
||||
assert_eq!(
|
||||
uuid(b"UUID=".as_ref()),
|
||||
Err(nom::Err::Error(nom::error::Error::new(
|
||||
b"".as_ref(),
|
||||
nom::error::ErrorKind::TakeWhile1
|
||||
))),
|
||||
);
|
||||
let mut input = b"UUID=".as_slice();
|
||||
assert!(uuid(&mut input).is_err());
|
||||
|
||||
assert_eq!(
|
||||
uuid(b"uUID=1234".as_ref()),
|
||||
Err(nom::Err::Error(nom::error::Error::new(
|
||||
b"uUID=1234".as_ref(),
|
||||
nom::error::ErrorKind::Tag,
|
||||
))),
|
||||
);
|
||||
let mut input = b"uUID=1234".as_slice();
|
||||
assert!(uuid(&mut input).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_time_code_rate() {
|
||||
let mut input = b"Time Code Rate=30".as_slice();
|
||||
assert_eq!(
|
||||
time_code_rate(b"Time Code Rate=30".as_ref()),
|
||||
Ok((b"".as_ref(), MccLine::TimeCodeRate(30, false)))
|
||||
time_code_rate(&mut input),
|
||||
Ok(MccLine::TimeCodeRate(30, false))
|
||||
);
|
||||
assert!(input.is_empty());
|
||||
|
||||
let mut input = b"Time Code Rate=30DF".as_slice();
|
||||
assert_eq!(
|
||||
time_code_rate(b"Time Code Rate=30DF".as_ref()),
|
||||
Ok((b"".as_ref(), MccLine::TimeCodeRate(30, true)))
|
||||
time_code_rate(&mut input),
|
||||
Ok(MccLine::TimeCodeRate(30, true))
|
||||
);
|
||||
assert!(input.is_empty());
|
||||
|
||||
let mut input = b"Time Code Rate=60".as_slice();
|
||||
assert_eq!(
|
||||
time_code_rate(b"Time Code Rate=60".as_ref()),
|
||||
Ok((b"".as_ref(), MccLine::TimeCodeRate(60, false)))
|
||||
time_code_rate(&mut input),
|
||||
Ok(MccLine::TimeCodeRate(60, false))
|
||||
);
|
||||
assert!(input.is_empty());
|
||||
|
||||
assert_eq!(
|
||||
time_code_rate(b"Time Code Rate=17F".as_ref()),
|
||||
Err(nom::Err::Error(nom::error::Error::new(
|
||||
b"F".as_ref(),
|
||||
nom::error::ErrorKind::Eof
|
||||
))),
|
||||
);
|
||||
let mut input = b"Time Code Rate=17F".as_slice();
|
||||
assert!(time_code_rate(&mut input).is_err(),);
|
||||
|
||||
assert_eq!(
|
||||
time_code_rate(b"Time Code Rate=256".as_ref()),
|
||||
Err(nom::Err::Error(nom::error::Error::new(
|
||||
b"256".as_ref(),
|
||||
nom::error::ErrorKind::Verify
|
||||
))),
|
||||
);
|
||||
let mut input = b"Time Code Rate=256".as_slice();
|
||||
assert!(time_code_rate(&mut input).is_err(),);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_metadata() {
|
||||
let mut input = b"Creation Date=Thursday, June 04, 2015".as_slice();
|
||||
assert_eq!(
|
||||
metadata(b"Creation Date=Thursday, June 04, 2015".as_ref()),
|
||||
Ok((
|
||||
b"".as_ref(),
|
||||
MccLine::Metadata(
|
||||
b"Creation Date".as_ref(),
|
||||
b"Thursday, June 04, 2015".as_ref()
|
||||
),
|
||||
metadata(&mut input),
|
||||
Ok(MccLine::Metadata(
|
||||
b"Creation Date".as_ref(),
|
||||
b"Thursday, June 04, 2015".as_ref()
|
||||
))
|
||||
);
|
||||
assert!(input.is_empty());
|
||||
|
||||
let mut input = b"Creation Date= ".as_slice();
|
||||
assert_eq!(
|
||||
metadata(b"Creation Date= ".as_ref()),
|
||||
Ok((
|
||||
b"".as_ref(),
|
||||
MccLine::Metadata(b"Creation Date".as_ref(), b" ".as_ref()),
|
||||
))
|
||||
metadata(&mut input),
|
||||
Ok(MccLine::Metadata(b"Creation Date".as_ref(), b" ".as_ref()),)
|
||||
);
|
||||
assert!(input.is_empty());
|
||||
|
||||
assert_eq!(
|
||||
metadata(b"Creation Date".as_ref()),
|
||||
Err(nom::Err::Error(nom::error::Error::new(
|
||||
b"".as_ref(),
|
||||
nom::error::ErrorKind::Char
|
||||
))),
|
||||
);
|
||||
let mut input = b"Creation Date".as_slice();
|
||||
assert!(metadata(&mut input).is_err());
|
||||
|
||||
assert_eq!(
|
||||
metadata(b"Creation Date\n".as_ref()),
|
||||
Err(nom::Err::Error(nom::error::Error::new(
|
||||
b"".as_ref(),
|
||||
nom::error::ErrorKind::Char,
|
||||
))),
|
||||
);
|
||||
let mut input = b"Creation Date\n".as_slice();
|
||||
assert!(metadata(&mut input).is_err());
|
||||
|
||||
assert_eq!(
|
||||
metadata(b"Creation Date=".as_ref()),
|
||||
Err(nom::Err::Error(nom::error::Error::new(
|
||||
b"".as_ref(),
|
||||
nom::error::ErrorKind::TakeWhile1
|
||||
))),
|
||||
);
|
||||
let mut input = b"Creation Date=".as_slice();
|
||||
assert!(metadata(&mut input).is_err());
|
||||
|
||||
assert_eq!(
|
||||
metadata(b"Creation Date=\n".as_ref()),
|
||||
Err(nom::Err::Error(nom::error::Error::new(
|
||||
b"\n".as_ref(),
|
||||
nom::error::ErrorKind::TakeWhile1
|
||||
))),
|
||||
);
|
||||
let mut input = b"Creation Date=\n".as_slice();
|
||||
assert!(metadata(&mut input).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_caption() {
|
||||
let mut input = b"00:00:00:00\tT52S524F67ZZ72F4QROO7391UC13FFF74ZZAEB4".as_slice();
|
||||
assert_eq!(
|
||||
caption(true)(b"00:00:00:00\tT52S524F67ZZ72F4QROO7391UC13FFF74ZZAEB4".as_ref()),
|
||||
Ok((
|
||||
b"".as_ref(),
|
||||
MccLine::Caption(
|
||||
TimeCode {
|
||||
hours: 0,
|
||||
minutes: 0,
|
||||
seconds: 0,
|
||||
frames: 0,
|
||||
drop_frame: false
|
||||
},
|
||||
Some(vec![
|
||||
0x61, 0x01, 0x52, 0x96, 0x69, 0x52, 0x4F, 0x67, 0x00, 0x00, 0x72, 0xF4,
|
||||
0xFC, 0x80, 0x80, 0xFD, 0x80, 0x80, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
|
||||
0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
|
||||
0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
|
||||
0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
|
||||
0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
|
||||
0x73, 0x91, 0xE1, 0x00, 0x00, 0xC1, 0x3F, 0xFF, 0x74, 0x00, 0x00, 0xAE,
|
||||
0xB4
|
||||
])
|
||||
),
|
||||
caption(true).parse_next(&mut input),
|
||||
Ok(MccLine::Caption(
|
||||
TimeCode {
|
||||
hours: 0,
|
||||
minutes: 0,
|
||||
seconds: 0,
|
||||
frames: 0,
|
||||
drop_frame: false
|
||||
},
|
||||
Some(vec![
|
||||
0x61, 0x01, 0x52, 0x96, 0x69, 0x52, 0x4F, 0x67, 0x00, 0x00, 0x72, 0xF4, 0xFC,
|
||||
0x80, 0x80, 0xFD, 0x80, 0x80, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00,
|
||||
0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
|
||||
0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA,
|
||||
0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00,
|
||||
0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0x73, 0x91, 0xE1, 0x00, 0x00, 0xC1,
|
||||
0x3F, 0xFF, 0x74, 0x00, 0x00, 0xAE, 0xB4
|
||||
])
|
||||
))
|
||||
);
|
||||
assert!(input.is_empty());
|
||||
|
||||
let mut input = b"00:00:00:00.0\tT52S524F67ZZ72F4QROO7391UC13FFF74ZZAEB4".as_slice();
|
||||
assert_eq!(
|
||||
caption(true)(b"00:00:00:00.0\tT52S524F67ZZ72F4QROO7391UC13FFF74ZZAEB4".as_ref()),
|
||||
Ok((
|
||||
b"".as_ref(),
|
||||
MccLine::Caption(
|
||||
TimeCode {
|
||||
hours: 0,
|
||||
minutes: 0,
|
||||
seconds: 0,
|
||||
frames: 0,
|
||||
drop_frame: false
|
||||
},
|
||||
Some(vec![
|
||||
0x61, 0x01, 0x52, 0x96, 0x69, 0x52, 0x4F, 0x67, 0x00, 0x00, 0x72, 0xF4,
|
||||
0xFC, 0x80, 0x80, 0xFD, 0x80, 0x80, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
|
||||
0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
|
||||
0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
|
||||
0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
|
||||
0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
|
||||
0x73, 0x91, 0xE1, 0x00, 0x00, 0xC1, 0x3F, 0xFF, 0x74, 0x00, 0x00, 0xAE,
|
||||
0xB4
|
||||
])
|
||||
),
|
||||
caption(true).parse_next(&mut input),
|
||||
Ok(MccLine::Caption(
|
||||
TimeCode {
|
||||
hours: 0,
|
||||
minutes: 0,
|
||||
seconds: 0,
|
||||
frames: 0,
|
||||
drop_frame: false
|
||||
},
|
||||
Some(vec![
|
||||
0x61, 0x01, 0x52, 0x96, 0x69, 0x52, 0x4F, 0x67, 0x00, 0x00, 0x72, 0xF4, 0xFC,
|
||||
0x80, 0x80, 0xFD, 0x80, 0x80, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00,
|
||||
0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
|
||||
0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA,
|
||||
0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00,
|
||||
0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0x73, 0x91, 0xE1, 0x00, 0x00, 0xC1,
|
||||
0x3F, 0xFF, 0x74, 0x00, 0x00, 0xAE, 0xB4
|
||||
])
|
||||
))
|
||||
);
|
||||
assert!(input.is_empty());
|
||||
|
||||
let mut input = b"00:00:00:00.0,9\tT52S524F67ZZ72F4QROO7391UC13FFF74ZZAEB4".as_slice();
|
||||
assert_eq!(
|
||||
caption(true)(b"00:00:00:00.0,9\tT52S524F67ZZ72F4QROO7391UC13FFF74ZZAEB4".as_ref()),
|
||||
Ok((
|
||||
b"".as_ref(),
|
||||
MccLine::Caption(
|
||||
TimeCode {
|
||||
hours: 0,
|
||||
minutes: 0,
|
||||
seconds: 0,
|
||||
frames: 0,
|
||||
drop_frame: false
|
||||
},
|
||||
Some(vec![
|
||||
0x61, 0x01, 0x52, 0x96, 0x69, 0x52, 0x4F, 0x67, 0x00, 0x00, 0x72, 0xF4,
|
||||
0xFC, 0x80, 0x80, 0xFD, 0x80, 0x80, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
|
||||
0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
|
||||
0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
|
||||
0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
|
||||
0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
|
||||
0x73, 0x91, 0xE1, 0x00, 0x00, 0xC1, 0x3F, 0xFF, 0x74, 0x00, 0x00, 0xAE,
|
||||
0xB4
|
||||
])
|
||||
),
|
||||
caption(true).parse_next(&mut input),
|
||||
Ok(MccLine::Caption(
|
||||
TimeCode {
|
||||
hours: 0,
|
||||
minutes: 0,
|
||||
seconds: 0,
|
||||
frames: 0,
|
||||
drop_frame: false
|
||||
},
|
||||
Some(vec![
|
||||
0x61, 0x01, 0x52, 0x96, 0x69, 0x52, 0x4F, 0x67, 0x00, 0x00, 0x72, 0xF4, 0xFC,
|
||||
0x80, 0x80, 0xFD, 0x80, 0x80, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00,
|
||||
0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
|
||||
0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA,
|
||||
0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00,
|
||||
0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0x73, 0x91, 0xE1, 0x00, 0x00, 0xC1,
|
||||
0x3F, 0xFF, 0x74, 0x00, 0x00, 0xAE, 0xB4
|
||||
])
|
||||
))
|
||||
);
|
||||
assert!(input.is_empty());
|
||||
|
||||
assert_eq!(
|
||||
caption(true)(b"Creation Date=\n".as_ref()),
|
||||
Err(nom::Err::Error(nom::error::Error::new(
|
||||
b"Creation Date=\n".as_ref(),
|
||||
nom::error::ErrorKind::MapRes
|
||||
))),
|
||||
);
|
||||
let mut input = b"Creation Date=\n".as_slice();
|
||||
assert!(caption(true).parse_next(&mut input).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use nom::IResult;
|
||||
use winnow::{error::StrContext, PResult, Parser};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct TimeCode {
|
||||
|
@ -18,66 +18,62 @@ pub struct TimeCode {
|
|||
}
|
||||
|
||||
/// Parser for parsing a run of ASCII, decimal digits and converting them into a `u32`
|
||||
pub fn digits(s: &[u8]) -> IResult<&[u8], u32> {
|
||||
use nom::bytes::complete::take_while;
|
||||
use nom::character::is_digit;
|
||||
use nom::combinator::map_res;
|
||||
pub fn digits(s: &mut &[u8]) -> PResult<u32> {
|
||||
use winnow::stream::AsChar;
|
||||
use winnow::token::take_while;
|
||||
|
||||
map_res(
|
||||
map_res(take_while(is_digit), std::str::from_utf8),
|
||||
|s: &str| s.parse::<u32>(),
|
||||
)(s)
|
||||
take_while(0.., AsChar::is_dec_digit)
|
||||
.try_map(std::str::from_utf8)
|
||||
.try_map(|s: &str| s.parse::<u32>())
|
||||
.parse_next(s)
|
||||
}
|
||||
|
||||
/// Parser for a run of decimal digits, that converts them into a `u32` and checks if the result is
|
||||
/// in the allowed range.
|
||||
pub fn digits_range<R: std::ops::RangeBounds<u32>>(
|
||||
pub fn digits_range<'a, R: std::ops::RangeBounds<u32>>(
|
||||
range: R,
|
||||
) -> impl FnMut(&[u8]) -> IResult<&[u8], u32> {
|
||||
use nom::combinator::verify;
|
||||
use nom::error::context;
|
||||
|
||||
move |s: &[u8]| context("digits out of range", verify(digits, |v| range.contains(v)))(s)
|
||||
) -> impl Parser<&'a [u8], u32, winnow::error::ContextError> {
|
||||
move |s: &mut &'a [u8]| {
|
||||
digits
|
||||
.verify(|v| range.contains(v))
|
||||
.context(StrContext::Label("digits out of range"))
|
||||
.parse_next(s)
|
||||
}
|
||||
}
|
||||
|
||||
/// Parser for a timecode in the form `hh:mm:ss:fs`
|
||||
pub fn timecode(s: &[u8]) -> IResult<&[u8], TimeCode> {
|
||||
use nom::character::complete::{char, one_of};
|
||||
use nom::combinator::map;
|
||||
use nom::error::context;
|
||||
use nom::sequence::tuple;
|
||||
pub fn timecode(s: &mut &[u8]) -> PResult<TimeCode> {
|
||||
use winnow::token::one_of;
|
||||
|
||||
context(
|
||||
"invalid timecode",
|
||||
map(
|
||||
tuple((
|
||||
digits,
|
||||
char(':'),
|
||||
digits_range(0..60),
|
||||
char(':'),
|
||||
digits_range(0..60),
|
||||
one_of(":.;,"),
|
||||
digits,
|
||||
)),
|
||||
|(hours, _, minutes, _, seconds, sep, frames)| TimeCode {
|
||||
hours,
|
||||
minutes,
|
||||
seconds,
|
||||
frames,
|
||||
drop_frame: sep == ';' || sep == ',',
|
||||
},
|
||||
),
|
||||
)(s)
|
||||
(
|
||||
digits,
|
||||
one_of(':'),
|
||||
digits_range(0..60),
|
||||
one_of(':'),
|
||||
digits_range(0..60),
|
||||
one_of(|c| b":.;,".contains(&c)),
|
||||
digits,
|
||||
)
|
||||
.map(|(hours, _, minutes, _, seconds, sep, frames)| TimeCode {
|
||||
hours,
|
||||
minutes,
|
||||
seconds,
|
||||
frames,
|
||||
drop_frame: sep == b';' || sep == b',',
|
||||
})
|
||||
.context(StrContext::Label("invalid timecode"))
|
||||
.parse_next(s)
|
||||
}
|
||||
|
||||
/// Parser that checks for EOF and optionally `\n` or `\r\n` before EOF
|
||||
pub fn end_of_line(s: &[u8]) -> IResult<&[u8], ()> {
|
||||
use nom::branch::alt;
|
||||
use nom::bytes::complete::tag;
|
||||
use nom::combinator::{eof, map, opt};
|
||||
use nom::sequence::pair;
|
||||
pub fn end_of_line(s: &mut &[u8]) -> PResult<()> {
|
||||
use winnow::combinator::alt;
|
||||
use winnow::combinator::{eof, opt};
|
||||
use winnow::token::literal;
|
||||
|
||||
map(pair(opt(alt((tag("\r\n"), tag("\n")))), eof), |_| ())(s)
|
||||
(opt(alt((literal("\r\n"), literal("\n")))), eof)
|
||||
.map(|_| ())
|
||||
.parse_next(s)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -86,54 +82,46 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_timecode() {
|
||||
let mut input = b"11:12:13;14".as_slice();
|
||||
assert_eq!(
|
||||
timecode(b"11:12:13;14".as_ref()),
|
||||
Ok((
|
||||
b"".as_ref(),
|
||||
TimeCode {
|
||||
hours: 11,
|
||||
minutes: 12,
|
||||
seconds: 13,
|
||||
frames: 14,
|
||||
drop_frame: true
|
||||
},
|
||||
))
|
||||
timecode(&mut input),
|
||||
Ok(TimeCode {
|
||||
hours: 11,
|
||||
minutes: 12,
|
||||
seconds: 13,
|
||||
frames: 14,
|
||||
drop_frame: true
|
||||
})
|
||||
);
|
||||
assert!(input.is_empty());
|
||||
|
||||
let mut input = b"11:12:13:14".as_slice();
|
||||
assert_eq!(
|
||||
timecode(b"11:12:13:14".as_ref()),
|
||||
Ok((
|
||||
b"".as_ref(),
|
||||
TimeCode {
|
||||
hours: 11,
|
||||
minutes: 12,
|
||||
seconds: 13,
|
||||
frames: 14,
|
||||
drop_frame: false
|
||||
},
|
||||
))
|
||||
timecode(&mut input),
|
||||
Ok(TimeCode {
|
||||
hours: 11,
|
||||
minutes: 12,
|
||||
seconds: 13,
|
||||
frames: 14,
|
||||
drop_frame: false
|
||||
})
|
||||
);
|
||||
assert!(input.is_empty());
|
||||
|
||||
let mut input = b"11:12:13:14abcd".as_slice();
|
||||
assert_eq!(
|
||||
timecode(b"11:12:13:14abcd".as_ref()),
|
||||
Ok((
|
||||
b"abcd".as_ref(),
|
||||
TimeCode {
|
||||
hours: 11,
|
||||
minutes: 12,
|
||||
seconds: 13,
|
||||
frames: 14,
|
||||
drop_frame: false
|
||||
},
|
||||
))
|
||||
timecode(&mut input),
|
||||
Ok(TimeCode {
|
||||
hours: 11,
|
||||
minutes: 12,
|
||||
seconds: 13,
|
||||
frames: 14,
|
||||
drop_frame: false
|
||||
})
|
||||
);
|
||||
assert_eq!(input, b"abcd");
|
||||
|
||||
assert_eq!(
|
||||
timecode(b"abcd11:12:13:14".as_ref()),
|
||||
Err(nom::Err::Error(nom::error::Error::new(
|
||||
b"abcd11:12:13:14".as_ref(),
|
||||
nom::error::ErrorKind::MapRes
|
||||
))),
|
||||
);
|
||||
let mut input = b"abcd11:12:13:14".as_slice();
|
||||
assert!(timecode(&mut input).is_err());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -123,7 +123,10 @@ fn parse_timecode(
|
|||
|
||||
impl State {
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn line(&mut self, drain: bool) -> Result<Option<SccLine>, (&[u8], nom::error::Error<&[u8]>)> {
|
||||
fn line(
|
||||
&mut self,
|
||||
drain: bool,
|
||||
) -> Result<Option<SccLine>, (&[u8], winnow::error::ContextError)> {
|
||||
let line = match self.reader.line_with_drain(drain) {
|
||||
None => {
|
||||
return Ok(None);
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use crate::parser_utils::{end_of_line, timecode, TimeCode};
|
||||
use nom::IResult;
|
||||
use winnow::{error::StrContext, PResult, Parser};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum SccLine {
|
||||
|
@ -30,44 +30,36 @@ pub struct SccParser {
|
|||
}
|
||||
|
||||
/// Parser for the SCC header
|
||||
fn header(s: &[u8]) -> IResult<&[u8], SccLine> {
|
||||
use nom::bytes::complete::tag;
|
||||
use nom::combinator::{map, opt};
|
||||
use nom::error::context;
|
||||
use nom::sequence::tuple;
|
||||
fn header(s: &mut &[u8]) -> PResult<SccLine> {
|
||||
use winnow::combinator::opt;
|
||||
use winnow::token::literal;
|
||||
|
||||
context(
|
||||
"invalid header",
|
||||
map(
|
||||
tuple((
|
||||
opt(tag(&[0xEFu8, 0xBBu8, 0xBFu8][..])),
|
||||
tag("Scenarist_SCC V1.0"),
|
||||
end_of_line,
|
||||
)),
|
||||
|_| SccLine::Header,
|
||||
),
|
||||
)(s)
|
||||
(
|
||||
opt(literal(&[0xEFu8, 0xBBu8, 0xBFu8][..])),
|
||||
literal("Scenarist_SCC V1.0"),
|
||||
end_of_line,
|
||||
)
|
||||
.map(|_| SccLine::Header)
|
||||
.context(StrContext::Label("invalid header"))
|
||||
.parse_next(s)
|
||||
}
|
||||
|
||||
/// Parser that accepts only an empty line
|
||||
fn empty_line(s: &[u8]) -> IResult<&[u8], SccLine> {
|
||||
use nom::combinator::map;
|
||||
use nom::error::context;
|
||||
|
||||
context("invalid empty line", map(end_of_line, |_| SccLine::Empty))(s)
|
||||
fn empty_line(s: &mut &[u8]) -> PResult<SccLine> {
|
||||
end_of_line
|
||||
.map(|_| SccLine::Empty)
|
||||
.context(StrContext::Label("invalid empty line"))
|
||||
.parse_next(s)
|
||||
}
|
||||
|
||||
/// A single SCC payload item. This is ASCII hex encoded bytes.
|
||||
/// It returns an tuple of `(u8, u8)` of the hex encoded bytes.
|
||||
fn scc_payload_item(s: &[u8]) -> IResult<&[u8], (u8, u8)> {
|
||||
use nom::bytes::complete::take_while_m_n;
|
||||
use nom::character::is_hex_digit;
|
||||
use nom::combinator::map;
|
||||
use nom::error::context;
|
||||
fn scc_payload_item(s: &mut &[u8]) -> PResult<(u8, u8)> {
|
||||
use winnow::stream::AsChar;
|
||||
use winnow::token::take_while;
|
||||
|
||||
context(
|
||||
"invalid SCC payload item",
|
||||
map(take_while_m_n(4, 4, is_hex_digit), |s: &[u8]| {
|
||||
take_while(4..=4, AsChar::is_hex_digit)
|
||||
.map(|s: &[u8]| {
|
||||
let hex_to_u8 = |v: u8| match v {
|
||||
v if v.is_ascii_digit() => v - b'0',
|
||||
v if (b'A'..=b'F').contains(&v) => 10 + v - b'A',
|
||||
|
@ -79,50 +71,47 @@ fn scc_payload_item(s: &[u8]) -> IResult<&[u8], (u8, u8)> {
|
|||
let val2 = (hex_to_u8(s[2]) << 4) | hex_to_u8(s[3]);
|
||||
|
||||
(val1, val2)
|
||||
}),
|
||||
)(s)
|
||||
})
|
||||
.context(StrContext::Label("invalid SCC payload item"))
|
||||
.parse_next(s)
|
||||
}
|
||||
|
||||
/// Parser for the whole SCC payload with conversion to the underlying byte values.
|
||||
fn scc_payload(s: &[u8]) -> IResult<&[u8], Vec<u8>> {
|
||||
use nom::branch::alt;
|
||||
use nom::bytes::complete::tag;
|
||||
use nom::combinator::map;
|
||||
use nom::error::context;
|
||||
use nom::multi::fold_many1;
|
||||
use nom::sequence::pair;
|
||||
use nom::Parser;
|
||||
fn scc_payload(s: &mut &[u8]) -> PResult<Vec<u8>> {
|
||||
use winnow::combinator::{alt, repeat};
|
||||
use winnow::token::literal;
|
||||
|
||||
let parse_item = map(
|
||||
pair(scc_payload_item, alt((tag(" ").map(|_| ()), end_of_line))),
|
||||
|(item, _)| item,
|
||||
);
|
||||
let parse_item = (
|
||||
scc_payload_item,
|
||||
alt((literal(" ").map(|_| ()), end_of_line)),
|
||||
)
|
||||
.map(|(item, _)| item);
|
||||
|
||||
context(
|
||||
"invalid SCC payload",
|
||||
fold_many1(parse_item, Vec::new, |mut acc: Vec<_>, item| {
|
||||
repeat(1.., parse_item)
|
||||
.fold(Vec::new, |mut acc: Vec<_>, item| {
|
||||
acc.push(item.0);
|
||||
acc.push(item.1);
|
||||
acc
|
||||
}),
|
||||
)(s)
|
||||
})
|
||||
.context(StrContext::Label("invalid SCC payload"))
|
||||
.parse_next(s)
|
||||
}
|
||||
|
||||
/// Parser for a SCC caption line in the form `timecode\tpayload`.
|
||||
fn caption(s: &[u8]) -> IResult<&[u8], SccLine> {
|
||||
use nom::bytes::complete::tag;
|
||||
use nom::character::complete::multispace0;
|
||||
use nom::combinator::map;
|
||||
use nom::error::context;
|
||||
use nom::sequence::tuple;
|
||||
fn caption(s: &mut &[u8]) -> PResult<SccLine> {
|
||||
use winnow::ascii::multispace0;
|
||||
use winnow::token::literal;
|
||||
|
||||
context(
|
||||
"invalid SCC caption line",
|
||||
map(
|
||||
tuple((timecode, tag("\t"), multispace0, scc_payload, end_of_line)),
|
||||
|(tc, _, _, value, _)| SccLine::Caption(tc, value),
|
||||
),
|
||||
)(s)
|
||||
(
|
||||
timecode,
|
||||
literal("\t"),
|
||||
multispace0,
|
||||
scc_payload,
|
||||
end_of_line,
|
||||
)
|
||||
.map(|(tc, _, _, value, _)| SccLine::Caption(tc, value))
|
||||
.context(StrContext::Label("invalid SCC caption line"))
|
||||
.parse_next(s)
|
||||
}
|
||||
|
||||
/// SCC parser the parses line-by-line and keeps track of the current state in the file.
|
||||
|
@ -143,37 +132,36 @@ impl SccParser {
|
|||
self.state = State::Header;
|
||||
}
|
||||
|
||||
pub fn parse_line<'a>(
|
||||
&mut self,
|
||||
line: &'a [u8],
|
||||
) -> Result<SccLine, nom::error::Error<&'a [u8]>> {
|
||||
use nom::branch::alt;
|
||||
pub fn parse_line(&mut self, mut line: &[u8]) -> Result<SccLine, winnow::error::ContextError> {
|
||||
use winnow::combinator::alt;
|
||||
|
||||
match self.state {
|
||||
State::Header => header(line)
|
||||
State::Header => header(&mut line)
|
||||
.map(|v| {
|
||||
self.state = State::Empty;
|
||||
v.1
|
||||
v
|
||||
})
|
||||
.map_err(|err| match err {
|
||||
nom::Err::Incomplete(_) => unreachable!(),
|
||||
nom::Err::Error(e) | nom::Err::Failure(e) => e,
|
||||
winnow::error::ErrMode::Incomplete(_) => unreachable!(),
|
||||
winnow::error::ErrMode::Backtrack(e) | winnow::error::ErrMode::Cut(e) => e,
|
||||
}),
|
||||
State::Empty => empty_line(line)
|
||||
State::Empty => empty_line(&mut line)
|
||||
.map(|v| {
|
||||
self.state = State::CaptionOrEmpty;
|
||||
v.1
|
||||
v
|
||||
})
|
||||
.map_err(|err| match err {
|
||||
nom::Err::Incomplete(_) => unreachable!(),
|
||||
nom::Err::Error(e) | nom::Err::Failure(e) => e,
|
||||
}),
|
||||
State::CaptionOrEmpty => alt((caption, empty_line))(line)
|
||||
.map(|v| v.1)
|
||||
.map_err(|err| match err {
|
||||
nom::Err::Incomplete(_) => unreachable!(),
|
||||
nom::Err::Error(e) | nom::Err::Failure(e) => e,
|
||||
winnow::error::ErrMode::Incomplete(_) => unreachable!(),
|
||||
winnow::error::ErrMode::Backtrack(e) | winnow::error::ErrMode::Cut(e) => e,
|
||||
}),
|
||||
State::CaptionOrEmpty => {
|
||||
alt((caption, empty_line))
|
||||
.parse_next(&mut line)
|
||||
.map_err(|err| match err {
|
||||
winnow::error::ErrMode::Incomplete(_) => unreachable!(),
|
||||
winnow::error::ErrMode::Backtrack(e) | winnow::error::ErrMode::Cut(e) => e,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -184,149 +172,130 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_header() {
|
||||
assert_eq!(
|
||||
header(b"Scenarist_SCC V1.0".as_ref()),
|
||||
Ok((b"".as_ref(), SccLine::Header,))
|
||||
);
|
||||
let mut input = b"Scenarist_SCC V1.0".as_slice();
|
||||
assert_eq!(header(&mut input), Ok(SccLine::Header));
|
||||
assert!(input.is_empty());
|
||||
|
||||
assert_eq!(
|
||||
header(b"Scenarist_SCC V1.0\n".as_ref()),
|
||||
Ok((b"".as_ref(), SccLine::Header))
|
||||
);
|
||||
let mut input = b"Scenarist_SCC V1.0\n".as_slice();
|
||||
assert_eq!(header(&mut input), Ok(SccLine::Header));
|
||||
assert!(input.is_empty());
|
||||
|
||||
assert_eq!(
|
||||
header(b"Scenarist_SCC V1.0\r\n".as_ref()),
|
||||
Ok((b"".as_ref(), SccLine::Header))
|
||||
);
|
||||
let mut input = b"Scenarist_SCC V1.0\r\n".as_slice();
|
||||
assert_eq!(header(&mut input), Ok(SccLine::Header));
|
||||
assert!(input.is_empty());
|
||||
|
||||
assert_eq!(
|
||||
header(b"Scenarist_SCC V1.1".as_ref()),
|
||||
Err(nom::Err::Error(nom::error::Error::new(
|
||||
b"Scenarist_SCC V1.1".as_ref(),
|
||||
nom::error::ErrorKind::Tag
|
||||
))),
|
||||
);
|
||||
let mut input = b"Scenarist_SCC V1.1".as_slice();
|
||||
assert!(header(&mut input).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_line() {
|
||||
assert_eq!(empty_line(b"".as_ref()), Ok((b"".as_ref(), SccLine::Empty)));
|
||||
let mut input = b"".as_slice();
|
||||
assert_eq!(empty_line(&mut input), Ok(SccLine::Empty));
|
||||
assert!(input.is_empty());
|
||||
|
||||
assert_eq!(
|
||||
empty_line(b"\n".as_ref()),
|
||||
Ok((b"".as_ref(), SccLine::Empty))
|
||||
);
|
||||
let mut input = b"\n".as_slice();
|
||||
assert_eq!(empty_line(&mut input), Ok(SccLine::Empty));
|
||||
assert!(input.is_empty());
|
||||
|
||||
assert_eq!(
|
||||
empty_line(b"\r\n".as_ref()),
|
||||
Ok((b"".as_ref(), SccLine::Empty))
|
||||
);
|
||||
let mut input = b"\r\n".as_slice();
|
||||
assert_eq!(empty_line(&mut input), Ok(SccLine::Empty));
|
||||
assert!(input.is_empty());
|
||||
|
||||
assert_eq!(
|
||||
empty_line(b" \r\n".as_ref()),
|
||||
Err(nom::Err::Error(nom::error::Error::new(
|
||||
b" \r\n".as_ref(),
|
||||
nom::error::ErrorKind::Eof
|
||||
))),
|
||||
);
|
||||
let mut input = b" \r\n".as_slice();
|
||||
assert!(empty_line(&mut input).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_caption() {
|
||||
let mut input = b"01:02:53:14\t94ae 94ae 9420 9420 947a 947a 97a2 97a2 a820 68ef f26e 2068 ef6e 6be9 6e67 2029 942c 942c 8080 8080 942f 942f".as_slice();
|
||||
assert_eq!(
|
||||
caption(b"01:02:53:14\t94ae 94ae 9420 9420 947a 947a 97a2 97a2 a820 68ef f26e 2068 ef6e 6be9 6e67 2029 942c 942c 8080 8080 942f 942f".as_ref()),
|
||||
Ok((
|
||||
b"".as_ref(),
|
||||
SccLine::Caption(
|
||||
TimeCode {
|
||||
hours: 1,
|
||||
minutes: 2,
|
||||
seconds: 53,
|
||||
frames: 14,
|
||||
drop_frame: false
|
||||
},
|
||||
|
||||
vec![
|
||||
0x94, 0xae, 0x94, 0xae, 0x94, 0x20, 0x94, 0x20, 0x94, 0x7a, 0x94, 0x7a, 0x97, 0xa2,
|
||||
0x97, 0xa2, 0xa8, 0x20, 0x68, 0xef, 0xf2, 0x6e, 0x20, 0x68, 0xef, 0x6e, 0x6b, 0xe9,
|
||||
0x6e, 0x67, 0x20, 0x29, 0x94, 0x2c, 0x94, 0x2c, 0x80, 0x80, 0x80, 0x80, 0x94, 0x2f,
|
||||
0x94, 0x2f,
|
||||
]
|
||||
),
|
||||
caption(&mut input),
|
||||
Ok(SccLine::Caption(
|
||||
TimeCode {
|
||||
hours: 1,
|
||||
minutes: 2,
|
||||
seconds: 53,
|
||||
frames: 14,
|
||||
drop_frame: false
|
||||
},
|
||||
vec![
|
||||
0x94, 0xae, 0x94, 0xae, 0x94, 0x20, 0x94, 0x20, 0x94, 0x7a, 0x94, 0x7a, 0x97,
|
||||
0xa2, 0x97, 0xa2, 0xa8, 0x20, 0x68, 0xef, 0xf2, 0x6e, 0x20, 0x68, 0xef, 0x6e,
|
||||
0x6b, 0xe9, 0x6e, 0x67, 0x20, 0x29, 0x94, 0x2c, 0x94, 0x2c, 0x80, 0x80, 0x80,
|
||||
0x80, 0x94, 0x2f, 0x94, 0x2f,
|
||||
]
|
||||
))
|
||||
);
|
||||
assert!(input.is_empty());
|
||||
|
||||
let mut input = b"01:02:55;14 942c 942c".as_slice();
|
||||
assert_eq!(
|
||||
caption(b"01:02:55;14 942c 942c".as_ref()),
|
||||
Ok((
|
||||
b"".as_ref(),
|
||||
SccLine::Caption(
|
||||
TimeCode {
|
||||
hours: 1,
|
||||
minutes: 2,
|
||||
seconds: 55,
|
||||
frames: 14,
|
||||
drop_frame: true
|
||||
},
|
||||
vec![0x94, 0x2c, 0x94, 0x2c]
|
||||
),
|
||||
caption(&mut input),
|
||||
Ok(SccLine::Caption(
|
||||
TimeCode {
|
||||
hours: 1,
|
||||
minutes: 2,
|
||||
seconds: 55,
|
||||
frames: 14,
|
||||
drop_frame: true
|
||||
},
|
||||
vec![0x94, 0x2c, 0x94, 0x2c]
|
||||
))
|
||||
);
|
||||
assert!(input.is_empty());
|
||||
|
||||
let mut input = b"01:03:27:29 94ae 94ae 9420 9420 94f2 94f2 c845 d92c 2054 c845 5245 ae80 942c 942c 8080 8080 942f 942f".as_slice();
|
||||
assert_eq!(
|
||||
caption(b"01:03:27:29 94ae 94ae 9420 9420 94f2 94f2 c845 d92c 2054 c845 5245 ae80 942c 942c 8080 8080 942f 942f".as_ref()),
|
||||
Ok((
|
||||
b"".as_ref(),
|
||||
SccLine::Caption(
|
||||
TimeCode {
|
||||
hours: 1,
|
||||
minutes: 3,
|
||||
seconds: 27,
|
||||
frames: 29,
|
||||
drop_frame: false
|
||||
},
|
||||
vec![
|
||||
0x94, 0xae, 0x94, 0xae, 0x94, 0x20, 0x94, 0x20, 0x94, 0xf2, 0x94, 0xf2, 0xc8, 0x45,
|
||||
0xd9, 0x2c, 0x20, 0x54, 0xc8, 0x45, 0x52, 0x45, 0xae, 0x80, 0x94, 0x2c, 0x94, 0x2c,
|
||||
0x80, 0x80, 0x80, 0x80, 0x94, 0x2f, 0x94, 0x2f,
|
||||
]
|
||||
),
|
||||
caption(&mut input),
|
||||
Ok(SccLine::Caption(
|
||||
TimeCode {
|
||||
hours: 1,
|
||||
minutes: 3,
|
||||
seconds: 27,
|
||||
frames: 29,
|
||||
drop_frame: false
|
||||
},
|
||||
vec![
|
||||
0x94, 0xae, 0x94, 0xae, 0x94, 0x20, 0x94, 0x20, 0x94, 0xf2, 0x94, 0xf2, 0xc8,
|
||||
0x45, 0xd9, 0x2c, 0x20, 0x54, 0xc8, 0x45, 0x52, 0x45, 0xae, 0x80, 0x94, 0x2c,
|
||||
0x94, 0x2c, 0x80, 0x80, 0x80, 0x80, 0x94, 0x2f, 0x94, 0x2f,
|
||||
]
|
||||
))
|
||||
);
|
||||
assert!(input.is_empty());
|
||||
|
||||
let mut input = b"00:00:00;00\t942c 942c".as_slice();
|
||||
assert_eq!(
|
||||
caption(b"00:00:00;00\t942c 942c".as_ref()),
|
||||
Ok((
|
||||
b"".as_ref(),
|
||||
SccLine::Caption(
|
||||
TimeCode {
|
||||
hours: 0,
|
||||
minutes: 0,
|
||||
seconds: 00,
|
||||
frames: 00,
|
||||
drop_frame: true,
|
||||
},
|
||||
vec![0x94, 0x2c, 0x94, 0x2c],
|
||||
),
|
||||
caption(&mut input),
|
||||
Ok(SccLine::Caption(
|
||||
TimeCode {
|
||||
hours: 0,
|
||||
minutes: 0,
|
||||
seconds: 00,
|
||||
frames: 00,
|
||||
drop_frame: true,
|
||||
},
|
||||
vec![0x94, 0x2c, 0x94, 0x2c],
|
||||
))
|
||||
);
|
||||
assert!(input.is_empty());
|
||||
|
||||
let mut input = b"00:00:00;00\t942c 942c\r\n".as_slice();
|
||||
assert_eq!(
|
||||
caption(b"00:00:00;00\t942c 942c\r\n".as_ref()),
|
||||
Ok((
|
||||
b"".as_ref(),
|
||||
SccLine::Caption(
|
||||
TimeCode {
|
||||
hours: 0,
|
||||
minutes: 0,
|
||||
seconds: 00,
|
||||
frames: 00,
|
||||
drop_frame: true,
|
||||
},
|
||||
vec![0x94, 0x2c, 0x94, 0x2c],
|
||||
),
|
||||
caption(&mut input),
|
||||
Ok(SccLine::Caption(
|
||||
TimeCode {
|
||||
hours: 0,
|
||||
minutes: 0,
|
||||
seconds: 00,
|
||||
frames: 00,
|
||||
drop_frame: true,
|
||||
},
|
||||
vec![0x94, 0x2c, 0x94, 0x2c],
|
||||
))
|
||||
);
|
||||
assert!(input.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
Loading…
Reference in a new issue