123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138 |
- #!/usr/bin/env python3
- # -*- coding: utf-8 -*-
- import datetime as dt
- import dateutil.parser
- import mutagen
- import mutagen.id3
- import mutagen.mp4
- import os
- import urllib.parse
- import xml.etree.ElementTree
- def generate_play_count_tag_label(player, library_id, reg_dt):
- return 'symuid:pcnt:{}:{}:{}'.format(player, library_id, int(reg_dt.timestamp()))
- def get_itunes_dict_value_node(dict_node, key):
- assert isinstance(dict_node, xml.etree.ElementTree.Element)
- assert isinstance(key, str)
- # WORKAROUND method getnext() is sadly not available
- for child_idx, child_node in enumerate(dict_node):
- if child_node.tag == 'key' and child_node.text == key:
- return dict_node[child_idx + 1]
- raise KeyError()
- def get_itunes_dict_value(dict_node, key):
- value_node = get_itunes_dict_value_node(dict_node, key)
- if value_node.tag == 'string':
- return value_node.text
- elif value_node.tag == 'integer':
- return int(value_node.text)
- elif value_node.tag == 'date':
- return dateutil.parser.parse(value_node.text)
- else:
- return value_node
- def set_play_count_tag(track_path, player, library_id, reg_dt, play_count):
- assert isinstance(reg_dt, dt.datetime), reg_dt
- tag_label = generate_play_count_tag_label(
- player=player,
- library_id=library_id,
- reg_dt=reg_dt,
- )
- track = mutagen.File(filename=track_path)
- if isinstance(track.tags, mutagen.id3.ID3):
- tag_label_id3 = 'TXXX:' + tag_label
- if not tag_label_id3 in track.tags:
- # mutagen.id3._specs.EncodedTextSpec.write encodes
- # 'desc' and 'text'
- tag = mutagen.id3.TXXX(
- encoding=mutagen.id3.Encoding.LATIN1,
- desc=tag_label,
- text=[str(play_count)],
- )
- track.tags.add(tag)
- track.save()
- print('{!r}: set ID3 tag {!r}'.format(track_path, tag))
- elif isinstance(track.tags, mutagen.mp4.MP4Tags):
- tag_label_mp4 = '----:' + tag_label
- if not tag_label_mp4 in track.tags:
- track.tags[tag_label_mp4] = tag = mutagen.mp4.MP4FreeForm(
- # "a signed big-endian integer with length one of { 1,2,3,4,8 } bytes"
- # TODO set byte length properly
- data=play_count.to_bytes(1, byteorder='big'),
- dataformat=mutagen.mp4.AtomDataType.INTEGER,
- )
- track.save()
- print('{!r}: set MP4 tag {!r}'.format(track_path, tag))
- else:
- raise Exception(track_path)
- def symuid_import_itunes(xml_library_path, root_url, root_path):
- root_path = os.path.expanduser(root_path)
- lib = xml.etree.ElementTree.parse(xml_library_path)
- # WORKAROUND find('.//key[.="Library Persistent ID"]')
- # -> SyntaxError: invalid predicate
- lib_root_dict = lib.find('./dict')
- lib_id = get_itunes_dict_value(lib_root_dict, 'Library Persistent ID')
- assert isinstance(lib_id, str) and len(lib_id) > 0, lib_id
- for track_node in get_itunes_dict_value(lib_root_dict, 'Tracks').iterfind('./dict'):
- try:
- track_url = get_itunes_dict_value(track_node, 'Location')
- except KeyError:
- track_url = None
- try:
- play_count = get_itunes_dict_value(track_node, 'Play Count')
- except KeyError:
- play_count = 0
- try:
- last_play_dt = get_itunes_dict_value(track_node, 'Play Date UTC')
- except KeyError:
- last_play_dt = None
- # TODO create tag if last_play_dt is None
- if last_play_dt and track_url and track_url.startswith(root_url):
- track_path = os.path.join(
- root_path,
- urllib.parse.unquote(track_url[len(root_url):]),
- )
- # TODO dt=dt.datetime.now()
- set_play_count_tag(
- track_path=track_path,
- player='itunes',
- library_id=lib_id,
- reg_dt=last_play_dt,
- play_count=play_count,
- )
- def _init_argparser():
- import argparse
- argparser = argparse.ArgumentParser(description=None)
- argparser.add_argument('xml_library_path')
- argparser.add_argument(
- '--root-url',
- default='file://localhost/',
- help='(default: %(default)r)',
- )
- argparser.add_argument(
- '--root-path',
- default='',
- help='(default: %(default)r)',
- )
- return argparser
- def main(argv):
- argparser = _init_argparser()
- args = argparser.parse_args(argv[1:])
- symuid_import_itunes(**vars(args))
- return 0
- if __name__ == "__main__":
- import sys
- sys.exit(main(sys.argv))
|