Browse Source

id3 interface: refactor; added return type hints; added get/set_free_int_ratio()

Fabian Peter Hammerle 1 month ago
parent
commit
2a930a7fa7
2 changed files with 93 additions and 17 deletions
  1. 31 13
      symuid/_tag_interface.py
  2. 62 4
      tests/tag_interface/test_id3.py

+ 31 - 13
symuid/_tag_interface.py

@@ -72,39 +72,57 @@ class ID3(_MutagenTagInterface):
             mutagen_file.tags
         super().__init__(mutagen_file)
 
-    def _get_single_text(self, tag_label):
+    def _get_str(self, tag_label) -> str:
         tag = self._mutagen_file.tags.get(tag_label, None)
         if tag is None:
-            # {}.get('a') == None
-            return None
+            raise KeyError(tag_label)
         if len(tag.text) == 1:
             return tag.text[0]
         raise ValueError(tag)
 
+    def _get_free_str(self, tag_label) -> str:
+        return self._get_str('TXXX:' + tag_label)
+
     def get_free_ints(self, tag_label_prefix):
         for tag in self._mutagen_file.tags.getall('TXXX:' + tag_label_prefix):
             assert len(tag.text) == 1, tag
             yield (tag.desc, int(tag.text[0]))
 
-    def get_free_int(self, tag_label):
-        text = self._get_single_text('TXXX:' + tag_label)
-        return int(text) if text else None
+    def get_free_int(self, tag_label) -> typing.Optional[int]:
+        try:
+            return int(self._get_free_str(tag_label))
+        except KeyError:
+            return None
 
-    def set_free_int(self, tag_label, data):
+    def get_free_int_ratio(self, tag_label) -> typing.Tuple[int, int]:
+        nominator, denominator = map(int, self._get_free_str(tag_label).split('/'))
+        return (nominator, denominator)
+
+    def _set_free_str(self, tag_label: str, string: str) -> mutagen.id3.TXXX:
         # mutagen.id3._specs.EncodedTextSpec.write encodes 'desc' and 'text'
         tag = mutagen.id3.TXXX(
             encoding=mutagen.id3.Encoding.LATIN1,
             desc=tag_label,
-            text=[str(data)],
+            text=[string],
         )
         # TODO overwrite instead of add() ?
         self._mutagen_file.tags.add(tag)
         return tag
 
-    def get_comment(self):
-        return self._get_single_text('COMM::eng')
+    def set_free_int(self, tag_label: str, data: str) -> mutagen.id3.TXXX:
+        return self._set_free_str(tag_label=tag_label, string=str(data))
 
-    def set_comment(self, comment):
+    def set_free_int_ratio(self, tag_label, numerator: int, denominator: int) -> mutagen.id3.TXXX:
+        return self._set_free_str(tag_label=tag_label,
+                                  string='{}/{}'.format(numerator, denominator))
+
+    def get_comment(self) -> typing.Optional[str]:
+        try:
+            return self._get_str('COMM::eng')
+        except KeyError:
+            return None
+
+    def set_comment(self, comment) -> mutagen.id3.COMM:
         tag = mutagen.id3.COMM(
             encoding=mutagen.id3.Encoding.UTF8,
             lang='eng',
@@ -113,13 +131,13 @@ class ID3(_MutagenTagInterface):
         self._mutagen_file.tags.add(tag)
         return tag
 
-    def get_track_uuid(self):
+    def get_track_uuid(self) -> typing.Optional:
         for ufid in self._mutagen_file.tags.getall('UFID'):
             if ufid.owner == self._UFID_OWNER_ID:
                 return ufid.data
         return None
 
-    def set_track_uuid(self, uuid):
+    def set_track_uuid(self, uuid) -> mutagen.id3.UFID:
         # mutagen.id3._specs.EncodedTextSpec.write encodes 'owner'
         tag = mutagen.id3.UFID(owner=self._UFID_OWNER_ID, data=uuid)
         self._mutagen_file.tags.add(tag)

+ 62 - 4
tests/tag_interface/test_id3.py

@@ -1,4 +1,5 @@
 import os
+import re
 
 import mutagen
 import pytest
@@ -16,14 +17,39 @@ def test_get_track_path(tracks_dir_path, track_name):
 
 
 @pytest.mark.parametrize(('track_name', 'tag_label', 'expected_text'), [
-    ('id3v2.4-empty.mp3', 'TPE1', None),
     ('id3v2.4-typical.mp3', 'TPE1', 'some artist'),
     ('id3v2.4-typical.mp3', 'COMM::eng', 'some comment'),
-    ('id3v2.4-typical.mp3', 'COMM', None),
 ])
-def test__get_single_text(tracks_dir_path, track_name, tag_label, expected_text):
+def test__get_str(tracks_dir_path, track_name, tag_label, expected_text):
     iface = ID3(mutagen.File(os.path.join(tracks_dir_path, track_name)))
-    assert expected_text == iface._get_single_text(tag_label)
+    assert expected_text == iface._get_str(tag_label)
+
+
+@pytest.mark.parametrize(('track_name', 'tag_label'), [
+    ('id3v2.4-empty.mp3', 'TPE1'),
+    ('id3v2.4-typical.mp3', 'COMM'),
+])
+def test__get_str_missing(tracks_dir_path, track_name, tag_label):
+    iface = ID3(mutagen.File(os.path.join(tracks_dir_path, track_name)))
+    with pytest.raises(KeyError, match=re.escape(tag_label)):
+        iface._get_str(tag_label)
+
+
+def test__get_free_str(empty_id3_path):
+    mutagen_file = mutagen.File(empty_id3_path)
+    mutagen_file.tags.add(mutagen.id3.TXXX(
+        desc='foo',
+        text='bar',
+    ))
+    mutagen_file.save()
+    id3_iface = ID3(mutagen.File(empty_id3_path))
+    assert id3_iface._get_free_str('foo') == 'bar'
+
+
+def test__get_free_str_missing(empty_id3_path):
+    iface = ID3(mutagen.File(empty_id3_path))
+    with pytest.raises(KeyError, match=r'TXXX:foo'):
+        iface._get_free_str('foo')
 
 
 @pytest.mark.parametrize(('track_name', 'expected_comment'), [
@@ -49,3 +75,35 @@ def test_set_comment(empty_id3_path):
     assert isinstance(tag, mutagen.id3.COMM)
     assert tag.lang == 'eng'
     assert tag.text == ['你好']
+
+
+@pytest.mark.parametrize(('tag_label', 'tag_value', 'expected_value'), [
+    ('foo', '21/42', (21, 42)),
+    ('bar', '-1/4', (-1, 4)),
+    ('ham:egg', '0/-3', (0, -3)),
+])
+def test_get_free_int_ratio(empty_id3_path, tag_label, tag_value, expected_value):
+    mutagen_file = mutagen.File(empty_id3_path)
+    mutagen_file.tags.add(mutagen.id3.TXXX(
+        encoding=mutagen.id3.Encoding.LATIN1,
+        desc=tag_label,
+        text=[tag_value],
+    ))
+    mutagen_file.save()
+    id3_iface = ID3(mutagen.File(empty_id3_path))
+    assert id3_iface.get_free_int_ratio(tag_label) == expected_value
+
+
+@pytest.mark.parametrize(
+    ('tag_label', 'nominator', 'denominator', 'expected_tag_value'),
+    [('foo', 21, 42, '21/42'),
+     ('bar', -1, 4, '-1/4'),
+     ('ham:egg', 0, -3, '0/-3')],
+)
+def test_set_free_int_ratio(empty_id3_path, tag_label, nominator, denominator, expected_tag_value):
+    id3_iface = ID3(mutagen.File(empty_id3_path))
+    assert repr(id3_iface.set_free_int_ratio(tag_label, nominator, denominator))
+    id3_iface.save()
+    mutagen_file = mutagen.File(empty_id3_path)
+    assert len(mutagen_file.tags) == 1
+    assert mutagen_file.tags['TXXX:' + tag_label].text == [expected_tag_value]