From d425f4a83f8bdfe188fc6920209b54ced91db6d7 Mon Sep 17 00:00:00 2001 From: Sebastian Mohr Date: Wed, 7 Jan 2026 16:52:29 +0100 Subject: [PATCH 1/3] Depending on python version and hardware the `DEFAULT_BUFFER_SIZE` which we use to ingest/feed in fingerprinting can influence the generated fingerprints. This fixes the issue by only consuming the expected samples. --- acoustid.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/acoustid.py b/acoustid.py index b7e66cb..ccab5c4 100644 --- a/acoustid.py +++ b/acoustid.py @@ -221,13 +221,21 @@ def fingerprint(samplerate, channels, pcmiter, maxlength=MAX_AUDIO_LENGTH): fper = chromaprint.Fingerprinter() fper.start(samplerate, channels) - position = 0 # Samples of audio fed to the fingerprinter. - for block in pcmiter: - fper.feed(block) - position += len(block) // 2 # 2 bytes/sample. - if position >= endposition: + position = 0 + while position < endposition: + try: + block = next(pcmiter) + except StopIteration: + # No more data break + # Calculate remaining samples needed + remaining = endposition - position + # Feed only up to remaining samples + bytes_to_feed = min(len(block), remaining * 2) + fper.feed(block[:bytes_to_feed]) + position += bytes_to_feed // 2 + return fper.finish() except chromaprint.FingerprintError: raise FingerprintGenerationError("fingerprint calculation failed") From 01a5af00017fef1907bbc2b30fd0a417d8ebd68d Mon Sep 17 00:00:00 2001 From: Sebastian Mohr Date: Thu, 9 Apr 2026 15:18:17 +0200 Subject: [PATCH 2/3] Added a test which shows allows to reproduce the issue. --- test/test_acoustid.py | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/test/test_acoustid.py b/test/test_acoustid.py index 79b1a1e..3cd16b4 100644 --- a/test/test_acoustid.py +++ b/test/test_acoustid.py @@ -257,6 +257,46 @@ def raise_error(*_): with pytest.raises(acoustid.FingerprintGenerationError): acoustid.fingerprint(44100, 2, [b"\x00\x00"]) + @pytest.mark.parametrize( + "block_sizes", + [ + (2, 4), + ], + ) + def test_fingerprint_inconsistent_block_sizes( + self, monkeypatch, block_sizes: list[int] + ): + """Different block sizes may produce inconsistent results.""" + from unittest.mock import Mock + + def chunk_by_size(lst, size): + for i in range(0, len(lst), size): + yield lst[i : i + size] + + data = bytes(range(100)) # b'\x00\x01\x02...\x63' + + monkeypatch.setattr("chromaprint.Fingerprinter.start", Mock()) + monkeypatch.setattr("chromaprint.Fingerprinter.finish", Mock()) + + values = [] + for b in block_sizes: + mock = Mock() + monkeypatch.setattr("chromaprint.Fingerprinter.feed", mock) + + acoustid.fingerprint( + 1, + 1, + chunk_by_size(data, b), + 5, + # Half the block is consumed -> 5*2 = 10 + ) + + flattened_bytes = b"".join(c.args[0] for c in mock.call_args_list) + values.append(flattened_bytes) + # Different block lengths should always feed the same number + # of bytes + assert len(set(values)) == 1 + class TestSubmissions: @pytest.fixture(autouse=True) From 15f8ae24d365ad03e46bffbd4bc53e8447fb9b65 Mon Sep 17 00:00:00 2001 From: Sebastian Mohr Date: Thu, 9 Apr 2026 15:22:05 +0200 Subject: [PATCH 3/3] Apt get update should fix the broken dependency installation. --- .github/workflows/main.yml | 1 + test/test_acoustid.py | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4420f11..bd81047 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -28,6 +28,7 @@ jobs: - name: Install system dependencies run: | + sudo apt-get update sudo apt install --yes --no-install-recommends \ gstreamer1.0-plugins-good \ gstreamer1.0-plugins-bad \ diff --git a/test/test_acoustid.py b/test/test_acoustid.py index 3cd16b4..76fab98 100644 --- a/test/test_acoustid.py +++ b/test/test_acoustid.py @@ -288,13 +288,14 @@ def chunk_by_size(lst, size): 1, chunk_by_size(data, b), 5, - # Half the block is consumed -> 5*2 = 10 + # Twice the maxlength is consumed/feed -> 5*2 = 10 ) flattened_bytes = b"".join(c.args[0] for c in mock.call_args_list) values.append(flattened_bytes) - # Different block lengths should always feed the same number - # of bytes + + # Regardless of the block chunking + # we always feed the same bytes! assert len(set(values)) == 1