Keeping track of podcast times with a simple bash script on Linux

I was recording some podcast audio tonight and wanted to be able to press a single key when I reached a significant moment, so I could add the times to the show notes.

I couldn’t find anything that already did this, so I wrote a tiny bash script. I ran this script and pressed Enter whenever I wanted a time recorded:

T=0
echo
while sleep 1; do
    echo -n -e "\e[1A"
    echo $(($T / 60))m $(($T % 60))s
    T=$(($T + 1))
done

The output looks like this:

$ ./times.bash 
0m 41s
6m 16s
9m 59s
13m 30s

The time ticks along, and when you press Enter that time stamp is recorded. You can copy this text out of your terminal to use in show notes. On most terminals you can copy text by selecting it with the mouse and pressing Ctrl-Shift-v.

Custom Bash tab completion for my program

I love Bash tab completion, and I want it for the command I am writing, so it can automatically complete parts of the command line when I run my program.

Code

Here is the script (install-bash-completion) I wrote to set it up (no need to be root – it installs in ~/.local):

#!/bin/bash

set -u
set -e

DIR=${BASH_COMPLETION_USER_DIR:-${XDG_DATA_HOME:-$HOME/.local/share}/bash-completion}/completions

mkdir -p ${DIR}

cp _myprogram ${DIR}

The actual completion script (_myprogram) it installs looks like this:

_myprogram_commands()
{
    local cur prev opts
    COMPREPLY=()
    cur="${COMP_WORDS[COMP_CWORD]}"
    prev="${COMP_WORDS[COMP_CWORD-1]}"
    opts=$(bash -c "./myprogram --commands")

    COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
    return 0
}
complete -F _myprogram_commands ./myprogram

Installing

To install it, run:

./install-bash-completion

Then log out and log in again.

Now when you type ./myprogram and press TAB a couple of times, you should see the possible completions listed.

Notes

The completion script must be named to match the program name, with a leading underscore.

If I wanted it to work when it was installed in my PATH, I would need to change ./myprogram to just myprogram in 2 places.

Notice the line opts=$(bash -c "./myprogram --commands") – it actually runs my program to get the list of completions. This means my program needs to accept a --commands option which prints the valid commands. Alternatively, I could have hard-coded it by replacing that line with just:

opts="cmd1 cmd2 --help --etc"

More info:

Convert a video to a GIF with reasonable colours

Here’s a little script I wrote to avoid copy-pasting the ffmpeg command from superuser every time I needed it.

It converts a video to a GIF file by pre-calculating a good palette, then using that palette.

Usage:

./to_gif input.mp4 output.gif

The file to_gif (which should be executable):

#!/bin/bash

set -e
set -u

# Credit: https://superuser.com/questions/556029/how-do-i-convert-a-video-to-gif-using-ffmpeg-with-reasonable-quality

INPUT="$1"
OUTPUT="$2"

PALETTE=$(mktemp --suffix=.png)

ffmpeg -y -i "${INPUT}" -vf palettegen "${PALETTE}"
ffmpeg -y -i "${INPUT}" -i "${PALETTE}" \
    -filter_complex "fps=15,paletteuse" "${OUTPUT}"

rm -f "${PALETTE}"

Note: you might want to modify the number after fps= to adjust how fast the video plays.

Update: changed to use mktemp instead of tempfile.

Update: and here is how I add it to the context menu in Caja:

Place the above file in $HOME/bin/to_gif.

Create a file ~/.config/caja/scripts/to_gif (don’t forget to make it executable):

#!/bin/bash

mate-terminal -x "${HOME}/bin/to_gif" "$1" "$1".gif

Keybase chat bot in 10 lines of bash

I’ve been getting very excited about keybase.io recently, not least because it offers secure conversation, and you can have bots.

I wrote a quick bot to simulate Arnold Schwarzenegger which I thought I’d share to demonstrate how easy it is. It is based on the keybase command line tool (which comes with the desktop client as standard) and jq, the brilliant command-line JSON manipulator.

For this to work, you need to have the keybase command installed and working, and you need jq.

Here’s the bot:

#!/bin/bash
CHANNEL=mychannel
keybase chat api-listen | while read L; do
{
    OUT=$(jq --raw-output 'select(.type == "chat")|select(.msg.content.text.body|startswith("!arnie "))| .msg.content.text.body | "*" + ltrimstr("!arnie ") + "*"' <<< "$L")
    if [ "${OUT}" != "" ]; then
    {
        keybase chat send "${CHANNEL}" "${OUT}"
    }; fi
}; done

and here's it working:

andy> !arnie Do eet do eet now!!!
andy> Do eet do eet now!!!

Note: here the bot is pretending to be me. To do this nicely, you will want a different account for the bot, but you get the idea.

Obviously, I am now working on a comprehensive bot framework in Rust. Watch this space.

Batch-converting audio files to be louder (on Linux)

My mp3 player is very quiet, so I wanted to make all my podcasts as loud as possible.

First I ran this to get the programs I needed:

sudo apt-get install libav-tools normalize-audio

To convert each file I made a script that makes a “loud” directory, and puts the loud version of a file inside there. It uses the normalize-audio command to do it.

Note that this script encodes your (now louder) podcasts into Ogg Vorbis format at 50kb/s, which is quite low quality.

#!/bin/bash

set -e
set -u

FILE="$1"
DIR=`dirname "$FILE"`

FILENAME=`basename "$FILE"`
WAV_FILENAME="${FILENAME}.wav"

LOUD_DIR="$DIR/loud"
WAV_FILE="$LOUD_DIR/$WAV_FILENAME"
LOUD_FILE="$LOUD_DIR/$FILENAME"

mkdir -p "$LOUD_DIR"

avconv -loglevel quiet -i "$FILE" "$WAV_FILE"
normalize-audio -q -a 1 "$WAV_FILE"
avconv -loglevel quiet -i "$WAV_FILE" -c:a libvorbis -b:a 50k "$LOUD_FILE"
rm "$WAV_FILE"

Finally I placed a Makefile in the directory containing podcasts directories, like this:

MP3S := $(wildcard *.mp3)
LOUD_MP3S := $(MP3S:%.mp3=loud/%.mp3)

OGGS := $(wildcard *.ogg)
LOUD_OGGS := $(OGGS:%.ogg=loud/%.ogg)

all: $(LOUD_OGGS) $(LOUD_MP3S)

loud/%.ogg: %.ogg
	loud "$<"

loud/%.mp3: %.mp3
	loud "$<"

Now I can make loud versions of all podcasts by just cding into the directory containing the Makefile, and typing make. By the power of make, it only converts files that have not already been converted.