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