aspasia/substation/common/
parse.rs1use nom::{
2 branch::alt,
3 bytes::complete::{tag, tag_no_case, take_until},
4 character::complete::{anychar, char, i64, multispace0, space0},
5 combinator::{map, value},
6 multi::{many0, many_till},
7 sequence::{delimited, preceded, separated_pair, terminated, tuple},
8 IResult, Parser,
9};
10
11use crate::{
12 substation::{SubStationFont, SubStationGraphic},
13 Format, Moment,
14};
15
16#[derive(Clone, Debug)]
17pub(crate) enum SubStationSection {
18 ScriptInfo,
19 Styles,
20 Events,
21 Fonts,
22 Graphics,
23}
24
25pub(crate) fn parse_script_info_heading(input: &str) -> IResult<&str, &str> {
26 delimited(char('['), tag_no_case("Script Info"), char(']')).parse(input)
27}
28
29fn parse_script_type(input: &str) -> IResult<&str, Format> {
30 map(
31 preceded(
32 tuple((space0, tag_no_case("ScriptType"), space0, char(':'), space0)),
33 terminated(
34 alt((tag_no_case("v4.00+"), tag_no_case("v4.00"))),
35 multispace0,
36 ),
37 ),
38 |s: &str| match s.to_ascii_lowercase().as_str() {
39 "v4.00" => Format::Ssa,
40 _ => Format::Ass,
41 },
42 )
43 .parse(input)
44}
45
46pub(crate) fn parse_format(input: &str) -> IResult<&str, Format> {
47 map(many_till(anychar, parse_script_type), |(_, format)| format).parse(input)
48}
49
50fn parse_script_info_header(input: &str) -> IResult<&str, SubStationSection> {
51 value(SubStationSection::ScriptInfo, tag_no_case("Script Info")).parse(input)
52}
53
54fn parse_events_header(input: &str) -> IResult<&str, SubStationSection> {
55 value(SubStationSection::Events, tag_no_case("Events")).parse(input)
56}
57
58fn parse_graphics_header(input: &str) -> IResult<&str, SubStationSection> {
59 value(SubStationSection::Graphics, tag_no_case("Graphics")).parse(input)
60}
61
62fn parse_fonts_header(input: &str) -> IResult<&str, SubStationSection> {
63 value(SubStationSection::Fonts, tag_no_case("Fonts")).parse(input)
64}
65
66fn parse_styles_header(input: &str) -> IResult<&str, SubStationSection> {
67 value(
68 SubStationSection::Styles,
69 alt((tag_no_case("V4 Styles"), tag_no_case("V4+ Styles"))),
70 )
71 .parse(input)
72}
73
74pub(crate) fn parse_category_header(input: &str) -> IResult<&str, SubStationSection> {
75 delimited(
76 multispace0,
77 delimited(
78 char('['),
79 alt((
80 parse_events_header,
81 parse_styles_header,
82 parse_fonts_header,
83 parse_graphics_header,
84 parse_script_info_header,
85 )),
86 char(']'),
87 ),
88 multispace0,
89 )
90 .parse(input)
91}
92
93pub(crate) fn parse_reverse_bool(input: &str) -> IResult<&str, bool> {
94 alt((value(true, tag("-1")), value(false, char('0')))).parse(input)
95}
96
97fn parse_fontname(input: &str) -> IResult<&str, &str> {
98 preceded(
99 tuple((multispace0, tag("fontname:"), space0)),
100 take_until("\n"),
101 )
102 .parse(input)
103}
104
105fn parse_font(input: &str) -> IResult<&str, SubStationFont> {
106 map(
107 separated_pair(parse_fontname, multispace0, take_until("\n\n")),
108 |(fontname, data)| SubStationFont {
109 fontname: fontname.to_string(),
110 data: data.to_string(),
111 },
112 )
113 .parse(input)
114}
115
116pub(crate) fn parse_fonts(input: &str) -> IResult<&str, Vec<SubStationFont>> {
117 many0(parse_font).parse(input)
118}
119
120fn parse_filename(input: &str) -> IResult<&str, &str> {
121 preceded(
122 tuple((multispace0, tag("filename:"), space0)),
123 take_until("\n"),
124 )
125 .parse(input)
126}
127
128fn parse_graphic(input: &str) -> IResult<&str, SubStationGraphic> {
129 map(
130 separated_pair(parse_filename, multispace0, take_until("\n\n")),
131 |(filename, data)| SubStationGraphic {
132 filename: filename.to_string(),
133 data: data.to_string(),
134 },
135 )
136 .parse(input)
137}
138
139pub(crate) fn parse_graphics(input: &str) -> IResult<&str, Vec<SubStationGraphic>> {
140 many0(parse_graphic).parse(input)
141}
142
143pub(crate) fn parse_timestamp(input: &str) -> IResult<&str, Moment> {
144 map(
145 delimited(
146 space0,
147 tuple((
148 terminated(i64, char(':')),
149 i64,
150 delimited(char(':'), i64, char('.')),
151 i64,
152 )),
153 space0,
154 ),
155 |(h, m, s, cs)| Moment::from_timestamp(h, m, s, cs * 10),
156 )
157 .parse(input)
158}