Implemented writing of link frames.

This commit is contained in:
The Wobbler 2025-03-18 19:28:24 +01:00
parent d48394a522
commit 2db7097fdf
4 changed files with 50 additions and 13 deletions

View file

@ -4,9 +4,9 @@ A module that extends TinyTag's capabilities by also writing tags.
### Features
| Feature | Sub-Features | State |
|-------------|-------------------------------------------------------------------|--------------------------------------------------------|
| ID3-Writing | <input type="checkbox" disabled checked> Writing Of String Frames | <input type="checkbox" disabled> Partially Implemented |
| Feature | Sub-Features | State |
|-------------|--------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------|
| ID3-Writing | <input type="checkbox" disabled checked> Writing Of String Frames<br><input type="checkbox" disabled checked> Writing Of Link Frames | <input type="checkbox" disabled> Partially Implemented |
## Usage

View file

@ -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")

View file

@ -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:

View file

@ -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"]