Enhance your asciinema recordings

Asciinema is great. It lets you create textmode screen recordings like this one:

The downside is that I'm a slow typist, and you have to sit there and watch me type out the commands in real time. It's annoying.

No problem, we can use the builtin --idle-time-limit option to limit the time between events to 0.1 seconds:

But somethings is still missing. There is no time for the viewer to read the command before it is run. There is also no time for the viewer to read the output of one command before the next one starts appearing on screen. If the original recording felt too slow, this one is way too fast! In addition to that, the loading time of important-command.sh is also limited, making it appear faster than it actually is.

Let's go a step further. We can write a Python script that will change the timings of events based on the following rules:

Here is what is looks like:

Doesn't that look so much nicer?

If you want to apply the same effect to your asciinema recordings, you can use my script below. The script takes two arguments: the input filepath and the output filepath.

Note that you will probably need to tweak it, in particular the PROMPT constant at the top of the file. This should correspond to the string captured by asciinema when a command finishes running and the shell displays a prompt. I've set it to the value that works for a simple $ prompt. You will need to change it if your prompt is different.

If everything works correctly, the script should print a message to stderr whenever it adds extra time before and after a command is run in the asciinema recording.

#!/usr/bin/env python3

from sys import stderr
import re
from pathlib import Path
import argparse

PROMPT = r'$ \u001b[6n'

def cli():
    parser = argparse.ArgumentParser()

    parser.add_argument("input", type=Path, help="Input file path")
    parser.add_argument("output", type=Path, help="Output file path")

    args = parser.parse_args()

    args.output.write_text(
        '\n'.join(enhance(args.input))
        )

def enhance(input_path):
    lines = []
    for line in input_path.read_text().split('\n'):
        match = re.match(r'\[(\d+\.\d+), "o", "(.*)"\]', line)
        if match:
            lines.append((
                float(match.groups()[0]),
                match.groups()[1],
                line
                ))
        else:
            lines.append((None, None, line))

    time = 0.0
    last_line_time = 0.0
    last_line_length = 0

    for i, (line_time, string, line) in enumerate(lines):
        if not line_time:
            yield line
            continue
        

        if string == r'\r\n':
            # Time before next command
            # (to let the viewer read the output)
            print('Added time before next command', file=stderr)
            time += 2

        elif len(string) == 1:
            # Timing between keystrokes
            time += 0.05

            if (
                    i - 1 >= 0
                    and
                    lines[i - 1][1] == PROMPT
                    ):
                # Time before executing command
                # (to let the viewer read your command)
                print('Added time before running command', file=stderr)
                time += 2
        else:
            time += line_time - last_line_time

        yield f'[{time:.6f}, "o", "{string}"]'

        last_string = string
        last_line_time = line_time
        last_line_length = len(string)

if __name__ == '__main__':
    cli()