Skip to main content

ff_format/stream/
subtitle.rs

1//! Subtitle stream info and builder.
2
3use std::time::Duration;
4
5use crate::codec::SubtitleCodec;
6
7/// Information about a subtitle stream within a media file.
8///
9/// This struct contains all metadata needed to identify and categorize
10/// a subtitle stream, including codec, language, and forced flag.
11///
12/// # Construction
13///
14/// Use [`SubtitleStreamInfo::builder()`] for fluent construction:
15///
16/// ```
17/// use ff_format::stream::SubtitleStreamInfo;
18/// use ff_format::codec::SubtitleCodec;
19///
20/// let info = SubtitleStreamInfo::builder()
21///     .index(2)
22///     .codec(SubtitleCodec::Srt)
23///     .codec_name("srt")
24///     .language("eng")
25///     .build();
26/// ```
27#[derive(Debug, Clone)]
28pub struct SubtitleStreamInfo {
29    /// Stream index within the container
30    index: u32,
31    /// Subtitle codec
32    codec: SubtitleCodec,
33    /// Codec name as reported by the demuxer
34    codec_name: String,
35    /// Language code (e.g., "eng", "jpn")
36    language: Option<String>,
37    /// Stream title (e.g., "English (Forced)")
38    title: Option<String>,
39    /// Stream duration (if known)
40    duration: Option<Duration>,
41    /// Whether this is a forced subtitle track
42    forced: bool,
43}
44
45impl SubtitleStreamInfo {
46    /// Creates a new builder for constructing `SubtitleStreamInfo`.
47    #[must_use]
48    pub fn builder() -> SubtitleStreamInfoBuilder {
49        SubtitleStreamInfoBuilder::default()
50    }
51
52    /// Returns the stream index within the container.
53    #[must_use]
54    #[inline]
55    pub const fn index(&self) -> u32 {
56        self.index
57    }
58
59    /// Returns the subtitle codec.
60    #[must_use]
61    #[inline]
62    pub fn codec(&self) -> &SubtitleCodec {
63        &self.codec
64    }
65
66    /// Returns the codec name as reported by the demuxer.
67    #[must_use]
68    #[inline]
69    pub fn codec_name(&self) -> &str {
70        &self.codec_name
71    }
72
73    /// Returns the language code, if specified.
74    #[must_use]
75    #[inline]
76    pub fn language(&self) -> Option<&str> {
77        self.language.as_deref()
78    }
79
80    /// Returns the stream title, if specified.
81    #[must_use]
82    #[inline]
83    pub fn title(&self) -> Option<&str> {
84        self.title.as_deref()
85    }
86
87    /// Returns the stream duration, if known.
88    #[must_use]
89    #[inline]
90    pub const fn duration(&self) -> Option<Duration> {
91        self.duration
92    }
93
94    /// Returns `true` if this is a forced subtitle track.
95    #[must_use]
96    #[inline]
97    pub const fn is_forced(&self) -> bool {
98        self.forced
99    }
100
101    /// Returns `true` if the codec is text-based.
102    #[must_use]
103    #[inline]
104    pub fn is_text_based(&self) -> bool {
105        self.codec.is_text_based()
106    }
107}
108
109/// Builder for constructing `SubtitleStreamInfo`.
110#[derive(Debug, Clone)]
111pub struct SubtitleStreamInfoBuilder {
112    index: u32,
113    codec: SubtitleCodec,
114    codec_name: String,
115    language: Option<String>,
116    title: Option<String>,
117    duration: Option<Duration>,
118    forced: bool,
119}
120
121impl Default for SubtitleStreamInfoBuilder {
122    fn default() -> Self {
123        Self {
124            index: 0,
125            codec: SubtitleCodec::Other(String::new()),
126            codec_name: String::new(),
127            language: None,
128            title: None,
129            duration: None,
130            forced: false,
131        }
132    }
133}
134
135impl SubtitleStreamInfoBuilder {
136    /// Sets the stream index.
137    #[must_use]
138    pub fn index(mut self, index: u32) -> Self {
139        self.index = index;
140        self
141    }
142
143    /// Sets the subtitle codec.
144    #[must_use]
145    pub fn codec(mut self, codec: SubtitleCodec) -> Self {
146        self.codec = codec;
147        self
148    }
149
150    /// Sets the codec name string.
151    #[must_use]
152    pub fn codec_name(mut self, name: impl Into<String>) -> Self {
153        self.codec_name = name.into();
154        self
155    }
156
157    /// Sets the language code.
158    #[must_use]
159    pub fn language(mut self, lang: impl Into<String>) -> Self {
160        self.language = Some(lang.into());
161        self
162    }
163
164    /// Sets the stream title.
165    #[must_use]
166    pub fn title(mut self, title: impl Into<String>) -> Self {
167        self.title = Some(title.into());
168        self
169    }
170
171    /// Sets the stream duration.
172    #[must_use]
173    pub fn duration(mut self, duration: Duration) -> Self {
174        self.duration = Some(duration);
175        self
176    }
177
178    /// Sets the forced flag.
179    #[must_use]
180    pub fn forced(mut self, forced: bool) -> Self {
181        self.forced = forced;
182        self
183    }
184
185    /// Builds the `SubtitleStreamInfo`.
186    #[must_use]
187    pub fn build(self) -> SubtitleStreamInfo {
188        SubtitleStreamInfo {
189            index: self.index,
190            codec: self.codec,
191            codec_name: self.codec_name,
192            language: self.language,
193            title: self.title,
194            duration: self.duration,
195            forced: self.forced,
196        }
197    }
198}
199
200#[cfg(test)]
201mod tests {
202    use super::*;
203
204    #[test]
205    fn builder_should_store_all_fields() {
206        let info = SubtitleStreamInfo::builder()
207            .index(2)
208            .codec(SubtitleCodec::Srt)
209            .codec_name("srt")
210            .language("eng")
211            .title("English")
212            .duration(Duration::from_secs(120))
213            .forced(true)
214            .build();
215
216        assert_eq!(info.index(), 2);
217        assert_eq!(info.codec(), &SubtitleCodec::Srt);
218        assert_eq!(info.codec_name(), "srt");
219        assert_eq!(info.language(), Some("eng"));
220        assert_eq!(info.title(), Some("English"));
221        assert_eq!(info.duration(), Some(Duration::from_secs(120)));
222        assert!(info.is_forced());
223    }
224
225    #[test]
226    fn is_forced_should_default_to_false() {
227        let info = SubtitleStreamInfo::builder()
228            .codec(SubtitleCodec::Ass)
229            .build();
230        assert!(!info.is_forced());
231    }
232
233    #[test]
234    fn is_text_based_should_delegate_to_codec() {
235        let text = SubtitleStreamInfo::builder()
236            .codec(SubtitleCodec::Srt)
237            .build();
238        assert!(text.is_text_based());
239
240        let bitmap = SubtitleStreamInfo::builder()
241            .codec(SubtitleCodec::Hdmv)
242            .build();
243        assert!(!bitmap.is_text_based());
244    }
245
246    #[test]
247    fn optional_fields_should_default_to_none() {
248        let info = SubtitleStreamInfo::builder().build();
249        assert!(info.language().is_none());
250        assert!(info.title().is_none());
251        assert!(info.duration().is_none());
252    }
253}