1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{self, parse_macro_input, DeriveInput, Ident, ItemMod};
4
5#[proc_macro_derive(IntoOwnedMap)]
25pub fn into_owned_map_derive(input: TokenStream) -> TokenStream {
26 let ast = syn::parse(input).unwrap();
29
30 impl_map_macro(&ast)
32}
33
34fn impl_map_macro(ast: &syn::DeriveInput) -> TokenStream {
35 let name = &ast.ident;
36 let fields: Vec<Ident> = match &ast.data {
37 syn::Data::Struct(ds) => match &ds.fields {
38 syn::Fields::Named(named) => named
39 .named
40 .iter()
41 .map(|x| x.ident.clone().unwrap())
42 .collect(),
43 _ => panic!("Must have named fields"),
44 },
45 _ => panic!("Must be a data struct"),
46 };
47 let gen = quote! {
48 impl rustsynth::map::IntoOwnedMap for #name {
49 fn into_owned_map<'elem>(self) -> rustsynth::map::Map<'elem> {
50 let mut map = rustsynth::map::Map::new()
53 .expect("Failed to create map - ensure VapourSynth API is initialized with init_api() or use within a VapourSynth plugin context");
54 #(
55 map.set(stringify!(#fields), &self.#fields)
56 .expect(&format!("Failed to set field '{}' in map", stringify!(#fields)));
57 )*
58 map
59 }
60 }
61 };
62 gen.into()
63}
64
65#[proc_macro_attribute]
67pub fn vapoursynth_plugin(_args: TokenStream, input: TokenStream) -> TokenStream {
68 let input = parse_macro_input!(input as ItemMod);
69
70 match generate_vs_plugin(input) {
71 Ok(tokens) => tokens.into(),
72 Err(err) => err.to_compile_error().into(),
73 }
74}
75
76#[proc_macro_attribute]
78pub fn vapoursynth_filter(arg: TokenStream, input: TokenStream) -> TokenStream {
79 let input = parse_macro_input!(input as DeriveInput);
80 match generate_vs_filter(input, arg) {
81 Ok(tokens) => tokens.into(),
82 Err(err) => err.to_compile_error().into(),
83 }
84}
85
86fn generate_vs_plugin(input: ItemMod) -> syn::Result<proc_macro2::TokenStream> {
87 let items = if let Some((_, items)) = &input.content {
88 items
89 } else {
90 return Err(syn::Error::new_spanned(&input, "Module must have content"));
91 };
92
93 let expanded = quote! {
94 #( #items )*
95
96 #[no_mangle]
98 pub unsafe extern "C" fn VapourSynthPluginInit2(
99 plugin: *mut rustsynth::ffi::VSPlugin,
100 vspapi: *const rustsynth::ffi::VSPLUGINAPI,
101 ) {
102 let api = &*vspapi;
103
104 let identifier = std::ffi::CString::new(ID).unwrap();
106 let namespace = std::ffi::CString::new(NAMESPACE).unwrap();
107 let name = std::ffi::CString::new(NAME).unwrap();
108 let plugin_version = PLUGIN_VER;
109 let api_version = API_VER;
110 let flags = FLAGS;
111
112 api.configPlugin.expect("configPlugin is null")(
113 identifier.as_ptr(),
114 namespace.as_ptr(),
115 name.as_ptr(),
116 plugin_version,
117 api_version,
118 flags,
119 plugin
120 );
121 __register_filters(plugin, vspapi);
123 }
124 };
125
126 Ok(expanded)
127}
128
129fn generate_vs_filter(
130 input: DeriveInput,
131 arg: TokenStream,
132) -> syn::Result<proc_macro2::TokenStream> {
133 let struct_name = &input.ident;
134
135 let mut clean_input = input.clone();
137 clean_input
138 .attrs
139 .retain(|attr| !attr.path().is_ident("vapoursynth_filter"));
140
141 let lifetimes = &input.generics.params;
143 let has_lifetime = !lifetimes.is_empty();
144
145 let struct_type = if has_lifetime {
147 quote! { #struct_name<'_> }
148 } else {
149 quote! { #struct_name }
150 };
151
152 let create_name = format!("{}Create", struct_name);
154 let getframe_name = format!("{}GetFrame", struct_name);
155 let free_name = format!("{}Free", struct_name);
156
157 let create_ident = syn::Ident::new(&create_name, struct_name.span());
158 let getframe_ident = syn::Ident::new(&getframe_name, struct_name.span());
159 let free_ident = syn::Ident::new(&free_name, struct_name.span());
160
161 let function_signature = quote! {
163 #[no_mangle]
164 pub unsafe extern "C" fn #create_ident(
165 in_: *const rustsynth::ffi::VSMap,
166 out: *mut rustsynth::ffi::VSMap,
167 user_data: *mut std::os::raw::c_void,
168 core: *mut rustsynth::ffi::VSCore,
169 vsapi: *const rustsynth::ffi::VSAPI,
170 )
171 };
172
173 let create = match arg.to_string().as_str() {
174 "video" => {
175 quote! {
176 #function_signature {
177 rustsynth::init_api(vsapi);
178 let api = &*vsapi;
179 std::panic::catch_unwind(|| {
180 let core_ref = rustsynth::core::CoreRef::from_ptr(core);
181 let in_map = rustsynth::map::MapRef::from_ptr(in_);
182 match <#struct_type>::from_args(&in_map, &core_ref) {
184 Ok(filter_data) => {
185 let deps = filter_data.get_dependencies();
186 let deps_ffi: Vec<rustsynth::ffi::VSFilterDependency> = deps.iter()
187 .map(|d| d.as_ffi())
188 .collect();
189
190 let filter_mode = <#struct_type>::MODE;
192 let media_info = match filter_data.get_video_info() {
193 Ok(ai) => ai,
194 Err(error_msg) => {
195 let error_cstr = std::ffi::CString::new(error_msg).unwrap_or_else(|_| {
196 std::ffi::CString::new("Failed to get video info").unwrap()
197 });
198 api.mapSetError.unwrap()(out, error_cstr.as_ptr());
199 return;
200 }
201 };
202
203 let data_ptr = Box::into_raw(Box::new(filter_data)) as *mut std::os::raw::c_void;
205 let filter_name = std::ffi::CString::new(<#struct_type>::NAME).unwrap();
206
207 api.createVideoFilter.unwrap()(
208 out,
209 filter_name.as_ptr(),
210 &media_info.as_ffi() as *const rustsynth::ffi::VSVideoInfo,
211 Some(#getframe_ident),
212 Some(#free_ident),
213 filter_mode.as_ffi() as i32,
214 deps_ffi.as_ptr(),
215 deps_ffi.len() as i32,
216 data_ptr,
217 core,
218 );
219 },
220 Err(error_msg) => {
221 let error_cstr = std::ffi::CString::new(error_msg).unwrap_or_else(|_| {
222 std::ffi::CString::new("Filter creation failed").unwrap()
223 });
224 api.mapSetError.unwrap()(out, error_cstr.as_ptr());
225 }
226 }
227 }).unwrap_or_else(|_| {
228 api.mapSetError.unwrap()(out, b"Filter creation panicked\0".as_ptr() as *const std::os::raw::c_char);
229 });
230 }
231 }
232 }
233 "audio" => {
234 quote! {
235 #function_signature {
236 rustsynth::init_api(vsapi);
237 let api = &*vsapi;
238 std::panic::catch_unwind(|| {
239 let core_ref = rustsynth::core::CoreRef::from_ptr(core);
240 let in_map = rustsynth::map::Map::from_ptr(in_);
241 match <#struct_type>::from_args(&in_map, &core_ref) {
243 Ok(filter_data) => {
244 let deps = filter_data.get_dependencies();
245 let deps_ffi: Vec<rustsynth::ffi::VSFilterDependency> = deps.iter()
246 .map(|d| d.as_ffi())
247 .collect();
248
249 let filter_mode = <#struct_type>::MODE;
251 let media_info = match filter_data.get_audio_info() {
252 Ok(ai) => ai,
253 Err(error_msg) => {
254 let error_cstr = std::ffi::CString::new(error_msg).unwrap_or_else(|_| {
255 std::ffi::CString::new("Failed to get audio info").unwrap()
256 });
257 api.mapSetError.unwrap()(out, error_cstr.as_ptr());
258 return;
259 }
260 };
261
262 let data_ptr = Box::into_raw(Box::new(filter_data)) as *mut std::os::raw::c_void;
264 let filter_name = std::ffi::CString::new(<#struct_type>::NAME).unwrap();
265
266 api.createAudioFilter.unwrap()(
267 out,
268 filter_name.as_ptr(),
269 &media_info,
270 Some(#getframe_ident),
271 Some(#free_ident),
272 filter_mode.as_ffi(),
273 deps_ffi.as_ptr(),
274 deps_ffi.len() as i32,
275 data_ptr,
276 core,
277 );
278 },
279 Err(error_msg) => {
280 let error_cstr = std::ffi::CString::new(error_msg).unwrap_or_else(|_| {
281 std::ffi::CString::new("Filter creation failed").unwrap()
282 });
283 api.mapSetError.unwrap()(out, error_cstr.as_ptr());
284 }
285 }
286 }).unwrap_or_else(|_| {
287 api.mapSetError.unwrap()(out, b"Filter creation panicked\0".as_ptr() as *const std::os::raw::c_char);
288 });
289 }
290 }
291 }
292 _ => {
293 return Err(syn::Error::new_spanned(
294 arg.to_string(),
295 "Unsupported filter type. Use 'video' or 'audio'",
296 ))
297 }
298 };
299
300 let expanded = quote! {
301 #input
303
304 #create
305
306 #[no_mangle]
308 pub unsafe extern "C" fn #getframe_ident(
309 n: i32,
310 activation_reason: i32,
311 instance_data: *mut std::os::raw::c_void,
312 frame_data: *mut *mut std::os::raw::c_void,
313 frame_ctx: *mut rustsynth::ffi::VSFrameContext,
314 core: *mut rustsynth::ffi::VSCore,
315 vsapi: *const rustsynth::ffi::VSAPI,
316 ) -> *const rustsynth::ffi::VSFrame {
317 let api = &*vsapi;
318
319 std::panic::catch_unwind(|| {
320 let filter = &mut *(instance_data as *mut #struct_type);
321 let core_ref = rustsynth::core::CoreRef::from_ptr(core);
322 let frame_ctx_wrapper = rustsynth::frame::FrameContext::from_ptr(frame_ctx);
323 let activation = rustsynth::filter::ActivationReason::from_ffi(activation_reason);
324
325 match activation {
326 rustsynth::filter::ActivationReason::Initial => {
327 filter.request_input_frames(n, &frame_ctx_wrapper);
329 std::ptr::null()
330 },
331 rustsynth::filter::ActivationReason::AllFramesReady => {
332 let frame_data_array: &[u8; 4] = if (*frame_data).is_null() {
335 &[0; 4]
336 } else {
337 std::slice::from_raw_parts(*frame_data as *const u8, 4).try_into().unwrap_or(&[0; 4])
338 };
339
340 match filter.process_frame(n, frame_data_array, &frame_ctx_wrapper, core_ref) {
341 Ok(output_frame) => {
342 output_frame.as_ptr()
343 },
344 Err(error_msg) => {
345 let error_cstr = std::ffi::CString::new(error_msg).unwrap_or_else(|_| {
346 std::ffi::CString::new("Frame processing failed").unwrap()
347 });
348 api.setFilterError.unwrap()(error_cstr.as_ptr(), frame_ctx);
349
350 if !(*frame_data).is_null() {
352 filter.cleanup_frame_data(frame_data_array);
353 *frame_data = std::ptr::null_mut();
354 }
355 std::ptr::null()
356 }
357 }
358 },
359 rustsynth::filter::ActivationReason::Error => {
360 if !(*frame_data).is_null() {
362 let frame_data_array: &[u8; 4] = std::slice::from_raw_parts(*frame_data as *const u8, 4).try_into().unwrap_or(&[0; 4]);
363 filter.cleanup_frame_data(frame_data_array);
364 *frame_data = std::ptr::null_mut();
365 }
366 std::ptr::null()
367 }
368 }
369 }).unwrap_or_else(|_| {
370 api.setFilterError.unwrap()(
371 b"Frame processing panicked\0".as_ptr() as *const std::os::raw::c_char,
372 frame_ctx
373 );
374
375 if !(*frame_data).is_null() {
376 *frame_data = std::ptr::null_mut();
377 }
378 std::ptr::null()
379 })
380 }
381
382 #[no_mangle]
384 pub unsafe extern "C" fn #free_ident(
385 instance_data: *mut std::os::raw::c_void,
386 core: *mut rustsynth::ffi::VSCore,
387 vsapi: *const rustsynth::ffi::VSAPI,
388 ) {
389 if !instance_data.is_null() {
390 let _ = std::panic::catch_unwind(|| {
391 let filter = Box::from_raw(instance_data as *mut #struct_type);
392 filter.cleanup();
393 });
395 }
396 }
397
398 impl<#lifetimes> #struct_name<#lifetimes> {
400 fn register_filter(
401 plugin: *mut rustsynth::ffi::VSPlugin,
402 vspapi: *const rustsynth::ffi::VSPLUGINAPI
403 ) {
404 unsafe {
405 let api = &*vspapi;
406 let filter_name = std::ffi::CString::new(Self::NAME).unwrap();
407 let args_spec = std::ffi::CString::new(Self::ARGS).unwrap();
408 let return_spec = std::ffi::CString::new(Self::RETURNTYPE).unwrap();
409
410 if let Some(register_fn) = api.registerFunction {
411 let ret = register_fn(
412 filter_name.as_ptr(),
413 args_spec.as_ptr(),
414 return_spec.as_ptr(),
415 Some(#create_ident),
416 std::ptr::null_mut(),
417 plugin
418 );
419 if ret == 0 {
420 eprintln!("Failed to register filter '{}'", Self::NAME);
421 }
422 } else {
423 eprintln!("registerFunction API is NULL - cannot register filter '{}'", Self::NAME);
424 }
425 }
426 }
427 }
428 };
429
430 Ok(expanded)
431}