diff --git a/examples/render_srt.rs b/examples/render_srt.rs index 29efa06..1a06bce 100644 --- a/examples/render_srt.rs +++ b/examples/render_srt.rs @@ -23,6 +23,7 @@ fn main() -> anyhow::Result<()> { ..Default::default() }, text: vec!["This is the first subtitle.".to_string()], + ..Default::default() }, SrtSubtitle { sequence: 2, @@ -38,6 +39,7 @@ fn main() -> anyhow::Result<()> { "This is the second subtitle.".to_string(), "Subtitle text can span multiple lines.".to_string(), ], + ..Default::default() }, ], }; @@ -59,6 +61,7 @@ fn main() -> anyhow::Result<()> { ..Default::default() }, text: vec!["This is the third subtitle.".to_string()], + ..Default::default() }); println!("Rendered srt:\n{}", subrip.render()); diff --git a/src/srt.rs b/src/srt.rs index 5a714dd..c1f7d7e 100644 --- a/src/srt.rs +++ b/src/srt.rs @@ -34,6 +34,7 @@ //! milliseconds: 0, //! }, //! text: vec!["Hello, world!".to_string()], +//! line_position: None, //! }, //! SrtSubtitle { //! sequence: 2, @@ -53,6 +54,7 @@ //! "This is a sample.".to_string(), //! "Thank you for your reading.".to_string() //! ], +//! line_position: None, //! }, //! ], //! }); @@ -96,6 +98,7 @@ use crate::ParseResult; /// milliseconds: 0, /// }, /// text: vec!["Hello, world!".to_string()], +/// line_position: None, /// } /// ], /// }; @@ -159,6 +162,7 @@ impl SubRip { /// milliseconds: 0, /// }, /// text: vec!["Hello, world!".to_string()], + /// line_position: None, /// } /// ], /// }; @@ -234,6 +238,7 @@ impl Iterator for SubRip { /// milliseconds: 0, /// }, /// text: vec!["Hello, world!".to_string()], +/// line_position: None, /// }; /// /// assert_eq!( @@ -259,6 +264,7 @@ impl Iterator for SubRip { /// ..Default::default() /// }, /// text: vec!["Hello, world!".to_string()], +/// ..Default::default() /// }; /// ``` #[derive(Debug, Clone, Eq, Hash)] @@ -271,6 +277,8 @@ pub struct SrtSubtitle { pub end: SrtTimestamp, /// The subtitle text. pub text: Vec, + /// The unofficial line position. + pub line_position: Option, } impl PartialEq for SrtSubtitle { @@ -308,6 +316,7 @@ impl Default for SrtSubtitle { start: SrtTimestamp::default(), end: SrtTimestamp::default(), text: vec![], + line_position: None, } } } @@ -427,6 +436,43 @@ impl Into for SrtTimestamp { } } +/// Unofficial line position settings. +#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct LinePosition { + /// X1 of the line position. + pub x1: u32, + /// X2 of the line position. + pub x2: u32, + /// Y1 of the line position. + pub y1: u32, + /// Y2 of the line position. + pub y2: u32, +} + +impl Default for LinePosition { + fn default() -> Self { + Self { + x1: 0, + x2: 0, + y1: 0, + y2: 0, + } + } +} + +impl Display for LinePosition { + fn fmt( + &self, + f: &mut Formatter<'_>, + ) -> std::fmt::Result { + write!( + f, + "X1:{} X2:{} Y1:{} Y2:{}", + self.x1, self.x2, self.y1, self.y2 + ) + } +} + #[cfg(test)] mod tests { use super::*; @@ -461,6 +507,7 @@ This is a test. milliseconds: 0, }, text: vec!["Hello, world!".to_string()], + line_position: None, }, SrtSubtitle { sequence: 2, @@ -477,6 +524,7 @@ This is a test. milliseconds: 0, }, text: vec!["This is a test.".to_string()], + line_position: None, }, ], }; @@ -505,6 +553,7 @@ This is a test. milliseconds: 0, }, text: vec!["Hello, world!".to_string()], + line_position: None, }], }; let expected = r#"1 @@ -530,6 +579,7 @@ Hello, world! milliseconds: 0, }, text: vec!["Hello, world!".to_string()], + line_position: None, }, SrtSubtitle { sequence: 2, @@ -546,6 +596,7 @@ Hello, world! milliseconds: 0, }, text: vec!["This is a test.".to_string()], + line_position: None, }, ], }; @@ -579,6 +630,7 @@ This is a test. milliseconds: 0, }, text: vec!["Hello, world!".to_string()], + line_position: None, }, SrtSubtitle { sequence: 2, @@ -595,6 +647,7 @@ This is a test. milliseconds: 0, }, text: vec!["This is a test.".to_string()], + line_position: None, }, ], }; @@ -618,6 +671,7 @@ This is a test. milliseconds: 0, }, text: vec!["Hello, world!".to_string()], + line_position: None, }) ); @@ -638,6 +692,7 @@ This is a test. milliseconds: 0, }, text: vec!["This is a test.".to_string()], + line_position: None, }) ); @@ -661,6 +716,7 @@ This is a test. milliseconds: 0, }, text: vec!["Hello, world!".to_string()], + line_position: None, }; let displayed = format!("{}", subtitle); let expected = "1\n00:00:01,000 --> 00:00:02,000\nHello, world!\n"; @@ -684,6 +740,7 @@ This is a test. "Hello, world!".to_string(), "This is the test.".to_string(), ], + line_position: None, }; let displayed = format!("{}", subtitle); let expected = "1\n00:00:01,000 --> 00:00:02,000\nHello, world!\nThis is the test.\n"; @@ -707,6 +764,7 @@ This is a test. milliseconds: 0, }, text: vec!["First".to_string()], + line_position: None, }; let subtitle2 = SrtSubtitle { sequence: 2, @@ -723,6 +781,7 @@ This is a test. milliseconds: 0, }, text: vec!["Second".to_string()], + line_position: None, }; assert!(subtitle1 < subtitle2); } diff --git a/src/str_parser.rs b/src/str_parser.rs index 0675ef8..03377c1 100644 --- a/src/str_parser.rs +++ b/src/str_parser.rs @@ -8,6 +8,7 @@ peg::parser! { use crate::srt::SrtTimestamp; use crate::srt::SubRip; use crate::srt::SrtSubtitle; + use crate::srt::LinePosition; /// Whitespace. rule whitespace() = [' ' | '\t'] @@ -58,13 +59,50 @@ peg::parser! { } } + pub(crate) rule line_position() -> LinePosition + = "X1:" x1:number() separator()+ + "X2:" x2:number() separator()+ + "Y1:" y1:number() separator()+ + "Y2:" y2:number() + { + LinePosition { + x1, + y1, + x2, + y2, + } + } + /// Single subtitle entry. pub(crate) rule subtitle() -> SrtSubtitle + = subtitle_with_line_position() / subtitle_without_line_position() + + rule subtitle_without_line_position() -> SrtSubtitle = sequence:number() separator() start:timestamp() separator()* "-->" separator()* end:timestamp() separator() text:multiline() { - SrtSubtitle { sequence, start, end, text } + SrtSubtitle { + sequence, + start, + end, + text, + line_position: None, + } + } + + rule subtitle_with_line_position() -> SrtSubtitle + = sequence:number() separator() + start:timestamp() separator()* "-->" separator()* end:timestamp() separator()+ line_position:line_position() separator() + text:multiline() + { + SrtSubtitle { + sequence, + start, + end, + text, + line_position: Some(line_position), + } } /// The entire SRT. @@ -162,6 +200,7 @@ mod test { milliseconds: 0, }, text: vec!["Hello, world!".to_string()], + line_position: None, }; assert_eq!( @@ -217,6 +256,36 @@ mod test { subtitle ); + // Allow unofficial line position setting. + assert_eq!( + srt_parser::subtitle( + "1\n00:00:00,000\n-->\n00:00:01,000 X1:1 X2:2 Y1:3 Y2:4\nHello, world!\n" + ) + .unwrap(), + SrtSubtitle { + sequence: 1, + start: SrtTimestamp { + hours: 0, + minutes: 0, + seconds: 0, + milliseconds: 0, + }, + end: SrtTimestamp { + hours: 0, + minutes: 0, + seconds: 1, + milliseconds: 0, + }, + text: vec!["Hello, world!".to_string()], + line_position: Some(LinePosition { + x1: 1, + x2: 2, + y1: 3, + y2: 4, + }), + } + ); + // Prohibit spaces or new lines in header. assert!(srt_parser::subtitle( "\n1\n00:00:00,000 --> 00:00:01,000\nHello, world!\n" @@ -260,6 +329,7 @@ mod test { milliseconds: 0, }, text: vec!["Hello, world!".to_string()], + line_position: None, }], }; @@ -318,6 +388,7 @@ Hello, world! milliseconds: 0, }, text: vec!["Hello, world!".to_string()], + line_position: None, }, SrtSubtitle { sequence: 2, @@ -334,6 +405,7 @@ Hello, world! milliseconds: 0, }, text: vec!["This is a test.".to_string()], + line_position: None, }, ], };