Custom URI schemes in Obisidian (on a mac)

3 minute read

My youngest child started school this week, so I have a bit of time for blogging!

This is another “a neat hack that I’m sharing partly so I remember how I did it in the future”…

The problem

Basically, I have a big ripped mp3 music collection I play using the venerable MPD Music Player Daemon system. I can play tracks and playlists fine with MPD-compatible applications, but I quite often want to listen to and browse music based on albums not tracks. I want “these are my favourite albums” and “these are recent ones I’m still learning” and the like.

Fundamentally mp3 tags are not set up for this - especially for ratings, as the support for rating tags is (a) based on individual files not albums, and (b) pretty inconsistent - different systems use different rating scales, a “3 star” rating on iTunes is different from other apps. It’s a mess.

The core problem is that “I like this track (or album)” doesn’t make sense as metadata - what happens when you share music? Whose rating counts? Do you have one global rating or per-user ratings - and where does that end?

So, after looking for a while at various options, I realised I could just use markdown pages in my Obsidian vault to handle album lists. I can make a “favourite albums” page, and then just add/remove albums from there manually.

But - I’d want a way to play those albums, directly from my vault. A list is no use if you can’t play them.

I could play them from the command-line using mpc - I just needed a way to call mpc from an Obsidian page

First try - using buttons

I’ve used a few ways to add custom buttons to Obsidian before, I thought maybe I could add a button you click to play an album.

I found the Meta Bind Plugin seems to have replaced the Buttons plugin I used to use. It has a way to run inline Javascript which would do what I need.

And this basic code worked, by writing a meta-bind-button code block like:

label: play an album
hidden: false
style: default
actions:
  - type: inlineJS
    code: |
      const execSync = require('child_process').execSync;
      execSync('/opt/homebrew/bin/mpc add "Albums/R/Radiohead/A Moon Shaped Pool"', { encoding: 'utf-8' });

However - I hit a snag. Two snags really. First - the buttons produced this way couldn’t be included in a table, and I wanted a table like:

album artist year  
A moon shaped pool Radiohead 2016 play
hail to the thief Radiohead 2003 play
kid a Radiohead 2000 play

Those buttons could be made but it required yet more code - I had to give every meta-bind-button snipped an ID, and then refer to the ID in the table.

The second snag was that the block of code above was verbose, hard to generate - and adding IDs made it even harder to generate.

I’d seen links before with custom schemes to trigger actions. Obsidian itself has a custom scheme - maybe I could register my own scheme? How hard could it be?

Enter the Platypus! No, not Perry - this Platypus

Platypus is a very neat tool that wraps your own custom scripts - shell scripts or python/ruby/whatever scripts - in a Mac app.

Apart from being handy for running apps, I also found it had a very handy feature - You can register your app as a URI scheme handler

So - I wrote a very generic mpc wrapper in python:

#!/usr/bin/python3

import subprocess
import sys
import shlex
from urllib.parse import urlparse, unquote

if len(sys.argv) != 2:
    print("ALERT:MpcUri|No or too many arguments provided:", len(sys.argv))
    sys.exit(1)

argument = unquote(sys.argv[1])

if not argument.startswith("mpcuri://"):
    print("ALERT:MpcUri|Argument does not start with mpcuri://", argument)
    sys.exit(1)

arguments = shlex.split(argument.split("mpcuri://")[1])

result = subprocess.run(["/opt/homebrew/bin/mpc"] + arguments,
                        stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

print("NOTIFICATION:McpUri output|" + ":".join(result.stdout.decode("utf-8").split("\n")))

and then I used Platypus to wrap this script in an app, and registered it to the mcpuri:: URI scheme. (the process is a bit fiddly - I had to play around a bit to get the right settings, but it wasn’t too hard)

This works nicely! Now I can run arbitrary MCP commands with a link - e.g. to stop playing I can make a link [stop](mpcuri://stop) which turns into a shell command mpc stop

And I can easily generate these links with a bit more scripting, so my eventual “top albums” table can include rows like:

artist album date  
Radiohead Kid A 2000-08-03 play
Radiohead OK Computer 1997-05-21 play
Radiohead The Bends 1995-03-08 play

Where the play links are markdown links like: [play](mpcuri://add%20%22Albums%2FR%2FRadiohead%2FKid%20A%22)

Needless to say this only works on my computer, and it’s very hacky - but it works!

Comments