|
@@ -1,15 +1,11 @@
|
|
|
#!/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 subprocess
|
|
|
-import urllib.parse
|
|
|
-import xml.etree.ElementTree
|
|
|
|
|
|
# http://id3.org/id3v2.4.0-frames#4.1.
|
|
|
TRACK_UUID_ID3_OWNER_ID = 'symuid'
|
|
@@ -18,10 +14,6 @@ TRACK_UUID_ID3_OWNER_ID = 'symuid'
|
|
|
TRACK_UUID_MP4_TAG = '----:symuid:uuid'
|
|
|
|
|
|
|
|
|
-def generate_play_count_tag_label(player, library_id, dt):
|
|
|
- return 'symuid:pcnt:{}:{}:{}'.format(player, library_id, int(dt.timestamp()))
|
|
|
-
|
|
|
-
|
|
|
def generate_uuid():
|
|
|
return subprocess.check_output(['uuid', '-v', '4', '-F', 'BIN']).strip()
|
|
|
|
|
@@ -66,107 +58,7 @@ def get_or_assign_uuid_mp4(mp4_file):
|
|
|
return mp4_file[TRACK_UUID_MP4_TAG][0]
|
|
|
|
|
|
|
|
|
-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, dt, play_count):
|
|
|
- tag_label = generate_play_count_tag_label(
|
|
|
- player=player,
|
|
|
- library_id=library_id,
|
|
|
- dt=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 import_itunes_play_count(itunes_library_path, itunes_library_root_url, itunes_library_root_path):
|
|
|
- itunes_library_root_path = os.path.expanduser(itunes_library_root_path)
|
|
|
- lib = xml.etree.ElementTree.parse(itunes_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(itunes_library_root_url):
|
|
|
- track_path = os.path.join(
|
|
|
- itunes_library_root_path,
|
|
|
- urllib.parse.unquote(track_url[len(itunes_library_root_url):]),
|
|
|
- )
|
|
|
- # TODO dt=dt.datetime.now()
|
|
|
- set_play_count_tag(
|
|
|
- track_path=track_path,
|
|
|
- player='itunes',
|
|
|
- library_id=lib_id,
|
|
|
- dt=last_play_dt,
|
|
|
- play_count=play_count,
|
|
|
- )
|
|
|
-
|
|
|
-
|
|
|
-def symuid(path, itunes_library_path=None, itunes_library_root_url=None, itunes_library_root_path=None):
|
|
|
- if itunes_library_path:
|
|
|
- import_itunes_play_count(
|
|
|
- itunes_library_path,
|
|
|
- itunes_library_root_url,
|
|
|
- itunes_library_root_path,
|
|
|
- )
|
|
|
+def symuid(path):
|
|
|
if os.path.isdir(path):
|
|
|
for dirpath, dirnames, filenames in os.walk(path):
|
|
|
for filename in filenames:
|
|
@@ -190,17 +82,6 @@ def _init_argparser():
|
|
|
import argparse
|
|
|
argparser = argparse.ArgumentParser(description=None)
|
|
|
argparser.add_argument('path')
|
|
|
- argparser.add_argument('--itunes-library-path')
|
|
|
- argparser.add_argument(
|
|
|
- '--itunes-library-root-url',
|
|
|
- default='file://localhost/',
|
|
|
- help='(default: %(default)r)',
|
|
|
- )
|
|
|
- argparser.add_argument(
|
|
|
- '--itunes-library-root-path',
|
|
|
- default='',
|
|
|
- help='(default: %(default)r)',
|
|
|
- )
|
|
|
return argparser
|
|
|
|
|
|
|