Art Class

Forewarning: blunt re-telling of personal history, discussions of politics, indoctrination and propaganda, anti-Semitism, all relevant to current events. If you’ve read other posts on this site, this one is very much not like the rest.

I was born in Egypt in the very early 90s. When I was about two or three my parents moved to one of the oil-rich Gulf countries to work. That is where I went to school.

Explaining education in Gulf countries is way out of scope for what I want to exorcise today. What I can tell you is that until I was almost done with high school, we didn’t know whether we’d have to go back to Egypt, so from grades 1 to 9 I went to a school that taught the Egyptian curriculum. The country was not Egypt, but the students were Egyptian, the teachers were Egyptian, the books came from Egypt, and the exams came from Egypt.

This is not a day to mince words, so I will also tell you that the education I received was abysmal on every level: factually, practically, morally. It would be a joke if it wasn’t so despicably ruinous of childhood as well as an education. In the midst of it there is one particular black mark I often think of, and that’s art class.

The Egyptian school curriculum for middle school grades included art. You had to take art.1 Of course like with every other subject, this requirement was purely nominal and for show. What it meant was children’s asses needed to be in seats, and a teacher tasked with being the “art” teacher had to check a box that says art happened. No one taught any artistic technique, theory, history, media. I do remember being taught one thing in art class though, and that’s hating Israel and hating Jews.


The Egyptian school curriculum and culture are awash with propaganda. Any subject that could be warped to extol the virtues of the nation and demean its enemies was thusly warped. Geography books taught lies about what the country produced, chapters on civics told fantasies about the form of government we had, and history books taught outrageous fables about victories that were in fact defeats and betrayals that never happened.2

One of the most important national holidays in Egypt has nothing to do with independence or the founding of a nation. It’s October 6th, the day Egypt “defeated” Israel in the Yom Kippur War of 1973. Egypt, a nation that gained independence in 1922 and sits on top of one of the world’s oldest civilizations marks the peak of its national pride on a war it lies about to its own people. A war that Egyptians are taught was a rout when in fact by the end Damascus itself was being shelled and Israeli forces were 100km from Cairo.

And each year in art class, for weeks that run up to October 6th, we did one thing and one thing only: paint the Egyptian victory, and more importantly, paint the Israeli defeat.

I have memories of countless paintings I had to do of generic battlefields with Egyptian flags flapping in the wind while Israeli flags burn, of proud Egyptian soldiers cresting one sand dune after another, machine guns in hand spewing bullets at hapless Israeli soldiers collapsing every which way while Egyptian fighter jets shoot Israeli planes out of the sky.

Blood, fire, barbed wire, bodies, and burnt tanks. Those are the only things I remember from art class.


It’s embarrassing to think of the things I was taught and mortifying to think of the things I believed, I cannot even repeat them.

As a child in that system I was taught anti-Semitism and anti-Zionism in their purest forms. Stories of sadistic Israeli domination and crushed Arab innocence, stories much like blood-libel or excerpts from The Protocols of the Elders of Zion were exchanged between average kids in average school yards all the time. All lies.

The indoctrination happens in homes, it happens on television, it happens in recess, it happens from friends’ parents, and it happens in art class. It happens from complete strangers, like the man who stopped me while I was walking with a can of Pepsi in hand to tell me that Pepsi was short for “Pay Every Penny Save Israel”, and he was dead serious.3

I am sorry to bring this fact to your consciousness, but it’s what happened. And it is what still happens.


I’m telling you this because I know that for those who never lived in a world like that, it is impossible to imagine what it’s really like. Even as I tell you now, you still don’t know. How could it be real. How could children be taught to hate before they learn to add and subtract, to imagine killing the enemy and shedding their blood, to imagine the glory of blowing themselves up to strike fear and terror in their hearts, to delight in their suffering.

That was my world, that was the world for millions of children who are now adults, and that is the world for millions of children today. That is what happens there. They teach children to hate Jews. I’m not here to tell you to do something about it, I don’t know what is to be done about it. But you have to know, and I had to tell you.

  1. We “took” art. Not learned it, or practiced it. We took it like medicine. ↩︎

  2. Since the revolution and coup d’état of 2011 and 2013, I’m sure all those books have changed although I doubt they are any more truthful. Instead of lies about Mubarak’s Egypt and Nasser’s revolution, there will be lies about Sisi’s Egypt and the 2011 revolution. ↩︎

  3. To be clear, my parents did not believe any of this, but we all had to be careful about what we said. As expatriates belonging to a religious minority, we were second class citizens in that country and always lived in fear that the wrong word spoken to the wrong person would send us packing. ↩︎

scrobble 0.1

My beloved Cambridge Audio CD transport

Picture this.1

It’s been a long day. A long week even? You’ve earned your government salary and then some. It’s you time.

You head to your favorite chair and you think “ah what I would love right now is to pick a CD from this shelf here, slip that baby out of its jewel case2, pop it into this player, put my feet up and listen to the album from start to finish.”

Suddenly, though, an irk. A perturbance in the vibe. Something isn’t right. Then you see it. Your CD player won’t scrobble your album to Last.fm. There will exist no record of this experience, no entry in a NoSQL key-value database maintained by a hardly-changing music tracker owned by a massive media conglomerate to be used to train machine learning models that in the end won’t work all that well.3

Is this you? Because this is me. This was me. And to de-irk myself, I did what any person who is large and in charge would do. I created a Python library.


OK we’ve had our fun. Let’s get serious for a second.

This is for a niche audience, there’s no denying it. The problem I wanted to solve is I’m one of those people who are very attached to their Last.fm profiles, one of those “scrobbled or it didn’t happen” crowd, but I also like to collect CDs and listen to them. Yes you can rip the CD and play the files in a player that will scrobble to Last.fm, and that’s what I do a lot of the time. But I love the ritual of taking a CD out of its case and putting it into a dedicated player, and flipping through the booklet/liner notes while I listen to it from start to finish.

The problem is CD players are dumb machines (meant in the most flattering way possible) that do not have network interfaces or fancy firmwares (again, that’s a good thing). They won’t scrobble. They won’t even know what album or tracks they’re playing unless the CD has CD-Text (rare).

I’m not the first person to try and solve this problem. Daniel Puscher built CodeScrobble (GitHub link), a clever web app that uses the camera to scan the barcode on your CD or vinyl case, matches that to an album, and scrobbles the tracks (with correctly backdated timestamps) to your Last.fm account. There also exists OpenScrobbler created by Enrico Lamperti, which lets you search for and scrobble releases manually.

Unfortunately neither worked well enough for me. With CodeScrobble I had too many instances where the camera would freeze, frames would lag, or it would just not read the barcode. OpenScrobbler worked a little better, but searching manually was tedious, and sometimes search results are messy or miss the specific release I’m looking for.


pip install scrobble

So for fun, I built scrobble, a small Python library that takes a barcode text, looks up the release on MusicBrainz, and scrobbles your tracks to your Last.fm account. Since barcodes can match more than one release with different tracklists, scrobble will ask you to choose a release if that happens (unless you tell it not to). You can also back (or future) date your scrobbles if you are logging an album that you listened to earlier in the day, or just started listening to now.

~  scrobble --help                                                           

 Usage: scrobble [OPTIONS] BARCODE [PLAYBACKEND]

╭─ Arguments ────────────────────────────────────────────────────────────────────────╮
│ *    barcode          TEXT           Barcode of the CD you want to scrobble.       │
│                                      Double album releases are supported.          │
│                                      [default: None]                               │
│                                      [required]                                    │
│      playbackend      [PLAYBACKEND]  When did you finish listening? e.g., 'now' or │
│                                      '1 hour ago'.                                 │
│                                      [default: now]                                │
╰────────────────────────────────────────────────────────────────────────────────────╯
╭─ Options ──────────────────────────────────────────────────────────────────────────╮
│ --dryrun                --no-dryrun       --dryrun will print a list of tracks     │
│                                           without scrobbling to Last.fm            │
│                                           [default: no-dryrun]                     │
│ --verbose               --no-verbose      --verbose will print a bunch of stuff to │
│                                           your terminal.                           │
│                                           [default: no-verbose]                    │
│ --notify                --no-notify       --notify will send a push notification   │
│                                           via Pushover with CD information.        │
│                                           [default: no-notify]                     │
│ --choice                --no-choice       --choice will give you a list of options │
│                                           of more than one CD is matched.          │
│                                           Otherwise, the app will go with the      │
│                                           first match.                             │
│                                           [default: choice]                        │
│ --install-completion                      Install completion for the current       │
│                                           shell.                                   │
│ --show-completion                         Show completion for the current shell,   │
│                                           to copy it or customize the              │
│                                           installation.                            │
│ --help                                    Show this message and exit.              │
╰────────────────────────────────────────────────────────────────────────────────────╯
~ scrobble --no-choice --verbose 093624966524
  
💿 The Dead Weather - Sea of Cowards (2010)
🎵 1 Blue Blood Blues
🎵 2 Hustle and Cuss
🎵 3 The Difference Between Us
🎵 4 I’m Mad
🎵 5 Die by the Drop
🎵 6 I Can’t Hear You
🎵 7 Gasoline
🎵 8 No Horse
🎵 9 Looking at the Invisible Man
🎵 10 Jawbreaker
🎵 11 Old Mary

See the GitHub README for more information. Contributions welcome (I want to support other sources besides MusicBrainz, like Discogs, Bandcamp, etc.) Drop me a line if you have thoughts.

A note on usability

Now look here. I am happier in a terminal than a pig in a nice yard.4 But even I don’t want to have to fire up a shell as I sit in a chair listening to music. So, the way I actually use this tool is using an iOS shortcut that will either try to OCR an image of the barcode or prompt me to enter it manually and then ssh into a host and run scrobble with the barcode as an argument. It… works?

What’s missing is a proper barcode scanner. The OCR piece helps but it always requires manual correction. Problem is I can’t find a barcode scanner with a Shortcuts component on the App Store that doesn’t sketch the hell out of me. Do you know of one? You know what to do.

🖤

  1. If you read that and started hearing the lyrics to “Nobody Speak” by DJ Shadow, I see you. ↩︎

  2. Or slip case if that’s the kind of day you’re having. ↩︎

  3. Until we call them AI and they kill us all. A STORY FOR ANOTHER TIME PERHAPS. ↩︎

  4. Because pigs are very clean animals. ↩︎

The Kindle Should be Better

This is a draft that I wrote years ago and never published (for reasons). I thought the moment passed, but you know what, new information has come to light. So I will publish the original draft with no or minor modifications, and then add some followup thoughts at the bottom.

Summary: In the year 2023 there is no good reason to buy a Kindle. Buy something else. If the reason you feel stuck with Kindles is the collection of books you bought from Amazon for years, a) that sucks, and b) you can do something about it.


Original draft

Years ago, I wrote

Anything could be better, but some things should be better.

The Kindle has been mediocre for a long time. At some point, we crossed the line from “fine” to “please fix this, it shouldn’t be this way”.

I own a second-generation Kindle Oasis. It’s not the newest one, but it’s an otherwise top of the line Kindle. This fortifies me in my arguments below since if I have the following complaints about the top model, imagine the experience with the middle and bottom ones.

The best Kindle is overpriced

Present-day me interjection: The iPad Mini isn’t that affordable anymore. I still think the Kindle is overpriced.

This complaint is exclusive to the Kindle Oasis, which is $269.99.1 For $60 more, you can buy an iPad that has four times more storage (32 GB to the Oasis’ 8), a larger screen that’s amazing, a whole OS that lets you do close to whatever you want including read books, and everything else that makes the iPad effectively 100% of the tablet market.

The Kindle and iPad are devices that serve different needs, I get that. The iPad is significantly heavier than the Oasis, and the iPad’s screen doesn’t provide one of the primary reasons to use an e-reader: E ink screens that are easier on the eyes. But is that a reasonable price difference? Has the Kindle Oasis earned that price tag? My argument here is that it hasn’t, because the Kindle has hardly changed or improved for years.

It’s too slow

I don’t read as much as I want to. I know I’m not a power user of the Kindle and I don’t think I push it to its limits in terms of storage or usage nearly as much as other readers do. Why does it take 2.5 to 3 seconds to go from power button push to text on screen?

Present-day me interjection: this was one of the complaints I felt funny having because who was I to know that I _could be faster? Well, now I know._

The Kindle’s UI is sluggish. This was understandable years ago, E ink was a new technology that not many customers had prior experience with, and since it wasn’t a tablet you were willing to sacrifice some performance to get good battery life. I could be wrong, but I don’t think we have to make that tradeoff anymore. Pages should turn faster. Settings pages should refresh faster.

The ergonomics are terrible

For a light device, the naked Oasis is impossible to hold with something less than a kung-fu grip without it slipping. For a while I used the Kindle in a case until I realized that I didn’t use the case to protect it – it did not… – I used it because it made holding it easier.

At some point I ditched the case and covered the contact points with gaffer tape which improved the experience at the cost of aesthetics.

I don’t think I have abnormal hands. Is this a failure of testing during development? Did they not notice this? Did they notice and not care?

Library, highlights, and notes management is archaic

The library view on a Kindle is one big bag of all the books you’ve added or read sorted in descending order of date.2 You can create collections and add books to those collections, but that painful process is made even more painful by the sluggish UI I mentioned before. In addition to being one of the largest vendors of books in the world, Amazon also owns Goodreads, which is like saying “I fear my ocean of data is not enough, pray pour another ocean of data on top of it lest I expire from thirst.” None of this has trickled down to a better library management experience for the Kindle.

For many years, the Kindle saved every highlight and note to a plain text file on the device called “My clippings.txt”. I’ve always thought that was a great thing, and even played around with building my own highlight review system on top of the plain text file. But it’s 2023 and the markup is still plain text. Any text formatting in the form of bold, italics, or hyperlinking is lost. Could they consider migrating to a Markdown format? Or a side rich text file?

Typing notes on the Kindle Oasis is brutal. As far as I can tell there is no predictive intelligence to the keyboard at all. The lag and friction are way too much. I end up writing stinted comments that lack nuance, and that’s not good.

Reading anything other than an ebook is a bad experience

I want a fast, clean, Instapaper-like experience of reading online articles on the Kindle. Send to Kindle feels like a service its owners forgot was still live. You need to use Chrome or Firefox to install their plugin.3 Or you can install Mac or PC applications, and if you’re going to require desktop operating systems in the decade we’re in, take inventory of the situation.

Instapaper has a “Send to Kindle” bookmarklet they generously make available, but 1) you have to use Instapaper, and last I checked 2) you have to disable “Prevent cross-site tracking” in Safari for it to work.

I’ll give them a bit of a pass when it comes to PDFs, but the zoom and scrolling experience is a non-starter.


People don’t complain about things like this unless they care, and I care. I like the Kindle and I want it, desperately, to be the device it ought to be.

When something is not the way you want it to be, there will be reasons and it behooves you to think about them. Why isn’t the Kindle better?

I’m afraid it’s for the same reasons Goodreads has been sad and stuck for years. The Kindle doesn’t exist for the beauty of reading, it exists to sell books. If you’re the only game in town, are you going to sell more books if you keep pushing the hardware’s performance and user experience envelope, if you push its performance to make it instantaneous, if you remember that the Kindle has an OS and oh maybe we should, like, make it better and add some features and stuff?

I think the people who run Kindle think the answer is No, but I believe it’s short-sighted. No experiment or A/B test will tell you how many more Kindles you might sell if you revolutionize it just like no user survey could have told Apple how much the money machine would go brrrrr for the iPhone if they made it.

Having written all of this, I’ve convinced myself that the Kindle is Blackberry of ereaders. It’s still the default device to get, but not even its customers realize what they would do to have the iPhone of E ink reading.

I just learned that Kobo works with Libby, and Readwise has Kobo highlight sync integration in beta. Given that, the probability that I will buy another Kindle is practically zero.


Back to the present day

My thoughts haven’t changed much. The reason I decided to publish this draft is that I just got a Kobo Libra 2, and I am a big fan. Turns our I was correct, you can make an E ink based reader more responsive.

Even today, the Oasis costs $60 more than a Libra 2. Why? There is not a single reason about the device itself that justifies this. The only reason I can think of is that Amazon knows people are locked into the library and the ebooks they’ve already bought, and because the books have DRM, you need a Kindle to read them.

All I’ll say here is if you’re somewhat tech-savvy, I highly recommend you research how to download and strip your Amazon ebooks of their DRM and never look back.

It gets better

If you want to get even fancier, look into KOReader. It’s a little hacky to set up, but it gives you a lot more control over your reading view, device settings and functions, and even lets you send books to your device wirelessly straight from Calibre.

Even more proof that E ink devices that suck can’t blame the hardware. They suck because no one’s making them not suck.

See also

  • [How Kindle Promotes Bad Book-Reading Hygiene Big Think](http://bigthink.com/harpys-review/how-kindle-promotes-bad-book-reading-hygiene “Article on things we lose in connection to books when we use an e-reader.”)
  1. (This is still the price) That’s the “Without Ads” version. The “Ad-supported” isn’t proper for comparison and is just… can we stop putting ads in every fucking thing? ↩︎

  2. I wrote this draft in December of 2021. I thought I might have to chuck it but I realized that everything here still stands with one addition: at some point the Kindle OS was updated to show a more organized library view than a simple list of books. It looks better than before, but also half the page above the fold is dedicated to Amazon pushing other books to you. ↩︎

  3. I do not use those browsers and therefore haven’t confirmed if this works. ↩︎

Under new management

Blue Angels, Seattle, WA

It’s hard to avoid going stale. A person, a business, a store, a band, a job, a friendship, a relationship. They can all go stale. They want to go stale. They naturally tend towards staleness if you don’t keep steering and pushing and lifting and refreshing.

stale

stale - adjective
1: tasteless or unpalatable from age
stale bread
2: tedious from familiarity
a stale routine
3: impaired in legal force or effect by reason of being allowed to rest without timely use, action, or demand
a stale affidavit
a stale debt
4: impaired in vigor or effectiveness

Can you believe “stalely” is a word?

Staleness is one of those spiraling states that promote their own entrenchment. Once a thing starts going stale, it’s that less attractive to you, you’re less likely to touch it, eat it, deepen it, grow it, have fun with it. The less you do any of that, the more stale it goes. And so it goes.

Also known as being in a rut.


Fresh starts are so tempting. Drastic action feels so much easier doesn’t it? When you’re stale for so long you feel numb, and drastic action feels like something. Gradual repair is such a drag in comparison. Can you even tell anything is happening? It feels like nothing.

Fresh starts help you save face, kinda. I think they’re often about you saying to the world, to yourself, “hey, I’m aware of what has been happening. I take it seriously. Don’t you worry about whether I’m aware, I am. Here I’ll prove it to you.”


There’s no new redesign, no grand breaks with the past or promises about the future here. Frankly it’s just that I’ve thought of the expression “under new management” on an almost daily basis for weeks and I wanted to write something about it.

And since we’re here, I’ll tell you that I’m aware that ever since I put on my big boy pants and started my first full time job at $COMPANY in early 2017, my creative output has gone to shit. Writing, personal projects, photography, all those links on the chain are rusty as heck, sabbatical having come and gone. As you can see. But there are no new year’s resolutions or grand declarations here. Any action taken will be gradual, and in fact may not feel like much of anything.


One last something. Think of anything you’ve liked for a long time. Maybe it’s a shop that’s been a little bla, maybe what used to never miss the mark at the restaurant has been hit or miss lately. One day you show up and you see a sign: under new management. How do you feel? I’ll tell you how I felt when the exact same thing happened to my favorite restaurant in Seattle that had been slipping for a while: I did not feel good. I don’t think I’ve ever felt excited about new management.

You almost never want new management, you usually just want the old management to regroup and tighten shit up. I think if you’re excited about new management, you want a different thing altogether.

Shortcut: Dispatch Bulletin

Dispatch Bulletin notification

I’m late to the Shortcuts game.1 I’ve seen others build impressive shortcuts for years but for some reason it took me until late last year to start building some of my own. And really it was my loss, because Shortcuts have long stopped being just a gimmick. They are not a serious tool to mix point-and-click with code to build some serious workflows.

This post is about an iOS Shortcut that I use to publish short posts and photos to my Micro.blog site Bulletin. I call it ‘Dispatch Bulletin’.

Context

Bulletin is built using Jekyll, meaning it’s a static website that needs to be generated each time I make a change, and the generated pages need to be hosted somewhere. Bulletin’s source code lives in a privare GitHub repo, and the site is hosted on Netlify.2

You tell Netlify to watch your site’s repo, and it will automatically build and serve your site whenever it sees a new commit in the branch of choice. It’s really cool.

Dependencies

The Shortcut relies on two third-party apps:

  • Scriptable. To run a script that identifies the local timezone and returns its TZ database name.3
  • Working Copy. Critical since that’s how the shortcut can add posts and photos to the site’s git repo.

Summary

Brief overview of how it all works. The shortcut…

  1. Receives text input.
  2. Detects the local timezone to use later in the post frontmatter.
  3. Prompts the user for file suffix. i.e., what to add after the date in the post filename.
  4. Pulls the latest updates to the repo in Working Copy.
  5. Asks the user if they want to attach a photo.
  6. If Yes, prompts the user to select the photo from Files.app and generated the body of a photo post. If No, generates a text only post.
  7. Writes the post and optional image to the repo in Working Copy.
  8. Commits changes and pushes to remote.
  9. Displays a notification that the bulletin was published.

Details

Let’s get into it.

step explanation
Step 1: Receive text input Accept text input, save value in Bulletin variable. We’ll use that later.
Step 2: Format current date and time Get current date and time in ISO 8601 format. We’ll use that later.
Step 3: Run TZIdentifier Scriptable script Run TZIdentifier script. See below for the simple code. We’ll refer to its output later.
Step 4: Ask for file suffix Prompt for the file suffix and create a text variable of the full file path. Okay you get it, I won’t keep saying “we’ll use that later”.
Step 5: Pull latest commits Pull any new commits to the local repo in Working Copy.
Step 6: Ask if you want to attach a photo Ask if I want to attach a photo to the bulletin.
Step 7: If Yes Self explanatory.
Step 8: Browse for photo then save it and stage it. Browse Files.app for the photo, save the photo to the proper path in Working Copy and stage it for commit.
Step 9: Ask for alt text Ask for image alt text.
Step 10: Create body of photo post Create the body of the photo bulletin and save it to BulletinBody variable.
Step 11: If you don't want to wattach a photo If you don’t want to attach a photo…
Step 12: Create text-only bulletin Create text only bulletin and save it to BulletinBody variable, then end the if statement.
Step 13: Save post file to repo Save BulletinBody to a Markdown file in Working Copy and stage it for commit.
Step 14: Commit and sign Commit all staged files with a simple commit message and sign the commit with the key Working Copy already has.
Step 15: Push to remote Push changes to remote.
Step 16: Show a fancy notification at the end Show a fancy notification at the end.

At this point my work is done and Netlify takes the wheel. Once the shortcut is done pushing the commit, Netlify will detect the commit to the main branch, pull it, build it, then serve it.

Appendix

Here’s the TZIdentifier script run in Scriptable:

const tz = Intl.DateTimeFormat().resolvedOptions().timeZone

function main() {
  Script.setShortcutOutput(tz)  
}

main()

Final notes

I had a lot of fun building this. It holds the highest honor an automation can receive: I use it all the time. It’s the most convenient way to publish to a Jekyll site I’ve ever worked out and it’s how I publish 9/10 posts to Bulletin.

  1. A note on capitalization. “Shortcuts” is a noun but it’s also the name of the app itself. Classic Apple. My executive editorial decision is to capitalize ’Shortcuts’ and not ’shortcut’. ↩︎

  2. From what I can tell Netlify is one of the most common ways to host a Jekyll site online besides Github Pages. It’s a really pleasant experience to set up, and they have a free tier that will work for most hobbyists like me. I have nothing but good vibes for the company. ↩︎

  3. I need to run a script for this because I need the timezone database name for… uh… Jekyll reasons, and as far as I can tell there’s no other way to get that on iOS. ↩︎

Brief automations: Playing a random album from Plex

Another installment in my series on listening to music is forthcoming and it will mostly be a love letter disguised as a 1000+ word use guide for Plexamp. In the meantime, this is a slightly out of context taste.

My music library lives under the watchful eye of a Plex server. The server pulls down a lot of metadata that makes artist and album pages much more delightful to browse than iTunes/Music.app ever did. I then use Plexamp to listen to music both at and away from home. I really love this setup.

Besides the official Plex and Plexamp clients, there is an unofficial Python Plex library whose goal is to “match all capabilities of the official Plex Web Client”. I’ve been playing with it for a while, it is impressive, and this is the first of many uses I have in mind for it.

Play a random album the simple way

I keep all the albums I own as CDs in a Collection called ‘💿’.1 Sometimes I want to put on some music but I don’t have a specific album in mind and end up scrolling the collection aimlessly.

A common way to deal with this kind of choice paralysis is to let a random picker choose for you. This is very easily done using the Python library.

Here is a simple implementation.

import random
from plexapi.server import PlexServer

BASEURL = 'http://192.168.1.123:32400'
TOKEN = '<insert your own token here>'
plex = PlexServer(BASEURL, TOKEN)

owned = plex.library.section('Music').searchAlbums(collection='💿')
plex.client('KEF').playMedia(random.choice(owned))

Requirements:

  • pip install plexapi
  • Know the local or remote IP address of your server.
  • Your X-Plex-Token for authentication. Here’s how to find it.
  • Know the name of the client that will play the album. This is cool because it doesn’t have to be on the same machine running the script! In my case I have a machine running Plexamp connected to my KEF speakers.

Steps:

  • Import the random library and the PlexServer class.
  • Create an instance of your server with the correct url and token.
  • Get a list of the albums in the ‘💿’ collection.
  • Tell the client to play a random album from that collection.

So that’s amazing and simple. Here’s how you can make it amazing and a little fancy.

Playing a random album the glamorous way

A random choice from a large collection can be jarring. Too decisive, you know? One compromise you can make between that and choosing from the whole collection yourself is to get random to offer you two – or more – candidates to choose from.

The implementation is a bit involved but still pretty straightforward.

#! /usr/local/bin/python3

import random
import requests
import subprocess

from PIL import Image
from io import BytesIO
from plexapi.server import PlexServer
from rich.console import Console

CONSOLE = Console(color_system='truecolor')
BASEURL = 'http://192.168.1.123:32400'
TOKEN = '<insert your own token here>'

plex = PlexServer(BASEURL, TOKEN)
owned = plex.library.section('Music').searchAlbums(collection='💿')

albums = {
    album.title: album
    for album in random.sample(owned, 2)
}

albumart = [
    Image.open(BytesIO(requests.get(candidate.thumbUrl).content)).resize((500, 500), Image.LANCZOS)
    for candidate in albums.values()
]

merged = Image.new('RGBA', (1020, 500))
for ii, art in enumerate(albumart):
    padding = 0 if ii == 0 else 20
    merged.paste(art, (ii*500 + padding, 0))
merged.save('/var/tmp/amp.png')


CONSOLE.print('\n')
subprocess.run(['/Applications/kitty.app/Contents/MacOS/kitty', 'icat', '/var/tmp/amp.png'])

CONSOLE.print('\nPick an album')
choices = ' '.join(['"'+albumtitle+'"' for albumtitle in albums.keys()])
choice = (
    subprocess.check_output(
        f"/usr/local/bin/gum choose {choices} --cursor '' --selected.foreground='#7851a9'",
        shell=True,
        encoding='UTF-8')
    .rstrip()
)

plex.client('KEF').playMedia(albums[choice])

CONSOLE.print(
    f'\n  [italic]Now Playing: '
    f'[#9966cc]{albums[choice].title} ({albums[choice].year})[/#9966cc] '
    f'by [#47c1ff]{albums[choice].artist().title}'
    '\n',
    justify='center'
)

Requirements: I wanted to make this aesthetically pleasing, so there are some splurgy requirements.

Steps:

  • Same imports from the simple implementation. Then a few imports for image processing, reading the album art from the server, and pretty printing in the console.
  • Get a list of the albums in the ‘💿’ collection.
  • Get the titles for two random albums, then get the album art for those two albums.
  • Merge the album art into one image. I have to do this because as far as I can tell kitty won’t let me display two images next to each other in the terminal.
  • Tell kitty to display the merged image.
  • Use gum to ask the user which album to play.
  • Tell the client to play the chosen album.
  • Display a “Now Playing” status with the album title, release year, and artist.

Other notes:

  • The characters appearing as in the code are music glyphs in the PragmataPro font I use in kitty. You can see it in the video.
  • Resizing the album art to 500×500 is hardcoded across the script. You could set that as a variable to change later. Also some album art isn’t perfectly square and will look silly when resized to a square. I might deal with that later.
  • It’s more idiomatic to use tempfile to get a path to a temporary filename. You can read more about the tradeoffs here.
  • I wrote this to make it easy to accept the number of albums as an argument instead of that also being hardcoded in the script.
  • You could build this out to also ask the user which collection they want to pick from. I plan to do just that.

And that’s it. Kinda swanky.

  1. This also has music I bought from Bandcamp, Qobuz, Boomkat, etc. But it’s mostly CDs. ↩︎

Programming the Atreus Keyboard

Happy New Year.

Okay here’s a thing. About a year ago I bought a Keyboardio Atreus keyboard.1

I don’t remember what made me think that was a good idea, or how I even found out about it. I did it for my Mental Health? Some people buy a Rolex, others buy a keyboard. Let’s move on.

What’s all this then?

Keyboardio Atreus. Bright keycaps are Massdrop x MiTo XDA Canvas custom keycap set. Black keycaps came on the Atreus.

It’s a tiny little thing.

The Atreus has a learning curve, no question. Space is a key not a bar. Arrow keys, number keys, special characters, function keys, and media controls exist in Layers 1 and 2.2 This means you use Fun E / D / S / F for ↑, ↓, ←, and →. A numpad exists on the right side with its corners defined as Fun M for 1 and Fun O for 9. All special characters are accessed using Fun other keys. To access media controls, function keys, and other lesser used buttons like Page Up and Page Down keys, you need to activate Layer 2 by pressing Fun Esc then releasing them, which locks the keyboard in Layer 2 until you Esc out of it. So if you want to turn up the volume, you press Fun Esc and let go, press X for volume up, then press Esc to go back to the base layer.

It’s a lot, I know. I want to tell you it takes a week or two to get used to it, but the truth is it takes about 3-4 weeks for proficiency in typing prose and maybe double that for proficiency in writing code. That said, I love this thing so much that it activated the principle of “two is one and one is none” and I got myself a second board as a backup.

Programmability

One of the big selling points of the Atreus is that it runs on open source firmware called Kaleidoscope.

Keyboard.io created a GUI interface around Kaleidoscope called Chrysalis which you can use to remap and modify some things on your keyboard without messing around with code, compilations, flashing, etc. The downside of this option is that Chrysalis doesn’t support all of Kaleidoscope’s features (yet–including the one that matters the most to me: Macros).

You might opt for modifying and building Kaleidoscope directly for any of the following reasons: You want to use a feature not exposed in Chrysalis. You want to define a complicated setup in code so you avoid forgetting what you implemented and how you did it, and so you can commit it to a dotfiles repo or something similar. Or maybe you just have a preference for code and live a terminal > GUI lifestyle.

If you’ve ever messed around with an Arduino board, it’s the same idea: you write a script encoding the behavior you want, compile it, then install it to the keyboard. In fact, Arduino software is required if you want to build and install Kaleidoscope yourself.

Quick note on why this matters: some programs will let you create macros or remap your keys. Keyboard Maestro is one example and it can do (probably?) everything Kaleidoscope can. The benefit of programming your keyboard’s firmware is that you can plug that keyboard into anything and have the same behavior come with it. Set it up once, have it everywhere. This is especially useful if you use external keyboard with an iPad like I do, where a utility like Keyboard Maestro cannot exist.

Code

I’ll use the first macro I created in Kaleidoscope as an example.

On a Mac, the common keyboard shortcut to move to the tab to the left or right of the current one is Shift Command [ and Shift Command ] respectively.3 On a normal keyboard this chord is simple enough to type, but on an Atreus, it’s a 4-key contortion: Shift Command Fun Z for previous tab and Shift Command Fun X for next tab.

Here’s how you can modify the Atreus firmware to add a simpler macro – Fun H and Fun ; – that translates to the Mac keyboard shortcuts for switching tabs.

1) Start with the base firmware file.

2) Define the macros in macro_t constant. In this case I named them LEFT_TAB and RIGHT_TAB. This defines which keys are pressed when the macros are triggered.

const macro_t *macroAction(uint8_t macro_id, KeyEvent &event) {
  if (keyToggledOn(event.state)) {
    switch (macro_id) {
    case MACRO_QWERTY:
      Layer.move(QWERTY);
      break;
    case MACRO_VERSION_INFO:
      Macros.type(PSTR("Keyboardio Atreus - Kaleidoscope "));
      Macros.type(PSTR(BUILD_INFORMATION));
      break;
    case LEFT_TAB:
      return MACRO(D(LeftShift), D(LeftGui), D(LeftBracket));
      break;
    case RIGHT_TAB:
      return MACRO(D(LeftShift), D(LeftGui), D(RightBracket));
      break;
    default:
      break;
    }
  }
  return MACRO_NONE;
}

3) Add the macros to the H and ; keys on the second layer (which is triggered by holding the FUN key). Look for M(LEFT_TAB) and M(RIGHT_TAB).

KEYMAPS(
...
  [FUN] = KEYMAP_STACKED
  (
       Key_Exclamation ,Key_At           ,Key_UpArrow   ,Key_Dollar           ,Key_Percent
      ,Key_LeftParen   ,Key_LeftArrow    ,Key_DownArrow ,Key_RightArrow       ,Key_RightParen
      ,Key_LeftBracket ,Key_RightBracket ,Key_Hash      ,Key_LeftCurlyBracket ,Key_RightCurlyBracket ,Key_Caret
      ,TG(UPPER)       ,Key_Insert       ,Key_LeftGui   ,Key_LeftShift        ,Key_Delete         ,Key_LeftControl

                   ,Key_PageUp   ,Key_7 ,Key_8      ,Key_9 ,Key_Backspace
                   ,M(LEFT_TAB)  ,Key_4 ,Key_5      ,Key_6 ,M(RIGHT_TAB)
      ,Key_And     ,Key_Star     ,Key_1 ,Key_2      ,Key_3 ,Key_Plus
      ,Key_LeftAlt ,Key_Space    ,___   ,Key_Period ,Key_0 ,Key_Equals
   ),
...

4) Finally, add the macros to the enum declared towards the beginning of the file.

enum {
  MACRO_QWERTY,
  MACRO_VERSION_INFO,
  LEFT_TAB,
  RIGHT_TAB
};

5) Follow the development guide I linked above to compile and flash this new firmware, and you’ve built your first Atreus macro

See also

  1. I’m glad you asked. Kailh BOX White switches. ↩︎

  2. What you see in the photo is Layer 0, the base layer. Because of course the layers are 0-indexed. ↩︎

  3. I just learned that Control Tab and Shift Control Tab also works. ↩︎

Listening to music: Last.fm

This is part 2 of a series on listening to music. Other parts:

  1. Beginnings
  2. Last.fm

My Last.fm account knows about every song I’ve listened to since 2008.

~6,500 artists, ~7,700 albums, ~170,000 tracks.

When I listened to the MP3 files I brought with me from high school, I made sure Last.fm was scrobbling. When I listened to the mix CDs my best friend made me, Last.fm listened too. When I tried Spotify for a few months, when I switched to Hype Machine, when I listen to the CDs I own or the albums I bought from Bandcamp, everything I’ve played in iTunes, on my iPods, on Music.app on iOS, on Marvis, during my short-lived Roon experiment, through Plexamp. It’s all there.

My life.

Last.fm remembers every day of the last fourteen years of my life.


Last.fm is an anomaly, a mutation of the internet, and a service that just celebrated its 20th birthday.

Now you could say so what, right? Myspace is still alive and its website still technically loads. Google is like, 100 years old or something. So what.

Sure. First of all Myspace launched in 2003, so it’s technically 19 years old. That’s right: Last.fm is older than Myspace! And Google is…well who gives a shit about Google. Do you feel anything when I say “Google”? No you don’t.

Also thing is, Myspace doesn’t exist the way Last.fm still exists. I don’t know what Myspace is today, it looks like it’s a social network for singers and actors? No one who uses it today sits and thinks about where it was and how it got to where it is now.

Last.fm had a lot of ups and downs. In its heyday it had a technical blog and a staff that posted photos of server rooms and office space, it had actual streaming radio that did intelligent things with music it knew you liked, they put random slogans at the bottom of site pages. It was scrappy and ambitious. They figured out how to create a plugin that automatically scrobbled what you listened to in iTunes, but that also figured out how to scrobble what you listened to on your iPod.1 As far as I know, that’s the only useful thing any third party app did with what happened on your iPod.

Everything was fine. No everything was great! Then came a redesign or two, the streaming died and got replaced with a hacky “we’ll just play YouTube videos and pretend you’re streaming” setup, Groups which was actually fun died, CBS bought the company (that was the moment I thought it was all over), and then a long silence during which it felt like every other month a piece of the site would disappear. They used to let you export your data, that went away. https://status.last.fm which was a proper service status page now redirects to a Twitter account. It all felt a little grim. Okay a lot grim.

But. Throughout all of this, the ups, downs, happy days and sad, not being owned by a large media conglomerate and being owned by a large media conglomerate, the site never stopped accepting scrobbles.2

And in a world that has turned its back on, then mooned the interconnectivity of Web 2.0, in a world where APIs get turned off and rarely on, Last.fm still commands death-defying loyalty with music listeners who demand scrobbling of old and new music apps and streaming services.

There are signs of life. The listening reports feature gets improvements every once in a while. Library search is here.3 Their Twitter account is fairly active. The site isn’t…you know, dead. Every time I update that plot I wonder how many more years of data I’ll be able to add to it. Who knows. It’s the internet, and nothing is forever, even if some things feel like they are.

Appendix: Listenbrainz

I mentioned in passing that Last.fm used to let you export your data including all scrobbles and loved tracks, and that this disappeared at some point. So how did I make the plot at the top of this post?

Listenbrainz is part of the MetaBrainz Foundation.4 Its goal is to be a “public [and] permanent” store of your listen history, to make this data available for download, and share this in some technically knowable and supported fashion.

Here is how I think of Listenbrainz: Yes we all can hardly believe that Last.fm still stands, but the clock is ticking. You know that right? Don’t you want a backup plan? Don’t you want a way to get your money outta that bank before it craters? That’s Listenbrainz.

The good. Listenbrainz will let you import all your listens from Last.fm, and will let you download it in a fairly structured payload. That’s how I made the plot at the top.

The bad. This is a manual process. You have to remember to come back and do it regularly. Why? Because it seems to basically load and scrape each page of listens from your Last.fm profile. Why? Probably because Last.fm doesn’t want them doing this, and probably has no API for it. Lord knows the APIs they do have barely work. The other reason this is bad is that it might stop working.

Listenbrainz has an API. Technically music players can start letting you authenticate with Listenbrainz in addition to or instead of Last.fm and send your listens there too. As far as I know, as of today no Mac/iOS music players do this.

  1. It wasn’t black magic, but it was still technically impressive. I don’t know if they ever officially said how that worked, but I’m fairly sure it relied on play counts. Whenever you listened to music on your iPod (or iPhone) the playcounts were incremented, and when you synced your device to your computer the playcounts in your iTunes library got updated too. Last.fm would use a track’s changed playcount and last played timestamp to decide what you listened to since the last sync. One thing I can’t remember is whether the plugin could figure out if you played tracks A -> B -> A. A’s playcount would be incremented twice, and its last played timestamp would show the latest play, so as far as the plugin could guess, what you did was B -> A -> A. So if the plugin could figure out that you played A -> B -> A, the solution must be more sophisticated than I thought. ↩︎

  2. There are frequent downtimes, but the core service seems well-designed enough that it always catches up with what I played once it’s back online. ↩︎

  3. The blog post before that one is from Feb 2019. The one before that was Sep 2017. So it’s still a little sad. ↩︎

  4. The one Metaverse that actually exists. ↩︎

Archive