#!/usr/bin/python # -*- coding: utf-8 -*- # # Copyright (C) 2007 ftrvxmtrx # # 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 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # ############## # # Simple script to display the song currently played in any music player # Changes: # v0.999 - Got rid of boost and tagpy, written small C library that uses taglib # v1.0 - Got rid of lsof, added ability to show player name # v1.1 - Message formatting, optional clipboard copy via dcop (for klipper), # optional message printing to stdout, few bugs fixed, adding players # as a parameter # v1.11 - Added mpd and irssi (run "uninp.py irssi" for help) support # v1.12 - HTML special chars support via "html" arg # "no_me" arg # "no_clipboard"->"clipboard" as requested by bes __module_name__ = "uninp" __module_version__ = "1.11" __module_description__ = "python module to display titles playing in any music player for any IM client" # feel free to add some players here players = ['mpd', 'mpg123', 'amarok', 'cmus', 'mocp', 'audacious', 'alsaplayer', 'mplayer', 'mpg321', 'cat', 'tac'] # default message format format = '"/me is listening to \\"", [artist, artist, "Unknown Artist"], " - ", [album, album + " ", ""], [year, "(" + year + ") - ", "- "], [title, title, "Unknown Track"], "\\"", [time, " (" + time + ")", ""], [genre, " [" + genre + "]", ""], [player, " with " + player, ""]' format_not_playing = '/me is not listening to anything' # mpd connection address and port mpd_connection = ('127.0.0.1', 6600) error = False noxchat = False notag = False import os import stat import traceback import socket from time import strftime, gmtime try: import xchat except ImportError: noxchat = True if __name__ != '__main__': print 'Failed to import xchat module' error = True try: import uninp_tag except ImportError: print traceback.format_exc() print '\"uninp_tag\" import error occured, not all players will be supported' notag = True help_string = 'uninp [no_me] [no_print] [html] [clipboard] [player_*] [player_*] ... [message format] [not playing message]\n' \ 'player_* is a music player name to add for searching (eg \"player_bmp\")\n' \ 'message format is a quoted, comma separated list of two types of stuff:\n' \ '1) simple quoted string, it will be always included in the final message\n' \ '2) three parms list in \"[ ]\". if first parm is not empty, second will be in the message,\n' \ ' if first parm is empty, third will be used\n' \ 'example: \"\'/me is listening to \', artist, \' - \', [album, album + \' - \', \'\'], title\"\n' \ 'not playing message will be shown when nothing is playing\n' \ 'available parms: artist, album, year, title, genre, time, comment, track, bitrate, samplerate, channels\n' \ 'supported file formats: all that taglib supports (mpeg, ogg vorbis, flac, mpc)\n' \ 'NOTE: when using this script as xchat module, only "message format" and "not playing message" parms should be used' irssi_help = '#!/usr/bin/irssi\n' \ '# put this script as ~/.irssi/scripts/autorun/my_np.pl\n' \ '# load it, type /np\n' \ '\n' \ '$VERSION = "0.1";\n' \ '\n' \ '%IRSSI = (\n' \ ' authors => "[515]",\n' \ ' name => "my_np",\n' \ ' description => "uninp wrapper for irssi",\n' \ ' license => "Public Domain",\n' \ ' );\n' \ '\n' \ 'use Irssi;\n' \ '\n' \ '\n' \ 'sub Np {\n' \ ' my ($data, $server, $witem) = @_;\n' \ '\n' \ ' my $s = qx { uninp.py 2>/dev/null };\n' \ ' $s = substr($s, 4);\n' \ '\n' \ ' if ($s eq "") {\n' \ ' $s = \'is not listening to anything\'\n' \ ' }\n' \ '\n' \ ' if ($witem && ($witem->{\'type\'} eq "CHANNEL" ||\n' \ ' $witem->{\'type\'} eq "QUERY"))\n' \ ' { $witem->command("ME $s"); }\n' \ '\n' \ '}\n' \ '\n' \ 'Irssi::command_bind(\'np\', \'Np\');\n' def display_tag(t, player): """Get the tags""" artist = t['artist'].strip() album = t['album'].strip() title = t['title'].strip() genre = t['genre'].strip() time = t['length'] comment = t['comment'].strip() year = str(t['year']) track = str(t['track']) bitrate = str(t['bitrate']) samplerate = str(t['samplerate']) channels = str(t['channels']) if year == '0': year = '' elif '-' in year: year = year[:year.index('-')] if track == '0': track = '' if bitrate == '0': bitrate = '' if samplerate == '0': samplerate = '' if channels == '0': channels = '' msg = '' if time: if time > 3600: time = strftime('%H:%M:%S', gmtime(time)) else: time = strftime('%M:%S', gmtime(time)) else: time = '' for i in eval('[' + format + ']'): if type(i) == type([]): if i[0]: msg += i[1] else: msg += i[2] else: msg += i cmd = msg.decode('utf-8', 'replace').encode('utf-8', 'replace') if noxchat: return cmd if cmd[0] == '/': cmd = cmd[1:] xchat.command(cmd) return xchat.EAT_ALL def search_player_files(player): """Searches for the specified player in the process tree and gets its files""" ps = os.listdir('/proc') ps = ps[ps.index('1') + 10:] #skip some unneeded processes for i in ps: try: if player in os.readlink('/proc/' + i + '/exe'): files = [] for f in os.listdir('/proc/' + i + '/fd'): if stat.S_ISREG(os.stat('/proc/' + i + '/fd/' + f)[stat.ST_MODE]): files += [os.readlink('/proc/' + i + '/fd/' + f),] return files except: pass #print traceback.format_exc() return [] def uninp_help(word, word_eol, userdata): """Prints uninp help for xchat""" print help_string return xchat.EAT_ALL def uninp_send(word=None, word_eol=None, userdata=None): """Searches for player and current song tags""" global format global format_not_playing if word: if len(word) > 1: format = word[1] if len(word) > 2: format_not_playing = word[2] for i in players: if i == 'mpd': import socket try: s = socket.socket(socket.AF_INET) s.connect(mpd_connection) s.recv(9999) s.send('status\n') st = s.recv(9999).splitlines() status = dict([(x[:x.index(':')].lower(), x[x.index(': ')+2:]) for x in st if ': ' in x]) s.send('playlistinfo ' + status['song'] + '\n') inf = s.recv(9999).splitlines() s.close() f = dict([(x[:x.index(':')].lower(), x[x.index(': ')+2:]) for x in inf if ': ' in x]) if not 'artist' in f.keys() and 'name' in f.keys() and 'title' in f.keys(): if ' -' in f['name']: f['name'] = f['name'][0:f['name'].index(' -')] return '/me is listening to \"' + f['title'] + '\" [' + f['name'] + '] with mpd' elif not 'artist' in f.keys() and not 'title' in f.keys() and 'name' in f.keys() and 'file' in f.keys(): return '/me is listening to \"' + f['name'] + '\" [' + f['file'] + '] with mpd' if not 'date' in f.keys(): f['date'] = 0 if not 'track' in f.keys(): f['track'] = 0 if not 'time' in f.keys(): f['time'] = 0 fields = ('artist', 'album', 'title', 'genre') for fi in fields: if not fi in f.keys(): f[fi] = '' f['year'] = f['date'] f['length'] = int(f['time']) f['comment'] = '' f['bitrate'] = status['bitrate'] f['samplerate'] = status['audio'].split(':')[0] f['channels'] = status['audio'].split(':')[2] return display_tag(f, i) except: pass #print traceback.format_exc() elif not notag: files = search_player_files(i) if files: try: for j in files: f = uninp_tag.get_tag(j) if f: try: return display_tag(f, i) except: print traceback.format_exc() except: print traceback.format_exc() if noxchat: return format_not_playing if format_not_playing[0] == '/': xchat.command(format_not_playing[1:]) else: xchat.command(format_not_playing) return xchat.EAT_ALL def htmlize(s): """Terrorize, brutalize, sodomize""" rep = { 'À' : 'À', 'Á' : 'Á', 'Â' : 'Â', 'Ã' : 'Ã', 'Ä' : 'Ä', 'Æ' : 'Æ', 'Ç' : 'Ç', 'È' : 'È', 'É' : 'É', 'Ê' : 'Ê', 'Ë' : 'Ë', 'Ì' : 'Ì', 'Í' : 'Í', 'Î' : 'Î', 'Ï' : 'Ï', 'Ð' : 'Ð', 'Ñ' : 'Ñ', 'Ò' : 'Ò', 'Ó' : 'Ó', 'Ô' : 'Ô', 'Õ' : 'Õ', 'Ö' : 'Ö', 'Ø' : 'Ø', 'Ù' : 'Ù', 'Ú' : 'Ú', 'Û' : 'Û', 'Ü' : 'Ü', 'Ý' : 'Ý', 'Þ' : 'Þ', 'ß' : 'ß', 'à' : 'à', 'á' : 'á', 'â' : 'â', 'ã' : 'ã', 'ä' : 'ä', 'å' : 'å', 'æ' : 'æ', 'ç' : 'ç', 'è' : 'è', 'é' : 'é', 'ê' : 'ê', 'ë' : 'ë', 'ì' : 'ì', 'í' : 'í', 'î' : 'î', 'ï' : 'ï', 'ð' : 'ð', 'ñ' : 'ñ', 'ò' : 'ò', 'ó' : 'ó', 'ô' : 'ô', 'õ' : 'õ', 'ö' : 'ö', 'ø' : 'ø', 'ù' : 'ù', 'ú' : 'ú', 'û' : 'û', 'ü' : 'ü', 'ý' : 'ý' } for i in rep.keys(): s = s.replace(i, rep[i]) return s if not error: if __name__ == '__main__' and noxchat: import sys import subprocess for i in sys.argv[1:]: if i == 'help': print help_string sys.exit(0) elif i == 'irssi': print irssi_help sys.exit(0) elif i.startswith('player_'): players += i.strip('player_') elif i != 'clipboard' and i != 'no_print' and i != 'html' and i != 'no_me': format = i break cmd = uninp_send(None, None, None).replace(' - -', ' -') if 'html' in sys.argv: cmd = htmlize(cmd) if 'no_me' in sys.argv: cmd = cmd.replace('/me is listening to ', '') if not 'no_print' in sys.argv: print cmd if not 'clipboard' in sys.argv: try: subprocess.call(['dcop', 'klipper', 'klipper', 'setClipboardContents', cmd]) except: pass else: xchat.hook_command('uninp', uninp_send) xchat.hook_command('uninp_help', uninp_help) print __module_name__ + ' v' + __module_version__ + ' loaded'