scrobble
is a small Python library and command line tool I wrote for:
So, yeah, it’s niche, but I use it every day!
I just added a couple of small features worth mentioning for posterity.
The first version of scrobble
required you to enter the CD barcode yourself. This was fine. I mean, I lived with it. But obviously it’s a pain in the ass to type out that long number, especially on a smartphone screen.1 I tried to hack my way around that by using the iOS Shortcuts OCR action to process an image and detect the numbers, but I always had to fix the result before submitting. Obviously the tool needed a proper barcode scanner.
This turned out to be very easy to do, I just had to use the barcode.BarcodeDetector
class from opencv-python
.
So now you can take a photo of the back of your CD case and pass the path to that image. e.g.:
scrobble cd --verbose ~/IMG_1234.jpg
If you’re thinking it seems like a pain to take a photo, save that somewhere, and pass the path to that to a command, consider that iOS Shortcuts is still the primary way at least I use this tool.
I didn’t expect this to be something I would need, but I realized it was when I listened to my copy of MF DOOM’s MM..FOOD.2
My copy of the album is a double disc release with one audio disc holding all the tracks and the other an extras DVD-Video. The MusicBrainz release represents the DVD with a single 1:04:19 track called “MM..FOOD Drive Tour”. Well, that’s nice, but I don’t want an hour long track I never listened to being scrobbled.
So scrobble
has a new --track-choice
flag that lets you choose which tracks from the release to scrobble. Right now the feature requires charmbracelet/gum
to be installed, and the experience of using it is very cool.
This feature is also useful when you can’t listen to an entire album in one sitting. I try not to let that happen, but it happens, and when it does it feels “wrong” to have the whole album scrobbled at once without gaps.
scrobble
is pretty simple and it’s built on the shoulders of people who did real work, like the authors of musicbrainzngs
, pyLast
, and the other dependencies the tool has.
Here’s another problem to solve. Believe it or not, some CDs don’t have barcodes. Sorry to tear the fabric of your reality like that, but it’s true. What do you do if you own a copy of the US release of Vol. 9 & 10 by The Desert Sessions and you look at the back and find no barcode?
It would be nice to be able to pass a url to a Discogs or a MusicBrainz page and scrobble the tracks from there. Maybe Last.fm album pages too? We’ll see.
]]>Emphasis on personal.
This was a year of good and bad extremes.
This still deserves a separate retrospective. I tried to write one, but life intervened.
The summary is: although duration was not pre-determined, my break from having a full time job ended up being a few days shy of an even year. I don’t regret taking the break, although I did it assuming the world would stay more or less the same until I came back, and it kinda didn’t. Don’t you hate it when that happens!
One must start here.
I am one of the luckiest people on the planet. I really am. Chances are you are too, maybe a bit more lucky or a bit less, but we’d be haggling over third and fourth places in a race of hundreds.
I live in the United States. I am a permanent resident in this country, and a citizen of another safe country. I have undergraduate and graduate degrees. I have a job right now. I have health insurance. I’ve worked in tech for a number of years and I’ve done well financially. Last year I took a break from full time work and spent a good number of months doing not-work. I’ve been able to afford the time and money to engage in most or all of my hobbies. Any chronic physical or mental health issues I might have at the moment are either minor or manageable. I have shelter. I’m not worried about food or water. I do not live in a war zone. I have not lost my home to a fire, a flood, a tornado, or other natural disaster. I have not lost a loved one to an illness or an untimely or violent death.
“Gratitude” is having a moment right now, and so there’s a way this could come across as canned, but it’s not. It’s real. I feel it in my bones.
And I think something that heightened that feeling was another feeling, one of impending doom I felt in a way I hadn’t before. Like this is a moment of twilight. There’s nothing like fear of loss or a looming threat to make you appreciate what you have.
The first three months of the year are lost to me. They were not just the worst of the year but maybe of the last decade of my life. Things got truly existential and I still feel the effects of that to this day. Believe me I don’t like being cryptic about it, but it’s not something I’m ready to talk about in full detail just yet.
I ended my sabbatical in June of 2023. Things have been significantly better since. I started a new job at a place I’m proud to work at, re-engaged negotiations with chronic imposter syndrome, got a new bike, got hit by a car riding said bike and broke my elbow, attended a family wedding, said goodbye to a beloved pet, and handled it all pretty well I think.
I haven’t flown for about a year now. It’s not a surprise, many private pilots lapse and get rusty as soon as they pass their checkride. You need a reason to fly, otherwise it’ll be difficult to get over the resistance of booking a plane, planning a flight and checking weather, driving to the airport and preflighting, not to mention the cost, etc. I’m okay with it. I’m still a pilot, and I’ll get back to it at some point.
In an effort to expand my social circle and build an auxiliary sense of purpose, I started volunteering in a community owned bike shop near where I live and that’s been great. I like the people I’m meeting at the shop, I like working with my hands, and I like helping people fix their bikes. I signed up for a lot of responsibility pretty fast and I hope I can manage to not disappoint myself and everyone else around me.
More music and fewer podcasts. I spent a lot of time listening to new music and buying and (re)listening to CDs and old favorite albums. I like being bugged by low-stakes questions like “should I alphabetize my CDs globally or by genre”.
Podcasts are now considered harmful and my goal is to have as few of them in my life as possible. It’s a vice, really. More on this another day.
I’ve read a good number of books. Another good transfer of time away of podcasts.
I’m also glad to see that despite the year it’s been, I still managed to post a few things throughout, although I’m still struggling to write well. My prose is stiff and stilted. I attribute it to lack of practice and a very unfavorable consumption:creation ratio.
In November we visited Vienna, Carvoeiro, and Lisbon. Vienna may be my favorite city ever, and my heart still aches every time I return to try using US public transit after a visit to Europe.
Returning to Vienna got me back into Duolingo. It’s not perfect (see below) but it’s probably the most wholesomely gamified addictive app out there.
I still live here, and I still feel like I don’t belong.
Clearly this is the headline for the tech world, a world I decreasingly and reluctantly inhabit. I really don’t want to talk about this, but I can’t leave it out of a review of my year.
So what of it? There’s a million takes, many of which are more thought-through than mine while still disagreeing with each other about what is happening, what will happen, and whether it’s good or bad.
I made many attempts at writing something coherent about this topic and failed quite completely. I suppose it’s important to admit it’s related to the crisis I went through earlier in the year.
My feelings are stronger than my thoughts on this, though both are strong and both are pretty negative. I think the effect in the short and medium terms will easily be net bad, and the effect in the long term is a big wide question. I struggle to see how it goes well without a fundamental change in human nature and organized society, and how we individually and collectively respond to incentives. Meanwhile Amazon is shoving ads in every crevice it can find to wring out every last incremental cent it can get, and last week loud ads shouted at me from the pump while I filled up the tank at a Shell gas station. I’m not optimistic.
Maybe a shock is what we need, but it would have to be a bigger shock than COVID-19, and I really don’t want to live through that.
Nothing has dislocated me more from my generation, (some of) my hobbies, or my career than the breathless sprint towards using generative models for anything they can be shoe-horned into. I can’t interpret it as anything other than total willingness and eagerness to commoditize and automate everything, collateral damage be damned. That’s someone else’s problem to solve. Illustration, art, writing, reading, modes of creation and connection, the very god damn atoms of our humanity. I should’ve known better, but I’m still stunned by how many think of art as just pretty pictures, and writing as something to summarize into the shortest fastest snort of words possible. How few see the inherent value in paying an artist or a photographer well for their work. I understood we lived in capitalism, but I thought we knew where to draw lines to demarcate the commercial from the human, work from craft, financial vs psychological value. I thought we cared to know that another person drew this picture or took this photo, or that someone with feelings sang this song. But so many just want passable pixels for their blog and a bop to listen to.
I miss crypto. At least I knew it was all horseshit that was never going to work, and it soaked up all the hype-hungry fast-money hucksters into one drain and kept the rest of us somewhat isolated.
And sure, some people want to use the new tech to cure cancer or dementia, and I’m really glad I work at a place where I can try to be one of those people (and I will). But let’s be honest, all the sweatiness isn’t about that.
I can find some solace in knowing that I’m not alone in how I feel about it all, but will there be enough of us?
I can’t do the topic justice here and it’s not the point of the post. In the meantime I’ll leave a small collection of random links to stimulate the discerning reader.1
I would love to come back to this post years later and wince at making a big deal out of something that didn’t change much for the worse in the end.
Oops. This is all coming off pretty bleak and angsty I think. Sorry about that. I’ve also been having a lot of fun, I promise! I’m trying to live in the present as much as possible, and just hope that today is not yet the best day I’ll ever have.
I tried coming up with themes for next year and struck out. “Year of less”, “Year of less resistance”, “Year of attention”, “Year of presence”, “Year of focus”. None of it fits well. Like many others my attention and time-management are shot, and I want to repair that. I want to do fewer things and do them better.
]]>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.
We “took” art. Not learned it, or practiced it. We took it like medicine. ↩︎
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. ↩︎
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. ↩︎
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.
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.
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.
🖤
]]>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.
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.
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.
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.
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?
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.
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.
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.
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.
(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? ↩︎
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. ↩︎
I do not use those browsers and therefore haven’t confirmed if this works. ↩︎
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 - 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.
]]>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’.
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.
The Shortcut relies on two third-party apps:
Brief overview of how it all works. The shortcut…
Let’s get into it.
step | explanation |
---|---|
Accept text input, save value in Bulletin variable. We’ll use that later. |
|
Get current date and time in ISO 8601 format. We’ll use that later. | |
Run TZIdentifier script. See below for the simple code. We’ll refer to its output later. |
|
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”. | |
Pull any new commits to the local repo in Working Copy. | |
Ask if I want to attach a photo to the bulletin. | |
Self explanatory. | |
Browse Files.app for the photo, save the photo to the proper path in Working Copy and stage it for commit. | |
Ask for image alt text. | |
Create the body of the photo bulletin and save it to BulletinBody variable. |
|
If you don’t want to attach a photo… | |
Create text only bulletin and save it to BulletinBody variable, then end the if statement. |
|
Save BulletinBody to a Markdown file in Working Copy and stage it for commit. |
|
Commit all staged files with a simple commit message and sign the commit with the key Working Copy already has. | |
Push changes to remote. | |
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.
Here’s the TZIdentifier
script run in Scriptable:
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone
function main() {
Script.setShortcutOutput(tz)
}
main()
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.
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’. ↩︎
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. ↩︎
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. ↩︎
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.
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
Steps:
random
library and the PlexServer
class.So that’s amazing and simple. Here’s how you can make it amazing and a little fancy.
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:
Other notes:
in the code are music glyphs in the PragmataPro font I use in kitty. You can see it in the video.tempfile
to get a path to a temporary filename. You can read more about the tradeoffs here.And that’s it. Kinda swanky.
This also has music I bought from Bandcamp, Qobuz, Boomkat, etc. But it’s mostly CDs. ↩︎
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.
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.
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.
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
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.
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.
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.
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. ↩︎
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. ↩︎
The blog post before that one is from Feb 2019. The one before that was Sep 2017. So it’s still a little sad. ↩︎
The one Metaverse that actually exists. ↩︎