use std::fmt;
use crate::blob::BlobKind;
use crate::format::{MediaType, PixelFormat, SampleFormat};
pub const DEFAULT_PRIORITY: i32 = 100;
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct CodecCapabilities {
pub decode: bool,
pub encode: bool,
pub media_type: MediaType,
pub intra_only: bool,
pub lossy: bool,
pub lossless: bool,
pub hardware_accelerated: bool,
pub implementation: String,
pub max_width: Option<u32>,
pub max_height: Option<u32>,
pub max_bitrate: Option<u64>,
pub max_sample_rate: Option<u32>,
pub max_channels: Option<u16>,
pub priority: i32,
pub accepted_pixel_formats: Vec<PixelFormat>,
pub accepted_sample_rates: Vec<u32>,
pub accepted_sample_formats: Vec<SampleFormat>,
pub embedded_blob_kinds: Vec<BlobKind>,
}
impl CodecCapabilities {
pub fn audio(implementation: impl Into<String>) -> Self {
Self {
decode: false,
encode: false,
media_type: MediaType::Audio,
intra_only: true, lossy: false,
lossless: false,
hardware_accelerated: false,
implementation: implementation.into(),
max_width: None,
max_height: None,
max_bitrate: None,
max_sample_rate: None,
max_channels: None,
priority: DEFAULT_PRIORITY,
accepted_pixel_formats: Vec::new(),
accepted_sample_rates: Vec::new(),
accepted_sample_formats: Vec::new(),
embedded_blob_kinds: Vec::new(),
}
}
pub fn video(implementation: impl Into<String>) -> Self {
Self {
decode: false,
encode: false,
media_type: MediaType::Video,
intra_only: false,
lossy: false,
lossless: false,
hardware_accelerated: false,
implementation: implementation.into(),
max_width: None,
max_height: None,
max_bitrate: None,
max_sample_rate: None,
max_channels: None,
priority: DEFAULT_PRIORITY,
accepted_pixel_formats: Vec::new(),
accepted_sample_rates: Vec::new(),
accepted_sample_formats: Vec::new(),
embedded_blob_kinds: Vec::new(),
}
}
pub fn subtitle(implementation: impl Into<String>) -> Self {
let mut caps = Self::audio(implementation);
caps.media_type = MediaType::Subtitle;
caps.intra_only = true;
caps.lossless = true;
caps
}
pub fn with_media_type(mut self, media_type: MediaType) -> Self {
self.media_type = media_type;
self
}
pub fn flag_string(&self) -> String {
let mut s = String::with_capacity(6);
s.push(if self.decode { 'D' } else { '.' });
s.push(if self.encode { 'E' } else { '.' });
s.push(match self.media_type {
MediaType::Video => 'V',
MediaType::Audio => 'A',
MediaType::Subtitle => 'S',
MediaType::Data => 'D',
MediaType::Unknown => '.',
});
s.push(if self.intra_only { 'I' } else { '.' });
s.push(if self.lossy { 'L' } else { '.' });
s.push(if self.lossless { 'S' } else { '.' });
s
}
pub fn with_decode(mut self) -> Self {
self.decode = true;
self
}
pub fn with_encode(mut self) -> Self {
self.encode = true;
self
}
pub fn with_intra_only(mut self, v: bool) -> Self {
self.intra_only = v;
self
}
pub fn with_lossy(mut self, v: bool) -> Self {
self.lossy = v;
self
}
pub fn with_lossless(mut self, v: bool) -> Self {
self.lossless = v;
self
}
pub fn with_hardware(mut self, v: bool) -> Self {
self.hardware_accelerated = v;
self
}
pub fn with_priority(mut self, p: i32) -> Self {
self.priority = p;
self
}
pub fn with_max_size(mut self, w: u32, h: u32) -> Self {
self.max_width = Some(w);
self.max_height = Some(h);
self
}
pub fn with_max_bitrate(mut self, br: u64) -> Self {
self.max_bitrate = Some(br);
self
}
pub fn with_max_sample_rate(mut self, sr: u32) -> Self {
self.max_sample_rate = Some(sr);
self
}
pub fn with_max_channels(mut self, ch: u16) -> Self {
self.max_channels = Some(ch);
self
}
pub fn with_pixel_format(mut self, fmt: PixelFormat) -> Self {
self.accepted_pixel_formats.push(fmt);
self
}
pub fn with_pixel_formats(mut self, fmts: Vec<PixelFormat>) -> Self {
self.accepted_pixel_formats = fmts;
self
}
pub fn with_sample_rates(mut self, rates: Vec<u32>) -> Self {
self.accepted_sample_rates = rates;
self
}
pub fn with_sample_formats(mut self, fmts: Vec<SampleFormat>) -> Self {
self.accepted_sample_formats = fmts;
self
}
pub fn with_embedded_blob_kind(mut self, kind: impl Into<BlobKind>) -> Self {
let kind = kind.into();
if !self.embedded_blob_kinds.contains(&kind) {
self.embedded_blob_kinds.push(kind);
}
self
}
pub fn with_embedded_blob_kinds(mut self, kinds: Vec<BlobKind>) -> Self {
self.embedded_blob_kinds.clear();
for kind in kinds {
if !self.embedded_blob_kinds.contains(&kind) {
self.embedded_blob_kinds.push(kind);
}
}
self
}
pub fn embeds_blob(&self, kind: &BlobKind) -> bool {
self.embedded_blob_kinds.contains(kind)
}
pub fn declares_embedded_blobs(&self) -> bool {
!self.embedded_blob_kinds.is_empty()
}
pub fn pick_sample_rate(&self, rate: u32) -> u32 {
if self.accepted_sample_rates.is_empty() || self.accepted_sample_rates.contains(&rate) {
return rate;
}
let above = self
.accepted_sample_rates
.iter()
.copied()
.filter(|&r| r > rate)
.min();
above.unwrap_or_else(|| {
self.accepted_sample_rates
.iter()
.copied()
.max()
.unwrap_or(rate)
})
}
}
impl fmt::Display for CodecCapabilities {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} {}", self.flag_string(), self.implementation)
}
}
impl CodecCapabilities {
pub fn fits_params(&self, p: &crate::CodecParameters, for_encode: bool) -> bool {
let _ = for_encode;
if let (Some(max), Some(w)) = (self.max_width, p.width)
&& w > max
{
return false;
}
if let (Some(max), Some(h)) = (self.max_height, p.height)
&& h > max
{
return false;
}
if let (Some(max), Some(br)) = (self.max_bitrate, p.bit_rate)
&& br > max
{
return false;
}
if let (Some(max), Some(sr)) = (self.max_sample_rate, p.sample_rate)
&& sr > max
{
return false;
}
if let (Some(max), Some(ch)) = (self.max_channels, p.channels)
&& ch > max
{
return false;
}
true
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pick_sample_rate_prefers_exact_then_next_above_then_max() {
let any = CodecCapabilities::audio("any");
assert_eq!(any.pick_sample_rate(44_100), 44_100);
let opus = CodecCapabilities::audio("opus")
.with_sample_rates(vec![8_000, 12_000, 16_000, 24_000, 48_000]);
assert_eq!(opus.pick_sample_rate(16_000), 16_000);
assert_eq!(opus.pick_sample_rate(44_100), 48_000);
assert_eq!(opus.pick_sample_rate(11_025), 12_000);
assert_eq!(opus.pick_sample_rate(96_000), 48_000);
}
#[test]
fn embedded_blob_kinds_default_empty_and_builders_dedupe() {
for caps in [
CodecCapabilities::audio("a"),
CodecCapabilities::video("v"),
CodecCapabilities::subtitle("s"),
] {
assert!(caps.embedded_blob_kinds.is_empty(), "{caps}");
assert!(!caps.declares_embedded_blobs(), "{caps}");
assert!(!caps.embeds_blob(&BlobKind::ICC), "{caps}");
}
let c = CodecCapabilities::video("png")
.with_encode()
.with_embedded_blob_kind(BlobKind::ICC)
.with_embedded_blob_kind(BlobKind::EXIF)
.with_embedded_blob_kind("png-text")
.with_embedded_blob_kind(BlobKind::ICC);
assert_eq!(
c.embedded_blob_kinds,
vec![BlobKind::ICC, BlobKind::EXIF, BlobKind::custom("png-text")]
);
assert!(c.declares_embedded_blobs());
assert!(c.embeds_blob(&BlobKind::ICC));
assert!(c.embeds_blob(&BlobKind::custom("png-text")));
assert!(!c.embeds_blob(&BlobKind::XMP));
let c = c.with_embedded_blob_kinds(vec![BlobKind::XMP, BlobKind::IPTC, BlobKind::XMP]);
assert_eq!(c.embedded_blob_kinds, vec![BlobKind::XMP, BlobKind::IPTC]);
assert!(!c.embeds_blob(&BlobKind::ICC));
let c = c.with_embedded_blob_kinds(Vec::new());
assert!(!c.declares_embedded_blobs());
assert_eq!(c.flag_string(), ".EV...");
}
#[test]
fn sample_format_builder_replaces_set() {
let c = CodecCapabilities::audio("x")
.with_sample_formats(vec![SampleFormat::S16, SampleFormat::F32]);
assert_eq!(
c.accepted_sample_formats,
vec![SampleFormat::S16, SampleFormat::F32]
);
}
}