Some checks failed
Deploy analytics worker / Deploy analytics worker to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
i18n upload strings / Upload latest strings (push) Failing after 1s
Deploy bemo / Deploy bemo to ${{ (github.ref == 'refs/heads/bemo-production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
Deploy grafana resources / ${{ github.event_name == 'push' && 'Deploy grafana resources' || 'Validate grafana resources (dry run)' }} (push) Failing after 0s
End to end tests (dotcom) / Check for relevant changes (push) Failing after 0s
End to end tests (examples) / Check for relevant changes (push) Failing after 0s
End to end tests (mermaid readability) / Check for relevant changes (push) Failing after 1s
End to end tests (perf) / Check for relevant changes (push) Failing after 0s
Publish editor extensions / Publish editor extensions (push) Failing after 1s
Publish SDK packages / Remove the publish-packages label (push) Has been skipped
Checks / Tests & checks (push) Has been cancelled
Checks / Build all projects (push) Has been cancelled
Deploy templates / Deploy templates (push) Has been cancelled
End to end tests (dotcom) / End to end tests (dotcom) (push) Has been cancelled
End to end tests (mermaid readability) / End to end tests (mermaid readability) (push) Has been cancelled
End to end tests (perf) / End to end tests (perf) (push) Has been cancelled
Deploy .com / Deploy dotcom to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Has been cancelled
End to end tests (examples) / End to end tests (examples) (push) Has been cancelled
Publish SDK packages / Publish SDK packages (push) Has been cancelled
Publish SDK packages / Tell user released version (push) Has been cancelled
Publish SDK packages / Publishes code templates to separate repositories (push) Has been cancelled
Publish SDK packages / Deploys the template demos with the new SDK version (push) Has been cancelled
90 lines
2.9 KiB
Bash
Executable File
90 lines
2.9 KiB
Bash
Executable File
#!/bin/bash
|
|
# make-video.sh — Assemble walkthrough slides + audio into a final MP4.
|
|
#
|
|
# Usage:
|
|
# ./make-video.sh <slide-dir> <output.mp4> [outro-duration]
|
|
#
|
|
# Expects in <slide-dir>:
|
|
# slide-00.png, slide-01.png, ... (one per segment, including outro)
|
|
# audio-00.wav, audio-01.wav, ... (one per narrated segment)
|
|
#
|
|
# The last slide PNG without a matching audio WAV is the silent outro.
|
|
#
|
|
set -euo pipefail
|
|
|
|
SLIDE_DIR="${1:?Usage: make-video.sh <slide-dir> <output.mp4> [outro-duration]}"
|
|
OUTPUT="${2:?Usage: make-video.sh <slide-dir> <output.mp4> [outro-duration]}"
|
|
OUTRO_DUR="${3:-3}"
|
|
|
|
# Resolve relative paths
|
|
[[ "$SLIDE_DIR" != /* ]] && SLIDE_DIR="$(pwd)/$SLIDE_DIR"
|
|
[[ "$OUTPUT" != /* ]] && OUTPUT="$(pwd)/$OUTPUT"
|
|
|
|
mkdir -p "$(dirname "$OUTPUT")"
|
|
|
|
TMPDIR_WORK=$(mktemp -d)
|
|
trap "rm -rf $TMPDIR_WORK" EXIT
|
|
|
|
echo "=== Assembling video ==="
|
|
echo " Slides: $SLIDE_DIR"
|
|
echo " Output: $OUTPUT"
|
|
|
|
# Count slides and audio
|
|
SLIDE_COUNT=$(ls "$SLIDE_DIR"/slide-*.png 2>/dev/null | wc -l | tr -d ' ')
|
|
AUDIO_COUNT=$(ls "$SLIDE_DIR"/audio-*.wav 2>/dev/null | wc -l | tr -d ' ')
|
|
|
|
echo " Found $SLIDE_COUNT slides, $AUDIO_COUNT audio clips"
|
|
echo " Last slide (no audio) = outro (${OUTRO_DUR}s)"
|
|
|
|
# Create per-segment videos
|
|
CONCAT_LIST="$TMPDIR_WORK/concat.txt"
|
|
> "$CONCAT_LIST"
|
|
|
|
for i in $(seq 0 $((SLIDE_COUNT - 1))); do
|
|
NUM=$(printf "%02d" $i)
|
|
SLIDE="$SLIDE_DIR/slide-${NUM}.png"
|
|
AUDIO="$SLIDE_DIR/audio-${NUM}.wav"
|
|
SEGMENT="$TMPDIR_WORK/segment-${NUM}.mp4"
|
|
|
|
if [ -f "$AUDIO" ]; then
|
|
# Get audio duration
|
|
DUR=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$AUDIO")
|
|
echo " segment-${NUM}: slide + audio (${DUR}s)"
|
|
|
|
# Create video segment: static image for duration of audio, with audio
|
|
ffmpeg -y -loop 1 -i "$SLIDE" -i "$AUDIO" \
|
|
-c:v libx264 -tune stillimage -pix_fmt yuv420p \
|
|
-vf "scale=1600:900:force_original_aspect_ratio=decrease,pad=1600:900:(ow-iw)/2:(oh-ih)/2" \
|
|
-c:a aac -b:a 192k -ar 48000 \
|
|
-shortest -movflags +faststart \
|
|
"$SEGMENT" 2>/dev/null
|
|
else
|
|
# Silent outro slide
|
|
echo " segment-${NUM}: silent outro (${OUTRO_DUR}s)"
|
|
ffmpeg -y -loop 1 -i "$SLIDE" -f lavfi -i anullsrc=r=48000:cl=mono \
|
|
-c:v libx264 -tune stillimage -pix_fmt yuv420p \
|
|
-vf "scale=1600:900:force_original_aspect_ratio=decrease,pad=1600:900:(ow-iw)/2:(oh-ih)/2" \
|
|
-c:a aac -b:a 192k -ar 48000 \
|
|
-t "$OUTRO_DUR" -movflags +faststart \
|
|
"$SEGMENT" 2>/dev/null
|
|
fi
|
|
|
|
echo "file '$SEGMENT'" >> "$CONCAT_LIST"
|
|
done
|
|
|
|
# Concatenate all segments
|
|
echo ""
|
|
echo " Concatenating ${SLIDE_COUNT} segments..."
|
|
ffmpeg -y -f concat -safe 0 -i "$CONCAT_LIST" \
|
|
-c copy -movflags +faststart \
|
|
"$OUTPUT" 2>/dev/null
|
|
|
|
# Report
|
|
FINAL_DUR=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$OUTPUT")
|
|
FINAL_SIZE=$(ls -lh "$OUTPUT" | awk '{print $5}')
|
|
echo ""
|
|
echo "=== Done ==="
|
|
echo " Output: $OUTPUT"
|
|
echo " Duration: ${FINAL_DUR}s"
|
|
echo " Size: $FINAL_SIZE"
|