Skip to content

Instantly share code, notes, and snippets.

@deseven
Last active October 18, 2025 18:10
Show Gist options
  • Select an option

  • Save deseven/f3dc4ae7141babeeac860a4305c34122 to your computer and use it in GitHub Desktop.

Select an option

Save deseven/f3dc4ae7141babeeac860a4305c34122 to your computer and use it in GitHub Desktop.
a wrapper for quick web-friendly video convertions using ffmpeg, intended for macOS
#!/bin/bash
# a wrapper for quick web-friendly video convertions using ffmpeg
# mostly intended to be used on macOS (hence videotoolbox hardware acceleration)
# info & updates: https://gist.github.com/deseven/f3dc4ae7141babeeac860a4305c34122
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
PURPLE='\033[0;35m'
CYAN='\033[0;36m'
NC='\033[0m'
DEFAULT_QUALITY_H264=23
DEFAULT_QUALITY_H265=28
DEFAULT_QUALITY_HARDWARE=52
DEFAULT_CODEC="h264"
DEFAULT_HALF_RES="no"
DEFAULT_SILENT="no"
DEFAULT_KEEP_SOURCE="no"
DEFAULT_HARDWARE="no"
DEFAULT_EXTRA_ARGS=""
DEFAULT_SUBTITLES=""
DEFAULT_SPEED_FACTOR="1.0"
while getopts hkq:c:HsA:S:f: flag; do
case "$flag" in
h) half_res=yes ;;
k) keep_source=yes ;;
s) silent=yes ;;
q) quality=$OPTARG ;;
c) codec=$OPTARG ;;
H) hardware=yes ;;
A) extra_args=$OPTARG ;;
S) subtitles=$OPTARG ;;
f) speed_factor=$OPTARG ;;
esac
done
shift $((OPTIND - 1))
if [ -z "$1" ]; then
echo -e "${RED}syntax: $0 file_path [file_path...]${NC}"
echo -e "${BLUE}-h ${PURPLE}to use half resolution (default no)"
echo -e "${BLUE}-k ${PURPLE}to keep the source file (default no)"
echo -e "${BLUE}-s ${PURPLE}to remove the audio stream (default no)"
echo -e "${BLUE}-q ${PURPLE}to set the quality (default $DEFAULT_QUALITY_H264 for h264, $DEFAULT_QUALITY_H265 for h265, $DEFAULT_QUALITY_HARDWARE for hardware)"
echo -e "${BLUE}-c ${PURPLE}to set the codec (default $DEFAULT_CODEC, options: h264, h265)"
echo -e "${BLUE}-H ${PURPLE}to enable hardware encoding (default no)"
echo -e "${BLUE}-A ${PURPLE}to pass additional ffmpeg arguments (default none)"
echo -e "${BLUE}-S ${PURPLE}to burn in subtitles (provide path to subtitles file)"
echo -e "${BLUE}-f ${PURPLE}to set speed factor (default $DEFAULT_SPEED_FACTOR, e.g., 0.5 for half speed, 2.0 for double speed)${NC}"
exit 1
fi
if [ -z "$half_res" ]; then
half_res=$DEFAULT_HALF_RES
fi
if [ -z "$silent" ]; then
silent=$DEFAULT_SILENT
fi
if [ -z "$keep_source" ]; then
keep_source=$DEFAULT_KEEP_SOURCE
fi
if [ -z "$codec" ]; then
codec=$DEFAULT_CODEC
fi
if [ -z "$hardware" ]; then
hardware=$DEFAULT_HARDWARE
fi
if [ -z "$extra_args" ]; then
extra_args=$DEFAULT_EXTRA_ARGS
fi
if [ -z "$subtitles" ]; then
subtitles=$DEFAULT_SUBTITLES
fi
if [ -z "$speed_factor" ]; then
speed_factor=$DEFAULT_SPEED_FACTOR
fi
if [ -z "$quality" ]; then
if [ "$hardware" == "yes" ]; then
quality=$DEFAULT_QUALITY_HARDWARE
else
if [ "$codec" == "h265" ]; then
quality=$DEFAULT_QUALITY_H265
else
quality=$DEFAULT_QUALITY_H264
fi
fi
fi
if [ "$silent" == "yes" ]; then
audio_arg="-an"
else
if [ "$speed_factor" != "1.0" ]; then
audio_arg="-af atempo=$speed_factor -c:a aac -b:a 192k -ac 2"
else
audio_arg="-c:a aac -b:a 192k -ac 2"
fi
fi
# Build video filter chain
video_filters=""
if [ "$half_res" == "yes" ]; then
video_filters="scale='bitand(oh*dar,65534)':'bitand(ih/2,65534)',setsar=1"
fi
if [ "$speed_factor" != "1.0" ]; then
if [ -n "$video_filters" ]; then
video_filters="$video_filters,setpts=PTS/$speed_factor"
else
video_filters="setpts=PTS/$speed_factor"
fi
fi
if [ -n "$video_filters" ]; then
res="-vf $video_filters"
else
res=""
fi
if [ -n "$subtitles" ]; then
if [ -n "$video_filters" ]; then
video_filters="$video_filters,subtitles=$subtitles"
res="-vf $video_filters"
else
res="-vf subtitles=$subtitles"
fi
fi
if [ "$hardware" == "yes" ]; then
if [ "$codec" == "h265" ]; then
codec_opts="-c:v hevc_videotoolbox -q:v $quality -pix_fmt yuv420p -movflags faststart -tag:v hvc1"
else
codec_opts="-c:v h264_videotoolbox -q:v $quality -pix_fmt yuv420p -movflags faststart"
fi
else
if [ "$codec" == "h265" ]; then
codec_opts="-c:v libx265 -crf $quality -pix_fmt yuv420p -movflags faststart -tag:v hvc1"
else
codec_opts="-c:v libx264 -crf $quality -profile:v baseline -level 3.0 -pix_fmt yuv420p -movflags faststart"
fi
fi
results=()
for input_file in "$@"; do
name=$(basename -- "$input_file")
ext="${name##*.}"
name="${name%.*}"
dir=$(dirname "$input_file")
output_file="$dir/$name-1.mp4"
counter=1
while [ -f "$output_file" ]; do
counter=$((counter + 1))
output_file="$dir/$name-$counter.mp4"
done
echo -e "${BLUE}file: ${PURPLE}$name.$ext${NC}"
echo -e "${BLUE}half_res: ${PURPLE}$half_res${NC}"
echo -e "${BLUE}silent: ${PURPLE}$silent${NC}"
echo -e "${BLUE}keep_source: ${PURPLE}$keep_source${NC}"
echo -e "${BLUE}quality: ${PURPLE}$quality${NC}"
echo -e "${BLUE}codec: ${PURPLE}$codec${NC}"
echo -e "${BLUE}hardware: ${PURPLE}$hardware${NC}"
echo -e "${BLUE}speed_factor: ${PURPLE}$speed_factor${NC}"
echo -e "${BLUE}extra_args: ${PURPLE}$extra_args${NC}"
echo -e "${BLUE}subtitles: ${PURPLE}$subtitles${NC}"
echo "Running \`ffmpeg -hide_banner -loglevel error -stats -i \"$input_file\" $res $audio_arg $codec_opts $extra_args \"$output_file\"\`"
if nice -n 8 ffmpeg -hide_banner -loglevel error -stats -i "$input_file" $res $audio_arg $codec_opts $extra_args "$output_file"; then
if [ -s "$output_file" ]; then
if [ "$keep_source" == "no" ]; then
rm -f "$input_file"
mv "$output_file" "$dir/$name.mp4"
results+=("${GREEN}SUCCESS: ${CYAN}$dir/$name.mp4${NC}")
else
results+=("${GREEN}SUCCESS: ${CYAN}$output_file${NC}")
fi
else
results+=("${RED}FAILED: ${CYAN}$input_file${NC}")
fi
else
results+=("${RED}FAILED: ${CYAN}$input_file${NC}")
fi
done
echo ""
echo -e "${YELLOW}Conversion Summary:${NC}"
for result in "${results[@]}"; do
echo -e "$result"
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment