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/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") diff --git a/test/test_acoustid.py b/test/test_acoustid.py index 79b1a1e..76fab98 100644 --- a/test/test_acoustid.py +++ b/test/test_acoustid.py @@ -257,6 +257,47 @@ 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, + # 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) + + # Regardless of the block chunking + # we always feed the same bytes! + assert len(set(values)) == 1 + class TestSubmissions: @pytest.fixture(autouse=True)