From 00319c98e44d9b6b00ab43b67e8fdde893149c2d Mon Sep 17 00:00:00 2001 From: Ben Miller Date: Sun, 2 Jun 2024 18:50:24 -0700 Subject: [PATCH 1/7] reducing to higher level dependencies --- requirements.txt | 50 ++++++++---------------------------------------- 1 file changed, 8 insertions(+), 42 deletions(-) diff --git a/requirements.txt b/requirements.txt index 11a7a0f..c889919 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,45 +1,11 @@ -appdirs==1.4.4 -attrs==22.2.0 -audioread==3.0.0 -certifi==2022.12.7 -cffi==1.15.1 -charset-normalizer==2.1.1 -contourpy==1.0.6 -cycler==0.11.0 -decorator==5.1.1 -exceptiongroup==1.1.0 -execnet==1.9.0 -fonttools==4.38.0 -idna==3.4 -imageio==2.23.0 -iniconfig==2.0.0 -joblib==1.2.0 -kiwisolver==1.4.4 -librosa==0.10.1 -llvmlite==0.41.1 -matplotlib==3.6.2 -networkx==3.0 +matplotlib==3.8.2 noisereduce==2.0.1 -numba==0.58.1 -numpy==1.26.2 -packaging==23.0 -Pillow==9.4.0 -pluggy==1.0.0 -pooch==1.6.0 -pycparser==2.21 +numpy==1.26.4 +Pillow==10.2.0 pydub==0.25.1 -pyparsing==3.1.1 -python-dateutil==2.8.2 -PyWavelets==1.4.1 -requests==2.28.1 -resampy==0.4.2 +pytest==8.0.0 +scipy==1.12.0 +setuptools==59.6.0 scikit-image==0.19.3 -scikit-learn==1.3.2 -scipy==1.11.4 -six==1.16.0 -soundfile==0.12.1 -threadpoolctl==3.1.0 -tifffile==2022.10.10 -tomli==2.0.1 -tqdm==4.64.1 -urllib3==1.26.13 +tqdm==4.66.2 +torch==2.2.0 From 69881635c767c9e77e18b4dccd241c00be54006e Mon Sep 17 00:00:00 2001 From: Ben Miller Date: Sun, 2 Jun 2024 21:19:02 -0700 Subject: [PATCH 2/7] migrating to pyproject.toml, starting splitting to optional dependencies --- pyproject.toml | 56 ++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 11 --------- requirements_dev.txt | 2 -- setup.py | 45 ----------------------------------- 4 files changed, 56 insertions(+), 58 deletions(-) create mode 100644 pyproject.toml delete mode 100644 requirements.txt delete mode 100644 requirements_dev.txt delete mode 100644 setup.py diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..9dffc11 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,56 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "audalign" +version = "1.2.4" +description = "Audio Alignment and Recognition in Python" +readme = "README.md" +license = "MIT" +authors = [ + { name = "Ben Miller", email = "benfmiller132@gmail.com" }, +] +maintainers = [ + { name = "Ben Miller", email = "benfmiller132@gmail.com" }, +] +keywords = [ + "align", + "alignment", + "audio", + "fingerprinting", + "music", + "python", +] +dependencies = [ + "matplotlib==3.8.2", + "numpy==1.26.4", + "pydub==0.25.1", + "scipy==1.12.0", + "setuptools==59.6.0", + "tqdm==4.66.2", +] + +[project.optional-dependencies] +noisereduce = [ + "noisereduce==2.0.1", + "torch==2.2.0", +] +visrecognize = [ + "Pillow==10.2.0", + "scikit-image==0.19.3", +] +test = [ + "pytest==8.0.0", + "pytest-xdist==3.1.0", +] + +[project.urls] +Homepage = "http://github.com/benfmiller/audalign" +Documentation = "https://github.com/benfmiller/audalign/wiki" +Changelog = "https://github.com/benfmiller/audalign/blob/main/CHANGELOG.md" + +[tool.hatch.build.targets.sdist] +include = [ + "/audalign", +] diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index c889919..0000000 --- a/requirements.txt +++ /dev/null @@ -1,11 +0,0 @@ -matplotlib==3.8.2 -noisereduce==2.0.1 -numpy==1.26.4 -Pillow==10.2.0 -pydub==0.25.1 -pytest==8.0.0 -scipy==1.12.0 -setuptools==59.6.0 -scikit-image==0.19.3 -tqdm==4.66.2 -torch==2.2.0 diff --git a/requirements_dev.txt b/requirements_dev.txt deleted file mode 100644 index 0582dd5..0000000 --- a/requirements_dev.txt +++ /dev/null @@ -1,2 +0,0 @@ -pytest==7.2.0 -pytest-xdist==3.1.0 diff --git a/setup.py b/setup.py deleted file mode 100644 index fe3bf87..0000000 --- a/setup.py +++ /dev/null @@ -1,45 +0,0 @@ -import pathlib - -from setuptools import find_packages, setup - -HERE = pathlib.Path(__file__).parent -README = (HERE / "README.md").read_text() - - -def parse_requirements(requirements): - # load from requirements.txt - with open(requirements) as f: - lines = [l for l in f] - # remove spaces - stripped = list(map((lambda x: x.strip()), lines)) - # remove comments - nocomments = list(filter((lambda x: not x.startswith("#")), stripped)) - # remove empty lines - reqs = list(filter((lambda x: x), nocomments)) - return reqs - - -PACKAGE_NAME = "audalign" -PACKAGE_VERSION = "1.2.4" -SUMMARY = "Audio Alignment and Recognition in Python" - -REQUIREMENTS = parse_requirements("requirements.txt") - -setup( - name=PACKAGE_NAME, - version=PACKAGE_VERSION, - packages=find_packages(), - license="MIT", - description=SUMMARY, - long_description=README, - long_description_content_type="text/markdown", - author="Ben Miller", - author_email="benfmiller132@gmail.com", - maintainer="Ben Miller", - maintainer_email="benfmiller132@gmail.com", - url="http://github.com/benfmiller/audalign", - include_package_data=True, - platforms=["Unix", "Windows"], - install_requires=REQUIREMENTS, - keywords="python, audio, align, alignment, fingerprinting, music", -) From 25b86a0235f726787a55d38090937c7b4de3f08d Mon Sep 17 00:00:00 2001 From: Ben Miller Date: Sun, 2 Jun 2024 21:21:35 -0700 Subject: [PATCH 3/7] Code support for optional dependencies --- audalign/filehandler.py | 23 ++++++++++++++++++- audalign/recognizers/visrecognize/__init__.py | 13 +++++++++++ .../recognizers/visrecognize/visrecognize.py | 8 +++++-- tests/test_align.py | 10 ++++++++ tests/test_audalign.py | 5 ++++ tests/test_recognize.py | 8 +++++++ 6 files changed, 64 insertions(+), 3 deletions(-) diff --git a/audalign/filehandler.py b/audalign/filehandler.py index 5386d47..b50e817 100644 --- a/audalign/filehandler.py +++ b/audalign/filehandler.py @@ -4,8 +4,8 @@ import os import typing from functools import partial +from functools import wraps -import noisereduce import numpy as np from numpy.core.defchararray import array from pydub import AudioSegment, effects @@ -13,6 +13,13 @@ from audalign.config import BaseConfig +try: + import noisereduce +except ImportError: + # Optional dependency + ... + + cant_write_ext = [".mov", ".mp4", ".m4a"] cant_read_ext = [".txt", ".md", ".pkf", ".py", ".pyc"] can_read_ext = [ @@ -30,6 +37,18 @@ ".flac", ] +def _import_optional_dependencies(func): + @wraps(func) + def wrapper_decorator(*args, **kwargs): + try: + import noisereduce + except ImportError: + raise ImportError("skimage not found, please install 'visrecognize' module") + results = func(*args, **kwargs) + return results + + return wrapper_decorator + def find_files(path, extensions=["*"]): """ @@ -192,6 +211,7 @@ def _int16ify_data(data: array): return data.astype(np.int16) +@_import_optional_dependencies def noise_remove( filepath, noise_start, @@ -296,6 +316,7 @@ def noise_remove_directory( _reduce_noise(i) +@_import_optional_dependencies def _remove_noise( file_path, noise_section=[], diff --git a/audalign/recognizers/visrecognize/__init__.py b/audalign/recognizers/visrecognize/__init__.py index c326983..1ab9c18 100644 --- a/audalign/recognizers/visrecognize/__init__.py +++ b/audalign/recognizers/visrecognize/__init__.py @@ -9,11 +9,24 @@ import typing import copy from functools import partial +from functools import wraps +def _import_optional_dependencies(func): + @wraps(func) + def wrapper_decorator(*args, **kwargs): + try: + import skimage + except ImportError: + raise ImportError("skimage not found, please install 'visrecognize' module") + results = func(*args, **kwargs) + return results + + return wrapper_decorator class VisualRecognizer(BaseRecognizer): config: VisualConfig + @_import_optional_dependencies def __init__(self, config: VisualConfig = None): self.config = VisualConfig() if config is None else config self.last_recognition = None diff --git a/audalign/recognizers/visrecognize/visrecognize.py b/audalign/recognizers/visrecognize/visrecognize.py index e3fe624..25b22c1 100644 --- a/audalign/recognizers/visrecognize/visrecognize.py +++ b/audalign/recognizers/visrecognize/visrecognize.py @@ -12,8 +12,12 @@ from audalign.filehandler import find_files, get_shifted_file, read from PIL import Image from pydub.exceptions import CouldntDecodeError -from skimage.metrics import mean_squared_error -from skimage.metrics import structural_similarity as ssim +try: + from skimage.metrics import mean_squared_error + from skimage.metrics import structural_similarity as ssim +except ImportError: + # Optional dependency + ... upper_clip = 255 diff --git a/tests/test_align.py b/tests/test_align.py index 02be91d..5c5fb1e 100644 --- a/tests/test_align.py +++ b/tests/test_align.py @@ -4,6 +4,11 @@ import pytest from audalign import recognizers +try: + import skimage +except ImportError: + skimage = None + test_file_eig = "test_audio/test_shifts/Eigen-20sec.mp3" test_file_eig2 = "test_audio/test_shifts/Eigen-song-base.mp3" test_folder_eig = "test_audio/test_shifts/" @@ -98,6 +103,7 @@ def test_align_cor_spec_options(self, tmpdir): assert result is not None ad.pretty_print_alignment(result) + @pytest.mark.skipif(skimage is None, reason="visrecognize optional dependencies not installed") def test_align_vis(self, tmpdir): recognizer = ad.VisualRecognizer() recognizer.config.volume_threshold = 214 @@ -147,6 +153,7 @@ def test_align_files_load_fingerprints(self): ) assert result + @pytest.mark.skipif(skimage is None, reason="visrecognize optional dependencies not installed") def test_align_files_vis(self, tmpdir): recognizer = ad.VisualRecognizer() recognizer.config.volume_threshold = 214 @@ -214,6 +221,7 @@ def test_align_close_seconds_filter(self, tmpdir): ensure_close_seconds_filter(result, close_seconds_filter) class TestTargetAlign: + @pytest.mark.skipif(skimage is None, reason="visrecognize optional dependencies not installed") def test_target_align_vis(self, tmpdir): recognizer = ad.VisualRecognizer() recognizer.config.volume_threshold = 214 @@ -234,6 +242,7 @@ def test_target_align_vis(self, tmpdir): ) assert result is not None + @pytest.mark.skipif(skimage is None, reason="visrecognize optional dependencies not installed") def test_target_align_vis_mse(self, tmpdir): recognizer = ad.VisualRecognizer() recognizer.config.volume_threshold = 214 @@ -352,6 +361,7 @@ def test_fine_align_load_fingerprints(self): assert result is not None ad.pretty_print_alignment(result, match_keys="match_info") + @pytest.mark.skipif(skimage is None, reason="visrecognize optional dependencies not installed") def test_fine_align_visual(self, tmpdir): recognizer = ad.VisualRecognizer() recognizer.config.volume_threshold = 210 diff --git a/tests/test_audalign.py b/tests/test_audalign.py index 4f80278..12e8673 100644 --- a/tests/test_audalign.py +++ b/tests/test_audalign.py @@ -4,6 +4,10 @@ import audalign as ad import pytest +try: + import noisereduce +except ImportError: + noisereduce = None def test_always_true(): assert True @@ -162,6 +166,7 @@ def test_uniform_level_file_average(self, tmpdir): ) +@pytest.mark.skipif(noisereduce is None, reason="noisereduce optional dependencies not installed") class TestRemoveNoise: test_file = "test_audio/testers/test.mp3" diff --git a/tests/test_recognize.py b/tests/test_recognize.py index 7bbc409..2517b89 100644 --- a/tests/test_recognize.py +++ b/tests/test_recognize.py @@ -1,6 +1,10 @@ import audalign as ad import os import pytest +try: + import skimage +except ImportError: + skimage = None test_file = "test_audio/testers/test.mp3" test_file2 = "test_audio/testers/pink_noise.mp3" @@ -172,6 +176,7 @@ def test_recognize_locality_max_lags(self): self.fingerprint_recognizer.config = ad.config.fingerprint.FingerprintConfig() @pytest.mark.smoke + @pytest.mark.skipif(skimage is None, reason="visrecognize optional dependencies not installed") def test_visrecognize(self): recognizer = ad.VisualRecognizer() recognizer.config.img_width = 0.5 @@ -183,6 +188,7 @@ def test_visrecognize(self): ) assert results + @pytest.mark.skipif(skimage is None, reason="visrecognize optional dependencies not installed") def test_visrecognize_single_threaded(self): recognizer = ad.VisualRecognizer() @@ -197,6 +203,7 @@ def test_visrecognize_single_threaded(self): ) assert results + @pytest.mark.skipif(skimage is None, reason="visrecognize optional dependencies not installed") def test_visrecognize_options(self): recognizer = ad.VisualRecognizer() recognizer.config.img_width = 0.5 @@ -213,6 +220,7 @@ def test_visrecognize_options(self): assert results assert results["match_info"]["test.mp3"]["mse"][0] == 20000000.0 + @pytest.mark.skipif(skimage is None, reason="visrecognize optional dependencies not installed") def test_visrecognize_directory(self): recognizer = ad.VisualRecognizer() recognizer.config.img_width = 0.5 From 8b3f01f8586936842bfa60c505baaa3cc5351fc0 Mon Sep 17 00:00:00 2001 From: Ben Miller Date: Sun, 2 Jun 2024 21:37:08 -0700 Subject: [PATCH 4/7] Update github actions to use pyproject.toml --- .github/workflows/python-package.yml | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index de23f20..66281a4 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -64,9 +64,8 @@ jobs: sudo apt install libsndfile1-dev python -m pip install wheel python -m pip install --upgrade pip - python -m pip install flake8 pytest - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - if [ -f requirements_dev.txt ]; then pip install -r requirements_dev.txt; fi + python -m pip install flake8 + pip install -e .[visrecognize,noisereduce,test] - name: Lint with flake8 run: | @@ -78,10 +77,3 @@ jobs: - name: Test with pytest run: | pytest - -# replaced with this https://github.com/KyleMayes/install-llvm-action -# sudo apt install llvm-9 -# sudo -i -# cd /usr/bin -# rm llvm-config #if it exists already, which probably it does -# ln -s llvm-config-10 llvm-config From db18bb08f4470b3dc1d3283c535f23ba73a19977 Mon Sep 17 00:00:00 2001 From: Ben Miller Date: Sun, 2 Jun 2024 22:11:22 -0700 Subject: [PATCH 5/7] update GHA dependencies, add minimal build --- .github/workflows/python-package.yml | 106 ++++++++++++++++++++++++--- 1 file changed, 94 insertions(+), 12 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 66281a4..e593419 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -16,14 +16,13 @@ on: jobs: build: - runs-on: ubuntu-latest strategy: matrix: python-version: ["3.9", "3.10", "3.11"] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: install ffmpeg run: | @@ -44,20 +43,63 @@ jobs: cached: ${{ steps.cache-llvm.outputs.cache-hit }} - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} + cache: 'pip' - - name: Cache pip + - name: Install dependencies + run: | + sudo apt install libsndfile1-dev + python -m pip install wheel + python -m pip install --upgrade pip + python -m pip install flake8 + pip install -e .[visrecognize,noisereduce,test] + + - name: Lint with flake8 + run: | + # stop the build if there are Python syntax errors or undefined names + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + + - name: Test with pytest + run: | + pytest + + + build-minimal: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.9", "3.10", "3.11"] + + steps: + - uses: actions/checkout@v4 + + - name: install ffmpeg + run: | + sudo apt-get update && sudo apt-get install ffmpeg -y + + - name: Cache LLVM and Clang + id: cache-llvm uses: actions/cache@v2 with: - # This path is specific to Ubuntu - path: ~/.cache/pip - # Look to see if there is a cache hit for the corresponding requirements file - key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }} - restore-keys: | - ${{ runner.os }}-pip- - ${{ runner.os }}- + path: ${{ runner.temp }}/llvm + key: llvm-10.0 + + - name: Install LLVM and Clang + uses: KyleMayes/install-llvm-action@v1 + with: + version: "10.0" + directory: ${{ runner.temp }}/llvm + cached: ${{ steps.cache-llvm.outputs.cache-hit }} + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + cache: 'pip' - name: Install dependencies run: | @@ -65,7 +107,7 @@ jobs: python -m pip install wheel python -m pip install --upgrade pip python -m pip install flake8 - pip install -e .[visrecognize,noisereduce,test] + pip install -e .[test] - name: Lint with flake8 run: | @@ -77,3 +119,43 @@ jobs: - name: Test with pytest run: | pytest + + # + # build-windows: + # runs-on: windows-latest + # strategy: + # matrix: + # python-version: ["3.9", "3.10", "3.11"] + # + # steps: + # - uses: actions/checkout@v4 + # - uses: FedericoCarboni/setup-ffmpeg@v3 + # id: setup-ffmpeg + # with: + # ffmpeg-version: release + # linking-type: static + # github-token: ${{ github.server_url == 'https://github.com' && github.token || '' }} + # + # - name: Set up Python ${{ matrix.python-version }} + # uses: actions/setup-python@v5 + # with: + # python-version: ${{ matrix.python-version }} + # cache: 'pip' + # + # - name: Install dependencies + # run: | + # python -m pip install wheel + # python -m pip install --upgrade pip + # python -m pip install flake8 + # pip install -e .[visrecognize,noisereduce,test] + # + # - name: Lint with flake8 + # run: | + # # stop the build if there are Python syntax errors or undefined names + # flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + # # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + # flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + # + # - name: Test with pytest + # run: | + # pytest From 4d5ef5fca48e97624d48bbdb005bb5e2f117fb58 Mon Sep 17 00:00:00 2001 From: Ben Miller Date: Sun, 2 Jun 2024 22:20:04 -0700 Subject: [PATCH 6/7] Update publish GHA for pyproject.toml --- .github/workflows/python-publish.yml | 6 +++--- pyproject.toml | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 5534a2c..983d3c8 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -20,16 +20,16 @@ jobs: - uses: FedericoCarboni/setup-ffmpeg@v1-beta id: setup-ffmpeg - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.x' - name: Install dependencies run: | python -m pip install --upgrade pip - pip install setuptools wheel twine + pip install build wheel twine - name: Build and publish run: | - python setup.py sdist bdist_wheel + python -m build --wheel - name: Publish package uses: pypa/gh-action-pypi-publish@v1.8.11 with: diff --git a/pyproject.toml b/pyproject.toml index 9dffc11..a0f5478 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,6 +6,7 @@ build-backend = "hatchling.build" name = "audalign" version = "1.2.4" description = "Audio Alignment and Recognition in Python" +requires-python = ">= 3.8" readme = "README.md" license = "MIT" authors = [ From 59aeb58d9fe7c3f3193b012af76550d256f39ee2 Mon Sep 17 00:00:00 2001 From: Ben Miller Date: Sun, 2 Jun 2024 22:29:09 -0700 Subject: [PATCH 7/7] Update readme, changelog for 1.3.0 release --- CHANGELOG.md | 7 +++++++ README.md | 9 +++++++-- pyproject.toml | 2 +- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cee4f9e..f9dad84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Change Log +## [1.3.0] 2024 - 06 - 02 + +### Changed + +- Migrated to pyproject.toml +- Extracted noisereduce and visrecognize to optional dependencies + ## [1.2.4] 2024 - 01 - 07 ### Added diff --git a/README.md b/README.md index 57d488f..f1a4e2f 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,11 @@ pip install audalign in the directory +### Optional dependencies + +- visrecognize: additional recognizer based on spectrogram image comparison. `pip install audalign[visrecognize]` +- noisereduce: wrapper utils around [timsainb/noisereduce](https://github.com/timsainb/noisereduce). `pip install audalign[noisereduce]` + ## Recognizers There are currently four included recognizers, each with their own config objects. @@ -64,7 +69,7 @@ import audalign as ad fingerprint_rec = ad.FingerprintRecognizer() correlation_rec = ad.CorrelationRecognizer() cor_spec_rec = ad.CorrelationSpectrogramRecognizer() -visual_rec = ad.VisualRecognizer() +visual_rec = ad.VisualRecognizer() # requires installting optional visrecognize dependencies fingerprint_rec.config.set_accuracy(3) # recognizer.config.some_item @@ -108,7 +113,7 @@ Correlation is more precise than fingerprints and will always give a best alignm ## Other Functions ```python -# wrapper for timsainb/noisereduce +# wrapper for timsainb/noisereduce, optional dependency ad.remove_noise_file( "target/file", "5", # noise start in seconds diff --git a/pyproject.toml b/pyproject.toml index a0f5478..4fc5d3f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "audalign" -version = "1.2.4" +version = "1.3.0" description = "Audio Alignment and Recognition in Python" requires-python = ">= 3.8" readme = "README.md"