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#[proc_macro_derive(OwnedMap)]
9pub fn owned_map_derive(input: TokenStream) -> TokenStream {
10 let ast = syn::parse(input).unwrap();
13
14 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#[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 _ => {
146 quote! {
147 #x0: i64
148 }
149 }
150 }
151 })
152 .collect()
153}
154
155#[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#[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 #[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 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_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 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 #[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 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 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 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 #[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 let core_ref = rustsynth::core::CoreRef::from_ptr(core);
312 let in_map = rustsynth::map::Map::from_ptr(in_);
313
314 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 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 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 #input
376
377 #create
378
379 #[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 filter.request_input_frames(n, frame_ctx_wrapper);
402 std::ptr::null()
403 },
404 rustsynth::filter::ActivationReason::AllFramesReady => {
405 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 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 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 #[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 });
470 }
471 }
472
473 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}