1use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6pub type ContainerFormat = String;
8
9pub type CodecId = String;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
14pub enum CodecKind {
15 Video,
17 Audio,
19 Subtitle,
21}
22
23#[derive(Debug, Clone)]
35pub struct CodecMapping {
36 video: HashMap<ContainerFormat, Vec<CodecId>>,
38 audio: HashMap<ContainerFormat, Vec<CodecId>>,
40 subtitle: HashMap<ContainerFormat, Vec<CodecId>>,
42}
43
44impl Default for CodecMapping {
45 fn default() -> Self {
46 Self::new()
47 }
48}
49
50impl CodecMapping {
51 #[must_use]
53 pub fn new() -> Self {
54 let mut video: HashMap<ContainerFormat, Vec<CodecId>> = HashMap::new();
55 let mut audio: HashMap<ContainerFormat, Vec<CodecId>> = HashMap::new();
56 let mut subtitle: HashMap<ContainerFormat, Vec<CodecId>> = HashMap::new();
57
58 video.insert(
60 "mp4".into(),
61 vec![
62 "h264".into(),
63 "h265".into(),
64 "hevc".into(),
65 "av1".into(),
66 "mpeg4".into(),
67 ],
68 );
69 audio.insert(
70 "mp4".into(),
71 vec![
72 "aac".into(),
73 "mp3".into(),
74 "ac3".into(),
75 "eac3".into(),
76 "flac".into(),
77 ],
78 );
79 subtitle.insert("mp4".into(), vec!["mov_text".into(), "dvdsub".into()]);
80
81 video.insert(
83 "webm".into(),
84 vec!["vp8".into(), "vp9".into(), "av1".into()],
85 );
86 audio.insert("webm".into(), vec!["opus".into(), "vorbis".into()]);
87 subtitle.insert("webm".into(), vec!["webvtt".into()]);
88
89 video.insert(
91 "mkv".into(),
92 vec![
93 "h264".into(),
94 "h265".into(),
95 "hevc".into(),
96 "av1".into(),
97 "vp9".into(),
98 "vp8".into(),
99 "mpeg4".into(),
100 "theora".into(),
101 "ffv1".into(),
102 ],
103 );
104 audio.insert(
105 "mkv".into(),
106 vec![
107 "aac".into(),
108 "mp3".into(),
109 "opus".into(),
110 "vorbis".into(),
111 "flac".into(),
112 "ac3".into(),
113 "truehd".into(),
114 "dts".into(),
115 "pcm_s16le".into(),
116 ],
117 );
118 subtitle.insert(
119 "mkv".into(),
120 vec![
121 "srt".into(),
122 "ass".into(),
123 "ssa".into(),
124 "webvtt".into(),
125 "dvdsub".into(),
126 ],
127 );
128
129 video.insert(
131 "mov".into(),
132 vec![
133 "h264".into(),
134 "h265".into(),
135 "prores".into(),
136 "dnxhd".into(),
137 "av1".into(),
138 ],
139 );
140 audio.insert(
141 "mov".into(),
142 vec![
143 "aac".into(),
144 "pcm_s16le".into(),
145 "pcm_s24le".into(),
146 "mp3".into(),
147 ],
148 );
149 subtitle.insert("mov".into(), vec!["mov_text".into()]);
150
151 video.insert(
153 "avi".into(),
154 vec![
155 "h264".into(),
156 "mpeg4".into(),
157 "xvid".into(),
158 "divx".into(),
159 "wmv2".into(),
160 ],
161 );
162 audio.insert(
163 "avi".into(),
164 vec!["mp3".into(), "aac".into(), "pcm_s16le".into(), "ac3".into()],
165 );
166 subtitle.insert("avi".into(), vec![]);
167
168 video.insert(
170 "ts".into(),
171 vec!["h264".into(), "h265".into(), "mpeg2video".into()],
172 );
173 audio.insert("ts".into(), vec!["aac".into(), "mp3".into(), "ac3".into()]);
174 subtitle.insert("ts".into(), vec!["dvbsub".into()]);
175
176 video.insert("flv".into(), vec!["h264".into(), "flv1".into()]);
178 audio.insert("flv".into(), vec!["aac".into(), "mp3".into()]);
179 subtitle.insert("flv".into(), vec![]);
180
181 video.insert("ogg".into(), vec!["theora".into()]);
183 audio.insert(
184 "ogg".into(),
185 vec!["vorbis".into(), "opus".into(), "flac".into()],
186 );
187 subtitle.insert("ogg".into(), vec![]);
188
189 Self {
190 video,
191 audio,
192 subtitle,
193 }
194 }
195
196 #[must_use]
200 pub fn supported_codecs(&self, container: &str, kind: CodecKind) -> Vec<CodecId> {
201 let map = match kind {
202 CodecKind::Video => &self.video,
203 CodecKind::Audio => &self.audio,
204 CodecKind::Subtitle => &self.subtitle,
205 };
206 map.get(container).cloned().unwrap_or_default()
207 }
208
209 #[must_use]
211 pub fn is_compatible(&self, container: &str, codec: &str, kind: CodecKind) -> bool {
212 self.supported_codecs(container, kind)
213 .contains(&codec.to_string())
214 }
215
216 #[must_use]
218 pub fn known_containers(&self) -> Vec<ContainerFormat> {
219 let mut containers: std::collections::HashSet<&str> = std::collections::HashSet::new();
220 for k in self.video.keys() {
221 containers.insert(k.as_str());
222 }
223 for k in self.audio.keys() {
224 containers.insert(k.as_str());
225 }
226 let mut result: Vec<ContainerFormat> = containers.into_iter().map(String::from).collect();
227 result.sort_unstable();
228 result
229 }
230
231 #[must_use]
233 pub fn find_compatible_containers(
234 &self,
235 video_codec: &str,
236 audio_codec: &str,
237 ) -> Vec<ContainerFormat> {
238 let mut result = Vec::new();
239 for container in self.known_containers() {
240 let has_video = self.is_compatible(&container, video_codec, CodecKind::Video);
241 let has_audio = self.is_compatible(&container, audio_codec, CodecKind::Audio);
242 if has_video && has_audio {
243 result.push(container);
244 }
245 }
246 result
247 }
248
249 pub fn add_codec(
251 &mut self,
252 container: impl Into<ContainerFormat>,
253 codec: impl Into<CodecId>,
254 kind: CodecKind,
255 ) {
256 let map = match kind {
257 CodecKind::Video => &mut self.video,
258 CodecKind::Audio => &mut self.audio,
259 CodecKind::Subtitle => &mut self.subtitle,
260 };
261 map.entry(container.into()).or_default().push(codec.into());
262 }
263}
264
265#[cfg(test)]
266mod tests {
267 use super::*;
268
269 #[test]
270 fn test_mp4_video_codecs() {
271 let mapping = CodecMapping::default();
272 let codecs = mapping.supported_codecs("mp4", CodecKind::Video);
273 assert!(codecs.contains(&"h264".to_string()));
274 assert!(codecs.contains(&"h265".to_string()));
275 }
276
277 #[test]
278 fn test_webm_audio_codecs() {
279 let mapping = CodecMapping::default();
280 let codecs = mapping.supported_codecs("webm", CodecKind::Audio);
281 assert!(codecs.contains(&"opus".to_string()));
282 assert!(codecs.contains(&"vorbis".to_string()));
283 assert!(!codecs.contains(&"aac".to_string()));
284 }
285
286 #[test]
287 fn test_is_compatible() {
288 let mapping = CodecMapping::default();
289 assert!(mapping.is_compatible("mp4", "h264", CodecKind::Video));
290 assert!(!mapping.is_compatible("webm", "h264", CodecKind::Video));
291 assert!(mapping.is_compatible("mkv", "opus", CodecKind::Audio));
292 }
293
294 #[test]
295 fn test_unknown_container_returns_empty() {
296 let mapping = CodecMapping::default();
297 let codecs = mapping.supported_codecs("unknown_fmt", CodecKind::Video);
298 assert!(codecs.is_empty());
299 }
300
301 #[test]
302 fn test_known_containers_non_empty() {
303 let mapping = CodecMapping::default();
304 let containers = mapping.known_containers();
305 assert!(!containers.is_empty());
306 assert!(containers.contains(&"mp4".to_string()));
307 assert!(containers.contains(&"webm".to_string()));
308 assert!(containers.contains(&"mkv".to_string()));
309 }
310
311 #[test]
312 fn test_find_compatible_containers() {
313 let mapping = CodecMapping::default();
314 let containers = mapping.find_compatible_containers("vp9", "opus");
315 assert!(containers.contains(&"webm".to_string()));
316 assert!(containers.contains(&"mkv".to_string()));
317 assert!(!containers.contains(&"mp4".to_string()));
318 }
319
320 #[test]
321 fn test_add_custom_codec() {
322 let mut mapping = CodecMapping::new();
323 mapping.add_codec("mp4", "custom_codec", CodecKind::Video);
324 assert!(mapping.is_compatible("mp4", "custom_codec", CodecKind::Video));
325 }
326
327 #[test]
328 fn test_subtitle_codecs_mkv() {
329 let mapping = CodecMapping::default();
330 let subs = mapping.supported_codecs("mkv", CodecKind::Subtitle);
331 assert!(subs.contains(&"ass".to_string()));
332 assert!(subs.contains(&"srt".to_string()));
333 }
334}