Skip to content

Instantly share code, notes, and snippets.

@hillkim
Forked from stenuto/hls.sh
Created November 8, 2024 18:07
Show Gist options
  • Save hillkim/3608c9bb2c5264e06abc5a0441d73ca2 to your computer and use it in GitHub Desktop.
Save hillkim/3608c9bb2c5264e06abc5a0441d73ca2 to your computer and use it in GitHub Desktop.
HLS ffmpeg script
#!/bin/bash
# Function to display usage information
usage() {
echo "Usage: $0 /path/to/input.mp4 [ /path/to/output_directory ]"
exit 1
}
# Check if at least one argument (input file) is provided
if [ $# -lt 1 ]; then
usage
fi
# Assign input file and output directory from arguments
INPUT_FILE="$1"
# Check if input file exists
if [ ! -f "$INPUT_FILE" ]; then
echo "Error: Input file '$INPUT_FILE' does not exist."
exit 1
fi
# Check for required commands
for cmd in ffmpeg ffprobe; do
if ! command -v $cmd &> /dev/null; then
echo "Error: $cmd is not installed or not in PATH."
exit 1
fi
done
# Get the base filename without extension
FILENAME=$(basename "$INPUT_FILE")
BASENAME="${FILENAME%.*}"
# Determine input directory
INPUT_DIR=$(dirname "$INPUT_FILE")
# Assign output directory: second argument or default to current directory
if [ $# -ge 2 ]; then
OUTPUT_BASE_DIR="$2"
# Create the output base directory if it doesn't exist
mkdir -p "$OUTPUT_BASE_DIR"
else
OUTPUT_BASE_DIR="$PWD"
fi
# Create specific output directory within the base output directory using the base filename
OUTPUT_DIR="$OUTPUT_BASE_DIR/$BASENAME"
# Remove existing output directory if it exists
if [ -d "$OUTPUT_DIR" ]; then
echo "Output directory '$OUTPUT_DIR' already exists. Removing it to avoid unused .ts files."
rm -rf "$OUTPUT_DIR"
fi
mkdir -p "$OUTPUT_DIR"
# Get the frame rate of the input video
FRAME_RATE=$(ffprobe -v 0 -of default=noprint_wrappers=1:nokey=1 \
-select_streams v:0 -show_entries stream=avg_frame_rate "$INPUT_FILE")
# Convert frame rate to a number
IFS='/' read -r num denom <<< "$FRAME_RATE"
if [ -z "$denom" ] || [ "$denom" -eq 0 ]; then
denom=1
fi
FRAME_RATE=$(echo "scale=2; $num/$denom" | bc)
FRAME_RATE=${FRAME_RATE%.*}
# Calculate GOP size (number of frames per 4 seconds)
GOP_SIZE=$((FRAME_RATE * 4))
# Define resolutions, bitrates, and output names
RESOLUTIONS=("1280x720" "1920x1080" "3840x2160")
BITRATES=("1200k" "2500k" "8000k")
OUTPUTS=("720p" "1080p" "2160p")
PLAYLISTS=()
# Loop over the variants
for i in "${!RESOLUTIONS[@]}"; do
RES="${RESOLUTIONS[$i]}"
BITRATE="${BITRATES[$i]}"
OUTPUT_NAME="${OUTPUTS[$i]}"
PLAYLIST="${OUTPUT_NAME}.m3u8"
PLAYLISTS+=("$PLAYLIST")
# Set profile and level based on resolution
if [ "$OUTPUT_NAME" == "2160p" ]; then
PROFILE="high"
LEVEL="5.1"
elif [ "$OUTPUT_NAME" == "1080p" ]; then
PROFILE="high"
LEVEL="4.2"
else
PROFILE="main"
LEVEL="3.1"
fi
echo "Processing $OUTPUT_NAME..."
if ! ffmpeg -y -i "$INPUT_FILE" \
-c:v libx264 -preset veryfast -profile:v "$PROFILE" -level:v "$LEVEL" -b:v "$BITRATE" -s "$RES" \
-c:a aac -b:a 128k -ac 2 \
-g $GOP_SIZE -keyint_min $GOP_SIZE -sc_threshold 0 \
-force_key_frames "expr:gte(t,n_forced*4)" \
-hls_time 4 -hls_list_size 0 -hls_flags independent_segments \
-hls_segment_filename "$OUTPUT_DIR/${OUTPUT_NAME}_%03d.ts" \
"$OUTPUT_DIR/$PLAYLIST"; then
echo "Error: Failed to process $OUTPUT_NAME."
exit 1
fi
done
# Generate master playlist
MASTER_PLAYLIST="$OUTPUT_DIR/playlist.m3u8"
echo "Generating master playlist..."
{
echo "#EXTM3U"
echo "#EXT-X-VERSION:3"
for i in "${!RESOLUTIONS[@]}"; do
RESOLUTION="${RESOLUTIONS[$i]}"
OUTPUT_NAME="${OUTPUTS[$i]}"
PLAYLIST="${OUTPUTS[$i]}.m3u8"
BITRATE="${BITRATES[$i]}"
BANDWIDTH=$(( ${BITRATE%k} * 1000 + 128000 )) # Video bitrate + audio bitrate in bits per second
echo ""
echo "#EXT-X-STREAM-INF:BANDWIDTH=$BANDWIDTH,RESOLUTION=$RESOLUTION"
echo "$PLAYLIST"
done
} > "$MASTER_PLAYLIST"
echo "Processing completed successfully."
echo "Output directory: $OUTPUT_DIR"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment