Christophe Weblog Wiki Code Publications Music
first cut of iPlayer interface for Emacs
authorChristophe Rhodes <csr21@cantab.net>
Mon, 23 Apr 2012 20:19:36 +0000 (21:19 +0100)
committerChristophe Rhodes <csr21@cantab.net>
Mon, 23 Apr 2012 20:19:36 +0000 (21:19 +0100)
Very raw.  Uses get_iplayer (spelled "get-iplayer" on my system); "parses"
the tree output; uses Orgstruct for presentation, and text properties for
user interface.  Uncached calls to get-iplayer (the first in any four hours,
unless the user has a cron job) blocks the whole of emacs for a good little
while as get-iplayer spends its time pulling down metadata, and there is
almost no useful feedback when downloading.

On the other hand, it's easier for me than the command-line interface.

iplayer.el [new file with mode: 0644]

diff --git a/iplayer.el b/iplayer.el
new file mode 100644 (file)
index 0000000..47ec879
--- /dev/null
@@ -0,0 +1,107 @@
+(defun get-iplayer-tree (&rest args)
+  (with-temp-buffer
+    (apply #'call-process "get-iplayer" nil t nil "--nocopyright" "--type" "radio,tv" "--tree" "--terse" args)
+    (goto-char (point-min))
+    (let (result program episodes)
+      (while (< (point) (point-max))
+        (cond
+         ((looking-at "^\\w")
+          (when (and program episodes)
+            (push (cons program (reverse episodes)) result))
+          (setf program (buffer-substring (point) (progn (end-of-line) (point))))
+          (when (string-match "^\\(tv\\|radio\\), " program)
+            (setq program (substring program (match-end 0))))
+          (setf episodes nil)
+          (unless (= (point) (point-max))
+            (forward-char)))
+         ((looking-at "^  \\([0-9]+\\):\\s-\\(.*\\)$")
+          (let ((episode
+                 (cons (buffer-substring (match-beginning 1) (match-end 1))
+                       (buffer-substring (match-beginning 2) (match-end 2)))))
+            (when (string-match "^\\(tv\\|radio\\), " (cdr  episode))
+              (rplacd episode (substring (cdr episode) (match-end 0))))
+            (push episode episodes))
+          (forward-line))
+         (t (forward-line))))
+      (reverse result))))
+
+(defun display-iplayer-tree (tree)
+  (with-current-buffer (get-buffer-create "*iplayer*")
+    (delete-region (point-min) (point-max))
+    (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)))
+  (switch-to-buffer (get-buffer-create "*iplayer*")))
+
+(defvar iplayer-presets
+  '(("1" . "BBC One")
+    ("2" . "BBC Two")
+    ("3" . "BBC Three")
+    ("4" . "BBC Four")
+
+    ("!" . "BBC Radio 1")
+    ("\"" . "BBC Radio 2")
+    ("£" . "BBC Radio 3")
+    ("$" . "BBC Radio 4")
+    ("%" . "BBC Radio 5 live")
+    ("^" . "BBC 6 Music")
+    ("&" . "BBC 7")
+    ("*" . "BBC Radio 4 Extra")))
+
+(defun iplayer-preset (&optional prefix)
+  (interactive "p")
+  (let ((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))
+          (error "no preset for key %s" keys)))))))
+
+(defun iplayer-channel (channel)
+  (display-iplayer-tree (get-iplayer-tree "--channel" channel)))
+
+(defun iplayer-download ()
+  (interactive)
+  (let ((id (get-text-property (point) 'iplayer-id)))
+    (if id
+        (let ((default-directory "~/iPlayer/"))
+          ;; 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)))
+      (message "no id at point"))))
+
+(defconst iplayer-mode-map
+  (let ((map (make-sparse-keymap)))
+    (define-key map (kbd "0") 'iplayer)
+    (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)
+    map
+    ))
+
+(defun iplayer-mode ()
+  "A major mode for the BBC's iPlayer.
+\\{iplayer-mode-map}"
+  (interactive)
+  (use-local-map iplayer-mode-map)
+  (setq major-mode 'iplayer-mode mode-name "iPlayer"))
+
+(defun iplayer ()
+  (interactive)
+  (display-iplayer-tree (get-iplayer-tree)))
+
+(provide 'iplayer)