fordon_greeman / voidfiles

Branch: main
Switch branches/tags
main
theidioticdev add emacs 2079ec5 22h ago
503 lines · 19.6 KB Raw Blame History
1 ;;; emms-source-playlist.el --- EMMS sources from playlist files -*- lexical-binding: t; -*-
2
3 ;; Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008,
4 ;; 2009, 2023 Free Software Foundation, Inc.
5
6 ;; Author: Jorgen Schäfer <forcer@forcix.cx>
7 ;; Keywords: emms, mp3, mpeg, multimedia
8
9 ;; This file is part of EMMS.
10
11 ;; EMMS is free software; you can redistribute it and/or modify
12 ;; it under the terms of the GNU General Public License as published by
13 ;; the Free Software Foundation; either version 3, or (at your option)
14 ;; any later version.
15 ;;
16 ;; EMMS is distributed in the hope that it will be useful,
17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 ;; GNU General Public License for more details.
20 ;;
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with EMMS; see the file COPYING. If not, write to the
23 ;; Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
24 ;; Boston, MA 02110-1301, USA.
25
26 ;;; Commentary:
27
28 ;; This file contains track sources for EMMS which read playlist
29 ;; files. EMMS' own playlist files are supported as well as .m3u and
30 ;; .pls files.
31
32 ;;; Code:
33
34 ;; Version control
35 (defvar emms-source-playlist-version "0.5 $Revision: 1.30 $"
36 "emms-source-playlist.el version string")
37 ;; $Id: emms-source-file.el,v 1.30 2005/08/11 06:16:15 yonirabkin Exp $
38
39 (require 'emms)
40 (require 'emms-source-file)
41
42 (defcustom emms-source-playlist-formats '(native pls m3u)
43 "*A list of playlist formats.
44 Each entry must have at least three corresponding functions.
45
46 First, a function named `emms-source-playlist-FORMAT-p' which
47 returns non-nil if the current buffer is of the type FORMAT. It
48 is called with no arguments.
49
50 Second, a function named `emms-source-playlist-parse-FORMAT'
51 which parses the current buffer into tracks. It is called with
52 no arguments.
53
54 Third, a function named `emms-source-playlist-unparse-FORMAT'
55 which creates an output file in the type FORMAT that contains the
56 tracks of a playlist buffer. It is called with two arguments:
57 The playlist buffer and the file buffer.
58
59 It is also recommended to have a function named
60 `emms-source-playlist-FORMAT-files' which returns a list of the
61 files contained in the playlist."
62 :type '(repeat (symbol :tag "Format"))
63 :group 'emms)
64
65 (defcustom emms-source-playlist-default-format nil
66 "*The default format to use for saving playlists.
67 If this is nil, you will be prompted for a format to use."
68 :type '(choice (const :tag "Prompt each time" nil)
69 (const :tag "Native" native)
70 (const :tag "m3u" m3u)
71 (const :tag "pls" pls)
72 (symbol :tag "Other"))
73 :group 'emms)
74
75 (defcustom emms-source-playlist-ask-before-overwrite t
76 "*Ask before saving over an existing playlist.
77 If this is nil, existing playlists will be quietly overwritten."
78 :type 'boolean
79 :group 'emms)
80
81 (defvar emms-source-playlist-native-header-line
82 ";;; This is an EMMS playlist file"
83 "Line which identifies a native emms playlist.")
84
85 ;;; General playlist
86
87 (defsubst emms-source-playlist-p-sym (format)
88 (intern (concat "emms-source-playlist-" (symbol-name format) "-p")))
89
90 (defsubst emms-source-playlist-parse-sym (format)
91 (intern (concat "emms-source-playlist-parse-" (symbol-name format))))
92
93 (defsubst emms-source-playlist-unparse-sym (format)
94 (intern (concat "emms-source-playlist-unparse-" (symbol-name format))))
95
96 (defsubst emms-source-playlist-files-sym (format)
97 (intern (concat "emms-source-playlist-" (symbol-name format) "-files")))
98
99 (defun emms-source-playlist-p (format &optional parse-files)
100 (let ((sym (emms-source-playlist-p-sym format)))
101 (when (and (functionp sym)
102 (or (not parse-files)
103 (functionp (emms-source-playlist-files-sym format))))
104 (funcall sym))))
105
106 (defun emms-source-playlist-parse (format file)
107 (funcall (emms-source-playlist-parse-sym format) file))
108
109 (defun emms-source-playlist-unparse (format playlist-buf file-buf)
110 (funcall (emms-source-playlist-unparse-sym format) playlist-buf file-buf))
111
112 (defun emms-source-playlist-files (format)
113 (let ((sym (emms-source-playlist-files-sym format)))
114 (if (functionp sym)
115 (funcall sym)
116 (error "The `%s' format cannot parse files from a playlist" format))))
117
118 (defvar emms-source-playlist-format-history nil
119 "List of recently-entered formats; used by `emms-playlist-save'.")
120
121 (defun emms-source-playlist-read-format ()
122 "Read a playlist format from the user.
123 If `emms-source-playlist-default-format' is non-nil, use it
124 instead of prompting the user."
125 (or emms-source-playlist-default-format
126 (let ((format
127 (emms-completing-read
128 (concat "Playlist format: (default: "
129 (if emms-source-playlist-format-history
130 (car emms-source-playlist-format-history)
131 "native")
132 ") ")
133 (mapcar #'symbol-name emms-source-playlist-formats)
134 nil nil nil 'emms-source-playlist-format-history
135 (if emms-source-playlist-format-history
136 (car emms-source-playlist-format-history)
137 "native"))))
138 ;; Sometimes the completion function can put partial results
139 ;; onto the history, so pop the last one off and include the
140 ;; completed version instead.
141 (setq emms-source-playlist-format-history
142 (cons format
143 (cdr emms-source-playlist-format-history)))
144 (intern format))))
145
146 (defun emms-playlist-save (format file)
147 "Store the current playlist to FILE as the type FORMAT.
148 The default format is specified by `emms-source-playlist-default-format'."
149 (interactive (list (emms-source-playlist-read-format)
150 (read-file-name "Store as: "
151 emms-source-file-default-directory
152 emms-source-file-default-directory
153 nil)))
154 (if (or (eq emms-playlist-buffer (current-buffer))
155 (and (not (eq emms-playlist-buffer (current-buffer)))
156 (y-or-n-p
157 (format "Current playlist buffer (%s) is not the one you are visiting (%s). Save anyway?"
158 emms-playlist-buffer (current-buffer)))))
159 (with-temp-buffer
160 (emms-source-playlist-unparse format
161 (with-current-emms-playlist
162 (current-buffer))
163 (current-buffer))
164 (let ((backup-inhibited t))
165 (write-file file emms-source-playlist-ask-before-overwrite)))
166 (message "aborting save")))
167
168 (defun emms-source-playlist-determine-format (&optional parse-files)
169 "Determine the playlist format of the current buffer.
170 If PARSE-FILES is specified, the given format must be able to
171 return a list of the files contained in the playlist."
172 (catch 'return
173 (let ((formats emms-source-playlist-formats))
174 (while formats
175 (when (emms-source-playlist-p (car formats) parse-files)
176 (throw 'return (car formats)))
177 (setq formats (cdr formats))))))
178
179 ;;;###autoload (autoload 'emms-play-playlist "emms-source-playlist" nil t)
180 ;;;###autoload (autoload 'emms-add-playlist "emms-source-playlist" nil t)
181 (define-emms-source playlist (file)
182 "An EMMS source for playlists.
183 See `emms-source-playlist-formats' for a list of supported formats."
184 (interactive (list (read-file-name "Playlist file: "
185 emms-source-file-default-directory
186 emms-source-file-default-directory
187 t)))
188 (mapc #'emms-playlist-insert-track
189 (with-temp-buffer
190 (emms-insert-file-contents file)
191 (goto-char (point-min))
192 (let ((format (emms-source-playlist-determine-format)))
193 (if format
194 (emms-source-playlist-parse format file)
195 (error "Not a recognized playlist format"))))))
196
197 ;;; Emms native playlists
198
199 ;; An Emms native playlist file starts with the contents of
200 ;; `emms-source-playlist-native-header-line' and is followed by
201 ;; tracks in sexp format.
202
203 (defun emms-source-playlist-native-p ()
204 "Return non-nil if the current buffer contains a native EMMS playlist."
205 (save-excursion
206 (goto-char (point-min))
207 (looking-at (concat "^" emms-source-playlist-native-header-line))))
208
209 (defun emms-source-playlist-parse-native (file)
210 "Parse the native EMMS playlist in the current buffer."
211 (ignore file)
212 (let ((tracks (save-excursion
213 (goto-char (point-min))
214 (read (current-buffer)))))
215 (mapc (lambda (track)
216 (when (fboundp emms-cache-set-function)
217 (funcall emms-cache-set-function
218 (emms-track-type track)
219 (emms-track-name track)
220 track)))
221 tracks)
222 tracks))
223
224 (defun emms-source-playlist-unparse-native (in out)
225 "Unparse a native playlist from IN to OUT.
226 IN should be a buffer with a EMMS playlist in it.
227 OUT should be the buffer where tracks are stored in the native EMMS format."
228 (with-current-buffer in ;; Don't modify the position
229 (save-excursion ;; in the IN buffer
230 (with-current-buffer out
231 (insert emms-source-playlist-native-header-line
232 " Play it with M-x emms-play-playlist\n")
233 (insert "("))
234 (let ((firstp t))
235 (goto-char (point-min))
236 (emms-walk-tracks
237 (let ((track (emms-playlist-track-at (point))))
238 (with-current-buffer out
239 (if (not firstp)
240 (insert "\n ")
241 (setq firstp nil))
242 (prin1 track (current-buffer))))))
243 (with-current-buffer out
244 (insert ")\n")))))
245
246 ;;;###autoload (autoload 'emms-play-native-playlist "emms-source-playlist" nil t)
247 ;;;###autoload (autoload 'emms-add-native-playlist "emms-source-playlist" nil t)
248 (define-emms-source native-playlist (file)
249 "An EMMS source for a native EMMS playlist file."
250 (interactive (list (read-file-name "Playlist file: "
251 emms-source-file-default-directory
252 emms-source-file-default-directory
253 t)))
254 (mapc #'emms-playlist-insert-track
255 (with-temp-buffer
256 (emms-insert-file-contents file)
257 (goto-char (point-min))
258 (when (not (emms-source-playlist-native-p))
259 (error "Not a native EMMS playlist file."))
260 (emms-source-playlist-parse-native file))))
261
262 ;;; m3u files
263
264 ;; Format:
265 ;; Either a list of filename-per-line, ignore lines beginning with #
266 ;; or:
267 ;; #EXTM3U
268 ;; #EXTINF:<length in seconds>,<name>
269 ;; <filename>
270
271 ; emms-source-playlist-m3u-p
272 ; emms-source-playlist-parse-m3u
273 ; emms-source-playlist-m3u-files
274 ; emms-source-playlist-unparse-m3u
275
276 (defun emms-source-playlist-m3u-p ()
277 "Return non-nil if the current buffer contains an m3u playlist.
278
279 We currently have no metric for determining whether a buffer is
280 an .m3u playlist based on its contents alone, so we assume that
281 the more restrictive playlist formats have already been
282 detected and simply return non-nil always."
283 t)
284
285 (defun emms-source-playlist-parse-m3u (playlist-file)
286 "Parse the m3u playlist in the current buffer.
287 Files will be relative to the directory of PLAYLIST-FILE, unless
288 they have absolute paths."
289 (let ((dir (file-name-directory playlist-file)))
290 (mapcar (lambda (file)
291 (if (string-match "\\`\\(http[s]?\\|mms\\)://" file)
292 (emms-track 'url file)
293 (emms-track 'file (expand-file-name file dir))))
294 (emms-source-playlist-m3u-files))))
295
296 (defun emms-source-playlist-m3u-files ()
297 "Extract a list of filenames from the given m3u playlist.
298
299 Empty lines and lines starting with '#' are ignored."
300 (let ((files nil))
301 (save-excursion
302 (goto-char (point-min))
303 (while (re-search-forward "^[^# \n].*$" nil t)
304 (setq files (cons (match-string 0) files))))
305 (nreverse files)))
306
307 (defun emms-source-playlist-unparse-m3u (in out)
308 "Unparse an m3u playlist from IN to OUT.
309 IN should be a buffer containing an m3u playlist.
310 OUT should be the buffer where tracks are stored in m3u format."
311 (with-current-buffer in ;; Don't modify the position
312 (save-excursion ;; in the IN buffer
313 (goto-char (point-min))
314 (emms-walk-tracks
315 (let ((track (emms-playlist-track-at (point))))
316 (with-current-buffer out
317 (insert (emms-track-name track) ?\n)))))))
318
319 ;;;###autoload (autoload 'emms-play-m3u-playlist "emms-source-playlist" nil t)
320 ;;;###autoload (autoload 'emms-add-m3u-playlist "emms-source-playlist" nil t)
321 (define-emms-source m3u-playlist (file)
322 "An EMMS source for an m3u playlist file."
323 (interactive (list (read-file-name "Playlist file: "
324 emms-source-file-default-directory
325 emms-source-file-default-directory
326 t)))
327 (mapc #'emms-playlist-insert-track
328 (with-temp-buffer
329 (emms-insert-file-contents file)
330 (goto-char (point-min))
331 (when (not (emms-source-playlist-m3u-p))
332 (error "Not an m3u playlist file."))
333 (emms-source-playlist-parse-m3u file))))
334
335 ;;; pls files
336
337 ;; Format:
338 ;; A list of one filename per line.
339 ;; [playlist]
340 ;; NumberOfEntries=<num_entries>
341 ;; File<position>=<filename>
342
343 ; emms-source-playlist-pls-p
344 ; emms-source-playlist-parse-pls
345 ; emms-source-playlist-pls-files
346 ; emms-source-playlist-unparse-pls
347
348 (defun emms-source-playlist-pls-p ()
349 "Return non-nil if the current buffer contains a pls playlist."
350 (save-excursion
351 (goto-char (point-min))
352 (if (re-search-forward "^File[0-9]*=.+$" nil t)
353 t
354 nil)))
355
356 (defun emms-source-playlist-parse-pls (playlist-file)
357 "Parse the pls playlist in the current buffer.
358 Files will be relative to the directory of PLAYLIST-FILE, unless
359 they have absolute paths."
360 (let ((dir (file-name-directory playlist-file)))
361 (mapcar (lambda (file)
362 (if (string-match "\\`\\(http[s]?\\|mms\\)://" file)
363 (emms-track 'url file)
364 (if (string-match "\\`file://" file) ;; handle file:// uris
365 (let ((file (url-unhex-string (substring file 7))))
366 (emms-track 'file file))
367 (emms-track 'file (expand-file-name file dir)))))
368 (emms-source-playlist-pls-files))))
369
370
371 (defun emms-source-playlist-pls-files ()
372 "Extract a list of filenames from the given pls playlist.
373
374 Empty lines and lines starting with '#' are ignored."
375 (let ((files nil))
376 (save-excursion
377 (goto-char (point-min))
378 (while (re-search-forward "^File[0-9]*=\\(.+\\)$" nil t)
379 (setq files (cons (match-string 1) files))))
380 (nreverse files)))
381
382 (defun emms-source-playlist-unparse-pls (in out)
383 "Unparse a pls playlist from IN to OUT.
384 IN should be a buffer conatining a pls playlist.
385 OUT should be the buffer where tracks are stored in pls format."
386 (with-current-buffer in ;; Don't modify the position
387 (save-excursion ;; in the IN buffer
388 (let ((pos 0)
389 beg)
390 (with-current-buffer out
391 (insert "[playlist]\n")
392 (setq beg (point)))
393 (goto-char (point-min))
394 (emms-walk-tracks
395 (let ((track (emms-playlist-track-at (point))))
396 (setq pos (1+ pos))
397 (with-current-buffer out
398 (insert "File" (number-to-string pos) "="
399 (emms-track-name track) ?\n))))
400 (with-current-buffer out
401 (goto-char beg)
402 (insert "NumberOfEntries=" (number-to-string pos) ?\n))))))
403
404 ;;;###autoload (autoload 'emms-play-pls-playlist "emms-source-playlist" nil t)
405 ;;;###autoload (autoload 'emms-add-pls-playlist "emms-source-playlist" nil t)
406 (define-emms-source pls-playlist (file)
407 "An EMMS source for a pls playlist file."
408 (interactive (list (read-file-name "Playlist file: "
409 emms-source-file-default-directory
410 emms-source-file-default-directory
411 t)))
412 (mapc #'emms-playlist-insert-track
413 (with-temp-buffer
414 (emms-insert-file-contents file)
415 (goto-char (point-min))
416 (when (not (emms-source-playlist-pls-p))
417 (error "Not a pls playlist file."))
418 (emms-source-playlist-parse-pls file))))
419
420 ;;; extm3u files
421
422 ;; Format:
423 ;; #EXTM3U
424 ;; #EXTINF:<length in seconds>,<name>
425 ;; <filename>
426
427 ; emms-source-playlist-extm3u-p
428 ; emms-source-playlist-parse-extm3u
429 ; emms-source-playlist-unparse-extm3u
430
431 ;; (erase-buffer)
432 ;; (insert "#EXTM3U\n")
433 ;; (mapc (lambda (track)
434 ;; (let ((time (or (emms-track-get track 'info-mtime) ""))
435 ;; (artist (emms-track-get track 'info-artist))
436 ;; (title (emms-track-get track 'info-title))
437 ;; (name (emms-track-get track 'name)))
438 ;; (insert (format "#EXTINF: %s,%s - %s\n%s\n"
439 ;; time artist title name))))
440 ;; tracklist)
441 ;; (save-buffer)
442 ;; (kill-buffer (current-buffer)))))
443
444 ;; Not implemented yet
445
446 ;;; Adding playlists as files
447
448 ;;;###autoload (autoload 'emms-play-playlist-file "emms-source-playlist" nil t)
449 ;;;###autoload (autoload 'emms-add-playlist-file "emms-source-playlist" nil t)
450 (define-emms-source playlist-file (file)
451 "An EMMS source for playlist files.
452 This adds the given file to the current EMMS playlist buffer,
453 without adding its contents.
454
455 See `emms-source-playlist-formats' for a list of supported formats."
456 (interactive (list (read-file-name "Playlist file: "
457 emms-source-file-default-directory
458 emms-source-file-default-directory
459 t)))
460 (emms-playlist-insert-track
461 (emms-track 'playlist (expand-file-name file))))
462
463 ;;;###autoload (autoload 'emms-play-playlist-directory
464 ;;;###autoload "emms-source-playlist" nil t)
465 ;;;###autoload (autoload 'emms-add-playlist-directory
466 ;;;###autoload "emms-source-playlist" nil t)
467 (define-emms-source playlist-directory (dir)
468 "An EMMS source for a whole directory tree of playlist files.
469 If DIR is not specified, it is queried from the user."
470 (interactive (list
471 (emms-read-directory-name "Play directory: "
472 emms-source-file-default-directory
473 emms-source-file-default-directory
474 t)))
475 (mapc (lambda (file)
476 (unless (or (let ((case-fold-search nil))
477 (string-match emms-source-file-exclude-regexp file))
478 (file-directory-p file))
479 (emms-playlist-insert-track
480 (emms-track 'playlist (expand-file-name file)))))
481 (directory-files dir t "^[^.]")))
482
483 ;;;###autoload (autoload 'emms-play-playlist-directory-tree
484 ;;;###autoload "emms-source-playlist" nil t)
485 ;;;###autoload (autoload 'emms-add-playlist-directory-tree
486 ;;;###autoload "emms-source-file" nil t)
487 (define-emms-source playlist-directory-tree (dir)
488 "An EMMS source for multiple directory trees of playlist files.
489 If DIR is not specified, it is queried from the user."
490 (interactive (list
491 (emms-read-directory-name "Play directory tree: "
492 emms-source-file-default-directory
493 emms-source-file-default-directory
494 t)))
495 (mapc (lambda (file)
496 (unless (let ((case-fold-search nil))
497 (string-match emms-source-file-exclude-regexp file))
498 (emms-playlist-insert-track
499 (emms-track 'playlist file))))
500 (emms-source-file-directory-tree (expand-file-name dir) "^[^.]")))
501
502 (provide 'emms-source-playlist)
503 ;;; emms-source-playlist.el ends here