summaryrefslogtreecommitdiffstats
path: root/konversation/scripts/media
diff options
context:
space:
mode:
Diffstat (limited to 'konversation/scripts/media')
-rwxr-xr-xkonversation/scripts/media484
1 files changed, 484 insertions, 0 deletions
diff --git a/konversation/scripts/media b/konversation/scripts/media
new file mode 100755
index 0000000..e369262
--- /dev/null
+++ b/konversation/scripts/media
@@ -0,0 +1,484 @@
+#!/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
+
+try:
+ APP_ID = sys.argv[1]
+ IRC_SERVER = sys.argv[2]
+ TARGET = sys.argv[3]
+except IndexError:
+ print >>sys.stderr, "This script is intended to be run from within Konversation."
+ 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 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/<media file> - KMPlayer"
+ data=data.split(None,2)[2].rsplit(None,2)[0].rsplit('/')[-1]
+ else:
+ # KPlayer window titles in the form of "<media file> - 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(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)
+