123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269 |
- ;;; -*- coding: utf-8-unix -*-
- ;;; org-player.el - Play audio and video files in org-mode hyperlinks
- ;;;
- ;;; Author: Paul Sexton <eeeickythump@gmail.com>
- ;;; Version: 1.0.0
- ;;; Repository at http://bitbucket.org/eeeickythump/org-player/
- ;;;
- ;;;
- ;;; Synopsis
- ;;; ========
- ;;;
- ;;; I was learning a language, and wanted to include hyperlinks to audio files
- ;;; within my org document, and to be able to play each file by clicking on its
- ;;; link. I wrote the code in this file, which makes org mode use an Emacs
- ;;; media player library, Bongo, to play audio and video files contained in
- ;;; 'file:' hyperlinks.
- ;;;
- ;;; Installation
- ;;; ============
- ;;;
- ;;; First you will need to install Bongo, which you can download from:
- ;;; https://github.com/dbrock/bongo
- ;;;
- ;;; You will also need to ensure you have an actual media player program
- ;;; installed, and you need to tell Bongo which program to use to play files. I
- ;;; have tested org-player with MPlayer, an open source media player which
- ;;; works well on Windows and Linux. VLC does not work with Bongo on Windows at
- ;;; the time of writing.
- ;;;
- ;;; You can obtain MPlayer from:
- ;;; http://www.mplayerhq.hu/design7/news.html
- ;;;
- ;;; Ensure you have Bongo set up and working. Once this is achieved, installing
- ;;; org-player is easy - just put the file somewhere in your Emacs load-path
- ;;; and put (require 'org-player) in your .emacs file.
- ;;;
- ;;; Usage
- ;;; =====
- ;;;
- ;;; Clicking on a link such as
- ;;;
- ;;; [[file:/path/to/song.mp3]]
- ;;;
- ;;; adds it to the active Bongo playlist and immediately starts playing
- ;;; it. Playback can be paused, fast-forwarded etc using Bongo commands (see
- ;;; below).
- ;;;
- ;;; Links can also specify track positions. When a link contains a track
- ;;; position, playback will start at that position in the track. For example:
- ;;;
- ;;; [[file:/path/to/song.mp3::2:43]] Starts playback at 2 min 43 sec.
- ;;; [[file:/path/to/song.mp3::1:10:45]] Starts playback at 1 hr 10 min 45 sec.
- ;;; [[file:/path/to/song.mp3::3m15s]] Starts playback at 3 min 15 sec.
- ;;; [[file:/path/to/song.mp3::49s]] Starts playback at 0 min 49 sec.
- ;;; [[file:/path/to/song.mp3::1h21m10s]] Starts playback at 1 hr 21 min 10 sec.
- ;;;
- ;;; Controlling playback
- ;;; ====================
- ;;;
- ;;; When Bongo plays a file it puts some icons in the modeline that resemble the
- ;;; well-known symbols for 'play', 'stop', 'rewind' and so on, and which can be
- ;;; used to control playback using the mouse. I found these worked erratically when
- ;;; outside the actual Bongo playlist buffer, so I have instead bound some org-mode
- ;;; keys (ctrl + numpad keys) to the relevant functions. These are:
- ;;;
- ;;; C-<keypad 0> pause/resume
- ;;; C-<keypad .> stop, or restart playback from beginning
- ;;; C-<keypad /> show track info
- ;;; C-<keypad 4> skip back 10 seconds
- ;;; C-<keypad 6> skip forward 10 seconds
- ;;; C-<keypad 9> increase volume (requires separate 'volume' library
- ;;; at https://github.com/dbrock/volume-el )
- ;;; C-<keypad 3> decrease volume
- ;;;
- ;;; Note that these keys (and the modeline icons, if they work) act on the
- ;;; track that is active in the Bongo playlist. This will always be the last
- ;;; track that you added by clicking on a link in org-mode, unless you alter
- ;;; the Bongo playlist outside org mode.
- ;;;
- ;;; Also note that these keys are only bound within org mode buffers.
- ;;;
- ;;; Notes
- ;;; =====
- ;;;
- ;;; I have only tested this with MP3 files, but it ought to work with
- ;;; video as well, as both MPlayer and Bongo claim to work with video files.
- ;;;
- ;;; Future plans
- ;;; ============
- ;;;
- ;;; If anyone wants to integrate this code with EMMS, another popular Emacs
- ;;; media player library, contributions would be welcome.
- (require 'org)
- (require 'bongo)
- (defvar org-player-file-extensions-regexp
- (concat "\\." (regexp-opt
- (append bongo-audio-file-name-extensions
- bongo-video-file-name-extensions))))
- (add-to-list 'org-file-apps
- (cons (concat org-player-file-extensions-regexp "$")
- '(org-player-play-file file)))
- (add-to-list 'org-file-apps
- (cons (concat org-player-file-extensions-regexp
- "::[0-9]+:[0-9]+\\(:[0-9]+\\|\\)")
- '(org-player-play-file file search)))
- (add-to-list 'org-file-apps
- (cons (concat org-player-file-extensions-regexp
- "::[0-9]+[HhMmSs]\\([0-9]+[MmSs]\\|\\)\\([0-9]+[MmSs]\\|\\)")
- '(org-player-play-file file search)))
- (defun org-player-string-to-time (str)
- "Understands any of the following formats: MM:SS, HH:MM:SS, NNhNNmNNs,
- NNhNNm, NNmNNs, NNh, NNm, NNs."
- (let ((str (substring-no-properties str))
- hours mins secs)
- (cond
- ((string-match "\\([0-9]+\\):\\([0-9]+\\)\\(:[0-9.]+\\|\\)" str)
- (if (string= "" (match-string 3 str)) ; XX:YY
- (setq mins (match-string 1 str)
- secs (match-string 2 str))
- (setq hours (match-string 1 str) ; XX:YY:ZZ
- mins (match-string 2 str)
- secs (subseq (match-string 3 str) 1))))
- ((eq (string-match "[0-9]+[HhMmSs]" str)
- (string-match "\\([0-9]+[Hh]\\|\\)\\([0-9]+[Mm]\\|\\)\\([0-9.]+[Ss]\\|\\)"
- str))
- (setq hours (match-string 1 str)
- mins (match-string 2 str)
- secs (match-string 3 str))
- (unless (string= "" hours)
- (setq hours (subseq hours 0 (1- (length hours)))))
- (unless (string= "" mins)
- (setq mins (subseq mins 0 (1- (length mins)))))
- (unless (string= "" secs)
- (setq secs (subseq secs 0 (1- (length secs))))))
- (t
- (error "Unrecognised track position specifier: %S" str)))
- (setq hours (if (or (null hours) (string= "" hours))
- 0 (read hours)))
- (setq mins (if (or (null mins) (string= "" mins))
- 0 (read mins)))
- (setq secs (if (or (null secs) (string= "" secs))
- 0 (read secs)))
- (list hours mins secs)))
- (defun org-player-play-file (filename &optional pos)
- "POS, if given, is a string that specifies a track position where
- playback should begin. This string is taken from the portion of the
- hyperlink that follows a double colon. For example:
- [[file:song.mp3::03:22]]
- [[file:song.mp3::1h24m3s]]
- See `org-player-string-to-time' for a description of the format of
- the POS string."
- (let ((seek (if pos
- (destructuring-bind (hours mins secs)
- (org-player-string-to-time pos)
- (+ secs
- (* mins 60)
- (* hours 60 60))))))
- (with-bongo-buffer
- (bongo-insert-file filename)
- (backward-char)
- (org-player-start/stop 'play seek)
- (when seek
- (bongo-seek-to seek)))))
- (defun org-player-get-track-info ()
- "Returns a list: (TRACKTITLE ALBUMNAME ARTISTNAME ELAPSEDSECS LENGTHSECS)"
- (with-bongo-buffer
- (let* ((info (bongo-line-infoset))
- (track (cdr (assoc 'track info))))
- (message "%s" info)
- (list (cdr (assoc 'title track))
- (cdr (assoc 'album info))
- (cdr (assoc 'artist info))
- (or (bongo-elapsed-time) 0)
- (or (bongo-total-time) 0)))))
- (defun org-player-print-track-info ()
- "Print out some info about the active track, in the minibuffer."
- (interactive)
- (destructuring-bind (title album artist elapsed total)
- (org-player-get-track-info)
- (message "%s" (list title album artist elapsed total))
- (cond
- ((null title)
- (message "No active track."))
- (t
- (message "Track: %s
- Album: %s / %s
- %s / %s (%d%%) %s"
- title album artist
- (format "%02d:%02d" (floor elapsed 60) (mod elapsed 60))
- (format "%02d:%02d" (floor total 60) (mod total 60))
- (/ (* elapsed 100) total)
- (cond
- ((bongo-paused-p) "(paused)")
- ((bongo-playing-p) "(playing)")
- (t "(stopped)"))
- )))))
- (defun org-player-pause/resume ()
- (interactive)
- (destructuring-bind (title album artist elapsed total)
- (org-player-get-track-info)
- (when title
- (message "%02d:%02d/%02d:%02d %s %s (%s / %s)"
- (floor elapsed 60) (mod elapsed 60)
- (floor total 60) (mod total 60)
- (if (bongo-paused-p) "Unpaused:" "Paused:")
- title album artist)))
- (bongo-pause/resume))
- (defun org-player-start/stop (&optional force starting-pos)
- (interactive)
- (destructuring-bind (title album artist elapsed total)
- (org-player-get-track-info)
- (when title
- (cond
- ((eq force 'play)
- (bongo-play))
- ((eq force 'stop)
- (bongo-stop))
- (t
- (bongo-start/stop)))
- (let ((msg (format "%02d:%02d/%02d:%02d %s %s (%s / %s)"
- (floor (or starting-pos 0) 60)
- (mod (or starting-pos 0) 60)
- (floor total 60) (mod total 60)
- (if (bongo-playing-p) "Playing:" "Stopped:")
- title album artist)))
- (message msg)))))
- ;; Numpad Ctrl-0: pause/resume
- (define-key org-mode-map (kbd "<C-kp-0>") 'org-player-pause/resume)
- (define-key org-mode-map (kbd "<C-kp-insert>") 'org-player-pause/resume)
- ;; Numpad Ctrl-.: stop current track, or restart from beginning if stopped
- (define-key org-mode-map (kbd "<C-kp-decimal>") 'org-player-start/stop)
- (define-key org-mode-map (kbd "<C-kp-delete>") 'org-player-start/stop)
- ;; Numpad Ctrl-PgUp, Ctrl-PgDn: raise/lower volume
- (define-key org-mode-map (kbd "<C-kp-prior>") 'volume-raise)
- (define-key org-mode-map (kbd "<C-kp-next>") 'volume-lower)
- (define-key org-mode-map (kbd "<C-kp-9>") 'volume-raise)
- (define-key org-mode-map (kbd "<C-kp-3>") 'volume-lower)
- ;; Numpad Ctrl-left, Ctrl-right: skip back/forward 10 seconds
- (define-key org-mode-map (kbd "<C-kp-left>") 'bongo-seek-backward-10)
- (define-key org-mode-map (kbd "<C-kp-right>") 'bongo-seek-forward-10)
- (define-key org-mode-map (kbd "<C-kp-4>") 'bongo-seek-backward-10)
- (define-key org-mode-map (kbd "<C-kp-6>") 'bongo-seek-forward-10)
- ;; Ctrl-/: show track info
- (define-key org-mode-map (kbd "<C-kp-divide>") 'org-player-print-track-info)
- (provide 'org-player)
|