# This is a shell archive. Save it in a file, remove anything before # this line, and then unpack it by entering "sh file". Note, it may # create directories; files and directories will be owned by you and # have default permissions. # # This archive contains: # # /usr/ports/multimedia/quodlibet # /usr/ports/multimedia/quodlibet/Makefile # /usr/ports/multimedia/quodlibet/files # /usr/ports/multimedia/quodlibet/files/patch-Makefile # /usr/ports/multimedia/quodlibet/files/qlscrobbler.py # /usr/ports/multimedia/quodlibet/distinfo # /usr/ports/multimedia/quodlibet/pkg-descr # /usr/ports/multimedia/quodlibet/pkg-plist # echo c - /usr/ports/multimedia/quodlibet mkdir -p /usr/ports/multimedia/quodlibet > /dev/null 2>&1 echo x - /usr/ports/multimedia/quodlibet/Makefile sed 's/^X//' >/usr/ports/multimedia/quodlibet/Makefile << 'END-of-/usr/ports/multimedia/quodlibet/Makefile' X# New ports collection makefile for: quodlibet X# Date created: 19 December 2005 X# Whom: Byung-Hee HWANG X# X# $FreeBSD$ X# X XPORTNAME= quodlibet XPORTVERSION= 0.15 XCATEGORIES= multimedia audio python XMASTER_SITES= http://www.sacredchao.net/~piman/software/ \ X ${MASTER_SITE_LOCAL} X XMAINTAINER= bh@izb.knu.ac.kr XCOMMENT= A GTK+-based audio player written in Python X XBUILD_DEPENDS= ${PYTHON_SITELIBDIR}/gst/__init__.py:${PORTSDIR}/multimedia/py-gstreamer \ X ${PYTHON_SITELIBDIR}/ogg/_ogg.so:${PORTSDIR}/audio/py-ogg \ X ${PYTHON_SITELIBDIR}/ogg/vorbis.so:${PORTSDIR}/audio/py-vorbis \ X ${PYTHON_SITELIBDIR}/madmodule.so:${PORTSDIR}/audio/py-mad XRUN_DEPENDS= ${PYTHON_SITELIBDIR}/gst/__init__.py:${PORTSDIR}/multimedia/py-gstreamer \ X ${PYTHON_SITELIBDIR}/ogg/_ogg.so:${PORTSDIR}/audio/py-ogg \ X ${PYTHON_SITELIBDIR}/ogg/vorbis.so:${PORTSDIR}/audio/py-vorbis \ X ${PYTHON_SITELIBDIR}/madmodule.so:${PORTSDIR}/audio/py-mad X XUSE_PYTHON= 2.3+ XUSE_GNOME= pygtk2 XUSE_GMAKE= yes XUSE_X_PREFIX= yes XUSE_GSTREAMER= ogg vorbis mad X X.if !defined(DISPLAY) XBUILD_DEPENDS+= Xvfb:${X_VFBSERVER_PORT} XDISPLAYHACK= localhost:1001 X.endif X X.if defined(WITH_EXTENSIONS) XBUILD_DEPENDS+= ${PYTHON_SITELIBDIR}/gtk-2.0/egg/__init__.py:${PORTSDIR}/x11-toolkits/py-gnome-extras XRUN_DEPENDS+= ${PYTHON_SITELIBDIR}/gtk-2.0/egg/__init__.py:${PORTSDIR}/x11-toolkits/py-gnome-extras XPLIST_SUB+= EXTENSIONS:="" X.else XPLIST_SUB+= EXTENSIONS:="@comment " X.endif X X.include X X.if ${OSVERSION} < 500000 XBROKEN= "This port is unsupported on FreeBSD 4.x and below that" X.endif X Xpre-configure: X.if !defined(DISPLAY) X.if exists(${WRKDIR}/.Xvfb.pid) X @-${CAT} ${WRKDIR}/.Xvfb.pid | ${XARGS} kill X @${RM} -f ${WRKDIR}/.Xvfb.pid X.endif X ${X11BASE}/bin/Xvfb :1001 -screen 0 800x600x24 > /dev/null 2>&1 & ${ECHO_CMD} $$! > \ X ${WRKDIR}/.Xvfb.pid X.endif X Xdo-configure: X.if !defined(DISPLAY) X @${SETENV} DISPLAY=${DISPLAYHACK} ${PYTHON_CMD} ${WRKSRC}/check.py X.else X @${PYTHON_CMD} ${WRKSRC}/check.py X.endif X Xpost-configure: X.if !defined(DISPLAY) X @${CAT} ${WRKDIR}/.Xvfb.pid | ${XARGS} kill X @${RM} ${WRKDIR}/.Xvfb.pid X.endif X Xdo-build: X.if defined(WITH_EXTENSIONS) X @cd ${WRKSRC} && ${GMAKE} extensions X.else X @${ECHO_MSG} "Build done." X.endif X Xpost-install: X @${MKDIR} ${PREFIX}/lib/${PORTNAME}/plugins X @${INSTALL_DATA} ${FILESDIR}/qlscrobbler.py ${PREFIX}/lib/${PORTNAME}/plugins X X.include END-of-/usr/ports/multimedia/quodlibet/Makefile echo c - /usr/ports/multimedia/quodlibet/files mkdir -p /usr/ports/multimedia/quodlibet/files > /dev/null 2>&1 echo x - /usr/ports/multimedia/quodlibet/files/patch-Makefile sed 's/^X//' >/usr/ports/multimedia/quodlibet/files/patch-Makefile << 'END-of-/usr/ports/multimedia/quodlibet/files/patch-Makefile' X--- Makefile.orig 2005-11-11 08:06:26.000000000 -0700 X+++ Makefile 2005-12-21 17:30:49.447341885 -0700 X@@ -28,9 +28,9 @@ X install-%: make-install-dirs X install -m 755 $*.py $(DESTDIR)$(PREFIX)/$(TO) X install -m 644 $*.1 $(DESTDIR)$(PREFIX)/share/man/man1/$*.1 X- install -D -m 644 $*.png $(DESTDIR)$(PREFIX)/share/pixmaps/$*.png X+ install -m 644 $*.png $(DESTDIR)$(PREFIX)/share/pixmaps/$*.png X install -m 644 $*.png $(DESTDIR)$(PREFIX)/$(TO) X- install -D -m 644 $*.desktop $(DESTDIR)$(PREFIX)/share/applications/$*.desktop X+ install -m 644 $*.desktop $(DESTDIR)$(PREFIX)/share/applications/$*.desktop X ln -sf ../$(TO)/$*.py $(DESTDIR)$(PREFIX)/bin/$* X X clean: END-of-/usr/ports/multimedia/quodlibet/files/patch-Makefile echo x - /usr/ports/multimedia/quodlibet/files/qlscrobbler.py sed 's/^X//' >/usr/ports/multimedia/quodlibet/files/qlscrobbler.py << 'END-of-/usr/ports/multimedia/quodlibet/files/qlscrobbler.py' X# QLScrobbler: an Audioscrobbler client plugin for Quod Libet. X# version 0.7 X# (C) 2005 by Joshua Kwan , X# Joe Wreschnig X# Licensed under GPLv2. See Quod Libet's COPYING for more information. X Ximport random Ximport md5, urllib, urllib2, time, threading, os Ximport player, config, const Ximport gobject, gtk Xfrom qltk import Message Xfrom util import to X Xclass QLScrobbler(object): X # session invariants X PLUGIN_NAME = "QLScrobbler" X PLUGIN_DESC = "Audioscrobbler client for Quod Libet" X PLUGIN_ICON = gtk.STOCK_CONNECT X PLUGIN_VERSION = "0.7" X CLIENT = "qlb" X PROTOCOL_VERSION = "1.1" X DUMP = os.path.join(const.DIR, "scrobbler_cache") X X # things that could change X X username = "" X password = "" X pwhash = "" X X timeout_id = -1 X submission_tid = -1 X X challenge = "" X submit_url = "" X X # state management X waiting = False X challenge_sent = False X broken = False X need_config = False X need_update = False X already_submitted = False X locked = False X flushing = False X disabled = False X X # we need to store this because not all events get the song X song = None X X queue = [] X X def __init__(self): X # Read dumped queue and delete it X try: X dump = open(self.DUMP, 'r') X self.read_dump(dump) X except: pass X X # Set up exit hook to dump queue X gtk.quit_add(0, self.dump_queue) X X def read_dump(self, dump): X print "Loading dumped queue." X X current = {} X X for line in dump.readlines(): X key = "" X value = "" X X line = line.rstrip() X try: (key, value) = line.split(" = ", 1) X except: X if line == "-": X for key in ["album", "mbid"]: X if key not in current: X current[key] = "" X X for reqkey in ["artist", "title", "length", "stamp"]: X # discard if somehow invalid X if reqkey not in current: X current = {} X X if current != {}: X self.queue.append(current) X current = {} X continue X X if key == "length": current[key] = int(value) X else: current[key] = value X X dump.close() X X os.remove(self.DUMP) X X# print "Queue contents:" X# for item in self.queue: X# print "\t%s - %s" % (item['artist'], item['title']) X X # Try to flush it immediately X if len(self.queue) > 0: X self.flushing = True X self.submit_song() X else: print "Queue was empty!" X X def dump_queue(self): X if len(self.queue) == 0: return 0 X X print "Dumping offline queue, will submit next time." X X dump = open(self.DUMP, 'w') X X for item in self.queue: X for key in item: X dump.write("%s = %s\n" % (key, item[key])) X X dump.close() X X return 0 X X def plugin_on_removed(self, songs): X try: X if self.song in songs: X self.already_submitted = True X except: X # Older version compatibility. X if self.song is songs: X self.already_submitted = True X X def plugin_on_song_ended(self, song, stopped): X if song is None: return X X if self.timeout_id > 0: X gobject.source_remove(self.timeout_id) X self.timeout_id = -1 X X def plugin_on_song_started(self, song): X if song is None: return X X self.already_submitted = False X if self.timeout_id > 0: X gobject.source_remove(self.timeout_id) X X self.timeout_id = -1 X X # Protocol stipulation: X # * don't submit when length < 00:30 X # NOTE: >30:00 stipulation has been REMOVED as of Protocol1.1 X # * don't submit if artist and title are not available X if song["~#length"] < 30: return X elif 'title' not in song: return X elif "artist" not in song: X if ("composer" not in song) and ("performer" not in song): return X X self.song = song X if player.playlist.paused == False: X self.prepare() X X def plugin_on_paused(self): X if self.timeout_id > 0: X gobject.source_remove(self.timeout_id) X # special value that will tell on_unpaused to check song length X self.timeout_id = -2 X X def plugin_on_unpaused(self): X if self.already_submitted == False and self.timeout_id == -1: self.prepare() X X def plugin_on_seek(self, song, msec): X if self.timeout_id > 0: X gobject.source_remove(self.timeout_id) X self.timeout_id = -1 X X if msec == 0: #I think this is okay! X self.prepare() X else: X self.already_submitted = True # cancel X X def prepare(self): X if self.song is None: return X X # Protocol stipulations: X # * submit 240 seconds in or at 50%, whichever comes first X delay = int(min(self.song["~#length"] / 2, 240)) X X if self.timeout_id == -2: # change delta based on current progress X # assumption is that self.already_submitted == 0, therefore X # delay - progress > 0 X progress = int(player.playlist.info.time[0] / 1000) X delay -= progress X X self.timeout_id = gobject.timeout_add(delay * 1000, self.submit_song) X X def read_config(self): X username = "" X password = "" X try: X username = config.get("plugins", "scrobbler_username") X password = config.get("plugins", "scrobbler_password") X except: X if self.need_config == False: X self.quick_info("Please visit the Preferences window to set QLScrobbler up. Until then, songs will not be submitted.") X self.need_config = True X return X X self.username = username X X hasher = md5.new() X hasher.update(password); X self.password = hasher.hexdigest() X self.need_config = False X X def __destroy_cb(self, dialog, response_id): X dialog.destroy() X X def quick_error_helper(self, str): X dialog = Message(gtk.MESSAGE_ERROR, None, "QLScrobbler", str) X dialog.connect('response', self.__destroy_cb) X dialog.show() X X def quick_error(self, str): X gobject.idle_add(self.quick_error_helper, str) X X def quick_info_helper(self, str): X dialog = Message(gtk.MESSAGE_INFO, widgets.widgets.main, "QLScrobbler", str).run() X dialog.connect('response', self.__destroy_cb) X dialog.show() X X def quick_info(self, str): X gobject.idle_add(self.quick_info_helper, str) X X def clear_waiting(self): X self.waiting = False X X def send_handshake(self): X # construct url X url = "http://post.audioscrobbler.com/?hs=true&p=%s&c=%s&v=%s&u=%s" % ( self.PROTOCOL_VERSION, self.CLIENT, self.PLUGIN_VERSION, self.username ) X X print "Sending handshake to Audioscrobbler." X X resp = None X X try: X resp = urllib2.urlopen(url); X except: X print "Server not responding, handshake failed." X return # challenge_sent is NOT set to 1 X X # check response X lines = resp.read().rstrip().split("\n") X status = lines.pop(0) X X print "Handshake status: %s" % status X X if status == "UPTODATE" or status.startswith("UPDATE"): X if status.startswith("UPDATE"): X self.quick_info("A new plugin is available at %s! Please download it, or your Audioscrobbler stats may not be updated, and this message will be displayed every session." % status.split()[1]) X self.need_update = True X X # Scan for submit URL and challenge. X self.challenge = lines.pop(0) X X print "Challenge: %s" % self.challenge X X # determine password X hasher = md5.new() X hasher.update(self.password) X hasher.update(self.challenge) X self.pwhash = hasher.hexdigest() X X self.submit_url = lines.pop(0) X X self.challenge_sent = True X elif status == "BADUSER": X self.quick_error("Authentication failed: invalid username %s or bad password." % self.username) X X self.broken = True X X # Honor INTERVAL if available X try: interval = int(lines.pop(0).split()[1]) X except: interval = 0 X X if interval > 1: X self.waiting = True X gobject.timeout_add(interval * 1000, self.clear_waiting) X print "Server says to wait for %d seconds." % interval X X def submit_song(self): X bg = threading.Thread(None, self.submit_song_helper) X bg.setDaemon(True) X bg.start() X X def submit_song_helper(self): X enabled = getattr(self, 'PMEnFlag', False) X if enabled and self.disabled: X print "Plugin re-enabled - accepting new songs." X self.disabled = False X if self.submission_tid != -1: X gobject.source_remove(self.submission_tid); X self.submission_tid = -1 X elif not enabled and not self.disabled: #if we've already printed X print "Plugin disabled - not accepting any new songs." X self.disabled = True X if len(self.queue) > 0: X self.submission_tid = gobject.timeout_add(120 * 1000, self.submit_song_helper) X print "Attempts will continue to submit the last %d songs." % len(self.queue) X X if self.already_submitted == True or self.broken == True: return X X if self.flushing == False: X stamp = time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime()) X X store = { X "title": self.song.comma("title"), X "length": str(self.song["~#length"]), X "album": self.song.comma("album"), X "mbid": "", # will be correctly set if available X "stamp": stamp X } X X if "artist" in self.song: X store["artist"] = self.song.comma("artist") X elif "composer" in self.song: X store["artist"] = self.song.comma("composer") X elif "performer" in self.song: X performer = self.song.comma('performer') X if performer[-1] == ")" and "(" in performer: X store["artist"] = performer[:performer.rindex("(")].strip() X else: X store["artist"] = performer X elif "musicbrainz_trackid" in self.song: X store["mbid"] = self.song["musicbrainz_trackid"] X X self.queue.append(store) X else: self.flushing = False X X if self.locked == True: X # another instance running, let it deal with this X return X X self.locked = True X X while self.waiting == True: time.sleep(1) X X # Read config, handshake, and send challenge if not already done X if self.challenge_sent == False: X self.read_config() X if self.broken == False and self.need_config == False: X self.send_handshake() X X # INTERVAL may have been set during handshake. X while self.waiting == True: time.sleep(1) X X if self.challenge_sent == False: X self.locked = False X return X X data = { X 'u': self.username, X 's': self.pwhash X } X X # Flush the cache X for i in range(len(self.queue)): X print to("Sending song: %s - %s" % (self.queue[i]['artist'], self.queue[i]['title'])) X data["a[%d]" % i] = self.queue[i]['artist'].encode('utf-8') X data["t[%d]" % i] = self.queue[i]['title'].encode('utf-8') X data["l[%d]" % i] = str(self.queue[i]['length']) X data["b[%d]" % i] = self.queue[i]['album'].encode('utf-8') X data["m[%d]" % i] = self.queue[i]['mbid'] X data["i[%d]" % i] = self.queue[i]['stamp'] X X (host, file) = self.submit_url[7:].split("/") X X resp = None X X try: X data_str = urllib.urlencode(data) X resp = urllib2.urlopen("http://" + host + "/" + file, data_str) X except: X print "Audioscrobbler server not responding, will try later." X self.locked = False X return # preserve the queue, yadda yadda X X resp_save = resp.read() X lines = resp_save.rstrip().split("\n") X X try: (status, interval) = lines X except: X try: status = lines[0] X except: X print "Truncated server response, will try later..." X self.locked = False X return X interval = None X X print "Submission status: %s" % status X X if status == "BADAUTH": X print "Attempting to re-authenticate." X self.challenge_sent = False X self.send_handshake() X if self.challenge_sent == False: X self.quick_error("Your Audioscrobbler login data is incorrect, so you must re-enter it before any songs will be submitted.\n\nThis message will not be shown again.") X self.broken = True X elif status == "OK": X self.queue = [] X elif status.startswith("FAILED"): X if status.startswith("FAILED Plugin bug"): X print "Plugin bug!? Ridiculous! Dumping queue contents." X for item in self.queue: X for key in item: X print "%s = %s" % (key, item[key]) X # possibly handle other specific cases here for debugging later X else: X print "Unknown response from server: %s" % status X print "Dumping full response:" X print resp_save X X if interval != None: interval_secs = int(interval.split()[1]) X else: interval_secs = 0 X X if interval_secs > 1: X self.waiting = True X gobject.timeout_add(interval_secs * 1000, self.clear_waiting) X print "Server says to wait for %d seconds." % interval_secs X X if self.disabled and len(self.queue) == 0 and self.submission_tid != -1: X print "All songs submitted, disabling retries." X gobject.source_remove(self.submission_tid) X self.submission_tid = -1 X X self.already_submitted = True X self.locked = False X X def PluginPreferences(self, parent): X def changed(entry, key): X # having two functions is unnecessary.. X config.set("plugins", "scrobbler_" + key, entry.get_text()) X X def destroyed(*args): X # if changed, let's say that things just got better and we should X # try everything again X newu = None X newp = None X try: X newu = config.get("plugins", "scrobbler_username") X newp = config.get("plugins", "scrobbler_password") X except: X return X X if self.username != newu or self.password != newp: X self.broken = False X X table = gtk.Table(3, 2) X table.set_col_spacings(3) X lt = gtk.Label(_("Please enter your Audioscrobbler username and password.")) X lt.set_size_request(260, -1) X lu = gtk.Label(_("Username:")) X lp = gtk.Label(_("Password:")) X for l in [lt, lu, lp]: X l.set_line_wrap(True) X l.set_alignment(0.0, 0.5) X table.attach(lt, 0, 2, 0, 1, xoptions=gtk.FILL) X table.attach(lu, 0, 1, 1, 2, xoptions=gtk.FILL) X table.attach(lp, 0, 1, 2, 3, xoptions=gtk.FILL) X userent = gtk.Entry() X pwent = gtk.Entry() X pwent.set_visibility(False) X pwent.set_invisible_char('*') X table.set_border_width(6) X X try: userent.set_text(config.get("plugins", "scrobbler_username")) X except: pass X try: pwent.set_text(config.get("plugins", "scrobbler_password")) X except: pass X X table.attach(userent, 1, 2, 1, 2) X table.attach(pwent, 1, 2, 2, 3) X pwent.connect('changed', changed, 'password') X userent.connect('changed', changed, 'username') X table.connect('destroy', destroyed) X return table END-of-/usr/ports/multimedia/quodlibet/files/qlscrobbler.py echo x - /usr/ports/multimedia/quodlibet/distinfo sed 's/^X//' >/usr/ports/multimedia/quodlibet/distinfo << 'END-of-/usr/ports/multimedia/quodlibet/distinfo' XMD5 (quodlibet-0.15.tar.gz) = 09b14d03e587af6ba929b18ed8e0df56 XSHA256 (quodlibet-0.15.tar.gz) = 20851cc270f17330204683b0a29ab86dcfbbfdda0e1c399c8f97836347063cf5 XSIZE (quodlibet-0.15.tar.gz) = 476236 END-of-/usr/ports/multimedia/quodlibet/distinfo echo x - /usr/ports/multimedia/quodlibet/pkg-descr sed 's/^X//' >/usr/ports/multimedia/quodlibet/pkg-descr << 'END-of-/usr/ports/multimedia/quodlibet/pkg-descr' XQuod Libet is a GTK+-based audio player written in Python. X XWWW: http://www.sacredchao.net/quodlibet/ END-of-/usr/ports/multimedia/quodlibet/pkg-descr echo x - /usr/ports/multimedia/quodlibet/pkg-plist sed 's/^X//' >/usr/ports/multimedia/quodlibet/pkg-plist << 'END-of-/usr/ports/multimedia/quodlibet/pkg-plist' Xbin/exfalso Xbin/quodlibet Xlib/quodlibet/exfalso.png Xlib/quodlibet/exfalso.py X%%EXTENSIONS:%%lib/quodlibet/mmkeys.so Xlib/quodlibet/plugins/qlscrobbler.py Xlib/quodlibet/ql-volume-max.png Xlib/quodlibet/ql-volume-medium.png Xlib/quodlibet/ql-volume-min.png Xlib/quodlibet/ql-volume-zero.png Xlib/quodlibet/quodlibet.png Xlib/quodlibet/quodlibet.py Xlib/quodlibet/quodlibet.zip X%%EXTENSIONS:%%lib/quodlibet/trayicon.so Xshare/applications/exfalso.desktop Xshare/applications/quodlibet.desktop Xshare/locale/bg/LC_MESSAGES/quodlibet.mo Xshare/locale/de/LC_MESSAGES/quodlibet.mo Xshare/locale/en_CA/LC_MESSAGES/quodlibet.mo Xshare/locale/en_GB/LC_MESSAGES/quodlibet.mo Xshare/locale/es/LC_MESSAGES/quodlibet.mo Xshare/locale/fi/LC_MESSAGES/quodlibet.mo Xshare/locale/fr/LC_MESSAGES/quodlibet.mo Xshare/locale/gl/LC_MESSAGES/quodlibet.mo Xshare/locale/he/LC_MESSAGES/quodlibet.mo Xshare/locale/it/LC_MESSAGES/quodlibet.mo Xshare/locale/nl/LC_MESSAGES/quodlibet.mo Xshare/locale/pl/LC_MESSAGES/quodlibet.mo Xshare/locale/pt/LC_MESSAGES/quodlibet.mo Xshare/locale/ru/LC_MESSAGES/quodlibet.mo Xshare/man/man1/exfalso.1 Xshare/man/man1/quodlibet.1 Xshare/pixmaps/exfalso.png Xshare/pixmaps/quodlibet.png X@exec mkdir -p %D/lib/quodlibet/plugins X@dirrm lib/quodlibet/plugins X@exec mkdir -p %D/lib/quodlibet X@dirrm lib/quodlibet END-of-/usr/ports/multimedia/quodlibet/pkg-plist exit