tag_interface.py 3.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. # -*- coding: utf-8 -*-
  2. import mutagen.id3
  3. import mutagen.mp4
  4. import re
  5. class _mutagen:
  6. @property
  7. def track_path(self):
  8. return self._mutagen_file.filename
  9. def save(self):
  10. self._mutagen_file.save()
  11. class ID3(_mutagen):
  12. # http://id3.org/id3v2.4.0-frames#4.1.
  13. _UFID_OWNER_ID = 'symuid'
  14. def __init__(self, mutagen_file):
  15. assert mutagen_file.tags, mutagen_file
  16. assert isinstance(mutagen_file.tags, mutagen.id3.ID3), \
  17. mutagen_file.tags
  18. self._mutagen_file = mutagen_file
  19. def get_free_ints(self, tag_label_prefix):
  20. for t in self._mutagen_file.tags.getall('TXXX:' + tag_label_prefix):
  21. assert len(t.text) == 1, t
  22. yield (t.desc, int(t.text[0]))
  23. def get_free_int(self, tag_label):
  24. tags = [t for t in self.get_free_ints(tag_label)]
  25. if len(tags) == 0:
  26. raise KeyError(tag_label)
  27. else:
  28. assert len(tags) == 1, tags
  29. assert tags[0][0] == tag_label, tag
  30. return tags[0][1]
  31. def set_free_int(self, tag_label, data):
  32. # mutagen.id3._specs.EncodedTextSpec.write encodes 'desc' and 'text'
  33. tag = mutagen.id3.TXXX(
  34. encoding=mutagen.id3.Encoding.LATIN1,
  35. desc=tag_label,
  36. text=[str(data)],
  37. )
  38. self._mutagen_file.tags.add(tag)
  39. return tag
  40. def get_uuid(self):
  41. for ufid in self._mutagen_file.tags.getall('UFID'):
  42. if ufid.owner == self._UFID_OWNER_ID:
  43. return ufid.data
  44. return None
  45. class MP4(_mutagen):
  46. def __init__(self, mutagen_file):
  47. assert mutagen_file.tags, mutagen_file
  48. assert isinstance(mutagen_file.tags, mutagen.mp4.MP4Tags), \
  49. mutagen_file.tags
  50. self._mutagen_file = mutagen_file
  51. @staticmethod
  52. def _freeform_to_int(freeform):
  53. # "a signed big-endian integer with length one of { 1,2,3,4,8 } bytes"
  54. assert freeform.dataformat == mutagen.mp4.AtomDataType.INTEGER, freeform
  55. return int.from_bytes(freeform, byteorder='big', signed=True)
  56. def get_free_ints(self, tag_label_prefix):
  57. label_pattern = re.compile(r'^----:{}(:|$)'.format(
  58. re.escape(tag_label_prefix),
  59. ))
  60. for label, values in self._mutagen_file.tags.items():
  61. if label_pattern.match(label):
  62. assert len(values) == 1, (label, values)
  63. value = MP4._freeform_to_int(values[0])
  64. yield (re.sub(r'^----:', '', label), value)
  65. def get_free_int(self, tag_label):
  66. tag, = self._mutagen_file.tags['----:' + tag_label]
  67. return MP4._freeform_to_int(tag)
  68. def set_free_int(self, tag_label, data):
  69. assert isinstance(data, int)
  70. tag = mutagen.mp4.MP4FreeForm(
  71. # "a signed big-endian integer with length one of { 1,2,3,4,8 } bytes"
  72. dataformat=mutagen.mp4.AtomDataType.INTEGER,
  73. # TODO set byte length properly
  74. data=data.to_bytes(1, byteorder='big', signed=True),
  75. )
  76. self._mutagen_file.tags['----:' + tag_label] = tag
  77. return tag