[svn] New bestofimms, cleanimms, imms.py and utils.py. immsview
authorfabien
Sun, 08 Feb 2004 16:55:24 -0500
branchimmsview
changeset 31 13f56bb29b96
parent 30 a7f7026f9416
child 32 85c8f5280d48
[svn] New bestofimms, cleanimms, imms.py and utils.py.
.cvsignore
Makefile
bestofimms
bestofimms.tmpl
cleanimms
imms.py
immsview
index.html.in
utils.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.cvsignore	Sun Feb 08 16:55:24 2004 -0500
@@ -0,0 +1,3 @@
+*.pyc
+*.tmplc
+index.html
--- a/Makefile	Sat Feb 07 13:29:53 2004 -0500
+++ b/Makefile	Sun Feb 08 16:55:24 2004 -0500
@@ -1,7 +1,7 @@
 REMOTE_DIR=o:public_html/oss/immsview
 VERSION=$(shell python -c 'from imp import load_source; \
 		print load_source(".","immsview")._version_' \
-		| sed -e 's;\$$Id$$;\1;')
+		| sed -e 's;\$$\I\d\: immsview,v \(.*\) fabien Exp \$$;\1;')
 
 index.html: index.html.in immsview
 	perl -p -e 's;\@IMMSVIEW_VERSION\@;$(VERSION);g' $< > $@
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bestofimms	Sun Feb 08 16:55:24 2004 -0500
@@ -0,0 +1,78 @@
+#!/usr/bin/python
+
+import sqlite
+from htmltmpl import TemplateManager, TemplateProcessor
+import os
+import ID3
+from sys import stderr
+from ogg.vorbis import VorbisFile
+
+_imms_db = os.environ['HOME'] + '/.imms/imms.db'
+_template = __file__ + '.tmpl'
+
+def rating_to_color(rating):
+	i = rating - 75
+    	if i <= 25:
+       		red = 255
+		green = i * 255 / 25
+		blue = 0
+	elif i <= 50:
+		red = (50-i) * 255 / 25
+		green = 255
+		blue = 0
+	else:
+		red = 0
+		green = 255
+		blue = (i-50) * 255 / 25
+	return "#%02X%02X%02X" % (red, green, blue)
+
+def grab_tunes():
+	sql = sqlite.connect(_imms_db, autocommit = 1)
+	cu = sql.cursor()
+	cu.execute(''' SELECT r.rating, l.path
+		FROM Library l, Rating r
+		WHERE r.uid = l.uid AND r.rating >= 125
+		ORDER BY r.rating DESC;''')
+	return map(lambda x: {
+		'rating' : x[0],
+		'path': x[1]},
+		cu.fetchall() )
+
+def check_tunes(tunes):
+	res = []
+	for tune in tunes:
+		song = tune['path']
+		try:
+			os.stat(song)
+		except:
+			continue
+		tune['color'] = rating_to_color(tune['rating'])
+		if song[-4:] == '.mp3':
+			id3 = ID3.ID3(song)
+			try:
+				tune['path'] = \
+					id3['ARTIST'] + \
+					' - ' + id3['TITLE']
+			except:
+				pass
+		elif song[-4:] == '.ogg':
+			vf = VorbisFile(song)
+			vc = vf.comment()
+			try:
+				tune['path'] = \
+					vc['ARTIST'][0] + \
+					u' - ' + vc['TITLE'][0]
+			except:
+				pass
+		res.append(tune)
+	return res
+
+
+def output_web():
+	tproc = TemplateProcessor()
+	tmpl = TemplateManager().prepare(_template)
+	tproc.set('Bestof', check_tunes(grab_tunes()))
+	print tproc.process(tmpl)
+
+if __name__ == '__main__':
+	output_web()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bestofimms.tmpl	Sun Feb 08 16:55:24 2004 -0500
@@ -0,0 +1,16 @@
+<html>
+  <head>
+  <title>Fabien Niñoles's Top Songs</title>
+  </head>
+  <body style="background: black; color: white">
+    <h1>Fabien Niñoles's Top Songs</h1>
+    <ul>
+    <TMPL_LOOP Bestof>
+      <li style="color: <TMPL_VAR color>"><TMPL_VAR rating> - <tt><TMPL_VAR path></tt></li>
+    </TMPL_LOOP>
+    </ul>
+  </body>
+<!--
+vi: syntax=html
+-->
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cleanimms	Sun Feb 08 16:55:24 2004 -0500
@@ -0,0 +1,110 @@
+#!/usr/bin/python
+
+import os
+import sqlite
+from ogg.vorbis import VorbisFile
+import ID3
+import readline
+import imms
+from utils import unique, copy_file, set_file_completer
+
+
+class CleanupIMMS:
+    def __init__(self, db_file):
+        self.db = imms.IMMSDb(db_file)
+    def check_uid(self, uid):
+        lib = self.db.get_library_entries(uid = uid)
+        if len(lib) == 0:
+            print "Erased uid = ", uid
+            self.db.erase_uid(uid)
+    def check_sid(self, sid):
+        lib = self.db.get_library_entries(sid = sid)
+        if len(lib) == 0:
+            print "Erased sid = ", uid
+            self.db.erase_sid(sid)
+    def handle_no_file(self, path, uid, sid):
+        other_paths = self.db.get_library_entries(uid = uid)
+        other_paths = unique(map(lambda x: x[0], other_paths))
+        for opath in other_paths:
+            if os.path.isfile(opath):
+                return None
+        other_paths = self.db.get_library_entries(sid = sid)
+        other_paths = unique(map(lambda x: x[0], other_paths))
+        for opath in other_paths:
+            if os.path.isfile(opath):
+                readline.add_history(opath)
+        readline.add_history(path)
+        readline.insert_text('Edit')
+        while 1:
+            cmd = raw_input("I can't find '%s'.\n"
+                            "Edit, Skip or Remove? " % path)
+            if len(cmd) > 0:
+                cmd = cmd.lstrip()[0].lower()
+                if cmd == 'e':
+                    newpath = raw_input()
+                    if newpath in other_paths:
+                        return None
+                    if os.path.isfile(newpath):
+                        return newpath
+                    print "Invalid filename!"
+                elif cmd == 's':
+                    return path;
+                elif cmd == 'r':
+                    return None
+    def clean_library(self):
+        lib = self.db.get_library_entries()
+        deleted_uids = []
+        deleted_sids = []
+        for entry in lib:
+            path, uid, sid = entry
+            if not os.path.isfile(path):
+                uid = int(uid)
+                sid = int(sid)
+                newfile = self.handle_no_file(path, uid, sid)
+                if not newfile:
+                    print "Erasing ", path
+                    self.db.erase_filename(path)
+                    deleted_uids.append(uid)
+                    deleted_sids.append(sid)
+                elif newfile != path:
+                    print "Renaming ", path, " into ", newfile
+                    self.db.update_filename(path, newfile)
+                else:
+                    print "Skipping ", path
+        map(self.check_uid, unique(deleted_uids))
+        map(self.check_sid, unique(deleted_sids))
+    def clean_rating(self):
+        rates = self.db.get_ratings()
+        rates = unique(map(lambda x: x[0], rates))
+        map(self.check_uid, rates)
+    def clean_acoustic(self):
+        uids = map(lambda x: x[0], self.db.get_acoustics())
+        map(self.check_uid, uids)
+    def clean_info(self):
+        sids = map(lambda x: x[0], self.db.get_infos())
+        map(self.check_sid, sids)
+    def clean_last(self):
+        sids = map(lambda x: x[0], self.db.get_last())
+        map(self.check_sid, sids)
+    def clean_all(self):
+        self.clean_library()
+        self.clean_rating()
+        self.clean_acoustic()
+        self.clean_info()
+        self.clean_last()
+
+if __name__ == '__main__':
+    set_file_completer()
+    readline.parse_and_bind('tab: complete')
+    db_file = os.environ['HOME'] + '/.imms/imms.db'
+    db_backup = db_file + '.bak'
+    copy_file(db_file, db_backup).close()
+    try:
+        clean_up = CleanupIMMS(db_file)
+    except Exception, inst:
+        print inst
+        ans = raw_input('Do you want to get back the backup file? ')
+        if len(ans) > 0:
+            if ans.lstrip()[0].lower() == 'y':
+                copy_file(db_backup, db_file)
+    print "backup file preserved: %s." % db_backup
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/imms.py	Sun Feb 08 16:55:24 2004 -0500
@@ -0,0 +1,133 @@
+import os
+import sqlite
+
+def quote_sql(str):
+    return str.replace("'", "''")
+    
+class IMMSDb:
+    def __init__(self, dbname = None):
+        if not dbname:
+            dbname = os.environ['HOME'] + '/.imms/imms.db'
+        # autocommit = 1 disable autocommit!
+        self.cx = sqlite.connect(dbname, autocommit = 1,
+                                 timeout = 2, encoding = ('utf-8', 'replace'))
+        self.cu = self.cx.cursor()
+    def get_library_entries(self, **kw):
+        qry = "SELECT path, uid, sid FROM Library";
+        first = 1
+        for key in kw.keys():
+            if first:
+                qry += " WHERE "
+                first = 0
+            else:
+                qry += " AND "
+            if key in ['uid', 'sid']:
+                qry += "%s = %d" % (key, kw[key])
+            else:
+                qry += "%s = '%s'" % (key, quote_sql(kw[key]))
+        qry += ";"
+        self.cu.execute(qry)
+        return self.cu.fetchall()
+    def update_filename(self, oldname, newname):
+        self.cu.execute("""UPDATE Library
+            SET path = '%s'
+            WHERE path = '%s';""" % (quote_sql(newname),
+                                     quote_sql(oldname)))
+    def erase_filename(self, name):
+        self.cu.execute("""DELETE FROM Library
+            WHERE path = '%s';""" % quote_sql(name))
+    def erase_uid(self, uid):
+        self.cu.execute("""BEGIN TRANSACTION;
+             DELETE FROM Library WHERE uid = %d;
+             DELETE FROM Rating WHERE uid = %d;
+             DELETE FROM Acoustic WHERE uid = %d;
+             COMMIT;""" % (uid, uid, uid))
+    def erase_sid(self, sid):
+        self.cu.execute("""BEGIN TRANSACTION;
+              DELETE FROM Library WHERE sid = %d;
+              DELETE FROM Info WHERE sid = %d;
+              DELETE FROM Last WHERE sid = %d;
+              COMMIT;""")
+    def erase_path(self, path):
+        self.cu.execute("DELETE FROM Library WHERE path = '%s';" \
+                        % quote_sql(path))
+    def get_ratings(self, min = 0, max = 250):
+        self.cu.execute('''SELECT Rating.uid, Rating.rating 
+                   FROM Rating
+                   WHERE Rating.rating >= %d
+                   AND Rating.rating <= %d
+                   ORDER BY Rating.rating;''' % (min, max))
+        return cu.fetchall()
+    def get_acoustics(self, uids = None):
+        qry = "SELECT uid, bpm. spectrum FROM Acoustic"
+        first = 1
+        for uid in uids:
+            if first:
+                qry += ' WHERE'
+                first = 0
+            else:
+                qry += ' OR'
+            qry += " uid = %d" % uid
+        qry += ';'
+        self.cu.execute(qry)
+        return cu.fetchall()
+    def get_infos(self, sids = None):
+        qry = "SELECT sid, artist, title FROM Infos"
+        first = 1
+        for sid in sids:
+            if first:
+                qry += ' WHERE'
+                first = 0
+            else:
+                qry += ' OR'
+            qry += " sid = %d" % id
+        qry += ';'
+        self.cu.execute(qry)
+        return cu.fetchall()
+    def get_last(self, sids = None):
+        qry = "SELECT sid, last FROM Last"
+        first = 1
+        for sid in sids:
+            if first:
+                qry += ' WHERE'
+                first = 0
+            else:
+                qry += ' OR'
+            qry += " sid = %d" % id
+        qry += ';'
+        self.cu.execute(qry)
+        return cu.fetchall()
+    def get_uid_by_path(self, path):
+        entries = self.get_library_entries(path = path)
+        return map(lambda x: x[1], entries)
+    def get_ratings_and_info(self, uids = None):
+        cu = self.cx.cursor()
+        qry = '''SELECT l.uid, r.rating, l.path, ls.last
+                   FROM Library l, Rating r, Last ls
+                   WHERE l.uid = r.uid AND l.sid = ls.sid'''
+        if uids:
+            qry += ' AND (l.uid = %d' % (uids.pop())
+            for uid in uids:
+                qry += ' OR l.uid = %d' % uid
+            qry += ')'
+        qry += ';'
+        cu.execute(qry)
+        # Better to fetch everything since locking can really mess
+        # things in imms plugin.
+	results = {}
+	tune = cu.fetchone()
+        while tune:
+            try:
+                uid = int(tune[0])
+                if results.has_key(uid):
+                    results[uid]['path'].append(
+                        tune[2].decode('utf-8', 'replace'))
+                else:
+                    results[uid] = {
+                        'rating' : int(tune[1]),
+                        'path' : [ tune[2].decode('utf-8', 'replace') ],
+                        'last' : int(tune[3])}
+            except UnicodeDecodeError:
+                print tune[2]
+	    tune = cu.fetchone()
+        return results
--- a/immsview	Sat Feb 07 13:29:53 2004 -0500
+++ b/immsview	Sun Feb 08 16:55:24 2004 -0500
@@ -20,9 +20,12 @@
 # Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 # Boston, MA 02111-1307, USA.
 
-_version_ = "$Id: immsview 1710 2004-02-07 15:58:54Z fabien $"
+_version_ = "$Id: immsview 1713 2004-02-08 21:55:24Z fabien $"
 
 # $Log$
+# Revision 1.29  2004/02/08 21:55:24  fabien
+# New bestofimms, cleanimms, imms.py and utils.py.
+#
 # Revision 1.28  2004/02/07 15:58:54  fabien
 # Use Python-Gnuplot instead of immsplot.
 #
@@ -139,7 +142,6 @@
 
 import sys
 import os
-import sqlite
 import gobject
 import gtk
 import gtk.glade
@@ -147,6 +149,7 @@
 import xmms.control
 import time
 import Gnuplot
+from imms import IMMSDb
 
 gtk.glade.bindtextdomain('immsview', '/usr/share/immsview/LANG')
 gtk.glade.textdomain('immsview')
@@ -193,71 +196,6 @@
         else:
             self.set_playlist_pos(idx)
 
-def quote_sql(str):
-    return str.replace("'", "''")
-    
-class IMMSDb:
-    _dbname = os.environ['HOME'] + '/.imms/imms.db'
-    # _dbname = os.environ['HOME'] + '/.imms/imms.backup.db'
-    def __init__(self):
-        # autocommit = 1 disable autocommit!
-        self.cx = sqlite.connect(IMMSDb._dbname, autocommit = 1,
-                                 timeout = 2, encoding = ('utf-8', 'replace'))
-    def commit(self):
-        # self.cx.commit()
-        pass
-    def _get_ratings(self, min = 0, max = 250):
-        cu = self.cx.cursor()
-        cu.execute('''SELECT Rating.uid, Rating.rating 
-                   FROM Rating
-                   WHERE Rating.rating >= %d
-                   AND Rating.rating <= %d
-                   ORDER BY Rating.rating;''' % (min, max))
-        return cu.fetchall()
-    def _get_library_uid(self, uid):
-        cu = self.cx.cursor()
-        cu.execute('''SELECT Library.path
-                   FROM Library
-                   WHERE Library.uid = %d;''' % (uid,))
-        return cu.fetchone()
-    def get_uid_by_path(self, path):
-        cu = self.cx.cursor()
-        cu.execute("""SELECT Library.uid FROM Library
-                   WHERE Library.path = '%s';""" % quote_sql(path))
-        return map(lambda x: x[0], cu.fetchall())
-    def get_ratings_and_info(self, uids = None):
-    	print time.ctime(time.time()) + ": querying"
-        cu = self.cx.cursor()
-        qry = '''SELECT l.uid, r.rating, l.path, ls.last
-                   FROM Library l, Rating r, Last ls
-                   WHERE l.uid = r.uid AND l.sid = ls.sid'''
-        if uids:
-            qry += ' AND (l.uid = %d' % (uids.pop())
-            for uid in uids:
-                qry += ' OR l.uid = %d' % uid
-            qry += ')'
-        qry += ';'
-        cu.execute(qry)
-        # Better to fetch everything since locking can really mess
-        # things in imms plugin.
-    	print time.ctime(time.time()) + ": mapping"
-	results = {}
-	tune = cu.fetchone()
-        while tune:
-            try:
-                uid = int(tune[0])
-                if results.has_key(uid):
-                    results[uid]['path'].append(
-                        tune[2].decode('utf-8', 'replace'))
-                else:
-                    results[uid] = {
-                        'rating' : int(tune[1]),
-                        'path' : [ tune[2].decode('utf-8', 'replace') ],
-                        'last' : int(tune[3])}
-            except UnicodeDecodeError:
-                print tune[2]
-	    tune = cu.fetchone()
-        return results
 
 _gdk_colors = []
 for i in range(76):
@@ -554,7 +492,10 @@
             self.iview.get_model().update()
     def do_get_current(self, dummy):
         song = self.xmms.get_current_file()
-        self.iview.set_current_song(song)
+        try:
+            self.iview.set_current_song(song)
+        except:
+            pass
     def do_play(self, dummy):
         fn = self.iview.get_file_selected()
         if fn:
--- a/index.html.in	Sat Feb 07 13:29:53 2004 -0500
+++ b/index.html.in	Sun Feb 08 16:55:24 2004 -0500
@@ -34,15 +34,16 @@
 
     <h2>Requirements</h2>
     <ul>
+      <li><a href="http://www.xmms.org/">XMMS</a></li>
       <li><a href="http://www.luminal.org/wiki/index.php/IMMS/IMMS">IMMS</a></li>
+      <li><a href="http://www.gnuplot.info/">gnuplot</a></li>
+      <li><a href="http://www.sqlite.org/">SQLite</a></li>
+      <li><a href="http://www.python.org/">Python (>=2.3.x)</a></li>
       <li><a href="http://pysqlite.sourceforge.net/">PySQLite</a></li>
       <li><a href="http://people.via.ecp.fr/~flo/index.en.xhtml#PyXMMS">PyXMMS</a></li>
       <li><a href="http://gnuplot-py.sourceforge.net/">Gnuplot.py</a></li>
     </ul>
 
-    <p>As well as any requirements for those programs (that's mean python,
-      gnuplot and xmms too!).</p>
-
     <hr>
     <address><a href="mailto:fabien@tzone.org?subject=immsview">Fabien Niñoles &lt;fabien@tzone.org&gt;</a>
 </address>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/utils.py	Sun Feb 08 16:55:24 2004 -0500
@@ -0,0 +1,103 @@
+#!/usr/bin/python
+
+import dircache, os.path
+from sys import stderr
+import readline
+
+def set_file_completer():
+    readline.set_completer_delims('')
+    readline.set_completer(_file_completer)
+
+def _file_completer(text, state):
+    dirname, filename = os.path.split(text)
+    if not os.path.isdir(dirname):
+        return None
+    paths = dircache.listdir(dirname)
+    tlen = len(filename)
+    checkpaths = []
+    if tlen > 0:
+        for fn in paths:
+            if fn[:len(filename)] == filename:
+                checkpaths.append(fn)
+    else:
+        checkpaths = paths
+    if len(checkpaths) > state:
+        return os.path.join(dirname, checkpaths[state])
+    return None
+    
+def copy_file(origname, copyname):
+    orig = file(origname, 'r')
+    new = file(copyname, 'w')
+    new.write(orig.read())
+    new.flush()
+    return new
+
+def unique(s):
+    """Return a list of the elements in s, but without duplicates.
+
+    For example, unique([1,2,3,1,2,3]) is some permutation of [1,2,3],
+    unique("abcabc") some permutation of ["a", "b", "c"], and
+    unique(([1, 2], [2, 3], [1, 2])) some permutation of
+    [[2, 3], [1, 2]].
+
+    For best speed, all sequence elements should be hashable.  Then
+    unique() will usually work in linear time.
+
+    If not possible, the sequence elements should enjoy a total
+    ordering, and if list(s).sort() doesn't raise TypeError it's
+    assumed that they do enjoy a total ordering.  Then unique() will
+    usually work in O(N*log2(N)) time.
+
+    If that's not possible either, the sequence elements must support
+    equality-testing.  Then unique() will usually work in quadratic
+    time.
+    """
+
+    n = len(s)
+    if n == 0:
+        return []
+
+    # Try using a dict first, as that's the fastest and will usually
+    # work.  If it doesn't work, it will usually fail quickly, so it
+    # usually doesn't cost much to *try* it.  It requires that all the
+    # sequence elements be hashable, and support equality comparison.
+    u = {}
+    try:
+        for x in s:
+            u[x] = 1
+    except TypeError:
+        del u  # move on to the next method
+    else:
+        return u.keys()
+
+    # We can't hash all the elements.  Second fastest is to sort,
+    # which brings the equal elements together; then duplicates are
+    # easy to weed out in a single pass.
+    # NOTE:  Python's list.sort() was designed to be efficient in the
+    # presence of many duplicate elements.  This isn't true of all
+    # sort functions in all languages or libraries, so this approach
+    # is more effective in Python than it may be elsewhere.
+    try:
+        t = list(s)
+        t.sort()
+    except TypeError:
+        del t  # move on to the next method
+    else:
+        assert n > 0
+        last = t[0]
+        lasti = i = 1
+        while i < n:
+            if t[i] != last:
+                t[lasti] = last = t[i]
+                lasti += 1
+            i += 1
+        return t[:lasti]
+
+    # Brute force is all that's left.
+    u = []
+    for x in s:
+        if x not in u:
+            u.append(x)
+    return u
+
+