aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorforcer <forcer>2005-09-11 20:05:00 +0000
committerforcer <mwolson@gnu.org>2005-09-11 20:05:00 +0000
commitbb65333ef00df02dbf6bd83294b4df49e64ea325 (patch)
tree5435715fe823d566ac5494bc672088522af5a763
Initial commit (CVS 2005-09-11)
darcs-hash:20050911200506-2189f-48a136015e33465c3cf09940ce935ec2203df463.gz
-rw-r--r--AUTHORS18
-rw-r--r--FAQ19
-rw-r--r--Makefile39
-rw-r--r--README171
-rw-r--r--RELEASE8
-rw-r--r--debian/changelog58
-rw-r--r--debian/compat1
-rw-r--r--debian/control18
-rw-r--r--debian/copyright31
-rw-r--r--debian/dirs2
-rw-r--r--debian/docs2
-rwxr-xr-xdebian/emms.emacsen-install45
-rwxr-xr-xdebian/emms.emacsen-remove15
-rw-r--r--debian/emms.emacsen-startup20
-rwxr-xr-xdebian/rules91
-rw-r--r--emms-auto.in13
-rw-r--r--emms-default.el130
-rw-r--r--emms-gstreamer.el58
-rw-r--r--emms-info-later-do.el161
-rw-r--r--emms-info-mp3info.el158
-rw-r--r--emms-info-ogg.el81
-rw-r--r--emms-info.el182
-rw-r--r--emms-lyric.el341
-rw-r--r--emms-maint.el1
-rw-r--r--emms-mode-line-icon.el77
-rw-r--r--emms-mode-line.el124
-rw-r--r--emms-mpd.el249
-rw-r--r--emms-pbi-filter.el66
-rw-r--r--emms-pbi-mark.el167
-rw-r--r--emms-pbi-popup.el128
-rw-r--r--emms-pbi.el473
-rw-r--r--emms-pl-manip.el131
-rw-r--r--emms-player-extensions.el119
-rw-r--r--emms-player-simple.el134
-rw-r--r--emms-playing-time.el137
-rw-r--r--emms-score.el189
-rw-r--r--emms-source-file.el288
-rw-r--r--emms-stream-info.el730
-rw-r--r--emms-streams.el445
-rw-r--r--emms-tageditor.el455
-rw-r--r--emms.el676
-rw-r--r--emms.texinfo1255
-rw-r--r--fdl.texi451
-rw-r--r--gpl.texi392
-rwxr-xr-xgst-flac-wrapper3
-rwxr-xr-xgst-mod-wrapper3
-rwxr-xr-xgst-mp3-wrapper3
-rwxr-xr-xgst-ogg-wrapper3
-rw-r--r--later-do.el72
-rw-r--r--ogg-comment.el271
50 files changed, 8704 insertions, 0 deletions
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..e55302c
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,18 @@
+This file lists all people who contributed more than a few lines to
+emms. This is necessary to keep track of people who have copyright
+claims on sources, so please don't be too humble and add yourself.
+
+Daniel Brockman <daniel@brockman.se>
+Jean-Philippe Theberge <jphiltheberge@videotron.ca>
+Jonathan Gonzalez V. <Zeus>
+Lawrence Mitchell <wence@gmx.li>
+Lucas Bonnet <lukhas@free.fr>
+Mario Domgörgen <kanaldrache@gmx.de>
+Michael Olson <mwolson@gnu.org>
+Ulrik Jensen <terryp@daimi.au.dk>
+William XWL <william.xwl@gmail.com>
+Yoni Rabkin Katzenell <yoni-r@actcom.com>
+
+;; Local variables:
+;; coding: utf-8
+;; End:
diff --git a/FAQ b/FAQ
new file mode 100644
index 0000000..d5face4
--- /dev/null
+++ b/FAQ
@@ -0,0 +1,19 @@
+Frequently Asked Questions about emms. Please read this before
+submitting a bug report.
+
+Q: I seem unable to play files with accents in them. Why?
+A: Emacs doesn't know the coding system of your files, and it
+ apparently decodes them the wrong way. Set
+ `default-file-name-coding-system' to the correct encoding of your
+ file names. It might even work to set it to 'undecided and let
+ Emacs guess.
+
+Q: Emms skips some songs in the playlist for no apparent reason. When
+ I select them manually, everything works. Why?
+A: Increase `emms-player-delay' until it works.
+ The problem is that emms is told by Emacs that a player finished,
+ so it starts a new one. But in reality, the player has not yet
+ freed the audio device, so the next player gets an error when
+ trying to play.
+ Thes best way to fix this by using ALSA or other sound systems which
+ allow concurrent access.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..7410fe1
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,39 @@
+DESTDIR=
+EMACS=emacs
+ALLSOURCE=$(wildcard *.el)
+SPECIAL=emms-auto.el emms-maint.el
+SOURCE=$(filter-out $(SPECIAL),$(ALLSOURCE))
+TARGET=$(patsubst %.el,%.elc,$(SOURCE))
+DESTDIR=/usr/share/emacs/site-lisp/emms
+INSTALLINFO=/usr/sbin/install-info
+
+.PHONY: all install clean
+all: $(TARGET) emms-auto.el emms.info
+
+emms-auto.el: emms-auto.in $(SOURCE)
+ cp emms-auto.in emms-auto.el
+ -rm -f emms-auto.elc
+ $(EMACS) --no-init-file --no-site-file -batch \
+ -l emms-maint.el \
+ -l emms-auto.el \
+ -f generate-autoloads \
+ $(shell pwd)/emms-auto.el .
+
+%.elc: %.el
+ $(EMACS) --no-init-file --no-site-file -batch \
+ -l emms-maint.el \
+ -f batch-byte-compile $<
+
+%.info: %.texinfo
+ makeinfo $<
+
+install:
+ test -d $(DESTDIR) || mkdir -p $(DESTDIR)
+ install -m 644 $(ALLSOURCE) $(DESTDIR)/usr/share/emacs/site-lisp/emms/
+ $(INSTALLINFO) --infodir=$(DESTDIR)/usr/share/info/ emms.info
+
+deb-install:
+ install -m 644 $(ALLSOURCE) $(DESTDIR)/usr/share/emacs/site-lisp/emms/
+
+clean:
+ -rm -f *~ *.elc emms-auto.el emms.info
diff --git a/README b/README
new file mode 100644
index 0000000..8a1e97a
--- /dev/null
+++ b/README
@@ -0,0 +1,171 @@
+EMMS --- The Emacs Multi-Media System -*-outline-*-
+=====================================
+
+* Introduction
+==============
+EMMS is the Emacs Multi-Media System. It tries to be a clean and small
+application to play multimedia files from Emacs using external
+players. Many of it's ideas are derived from MpthreePlayer
+(http://www.nongnu.org/mp3player), but it tries to be more general and
+more clean.
+
+** EMMS, Emms, emms, or what?
+-----------------------------
+In various contexts, this program is called EMMS, Emms or emms. Those
+are all correct, and which you use is a matter of personal preference.
+EMMS highlights the acronym character of the name. Emms is akin to
+Emacs and Gnus, ignoring that Emms is pronounce ee-em-em-es, and not a
+single name. emms is highlighting that emms is a case-sensitive file
+name and Emacs Lisp command.
+
+
+* Installation
+==============
+You need to put all the .el files of EMMS in a directory in your
+load-path. For example, if you put all those files into ~/elisp/emms/,
+then in your ~/.emacs, you should do:
+
+(add-to-list 'load-path "~/elisp/emms/")
+
+
+** Setup
+--------
+EMMS is quite simple to set up. For the most basic needs, you will
+just need the following line:
+
+(require 'emms)
+
+Which installs the core of EMMS. Now we need to do some configuration.
+
+The EMMS module `emms-default' provides the function `emms-setup',
+which is a way to quickly configure your EMMS. You can add any number
+of directories which contain media. The first argument is the
+complexity level of the user interface. Here's an example:
+
+(require 'emms-default)
+(emms-setup 'tiny "directory")
+
+Here are the all the interface complexity options:
+
+ * minimalistic : defines the players, play directory but nothing
+ more.
+
+ * tiny : adds the pbi (playlist buffer interface)
+
+ * default : adds the info reading (tags for mp3 and oggs)
+
+ * advanced : features the tageditor and playlist manipulation
+
+ * cvs : features playlist pop-up, pbi marking, mode-line, and
+ asynchronous loading of tags.
+
+Now your configuration is done.
+
+The (optional) directory is used for
+`emms-source-file-default-directory', in case you were wondering.
+
+
+** Usage
+--------
+The basic functionality of EMMS is just to play music without being
+noticed. It provides a few commands to skip the current track and
+such, but else, it doesn't show up. EMMS provides the following basic
+user commands (that you might want to bind to keys):
+
+emms-start ...... Start playing the current playlist
+emms-stop ....... Stop playing
+emms-next ....... Go to the next track in the playlist
+emms-previous ... Go to the previous track in the playlist
+emms-shuffle .... Shuffle the playlist
+emms-show ....... What are you playing?
+
+But before you can use these, you need a playlist to start with. The
+following commands allow you to create a playlist from different
+sources:
+
+emms-play-file ............. Play a single file
+emms-play-directory ........ Play a whole directory
+emms-play-directory-tree ... Play a directory tree
+
+
+* Overview
+==========
+The basic functionality of EMMS consists of three parts: The core, the
+sources, and the players.
+
+The core resides in emms.el, and provides a simple playlist and the
+basic functionality to use all the other features of EMMS. It provides
+the common user commands and interfaces for other parts. It thinks in
+tracks, where a track is the combination of a type and a name - e.g.
+the track type 'file has a name that is the file name. Other track
+types are possible.
+
+To get to tracks, the core needs sources. The file emms-source-file.el
+provides simple sources to interact with the file system.
+
+When EMMS finally has the sources in the playlist, it needs a player
+to play them. emms-player-simple.el defines a few useful players, and
+allows you to define your own in a very simple way.
+
+
+* Modules
+=========
+
+To use one of the modules that come with EMMS just put:
+
+(require 'MODULE-NAME)
+
+in your .emacs
+
+
+** Playlist buffer (emms-pbi)
+-----------------------------
+
+emms-pbi ................ Switch to playlist buffer
+
+The playlist-buffer *Playlist* will be created and put into
+emms-pbi-mode, which give you some useful key bindings.
+
+key binding
+--- -------
+? describe-mode
+<mouse-2> emms-pbi-play-current-line
+RET emms-pbi-play-current-line
+q bury-buffer
+Q emms-pbi-quit
+f emms-pbi-show-current-line
+s emms-stop
+C-y emms-pbi-yank
+C-k emms-pbi-kill-line
+c emms-pbi-recenter
+p emms-previous
+n emms-next
+C-x C-s emms-pbi-export-playlist
+
+
+** Pop-up the Playlist Buffer (emms-pbi-popup)
+----------------------------------------------
+
+emms-pbi-popup-playlist...Popup Playlist buffer
+
+After changing manually the track with emms-pbi-play-current-line the
+old window configuration is restored. It might be useful to bind that
+function to a global-key in your .emacs, for example:
+
+(global-set-key (kbd "<f3>") 'emms-pbi-popup-playlist)
+
+* Bare Bones Setup
+==================
+The following code fragment provides a minimal EMMS setup without
+using the layer of `emms-default'. It can maybe be used to better
+understand the internals of EMMS. You can see how EMMS needs to know
+about players (these are defined in `emms-player-simple') and about
+sources for tracks (trivial file system based sources, such as this
+`emms-directory-tree', are defined in `emms-source-file').
+
+(require 'emms-player-simple)
+(require 'emms-source-file)
+(setq emms-player-list '(emms-player-mpg321
+ emms-player-ogg123
+ emms-player-mplayer))
+
diff --git a/RELEASE b/RELEASE
new file mode 100644
index 0000000..61f6d3f
--- /dev/null
+++ b/RELEASE
@@ -0,0 +1,8 @@
+This file tries to list the things people have to do before they do a
+release.
+
+1) cvs2cl - This generates our ChangeLog
+2) Increase all version numbers in the files
+3) Try to compile
+4) make dist VER=2.3 [FIXME! make dist doesn't work yet *g*]
+5) Put the .tar.gz on the website and dupload the .deb
diff --git a/debian/changelog b/debian/changelog
new file mode 100644
index 0000000..efd717e
--- /dev/null
+++ b/debian/changelog
@@ -0,0 +1,58 @@
+emms (1.4) unstable; urgency=low
+
+ * Depend alternatively on emacsen (Closes: #321493)
+
+ --
+
+emms (1.3) unstable; urgency=low
+
+ * Don't pass docstring to defvaralias due to XEmacs (Closes: #316583)
+
+ -- Jorgen Schaefer <forcer@debian.org> Sat, 2 Jul 2005 05:34:57 +0200
+
+emms (1.2) unstable; urgency=low
+
+ * Use legacy function in emacs21 for `read-directory-name (Closes: #316486)
+ * Implements url parsing without url library (Closes: #314299)
+
+ -- Jorgen Schaefer <forcer@debian.org> Fri, 1 Jul 2005 15:18:54 +0200
+
+emms (1.1-1) unstable; urgency=low
+
+ * New CVS checkout.
+ * Conflicts with emacs20 (Closes #306489).
+
+ -- Jorgen Schaefer <forcer@debian.org> Sun, 29 May 2005 00:25:23 +0200
+
+emms (1.0-4) unstable; urgency=low
+
+ * New CVS checkout.
+
+ -- Jorgen Schaefer <forcer@debian.org> Sun, 11 Jul 2004 03:48:28 +0200
+
+emms (1.0-3) unstable; urgency=low
+
+ * Standards-Version: 3.6.1 (no changes)
+ * Removed ChangeLog from debian/docs.
+ * XEmacs should compile now.
+ * Recommends: vorbis-tools, not ogg123. hmph.
+
+ -- Jorgen Schaefer <forcer@debian.org> Sat, 3 Apr 2004 13:16:25 +0200
+
+emms (1.0-2) unstable; urgency=low
+
+ * Architecture: all
+ * Recommends: mpg321, ogg123 (Closes: #239525)
+ * Suggests: mp3info
+ * Build-depends: texinfo (Closes: #236953)
+ * emms-source-file.el: Don't throw an error when compiling if locate.el
+ is not available. (Closes: #237183)
+
+ -- Jorgen Schaefer <forcer@debian.org> Tue, 9 Mar 2004 02:01:21 +0100
+
+emms (1.0-1) unstable; urgency=low
+
+ * Initial Release.
+
+ -- Jorgen Schaefer <forcer@debian.org> Sat, 21 Feb 2004 05:23:17 +0100
+
diff --git a/debian/compat b/debian/compat
new file mode 100644
index 0000000..b8626c4
--- /dev/null
+++ b/debian/compat
@@ -0,0 +1 @@
+4
diff --git a/debian/control b/debian/control
new file mode 100644
index 0000000..b62482e
--- /dev/null
+++ b/debian/control
@@ -0,0 +1,18 @@
+Source: emms
+Section: sound
+Priority: optional
+Maintainer: Jorgen Schaefer <forcer@debian.org>
+Build-Depends: debhelper (>= 4.0.0), emacs21, texinfo
+Standards-Version: 3.6.2.1
+
+Package: emms
+Architecture: all
+Depends: emacsen-common, emacs21 | emacsen
+Recommends: mpg321, vorbis-tools
+Suggests: mp3info
+Conflicts: emacs20
+Description: The Emacs MultiMedia System
+ EMMS is the Emacs Multi-Media System. It tries to be a clean and small
+ application to play multimedia files from Emacs using external
+ players.
+
diff --git a/debian/copyright b/debian/copyright
new file mode 100644
index 0000000..5570a6e
--- /dev/null
+++ b/debian/copyright
@@ -0,0 +1,31 @@
+This package was debianized by Jorgen Schaefer <forcer@debian.org> on
+Sat, 7 Feb 2004 21:10:24 +0100.
+
+It was downloaded from http://www.gnu.org/software/emms/
+
+Upstream Authors:
+Jorgen Schäfer <forcer@forcix.cx>
+Ulrik Jensen <terryp@daimi.au.dk>
+Mario Domgörgen <kanaldrache@gmx.de>
+
+Copyright:
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 dated June, 1991.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+On Debian GNU systems, the complete text of the GNU General
+Public License can be found in `/usr/share/common-licenses/GPL'.
+
+;; Local variables:
+;; coding: utf-8
+;; End:
diff --git a/debian/dirs b/debian/dirs
new file mode 100644
index 0000000..ddac531
--- /dev/null
+++ b/debian/dirs
@@ -0,0 +1,2 @@
+usr/share/emacs/site-lisp/emms
+usr/share/info
diff --git a/debian/docs b/debian/docs
new file mode 100644
index 0000000..724e084
--- /dev/null
+++ b/debian/docs
@@ -0,0 +1,2 @@
+README
+TODO
diff --git a/debian/emms.emacsen-install b/debian/emms.emacsen-install
new file mode 100755
index 0000000..cbbab73
--- /dev/null
+++ b/debian/emms.emacsen-install
@@ -0,0 +1,45 @@
+#! /bin/sh -e
+# /usr/lib/emacsen-common/packages/install/emms
+
+# Written by Jim Van Zandt <jrv@vanzandt.mv.com>, borrowing heavily
+# from the install scripts for gettext by Santiago Vila
+# <sanvila@ctv.es> and octave by Dirk Eddelbuettel <edd@debian.org>.
+
+FLAVOR=$1
+PACKAGE=emms
+
+if [ ${FLAVOR} = emacs ]; then exit 0; fi
+
+echo install/${PACKAGE}: Handling install for emacsen flavor ${FLAVOR}
+
+#FLAVORTEST=`echo $FLAVOR | cut -c-6`
+#if [ ${FLAVORTEST} = xemacs ] ; then
+# SITEFLAG="-no-site-file"
+#else
+# SITEFLAG="--no-site-file"
+#fi
+FLAGS="${SITEFLAG} -q -batch -l path.el -f batch-byte-compile"
+
+ELDIR=/usr/share/emacs/site-lisp/${PACKAGE}
+ELCDIR=/usr/share/${FLAVOR}/site-lisp/${PACKAGE}
+
+# Install-info-altdir does not actually exist.
+# Maybe somebody will write it.
+if test -x /usr/sbin/install-info-altdir; then
+ echo install/${PACKAGE}: install Info links for ${FLAVOR}
+ install-info-altdir --quiet --section "" "" --dirname=${FLAVOR} /usr/info/${PACKAGE}.info.gz
+fi
+
+install -m 755 -d ${ELCDIR}
+cd ${ELDIR}
+FILES=`echo *.el`
+cp ${FILES} ${ELCDIR}
+cd ${ELCDIR}
+
+cat << EOF > path.el
+(setq load-path (cons "." load-path) byte-compile-warnings nil)
+EOF
+${FLAVOR} ${FLAGS} ${FILES}
+rm -f *.el path.el
+
+exit 0
diff --git a/debian/emms.emacsen-remove b/debian/emms.emacsen-remove
new file mode 100755
index 0000000..b4e552a
--- /dev/null
+++ b/debian/emms.emacsen-remove
@@ -0,0 +1,15 @@
+#!/bin/sh -e
+# /usr/lib/emacsen-common/packages/remove/emms
+
+FLAVOR=$1
+PACKAGE=emms
+
+if [ ${FLAVOR} != emacs ]; then
+ if test -x /usr/sbin/install-info-altdir; then
+ echo remove/${PACKAGE}: removing Info links for ${FLAVOR}
+ install-info-altdir --quiet --remove --dirname=${FLAVOR} /usr/info/emms.info.gz
+ fi
+
+ echo remove/${PACKAGE}: purging byte-compiled files for ${FLAVOR}
+ rm -rf /usr/share/${FLAVOR}/site-lisp/${PACKAGE}
+fi
diff --git a/debian/emms.emacsen-startup b/debian/emms.emacsen-startup
new file mode 100644
index 0000000..e6eaac1
--- /dev/null
+++ b/debian/emms.emacsen-startup
@@ -0,0 +1,20 @@
+;; -*-emacs-lisp-*-
+;;
+;; Emacs startup file for the Debian emms package
+;;
+;; Originally contributed by Nils Naumann <naumann@unileoben.ac.at>
+;; Modified by Dirk Eddelbuettel <edd@debian.org>
+;; Adapted for dh-make by Jim Van Zandt <jrv@vanzandt.mv.com>
+
+;; The emms package follows the Debian/GNU Linux 'emacsen' policy and
+;; byte-compiles its elisp files for each 'emacs flavor' (emacs19,
+;; xemacs19, emacs20, xemacs20...). The compiled code is then
+;; installed in a subdirectory of the respective site-lisp directory.
+;; We have to add this to the load-path:
+(let ((package-dir (concat "/usr/share/"
+ (symbol-name flavor)
+ "/site-lisp/emms")))
+ (when (file-directory-p package-dir)
+ (setq load-path (cons package-dir load-path))
+ (require 'emms-auto)))
+
diff --git a/debian/rules b/debian/rules
new file mode 100755
index 0000000..1233e79
--- /dev/null
+++ b/debian/rules
@@ -0,0 +1,91 @@
+#!/usr/bin/make -f
+# -*- makefile -*-
+# Sample debian/rules that uses debhelper.
+# GNU copyright 1997 to 1999 by Joey Hess.
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+CFLAGS = -Wall -g
+
+ifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS)))
+ CFLAGS += -O0
+else
+ CFLAGS += -O2
+endif
+ifeq (,$(findstring nostrip,$(DEB_BUILD_OPTIONS)))
+ INSTALL_PROGRAM += -s
+endif
+
+configure: configure-stamp
+configure-stamp:
+ dh_testdir
+ # Add here commands to configure the package.
+
+ touch configure-stamp
+
+
+build: build-stamp
+
+build-stamp: configure-stamp
+ dh_testdir
+ make emms.info
+ make emms-auto.el
+ touch build-stamp
+
+clean:
+ dh_testdir
+ dh_testroot
+ rm -f build-stamp configure-stamp
+
+ # Add here commands to clean up after the build process.
+ -$(MAKE) clean
+
+ dh_clean
+
+install: build
+ dh_testdir
+ dh_testroot
+ dh_clean -k
+ dh_installdirs
+
+ # Add here commands to install the package into debian/emms.
+ $(MAKE) deb-install DESTDIR=$(CURDIR)/debian/emms
+
+# Build architecture-independent files here.
+binary-indep: build install
+# We have nothing to do by default.
+
+# Build architecture-dependent files here.
+binary-arch: build install
+ dh_testdir
+ dh_testroot
+ dh_installchangelogs ChangeLog
+ dh_installdocs
+# dh_installexamples
+ dh_install
+# dh_installmenu
+# dh_installdebconf
+# dh_installlogrotate
+ dh_installemacsen
+# dh_installpam
+# dh_installmime
+# dh_installinit
+# dh_installcron
+ dh_installinfo emms.info
+# dh_installman
+ dh_link
+# dh_strip
+ dh_compress
+ dh_fixperms
+# dh_perl
+# dh_python
+# dh_makeshlibs
+ dh_installdeb
+ dh_shlibdeps
+ dh_gencontrol
+ dh_md5sums
+ dh_builddeb
+
+binary: binary-indep binary-arch
+.PHONY: build clean binary-indep binary-arch binary install configure
diff --git a/emms-auto.in b/emms-auto.in
new file mode 100644
index 0000000..78c71ef
--- /dev/null
+++ b/emms-auto.in
@@ -0,0 +1,13 @@
+;;; -*-emacs-lisp-*-
+
+(defvar generated-autoload-file)
+(defvar command-line-args-left)
+(defun generate-autoloads ()
+ (interactive)
+ (require 'autoload)
+ (setq generated-autoload-file (car command-line-args-left))
+ (setq command-line-args-left (cdr command-line-args-left))
+ (batch-update-autoloads))
+
+(provide 'emms-auto)
+;;; Generated autoloads follow (made by autoload.el).
diff --git a/emms-default.el b/emms-default.el
new file mode 100644
index 0000000..95b4691
--- /dev/null
+++ b/emms-default.el
@@ -0,0 +1,130 @@
+;;; emms-default.el --- Setup script for EMMS
+
+;; Copyright (C) 2004 Free Software Foundation, Inc.
+
+;; Author: Ulrik Jensen <terryp@vernon>
+;; Keywords:
+
+;; This file is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; This file is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING. If not, write to the
+;; Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+;; Boston, MA 02110-1301 USA
+
+;;; Commentary:
+
+;; This script can intiialise EMMS to different "levels" of usage.
+
+;;; Code:
+
+(eval-when-compile
+ (require 'cl))
+
+;;; FIXME! This is only backwards-compatibility stuff, remove
+;;; `ignored' parameter.
+(defun emms-setup (level &optional directory &rest ignored)
+ "Sets up EMMS to a specific LEVEL of bells and whistles.
+
+This also sets DIRECTORY as the default directory for finding
+file-tracks.
+
+\(emms-setup 'cvs\) -- Will setup EMMS to a testing environment, that
+probably won't work, but utilizes all the available bells and whistles
+of the version you have installed.
+
+All possible values for the LEVEL, are:
+
+`cvs' -- Everything and no guarantees
+`advanced' -- info, pbi, tageditor
+`default' -- info and the playlist-buffer-interface.
+`tiny' -- basic and pbi
+`minimalistic' -- No bells and whistles, no info, no interfaces. M-x
+emms-next RET and such, as well as a single player. This should almost
+always work, unless you get very unlucky with a CVS-build."
+ ;; Always load the minimalistic setup
+ (require 'emms) ; minimalistic
+ (require 'emms-source-file)
+ (require 'emms-player-simple)
+ (setq emms-player-list
+ '(emms-player-mpg321 emms-player-ogg123 emms-player-mplayer-playlist emms-player-mplayer)
+ emms-source-file-default-directory directory)
+
+ (when ignored
+ (message "Interface for `emms-setup' has changed, please consult the docstring.")
+ (ding))
+
+ (unless (equal level 'minimalistic) ; tiny
+ (require 'emms-pbi)
+
+ (unless (equal level 'tiny) ; default
+ ;; must be default, advanced or cvs, include the pbi and the info
+ (require 'emms-info)
+ (require 'emms-info-mp3info)
+ (setq emms-info-methods-list '(emms-info-mp3info))
+
+ ;; ogg-info might fail!
+ (ignore-errors
+ (require 'emms-info-ogg)
+ (add-to-list 'emms-info-methods-list 'emms-info-ogg-comment))
+
+ ;; setup info
+ (setq emms-track-description-function 'emms-info-file-info-song-artist)
+
+ (unless (equal level 'default) ; advanced
+ ;; + tageditor.
+ (require 'emms-tageditor)
+ (emms-tageditor-pbi-mode 1)
+
+ ;; and pl-manip
+ (require 'emms-pl-manip)
+
+ (unless (equal level 'advanced) ; cvs
+ (require 'emms-pbi-mark)
+ (emms-pbi-mark 1)
+ (emms-tageditor-pbi-mark-mode 1)
+ (require 'emms-pbi-popup)
+
+ ;; load the mode-line
+ (require 'emms-mode-line)
+ (emms-mode-line 1)
+ (emms-mode-line-blank)
+
+ ;; load emms-info-later-do, but ignore problems (since
+ ;; later-do.el might not be available on this system)
+ (ignore-errors
+ (require 'emms-info-later-do)
+ (emms-info-later-do-mode 1)
+ (add-hook 'emms-info-later-do-read-info-functions
+ (lambda (track)
+ (when (get-buffer emms-pbi-playlist-buffer-name)
+ (emms-pbi-entry-update-track track)))))
+
+ ;; try using setnu
+ ;; (ignore-errors
+ ;; (require 'setnu)
+ ;; (add-hook 'emms-pbi-after-build-hook
+ ;; (lambda ()
+ ;; (setnu-mode 1)))))))))
+
+ ;; pause, seek
+ (require 'emms-player-extensions)
+
+ ;; display lyrics
+ (require 'emms-lyric)
+
+ ;; display playing-time
+ (require 'emms-playing-time))))))
+
+
+(provide 'emms-default)
+
+;;; emms-default.el ends here
diff --git a/emms-gstreamer.el b/emms-gstreamer.el
new file mode 100644
index 0000000..5f3aa88
--- /dev/null
+++ b/emms-gstreamer.el
@@ -0,0 +1,58 @@
+;; emms-gstreamer.el --- EMMS Gstreamer interaction
+
+;; License : GPL v2.1 or later
+
+;; currently outside other files, as it's very preliminary support
+
+;; The wrapper concept is easier to set up than a generic gstreamer
+;; support, but in the long term, it's probably not a good idea.
+
+;; Installation instructions :
+
+;; 1. Put (require 'emms-gstreamer) in your ~/.emacs or whatever you
+;; use to configure EMMS.
+
+;; 2. Put the wrappers in your `exec-path' :
+;; (add-to-list 'exec-path "/path/to/wrappers") or the other way,
+;; by moving them in an already present directory.
+
+(require 'emms-player-simple)
+
+(defvar emms-gst-sink "alsasink"
+ "The audio output sink to use")
+
+(defvar emms-gstreamer-paused-p nil)
+
+(define-emms-simple-player gst-mp3 '(file) "\\.[mM][pP][23]$" "gst-mp3-wrapper")
+(define-emms-simple-player gst-ogg '(file) (regexp-opt '(".ogg" ".OGG")) "gst-ogg-wrapper")
+(define-emms-simple-player gst-flac '(file) (regexp-opt '(".FLAC" ".flac" )) "gst-flac-wrapper")
+(define-emms-simple-player gst-mod '(file) (regexp-opt '(".xm" ".it" ".ft" ".mod")) "gst-mod-wrapper")
+
+(add-to-list 'emms-player-list 'emms-player-gst-mp3)
+(add-to-list 'emms-player-list 'emms-player-gst-ogg)
+(add-to-list 'emms-player-list 'emms-player-gst-flac)
+(add-to-list 'emms-player-list 'emms-player-gst-mod)
+
+(setq emms-player-gst-mp3-parameters `(,emms-gst-sink))
+(setq emms-player-gst-ogg-parameters `(,emms-gst-sink))
+(setq emms-player-gst-flac-parameters `(,emms-gst-sink))
+(setq emms-player-gst-mod-parameters `(,emms-gst-sink))
+
+
+(defun emms-gstreamer-play/pause ()
+ (interactive)
+ (if emms-gstreamer-paused-p
+ (emms-gstreamer-resume)
+ (emms-gstreamer-pause)))
+
+(defun emms-gstreamer-pause ()
+ (interactive)
+ (signal-process (shell-command-to-string "pgrep gst-launch") 'SIGSTOP)
+ (setq emms-gstreamer-paused-p t))
+
+(defun emms-gstreamer-resume ()
+ (interactive)
+ (signal-process (shell-command-to-string "pgrep gst-launch") 'SIGCONT)
+ (setq emms-gstreamer-paused-p nil))
+
+(provide 'emms-gstreamer)
diff --git a/emms-info-later-do.el b/emms-info-later-do.el
new file mode 100644
index 0000000..da8acde
--- /dev/null
+++ b/emms-info-later-do.el
@@ -0,0 +1,161 @@
+;;; emms-info-later-do.el --- Using later-do.el to load info
+
+;; Copyright (C) 2004 Free Software Foundation, Inc.
+
+;; Author: Ulrik Jensen <terryp@daimi.au.dk>
+;; Keywords:
+
+;; This file is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; This file is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING. If not, write to the
+;; Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+;; Boston, MA 02110-1301 USA
+
+;;; Commentary:
+
+;; This probably *won't* work if you have the info-cache disabled!
+
+;; Also, you need to have Jorgen Schfers later-do.el in your
+;; loadpath. That file can be found here:
+
+;; <http://www.emacswiki.org/cgi-bin/wiki.pl/LaterDo>
+
+;; Possible problems: if the init-playlist hook gets called *after*
+;; the playlists hook for the same thing gets called, we're doomed.
+
+;; To use this, add the following to your EMMS-configuration
+
+;; (require 'emms-info-later-do)
+;; (emms-info-later-do-mode 1)
+
+;; If you want to use the playlist-buffer interface with this, you
+;; need to add the following as well:
+
+;; (add-hook 'emms-info-later-do-read-info-functions
+;; 'emms-pbi-entry-update-track)
+
+;; Which will make the PBI update it's entries as their info is being
+;; loaded.
+
+;; To change the speed this loading happens with, see
+;; `later-do-interval'.
+
+;;; Code:
+(eval-when-compile (require 'cl))
+(require 'emms-info)
+(condition-case nil
+ (require 'later-do)
+ (error nil))
+
+(defvar emms-info-later-do-track-queue nil
+ "A list of tracks whose info needs to be updated.")
+
+(defun emms-info-later-do-mode (arg)
+ "Activate later-do-mode for loading info."
+ (interactive "p")
+ (if (and (numberp arg) (> arg 0))
+ ;; this hook needs to be run before any hooks that uses the
+ ;; info-cache to do something productive.
+ (add-hook 'emms-playlist-changed-hook
+ 'emms-info-later-do-new-playlist)
+ (remove-hook 'emms-playlist-changed-hook
+ 'emms-info-later-do-new-playlist)))
+
+(defvar emms-info-later-do-read-info-functions nil
+ "Functions to run when the info for a track has been read.
+
+The functions will be called with one argument, TRACK, the track just
+updated.")
+
+(defun emms-info-later-do-clean-queue ()
+ "Remove all emms-info jobs from the later-do queue."
+ (let ((funcs later-do-list)
+ (everything-up-to nil))
+ (while funcs
+ (let ((queued-job (car funcs)))
+ ;; if this job is an emms-info-later-do-process
+ (if (equal (car queued-job) 'emms-info-later-do-process)
+ (progn
+ ;; Remove this item from the list, and continue in a clean
+ ;; way!
+ (let ((everything-after (cdr funcs)))
+ (setq later-do-list (append everything-up-to
+ everything-after))
+ (setq funcs everything-after)))
+ ;; Add this item to everything-up-to
+ (setq everything-up-to (append everything-up-to
+ (list queued-job)))
+ (setq funcs (cdr funcs)))))))
+
+(defun emms-info-later-do-process ()
+ "Load the info for one single item.
+
+Also set up loading the next item, if there are anymore."
+ (when emms-info-later-do-track-queue
+ (let ((track (car emms-info-later-do-track-queue)))
+ ;; remove track from queue
+ (setq emms-info-later-do-track-queue
+ (cdr emms-info-later-do-track-queue))
+ ;; load info for track -- this is kinda redundant, considering
+ ;; that info-get will update the cache when forced to go around
+ ;; it, but just in case.
+ (emms-info-set-cached track (emms-info-get track t))
+ ;; notify the world of our heroic conquest!
+ (run-hook-with-args 'emms-info-later-do-read-info-functions
+ track)
+ (if emms-info-later-do-track-queue
+ ;; we still have a queue, run us again!
+ (later-do 'emms-info-later-do-process)
+ (message "Lazy loading of info complete!")))))
+
+(defun emms-info-later-do-new-playlist ()
+ "Function that gets ready to lazy-load a new playlist.
+
+Should be called from `emms-playlist-changed-hook'."
+ ;; Cleaning up the potential old playlist/process:
+ ;; Clean up any currently running emms-info-later-do-process lists
+ (emms-info-later-do-clean-queue)
+ ;; Remove the cache for any tracks currently containing placeholder
+ ;; info-objects.
+ (while emms-info-later-do-track-queue
+ ;; This track still holds a placeholder info-object, remove that.
+ (emms-info-set-cached (car emms-info-later-do-track-queue) nil)
+ (setq emms-info-later-do-track-queue (cdr emms-info-later-do-track-queue)))
+ (setq emms-info-later-do-track-queue nil)
+ ;; Processing the new playlist:
+ ;; For all tracks where info isn't already cached, create a
+ ;; placeholder info object, so modules requesting info get
+ ;; *something*. Accumulate indices for each track needing to be
+ ;; reread, and add them to the queue.
+ (let ((tracks (emms-playlist-get-playlist))
+ (idx 0))
+ (while (< idx (length tracks))
+ (let* ((curtrack (aref tracks idx))
+ (curinfo (emms-info-get-cached curtrack)))
+ (unless curinfo
+ ;; Mark this one as needing to be loaded
+ (add-to-list 'emms-info-later-do-track-queue curtrack t)
+ ;; Setup a placeholder-info object in the cache
+ (emms-info-set-cached
+ curtrack
+ (make-emms-info :title "" :album ""
+ :artist "" :note ""
+ :file (emms-track-name curtrack)))))
+ (setq idx (1+ idx))))
+ ;; Queue loading the info for each track, if any tracks need to be loaded.
+ (when emms-info-later-do-track-queue
+ ;; Process the first item synchronously to avoid having it displayed in
+ ;; the mode line before its meta-data has been fetched.
+ (emms-info-later-do-process)))
+
+(provide 'emms-info-later-do)
+;;; emms-info-later-do.el ends here
diff --git a/emms-info-mp3info.el b/emms-info-mp3info.el
new file mode 100644
index 0000000..f1a0ed9
--- /dev/null
+++ b/emms-info-mp3info.el
@@ -0,0 +1,158 @@
+;;; emms-info-mp3info.el --- Info-method for EMMS using mp3info
+
+;; Copyright (C) 2003 Free Software Foundation, Inc.
+
+;; Author: Ulrik Jensen <terryp@daimi.au.dk>
+;; Keywords:
+
+;; This file is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; This file is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING. If not, write to the
+;; Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+;; Boston, MA 02110-1301 USA
+
+;;; Commentary:
+
+;; This code has been adapted from code found in mp3player.el, written
+;; by Jean-Philippe Theberge (jphiltheberge@videotron.ca), Mario
+;; Domgoergen (kanaldrache@gmx.de) and Jorgen Schfer
+;; <forcer@forcix.cx>
+
+;; To activate this method for getting info, use something like:
+
+;; (require 'emms-info-mp3info)
+;; (add-to-list 'emms-info-methods-list 'emms-info-mp3info)
+
+;;; Code:
+(eval-when-compile (require 'cl))
+(require 'emms-info)
+
+(defvar emms-info-mp3info-version "0.2 $Revision: 1.10 $"
+ "EMMS info mp3info version string.")
+;; $Id: emms-info-mp3info.el,v 1.10 2005/08/12 18:01:16 xwl Exp $
+
+(defgroup emms-info-mp3info nil
+ "An EMMS-info method for getting/setting ID3v1 tags, using a
+external mp3info program"
+ :group 'emms-info-methods)
+
+(defcustom emms-info-mp3info-program-name "mp3info"
+ "*The name/path of the mp3info tag program."
+ :type 'string
+ :group 'emms-info-mp3info)
+
+(defcustom emms-info-mp3info-field-names
+ '(title "title"
+ artist "artist"
+ album "album"
+ note "description"
+ year "date"
+ genre "genre"
+ file (emms-track-name track)
+ playing-time "playing-time"
+ playing-time-min "playing-time-min"
+ playing-time-sec "playing-time-sec")
+ "Plist of field names."
+ :type 'plist
+ :group 'emms-info-mp3info)
+
+(defcustom emms-info-mp3info-set-parameter
+ '(title "-t"
+ artist "-a"
+ album "-l"
+ note "-c"
+ year "-y"
+ genre "-g")
+ "Parameter list"
+ :type 'plist
+ :group 'emms-info-mp3info)
+
+(defcustom emms-info-field-delemiter "="
+ "Delemiter between field and content."
+ :type 'string
+ :group 'emms-info-mp3info)
+
+(defcustom emms-info-field-seperator "\n"
+ "Seperator between different fields."
+ :type 'string
+ :group 'emms-info-mp3info)
+
+
+(define-emms-info-method emms-info-mp3info
+ :providep 'emms-info-mp3info-providep
+ :get 'emms-info-mp3info-get
+ :set 'emms-info-mp3info-set)
+
+(defun emms-info-mp3info-providep (track)
+ "Return non-nil if this info-method provides info for the track."
+ (if (and track (emms-track-name track)
+ (string-match "\\.mp3$" (emms-track-name track)))
+ t
+ nil))
+
+(defun emms-info-mp3info-set (track info)
+ "Set the id3v1 tag of file TRACK to id3info INFO, using the
+mp3info-program"
+ ;; 0 = discard & don't wait for termination.
+ (call-process emms-info-mp3info-program-name nil 0 nil
+ "-a" (emms-info-artist info)
+ "-t" (emms-info-title info)
+ "-l" (emms-info-album info)
+ "-c" (emms-info-note info)
+ "-y" (emms-info-year info)
+ (emms-track-name track)))
+
+(defun emms-info-retrieve (field)
+ "Search for field and get content.
+If there is no field return an empty string"
+ (goto-char (point-min))
+ (save-match-data
+ (or (progn
+ (re-search-forward
+ (concat field emms-info-field-delemiter "\\(.*\\)"
+ emms-info-field-seperator)
+ nil t)
+ (match-string 1))
+ "")))
+
+
+(defun emms-info-mp3info-get (track)
+ "Get the id3v1 tag of file TRACK, using the mp3info-program and
+return an emms-info structure representing it."
+ (with-temp-buffer
+ (call-process emms-info-mp3info-program-name nil t nil
+ "-p"
+ (concat "title=%t\n" "artist=%a\n" "album=%l\n"
+ "date=%y\n" "genre=%G\n" "description=%c\n"
+ "playing-time=%S\n" "playing-time-min=%m\n"
+ "playing-time-sec=%s\n")
+ (emms-track-name track))
+ (flet ((retrieve-info (field)
+ (goto-char (point-min))
+ (save-match-data
+ (when (re-search-forward (concat field "=\\(.*\\)")
+ nil t)
+ (or (match-string 1) "")))))
+ (make-emms-info
+ :title (retrieve-info "title")
+ :artist (retrieve-info "artist")
+ :album (retrieve-info "album")
+ :note (retrieve-info "description")
+ :year (retrieve-info "date")
+ :genre (retrieve-info "genre")
+ :file (emms-track-name track)
+ :playing-time (retrieve-info "playing-time")
+ :playing-time-min (retrieve-info "playing-time-min")
+ :playing-time-sec (retrieve-info "playing-time-sec")))))
+
+(provide 'emms-info-mp3info)
+;;; emms-info-mp3info.el ends here
diff --git a/emms-info-ogg.el b/emms-info-ogg.el
new file mode 100644
index 0000000..b284df6
--- /dev/null
+++ b/emms-info-ogg.el
@@ -0,0 +1,81 @@
+;;; emms-info-ogg.el --- ogg-comment.el info-interface for EMMS
+
+;; Copyright (C) 2003 Free Software Foundation, Inc.
+
+;; Author: Ulrik Jensen <terryp@daimi.au.dk>
+;; Keywords: ogg, emms, info
+
+;; This file is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; This file is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING. If not, write to the
+;; Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+;; Boston, MA 02110-1301 USA
+
+;;; Commentary:
+
+;; This file provides an interface to retrieving comments from
+;; ogg-files, using Lawrence Mitchells ogg-comment.el.
+
+;; To activate, put something like this in your ~/.emacs:
+
+;; (require 'emms-info-ogg)
+;; (add-to-list 'emms-info-methods-list 'emms-info-ogg-comment)
+
+;; You'll of course need to also have a player if you want to actually
+;; play the files.
+
+;;; Code:
+
+(require 'emms-info)
+(require 'ogg-comment)
+
+(defvar emms-info-ogg-version "0.2 $Revision: 1.14 $"
+ "EMMS info ogg version string.")
+;; $Id: emms-info-ogg.el,v 1.14 2005/07/09 11:56:00 forcer Exp $
+
+(defgroup emms-info-ogg-comments nil
+ "An EMMS-info method for getting/setting ogg-comments, using
+ogg-comments.el"
+ :group 'emms-info-methods
+ :prefix "emms-info-ogg-")
+
+;; Doesn't implement set yet
+(define-emms-info-method emms-info-ogg-comment
+ :providep 'emms-info-ogg-comment-providep
+ :get 'emms-info-ogg-comment-get)
+
+(defun emms-info-ogg-comment-providep (track)
+ "Return non-nil if this info-method provides info for the track."
+ (if (and track (emms-track-name track)
+ (string-match "\\.ogg$" (emms-track-name track)))
+ t
+ nil))
+
+(defun emms-info-ogg-get-comment (field info)
+ (let ((comment (cadr (assoc field (cadr info)))))
+ (if comment
+ comment
+ "")))
+
+(defun emms-info-ogg-comment-get (track)
+ "Retrieve an emms-info strucutre as an ogg-comment"
+ (let ((info (oggc-read-header (emms-track-name track))))
+ (make-emms-info :title (emms-info-ogg-get-comment "title" info)
+ :artist (emms-info-ogg-get-comment "artist" info)
+ :album (emms-info-ogg-get-comment "album" info)
+ :note (emms-info-ogg-get-comment "comment" info)
+ :year (emms-info-ogg-get-comment "date" info)
+ :genre (emms-info-ogg-get-comment "genre" info)
+ :file (emms-track-name track))))
+
+(provide 'emms-info-ogg)
+;;; emms-info-ogg.el ends here
diff --git a/emms-info.el b/emms-info.el
new file mode 100644
index 0000000..b5148f3
--- /dev/null
+++ b/emms-info.el
@@ -0,0 +1,182 @@
+;;; emms-info.el --- Info system for EMMS
+
+;; Copyright (C) 2003 Free Software Foundation, Inc.
+
+;; Author: Ulrik Jensen <terryp@daimi.au.dk>
+;; Keywords:
+
+;; This file is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; This file is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING. If not, write to the
+;; Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+;; Boston, MA 02110-1301 USA
+
+;;; Commentary:
+
+;; This file provides an interface for different methods of reading
+;; info about the files that EMMS is playing, and displaying it.
+
+;; To create a method for retrieving info about a file, you create an
+;; object like this:
+
+;; (define-emms-info-method emms-info-mp3info
+;; :providep 'emms-info-mp3info-providep
+;; :get 'emms-info-mp3info-get
+;; :set 'emms-info-mp3info-set)
+
+;; Then you register it with emms-info, by adding it to
+;; `emms-info-methods-list'.
+
+;; (add-to-list 'emms-info-methods-list 'emms-info-ogg-comment)
+
+;;; Code:
+(require 'emms)
+
+(eval-when-compile (require 'cl))
+
+(defvar emms-info-version "0.2 $Revision: 1.16 $"
+ "EMMS info version string.")
+;; $Id: emms-info.el,v 1.16 2005/08/12 17:59:30 xwl Exp $
+
+;; Customizations
+(defgroup emms-info nil
+ "*Info system for EMMS"
+ :prefix "emms-info-"
+ :group 'emms)
+
+(defgroup emms-info-methods nil
+ "*Methods to get info for EMMS-info"
+ :group 'emms-info
+ :prefix "emms-info-")
+
+(defcustom emms-info-methods-list nil
+ "List of info-methods. You need to set this."
+ :group 'emms-info
+ :type '(repeat function))
+
+;; Caching
+(defcustom emms-info-cache t
+ "Boolean value, indicating whether or not to use a cache for
+info-structures."
+ :group 'emms-info
+ :type 'boolean)
+
+(defcustom emms-info-format '("%s - %s" (emms-info-artist emms-info-title))
+ "Format the info string.
+The first element of the list is a string with typical format
+instructions, the cdr is a list of functions that get called with
+the struct as argument."
+ :type '(list string (repeat sexp))
+ :group 'emms-pbi)
+
+(defvar emms-info-cache-hash-table nil
+ "A hash-table storing the cached info.
+
+Uses tracks as keys and the emms-info structures as
+values.")
+
+ ;; The structure for info about files:
+(defstruct emms-info title artist album note year genre file
+ playing-time playing-time-min playing-time-sec)
+
+;; Interface
+(defmacro define-emms-info-method (name &rest alist)
+ `(defun ,name (action)
+ (plist-get ',(mapcar (lambda (keyword)
+ (if (not (keywordp keyword))
+ (cadr keyword)
+ (intern (substring (symbol-name keyword) 1))))
+ alist)
+ action)))
+
+;; Methods for the cache
+(defun emms-info-get-cached (track)
+ "Return cached info for the track TRACK, nil of no cache."
+ (if emms-info-cache-hash-table
+ (gethash track emms-info-cache-hash-table nil)
+ nil))
+
+(defun emms-info-set-cached (track info)
+ "Set cached info for TRACK to INFO"
+ (unless emms-info-cache-hash-table
+ ;; No hash-table yet, create one
+ (setq emms-info-cache-hash-table (make-hash-table :test 'equal)))
+ (puthash track info emms-info-cache-hash-table))
+
+;; Retrieve
+(defun emms-info-method-for (track)
+ "Return an info-method suitable for TRACK."
+ (unless emms-info-methods-list
+ (error "There are no info-methods defined at all. You should customize `emms-info-methods-alist'."))
+ ;; find an info-method capable of providing info for this file
+ (let ((curmethod emms-info-methods-list))
+ (while (and curmethod
+ (not (funcall (funcall (car curmethod) 'providep) track)))
+ (setq curmethod (cdr curmethod)))
+ (when curmethod
+ (car curmethod))))
+
+(defun emms-info-get (track &optional dont-use-cached)
+ "Return an emms-info structure representing the track TRACK.
+if DONT-USE-CACHED is non-nil, then always read from the file."
+ ;; extend with methods for caching
+ (let ((method (emms-info-method-for track))
+ (cached (emms-info-get-cached track)))
+ (if (or dont-use-cached (not emms-info-cache) (not cached))
+ ;; read from the file
+ (when method
+ (let ((readinfo (funcall (funcall method 'get) track)))
+ (when emms-info-cache
+ ;; save the cache
+ (emms-info-set-cached track readinfo))
+ ;; return the read version
+ readinfo))
+ ;; else just use the cached
+ cached)))
+
+(defun emms-info-set (track info)
+ "Set the info of the file TRACK to the emms-info structure INFO."
+ (let ((method (emms-info-method-for track)))
+ (when method
+ (when emms-info-cache
+ (emms-info-set-cached track info))
+ (funcall (funcall method 'set) track info))))
+
+(defun emms-info-format-info (format struct)
+ "Take FORMAT and format it with STRUCT.
+For the formaz of FORMAT see `emms-info-format')"
+ (apply 'format (car format)
+ (mapcar (lambda (func)
+ (funcall func struct))
+ (cadr format))))
+
+;; Functions suitable as values of
+;; `emms-playlist-get-file-name-function':
+
+(defun emms-info-file-info-song-artist (track)
+ "Returns a description of TRACK, build from it's comments.
+
+If `emms-info-methods-list' indicates how to retrieve special info
+about it, use this. Otherwise returns the name alone."
+ (if (not (and track (emms-track-name track)))
+ "Invalid track!"
+ (if (emms-info-method-for track)
+ ;; read the info
+ (let ((info (emms-info-get track)))
+ (if (and info (not (string= (emms-info-artist info) "")) (not (string= (emms-info-title info) "")))
+ (concat (emms-info-artist info) " - " (emms-info-title info))
+ (file-name-sans-extension (file-name-nondirectory (emms-track-name track)))))
+ ;; we can't read info for this file, default to the name
+ (file-name-sans-extension (file-name-nondirectory (emms-track-name track))))))
+
+(provide 'emms-info)
+;;; emms-info.el ends here
diff --git a/emms-lyric.el b/emms-lyric.el
new file mode 100644
index 0000000..d6f61da
--- /dev/null
+++ b/emms-lyric.el
@@ -0,0 +1,341 @@
+;;; emms-lyric.el --- Display lyrics synchronically
+
+;; Copyright (C) 2005 William XWL
+
+;; Author: William XWL <william.xwl@gmail.com>
+;; Keywords: emms music lyric
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+;;
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+;; 02110-1301 USA
+
+;;; Commentary:
+
+;; This package enables you to play music files and display lyrics
+;; synchronically! :-) It requires `emms-player-extensions'.
+
+;; Put this file into your load-path and the following into your
+;; ~/.emacs:
+;; (require 'emms-lyric)
+
+;; Take a look at the "User Customizable" part for possible personal
+;; customizations.
+
+;;; Change Log:
+
+;; v 0.3 [2005/07/19 17:53:25] Add `emms-lyric-find-lyric' for find
+;; lyric files in local repository `emms-lyric-dir'. Rewrite
+;; `emms-lyric-setup' to support more lyric formats.
+
+;; v 0.2 [2005/07/18 16:10:02] Fix `emms-lyric-pause' bug. Now it works
+;; fine. Add `emms-lyric-seek', but which does not work very well
+;; currently.
+
+;; v 0.1 [2005/07/17 20:07:30] Initial version.
+
+;;; Known bugs:
+
+;; 1. Sometimes music playing would be blocked by some process, like
+;; startup Gnus, while emms-lyric still goes on, thus make music and
+;; lyrics asynchronical.
+
+;;; Todo:
+
+;; 1. Maybe the lyric setup should run before `emms-start'.
+;; 2. Give a user a chance to choose when finding out multiple lyrics.
+;; 3. Search lyrics from internet ?
+
+;;; Code:
+
+(defvar emms-lyric-version "0.4 $Revision: 1.14 $"
+ "EMMS lyric version string.")
+;; $Id: emms-lyric.el,v 1.14 2005/08/25 13:03:02 xwl Exp $
+
+(require 'emms)
+(require 'emms-player-simple)
+(require 'emms-source-file)
+(require 'emms-player-extensions)
+
+;;; User Customizations
+(defvar emms-lyric-display-p t
+ "Whether to diplay lyrics or not.")
+
+(defvar emms-lyric-display-on-modeline t
+ "Display lyrics on mode line.")
+
+(defvar emms-lyric-display-on-minibuffer nil
+ "Display lyrics on minibuffer.")
+
+(defvar emms-lyric-dir ""
+ "The directory of local lyric files. `emms-lyric-find-lyric' will look
+for lyrics in current directory and here.")
+
+(defvar emms-lyric-display-format " %s "
+ "Format for displaying lyric on mode-line.")
+
+;;; Variables
+(defvar emms-lyric-alist nil
+ "a list of the form: '((time0 lyric0) (time1 lyric1)...)). In short,
+at time-i, display lyric-i.")
+
+(defvar emms-lyric-timers nil
+ "timers for displaying lyric.")
+
+(defvar emms-lyric-start-time nil
+ "emms lyric start time.")
+
+(defvar emms-lyric-pause-time nil
+ "emms lyric pause time.")
+
+(defvar emms-lyric-elapsed-time 0
+ "How long time has emms lyric played.")
+
+(defvar emms-lyric-mode-line-string ""
+ "current lyric.")
+
+;;; emms lyric control
+
+(defun emms-lyric-read-file (file)
+ "Read a lyric file(LRC format). File should end up with \".lrc\", its
+contents look like:
+
+ [1:39]I love you, Emacs!
+ [00:39]I love you, Emacs!
+ [00:39.67]I love you, Emacs!
+
+To find FILE, first look up in current directory, if not found, continue
+looking up in `emms-lyric-dir'."
+ (when emms-lyric-display-p
+ (unless (file-exists-p file)
+ (setq file (emms-lyric-find-lyric file)))
+ (when (and file (not (string= file "")) (file-exists-p file))
+ (with-temp-buffer
+ (insert-file-contents file)
+ (while (search-forward-regexp "\\[[0-9:.]+\\].*" nil t)
+ (let ((lyric-string (match-string 0))
+ (time 0)
+ (lyric ""))
+ (setq lyric
+ (replace-regexp-in-string ".*\\]" "" lyric-string))
+ (while (string-match "\\[[0-9:.]+\\]" lyric-string)
+ (let* ((time-string (match-string 0 lyric-string))
+ (semi-pos (string-match ":" time-string)))
+ (setq time
+ (+ (* (string-to-number
+ (substring time-string 1 semi-pos))
+ 60)
+ (string-to-number
+ (substring time-string
+ (1+ semi-pos)
+ (1- (length time-string))))))
+ (setq lyric-string
+ (substring lyric-string (length time-string)))
+ (setq emms-lyric-alist
+ (append emms-lyric-alist `((,time ,lyric))))
+ (setq time 0)))))
+ t))))
+
+(defun emms-lyric-start ()
+ "Start displaying lryics."
+ (setq emms-lyric-start-time (current-time)
+ emms-lyric-pause-time nil
+ emms-lyric-elapsed-time 0)
+ (when (and emms-lyric-display-p
+ (let ((file (cdaddr (emms-playlist-current-track))))
+ (emms-lyric-read-file
+ (replace-regexp-in-string
+ (file-name-extension file) "lrc" file))))
+ (emms-lyric-set-timer)))
+
+(add-hook 'emms-player-started-hook 'emms-lyric-start)
+
+(defun emms-lyric-stop ()
+ "Stop displaying lyrics."
+ (interactive)
+ (when (and emms-lyric-display-p
+ emms-lyric-alist)
+ (cancel-function-timers 'emms-lyric-display)
+ (if (or (not emms-player-paused-p)
+ emms-player-stopped-p)
+ (setq emms-lyric-alist nil
+ emms-lyric-timers nil
+ emms-lyric-mode-line-string ""))))
+
+(add-hook 'emms-player-stopped-hook 'emms-lyric-stop)
+(add-hook 'emms-player-finished-hook 'emms-lyric-stop)
+
+(defun emms-lyric-pause ()
+ "Pause displaying lyrics."
+ (if emms-player-paused-p
+ (setq emms-lyric-pause-time (current-time))
+ (when emms-lyric-pause-time
+ (setq emms-lyric-elapsed-time
+ (+ (time-to-seconds
+ (time-subtract emms-lyric-pause-time
+ emms-lyric-start-time))
+ emms-lyric-elapsed-time)))
+ (setq emms-lyric-start-time (current-time)))
+ (when (and emms-lyric-display-p
+ emms-lyric-alist)
+ (if emms-player-paused-p
+ (emms-lyric-stop)
+ (emms-lyric-set-timer))))
+
+(add-hook 'emms-player-paused-hook 'emms-lyric-pause)
+
+(defun emms-lyric-seek (sec)
+ "Seek forward or backward SEC seconds lyrics."
+ (setq emms-lyric-elapsed-time
+ (+ emms-lyric-elapsed-time
+ (time-to-seconds
+ (time-subtract (current-time)
+ emms-lyric-start-time))
+ sec))
+ (when (< emms-lyric-elapsed-time 0) ; back to start point
+ (setq emms-lyric-elapsed-time 0))
+ (setq emms-lyric-start-time (current-time))
+ (when (and emms-lyric-display-p
+ emms-lyric-alist)
+ (let ((paused-orig emms-player-paused-p))
+ (setq emms-player-paused-p t)
+ (emms-lyric-stop)
+ (setq emms-player-paused-p paused-orig))
+ (emms-lyric-set-timer)))
+
+(add-hook 'emms-player-seeked-hook 'emms-lyric-seek)
+
+(defun emms-lyric-toggle-display-on-minibuffer ()
+ "Toggle display lyric on minibbufer."
+ (interactive)
+ (if emms-lyric-display-on-minibuffer
+ (progn
+ (setq emms-lyric-display-on-minibuffer nil)
+ (message "Disable lyric on minibufer."))
+ (setq emms-lyric-display-on-minibuffer t)
+ (message "Enable lyric on minibufer.")))
+
+(defun emms-lyric-toggle-display-on-modeline ()
+ "Toggle display lyric on modeline."
+ (interactive)
+ (if emms-lyric-display-on-modeline
+ (progn
+ (setq emms-lyric-display-on-modeline nil
+ emms-lyric-mode-line-string "")
+ (message "Disable lyric on modeline."))
+ (setq emms-lyric-display-on-modeline t)
+ (message "Enable lyric on modeline.")))
+
+(defun emms-lyric-set-timer ()
+ "Set timers for displaying lyrics."
+ (setq emms-lyric-timers
+ (mapcar
+ '(lambda (arg)
+ (let ((time (- (car arg) emms-lyric-elapsed-time))
+ (lyric (cadr arg)))
+ (when (>= time 0)
+ (run-at-time (format "%d sec" time)
+ nil
+ 'emms-lyric-display
+ lyric))))
+ emms-lyric-alist)))
+
+(defun emms-lyric-mode-line ()
+ "Add lyric to the mode line."
+ (unless (member 'emms-lyric-mode-line-string
+ global-mode-string)
+ (setq global-mode-string
+ (append global-mode-string
+ '(emms-lyric-mode-line-string)))))
+
+(defun emms-lyric-display (lyric)
+ "Display lyric.
+
+LYRIC is current playing lyric.
+
+See `emms-lyric-display-on-modeline' and
+`emms-lyric-display-on-minibuffer' on how to config where to
+display."
+ (when (and emms-lyric-display-p
+ emms-lyric-alist)
+ (when emms-lyric-display-on-modeline
+ (emms-lyric-mode-line)
+ (setq emms-lyric-mode-line-string
+ (format emms-lyric-display-format lyric))
+ (force-mode-line-update))
+ (when emms-lyric-display-on-minibuffer
+ (message lyric))))
+
+(defun emms-lyric-find-lyric (file)
+ "Use `emms-source-file-gnu-find' to find lrc FILE. You should specify
+a valid `emms-lyric-dir'."
+ (unless (string= emms-lyric-dir "")
+ ;; If find two or more lyric files, only return the first one. Good
+ ;; luck! :-)
+ (car (split-string
+ (shell-command-to-string
+ (concat emms-source-file-gnu-find " "
+ emms-lyric-dir " -name "
+ "'" ; wrap up whitespaces
+ (replace-regexp-in-string
+ "'" "*" ; FIX ME, '->\'
+ (file-name-nondirectory file))
+ "'"))
+ "\n"))))
+
+;;; emms-lyric-mode
+
+(defun emms-lyric-insert-time ()
+ "Insert lyric time in the form: [01:23.21], then goto the
+beginning of next line."
+ (interactive)
+ (let* ((total (+ (time-to-seconds
+ (time-subtract (current-time)
+ emms-lyric-start-time))
+ emms-lyric-elapsed-time))
+ (min (/ (* (floor (/ total 60)) 100) 100))
+ (sec (/ (floor (* (rem* total 60) 100)) 100.0)))
+ (insert (replace-regexp-in-string
+ " " "0" (format "[%2d:%2d]" min sec))))
+ (emms-lyric-next-line))
+
+(defun emms-lyric-next-line ()
+ "Goto the beginning of next line."
+ (interactive)
+ (forward-line 1))
+
+(defun emms-lyric-previous-line ()
+ "Goto the beginning of previous line."
+ (interactive)
+ (forward-line -1))
+
+(defvar emms-lyric-mode-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map "p" 'emms-lyric-previous-line)
+ (define-key map "n" 'emms-lyric-next-line)
+ (define-key map "i" 'emms-lyric-insert-time)
+ map)
+ "Keymap for `emms-lyric-mode'.")
+
+(defvar emms-lyric-mode-hook nil
+ "Normal hook run when entering Emms Lyric mode.")
+
+(define-derived-mode emms-lyric-mode nil "Emms Lyric"
+ "Major mode for creating lyric files.
+\\{emms-lyric-mode-map}"
+ (run-hooks 'emms-lyric-mode-hook))
+
+
+(provide 'emms-lyric)
+
+;;; emms-lyric.el ends here
diff --git a/emms-maint.el b/emms-maint.el
new file mode 100644
index 0000000..f68f6bd
--- /dev/null
+++ b/emms-maint.el
@@ -0,0 +1 @@
+(add-to-list 'load-path ".")
diff --git a/emms-mode-line-icon.el b/emms-mode-line-icon.el
new file mode 100644
index 0000000..28bdc12
--- /dev/null
+++ b/emms-mode-line-icon.el
@@ -0,0 +1,77 @@
+;; emms-mode-line-icon.el --- show an icon in the Emacs mode-line
+
+;; Copyright (C) 2005 Daniel Brockman <daniel@brockman.se>
+;;
+;; Version: 1.1
+;; Keywords: emms
+;; Author: Daniel Brockman <daniel@brockman.se>
+;; Maintainer: Lucas Bonnet <lucas@rincevent.net>
+
+;; This program is free software; you can redistribute it and/or
+;; modify it under the terms of the GNU General Public License
+;; as published by the Free Software Foundation; either version 2
+;; of the License, or (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+;; 02110-1301 USA
+
+;; Commentary:
+
+;; This EMMS extension shows an icon in the mode-line next to the
+;; info-tag.
+
+;; Code:
+
+
+(defvar emms-mode-line-icon-color "black"
+ "Color of the little icon displayed in the mode-line.")
+
+(defvar emms-mode-line-icon-before-format ""
+ "String to put before the icon, in the mode-line.
+For example, if you want to have something like :
+[ <icon> Foo - The Foo Song ]
+You should set it to \"[\", and set emms-mode-line-format to \"%s ]\"")
+
+(setq emms-mode-line-icon-image-cache
+ `(image :type xpm :ascent center :data ,(concat "/* XPM */
+static char *note[] = {
+/* width height num_colors chars_per_pixel */
+\" 10 11 2 1\",
+/* colors */
+\". c " emms-mode-line-icon-color "\",
+\"# c None s None\",
+/* pixels */
+\"###...####\",
+\"###.#...##\",
+\"###.###...\",
+\"###.#####.\",
+\"###.#####.\",
+\"#...#####.\",
+\"....#####.\",
+\"#..######.\",
+\"#######...\",
+\"######....\",
+\"#######..#\"};")))
+
+
+(defun emms-mode-line-icon-function ()
+ (concat " "
+ emms-mode-line-icon-before-format
+ (propertize "NP:" 'display emms-mode-line-icon-image-cache)
+ (format emms-mode-line-format (emms-info-file-info-song-artist
+ (emms-playlist-current-track)))))
+
+(setq emms-mode-line-mode-line-function 'emms-mode-line-icon-function)
+
+;; This is needed for text properties to work in the mode line.
+(put 'emms-mode-line-string 'risky-local-variable t)
+
+(provide 'emms-mode-line-icon)
+;;; emms-mode-line-icone.el ends here
diff --git a/emms-mode-line.el b/emms-mode-line.el
new file mode 100644
index 0000000..87c0f9d
--- /dev/null
+++ b/emms-mode-line.el
@@ -0,0 +1,124 @@
+;;; emms-mode-line.el --- Mode-Line and titlebar infos for emms
+
+;; Copyright (C) 2004 Free Software Foundation, Inc.
+
+;; Author: Mario Domgrgen <kanaldrache@gmx.de>
+;; Keywords: multimedia
+
+;; This file is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; This file is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING. If not, write to the
+;; Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+;; Boston, MA 02110-1301 USA
+
+;;; Commentary:
+;;
+;; To activate put simply the following line in your Emacs:
+;;
+;; (require 'emms-mode-line)
+;; (emms-mode-line 1)
+
+;;; Code:
+
+(require 'emms)
+
+(defgroup emms-mode-line nil
+ "Showing information on mode-line and titlebar"
+ :prefix "emms-mode-line-"
+ :group 'emms)
+
+(defcustom emms-mode-line-mode-line-function 'emms-mode-line-playlist-current
+ "Function for showing infos in mode-line or nil if don't want to."
+ :type '(choice (const :tag "Don't show info on mode-line" nil) function)
+ :group 'emms-mode-line)
+
+(defcustom emms-mode-line-titlebar-function nil
+ "Function for showing infos in titlebar or nil if you don't want to."
+ :type '(choice (const :tag "Don't show info on titlebar" nil) function)
+ :group 'emms-mode-line)
+
+(defcustom emms-mode-line-format " [ %s ] "
+ "String used for displaying the current track in mode-line and titlebar."
+ :type 'string
+ :group 'emms)
+
+(defun emms-mode-line-playlist-current ()
+ "Format the currently playing song"
+ (format emms-mode-line-format (emms-playlist-current)))
+
+(defvar emms-mode-line-initial-titlebar frame-title-format)
+
+(defun emms-mode-line (arg)
+ "Turn on `emms-mode-line' if ARG is positive, off otherwise."
+ (interactive "p")
+ (or global-mode-string (setq global-mode-string '("")))
+ (if (and arg (> arg 0))
+ (progn
+ (add-hook 'emms-playlist-current-track-changed-hook
+ 'emms-mode-line-alter)
+ (add-hook 'emms-player-finished-hook 'emms-mode-line-blank)
+ (add-hook 'emms-player-stopped-hook 'emms-mode-line-blank)
+ (add-hook 'emms-player-started-hook 'emms-mode-line-alter)
+ (when (and emms-mode-line-mode-line-function
+ (not (member 'emms-mode-line-string global-mode-string)))
+ (setq global-mode-string
+ (append global-mode-string
+ '(emms-mode-line-string))))
+ (when emms-player-playing-p (emms-mode-line-alter)))
+ (remove-hook 'emms-playlist-current-track-changed-hook
+ 'emms-mode-line-alter)
+ (remove-hook 'emms-player-finished-hook 'emms-mode-line-blank)
+ (remove-hook 'emms-player-stopped-hook 'emms-mode-line-blank)
+ (remove-hook 'emms-player-started-hook 'emms-mode-line-alter)
+ (emms-mode-line-restore-titlebar)
+ (emms-mode-line-restore-mode-line)))
+
+(defun emms-mode-line-alter ()
+ "Alter mode-line/titlebar."
+ (emms-mode-line-alter-mode-line)
+ (emms-mode-line-alter-titlebar))
+
+(defun emms-mode-line-alter-mode-line ()
+ "Update the mode-line with song info."
+ (when emms-mode-line-mode-line-function
+ (setq emms-mode-line-string
+ (funcall emms-mode-line-mode-line-function))
+ (force-mode-line-update)))
+
+(defun emms-mode-line-alter-titlebar ()
+ "Update the titlebar with song info."
+ (when emms-mode-line-titlebar-function
+ (setq frame-title-format
+ (list "" emms-mode-line-initial-titlebar (funcall emms-mode-line-titlebar-function)))))
+
+
+(defun emms-mode-line-blank ()
+ "Blank mode-line and titlebar but not quit `emms-mode-line'."
+ (setq emms-mode-line-string nil)
+ (force-mode-line-update)
+ (emms-mode-line-restore-titlebar))
+
+(defun emms-mode-line-restore-mode-line ()
+ "Restore the mode-line."
+ (when emms-mode-line-mode-line-function
+ (setq global-mode-string
+ (remove 'emms-mode-line-string global-mode-string))
+ (force-mode-line-update)))
+
+(defun emms-mode-line-restore-titlebar ()
+ "Restore the mode-line."
+ (when emms-mode-line-titlebar-function
+ (setq frame-title-format
+ (list emms-mode-line-initial-titlebar))))
+
+(provide 'emms-mode-line)
+;;; emms-mode-line.el ends here
diff --git a/emms-mpd.el b/emms-mpd.el
new file mode 100644
index 0000000..4951d93
--- /dev/null
+++ b/emms-mpd.el
@@ -0,0 +1,249 @@
+;; emms-mpd.el --- MusicPD support for EMMS
+
+;; Copyright (C) 2005 Free Software Foundation, Inc.
+
+;; Author: Michael Olson (mwolson AT gnu DOT org)
+
+;; This file is not part of GNU Emacs.
+
+;; This is free software; you can redistribute it and/or modify it under
+;; the terms of the GNU General Public License as published by the Free
+;; Software Foundation; either version 2, or (at your option) any later
+;; version.
+;;
+;; This is distributed in the hope that it will be useful, but WITHOUT
+;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+;; FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+;; for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING. If not, write to the
+;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+;; Boston, MA 02110-1301, USA.
+
+;;; Commentary:
+
+;;; Benefits
+
+;; MusicPD features crossfade, very little skipping, many clients,
+;; many supported output formats, and good abstraction of client and
+;; server, among other things.
+
+;;; MusicPD setup
+
+;; You'll need to have both mpc and mpd installed. The website is at
+;; http://musicpd.org/. Debian packages are available. I recommend
+;; getting the latest development version; see
+;; http://mpd.wikicities.com/wiki/Subversion for nightly Debian
+;; packages and the svn repo.
+;;
+;; Copy the example configuration for mpd into ~/.mpdconf and edit it
+;; to your needs. I using your top level music directory for
+;; music_directory. If your playlists use absolute file names, be
+;; certain that music_directory has the leading directory part.
+;;
+;; Before you try to play anything, but after setting up the above,
+;; run `mkdir ~/.mpd && mpd --create-db' to create MusicPD's track
+;; database.
+;;
+;; Check to see if mpd is running. It must be running as a daemon for
+;; you to be able to play anything. Launch it by executing "mpd". It
+;; can be killed later with "mpd --kill" (or just "killall mpd" if
+;; you're not using the latest development version).
+
+;;; EMMS setup
+
+;; emms-pause and emms-seek are evil functions. You should hack them
+;; so that they accept emms-player-mpd.
+
+;; Add "emms-player-mpd" to the top of `emms-player-list'. If you use
+;; absolute file names in your m3u playlists, make sure you set
+;; `emms-player-mpd-music-directory' to the value of "music_directory"
+;; from your MusicPD config.
+
+;;; TODO
+
+;; If you try to play individual songs, the tracks will not advance.
+;; I recommend playing playlists instead. This should be addresed
+;; eventually, though, perhaps with a connection to the mpd process.
+;;
+;; Instead of relying on mpc, we should get MPD_HOST and MPD_PORT from
+;; the environment (or specify them as options), open a network
+;; connection, send the command you want, then "\nclose" to close the
+;; connection. Alternatively, the process can be left open (omitting
+;; "close" but keeping "\n") for more commands. Apparently the
+;; process closes automatically after a while though ... wonder how
+;; other clients handle that.
+;;
+;; It might also be good to "sync" the mpd playlist with the emms one.
+;; Currently we just clear the mpd playlist, add the track, and play,
+;; for each track. Not the best approach, unless your track is a
+;; playlist in itself, in which case all tracks from the playlist are
+;; added immediately after clearing the mpd playlist.
+
+(require 'emms-player-simple)
+(require 'emms-player-extensions)
+
+(defvar emms-mpd-paused-p nil
+ "Whether the sound is paused.")
+
+(defun emms-mpd-get-supported-regexp ()
+ "Returns a regexp of file extensions that MusicPD supports,
+or nil if we cannot figure it out."
+ (let ((out (split-string (shell-command-to-string "mpd --version")
+ "\n"))
+ supported)
+ ;; Get last non-empty line
+ (while (car out)
+ (when (not (string= (car out) ""))
+ (setq supported (car out)))
+ (setq out (cdr out)))
+ ;; Create regexp
+ (when (and (stringp supported)
+ (not (string= supported "")))
+ (concat "\\`http://\\|\\.\\(m3u\\|pls\\|"
+ (mapconcat 'identity (delq nil (split-string supported))
+ "\\|")
+ "\\)\\'"))))
+
+(defvar emms-mpd-supported-regexp
+ ;; Use a sane default, just in case
+ (or (emms-mpd-get-supported-regexp)
+ "\\.\\(m3u\\|ogg\\|flac\\|mp3\\|wav\\|mod\\|aac\\)\\'")
+ "Formats supported by MusicPD Client.")
+
+(defcustom emms-player-mpd-music-directory nil
+ "The value of 'music_directory' in your MusicPD configuration file.
+You need this if your playlists use absolute file names, otherwise
+leave it set to nil."
+ ;; The :format part ensures that entering directories happens on the
+ ;; next line, where there is more space to work with
+ :type '(choice :format "%{%t%}:\n %[Value Menu%] %v"
+ (const nil)
+ directory)
+ :group 'emms-player-mpd)
+
+(define-emms-simple-player mpd '(file url playlist)
+ emms-mpd-supported-regexp "mpc")
+
+(emms-player-set emms-player-mpd
+ 'pause
+ 'emms-player-mpd-pause)
+
+(emms-player-set emms-player-mpd
+ 'seek
+ 'emms-player-mpd-seek)
+
+(defun emms-player-mpd-clear ()
+ "Clear the playlist."
+ (shell-command-to-string (concat emms-player-mpd-command-name " clear")))
+
+(defun emms-player-mpd-add (file)
+ "Add FILE to the current MusicPD playlist.
+If we succeeded in adding the file, return the string from the
+process, nil otherwise."
+ (when (and emms-player-mpd-music-directory
+ (not (string-match "\\`http://" file)))
+ (setq file (file-relative-name file emms-player-mpd-music-directory)))
+ (let ((output (shell-command-to-string (concat emms-player-mpd-command-name
+ " add " file))))
+ (when (and output (not (string= output "")))
+ output)))
+
+(defun emms-player-mpd-load (playlist)
+ "Load contents of PLAYLIST into MusicPD by adding each line.
+This handles both m3u and pls type playlists."
+ ;; This allows us to keep playlists anywhere and not worry about
+ ;; having to mangle their names. Also, mpd can't handle pls
+ ;; playlists by itself.
+ (let ((pls-p (if (string-match "\\.pls\\'" playlist)
+ t
+ nil)))
+ (mapc #'(lambda (file)
+ (when pls-p
+ (if (string-match "\\`File[0-9]*=\\(.*\\)\\'" file)
+ (setq file (match-string 1 file))
+ (setq file "")))
+ (unless (or (string= file "")
+ (string-match "\\`#" file))
+ (emms-player-mpd-add file)))
+ (split-string (with-temp-buffer
+ (insert-file-contents playlist)
+ (buffer-string))
+ "\n"))))
+
+(defun emms-player-mpd-play ()
+ "Play whatever is in the current MusicPD playlist."
+ (shell-command-to-string (concat emms-player-mpd-command-name " play"))
+ (setq emms-mpd-paused-p nil))
+
+(defun emms-player-mpd-start (track)
+ "Starts a process playing TRACK."
+ (interactive)
+ (emms-player-mpd-clear)
+ (let ((name (emms-track-get track 'name)))
+ ;; If it's a playlist, we have to `load' rather than `add' it
+ (if (string-match "\\.\\(m3u\\|pls\\)\\'" name)
+ (emms-player-mpd-load name)
+ (emms-player-mpd-add name)))
+ ;; Now that we've added/loaded the file/playlist, play it
+ (emms-player-mpd-play))
+
+(defun emms-player-mpd-stop ()
+ "Stop the currently playing song."
+ (interactive)
+ (shell-command-to-string (concat emms-player-mpd-command-name " stop"))
+ (setq emms-mpd-paused-p nil))
+
+(defun emms-player-mpd-pause ()
+ "Pause the currently playing song."
+ (interactive)
+ (if emms-mpd-paused-p
+ (progn
+ (shell-command-to-string (concat emms-player-mpd-command-name " play"))
+ (setq emms-mpd-paused-p nil))
+ (shell-command-to-string (concat emms-player-mpd-command-name " pause"))
+ (setq emms-mpd-paused-p t)))
+
+(defun emms-player-mpd-seek (sec)
+ "Seek backward or forward by SEC seconds, depending on sign of SEC."
+ (interactive)
+ (shell-command-to-string (concat emms-player-mpd-command-name
+ (format " seek %s%d"
+ (if (> sec 0) "+" "")
+ sec)))
+ ;; Taking our cue from emms-player-mplayer-seek
+ (when (fboundp 'emms-lyric-seek)
+ (emms-lyric-seek sec)))
+
+;; Not currently used by the API (to my knowledge), but I make use of
+;; these to advance my playlists.
+(defun emms-player-mpd-next ()
+ "Move forward by one track in MusicPD's internal playlist."
+ (interactive)
+ (shell-command-to-string (concat emms-player-mpd-command-name " next"))
+ (setq emms-mpd-paused-p nil))
+
+(defun emms-player-mpd-previous ()
+ "Move backward by one track in MusicPD's internal playlist."
+ (interactive)
+ (shell-command-to-string (concat emms-player-mpd-command-name " previous"))
+ (setq emms-mpd-paused-p nil))
+
+;; A "Now Playing" function -- I don't know how to integrate this into
+;; emms-show.
+(defun emms-player-mpd-show ()
+ "Show the currently-playing track. If nothing is playing, return nil."
+ (interactive)
+ (let ((np (car (split-string
+ (shell-command-to-string
+ (concat emms-player-mpd-command-name))
+ "\n"))))
+ (when (and np
+ (not (string= np ""))
+ (not (string-match "\\`\\(volume\\|error\\):" np)))
+ np)))
+
+(provide 'emms-mpd)
+
+;;; emms-mpd.el ends here
diff --git a/emms-pbi-filter.el b/emms-pbi-filter.el
new file mode 100644
index 0000000..5ec8e6c
--- /dev/null
+++ b/emms-pbi-filter.el
@@ -0,0 +1,66 @@
+;;; emms-pbi-filter.el --- Filtering functions for the PBI
+
+;; Copyright (C) 2004 Free Software Foundation, Inc.
+
+;; Author: Ulrik Jensen <terryp@daimi.au.dk>
+;; Keywords:
+
+;; This file is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; This file is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING. If not, write to the
+;; Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+;; Boston, MA 02110-1301 USA
+
+;;; Commentary:
+
+;; This file provides a way to filter the playlist to only show
+;; certain parts of a big playlist, and only play after those.
+
+;;; Code:
+
+(require 'emms-pbi)
+
+(defvar emms-pbi-not-filtered nil
+ "A list of trackindexes in `emms-playlist' that aren't filtered
+out.")
+
+(defun emms-pbi-filter-region (beg end)
+ "Hide the lines spanning the region from BEG to END."
+ (interactive "r")
+ (save-excursion
+ (set-buffer emms-pbi-playlist-buffer-name)
+ (goto-char beg)
+ (let ((startofregion (point-at-bol)))
+ (goto-char end)
+ (let ((endofregion (point-at-eol)))
+ (let ((hideoverlay (make-overlay startofregion endofregion))
+ (newlineoverlay (make-overlay (- startofregion 1) startofregion))
+ (intangibleoverlay (make-overlay (- startofregion 1) (+ endofregion 1))))
+ (overlay-put newlineoverlay 'after-string "\n")
+ (overlay-put newlineoverlay 'invisible t)
+ (overlay-put newlineoverlay 'emms-id 'emms-pbi-filter-overlay)
+ (overlay-put intangibleoverlay 'intangible t)
+ (overlay-put intangibleoverlay 'emms-id 'emms-pbi-filter-overlay)
+ (overlay-put hideoverlay 'emms-id 'emms-pbi-filter-overlay)
+ (overlay-put hideoverlay 'invisible t))))))
+
+(defun emms-pbi-unfilter-region (beg end)
+ "Show all hidden lines between BEG and END."
+ (interactive "r")
+ (let ((overlays (overlays-in beg end)))
+ (while overlays
+ (when (equal (overlay-get (car overlays) 'emms-id) 'emms-pbi-filter-overlay)
+ (delete-overlay (car overlays)))
+ (setq overlays (cdr overlays)))))
+
+(provide 'emms-pbi-filter)
+;;; emms-pbi-filter.el ends here
diff --git a/emms-pbi-mark.el b/emms-pbi-mark.el
new file mode 100644
index 0000000..ec633b1
--- /dev/null
+++ b/emms-pbi-mark.el
@@ -0,0 +1,167 @@
+;;; emms-pbi-mark.el --- Mark-functions for the EMMS playlist
+
+;; Copyright (C) 2003 Free Software Foundation, Inc.
+
+;; Author: Ulrik Jensen <terryp@daimi.au.dk>
+;; Keywords:
+
+;; This file is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; This file is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING. If not, write to the
+;; Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+;; Boston, MA 02110-1301 USA
+
+;;; Commentary:
+
+;; This file provides a mark-facility for the EMMS playlist-buffer
+;; interface.
+
+;; To use it, add:
+
+;; (require 'emms-pbi-mark)
+;; (emms-pbi-mark 1)
+
+;; To your ~/.emacs.
+
+;;; Code:
+
+;; $Revision: 1.7 $
+;; $Id: emms-pbi-mark.el,v 1.7 2005/07/09 11:56:00 forcer Exp $
+
+;; Customs
+(defgroup emms-pbi-mark nil
+ "Marking utilities for the EMMS playlist"
+ :prefix "emms-pbi-mark-"
+ :group 'emms-pbi)
+
+(defface emms-pbi-mark-marked-face
+ '((((class color))
+ (:foreground "green" :weight bold)))
+ "Face used for marked files"
+ :group 'emms-pbi-mark)
+
+;; variables
+(defvar emms-pbi-mark-indices nil
+ "A list containing the indices of the marked elements.")
+
+;; Entry
+(defun emms-pbi-mark (arg)
+ "Activate mark-mode for the EMMS-playlist."
+ (interactive "p")
+ (if (and arg (< 0 arg))
+ (progn
+ (add-hook 'emms-pbi-current-line-face-changed-hook 'emms-pbi-add-mark-face)
+ (add-hook 'emms-playlist-changed-hook 'emms-pbi-mark-invalidate-marks)
+ (add-hook 'emms-pbi-mode-hook 'emms-pbi-mark-add-keybindings))
+ (remove-hook 'emms-pbi-current-line-face-changed-hook 'emms-pbi-add-mark-face)
+ (remove-hook 'emms-pbi-mode-hook 'emms-pbi-mark-add-keybindings)
+ (remove-hook 'emms-playlist-changed-hook 'emms-pbi-mark-invalidate-marks)))
+
+(defun emms-pbi-mark-invalidate-marks ()
+ "Force a reset of marked tracks."
+ (when (get-buffer emms-pbi-playlist-buffer-name)
+ (emms-pbi-mark-clear-marked))
+ (setq emms-pbi-mark-indices nil))
+
+(defun emms-pbi-mark-add-keybindings ()
+ "Adds keybindings for the mark-functions to a *Playlist* buffer."
+ (local-set-key (kbd "m") 'emms-pbi-mark-mark-file)
+ (local-set-key (kbd "M-u") 'emms-pbi-mark-clear-marked)
+ (local-set-key (kbd "u") 'emms-pbi-mark-unmark-file))
+
+(defun emms-pbi-mark-current-line-marked-p ()
+ "Return non-nil if the current line is marked, nil otherwise."
+ (let ((idx (emms-pbi-return-current-line-index)))
+ (if (not idx)
+ nil
+ (member idx emms-pbi-mark-indices))))
+
+(defun emms-pbi-mark-current-line-mark ()
+ "Mark the current line."
+ (let ((idx (emms-pbi-return-current-line-index)))
+ (when (emms-pbi-valid-index-p idx)
+ (add-to-list 'emms-pbi-mark-indices idx)
+ (emms-pbi-add-mark-face))))
+
+(defun emms-pbi-mark-current-line-unmark ()
+ "Unmark the current line."
+ (let ((idx (emms-pbi-return-current-line-index)))
+ (when (and (emms-pbi-valid-index-p idx)
+ (emms-pbi-mark-current-line-marked-p))
+ (setq emms-pbi-mark-indices (remove idx emms-pbi-mark-indices))
+ (emms-pbi-remove-mark-face))))
+
+(defun emms-pbi-add-mark-face ()
+ "Add a face to the current line, if it is marked."
+ (when (emms-pbi-mark-current-line-marked-p)
+ (put-text-property (point-at-bol) (point-at-eol) 'face 'emms-pbi-mark-marked-face)))
+
+(defun emms-pbi-remove-mark-face ()
+ "Remove the marked-face from the current-line, if it's no longer
+marked."
+ (when (not (emms-pbi-mark-current-line-marked-p))
+ (let ((inhibit-read-only t))
+ (remove-text-properties (point-at-bol) (point-at-eol) '(face))
+ (emms-pbi-add-properties-current-line))))
+
+;; Programming interface functions
+(defun emms-pbi-mark-get-marked ()
+ "Return a list of tracks marked in the playlist"
+ (let ((indices emms-pbi-mark-indices)
+ (tracks nil))
+ (while indices
+ (let ((idx (car indices)))
+ (when (emms-pbi-valid-index-p idx)
+ (add-to-list 'tracks (emms-playlist-get-track idx))))
+ (setq indices (cdr indices)))
+ tracks))
+
+;; User Interface functions
+(defun emms-pbi-mark-clear-marked ()
+ "Clear all marks from the playlist-buffer"
+ (interactive)
+ (save-excursion
+ (set-buffer (get-buffer emms-pbi-playlist-buffer-name))
+ (let ((indices emms-pbi-mark-indices)
+ (tracks nil))
+ (while emms-pbi-mark-indices
+ (let ((idx (car emms-pbi-mark-indices)))
+ (when (emms-pbi-valid-index-p idx)
+ (goto-line (1+ idx))
+ (emms-pbi-mark-current-line-unmark)))))))
+
+(defun emms-pbi-mark-mark-file (arg)
+ "Marks the current line in the playlist.
+
+With prefix argument, mark the following ARG lines."
+ (interactive "p")
+ (let ((marksleft arg)
+ (inhibit-read-only t))
+ (while (> marksleft 0)
+ (emms-pbi-mark-current-line-mark)
+ (forward-line 1)
+ (setq marksleft (1- marksleft)))))
+
+(defun emms-pbi-mark-unmark-file (arg)
+ "Removes a mark from the current line in the playlist.
+
+With prefix argument, unmark the following ARG lines."
+ (interactive "p")
+ (let ((marksleft arg)
+ (inhibit-read-only t))
+ (while (> marksleft 0)
+ (emms-pbi-mark-current-line-unmark)
+ (forward-line 1)
+ (setq marksleft (1- marksleft)))))
+
+(provide 'emms-pbi-mark)
+;;; emms-pbi-mark.el ends here
diff --git a/emms-pbi-popup.el b/emms-pbi-popup.el
new file mode 100644
index 0000000..53ea4b1
--- /dev/null
+++ b/emms-pbi-popup.el
@@ -0,0 +1,128 @@
+;;; emms-pbi-popup.el --- Playlist-popup functionality for EMMS
+
+;; Copyright (C) 2003 Free Software Foundation, Inc.
+
+;; Author: Ulrik Jensen <terryp@daimi.au.dk>
+;; Keywords:
+
+;; This file is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; This file is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING. If not, write to the
+;; Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+;; Boston, MA 02110-1301 USA
+
+;;; Commentary:
+
+;;
+
+;;; Code:
+(require 'emms-pbi)
+
+(defvar emms-pbi-popup-version "0.2 $Revision: 1.7 $"
+ "EMMS pbi popup version string.")
+;; $Id: emms-pbi-popup.el,v 1.7 2005/07/09 11:56:00 forcer Exp $
+
+(defgroup emms-pbi-popup nil
+ "*Module for popping up the playlist in a keystroke."
+ :group 'emms-pbi
+ :prefix "emms-pbi-popup-")
+
+(defcustom emms-pbi-popup-default-width (+ (or emms-pbi-playlist-entry-max-length 36) 4)
+ "*The default width of the window to popup the playlist in.
+
+This defaults to `emms-pbi-playlist-entry-max-length' + 4, or 40 if
+`emms-pbi-playlist-entry-max-length' is nil."
+ :group 'emms-pbi-popup
+ :type 'number)
+
+(defcustom emms-pbi-popup-default-side-left nil
+ "*Boolean determining whether to popup in the left-side as a
+default. If nil, the popup will appear in the right side."
+ :type 'boolean
+ :group 'emms-pbi-popup)
+
+(defvar emms-pbi-popup-old-conf nil
+ "The window-configuration when popping up the playlist.")
+
+(defun emms-pbi-popup-forget-conf ()
+ "Forget the previously saved configuration, and make the changes
+final."
+ (setq emms-pbi-popup-old-conf nil)
+ ;; Remove the special bindings
+ (emms-pbi-popup-revert)
+ ;; Remove this function again, it will get addded when a new
+ ;; configuration is saved anyway
+ (remove-hook 'window-configuration-change-hook 'emms-pbi-popup-forget-conf))
+
+(defun emms-pbi-popup-revert ()
+ "Revert to the window-configuration from before if there is one,
+otherwise just remove the special bindings from the playlist."
+ (interactive)
+ (remove-hook 'emms-pbi-manually-change-song-hook 'emms-pbi-popup-revert)
+ (let ((playlistbuffer (get-buffer emms-pbi-playlist-buffer-name)))
+ (when playlistbuffer
+ (save-excursion
+ (set-buffer playlistbuffer)
+ (local-unset-key (kbd "q"))
+ (local-unset-key (kbd "TAB")))))
+ (when emms-pbi-popup-old-conf
+ (set-window-configuration emms-pbi-popup-old-conf)))
+
+;; Entry-point
+(defun emms-pbi-popup-playlist (&optional popup-left popup-width )
+ "Pops up the playlist temporarily, for selecting a new song.
+
+If POPUP-LEFT is non-nil, the window will appear in the left side of
+the current window, otherwise it will appear in the right side.
+
+POPUP-WIDTH is the width of the new frame, defaulting to
+`emms-pbi-popup-default-width'."
+ (interactive)
+ (setq popup-width (or popup-width emms-pbi-popup-default-width)
+ popup-left (or popup-left emms-pbi-popup-default-side-left))
+ ;; Split the current screen, and make the playlist popup
+ (let ((new-window-width (- (window-width) popup-width)))
+ (if (not (> new-window-width 0))
+ ;; consider just opening the playlist here instead of arguing
+ ;; semantics with the user?
+ (error "Current window not wide enough to popup playlist!")
+ ;; Negative value to popup in the left side
+ (when popup-left
+ (setq new-window-width (- new-window-width)))
+ ;; Make sure EMMS is actually playing before continuing
+ (if (or (not (emms-playlist-get-playlist)) (= (length (emms-playlist-get-playlist)) 0))
+ ;; we haven't got a playlist, exit.
+ (error "Can't popup playlist-buffer until a playlist has been loaded!")
+ ;; if
+ ;; Save the current window-configuration
+ (setq emms-pbi-popup-old-conf (current-window-configuration))
+ ;; Split and select the playlist
+ (let ((buffer-on-the-right
+ (split-window-horizontally new-window-width)))
+ (unless popup-left
+ (select-window buffer-on-the-right)))
+ (unless (get-buffer emms-pbi-playlist-buffer-name)
+ ;; No playlist-buffer yet, create it.
+ (emms-pbi 1))
+ (switch-to-buffer emms-pbi-playlist-buffer-name t)
+ ;; Now, modify the playlist functionality to revert to the
+ ;; window-configuration from before when a song is selected
+ (add-hook 'emms-pbi-manually-change-song-hook 'emms-pbi-popup-revert)
+ (local-set-key (kbd "TAB") 'emms-pbi-popup-revert)
+ (local-set-key (kbd "q") 'delete-window)
+ ;; Also, forget about the whole thing if the user does something
+ ;; to the window-configuration
+ (add-hook 'window-configuration-change-hook 'emms-pbi-popup-forget-conf)))))
+
+
+(provide 'emms-pbi-popup)
+;;; emms-pbi-popup.el ends here
diff --git a/emms-pbi.el b/emms-pbi.el
new file mode 100644
index 0000000..6480ce8
--- /dev/null
+++ b/emms-pbi.el
@@ -0,0 +1,473 @@
+;;; pbi.el --- Playlist-buffer interface for emms.el
+
+;; Copyright (C) 2003 Free Software Foundation, Inc.
+
+;; Author: Ulrik Jensen <terryp@daimi.au.dk>
+;; Keywords:
+
+;; This file is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; This file is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING. If not, write to the
+;; Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+;; Boston, MA 02110-1301 USA
+
+;;; Commentary:
+
+;; This module provices a playlist-buffer interface (pbi) to EMMS.
+
+;; To use it, just add the following to your emms-configuration
+;; (.emacs, for example):
+
+;; (require 'emms-pbi)
+
+;; If you want the playlist to be generated automagically when you
+;; start playing your music, use the following:
+
+;; (emms-pbi 1)
+
+;; Another common usage, is to just load the playlist when you need
+;; it. This can be achieved by starting emms, and then typing M-x
+;; emms-pbi RET.
+
+;; The default look of the playlist depends on the value of
+;; `emms-track-description-function'. This function takes a
+;; track-value and returns a string, that will then be formatted
+;; further, and inserted into the playlist-buffer. If you want a
+;; different function to handle the base-description, but you only
+;; want that function for emms-pbi, you can override
+;; `emms-pbi-track-description-function', which will fall back to
+;; `emms-track-description-function' when nil
+
+;; You'll probably also want to customize the faces, in which case,
+;; you do M-x customize-group RET emms-pbi RET and tweak.
+
+;; To get id3-tags, and ogg-info, you should look at
+;; emms-info.el. This file provides amongst other things, functions
+;; suitable as values of `emms-track-description-function' (or if you
+;; only want the info in emms-pbi, values of
+;; `emms-pbi-track-description-function').
+
+;; If you use a big playlist, and info, you probably don't want info
+;; to load everything right at once. This can be accomplished through
+;; the `emms-info-later-do'-module, which will gradually load the
+;; playlist. A note on how to make it work with the PBI as well, is
+;; included in that file (and also a part of emms-default.el,
+;; currently the 'cvs-setup)
+
+;; Linenumbering (Changed!)
+;;
+;; Prior versions of emms-pbi had their own linenumbering
+;; functions. But these functions were either error prone or damn
+;; slow. And besides there was already a emacs mode that does exactly
+;; the same: setnu.el So we remove the linenumbering functions in
+;; favour of setnu. You can get setnu from
+;; http://www.wonderworks.com/download/setnu.el. To get linenumbers
+;; just put the following code in your ~/.emacs and put setnu.el
+;; somewhere on your loadpath:
+;;
+;; (require 'setnu)
+;; (add-hook 'emms-pbi-after-build-hook (lambda () (setnu-mode 1)))
+
+;;; Code:
+
+(require 'emms)
+(require 'cl)
+
+(defvar emms-pbi-version "0.2 $Revision: 1.37 $"
+ "EMMS pbi version string.")
+;; $Id: emms-pbi.el,v 1.37 2005/07/09 19:14:57 yonirabkin Exp $
+
+;; Customizations
+
+(defgroup emms-pbi nil
+ "*A playlist-buffer user-interface for EMMS."
+ :group 'emms
+ :prefix "emms-pbi-")
+
+(defcustom emms-pbi-playlist-entry-generate-function 'identity
+ "*The function to call for generating a single item of the
+playlist. This will be called with a string argument FILENAME, and
+should return the text to be inserted in the playlist."
+ :type 'function
+ :group 'emms-pbi)
+
+(defcustom emms-pbi-playlist-entry-max-length nil
+ "*The maximum length of an entry in the playlist. If this is nil,
+the entire string provided by `emms-track-description-function'.
+will be used. Beware, the output of that function is cut off to fit
+the max-length before running
+`emms-pbi-playlist-entry-generate-function'."
+ :type '(restricted-sexp :match-alternatives (integerp 'nil))
+ :group 'emms-pbi)
+
+(defcustom emms-pbi-playlist-buffer-name "*Playlist*"
+ "Name of the buffer to use as a playlist-buffer"
+ :type 'string
+ :group 'emms-pbi)
+
+(defcustom emms-pbi-track-description-function nil
+ "Returns a description for the playlist.
+
+Take track as only argument. If `emms-pbi-track-description-function' is nil,
+`emms-track-description-function' is used instead."
+ :type 'function
+ :group 'emms-pbi)
+
+;; Hooks
+
+(defcustom emms-pbi-after-build-hook nil
+ "Hook that is run after the playlist buffer is built.
+That might be usefull to change the playlist buffer before the
+buffer is set read-only."
+ :type 'hook
+ :group 'emms-pbi)
+
+(defcustom emms-pbi-current-line-face-changed-hook nil
+ "Hook that is called when the face of the current line changes."
+ :type 'hook
+ :group 'emms-pbi)
+
+(defcustom emms-pbi-manually-change-song-hook nil
+ "Hook that is called when the song is manually changed."
+ :type 'hook
+ :group 'emms-pbi)
+
+;; Faces
+(defface emms-pbi-song-face
+ '((((class color) (background light))
+ (:foreground "red"))
+ (((class color) (background dark))
+ (:foreground "red")))
+ "Face used for songs"
+ :group 'emms-pbi)
+
+(defface emms-pbi-current-face
+ '((((class color)(background light))
+ (:foreground "blue" :weight bold))
+ (((class color)(background dark))
+ (:foreground "yellow" :weight bold)))
+ "Face used for the currently played song"
+ :group 'emms-pbi)
+
+;; Variables
+(defvar emms-pbi-suspend-hooks nil
+ "When this variable is t, the hooks updating the playlist stop
+reacting.
+
+If you do something with `emms-playlist', and don't want to regenerate
+the entire playlist-buffer, a good idea is to bind
+`emms-pbi-suspend-hooks' to t when you set `emms-playlist'.")
+
+(defvar emms-pbi-current-overlay nil
+ "Overlay moving with the current track.")
+
+(defvar emms-kill-ring ()
+ "Kill-ring for the playlist buffer.")
+
+(defvar emms-pbi-longest-line 0
+ "The length of the longest line yet inserted.")
+
+;; Entry points
+(defun emms-pbi (arg)
+ "Turn on emms-playlist if prefix argument ARG is a positive integer,
+off otherwise."
+ (interactive "p")
+ (if (and arg (> arg 0))
+ (progn
+ (add-hook 'emms-player-started-hook 'emms-pbi-update-current-face)
+ ;; make sure this is appended!
+ (add-hook 'emms-playlist-changed-hook 'emms-pbi-rebuild-playlist-buffer t)
+ ;; build the playlist, if we have a playlist
+ (if (> (length emms-playlist) 0)
+ (progn
+ (emms-pbi-build-playlist-buffer)
+ (switch-to-buffer emms-pbi-playlist-buffer-name))
+ (message "Empty playlist, won't build playlist-buffer!")))
+ (remove-hook 'emms-player-stopped-hook 'emms-pbi-remove-current-face)
+ (remove-hook 'emms-player-starter-hook 'emms-pbi-add-current-face)
+ (remove-hook 'emms-playlist-changed-hook 'emms-pbi-rebuild-playlist-buffer)))
+
+(defun emms-pbi-shorten-entry-to-max-length (entry)
+ "Cut off an entry-text to make sure it's no longer than
+`emms-pbi-playlist-entry-max-length' characters long."
+ (if (and emms-pbi-playlist-entry-max-length
+ (> (length entry) emms-pbi-playlist-entry-max-length))
+ (substring entry 0 emms-pbi-playlist-entry-max-length)
+ entry))
+
+(defun emms-position-vector (elt vector)
+ "Returns the index of elt in vector"
+ (let ((curidx 0)
+ (residx nil))
+ (while (and (< curidx (length vector)) (eq residx nil))
+ (let ((curelt (aref vector curidx)))
+ (when (equal elt curelt)
+ (setq residx curidx)))
+ (setq curidx (1+ curidx)))
+ residx))
+
+;; This function should probably be phased out, since it depends too
+;; much on emms-info. All uses should be replaced by the function
+;; below:
+(defun emms-pbi-entry-info-updated (track info)
+ "Update the track-entry based on the info"
+ (save-excursion
+ (set-buffer emms-pbi-playlist-buffer-name)
+ (let ((inhibit-read-only t)
+ (pos (emms-position-vector track emms-playlist)))
+ ;; find the entry in the playlist, corresponding to TRACK
+ (when
+ (goto-line (1+ pos))
+ ;; update the text of it - by generating it again simply
+ ;; first, find the index of the entry in the playlist.
+ ;; and save the current properties
+ (delete-region (point-at-bol) (point-at-eol))
+ (emms-pbi-insert-entry (emms-playlist-get-track pos))
+ ;; and update them
+ (emms-pbi-add-properties-current-line)))))
+
+(defun emms-pbi-entry-update-track (track)
+ "Update the playlist entry for TRACK."
+ (let ((trackidx
+ (loop for i from 0 for a across (emms-playlist-get-playlist)
+ if (equal a track) return i)))
+ (when (and trackidx (emms-pbi-valid-index-p trackidx))
+ (emms-pbi-entry-update-idx trackidx))))
+
+(defun emms-pbi-entry-update-idx (trackidx)
+ "Update the playlist entry for the track at index TRACKIDX."
+ (save-excursion
+ (set-buffer emms-pbi-playlist-buffer-name)
+ ;; Find the track
+ (when (emms-pbi-valid-index-p trackidx)
+ (let ((lineidx (1+ trackidx)))
+ (let ((inhibit-read-only t))
+ ;; Erase the line
+ (goto-line lineidx)
+ (delete-region (point-at-bol) (point-at-eol))
+ ;; Insert the track and add properties
+ (emms-pbi-insert-entry (emms-playlist-get-track trackidx))
+ (emms-pbi-add-properties-current-line)
+ ;; Make sure the overlay is in place
+ (emms-pbi-update-current-face))))))
+
+(defun emms-pbi-entry-generate (track)
+ "Generate an entry for TRACK in the playlist-buffer.
+
+This uses `emms-pbi-track-description-function', or if that is nil, it defaults
+to `emms-track-description'."
+ (if emms-pbi-track-description-function
+ (funcall emms-pbi-track-description-function track)
+ ;; default to the emms way
+ (funcall emms-track-description-function track)))
+
+(defun emms-pbi-insert-entry (track)
+ "Insert an entry for TRACK in the playlist."
+ (let ((inhibit-read-only t)
+ (line (emms-pbi-shorten-entry-to-max-length
+ (emms-pbi-entry-generate track))))
+ (insert line)
+ (emms-pbi-add-properties-current-line)
+ ;; for the convenience of other modules, keep track of the longest
+ ;; line yet.
+ (setq emms-pbi-longest-line (max emms-pbi-longest-line (length line)))))
+
+(defun emms-pbi-rebuild-playlist-buffer ()
+ "This function rebuilds the playlist-buffer if necessary."
+ (unless emms-pbi-suspend-hooks
+ (emms-pbi-build-playlist-buffer)))
+
+;; Function for building the playlist
+
+(defun emms-pbi-build-playlist-buffer ()
+ "Build a playlist-buffer based on the current playlist."
+ (save-excursion
+ (set-buffer (get-buffer-create emms-pbi-playlist-buffer-name))
+ (let ((playlist-length (length emms-playlist))
+ (idx 0)
+ (inhibit-read-only t))
+ ;; reset the buffer
+ (erase-buffer)
+ ;; insert all elements
+ (while (< idx playlist-length)
+ (emms-pbi-insert-entry (emms-playlist-get-track idx))
+ (insert "\n")
+ (setq idx (1+ idx)))
+ ;; Initialise the buffer variables
+ ;; remove the last line
+ (emms-pbi-update-current-face)
+ (delete-backward-char 1)
+ (run-hooks 'emms-pbi-after-build-hook)
+ (setq buffer-read-only t)
+ (emms-pbi-mode)
+ ;; as the last thing we do, update the current-face.
+ (when emms-player-playing-p
+ (emms-pbi-update-current-face)))))
+
+;; Updating the currently playing face
+(defun emms-pbi-update-current-face ()
+ "Updates the file line with the current-face"
+ (when (get-buffer emms-pbi-playlist-buffer-name)
+ (save-excursion
+ (set-buffer emms-pbi-playlist-buffer-name)
+ (let ((inhibit-read-only t))
+ ;; don't try to `1+' the value `nil'
+ (unless (null emms-playlist-current)
+ (goto-line (1+ emms-playlist-current))
+ (if (overlayp emms-pbi-current-overlay)
+ (move-overlay emms-pbi-current-overlay
+ (point-at-bol) (point-at-eol))
+ (setq emms-pbi-current-overlay
+ (make-overlay (point-at-bol) (point-at-eol)))
+ (overlay-put emms-pbi-current-overlay 'face 'emms-pbi-current-face)))))))
+
+;;Handling faces & properties
+(defun emms-pbi-add-properties-current-line ()
+ "Adds the correct faces and other properties to the current line"
+ ;; Default face
+ (let ((idx (emms-pbi-return-current-line-index)))
+ (add-text-properties (point-at-bol) (point-at-eol)
+ '(face emms-pbi-song-face))
+ (run-hooks 'emms-pbi-current-line-face-changed-hook)))
+
+(defun emms-pbi-play-current-line ()
+ "Play the current line"
+ (interactive)
+ (let ((new-idx (emms-pbi-return-current-line-index)))
+ ;; check boundaries
+ (when (and new-idx (> new-idx -1) (< new-idx (length emms-playlist)))
+ (emms-stop)
+ (emms-playlist-set-current new-idx)
+ (emms-start)
+ (run-hooks 'emms-pbi-manually-change-song-hook))))
+
+(defun emms-pbi-show-current-line ()
+ "Show filename and info for track on current line."
+ (interactive)
+ (let ((idx (emms-pbi-return-current-line-index)))
+ (message "Filename: %s; Info: %s"
+ (emms-track-name
+ (emms-playlist-get-track idx))
+ (emms-playlist-get idx))))
+
+;; Major-mode for the playlist-buffer
+(define-derived-mode emms-pbi-mode fundamental-mode "EMMS playlist"
+ (suppress-keymap emms-pbi-mode-map t)
+ (define-key emms-pbi-mode-map (kbd "n") 'emms-next)
+ (define-key emms-pbi-mode-map (kbd "p") 'emms-previous)
+ (define-key emms-pbi-mode-map (kbd "c") 'emms-pbi-recenter)
+ (define-key emms-pbi-mode-map (kbd "l") 'emms-pbi-recenter)
+ (define-key emms-pbi-mode-map (kbd "C-x C-s") 'emms-pbi-export-playlist)
+ (define-key emms-pbi-mode-map (kbd "C-k") 'emms-pbi-kill-line)
+ (define-key emms-pbi-mode-map (kbd "d") 'emms-pbi-kill-line)
+ (define-key emms-pbi-mode-map (kbd "C-y") 'emms-pbi-yank)
+ (define-key emms-pbi-mode-map (kbd "s") 'emms-stop)
+ (define-key emms-pbi-mode-map (kbd "f") 'emms-pbi-show-current-line)
+ (define-key emms-pbi-mode-map (kbd "RET") 'emms-pbi-play-current-line)
+ (define-key emms-pbi-mode-map (kbd "q") 'bury-buffer)
+ (define-key emms-pbi-mode-map (kbd "<mouse-2>") 'emms-pbi-play-current-line)
+ (define-key emms-pbi-mode-map (kbd "Q") 'emms-pbi-quit)
+ (define-key emms-pbi-mode-map (kbd "?") 'describe-mode))
+
+(defun emms-pbi-quit ()
+ "Stops emms and kill the playlist buffer"
+ (interactive)
+ (emms-stop)
+ (kill-buffer emms-pbi-playlist-buffer-name))
+
+(defun emms-playlist-empty-p ()
+ (= (length emms-playlist) 0))
+
+(defun emms-pbi-kill-line ()
+ "Kill the playlist line the cursor is currently on and update
+ the playlist accordingly."
+ (interactive)
+ (if (emms-playlist-empty-p)
+ (message "One cannot remove what is not there grasshopper")
+ (let ((idx (emms-pbi-return-current-line-index))
+ (inhibit-read-only t))
+ ;; remove from buffer
+ (goto-char (point-at-bol))
+ (kill-line 1)
+ ;; push track onto emms-kill-ring
+ (push (aref emms-playlist idx)
+ emms-kill-ring)
+ ;; now delete the entry from the playlist. - making sure that
+ ;; the entire list isn't regenerated
+ (let ((emms-pbi-suspend-hooks t))
+ (emms-playlist-remove idx))
+ (if (numberp emms-playlist-current)
+ ;; this deals with edge cases, explicit
+ (cond ((and emms-player-playing-p
+ (= idx emms-playlist-current))
+ (emms-stop) (emms-start)
+ (emms-pbi-update-current-face))
+ ((= idx emms-playlist-current)
+ (emms-pbi-update-current-face))
+ ((< idx emms-playlist-current)
+ (setq emms-playlist-current
+ (1- emms-playlist-current))))
+ (emms-stop))))) ;; stop if playlist empty after kill
+
+(defun emms-pbi-yank ()
+ "Yank a filename from `kill-ring' into the playlist."
+ (interactive)
+ (let ((track (pop emms-kill-ring))
+ (inhibit-read-only t)
+ (idx (emms-pbi-return-current-line-index)))
+ (if (and track (emms-player-for track))
+ ;; only insert files that actually exist, and can be played.
+ ;; insert it into the buffer
+ (save-excursion
+ (goto-char (point-at-bol))
+ (insert "\n")
+ (forward-line -1)
+ (goto-char (point-at-bol))
+ (emms-pbi-insert-entry track)
+ ;; insert it into the playlist
+ (let ((emms-pbi-suspend-hooks t))
+ (emms-playlist-add (list track) idx))))
+ (message "No playable track in emms-kill-ring!")))
+
+(defun emms-pbi-return-current-line-index ()
+ "Return the index position in the playlist of the current line."
+ (1- (count-lines (point-min) (point-at-eol))))
+
+(defun emms-pbi-valid-index-p (idx)
+ "Return non-nil if IDX is a valid index in the current playlist."
+ (and idx (> idx -1) (< idx (length emms-playlist))))
+
+(defun emms-pbi-recenter ()
+ "Center on current playing track"
+ (interactive)
+ (let ((line (1+ emms-playlist-current)))
+ (goto-line line)
+ (recenter)))
+
+(defun emms-pbi-export-playlist (file)
+ (interactive "Fsave playlist:")
+ (let ((buffer (find-file-noselect file t)))
+ (set-buffer buffer)
+ (prin1 emms-playlist buffer)
+ (save-buffer)
+ (kill-buffer buffer)))
+
+(defun emms-pbi-open-playlist (file)
+ (interactive "fOpen playlist:")
+ (let ((buffer (find-file-noselect file)))
+ (set-buffer buffer)
+ (beginning-of-buffer)
+ (emms-playlist-set-playlist (read buffer))
+ (kill-buffer buffer)))
+
+(provide 'emms-pbi)
+;;; emms-pbi.el ends here
diff --git a/emms-pl-manip.el b/emms-pl-manip.el
new file mode 100644
index 0000000..649f2dc
--- /dev/null
+++ b/emms-pl-manip.el
@@ -0,0 +1,131 @@
+;;; emms-pl-manip.el --- Advanced playlist manipulation for EMMS
+
+;; Copyright (C) 2003 Free Software Foundation, Inc.
+
+;; Author: Ulrik Jensen <terryp@daimi.au.dk>
+;; Keywords:
+
+;; This file is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; This file is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING. If not, write to the
+;; Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+;; Boston, MA 02110-1301 USA
+
+;;; Commentary:
+
+;; This file offers various advanced playlist-manipulations functions
+;; for EMMS.
+
+;; Basically just load up this file, and check out some of these
+;; functions:
+
+;; `emms-playlist-sort' - for sorting
+
+;; `emms-playlist-sort-by-info-artist' - if you have `emms-info', this
+;; well let you sort based on the artist.
+
+;; Todo
+
+;; `emms-playlist-add-file' - adds a single file to the playlist.
+;; `emms-playlist-add-dir' - adds a directory to the playlist
+
+;;; Code:
+
+(defvar emms-pl-manip-version "0.2 $Revision: 1.16 $"
+ "EMMS pl manip version string.")
+;; $Id: emms-pl-manip.el,v 1.16 2005/07/09 11:56:00 forcer Exp $
+
+
+(defvar emms-playlist-get-file-name-function 'emms-track-name)
+
+(defun vector-sort (vec pred &optional beg end)
+ "Sort a vector VEC, using the predicate PRED, and return the new
+vector. If BEG and END are specified, sort only this subrange.
+
+PRED is called with 2 elements and should return true, if the first is
+less than the other."
+ (let ((begidx (or beg 0))
+ (endidx (or end (1- (length vec)))))
+ (if (= begidx endidx)
+ ;; return a vector of this element
+ (make-vector 1 (aref vec begidx))
+ ;; split the vector
+ (let ((mididx (/ (- endidx begidx) 2)))
+ ;; sort the two sub-vectors
+ (vector-sort vec pred begidx mididx)
+ (vector-sort vec pred mididx endidx)
+ ;; merge the vectors - *this* can't be
+ (let ((result (make-vector (length vec) nil))
+ (lowidx begidx)
+ (highidx mididx)
+ (residx 0))
+ ;; first merge while there are still elements left in both
+ (while (and (< lowidx mididx) (< highidx endidx))
+ (if (funcall pred (aref vec lowidx) (aref vec highidx))
+ (progn
+ (aset result residx (aref vec lowidx))
+ (setq lowidx (1+ lowidx)))
+ (aset result residx (aref vec highidx))
+ (setq highidx (1+ highidx)))
+ (setq residx (1+ residx)))
+ ;; now only one of the ranges have elements left, merge those
+ (let ((idx -1)
+ (idxterm endidx))
+ (if (< lowidx mididx)
+ (setq idxterm mididx)
+ (setq idx mididx))
+ (while (< idx idxterm)
+ (aset residx result (aref vec idx))
+ (setq residx (1+ residx)
+ idx (1+ idx))))
+ ;; return
+ result)))))
+
+(defun emms-pl-manip-sort (by pred)
+ "Sorts the EMMS-playlist, by applying BY as a function to each
+filename in the list, and then comparing the results with PRED."
+ ;; convert to a list
+ (let ((listplaylist (append emms-playlist nil)))
+ ;; sort the list
+ (emms-playlist-set-playlist
+ (vconcat
+ (sort listplaylist
+ (lambda (arg-one arg-two)
+ (funcall pred
+ (funcall by arg-one)
+ (funcall by arg-two))))))))
+
+(defun emms-pl-manip-sort-by-filename ()
+ (interactive)
+ (emms-pl-manip-sort (lambda (x) x) 'string<))
+
+
+(defun emms-pl-manip-sort-by-name ()
+ (interactive)
+ (emms-pl-manip-sort emms-playlist-get-file-name-function 'string<))
+
+(defun emms-pl-manip-sort-by-info-artist ()
+ "Sort the playlist, using "
+ (interactive)
+ (unless (featurep 'emms-info)
+ (error "You have to load emms-info before using emms-pl-manip-sort-by-info-artist."))
+ (emms-pl-manip-sort (lambda (entry)
+ (emms-info-artist (emms-info-get entry)))
+ 'string<))
+
+(defun emms-playlist-reshuffle ()
+ "Reshuffle the playlist."
+ (interactive)
+ (emms-playlist-set-playlist (emms-playlist-shuffle)))
+
+(provide 'emms-pl-manip)
+;;; emms-pl-manip.el ends here
diff --git a/emms-player-extensions.el b/emms-player-extensions.el
new file mode 100644
index 0000000..2956edf
--- /dev/null
+++ b/emms-player-extensions.el
@@ -0,0 +1,119 @@
+;;; emms-player-extensions.el - Add more user control functions for EMMS
+
+;; Copyright (C) 2005 William XWL
+
+;; Author: William XWL <william.xwl@gmail.com>
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+;;
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+;; 02110-1301 USA
+
+;;; Commentary:
+
+;; This package adds pause and seek support for EMMS. While pause and
+;; seek are only available for special players.
+
+;; Specically, here we add pause and seek support for
+;; `emms-player-mplayer' with mplayer's slave mode enabled. Put
+;; something like this to your .emacs first:
+;;
+;; (setq emms-player-mplayer-command-name "mplayer"
+;; emms-player-mplayer-parameters '("-slave")
+;; emms-player-list
+;; '(emms-player-mplayer
+;; emms-player-mplayer-playlist
+;; emms-player-mpg321
+;; emms-player-ogg123))
+
+;; To use, put this file to your load-path and the following to your
+;; .emacs:
+;;
+;; (require 'emms-player-extensions)
+
+;;; Change Log
+
+;; v 0.21 [2005/07/18 16:42:51] In function `emms-seek', add a call for
+;; `emms-lyric-seek' (defined in `emms-lyric.el').
+
+;; v 0.2 [2005/07/17 20:22:53] And `emms-player-paused-p',
+;; `emms-player-player-paused-hook', and some modifications to
+;; cooperate with another package(`emms-lyric.el'). Rename this
+;; file from emms-patch.el to emms-player-extensions.el.
+
+;; v 0.1 The initial version. Add `emms-pause', `emms-seek',
+;; `emms-repeat-curr', `emms-unrepeat-curr', `emms-repeat-all',
+;; `emms-unrepeat-all'.
+
+;;; Codes:
+
+;; Version control
+(defvar emms-player-extensions-version "0.3 $Revision: 1.7 $"
+ "EMMS player extensions version string.")
+;; $Id: emms-player-extensions.el,v 1.7 2005/08/17 14:10:11 xwl Exp $
+
+(require 'emms)
+
+;;; Variables:
+
+(defvar emms-player-paused-p nil
+ "The EMMS player paused.")
+
+(defvar emms-player-paused-hook nil
+ "*Hook run when an EMMS player pauses playing.")
+
+(defvar emms-player-seeked-hook nil
+ "*Hook run when an EMMS player seeks forward or backward.")
+
+;;; User Interfaces:
+
+(defun emms-pause ()
+ "Pause the current player."
+ (interactive)
+ (when emms-player-playing-p
+ (funcall (emms-player-get emms-player-playing-p 'pause))
+ (setq emms-player-paused-p (not emms-player-paused-p))
+ (run-hooks 'emms-player-paused-hook)))
+
+(defun emms-seek (&optional sec)
+ "Seek forward/backward SEC(default is 10) seconds."
+ (interactive)
+ (unless sec (setq sec 10))
+ (when emms-player-playing-p
+ (funcall (emms-player-get emms-player-playing-p 'seek) sec)
+ (run-hook-with-args 'emms-player-seeked-hook sec)))
+
+;;; mplayer: pause, seek
+(emms-player-set emms-player-mplayer
+ 'pause
+ 'emms-player-mplayer-pause)
+
+(emms-player-set emms-player-mplayer
+ 'seek
+ 'emms-player-mplayer-seek)
+
+(defun emms-player-mplayer-pause ()
+ "Depends on mplayer's -slave mode."
+ (process-send-string
+ emms-player-simple-process-name "pause\n"))
+
+(defun emms-player-mplayer-seek (sec)
+ "Depends on mplayer's -slave mode."
+ (process-send-string
+ emms-player-simple-process-name
+ (format "seek %d\n" sec)))
+
+
+(provide 'emms-player-extensions)
+
+;;; emms-player-extensions.el ends here
diff --git a/emms-player-simple.el b/emms-player-simple.el
new file mode 100644
index 0000000..35ff2de
--- /dev/null
+++ b/emms-player-simple.el
@@ -0,0 +1,134 @@
+;;; emms-player-simple.el --- A generic simple player.
+
+;; Copyright (C) 2003 Free Software Foundation, Inc.
+
+;; Authors: Ulrik Jensen <terryp@daimi.au.dk>
+;; Jorgen Schfer <forcer@forcix.cx>
+;; Keywords: emms, mpg321, ogg123, mplayer
+
+;; This file is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; This file is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING. If not, write to the
+;; Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+;; Boston, MA 02110-1301 USA
+
+;;; Commentary:
+
+;; This is a simple player interface - if you have an external player
+;; that just expects the filename to play as an argument, this should
+;; be able to use it. See the define-emms-simple-player lines at the
+;; end of this file for examples.
+
+;; Add the following to your `emms-player-list':
+
+;; emms-player-mpg321
+;; emms-player-ogg123
+;; emms-player-mplayer
+
+;;; Code:
+
+;; Version control
+(defvar emms-player-simple-version "0.2 $Revision: 1.26 $"
+ "Simple player for EMMS version string.")
+;; $Id: emms-player-simple.el,v 1.26 2005/08/02 15:27:51 forcer Exp $
+
+(require 'emms)
+
+;; Customization
+
+(defmacro define-emms-simple-player (name types regex command &rest args)
+ "Define a simple player with the use of `emms-define-player'.
+NAME is used to contruct the name of the function like
+emms-player-NAME. TYPES is a list of track types understood by
+this player. REGEX must be a regexp that matches the filenames
+the player can play. COMMAND specifies the command line arguement
+to call the player and ARGS are the command line arguements."
+ (let ((group (intern (concat "emms-player-" (symbol-name name))))
+ (command-name (intern (concat "emms-player-"
+ (symbol-name name)
+ "-command-name")))
+ (parameters (intern (concat "emms-player-"
+ (symbol-name name)
+ "-parameters")))
+ (player-name (intern (concat "emms-player-" (symbol-name name))))
+ (start (intern (concat "emms-player-" (symbol-name name) "-start")))
+ (stop (intern (concat "emms-player-" (symbol-name name) "-stop")))
+ (playablep (intern (concat "emms-player-" (symbol-name name) "-playable-p"))))
+ `(progn
+ (defgroup ,group nil
+ ,(concat "EMMS player for " command ".")
+ :group 'emms-player
+ :prefix ,(concat "emms-player-" (symbol-name name) "-"))
+ (defcustom ,command-name ,command
+ ,(concat "*The command name of " command ".")
+ :type 'string
+ :group ',group)
+ (defcustom ,parameters ',args
+ ,(concat "*The arguments to `" (symbol-name command-name) "'.")
+ :type '(repeat string)
+ :group ',group)
+ (defcustom ,player-name (emms-player ',start ',stop ',playablep)
+ ,(concat "*A player for EMMS.")
+ :type '(cons symbol alist))
+ (emms-player-set ,player-name 'regex ,regex)
+ (defun ,start (track)
+ "Start the player process."
+ (emms-player-simple-start (emms-track-name track)
+ ,command-name
+ ,parameters))
+ (defun ,stop ()
+ "Stop the player process."
+ (emms-player-simple-stop))
+ (defun ,playablep (track)
+ "Return non-nil when we can play this track."
+ (and (memq (emms-track-type track) ,types)
+ (string-match ,regex (emms-track-name track)))))))
+
+;; Global variables
+(defvar emms-player-simple-process-name "emms-player-simple-process"
+ "The name of the simple player process")
+
+(defun emms-player-simple-stop ()
+ "Stop the currently playing process, if indeed there is one"
+ (let ((process (get-process emms-player-simple-process-name)))
+ (when process
+ (kill-process process)
+ (delete-process process))))
+
+;; Utility-functions
+(defun emms-player-simple-start (filename cmdname params)
+ "Starts a process playing FILENAME using the specified CMDNAME with
+the specified PARAMS."
+ (let ((process (apply 'start-process
+ emms-player-simple-process-name
+ nil
+ cmdname
+ ;; splice in params here
+ (append params (list filename)))))
+ ;; add a sentinel for signaling termination
+ (set-process-sentinel process 'emms-player-simple-sentinel)))
+
+(defun emms-player-simple-sentinel (proc str)
+ "Sentinel for determining the end of process"
+ (when (or (eq (process-status proc) 'exit)
+ (eq (process-status proc) 'signal))
+ (emms-player-stopped)))
+
+(define-emms-simple-player mpg321 '(file url) "\\.[mM][pP][23]$" "mpg321")
+(define-emms-simple-player ogg123 '(file) (regexp-opt '(".ogg" ".OGG" ".FLAC" ".flac" )) "ogg123")
+(define-emms-simple-player mplayer-playlist '(playlist) "http://" "mplayer" "-playlist")
+(define-emms-simple-player mplayer '(file url)
+ (regexp-opt '(".ogg" ".mp3" ".wav" ".mpg" ".mpeg" ".wmv" ".wma" ".mov" ".avi" ".divx" ".ogm" ".asf" ".mkv" "http://")) "mplayer")
+
+
+(provide 'emms-player-simple)
+;;; emms-player-simple.el ends here
diff --git a/emms-playing-time.el b/emms-playing-time.el
new file mode 100644
index 0000000..6dd6eb6
--- /dev/null
+++ b/emms-playing-time.el
@@ -0,0 +1,137 @@
+;;; emms-playing-time.el --- Display emms playing time on mode line
+
+;; Copyright (C) 2005 William XWL
+
+;; Author: William XWL <william.xwl@gmail.com>
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+;;
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+;; 02110-1301 USA
+
+;;; Commentary:
+
+;; Display playing time on mode line, it looks like: 01:32/04:09.
+
+;; Put this file into your load-path and the following into your
+;; ~/.emacs:
+;; (require 'emms-playing-time)
+
+;;; Code:
+
+(defvar emms-playing-time-version "0.1 $Revision: 1.7 $"
+ "EMMS playing time version string.")
+;; $Id: emms-playing-time.el,v 1.7 2005/09/08 16:07:20 xwl Exp $
+
+(eval-when-compile (require 'cl))
+(require 'emms-info)
+(require 'emms-info-mp3info)
+(require 'emms-player-simple)
+(require 'emms-player-extensions)
+
+;;; Customizations
+(defvar emms-playing-time-display-p t
+ "Whether to display playing time on mode line or not.")
+
+(defvar emms-playing-time-display-short-p nil
+ "Only display elapsed time, don't display total playing time,
+e.g., display 02:37 instead of 02:37/05:49.")
+
+(defvar emms-playing-time-display-format " %s "
+ "String used for displaying playing time on mode-line.")
+
+;;; Variables
+(defvar emms-playing-time 0
+ "How long has EMMS run up to now.")
+
+(defvar emms-playing-time-string "")
+
+;;; Functions
+(defun emms-playing-time-start ()
+ "Get ready for display playing time."
+ (when emms-playing-time-display-p
+ (setq emms-playing-time 0)
+ (emms-playing-time-mode-line)
+ (run-at-time t 1 'emms-playing-time-display)))
+
+(add-hook 'emms-player-started-hook 'emms-playing-time-start)
+
+(defun emms-playing-time-stop ()
+ "Remove playing time on the mode line."
+ (when emms-playing-time-display-p
+ (if (or (not emms-player-paused-p)
+ emms-player-stopped-p)
+ (progn
+ (setq emms-playing-time-string "")
+ (force-mode-line-update)))
+ (cancel-function-timers 'emms-playing-time-display)))
+
+(add-hook 'emms-player-stopped-hook 'emms-playing-time-stop)
+(add-hook 'emms-player-finished-hook 'emms-playing-time-stop)
+
+(defun emms-playing-time-pause ()
+ "Pause playing time."
+ (when emms-playing-time-display-p
+ (if emms-player-paused-p
+ (emms-playing-time-stop)
+ (run-at-time t 1 'emms-playing-time-display))))
+
+(add-hook 'emms-player-paused-hook 'emms-playing-time-pause)
+
+(defun emms-playing-time-seek (sec)
+ "Seek forward or backward SEC playing time."
+ (when emms-playing-time-display-p
+ (setq emms-playing-time (+ emms-playing-time sec))
+ (when (< emms-playing-time 0) ; back to start point
+ (setq emms-playing-time 0))))
+
+(add-hook 'emms-player-seeked-hook 'emms-playing-time-seek)
+
+(defun emms-playing-time-display ()
+ "Display playing time on the mode line."
+ (setq emms-playing-time (1+ emms-playing-time))
+ (let* ((min (/ emms-playing-time 60))
+ (sec (% emms-playing-time 60))
+ ;; How to adapt `emms-info-format-info' here?
+ (struct
+ (emms-info-get (emms-playlist-current-track)))
+ (total-min-only
+ (when struct (emms-info-playing-time-min struct)))
+ (total-sec-only
+ (when struct (emms-info-playing-time-sec struct))))
+ (setq emms-playing-time-string
+ (format
+ emms-playing-time-display-format
+ (replace-regexp-in-string
+ " " "0"
+ (if (or emms-playing-time-display-short-p
+ ;; unable to get total time info
+ (not total-min-only)
+ (not total-sec-only))
+ (format "%2d:%2d" min sec)
+ (format "%2d:%2d/%2s:%2s"
+ min sec total-min-only total-sec-only)))))
+ (force-mode-line-update)))
+
+(defun emms-playing-time-mode-line ()
+ "Add playing time to the mode line."
+ (unless (member 'emms-playing-time-string
+ global-mode-string)
+ (setq global-mode-string
+ (append global-mode-string
+ '(emms-playing-time-string)))))
+
+
+(provide 'emms-playing-time)
+
+;;; emms-playing-time.el ends here
diff --git a/emms-score.el b/emms-score.el
new file mode 100644
index 0000000..da28828
--- /dev/null
+++ b/emms-score.el
@@ -0,0 +1,189 @@
+;;; emms-scores.el --- Scoring system for mp3player
+;; Author & Maintainer: Jean-Philippe Theberge (jphiltheberge@videotron.ca)
+;; version :
+(defconst emms-scores-version "1.92 $Revision: 1.4 $")
+;; $Id: emms-score.el,v 1.4 2004/02/17 09:01:49 kanaldrache Exp $
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; Copyright (c) 1/me think he smoke too much marijuana in it's yong years998 - 1999 Free Software Foundation, Inc.
+;;
+;; This file is not part of GNU Emacs. :-(
+;;
+;; This file is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+;;
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;
+;; NOTE: This is experimental stuff - comments welcome! There
+;; shouldn't worky anything in that file... scores aren't saved, they
+;; even don't have any consequence on playing order and there's just
+;; one mood in the moment. But it's a beginning and you can score down
+;; or up tracks... :)
+;;
+;; * How to use scoring in emms
+;;
+;; When you load emms, you are set to a default mood
+;; 'emms-default-mood' A mood is a one word string describing how
+;; you feel (like "funny", "tired", "aggresive"...) Each mood have is
+;; own set of scoring rules.
+;;
+;; You can change your mood with M-x emms-score-change-mood.
+;;
+;; Every music file start with a default score of 0 the command
+;; emms-score-up-current and emms-score-down-current modify the
+;; score of the file you are curently listening by 1 In addition,
+;; skipping a file (with emms-skip) automaticaly score the file
+;; down.
+;;
+;; With scoring on (this mean the variable emms-use-scoring is t),
+;; emms will compare the score of the file with your tolerance to
+;; decide if it is played or not.
+;;
+;; The default tolerance level is 0 (or the variable
+;; emms-score-min-score). This mean files with a score of 0 or more will
+;; be played and files with a score of -1 or less will be skipped.
+;;
+;; You can change the tolerance (by 1) with M-x
+;; emms-score-lower-tolerance and M-x
+;; emms-score-be-more-tolerant
+
+;;; Code:
+
+(defvar emms-score-version "0.2 $Revision: 1.4 $"
+ "EMMS score version string.")
+;; $Id: emms-score.el,v 1.4 2004/02/17 09:01:49 kanaldrache Exp $
+
+(defvar emms-scores-list nil)
+(defvar emms-score-current-mood 'default)
+(defvar emms-score-min-score 0)
+(defvar emms-score-default-score 0)
+(defvar emms-score-hash (make-hash-table :test 'equal))
+
+(add-hook 'kill-emacs-hook 'emms-score-save-hash)
+
+(defcustom emms-score-file "~/.emms/scores"
+ "*Directory to store the score file."
+ :type 'directory
+ :group 'emms)
+
+(defun emms-score-change-mood (mood)
+ "Change the current MOOD.
+The score hash is automatically saved."
+ (interactive "sMood: ")
+ (emms-score-save-hash)
+ (setq emms-score-current-mood (intern (downcase mood))))
+
+
+(defun emms-score-save-hash ()
+ "Save score hash in `emms-score-file'."
+ (interactive)
+ (unless (file-directory-p (file-name-directory emms-score-file))
+ (make-directory (file-name-directory emms-score-file)))
+ (with-temp-file emms-score-file
+ (let ((standard-output (current-buffer)))
+ (insert "(")
+ (maphash (lambda (key value)
+ (prin1 (cons key value)))
+ emms-score-hash)
+ (insert ")"))))
+
+(defun emms-score-load-hash ()
+ "Load score hash from `emms-score-file'."
+ (interactive)
+ (mapc (lambda (elt)
+ (puthash (car elt) (cdr elt) emms-score-hash))
+ (read
+ (with-temp-buffer
+ (insert-file-contents emms-score-file)
+ (buffer-string)))))
+
+(defun emms-score-get-plist (filename)
+ (gethash filename emms-score-hash))
+
+(defun emms-score-change-score (score filename)
+ (let ((sp (emms-score-get-plist filename) )
+ (sc (emms-score-get-score filename)))
+ (puthash filename
+ (plist-put sp emms-score-current-mood (+ sc score))
+ emms-score-hash)
+ (message "New score is %s" (+ score sc))))
+
+(defun emms-score-up-current ()
+ (interactive)
+ (emms-score-change-score 1 (emms-playlist-current)))
+
+(defun emms-score-down-current ()
+ (interactive)
+ (emms-score-change-score -1 (emms-playlist-current)))
+
+(defun emms-score-up-file-on-line ()
+ (interactive)
+ (let ((idx (1- (count-lines (point-min) (point-at-eol)))))
+ (emms-score-change-score 1 (emms-track-name (emms-playlist-get-track idx)))))
+
+(defun emms-score-down-file-on-line ()
+ (interactive)
+ (let ((idx (1- (count-lines (point-min) (point-at-eol)))))
+ (emms-score-change-score -1 (emms-track-name (emms-playlist-get-track idx)))))
+
+
+(defun emms-score (arg)
+ "Turn on emms-score if prefix argument ARG is a positive integer,
+off otherwise."
+ (interactive "p")
+ (if (and arg (> arg 0))
+ (progn
+ (emms-score-load-hash)
+ (remove-hook 'emms-player-stopped-hook 'emms-next-noerror)
+ (add-hook 'emms-player-stopped-hook 'emms-score-next-noerror))
+ (emms-score-save-hash)
+ (remove-hook 'emms-player-stopped-hook 'emms-score-next-noerror)
+ (add-hook 'emms-player-stopped-hook 'emms-next-noerror)))
+
+(defun emms-score-next-noerror ()
+ "Play the next track in the playlist, but don't signal an error when
+we're at the end. This should be called when no player is playing.
+This is a suitable function to put in `emms-player-stopped-hook'."
+ (interactive)
+ (when emms-player-playing-p
+ (error "A track is already playing."))
+ (if (emms-playlist-next)
+ (if (emms-score-check-score (emms-playlist-current))
+ (emms-start)
+ (emms-score-next-noerror))
+ (message "No track in playlist that matches your score anymore")))
+
+(defun emms-score-create-entry (filename)
+ (puthash filename (list emms-score-current-mood emms-score-default-score)
+ emms-score-hash))
+
+(defun emms-score-get-score (filename)
+ "Return score of TRACK."
+ (let ((plist (emms-score-get-plist filename)))
+ (if (member emms-score-current-mood plist)
+ (plist-get plist emms-score-current-mood)
+ (emms-score-create-entry filename)
+ (emms-score-get-score filename))))
+
+(defun emms-score-check-score (filename)
+ (>= (emms-score-get-score filename) emms-score-min-score))
+
+(defun emms-score-lower-tolerance ()
+ "Only play mp3 with a higher score"
+ (interactive)
+ (setq emms-score-min-score (+ emms-score-min-score 1)))
+
+(defun emms-score-be-more-tolerant ()
+ "Allow playing of mp3 with a lower score"
+ (interactive)
+ (setq emms-score-min-score (- emms-score-min-score 1)))
+
+(provide 'emms-score)
+
+;;; emms-scores.el ends here
diff --git a/emms-source-file.el b/emms-source-file.el
new file mode 100644
index 0000000..f2f4846
--- /dev/null
+++ b/emms-source-file.el
@@ -0,0 +1,288 @@
+;;; emms-source-file.el --- EMMS sources from the filesystem.
+
+;; Copyright (C) 2003 Jorgen Schfer
+
+;; Author: Jorgen Schfer <forcer@forcix.cx>
+;; Keywords: emms, mp3, mpeg, multimedia
+
+;; This file is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+;;
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING. If not, write to the
+;; Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+;; Boston, MA 02110-1301 USA
+
+;;; Commentary:
+
+;; This file contains a track source for EMMS that is based on the
+;; file system. You can retrieve single files or whole directories.
+;; Also, this file offers the commands to play from these sources.
+
+;; TODO:
+
+;;; Code:
+
+;; Version control
+(defvar emms-source-file-version "0.2 $Revision: 1.30 $"
+ "emms-source-file.el version string")
+;; $Id: emms-source-file.el,v 1.30 2005/08/11 06:16:15 yonirabkin Exp $
+
+;;; User Customization
+
+(require 'emms)
+(eval-when-compile
+ (condition-case nil
+ (require 'locate)
+ (error nil)))
+
+(defgroup emms-source-file nil
+ "*Sources for EMMS that use the file system."
+ :prefix "emms-source-file-"
+ :group 'emms-source)
+
+(defcustom emms-source-file-default-directory nil
+ "*The default directory to look for media files."
+ :type 'string
+ :group 'emms-source-file)
+
+(defcustom emms-source-file-directory-tree-function
+ 'emms-source-file-directory-tree-internal
+ "*A function to call that searches in a given directory all files
+that match a given regex. DIR and REGEX are the only arguments passed
+to this function.
+You have two build-in options:
+`emms-source-file-directory-tree-internal' will work always, but might
+be slow.
+`emms-source-file-directory-tree-find' will work only if you have GNU
+find, but it's faster."
+ :type 'function
+ :options '(emms-source-file-directory-tree-internal
+ emms-source-file-directory-tree-find)
+ :group 'emms-source-file)
+
+(defcustom emms-source-file-gnu-find "find"
+ "*The program name for GNU find."
+ :type 'string
+ :group 'emms-source-file)
+
+;; The `read-directory-name' function is not available in Emacs 21.
+(defalias 'emms-read-directory-name
+ (if (fboundp 'read-directory-name)
+ #'read-directory-name
+ #'read-file-name))
+
+;;; Sources
+
+;;;###autoload (autoload 'emms-play-file "emms-source-file" t)
+;;;###autoload (autoload 'emms-add-file "emms-source-file" t)
+(define-emms-source file (file)
+ "An EMMS source for a single file - either FILE, or queried from the
+user."
+ (interactive (list (read-file-name "Play file: "
+ emms-source-file-default-directory
+ emms-source-file-default-directory
+ t)))
+ (if (file-directory-p file)
+ (emms-source-directory file)
+ (list (emms-track 'file (expand-file-name file)))))
+
+;;;###autoload (autoload 'emms-play-directory "emms-source-file" t)
+;;;###autoload (autoload 'emms-add-directory "emms-source-file" t)
+(define-emms-source directory (dir)
+ "An EMMS source for a whole directory tree - either DIR, or queried
+from the user"
+ (interactive (list
+ (emms-read-directory-name "Play directory: "
+ emms-source-file-default-directory
+ emms-source-file-default-directory
+ t)))
+ (mapcar (lambda (file)
+ (emms-track 'file (expand-file-name file)))
+ (directory-files dir t (emms-source-file-regex))))
+
+;;;###autoload (autoload 'emms-play-directory-tree "emms-source-file" t)
+;;;###autoload (autoload 'emms-add-directory-tree "emms-source-file" t)
+(define-emms-source directory-tree (dir)
+ "An EMMS source for multiple directory trees - either DIR, or the
+value of `emms-source-file-default-directory'."
+ (interactive (list
+ (emms-read-directory-name "Play directory tree: "
+ emms-source-file-default-directory
+ emms-source-file-default-directory
+ t)))
+ (mapcar (lambda (file)
+ (emms-track 'file file))
+ (emms-source-file-directory-tree (expand-file-name dir)
+ (emms-source-file-regex))))
+
+
+;;;###autoload (autoload 'emms-play-find "emms-source-file" t)
+;;;###autoload (autoload 'emms-add-find "emms-source-file" t)
+(define-emms-source find (dir regex)
+ "An EMMS source that will find files in DIR or
+`emms-source-file-default-directory' that match REGEXP."
+ (interactive (list
+ (emms-read-directory-name "Find in directory: "
+ emms-source-file-default-directory
+ emms-source-file-default-directory
+ t)
+ (read-from-minibuffer "Find files matching: ")))
+ (mapcar (lambda (file)
+ (emms-track 'file file))
+ (emms-source-file-directory-tree dir regex)))
+
+
+;;;###autoload (autoload 'emms-play-m3u-playlist "emms-source-file" t)
+;;;###autoload (autoload 'emms-add-m3u-playlist "emms-source-file" t)
+(define-emms-source m3u-playlist (playlist)
+ "A source for simple .m3u playlists. It ignores empty lines, or
+lines starting with '#'."
+ (interactive (list (read-file-name "Play file: "
+ emms-source-file-default-directory
+ emms-source-file-default-directory
+ t)))
+ (emms-source-files
+ (let ((files '())
+ (dir (file-name-directory playlist)))
+ (with-temp-buffer
+ (insert-file-contents playlist)
+ (goto-char (point-min))
+ (while (re-search-forward "^[^# ].*$" nil t)
+ (let ((line (match-string 0)))
+ (setq files (cons (if (file-name-absolute-p line)
+ line
+ (concat dir line))
+ files)))))
+ (reverse files))))
+
+
+;;; Helper functions
+
+;;;###autoload
+(defun emms-source-file-directory-tree (dir regex)
+ "Return a list of all files under DIR that match REGEX.
+This function uses `emms-source-file-directory-tree-function'."
+ (message "Building playlist...")
+ (let ((pl (funcall emms-source-file-directory-tree-function
+ dir
+ regex)))
+ (message "Building playlist...done")
+ pl))
+
+(defun emms-source-file-directory-tree-internal (dir regex)
+ "Return a list of all files under DIR that match REGEX.
+This function uses only emacs functions, so it might be a bit slow."
+ (let ((files '())
+ (dirs (list dir)))
+ (while dirs
+ (cond
+ ((file-directory-p (car dirs))
+ (if (string-match "/\\.\\.?$" (car dirs))
+ (setq dirs (cdr dirs))
+ (setq dirs
+ (condition-case nil
+ (append (cdr dirs)
+ (directory-files (car dirs)
+ t nil t))
+ (error
+ (cdr dirs))))))
+ ((string-match regex (car dirs))
+ (setq files (cons (car dirs) files)
+ dirs (cdr dirs)))
+ (t
+ (setq dirs (cdr dirs)))))
+ files))
+
+(defun emms-source-file-directory-tree-find (dir regex)
+ "Return a list of all files under DIR that match REGEX.
+This function uses the external find utility. The name for GNU find
+may be supplied using `emms-source-file-gnu-find'."
+ (with-temp-buffer
+ (call-process emms-source-file-gnu-find
+ nil t nil
+ (expand-file-name dir)
+ "-type" "f"
+ "-iregex" (concat ".*\\(" regex "\\).*"))
+ (delete ""
+ (split-string (buffer-substring (point-min)
+ (point-max))
+ "\n"))))
+
+;;;###autoload
+(defun emms-source-files (files)
+ "An EMMS source for a list of FILES."
+ (apply #'append (mapcar #'emms-source-file files)))
+
+;;;###autoload
+(defun emms-source-dired ()
+ "Return all marked files of a dired buffer"
+ (emms-source-files (dired-get-marked-files)))
+
+;;;###autoload
+(defun emms-source-file-regex ()
+ "Return a regexp that matches everything any player (that supports
+files) can play."
+ (mapconcat (lambda (player)
+ (or (emms-player-get player 'regex)
+ ""))
+ emms-player-list
+ "\\|"))
+
+;; Really don't know where to put this, but as the functions for
+;; important and playing a playlist are in ths file i suppose it a
+;; good place for it.
+
+(defun emms-save-playlist (filename)
+ "Export the current playlist as to FILENAME. See also:
+`emms-pbi-import-playlist'."
+ (interactive "FFile to save playlist as: ")
+ (with-temp-file filename
+ (mapc (lambda (elt) (insert (cdr (assoc 'name elt)) "\n")) emms-playlist)))
+
+;; emms-locate should be part of a once to be emms-dired, with maybe
+;; file rename after tag functions and so on, but till then i park it
+;; here... :)
+
+;;;###autoload
+(defun emms-locate (regexp)
+ "Search for REGEXP and display the results in a locate buffer"
+ (interactive "sRegexp to search for: ")
+ (require 'locate)
+ (save-window-excursion
+ (set-buffer (get-buffer-create "*EMMS Find*"))
+ (locate-mode)
+ (erase-buffer)
+ (mapc (lambda (elt) (insert (cdr (assoc 'name elt)) "\n"))
+ (emms-source-find emms-source-file-default-directory regexp))
+ (locate-do-setup regexp))
+ (and (not (string-equal (buffer-name) "*EMMS Find*"))
+ (switch-to-buffer-other-window "*EMMS Find*"))
+ (run-hooks 'dired-mode-hook)
+ (dired-next-line 2))
+
+;; Strictly speaking, this does not belong in this file (URLs are not
+;; real files), but it's close enough :-)
+
+;;;###autoload (autoload 'emms-play-url "emms-source-file" t)
+;;;###autoload (autoload 'emms-add-url "emms-source-file" t)
+(define-emms-source url (url)
+ "An EMMS source for an URL - for example, for streaming."
+ (interactive "sPlay URL: ")
+ (list (emms-track 'url url)))
+
+(define-emms-source playlist (playlist)
+ "An EMMS source for streaming playlists (usually URLs ending in .pls."
+ (interactive "sPlay URL: ")
+ (list (emms-track 'playlist playlist)))
+
+
+(provide 'emms-source-file)
+;;; emms-source-file.el ends here
diff --git a/emms-stream-info.el b/emms-stream-info.el
new file mode 100644
index 0000000..59a5b6d
--- /dev/null
+++ b/emms-stream-info.el
@@ -0,0 +1,730 @@
+;;; emms-stream-info.el --- Show what is currently playing on a
+;;; streaming audio station.
+
+;; Copyright (C) 2004 Yoni Rabkin Katzenell <yoni-r@actcom.com>
+;; This program is free software; you can redistribute it and/or
+;; modify it under the terms of the GNU General Public License
+;; as published by the Free Software Foundation; either version 2
+;; of the License, or (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+;; 02110-1301 USA
+
+;;; Commentary:
+;;
+;; 'emms-stream-info' establishes a TCP connection with the server and
+;; sends an HTTP request string. The server (hopefully) responds with
+;; some header information describing the streaming audio channel,
+;; some audio data and then the name of the song being played (usually
+;; in that order).
+;;
+;; Some stations like WCPE [http://wcpe.org], while giving excellent
+;; broadcasts do not support title streaming over MP3 or Ogg. Using
+;; this software on such stations will only result in general station
+;; information and not the artist name or title of the track being
+;; played.
+
+;;; Functionality:
+;;
+;; Currently supports Icecast and Shoutcast servers with Ogg and MP3
+;; streams.
+
+;;; Use:
+;;
+;; Look at the documentation strings for the three interactive
+;; functions: 'emms-stream-info-get', 'emms-stream-info-message' and
+;; 'emms-stream-info-insert'.
+
+;;; Important Notes:
+;;
+;; 1) This software does not parse, cache or save audio data at
+;; all. This software downloads a limited amount of data from a
+;; given streaming audio channel per call. This software is
+;; optimized to download as little as possible from a given
+;; streaming audio channel and then to immediately disconnect.
+;;
+;; 2) This software disregards and then discards all audio data
+;; automatically after each call.
+;;
+;; 3) This software connects for a maximum of 10 seconds and then
+;; immediately disconnects. Usually the software will disconnect
+;; long before the 10 second limit is reached.
+;;
+;; 4) It is the responsibility of the user to read the Terms of
+;; Service of the streaming audio channel before running this
+;; software on that channel's service. Some streaming audio
+;; channels explicitly request 3rd party applications not to
+;; connect to their service. This is their prerogative. Respect it.
+
+;; $Id: emms-stream-info.el,v 1.8 2005/07/09 11:56:00 forcer Exp $
+
+;;; Code:
+
+;; A higher value for 'emms-stream-info-max' this gives us a
+;; correspondingly higher chance of grabbing the title information
+;; from a stream but incurs a price in the additional time it takes to
+;; download.
+;;
+;; This value is not relevant for Ogg streams since the title info in
+;; Ogg streams arrives almost immediately.
+;;
+;; Do not set under 30000 since the typical value of 'metaint' on most
+;; streaming audio servers is either 8192 or 24576
+(defconst emms-stream-info-max 120000
+ "Byte limit for downloads.")
+
+(defconst emms-stream-info-timeout 10
+ "Seconds to timeout connection (dead or alive).")
+
+(defconst emms-stream-info-verbose t
+ "Output real-time information about the connection.")
+
+(defconst emms-stream-info-version
+ "$Revision: 1.8 $"
+ "Software version.")
+
+(defconst emms-stream-info-char-alter-regexp "[-,'=:%+&0-9A-Za-z\.()/ ]"
+ "Unified character alternative clause for regular expressions.")
+
+(defconst emms-stream-info-shoutcast-regexp
+ (concat emms-stream-info-char-alter-regexp ".*?")
+ "Regular expression for Shoutcast.")
+
+(defconst emms-stream-info-icecast-regexp
+ (concat emms-stream-info-char-alter-regexp "+")
+ "Regular expression for Icecast.")
+
+(defconst emms-stream-info-shoutcast-title-regexp
+ (concat "StreamTitle='\\(" emms-stream-info-shoutcast-regexp "\\)';")
+ "Regular expression for Shoutcast.")
+
+;; Reference: http://www.xiph.org/ogg/vorbis/doc/framing.html
+(defconst emms-stream-info-icecast-capture-pattern "Oggs\\(.*\\)BCV"
+ "Regular Expression for the beggining of an Ogg bitstream page.")
+
+;; For all servers
+(defconst emms-stream-info-stream-header-regexp
+ (concat emms-stream-info-char-alter-regexp "+")
+ "Regular expression for metainformation headers.")
+
+(defconst emms-stream-info-playlist-regexp
+ "\\(^http://.*\\)\\|^File.=\\(http://.*\\)"
+ "Regular expression for playlist URLs.")
+
+;; When t output debugging info
+(defconst emms-stream-info-debugging nil
+ "If t then emms-stream-info will spill the stream into a buffer.
+Set to NIL unless you want a buffer filled with binary junk.")
+
+(defconst emms-stream-info-debug-buffer "*emms-stream-info-debug*"
+ "Buffer for debugging information.")
+
+(defconst emms-stream-info-vocab (list "name"
+ "genre"
+ "pub"
+ "metaint"
+ "br"
+ "bitrate"
+ "description"
+ "public"
+ "audio-info")
+ "List of header keys.")
+
+(defconst emms-stream-info-format-string
+ "Now streaming:%s, %c %bKb/sec"
+ "The following %-sequences are supported:
+
+%b Bitrate
+%s Song title and artist name
+%c Station/Channel name and short description
+%t Song title
+%g Station/Channel genre
+%a Artist name
+
+Note that some stations do not supply artist and song title
+information.")
+
+(defconst emms-stream-info-format-string-notitle
+ "Now streaming: %c %bKb/sec %g"
+ "Some streaming audio stations/channels do not provide artist
+and songtitle information. This string specifies an alternate
+format for those stations.")
+
+(defconst emms-stream-info-pls-regexp ".*\.pls"
+ "Regular expression for a .pls playlist file.")
+
+(defconst emms-stream-info-m3u-regexp ".*\.m3u"
+ "Regular expression for a .m3u playlist file.")
+
+(defvar emms-stream-info-url nil
+ "Server URL.")
+
+(defvar emms-stream-info-port nil
+ "Server port.")
+
+(defvar emms-stream-info-found nil
+ "Results of our search.")
+
+(defvar emms-stream-info-playlist-found nil
+ "Results of our playlist search.")
+
+(defvar emms-stream-info-procname "emms-stream-info-process"
+ "Name of network connection process.")
+
+(defvar emms-stream-info-downloaded 0
+ "Amount of stream data downloaded.")
+
+(defvar emms-stream-info-read-inhibit nil
+ "When t do not attempt to read 'emms-stream-info-found'.")
+
+(defvar emms-stream-info-return-hook nil
+ "Activated after the disconnection from the streaming audio server.")
+
+(defvar emms-stream-info-read-hook nil
+ "Activated after the disconnection from the streaming audio
+server. This hook is for integration purposes, for general user
+functions use 'emms-stream-info-return-hook'.")
+
+(defvar emms-stream-info-header-flag nil
+ "Non-nil means header information has been captured.")
+
+(defvar emms-stream-info-title-flag nil
+ "Non-nil means title information has been captured.")
+
+(defvar emms-stream-info-playlist-flag nil
+ "Non-nil means playlist information has been captured.")
+
+(defvar emms-stream-info-request-string nil
+ "String sent to streaming audio server.")
+
+(defun emms-stream-info-decompose-url (urlstr)
+ "Return a vector containing the elements of the URI URLSTR."
+ (let ((host nil)
+ (file nil)
+ (port nil)
+ (protocol nil)
+ (user nil) ; nil
+ (pass nil) ; nil
+ (refs nil) ; nil
+ (attr nil) ; nil
+ (full nil)
+ (pos 1))
+ (with-temp-buffer
+ (insert urlstr)
+ (goto-char (point-min))
+ (if (looking-at "http")
+ (progn
+ (forward-char 4)
+ (setq protocol (buffer-substring-no-properties pos (point)))
+ (setq pos (point))))
+ (skip-chars-forward "://")
+ (setq pos (point))
+ (skip-chars-forward "^/")
+ (setq host (buffer-substring pos (point)))
+ (if (string-match ":\\([0-9+]+\\)" host)
+ (setq port (string-to-number (match-string 1 host))
+ host (substring host 0 (match-beginning 0))))
+ (setq pos (point))
+ (setq file (buffer-substring pos (point-max)))
+ (setq full (buffer-substring (point-min) (point-max))))
+ ;; Return in format compatible with 'url-generic-parse-url'.
+ (vector protocol user pass host port file refs attr full)))
+
+;; This is our tiny state machine for keeping track across multiple
+;; connections.
+(defvar emms-stream-info-state-bv
+ (make-bool-vector 3 nil)
+ "State of sequential connections.
+true at index 0 means output formatted message.
+true at index 1 means insert formatted message.
+trye at index 2 means continue to next connection.")
+
+;; This bit is ugly and non-lispish, but asynchronous communications
+;; need a state machine. Better to do it with a macro. and once
+;; everything works I will too!
+(defun emms-stream-info-set-message ()
+ (aset emms-stream-info-state-bv 0 t))
+(defun emms-stream-info-unset-message ()
+ (aset emms-stream-info-state-bv 0 nil))
+(defun emms-stream-info-message-p ()
+ (aref emms-stream-info-state-bv 0))
+
+(defun emms-stream-info-set-insert ()
+ (aset emms-stream-info-state-bv 1 t))
+(defun emms-stream-info-unset-insert ()
+ (aset emms-stream-info-state-bv 1 nil))
+(defun emms-stream-info-insert-p ()
+ (aref emms-stream-info-state-bv 1))
+
+(defun emms-stream-info-set-continue ()
+ (aset emms-stream-info-state-bv 2 t))
+(defun emms-stream-info-unset-continue ()
+ (aset emms-stream-info-state-bv 2 nil))
+(defun emms-stream-info-continue-p ()
+ (aref emms-stream-info-state-bv 2))
+
+(defun emms-stream-info-playlist-type (str)
+ (if (stringp str)
+ (cond ((string-match emms-stream-info-pls-regexp str)
+ 'pls)
+ ((string-match emms-stream-info-m3u-regexp str)
+ 'm3u)
+ (t nil))
+ nil))
+
+(defun emms-stream-info-format (str format-alist)
+ (let ((key-list (mapcar 'car format-alist)))
+ (setq key-list (mapcar 'car format-alist))
+ (mapc (lambda (e)
+ (setq str
+ (replace-regexp-in-string
+ e
+ (cdr (assoc e format-alist))
+ str)))
+ key-list))
+ str)
+
+;; Output a human readable message
+(defun emms-stream-info-pretty-print (&optional string-out)
+ "Output a human readable message. If STRING-OUT is non-nil, do
+not output a message and only return a string."
+ (let (str
+ (format-string emms-stream-info-format-string)
+ (format-alist
+ (list
+ (cons "%b" (or (emms-stream-info-get-key "br")
+ (emms-stream-info-get-key "bitrate")
+ ""))
+ (cons "%s" (or (emms-stream-info-get-key "songtitle") ""))
+ (cons "%c" (or (emms-stream-info-get-key "name") ""))
+ (cons "%t" (or (emms-stream-info-get-key "title") ""))
+ (cons "%g" (or (emms-stream-info-get-key "genre") ""))
+ (cons "%a" (or (emms-stream-info-get-key "artist") ""))
+ (cons "%. " "")))) ; clean untreated tags
+
+ ;; Choose alternate string format if necessary
+ (unless (emms-stream-info-get-key "title")
+ (setq format-string emms-stream-info-format-string-notitle))
+
+ ;; format according to the format-string
+ (setq str
+ (emms-stream-info-format
+ format-string
+ format-alist))
+
+ ;; Escape rougue percent signs hiding in our string.
+ (setq str (replace-regexp-in-string "%" "%%" str))
+
+ ;; Either output a message or return a string. But only if it is
+ ;; an identifiable station/channel
+ (when (emms-stream-info-get-key "name")
+ (if string-out
+ str
+ (message str)))))
+
+(defun emms-stream-info-pretty-print-insert ()
+ "Insert the formatted output of 'emms-stream-info-get' at point."
+ (insert (or (emms-stream-info-pretty-print t) "")))
+
+(defun emms-stream-info-continue ()
+ (emms-stream-info-unset-continue)
+ (if emms-stream-info-playlist-found
+ (emms-stream-info-get emms-stream-info-playlist-found
+ (emms-stream-info-message-p)
+ (emms-stream-info-insert-p)
+ nil)
+ (error "No playlist found at URL")))
+
+;; Useful
+(defun list-to-string (l)
+ "Return a STRING which is the concatenation of the elements of
+L."
+ (if (not l)
+ nil
+ (if (stringp (car l))
+ (concat (car l) (list-to-string (cdr l)))
+ (list-to-string (cdr l)))))
+
+(defun emms-stream-info-get-key (key)
+ "Return STRING associated with KEY."
+ (unless emms-stream-info-read-inhibit
+ (cdr (assoc key emms-stream-info-found))))
+
+(defun emms-stream-info-get-keys (keys)
+ "Return a list of strings associated with each key in
+KEYS. KEYS should be a list of strings."
+ (mapcar (lambda (e)
+ (emms-stream-info-get-key e))
+ keys))
+
+;; BEGIN to END should typically be a segment of about 250 Bytes
+;; length for Ogg streams.
+(defun emms-stream-info-decode-ogg (begin end)
+ "Parse Ogg stream segment from BEGIN to END."
+ (let ((artist nil)
+ (title nil))
+
+ (goto-char begin)
+ (re-search-forward (concat "artist=\\("
+ emms-stream-info-icecast-regexp
+ "\\)") end t)
+ (setq artist (match-string-no-properties 1))
+
+ (goto-char begin)
+ (re-search-forward (concat "title=\\("
+ emms-stream-info-icecast-regexp
+ "\\)") end t)
+ (setq title (match-string-no-properties 1))
+
+ ;; ugh
+ (if (or artist title)
+ (list (cons "songtitle" (concat artist
+ (if (and artist title)
+ " - "
+ " ")
+ title))
+ (cons "artist" artist)
+ (cons "title" title))
+ nil)))
+
+;; BEGIN to END should be about 20 Bytes long
+(defun emms-stream-info-decode-mp3 (begin end)
+ "Parse Shoutcast/Icecast-MP3 segment from BEGIN to END."
+ (let ((split nil)
+ (songtitle nil)
+ (artist nil)
+ (title nil))
+
+ (goto-char begin)
+ (setq songtitle (buffer-substring begin end)
+ split (split-string songtitle "-"))
+
+ (if (cdr split)
+ (setq artist (car split)
+ title (list-to-string (cdr split))))
+
+ (list (cons "songtitle" songtitle)
+ (cons "artist" artist)
+ (cons "title" title))))
+
+(defun emms-stream-info-filter (proc str)
+ "Filter function for the network process.
+Argument PROC Process.
+Argument STR Quanta of data."
+
+ ;; Debugging flag dependent
+ (if emms-stream-info-debugging
+ (with-current-buffer emms-stream-info-debug-buffer
+ (insert str)))
+
+ (with-temp-buffer
+ (setq emms-stream-info-downloaded (+ emms-stream-info-downloaded
+ (length str)))
+
+ ;; Insert a quanta of data.
+ (insert str)
+
+ ;; Look for headers
+ (unless emms-stream-info-header-flag
+ (mapcar (lambda (term)
+ (goto-char (point-min))
+ (if (re-search-forward
+ (concat (regexp-opt
+ (list "icy-" "ice-"))
+ term
+ ":\\("
+ emms-stream-info-stream-header-regexp
+ "\\)")
+ (point-max) t)
+ (progn
+ (add-to-list 'emms-stream-info-found
+ (cons term
+ (match-string-no-properties 1)))
+ (setq emms-stream-info-header-flag t))))
+ emms-stream-info-vocab))
+
+ ;; Look for title
+ (unless emms-stream-info-title-flag
+ (goto-char (- (point)
+ (length str)))
+ (cond ((re-search-forward
+ emms-stream-info-icecast-capture-pattern
+ (point-max)
+ t)
+ (setq emms-stream-info-found
+ (append
+ emms-stream-info-found
+ (emms-stream-info-decode-ogg
+ (match-beginning 1)
+ (match-end 1))))
+ (setq emms-stream-info-title-flag t))
+ ;; In retrospect this section mimics input_http.c from
+ ;; the Xine project only that it uses buffer searching.
+ ((re-search-forward
+ emms-stream-info-shoutcast-title-regexp
+ (point-max)
+ t)
+ (setq emms-stream-info-found
+ (append emms-stream-info-found
+ (emms-stream-info-decode-mp3
+ (match-beginning 1)
+ (match-end 1))))
+ (setq emms-stream-info-title-flag t))))
+
+ ;; Too many nested conditions
+ (if (emms-stream-info-set-continue)
+ (unless emms-stream-info-playlist-flag
+ (goto-char (point-min))
+ (if (re-search-forward
+ emms-stream-info-playlist-regexp
+ (point-max) t)
+ (progn
+ (setq emms-stream-info-playlist-found
+ (or (match-string-no-properties 1)
+ (match-string-no-properties 2)))
+ (setq emms-stream-info-playlist-flag t))))))
+
+ ;; Be chatty at the user
+ (if emms-stream-info-verbose
+ (message "Connection %s. Downloaded %d/%d bytes."
+ (process-status proc)
+ emms-stream-info-downloaded
+ emms-stream-info-max))
+
+ ;; Find out if we need to kill the connection
+ (if (or (> emms-stream-info-downloaded emms-stream-info-max) ; maxed out?
+ ;; Captured header and title info?
+ (and emms-stream-info-header-flag emms-stream-info-title-flag)
+ ;; Captured playlist info?
+ emms-stream-info-playlist-flag)
+ (emms-stream-info-kill-process proc)))
+
+;; Closing the connection proves to be the most difficult part of the
+;; program. There is a difference in the way emacs21 vs. emacs22
+;; behave.
+(defun emms-stream-info-kill-process (proc)
+ "Hold Emacs while trying to close the connection.
+Argument PROC Process."
+ (while (not (equal (process-status proc) 'closed))
+ (delete-process proc))
+ (if (process-filter proc)
+ (set-process-filter proc nil))
+ ;; Workaround Emacs 21 sentinel problems
+ (when (= emacs-major-version 21)
+ (emms-stream-info-after-function)))
+
+(defun emms-stream-info-after-function ()
+ "Evalutated when the connection ends."
+ (setq emms-stream-info-read-inhibit nil) ; allow reading
+ (run-hooks 'emms-stream-info-read-hook)
+ (run-hooks 'emms-stream-info-return-hook))
+
+(defun emms-stream-info-sentinel (proc ev)
+ "Sentinel function for network process.
+Argument PROC Process.
+Argument EV Event string."
+ ;; Workaround Emacs 21 sentinel problems
+ (unless (= emacs-major-version 21)
+ (emms-stream-info-after-function)))
+
+(defun emms-stream-info-make-request-string (file)
+ "Return a valid HTTP request string with FILE as a URI."
+ (concat "GET "
+ (if (equal file "")
+ "/"
+ file)
+ " HTTP/1.0\r\n"
+ "User-Agent: Free software (see www.gnu.org), reads title of currently playing track (discards audio).\r\n"
+ "Icy-MetaData:1\r\n"
+ "\r\n"))
+
+(defun emms-stream-info-parse-url (urlstring)
+ "Set the global variables for connecting to the streaming audio
+server at URLSTRING."
+ (let* ((url (emms-stream-info-decompose-url urlstring))
+ (hostname (elt url 3))
+ (port (elt url 4))
+ (file (elt url 5))
+ (protocol (elt url 0)))
+
+ (cond ((or (not (equal protocol "http"))
+ (equal hostname ""))
+ (error "Invalid URL"))
+
+ ;; eg. "http://music.station.com:8014"
+ ((and (empty-string-p file)
+ port)
+ (setq emms-stream-info-port port))
+
+ ;; eg. "http://ogg.smgradio.com/vr96.ogg"
+ ((and (not (empty-string-p file))
+ (or (equal port "")
+ (equal port nil)
+ (equal port 0)))
+ (setq emms-stream-info-port 80))
+
+ ;; eg. "http://audio.ibiblio.org:8010/wcpe.ogg"
+ ((and (not (empty-string-p file))
+ port)
+ (setq emms-stream-info-port port))
+
+ (t (error "Invalid URL")))
+
+ (setq emms-stream-info-url hostname
+ emms-stream-info-request-string
+ (emms-stream-info-make-request-string file))))
+
+(defun empty-string-p (str)
+ "Return t if STR is equal to the empty string."
+ (equal str ""))
+
+(defun emms-stream-info-reset-state ()
+ (setq emms-stream-info-downloaded 0) ; restart fallback
+ (setq emms-stream-info-title-flag nil) ; forget title flag
+ (setq emms-stream-info-header-flag nil) ; forget header flag
+ (setq emms-stream-info-found nil) ; forget output
+ (setq emms-stream-info-playlist-found nil) ; forget playlist
+ (setq emms-stream-info-playlist-flag nil) ; forget playlist
+ (setq emms-stream-info-read-inhibit t) ; do not read output
+
+ ;; Reset state machine
+ (emms-stream-info-unset-message)
+ (emms-stream-info-unset-insert)
+ (emms-stream-info-unset-continue)
+
+ ;; forget hooks
+ (remove-hook 'emms-stream-info-return-hook
+ 'emms-stream-info-pretty-print)
+ (remove-hook 'emms-stream-info-return-hook
+ 'emms-stream-info-continue)
+ (remove-hook 'emms-stream-info-return-hook
+ 'emms-stream-info-pretty-print-insert))
+
+;; -------------------------------------------------------------------
+;; Interactive functions
+;; -------------------------------------------------------------------
+
+(defun emms-stream-info-get (&optional urlstring say write cont)
+ "Get streaming audio server header metadata and song title from stream at URL.
+Argument URLSTRING Address of streaming audio server as a string.
+If URLSTRING is nil then get the latest stream played via emms.
+Optional argument SAY boolean.
+Optional argument WRITE boolean.
+Optional argument CONT boolean."
+ (interactive)
+
+ (if urlstring
+ (emms-stream-info-parse-url urlstring)
+ (emms-stream-info-parse-url
+ ;; possible bug, what if there is no last stream?
+ (emms-stream-url emms-stream-last-stream)))
+
+ (emms-stream-info-reset-state)
+
+ ;; Output formatted text as a message.
+ (if say
+ (progn
+ (add-hook 'emms-stream-info-return-hook
+ 'emms-stream-info-pretty-print)
+ (emms-stream-info-set-message)))
+ ;; Insert formatted text into the current buffer.
+ (if write
+ (progn
+ (add-hook 'emms-stream-info-return-hook
+ 'emms-stream-info-pretty-print-insert)
+ (emms-stream-info-set-insert)))
+ ;; Continue to the next connection after this one.
+ (if cont
+ (progn
+ (add-hook 'emms-stream-info-return-hook
+ 'emms-stream-info-continue)
+ (emms-stream-info-set-continue)))
+
+ ;; Debugging flag dependent
+ (if emms-stream-info-debugging
+ (progn
+ (if (get-buffer emms-stream-info-debug-buffer)
+ (kill-buffer emms-stream-info-debug-buffer))
+ (get-buffer-create emms-stream-info-debug-buffer)))
+
+ ;; Open connection
+ (condition-case nil
+ (if (fboundp 'make-network-process)
+ (make-network-process :name emms-stream-info-procname
+ :buffer nil
+ :host emms-stream-info-url
+ :service emms-stream-info-port)
+ (open-network-stream emms-stream-info-procname
+ nil
+ emms-stream-info-url
+ emms-stream-info-port))
+ (error
+ (emms-stream-info-reset-state)
+ (message "Error connecting to streaming audio sever at %s"
+ emms-stream-info-url)))
+
+ (let ((proc (get-process emms-stream-info-procname)))
+ (when proc
+
+ ;; Connection timeone
+ (run-at-time emms-stream-info-timeout
+ nil
+ 'emms-stream-info-kill-process
+ proc)
+
+ ;; Start download
+ (process-send-string emms-stream-info-procname
+ emms-stream-info-request-string)
+ (set-process-sentinel proc
+ 'emms-stream-info-sentinel)
+ (set-process-filter proc
+ 'emms-stream-info-filter)
+ (unless (process-sentinel proc)
+ (error "No process sentinel")))))
+
+;; Users. You can never tell what they are going to use as input.
+(defun emms-stream-info-input-sanity (&optional urlstring)
+ (let ((type (emms-track-type (emms-playlist-current-track))))
+ (cond ((null urlstring)
+ (if (or (equal type 'playlist)
+ (equal type 'url))
+ (emms-track-name (emms-playlist-current-track))))
+ ((not (stringp urlstring))
+ (error "URL must be in string format"))
+ ((stringp url) urlstring))))
+
+(defun emms-stream-info-message (&optional urlstring)
+ "Get information from streaming audio server at URLSTRING.
+Return a formatted message.
+URLSTRING should be a string."
+ (interactive)
+ (let ((url (emms-stream-info-input-sanity urlstring)))
+ (cond ((equal (emms-stream-info-playlist-type url) 'pls)
+ (emms-stream-info-get url t nil t))
+ ((equal (emms-stream-info-playlist-type url) 'm3u)
+ (emms-stream-info-get url t nil t))
+ (t (emms-stream-info-get url t)))))
+
+;; Insertion does not work for sequential connections.
+(defun emms-stream-info-insert (&optional urlstring)
+ "Get information from streaming audio server at URLSTRING.
+Insert a formatted message at point.
+URLSTRING should be a string."
+ (interactive)
+ (let ((url (emms-stream-info-input-sanity urlstring)))
+ (cond ((equal (emms-stream-info-playlist-type url) 'pls)
+ (emms-stream-info-get url nil t t))
+ ((equal (emms-stream-info-playlist-type url) 'm3u)
+ (emms-stream-info-get url nil t t))
+ (t (emms-stream-info-get url nil t)))))
+
+(provide 'emms-stream-info)
+
+;;; emms-stream-info.el ends here
diff --git a/emms-streams.el b/emms-streams.el
new file mode 100644
index 0000000..e879618
--- /dev/null
+++ b/emms-streams.el
@@ -0,0 +1,445 @@
+;; emms-streams.el -- interface to add and play streams
+
+;; This program is free software; you can redistribute it and/or
+;; modify it under the terms of the GNU General Public License
+;; as published by the Free Software Foundation; either version 2
+;; of the License, or (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+;; 02110-1301 USA
+
+;; Commentary:
+
+;; It is part of the EMMS package
+
+;; Heavily based on bmk-mgr.el by Jose A Ortega Ruiz <jao@gnu.org>
+;; thanks to you !
+
+;; Code:
+
+(defvar emms-stream-bookmarks-file "~/.emacs.d/emms-streams"
+ "The file where you store your favorite emms streams")
+
+(defvar emms-stream-list nil
+ "The list that contains your current stream bookmarks.")
+
+(defvar emms-stream-buffer-name "*EMMS Streams*"
+ "The name of the buffer used by emms-stream interface.")
+
+(defvar emms-stream-play-hook nil
+ "*A hook run when you add or play an EMMS stream via the popup.")
+
+(defvar emms-stream-current-stream nil
+ "The stream currently being played.
+Needed by the info method, as the track doesn't contain all the
+needed info.")
+
+(defvar emms-stream-default-action "add"
+ "The default action when you press RET in the EMMS Stream interface.
+Can be either \"add\" or \"play\". The default is \"add\".")
+
+(defvar emms-stream-last-stream nil
+ "The last stream added/played by EMMS.")
+
+(defface emms-stream-name-face '((t (:bold t :foreground nil :weight bold)))
+ "Face for stream names.")
+
+(defface emms-stream-url-face '((t (:foreground "LightSteelBlue")))
+ "Face for stream URLs.")
+
+;; Format: (("descriptive name" url feed-number type))
+;; type could be either url or playlist. If url, then it represents a
+;; direct IP, otherwite it's a stream playlist
+(defvar emms-stream-default-list
+ '(("SomaFM: Beatblender"
+ "http://www.somafm.com/beatblender.pls" 1 playlist)
+ ("SomaFM: Secret Agent"
+ "http://www.somafm.com/secretagent.pls" 1 playlist)
+ ("SomaFM: Groove Salad"
+ "http://www.somafm.com/groovesalad.pls" 1 playlist)
+ ("SomaFM: Drone Zone"
+ "http://www.somafm.com/dronezone.pls" 1 playlist)
+ ("SomaFM: Tag's Trance"
+ "http://www.somafm.com/tagstrance.pls" 1 playlist)
+ ("Digitally Imported, Trance"
+ "http://www.digitallyimported.com/mp3/trance.pls" 1 playlist)
+ ("Digitally Imported, Deephouse"
+ "http://www.digitallyimported.com/mp3/deephouse.pls" 1 playlist)
+ ("Digitally Imported, Mostly Classical"
+ "http://www.digitallyimported.com/mp3/classical.pls" 1 playlist)
+ ("Digitally Imported, Chillout"
+ "http://www.digitallyimported.com/mp3/chillout.pls" 1 playlist)
+ ("Digitally Imported, Drum and Bass"
+ "http://www.digitallyimported.com/mp3/drumandbass.pls" 1 playlist)
+ ("Philosomatika, Goa-Trance"
+ "http://www.philosomatika.com/Philosomatika.pls" 1 playlist)
+ ("Drum and Bass Radio, BassDrive"
+ "http://www.bassdrive.com/BassDrive.m3u" 1 playlist)
+ ("Flaresound, Jazzmusique"
+ "http://64.236.34.196:80/stream/1016" 1 url)
+ ("Flaresound, Jazzmusique"
+ "http://205.188.234.4:8004" 2 url)
+ ("Flaresound, L'Electric"
+ "http://www.bp6.com:8002" 1 url)
+ ("Stangs Garage, Eclectic"
+ "http://www.stangsgarage.com/listen.pls" 1 playlist)
+ ("DNA Lounge, Live"
+ "http://www.dnalounge.com/audio/128.m3u" 1 playlist)
+ ("Virgin Radio, The Groove"
+ "http://www.smgradio.com/core/audio/ogg/live.pls?service=grbb" 1 playlist)
+ ("Virgin Radio, Virgin Classic"
+ "http://www.smgradio.com/core/audio/ogg/live.pls?service=vcbb" 1 playlist)
+ ("Virgin Radio, Virgin 1215AM"
+ "http://www.smgradio.com/core/audio/ogg/live.pls?service=vrbb" 1 playlist)
+ ("WCPE, Classical Music"
+ "http://www.ibiblio.org/wcpe/wcpe.pls" 1 playlist)))
+
+(defvar emms-stream-mode-map
+ (let ((map (make-keymap)))
+ (suppress-keymap map)
+ (define-key map [(control ?a)] 'beginning-of-line)
+ (define-key map [(control ?e)] 'end-of-line)
+ (define-key map [(control ?k)] 'emms-stream-kill-bookmark)
+ (define-key map [(control ?y)] 'emms-stream-yank-bookmark)
+ (define-key map [(control ?n)] 'emms-stream-next-line)
+ (define-key map [(control ?p)] 'emms-stream-previous-line)
+ (define-key map [?Q] 'emms-stream-quit)
+ (define-key map [?a] 'emms-stream-add-bookmark)
+ (define-key map [?d] 'emms-stream-delete-bookmark)
+ (define-key map [?e] 'emms-stream-edit-bookmark)
+ (define-key map [?h] 'describe-mode)
+ (define-key map [?n] 'emms-stream-next-line)
+ (define-key map [?p] 'emms-stream-previous-line)
+ (define-key map [?q] 'emms-stream-quit)
+ (define-key map [?s] 'emms-stream-save-bookmarks-file)
+;; (define-key map [?u] 'emms-stream-move-bookmark-up)
+ (define-key map [?i] 'emms-stream-info-bookmark)
+ (define-key map (kbd "<up>") 'emms-stream-previous-line)
+ (define-key map (kbd "<down>") 'emms-stream-next-line)
+ (define-key map (kbd "<left>") 'beginning-of-line)
+ (define-key map (kbd "<right>") 'end-of-line)
+ (define-key map (kbd "RET") 'emms-stream-play)
+ map)
+ "Keymap for `emms-stream-menu'.")
+
+(defun emms-stream-line-number-at-pos (&optional pos)
+ "Return (narrowed) buffer line number at position POS.
+If POS is nil, use current buffer location."
+ (let ((opoint (or pos (point))) start)
+ (save-excursion
+ (goto-char (point-min))
+ (setq start (point))
+ (goto-char opoint)
+ (forward-line 0)
+ (1+ (count-lines start (point))))))
+
+(defun emms-streams ()
+ "Opens the EMMS Streams interface."
+ (interactive)
+ (kill-buffer (get-buffer-create emms-stream-buffer-name))
+ (set-buffer (get-buffer-create emms-stream-buffer-name))
+ (erase-buffer)
+ (emms-stream-mode)
+ (switch-to-buffer emms-stream-buffer-name))
+
+(defun emms-stream-mode ()
+ (kill-all-local-variables)
+ (buffer-disable-undo)
+ (setq major-mode 'emms-stream-mode)
+ (setq mode-name "EMMS Streams")
+ (use-local-map emms-stream-mode-map)
+ (emms-stream-init)
+ (set (make-local-variable 'truncate-lines) t)
+ (set (make-local-variable 'automatic-hscrolling) t)
+ (set (make-local-variable 'kill-whole-line) t)
+ (set (make-local-variable 'next-line-add-newlines) nil)
+ (goto-char 1)
+ (emms-stream-display)
+ (toggle-read-only 1)
+ (message "EMMS Stream Menu"))
+
+(defun emms-stream-popup-revert ()
+ "Revert to the window-configuration from before if there is one,
+otherwise just remove the special bindings from the stream menu."
+ (interactive)
+ (remove-hook 'emms-pbi-manually-change-song-hook 'emms-pbi-popup-revert)
+ (let ((streambuffer (get-buffer emms-stream-buffer-name)))
+ (when streambuffer
+ (save-excursion
+ (set-buffer streambuffer)
+ ;; (local-unset-key (kbd "q"))
+ (local-unset-key (kbd "TAB")))))
+ ;; (local-unset-key (kbd "RET")))))
+ (when emms-stream-popup-old-conf
+ (set-window-configuration emms-stream-popup-old-conf))
+ (remove-hook 'emms-stream-play-hook 'emms-stream-popup-revert)
+ (remove-hook 'emms-stream-quit-hook 'emms-stream-popup-revert))
+
+(defun emms-stream-popup (&optional popup-height)
+ "Pops up the stream Menu, for the new stream selection.
+
+POPUP-HEIGHT is the height of the new frame, defaulting to
+`emms-popup-default-height'."
+ (interactive)
+ (setq popup-height (or popup-height (/ (window-height) 2)))
+ ;; Split the current screen, and make the stream menu popup
+ (let ((new-window-height (- (window-height) popup-height)))
+ (if (not (> new-window-height 0))
+ (error "Current window too small to popup menu!"))
+ ;; Save the current window-configuration
+ (setq emms-stream-popup-old-conf (current-window-configuration))
+ ;; Split and select the menu
+ (let ((buffer-down
+ (split-window-vertically new-window-height)))
+ (select-window buffer-down))
+
+ (kill-buffer (get-buffer-create emms-stream-buffer-name))
+ (switch-to-buffer (get-buffer-create emms-stream-buffer-name))
+ (erase-buffer)
+ (emms-stream-mode)
+
+ (add-hook 'emms-stream-play-hook 'emms-stream-popup-revert)
+ (add-hook 'emms-stream-quit-hook 'emms-stream-popup-revert)
+ (local-set-key (kbd "TAB") 'emms-stream-popup-revert)
+ (local-set-key (kbd "RET") 'emms-stream-play)
+ ;; (local-set-key (kbd "q") 'delete-window)
+ ;; Also, forget about the whole thing if the user does something
+ ;; to the window-configuration
+ ;; (add-hook 'window-configuration-change-hook 'emms-stream-popup-forget-conf)))
+ ))
+
+(defun emms-stream-init ()
+ (setq emms-stream-list (emms-stream-read-file emms-stream-bookmarks-file)))
+
+(defun emms-stream-read-file (file)
+ "Returns a sexp."
+ (let ((file (expand-file-name file)))
+ (if (file-readable-p file)
+ (with-temp-buffer
+ (insert-file-contents-literally file)
+ (goto-char (point-min))
+ (read (current-buffer)))
+ emms-stream-default-list)))
+
+(defun emms-stream-save-bookmarks-file ()
+ (interactive)
+ (let ((buffer (find-file-noselect emms-stream-bookmarks-file)))
+ (set-buffer buffer)
+ (erase-buffer)
+ (prin1 emms-stream-list buffer)
+ (save-buffer)
+ (kill-buffer buffer)))
+
+(defun emms-stream-display-line (line)
+ (insert (emms-stream-name line))
+ (add-text-properties (point-at-bol) (point-at-eol) '(face emms-stream-name-face))
+ (add-text-properties (point-at-bol) (point-at-eol) `(emms-stream ,line))
+ (insert "\n ")
+ (insert (emms-stream-url line))
+ (add-text-properties (point-at-bol) (point-at-eol) '(face emms-stream-url-face))
+ (insert "\n"))
+
+(defun emms-stream-display ()
+ "Displays the bookmark list in the current buffer, in a human
+ readable way."
+ (mapc 'emms-stream-display-line emms-stream-list)
+ (goto-char (point-min)))
+
+;; Helper functions
+(defun take (n list)
+ "Takes N elements from LIST."
+ (let ((idx 0)
+ (res '()))
+ (while (< idx n)
+ (setq res (append res (list (nth idx list))))
+ (setq idx (+ idx 1)))
+ res))
+
+(defun insert-at (n elt list)
+ "Inserts the element ELT in LIST, *before* position N.
+Positions are counted starting with 0."
+ (let* ((n-1 (- n 1))
+ (before (take n-1 list))
+ (after (last list (- (length list) n-1))))
+ (append before (list elt) after)))
+
+(defun emms-stream-get-bookmark-at-point ()
+ "Returns the bookmark under point."
+ (get-text-property (point) 'emms-stream))
+
+
+(defun emms-stream-redisplay ()
+ (let ((inhibit-read-only t))
+ (erase-buffer)
+ (goto-char 1)
+ (emms-stream-display)))
+
+(defun emms-stream-add-bookmark (name url fd type)
+ "Creates a new bookmark, and inserts it at point position.
+
+Don't forget to run `emms-stream-save-bookmarks-file' after !"
+ (interactive "sName of the bookmark:
+sURL:
+nFeed descriptor:
+SType (url or playlist): ")
+ (let* ((line (emms-stream-line-number-at-pos (point)))
+ (index (+ (/ line 2) 1)))
+ (setq emms-stream-list (insert-at index (list name url fd type) emms-stream-list))
+ (emms-stream-redisplay)
+ (goto-line line)))
+
+(defun emms-stream-delete-bookmark ()
+ "Deletes the bookmark under the point.
+
+Don't forget to save your modifications !"
+ (interactive)
+ (let ((line (emms-stream-line-number-at-pos (point))))
+ (setq emms-stream-list
+ (remove (emms-stream-get-bookmark-at-point) emms-stream-list))
+ (emms-stream-redisplay)
+ (goto-line line)))
+
+(defun emms-stream-edit-bookmark ()
+ "Change the information of current bookmark."
+ (interactive)
+ (let* ((bookmark (emms-stream-get-bookmark-at-point))
+ (name (read-from-minibuffer "Description: "
+ (emms-stream-name bookmark)))
+ (url (read-from-minibuffer "URL: "
+ (emms-stream-url bookmark)))
+ (fd (read-from-minibuffer "Feed Descriptor: "
+ (int-to-string (emms-stream-fd bookmark))))
+ (type (read-from-minibuffer "Type (url or playlist): "
+ (format "%s" (emms-stream-type bookmark)))))
+ (emms-stream-delete-bookmark)
+ (emms-stream-add-bookmark name url (string-to-int fd) type)))
+
+(defun emms-stream-name (el)
+ (car el))
+(defun emms-stream-url (el)
+ (cadr el))
+(defun emms-stream-fd (el)
+ (caddr el))
+(defun emms-stream-type (el)
+ (cadddr el))
+
+(defun emms-stream-play ()
+ (interactive)
+ (let* ((line (get-text-property (point) 'emms-stream))
+ (name (emms-stream-name line))
+ (url (emms-stream-url line))
+ (fd (emms-stream-fd line))
+ (type (emms-stream-type line))
+ (player (read (concat "emms-" emms-stream-default-action "-" (format "%s" type)))))
+ (setq emms-stream-last-stream line)
+;; (funcall emms-stream-default-action url)
+ (funcall player url)
+ (if (string= emms-stream-default-action "add")
+ (message "URL added to playlist")))
+ (later-do 'emms-mode-line-alter)
+ (run-hooks 'emms-stream-play-hook))
+
+(defun emms-stream-info-bookmark ()
+ "Return the station and track information for the streaming audio station under point."
+ (interactive)
+ (if (fboundp 'emms-stream-info-message)
+ (let* ((line (get-text-property (point) 'emms-stream))
+ (url (emms-stream-url line)))
+ (emms-stream-info-message url))
+ (message "Streaming media info not available.")))
+
+;; Navigation
+(defun emms-stream-next-line ()
+ (interactive)
+ (forward-line 2))
+
+(defun emms-stream-previous-line ()
+ (interactive)
+ (forward-line -2))
+
+(defun emms-stream-quit ()
+ (interactive)
+ (kill-this-buffer)
+ (run-hooks 'emms-stream-quit-hook))
+
+(defun emms-stream-toggle-default-action ()
+ (interactive)
+ (if (string= emms-stream-default-action "play")
+ (progn
+ (setq emms-stream-default-action "add")
+ (message "Default action is now add"))
+ (setq emms-stream-default-action "play")
+ (message "Default action is now play")))
+
+;; info part
+(define-emms-info-method emms-info-url
+ :providep 'emms-info-url-providep
+ :get 'emms-info-url-get)
+;; :set 'emms-info-url-set)
+
+;; A way to get the last element. it is either the only one, or the
+;; last one added by emms-add-url. so in both cases, that's what we
+;; want.
+(defun emms-stream-last-element ()
+ (elt emms-playlist (- (length emms-playlist) 1)))
+
+(defun emms-info-url-providep (track)
+ (if (eq (emms-track-type track) 'url)
+ t
+ nil))
+
+(defun emms-info-url-get (track)
+ (make-emms-info
+ :title (emms-stream-url (emms-track-get track 'metadata))
+ :artist (emms-stream-name (emms-track-get track 'metadata))
+ :album " "
+ :note " "
+ :year " "
+ :genre " "
+ :file (emms-stream-url (emms-track-get track 'metadata))))
+
+;; Then you register it with emms-info, by adding it to
+;; `emms-info-methods-list'.
+
+(add-to-list 'emms-info-methods-list 'emms-info-url)
+
+(defun emms-info-file-info-song-artist (track)
+ "Returns a description of TRACK, build from its comments.
+
+If `emms-info-methods-list' indicates how to retrieve special info
+about it, use this. Otherwise returns the name alone."
+ (if (not (and track (emms-track-name track)))
+ "Invalid track!"
+ (let ((info (emms-info-get track)))
+ (if (eq (emms-info-method-for track) 'emms-info-url)
+ (progn
+ (concat (emms-info-artist info) " - " (emms-info-title info)))
+ (if (and info (not (string= (emms-info-artist info) "")) (not (string= (emms-info-title info) "")))
+ (concat (emms-info-artist info) " - " (emms-info-title info))
+ (file-name-sans-extension (file-name-nondirectory (emms-track-name track))))))))
+
+(defun emms-stream-add-data-to-track (track)
+ (emms-track-set track 'metadata emms-stream-last-stream))
+
+(setq emms-track-initialize-functions '(emms-stream-add-data-to-track))
+
+(when (featurep 'emms-info)
+ (eval-when-compile (require 'emms-info)) ; appease byte-compiler
+ (add-to-list 'emms-info-methods-list 'emms-info-playlist)
+ (defun emms-info-playlist-providep (track)
+ (if (eq (emms-track-type track) 'playlist)
+ t
+ nil))
+ (define-emms-info-method emms-info-playlist
+ :providep 'emms-info-playlist-providep
+ :get 'emms-info-url-get))
+
+(provide 'emms-streams)
+;;; emms-streams.el ends here
diff --git a/emms-tageditor.el b/emms-tageditor.el
new file mode 100644
index 0000000..f093d85
--- /dev/null
+++ b/emms-tageditor.el
@@ -0,0 +1,455 @@
+;;; emms-tageditor.el --- Info-editor for EMMS
+
+;; Copyright (C) 2004 Free Software Foundation, Inc.
+
+;; Author: Ulrik Jensen <terryp@daimi.au.dk>
+;; Keywords:
+
+;; This file is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; This file is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING. If not, write to the
+;; Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+;; Boston, MA 02110-1301 USA
+
+;;; Commentary:
+
+;; This file provides an info-editor for EMMS. It also provides, as an
+;; option, functions for integrating this with the
+;; playlist-buffer-interface as well as the mark-module for the pbi.
+
+;; To activate the basic editor, use this in your EMMS-configuration:
+
+;; (require 'emms-tageditor)
+
+;; And call M-x emms-tageditor-edit-current RET, when you find a song
+;; with a tag that needs to be changed.
+
+;; To use the pbi-functionality, add the following to your
+;; configuration:
+
+;; (emms-tageditor-pbi-mode 1)
+
+;; Which will add 'e' as a key, to edit the track under the point in
+;; the pbi.
+
+;; To use the extended pbi-functionality, with `emms-pbi-mark', use:
+
+;; (emms-tageditor-pbi-mark-mode 1)
+
+;; Which likewise will bind 'E' to edit all the tracks marked as a
+;; whole, in a way that allows you to set the "album"-tag of each
+;; track, or f.ex. to match the filename to a regexp, and set a group
+;; from that regexp as the title-tag, or any other.
+
+;;; Code:
+
+(require 'emms)
+(require 'emms-info)
+(require 'emms-pbi)
+(require 'emms-pbi-mark)
+(require 'widget)
+(require 'wid-edit)
+
+;; Custom
+(defgroup emms-tageditor nil
+ "*A wizard-style tageditor for EMMS."
+ :group 'emms
+ :prefix "emms-tageditor-")
+
+(defcustom emms-tageditor-buffer-name "*emms-tageditor*"
+ "The name of the buffer used for tag-editing."
+ :type 'string
+ :group 'emms-tageditor)
+
+;; Variables
+(defvar emms-tageditor-current-tracks []
+ "A vector of the tracks currently being edited.")
+
+(defvar emms-tageditor-current-infos []
+ "A vector of the info-ojects currently being edited.")
+
+(defvar emms-tageditor-message nil
+ "A message to show with the form, if non-nil.")
+
+(defvar emms-tageditor-widgets nil
+ "A hash map of the widgets on the screen.")
+
+;; Hashtable interface
+(defun emms-tageditor-get-widget (trackidx fieldname)
+ "Return the widget of FIELDNAME, for the track with TRACKIDX"
+ (let ((symbol (emms-tageditor-get-widget-id trackidx fieldname)))
+ (gethash symbol emms-tageditor-widgets)))
+
+(defun emms-tageditor-set-widget (trackidx fieldname widget)
+ "Save a reference to WIDGET, as FIELDNAME of the track with TRACKIDX."
+ (let ((symbol (emms-tageditor-get-widget-id trackidx fieldname)))
+ (puthash symbol widget emms-tageditor-widgets)))
+
+;; Helper function for the hashtable
+(defun emms-tageditor-get-widget-id (trackidx fieldname)
+ "Get a symbol ID of the FIELDNAME widget of TRACKIDX."
+ (intern (concat "-trackidx-" (number-to-string trackidx)
+ "-" (symbol-name fieldname))))
+
+(defun emms-tageditor-read-tag (trackidx)
+ "Read the form for a single track, and parse it into an info-object."
+ (let ((info (aref emms-tageditor-current-infos trackidx))
+ (track (aref emms-tageditor-current-tracks trackidx))
+ (tags '(title artist album note)))
+ (while tags
+ (let* ((tag (car tags))
+ (infotag (intern (concat "emms-info-" (symbol-name tag)))))
+ (eval `(setf (,infotag ,info)
+ ,(widget-value (emms-tageditor-get-widget
+ trackidx
+ tag)))))
+ (setq tags (cdr tags)))
+ info))
+
+;; Parsing the form
+(defun emms-tageditor-read-tags (tracks)
+ "Create a new list of info-object from the form, and return it."
+ (mapcar 'emms-tageditor-read-tag tracks))
+
+;; Event-handling
+(defun emms-tageditor-save (widget &rest ignore)
+ "Save the info of a single tag."
+ ;; This function only saves a single form. Figure out which track
+ ;; this is bound to, by extracting the trackidx from the
+ (let* ((trackidx (widget-get widget :trackidx))
+ (track (aref emms-tageditor-current-tracks trackidx)))
+ ;; Let's make sure we have an info-source capable of writing tags.
+ (if (funcall (emms-info-method-for track) 'set)
+ (progn
+ ;; we have an info-method for it, let's set it.
+ (emms-info-set track
+ (emms-tageditor-read-tag trackidx))
+ (when (and (featurep 'emms-pbi)
+ (get-buffer emms-pbi-playlist-buffer-name))
+ ;; If a playlist is available, it's info might need to be updated
+ ;; for this track.
+ (emms-pbi-entry-update-track track)
+ (when (= (length emms-tageditor-current-tracks) 1)
+ ;; pressing save should kill the buffer when only one track is
+ ;; being edited.
+ (emms-tageditor-cleanup))))
+ ;; if the above returned nil, no function to save info for this
+ ;; track has been made! signal an error and escape!
+ (message (format (concat "Track %s doesn't have an associated info-method "
+ " capable of saving data")
+ (emms-track-name track))))))
+
+(defun emms-tageditor-save-all ()
+ "Save all entries currently being edited."
+ ;; Loop through all forms, and save them
+ (let ((idx 0))
+ (while (< idx (length emms-tageditor-current-tracks))
+ (emms-tageditor-save (emms-tageditor-get-widget idx 'save))
+ (setq idx (1+ idx))))
+ ;; Always cleanup when saving everything
+ (emms-tageditor-cleanup))
+
+(defun emms-tageditor-cancel (&rest ignore)
+ (emms-tageditor-cleanup))
+
+(defun emms-tageditor-create-string (rep times)
+ "Concat TIMES occurances of REP into a string and return it."
+ (if (> times 1)
+ (concat (emms-tageditor-create-string rep (1- times)) rep)
+ rep))
+
+;; Setting up the form, and destroying it
+(defun emms-tageditor-create-widgets (trackidx info)
+ "Create widgets for a single track-form"
+ (let ((inhibit-read-only t)
+ (track (aref emms-tageditor-current-tracks trackidx))
+ (info (aref emms-tageditor-current-infos trackidx)))
+ (goto-char (point-min))
+ (widget-insert (format "Editing tag for track: %s (%s)\n"
+ (emms-track-name track)
+ (symbol-name
+ (emms-track-type track))))
+ (widget-insert (concat "----------------------------------------"
+ "----------------------------------------"
+ "\n"))
+ ;; Insert the tags
+ (let ((tags '(title artist album note)))
+ (while tags
+ (let* ((tag (car tags))
+ (info-tag (intern (concat "emms-info-" (symbol-name tag))))
+ (tag-name
+ (concat (upcase-initials (symbol-name tag)) ":"
+ (emms-tageditor-create-string " "
+ (- 10 (1+ (length (symbol-name tag))))))))
+ (widget-insert tag-name)
+ (eval `(emms-tageditor-set-widget
+ trackidx (quote ,tag)
+ (widget-create 'editable-field
+ :size 69
+ :trackidx trackidx
+ :value (,info-tag ,info))))
+ (when (not (= (length tags) 1))
+ (widget-insert "\n")))
+ (setq tags (cdr tags))))
+ ;; Insert the rest
+ (widget-insert (concat "\n"
+ "----------------------------------------"
+ "----------------------------------------"
+ "\n"))
+ (emms-tageditor-set-widget
+ trackidx 'save
+ (widget-create 'push-button
+ :notify 'emms-tageditor-save
+ :trackidx trackidx
+ :help-echo "Save changes to this tag"
+ "Save"))
+ (widget-insert " ")
+ (widget-create 'push-button
+ :notify 'emms-tageditor-cancel
+ :help-echo "Cancel changes"
+ "Cancel")))
+
+(defun emms-tageditor-cleanup ()
+ "Clean up and exit the tageditor."
+ ;; delete all widgets
+ (let ((idx 0))
+ (while (< idx (length emms-tageditor-current-tracks))
+ (when emms-tageditor-widgets
+ (let ((tags '(title artist album note save)))
+ (while tags
+ (let ((tag (car tags)))
+ (eval `(widget-delete (emms-tageditor-get-widget idx tag))))
+ (setq tags (cdr tags)))))
+ ;; continue idx loop
+ (setq idx (1+ idx))))
+ ;; kill the buffer & delete the hashmap
+ (setq emms-tageditor-widgets nil)
+ (kill-buffer (get-buffer-create emms-tageditor-buffer-name)))
+
+(defun emms-tageditor-replace-regexp (regexp rep string &optional fixedcase literal subexp start)
+ "Compatibility wrapper for replace-regexp-in-string/replace-in-string."
+ (if (featurep 'xemacs)
+ (replace-in-string regexp rep string fixedcase literal subexp start)
+ (replace-regexp-in-string regexp rep string fixedcase literal subexp start)))
+
+(defun emms-tageditor-replace-create-replacement (replace-with trackidx)
+ (let ((info (aref emms-tageditor-current-infos trackidx))
+ (track (aref emms-tageditor-current-tracks trackidx)))
+ (setq replace-with (emms-tageditor-replace-regexp "$TITLE" (emms-info-title info) replace-with))
+ (setq replace-with (emms-tageditor-replace-regexp "$ALBUM" (emms-info-album info) replace-with))
+ (setq replace-with (emms-tageditor-replace-regexp "$ARTIST" (emms-info-artist info) replace-with))
+ (setq replace-with (emms-tageditor-replace-regexp "$NOTE" (emms-info-note info) replace-with))
+ (setq replace-with (emms-tageditor-replace-regexp "$TRACKNAME" (emms-track-name track) replace-with)))
+ replace-with)
+
+(defun emms-tageditor-replace-tag (field regexp replace-with)
+ "Replace REGEXP with REPLACE-WITH in all fields of type FIELD."
+ (let ((idx 0))
+ (while (< idx (length emms-tageditor-current-tracks))
+ ;; Find the widget for the current track
+ (let ((widget (emms-tageditor-get-widget idx field)))
+ (let* ((str (widget-value widget))
+ (str (emms-tageditor-replace-regexp regexp replace-with str)))
+ (if (string= "$SET" regexp)
+ (widget-value-set
+ widget
+ (emms-tageditor-replace-create-replacement replace-with idx))
+ (widget-value-set
+ widget
+ (emms-tageditor-replace-create-replacement str idx)))))
+ (setq idx (1+ idx)))))
+
+(defun emms-tageditor-replace-tags (&optional field regexp replace-with)
+ "Replace REGEXP with REPLACE-WITH in the widgets matching FIELD."
+ (interactive)
+ (setq field (or field (intern (completing-read
+ "Select which tags to replace in: "
+ '(("all" . all) ("title" . title)
+ ("artist" . artist) ("album" . album)
+ ("note" . note))
+ nil t "title"))))
+ (setq regexp (or regexp (read-from-minibuffer "Regexp to replace: ")))
+ (setq replace-with (or replace-with (read-from-minibuffer (concat "Replace regexp " regexp " with: "))))
+ ;; Having all input, let's continue to act on it.
+ (when (and field regexp replace-with)
+ ;; two cases, 'all or something else
+ (if (equal field 'all)
+ (progn
+ ;; We need a sweep-search of all tag-fields
+ (let ((tags '(title artist album note)))
+ (while tags
+ (emms-tageditor-replace-tag (car tags) regexp replace-with)
+ (setq tags (cdr tags)))))
+ ;; only search the field called field
+ (emms-tageditor-replace-tag field regexp replace-with))
+ ;; we've probably changed some widget values, so we need to make
+ ;; them count.
+ (widget-setup)))
+
+;; Setting up the buffer
+(defun emms-tageditor-edit (tracks &optional infos)
+ "Open an editor for the vector TRACKS.
+
+Optionally, use the vector INFOS as the default info for each track,
+and use the function SAVEFUNCTION as the event-handler for each
+save-button."
+ ;; Save variables
+ (setq emms-tageditor-current-tracks tracks)
+ (if infos
+ (setq emms-tageditor-current-infos infos)
+ ;; Otherwise, create the vector of infos by loading them.
+ (setq emms-tageditor-current-infos
+ (make-vector (length emms-tageditor-current-tracks)
+ nil))
+ (let ((idx 0))
+ (while (< idx (length emms-tageditor-current-tracks))
+ (setf (aref emms-tageditor-current-infos idx)
+ ;; should we allow cache here?
+ (emms-info-get (aref emms-tageditor-current-tracks idx)))
+ (setq idx (1+ idx)))))
+ ;; Kill the buffer, then recreate it. Otherwise, everything will be
+ ;; in one big widget.
+ (kill-buffer (get-buffer-create emms-tageditor-buffer-name))
+ (switch-to-buffer (get-buffer-create emms-tageditor-buffer-name))
+ ;; Initialise buffer
+ (kill-all-local-variables)
+ (widget-minor-mode 1)
+ ;; Setup widget hashmap,
+ (setq emms-tageditor-widgets (make-hash-table :test 'equal))
+ ;; and create the widgets
+ (let ((idx 0))
+ (while (< idx (length emms-tageditor-current-tracks))
+ (emms-tageditor-create-widgets idx
+ (aref emms-tageditor-current-infos idx))
+ (widget-insert "\n\n")
+ (setq idx (1+ idx)))
+ ;; Create the save _all_ widget?
+ ;; setup the help-message
+ (when emms-tageditor-message
+ (goto-char (point-max))
+ (widget-insert (concat "\n"
+ "........................................"
+ "........................................"
+ "\n"))
+ (widget-insert emms-tageditor-message)
+ (setq emms-tageditor-message nil))
+ (use-local-map widget-keymap)
+ ;; Bind some additional keys
+ (widget-setup)
+ (local-set-key (kbd "C-x C-s") (lambda () (interactive) (emms-tageditor-save-all)))
+ (local-set-key (kbd "C-c C-r") 'emms-tageditor-replace-tags)
+ (local-set-key (kbd "ESC") (lambda () (interactive) (emms-tageditor-cancel)))))
+
+;; Entry function
+(defun emms-tageditor-edit-current ()
+ "Edit the info of the currently playing track"
+ (interactive)
+ (emms-tageditor-edit (vconcat (list (emms-playlist-current-track)))))
+
+;; Integrating with emms-pbi
+(defvar emms-tageditor-pbi-mark-message
+ "When editing multiple files, some things works a bit
+differently. First of all, to save *all* changes made to tracks, use
+C-x C-s.
+
+The changes to each individual track, can be saved by using the
+corresponding Save-buttons.
+
+To utilize the full power of this mode of editing, you should use
+M-x emms-tageditor-replace-tags RET, bound to C-c C-r.
+
+When using `emms-tageditor-replace-tags', you have the following
+special keyword available:
+
+For matching:
+
+ $SET -- Attempting to replace this value with anything, will tell
+ the function to simply override the previous value.
+
+For what to replace with:
+
+ $TRACKNAME -- The trackname (for file-type tracks, the full filename)
+ $TITLE -- The (saved) title of this track.
+ $ARTIST -- Likewise, with the artist
+ $ALBUM -- Likewise, with the album
+ $ALBUM -- Likewise, with the note.
+
+NOT IMPLEMENTED YET:
+If the power of that function doesn't fit your needs, you can use M-x
+emms-tageditor-toggle-read-only RET, bound to C-c C-t. This function
+will make the buffer read-only, which means you can use the regular
+editing functions on the entire buffer. This means that doing an M-x
+replace-regexp RET, won't halt if it matches any of the text outsie
+widgets, as it would otherwise.")
+
+(defun emms-tageditor-pbi-mode (&optional arg)
+ "Register the intergration with the playlist-buffer interface for EMMS.
+
+Turn the registration on, if and only if ARG is a positive integer,
+off otherwise."
+ (interactive "p")
+ (if (not (featurep 'emms-pbi))
+ (message "You need `emms-pbi' loaded to use this!")
+ (if (and (numberp arg) (< 0 arg))
+ (add-hook 'emms-pbi-mode-hook 'emms-tageditor-pbi-register)
+ (remove-hook 'emms-pbi-mode-hook 'emms-tageditor-pbi-register))))
+
+(defun emms-tageditor-pbi-register ()
+ "Register keybindings for the playlist-buffer interface.
+
+Should be run in `emms-pbi-mode-hook'."
+ (local-set-key (kbd "e")
+ 'emms-tageditor-pbi-edit-current-line))
+
+(defun emms-tageditor-pbi-edit-current-line ()
+ "Edit the track under point."
+ (interactive)
+ (if (not (featurep 'emms-pbi))
+ (message "You need `emms-pbi' loaded to use this!")
+ ;; Fetch track under current line
+ (let ((curidx (emms-pbi-return-current-line-index)))
+ (when (emms-pbi-valid-index-p curidx)
+ (other-window 1)
+ (emms-tageditor-edit (vconcat (list (emms-playlist-get-track curidx))))))))
+
+;; Integrating with pbi-mark
+(defun emms-tageditor-pbi-mark-mode (&optional arg)
+ "Register the intergration with the playlist-buffer-marks for EMMS.
+
+Turn the integration on, if and only if ARG is a positive integer, off
+otherwise."
+ (interactive "p")
+ (if (not (featurep 'emms-pbi-mark))
+ (message "You need `emms-pbi-mark' loaded to use this!")
+ (if (and (numberp arg) (< 0 arg))
+ (add-hook 'emms-pbi-mode-hook 'emms-tageditor-pbi-mark-register)
+ (remove-hook 'emms-pbi-mode-hook 'emms-tageditor-pbi-mark-register))))
+
+(defun emms-tageditor-pbi-mark-register ()
+ "Register keybindings for the playlist-buffer interface marking functions.
+
+Should be run in `emms-pbi-mode-hook'."
+ (local-set-key (kbd "E")
+ 'emms-tageditor-pbi-mark-edit-marked-entries))
+
+(defun emms-tageditor-pbi-mark-edit-marked-entries ()
+ "Edit all marked entries as one, using a special editor."
+ (interactive)
+ (if (not (featurep 'emms-pbi-mark))
+ (message "You need `emms-pbi-mark' loaded to use this!")
+ (other-window 1)
+ (setq emms-tageditor-message emms-tageditor-pbi-mark-message)
+ (emms-tageditor-edit (vconcat (emms-pbi-mark-get-marked)))
+ (goto-char (point-min))))
+
+(provide 'emms-tageditor)
+;;; emms-tageditor.el ends here
diff --git a/emms.el b/emms.el
new file mode 100644
index 0000000..a010ce5
--- /dev/null
+++ b/emms.el
@@ -0,0 +1,676 @@
+;;; emms.el --- The Emacs Multimedia System
+
+;; Copyright (C) 2003, 2004, 2005 Jorgen Schfer
+
+;; Author: Jorgen Schfer <forcer@forcix.cx>
+;; Keywords: emms, mp3, mpeg, multimedia
+
+;; This file is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+;;
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING. If not, write to the
+;; Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+;; Boston, MA 02110-1301 USA
+
+;;; Commentary:
+
+;; This is the very core of EMMS. It provides ways to play a track
+;; using `emms-start', to go through the playlist using the commands
+;; `emms-next' and `emms-previous', to stop the playback using
+;; `emms-stop', and to see what's currently playing using `emms-show'.
+
+;; But in itself, this core is useless, because it doesn't know how to
+;; play any tracks --- you need players for this. In fact, it doesn't
+;; even know how to find any tracks to consider playing --- for this,
+;; you need sources.
+
+;; A sample configuration is offered in emms-default.el, so you might
+;; just want to use that file.
+
+;;; Code:
+
+;; $Id: emms.el,v 1.63 2005/08/18 13:52:23 forcer Exp $
+(defvar emms-version "1.3 $Revision: 1.63 $"
+ "EMMS version string.")
+
+(defmacro emms-define-obsolete-variable-alias
+ (obsolete-name current-name &optional when docstring)
+ "Make OBSOLETE-NAME an obsolete variable alias for CURRENT-NAME.
+See `define-obsolete-variable-alias' in Emacs 22.1 and above."
+ `(progn
+ (when (fboundp 'defvaralias)
+ (defvaralias ,obsolete-name ,current-name ,docstring))
+ (make-obsolete-variable ,obsolete-name ,current-name ,when)))
+
+(defmacro emms-define-obsolete-function-alias
+ (obsolete-name current-name &optional when docstring)
+ "Make OBSOLETE-NAME an obsolete function alias for CURRENT-NAME.
+See `define-obsolete-function-alias' in Emacs 22.1 and above."
+ `(progn
+ (defalias ,obsolete-name ,current-name ,docstring)
+ (make-obsolete ,obsolete-name ,current-name ,when)))
+
+
+;;; User Customization
+
+(defgroup emms nil
+ "*The Emacs Multimedia System."
+ :prefix "emms-"
+ :group 'multimedia
+ :group 'applications)
+
+(defgroup emms-player nil
+ "*Track players for EMMS."
+ :prefix "emms-player-"
+ :group 'emms)
+
+(defgroup emms-source nil
+ "*Track sources for EMMS."
+ :prefix "emms-source-"
+ :group 'emms)
+
+(defcustom emms-player-list nil
+ "*List of players that EMMS can use. You need to set this!"
+ :group 'emms
+ :type '(repeat (symbol :tag "Player")))
+
+(defcustom emms-show-format "Currently playing: %s"
+ "*The format to use for `emms-show'.
+Any \"%s\" is replaced by what `emms-track-description-function' returns
+for the currently playing track."
+ :group 'emms
+ :type 'string)
+
+(defcustom emms-repeat-playlist nil
+ "*Non-nil if the EMMS playlist should automatically repeat.
+If nil, playback will stop when the last track finishes playing.
+If non-nil, EMMS will wrap back to the first track when that happens."
+ :group 'emms
+ :type 'boolean)
+
+(defcustom emms-repeat-track nil
+ "Non-nil, playback will repeat current track. If nil, EMMS will play
+track by track normally."
+ :group 'emms
+ :type 'boolean)
+
+(defcustom emms-track-description-function 'emms-track-description
+ "*Function for describing an EMMS track in a user-friendly way."
+ :group 'emms
+ :type 'function)
+
+(defcustom emms-player-delay 0
+ "The delay to pause after a player finished.
+This is a floating-point number of seconds.
+This is necessary for some platforms where it takes a bit to free
+the audio device after a player has finished. If EMMS is skipping
+songs, increase this number."
+ :type 'number
+ :group 'emms)
+
+(defcustom emms-sort-lessp-function 'emms-sort-track-name-less-p
+ "*Function for comparing two EMMS tracks.
+The function should return non-nil if and only if the first track
+sorts before the second (see `sort')."
+ :group 'emms
+ :type 'function)
+
+(defcustom emms-playlist-changed-hook nil
+ "*Hook run after the EMMS playlist changes."
+ :group 'emms
+ :type 'hook)
+
+(emms-define-obsolete-variable-alias
+ 'emms-playlist-current-changed-hook
+ 'emms-playlist-current-track-changed-hook)
+
+(defcustom emms-playlist-current-track-changed-hook nil
+ "*Hook run after another track is selected in the EMMS playlist."
+ :group 'emms
+ :type 'hook)
+
+(defcustom emms-track-initialize-functions nil
+ "*List of functions to call for each new EMMS track.
+This can be used to initialize tracks with various info."
+ :group 'emms
+ :type 'hook)
+
+(defcustom emms-player-started-hook nil
+ "*Hook run when an EMMS player starts playing."
+ :group 'emms
+ :type 'hook
+ :options '(emms-show))
+
+(defcustom emms-player-stopped-hook nil
+ "*Hook run when an EMMS player is stopped by the user.
+See `emms-player-finished-hook'."
+ :group 'emms
+ :type 'hook)
+
+(defcustom emms-player-finished-hook '(emms-next-noerror)
+ "*Hook run when an EMMS player finishes playing a track.
+Please pay attention to the differences between
+`emms-player-finished-hook' and `emms-player-stopped-hook'.
+The former is called only when the player is stopped interactively;
+the latter, only when the player actually finishes playing a track."
+ :group 'emms
+ :type 'hook
+ :options '(emms-next-noerror))
+
+(defvar emms-player-playing-p nil
+ "The currently playing EMMS player, or nil.")
+
+(defvar emms-playlist []
+ "The current EMMS playlist: a vector of tracks.")
+(defvar emms-playlist-current nil
+ "The zero-based playlist index of the current EMMS track.
+If there is no playlist, this will be set to nil.")
+
+(defcustom emms-playlist-sort-added-tracks-p nil
+ "*If non-nil, sort tracks before adding them to the EMMS playlist."
+ :group 'emms
+ :type 'boolean)
+
+(emms-define-obsolete-variable-alias
+ 'emms-sort-on-file-add
+ 'emms-playlist-sort-added-tracks-p)
+
+
+;;; User Interface
+
+(defun emms-start ()
+ "Start playing the current track in the EMMS playlist."
+ (interactive)
+ (unless emms-player-playing-p
+ (emms-player-start (emms-playlist-current-track))))
+
+(defun emms-stop ()
+ "Stop any current EMMS playback."
+ (interactive)
+ (when emms-player-playing-p
+ (emms-player-stop)))
+
+(defun emms-next ()
+ "Start playing the next track in the EMMS playlist.
+This might behave funny if called from `emms-player-finished-hook',
+so use `emms-next-noerror' in that case."
+ (interactive)
+ (when emms-player-playing-p
+ (emms-stop))
+ (if (emms-playlist-next)
+ (emms-start)
+ (error "No next track in playlist")))
+
+(defun emms-next-noerror ()
+ "Start playing the next track in the EMMS playlist.
+Unlike `emms-next', this function doesn't signal an error when called
+at the end of the playlist.
+This function should only be called when no player is playing.
+This is a good function to put in `emms-player-finished-hook'."
+ (interactive)
+ (when emms-player-playing-p
+ (error "A track is already being played"))
+ (cond (emms-repeat-track
+ (emms-start))
+ ((emms-playlist-next)
+ (emms-start))
+ (emms-repeat-playlist
+ (setq emms-playlist-current 0)
+ (emms-start))
+ (t
+ (message "No next track in playlist"))))
+
+(defun emms-previous ()
+ "Start playing the previous track in the EMMS playlist."
+ (interactive)
+ (when emms-player-playing-p
+ (emms-stop))
+ (if (emms-playlist-previous)
+ (emms-start)
+ (error "No previous track in playlist")))
+
+(defun emms-show (&optional insertp)
+ "Describe the current EMMS track in the minibuffer.
+If INSERTP is non-nil, insert the description into the current buffer instead.
+This function uses `emms-show-format' to format the current track."
+ (interactive "P")
+ (let ((string (format emms-show-format (emms-playlist-current))))
+ (if insertp
+ (insert string)
+ (message "%s" string))))
+
+(defun emms-shuffle ()
+ "Shuffle the EMMS playlist."
+ (interactive)
+ (emms-playlist-shuffle))
+
+(defun emms-sort ()
+ "Sort the EMMS playlist."
+ (interactive)
+ (emms-playlist-sort))
+
+(defun emms-toggle-repeat-playlist ()
+ "Toggle whether emms repeats the playlist after it is done.
+See `emms-repeat-playlist'."
+ (interactive)
+ (setq emms-repeat-playlist (not emms-repeat-playlist))
+ (if emms-repeat-playlist
+ (message "Will repeat the playlist after it is done.")
+ (message "Will stop after the playlist is over.")))
+
+(defun emms-toggle-repeat-track ()
+ "Toggle whether emms repeats the current track.
+See `emms-repeat-track'."
+ (interactive)
+ (setq emms-repeat-track (not emms-repeat-track))
+ (if emms-repeat-track
+ (message "Will repeat the current track.")
+ (message "Will advance to the next track after this one.")))
+
+(defun emms-sort-track-name-less-p (a b)
+ "Return non-nil if the track name of A sorts before B."
+ (string< (emms-track-name a)
+ (emms-track-name b)))
+
+
+;;; Tracks
+
+;; This is a simple datatype to store track information.
+;; Each track consists of a type (a symbol) and a name (a string).
+;; In addition, each track has an associated dictionary of information.
+
+(defun emms-track (type name)
+ "Create an EMMS track with type TYPE and name NAME."
+ (let ((track (emms-dictionary '*track*)))
+ (emms-track-set track 'type type)
+ (emms-track-set track 'name name)
+ (run-hook-with-args 'emms-track-initialize-functions track)
+ track))
+
+(defun emms-track-type (track)
+ "Return the type of TRACK."
+ (emms-track-get track 'type))
+
+(defun emms-track-name (track)
+ "Return the name of TRACK."
+ (emms-track-get track 'name))
+
+(defun emms-track-get (track name &optional default)
+ "Return the value of NAME for TRACK.
+If there is no value, return DEFAULT (or nil, if not given)."
+ (emms-dictionary-get track name default))
+
+(defun emms-track-set (track name value)
+ "Set the value of NAME for TRACK to VALUE."
+ (emms-dictionary-set track name value))
+
+(defun emms-track-description (track)
+ "Simple function to give a user-readable description of a track.
+If it's a file track, just return the file name.
+Otherwise, return the type and the name with a colon in between."
+ (if (eq 'file (emms-track-type track))
+ (emms-track-name track)
+ (concat (symbol-name (emms-track-type track))
+ ":"
+ (emms-track-name track))))
+
+
+;;; The Playlist
+
+;; This is a simple vector storing the current playlist. You should avoid
+;; accessing the vector directly, and use the functions provided here instead.
+;; If you can't avoid accessing the vector directly, be careful to call the
+;; right hooks at the right times.
+
+(defun emms-playlist-current ()
+ "Return a description of the currently playing EMMS track.
+This function uses `emms-track-description-function'."
+ (funcall emms-track-description-function
+ (emms-playlist-current-track)))
+
+(defun emms-playlist-current-track ()
+ "Return the currently playing EMMS track."
+ (when emms-playlist-current
+ (emms-playlist-get-track emms-playlist-current)))
+
+(defun emms-playlist-get-track-description (track)
+ "Return a description of TRACK.
+This uses `emms-track-description-function'."
+ (funcall emms-track-description-function track))
+
+(defun emms-playlist-get (n)
+ "Return a description of the Nth entry of the current EMMS playlist.
+This uses `emms-track-description-function'"
+ (funcall emms-track-description-function
+ (emms-playlist-get-track n)))
+
+(defun emms-playlist-get-track (n)
+ "Return the Nth track of the current EMMS playlist."
+ (aref emms-playlist n))
+
+(defun emms-playlist-set-playlist (new)
+ "Set the current EMMS playlist to NEW.
+This runs `emms-playlist-changed-hook'."
+ (setq emms-playlist new)
+ (cond
+ ((= 0 (length new))
+ (setq emms-playlist-current nil))
+ ((null emms-playlist-current)
+ (setq emms-playlist-current 0))
+ ((>= emms-playlist-current (length emms-playlist))
+ (setq emms-playlist-current (- (length emms-playlist) 1))))
+ (run-hooks 'emms-playlist-changed-hook))
+
+(defun emms-playlist-get-playlist ()
+ "Return the current EMMS playlist.
+Avoid changing the structure returned by this function."
+ emms-playlist)
+
+(defun emms-playlist-set-current (n)
+ "Set the current track in the EMMS playlist to N (a number).
+This runs `emms-playlist-current-track-changed-hook'."
+ (setq emms-playlist-current n)
+ (run-hooks 'emms-playlist-current-track-changed-hook))
+
+(defun emms-playlist-get-current ()
+ "Return the index number of the current EMMS track.
+If the playlist is empty, returns nil."
+ emms-playlist-current)
+
+(defun emms-playlist-next ()
+ "Advance to the next entry in the EMMS playlist.
+Return nil if there was no next track, or non-nil otherwise."
+ (let ((cur (emms-playlist-get-current)))
+ (when (and cur
+ (< cur (- (length (emms-playlist-get-playlist)) 1)))
+ (emms-playlist-set-current (+ 1 cur))
+ t)))
+
+(defun emms-playlist-previous ()
+ "Back up to the previous entry in the EMMS playlist.
+Return nil if there was no previous track, or non-nil otherwise."
+ (let ((cur (emms-playlist-get-current)))
+ (when (and cur
+ (> cur 0))
+ (emms-playlist-set-current (- cur 1))
+ t)))
+
+(defun emms-playlist-add (seq &optional idx)
+ "Add each track of the sequence SEQ to the current playlist.
+Insert at IDX, which defaults to the end."
+ (let ((idx (or idx (length emms-playlist))))
+ (emms-playlist-set-playlist
+ (vconcat (substring emms-playlist 0 idx)
+ (if emms-playlist-sort-added-tracks-p
+ (emms-playlist-sort-vector seq)
+ seq)
+ (substring emms-playlist idx)))))
+
+(defun emms-playlist-remove (idx)
+ "Remove track at IDX from the EMMS playlist."
+ (emms-playlist-set-playlist
+ (vconcat (substring emms-playlist 0 idx)
+ (substring emms-playlist (1+ idx)))))
+
+(defun emms-playlist-search-vector (track vector)
+ "Return the index of TRACK in VECTOR, or nil if not found.
+Comparison is done with `eq'."
+ (catch 'loop
+ (let ((i 0))
+ (while (< i (length vector))
+ (if (eq track
+ (elt vector i))
+ (throw 'loop i)
+ (setq i (1+ i)))))))
+
+(defun emms-playlist-shuffle ()
+ "Shuffle the current EMMS playlist.
+If a track is currently being played, it will end up at the front
+of the playlist after shuffling."
+ (if (not emms-player-playing-p)
+ (emms-playlist-set-playlist
+ (emms-playlist-shuffle-vector
+ (emms-playlist-get-playlist)))
+ (let* ((current-track (emms-playlist-current-track))
+ (playlist (emms-playlist-shuffle-vector
+ (emms-playlist-get-playlist)))
+ (new-index (emms-playlist-search-vector current-track playlist))
+ (first (elt playlist 0)))
+ (aset playlist 0 (elt playlist new-index))
+ (aset playlist new-index first)
+ (emms-playlist-set-playlist playlist)
+ (emms-playlist-set-current 0))))
+
+(defun emms-playlist-sort ()
+ "Sort the current EMMS playlist.
+Comparison is done with `emms-sort-lessp-function'.
+If a song is currently being played, it will remain the current track
+after sorting, though its index may change as appropriate."
+ (if (not emms-player-playing-p)
+ (emms-playlist-set-playlist
+ (emms-playlist-sort-vector
+ (emms-playlist-get-playlist)))
+ (let* ((current-track (emms-playlist-current-track))
+ (playlist (emms-playlist-sort-vector
+ (emms-playlist-get-playlist)))
+ (new-index (emms-playlist-search-vector current-track playlist)))
+ (emms-playlist-set-playlist playlist)
+ (emms-playlist-set-current new-index))))
+
+(defun emms-playlist-shuffle-vector (vector)
+ "Shuffle VECTOR."
+ (let ((i (- (length vector) 1)))
+ (while (>= i 0)
+ (let* ((r (random (1+ i)))
+ (old (aref vector r)))
+ (aset vector r (aref vector i))
+ (aset vector i old))
+ (setq i (- i 1))))
+ vector)
+
+(defun emms-playlist-sort-vector (vector)
+ "Sort VECTOR according to `emms-sort-lessp-function'."
+ (vconcat (sort (append vector nil)
+ emms-sort-lessp-function)))
+
+
+;;; User-defined playlists.
+(defmacro define-emms-playlist (name shufflep tracklist)
+ "Define a `emms-play-X' and `emms-add-X' function for TRACKLIST."
+ `(define-emms-source ,name ()
+ "An EMMS source for a tracklist."
+ (interactive)
+ (let* ((new (apply #'append
+ (mapcar (lambda (source)
+ (apply (car source)
+ (cdr source)))
+ ,tracklist))))
+ ,(if shufflep
+ '(append (emms-playlist-shuffle-vector (vconcat new)) nil)
+ 'new))))
+
+
+;;; Sources
+
+;; A source is just a function that returns a list of tracks.
+;; The define-emms-source macro also defines functions emms-play-SOURCE
+;; and emms-add-SOURCE. The former will replace the current playlist,
+;; while the latter will add to the end.
+
+(defmacro define-emms-source (name arglist &rest body)
+ "Define a new EMMS source called NAME.
+This macro defines three functions: `emms-source-NAME', `emms-play-NAME'
+and `emms-add-NAME'. BODY should evaluate do a list of tracks to be played,
+which is exactly what `emms-source-NAME' will return.
+The other two functions will be simple wrappers around `emms-source-NAME';
+any `interactive' form that you specify in BODY will end up in these.
+See emms-source-file.el for some examples."
+ (let ((source-name (intern (format "emms-source-%s" name)))
+ (source-play (intern (format "emms-play-%s" name)))
+ (source-add (intern (format "emms-add-%s" name)))
+ (docstring "A source of tracks for EMMS.")
+ (interactive nil)
+ (call-args (delete '&rest
+ (delete '&optional
+ arglist))))
+ (when (stringp (car body))
+ (setq docstring (car body)
+ body (cdr body)))
+ (when (eq 'interactive (caar body))
+ (setq interactive (car body)
+ body (cdr body)))
+ `(progn
+ (defun ,source-name ,arglist
+ ,docstring
+ ,@body)
+ (defun ,source-play ,arglist
+ ,docstring
+ ,interactive
+ (emms-source-play (,source-name ,@call-args)))
+ (defun ,source-add ,arglist
+ ,docstring
+ ,interactive
+ (emms-source-add (,source-name ,@call-args))))))
+
+(defun emms-source-play (lis)
+ "Play the tracks in LIS, after first clearing the EMMS playlist."
+ (let ((new
+ (if emms-playlist-sort-added-tracks-p
+ (emms-playlist-sort-vector (vconcat lis))
+ (vconcat lis))))
+ (when (zerop (length new))
+ (error "No tracks found"))
+ (emms-stop)
+ (emms-playlist-set-playlist new)
+ (emms-playlist-set-current 0)
+ (emms-start)))
+
+(defun emms-source-add (lis)
+ "Add the tracks in LIS to the end of the EMMS playlist."
+ (emms-playlist-add lis))
+
+
+;;; Players
+
+;; A player is a data structure created by `emms-player'.
+;; See the docstring of that function for more information.
+
+(defvar emms-player-stopped-p nil
+ "Non-nil if the last EMMS player was stopped by the user.")
+
+(defun emms-player (start stop playablep)
+ "Create a new EMMS player.
+The start function will be START, and the stop function STOP.
+PLAYABLEP should return non-nil for tracks that this player can play.
+
+When trying to play a track, EMMS walks `emms-player-list'.
+For each player,it calls the PLAYABLEP function.
+The player corresponding to the first PLAYABLEP function that returns
+non-nil is used to play the track.
+To actually play the track, EMMS calls the START function,
+passing the chosen track as a parameter.
+
+If the user tells EMMS to stop playing, the STOP function is called.
+Once the player has finished playing, it should call `emms-player-stopped'
+to let EMMS know."
+ (let ((p (emms-dictionary '*player*)))
+ (emms-player-set p 'start start)
+ (emms-player-set p 'stop stop)
+ (emms-player-set p 'playablep playablep)
+ p))
+
+(defun emms-player-get (player name &optional inexistent)
+ "Return the value of entry NAME in PLAYER."
+ (let ((p (if (symbolp player)
+ (symbol-value player)
+ player)))
+ (emms-dictionary-get p name inexistent)))
+
+(defun emms-player-set (player name value)
+ "Set the value of entry NAME in PLAYER to VALUE."
+ (let ((p (if (symbolp player)
+ (symbol-value player)
+ player)))
+ (emms-dictionary-set p name value)))
+
+(defun emms-player-for (track)
+ "Return an EMMS player capable of playing TRACK.
+This will be the first player whose PLAYABLEP function returns non-nil,
+or nil if no such player exists."
+ (let ((lis emms-player-list))
+ (while (and lis
+ (not (funcall (emms-player-get (car lis) 'playablep)
+ track)))
+ (setq lis (cdr lis)))
+ (if lis
+ (car lis)
+ nil)))
+
+(defun emms-player-start (track)
+ "Start playing TRACK."
+ (if emms-player-playing-p
+ (error "A player is already playing")
+ (let ((player (emms-player-for track)))
+ (if (not player)
+ (error "Don't know how to play track: %s" track)
+ (funcall (emms-player-get player 'start)
+ track)
+ (setq emms-player-playing-p player)
+ (run-hooks 'emms-player-started-hook)))))
+
+(defun emms-player-stop ()
+ "Stop the current EMMS player."
+ (when emms-player-playing-p
+ (let ((emms-player-stopped-p t))
+ (funcall (emms-player-get emms-player-playing-p 'stop)))
+ (setq emms-player-playing-p nil)))
+
+(defun emms-player-stopped ()
+ "Declare that the current EMMS player is finished.
+This should only be done by the current player itself."
+ (setq emms-player-playing-p nil)
+ (if emms-player-stopped-p
+ (run-hooks 'emms-player-stopped-hook)
+ (sleep-for emms-player-delay)
+ (run-hooks 'emms-player-finished-hook)))
+
+
+;;; Dictionaries
+
+;; This is a simple helper data structure, used by both players
+;; and tracks.
+
+(defun emms-dictionary (name)
+ "Create a new dictionary of type NAME."
+ (list name))
+
+(defun emms-dictionary-type (dict)
+ "Return the type of the dictionary DICT."
+ (car dict))
+
+(defun emms-dictionary-get (dict name &optional default)
+ "Return the value of NAME in DICT."
+ (let ((item (assq name (cdr dict))))
+ (if item
+ (cdr item)
+ default)))
+
+(defun emms-dictionary-set (dict name value)
+ "Set the value of NAME in DICT to VALUE."
+ (let ((item (assq name (cdr dict))))
+ (if item
+ (setcdr item value)
+ (setcdr dict (append (cdr dict)
+ (list (cons name value))))))
+ dict)
+
+(provide 'emms)
+;;; emms.el ends here
diff --git a/emms.texinfo b/emms.texinfo
new file mode 100644
index 0000000..2d208a9
--- /dev/null
+++ b/emms.texinfo
@@ -0,0 +1,1255 @@
+\input texinfo @c -*-texinfo-*-
+@c %**start of header
+@setfilename emms.info
+@settitle The Emms Manual 0.1.23
+@c %**end of header
+
+@c CVS info:
+@c $Revision: 1.24 $
+
+@c Maintainer comments:
+@c There is always work to do in a manual.
+
+@dircategory Emacs
+@direntry
+* Emms: (emms). The Emacs Multimedia System
+@end direntry
+
+@copying
+ @copyright{} (c) 2004, 2005
+ Mario Domgoergen, Jorgen Schaefer, Yoni Rabkin
+@quotation
+Permission is granted to copy, distribute and/or modify this document
+under the terms of the GNU Free Documentation License, Version 1.1 or
+any later version published by the Free Software Foundation; with no
+Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A
+copy of the license is included in the section entitled "GNU Free
+Documentation License".
+@end quotation
+@end copying
+
+@c For printed material
+@titlepage
+@title The Emms Manual
+
+@page
+@vskip 0pt plus 1filll
+@insertcopying
+@end titlepage
+
+@contents
+@c END For printed material
+
+@ifnottex
+@node Top, Copying, (dir), (dir)
+@top Emms Manual 0.1.21
+
+This is the Manual for the Emacs Multimedia System
+
+@menu
+* Copying:: The GNU General Public License gives you permission to
+ redistribute Emms on certain terms; it also explains
+ that there is no warranty.
+* The GNU FDL:: The license for this documentation.
+
+Starting out
+* Introduction:: Introduction to Emms
+* Installation:: How to install Emms on your System
+* Quick Setup:: Quick start in Emms
+* Configuration Example:: Bare bones configuration
+
+Emms basics
+* Basic Commands:: How to control Emms with ease
+* The Core File:: The inner core of Emms
+* Sources:: Sources for playlists-creation
+* Simple Players:: Some simple players
+
+Advanced Features
+* Info Tags:: More narrative track descriptions
+* The Playlist Buffer:: Interactive Playlist
+* Scoring:: Playing files based on their rating
+* Extending Emms:: How to define new players and modules
+* Streaming Audio:: Interface to streaming audio
+
+Indices
+* Concept Index::
+* Function Index::
+* Variable Index::
+* Keybinding Index::
+
+@detailmenu
+--- The Detailed Node Listing ---
+
+Here are some other nodes which are really inferiors of the ones
+already listed, mentioned here so you can get to them in one step:
+
+Installation
+* Compiling Emms:: Compiling Emms into Byte-Code
+
+Info Tags
+* Ogg Info:: Reading ogg info tags
+* MP3 Info:: Reading mp3 info tags
+@end detailmenu
+
+The Playlist Buffer
+* Playlist Buffer:: Browsing buffer known by other players
+* Playlist Popup:: Poping-up the playlist buffer
+* Playlist Manipulation:: Some playlist manipulation functions
+
+Extending Emms
+* New Player:: How to define a new player
+
+New Player
+* Simple Player for @command{play}:: Example player using @command{play}
+* More Complex Player:: Example of a complex player using @command{mpg321}
+@end menu
+
+@end ifnottex
+
+@c including the relevant licenses
+@include gpl.texi
+@include fdl.texi
+
+@node Introduction
+@chapter Introduction
+
+@cindex introduction
+
+Emms is the Emacs Multi-Media System. It tries to be a clean and small
+application to play multimedia files from Emacs using external
+players. Many of its ideas are derived from MpthreePlayer
+(http://www.nongnu.org/mp3player), but it tries to be more general and
+more clean.
+
+The basic functionality of Emms consists of three parts: The core, the
+sources, and the players.
+
+The core resides in emms.el, and provides a simple playlist and the
+basic functionality to use all the other features of Emms. It provides
+the common user commands and interfaces for other parts. It thinks in
+tracks, where a track is the combination of a type and a name - e.g.
+the track type 'file has a name that is the file name. Other track
+types are possible.
+
+To get to tracks, the core needs sources. The file emms-source-file.el
+provides simple sources to interact with the file system.
+
+When Emms finally has the sources in the playlist, it needs a player
+to play them. emms-player-simple.el defines a few useful players, and
+allows you to define your own in a very simple way.
+
+Emms is easy to customise by using the modules shipped with
+emms. @xref{Extending Emms}.
+
+Emms can also be customised by using `M-x customize'.
+
+@node Installation
+@chapter Installation
+
+@cindex installation
+
+You need to put all the .el files of emms in a directory in your
+load-path. For example, if you put all those files into ~/elisp/emms/,
+then in your ~/.emacs, you should do:
+
+@lisp
+(add-to-list 'load-path "~/elisp/emms/")
+@end lisp
+@noindent
+
+@menu
+* Compiling Emms:: Compiling Emms into Byte-Code
+@end menu
+
+@node Compiling Emms
+@section Compiling Emms
+
+@cindex compiling
+
+You can byte-compile Emms by first entering the directory containing the
+Emms source code, followed by invoking:
+
+@command{make}
+
+Which will byte compile Emms. You can then invoke:
+
+@command{make install}
+
+Which will install Emms into your Emacs directories (provided you have
+the appropriate permissions to do so on your system).
+
+Note that Emms is a light-weight and agile program, you can therefore
+run Emms just fine without byte compiling it.
+
+@node Quick Setup
+@chapter Quick setup
+
+Emms is quite simple to set up. For the most basic needs, you will
+just need the following line,
+
+@lisp
+(require 'emms)
+@end lisp
+@noindent
+
+which installs the core of Emms.
+
+Now we need to do some configuration.
+
+The Emms module `emms-default' provides the function `emms-setup',
+which is a way to quickly configure your Emms. You can add any number
+of directories which contain media. The first argument is the
+complexity level of the user interface. Here's an example:
+
+@lisp
+(require 'emms-default)
+(emms-setup 'tiny "directory")
+@end lisp
+
+Here is a list of the interface complexity options:
+
+@table @samp
+@item minimalistic
+Define the players and play directory but nothing more.
+@item tiny
+Features the pbi (playlist buffer interface).
+@item default
+Features info reading for MP3 and OGG files.
+@item advanced
+Features the tageditor and playlist manipulation.
+@item cvs
+Features playlist pop-up, pbi marking, mode-line, asynchronous loading
+of tags and (of course) the kitchen sink.
+@end table
+
+Now your configuration is done.
+
+The (optional) directory is used for
+`emms-source-file-default-directory', in case you were wondering.
+
+@node Configuration Example
+@chapter Configuration Example
+
+@cindex Configuration Example
+
+The following code fragment provides a minimal EMMS setup without
+using the layer of `emms-default'. It can maybe be used to better
+understand the internals of EMMS. You can see how EMMS needs to know
+about players (these are defined in `emms-player-simple') and about
+sources for tracks (trivial file system based sources, such as this
+`emms-directory-tree', are defined in `emms-source-file').
+
+@lisp
+(require 'emms-player-simple)
+(require 'emms-source-file)
+(setq emms-player-list '(emms-player-mpg321
+ emms-player-ogg123
+ emms-player-mplayer))
+@end lisp
+@noindent
+
+@node Basic Commands
+@chapter Basic Commands
+
+@cindex basic commands
+@cindex commands, basic
+
+Before you can use the interface commands, you need a playlist to
+start with. The following commands allow you to create or add to the
+current playlist from different sources:
+
+@defun emms-play-file file
+Play the single file @var{file}.
+@end defun
+@defun emms-add-file file
+Add the single file @var{file} to the playlist.
+@end defun
+@defun emms-play-directory dir
+Play the single directory @var{dir}.
+@end defun
+@defun emms-add-directory dir
+Add the single directory @var{dir} to the playlist.
+@end defun
+@defun emms-play-directory-tree dir
+Play the entire directory tree of which @var{dir} is the top directory.
+@end defun
+@defun emms-add-directory-tree dir
+Add the entire directory tree of which @var{dir} is the top directory.
+@end defun
+@defun emms-play-url url
+Play streaming audio from @var{url}.
+@end defun
+@defun emms-add-url url
+Add the streaming audio station at @var{url} to the playlist.
+@end defun
+@defun emms-play-m3u-playlist playlist
+Play the M3U (XMMS) playlist from the file @var{playlist}.
+@end defun
+@defun emms-add-m3u-playlist playlist
+Add an M3U (XMMS) playlist to Emms from the file @var{playlist}.
+@end defun
+@defun emms-play-find dir regexp
+Search for files in @var{dir} matching @var{regexp} to play.
+@end defun
+@defun emms-add-find dir regexp
+Search for files in @var{dir} matching @var{regexp} to add.
+@end defun
+
+The basic functionality of Emms is just to play music without being
+noticed. It provides a few commands to skip the current track and
+such, but other than that it doesn't show up. Emms provides the
+following basic user commands (which you might want to bind to
+keystrokes):
+
+@defun emms-start
+Start playing the current playlist
+@end defun
+@defun emms-stop
+Stop playing
+@end defun
+@defun emms-next
+Go to the next track in the playlist
+@end defun
+@defun emms-previous
+Go to the previous track in the playlist
+@end defun
+@defun emms-shuffle
+Shuffle the playlist
+@end defun
+@defun emms-show &optional insertp
+Describe the current Emms track in the minibuffer. If @var{insertp} is
+non-nil, insert the description into the current buffer instead.
+@end defun
+
+@node The Core File
+@chapter The Core File
+
+The file @file{emms.el} provides all basic functions for playing
+music, generating a playlist and defining player.
+
+@defopt emms-source-list
+A list of sources Emms can get tracks from.
+@lisp
+(setq emms-source-list '((emms-source-directory-tree \"~/media\")))
+@end lisp
+@noindent
+@end defopt
+@defopt emms-player-list
+A list of players Emms can use. You need to set this in order to play
+files. Unless you use @file{emms-player-simple} you have to define a
+player with @command{emms-define-player} first.
+@end defopt
+@defopt emms-show-format
+The format to use for @command{emms-show}. The only argument %s is the
+string returned by @command{emms-track-description}
+@end defopt
+@defopt emms-repeat-playlist
+Non-nil for repeating the playlist after playing the last track.
+@end defopt
+@defopt emms-track-description-function
+A function to be called to give a nice, user-friendly description of
+the track passed to it as an argument.
+@end defopt
+@defopt emms-sort-lessp-function
+A function that compares two tracks, and returns non-nil if the first
+track should be sorted before the second (see `sort').
+@end defopt
+@defopt emms-playlist-changed-hook
+A hook run when the playlist of Emms has changed.
+@end defopt
+@defopt emms-playlist-current-changed-hook
+A hook run when the current track in the playlist of Emms has
+changed.
+@end defopt
+@defopt emms-player-started-hook
+A hook run when an Emms player started playing.
+@end defopt
+@defopt emms-player-stopped-hook
+A hook run when an Emms player stopped playing.
+@end defopt
+
+@defun emms-next-noerror
+Play the next track in the playlist, but don't signal an error when
+we're at the end. This should be called when no player is playing.
+This is a suitable function to put in @var{emms-player-stopped-hook}.
+@end defun
+@defun emms-sort
+Sort the playlist.
+@end defun
+@defun emms-sort-track-name-less-p a b
+Return non-nil if the track name of @var{a} is before @var{b}.
+@end defun
+@defun emms-track type name
+Create a track with type @var{type} and name @var{name}.
+@end defun
+@defun emms-track-type track
+Return the type of @var{track}.
+@end defun
+@defun emms-track-name track
+Return the name of @var{track}.
+@end defun
+@defun emms-track-get name track &optional inexistent
+Return the value of @var{name} for @var{track}. If there is no value, return
+@var{inexistent} (or nil if not given).
+@end defun
+@defun emms-track-set track name value
+Set the value of @var{name} for @var{track} to @var{value}.
+@end defun
+@defun emms-track-description track
+A simple function to give a user-readable description of @var{track}.
+If it's a file track, it's just the filename.
+Else it's the type and the name with a colon in between.
+@end defun
+@defun emms-playlist-current
+Return a description of the currently playing track.
+This uses @var{emms-track-description-function}.
+@end defun
+@defun emms-playlist-current-track
+Return the currently playing track.
+@end defun
+@defun emms-playlist-get n
+Return a description of the @var{n}th entry of the current playlist.
+This uses `emms-track-description-function'
+@end defun
+@defun emms-playlist-get-track n
+Return the @var{n}th track of the current playlist.
+@end defun
+@defun emms-playlist-set-playlist new
+Set the playlist to @var{new}.
+This runs `emms-playlist-changed-hook'.
+@end defun
+@defun emms-playlist-get-playlist
+Return the current playlist.
+@end defun
+@defun emms-playlist-set-current n
+Set the current track in the playlist to @var{n} (a number).
+This runs `emms-playlist-current-changed-hook'.
+@end defun
+@defun emms-playlist-get-current
+Return the number of the current track, or nil if the playlist is
+empty.
+@end defun
+@defun emms-playlist-next
+Advance the current track to the next entry in the playlist and
+return non-nil. Return nil if there is no next track.
+@end defun
+@defun emms-playlist-previous
+Set the current track to the previous entry in the playlist and
+return non-nil. Return nil if there is no previous track.
+@end defun
+@defun emms-playlist-add seq &optional idx
+Add each track of the sequence @var{seq} to the current playlist.
+Insert at @var{idx}, which defaults to the end.
+@end defun
+@defun emms-playlist-remove idx
+Remove track at @var{idx} from playlist.
+@end defun
+@defun emms-playlist-shuffle
+Shuffle the current playlist.
+@end defun
+@defun emms-playlist-sort
+Sort the current playlist according to `emms-sort-lessp-function'
+@end defun
+@defun emms-playlist-shuffle-vector vector
+Shuffle @var{vector}.
+@end defun
+@defun emms-playlist-sort-vector vector
+Sort @var{vector} according to `emms-sort-lessp-function'.
+@end defun
+@defun emms-source-play lis
+Play the tracks returned by @var{lis}.
+@end defun
+@defun emms-player-for track
+Return the player which is responsible for @var{track}, or nil if
+there is none.
+@end defun
+@defun emms-player-start track
+Start playing @var{track}.
+@end defun
+@defun emms-player-stop
+Stop the currently playing player.
+@end defun
+@defun emms-player-stopped
+Declare that the current player has finished playing.
+This should only be called by a player.
+@end defun
+
+@node Sources
+@chapter Sources
+
+@cindex sources
+
+@defopt emms-source-file-default-directory
+The default directory to look for media files.
+@end defopt
+
+@defun emms-play-find
+Play all files in @var{emms-source-file-default-directory} that match
+a specific regular expression.
+@end defun
+@defun emms-play-dired
+Play marked files from the current dired buffer
+@end defun
+@defun emms-play-playlist
+Play all files from a playlist file.
+@end defun
+@defun emms-source-file &optional file
+An Emms source for a single file - either @var{file}, or queried from the
+user.
+@end defun
+@defun emms-source-files files
+An Emms source for a list of @var{files}.
+@end defun
+@defun emms-source-directory &optional dir
+An Emms source for a whole directory tree - either @var{dir}, or queried
+from the user
+@end defun
+@defun emms-source-directory-tree & optional dir
+An Emms source for multiple directory trees - either @var{dir}, or the
+value of @var{emms-source-file-default-directory}.
+@end defun
+@defun emms-source-find &optional dir regex
+An Emms source that will find files in @var{dir} or
+@var{emms-source-file-default-directory} that match @var{regexp}.
+@end defun
+@defun emms-source-file-directory-tree &optional dir
+Return a list of all files under @var{dir} that match @var{regex}.
+@end defun
+@defun emms-source-playlist-file file
+Return all files from playlist @var{file}.
+@end defun
+@defun emms-source-dired
+Play all marked files of a dired buffer
+@end defun
+@defun emms-source-file-regex
+Return a regexp that matches everything any player (that supports
+files) can play.
+@end defun
+@defun emms-save-playlist filename
+Export the current playlist as to @var{filename}. See also
+@command{emms-source-playlist-file}.
+@end defun
+@defun emms-locate
+Search for REGEXP and display the results in a locate buffer
+@end defun
+
+@node Simple Players
+@chapter Simple Players
+
+@cindex players, simple
+
+@defmac define-emms-simple-player
+Returns a simple player with the use of emms-define-player.
+NAME is used to construct the name of the function like
+emms-player-NAME. REGEX must be a regexp that matches the
+filenames the player can play. COMMAND specifies the command line
+argument to call the player and ARGS are the command line
+arguments.
+@end defmac
+
+@defun emms-player-simple-stop
+Stop the currently playing process, if indeed there is one
+@end defun
+@defun emms-player-simple-start
+Starts a process playing FILENAME using the specified CMDNAME with
+the specified PARAMS.
+@end defun
+@defun emms-player-simple-sentinel
+Sentinel for determining the end of process
+@end defun
+
+@node Info Tags
+@chapter Info Tags
+
+@cindex track information
+
+The file @file{emms-info.el} provides an interface for different
+methods of reading info about the files that Emms is playing, and
+displaying it.
+
+To create a method for retrieving info about a file, you create an
+object like this:
+
+@lisp
+(define-emms-info-method emms-info-mp3info
+ :providep 'emms-info-mp3info-providep
+ :get 'emms-info-mp3info-get
+ :set 'emms-info-mp3info-set)
+@end lisp
+@noindent
+
+Then you register it with emms-info, by adding it to
+@var{emms-info-methods-list}.
+
+If you wish to use 'emms-info-mp3info' you will need the mp3info
+program which is available at http://www.ibiblio.org/mp3info/.
+Otherwise Emms will display an error when you attempt to access MP3
+info.
+
+@lisp
+(add-to-list 'emms-info-methods-list 'emms-info-mp3info)
+@end lisp
+@noindent
+
+There are already two predefined methods for retrieving info
+
+@menu
+* Ogg Info:: Reading ogg info tags
+* MP3 Info:: Reading mp3 info tags
+@end menu
+
+@defun emms-info-get-cached track
+Return cached info for the track @var{track}, nil of no cache.
+@end defun
+@defun emms-info-set-cached
+Set cached info for @var{track} to @var{info}
+@end defun
+@defun emms-info-method-for track
+Return an info-method suitable for @var{track}.
+@end defun
+@defun emms-info-get track &optional dont-use-cached
+Return an emms-info structure representing the track @var{track}.
+if @var{dont-USE-CACHED} is non-nil, then always read from the file.
+@end defun
+@defun emms-info-get-multiple callback tracks &optional dont-use-cached
+Asynchronously get all info tags from the tracks in the listlaw
+@var{tracks}. For each file, call @var{callback} with the track and the info
+structure.
+@end defun
+@defun emms-info-set track info
+Set the info of the file @var{track} to the emms-info structure @var{info}.
+@end defun
+@defun emms-info-file-info-song-artist track
+Returns a description of @var{track}, build from it's comments.
+
+If @var{emms-info-methods-list} indicates how to retrieve special info
+about it, use this. Otherwise returns the name alone.
+@end defun
+
+@defopt emms-info-methods-list
+List of info-methods. You need to set this!
+@end defopt
+@defopt emms-info-cache
+Boolean value, indicating whether or not to use a cache for
+info-structures.
+@end defopt
+@defopt emms-info-get-multiple-idletime
+The number of seconds emacs should be idle to get the next info.
+Increase this if emacs becomes unresponsive when building the
+playlist.
+@end defopt
+
+@node Ogg Info
+@section Ogg Info
+The file @file{emms-info-ogg.el} provides an interface to retrieving
+comments from ogg-files, using Lawrence Mitchells ogg-comment.el.
+
+To activate, put something like this in your ~/.emacs:
+
+@lisp
+(require 'emms-info-ogg)
+(add-to-list 'emms-player-alist
+ '("\\.ogg$" . emms-info-ogg-comments))
+@end lisp
+@noindent
+
+Of course, you'll also need a player if you want to actually play the
+files.
+
+@defun emms-info-ogg-comment-providep
+Return non-nil if this info-method provides info for the track.
+@end defun
+@defun emms-info-ogg-get-comment
+@end defun
+@defun emms-info-ogg-comment-get
+Retrieve an emms-info structure as an ogg-comment
+@end defun
+
+@node MP3 Info
+@section MP3 Info
+
+This code has been adapted from code found in mp3player.el, written by
+Jean-Philippe Theberge @email{jphiltheberge@@videotron.ca}, Mario Domgoergen
+@email{kanaldrache@@gmx.de} and Jorgen Schfer @email{forcer@@forcix.cx}
+
+To activate this method for getting info, use something like:
+
+@lisp
+(require 'emms-info-mp3info)
+(add-to-list 'emms-info-methods-alist
+ '("\\.mp3$" . emms-info-mp3info))
+@end lisp
+@noindent
+
+Of course, you'll also need a player if you want to actually play the
+files.
+
+@defun emms-info-mp3info-providep
+Return non-nil if this info-method provides info for the track.
+@end defun
+@defun emms-info-mp3info-set track info
+Set the id3v1 tag of file @var{track} to id3info @var{info}, using the
+@var{mp3info-program}.
+@end defun
+@defun emms-info-mp3info-get track
+Get the id3v1 tag of file @var{track}, using the mp3info-program and
+return an emms-info structure representing it.
+@end defun
+
+@defopt emms-info-mp3info-program-name
+*The name/path of the mp3info-program.
+@end defopt
+
+@node The Playlist Buffer
+@chapter The Playlist Buffer
+@cindex playlist buffer
+
+@menu
+* Playlist Buffer:: Browsing buffer known by other players
+* Playlist Popup:: Poping-up the playlist buffer
+* Playlist Manipulation:: Some playlist manipulation functions
+@end menu
+
+@node Playlist Buffer
+@section Playlist buffer
+
+@table @code
+@findex emms-pbi
+@item emms-pbi
+Switch to playlist buffer
+
+The playlist-buffer *Playlist* will be created and put into
+emms-pbi-mode, which give you some usefull keybinings
+@end table
+
+@table @kbd
+@item ?
+@kindex ? (Emms-pbi)
+@findex describe-mode
+Describe the keybindings
+@item <mouse-2>
+@kindex <mouse-2> (Emms-pbi)
+@findex emms-pbi-play-current-line
+Play the current line
+@item RET
+@kindex RET (Emms-pbi)
+@findex emms-pbi-play-current-line
+Play the current line
+@item q
+@kindex q
+@findex bury-buffer
+@item Q
+@kindex Q
+@findex emms-pbi-quit
+Stops emms and kill the playlist buffer
+@item f
+@kindex f
+@findex emms-pbi-show-current-line
+Show the trackname on current line
+@item s
+@kindex s
+@findex emms-stop
+Stop Emms
+@item C-y
+@kindex C-y
+@findex emms-pbi-yank
+Yank a filename from @var{emms-kill-ring} into the playlist.
+@item C-k
+@kindex C-k
+@findex emms-pbi-kill-line
+Kill the current line from the playlist.
+
+Send the filename to the @var{emms-kill-ring}. Make sure hooks that regenerate
+the entire playlist aren't run.
+@item c
+@kindex c
+@findex emms-pbi-recenter
+Center on current playing track
+@item p
+@kindex p
+@findex emms-previous
+Play the previous track in the playlist.
+@item n
+@kindex n
+@findex emms-next
+Play the next track in the playlist.
+This might behave funny in @var{emms-player-stopped-hook}, use
+@var{emms-next-noerror} instead for that.
+@item C-x C-s
+@kindex C-x C-s
+@findex emms-pbi-export-playlist
+Export the current playlist as to FILENAME. See also:
+@var{emms-pbi-import-playlist}.
+@end table
+
+Prior versions of emms-pbi had their own linenumbering functions. But
+these functions were either error prone or very slow. And besides
+there was already a emacs mode that does exactly the same: setnu.el So
+we remove the linenumbering functions in favour of setnu. You can get
+setnu from @url{http://www.wonderworks.com/download/setnu.el}. To get
+line numbers just put the following code in your @file{~/.emacs} and put
+setnu.el somewhere on your loadpath:
+
+@lisp
+(require 'setnu)
+(add-hook 'emms-pbi-after-build-hook (lambda (setnu-mode 1)))
+@end lisp
+
+@defun emms-pbi
+Turn on emms-playlist if prefix argument ARG is a positive integer,
+off otherwise.
+@end defun
+@defun emms-pbi-shorten-entry-to-max-length
+Cut off an entry-text to make sure it's no longer than
+`emms-pbi-playlist-entry-max-length' characters long.
+@end defun
+@defun emms-pbi-entry-info-updated
+Update the track-entry based on the info
+@end defun
+@defun emms-pbi-rebuild-playlist-buffer
+This function rebuilds the playlist-buffer if necessary.
+@end defun
+@defun emms-pbi-build-playlist-buffer
+Build a playlist-buffer based on the current playlist.
+@end defun
+@defun emms-pbi-insert-tag
+Insert the TRACK tag at point.
+The tag is automatically shortened by
+@command{emms-pbi-shorten-entry-to-max-length'}.
+@end defun
+@defun emms-pbi-insert-entry
+Insert an entry in the playlist
+@end defun
+@defun emms-pbi-async-alternative-text filename
+Generates a single replacement-text for @var{filename}, to be
+displayed in the playlist while the info is being loaded.
+@end defun
+@defun emms-pbi-update-current-face
+Updates the file line with the current-face
+@end defun
+@defun emms-pbi-add-properties-current-line
+Adds the correct faces and other properties to the current line
+@end defun
+@defun emms-pbi-play-current-line
+Play the current line
+@end defun
+@defun emms-pbi-show-current-line
+Show filename and info for track on current line.
+@end defun
+@defun emms-pbi-export-playlist filename
+Export the current playlist as to @var{filename}.
+@end defun
+@defun emms-pbi-quit
+Stops emms and kill the playlist buffer
+@end defun
+@defun emms-pbi-kill-line
+Kill the current line from the playlist.
+
+Send the filename to @var{emms-kill-ring}. Make sure hooks that
+regenerate the entire playlist aren't run.
+@end defun
+@defun emms-pbi-yank
+Yank a filename from `kill-ring' into the playlist.
+@end defun
+@defun emms-pbi-return-current-line-index
+Return the index position in the playlist of the current line.
+@end defun
+@defun emms-pbi-recenter
+Center on current playing track
+@end defun
+
+@defopt emms-pbi-playlist-entry-generate-function
+The function to call for generating a single item of the
+playlist. This will be called with a string argument FILENAME, and
+should return the text to be inserted in the playlist.
+@end defopt
+@defopt emms-pbi-playlist-entry-max-length
+The maximum length of an entry in the playlist. If this is nil, the
+entire string provided by `emms-track-description-function'. will be
+used. Beware, the output of that function is cut off to fit the
+max-length before running `emms-pbi-playlist-entry-generate-function'.
+@end defopt
+@defopt emms-pbi-async-alternative-text-function
+The function to call for generating the replacement-text for a
+playlist-item, while the info is lazy-loading. This will be called
+with a string argument FILENAME, and should return the text to be
+processed by emms-pbi-playlist-entry-generate-function.
+@end defopt
+@defopt emms-pbi-playlist-buffer-name
+Name of the buffer to use as a playlist-buffer
+@end defopt
+@defopt emms-pbi-load-info-async
+Whether or not to use emms-info.el's features for async loading
+info. Defaults to t when emms-info is available, and nil otherwise.
+@end defopt
+@defopt emms-pbi-after-build-hook
+Hook that is run after the playlist buffer is built.
+That might be usefull to change the playlist buffer before the
+buffer is set read-only.
+@end defopt
+@defopt emms-pbi-current-line-face-changed-hook
+Hook that is called when the face of the current line changes.
+@end defopt
+@defopt emms-pbi-manually-change-song-hook
+Hook that is called when the song is manually changed.
+@end defopt
+
+@node Playlist Popup
+@section Playlist Popup
+
+The emms-pbi-popup module makes it easy to popup the playlist buffer
+and restore the old window configuration after choosing a new track.
+
+This module defines the following functions:
+
+@table @code
+@findex emms-pbi-popup-playlist
+@item emms-pbi-popup-playlist
+Popup Playlist buffer
+
+After changing manually the track with @command{emms-pbi-play-current-line} the
+old window configuration is restored. It might be useful to bind that
+function to a global-key in your .emacs, for example:
+
+@lisp
+(global-set-key (kbd "<f3>") 'emms-pbi-popup-playlist)
+@end lisp
+
+@end table
+
+@node Playlist Manipulation
+@section Playlist Manipulation
+
+The file @file{emms-pl-manip} offers various advanced playlist-manipulations functions for
+Emms.
+
+Basically just load up this file, and check out some of these
+functions.
+
+@defun vector-sort vec pred &optional beg end
+Sort a vector @var{vec}, using the predicate @var{pred}, and return the new
+vector. If @var{beg} and @var{end} are specified, sort only this subrange.
+
+@var{pred} is called with 2 elements and should return true, if the first is
+less than the other.
+@end defun
+@defun emms-pl-manip-sort by pred
+Sorts the Emms-playlist, by applying @var{by} as a function to each
+filename in the list, and then comparing the results with @var{pred}.
+@end defun
+@defun emms-pl-manip-sort-by-filename
+
+@end defun
+@defun emms-pl-manip-sort-by-name
+
+@end defun
+@defun emms-pl-manip-sort-by-info-artist
+Sort the playlist, using
+@end defun
+@defun emms-playlist-reshuffle
+Reshuffle the playlist.
+@end defun
+
+@node Scoring
+@chapter Scoring
+
+Scoring allows you to assign scores to individual files and play media
+according to your mood.
+
+When you load @file{emms-score}, you are set to a default mood 'emms-default-mood'
+A mood is a one word string describing how you feel (like "funny",
+"tired", "aggressive"...) Each mood have is own set of scoring rules.
+
+You can change your mood with M-x @command{emms-score-change-mood}
+
+Every music file start with a default score of 0 the command
+emms-score-up-current and emms-score-down-current modify the score of
+the file you are currently listening by 1 In addition, skipping a file
+(with emms-skip) automatically score the file down.
+
+With scoring on (this mean the variable @var{emms-use-scoring} is t), emms
+will compare the score of the file with your tolerance to decide if it
+is played or not.
+
+The default tolerance level is 0 (or the variable
+@var{emms-score-min-score}). This mean files with a score of 0 or more will
+be played and files with a score of -1 or less will be skipped.
+
+You can change the tolerance (by 1) with
+@command{emms-score-lower-tolerance} and @command{emms-score-be-more-tolerant}.
+
+@table @code
+@findex emms-score
+@item emms-score
+Activate scoring
+@findex emms-score-change-mood
+@item emms-score-change-mood
+Change current mood
+@findex emms-score-up-current
+@item emms-score-up-current
+Score up the current track
+@findex emms-score-down-current
+@item emms-score-down-current
+Score down the current track
+@findex emms-score-up-file-on-line
+@item emms-score-up-file-on-line
+Score up file on line
+@findex emms-score-down-file-on-line
+@item emms-score-down-file-on-line
+Score down file on line
+@findex emms-score-be-more-tolerant
+@item emms-score-be-more-tolerant
+Lower minimum score
+@findex emms-score-lower-tolerance
+@item emms-score-lower-tolerance
+Raise minimum score
+@end table
+
+@node Extending Emms
+@chapter Extending Emms
+
+@cindex new players
+@cindex defining players
+@cindex new players, defining
+
+Emms introduces a high abstraction layer for playing music so you can
+customise a lot of things to morph Emms into @emph{your} media player.
+
+@menu
+* New Player:: How to define a new player
+@end menu
+
+@node New Player
+@section New Player
+
+The file @file{emms-player-simple.el} defines some easy players to
+start with, but it shouldn't be hard to provide a function for your
+favourite player. We will start with an easy example that shows how
+we can use the @command{play} command under Unix to play our WAV files.
+
+@menu
+* Simple Player for @command{play}:: An example player using @command{play}
+* More Complex Player:: Example of a complex player using @command{mpg321}
+@end menu
+
+@node Simple Player for @command{play}
+@subsection Simple Player for @command{play}
+
+Play is a very easy command line player for various format. If you
+want your emms to play WAV files just put the following lines in you
+@file{.emacs}:
+
+@lisp
+(require 'emms-player-simple)
+(define-emms-simple-player play "\\.wav$" "play")
+@end lisp
+@noindent
+
+Huh! Wasn't that easy?
+
+The macro function @command{define-emms-simple-player} takes a minimum
+of three arguments. The first argument (@emph{play} in our example)
+defines the name of the player. It's used to name the player
+functions. The second is a regexp, that defines which files to play
+with our player. @emph{\\.wav$} matches any filename ending with a dot
+and the string wav. The last argument is the actual command line
+command we use to play our files. You can also add the path but we
+just assume that the command is in your path. All arguments you add to
+these three are optional. They define the command line arguments you
+want to add to your argument. If you want to hear the wav file of
+your favourite artist in the most possible volume use the following
+line:
+
+@lisp
+(require 'emms-player-simple)
+(define-emms-simple-player play "\\artist-*.wav$" "play" "--volume=100")
+@end lisp
+@noindent
+
+Please notice that you have to add the arguments as strings!
+
+The command line tool you use for @command{define-emms-simple-player}
+has to take one song as argument and stop after playing that
+particular song. For any other concept you will need to customise
+emms a bit more...
+
+@node More Complex Player
+@subsection More Complex Player
+
+The most players you use will be simple players so you don't need to
+read this chapter. But if you are curious how you can use (almost) every
+player in emms read further...
+
+In this chapter we will use mpg321 to construct a player that
+actually can pause a track, restart it and show rest time. We won't
+implement all of that, but after that chapter you will know how to
+define it.
+
+The command @command{define-emms-simple-player} is just a abstraction
+layer for @command{define-emms-player}, which is a little bit more
+complicated but much more powerful!
+
+@lisp
+(define-emms-player "emms-mpg321-remote"
+ :start 'emms-mpg321-remote-start
+ :stop 'emms-mpg321-remote-stop
+ :playablep 'emms-mpg321-remote-playable-p)
+@end lisp
+@noindent
+
+So, that almost all! @command{define-emms-player} takes a minimum of
+three arguments. The first is the name of the player. The rest are
+methods with functions to call. Three methods are required: start,
+stop and playable. Start says Emms how to start a track (sic!), stop
+how to stop a player and playablep should return non-nil if the
+player can play the track.
+
+So we just need these three functions to get our mpg321-remote:
+
+First we code the start function. We will check if there's a open
+process and start one otherwise. Then we send a string to the process
+with the filename and set a filter.
+
+@lisp
+(defun emms-mpg321-remote-start ()
+ (unless (get-process "mpg321-remote")
+ (setq emms-mpg321-remote-process
+ (start-process "mpg321-remote-process"
+ "*mpg321*" "mpg321" "-R" "abc"))
+ (process-send-string "mpg321-remote-process"
+ (concat "l " (emms-track-name track)))
+ (set-process-filter emms-mpg321-remote-process 'emms-mpg321-remote-filter)))
+@end lisp
+@noindent
+
+We need the filter, as mpg321-remote won't quit after playing the
+track as the simple player do. We wait until the process send the
+output ``@@P 0'' (the signal of mpg321 that the song ended) to the
+filter and call emms-mpg321-remote-stop.
+
+@lisp
+(defun emms-mpg321-remote-filter (process output)
+ (when (string-match "@@P 0" output)
+ (emms-mpg321-remote-stop)))
+@end lisp
+@noindent
+
+@command{emms-mpg321-remote-stop} won't do anything interesting. It
+just test if there are other files to play and close the process otherwise.
+
+@lisp
+(defun emms-mpg321-remote-stop ()
+ (unless emms-playlist
+ (process-send-string "mpg321-remote-process" "Q\n"))
+@end lisp
+@noindent
+
+And to make that a playable example i also added
+@command{emms-mpg321-remote-playablep}, which i just really steal
+from @file{emms-player-simple.el}
+
+@lisp
+(defun emms-mpg321-remote-playablep (track)
+ "Return non-nil when we can play this track."
+ (and (eq 'file (emms-track-type track))
+@end lisp
+@noindent
+
+Now we have a ready player and we could add commands like
+@command{emms-mpg321-remote-pause} for example.
+
+@node Streaming Audio
+@chapter Streaming Audio
+
+@cindex streaming audio
+@cindex internet radio
+
+Emms provides a friendly interface for managing and playing streaming
+audio in addition to the Emms playlist interface. The interface is
+defined in the @file{emms-streams.el} package and can be loaded by
+invoking:
+
+@lisp
+(require 'emms-streams)
+@end lisp
+
+Enter the emms-streams interface by invoking M-x
+@command{emms-streams}. The emms-streams interface comes with a
+built-in, eclectic list of streaming audio channels from thoughout the
+Web. Emms can of-course play other streams than the ones listed by
+default, you are free to remove any or all of them and add your
+own.@footnote{If you enjoy a particular streaming audio station on the
+Web and think that it belongs in the default list, please send us a
+link and we will gladly add it!}
+
+The following is a list of the key-bindings for the emms-streams
+interface:
+
+@table @kbd
+@item RET
+@kindex RET (emms-streams)
+@vindex emms-stream-default-action
+Perform the default action when you press RET in the EMMS Stream
+interface. Can be either ``add'' or ``play''. The default is ``add'',
+which adds the station under point to the Emms playlist. When
+@var{emms-stream-default-action} is ``play'' then Emms will play the
+streaming audio channel under point.
+@item q
+@kindex q (emms-streams)
+@findex emms-stream-quit
+Quit the emms-streams interface.
+@item a
+@kindex a (emms-streams)
+@findex emms-stream-add-bookmark
+Add a bookmark to a streaming audio URL to the list.
+@item d
+@kindex d (emms-streams)
+@findex emms-stream-delete-bookmark
+Remove a bookmark to a streaming audio URL from the list.
+@item e
+@kindex e (emms-streams)
+@findex emms-stream-edit-bookmark
+Edit the details of the bookmark under point.
+@item h
+@kindex h (emms-streams)
+@findex describe-mode
+Describe the emms-streams mode.
+@item n
+@kindex n (emms-streams)
+@findex emms-stream-next-line
+Move to the next line in the emms-streams buffer (same as C-n).
+@item p
+@kindex p (emms-streams)
+@findex emms-stream-previous-line
+Move to the previous line in the emms-streams buffer (same as C-p).
+@item s
+@kindex s (emms-streams)
+@findex emms-stream-save-bookmarks-file
+Save the bookmarks in the emms-streams interface to disk. The
+bookmarks will be to the location designated in the variable
+@var{emms-stream-bookmarks-file}.
+@item i
+@kindex i (emms-streams)
+@findex emms-stream-info-bookmark
+Return information about the streaming audio at the URL of the
+bookmark under point. Note that this will only work if the
+`emms-stream-info' has already been loaded.
+@end table
+
+@node Concept Index
+@unnumbered Concept Index
+@printindex cp
+
+@node Function Index
+@unnumbered Function Index
+@printindex fn
+
+@node Variable Index
+@unnumbered Variable Index
+@printindex vr
+
+@node Keybinding Index
+@unnumbered Keybinding Index
+@printindex ky
+
+@bye
diff --git a/fdl.texi b/fdl.texi
new file mode 100644
index 0000000..8b2d24c
--- /dev/null
+++ b/fdl.texi
@@ -0,0 +1,451 @@
+@node The GNU FDL, Introduction, Copying, Top
+@appendixsec GNU Free Documentation License
+
+@cindex FDL, GNU Free Documentation License
+@center Version 1.2, November 2002
+
+@display
+Copyright @copyright{} 2000,2001,2002 Free Software Foundation, Inc.
+51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+Everyone is permitted to copy and distribute verbatim copies
+of this license document, but changing it is not allowed.
+@end display
+
+@enumerate 0
+@item
+PREAMBLE
+
+The purpose of this License is to make a manual, textbook, or other
+functional and useful document @dfn{free} in the sense of freedom: to
+assure everyone the effective freedom to copy and redistribute it,
+with or without modifying it, either commercially or noncommercially.
+Secondarily, this License preserves for the author and publisher a way
+to get credit for their work, while not being considered responsible
+for modifications made by others.
+
+This License is a kind of ``copyleft'', which means that derivative
+works of the document must themselves be free in the same sense. It
+complements the GNU General Public License, which is a copyleft
+license designed for free software.
+
+We have designed this License in order to use it for manuals for free
+software, because free software needs free documentation: a free
+program should come with manuals providing the same freedoms that the
+software does. But this License is not limited to software manuals;
+it can be used for any textual work, regardless of subject matter or
+whether it is published as a printed book. We recommend this License
+principally for works whose purpose is instruction or reference.
+
+@item
+APPLICABILITY AND DEFINITIONS
+
+This License applies to any manual or other work, in any medium, that
+contains a notice placed by the copyright holder saying it can be
+distributed under the terms of this License. Such a notice grants a
+world-wide, royalty-free license, unlimited in duration, to use that
+work under the conditions stated herein. The ``Document'', below,
+refers to any such manual or work. Any member of the public is a
+licensee, and is addressed as ``you''. You accept the license if you
+copy, modify or distribute the work in a way requiring permission
+under copyright law.
+
+A ``Modified Version'' of the Document means any work containing the
+Document or a portion of it, either copied verbatim, or with
+modifications and/or translated into another language.
+
+A ``Secondary Section'' is a named appendix or a front-matter section
+of the Document that deals exclusively with the relationship of the
+publishers or authors of the Document to the Document's overall
+subject (or to related matters) and contains nothing that could fall
+directly within that overall subject. (Thus, if the Document is in
+part a textbook of mathematics, a Secondary Section may not explain
+any mathematics.) The relationship could be a matter of historical
+connection with the subject or with related matters, or of legal,
+commercial, philosophical, ethical or political position regarding
+them.
+
+The ``Invariant Sections'' are certain Secondary Sections whose titles
+are designated, as being those of Invariant Sections, in the notice
+that says that the Document is released under this License. If a
+section does not fit the above definition of Secondary then it is not
+allowed to be designated as Invariant. The Document may contain zero
+Invariant Sections. If the Document does not identify any Invariant
+Sections then there are none.
+
+The ``Cover Texts'' are certain short passages of text that are listed,
+as Front-Cover Texts or Back-Cover Texts, in the notice that says that
+the Document is released under this License. A Front-Cover Text may
+be at most 5 words, and a Back-Cover Text may be at most 25 words.
+
+A ``Transparent'' copy of the Document means a machine-readable copy,
+represented in a format whose specification is available to the
+general public, that is suitable for revising the document
+straightforwardly with generic text editors or (for images composed of
+pixels) generic paint programs or (for drawings) some widely available
+drawing editor, and that is suitable for input to text formatters or
+for automatic translation to a variety of formats suitable for input
+to text formatters. A copy made in an otherwise Transparent file
+format whose markup, or absence of markup, has been arranged to thwart
+or discourage subsequent modification by readers is not Transparent.
+An image format is not Transparent if used for any substantial amount
+of text. A copy that is not ``Transparent'' is called ``Opaque''.
+
+Examples of suitable formats for Transparent copies include plain
+@sc{ascii} without markup, Texinfo input format, La@TeX{} input
+format, @acronym{SGML} or @acronym{XML} using a publicly available
+@acronym{DTD}, and standard-conforming simple @acronym{HTML},
+PostScript or @acronym{PDF} designed for human modification. Examples
+of transparent image formats include @acronym{PNG}, @acronym{XCF} and
+@acronym{JPG}. Opaque formats include proprietary formats that can be
+read and edited only by proprietary word processors, @acronym{SGML} or
+@acronym{XML} for which the @acronym{DTD} and/or processing tools are
+not generally available, and the machine-generated @acronym{HTML},
+PostScript or @acronym{PDF} produced by some word processors for
+output purposes only.
+
+The ``Title Page'' means, for a printed book, the title page itself,
+plus such following pages as are needed to hold, legibly, the material
+this License requires to appear in the title page. For works in
+formats which do not have any title page as such, ``Title Page'' means
+the text near the most prominent appearance of the work's title,
+preceding the beginning of the body of the text.
+
+A section ``Entitled XYZ'' means a named subunit of the Document whose
+title either is precisely XYZ or contains XYZ in parentheses following
+text that translates XYZ in another language. (Here XYZ stands for a
+specific section name mentioned below, such as ``Acknowledgements'',
+``Dedications'', ``Endorsements'', or ``History''.) To ``Preserve the Title''
+of such a section when you modify the Document means that it remains a
+section ``Entitled XYZ'' according to this definition.
+
+The Document may include Warranty Disclaimers next to the notice which
+states that this License applies to the Document. These Warranty
+Disclaimers are considered to be included by reference in this
+License, but only as regards disclaiming warranties: any other
+implication that these Warranty Disclaimers may have is void and has
+no effect on the meaning of this License.
+
+@item
+VERBATIM COPYING
+
+You may copy and distribute the Document in any medium, either
+commercially or noncommercially, provided that this License, the
+copyright notices, and the license notice saying this License applies
+to the Document are reproduced in all copies, and that you add no other
+conditions whatsoever to those of this License. You may not use
+technical measures to obstruct or control the reading or further
+copying of the copies you make or distribute. However, you may accept
+compensation in exchange for copies. If you distribute a large enough
+number of copies you must also follow the conditions in section 3.
+
+You may also lend copies, under the same conditions stated above, and
+you may publicly display copies.
+
+@item
+COPYING IN QUANTITY
+
+If you publish printed copies (or copies in media that commonly have
+printed covers) of the Document, numbering more than 100, and the
+Document's license notice requires Cover Texts, you must enclose the
+copies in covers that carry, clearly and legibly, all these Cover
+Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on
+the back cover. Both covers must also clearly and legibly identify
+you as the publisher of these copies. The front cover must present
+the full title with all words of the title equally prominent and
+visible. You may add other material on the covers in addition.
+Copying with changes limited to the covers, as long as they preserve
+the title of the Document and satisfy these conditions, can be treated
+as verbatim copying in other respects.
+
+If the required texts for either cover are too voluminous to fit
+legibly, you should put the first ones listed (as many as fit
+reasonably) on the actual cover, and continue the rest onto adjacent
+pages.
+
+If you publish or distribute Opaque copies of the Document numbering
+more than 100, you must either include a machine-readable Transparent
+copy along with each Opaque copy, or state in or with each Opaque copy
+a computer-network location from which the general network-using
+public has access to download using public-standard network protocols
+a complete Transparent copy of the Document, free of added material.
+If you use the latter option, you must take reasonably prudent steps,
+when you begin distribution of Opaque copies in quantity, to ensure
+that this Transparent copy will remain thus accessible at the stated
+location until at least one year after the last time you distribute an
+Opaque copy (directly or through your agents or retailers) of that
+edition to the public.
+
+It is requested, but not required, that you contact the authors of the
+Document well before redistributing any large number of copies, to give
+them a chance to provide you with an updated version of the Document.
+
+@item
+MODIFICATIONS
+
+You may copy and distribute a Modified Version of the Document under
+the conditions of sections 2 and 3 above, provided that you release
+the Modified Version under precisely this License, with the Modified
+Version filling the role of the Document, thus licensing distribution
+and modification of the Modified Version to whoever possesses a copy
+of it. In addition, you must do these things in the Modified Version:
+
+@enumerate A
+@item
+Use in the Title Page (and on the covers, if any) a title distinct
+from that of the Document, and from those of previous versions
+(which should, if there were any, be listed in the History section
+of the Document). You may use the same title as a previous version
+if the original publisher of that version gives permission.
+
+@item
+List on the Title Page, as authors, one or more persons or entities
+responsible for authorship of the modifications in the Modified
+Version, together with at least five of the principal authors of the
+Document (all of its principal authors, if it has fewer than five),
+unless they release you from this requirement.
+
+@item
+State on the Title page the name of the publisher of the
+Modified Version, as the publisher.
+
+@item
+Preserve all the copyright notices of the Document.
+
+@item
+Add an appropriate copyright notice for your modifications
+adjacent to the other copyright notices.
+
+@item
+Include, immediately after the copyright notices, a license notice
+giving the public permission to use the Modified Version under the
+terms of this License, in the form shown in the Addendum below.
+
+@item
+Preserve in that license notice the full lists of Invariant Sections
+and required Cover Texts given in the Document's license notice.
+
+@item
+Include an unaltered copy of this License.
+
+@item
+Preserve the section Entitled ``History'', Preserve its Title, and add
+to it an item stating at least the title, year, new authors, and
+publisher of the Modified Version as given on the Title Page. If
+there is no section Entitled ``History'' in the Document, create one
+stating the title, year, authors, and publisher of the Document as
+given on its Title Page, then add an item describing the Modified
+Version as stated in the previous sentence.
+
+@item
+Preserve the network location, if any, given in the Document for
+public access to a Transparent copy of the Document, and likewise
+the network locations given in the Document for previous versions
+it was based on. These may be placed in the ``History'' section.
+You may omit a network location for a work that was published at
+least four years before the Document itself, or if the original
+publisher of the version it refers to gives permission.
+
+@item
+For any section Entitled ``Acknowledgements'' or ``Dedications'', Preserve
+the Title of the section, and preserve in the section all the
+substance and tone of each of the contributor acknowledgements and/or
+dedications given therein.
+
+@item
+Preserve all the Invariant Sections of the Document,
+unaltered in their text and in their titles. Section numbers
+or the equivalent are not considered part of the section titles.
+
+@item
+Delete any section Entitled ``Endorsements''. Such a section
+may not be included in the Modified Version.
+
+@item
+Do not retitle any existing section to be Entitled ``Endorsements'' or
+to conflict in title with any Invariant Section.
+
+@item
+Preserve any Warranty Disclaimers.
+@end enumerate
+
+If the Modified Version includes new front-matter sections or
+appendices that qualify as Secondary Sections and contain no material
+copied from the Document, you may at your option designate some or all
+of these sections as invariant. To do this, add their titles to the
+list of Invariant Sections in the Modified Version's license notice.
+These titles must be distinct from any other section titles.
+
+You may add a section Entitled ``Endorsements'', provided it contains
+nothing but endorsements of your Modified Version by various
+parties---for example, statements of peer review or that the text has
+been approved by an organization as the authoritative definition of a
+standard.
+
+You may add a passage of up to five words as a Front-Cover Text, and a
+passage of up to 25 words as a Back-Cover Text, to the end of the list
+of Cover Texts in the Modified Version. Only one passage of
+Front-Cover Text and one of Back-Cover Text may be added by (or
+through arrangements made by) any one entity. If the Document already
+includes a cover text for the same cover, previously added by you or
+by arrangement made by the same entity you are acting on behalf of,
+you may not add another; but you may replace the old one, on explicit
+permission from the previous publisher that added the old one.
+
+The author(s) and publisher(s) of the Document do not by this License
+give permission to use their names for publicity for or to assert or
+imply endorsement of any Modified Version.
+
+@item
+COMBINING DOCUMENTS
+
+You may combine the Document with other documents released under this
+License, under the terms defined in section 4 above for modified
+versions, provided that you include in the combination all of the
+Invariant Sections of all of the original documents, unmodified, and
+list them all as Invariant Sections of your combined work in its
+license notice, and that you preserve all their Warranty Disclaimers.
+
+The combined work need only contain one copy of this License, and
+multiple identical Invariant Sections may be replaced with a single
+copy. If there are multiple Invariant Sections with the same name but
+different contents, make the title of each such section unique by
+adding at the end of it, in parentheses, the name of the original
+author or publisher of that section if known, or else a unique number.
+Make the same adjustment to the section titles in the list of
+Invariant Sections in the license notice of the combined work.
+
+In the combination, you must combine any sections Entitled ``History''
+in the various original documents, forming one section Entitled
+``History''; likewise combine any sections Entitled ``Acknowledgements'',
+and any sections Entitled ``Dedications''. You must delete all
+sections Entitled ``Endorsements.''
+
+@item
+COLLECTIONS OF DOCUMENTS
+
+You may make a collection consisting of the Document and other documents
+released under this License, and replace the individual copies of this
+License in the various documents with a single copy that is included in
+the collection, provided that you follow the rules of this License for
+verbatim copying of each of the documents in all other respects.
+
+You may extract a single document from such a collection, and distribute
+it individually under this License, provided you insert a copy of this
+License into the extracted document, and follow this License in all
+other respects regarding verbatim copying of that document.
+
+@item
+AGGREGATION WITH INDEPENDENT WORKS
+
+A compilation of the Document or its derivatives with other separate
+and independent documents or works, in or on a volume of a storage or
+distribution medium, is called an ``aggregate'' if the copyright
+resulting from the compilation is not used to limit the legal rights
+of the compilation's users beyond what the individual works permit.
+When the Document is included in an aggregate, this License does not
+apply to the other works in the aggregate which are not themselves
+derivative works of the Document.
+
+If the Cover Text requirement of section 3 is applicable to these
+copies of the Document, then if the Document is less than one half of
+the entire aggregate, the Document's Cover Texts may be placed on
+covers that bracket the Document within the aggregate, or the
+electronic equivalent of covers if the Document is in electronic form.
+Otherwise they must appear on printed covers that bracket the whole
+aggregate.
+
+@item
+TRANSLATION
+
+Translation is considered a kind of modification, so you may
+distribute translations of the Document under the terms of section 4.
+Replacing Invariant Sections with translations requires special
+permission from their copyright holders, but you may include
+translations of some or all Invariant Sections in addition to the
+original versions of these Invariant Sections. You may include a
+translation of this License, and all the license notices in the
+Document, and any Warranty Disclaimers, provided that you also include
+the original English version of this License and the original versions
+of those notices and disclaimers. In case of a disagreement between
+the translation and the original version of this License or a notice
+or disclaimer, the original version will prevail.
+
+If a section in the Document is Entitled ``Acknowledgements'',
+``Dedications'', or ``History'', the requirement (section 4) to Preserve
+its Title (section 1) will typically require changing the actual
+title.
+
+@item
+TERMINATION
+
+You may not copy, modify, sublicense, or distribute the Document except
+as expressly provided for under this License. Any other attempt to
+copy, modify, sublicense or distribute the Document is void, and will
+automatically terminate your rights under this License. However,
+parties who have received copies, or rights, from you under this
+License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+@item
+FUTURE REVISIONS OF THIS LICENSE
+
+The Free Software Foundation may publish new, revised versions
+of the GNU Free Documentation License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns. See
+@uref{http://www.gnu.org/copyleft/}.
+
+Each version of the License is given a distinguishing version number.
+If the Document specifies that a particular numbered version of this
+License ``or any later version'' applies to it, you have the option of
+following the terms and conditions either of that specified version or
+of any later version that has been published (not as a draft) by the
+Free Software Foundation. If the Document does not specify a version
+number of this License, you may choose any version ever published (not
+as a draft) by the Free Software Foundation.
+@end enumerate
+
+@page
+@appendixsubsec ADDENDUM: How to use this License for your documents
+
+To use this License in a document you have written, include a copy of
+the License in the document and put the following copyright and
+license notices just after the title page:
+
+@smallexample
+@group
+ Copyright (C) @var{year} @var{your name}.
+ Permission is granted to copy, distribute and/or modify this document
+ under the terms of the GNU Free Documentation License, Version 1.2
+ or any later version published by the Free Software Foundation;
+ with no Invariant Sections, no Front-Cover Texts, and no Back-Cover
+ Texts. A copy of the license is included in the section entitled ``GNU
+ Free Documentation License''.
+@end group
+@end smallexample
+
+If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts,
+replace the ``with...Texts.'' line with this:
+
+@smallexample
+@group
+ with the Invariant Sections being @var{list their titles}, with
+ the Front-Cover Texts being @var{list}, and with the Back-Cover Texts
+ being @var{list}.
+@end group
+@end smallexample
+
+If you have Invariant Sections without Cover Texts, or some other
+combination of the three, merge those two alternatives to suit the
+situation.
+
+If your document contains nontrivial examples of program code, we
+recommend releasing these examples in parallel under your choice of
+free software license, such as the GNU General Public License,
+to permit their use in free software.
+
+@c Local Variables:
+@c ispell-local-pdict: "ispell-dict"
+@c End:
+
diff --git a/gpl.texi b/gpl.texi
new file mode 100644
index 0000000..a85d117
--- /dev/null
+++ b/gpl.texi
@@ -0,0 +1,392 @@
+@node Copying, The GNU FDL, Top, Top
+@unnumbered GNU GENERAL PUBLIC LICENSE
+@center Version 2, June 1991
+
+@c This file is intended to be included in another file.
+
+@display
+Copyright @copyright{} 1989, 1991 Free Software Foundation, Inc.
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+
+Everyone is permitted to copy and distribute verbatim copies
+of this license document, but changing it is not allowed.
+@end display
+
+@unnumberedsec Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software---to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+@iftex
+@unnumberedsec TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+@end iftex
+@ifinfo
+@center TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+@end ifinfo
+
+@enumerate 0
+@item
+This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The ``Program'', below,
+refers to any such program or work, and a ``work based on the Program''
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term ``modification''.) Each licensee is addressed as ``you''.
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+@item
+You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+@item
+You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+@enumerate a
+@item
+You must cause the modified files to carry prominent notices
+stating that you changed the files and the date of any change.
+
+@item
+You must cause any work that you distribute or publish, that in
+whole or in part contains or is derived from the Program or any
+part thereof, to be licensed as a whole at no charge to all third
+parties under the terms of this License.
+
+@item
+If the modified program normally reads commands interactively
+when run, you must cause it, when started running for such
+interactive use in the most ordinary way, to print or display an
+announcement including an appropriate copyright notice and a
+notice that there is no warranty (or else, saying that you provide
+a warranty) and that users may redistribute the program under
+these conditions, and telling the user how to view a copy of this
+License. (Exception: if the Program itself is interactive but
+does not normally print such an announcement, your work based on
+the Program is not required to print an announcement.)
+@end enumerate
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+@item
+You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+@enumerate a
+@item
+Accompany it with the complete corresponding machine-readable
+source code, which must be distributed under the terms of Sections
+1 and 2 above on a medium customarily used for software interchange; or,
+
+@item
+Accompany it with a written offer, valid for at least three
+years, to give any third party, for a charge no more than your
+cost of physically performing source distribution, a complete
+machine-readable copy of the corresponding source code, to be
+distributed under the terms of Sections 1 and 2 above on a medium
+customarily used for software interchange; or,
+
+@item
+Accompany it with the information you received as to the offer
+to distribute corresponding source code. (This alternative is
+allowed only for noncommercial distribution and only if you
+received the program in object code or executable form with such
+an offer, in accord with Subsection b above.)
+@end enumerate
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+@item
+You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+@item
+You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+@item
+Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+@item
+If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+@item
+If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+@item
+The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and ``any
+later version'', you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+@item
+If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+@iftex
+@heading NO WARRANTY
+@end iftex
+@ifinfo
+@center NO WARRANTY
+@end ifinfo
+
+@item
+BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM ``AS IS'' WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+@item
+IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+@end enumerate
+
+@iftex
+@heading END OF TERMS AND CONDITIONS
+@end iftex
+@ifinfo
+@center END OF TERMS AND CONDITIONS
+@end ifinfo
+
+@page
+@unnumberedsec Appendix: How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the ``copyright'' line and a pointer to where the full notice is found.
+
+@smallexample
+@var{one line to give the program's name and a brief idea of what it does.}
+Copyright (C) @var{yyyy} @var{name of author}
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+@end smallexample
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+@smallexample
+Gnomovision version 69, Copyright (C) 19@var{yy} @var{name of author}
+Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+This is free software, and you are welcome to redistribute it
+under certain conditions; type `show c' for details.
+@end smallexample
+
+The hypothetical commands @samp{show w} and @samp{show c} should show
+the appropriate parts of the General Public License. Of course, the
+commands you use may be called something other than @samp{show w} and
+@samp{show c}; they could even be mouse-clicks or menu items---whatever
+suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a ``copyright disclaimer'' for the program, if
+necessary. Here is a sample; alter the names:
+
+@example
+Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+`Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+@var{signature of Ty Coon}, 1 April 1989
+Ty Coon, President of Vice
+@end example
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/gst-flac-wrapper b/gst-flac-wrapper
new file mode 100755
index 0000000..c079de6
--- /dev/null
+++ b/gst-flac-wrapper
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+gst-launch-0.8 filesrc location="$2" ! spider ! $1
diff --git a/gst-mod-wrapper b/gst-mod-wrapper
new file mode 100755
index 0000000..c079de6
--- /dev/null
+++ b/gst-mod-wrapper
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+gst-launch-0.8 filesrc location="$2" ! spider ! $1
diff --git a/gst-mp3-wrapper b/gst-mp3-wrapper
new file mode 100755
index 0000000..c079de6
--- /dev/null
+++ b/gst-mp3-wrapper
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+gst-launch-0.8 filesrc location="$2" ! spider ! $1
diff --git a/gst-ogg-wrapper b/gst-ogg-wrapper
new file mode 100755
index 0000000..c079de6
--- /dev/null
+++ b/gst-ogg-wrapper
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+gst-launch-0.8 filesrc location="$2" ! spider ! $1
diff --git a/later-do.el b/later-do.el
new file mode 100644
index 0000000..8cd4f94
--- /dev/null
+++ b/later-do.el
@@ -0,0 +1,72 @@
+;;; later-do.el --- execute lisp code ... later
+
+;;; Copyright (C) 2004 Jorgen Schaefer <forcer@forcix.cx>
+
+;;; This program is free software; you can redistribute it and/or
+;;; modify it under the terms of the GNU General Public License
+;;; as published by the Free Software Foundation; either version 2
+;;; of the License, or (at your option) any later version.
+
+;;; This program is distributed in the hope that it will be useful,
+;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;;; GNU General Public License for more details.
+
+;;; You should have received a copy of the GNU General Public License
+;;; along with this program; if not, write to the Free Software
+;;; Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+;;; 02110-1301 USA
+
+;;; Commentary
+
+;; This file will execute lisp code "later on". This way it is
+;; possible to work while elisp does some longer calculations, if you
+;; can convert those calculations into a sequence of function calls.
+
+;;; Code:
+
+(defvar later-do-version "0.2 (2004-02-09)"
+ "Version string of later-do.")
+
+(defgroup later-do nil
+ "*Running functions ... later!"
+ :prefix "later-do-"
+ :group 'development)
+
+(defcustom later-do-interval 0.5
+ "How many seconds to wait between running events."
+ :group 'later-do
+ :type 'number)
+
+(defvar later-do-list nil
+ "A list of functions to be called lateron.")
+
+(defvar later-do-timer nil
+ "The timer that later-do uses.")
+
+(defun later-do (function &rest args)
+ "Apply FUNCTION to ARGS later on. This is an unspecified amount of
+time after this call, and definitely not while lisp is still
+executing.
+Code added using `later-do' is guaranteed to be executed in the
+sequence it was added."
+ (setq later-do-list (append later-do-list
+ (list (cons function args))))
+ (unless later-do-timer
+ (setq later-do-timer
+ (run-with-timer later-do-interval nil 'later-do-timer))))
+
+(defun later-do-timer ()
+ "Run the next element in `later-do-list', or do nothing if it's
+empty."
+ (if (null later-do-list)
+ (setq later-do-timer nil)
+ (setq later-do-timer (run-with-timer later-do-interval
+ nil
+ 'later-do-timer))
+ (apply (caar later-do-list)
+ (cdar later-do-list))
+ (setq later-do-list (cdr later-do-list))))
+
+(provide 'later-do)
+;;; later-do.el ends here
diff --git a/ogg-comment.el b/ogg-comment.el
new file mode 100644
index 0000000..39a490a
--- /dev/null
+++ b/ogg-comment.el
@@ -0,0 +1,271 @@
+;;; ogg-comment.el --- Read Ogg-Vorbis file headers.
+
+;; Copyright (C) 2003, 2004 lawrence mitchell <wence@gmx.li>
+;; Filename: ogg-comment.el
+;; Version: $Revision: 1.5 $
+;; Author: lawrence mitchell <wence@gmx.li>
+;; Maintainer: lawrence mitchell <wence@gmx.li>
+;; Created: 2003-09-26
+;; Keywords: music
+
+;; COPYRIGHT NOTICE
+
+;; This program is free software; you can redistribute it and/or
+;; modify it under the terms of the GNU General Public License as
+;; published by the Free Software Foundation; either version 2 of the
+;; License, or (at your option) any later version.
+;;
+;; This program is distributed in the hope that it will be useful, but
+;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+;; General Public License for more
+;; details. http://www.gnu.org/copyleft/gpl.html
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs. If you did not, write to the Free Software
+;; Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+;; 02110-1301 USA
+
+;;; Commentary:
+;; This file provides a minimal interface to reading the "comment"
+;; section from an Ogg-Vorbis stream as defined in <URL:
+;; http://www.xiph.org/ogg/vorbis/doc/Vorbis_I_spec.html>
+;; It relies on all the comments being in the first 28kilobytes of
+;; the file, thus removing the need to read the whole ogg file into
+;; an Emacs buffer.
+
+;; The implementation is rather "byte-oriented", due to the way the
+;; Ogg-Vorbis file headers are specified. Any improvements in making
+;; the implementation more emacsy would be welcomed.
+
+;;; Installation:
+;; To use, put this file somewhere in your `load-path' and do
+;; (require 'ogg-comment).
+;; You can then read ogg comments from a file by doing:
+;; M-x oggc-show-header RET.
+
+;;; History:
+;;
+
+;;; TODO:
+;; o Read setup header, to get bitrate and such like.
+;; o Make writing comments possible.
+
+;;; Code:
+(eval-when-compile
+ (defvar it)
+ (require 'cl))
+
+(defconst oggc-ogg-header "OggS"
+ "The string indicating the start of an Ogg stream.")
+
+(defconst oggc-identification-header "\001vorbis"
+ "The string indicating the start of the Ogg identification header.")
+
+(defconst oggc-comment-header "\003vorbis"
+ "The string indicating the start of the Ogg comment header.")
+
+(defconst oggc-setup-header "\005vorbis"
+ "The string indicating the start of the Ogg setup header.")
+
+(defconst oggc-code-book-pattern "BCV"
+ "The string indicating the start of an Ogg code book.")
+
+(defconst oggc-version "$Revision: 1.5 $"
+ "Ogg-comment's version number.")
+
+(defun oggc-split-comment (comment)
+ "Split Ogg COMMENT into a (name, value) pair.
+
+If possible (`ccl-execute-on-string' and `ccl-decode-mule-utf-8'
+available), COMMENT is decoded into utf-8.
+
+The name-part is converted to lowercase, to make sure case-differences
+are ignored."
+ (setq comment (split-string comment "="))
+ (list (downcase (car comment))
+ (oggc-decode-utf-8 (or (cadr comment)
+ ""))))
+
+(defun oggc-encode-utf-8 (string)
+ "Encode STRING into utf-8."
+ (if (and (fboundp 'ccl-execute-on-string)
+ (boundp 'ccl-encode-mule-utf-8))
+ (ccl-execute-on-string ccl-encode-mule-utf-8
+ (make-vector 9 nil)
+ string)
+ string))
+
+(defun oggc-decode-utf-8 (string)
+ "Decode STRING from utf-8."
+ (if (and (fboundp 'ccl-execute-on-string)
+ (boundp 'ccl-decode-mule-utf-8))
+ (ccl-execute-on-string ccl-decode-mule-utf-8
+ (make-vector 9 nil)
+ string)
+ string))
+
+(defun oggc-read-string (length)
+ "Read a string from `point' of LENGTH characters.
+
+Advances to (+ LENGTH (point))."
+ (buffer-substring-no-properties
+ (point) (goto-char (+ length (point)))))
+
+(defun oggc-valid-ogg-stream-p ()
+ "Return non-nil if the current buffer contains a valid Ogg-Vorbis stream."
+ (or (search-forward oggc-ogg-header (min 100 (point-max)) t)
+ (error "File does not appear to be a valid ogg stream"))
+ (or (search-forward oggc-identification-header (min 300 (point-max)) t)
+ (error "Not a valid ogg stream")))
+
+(defun oggc-comment-exists-p ()
+ "Return the value of `point' where comments are found in the current buffer."
+ (let ((max (save-excursion
+ (search-forward oggc-setup-header nil t)
+ (point))))
+ (and (search-forward oggc-comment-header max t)
+ (point))))
+
+(defun oggc-bytes-to-lsb-int (n)
+ "Read N bytes as a LSB integer."
+ (loop for i from 0 below n
+ sum (* (expt 256 i)
+ (prog1 (char-after)
+ (forward-char 1)))))
+
+(defun oggc-int-to-lsb-bytes (int n)
+ "Return a list of N bytes encoding INT as a LSB integer."
+ (nreverse (loop for i downfrom (1- n) to 0
+ for exp = (expt 256 i)
+ collect (floor int exp)
+ when (<= exp int)
+ do (setq int (/ int exp)))))
+
+(defun oggc-construct-comment-field (comment-list)
+ "Construct an Ogg-Vorbis comment header from COMMENT-LIST.
+
+COMMENT-LIST should be of the form (TITLE VALUE).
+VALUE is encoded into UTF-8 if possible (`ccl-execute-on-string' and
+`ccl-decode-mule-utf-8' available). The length of the thus ensuing
+comment header is prepended to the string as a 4-byte lsb int."
+ (let* ((title (pop comment-list))
+ (value (pop comment-list)))
+ (setq title (concat title "="
+ (oggc-encode-utf-8 value)))
+ (concat (oggc-int-to-lsb-bytes (length title) 4)
+ title)))
+
+(defun oggc-construct-vendor (vendor)
+ "Construct a vendor string from VENDOR."
+ (concat (oggc-int-to-lsb-bytes (length vendor) 4)
+ vendor))
+
+;;; FIXME: This doesn't work!!
+;;; Somehow, we need to modify one of the code-book headers to make
+;;; note of the fact that the comment has changed. I can't see in
+;;; the spec what needs to be done.
+;;; This doesn't work even for the case where we don't change the
+;;; length of the comment, just one character, e.g. tracknumber=1 to
+;;; tracknumber=2.
+(defun oggc-write-comments (file comments)
+ "Write COMMENTS to FILE.
+
+COMMENTS should be as for `oggc-construct-comment-string' (q.v.)."
+ (with-temp-buffer
+ ;; dog slow for large files.
+ ;; an alternative would be to use head/tail/cut as needed to
+ ;; split the file up and put it back together again.
+ (insert-file-contents-literally file)
+ (when (oggc-valid-ogg-stream-p)
+ (when (oggc-comment-exists-p)
+ (let ((vendor (save-excursion (oggc-read-vendor))))
+ (delete-region (point) (progn (oggc-read-comments (point))
+ (point)))
+ (insert (oggc-construct-vendor vendor)
+ (oggc-construct-comment-string comments))))
+ (write-region nil nil file))))
+
+(defun oggc-construct-comment-string (comments)
+ "Construct a string off Ogg-Vorbis comment headers from COMMENTS.
+
+COMMENTS should be an alist of the form:
+ ((TITLE-1 VALUE-1)
+ (TITLE-2 VALUE-2))"
+ (concat (oggc-int-to-lsb-bytes (length comments) 4)
+ (mapconcat #'oggc-construct-comment-field comments "")))
+
+(defun oggc-read-vendor ()
+ "Read an Ogg-Vorbis vendor string from the current buffer."
+ (let ((length (oggc-bytes-to-lsb-int 4)))
+ (oggc-read-string length)))
+
+(defun oggc-read-comments (pos)
+ "Read Ogg-Vorbis comments, starting POS bytes from `point-min'."
+ (goto-char pos)
+ (let ((vendor (oggc-read-vendor))
+ (length (oggc-bytes-to-lsb-int 4))
+ comments)
+ (loop repeat length
+ for this-length = (oggc-bytes-to-lsb-int 4)
+ for c = (oggc-read-string this-length) do
+ (push (oggc-split-comment c) comments))
+ (list vendor (nreverse comments))))
+
+(defun oggc-read-header (file)
+ "Read an Ogg-Vorbis header from FILE."
+ (with-part-of-file (file 0
+ ;; Lets hope that the comments
+ ;; aren't more than 28KB long.
+ (* 1024 28))
+ (when (oggc-valid-ogg-stream-p)
+ (aif (oggc-comment-exists-p)
+ (oggc-read-comments it)))))
+
+(defun oggc-pretty-print-header (header)
+ "Print Ogg HEADER readably in a temporary buffer."
+ (let ((vendor (car header))
+ (comments (cadr header)))
+ (switch-to-buffer (get-buffer-create "*comments*"))
+ (erase-buffer)
+ (insert "Vendor: "vendor "\n")
+ (mapc #'(lambda (s)
+ (insert (car s) ": " (cadr s) "\n"))
+ comments)))
+
+;;;###autoload
+(defun oggc-show-header (file)
+ "Show a pretty printed representation of the Ogg Comments in FILE."
+ (interactive "FFile: ")
+ (oggc-pretty-print-header (oggc-read-header file)))
+
+(defmacro with-part-of-file (file-spec &rest body)
+ "Execute BODY in a buffer containing part of FILE.
+
+BEG and END are as `insert-file-contents' (q.v.).
+
+\(fn (FILE &optional BEG END) &rest BODY)"
+ (let (file beg end)
+ (setq file (pop file-spec))
+ (and file-spec (setq beg (pop file-spec)))
+ (and file-spec (setq end (pop file-spec)))
+ `(with-temp-buffer
+ (insert-file-contents-literally ,file nil ,beg ,end)
+ (goto-char (point-min))
+ ,@body)))
+
+(defmacro aif (test-form then &rest else)
+ "Like `if', but with `it' bound to the result of TEST-FORM.
+`it' is accessible in the THEN and ELSE clauses.
+
+Warning, non-hygienic by design.
+
+\(fn TEST-FORM THEN &rest ELSE)"
+ `(let ((it ,test-form))
+ (if it
+ ,then
+ ,@else)))
+
+(provide 'ogg-comment)
+
+;;; ogg-comment.el ends here