use std::collections::HashMap;
use std::sync::Arc;
use rskit_errors::{AppError, AppResult, ErrorCode};
use crate::{
codec::{Codec, CodecKind},
executor::MediaExecutor,
format::Format,
probe::MediaProbe,
types::MediaType,
};
use super::defaults;
pub type ExecutorFactory = Arc<dyn Fn() -> AppResult<Arc<dyn MediaExecutor>> + Send + Sync>;
pub type ProbeFactory = Arc<dyn Fn() -> AppResult<Arc<dyn MediaProbe>> + Send + Sync>;
#[derive(Debug, Clone)]
pub struct CodecInfo {
pub id: Codec,
pub kind: CodecKind,
pub display_name: String,
pub ffmpeg_encoder: Option<String>,
pub ffmpeg_decoder: Option<String>,
pub compatible_formats: Vec<Format>,
}
#[derive(Debug, Clone)]
pub struct FormatInfo {
pub id: Format,
pub extension: String,
pub mime_type: String,
pub is_container: bool,
pub supported_media_types: Vec<MediaType>,
pub default_video_codec: Option<Codec>,
pub default_audio_codec: Option<Codec>,
}
#[derive(Clone)]
pub struct Registry {
codecs: HashMap<Codec, CodecInfo>,
formats: HashMap<Format, FormatInfo>,
executor_factories: HashMap<String, ExecutorFactory>,
probe_factories: HashMap<String, ProbeFactory>,
}
impl Default for Registry {
fn default() -> Self {
let mut reg = Self {
codecs: HashMap::new(),
formats: HashMap::new(),
executor_factories: HashMap::new(),
probe_factories: HashMap::new(),
};
defaults::load_defaults(&mut reg);
reg
}
}
impl Registry {
pub fn register_codec(&mut self, info: CodecInfo) {
self.codecs.insert(info.id.clone(), info);
}
pub fn register_format(&mut self, info: FormatInfo) {
self.formats.insert(info.id.clone(), info);
}
pub fn register_executor(
&mut self,
name: impl Into<String>,
factory: ExecutorFactory,
) -> AppResult<()> {
let name = Self::normalize_backend_name(name)?;
if self.executor_factories.contains_key(&name) {
return Err(AppError::new(
ErrorCode::AlreadyExists,
format!("media executor '{name}' is already registered"),
));
}
self.executor_factories.insert(name, factory);
Ok(())
}
pub fn register_probe(
&mut self,
name: impl Into<String>,
factory: ProbeFactory,
) -> AppResult<()> {
let name = Self::normalize_backend_name(name)?;
if self.probe_factories.contains_key(&name) {
return Err(AppError::new(
ErrorCode::AlreadyExists,
format!("media probe '{name}' is already registered"),
));
}
self.probe_factories.insert(name, factory);
Ok(())
}
pub fn executor(&self, name: &str) -> AppResult<Arc<dyn MediaExecutor>> {
let name = Self::normalize_backend_name(name)?;
self.executor_factories.get(&name).ok_or_else(|| {
AppError::new(
ErrorCode::NotFound,
format!("media executor '{name}' is not registered"),
)
})?()
}
pub fn probe(&self, name: &str) -> AppResult<Arc<dyn MediaProbe>> {
let name = Self::normalize_backend_name(name)?;
self.probe_factories.get(&name).ok_or_else(|| {
AppError::new(
ErrorCode::NotFound,
format!("media probe '{name}' is not registered"),
)
})?()
}
pub fn is_compatible(&self, codec: &Codec, format: &Format) -> bool {
self.codecs
.get(codec)
.is_some_and(|info| info.compatible_formats.contains(format))
}
fn normalize_backend_name(name: impl Into<String>) -> AppResult<String> {
let name = name.into().trim().to_owned();
if name.is_empty() {
return Err(AppError::new(
ErrorCode::InvalidInput,
"media backend name is required",
));
}
Ok(name)
}
pub fn default_codecs(&self, format: &Format) -> Option<(Codec, Codec)> {
let info = self.formats.get(format)?;
let video = info.default_video_codec.clone()?;
let audio = info.default_audio_codec.clone()?;
Some((video, audio))
}
pub fn codec_info(&self, codec: &Codec) -> Option<&CodecInfo> {
self.codecs.get(codec)
}
pub fn format_info(&self, format: &Format) -> Option<&FormatInfo> {
self.formats.get(format)
}
pub fn format_from_extension(&self, ext: &str) -> Option<&FormatInfo> {
let ext_lower = ext.to_lowercase();
self.formats.values().find(|f| f.extension == ext_lower)
}
pub fn format_from_mime(&self, mime: &str) -> Option<&FormatInfo> {
self.formats.values().find(|f| f.mime_type == mime)
}
pub fn codecs_by_kind(&self, kind: CodecKind) -> Vec<&CodecInfo> {
self.codecs.values().filter(|c| c.kind == kind).collect()
}
pub fn formats_for_codec(&self, codec: &Codec) -> Vec<&FormatInfo> {
match self.codecs.get(codec) {
Some(info) => info
.compatible_formats
.iter()
.filter_map(|f| self.formats.get(f))
.collect(),
None => Vec::new(),
}
}
}
#[cfg(test)]
mod tests {
use rskit_errors::ErrorCode;
use super::Registry;
#[test]
fn executor_rejects_empty_backend_name() {
let Err(err) = Registry::default().executor(" \t ") else {
panic!("empty executor name should be rejected");
};
assert_eq!(err.code(), ErrorCode::InvalidInput);
}
#[test]
fn probe_rejects_empty_backend_name() {
let Err(err) = Registry::default().probe(" \t ") else {
panic!("empty probe name should be rejected");
};
assert_eq!(err.code(), ErrorCode::InvalidInput);
}
}