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)
|
sys.path.insert(0, root)
|
||||||
|
|
||||||
import oxcd
|
import oxcd
|
||||||
|
import oxcd.itunes
|
||||||
|
import oxcd.rhythmbox
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
parser = OptionParser()
|
parser = OptionParser()
|
||||||
parser.add_option('-p', '--port', dest='port', help='port', default=2681)
|
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()
|
(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()
|
parser.print_help()
|
||||||
sys.exit()
|
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__
|
from version import __version__
|
||||||
|
|
||||||
def itunes_path():
|
def main(port, backend):
|
||||||
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):
|
|
||||||
base = os.path.abspath(os.path.dirname(__file__))
|
base = os.path.abspath(os.path.dirname(__file__))
|
||||||
print 'loading', itunes
|
print 'loading', backend
|
||||||
backend = iTunes(itunes)
|
|
||||||
root = Server(base, backend)
|
root = Server(base, backend)
|
||||||
site = Site(root)
|
site = Site(root)
|
||||||
reactor.listenTCP(port, site)
|
reactor.listenTCP(port, site)
|
||||||
|
|
|
@ -6,6 +6,7 @@ import os
|
||||||
import re
|
import re
|
||||||
from urllib import unquote
|
from urllib import unquote
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
import sys
|
||||||
|
|
||||||
from plistlib import readPlist
|
from plistlib import readPlist
|
||||||
|
|
||||||
|
@ -16,6 +17,9 @@ class iTunes(object):
|
||||||
t = Thread(target=self.parse_xml, args=[])
|
t = Thread(target=self.parse_xml, args=[])
|
||||||
t.start()
|
t.start()
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return self.xml
|
||||||
|
|
||||||
def parse_xml(self):
|
def parse_xml(self):
|
||||||
self.library = readPlist(self.xml)
|
self.library = readPlist(self.xml)
|
||||||
for id in self.library['Tracks']:
|
for id in self.library['Tracks']:
|
||||||
|
@ -37,6 +41,7 @@ class iTunes(object):
|
||||||
]
|
]
|
||||||
for t in self.library['Tracks']:
|
for t in self.library['Tracks']:
|
||||||
track = self.library['Tracks'][t]
|
track = self.library['Tracks'][t]
|
||||||
|
if track.get('kind') in ('MPEG audio file') and not track.get('podcast'):
|
||||||
item = {}
|
item = {}
|
||||||
for key in keys:
|
for key in keys:
|
||||||
item[key] = track.get({
|
item[key] = track.get({
|
||||||
|
@ -50,7 +55,22 @@ class iTunes(object):
|
||||||
re.sub('([A-Z])', ' \\1', key)
|
re.sub('([A-Z])', ' \\1', key)
|
||||||
)
|
)
|
||||||
), None)
|
), None)
|
||||||
|
if key == 'duration' and item[key]:
|
||||||
|
item[key] = item[key] / 1000
|
||||||
if item[key] == None:
|
if item[key] == None:
|
||||||
del item[key]
|
del item[key]
|
||||||
tracks.append(item)
|
tracks.append(item)
|
||||||
return tracks
|
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/'):
|
if request.path.startswith('/track/'):
|
||||||
track_id = request.path.split('/')[-1].split('.')[0]
|
track_id = request.path.split('/')[-1].split('.')[0]
|
||||||
track = self.backend.library['Tracks'].get(track_id)
|
path = self.backend.track(track_id)
|
||||||
if track:
|
if path:
|
||||||
path = track['Location']
|
|
||||||
if os.path.exists(path):
|
if os.path.exists(path):
|
||||||
request.headers['Access-Control-Allow-Origin'] = '*'
|
request.headers['Access-Control-Allow-Origin'] = '*'
|
||||||
f = File(path, 'audio/mpeg')
|
f = File(path, 'audio/mpeg')
|
||||||
|
|
|
@ -233,7 +233,7 @@ Ox.load('UI', function() {
|
||||||
return $element;
|
return $element;
|
||||||
};
|
};
|
||||||
app.utils.formatTime = function(duration) {
|
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) {
|
app.utils.formatTitle = function(title) {
|
||||||
return title.replace(/\[(.+)\]$/, '<span class="OxLight">($1)</span>');
|
return title.replace(/\[(.+)\]$/, '<span class="OxLight">($1)</span>');
|
||||||
|
@ -245,7 +245,6 @@ Ox.load('UI', function() {
|
||||||
var data = {playlists: [], tracks: []};
|
var data = {playlists: [], tracks: []};
|
||||||
oxcd.api.library(function(result) {
|
oxcd.api.library(function(result) {
|
||||||
Ox.forEach(result.data.tracks, function(track) {
|
Ox.forEach(result.data.tracks, function(track) {
|
||||||
if (track.kind == 'MPEG audio file' && !track.podcast) {
|
|
||||||
app.site.columns.map(function(column) {
|
app.site.columns.map(function(column) {
|
||||||
return column.id;
|
return column.id;
|
||||||
}).forEach(function(key) {
|
}).forEach(function(key) {
|
||||||
|
@ -254,7 +253,6 @@ Ox.load('UI', function() {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
data.tracks.push(track);
|
data.tracks.push(track);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
callback(data);
|
callback(data);
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue