ports/90627: [NEW PORTS] multimedia/quodlibet

Byung-Hee HWANG bh at izb.knu.ac.kr
Mon Dec 19 02:10:16 UTC 2005


>Number:         90627
>Category:       ports
>Synopsis:       [NEW PORTS] multimedia/quodlibet
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    freebsd-ports-bugs
>State:          open
>Quarter:        
>Keywords:       
>Date-Required:
>Class:          change-request
>Submitter-Id:   current-users
>Arrival-Date:   Mon Dec 19 02:10:01 GMT 2005
>Closed-Date:
>Last-Modified:
>Originator:     Byung-Hee HWANG
>Release:        FreeBSD 6.0-STABLE i386
>Organization:
InZealBomb
>Environment:
System: FreeBSD viola.izb.knu.ac.kr 6.0-STABLE FreeBSD 6.0-STABLE #0: Sat Dec 10 09:05:22 MST 2005 bh at viola.izb.knu.ac.kr:/usr/src/sys/i386/compile/II82801BA i386

>Description:
Quod Libet is a GTK+-based audio player written in Python.

>How-To-Repeat:

>Fix:

--- quodlibet.shar begins here ---
# 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 <bh at izb.knu.ac.kr>
X#
X# $FreeBSD$
X#
X
XPORTNAME=	quodlibet
XPORTVERSION=	0.15
XCATEGORIES=	multimedia audio
XMASTER_SITES=	http://www.sacredchao.net/~piman/software/ \
X		http://izb.knu.ac.kr/~bh/software/
XDISTNAME=	quodlibet-${PORTVERSION}
X
XMAINTAINER=	ports at FreeBSD.org
XCOMMENT=	A GTK+-based audio player written in Python
X
XBUILD_DEPENDS=	${PYTHON_SITELIBDIR}/gtk-2.0/gtk/_gtk.so:${PORTSDIR}/x11-toolkits/py-gtk2 \
X		${PYTHON_SITELIBDIR}/gst/_gst.so:${PORTSDIR}/multimedia/py-gstreamer \
X		${PYTHON_SITELIBDIR}/madmodule.so:${PORTSDIR}/audio/py-mad \
X		${PYTHON_SITELIBDIR}/ogg/_ogg.so:${PORTSDIR}/audio/py-ogg \
X		${PYTHON_SITELIBDIR}/ogg/vorbis.so:${PORTSDIR}/audio/py-vorbis \
X		${PYTHON_SITELIBDIR}/gtk-2.0/egg/trayicon.so:${PORTSDIR}/x11-toolkits/py-gnome-extras
XRUN_DEPENDS=	${BUILD_DEPENDS}
X
XUSE_PYTHON=	yes
XUSE_GMAKE=	yes
XUSE_X_PREFIX=	yes
XUSE_GNOME=	gtk20
X
X.include <bsd.port.pre.mk>
X
Xpost-install:
X.if !defined(WITHOUT_PLUGINS)
X		@${MKDIR} ${PREFIX}/lib/${PORTNAME}/plugins
X		@${INSTALL_SCRIPT} ${FILESDIR}/qlscrobbler.py ${PREFIX}/lib/${PORTNAME}/plugins
X.endif
X
X.include <bsd.port.post.mk>
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-17 21:13:46.307487627 -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 <joshk at triplehelix.org>,
X#             Joe Wreschnig <piman at sacredchao.net>
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
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
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 at dirrm lib/quodlibet/plugins
X at dirrm lib/quodlibet
END-of-/usr/ports/multimedia/quodlibet/pkg-plist
exit
--- quodlibet.shar ends here ---


>Release-Note:
>Audit-Trail:
>Unformatted:



More information about the freebsd-ports-bugs mailing list