| 1 |
|
|---|
| 2 |
|
|---|
| 3 |
|
|---|
| 4 |
|
|---|
| 5 |
|
|---|
| 6 |
|
|---|
| 7 |
|
|---|
| 8 |
|
|---|
| 9 |
|
|---|
| 10 |
|
|---|
| 11 |
|
|---|
| 12 |
|
|---|
| 13 |
|
|---|
| 14 |
|
|---|
| 15 |
|
|---|
| 16 |
|
|---|
| 17 |
|
|---|
| 18 |
|
|---|
| 19 |
|
|---|
| 20 |
|
|---|
| 21 |
|
|---|
| 22 |
|
|---|
| 23 |
|
|---|
| 24 |
|
|---|
| 25 |
|
|---|
| 26 |
import xchat |
|---|
| 27 |
import sys, struct |
|---|
| 28 |
import socket, os, pwd |
|---|
| 29 |
from subprocess import * |
|---|
| 30 |
|
|---|
| 31 |
pcop, pydcop, bus = False, False, False |
|---|
| 32 |
try: |
|---|
| 33 |
import pcop, pydcop |
|---|
| 34 |
except: pass |
|---|
| 35 |
try: |
|---|
| 36 |
import dbus |
|---|
| 37 |
bus = dbus.SessionBus() |
|---|
| 38 |
except: pass |
|---|
| 39 |
|
|---|
| 40 |
__module_name__ = "pymp3" |
|---|
| 41 |
__module_version__ = "0.5" |
|---|
| 42 |
__module_description__ = "mp3 announce/utils" |
|---|
| 43 |
|
|---|
| 44 |
__debugging__ = False |
|---|
| 45 |
|
|---|
| 46 |
if __debugging__: |
|---|
| 47 |
import traceback |
|---|
| 48 |
|
|---|
| 49 |
def print_debug(string): |
|---|
| 50 |
global __debugging__ |
|---|
| 51 |
if __debugging__: print "\00302" + str(string) + "\003" |
|---|
| 52 |
|
|---|
| 53 |
def print_info(string): |
|---|
| 54 |
print "\00303" + str(string) + "\003" |
|---|
| 55 |
|
|---|
| 56 |
players = { |
|---|
| 57 |
'audacious' : 'audacious', |
|---|
| 58 |
'beep' : 'beep-media-player', |
|---|
| 59 |
'xmms' : 'xmms', |
|---|
| 60 |
'banshee' : 'banshee.exe', |
|---|
| 61 |
'juk' : 'juk', |
|---|
| 62 |
'amarok' : 'amarokapp', |
|---|
| 63 |
'rhythmbox' : 'rhythmbox', |
|---|
| 64 |
} |
|---|
| 65 |
|
|---|
| 66 |
|
|---|
| 67 |
def which(): |
|---|
| 68 |
ps = Popen(['ps', 'aux'], stdout=PIPE) |
|---|
| 69 |
output = ps.stdout.readlines() |
|---|
| 70 |
for line in output: |
|---|
| 71 |
for player,findstr in players.items(): |
|---|
| 72 |
if line.rfind(findstr) > -1: |
|---|
| 73 |
return player |
|---|
| 74 |
return |
|---|
| 75 |
|
|---|
| 76 |
|
|---|
| 77 |
|
|---|
| 78 |
def command(runstr): |
|---|
| 79 |
return Popen(runstr.split(), stdout=PIPE).communicate()[0] |
|---|
| 80 |
|
|---|
| 81 |
|
|---|
| 82 |
SOCKET_PLAYERS = ['audacious', 'beep', 'xmms'] |
|---|
| 83 |
|
|---|
| 84 |
class SocketCommand: |
|---|
| 85 |
CMD_PLAY = 2 |
|---|
| 86 |
CMD_PAUSE = 3 |
|---|
| 87 |
CMD_STOP = 4 |
|---|
| 88 |
CMD_GET_PLAYLIST_POS = 7 |
|---|
| 89 |
|
|---|
| 90 |
|
|---|
| 91 |
|
|---|
| 92 |
CMD_GET_PLAYLIST_LENGTH = 9 |
|---|
| 93 |
CMD_GET_OUTPUT_TIME = 11 |
|---|
| 94 |
CMD_GET_PLAYLIST_FILE = 17 |
|---|
| 95 |
CMD_GET_PLAYLIST_TITLE = 18 |
|---|
| 96 |
CMD_GET_PLAYLIST_TIME = 19 |
|---|
| 97 |
CMD_GET_INFO = 20 |
|---|
| 98 |
CMD_EJECT = 28 |
|---|
| 99 |
CMD_PLAYLIST_PREV = 29 |
|---|
| 100 |
CMD_PLAYLIST_NEXT = 30 |
|---|
| 101 |
CMD_TOGGLE_REPEAT = 33 |
|---|
| 102 |
CMD_TOGGLE_SHUFFLE = 34 |
|---|
| 103 |
|
|---|
| 104 |
class ClientPacketHeader: |
|---|
| 105 |
def __init__(self): |
|---|
| 106 |
self.version,self.cmd,self.length = 0,0,0 |
|---|
| 107 |
def __repr__(self): |
|---|
| 108 |
return "<< %s : version: %s cmd: %s length: %s >>"\ |
|---|
| 109 |
%(self.__class__.__name__,self.version,self.cmd,self.length) |
|---|
| 110 |
def encode(self): |
|---|
| 111 |
return struct.pack("hhl",self.version,self.cmd,self.length) |
|---|
| 112 |
|
|---|
| 113 |
""" |
|---|
| 114 |
I've tried to make the following class a facsimily of a "persistent connection", |
|---|
| 115 |
but my attempts have led to the following error with xmms: |
|---|
| 116 |
** WARNING **: ctrl_write_packet(): Failed to send data: Broken pipe |
|---|
| 117 |
Even manually closing, deleting, and then re-initializing the socket did not avoid |
|---|
| 118 |
this warning. It seems that only letting the garbage collector snag old Connection |
|---|
| 119 |
objects makes xmms happy. |
|---|
| 120 |
|
|---|
| 121 |
There is one aspect here missing from Hudson's original library: sending a custom |
|---|
| 122 |
send format with the 'args' option. I wasn't using this feature in any requests, |
|---|
| 123 |
as all of my provided formats were 'l' anyway. |
|---|
| 124 |
""" |
|---|
| 125 |
class XmmsConnection: |
|---|
| 126 |
def __init__(self,session=0): |
|---|
| 127 |
self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) |
|---|
| 128 |
self.sock.connect("/tmp/xmms_%s.%d"%(pwd.getpwuid(os.geteuid())[0],session)) |
|---|
| 129 |
|
|---|
| 130 |
def read_header(self): |
|---|
| 131 |
head = ClientPacketHeader() |
|---|
| 132 |
head.version, head.cmd, head.length = struct.unpack("hhl",self.sock.recv(8)) |
|---|
| 133 |
return head |
|---|
| 134 |
|
|---|
| 135 |
def send(self, cmd, args=''): |
|---|
| 136 |
data = "" |
|---|
| 137 |
if isinstance(args, int): |
|---|
| 138 |
data = struct.pack('l', args) |
|---|
| 139 |
packet = struct.pack("hhl", 1, cmd, len(data)) + data |
|---|
| 140 |
self.sock.send(packet) |
|---|
| 141 |
|
|---|
| 142 |
def get_reply(self, format=''): |
|---|
| 143 |
header = self.read_header() |
|---|
| 144 |
if format: reply = struct.unpack(format, self.sock.recv(header.length)) |
|---|
| 145 |
else: reply = self.sock.recv(header.length) |
|---|
| 146 |
return reply |
|---|
| 147 |
|
|---|
| 148 |
class AudaciousConnection(XmmsConnection): |
|---|
| 149 |
def __init__(self,session=0): |
|---|
| 150 |
self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) |
|---|
| 151 |
self.sock.connect("/tmp/audacious_%s.%d"%(pwd.getpwuid(os.geteuid())[0],session)) |
|---|
| 152 |
|
|---|
| 153 |
class MediaPlayer: |
|---|
| 154 |
"""A superclass that implements some book-keeping and some convenience functions |
|---|
| 155 |
for media player objects. These objects print out as an announce string, and |
|---|
| 156 |
get (and cache) info from the player in a clean, consistent "getitem" API.""" |
|---|
| 157 |
def __init__(self, name): |
|---|
| 158 |
self.name = name |
|---|
| 159 |
|
|---|
| 160 |
def _nyi(self, name): |
|---|
| 161 |
print_debug("%s not yet implemented for `%s`." % name, self.name) |
|---|
| 162 |
|
|---|
| 163 |
def _empty_dict(self): |
|---|
| 164 |
"""Return an empty info dictionary with all of the keys set.""" |
|---|
| 165 |
keys = ['player', 'playlist_position', 'playlist_length', 'file', |
|---|
| 166 |
'display_title', 'elapsed', 'length', 'bitrate', 'frequency', |
|---|
| 167 |
'title', 'artist', 'album', 'track'] |
|---|
| 168 |
d = {} |
|---|
| 169 |
for key in keys: d[key] = '' |
|---|
| 170 |
d['player'] = self.name |
|---|
| 171 |
return d |
|---|
| 172 |
|
|---|
| 173 |
def human_bitrate(self, bps): |
|---|
| 174 |
"""Takes bits per second and returns a string with appropriate units.""" |
|---|
| 175 |
units = ['bps', 'kbps', 'Mbps'] |
|---|
| 176 |
|
|---|
| 177 |
|
|---|
| 178 |
|
|---|
| 179 |
if str(bps).endswith("00"): |
|---|
| 180 |
reduce_factor = 1000 |
|---|
| 181 |
else: |
|---|
| 182 |
reduce_factor = 1024.0 |
|---|
| 183 |
oom = 0 |
|---|
| 184 |
while bps /(reduce_factor**(oom+1)) >= 1: |
|---|
| 185 |
oom += 1 |
|---|
| 186 |
return '%0.1f %s' % (bps/reduce_factor**oom, units[oom]) |
|---|
| 187 |
|
|---|
| 188 |
def s_to_ms(self, s): |
|---|
| 189 |
"""Converts seconds to minutes:seconds: mm:ss.""" |
|---|
| 190 |
s = int(s) |
|---|
| 191 |
sec = s % 60 |
|---|
| 192 |
min = s / 60 |
|---|
| 193 |
return '%2d:%02d' % (min, sec) |
|---|
| 194 |
|
|---|
| 195 |
def us_to_ms(self, us): |
|---|
| 196 |
"""Converts miliseconds to minutes:seconds: mm:ss.""" |
|---|
| 197 |
us = int(us) |
|---|
| 198 |
return self.s_to_ms(us/1000) |
|---|
| 199 |
|
|---|
| 200 |
def play(self): self._nyi('Play') |
|---|
| 201 |
def stop(self): self._nyi('Stop') |
|---|
| 202 |
def pause(self): self._nyi('Pause') |
|---|
| 203 |
def next(self): self._nyi('Next') |
|---|
| 204 |
def prev(self): self._nyi('Prev') |
|---|
| 205 |
def eject(self): self._nyi('eject') |
|---|
| 206 |
def open(self): self._nyi('open') |
|---|
| 207 |
def shuffle(self): self._nyi('shuffle') |
|---|
| 208 |
def repeat(self): self._nyi('repeat') |
|---|
| 209 |
|
|---|
| 210 |
def get_info(self): return self._empty_dict() |
|---|
| 211 |
|
|---|
| 212 |
def __str__(self): |
|---|
| 213 |
"""FIXME: This implements the old announce strings. It's probably easier |
|---|
| 214 |
to move this to the subclasses, but for now this is fine.""" |
|---|
| 215 |
info = self.get_info() |
|---|
| 216 |
if self.name in SOCKET_PLAYERS: |
|---|
| 217 |
return '%s ~ [%s] of [%s] ~ %s ~ %sHz' % (info['display_title'], \ |
|---|
| 218 |
info['elapsed'], info['length'], info['bitrate'], info['frequency']) |
|---|
| 219 |
|
|---|
| 220 |
elif self.name in ['juk', 'amarok']: |
|---|
| 221 |
return '%s - [%s] - %s ~ [%s] of [%s] ~ %s' % (info['artist'], \ |
|---|
| 222 |
info['album'], info['title'], info['elapsed'], info['length'], \ |
|---|
| 223 |
info['bitrate']) |
|---|
| 224 |
|
|---|
| 225 |
elif self.name in ['banshee', 'rhythmbox']: |
|---|
| 226 |
return '%s - [%s] - %s ~ [%s] of [%s]' % (info['artist'], info['album'], \ |
|---|
| 227 |
info['title'], info['elapsed'], info['length']) |
|---|
| 228 |
|
|---|
| 229 |
def __repr__(self): |
|---|
| 230 |
return '<MediaPlayer %s ...>' % (self.name) |
|---|
| 231 |
|
|---|
| 232 |
class Xmms(MediaPlayer): |
|---|
| 233 |
def __init__(self, name='xmms'): |
|---|
| 234 |
MediaPlayer.__init__(self, name) |
|---|
| 235 |
self._ifcache = {} |
|---|
| 236 |
|
|---|
| 237 |
def _makeConnection(self): |
|---|
| 238 |
if self.name in ['beep', 'xmms']: return XmmsConnection() |
|---|
| 239 |
elif self.name == 'audacious': return AudaciousConnection() |
|---|
| 240 |
return False |
|---|
| 241 |
|
|---|
| 242 |
def _cmd(self, command, args='', reply_format=''): |
|---|
| 243 |
connection = self._makeConnection() |
|---|
| 244 |
connection.send(command, args=args) |
|---|
| 245 |
return connection.get_reply(format=reply_format) |
|---|
| 246 |
|
|---|
| 247 |
def play(self): self._cmd(SocketCommand.CMD_PLAY) |
|---|
| 248 |
def stop(self): self._cmd(SocketCommand.CMD_STOP) |
|---|
| 249 |
def pause(self): self._cmd(SocketCommand.CMD_PAUSE) |
|---|
| 250 |
def next(self): self._cmd(SocketCommand.CMD_PLAYLIST_NEXT) |
|---|
| 251 |
def prev(self): self._cmd(SocketCommand.CMD_PLAYLIST_PREV) |
|---|
| 252 |
def eject(self): self._cmd(SocketCommand.CMD_EJECT) |
|---|
| 253 |
def open(self): self._cmd(SocketCommand.CMD_EJECT) |
|---|
| 254 |
def shuffle(self): self._cmd(SocketCommand.CMD_TOGGLE_SHUFFLE) |
|---|
| 255 |
def repeat(self): self._cmd(SocketCommand.CMD_TOGGLE_REPEAT) |
|---|
| 256 |
|
|---|
| 257 |
def get_info(self): |
|---|
| 258 |
d = self._empty_dict() |
|---|
| 259 |
d['playlist_position'] = self._cmd(SocketCommand.CMD_GET_PLAYLIST_POS, reply_format='l')[0] |
|---|
| 260 |
position = d['playlist_position'] |
|---|
| 261 |
d['playlist_length'] = self._cmd(SocketCommand.CMD_GET_PLAYLIST_LENGTH, reply_format='l')[0] |
|---|
| 262 |
d['file'] = self._cmd(SocketCommand.CMD_GET_PLAYLIST_FILE, args=position)[:-1] |
|---|
| 263 |
d['display_title'] = self._cmd(SocketCommand.CMD_GET_PLAYLIST_TITLE, args=position)[:-1] |
|---|
| 264 |
info = self._cmd(SocketCommand.CMD_GET_INFO, reply_format='lll') |
|---|
| 265 |
utime_elapsed = self._cmd(SocketCommand.CMD_GET_OUTPUT_TIME, reply_format='l')[0] |
|---|
| 266 |
utime_length = self._cmd(SocketCommand.CMD_GET_PLAYLIST_TIME, args=position, reply_format='l')[0] |
|---|
| 267 |
d['elapsed'] = self.us_to_ms(utime_elapsed) |
|---|
| 268 |
d['length'] = self.us_to_ms(utime_length) |
|---|
| 269 |
d['bitrate'] = self.human_bitrate(info[0]) |
|---|
| 270 |
d['frequency'] = info[1] |
|---|
| 271 |
return d |
|---|
| 272 |
|
|---|
| 273 |
BEEP_FIRST_RUN = True |
|---|
| 274 |
BEEP_MESSAGE = """beep-media-player has a bug with its control socket and returns |
|---|
| 275 |
bogus information for bitrate, frequency, and number of channels. Consider the |
|---|
| 276 |
'audacious' media player, or BMPx, as beep-media-player is no longer in |
|---|
| 277 |
development.""".replace("\n", ' ') |
|---|
| 278 |
|
|---|
| 279 |
class Beep(Xmms): |
|---|
| 280 |
def __init__(self): |
|---|
| 281 |
global BEEP_FIRST_RUN, BEEP_MESSAGE |
|---|
| 282 |
if BEEP_FIRST_RUN: |
|---|
| 283 |
print_info(BEEP_MESSAGE) |
|---|
| 284 |
BEEP_FIRST_RUN = False |
|---|
| 285 |
Xmms.__init__(self, 'beep') |
|---|
| 286 |
|
|---|
| 287 |
class Audacious(Xmms): |
|---|
| 288 |
def __init__(self): |
|---|
| 289 |
Xmms.__init__(self, 'audacious') |
|---|
| 290 |
|
|---|
| 291 |
BANSHEE_FIRST_RUN = True |
|---|
| 292 |
BANSHEE_MESSAGE = """Although banshee is supported without them, it is recommended |
|---|
| 293 |
that you install the python-dbus bindings for increased speed.""".replace("\n", " ") |
|---|
| 294 |
|
|---|
| 295 |
class Banshee(MediaPlayer): |
|---|
| 296 |
def __init__(self): |
|---|
| 297 |
global BANSHEE_FIRST_RUN, BANSHEE_MESSAGE |
|---|
| 298 |
if BANSHEE_FIRST_RUN and not bus: |
|---|
| 299 |
print_info(BANSHEE_MESSAGE) |
|---|
| 300 |
BANSHEE_FIRST_RUN = False |
|---|
| 301 |
MediaPlayer.__init__(self, 'banshee') |
|---|
| 302 |
self._ifcache = {} |
|---|
| 303 |
interface = ['play', 'stop', 'pause', 'next', 'prev', 'eject', 'open', 'get_info'] |
|---|
| 304 |
if bus: |
|---|
| 305 |
self.d_obj = bus.get_object("org.gnome.Banshee", "/org/gnome/Banshee/Player") |
|---|
| 306 |
self.banshee = dbus.Interface(self.d_obj, "org.gnome.Banshee.Core") |
|---|
| 307 |
for func in interface: |
|---|
| 308 |
setattr(self, func, getattr(self, '%s_dbus' % func)) |
|---|
| 309 |
else: |
|---|
| 310 |
for func in interface: |
|---|
| 311 |
setattr(self, func, getattr(self, '%s_nodbus' % func)) |
|---|
| 312 |
|
|---|
| 313 |
def play_dbus(self): self.banshee.Play() |
|---|
| 314 |
def stop_dbus(self): self.banshee.Pause() |
|---|
| 315 |
def pause_dbus(self): self.banshee.TogglePlaying() |
|---|
| 316 |
def next_dbus(self): self.banshee.Next() |
|---|
| 317 |
def prev_dbus(self): self.banshee.Previous() |
|---|
| 318 |
def eject_dbus(self): self.banshee.ShowWindow() |
|---|
| 319 |
def open_dbus(self): self.banshee.ShowWindow() |
|---|
| 320 |
|
|---|
| 321 |
def get_info_dbus(self): |
|---|
| 322 |
d = self._empty_dict() |
|---|
| 323 |
d['length'] = self.s_to_ms(self.banshee.GetPlayingDuration()) |
|---|
| 324 |
d['elapsed'] = self.s_to_ms(self.banshee.GetPlayingPosition()) |
|---|
| 325 |
d['artist'] = unicode(self.banshee.GetPlayingArtist()).encode('UTF-8') |
|---|
| 326 |
d['title'] = unicode(self.banshee.GetPlayingTitle()).encode('UTF-8') |
|---|
| 327 |
d['album'] = unicode(self.banshee.GetPlayingAlbum()).encode('UTF-8') |
|---|
| 328 |
return d |
|---|
| 329 |
|
|---|
| 330 |
def play_nodbus(self): command('banshee --play') |
|---|
| 331 |
def stop_nodbus(self): command('banshee --pause') |
|---|
| 332 |
def pause_nodbus(self): command('banshee --toggle-playing') |
|---|
| 333 |
def next_nodbus(self): command('banshee --next') |
|---|
| 334 |
def prev_nodbus(self): command('banshee --previous') |
|---|
| 335 |
def eject_nodbus(self): command('banshee --show') |
|---|
| 336 |
def open_nodbus(self): command('banshee --show') |
|---|
| 337 |
|
|---|
| 338 |
|
|---|
| 339 |
def get_info_nodbus(self): |
|---|
| 340 |
d = self._empty_dict() |
|---|
| 341 |
info = command(' '.join(['banshee', '--hide-field', '--query-title', |
|---|
| 342 |
'--query-artist', '--query-position', '--query-album', |
|---|
| 343 |
'--query-duration'])).strip() |
|---|
| 344 |
|
|---|
| 345 |
|
|---|
| 346 |
info = info.split('\n') |
|---|
| 347 |
d['length'] = self.s_to_ms(info[0]) |
|---|
| 348 |
d['artist'] = info[1] |
|---|
| 349 |
d['album'] = info[2] |
|---|
| 350 |
d['title'] = info[3] |
|---|
| 351 |
d['elapsed'] = self.s_to_ms(info[4]) |
|---|
| 352 |
return d |
|---|
| 353 |
|
|---|
| 354 |
class Rhythmbox(MediaPlayer): |
|---|
| 355 |
"""MediaPlayer class for Rhythmbox, a Gtk/Gnome media player. It's possible |
|---|
| 356 |
to implement this without using python-dbus by wrapping around 'dbus-send', |
|---|
| 357 |
but frankly I didn't feel like it.""" |
|---|
| 358 |
def __init__(self): |
|---|
| 359 |
if not bus: |
|---|
| 360 |
raise Exception('Rhythmbox is not supported w/o python-dbus bindings.') |
|---|
| 361 |
MediaPlayer.__init__(self, 'rhythmbox') |
|---|
| 362 |
player_obj = bus.get_object("org.gnome.Rhythmbox", "/org/gnome/Rhythmbox/Player") |
|---|
| 363 |
shell_obj = bus.get_object("org.gnome.Rhythmbox", "/org/gnome/Rhythmbox/Shell") |
|---|
| 364 |
self.player = dbus.Interface(player_obj, "org.gnome.Rhythmbox.Player") |
|---|
| 365 |
self.shell = dbus.Interface(shell_obj, "org.gnome.Rhythmbox.Shell") |
|---|
| 366 |
|
|---|
| 367 |
def play(self): |
|---|
| 368 |
if not bool(self.player.getPlaying()): self.player.playPause() |
|---|
| 369 |
def stop(self): |
|---|
| 370 |
if bool(self.player.getPlaying()): self.player.playPause() |
|---|
| 371 |
def pause(self): self.player.playPause() |
|---|
| 372 |
def next(self): self.player.next() |
|---|
| 373 |
def prev(self): self.player.previous() |
|---|
| 374 |
def eject(self): print_info("There isn't an easy way to do this in rhythmbox right now.") |
|---|
| 375 |
def open(self): print_info("There isn't an easy way to do this in rhythmbox right now.") |
|---|
| 376 |
|
|---|
| 377 |
def get_info(self): |
|---|
| 378 |
d = self._empty_dict() |
|---|
| 379 |
uri = unicode(self.player.getPlayingUri()) |
|---|
| 380 |
properties = dict([(unicode(key), val) for key,val in dict(self.shell.getSongProperties(uri)).items()]) |
|---|
| 381 |
d['length'] = self.s_to_ms(int(properties.get('duration', 0))) |
|---|
| 382 |
d['elapsed'] = self.s_to_ms(int(self.player.getElapsed())) |
|---|
| 383 |
d['artist'] = unicode(properties.get('artist', '')).encode('UTF-8') |
|---|
| 384 |
d['album'] = unicode(properties.get('album', '')).encode('UTF-8') |
|---|
| 385 |
d['title'] = unicode(properties.get('title', '')).encode('UTF-8') |
|---|
| 386 |
|
|---|
| 387 |
return d |
|---|
| 388 |
|
|---|
| 389 |
JUK_FIRST_RUN = True |
|---|
| 390 |
DCOP_MESSAGE = """Although juk is supported without them, it is recommended that |
|---|
| 391 |
you install the python-dcop bindings for increased speed.""".replace("\n", ' ') |
|---|
| 392 |
|
|---|
| 393 |
class Juk(MediaPlayer): |
|---|
| 394 |
"""MediaPlayer class for Juk, a Qt/KDE media player. This implementation is |
|---|
| 395 |
a bit messy because it resolves whether or not to use DCOP statically; after |
|---|
| 396 |
importing, the comparissons are made and the appropriate functions are used.""" |
|---|
| 397 |
def __init__(self): |
|---|
| 398 |
global JUK_FIRST_RUN, DCOP_MESSAGE, pydcop |
|---|
| 399 |
if JUK_FIRST_RUN and not pydcop: |
|---|
| 400 |
print_info(DCOP_MESSAGE) |
|---|
| 401 |
JUK_FIRST_RUN = False |
|---|
| 402 |
MediaPlayer.__init__(self, 'juk') |
|---|
| 403 |
self._ifcache = {} |
|---|
| 404 |
|
|---|
| 405 |
self._functions = ['eject', 'open'] |
|---|
| 406 |
|
|---|
| 407 |
|
|---|
| 408 |
self._func_map = {'play':'play', 'stop':'stop', 'pause':'playPause', 'next':'forward', 'prev':'back'} |
|---|
| 409 |
if pydcop: |
|---|
| 410 |
|
|---|
| 411 |
self.juk = pydcop.anyAppCalled("juk") |
|---|
| 412 |
self.get_property = (lambda x: self.juk.Player.trackProperty(x)) |
|---|
| 413 |
self.get_juk = (lambda func: getattr(self.juk.Player, func)()) |
|---|
| 414 |
for func in self._functions: |
|---|
| 415 |
setattr(self, func, getattr(self, '_%s_dcop' % func)) |
|---|
| 416 |
else: |
|---|
| 417 |
|
|---|
| 418 |
self.get_property = (lambda x: command('dcop juk Player trackProperty %s' % (x)).strip()) |
|---|
| 419 |
self.get_juk = (lambda func: command('dcop juk Player %s' % func)) |
|---|
| 420 |
for func in self._functions: |
|---|
| 421 |
setattr(self, func, getattr(self, '_%s_nodcop' % func)) |
|---|
| 422 |
|
|---|
| 423 |
|
|---|
| 424 |
for funcname, juk_property in self._func_map.items(): |
|---|
| 425 |
setattr(self, funcname, (lambda prop=juk_property: self.get_juk(prop))) |
|---|
| 426 |
|
|---|
| 427 |
def _eject_dcop(self): |
|---|
| 428 |
pcop.dcop_call("juk", "juk-mainwindow#1", "restore", ()) |
|---|
| 429 |
pcop.dcop_call("juk", "juk-mainwindow#1", "raise", ()) |
|---|
| 430 |
def _open_dcop(self): self._eject_dcop() |
|---|
| 431 |
|
|---|
| 432 |
def _eject_nodcop(self): |
|---|
| 433 |
command('dcop juk juk-mainwindow#1 restore') |
|---|
| 434 |
command('dcop juk juk-mainwindow#1 raise') |
|---|
| 435 |
def _open_nodcop(self): self._eject_nodcop() |
|---|
| 436 |
|
|---|
| 437 |
def get_info(self): |
|---|
| 438 |
d = self._empty_dict() |
|---|
| 439 |
elapsed = self.get_juk('currentTime') |
|---|
| 440 |
d['elapsed'] = self.s_to_ms(elapsed) |
|---|
| 441 |
d['title'] = self.get_property('Title') |
|---|
| 442 |
d['artist'] = self.get_property('Artist') |
|---|
| 443 |
d['album'] = self.get_property('Album') |
|---|
| 444 |
d['length'] = self.s_to_ms(self.get_property('Seconds')) |
|---|
| 445 |
d['bitrate'] = '%s Kbps' % self.get_property('Bitrate') |
|---|
| 446 |
return d |
|---|
| 447 |
|
|---|
| 448 |
AMAROK_FIRST_RUN = True |
|---|
| 449 |
AMAROK_DCOP_MESSAGE = """Although amarok is supported without them, it is recommended that |
|---|
| 450 |
you install the python-dcop bindings for increased speed.""".replace("\n", ' ') |
|---|
| 451 |
|
|---|
| 452 |
class Amarok(MediaPlayer): |
|---|
| 453 |
"""MediaPlayer class for Amarok, a Qt/KDE media player. This implementation is |
|---|
| 454 |
a bit messy because it resolves whether or not to use DCOP statically; after |
|---|
| 455 |
importing, the comparissons are made and the appropriate functions are used.""" |
|---|
| 456 |
def __init__(self): |
|---|
| 457 |
global AMAROK_FIRST_RUN, AMAROK_DCOP_MESSAGE, pydcop |
|---|
| 458 |
if AMAROK_FIRST_RUN and not pydcop: |
|---|
| 459 |
print_info(AMAROK_DCOP_MESSAGE) |
|---|
| 460 |
AMAROK_FIRST_RUN = False |
|---|
| 461 |
MediaPlayer.__init__(self, 'amarok') |
|---|
| 462 |
self._ifcache = {} |
|---|
| 463 |
"""If the pydcop is available, then we create a 'self.get_property' function |
|---|
| 464 |
that uses pydcop; if it isn't available, we create a function that works the same |
|---|
| 465 |
but using our 'command' interface. Then, using the 'self.get_property', we bind |
|---|
| 466 |
'self.play', 'self.stop', etc. to the object's namespace.""" |
|---|
| 467 |
self._functions = ['play', 'stop', 'pause'] |
|---|
| 468 |
if pydcop: |
|---|
| 469 |
self.amarok = pydcop.anyAppCalled("amarok") |
|---|
| 470 |
self.get_property = (lambda x: getattr(self.amarok.player, x)()) |
|---|
| 471 |
self.get_playlist = (lambda x: getattr(self.amarok.playlist, x)()) |
|---|
| 472 |
self.set_playlist = (lambda x: self.amarok.playlist.playByIndex(x)) |
|---|
| 473 |
else: |
|---|
| 474 |
self.get_property = (lambda x: command('dcop amarok player %s' % x).strip()) |
|---|
| 475 |
self.get_playlist = (lambda x: command('dcop amarok playlist %s' % x).strip()) |
|---|
| 476 |
self.set_playlist = (lambda x: command('dcop amarok playlist playByIndex %s' % x)) |
|---|
| 477 |
for func in self._functions: |
|---|
| 478 |
setattr(self, func, (lambda func=func: self.get_property(func))) |
|---|
| 479 |
|
|---|
| 480 |
def open(self): print_info("There isn't an easy way to do this with amarok right now.") |
|---|
| 481 |
def eject(self): print_info("There isn't an easy way to do this with amarok right now.") |
|---|
| 482 |
|
|---|
| 483 |
def prev_n(self, n): |
|---|
| 484 |
"""Go backwards 'n' times in the playlist""" |
|---|
| 485 |
position = self.get_playlist('getActiveIndex') |
|---|
| 486 |
new_position = position - n |
|---|
| 487 |
if new_position < 0: new_position = 0 |
|---|
| 488 |
self.set_playlist(new_position) |
|---|
| 489 |
|
|---|
| 490 |
def next_n(self, n): |
|---|
| 491 |
"""Go forwards 'n' times in the playlist""" |
|---|
| 492 |
position = self.get_playlist('getActiveIndex') |
|---|
| 493 |
playlist_length = self.get_playlist('getTotalTrackCount') |
|---|
| 494 |
new_position = position + n |
|---|
| 495 |
if new_position >= playlist_length: |
|---|
| 496 |
new_position = playlist_length - 1 |
|---|
| 497 |
self.set_playlist(new_position) |
|---|
| 498 |
|
|---|
| 499 |
def get_info(self): |
|---|
| 500 |
d = self._empty_dict() |
|---|
| 501 |
|
|---|
| 502 |
d['elapsed'] = self.get_property('currentTime') |
|---|
| 503 |
d['title'] = self.get_property('title') |
|---|
| 504 |
d['artist'] = self.get_property('artist') |
|---|
| 505 |
d['album'] = self.get_property('album') |
|---|
| 506 |
d['length'] = self.get_property('totalTime') |
|---|
| 507 |
d['bitrate'] = '%s Kbps' % self.get_property('bitrate') |
|---|
| 508 |
return d |
|---|
| 509 |
|
|---|
| 510 |
def current_player(): |
|---|
| 511 |
player = which() |
|---|
| 512 |
print_debug("detected %s is running" % player) |
|---|
| 513 |
if not player: |
|---|
| 514 |
raise Exception("Currently not running a supported media player.") |
|---|
| 515 |
player_obj = eval("%s()" % player.capitalize()) |
|---|
| 516 |
return player_obj |
|---|
| 517 |
|
|---|
| 518 |
def help(args): |
|---|
| 519 |
print "\nCommands:" |
|---|
| 520 |
print " \002/mp3\002 : announce the currently playing mp3" |
|---|
| 521 |
print " \002/mp3\002 \00303stop\003 : stop playing" |
|---|
| 522 |
print " \002/mp3\002 \00303play\003 : start playing" |
|---|
| 523 |
print " \002/mp3\002 \00303pause\003 : pause playback" |
|---|
| 524 |
print " \002/mp3\002 \00303next [#]\003 : skip to next (# of) track(s)" |
|---|
| 525 |
print " \002/mp3\002 \00303prev [#]\003 : skip to prev (# of) track(s)" |
|---|
| 526 |
print " \002/mp3\002 \00303open\003 : open files" |
|---|
| 527 |
print "" |
|---|
| 528 |
|
|---|
| 529 |
def usage(): |
|---|
| 530 |
print "Usage: \002/mp3\002 [cmd]\n\002/mp3\002 \037help\037 for commands." |
|---|
| 531 |
|
|---|
| 532 |
def announce(): |
|---|
| 533 |
player = current_player() |
|---|
| 534 |
xchat.command('me is listening to: %s' % (player)) |
|---|
| 535 |
|
|---|
| 536 |
def stop(*args): |
|---|
| 537 |
player = current_player() |
|---|
| 538 |
player.stop() |
|---|
| 539 |
|
|---|
| 540 |
def play(*args): |
|---|
| 541 |
player = current_player() |
|---|
| 542 |
player.play() |
|---|
| 543 |
|
|---|
| 544 |
def pause(*args): |
|---|
| 545 |
player = current_player() |
|---|
| 546 |
player.pause() |
|---|
| 547 |
|
|---|
| 548 |
def open(*args): |
|---|
| 549 |
player = current_player() |
|---|
| 550 |
player.open() |
|---|
| 551 |
|
|---|
| 552 |
def eject(*args): |
|---|
| 553 |
player = current_player() |
|---|
| 554 |
player.eject() |
|---|
| 555 |
|
|---|
| 556 |
def _make_num(numstr): |
|---|
| 557 |
try: return int(numstr) |
|---|
| 558 |
except: |
|---|
| 559 |
print_error('"%s" must be a number.' % numstr) |
|---|
| 560 |
return False |
|---|
| 561 |
|
|---|
| 562 |
def next(argv): |
|---|
| 563 |
num = 1 |
|---|
| 564 |
if len(argv) == 3: |
|---|
| 565 |
num = _make_num(argv[2]) |
|---|
| 566 |
if not num: return |
|---|
| 567 |
player = current_player() |
|---|
| 568 |
if player.name in ['amarok']: |
|---|
| 569 |
player.next_n(num) |
|---|
| 570 |
else: |
|---|
| 571 |
for i in range(num): |
|---|
| 572 |
player.next() |
|---|
| 573 |
|
|---|
| 574 |
def prev(argv): |
|---|
| 575 |
num = 1 |
|---|
| 576 |
if len(argv) == 3: |
|---|
| 577 |
num = _make_num(argv[2]) |
|---|
| 578 |
if not num: return |
|---|
| 579 |
player = current_player() |
|---|
| 580 |
if player.name in ['amarok']: |
|---|
| 581 |
player.prev_n(num) |
|---|
| 582 |
else: |
|---|
| 583 |
for i in range(num): |
|---|
| 584 |
player.prev() |
|---|
| 585 |
|
|---|
| 586 |
def dispatch(argv, arg_to_eol, c): |
|---|
| 587 |
if len(argv) == 1: |
|---|
| 588 |
try: announce() |
|---|
| 589 |
except Exception, ex: |
|---|
| 590 |
if __debugging__: traceback.print_exc(sys.stdout) |
|---|
| 591 |
if len(getattr(ex, 'args', [])): print_info(ex.args[0]) |
|---|
| 592 |
else: usage() |
|---|
| 593 |
return xchat.EAT_XCHAT |
|---|
| 594 |
try: |
|---|
| 595 |
{ |
|---|
| 596 |
"help" : help, |
|---|
| 597 |
"stop" : stop, |
|---|
| 598 |
"play" : play, |
|---|
| 599 |
"pause" : pause, |
|---|
| 600 |
"next" : next, |
|---|
| 601 |
"prev" : prev, |
|---|
| 602 |
"eject" : eject, |
|---|
| 603 |
"open" : open, |
|---|
| 604 |
}[argv[1]](argv) |
|---|
| 605 |
except Exception, ex: |
|---|
| 606 |
if __debugging__: traceback.print_exc(sys.stdout) |
|---|
| 607 |
if len(getattr(ex, 'args', [])): print_info(ex.args[0]) |
|---|
| 608 |
else: usage() |
|---|
| 609 |
return xchat.EAT_XCHAT |
|---|
| 610 |
|
|---|
| 611 |
__unhook__ = xchat.hook_command("mp3", dispatch, help="/mp3 help for commands.") |
|---|