class Mpd_factory(ReconnectingClientFactory, Client): ''' Twisted Reconnecting client factory for mpd server convert all UPnP and OpenHome service function to mpd remote calls ''' _mtlist = '' _muted = True _rate = "1" _state = "pending" _track_duration = '0:00:00.000' ohduration = 0 _track_URI = '' _volume = 100 active_source = 0 attributes = 'Info Time Volume' balance = 0 balancemax = 10 bitdepth = 0 bitrate = 0 cancelplay = False codecname = 'MP3' counter = 0 detailscount = 0 fade = 0 fademax = 10 idArray = '' lossless = False maxid = 0 max_volume = 100 metadata = {} metadata_str = 'NOT_IMPLEMENTED' metatext = '' metatextcount = 0 mimetypes = [ 'audio/aac', 'audio/mp4', 'audio/mpeg', 'audio/mpegurl', 'audio/vnd.rn-realaudio', 'audio/vorbis', 'audio/x-flac', 'audio/x-mp3', 'audio/x-mpegurl', 'audio/x-ms-wma', 'audio/x-musepack', 'audio/x-oggflac', 'audio/x-pn-realaudio', 'audio/x-scpls', 'audio/x-speex', 'audio/x-vorbis', 'audio/x-wav', 'application/x-ogm-audio', 'audio/x-vorbis+ogg', 'audio/ogg' ] mtlist = '' name = "MpdPlayer" oh_state = 'Buffering' old_vol = 0 parent = None reltime = '0:00:00.000' repeat = False room = 'Home' samplerate = 0 seconds = 0 shuffle = False songid = 0 sources = ['Playlist'] sourceindex = 0 upnp_state = "TRANSITIONNING" volumeunity = 3 volumemillidbperstep = 600 timer = None token = 0 trackcount = 0 tracksmax = 0 transport_actions = ['PLAY'] # ohduration = 0 def __init__(self, cover_dir, net_type='lan'): self.connected = False self.net_type = net_type self.proto = MpdProtocol() self.proto._event = self.dispatch_event self.status = {'state': 'stop'} self.playlist = Playlist(self) self.waiting = False self.calls = [] self.cover_dir = cover_dir self._sources = [ OrderedDict( sorted({ 'Name': '_'.join((self.name, n)), 'Type': n, 'Visible': True }.items(), key=lambda t: t[0])) for n in self.sources ] self.sourcexml = dict2xml( {'SourceList': [{ 'Source': n } for n in self._sources]}) self.protocolinfo = self.mtlist = 'http-get:*:' + ''\ .join([':*,http-get:*:'.join(self.mimetypes)]) + ':*' def buildProtocol(self, addr): return self.proto ''' Internal functions ''' def dispatch_event(self, events=None): def set_connected(): self.connected = True p = self.call('plchanges', self.playlist.plsversion) p.addCallback(self.playlist.update) d = None for evt in events: if evt == 'Error': log.error('bad event: {name}', name=events[evt]) return elif evt == 'changed': chg = events[evt] else: # log.msg(events[evt]) return if chg == 'disconnected': self.connected = False continue if chg == 'playlist': log.error('update from: %s' % self.playlist.plsversion) d = self.call('plchanges', self.playlist.plsversion) d.addCallback(self.playlist.update) else: d = self.call('status') if chg == 'connected': d.addCallback(self.filter_event) reactor.callLater(1, set_connected) # @UndefinedVariable elif chg == 'mixer': d.addCallback(self.filter_event, 'volume') elif chg == 'player': d.addCallback(self.filter_event) return d def filter_event(self, data, filtr=None): if data is None: return if not isinstance(data, list): data = [data] if filtr is not None: for d in data: self.status.update(d) self.changed_state({filtr: self.status[filtr]}) else: dic = {} for d in data: for it in d.iteritems(): if it not in self.status.items(): self.status.update(dict([it])) dic.update(dict([it])) if it[0] == 'playlistlength': self.playlist.length = int(it[1]) self.changed_state(dic) def call(self, command=None, params=None): # log.error('register %s' % command) d = defer.Deferred() if command is not None: self.proto.addCallback(d) if not self.connected: if command: self.calls.append((command, params)) else: if len(self.calls) > 0: if command: self.calls.append((command, params)) else: d = None command, params = self.calls.pop(0) if params is not None: c = ' '.join([command, str(params)]).encode('utf-8') else: c = command.encode('utf-8') if self.proto.idle: self.proto.noidle() self.proto.sendLine(c) # log.error('send %s' % c) if len(self.calls) > 0: self.call() return d def changed_state(self, state): changed = state.keys() if 'state' in changed: self.set_state(state['state']) if "volume" in changed: log.debug('volume changed') vol = int(state['volume']) if vol != self._volume: if vol != 0: self._volume = vol muted = False else: muted = True log.debug('send volume') self.oh_eventVOLUME(self._volume, 'volume') self.upnp_eventRCS(self._volume, 'volume') if muted is not self._muted: self._muted = muted self.upnp_eventRCS(self._muted, 'mute') self.oh_eventVOLUME(int(self._muted), 'mute') if 'songid' in changed: self.playlist.current_track(state['songid']) if 'repeat' in changed: # log.debug('************repeat***********: %s' % # bool(int(state['repeat']))) if self.repeat != bool(int(state['repeat'])): self.repeat = bool(int(state['repeat'])) if not self.shuffle: self.upnp_eventAV( 'REPEAT_ALL' if self.repeat else 'NORMAL', 'currentplayMode') else: self.upnp_eventAV( 'REPEAT_ALL SHUFFLE' if self.repeat else 'NORMAL SHUFFLE', 'currentplaymode') self.oh_eventPLAYLIST(self.repeat, 'repeat') if 'random' in changed: log.debug('************shuffle***********: %s' % bool(int(state['random']))) if self.shuffle != bool(int(state['random'])): self.shuffle = bool(int(state['random'])) if not self.repeat: self.upnp_eventAV( 'NORMAL SHUFFLE' if self.shuffle else 'NORMAL', 'currentplaymode') else: self.upnp_eventAV( 'REPEAT_ALL SHUFFLE' if self.shuffle else 'NORMAL SHUFFLE', 'currentplaymode') self.oh_eventPLAYLIST(self.shuffle, 'shuffle') if 'elapsed' in changed: if self.timer is not None: self.timer.set(float(state['elapsed'])) if 'playlist' in changed: self.token = int(state['playlist']) # self.playlist.plsversion = state['playlist'] if 'playlistdata' in changed: self.playlist.update(state['playlistdata']) if 'bitrate' in changed: self.detailscount += 1 self.bitrate = int(state['bitrate']) self.oh_eventINFO(self.bitrate, 'bitrate') if 'audio' in changed: try: sr = int(state['audio'].split(':')[0]) self.samplerate = sr self.oh_eventINFO(sr, 'samplerate') except: log.critical('Bad Samplerate: %s' % state['audio'].split(':')[0]) try: bd = int(state['audio'].split(':')[1]) self.bitdepth = bd self.oh_eventINFO(bd, 'bitdepth') except: log.critical('Bad Bitdepth: %s' % state['audio'].split(':')[1]) def update_state(self): # log.err('Update State: %s' % self.mpd.status['state']) self.set_state(self.status['state']) def update_mimetypes(self): self.set_mimetypes(self.mimetypes) def upnp_eventAV(self, evt, var): pass def upnp_eventCM(self, evt, var): pass def upnp_eventRCS(self, evt, var): pass def oh_eventPLAYLIST(self, evt, var): pass def oh_eventINFO(self, evt, var): pass def oh_eventTIME(self, evt, var): pass def oh_eventVOLUME(self, evt, var): pass def getMetadata(self): return self.call('playlistid', self.status['songid']) def getMimeTypes(self): return self.call('decoders') def get_state(self): return self._state def get_rate(self): return self._rate def get_track_duration(self): try: duration = self._track_duration except: duration = '00:00:00' return duration def get_track_URI(self): try: uri = self._track_URI except: uri = '' return uri def get_track_md(self): return self.metadata_str def get_sticker(self, url, dic={}): def got_sticker(stklist, dic): if not isinstance(stklist, list): stklist = [stklist] for sticker in stklist: try: dic.update({ sticker['sticker'].split('=')[0], sticker['sticker'].split('=')[1] }) except: continue return dic d = self.call('sticker', ' '.join(('list song', url.join('"' * 2)))) d.addBoth(got_sticker, dic) return d def get_relcount(self): return 2147483647 def get_abscount(self): return 2147483647 def get_abstime(self): return '00:00:00' def get_reltime(self, fmt='UPNP'): if self.timer is not None: if fmt == 'UPNP': t = mpdtime_to_upnptime(self.timer.get()) elif fmt == 'seconds': t = int(self.timer.get()) else: # msec t = self.timer.get() else: if fmt == 'UPNP': t = self.reltime else: t = self.seconds return t def get_volume(self): def noVolume(err): if self._muted: return 0 else: return self._volume def convert_volume(vol): self._volume = int(float(vol) * 100) return self._volume d = self.mediaplayer.get("Volume", interf='org.mpris.MediaPlayer2.Player') d.addCallbacks(convert_volume, noVolume) return d def get_transport_actions(self): return {','.join(self.transport_actions)} def set_mimetypes(self, mtlist): if self._mtlist != mtlist: self._mtlist = mtlist self.mtlist = 'http-get:*:' + ''\ .join([':*,http-get:*:'.join(mtlist)]) + ':*' self.upnp_eventCM(self.mtlist, 'sinkprotocolinfo') self.oh_eventPLAYLIST(self.mtlist, 'protocolinfo') def set_metadata(self, metadata): if metadata != self.metadata: self.metadata.update(metadata) if 'duration' in metadata.keys(): self._track_duration = metadata['duration'] if 'url' in metadata.keys(): self._track_URI = metadata['url'] if 'file' in metadata.keys(): self._track_URI = metadata['file'] def set_reltime(self, t): self.seconds = int(float(t)) self.reltime = mpdtime_to_upnptime(t) # log.err('seconds: %s' % t) self.timer = None def set_state(self, state): log.debug("SET NEW STATE : %s " % state) if state == self._state: return self._state = state if state == 'pause': # print(self.transport_actions) self.transport_actions = ['PLAY', 'STOP'] # self.transport_actions.remove('PAUSE') # self.transport_actions.append('PLAY') if self.timer is not None: self.timer.stop() d = self.call('status') d.addCallback(lambda st: st['elapsed']) d.addCallbacks( self.set_reltime, lambda ignored: self.set_reltime(self.timer.get())) self.upnp_state = 'PAUSED_PLAYBACK' self.oh_state = 'Paused' elif state == 'play': self.transport_actions = ['STOP', 'PAUSE', 'SEEK'] self.changed_state({'volume': self.status['volume']}) self.timer = Timer() self.timer.set(self.seconds) d = self.call('status') d.addCallbacks(lambda st: st['elapsed']) d.addCallbacks(lambda t: self.timer.set(float(t)), lambda ignored: self.timer.set(0.000)) self.upnp_state = 'PLAYING' self.oh_state = 'Playing' elif state == 'stop': self.transport_actions = ['PLAY'] self.set_reltime(0) self.upnp_state = 'STOPPED' self.oh_state = 'Stopped' elif state == 'pending': self.upnp_state = 'TRANSITIONNING' self.oh_state = 'Buffering' else: return self.upnp_eventAV(self.upnp_state, 'transportstate') self.oh_eventPLAYLIST(self.oh_state, 'transportstate') def set_songid(self, songid): self.songid = songid['Id'] return self.songid def set_position(self, newpos, fmt='UPNP'): def transition(obj): current_state = self._state self.set_state('pending') reactor.callLater( 0.5, # @UndefinedVariable self.set_state, current_state) if fmt == 'UPNP': pos = upnptime_to_mpdtime(newpos) else: pos = newpos log.debug('seek to %s' % pos) d = self.call('seekcur', pos) d.addCallback(transition) return d def set_position_relative(self, delta, fmt='UPNP'): newpos = int(self.get_reltime('Seconds')) + int(delta) self.set_position(newpos, fmt) def set_track_URI(self, uri, md=''): log.debug("set track uri : %s " % uri) try: log.debug("current uri : %s " % self._track_URI) except: pass if uri != self._track_URI: self._track_URI = uri self.metadata_str = md self.set_metadata(didl_decode(md.encode('utf-8'))) d = self.call('addid', uri) d.addCallback(self.set_songid) d.addCallback(self.play) def playing(self, *ret): log.debug('playing...') self.set_state('play') def playindex(self, index): return self.play(songid=self.playlist[int(index)]) def playpause(self): if self._state == 'pause': return self.play() else: return self.pause() def insert_metadata(self, md): dic = didl_decode(md) # log.err(dic) for i, tag in enumerate(dic.keys()): if tag.lower() in ('class', 'restricted', 'id', 'duration', 'parentid', 'protocolinfo', 'url', 'ownerudn'): continue if not isinstance(dic[tag], str): continue reactor.callLater( # @UndefinedVariable float(i) / 2, self.call, ' '.join( ('sticker', 'set song', dic['url'].join('"' * 2), tag, '"' + dic[tag] + '"'))) def delete(self, songid): self.call('deleteid', str(songid)) def clear(self): self.call('clear') def volUp(self): if self._volume == 0: vol = self.r_get_volume() try: newvol = vol + 5 except: newvol = self._volume + 5 return self.r_set_volume(newvol, 'Master') else: if self._volume > 95: return newvol = self._volume + 5 return self.r_set_volume(newvol, 'Master') def volDown(self): if self._volume > 5: newvol = self._volume - 5 return self.r_set_volume(newvol, 'Master') else: newvol = self._volume self._volume = 0 return self.r_set_volume(newvol, '0') ''' UPNP wrapped functions ''' # onDemand vendor method def r_get_room(self): return self.room ''' AVTransport and OpenHome Playlist ''' def r_delete_all(self): return self.clear() def r_delete_id(self, value): return self.delete(value) def r_get_current_transport_actions(self, instanceID): return self.get_transport_actions() def r_get_media_info(self, instanceID): return ( str(len(self.playlist)), self.get_track_duration(), self.get_track_URI(), self.get_track_md(), '', '', 'UNKNOWN', 'UNKNOWN', 'UNKNOWN', ) def r_get_media_info_ext(self, instanceID): return ( str(len(self.playlist)), 'TRACK_AWARE', self.get_track_duration(), self.get_track_URI(), self.get_track_md(), '', '', 'UNKNOWN', 'UNKNOWN', 'UNKNOWN', ) def r_get_position_info(self, instanceID): return ( self.player.get_track(), self.get_track_duration(), self.get_track_md(), self.player.get_track_URI(), self.player.get_reltime(), self.player.get_abstime(), self.player.get_relcount(), self.player.get_abscount(), ) def r_get_transport_info(self, instanceID): return ( self.get_state(), 'OK', self.get_rate(), ) def r_id(self): return self.songid def r_id_array(self): return ( self.token, self.idArray, ) def r_id_array_changed(self, token): if token != self.token: return 1 return 0 def r_insert(self, afterid, url, metadata, checked=False): log.debug('Insert :%s -- %s -- %s' % (afterid, url, metadata)) def inserted(res, md): log.debug(res) return res['Id'] if 'youtube' in url and not checked: # eclipse workaround !!! y = os.environ y.update({'PYTHONPATH': '/usr/bin/python'}) # /eclipse workaround d = utils.getProcessOutput('/usr/bin/youtube-dl', ['-g', '-f', 'bestaudio', url], env=y, reactor=reactor) d.addCallback(lambda u: self.insert( u.split('\n')[0], afterid, metadata, True)) return d if len(self.playlist.tracks) == 0: d = self.call('addid', url) elif int(afterid) == 0: d = self.call('addid', url + ' 0') else: log.critical('crash ? %s' % self.playlist.tracks) log.critical('here ? %s' % str(self.playlist.tracks.index(int(afterid)) + 1)) d = self.call( 'addid', ' '.join( (url.encode('utf-8'), str(self.playlist.tracks.index(int(afterid)) + 1)))) d.addCallback(inserted, metadata) return d def r_next(self, instanceID=0): if self._state not in ('play', 'pause'): if self.songid == 0: self.r_play(1) else: self.r_play() else: self.call('next') def r_play(self, instanceID=0, speed=1, songid=None, ignored=None): log.debug('entering play...') def success(result): return None if self.cancelplay: self.cancelplay = False else: if songid is not None: # log.err(songid) d = self.call('playid', str(songid)) else: if self._state == 'pause': d = self.call('pause', '0') else: d = self.call('playid', self.songid) d.addCallback(self.playing) def r_pause(self, instanceID=0): print('pause') def paused(ret): if self._state == 'play': self.set_state('pause') d = self.call('pause', '1') d.addCallback(paused) return d def r_previous(self, instanceID=0): self.call('previous') def r_protocol_info(self): return self.protocolinfo def r_read(self, value): log.debug('read') d = self.playlist.get_track(value) return (d, ) def r_read_list(self, items): log.debug('readlist') d = self.playlist.get_tracks([int(track) for track in items.split()]) return d def r_repeat(self): return self.repeat def r_record(self, instanceID): raise NotImplementedError() def r_seek(self, instanceID, unit, pos): log.debug('seek: %s %s' % (unit, pos)) self.set_position(pos) def r_seek_id(self, value): log.debug('SeekId') return self.r_play(songid=value) def r_seek_index(self, value): log.debug('Seekindex') return self.playindex(value) def r_seek_second_absolute(self, value): return self.set_position(value, 'SECONDS') def r_seek_second_relative(self, value): return self.set_position_relative(value, 'SECONDS') def r_set_repeat(self, repeat): self.call('repeat', str(int(repeat))) self.changed_state({'repeat': str(int(repeat))}) def r_set_shuffle(self, shuffle): self.call('random', str(int(shuffle))) self.changed_state({'random': str(int(shuffle))}) def r_set_avtransport_uri(self, instanceID, uri, uri_metadata): self.set_track_URI(uri, uri_metadata) def r_shuffle(self): return self.shuffle def r_stop(self, instanceID=0): def stopped(ret): self.set_state('stop') if self._state != 'STOPPED': d = self.call('stop') self.reltime = '00:00:00' d.addCallback(stopped) def r_tracks_max(self): return self.tracksmax def r_transport_state(self, instanceID=0): if self.parent.type == 'Source': return self.oh_state return self.upnp_state ''' OpenHome Info ''' def r_counters(self): return ( self.trackcount, self.detailscount, self.metatextcount, ) def r_track(self): return ( self._track_URI, self.metadata_str, ) def r_details(self): return ( self.ohduration, self.bitrate, self.bitdepth, self.samplerate, self.lossless, self.codecname, ) def r_metatext(self): return self.metatext ''' Rendering Control and Open Home Volume ''' def r_volume(self): return self._volume def r_set_volume(self, volume, channel=0): volume = str(volume) d = self.call('setvol', volume) d.addErrback(log.critical, 'Set Volume Error : %s - %d' % (channel, int(volume))) reactor.callLater( 0.1, # @UndefinedVariable self.changed_state, {'volume': str(volume)}) def r_volume_inc(self): return self.volUp() def r_volume_dec(self): return self.volDown() def r_volume_limit(self): return self.max_volume def r_balance(self): return self.balance def r_balance_inc(self): # TODO self.balance += 1 def r_balance_dec(self): # TODO self.balance -= 1 def r_set_fade(self, value): # TODO self.fade = int(value) def r_fade_inc(self): # TODO self.fade += 1 def r_fade_dec(self): # TODO self.fade -= 1 def r_mute(self): return self._muted def r_set_mute(self, mute): if mute is not self._muted: self._muted = mute if mute: self.old_vol = self._volume self.r_set_volume('0') else: self.r_set_volume(self.old_vol) def r_characteristics(self): return self.max_volume, self.volumeunity, self.max_volume,\ self.volumemillidbperstep, self.balancemax, self.fademax ''' OpenHome Time ''' def r_time(self): return (self.trackcount, self.ohduration, self.get_reltime('seconds')) ''' OpenHome Product ''' def r_manufacturer(self=None): log.debug('Manufacturer from Product') return ( self.parent.manufacturer, self.parent.manufacturerInfo, self.parent.manufacturerURL, ''.join((self.parent.getLocation(get_default_v4_address()), '/pictures/icon.png')), ) def r_model(self=None): log.debug('Model from Product') return (self.parent.modelName, self.parent.modelDescription, self.parent.manufacturerURL, ''.join(( self.parent.getLocation(get_default_v4_address()), '/pictures/', self.parent.modelName, '.png', ))) def r_product(self): log.debug('Product from Product') return self.room, self.parent.modelName, self.parent.modelDescription,\ self.parent.manufacturerURL,\ ''.join((self.parent.getLocation(get_default_v4_address()), '/pictures/', self.parent.modelName, '.png',)) def r_standby(self): log.debug('Standby from Product') return self.standby def r_set_standby(self, val=None): log.debug('SetStandby from Product') if val is None: return self.standby raise NotImplementedError() def r_source_count(self): log.debug('SourceCount from Product') return len(self.sources) def r_source_xml(self, *args, **kwargs): log.debug('SourceXml from Product') return self.sourcexml # return dict2xml({'SourceList': [{'Source': n} for n in self.sources]}) def r_source_index(self): log.debug('SourceIndex from Product') return self.sourceindex def r_set_source_index(self, idx=None): log.debug('SetSourceIndex from Product') if idx is None: return self.sourceindex else: try: self.sourceindex = int(idx) self.oh_product_event('sourceindex', self.sourceindex) except: for i, source in enumerate(self.sources.keys()): if source['Name'] == idx: self.sourceindex = i self.oh_product_event('sourceindex', self.sourceindex) return log.critical('Unknown Source: %s' % idx) def r_set_source_index_by_name(self, value): log.debug('SetSourceIndexByName from Product') return self.set_source_index(value) def r_source(self, idx): idx = int(idx) return ( self.parent.sources[idx].friendlyName, self.parent.sources[idx].type, True, self.parent.sources[idx].name, ) def r_attributes(self): return self.attributes def r_source_xml_change_count(self): raise NotImplementedError()
class Mpdclient(internet.TCPClient): # @UndefinedVariable ''' classdocs ''' _state = "pending" upnp_state = "TRANSITIONNING" oh_state = 'Buffering' _track_URI = '' _managed = False _errors = 0 _metadata = {} _playlist = [] _volume = 100 _rate = "1" _mtlist = '' mtlist = '' _muted = True _track_duration = '0:00:00.000' name = "MpdPlayer" cancelplay = False reltime = '0:00:00.000' seconds = 0 repeat = False shuffle = False counter = 0 tracksmax = 0 metadata = {} playlist = [] idArray = '' maxid = 0 metadata_str = 'NOT_IMPLEMENTED' songid = 0 transport_actions = ['PLAY'] timer = None token = 0 max_volume = 100 def __init__(self, addr='127.0.0.1', port='6600', **kwargs): ''' Constructor ''' self.addr = addr print addr print port self.port = int(port) self.mpd = MpdFactory(self.changed_state) internet.TCPClient.__init__(self, # @UndefinedVariable addr, self.port, self.mpd) def startService(self): internet.TCPClient.startService(self) # @UndefinedVariable self.update_state() self.update_metadata() self.update_mimetypes() def changed_state(self, state): changed = state.keys() if 'state' in changed: self.set_state(state['state']) if "volume" in changed: log.msg('volume changed', loglevel=logging.DEBUG) vol = int(state['volume']) if vol != self._volume: if vol != 0: self._volume = vol muted = False else: muted = True log.msg('send volume', loglevel=logging.DEBUG) self.oh_eventVOLUME(self._volume, 'volume') self.upnp_eventRCS(self._volume, 'Volume') if muted is not self._muted: self._muted = muted self.upnp_eventRCS(self._muted, 'Mute') self.oh_eventVOLUME(int(self._muted), 'mute') if 'songid' in changed: self.update_metadata(state['songid']) if 'repeat' in changed: if self.repeat != bool(state['repeat']): self.repeat = bool(state['repeat']) if not self.shuffle: self.upnp_eventAV( 'REPEAT_ALL' if self.repeat else 'NORMAL', 'CurrentPlayMode') else: self.upnp_eventAV('REPEAT_ALL SHUFFLE' if self.repeat else 'NORMAL SHUFFLE', 'CurrentPlayMode') self.oh_eventPLAYLIST(self.repeat, 'repeat') if 'random' in changed: if self.shuffle != bool(state['repeat']): self.shuffle = bool(state['repeat']) if not self.repeat: self.upnp_eventAV( 'NORMAL SHUFFLE' if self.shuffle else 'NORMAL', 'CurrentPlayMode') else: self.upnp_eventAV( 'REPEAT_ALL SHUFFLE' if self.shuffle else 'NORMAL SHUFFLE', 'CurrentPlayMode') self.oh_eventPLAYLIST(self.shuffle, 'shuffle') if 'elapsed' in changed: if self.timer is not None: self.timer.set(float(state['elapsed'])) if 'playlist' in changed: self.token = int(state['playlist']) if 'playlistdata' in changed: self.update_playlist(state['playlistdata']) if 'bitrate' in changed: self.oh_eventINFO(int(state['bitrate']), 'bitrate') if 'audio' in changed: try: sr = int(state['audio'].split(':')[0]) self.oh_eventINFO(sr, 'samplerate') except: log.err('Bad Samplerate: %s' % state['audio'].split(':')[0]) try: bd = int(state['audio'].split(':')[1]) self.oh_eventINFO(bd, 'bitdepth') except: log.err('Bad Bitdepth: %s' % state['audio'].split(':')[1]) def update_state(self): # log.err('Update State: %s' % self.mpd.status['state']) self.set_state(self.mpd.status['state']) def update_playlist(self, newpl): self.playlist = newpl self.idArray = id_array(newpl) self.oh_eventPLAYLIST(self.idArray, 'idarray') # d.addCallback(self.oh_eventPLAYLIST, 'idarray') def update_metadata(self, songid=None): log.msg('update metadata') def getmd(md): if isinstance(md, list): nd = {} for d in md: nd.update(d) md = nd if md != self._metadata: self._metadata = md # log.err(md) self.metadata.update(mpd_decode(self._metadata)) if self._track_duration != self.metadata['duration']: self._track_duration = self.metadata['duration'] self.upnp_eventAV(self._track_duration, 'CurrentTrackDuration') sec = upnptime_to_mpdtime(self._track_duration) self.oh_eventINFO(sec, 'duration') self.oh_eventTIME(sec, 'duration') if self.songid != self.metadata['id']: self.songid = self.metadata['id'] self.upnp_eventAV(int(self.songid), 'CurrentTrack') self.oh_eventPLAYLIST(int(self.songid), 'id') self.oh_eventTIME(1, 'trackcount') if 'url' in self.metadata.keys(): self._track_URI = self.metadata['url'] self.upnp_eventAV(self._track_URI, 'AVTransportURI') self.oh_eventINFO(self._track_URI, 'uri') self.upnp_eventAV(self._track_URI, 'CurrentTrackURI') try: self.oh_eventINFO( self.metadata['codec'].upper(), 'codecname') if self.metadata['codec'].lower() in ['flac', 'm4a']: self.oh_eventINFO(1, 'lossless') else: self.oh_eventINFO(0, 'lossless') except KeyError: pass self.metadata_str = didl_encode(self.metadata) self.oh_eventINFO(self.metadata_str, 'metadata') self.upnp_eventAV(self.metadata_str, 'AVTransportURIMetaData') if self.tracksmax == 0: self.tracksmax = 10000 self.oh_eventPLAYLIST(self.tracksmax, 'tracksmax') if songid is not None: d = self.mpd.call('playlistid', songid) else: d = self.mpd.call('currentsong') d.addCallback(getmd) def update_mimetypes(self): self.set_mimetypes(self.mpd.mimetypes) def upnp_eventAV(self, evt, var): pass def upnp_eventCM(self, evt, var): pass def upnp_eventRCS(self, evt, var): pass def oh_eventPLAYLIST(self, evt, var): pass def oh_eventINFO(self, evt, var): pass def oh_eventTIME(self, evt, var): pass def oh_eventVOLUME(self, evt, var): pass def getMetadata(self): return self.mpd.call('playlistid', self.mpd.status['songid']) def getMimeTypes(self): return self.mpd.call('decoders') def get_state(self): return self._state def get_rate(self): return self._rate def get_track_duration(self): try: duration = self._track_duration except: duration = '00:00:00' return duration def get_track_URI(self): try: uri = self._track_URI except: uri = '' return uri def get_track_md(self): return self.metadata_str def get_sticker(self, url, dic={}): def got_sticker(stklist, dic): if not isinstance(stklist, list): stklist = [stklist] for sticker in stklist: try: dic.update( {sticker['sticker'].split('=')[0], sticker['sticker'].split('=')[1]}) except: continue return dic d = self.mpd.call('sticker', ' '.join(('list song', url.join('"'*2)))) d.addBoth(got_sticker, dic) return d def get_tracks(self, tracks): def got_tracks(tracks): if not isinstance(tracks, list): tracks = [tracks] sl = [] for track in tracks: t = mpd_decode(track) sl.append(self.get_sticker(t['url'], t)) return defer.gatherResults(sl) def generate_tracklist(tracks, tracklist=None): # log.err(tracks) if not isinstance(tracks, list): tracks = [tracks] tl = et.Element('TrackList') for idx, track in enumerate(tracks): # log.err(track) if isinstance(track, dict): track = mpd_decode(track) else: # log.err(track) nd = {} for d in track: # log.err(d) nd.update(d) track = mpd_decode(nd) # log.msg(nd) en = et.Element('Entry') i = et.Element('Id') if not 'id' in track: if tracklist: track.update({'id': str(tracklist[idx])}) else: log.err(track) i.text = track['id'].decode('utf-8') en.append(i) uri = et.Element('Uri') uri.text = track['url'].decode('utf-8') en.append(uri) md = et.Element('Metadata') md.text = didl_encode(track) en.append(md) tl.append(en) return et.tostring(tl) # if tracks == self.playlist: # d = self.mpd.call('playlistid') # d.addCallback(generate_tracklist, tracks) # else: tl = [] for track in tracks: tl.append(self.mpd.call('playlistid', str(track))) d = defer.gatherResults(tl) # d.addCallback(got_tracks) d.addCallback(generate_tracklist) return d def get_track(self, track=None): def got_result(res): uri = res['url'] return (uri, didl_encode(res)) if track is None: d = defer.succeed((self._track_URI, self.metadata_str)) else: d = self.mpd.call('playlistid', str(track)) d.addCallback(mpd_decode) d.addCallback(got_result) return d def get_relcount(self): return 2147483647 def get_abscount(self): return 2147483647 def get_abstime(self): return '00:00:00' def get_reltime(self, fmt='UPNP'): if self.timer is not None: if fmt == 'UPNP': t = mpdtime_to_upnptime(self.timer.get()) elif fmt == 'seconds': t = int(self.timer.get()) else: # msec t = self.timer.get() else: if fmt == 'UPNP': t = self.reltime else: t = self.seconds # log.err('reltime: %s' % t) return t def get_volume(self): def noVolume(err): if self._muted: return 0 else: return self._volume def convert_volume(vol): self._volume = int(float(vol) * 100) log.msg("volume= %d" % self._volume, loglevel=logging.DEBUG) return self._volume d = self.mediaplayer.get("Volume", interf='org.mpris.MediaPlayer2.Player') d.addCallbacks(convert_volume, noVolume) return d def get_transport_actions(self): return {','.join(self.transport_actions)} def set_volume(self, channel, volume): volume = str(volume) d = self.mpd.call('setvol', volume) d.addErrback( log.msg, 'Set Volume Error : %s - %d' % (channel, int(volume))) reactor.callLater(0.1, # @UndefinedVariable self.changed_state, {'volume': str(volume)}) def set_mimetypes(self, mtlist): if self._mtlist != mtlist: self._mtlist = mtlist self.mtlist = 'http-get:*:' + ''\ .join([':*,http-get:*:'.join(mtlist)]) + ':*' self.upnp_eventCM(self.mtlist, 'SinkProtocolInfo') self.oh_eventPLAYLIST(self.mtlist, 'protocolinfo') def set_metadata(self, metadata): if metadata != self.metadata: self.metadata.update(metadata) if 'duration' in metadata.keys(): # log.err('duration set to %s by metadata' # % metadata['duration']) self._track_duration = metadata['duration'] if 'url' in metadata.keys(): self._track_URI = metadata['url'] if 'file' in metadata.keys(): self._track_URI = metadata['file'] def set_reltime(self, t): self.seconds = int(float(t)) self.reltime = mpdtime_to_upnptime(t) # log.err('seconds: %s' % t) self.timer = None def set_state(self, state): log.msg("SET NEW STATE : %s " % state, loglevel=logging.DEBUG) if state == self._state: return self._state = state if state == 'pause': # print(self.transport_actions) self.transport_actions = ['PLAY', 'STOP'] # self.transport_actions.remove('PAUSE') # self.transport_actions.append('PLAY') if self.timer is not None: self.timer.stop() d = self.mpd.call('status') d.addCallback(lambda st: st['elapsed']) d.addCallbacks( self.set_reltime, lambda ignored: self.set_reltime(self.timer.get())) self.upnp_state = 'PAUSED_PLAYBACK' self.oh_state = 'Paused' elif state == 'play': self.transport_actions = ['STOP', 'PAUSE', 'SEEK'] self.changed_state({'volume': self.mpd.status['volume']}) self.timer = Timer() self.timer.set(self.seconds) d = self.mpd.call('status') d.addCallbacks(lambda st: st['elapsed']) d.addCallbacks(lambda t: self.timer.set(float(t)), lambda ignored: self.timer.set(0.000)) self.upnp_state = 'PLAYING' self.oh_state = 'Playing' elif state == 'stop': self.transport_actions = ['PLAY'] self.set_reltime(0) self.upnp_state = 'STOPPED' self.oh_state = 'Stopped' elif state == 'pending': self.upnp_state = 'TRANSITIONNING' self.oh_state = 'Buffering' else: # log.err('Unknow State from player : %s' % state) return log.msg('send new state: %s' % self._state, loglevel=logging.DEBUG) self.upnp_eventAV(self.upnp_state, 'TransportState') self.oh_eventPLAYLIST(self.oh_state, 'transportstate') def set_songid(self, songid): self.songid = songid['Id'] # log.err('songid = %s' % songid) return self.songid def set_position(self, newpos, fmt='UPNP'): def transition(obj): current_state = self._state self.set_state('pending') reactor.callLater(0.5, # @UndefinedVariable self.set_state, current_state) if fmt == 'UPNP': pos = upnptime_to_mpdtime(newpos) else: pos = newpos d = self.mpd.call('seekcur', pos) d.addCallback(transition) return d def set_position_relative(self, delta, fmt='UPNP'): newpos = int(self.get_reltime('Seconds')) + int(delta) self.set_position(newpos, fmt) def set_track_URI(self, uri, md=''): log.msg("set track uri : %s " % uri, loglevel=logging.DEBUG) try: log.msg("current uri : %s " % self._track_URI, loglevel=logging.DEBUG) except: pass if uri != self._track_URI: self._track_URI = uri self.metadata_str = md self.set_metadata(didl_decode(md.encode('utf-8'))) d = self.mpd.call('addid', uri) d.addCallback(self.set_songid) d.addCallback(self.play) def set_repeat(self, repeat): self.mpd.call('repeat', str(int(repeat))) def set_shuffle(self, repeat): self.mpd.call('shuffle', str(int(repeat))) def stop(self): def stopped(ret): self.set_state('stop') if self._state != 'STOPPED': d = self.mpd.call('stop') self.reltime = '00:00:00' d.addCallback(stopped) def play(self, songid=None, ignored=None): def success(result): return None if self.cancelplay: self.cancelplay = False else: if songid is not None: d = self.mpd.call('playid', songid) else: if self._state == 'pause': d = self.mpd.call('pause', '0') else: d = self.mpd.call('playid', self.songid) d.addCallback(self.playing) def playing(self, *ret): log.msg('playing...', loglevel=logging.DEBUG) self.set_state('play') def playindex(self, index): return self.play(self.playlist[int(index)]) def playpause(self): if self._state == 'pause': return self.play() else: return self.pause() def pause(self): def paused(ret): if self._state == 'play': self.set_state('pause') # d = self.player_func('Pause','org.mpris.MediaPlayer2.Player' ) d = self.mpd.call('pause', '1') d.addCallback(paused) return d def next(self): self.mpd.call('next') def previous(self): self.mpd.call('previous') def volUp(self): if self._volume == 0: vol = self.get_volume() try: newvol = vol + 5 except: newvol = self._volume + 5 return self.set_volume('Master', newvol) else: if self._volume > 95: return newvol = self._volume + 5 return self.set_volume('Master', newvol) def volDown(self): if self._volume > 5: newvol = self._volume - 5 return self.set_volume('Master', newvol) else: self._volume = 0 return self.set_volume('Master', '0') def insert_metadata(self, md): dic = didl_decode(md) # log.err(dic) for i, tag in enumerate(dic.keys()): if tag.lower() in ('class', 'restricted', 'id', 'duration', 'parentid', 'protocolinfo', 'url', 'ownerudn'): continue if not isinstance(dic[tag], str): continue reactor.callLater( # @UndefinedVariable float(i)/2, self.mpd.call, ' '.join(('sticker', 'set song', dic['url'].join('"'*2), tag, '"' + dic[tag] + '"'))) def insert(self, url, afterid, metadata, checked=False): def inserted(res, md): # log.err('%s %s' % (res, md)) # reactor.callLater(2, # @UndefinedVariable # self.insert_metadata, # md) return res['Id'] if 'youtube' in url and not checked: # eclipse workaround !!! y = os.environ y.update({'PYTHONPATH': '/usr/bin/python'}) # / eclipse workaround d = utils.getProcessOutput( '/usr/bin/youtube-dl', ['-g', '-f', 'bestaudio', url], env=y, reactor=reactor) d.addCallback( lambda u: self.insert( u.split('\n')[0], afterid, metadata, True)) return d if len(self.playlist) == 0: d = self.mpd.call('addid', url) elif int(afterid) == 0: d = self.mpd.call('addid', url + ' 0') else: d = self.mpd.call( 'addid', ' '.join((url, str(self.playlist.index(int(afterid))+1)))) d.addCallback(inserted, metadata) return d def delete(self, songid): self.mpd.call('deleteid', str(songid)) def clear(self): self.mpd.call('clear')
class Omxclient(Service): bus = None addrs = None token = 0 con = None _state = 'Pending' _volume = 1 max_volume = 100 seconds = 0 uri = '' properties = None player = None is_connected = False pending = True event_msg = { 'Paused': ['PAUSED_PLAYBACK', 'Paused'], 'Playing': ['PLAYING', 'Playing'], 'Pending': ['TRANSITIONNING', 'Buffering'], 'Stopped': ['STOPPED', 'Stopped']} _metadata = {} metadata_str = '' metadata = {} songid = 0 maxsongid = 0 _duration = 0 upnp_duration = '0:00:00' reltime = '0:00:00.000' _playlist = [] playlist = [] _track_URI = '' idArray = '' repeat = False shuffle = False tracksmax = 1000 mtlist = '' timer = None _next = False def __init__(self, program='', args=None, **kwargs): if args: self.process_args = [program] + [arg for arg in args.split()] else: self.process_args = [program] self.process_path = program self.process = PlayerProcess(self) self.playername = program.split("/")[-1] def startService(self): self.con = DbusConnection() d = self.con.connect() d.addCallbacks(self.connected, self.not_connected) def connected(self, bus): log.msg('connected: %s' % bus) self.bus = bus def got_properties(proxy): d = self.player.get_proxy() d.addCallback(got_player) return d def got_player(proxy): self.con.watch_process( 'org.mpris.MediaPlayer2.omxplayer', self.connection_lost) self.pending = False reactor.callLater(6, # @UndefinedVariable lambda: setattr(self, 'polling', True)) reactor.callLater( # @UndefinedVariable 7, self.poll_status) if not self.properties: self.properties = ODbusProxy( self.bus, bus_name='org.mpris.MediaPlayer2.omxplayer', object_path='/org/mpris/MediaPlayer2', interface='org.freedesktop.DBus.Properties', timeout=5) self.player = ODbusProxy( self.bus, bus_name='org.mpris.MediaPlayer2.omxplayer', object_path='/org/mpris/MediaPlayer2', interface='org.mpris.MediaPlayer2.Player', timeout=5) d = self.properties.get_proxy() else: return defer.succeed(None) d.addCallback(got_properties) return d def connection_lost(self, *args, **kwargs): log.err('connection lost') #for arg in args: #log.err(arg) if 'really_lost' in kwargs: really_lost = kwargs['really_lost'] else: really_lost = False if really_lost: self.is_connected = False else: self.is_connected = not self.is_connected if not self.is_connected: if self.process.launched: log.err('launched') self.connect() else: log.err('not launched') self.properties = None self.player = None self.polling = False self.changed_state({'PlaybackStatus': 'Stopped'}) self.timer = None self.seconds = 0 self.reltime = '0:00:00.000' #self.changed_state({'PlaybackStatus': 'Stopped'}) def not_connected(self, err): self.bus = None log.err('Dbus Connection failed: %s' % err) def connect(self, err=None): log.msg('connect') if not self.bus: if not self.addrs: self.addrs = get_user_sessions() log.msg(self.addrs) if len(self.addrs) > 0: if self.con: d = self.con.connect_addr(self.addrs.pop(0)) d.addCallbacks(self.connected, self.connect) else: d = task.deferLater(reactor, 2, self.connect) else: self.addrs = None d = task.deferLater(reactor, 2, self.connect) return d else: return self.connected(self.bus) def poll_status(self): if self.polling: if not self.pending: if self.process.launched: self.pending = True d = self.properties.Position() d.addCallbacks(self.update_position, self.call_failed) d.addCallback( lambda ignored: setattr(self, 'pending', False)) reactor.callLater(5, self.poll_status) # @UndefinedVariable def call_failed(self, err): self.pending = False log.msg(err) def got_event(self, *args, **kwargs): if args[0] == 'properties': if args[1][0] == 'org.mpris.MediaPlayer2.Player': self.changed_state(args[1][1]) elif args[1][0] == 'org.mpris.MediaPlayer2.TrackList': self.changed_tracks() elif args[0] == 'tracklist': self.changed_tracks() def update_position(self, pos): if self._state in ('Paused', 'Stopped'): log.msg('del timer') self.seconds = int(pos)/1000000 self.timer = None elif self.timer: log.msg('timer alive') self.timer.set(int(pos)/1000000) else: self.seconds = int(pos)/1000000 def changed_state(self, *args, **kwargs): for arg in args: if isinstance(arg, dict): if 'PlaybackStatus' in arg: log.err(arg) if self._state != arg['PlaybackStatus']: self._state = arg['PlaybackStatus'] self.upnp_eventAV( self.event_msg[self._state][0], 'TransportState') self.oh_eventPLAYLIST( self.event_msg[self._state][1], 'transportstate') if 'Volume' in arg: if self._volume != arg['Volume']: self._volume = arg['Volume'] self.upnp_eventRCS(int(self._volume*100), 'Volume') self.oh_eventVOLUME(int(self._volume*100), 'volume') if 'Metadata' in arg: self.update_metadata(arg['Metadata']) def update_metadata(self, metadata): songid = None if isinstance(metadata, dict): if self._metadata == metadata: return else: self._metadata = metadata self.metadata = mpris_decode(metadata) elif isinstance(metadata, str): if self.metadata_str == metadata: return else: self.metadata_str = metadata self.metadata = didl_decode(metadata) log.msg(self.metadata) else: log.err('Bad metadata format : %s' % metadata) return if 'songid' in self.metadata: if self.songid != int(self.metadata['songid']): songid = int(self.metadata['songid']) if songid: self.songid = songid self.upnp_eventAV(int(self.songid), 'CurrentTrack') self.oh_eventPLAYLIST(int(self.songid), 'id') self.oh_eventTIME(1, 'trackcount') if 'duration' in self.metadata: if self._duration != self.metadata['duration']: duration = int(self.metadata['duration']) log.msg('duration: %d' % duration) if duration < 1: self.upnp_duration = "0:00:00" self._duration = 0 else: self._duration = duration self.upnp_duration = mpristime_to_upnptime(duration) log.msg('track length: %s' % self.upnp_duration, loglevel=logging.DEBUG) self.upnp_eventAV(self.upnp_duration, 'CurrentTrackDuration') self.oh_eventINFO(int(self._duration//1000000), 'duration') self.oh_eventTIME(int(self._duration//1000000), 'duration') if 'url' in self.metadata: if self._track_URI != self.metadata['url']: self._track_URI = self.metadata['url'] self.upnp_eventAV(self._track_URI, 'AVTransportURI') self.oh_eventINFO(self._track_URI, 'uri') self.upnp_eventAV(self._track_URI, 'CurrentTrackURI') if 'mpris:artUrl' in self.metadata: url = self.parent.register_art_url(self.metadata['mpris:artUrl']) self.metadata['albumArtURI'] = url self.oh_eventINFO(self.metadata_str, 'metadata') self.upnp_eventAV(self.metadata_str, 'AVTransportURIMetaData') def changed_tracks(self): self.oh_eventPLAYLIST(id_array(self.playlist), 'idarray') def upnp_eventAV(self, evt, var): pass def upnp_eventCM(self, evt, var): pass def upnp_eventRCS(self, evt, var): pass def oh_eventPLAYLIST(self, evt, var): pass def oh_eventINFO(self, evt, var): pass def oh_eventTIME(self, evt, var): pass def oh_eventVOLUME(self, evt, var): pass def get_track_id(self): return self.songid def get_track(self, track): ind = self.playlist.index(int(track)) return defer.succeed((self._playlist[ind][1], self._playlist[ind][2],)) def get_tracks(self, tracks): tr = [] for track in tracks: ind = self.playlist.index(int(track)) tr.append( (self._playlist[ind][0], self._playlist[ind][1], self._playlist[ind][2],)) tracks = tr if not isinstance(tracks, list): tracks = [tracks] tl = et.Element('TrackList') for track in tracks: # log.err('track: %s' % track[0]) en = et.Element('Entry') i = et.Element('Id') i.text = str(track[0]) en.append(i) uri = et.Element('Uri') uri.text = track[1].decode('utf-8') en.append(uri) md = et.Element('Metadata') md.text = track[2] en.append(md) tl.append(en) return defer.succeed(et.tostring(tl)) def get_state(self): return self._state def get_rate(self): return self.rate def get_track_duration(self): try: duration = self._track_duration except: duration = '00:00:00' return duration def get_track_URI(self): try: uri = self._track_URI except: uri = '' return uri def get_track_md(self): return self.metadata_str def get_relcount(self): return 2147483647 def get_abscount(self): return 2147483647 def get_abstime(self): return '00:00:00' def get_reltime(self, fmt='UPNP'): if self.timer is not None: s = self.timer.get() if (self._duration//1000000 - s) < 2: if not self._next: log.msg('next!!') self._next = True reactor.callLater(3, self.next) # @UndefinedVariable if fmt == 'UPNP': t = mpristime_to_upnptime(s) elif fmt == 'seconds': t = int(s) else: # msec t = s else: if fmt == 'UPNP': t = self.reltime else: t = self.seconds return t def get_volume(self): if self.process.launched: def noVolume(err): if self._muted: return 0 else: return self._volume def convert_volume(vol): self._volume = int(float(vol)) log.msg("volume= %d" % self._volume, loglevel=logging.DEBUG) return self._volume d = self.properties.Volume() d.addCallbacks(convert_volume, noVolume) return d else: return 1 def set_volume(self, channel, volume): if self.process.launched: if int(volume) != 0: d = self.properties.Volume(float(int(volume)/100.00)) else: if self._muted: d = self.properties.Volume(float(self._volume)) else: d = self.properties.Volume(0.00) d.addErrback(self.call_failed) reactor.callLater(0.1, self.changed_state, # @UndefinedVariable {'Volume': float(int(volume)/100.00)}) def set_track_URI(self, uri, md=''): log.msg("set track uri : %s " % uri, loglevel=logging.DEBUG) try: log.msg("current uri : %s " % self._track_URI, loglevel=logging.DEBUG) except: pass if uri != self._track_URI: self.changed_state({'Metadata': md.encode('utf-8')}) def set_position(self, newpos, fmt='UPNP'): if self.process.launched: def transition(obj): current_state = self._state self.changed_state({'PlaybackStatus': 'Pending'}) reactor.callLater( # @UndefinedVariable 0.5, self.changed_state, {'PlaybackStatus': current_state}) if fmt == 'UPNP': newtime = upnptime_to_mpristime(newpos) offset = newtime - self.seconds else: offset = float(newpos) - self.seconds d = self.player.Seek(offset) d.addCallbacks(transition, self.call_failed) return d def set_repeat(self, repeat): log.err('repeat=%s' % repeat) self.repeat = repeat self.oh_eventPLAYLIST(self.repeat, 'repeat') def set_shuffle(self, shuffle): self.shuffle = shuffle def play(self, songid=None): log.err('play :%s' % songid) def playing(ignored, trackid=None, md=None): log.err('playing') self.pending = False if not self.timer: log.msg('create timer') self.timer = Timer() self.timer.set(self.seconds) else: log.msg('resume timer') self.timer.resume() if trackid: self.changed_state( {'PlaybackStatus': 'Playing', 'Metadata': {'songid': trackid}}, {'Metadata': md}) else: self.changed_state({'PlaybackStatus': 'Playing'}) log.msg('from %s to %s' % (self._state, 'Play')) if songid: songid = int(songid) if self.process.launched: log.err('killing player') self.player.Stop() task.deferLater(reactor, 1, self.play, songid) else: volmb = 2000.0 * math.log10(self._volume) self.pending = True args = self.process_args\ + ['--vol']\ + [str(volmb)]\ + [self._playlist[self.playlist.index(songid)][1]] # args = self.process_args +\ # [self._playlist[self.playlist.index(songid)][1]] reactor.spawnProcess( # @UndefinedVariable self.process, self.process_path, tuple(args), env=os.environ) log.msg('play in one second') reactor.callLater( # @UndefinedVariable 1, playing, *(0, songid, self._playlist[self.playlist.index(songid)][2],)) else: if self.process.launched: if self._state == 'Paused': if not self.pending: self.pending = True d = self.player.Pause() d.addCallbacks(playing, self.call_failed) else: reactor.callLater( # @UndefinedVariable 0.2, self.play) else: volmb = 2000.0 * math.log10(self._volume) self.pending = True args = self.process_args\ + ['--vol']\ + [str(volmb)]\ + [self.track_URI] reactor.spawnProcess( # @UndefinedVariable self.process, self.process_path, tuple(args), env=os.environ) reactor.callLater( # @UndefinedVariable 1, playing, *(0, self.songid, self.metadata_str)) def pause(self): def paused(ignored): self.pending = False self.timer.stop() # self.seconds = self.timer.get() # self.timer = None self.changed_state({'PlaybackStatus': 'Paused'}) log.msg('from %s to %s' % (self._state, 'Pause')) if self._state != 'Paused': if not self.pending: self.pending = True d = self.player.Pause() d.addCallbacks(paused, self.call_failed) else: reactor.callLater(0.3, self.pause) # @UndefinedVariable def stop(self): if self._state != 'Stopped': self.timer = None self.seconds = 0 self.player.Stop() self.reltime = '00:00:00' self.changed_state({'PlaybackStatus': 'Stopped'}) def next(self): if self._next: self._next = False if len(self.playlist) > 0: if self.songid == self.playlist[-1]: if self.repeat: self.play(songid=self.playlist[0]) else: self.play( songid=self.playlist[ self.playlist.index(self.songid) + 1]) def previous(self): if len(self.playlist) > 0: if self.songid == self.playlist[0]: if not self.repeat: return self.play( songid=self.playlist[ self.playlist.index(self.songid) - 1]) def volUp(self): if self._volume == 0: vol = self.get_volume() try: newvol = vol + 5 except: newvol = self._volume*100 + 5 return self.set_volume('Master', newvol) else: if self._volume > 95: return newvol = self._volume*100 + 5 return self.set_volume('Master', newvol) def volDown(self): if self._volume > 0.05: newvol = self._volume*100 - 5 return self.set_volume('Master', newvol) else: self._volume = 0 return self.set_volume('Master', 0) def insert(self, url, afterid, metadata, checked=False): if 'youtube' in url and not checked: # eclipse workaround !!! y = os.environ y.update({'PYTHONPATH': '/usr/bin/python'}) # / eclipse workaround d = utils.getProcessOutput( '/usr/bin/youtube-dl', ['-g', '-f', 'bestaudio', url], env=y, reactor=reactor) d.addCallback( lambda u: self.insert( u.split('\n')[0], afterid, metadata, True)) return d # log.err('playlist length:%s' % len(self._playlist)) self.maxsongid += 1 if len(self._playlist) == 0: self._playlist.append([self.maxsongid, url, metadata]) else: if afterid == '0': self._playlist.insert(0, [self.maxsongid, url, metadata]) else: self._playlist.insert( self.playlist[self.playlist.index(int(afterid))], [self.maxsongid, url, metadata]) # log.err('real playlist: %s' % self._playlist) self.playlist = [i[0] for i in self._playlist] # log.err('new playlist: %s' % self.playlist) # log.err('metadata dic: %s' % metadata) self.oh_playlist = [str(i) for i in self.playlist] self.idArray = id_array(self.playlist) self.changed_tracks() if self.songid == 0: self.update_metadata({'songid': 1}) return defer.succeed(self.maxsongid) def delete(self, songid): # log.err(self.playlist) try: suppressed = self.playlist.index(int(songid)) except IndexError: pass else: self._playlist.pop(suppressed) self.playlist.pop(suppressed) self.idArray = id_array(self.playlist) self.changed_tracks() def clear(self): self.playlist = [] self._playlist = [] self.songid = 0 self.idArray = '' self.changed_state('TrackList', {}, '')
class Mpd_factory(ReconnectingClientFactory, Client): ''' Twisted Reconnecting client factory for mpd server convert all UPnP and OpenHome service function to mpd remote calls ''' _mtlist = '' _muted = True _rate = "1" _state = "pending" _track_duration = '0:00:00.000' ohduration = 0 _track_URI = '' _volume = 100 active_source = 0 attributes = 'Info Time Volume' balance = 0 balancemax = 10 bitdepth = 0 bitrate = 0 cancelplay = False codecname = 'MP3' counter = 0 detailscount = 0 fade = 0 fademax = 10 idArray = '' lossless = False maxid = 0 max_volume = 100 metadata = {} metadata_str = 'NOT_IMPLEMENTED' metatext = '' metatextcount = 0 mimetypes = ['audio/aac', 'audio/mp4', 'audio/mpeg', 'audio/mpegurl', 'audio/vnd.rn-realaudio', 'audio/vorbis', 'audio/x-flac', 'audio/x-mp3', 'audio/x-mpegurl', 'audio/x-ms-wma', 'audio/x-musepack', 'audio/x-oggflac', 'audio/x-pn-realaudio', 'audio/x-scpls', 'audio/x-speex', 'audio/x-vorbis', 'audio/x-wav', 'application/x-ogm-audio', 'audio/x-vorbis+ogg', 'audio/ogg'] mtlist = '' name = "MpdPlayer" oh_state = 'Buffering' old_vol = 0 parent = None reltime = '0:00:00.000' repeat = False room = 'Home' samplerate = 0 seconds = 0 shuffle = False songid = 0 sources = ['Playlist'] sourceindex = 0 upnp_state = "TRANSITIONNING" volumeunity = 3 volumemillidbperstep = 600 timer = None token = 0 trackcount = 0 tracksmax = 0 transport_actions = ['PLAY'] # ohduration = 0 def __init__(self, cover_dir, net_type='lan'): self.connected = False self.net_type = net_type self.proto = MpdProtocol() self.proto._event = self.dispatch_event self.status = {'state': 'stop'} self.playlist = Playlist(self) self.waiting = False self.calls = [] self.cover_dir = cover_dir self._sources = [OrderedDict(sorted( {'Name': '_'.join((self.name, n)), 'Type': n, 'Visible': True}. items(), key=lambda t: t[0])) for n in self.sources] self.sourcexml = dict2xml( {'SourceList': [{'Source': n} for n in self._sources]}) self.protocolinfo = self.mtlist = 'http-get:*:' + ''\ .join([':*,http-get:*:'.join(self.mimetypes)]) + ':*' def buildProtocol(self, addr): return self.proto ''' Internal functions ''' def dispatch_event(self, events=None): def set_connected(): self.connected = True p = self.call('plchanges', self.playlist.plsversion) p.addCallback(self.playlist.update) d = None for evt in events: if evt == 'Error': log.error('bad event: {name}', name=events[evt]) return elif evt == 'changed': chg = events[evt] else: # log.msg(events[evt]) return if chg == 'disconnected': self.connected = False continue if chg == 'playlist': log.error('update from: %s' % self.playlist.plsversion) d = self.call('plchanges', self.playlist.plsversion) d.addCallback(self.playlist.update) else: d = self.call('status') if chg == 'connected': d.addCallback(self.filter_event) reactor.callLater(1, set_connected) # @UndefinedVariable elif chg == 'mixer': d.addCallback(self.filter_event, 'volume') elif chg == 'player': d.addCallback(self.filter_event) return d def filter_event(self, data, filtr=None): if data is None: return if not isinstance(data, list): data = [data] if filtr is not None: for d in data: self.status.update(d) self.changed_state({filtr: self.status[filtr]}) else: dic = {} for d in data: for it in d.iteritems(): if it not in self.status.items(): self.status.update(dict([it])) dic.update(dict([it])) if it[0] == 'playlistlength': self.playlist.length = int(it[1]) self.changed_state(dic) def call(self, command=None, params=None): # log.error('register %s' % command) d = defer.Deferred() if command is not None: self.proto.addCallback(d) if not self.connected: if command: self.calls.append((command, params)) else: if len(self.calls) > 0: if command: self.calls.append((command, params)) else: d = None command, params = self.calls.pop(0) if params is not None: c = ' '.join([command, str(params)]).encode('utf-8') else: c = command.encode('utf-8') if self.proto.idle: self.proto.noidle() self.proto.sendLine(c) # log.error('send %s' % c) if len(self.calls) > 0: self.call() return d def changed_state(self, state): changed = state.keys() if 'state' in changed: self.set_state(state['state']) if "volume" in changed: log.debug('volume changed') vol = int(state['volume']) if vol != self._volume: if vol != 0: self._volume = vol muted = False else: muted = True log.debug('send volume') self.oh_eventVOLUME(self._volume, 'volume') self.upnp_eventRCS(self._volume, 'volume') if muted is not self._muted: self._muted = muted self.upnp_eventRCS(self._muted, 'mute') self.oh_eventVOLUME(int(self._muted), 'mute') if 'songid' in changed: self.playlist.current_track(state['songid']) if 'repeat' in changed: # log.debug('************repeat***********: %s' % # bool(int(state['repeat']))) if self.repeat != bool(int(state['repeat'])): self.repeat = bool(int(state['repeat'])) if not self.shuffle: self.upnp_eventAV( 'REPEAT_ALL' if self.repeat else 'NORMAL', 'currentplayMode') else: self.upnp_eventAV('REPEAT_ALL SHUFFLE' if self.repeat else 'NORMAL SHUFFLE', 'currentplaymode') self.oh_eventPLAYLIST(self.repeat, 'repeat') if 'random' in changed: log.debug('************shuffle***********: %s' % bool(int(state['random']))) if self.shuffle != bool(int(state['random'])): self.shuffle = bool(int(state['random'])) if not self.repeat: self.upnp_eventAV( 'NORMAL SHUFFLE' if self.shuffle else 'NORMAL', 'currentplaymode') else: self.upnp_eventAV( 'REPEAT_ALL SHUFFLE' if self.shuffle else 'NORMAL SHUFFLE', 'currentplaymode') self.oh_eventPLAYLIST(self.shuffle, 'shuffle') if 'elapsed' in changed: if self.timer is not None: self.timer.set(float(state['elapsed'])) if 'playlist' in changed: self.token = int(state['playlist']) # self.playlist.plsversion = state['playlist'] if 'playlistdata' in changed: self.playlist.update(state['playlistdata']) if 'bitrate' in changed: self.detailscount += 1 self.bitrate = int(state['bitrate']) self.oh_eventINFO(self.bitrate, 'bitrate') if 'audio' in changed: try: sr = int(state['audio'].split(':')[0]) self.samplerate = sr self.oh_eventINFO(sr, 'samplerate') except: log.critical('Bad Samplerate: %s' % state['audio'].split(':')[0]) try: bd = int(state['audio'].split(':')[1]) self.bitdepth = bd self.oh_eventINFO(bd, 'bitdepth') except: log.critical('Bad Bitdepth: %s' % state['audio'].split(':')[1]) def update_state(self): # log.err('Update State: %s' % self.mpd.status['state']) self.set_state(self.status['state']) def update_mimetypes(self): self.set_mimetypes(self.mimetypes) def upnp_eventAV(self, evt, var): pass def upnp_eventCM(self, evt, var): pass def upnp_eventRCS(self, evt, var): pass def oh_eventPLAYLIST(self, evt, var): pass def oh_eventINFO(self, evt, var): pass def oh_eventTIME(self, evt, var): pass def oh_eventVOLUME(self, evt, var): pass def getMetadata(self): return self.call('playlistid', self.status['songid']) def getMimeTypes(self): return self.call('decoders') def get_state(self): return self._state def get_rate(self): return self._rate def get_track_duration(self): try: duration = self._track_duration except: duration = '00:00:00' return duration def get_track_URI(self): try: uri = self._track_URI except: uri = '' return uri def get_track_md(self): return self.metadata_str def get_sticker(self, url, dic={}): def got_sticker(stklist, dic): if not isinstance(stklist, list): stklist = [stklist] for sticker in stklist: try: dic.update( {sticker['sticker'].split('=')[0], sticker['sticker'].split('=')[1]}) except: continue return dic d = self.call('sticker', ' '.join(('list song', url.join('"' * 2)))) d.addBoth(got_sticker, dic) return d def get_relcount(self): return 2147483647 def get_abscount(self): return 2147483647 def get_abstime(self): return '00:00:00' def get_reltime(self, fmt='UPNP'): if self.timer is not None: if fmt == 'UPNP': t = mpdtime_to_upnptime(self.timer.get()) elif fmt == 'seconds': t = int(self.timer.get()) else: # msec t = self.timer.get() else: if fmt == 'UPNP': t = self.reltime else: t = self.seconds return t def get_volume(self): def noVolume(err): if self._muted: return 0 else: return self._volume def convert_volume(vol): self._volume = int(float(vol) * 100) return self._volume d = self.mediaplayer.get("Volume", interf='org.mpris.MediaPlayer2.Player') d.addCallbacks(convert_volume, noVolume) return d def get_transport_actions(self): return {','.join(self.transport_actions)} def set_mimetypes(self, mtlist): if self._mtlist != mtlist: self._mtlist = mtlist self.mtlist = 'http-get:*:' + ''\ .join([':*,http-get:*:'.join(mtlist)]) + ':*' self.upnp_eventCM(self.mtlist, 'sinkprotocolinfo') self.oh_eventPLAYLIST(self.mtlist, 'protocolinfo') def set_metadata(self, metadata): if metadata != self.metadata: self.metadata.update(metadata) if 'duration' in metadata.keys(): self._track_duration = metadata['duration'] if 'url' in metadata.keys(): self._track_URI = metadata['url'] if 'file' in metadata.keys(): self._track_URI = metadata['file'] def set_reltime(self, t): self.seconds = int(float(t)) self.reltime = mpdtime_to_upnptime(t) # log.err('seconds: %s' % t) self.timer = None def set_state(self, state): log.debug("SET NEW STATE : %s " % state) if state == self._state: return self._state = state if state == 'pause': # print(self.transport_actions) self.transport_actions = ['PLAY', 'STOP'] # self.transport_actions.remove('PAUSE') # self.transport_actions.append('PLAY') if self.timer is not None: self.timer.stop() d = self.call('status') d.addCallback(lambda st: st['elapsed']) d.addCallbacks( self.set_reltime, lambda ignored: self.set_reltime(self.timer.get())) self.upnp_state = 'PAUSED_PLAYBACK' self.oh_state = 'Paused' elif state == 'play': self.transport_actions = ['STOP', 'PAUSE', 'SEEK'] self.changed_state({'volume': self.status['volume']}) self.timer = Timer() self.timer.set(self.seconds) d = self.call('status') d.addCallbacks(lambda st: st['elapsed']) d.addCallbacks(lambda t: self.timer.set(float(t)), lambda ignored: self.timer.set(0.000)) self.upnp_state = 'PLAYING' self.oh_state = 'Playing' elif state == 'stop': self.transport_actions = ['PLAY'] self.set_reltime(0) self.upnp_state = 'STOPPED' self.oh_state = 'Stopped' elif state == 'pending': self.upnp_state = 'TRANSITIONNING' self.oh_state = 'Buffering' else: return self.upnp_eventAV(self.upnp_state, 'transportstate') self.oh_eventPLAYLIST(self.oh_state, 'transportstate') def set_songid(self, songid): self.songid = songid['Id'] return self.songid def set_position(self, newpos, fmt='UPNP'): def transition(obj): current_state = self._state self.set_state('pending') reactor.callLater(0.5, # @UndefinedVariable self.set_state, current_state) if fmt == 'UPNP': pos = upnptime_to_mpdtime(newpos) else: pos = newpos log.debug('seek to %s' % pos) d = self.call('seekcur', pos) d.addCallback(transition) return d def set_position_relative(self, delta, fmt='UPNP'): newpos = int(self.get_reltime('Seconds')) + int(delta) self.set_position(newpos, fmt) def set_track_URI(self, uri, md=''): log.debug("set track uri : %s " % uri) try: log.debug("current uri : %s " % self._track_URI) except: pass if uri != self._track_URI: self._track_URI = uri self.metadata_str = md self.set_metadata(didl_decode(md.encode('utf-8'))) d = self.call('addid', uri) d.addCallback(self.set_songid) d.addCallback(self.play) def playing(self, *ret): log.debug('playing...') self.set_state('play') def playindex(self, index): return self.play(songid=self.playlist[int(index)]) def playpause(self): if self._state == 'pause': return self.play() else: return self.pause() def insert_metadata(self, md): dic = didl_decode(md) # log.err(dic) for i, tag in enumerate(dic.keys()): if tag.lower() in ('class', 'restricted', 'id', 'duration', 'parentid', 'protocolinfo', 'url', 'ownerudn'): continue if not isinstance(dic[tag], str): continue reactor.callLater( # @UndefinedVariable float(i) / 2, self.call, ' '.join(('sticker', 'set song', dic['url'].join('"' * 2), tag, '"' + dic[tag] + '"'))) def delete(self, songid): self.call('deleteid', str(songid)) def clear(self): self.call('clear') def volUp(self): if self._volume == 0: vol = self.r_get_volume() try: newvol = vol + 5 except: newvol = self._volume + 5 return self.r_set_volume(newvol, 'Master') else: if self._volume > 95: return newvol = self._volume + 5 return self.r_set_volume(newvol, 'Master') def volDown(self): if self._volume > 5: newvol = self._volume - 5 return self.r_set_volume(newvol, 'Master') else: newvol = self._volume self._volume = 0 return self.r_set_volume(newvol, '0') ''' UPNP wrapped functions ''' # onDemand vendor method def r_get_room(self): return self.room ''' AVTransport and OpenHome Playlist ''' def r_delete_all(self): return self.clear() def r_delete_id(self, value): return self.delete(value) def r_get_current_transport_actions(self, instanceID): return self.get_transport_actions() def r_get_media_info(self, instanceID): return (str(len(self.playlist)), self.get_track_duration(), self.get_track_URI(), self.get_track_md(), '', '', 'UNKNOWN', 'UNKNOWN', 'UNKNOWN',) def r_get_media_info_ext(self, instanceID): return (str(len(self.playlist)), 'TRACK_AWARE', self.get_track_duration(), self.get_track_URI(), self.get_track_md(), '', '', 'UNKNOWN', 'UNKNOWN', 'UNKNOWN',) def r_get_position_info(self, instanceID): return (self.player.get_track(), self.get_track_duration(), self.get_track_md(), self.player.get_track_URI(), self.player.get_reltime(), self.player.get_abstime(), self.player.get_relcount(), self.player.get_abscount(),) def r_get_transport_info(self, instanceID): return (self.get_state(), 'OK', self.get_rate(),) def r_id(self): return self.songid def r_id_array(self): return (self.token, self.idArray,) def r_id_array_changed(self, token): if token != self.token: return 1 return 0 def r_insert(self, afterid, url, metadata, checked=False): log.debug('Insert :%s -- %s -- %s' % (afterid, url, metadata)) def inserted(res, md): log.debug(res) return res['Id'] if 'youtube' in url and not checked: # eclipse workaround !!! y = os.environ y.update({'PYTHONPATH': '/usr/bin/python'}) # /eclipse workaround d = utils.getProcessOutput( '/usr/bin/youtube-dl', ['-g', '-f', 'bestaudio', url], env=y, reactor=reactor) d.addCallback( lambda u: self.insert( u.split('\n')[0], afterid, metadata, True)) return d if len(self.playlist.tracks) == 0: d = self.call('addid', url) elif int(afterid) == 0: d = self.call('addid', url + ' 0') else: log.critical('crash ? %s' % self.playlist.tracks) log.critical('here ? %s' % str(self.playlist.tracks.index(int(afterid)) + 1)) d = self.call( 'addid', ' '.join( (url.encode('utf-8'), str(self.playlist.tracks.index(int(afterid)) + 1)))) d.addCallback(inserted, metadata) return d def r_next(self, instanceID=0): if self._state not in ('play', 'pause'): if self.songid == 0: self.r_play(1) else: self.r_play() else: self.call('next') def r_play(self, instanceID=0, speed=1, songid=None, ignored=None): log.debug('entering play...') def success(result): return None if self.cancelplay: self.cancelplay = False else: if songid is not None: # log.err(songid) d = self.call('playid', str(songid)) else: if self._state == 'pause': d = self.call('pause', '0') else: d = self.call('playid', self.songid) d.addCallback(self.playing) def r_pause(self, instanceID=0): print('pause') def paused(ret): if self._state == 'play': self.set_state('pause') d = self.call('pause', '1') d.addCallback(paused) return d def r_previous(self, instanceID=0): self.call('previous') def r_protocol_info(self): return self.protocolinfo def r_read(self, value): log.debug('read') d = self.playlist.get_track(value) return (d,) def r_read_list(self, items): log.debug('readlist') d = self.playlist.get_tracks( [int(track) for track in items.split()]) return d def r_repeat(self): return self.repeat def r_record(self, instanceID): raise NotImplementedError() def r_seek(self, instanceID, unit, pos): log.debug('seek: %s %s' % (unit, pos)) self.set_position(pos) def r_seek_id(self, value): log.debug('SeekId') return self.r_play(songid=value) def r_seek_index(self, value): log.debug('Seekindex') return self.playindex(value) def r_seek_second_absolute(self, value): return self.set_position(value, 'SECONDS') def r_seek_second_relative(self, value): return self.set_position_relative(value, 'SECONDS') def r_set_repeat(self, repeat): self.call('repeat', str(int(repeat))) self.changed_state({'repeat': str(int(repeat))}) def r_set_shuffle(self, shuffle): self.call('random', str(int(shuffle))) self.changed_state({'random': str(int(shuffle))}) def r_set_avtransport_uri(self, instanceID, uri, uri_metadata): self.set_track_URI(uri, uri_metadata) def r_shuffle(self): return self.shuffle def r_stop(self, instanceID=0): def stopped(ret): self.set_state('stop') if self._state != 'STOPPED': d = self.call('stop') self.reltime = '00:00:00' d.addCallback(stopped) def r_tracks_max(self): return self.tracksmax def r_transport_state(self, instanceID=0): if self.parent.type == 'Source': return self.oh_state return self.upnp_state ''' OpenHome Info ''' def r_counters(self): return (self.trackcount, self.detailscount, self.metatextcount,) def r_track(self): return (self._track_URI, self.metadata_str,) def r_details(self): return ( self.ohduration, self.bitrate, self.bitdepth, self.samplerate, self.lossless, self.codecname,) def r_metatext(self): return self.metatext ''' Rendering Control and Open Home Volume ''' def r_volume(self): return self._volume def r_set_volume(self, volume, channel=0): volume = str(volume) d = self.call('setvol', volume) d.addErrback( log.critical, 'Set Volume Error : %s - %d' % (channel, int(volume))) reactor.callLater(0.1, # @UndefinedVariable self.changed_state, {'volume': str(volume)}) def r_volume_inc(self): return self.volUp() def r_volume_dec(self): return self.volDown() def r_volume_limit(self): return self.max_volume def r_balance(self): return self.balance def r_balance_inc(self): # TODO self.balance += 1 def r_balance_dec(self): # TODO self.balance -= 1 def r_set_fade(self, value): # TODO self.fade = int(value) def r_fade_inc(self): # TODO self.fade += 1 def r_fade_dec(self): # TODO self.fade -= 1 def r_mute(self): return self._muted def r_set_mute(self, mute): if mute is not self._muted: self._muted = mute if mute: self.old_vol = self._volume self.r_set_volume('0') else: self.r_set_volume(self.old_vol) def r_characteristics(self): return self.max_volume, self.volumeunity, self.max_volume,\ self.volumemillidbperstep, self.balancemax, self.fademax ''' OpenHome Time ''' def r_time(self): return (self.trackcount, self.ohduration, self.get_reltime('seconds')) ''' OpenHome Product ''' def r_manufacturer(self=None): log.debug('Manufacturer from Product') return (self.parent.manufacturer, self.parent.manufacturerInfo, self.parent.manufacturerURL, ''.join((self.parent.getLocation(get_default_v4_address()), '/pictures/icon.png')),) def r_model(self=None): log.debug('Model from Product') return (self.parent.modelName, self.parent.modelDescription, self.parent.manufacturerURL, ''.join((self.parent.getLocation(get_default_v4_address()), '/pictures/', self.parent.modelName, '.png',))) def r_product(self): log.debug('Product from Product') return self.room, self.parent.modelName, self.parent.modelDescription,\ self.parent.manufacturerURL,\ ''.join((self.parent.getLocation(get_default_v4_address()), '/pictures/', self.parent.modelName, '.png',)) def r_standby(self): log.debug('Standby from Product') return self.standby def r_set_standby(self, val=None): log.debug('SetStandby from Product') if val is None: return self.standby raise NotImplementedError() def r_source_count(self): log.debug('SourceCount from Product') return len(self.sources) def r_source_xml(self, *args, **kwargs): log.debug('SourceXml from Product') return self.sourcexml # return dict2xml({'SourceList': [{'Source': n} for n in self.sources]}) def r_source_index(self): log.debug('SourceIndex from Product') return self.sourceindex def r_set_source_index(self, idx=None): log.debug('SetSourceIndex from Product') if idx is None: return self.sourceindex else: try: self.sourceindex = int(idx) self.oh_product_event('sourceindex', self.sourceindex) except: for i, source in enumerate(self.sources.keys()): if source['Name'] == idx: self.sourceindex = i self.oh_product_event('sourceindex', self.sourceindex) return log.critical('Unknown Source: %s' % idx) def r_set_source_index_by_name(self, value): log.debug('SetSourceIndexByName from Product') return self.set_source_index(value) def r_source(self, idx): idx = int(idx) return (self.parent.sources[idx].friendlyName, self.parent.sources[idx].type, True, self.parent.sources[idx].name,) def r_attributes(self): return self.attributes def r_source_xml_change_count(self): raise NotImplementedError()
class Omxclient(Service): bus = None addrs = None token = 0 con = None _state = 'Pending' _volume = 1 max_volume = 100 seconds = 0 uri = '' properties = None player = None is_connected = False pending = True event_msg = { 'Paused': ['PAUSED_PLAYBACK', 'Paused'], 'Playing': ['PLAYING', 'Playing'], 'Pending': ['TRANSITIONNING', 'Buffering'], 'Stopped': ['STOPPED', 'Stopped'] } _metadata = {} metadata_str = '' metadata = {} songid = 0 maxsongid = 0 _duration = 0 upnp_duration = '0:00:00' reltime = '0:00:00.000' _playlist = [] playlist = [] _track_URI = '' idArray = '' repeat = False shuffle = False tracksmax = 1000 mtlist = '' timer = None _next = False def __init__(self, program='', args=None, **kwargs): if args: self.process_args = [program] + [arg for arg in args.split()] else: self.process_args = [program] self.process_path = program self.process = PlayerProcess(self) self.playername = program.split("/")[-1] def startService(self): self.con = DbusConnection() d = self.con.connect() d.addCallbacks(self.connected, self.not_connected) def connected(self, bus): log.msg('connected: %s' % bus) self.bus = bus def got_properties(proxy): d = self.player.get_proxy() d.addCallback(got_player) return d def got_player(proxy): self.con.watch_process('org.mpris.MediaPlayer2.omxplayer', self.connection_lost) self.pending = False reactor.callLater( 6, # @UndefinedVariable lambda: setattr(self, 'polling', True)) reactor.callLater( # @UndefinedVariable 7, self.poll_status) if not self.properties: self.properties = ODbusProxy( self.bus, bus_name='org.mpris.MediaPlayer2.omxplayer', object_path='/org/mpris/MediaPlayer2', interface='org.freedesktop.DBus.Properties', timeout=5) self.player = ODbusProxy( self.bus, bus_name='org.mpris.MediaPlayer2.omxplayer', object_path='/org/mpris/MediaPlayer2', interface='org.mpris.MediaPlayer2.Player', timeout=5) d = self.properties.get_proxy() else: return defer.succeed(None) d.addCallback(got_properties) return d def connection_lost(self, *args, **kwargs): log.err('connection lost') #for arg in args: #log.err(arg) if 'really_lost' in kwargs: really_lost = kwargs['really_lost'] else: really_lost = False if really_lost: self.is_connected = False else: self.is_connected = not self.is_connected if not self.is_connected: if self.process.launched: log.err('launched') self.connect() else: log.err('not launched') self.properties = None self.player = None self.polling = False self.changed_state({'PlaybackStatus': 'Stopped'}) self.timer = None self.seconds = 0 self.reltime = '0:00:00.000' #self.changed_state({'PlaybackStatus': 'Stopped'}) def not_connected(self, err): self.bus = None log.err('Dbus Connection failed: %s' % err) def connect(self, err=None): log.msg('connect') if not self.bus: if not self.addrs: self.addrs = get_user_sessions() log.msg(self.addrs) if len(self.addrs) > 0: if self.con: d = self.con.connect_addr(self.addrs.pop(0)) d.addCallbacks(self.connected, self.connect) else: d = task.deferLater(reactor, 2, self.connect) else: self.addrs = None d = task.deferLater(reactor, 2, self.connect) return d else: return self.connected(self.bus) def poll_status(self): if self.polling: if not self.pending: if self.process.launched: self.pending = True d = self.properties.Position() d.addCallbacks(self.update_position, self.call_failed) d.addCallback( lambda ignored: setattr(self, 'pending', False)) reactor.callLater(5, self.poll_status) # @UndefinedVariable def call_failed(self, err): self.pending = False log.msg(err) def got_event(self, *args, **kwargs): if args[0] == 'properties': if args[1][0] == 'org.mpris.MediaPlayer2.Player': self.changed_state(args[1][1]) elif args[1][0] == 'org.mpris.MediaPlayer2.TrackList': self.changed_tracks() elif args[0] == 'tracklist': self.changed_tracks() def update_position(self, pos): if self._state in ('Paused', 'Stopped'): log.msg('del timer') self.seconds = int(pos) / 1000000 self.timer = None elif self.timer: log.msg('timer alive') self.timer.set(int(pos) / 1000000) else: self.seconds = int(pos) / 1000000 def changed_state(self, *args, **kwargs): for arg in args: if isinstance(arg, dict): if 'PlaybackStatus' in arg: log.err(arg) if self._state != arg['PlaybackStatus']: self._state = arg['PlaybackStatus'] self.upnp_eventAV(self.event_msg[self._state][0], 'TransportState') self.oh_eventPLAYLIST(self.event_msg[self._state][1], 'transportstate') if 'Volume' in arg: if self._volume != arg['Volume']: self._volume = arg['Volume'] self.upnp_eventRCS(int(self._volume * 100), 'Volume') self.oh_eventVOLUME(int(self._volume * 100), 'volume') if 'Metadata' in arg: self.update_metadata(arg['Metadata']) def update_metadata(self, metadata): songid = None if isinstance(metadata, dict): if self._metadata == metadata: return else: self._metadata = metadata self.metadata = mpris_decode(metadata) elif isinstance(metadata, str): if self.metadata_str == metadata: return else: self.metadata_str = metadata self.metadata = didl_decode(metadata) log.msg(self.metadata) else: log.err('Bad metadata format : %s' % metadata) return if 'songid' in self.metadata: if self.songid != int(self.metadata['songid']): songid = int(self.metadata['songid']) if songid: self.songid = songid self.upnp_eventAV(int(self.songid), 'CurrentTrack') self.oh_eventPLAYLIST(int(self.songid), 'id') self.oh_eventTIME(1, 'trackcount') if 'duration' in self.metadata: if self._duration != self.metadata['duration']: duration = int(self.metadata['duration']) log.msg('duration: %d' % duration) if duration < 1: self.upnp_duration = "0:00:00" self._duration = 0 else: self._duration = duration self.upnp_duration = mpristime_to_upnptime(duration) log.msg('track length: %s' % self.upnp_duration, loglevel=logging.DEBUG) self.upnp_eventAV(self.upnp_duration, 'CurrentTrackDuration') self.oh_eventINFO(int(self._duration // 1000000), 'duration') self.oh_eventTIME(int(self._duration // 1000000), 'duration') if 'url' in self.metadata: if self._track_URI != self.metadata['url']: self._track_URI = self.metadata['url'] self.upnp_eventAV(self._track_URI, 'AVTransportURI') self.oh_eventINFO(self._track_URI, 'uri') self.upnp_eventAV(self._track_URI, 'CurrentTrackURI') if 'mpris:artUrl' in self.metadata: url = self.parent.register_art_url(self.metadata['mpris:artUrl']) self.metadata['albumArtURI'] = url self.oh_eventINFO(self.metadata_str, 'metadata') self.upnp_eventAV(self.metadata_str, 'AVTransportURIMetaData') def changed_tracks(self): self.oh_eventPLAYLIST(id_array(self.playlist), 'idarray') def upnp_eventAV(self, evt, var): pass def upnp_eventCM(self, evt, var): pass def upnp_eventRCS(self, evt, var): pass def oh_eventPLAYLIST(self, evt, var): pass def oh_eventINFO(self, evt, var): pass def oh_eventTIME(self, evt, var): pass def oh_eventVOLUME(self, evt, var): pass def get_track_id(self): return self.songid def get_track(self, track): ind = self.playlist.index(int(track)) return defer.succeed(( self._playlist[ind][1], self._playlist[ind][2], )) def get_tracks(self, tracks): tr = [] for track in tracks: ind = self.playlist.index(int(track)) tr.append(( self._playlist[ind][0], self._playlist[ind][1], self._playlist[ind][2], )) tracks = tr if not isinstance(tracks, list): tracks = [tracks] tl = et.Element('TrackList') for track in tracks: # log.err('track: %s' % track[0]) en = et.Element('Entry') i = et.Element('Id') i.text = str(track[0]) en.append(i) uri = et.Element('Uri') uri.text = track[1].decode('utf-8') en.append(uri) md = et.Element('Metadata') md.text = track[2] en.append(md) tl.append(en) return defer.succeed(et.tostring(tl)) def get_state(self): return self._state def get_rate(self): return self.rate def get_track_duration(self): try: duration = self._track_duration except: duration = '00:00:00' return duration def get_track_URI(self): try: uri = self._track_URI except: uri = '' return uri def get_track_md(self): return self.metadata_str def get_relcount(self): return 2147483647 def get_abscount(self): return 2147483647 def get_abstime(self): return '00:00:00' def get_reltime(self, fmt='UPNP'): if self.timer is not None: s = self.timer.get() if (self._duration // 1000000 - s) < 2: if not self._next: log.msg('next!!') self._next = True reactor.callLater(3, self.next) # @UndefinedVariable if fmt == 'UPNP': t = mpristime_to_upnptime(s) elif fmt == 'seconds': t = int(s) else: # msec t = s else: if fmt == 'UPNP': t = self.reltime else: t = self.seconds return t def get_volume(self): if self.process.launched: def noVolume(err): if self._muted: return 0 else: return self._volume def convert_volume(vol): self._volume = int(float(vol)) log.msg("volume= %d" % self._volume, loglevel=logging.DEBUG) return self._volume d = self.properties.Volume() d.addCallbacks(convert_volume, noVolume) return d else: return 1 def set_volume(self, channel, volume): if self.process.launched: if int(volume) != 0: d = self.properties.Volume(float(int(volume) / 100.00)) else: if self._muted: d = self.properties.Volume(float(self._volume)) else: d = self.properties.Volume(0.00) d.addErrback(self.call_failed) reactor.callLater( 0.1, self.changed_state, # @UndefinedVariable {'Volume': float(int(volume) / 100.00)}) def set_track_URI(self, uri, md=''): log.msg("set track uri : %s " % uri, loglevel=logging.DEBUG) try: log.msg("current uri : %s " % self._track_URI, loglevel=logging.DEBUG) except: pass if uri != self._track_URI: self.changed_state({'Metadata': md.encode('utf-8')}) def set_position(self, newpos, fmt='UPNP'): if self.process.launched: def transition(obj): current_state = self._state self.changed_state({'PlaybackStatus': 'Pending'}) reactor.callLater( # @UndefinedVariable 0.5, self.changed_state, {'PlaybackStatus': current_state}) if fmt == 'UPNP': newtime = upnptime_to_mpristime(newpos) offset = newtime - self.seconds else: offset = float(newpos) - self.seconds d = self.player.Seek(offset) d.addCallbacks(transition, self.call_failed) return d def set_repeat(self, repeat): log.err('repeat=%s' % repeat) self.repeat = repeat self.oh_eventPLAYLIST(self.repeat, 'repeat') def set_shuffle(self, shuffle): self.shuffle = shuffle def play(self, songid=None): log.err('play :%s' % songid) def playing(ignored, trackid=None, md=None): log.err('playing') self.pending = False if not self.timer: log.msg('create timer') self.timer = Timer() self.timer.set(self.seconds) else: log.msg('resume timer') self.timer.resume() if trackid: self.changed_state( { 'PlaybackStatus': 'Playing', 'Metadata': { 'songid': trackid } }, {'Metadata': md}) else: self.changed_state({'PlaybackStatus': 'Playing'}) log.msg('from %s to %s' % (self._state, 'Play')) if songid: songid = int(songid) if self.process.launched: log.err('killing player') self.player.Stop() task.deferLater(reactor, 1, self.play, songid) else: volmb = 2000.0 * math.log10(self._volume) self.pending = True args = self.process_args\ + ['--vol']\ + [str(volmb)]\ + [self._playlist[self.playlist.index(songid)][1]] # args = self.process_args +\ # [self._playlist[self.playlist.index(songid)][1]] reactor.spawnProcess( # @UndefinedVariable self.process, self.process_path, tuple(args), env=os.environ) log.msg('play in one second') reactor.callLater( # @UndefinedVariable 1, playing, *( 0, songid, self._playlist[self.playlist.index(songid)][2], )) else: if self.process.launched: if self._state == 'Paused': if not self.pending: self.pending = True d = self.player.Pause() d.addCallbacks(playing, self.call_failed) else: reactor.callLater( # @UndefinedVariable 0.2, self.play) else: volmb = 2000.0 * math.log10(self._volume) self.pending = True args = self.process_args\ + ['--vol']\ + [str(volmb)]\ + [self.track_URI] reactor.spawnProcess( # @UndefinedVariable self.process, self.process_path, tuple(args), env=os.environ) reactor.callLater( # @UndefinedVariable 1, playing, *(0, self.songid, self.metadata_str)) def pause(self): def paused(ignored): self.pending = False self.timer.stop() # self.seconds = self.timer.get() # self.timer = None self.changed_state({'PlaybackStatus': 'Paused'}) log.msg('from %s to %s' % (self._state, 'Pause')) if self._state != 'Paused': if not self.pending: self.pending = True d = self.player.Pause() d.addCallbacks(paused, self.call_failed) else: reactor.callLater(0.3, self.pause) # @UndefinedVariable def stop(self): if self._state != 'Stopped': self.timer = None self.seconds = 0 self.player.Stop() self.reltime = '00:00:00' self.changed_state({'PlaybackStatus': 'Stopped'}) def next(self): if self._next: self._next = False if len(self.playlist) > 0: if self.songid == self.playlist[-1]: if self.repeat: self.play(songid=self.playlist[0]) else: self.play( songid=self.playlist[self.playlist.index(self.songid) + 1]) def previous(self): if len(self.playlist) > 0: if self.songid == self.playlist[0]: if not self.repeat: return self.play(songid=self.playlist[self.playlist.index(self.songid) - 1]) def volUp(self): if self._volume == 0: vol = self.get_volume() try: newvol = vol + 5 except: newvol = self._volume * 100 + 5 return self.set_volume('Master', newvol) else: if self._volume > 95: return newvol = self._volume * 100 + 5 return self.set_volume('Master', newvol) def volDown(self): if self._volume > 0.05: newvol = self._volume * 100 - 5 return self.set_volume('Master', newvol) else: self._volume = 0 return self.set_volume('Master', 0) def insert(self, url, afterid, metadata, checked=False): if 'youtube' in url and not checked: # eclipse workaround !!! y = os.environ y.update({'PYTHONPATH': '/usr/bin/python'}) # / eclipse workaround d = utils.getProcessOutput('/usr/bin/youtube-dl', ['-g', '-f', 'bestaudio', url], env=y, reactor=reactor) d.addCallback(lambda u: self.insert( u.split('\n')[0], afterid, metadata, True)) return d # log.err('playlist length:%s' % len(self._playlist)) self.maxsongid += 1 if len(self._playlist) == 0: self._playlist.append([self.maxsongid, url, metadata]) else: if afterid == '0': self._playlist.insert(0, [self.maxsongid, url, metadata]) else: self._playlist.insert( self.playlist[self.playlist.index(int(afterid))], [self.maxsongid, url, metadata]) # log.err('real playlist: %s' % self._playlist) self.playlist = [i[0] for i in self._playlist] # log.err('new playlist: %s' % self.playlist) # log.err('metadata dic: %s' % metadata) self.oh_playlist = [str(i) for i in self.playlist] self.idArray = id_array(self.playlist) self.changed_tracks() if self.songid == 0: self.update_metadata({'songid': 1}) return defer.succeed(self.maxsongid) def delete(self, songid): # log.err(self.playlist) try: suppressed = self.playlist.index(int(songid)) except IndexError: pass else: self._playlist.pop(suppressed) self.playlist.pop(suppressed) self.idArray = id_array(self.playlist) self.changed_tracks() def clear(self): self.playlist = [] self._playlist = [] self.songid = 0 self.idArray = '' self.changed_state('TrackList', {}, '')