X-Git-Url: http://christophe.rhodes.io/gitweb/?p=iplayer-el.git;a=blobdiff_plain;f=iplayer.el;h=a27450b6da1250ab823320641887dceb5dcd15ab;hp=47ec879760498c184a98e8207e0b1a2486be8ec6;hb=95b13c36c85b1276b4d2eb198497f2249e6de8f4;hpb=4477c6b3556d932de04a34aa3430523355460fdc diff --git a/iplayer.el b/iplayer.el index 47ec879..a27450b 100644 --- a/iplayer.el +++ b/iplayer.el @@ -1,3 +1,107 @@ +;;; iplayer.el --- Browse and download BBC TV/radio shows + +;; Copyright (C) 2012-2013 Christophe Rhodes + +;; Author: Christophe Rhodes +;; Version: 0.1 +;; Keywords: multimedia + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; Requires and uses the 'get-iplayer' script to provide a +;; convenient interface to BBC iPlayer. + +;;; Code: +(defvar iplayer-updating-cache-process nil) +(defvar iplayer-updating-cache-sentinel-info nil) +(defvar iplayer-updating-cache-sentinel-executing nil) + +(defun iplayer-updating-cache-sentinel (process event) + ;; FIXME: assumes that all went well + (let* ((iplayer-updating-cache-sentinel-executing t) + (info (reverse iplayer-updating-cache-sentinel-info))) + (setq iplayer-updating-cache-process nil + iplayer-updating-cache-sentinel-info nil) + (dolist (info info) + (let ((iplayer-command-frame (nth 0 info)) + (iplayer-command-window (nth 1 info)) + (iplayer-command-buffer (nth 2 info)) + (keys (nth 3 info)) + (function (nth 4 info))) + (when (and (frame-live-p iplayer-command-frame) + (window-live-p iplayer-command-window) + (buffer-live-p iplayer-command-buffer)) + (let ((old-frame (selected-frame)) + (old-window (selected-window)) + (old-buffer (current-buffer))) + (cond + ((version< emacs-version "24") + (let ((pre-command-hook + (lambda () + (select-frame iplayer-command-frame) + (select-window iplayer-command-window) + (set-buffer iplayer-command-buffer) + (setq pre-command-hook nil)))) + ;; KLUDGE: execute-kbd-macro executes a normal + ;; command-loop, whose first action is to select the + ;; current frame and window, which is why we contort + ;; things to select the frame/window/buffer we actually + ;; want in pre-command-hook. I'm actually surprised + ;; that it works, but mine is not too much to reason + ;; why; lots of other ways to try to achieve this didn't + ;; in fact work. + (execute-kbd-macro keys) + ;; KLUDGE: and then we restore old state + (select-window old-window) + (select-frame old-frame) + (set-buffer old-buffer))) + (t + ;; KLUDGE: we store the function name, which is fine, + ;; but some of our functions need to know which + ;; keystrokes were used to invoke them, so we need to + ;; pass those along, so we need to make sure that all + ;; iplayer-functions accept an optional argument, argh + ;; argh argh. + (with-selected-frame iplayer-command-frame + (with-current-buffer iplayer-command-buffer + (with-selected-window iplayer-command-window + (funcall function keys))))))))) + (message "Done updating iPlayer cache")))) + +(defmacro define-iplayer-command (name arglist &rest body) + (let (docstring interactive) + (when (stringp (car body)) + (setq docstring (car body) body (cdr body))) + (when (and (consp (car body)) (eql (car (car body)) 'interactive)) + (setq interactive (car body) body (cdr body))) + `(defun ,name ,arglist + ,@(when docstring (list docstring)) + ,@(when interactive (list interactive)) + (unless iplayer-updating-cache-process + (setq iplayer-updating-cache-process + (start-process "updating-iplayer" " *updating-iplayer*" + "get-iplayer" "--type" "radio,tv" "-q")) + (set-process-sentinel iplayer-updating-cache-process + 'iplayer-updating-cache-sentinel) + (message "Updating iPlayer cache")) + (if iplayer-updating-cache-sentinel-executing + (progn ,@body) + (push (list (selected-frame) (selected-window) (current-buffer) (this-command-keys-vector) ',name) + iplayer-updating-cache-sentinel-info))))) + (defun get-iplayer-tree (&rest args) (with-temp-buffer (apply #'call-process "get-iplayer" nil t nil "--nocopyright" "--type" "radio,tv" "--tree" "--terse" args) @@ -54,18 +158,25 @@ ("%" . "BBC Radio 5 live") ("^" . "BBC 6 Music") ("&" . "BBC 7") - ("*" . "BBC Radio 4 Extra"))) + ("*" . "BBC Radio 4 Extra")) + "Alist mapping keys to iPlayer channels. + +Used in the `iplayer-preset' command.") + +(define-iplayer-command iplayer-preset (&optional keys) + "Switch display to a preset channel. -(defun iplayer-preset (&optional prefix) - (interactive "p") - (let ((keys (this-command-keys)) +The presets are defined in the variable `iplayer-presets'." + (interactive) + (let ((keys (or (and keys (concat keys)) (this-command-keys))) (presets (mapcar (lambda (x) (cons (read-kbd-macro (car x)) (cdr x))) iplayer-presets))) - (message "%s" (this-command-keys)) (cond ((= (length keys) 1) (let ((channel (cdr (assoc keys presets)))) (if channel - (iplayer-channel (format "^%s$" channel)) + (progn + (setq mode-line-process (format "[%s]" channel)) + (iplayer-channel (format "^%s$" channel))) (error "no preset for key %s" keys))))))) (defun iplayer-channel (channel) @@ -79,9 +190,32 @@ ;; should probably use a process filter instead to give us a ;; progress bar (message "downloading id %s" id) - (start-process "get-iplayer" " *get-iplayer*" "get-iplayer" "--get" (format "%s" id))) + (start-process "get-iplayer" " *get-iplayer*" "get-iplayer" "--modes=best" "--get" (format "%s" id))) (message "no id at point")))) +(defun iplayer-previous () + (interactive) + (save-match-data + (outline-previous-heading) + (while (and (= (funcall outline-level) 1) (not (bobp))) + (outline-previous-heading))) + (hide-other) + (unless (bobp) + (save-excursion + (outline-up-heading 1 t) + (show-children)))) + +(defun iplayer-next () + (interactive) + (save-match-data + (outline-next-heading) + (while (and (= (funcall outline-level) 1) (not (eobp))) + (outline-next-heading))) + (hide-other) + (save-excursion + (outline-up-heading 1 t) + (show-children))) + (defconst iplayer-mode-map (let ((map (make-sparse-keymap))) (define-key map (kbd "0") 'iplayer) @@ -90,6 +224,8 @@ (define-key map (read-kbd-macro (substring presets i (1+ i))) 'iplayer-preset))) (define-key map (kbd "RET") 'iplayer-download) + (define-key map (kbd "j") 'iplayer-next) + (define-key map (kbd "k") 'iplayer-previous) map )) @@ -100,8 +236,14 @@ (use-local-map iplayer-mode-map) (setq major-mode 'iplayer-mode mode-name "iPlayer")) -(defun iplayer () +(define-iplayer-command iplayer (&optional keys) + "Start the emacs iPlayer interface." (interactive) + (setq mode-line-process nil) (display-iplayer-tree (get-iplayer-tree))) +;;;###autoload +(autoload 'iplayer "iplayer" "Start the emacs iPlayer interface." t) + (provide 'iplayer) +;;; iplayer.el ends here