ITunes Playlists Importieren: Unterschied zwischen den Versionen
imported>Raymond →Scripte: typos |
imported>Raymond →Voraussetzungen: typo |
||
(2 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt) | |||
Zeile 14: | Zeile 14: | ||
'''WARNUNG: die bestehenden "shared" playlists werden automatisch gelöscht''' | '''WARNUNG: die bestehenden "shared" playlists werden automatisch gelöscht''' | ||
Es funktioniert durch ein Python | Es funktioniert durch ein Python Skript, das aus der iTunes XML Datei eine Vielzahl M3Us macht. | ||
Die playlists werden nur geändert, wenn sich das Datum der iTunes XML Datei ändert. | Die playlists werden nur geändert, wenn sich das Datum der iTunes XML Datei ändert. | ||
Zeile 23: | Zeile 23: | ||
#ein nützliches Python script um das iTunes XML in M3Us umzuwandeln: 'itunes2m3u.py' (siehe unten) | #ein nützliches Python script um das iTunes XML in M3Us umzuwandeln: 'itunes2m3u.py' (siehe unten) | ||
Die iTunes Musiklibrary muss auf der DiskStation gespeichert sein. Genauso muss die 'iTunes Music Library.xml' vorhanden sein. Die ist in Windows | Die iTunes Musiklibrary muss auf der DiskStation gespeichert sein. Genauso muss die 'iTunes Music Library.xml' vorhanden sein. Die ist in Windows übrigens in "Eigene Musik\iTunes" zu finden. | ||
== Installation == | == Installation == | ||
Zeile 42: | Zeile 42: | ||
= Skripte = | = Skripte = | ||
Da dieses wiki keine sh/py | Da dieses wiki keine sh/py Anhänge erlaubt, die Skripte der Einfachheit halber hier: | ||
== updateplaylists.sh == | == updateplaylists.sh == |
Aktuelle Version vom 7. Januar 2013, 21:27 Uhr
Oft will man iTunes Musik und Playlists auf die Synology kopieren und von der Synology auch abspielen. Zahlreiche Foreneinträge fragen nach dieser Möglichkeit. Da im Englischen Forum nicht so leicht zu editieren, trage ichs mal hier ein.
Ziel
Voraussetzung ist, dass die Musiksammlung primär in iTunes organisiert wird und regelmäßig (etwa per rsync) auf die Synology kopiert wird um sie von dort weiter per upnp oder ähnlichem zu verbreiten. Das Problem das dabei auftritt, ist, dass es nicht so einfach ist, die wohlgewarteten Playlists auf upnp zu übernehmen.
Achtung:
- nur für Mutige
- die bereits ipkg installiert haben
- ein wenig shell-scripten können
- und ihre eigenen Playlisten aus Audio Station vorher gebackuped haben
Ziel des Setup ist es, den Standardpaketen - insbesondere "Audio Station" - die Playlisten als m3u unterzujubeln. WARNUNG: die bestehenden "shared" playlists werden automatisch gelöscht
Es funktioniert durch ein Python Skript, das aus der iTunes XML Datei eine Vielzahl M3Us macht. Die playlists werden nur geändert, wenn sich das Datum der iTunes XML Datei ändert.
Voraussetzungen
- ipkg - siehe [IPKG]
- python - dank ipkg per 'ipkg install python' leicht machbar
- mein selbstgestricktes shell script 'updateplaylists.sh' (siehe unten)
- ein nützliches Python script um das iTunes XML in M3Us umzuwandeln: 'itunes2m3u.py' (siehe unten)
Die iTunes Musiklibrary muss auf der DiskStation gespeichert sein. Genauso muss die 'iTunes Music Library.xml' vorhanden sein. Die ist in Windows übrigens in "Eigene Musik\iTunes" zu finden.
Installation
- updateplaylists.sh in einen folder kopieren. Ich schlage /opt/sbin vor, aber das kann man beliebig wählen
- itunes2m3u.py in einen folder kopieren. Wieder ist /opt/sbin ein guter ort
- in updateplaylists.sh alle Konfigurationsvariablen ändern. Absolute Pfade in der Konfiguration ftw.
Kurzes Gebet, danach testen:
myserver>./updateplaylists.sh cat: updateplaylists_oldlibrarydate: No such file or directory rm: cannot remove m3u, not found Parsing /volume1/music/iTunes/iTunesMusic/iTunes Music Library.xml... done
Die zwei Meldungen sind mit dem cat und rm sind OK. War ich zu faul, es schöner zu machen. Nach dem Durchlauf sollten die Playlists generiert worden sein. Wenn sie nicht funktionieren - einfach in die Listen hineinschauen, man vertippt sich leicht mit den Pfadangaben.
Jetzt noch in /etc/crontab eine Zeile hinzufügen. Das Skript macht nur was, wenn sich das Datum der iTunes XML Datei ändert, kann also ruhig alle 2 Stunden laufen.
Skripte
Da dieses wiki keine sh/py Anhänge erlaubt, die Skripte der Einfachheit halber hier:
updateplaylists.sh
#!/bin/ash # CONFIGURE THIS: # location of the xml file LIBRARYFILE="/volume1/music/iTunes/iTunesMusic/iTunes Music Library.xml" # location of the MP3s. This folder must have the many many subfolders iTunes manages LIBRARYPATH="/volume1/music/iTunes/iTunesMusic/" # location of itunes2m3u.py ITUNES2M3U="/volume1/homes/leobard/bin/itunes2m3u.py" # destination folder of the m3u. # WARNING: ALL PLAYLISTS IN THIS FOLDER WILL BE DELETED!!! PLAYLISTFOLDER="/volume1/music/playlists" cd $PLAYLISTFOLDER # check changedate, I keep a copy of the last stamp somewhere OLDDATE=`cat updateplaylists_oldlibrarydate` CURDATE=`stat -c %Y "$LIBRARYFILE"` # is the old filedate different from the new one? if [ "$OLDDATE" != "$CURDATE" ]; then # remove the playlists, just to be sure rm *.m3u # recreate them from itunes python $ITUNES2M3U -d "$LIBRARYPATH" "$LIBRARYFILE" # CONFIGURE THIS # remove the big ones I don't need anyway to speed up updating them in the database in mediatomb database rm Mediathek.m3u rm Musik.m3u rm radio-base.m3u # keep the new changedate, also marking that this run was successful stat -c %Y "$LIBRARYFILE" > updateplaylists_oldlibrarydate echo "updated all playlists." else # no changes exit 0 fi
itunes2m3u.py
#!/usr/bin/python # # Converts an Apple iTunes Music Library.xml file into a set of .m3u # playlists. # # Copyright (C) 2006 Mark Huang <mark.l.huang@gmail.com> # License is GPL # # $Id: itunes2m3u.py,v 1.1 2006/07/05 05:24:42 mlhuang Exp $ # import os import sys import getopt import xml.sax.handler import pprint import base64 import datetime import urlparse import codecs import array import re # Defaults # Encoding of track and file names in .m3u files. Escaped 8-bit # character codes in URIs are NOT encoded, they are written in raw # binary mode. encoding = "utf-8" class PropertyList(xml.sax.handler.ContentHandler): """ Parses an Apple iTunes Music Library.xml file into a dictionary. """ def __init__(self, file = None): # Root object self.plist = None # Names of parent elements self.parents = [None] # Dicts can be nested self.dicts = [] # So can arrays self.arrays = [] # Since dicts can be nested, we have to keep a queue of the # current outstanding keys whose values we have yet to set. self.keys = [] # Accumulated CDATA self.cdata = "" # Open file if type(file) == str: file = open(file, 'r') # Parse it parser = xml.sax.make_parser() parser.setContentHandler(self) parser.parse(file) def __str__(self): return pprint.pformat(self.plist) def __getitem__(self, name): if type(self.plist) == dict: return self.plist[name] else: return self.plist def startElement(self, name, attrs): if name == "dict": self.dicts.append({}) elif name == "array": self.arrays.append([]) else: self.cdata = "" self.parents.append(name) def endElement(self, name): last = self.parents.pop() assert last == name value = None if name == "dict": value = self.dicts.pop() elif name == "key": if self.keys and self.keys[-1] == "Tracks": # Convert track keys to integer self.keys.append(int(self.cdata.strip())) else: self.keys.append(self.cdata.strip()) elif name == "array": value = self.arrays.pop() elif name == "data": # Contents interpreted as Base-64 encoded value = base64.b64decode(self.cdata.strip()) elif name == "date": # Contents should conform to a subset of ISO 8601 (in # particular, YYYY '-' MM '-' DD 'T' HH ':' MM ':' SS 'Z'. # Smaller units may be omitted with a loss of precision) year = month = day = hour = minutes = seconds = 0 try: (date, time) = self.cdata.strip().split('T') parts = date.split('-') if len(parts) >= 1: year = int(parts[0]) if len(parts) >= 2: month = int(parts[1]) if len(parts) >= 3: day = int(parts[2]) time = time.replace('Z', '') parts = time.split(':') if len(parts) >= 1: hour = int(parts[0]) if len(parts) >= 2: minutes = int(parts[1]) if len(parts) >= 3: seconds = int(parts[2]) except: pass value = datetime.datetime(year, month, day, hour, minutes, seconds) elif name == "real": # Contents should represent a floating point number # matching ("+" | "-")? d+ ("."d*)? ("E" ("+" | "-") d+)? # where d is a digit 0-9. value = float(self.cdata.strip()) elif name == "integer": # Contents should represent a (possibly signed) integer # number in base 10 value = int(self.cdata.strip()) elif name == "string": value = self.cdata.strip() elif name == "true": # Boolean constant true value = True elif name == "false": # Boolean constant false value = False if self.parents[-1] == "plist": self.plist = value elif self.parents[-1] == "dict" and name != "key": if self.dicts and self.keys: key = self.keys.pop() self.dicts[-1][key] = value elif self.parents[-1] == "array": if self.arrays: self.arrays[-1].append(value) def characters(self, content): self.cdata += content def writeurl(s, fileobj, encoding = "utf-8"): """ Write a URI to the specified file object using the specified encoding. Escaped 8-bit character codes in URIs are NOT encoded, they are written in raw binary mode. """ skip = 0 for i, c in enumerate(s): if skip: skip -= 1 continue if c == '%': # Write 8-bit ASCII character codes in raw binary mode try: a = array.array('B', [int(s[i+1:i+3], 16)]) a.tofile(fileobj) skip = 2 continue except IndexError: pass except ValueError: pass # Write everything else in the specified encoding fileobj.write(c.encode(encoding)) def usage(): print """ Usage: %s [OPTION]... [FILE] Options: -e, --encoding=ENCODING Use specified encoding for track and file names (default: %s) -d, --directory=DIR Replace path to Music Library with specified path -h, --help This message """.lstrip() % (sys.argv[0], encoding) sys.exit(1) def main(): global encoding directory = None if len(sys.argv) < 1: usage() try: (opts, argv) = getopt.getopt(sys.argv[1:], "e:d:h", ["encoding=", "directory=", "help"]) except getopt.GetoptError, e: print "Error: " + e.msg usage() for (opt, optval) in opts: if opt == "-e" or opt == "--encoding": encoding = optval if opt == "-d" or opt == "--directory": directory = optval else: usage() print "Parsing " + argv[0] + "...", sys.stdout.flush() plist = PropertyList(argv[0]) print "done" (scheme, netloc, music_folder_path, params, query, fragment) = \ urlparse.urlparse(plist['Music Folder']) for playlist in plist['Playlists']: if not playlist.has_key('Playlist Items'): continue try: filename = playlist['Name'] + ".m3u" m3u = open(filename, mode = "wb") m3u.write("#EXTM3U" + os.linesep) except: # Try to continue continue tracks = 0 for item in playlist['Playlist Items']: try: track = plist['Tracks'][item['Track ID']] seconds = track['Total Time'] / 1000 m3u.write("#EXTINF:" + "%d" % seconds + ",") m3u.write(track['Name'].encode(encoding)) m3u.write(os.linesep) (scheme, netloc, path, params, query, fragment) = \ urlparse.urlparse(track['Location']) if directory is not None: path = path.replace(music_folder_path, directory) writeurl(path, m3u, encoding) m3u.write(os.linesep) tracks += 1 print filename + ": %d tracks\r" % tracks, except: # Try to continue continue print m3u.close() if __name__ == "__main__": main()