1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{self, parse_macro_input, DeriveInput, Ident, ItemMod};
4
5#[proc_macro_derive(IntoOwnedMap)]
22pub fn into_owned_map_derive(input: TokenStream) -> TokenStream {
23 let ast = syn::parse(input).unwrap();
26
27 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#[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#[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 #[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 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_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 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 #[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 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 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 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 #[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 let core_ref = rustsynth::core::CoreRef::from_ptr(core);
215 let in_map = rustsynth::map::Map::from_ptr(in_);
216
217 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 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 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 #input
279
280 #create
281
282 #[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 filter.request_input_frames(n, &frame_ctx_wrapper);
305 std::ptr::null()
306 },
307 rustsynth::filter::ActivationReason::AllFramesReady => {
308 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 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 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 #[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 });
372 }
373 }
374
375 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}