SinceTag is free, and its promo film is a web page

In the 30 days up to 26 October, SinceTag showed up 439 times on the Mac App Store. 22 people opened the product page. One of them bought it. That was my friend, Tjøl, as thank you for buying his build monitor app. Even though Claude said I had “the best price”, that was clearly not the case. So now the app is free, and this is a poor attempt at marketing.

A couple of years ago it became obvious that I had lost control of which side projects had unreleased work. Before AI I became stuck here and there on the projects, and after AI I moved too fast doing many things at once.

My solution was what everybody seems to be doing these days: I made my own release dashboard. SinceTag shows which repos have unreleased work, whether the builds are green, and what is left before and after the next release.

A film without a video editor

I don’t own a video editor, and I was not going to learn one for 44 seconds of footage. I made the film today together with Claude Code, and the whole thing is one HTML file in the SinceTag repo.

All the animation lives in a single function, render(t). It takes a time in seconds and puts every element where it belongs at that moment: the sticky note that says “I have 38 side projects”, the grid of commit squares flickering green, the repo cards sorting themselves, the agent typing “PhotoFlow is ready. Tag it and push it.” There are no CSS transitions and no timers. Open the page in a browser and it plays on a loop. Ask it for second 16.4 and you get exactly that frame, every time.

The dashboard in the film is drawn in HTML too, with made-up repos like PhotoFlow, Daylist and Habit Garden, so no real project of mine had to pose for the camera.

Rendering it frame by frame

A deterministic render(t) makes the rendering boring, which is what you want. A small Node script opens the page in Playwright’s headless Chromium, steps through all 1320 frames (44 seconds at 30 fps), screenshots the stage and pipes each PNG straight into ffmpeg:

for (let f = 0; f < frames; f++) {
  await page.evaluate(t => render(t), f / FPS);
  const png = await stage.screenshot({ type: 'png' });
  if (!ffmpeg.stdin.write(png)) await new Promise(r => ffmpeg.stdin.once('drain', r));
}

A render takes about six minutes. The page is square, 1080 by 1080, for social media. A --wide flag gives 1920 by 1080 for the website. And --stills 2,16,30 saves single PNGs at those seconds, which was how I checked a scene without sitting through the whole film again.

The soundtrack, take three

The first soundtrack was synthesized in Python with numpy and scipy, so there was no licence to worry about. The guitar was a Karplus-Strong plucked string, an old trick that fakes a string with a delay line and a burst of noise. It sounded like a guitar the way a ringtone sounds like a guitar. Making it brighter and quieter did not save it, and neither did swapping it for synthesized 80s synthwave.

Take three was a real track: “Folk” by Kulakovka from Pixabay, free for commercial use with no attribution needed. It runs at 80 bpm in C major, so the beats land at 0.1875 seconds plus multiples of 0.75. My cut keeps the first 30 seconds and jumps to the outro, which skips the giggling near the end of each pass through the tune.

The sound effects are still made in Python, and every one of them sits on a beat and in the song’s key. Sticky notes get a clap. Cards appearing get woodblocks climbing C, E, G and C. A green build gets a glockenspiel, and a red build gets a dull low knock. The keys clatter while the agent types its prompt. The music ducks under the effects, and ffmpeg’s loudnorm brings the whole mix to -16 LUFS. One catch: the track is registered with Content ID, so uploads to YouTube or Facebook may get a claim to clear.

The cue times in the Python script follow render(t), so a scene that moves needs its sounds moved too. That is the price of a film made in a text editor. I’ll take it over learning Final Cut.

The end card says the app was made in Trondheim by a guy with too many repos. Now it is free for 2 of them.

参与讨论