Skip to main content

oxideav_core/
capabilities.rs

1//! Codec capability description.
2//!
3//! Each codec implementation registered with the codec registry attaches one
4//! of these structs to declare what it can do, what its constraints are, and
5//! how the registry should rank it against alternative implementations of
6//! the same codec id.
7//!
8//! The flag layout mirrors ffmpeg's `-codecs` output:
9//!
10//! ```text
11//!  D..... = Decoding supported
12//!  .E.... = Encoding supported
13//!  ..V... = Video codec       ..A... = Audio       ..S... = Subtitle
14//!  ..D... = Data              ..T... = Attachment
15//!  ...I.. = Intra-frame-only codec
16//!  ....L. = Lossy compression
17//!  .....S = Lossless compression
18//! ```
19
20use std::fmt;
21
22use crate::format::{MediaType, PixelFormat};
23
24/// Default priority for software implementations. Lower numbers are preferred
25/// at resolution time, so register hardware impls with a smaller value (e.g.
26/// `10`) and software fallbacks with the default `100`.
27pub const DEFAULT_PRIORITY: i32 = 100;
28
29/// What an implementation can do plus how it ranks vs alternatives.
30#[derive(Clone, Debug)]
31pub struct CodecCapabilities {
32    pub decode: bool,
33    pub encode: bool,
34    pub media_type: MediaType,
35    pub intra_only: bool,
36    pub lossy: bool,
37    pub lossless: bool,
38    /// Hardware-accelerated implementation (VAAPI/NVENC/QSV/VideoToolbox/...).
39    pub hardware_accelerated: bool,
40    /// Short identifier for this implementation, e.g. "flac_sw", "h264_qsv".
41    pub implementation: String,
42    /// Restrictions — `None` means "no constraint".
43    pub max_width: Option<u32>,
44    pub max_height: Option<u32>,
45    pub max_bitrate: Option<u64>,
46    pub max_sample_rate: Option<u32>,
47    pub max_channels: Option<u16>,
48    /// Lower numbers are preferred. HW impls should be ~10, SW impls ~100.
49    pub priority: i32,
50    /// Pixel formats this implementation accepts (video only). An empty
51    /// `Vec` means "any format" — resolution won't filter on it. When
52    /// populated, the registry can skip impls whose accepted set does not
53    /// include the format requested by the caller.
54    pub accepted_pixel_formats: Vec<PixelFormat>,
55}
56
57impl CodecCapabilities {
58    /// Construct a software audio decoder/encoder capability set with sensible
59    /// defaults — adjust fields after creation.
60    pub fn audio(implementation: impl Into<String>) -> Self {
61        Self {
62            decode: false,
63            encode: false,
64            media_type: MediaType::Audio,
65            intra_only: true, // audio packets are independently decodable in most codecs
66            lossy: false,
67            lossless: false,
68            hardware_accelerated: false,
69            implementation: implementation.into(),
70            max_width: None,
71            max_height: None,
72            max_bitrate: None,
73            max_sample_rate: None,
74            max_channels: None,
75            priority: DEFAULT_PRIORITY,
76            accepted_pixel_formats: Vec::new(),
77        }
78    }
79
80    pub fn video(implementation: impl Into<String>) -> Self {
81        Self {
82            decode: false,
83            encode: false,
84            media_type: MediaType::Video,
85            intra_only: false,
86            lossy: false,
87            lossless: false,
88            hardware_accelerated: false,
89            implementation: implementation.into(),
90            max_width: None,
91            max_height: None,
92            max_bitrate: None,
93            max_sample_rate: None,
94            max_channels: None,
95            priority: DEFAULT_PRIORITY,
96            accepted_pixel_formats: Vec::new(),
97        }
98    }
99
100    /// 6-character ffmpeg-style flag string. Useful for `oxideav list`-style
101    /// output.
102    pub fn flag_string(&self) -> String {
103        let mut s = String::with_capacity(6);
104        s.push(if self.decode { 'D' } else { '.' });
105        s.push(if self.encode { 'E' } else { '.' });
106        s.push(match self.media_type {
107            MediaType::Video => 'V',
108            MediaType::Audio => 'A',
109            MediaType::Subtitle => 'S',
110            MediaType::Data => 'D',
111            MediaType::Unknown => '.',
112        });
113        s.push(if self.intra_only { 'I' } else { '.' });
114        s.push(if self.lossy { 'L' } else { '.' });
115        s.push(if self.lossless { 'S' } else { '.' });
116        s
117    }
118
119    // Builder-style helpers so registrations stay compact.
120
121    pub fn with_decode(mut self) -> Self {
122        self.decode = true;
123        self
124    }
125    pub fn with_encode(mut self) -> Self {
126        self.encode = true;
127        self
128    }
129    pub fn with_intra_only(mut self, v: bool) -> Self {
130        self.intra_only = v;
131        self
132    }
133    pub fn with_lossy(mut self, v: bool) -> Self {
134        self.lossy = v;
135        self
136    }
137    pub fn with_lossless(mut self, v: bool) -> Self {
138        self.lossless = v;
139        self
140    }
141    pub fn with_hardware(mut self, v: bool) -> Self {
142        self.hardware_accelerated = v;
143        self
144    }
145    pub fn with_priority(mut self, p: i32) -> Self {
146        self.priority = p;
147        self
148    }
149    pub fn with_max_size(mut self, w: u32, h: u32) -> Self {
150        self.max_width = Some(w);
151        self.max_height = Some(h);
152        self
153    }
154    pub fn with_max_bitrate(mut self, br: u64) -> Self {
155        self.max_bitrate = Some(br);
156        self
157    }
158    pub fn with_max_sample_rate(mut self, sr: u32) -> Self {
159        self.max_sample_rate = Some(sr);
160        self
161    }
162    pub fn with_max_channels(mut self, ch: u16) -> Self {
163        self.max_channels = Some(ch);
164        self
165    }
166
167    /// Add one accepted pixel format. Appends — call multiple times to
168    /// list several.
169    pub fn with_pixel_format(mut self, fmt: PixelFormat) -> Self {
170        self.accepted_pixel_formats.push(fmt);
171        self
172    }
173
174    /// Replace the accepted pixel-format set wholesale.
175    pub fn with_pixel_formats(mut self, fmts: Vec<PixelFormat>) -> Self {
176        self.accepted_pixel_formats = fmts;
177        self
178    }
179}
180
181impl fmt::Display for CodecCapabilities {
182    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183        write!(f, "{} {}", self.flag_string(), self.implementation)
184    }
185}
186
187/// User preferences for codec selection — pass to the registry's resolve
188/// methods to bias / restrict the choice.
189#[derive(Clone, Debug, Default)]
190pub struct CodecPreferences {
191    /// Implementation names to prefer (boost their priority by `boost`).
192    pub prefer: Vec<String>,
193    /// Implementation names to skip entirely.
194    pub exclude: Vec<String>,
195    /// Forbid hardware-accelerated impls.
196    pub no_hardware: bool,
197    /// Boost amount for `prefer` impls (subtracted from priority).
198    pub boost: i32,
199}
200
201impl CodecPreferences {
202    pub fn excludes(&self, caps: &CodecCapabilities) -> bool {
203        self.exclude.iter().any(|n| n == &caps.implementation)
204            || (self.no_hardware && caps.hardware_accelerated)
205    }
206
207    pub fn effective_priority(&self, caps: &CodecCapabilities) -> i32 {
208        if self.prefer.iter().any(|n| n == &caps.implementation) {
209            caps.priority - self.boost.max(0)
210        } else {
211            caps.priority
212        }
213    }
214}