use rsubs_lib::{SSAErrorKind, SSA}; const SIMPLE: &str = r"[Script Info] [V4+ Styles] Format: Name,Fontname,Fontsize,PrimaryColour,SecondaryColour,OutlineColour,BackColour,Bold,Italic,Underline,Strikeout,ScaleX,ScaleY,Spacing,Angle,BorderStyle,Outline,Shadow,Alignment,MarginL,MarginR,MarginV,Encoding Style: Default,Arial,25.5,&H00FFFFFF,&H00000000,&H00000000,&H00000000,0,0,0,0,120,120,0,0,1,1,1,2,0000,0000,0030,0 [Events] Format: Layer,Start,End,Style,Name,MarginL,MarginR,MarginV,Effect,Text Dialogue: 0,0:00:06.20,0:00:08.20,Default,,0000,0000,0000,,Lorem Ipsum1 Dialogue: 0,0:00:08.20,0:00:10.20,Default,,0000,0000,0000,,Lorem Ipsum2 Dialogue: 0,0:00:12.20,0:00:14.20,Default,,0000,0000,0000,,Lorem Ipsum3 Dialogue: 0,0:00:14.20,0:00:16.20,Default,,0000,0000,0000,,Lorem Ipsum4"; #[test] fn convert_simple_to_srt() { let expected = r"1 00:00:06,200 --> 00:00:08,200 Lorem Ipsum1 2 00:00:08,200 --> 00:00:10,200 Lorem Ipsum2 3 00:00:12,200 --> 00:00:14,200 Lorem Ipsum3 4 00:00:14,200 --> 00:00:16,200 Lorem Ipsum4" .replace('\n', "\r\n"); let ssa = SSA::parse(SIMPLE).unwrap(); assert_eq!(ssa.to_srt().to_string(), expected) } #[test] fn convert_simple_to_vtt() { let expected = r"WEBVTT 1 00:00:06.200 --> 00:00:08.200 Lorem Ipsum1 2 00:00:08.200 --> 00:00:10.200 Lorem Ipsum2 3 00:00:12.200 --> 00:00:14.200 Lorem Ipsum3 4 00:00:14.200 --> 00:00:16.200 Lorem Ipsum4"; let ssa = SSA::parse(SIMPLE).unwrap(); assert_eq!(ssa.to_vtt().to_string(), expected) } const STYLING_INLINE: &str = r"[Script Info] [V4+ Styles] Format: Name,Fontname,Fontsize,PrimaryColour,SecondaryColour,OutlineColour,BackColour,Bold,Italic,Underline,Strikeout,ScaleX,ScaleY,Spacing,Angle,BorderStyle,Outline,Shadow,Alignment,MarginL,MarginR,MarginV,Encoding Style: Default,Arial,25.5,&H00FFFFFF,&H00000000,&H00000000,&H00000000,0,0,0,0,120,120,0,0,1,1,1,2,0000,0000,0030,0 [Events] Format: Layer,Start,End,Style,Name,MarginL,MarginR,MarginV,Effect,Text Dialogue: 0,0:00:00.20,0:00:02.20,Default,,0000,0000,0000,,{\i1}Lorem Ipsum1{\i0} Dialogue: 0,0:00:02.20,0:00:04.20,Default,,0000,0000,0000,,{\u1}Lorem Ipsum2{\u0} Dialogue: 0,0:00:06.20,0:00:08.20,Default,,0000,0000,0000,,{\c1}Lorem Ipsum3{\c0} "; #[test] fn convert_styling_inline_to_srt() { let expected = r"1 00:00:00,200 --> 00:00:02,200 Lorem Ipsum1 2 00:00:02,200 --> 00:00:04,200 Lorem Ipsum2 3 00:00:06,200 --> 00:00:08,200 Lorem Ipsum3" .replace('\n', "\r\n"); let ssa = SSA::parse(STYLING_INLINE).unwrap(); assert_eq!(ssa.to_srt().to_string(), expected) } #[test] fn convert_styling_inline_to_vtt() { let expected = r"WEBVTT 1 00:00:00.200 --> 00:00:02.200 Lorem Ipsum1 2 00:00:02.200 --> 00:00:04.200 Lorem Ipsum2 3 00:00:06.200 --> 00:00:08.200 Lorem Ipsum3"; let ssa = SSA::parse(STYLING_INLINE).unwrap(); assert_eq!(ssa.to_vtt().to_string(), expected) } const STYLING_GLOBAL: &str = r"[Script Info] [V4+ Styles] Format: Name,Fontname,Fontsize,PrimaryColour,SecondaryColour,OutlineColour,BackColour,Bold,Italic,Underline,Strikeout,ScaleX,ScaleY,Spacing,Angle,BorderStyle,Outline,Shadow,Alignment,MarginL,MarginR,MarginV,Encoding Style: Default,Arial,25.5,&H00FFFFFF,&H00000000,&H00000000,&H00000000,-1,0,0,0,120,120,0,0,1,1,1,2,0000,0000,0030,0 [Events] Format: Layer,Start,End,Style,Name,MarginL,MarginR,MarginV,Effect,Text Dialogue: 0,0:00:00.20,0:00:02.20,Default,,0000,0000,0000,,{\i1}Lorem Ipsum1{\i0} Dialogue: 0,0:00:02.20,0:00:04.20,Default,,0000,0000,0000,,Lorem Ipsum2 Dialogue: 0,0:00:06.20,0:00:08.20,Default,,0000,0000,0000,,Lorem Ipsum3"; #[test] fn convert_styling_global_to_srt() { let expected = r"1 00:00:00,200 --> 00:00:02,200 Lorem Ipsum1 2 00:00:02,200 --> 00:00:04,200 Lorem Ipsum2 3 00:00:06,200 --> 00:00:08,200 Lorem Ipsum3" .replace('\n', "\r\n"); let ssa = SSA::parse(STYLING_GLOBAL).unwrap(); assert_eq!(ssa.to_srt().to_string(), expected) } #[test] fn convert_styling_global_to_vtt() { let expected = r"WEBVTT 1 00:00:00.200 --> 00:00:02.200 Lorem Ipsum1 2 00:00:02.200 --> 00:00:04.200 Lorem Ipsum2 3 00:00:06.200 --> 00:00:08.200 Lorem Ipsum3"; let ssa = SSA::parse(STYLING_GLOBAL).unwrap(); assert_eq!(ssa.to_vtt().to_string(), expected) } const MULTILINE: &str = r"[Script Info] [V4+ Styles] Format: Name,Fontname,Fontsize,PrimaryColour,SecondaryColour,OutlineColour,BackColour,Bold,Italic,Underline,Strikeout,ScaleX,ScaleY,Spacing,Angle,BorderStyle,Outline,Shadow,Alignment,MarginL,MarginR,MarginV,Encoding Style: Default,Arial,25.5,&H00FFFFFF,&H00000000,&H00000000,&H00000000,0,0,0,0,120,120,0,0,1,1,1,2,0000,0000,0030,0 [Events] Format: Layer,Start,End,Style,Name,MarginL,MarginR,MarginV,Effect,Text Dialogue: 0,0:00:00.20,0:00:02.20,Default,,0000,0000,0000,,Lorem Ipsum1\NLorem Ipsum2\NLorem Ipsum3 Dialogue: 0,0:00:02.20,0:00:04.20,Default,,0000,0000,0000,,Lorem Ipsum4"; #[test] fn convert_multiline_to_srt() { let expected = r"1 00:00:00,200 --> 00:00:02,200 Lorem Ipsum1 Lorem Ipsum2 Lorem Ipsum3 2 00:00:02,200 --> 00:00:04,200 Lorem Ipsum4" .replace('\n', "\r\n"); let ssa = SSA::parse(MULTILINE).unwrap(); assert_eq!(ssa.to_srt().to_string(), expected) } #[test] fn convert_multiline_to_vtt() { let expected = r"WEBVTT 1 00:00:00.200 --> 00:00:02.200 Lorem Ipsum1 Lorem Ipsum2 Lorem Ipsum3 2 00:00:02.200 --> 00:00:04.200 Lorem Ipsum4"; let ssa = SSA::parse(MULTILINE).unwrap(); assert_eq!(ssa.to_vtt().to_string(), expected) } #[test] fn serde() { let ssa = r#"[Script Info] [V4+ Styles] Format: Name,Fontname,Fontsize,PrimaryColour,SecondaryColour,OutlineColour,BackColour,Bold,Italic,Underline,Strikeout,ScaleX,ScaleY,Spacing,Angle,BorderStyle,Outline,Shadow,Alignment,MarginL,MarginR,MarginV,Encoding Style: Default,Arial,25.5,&H00FFFFFF,&H00000000,&H00000000,&H00000000,-1,0,0,0,120,120,0,0,1,1,1,2,0000,0000,0030,0 [Events] Format: Layer,Start,End,Style,Name,MarginL,MarginR,MarginV,Effect,Text Dialogue: 0,0:00:00.20,0:00:02.20,Default,,0000,0000,0000,,{\i1}Lorem Ipsum1{\i0} Dialogue: 0,0:00:02.20,0:00:04.20,Default,,0000,0000,0000,,{\c1}Lorem Ipsum2{\c0} "#; let deserialized = serde_json::to_value(SSA::parse(ssa).unwrap()).unwrap(); let _: SSA = serde_json::from_value(deserialized).unwrap(); } #[test] fn invalid_start_block() { let ssa = r#"[Events] Format: Layer,Start,End,Style,Name,MarginL,MarginR,MarginV,Effect,Text Dialogue: 0,0:00:00.20,0:00:02.20,Default,,0000,0000,0000,,{\i1}Lorem Ipsum1{\i0} Dialogue: 0,0:00:02.20,0:00:04.20,Default,,0000,0000,0000,,{\c1}Lorem Ipsum2{\c0} Dialogue: 0,0:00:06.20,0:00:08.20,Default,,0000,0000,0000,,Lorem Ipsum3 Dialogue: 0,0:00:08.20,0:00:10.20,Default,,0000,0000,0000,,Lorem Ipsum4 Dialogue: 0,0:00:12.20,0:00:14.20,Default,,0000,0000,0000,,Lorem Ipsum5 Dialogue: 0,0:00:14.20,0:00:16.20,Default,,0000,0000,0000,,Lorem Ipsum6 [Script Info] [V4+ Styles] Format: Name,Fontname,Fontsize,PrimaryColour,SecondaryColour,OutlineColour,BackColour,Bold,Italic,Underline,Strikeout,ScaleX,ScaleY,Spacing,Angle,BorderStyle,Outline,Shadow,Alignment,MarginL,MarginR,MarginV,Encoding Style: Default,Arial,25.5,&H00FFFFFF,&H00000000,&H00000000,&H00000000,-1,0,0,0,120,120,0,0,1,1,1,2,0000,0000,0030,0 Style: De2,Trebuchet MS,25.5,&H00FFFFFF,&H00000000,&H00000000,&H00000000,-1,0,0,0,120,120,0,0,1,1,1,2,0000,0000,0030,0 Style: De4,Trebuchet MS,25.5,&H00FFFFFF,&H00000000,&H00000000,&H00000000,-1,0,0,0,120,120,0,0,1,1,1,2,0000,0000,0030,0 "#; let err = SSA::parse(ssa).unwrap_err(); assert_eq!(err.line(), 1); assert!(matches!(err.kind(), SSAErrorKind::Invalid)) } #[test] fn empty_block() { let ssa = r#"[Script Info] [V4+ Styles] Format: Name,Fontname,Fontsize,PrimaryColour,SecondaryColour,OutlineColour,BackColour,Bold,Italic,Underline,Strikeout,ScaleX,ScaleY,Spacing,Angle,BorderStyle,Outline,Shadow,Alignment,MarginL,MarginR,MarginV,Encoding Style: Default,Arial,25.5,&H00FFFFFF,&H00000000,&H00000000,&H00000000,-1,0,0,0,120,120,0,0,1,1,1,2,0000,0000,0030,0 "#; let err = SSA::parse(ssa).unwrap_err(); assert_eq!(err.line(), 3); assert!(matches!(err.kind(), SSAErrorKind::EmptyBlock)) } #[test] fn info_missing_delimiter() { let ssa = r#"[Script Info] Collisions "#; let err = SSA::parse(ssa).unwrap_err(); assert_eq!(err.line(), 2); assert!(matches!(err.kind(), SSAErrorKind::Parse(_))) } #[test] fn styles_missing_header() { let ssa = r#"[Script Info] [V4+ Styles] Format: Name,Fontname,Fontsize,PrimaryColour,SecondaryColour,OutlineColour,Bold,Italic,Underline,Strikeout,ScaleX,ScaleY,Spacing,Angle,BorderStyle,Outline,Shadow,Alignment,MarginL,MarginR,MarginV,Encoding Style: Default,Arial,25.5,&H00FFFFFF,&H00000000,&H00000000,&H00000000,-1,0,0,0,120,120,0,0,1,1,1,2,0000,0000,0030,0 "#; let err = SSA::parse(ssa).unwrap_err(); assert_eq!(err.line(), 4); assert_eq!( err.kind(), &SSAErrorKind::MissingHeader("BackColour".to_string()) ) } #[test] fn styles_missing_field() { let ssa = r#"[Script Info] [V4+ Styles] Format: Name,Fontname,Fontsize,PrimaryColour,SecondaryColour,OutlineColour,BackColour,Bold,Italic,Underline,Strikeout,ScaleX,ScaleY,Spacing,Angle,BorderStyle,Outline,Shadow,Alignment,MarginL,MarginR,MarginV,Encoding Style: Default,25.5,&H00FFFFFF,&H00000000,&H00000000,&H00000000,-1,0,0,0,120,120,0,0,1,1,1,2,0000,0000,0030,0 "#; let err = SSA::parse(ssa).unwrap_err(); assert_eq!(err.line(), 5); assert!(matches!(err.kind(), SSAErrorKind::Parse(_))) } #[test] fn styles_invalid_colors() { let colors = [ "#00000000", "&H-1000000", "&H00-10000", "&H0000-100", "&H000000-1", ]; for color in colors { let err = SSA::parse(format!(r#"[Script Info] [V4+ Styles] Format: Name,Fontname,Fontsize,PrimaryColour,SecondaryColour,OutlineColour,BackColour,Bold,Italic,Underline,Strikeout,ScaleX,ScaleY,Spacing,Angle,BorderStyle,Outline,Shadow,Alignment,MarginL,MarginR,MarginV,Encoding Style: Default,Arial,25.5,&H00FFFFFF,&H00000000,{color},&H00000000,-1,0,0,0,120,120,0,0,1,1,1,2,0000,0000,0030,0 "#)).unwrap_err(); assert_eq!(err.line(), 5); assert!(matches!(err.kind(), SSAErrorKind::Parse(_))) } } #[test] fn events_missing_header() { let ssa = r#"[Script Info] [Events] Format: Layer,Start,End,Style,Name,MarginL,MarginR,MarginV,Effect Dialogue: 0,0:00:00.20,0:00:02.20,Default,,0000,0000,0000,,{\i1}Lorem Ipsum1{\i0} "#; let err = SSA::parse(ssa).unwrap_err(); assert_eq!(err.line(), 4); assert_eq!(err.kind(), &SSAErrorKind::MissingHeader("Text".to_string())) } #[test] fn events_missing_field() { let ssa = r#"[Script Info] [Events] Format: Layer,Start,End,Style,Name,MarginL,MarginR,MarginV,Effect,Text Dialogue: 0,0:00:00.20,0:00:02.20,Default,,0000,0000,,{\i1}Lorem Ipsum1{\i0} "#; let err = SSA::parse(ssa).unwrap_err(); assert_eq!(err.line(), 5); assert!(matches!(err.kind(), SSAErrorKind::Parse(_))) }