add rhythmbox backend
This commit is contained in:
parent
1f2dd79517
commit
304675110a
6 changed files with 129 additions and 45 deletions
16
bin/0xcd
16
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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -6,6 +6,7 @@ import os
|
|||
import re
|
||||
from urllib import unquote
|
||||
from threading import Thread
|
||||
import sys
|
||||
|
||||
from plistlib import readPlist
|
||||
|
||||
|
@ -16,6 +17,9 @@ class iTunes(object):
|
|||
t = Thread(target=self.parse_xml, args=[])
|
||||
t.start()
|
||||
|
||||
def __repr__(self):
|
||||
return self.xml
|
||||
|
||||
def parse_xml(self):
|
||||
self.library = readPlist(self.xml)
|
||||
for id in self.library['Tracks']:
|
||||
|
@ -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
|
||||
|
|
67
oxcd/rhythmbox.py
Normal file
67
oxcd/rhythmbox.py
Normal file
|
@ -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
|
|
@ -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')
|
||||
|
|
|
@ -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(/\[(.+)\]$/, '<span class="OxLight">($1)</span>');
|
||||
|
@ -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);
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue