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}