Skip to content

Instantly share code, notes, and snippets.

@FiveBoroughs
Last active January 28, 2026 00:39
Show Gist options
  • Select an option

  • Save FiveBoroughs/b6401ee0df041f66d62dbe3db295ff37 to your computer and use it in GitHub Desktop.

Select an option

Save FiveBoroughs/b6401ee0df041f66d62dbe3db295ff37 to your computer and use it in GitHub Desktop.
Smart HEVC/H.264/MPEG-2 Transcoding
#!/bin/bash
set -euo pipefail
LOG_PREFIX="[ffmpeg-smart]"
# Parse args
AGENT=""
URL=""
while [[ $# -gt 0 ]]; do
if [[ "$1" == "-user_agent" ]]; then
AGENT="$2"
shift 2
elif [[ "$1" == "-i" ]]; then
URL="$2"
shift 2
else
shift
fi
done
# Validate URL
if [[ -z "$URL" ]]; then
echo "$LOG_PREFIX ERROR: No stream URL provided" >&2
exit 1
fi
# Probe stream
PROBE=$(ffprobe -user_agent "$AGENT" -v quiet -print_format json -show_streams "$URL" 2>&1) || {
echo "$LOG_PREFIX ERROR: ffprobe failed - cannot access stream" >&2
exit 1
}
# Parse streams by codec_type
VCODEC=$(echo "$PROBE" | jq -r '.streams[] | select(.codec_type=="video") | .codec_name' | head -n1)
FPS_FRAC=$(echo "$PROBE" | jq -r '.streams[] | select(.codec_type=="video") | .r_frame_rate' | head -n1)
ABITRATE_RAW=$(echo "$PROBE" | jq -r '.streams[] | select(.codec_type=="audio") | .bit_rate // empty' | head -n1)
ACHANNELS=$(echo "$PROBE" | jq -r '.streams[] | select(.codec_type=="audio") | .channels // empty' | head -n1)
# Validate video stream
if [[ -z "$VCODEC" || "$VCODEC" == "null" ]]; then
echo "$LOG_PREFIX ERROR: No video stream found" >&2
exit 1
fi
# Set video codec
if [[ "$VCODEC" == "hevc" ]]; then
VCODEC_OUT="hevc_qsv"
TAG_ARGS="-tag:v hvc1"
elif [[ "$VCODEC" == "h264" ]]; then
VCODEC_OUT="h264_qsv"
TAG_ARGS=""
elif [[ "$VCODEC" == "mpeg2video" ]]; then
VCODEC_OUT="mpeg2_qsv"
TAG_ARGS=""
else
VCODEC_OUT="h264_qsv"
TAG_ARGS=""
fi
# Validate audio bitrate (reject sample rates like 44100/48000)
if [[ -n "$ABITRATE_RAW" ]] && [[ "$ABITRATE_RAW" -ge 60000 ]] && [[ "$ABITRATE_RAW" -le 500000 ]]; then
ABITRATE="$ABITRATE_RAW"
else
ABITRATE="128000"
fi
# Set channel layout based on channel count
CHANNEL_LAYOUT=""
case "$ACHANNELS" in
1)
CHANNEL_LAYOUT="-channel_layout mono"
;;
2)
CHANNEL_LAYOUT="-channel_layout stereo"
;;
6)
CHANNEL_LAYOUT="-channel_layout 5.1"
;;
8)
CHANNEL_LAYOUT="-channel_layout 7.1"
;;
*)
# No audio detected OR unknown channel count - force stereo for compatibility
CHANNEL_LAYOUT="-ac 2 -channel_layout stereo"
ACHANNELS="${ACHANNELS:-0} -> 2 (forced)"
;;
esac
# Bitrates
VBITRATE="8000000"
MAXRATE="10000000"
BUFSIZE="20000000"
# Calculate GOP with rounding
if [[ "$FPS_FRAC" =~ ^([0-9]+)/([0-9]+)$ ]]; then
NUM=${BASH_REMATCH[1]}
DEN=${BASH_REMATCH[2]}
if [[ $DEN -gt 0 ]]; then
GOP=$(( (NUM + DEN/2) / DEN ))
FPS_OUT="$FPS_FRAC"
GOP_WARN=""
else
GOP=50
FPS_OUT="25/1"
GOP_WARN=" (invalid fps denominator)"
fi
else
GOP=50
FPS_OUT="25/1"
GOP_WARN=" (fps parse failed)"
fi
# Single combined log line
echo "$LOG_PREFIX Detected $VCODEC @ $FPS_FRAC -> $VCODEC_OUT GOP=$GOP${GOP_WARN} audio=${ABITRATE}bps ${ACHANNELS}ch" >&2
# Execute ffmpeg
exec ffmpeg \
-user_agent "$AGENT" \
-hwaccel qsv \
-hwaccel_output_format qsv \
-reconnect 1 \
-reconnect_at_eof 1 \
-reconnect_streamed 1 \
-reconnect_delay_max 30 \
-rw_timeout 15000000 \
-fflags +genpts+igndts+discardcorrupt \
-err_detect ignore_err \
-i "$URL" \
-map 0:v:0 \
-map 0:a:0? \
-c:v "$VCODEC_OUT" \
-preset fast \
-b:v "$VBITRATE" \
-maxrate "$MAXRATE" \
-bufsize "$BUFSIZE" \
-g "$GOP" \
-bf 0 \
-look_ahead 0 \
-fps_mode cfr \
-r "$FPS_OUT" \
-async 1 \
$TAG_ARGS \
-c:a aac \
-b:a "$ABITRATE" \
$CHANNEL_LAYOUT \
-af "aresample=async=1" \
-avoid_negative_ts make_zero \
-start_at_zero \
-mpegts_copyts 0 \
-mpegts_flags +pat_pmt_at_frames+resend_headers \
-flush_packets 1 \
-max_muxing_queue_size 4096 \
-f mpegts \
pipe:1
@FiveBoroughs
Copy link
Author

Revision 3

Audio bitrate validation:

Added 60k-500k range check to reject corrupt metadata
Fixes streams with sample_rate (44100/48000) incorrectly set as bit_rate
Falls back to 128k for invalid/missing values

@FiveBoroughs
Copy link
Author

FiveBoroughs commented Jan 20, 2026

Revision 4

ffprobe now uses -user_agent "$AGENT".
Added TAG_ARGS="-tag:v hvc1" for HEVC output (empty for others).
GOP calc now guards against DEN=0 and also derives FPS_OUT (defaults to 25/1 when parsing fails).

ffmpeg changes:
-fflags adds +igndts
audio map is optional now: -map 0:a:0?
forces CFR: -fps_mode cfr -r "$FPS_OUT"
adds audio resample async filter: -af "aresample=async=1"
adds timestamp/TS stabilization: -avoid_negative_ts make_zero -start_at_zero -mpegts_copyts 0
formatted as multi-line for readability

@FiveBoroughs
Copy link
Author

Revision 5

Audio channel normalization:

  • Detects source channel count (mono/stereo/5.1/7.1)
  • Sets explicit -channel_layout for proper encoding
  • Forces stereo for unknown/missing channel configs
  • Logs channel count: audio=128000bps 6ch

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment