|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411 |
- #!/usr/bin/env python3
- # -*- coding: utf-8 -*-
-
- # Carla plugin list code
- # Copyright (C) 2011-2022 Filipe Coelho <falktx@falktx.com>
- #
- # This program is free software; you can redistribute it and/or
- # modify it under the terms of the GNU General Public License as
- # published by the Free Software Foundation; either version 2 of
- # the License, or any later version.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # For a full copy of the GNU General Public License see the doc/GPL.txt file.
-
- # ---------------------------------------------------------------------------------------------------------------------
- # Imports (Global)
-
- import os
-
- from copy import deepcopy
- from subprocess import Popen, PIPE
- from PyQt5.QtCore import qWarning
-
- # ---------------------------------------------------------------------------------------------------------------------
- # Imports (Carla)
-
- from carla_backend import (
- BINARY_NATIVE,
- BINARY_NONE,
- PLUGIN_AU,
- PLUGIN_DSSI,
- PLUGIN_LADSPA,
- PLUGIN_LV2,
- PLUGIN_NONE,
- PLUGIN_SF2,
- PLUGIN_SFZ,
- PLUGIN_VST2,
- PLUGIN_VST3,
- PLUGIN_CLAP,
- )
-
- from carla_shared import (
- LINUX,
- MACOS,
- WINDOWS,
- )
-
- from carla_utils import getPluginCategoryAsString
-
- # ---------------------------------------------------------------------------------------------------------------------
- # Plugin Query (helper functions)
-
- def findBinaries(binPath, pluginType, OS):
- binaries = []
-
- if OS == "HAIKU":
- extensions = ("") if pluginType == PLUGIN_VST2 else (".so",)
- elif OS == "MACOS":
- extensions = (".dylib", ".so")
- elif OS == "WINDOWS":
- extensions = (".dll",)
- else:
- extensions = (".so",)
-
- for root, _, files in os.walk(binPath):
- for name in tuple(name for name in files if name.lower().endswith(extensions)):
- binaries.append(os.path.join(root, name))
-
- return binaries
-
- def findVST3Binaries(binPath):
- binaries = []
-
- for root, dirs, files in os.walk(binPath):
- for name in tuple(name for name in (files+dirs) if name.lower().endswith(".vst3")):
- binaries.append(os.path.join(root, name))
-
- return binaries
-
- def findCLAPBinaries(binPath):
- binaries = []
-
- for root, _, files in os.walk(binPath, followlinks=True):
- for name in tuple(name for name in files if name.lower().endswith(".clap")):
- binaries.append(os.path.join(root, name))
-
- return binaries
-
- def findLV2Bundles(bundlePath):
- bundles = []
-
- for root, _, _2 in os.walk(bundlePath, followlinks=True):
- if root == bundlePath:
- continue
- if os.path.exists(os.path.join(root, "manifest.ttl")):
- bundles.append(root)
-
- return bundles
-
- def findMacBundles(bundlePath, pluginType):
- bundles = []
-
- if pluginType == PLUGIN_VST2:
- extension = ".vst"
- elif pluginType == PLUGIN_VST3:
- extension = ".vst3"
- elif pluginType == PLUGIN_CLAP:
- extension = ".clap"
- else:
- return bundles
-
- for root, dirs, _ in os.walk(bundlePath, followlinks=True):
- for name in tuple(name for name in dirs if name.lower().endswith(extension)):
- bundles.append(os.path.join(root, name))
-
- return bundles
-
- def findFilenames(filePath, stype):
- filenames = []
-
- if stype == "sf2":
- extensions = (".sf2",".sf3",)
- else:
- return []
-
- for root, _, files in os.walk(filePath):
- for name in tuple(name for name in files if name.lower().endswith(extensions)):
- filenames.append(os.path.join(root, name))
-
- return filenames
-
- # ---------------------------------------------------------------------------------------------------------------------
- # Plugin Query
-
- # NOTE: this code is ugly, it is meant to be replaced, so let it be as-is for now
-
- PLUGIN_QUERY_API_VERSION = 12
-
- PyPluginInfo = {
- 'API': PLUGIN_QUERY_API_VERSION,
- 'valid': False,
- 'build': BINARY_NONE,
- 'type': PLUGIN_NONE,
- 'hints': 0x0,
- 'category': "",
- 'filename': "",
- 'name': "",
- 'label': "",
- 'maker': "",
- 'uniqueId': 0,
- 'audio.ins': 0,
- 'audio.outs': 0,
- 'cv.ins': 0,
- 'cv.outs': 0,
- 'midi.ins': 0,
- 'midi.outs': 0,
- 'parameters.ins': 0,
- 'parameters.outs': 0
- }
-
- gDiscoveryProcess = None
-
- def findWinePrefix(filename, recursionLimit = 10):
- if recursionLimit == 0 or len(filename) < 5 or "/" not in filename:
- return ""
-
- path = filename[:filename.rfind("/")]
-
- if os.path.isdir(path + "/dosdevices"):
- return path
-
- return findWinePrefix(path, recursionLimit-1)
-
- def runCarlaDiscovery(itype, stype, filename, tool, wineSettings=None):
- if not os.path.exists(tool):
- qWarning(f"runCarlaDiscovery() - tool '{tool}' does not exist")
- return []
-
- command = []
-
- if LINUX or MACOS:
- command.append("env")
- command.append("LANG=C")
- command.append("LD_PRELOAD=")
- if wineSettings is not None:
- command.append("WINEDEBUG=-all")
-
- if wineSettings['autoPrefix']:
- winePrefix = findWinePrefix(filename)
- else:
- winePrefix = ""
-
- if not winePrefix:
- envWinePrefix = os.getenv("WINEPREFIX")
-
- if envWinePrefix:
- winePrefix = envWinePrefix
- elif wineSettings['fallbackPrefix']:
- winePrefix = os.path.expanduser(wineSettings['fallbackPrefix'])
- else:
- winePrefix = os.path.expanduser("~/.wine")
-
- wineCMD = wineSettings['executable'] if wineSettings['executable'] else "wine"
-
- if tool.endswith("64.exe") and os.path.exists(wineCMD + "64"):
- wineCMD += "64"
-
- command.append("WINEPREFIX=" + winePrefix)
- command.append(wineCMD)
-
- command.append(tool)
- command.append(stype)
- command.append(filename)
-
- # pylint: disable=global-statement
- global gDiscoveryProcess
- # pylint: enable=global-statement
-
- # pylint: disable=consider-using-with
- gDiscoveryProcess = Popen(command, stdout=PIPE)
- # pylint: enable=consider-using-with
-
- pinfo = None
- plugins = []
- fakeLabel = os.path.basename(filename).rsplit(".", 1)[0]
-
- while True:
- try:
- line = gDiscoveryProcess.stdout.readline().decode("utf-8", errors="ignore")
- except:
- print("ERROR: discovery readline failed")
- break
-
- # line is valid, strip it
- if line:
- line = line.strip()
-
- # line is invalid, try poll() again
- elif gDiscoveryProcess.poll() is None:
- continue
-
- # line is invalid and poll() failed, stop here
- else:
- break
-
- if line == "carla-discovery::init::------------":
- pinfo = deepcopy(PyPluginInfo)
- pinfo['type'] = itype
- pinfo['filename'] = filename if filename != ":all" else ""
-
- elif line == "carla-discovery::end::------------":
- if pinfo is not None:
- plugins.append(pinfo)
- del pinfo
- pinfo = None
-
- elif line == "Segmentation fault":
- print(f"carla-discovery::crash::{filename} crashed during discovery")
-
- elif line.startswith("err:module:import_dll Library"):
- print(line)
-
- elif line.startswith("carla-discovery::info::"):
- print(f"{line} - {filename}")
-
- elif line.startswith("carla-discovery::warning::"):
- print(f"{line} - {filename}")
-
- elif line.startswith("carla-discovery::error::"):
- print(f"{line} - {filename}")
-
- elif line.startswith("carla-discovery::"):
- if pinfo is None:
- continue
-
- try:
- prop, value = line.replace("carla-discovery::", "").split("::", 1)
- except:
- continue
-
- # pylint: disable=unsupported-assignment-operation
- if prop == "build":
- if value.isdigit():
- pinfo['build'] = int(value)
- elif prop == "name":
- pinfo['name'] = value if value else fakeLabel
- elif prop == "label":
- pinfo['label'] = value if value else fakeLabel
- elif prop == "filename":
- pinfo['filename'] = value
- elif prop == "maker":
- pinfo['maker'] = value
- elif prop == "category":
- pinfo['category'] = value
- elif prop == "uniqueId":
- if value.isdigit():
- pinfo['uniqueId'] = int(value)
- elif prop == "hints":
- if value.isdigit():
- pinfo['hints'] = int(value)
- elif prop == "audio.ins":
- if value.isdigit():
- pinfo['audio.ins'] = int(value)
- elif prop == "audio.outs":
- if value.isdigit():
- pinfo['audio.outs'] = int(value)
- elif prop == "cv.ins":
- if value.isdigit():
- pinfo['cv.ins'] = int(value)
- elif prop == "cv.outs":
- if value.isdigit():
- pinfo['cv.outs'] = int(value)
- elif prop == "midi.ins":
- if value.isdigit():
- pinfo['midi.ins'] = int(value)
- elif prop == "midi.outs":
- if value.isdigit():
- pinfo['midi.outs'] = int(value)
- elif prop == "parameters.ins":
- if value.isdigit():
- pinfo['parameters.ins'] = int(value)
- elif prop == "parameters.outs":
- if value.isdigit():
- pinfo['parameters.outs'] = int(value)
- elif prop == "uri":
- if value:
- pinfo['label'] = value
- else:
- # cannot use empty URIs
- del pinfo
- pinfo = None
- continue
- else:
- print(f"{line} - {filename} (unknown property)")
- # pylint: enable=unsupported-assignment-operation
-
- tmp = gDiscoveryProcess
- gDiscoveryProcess = None
- del tmp
-
- return plugins
-
- def killDiscovery():
- # pylint: disable=global-variable-not-assigned
- global gDiscoveryProcess
- # pylint: enable=global-variable-not-assigned
-
- if gDiscoveryProcess is not None:
- gDiscoveryProcess.kill()
-
- def checkPluginCached(desc, ptype):
- pinfo = deepcopy(PyPluginInfo)
- pinfo['build'] = BINARY_NATIVE
- pinfo['type'] = ptype
- pinfo['hints'] = desc['hints']
- pinfo['name'] = desc['name']
- pinfo['label'] = desc['label']
- pinfo['maker'] = desc['maker']
- pinfo['category'] = getPluginCategoryAsString(desc['category'])
-
- pinfo['audio.ins'] = desc['audioIns']
- pinfo['audio.outs'] = desc['audioOuts']
-
- pinfo['cv.ins'] = desc['cvIns']
- pinfo['cv.outs'] = desc['cvOuts']
-
- pinfo['midi.ins'] = desc['midiIns']
- pinfo['midi.outs'] = desc['midiOuts']
-
- pinfo['parameters.ins'] = desc['parameterIns']
- pinfo['parameters.outs'] = desc['parameterOuts']
-
- if ptype == PLUGIN_LV2:
- pinfo['filename'], pinfo['label'] = pinfo['label'].split('\\' if WINDOWS else '/',1)
-
- elif ptype == PLUGIN_SFZ:
- pinfo['filename'] = pinfo['label']
- pinfo['label'] = pinfo['name']
-
- return pinfo
-
- def checkPluginLADSPA(filename, tool, wineSettings=None):
- return runCarlaDiscovery(PLUGIN_LADSPA, "LADSPA", filename, tool, wineSettings)
-
- def checkPluginDSSI(filename, tool, wineSettings=None):
- return runCarlaDiscovery(PLUGIN_DSSI, "DSSI", filename, tool, wineSettings)
-
- def checkPluginLV2(filename, tool, wineSettings=None):
- return runCarlaDiscovery(PLUGIN_LV2, "LV2", filename, tool, wineSettings)
-
- def checkPluginVST2(filename, tool, wineSettings=None):
- return runCarlaDiscovery(PLUGIN_VST2, "VST2", filename, tool, wineSettings)
-
- def checkPluginVST3(filename, tool, wineSettings=None):
- return runCarlaDiscovery(PLUGIN_VST3, "VST3", filename, tool, wineSettings)
-
- def checkPluginCLAP(filename, tool, wineSettings=None):
- return runCarlaDiscovery(PLUGIN_CLAP, "CLAP", filename, tool, wineSettings)
-
- def checkFileSF2(filename, tool):
- return runCarlaDiscovery(PLUGIN_SF2, "SF2", filename, tool)
-
- def checkFileSFZ(filename, tool):
- return runCarlaDiscovery(PLUGIN_SFZ, "SFZ", filename, tool)
-
- def checkAllPluginsAU(tool):
- return runCarlaDiscovery(PLUGIN_AU, "AU", ":all", tool)
|