Plugins: imms.2.py

File imms.2.py, 11.0 kB (added by jkaartinen@iki.fi, 1 year ago)

version 0.2.2 of imms plugin for quod libet that uses immsd for weighted playback and automatic rating. For more information check http://www.luminal.org/wiki/index.php/IMMS/IMMS. Should work with imms-3.1.0-rc2 and quod libet 0.23.1 at least. if you find bugs, leave me a note.

Line 
1 # Copyright 2007 Joonatan Kaartinen <jkaartinen@iki.fi>
2 #
3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License version 2 as
5 # published by the Free Software Foundation
6 #
7 # $Id$
8
9 import gtk, widgets
10 import gobject, config, player
11 import threading
12 import socket
13 import os, select
14
15 from plugins.events import EventPlugin
16 from library import library
17
18 verbose = True
19 def log(msg):
20     if verbose:
21         print "[IMMS] ", msg, "\n"
22
23 class IMMS(EventPlugin):
24     PLUGIN_ID = "IMMS"
25     PLUGIN_NAME = _("Intelligent Multimedia Management System")
26     PLUGIN_VERSION = "0.2.2"
27     PLUGIN_ICON = gtk.STOCK_CONNECT
28     PLUGIN_DESC = ("Completely transparently keeps track of your listening "
29                    "habits, like Auto Rating plugin but uses the external "
30                    "program immsd for the actual work.\n\nFor more "
31                    "information, see the website:\n"
32                    "http://www.luminal.org/wiki/index.php/IMMS/IMMS\n\n"
33                    "Warning, imms system's internal database is used for "
34                    "ratings. If this plugin is activated, quod libet's own "
35                    "ratings stop affecting weighted playback.")
36                    
37     socket = None
38     connected = False
39     modelproxy = None
40     order_id = None
41     songs_id = None
42     songhandler_id = None
43     activated = False
44     enable = False
45     thread = None
46     immsqueue = []
47     song_manual = False
48     songlist_sent = False
49     songs = []
50     active_songs = []
51     songdic = {}
52     use_xidle = None
53     sent_queuerequest = False
54     __buffer = ""
55
56     def __init__(self):
57         log("Initializing.")
58         try:
59             self.use_xidle = config.get("plugins","imms_use_xidle")
60         except:
61             self.use_xidle = True
62    
63     def PluginPreferences(self,parent):
64         def toggled(widget):
65             if widget.get_active():
66                 config.set("plugins", "imms_use_xidle", "true")
67                 self.use_xidle = True
68             else:
69                 config.set("plugins", "imms_use_xidle", "false")
70                 self.use_xidle = False
71             if self.connected:
72                 self.write_command("Setup " + str(self.use_xidle))
73         xidle = gtk.CheckButton(_("Use X idleness statistics."))
74         xidle.set_active(self.use_xidle)
75         xidle.connect("toggled", toggled)
76         return xidle
77
78     def enabled(self):
79         log("enabled\n")
80         order = widgets.main.order
81         self.order_id = order.connect_after("changed",self.order_changed)
82         model = widgets.main.playlist.pl
83         self.songs_id = model.connect_after("songs-set",self.active_songlist_changed)
84
85         proxy = PlayListModelProxy(model,self)
86         widgets.main.playlist.pl = proxy
87         self.modelproxy = proxy
88         self._prepare_songlist()
89         self.active_songlist_changed(proxy)
90         self.enable = True
91         os.system("nice -n 5 immsd &")
92         gobject.timeout_add(3000,self.enabled_continue)
93    
94     def enabled_continue(self):
95         self.connect()
96         self.order_changed(widgets.main.order)
97
98     def disabled(self):
99         log("disabled")
100         widgets.main.order.disconnect(self.order_id)
101         self.order_id = None
102         model = widgets.main.playlist.pl._subject
103         model.disconnect(self.songs_id)
104         self.songs_id = None
105         widgets.main.playlist.pl = model
106         self.modelproxy = None
107         self.enable = False
108        
109     def destroy(self):
110         self.disabled()
111        
112     def order_changed(self,playorder):
113         if playorder.get_active_name() == "weighted":
114             self.activated = True
115             if (self.songlist_sent):
116                 self.queue_song()
117             log("Activating")
118         else:
119             self.activated = False
120             self.immsqueue = []
121             log("Deactivating")
122  
123     def connect(self):
124         try:
125             sock = socket.socket(socket.AF_UNIX,socket.SOCK_STREAM)
126             sock.connect(self.get_imms_root("socket"))
127             sock.send("IMMS\n")
128             log(self.__buffer)
129             sock.send(self.__buffer)
130             self.__buffer = ""
131         except os.error, msg:
132             log("Connection failed." + msg)
133             return
134         log ("Connection Established.")
135         self.socket = sock
136         self.connected = True
137         self.thread = threading.Thread(None,self.socket_handler)
138         self.thread.setDaemon(True)
139         self.thread.start()
140            
141     def get_imms_root(self, filename):
142         try:
143             return os.environ['IMMSROOT'] + "/" + filename
144         except:
145             return os.environ['HOME'] + "/.imms/" + filename
146      
147     def write_command(self,command,log_this = True):
148         if not self.connected:
149             self.__buffer += command + "\n"
150         else:
151             command = self.__buffer + command
152             self.__buffer = ""
153             try:
154                 sent = self.socket.send(command + "\n")
155                 if log_this:
156                     log("sent: " + command)
157             except socket.error, msg:
158                 log(msg)
159                 self.__buffer += command + "\n"
160        
161     def queue_song(self):
162         if not len(self.immsqueue) and not self.sent_queuerequest:
163             self.write_command("SelectNext")
164             self.sent_queuerequest = True
165        
166     def get_next(self):
167         if self.activated:
168             self.queue_song()
169             if len(self.immsqueue):
170                 return self.immsqueue.pop(0)
171         return False
172    
173     def send_active_songlist(self):
174         self.immsqueue = []
175         length = str(len(self.active_songlist()))
176         self.write_command("PlaylistChanged " + length)
177
178     def socket_handler(self):
179         log("socket_handler()")
180         try:
181             self.write_command("Setup " + str(self.use_xidle))
182             while self.enable:
183                 select.select( (self.socket,), (), () )
184                 for line in self.readlines():
185                     self.process_line(line)
186         except socket.error, msg:
187             log("Connection terminated: " + msg)
188         log("socket_handler exiting...")
189         self.connected = False
190         self.socket = None
191            
192     def readlines(self):
193         try:
194             buf = self.__leftover + self.socket.recv(4096)
195             self.__leftover = ""
196         except:
197             return None
198         lines = buf.splitlines()
199         if buf[len(buf)-1] != '\n':
200             self.__leftover = lines.pop()
201         return lines
202    
203     def send_song(self,index):
204         try:
205             filename = self.songlist()[index]['~filename']
206             index = str(index)
207         except:
208             log("Invalid index. " + str(index))
209             return
210         self.write_command("PlaylistItem " + index + " " + filename, False)
211        
212     def send_playlist(self):
213         for song in self.active_songlist():
214             index = str(self.song_to_index(song))
215             filename = song['~filename']
216             self.write_command("Playlist " + index + " " + filename, False)
217         self.write_command("PlaylistEnd");
218         if (not self.songlist_sent):
219             self.songlist_sent = True
220             self.order_changed(widgets.main.order)
221    
222     def process_line(self,line):
223         parts = line.split()
224         command = parts.pop(0)
225         if command == "ResetSelection":
226             log("Receiving: " + line.strip())
227             self.immsqueue = []
228             self.queue_song()
229         elif command == "TryAgain":
230             self.write_command("SelectNext")
231         elif command == "EnqueueNext":
232             next = int(parts[0])
233             song = self.songlist()[next]
234             self.immsqueue.append(song)
235             self.sent_queuerequest = False
236             log("Queueing " + str(next) + " " + song['~filename'])
237         elif command == "PlaylistChanged":
238             log("Receiving: " + line.strip())
239             self.send_active_songlist()
240         elif command == "GetEntirePlaylist":
241             log("Receiving: " + line.strip())
242             self.send_playlist()
243         elif command == "GetPlaylistItem":
244             self.send_song(int(parts[0]))
245         else:
246             log("IMMS: unknown command " + line)
247  
248     __leftover = ""
249     __lines = []
250     def plugin_on_song_ended(self, song, skipped):
251         log("plugin_on_song_ended: " + str(skipped))
252         if song is None: return
253         playpos = player.playlist.get_position() / 1000.0
254         length = song['~#length']
255         end = str((length - playpos) < 20.0)
256         forced = str(self.song_manual)
257         bad = str(length < 30.0)
258         self.write_command("EndSong " + end + " " + forced + " " + bad)
259
260     def plugin_on_song_started(self, song):
261         log("plugin_on_song_started")
262         if song is None: return
263         index = self.song_to_index(song)
264         filename = song['~filename']
265         if index:
266             self.write_command("StartSong " + str(index) + " " + filename)
267    
268 # this event might be necessary to track renames.
269     def plugin_on_changed(self,songlist):
270         log("plugin_on_changed")
271
272 # need to update songlist when this gets fired
273     def plugin_on_added(self,songlist):
274         log("plugin_on_added")
275         for song in songlist:
276             self.songdic[song.key] = len(self.songs)
277             self.songs.extend(song)
278        
279 # just clearing out the song object to keep indexes stable
280     def plugin_on_removed(self,songlist):
281         log("plugin_on_removed")
282         for song in songlist:
283             index = self.song_to_index(song)
284             self.songs[index] = None
285
286 # I guess pauses aren't useful.
287     def plugin_on_paused(self):
288         log("plugin_on_paused")
289    
290     def plugin_on_unpaused(self):
291         log("plugin_on_unpaused")
292        
293     def plugin_on_seek(self, song, msec):
294         log("plugin_on_seek: " + str(msec))
295    
296     def song_to_index(self, song):
297         return self.songdic[song.key]
298            
299     def songlist(self):
300         return self.songs
301
302     def active_songlist_changed(self,model = None):
303         songs = self.modelproxy.get()
304         self.set_active_songlist(songs)
305
306     def set_active_songlist(self,songs):
307         self.active_songs = songs
308         self.songlist_sent = False
309         self.send_active_songlist()
310        
311     def active_songlist(self):
312         return self.active_songs
313    
314     def _prepare_songlist(self):
315         self.songs = library.query("")
316         for index,song in enumerate(self.songs):
317             self.songdic[song.key] = index
318
319
320 class Proxy:
321     def __init__( self, subject ):
322         self._subject = subject
323     def __getattr__( self, name ):
324         return getattr( self._subject, name )
325
326 class PlayListModelProxy(Proxy):
327     __imms = None
328     _subject = None
329     def __init__(self,subject,imms):
330         self._subject = subject
331         self.__imms = imms
332        
333     def next(self):
334         self.__imms.song_manual = False
335         next = self.__imms.get_next()
336         if next:
337             self._subject.go_to(next)
338         else:
339             self._subject.next()
340
341     def next_ended(self):
342         self.next()
343    
344     def go_to(self,song):
345         self.__imms.song_manual = True
346         self._subject.go_to(song)