diff --git a/demo/config.yml b/demo/config.yml deleted file mode 100644 index defe50a5b..000000000 --- a/demo/config.yml +++ /dev/null @@ -1,112 +0,0 @@ -# Specify a command to be executed -# like `/bin/bash -l`, `ls`, or any other commands -# the default is bash for Linux -# or powershell.exe for Windows -command: echo "YOU NEED TO SPECIFY YOUR OWN COMMAND WITH THE -d ARG" - -# Specify the current working directory path -# the default is the current working directory path -cwd: null - -# Export additional ENV variables -env: - recording: true - -# Explicitly set the number of columns -# or use `auto` to take the current -# number of columns of your shell -cols: 120 # 100 - -# Explicitly set the number of rows -# or use `auto` to take the current -# number of rows of your shell -rows: 35 # 30 - -# Amount of times to repeat GIF -# If value is -1, play once -# If value is 0, loop indefinitely -# If value is a positive number, loop n times -repeat: 0 - -# Quality -# 1 - 100 -# Higher quality seems to make no difference, but running it through -# gifsicle ends up with a much better compressed version. -quality: 100 - -# Delay between frames in ms -# If the value is `auto` use the actual recording delays -frameDelay: auto - -# Maximum delay between frames in ms -# Ignored if the `frameDelay` isn't set to `auto` -# Set to `auto` to prevent limiting the max idle time -maxIdleTime: 2000 - -# The surrounding frame box -# The `type` can be null, window, floating, or solid` -# To hide the title use the value null -# Don't forget to add a backgroundColor style with a null as type -frameBox: - type: floating - title: Lazygit - style: - border: 0px black solid - backgroundColor: "#1d1d1d" - margin: -5px - -# Add a watermark image to the rendered gif -# You need to specify an absolute path for -# the image on your machine or a URL, and you can also -# add your own CSS styles -watermark: - imagePath: null - style: - position: absolute - right: 15px - bottom: 15px - width: 100px - opacity: 0.9 - -# Cursor style can be one of -# `block`, `underline`, or `bar` -cursorStyle: block - -# Font family -# You can use any font that is installed on your machine -# in CSS-like syntax -# Download from: -# https://github.com/ryanoasis/nerd-fonts/releases/download/v3.0.2/DejaVuSansMono.zip -# Not using the mono font because it makes icons too small. -fontFamily: "DejaVuSansM Nerd Font" - -# The size of the font -fontSize: 8 - -# The height of lines -lineHeight: 1 - -# The spacing between letters -letterSpacing: 0 - -# Theme -theme: - background: "transparent" - foreground: "#dddad6" - cursor: "#c7c7c7" - black: "#7a7a7a" - red: "#fc4384" - green: "#b3e33b" - yellow: "#ffa727" - blue: "#102895" - magenta: "#c930c7" - cyan: "#00c5c7" - white: "#c7c7c7" - brightBlack: "#676767" - brightRed: "#ff7fac" - brightGreen: "#c8ed71" - brightYellow: "#ebdf86" - brightBlue: "#6871ff" - brightMagenta: "#ff76ff" - brightCyan: "#5ffdff" - brightWhite: "#fffefe" diff --git a/demo/record_demo.sh b/demo/record_demo.sh index 97d5c2f36..b15bb8ac9 100755 --- a/demo/record_demo.sh +++ b/demo/record_demo.sh @@ -2,30 +2,32 @@ set -e -TYPE=$1 -TEST=$2 +TEST=$1 + +# The repository the demo is uploaded to. GitHub only plays videos that live in +# its own attachment store, and an attachment is tied to one repository. +REPO=jesseduffield/lazygit usage() { - echo "Usage: $0 [gif|mp4] " - echo "e.g. using full path: $0 gif pkg/integration/tests/demo/nuke_working_tree.go" + echo "Usage: $0 " + echo "e.g. $0 pkg/integration/tests/demo/nuke_working_tree.go" exit 1 } -if [ "$#" -ne 2 ] +if [ "$#" -ne 1 ] then usage fi -if [ "$TYPE" != "gif" ] && [ "$TYPE" != "mp4" ] -then - usage - exit 1 -fi - -if [ -z "$TEST" ] -then - usage -fi +for TOOL in vhs ttyd ffmpeg gh +do + if ! command -v "$TOOL" > /dev/null 2>&1 + then + echo "$TOOL could not be found" + echo "Install it with: brew install $TOOL" + exit 1 + fi +done WORKTREE_PATH=$(git worktree list | grep assets | awk '{print $1}') @@ -39,20 +41,6 @@ fi OUTPUT_DIR="$WORKTREE_PATH/demo" -if ! command -v terminalizer &> /dev/null -then - echo "terminalizer could not be found" - echo "Install it with: npm install -g terminalizer" - exit 1 -fi - -if ! command -v "gifsicle" &> /dev/null -then - echo "gifsicle could not be found" - echo "Install it with: npm install -g gifsicle" - exit 1 -fi - # Get last part of the test path and set that as the output name # example test path: pkg/integration/tests/01_basic_test.go # For that we want: NAME=01_basic_test @@ -63,19 +51,76 @@ go generate pkg/integration/tests/tests.go mkdir -p "$OUTPUT_DIR" -# First we record the demo into a yaml representation -terminalizer -c demo/config.yml record --skip-sharing -d "go run cmd/integration_test/main.go cli --slow $TEST" "$OUTPUT_DIR/$NAME" -# Then we render it into a gif -terminalizer render "$OUTPUT_DIR/$NAME" -o "$OUTPUT_DIR/$NAME.gif" +SCRATCH=$(mktemp -d) +trap 'rm -rf "$SCRATCH"' EXIT -# Then we convert it to either an mp4 or gif based on the command line argument -if [ "$TYPE" = "mp4" ] +TAPE="$SCRATCH/$NAME.tape" +RECORDING="$SCRATCH/$NAME.mp4" +OUTPUT="$OUTPUT_DIR/$NAME.mp4" + +# The two quotes in the marker keep the literal VHSDONE out of the command line +# that stays on screen while we wait for the marker to be printed. +cat > "$TAPE" < "$COMPRESSED_PATH" + echo "vhs recorded the demo but wrote no video." + echo "vhs 0.12.0 does this; see https://github.com/charmbracelet/vhs/issues/787." + echo "Install a working version with: go install github.com/charmbracelet/vhs@v0.11.0" + exit 1 fi -echo "Demo recorded to $COMPRESSED_PATH" +# Hold the last frame for a moment so that the end state stays readable, and +# move the moov atom to the front so that the video starts playing before it +# has fully downloaded. +ffmpeg -y -loglevel error -i "$RECORDING" \ + -vf "tpad=stop_mode=clone:stop_duration=1.2" \ + -c:v libx264 -crf 23 -preset slow -pix_fmt yuv420p \ + -movflags +faststart -an "$OUTPUT" + +# GitHub's web editor posts to this endpoint when you drag a file into a +# comment box. It is undocumented, but it accepts an ordinary token, so we can +# upload from here. You need push access to $REPO for it to work. If the +# endpoint ever goes away, drag the video into a comment box on github.com +# instead and copy the URL that GitHub inserts. +REPOSITORY_ID=$(gh api "repos/$REPO" --jq .id) + +RESPONSE=$(curl --silent --show-error --fail \ + --request POST \ + --header "Authorization: Bearer $(gh auth token)" \ + --header "Accept: application/json" \ + --header "Content-Type: video/mp4" \ + --data-binary "@$OUTPUT" \ + "https://uploads.github.com/user-attachments/assets?name=$NAME.mp4&content_type=video%2Fmp4&repository_id=$REPOSITORY_ID") + +URL=$(echo "$RESPONSE" | sed -e 's/.*"url":"//' -e 's/".*//') + +if [ -z "$URL" ] +then + echo "Could not read an attachment URL out of GitHub's response:" + echo "$RESPONSE" + exit 1 +fi + +echo "Demo recorded to $OUTPUT" +echo +echo "Embed it with:" +echo "" diff --git a/demo/settings.tape b/demo/settings.tape new file mode 100644 index 000000000..0ad961250 --- /dev/null +++ b/demo/settings.tape @@ -0,0 +1,25 @@ +# Terminal settings shared by all demo recordings. The tape that +# demo/record_demo.sh generates for a demo sources this file. + +# Use the non-Mono Nerd Font variant; the Mono variant renders the icons too +# small. +Set FontFamily "SauceCodePro NF" +Set FontSize 24 +Set LineHeight 1.0 +Set Padding 20 + +# vhs sizes the terminal in pixels rather than in cells, so these two numbers +# are what produce a 120x35 grid at the font settings above. If you change the +# font, the font size or the padding, record a tape that runs `stty size` and +# adjust them until the grid is 120x35 again. +Set Width 1866 +Set Height 1140 + +# There is no frame rate setting here because vhs ignores `Set Framerate` when +# it writes a video; it captures at 25 fps either way. + +# The frame around the focused view is bold green, and xterm.js draws bold text +# in the bright variant of a colour, so brightGreen is the one those frames end +# up using. + +Set Theme { "background": "#1d1d1d", "foreground": "#dddad6", "cursor": "#c7c7c7", "selection": "#44475a", "black": "#7a7a7a", "red": "#fc4384", "green": "#3fb950", "yellow": "#ffa727", "blue": "#102895", "magenta": "#c930c7", "cyan": "#00c5c7", "white": "#c7c7c7", "brightBlack": "#676767", "brightRed": "#ff7fac", "brightGreen": "#56d364", "brightYellow": "#ebdf86", "brightBlue": "#6871ff", "brightMagenta": "#ff76ff", "brightCyan": "#5ffdff", "brightWhite": "#fffefe" } diff --git a/docs-master/dev/Demo_Recordings.md b/docs-master/dev/Demo_Recordings.md index 1068e688f..b433d8342 100644 --- a/docs-master/dev/Demo_Recordings.md +++ b/docs-master/dev/Demo_Recordings.md @@ -8,17 +8,18 @@ You'll want to familiarise yourself with how integration tests are written: see Ideally we'd run this whole thing through docker but we haven't got that working. So you will need: ``` -# for recording -npm i -g terminalizer -# for gif compression -npm i -g gifsicle -# for mp4 conversion -brew install ffmpeg +# for recording; vhs drives ttyd and ffmpeg under the hood +brew install ttyd ffmpeg + +# vhs 0.12.0 runs the tape, reports success and writes no video at all +# (https://github.com/charmbracelet/vhs/issues/787), so pin the release +# before it +go install github.com/charmbracelet/vhs@v0.11.0 # font with icons -wget https://github.com/ryanoasis/nerd-fonts/releases/download/v3.0.2/DejaVuSansMono.tar.xz && \ - tar -xf DejaVuSansMono.tar.xz -C /usr/local/share/fonts && \ - rm DejaVuSansMono.tar.xz +wget https://github.com/ryanoasis/nerd-fonts/releases/download/v3.0.2/SourceCodePro.tar.xz && \ + tar -xf SourceCodePro.tar.xz -C ~/Library/Fonts && \ + rm SourceCodePro.tar.xz ``` ## Creating a demo @@ -49,34 +50,40 @@ The scripts and demo definitions live in the code branches but the output lives git worktree add .worktrees/assets assets ``` -Outputs will be stored in `.worktrees/assets/demos/`. We'll store three separate things: -* the yaml of the recording -* the original gif -* either the compressed gif or the mp4 depending on the output you chose (see below) +The mp4 of the recording will be stored in `.worktrees/assets/demo/`. ### Recording the demo Once you're happy with your demo you can record it using: ```sh -scripts/record_demo.sh [gif|mp4] +scripts/record_demo.sh # e.g. -scripts/record_demo.sh gif pkg/integration/tests/demo/interactive_rebase.go +scripts/record_demo.sh pkg/integration/tests/demo/interactive_rebase.go ``` -~~The gif format is for use in the first video of the readme (it has a larger size but has auto-play and looping)~~ -~~The mp4 format is for everything else (no looping, requires clicking, but smaller size).~~ - -Turns out that you can't store mp4s in a repo and link them from a README so we're gonna just use gifs across the board for now. +The terminal size, font and colours live in `demo/settings.tape`, which the +script sources into the tape it generates for the demo. ### Including demos in README/docs -If you've followed the above steps you'll end up with your output in your assets worktree. +Recording a demo does two things with the mp4: it writes it to your assets +worktree, and it uploads a copy to GitHub's attachment store. The script then +prints the tag to embed: -Within that worktree, stage all three output files and raise a PR against the assets branch. - -Then back in the code branch, in the doc, you can embed the recording like so: -```md -![Nuke working tree](../assets/demo/interactive_rebase-compressed.gif) +```html + ``` -This means we can update assets without needing to update the docs that embed them. +GitHub plays a video in a README only when it is served from its own attachment +store. If you commit a video to the assets branch and link it the way we link +the images, GitHub drops the whole `