Add files via upload

This commit is contained in:
LukeSpad 2021-12-17 18:45:05 +00:00 committed by GitHub
parent d58e5a0c77
commit fded45c031
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 65 additions and 22 deletions

View file

@ -1,13 +1,14 @@
#!/usr/bin/python
import os
import unicodedata
from Foundation import NSAppleScript
from ScriptingBridge import SBApplication
''' Module defining a MusicController class for Apple Music, enables:
basic transport control of the player,
query of player status,
playing existing playlists, and
running external appleScripts for more complex control
playing existing playlists,
running external appleScripts for more complex control, and
reporting messages in the notification centre'''
PLAYSTATE = dict([
@ -26,9 +27,14 @@ class MusicController(object):
# Player information
def get_current_track_info(self):
name = str(self.app.currentTrack().name())
album = str(self.app.currentTrack().album())
artist = str(self.app.currentTrack().artist())
name = self.app.currentTrack().name()
album = self.app.currentTrack().album()
artist = self.app.currentTrack().artist()
if name:
# Deal with tracks with non-ascii characters such as accents
name = unicodedata.normalize('NFD', name).encode('ascii', 'ignore')
album = unicodedata.normalize('NFD', album).encode('ascii', 'ignore')
artist = unicodedata.normalize('NFD', artist).encode('ascii', 'ignore')
return [name, album, artist]
def get_current_play_state(self):
@ -48,6 +54,7 @@ class MusicController(object):
elif PLAYSTATE.get(self.app.playerState()) == 'Pause':
self.app.playpause()
elif PLAYSTATE.get(self.app.playerState()) == 'Stop':
self. app.setValue_forKey_('true', 'shuffleEnabled')
playlist = self.app.sources().objectWithName_("Library")
playlist.playOnce_(None)
@ -79,6 +86,12 @@ class MusicController(object):
# ########################################################################################
# More complex playback control functions
def shuffle(self):
if self.app.shuffleEnabled():
self.app.setValue_forKey_('false', 'shuffleEnabled')
else:
self.app.setValue_forKey_('true', 'shuffleEnabled')
def set_current_track_position(self, time, mode='Relative'):
if mode == 'Relative':
# Set playback position in seconds relative to current position
@ -102,10 +115,10 @@ class MusicController(object):
s.executeAndReturnError_(None)
@staticmethod
def notify(message):
def notify(message, subtitle):
# Post message in notification center
path = os.path.dirname(os.path.abspath(__file__))
script = 'tell application "' + path + '/Notify.app" to notify("BeoGateway", "' + \
message + '")'
message + '", "' + subtitle + '")'
s = NSAppleScript.alloc().initWithSource_(script)
s.executeAndReturnError_(None)

View file

@ -1,7 +1,14 @@
# Constants for B&O telegram protocols
# ########################################################################################
# Config data (set on initialisation)
gateway = dict([("Current Audio Source", ''), ("Current Video Source", '')])
gateway = dict(
[
("Current Audio Source", 'Unknown'),
("Current Video Source", 'Unknown'),
("Now Playing", 'Unknown'),
("N.MUSIC Renderers", [])
]
)
rooms = []
devices = []
available_sources = []
@ -13,7 +20,7 @@ source_type_dict = dict(
[
("Video Sources", ("TV", "V.AUX/DTV2", "MEDIA", "V.TAPE/V.MEM/DVD2", "DVD", "CAMERA",
"SAT/DTV", "PC", "WEB", "DOORCAM", "PHOTO", "USB2", "WEBMEDIA", "AV.IN",
"HOMEMEDIA", "DNLA", "RECORDINGS", "CAMERA", "USB", "DNLA-DMR", "YOUTUBE",
"HOMEMEDIA", "DVB_RADIO", "DNLA", "RECORDINGS", "CAMERA", "USB", "DNLA-DMR", "YOUTUBE",
"HOME.APP", "HDMI_1", "HDMI_2", "HDMI_3", "HDMI_4", "HDMI_5", "HDMI_6",
"HDMI_7", "HDMI_8", "MATRIX_1", "MATRIX_2", "MATRIX_3", "MATRIX_4", "MATRIX_5",
"MATRIX_6", "MATRIX_7", "MATRIX_8", "MATRIX_9", "MATRIX_10", "MATRIX_11",
@ -262,9 +269,9 @@ ml_command_type_dict = dict(
(0x3C, "TIMER"),
(0x40, "CLOCK"),
(0x44, "TRACK_INFO"),
# LOCKmANAGER_COMMAND: Lock to Determine what device issues source commands
# reference: https://tidsskrift.dk/daimipb/article/download/7043/6004/0
(0x45, "GOTO_SOURCE"),
# LOCKMANAGER_COMMAND: Lock to Determine what device issues source commands
# reference: https://tidsskrift.dk/daimipb/article/download/7043/6004/0
(0x5C, "LOCK_MANAGER_COMMAND"),
(0x6C, "DISTRIBUTION_REQUEST"),
(0x82, "TRACK_INFO_LONG"),
@ -301,7 +308,8 @@ ml_command_type_dict = dict(
# On power up all devices send out a request key telegram. If
# no lock manager is allocated the devices send out a key_lost telegram. The Video Master (or Power
# Master in older implementations) then asserts a NEW_LOCKmANAGER telegram and assumes responsibility
# for LOCKmANAGER_COMMAND telegrams until a key transfer occurs.
# for LOCKMANAGER_COMMAND telegrams until a key transfer occurs.
# reference: https://tidsskrift.dk/daimipb/article/download/7043/6004/0
(0x12, "KEY_LOST"), # ?
# Unknown command with payload of length 1.
# bit 0: unknown
@ -344,9 +352,11 @@ ml_device_dict = dict(
(0x83, "ALL LINK DEVICES"),
(0x80, "ALL"),
(0xF0, "MLGW"),
(0x21, "BLC NL/ML"),
# Power Master exists in older (pre 1996?) ML implementations. Later revisions enforced the Video Master
# as lock key manager for the system and the concept was phased out. If your system is older than 2000
# you may see this device type on the network.
# reference: https://tidsskrift.dk/daimipb/article/download/7043/6004/0
(0xFF, "POWER MASTER"), # ?
]
)
@ -433,7 +443,7 @@ destselectordict = dict(
[
(0x00, "Video Source"),
(0x01, "Audio Source"),
(0x05, "V.TAPE/V.MEM"),
(0x05, "Secondary Video Source (V.TAPE/V.MEM)"),
(0x0F, "All Products"),
(0x1B, "MLGW"),
]
@ -451,9 +461,9 @@ mlgw_soundstatusdict = dict([(0x00, "Not muted"), (0x01, "Muted")])
mlgw_speakermodedict = dict(
[
(0x01, "Center channel"),
(0x02, "2ch stereo"),
(0x02, "2 channel stereo"),
(0x03, "Front surround"),
(0x04, "4ch stereo"),
(0x04, "4 channel stereo"),
(0x05, "Full surround"),
(0xFD, "<all>"), # Dummy for 'Listen for all modes'
]

View file

@ -193,7 +193,10 @@ class MLCLIClient(asynchat.async_chat):
# Decode header
message = OrderedDict()
self._get_device_info(message, telegram)
message["from_device"] = self._get_device_name(self._dictsanitize(CONST.ml_device_dict, telegram[1]))
if 'Device' not in message:
# If ML telegram has been matched to a Masterlink node in the devices list then the 'from_device'
# key is redundant - it will always be identical to the 'Device' key
message["from_device"] = self._dictsanitize(CONST.ml_device_dict, telegram[1])
message["from_source"] = self._dictsanitize(CONST.ml_selectedsourcedict, telegram[5])
message["to_device"] = self._get_device_name(self._dictsanitize(CONST.ml_device_dict, telegram[0]))
message["to_source"] = self._dictsanitize(CONST.ml_selectedsourcedict, telegram[4])
@ -252,6 +255,10 @@ class MLCLIClient(asynchat.async_chat):
message["State_Update"]["sourceID"] = telegram[10]
message["State_Update"]["source_type"] = self._get_type(CONST.ml_selectedsource_type_dict, telegram[10])
message["State_Update"]["command"] = self._dictsanitize(CONST.beo4_commanddict, telegram[11])
if message["State_Update"]["command"] == 'Go/Play':
message["State_Update"]["state"] = 'Play'
elif message["State_Update"]["command"] in ['Standby', 'Stop', 'Wind', 'Rewind']:
message["State_Update"]["state"] = message["State_Update"]["command"]
# audio track info long
if message.get("payload_type") == "TRACK_INFO_LONG":
@ -304,6 +311,8 @@ class MLCLIClient(asynchat.async_chat):
message["State_Update"]["source"] = source
message["State_Update"]["sourceID"] = telegram[11]
message["State_Update"]["source_type"] = self._get_type(CONST.ml_selectedsource_type_dict, telegram[11])
# This device is playing
message["State_Update"]["state"] = 'Play'
else:
message["State_Update"]["subtype"] = "Undefined: " + self._hexbyte(telegram[9])
@ -319,6 +328,8 @@ class MLCLIClient(asynchat.async_chat):
message["State_Update"]["source"] = source
message["State_Update"]["sourceID"] = telegram[11]
self._get_channel_track(telegram, message)
# Device sending goto source command is playing
message["State_Update"]["state"] = 'Play'
# remote request
if message.get("payload_type") == "MLGW_REMOTE_BEO4":

View file

@ -103,6 +103,9 @@ class MLConfig:
for selectCmd in source["selectCmds"]:
if gateway_type == 'blgw':
# get source information from the BLGW config file
if str(source['sourceId']) == '':
source_id = self._srcdictsanitize(CONST.beo4_commanddict, source['selectID']).upper()
else:
source_id = str(source['sourceId'].split(':')[0])
source_id = self._srcdictsanitize(CONST.blgw_srcdict, source_id).upper()
device['Sources'][str(source["name"])]['source'] = source_id
@ -142,7 +145,7 @@ class MLConfig:
self.log.info('\tFound ' + str(len(CONST.available_sources)) + ' Available Sources [Name, Type]:')
for i in range(len(CONST.available_sources)):
self.log.info('\t\t' + str(list(CONST.available_sources[i])))
self.log.info('Done!\n')
self.log.info('\tDone!\n')
self.log.debug(json.dumps(CONST.gateway, indent=4))
self.log.debug(json.dumps(CONST.rooms, indent=4))
@ -169,6 +172,7 @@ class MLConfig:
mlcli.last_message['State_Update']['command'] == "Light Timeout":
device['ML_ID'] = mlcli.last_message.get('to_device')
device['Serial_num'] = 'NA'
self.log.info("\tMasterLink ID of product " +
device.get('Device') + " is " + device.get('ML_ID') + ".\n")
test = False
@ -177,6 +181,11 @@ class MLConfig:
else:
# If this is a NetLink product then it has a serial number and no ML_ID
if device['Device'] == 'BeoMaster 7000':
device['ML_ID'] = "AUDIO MASTER"
self.log.info("\tNetworkLink ID of product " + device.get('Device') + " is " +
device.get('Serial_num') + ". MasterLink ID manually assigned to AUDIO MASTER.\n")
else:
device['ML_ID'] = 'NA'
self.log.info("\tNetworkLink ID of product " + device.get('Device') + " is " +
device.get('Serial_num') + ". No MasterLink ID assigned.\n")