Skip to content
Branch: master
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
executable file 188 lines (151 sloc) 6.55 KB
#!/usr/bin/env python3
import os
import sys
from xml.etree import ElementTree
help_message = '''Usage: Close Rhythmbox, then type: itunes2rhythmbox
If you are to move or sync your whole iTunes library to Rhythmbox, This script
will help you to convert iTunes playlist to Rhythmbox style playlist, and
import the playlist to Rhythmbox directly.
Before using this script, you should have:
- an iTunes folder copied directly from your Windows/Mac computer(with the
iTunes files already consolidated: in iTunes, click File -> Library ->
Organize Library -> Consolidate files -> OK), or just the `iTunes Media`
Folder located at `iTunes/`
- a copy of `iTunes Music Library.xml`, which usually located at `iTunes/`
You should modify this script configurations at start of the script, and the
meaning of entries will be described as follows:
- target_location: The `iTunes Media` folder path
- rhythmbox_playlist: The rhythmbox playlist xml path
- iTunes_db: `iTunes Music Library.xml` path
- skip_list: playlists which you do not want to convert to your Rhythmbox
- source_itunes_music_folder: do not modify it
# Configurations
target_location = '~/Music/iTunes/iTunes Media/'
rhythmbox_playlist = '~/.local/share/rhythmbox/playlists.xml'
itunes_db = '~/Music/iTunes/iTunes Music Library.xml'
skip_list = [
'TV Shows',
'Not Classified',
'Not Favorate',
'Not Rated',
'Voice Memos',
quote_safe_char = "!$'()*+,-/=@_~"
source_itunes_music_folder = None
# End of configuration
__author__ = "Charles Xu"
__github__ = ""
__girlfriend__ = "pang"
from urllib import quote, unquote
except ImportError:
from urllib.parse import quote, unquote
def itunes_quote(string):
string = quote(string, safe=quote_safe_char)
string = string.replace('%26', '&')
return string
if not (target_location.startswith('http://') or target_location.startswith('file://') or target_location.startswith('https://')):
target_location = 'file://' + \
if not target_location.endswith('/'):
target_location += '/'
rhythmbox_playlist, itunes_db = os.path.abspath(os.path.expanduser(rhythmbox_playlist)), os.path.abspath(os.path.expanduser(itunes_db))
def xml2dict(xml_path):
xml_obj = ElementTree.parse(xml_path)
root = xml_obj.getroot()
def traverse(root):
children = root.getchildren()
return {root.tag: {'children': [traverse(child) for child in children], 'attrib': root.attrib, 'text': root.text}}
return traverse(root)
def parse_itunesdb(xml_path):
xml_obj = ElementTree.parse(xml_path)
root = xml_obj.getroot().getchildren()[0]
def traverse(root):
children = root.getchildren()
handlers = {
'dict': lambda root, children: {traverse(key): traverse(value) for key, value in zip(children[::2], children[1::2])} if len(children) % 2 == 0 else ValueError('dict must be paired'),
'key': lambda root, children: root.text,
'string': lambda root, children: root.text,
'date': lambda root, children: root.text,
'data': lambda root, children: root.text,
'integer': lambda root, children: root.text,
'true': lambda root, children: True,
'false': lambda root, children: False,
'array': lambda root, children: [traverse(child) for child in children],
if root.tag in handlers:
return handlers[root.tag](root, children)
raise KeyError(root.tag)
return traverse(root)
def playlist2rbxml(playlists, destination):
playlists [{'name': str, 'item': [str]}]
head = '<?xml version="1.0"?>\n<rhythmdb-playlists>\n'
playlist_head = '<playlist name="{}" show-browser="true" browser-position="180" search-type="search-match" type="static">\n'
song = '<location>{}</location>\n'
playlist_tail = '</playlist>\n'
tail = '<playlist name="Play Queue" show-browser="false" browser-position="180" search-type="search-match" type="queue"/>\n</rhythmdb-playlists>\n'
with open(destination, 'w') as f:
for playlist in playlists:
for track in playlist['item']:
def convert_music_path(music_location, source_itunes_music_folder, target_location):
data = music_location.replace(
source_itunes_music_folder, target_location, 1).split(':', 1)
if len(data) == 1:
return itunes_quote(unquote(data[0]))
return ':'.join((data[0], itunes_quote(unquote(data[1]))))
def get_playlist(itunes_dict, skip_list=skip_list, convert_function=convert_music_path, **convert_args):
tracks, playlists = itunes_dict['Tracks'], itunes_dict['Playlists']
return [{'name': playlist['Name'], 'item': [convert_function(tracks[track['Track ID']]['Location'], **convert_args) for track in playlist['Playlist Items']]} for playlist in playlists if playlist['Name'] not in skip_list]
def main():
rhythmbox_pid = list(map(eval, os.popen("ps -A|grep rhythmbox|awk '{print $1}'").readlines()))
if rhythmbox_pid:
if '-f' in sys.argv or '--force' in sys.argv:
prompt = 'y'
prompt = input('rhyhtmbox process found: {}, kill it?[Y/n]:'.format(rhythmbox_pid)).lower()
if prompt == 'n':
elif prompt == 'y' or prompt == '':
for pid in rhythmbox_pid:
os.kill(pid, 2)
print('rhythmbox quitted')
print('You should input "y" or "n"')
itunes_dict = parse_itunesdb(itunes_db)
global source_itunes_music_folder
if not source_itunes_music_folder:
source_itunes_music_folder = itunes_dict['Music Folder']
playlists = get_playlist(
itunes_dict, skip_list=skip_list,
source_itunes_music_folder=source_itunes_music_folder, target_location=target_location)
playlist2rbxml(playlists, rhythmbox_playlist)
print(len(playlists), 'playlists imported')
if __name__ == '__main__':
if 'help' in sys.argv or '-h' in sys.argv or '--help' in sys.argv or not os.path.isfile(itunes_db):
You can’t perform that action at this time.