Advertisement
irvine

XChat mp3 plugin

Sep 12th, 2011
219
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 34.74 KB | None | 0 0
  1. #!/usr/bin/env python
  2. #
  3. # this module uses code from Michael Hudson's xmms-py modules
  4. # this code is available in its original form here:
  5. #   http://www.python.net/crew/mwh/hacks/xmms-py.html
  6. # the original code had this notice on it:
  7. #
  8. # Released by Michael Hudson on 2000-07-01, and again on 2001-04-26
  9. # public domain; no warranty, no restrictions
  10. #
  11. # most of the support for xmms, beep, and audacious comes from
  12. # various pieces of Hudson's modules
  13. #
  14. # licensed under the GNU GPL v2
  15. # a copy of this license can be found:
  16. #   http://www.gnu.org/copyleft/gpl.html
  17. #
  18. # Future Plan:
  19. #   - allow for customizeable announce string
  20. #   - more mp3 players (any requests?)
  21. #
  22. # Note:  Around 11/28/07, I stopped using X-chat as my main IRC client. This
  23. # decision was entirely due to a usable command line IRC client with python
  24. # scripting becoming available (weechat).  I continue to maintain this script
  25. # and add to it as I update the weechat version (the differences between the
  26. # two are so slight I could probably make them one script), but as a warning,
  27. # this module does not receive nearly as heavy testing.
  28. #
  29. # 11/26/07 - Fixed a bug w/ using juk/amarok w/o pydcop
  30. # 11/28/07 - Started weechat port
  31. # 07/13/08 - Changed Audacious support to Dbus, added BMPx (v 0.6)
  32. # 01/27/09 - Added Amarok2 support, Menu items. (v 0.7)
  33. #            Many thanks to Nikolas La Belle for the patch for these features.
  34. # 10/12/11 - Added Guadayeque support (I might have changed nonfuncional Rhythmbox behaviour before) (by irvine)
  35.  
  36.  
  37. ENABLE_MENU = False
  38.  
  39. class Xchat(object):
  40.     """Fake xchat object.  For testing via command line."""
  41.     def __nonzero__(self):
  42.         """>>> bool(Xchat()) == False"""
  43.         return False
  44.     def prnt(self, s):
  45.         s = s.replace('\00302', '')
  46.         s = s.replace('\00303', '')
  47.         s = s.replace('\002', '')
  48.         s = s.replace('\003', '')
  49.         print s
  50.     command=prnt
  51.     def __getattr__(self, name):
  52.         def lambda_(*x, **y):
  53.             print '%s: %s, %s' % (name, x, y)
  54.         return lambda_
  55.  
  56. try: import xchat
  57.  
  58. except ImportError: xchat = Xchat()
  59. import sys, struct
  60. import socket, os, pwd
  61. from subprocess import *
  62.  
  63. pcop, pydcop, bus = False, False, False
  64. try:
  65.     import pcop, pydcop
  66. except: pass
  67. try:
  68.     import dbus
  69.     bus = dbus.SessionBus()
  70. except:
  71.     dbus = False
  72.  
  73. __module_name__ = "pymp3"
  74. __module_version__ = "0.7"
  75. __module_description__ = "mp3 announce/utils"
  76.  
  77. __debugging__ = False
  78.  
  79. if __debugging__:
  80.     import traceback
  81.  
  82. # MENU ITEMS
  83. # Courtesy of Nikolas La Belle
  84. if ENABLE_MENU:
  85.     xchat.command('MENU DEL pymp')
  86.     xchat.command('MENU -p6 ADD pymp')
  87.     xchat.command('MENU ADD \"pymp/Announce\" \"mp3\"')
  88.     xchat.command('MENU ADD \"pymp/-\"')
  89.     xchat.command('MENU ADD \"pymp/Play\" \"mp3 play\"')
  90.     xchat.command('MENU ADD \"pymp/Stop\" \"mp3 stop\"')
  91.     xchat.command('MENU ADD \"pymp/Pause\" \"mp3 pause\"')
  92.     xchat.command('MENU ADD \"pymp/Next\" \"mp3 next\"')
  93.     xchat.command('MENU ADD \"pymp/Prev\" \"mp3 prev\"')
  94.  
  95. def prnt(s): print s
  96.  
  97. def print_debug(string):
  98.     global __debugging__
  99.     if __debugging__:
  100.         string = str(string)
  101.         print "\00302" + string + "\003"
  102.  
  103. def print_info(string):
  104.     string = str(string)
  105.     print "\00303" + string + "\003"
  106.  
  107. if not xchat:
  108.     prnt = xchat.prnt
  109.     print_debug = xchat.prnt
  110.     print_info = xchat.prnt
  111.  
  112. # XXX: beep was superceded by BMPx, BMPx is in the process of being replaced
  113. # by MPX (from the same developers).  Hopefully, MPRIS will at least bring
  114. # some stability to the IPC/RPC interface :)
  115.  
  116. players = {
  117.     'audacious'  : 'audacious',
  118.     'bmpx'       : 'beep-media-player-2',
  119.     'beep'       : 'beep-media-player',
  120. #    'xmms2'      : 'xmms2d',
  121.     'xmms'       : 'xmms',
  122.     'banshee'    : 'Banshee.exe',
  123.     'banshee1'   : 'banshee-1',
  124.     'juk'        : 'juk',
  125.     'amarok'     : 'amarokapp',
  126.     'amarok2'    : 'amarok',
  127.     'rhythmbox'  : 'rhythmbox',
  128.     'guayadeque' : 'guayadeque',
  129. }
  130.  
  131. _player_order = ['audacious', 'amarok2', 'bmpx', 'beep', 'xmms',
  132.     'banshee1', 'banshee', 'juk', 'amarok', 'rhythmbox', 'guayadeque']
  133.  
  134. # find out which player is running
  135. def which():
  136.     ps = Popen(['ps', 'aux'], stdout=PIPE)
  137.     output = ps.stdout.readlines()
  138.     for line in output:
  139.         for player in _player_order:
  140.             findstr = players[player]
  141.             if line.rfind(findstr) > -1:
  142.                 return player
  143.     return
  144.  
  145. #FIXME: This code isn't that great; it should probably not rely on 'split' since
  146. # quoted won't work properly.  Think of a way to fix this (maybe resort to shell=True)
  147. def command(runstr):
  148.     return Popen(runstr.split(), stdout=PIPE).communicate()[0]
  149.  
  150. # these players use xmms style command socket
  151. SOCKET_PLAYERS = ['audacious', 'beep', 'xmms']
  152.  
  153. class SocketCommand:
  154.     CMD_PLAY = 2                #
  155.     CMD_PAUSE = 3               #
  156.     CMD_STOP = 4                #
  157.     CMD_GET_PLAYLIST_POS = 7    #
  158.     # TODO: make socket_next and socket_prev use this
  159.     # instead of using next/prev repeatedly
  160.     #CMD_SET_PLAYLIST_POS = 8    #
  161.     CMD_GET_PLAYLIST_LENGTH = 9 #
  162.     CMD_GET_OUTPUT_TIME = 11    #
  163.     CMD_GET_PLAYLIST_FILE = 17  #
  164.     CMD_GET_PLAYLIST_TITLE = 18 #
  165.     CMD_GET_PLAYLIST_TIME = 19  #
  166.     CMD_GET_INFO = 20           #
  167.     CMD_EJECT = 28              #
  168.     CMD_PLAYLIST_PREV = 29      #
  169.     CMD_PLAYLIST_NEXT = 30      #
  170.     CMD_TOGGLE_REPEAT = 33      #
  171.     CMD_TOGGLE_SHUFFLE = 34     #
  172.  
  173. """
  174. I've tried to make the following class a facsimily of a "persistent connection",
  175. but my attempts have led to the following error with xmms:
  176.    ** WARNING **: ctrl_write_packet(): Failed to send data: Broken pipe
  177. Even manually closing, deleting, and then re-initializing the socket did not avoid
  178. this warning.  It seems that only letting the garbage collector snag old Connection
  179. objects makes xmms happy.
  180.  
  181. There is one aspect here missing from Hudson's original library: sending a custom
  182. send format with the 'args' option.  I wasn't using this feature in any requests,
  183. as all of my provided formats were 'l' anyway.
  184. """
  185. class XmmsConnection:
  186.     class ClientPacketHeader:
  187.         def __init__(self):
  188.             self.version,self.cmd,self.length = 0,0,0
  189.         def __repr__(self):
  190.             return "<< %s : version: %s cmd: %s length: %s >>"\
  191.                 %(self.__class__.__name__,self.version,self.cmd,self.length)
  192.         def encode(self):
  193.             return struct.pack("hhl",self.version,self.cmd,self.length)
  194.  
  195.     def __init__(self,session=0):
  196.         self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
  197.         self.sock.connect("/tmp/xmms_%s.%d"%(pwd.getpwuid(os.geteuid())[0],session))
  198.  
  199.     def read_header(self):
  200.         head = self.ClientPacketHeader()
  201.         head.version, head.cmd, head.length = struct.unpack("hhl",self.sock.recv(8))
  202.         return head
  203.  
  204.     def send(self, cmd, args=''):
  205.         data = ""
  206.         if isinstance(args, int):
  207.             data = struct.pack('l', args)
  208.         packet = struct.pack("hhl", 1, cmd, len(data)) + data
  209.         self.sock.send(packet)
  210.  
  211.     def get_reply(self, format=''):
  212.         header = self.read_header()
  213.         if format: reply = struct.unpack(format, self.sock.recv(header.length))
  214.         else: reply = self.sock.recv(header.length)
  215.         return reply
  216.  
  217. # general utilities ---
  218.  
  219. def human_bitrate(bps):
  220.     """Takes bits per second and returns a string w/ appropriate units."""
  221.     units = ['bps', 'kbps', 'Mbps']
  222.     # order of magnitude
  223.     # if we get a weird number, assume kbps = kiloBYTESpersec
  224.     # if we get a number ending in '00', assume it's 1000's of bits (correct)
  225.     if str(bps).endswith("00"):
  226.         reduce_factor = 1000
  227.     else:
  228.         reduce_factor = 1024.0
  229.     oom = 0
  230.     while bps /(reduce_factor**(oom+1)) >= 1:
  231.         oom += 1
  232.     return '%0.1f %s' % (bps/reduce_factor**oom, units[oom])
  233.  
  234. def s_to_mmss(s):
  235.     """Converts seconds to minutes:seconds: mm:ss."""
  236.     s = int(s)
  237.     sec = s % 60
  238.     min = s / 60
  239.     return '%2d:%02d' % (min, sec)
  240.  
  241. def us_to_mmss(us):
  242.     """Converts miliseconds to minutes:seconds:  mm:ss."""
  243.     us = int(us)
  244.     return s_to_mmss(us/1000)
  245.  
  246. class MediaPlayer:
  247.     """A superclass that implements some book-keeping and some convenience functions
  248.    for media player objects.  These objects print out as an announce string, and
  249.    get (and cache) info from the player in a clean, consistent "getitem" API."""
  250.     def __init__(self, name):
  251.         self.name = name
  252.  
  253.     def _nyi(self, name):
  254.         print_debug("%s not yet implemented for `%s`." % (name, self.name))
  255.  
  256.     def _empty_dict(self):
  257.         """Return an empty info dictionary with all of the keys set."""
  258.         keys = ['player', 'playlist_position', 'playlist_length', 'file',
  259.                 'display_title', 'elapsed', 'length', 'bitrate', 'frequency',
  260.                 'title', 'artist', 'album', 'track']
  261.         d = {}
  262.         for key in keys: d[key] = ''
  263.         d['player'] = self.name
  264.         return d
  265.  
  266.     def play(self): self._nyi('Play')
  267.     def stop(self): self._nyi('Stop')
  268.     def pause(self): self._nyi('Pause')
  269.     def next(self): self._nyi('Next')
  270.     def prev(self): self._nyi('Prev')
  271.     def eject(self): self._nyi('eject')
  272.     def open(self): self._nyi('open')
  273.     def shuffle(self): self._nyi('shuffle')
  274.     def repeat(self): self._nyi('repeat')
  275.  
  276.     def next_n(self, n):
  277.         """Skip "forward" `n` songs.  Should obey the player's current
  278.        repeat settings, and preferably shuffle.  Overwrite if player has
  279.        Playlist position setting."""
  280.         for i in range(n):
  281.             self.next()
  282.  
  283.     def prev_n(self, n):
  284.         """Go "backward" `n` songs.  Should obey the player's current
  285.        repeat settings, and preferably shuffle.  Overwrite if player has
  286.        Playlist position setting."""
  287.         for i in range(n):
  288.             self.prev()
  289.  
  290.     def get_info(self): return self._empty_dict()
  291.  
  292.     def __str__(self):
  293.         # FIXME: this should basically be done away with.
  294.         """FIXME: This implements the old announce strings.  It's probably easier
  295.        to move this to the subclasses, but for now this is fine."""
  296.         info = self.get_info()
  297.         if self.name in SOCKET_PLAYERS:
  298.             return '%s ~ [%s] of [%s] ~ %s ~ %sHz' % (info['display_title'], \
  299.                 info['elapsed'], info['length'], info['bitrate'], info['frequency'])
  300.  
  301.         elif self.name in ['juk', 'amarok']:
  302.             return '%s - [%s] - %s ~ [%s] of [%s] ~ %s' % (info['artist'], \
  303.                 info['album'], info['title'], info['elapsed'], info['length'], \
  304.                 info['bitrate'])
  305.  
  306.         elif self.name in ['banshee', 'rhythmbox']:
  307.             return '%s - [%s] - %s ~ [%s] of [%s]' % (info['artist'], info['album'], \
  308.                 info['title'], info['elapsed'], info['length'])
  309.  
  310.     def __repr__(self):
  311.         return '<MediaPlayer %s ...>' % (self.name)
  312.  
  313. class Xmms(MediaPlayer):
  314.     def __init__(self, name='xmms'):
  315.         MediaPlayer.__init__(self, name)
  316.         self._ifcache = {}
  317.  
  318.     def _makeConnection(self):
  319.         if self.name in ['beep', 'xmms']: return XmmsConnection()
  320.         return False
  321.  
  322.     def _cmd(self, command, args='', reply_format=''):
  323.         connection = self._makeConnection()
  324.         connection.send(command, args=args)
  325.         return connection.get_reply(format=reply_format)
  326.  
  327.     def play(self):     self._cmd(SocketCommand.CMD_PLAY)
  328.     def stop(self):     self._cmd(SocketCommand.CMD_STOP)
  329.     def pause(self):    self._cmd(SocketCommand.CMD_PAUSE)
  330.     def next(self):     self._cmd(SocketCommand.CMD_PLAYLIST_NEXT)
  331.     def prev(self):     self._cmd(SocketCommand.CMD_PLAYLIST_PREV)
  332.     def eject(self):    self._cmd(SocketCommand.CMD_EJECT)
  333.     def open(self):     self._cmd(SocketCommand.CMD_EJECT)
  334.     def shuffle(self):  self._cmd(SocketCommand.CMD_TOGGLE_SHUFFLE)
  335.     def repeat(self):   self._cmd(SocketCommand.CMD_TOGGLE_REPEAT)
  336.  
  337.     def get_info(self):
  338.         d = self._empty_dict()
  339.         d['playlist_position'] = self._cmd(SocketCommand.CMD_GET_PLAYLIST_POS, reply_format='l')[0]
  340.         position = d['playlist_position']
  341.         d['playlist_length'] = self._cmd(SocketCommand.CMD_GET_PLAYLIST_LENGTH, reply_format='l')[0]
  342.         d['file']  = self._cmd(SocketCommand.CMD_GET_PLAYLIST_FILE, args=position)[:-1]
  343.         d['display_title'] = self._cmd(SocketCommand.CMD_GET_PLAYLIST_TITLE, args=position)[:-1]
  344.         info = self._cmd(SocketCommand.CMD_GET_INFO, reply_format='lll')
  345.         utime_elapsed = self._cmd(SocketCommand.CMD_GET_OUTPUT_TIME, reply_format='l')[0]
  346.         utime_length  = self._cmd(SocketCommand.CMD_GET_PLAYLIST_TIME, args=position, reply_format='l')[0]
  347.         d['elapsed'] = us_to_mmss(utime_elapsed)
  348.         d['length'] = us_to_mmss(utime_length)
  349.         d['bitrate'] = human_bitrate(info[0])
  350.         d['frequency'] = info[1]
  351.         return d
  352.  
  353. BEEP_FIRST_RUN = True
  354. BEEP_MESSAGE = """beep-media-player has a bug with its control socket and returns
  355. bogus information for bitrate, frequency, and number of channels.  Consider the
  356. 'audacious' media player, or BMPx, as beep-media-player is no longer in
  357. development.""".replace("\n", ' ')
  358.  
  359. class Beep(Xmms):
  360.     def __init__(self):
  361.         global BEEP_FIRST_RUN, BEEP_MESSAGE
  362.         if BEEP_FIRST_RUN:
  363.             print_info(BEEP_MESSAGE)
  364.             BEEP_FIRST_RUN = False
  365.         Xmms.__init__(self, 'beep')
  366.  
  367. BMPX_FIRST_RUN = True
  368. BMPX_4013_WARNING = """You are running bmpx version "%s", which has known \
  369. bugs in the dbus interface.  "BMP 0.40.14" fixes some of these, but pause \
  370. support is still known to be broken in this release."""
  371. BMPX_FORMAT = """%(artist)s - [%(album)s] - %(title)s ~ [%(length)s] \
  372. ~ %(kbps)s ~ %(freq)sHz"""
  373.  
  374. class Bmpx(MediaPlayer):
  375.     def __init__(self, name="bmpx"):
  376.         global BMPX_FIRST_RUN
  377.         if not bus:
  378.             return
  379.         MediaPlayer.__init__(self, name)
  380.         self.Root = bus.get_object('org.mpris.bmp', '/')
  381.         self.Player = bus.get_object('org.mpris.bmp', '/Player')
  382.         self.TrackList = bus.get_object('org.mpris.bmp', '/TrackList')
  383.         if BMPX_FIRST_RUN:
  384.             BMPX_FIRST_RUN = False
  385.             self.version = str(self.Root.Identity())
  386.             if self.version < 'BMP 0.40.14':
  387.                 print BMPX_4013_WARNING % self.version
  388.  
  389.  
  390.     def play(self):
  391.         if self.version < 'BMP 0.40.14':
  392.             print_info("playing does not work with version \"%s\" of BMPx" % self.version)
  393.             return
  394.         self.Player.Play()
  395.  
  396.     def stop(self):
  397.         if self.version < 'BMP 0.40.14':
  398.             print_info("stop disabled for this version of BMPx, since playing does not work.")
  399.             return
  400.         self.Player.Stop()
  401.  
  402.     def pause(self):
  403.         if self.version < 'BMP 0.40.15':
  404.             print_info("pausing does not work with version \"%s\" of BMPx" % self.version)
  405.             return
  406.         self.Player.Pause()
  407.  
  408.     def next(self): self.Player.Next()
  409.     def prev(self): self.Player.Prev()
  410.     # are these necessary?  maybe they should be removed
  411.     def eject(self): pass
  412.     def open(self): pass
  413.  
  414.     def get_info(self):
  415.         info = self.Player.GetMetadata()
  416.         decode = lambda x: unicode(x).encode('utf-8')
  417.         return {
  418.             'artist' : decode(info['artist']),
  419.             'album'  : decode(info['album']),
  420.             'title'  : decode(info['title']),
  421.             'length' : s_to_mmss(int(info['time'])),
  422.             'kbps'   : human_bitrate(int(info['bitrate'])),
  423.             'freq'   : decode(info['samplerate']),
  424.         }
  425.  
  426.     def __str__(self):
  427.         info = self.get_info()
  428.         return BMPX_FORMAT % info
  429.  
  430.  
  431. AUDACIOUS_FIRST_RUN = True
  432. AUDACIOUS_NODBUS = """Audacious deprecated the control socket interface many \
  433. releases ago, and as of the release included with Ubuntu 8.04, it's officially \
  434. gone.  For now, the python dbus bindings are required for Audacious usage until \
  435. a suitable interface using 'dbus-send' can be developed."""
  436.  
  437. AUDACIOUS_FORMAT = """%(artist)s - [%(album)s] - %(title)s ~ [%(elapsed)s] \
  438. of [%(length)s] ~ %(kbps)s ~ %(freq)sHz"""
  439.  
  440. class Audacious(MediaPlayer):
  441.     format = AUDACIOUS_FORMAT
  442.     def __init__(self, name="audacious"):
  443.         MediaPlayer.__init__(self, name)
  444.         global AUDACIOUS_FIRST_RUN
  445.         if not bus and AUDACIOUS_FIRST_RUN:
  446.             print_info(AUDACIOUS_NODBUS)
  447.             AUDACIOUS_FIRST_RUN = False
  448.             return
  449.         AUDACIOUS_FIRST_RUN = False
  450.         self.bus = bus
  451.         # set up the mpris interfaces
  452.         self.Root   = bus.get_object('org.mpris.audacious', '/')
  453.         self.Player = bus.get_object('org.mpris.audacious', '/Player')
  454.         self.TrackList = bus.get_object('org.mpris.audacious', '/TrackList')
  455.         # XXX: this interface is going away in Audacious 2.0 as per nenolod
  456.         self.Atheme = bus.get_object('org.atheme.audacious', '/org/atheme/audacious')
  457.  
  458.     def play(self): self.Player.Play()
  459.     def stop(self): self.Player.Stop()
  460.     def pause(self): self.Player.Pause()
  461.     def next(self): self.Player.Next()
  462.     def prev(self): self.Player.Prev()
  463.     # are these necessary?  maybe they should be removed
  464.     def eject(self): self.Atheme.Eject()
  465.     def open(self): self.Atheme.Eject()
  466.  
  467.     def __str__(self):
  468.         info = self.get_info()
  469.         return self.format % info
  470.  
  471.     def get_info(self):
  472.         kbps, freq, ch = map(int, self.Atheme.GetInfo())
  473.         info_dict = self.Player.GetMetadata()
  474.         return {
  475.             'kbps'     : human_bitrate(kbps),
  476.             'freq'     : freq,
  477.             'channels' : ch,
  478.             'artist'   : unicode(info_dict['artist']).encode('utf-8'),
  479.             'album'    : unicode(info_dict['album']).encode('utf-8'),
  480.             'title'    : unicode(info_dict['title']).encode('utf-8'),
  481.             'elapsed'  : us_to_mmss(self.Player.PositionGet()),
  482.             'length'   : us_to_mmss(info_dict['length']),
  483.         }
  484.  
  485. BANSHEE_FIRST_RUN = True
  486. BANSHEE_MESSAGE = """Although banshee is supported without them, it is recommended
  487. that you install the python-dbus bindings for increased speed.""".replace("\n", " ")
  488.  
  489. class Banshee(MediaPlayer):
  490.     def __init__(self):
  491.         global BANSHEE_FIRST_RUN, BANSHEE_MESSAGE
  492.         if BANSHEE_FIRST_RUN and not bus:
  493.             print_info(BANSHEE_MESSAGE)
  494.             BANSHEE_FIRST_RUN = False
  495.         MediaPlayer.__init__(self, 'banshee')
  496.         self._ifcache = {}
  497.         interface = ['play', 'stop', 'pause', 'next', 'prev', 'eject', 'open', 'get_info']
  498.         if bus:
  499.             self.d_obj = bus.get_object("org.bansheeproject.Banshee", "/org/bansheeproject/Banshee/PlayerEngine")
  500.             self.banshee = dbus.Interface(self.d_obj, "org.bansheeproject.Banshee.PlayerEngine")
  501.             for func in interface:
  502.                 setattr(self, func, getattr(self, '%s_dbus' % func))
  503.         else:
  504.             for func in interface:
  505.                 setattr(self, func, getattr(self, '%s_nodbus' % func))
  506.  
  507.     def play_dbus(self): self.banshee.Play()
  508.     def stop_dbus(self): self.banshee.Pause()
  509.     def pause_dbus(self): self.banshee.TogglePlaying()
  510.     def next_dbus(self): self.banshee.Next()
  511.     def prev_dbus(self): self.banshee.Previous()
  512.     def eject_dbus(self): self.banshee.ShowWindow()
  513.     def open_dbus(self): self.banshee.ShowWindow()
  514.  
  515.     def get_info_dbus(self):
  516.         d = self._empty_dict()
  517.         currentTrack = self.banshee.GetCurrentTrack()
  518.         d['length'] = us_to_mmss(self.banshee.GetLength())
  519.         d['elapsed'] = us_to_mmss(self.banshee.GetPosition())
  520.         d['artist'] = str(currentTrack[u'album-artist'])
  521.         d['title'] = str(currentTrack[u'name'])
  522.         d['album'] = str(currentTrack[u'album'])
  523.         return d
  524.  
  525.     def play_nodbus(self):   command('banshee --play')
  526.     def stop_nodbus(self):   command('banshee --pause')
  527.     def pause_nodbus(self):  command('banshee --toggle-playing')
  528.     def next_nodbus(self):   command('banshee --next')
  529.     def prev_nodbus(self):   command('banshee --previous')
  530.     def eject_nodbus(self):  command('banshee --show')
  531.     def open_nodbus(self):   command('banshee --show')
  532.     # shuffle & repeat not yet implemented
  533.  
  534.     def get_info_nodbus(self):
  535.         d = self._empty_dict()
  536.         info = command(' '.join(['banshee', '--hide-field', '--query-title',
  537.                                  '--query-artist', '--query-position', '--query-album',
  538.                                  '--query-duration'])).strip()
  539.         # duration, artist, album, title, position
  540.         # banshee reports things in seconds
  541.         info = info.split('\n')
  542.         d['length'] = us_to_mmss(info[0])
  543.         d['artist'] = info[1]
  544.         d['album']  = info[2]
  545.         d['title']  = info[3]
  546.         d['elapsed'] = us_to_mmss(info[4])
  547.         return d
  548.  
  549. class Rhythmbox(MediaPlayer):
  550.     """MediaPlayer class for Rhythmbox, a Gtk/Gnome media player."""
  551.     def __init__(self):
  552.         if not bus:
  553.             raise Exception('Rhythmbox is not supported w/o python-dbus bindings.')
  554.         MediaPlayer.__init__(self, 'rhythmbox')
  555.         player_obj = bus.get_object("org.gnome.Rhythmbox", "/org/gnome/Rhythmbox/Player")
  556.         shell_obj  = bus.get_object("org.gnome.Rhythmbox", "/org/gnome/Rhythmbox/Shell")
  557.         self.player = dbus.Interface(player_obj, "org.gnome.Rhythmbox.Player")
  558.         self.shell  = dbus.Interface(shell_obj,  "org.gnome.Rhythmbox.Shell")
  559.  
  560.     def play(self):
  561.         if not bool(self.player.getPlaying()): self.player.playPause()
  562.     def stop(self):
  563.         if bool(self.player.getPlaying()): self.player.playPause()
  564.     def pause(self): self.player.playPause()
  565.     def next(self): self.player.next()
  566.     def prev(self): self.player.previous()
  567.     def eject(self): print_info("There isn't an easy way to do this in rhythmbox right now.")
  568.     def open(self): print_info("There isn't an easy way to do this in rhythmbox right now.")
  569.  
  570.     def get_info(self):
  571.         d = self._empty_dict()
  572.         uri = unicode(self.player.getPlayingUri())
  573.         properties = dict([(unicode(key), val) for key,val in dict(self.shell.getSongProperties(uri)).items()])
  574.         d['length'] = s_to_mmss(int(properties.get('duration', 0)))
  575.         d['elapsed'] = s_to_mmss(int(self.player.getElapsed()))
  576.         d['artist'] = unicode(properties.get('artist', '')).encode('UTF-8')
  577.         d['album']  = unicode(properties.get('album', '')).encode('UTF-8')
  578.         d['title']  = unicode(properties.get('title', '')).encode('UTF-8')
  579.         # Rhythmbox reports a 'bitrate', but as far as i can tell it's always 0
  580.         return d
  581.  
  582. JUK_FIRST_RUN = True
  583. DCOP_MESSAGE = """Although juk is supported without them, it is recommended that
  584. you install the python-dcop bindings for increased speed.""".replace("\n", ' ')
  585.  
  586. class Juk(MediaPlayer):
  587.     """MediaPlayer class for Juk, a Qt/KDE media player.  This implementation is
  588.    a bit messy because it resolves whether or not to use DCOP statically;  after
  589.    importing, the comparissons are made and the appropriate functions are used."""
  590.     def __init__(self):
  591.         global JUK_FIRST_RUN, DCOP_MESSAGE, pydcop
  592.         if JUK_FIRST_RUN and not pydcop:
  593.             print_info(DCOP_MESSAGE)
  594.         JUK_FIRST_RUN = False
  595.         MediaPlayer.__init__(self, 'juk')
  596.         self._ifcache = {}
  597.         # these functions are to be selected from _%s_dcop and #s_nodcop
  598.         self._functions = ['eject', 'open']
  599.         # these functions are created below; the keys are function names, the values
  600.         # are juk PLayer dcop values
  601.         self._func_map = {'play':'play', 'stop':'stop', 'pause':'playPause', 'next':'forward', 'prev':'back'}
  602.         if pydcop:
  603.             # if we have pydcop, create 'juk' and set some functions
  604.             self.juk = pydcop.anyAppCalled("juk")
  605.             self.get_property = (lambda x: self.juk.Player.trackProperty(x))
  606.             self.get_juk = (lambda func: getattr(self.juk.Player, func)())
  607.             for func in self._functions:
  608.                 setattr(self, func, getattr(self, '_%s_dcop' % func))
  609.         else:
  610.             # with no dcop, set equivalent functions to above using 'command' interface
  611.             self.get_property = (lambda x: command('dcop juk Player trackProperty %s' % (x)).strip())
  612.             self.get_juk = (lambda func: command('dcop juk Player %s' % func))
  613.             for func in self._functions:
  614.                 setattr(self, func, getattr(self, '_%s_nodcop' % func))
  615.         # this forloop sets all of the keys in 'func_map' to lambdas that call
  616.         # whatever 'get_juk' was created by the conditional above
  617.         for funcname, juk_property in self._func_map.items():
  618.             setattr(self, funcname, (lambda prop=juk_property: self.get_juk(prop)))
  619.  
  620.     def _eject_dcop(self):
  621.         pcop.dcop_call("juk", "juk-mainwindow#1", "restore", ())
  622.         pcop.dcop_call("juk", "juk-mainwindow#1", "raise", ())
  623.     def _open_dcop(self): self._eject_dcop()
  624.  
  625.     def _eject_nodcop(self):
  626.         command('dcop juk juk-mainwindow#1 restore')
  627.         command('dcop juk juk-mainwindow#1 raise')
  628.     def _open_nodcop(self): self._eject_nodcop()
  629.  
  630.     def get_info(self):
  631.         d = self._empty_dict()
  632.         elapsed = self.get_juk('currentTime')
  633.         d['elapsed'] = s_to_mmss(elapsed)
  634.         d['title'] = self.get_property('Title')
  635.         d['artist'] = self.get_property('Artist')
  636.         d['album'] = self.get_property('Album')
  637.         d['length'] = s_to_mmss(self.get_property('Seconds'))
  638.         d['bitrate'] = '%s Kbps' % self.get_property('Bitrate')
  639.         return d
  640.  
  641. AMAROK2_FIRST_RUN = True
  642. AMAROK2_FORMAT = """%(artist)s - [%(album)s] - %(title)s ~ [%(elapsed)s] \
  643. of [%(length)s] ~ %(kbps)skbps ~ %(freq)sHz"""
  644. AMAROK2_NODBUS = """Amarok 2 uses dbus hooks, python dbus bindings are \
  645. required for Amarok 2 usage until a suitable interface using 'dbus-send' \
  646. can be developed."""
  647.  
  648. class Amarok2(MediaPlayer):
  649.     format = AMAROK2_FORMAT
  650.     def __init__(self, name="amarok2"):
  651.         MediaPlayer.__init__(self, name)
  652.         global AMAROK2_FIRST_RUN
  653.         if not bus and AMAROK2_FIRST_RUN:
  654.             print_info(AMAROK2_NODBUS)
  655.             AMAROK2_FIRST_RUN = False
  656.             return
  657.         AMAROK2_FIRST_RUN = False
  658.         self.bus = bus
  659.         # set up the mpris interfaces
  660.         self.Root = bus.get_object("org.kde.amarok", "/")
  661.         self.Player = bus.get_object("org.kde.amarok", "/Player")
  662.         self.TrackList = bus.get_object("org.kde.amarok", "/TrackList")
  663.  
  664.     #DBUS COMMANDS
  665.     def play(self): self.Player.Play()
  666.     def stop(self): self.Player.Stop()
  667.     def pause(self): self.Player.Pause()
  668.     def next(self): self.Player.Next()
  669.     def prev(self): self.Player.Prev()
  670.  
  671.     def __str__(self):
  672.         info = self.get_info()
  673.         return self.format % info
  674.  
  675.     def get_info(self):
  676.         info_dict = self.Player.GetMetadata()
  677.         return {
  678.             'kbps'     : unicode(info_dict['audio-bitrate']).encode('utf-8'),
  679.             'freq'     : unicode(info_dict['audio-samplerate']).encode('utf-8'),
  680.             'artist'   : unicode(info_dict['artist']).encode('utf-8'),
  681.             'album'    : unicode(info_dict['album']).encode('utf-8'),
  682.             'title'    : unicode(info_dict['title']).encode('utf-8'),
  683.             'elapsed'  : us_to_mmss(self.Player.PositionGet()),
  684.             'length'   : us_to_mmss(info_dict['mtime']),
  685.         }
  686.  
  687.  
  688. AMAROK_FIRST_RUN = True
  689. AMAROK_DCOP_MESSAGE = """Although amarok is supported without them, it is recommended that
  690. you install the python-dcop bindings for increased speed.""".replace("\n", ' ')
  691.  
  692. class Amarok(MediaPlayer):
  693.     """MediaPlayer class for Amarok, a Qt/KDE media player.  This implementation is
  694.    a bit messy because it resolves whether or not to use DCOP statically;  after
  695.    importing, the comparissons are made and the appropriate functions are used."""
  696.     def __init__(self):
  697.         global AMAROK_FIRST_RUN, AMAROK_DCOP_MESSAGE, pydcop
  698.         if AMAROK_FIRST_RUN and not pydcop:
  699.             print_info(AMAROK_DCOP_MESSAGE)
  700.         AMAROK_FIRST_RUN = False
  701.         MediaPlayer.__init__(self, 'amarok')
  702.         self._ifcache = {}
  703.         """If the pydcop is available, then we create a 'self.get_property' function
  704.        that uses pydcop; if it isn't available, we create a function that works the same
  705.        but using our 'command' interface.  Then, using the 'self.get_property', we bind
  706.        'self.play', 'self.stop', etc. to the object's namespace."""
  707.         self._functions = ['play', 'stop', 'pause']
  708.         if pydcop:
  709.             self.amarok = pydcop.anyAppCalled("amarok")
  710.             self.get_property = (lambda x: getattr(self.amarok.player, x)())
  711.             self.get_playlist = (lambda x: getattr(self.amarok.playlist, x)())
  712.             self.set_playlist = (lambda x: self.amarok.playlist.playByIndex(x))
  713.         else:
  714.             self.get_property = (lambda x: command('dcop amarok player %s' % x).strip())
  715.             self.get_playlist = (lambda x: command('dcop amarok playlist %s' % x).strip())
  716.             self.set_playlist = (lambda x: command('dcop amarok playlist playByIndex %s' % x))
  717.         for func in self._functions:
  718.             setattr(self, func, (lambda func=func: self.get_property(func)))
  719.  
  720.     def open(self): print_info("There isn't an easy way to do this with amarok right now.")
  721.     def eject(self): print_info("There isn't an easy way to do this with amarok right now.")
  722.  
  723.     def prev_n(self, n):
  724.         """Go backwards 'n' times in the playlist"""
  725.         position = self.get_playlist('getActiveIndex')
  726.         new_position = position - n
  727.         if new_position < 0: new_position = 0
  728.         self.set_playlist(new_position)
  729.  
  730.     def next_n(self, n):
  731.         """Go forwards 'n' times in the playlist"""
  732.         position = self.get_playlist('getActiveIndex')
  733.         playlist_length = self.get_playlist('getTotalTrackCount')
  734.         new_position = position + n
  735.         if new_position >= playlist_length:
  736.             new_position = playlist_length - 1
  737.         self.set_playlist(new_position)
  738.  
  739.     def get_info(self):
  740.         d = self._empty_dict()
  741.         # this comes back in 'm:ss'
  742.         d['elapsed'] = self.get_property('currentTime')
  743.         d['title'] = self.get_property('title')
  744.         d['artist'] = self.get_property('artist')
  745.         d['album'] = self.get_property('album')
  746.         d['length'] = self.get_property('totalTime')
  747.         d['bitrate'] = '%s Kbps' % self.get_property('bitrate')
  748.         return d
  749.  
  750.  
  751. GDQ_FORMAT = """%(artist)s - [%(album)s] - %(title)s [%(elapsed)s of%(length)s]"""
  752.  
  753. class Guayadeque(MediaPlayer):
  754.     """MediaPlayer class for Guayadeque."""
  755.     def __init__(self):
  756.         if not bus:
  757.             raise Exception('guayadeque is not supported w/o python-dbus bindings.')
  758.         MediaPlayer.__init__(self, 'guayadeque')
  759.         # bus = dbus.SessionBus()
  760.         player_obj = bus.get_object("org.mpris.guayadeque", "/Player")
  761.         shell_obj  = bus.get_object("org.mpris.guayadeque", "/org/mpris/MediaPlayer2")
  762.         self.player = dbus.Interface(player_obj, "org.freedesktop.MediaPlayer")
  763.         self.shell  = dbus.Interface(shell_obj,  "org.freedesktop.MediaPlayer.Shell")
  764.  
  765.     def play(self): self.player.Play()
  766.     def stop(self): self.player.Stop()
  767.     def pause(self): self.player.Pause()
  768.     def next(self): self.player.Next()
  769.     def prev(self): self.player.Prev()
  770.     def eject(self): print_info("There isn't an easy way to do this in guayadeque right now.")
  771.     def open(self): print_info("There isn't an easy way to do this in guayadeque right now.")
  772.  
  773.     def get_info(self):
  774.         info = self.player.GetMetadata()
  775.         elapsed = self.player.PositionGet()
  776.         decode = lambda x: unicode(x).encode('utf-8')
  777.         return {
  778.             'artist' : decode(info['artist']),
  779.             'album'  : decode(info['album']),
  780.             'title'  : decode(info['title']),
  781.             'length' : s_to_mmss(int(info['time'])),
  782.             'elapsed' : us_to_mmss(int(elapsed)),
  783.             # 'kbps'   : human_bitrate(int(info['bitrate'])),
  784.             # 'freq'   : decode(info['samplerate']),
  785.         }
  786.  
  787.     def __str__(self):
  788.         info = self.get_info()
  789.         return GDQ_FORMAT % info
  790.  
  791.  
  792. def current_player():
  793.     player = which()
  794.     print_debug("detected %s is running" % player)
  795.     if player is 'banshee1':
  796.         player = 'banshee'
  797.     if not player:
  798.         raise Exception("Currently not running a supported media player.")
  799.     player_obj = eval("%s()" % player.capitalize())
  800.     return player_obj
  801.  
  802. def help(args):
  803.     prnt("Commands:")
  804.     prnt("   \002/mp3\002          : announce the currently playing mp3")
  805.     prnt("   \002/mp3\002     \00303stop\003 : stop playing")
  806.     prnt("   \002/mp3\002     \00303play\003 : start playing")
  807.     prnt("   \002/mp3\002    \00303pause\003 : pause playback")
  808.     prnt("   \002/mp3\002 \00303next [#]\003 : skip to next (# of) track(s)")
  809.     prnt("   \002/mp3\002 \00303prev [#]\003 : skip to prev (# of) track(s)")
  810.     prnt("   \002/mp3\002     \00303open\003 : open files")
  811.     prnt("")
  812.  
  813. def usage():
  814.     prnt("Usage: \002/mp3\002 [cmd]")
  815.     prnt("\t\002/mp3\002 \037help\037 for commands.")
  816.  
  817. def announce():
  818.     xchat.command('me is listening to: %s' % (current_player()))
  819.  
  820. def stop(*args):
  821.     current_player().stop()
  822.  
  823. def play(*args):
  824.     current_player().play()
  825.  
  826. def pause(*args):
  827.     current_player().pause()
  828.  
  829. def open(*args):
  830.     current_player().open()
  831.  
  832. def eject(*args):
  833.     current_player().eject()
  834.  
  835. def _make_num(numstr):
  836.     try: return int(numstr)
  837.     except:
  838.         print_error('"%s" must be a number.' % numstr)
  839.         return None
  840.  
  841. def next(argv):
  842.     num = None
  843.     if len(argv) == 3:
  844.         num = _make_num(argv[2])
  845.         if num is None: return
  846.     if num is None:
  847.         current_player().next()
  848.     else:
  849.         current_player().next_n(num)
  850.  
  851. def prev(argv):
  852.     num = None
  853.     if len(argv) == 3:
  854.         num = _make_num(argv[2])
  855.         if num is None: return
  856.     if num is None:
  857.         current_player().prev()
  858.     else:
  859.         current_player().prev_n(num)
  860.  
  861. def dispatch(argv, arg_to_eol, c):
  862.     if len(argv) == 1:
  863.         try: announce()
  864.         except Exception, ex:
  865.             if __debugging__: print_debug(traceback.format_exc())
  866.             if len(getattr(ex, 'args', [])): print_info(ex.args[0])
  867.             else: usage()
  868.         return xchat.EAT_XCHAT
  869.     try:
  870.         {
  871.         "help"  : help,
  872.         "stop"  : stop,
  873.         "play"  : play,
  874.         "pause" : pause,
  875.         "next"  : next,
  876.         "prev"  : prev,
  877.         "eject" : eject,
  878.         "open"  : open,
  879.     }[argv[1]](argv)
  880.     except Exception, ex:
  881.         if __debugging__: print_debug(traceback.format_exc())
  882.         if len(getattr(ex, 'args', [])): print_info(ex.args[0])
  883.         else: usage()
  884.     return xchat.EAT_XCHAT
  885.  
  886. __unhook__ = xchat.hook_command("mp3", dispatch, help="/mp3 help for commands.")
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement