A Weird Imagination

Clipping videos from the shell

The problem#

Sometimes I have a long video that I only want a shorter section of. Maybe it's a TV show that I want to clip a funny scene out of. Or a video of a concert that I want to clip the individual songs out of so I can put them into my music library. But figuring out exactly where to start and end the clip so there's no weird sounds or flashes due to accidentally including the surrounding video takes some care.

The solution#

In order to determine the right clip location, I wanted an easy way to repeatedly watch and listen to the start or end of a proposed clip and get immediate feedback on adjusting its position frame-by-frame.

find_split is a script that implements this workflow. You give it a video, a proposed frame index to split the video on, a preview context length in number of frames, and whether the preview should be the video before (to the left of) or after (to the right of) the split. It plays the preview window ending or starting at the proposed split and waits for the user to press a key indicating how to move the proposed split and then repeats, starting by playing that preview. Once you've confirmed you've determined the desired frame index, press ` (or just Ctrl+c) to exit. The full keyboard controls are described at the link.

$ ./find_split video.mkv 1000 60 left
Playing video.mkv on left of split at 1000 \(with 60 extra frames\).
Playing video.mkv on left of split at 1060 \(with 60 extra frames\).

The second line appears after typing Shift+h to increase the proposed frame index by 60 after the first clip finishes. That means the second clip played will be from frame 1000 to frame 1060, so about two seconds (60 frames) long starting at approximately 33 seconds into the video. You can continue to adjust the position until you've decided you've determined the correct frame.

Once you have found the start and end frames (here 1060 to 2034), you can save the clip to a file:

$ ./encode_frames video.mkv 1060 2034 clip.mkv av

(Change the av to audio if you only want the audio and not the video.)

The details#

Read more…

Linting Markdown reference-style links

The problem#

When writing blog posts, I like to use Markdown's reference-style links which let you avoid writing URLs inline and instead provide a short name and define it elsewhere in the document. I always put them at the end, which results in the bottom of the Markdown file looking like a bibliography for the post. But then there's the extra task of making sure the references at the bottom of the post are consistent with their usage in the blog post; this isn't a huge problem as usually I add a link and immediately use it, looking at the preview to make sure it got used properly. But sometimes I'll start a post by entering a list of links I expect to use, and sometimes I'll miss something checking the preview.

The solution#

lint_refs.py takes any number of Markdown files as arguments and prints the references that are invalid or not used:

#!/usr/bin/env python

import sys
from pathlib import Path
from markdown import markdown, extensions, postprocessors


class ReferenceProxy(dict):
    def __init__(self, *arg, **kw):
        super(ReferenceProxy, self).__init__(*arg, **kw)
        self.read = set()

    def __contains__(self, key):
        self.read.add(key)
        return super().__contains__(key)


class ReferenceLintExtension(extensions.Extension,
                             postprocessors.Postprocessor):
    def __init__(self, filename):
        self.filename = filename

    def extendMarkdown(self, md):
        self.refs = md.references = \
                ReferenceProxy(**md.references)
        md.postprocessors.register(self, 'ref_lint', 1)

    def run(self, text):
        undefined = self.refs.read - set(self.refs.keys())
        unused = set(self.refs.keys()) - self.refs.read
        if unused or undefined:
            print(f"\n\n# {self.filename}")
            if undefined:
                print("\n## UNDEFINED REFERENCES")
                print('\n'.join(sorted(undefined)))
            if unused:
                print("\n## UNUSED REFERENCES")
                print('\n'.join(sorted(unused)))
        return text


for filename in sys.argv[1:]:
    markdown(Path(filename).read_text(), extensions=[
        ReferenceLintExtension(filename),
        'markdown.extensions.extra'])

Given example.md:

A [broken link][broken]. A [working link][working].

[working]: https://example.com/
[not-used]: https://example.org/
$ ./lint-refs.py example.md 


# example.md

## UNDEFINED REFERENCES
broken
broken link

## UNUSED REFERENCES
not-used

The details#

Read more…

Interpreting keypresses in shell

The problem#

Last time, I presented a script read_keypress.sh which would read any single keypress in a shell script. For printable characters, it's straightforward what this means, but for keys like left arrow (🠈), it's not obvious how to deal with them.

The solution#

keytest.sh demonstrates using read_keypress.sh to respond to keypresses including 🠈 and Ctrl+c:

#!/bin/bash

esc=$(printf '\e')
ctrlc=$(printf '\x03')

while true
do
  key="$(./read_keypress.sh)"
  case $key in
    q|"$ctrlc")
      echo "Quitting..."
      exit
      ;;
    "${esc}[D")
      echo "Moved left."
      ;;
    *)
      echo "Unexpected key: $key ($(echo -n "$key" | xxd))"
      ;;
  esac
done

The details#

Read more…

Handling keypresses in shell

The problem#

For certain applications it can be useful to get many quick responses from the computer, so we would like it to react to individual keypresses instead of requiring an entire command to be typed out and confirmed by pressing Enter. While this is a common feature of GUI and TUI toolkits (e.g., ncurses), it can also be useful for very lightweight inactive experiences written as simple shell scripts.

The solution#

read_keypress.sh will wait for the next keypress and return it as a string:

#!/bin/sh

STTY=$(stty --save)
stty raw -echo
SEL=$(dd if=/dev/tty bs=1k count=1 2>/dev/null)
stty "$STTY"
echo -n "$SEL"

The details#

Read more…

Thunar custom actions

The problem#

While I use the command-line and keyboard-based interfaces a lot to manage files, I also regularly use graphical and mouse-based interfaces. As with any time there's different ways of interacting with a system, there's some times when in one mode when you want the features of the other.

The solution#

Xfce's file manager Thunar has a feature called custom actions, which lets you add menu items that run commands on files or directories you are viewing or selecting.

The documentation suggests multiple ideas of actions you might want, including converting among different formants, rotating images, and various file management actions like viewing disk usage and bulk moving files. Additionally, the default action "Open Terminal Here" is one I use frequently.

The details#

Read more…

Playing Android motion photos

The problem#

My Android phone's camera app has an option to take a "motion photo", which, similar to the iOS "live photo" feature, records a short, silent video along with the photo. When viewing the photos on the phone, there's an option to play the video. But image viewer programs on my computer like Eye of GNOME (eog) do not support playing them.

The solution#

ExifTool (in Debian, the package name is libimage-exiftool-perl) can extract the video to a file using this example from a forum post:

exiftool -b -EmbeddedVideoFile photo.jpg > photo_motion.mp4

You can also play it directly without saving it to a file by piping to VLC:

exiftool -b -EmbeddedVideoFile photo.jpg | vlc -

The details#

Read more…

Viewing Signal messenger backups

Posted in

The problem#

Signal is a privacy-focused instant messaging application which ensures end-to-end encryption on all messages and generally goes out of its way to avoid accidentally revealing your conversations to third parties. As is common with security mechanisms, this necessarily adds some friction some some tasks you actually want to do. In particular, this means viewing your message history outside of the official app is unsupported. Which is especially a problem due to the app being tied to a smartphone that will likely only continue being usable for at most several years. So there's no official way to maintain access to your conversations indefinitely in contrast to IRC and Pidgin logs I have going back decades.

The solution#

The Signal Andriod app supports making encrypted backups (there is no way to get messages out of Signal iOS).

When you enable backups, it gives you a "passphrase" (a sequence of 30 digits). If you have lost this passphrase, there is no way to recover it, but you can disable backups and then re-enable them which will generate a fresh passphrase that will be used for your backups going forward. I recommend storing this passphrase in a password manager like KeePassXC.

To actually use the backup file, you'll need to know where it is. The location is shown on the Signal settings under Chats -> Chat backups -> Backup folder. You can copy it to your computer by plugging the phone in with a USB cable and using an MTP client, which is included in most file managers (I use XFCE's Thunar).

Once you have the backup file and passphrase, you can use signalbackup-tools to convert the backup to something human-readable:

signalbackup-tools [input] [passphrase] \
                   --exporthtml [directory] --allhtmlpages

The output does a good job of trying to look like the Signal interface. See the documentation for more options on controlling the output including various options to filter the output to just some conversations or time ranges.

The details#

Read more…

Move window to current desktop

The problem#

Previously, I wrote a script for opening and immediately focusing xfce4-appfinder. But xfce4-appfinder will notice if it's already open and just assume you want to use the existing window. Even if it's on a different desktop. And therefore attempting to focus it will either do nothing or switch to that desktop, neither of which is desirable.

The solution#

The actual solution I settled on is more of a workaround than a solution: having xfce4-appfinder on a different desktop doesn't really make sense, so I just set it to be on all desktops (specified as the non-existent desktop -1), so it would never be on the wrong desktop:

xdotool set_desktop_for_window "$winid" -1

To actually move the window to the current desktop, replace xdotool windowactivate "$winid" with

desktop="$(xdotool get_desktop)"
win_desktop="$(xdotool get_desktop_for_window "$winid")"
if [[ "$desktop" == "$win_desktop" ]]
then
  xdotool windowactivate "$winid"
else
  xdotool set_desktop_for_window "$winid" -1
  xdotool set_desktop_for_window "$winid" "$desktop" \
          windowactivate "$winid"
fi

Although this assumes that you know the $winid of the window you want to move. If you have just the title it works just as well to use

wmctrl -R "Application Finder"

The details#

Read more…

Tracking household tasks

Posted in

The problem#

The new year is a traditional time for adopting new organizational schemes, among other oft-broken promises to oneself of improved habits. In that vein, I recently adopted a new system for managing my TODO list.

Managing a household involves a lot of infrequent tasks that are easy to forget like checking filters on various appliances every few months and similar invisible maintenance tasks. I had been managing such tasks using recurring Google Calendar events with email reminders, but it was getting unwieldy for multiple reasons. It didn't provide a good record of whether and when tasks were completed (which matters for tasks that should be done some number of weeks or months since the last time it was completed, not since the last time I was reminded of it). Additionally, it doesn't provide a good way to share the TODOs with other members of the household who are also responsible for some of those tasks. And it also cluttered up my calendar with items that didn't really have a meaningful assignment to a particular day or time.

The solution#

Task management systems are very personal: while I will describe what I came up with that hopefully I will continue to find useful, what works for you may be very different.

I set up a TODO list for my household (plus a separate one just for myself) using Sleek which uses the todo.txt format (see todotxt.org for more info and other software). The directory containing the TODO file is shared with the rest of the household with Syncthing. For backups, the directory is a ZFS dataset, so it is automatically snapshotted regularly and included in my backups. If you wanted, you could also apply my copy on save logic to snapshot every change, but that's likely overkill.

Example tasks#

While the simplicity of the todo.txt format means it's easy to edit by hand or use any tool including ones you write yourself, the Sleek GUI handles the syntax for you so it is accessible to non-technical users as well.

Sleek supports "threshold" dates before which tasks are hidden from view by default and recurring tasks which can be "strict" (based on due date, prefixed with +) or not (based on completion date), which allows the specification of tasks like "check funance filter 2-3 months since the last time it was checked":

rec:3m t:2025-02-15 due:2025-03-15 check furnace @filter

as well as "pay the electric bill between the 20th and the end of each month":

rec:+1m t:2024-12-20 due:2025-12-30 pay electric @bill

The thresholds allow for keeping the noise down on the list by hiding tasks that cannot be done yet (can't pay a bill that hasn't arrived yet) or don't make sense to do so soon after they were last done.

The details#

Read more…

Killing pipes from within

The problem#

Last week, I mentioned that I needed a hack to kill xprop that seemed like it should be unnecessary. Specifically, I had its output piped to a Bash while read loop and once that had found a line to act on, there was no further need to get more lines from xprop, but break or exit didn't result in xprop exiting.

The solution#

Use $BASHPID to get the actual PID of the subshell, ps to climb the process tree to the appropriate parent and pkill to kill its children:

some_pipeline |
  while read -r line
  do
    # Do whatever until ready to kill the pipe...
    # ... then kill it:
    ppid=$BASHPID
    ppid=$(ps -o ppid:1= "$ppid")
    pkill -9 -P "$ppid" 
  done

Rewrite appfinder-and-focus.sh from last time:

#!/usr/bin/bash
xprop -spy -root _NET_CLIENT_LIST | stdbuf -oL head -2 |
  while read -r l
  do
    winid="${l/#*, /}"
    if [[ $(xdotool getwindowname "$winid") == \
      "Application Finder" ]]
    then
      xdotool windowactivate "$winid"
      ppid=$BASHPID
      ppid=$(ps -o ppid:1= "$ppid")
      pkill -9 -P "$ppid" 
    fi
  done) 2>/dev/null &
xfce4-appfinder &

The details#

Read more…