rustsynth_derive/
lib.rs

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