|
a |
|
b/src/cdplugins/tidal/tidal.py |
|
|
1 |
#!/usr/bin/env python
|
|
|
2 |
# Copyright (C) 2016 J.F.Dockes
|
|
|
3 |
# This program is free software; you can redistribute it and/or modify
|
|
|
4 |
# it under the terms of the GNU General Public License as published by
|
|
|
5 |
# the Free Software Foundation; either version 2 of the License, or
|
|
|
6 |
# (at your option) any later version.
|
|
|
7 |
#
|
|
|
8 |
# This program is distributed in the hope that it will be useful,
|
|
|
9 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
10 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
11 |
# GNU General Public License for more details.
|
|
|
12 |
#
|
|
|
13 |
# You should have received a copy of the GNU General Public License
|
|
|
14 |
# along with this program; if not, write to the
|
|
|
15 |
# Free Software Foundation, Inc.,
|
|
|
16 |
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
|
17 |
|
|
|
18 |
from __future__ import print_function
|
|
|
19 |
|
|
|
20 |
import sys
|
|
|
21 |
import os
|
|
|
22 |
import json
|
|
|
23 |
import re
|
|
|
24 |
import conftree
|
|
|
25 |
import cmdtalkplugin
|
|
|
26 |
import tidalapi
|
|
|
27 |
from tidalapi.models import Album, Artist
|
|
|
28 |
from tidalapi import Quality
|
|
|
29 |
|
|
|
30 |
routes = cmdtalkplugin.Routes()
|
|
|
31 |
processor = cmdtalkplugin.Processor(routes)
|
|
|
32 |
|
|
|
33 |
is_logged_in = False
|
|
|
34 |
|
|
|
35 |
def maybelogin():
|
|
|
36 |
global session
|
|
|
37 |
global quality
|
|
|
38 |
|
|
|
39 |
if is_logged_in:
|
|
|
40 |
return True
|
|
|
41 |
|
|
|
42 |
if "UPMPD_CONFIG" not in os.environ:
|
|
|
43 |
raise Exception("No UPMPD_CONFIG in environment")
|
|
|
44 |
upconfig = conftree.ConfSimple(os.environ["UPMPD_CONFIG"])
|
|
|
45 |
|
|
|
46 |
username = upconfig.get('tidaluser')
|
|
|
47 |
password = upconfig.get('tidalpass')
|
|
|
48 |
qalstr = upconfig.get('tidalquality')
|
|
|
49 |
if not username or not password:
|
|
|
50 |
raise Exception("tidaluser and/or tidalpass not set in configuration")
|
|
|
51 |
|
|
|
52 |
if qalstr == 'lossless':
|
|
|
53 |
quality = Quality.lossless
|
|
|
54 |
elif qalstr == 'high':
|
|
|
55 |
quality = Quality.high
|
|
|
56 |
elif qalstr == 'low':
|
|
|
57 |
quality = Quality.low
|
|
|
58 |
elif not qalstr:
|
|
|
59 |
quality = Quality.high
|
|
|
60 |
else:
|
|
|
61 |
raise Exception("Bad tidal quality string in config. "+
|
|
|
62 |
"Must be lossless/high/low")
|
|
|
63 |
tidalconf = tidalapi.Config(quality)
|
|
|
64 |
session = tidalapi.Session(config=tidalconf)
|
|
|
65 |
session.login(username, password)
|
|
|
66 |
|
|
|
67 |
|
|
|
68 |
|
|
|
69 |
def trackentries(objid, tracks):
|
|
|
70 |
entries = []
|
|
|
71 |
for track in tracks:
|
|
|
72 |
if not track.available:
|
|
|
73 |
continue
|
|
|
74 |
li = {}
|
|
|
75 |
|
|
|
76 |
|
|
|
77 |
li['pid'] = objid
|
|
|
78 |
li['id'] = objid + '$' + "%s"%track.id
|
|
|
79 |
li['tt'] = track.name
|
|
|
80 |
li['uri'] = 'http://s.com/upmpcli-tidal/track?version=1&trackId=%s' % track.id
|
|
|
81 |
li['tp'] = 'it'
|
|
|
82 |
if track.album:
|
|
|
83 |
li['upnp:album'] = track.album.name
|
|
|
84 |
if track.album.image:
|
|
|
85 |
li['upnp:albumArtUri'] = track.album.image
|
|
|
86 |
li['upnp:originalTrackNumber'] = str(track.track_num)
|
|
|
87 |
li['upnp:artist'] = track.artist.name
|
|
|
88 |
li['dc:creator'] = track.artist.name
|
|
|
89 |
li['dc:title'] = track.name
|
|
|
90 |
li['discnumber'] = str(track.disc_num)
|
|
|
91 |
li['duration'] = track.duration
|
|
|
92 |
# track.album.release_date.year if track.album.release_date else None,
|
|
|
93 |
|
|
|
94 |
entries.append(li)
|
|
|
95 |
return entries
|
|
|
96 |
|
|
|
97 |
|
|
|
98 |
def direntry(id, pid, title):
|
|
|
99 |
return {'id': id, 'pid' : pid, 'tt':title, 'tp':'ct'}
|
|
|
100 |
|
|
|
101 |
def direntries(objid, ttidlist):
|
|
|
102 |
content = []
|
|
|
103 |
for tt,id in ttidlist:
|
|
|
104 |
content.append(direntry(objid+'$'+id, objid, tt))
|
|
|
105 |
return content
|
|
|
106 |
|
|
|
107 |
@routes.route('browse')
|
|
|
108 |
def browse(a):
|
|
|
109 |
processor.rclog("browse: [%s]" % a)
|
|
|
110 |
if 'objid' not in a:
|
|
|
111 |
raise Exception("No objid in args")
|
|
|
112 |
objid = a['objid']
|
|
|
113 |
if re.match('0\$tidal', objid) is None:
|
|
|
114 |
raise Exception("bad objid [%s]" % objid)
|
|
|
115 |
|
|
|
116 |
maybelogin()
|
|
|
117 |
if objid == '0$tidal':
|
|
|
118 |
# Root
|
|
|
119 |
content = direntries(objid,
|
|
|
120 |
(('My Music', 'my_music'),
|
|
|
121 |
('Featured Playlists', 'featured'),
|
|
|
122 |
("What's New", 'new'),
|
|
|
123 |
('Genres', 'genres'),
|
|
|
124 |
('Moods', 'moods'),
|
|
|
125 |
('Search', 'search')))
|
|
|
126 |
elif objid == '0$tidal$my_music':
|
|
|
127 |
content = direntries(objid,
|
|
|
128 |
(('My Playlists', 'my_pl'),
|
|
|
129 |
('Favourite Playlists', 'fav_pl'),
|
|
|
130 |
('Favourite Artists', 'fav_art'),
|
|
|
131 |
('Favourite Albums', 'fav_alb'),
|
|
|
132 |
('Favourite Tracks', 'fav_trk')))
|
|
|
133 |
elif objid == '0$tidal$my_music$fav_trk':
|
|
|
134 |
content = trackentries(objid, session.user.favorites.tracks())
|
|
|
135 |
elif objid == '0$tidal$my_music$my_pl':
|
|
|
136 |
items = session.user.playlists()
|
|
|
137 |
view(items, urls_from_id(playlist_view, items))
|
|
|
138 |
elif objid == '0$tidal$my_music$fav_pl':
|
|
|
139 |
items = session.user.favorites.playlists()
|
|
|
140 |
view(items, urls_from_id(playlist_view, items))
|
|
|
141 |
elif objid == '0$tidal$my_music$fav_art':
|
|
|
142 |
xbmcplugin.setContent(plugin.handle, 'artists')
|
|
|
143 |
items = session.user.favorites.artists()
|
|
|
144 |
view(items, urls_from_id(artist_view, items))
|
|
|
145 |
elif objid == '0$tidal$my_music$fav_alb':
|
|
|
146 |
xbmcplugin.setContent(plugin.handle, 'albums')
|
|
|
147 |
items = session.user.favorites.albums()
|
|
|
148 |
view(items, urls_from_id(album_view, items))
|
|
|
149 |
else:
|
|
|
150 |
raise Exception("unhandled path")
|
|
|
151 |
|
|
|
152 |
encoded = json.dumps(content)
|
|
|
153 |
return {"entries":encoded}
|
|
|
154 |
|
|
|
155 |
|
|
|
156 |
|
|
|
157 |
processor.rclog("Tidal running")
|
|
|
158 |
processor.mainloop()
|