(or emacs irrelevant)

My org-protocol setup, part 2.

This continues the code from the part 1.

I tried to make the first call to youtube-dl asynchronous, but it wasn't working out. So for the current code, there's still about a 2 second delay before the capture buffer appears.

(require 'async)
(defun org-handle-link-youtube (link)
  (lexical-let*
      ((file-name (org-trim
                   (shell-command-to-string
                    (concat
                     "youtube-dl \""
                     link
                     "\""
                     " -o \"%(title)s.%(ext)s\" --get-filename"))))
       (dir "~/Downloads/Videos")
       (full-name
        (expand-file-name file-name dir)))
    (add-hook 'org-link-hook
              (lambda ()
                (concat
                 (org-make-link-string dir dir)
                 "\n"
                 (org-make-link-string full-name file-name))))
    (async-shell-command
     (format "youtube-dl \"%s\" -o \"%s\"" link full-name))
    (find-file (org-expand "ent.org"))
    (goto-char (point-min))
    (re-search-forward "^\\*+ +Videos" nil t)))

Some notes for people who want to learn more Elisp:

  • lexical-let* is needed to have dir and full-name visible in the lambda.
  • org-make-link-string is a nice utility command that escapes all sorts of characters that org-mode doesn't like, e.g. brackets etc.

You can see my full org-capture and org-protocol setup here.

My org-protocol setup, part 1.

I'm quite busy with a project today, so I can't compose many words. However, pasting and explaining some code is fine. The basic idea is creating TODO tasks in certain org-mode files by clicking a link in Firefox, thanks to org-mode capture.

org-protocol starter

(require 'org-capture)
(require 'org-protocol)
(setq org-protocol-default-template-key "l")
(push '("l" "Link" entry (function org-handle-link)
        "* TODO %(org-wash-link)\nAdded: %U\n%(org-link-hooks)\n%?")
        org-capture-templates)
  • org-wash-link should clear up some redundancies in the TODO
  • org-handle-link should open the appropriate file and heading.
  • org-link-hooks should insert some extra information

Basically, when I capture a question on Stack Overflow, I don't want to see - Stack Overflow - as part of the TODO string, since the TODO itself is stored in wiki/stack.org/* Questions.

(defun org-wash-link ()
  (let ((link (caar org-stored-links))
        (title (cadar org-stored-links)))
    (setq title (replace-regexp-in-string
                 " - Stack Overflow" "" title))
    (org-make-link-string link title)))

This is just a hack for passing information around that functions from org-handle-link can use.

(defvar org-link-hook nil)

(defun org-link-hooks ()
  (prog1
      (mapconcat #'funcall
                 org-link-hook
                 "\n")
    (setq org-link-hook)))

This is the heart of the setup.

(defun org-handle-link ()
  (let ((link (caar org-stored-links))
        file)
    (cond ((string-match "^https://www.youtube.com/" link)
           (org-handle-link-youtube link))
          ((string-match (regexp-quote
                          "http://stackoverflow.com/") link)
           (find-file (org-expand "wiki/stack.org"))
           (goto-char (point-min))
           (re-search-forward "^\\*+ +Questions" nil t))
          (t
           (find-file (org-expand "ent.org"))
           (goto-char (point-min))
           (re-search-forward "^\\*+ +Articles" nil t)))))
  • Youtube links will be handled with org-handle-link-youtube
  • Stack Overflow links will be stored in wiki/stack.org/* Questions
  • all other links will be stored in ent.org/* Articles

I'll write down org-handle-link-youtube in a later post, since I would still like to sort out a few kinks with it. The main issue is that I'm sending two requests to Youtube: one to download the video, which is fine, since async handles it; and other to get the title of the video and put it in the heading. And this other request causes a perceptible delay when capturing.

Rushing headlong

And you're rushing headlong out of control...

-- Brian May

I've finally wrapped a piece of config that I was using for a while in a package called headlong.

What does it do?

It provides a macro called headlong-with that modifies minibuffer completion for the forms within it, making it faster in some situations. For instance:

(headlong-with
 (completing-read "Jump to bookmark: "
                  bookmark-alist nil t))

or:

(headlong-with (read-extended-command))

But more importantly, it provides two commands that can use it efficiently: headlong-bookmark-jump and headlong-bookmark-jump-other. The second one is basically the same as the first one, except it opens the bookmark with pop-to-buffer, i.e. in other window.

How does this completion work?

It's nothing fancy, you will just exit the minibuffer automatically as soon as there is only one completion candidate left. So it saves you one keystroke, namely RET. How much is one keystroke worth? It depends.

If you arrange your bookmarks in a way that I do, with each one starting with a different letter, it saves you 33% of the total keystrokes. For example, suppose I have:

(global-set-key (kbd "M-p") 'bookmark-jump)
(global-set-key (kbd "M-o") 'headlong-bookmark-jump)

Then I can jump to my bookmarked directory named "s: sources" with two methods:

  • M-psRET
  • M-os

The second method looks like it's 33% shorter, but it feels like it's even more, since pressing RET is harder than it should be on most keyboards.

Why is this cool?

This is cool because you can implement your bookmarks as efficiently as you would with just wrapping stuff with a lambda and using global-set-key, except that you can view and edit the bindings with bookmark-bmenu-list, and quickly the update bookmark positions with bookmark-set.

Here's what I get when I call M-x bookmark-bmenu-list:

bookmarks

In the list above:

  • black bookmarks are files
  • blue bookmarks are directories
  • pink bookmarks are functions (you need bookmark+ for them)

The package should be available in MELPA soon.

Yet another youtube-dl interface for Emacs

If you haven't been living under a rock, you already know what Youtube is. It's a repository with videos of varying degree of usefulness with a terrible media player tacked on. Instead, I like to watch my videos in VLC, which comes closest to providing an Emacs-like experience among video players.

Useful VLC shortcuts

Here is a list of shortcuts that really make me stick with VLC:

  • f - toggle full-screen
  • b - toggle audio track
  • n - toggle subtitle track
  • ] - speed up play by 0.1
  • [ - slow down play by 0.1
  • M-right - forward by 15 seconds
  • M-left - backward by 15 seconds
  • C-right - forward by 60 seconds
  • C-left - backward by 60 seconds
  • M-1 - quarter of video size
  • M-2 - half of video size
  • M-3 - full video size
  • M-4 - double video size
  • C-h - toggle mouse buttons

So if you're not watching instructional videos at 1.6 speed, or skipping the Simpsons intro sequence with M-right, you're missing out.

From Youtube to VLC

youtube-dl is an excellent command-line tool for saving the videos from Youtube. It downloads the highest resolution at a usually higher speed than Youtube's player buffers. I've discovered it when I had to download a bunch of lecture videos from edX. You can install it with:

sudo pip install youtube-dl

One Emacs script to rule them all

I quickly tired of opening a shell, setting the directory, entering the command, and pasting the link. So I wrote some Elisp code that does it for me. It's nothing too sophisticated, but I've been using this version for a couple months:

(defun youtube-dl ()
  (interactive)
  (let* ((str (current-kill 0))
         (default-directory "~/Downloads")
         (proc (get-buffer-process (ansi-term "/bin/bash"))))
    (term-send-string
     proc
     (concat "cd ~/Downloads && youtube-dl " str "\n"))))

How it works:

  1. Copy the link in Firefox
  2. M-x youtube-dl.

That's it. A new *ansi-term* will open with the task of downloading the video from the link in the clipboard to ~/Downloads. I don't have to wait for the download to finish and can immediately open the video from dired. See the previous post for the description of dired process-starting setup. I can stack up multiple downloads at once if I wish in different *ansi-term*s.

This is my script. There are many like it, but this one is mine.

I did an internet search before writing this post. Apparently many others had the same idea of integrating youtube-dl into Emacs. You can use mine or any other code to generate a setup that works for you. For instance, for a while, instead of copy-pasting the URL and calling youtube-dl I used to just click the org-mode capture button in Firefox, and it would automatically create a TODO item, download the video, and put the link to the downloaded video in the TODO.

I've dropped this workflow when the yank bug surfaced. I don't yet have enough experience of working with Emacs's C code to fix it. Although, according to this excellent rant, fixing the bug is only half of the problem: getting it merged is hard. I'll see how it goes with my latest tiny patch. So far it has been ignored, but it is only two days old as of now.

Start a process from dired

Here are the standard dired functions for starting processes:

  • ! calls dired-do-shell-command
  • & calls dired-do-async-shell-command

While the second one is usually better than the first one, having the benefit of not locking up Emacs, it's still not convenient enough for me. The reason is pretty simple: I want to keep the processes that I started even when I close Emacs (like opened PDFs or videos). This is a non-issue for people with months-long emacs-uptime, but for me an Emacs session lasts on the order of hours, since I mess about with Elisp a lot. Below, I'll share some of my dired process-related customizations.

Ignore running processes when closing Emacs

Usually there's nothing wrong with just killing a spawned process, like an ipython shell or something.

;; add `flet'
(require 'cl)

(defadvice save-buffers-kill-emacs
  (around no-query-kill-emacs activate)
  "Prevent \"Active processes exist\" query on exit."
  (flet ((process-list ())) ad-do-it))

Guess programs by file extension

With this setup, usually there's no need to manually type in the command name.

(require 'dired-x)

(setq dired-guess-shell-alist-user
      '(("\\.pdf\\'" "evince" "okular")
        ("\\.\\(?:djvu\\|eps\\)\\'" "evince")
        ("\\.\\(?:jpg\\|jpeg\\|png\\|gif\\|xpm\\)\\'" "eog")
        ("\\.\\(?:xcf\\)\\'" "gimp")
        ("\\.csv\\'" "libreoffice")
        ("\\.tex\\'" "pdflatex" "latex")
        ("\\.\\(?:mp4\\|mkv\\|avi\\|flv\\|ogv\\)\\(?:\\.part\\)?\\'"
         "vlc")
        ("\\.\\(?:mp3\\|flac\\)\\'" "rhythmbox")
        ("\\.html?\\'" "firefox")
        ("\\.cue?\\'" "audacious")))

Add nohup

According to info nohup:

`nohup' runs the given COMMAND with hangup signals ignored, so that the command can continue running in the background after you log out.

In my case, it means that the processes started by Emacs can continue running even when Emacs is closed.

(require 'dired-aux)

(defvar dired-filelist-cmd
  '(("vlc" "-L")))

(defun dired-start-process (cmd &optional file-list)
  (interactive
   (let ((files (dired-get-marked-files
                 t current-prefix-arg)))
     (list
      (dired-read-shell-command "& on %s: "
                                current-prefix-arg files)
      files)))
  (let (list-switch)
    (start-process
     cmd nil shell-file-name
     shell-command-switch
     (format
      "nohup 1>/dev/null 2>/dev/null %s \"%s\""
      (if (and (> (length file-list) 1)
               (setq list-switch
                     (cadr (assoc cmd dired-filelist-cmd))))
          (format "%s %s" cmd list-switch)
        cmd)
      (mapconcat #'expand-file-name file-list "\" \"")))))

The dired-filelist-cmd is necessary because vlc weirdly doesn't make a playlist when given a list of files.

Then I bind it to r - a nice shortcut not bound by default in dired:

(define-key dired-mode-map "r" 'dired-start-process)