Author Archives: Scott Alfter

An ffmpeg cheatsheet

I recently had some need to clean up the subtitles in some video files in my possession. It took a little while to track down the exact set of options to get it to do what I wanted it to do, so I’ve written them down here for future reference. This post may be amended from time to time to add more

Extract subpictures from file

The second number in the -map parameter selects the (zero-indexed) stream to extract. Use something like mediainfo to determine which stream to select, then issue something like this:

ffmpeg -i src.m4v -c copy -map 0:2 dest.en.sup

Subtitle Edit can read subpictures and convert them to text subtitles. As long as you have a .NET runtime available, it should work; I’ve run it on Windows 11 and Gentoo Linux. Its accuracy is pretty good, especially if you have it use one of the Tesseract OCR engines.

Extract text subtitles from file

ffmpeg -i src.m4v -f srt dest.en.srt

“srt” can be replaced with several other formats (ssa, ass, etc.) if you want those. Depending on the source, there may be extra HTML formatting that you might want to use sed to filter out. If more than one subtitle stream is present, a “-map” parameter may be necessary to select the one you want.

Mux text subtitles into file

ffmpeg -i src.m4v -i src.en.srt -c copy -c:s mov_text -map 0:0 -map 0:1 -map 1:0 -metadata:s:2 language=eng -metadata title= dest.m4v

Assumptions: the desired video and audio are in the first two streams of the first file, and the subtitles are in English. (Audio language should already be set in that stream to whatever it is.)

“-metadata title=” will clear out the title string that might have been set in the original file.

Selecting tracks by language

Instead of needing to look up track numbers, it’d be nice to just specify the language(s) we want to include. That is possible, with something like this:

ffmpeg -i src.mkv -c copy -map 0:v:0 -map 0:a:m:language:ger -map 0:s:m:language:eng dest.mkv

This example selects German audio and English subtitles…useful for something like Das Boot or Deutschland 83. (Assuming that you like foreign shows in their original language with English subtitles, anyway…which I do.) These track mapping options should also apply in the preceding examples.

Extracting chapters from existing file

ffmpeg -i src.mkv -f ffmetadata src.metadata

This creates a file with chapter information (and possibly other metadata) structured like this:

;FFMETADATA1
encoder=Lavf60.3.100
[CHAPTER]
TIMEBASE=1/1000
START=0
END=728102
title=Chapter 1
[CHAPTER]
TIMEBASE=1/1000
START=728102
END=1319902
title=Chapter 2
[CHAPTER]
TIMEBASE=1/1000
START=1319902
END=2008965
...

Inserting chapters

A metadata file structured as above can be inserted into a file when it’s encoded:

ffmpeg -i src.mkv -i src.metadata -map_chapters 1 ...

On Trump and taxes

According to the Epoch Times, Donald and Melania Trump reported negative income in four of the six years’ worth of tax returns that were dumped. I’m sure the usual scumbags will try to make something of this, but as long as all the “I”s are dotted and “T”s are crossed, I don’t see anything at all wrong with this:

Anyone may arrange his affairs so that his taxes shall be as low as possible; he is not bound to choose that pattern which best pays the treasury. There is not even a patriotic duty to increase one’s taxes. Over and over again the Courts have said that there is nothing sinister in so arranging affairs as to keep taxes as low as possible. Everyone does it, rich and poor alike and all do right, for nobody owes any public duty to pay more than the law demands.

– Learned Hand

LED Marquee, or scrolling text across multiple IS31FL3731 displays

I lost my first Tindie sale to misplaced email. By the time I discovered I’d had an order come in, it had already been refunded automatically. Determined to avoid a repeat, I figured that a display of some sort that could be updated with live order information from Tindie, H3 Hab Bazaar, and elsewhere would be useful for making sure orders are handled expeditiously.

For display elements, I wanted LED modules that aren’t too ridiculously large, and a single color would be sufficient. Adafruit has charlieplexed 16×9 LED matrix boards (such as this) that are just a mess of 0603 LEDs wired together appropriately. They’re meant to be used with this driver board, which provides an I2C interface. As many as four boards can be used together on one I2C bus; that’ll make a 64×9 display about eight inches wide and an inch tall. I2C allows use of something small and cheap like an ESP-01 module that also provides WiFi so the display can be reconfigured on the fly.

Here’s the prototype, with two of the display modules connected:

(It looks brighter in person than it does in the video. That said, I also have the brightness dialed way back from maximum. You can crank it bright enough that your eyes will bleed. :-) The blue LED on the ESP-01 is connected to GPIO2, which we’re using for the I2C clock…that’s why it’s lit up.)

It’s just a fixed string right now, with WiFi disabled. Adding the rest of the displays is a matter of soldering them together and daisy-chaining them with some cables. I intend to put the displays in a 3D-printed enclosure and hang it on the wall, with some code running on my home server to update the display periodically over WiFi.

Finding code that uses multiple Adafruit_GFX-compatible displays and treats them as one unit was a bit tricky. I thought it might combine the four 16×9 screens into a single 64×9 virtual screen, but it doesn’t. Instead, your code needs to divide the image across the four screens. Fortunately, Adafruit_GFX allows for graphics coordinates outside the range of the screen, which (1) facilitates smooth scrolling by allowing the same text to be written one pixel to the left at a time and (2) allows the same content to be written to multiple screens with different offsets so they end up showing a larger image. Here’s the code that runs the demo in the video shown above:

#include <Arduino.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_IS31FL3731.h>

#define NUM_SCREENS 2 
#define BRIGHTNESS 8

Adafruit_IS31FL3731 matrix0 = Adafruit_IS31FL3731();
#if NUM_SCREENS>=2
Adafruit_IS31FL3731 matrix1 = Adafruit_IS31FL3731();
#endif
#if NUM_SCREENS>=3
Adafruit_IS31FL3731 matrix2 = Adafruit_IS31FL3731();
#endif
#if NUM_SCREENS>=4
Adafruit_IS31FL3731 matrix3 = Adafruit_IS31FL3731();
#endif

void setup() {
  // set up I2C
  Wire.pins(0,2); // SDA on GPIO0, SCL on GPIO2
  Wire.begin(0,2);

  // connect to display(s)
  if (! matrix0.begin(0x74)) 
    while (1);
  #if NUM_SCREENS>=2
  if (! matrix1.begin(0x75)) 
    while (1);
  #endif
  #if NUM_SCREENS>=3
  if (! matrix2.begin(0x76)) 
    while (1);
  #endif
  #if NUM_SCREENS>=4
  if (! matrix3.begin(0x77)) 
    while (1);
  #endif
}

void loop() {

  String msg="The quick brown fox jumps over the lazy dog.";

  msg="    "+msg+"    ";
  matrix0.setRotation(0);
  matrix0.setTextSize(1);
  matrix0.setTextWrap(false);
  matrix0.setTextColor(BRIGHTNESS,0);
  matrix0.clear();
  #if NUM_SCREENS>=2
  msg="    "+msg+"    ";
  matrix1.setRotation(0);
  matrix1.setTextSize(1);
  matrix1.setTextWrap(false);
  matrix1.setTextColor(BRIGHTNESS,0);
  matrix1.clear();
  #endif
  #if NUM_SCREENS>=3
  msg="    "+msg+"    ";
  matrix2.setRotation(0);
  matrix2.setTextSize(1);
  matrix2.setTextWrap(false);
  matrix2.setTextColor(BRIGHTNESS,0);
  matrix2.clear();
  #endif
  #if NUM_SCREENS>=4
  msg="    "+msg+"    ";
  matrix3.setRotation(0);
  matrix3.setTextSize(1);
  matrix3.setTextWrap(false);
  matrix3.setTextColor(BRIGHTNESS,0);
  matrix3.clear();
  #endif

  for (int i=0; i<msg.length()-4*NUM_SCREENS; i++)
  {
    String segment=msg.substring(i, i+4*NUM_SCREENS);
    for (int8_t x=0; x>-6; x--) {
      matrix0.setCursor(x,0);
      matrix0.print(segment);
      #if NUM_SCREENS>=2
      matrix1.setCursor(x-16,0);
      matrix1.print(segment);
      #endif
      #if NUM_SCREENS>=3
      matrix2.setCursor(x-32,0);
      matrix2.print(segment);
      #endif
      #if NUM_SCREENS>=4
      matrix3.setCursor(x-48,0);
      matrix3.print(segment);
      #endif
      delay(25);
    }
  }
}

I really wanted an array of Adafruit_IS31FL3731 instances that I could iterate through with loops, but I couldn’t get that to work, so instead the loops are unrolled with a bunch of #ifdefs to control how many are produced, based on the number of attached screens. At first I was running with one screen, but last night I got it running with two. I haven’t yet tested it, but three or four screens should work as well.

This code builds in PlatformIO, and I currently have it targeting the ESP-01 (which uses an ESP8266). Before I figured out how to enable I2C on the ESP-01, I had a single screen driven by an Arduino Nano, but I’ve seen posts elsewhere indicating that it might not have enough RAM to handle multiple screens.

Next task is to set up some sort of control interface (probably web-based) to allow content to be changed on the fly, and to knock together an enclosure so it’s more than just a bunch of boards and wires.

Fuck Joe Biden.

Imagine if the roles were reversed

My tax refund finally came through today, 4½ months after filing. By far, this is the longest it has ever taken. Even though I file by mail with printed forms, somewhere around 3-4 weeks is more typical.

If I owed money, do you think the IRS would tolerate such a lengthy delay without at least tacking on some penalties? Didn’t think so.

Fuck Joe Biden.

Solid advice

Hate them back:

I’m a big believer in giving back to people what they give to others. Think of it as a twist on the Golden Rule: if someone is a bad person to others, even if it isn’t you, treat them accordingly. Someone who is a bad person to people who aren’t you will eventually get around to you because it’s who they are at their core. That’s how I see Democrats, particularly Joe Biden. Jill too, but she’s such a non-entity without him she barely deserves a mention. If you are not with them, not one of them., they hate you, they really do. Return the favor.

Fuck Joe Biden.