diff options
Diffstat (limited to 'emms-source-playlist.el')
-rw-r--r-- | emms-source-playlist.el | 494 |
1 files changed, 494 insertions, 0 deletions
diff --git a/emms-source-playlist.el b/emms-source-playlist.el new file mode 100644 index 0000000..327be8c --- /dev/null +++ b/emms-source-playlist.el @@ -0,0 +1,494 @@ +;;; emms-source-playlist.el --- EMMS sources from playlist files + +;; Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, +;; 2009 Free Software Foundation, Inc. + +;; Author: Jorgen Schäfer <forcer@forcix.cx> +;; Keywords: emms, mp3, mpeg, multimedia + +;; This file is part of EMMS. + +;; EMMS 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 3, or (at your option) +;; any later version. +;; +;; EMMS 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 EMMS; 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 track sources for EMMS which read playlist +;; files. EMMS' own playlist files are supported as well as .m3u and +;; .pls files. + +;;; Code: + +;; Version control +(defvar emms-source-playlist-version "0.5 $Revision: 1.30 $" + "emms-source-playlist.el version string") +;; $Id: emms-source-file.el,v 1.30 2005/08/11 06:16:15 yonirabkin Exp $ + +(require 'emms) +(require 'emms-source-file) + +(defcustom emms-source-playlist-formats '(native pls m3u) + "*A list of playlist formats. +Each entry must have at least three corresponding functions. + +First, a function named `emms-source-playlist-FORMAT-p' which +returns non-nil if the current buffer is of the type FORMAT. It +is called with no arguments. + +Second, a function named `emms-source-playlist-parse-FORMAT' +which parses the current buffer into tracks. It is called with +no arguments. + +Third, a function named `emms-source-playlist-unparse-FORMAT' +which creates an output file in the type FORMAT that contains the +tracks of a playlist buffer. It is called with two arguments: +The playlist buffer and the file buffer. + +It is also recommended to have a function named +`emms-source-playlist-FORMAT-files' which returns a list of the +files contained in the playlist." + :type '(repeat (symbol :tag "Format")) + :group 'emms) + +(defcustom emms-source-playlist-default-format nil + "*The default format to use for saving playlists. +If this is nil, you will be prompted for a format to use." + :type '(choice (const :tag "Prompt each time" nil) + (const :tag "Native" native) + (const :tag "m3u" m3u) + (const :tag "pls" pls) + (symbol :tag "Other")) + :group 'emms) + +(defcustom emms-source-playlist-ask-before-overwrite t + "*Ask before saving over an existing playlist. +If this is nil, existing playlists will be quietly overwritten." + :type 'boolean + :group 'emms) + +(defvar emms-source-playlist-native-header-line + ";;; This is an EMMS playlist file" + "Line which identifies a native emms playlist.") + +;;; General playlist + +(defsubst emms-source-playlist-p-sym (format) + (intern (concat "emms-source-playlist-" (symbol-name format) "-p"))) + +(defsubst emms-source-playlist-parse-sym (format) + (intern (concat "emms-source-playlist-parse-" (symbol-name format)))) + +(defsubst emms-source-playlist-unparse-sym (format) + (intern (concat "emms-source-playlist-unparse-" (symbol-name format)))) + +(defsubst emms-source-playlist-files-sym (format) + (intern (concat "emms-source-playlist-" (symbol-name format) "-files"))) + +(defun emms-source-playlist-p (format &optional parse-files) + (let ((sym (emms-source-playlist-p-sym format))) + (when (and (functionp sym) + (or (not parse-files) + (functionp (emms-source-playlist-files-sym format)))) + (funcall sym)))) + +(defun emms-source-playlist-parse (format file) + (funcall (emms-source-playlist-parse-sym format) file)) + +(defun emms-source-playlist-unparse (format playlist-buf file-buf) + (funcall (emms-source-playlist-unparse-sym format) playlist-buf file-buf)) + +(defun emms-source-playlist-files (format) + (let ((sym (emms-source-playlist-files-sym format))) + (if (functionp sym) + (funcall sym) + (error "The `%s' format cannot parse files from a playlist" format)))) + +(defvar emms-source-playlist-format-history nil + "List of recently-entered formats; used by `emms-playlist-save'.") + +(defun emms-source-playlist-read-format () + "Read a playlist format from the user. +If `emms-source-playlist-default-format' is non-nil, use it +instead of prompting the user." + (or emms-source-playlist-default-format + (let ((format + (emms-completing-read + (concat "Playlist format: (default: " + (if emms-source-playlist-format-history + (car emms-source-playlist-format-history) + "native") + ") ") + (mapcar #'symbol-name emms-source-playlist-formats) + nil nil nil 'emms-source-playlist-format-history + (if emms-source-playlist-format-history + (car emms-source-playlist-format-history) + "native")))) + ;; Sometimes the completion function can put partial results + ;; onto the history, so pop the last one off and include the + ;; completed version instead. + (setq emms-source-playlist-format-history + (cons format + (cdr emms-source-playlist-format-history))) + (intern format)))) + +(defun emms-playlist-save (format file) + "Store the current playlist to FILE as the type FORMAT. +The default format is specified by `emms-source-playlist-default-format'." + (interactive (list (emms-source-playlist-read-format) + (read-file-name "Store as: " + emms-source-file-default-directory + emms-source-file-default-directory + nil))) + (if (or (eq emms-playlist-buffer (current-buffer)) + (and (not (eq emms-playlist-buffer (current-buffer))) + (y-or-n-p + (format "Current playlist buffer (%s) is not the one you are visiting (%s). Save anyway?" + emms-playlist-buffer (current-buffer))))) + (with-temp-buffer + (emms-source-playlist-unparse format + (with-current-emms-playlist + (current-buffer)) + (current-buffer)) + (let ((backup-inhibited t)) + (write-file file emms-source-playlist-ask-before-overwrite))) + (message "aborting save"))) + +(defun emms-source-playlist-determine-format (&optional parse-files) + "Determine the playlist format of the current buffer. +If PARSE-FILES is specified, the given format must be able to +return a list of the files contained in the playlist." + (catch 'return + (let ((formats emms-source-playlist-formats)) + (while formats + (when (emms-source-playlist-p (car formats) parse-files) + (throw 'return (car formats))) + (setq formats (cdr formats)))))) + +;;;###autoload (autoload 'emms-play-playlist "emms-source-playlist" nil t) +;;;###autoload (autoload 'emms-add-playlist "emms-source-playlist" nil t) +(define-emms-source playlist (file) + "An EMMS source for playlists. +See `emms-source-playlist-formats' for a list of supported formats." + (interactive (list (read-file-name "Playlist file: " + emms-source-file-default-directory + emms-source-file-default-directory + t))) + (mapc #'emms-playlist-insert-track + (with-temp-buffer + (emms-insert-file-contents file) + (goto-char (point-min)) + (let ((format (emms-source-playlist-determine-format))) + (if format + (emms-source-playlist-parse format file) + (error "Not a recognized playlist format")))))) + +;;; Emms native playlists + +;; An Emms native playlist file starts with the contents of +;; `emms-source-playlist-native-header-line' and is followed by +;; tracks in sexp format. + +(defun emms-source-playlist-native-p () + "Return non-nil if the current buffer contains a native EMMS playlist." + (save-excursion + (goto-char (point-min)) + (looking-at (concat "^" emms-source-playlist-native-header-line)))) + +(defun emms-source-playlist-parse-native (file) + "Parse the native EMMS playlist in the current buffer." + (save-excursion + (goto-char (point-min)) + (read (current-buffer)))) + +(defun emms-source-playlist-unparse-native (in out) + "Unparse a native playlist from IN to OUT. +IN should be a buffer with a EMMS playlist in it. +OUT should be the buffer where tracks are stored in the native EMMS format." + (with-current-buffer in ;; Don't modify the position + (save-excursion ;; in the IN buffer + (with-current-buffer out + (insert emms-source-playlist-native-header-line + " Play it with M-x emms-play-playlist\n") + (insert "(")) + (let ((firstp t)) + (goto-char (point-min)) + (emms-walk-tracks + (let ((track (emms-playlist-track-at (point)))) + (with-current-buffer out + (if (not firstp) + (insert "\n ") + (setq firstp nil)) + (prin1 track (current-buffer)))))) + (with-current-buffer out + (insert ")\n"))))) + +;;;###autoload (autoload 'emms-play-native-playlist "emms-source-playlist" nil t) +;;;###autoload (autoload 'emms-add-native-playlist "emms-source-playlist" nil t) +(define-emms-source native-playlist (file) + "An EMMS source for a native EMMS playlist file." + (interactive (list (read-file-name "Playlist file: " + emms-source-file-default-directory + emms-source-file-default-directory + t))) + (mapc #'emms-playlist-insert-track + (with-temp-buffer + (emms-insert-file-contents file) + (goto-char (point-min)) + (when (not (emms-source-playlist-native-p)) + (error "Not a native EMMS playlist file.")) + (emms-source-playlist-parse-native file)))) + +;;; m3u files + +;; Format: +;; Either a list of filename-per-line, ignore lines beginning with # +;; or: +;; #EXTM3U +;; #EXTINF:<length in seconds>,<name> +;; <filename> + +; emms-source-playlist-m3u-p +; emms-source-playlist-parse-m3u +; emms-source-playlist-m3u-files +; emms-source-playlist-unparse-m3u + +(defun emms-source-playlist-m3u-p () + "Return non-nil if the current buffer contains an m3u playlist. + +We currently have no metric for determining whether a buffer is +an .m3u playlist based on its contents alone, so we assume that +the more restrictive playlist formats have already been +detected and simply return non-nil always." + t) + +(defun emms-source-playlist-parse-m3u (playlist-file) + "Parse the m3u playlist in the current buffer. +Files will be relative to the directory of PLAYLIST-FILE, unless +they have absolute paths." + (let ((dir (file-name-directory playlist-file))) + (mapcar (lambda (file) + (if (string-match "\\`\\(http[s]?\\|mms\\)://" file) + (emms-track 'url file) + (emms-track 'file (expand-file-name file dir)))) + (emms-source-playlist-m3u-files)))) + +(defun emms-source-playlist-m3u-files () + "Extract a list of filenames from the given m3u playlist. + +Empty lines and lines starting with '#' are ignored." + (let ((files nil)) + (save-excursion + (goto-char (point-min)) + (while (re-search-forward "^[^# \n].*$" nil t) + (setq files (cons (match-string 0) files)))) + (nreverse files))) + +(defun emms-source-playlist-unparse-m3u (in out) + "Unparse an m3u playlist from IN to OUT. +IN should be a buffer containing an m3u playlist. +OUT should be the buffer where tracks are stored in m3u format." + (with-current-buffer in ;; Don't modify the position + (save-excursion ;; in the IN buffer + (goto-char (point-min)) + (emms-walk-tracks + (let ((track (emms-playlist-track-at (point)))) + (with-current-buffer out + (insert (emms-track-name track) ?\n))))))) + +;;;###autoload (autoload 'emms-play-m3u-playlist "emms-source-playlist" nil t) +;;;###autoload (autoload 'emms-add-m3u-playlist "emms-source-playlist" nil t) +(define-emms-source m3u-playlist (file) + "An EMMS source for an m3u playlist file." + (interactive (list (read-file-name "Playlist file: " + emms-source-file-default-directory + emms-source-file-default-directory + t))) + (mapc #'emms-playlist-insert-track + (with-temp-buffer + (emms-insert-file-contents file) + (goto-char (point-min)) + (when (not (emms-source-playlist-m3u-p)) + (error "Not an m3u playlist file.")) + (emms-source-playlist-parse-m3u file)))) + +;;; pls files + +;; Format: +;; A list of one filename per line. +;; [playlist] +;; NumberOfEntries=<num_entries> +;; File<position>=<filename> + +; emms-source-playlist-pls-p +; emms-source-playlist-parse-pls +; emms-source-playlist-pls-files +; emms-source-playlist-unparse-pls + +(defun emms-source-playlist-pls-p () + "Return non-nil if the current buffer contains a pls playlist." + (save-excursion + (goto-char (point-min)) + (if (re-search-forward "^File[0-9]*=.+$" nil t) + t + nil))) + +(defun emms-source-playlist-parse-pls (playlist-file) + "Parse the pls playlist in the current buffer. +Files will be relative to the directory of PLAYLIST-FILE, unless +they have absolute paths." + (let ((dir (file-name-directory playlist-file))) + (mapcar (lambda (file) + (if (string-match "\\`\\(http[s]?\\|mms\\)://" file) + (emms-track 'url file) + (if (string-match "\\`file://" file) ;; handle file:// uris + (let ((file (url-unhex-string (substring file 7)))) + (emms-track 'file file)) + (emms-track 'file (expand-file-name file dir))))) + (emms-source-playlist-pls-files)))) + + +(defun emms-source-playlist-pls-files () + "Extract a list of filenames from the given pls playlist. + +Empty lines and lines starting with '#' are ignored." + (let ((files nil)) + (save-excursion + (goto-char (point-min)) + (while (re-search-forward "^File[0-9]*=\\(.+\\)$" nil t) + (setq files (cons (match-string 1) files)))) + (nreverse files))) + +(defun emms-source-playlist-unparse-pls (in out) + "Unparse a pls playlist from IN to OUT. +IN should be a buffer conatining a pls playlist. +OUT should be the buffer where tracks are stored in pls format." + (with-current-buffer in ;; Don't modify the position + (save-excursion ;; in the IN buffer + (let ((pos 0) + beg) + (with-current-buffer out + (insert "[playlist]\n") + (setq beg (point))) + (goto-char (point-min)) + (emms-walk-tracks + (let ((track (emms-playlist-track-at (point)))) + (setq pos (1+ pos)) + (with-current-buffer out + (insert "File" (number-to-string pos) "=" + (emms-track-name track) ?\n)))) + (with-current-buffer out + (goto-char beg) + (insert "NumberOfEntries=" (number-to-string pos) ?\n)))))) + +;;;###autoload (autoload 'emms-play-pls-playlist "emms-source-playlist" nil t) +;;;###autoload (autoload 'emms-add-pls-playlist "emms-source-playlist" nil t) +(define-emms-source pls-playlist (file) + "An EMMS source for a pls playlist file." + (interactive (list (read-file-name "Playlist file: " + emms-source-file-default-directory + emms-source-file-default-directory + t))) + (mapc #'emms-playlist-insert-track + (with-temp-buffer + (emms-insert-file-contents file) + (goto-char (point-min)) + (when (not (emms-source-playlist-pls-p)) + (error "Not a pls playlist file.")) + (emms-source-playlist-parse-pls file)))) + +;;; extm3u files + +;; Format: +;; #EXTM3U +;; #EXTINF:<length in seconds>,<name> +;; <filename> + +; emms-source-playlist-extm3u-p +; emms-source-playlist-parse-extm3u +; emms-source-playlist-unparse-extm3u + +;; (erase-buffer) +;; (insert "#EXTM3U\n") +;; (mapc (lambda (track) +;; (let ((time (or (emms-track-get track 'info-mtime) "")) +;; (artist (emms-track-get track 'info-artist)) +;; (title (emms-track-get track 'info-title)) +;; (name (emms-track-get track 'name))) +;; (insert (format "#EXTINF: %s,%s - %s\n%s\n" +;; time artist title name)))) +;; tracklist) +;; (save-buffer) +;; (kill-buffer (current-buffer))))) + +;; Not implemented yet + +;;; Adding playlists as files + +;;;###autoload (autoload 'emms-play-playlist-file "emms-source-playlist" nil t) +;;;###autoload (autoload 'emms-add-playlist-file "emms-source-playlist" nil t) +(define-emms-source playlist-file (file) + "An EMMS source for playlist files. +This adds the given file to the current EMMS playlist buffer, +without adding its contents. + +See `emms-source-playlist-formats' for a list of supported formats." + (interactive (list (read-file-name "Playlist file: " + emms-source-file-default-directory + emms-source-file-default-directory + t))) + (emms-playlist-insert-track + (emms-track 'playlist (expand-file-name file)))) + +;;;###autoload (autoload 'emms-play-playlist-directory +;;;###autoload "emms-source-playlist" nil t) +;;;###autoload (autoload 'emms-add-playlist-directory +;;;###autoload "emms-source-playlist" nil t) +(define-emms-source playlist-directory (dir) + "An EMMS source for a whole directory tree of playlist files. +If DIR is not specified, it is queried from the user." + (interactive (list + (emms-read-directory-name "Play directory: " + emms-source-file-default-directory + emms-source-file-default-directory + t))) + (mapc (lambda (file) + (unless (or (let ((case-fold-search nil)) + (string-match emms-source-file-exclude-regexp file)) + (file-directory-p file)) + (emms-playlist-insert-track + (emms-track 'playlist (expand-file-name file))))) + (directory-files dir t "^[^.]"))) + +;;;###autoload (autoload 'emms-play-playlist-directory-tree +;;;###autoload "emms-source-playlist" nil t) +;;;###autoload (autoload 'emms-add-playlist-directory-tree +;;;###autoload "emms-source-file" nil t) +(define-emms-source playlist-directory-tree (dir) + "An EMMS source for multiple directory trees of playlist files. +If DIR is not specified, it is queried from the user." + (interactive (list + (emms-read-directory-name "Play directory tree: " + emms-source-file-default-directory + emms-source-file-default-directory + t))) + (mapc (lambda (file) + (unless (let ((case-fold-search nil)) + (string-match emms-source-file-exclude-regexp file)) + (emms-playlist-insert-track + (emms-track 'playlist file)))) + (emms-source-file-directory-tree (expand-file-name dir) "^[^.]"))) + +(provide 'emms-source-playlist) +;;; emms-source-playlist.el ends here |