symuid-sync 2.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. import mutagen
  4. import mutagen.id3
  5. import mutagen.mp4
  6. import os
  7. import re
  8. import subprocess
  9. import sys
  10. import symuid
  11. # freeform keys start with '----'
  12. # http://mutagen.readthedocs.io/en/latest/api/mp4.html
  13. TRACK_UUID_MP4_TAG = '----:symuid:uuid'
  14. PATH_DEFAULT_IGNORE_REGEX = r'\.(itdb|itc2|itl|jpg|midi?|plist|xml|zip)$'
  15. def generate_uuid():
  16. return subprocess.check_output(['uuid', '-v', '4', '-F', 'BIN']).strip()
  17. def get_or_assign_uuid_mp4(mp4_file):
  18. if not TRACK_UUID_MP4_TAG in mp4_file:
  19. mp4_file[TRACK_UUID_MP4_TAG] = mutagen.mp4.MP4FreeForm(
  20. data=generate_uuid(),
  21. # https://mutagen.readthedocs.io/en/latest/api/mp4.html#mutagen.mp4.AtomDataType.UUID
  22. dataformat=mutagen.mp4.AtomDataType.UUID,
  23. )
  24. mp4_file.save()
  25. mp4_file.load(filename=mp4_file.filename)
  26. print("{!r}: assigned uuid {!r}".format(
  27. mp4_file.filename, mp4_file[TRACK_UUID_MP4_TAG][0],
  28. ))
  29. return mp4_file[TRACK_UUID_MP4_TAG][0]
  30. def log_path(track_path, msg, stream=sys.stdout):
  31. stream.write("{!r}: {}\n".format(track_path, msg))
  32. def log_path_error(track_path, msg):
  33. log_path(track_path, msg, stream=sys.stderr)
  34. def symuid_sync(path, path_ignore_regex, show_ignored=False):
  35. for track in symuid.Track.walk(
  36. root_path=path,
  37. path_ignore_regex=path_ignore_regex,
  38. ignored_cb=lambda p: show_ignored and log_path(p, 'ignored'),
  39. unsupported_cb=lambda p, e: log_path_error(p, 'unsupported type, skipped'),
  40. ):
  41. if isinstance(track._iface, symuid.tag_interface.MP4):
  42. get_or_assign_uuid_mp4(track._iface._mutagen_file)
  43. elif track._iface.get_uuid() is None:
  44. track._iface.set_uuid(generate_uuid())
  45. track._iface.save()
  46. log_path(track.path, 'assigned uuid {!r}'.format(track._iface.get_uuid()))
  47. def _init_argparser():
  48. import argparse
  49. argparser = argparse.ArgumentParser(description=None)
  50. argparser.add_argument('path')
  51. argparser.add_argument(
  52. '--path-ignore-regex',
  53. default=PATH_DEFAULT_IGNORE_REGEX,
  54. nargs=1,
  55. metavar='pattern',
  56. dest='path_ignore_regex',
  57. type=lambda pattern: re.compile(pattern),
  58. help='(default: %(default)s)',
  59. )
  60. argparser.add_argument(
  61. '--show-ignored',
  62. action='store_true',
  63. )
  64. return argparser
  65. def main(argv):
  66. argparser = _init_argparser()
  67. args = argparser.parse_args(argv[1:])
  68. symuid_sync(**vars(args))
  69. return 0
  70. if __name__ == "__main__":
  71. sys.exit(main(sys.argv))