rustsynth_derive/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{self, parse_macro_input, DeriveInput, Ident, ItemMod};
4
5/// Derive macro generating an impl of `rustsynth::map::IntoOwnedMap`.
6///
7/// # Example
8/// ```
9/// use rustsynth::IntoOwnedMap;
10///
11/// #[derive(IntoOwnedMap)]
12/// struct MyStruct {
13///    field1: i32,
14///     field2: String,
15/// }
16/// let s = MyStruct { field1: 42, field2: "Hello".to_string() };
17/// let map = s.into_owned_map();
18/// assert_eq!(map.get::<i32>("field1").unwrap(), &42);
19/// assert_eq!(map.get::<String>("field2").unwrap(), &"Hello".to_string());
20/// ```
21#[proc_macro_derive(IntoOwnedMap)]
22pub fn into_owned_map_derive(input: TokenStream) -> TokenStream {
23    // Construct a representation of Rust code as a syntax tree
24    // that we can manipulate
25    let ast = syn::parse(input).unwrap();
26
27    // Build the From implementation
28    impl_map_macro(&ast)
29}
30
31fn impl_map_macro(ast: &syn::DeriveInput) -> TokenStream {
32    let name = &ast.ident;
33    let fields: Vec<Ident> = match &ast.data {
34        syn::Data::Struct(ds) => match &ds.fields {
35            syn::Fields::Named(named) => named
36                .named
37                .iter()
38                .map(|x| x.ident.clone().unwrap())
39                .collect(),
40            _ => panic!("Must have named fields"),
41        },
42        _ => panic!("Must be a data struct"),
43    };
44    let gen = quote! {
45        impl rustsynth::map::IntoOwnedMap for #name {
46            fn into_owned_map<'elem>(self) -> rustsynth::map::OwnedMap<'elem> {
47                let mut map = rustsynth::map::OwnedMap::new();
48                #(
49                    map.set(stringify!(#fields), &self.#fields).unwrap();
50                )*
51                map
52            }
53        }
54    };
55    gen.into()
56}
57
58/// Macro to define a VapourSynth plugin containing multiple filters
59#[proc_macro_attribute]
60pub fn vapoursynth_plugin(_args: TokenStream, input: TokenStream) -> TokenStream {
61    let input = parse_macro_input!(input as ItemMod);
62
63    match generate_vs_plugin(input) {
64        Ok(tokens) => tokens.into(),
65        Err(err) => err.to_compile_error().into(),
66    }
67}
68
69/// Macro to define individual filters within a plugin
70#[proc_macro_attribute]
71pub fn vapoursynth_filter(arg: TokenStream, input: TokenStream) -> TokenStream {
72    let input = parse_macro_input!(input as DeriveInput);
73    match generate_vs_filter(input, arg) {
74        Ok(tokens) => tokens.into(),
75        Err(err) => err.to_compile_error().into(),
76    }
77}
78
79fn generate_vs_plugin(input: ItemMod) -> syn::Result<proc_macro2::TokenStream> {
80    let items = if let Some((_, items)) = &input.content {
81        items
82    } else {
83        return Err(syn::Error::new_spanned(&input, "Module must have content"));
84    };
85
86    let expanded = quote! {
87            #( #items )*
88
89            // Plugin entry point - registers all filters
90            #[no_mangle]
91            pub unsafe extern "C" fn VapourSynthPluginInit2(
92                plugin: *mut rustsynth::ffi::VSPlugin,
93                vspapi: *const rustsynth::ffi::VSPLUGINAPI,
94            ) {
95                let api = &*vspapi;
96
97                // Configure the plugin
98                let identifier = std::ffi::CString::new(ID).unwrap();
99                let namespace = std::ffi::CString::new(NAMESPACE).unwrap();
100                let name = std::ffi::CString::new(NAME).unwrap();
101                let plugin_version = PLUGIN_VER;
102                let api_version = API_VER;
103                let flags = FLAGS;
104
105                api.configPlugin.expect("configPlugin is null")(
106                    identifier.as_ptr(),
107                    namespace.as_ptr(),
108                    name.as_ptr(),
109                    plugin_version,
110                    api_version,
111                    flags,
112                    plugin
113                );
114                // Register all filters in this plugin
115                __register_filters(plugin, vspapi);
116            }
117    };
118
119    Ok(expanded)
120}
121
122fn generate_vs_filter(
123    input: DeriveInput,
124    arg: TokenStream,
125) -> syn::Result<proc_macro2::TokenStream> {
126    let struct_name = &input.ident;
127
128    // Generate unique C function names based on struct name
129    let create_name = format!("{}Create", struct_name);
130    let getframe_name = format!("{}GetFrame", struct_name);
131    let free_name = format!("{}Free", struct_name);
132
133    let create_ident = syn::Ident::new(&create_name, struct_name.span());
134    let getframe_ident = syn::Ident::new(&getframe_name, struct_name.span());
135    let free_ident = syn::Ident::new(&free_name, struct_name.span());
136
137    let create = match arg.to_string().as_str() {
138        "video" => {
139            quote! {
140                // Filter creation function - the real workhorse
141            #[no_mangle]
142            pub unsafe extern "C" fn #create_ident(
143                in_: *const rustsynth::ffi::VSMap,
144                out: *mut rustsynth::ffi::VSMap,
145                user_data: *mut std::os::raw::c_void,
146                core: *mut rustsynth::ffi::VSCore,
147                vsapi: *const rustsynth::ffi::VSAPI,
148            ) {
149                rustsynth::init_api(vsapi);
150                let api = &*vsapi;
151                    let core_ref = rustsynth::core::CoreRef::from_ptr(core);
152                    let in_map = rustsynth::map::Map::from_ptr(in_);
153                    // Create filter instance from arguments
154                    match #struct_name::from_args(&in_map, &core_ref) {
155                        Ok(filter_data) => {
156                            let deps = filter_data.get_dependencies();
157                            let deps_ffi: Vec<rustsynth::ffi::VSFilterDependency> = deps.iter()
158                                .map(|d| d.as_ffi())
159                                .collect();
160
161                            // Get filter mode from const
162                            let filter_mode = #struct_name::MODE;
163                            let media_info = match filter_data.get_video_info() {
164                                        Ok(ai) => ai,
165                                        Err(error_msg) => {
166                                            let error_cstr = std::ffi::CString::new(error_msg).unwrap_or_else(|_| {
167                                                std::ffi::CString::new("Failed to video info").unwrap()
168                                            });
169                                            api.mapSetError.unwrap()(out, error_cstr.as_ptr());
170                                            return;
171                                        }
172                                    };
173
174                            // Allocate filter data on heap
175                            let data_ptr = Box::into_raw(Box::new(filter_data)) as *mut std::os::raw::c_void;
176
177                                    let filter_name = std::ffi::CString::new(#struct_name::NAME).unwrap();
178                                    api.createVideoFilter.unwrap()(
179                                        out,
180                                        filter_name.as_ptr(),
181                                        &media_info.as_ptr() as *const rustsynth::ffi::VSVideoInfo,
182                                        Some(#getframe_ident),
183                                        Some(#free_ident),
184                                        filter_mode.as_ptr() as i32,
185                                        deps_ffi.as_ptr(),
186                                        deps_ffi.len() as i32,
187                                        data_ptr,
188                                        core,
189                                    );
190
191                        },
192                        Err(error_msg) => {
193                           eprintln!("{}", error_msg);
194                        }
195                    }
196            }
197            }
198        }
199        "audio" => {
200            quote! {
201                // Filter creation function - the real workhorse
202            #[no_mangle]
203            pub unsafe extern "C" fn #create_ident(
204                in_: *const rustsynth::ffi::VSMap,
205                out: *mut rustsynth::ffi::VSMap,
206                user_data: *mut std::os::raw::c_void,
207                core: *mut rustsynth::ffi::VSCore,
208                vsapi: *const rustsynth::ffi::VSAPI,
209            ) {
210                let api = &*vsapi;
211                rustsynth::init_api(vsapi);
212                std::panic::catch_unwind(|| {
213                    // Create rustsynth wrapper objects
214                    let core_ref = rustsynth::core::CoreRef::from_ptr(core);
215                    let in_map = rustsynth::map::Map::from_ptr(in_);
216
217                    // Create filter instance from arguments
218                    match #struct_name::from_args(&in_map, &core_ref) {
219                        Ok(filter_data) => {
220                            let deps = filter_data.get_dependencies();
221                            let deps_ffi: Vec<rustsynth::ffi::VSFilterDependency> = deps.iter()
222                                .map(|d| d.as_ffi())
223                                .collect();
224
225                            // Get filter mode from const
226                            let filter_mode = #struct_name::MODE;
227                            let media_info = match filter_data.get_audio_info() {
228                                        Ok(ai) => ai,
229                                        Err(error_msg) => {
230                                            let error_cstr = std::ffi::CString::new(error_msg).unwrap_or_else(|_| {
231                                                std::ffi::CString::new("Failed to get audio info").unwrap()
232                                            });
233                                            api.mapSetError.unwrap()(out, error_cstr.as_ptr());
234                                            return;
235                                        }
236                                    };
237                            // Allocate filter data on heap
238                            let data_ptr = Box::into_raw(Box::new(filter_data)) as *mut std::os::raw::c_void;
239
240
241                            let filter_name = std::ffi::CString::new(#struct_name::NAME).unwrap();
242                            api.createAudioFilter.unwrap()(
243                                        out,
244                                        filter_name.as_ptr(),
245                                        &media_info,
246                                        Some(#getframe_ident),
247                                        Some(#free_ident),
248                                        *filter_mode.as_ptr(),
249                                        deps_ffi.as_ptr(),
250                                        deps_ffi.len() as i32,
251                                        data_ptr,
252                                        core,
253                                    );
254                        },
255                        Err(error_msg) => {
256                            let error_cstr = std::ffi::CString::new(error_msg).unwrap_or_else(|_| {
257                                std::ffi::CString::new("Filter creation failed").unwrap()
258                            });
259                            api.mapSetError.unwrap()(out, error_cstr.as_ptr());
260                        }
261                    }
262                }).unwrap_or_else(|_| {
263                    api.mapSetError.unwrap()(out, b"Filter creation panicked\0".as_ptr() as *const std::os::raw::c_char);
264                });
265            }
266            }
267        }
268        _ => {
269            return Err(syn::Error::new_spanned(
270                arg.to_string(),
271                "Unsupported filter type. Use 'video' or 'audio'",
272            ))
273        }
274    };
275
276    let expanded = quote! {
277        // Original struct definition
278        #input
279
280        #create
281
282        // Frame processing function
283        #[no_mangle]
284        pub unsafe extern "C" fn #getframe_ident(
285            n: i32,
286            activation_reason: i32,
287            instance_data: *mut std::os::raw::c_void,
288            frame_data: *mut *mut std::os::raw::c_void,
289            frame_ctx: *mut rustsynth::ffi::VSFrameContext,
290            core: *mut rustsynth::ffi::VSCore,
291            vsapi: *const rustsynth::ffi::VSAPI,
292        ) -> *const rustsynth::ffi::VSFrame {
293            let api = &*vsapi;
294
295            std::panic::catch_unwind(|| {
296                let filter = &mut *(instance_data as *mut #struct_name);
297                let core_ref = rustsynth::core::CoreRef::from_ptr(core);
298                let frame_ctx_wrapper = rustsynth::frame::FrameContext::from_ptr(frame_ctx);
299                let activation = rustsynth::filter::ActivationReason::from_ffi(activation_reason);
300
301                match activation {
302                    rustsynth::filter::ActivationReason::Initial => {
303                        // Request the frames we need
304                        filter.request_input_frames(n, &frame_ctx_wrapper);
305                        std::ptr::null()
306                    },
307                    rustsynth::filter::ActivationReason::AllFramesReady => {
308                        // All frames ready - do the processing
309                        // Convert frame_data to the expected format
310                        let frame_data_array: &[u8; 4] = if (*frame_data).is_null() {
311                            &[0; 4]
312                        } else {
313                            std::slice::from_raw_parts(*frame_data as *const u8, 4).try_into().unwrap_or(&[0; 4])
314                        };
315
316                        match filter.process_frame(n, frame_data_array, &frame_ctx_wrapper, core_ref) {
317                            Ok(output_frame) => {
318                                let out = std::mem::ManuallyDrop::new(output_frame);
319                                out.as_ptr()
320                            },
321                            Err(error_msg) => {
322                                let error_cstr = std::ffi::CString::new(error_msg).unwrap_or_else(|_| {
323                                    std::ffi::CString::new("Frame processing failed").unwrap()
324                                });
325                                api.setFilterError.unwrap()(error_cstr.as_ptr(), frame_ctx);
326
327                                // Clean up frame data if needed
328                                if !(*frame_data).is_null() {
329                                    filter.cleanup_frame_data(frame_data_array);
330                                    *frame_data = std::ptr::null_mut();
331                                }
332                                std::ptr::null()
333                            }
334                        }
335                    },
336                    rustsynth::filter::ActivationReason::Error => {
337                        // Error occurred - clean up
338                        if !(*frame_data).is_null() {
339                            let frame_data_array: &[u8; 4] = std::slice::from_raw_parts(*frame_data as *const u8, 4).try_into().unwrap_or(&[0; 4]);
340                            filter.cleanup_frame_data(frame_data_array);
341                            *frame_data = std::ptr::null_mut();
342                        }
343                        std::ptr::null()
344                    }
345                }
346            }).unwrap_or_else(|_| {
347                api.setFilterError.unwrap()(
348                    b"Frame processing panicked\0".as_ptr() as *const std::os::raw::c_char,
349                    frame_ctx
350                );
351
352                if !(*frame_data).is_null() {
353                    *frame_data = std::ptr::null_mut();
354                }
355                std::ptr::null()
356            })
357        }
358
359        // Filter cleanup function
360        #[no_mangle]
361        pub unsafe extern "C" fn #free_ident(
362            instance_data: *mut std::os::raw::c_void,
363            core: *mut rustsynth::ffi::VSCore,
364            vsapi: *const rustsynth::ffi::VSAPI,
365        ) {
366            if !instance_data.is_null() {
367                let _ = std::panic::catch_unwind(|| {
368                    let filter = Box::from_raw(instance_data as *mut #struct_name);
369                    filter.cleanup();
370                    // Box drop handles memory cleanup
371                });
372            }
373        }
374
375        // Register this filter in the plugin
376        impl #struct_name {
377            fn register_filter(
378                plugin: *mut rustsynth::ffi::VSPlugin,
379                vspapi: *const rustsynth::ffi::VSPLUGINAPI
380            ) {
381                unsafe {
382                    let api = &*vspapi;
383                    let filter_name = std::ffi::CString::new(#struct_name::NAME).unwrap();
384                    let args_spec = std::ffi::CString::new(#struct_name::ARGS).unwrap();
385                    let return_spec = std::ffi::CString::new(#struct_name::RETURNTYPE).unwrap();
386
387                    if let Some(register_fn) = api.registerFunction {
388                        let ret = register_fn(
389                            filter_name.as_ptr(),
390                            args_spec.as_ptr(),
391                            return_spec.as_ptr(),
392                            Some(#create_ident),
393                            std::ptr::null_mut(),
394                            plugin
395                        );
396                        if ret == 0 {
397                            eprintln!("Failed to register filter '{}'", #struct_name::NAME);
398                        }
399                    } else {
400                        eprintln!("registerFunction API is NULL - cannot register filter '{}'", #struct_name::NAME);
401                    }
402                }
403            }
404        }
405    };
406
407    Ok(expanded)
408}