|
a/src/mediaserver/cdplugins/uprcl/uprclutils.py |
|
b/src/mediaserver/cdplugins/uprcl/uprclutils.py |
1 |
from __future__ import print_function
|
1 |
from __future__ import print_function
|
2 |
|
2 |
|
3 |
import sys
|
3 |
import sys
|
4 |
import posixpath
|
4 |
import posixpath
|
5 |
import urllib
|
5 |
import urllib
|
|
|
6 |
import os
|
6 |
|
7 |
|
7 |
audiomtypes = frozenset([
|
8 |
audiomtypes = frozenset([
|
8 |
'audio/mpeg',
|
9 |
'audio/mpeg',
|
9 |
'application/x-flac',
|
10 |
'application/x-flac',
|
10 |
'application/ogg',
|
11 |
'application/ogg',
|
11 |
'audio/aac',
|
12 |
'audio/aac',
|
12 |
'audio/mp4',
|
13 |
'audio/mp4',
|
13 |
'audio/x-aiff',
|
14 |
'audio/x-aiff',
|
14 |
'audio/x-wav'
|
15 |
'audio/x-wav',
|
|
|
16 |
'inode/directory'
|
15 |
])
|
17 |
])
|
16 |
|
18 |
|
|
|
19 |
upnp2rclfields = {'upnp:album': 'album',
|
|
|
20 |
'releasedate' : 'date',
|
|
|
21 |
'upnp:originalTrackNumber' : 'tracknumber',
|
|
|
22 |
'upnp:artist' : 'artist',
|
|
|
23 |
'upnp:genre' : 'genre',
|
|
|
24 |
'res:mime' : 'mtype',
|
|
|
25 |
'duration' : 'duration',
|
|
|
26 |
'res:samplefreq' : 'sample_rate'
|
|
|
27 |
}
|
|
|
28 |
|
17 |
def rcldoctoentry(id, pid, httphp, pathprefix, doc):
|
29 |
def rcldoctoentry(id, pid, httphp, pathprefix, doc):
|
18 |
"""
|
30 |
"""
|
19 |
Transform a Doc objects into the format expected by the parent
|
31 |
Transform a Doc objects into the format expected by the parent
|
20 |
|
32 |
|
21 |
Args:
|
33 |
Args:
|
|
... |
|
... |
36 |
configured host:port and pathprefix arguments and track Id:
|
48 |
configured host:port and pathprefix arguments and track Id:
|
37 |
TBD
|
49 |
TBD
|
38 |
http://host:port/pathprefix/track?version=1&trackId=<trackid>
|
50 |
http://host:port/pathprefix/track?version=1&trackId=<trackid>
|
39 |
|
51 |
|
40 |
"""
|
52 |
"""
|
41 |
uplog("rcldoctoentry: pid %s id %s httphp %s pathprefix %s" %
|
53 |
uplog("rcldoctoentry: pid %s id %s mtype %s" %
|
42 |
(pid, id, httphp, pathprefix))
|
54 |
(pid, id, doc.mtype))
|
43 |
|
55 |
|
44 |
li = {}
|
56 |
li = {}
|
45 |
if doc.mtype not in audiomtypes:
|
57 |
if doc.mtype not in audiomtypes:
|
46 |
return li
|
58 |
return li
|
47 |
|
59 |
|
48 |
li['pid'] = pid
|
60 |
li['pid'] = pid
|
49 |
li['id'] = id
|
61 |
li['id'] = id
|
50 |
li['tp'] = 'it'
|
62 |
li['tp'] = 'ct' if doc.mtype == 'inode/directory' else 'it'
|
51 |
# Why no dc.title??
|
63 |
# Why no dc.title??
|
52 |
li['tt'] = doc.title
|
64 |
li['tt'] = doc.title
|
53 |
|
65 |
|
54 |
# TBD
|
66 |
# TBD
|
55 |
li['upnp:class'] = 'object.item.audioItem.musicTrack'
|
67 |
li['upnp:class'] = 'object.item.audioItem.musicTrack'
|
|
... |
|
... |
70 |
#discnumber=
|
82 |
#discnumber=
|
71 |
#genre=
|
83 |
#genre=
|
72 |
#lyricist=
|
84 |
#lyricist=
|
73 |
#lyrics=
|
85 |
#lyrics=
|
74 |
|
86 |
|
75 |
for oname,dname in [('upnp:album', 'album'), ('releasedate','date'),
|
87 |
for oname,dname in upnp2rclfields.iteritems():
|
76 |
('upnp:originalTrackNumber', 'tracknumber'),
|
|
|
77 |
('upnp:artist', 'artist'), ('upnp:genre', 'genre'),
|
|
|
78 |
('res:mime', 'mtype'), ('duration', 'duration'),
|
|
|
79 |
('res:samplefreq', 'sample_rate')]:
|
|
|
80 |
val = getattr(doc, dname)
|
88 |
val = getattr(doc, dname)
|
81 |
if val:
|
89 |
if val:
|
82 |
li[oname] = val
|
90 |
li[oname] = val
|
83 |
|
91 |
|
84 |
try:
|
92 |
try:
|
|
... |
|
... |
96 |
path = pathprefix + path
|
104 |
path = pathprefix + path
|
97 |
li['uri'] = "http://%s%s" % (httphp, urllib.quote(path))
|
105 |
li['uri'] = "http://%s%s" % (httphp, urllib.quote(path))
|
98 |
uplog("rcldoctoentry: uri: %s" % li['uri'])
|
106 |
uplog("rcldoctoentry: uri: %s" % li['uri'])
|
99 |
return li
|
107 |
return li
|
100 |
|
108 |
|
|
|
109 |
def cmpentries(e1, e2):
|
|
|
110 |
tp1 = e1['tp']
|
|
|
111 |
tp2 = e2['tp']
|
|
|
112 |
isct1 = tp1 == 'ct'
|
|
|
113 |
isct2 = tp2 == 'ct'
|
|
|
114 |
|
|
|
115 |
# Containers come before items, and are sorted in alphabetic order
|
|
|
116 |
if isct1 and not isct2:
|
|
|
117 |
return 1
|
|
|
118 |
elif not isct1 and isct2:
|
|
|
119 |
return -1
|
|
|
120 |
elif isct1 and isct2:
|
|
|
121 |
tt1 = e1['tt']
|
|
|
122 |
tt2 = e2['tt']
|
|
|
123 |
if tt1 < tt2:
|
|
|
124 |
return -1
|
|
|
125 |
elif tt1 > tt2:
|
|
|
126 |
return 1
|
|
|
127 |
else:
|
|
|
128 |
return 0
|
|
|
129 |
|
|
|
130 |
# Tracks. Sort by album then directory then track number
|
|
|
131 |
k = 'upnp:album'
|
|
|
132 |
a1 = e1[k] if k in e1 else ""
|
|
|
133 |
a2 = e2[k] if k in e2 else ""
|
|
|
134 |
if a1 < a2:
|
|
|
135 |
return -1
|
|
|
136 |
elif a1 > a2:
|
|
|
137 |
return 1
|
|
|
138 |
|
|
|
139 |
d1 = os.path.dirname(e1['uri'])
|
|
|
140 |
d2 = os.path.dirname(e2['uri'])
|
|
|
141 |
if d1 < d2:
|
|
|
142 |
return -1
|
|
|
143 |
elif d1 > d2:
|
|
|
144 |
return 1
|
|
|
145 |
|
|
|
146 |
k = 'upnp:originalTrackNumber'
|
|
|
147 |
a1 = e1[k] if k in e1 else "0"
|
|
|
148 |
a2 = e2[k] if k in e2 else "0"
|
|
|
149 |
return int(a1) - int(a2)
|
|
|
150 |
|
|
|
151 |
|
101 |
def rclpathtoreal(path, pathprefix, httphp, pathmap):
|
152 |
def rclpathtoreal(path, pathprefix, httphp, pathmap):
|
102 |
path = path.replace(pathprefix, '', 1)
|
153 |
path = path.replace(pathprefix, '', 1)
|
103 |
found = False
|
154 |
found = False
|
104 |
for fsp,htp in pathmap.iteritems():
|
155 |
for fsp,htp in pathmap.iteritems():
|
105 |
if path.startswith(fsp):
|
156 |
if path.startswith(fsp):
|
|
... |
|
... |
107 |
found = True
|
158 |
found = True
|
108 |
if not found:
|
159 |
if not found:
|
109 |
return None
|
160 |
return None
|
110 |
return "http://" + httphp + path
|
161 |
return "http://" + httphp + path
|
111 |
|
162 |
|
112 |
def rcldirentry(id, pid, title, arturi=None, artist=None, upnpclass=None):
|
163 |
def rcldirentry(id, pid, title, arturi=None, artist=None, upnpclass=None,
|
|
|
164 |
searchable='1'):
|
113 |
""" Create container entry in format expected by parent """
|
165 |
""" Create container entry in format expected by parent """
|
114 |
ret = {'id':id, 'pid':pid, 'tt':title, 'tp':'ct', 'searchable':'1'}
|
166 |
ret = {'id':id, 'pid':pid, 'tt':title, 'tp':'ct', 'searchable':searchable}
|
115 |
if arturi:
|
167 |
if arturi:
|
116 |
ret['upnp:albumArtURI'] = arturi
|
168 |
ret['upnp:albumArtURI'] = arturi
|
117 |
if artist:
|
169 |
if artist:
|
118 |
ret['upnp:artist'] = artist
|
170 |
ret['upnp:artist'] = artist
|
119 |
if upnpclass:
|
171 |
if upnpclass:
|
|
... |
|
... |
122 |
ret['upnp:class'] = 'object.container'
|
174 |
ret['upnp:class'] = 'object.container'
|
123 |
return ret
|
175 |
return ret
|
124 |
|
176 |
|
125 |
def uplog(s):
|
177 |
def uplog(s):
|
126 |
print(("%s: %s" % ('uprcl', s)).encode('utf-8'), file=sys.stderr)
|
178 |
print(("%s: %s" % ('uprcl', s)).encode('utf-8'), file=sys.stderr)
|
|
|
179 |
|
|
|
180 |
|
|
|
181 |
# Parse string into (possibly multiword) tokens
|
|
|
182 |
# 'a b "one phrase" c' -> [a, b, 'one phrase', c]
|
|
|
183 |
def stringToStrings(str):
|
|
|
184 |
# States. Note that ESCAPE can only occur inside INQUOTE
|
|
|
185 |
SPACE, TOKEN, INQUOTE, ESCAPE = range(4)
|
|
|
186 |
|
|
|
187 |
tokens = []
|
|
|
188 |
curtok = ""
|
|
|
189 |
state = SPACE;
|
|
|
190 |
|
|
|
191 |
for c in str:
|
|
|
192 |
if c == '"':
|
|
|
193 |
if state == SPACE:
|
|
|
194 |
state = INQUOTE
|
|
|
195 |
elif state == TOKEN:
|
|
|
196 |
curtok += '"'
|
|
|
197 |
elif state == INQUOTE:
|
|
|
198 |
if curtok:
|
|
|
199 |
tokens.append(curtok);
|
|
|
200 |
curtok = ""
|
|
|
201 |
state = SPACE
|
|
|
202 |
elif state == ESCAPE:
|
|
|
203 |
curtok += '"'
|
|
|
204 |
state = INQUOTE
|
|
|
205 |
continue;
|
|
|
206 |
|
|
|
207 |
elif c == '\\':
|
|
|
208 |
if state == SPACE or state == TOKEN:
|
|
|
209 |
curtok += '\\'
|
|
|
210 |
state = TOKEN
|
|
|
211 |
elif state == INQUOTE:
|
|
|
212 |
state = ESCAPE
|
|
|
213 |
elif state == ESCAPE:
|
|
|
214 |
curtok += '\\'
|
|
|
215 |
state = INQUOTE
|
|
|
216 |
continue
|
|
|
217 |
|
|
|
218 |
elif c == ' ' or c == '\t' or c == '\n' or c == '\r':
|
|
|
219 |
if state == SPACE or state == TOKEN:
|
|
|
220 |
if curtok:
|
|
|
221 |
tokens.append(curtok)
|
|
|
222 |
curtok = ""
|
|
|
223 |
state = SPACE
|
|
|
224 |
elif state == INQUOTE or state == ESCAPE:
|
|
|
225 |
curtok += c
|
|
|
226 |
continue;
|
|
|
227 |
|
|
|
228 |
else:
|
|
|
229 |
if state == ESCAPE:
|
|
|
230 |
state = INQUOTE
|
|
|
231 |
elif state == SPACE:
|
|
|
232 |
state = TOKEN
|
|
|
233 |
elif state == TOKEN or state == INQUOTE:
|
|
|
234 |
pass
|
|
|
235 |
curtok += c
|
|
|
236 |
|
|
|
237 |
if state == SPACE:
|
|
|
238 |
pass
|
|
|
239 |
elif state == TOKEN:
|
|
|
240 |
if curtok:
|
|
|
241 |
tokens.append(curtok)
|
|
|
242 |
elif state == INQUOTE or state == ESCAPE:
|
|
|
243 |
raise Exception("Bad string: <" + str + ">")
|
|
|
244 |
|
|
|
245 |
return tokens
|