symuid-import-itunes 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. import datetime as dt
  4. import dateutil.parser
  5. import mutagen
  6. import mutagen.id3
  7. import mutagen.mp4
  8. import os
  9. import symuid.library.itunes
  10. import urllib.parse
  11. def generate_play_count_tag_label(player, library_id, reg_dt):
  12. return 'symuid:pcnt:{}:{}:{}'.format(player, library_id, int(reg_dt.timestamp()))
  13. def set_play_count_tag(track_path, player, library_id, reg_dt, play_count):
  14. assert isinstance(reg_dt, dt.datetime), reg_dt
  15. tag_label = generate_play_count_tag_label(
  16. player=player,
  17. library_id=library_id,
  18. reg_dt=reg_dt,
  19. )
  20. track = mutagen.File(filename=track_path)
  21. if isinstance(track.tags, mutagen.id3.ID3):
  22. tag_label_id3 = 'TXXX:' + tag_label
  23. if not tag_label_id3 in track.tags:
  24. # mutagen.id3._specs.EncodedTextSpec.write encodes
  25. # 'desc' and 'text'
  26. tag = mutagen.id3.TXXX(
  27. encoding=mutagen.id3.Encoding.LATIN1,
  28. desc=tag_label,
  29. text=[str(play_count)],
  30. )
  31. track.tags.add(tag)
  32. track.save()
  33. print('{!r}: set ID3 tag {!r}'.format(track_path, tag))
  34. elif isinstance(track.tags, mutagen.mp4.MP4Tags):
  35. tag_label_mp4 = '----:' + tag_label
  36. if not tag_label_mp4 in track.tags:
  37. track.tags[tag_label_mp4] = tag = mutagen.mp4.MP4FreeForm(
  38. # "a signed big-endian integer with length one of { 1,2,3,4,8 } bytes"
  39. # TODO set byte length properly
  40. data=play_count.to_bytes(1, byteorder='big'),
  41. dataformat=mutagen.mp4.AtomDataType.INTEGER,
  42. )
  43. track.save()
  44. print('{!r}: set MP4 tag {!r}'.format(track_path, tag))
  45. else:
  46. raise Exception(track_path)
  47. def symuid_import_itunes(xml_library_path, root_url, root_path):
  48. root_path = os.path.expanduser(root_path)
  49. lib = symuid.library.itunes.XmlLibrary(xml_library_path)
  50. for track_dict in lib._root_dict['Tracks'].values():
  51. try:
  52. track_url = track_dict['Location']
  53. except KeyError:
  54. track_url = None
  55. try:
  56. play_count = track_dict['Play Count']
  57. except KeyError:
  58. play_count = 0
  59. try:
  60. last_play_dt = track_dict['Play Date UTC']
  61. except KeyError:
  62. last_play_dt = None
  63. # TODO create tag if last_play_dt is None
  64. if last_play_dt and track_url and track_url.startswith(root_url):
  65. track_path = os.path.join(
  66. root_path,
  67. urllib.parse.unquote(track_url[len(root_url):]),
  68. )
  69. # TODO dt=dt.datetime.now()
  70. set_play_count_tag(
  71. track_path=track_path,
  72. player='itunes',
  73. library_id=lib.id,
  74. reg_dt=last_play_dt,
  75. play_count=play_count,
  76. )
  77. def _init_argparser():
  78. import argparse
  79. argparser = argparse.ArgumentParser(description=None)
  80. argparser.add_argument('xml_library_path')
  81. argparser.add_argument(
  82. '--root-url',
  83. default='file://localhost/',
  84. help='(default: %(default)r)',
  85. )
  86. argparser.add_argument(
  87. '--root-path',
  88. default='',
  89. help='(default: %(default)r)',
  90. )
  91. return argparser
  92. def main(argv):
  93. argparser = _init_argparser()
  94. args = argparser.parse_args(argv[1:])
  95. symuid_import_itunes(**vars(args))
  96. return 0
  97. if __name__ == "__main__":
  98. import sys
  99. sys.exit(main(sys.argv))