X-Git-Url: https://git.distorted.org.uk/~mdw/autoys/blobdiff_plain/f4681f118a706c5ee0645eaf251c714b0fe1540e..86e491b0afbcd651478d6d9fb1f85ebef20562d6:/coverart/coverart diff --git a/coverart/coverart b/coverart/coverart deleted file mode 100755 index 789b06d..0000000 --- a/coverart/coverart +++ /dev/null @@ -1,483 +0,0 @@ -#! /usr/bin/python -# -*- coding: utf-8 -*- - -import sys as SYS -import os as OS -from cStringIO import StringIO - -import gobject as G -import gtk as GTK -GDK = GTK.gdk -import cairo as XR - -import urllib as U -import urllib2 as U2 -import json as JS - -THUMBSZ = 96 - -class ImageCache (object): - - THRESH = 128*1024*1024 - - def __init__(me): - me._total = 0 - me._first = me._last = None - - def add(me, img): - me._total += img.size - while me._first and me._total > me.THRESH: - me._first.evict() - img._prev = me._last - img._next = None - if me._last: - me._last._next = img - else: - me._first = img - me._last = img - - def rm(me, img): - if img._prev: - img._prev._next = img._next - else: - me._first = img._next - if img._next: - img._next._prev = img._prev - else: - img._last = img._prev - me._total -= img.size - -CACHE = ImageCache() - -class CacheableImage (object): - - def __init__(me): - me._pixbuf = None - me._prev = me._next = None - me._thumb = None - - @property - def pixbuf(me): - if not me._pixbuf: - me._pixbuf = me._acquire() - me.size = me._pixbuf.get_pixels_array().nbytes - CACHE.add(me) - return me._pixbuf - - def evict(me): - me._pixbuf = None - CACHE.rm(me) - - def flush(me): - me.evict() - me._thumb = None - - @property - def thumbnail(me): - if not me._thumb: - me._thumb = Thumbnail(me) - return me._thumb - -class Thumbnail (object): - - def __init__(me, img): - pix = img.pixbuf - wd, ht = pix.get_width(), pix.get_height() - m = max(wd, ht) - if m <= THUMBSZ: - me.pixbuf = pix - else: - twd, tht = [(x*THUMBSZ + m//2)//m for x in [wd, ht]] - me.pixbuf = pix.scale_simple(twd, tht, GDK.INTERP_HYPER) - -class NullImage (CacheableImage): - - MAP = {} - - def __init__(me, size, text): - CacheableImage.__init__(me) - me._size = size - me._text = text - - @staticmethod - def get(cls, size): - try: - return cls.MAP[size] - except KeyError: - img = cls.MAP[size] = cls(size) - return img - - def _acquire(me): - - surf = XR.ImageSurface(XR.FORMAT_ARGB32, me._size, me._size) - xr = XR.Context(surf) - - xr.set_source_rgb(0.3, 0.3, 0.3) - xr.paint() - - xr.move_to(me._size/2.0, me._size/2.0) - xr.select_font_face('sans-serif', - XR.FONT_SLANT_NORMAL, XR.FONT_WEIGHT_BOLD) - xb, yb, wd, ht, xa, ya = xr.text_extents(me._text) - m = max(wd, ht) - z = me._size/float(m) * 2.0/3.0 - xr.scale(z, z) - - xr.set_source_rgb(0.8, 0.8, 0.8) - xr.move_to(3.0*m/4.0 - wd/2.0 - xb, 3.0*m/4.0 - ht/2.0 - yb) - xr.show_text(me._text) - - surf.flush() - pix = GDK.pixbuf_new_from_data(surf.get_data(), - GDK.COLORSPACE_RGB, True, 8, - me._size, me._size, surf.get_stride()) - return pix - -class FileImage (CacheableImage): - - def __init__(me, file): - CacheableImage.__init__(me) - me._file = file - - def _acquire(me): - return GDK.pixbuf_new_from_file(me._file) - -def fetch_url(url): - out = StringIO() - with U.urlopen(url) as u: - while True: - stuff = u.read(16384) - if not stuff: - break - out.write(stuff) - return out.getvalue() - -def fix_background(w): - style = w.get_style().copy() - style.base[GTK.STATE_NORMAL] = BLACK - style.bg[GTK.STATE_NORMAL] = BLACK - style.text[GTK.STATE_NORMAL] = WHITE - w.set_style(style) - -class BaseCoverViewer (object): - - def __init__(me): - me.scr = GTK.ScrolledWindow() - me.scr.set_policy(GTK.POLICY_AUTOMATIC, GTK.POLICY_AUTOMATIC) - me.iv = GTK.IconView() - me.iv.connect('item-activated', - lambda iv, p: me.activate(me._frompath(p))) - me.iv.connect('selection-changed', me._select) - me.iv.set_pixbuf_column(0) - me.iv.set_text_column(1) - me.iv.set_orientation(GTK.ORIENTATION_VERTICAL) - me.iv.set_item_width(THUMBSZ + 32) - fix_background(me.iv) - me.scr.add(me.iv) - me.reset() - - def reset(me): - me.list = GTK.ListStore(GDK.Pixbuf, G.TYPE_STRING, G.TYPE_PYOBJECT) - me.iv.set_model(me.list) - me.iv.unselect_all() - - def add(me, item): - item.it = me.list.append([item.img.thumbnail.pixbuf, - item.text, - item]) - - def _frompath(me, path): - return me.list[path][2] - - def _select(me, iv): - sel = me.iv.get_selected_items() - if len(sel) != 1: - me.select(None) - else: - me.select(me._frompath(sel[0])) - -class SearchCover (object): - def __init__(me, img): - me.img = img - pix = img.pixbuf - me.text = '%d×%d*' % (pix.get_width(), pix.get_height()) - -class SearchViewer (BaseCoverViewer): - - def __init__(me, chooser): - BaseCoverViewer.__init__(me) - me._chooser = chooser - - def switch(me, current): - me.reset() - if current: - cov = SearchCover(current) - me.add(cov) - me.iv.select_path(me.list.get_path(cov.it)) - - def activate(me, cov): - me._chooser.activated(cov) - - def select(me, cov): - me._chooser.selected(cov) - -class RemoteImage (CacheableImage): - - ERRIMG = NullImage(256, '!') - - def __init__(me, url, ref = None): - CacheableImage.__init__(me) - me._url = url - me._ref = ref - me._data = None - - def _fetch(me): - if me._data: - return - d = StringIO() - rq = U2.Request(me._url) - if me._ref: - rq.add_header('Referer', me._ref) - rs = U2.urlopen(rq) - while True: - stuff = rs.read(16384) - if not stuff: - break - d.write(stuff) - me._data = d.getvalue() - ld = GDK.PixbufLoader() - try: - o = 0 - n = len(me._data) - while True: - if o >= n: - raise ValueError, 'not going to work' - l = min(n, o + 16384) - ld.write(me._data[o:l]) - o = l - f = ld.get_format() - if f: - break - me._format = f - if 'image/gif' in f['mime_types']: - raise ValueError, 'boycotting GIF image' - finally: - try: - ld.close() - except G.GError: - pass - - def _acquire(me): - try: - me._fetch() - ld = GDK.PixbufLoader() - try: - ld.write(me._data) - finally: - ld.close() - return ld.get_pixbuf() - except Exception, e: - print e - return me.ERRIMG.pixbuf - - @property - def ext(me): - exts = me._format['extensions'] - for i in ['jpg']: - if i in exts: - return i - return exts[0] - -class SearchImage (RemoteImage): - - def __init__(me, url, ref, tburl): - RemoteImage.__init__(me, url, ref) - me._tburl = tburl - - @property - def thumbnail(me): - if not me._thumb: - me._thumb = Thumbnail(RemoteImage(me._tburl)) - return me._thumb - -class SearchResult (SearchCover): - - def __init__(me, r): - w = int(r['width']) - h = int(r['height']) - url = r['unescapedUrl'] - ref = r['originalContextUrl'] - tburl = r['tbUrl'] - me.img = SearchImage(url, ref, tburl) - me.text = '%d×%d' % (w, h) - -class SearchFail (Exception): - pass - -class CoverChooser (object): - - SEARCHURL = \ - 'http://ajax.googleapis.com/ajax/services/search/images?v=1.0&rsz=8&q=' - - def __init__(me): - me.win = GTK.Window() - box = GTK.VBox() - top = GTK.HBox() - me.query = GTK.Entry() - top.pack_start(me.query, True, True, 2) - srch = GTK.Button('_Search') - srch.set_flags(GTK.CAN_DEFAULT) - srch.connect('clicked', me.search) - top.pack_start(srch, False, False, 2) - box.pack_start(top, False, False, 2) - me.sv = SearchViewer(me) - panes = GTK.HPaned() - panes.pack1(me.sv.scr, False, True) - scr = GTK.ScrolledWindow() - scr.set_policy(GTK.POLICY_AUTOMATIC, GTK.POLICY_AUTOMATIC) - me.img = GTK.Image() - evb = GTK.EventBox() - evb.add(me.img) - fix_background(evb) - scr.add_with_viewport(evb) - panes.pack2(scr, True, True) - panes.set_position(THUMBSZ + 64) - box.pack_start(panes, True, True, 0) - me.win.add(box) - me.win.connect('destroy', me.destroyed) - me.win.set_default_size(800, 550) - srch.grab_default() - - def update(me, view, which, dir, current): - me.view = view - me.dir = dir - me.which = which - me.current = current - me.img.clear() - me.sv.switch(current) - me.query.set_text(me.makequery(dir)) - me.win.show_all() - - def search(me, w): - q = me.query.get_text() - try: - try: - rq = U2.Request(me.SEARCHURL + U.quote_plus(q), - None, - { 'Referer': - 'http://www.distorted.org.uk/~mdw/coverart' }) - rs = U2.urlopen(rq) - except U2.URLError, e: - raise SearchFail(e.reason) - result = JS.load(rs) - if result['responseStatus'] != 200: - raise SearchFail('%s (status = %d)' % - (result['responseDetails'], - result['responseStatus'])) - d = result['responseData'] - me.sv.switch(me.current) - for r in d['results']: - try: - me.sv.add(SearchResult(r)) - except (U2.URLError, U2.HTTPError): - pass - except SearchFail, e: - print e.args[0] - - def makequery(me, path): - bits = path.split(OS.path.sep) - return ' '.join(['"%s"' % p for p in bits[-2:]]) - - def selected(me, cov): - if cov: - me.img.set_from_pixbuf(cov.img.pixbuf) - else: - me.img.clear() - - def activated(me, cov): - if isinstance(cov, SearchCover): - me.view.replace(me.which, cov.img) - - def destroyed(me, w): - global CHOOSER - CHOOSER = None - -CHOOSER = None - -class ViewCover (object): - - NULLIMG = NullImage(THUMBSZ, '?') - - def __init__(me, dir, path, leaf): - me.text = dir - me.path = path - me.leaf = leaf - if me.leaf: - me.img = me.covimg = FileImage(OS.path.join(me.path, me.leaf)) - else: - me.img = me.NULLIMG - me.covimg = None - -class MainViewer (BaseCoverViewer): - - ITERATTR = 'vit' - - def __init__(me, root): - BaseCoverViewer.__init__(me) - me.root = root - me.walk('') - - def walk(me, dir): - leafp = True - b = OS.path.join(me.root, dir) - imgfile = None - for l in sorted(OS.listdir(b)): - if OS.path.isdir(OS.path.join(b, l)): - leafp = False - me.walk(OS.path.join(dir, l)) - else: - base, ext = OS.path.splitext(l) - if base == 'cover' and ext in ['.jpg', '.png', '.gif']: - imgfile = l - if leafp: - me.add(ViewCover(dir, OS.path.join(me.root, dir), imgfile)) - - def select(me, cov): - pass - - def activate(me, cov): - global CHOOSER - if not CHOOSER: - CHOOSER = CoverChooser() - CHOOSER.update(me, cov, cov.text, cov.covimg) - - def replace(me, cov, img): - leaf = 'cover.%s' % img.ext - out = OS.path.join(cov.path, leaf) - new = out + '.new' - with open(new, 'wb') as f: - f.write(img._data) - OS.rename(new, out) - if cov.leaf not in [None, leaf]: - OS.unlink(OS.path.join(cov.path, cov.leaf)) - ncov = ViewCover(cov.text, cov.path, leaf) - ncov.it = cov.it - me.list[ncov.it] = [ncov.img.thumbnail.pixbuf, ncov.text, ncov] - me.activate(ncov) - -ROOT = SYS.argv[1] - -LOOP = G.MainLoop() - -BLACK = GDK.Color(0, 0, 0) -WHITE = GDK.Color(65535, 65535, 65535) - -WIN = GTK.Window() -VIEW = MainViewer(ROOT) -WIN.add(VIEW.scr) -WIN.set_default_size(814, 660) -WIN.set_title('coverart') -WIN.connect('destroy', lambda _: LOOP.quit()) -WIN.show_all() - -LOOP.run()