1use crate::clap::{
2 CLAP_EXT_AUDIO_PORTS, CLAP_EXT_PARAMS, CLAP_VERSION, ClapAudioPortInfo, ClapHost,
3 ClapParamInfo, ClapPluginAudioPorts, ClapPluginEntry, ClapPluginFactory, ClapPluginParams,
4};
5use serde::{Deserialize, Serialize};
6use std::ffi::{CStr, CString, c_char, c_void};
7use std::path::{Path, PathBuf};
8use std::ptr;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct ParamMetadata {
12 pub id: u32,
13 pub name: String,
14 pub module: String,
15 pub min_value: f64,
16 pub max_value: f64,
17 pub default_value: f64,
18 pub flags: u32,
19}
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct AudioPortMetadata {
23 pub id: u32,
24 pub name: String,
25 pub channel_count: u32,
26 pub flags: u32,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct PluginMetadata {
31 pub id: String,
32 pub name: String,
33 pub vendor: String,
34 pub version: String,
35 pub description: String,
36 pub params: Vec<ParamMetadata>,
37 pub audio_inputs: Vec<AudioPortMetadata>,
38 pub audio_outputs: Vec<AudioPortMetadata>,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct ScanResult {
43 pub format: String,
44 pub path: String,
45 pub plugins: Vec<PluginMetadata>,
46 pub error: Option<String>,
47}
48
49#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
50pub struct ClapPluginInfo {
51 pub name: String,
52 pub path: String,
53 pub capabilities: Option<ClapPluginCapabilities>,
54}
55
56#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
57pub struct ClapPluginCapabilities {
58 pub has_gui: bool,
59 pub gui_apis: Vec<String>,
60 pub supports_embedded: bool,
61 pub supports_floating: bool,
62 pub has_params: bool,
63 pub has_state: bool,
64 pub audio_inputs: usize,
65 pub audio_outputs: usize,
66 pub midi_inputs: usize,
67 pub midi_outputs: usize,
68}
69
70unsafe extern "C" fn dummy_get_extension(_: *const ClapHost, _: *const c_char) -> *const c_void {
71 ptr::null()
72}
73unsafe extern "C" fn dummy_request_restart(_: *const ClapHost) {}
74unsafe extern "C" fn dummy_request_process(_: *const ClapHost) {}
75unsafe extern "C" fn dummy_request_callback(_: *const ClapHost) {}
76
77fn cstr_to_string(ptr: *const c_char) -> String {
78 if ptr.is_null() {
79 return String::new();
80 }
81 unsafe { CStr::from_ptr(ptr) }
82 .to_string_lossy()
83 .into_owned()
84}
85
86fn clap_version_is_compatible(plugin_version: &crate::clap::ClapVersion) -> bool {
87 plugin_version.major == crate::clap::CLAP_VERSION.major
88 && plugin_version.minor <= crate::clap::CLAP_VERSION.minor
89}
90
91pub fn scan_clap_plugin(plugin_path: &str) -> ScanResult {
92 let path = Path::new(plugin_path);
93 if !path.exists() {
94 return ScanResult {
95 format: "clap".to_string(),
96 path: plugin_path.to_string(),
97 plugins: Vec::new(),
98 error: Some(format!("path does not exist: {plugin_path}")),
99 };
100 }
101
102 let library = match unsafe { libloading::Library::new(path) } {
103 Ok(lib) => lib,
104 Err(e) => {
105 return ScanResult {
106 format: "clap".to_string(),
107 path: plugin_path.to_string(),
108 plugins: Vec::new(),
109 error: Some(format!("failed to load library: {e}")),
110 };
111 }
112 };
113
114 let entry: libloading::Symbol<*const ClapPluginEntry> =
115 match unsafe { library.get(b"clap_entry\0") } {
116 Ok(sym) => sym,
117 Err(e) => {
118 return ScanResult {
119 format: "clap".to_string(),
120 path: plugin_path.to_string(),
121 plugins: Vec::new(),
122 error: Some(format!("clap_entry not found: {e}")),
123 };
124 }
125 };
126
127 let entry = unsafe { &**entry };
128
129 if let Some(init) = entry.init {
130 let path_c = match CString::new(plugin_path) {
131 Ok(s) => s,
132 Err(_) => {
133 return ScanResult {
134 format: "clap".to_string(),
135 path: plugin_path.to_string(),
136 plugins: Vec::new(),
137 error: Some("plugin path contains null bytes".to_string()),
138 };
139 }
140 };
141 if !unsafe { init(path_c.as_ptr()) } {
142 return ScanResult {
143 format: "clap".to_string(),
144 path: plugin_path.to_string(),
145 plugins: Vec::new(),
146 error: Some("clap_entry.init() failed".to_string()),
147 };
148 }
149 }
150
151 let factory = if let Some(get_factory) = entry.get_factory {
152 let factory_id = CString::new("clap.plugin-factory").unwrap();
153 let factory_ptr = unsafe { get_factory(factory_id.as_ptr()) };
154 if factory_ptr.is_null() {
155 return ScanResult {
156 format: "clap".to_string(),
157 path: plugin_path.to_string(),
158 plugins: Vec::new(),
159 error: Some("clap.plugin-factory not found".to_string()),
160 };
161 }
162 unsafe { &*(factory_ptr as *const ClapPluginFactory) }
163 } else {
164 return ScanResult {
165 format: "clap".to_string(),
166 path: plugin_path.to_string(),
167 plugins: Vec::new(),
168 error: Some("clap_entry.get_factory is null".to_string()),
169 };
170 };
171
172 let count = factory
173 .get_plugin_count
174 .map(|f| unsafe { f(factory) })
175 .unwrap_or(0);
176
177 let mut host = ClapHost {
178 clap_version: CLAP_VERSION,
179 host_data: ptr::null_mut(),
180 name: c"maolan-plugin-host".as_ptr(),
181 vendor: c"Maolan".as_ptr(),
182 url: c"https://maolan.github.io".as_ptr(),
183 version: c"0.1.0".as_ptr(),
184 get_extension: Some(dummy_get_extension),
185 request_restart: Some(dummy_request_restart),
186 request_process: Some(dummy_request_process),
187 request_callback: Some(dummy_request_callback),
188 };
189 host.host_data = (&mut host as *mut ClapHost).cast::<c_void>();
190
191 let mut plugins = Vec::with_capacity(count as usize);
192
193 for i in 0..count {
194 let desc = factory
195 .get_plugin_descriptor
196 .map(|f| unsafe { f(factory, i) })
197 .unwrap_or(ptr::null());
198 if desc.is_null() {
199 continue;
200 }
201 let desc = unsafe { &*desc };
202
203 if !clap_version_is_compatible(&desc.clap_version) {
204 continue;
205 }
206
207 let plugin_id = cstr_to_string(desc.id);
208 let plugin_id_c = match CString::new(&*plugin_id) {
209 Ok(s) => s,
210 Err(_) => continue,
211 };
212
213 let plugin = factory
214 .create_plugin
215 .map(|f| unsafe { f(factory, &host, plugin_id_c.as_ptr()) })
216 .unwrap_or(ptr::null());
217 if plugin.is_null() {
218 continue;
219 }
220
221 let init_ok = unsafe { (*plugin).init }
222 .map(|f| unsafe { f(plugin) })
223 .unwrap_or(false);
224 if !init_ok {
225 unsafe {
226 if let Some(destroy) = (*plugin).destroy {
227 destroy(plugin);
228 }
229 }
230 continue;
231 }
232
233 let mut params = Vec::new();
234 let mut audio_inputs = Vec::new();
235 let mut audio_outputs = Vec::new();
236
237 unsafe {
238 let ext = (*plugin)
239 .get_extension
240 .map(|f| f(plugin, CLAP_EXT_PARAMS.as_ptr()));
241 if let Some(ptr) = ext
242 && !ptr.is_null()
243 {
244 let p = &*(ptr as *const ClapPluginParams);
245 let count = p.count.map(|f| f(plugin)).unwrap_or(0);
246 for pi in 0..count {
247 let mut info = ClapParamInfo {
248 id: 0,
249 flags: 0,
250 cookie: ptr::null_mut(),
251 name: [0; 256],
252 module: [0; 1024],
253 min_value: 0.0,
254 max_value: 0.0,
255 default_value: 0.0,
256 };
257 if p.get_info
258 .map(|f| f(plugin, pi, &mut info))
259 .unwrap_or(false)
260 {
261 let name = CStr::from_ptr(info.name.as_ptr())
262 .to_string_lossy()
263 .into_owned();
264 let module = CStr::from_ptr(info.module.as_ptr())
265 .to_string_lossy()
266 .into_owned();
267 params.push(ParamMetadata {
268 id: info.id,
269 name,
270 module,
271 min_value: info.min_value,
272 max_value: info.max_value,
273 default_value: info.default_value,
274 flags: info.flags,
275 });
276 }
277 }
278 }
279 }
280
281 unsafe {
282 let ext = (*plugin)
283 .get_extension
284 .map(|f| f(plugin, CLAP_EXT_AUDIO_PORTS.as_ptr()));
285 if let Some(ptr) = ext
286 && !ptr.is_null()
287 {
288 let ap = &*(ptr as *const ClapPluginAudioPorts);
289 let in_count = ap.count.map(|f| f(plugin, true)).unwrap_or(0);
290 let out_count = ap.count.map(|f| f(plugin, false)).unwrap_or(0);
291 for pi in 0..in_count {
292 let mut info = ClapAudioPortInfo {
293 id: 0,
294 name: [0; 256],
295 flags: 0,
296 channel_count: 0,
297 port_type: ptr::null(),
298 in_place_pair: 0,
299 };
300 if ap
301 .get
302 .map(|f| f(plugin, pi, true, &mut info))
303 .unwrap_or(false)
304 {
305 let name = CStr::from_ptr(info.name.as_ptr())
306 .to_string_lossy()
307 .into_owned();
308 audio_inputs.push(AudioPortMetadata {
309 id: info.id,
310 name,
311 channel_count: info.channel_count,
312 flags: info.flags,
313 });
314 }
315 }
316 for pi in 0..out_count {
317 let mut info = ClapAudioPortInfo {
318 id: 0,
319 name: [0; 256],
320 flags: 0,
321 channel_count: 0,
322 port_type: ptr::null(),
323 in_place_pair: 0,
324 };
325 if ap
326 .get
327 .map(|f| f(plugin, pi, false, &mut info))
328 .unwrap_or(false)
329 {
330 let name = CStr::from_ptr(info.name.as_ptr())
331 .to_string_lossy()
332 .into_owned();
333 audio_outputs.push(AudioPortMetadata {
334 id: info.id,
335 name,
336 channel_count: info.channel_count,
337 flags: info.flags,
338 });
339 }
340 }
341 }
342 }
343
344 plugins.push(PluginMetadata {
345 id: plugin_id,
346 name: cstr_to_string(desc.name),
347 vendor: cstr_to_string(desc.vendor),
348 version: cstr_to_string(desc.version),
349 description: cstr_to_string(desc.description),
350 params,
351 audio_inputs,
352 audio_outputs,
353 });
354
355 unsafe {
356 if let Some(destroy) = (*plugin).destroy {
357 destroy(plugin);
358 }
359 }
360 }
361
362 if let Some(deinit) = entry.deinit {
363 unsafe { deinit() };
364 }
365
366 ScanResult {
367 format: "clap".to_string(),
368 path: plugin_path.to_string(),
369 plugins,
370 error: None,
371 }
372}
373
374#[cfg(any(
375 target_os = "macos",
376 target_os = "linux",
377 target_os = "freebsd",
378 target_os = "openbsd"
379))]
380fn default_clap_search_roots() -> Vec<PathBuf> {
381 let mut roots = Vec::new();
382 #[cfg(target_os = "macos")]
383 {
384 crate::paths::push_macos_audio_plugin_roots(&mut roots, "CLAP");
385 }
386 #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "openbsd"))]
387 {
388 crate::paths::push_unix_plugin_roots(&mut roots, "clap");
389 }
390 roots
391}
392
393#[cfg(windows)]
394fn default_clap_search_roots() -> Vec<PathBuf> {
395 let mut roots = Vec::new();
396 crate::paths::push_windows_clap_roots(&mut roots);
397 roots
398}
399
400#[cfg(not(any(
401 target_os = "macos",
402 target_os = "linux",
403 target_os = "freebsd",
404 target_os = "openbsd",
405 target_os = "windows"
406)))]
407fn default_clap_search_roots() -> Vec<PathBuf> {
408 Vec::new()
409}
410
411fn is_supported_clap_binary(path: &Path) -> bool {
412 path.extension()
413 .is_some_and(|ext| ext.eq_ignore_ascii_case("clap"))
414}
415
416fn scan_clap_bundle(path: &Path, scan_capabilities: bool) -> Vec<ClapPluginInfo> {
417 use crate::clap::{
418 ClapPluginAudioPorts, ClapPluginEntry, ClapPluginFactory, ClapPluginGui,
419 ClapPluginNotePorts, ClapPluginParams,
420 };
421
422 let path_str = path.to_string_lossy().to_string();
423 let factory_id = c"clap.plugin-factory";
424 let mut host = ClapHost {
425 clap_version: CLAP_VERSION,
426 host_data: ptr::null_mut(),
427 name: c"Maolan".as_ptr(),
428 vendor: c"Maolan".as_ptr(),
429 url: c"https://example.invalid".as_ptr(),
430 version: c"0.0.1".as_ptr(),
431 get_extension: Some(dummy_get_extension),
432 request_restart: Some(dummy_request_restart),
433 request_process: Some(dummy_request_process),
434 request_callback: Some(dummy_request_callback),
435 };
436 host.host_data = (&mut host as *mut ClapHost).cast::<c_void>();
437
438 let lib = match unsafe { libloading::Library::new(path) } {
439 Ok(l) => l,
440 Err(_) => {
441 return vec![ClapPluginInfo {
442 name: path
443 .file_stem()
444 .map(|s| s.to_string_lossy().to_string())
445 .unwrap_or_else(|| path_str.clone()),
446 path: path_str,
447 capabilities: None,
448 }];
449 }
450 };
451
452 let entry: libloading::Symbol<*const ClapPluginEntry> =
453 match unsafe { lib.get(b"clap_entry\0") } {
454 Ok(sym) => sym,
455 Err(_) => {
456 return vec![ClapPluginInfo {
457 name: path
458 .file_stem()
459 .map(|s| s.to_string_lossy().to_string())
460 .unwrap_or_else(|| path_str.clone()),
461 path: path_str,
462 capabilities: None,
463 }];
464 }
465 };
466 let entry = unsafe { &**entry };
467
468 if let Some(init) = entry.init {
469 let path_c = match CString::new(&*path_str) {
470 Ok(s) => s,
471 Err(_) => {
472 return vec![ClapPluginInfo {
473 name: path_str.clone(),
474 path: path_str,
475 capabilities: None,
476 }];
477 }
478 };
479 if !unsafe { init(path_c.as_ptr()) } {
480 return vec![ClapPluginInfo {
481 name: path_str.clone(),
482 path: path_str,
483 capabilities: None,
484 }];
485 }
486 }
487
488 let factory = if let Some(get_factory) = entry.get_factory {
489 let factory_ptr = unsafe { get_factory(factory_id.as_ptr()) };
490 if factory_ptr.is_null() {
491 return vec![ClapPluginInfo {
492 name: path_str.clone(),
493 path: path_str,
494 capabilities: None,
495 }];
496 }
497 unsafe { &*(factory_ptr as *const ClapPluginFactory) }
498 } else {
499 return vec![ClapPluginInfo {
500 name: path_str.clone(),
501 path: path_str,
502 capabilities: None,
503 }];
504 };
505
506 let count = factory
507 .get_plugin_count
508 .map(|f| unsafe { f(factory) })
509 .unwrap_or(0);
510
511 let mut out = Vec::with_capacity(count as usize);
512
513 for i in 0..count {
514 let desc = factory
515 .get_plugin_descriptor
516 .map(|f| unsafe { f(factory, i) })
517 .unwrap_or(ptr::null());
518 if desc.is_null() {
519 continue;
520 }
521 let desc = unsafe { &*desc };
522 if !clap_version_is_compatible(&desc.clap_version) {
523 continue;
524 }
525 let name = cstr_to_string(desc.name);
526 let plugin_id = cstr_to_string(desc.id);
527 let plugin_id_c = match CString::new(&*plugin_id) {
528 Ok(s) => s,
529 Err(_) => continue,
530 };
531
532 let plugin = factory
533 .create_plugin
534 .map(|f| unsafe { f(factory, &host, plugin_id_c.as_ptr()) })
535 .unwrap_or(ptr::null());
536 if plugin.is_null() {
537 continue;
538 }
539 let init_ok = unsafe { (*plugin).init }
540 .map(|f| unsafe { f(plugin) })
541 .unwrap_or(false);
542 if !init_ok {
543 unsafe {
544 if let Some(destroy) = (*plugin).destroy {
545 destroy(plugin);
546 }
547 }
548 continue;
549 }
550
551 let mut capabilities = None;
552 if scan_capabilities {
553 let mut caps = ClapPluginCapabilities {
554 has_gui: false,
555 gui_apis: Vec::new(),
556 supports_embedded: false,
557 supports_floating: false,
558 has_params: false,
559 has_state: false,
560 audio_inputs: 0,
561 audio_outputs: 0,
562 midi_inputs: 0,
563 midi_outputs: 0,
564 };
565
566 unsafe {
567 let ext = (*plugin)
568 .get_extension
569 .map(|f| f(plugin, c"clap.gui".as_ptr()));
570 if let Some(ptr) = ext
571 && !ptr.is_null()
572 {
573 let gui = &*(ptr as *const ClapPluginGui);
574 caps.has_gui = gui
575 .is_api_supported
576 .map(|f| f(plugin, c"x11".as_ptr(), true))
577 .unwrap_or(false)
578 || gui
579 .is_api_supported
580 .map(|f| f(plugin, c"win32".as_ptr(), true))
581 .unwrap_or(false)
582 || gui
583 .is_api_supported
584 .map(|f| f(plugin, c"cocoa".as_ptr(), true))
585 .unwrap_or(false);
586 if caps.has_gui {
587 caps.gui_apis = vec!["x11".to_string()];
588 caps.supports_embedded = true;
589 caps.supports_floating = gui
590 .is_api_supported
591 .map(|f| f(plugin, ptr::null(), false))
592 .unwrap_or(false);
593 }
594 }
595 }
596
597 unsafe {
598 let ext = (*plugin)
599 .get_extension
600 .map(|f| f(plugin, CLAP_EXT_PARAMS.as_ptr()));
601 if let Some(ptr) = ext
602 && !ptr.is_null()
603 {
604 let p = &*(ptr as *const ClapPluginParams);
605 caps.has_params = p.count.map(|f| f(plugin)).unwrap_or(0) > 0;
606 }
607 }
608
609 unsafe {
610 let ext = (*plugin)
611 .get_extension
612 .map(|f| f(plugin, c"clap.state".as_ptr()));
613 if let Some(ptr) = ext
614 && !ptr.is_null()
615 {
616 caps.has_state = true;
617 }
618 }
619
620 unsafe {
621 let ext = (*plugin)
622 .get_extension
623 .map(|f| f(plugin, CLAP_EXT_AUDIO_PORTS.as_ptr()));
624 if let Some(ptr) = ext
625 && !ptr.is_null()
626 {
627 let ap = &*(ptr as *const ClapPluginAudioPorts);
628 let in_count = ap.count.map(|f| f(plugin, true)).unwrap_or(0);
629 let out_count = ap.count.map(|f| f(plugin, false)).unwrap_or(0);
630 for pi in 0..in_count {
631 let mut info = ClapAudioPortInfo {
632 id: 0,
633 name: [0; 256],
634 flags: 0,
635 channel_count: 0,
636 port_type: ptr::null(),
637 in_place_pair: 0,
638 };
639 if ap
640 .get
641 .map(|f| f(plugin, pi, true, &mut info))
642 .unwrap_or(false)
643 {
644 caps.audio_inputs += info.channel_count as usize;
645 }
646 }
647 for pi in 0..out_count {
648 let mut info = ClapAudioPortInfo {
649 id: 0,
650 name: [0; 256],
651 flags: 0,
652 channel_count: 0,
653 port_type: ptr::null(),
654 in_place_pair: 0,
655 };
656 if ap
657 .get
658 .map(|f| f(plugin, pi, false, &mut info))
659 .unwrap_or(false)
660 {
661 caps.audio_outputs += info.channel_count as usize;
662 }
663 }
664 }
665 }
666
667 unsafe {
668 let ext = (*plugin)
669 .get_extension
670 .map(|f| f(plugin, c"clap.note-ports".as_ptr()));
671 if let Some(ptr) = ext
672 && !ptr.is_null()
673 {
674 let np = &*(ptr as *const ClapPluginNotePorts);
675 caps.midi_inputs = np.count.map(|f| f(plugin, true)).unwrap_or(0) as usize;
676 caps.midi_outputs = np.count.map(|f| f(plugin, false)).unwrap_or(0) as usize;
677 }
678 }
679
680 capabilities = Some(caps);
681 }
682
683 unsafe {
684 if let Some(destroy) = (*plugin).destroy {
685 destroy(plugin);
686 }
687 }
688
689 out.push(ClapPluginInfo {
690 name,
691 path: format!("{}::{}", path_str, plugin_id),
692 capabilities,
693 });
694 }
695
696 if let Some(deinit) = entry.deinit {
697 unsafe { deinit() };
698 }
699
700 if out.is_empty() {
701 out.push(ClapPluginInfo {
702 name: path
703 .file_stem()
704 .map(|s| s.to_string_lossy().to_string())
705 .unwrap_or_else(|| path_str.clone()),
706 path: path_str,
707 capabilities: None,
708 });
709 }
710
711 out
712}
713
714fn collect_clap_plugins(root: &Path, out: &mut Vec<ClapPluginInfo>, scan_capabilities: bool) {
715 let Ok(entries) = std::fs::read_dir(root) else {
716 return;
717 };
718 for entry in entries.flatten() {
719 let path = entry.path();
720 let Ok(ft) = entry.file_type() else {
721 continue;
722 };
723 if ft.is_dir() {
724 if path
725 .file_name()
726 .and_then(|name| name.to_str())
727 .is_some_and(|name| {
728 matches!(
729 name,
730 "deps" | "build" | "incremental" | ".fingerprint" | "examples"
731 )
732 })
733 {
734 continue;
735 }
736 collect_clap_plugins(&path, out, scan_capabilities);
737 continue;
738 }
739
740 if is_supported_clap_binary(&path) {
741 let infos = scan_clap_bundle(&path, scan_capabilities);
742 if infos.is_empty() {
743 let name = path
744 .file_stem()
745 .map(|s| s.to_string_lossy().to_string())
746 .unwrap_or_else(|| path.to_string_lossy().to_string());
747 out.push(ClapPluginInfo {
748 name,
749 path: path.to_string_lossy().to_string(),
750 capabilities: None,
751 });
752 } else {
753 out.extend(infos);
754 }
755 }
756 }
757}
758
759pub fn scan_clap_plugins(scan_capabilities: bool) -> Vec<ClapPluginInfo> {
760 let mut roots = default_clap_search_roots();
761
762 if let Ok(extra) = std::env::var("CLAP_PATH") {
763 for p in std::env::split_paths(&extra) {
764 if !p.as_os_str().is_empty() {
765 roots.push(p);
766 }
767 }
768 }
769
770 let mut out = Vec::new();
771 for root in roots {
772 collect_clap_plugins(&root, &mut out, scan_capabilities);
773 }
774
775 out.sort_by_key(|a| a.name.to_lowercase());
776 out.dedup_by(|a, b| {
777 a.name.eq_ignore_ascii_case(&b.name) && a.path.eq_ignore_ascii_case(&b.path)
778 });
779 out
780}
781
782pub fn scan_vst3_plugins() -> Vec<crate::vst3::Vst3PluginInfo> {
783 crate::vst3::host::Vst3Host::new().list_plugins()
784}
785
786#[cfg(unix)]
787pub fn scan_lv2_plugins() -> Vec<crate::lv2::Lv2PluginInfo> {
788 crate::lv2::Lv2Host::new(48_000.0).list_plugins()
789}
790
791pub fn run_scan(format: &str, plugin_path: &str, output_path: Option<&str>) -> i32 {
792 let json = match format {
793 "clap" => {
794 if plugin_path == "--system" {
795 match serde_json::to_string_pretty(&scan_clap_plugins(false)) {
796 Ok(j) => j,
797 Err(_e) => {
798 return 1;
799 }
800 }
801 } else {
802 match serde_json::to_string_pretty(&scan_clap_plugin(plugin_path)) {
803 Ok(j) => j,
804 Err(_e) => {
805 return 1;
806 }
807 }
808 }
809 }
810 "vst3" => {
811 if plugin_path != "--system" {
812 return 1;
813 }
814 match serde_json::to_string_pretty(&scan_vst3_plugins()) {
815 Ok(j) => j,
816 Err(_e) => {
817 return 1;
818 }
819 }
820 }
821 #[cfg(unix)]
822 "lv2" => {
823 if plugin_path != "--system" {
824 return 1;
825 }
826 match serde_json::to_string_pretty(&scan_lv2_plugins()) {
827 Ok(j) => j,
828 Err(_e) => {
829 return 1;
830 }
831 }
832 }
833 _ => {
834 return 1;
835 }
836 };
837
838 if let Some(path) = output_path {
839 match std::fs::write(path, &json) {
840 Ok(()) => 0,
841 Err(_e) => 1,
842 }
843 } else {
844 0
845 }
846}