diff options
author | forcer <forcer> | 2005-09-11 20:05:00 +0000 |
---|---|---|
committer | forcer <mwolson@gnu.org> | 2005-09-11 20:05:00 +0000 |
commit | bb65333ef00df02dbf6bd83294b4df49e64ea325 (patch) | |
tree | 5435715fe823d566ac5494bc672088522af5a763 |
Initial commit (CVS 2005-09-11)
darcs-hash:20050911200506-2189f-48a136015e33465c3cf09940ce935ec2203df463.gz
50 files changed, 8704 insertions, 0 deletions
@@ -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: @@ -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 @@ -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)) + @@ -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 @@ -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 |