Skip to main content

yt_dlp/model/types/
caption.rs

1//! Captions-related models.
2
3use std::fmt;
4use std::hash::{Hash, Hasher};
5
6use serde::{Deserialize, Serialize};
7
8/// Represents an automatic caption of a YouTube video.
9#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
10pub struct AutomaticCaption {
11    /// The extension of the caption file.
12    #[serde(rename = "ext")]
13    pub extension: Extension,
14    /// The URL of the caption file.
15    pub url: String,
16    /// The language of the caption file, e.g. 'English'.
17    pub name: Option<String>,
18}
19
20/// The available extensions for automatic caption files.
21#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
22#[serde(rename_all = "snake_case")]
23pub enum Extension {
24    /// The JSON extension.
25    Json,
26    /// The JSON3 extension.
27    Json3,
28    /// The Srv1 extension.
29    Srv1,
30    /// The Srv2 extension.
31    Srv2,
32    /// The Srv3 extension.
33    Srv3,
34    /// The Ttml extension.
35    Ttml,
36    /// The Vtt extension.
37    #[default]
38    Vtt,
39    /// The Srt extension.
40    Srt,
41    /// The ASS (Advanced SubStation Alpha) extension.
42    Ass,
43    /// The SSA (SubStation Alpha) extension.
44    Ssa,
45    /// An unknown extension not yet covered by the library.
46    #[serde(other)]
47    Unknown,
48}
49
50impl Extension {
51    /// Returns the extension as a string slice.
52    ///
53    /// # Returns
54    ///
55    /// A static string representation of this extension variant (e.g. `"vtt"`, `"srt"`).
56    pub fn as_str(&self) -> &'static str {
57        match self {
58            Extension::Json => "json",
59            Extension::Json3 => "json3",
60            Extension::Srv1 => "srv1",
61            Extension::Srv2 => "srv2",
62            Extension::Srv3 => "srv3",
63            Extension::Ttml => "ttml",
64            Extension::Vtt => "vtt",
65            Extension::Srt => "srt",
66            Extension::Ass => "ass",
67            Extension::Ssa => "ssa",
68            Extension::Unknown => "unknown",
69        }
70    }
71}
72
73impl fmt::Display for AutomaticCaption {
74    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75        write!(
76            f,
77            "AutomaticCaption(lang={}, ext={:?})",
78            self.name.as_deref().unwrap_or("unknown"),
79            self.extension
80        )
81    }
82}
83
84impl Hash for AutomaticCaption {
85    fn hash<H: Hasher>(&self, state: &mut H) {
86        self.url.hash(state);
87        self.name.hash(state);
88        std::mem::discriminant(&self.extension).hash(state);
89    }
90}
91
92impl fmt::Display for Extension {
93    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94        f.write_str(self.as_str())
95    }
96}
97
98// Implementation of Eq for Extension
99impl Eq for Extension {}
100
101// Implementation of Hash for Extension
102impl Hash for Extension {
103    fn hash<H: Hasher>(&self, state: &mut H) {
104        std::mem::discriminant(self).hash(state);
105    }
106}
107
108/// Represents a subtitle (user-uploaded or automatic caption) for a video.
109#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
110pub struct Subtitle {
111    /// The language code of the subtitle (e.g., 'en', 'fr', 'es').
112    pub language_code: Option<String>,
113    /// The full language name (e.g., 'English', 'French', 'Spanish').
114    pub language_name: Option<String>,
115    /// The URL of the subtitle file.
116    pub url: String,
117    /// The file extension/format of the subtitle.
118    #[serde(rename = "ext")]
119    pub extension: Extension,
120    /// Whether this is an automatically generated subtitle.
121    #[serde(default)]
122    pub is_automatic: bool,
123}
124
125impl Subtitle {
126    /// Creates a new [`Subtitle`] from an [`AutomaticCaption`], marking it as automatically generated.
127    ///
128    /// # Arguments
129    ///
130    /// * `caption` - The automatic caption to convert.
131    /// * `language_code` - The language code to assign (e.g. `"en"`, `"fr"`).
132    ///
133    /// # Returns
134    ///
135    /// A [`Subtitle`] with `is_automatic` set to `true`.
136    pub fn from_automatic_caption(caption: &AutomaticCaption, language_code: String) -> Self {
137        Self {
138            language_code: Some(language_code),
139            language_name: caption.name.clone(),
140            url: caption.url.clone(),
141            extension: caption.extension.clone(),
142            is_automatic: true,
143        }
144    }
145
146    /// Checks if this subtitle is in a specific format.
147    ///
148    /// # Arguments
149    ///
150    /// * `format` - The extension variant to compare against.
151    ///
152    /// # Returns
153    ///
154    /// `true` if the subtitle's extension matches the given format.
155    pub fn is_format(&self, format: &Extension) -> bool {
156        &self.extension == format
157    }
158
159    /// Returns the file extension as a string.
160    ///
161    /// # Returns
162    ///
163    /// The extension string (e.g. `"vtt"`, `"srt"`).
164    pub fn file_extension(&self) -> &str {
165        self.extension.as_str()
166    }
167}
168
169// Implementation of the Display trait for Subtitle
170impl fmt::Display for Subtitle {
171    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
172        write!(
173            f,
174            "Subtitle(lang={}, format={}, auto={})",
175            self.language_name
176                .as_deref()
177                .or(self.language_code.as_deref())
178                .unwrap_or("unknown"),
179            self.file_extension(),
180            self.is_automatic
181        )
182    }
183}
184
185// Implementation of Hash for Subtitle
186impl Hash for Subtitle {
187    fn hash<H: Hasher>(&self, state: &mut H) {
188        self.language_code.hash(state);
189        self.url.hash(state);
190        std::mem::discriminant(&self.extension).hash(state);
191    }
192}