diff --git a/README.md b/README.md index 614caad..c62d30d 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,9 @@ A module that extends TinyTag's capabilities by also writing tags. ### Features -| Feature | Sub-Features | State | -|-------------|-------------------------------------------------------------------|--------------------------------------------------------| -| ID3-Writing | Writing Of String Frames | Partially Implemented | +| Feature | Sub-Features | State | +|-------------|--------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------| +| ID3-Writing | Writing Of String Frames
Writing Of Link Frames | Partially Implemented | ## Usage diff --git a/smalltag/_formats/_id3.py b/smalltag/_formats/_id3.py index dbe9736..540e1fe 100644 --- a/smalltag/_formats/_id3.py +++ b/smalltag/_formats/_id3.py @@ -2,7 +2,6 @@ from __future__ import annotations from struct import pack -from io import BytesIO from tinytag import TinyTag, tinytag from .. import SmallTag @@ -24,10 +23,10 @@ class _ID3(SmallTag, tinytag._ID3): return ints - def write(self, file_obj: BinaryIO=None): + def write(self, advanced: dict[str, str | float | list[str]] | None=None, file_obj: BinaryIO=None) -> None: should_close_file = self._set_filehandler_for_writing(file_obj) - new_tag = self._compose_id3v2_tag() + new_tag = self._compose_id3v2_tag(advanced) size, extended, major = self._parse_id3v2_header(self._filehandler) @@ -41,14 +40,17 @@ class _ID3(SmallTag, tinytag._ID3): if should_close_file: self._filehandler.close() - def _compose_id3v2_tag(self) -> bytes: + def compose_tag(self, advanced: dict | None=None) -> bytes: + return self._compose_id3v2_tag(advanced) + + def _compose_id3v2_tag(self, advanced: dict | None=None) -> bytes: # change keys to values in the mapping dict self._ID3_WRITE_MAPPING = {} # noqa for frame_id, name in self._ID3_MAPPING.items(): if len(frame_id) == 4: self._ID3_WRITE_MAPPING[name] = frame_id - frames = self._compose_id3v2_frames() + frames = self._compose_id3v2_frames(advanced) frame_size = len(frames) header = self._compose_id3v2_header(frame_size) @@ -65,13 +67,22 @@ class _ID3(SmallTag, tinytag._ID3): return header - def _compose_id3v2_frames(self) -> bytes: - tag_dict = self.as_dict() + def _compose_id3v2_frames(self, advanced: dict | None=None) -> bytes: + if advanced is None: + tag_dict = self.as_dict() + + else: + tag_dict = advanced frames = b"" for field_name, field_value in tag_dict.items(): if field_name not in self._ID3_WRITE_MAPPING: + other_field = "other." + field_name + + if other_field in self._ID3_WRITE_MAPPING: + frames += self._compose_id3v2_frame(other_field, field_value) + continue frames += self._compose_id3v2_frame(field_name, field_value) @@ -85,7 +96,12 @@ class _ID3(SmallTag, tinytag._ID3): field_value = field_value[0] if isinstance(field_value, str): - frame_value = b"\x00" + bytes(field_value, "ISO-8859-1") + frame_value = bytes(field_value, "ISO-8859-1") + + # set format byte to ISO-8859-1 on normal text frames + # (link-frames are always ISO-8859-1 formatted and don't need the format byte) + if frame_id.startswith(b"T"): + frame_value = b"\x00" + frame_value elif isinstance(field_value, int) and frame_id.startswith(b"T"): frame_value = b"\x00" + bytes(str(field_value), "ISO-8859-1") diff --git a/smalltag/smalltag.py b/smalltag/smalltag.py index 0d9f846..fbdcced 100644 --- a/smalltag/smalltag.py +++ b/smalltag/smalltag.py @@ -75,7 +75,22 @@ class SmallTag(TinyTag): # return _Aiff # return None - def write(self, file_obj: BinaryIO = None): + def write(self, advanced: dict[str, str | float | list[str]] | None=None, file_obj: BinaryIO = None): + """ + Write to the file given at creation of the tag or to the file object given to this function. + :param advanced: Dict containing fields to write. + :param file_obj: File object to write to. (Has to be in "rb+" mode.) + """ + + raise NotImplementedError + + def compose_tag(self, advanced: dict[str, str | float | list[str]] | None=None) -> bytes: + """ + Only compose a tag without writing. + :param advanced: Dict containing fields to write. + :return: Composed tag as bytes. + """ + raise NotImplementedError def _set_filehandler_for_writing(self, file_obj: BinaryIO) -> bool: diff --git a/tests/formats/test_id3.py b/tests/formats/test_id3.py index a816aa0..205e8a5 100644 --- a/tests/formats/test_id3.py +++ b/tests/formats/test_id3.py @@ -14,13 +14,15 @@ def test_written_gets_recognized(): tag.artist = "An artist." tag.album = "The album." tag.track = 1234 + tag.other["url"] = ["https://teapot.informationsanarchistik.de/Wobbl/SmallTag"] - tag = SmallTag.get("formats/test.mp3", file_obj=BytesIO(tag._compose_id3v2_tag())) + tag = SmallTag.get("formats/test.mp3", file_obj=BytesIO(tag.compose_tag())) assert tag.title == "A normal title." assert tag.artist == "An artist." assert tag.album == "The album." assert tag.track == 1234 + assert tag.other["url"] == ["https://teapot.informationsanarchistik.de/Wobbl/SmallTag"] # chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_ " @@ -39,6 +41,8 @@ def test_full_written_gets_recognized(): tag.title = field_content tag.artist = field_content tag.album = field_content + tag.track = 1234 + tag.other["url"] = ["https://teapot.informationsanarchistik.de/Wobbl/SmallTag"] tag.write() @@ -47,3 +51,5 @@ def test_full_written_gets_recognized(): assert tag.title == field_content assert tag.artist == field_content assert tag.album == field_content + assert tag.track == 1234 + assert tag.other["url"] == ["https://teapot.informationsanarchistik.de/Wobbl/SmallTag"]