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