org-player.el 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. ;;; -*- coding: utf-8-unix -*-
  2. ;;; org-player.el - Play audio and video files in org-mode hyperlinks
  3. ;;;
  4. ;;; Author: Paul Sexton <eeeickythump@gmail.com>
  5. ;;; Version: 1.0.0
  6. ;;; Repository at http://bitbucket.org/eeeickythump/org-player/
  7. ;;;
  8. ;;;
  9. ;;; Synopsis
  10. ;;; ========
  11. ;;;
  12. ;;; I was learning a language, and wanted to include hyperlinks to audio files
  13. ;;; within my org document, and to be able to play each file by clicking on its
  14. ;;; link. I wrote the code in this file, which makes org mode use an Emacs
  15. ;;; media player library, Bongo, to play audio and video files contained in
  16. ;;; 'file:' hyperlinks.
  17. ;;;
  18. ;;; Installation
  19. ;;; ============
  20. ;;;
  21. ;;; First you will need to install Bongo, which you can download from:
  22. ;;; https://github.com/dbrock/bongo
  23. ;;;
  24. ;;; You will also need to ensure you have an actual media player program
  25. ;;; installed, and you need to tell Bongo which program to use to play files. I
  26. ;;; have tested org-player with MPlayer, an open source media player which
  27. ;;; works well on Windows and Linux. VLC does not work with Bongo on Windows at
  28. ;;; the time of writing.
  29. ;;;
  30. ;;; You can obtain MPlayer from:
  31. ;;; http://www.mplayerhq.hu/design7/news.html
  32. ;;;
  33. ;;; Ensure you have Bongo set up and working. Once this is achieved, installing
  34. ;;; org-player is easy - just put the file somewhere in your Emacs load-path
  35. ;;; and put (require 'org-player) in your .emacs file.
  36. ;;;
  37. ;;; Usage
  38. ;;; =====
  39. ;;;
  40. ;;; Clicking on a link such as
  41. ;;;
  42. ;;; [[file:/path/to/song.mp3]]
  43. ;;;
  44. ;;; adds it to the active Bongo playlist and immediately starts playing
  45. ;;; it. Playback can be paused, fast-forwarded etc using Bongo commands (see
  46. ;;; below).
  47. ;;;
  48. ;;; Links can also specify track positions. When a link contains a track
  49. ;;; position, playback will start at that position in the track. For example:
  50. ;;;
  51. ;;; [[file:/path/to/song.mp3::2:43]] Starts playback at 2 min 43 sec.
  52. ;;; [[file:/path/to/song.mp3::1:10:45]] Starts playback at 1 hr 10 min 45 sec.
  53. ;;; [[file:/path/to/song.mp3::3m15s]] Starts playback at 3 min 15 sec.
  54. ;;; [[file:/path/to/song.mp3::49s]] Starts playback at 0 min 49 sec.
  55. ;;; [[file:/path/to/song.mp3::1h21m10s]] Starts playback at 1 hr 21 min 10 sec.
  56. ;;;
  57. ;;; Controlling playback
  58. ;;; ====================
  59. ;;;
  60. ;;; When Bongo plays a file it puts some icons in the modeline that resemble the
  61. ;;; well-known symbols for 'play', 'stop', 'rewind' and so on, and which can be
  62. ;;; used to control playback using the mouse. I found these worked erratically when
  63. ;;; outside the actual Bongo playlist buffer, so I have instead bound some org-mode
  64. ;;; keys (ctrl + numpad keys) to the relevant functions. These are:
  65. ;;;
  66. ;;; C-<keypad 0> pause/resume
  67. ;;; C-<keypad .> stop, or restart playback from beginning
  68. ;;; C-<keypad /> show track info
  69. ;;; C-<keypad 4> skip back 10 seconds
  70. ;;; C-<keypad 6> skip forward 10 seconds
  71. ;;; C-<keypad 9> increase volume (requires separate 'volume' library
  72. ;;; at https://github.com/dbrock/volume-el )
  73. ;;; C-<keypad 3> decrease volume
  74. ;;;
  75. ;;; Note that these keys (and the modeline icons, if they work) act on the
  76. ;;; track that is active in the Bongo playlist. This will always be the last
  77. ;;; track that you added by clicking on a link in org-mode, unless you alter
  78. ;;; the Bongo playlist outside org mode.
  79. ;;;
  80. ;;; Also note that these keys are only bound within org mode buffers.
  81. ;;;
  82. ;;; Notes
  83. ;;; =====
  84. ;;;
  85. ;;; I have only tested this with MP3 files, but it ought to work with
  86. ;;; video as well, as both MPlayer and Bongo claim to work with video files.
  87. ;;;
  88. ;;; Future plans
  89. ;;; ============
  90. ;;;
  91. ;;; If anyone wants to integrate this code with EMMS, another popular Emacs
  92. ;;; media player library, contributions would be welcome.
  93. (require 'org)
  94. (require 'bongo)
  95. (defvar org-player-file-extensions-regexp
  96. (concat "\\." (regexp-opt
  97. (append bongo-audio-file-name-extensions
  98. bongo-video-file-name-extensions))))
  99. (add-to-list 'org-file-apps
  100. (cons (concat org-player-file-extensions-regexp "$")
  101. '(org-player-play-file file)))
  102. (add-to-list 'org-file-apps
  103. (cons (concat org-player-file-extensions-regexp
  104. "::[0-9]+:[0-9]+\\(:[0-9]+\\|\\)")
  105. '(org-player-play-file file search)))
  106. (add-to-list 'org-file-apps
  107. (cons (concat org-player-file-extensions-regexp
  108. "::[0-9]+[HhMmSs]\\([0-9]+[MmSs]\\|\\)\\([0-9]+[MmSs]\\|\\)")
  109. '(org-player-play-file file search)))
  110. (defun org-player-string-to-time (str)
  111. "Understands any of the following formats: MM:SS, HH:MM:SS, NNhNNmNNs,
  112. NNhNNm, NNmNNs, NNh, NNm, NNs."
  113. (let ((str (substring-no-properties str))
  114. hours mins secs)
  115. (cond
  116. ((string-match "\\([0-9]+\\):\\([0-9]+\\)\\(:[0-9.]+\\|\\)" str)
  117. (if (string= "" (match-string 3 str)) ; XX:YY
  118. (setq mins (match-string 1 str)
  119. secs (match-string 2 str))
  120. (setq hours (match-string 1 str) ; XX:YY:ZZ
  121. mins (match-string 2 str)
  122. secs (subseq (match-string 3 str) 1))))
  123. ((eq (string-match "[0-9]+[HhMmSs]" str)
  124. (string-match "\\([0-9]+[Hh]\\|\\)\\([0-9]+[Mm]\\|\\)\\([0-9.]+[Ss]\\|\\)"
  125. str))
  126. (setq hours (match-string 1 str)
  127. mins (match-string 2 str)
  128. secs (match-string 3 str))
  129. (unless (string= "" hours)
  130. (setq hours (subseq hours 0 (1- (length hours)))))
  131. (unless (string= "" mins)
  132. (setq mins (subseq mins 0 (1- (length mins)))))
  133. (unless (string= "" secs)
  134. (setq secs (subseq secs 0 (1- (length secs))))))
  135. (t
  136. (error "Unrecognised track position specifier: %S" str)))
  137. (setq hours (if (or (null hours) (string= "" hours))
  138. 0 (read hours)))
  139. (setq mins (if (or (null mins) (string= "" mins))
  140. 0 (read mins)))
  141. (setq secs (if (or (null secs) (string= "" secs))
  142. 0 (read secs)))
  143. (list hours mins secs)))
  144. (defun org-player-play-file (filename &optional pos)
  145. "POS, if given, is a string that specifies a track position where
  146. playback should begin. This string is taken from the portion of the
  147. hyperlink that follows a double colon. For example:
  148. [[file:song.mp3::03:22]]
  149. [[file:song.mp3::1h24m3s]]
  150. See `org-player-string-to-time' for a description of the format of
  151. the POS string."
  152. (let ((seek (if pos
  153. (destructuring-bind (hours mins secs)
  154. (org-player-string-to-time pos)
  155. (+ secs
  156. (* mins 60)
  157. (* hours 60 60))))))
  158. (with-bongo-buffer
  159. (bongo-insert-file filename)
  160. (backward-char)
  161. (org-player-start/stop 'play seek)
  162. (when seek
  163. (bongo-seek-to seek)))))
  164. (defun org-player-get-track-info ()
  165. "Returns a list: (TRACKTITLE ALBUMNAME ARTISTNAME ELAPSEDSECS LENGTHSECS)"
  166. (with-bongo-buffer
  167. (let* ((info (bongo-line-infoset))
  168. (track (cdr (assoc 'track info))))
  169. (message "%s" info)
  170. (list (cdr (assoc 'title track))
  171. (cdr (assoc 'album info))
  172. (cdr (assoc 'artist info))
  173. (or (bongo-elapsed-time) 0)
  174. (or (bongo-total-time) 0)))))
  175. (defun org-player-print-track-info ()
  176. "Print out some info about the active track, in the minibuffer."
  177. (interactive)
  178. (destructuring-bind (title album artist elapsed total)
  179. (org-player-get-track-info)
  180. (message "%s" (list title album artist elapsed total))
  181. (cond
  182. ((null title)
  183. (message "No active track."))
  184. (t
  185. (message "Track: %s
  186. Album: %s / %s
  187. %s / %s (%d%%) %s"
  188. title album artist
  189. (format "%02d:%02d" (floor elapsed 60) (mod elapsed 60))
  190. (format "%02d:%02d" (floor total 60) (mod total 60))
  191. (/ (* elapsed 100) total)
  192. (cond
  193. ((bongo-paused-p) "(paused)")
  194. ((bongo-playing-p) "(playing)")
  195. (t "(stopped)"))
  196. )))))
  197. (defun org-player-pause/resume ()
  198. (interactive)
  199. (destructuring-bind (title album artist elapsed total)
  200. (org-player-get-track-info)
  201. (when title
  202. (message "%02d:%02d/%02d:%02d %s %s (%s / %s)"
  203. (floor elapsed 60) (mod elapsed 60)
  204. (floor total 60) (mod total 60)
  205. (if (bongo-paused-p) "Unpaused:" "Paused:")
  206. title album artist)))
  207. (bongo-pause/resume))
  208. (defun org-player-start/stop (&optional force starting-pos)
  209. (interactive)
  210. (destructuring-bind (title album artist elapsed total)
  211. (org-player-get-track-info)
  212. (when title
  213. (cond
  214. ((eq force 'play)
  215. (bongo-play))
  216. ((eq force 'stop)
  217. (bongo-stop))
  218. (t
  219. (bongo-start/stop)))
  220. (let ((msg (format "%02d:%02d/%02d:%02d %s %s (%s / %s)"
  221. (floor (or starting-pos 0) 60)
  222. (mod (or starting-pos 0) 60)
  223. (floor total 60) (mod total 60)
  224. (if (bongo-playing-p) "Playing:" "Stopped:")
  225. title album artist)))
  226. (message msg)))))
  227. ;; Numpad Ctrl-0: pause/resume
  228. (define-key org-mode-map (kbd "<C-kp-0>") 'org-player-pause/resume)
  229. (define-key org-mode-map (kbd "<C-kp-insert>") 'org-player-pause/resume)
  230. ;; Numpad Ctrl-.: stop current track, or restart from beginning if stopped
  231. (define-key org-mode-map (kbd "<C-kp-decimal>") 'org-player-start/stop)
  232. (define-key org-mode-map (kbd "<C-kp-delete>") 'org-player-start/stop)
  233. ;; Numpad Ctrl-PgUp, Ctrl-PgDn: raise/lower volume
  234. (define-key org-mode-map (kbd "<C-kp-prior>") 'volume-raise)
  235. (define-key org-mode-map (kbd "<C-kp-next>") 'volume-lower)
  236. (define-key org-mode-map (kbd "<C-kp-9>") 'volume-raise)
  237. (define-key org-mode-map (kbd "<C-kp-3>") 'volume-lower)
  238. ;; Numpad Ctrl-left, Ctrl-right: skip back/forward 10 seconds
  239. (define-key org-mode-map (kbd "<C-kp-left>") 'bongo-seek-backward-10)
  240. (define-key org-mode-map (kbd "<C-kp-right>") 'bongo-seek-forward-10)
  241. (define-key org-mode-map (kbd "<C-kp-4>") 'bongo-seek-backward-10)
  242. (define-key org-mode-map (kbd "<C-kp-6>") 'bongo-seek-forward-10)
  243. ;; Ctrl-/: show track info
  244. (define-key org-mode-map (kbd "<C-kp-divide>") 'org-player-print-track-info)
  245. (provide 'org-player)