#!/usr/bin/env python # -*- coding: utf-8 -*- # #This program is free software; you can redistribute it and/or modify #it under the terms of the GNU General Public License as published by #the Free Software Foundation; either version 2 of the License, or #(at your option) any later version. # Copyright 2006 Eli J. MacKenzie # Inspired by `media` (Copyright 2005 İsmail Dönmez) # If you wish to customize the formatting strings, do so in this table. # Do not change the numbers unless you're changing the logic. # Title, artist, and album will be set once the player is queried. # See Player.format() for how these are used. #Changing these 3 values will likely cause the script to fail Title =4 Artist=2 Album =1 #To disable self-titled (eponymous) checking, subtract 8 SelfTitled=11 outputFormat="/me $intro $info [$player]" formatStrings = { Title+SelfTitled : "$title by $artist (eponymous)", SelfTitled : "${artist}'s self-titled album", Title+Artist+Album : "$title by $artist on $album", #7,15 Title+Artist : "$title by $artist", #6,14 Title+Album : "$title from $album", #5,13 Album+Artist : "$album by $artist", #3,11 Title : "$title", #4,12 Artist : "$artist", #2,10 Album : "$album", #1,9 } #Intro defaults to first type the player supports when a specific type was not demanded formatVariables={'audio': 'is listening to', 'video': 'is watching'} ## Static player ranking list ## If you add a new player, you must add it here or it won't get checked when in audio-only or video-only modes. playerRankings= { 'video' :['kaffeine','kmplayer', 'kplayer', 'noatun', 'kdetv'], 'audio' :['amarok', 'MPD' 'juk', 'noatun', 'kscd', 'kaffeine', 'kmplayer', 'Audacious', 'xmms', 'yammi'] } ## Title, album and artist fields to be quoted depending on contents # List the possible trigger characters here. # If you want a '-', it must be first. if you want a '^', it must be last. SIMPLE_FIXUP = '' #I use ' ' # If you want to use a regex for the above, specify it here in which case it will be used REGEX_FIXUP = '' # Quote chars to use: QUOTE_BEFORE = '"' QUOTE_AFTER = '"' ############################### ## The Real work is done below ############################# import os import sys import re import string from functools import reduce try: APP_ID = sys.argv[1] IRC_SERVER = sys.argv[2] TARGET = sys.argv[3] except IndexError: print("This script is intended to be run from within Konversation.", file=sys.stderr) sys.exit(0) if (sys.hexversion >> 16) < 0x0204: err="The media script requires Python 2.4." os.popen('dcop %s default error "%s"' %(APP_ID,err)) sys.exit(err) import subprocess # Python 2.5 has this ... try: any(()) except NameError: def any(data): """Return true of any of the items in the sequence 'data' are true. (ie non-zero or not empty)""" try: return reduce(lambda x,y: bool(x) or bool(y), data) except TypeError: return False def tell(data, feedback='info'): """Report back to the user""" l=['dcop', APP_ID, 'default', feedback] if type(data) is type(''): l.append(data) else: l.extend(data) subprocess.Popen(l).communicate() class Player(object): def __init__(self, display_name, playerType=None): if playerType is None: self.type = "audio" else: self.type=playerType self.displayName=display_name self.running = False d={} d.update(formatVariables) d['player']=self.displayName self._format = d def get(self, mode): data=self.getData() if any(data): self._format['info']=self.format(*data) if mode and mode != self.displayName: self._format['intro']=self._format[mode] else: self._format['intro']=self._format[self.type.replace(',','').split()[0]] return string.Template(outputFormat).safe_substitute(self._format) return '' def format(self, title='', artist='', album=''): """Return a 'pretty-printed' info string for the track. Uses formatStrings from above.""" #Update args last to prevent non-sensical override in formatVariables x={'title':title, 'artist':artist, 'album':album} if FIXUP: for i,j in list(x.items()): if re.search(FIXUP,j): x[i]='%s%s%s'%(QUOTE_BEFORE,j,QUOTE_AFTER) self._format.update(x) n=0 if title: n|=4 #Still binary to make you read the code ;p if artist: if artist == album: n|=SelfTitled else: n|=2 if album: n|=1 if n: return string.Template(formatStrings[n]).safe_substitute(self._format) return '' def getData(self): """Implement this to do the work""" return '' def reEncodeString(self, input): if input: try: input = input.decode('utf-8') except UnicodeError: try: input = input.decode('latin-1') except UnicodeError: input = input.decode('ascii', 'replace') except NameError: pass return input.encode('utf-8') def test_format(self, title='', artist='', album=''): s=[] l=["to","by","on"] if title: s.append(title) else: album,artist=artist,album l.pop() if artist: s.append(artist) else: del l[1] if album: s.append(album) else: l.pop() t=["is listening"] while l: t.append(l.pop(0)) t.append(s.pop(0)) return ' '.join(t) def isRunning(self): return self.running class DCOPPlayer(Player): def __init__(self, display_name, service_name, getTitle='', getArtist='', getAlbum='',playerType=None): Player.__init__(self, display_name, playerType) self.serviceName=service_name self._title=getTitle self._artist=getArtist self._album=getAlbum self.DCOP="" def getData(self): self.getService() return (self.grab(self._title), self.grab(self._artist), self.grab(self._album)) def getService(self): if self.DCOP: return self.DCOP running = re.findall('^' + self.serviceName + "(?:-\\d*)?$", DCOP_ITEMS, re.M) if type(running) is list: try: running=running[0] except IndexError: running='' self.DCOP=running.strip() self.running=bool(self.DCOP) return self.DCOP def grab(self, item): if item and self.isRunning(): return self.reEncodeString(os.popen("dcop %s %s"%(self.DCOP, item)).readline().rstrip('\n')) return '' def isRunning(self): self.getService() return self.running class AmarokPlayer(DCOPPlayer): def __init__(self): DCOPPlayer.__init__(self,'Amarok','amarok','player title','player artist','player album') def getData(self): data=DCOPPlayer.getData(self) if not any(data): data=(self.grab('player nowPlaying'),'','') if not data[0]: return '' return data #class Amarok2Player(Player): # def __init__(self): # Player.__init__(self, 'Amarok2', 'audio') # self.isRunning() # # def getData(self): # playing=os.popen("qdbus org.mpris.amarok /Player PositionGet").readline().strip() != "0" # if playing and self.isRunning(): # for line in os.popen("qdbus org.mpris.amarok /Player GetMetadata").readlines(): # if re.match("^title", line): # title=self.reEncodeString(line.strip().split(None,1)[1]) # if re.match("^artist", line): # artist=self.reEncodeString(line.strip().split(None,1)[1]) # if re.match("^album", line): # album=self.reEncodeString(line.strip().split(None,1)[1]) # return (title, artist, album) # else: # return '' # # def isRunning(self): # qdbus_items=subprocess.Popen(['qdbus'], stdout=subprocess.PIPE).communicate()[0] # running=re.findall('^ org.mpris.amarok$', qdbus_items, re.M) # if type(running) is list: # try: # running=running[0] # except IndexError: # running='' # self.running=bool(running.strip()) # return self.running import socket class MPD(Player): def __init__(self, display_name): Player.__init__(self, display_name) self.host = "localhost" self.port = 6600 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.settimeout(0.5) try: self.sock.connect((self.host, self.port)) # just welcome message, we don't need it self.sock.recv(1024) self.running = True except socket.error: self.running = False def getData(self): if not self.running: return '' try: self.sock.send("currentsong\n") data = self.sock.recv(1024) except socket.error: return '' # mpd sends OK always, so if nothing to show, we should seek for at least 3 chars if len(data) < 4: return '' else: # if there is Artist, Title and Album, get it data=data.splitlines() d={} for i in data: if ':' not in i: continue k,v=i.split(':',1) d[k.lower()]=self.reEncodeString(v.strip()) data=(d.get('title',''),d.get('artist',''),d.get('album','')) if not any(data): return d.get('file','') return data class StupidPlayer(DCOPPlayer): def getData(self): data=DCOPPlayer.getData(self)[0] if data: if data.startswith('URL'): # KMPlayer window titles in the form of "URL - file:///path/to/ - KMPlayer" data=data.split(None,2)[2].rsplit(None,2)[0].rsplit('/')[-1] else: # KPlayer window titles in the form of " - KPlayer" data=data.rsplit(None,2)[0] return (data,'','') return '' try: import xmms.common class XmmsPlayer(Player): def __init__(self, display_name): Player.__init__(self, display_name) def isRunning(self): self.running = xmms.control.is_running() return self.running def getData(self): if self.isRunning() and xmms.control.is_playing(): # get the position in the playlist for current playing track index = xmms.control.get_playlist_pos(); # get the title of the currently playing track return (self.reEncodeString(xmms.control.get_playlist_title(index)),'','') return '' except ImportError: XmmsPlayer=Player class AudaciousPlayer(Player): def __init__(self, display_name): Player.__init__(self, display_name) def isRunning(self): self.running = not os.system('audtool current-song') return self.running def getData(self): if self.isRunning() and not os.system('audtool playback-playing'): # get the title of the currently playing track data = os.popen('audtool current-song').read().strip() data_list = data.split(' - ') list_length = len(data_list) if list_length == 1: return (self.reEncodeString(data_list[0]),'','') elif list_length == 3: return (self.reEncodeString(data_list[-1]),data_list[0],data_list[1]) else: return (self.reEncodeString(data),'','') else: return '' def playing(playerList, mode=None): for i in playerList: s=i.get(mode) if s: tell([IRC_SERVER, TARGET, s], 'say' ) return 1 return 0 def handleErrors(playerList, kind): if kind: kind=kind.strip() kind=kind.center(len(kind)+2) else: kind= ' supported ' x=any([i.running for i in playerList]) if x: l=[i.displayName for i in playerList if i.isRunning()] err= "Nothing is playing in %s."%(', '.join(l)) else: err= "No%splayers are running."%(kind,) tell(err,'error') def run(kind): if not kind: kind = '' play=PLAYERS else: if kind in ['audio', 'video']: unsorted=dict([(i.displayName.lower(),i) for i in PLAYERS if kind in i.type]) play=[unsorted.pop(i.lower(),Player("ImproperlySupported")) for i in playerRankings[kind]] if len(unsorted): play.extend(list(unsorted.values())) else: play=[i for i in PLAYERS if i.displayName.lower() == kind] try: kind=play[0].displayName except IndexError: tell("%s is not a supported player."%(kind,),'error') sys.exit(0) if not playing(play, kind): handleErrors(play, kind) #It would be so nice to just keep this pipe open and use it for all the dcop action, #but of course you're supposed to use the big iron (language bindings) instead of #the command line tools. One could consider `dcop` the bash dcop language binding, #but of course when using shell you don't need to be efficient at all, right? DCOP_ITEMS=subprocess.Popen(['dcop'], stdout=subprocess.PIPE).communicate()[0] #re.findall("^amarok(?:-\\d*)?$",l,re.M) # Add your new players here. No more faulty logic due to copy+paste. PLAYERS = [ AmarokPlayer(), DCOPPlayer("JuK","juk","Player trackProperty Title","Player trackProperty Artist","Player trackProperty Album"), DCOPPlayer("Noatun",'noatun',"Noatun title",playerType='audio, video'), DCOPPlayer("Kaffeine","kaffeine","KaffeineIface title","KaffeineIface artist","KaffeineIface album",playerType='video, audio'), StupidPlayer("KMPlayer","kmplayer","kmplayer-mainwindow#1 caption",playerType="video audio"), StupidPlayer("KPlayer","kplayer","kplayer-mainwindow#1 caption",playerType="video audio"), DCOPPlayer("KsCD","kscd","CDPlayer currentTrackTitle","CDPlayer currentArtist","CDPlayer currentAlbum"), DCOPPlayer("kdetv","kdetv","KdetvIface channelName",playerType='video'), AudaciousPlayer('Audacious'), XmmsPlayer('XMMS'), DCOPPlayer("Yammi","yammi","YammiPlayer songTitle","YammiPlayer songArtist","YammiPlayer songAlbum"), MPD('MPD') ] # Get rid of players that didn't get subclassed so they don't appear in the available players list for i in PLAYERS[:]: if type(i) is Player: PLAYERS.remove(i) if REGEX_FIXUP: FIXUP=REGEX_FIXUP elif SIMPLE_FIXUP: FIXUP="[%s]"%(SIMPLE_FIXUP) else: FIXUP='' # It all comes together right here if __name__=="__main__": if not TARGET: s="""media v2.0.1 for Konversation 1.0. One media command to rule them all, inspired from Kopete's now listening plugin. Usage: "\00312/media\017" - report what the first player found is playing "\00312/media\017 [ '\00312audio\017' | '\00312video\017' ]" - report what is playing in a supported audio or video player "\00312/media\017 { \00312Player\017 }" - report what is playing in \00312Player\017 if it is supported Available players are: """ + ', '.join([("%s (%s)"%(i.displayName,i.type)) for i in PLAYERS]) for i in s.splitlines(): tell(i) #tell("%s"%(len(s.splitlines()),)) # called from the server tab pass else: try: kind = sys.argv[4].lower() except IndexError: kind = None run(kind)