# Copyright (C) 2016 J.F.Dockes
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the
# Free Software Foundation, Inc.,
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
#
"""
Shared code for the tidal, qobuz, gmusic plugins.
- Uses the interface for the entity objects (track, album...)
concretely defined in models.py, but duck-typed.
- Defines and uses the format for the permanent URLs
The module implements utility functions for translating to/from what
our parent expects or sends on the pipe.
"""
from __future__ import print_function, unicode_literals
import posixpath
import re
import sys
PY3 = sys.version > '3'
import conftree
default_mime = "audio/mpeg"
default_samplerate = "44100"
# This is only used for log messages
_idprefix = '0$UNKNOWN'
def setidprefix(idprefix):
global _idprefix
_idprefix = idprefix
# Bogus class instanciated as global object for helping with reusing
# kodi addon code
class XbmcPlugin:
SORT_METHOD_TRACKNUM = 1
def __init__(self, idprefix):
self.entries = []
self.objid = ''
self.idprefix = idprefix
setidprefix(idprefix)
def addDirectoryItem(self, hdl, endpoint, title, isend):
self.entries.append(direntry(self.idprefix + endpoint, self.objid,
title))
def endOfDirectory(self, h):
return
def setContent(self, a, b):
return
def addSortMethod(self, a, b):
return
# For now, we pretend that all tracks have the same format (for the
# resource record). For some services this may not be true, we'll see
# if it can stay this way.
def setMimeAndSamplerate(m, s):
global default_mime, default_samplerate
default_mime = m
default_samplerate = s
def trackentries(httphp, pathprefix, objid, tracks):
"""
Transform a list of Track objects to the format expected by the parent
Args:
objid (str): objid for the browsed object (the parent container)
tracks is the array of Track objects to be translated
tracks: a list of Track objects.
Returns:
A list of dicts, each representing an UPnP item, with the
keys as expected in the plgwithslave.cxx resultToEntries() function.
The permanent URIs, are of the following form, based on the
configured host:port and pathprefix arguments and track Id:
http://host:port/pathprefix/track?version=1&trackId=<trackid>
"""
global default_mime, default_samplerate
entries = []
for track in tracks:
if not track.available:
if 1:
uplog("NOT AVAILABLE")
try:
uplog("%s by %s" % (track.name, track.artist.name))
except:
pass
continue
li = {}
li['pid'] = objid
li['id'] = objid + '$' + "%s" % track.id
li['tt'] = track.name
li['uri'] = 'http://%s' % httphp + \
posixpath.join(pathprefix,
'track?version=1&trackId=%s' % track.id)
li['tp'] = 'it'
if track.album:
li['upnp:album'] = track.album.name
if track.album.image:
li['upnp:albumArtURI'] = track.album.image
if track.album.release_date:
li['releasedate'] = track.album.release_date
li['upnp:originalTrackNumber'] = str(track.track_num)
li['upnp:artist'] = track.artist.name
li['dc:title'] = track.name
li['discnumber'] = str(track.disc_num)
li['duration'] = str(track.duration)
li['upnp:class'] = track.upnpclass
li['res:mime'] = default_mime
li['res:samplefreq'] = default_samplerate
entries.append(li)
return entries
def trackid_from_urlpath(pathprefix, a):
"""
Extract track id from a permanent URL path part.
This supposes that the input URL has the format produced by the
trackentries() method: <pathprefix>/track?version=1&trackId=<trackid>
Args:
pathprefix (str): our configured path prefix (e.g. /qobuz/)
a (dict): the argument dict out of cmdtalk with a 'path' key
Returns:
str: the track Id.
"""
if 'path' not in a:
raise Exception("trackuri: no 'path' in args")
path = a['path']
# pathprefix + 'track?version=1&trackId=trackid
exp = posixpath.join(pathprefix, '''track\?version=1&trackId=(.+)$''')
m = re.match(exp, path)
if m is None:
raise Exception("trackuri: path [%s] does not match [%s]" % (path, exp))
trackid = m.group(1)
return trackid
def direntry(id, pid, title, arturi=None, artist=None, upnpclass=None):
""" Create container entry in format expected by parent """
ret = {'id':id, 'pid':pid, 'tt':title, 'tp':'ct', 'searchable':'1'}
if arturi:
ret['upnp:albumArtURI'] = arturi
if artist:
ret['upnp:artist'] = artist
if upnpclass:
ret['upnp:class'] = upnpclass
return ret
# Get user and password from service, from the main configuration
# file, or possibly from the ohcredentials scratchpad. In both files,
# the entries are like:
# qobuzuser=xxx
# qobuzpass=yyy
def getserviceuserpass(upconfig, servicename):
username = upconfig.get(servicename + 'user')
password = upconfig.get(servicename + 'pass')
if not username or not password:
altconf = conftree.ConfSimple('/var/cache/upmpdcli/ohcreds/screds')
username = altconf.get(servicename + 'user')
password = altconf.get(servicename + 'pass')
return username, password
def uplog(s):
if not type(s) == type(b''):
s = ("%s: %s" % (_idprefix, s)).encode('utf-8')
if PY3:
sys.stderr.buffer.write(s + b'\n')
else:
sys.stderr.write(s + b'\n')
sys.stderr.flush()