tag_interface.py 2.8 KB

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