Created
November 7, 2024 16:58
-
Star
(208)
You must be signed in to star a gist -
Fork
(85)
You must be signed in to fork a gist
-
-
Save stenuto/9ff19ce89f07c7419a8d0976736ebe12 to your computer and use it in GitHub Desktop.
HLS ffmpeg script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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" |
Thanks!
Thank you very much for sharing, this is really awesome and helpful .
@stenuto thanks for the script :)
I believe the framerate part can be done a bit more simply with
FRAME_RATE=$(ffprobe -v 0 -select_streams v:0 -show_entries stream=avg_frame_rate \
-of csv=p=0 "$INPUT_FILE" | awk -F'/' '{printf "%.0f", ($1 / ($2 ? $2 : 1))}')
The of
flag does what you need in your case.
Btw, the awk part is only needed if the denom is zero, if you are sure it doesn't happen, you could just pipe this to bc
Cool script, but no support for 480? :(
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thank you for sharing. Awesome work.