mirror of
https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs.git
synced 2025-06-08 00:18:56 +00:00
closedcaption: Switch MCC parser from combine to nom
nom gives easier to understand compiler errors when something is wrong and compiles considerably faster.
This commit is contained in:
parent
0ff11b2cc1
commit
82c8a7b7e8
2 changed files with 387 additions and 403 deletions
|
@ -26,8 +26,9 @@ use std::cmp;
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
use std::sync::{Mutex, MutexGuard};
|
use std::sync::{Mutex, MutexGuard};
|
||||||
|
|
||||||
use super::parser::{MccLine, MccParser, TimeCode};
|
use super::parser::{MccLine, MccParser};
|
||||||
use crate::line_reader::LineReader;
|
use crate::line_reader::LineReader;
|
||||||
|
use crate::parser_utils::TimeCode;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref CAT: gst::DebugCategory = {
|
static ref CAT: gst::DebugCategory = {
|
||||||
|
@ -92,8 +93,6 @@ struct State {
|
||||||
need_flush_stop: bool,
|
need_flush_stop: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
type CombineError<'a> = combine::easy::ParseError<&'a [u8]>;
|
|
||||||
|
|
||||||
impl Default for State {
|
impl Default for State {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
@ -156,7 +155,10 @@ fn parse_timecode_rate(
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
fn get_line(&mut self, drain: bool) -> Result<Option<MccLine>, (&[u8], CombineError)> {
|
fn get_line(
|
||||||
|
&mut self,
|
||||||
|
drain: bool,
|
||||||
|
) -> Result<Option<MccLine>, (&[u8], nom::error::Error<&[u8]>)> {
|
||||||
let line = if self.replay_last_line {
|
let line = if self.replay_last_line {
|
||||||
self.replay_last_line = false;
|
self.replay_last_line = false;
|
||||||
&self.last_raw_line
|
&self.last_raw_line
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright (C) 2018 Sebastian Dröge <sebastian@centricular.com>
|
// Copyright (C) 2018,2020 Sebastian Dröge <sebastian@centricular.com>
|
||||||
//
|
//
|
||||||
// This library is free software; you can redistribute it and/or
|
// This library is free software; you can redistribute it and/or
|
||||||
// modify it under the terms of the GNU Library General Public
|
// modify it under the terms of the GNU Library General Public
|
||||||
|
@ -17,21 +17,8 @@
|
||||||
|
|
||||||
use either::Either;
|
use either::Either;
|
||||||
|
|
||||||
use combine::parser::byte::hex_digit;
|
use crate::parser_utils::{digits, digits_range, end_of_line, timecode, TimeCode};
|
||||||
use combine::parser::range::{range, take_while1};
|
use nom::IResult;
|
||||||
use combine::parser::repeat::skip_many;
|
|
||||||
use combine::parser::EasyParser;
|
|
||||||
use combine::{any, choice, eof, from_str, many1, one_of, optional, token, unexpected_any, value};
|
|
||||||
use combine::{ParseError, Parser, RangeStream};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct TimeCode {
|
|
||||||
pub hours: u32,
|
|
||||||
pub minutes: u32,
|
|
||||||
pub seconds: u32,
|
|
||||||
pub frames: u32,
|
|
||||||
pub drop_frame: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum MccLine<'a> {
|
pub enum MccLine<'a> {
|
||||||
|
@ -58,155 +45,111 @@ pub struct MccParser {
|
||||||
state: State,
|
state: State,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parser for parsing a run of ASCII, decimal digits and converting them into a `u32`
|
|
||||||
fn digits<'a, I: 'a>() -> impl Parser<I, Output = u32>
|
|
||||||
where
|
|
||||||
I: RangeStream<Token = u8, Range = &'a [u8]>,
|
|
||||||
I::Error: ParseError<I::Token, I::Range, I::Position>,
|
|
||||||
{
|
|
||||||
from_str(take_while1(|c: u8| c >= b'0' && c <= b'9').message("while parsing digits"))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parser for a run of decimal digits, that converts them into a `u32` and checks if the result is
|
|
||||||
/// in the allowed range.
|
|
||||||
fn digits_range<'a, I: 'a, R: std::ops::RangeBounds<u32>>(range: R) -> impl Parser<I, Output = u32>
|
|
||||||
where
|
|
||||||
I: RangeStream<Token = u8, Range = &'a [u8]>,
|
|
||||||
I::Error: ParseError<I::Token, I::Range, I::Position>,
|
|
||||||
{
|
|
||||||
digits().then(move |v| {
|
|
||||||
if range.contains(&v) {
|
|
||||||
value(v).left()
|
|
||||||
} else {
|
|
||||||
unexpected_any("digits out of range").right()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parser for a timecode in the form `hh:mm:ss:fs`
|
|
||||||
fn timecode<'a, I: 'a>() -> impl Parser<I, Output = TimeCode>
|
|
||||||
where
|
|
||||||
I: RangeStream<Token = u8, Range = &'a [u8]>,
|
|
||||||
I::Error: ParseError<I::Token, I::Range, I::Position>,
|
|
||||||
{
|
|
||||||
(
|
|
||||||
digits(),
|
|
||||||
token(b':'),
|
|
||||||
digits_range(0..60),
|
|
||||||
token(b':'),
|
|
||||||
digits_range(0..60),
|
|
||||||
one_of([b':', b'.', b';', b','].iter().cloned()),
|
|
||||||
digits(),
|
|
||||||
)
|
|
||||||
.map(|(hours, _, minutes, _, seconds, sep, frames)| TimeCode {
|
|
||||||
hours,
|
|
||||||
minutes,
|
|
||||||
seconds,
|
|
||||||
frames,
|
|
||||||
drop_frame: sep == b';' || sep == b',',
|
|
||||||
})
|
|
||||||
.message("while parsing timecode")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parser that checks for EOF and optionally `\n` or `\r\n` before EOF
|
|
||||||
fn end_of_line<'a, I: 'a>() -> impl Parser<I, Output = ()> + 'a
|
|
||||||
where
|
|
||||||
I: RangeStream<Token = u8, Range = &'a [u8]>,
|
|
||||||
I::Error: ParseError<I::Token, I::Range, I::Position>,
|
|
||||||
{
|
|
||||||
(
|
|
||||||
optional(choice((range(b"\n".as_ref()), range(b"\r\n".as_ref())))),
|
|
||||||
eof(),
|
|
||||||
)
|
|
||||||
.map(|_| ())
|
|
||||||
.message("while parsing end of line")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parser for the MCC header
|
/// Parser for the MCC header
|
||||||
fn header<'a, I: 'a>() -> impl Parser<I, Output = MccLine<'a>>
|
fn header(s: &[u8]) -> IResult<&[u8], MccLine> {
|
||||||
where
|
use nom::branch::alt;
|
||||||
I: RangeStream<Token = u8, Range = &'a [u8]>,
|
use nom::bytes::complete::tag;
|
||||||
I::Error: ParseError<I::Token, I::Range, I::Position>,
|
use nom::combinator::{map, opt};
|
||||||
{
|
use nom::error::context;
|
||||||
(
|
use nom::sequence::tuple;
|
||||||
optional(range(&[0xEFu8, 0xBBu8, 0xBFu8][..])),
|
|
||||||
range(b"File Format=MacCaption_MCC V".as_ref()),
|
|
||||||
choice!(range(b"1.0".as_ref()), range(b"2.0".as_ref())),
|
|
||||||
end_of_line(),
|
|
||||||
)
|
|
||||||
.map(|_| MccLine::Header)
|
|
||||||
.message("while parsing header")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parser that accepts only an empty line
|
context(
|
||||||
fn empty_line<'a, I: 'a>() -> impl Parser<I, Output = MccLine<'a>>
|
"invalid header",
|
||||||
where
|
map(
|
||||||
I: RangeStream<Token = u8, Range = &'a [u8]>,
|
tuple((
|
||||||
I::Error: ParseError<I::Token, I::Range, I::Position>,
|
opt(tag(&[0xEFu8, 0xBBu8, 0xBFu8][..])),
|
||||||
{
|
tag("File Format=MacCaption_MCC V"),
|
||||||
end_of_line()
|
alt((tag("1.0"), tag("2.0"))),
|
||||||
.map(|_| MccLine::Empty)
|
end_of_line,
|
||||||
.message("while parsing empty line")
|
)),
|
||||||
|
|_| MccLine::Header,
|
||||||
|
),
|
||||||
|
)(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parser for an MCC comment, i.e. a line starting with `//`. We don't return the actual comment
|
/// 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.
|
/// text as it's irrelevant for us.
|
||||||
fn comment<'a, I: 'a>() -> impl Parser<I, Output = MccLine<'a>>
|
fn comment(s: &[u8]) -> IResult<&[u8], MccLine> {
|
||||||
where
|
use nom::bytes::complete::tag;
|
||||||
I: RangeStream<Token = u8, Range = &'a [u8]>,
|
use nom::combinator::{map, rest};
|
||||||
I::Error: ParseError<I::Token, I::Range, I::Position>,
|
use nom::error::context;
|
||||||
{
|
use nom::sequence::tuple;
|
||||||
(token(b'/'), token(b'/'), skip_many(any()))
|
|
||||||
.map(|_| MccLine::Comment)
|
context(
|
||||||
.message("while parsing comment")
|
"invalid comment",
|
||||||
|
map(tuple((tag("//"), rest)), |_| MccLine::Comment),
|
||||||
|
)(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parser for the MCC UUID line.
|
/// Parser for the MCC UUID line.
|
||||||
fn uuid<'a, I: 'a>() -> impl Parser<I, Output = MccLine<'a>>
|
fn uuid(s: &[u8]) -> IResult<&[u8], MccLine> {
|
||||||
where
|
use nom::bytes::complete::{tag, take_while1};
|
||||||
I: RangeStream<Token = u8, Range = &'a [u8]>,
|
use nom::combinator::map;
|
||||||
I::Error: ParseError<I::Token, I::Range, I::Position>,
|
use nom::error::context;
|
||||||
{
|
use nom::sequence::tuple;
|
||||||
(
|
|
||||||
range(b"UUID=".as_ref()),
|
context(
|
||||||
|
"invalid uuid",
|
||||||
|
map(
|
||||||
|
tuple((
|
||||||
|
tag("UUID="),
|
||||||
take_while1(|b| b != b'\n' && b != b'\r'),
|
take_while1(|b| b != b'\n' && b != b'\r'),
|
||||||
end_of_line(),
|
end_of_line,
|
||||||
)
|
)),
|
||||||
.map(|(_, uuid, _)| MccLine::UUID(uuid))
|
|(_, uuid, _)| MccLine::UUID(uuid),
|
||||||
.message("while parsing UUID")
|
),
|
||||||
|
)(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parser for the MCC Time Code Rate line.
|
/// Parser for the MCC Time Code Rate line.
|
||||||
fn time_code_rate<'a, I: 'a>() -> impl Parser<I, Output = MccLine<'a>>
|
fn time_code_rate(s: &[u8]) -> IResult<&[u8], MccLine> {
|
||||||
where
|
use nom::bytes::complete::tag;
|
||||||
I: RangeStream<Token = u8, Range = &'a [u8]>,
|
use nom::combinator::{map, opt};
|
||||||
I::Error: ParseError<I::Token, I::Range, I::Position>,
|
use nom::error::context;
|
||||||
{
|
use nom::sequence::tuple;
|
||||||
(
|
|
||||||
range(b"Time Code Rate=".as_ref()),
|
context(
|
||||||
digits_range(1..256)
|
"invalid timecode rate",
|
||||||
.and(optional(range(b"DF".as_ref())))
|
map(
|
||||||
.map(|(v, df)| MccLine::TimeCodeRate(v as u8, df.is_some())),
|
tuple((
|
||||||
end_of_line(),
|
tag("Time Code Rate="),
|
||||||
)
|
digits_range(1..256),
|
||||||
.map(|(_, v, _)| v)
|
opt(tag("DF")),
|
||||||
.message("while parsing time code rate")
|
end_of_line,
|
||||||
|
)),
|
||||||
|
|(_, v, df, _)| MccLine::TimeCodeRate(v as u8, df.is_some()),
|
||||||
|
),
|
||||||
|
)(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parser for generic MCC metadata lines in the form `key=value`.
|
/// Parser for generic MCC metadata lines in the form `key=value`.
|
||||||
fn metadata<'a, I: 'a>() -> impl Parser<I, Output = MccLine<'a>>
|
fn metadata(s: &[u8]) -> IResult<&[u8], MccLine> {
|
||||||
where
|
use nom::bytes::complete::take_while1;
|
||||||
I: RangeStream<Token = u8, Range = &'a [u8]>,
|
use nom::character::complete::char;
|
||||||
I::Error: ParseError<I::Token, I::Range, I::Position>,
|
use nom::combinator::map;
|
||||||
{
|
use nom::error::context;
|
||||||
(
|
use nom::sequence::tuple;
|
||||||
|
|
||||||
|
context(
|
||||||
|
"invalid metadata",
|
||||||
|
map(
|
||||||
|
tuple((
|
||||||
take_while1(|b| b != b'='),
|
take_while1(|b| b != b'='),
|
||||||
token(b'='),
|
char('='),
|
||||||
take_while1(|b| b != b'\n' && b != b'\r'),
|
take_while1(|b| b != b'\n' && b != b'\r'),
|
||||||
end_of_line(),
|
end_of_line,
|
||||||
)
|
)),
|
||||||
.map(|(name, _, value, _)| MccLine::Metadata(name, value))
|
|(name, _, value, _)| MccLine::Metadata(name, value),
|
||||||
.message("while parsing metadata")
|
),
|
||||||
|
)(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)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A single MCC payload item. This is ASCII hex encoded bytes plus some single-character
|
/// A single MCC payload item. This is ASCII hex encoded bytes plus some single-character
|
||||||
|
@ -214,153 +157,153 @@ where
|
||||||
///
|
///
|
||||||
/// It returns an `Either` of the single hex encoded byte or the short-cut byte sequence as a
|
/// It returns an `Either` of the single hex encoded byte or the short-cut byte sequence as a
|
||||||
/// static byte slice.
|
/// static byte slice.
|
||||||
fn mcc_payload_item<'a, I: 'a>() -> impl Parser<I, Output = Either<u8, &'static [u8]>>
|
fn mcc_payload_item(s: &[u8]) -> IResult<&[u8], Either<u8, &'static [u8]>> {
|
||||||
where
|
use nom::branch::alt;
|
||||||
I: RangeStream<Token = u8, Range = &'a [u8]>,
|
use nom::bytes::complete::tag;
|
||||||
I::Error: ParseError<I::Token, I::Range, I::Position>,
|
use nom::bytes::complete::take_while_m_n;
|
||||||
{
|
use nom::character::is_hex_digit;
|
||||||
// FIXME: Switch back to the choice! macro once https://github.com/rust-lang/rust/issues/68666
|
use nom::combinator::map;
|
||||||
// is fixed and we depend on a new enough Rust version.
|
use nom::error::context;
|
||||||
choice((
|
|
||||||
token(b'G').map(|_| Either::Right([0xfau8, 0x00, 0x00].as_ref())),
|
context(
|
||||||
token(b'H').map(|_| Either::Right([0xfau8, 0x00, 0x00, 0xfa, 0x00, 0x00].as_ref())),
|
"invalid payload item",
|
||||||
token(b'I').map(|_| {
|
alt((
|
||||||
Either::Right([0xfau8, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00].as_ref())
|
map(tag("G"), |_| Either::Right([0xfa, 0x00, 0x00].as_ref())),
|
||||||
|
map(tag("H"), |_| {
|
||||||
|
Either::Right([0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00].as_ref())
|
||||||
}),
|
}),
|
||||||
token(b'J').map(|_| {
|
map(tag("I"), |_| {
|
||||||
|
Either::Right([0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00].as_ref())
|
||||||
|
}),
|
||||||
|
map(tag("J"), |_| {
|
||||||
Either::Right(
|
Either::Right(
|
||||||
[
|
[
|
||||||
0xfau8, 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(),
|
.as_ref(),
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
token(b'K').map(|_| {
|
map(tag("K"), |_| {
|
||||||
Either::Right(
|
Either::Right(
|
||||||
[
|
[
|
||||||
0xfau8, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa,
|
0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00,
|
||||||
0x00, 0x00,
|
0xfa, 0x00, 0x00,
|
||||||
]
|
]
|
||||||
.as_ref(),
|
.as_ref(),
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
token(b'L').map(|_| {
|
map(tag("L"), |_| {
|
||||||
Either::Right(
|
Either::Right(
|
||||||
[
|
[
|
||||||
0xfau8, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa,
|
0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00,
|
||||||
0x00, 0x00, 0xfa, 0x00, 0x00,
|
0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00,
|
||||||
]
|
]
|
||||||
.as_ref(),
|
.as_ref(),
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
token(b'M').map(|_| {
|
map(tag("M"), |_| {
|
||||||
Either::Right(
|
Either::Right(
|
||||||
[
|
[
|
||||||
0xfau8, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa,
|
0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00,
|
||||||
0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00,
|
0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00,
|
||||||
]
|
]
|
||||||
.as_ref(),
|
.as_ref(),
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
token(b'N').map(|_| {
|
map(tag("N"), |_| {
|
||||||
Either::Right(
|
Either::Right(
|
||||||
[
|
[
|
||||||
0xfau8, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa,
|
0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00,
|
||||||
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(),
|
.as_ref(),
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
token(b'O').map(|_| {
|
map(tag("O"), |_| {
|
||||||
Either::Right(
|
Either::Right(
|
||||||
[
|
[
|
||||||
0xfau8, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa,
|
0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00,
|
||||||
0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00,
|
0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00,
|
||||||
0x00,
|
0xfa, 0x00, 0x00,
|
||||||
]
|
]
|
||||||
.as_ref(),
|
.as_ref(),
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
token(b'P').map(|_| Either::Right([0xfbu8, 0x80, 0x80].as_ref())),
|
map(tag("P"), |_| Either::Right([0xfb, 0x80, 0x80].as_ref())),
|
||||||
token(b'Q').map(|_| Either::Right([0xfcu8, 0x80, 0x80].as_ref())),
|
map(tag("Q"), |_| Either::Right([0xfc, 0x80, 0x80].as_ref())),
|
||||||
token(b'R').map(|_| Either::Right([0xfdu8, 0x80, 0x80].as_ref())),
|
map(tag("R"), |_| Either::Right([0xfd, 0x80, 0x80].as_ref())),
|
||||||
token(b'S').map(|_| Either::Right([0x96u8, 0x69].as_ref())),
|
map(tag("S"), |_| Either::Right([0x96, 0x69].as_ref())),
|
||||||
token(b'T').map(|_| Either::Right([0x61u8, 0x01].as_ref())),
|
map(tag("T"), |_| Either::Right([0x61, 0x01].as_ref())),
|
||||||
token(b'U').map(|_| Either::Right([0xe1u8, 0x00, 0x00].as_ref())),
|
map(tag("U"), |_| Either::Right([0xe1, 0x00, 0x00].as_ref())),
|
||||||
token(b'Z').map(|_| Either::Left(0x00u8)),
|
map(tag("Z"), |_| Either::Right([0x00].as_ref())),
|
||||||
(hex_digit(), hex_digit()).map(|(u, l)| {
|
map(take_while_m_n(2, 2, is_hex_digit), |s: &[u8]| {
|
||||||
let hex_to_u8 = |v: u8| match v {
|
let hex_to_u8 = |v: u8| match v {
|
||||||
v if v >= b'0' && v <= b'9' => v - b'0',
|
v if v >= b'0' && v <= b'9' => v - b'0',
|
||||||
v if v >= b'A' && v <= b'F' => 10 + v - b'A',
|
v if v >= b'A' && v <= b'F' => 10 + v - b'A',
|
||||||
v if v >= b'a' && v <= b'f' => 10 + v - b'a',
|
v if v >= b'a' && v <= b'f' => 10 + v - b'a',
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
let val = (hex_to_u8(u) << 4) | hex_to_u8(l);
|
let val = (hex_to_u8(s[0]) << 4) | hex_to_u8(s[1]);
|
||||||
Either::Left(val)
|
Either::Left(val)
|
||||||
}),
|
}),
|
||||||
))
|
)),
|
||||||
.message("while parsing MCC payload")
|
)(s)
|
||||||
}
|
|
||||||
|
|
||||||
/// A wrapper around `Vec<u8>` that implements `Extend` in a special way. It can be
|
|
||||||
/// extended from an iterator of `Either<u8, &[u8]>` while the default `Extend` implementation for
|
|
||||||
/// `Vec` only allows to extend over vector items.
|
|
||||||
struct VecExtend(Vec<u8>);
|
|
||||||
|
|
||||||
impl Default for VecExtend {
|
|
||||||
fn default() -> Self {
|
|
||||||
VecExtend(Vec::with_capacity(256))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Extend<Either<u8, &'a [u8]>> for VecExtend {
|
|
||||||
fn extend<T>(&mut self, iter: T)
|
|
||||||
where
|
|
||||||
T: IntoIterator<Item = Either<u8, &'a [u8]>>,
|
|
||||||
{
|
|
||||||
for item in iter {
|
|
||||||
match item {
|
|
||||||
Either::Left(v) => self.0.push(v),
|
|
||||||
Either::Right(v) => self.0.extend_from_slice(v),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parser for the whole MCC payload with conversion to the underlying byte values.
|
/// Parser for the whole MCC payload with conversion to the underlying byte values.
|
||||||
fn mcc_payload<'a, I: 'a>() -> impl Parser<I, Output = Vec<u8>>
|
fn mcc_payload(s: &[u8]) -> IResult<&[u8], Vec<u8>> {
|
||||||
where
|
use nom::error::context;
|
||||||
I: RangeStream<Token = u8, Range = &'a [u8]>,
|
use nom::multi::fold_many1;
|
||||||
I::Error: ParseError<I::Token, I::Range, I::Position>,
|
|
||||||
{
|
context(
|
||||||
many1(mcc_payload_item())
|
"invalid MCC payload",
|
||||||
.map(|v: VecExtend| v.0)
|
fold_many1(mcc_payload_item, Vec::new(), |mut acc: Vec<_>, item| {
|
||||||
.message("while parsing MCC payloads")
|
match item {
|
||||||
|
Either::Left(val) => acc.push(val),
|
||||||
|
Either::Right(vals) => acc.extend_from_slice(vals),
|
||||||
|
}
|
||||||
|
acc
|
||||||
|
}),
|
||||||
|
)(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parser for a MCC caption line in the form `timecode\tpayload`.
|
/// Parser for a MCC caption line in the form `timecode\tpayload`.
|
||||||
fn caption<'a, I: 'a>(parse_payload: bool) -> impl Parser<I, Output = MccLine<'a>>
|
fn caption(parse_payload: bool) -> impl FnMut(&[u8]) -> IResult<&[u8], MccLine> {
|
||||||
where
|
use nom::bytes::complete::take_while;
|
||||||
I: RangeStream<Token = u8, Range = &'a [u8]>,
|
use nom::character::complete::{char, one_of};
|
||||||
I::Error: ParseError<I::Token, I::Range, I::Position>,
|
use nom::combinator::{map, opt};
|
||||||
{
|
use nom::error::context;
|
||||||
(
|
use nom::sequence::tuple;
|
||||||
timecode(),
|
|
||||||
optional((
|
fn parse(parse_payload: bool) -> impl FnMut(&[u8]) -> IResult<&[u8], Option<Vec<u8>>> {
|
||||||
token(b'.'),
|
move |s| {
|
||||||
one_of([b'0', b'1'].iter().cloned()),
|
|
||||||
optional((token(b','), digits())),
|
|
||||||
)),
|
|
||||||
token(b'\t'),
|
|
||||||
if parse_payload {
|
if parse_payload {
|
||||||
mcc_payload().map(Some).right()
|
map(mcc_payload, Some)(s)
|
||||||
} else {
|
} else {
|
||||||
skip_many(any()).map(|_| None).left()
|
map(take_while(|b| b != b'\n' && b != b'\r'), |_| None)(s)
|
||||||
},
|
}
|
||||||
end_of_line(),
|
}
|
||||||
)
|
}
|
||||||
.map(|(tc, _, _, value, _)| MccLine::Caption(tc, value))
|
|
||||||
.message("while parsing caption")
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// MCC parser the parses line-by-line and keeps track of the current state in the file.
|
/// MCC parser the parses line-by-line and keeps track of the current state in the file.
|
||||||
|
@ -385,46 +328,58 @@ impl MccParser {
|
||||||
&mut self,
|
&mut self,
|
||||||
line: &'a [u8],
|
line: &'a [u8],
|
||||||
parse_payload: bool,
|
parse_payload: bool,
|
||||||
) -> Result<MccLine<'a>, combine::easy::ParseError<&'a [u8]>> {
|
) -> Result<MccLine<'a>, nom::error::Error<&'a [u8]>> {
|
||||||
|
use nom::branch::alt;
|
||||||
|
|
||||||
match self.state {
|
match self.state {
|
||||||
State::Header => header()
|
State::Header => header(line)
|
||||||
.message("while in Header state")
|
|
||||||
.easy_parse(line)
|
|
||||||
.map(|v| {
|
.map(|v| {
|
||||||
self.state = State::EmptyAfterHeader;
|
self.state = State::EmptyAfterHeader;
|
||||||
v.0
|
v.1
|
||||||
|
})
|
||||||
|
.map_err(|err| match err {
|
||||||
|
nom::Err::Incomplete(_) => unreachable!(),
|
||||||
|
nom::Err::Error(e) | nom::Err::Failure(e) => e,
|
||||||
}),
|
}),
|
||||||
State::EmptyAfterHeader => empty_line()
|
State::EmptyAfterHeader => empty_line(line)
|
||||||
.message("while in EmptyAfterHeader state")
|
|
||||||
.easy_parse(line)
|
|
||||||
.map(|v| {
|
.map(|v| {
|
||||||
self.state = State::Comments;
|
self.state = State::Comments;
|
||||||
v.0
|
v.1
|
||||||
|
})
|
||||||
|
.map_err(|err| match err {
|
||||||
|
nom::Err::Incomplete(_) => unreachable!(),
|
||||||
|
nom::Err::Error(e) | nom::Err::Failure(e) => e,
|
||||||
}),
|
}),
|
||||||
State::Comments => choice!(empty_line(), comment())
|
State::Comments => alt((empty_line, comment))(line)
|
||||||
.message("while in Comments state")
|
|
||||||
.easy_parse(line)
|
|
||||||
.map(|v| {
|
.map(|v| {
|
||||||
if v.0 == MccLine::Empty {
|
if v.1 == MccLine::Empty {
|
||||||
self.state = State::Metadata;
|
self.state = State::Metadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
v.0
|
v.1
|
||||||
|
})
|
||||||
|
.map_err(|err| match err {
|
||||||
|
nom::Err::Incomplete(_) => unreachable!(),
|
||||||
|
nom::Err::Error(e) | nom::Err::Failure(e) => e,
|
||||||
}),
|
}),
|
||||||
State::Metadata => choice!(empty_line(), uuid(), time_code_rate(), metadata())
|
State::Metadata => alt((empty_line, uuid, time_code_rate, metadata))(line)
|
||||||
.message("while in Metadata state")
|
|
||||||
.easy_parse(line)
|
|
||||||
.map(|v| {
|
.map(|v| {
|
||||||
if v.0 == MccLine::Empty {
|
if v.1 == MccLine::Empty {
|
||||||
self.state = State::Captions;
|
self.state = State::Captions;
|
||||||
}
|
}
|
||||||
|
|
||||||
v.0
|
v.1
|
||||||
|
})
|
||||||
|
.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,
|
||||||
}),
|
}),
|
||||||
State::Captions => caption(parse_payload)
|
|
||||||
.message("while in Captions state")
|
|
||||||
.easy_parse(line)
|
|
||||||
.map(|v| v.0),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -432,14 +387,13 @@ impl MccParser {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use combine::error::UnexpectedParse;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_timecode() {
|
fn test_timecode() {
|
||||||
let mut parser = timecode();
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parser.parse(b"11:12:13;14".as_ref()),
|
timecode(b"11:12:13;14".as_ref()),
|
||||||
Ok((
|
Ok((
|
||||||
|
b"".as_ref(),
|
||||||
TimeCode {
|
TimeCode {
|
||||||
hours: 11,
|
hours: 11,
|
||||||
minutes: 12,
|
minutes: 12,
|
||||||
|
@ -447,13 +401,13 @@ mod tests {
|
||||||
frames: 14,
|
frames: 14,
|
||||||
drop_frame: true
|
drop_frame: true
|
||||||
},
|
},
|
||||||
b"".as_ref()
|
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parser.parse(b"11:12:13:14".as_ref()),
|
timecode(b"11:12:13:14".as_ref()),
|
||||||
Ok((
|
Ok((
|
||||||
|
b"".as_ref(),
|
||||||
TimeCode {
|
TimeCode {
|
||||||
hours: 11,
|
hours: 11,
|
||||||
minutes: 12,
|
minutes: 12,
|
||||||
|
@ -461,13 +415,13 @@ mod tests {
|
||||||
frames: 14,
|
frames: 14,
|
||||||
drop_frame: false
|
drop_frame: false
|
||||||
},
|
},
|
||||||
b"".as_ref()
|
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parser.parse(b"11:12:13:14abcd".as_ref()),
|
timecode(b"11:12:13:14abcd".as_ref()),
|
||||||
Ok((
|
Ok((
|
||||||
|
b"abcd".as_ref(),
|
||||||
TimeCode {
|
TimeCode {
|
||||||
hours: 11,
|
hours: 11,
|
||||||
minutes: 12,
|
minutes: 12,
|
||||||
|
@ -475,200 +429,226 @@ mod tests {
|
||||||
frames: 14,
|
frames: 14,
|
||||||
drop_frame: false
|
drop_frame: false
|
||||||
},
|
},
|
||||||
b"abcd".as_ref()
|
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parser.parse(b"abcd11:12:13:14".as_ref()),
|
timecode(b"abcd11:12:13:14".as_ref()),
|
||||||
Err(UnexpectedParse::Unexpected)
|
Err(nom::Err::Error(nom::error::Error::new(
|
||||||
|
b"abcd11:12:13:14".as_ref(),
|
||||||
|
nom::error::ErrorKind::MapRes
|
||||||
|
))),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_header() {
|
fn test_header() {
|
||||||
let mut parser = header();
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parser.parse(b"File Format=MacCaption_MCC V1.0".as_ref()),
|
header(b"File Format=MacCaption_MCC V1.0".as_ref()),
|
||||||
Ok((MccLine::Header, b"".as_ref()))
|
Ok((b"".as_ref(), MccLine::Header))
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parser.parse(b"File Format=MacCaption_MCC V1.0\n".as_ref()),
|
header(b"File Format=MacCaption_MCC V1.0\n".as_ref()),
|
||||||
Ok((MccLine::Header, b"".as_ref()))
|
Ok((b"".as_ref(), MccLine::Header))
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parser.parse(b"File Format=MacCaption_MCC V1.0\r\n".as_ref()),
|
header(b"File Format=MacCaption_MCC V1.0\r\n".as_ref()),
|
||||||
Ok((MccLine::Header, b"".as_ref()))
|
Ok((b"".as_ref(), MccLine::Header))
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parser.parse(b"File Format=MacCaption_MCC V2.0\r\n".as_ref()),
|
header(b"File Format=MacCaption_MCC V2.0\r\n".as_ref()),
|
||||||
Ok((MccLine::Header, b"".as_ref()))
|
Ok((b"".as_ref(), MccLine::Header))
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parser.parse(b"File Format=MacCaption_MCC V1.1".as_ref()),
|
header(b"File Format=MacCaption_MCC V1.1".as_ref()),
|
||||||
Err(UnexpectedParse::Unexpected)
|
Err(nom::Err::Error(nom::error::Error::new(
|
||||||
|
b"1.1".as_ref(),
|
||||||
|
nom::error::ErrorKind::Tag
|
||||||
|
))),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_empty_line() {
|
fn test_empty_line() {
|
||||||
let mut parser = empty_line();
|
assert_eq!(empty_line(b"".as_ref()), Ok((b"".as_ref(), MccLine::Empty)));
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parser.parse(b"".as_ref()),
|
empty_line(b"\n".as_ref()),
|
||||||
Ok((MccLine::Empty, b"".as_ref()))
|
Ok((b"".as_ref(), MccLine::Empty))
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parser.parse(b"\n".as_ref()),
|
empty_line(b"\r\n".as_ref()),
|
||||||
Ok((MccLine::Empty, b"".as_ref()))
|
Ok((b"".as_ref(), MccLine::Empty))
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parser.parse(b"\r\n".as_ref()),
|
empty_line(b" \r\n".as_ref()),
|
||||||
Ok((MccLine::Empty, b"".as_ref()))
|
Err(nom::Err::Error(nom::error::Error::new(
|
||||||
);
|
b" \r\n".as_ref(),
|
||||||
|
nom::error::ErrorKind::Eof
|
||||||
assert_eq!(
|
))),
|
||||||
parser.parse(b" \r\n".as_ref()),
|
|
||||||
Err(UnexpectedParse::Unexpected)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_comment() {
|
fn test_comment() {
|
||||||
let mut parser = comment();
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parser.parse(b"// blabla".as_ref()),
|
comment(b"// blabla".as_ref()),
|
||||||
Ok((MccLine::Comment, b"".as_ref()))
|
Ok((b"".as_ref(), MccLine::Comment))
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parser.parse(b"//\n".as_ref()),
|
comment(b"//\n".as_ref()),
|
||||||
Ok((MccLine::Comment, b"".as_ref()))
|
Ok((b"".as_ref(), MccLine::Comment))
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parser.parse(b"//".as_ref()),
|
comment(b"//".as_ref()),
|
||||||
Ok((MccLine::Comment, b"".as_ref()))
|
Ok((b"".as_ref(), MccLine::Comment))
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parser.parse(b" //".as_ref()),
|
comment(b" //".as_ref()),
|
||||||
Err(UnexpectedParse::Unexpected)
|
Err(nom::Err::Error(nom::error::Error::new(
|
||||||
|
b" //".as_ref(),
|
||||||
|
nom::error::ErrorKind::Tag
|
||||||
|
))),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_uuid() {
|
fn test_uuid() {
|
||||||
let mut parser = uuid();
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parser.parse(b"UUID=1234".as_ref()),
|
uuid(b"UUID=1234".as_ref()),
|
||||||
Ok((MccLine::UUID(b"1234".as_ref()), b"".as_ref()))
|
Ok((b"".as_ref(), MccLine::UUID(b"1234".as_ref())))
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parser.parse(b"UUID=1234\n".as_ref()),
|
uuid(b"UUID=1234\n".as_ref()),
|
||||||
Ok((MccLine::UUID(b"1234".as_ref()), b"".as_ref()))
|
Ok((b"".as_ref(), MccLine::UUID(b"1234".as_ref())))
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parser.parse(b"UUID=1234\r\n".as_ref()),
|
uuid(b"UUID=1234\r\n".as_ref()),
|
||||||
Ok((MccLine::UUID(b"1234".as_ref()), b"".as_ref()))
|
Ok((b"".as_ref(), MccLine::UUID(b"1234".as_ref())))
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parser.parse(b"UUID=".as_ref()),
|
uuid(b"UUID=".as_ref()),
|
||||||
Err(UnexpectedParse::Unexpected)
|
Err(nom::Err::Error(nom::error::Error::new(
|
||||||
|
b"".as_ref(),
|
||||||
|
nom::error::ErrorKind::TakeWhile1
|
||||||
|
))),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parser.parse(b"uUID=1234".as_ref()),
|
uuid(b"uUID=1234".as_ref()),
|
||||||
Err(UnexpectedParse::Unexpected)
|
Err(nom::Err::Error(nom::error::Error::new(
|
||||||
|
b"uUID=1234".as_ref(),
|
||||||
|
nom::error::ErrorKind::Tag,
|
||||||
|
))),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_time_code_rate() {
|
fn test_time_code_rate() {
|
||||||
let mut parser = time_code_rate();
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parser.parse(b"Time Code Rate=30".as_ref()),
|
time_code_rate(b"Time Code Rate=30".as_ref()),
|
||||||
Ok((MccLine::TimeCodeRate(30, false), b"".as_ref()))
|
Ok((b"".as_ref(), MccLine::TimeCodeRate(30, false)))
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parser.parse(b"Time Code Rate=30DF".as_ref()),
|
time_code_rate(b"Time Code Rate=30DF".as_ref()),
|
||||||
Ok((MccLine::TimeCodeRate(30, true), b"".as_ref()))
|
Ok((b"".as_ref(), MccLine::TimeCodeRate(30, true)))
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parser.parse(b"Time Code Rate=60".as_ref()),
|
time_code_rate(b"Time Code Rate=60".as_ref()),
|
||||||
Ok((MccLine::TimeCodeRate(60, false), b"".as_ref()))
|
Ok((b"".as_ref(), MccLine::TimeCodeRate(60, false)))
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parser.parse(b"Time Code Rate=17F".as_ref()),
|
time_code_rate(b"Time Code Rate=17F".as_ref()),
|
||||||
Err(UnexpectedParse::Unexpected)
|
Err(nom::Err::Error(nom::error::Error::new(
|
||||||
|
b"F".as_ref(),
|
||||||
|
nom::error::ErrorKind::Eof
|
||||||
|
))),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parser.parse(b"Time Code Rate=256".as_ref()),
|
time_code_rate(b"Time Code Rate=256".as_ref()),
|
||||||
Err(UnexpectedParse::Unexpected)
|
Err(nom::Err::Error(nom::error::Error::new(
|
||||||
|
b"256".as_ref(),
|
||||||
|
nom::error::ErrorKind::Verify
|
||||||
|
))),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_metadata() {
|
fn test_metadata() {
|
||||||
let mut parser = metadata();
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parser.parse(b"Creation Date=Thursday, June 04, 2015".as_ref()),
|
metadata(b"Creation Date=Thursday, June 04, 2015".as_ref()),
|
||||||
Ok((
|
Ok((
|
||||||
|
b"".as_ref(),
|
||||||
MccLine::Metadata(
|
MccLine::Metadata(
|
||||||
b"Creation Date".as_ref(),
|
b"Creation Date".as_ref(),
|
||||||
b"Thursday, June 04, 2015".as_ref()
|
b"Thursday, June 04, 2015".as_ref()
|
||||||
),
|
),
|
||||||
b"".as_ref()
|
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parser.parse(b"Creation Date= ".as_ref()),
|
metadata(b"Creation Date= ".as_ref()),
|
||||||
Ok((
|
Ok((
|
||||||
|
b"".as_ref(),
|
||||||
MccLine::Metadata(b"Creation Date".as_ref(), b" ".as_ref()),
|
MccLine::Metadata(b"Creation Date".as_ref(), b" ".as_ref()),
|
||||||
b"".as_ref()
|
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parser.parse(b"Creation Date".as_ref()),
|
metadata(b"Creation Date".as_ref()),
|
||||||
Err(UnexpectedParse::Eoi)
|
Err(nom::Err::Error(nom::error::Error::new(
|
||||||
|
b"".as_ref(),
|
||||||
|
nom::error::ErrorKind::Char
|
||||||
|
))),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parser.parse(b"Creation Date\n".as_ref()),
|
metadata(b"Creation Date\n".as_ref()),
|
||||||
Err(UnexpectedParse::Eoi)
|
Err(nom::Err::Error(nom::error::Error::new(
|
||||||
|
b"".as_ref(),
|
||||||
|
nom::error::ErrorKind::Char,
|
||||||
|
))),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parser.parse(b"Creation Date=".as_ref()),
|
metadata(b"Creation Date=".as_ref()),
|
||||||
Err(UnexpectedParse::Unexpected)
|
Err(nom::Err::Error(nom::error::Error::new(
|
||||||
|
b"".as_ref(),
|
||||||
|
nom::error::ErrorKind::TakeWhile1
|
||||||
|
))),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parser.parse(b"Creation Date=\n".as_ref()),
|
metadata(b"Creation Date=\n".as_ref()),
|
||||||
Err(UnexpectedParse::Unexpected)
|
Err(nom::Err::Error(nom::error::Error::new(
|
||||||
|
b"\n".as_ref(),
|
||||||
|
nom::error::ErrorKind::TakeWhile1
|
||||||
|
))),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_caption() {
|
fn test_caption() {
|
||||||
let mut parser = caption(true);
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parser.parse(b"00:00:00:00\tT52S524F67ZZ72F4QROO7391UC13FFF74ZZAEB4".as_ref()),
|
caption(true)(b"00:00:00:00\tT52S524F67ZZ72F4QROO7391UC13FFF74ZZAEB4".as_ref()),
|
||||||
Ok((
|
Ok((
|
||||||
|
b"".as_ref(),
|
||||||
MccLine::Caption(
|
MccLine::Caption(
|
||||||
TimeCode {
|
TimeCode {
|
||||||
hours: 0,
|
hours: 0,
|
||||||
|
@ -688,13 +668,13 @@ mod tests {
|
||||||
0xB4
|
0xB4
|
||||||
])
|
])
|
||||||
),
|
),
|
||||||
b"".as_ref()
|
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parser.parse(b"00:00:00:00.0\tT52S524F67ZZ72F4QROO7391UC13FFF74ZZAEB4".as_ref()),
|
caption(true)(b"00:00:00:00.0\tT52S524F67ZZ72F4QROO7391UC13FFF74ZZAEB4".as_ref()),
|
||||||
Ok((
|
Ok((
|
||||||
|
b"".as_ref(),
|
||||||
MccLine::Caption(
|
MccLine::Caption(
|
||||||
TimeCode {
|
TimeCode {
|
||||||
hours: 0,
|
hours: 0,
|
||||||
|
@ -714,13 +694,13 @@ mod tests {
|
||||||
0xB4
|
0xB4
|
||||||
])
|
])
|
||||||
),
|
),
|
||||||
b"".as_ref()
|
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parser.parse(b"00:00:00:00.0,9\tT52S524F67ZZ72F4QROO7391UC13FFF74ZZAEB4".as_ref()),
|
caption(true)(b"00:00:00:00.0,9\tT52S524F67ZZ72F4QROO7391UC13FFF74ZZAEB4".as_ref()),
|
||||||
Ok((
|
Ok((
|
||||||
|
b"".as_ref(),
|
||||||
MccLine::Caption(
|
MccLine::Caption(
|
||||||
TimeCode {
|
TimeCode {
|
||||||
hours: 0,
|
hours: 0,
|
||||||
|
@ -740,13 +720,15 @@ mod tests {
|
||||||
0xB4
|
0xB4
|
||||||
])
|
])
|
||||||
),
|
),
|
||||||
b"".as_ref()
|
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parser.parse(b"Creation Date=\n".as_ref()),
|
caption(true)(b"Creation Date=\n".as_ref()),
|
||||||
Err(UnexpectedParse::Unexpected)
|
Err(nom::Err::Error(nom::error::Error::new(
|
||||||
|
b"Creation Date=\n".as_ref(),
|
||||||
|
nom::error::ErrorKind::MapRes
|
||||||
|
))),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue