rskit_media/encoding/
registry.rs1use std::collections::HashMap;
4use std::sync::Arc;
5
6use rskit_errors::{AppError, AppResult, ErrorCode};
7
8use crate::{
9 codec::{Codec, CodecKind},
10 executor::MediaExecutor,
11 format::Format,
12 probe::MediaProbe,
13 types::MediaType,
14};
15
16use super::defaults;
17
18pub type ExecutorFactory = Arc<dyn Fn() -> AppResult<Arc<dyn MediaExecutor>> + Send + Sync>;
20
21pub type ProbeFactory = Arc<dyn Fn() -> AppResult<Arc<dyn MediaProbe>> + Send + Sync>;
23
24#[derive(Debug, Clone)]
26pub struct CodecInfo {
27 pub id: Codec,
29 pub kind: CodecKind,
31 pub display_name: String,
33 pub ffmpeg_encoder: Option<String>,
35 pub ffmpeg_decoder: Option<String>,
37 pub compatible_formats: Vec<Format>,
39}
40
41#[derive(Debug, Clone)]
43pub struct FormatInfo {
44 pub id: Format,
46 pub extension: String,
48 pub mime_type: String,
50 pub is_container: bool,
52 pub supported_media_types: Vec<MediaType>,
54 pub default_video_codec: Option<Codec>,
56 pub default_audio_codec: Option<Codec>,
58}
59
60#[derive(Clone)]
62pub struct Registry {
63 codecs: HashMap<Codec, CodecInfo>,
64 formats: HashMap<Format, FormatInfo>,
65 executor_factories: HashMap<String, ExecutorFactory>,
66 probe_factories: HashMap<String, ProbeFactory>,
67}
68
69impl Default for Registry {
70 fn default() -> Self {
71 let mut reg = Self {
72 codecs: HashMap::new(),
73 formats: HashMap::new(),
74 executor_factories: HashMap::new(),
75 probe_factories: HashMap::new(),
76 };
77 defaults::load_defaults(&mut reg);
78 reg
79 }
80}
81
82impl Registry {
83 pub fn register_codec(&mut self, info: CodecInfo) {
85 self.codecs.insert(info.id.clone(), info);
86 }
87
88 pub fn register_format(&mut self, info: FormatInfo) {
90 self.formats.insert(info.id.clone(), info);
91 }
92
93 pub fn register_executor(
95 &mut self,
96 name: impl Into<String>,
97 factory: ExecutorFactory,
98 ) -> AppResult<()> {
99 let name = Self::normalize_backend_name(name)?;
100 if self.executor_factories.contains_key(&name) {
101 return Err(AppError::new(
102 ErrorCode::AlreadyExists,
103 format!("media executor '{name}' is already registered"),
104 ));
105 }
106 self.executor_factories.insert(name, factory);
107 Ok(())
108 }
109
110 pub fn register_probe(
112 &mut self,
113 name: impl Into<String>,
114 factory: ProbeFactory,
115 ) -> AppResult<()> {
116 let name = Self::normalize_backend_name(name)?;
117 if self.probe_factories.contains_key(&name) {
118 return Err(AppError::new(
119 ErrorCode::AlreadyExists,
120 format!("media probe '{name}' is already registered"),
121 ));
122 }
123 self.probe_factories.insert(name, factory);
124 Ok(())
125 }
126
127 pub fn executor(&self, name: &str) -> AppResult<Arc<dyn MediaExecutor>> {
129 let name = Self::normalize_backend_name(name)?;
130 self.executor_factories.get(&name).ok_or_else(|| {
131 AppError::new(
132 ErrorCode::NotFound,
133 format!("media executor '{name}' is not registered"),
134 )
135 })?()
136 }
137
138 pub fn probe(&self, name: &str) -> AppResult<Arc<dyn MediaProbe>> {
140 let name = Self::normalize_backend_name(name)?;
141 self.probe_factories.get(&name).ok_or_else(|| {
142 AppError::new(
143 ErrorCode::NotFound,
144 format!("media probe '{name}' is not registered"),
145 )
146 })?()
147 }
148
149 pub fn is_compatible(&self, codec: &Codec, format: &Format) -> bool {
151 self.codecs
152 .get(codec)
153 .is_some_and(|info| info.compatible_formats.contains(format))
154 }
155
156 fn normalize_backend_name(name: impl Into<String>) -> AppResult<String> {
157 let name = name.into().trim().to_owned();
158 if name.is_empty() {
159 return Err(AppError::new(
160 ErrorCode::InvalidInput,
161 "media backend name is required",
162 ));
163 }
164 Ok(name)
165 }
166
167 pub fn default_codecs(&self, format: &Format) -> Option<(Codec, Codec)> {
169 let info = self.formats.get(format)?;
170 let video = info.default_video_codec.clone()?;
171 let audio = info.default_audio_codec.clone()?;
172 Some((video, audio))
173 }
174
175 pub fn codec_info(&self, codec: &Codec) -> Option<&CodecInfo> {
177 self.codecs.get(codec)
178 }
179
180 pub fn format_info(&self, format: &Format) -> Option<&FormatInfo> {
182 self.formats.get(format)
183 }
184
185 pub fn format_from_extension(&self, ext: &str) -> Option<&FormatInfo> {
187 let ext_lower = ext.to_lowercase();
188 self.formats.values().find(|f| f.extension == ext_lower)
189 }
190
191 pub fn format_from_mime(&self, mime: &str) -> Option<&FormatInfo> {
193 self.formats.values().find(|f| f.mime_type == mime)
194 }
195
196 pub fn codecs_by_kind(&self, kind: CodecKind) -> Vec<&CodecInfo> {
198 self.codecs.values().filter(|c| c.kind == kind).collect()
199 }
200
201 pub fn formats_for_codec(&self, codec: &Codec) -> Vec<&FormatInfo> {
203 match self.codecs.get(codec) {
204 Some(info) => info
205 .compatible_formats
206 .iter()
207 .filter_map(|f| self.formats.get(f))
208 .collect(),
209 None => Vec::new(),
210 }
211 }
212}
213
214#[cfg(test)]
215mod tests {
216 use rskit_errors::ErrorCode;
217
218 use super::Registry;
219
220 #[test]
221 fn executor_rejects_empty_backend_name() {
222 let Err(err) = Registry::default().executor(" \t ") else {
223 panic!("empty executor name should be rejected");
224 };
225 assert_eq!(err.code(), ErrorCode::InvalidInput);
226 }
227
228 #[test]
229 fn probe_rejects_empty_backend_name() {
230 let Err(err) = Registry::default().probe(" \t ") else {
231 panic!("empty probe name should be rejected");
232 };
233 assert_eq!(err.code(), ErrorCode::InvalidInput);
234 }
235}