From 7ba59e0238c9234ed0eb6f70fcfd2b4fec8b6c39 Mon Sep 17 00:00:00 2001 From: Roman Nebaluev Date: Sat, 16 Nov 2024 11:14:52 +0500 Subject: [PATCH] add flac tag test --- .gitattributes | 1 + tests/conftest.py | 19 ++++-- tests/contrib/test.flac | 3 + tests/test_tag.py | 130 +++++++++++++++++++--------------------- 4 files changed, 79 insertions(+), 74 deletions(-) create mode 100644 tests/contrib/test.flac diff --git a/.gitattributes b/.gitattributes index 7933f2b..07a31e7 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,3 @@ tests/contrib/test.m4a filter=lfs diff=lfs merge=lfs -text tests/contrib/test.mp3 filter=lfs diff=lfs merge=lfs -text +tests/contrib/test.flac filter=lfs diff=lfs merge=lfs -text diff --git a/tests/conftest.py b/tests/conftest.py index b16b00d..c65e858 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,11 +4,22 @@ import pytest +def _load_audio(codec: str) -> BytesIO: + return BytesIO( + initial_bytes=Path(f"tests/contrib/test.{codec}").read_bytes() + ) + + +@pytest.fixture +def m4a_audio() -> BytesIO: + return _load_audio("m4a") + + @pytest.fixture -def m4a_wtihout_tag() -> BytesIO: - return BytesIO(initial_bytes=Path("tests/contrib/test.m4a").read_bytes()) +def mp3_audio() -> BytesIO: + return _load_audio("mp3") @pytest.fixture -def mp3_wtihout_tag() -> BytesIO: - return BytesIO(initial_bytes=Path("tests/contrib/test.mp3").read_bytes()) +def flac_audio() -> BytesIO: + return _load_audio("flac") diff --git a/tests/contrib/test.flac b/tests/contrib/test.flac new file mode 100644 index 0000000..2d23dc6 --- /dev/null +++ b/tests/contrib/test.flac @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:98f6ef80f9f82fc7ec24f8952438631731a4ae94305a40b0955f251f5c0b34ff +size 1493033 diff --git a/tests/test_tag.py b/tests/test_tag.py index c0d23b2..a037837 100644 --- a/tests/test_tag.py +++ b/tests/test_tag.py @@ -1,4 +1,6 @@ # ruff: noqa: S101 +from __future__ import annotations + import json from io import BytesIO from subprocess import check_output @@ -14,6 +16,26 @@ class TestTag: + def ffprobe_tag(self, data: bytes) -> dict[str, str]: + with NamedTemporaryFile("wb") as tmp_fd: + tmp_fd.write(data) + tmp_fd.flush() + json_output = json.loads( + check_output( + [ + "ffprobe", + "-loglevel", + "panic", + "-print_format", + "json", + "-show_format", + "-show_streams", + tmp_fd.name, + ] + ) + ) + return {k.lower(): v for k, v in json_output["format"]["tags"].items()} + @pytest.fixture def tag(self) -> TrackTag: return TrackTag( @@ -25,95 +47,63 @@ def tag(self) -> TrackTag: duration_ms=100.0, ) - def test_tag_mp4(self, tag: TrackTag, m4a_wtihout_tag: BytesIO) -> None: - after_tag = tag.to_bytes(m4a_wtihout_tag) + def tagging(self, track_tag: TrackTag, audio: BytesIO, codec: str) -> None: + after_tag = track_tag.to_bytes(audio, codec) assert after_tag is not None - with NamedTemporaryFile("wb") as tmp_fd: - tmp_fd.write(after_tag) - tmp_fd.flush() - ffprobe_output = check_output( - [ - "ffprobe", - "-loglevel", - "panic", - "-print_format", - "json", - "-show_format", - "-show_streams", - tmp_fd.name, - ] - ) - json_output = json.loads(ffprobe_output) + audio_tags = self.ffprobe_tag(after_tag) - tag_json = tag.to_dict() + tag_json = track_tag.to_dict() for key in ("artist", "title", "album", "genre"): - assert json_output["format"]["tags"][key] == tag_json[key] + assert audio_tags.get(key, "") == tag_json[key], audio_tags - assert json_output["format"]["tags"]["date"] == tag_json["year"] + assert audio_tags["date"] == tag_json["year"] - def test_tag_partial_data_mp4( - self, tag: TrackTag, m4a_wtihout_tag: BytesIO + def partial_tagging( + self, + track_tag: TrackTag, + audio: BytesIO, + codec: str, + read_size: int, + min_tag_size: int, ) -> None: - m4a_parital = BytesIO() - while data := m4a_wtihout_tag.read(49): - m4a_parital.write(data) - last_seek = m4a_parital.tell() + audio_parital = BytesIO() + while data := audio.read(read_size): + audio_parital.write(data) + last_seek = audio_parital.tell() - after_tag = tag.to_bytes(m4a_parital) - if len(m4a_parital.getbuffer()) < MP4_HEADER_MIN_SIZE: + after_tag = track_tag.to_bytes(audio_parital, codec) + if len(audio_parital.getbuffer()) < min_tag_size: assert after_tag is None else: assert after_tag is not None - assert m4a_parital.tell() == last_seek - - m4a_parital.seek(0) - - assert m4a_wtihout_tag.getvalue() == m4a_parital.getvalue() + assert audio_parital.tell() == last_seek - def test_tag_mp3(self, tag: TrackTag, mp3_wtihout_tag: BytesIO) -> None: - after_tag = tag.to_bytes(mp3_wtihout_tag) - assert after_tag is not None + audio_parital.seek(0) - with NamedTemporaryFile("wb") as tmp_fd: - tmp_fd.write(after_tag) - tmp_fd.flush() - ffprobe_output = check_output( - [ - "ffprobe", - "-loglevel", - "panic", - "-print_format", - "json", - "-show_format", - "-show_streams", - tmp_fd.name, - ] - ) - json_output = json.loads(ffprobe_output) + assert audio.getvalue() == audio_parital.getvalue() - tag_json = tag.to_dict() + def test_tag_mp4(self, tag: TrackTag, m4a_audio: BytesIO) -> None: + self.tagging(tag, m4a_audio, "aac") - for key in ("artist", "title", "album", "genre"): - assert json_output["format"]["tags"][key] == tag_json[key] + def test_tag_partial_data_mp4( + self, tag: TrackTag, m4a_audio: BytesIO + ) -> None: + self.partial_tagging(tag, m4a_audio, "aac", 49, MP4_HEADER_MIN_SIZE) - assert json_output["format"]["tags"]["date"] == tag_json["year"] + def test_tag_mp3(self, tag: TrackTag, mp3_audio: BytesIO) -> None: + self.tagging(tag, mp3_audio, "mp3") def test_tag_partial_data_mp3( - self, tag: TrackTag, mp3_wtihout_tag: BytesIO + self, tag: TrackTag, mp3_audio: BytesIO ) -> None: - mp3_parital = BytesIO() - while data := mp3_wtihout_tag.read(1024): - mp3_parital.write(data) - last_seek = mp3_parital.tell() + self.partial_tagging(tag, mp3_audio, "mp3", 1024, MP3_HEADER_MIN_SIZE) - after_tag = tag.to_bytes(mp3_parital) - if len(mp3_parital.getbuffer()) < MP3_HEADER_MIN_SIZE: - assert after_tag is None - else: - assert after_tag is not None - assert mp3_parital.tell() == last_seek + def test_tag_flac(self, tag: TrackTag, flac_audio: BytesIO) -> None: + self.tagging(tag, flac_audio, "flac") - mp3_parital.seek(0) - assert mp3_wtihout_tag.getvalue() == mp3_parital.getvalue() + def test_tag_partial_data_flac( + self, tag: TrackTag, flac_audio: BytesIO + ) -> None: + self.partial_tagging(tag, flac_audio, "flac", 1024, 9098)