Plugins: imms.py

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

a 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 for more information. Should work with imms-3.1.0-rc2 and quod libet 0.23.1 at least.

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.1"
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     songhandler_id = None
42     activated = False
43     enable = False
44     thread = None
45     immsqueue = []
46     song_manual = False
47     songlist_sent = False
48     songs = []
49     active_songs = []
50     songdic = {}
51     use_xidle = None
52
53     __buffer = ""
54
55     def __init__(self):
56         log("Initializing.")
57         try:
58             self.use_xidle = config.get("plugins","imms_use_xidle")
59         except:
60             self.use_xidle = True
61    
62     def PluginPreferences(self,parent):
63         def toggled(widget):
64             if widget.get_active():
65                 config.set("plugins", "imms_use_xidle", "true")
66                 self.use_xidle = True
67             else:
68                 config.set("plugins", "imms_use_xidle", "false")
69                 self.use_xidle = False
70             if self.connected:
71                 self.write_command("Setup " + str(self.use_xidle))
72         xidle = gtk.CheckButton(_("Use X idleness statistics."))
73         xidle.set_active(self.use_xidle)
74         xidle.connect("toggled", toggled)
75         return xidle
76
77     def enabled(self):
78         log("enabled\n")
79         order = widgets.main.order
80         self.order_id = order.connect_after("changed",self.order_changed)
81         model = widgets.main.songlist.model
82         proxy = PlayListModelProxy(model,self)
83         widgets.main.playlist.pl = proxy
84         widgets.main.songlist.model = proxy
85         self.modelproxy = proxy
86         self._prepare_songlist()
87         self.enable = True
88         os.system("nice -n 5 immsd &")
89         gobject.timeout_add(3000,self.enabled_continue)
90    
91     def enabled_continue(self):
92         self.connect()
93         self.order_changed(widgets.main.order)
94
95     def disabled(self):
96         log("disabled")
97         widgets.main.order.disconnect(self.order_id)
98         self.order_id = None
99         model = widgets.main.playlist.pl._subject
100         widgets.main.playlist.pl = model
101         widgets.main.songlist.model = model
102         self.modelproxy = None
103         self.enable = False
104        
105     def destroy(self):
106         self.disabled()
107        
108     def order_changed(self,playorder):
109         if playorder.get_active_name() == "weighted":
110             self.activated = True
111             if (self.songlist_sent):
112                 self.queue_song()
113             log("Activating")
114         else:
115             self.activated = False
116             self.immsqueue = []
117             log("Deactivating")
118  
119     def connect(self):
120         try:
121             sock = socket.socket(socket.AF_UNIX,socket.SOCK_STREAM)
122             sock.connect(self.get_imms_root("socket"))
123             sock.send("IMMS\n")
124             log(self.__buffer)
125             sock.send(self.__buffer)
126             self.__buffer = ""
127         except os.error, msg:
128             log("Connection failed." + msg)
129             return
130         log ("Connection Established.")
131         self.socket = sock
132         self.connected = True
133         self.thread = threading.Thread(None,self.socket_handler)
134         self.thread.setDaemon(True)
135         self.thread.start()
136            
137     def get_imms_root(self, filename):
138         try:
139             return os.environ['IMMSROOT'] + "/" + filename
140         except:
141             return os.environ['HOME'] + "/.imms/" + filename
142      
143     def write_command(self,command,log_this = True):
144         if not self.connected:
145             self.__buffer += command + "\n"
146         else:
147             command = self.__buffer + command
148             self.__buffer = ""
149             try:
150                 sent = self.socket.send(command + "\n")
151                 if log_this:
152                     log("sent: " + command)
153             except socket.error, msg:
154                 log(msg)
155                 self.__buffer += command + "\n"
156        
157     def queue_song(self):
158         self.write_command("SelectNext")
159        
160     def get_next(self):
161         if self.activated:
162             self.queue_song()
163             if len(self.immsqueue):
164                 return self.immsqueue.pop(0)
165         return False
166    
167     def send_active_songlist(self):
168         self.immsqueue = []
169         length = str(len(self.active_songlist()))
170         self.write_command("PlaylistChanged " + length)
171
172     def socket_handler(self):
173         log("socket_handler()")
174         try:
175             self.write_command("Setup " + str(self.use_xidle))
176             while self.enable:
177                 select.select( (self.socket,), (), () )
178                 for line in self.readlines():
179                     self.process_line(line)
180         except socket.error, msg:
181             log("Connection terminated: " + msg)
182         log("socket_handler exiting...")
183         self.connected = False
184         self.socket = None
185            
186     def readlines(self):
187         try:
188             buf = self.__leftover + self.socket.recv(4096)
189             self.__leftover = ""
190         except:
191             return None
192         lines = buf.splitlines()
193         if buf[len(buf)-1] != '\n':
194             self.__leftover = lines.pop()
195         return lines
196    
197     def send_song(self,index):
198         try:
199             filename = self.songlist()[index]['~filename']
200             index = str(index)
201         except:
202             log("Invalid index. " + str(index))
203             return
204         self.write_command("PlaylistItem " + index + " " + filename, False)
205        
206     def send_playlist(self):
207         for song in self.active_songlist():
208             index = str(self.song_to_index(song))
209             filename = song['~filename']
210             self.write_command("Playlist " + index + " " + filename, False)
211         self.write_command("PlaylistEnd");
212         if (not self.songlist_sent):
213             self.songlist_sent = True
214             self.order_changed(widgets.main.order)
215    
216     def process_line(self,line):
217         parts = line.split()
218         command = parts.pop(0)
219         if command == "ResetSelection":
220             log("Receiving: " + line.strip())
221             self.immsqueue = []
222             self.queue_song()
223         elif command == "TryAgain":
224             self.write_command("SelectNext")
225         elif command == "EnqueueNext":
226             log("Receiving: " + line.strip())
227             next = int(parts[0])
228             song = self.songlist()[next]
229             self.immsqueue.append(song)
230         elif command == "PlaylistChanged":
231             log("Receiving: " + line.strip())
232             self.send_active_songlist()
233         elif command == "GetEntirePlaylist":
234             log("Receiving: " + line.strip())
235             self.send_playlist()
236         elif command == "GetPlaylistItem":
237             self.send_song(int(parts[0]))
238         else:
239             log("IMMS: unknown command " + line)
240  
241     __leftover = ""
242     __lines = []
243     def plugin_on_song_ended(self, song, skipped):
244         log("plugin_on_song_ended: " + str(skipped))
245         if song is None: return
246         playpos = player.playlist.get_position() / 1000.0
247         length = song['~#length']
248         end = str((length - playpos) < 20.0)
249         forced = str(self.song_manual)
250         bad = str(length < 30.0)
251         self.write_command("EndSong " + end + " " + forced + " " + bad)
252
253     def plugin_on_song_started(self, song):
254         log("plugin_on_song_started")
255         if song is None: return
256         index = self.song_to_index(song)
257         filename = song['~filename']
258         if index:
259             self.write_command("StartSong " + str(index) + " " + filename)
260    
261 # this event might be necessary to track renames.
262     def plugin_on_changed(self,songlist):
263         log("plugin_on_changed")
264
265 # need to update songlist when this gets fired
266     def plugin_on_added(self,songlist):
267         log("plugin_on_added")
268         for song in songlist:
269             self.songdic[song.key] = len(self.songs)
270             self.songs.extend(song)
271        
272 # just clearing out the song object to keep indexes stable
273     def plugin_on_removed(self,songlist):
274         log("plugin_on_removed")
275         for song in songlist:
276             index = self.song_to_index(song)
277             self.songs[index] = None
278
279 # I guess pauses aren't useful.
280     def plugin_on_paused(self):
281         log("plugin_on_paused")
282    
283     def plugin_on_unpaused(self):
284         log("plugin_on_unpaused")
285        
286     def plugin_on_seek(self, song, msec):
287         log("plugin_on_seek: " + str(msec))
288    
289     def song_to_index(self, song):
290         return self.songdic[song.key]
291            
292     def songlist(self):
293         return self.songs
294
295     def set_active_songlist(self,songs):
296         self.active_songs = songs
297         self.songlist_sent = False
298         self.send_active_songlist()
299        
300     def active_songlist(self):
301         return self.active_songs
302    
303     def _prepare_songlist(self):
304         self.songs = library.query("")
305         for index,song in enumerate(self.songs):
306             self.songdic[song.key] = index
307
308
309 class Proxy:
310     def __init__( self, subject ):
311         self._subject = subject
312     def __getattr__( self, name ):
313         return getattr( self._subject, name )
314
315 class PlayListModelProxy(Proxy):
316     __imms = None
317     _subject = None
318     def __init__(self,subject,imms):
319         self._subject = subject
320         self.__imms = imms
321         songs = self._subject.get()
322         self.__imms.set_active_songlist(songs)
323        
324     def next(self):
325         self.__imms.song_manual = False
326         next = self.__imms.get_next()
327         if next:
328             self._subject.go_to(next)
329         else:
330             self._subject.next()
331     def next_ended(self):
332         self.next()
333    
334     def go_to(self,song):
335         self.__imms.song_manual = True
336         self._subject.go_to(song)
337
338     def set(self, songs):
339         self.__imms._set_active_songlist(songs)
340         self._subject.set(songs)