├── README.es_MX.md ├── README.md └── mplayer-mode.el /README.es_MX.md: -------------------------------------------------------------------------------- 1 | MPlayer Mode: 2 | ============= 3 | 4 | Este es un minor-mode sencillo para controlar Mplayer desde Emacs. Fue motivado cuando traté de tomar notas mientras veía un video, lo pausaba contínuamente, lo atrasaba un poco, etc. Al final también me hubiera gustado insertar estampas de tiempo. 5 | 6 | Así que esta es la solución (escribirlo probablemente tomó más tiempo que el que me hubiera ahorrado usándolo, pero ya veremos). 7 | 8 | Para empezar, se debe cargar el archivo directamente: 9 | 10 | ; Mplayer-mode 11 | (add-to-list 'load-path "~/.emacs.d/mplayer-mode/mplayer-mode.el") 12 | 13 | o agregarlo en la ruta de carga (`load-path`) y llamar `(require 'mplayer)`. 14 | 15 | ; Mplayer-mode 16 | (add-to-list 'load-path "~/.emacs.d/mplayer-mode/") 17 | (require 'mplayer-mode) 18 | 19 | Ahora, si deseas tomar notas, llama `M-x mplayer-find-file`, esto inicia la reproducción del video. 20 | 21 | Consulta la documentación para más información acerca de los demás comandos. 22 | 23 | Es posible ajustar el formato de las estampas de tiempo. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | MPlayer Mode: 2 | ============= 3 | 4 | This is a simple minor-mode to control mplayer from within emacs. It was motivated when I was trying to take notes while watching a movie, continually pausing, skipping backwards a bit, etc. Then at the finish I wished I'd been inserting timestamps as well. 5 | 6 | So, this is the solution (writing it probably took longer than any time I'll ever save from using it, but we'll see). 7 | 8 | To get started, either load the file directly, or add mplayer-mode directory to your load-path and call `(require 'mplayer-mode)`. 9 | 10 | ; Mplayer-mode 11 | (add-to-list 'load-path "~/.emacs.d/mplayer-mode/") 12 | (require 'mplayer-mode) 13 | 14 | Then, from the buffer you want to take notes in, call `M-x mplayer-find-file`. This starts the movie playing, and you can investigate the documentation for `mplayer-mode` for the available commands. It is possible to configure the timestamp format, default skip time, etc. 15 | -------------------------------------------------------------------------------- /mplayer-mode.el: -------------------------------------------------------------------------------- 1 | ;;; mplayer-mode.el --- control mplayer, facilitating transcription and note-taking. 2 | 3 | ;; Copyright (C) 2011 Mark Hepburn 4 | ;; Copyright (C) 2015 Karl M. Hegbloom 5 | 6 | ;; Author: Mark Hepburn (mark.hepburn@gmail.com) 7 | ;; Compatibility: Emacs20, Emacs21, Emacs22, Emacs23, Emacs24 8 | 9 | ;; Improvements by: Karl M. Hegbloom (karl.hegbloom@gmail.com) 10 | ;; 11 | ;; 2015-11-12 Added: 12 | ;; 13 | ;; `mplayer-find-file-at-point' M-x mplayer-find-file-at-point 14 | ;; 15 | ;; `mplayer-insert-position-and-timestamp' C-x SPC h 16 | ;; 17 | ;; `mplayer-seek-position-at-point' C-x SPC g 18 | ;; 19 | ;; `mplayer-file-start-offset' buffer-local in seconds since midnight. 20 | ;; 21 | ;; When I do a transcription, I put the audio file's name at the top 22 | ;; of the file, and also a heading, like: 23 | ;; 24 | ;; # -*- mode: text; mplayer-file-start-offset: 40430; -*- 25 | ;; 26 | ;; ... so that the time stamps reflect the actual time of day when the 27 | ;; recording was made. Obviously that only works when you have that 28 | ;; information, as for a court recording. 29 | ;; 30 | ;; Suggestions for using this mode: 31 | ;; 32 | ;; In my .emacs, I have: 33 | ;; 34 | ;; (autoload 'mplayer-find-file "mplayer-mode" "Control mplayer from within Emacs") 35 | ;; (autoload 'mplayer-find-file-at-point "mplayer-mode" "Control mplayer from within Emacs") 36 | ;; (require 'mplayer-mode) 37 | ;; (fset 'mplayer-jump-to-start-of-this-block 38 | ;; [?\C-\M-r ?\( ?\[ ?0 ?- ?9 ?\] right ?\C-x ? ?g ?\C-u ?\C- ]) 39 | ;; (define-key text-mode-map (kbd "") #'mplayer-jump-to-start-of-this-block) 40 | ;; 41 | ;; I would have created an interactive function for the jump to start 42 | ;; of block, except that the format of a "position" is configurable, 43 | ;; so I could not hard-code the isearch backward regexp expression 44 | ;; this macro uses. 45 | ;; 46 | ;; When I open a new transcription file, I insert the mode heading, 47 | ;; and then use C-u M-! to run: 48 | ;; 49 | ;; lltags -S 2011-09-09-10-25-00_10-30-12_111905405_Audio_W48_Scheduling_Conference.flac 50 | ;; 51 | ;; ... and it inserts the file's name and the flac metadata, which 52 | ;; I've set for each file, giving casename, courtroom, casenumber, 53 | ;; date, hearingtype, start, and end times, most of which is also 54 | ;; encoded in the file-name, which is designed so they sort 55 | ;; chronographically. I delete the : from the end of the file-name it 56 | ;; prints, put # at the start of each line in case I want to strip 57 | ;; that header from a munged copy used for pretty presentation, 58 | ;; perhaps run through a script and LaTeX'd, and format the heading 59 | ;; nicely to make it readable, with = aligned... 60 | ;; 61 | ;; Now I can put the cursor on the file name, and use: 62 | ;; M-x mplayer-find-file-at-point to begin playback. 63 | ;; 64 | ;; I determine and enter a key at the top, giving Judge, Victim 65 | ;; Advocate, Prosecutor, Defender, and Defendant, but during the part 66 | ;; where they announce their appearances at the start of the 67 | ;; hearing. To begin with, I type: 68 | ;; 69 | ;; C-x SPC SPC to pause the audio, then at the bottom of the file, the 70 | ;; start of the new transcript text, I enter: 71 | ;; 72 | ;; (0.0) F11 using my macro to seek to the start of the file. 73 | 74 | ;; And then C-x SPC i to insert the timestamp for the start of the 75 | ;; hearing, and check to be sure it's right, properly adjusted to the 76 | ;; correct time of day for the hearing start. 77 | ;; 78 | ;; Any time there's a new speaker, I use C-x SPC h to insert a 79 | ;; location and a timestamp, then I enter the person's designation, 80 | ;; eg. Judge: blah blah what she said here. Any time I need to jump 81 | ;; back, to the beginning of the utterance I can push F11. I can 82 | ;; insert a position using C-x SPC t, to set points where I can jump 83 | ;; back to, to hear it again. To get the right setting for the start 84 | ;; of an utterance, I use C-x SPC t, then listen to it, and adjust the 85 | ;; number manually to hit the desired spot in the audio file. With it 86 | ;; paused and F11 pushed to seek to the point indicated by the 87 | ;; position marker, I then use C-x SPC i to insert the 88 | ;; timestamp. Sometimes there's enough time to listen to the tail of 89 | ;; the utterance, with C-x SPC pressed, and my finger poised above the 90 | ;; h key... 91 | ;; 92 | ;; This system works very well and makes it easy to jump back to a 93 | ;; spcecific point in the recording. 94 | ;; 95 | ;; The flac audio format is the only one that this is likely to work 96 | ;; well with aside from wav, since a flac file has an embedded seek 97 | ;; point index. If you try this with an ogg or an mp3, it might not 98 | ;; jump to exactly the same spot each time due to variable bitrate 99 | ;; encoding. With vbr encoded audio, the amount of file space required 100 | ;; to represent a given time length is variable throughout the file, 101 | ;; so there's not a deterministic arithmatic to compute an exact time 102 | ;; offset within the file. That's why flac have seek point indexes in 103 | ;; them! 104 | 105 | 106 | ;; This file is not part of GNU Emacs. 107 | ;; 108 | ;; This is free software; you can redistribute it and/or modify 109 | ;; it under the terms of the GNU General Public License as published by 110 | ;; the Free Software Foundation; either version 3, or (at your option) 111 | ;; any later version. 112 | 113 | ;; This is distributed in the hope that it will be useful, 114 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 115 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 116 | ;; GNU General Public License for more details. 117 | 118 | ;; You should have received a copy of the GNU General Public License 119 | ;; along with GNU Emacs; see the file COPYING. If not, write to the 120 | ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 121 | ;; Boston, MA 02110-1301, USA. 122 | 123 | ;;; Commentary: 124 | ;; Owes a lot in initial idea to the emacs video editor gneve 125 | ;; (http://www.1010.co.uk/gneve.html). This mode controls mplayer 126 | ;; directly, using its slave-mode (see 127 | ;; http://www.mplayerhq.hu/DOCS/tech/slave.txt), which accepts 128 | ;; commands on stdin. The original motivation was to facilitate note 129 | ;; taking from videos; hence it is possible to pause, skip backwards 130 | ;; and forwards, and insert a timestamp of the current position. 131 | 132 | ;;; Use: 133 | 134 | ;;; Install: 135 | 136 | ;; Put something similar to the following in your ~/.emacs to use this file: 137 | ;; 138 | ;; (load "~/path/to/mplayer-mode.el") 139 | ;; 140 | 141 | ;;; Dependency: 142 | 143 | ;; mplayer 144 | 145 | ;;; TODO: 146 | ;; - Proper org-mode integration would probably be nice (eg, a link to the file) 147 | ;; - Error handling and clean-up 148 | 149 | ;;; Code: 150 | 151 | (defgroup mplayer nil 152 | "Group used to store various mplayer-mode variables.") 153 | 154 | 155 | (defcustom mplayer-executable "mplayer" 156 | "Name or path to the mplayer executable." 157 | :type 'file 158 | :group 'mplayer) 159 | 160 | (defvar mplayer-mode-map nil 161 | "Local keymap for mplayer-mode") 162 | 163 | ;;; This prefix is chosen for ergonomic accessibility; it does ignore 164 | ;;; the recomendations about C-x being for global combinations, etc, 165 | ;;; so change if it's inconvenient. 166 | (defcustom mplayer-prefix-command "\C-x " 167 | "The prefix for all mplayer minor-mode commands. Default C-x SPC." 168 | :type 'key-sequence 169 | :group 'mplayer) 170 | 171 | (defcustom mplayer-default-seek-step 10 172 | "The number of seconds that the skip command will use." 173 | :type 'integer 174 | :group 'mplayer) 175 | 176 | (defcustom mplayer-default-speed-step 10 177 | "The increase/decrease of playback speed that the faster/slower commands will use (percent of standard playback speed)." 178 | :type 'integer 179 | :group 'mplayer) 180 | 181 | (defcustom mplayer-osd-level 3 182 | "OSD level used by mplayer. 3 (the default) means position/length." 183 | :type 'integer 184 | :group 'mplayer) 185 | 186 | (defcustom mplayer-position-stamp-format "(%s)" 187 | "Format used for inserting position stamps. 188 | It must contain \%s since the position is given as a string where 189 | this is called." 190 | :type 'string 191 | :group 'mplayer) 192 | 193 | (defcustom mplayer-timestamp-format "[%H:%M:%S.%1N] " 194 | "Format used for inserting timestamps." 195 | :type 'string 196 | :group 'mplayer) 197 | 198 | 199 | ;;; Utilities: 200 | 201 | (defun mplayer--send (cmd) 202 | (process-send-string mplayer-process (concat cmd "\n"))) 203 | 204 | (defun mplayer--parse-seconds (seconds) 205 | (cond 206 | ((null seconds) mplayer-default-seek-step) 207 | ((numberp seconds) seconds) 208 | ((listp seconds) 209 | (* mplayer-default-seek-step (log (abs (car seconds)) 4))))) 210 | 211 | (defun mplayer--parse-speedstep (speedstep) 212 | (cond 213 | ((null speedstep) (/ mplayer-default-speed-step 100.0)) 214 | ((numberp speedstep) (/ speedstep 100.0)) 215 | ((listp speedstep) 216 | (/ (* mplayer-default-speed-step (+ 1 (log (abs (car speedstep)) 4))) 100.0)))) 217 | 218 | (defvar mplayer-file-start-offset 0 219 | "Buffer local wall clock offset for start of file, in seconds.") 220 | (make-variable-buffer-local 'mplayer-file-start-offset) 221 | (put 'mplayer-file-start-offset 'safe-local-variable #'identity) 222 | 223 | (defun mplayer--format-time (time) 224 | "Return a formatted time string, using the format string 225 | `mplayer-timestamp-format'. The argument is in seconds, and 226 | can be an integer or a string. If the buffer-local variable 227 | `mplayer-file-start-offset' is set, that offset is added to 228 | the argument. Its value must be specified in seconds since 229 | midnight." 230 | (message "format-time: %s" time) 231 | (if (stringp time) 232 | (setq time (string-to-number time))) 233 | (if (stringp mplayer-file-start-offset) 234 | (setq mplayer-file-start-offset 235 | (string-to-number mplayer-file-start-offset))) 236 | (setq time (+ time mplayer-file-start-offset)) 237 | (message "time to format: %.1f" time) 238 | ;; All this truncating and rounding is needed because floating point 239 | ;; numbers are not exact, and multiplying by 1000000 amplifies the 240 | ;; error, causing the timestamp to end with .6 rather than .7, etc. 241 | ;; Multiplying by 10, rounding, then dividing by 10.0 gets it right. 242 | (let ((sec (truncate time)) 243 | (usec (truncate (* 1000000 244 | (/ (round (* 10 245 | (- time 246 | (truncate time)))) 247 | 10.0))))) 248 | (format-time-string 249 | mplayer-timestamp-format `(0 ,(truncate time) ,usec) t))) 250 | 251 | 252 | ;;; Interactive Commands: 253 | 254 | (defun mplayer-find-file (filename) 255 | "Entry point to this mode. Starts playing the file using 256 | mplayer, and enables some keybindings to support it; see the 257 | documentation for `mplayer-mode' for available bindings." 258 | (interactive "fOpen recording file: ") 259 | (set (make-local-variable 'mplayer--osd-enabled) nil) 260 | (set (make-local-variable 'mplayer-process-buffer) (generate-new-buffer "*mplayer*")) 261 | (set (make-local-variable 'mplayer-process) 262 | (start-process "mplayer" mplayer-process-buffer 263 | mplayer-executable 264 | "-quiet" "-slave" 265 | filename)) 266 | (mplayer-mode t)) 267 | 268 | (defun mplayer-find-file-at-point () 269 | "Start mplayer on the file name at point. Optionally provide 270 | base director where the audio file is expected to be located." 271 | (interactive) 272 | (let ((filename (thing-at-point 'filename))) 273 | (mplayer-find-file filename))) 274 | 275 | (defun mplayer-toggle-pause () 276 | "Pause or play the currently-open recording." 277 | (interactive) 278 | (mplayer--send "pause")) 279 | 280 | (defun mplayer-seek-forward (seconds) 281 | "Skip forward in the recording. By default this is 282 | `mplayer-default-seek-step' seconds; it can also be specified as 283 | a numeric prefix arg, or plain prefix args act as a 284 | successive (linear) multipliers of `mplayer-default-seek-step'." 285 | (interactive "P") 286 | (let ((seconds (mplayer--parse-seconds seconds))) 287 | (mplayer--send (format "seek %d 0" seconds)))) 288 | 289 | (defun mplayer-seek-backward (seconds) 290 | "Skip backward in the recording. By default this is 291 | `mplayer-default-seek-step' seconds; it can also be specified as 292 | a numeric prefix arg, or plain prefix args act as a 293 | successive (linear) multipliers of `mplayer-default-seek-step'." 294 | (interactive "P") 295 | (let ((seconds (- (mplayer--parse-seconds seconds)))) 296 | (mplayer--send (format "seek %d 0" seconds)))) 297 | 298 | (defun mplayer-faster (speedstep) 299 | "Increase playback speed. By default by `mplayer-default-speed-step' percentage points; it can also be set with a numeric prefix arg, or plain prefix args acts as successive multipliers (2,3,4...) of `mplayer-default-speed-step'" 300 | (interactive "P") 301 | (let ((speedstep (mplayer--parse-speedstep speedstep))) 302 | (mplayer--send (format "speed_incr %.2f" speedstep)))) 303 | 304 | (defun mplayer-slower (speedstep) 305 | "Decreaser playback speed. By default by `mplayer-default-speed-step' percentage points; it can also be set with a numeric prefix arg, or plain prefix args acts as successive multipliers (2,3,4...) of `mplayer-default-speed-step'" 306 | (interactive "P") 307 | (let ((speedstep (mplayer--parse-speedstep speedstep))) 308 | (mplayer--send (format "speed_incr -%.2f" speedstep)))) 309 | 310 | (defun mplayer-reset-speed () 311 | "Reset playback speed." 312 | (interactive) 313 | (mplayer--send "speed_set 1")) 314 | 315 | (defun mplayer-toggle-osd () 316 | "Toggle on-screen display on or off. See `mplayer-osd-level' 317 | for the type of display." 318 | (interactive) 319 | (if mplayer--osd-enabled 320 | (mplayer--send "osd") 321 | (mplayer--send (format "osd %d" mplayer-osd-level))) 322 | (setq mplayer--osd-enabled (not mplayer--osd-enabled))) 323 | 324 | (defun mplayer-insert-timestamp () 325 | "Insert a time-stamp of the current recording position in the 326 | buffer. See `mplayer-timestamp-format' for the insertion 327 | format." 328 | (interactive) 329 | (let (time) 330 | (set-process-filter 331 | mplayer-process 332 | ;; wait for output, process, and remove filter: 333 | (lambda (process output) 334 | (message "process: %s output: %s" process output) 335 | (string-match "^ANS_TIME_POSITION=\\(.*\\)$" output) 336 | (setq time (match-string 1 output)) 337 | (if time 338 | (insert (mplayer--format-time time)) 339 | (message "MPlayer: couldn't detect current time.")) 340 | (set-process-filter mplayer-process nil))) 341 | ;; Then send the command: 342 | (mplayer--send "get_time_pos"))) 343 | 344 | (defun mplayer-insert-position () 345 | "Insert the current recording position in seconds, 346 | into the buffer." 347 | (interactive) 348 | (let (time) 349 | (set-process-filter 350 | mplayer-process 351 | ;; wait for output, process, and remove filter: 352 | (lambda (process output) 353 | (message "process: %s output: %s" process output) 354 | (string-match "^ANS_TIME_POSITION=\\(.*\\)$" output) 355 | (setq time (match-string 1 output)) 356 | (if time 357 | (insert (format mplayer-position-stamp-format time)) 358 | (message "MPlayer: couldn't detect current time.")) 359 | (set-process-filter mplayer-process nil))) 360 | ;; Then send the command: 361 | (mplayer--send "get_time_pos"))) 362 | 363 | (defun mplayer-insert-position-and-timestamp () 364 | (interactive) 365 | (let (time) 366 | (set-process-filter 367 | mplayer-process 368 | ;; wait for output, process, and remove filter: 369 | (lambda (process output) 370 | (message "process: %s output: %s" process output) 371 | (string-match "^ANS_TIME_POSITION=\\(.*\\)$" output) 372 | (setq time (match-string 1 output)) 373 | (if (not time) 374 | (message "MPlayer: couldn't detect current time.") 375 | (insert (format mplayer-position-stamp-format time)) 376 | (insert (mplayer--format-time time))) 377 | (set-process-filter mplayer-process nil))) 378 | ;; Then send the command: 379 | (mplayer--send "get_time_pos"))) 380 | 381 | (defun mplayer-seek-position (position) 382 | "Seek to some place in the recording." 383 | ;; (interactive "P") 384 | (interactive "nEnter seek position: ") 385 | ;; (message "Seeking to position: %d" position) 386 | (mplayer--send (format "seek %.1f 2" position))) 387 | 388 | ;; (bounds-of-thing-at-point 'mplayer-pos)34.9 389 | ;; (thing-at-point 'mplayer-pos)34.9 390 | 391 | (put 'mplayer-pos 'bounds-of-thing-at-point 392 | (lambda () 393 | (let ((thing (thing-at-point-looking-at "[0-9]+\\.[0-9]" 6))) 394 | (if thing 395 | (let ((beginning (match-beginning 0)) 396 | (end (match-end 0))) 397 | (cons beginning end)))))) 398 | 399 | (put 'mplayer-pos 'thing-at-point 400 | (lambda () 401 | (let ((boundary-pair (bounds-of-thing-at-point 'mplayer-pos))) 402 | (if boundary-pair 403 | (string-to-number 404 | (buffer-substring-no-properties 405 | (car boundary-pair) (cdr boundary-pair))))))) 406 | 407 | 408 | (defun mplayer-seek-position-at-point () 409 | "Seek to the position represented by the number at point." 410 | (interactive) 411 | (let ((pos (thing-at-point 'mplayer-pos))) 412 | (message "Seeking to position: %.1f" pos) 413 | (mplayer--send (format "seek %.1f 2" pos)))) 414 | 415 | (defun mplayer-quit-mplayer () 416 | "Quit mplayer and exit this mode." 417 | (interactive) 418 | (mplayer--send "quit") 419 | (set-process-filter 420 | mplayer-process 421 | (lambda (process output) 422 | (kill-buffer mplayer-process-buffer))) 423 | (mplayer-mode nil)) 424 | 425 | ;;; Mode setup: 426 | 427 | (unless mplayer-mode-map 428 | (setq mplayer-mode-map (make-sparse-keymap))) 429 | 430 | (let ((map (make-sparse-keymap))) 431 | (define-key map (kbd "SPC") 'mplayer-toggle-pause) 432 | (define-key map (kbd "") 'mplayer-seek-forward) 433 | (define-key map (kbd "") 'mplayer-seek-backward) 434 | (define-key map (kbd "f") 'mplayer-faster) 435 | (define-key map (kbd "s") 'mplayer-slower) 436 | (define-key map (kbd "r") 'mplayer-reset-speed) 437 | (define-key map (kbd "p") 'mplayer-seek-position) 438 | (define-key map (kbd "g") 'mplayer-seek-position-at-point) 439 | (define-key map (kbd "t") 'mplayer-insert-position) 440 | (define-key map (kbd "d") 'mplayer-toggle-osd) 441 | (define-key map (kbd "i") 'mplayer-insert-timestamp) 442 | (define-key map (kbd "h") 'mplayer-insert-position-and-timestamp) 443 | (define-key map (kbd "q") 'mplayer-quit-mplayer) 444 | 445 | (define-key mplayer-mode-map mplayer-prefix-command map)) 446 | 447 | ;;; This is putting a keybinding into somebody else's keymap... But we 448 | ;;; want that only when this mode is loaded, so why not? What are the 449 | ;;; odds of it clashing with another keybinding really? 450 | (define-key text-mode-map (kbd "C-x SPC C-x C-f") 'mplayer-find-file-at-point) 451 | 452 | (define-minor-mode mplayer-mode 453 | "Control mplayer from within Emacs. Mainly intended for 454 | transcription purposes, so commands exist to pause, seek, set playback speed, and 455 | insert the current time as a timestamp. This mode should not be 456 | invoked directly; see `mplayer-find-file' and 457 | `mplayer-quit-mplayer' for the entry and exit points. 458 | 459 | Key bindings: 460 | \\{mplayer-mode-map}" 461 | nil ; initial value 462 | " MPlayer" ; mode-line string 463 | mplayer-mode-map) 464 | 465 | (provide 'mplayer-mode) 466 | --------------------------------------------------------------------------------