;; convenient interface to BBC iPlayer.
;;; Code:
+
+(defgroup iplayer nil
+ "Browse and download BBC TV/radio shows."
+ :prefix "iplayer-"
+ :group 'applications)
+
+(defcustom iplayer-download-directory "~/iPlayer/"
+ "Directory into which shows will be downloaded."
+ :group 'iplayer
+ :type 'directory)
+
(defvar iplayer-updating-cache-process nil)
(defvar iplayer-updating-cache-sentinel-info nil)
(defvar iplayer-updating-cache-sentinel-executing nil)
(defun display-iplayer-tree (tree)
(with-current-buffer (get-buffer-create "*iplayer*")
- (delete-region (point-min) (point-max))
+ (let ((buffer-read-only nil))
+ (fundamental-mode)
+ (delete-region (point-min) (point-max))
+ (dolist (entry tree)
+ (let ((program (car entry))
+ (episodes (cdr entry)))
+ (insert (propertize (format "* %s\n" program) 'face 'outline-1))
+ (dolist (episode episodes)
+ (insert (propertize (format "** %s\n" (cdr episode))
+ 'face 'outline-2 'iplayer-id (car episode)))))))
(iplayer-mode)
(orgstruct-mode 1)
- (dolist (entry tree)
- (let ((program (car entry))
- (episodes (cdr entry)))
- (insert (propertize (format "* %s\n" program) 'face 'outline-1))
- (dolist (episode episodes)
- (insert (propertize (format "** %s\n" (cdr episode))
- 'face 'outline-2 'iplayer-id (car episode))))))
(org-overview)
- (goto-char (point-min)))
+ (goto-char (point-min))
+ (if iplayer-current-channel
+ (setq mode-line-process (format "[%s]" iplayer-current-channel))
+ (setq mode-line-process nil)))
(switch-to-buffer (get-buffer-create "*iplayer*")))
(defvar iplayer-presets
Used in the `iplayer-preset' command.")
+(defcustom iplayer-startup-channel "BBC One"
+ "The channel to display at startup"
+ :type `(choice
+ ,@(mapcar (lambda (x) `(const ,(cdr x))) iplayer-presets)
+ (const :tag "Show all content" nil))
+ :group 'iplayer)
+
(defun iplayer-frob-presets (presets)
(cond
((version< emacs-version "24")
(mapcar (lambda (x) (cons (read-kbd-macro (car x)) (cdr x))) presets))
(t presets)))
+(defvar iplayer-current-channel nil)
+
(define-iplayer-command iplayer-preset (&optional keys)
"Switch display to a preset channel.
((= (length keys) 1)
(let ((channel (cdr (assoc keys presets))))
(if channel
- (progn
- (setq mode-line-process (format "[%s]" channel))
- (iplayer-channel (format "^%s$" channel)))
+ (iplayer-channel channel)
(error "no preset for key %s" keys)))))))
(defun iplayer-channel (channel)
- (display-iplayer-tree (get-iplayer-tree "--channel" channel)))
+ (setq iplayer-current-channel channel)
+ (display-iplayer-tree (get-iplayer-tree "--channel" (format "^%s$" channel))))
+
+(define-iplayer-command iplayer-refresh (&optional keys)
+ "Refresh the current iPlayer channel display."
+ (interactive)
+ (if iplayer-current-channel
+ (iplayer-channel iplayer-current-channel)
+ (iplayer-show-all)))
+
+(defun iplayer-download-process-filter (process string)
+ (cond
+ ((string-match "^Starting download" string)
+ (process-put process 'iplayer-state 'downloading)
+ (process-put process 'iplayer-progress 0.0))
+ ((and (eql (process-get process 'iplayer-state) 'downloading)
+ (string-match "(\\([0-9]\\{1,3\\}.[0-9]\\)%)$" string))
+ (process-put process 'iplayer-progress (string-to-number (match-string 1 string))))
+ ((string-match "Started writing to temp file" string)
+ (process-put process 'iplayer-state 'transcoding)
+ (process-put process 'iplayer-progress 0.0))
+ ((string-match " Progress: =*>?\\([0-9]\\{1,3\\}\\)%-*|" string)
+ (let ((idx (match-beginning 0)) (data (match-data)))
+ (while (string-match " Progress: =*>?\\([0-9]\\{1,3\\}\\)%-*|" string (match-end 0))
+ (setq idx (match-beginning 0))
+ (setq data (match-data)))
+ (set-match-data data)
+ (process-put process 'iplayer-progress (string-to-number (match-string 1 string)))))
+ (t (with-current-buffer (process-buffer process)
+ (newline)
+ (insert string)
+ (newline))))
+ (with-current-buffer (get-buffer-create "*iplayer-progress*")
+ (goto-char (point-min))
+ (let ((found
+ (re-search-forward (format "^%s:" (process-get process 'iplayer-id)) nil 'end)))
+ (unless found
+ (unless (= (point) (progn (forward-line 0) (point)))
+ (goto-char (point-max))
+ (newline)))
+ (forward-line 0)
+ (let ((beg (point)))
+ (end-of-line)
+ (delete-region beg (point)))
+ (insert (format "%s: %s %s%%"
+ (process-get process 'iplayer-id)
+ (process-get process 'iplayer-state)
+ (process-get process 'iplayer-progress))))))
(defun iplayer-download ()
(interactive)
(let ((id (get-text-property (point) 'iplayer-id)))
(if id
- (let ((default-directory "~/iPlayer/"))
+ (let ((default-directory iplayer-download-directory))
;; 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" "--modes=best" "--get" (format "%s" id)))
+ (let ((process
+ (start-process "get-iplayer" " *get-iplayer*" "get-iplayer" "--modes=best" "--get" (format "%s" id))))
+ (process-put process 'iplayer-id id)
+ (set-process-filter process 'iplayer-download-process-filter)
+ (display-buffer (get-buffer-create "*iplayer-progress*"))))
(message "no id at point"))))
(defun iplayer-previous ()
(defconst iplayer-mode-map
(let ((map (make-sparse-keymap)))
- (define-key map (kbd "0") 'iplayer)
+ (define-key map (kbd "0") 'iplayer-show-all)
(let ((presets "123456789!\"£$%^&*()"))
(dotimes (i (length presets))
(define-key map (read-kbd-macro (substring presets i (1+ i)))
'iplayer-preset)))
(define-key map (kbd "RET") 'iplayer-download)
+ (define-key map (kbd "g") 'iplayer-refresh)
(define-key map (kbd "j") 'iplayer-next)
(define-key map (kbd "k") 'iplayer-previous)
+ (define-key map (kbd "n") 'iplayer-next)
+ (define-key map (kbd "p") 'iplayer-previous)
map
))
-(defun iplayer-mode ()
+(define-derived-mode iplayer-mode special-mode "iPlayer"
"A major mode for the BBC's iPlayer.
-\\{iplayer-mode-map}"
+\\{iplayer-mode-map}")
+
+(define-iplayer-command iplayer-show-all (&optional keys)
+ "Show all iPlayer entries."
(interactive)
- (use-local-map iplayer-mode-map)
- (setq major-mode 'iplayer-mode mode-name "iPlayer"))
+ (setq iplayer-current-channel nil)
+ (display-iplayer-tree (get-iplayer-tree)))
(define-iplayer-command iplayer (&optional keys)
"Start the emacs iPlayer interface."
(interactive)
- (setq mode-line-process nil)
- (display-iplayer-tree (get-iplayer-tree)))
+ (if iplayer-startup-channel
+ (iplayer-channel iplayer-startup-channel)
+ (iplayer-show-all)))
;;;###autoload
(autoload 'iplayer "iplayer" "Start the emacs iPlayer interface." t)