Skip to content

Instantly share code, notes, and snippets.

@birkhofflee
Last active February 26, 2026 17:58
Show Gist options
  • Select an option

  • Save birkhofflee/041634a00e4de4322e1fb8a180abfe01 to your computer and use it in GitHub Desktop.

Select an option

Save birkhofflee/041634a00e4de4322e1fb8a180abfe01 to your computer and use it in GitHub Desktop.
Generate subtitles (transcription) for a long video on macOS

This is a shell script that uses ffmpeg to extract WAV from a video, transcribes it locally with Parakeet on macOS, and postprocesses the subtitles to clean it up.

In total, it took 4m for a ~2h video (~300 MB) on M1 Pro.

Prerequisites

This guide is for power users: requires usage of terminal.

  1. uv is used to run senstella/parakeet-mlx
  2. Nix is used to run the SRT postprocessor
  3. OpenAI API (or compatible ones) credentials

Steps

  1. Put the LLM credentials in the script
  2. Run it ./video2srt.zsh /path/to/video.mp4

simple as that.

#!/bin/zsh
set -euo pipefail
export OPENAI_API_URL=https://openrouter.ai/api/v1
export OPENAI_API_KEY="xxxx"
export OPENAI_MODEL=google/gemini-3-flash-preview
# Color codes for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Temporary directory for intermediate files
TMPDIR=$(mktemp -d)
# Cleanup function
cleanup() {
local exit_code=$?
if [[ -d "$TMPDIR" ]]; then
echo "${YELLOW}Cleaning up temporary files...${NC}"
rm -rf "$TMPDIR"
echo " Removed: $TMPDIR"
fi
if [[ $exit_code -ne 0 ]]; then
echo "${RED}Script failed with exit code $exit_code${NC}"
fi
}
# Set up trap for cleanup
trap cleanup EXIT INT TERM
# Error handler
error_exit() {
echo "${RED}Error: $1${NC}" >&2
exit 1
}
# Check arguments
if [[ $# -ne 1 ]]; then
error_exit "Usage: $0 <input.mp4>"
fi
INPUT_MP4="$1"
# Check if input file exists
if [[ ! -f "$INPUT_MP4" ]]; then
error_exit "Input file does not exist: $INPUT_MP4"
fi
# Check if input is an mp4 file
if [[ ! "$INPUT_MP4" =~ \.mp4$ ]]; then
error_exit "Input file must be an MP4 file: $INPUT_MP4"
fi
# Get basename without extension
BASENAME="${INPUT_MP4:r:t}"
WAV_FILE="$TMPDIR/${BASENAME}.wav"
SRT_FILE="${BASENAME}.srt"
JSON_FILE="${BASENAME}.json"
PROCESSED_SRT="${BASENAME}.processed.srt"
echo "${GREEN}Starting video to subtitle conversion...${NC}"
echo "Input: $INPUT_MP4"
echo ""
# Step 1: Convert MP4 to WAV
echo "${GREEN}[1/3] Converting MP4 to WAV...${NC}"
if ! ffmpeg -i "$INPUT_MP4" -ac 2 -f wav "$WAV_FILE"; then
error_exit "Failed to convert MP4 to WAV"
fi
echo "${GREEN}✓ WAV file created: $WAV_FILE${NC}"
echo ""
# Step 2: Run parakeet-mlx to generate SRT
echo "${GREEN}[2/3] Running parakeet-mlx for transcription...${NC}"
if ! uv tool run parakeet-mlx "$WAV_FILE" --max-words 15; then
error_exit "Failed to run parakeet-mlx"
fi
if [[ ! -f "$SRT_FILE" ]]; then
error_exit "Expected SRT file not found: $SRT_FILE"
fi
echo "${GREEN}✓ SRT file created: $SRT_FILE${NC}"
echo ""
# Step 3: Post-process SRT file with srt-llm-processor
echo "${GREEN}[3/3] Post-processing SRT file with LLM...${NC}"
if ! nix run github:BirkhoffLee/srt-llm-processor -- --file "$SRT_FILE"; then
error_exit "Failed to post-process SRT file"
fi
echo "${GREEN}✓ Processed SRT file created: $PROCESSED_SRT${NC}"
echo ""
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment