My video publishing pipeline

Tags:

GPW2026 German Perl Workshop 2026 logo

One of my yearly things is to post-process and publish the videos we (well, Lee Johnson) record of the German Perl Workshop.

The videos get recorded in OBS Studio and already get the sidebar with the sponsor information, talk and speaker information. After the workshop has concluded, these files then need to be post-processed. Most of it is automated with a large Makefile. This covers

  • Importing the talk metadata from Act
  • Setting up a cutting pipeline to trim the talks
  • Adding the metadata of speaker and talk title to each video
  • Adding a header and a trailer to each talk

My main tasks there are to create an SVG image to use as a titlecard, and to wrestle with ffmpeg so it uses hardware acceleration instead of only CPU processing.

After producing the final videos, each talk gets reviewed for obvious bad stuff, like audio cutting out or otherwise too bad quality.

The last step then is to upload the videos to Youtube, create a new playlist there and retitle all the uploaded videos with the event, speaker and title. This is mostly a manual task, since automating Youtube would mean to have to deal with another always-shifting API.

After all these manual steps, I'm still happy to present the GPW2026 videos to you.

Image of the Week

Tags:

I took this image this week while trying out the Canon 100mm macro lens, together with some H0-size figurines I ordered from Aliexpress. When doing the test pictures, I found that I'm too strict with using the f/2.8 - that leaves a far too tight area of sharpness for most subjects. Of course, having a small figurine wiping the lens strikes me as not original but still a fun idea. The colours of white/black/red/orange are also nice.

Just read: Murder by Memory by Olivia Waite

Tags:

Just read: Murder by Memory

Image of Murder by Memory

This years Hugo Awards finalists and winners have been announced, and as I'm always looking for new authors with stories worth my reading time, I ordered a random selection of them as used books. As I don't care much for the books as physical items, getting them used is fine - they will be read in the bathtub anyway.

Murder by Memory is ab interesting 100 page murder mistery novella, maybe in the "cozy" genre of crime fiction. The premise is that the ship detective of a star faring slow ship gets woken up because some backups were destroyed and somebody was murdered. They now have to find the murderer, the motive and also the mechanism.

The book is part one of a three part series. I think it was Charles Stross who wrote that it is basically impossible nowadays to sell a single book, you always have to sell them as a trilogy to a publisher. The other two books are already listed on Amazon but not yet published, or at least, not yet available as used books, so I will stay patient.

Some of the trappings of the story remind me of Altered Carbon, but the story is quite short and fast-paced, so it doesn't hurt too much. The book does not go too deep into the societal consequences of human backups. On the other hand, you would expect most people on a 1000-year voyage to be on board with backups and restoration, in contrast to the larger, and more layered society of Altered Carbon.

There are some things that don't feel sensical to me, but that may be because I think too much about society and money. The ship has a (backed up) population of 10.000 persons, but they seem to have money, huge interest rates and at least one large bank. While I understand that the conceit is to keep people exercising their skills until landfall on a new planet, I don't understand how the interest rates could work like they do in the story. As a mostly stagnant population, I wonder where the need for that much capital and loans goes into, with no economy to grow. But most likely, I'll attribute this criticism to my view of the small population.

Story wise, the book could be double the size, as it has some traces of a longer noir story that aren't followed up on. Maybe it started out as a longer story and was then trimmed back for publication instead of trying to bring it to an Altered Carbon style lobger story. There could be one or two more plot twists to stretch out the story and my instincts led me to suspect deeper machinations to the murder. But the story works as-is , and better a published story that is short than an unpublished story that does not go anywhere, or is too similar to comparable setups like Altered Carbon or Glasshouse .

Image of the Week

Tags:

I took this image last year on my way through downtown Frankfurt. The sun was shining and it was a relatively warm day for Winter. The non-traditional view of the skyline is what drew me in. I'm not entirely happy with the large free space in the foreground, but then again, the image is good enough.

Convert images for the Samsung EM32DX

Tags:

Photo of the frame displaying an image

Now that I have a shell script to send images to my Eink display, I'm looking at improving the experience. Mostly by porting the tools to Perl to better integrate them with my other tools. But also, by making it more convenient to find and select new images to display on the screen.

Shell script

I want to build a good collection of images to display on the Eink display. As I often lose track of the filename of a very good looking image ( I'm already looking at it on the display and I like it, why should I bother with the filename? ), I've added logging to my interim shell script so that I have a log of files to go through and re-evaluate.

I'm using JSONL as the log format, not because it is especially easy to create from the shell, but it is highly convenient for later processing with other tools.

#!/usr/bin/perl
use 5.020;
use JSON::Tiny 'encode_json';
use Getopt::Long (qw(:config pass_through permute)); # stop at the first unknown option or first argument
use POSIX 'strftime';

my $result = {};

my $name;

GetOptions(
    '<>' => sub( $v ) { if( ! $name ) { $name = $v =~ s/^--//r; } else { $result->{$name} = $v; undef $name }; },
);

$result->{ timestamp } //= strftime '%Y-%m-%dT%H:%M:%SZ', gmtime();

say encode_json( $result );

I've also added bas64-encoded thumbnail images to the log lines so that I can easily display the logfile as an HTML page to better pick out good images or images to discard.

Screenshot of thumbnails from the logfile

Shell integration

After adding the appropriate file in ~/.local/share/applications/eink.desktop I can send any image to the frame by using a right-click. This is highly convenient and encourages me to quickly try out new images.

The Perl port of the sender script will likely use

  • Imager
    • error distribution errdiff quantization option
  • Mojolicious for communication
    • request/response mechanism, easily adapted into Future / Future::Mojo

I need to port the mdc Javascript module and create a test suite to properly encode the blobs.

MDC documentation PDF

While looking at the Javascript code, I found the official MDC documentation version 15.0 PDF from 2020-11-06 . This describes the actual protocol (serial, or over IP) in detail.

The command blocks are simple, consisting only of 8-bit bytes. This is a problem for payloads (like URLs) that are longer than 230 bytes. But for my use-case, that is no problem.

Request

| Header | Command | Display ID | Payload length | Payload | 8-bit checksum | | --- | --- | --- | --- | --- | --- | | 0xAA | . | . | . | . | 8-bit sum of payload |

Response

| Response | Header | Command | Display ID | Payload length | ACK / NACK | Payload | 8-bit checksum | | --- | --- | --- | --- | --- | --- | --- | --- | | 0xFF | 0xAA | . | . | . | "A" / "N" | . | (not verified) |

The inconvenient thing is that the length does not immediately precede the payload, so that unpack does not work. Instead I've used a regular expression to extract the parts.

The blobs are sent and received via port 1515. I have not found a way to change this port, but I'm not sure why I would want that.

Program structure

The program to change the image would then

  • Spin up a webserver ( Mojo::Server::Daemon
  • send the URL with code 0x53 / 0x80
  • wait for playlist and image(s) to be fetched
  • done