Reformat code

This commit is contained in:
badaix 2024-02-17 20:34:11 +01:00
parent 48b2840365
commit baf09b340e
6 changed files with 436 additions and 307 deletions

View file

@ -4,61 +4,72 @@ import telnetlib
import json import json
if len(sys.argv) < 3: if len(sys.argv) < 3:
print("usage: control.py <SERVER HOST> [setVolume|setName]") print("usage: control.py <SERVER HOST> [setVolume|setName]")
sys.exit(0) sys.exit(0)
telnet = telnetlib.Telnet(sys.argv[1], 1705) telnet = telnetlib.Telnet(sys.argv[1], 1705)
requestId = 1 requestId = 1
def doRequest( j, requestId ):
print("send: " + j) def doRequest(j, requestId):
telnet.write(j + "\r\n") print("send: " + j)
while (True): telnet.write(j + "\r\n")
response = telnet.read_until("\r\n", 2) while (True):
jResponse = json.loads(response) response = telnet.read_until("\r\n", 2)
if 'id' in jResponse: jResponse = json.loads(response)
if jResponse['id'] == requestId: if 'id' in jResponse:
print("recv: " + response) if jResponse['id'] == requestId:
return jResponse; print("recv: " + response)
return jResponse
def setVolume(client, volume): def setVolume(client, volume):
global requestId global requestId
doRequest(json.dumps({'jsonrpc': '2.0', 'method': 'Client.SetVolume', 'params': {'client': client, 'volume': volume}, 'id': requestId}), requestId) doRequest(json.dumps({'jsonrpc': '2.0', 'method': 'Client.SetVolume', 'params': {
requestId = requestId + 1 'client': client, 'volume': volume}, 'id': requestId}), requestId)
requestId = requestId + 1
def setName(client, name): def setName(client, name):
global requestId global requestId
doRequest(json.dumps({'jsonrpc': '2.0', 'method': 'Client.SetName', 'params': {'client': client, 'name': name}, 'id': requestId}), requestId) doRequest(json.dumps({'jsonrpc': '2.0', 'method': 'Client.SetName', 'params': {
requestId = requestId + 1 'client': client, 'name': name}, 'id': requestId}), requestId)
requestId = requestId + 1
if sys.argv[2] == "setVolume": if sys.argv[2] == "setVolume":
if len(sys.argv) < 5: if len(sys.argv) < 5:
print("usage: control.py <SERVER HOST> setVolume <HOSTNAME> [+/-]<VOLUME>") print(
exit(0) "usage: control.py <SERVER HOST> setVolume <HOSTNAME> [+/-]<VOLUME>")
volstr = sys.argv[4] exit(0)
j = doRequest(json.dumps({'jsonrpc': '2.0', 'method': 'Server.GetStatus', 'id': 1}), 1) volstr = sys.argv[4]
for client in j["result"]["clients"]: j = doRequest(json.dumps(
if(sys.argv[3] == client['host']['name'] or sys.argv[3] == 'all'): {'jsonrpc': '2.0', 'method': 'Server.GetStatus', 'id': 1}), 1)
if(volstr[0] == '+'): for client in j["result"]["clients"]:
volume = int(client['config']['volume']['percent']) + int(volstr[1:]) if (sys.argv[3] == client['host']['name'] or sys.argv[3] == 'all'):
elif(volstr[0] == '-'): if (volstr[0] == '+'):
volume = int(client['config']['volume']['percent']) - int(volstr[1:]) volume = int(client['config']['volume']
else: ['percent']) + int(volstr[1:])
volume = int(volstr) elif (volstr[0] == '-'):
setVolume(client['host']['mac'], volume) volume = int(client['config']['volume']
['percent']) - int(volstr[1:])
else:
volume = int(volstr)
setVolume(client['host']['mac'], volume)
elif sys.argv[2] == "setName": elif sys.argv[2] == "setName":
if len(sys.argv) < 5: if len(sys.argv) < 5:
print("usage: control.py <SERVER HOST> setName <MAC> <NAME>") print("usage: control.py <SERVER HOST> setName <MAC> <NAME>")
exit(0) exit(0)
setName(sys.argv[3], sys.argv[4]) setName(sys.argv[3], sys.argv[4])
else: else:
print("unknown command \"" + sys.argv[2] + "\"") print("unknown command \"" + sys.argv[2] + "\"")
j = doRequest(json.dumps({'jsonrpc': '2.0', 'method': 'Server.GetStatus', 'id': 1}), 1) j = doRequest(json.dumps(
{'jsonrpc': '2.0', 'method': 'Server.GetStatus', 'id': 1}), 1)
for client in j["result"]["clients"]: for client in j["result"]["clients"]:
print("MAC: " + client['host']['mac'] + ", connect: " + str(client['connected']) + ", volume: " + str(client['config']['volume']['percent']) + ", name: " + client['host']['name'] + ", host: " + client['host']['ip']) print("MAC: " + client['host']['mac'] + ", connect: " + str(client['connected']) + ", volume: " + str(
client['config']['volume']['percent']) + ", name: " + client['host']['name'] + ", host: " + client['host']['ip'])
telnet.close telnet.close

View file

@ -6,32 +6,38 @@ import json
telnet = telnetlib.Telnet(sys.argv[1], 1705) telnet = telnetlib.Telnet(sys.argv[1], 1705)
requestId = 1 requestId = 1
def doRequest( j, requestId ):
print("send: " + j) def doRequest(j, requestId):
telnet.write(j + "\r\n") print("send: " + j)
while (True): telnet.write(j + "\r\n")
response = telnet.read_until("\r\n", 2) while (True):
jResponse = json.loads(response) response = telnet.read_until("\r\n", 2)
if 'id' in jResponse: jResponse = json.loads(response)
if jResponse['id'] == requestId: if 'id' in jResponse:
# print("recv: " + response) if jResponse['id'] == requestId:
return jResponse; # print("recv: " + response)
return jResponse
def setVolume(client, volume): def setVolume(client, volume):
global requestId global requestId
doRequest(json.dumps({'jsonrpc': '2.0', 'method': 'Client.SetVolume', 'params': {'id': client, 'volume': {'muted': False, 'percent': volume}}, 'id': requestId}), requestId) doRequest(json.dumps({'jsonrpc': '2.0', 'method': 'Client.SetVolume', 'params': {
requestId = requestId + 1 'id': client, 'volume': {'muted': False, 'percent': volume}}, 'id': requestId}), requestId)
requestId = requestId + 1
volume = int(sys.argv[2]) volume = int(sys.argv[2])
j = doRequest(json.dumps({'jsonrpc': '2.0', 'method': 'Server.GetStatus', 'id': 1}), 1) j = doRequest(json.dumps(
{'jsonrpc': '2.0', 'method': 'Server.GetStatus', 'id': 1}), 1)
for group in j["result"]["server"]["groups"]: for group in j["result"]["server"]["groups"]:
for client in group["clients"]: for client in group["clients"]:
setVolume(client['id'], volume) setVolume(client['id'], volume)
j = doRequest(json.dumps({'jsonrpc': '2.0', 'method': 'Server.GetStatus', 'id': 1}), 1) j = doRequest(json.dumps(
{'jsonrpc': '2.0', 'method': 'Server.GetStatus', 'id': 1}), 1)
for group in j["result"]["server"]["groups"]: for group in j["result"]["server"]["groups"]:
for client in group["clients"]: for client in group["clients"]:
print("MAC: " + client['host']['mac'] + ", name: " + client['config']['name'] + ", conntect: " + str(client['connected']) + ", volume: " + str(client['config']['volume']['percent'])) print("MAC: " + client['host']['mac'] + ", name: " + client['config']['name'] + ", conntect: " + str(
client['connected']) + ", volume: " + str(client['config']['volume']['percent']))
telnet.close telnet.close

View file

@ -6,36 +6,36 @@ import threading
import time import time
if len(sys.argv) < 2: if len(sys.argv) < 2:
print("usage: testClient.py <SERVER HOST>") print("usage: testClient.py <SERVER HOST>")
sys.exit(0) sys.exit(0)
telnet = telnetlib.Telnet(sys.argv[1], 1705) telnet = telnetlib.Telnet(sys.argv[1], 1705)
class ReaderThread(threading.Thread): class ReaderThread(threading.Thread):
def __init__(self, tn, stop_event): def __init__(self, tn, stop_event):
super(ReaderThread, self).__init__() super(ReaderThread, self).__init__()
self.tn = tn self.tn = tn
self.stop_event = stop_event self.stop_event = stop_event
def run(self): def run(self):
while (not self.stop_event.is_set()): while (not self.stop_event.is_set()):
response = self.tn.read_until("\r\n", 2) response = self.tn.read_until("\r\n", 2)
if response: if response:
print("received: " + response) print("received: " + response)
jresponse = json.loads(response) jresponse = json.loads(response)
print(json.dumps(jresponse, indent=2)) print(json.dumps(jresponse, indent=2))
print("\r\n") print("\r\n")
def doRequest( str ): def doRequest(str):
print("send: " + str) print("send: " + str)
telnet.write(str) telnet.write(str)
time.sleep(1) time.sleep(1)
return; return
t_stop= threading.Event() t_stop = threading.Event()
t = ReaderThread(telnet, t_stop) t = ReaderThread(telnet, t_stop)
t.start() t.start()
@ -58,7 +58,7 @@ doRequest("{\"jsonrpc\": \"2.0\", \"method\": \"Client.SetStream\", \"params\":
time.sleep(5) time.sleep(5)
#doRequest("{\"jsonrpc\": \"2.0\", \"method\": \"Server.GetStatus\", \"params\": {\"client\": \"80:1f:02:ed:fd:e0\"}, \"id\": 2}\r\n") # doRequest("{\"jsonrpc\": \"2.0\", \"method\": \"Server.GetStatus\", \"params\": {\"client\": \"80:1f:02:ed:fd:e0\"}, \"id\": 2}\r\n")
''' '''
doRequest("{\"jsonrpc\": \"2.0\", \"method\": \"Client.SetVolume\", \"params\": {\"client\": \"80:1f:02:ed:fd:e0\", \"volume\": 10}, \"id\": 3}\r\n") doRequest("{\"jsonrpc\": \"2.0\", \"method\": \"Client.SetVolume\", \"params\": {\"client\": \"80:1f:02:ed:fd:e0\", \"volume\": 10}, \"id\": 3}\r\n")
doRequest("{\"jsonrpc\": \"2.0\", \"method\": \"Client.SetVolume\", \"params\": {\"client\": \"80:1f:02:ed:fd:e0\", \"volume\": 30}, \"id\": 4}\r\n") doRequest("{\"jsonrpc\": \"2.0\", \"method\": \"Client.SetVolume\", \"params\": {\"client\": \"80:1f:02:ed:fd:e0\", \"volume\": 30}, \"id\": 4}\r\n")
@ -83,6 +83,6 @@ doRequest("{\"jsonrpc\": \"2.0\", \"method\": \"Client.SetVolume\", \"params\":
''' '''
s = raw_input("") s = raw_input("")
print(s) print(s)
t_stop.set(); t_stop.set()
t.join() t.join()
telnet.close telnet.close

View file

@ -28,9 +28,12 @@ if __name__ == "__main__":
with open(sys.argv[1], 'r') as file: with open(sys.argv[1], 'r') as file:
data = file.read() data = file.read()
data = re.sub('^\s*# Snapcast changelog *\n*', '', data, flags=re.MULTILINE) data = re.sub('^\s*# Snapcast changelog *\n*',
data = re.sub('^\s*### ([a-zA-Z]+) *\n', r'\n * \1\n', data, flags=re.MULTILINE) '', data, flags=re.MULTILINE)
data = re.sub('^\s*## Version\s+(\S*) *\n', r'snapcast (\1-1) unstable; urgency=medium\n', data, flags=re.MULTILINE) data = re.sub('^\s*### ([a-zA-Z]+) *\n',
r'\n * \1\n', data, flags=re.MULTILINE)
data = re.sub('^\s*## Version\s+(\S*) *\n',
r'snapcast (\1-1) unstable; urgency=medium\n', data, flags=re.MULTILINE)
data = re.sub('^\s*-\s*(.*) *\n', r' -\1\n', data, flags=re.MULTILINE) data = re.sub('^\s*-\s*(.*) *\n', r' -\1\n', data, flags=re.MULTILINE)
data = re.sub('^_(.*)_ *\n', r' -- \1\n\n', data, flags=re.MULTILINE) data = re.sub('^_(.*)_ *\n', r' -- \1\n\n', data, flags=re.MULTILINE)

View file

@ -37,33 +37,33 @@ __git_version__ = "@gitversion@"
identity = "Snapcast" identity = "Snapcast"
params = { params = {
'progname': sys.argv[0], "progname": sys.argv[0],
# Connection # Connection
'mopidy-host': None, "mopidy-host": None,
'mopidy-port': None, "mopidy-port": None,
'snapcast-host': None, "snapcast-host": None,
'snapcast-port': None, "snapcast-port": None,
'stream': None, "stream": None,
} }
defaults = { defaults = {
# Connection # Connection
'mopidy-host': 'localhost', "mopidy-host": "localhost",
'mopidy-port': 6680, "mopidy-port": 6680,
'snapcast-host': 'localhost', "snapcast-host": "localhost",
'snapcast-port': 1780, "snapcast-port": 1780,
'stream': 'default', "stream": "default",
} }
def send(json_msg): def send(json_msg):
sys.stdout.write(json.dumps(json_msg)) sys.stdout.write(json.dumps(json_msg))
sys.stdout.write('\n') sys.stdout.write("\n")
sys.stdout.flush() sys.stdout.flush()
class MopidyControl(object): class MopidyControl(object):
""" Mopidy websocket remote control """ """Mopidy websocket remote control"""
def __init__(self, params): def __init__(self, params):
self._params = params self._params = params
@ -74,17 +74,20 @@ class MopidyControl(object):
self._mopidy_request_map = {} self._mopidy_request_map = {}
self._seek_offset = 0.0 self._seek_offset = 0.0
wsversion = websocket.__version__.split('.') wsversion = websocket.__version__.split(".")
if int(wsversion[0]) == 0 and int(wsversion[1]) < 58: if int(wsversion[0]) == 0 and int(wsversion[1]) < 58:
logger.error( logger.error(
f"websocket-client version 0.58.0 or higher required, installed: {websocket.__version__}, exiting.") f"websocket-client version 0.58.0 or higher required, installed: {websocket.__version__}, exiting."
)
exit() exit()
self.websocket = websocket.WebSocketApp("ws://" + self._params['mopidy-host'] + ":" + str(self._params['mopidy-port']) + "/mopidy/ws", self.websocket = websocket.WebSocketApp(
on_message=self.on_ws_message, f"ws://{self._params["mopidy-host"]}:{str(self._params["mopidy-port"])}/mopidy/ws",
on_error=self.on_ws_error, on_message=self.on_ws_message,
on_open=self.on_ws_open, on_error=self.on_ws_error,
on_close=self.on_ws_close) on_open=self.on_ws_open,
on_close=self.on_ws_close,
)
self.websocket_thread = threading.Thread( self.websocket_thread = threading.Thread(
target=self.websocket_loop, args=()) target=self.websocket_loop, args=())
@ -105,70 +108,73 @@ class MopidyControl(object):
def extractImageUrl(self, track_uri, jmsg): def extractImageUrl(self, track_uri, jmsg):
url = None url = None
if jmsg and track_uri in jmsg and jmsg[track_uri]: if jmsg and track_uri in jmsg and jmsg[track_uri]:
url = jmsg[track_uri][0]['uri'] url = jmsg[track_uri][0]["uri"]
if url.find('://') == -1: if url.find("://") == -1:
url = str( url = str(
f"http://{self._params['mopidy-host']}:{self._params['mopidy-port']}{url}") f"http://{self._params['mopidy-host']}:{self._params['mopidy-port']}{url}"
)
logger.debug(f"Image: {url}") logger.debug(f"Image: {url}")
return url return url
def onGetImageResponse(self, result): def onGetImageResponse(self, result):
if 'metadata' in self._properties: if "metadata" in self._properties:
# => {'id': 25, 'jsonrpc': '2.0', 'method': 'core.library.get_images', 'params': {'uris': ['local:track:A/ABBA/ABBA%20-%20Voyage%20%282021%29/10%20-%20Ode%20to%20Freedom.ogg']}} # => {'id': 25, 'jsonrpc': '2.0', 'method': 'core.library.get_images', 'params': {'uris': ['local:track:A/ABBA/ABBA%20-%20Voyage%20%282021%29/10%20-%20Ode%20to%20Freedom.ogg']}}
# <= {"jsonrpc": "2.0", "id": 25, "result": {"local:track:A/ABBA/ABBA%20-%20Voyage%20%282021%29/10%20-%20Ode%20to%20Freedom.ogg": [{"__model__": "Image", "uri": "/local/f0b20b441175563334f6ad75e76426b7-500x500.jpeg", "width": 500, "height": 500}]}} # <= {"jsonrpc": "2.0", "id": 25, "result": {"local:track:A/ABBA/ABBA%20-%20Voyage%20%282021%29/10%20-%20Ode%20to%20Freedom.ogg": [{"__model__": "Image", "uri": "/local/f0b20b441175563334f6ad75e76426b7-500x500.jpeg", "width": 500, "height": 500}]}}
meta = self._properties['metadata'] meta = self._properties["metadata"]
if 'url' in meta: if "url" in meta:
url = self.extractImageUrl(meta['url'], result) url = self.extractImageUrl(meta["url"], result)
if not url is None: if not url is None:
self._properties['metadata']['artUrl'] = url self._properties["metadata"]["artUrl"] = url
logger.info(f'New properties: {self._properties}') logger.info(f"New properties: {self._properties}")
return send({"jsonrpc": "2.0", "method": "Plugin.Stream.Player.Properties", return send(
"params": self._properties}) {
"jsonrpc": "2.0",
"method": "Plugin.Stream.Player.Properties",
"params": self._properties,
}
)
def getMetaData(self, track): def getMetaData(self, track):
metadata = {} metadata = {}
if track is None: if track is None:
return metadata return metadata
if 'uri' in track: if "uri" in track:
metadata['url'] = track['uri'] metadata["url"] = track["uri"]
if 'name' in track: if "name" in track:
metadata['title'] = track['name'] metadata["title"] = track["name"]
if 'artists' in track: if "artists" in track:
for artist in track['artists']: for artist in track["artists"]:
if 'musicbrainz_id' in artist: if "musicbrainz_id" in artist:
metadata['musicbrainzArtistId'] = artist['musicbrainz_id'] metadata["musicbrainzArtistId"] = artist["musicbrainz_id"]
if 'name' in artist: if "name" in artist:
if not 'artist' in metadata: if not "artist" in metadata:
metadata['artist'] = [] metadata["artist"] = []
metadata['artist'].append( metadata["artist"].append(artist["name"])
artist['name']) if "sortname" in artist:
if 'sortname' in artist: if not "sortname" in metadata:
if not 'sortname' in metadata: metadata["artistsort"] = []
metadata['artistsort'] = [] metadata["artistsort"].append(artist["sortname"])
metadata['artistsort'].append( if "album" in track:
artist['sortname']) album = track["album"]
if 'album' in track: if "musicbrainz_id" in album:
album = track['album'] self._metadata["musicbrainzAlbumId"] = album["musicbrainz_id"]
if 'musicbrainz_id' in album: if "name" in album:
self._metadata['musicbrainzAlbumId'] = album['musicbrainz_id'] self._metadata["album"] = album["name"]
if 'name' in album: if "genre" in track:
self._metadata['album'] = album['name'] metadata["genre"] = [track["genre"]]
if 'genre' in track: if "track_no" in track:
metadata['genre'] = [track['genre']] metadata["trackNumber"] = track["track_no"]
if 'track_no' in track: metadata["trackId"] = str(track["track_no"])
metadata['trackNumber'] = track['track_no'] if "disc_no" in track:
metadata['trackId'] = str(track['track_no']) metadata["discNumber"] = track["disc_no"]
if 'disc_no' in track: if "date" in track:
metadata['discNumber'] = track['disc_no'] metadata["contentCreated"] = track["date"]
if 'date' in track: if "length" in track:
metadata['contentCreated'] = track['date'] metadata["duration"] = float(track["length"]) / 1000
if 'length' in track: if "comment" in track:
metadata['duration'] = float( metadata["comment"] = [track["comment"]]
track['length']) / 1000 if "musicbrainz_id" in track:
if 'comment' in track: metadata["musicbrainzTrackId"] = track["musicbrainz_id"]
metadata['comment'] = [track['comment']]
if 'musicbrainz_id' in track:
metadata['musicbrainzTrackId'] = track['musicbrainz_id']
# Not supported: # Not supported:
# if 'composers' in result: # if 'composers' in result:
# if 'performers' in result: # if 'performers' in result:
@ -183,85 +189,97 @@ class MopidyControl(object):
for rr in req_res: for rr in req_res:
request = rr[0] request = rr[0]
result = rr[1] result = rr[1]
logger.debug( logger.debug(f"getProperties request: {request}, result: {result}")
f'getProperties request: {request}, result: {result}') if request == "core.playback.get_stream_title":
if request == 'core.playback.get_stream_title':
if not result is None and not self._metadata is None: if not result is None and not self._metadata is None:
self._metadata['title'] = result self._metadata["title"] = result
elif request == 'core.playback.get_state': elif request == "core.playback.get_state":
properties['playbackStatus'] = str(result) properties["playbackStatus"] = str(result)
elif request == 'core.tracklist.get_repeat': elif request == "core.tracklist.get_repeat":
repeat = result repeat = result
elif request == 'core.tracklist.get_single': elif request == "core.tracklist.get_single":
single = result single = result
elif request == 'core.tracklist.get_random': elif request == "core.tracklist.get_random":
properties['shuffle'] = result properties["shuffle"] = result
elif request == 'core.mixer.get_volume': elif request == "core.mixer.get_volume":
properties['volume'] = result properties["volume"] = result
elif request == 'core.mixer.get_mute': elif request == "core.mixer.get_mute":
properties['mute'] = result properties["mute"] = result
elif request == 'core.playback.get_time_position': elif request == "core.playback.get_time_position":
properties['position'] = float(result) / 1000 properties["position"] = float(result) / 1000
elif request == 'core.playback.get_current_track': elif request == "core.playback.get_current_track":
self._metadata = self.getMetaData(result) self._metadata = self.getMetaData(result)
elif request == 'core.library.get_images': elif request == "core.library.get_images":
metadata = self._metadata metadata = self._metadata
if not metadata is None and 'url' in metadata: if not metadata is None and "url" in metadata:
url = self.extractImageUrl(metadata['url'], result) url = self.extractImageUrl(metadata["url"], result)
if not url is None: if not url is None:
self._metadata['artUrl'] = url self._metadata["artUrl"] = url
if repeat and single: if repeat and single:
properties['loopStatus'] = 'track' properties["loopStatus"] = "track"
elif repeat: elif repeat:
properties['loopStatus'] = 'playlist' properties["loopStatus"] = "playlist"
else: else:
properties['loopStatus'] = 'none' properties["loopStatus"] = "none"
properties['canGoNext'] = True properties["canGoNext"] = True
properties['canGoPrevious'] = True properties["canGoPrevious"] = True
properties['canPlay'] = True properties["canPlay"] = True
properties['canPause'] = True properties["canPause"] = True
properties['canSeek'] = 'duration' in self._metadata properties["canSeek"] = "duration" in self._metadata
properties['canControl'] = True properties["canControl"] = True
if self._metadata: if self._metadata:
properties['metadata'] = self._metadata properties["metadata"] = self._metadata
return properties return properties
def onGetTrackResponse(self, req_id, track): def onGetTrackResponse(self, req_id, track):
self._metadata = self.getMetaData(track) self._metadata = self.getMetaData(track)
batch_req = [("core.playback.get_stream_title", None), ("core.playback.get_state", None), ("core.tracklist.get_repeat", None), ("core.tracklist.get_single", None), batch_req = [
("core.tracklist.get_random", None), ("core.mixer.get_volume", None), ("core.playback.get_time_position", None)] ("core.playback.get_stream_title", None),
if 'url' in self._metadata: ("core.playback.get_state", None),
batch_req.append(('core.library.get_images', { ("core.tracklist.get_repeat", None),
'uris': [self._metadata['url']]})) ("core.tracklist.get_single", None),
("core.tracklist.get_random", None),
("core.mixer.get_volume", None),
("core.playback.get_time_position", None),
]
if "url" in self._metadata:
batch_req.append(
("core.library.get_images", {"uris": [self._metadata["url"]]})
)
self.send_batch_request( self.send_batch_request(
batch_req, lambda req_res: self.onSnapcastPropertiesResponse(req_id, req_res)) batch_req,
lambda req_res: self.onSnapcastPropertiesResponse(req_id, req_res),
)
def onSnapcastPropertiesResponse(self, req_id, req_res): def onSnapcastPropertiesResponse(self, req_id, req_res):
logger.debug(f'onSnapcastPropertiesRequest id: {req_id}') logger.debug(f"onSnapcastPropertiesRequest id: {req_id}")
self._properties = self.getProperties(req_res) self._properties = self.getProperties(req_res)
logger.info(f'New properties: {self._properties}') logger.info(f"New properties: {self._properties}")
send({"jsonrpc": "2.0", "id": req_id, send({"jsonrpc": "2.0", "id": req_id, "result": self._properties})
"result": self._properties})
def onPropertiesResponse(self, req_res): def onPropertiesResponse(self, req_res):
self._properties = self.getProperties(req_res) self._properties = self.getProperties(req_res)
logger.info(f'New properties: {self._properties}') logger.info(f"New properties: {self._properties}")
send({"jsonrpc": "2.0", "method": "Plugin.Stream.Player.Properties", send(
"params": self._properties}) {
"jsonrpc": "2.0",
"method": "Plugin.Stream.Player.Properties",
"params": self._properties,
}
)
def onGetTimePositionResponse(self, result): def onGetTimePositionResponse(self, result):
if self._seek_offset != 0: if self._seek_offset != 0:
pos = int(int(result) + self._seek_offset * 1000) pos = int(int(result) + self._seek_offset * 1000)
logger.debug(f"Seeking to: {pos}") logger.debug(f"Seeking to: {pos}")
self.send_request("core.playback.seek", { self.send_request("core.playback.seek", {"time_position": pos})
"time_position": pos})
self._seek_offset = 0.0 self._seek_offset = 0.0
def on_ws_message(self, ws, message): def on_ws_message(self, ws, message):
# TODO: error handling # TODO: error handling
logger.debug(f'Snapcast RPC websocket message received: {message}') logger.debug(f"Snapcast RPC websocket message received: {message}")
jmsg = json.loads(message) jmsg = json.loads(message)
# Batch request # Batch request
@ -270,52 +288,82 @@ class MopidyControl(object):
req_res = [] req_res = []
callback = None callback = None
for msg in jmsg: for msg in jmsg:
id = msg['id'] id = msg["id"]
if id in self._mopidy_request_map: if id in self._mopidy_request_map:
request = self._mopidy_request_map[id] request = self._mopidy_request_map[id]
del self._mopidy_request_map[id] del self._mopidy_request_map[id]
req_res.append((request[0], msg['result'])) req_res.append((request[0], msg["result"]))
if not request[1] is None: if not request[1] is None:
callback = request[1] callback = request[1]
if not callback is None: if not callback is None:
callback(req_res) callback(req_res)
# Request # Request
elif 'id' in jmsg: elif "id" in jmsg:
id = jmsg['id'] id = jmsg["id"]
if id in self._mopidy_request_map: if id in self._mopidy_request_map:
request = self._mopidy_request_map[id] request = self._mopidy_request_map[id]
del self._mopidy_request_map[id] del self._mopidy_request_map[id]
logger.debug(f'Received response to request "{request[0]}"') logger.debug(f'Received response to request "{request[0]}"')
callback = request[1] callback = request[1]
if not callback is None: if not callback is None:
callback(jmsg['result']) callback(jmsg["result"])
# Notification # Notification
else: else:
if 'event' in jmsg: if "event" in jmsg:
event = jmsg['event'] event = jmsg["event"]
logger.info(f"Event: {event}") logger.info(f"Event: {event}")
if event == 'track_playback_started': if event == "track_playback_started":
self._metadata = self.getMetaData( self._metadata = self.getMetaData(
jmsg['tl_track']['track']) jmsg["tl_track"]["track"])
logger.debug(f'Meta: {self._metadata}') logger.debug(f"Meta: {self._metadata}")
batch_req = [("core.playback.get_stream_title", None), ("core.playback.get_state", None), ("core.tracklist.get_repeat", None), ("core.tracklist.get_single", None), batch_req = [
("core.tracklist.get_random", None), ("core.mixer.get_volume", None), ("core.mixer.get_mute", None), ("core.playback.get_time_position", None)] ("core.playback.get_stream_title", None),
if 'url' in self._metadata: ("core.playback.get_state", None),
batch_req.append(('core.library.get_images', { ("core.tracklist.get_repeat", None),
'uris': [self._metadata['url']]})) ("core.tracklist.get_single", None),
("core.tracklist.get_random", None),
("core.mixer.get_volume", None),
("core.mixer.get_mute", None),
("core.playback.get_time_position", None),
]
if "url" in self._metadata:
batch_req.append(
(
"core.library.get_images",
{"uris": [self._metadata["url"]]},
)
)
self.send_batch_request( self.send_batch_request(
batch_req, self.onPropertiesResponse) batch_req, self.onPropertiesResponse)
elif event in ['tracklist_changed', 'track_playback_ended']: elif event in ["tracklist_changed", "track_playback_ended"]:
logger.debug("Nothing to do") logger.debug("Nothing to do")
elif event == 'playback_state_changed' and jmsg["old_state"] == jmsg["new_state"]: elif (
event == "playback_state_changed"
and jmsg["old_state"] == jmsg["new_state"]
):
logger.debug("Nothing to do") logger.debug("Nothing to do")
elif event == 'volume_changed' and 'volume' in self._properties and jmsg['volume'] == self._properties['volume']: elif (
event == "volume_changed"
and "volume" in self._properties
and jmsg["volume"] == self._properties["volume"]
):
logger.debug("Nothing to do") logger.debug("Nothing to do")
else: else:
self.send_batch_request([("core.playback.get_stream_title", None), ("core.playback.get_state", None), ("core.tracklist.get_repeat", None), ("core.tracklist.get_single", None), self.send_batch_request(
("core.tracklist.get_random", None), ("core.mixer.get_volume", None), ("core.mixer.get_mute", None), ("core.playback.get_time_position", None)], self.onPropertiesResponse) [
("core.playback.get_stream_title", None),
("core.playback.get_state", None),
("core.tracklist.get_repeat", None),
("core.tracklist.get_single", None),
("core.tracklist.get_random", None),
("core.mixer.get_volume", None),
("core.mixer.get_mute", None),
("core.playback.get_time_position", None),
],
self.onPropertiesResponse,
)
def on_ws_error(self, ws, error): def on_ws_error(self, ws, error):
logger.error("Snapcast RPC websocket error") logger.error("Snapcast RPC websocket error")
@ -332,7 +380,7 @@ class MopidyControl(object):
j = {"id": self._req_id, "jsonrpc": "2.0", "method": str(method)} j = {"id": self._req_id, "jsonrpc": "2.0", "method": str(method)}
if not params is None: if not params is None:
j["params"] = params j["params"] = params
logger.debug(f'send_request: {j}') logger.debug(f"send_request: {j}")
result = self._req_id result = self._req_id
self._mopidy_request_map[result] = (str(method), callback) self._mopidy_request_map[result] = (str(method), callback)
self._req_id += 1 self._req_id += 1
@ -345,15 +393,13 @@ class MopidyControl(object):
for method_param in methods_params: for method_param in methods_params:
method = str(method_param[0]) method = str(method_param[0])
params = method_param[1] params = method_param[1]
j = {"id": self._req_id, "jsonrpc": "2.0", j = {"id": self._req_id, "jsonrpc": "2.0", "method": method}
"method": method}
if not params is None: if not params is None:
j["params"] = params j["params"] = params
batch.append(j) batch.append(j)
self._mopidy_request_map[self._req_id] = ( self._mopidy_request_map[self._req_id] = (method, callback)
method, callback)
self._req_id += 1 self._req_id += 1
logger.debug(f'send_batch_request: {batch}') logger.debug(f"send_batch_request: {batch}")
self.websocket.send(json.dumps(batch)) self.websocket.send(json.dumps(batch))
return result return result
@ -366,90 +412,138 @@ class MopidyControl(object):
try: try:
id = None id = None
request = json.loads(cmd) request = json.loads(cmd)
id = request['id'] id = request["id"]
[interface, cmd] = request['method'].rsplit('.', 1) [interface, cmd] = request["method"].rsplit(".", 1)
if interface == 'Plugin.Stream.Player': if interface == "Plugin.Stream.Player":
if cmd == 'Control': if cmd == "Control":
success = True success = True
command = request['params']['command'] command = request["params"]["command"]
params = request['params'].get('params', {}) params = request["params"].get("params", {})
logger.debug( logger.debug(
f'Control command: {command}, params: {params}') f"Control command: {command}, params: {params}")
if command == 'next': if command == "next":
self.send_request("core.playback.next") self.send_request("core.playback.next")
elif command == 'previous': elif command == "previous":
self.send_request("core.playback.previous") self.send_request("core.playback.previous")
elif command == 'play': elif command == "play":
self.send_request("core.playback.play") self.send_request("core.playback.play")
elif command == 'pause': elif command == "pause":
self.send_request("core.playback.pause") self.send_request("core.playback.pause")
elif command == 'playPause': elif command == "playPause":
if self._properties['playbackStatus'] == 'playing': if self._properties["playbackStatus"] == "playing":
self.send_request("core.playback.pause") self.send_request("core.playback.pause")
else: else:
self.send_request("core.playback.play") self.send_request("core.playback.play")
elif command == 'stop': elif command == "stop":
self.send_request("core.playback.stop") self.send_request("core.playback.stop")
elif command == 'setPosition': elif command == "setPosition":
position = float(params['position']) position = float(params["position"])
self.send_request("core.playback.seek", {
"time_position": int(position * 1000)})
elif command == 'seek':
self._seek_offset = float(params['offset'])
self.send_request( self.send_request(
"core.playback.get_time_position", None, self.onGetTimePositionResponse) "core.playback.seek",
elif cmd == 'SetProperty': {"time_position": int(position * 1000)},
property = request['params'] )
logger.debug(f'SetProperty: {property}') elif command == "seek":
if 'shuffle' in property: self._seek_offset = float(params["offset"])
self.send_request("core.tracklist.set_random", { self.send_request(
"value": property['shuffle']}) "core.playback.get_time_position",
if 'loopStatus' in property: None,
value = property['loopStatus'] self.onGetTimePositionResponse,
)
elif cmd == "SetProperty":
property = request["params"]
logger.debug(f"SetProperty: {property}")
if "shuffle" in property:
self.send_request(
"core.tracklist.set_random", {
"value": property["shuffle"]}
)
if "loopStatus" in property:
value = property["loopStatus"]
if value == "playlist": if value == "playlist":
self.send_request( self.send_request(
"core.tracklist.set_single", {"value": False}) "core.tracklist.set_single", {"value": False}
)
self.send_request( self.send_request(
"core.tracklist.set_repeat", {"value": True}) "core.tracklist.set_repeat", {"value": True}
)
elif value == "track": elif value == "track":
self.send_request( self.send_request(
"core.tracklist.set_single", {"value": True}) "core.tracklist.set_single", {"value": True}
)
self.send_request( self.send_request(
"core.tracklist.set_repeat", {"value": True}) "core.tracklist.set_repeat", {"value": True}
)
elif value == "none": elif value == "none":
self.send_request( self.send_request(
"core.tracklist.set_single", {"value": False}) "core.tracklist.set_single", {"value": False}
)
self.send_request( self.send_request(
"core.tracklist.set_repeat", {"value": False}) "core.tracklist.set_repeat", {"value": False}
if 'volume' in property: )
self.send_request("core.mixer.set_volume", { if "volume" in property:
"volume": int(property['volume'])}) self.send_request(
if 'mute' in property: "core.mixer.set_volume", {
self.send_request("core.mixer.set_mute", { "volume": int(property["volume"])}
"mute": property['mute']}) )
elif cmd == 'GetProperties': if "mute" in property:
self.send_request("core.playback.get_current_track", None, self.send_request(
lambda track: self.onGetTrackResponse(id, track)) "core.mixer.set_mute", {"mute": property["mute"]}
)
elif cmd == "GetProperties":
self.send_request(
"core.playback.get_current_track",
None,
lambda track: self.onGetTrackResponse(id, track),
)
return return
elif cmd == 'GetMetadata': elif cmd == "GetMetadata":
send({"jsonrpc": "2.0", "method": "Plugin.Stream.Log", "params": { send(
"severity": "Info", "message": "Logmessage"}}) {
return send({"jsonrpc": "2.0", "error": {"code": -32601, "jsonrpc": "2.0",
"message": "TODO: GetMetadata not yet implemented"}, "id": id}) "method": "Plugin.Stream.Log",
"params": {"severity": "Info", "message": "Logmessage"},
}
)
return send(
{
"jsonrpc": "2.0",
"error": {
"code": -32601,
"message": "TODO: GetMetadata not yet implemented",
},
"id": id,
}
)
else: else:
return send({"jsonrpc": "2.0", "error": {"code": -32601, return send(
"message": "Method not found"}, "id": id}) {
"jsonrpc": "2.0",
"error": {"code": -32601, "message": "Method not found"},
"id": id,
}
)
else: else:
return send({"jsonrpc": "2.0", "error": {"code": -32601, return send(
"message": "Method not found"}, "id": id}) {
"jsonrpc": "2.0",
"error": {"code": -32601, "message": "Method not found"},
"id": id,
}
)
send({"jsonrpc": "2.0", "result": "ok", "id": id}) send({"jsonrpc": "2.0", "result": "ok", "id": id})
except Exception as e: except Exception as e:
send({"jsonrpc": "2.0", "error": { send(
"code": -32700, "message": "Parse error", "data": str(e)}, "id": id}) {
"jsonrpc": "2.0",
"error": {"code": -32700, "message": "Parse error", "data": str(e)},
"id": id,
}
)
def usage(params): def usage(params):
print("""\ print(
"""\
Usage: %(progname)s [OPTION]... Usage: %(progname)s [OPTION]...
--mopidy-host=ADDR Set the mopidy server address --mopidy-host=ADDR Set the mopidy server address
@ -462,19 +556,33 @@ Usage: %(progname)s [OPTION]...
-d, --debug Run in debug mode -d, --debug Run in debug mode
-v, --version meta_mopidy version -v, --version meta_mopidy version
Report bugs to https://github.com/badaix/snapcast/issues""" % params) Report bugs to https://github.com/badaix/snapcast/issues"""
% params
)
if __name__ == '__main__': if __name__ == "__main__":
log_format_stderr = '%(asctime)s %(module)s %(levelname)s: %(message)s' log_format_stderr = "%(asctime)s %(module)s %(levelname)s: %(message)s"
log_level = logging.INFO log_level = logging.INFO
# Parse command line # Parse command line
try: try:
(opts, args) = getopt.getopt(sys.argv[1:], 'hdv', (opts, args) = getopt.getopt(
['help', 'mopidy-host=', 'mopidy-port=', 'snapcast-host=', 'snapcast-port=', 'stream=', 'debug', 'version']) sys.argv[1:],
"hdv",
[
"help",
"mopidy-host=",
"mopidy-port=",
"snapcast-host=",
"snapcast-port=",
"stream=",
"debug",
"version",
],
)
except getopt.GetoptError as ex: except getopt.GetoptError as ex:
(msg, opt) = ex.args (msg, opt) = ex.args
@ -483,23 +591,23 @@ if __name__ == '__main__':
usage(params) usage(params)
sys.exit(2) sys.exit(2)
for (opt, arg) in opts: for opt, arg in opts:
if opt in ['-h', '--help']: if opt in ["-h", "--help"]:
usage(params) usage(params)
sys.exit() sys.exit()
elif opt in ['--mopidy-host']: elif opt in ["--mopidy-host"]:
params['mopidy-host'] = arg params["mopidy-host"] = arg
elif opt in ['--mopidy-port']: elif opt in ["--mopidy-port"]:
params['mopidy-port'] = int(arg) params["mopidy-port"] = int(arg)
elif opt in ['--snapcast-host']: elif opt in ["--snapcast-host"]:
params['snapcast-host'] = arg params["snapcast-host"] = arg
elif opt in ['--snapcast-port']: elif opt in ["--snapcast-port"]:
params['snapcast-port'] = int(arg) params["snapcast-port"] = int(arg)
elif opt in ['--stream']: elif opt in ["--stream"]:
params['stream'] = arg params["stream"] = arg
elif opt in ['-d', '--debug']: elif opt in ["-d", "--debug"]:
log_level = logging.DEBUG log_level = logging.DEBUG
elif opt in ['-v', '--version']: elif opt in ["-v", "--version"]:
v = __version__ v = __version__
if __git_version__: if __git_version__:
v = __git_version__ v = __git_version__
@ -510,7 +618,7 @@ if __name__ == '__main__':
usage(params) usage(params)
sys.exit() sys.exit()
logger = logging.getLogger('meta_mopidy') logger = logging.getLogger("meta_mopidy")
logger.propagate = False logger.propagate = False
logger.setLevel(log_level) logger.setLevel(log_level)
@ -520,11 +628,11 @@ if __name__ == '__main__':
logger.addHandler(log_handler) logger.addHandler(log_handler)
for p in ['mopidy-host', 'mopidy-port', 'snapcast-host', 'snapcast-port', 'stream']: for p in ["mopidy-host", "mopidy-port", "snapcast-host", "snapcast-port", "stream"]:
if not params[p]: if not params[p]:
params[p] = defaults[p] params[p] = defaults[p]
logger.debug(f'Parameters: {params}') logger.debug(f"Parameters: {params}")
mopidy_ctrl = MopidyControl(params) mopidy_ctrl = MopidyControl(params)

View file

@ -449,7 +449,8 @@ class MPDWrapper(object):
def io_callback(self, fd, event): def io_callback(self, fd, event):
try: try:
logger.error(f'IO event "{event}" on fd "{fd}" (type: "{type(fd)}"') logger.error(
f'IO event "{event}" on fd "{fd}" (type: "{type(fd)}"')
if event & GLib.IO_HUP: if event & GLib.IO_HUP:
logger.debug("IO_HUP") logger.debug("IO_HUP")
return True return True