rsubs_lib/
srt.rs

1//! Implements helpers for `.srt`.
2//!
3//! It describes the [SRTFile] and [SRTLine] structs and
4//! provides the [parse] function.
5
6use serde::Deserialize;
7use serde::Serialize;
8
9use std::fmt::Display;
10use std::str;
11use time::format_description::BorrowedFormatItem;
12use time::macros::format_description;
13
14use crate::error;
15use time::Time;
16
17use super::ssa::SSA;
18use super::vtt::{VTTLine, VTT};
19
20const TIME_FORMAT: &[BorrowedFormatItem] =
21    format_description!("[hour]:[minute]:[second],[subsecond digits:3]");
22
23/// Contains a Vec<[SRTLine]>
24///
25/// The `.srt` format is relatively simple to parse and generally looks like :
26///```text
27/// 0
28/// 00:00:00,000 --> 00:00:02,000
29/// This is my text
30///
31/// 1
32/// 00:00:02,000 --> 00:00:04,000
33/// This is my second text
34/// ```
35#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
36pub struct SRT {
37    pub lines: Vec<SRTLine>,
38}
39
40/// Describes each line
41///
42/// Each line has a start and end [Time], a [String] text and an [i32] line number.
43#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
44pub struct SRTLine {
45    pub sequence_number: u32,
46    pub start: Time,
47    pub end: Time,
48    pub text: String,
49}
50
51impl SRT {
52    /// Parses the given [String] into a [SRTFile].
53    pub fn parse<S: AsRef<str>>(content: S) -> Result<SRT, SRTError> {
54        let mut line_num = 0;
55
56        let mut blocks = vec![vec![]];
57        for line in content.as_ref().lines() {
58            if line.trim().is_empty() {
59                if !blocks.last().unwrap().is_empty() {
60                    blocks.push(vec![])
61                }
62            } else {
63                blocks.last_mut().unwrap().push(line)
64            }
65        }
66        if blocks.last().is_some_and(|b| b.is_empty()) {
67            blocks.remove(blocks.len() - 1);
68        }
69
70        let mut lines = vec![];
71        for block in blocks {
72            line_num += 1;
73
74            let mut block_lines = block.into_iter();
75
76            // sequence number
77            let sequence_number = block_lines
78                .next()
79                .ok_or(SRTError::new(
80                    SRTErrorKind::Parse("invalid sequence number".to_string()),
81                    line_num,
82                ))?
83                .trim()
84                .parse::<u32>()
85                .map_err(|e| SRTError::new(SRTErrorKind::Parse(e.to_string()), line_num))?;
86            line_num += 1;
87            // start & end times
88            let (start, end) = {
89                let (start, end) = block_lines
90                    .next()
91                    .ok_or(SRTError::new(
92                        SRTErrorKind::Parse("invalid time range".to_string()),
93                        line_num,
94                    ))?
95                    .split_once("-->")
96                    .ok_or(SRTError::new(
97                        SRTErrorKind::Parse("invalid time range".to_string()),
98                        line_num,
99                    ))?;
100                let start_time = Time::parse(start.trim(), TIME_FORMAT)
101                    .map_err(|e| SRTError::new(SRTErrorKind::Parse(e.to_string()), line_num))?;
102                let end_time = Time::parse(end.trim(), TIME_FORMAT)
103                    .map_err(|e| SRTError::new(SRTErrorKind::Parse(e.to_string()), line_num))?;
104                (start_time, end_time)
105            };
106            line_num += 1;
107            line_num += block_lines.len();
108            // text
109            let text = block_lines.collect::<Vec<&str>>().join("\r\n");
110
111            lines.push(SRTLine {
112                sequence_number,
113                start,
114                end,
115                text,
116            })
117        }
118
119        Ok(SRT { lines })
120    }
121
122    /// Convert from [SRTFile] to [SSAFile] replacing `\r\n` to `\\N` since SSA/ASS is single line
123    pub fn to_ssa(&self) -> SSA {
124        self.to_vtt().to_ssa()
125    }
126    /// Convert from [SRTFile] to [VTTFile], WebVTT at its core is exactly the same as Subrip
127    pub fn to_vtt(&self) -> VTT {
128        VTT {
129            regions: vec![],
130            styles: vec![],
131            lines: self
132                .lines
133                .iter()
134                .map(|l| VTTLine {
135                    identifier: Some(l.sequence_number.to_string()),
136                    start: l.start,
137                    end: l.end,
138                    text: l.text.replace("\r\n", "\n"),
139                    ..Default::default()
140                })
141                .collect(),
142        }
143    }
144}
145
146impl Display for SRT {
147    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148        let mut blocks = vec![];
149
150        for line in &self.lines {
151            let mut block = vec![];
152
153            block.push(line.sequence_number.to_string());
154            block.push(format!(
155                "{} --> {}",
156                line.start.format(TIME_FORMAT).unwrap(),
157                line.end.format(TIME_FORMAT).unwrap()
158            ));
159            block.push(line.text.clone());
160
161            blocks.push(block)
162        }
163
164        write!(
165            f,
166            "{}",
167            blocks
168                .into_iter()
169                .map(|b| b.join("\r\n"))
170                .collect::<Vec<String>>()
171                .join("\r\n\r\n")
172        )
173    }
174}
175
176error! {
177    SRTError => SRTErrorKind {
178        Parse(String),
179    }
180}