From 304675110a3ce3834600b5c2386aa0a4d72cf49a Mon Sep 17 00:00:00 2001 From: j <0x006A@0x2620.org> Date: Sat, 8 Sep 2012 19:37:51 +0200 Subject: [PATCH] add rhythmbox backend --- bin/0xcd | 16 ++++++++-- oxcd/__init__.py | 14 ++------- oxcd/itunes.py | 52 ++++++++++++++++++++++---------- oxcd/rhythmbox.py | 67 +++++++++++++++++++++++++++++++++++++++++ oxcd/server.py | 5 ++- oxcd/static/js/index.js | 20 ++++++------ 6 files changed, 129 insertions(+), 45 deletions(-) create mode 100644 oxcd/rhythmbox.py diff --git a/bin/0xcd b/bin/0xcd index eed319b..55d9ffc 100755 --- a/bin/0xcd +++ b/bin/0xcd @@ -11,14 +11,24 @@ if os.path.exists(os.path.join(root, 'oxcd')): sys.path.insert(0, root) import oxcd +import oxcd.itunes +import oxcd.rhythmbox if __name__ == '__main__': parser = OptionParser() parser.add_option('-p', '--port', dest='port', help='port', default=2681) - parser.add_option('-i', '--itunes', dest='itunes', help='iTunes xml', default=oxcd.itunes_path()) + parser.add_option('-i', '--itunes', dest='itunes', help='iTunes xml', default=oxcd.itunes.path()) + parser.add_option('-r', '--rhythmbox', dest='rhythmbox', help='Rhythmbox xml', default=oxcd.rhythmbox.path()) (opts, args) = parser.parse_args() - if None in (opts.port, opts.itunes): + print (opts.port, ) + print not filter(None, (opts.itunes, opts.rhythmbox)) + + if None in (opts.port, ) or not filter(None, (opts.itunes, opts.rhythmbox)): parser.print_help() sys.exit() - oxcd.main(opts.port, opts.itunes) + if opts.itunes: + backend = oxcd.itunes.iTunes(opts.itunes) + elif opts.rhythmbox: + backend = oxcd.rhythmbox.Rhythmbox(opts.rhythmbox) + oxcd.main(opts.port, backend) diff --git a/oxcd/__init__.py b/oxcd/__init__.py index b99aa78..0f7b015 100644 --- a/oxcd/__init__.py +++ b/oxcd/__init__.py @@ -13,19 +13,9 @@ import api from version import __version__ -def itunes_path(): - if sys.platform == 'darwin': - path = os.path.expanduser('~/Music/iTunes/iTunes Music Library.xml') - elif sys.platform == 'win32': - path = os.path.expanduser('~\\Music\\iTunes\\iTunes Music Library.xml') - else: - path = None - return path - -def main(port, itunes): +def main(port, backend): base = os.path.abspath(os.path.dirname(__file__)) - print 'loading', itunes - backend = iTunes(itunes) + print 'loading', backend root = Server(base, backend) site = Site(root) reactor.listenTCP(port, site) diff --git a/oxcd/itunes.py b/oxcd/itunes.py index 3d046e6..f2df02c 100644 --- a/oxcd/itunes.py +++ b/oxcd/itunes.py @@ -6,6 +6,7 @@ import os import re from urllib import unquote from threading import Thread +import sys from plistlib import readPlist @@ -15,6 +16,9 @@ class iTunes(object): self.xml = xml t = Thread(target=self.parse_xml, args=[]) t.start() + + def __repr__(self): + return self.xml def parse_xml(self): self.library = readPlist(self.xml) @@ -37,20 +41,36 @@ class iTunes(object): ] for t in self.library['Tracks']: track = self.library['Tracks'][t] - item = {} - for key in keys: - item[key] = track.get({ - 'id': 'Track ID', - 'duration': 'Total Time', - }.get( - key, - re.sub( - '^(.)', - lambda m: m.groups(0)[0].capitalize(), - re.sub('([A-Z])', ' \\1', key) - ) - ), None) - if item[key] == None: - del item[key] - tracks.append(item) + if track.get('kind') in ('MPEG audio file') and not track.get('podcast'): + item = {} + for key in keys: + item[key] = track.get({ + 'id': 'Track ID', + 'duration': 'Total Time', + }.get( + key, + re.sub( + '^(.)', + lambda m: m.groups(0)[0].capitalize(), + re.sub('([A-Z])', ' \\1', key) + ) + ), None) + if key == 'duration' and item[key]: + item[key] = item[key] / 1000 + if item[key] == None: + del item[key] + tracks.append(item) return tracks + + def track(self, track_id): + track = self.library['Tracks'].get(track_id) + return track and track['Location'] or None + +def path(): + if sys.platform == 'darwin': + path = os.path.expanduser('~/Music/iTunes/iTunes Music Library.xml') + elif sys.platform == 'win32': + path = os.path.expanduser('~\\Music\\iTunes\\iTunes Music Library.xml') + else: + path = None + return path diff --git a/oxcd/rhythmbox.py b/oxcd/rhythmbox.py new file mode 100644 index 0000000..daac840 --- /dev/null +++ b/oxcd/rhythmbox.py @@ -0,0 +1,67 @@ +# encoding: utf-8 +# vi:si:et:sw=4:sts=4:ts=4 +from __future__ import with_statement, division + +import os +import re +from urllib import unquote +from threading import Thread +import sys + +import ox + +class Rhythmbox(object): + tracks = [] + locations = {} + def __init__(self, xml): + self.xml = os.path.expanduser(xml) + t = Thread(target=self.parse_xml, args=[]) + t.start() + + def __repr__(self): + return self.xml + + def parse_xml(self): + from lxml import etree + with open(self.xml) as f: + self.library = etree.fromstring(f.read()) + self.tracks = self.load_tracks() + + def load_tracks(self): + tracks = [] + keys = [ + 'id', 'name', 'artist', 'album', 'kind', 'year', 'duration', 'size', + 'sortArtist', 'albumArtist', 'sortAlbumArtist', 'compliation', + ] + key_map = { + 'title': 'name', + 'file-size': 'size', + } + for e in self.library.xpath('//entry[@type="song"]'): + item = {} + path = e.find('location').text.split('file://')[-1] + path = unquote(path) + item['id'] = ox.oshash(path) + self.locations[item['id']] = path + for c in e: + if c.tag in keys + key_map.keys(): + key = key_map.get(c.tag, c.tag) + if key == 'duration': + item[key] = int(c.text) + else: + item[key] = c.text + info = ox.avinfo(path) + metadata = info.get('metadata', {}) + if 'date' in metadata: + item['year'] = metadata['date'] + tracks.append(item) + return tracks + + def track(self, track_id): + return self.locations.get(track_id) + +def path(): + path = os.path.expanduser('~/.local/share/rhythmbox/rhythmdb.xml') + if not os.path.exists(path): + path = None + return path diff --git a/oxcd/server.py b/oxcd/server.py index 5ddf78d..eb6d317 100644 --- a/oxcd/server.py +++ b/oxcd/server.py @@ -168,9 +168,8 @@ class Server(Resource): if request.path.startswith('/track/'): track_id = request.path.split('/')[-1].split('.')[0] - track = self.backend.library['Tracks'].get(track_id) - if track: - path = track['Location'] + path = self.backend.track(track_id) + if path: if os.path.exists(path): request.headers['Access-Control-Allow-Origin'] = '*' f = File(path, 'audio/mpeg') diff --git a/oxcd/static/js/index.js b/oxcd/static/js/index.js index a11ef19..8512fb6 100644 --- a/oxcd/static/js/index.js +++ b/oxcd/static/js/index.js @@ -233,7 +233,7 @@ Ox.load('UI', function() { return $element; }; app.utils.formatTime = function(duration) { - return Ox.formatDuration(duration / 1000).replace(/^00:/, '').replace(/^0/, ''); + return Ox.formatDuration(duration).replace(/^00:/, '').replace(/^0/, ''); }; app.utils.formatTitle = function(title) { return title.replace(/\[(.+)\]$/, '($1)'); @@ -245,16 +245,14 @@ Ox.load('UI', function() { var data = {playlists: [], tracks: []}; oxcd.api.library(function(result) { Ox.forEach(result.data.tracks, function(track) { - if (track.kind == 'MPEG audio file' && !track.podcast) { - app.site.columns.map(function(column) { - return column.id; - }).forEach(function(key) { - if (!track[key]) { - track[key] = ''; - } - }) - data.tracks.push(track); - } + app.site.columns.map(function(column) { + return column.id; + }).forEach(function(key) { + if (!track[key]) { + track[key] = ''; + } + }) + data.tracks.push(track); }); callback(data); });