Skip to content

Instantly share code, notes, and snippets.

@tlan16
Last active April 15, 2022 09:58
Show Gist options
  • Save tlan16/50017634ebfa3b643f774a41cb17801e to your computer and use it in GitHub Desktop.
Save tlan16/50017634ebfa3b643f774a41cb17801e to your computer and use it in GitHub Desktop.
ffmpeg convert pipeline
#!/usr/bin/env bash
# Usage Example: find ./*.{mp4,flv} -type f | parallel -j 2 --keep-order --line-buffer ./convert_pipe.sh {} {.}.mkv
# Please use git bash on windows
set -e
# Shell colors
NC='\e[0m' # No Color
DRY_RUN=0
INPUT=""
OUTPUT=""
SKIP_HEVC=0
# START: Parse arguments
for i in "$@"; do
case $i in
-d | --dry-run)
DRY_RUN=1
;;
-i=*| --input=*)
INPUT="${i#*=}"
;;
-o=*| --output=*)
OUTPUT="${i#*=}"
;;
-s| --skip-hevc)
SKIP_HEVC=1
;;
esac
done
# END: parse arguments
print_error() {
local red='\e[31m'
echo -e "${red}$1${NC}"
}
print_info() {
local green='\e[32m'
echo -e "${green}$1${NC}"
}
ask_to_exit() {
read -n 1 -s -r -p "Press any key to exit."
exit 1
}
detect_windows() {
local uname
uname=$(which uname) 2>/dev/null
if [ "$uname" = "" ]; then
print_error "${RED}Unable to detect system type. If you are on Windows, please use git bash.${NC}"
ask_to_exit
fi
}
is_hevc() {
local codec
codec=$(ffprobe -v error -select_streams v:0 -show_entries stream=codec_name -of default=nokey=1:noprint_wrappers=1 "${1}")
if [ "${codec}" = "hevc" ]; then
echo 1
else
echo 0
fi
}
check_file_exist() {
if [ -z "$1" ]; then
print_error "Input file cannot be empty."
ask_to_exit
fi
if [ ! -f "$1" ]; then
print_error "File $1 does not exist."
ask_to_exit
fi
}
get_extension() {
local ext="${1##*.}"
if [ -z "$ext" ]; then
print_error "Unable to get file extension from $1"
ask_to_exit
fi
echo "$ext"
}
compute_temp_file() {
local path="$1"
local extension random_string
extension="$(get_extension "$path")"
# shellcheck disable=SC2002
random_string="$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 16 | head -n 1)"
echo "/tmp/${random_string}.${extension}"
}
get_duration() {
ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 -sexagesimal "$1"
}
get_codec_type() {
local type
type="$(ffprobe -loglevel error -show_entries stream=codec_type -of default=nw=1 "$1")"
if [ "codec_type=video" = "$(echo "$type" | grep video)" ]; then
echo "video"
elif [ "codec_type=audio" = "$(echo "$type" | grep audio)" ]; then
echo "audio"
else
print_error "Unable to detect media type of $1"
ask_to_exit
fi
}
convert() {
local input_file="$1"
local output_file="$2"
local default_video_params
local default_audio_params
local video_params=""
local audio_params=""
local command=""
local type
default_video_params="-c:v hevc_nvenc"
default_video_params="${default_video_params} -rc vbr"
default_video_params="${default_video_params} -preset:v slow"
default_video_params="${default_video_params} -profile:v main10"
default_video_params="${default_video_params} -level:v 6.2"
default_video_params="${default_video_params} -tier:v high"
default_audio_params="-c:a libopus"
default_audio_params="${default_audio_params} -b:a 20k"
default_audio_params="${default_audio_params} -vbr on"
default_audio_params="${default_audio_params} -cutoff 8000"
default_audio_params="${default_audio_params} -ac 1"
default_audio_params="${default_audio_params} -apply_phase_inv 0"
default_audio_params="${default_audio_params} -compression_level 10"
default_audio_params="${default_audio_params} -application voip"
default_audio_params="${default_audio_params} -frame_duration 60"
type="$(get_codec_type "$input_file")"
audio_params="$default_audio_params"
if [ "video" = "$type" ]; then
if [ "$(is_hevc "$input_file")" = "1" ]; then
print_info "Input is already in hevc."
video_params="-c:v copy"
else
video_params="$default_video_params"
fi
fi
command="ffmpeg -hide_banner -i \"$input_file\""
command="${command} ${audio_params} ${video_params} -y $output_file"
if [ "0" = "$DRY_RUN" ]; then
print_info "Start: $command"
eval "$command"
else
print_info "Dry run: $command"
fi
}
main() {
local input_file output_file temp_file command
input_file="$1"
check_file_exist "$input_file"
print_info "Input: $input_file"
print_info "Duration: $(get_duration "$input_file")"
if [ -z "$2" ]; then
output_file="$input_file"
else
output_file="$2"
fi
print_info "Output: $output_file"
temp_file=$(compute_temp_file "$output_file")
print_info "Temp file: $temp_file"
convert "$input_file" "$temp_file"
command="mv \"$temp_file\" \"$output_file\""
if [ "0" = "$DRY_RUN" ]; then
print_info "$command"
eval "$command"
else
print_info "Dry run: $command"
fi
}
main "$INPUT" "$OUTPUT"
@tlan16
Copy link
Author

tlan16 commented Mar 2, 2022

Mac version:

#!/bin/sh

# Usage Example: 
#   find ./*.{mp4,flv} -type f | parallel -j 2 --keep-order --line-buffer ./convert_pipe.sh {} {.}.mkv
#   ./convert_pipe.sh input.mp4 output.mp4

set -e

# Shell colors
RED='\033[0;31m'
GREEN='\033[32m'
NC='\033[0m' # No Color

echo "${GREEN}Input: $(realpath "$1")"
echo "${GREEN}Output: $(realpath "$2")"

INPUT_ENV=`ffprobe -v error -select_streams v:0 -show_entries stream=codec_name -of default=nokey=1:noprint_wrappers=1 "${1}"`
OUTPUT_EXT="${2##*.}"
TEMP_FILE=/tmp/"$(cat /dev/urandom | gtr -dc 'a-zA-Z0-9' | fold -w 16 | head -n 1)"."${OUTPUT_EXT}"

# validation
if [ "${OUTPUT_EXT}" = "" ]; then
  echo "${RED}Invalid output of ${2}, skipping ...${NC}"
  return 1
fi

if [ "${INPUT_ENV}" = "hevc" ]; then
  echo "${RED}Already in ${INPUT_ENV}, skipping ...${NC}"
  return 1
fi

echo "${GREEN}Start converting "$(basename "$1")" to temp file: ${TEMP_FILE}"
echo "${NC}"

ffmpeg -hide_banner \
  -i "${1}" \
  -c:v hevc_videotoolbox \
  -c:a libopus \
  -b:a 20k \
  -y \
  "${TEMP_FILE}"

mv "${TEMP_FILE}" "${2}"

if [ "${1}" != "${2}" ]; then
  rm -f "${1}"
  echo "${GREEN}Deleted source file" "$(realpath "$1")"
fi

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