Skip to main content

oximedia_plugin/
traits.rs

1//! Core plugin traits and types.
2//!
3//! This module defines the [`CodecPlugin`] trait that all plugins must
4//! implement, along with supporting types for plugin metadata and
5//! capability declaration.
6
7use oximedia_codec::{CodecResult, EncoderConfig, VideoDecoder, VideoEncoder};
8use std::collections::HashMap;
9
10/// API version for plugin compatibility checking.
11///
12/// This value is incremented whenever the plugin ABI changes in a
13/// backward-incompatible way. Plugins built against a different
14/// API version will be rejected at load time.
15pub const PLUGIN_API_VERSION: u32 = 1;
16
17/// Metadata about a codec plugin.
18///
19/// This struct is returned by [`CodecPlugin::info`] and provides
20/// identification, versioning, and licensing information.
21#[derive(Debug, Clone)]
22pub struct CodecPluginInfo {
23    /// Plugin name (e.g., "oximedia-plugin-h264").
24    pub name: String,
25    /// Plugin version (semver string, e.g., "1.0.0").
26    pub version: String,
27    /// Plugin author or organization.
28    pub author: String,
29    /// Human-readable description of the plugin.
30    pub description: String,
31    /// API version this plugin was built against.
32    pub api_version: u32,
33    /// License identifier (e.g., "MIT", "GPL-2.0", "proprietary").
34    pub license: String,
35    /// Whether this plugin contains patent-encumbered codecs.
36    ///
37    /// When true, a warning is emitted at load time. Users must
38    /// acknowledge the patent implications of using such plugins.
39    pub patent_encumbered: bool,
40}
41
42impl std::fmt::Display for CodecPluginInfo {
43    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44        write!(
45            f,
46            "{} v{} [{}]{}",
47            self.name,
48            self.version,
49            self.license,
50            if self.patent_encumbered {
51                " (patent-encumbered)"
52            } else {
53                ""
54            }
55        )
56    }
57}
58
59/// A single capability (codec) provided by a plugin.
60///
61/// Each plugin can provide multiple capabilities, one per codec
62/// it supports. The capability declares whether decoding and/or
63/// encoding is available, along with supported formats and properties.
64#[derive(Debug, Clone)]
65pub struct PluginCapability {
66    /// Codec identifier string (e.g., "h264", "h265", "aac").
67    pub codec_name: String,
68    /// Whether decoding is supported for this codec.
69    pub can_decode: bool,
70    /// Whether encoding is supported for this codec.
71    pub can_encode: bool,
72    /// Supported pixel formats for video (e.g., "yuv420p", "nv12").
73    pub pixel_formats: Vec<String>,
74    /// Additional codec-specific properties (key-value pairs).
75    pub properties: HashMap<String, String>,
76}
77
78impl std::fmt::Display for PluginCapability {
79    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80        let mode = match (self.can_decode, self.can_encode) {
81            (true, true) => "decode+encode",
82            (true, false) => "decode",
83            (false, true) => "encode",
84            (false, false) => "none",
85        };
86        write!(f, "{} ({})", self.codec_name, mode)
87    }
88}
89
90/// The main plugin trait that external codec libraries implement.
91///
92/// A plugin provides one or more codecs, each with optional decode
93/// and encode support. The host application uses the registry to
94/// discover plugins and create decoder/encoder instances on demand.
95///
96/// # Thread Safety
97///
98/// Plugins must be `Send + Sync` because the registry may be shared
99/// across threads. Individual decoder/encoder instances returned by
100/// the factory methods need only be `Send`.
101///
102/// # Implementing a Plugin
103///
104/// For simple cases, use [`StaticPlugin`](crate::StaticPlugin) with
105/// the builder pattern. For shared libraries, implement this trait
106/// on your own type and use the `declare_plugin!` macro.
107pub trait CodecPlugin: Send + Sync {
108    /// Get plugin metadata and identification.
109    fn info(&self) -> CodecPluginInfo;
110
111    /// List all capabilities (codecs) provided by this plugin.
112    fn capabilities(&self) -> Vec<PluginCapability>;
113
114    /// Create a decoder instance for the given codec name.
115    ///
116    /// # Errors
117    ///
118    /// Returns [`CodecError`](oximedia_codec::CodecError) if the codec
119    /// is not supported or decoder creation fails.
120    fn create_decoder(&self, codec_name: &str) -> CodecResult<Box<dyn VideoDecoder>>;
121
122    /// Create an encoder instance for the given codec name with configuration.
123    ///
124    /// # Errors
125    ///
126    /// Returns [`CodecError`](oximedia_codec::CodecError) if the codec
127    /// is not supported or encoder creation fails.
128    fn create_encoder(
129        &self,
130        codec_name: &str,
131        config: EncoderConfig,
132    ) -> CodecResult<Box<dyn VideoEncoder>>;
133
134    /// Check if this plugin supports a specific codec (decode or encode).
135    fn supports_codec(&self, codec_name: &str) -> bool {
136        self.capabilities()
137            .iter()
138            .any(|c| c.codec_name == codec_name)
139    }
140
141    /// Check if this plugin can decode a specific codec.
142    fn can_decode(&self, codec_name: &str) -> bool {
143        self.capabilities()
144            .iter()
145            .any(|c| c.codec_name == codec_name && c.can_decode)
146    }
147
148    /// Check if this plugin can encode a specific codec.
149    fn can_encode(&self, codec_name: &str) -> bool {
150        self.capabilities()
151            .iter()
152            .any(|c| c.codec_name == codec_name && c.can_encode)
153    }
154}
155
156/// Function signature for the plugin API version query.
157///
158/// The shared library must export this as:
159/// `extern "C" fn oximedia_plugin_api_version() -> u32`
160pub type PluginApiVersionFn = unsafe extern "C" fn() -> u32;
161
162/// Function signature for the plugin factory function.
163///
164/// The shared library must export this as:
165/// `extern "C" fn oximedia_plugin_create() -> *mut dyn CodecPlugin`
166///
167/// The returned pointer must have been created via `Box::into_raw(Box::new(...))`.
168/// The host takes ownership and will drop it when the plugin is unloaded.
169///
170/// Note: trait objects are not FFI-safe per Rust's type system, but this is
171/// the standard pattern for Rust-to-Rust plugin loading where both sides
172/// share the same trait definition and ABI. This is safe as long as both
173/// the host and plugin are compiled with the same Rust compiler version.
174#[allow(improper_ctypes_definitions)]
175pub type PluginCreateFn = unsafe extern "C" fn() -> *mut dyn CodecPlugin;
176
177#[cfg(test)]
178mod tests {
179    use super::*;
180
181    #[test]
182    fn test_plugin_info_display() {
183        let info = CodecPluginInfo {
184            name: "test-plugin".to_string(),
185            version: "1.0.0".to_string(),
186            author: "Test Author".to_string(),
187            description: "A test plugin".to_string(),
188            api_version: PLUGIN_API_VERSION,
189            license: "MIT".to_string(),
190            patent_encumbered: false,
191        };
192        assert_eq!(format!("{info}"), "test-plugin v1.0.0 [MIT]");
193    }
194
195    #[test]
196    fn test_plugin_info_display_patent() {
197        let info = CodecPluginInfo {
198            name: "h264-plugin".to_string(),
199            version: "2.0.0".to_string(),
200            author: "Corp".to_string(),
201            description: "H.264 decoder".to_string(),
202            api_version: PLUGIN_API_VERSION,
203            license: "proprietary".to_string(),
204            patent_encumbered: true,
205        };
206        assert_eq!(
207            format!("{info}"),
208            "h264-plugin v2.0.0 [proprietary] (patent-encumbered)"
209        );
210    }
211
212    #[test]
213    fn test_capability_display() {
214        let cap = PluginCapability {
215            codec_name: "h264".to_string(),
216            can_decode: true,
217            can_encode: true,
218            pixel_formats: vec!["yuv420p".to_string()],
219            properties: HashMap::new(),
220        };
221        assert_eq!(format!("{cap}"), "h264 (decode+encode)");
222
223        let decode_only = PluginCapability {
224            codec_name: "hevc".to_string(),
225            can_decode: true,
226            can_encode: false,
227            pixel_formats: vec![],
228            properties: HashMap::new(),
229        };
230        assert_eq!(format!("{decode_only}"), "hevc (decode)");
231
232        let encode_only = PluginCapability {
233            codec_name: "aac".to_string(),
234            can_decode: false,
235            can_encode: true,
236            pixel_formats: vec![],
237            properties: HashMap::new(),
238        };
239        assert_eq!(format!("{encode_only}"), "aac (encode)");
240    }
241
242    #[test]
243    fn test_api_version_is_nonzero() {
244        assert!(PLUGIN_API_VERSION > 0);
245    }
246}