Skip to content

Instantly share code, notes, and snippets.

@FiveBoroughs
Last active January 20, 2026 00:18
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)
# 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
# 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" >&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" \
-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

FiveBoroughs commented Jan 13, 2026

Revision 2

Robustness:

Added set -euo pipefail - fail fast on errors
Error handling for missing URL, probe failures, no video stream
Stream selection by codec_type (not array position) - handles multi-audio/reordered streams
Explicit stream mapping

Codec Support:

Added MPEG-2 → mpeg2_qsv
Added fallback for unknown codecs → h264_qsv

Math Fix:

GOP calculation uses rounding not truncation (30000/1001 → 30, not 29)

Logging:

Single-line [ffmpeg-smart] prefixed log with probe results
Shows: detected codec, FPS, target encoder, GOP, audio bitrate
Only logs errors/warnings when they occur

Result: Handles edge cases, multi-stream inputs, and all common broadcast codecs while providing debug visibility.

@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

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