diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..ad403a9 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,39 @@ +name: Build and Test + +on: [push, pull_request] + +env: + DEBIAN_FRONTEND: noninteractive + DIST_DIR: dist + +jobs: + build: + runs-on: ubuntu-latest + container: + image: ubuntu:22.04 + steps: + - uses: actions/checkout@v4 + + - name: Install build dependencies + run: | + apt-get update -qq + apt-get install -yqq --no-install-recommends \ + ca-certificates curl git \ + debhelper devscripts dpkg-dev meson pkg-config \ + python3-all-dev python3-pip python3-setuptools python3-venv python3-wheel \ + libpcre3-dev + curl -sLO https://launchpad.net/~kxstudio-debian/+archive/ubuntu/toolchain/+files/meson_1.9.1-1kxstudio2_all.deb + dpkg -i meson_1.9.1-1kxstudio2_all.deb + + - name: Build + run: ./scripts/build.sh + + - name: Test + run: ./scripts/test.sh + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: lilvlib-packages + path: dist/ + retention-days: 30 diff --git a/.gitignore b/.gitignore index 400f08b..f7fcbc1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ *.deb +*.ddeb .idea/ *.egg-info build -dist \ No newline at end of file +dist +__pycache__ diff --git a/Dockerfile b/Dockerfile index 58cf8ec..9b8b278 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,21 +1,73 @@ -# This Dockerfile can be used to build lilvlib using: -# - Ubuntu 18 -# - Python 3.6 +# Multi-stage Dockerfile for building and testing lilvlib +# Usage: +# docker build -t lilvlib-build . +# docker run --rm -v $(pwd)/dist:/dist lilvlib-build +# +# To run only the build stage (skip tests): +# docker build --target builder -t lilvlib-builder . -FROM moddevices/devtools:ub18-py36 +# ============================================================================= +# Stage 1: Builder - compile python3-lilv and build wheel +# ============================================================================= +FROM ubuntu:22.04 AS builder -LABEL Alexandre Cunha +ENV DEBIAN_FRONTEND=noninteractive -RUN mkdir /root/.ssh -RUN touch /root/.ssh/known_hosts -RUN ssh-keyscan github.com >> /root/.ssh/known_hosts +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates \ + curl \ + debhelper \ + devscripts \ + dpkg-dev \ + git \ + libpcre3-dev \ + meson \ + pkg-config \ + python3-all-dev \ + python3-pip \ + python3-setuptools \ + python3-wheel \ + && rm -rf /var/lib/apt/lists/* -RUN apt-get install --no-install-recommends -qy libpcre3-dev \ - devscripts pkg-config swig debhelper python3-numpy \ - && apt-get clean +# Ubuntu 22.04's meson (0.61.2) is incompatible with the build +RUN curl -sLO https://launchpad.net/~kxstudio-debian/+archive/ubuntu/toolchain/+files/meson_1.9.1-1kxstudio2_all.deb \ + && dpkg -i meson_1.9.1-1kxstudio2_all.deb \ + && rm meson_1.9.1-1kxstudio2_all.deb -COPY . /lilvlib -WORKDIR /lilvlib +WORKDIR /src +COPY . . -RUN ./build-python3-lilv.sh -RUN pip3 wheel -w wheelhouse . +ENV DIST_DIR=/artifacts +RUN ./scripts/build.sh + +# ============================================================================= +# Stage 2: Tester - validate artifacts in a clean environment +# ============================================================================= +FROM ubuntu:22.04 AS tester + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update && apt-get install -y --no-install-recommends \ + python3 \ + python3-pip \ + python3-venv \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /src +COPY --from=builder /artifacts /artifacts +COPY --from=builder /src/test.py /src/ +COPY --from=builder /src/lilvlib /src/lilvlib +COPY --from=builder /src/scripts /src/scripts + +ENV DIST_DIR=/artifacts +RUN apt-get update && ./scripts/test.sh && rm -rf /var/lib/apt/lists/* + +# ============================================================================= +# Final Stage: Artifacts - extract build outputs (runs after tests pass) +# ============================================================================= +FROM ubuntu:22.04 + +WORKDIR /artifacts +COPY --from=tester /artifacts /artifacts + +CMD ["sh", "-c", "cp -v /artifacts/* /dist/ 2>/dev/null || echo 'Mount a volume to /dist to extract artifacts: docker run --rm -v $(pwd)/dist:/dist '"] diff --git a/README.md b/README.md index c4e161f..9ee2e59 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ This repository contains a build script for lilvlib and its python3-lilv dependency, using the latest development version from git. -The reason why you need this script is because most distros have an outdated lilv binary, don't build lilv python modules or build the python2 modules instead of our required python3 version. +The reason why you need this script is because most distros have an outdated lilv binary or don't build lilv python modules. To start simply run: @@ -15,12 +15,13 @@ The generated package will contain `python3-lilv` and also everything needed for This includes: - LV2 headers and definitions -- MOD-SDK LV2 definitions +- Dargklass LV2 definitions - KXStudio LV2 definitions +- MOD Audio LV2 definitions - sord_validate (static binary) -- sord_validate_mod +- lv2_validate_mod -The `sord_validate_mod` is a helper script that runs `sord_validate` with the correct bundles. +The `lv2_validate_mod` is a helper script that runs `lv2_validate` with extra Darkglass, KXStudio and MOD Audio related bundles. Because this package uses the definitions copied during build (in `/opt`), it does not depend on any external resources. @@ -35,13 +36,15 @@ Install dpkg -i python3-lilv_0.22.1+git20170620_amd64.deb ``` +## Testing + Use ```bash /usr/bin/python3 ``` -```python +```python import lilvlib lilvlib.get_plugin_info_helper('') ``` diff --git a/build-python3-lilv.sh b/build-python3-lilv.sh index f951bc7..91b3c82 100755 --- a/build-python3-lilv.sh +++ b/build-python3-lilv.sh @@ -2,218 +2,307 @@ set -e -# ------------------------------------------------------------------------------------------- +cd "$(dirname ${0})" + +# --------------------------------------------------------------------------------------------------------------------- # Check dependencies -# sudo apt-get install --no-install-recommends debhelper devscripts dpkg-dev git pkg-config python3-all-dev python3-numpy subversion swig libpcre3-dev +# sudo apt-get install --no-install-recommends debhelper devscripts dpkg-dev git meson pkg-config python3-all-dev subversion libpcre3-dev if (which debuild > /dev/null); then true; else echo "debuild not found, please install it" - exit + exit 1 fi if (which git > /dev/null); then true; else echo "git not found, please install it" - exit + exit 1 +fi + +if (which meson > /dev/null); then true; else + echo "meson not found, please install it" + exit 1 fi if (which pkg-config > /dev/null); then true; else echo "pkg-config not found, please install it" - exit + exit 1 fi if (which python3 > /dev/null); then true; else echo "python3 not found, please install it" - exit -fi - -if (which swig > /dev/null); then true; else - echo "swig not found, please install it" - exit + exit 1 fi if (dpkg -l | grep debhelper > /dev/null); then true; else echo "debhelper not installed, please install it" - exit + exit 1 fi if (dpkg -l | grep dpkg-dev > /dev/null); then true; else echo "dpkg-dev not installed, please install it" - exit + exit 1 fi if (dpkg -l | grep python3-all-dev > /dev/null); then true; else echo "python3-all-dev not installed, please install it" - exit -fi - -if (dpkg -l | grep python3-numpy > /dev/null); then true; else - echo "python3-numpy not installed, please install it" - exit + exit 1 fi if (pkg-config --exists libpcre); then true; else echo "libpcre-dev not installed, please install it" - exit + exit 1 fi -# ------------------------------------------------------------------------------------------- +if [ "$(meson -v)" = "0.61.2" ]; then + echo "Using old incompatible meson version, please update first" + exit 1 +fi + +# --------------------------------------------------------------------------------------------------------------------- # Prepare environment export OLDDIR=$(pwd) export BASEDIR="/tmp/python3-lilv-build" -export PREFIX="$BASEDIR/system" -export PKG_CONFIG_PATH="$PREFIX/lib/pkgconfig" +export PREFIX="${BASEDIR}/system/opt/lilvlib" +export PKG_CONFIG_PATH="${PREFIX}/lib/pkgconfig" export CFLAGS="-fPIC -O2 -mtune=generic" export CXXFLAGS="-fPIC -O2 -mtune=generic" export CPPFLAGS="" -export LDFLAGS="-ldl -lm" +export LDFLAGS="" export LC_ALL="C" -mkdir -p "$BASEDIR" -cd "$BASEDIR" +rm -rf "${BASEDIR}" +rm -rf "${OLDDIR}/python3-lilv-pkg/system" -# ------------------------------------------------------------------------------------------- +mkdir -p "${BASEDIR}" +cd "${BASEDIR}" + +# --------------------------------------------------------------------------------------------------------------------- # Get code if [ ! -d lv2 ]; then - git clone git://github.com/drobilla/lv2 - cd lv2 && - git reset --hard 0713986dcd50195c81675d5819e1cf6658a38fee && - cd .. - patch -p1 -d lv2 -i "$OLDDIR"/lv2-plugin-is-project.patch + git clone https://github.com/lv2/lv2.git + git -C lv2 reset --hard 86a8bb5d103f749017e6288dbce9bbe981ed9955 + patch -p1 -d lv2 -i "${OLDDIR}"/lv2-plugin-is-project.patch +fi + +if [ ! -d darkglass-lv2-extensions ]; then + git clone --depth 1 https://github.com/Darkglass-Electronics/LV2-Extensions.git darkglass-lv2-extensions +fi + +if [ ! -d kxstudio-lv2-extensions ]; then + git clone --depth 1 https://github.com/KXStudio/LV2-Extensions.git kxstudio-lv2-extensions fi -if [ ! -d mod-sdk ]; then - git clone --depth 1 git://github.com/moddevices/mod-sdk +if [ ! -d mod-lv2-extensions ]; then + git clone --depth 1 https://github.com/mod-audio/mod-lv2-extensions.git fi -if [ ! -d kxstudio-ext ]; then - git clone --depth 1 git://github.com/KXStudio/LV2-Extensions kxstudio-ext +if [ ! -d zix ]; then + git clone https://github.com/drobilla/zix.git zix + git -C zix reset --hard 9a2af45aef5d782a3ab0cd52065894f281629055 fi if [ ! -d serd ]; then - git clone http://git.drobilla.net/serd.git serd - cd serd && - git reset --hard 83de3f80ca6cbbaac35c003bba9d6625db525939 && - cd .. - sed -i "s|Libs: -L\${libdir} -l@LIB_SERD@|Libs: -L\${libdir} -l@LIB_SERD@ -lm|" serd/serd.pc.in + git clone https://github.com/drobilla/serd.git serd + git -C serd reset --hard 1fd12ee91b4dfc124ce4435b1fe52b3a69c75255 fi if [ ! -d sord ]; then - git clone http://git.drobilla.net/sord.git sord - cd sord && - git reset --hard 31ea384f24e12778d6e30cc7a30b0f48f3d50523 && - cd .. + git clone https://github.com/drobilla/sord.git sord + git -C sord reset --hard 5458be87a898a658985248b4178ae5cfd13696ea fi if [ ! -d sratom ]; then - git clone http://git.drobilla.net/sratom.git sratom - cd sratom && - git reset --hard f62a6d15cb63ffe266ec3cd133245df8947191b2 && - cd .. + git clone https://github.com/lv2/sratom.git sratom + git -C sratom reset --hard 0dee0bed63f2fe4d8178b9fe6482d1c686a39b0c fi if [ ! -d lilv ]; then - git clone http://git.drobilla.net/lilv.git lilv - cd lilv && - git reset --hard 23293be2f10fd64ff85eddb50b6aa381694fa3a3 && - cd .. + git clone https://github.com/lv2/lilv.git lilv + git -C lilv reset --hard 2868c482df9b58bde4934a925456e50114cdcc25 fi -sed -i "s/bld.add_post_fun(autowaf.run_ldconfig)//" */wscript - -# ------------------------------------------------------------------------------------------- +# --------------------------------------------------------------------------------------------------------------------- # Build dependency code if [ ! -f lv2/build-done ]; then - cd lv2 - python3 ./waf configure --prefix="$PREFIX" --no-plugins --copy-headers - python3 ./waf build - python3 ./waf install - cp -r schemas.lv2 "$PREFIX"/lib/lv2/ + pushd lv2 + meson setup build \ + --reconfigure \ + -Dlibdir="lib" \ + -Dprefix="${PREFIX}" \ + -Ddocs=disabled \ + -Dbundles=true \ + -Dheaders=true \ + -Dlint=false \ + -Dold_headers=true \ + -Dtests=disabled \ + -Dtools=enabled + ninja -C build + ninja -C build install + touch build-done + popd +fi + +if [ ! -f darkglass-lv2-extensions/build-done ]; then + pushd darkglass-lv2-extensions + cp -rv dg-meta *.lv2 "${PREFIX}/lib/lv2/" + touch build-done + popd +fi + +if [ ! -f kxstudio-lv2-extensions/build-done ]; then + pushd kxstudio-lv2-extensions + cp -rv kx-meta *.lv2 "${PREFIX}/lib/lv2/" touch build-done - cd .. + popd fi -if [ ! -f mod-sdk/build-done ]; then - cd mod-sdk - cp -r *.lv2 "$PREFIX"/lib/lv2/ +if [ ! -f mod-lv2-extensions/build-done ]; then + pushd mod-lv2-extensions + cp -rv *.lv2 "${PREFIX}/lib/lv2/" touch build-done - cd .. + popd fi -if [ ! -f kxstudio-ext/build-done ]; then - cd kxstudio-ext - cp -r kx-meta *.lv2 "$PREFIX"/lib/lv2/ +if [ ! -f zix/build-done ]; then + pushd zix + meson setup build \ + --reconfigure \ + -Ddefault_library=static \ + -Dlibdir="lib" \ + -Dprefix="${PREFIX}" \ + -Dbenchmarks=disabled \ + -Ddocs=disabled \ + -Dhtml=disabled \ + -Dlint=false \ + -Dsinglehtml=disabled \ + -Dtests=disabled \ + -Dtests_cpp=disabled + ninja -C build + ninja -C build install touch build-done - cd .. + popd fi if [ ! -f serd/build-done ]; then - cd serd - python3 ./waf configure --prefix="$PREFIX" --static --no-shared --no-utils - python3 ./waf build - python3 ./waf install + pushd serd + meson setup build \ + --reconfigure \ + -Ddefault_library=static \ + -Dlibdir="lib" \ + -Dprefix="${PREFIX}" \ + -Ddocs=disabled \ + -Dhtml=disabled \ + -Dlint=false \ + -Dman=disabled \ + -Dman_html=disabled \ + -Dsinglehtml=disabled \ + -Dstatic=true \ + -Dtests=disabled \ + -Dtools=disabled + ninja -C build + ninja -C build install touch build-done - cd .. + popd fi if [ ! -f sord/build-done ]; then - cd sord - python3 ./waf configure --prefix="$PREFIX" --static --no-shared - python3 ./waf build - python3 ./waf install + pushd sord + meson setup build \ + --reconfigure \ + -Ddefault_library=static \ + -Dlibdir="lib" \ + -Dprefix="${PREFIX}" \ + -Dbindings_cpp=disabled \ + -Ddocs=disabled \ + -Dlint=false \ + -Dman=disabled \ + -Dtests=disabled \ + -Dtools=enabled + ninja -C build + ninja -C build install touch build-done - cd .. + popd fi if [ ! -f sratom/build-done ]; then - cd sratom - python3 ./waf configure --prefix="$PREFIX" --static --no-shared - python3 ./waf build - python3 ./waf install + pushd sratom + meson setup build \ + --reconfigure \ + -Ddefault_library=static \ + -Dlibdir="lib" \ + -Dprefix="${PREFIX}" \ + -Ddocs=disabled \ + -Dhtml=disabled \ + -Dlint=false \ + -Dsinglehtml=disabled \ + -Dtests=disabled + ninja -C build + ninja -C build install touch build-done - cd .. + popd fi if [ ! -f lilv/build-done ]; then - cd lilv - python3 ./waf configure --prefix="$PREFIX" --static --static-progs --no-shared --no-utils --no-bash-completion - python3 ./waf build - python3 ./waf install + pushd lilv + meson setup build \ + --reconfigure \ + -Ddefault_library=shared \ + -Dlibdir="lib" \ + -Dprefix="${PREFIX}" \ + -Dbindings_cpp=disabled \ + -Dbindings_py=enabled \ + -Ddocs=disabled \ + -Ddynmanifest=disabled \ + -Dhtml=disabled \ + -Dlint=false \ + -Dsinglehtml=disabled \ + -Dtests=disabled \ + -Dtools=disabled + ninja -C build + ninja -C build install touch build-done - cd .. + popd fi -sed -i "s/-lserd-0/-lserd-0 -ldl -lm/" "$PKG_CONFIG_PATH"/serd-0.pc -sed -i "s/-llilv-0/-llilv-0 -lsratom-0 -lsord-0 -lserd-0 -ldl -lm/" "$PKG_CONFIG_PATH"/lilv-0.pc +sed -i -e 's|CDLL("liblilv|CDLL("/opt/lilvlib/lib/liblilv|' "${PREFIX}/lib/python3/dist-packages/lilv.py" +rm -rf "${PREFIX}/lib/python3/dist-packages/__pycache__" # ------------------------------------------------------------------------------------------- # Build package -cd "$OLDDIR/python3-lilv-pkg" -cp -r "$BASEDIR"/lilv/* . -patch -p1 -i debian/patches/fix-link.patch -fakeroot debian/rules clean +pushd "${OLDDIR}/python3-lilv-pkg" + +mv "${BASEDIR}/system" system +mkdir -p system/usr/bin +mkdir -p system/usr/lib + +install -m 755 ../lv2_validate_mod system/usr/bin/ +mv system/opt/lilvlib/lib/python3 system/usr/lib/python3 + +rm -rf system/opt/lilvlib/include +rm -rf system/opt/lilvlib/lib/pkgconfig +rm -rf system/opt/lilvlib/lib/python3 +rm -rf system/opt/lilvlib/lib/*.a + fakeroot debian/rules binary fakeroot debian/rules clean # if the above commands fail, try these: -# debuild clean # debuild binary # debuild clean +popd + # ------------------------------------------------------------------------------------------- # Cleanup -rm -rf "$BASEDIR" - -cd "$OLDDIR/python3-lilv-pkg" -mv debian .. -rm -rf * -mv ../debian . +rm -rf "${BASEDIR}" +rm -rf "${OLDDIR}/python3-lilv-pkg/system" # ------------------------------------------------------------------------------------------- diff --git a/lilvlib/__init__.py b/lilvlib/__init__.py index 0b63e24..b2dff1a 100644 --- a/lilvlib/__init__.py +++ b/lilvlib/__init__.py @@ -2,4 +2,4 @@ get_pedalboard_info, get_pedalboard_name, plugin_has_modgui, get_plugin_info, get_plugins_info, get_bundle_dirname, NS ) -__version__ = '1.0.5' +__version__ = '1.1.0' diff --git a/lilvlib/lilvlib.py b/lilvlib/lilvlib.py index 9b0d025..0956e1b 100755 --- a/lilvlib/lilvlib.py +++ b/lilvlib/lilvlib.py @@ -13,18 +13,15 @@ # ------------------------------------------------------------------------------------------------------------ # Definitions -PREFIX_LV2CORE = "http://lv2plug.in/ns/lv2core#" -PREFIX_MOD = "http://moddevices.com/ns/mod#" +PREFIX_INGEN = "http://drobilla.net/ns/ingen#" +PREFIX_LV2CORE = "http://lv2plug.in/ns/lv2core#" +PREFIX_MOD = "http://moddevices.com/ns/mod#" +PREFIX_MODGUI = "http://moddevices.com/ns/modgui#" +PREFIX_MODPEDAL = "http://moddevices.com/ns/modpedal#" # ------------------------------------------------------------------------------------------------------------ # Utilities -def LILV_FOREACH(collection, func): - itr = collection.begin() - while itr: - yield func(collection.get(itr)) - itr = collection.next(itr) - class NS(object): def __init__(self, world, base): self.world = world @@ -35,12 +32,29 @@ def __getattr__(self, attr): if attr.endswith("_"): attr = attr[:-1] if attr not in self._cache: - self._cache[attr] = lilv.Node(self.world.new_uri(self.base+attr)) + self._cache[attr] = self.world.new_uri(self.base + attr) return self._cache[attr] def is_integer(string): return string.strip().lstrip("-+").isdigit() +def first_or(nodes, fallback): + first = next(iter(nodes), None) + return first if first is not None else fallback + +def int_first_or(nodes): + first = next(iter(nodes), None) + return int(first) if first is not None else 0 + +def str_first_or(nodes): + if nodes is None: + return "" + first = next(iter(nodes), None) + return str(first) if first is not None else "" + +def str_or(node): + return str(node) if node is not None else "" + def get_short_port_name(portName): if len(portName) <= 16: return portName @@ -59,6 +73,9 @@ def get_short_port_name(portName): # ------------------------------------------------------------------------------------------------------------ def get_category(nodes): + if nodes is None: + return [] + lv2_category_indexes = { 'DelayPlugin': ['Delay'], 'DistortionPlugin': ['Distortion'], @@ -114,45 +131,40 @@ def get_category(nodes): 'ControlVoltagePlugin': ['ControlVoltage'], } - def fill_in_lv2_category(node): - category = node.as_string().replace(PREFIX_LV2CORE,"") - if category in lv2_category_indexes.keys(): - return lv2_category_indexes[category] - return [] - - def fill_in_mod_category(node): - category = node.as_string().replace(PREFIX_MOD,"") - if category in mod_category_indexes.keys(): - return mod_category_indexes[category] - return [] + def fill_in_lv2_categories(nodes): + cats = [] + for node in nodes: + category = str(node).replace(PREFIX_LV2CORE,"") + if category in lv2_category_indexes.keys(): + cats += lv2_category_indexes[category] + return cats + + def fill_in_mod_categories(nodes): + cats = [] + for node in nodes: + category = str(node).replace(PREFIX_MOD,"") + if category in mod_category_indexes.keys(): + cats += mod_category_indexes[category] + return cats categories = [] - for cat in [cat for catlist in LILV_FOREACH(nodes, fill_in_mod_category) for cat in catlist]: + + # find MOD category first, takes precedence + for cat in fill_in_mod_categories(nodes): if cat not in categories: categories.append(cat) if len(categories) > 0: return categories - for cat in [cat for catlist in LILV_FOREACH(nodes, fill_in_lv2_category) for cat in catlist]: + for cat in fill_in_lv2_categories(nodes): if cat not in categories: categories.append(cat) return categories def get_port_data(port, subj): - nodes = port.get_value(subj.me) - data = [] - - it = lilv.lilv_nodes_begin(nodes) - while not lilv.lilv_nodes_is_end(nodes, it): - dat = lilv.lilv_nodes_get(nodes, it) - it = lilv.lilv_nodes_next(nodes, it) - if dat is None: - continue - data.append(lilv.lilv_node_as_string(dat)) - - return data + return [str(node) for node in port.get_value(subj)] def get_port_unit(miniuri): # using label, render, symbol @@ -191,10 +203,12 @@ def get_port_unit(miniuri): # get_bundle_dirname def get_bundle_dirname(bundleuri): - bundle = lilv.lilv_uri_to_path(bundleuri) + world = lilv.World() + bundle = str(world.new_uri(bundleuri).get_path()) if not os.path.exists(bundle): raise IOError(bundleuri) + if os.path.isfile(bundle): bundle = os.path.dirname(bundle) @@ -216,31 +230,23 @@ def get_pedalboard_info(bundle): world = lilv.World() # this is needed when loading specific bundles instead of load_all - # (these functions are not exposed via World yet) - lilv.lilv_world_load_specifications(world.me) - lilv.lilv_world_load_plugin_classes(world.me) + world.load_specifications() + world.load_plugin_classes() # convert bundle string into a lilv node - bundlenode = lilv.lilv_new_file_uri(world.me, None, bundle) + bundlenode = world.new_file_uri(None, bundle) # load the bundle world.load_bundle(bundlenode) - # free bundlenode, no longer needed - lilv.lilv_node_free(bundlenode) - # get all plugins in the bundle plugins = world.get_all_plugins() # make sure the bundle includes 1 and only 1 plugin (the pedalboard) - if plugins.size() != 1: + if len(plugins) != 1: raise Exception('get_pedalboard_info(%s) - bundle has 0 or > 1 plugin'.format(bundle)) - # no indexing in python-lilv yet, just get the first item - plugin = None - for p in plugins: - plugin = p - break + plugin = first_or(plugins, None) if plugin is None: raise Exception('get_pedalboard_info(%s) - failed to get plugin, you are using an old lilv!'.format(bundle)) @@ -248,14 +254,12 @@ def get_pedalboard_info(bundle): # define the needed stuff ns_rdf = NS(world, lilv.LILV_NS_RDF) ns_lv2core = NS(world, lilv.LILV_NS_LV2) - ns_ingen = NS(world, "http://drobilla.net/ns/ingen#") - ns_mod = NS(world, "http://moddevices.com/ns/mod#") - ns_modpedal = NS(world, "http://moddevices.com/ns/modpedal#") + ns_ingen = NS(world, PREFIX_INGEN) + ns_mod = NS(world, PREFIX_MOD) + ns_modpedal = NS(world, PREFIX_MODPEDAL) # check if the plugin is a pedalboard - def fill_in_type(node): - return node.as_string() - plugin_types = [i for i in LILV_FOREACH(plugin.get_value(ns_rdf.type_), fill_in_type)] + plugin_types = tuple(str(node) for node in plugin.get_value(ns_rdf.type_)) if "http://moddevices.com/ns/modpedal#Pedalboard" not in plugin_types: raise Exception('get_pedalboard_info(%s) - plugin has no mod:Pedalboard type'.format(bundle)) @@ -265,12 +269,12 @@ def fill_in_type(node): ingenblocks = [] info = { - 'name' : plugin.get_name().as_string(), - 'uri' : plugin.get_uri().as_string(), - 'author': plugin.get_author_name().as_string() or "", # Might be empty + 'name' : str(plugin.get_name()), + 'uri' : str(plugin.get_uri()), + 'author': str_or(plugin.get_author_name()), # Might be empty 'unit': { - 'name': plugin.get_value(ns_modpedal.unitName).get_first().as_string() or "", - 'model': plugin.get_value(ns_modpedal.unitModel).get_first().as_string() or "", + 'name': str_first_or(plugin.get_value(ns_modpedal.unitName)), + 'model': str_first_or(plugin.get_value(ns_modpedal.unitModel)), }, 'hardware': { # we save this info later @@ -288,25 +292,19 @@ def fill_in_type(node): }, }, 'size': { - 'width' : plugin.get_value(ns_modpedal.width).get_first().as_int(), - 'height': plugin.get_value(ns_modpedal.height).get_first().as_int(), + 'width' : int_first_or(plugin.get_value(ns_modpedal.width)), + 'height': int_first_or(plugin.get_value(ns_modpedal.height)), }, - 'screenshot' : os.path.basename(plugin.get_value(ns_modpedal.screenshot).get_first().as_string() or ""), - 'thumbnail' : os.path.basename(plugin.get_value(ns_modpedal.thumbnail).get_first().as_string() or ""), + 'screenshot' : os.path.basename(str_first_or(plugin.get_value(ns_modpedal.screenshot))), + 'thumbnail' : os.path.basename(str_first_or(plugin.get_value(ns_modpedal.thumbnail))), 'connections': [], # we save this info later 'plugins' : [], # we save this info later } # handle unit name and model for old pedalboards if not info['unit']['name']: - ports = plugin.get_value(ns_lv2core.port) - it = ports.begin() - while not ports.is_end(it): - port = ports.get(it) - it = ports.next(it) - if port.me is None: - continue - if port.as_uri().endswith(("/midi_legacy_mode", "/midi_separated_mode")): + for port in plugin.get_value(ns_lv2core.port): + if str(port).endswith(("/midi_legacy_mode", "/midi_separated_mode")): isDuoX = True break else: @@ -324,39 +322,23 @@ def fill_in_type(node): } # connections - arcs = plugin.get_value(ns_ingen.arc) - it = arcs.begin() - while not arcs.is_end(it): - arc = arcs.get(it) - it = arcs.next(it) - - if arc.me is None: - continue - - head = lilv.lilv_world_get(world.me, arc.me, ns_ingen.head.me, None) - tail = lilv.lilv_world_get(world.me, arc.me, ns_ingen.tail.me, None) + for arc in plugin.get_value(ns_ingen.arc): + head = world.get(arc, ns_ingen.head, None) + tail = world.get(arc, ns_ingen.tail, None) if head is None or tail is None: continue ingenarcs.append({ - "source": lilv.lilv_uri_to_path(lilv.lilv_node_as_string(tail)).replace(bundle,"",1), - "target": lilv.lilv_uri_to_path(lilv.lilv_node_as_string(head)).replace(bundle,"",1) + "source": tail.get_path().replace(bundle,"",1), + "target": head.get_path().replace(bundle,"",1) }) # hardware ports handled_port_uris = [] - ports = plugin.get_value(ns_lv2core.port) - it = ports.begin() - while not ports.is_end(it): - port = ports.get(it) - it = ports.next(it) - - if port.me is None: - continue - + for port in plugin.get_value(ns_lv2core.port): # check if we already handled this port - port_uri = port.as_uri() + port_uri = str(port) if port_uri in handled_port_uris: continue if port_uri.endswith("/control_in") or port_uri.endswith("/control_out"): @@ -364,7 +346,7 @@ def fill_in_type(node): handled_port_uris.append(port_uri) # get types - port_types = lilv.lilv_world_find_nodes(world.me, port.me, ns_rdf.type_.me, None) + port_types = world.find_nodes(port, ns_rdf.type_, None) if port_types is None: continue @@ -372,15 +354,8 @@ def fill_in_type(node): portDir = "" # input or output portType = "" # atom, audio or cv - it2 = lilv.lilv_nodes_begin(port_types) - while not lilv.lilv_nodes_is_end(port_types, it2): - port_type = lilv.lilv_nodes_get(port_types, it2) - it2 = lilv.lilv_nodes_next(port_types, it2) - - if port_type is None: - continue - - port_type_uri = lilv.lilv_node_as_uri(port_type) + for port_type in port_types: + port_type_uri = str(port_type) if port_type_uri == (PREFIX_LV2CORE + "InputPort"): portDir = "input" @@ -415,17 +390,9 @@ def fill_in_type(node): info['hardware']['cv']['outs'] += 1 # plugins - blocks = plugin.get_value(ns_ingen.block) - it = blocks.begin() - while not blocks.is_end(it): - block = blocks.get(it) - it = blocks.next(it) - - if block.me is None: - continue - - protouri1 = lilv.lilv_world_get(world.me, block.me, ns_lv2core.prototype.me, None) - protouri2 = lilv.lilv_world_get(world.me, block.me, ns_ingen.prototype.me, None) + for block in plugin.get_value(ns_ingen.block): + protouri1 = world.get(block, ns_lv2core.prototype, None) + protouri2 = world.get(block, ns_ingen.prototype, None) if protouri1 is not None: proto = protouri1 @@ -434,31 +401,31 @@ def fill_in_type(node): else: continue - instance = lilv.lilv_uri_to_path(lilv.lilv_node_as_string(block.me)).replace(bundle,"",1) - uri = lilv.lilv_node_as_uri(proto) + instance = block.get_path().replace(bundle,"",1) + uri = str(proto) - x = lilv.lilv_world_get(world.me, block.me, ns_ingen.canvasX.me, None) - y = lilv.lilv_world_get(world.me, block.me, ns_ingen.canvasY.me, None) - enabled = lilv.lilv_world_get(world.me, block.me, ns_ingen.enabled.me, None) - builder = lilv.lilv_world_get(world.me, block.me, ns_mod.builderVersion.me, None) - release = lilv.lilv_world_get(world.me, block.me, ns_mod.releaseNumber.me, None) - minorver = lilv.lilv_world_get(world.me, block.me, ns_lv2core.minorVersion.me, None) - microver = lilv.lilv_world_get(world.me, block.me, ns_lv2core.microVersion.me, None) - buildId = lilv.lilv_world_get(world.me, block.me, ns_mod.buildId.me, None) - buildEnv = lilv.lilv_world_get(world.me, block.me, ns_mod.buildEnvironment.me, None) + x = world.get(block, ns_ingen.canvasX, None) + y = world.get(block, ns_ingen.canvasY, None) + enabled = world.get(block, ns_ingen.enabled, None) + builder = world.get(block, ns_mod.builderVersion, None) + release = world.get(block, ns_mod.releaseNumber, None) + minorver = world.get(block, ns_lv2core.minorVersion, None) + microver = world.get(block, ns_lv2core.microVersion, None) + buildId = world.get(block, ns_mod.buildId, None) + buildEnv = world.get(block, ns_mod.buildEnvironment, None) ingenblocks.append({ "instance": instance, "uri" : uri, - "x" : lilv.lilv_node_as_float(x), - "y" : lilv.lilv_node_as_float(y), - "enabled" : lilv.lilv_node_as_bool(enabled) if enabled is not None else False, - "builder" : lilv.lilv_node_as_int(builder) if builder else 0, - "release" : lilv.lilv_node_as_int(release) if release else 0, - "minorVersion": lilv.lilv_node_as_int(minorver) if minorver else 0, - "microVersion": lilv.lilv_node_as_int(microver) if microver else 0, - "buildId" : lilv.lilv_node_as_string(buildId), - "buildEnvironment": lilv.lilv_node_as_string(buildEnv), + "x" : float(x), + "y" : float(y), + "enabled" : bool(enabled) if enabled is not None else False, + "builder" : int(builder) if builder is not None else 0, + "release" : int(release) if release is not None else 0, + "minorVersion": int(minorver) if minorver is not None else 0, + "microVersion": int(microver) if microver is not None else 0, + "buildId" : str(buildId) if buildId is not None else "", + "buildEnvironment": str(buildEnv) if buildEnv is not None else "", }) info['connections'] = ingenarcs @@ -482,47 +449,37 @@ def get_pedalboard_name(bundle): world = lilv.World() # this is needed when loading specific bundles instead of load_all - # (these functions are not exposed via World yet) - lilv.lilv_world_load_specifications(world.me) - lilv.lilv_world_load_plugin_classes(world.me) + world.load_specifications() + world.load_plugin_classes() # convert bundle string into a lilv node - bundlenode = lilv.lilv_new_file_uri(world.me, None, bundle) + bundlenode = world.new_file_uri(None, bundle) # load the bundle world.load_bundle(bundlenode) - # free bundlenode, no longer needed - lilv.lilv_node_free(bundlenode) - # get all plugins in the bundle plugins = world.get_all_plugins() # make sure the bundle includes 1 and only 1 plugin (the pedalboard) - if plugins.size() != 1: + if len(plugins) != 1: raise Exception('get_pedalboard_info(%s) - bundle has 0 or > 1 plugin'.format(bundle)) - # no indexing in python-lilv yet, just get the first item - plugin = None - for p in plugins: - plugin = p - break + plugin = first_or(plugins, None) if plugin is None: - raise Exception('get_pedalboard_info(%s) - failed to get plugin, you are using an old lilv!'.format(bundle)) + raise Exception('get_pedalboard_info(%s) - failed to get plugin'.format(bundle)) # define the needed stuff ns_rdf = NS(world, lilv.LILV_NS_RDF) # check if the plugin is a pedalboard - def fill_in_type(node): - return node.as_string() - plugin_types = [i for i in LILV_FOREACH(plugin.get_value(ns_rdf.type_), fill_in_type)] + plugin_types = tuple(str(node) for node in plugin.get_value(ns_rdf.type_)) if "http://moddevices.com/ns/modpedal#Pedalboard" not in plugin_types: raise Exception('get_pedalboard_info(%s) - plugin has no mod:Pedalboard type'.format(bundle)) - return plugin.get_name().as_string() + return str(plugin.get_name()) # ------------------------------------------------------------------------------------------------------------ # plugin_has_modgui @@ -530,43 +487,35 @@ def fill_in_type(node): # Check if a plugin has modgui def plugin_has_modgui(world, plugin): # define the needed stuff - ns_modgui = NS(world, "http://moddevices.com/ns/modgui#") + ns_modgui = NS(world, PREFIX_MODGUI) # -------------------------------------------------------------------------------------------------------- # get the proper modgui modguigui = None - nodes = plugin.get_value(ns_modgui.gui) - it = nodes.begin() - while not nodes.is_end(it): - mgui = nodes.get(it) - it = nodes.next(it) - if mgui.me is None: - continue - resdir = world.find_nodes(mgui.me, ns_modgui.resourcesDirectory.me, None).get_first() - if resdir.me is None: + for mgui in plugin.get_value(ns_modgui.gui): + resdir = first_or(world.find_nodes(mgui, ns_modgui.resourcesDirectory, None), None) + if resdir is None: continue modguigui = mgui - if os.path.expanduser("~") in lilv.lilv_uri_to_path(resdir.as_string()): + if resdir.get_path().startswith(os.path.expanduser("~")): # found a modgui in the home dir, stop here and use it break - del nodes, it - # -------------------------------------------------------------------------------------------------------- # check selected modgui - if modguigui is None or modguigui.me is None: + if modguigui is None: return False # resourcesDirectory *must* be present - modgui_resdir = world.find_nodes(modguigui.me, ns_modgui.resourcesDirectory.me, None).get_first() + modgui_resdir = first_or(world.find_nodes(modguigui, ns_modgui.resourcesDirectory, None), None) - if modgui_resdir.me is None: + if modgui_resdir is None: return False - return os.path.exists(lilv.lilv_uri_to_path(modgui_resdir.as_string())) + return os.path.exists(modgui_resdir.get_path()) # ------------------------------------------------------------------------------------------------------------ # get_plugin_info @@ -586,11 +535,12 @@ def get_plugin_info(world, plugin, useAbsolutePath = True): ns_pprops = NS(world, "http://lv2plug.in/ns/ext/port-props#") ns_pset = NS(world, "http://lv2plug.in/ns/ext/presets#") ns_units = NS(world, "http://lv2plug.in/ns/extensions/units#") - ns_mod = NS(world, "http://moddevices.com/ns/mod#") - ns_modgui = NS(world, "http://moddevices.com/ns/modgui#") + ns_mod = NS(world, PREFIX_MOD) + ns_modgui = NS(world, PREFIX_MODGUI) - bundleuri = plugin.get_bundle_uri().as_string() - bundle = lilv.lilv_uri_to_path(bundleuri) + bundleuri = plugin.get_bundle_uri() + bundle = bundleuri.get_path() + bundleuri = str(bundleuri) errors = [] warnings = [] @@ -598,7 +548,7 @@ def get_plugin_info(world, plugin, useAbsolutePath = True): # -------------------------------------------------------------------------------------------------------- # uri - uri = plugin.get_uri().as_string() or "" + uri = str(plugin.get_uri()) if not uri: errors.append("plugin uri is missing or invalid") @@ -610,7 +560,7 @@ def get_plugin_info(world, plugin, useAbsolutePath = True): # -------------------------------------------------------------------------------------------------------- # name - name = plugin.get_name().as_string() or "" + name = str(plugin.get_name()) if not name: errors.append("plugin name is missing") @@ -618,7 +568,7 @@ def get_plugin_info(world, plugin, useAbsolutePath = True): # -------------------------------------------------------------------------------------------------------- # binary - binary = lilv.lilv_uri_to_path(plugin.get_library_uri().as_string() or "") + binary = plugin.get_library_uri().get_path() if not binary: errors.append("plugin binary is missing") @@ -628,28 +578,16 @@ def get_plugin_info(world, plugin, useAbsolutePath = True): # -------------------------------------------------------------------------------------------------------- # license - licenses = [] - nodes = plugin.get_value(ns_doap.license) - it = nodes.begin() - - while not nodes.is_end(it): - licenses.append(nodes.get(it).as_string()) - it = nodes.next(it) - - del nodes, it + licenses = tuple(str(node) for node in plugin.get_value(ns_doap.license)) if len(licenses) > 0: license = sorted(licenses)[0] else: license = "" - project = plugin.get_value(ns_lv2core.project).get_first() - if project.me is not None: - licsnode = lilv.lilv_world_get(world.me, project.me, ns_doap.license.me, None) - if licsnode is not None: - license = lilv.lilv_node_as_string(licsnode) - del licsnode - del project + project = first_or(plugin.get_value(ns_lv2core.project), None) + if project is not None: + license = str_first_or(world.get(project, ns_doap.license, None)) if not license: errors.append("plugin license is missing") @@ -661,7 +599,7 @@ def get_plugin_info(world, plugin, useAbsolutePath = True): # -------------------------------------------------------------------------------------------------------- # comment - comment = (plugin.get_value(ns_rdfs.comment).get_first().as_string() or "").strip() + comment = str_first_or(plugin.get_value(ns_rdfs.comment)).strip() # sneaky empty comments! if len(comment) > 0 and comment == len(comment) * comment[0]: @@ -673,29 +611,26 @@ def get_plugin_info(world, plugin, useAbsolutePath = True): # -------------------------------------------------------------------------------------------------------- # version - microver = plugin.get_value(ns_lv2core.microVersion).get_first() - minorver = plugin.get_value(ns_lv2core.minorVersion).get_first() + microver = first_or(plugin.get_value(ns_lv2core.microVersion), None) + minorver = first_or(plugin.get_value(ns_lv2core.minorVersion), None) - if microver.me is None and minorver.me is None: + if microver is None and minorver is None: errors.append("plugin is missing version information") minorVersion = 0 microVersion = 0 else: - if minorver.me is None: + if minorver is None: errors.append("plugin is missing minorVersion") minorVersion = 0 else: - minorVersion = minorver.as_int() + minorVersion = int(minorver) - if microver.me is None: + if microver is None: errors.append("plugin is missing microVersion") microVersion = 0 else: - microVersion = microver.as_int() - - del minorver - del microver + microVersion = int(microver) version = "%d.%d" % (minorVersion, microVersion) @@ -715,25 +650,22 @@ def get_plugin_info(world, plugin, useAbsolutePath = True): # author author = { - 'name' : plugin.get_author_name().as_string() or "", - 'homepage': plugin.get_author_homepage().as_string() or "", - 'email' : plugin.get_author_email().as_string() or "", + 'name' : str_or(plugin.get_author_name()), + 'homepage': str_or(plugin.get_author_homepage()), + 'email' : str_or(plugin.get_author_email()), } if not author['name']: errors.append("plugin author name is missing") if not author['homepage']: - prj = plugin.get_value(ns_lv2core.project).get_first() - if prj.me is not None: - maintainer = lilv.lilv_world_get(world.me, prj.me, ns_doap.maintainer.me, None) + prj = first_or(plugin.get_value(ns_lv2core.project), None) + if prj is not None: + maintainer = world.get(prj, ns_doap.maintainer, None) if maintainer is not None: - homepage = lilv.lilv_world_get(world.me, maintainer, ns_foaf.homepage.me, None) + homepage = world.get(maintainer, ns_foaf.homepage, None) if homepage is not None: - author['homepage'] = lilv.lilv_node_as_string(homepage) - del homepage - del maintainer - del prj + author['homepage'] = str(homepage) if not author['homepage']: warnings.append("plugin author homepage is missing") @@ -749,26 +681,26 @@ def get_plugin_info(world, plugin, useAbsolutePath = True): # -------------------------------------------------------------------------------------------------------- # brand - brand = plugin.get_value(ns_mod.brand).get_first().as_string() or "" + brand = str_first_or(plugin.get_value(ns_mod.brand)) if not brand: brand = author['name'].split(" - ",1)[0].split(" ",1)[0] brand = brand.rstrip(",").rstrip(";") - if len(brand) > 11: - brand = brand[:11] + if len(brand) > 16: + brand = brand[:16] warnings.append("plugin brand is missing") - elif len(brand) > 11: - brand = brand[:11] - errors.append("plugin brand has more than 11 characters") + elif len(brand) > 16: + brand = brand[:16] + errors.append("plugin brand has more than 16 characters") # -------------------------------------------------------------------------------------------------------- # label - label = plugin.get_value(ns_mod.label).get_first().as_string() or "" + label = str_first_or(plugin.get_value(ns_mod.label)) if not label: - if len(name) <= 16: + if len(name) <= 24: label = name else: labels = name.split(" - ",1)[0].split(" ") @@ -777,15 +709,14 @@ def get_plugin_info(world, plugin, useAbsolutePath = True): else: label = labels[0] - if len(label) > 16: - label = label[:16] + if len(label) > 24: + label = label[:24] warnings.append("plugin label is missing") - del labels - elif len(label) > 16: - label = label[:16] - errors.append("plugin label has more than 16 characters") + elif len(label) > 24: + label = label[:24] + errors.append("plugin label has more than 24 characters") # -------------------------------------------------------------------------------------------------------- # bundles @@ -793,19 +724,8 @@ def get_plugin_info(world, plugin, useAbsolutePath = True): bundles = [] if useAbsolutePath: - bnodes = lilv.lilv_plugin_get_data_uris(plugin.me) - - it = lilv.lilv_nodes_begin(bnodes) - while not lilv.lilv_nodes_is_end(bnodes, it): - bnode = lilv.lilv_nodes_get(bnodes, it) - it = lilv.lilv_nodes_next(bnodes, it) - - if bnode is None: - continue - if not lilv.lilv_node_is_uri(bnode): - continue - - bpath = os.path.abspath(os.path.dirname(lilv.lilv_uri_to_path(lilv.lilv_node_as_uri(bnode)))) + for bnode in plugin.get_data_uris(): + bpath = os.path.abspath(os.path.dirname(bnode.get_path())) if not bpath.endswith(os.sep): bpath += os.sep @@ -816,112 +736,98 @@ def get_plugin_info(world, plugin, useAbsolutePath = True): if bundle not in bundles: bundles.append(bundle) - del bnodes, it - # -------------------------------------------------------------------------------------------------------- # get the proper modgui modguigui = None - nodes = plugin.get_value(ns_modgui.gui) - it = nodes.begin() - while not nodes.is_end(it): - mgui = nodes.get(it) - it = nodes.next(it) - if mgui.me is None: - continue - resdir = world.find_nodes(mgui.me, ns_modgui.resourcesDirectory.me, None).get_first() - if resdir.me is None: + for mgui in plugin.get_value(ns_modgui.gui): + resdir = first_or(world.find_nodes(mgui, ns_modgui.resourcesDirectory, None), None) + if resdir is None: continue modguigui = mgui if not useAbsolutePath: # special build, use first modgui found break - if os.path.expanduser("~") in lilv.lilv_uri_to_path(resdir.as_string()): + if resdir.get_path().startswith(os.path.expanduser("~")): # found a modgui in the home dir, stop here and use it break - del nodes, it - # -------------------------------------------------------------------------------------------------------- # gui gui = {} - if modguigui is None or modguigui.me is None: + if modguigui is None: warnings.append("no modgui available") else: # resourcesDirectory *must* be present - modgui_resdir = world.find_nodes(modguigui.me, ns_modgui.resourcesDirectory.me, None).get_first() + modgui_resdir = first_or(world.find_nodes(modguigui, ns_modgui.resourcesDirectory, None), None) - if modgui_resdir.me is None: + if modgui_resdir is None: errors.append("modgui has no resourcesDirectory data") else: if useAbsolutePath: - gui['resourcesDirectory'] = lilv.lilv_uri_to_path(modgui_resdir.as_string()) + gui['resourcesDirectory'] = modgui_resdir.get_path() # check if modgui is defined in a separate file gui['usingSeeAlso'] = os.path.exists(os.path.join(bundle, "modgui.ttl")) # check if the modgui definition is on its own file and in the user dir gui['modificableInPlace'] = bool((bundle not in gui['resourcesDirectory'] or gui['usingSeeAlso']) and - os.path.expanduser("~") in gui['resourcesDirectory']) + gui['resourcesDirectory'].startswith(os.path.expanduser("~"))) else: - gui['resourcesDirectory'] = modgui_resdir.as_string().replace(bundleuri,"",1) + gui['resourcesDirectory'] = str(modgui_resdir).replace(bundleuri,"",1) # icon and settings templates - modgui_icon = world.find_nodes(modguigui.me, ns_modgui.iconTemplate .me, None).get_first() - modgui_setts = world.find_nodes(modguigui.me, ns_modgui.settingsTemplate.me, None).get_first() + modgui_icon = first_or(world.find_nodes(modguigui, ns_modgui.iconTemplate, None), None) + modgui_setts = first_or(world.find_nodes(modguigui, ns_modgui.settingsTemplate, None), None) - if modgui_icon.me is None: + if modgui_icon is None: errors.append("modgui has no iconTemplate data") else: - iconFile = lilv.lilv_uri_to_path(modgui_icon.as_string()) + iconFile = modgui_icon.get_path() if os.path.exists(iconFile): gui['iconTemplate'] = iconFile if useAbsolutePath else iconFile.replace(bundle,"",1) else: errors.append("modgui iconTemplate file is missing") - del iconFile - if modgui_setts.me is not None: - settingsFile = lilv.lilv_uri_to_path(modgui_setts.as_string()) + if modgui_setts is not None: + settingsFile = modgui_setts.get_path() if os.path.exists(settingsFile): gui['settingsTemplate'] = settingsFile if useAbsolutePath else settingsFile.replace(bundle,"",1) else: errors.append("modgui settingsTemplate file is missing") - del settingsFile # javascript and stylesheet files - modgui_script = world.find_nodes(modguigui.me, ns_modgui.javascript.me, None).get_first() - modgui_style = world.find_nodes(modguigui.me, ns_modgui.stylesheet.me, None).get_first() + modgui_script = first_or(world.find_nodes(modguigui, ns_modgui.javascript, None), None) + modgui_style = first_or(world.find_nodes(modguigui, ns_modgui.stylesheet, None), None) - if modgui_script.me is not None: - javascriptFile = lilv.lilv_uri_to_path(modgui_script.as_string()) + if modgui_script is not None: + javascriptFile = modgui_script.get_path() if os.path.exists(javascriptFile): gui['javascript'] = javascriptFile if useAbsolutePath else javascriptFile.replace(bundle,"",1) else: errors.append("modgui javascript file is missing") - del javascriptFile - if modgui_style.me is None: + if modgui_style is None: errors.append("modgui has no stylesheet data") else: - stylesheetFile = lilv.lilv_uri_to_path(modgui_style.as_string()) + stylesheetFile = modgui_style.get_path() if os.path.exists(stylesheetFile): gui['stylesheet'] = stylesheetFile if useAbsolutePath else stylesheetFile.replace(bundle,"",1) else: errors.append("modgui stylesheet file is missing") - del stylesheetFile # template data for backwards compatibility # FIXME remove later once we got rid of all templateData files - modgui_templ = world.find_nodes(modguigui.me, ns_modgui.templateData.me, None).get_first() + modgui_templ = first_or(world.find_nodes(modguigui, ns_modgui.templateData, None), None) - if modgui_templ.me is not None: + if modgui_templ is not None: warnings.append("modgui is using old deprecated templateData") - templFile = lilv.lilv_uri_to_path(modgui_templ.as_string()) + templFile = modgui_templ.get_path() if os.path.exists(templFile): with open(templFile, 'r') as fd: try: @@ -949,14 +855,13 @@ def get_plugin_info(world, plugin, useAbsolutePath = True): }) index += 1 gui['ports'] = ports - del templFile # screenshot and thumbnail - modgui_scrn = world.find_nodes(modguigui.me, ns_modgui.screenshot.me, None).get_first() - modgui_thumb = world.find_nodes(modguigui.me, ns_modgui.thumbnail .me, None).get_first() + modgui_scrn = first_or(world.find_nodes(modguigui, ns_modgui.screenshot, None), None) + modgui_thumb = first_or(world.find_nodes(modguigui, ns_modgui.thumbnail , None), None) - if modgui_scrn.me is not None: - gui['screenshot'] = lilv.lilv_uri_to_path(modgui_scrn.as_string()) + if modgui_scrn is not None: + gui['screenshot'] = modgui_scrn.get_path() if not os.path.exists(gui['screenshot']): errors.append("modgui screenshot file is missing") if not useAbsolutePath: @@ -964,8 +869,8 @@ def get_plugin_info(world, plugin, useAbsolutePath = True): else: errors.append("modgui has no screnshot data") - if modgui_thumb.me is not None: - gui['thumbnail'] = lilv.lilv_uri_to_path(modgui_thumb.as_string()) + if modgui_thumb is not None: + gui['thumbnail'] = modgui_thumb.get_path() if not os.path.exists(gui['thumbnail']): errors.append("modgui thumbnail file is missing") if not useAbsolutePath: @@ -974,50 +879,44 @@ def get_plugin_info(world, plugin, useAbsolutePath = True): errors.append("modgui has no thumbnail data") # extra stuff, all optional - modgui_brand = world.find_nodes(modguigui.me, ns_modgui.brand.me, None).get_first() - modgui_label = world.find_nodes(modguigui.me, ns_modgui.label.me, None).get_first() - modgui_model = world.find_nodes(modguigui.me, ns_modgui.model.me, None).get_first() - modgui_panel = world.find_nodes(modguigui.me, ns_modgui.panel.me, None).get_first() - modgui_color = world.find_nodes(modguigui.me, ns_modgui.color.me, None).get_first() - modgui_knob = world.find_nodes(modguigui.me, ns_modgui.knob .me, None).get_first() - - if modgui_brand.me is not None: - gui['brand'] = modgui_brand.as_string() - if modgui_label.me is not None: - gui['label'] = modgui_label.as_string() - if modgui_model.me is not None: - gui['model'] = modgui_model.as_string() - if modgui_panel.me is not None: - gui['panel'] = modgui_panel.as_string() - if modgui_color.me is not None: - gui['color'] = modgui_color.as_string() - if modgui_knob.me is not None: - gui['knob'] = modgui_knob.as_string() + modgui_brand = first_or(world.find_nodes(modguigui, ns_modgui.brand, None), None) + modgui_label = first_or(world.find_nodes(modguigui, ns_modgui.label, None), None) + modgui_model = first_or(world.find_nodes(modguigui, ns_modgui.model, None), None) + modgui_panel = first_or(world.find_nodes(modguigui, ns_modgui.panel, None), None) + modgui_color = first_or(world.find_nodes(modguigui, ns_modgui.color, None), None) + modgui_knob = first_or(world.find_nodes(modguigui, ns_modgui.knob , None), None) + + if modgui_brand is not None: + gui['brand'] = str(modgui_brand) + if modgui_label is not None: + gui['label'] = str(modgui_label) + if modgui_model is not None: + gui['model'] = str(modgui_model) + if modgui_panel is not None: + gui['panel'] = str(modgui_panel) + if modgui_color is not None: + gui['color'] = str(modgui_color) + if modgui_knob is not None: + gui['knob'] = str(modgui_knob) # ports errpr = False sybls = [] ports = [] - nodes = world.find_nodes(modguigui.me, ns_modgui.port.me, None) - it = lilv.lilv_nodes_begin(nodes.me) - while not lilv.lilv_nodes_is_end(nodes.me, it): - port = lilv.lilv_nodes_get(nodes.me, it) - it = lilv.lilv_nodes_next(nodes.me, it) - if port is None: - break - port_indx = world.find_nodes(port, ns_lv2core.index .me, None).get_first() - port_symb = world.find_nodes(port, ns_lv2core.symbol.me, None).get_first() - port_name = world.find_nodes(port, ns_lv2core.name .me, None).get_first() - - if None in (port_indx.me, port_name.me, port_symb.me): + for port in world.find_nodes(modguigui, ns_modgui.port, None): + port_indx = first_or(world.find_nodes(port, ns_lv2core.index, None), None) + port_symb = first_or(world.find_nodes(port, ns_lv2core.symbol, None), None) + port_name = first_or(world.find_nodes(port, ns_lv2core.name, None), None) + + if None in (port_indx, port_name, port_symb): if not errpr: errors.append("modgui has some invalid port data") errpr = True continue - port_indx = port_indx.as_int() - port_symb = port_symb.as_string() - port_name = port_name.as_string() + port_indx = int(port_indx) + port_symb = str(port_symb) + port_name = str(port_name) ports.append({ 'index' : port_indx, @@ -1039,11 +938,6 @@ def get_plugin_info(world, plugin, useAbsolutePath = True): ports2[port['index']] = port gui['ports'] = [ports2[i] for i in ports2] - del ports2 - - # cleanup - del ports, nodes, it - # -------------------------------------------------------------------------------------------------------- # ports @@ -1060,20 +954,20 @@ def get_plugin_info(world, plugin, useAbsolutePath = True): # function for filling port info def fill_port_info(port): # base data - portname = lilv.lilv_node_as_string(port.get_name()) or "" + portname = str_or(port.get_name()) if not portname: portname = "_%i" % index errors.append("port with index %i has no name" % index) - portsymbol = lilv.lilv_node_as_string(port.get_symbol()) or "" + portsymbol = str_or(port.get_symbol()) if not portsymbol: portsymbol = "_%i" % index errors.append("port with index %i has no symbol" % index) # check for duplicate names - if portname in portsymbols: + if portname in portnames: warnings.append("port name '%s' is not unique" % portname) else: portnames.append(portname) @@ -1085,10 +979,7 @@ def fill_port_info(port): portsymbols.append(portsymbol) # short name - psname = lilv.lilv_nodes_get_first(port.get_value(ns_lv2core.shortName.me)) - - if psname is not None: - psname = lilv.lilv_node_as_string(psname) or "" + psname = str_first_or(port.get_value(ns_lv2core.shortName)) if not psname: psname = get_short_port_name(portname) @@ -1100,23 +991,21 @@ def fill_port_info(port): errors.append("port '%s' short name has more than 16 characters" % portname) # check for old style shortName - if port.get_value(ns_lv2core.shortname.me) is not None: + if first_or(port.get_value(ns_lv2core.shortname), None) is not None: errors.append("port '%s' short name is using old style 'shortname' instead of 'shortName'" % portname) # port types types = [typ.rsplit("#",1)[-1].replace("Port","",1) for typ in get_port_data(port, ns_rdf.type_)] if "Atom" in types \ - and port.supports_event(ns_midi.MidiEvent.me) \ - and lilv.Nodes(port.get_value(ns_atom.bufferType.me)).get_first() == ns_atom.Sequence: + and port.supports_event(ns_midi.MidiEvent) \ + and str_first_or(port.get_value(ns_atom.bufferType)) == ns_atom.Sequence: types.append("MIDI") #if "Morph" in types: - #morphtyp = lilv.lilv_nodes_get_first(port.get_value(ns_morph.supportsType.me)) - #if morphtyp is not None: - #morphtyp = lilv.lilv_node_as_uri(morphtyp) - #if morphtyp: - #types.append(morphtyp.rsplit("#",1)[-1].replace("Port","",1)) + #morphtyp = str_first_or(port.get_value(ns_morph.supportsType)) + #if morphtyp: + #types.append(morphtyp.rsplit("#",1)[-1].replace("Port","",1)) # port comment pcomment = (get_port_data(port, ns_rdfs.comment) or [""])[0] @@ -1146,44 +1035,46 @@ def fill_port_info(port): if isInteger and "CV" in types: errors.append("port '%s' has integer property and CV type" % portname) - xdefault = lilv.lilv_nodes_get_first(port.get_value(ns_mod.default.me)) or \ - lilv.lilv_nodes_get_first(port.get_value(ns_lv2core.default.me)) - xminimum = lilv.lilv_nodes_get_first(port.get_value(ns_mod.minimum.me)) or \ - lilv.lilv_nodes_get_first(port.get_value(ns_lv2core.minimum.me)) - xmaximum = lilv.lilv_nodes_get_first(port.get_value(ns_mod.maximum.me)) or \ - lilv.lilv_nodes_get_first(port.get_value(ns_lv2core.maximum.me)) + xdefault = first_or(port.get_value(ns_mod.default), first_or(port.get_value(ns_lv2core.default), None)) + xminimum = first_or(port.get_value(ns_mod.minimum), first_or(port.get_value(ns_lv2core.minimum), None)) + xmaximum = first_or(port.get_value(ns_mod.maximum), first_or(port.get_value(ns_lv2core.maximum), None)) if xminimum is not None and xmaximum is not None: if isInteger: - if is_integer(lilv.lilv_node_as_string(xminimum)): - ranges['minimum'] = lilv.lilv_node_as_int(xminimum) + if xminimum.is_int(): + ranges['minimum'] = int(xminimum) + elif xminimum.is_float(): + ranges['minimum'] = int(float(xminimum)) + warnings.append("port '%s' has integer property but minimum value is not an integer" % portname) else: - ranges['minimum'] = lilv.lilv_node_as_float(xminimum) - if fmod(ranges['minimum'], 1.0) == 0.0: - warnings.append("port '%s' has integer property but minimum value is float" % portname) - else: - errors.append("port '%s' has integer property but minimum value has non-zero decimals" % portname) - ranges['minimum'] = int(ranges['minimum']) + errors.append("port '%s' minimum value is not an integer or float" % portname) - if is_integer(lilv.lilv_node_as_string(xmaximum)): - ranges['maximum'] = lilv.lilv_node_as_int(xmaximum) + if xmaximum.is_int(): + ranges['maximum'] = int(xmaximum) + elif xmaximum.is_float(): + ranges['maximum'] = int(float(xmaximum)) + warnings.append("port '%s' has integer property but maximum value is not an integer" % portname) else: - ranges['maximum'] = lilv.lilv_node_as_float(xmaximum) - if fmod(ranges['maximum'], 1.0) == 0.0: - warnings.append("port '%s' has integer property but maximum value is float" % portname) - else: - errors.append("port '%s' has integer property but maximum value has non-zero decimals" % portname) - ranges['maximum'] = int(ranges['maximum']) + errors.append("port '%s' maximum value is not an integer or float" % portname) else: - ranges['minimum'] = lilv.lilv_node_as_float(xminimum) - ranges['maximum'] = lilv.lilv_node_as_float(xmaximum) - - if is_integer(lilv.lilv_node_as_string(xminimum)): - warnings.append("port '%s' minimum value is an integer" % portname) - - if is_integer(lilv.lilv_node_as_string(xmaximum)): - warnings.append("port '%s' maximum value is an integer" % portname) + if xminimum.is_float(): + ranges['minimum'] = float(xminimum) + elif xminimum.is_int(): + ranges['minimum'] = float(int(xminimum)) + warnings.append("port '%s' does not have integer property but minimum value is an integer" % portname) + else: + ranges['minimum'] = 0.0 + errors.append("port '%s' minimum value is not an integer or float" % portname) + + if xmaximum.is_float(): + ranges['maximum'] = float(xmaximum) + elif xmaximum.is_int(): + ranges['maximum'] = float(int(xmaximum)) + warnings.append("port '%s' does not have integer property but maximum value is an integer" % portname) + else: + ranges['maximum'] = 1.0 + errors.append("port '%s' maximum value is not an integer or float" % portname) if ranges['minimum'] >= ranges['maximum']: ranges['maximum'] = ranges['minimum'] + (1 if isInteger else 0.1) @@ -1191,20 +1082,24 @@ def fill_port_info(port): if xdefault is not None: if isInteger: - if is_integer(lilv.lilv_node_as_string(xdefault)): - ranges['default'] = lilv.lilv_node_as_int(xdefault) + if xdefault.is_int(): + ranges['default'] = int(xdefault) + elif xdefault.is_float(): + ranges['default'] = int(float(xdefault)) + warnings.append("port '%s' has integer property but default value is not an integer" % portname) else: - ranges['default'] = lilv.lilv_node_as_float(xdefault) - if fmod(ranges['default'], 1.0) == 0.0: - warnings.append("port '%s' has integer property but default value is float" % portname) - else: - errors.append("port '%s' has integer property but default value has non-zero decimals" % portname) - ranges['default'] = int(ranges['default']) - else: - ranges['default'] = lilv.lilv_node_as_float(xdefault) + ranges['default'] = ranges['minimum'] + errors.append("port '%s' default value is not an integer or float" % portname) - if is_integer(lilv.lilv_node_as_string(xdefault)): - warnings.append("port '%s' default value is an integer" % portname) + else: + if xdefault.is_float(): + ranges['default'] = float(xdefault) + elif xdefault.is_int(): + ranges['default'] = float(int(xdefault)) + warnings.append("port '%s' does not have integer property but default value is an integer" % portname) + else: + ranges['default'] = 0.0 + errors.append("port '%s' default value is not an integer or float" % portname) testmin = ranges['minimum'] testmax = ranges['maximum'] @@ -1236,30 +1131,23 @@ def fill_port_info(port): if "CV" not in types and designation != (PREFIX_LV2CORE + "latency"): errors.append("port '%s' is missing value ranges" % portname) - nodes = port.get_scale_points() + scalepoint_nodes = port.get_scale_points() - if nodes is not None: + if scalepoint_nodes is not None: scalepoints_unsorted = [] - it = lilv.lilv_scale_points_begin(nodes) - while not lilv.lilv_scale_points_is_end(nodes, it): - sp = lilv.lilv_scale_points_get(nodes, it) - it = lilv.lilv_scale_points_next(nodes, it) - - if sp is None: - continue - - label = lilv.lilv_scale_point_get_label(sp) - value = lilv.lilv_scale_point_get_value(sp) + for sp in scalepoint_nodes: + label = sp.get_label() + value = sp.get_value() if label is None: errors.append("a port scalepoint is missing its label") continue - label = lilv.lilv_node_as_string(label) or "" + label = str(label) if not label: - errors.append("a port scalepoint is missing its label") + errors.append("a port scalepoint label is empty") continue if value is None: @@ -1267,19 +1155,24 @@ def fill_port_info(port): continue if isInteger: - if is_integer(lilv.lilv_node_as_string(value)): - value = lilv.lilv_node_as_int(value) - else: - value = lilv.lilv_node_as_float(value) - if fmod(value, 1.0) == 0.0: - warnings.append("port '%s' has integer property but scalepoint '%s' value is float" % (portname, label)) - else: - errors.append("port '%s' has integer property but scalepoint '%s' value has non-zero decimals" % (portname, label)) + if value.is_int(): value = int(value) + elif value.is_float(): + value = float(value) + warnings.append("port '%s' scalepoint '%s' value is not an integer" % (portname, label)) + else: + value = ranges['minimum'] + warnings.append("port '%s' scalepoint '%s' value is not an integer or float" % (portname, label)) + else: - if is_integer(lilv.lilv_node_as_string(value)): + if value.is_int(): + value = int(value) warnings.append("port '%s' scalepoint '%s' value is an integer" % (portname, label)) - value = lilv.lilv_node_as_float(value) + elif value.is_float(): + value = float(value) + else: + value = ranges['minimum'] + warnings.append("port '%s' scalepoint '%s' value is not an integer or float" % (portname, label)) if ranges['minimum'] <= value <= ranges['maximum']: scalepoints_unsorted.append((value, label)) @@ -1292,9 +1185,6 @@ def fill_port_info(port): values = list(v for v, l in scalepoints_unsorted) values.sort() scalepoints = list({ 'value': v, 'label': unsorted[v] } for v in values) - del unsorted, values - - del scalepoints_unsorted if "enumeration" in properties and len(scalepoints) <= 1: errors.append("port '%s' wants to use enumeration but doesn't have enough values" % portname) @@ -1303,10 +1193,10 @@ def fill_port_info(port): # control ports might contain unit if "Control" in types: # unit - uunit = lilv.lilv_nodes_get_first(port.get_value(ns_units.unit.me)) + uunit = first_or(port.get_value(ns_units.unit), None) if uunit is not None: - uuri = lilv.lilv_node_as_uri(uunit) + uuri = str(uunit) # using pre-existing lv2 unit if uuri is not None and uuri.startswith("http://lv2plug.in/ns/"): @@ -1328,22 +1218,22 @@ def fill_port_info(port): # using custom unit else: - xlabel = world.find_nodes(uunit, ns_rdfs .label.me, None).get_first() - xrender = world.find_nodes(uunit, ns_units.render.me, None).get_first() - xsymbol = world.find_nodes(uunit, ns_units.symbol.me, None).get_first() + xlabel = first_or(world.find_nodes(uunit, ns_rdfs.label, None), None) + xrender = first_or(world.find_nodes(uunit, ns_units.render, None), None) + xsymbol = first_or(world.find_nodes(uunit, ns_units.symbol, None), None) - if xlabel.me is not None: - ulabel = xlabel.as_string() + if xlabel is not None: + ulabel = str(xlabel) else: errors.append("port '%s' has custom unit with no label" % portname) - if xrender.me is not None: - urender = xrender.as_string() + if xrender is not None: + urender = str(xrender) else: errors.append("port '%s' has custom unit with no render" % portname) - if xsymbol.me is not None: - usymbol = xsymbol.as_string() + if xsymbol is not None: + usymbol = str(xsymbol) else: errors.append("port '%s' has custom unit with no symbol" % portname) @@ -1382,10 +1272,10 @@ def fill_port_info(port): # presets def get_preset_data(preset): - world.load_resource(preset.me) + world.load_resource(preset) - uri = preset.as_string() or "" - label = world.find_nodes(preset.me, ns_rdfs.label.me, None).get_first().as_string() or "" + uri = str_or(preset) + label = str_first_or(world.find_nodes(preset, ns_rdfs.label, None)) if not uri: errors.append("preset with label '%s' has no uri" % (label or "")) @@ -1396,8 +1286,7 @@ def get_preset_data(preset): presets = [] - presets_related = plugin.get_related(ns_pset.Preset) - presets_data = list(LILV_FOREACH(presets_related, get_preset_data)) + presets_data = tuple(get_preset_data(p) for p in plugin.get_related(ns_pset.Preset)) if len(presets_data) != 0: unsorted = dict(p for p in presets_data) @@ -1406,8 +1295,6 @@ def get_preset_data(preset): presets = list({ 'uri': p, 'label': unsorted[p] } for p in uris) del unsorted, uris - del presets_related - # -------------------------------------------------------------------------------------------------------- # done @@ -1464,9 +1351,8 @@ def get_plugins_info(bundles): world = lilv.World() # this is needed when loading specific bundles instead of load_all - # (these functions are not exposed via World yet) - lilv.lilv_world_load_specifications(world.me) - lilv.lilv_world_load_plugin_classes(world.me) + world.load_specifications() + world.load_plugin_classes() # load all bundles for bundle in bundles: @@ -1476,19 +1362,16 @@ def get_plugins_info(bundles): bundle += os.sep # convert bundle string into a lilv node - bundlenode = lilv.lilv_new_file_uri(world.me, None, bundle) + bundlenode = world.new_file_uri(None, bundle) # load the bundle world.load_bundle(bundlenode) - # free bundlenode, no longer needed - lilv.lilv_node_free(bundlenode) - # get all plugins available in the selected bundles plugins = world.get_all_plugins() # make sure the bundles include something - if plugins.size() == 0: + if len(plugins) == 0: raise Exception('get_plugins_info() - selected bundles have no plugins') # return all the info @@ -1496,12 +1379,10 @@ def get_plugins_info(bundles): # ------------------------------------------------------------------------------------------------------------ -if __name__ == '__main__': - from sys import argv, exit +def main(): + from sys import argv from pprint import pprint - #get_plugins_info(argv[1:]) - #for i in get_plugins_info(argv[1:]): pprint(i) - #exit(0) + for i in get_plugins_info(argv[1:]): warnings = i['warnings'].copy() @@ -1525,3 +1406,8 @@ def get_plugins_info(bundles): }, width=200) # ------------------------------------------------------------------------------------------------------------ + +if __name__ == '__main__': + main() + +# ------------------------------------------------------------------------------------------------------------ diff --git a/lv2-plugin-is-project.patch b/lv2-plugin-is-project.patch index 61c262f..0b1066d 100644 --- a/lv2-plugin-is-project.patch +++ b/lv2-plugin-is-project.patch @@ -1,11 +1,13 @@ ---- lv2-1.12.1+git20151117.orig/lv2/lv2plug.in/ns/lv2core/lv2core.ttl -+++ lv2-1.12.1+git20151117/lv2/lv2plug.in/ns/lv2core/lv2core.ttl -@@ -139,7 +139,7 @@ hosts and other tools already underst +diff --git a/lv2/core.lv2/lv2core.ttl b/lv2/core.lv2/lv2core.ttl +index c7dcf50..dfdc31b 100644 +--- a/lv2/core.lv2/lv2core.ttl ++++ b/lv2/core.lv2/lv2core.ttl +@@ -42,7 +42,7 @@ lv2:PluginBase lv2:Plugin a rdfs:Class , owl:Class ; - rdfs:subClassOf lv2:PluginBase ; + rdfs:subClassOf lv2:PluginBase , doap:Project ; rdfs:label "Plugin" ; + rdfs:comment "An LV2 plugin." ; rdfs:subClassOf [ - a owl:Restriction ; diff --git a/lv2_validate_mod b/lv2_validate_mod new file mode 100755 index 0000000..7b1f16a --- /dev/null +++ b/lv2_validate_mod @@ -0,0 +1,34 @@ +#!/bin/bash + +PREFIX=/opt/lilvlib +LV2DIR=${PREFIX}/lib/lv2 + +# don't check all lv2 bundles, if atom.lv2 is installed the others should be too +if [ ! -d ${LV2DIR}/atom.lv2 ]; then + echo "${LV2DIR}/atom.lv2 directory is missing" + exit 1 +fi + +if [ ! -d ${LV2DIR}/dg-properties.lv2 ]; then + echo "${LV2DIR}/dg-properties.lv2 directory is missing" + exit 1 +fi + +if [ ! -d ${LV2DIR}/kx-properties.lv2 ]; then + echo "${LV2DIR}/kx-properties.lv2 directory is missing" + exit 1 +fi + +if [ ! -d ${LV2DIR}/mod-license.lv2 ]; then + echo "${LV2DIR}/mod-license.lv2 directory is missing" + exit 1 +fi + +if [ -z "${1}" ]; then + echo "usage: ${0} /path/to/bundle" + exit 1 +fi + +exec ${PREFIX}/bin/sord_validate \ + $(find ${LV2DIR} -name '*.ttl') \ + $(find "$@" -name '*.ttl') diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..1621d6f --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,34 @@ +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "mod-lilvlib" +version = "1.1.0" +description = "A set of helper methods to extract plugin and pedalboard data from TTLs using lilv" +readme = "README.md" +license = {text = "MIT"} +authors = [ + { name = "Falktx", email = "falktx@mod.audio" } +] +classifiers = [ + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Natural Language :: English", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", +] + +[project.urls] +Homepage = "https://github.com/mod-audio/lilvlib" +Repository = "https://github.com/mod-audio/lilvlib" + +[project.scripts] +lilvlib = "lilvlib.lilvlib:main" + +[tool.setuptools.packages.find] +include = ["lilvlib*"] diff --git a/python3-lilv-pkg/debian/changelog b/python3-lilv-pkg/debian/changelog index a0152c1..ad24171 100644 --- a/python3-lilv-pkg/debian/changelog +++ b/python3-lilv-pkg/debian/changelog @@ -1,3 +1,9 @@ +python3-lilv (5:0.30.2+git20260117) stable; urgency=medium + + * Update for latest lilv, now building with meson and using ctypes + + -- falkTX Sat, 17 Jan 2026 16:04:21 +0100 + python3-lilv (4:0.22.1+git20170620) stable; urgency=medium * Latest git version diff --git a/python3-lilv-pkg/debian/clean b/python3-lilv-pkg/debian/clean deleted file mode 100644 index 89ce83f..0000000 --- a/python3-lilv-pkg/debian/clean +++ /dev/null @@ -1 +0,0 @@ -.lock-wscript diff --git a/python3-lilv-pkg/debian/compat b/python3-lilv-pkg/debian/compat deleted file mode 100644 index 45a4fb7..0000000 --- a/python3-lilv-pkg/debian/compat +++ /dev/null @@ -1 +0,0 @@ -8 diff --git a/python3-lilv-pkg/debian/control b/python3-lilv-pkg/debian/control index adb0e1b..4159f0a 100644 --- a/python3-lilv-pkg/debian/control +++ b/python3-lilv-pkg/debian/control @@ -1,25 +1,18 @@ Source: python3-lilv Section: libs Priority: optional -Maintainer: Debian Multimedia Maintainers -Uploaders: - Alessio Treglia , - Jaromír Mikeš -Build-Depends: - debhelper (>= 8), - pkg-config, - python3-all-dev, - python3-numpy, - swig -Standards-Version: 3.9.3 -Homepage: http://drobilla.net/software/lilv/ -Vcs-Git: git://anonscm.debian.org/pkg-multimedia/lilv.git -Vcs-Browser: http://anonscm.debian.org/gitweb/?p=pkg-multimedia/lilv.git +Maintainer: falkTX +Build-Depends: debhelper-compat (= 13), + meson, + pkg-config, + python3-all-dev +Standards-Version: 4.5.0 +Rules-Requires-Root: no Package: python3-lilv Section: python Architecture: any -Depends: ${misc:Depends}, ${shlibs:Depends}, python3-numpy +Depends: ${misc:Depends}, ${shlibs:Depends} Description: library for simple use of LV2 plugins (runtime files) Lilv (formerly SLV2) is a library for LV2 hosts intended to make using LV2 Plugins as simple as possible (without sacrificing capabilities). diff --git a/python3-lilv-pkg/debian/copyright b/python3-lilv-pkg/debian/copyright deleted file mode 100644 index 40a85b1..0000000 --- a/python3-lilv-pkg/debian/copyright +++ /dev/null @@ -1,60 +0,0 @@ -Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ -Upstream-Name: Lilv -Upstream-Contact: David Robillard -Source: http://download.drobilla.net -Copyright: 2007-2012 David Robillard -License: ISC - -Files: * -Copyright: - 2007-2012 David Robillard - 2008 Krzysztof Foltman -License: ISC - -Files: waf -Copyright: 2005-2012 Thomas Nagy -License: BSD-3-clause - -Files: debian/* -Copyright: 2011-2012 Alessio Treglia -License: ISC - -License: ISC - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - . - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -License: BSD-3-clause - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - . - 1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - . - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - . - 3. The name of the author may not be used to endorse or promote products - derived from this software without specific prior written permission. - . - THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR - IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, - INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING - IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - POSSIBILITY OF SUCH DAMAGE. diff --git a/python3-lilv-pkg/debian/install b/python3-lilv-pkg/debian/install index d5e5da9..235d6c2 100644 --- a/python3-lilv-pkg/debian/install +++ b/python3-lilv-pkg/debian/install @@ -1,4 +1 @@ -/opt/mod/bin/ -/opt/mod/lib/lv2/ -/usr/bin/sord_validate_mod -/usr/lib/python*/ +system/* / diff --git a/python3-lilv-pkg/debian/patches/fix-link.patch b/python3-lilv-pkg/debian/patches/fix-link.patch deleted file mode 100644 index e8d7de1..0000000 --- a/python3-lilv-pkg/debian/patches/fix-link.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- lilv-static-py3.2-0.21.3+svn5644.orig/wscript -+++ lilv-static-py3.2-0.21.3+svn5644/wscript -@@ -379,6 +379,8 @@ def build(bld): - includes = ['..'], - swig_flags = '-c++ -py3 -python -Wall -I.. -llilv -features autodoc=1', - use = 'liblilv') -+ if bld.env.STATIC_PROGS: -+ obj.lib = ['lilv-0', 'sratom-0', 'sord-0', 'serd-0', 'dl', 'm'] - autowaf.use_lib(bld, obj, 'LILV') - - bld.install_files('${PYTHONDIR}', 'bindings/lilv.py') diff --git a/python3-lilv-pkg/debian/rules b/python3-lilv-pkg/debian/rules index 5afc3f7..2d33f6a 100755 --- a/python3-lilv-pkg/debian/rules +++ b/python3-lilv-pkg/debian/rules @@ -1,47 +1,4 @@ #!/usr/bin/make -f -export TMP_PREFIX=/tmp/python3-lilv-build/system - -export CFLAGS=-I$(TMP_PREFIX)/include -export CXXFLAGS=-I$(TMP_PREFIX)/include -export LDFLAGS=-L$(TMP_PREFIX)/lib -ldl -lm -export PKG_CONFIG_PATH=$(TMP_PREFIX)/lib/pkgconfig - -WAF = python3 ./waf - -override_dh_auto_configure: - $(WAF) configure \ - --static \ - --static-progs \ - --no-shared \ - --no-utils \ - --no-bash-completion \ - --prefix=/usr \ - --bindings - -override_dh_auto_build: - $(WAF) -j 1 - -override_dh_auto_clean: - $(WAF) clean || true - find -name "*.pyc" -delete - rm -rf build .waf* .lock-waf_linux2_build .lock-waf_linux_build - dh_auto_clean - -override_dh_auto_install: - $(WAF) install --destdir=$(CURDIR)/debian/tmp - - install -d $(CURDIR)/debian/tmp/usr/bin - install -m 755 $(CURDIR)/../sord_validate_mod $(CURDIR)/debian/tmp/usr/bin - - install -d $(CURDIR)/debian/tmp/opt/mod/bin - install -m 755 $(TMP_PREFIX)/bin/sord_validate $(CURDIR)/debian/tmp/opt/mod/bin - - install -d $(CURDIR)/debian/tmp/opt/mod/lib/lv2 - cp -r $(TMP_PREFIX)/lib/lv2/* $(CURDIR)/debian/tmp/opt/mod/lib/lv2 - -override_dh_strip: - # skip - %: dh $@ diff --git a/python3-lilv-pkg/debian/watch b/python3-lilv-pkg/debian/watch deleted file mode 100644 index 386e359..0000000 --- a/python3-lilv-pkg/debian/watch +++ /dev/null @@ -1,3 +0,0 @@ -version=3 -opts="uversionmangle=s/-/./,dversionmangle=s/~dfsg.*//" \ -http://download.drobilla.net/lilv-(.*)\.tar\.bz2 diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100755 index 0000000..06e1415 --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,22 @@ +#!/bin/bash +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT_DIR="$(dirname "$SCRIPT_DIR")" +DIST_DIR="${DIST_DIR:-$ROOT_DIR/dist}" + +cd "$ROOT_DIR" + +echo "==> Building python3-lilv debian package..." +./build-python3-lilv.sh + +echo "==> Building wheel..." +mkdir -p "$DIST_DIR" +pip3 install --upgrade pip +pip3 wheel --no-deps -w "$DIST_DIR" . + +echo "==> Copying debian package to $DIST_DIR..." +cp python3-lilv_*.deb "$DIST_DIR/" + +echo "==> Build complete. Artifacts in $DIST_DIR:" +ls -la "$DIST_DIR" diff --git a/scripts/test.sh b/scripts/test.sh new file mode 100755 index 0000000..1ab2e10 --- /dev/null +++ b/scripts/test.sh @@ -0,0 +1,35 @@ +#!/bin/bash +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT_DIR="$(dirname "$SCRIPT_DIR")" +DIST_DIR="${DIST_DIR:-$ROOT_DIR/dist}" +VENV_DIR="${VENV_DIR:-/tmp/test-venv}" + +echo "==> Creating virtual environment..." +python3 -m venv --system-site-packages "$VENV_DIR" +source "$VENV_DIR/bin/activate" +pip install --upgrade pip + +echo "==> Installing debian package..." +dpkg -i "$DIST_DIR"/python3-lilv_*.deb + +echo "==> Installing wheel..." +pip install "$DIST_DIR"/mod_lilvlib-*.whl + +echo "==> Testing imports..." +python -c "import lilv; print('lilv import OK')" +python -c "import lilvlib; print('lilvlib import OK, version:', lilvlib.__version__)" + +echo "==> Testing CLI..." +which lilvlib + +echo "==> Running test script..." +cd "$ROOT_DIR" +python test.py + +echo "==> Running pylint..." +pip install pylint +pylint -E lilvlib/lilvlib.py + +echo "==> All tests passed" diff --git a/setup.py b/setup.py deleted file mode 100644 index 64e5863..0000000 --- a/setup.py +++ /dev/null @@ -1,40 +0,0 @@ -import os -import re -import sys - -from setuptools import setup - -with open('lilvlib/__init__.py', 'r') as fh: - version = re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', fh.read(), re.MULTILINE).group(1) - -buildid = os.environ.get('SETUP_BUILD_ID', None) -version = '{0}.dev{1}'.format(version, buildid) if buildid else version - - -def main(): - setup( - name='mod-lilvlib', - version=version, - description='A set of helper methods to extract plugin and pedalboard data from TTLs using lilv', - author='Falktx', - author_email='falktx@gmail.com', - license='MIT', - packages=['lilvlib'], - install_requires=[], # lilv must be installed locally but cannot be resolved by PIP - entry_points={'console_scripts': ['lilvlib = lilvlib.lilvlib:main']}, - classifiers=[ - 'Intended Audience :: Developers', - 'License :: OSI Approved :: MIT License', - 'Natural Language :: English', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - ], - url='https://github.com/moddevices/lilvlib', - ) - - -if __name__ == '__main__': - if len(sys.argv) > 1 and sys.argv[1] == 'package_version': - print(version) - exit(0) - main() diff --git a/sord_validate_mod b/sord_validate_mod deleted file mode 100755 index 7e5aead..0000000 --- a/sord_validate_mod +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/bash - -PREFIX=/opt/mod -LV2DIR=/opt/mod/lib/lv2 - -if [ ! -d $LV2DIR/schemas.lv2 ]; then - echo "$LV2DIR/schemas.lv2 directory is missing" - exit -fi - -# don't check all lv2 bundles, if atom.lv2 is installed the others should be too -if [ ! -d $LV2DIR/atom.lv2 ]; then - echo "$LV2DIR/atom.lv2 directory is missing" - exit -fi - -if [ ! -d $LV2DIR/mod.lv2 ]; then - echo "$LV2DIR/mod.lv2 directory is missing" - exit -fi - -if [ ! -d $LV2DIR/kx-programs.lv2 ]; then - echo "$LV2DIR/kx-programs.lv2 directory is missing" - exit -fi - -if [ "$1"x == ""x ]; then - echo "usage: $0 /path/to/bundle" - exit -fi - -exec $PREFIX/bin/sord_validate $(find \ - $LV2DIR/{atom,buf-size,data-access,dynmanifest,event,instance-access,log,lv2core,midi,morph,options,parameters,patch,port-groups,port-props,presets,resize-port,schemas,state,time,ui,units,urid,uri-map,worker}.lv2 \ - $LV2DIR/mod.lv2 \ - $LV2DIR/modgui.lv2 \ - $LV2DIR/kx-meta \ - $LV2DIR/kx-external-ui.lv2 \ - $LV2DIR/kx-programs.lv2 \ - $LV2DIR/kx-rtmempool.lv2 \ - -name '*.ttl') $(find "$@" -name '*.ttl') - -# modpedal.lv2 is NIY -# $LV2DIR/modpedal.lv2 diff --git a/test.py b/test.py new file mode 100755 index 0000000..6f7976c --- /dev/null +++ b/test.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 + +import lilv +import lilvlib + +world = lilv.World() +world.load_all() + +for plugin in world.get_all_plugins(): + info = lilvlib.get_plugin_info(world, plugin) + if not info['errors']: + continue + + errors = info['errors'].copy() + + if 'plugin comment is missing' in errors: + errors.remove('plugin comment is missing') + + if 'plugin license is missing' in errors: + errors.remove('plugin license is missing') + + if 'plugin is missing version information' in errors: + errors.remove('plugin is missing version information') + + if errors: + print("----------------------", str(plugin.get_uri())) + print(errors)