import os import platform import logging from typing import List, Dict, Any logger = logging.getLogger(__name__) class NativePluginScanner: """Quét thư mục custom chứa VST3 (.vst3) / Audio Unit (.component trên macOS) — spec DESKTOP VST (2026-08-08): POST /api/v1/desktop/plugins/scan.""" def __init__(self): self.os_type = platform.system() # 'Linux', 'Windows', 'Darwin' def scan_directories(self, target_paths: List[str]) -> List[Dict[str, Any]]: found_plugins = [] seen = set() for base_path in target_paths: if not base_path: continue if not os.path.exists(base_path): logger.warning(f"Scan path does not exist: {base_path}") continue for root, dirs, files in os.walk(base_path): for dir_name in dirs: full_path = os.path.join(root, dir_name) # VST3 = bundle directory *.vst3 if dir_name.endswith('.vst3'): plugin_name = dir_name[:-5] pid = f"vst3_{plugin_name.lower().replace(' ', '_')}" if pid in seen: continue seen.add(pid) found_plugins.append({ "id": pid, "name": plugin_name, "type": "VST3", "format": "VST3", "category": "Instrument", "vendor": self._read_vendor(full_path) or None, "path": full_path, "is_instrument": True, "has_native_gui": True }) # Audio Unit = bundle *.component (macOS only) elif dir_name.endswith('.component') and self.os_type == 'Darwin': plugin_name = dir_name[:-10] pid = f"au_{plugin_name.lower().replace(' ', '_')}" if pid in seen: continue seen.add(pid) found_plugins.append({ "id": pid, "name": plugin_name, "type": "AU", "format": "AudioUnit", "category": "Instrument", "vendor": self._read_vendor(full_path) or None, "path": full_path, "is_instrument": True, "has_native_gui": True }) return found_plugins @staticmethod def _read_vendor(bundle_path: str) -> str: """Đọc vendor từ Info.plist nếu có (macOS) — best-effort.""" try: plist = os.path.join(bundle_path, "Contents", "Info.plist") if os.path.exists(plist): with open(plist, "rb") as f: raw = f.read(65536) import plistlib try: data = plistlib.loads(raw) return data.get("CFBundleManufacturer") or data.get("NSHumanReadableCopyright") except Exception: return None except Exception: return None return None