Quickly change playlist view options on macOS

While Apple is slowly coming around to recognizing that some of its users listen to classical music, there is one quirk in the Music app on macOS that betrays its deep bias toward pop music. It’s this: when you create a new playlist, the application defaults to displaying the tracks in its “Playlist” view, which as far as I can tell serves no other function than to consume real-estate in the UI by displaying a thumbnail of the album art.

This is completely wasted space. The album art adds nothing. I just want a simple list of tracks. And please include the composer. Since the Music application refuses to allow me to set that as a permanent preference, we’re stuck with automating the reversion to our preferred view format.

Enter Keyboard Maestro. Again, it comes to the rescue. The order of actions is this:

  1. ⌘J to bring up the view options dialogue.
  2. Select “Songs” in “View As:”
  3. Select “Albums” in “Sort By:”
  4. Select “Composer” checked.
  5. Close the view options dialogue.

I’ve programmed this macro to respond to ⌃P (for “playlist”.)

Now, the Music app will display my playlist as just a simple list of tracks, including the composer.

Obsidian file creation date debacle and a solution

Obsidian is pretty reckless with file creation dates. If you modify a note in Obsidian, it updates the file creation date. This renders Dataview queries that rely on it useless. For an introduction to this issue, see this lengthy thread on the Obsidian forums.

Workarounds

There are a several solutions to this problem.

1. YAML-based dates

One can include a cdate (or similar) field in the note’s front matter and just direct the Dataview query against that, e.g. LIST FROM "" WHERE startswith(cdate,"2023-05-29") SORT file.ctime asc. This works, but of course it requires you to always place that field ahead of your note content. Some people like that; others not so much.

2. Timestamp in the note title

This is what a lot of Zettelkasten folks do. But, queries against the title are going to need a lot of fancy string manipulation to make that work.

Fixing the file system date

This leaves open the question of what to do about the file system dates that Obsidian keeps changing.

1. If you include the creation date in the front matter…

All of my notes have a creation date cdate filed in the YAML, so I can use that to update the file system creation date. What follows is a macOS-specific solution (sorry, Linux and Windows users.) If you’re not on macOS, you can at least see the approach that I take and use that as a basis for a more suitable solution on your platform. It does require SetFile which is a tool included in the macOS Developer command line tools. You’ll have to install those first.

#!/bin/bash

OBSDIR="/path/to/your/obsidian/vault"
find "$OBSDIR" -type f -newerBt "$(date -v-15m)" |
while read -r line ; do
   # get the actual date created from cdate in the file
   ACTUAL_DATE=$(cat "$line" | grep -E 'cdate:[[:space:]]+[0-9]{4}')
   if [[ "${#ACTUAL_DATE}" -gt 0 ]]; then
      ACTUAL_DATE=$(echo "$ACTUAL_DATE" | 
         sed -E 's/cdate:[[:space:]]+([0-9]{4})-([0-9]{2})-([0-9]{2})/\2\/\3\/\1/g')
      
      # get the file system creation date
      FS_DATE=$(getfileinfo -d "$line" | 
         sed -E 's/(.*[[:space:]]+[[:digit:]]+:[[:digit:]]+).*/\1/g')
      
      # check for mismatched dates
      if [ ! "$FS_DATE" = "$ACTUAL_DATE" ]; then
         # mismatched dates, need to fix filesystem
         setfile -d "$ACTUAL_DATE" "$line"
         
         # inform user
         FN=$(basename -- "$line")
         printf "Fixed creation date for %s\n" "$FN"
      fi
   fi
done

If you set this up to run as a global daemon every 5 minutes, it will continue fixing files “created” into last few minutes.

2. If you encode the creation date in the title…

If you name your notes with a timestamp like the Zettelkasten folks do (like, 20230530211642 This is my note title) then you can go through the same process as above, but parse the date a little differently.

#!/bin/bash

OBSDIR="/path/to/your/obsidian/vault"
FILES=$(find "$OBSDIR" -type f -newerBt "$(date -v-15M)" | grep -E '[0-9]{14}')

if [ "${#FILES[@]}" -gt 1 ]; then
   echo "Obsidian has ${#FILES[@]} ZK files with new creation dates."
fi

IFS=$'\n'
for FPATH in $FILES; do
   FN=$(basename -- "$FPATH")
   echo "$FN"
   ACTUAL=$(echo "$FN" |
      sed -E 's/([0-9]{4})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2}).*/\2\/\3\/\1 \4:\5/g')
   setfile -d "$ACTUAL" "$FPATH"
done

Again, you can set this up as a global daemon to run periodically and it will reset the creation date, similar to the previous code.

Changing the file creation date on macOS

If you modify a file in-place using sed with the -i option, you will get a file that has a new file creation date. On macOS 13.3.1, this is absolutely 100% true, although you will read claims otherwise. I ran into this problem while implementing a Hazel rule that updates YAML automatically in my Obsidian notes.

Background

I have use YAML frontmatter in my Obsidian notes. It looks like:

---
uid:     20221120152124
aliases: [20221120152124, AllAboutShell]
cdate:   2022-11-20 15:21
mdate:   2023-05-18 05:14
type:    zettel
---

My goal is to update the mdate field whenever the file changes. Hazel is the perfect tool for this, so I set about writing a rule that covers this case. The heart of the rule is a shell script action that writes the modification date:

Flatten airports in X-Plane

Some airports in X-Plane have terrain issues that can be quite entertaining.

This Delta 737-800 got lost in the maze of cargo ramps at PANC and was trying to taxi back to the terminal when it encountered a steep icy taxiway. It required 65% N1 just to get up the slope.

Clearly a fix is required. It turns out to be quite simple. In the global airports file apt.dat, find the offending airport. In this case, it’s PANC where its entry looks like:

Hazel deletes custom file icons, and a workaround

I use Hazel extensively for automating file management tasks on my macOS systems. Recently I found that Hazel aggressively matches an invisible system file that appears whenever you use a custom file or folder icon. I’ll describe the problem and present a workaround.

In a handful of directories, I have a rule that prevents users (me) from adding certain file types. So the rule just matches any file that is not an image, for example, and deletes it. This is all well and good until to try to add a custom icon to this directory. Since the file Icon? that gets created as a result is not an image, the Hazel rule dutifully deletes it.

AwesomeTTS Anki add-on: Use Amazon Polly

As its name implies, the AwesomeTTS Anki add-on is awesome. It’s nearly indispensable for language learners.

You can use it in one of two ways:

  1. Subscribe on your own to the text-to-speech services that you plan to use and add those credentials to AwesomeTTS. (à la carte)
  2. Subscribe to the AwesomeTTS+ service and gain access to these services. (prix fixe)

Because I had already subscribed to Google and Azure TTS before AwesomeTTS+ came on the scene, there was no reason for me to pay for the comprehensive prix fixe option. Furthermore, since I’ve never gone above the free tier on any of these services, it makes no sense for me to pay for something I’m already getting for free. For others, the convience of a one-stop-shopping experience probably makes the AwesomeTTS+ service worthwhile.

Using fswatch to dynamically update Obsidian documents

Although I’m a relative newcomer to Obsidian, I like what I see, especially the templating and data access functionality - both that provided natively and through the Templater and Dataview plugins.

One missing piece is the ability to dynamically update the YAML-formatted metadata in the frontmatter of Obsidian’s Markdown documents. Several threads on both the official support forums and on r/ObsidianMD have addressed this; and there seems to be no real solution.1 None proposed solution - mainly view Dataview inline queries or Templater dynamic commands - seems to work consistently.

Week functions in Dataview plugin for Obsidian

There are a couple features of the Dataview plugin for Obsidian that aren’t documented and are potentially useful.

For the start of the week, use date(sow) and for the end of the week date(eow). Since there’s no documentation as of yet, I’ll venture a guess that they are locale-dependendent. For me (in Canada), sow is Monday. Since I do my weekly notes on Saturday, I have to subtract a couple days to point to them.

Scraping Forvo pronunciations

Most language learners are familiar with Forvo, a site that allows users to download and contribute pronunciations for words and phrases. For my Russian studies, I make daily use of the site. In fact, to facilitate my Anki card-making workflow, I am a paid user of the Forvo API. But that’s where the trouble started.

When the Forvo API works, it works OK, often extremely slow. But lately, it has been down more than up. In an effort to patch my workflow and continue to download Russian word pronunciations, I wrote this little scraper. I’d prefer to use the API, but experience has shown now that the API is slow and unreliable. I’ll keep paying for the API access, because I support what the company does. And as often as not when a company offers a free service, it’s likely to be involved in surveillance capitalism. So I’d rather companies offer a reliable product at a reasonable price.

A regex to remove Anki's cloze markup

Recently, someone asked a question on r/Anki about changing and existing cloze-type note to a regular note. Part of the solution involves stripping the cloze markup from the existing cloze’d field. A cloze sentence has the form Play {{c1::studid}} games. or Play {{c1::stupid::pejorative adj}} games.

To handle both of these cases, the following regular expression will work. Just substitute for $1.

\{\{c\d::([^:\}]+)(?:::+[^\}]*)*\}\}

However, the Cloze Anything markup is different. It uses ( and ) instead of curly braces. If we want to flexibly remove both the standard and Cloze Anything markup, then our pattern would look like: