Skip to main content

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