fix(scan): fx_vst_bridge --scan timeout per-module 30s + emit UNKNOWN thay vi is_instrument=true

- RenderFxJob: safeScanModule chay tren worker thread + TerminateThread neu
  Module::create treo >30s (Ozone 11 Clarity treo vinh vien, SEH khong bat
  hang) -> scan khong bao gio ket thuc -> list_fx() timeout -> fx_paths rong
  -> filter /available vo hieu -> nut Synth hien ca VST FX
- Module crash/hang -> emit is_fx=false,is_instrument=false (UNKNOWN) thay vi
  is_instrument=true (truoc day xep VST FX crash vao danh sach instrument)
- vst_engine: list_scan_all() tra toan bo (FX+instrument+unknown); list_fx()
  loc is_fx tu cache chung
- plugins.py /available: loai ca fx_paths lan unknown_paths khoi
  vst_instruments -> nut Synth chi hien VSTi that
This commit is contained in:
2026-08-22 01:53:21 +00:00
parent 1a8b210063
commit dd2a320b2a
3 changed files with 88 additions and 25 deletions
+35 -15
View File
@@ -299,6 +299,7 @@ class PluginManager:
self.extra_vst_dirs = [d for d in (extra_vst_dirs or []) if d]
self._sf_scan_cache = None # cache for _scan_soundfonts()
self._fx_scan_cache = None # cache for list_fx()
self._fx_scan_all = None # cache for list_scan_all()
def _scan_plugins(self) -> dict:
plugins = {}
@@ -479,18 +480,33 @@ class PluginManager:
không introspect được nên bridge báo instrument — bỏ qua ở đây. Cache
process-wide; refresh=True quét lại (Plugins Manager → Scan).
"""
if not refresh and self._fx_scan_cache is not None:
return self._fx_scan_cache
all_entries = self.list_scan_all(refresh=refresh)
results = [e for e in all_entries if e.get("is_fx")]
results.sort(key=lambda x: x["name"].lower())
self._fx_scan_cache = results
return results
def list_scan_all(self, refresh=False) -> list:
"""Toàn bộ kết quả `fx_vst_bridge --scan` (FX + instrument + unknown).
Bridge scan giờ có timeout per-module (30s) và emit entry UNKNOWN
(is_fx=false, is_instrument=false) cho module crash/hang (vd Ozone 11
Clarity) — không xếp bừa vào instrument. `/available` dùng entry
unknown để LOẠI plugin đó khỏi nút Synth (không biết loại → không
hiện là instrument), còn list_fx() chỉ lấy is_fx=true.
"""
if not refresh and self._fx_scan_all is not None:
return self._fx_scan_all
from app.core.native_render import find_fx_bridge_exe
exe = find_fx_bridge_exe()
results = []
entries = []
if exe:
dirs = [d for d in [self.vst_dir] + self.extra_vst_dirs
if d and os.path.isdir(d)]
for d in dirs:
try:
r = subprocess.run([exe, "--scan", d], capture_output=True,
text=True, timeout=300)
text=True, timeout=600)
except Exception:
continue
if r.returncode != 0:
@@ -500,23 +516,27 @@ class PluginManager:
except Exception:
continue
for p in data.get("plugins", []):
if not p.get("is_fx"):
continue
path = p.get("path", "")
if not path:
continue
is_fx = bool(p.get("is_fx"))
is_instr = bool(p.get("is_instrument"))
name = p.get("name") or os.path.splitext(os.path.basename(path))[0]
results.append({"id": name, "name": name, "path": path,
"type": p.get("type", "VST3"),
"category": _classify_fx_category(name, path)})
entries.append({
"id": name, "name": name, "path": path,
"type": p.get("type", "VST3"),
"is_fx": is_fx,
"is_instrument": is_instr,
"is_unknown": not is_fx and not is_instr,
"category": _classify_fx_category(name, path),
})
seen, uniq = set(), []
for fx in results:
if fx["path"] in seen:
for e in entries:
if e["path"] in seen:
continue
seen.add(fx["path"])
uniq.append(fx)
uniq.sort(key=lambda x: x["name"].lower())
self._fx_scan_cache = uniq
seen.add(e["path"])
uniq.append(e)
self._fx_scan_all = uniq
return uniq
@staticmethod