| 1 | ;;; emms-cue.el --- Recognize cue sheet file -*- lexical-binding: t; -*- |
| 2 | |
| 3 | ;; Copyright (C) 2009 Free Software Foundation, Inc. |
| 4 | |
| 5 | ;; Author: William Xu <william.xwl@gmail.com> |
| 6 | |
| 7 | ;; This file is part of EMMS. |
| 8 | |
| 9 | ;; EMMS is free software; you can redistribute it and/or |
| 10 | ;; modify it under the terms of the GNU General Public License |
| 11 | ;; as published by the Free Software Foundation; either version 3 |
| 12 | ;; of the License, or (at your option) any later version. |
| 13 | |
| 14 | ;; EMMS is distributed in the hope that it will be useful, |
| 15 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 16 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 17 | ;; GNU General Public License for more details. |
| 18 | |
| 19 | ;; You should have received a copy of the GNU General Public License |
| 20 | ;; along with EMMS; if not, write to the Free Software Foundation, |
| 21 | ;; Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. |
| 22 | |
| 23 | ;;; Commentary: |
| 24 | |
| 25 | ;; By parsing cue file, we will be able to jump to arbitary track or |
| 26 | ;; play next/previous track from a single .ape or .flac file. |
| 27 | |
| 28 | ;;; Code: |
| 29 | |
| 30 | (require 'emms-playing-time) |
| 31 | (require 'emms-info) |
| 32 | |
| 33 | (defun emms-cue-next () |
| 34 | "Play next track from .cue file." |
| 35 | (interactive) |
| 36 | (let ((cue-track (emms-cue-next-track))) |
| 37 | (if (cdr cue-track) |
| 38 | (progn |
| 39 | (emms-seek-to (cdr cue-track)) |
| 40 | (message "Will play: %s" (car cue-track))) |
| 41 | (message "Nothing to seek or missing .cue file?")))) |
| 42 | |
| 43 | (defun emms-cue-previous () |
| 44 | "Play previous track from .cue file." |
| 45 | (interactive) |
| 46 | (let ((cue-track (emms-cue-previous-track))) |
| 47 | (if (cdr cue-track) |
| 48 | (progn |
| 49 | (emms-seek-to (cdr cue-track)) |
| 50 | (message "Will play: %s" (car cue-track))) |
| 51 | (message "Nothing to seek or missing .cue file?")))) |
| 52 | |
| 53 | (defun emms-cue-jump () |
| 54 | "Select a track from .cue file to play using completion." |
| 55 | (interactive) |
| 56 | (let ((cue-track (emms-cue-select-track))) |
| 57 | (if (cdr cue-track) |
| 58 | (progn |
| 59 | (emms-seek-to (cdr cue-track)) |
| 60 | (message "Will play: %s" (car cue-track))) |
| 61 | (message "Nothing to seek or missing .cue file?")))) |
| 62 | |
| 63 | (defun emms-cue-next-track (&optional previous-p) |
| 64 | "Get title and offset of next track from .cue file. |
| 65 | |
| 66 | When PREVIOUS-P is t, get previous track info instead." |
| 67 | (let* ((track (emms-playlist-current-selected-track)) |
| 68 | (name (emms-track-get track 'name)) |
| 69 | (cue (concat (file-name-sans-extension name)".cue"))) |
| 70 | (when (file-exists-p cue) |
| 71 | (with-temp-buffer |
| 72 | (emms-insert-file-contents cue) |
| 73 | (save-excursion |
| 74 | (if previous-p |
| 75 | (goto-char (point-max)) |
| 76 | (goto-char (point-min))) |
| 77 | (let ((offset nil) |
| 78 | (title "") |
| 79 | ;; We should search one more track far when getting previous |
| 80 | ;; track. |
| 81 | (one-more-track previous-p)) |
| 82 | (while (and (not offset) |
| 83 | (funcall |
| 84 | (if previous-p 'search-backward-regexp 'search-forward-regexp) |
| 85 | "INDEX 01 \\([0-9][0-9]\\):\\([0-9][0-9]\\):\\([0-9][0-9]\\)" nil t 1)) |
| 86 | (let* ((min (string-to-number (match-string-no-properties 1))) |
| 87 | (sec (string-to-number (match-string-no-properties 2))) |
| 88 | (msec (string-to-number (match-string-no-properties 3))) |
| 89 | (total-sec (+ (* min 60) sec (/ msec 100.0)))) |
| 90 | (when (funcall (if previous-p '> '<) emms-playing-time total-sec) |
| 91 | (if (not one-more-track) |
| 92 | (progn |
| 93 | (setq offset total-sec) |
| 94 | (when (search-backward-regexp "TITLE \"\\(.*\\)\"" nil t 1) |
| 95 | (setq title (match-string-no-properties 1)))) |
| 96 | (setq one-more-track nil))))) |
| 97 | (cons title offset))))))) |
| 98 | |
| 99 | (defun emms-cue-previous-track () |
| 100 | "See `emms-cue-next-track'." |
| 101 | (emms-cue-next-track t)) |
| 102 | |
| 103 | (defun emms-cue-select-track () |
| 104 | "Get a list of title and offset of tracks from .cue file and call |
| 105 | completing-read to select one" |
| 106 | (let* ((track (emms-playlist-current-selected-track)) |
| 107 | (name (emms-track-get track 'name)) |
| 108 | (cue (concat (file-name-sans-extension name)".cue")) |
| 109 | (tracks-found '())) |
| 110 | (when (file-exists-p cue) |
| 111 | (with-temp-buffer |
| 112 | (emms-insert-file-contents cue) |
| 113 | (save-excursion |
| 114 | (goto-char (point-max)) ; search backwards |
| 115 | (while (search-backward-regexp "INDEX 01 \\([0-9][0-9]\\):\\([0-9][0-9]\\):\\([0-9][0-9]\\)" nil t 1) |
| 116 | (let* ((min (string-to-number (match-string-no-properties 1))) |
| 117 | (sec (string-to-number (match-string-no-properties 2))) |
| 118 | (msec (string-to-number (match-string-no-properties 3))) |
| 119 | (total-sec (+ (* min 60) sec (/ msec 100.0))) |
| 120 | (title "")) |
| 121 | (when (search-backward-regexp "TITLE \"\\(.*\\)\"" nil t 1) |
| 122 | (setq title (match-string-no-properties 1))) |
| 123 | (push (cons title total-sec) tracks-found))))) |
| 124 | (let* ((tracks-complete-table (lambda (string pred action) |
| 125 | (if (eq action 'metadata) |
| 126 | `(metadata (display-sort-function . ,#'identity)) ; don't sort |
| 127 | (complete-with-action action (mapcar #'car tracks-found) string pred)))) |
| 128 | (selection (completing-read "Select a track to play: " tracks-complete-table nil t))) |
| 129 | (assoc selection tracks-found))))) |
| 130 | |
| 131 | (defun emms-info-cueinfo (track) |
| 132 | "Add track information to TRACK. |
| 133 | This is a useful element for `emms-info-functions'." |
| 134 | (when (and (emms-track-file-p track) |
| 135 | (string-match "\\.\\(ape\\|flac\\)\\'" (emms-track-name track))) |
| 136 | (let ((cue (concat (file-name-sans-extension (emms-track-name track)) |
| 137 | ".cue"))) |
| 138 | (when (file-exists-p cue) |
| 139 | (with-temp-buffer |
| 140 | (emms-insert-file-contents cue) |
| 141 | (save-excursion |
| 142 | (mapc (lambda (i) |
| 143 | (goto-char (point-min)) |
| 144 | (when (let ((case-fold-search t)) |
| 145 | (search-forward-regexp |
| 146 | (concat (car i) " \\(.*\\)") nil t 1)) |
| 147 | (emms-track-set track |
| 148 | (cdr i) |
| 149 | (replace-regexp-in-string |
| 150 | "\\`\"\\|\"\\'" "" (match-string 1))))) |
| 151 | '(("performer" . info-artist) |
| 152 | ("title" . info-album) |
| 153 | ("title" . info-title) |
| 154 | ("rem date" . info-year))))))))) |
| 155 | |
| 156 | |
| 157 | (provide 'emms-cue) |
| 158 | ;;; emms-cue.el ends here |