#!/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', 'noatun', 'kdetv'],
'audio' :['Amarok', 'JuK', 'Noatun', 'Kaffeine', 'kscd', '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('latin-1')
except UnicodeError:
try:
input = input.decode('latin-1')
except UnicodeError:
input = input.decode('ascii', 'replace')
except NameError:
pass
return input.encode('latin-1')
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
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
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'),
DCOPPlayer("KsCD","kscd","CDPlayer currentTrackTitle","CDPlayer currentArtist","CDPlayer currentAlbum"),
XmmsPlayer('XMMS'),
DCOPPlayer("Yammi","yammi","YammiPlayer songTitle","YammiPlayer songArtist","YammiPlayer songAlbum"),
DCOPPlayer("kdetv","kdetv","KdetvIface channelName",playerType='video'),
]
# 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 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, in the order they will be checked:
""" + ', '.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)