From fc077ac8dae027be8a2d429bca455b6ac08ddcd2 Mon Sep 17 00:00:00 2001 From: dotX12 Date: Mon, 16 May 2022 04:56:39 +0300 Subject: [PATCH 01/20] Reformat code --- shazamio/algorithm.py | 76 ++++++++++++++++++++++++++++--------------- shazamio/signature.py | 32 ++++++++++++------ 2 files changed, 72 insertions(+), 36 deletions(-) diff --git a/shazamio/algorithm.py b/shazamio/algorithm.py index 4fdbf20..9821d91 100644 --- a/shazamio/algorithm.py +++ b/shazamio/algorithm.py @@ -46,17 +46,23 @@ def __init__(self): # Used when processing input: - self.ring_buffer_of_samples: RingBuffer[int] = RingBuffer(buffer_size=2048, - default_value=0) - - self.fft_outputs: RingBuffer[List[float]] = RingBuffer(buffer_size=256, - default_value=[0. * 1025]) + self.ring_buffer_of_samples: RingBuffer[int] = RingBuffer( + buffer_size=2048, + default_value=0 + ) + + self.fft_outputs: RingBuffer[List[float]] = RingBuffer( + buffer_size=256, + default_value=[0. * 1025] + ) # Lists of 1025 floats, premultiplied with a Hanning function before being # passed through FFT, computed from # the ring buffer every new 128 samples - self.spread_fft_output: RingBuffer[List[float]] = RingBuffer(buffer_size=256, - default_value=[0] * 1025) + self.spread_fft_output: RingBuffer[List[float]] = RingBuffer( + buffer_size=256, + default_value=[0] * 1025 + ) # How much data to send to Shazam at once? @@ -95,11 +101,15 @@ def get_next_signature(self) -> Optional[DecodedMessage]: return None while (len(self.input_pending_processing) - self.samples_processed >= 128 and (self.next_signature.number_samples / self.next_signature.sample_rate_hz < - self.MAX_TIME_SECONDS or sum(len(peaks) for peaks in - self.next_signature.frequency_band_to_sound_peaks.values()) + self.MAX_TIME_SECONDS or sum( + len(peaks) for peaks in + self.next_signature.frequency_band_to_sound_peaks.values() + ) < self.MAX_PEAKS)): - self.process_input(self.input_pending_processing - [self.samples_processed:self.samples_processed + 128]) + self.process_input( + self.input_pending_processing + [self.samples_processed:self.samples_processed + 128] + ) self.samples_processed += 128 returned_signature = self.next_signature @@ -109,12 +119,18 @@ def get_next_signature(self) -> Optional[DecodedMessage]: self.next_signature.number_samples = 0 self.next_signature.frequency_band_to_sound_peaks = {} - self.ring_buffer_of_samples: RingBuffer[int] = RingBuffer(buffer_size=2048, - default_value=0) - self.fft_outputs: RingBuffer[List[float]] = RingBuffer(buffer_size=256, - default_value=[0. * 1025]) - self.spread_fft_output: RingBuffer[List[float]] = RingBuffer(buffer_size=256, - default_value=[0] * 1025) + self.ring_buffer_of_samples: RingBuffer[int] = RingBuffer( + buffer_size=2048, + default_value=0 + ) + self.fft_outputs: RingBuffer[List[float]] = RingBuffer( + buffer_size=256, + default_value=[0. * 1025] + ) + self.spread_fft_output: RingBuffer[List[float]] = RingBuffer( + buffer_size=256, + default_value=[0] * 1025 + ) return returned_signature @@ -175,8 +191,10 @@ def do_peak_spreading(self): (self.spread_fft_output.position + former_fft_num) % self.spread_fft_output.buffer_size] - former_fft_output[position] = max_value = max(former_fft_output[position], - max_value) + former_fft_output[position] = max_value = max( + former_fft_output[position], + max_value + ) # Save output locally @@ -206,7 +224,8 @@ def do_peak_recognition(self): for neighbor_offset in [*range(-10, -3, 3), -3, 1, *range(2, 9, 3)]: max_neighbor_in_fft_minus_49 = max( fft_minus_49[bin_position + neighbor_offset], - max_neighbor_in_fft_minus_49) + max_neighbor_in_fft_minus_49 + ) if fft_minus_46[bin_position] > max_neighbor_in_fft_minus_49: @@ -228,12 +247,15 @@ def do_peak_recognition(self): fft_number = self.spread_fft_output.num_written - 46 - peak_magnitude = log(max(1 / 64, fft_minus_46[bin_position]) - ) * 1477.3 + 6144 + peak_magnitude = log( + max(1 / 64, fft_minus_46[bin_position]) + ) * 1477.3 + 6144 peak_magnitude_before = log( - max(1 / 64, fft_minus_46[bin_position - 1])) * 1477.3 + 6144 + max(1 / 64, fft_minus_46[bin_position - 1]) + ) * 1477.3 + 6144 peak_magnitude_after = log( - max(1 / 64, fft_minus_46[bin_position + 1])) * 1477.3 + 6144 + max(1 / 64, fft_minus_46[bin_position + 1]) + ) * 1477.3 + 6144 peak_variation_1 = (peak_magnitude * 2 - peak_magnitude_before - peak_magnitude_after) @@ -261,6 +283,8 @@ def do_peak_recognition(self): self.next_signature.frequency_band_to_sound_peaks[band] = [] self.next_signature.frequency_band_to_sound_peaks[band].append( - FrequencyPeak(fft_number, int(peak_magnitude), - int(corrected_peak_frequency_bin), 16000) + FrequencyPeak( + fft_number, int(peak_magnitude), + int(corrected_peak_frequency_bin), 16000 + ) ) diff --git a/shazamio/signature.py b/shazamio/signature.py index d4f0922..4f2de86 100644 --- a/shazamio/signature.py +++ b/shazamio/signature.py @@ -14,16 +14,18 @@ class RawSignatureHeader(LittleEndianStructure): _fields_ = [ ('magic1', c_uint32), # Fixed 0xcafe2580 - 80 25 fe ca - ('crc32', c_uint32), # CRC-32 for all of the following (so excluding these first 8 bytes) + ('crc32', c_uint32), # CRC-32 for all following (so excluding these first 8 bytes) ('size_minus_header', c_uint32), # Total size of the message, minus the size of the current header (which is 48 bytes) ('magic2', c_uint32), # Fixed 0x94119c00 - 00 9c 11 94 ('void1', c_uint32 * 3), # Void ('shifted_sample_rate_id', c_uint32), - # A member of SampleRate (usually 3 for 16000 Hz), left-shifted by 27 (usually giving 0x18000000 - 00 00 00 18) + # A member of SampleRate (usually 3 for 16000 Hz), left-shifted by 27 (usually giving + # 0x18000000 - 00 00 00 18) ('void2', c_uint32 * 2), # Void, or maybe used only in "rolling window" mode? ('number_samples_plus_divided_sample_rate', c_uint32), - # int(number_of_samples + sample_rate * 0.24) - As the sample rate is known thanks to the field above, + # int(number_of_samples + sample_rate * 0.24) - As the sample rate is known thanks to the + # field above, # it can be inferred and subtracted so that we obtain the number of samples, # and from the number of samples and sample rate we can obtain the length of the recording ('fixed_value', c_uint32) @@ -38,8 +40,10 @@ class FrequencyPeak: corrected_peak_frequency_bin: int = None sample_rate_hz: int = None - def __init__(self, fft_pass_number: int, peak_magnitude: int, corrected_peak_frequency_bin: int, - sample_rate_hz: int): + def __init__( + self, fft_pass_number: int, peak_magnitude: int, corrected_peak_frequency_bin: int, + sample_rate_hz: int + ): self.fft_pass_number = fft_pass_number self.peak_magnitude = peak_magnitude self.corrected_peak_frequency_bin = corrected_peak_frequency_bin @@ -48,7 +52,7 @@ def __init__(self, fft_pass_number: int, peak_magnitude: int, corrected_peak_fre def get_frequency_hz(self) -> float: return self.corrected_peak_frequency_bin * (self.sample_rate_hz / 2 / 1024 / 64) - # ^ Convert back a FFT bin to a frequency, given a 16 KHz sample + # ^ Convert back FFT bin to a frequency, given a 16 KHz sample # rate, 1024 useful bins and the multiplication by 64 made before # storing the information @@ -93,7 +97,9 @@ def decode_from_binary(cls, data: bytes): self.sample_rate_hz = int(SampleRate(header.shifted_sample_rate_id >> 27).name.strip('_')) - self.number_samples = int(header.number_samples_plus_divided_sample_rate - self.sample_rate_hz * 0.24) + self.number_samples = int( + header.number_samples_plus_divided_sample_rate - self.sample_rate_hz * 0.24 + ) # Read the type-length-value sequence that follows the header @@ -145,7 +151,10 @@ def decode_from_binary(cls, data: bytes): corrected_peak_frequency_bin = int.from_bytes(frequency_peaks_buf.read(2), 'little') self.frequency_band_to_sound_peaks[frequency_band].append( - FrequencyPeak(fft_pass_number, peak_magnitude, corrected_peak_frequency_bin, self.sample_rate_hz) + FrequencyPeak( + fft_pass_number, peak_magnitude, corrected_peak_frequency_bin, + self.sample_rate_hz + ) ) return self @@ -180,7 +189,8 @@ def encode_to_json(self) -> dict: } for frequency_peak in frequency_peaks ] - for frequency_band, frequency_peaks in sorted(self.frequency_band_to_sound_peaks.items()) + for frequency_band, frequency_peaks in + sorted(self.frequency_band_to_sound_peaks.items()) } } @@ -192,7 +202,9 @@ def encode_to_binary(self) -> bytes: header.magic2 = 0x94119c00 header.shifted_sample_rate_id = int(getattr(SampleRate, '_%s' % self.sample_rate_hz)) << 27 header.fixed_value = ((15 << 19) + 0x40000) - header.number_samples_plus_divided_sample_rate = int(self.number_samples + self.sample_rate_hz * 0.24) + header.number_samples_plus_divided_sample_rate = int( + self.number_samples + self.sample_rate_hz * 0.24 + ) contents_buf = BytesIO() From 2187d8b68d0dd96e28385b0a438d3eae98af6c5b Mon Sep 17 00:00:00 2001 From: dotX12 Date: Mon, 16 May 2022 04:59:43 +0300 Subject: [PATCH 02/20] Reformat code --- shazamio/enums.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shazamio/enums.py b/shazamio/enums.py index e260006..415be9c 100644 --- a/shazamio/enums.py +++ b/shazamio/enums.py @@ -38,4 +38,4 @@ class FrequencyBand(IntEnum): hz_250_520 = 0 hz_520_1450 = 1 hz_1450_3500 = 2 - hz_3500_5500 = 3 # This one (3.5 KHz - 5.5 KHz) should not be used in legacy mode \ No newline at end of file + hz_3500_5500 = 3 # This one (3.5 KHz - 5.5 KHz) should not be used in legacy mode From 75ac6b1a377fa8db61f4ad1a5f25b57a5fff0ebc Mon Sep 17 00:00:00 2001 From: dotX12 Date: Mon, 16 May 2022 05:01:46 +0300 Subject: [PATCH 03/20] Reformat code --- shazamio/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shazamio/utils.py b/shazamio/utils.py index 1ff22a2..ae7ec8a 100644 --- a/shazamio/utils.py +++ b/shazamio/utils.py @@ -25,7 +25,7 @@ async def get_file_bytes(file: FileT) -> bytes: return await f.read() -async def get_song(data: SongT) -> bytes: +async def get_song(data: SongT) -> Union[AudioSegment]: if isinstance(data, (str, pathlib.Path)): song_bytes = await get_file_bytes(file=data) From 0baa29c3d6641511982144f83f9f84f5cdf28b8d Mon Sep 17 00:00:00 2001 From: dotX12 Date: Mon, 16 May 2022 05:09:48 +0300 Subject: [PATCH 04/20] Micro fixes --- shazamio/converter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shazamio/converter.py b/shazamio/converter.py index 9e21366..a77d7b6 100644 --- a/shazamio/converter.py +++ b/shazamio/converter.py @@ -57,7 +57,7 @@ def normalize_audio_data(audio: AudioSegment) -> AudioSegment: def create_signature_generator(audio: AudioSegment) -> SignatureGenerator: signature_generator = SignatureGenerator() signature_generator.feed_input(audio.get_array_of_samples()) - signature_generator.MAX_TIME_SECONDS = 8 + signature_generator.MAX_TIME_SECONDS = 12 if audio.duration_seconds > 12 * 3: - signature_generator.samples_processed += 16000 * (int(audio.duration_seconds / 16) - 6) + signature_generator.samples_processed += 16000 * (int(audio.duration_seconds / 2) - 6) return signature_generator From aa4aef573fa4c625e5f877b95b76824404d2ce35 Mon Sep 17 00:00:00 2001 From: dotX12 Date: Thu, 23 Jun 2022 11:09:18 +0300 Subject: [PATCH 05/20] include pytest --- .github/workflows/pytest.yml | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .github/workflows/pytest.yml diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml new file mode 100644 index 0000000..c5d335c --- /dev/null +++ b/.github/workflows/pytest.yml @@ -0,0 +1,31 @@ +name: PyTest +on: push + +jobs: + test: + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + - name: Check out repository code + uses: actions/checkout@v2 + + # Setup Python (faster than using Python container) + - name: Setup Python + uses: actions/setup-python@v2 + with: + python-version: "3.x" + + - name: Install pip + run: | + python -m pip install --upgrade pip + + - name: Install dependencies + run: | + pip install poetry + poetry config virtualenvs.create false + poetry install --no-dev + + - name: Run test suite + run: | + pytest \ No newline at end of file From 8f4af2dcff74bc60c476afa0b9aed060944599cf Mon Sep 17 00:00:00 2001 From: dotX12 Date: Thu, 23 Jun 2022 11:15:06 +0300 Subject: [PATCH 06/20] fix tests --- .github/workflows/pytest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index c5d335c..44b9b73 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -14,7 +14,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v2 with: - python-version: "3.x" + python-version: "3.10" - name: Install pip run: | From c89fcff62fae951da96ea1777d0d28025e828e21 Mon Sep 17 00:00:00 2001 From: dotX12 Date: Thu, 23 Jun 2022 11:16:22 +0300 Subject: [PATCH 07/20] test2 --- .github/workflows/test2.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .github/workflows/test2.yml diff --git a/.github/workflows/test2.yml b/.github/workflows/test2.yml new file mode 100644 index 0000000..f7cd66a --- /dev/null +++ b/.github/workflows/test2.yml @@ -0,0 +1,23 @@ +name: CI +on: pull_request + +jobs: + ci: + strategy: + fail-fast: false + matrix: + python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"] + poetry-version: ["1.0", "1.1.11"] + os: [ubuntu-18.04, macos-latest, windows-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Run image + uses: abatilo/actions-poetry@v2.0.0 + with: + poetry-version: ${{ matrix.poetry-version }} + - name: View poetry --help + run: poetry --help From cb6e5890c4db13587996cd37655cea86dd80d1de Mon Sep 17 00:00:00 2001 From: dotX12 Date: Thu, 23 Jun 2022 11:33:55 +0300 Subject: [PATCH 08/20] update tests + update min python version to 3.8 --- .github/workflows/pytest.yml | 52 +++++--- .github/workflows/test2.yml | 23 ---- pyproject.toml | 16 ++- requirements.txt | 16 ++- setup.py | 11 +- tests/conftest.py | 46 +------ tests/test_21_issue.py | 239 ++++++++++++++--------------------- tests/test_recognize.py | 12 +- 8 files changed, 164 insertions(+), 251 deletions(-) delete mode 100644 .github/workflows/test2.yml diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 44b9b73..af3f8c2 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -1,31 +1,45 @@ -name: PyTest -on: push +name: CI + +on: [push] jobs: + test: runs-on: ubuntu-latest - timeout-minutes: 10 steps: - - name: Check out repository code - uses: actions/checkout@v2 + - uses: actions/checkout@v1 + with: + fetch-depth: 1 - # Setup Python (faster than using Python container) - - name: Setup Python - uses: actions/setup-python@v2 + - name: Set up Python 3.7 + uses: actions/setup-python@v1 with: - python-version: "3.10" + python-version: 3.7 - - name: Install pip - run: | - python -m pip install --upgrade pip + - name: Install Poetry + uses: dschep/install-poetry-action@v1.2 - - name: Install dependencies - run: | - pip install poetry - poetry config virtualenvs.create false - poetry install --no-dev + - name: Cache Poetry virtualenv + uses: actions/cache@v1 + id: cache + with: + path: ~/.virtualenvs + key: poetry-${{ hashFiles('**/poetry.lock') }} + restore-keys: | + poetry-${{ hashFiles('**/poetry.lock') }} - - name: Run test suite + - name: Set Poetry config run: | - pytest \ No newline at end of file + poetry config settings.virtualenvs.in-project false + poetry config settings.virtualenvs.path ~/.virtualenvs + + - name: Install Dependencies + run: poetry install + if: steps.cache.outputs.cache-hit != 'true' + + - name: Code Quality + run: poetry run black . --check + + - name: Test with pytest + run: poetry run pytest \ No newline at end of file diff --git a/.github/workflows/test2.yml b/.github/workflows/test2.yml deleted file mode 100644 index f7cd66a..0000000 --- a/.github/workflows/test2.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: CI -on: pull_request - -jobs: - ci: - strategy: - fail-fast: false - matrix: - python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"] - poetry-version: ["1.0", "1.1.11"] - os: [ubuntu-18.04, macos-latest, windows-latest] - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - name: Run image - uses: abatilo/actions-poetry@v2.0.0 - with: - poetry-version: ${{ matrix.poetry-version }} - - name: View poetry --help - run: poetry --help diff --git a/pyproject.toml b/pyproject.toml index 0868a49..e0eee40 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "shazamio" version = "0.0.6" -description = "Is a asynchronous framework from reverse engineered Shazam API written in Python 3.6+ with asyncio and aiohttp." +description = "Is a asynchronous framework from reverse engineered Shazam API written in Python 3.8+ with asyncio and aiohttp." authors = ["dotX12"] license = "MIT License" keywords = ["python", "shazam", "music", "recognize", "api", "async", "asyncio", "aiohttp", "identification"] @@ -14,12 +14,15 @@ include = [ ] [tool.poetry.dependencies] -python = "^3.6" -numpy = "^1.20.1" -aiohttp = "^3.7.4" -pydub = "^0.24.1" +python = "^3.8" +numpy = "^1.22.1" +aiohttp = "^3.8.1" +pydub = "^0.25.1" dataclass-factory = "^2.10.1" -aiofiles = "^0.6.0" +aiofiles = "^0.8.0" +pytest = "^7.1.2" +pytest-asyncio = "^0.18.3" +anyio = "^3.6.1" [build-system] requires = ["poetry-core>=1.0.0", "wheel>=0.36,<1.0", "poetry>=1.1,<2", "virtualenv==20.0.33"] @@ -28,4 +31,5 @@ build-backend = "poetry.core.masonry.api" [tool.pytest.ini_options] addopts = "-scoped" +asyncio_mode = "auto" filterwarnings = ["ignore::DeprecationWarning"] diff --git a/requirements.txt b/requirements.txt index 67dc56e..df31e73 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,12 +1,16 @@ -aiofiles -aiohttp -async-timeout==3.0.1 +aiofiles~=0.8.0 +aiohttp~=3.8.1 attrs==20.3.0 chardet==3.0.4 -dataclass-factory +dataclass-factory==2.10.1 idna==3.1 multidict==5.1.0 -numpy -pydub==0.24.1 +numpy~=1.22.1 +pydub==0.25.1 typing-extensions==3.7.4.3 yarl==1.6.3 + +pytest~=7.1.2 +setuptools~=60.7.0 +pytest-asyncio~=0.18.3 +anyio~=3.6.1 \ No newline at end of file diff --git a/setup.py b/setup.py index 8920823..0ea1461 100644 --- a/setup.py +++ b/setup.py @@ -11,8 +11,13 @@ long_description=long_description, long_description_content_type="text/markdown", url="https://github.com/dotX12/ShazamIO", - install_requires=['aiohttp', 'pydub', 'numpy', 'aiofiles', 'dataclass-factory',], + install_requires=[ + "aiohttp", + "pydub", + "numpy", + "aiofiles", + "dataclass-factory", + ], packages=setuptools.find_packages(), - python_requires='>=3.6', - + python_requires=">=3.8", ) diff --git a/tests/conftest.py b/tests/conftest.py index 91d99c7..f7d680a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,51 +1,11 @@ import asyncio -from functools import wraps -import pytest +import pytest_asyncio -try: - from asyncio.proactor_events import _ProactorBasePipeTransport - HAS_PROACTOR = True -except ImportError: - _ProactorBasePipeTransport = None - HAS_PROACTOR = False - - -@pytest.fixture(scope="module") -def silence_event_loop_closed(): - """ - Mostly used to suppress "unhandled exception" error due to - ``_ProactorBasePipeTransport`` raising an exception when doing ``__del__`` - """ - if not HAS_PROACTOR: - return False - assert _ProactorBasePipeTransport is not None - if hasattr(_ProactorBasePipeTransport, "old_del"): - return True - - # From: https://github.com/aio-libs/aiohttp/issues/4324#issuecomment-733884349 - def silencer(func): - @wraps(func) - def wrapper(self, *args, **kwargs): - try: - return func(self, *args, **kwargs) - except RuntimeError as e: - if str(e) != "Event loop is closed": - raise - - return wrapper - - # noinspection PyUnresolvedReferences - old_del = _ProactorBasePipeTransport.__del__ - _ProactorBasePipeTransport._old_del = old_del - _ProactorBasePipeTransport.__del__ = silencer(old_del) - return True - - -@pytest.fixture(scope="session") +@pytest_asyncio.fixture(scope="session") def event_loop(): - loop = asyncio.get_event_loop() + loop = asyncio.new_event_loop() yield loop loop.run_until_complete(asyncio.sleep(1)) loop.close() diff --git a/tests/test_21_issue.py b/tests/test_21_issue.py index 20de7fa..a3c727f 100644 --- a/tests/test_21_issue.py +++ b/tests/test_21_issue.py @@ -1,11 +1,9 @@ -import asyncio -import pytest +import pytest_asyncio from shazamio import Serialize -from shazamio import Shazam -@pytest.fixture(scope="session") +@pytest_asyncio.fixture(scope="session") def song_response(): response = { "matches": [ @@ -13,12 +11,10 @@ def song_response(): "id": "230272433", "offset": 187.4215, "timeskew": -0.0001565814, - "frequencyskew": -0.000080525875 + "frequencyskew": -0.000080525875, } ], - "location": { - "accuracy": 0.01 - }, + "location": {"accuracy": 0.01}, "timestamp": 1652380596486, "timezone": "Europe/Moscow", "track": { @@ -29,43 +25,39 @@ def song_response(): "subtitle": "Steve Jablonsky", "images": { "background": "https://is3-ssl.mzstatic.com/image/thumb/Features125/v4/c2/35/67" - "/c23567d0-0f59-8573-5848-0a7844ed3416/mzl.fxlsnewc.jpg/800x800cc" - ".jpg", + "/c23567d0-0f59-8573-5848-0a7844ed3416/mzl.fxlsnewc.jpg/800x800cc" + ".jpg", "coverart": "https://is2-ssl.mzstatic.com/image/thumb/Music/c8/a6/4a/mzi.akybahch" - ".jpg/400x400cc.jpg", + ".jpg/400x400cc.jpg", "coverarthq": "https://is2-ssl.mzstatic.com/image/thumb/Music/c8/a6/4a/mzi" - ".akybahch.jpg/400x400cc.jpg", - "joecolor": "b:010417p:aec4d5s:ef9b41t:8c9dafq:bf7c38" + ".akybahch.jpg/400x400cc.jpg", + "joecolor": "b:010417p:aec4d5s:ef9b41t:8c9dafq:bf7c38", }, "share": { "subject": "Arrival To Earth - Steve Jablonsky", "text": "I used Shazam to discover Arrival To Earth by Steve Jablonsky.", "href": "https://www.shazam.com/track/47440537/arrival-to-earth", "image": "https://is2-ssl.mzstatic.com/image/thumb/Music/c8/a6/4a/mzi.akybahch" - ".jpg/400x400cc.jpg", + ".jpg/400x400cc.jpg", "twitter": "I used @Shazam to discover Arrival To Earth by Steve Jablonsky.", "html": "https://www.shazam.com/snippets/email-share/47440537?lang=en&country=GB", "avatar": "https://is3-ssl.mzstatic.com/image/thumb/Features125/v4/c2/35/67" - "/c23567d0-0f59-8573-5848-0a7844ed3416/mzl.fxlsnewc.jpg/800x800cc.jpg", - "snapchat": "https://www.shazam.com/partner/sc/track/47440537" + "/c23567d0-0f59-8573-5848-0a7844ed3416/mzl.fxlsnewc.jpg/800x800cc.jpg", + "snapchat": "https://www.shazam.com/partner/sc/track/47440537", }, "hub": { "type": "APPLEMUSIC", "image": "https://images.shazam.com/static/icons/hub/ios/v5/applemusic_{" - "scalefactor}.png", + "scalefactor}.png", "actions": [ - { - "name": "apple", - "type": "applemusicplay", - "id": "265018693" - }, + {"name": "apple", "type": "applemusicplay", "id": "265018693"}, { "name": "apple", "type": "uri", "uri": "https://audio-ssl.itunes.apple.com/itunes-assets/AudioPreview125" - "/v4/9c/1d/ec/9c1dec79-214a-c3a4-846b-87787421dc33" - "/mzaf_5388156565692493963.plus.aac.ep.m4a" - } + "/v4/9c/1d/ec/9c1dec79-214a-c3a4-846b-87787421dc33" + "/mzaf_5388156565692493963.plus.aac.ep.m4a", + }, ], "options": [ { @@ -74,107 +66,91 @@ def song_response(): { "name": "hub:applemusic:deeplink", "type": "applemusicopen", - "uri": - "https://music.apple.com/gb/album/arrival-to-earth/265018176" - "?i=265018693&mttnagencyid=s2n&mttnsiteid=125115&mttn3pid" - "=Apple-Shazam&mttnsub1=Shazam_ios&mttnsub2=5348615A-616D" - "-3235-3830-44754D6D5973&itscg=30201&app=music&itsct=Shazam_ios" + "uri": "https://music.apple.com/gb/album/arrival-to-earth/265018176" + "?i=265018693&mttnagencyid=s2n&mttnsiteid=125115&mttn3pid" + "=Apple-Shazam&mttnsub1=Shazam_ios&mttnsub2=5348615A-616D" + "-3235-3830-44754D6D5973&itscg=30201&app=music&itsct=Shazam_ios", }, { "name": "hub:applemusic:deeplink", "type": "uri", - "uri": - "https://music.apple.com/gb/album/arrival-to-earth/265018176" - "?i=265018693&mttnagencyid=s2n&mttnsiteid=125115&mttn3pid" - "=Apple-Shazam&mttnsub1=Shazam_ios&mttnsub2=5348615A-616D" - "-3235-3830-44754D6D5973&itscg=30201&app=music&itsct=Shazam_ios" - } + "uri": "https://music.apple.com/gb/album/arrival-to-earth/265018176" + "?i=265018693&mttnagencyid=s2n&mttnsiteid=125115&mttn3pid" + "=Apple-Shazam&mttnsub1=Shazam_ios&mttnsub2=5348615A-616D" + "-3235-3830-44754D6D5973&itscg=30201&app=music&itsct=Shazam_ios", + }, ], - "beacondata": { - "type": "open", - "providername": "applemusic" - }, + "beacondata": {"type": "open", "providername": "applemusic"}, "image": "https://images.shazam.com/static/icons/hub/ios/v5/overflow-open" - "-option_{scalefactor}.png", + "-option_{scalefactor}.png", "type": "open", "listcaption": "Open in Apple Music", - "overflowimage": - "https://images.shazam.com/static/icons/hub/ios/v5/applemusic" - "-overflow_{scalefactor}.png", + "overflowimage": "https://images.shazam.com/static/icons/hub/ios/v5/applemusic" + "-overflow_{scalefactor}.png", "colouroverflowimage": False, - "providername": "applemusic" + "providername": "applemusic", }, { "caption": "BUY", "actions": [ { "type": "uri", - "uri": - "https://itunes.apple.com/gb/album/arrival-to-earth/265018176" - "?i=265018693&mttnagencyid=s2n&mttnsiteid=125115&mttn3pid" - "=Apple-Shazam&mttnsub1=Shazam_ios&mttnsub2=5348615A-616D" - "-3235-3830-44754D6D5973&itscg=30201&app=itunes&itsct=Shazam_ios" + "uri": "https://itunes.apple.com/gb/album/arrival-to-earth/265018176" + "?i=265018693&mttnagencyid=s2n&mttnsiteid=125115&mttn3pid" + "=Apple-Shazam&mttnsub1=Shazam_ios&mttnsub2=5348615A-616D" + "-3235-3830-44754D6D5973&itscg=30201&app=itunes&itsct=Shazam_ios", } ], - "beacondata": { - "type": "buy", - "providername": "itunes" - }, + "beacondata": {"type": "buy", "providername": "itunes"}, "image": "https://images.shazam.com/static/icons/hub/ios/v5/itunes" - "-overflow-buy_{scalefactor}.png", + "-overflow-buy_{scalefactor}.png", "type": "buy", "listcaption": "Buy on iTunes", - "overflowimage": - "https://images.shazam.com/static/icons/hub/ios/v5/itunes-overflow" - "-buy_{scalefactor}.png", + "overflowimage": "https://images.shazam.com/static/icons/hub/ios/v5/itunes-overflow" + "-buy_{scalefactor}.png", "colouroverflowimage": False, - "providername": "itunes" - } + "providername": "itunes", + }, ], "providers": [ { "caption": "Open in Spotify", "images": { - "overflow": - "https://images.shazam.com/static/icons/hub/ios/v5/spotify" - "-overflow_{scalefactor}.png", - "default": - "https://images.shazam.com/static/icons/hub/ios/v5/spotify_{" - "scalefactor}.png" + "overflow": "https://images.shazam.com/static/icons/hub/ios/v5/spotify" + "-overflow_{scalefactor}.png", + "default": "https://images.shazam.com/static/icons/hub/ios/v5/spotify_{" + "scalefactor}.png", }, "actions": [ { "name": "hub:spotify:searchdeeplink", "type": "uri", - "uri": "spotify:search:Arrival%20To%20Earth%20Steve%20Jablonsky" + "uri": "spotify:search:Arrival%20To%20Earth%20Steve%20Jablonsky", } ], - "type": "SPOTIFY" + "type": "SPOTIFY", }, { "caption": "Open in Deezer", "images": { - "overflow": - "https://images.shazam.com/static/icons/hub/ios/v5/deezer" - "-overflow_{scalefactor}.png", - "default": - "https://images.shazam.com/static/icons/hub/ios/v5/deezer_{" - "scalefactor}.png" + "overflow": "https://images.shazam.com/static/icons/hub/ios/v5/deezer" + "-overflow_{scalefactor}.png", + "default": "https://images.shazam.com/static/icons/hub/ios/v5/deezer_{" + "scalefactor}.png", }, "actions": [ { "name": "hub:deezer:searchdeeplink", "type": "uri", - "uri": - "deezer-query://www.deezer.com/play?query=%7Btrack%3A" - "%27Arrival+To+Earth%27%20artist%3A%27Steve+Jablonsky%27%7D " + "uri": "deezer-query://www.deezer.com/play?query=%7Btrack%3A" + "%27Arrival+To+Earth%27%20artist%3A%27Steve+Jablonsky%27%7D ", } ], - "type": "DEEZER" - } + "type": "DEEZER", + }, ], "explicit": False, - "displayname": "APPLE MUSIC" + "displayname": "APPLE MUSIC", }, "sections": [ { @@ -182,85 +158,63 @@ def song_response(): "metapages": [ { "image": "https://is3-ssl.mzstatic.com/image/thumb/Features125/v4/c2" - "/35/67/c23567d0-0f59-8573-5848-0a7844ed3416/mzl.fxlsnewc" - ".jpg/800x800cc.jpg", - "caption": "Steve Jablonsky" + "/35/67/c23567d0-0f59-8573-5848-0a7844ed3416/mzl.fxlsnewc" + ".jpg/800x800cc.jpg", + "caption": "Steve Jablonsky", }, { "image": "https://is2-ssl.mzstatic.com/image/thumb/Music/c8/a6/4a/mzi" - ".akybahch.jpg/400x400cc.jpg", - "caption": "Arrival To Earth" - } + ".akybahch.jpg/400x400cc.jpg", + "caption": "Arrival To Earth", + }, ], "tabname": "Song", "metadata": [ - { - "title": "Album", - "text": "Transformers: The Score" - }, - { - "title": "Label", - "text": "Warner Records" - }, - { - "title": "Released", - "text": "2007" - } - ] + {"title": "Album", "text": "Transformers: The Score"}, + {"title": "Label", "text": "Warner Records"}, + {"title": "Released", "text": "2007"}, + ], }, { "type": "VIDEO", "tabname": "Video", "youtubeurl": "https://cdn.shazam.com/video/v3/-/GB/iphone/47440537/youtube" - "/video?q=Steve+Jablonsky+%22Arrival+To+Earth%22" + "/video?q=Steve+Jablonsky+%22Arrival+To+Earth%22", }, { "type": "ARTIST", "avatar": "https://is3-ssl.mzstatic.com/image/thumb/Features125/v4/c2/35/67" - "/c23567d0-0f59-8573-5848-0a7844ed3416/mzl.fxlsnewc.jpg/800x800cc" - ".jpg", + "/c23567d0-0f59-8573-5848-0a7844ed3416/mzl.fxlsnewc.jpg/800x800cc" + ".jpg", "id": "10194644", "name": "Steve Jablonsky", "verified": False, "url": "https://cdn.shazam.com/digest/v1/en/GB/iphone/artist/10194644" - "/recentpost", + "/recentpost", "actions": [ - { - "type": "artistposts", - "id": "10194644" - }, - { - "type": "artist", - "id": "10194644" - } + {"type": "artistposts", "id": "10194644"}, + {"type": "artist", "id": "10194644"}, ], "tabname": "Artist", "toptracks": { "url": "https://cdn.shazam.com/shazam/v3/en/GB/iphone/-/tracks" - "/artisttoptracks_10194644?startFrom=0&pageSize=20&connected=" - } + "/artisttoptracks_10194644?startFrom=0&pageSize=20&connected=" + }, }, { "type": "RELATED", "url": "https://cdn.shazam.com/shazam/v3/en/GB/iphone/-/tracks/track" - "-similarities-id-47440537?startFrom=0&pageSize=20&connected=", - "tabname": "Related" - } + "-similarities-id-47440537?startFrom=0&pageSize=20&connected=", + "tabname": "Related", + }, ], "url": "https://www.shazam.com/track/47440537/arrival-to-earth", - "artists": [ - { - "id": "10194644", - "adamid": "21402948" - } - ], + "artists": [{"id": "10194644", "adamid": "21402948"}], "isrc": "USWB10703613", - "genres": { - "primary": "Soundtrack" - }, + "genres": {"primary": "Soundtrack"}, "urlparams": { "{tracktitle}": "Arrival+To+Earth", - "{trackartist}": "Steve+Jablonsky" + "{trackartist}": "Steve+Jablonsky", }, "myshazam": { "apple": { @@ -269,36 +223,33 @@ def song_response(): "name": "myshazam:apple", "type": "uri", "uri": "https://music.apple.com/gb/album/arrival-to-earth/265018176?i" - "=265018693&mttnagencyid=s2n&mttnsiteid=125115&mttn3pid=Apple" - "-Shazam&mttnsub1=Shazam_ios&mttnsub2=5348615A-616D-3235-3830" - "-44754D6D5973&itscg=30201&app=music&itsct=Shazam_ios" + "=265018693&mttnagencyid=s2n&mttnsiteid=125115&mttn3pid=Apple" + "-Shazam&mttnsub1=Shazam_ios&mttnsub2=5348615A-616D-3235-3830" + "-44754D6D5973&itscg=30201&app=music&itsct=Shazam_ios", } ] } }, "highlightsurls": { - "artisthighlightsurl": - "https://cdn.shazam.com/video/v3/en/GB/iphone/21402948/highlights?affiliate" - "=mttnagencyid%3Ds2n%26mttnsiteid%3D125115%26mttn3pid%3DApple-Shazam" - "%26mttnsub1%3DShazam_ios%26mttnsub2%3D5348615A-616D-3235-3830-44754D6D5973" - "%26itscg%3D30201%26app%3Dmusic%26itsct%3DShazam_ios", - "relatedhighlightsurl": - "https://cdn.shazam.com/video/v3/en/GB/iphone/10194644/artist-similarities-id" - "-10194644/relatedhighlights?max_artists=5&affiliate=mttnagencyid%3Ds2n" - "%26mttnsiteid%3D125115%26mttn3pid%3DApple-Shazam%26mttnsub1%3DShazam_ios" - "%26mttnsub2%3D5348615A-616D-3235-3830-44754D6D5973%26itscg%3D30201%26app" - "%3Dmusic%26itsct%3DShazam_ios" + "artisthighlightsurl": "https://cdn.shazam.com/video/v3/en/GB/iphone/21402948/highlights?affiliate" + "=mttnagencyid%3Ds2n%26mttnsiteid%3D125115%26mttn3pid%3DApple-Shazam" + "%26mttnsub1%3DShazam_ios%26mttnsub2%3D5348615A-616D-3235-3830-44754D6D5973" + "%26itscg%3D30201%26app%3Dmusic%26itsct%3DShazam_ios", + "relatedhighlightsurl": "https://cdn.shazam.com/video/v3/en/GB/iphone/10194644/artist-similarities-id" + "-10194644/relatedhighlights?max_artists=5&affiliate=mttnagencyid%3Ds2n" + "%26mttnsiteid%3D125115%26mttn3pid%3DApple-Shazam%26mttnsub1%3DShazam_ios" + "%26mttnsub2%3D5348615A-616D-3235-3830-44754D6D5973%26itscg%3D30201%26app" + "%3Dmusic%26itsct%3DShazam_ios", }, "relatedtracksurl": "https://cdn.shazam.com/shazam/v3/en/GB/iphone/-/tracks/track" - "-similarities-id-47440537?startFrom=0&pageSize=20&connected=", - "albumadamid": "265018176" + "-similarities-id-47440537?startFrom=0&pageSize=20&connected=", + "albumadamid": "265018176", }, - "tagid": "89A4C33B-58C6-4A50-8475-94032FC34D06" + "tagid": "89A4C33B-58C6-4A50-8475-94032FC34D06", } yield response -@pytest.mark.asyncio(scope="session") async def test_recognize_song_bug(song_response: bytes): serialize_out = Serialize.full_track(data=song_response) assert serialize_out.matches[0].channel is None diff --git a/tests/test_recognize.py b/tests/test_recognize.py index f22d3bf..4813112 100644 --- a/tests/test_recognize.py +++ b/tests/test_recognize.py @@ -1,27 +1,25 @@ -import pytest +import pytest_asyncio from shazamio import Shazam from shazamio.utils import get_file_bytes -@pytest.fixture +@pytest_asyncio.fixture(scope="session") async def song_bytes(): yield await get_file_bytes(file="examples/data/dora.ogg") -@pytest.mark.asyncio(scope="session") async def test_recognize_song_file(): shazam = Shazam() - out = await shazam.recognize_song(data='examples/data/dora.ogg') + out = await shazam.recognize_song(data="examples/data/dora.ogg") assert out.get("matches") != [] - assert out['track']['key'] == "549679333" + assert out["track"]["key"] == "549679333" -@pytest.mark.asyncio(scope="session") async def test_recognize_song_bytes(song_bytes: bytes): shazam = Shazam() out = await shazam.recognize_song(data=song_bytes) assert out.get("matches") != [] - assert out['track']['key'] == "549679333" + assert out["track"]["key"] == "549679333" From c18445f31f8d292ae987ac67abbf40db7b12c91f Mon Sep 17 00:00:00 2001 From: dotX12 Date: Thu, 23 Jun 2022 11:34:47 +0300 Subject: [PATCH 09/20] update tests + update min python version to 3.8 --- .github/workflows/pytest.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index af3f8c2..c0dd6a5 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -12,10 +12,10 @@ jobs: with: fetch-depth: 1 - - name: Set up Python 3.7 + - name: Set up Python 3.8 uses: actions/setup-python@v1 with: - python-version: 3.7 + python-version: 3.8 - name: Install Poetry uses: dschep/install-poetry-action@v1.2 From d7205d92cbe5afb70e40186e57f39db11c55bc43 Mon Sep 17 00:00:00 2001 From: dotX12 Date: Thu, 23 Jun 2022 11:39:27 +0300 Subject: [PATCH 10/20] fix actions/checkout@v3 --- .github/workflows/pytest.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index c0dd6a5..e690f2d 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -8,12 +8,12 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 with: fetch-depth: 1 - name: Set up Python 3.8 - uses: actions/setup-python@v1 + uses: actions/setup-python@v3 with: python-version: 3.8 @@ -21,7 +21,7 @@ jobs: uses: dschep/install-poetry-action@v1.2 - name: Cache Poetry virtualenv - uses: actions/cache@v1 + uses: actions/cache@v3 id: cache with: path: ~/.virtualenvs From ba8afc9a914f93a26bd09887b1708acfc467ccee Mon Sep 17 00:00:00 2001 From: dotX12 Date: Thu, 23 Jun 2022 11:41:09 +0300 Subject: [PATCH 11/20] fix actions/checkout@v3 v2 --- .github/workflows/pytest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index e690f2d..a6e0c54 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -24,7 +24,7 @@ jobs: uses: actions/cache@v3 id: cache with: - path: ~/.virtualenvs + path: ~/.local/share/virtualenvs key: poetry-${{ hashFiles('**/poetry.lock') }} restore-keys: | poetry-${{ hashFiles('**/poetry.lock') }} From a6b93354f3446c3a869e94735b2a43e5904509fd Mon Sep 17 00:00:00 2001 From: dotX12 Date: Thu, 23 Jun 2022 11:45:30 +0300 Subject: [PATCH 12/20] fix test v3 --- .github/workflows/pytest.yml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index a6e0c54..cc6e749 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -18,25 +18,25 @@ jobs: python-version: 3.8 - name: Install Poetry - uses: dschep/install-poetry-action@v1.2 + uses: snok/install-poetry@v1 + with: + virtualenvs-create: true + virtualenvs-in-project: true + installer-parallel: true - name: Cache Poetry virtualenv uses: actions/cache@v3 id: cache with: - path: ~/.local/share/virtualenvs - key: poetry-${{ hashFiles('**/poetry.lock') }} - restore-keys: | - poetry-${{ hashFiles('**/poetry.lock') }} - - - name: Set Poetry config - run: | - poetry config settings.virtualenvs.in-project false - poetry config settings.virtualenvs.path ~/.virtualenvs + path: .venv + key: venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }} - name: Install Dependencies - run: poetry install - if: steps.cache.outputs.cache-hit != 'true' + if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' + run: poetry install --no-interaction --no-root + + - name: Install library + run: poetry install --no-interaction - name: Code Quality run: poetry run black . --check From da7c60adaf406aa2e2af568a002aee0e972e5bda Mon Sep 17 00:00:00 2001 From: dotX12 Date: Thu, 23 Jun 2022 11:46:28 +0300 Subject: [PATCH 13/20] fix test v4 --- .github/workflows/pytest.yml | 60 +++++++++++++++++++++--------------- 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index cc6e749..0869cc0 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -1,22 +1,24 @@ -name: CI +name: test -on: [push] +on: pull_request jobs: - test: runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 1 - - - name: Set up Python 3.8 - uses: actions/setup-python@v3 + #---------------------------------------------- + # check-out repo and set-up python + #---------------------------------------------- + - name: Check out repository + uses: actions/checkout@v2 + - name: Set up python + id: setup-python + uses: actions/setup-python@v2 with: python-version: 3.8 - + #---------------------------------------------- + # ----- install & configure poetry ----- + #---------------------------------------------- - name: Install Poetry uses: snok/install-poetry@v1 with: @@ -24,22 +26,30 @@ jobs: virtualenvs-in-project: true installer-parallel: true - - name: Cache Poetry virtualenv - uses: actions/cache@v3 - id: cache + #---------------------------------------------- + # load cached venv if cache exists + #---------------------------------------------- + - name: Load cached venv + id: cached-poetry-dependencies + uses: actions/cache@v2 with: path: .venv key: venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }} - - - name: Install Dependencies + #---------------------------------------------- + # install dependencies if cache does not exist + #---------------------------------------------- + - name: Install dependencies if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' run: poetry install --no-interaction --no-root - - - name: Install library - run: poetry install --no-interaction - - - name: Code Quality - run: poetry run black . --check - - - name: Test with pytest - run: poetry run pytest \ No newline at end of file + #---------------------------------------------- + # install your root project, if required + #---------------------------------------------- + - name: Install library + run: poetry install --no-interaction + #---------------------------------------------- + # run test suite + #---------------------------------------------- + - name: Run tests + run: | + source .venv/bin/activate + pytest tests/ \ No newline at end of file From a3fdbe54ef38a1afbef7d15580f2610d446c25ca Mon Sep 17 00:00:00 2001 From: dotX12 Date: Thu, 23 Jun 2022 11:50:52 +0300 Subject: [PATCH 14/20] added ffmpeg --- .github/workflows/pytest.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 0869cc0..84bcba8 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -38,6 +38,10 @@ jobs: #---------------------------------------------- # install dependencies if cache does not exist #---------------------------------------------- + - name: Install ffmpeg + id: setup-ffmpeg + uses: FedericoCarboni/setup-ffmpeg@v1 + - name: Install dependencies if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' run: poetry install --no-interaction --no-root From ecd04c0f513b8591f40466adfd0c27847b3de309 Mon Sep 17 00:00:00 2001 From: dotX12 Date: Thu, 23 Jun 2022 12:11:47 +0300 Subject: [PATCH 15/20] Fix SEARCH_ARTIST (New API Method) Added black. Micro fixes. Added tests. --- examples/about_artist.py | 5 +- examples/about_track.py | 5 +- examples/recognize_song.py | 8 +- examples/recognize_track_youtube.py | 4 +- examples/related_tracks.py | 1 + examples/search_artists.py | 9 +- examples/search_tracks.py | 3 +- examples/song_listening_counter.py | 1 + examples/top_artist_tracks.py | 11 +- examples/top_tracks_city.py | 11 +- examples/top_tracks_country.py | 10 +- examples/top_tracks_genre_country.py | 7 +- examples/top_tracks_genre_world.py | 10 +- examples/top_world_tracks.py | 7 +- shazamio/__init__.py | 7 +- shazamio/algorithm.py | 165 +++++++++++++++------------ shazamio/api.py | 102 ++++++----------- shazamio/client.py | 7 +- shazamio/converter.py | 41 ++++--- shazamio/factory.py | 24 ++-- shazamio/factory_misc.py | 30 ++--- shazamio/misc.py | 71 +++++++----- shazamio/models.py | 40 ++++--- shazamio/serializers.py | 5 +- shazamio/signature.py | 127 +++++++++++++-------- shazamio/typehints.py | 3 +- shazamio/user_agent.py | 2 +- shazamio/utils.py | 7 +- 28 files changed, 398 insertions(+), 325 deletions(-) diff --git a/examples/about_artist.py b/examples/about_artist.py index 1e42790..68a3432 100644 --- a/examples/about_artist.py +++ b/examples/about_artist.py @@ -1,15 +1,16 @@ import asyncio -from shazamio import Shazam, serialize_artist +from shazamio import Shazam, Serialize async def main(): shazam = Shazam() artist_id = 43328183 about_artist = await shazam.artist_about(artist_id) - serialized = serialize_artist(about_artist) + serialized = Serialize.artist(about_artist) print(about_artist) # dict print(serialized) # serialized from dataclass factory + loop = asyncio.get_event_loop() loop.run_until_complete(main()) diff --git a/examples/about_track.py b/examples/about_track.py index c41773c..ba44cec 100644 --- a/examples/about_track.py +++ b/examples/about_track.py @@ -1,15 +1,16 @@ import asyncio -from shazamio import Shazam, serialize_track +from shazamio import Shazam, Serialize async def main(): shazam = Shazam() track_id = 552406075 about_track = await shazam.track_about(track_id=track_id) - serialized = serialize_track(data=about_track) + serialized = Serialize.track(data=about_track) print(about_track) # dict print(serialized) # serialized from dataclass factory + loop = asyncio.get_event_loop() loop.run_until_complete(main()) diff --git a/examples/recognize_song.py b/examples/recognize_song.py index d5949ba..355f489 100644 --- a/examples/recognize_song.py +++ b/examples/recognize_song.py @@ -1,11 +1,15 @@ import asyncio -from shazamio import Shazam +from shazamio import Shazam, Serialize async def main(): shazam = Shazam() - out = await shazam.recognize_song('data/dora.ogg') + out = await shazam.recognize_song("data/dora.ogg") print(out) + serialized = Serialize.full_track(out) + print(serialized) + + loop = asyncio.get_event_loop() loop.run_until_complete(main()) diff --git a/examples/recognize_track_youtube.py b/examples/recognize_track_youtube.py index 65cd1ea..72bd8ef 100644 --- a/examples/recognize_track_youtube.py +++ b/examples/recognize_track_youtube.py @@ -5,12 +5,12 @@ async def main(): shazam = Shazam() - out = await shazam.recognize_song('data/dora.ogg') + out = await shazam.recognize_song("data/dora.ogg") result = Serialize.full_track(data=out) youtube_data = await shazam.get_youtube_data(link=result.track.youtube_link) serialized_youtube = Serialize.youtube(data=youtube_data) print(serialized_youtube.uri) + loop = asyncio.get_event_loop() loop.run_until_complete(main()) - diff --git a/examples/related_tracks.py b/examples/related_tracks.py index 5c69edf..f3835ae 100644 --- a/examples/related_tracks.py +++ b/examples/related_tracks.py @@ -9,5 +9,6 @@ async def main(): # ONLY №3, №4 SONG print(related) + loop = asyncio.get_event_loop() loop.run_until_complete(main()) diff --git a/examples/search_artists.py b/examples/search_artists.py index 6eab3da..ffdb0a2 100644 --- a/examples/search_artists.py +++ b/examples/search_artists.py @@ -1,13 +1,14 @@ import asyncio -from shazamio import Shazam, serialize_artist +from shazamio import Shazam, Serialize async def main(): shazam = Shazam() - artists = await shazam.search_artist(query='Lil', limit=5) - for artist in artists['artists']['hits']: - serialized = serialize_artist(data=artist) + artists = await shazam.search_artist(query="LIL", limit=5) + for artist in artists["artists"]["hits"]: + serialized = Serialize.artist(data=artist) print(serialized) + loop = asyncio.get_event_loop() loop.run_until_complete(main()) diff --git a/examples/search_tracks.py b/examples/search_tracks.py index 91e6105..984748e 100644 --- a/examples/search_tracks.py +++ b/examples/search_tracks.py @@ -4,8 +4,9 @@ async def main(): shazam = Shazam() - tracks = await shazam.search_track(query='Lil', limit=5) + tracks = await shazam.search_track(query="Lil", limit=5) print(tracks) + loop = asyncio.get_event_loop() loop.run_until_complete(main()) diff --git a/examples/song_listening_counter.py b/examples/song_listening_counter.py index e308408..2637940 100644 --- a/examples/song_listening_counter.py +++ b/examples/song_listening_counter.py @@ -10,5 +10,6 @@ async def main(): count = await shazam.listening_counter(track_id=track_id) print(count) + loop = asyncio.get_event_loop() loop.run_until_complete(main()) diff --git a/examples/top_artist_tracks.py b/examples/top_artist_tracks.py index 0f3b9be..4a4d143 100644 --- a/examples/top_artist_tracks.py +++ b/examples/top_artist_tracks.py @@ -1,14 +1,17 @@ import asyncio -from shazamio import Shazam, serialize_track +from shazamio import Shazam, Serialize async def main(): shazam = Shazam() artist_id = 201896832 - top_three_artist_tracks = await shazam.artist_top_tracks(artist_id=artist_id, limit=3) - for track in top_three_artist_tracks['tracks']: - serialized_track = serialize_track(data=track) + top_three_artist_tracks = await shazam.artist_top_tracks( + artist_id=artist_id, limit=3 + ) + for track in top_three_artist_tracks["tracks"]: + serialized_track = Serialize.track(data=track) print(serialized_track) + loop = asyncio.get_event_loop() loop.run_until_complete(main()) diff --git a/examples/top_tracks_city.py b/examples/top_tracks_city.py index 78e75e2..ac8ab00 100644 --- a/examples/top_tracks_city.py +++ b/examples/top_tracks_city.py @@ -1,16 +1,19 @@ import asyncio -from shazamio import Shazam, serialize_track +from shazamio import Shazam, Serialize async def main(): shazam = Shazam() - top_ten_moscow_tracks = await shazam.top_city_tracks(country_code='RU', city_name='Moscow', limit=10) + top_ten_moscow_tracks = await shazam.top_city_tracks( + country_code="RU", city_name="Moscow", limit=10 + ) print(top_ten_moscow_tracks) # ALL TRACKS DICT - for track in top_ten_moscow_tracks['tracks']: - serialized = serialize_track(data=track) + for track in top_ten_moscow_tracks["tracks"]: + serialized = Serialize.track(data=track) # SERIALIZE FROM DATACLASS FACTORY print(serialized) + loop = asyncio.get_event_loop() loop.run_until_complete(main()) diff --git a/examples/top_tracks_country.py b/examples/top_tracks_country.py index 6b5e820..411bcdc 100644 --- a/examples/top_tracks_country.py +++ b/examples/top_tracks_country.py @@ -1,14 +1,14 @@ import asyncio -from shazamio import Shazam, serialize_track +from shazamio import Shazam, Serialize async def main(): shazam = Shazam() - top_five_track_from_amsterdam = await shazam.top_country_tracks('NL', 5) - for track in top_five_track_from_amsterdam['tracks']: - serialized = serialize_track(data=track) + top_five_track_from_amsterdam = await shazam.top_country_tracks("NL", 5) + for track in top_five_track_from_amsterdam["tracks"]: + serialized = Serialize.track(data=track) print(serialized) + loop = asyncio.get_event_loop() loop.run_until_complete(main()) - diff --git a/examples/top_tracks_genre_country.py b/examples/top_tracks_genre_country.py index 93a42ed..7f8b83f 100644 --- a/examples/top_tracks_genre_country.py +++ b/examples/top_tracks_genre_country.py @@ -4,10 +4,11 @@ async def main(): shazam = Shazam() - top_spain_rap = await shazam.top_country_genre_tracks(country_code='ES', - genre=GenreMusic.HIP_HOP_RAP, - limit=4) + top_spain_rap = await shazam.top_country_genre_tracks( + country_code="ES", genre=GenreMusic.HIP_HOP_RAP, limit=4 + ) print(top_spain_rap) + loop = asyncio.get_event_loop() loop.run_until_complete(main()) diff --git a/examples/top_tracks_genre_world.py b/examples/top_tracks_genre_world.py index 41f41fc..b0a2080 100644 --- a/examples/top_tracks_genre_world.py +++ b/examples/top_tracks_genre_world.py @@ -1,13 +1,15 @@ import asyncio -from shazamio import Shazam, serialize_track, GenreMusic +from shazamio import Shazam, Serialize, GenreMusic async def main(): shazam = Shazam() - top_rock_in_the_world = await shazam.top_world_genre_tracks(genre=GenreMusic.ROCK, limit=10) + top_rock_in_the_world = await shazam.top_world_genre_tracks( + genre=GenreMusic.ROCK, limit=10 + ) - for track in top_rock_in_the_world['tracks']: - serialized_track = serialize_track(data=track) + for track in top_rock_in_the_world["tracks"]: + serialized_track = Serialize.track(data=track) print(serialized_track) diff --git a/examples/top_world_tracks.py b/examples/top_world_tracks.py index 0a0f361..0dd8695 100644 --- a/examples/top_world_tracks.py +++ b/examples/top_world_tracks.py @@ -1,14 +1,15 @@ import asyncio -from shazamio import Shazam, serialize_track +from shazamio import Shazam, Serialize async def main(): shazam = Shazam() top_world_tracks = await shazam.top_world_tracks(limit=10) print(top_world_tracks) - for track in top_world_tracks['tracks']: - serialized = serialize_track(track) + for track in top_world_tracks["tracks"]: + serialized = Serialize.track(track) print(serialized) + loop = asyncio.get_event_loop() loop.run_until_complete(main()) diff --git a/shazamio/__init__.py b/shazamio/__init__.py index fed8153..1d6b6aa 100644 --- a/shazamio/__init__.py +++ b/shazamio/__init__.py @@ -3,9 +3,4 @@ from .converter import Geo from .enums import GenreMusic -__all__ = ( - 'Serialize', - 'Shazam', - 'Geo', - 'GenreMusic' -) +__all__ = ("Serialize", "Shazam", "Geo", "GenreMusic") diff --git a/shazamio/algorithm.py b/shazamio/algorithm.py index 9821d91..c7c4389 100644 --- a/shazamio/algorithm.py +++ b/shazamio/algorithm.py @@ -10,7 +10,6 @@ class RingBuffer(list): - def __init__(self, buffer_size: int, default_value: Any = None): if default_value is not None: @@ -32,7 +31,6 @@ def append(self, value: Any): class SignatureGenerator: - def __init__(self): # Used when storing input that will be processed when requiring to @@ -47,22 +45,19 @@ def __init__(self): # Used when processing input: self.ring_buffer_of_samples: RingBuffer[int] = RingBuffer( - buffer_size=2048, - default_value=0 - ) + buffer_size=2048, default_value=0 + ) self.fft_outputs: RingBuffer[List[float]] = RingBuffer( - buffer_size=256, - default_value=[0. * 1025] - ) + buffer_size=256, default_value=[0.0 * 1025] + ) # Lists of 1025 floats, premultiplied with a Hanning function before being # passed through FFT, computed from # the ring buffer every new 128 samples self.spread_fft_output: RingBuffer[List[float]] = RingBuffer( - buffer_size=256, - default_value=[0] * 1025 - ) + buffer_size=256, default_value=[0] * 1025 + ) # How much data to send to Shazam at once? @@ -99,17 +94,20 @@ def feed_input(self, s16le_mono_samples: List[int]): def get_next_signature(self) -> Optional[DecodedMessage]: if len(self.input_pending_processing) - self.samples_processed < 128: return None - while (len(self.input_pending_processing) - self.samples_processed >= 128 and - (self.next_signature.number_samples / self.next_signature.sample_rate_hz < - self.MAX_TIME_SECONDS or sum( - len(peaks) for peaks in - self.next_signature.frequency_band_to_sound_peaks.values() - ) - < self.MAX_PEAKS)): + while len(self.input_pending_processing) - self.samples_processed >= 128 and ( + self.next_signature.number_samples / self.next_signature.sample_rate_hz + < self.MAX_TIME_SECONDS + or sum( + len(peaks) + for peaks in self.next_signature.frequency_band_to_sound_peaks.values() + ) + < self.MAX_PEAKS + ): self.process_input( - self.input_pending_processing - [self.samples_processed:self.samples_processed + 128] - ) + self.input_pending_processing[ + self.samples_processed : self.samples_processed + 128 + ] + ) self.samples_processed += 128 returned_signature = self.next_signature @@ -120,37 +118,37 @@ def get_next_signature(self) -> Optional[DecodedMessage]: self.next_signature.frequency_band_to_sound_peaks = {} self.ring_buffer_of_samples: RingBuffer[int] = RingBuffer( - buffer_size=2048, - default_value=0 - ) + buffer_size=2048, default_value=0 + ) self.fft_outputs: RingBuffer[List[float]] = RingBuffer( - buffer_size=256, - default_value=[0. * 1025] - ) + buffer_size=256, default_value=[0.0 * 1025] + ) self.spread_fft_output: RingBuffer[List[float]] = RingBuffer( - buffer_size=256, - default_value=[0] * 1025 - ) + buffer_size=256, default_value=[0] * 1025 + ) return returned_signature def process_input(self, s16le_mono_samples: List[int]): self.next_signature.number_samples += len(s16le_mono_samples) for position_of_chunk in range(0, len(s16le_mono_samples), 128): - self.do_fft(s16le_mono_samples[position_of_chunk:position_of_chunk + 128]) + self.do_fft(s16le_mono_samples[position_of_chunk : position_of_chunk + 128]) self.do_peak_spreading_and_recognition() def do_fft(self, batch_of_128_s16le_mono_samples): - type_ring = (self.ring_buffer_of_samples.position + len(batch_of_128_s16le_mono_samples)) + type_ring = self.ring_buffer_of_samples.position + len( + batch_of_128_s16le_mono_samples + ) self.ring_buffer_of_samples[ - self.ring_buffer_of_samples.position: type_ring] = batch_of_128_s16le_mono_samples + self.ring_buffer_of_samples.position : type_ring + ] = batch_of_128_s16le_mono_samples self.ring_buffer_of_samples.position += len(batch_of_128_s16le_mono_samples) self.ring_buffer_of_samples.position %= 2048 self.ring_buffer_of_samples.num_written += len(batch_of_128_s16le_mono_samples) excerpt_from_ring_buffer: list = ( - self.ring_buffer_of_samples[self.ring_buffer_of_samples.position:] + - self.ring_buffer_of_samples[:self.ring_buffer_of_samples.position] + self.ring_buffer_of_samples[self.ring_buffer_of_samples.position :] + + self.ring_buffer_of_samples[: self.ring_buffer_of_samples.position] ) # The pre multiplication of the array is for applying a windowing function before the DFT @@ -158,7 +156,7 @@ def do_fft(self, batch_of_128_s16le_mono_samples): fft_results: array = fft.rfft(HANNING_MATRIX * excerpt_from_ring_buffer) - fft_results = (fft_results.real ** 2 + fft_results.imag ** 2) / (1 << 17) + fft_results = (fft_results.real**2 + fft_results.imag**2) / (1 << 17) fft_results = maximum(fft_results, 0.0000000001) self.fft_outputs.append(fft_results) @@ -180,7 +178,9 @@ def do_peak_spreading(self): # Perform frequency-domain spreading of peak values if position < 1023: - spread_last_fft[position] = max(spread_last_fft[position:position + 3]) + spread_last_fft[position] = max( + spread_last_fft[position : position + 3] + ) # Perform time-domain spreading of peak values @@ -188,13 +188,13 @@ def do_peak_spreading(self): for former_fft_num in [-1, -3, -6]: former_fft_output = self.spread_fft_output[ - (self.spread_fft_output.position + former_fft_num) % - self.spread_fft_output.buffer_size] + (self.spread_fft_output.position + former_fft_num) + % self.spread_fft_output.buffer_size + ] former_fft_output[position] = max_value = max( - former_fft_output[position], - max_value - ) + former_fft_output[position], max_value + ) # Save output locally @@ -206,16 +206,18 @@ def do_peak_recognition(self): fft_minus_46 = self.fft_outputs[ (self.fft_outputs.position - 46) % self.fft_outputs.buffer_size - ] + ] fft_minus_49 = self.spread_fft_output[ - (self.spread_fft_output.position - 49) % self.spread_fft_output.buffer_size] + (self.spread_fft_output.position - 49) % self.spread_fft_output.buffer_size + ] for bin_position in range(10, 1015): # Ensure that the bin is large enough to be a peak - if fft_minus_46[bin_position] >= 1 / 64 and (fft_minus_46[bin_position] >= - fft_minus_49[bin_position - 1]): + if fft_minus_46[bin_position] >= 1 / 64 and ( + fft_minus_46[bin_position] >= fft_minus_49[bin_position - 1] + ): # Ensure that it is frequency-domain local minimum @@ -224,7 +226,7 @@ def do_peak_recognition(self): for neighbor_offset in [*range(-10, -3, 3), -3, 1, *range(2, 9, 3)]: max_neighbor_in_fft_minus_49 = max( fft_minus_49[bin_position + neighbor_offset], - max_neighbor_in_fft_minus_49 + max_neighbor_in_fft_minus_49, ) if fft_minus_46[bin_position] > max_neighbor_in_fft_minus_49: @@ -233,12 +235,18 @@ def do_peak_recognition(self): max_neighbor_in_other_adjacent_ffts = max_neighbor_in_fft_minus_49 - for other_offset in [-53, -45, *range(165, 201, 7), *range(214, 250, 7)]: + for other_offset in [ + -53, + -45, + *range(165, 201, 7), + *range(214, 250, 7), + ]: max_neighbor_in_other_adjacent_ffts = max( self.spread_fft_output[ - (self.spread_fft_output.position + other_offset) % - self.spread_fft_output.buffer_size][bin_position - 1], - max_neighbor_in_other_adjacent_ffts + (self.spread_fft_output.position + other_offset) + % self.spread_fft_output.buffer_size + ][bin_position - 1], + max_neighbor_in_other_adjacent_ffts, ) if fft_minus_46[bin_position] > max_neighbor_in_other_adjacent_ffts: @@ -247,26 +255,38 @@ def do_peak_recognition(self): fft_number = self.spread_fft_output.num_written - 46 - peak_magnitude = log( - max(1 / 64, fft_minus_46[bin_position]) - ) * 1477.3 + 6144 - peak_magnitude_before = log( - max(1 / 64, fft_minus_46[bin_position - 1]) - ) * 1477.3 + 6144 - peak_magnitude_after = log( - max(1 / 64, fft_minus_46[bin_position + 1]) - ) * 1477.3 + 6144 + peak_magnitude = ( + log(max(1 / 64, fft_minus_46[bin_position])) * 1477.3 + 6144 + ) + peak_magnitude_before = ( + log(max(1 / 64, fft_minus_46[bin_position - 1])) * 1477.3 + + 6144 + ) + peak_magnitude_after = ( + log(max(1 / 64, fft_minus_46[bin_position + 1])) * 1477.3 + + 6144 + ) - peak_variation_1 = (peak_magnitude * 2 - peak_magnitude_before - - peak_magnitude_after) - peak_variation_2 = (peak_magnitude_after - peak_magnitude_before - ) * 32 / peak_variation_1 + peak_variation_1 = ( + peak_magnitude * 2 + - peak_magnitude_before + - peak_magnitude_after + ) + peak_variation_2 = ( + (peak_magnitude_after - peak_magnitude_before) + * 32 + / peak_variation_1 + ) - corrected_peak_frequency_bin = bin_position * 64 + peak_variation_2 + corrected_peak_frequency_bin = ( + bin_position * 64 + peak_variation_2 + ) assert peak_variation_1 > 0 - frequency_hz = corrected_peak_frequency_bin * (16000 / 2 / 1024 / 64) + frequency_hz = corrected_peak_frequency_bin * ( + 16000 / 2 / 1024 / 64 + ) if 250 < frequency_hz < 520: band = FrequencyBand.hz_250_520 @@ -279,12 +299,17 @@ def do_peak_recognition(self): else: continue - if band not in self.next_signature.frequency_band_to_sound_peaks: + if ( + band + not in self.next_signature.frequency_band_to_sound_peaks + ): self.next_signature.frequency_band_to_sound_peaks[band] = [] self.next_signature.frequency_band_to_sound_peaks[band].append( FrequencyPeak( - fft_number, int(peak_magnitude), - int(corrected_peak_frequency_bin), 16000 - ) + fft_number, + int(peak_magnitude), + int(corrected_peak_frequency_bin), + 16000, + ) ) diff --git a/shazamio/api.py b/shazamio/api.py index 7c41139..e21177f 100644 --- a/shazamio/api.py +++ b/shazamio/api.py @@ -19,9 +19,7 @@ class Shazam(Converter, Geo): asyncio and aiohttp.""" async def top_world_tracks( - self, - limit: int = 200, - start_from: int = 0 + self, limit: int = 200, start_from: int = 0 ) -> Dict[str, Any]: """ Search top world tracks @@ -34,9 +32,9 @@ async def top_world_tracks( :return: dict tracks """ return await self.request( - 'GET', + "GET", ShazamUrl.TOP_TRACKS_WORLD.format(limit, start_from), - headers=Request.HEADERS + headers=Request.HEADERS, ) async def artist_about(self, artist_id: int) -> Dict[str, Any]: @@ -48,16 +46,11 @@ async def artist_about(self, artist_id: int) -> Dict[str, Any]: :return: dict about artist """ return await self.request( - 'GET', - ShazamUrl.ARTIST_ABOUT.format(artist_id), - headers=Request.HEADERS + "GET", ShazamUrl.ARTIST_ABOUT.format(artist_id), headers=Request.HEADERS ) async def artist_top_tracks( - self, - artist_id: int, - limit: int = 200, - start_from: int = 0 + self, artist_id: int, limit: int = 200, start_from: int = 0 ) -> Dict[str, Any]: """ Get the top songs according to Shazam @@ -72,9 +65,9 @@ async def artist_top_tracks( :return: dict tracks """ return await self.request( - 'GET', + "GET", ShazamUrl.ARTIST_TOP_TRACKS.format(artist_id, start_from, limit), - headers=Request.HEADERS + headers=Request.HEADERS, ) async def track_about(self, track_id: int) -> Dict[str, Any]: @@ -86,16 +79,14 @@ async def track_about(self, track_id: int) -> Dict[str, Any]: :return: dict about track """ return await self.request( - 'GET', - ShazamUrl.ABOUT_TRACK.format(track_id), - headers=Request.HEADERS + "GET", ShazamUrl.ABOUT_TRACK.format(track_id), headers=Request.HEADERS ) async def top_country_tracks( self, country_code: Union[CountryCode, str], limit: int = 200, - start_from: int = 0 + start_from: int = 0, ) -> Dict[str, Any]: """ Get the best tracks by country code @@ -110,9 +101,9 @@ async def top_country_tracks( :return: dict songs """ return await self.request( - 'GET', + "GET", ShazamUrl.TOP_TRACKS_COUNTRY.format(country_code, limit, start_from), - headers=Request.HEADERS + headers=Request.HEADERS, ) async def top_city_tracks( @@ -120,7 +111,7 @@ async def top_city_tracks( country_code: Union[CountryCode, str], city_name: str, limit: int = 200, - start_from: int = 0 + start_from: int = 0, ) -> Dict[str, Any]: """ @@ -139,16 +130,13 @@ async def top_city_tracks( """ city_id = await self.city_id_from(country=country_code, city=city_name) return await self.request( - 'GET', + "GET", ShazamUrl.TOP_TRACKS_CITY.format(city_id, limit, start_from), - headers=Request.HEADERS + headers=Request.HEADERS, ) async def top_world_genre_tracks( - self, - genre: Union[GenreMusic, int], - limit: int = 100, - start_from: int = 0 + self, genre: Union[GenreMusic, int], limit: int = 100, start_from: int = 0 ) -> Dict[str, Any]: """ Get world tracks by certain genre @@ -170,9 +158,9 @@ async def top_world_genre_tracks( :return: dict songs """ return await self.request( - 'GET', + "GET", ShazamUrl.GENRE_WORLD.format(genre, limit, start_from), - headers=Request.HEADERS + headers=Request.HEADERS, ) async def top_country_genre_tracks( @@ -180,7 +168,7 @@ async def top_country_genre_tracks( country_code: str, genre: Union[GenreMusic, int], limit: int = 200, - start_from: int = 0 + start_from: int = 0, ) -> Dict[str, Any]: """ The best tracks by a genre in the country @@ -201,16 +189,13 @@ async def top_country_genre_tracks( :return: dict songs """ return await self.request( - 'GET', + "GET", ShazamUrl.GENRE_COUNTRY.format(country_code, genre, limit, start_from), - headers=Request.HEADERS + headers=Request.HEADERS, ) async def related_tracks( - self, - track_id: int, - limit: int = 20, - start_from: int = 0 + self, track_id: int, limit: int = 20, start_from: int = 0 ) -> Dict[str, Any]: """ Similar songs based song id @@ -225,16 +210,12 @@ async def related_tracks( :return: dict tracks """ return await self.request( - 'GET', + "GET", ShazamUrl.RELATED_SONGS.format(track_id, start_from, limit), - headers=Request.HEADERS + headers=Request.HEADERS, ) - async def search_artist( - self, - query: str, - limit: int = 10 - ) -> Dict[str, Any]: + async def search_artist(self, query: str, limit: int = 10) -> Dict[str, Any]: """ Search all artists by prefix or fullname :param query: Artist name or search prefix @@ -243,16 +224,10 @@ async def search_artist( :return: dict artists """ return await self.request( - 'GET', - ShazamUrl.SEARCH_ARTIST.format(query, limit), - headers=Request.HEADERS + "GET", ShazamUrl.SEARCH_ARTIST.format(query, limit), headers=Request.HEADERS ) - async def search_track( - self, - query: str, - limit: int = 10 - ) -> Dict[str, Any]: + async def search_track(self, query: str, limit: int = 10) -> Dict[str, Any]: """ Search all tracks by prefix :param query: Track full title or prefix title @@ -261,9 +236,7 @@ async def search_track( :return: dict songs """ return await self.request( - 'GET', - ShazamUrl.SEARCH_MUSIC.format(query, limit), - headers=Request.HEADERS + "GET", ShazamUrl.SEARCH_MUSIC.format(query, limit), headers=Request.HEADERS ) async def listening_counter(self, track_id: int) -> Dict[str, Any]: @@ -275,19 +248,14 @@ async def listening_counter(self, track_id: int) -> Dict[str, Any]: """ return await self.request( - 'GET', ShazamUrl.LISTENING_COUNTER.format(track_id), - headers=Request.HEADERS + "GET", ShazamUrl.LISTENING_COUNTER.format(track_id), headers=Request.HEADERS ) async def get_youtube_data(self, link: str) -> Dict[str, Any]: - return await self.request( - 'GET', link, - headers=Request.HEADERS - ) + return await self.request("GET", link, headers=Request.HEADERS) async def recognize_song( - self, - data: Union[str, pathlib.Path, bytes, bytearray, AudioSegment] + self, data: Union[str, pathlib.Path, bytes, bytearray, AudioSegment] ) -> Dict[str, Any]: """ Creating a song signature based on a file and searching for this signature in the shazam @@ -310,14 +278,14 @@ async def send_recognize_request(self, sig: DecodedMessage) -> Dict[str, Any]: Request.TIME_ZONE, sig.encode_to_uri(), int(sig.number_samples / sig.sample_rate_hz * 1000), - int(time.time() * 1000) + int(time.time() * 1000), ) return await self.request( - 'POST', + "POST", ShazamUrl.SEARCH_FROM_FILE.format( - str(uuid.uuid4()).upper(), - str(uuid.uuid4()).upper() + str(uuid.uuid4()).upper(), str(uuid.uuid4()).upper() ), - headers=Request.HEADERS, json=data + headers=Request.HEADERS, + json=data, ) diff --git a/shazamio/client.py b/shazamio/client.py index 0319a57..8862942 100644 --- a/shazamio/client.py +++ b/shazamio/client.py @@ -5,15 +5,14 @@ class HTTPClient: - @staticmethod async def request(method: str, url: str, *args, **kwargs) -> dict: async with aiohttp.ClientSession() as session: - if method.upper() == 'GET': + if method.upper() == "GET": async with session.get(url, **kwargs) as resp: return await validate_json(resp, *args) - elif method.upper() == 'POST': + elif method.upper() == "POST": async with session.post(url, **kwargs) as resp: return await validate_json(resp, *args) else: - raise BadMethod('Accept only GET/POST') + raise BadMethod("Accept only GET/POST") diff --git a/shazamio/converter.py b/shazamio/converter.py index a77d7b6..d42f46b 100644 --- a/shazamio/converter.py +++ b/shazamio/converter.py @@ -10,7 +10,6 @@ class Geo(HTTPClient): - async def city_id_from(self, country: Union[CountryCode, str], city: str) -> int: """ Return City ID from country name and city name. @@ -19,31 +18,35 @@ async def city_id_from(self, country: Union[CountryCode, str], city: str) -> int :return: City ID """ - data = await self.request('GET', ShazamUrl.CITY_IDS, 'text/plain') - for response_country in data['countries']: - if country == response_country['id']: - for response_city in response_country['cities']: - if city == response_city['name']: - return response_city['id'] - raise BadCityName('City not found, check city name') + data = await self.request("GET", ShazamUrl.CITY_IDS, "text/plain") + for response_country in data["countries"]: + if country == response_country["id"]: + for response_city in response_country["cities"]: + if city == response_city["name"]: + return response_city["id"] + raise BadCityName("City not found, check city name") async def all_cities_from_country(self, country: Union[CountryCode, str]) -> list: cities = [] - data = await self.request('GET', ShazamUrl.CITY_IDS, 'text/plain') - for response_country in data['countries']: - if country == response_country['id']: - for city in response_country['cities']: - cities.append(city['name']) + data = await self.request("GET", ShazamUrl.CITY_IDS, "text/plain") + for response_country in data["countries"]: + if country == response_country["id"]: + for city in response_country["cities"]: + cities.append(city["name"]) return cities - raise BadCountryName('Country not found, check country name') + raise BadCountryName("Country not found, check country name") class Converter: - @staticmethod def data_search(timezone: str, uri: str, samplems: int, timestamp: int) -> dict: - return {'timezone': timezone, 'signature': {'uri': uri, 'samplems': samplems}, - 'timestamp': timestamp, 'context': {}, 'geolocation': {}} + return { + "timezone": timezone, + "signature": {"uri": uri, "samplems": samplems}, + "timestamp": timestamp, + "context": {}, + "geolocation": {}, + } @staticmethod def normalize_audio_data(audio: AudioSegment) -> AudioSegment: @@ -59,5 +62,7 @@ def create_signature_generator(audio: AudioSegment) -> SignatureGenerator: signature_generator.feed_input(audio.get_array_of_samples()) signature_generator.MAX_TIME_SECONDS = 12 if audio.duration_seconds > 12 * 3: - signature_generator.samples_processed += 16000 * (int(audio.duration_seconds / 2) - 6) + signature_generator.samples_processed += 16000 * ( + int(audio.duration_seconds / 2) - 6 + ) return signature_generator diff --git a/shazamio/factory.py b/shazamio/factory.py index ba6d0cc..f6b05fc 100644 --- a/shazamio/factory.py +++ b/shazamio/factory.py @@ -10,25 +10,29 @@ class FactorySchemas: "apple_music_url": ("hub", "options", 0, "actions", 0, "uri"), "spotify_url": ("hub", "providers", 0, "actions", 0, "uri"), "spotify_uri": ("hub", "providers", 0, "actions", 1, "uri"), - "_sections": "sections" - - }, skip_internal=True) + "_sections": "sections", + }, + skip_internal=True, + ) FACTORY_ARTIST_SCHEMA = Schema( name_mapping={ "avatar": "avatar", "genres": ("genres", "secondaries"), "genres_primary": ("genres", "primary"), - }) + "adam_id": "adamid", + "url": "weburl", + } + ) FACTORY_SONG_SECTION_SCHEMA = Schema( name_mapping={ "type": "type", "meta_pages": "metapages", "tab_name": "tabname", - "metadata": "metadata" + "metadata": "metadata", }, - skip_internal=True + skip_internal=True, ) FACTORY_VIDEO_SECTION_SCHEMA = Schema( @@ -37,7 +41,7 @@ class FactorySchemas: "youtube_url": "youtubeurl", "tab_name": "tabname", }, - skip_internal=True + skip_internal=True, ) FACTORY_RELATED_SECTION_SCHEMA = Schema( @@ -46,7 +50,7 @@ class FactorySchemas: "url": "url", "tab_name": "tabname", }, - skip_internal=True + skip_internal=True, ) FACTORY_YOUTUBE_TRACK_SCHEMA = Schema( @@ -55,7 +59,7 @@ class FactorySchemas: "image": "image", "actions": "actions", }, - skip_internal=True + skip_internal=True, ) FACTORY_RESPONSE_TRACK_SCHEMA = Schema( @@ -68,7 +72,7 @@ class FactorySchemas: "track": "track", "tag_id": "tagid", }, - skip_internal=True + skip_internal=True, ) FACTORY_LYRICS_SECTION = Schema( diff --git a/shazamio/factory_misc.py b/shazamio/factory_misc.py index 5bf31e3..f647364 100644 --- a/shazamio/factory_misc.py +++ b/shazamio/factory_misc.py @@ -15,20 +15,22 @@ from shazamio.models import YoutubeData from shazamio.models import ResponseTrack -FACTORY_TRACK = Factory(schemas={ - TrackInfo: FactorySchemas.FACTORY_TRACK_SCHEMA, - SongSection: FactorySchemas.FACTORY_SONG_SECTION_SCHEMA, - VideoSection: FactorySchemas.FACTORY_VIDEO_SECTION_SCHEMA, - LyricsSection: FactorySchemas.FACTORY_LYRICS_SECTION, - BeaconDataLyricsSection: FactorySchemas.FACTORY_BEACON_DATA_LYRICS_SECTION, - ArtistSection: FactorySchemas.FACTORY_ARTIST_SECTION, - MatchModel: FactorySchemas.FACTORY_MATCH_MODEL, - RelatedSection: FactorySchemas.FACTORY_RELATED_SECTION_SCHEMA, - YoutubeData: FactorySchemas.FACTORY_YOUTUBE_TRACK_SCHEMA, - ResponseTrack: FactorySchemas.FACTORY_RESPONSE_TRACK_SCHEMA, -}, debug_path=True) +FACTORY_TRACK = Factory( + schemas={ + TrackInfo: FactorySchemas.FACTORY_TRACK_SCHEMA, + SongSection: FactorySchemas.FACTORY_SONG_SECTION_SCHEMA, + VideoSection: FactorySchemas.FACTORY_VIDEO_SECTION_SCHEMA, + LyricsSection: FactorySchemas.FACTORY_LYRICS_SECTION, + BeaconDataLyricsSection: FactorySchemas.FACTORY_BEACON_DATA_LYRICS_SECTION, + ArtistSection: FactorySchemas.FACTORY_ARTIST_SECTION, + MatchModel: FactorySchemas.FACTORY_MATCH_MODEL, + RelatedSection: FactorySchemas.FACTORY_RELATED_SECTION_SCHEMA, + YoutubeData: FactorySchemas.FACTORY_YOUTUBE_TRACK_SCHEMA, + ResponseTrack: FactorySchemas.FACTORY_RESPONSE_TRACK_SCHEMA, + }, + debug_path=True, +) FACTORY_ARTIST = Factory( - schemas={ArtistInfo: FactorySchemas.FACTORY_ARTIST_SCHEMA}, - debug_path=True + schemas={ArtistInfo: FactorySchemas.FACTORY_ARTIST_SCHEMA}, debug_path=True ) diff --git a/shazamio/misc.py b/shazamio/misc.py index 2be9f0b..72c0988 100644 --- a/shazamio/misc.py +++ b/shazamio/misc.py @@ -4,51 +4,66 @@ class ShazamUrl: SEARCH_FROM_FILE = ( - 'https://amp.shazam.com/discovery/v5/en/GB/iphone/-/tag/{}/{' - '}?sync=true&webv3=true&sampling=true ' - '&connected=&shazamapiversion=v3&sharehub=true&hubv5minorversion=v5.1&hidelb=true&video=v3') - TOP_TRACKS_WORLD = 'https://www.shazam.com/shazam/v3/en/GB/web/-/tracks/ip-global-chart' \ - '?pageSize={}&startFrom={} ' - ARTIST_ABOUT = 'https://www.shazam.com/discovery/v3/en/GB/web/artist/{' \ - '}?shazamapiversion=v3&video=v3 ' + "https://amp.shazam.com/discovery/v5/en/GB/iphone/-/tag/{}/{" + "}?sync=true&webv3=true&sampling=true " + "&connected=&shazamapiversion=v3&sharehub=true&hubv5minorversion=v5.1&hidelb=true&video=v3" + ) + TOP_TRACKS_WORLD = ( + "https://www.shazam.com/shazam/v3/en/GB/web/-/tracks/ip-global-chart" + "?pageSize={}&startFrom={}" + ) + ARTIST_ABOUT = ( + "https://www.shazam.com/discovery/v3/en/GB/web/artist/{" + "}?shazamapiversion=v3&video=v3 " + ) ARTIST_TOP_TRACKS = ( - 'https://cdn.shazam.com/shazam/v3/en/GB/web/-/tracks/artisttoptracks_{}?startFrom={}' - '&pageSize={}&connected=&channel=') - ABOUT_TRACK = 'https://www.shazam.com/discovery/v5/en/GB/web/-/track/{' \ - '}?shazamapiversion=v3&video=v3 ' + "https://cdn.shazam.com/shazam/v3/en/GB/web/-/tracks/artisttoptracks_{}?startFrom={}" + "&pageSize={}&connected=&channel=" + ) + ABOUT_TRACK = ( + "https://www.shazam.com/discovery/v5/en/GB/web/-/track/{" + "}?shazamapiversion=v3&video=v3 " + ) TOP_TRACKS_COUNTRY = ( - 'https://www.shazam.com/shazam/v3/en/GB/web/-/tracks/ip-country-chart-{}?pageSize={' - '}&startFrom={}') + "https://www.shazam.com/shazam/v3/en/GB/web/-/tracks/ip-country-chart-{}?pageSize={" + "}&startFrom={}" + ) TOP_TRACKS_CITY = ( - 'https://www.shazam.com/shazam/v3/en/GB/web/-/tracks/ip-city-chart-{}?pageSize={' - '}&startFrom={}') - CITY_IDS = 'https://raw.githubusercontent.com/dotX12/dotX12/main/city.json' + "https://www.shazam.com/shazam/v3/en/GB/web/-/tracks/ip-city-chart-{}?pageSize={" + "}&startFrom={}" + ) + CITY_IDS = "https://raw.githubusercontent.com/dotX12/dotX12/main/city.json" GENRE_WORLD = ( "https://www.shazam.com/shazam/v3/en/GB/web/-/tracks/genre-global-chart-{}?pageSize={" - "}&startFrom={}") + "}&startFrom={}" + ) GENRE_COUNTRY = ( - 'https://www.shazam.com/shazam/v3/en/GB/web/-/tracks/genre-country-chart-{}-{}?pageSize={' - '}&startFrom={}') + "https://www.shazam.com/shazam/v3/en/GB/web/-/tracks/genre-country-chart-{}-{}?pageSize={" + "}&startFrom={}" + ) RELATED_SONGS = ( - 'https://cdn.shazam.com/shazam/v3/en/GB/web/-/tracks/track-similarities-id-{}' - '?startFrom={}&pageSize={}&connected=&channel=') + "https://cdn.shazam.com/shazam/v3/en/GB/web/-/tracks/track-similarities-id-{}" + "?startFrom={}&pageSize={}&connected=&channel=" + ) SEARCH_ARTIST = ( - 'https://www.shazam.com/services/search/v3/en/GB/web/search?query={}' - '&numResults={}&offset=0&types=artists') + "https://www.shazam.com/services/search/v4/en-US/RU/web/search?term={}" + "&offset=0&limit={}&types=artists" + ) SEARCH_MUSIC = ( - 'https://www.shazam.com/services/search/v3/en/GB/web/search?query={}' - '&numResults={}&offset=0&types=songs') + "https://www.shazam.com/services/search/v3/en/GB/web/search?query={}" + "&numResults={}&offset=0&types=songs" + ) LISTENING_COUNTER = "https://www.shazam.com/services/count/v2/web/track/{}" class Request: - LANG = 'ru' - TIME_ZONE = 'Europe/Moscow' + LANG = "ru" + TIME_ZONE = "Europe/Moscow" HEADERS = { "X-Shazam-Platform": "IPHONE", "X-Shazam-AppVersion": "14.1.0", "Accept": "*/*", "Accept-Language": LANG, "Accept-Encoding": "gzip, deflate", - "User-Agent": choice(USER_AGENTS) + "User-Agent": choice(USER_AGENTS), } diff --git a/shazamio/models.py b/shazamio/models.py index 2956be0..d2eaa6a 100644 --- a/shazamio/models.py +++ b/shazamio/models.py @@ -14,12 +14,13 @@ @dataclass class ArtistInfo(Factory): name: str - alias: str verified: Optional[bool] genres: Optional[List[str]] = field(default_factory=list) + alias: Optional[str] = None genres_primary: Optional[str] = None avatar: Optional[Union[dict, str]] = None - url: Optional[str] = '' + adam_id: Optional[int] = None + url: Optional[str] = "" def __post_init__(self): self.avatar = self.__optional_avatar() @@ -27,10 +28,15 @@ def __post_init__(self): def __optional_avatar(self): if self.avatar is None: return None - elif 'default' in self.avatar: - return self.avatar.get('default') + elif "default" in self.avatar: + return self.avatar.get("default") else: - return ''.join(self.avatar) + return "".join(self.avatar) + + +@dataclass +class ArtistV2(Factory): + artist: ArtistInfo @dataclass @@ -180,18 +186,20 @@ class TrackInfo(Factory): spotify_url: Optional[str] = field(default=None) spotify_uri: Optional[str] = field(default=None) youtube_link: Optional[str] = None - _sections: Optional[List[Union[ - SongSection, - VideoSection, - RelatedSection, - ArtistSection, - LyricsSection, - ]]] = field( - default_factory=list - ) + _sections: Optional[ + List[ + Union[ + SongSection, + VideoSection, + RelatedSection, + ArtistSection, + LyricsSection, + ] + ] + ] = field(default_factory=list) def __post_init__(self): - self.shazam_url = f'https://www.shazam.com/track/{self.artist_id}' + self.shazam_url = f"https://www.shazam.com/track/{self.artist_id}" self.apple_music_url = self.__apple_music_url() self.spotify_uri_query = self.__short_uri() self.youtube_link = self.__youtube_link() @@ -204,7 +212,7 @@ def __apple_music_url(self): def __short_uri(self): if self.spotify_uri: - return self.spotify_uri.split('spotify:search:')[1] + return self.spotify_uri.split("spotify:search:")[1] def __youtube_link(self): for i in self._sections: diff --git a/shazamio/serializers.py b/shazamio/serializers.py index b23a1b6..c14f629 100644 --- a/shazamio/serializers.py +++ b/shazamio/serializers.py @@ -1,6 +1,9 @@ +from typing import Union + from shazamio.factory_misc import FACTORY_ARTIST from shazamio.factory_misc import FACTORY_TRACK from shazamio.models import ArtistInfo +from shazamio.models import ArtistV2 from shazamio.models import ResponseTrack from shazamio.models import TrackInfo from shazamio.models import YoutubeData @@ -17,7 +20,7 @@ def youtube(cls, data): @classmethod def artist(cls, data): - return FACTORY_ARTIST.load(data, ArtistInfo) + return FACTORY_ARTIST.load(data, Union[ArtistV2, ArtistInfo]) @classmethod def full_track(cls, data): diff --git a/shazamio/signature.py b/shazamio/signature.py index 4f2de86..c2db7b3 100644 --- a/shazamio/signature.py +++ b/shazamio/signature.py @@ -6,29 +6,32 @@ from ctypes import * from .enums import FrequencyBand, SampleRate -DATA_URI_PREFIX = 'data:audio/vnd.shazam.sig;base64,' +DATA_URI_PREFIX = "data:audio/vnd.shazam.sig;base64," class RawSignatureHeader(LittleEndianStructure): _pack = True _fields_ = [ - ('magic1', c_uint32), # Fixed 0xcafe2580 - 80 25 fe ca - ('crc32', c_uint32), # CRC-32 for all following (so excluding these first 8 bytes) - ('size_minus_header', c_uint32), + ("magic1", c_uint32), # Fixed 0xcafe2580 - 80 25 fe ca + ( + "crc32", + c_uint32, + ), # CRC-32 for all following (so excluding these first 8 bytes) + ("size_minus_header", c_uint32), # Total size of the message, minus the size of the current header (which is 48 bytes) - ('magic2', c_uint32), # Fixed 0x94119c00 - 00 9c 11 94 - ('void1', c_uint32 * 3), # Void - ('shifted_sample_rate_id', c_uint32), + ("magic2", c_uint32), # Fixed 0x94119c00 - 00 9c 11 94 + ("void1", c_uint32 * 3), # Void + ("shifted_sample_rate_id", c_uint32), # A member of SampleRate (usually 3 for 16000 Hz), left-shifted by 27 (usually giving # 0x18000000 - 00 00 00 18) - ('void2', c_uint32 * 2), # Void, or maybe used only in "rolling window" mode? - ('number_samples_plus_divided_sample_rate', c_uint32), + ("void2", c_uint32 * 2), # Void, or maybe used only in "rolling window" mode? + ("number_samples_plus_divided_sample_rate", c_uint32), # int(number_of_samples + sample_rate * 0.24) - As the sample rate is known thanks to the # field above, # it can be inferred and subtracted so that we obtain the number of samples, # and from the number of samples and sample rate we can obtain the length of the recording - ('fixed_value', c_uint32) + ("fixed_value", c_uint32) # Calculated as ((15 << 19) + 0x40000) - 0x7c0000 or 00 00 7c 00 - seems pretty constant, # may be different in the "SigType.STREAMING" mode ] @@ -41,8 +44,11 @@ class FrequencyPeak: sample_rate_hz: int = None def __init__( - self, fft_pass_number: int, peak_magnitude: int, corrected_peak_frequency_bin: int, - sample_rate_hz: int + self, + fft_pass_number: int, + peak_magnitude: int, + corrected_peak_frequency_bin: int, + sample_rate_hz: int, ): self.fft_pass_number = fft_pass_number self.peak_magnitude = peak_magnitude @@ -90,12 +96,14 @@ def decode_from_binary(cls, data: bytes): header = RawSignatureHeader() buf.readinto(header) - assert header.magic1 == 0xcafe2580 + assert header.magic1 == 0xCAFE2580 assert header.size_minus_header == len(data) - 48 - assert crc32(check_summable_data) & 0xffffffff == header.crc32 - assert header.magic2 == 0x94119c00 + assert crc32(check_summable_data) & 0xFFFFFFFF == header.crc32 + assert header.magic2 == 0x94119C00 - self.sample_rate_hz = int(SampleRate(header.shifted_sample_rate_id >> 27).name.strip('_')) + self.sample_rate_hz = int( + SampleRate(header.shifted_sample_rate_id >> 27).name.strip("_") + ) self.number_samples = int( header.number_samples_plus_divided_sample_rate - self.sample_rate_hz * 0.24 @@ -105,8 +113,8 @@ def decode_from_binary(cls, data: bytes): # The first chunk is fixed and has no value, but instead just repeats # the length of the message size minus the header: - assert int.from_bytes(buf.read(4), 'little') == 0x40000000 - assert int.from_bytes(buf.read(4), 'little') == len(data) - 48 + assert int.from_bytes(buf.read(4), "little") == 0x40000000 + assert int.from_bytes(buf.read(4), "little") == len(data) - 48 # Then, lists of frequency peaks for respective bands follow @@ -118,8 +126,8 @@ def decode_from_binary(cls, data: bytes): if not tlv_header: break - frequency_band_id = int.from_bytes(tlv_header[:4], 'little') - frequency_peaks_size = int.from_bytes(tlv_header[4:], 'little') + frequency_band_id = int.from_bytes(tlv_header[:4], "little") + frequency_peaks_size = int.from_bytes(tlv_header[4:], "little") frequency_peaks_padding = -frequency_peaks_size % 4 @@ -141,19 +149,25 @@ def decode_from_binary(cls, data: bytes): break fft_pass_offset: int = raw_fft_pass[0] - if fft_pass_offset == 0xff: - fft_pass_number = int.from_bytes(frequency_peaks_buf.read(4), 'little') + if fft_pass_offset == 0xFF: + fft_pass_number = int.from_bytes( + frequency_peaks_buf.read(4), "little" + ) continue else: fft_pass_number += fft_pass_offset - peak_magnitude = int.from_bytes(frequency_peaks_buf.read(2), 'little') - corrected_peak_frequency_bin = int.from_bytes(frequency_peaks_buf.read(2), 'little') + peak_magnitude = int.from_bytes(frequency_peaks_buf.read(2), "little") + corrected_peak_frequency_bin = int.from_bytes( + frequency_peaks_buf.read(2), "little" + ) self.frequency_band_to_sound_peaks[frequency_band].append( FrequencyPeak( - fft_pass_number, peak_magnitude, corrected_peak_frequency_bin, - self.sample_rate_hz + fft_pass_number, + peak_magnitude, + corrected_peak_frequency_bin, + self.sample_rate_hz, ) ) @@ -164,7 +178,7 @@ def decode_from_uri(cls, uri: str): assert uri.startswith(DATA_URI_PREFIX) - return cls.decode_from_binary(b64decode(uri.replace(DATA_URI_PREFIX, '', 1))) + return cls.decode_from_binary(b64decode(uri.replace(DATA_URI_PREFIX, "", 1))) """ Encode the current object to a readable JSON format, for debugging @@ -178,37 +192,42 @@ def encode_to_json(self) -> dict: "number_samples": self.number_samples, "_seconds": self.number_samples / self.sample_rate_hz, "frequency_band_to_peaks": { - frequency_band.name.strip('_'): [ + frequency_band.name.strip("_"): [ { "fft_pass_number": frequency_peak.fft_pass_number, "peak_magnitude": frequency_peak.peak_magnitude, "corrected_peak_frequency_bin": frequency_peak.corrected_peak_frequency_bin, "_frequency_hz": frequency_peak.get_frequency_hz(), "_amplitude_pcm": frequency_peak.get_amplitude_pcm(), - "_seconds": frequency_peak.get_seconds() + "_seconds": frequency_peak.get_seconds(), } for frequency_peak in frequency_peaks ] - for frequency_band, frequency_peaks in - sorted(self.frequency_band_to_sound_peaks.items()) - } + for frequency_band, frequency_peaks in sorted( + self.frequency_band_to_sound_peaks.items() + ) + }, } def encode_to_binary(self) -> bytes: header = RawSignatureHeader() - header.magic1 = 0xcafe2580 - header.magic2 = 0x94119c00 - header.shifted_sample_rate_id = int(getattr(SampleRate, '_%s' % self.sample_rate_hz)) << 27 - header.fixed_value = ((15 << 19) + 0x40000) + header.magic1 = 0xCAFE2580 + header.magic2 = 0x94119C00 + header.shifted_sample_rate_id = ( + int(getattr(SampleRate, "_%s" % self.sample_rate_hz)) << 27 + ) + header.fixed_value = (15 << 19) + 0x40000 header.number_samples_plus_divided_sample_rate = int( self.number_samples + self.sample_rate_hz * 0.24 ) contents_buf = BytesIO() - for frequency_band, frequency_peaks in sorted(self.frequency_band_to_sound_peaks.items()): + for frequency_band, frequency_peaks in sorted( + self.frequency_band_to_sound_peaks.items() + ): peaks_buf = BytesIO() @@ -223,36 +242,44 @@ def encode_to_binary(self) -> bytes: assert frequency_peak.fft_pass_number >= fft_pass_number if frequency_peak.fft_pass_number - fft_pass_number >= 255: - peaks_buf.write(b'\xff') - peaks_buf.write(frequency_peak.fft_pass_number.to_bytes(4, 'little')) + peaks_buf.write(b"\xff") + peaks_buf.write( + frequency_peak.fft_pass_number.to_bytes(4, "little") + ) fft_pass_number = frequency_peak.fft_pass_number - peaks_buf.write(bytes([frequency_peak.fft_pass_number - fft_pass_number])) - peaks_buf.write(frequency_peak.peak_magnitude.to_bytes(2, 'little')) - peaks_buf.write(frequency_peak.corrected_peak_frequency_bin.to_bytes(2, 'little')) + peaks_buf.write( + bytes([frequency_peak.fft_pass_number - fft_pass_number]) + ) + peaks_buf.write(frequency_peak.peak_magnitude.to_bytes(2, "little")) + peaks_buf.write( + frequency_peak.corrected_peak_frequency_bin.to_bytes(2, "little") + ) fft_pass_number = frequency_peak.fft_pass_number - contents_buf.write((0x60030040 + int(frequency_band)).to_bytes(4, 'little')) - contents_buf.write(len(peaks_buf.getvalue()).to_bytes(4, 'little')) + contents_buf.write((0x60030040 + int(frequency_band)).to_bytes(4, "little")) + contents_buf.write(len(peaks_buf.getvalue()).to_bytes(4, "little")) contents_buf.write(peaks_buf.getvalue()) - contents_buf.write(b'\x00' * (-len(peaks_buf.getvalue()) % 4)) + contents_buf.write(b"\x00" * (-len(peaks_buf.getvalue()) % 4)) # Below, write the full message as a binary stream header.size_minus_header = len(contents_buf.getvalue()) + 8 buf = BytesIO() - buf.write(header) # We will rewrite it just after in order to include the final CRC-32 + buf.write( + header + ) # We will rewrite it just after in order to include the final CRC-32 - buf.write((0x40000000).to_bytes(4, 'little')) - buf.write((len(contents_buf.getvalue()) + 8).to_bytes(4, 'little')) + buf.write((0x40000000).to_bytes(4, "little")) + buf.write((len(contents_buf.getvalue()) + 8).to_bytes(4, "little")) buf.write(contents_buf.getvalue()) buf.seek(8) - header.crc32 = crc32(buf.read()) & 0xffffffff + header.crc32 = crc32(buf.read()) & 0xFFFFFFFF buf.seek(0) buf.write(header) @@ -260,4 +287,4 @@ def encode_to_binary(self) -> bytes: def encode_to_uri(self) -> str: - return DATA_URI_PREFIX + b64encode(self.encode_to_binary()).decode('ascii') + return DATA_URI_PREFIX + b64encode(self.encode_to_binary()).decode("ascii") diff --git a/shazamio/typehints.py b/shazamio/typehints.py index fdfb60a..90cc5bc 100644 --- a/shazamio/typehints.py +++ b/shazamio/typehints.py @@ -1,9 +1,10 @@ class CountryCode: """ISO 3166-3 alpha-2 code. Example: RU,NL,UA""" + pass class ShazamResponse: """Dictionary with found data on request""" - pass + pass diff --git a/shazamio/user_agent.py b/shazamio/user_agent.py index a07b588..850ed66 100644 --- a/shazamio/user_agent.py +++ b/shazamio/user_agent.py @@ -98,5 +98,5 @@ "Dalvik/2.1.0 (Linux; U; Android 6.0.1; SM-J700T Build/MMB29K)", "Dalvik/2.1.0 (Linux; U; Android 5.1.1; SM-J500FN Build/LMY48B)", "Dalvik/1.6.0 (Linux; U; Android 4.2.2; SM-T217S Build/JDQ39)", - "Dalvik/1.6.0 (Linux; U; Android 4.4.4; SAMSUNG-SM-N900A Build/KTU84P)" + "Dalvik/1.6.0 (Linux; U; Android 4.4.4; SAMSUNG-SM-N900A Build/KTU84P)", ] diff --git a/shazamio/utils.py b/shazamio/utils.py index ae7ec8a..059f426 100644 --- a/shazamio/utils.py +++ b/shazamio/utils.py @@ -12,7 +12,9 @@ FileT = Union[str, pathlib.Path] -async def validate_json(resp: aiohttp.ClientResponse, content_type: str = 'application/json') -> dict: +async def validate_json( + resp: aiohttp.ClientResponse, content_type: str = "application/json" +) -> dict: try: return await resp.json(content_type=content_type) except ContentTypeError as e: @@ -21,7 +23,7 @@ async def validate_json(resp: aiohttp.ClientResponse, content_type: str = 'appli async def get_file_bytes(file: FileT) -> bytes: - async with aiofiles.open(file, mode='rb') as f: + async with aiofiles.open(file, mode="rb") as f: return await f.read() @@ -36,4 +38,3 @@ async def get_song(data: SongT) -> Union[AudioSegment]: if isinstance(data, AudioSegment): return data - From 5b19677f5e11776a03c859115544f5cbcd46321b Mon Sep 17 00:00:00 2001 From: dotX12 Date: Thu, 23 Jun 2022 12:15:49 +0300 Subject: [PATCH 16/20] added check black --- .github/workflows/pytest.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 84bcba8..294e73d 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -53,6 +53,10 @@ jobs: #---------------------------------------------- # run test suite #---------------------------------------------- + - name: Check black + run: | + poetry run black . --check + - name: Run tests run: | source .venv/bin/activate From 7f0e334bc399e446e35699f94579c40f088594b3 Mon Sep 17 00:00:00 2001 From: dotX12 Date: Thu, 23 Jun 2022 12:16:25 +0300 Subject: [PATCH 17/20] change version python 3.6 to 3.8 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index de198c6..9cb3adc 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@

- 🎵 Is a FREE asynchronous library from reverse engineered Shazam API written in Python 3.6+ with asyncio and aiohttp. Includes all the methods that Shazam has, including searching for a song by file. + 🎵 Is a FREE asynchronous library from reverse engineered Shazam API written in Python 3.8+ with asyncio and aiohttp. Includes all the methods that Shazam has, including searching for a song by file. -----

From 2311dc6305dcc80838feb9eb3ca12f4027c32447 Mon Sep 17 00:00:00 2001 From: dotX12 Date: Thu, 23 Jun 2022 12:18:37 +0300 Subject: [PATCH 18/20] fix black --- .github/workflows/pytest.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 294e73d..43df5f0 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -54,6 +54,7 @@ jobs: # run test suite #---------------------------------------------- - name: Check black + - uses: psf/black@stable run: | poetry run black . --check From f8125707019b5d7a4cbb90cb5fc5b09b1137a848 Mon Sep 17 00:00:00 2001 From: dotX12 Date: Thu, 23 Jun 2022 12:19:29 +0300 Subject: [PATCH 19/20] fix black v2 --- .github/workflows/pytest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 43df5f0..3618e23 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -54,7 +54,7 @@ jobs: # run test suite #---------------------------------------------- - name: Check black - - uses: psf/black@stable + uses: psf/black@stable run: | poetry run black . --check From 55f611afc1aee1401382e62880adbc79dd6dce79 Mon Sep 17 00:00:00 2001 From: dotX12 Date: Thu, 23 Jun 2022 12:20:59 +0300 Subject: [PATCH 20/20] fix black v3 --- .github/workflows/pytest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 3618e23..26a4cb5 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -54,8 +54,8 @@ jobs: # run test suite #---------------------------------------------- - name: Check black - uses: psf/black@stable run: | + poetry add --dev black --allow-prereleases poetry run black . --check - name: Run tests