symuid-import-itunes 3.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. import datetime as dt
  4. import mutagen
  5. import mutagen.id3
  6. import mutagen.mp4
  7. import os
  8. import re
  9. import symuid.library.itunes
  10. def generate_play_count_tag_label(player, library_id, reg_dt):
  11. return 'symuid:pcnt:{}:{}:{}'.format(player, library_id, int(reg_dt.timestamp()))
  12. def set_play_count_tag(track_path, player, library_id, reg_dt, play_count):
  13. assert isinstance(reg_dt, dt.datetime), reg_dt
  14. tag_label = generate_play_count_tag_label(
  15. player=player,
  16. library_id=library_id,
  17. reg_dt=reg_dt,
  18. )
  19. track = mutagen.File(filename=track_path)
  20. if isinstance(track.tags, mutagen.id3.ID3):
  21. tag_label_id3 = 'TXXX:' + tag_label
  22. if not tag_label_id3 in track.tags:
  23. # mutagen.id3._specs.EncodedTextSpec.write encodes
  24. # 'desc' and 'text'
  25. tag = mutagen.id3.TXXX(
  26. encoding=mutagen.id3.Encoding.LATIN1,
  27. desc=tag_label,
  28. text=[str(play_count)],
  29. )
  30. track.tags.add(tag)
  31. track.save()
  32. print('{!r}: set ID3 tag {!r}'.format(track_path, tag))
  33. elif isinstance(track.tags, mutagen.mp4.MP4Tags):
  34. tag_label_mp4 = '----:' + tag_label
  35. if not tag_label_mp4 in track.tags:
  36. track.tags[tag_label_mp4] = tag = mutagen.mp4.MP4FreeForm(
  37. # "a signed big-endian integer with length one of { 1,2,3,4,8 } bytes"
  38. # TODO set byte length properly
  39. data=play_count.to_bytes(1, byteorder='big'),
  40. dataformat=mutagen.mp4.AtomDataType.INTEGER,
  41. )
  42. track.save()
  43. print('{!r}: set MP4 tag {!r}'.format(track_path, tag))
  44. else:
  45. raise Exception(track_path)
  46. def symuid_import_itunes(xml_library_path, path_regex_sub):
  47. lib = symuid.library.itunes.XmlLibrary(xml_library_path)
  48. for track in lib.tracks:
  49. # TODO create tag if last_play_dt is None
  50. if track.last_play_dt and track.local:
  51. track_path = track.local_path
  52. for pattern, repl in path_regex_sub:
  53. track_path = re.sub(pattern, repl, track_path)
  54. if not os.path.exists(track_path):
  55. raise ValueError(track_path)
  56. else:
  57. # TODO dt=dt.datetime.now()
  58. set_play_count_tag(
  59. track_path=track_path,
  60. player='itunes',
  61. library_id=lib.id,
  62. reg_dt=track.last_play_dt,
  63. play_count=track.play_count,
  64. )
  65. def _init_argparser():
  66. import argparse
  67. argparser = argparse.ArgumentParser(description=None)
  68. argparser.add_argument('xml_library_path')
  69. argparser.add_argument(
  70. '--path-regex-sub',
  71. nargs=2,
  72. action='append',
  73. metavar=('regex', 'replacement'),
  74. default=[],
  75. help='(default: %(default)r)',
  76. )
  77. return argparser
  78. def main(argv):
  79. argparser = _init_argparser()
  80. args = argparser.parse_args(argv[1:])
  81. symuid_import_itunes(**vars(args))
  82. return 0
  83. if __name__ == "__main__":
  84. import sys
  85. sys.exit(main(sys.argv))