Skip to content

Instantly share code, notes, and snippets.

@jmatthewturner
Last active April 5, 2023 00:03
Show Gist options
  • Save jmatthewturner/6b7034d13f4027f73c7982ecadf006ec to your computer and use it in GitHub Desktop.
Save jmatthewturner/6b7034d13f4027f73c7982ecadf006ec to your computer and use it in GitHub Desktop.
Bash Script for using FFMPEG to Transcode Video for DaVinci Resolve or Lightworks
#!/bin/bash
# This script uses ffmpeg to transcode video files into one of a few formats suitable for
# editing in either DaVinci Resolve or Lightworks on a Linux machine. It's purpose is to
# simplify my life and relieve me from having to look up and enter the same very long
# command over and over.
#
# If run with argument "all", the script will prompt for a directory containing video files, and
# transcode all video files in the directory into the selected format. If run with a file as an
# argument, it will transcode only that file, and will provide the additional option of
# selecting a specific range to encode.
#
# It uses a modified version of Dave Miller's excellent ask.sh script, which can be found here:
# https://gist.github.com/davejamesmiller/1965569
#
# To modify ask.sh, simply change line 23 to read
# echo -n -e "$1 [$prompt] "
#
# (Adding the -e flag enables support for escape sequences, which allows
# the colored text formatting.)
#
# It also uses my own colors.sh script, which defines a list of colors
# for text formatting:
# https://gist.github.com/jmatthewturner/2aeee120aa112fd51b000e13579c2654
#
# To run, just make sure that transcode.sh (this script), ask.sh and colors.sh
# are in the same directory. (Everything is done with absolute paths, so it doesn't
# matter where they are.)
#
# Written by jmatthewturner
# https://github.com/jmatthewturner
# https://www.youtube.com/jmatthewturner/
#
# https://gist.github.com/jmatthewturner/6b7034d13f4027f73c7982ecadf006ec
#
. ~/Scripts/ask.sh # https://gist.github.com/davejamesmiller/1965569
. ~/Scripts/colors.sh # https://gist.github.com/jmatthewturner/2aeee120aa112fd51b000e13579c2654
################ DEFINITIONS ###################
# CONSTANTS
#
FILE_TYPES='*.MTS *.mov *.mp4 *.mkv *.avi *.m4v' # Types of video files to transcode.
DEFAULT_TRANSCODE_DIRECTORY="${HOME}/Dragon_of_the_Black_Pool/" # Default directory for transcoded
# files. You might want to change
# this to your media drive.
# FFMPEG constants. These are the ffmpeg options that do not change in response to user input.
#
# OPTIONS:
# -loglevel quiet = suppress all output
# -acodec pcm_16le = audio codec: 16bit PCM wave
# -ar 48000 = audio sample rate: 48k
# -r 24000/1001 = framerate: 23.976
OPTIONS=' -loglevel quiet -acodec pcm_s16le -ar 48000 -r 24000/1001 '
# The PADDING option keeps the input video the same size, but adds blue padding to fill out
# a 1920x1080 frame. DNxHD requires this option for certain video sizes, so it is forced for DNxHD.
PADDING=' -vf pad=1920:1080:(ow-iw)/2:(oh-ih)/2:color=blue '
# Variable definitions
#
file_name="${1}" # Full path and name of file to be transcoded.
input_file_name=$( basename "${1}" ) # Name of the file to be transcoded.
input_path_name=$( dirname "${1}" ) # Path of the file to be transcoded.
current_dir="$( pwd )" # Current working directory.
source_directory="" # Source directory for input files.
task="" # Are we transcoding one file, or all video files in a directory?
padding_option="" # User's choice of blue padding. Default is none.
pixel_format="" # Pixel format is determined based on codec choice.
################# FUNCTIONS ################
explain_usage()
{
echo
echo -e ${LIGHTYELLOW}"Usage:${RESET}"
echo
echo "./transcode.sh = View this message."
echo "./transcode.sh all = transcode all vdeo files in current directory"
echo "./transcode.sh [filename] = transcode a section of [filename]"
echo
echo -e "${LIGHTYELLOW}Known issues:${RESET} Transcoding from ProRes to DNxHD may fail."
echo
exit 1
}
# Determine whether we're transcoding an entire directory or just one file.
#
determine_task()
{
if [[ ${file_name} == "" ]]
then
explain_usage
elif [[ ${file_name} == 'all' ]]
then
task="transcode_all"
elif [[ -f "${file_name}" ]]
then
task="transcode_one"
else
echo
echo "There is no file ${file_name}."
explain_usage
fi
}
# Prompt user for target directory to store transcoded files.
#
prompt_for_target_dir()
{
echo
echo -e "${LIGHTYELLOW}Target directory:${RESET}"
if [[ ${task} == "transcode_all" ]]
then
read -e -i "${source_directory}" target_dir
elif [[ ${task} == "transcode_one" ]]
then
read -e -i "${input_path_name}" target_dir
else exit 1
fi
}
# Create the target directory if it does not exist.
#
create_target_dir()
{
if [[ ! -e "${target_dir}" ]]
then
mkdir ${target_dir}
fi
}
# Prompt for a prefix to add to the file name(s).
#
prompt_for_prefix()
{
echo -e "\n${LIGHTYELLOW}Prefix: (leave blank if none):${RESET}"
read prefix
}
prompt_for_padding()
{
echo -e "\nSelecting the padding option will output a 1920x1080 file, regardless"
echo -e "of input file size. The video will reamin the same size, and a blue"
echo -e "padding will be added to fill out the remaining pixels of the 1920x1080 frame."
echo -e "\nDNxHD will have the padding option selected regardless of your choice.\n"
if ask "${LIGHTYELLOW}Select padding option?${RESET}" N
then
padding_option="${PADDING}"
fi
}
# Prompt user for codec.
#
prompt_for_codec()
{
echo -e "\n${LIGHTYELLOW}Export Codec?${RESET}"
echo -e "1: DNxHD 36Mbps"
echo -e "2: ProRes Proxy (45Mbps 422)"
echo -e "3: ProRes LT (102 Mbps 422)"
echo -e "4: ProRes Standard (147 Mbps 422)"
echo -e "5: ProRes HQ (220 Mbps 422)"
echo -e "6: ProRes 4444 (330 Mbps 4444)"
echo -e "7: ProRes 4444XQ (500 Mbps 4444)"
echo -e "8: H.264"
echo -e "9: H.265 / HEVC"
echo
echo -e -n "${LIGHTYELLOW}>${RESET}"
read codec_choice
# Set some options based on codec_choice.
case ${codec_choice} in
"1")
codec=' -vcodec dnxhd -b:v 36M -vf pad=1920:1080:(ow-iw)/2:(oh-ih)/2:color=blue '
codec_formatted='DNxHD-36Mbs-23.976'
pixel_format=' -pix_fmt yuv422p '
;;
"2")
codec=' -vcodec prores_ks -profile:v 0 '
codec_formatted='ProRes-Proxy-23.976'
pixel_format=' -pix_fmt yuv422p10le '
;;
"3")
codec=' -vcodec prores_ks -profile:v 1 '
codec_formatted='ProRes-LT-23.976'
pixel_format=' -pix_fmt yuv422p10le '
;;
"4")
codec=' -vcodec prores_ks -profile:v 2 '
codec_formatted='ProRes-Standard-23.976'
pixel_format=' -pix_fmt yuv422p10le '
;;
"5")
codec=' -vcodec prores_ks -profile:v 3 '
codec_formatted='ProRes-HQ-23.976'
pixel_format=' -pix_fmt yuv422p10le '
;;
"6")
codec=' -vcodec prores_ks -profile:v 4 '
codec_formatted='ProRes-4444-23.976'
pixel_format=' -pix_fmt yuv422p10le '
;;
"7")
codec=' -vcodec prores_ks -profile:v 5 '
codec_formatted='ProRes-4444HQ-23.976'
pixel_format=' -pix_fmt yuv422p10le '
;;
"8")
codec=' -vcodec h264 '
codec_formatted='H.264-23.976'
;;
"9")
codec=' -vcodec libx265 '
codec_formatted='H.265-HEVC-(libx)-23.976'
;;
*)
echo -e "\nPlease choose 1-9."
echo
prompt_for_codec # Poor man's while loop.
;;
esac
}
# Prompt user for source directory only if 'transcode all' was used.
#
prompt_for_source_directory()
{
if [[ ${task} == "transcode_all" ]]
then
echo -e "\n${LIGHTYELLOW}Source directory for video files:${RESET}"
read -e -i ${DEFAULT_TRANSCODE_DIRECTORY} source_directory
fi
}
# Transcode all video files in a supplied directory. Identifies files that match the
# types stored in FILE_TYPES.
#
# I'm just going to say it: I couldn't figure out how to get a directory listing
# directly into an array. My workaround is to put the directory listing into a temp file
# file first, and then move it from the temp file into an array. I'm not proud.
#
transcode_all()
{
tmp_video_list=$( mktemp ) || exit 1 # Temporary file to store identified video files.
# If mktemp fails for any reason, exit with an error.
local -a video_files # Array to store identified video files.
# For loop identifies all the files in the source directory that match FILE_TYPES
# and stores them in tmp_video_list.
for i in ${FILE_TYPES}
do
ls 2> /dev/null 1>> "${tmp_video_list}" "${source_directory}"${i}
done
# While loop reads tmp_video_list and adds each line to array variable video_files[].
while IFS= read -r line
do
video_files+=("${line}")
done < "${tmp_video_list}"
rm ${tmp_video_list} # Remove the temp file.
# Display the list of files to be transcoded for the user.
echo -e "\n${LIGHTYELLOW}Found ${#video_files[@]} files:${RESET}"
for ((i = 0; i < ${#video_files[@]}; i++))
do
echo -e ${LIGHTYELLOW}$((i+1)).${RESET}$( basename "${video_files[$i]}" )
done
echo
# Display a final confirmation of the target directory.
echo -e "${LIGHTYELLOW}Transcoding to:${RESET}"
echo -e "${target_dir}\n"
# Transcode each file contained in array variable video_files[].
if ask "${LIGHTYELLOW}Proceed?${RESET}" Y
then
echo
for ((i = 0; i < ${#video_files[@]}; i++))
do
# Assign input_file-name for code readability.
input_file_name=$( basename "${video_files[$i]}" )
# Display the current file for the user.
echo -e "${LIGHTYELLOW}Transcoding ${RESET}${input_file_name}..."
# Transcode the current file.
ffmpeg -i "${video_files[$i]}" ${codec} ${OPTIONS} ${padding_option} \
"${target_dir}${prefix}-${input_file_name%.*}-${codec_formatted}-PCM16b48k.mov"
done
else echo -e "Exiting.\n"
fi
}
# Transcode a single file provided as an argument to the script.
# Prompt for optional filename prefix, start time and duration to encode.
#
transcode_one()
{
echo -e "\n${LIGHTYELLOW}Transcoding${RESET}"
echo -e "${LIGHTRED}${input_file_name}${RESET}"
echo -e "${LIGHTYELLOW}to${RESET}"
echo -e "${input_path_name}"
# Prompt user for encoding start time.
echo -e -n "\n${LIGHTYELLOW}Enter start time in the format HH:MM:SS "
echo -e "(leave empty for beginning):${RESET}"
read start
# Check if start value is empty. If not, set the start option.
if [[ ! -z ${start} ]]
then
start=" -ss ${start} "
fi
# Prompt user for encoding length.
echo -e -n "${LIGHTYELLOW}Enter encode length in the format HH:MM:SS "
echo -e "(leave empty for entire video):${RESET}"
read length
# Check if length value is empty. If not, set the length option.
if [[ ! -z ${length} ]]
then
length=" -t ${length} "
fi
# Display a message for the user and transcode the file.
echo -e "${LIGHTYELLOW}Transcoding${RESET} ${LIGHTRED}${input_file_name}${LIGHTYELLOW}...${RESET}"
ffmpeg ${start} ${length} -i "${file_name}" \
${codec} ${OPTIONS} ${pixel_format} ${padding_option} \
"${target_dir}/${prefix}-${input_file_name%.*}-${codec_formatted}-PCM16b48k.mov"
echo -e -n "\n${target_dir}/${LIGHTRED}${prefix}${input_file_name%.*}-${codec_formatted}-"
echo -e "PCM16b48k.mov${LIGHTYELLOW} done.${RESET}"
}
############## MAIN ROUTINE ###############
# Configure some options.
#
determine_task # Use command line argument to determine which task to execute.
prompt_for_codec # Prompt user for codec.
prompt_for_prefix # Prompt user for prefix for transcoded files.
prompt_for_padding # Ask user if padding is desired.
prompt_for_source_directory # Prompt user for source directory if transcode all is used.
prompt_for_target_dir # Prompt user for destination directory.
create_target_dir # Confirm destination directory exists, create if necessary.
# Execute transcoding job.
#
${task} # Executes either transcode_one() or transcode_all().
exit 0 # Exit successfully.
#
# Written by jmatthewturner
# https://github.com/jmatthewturner
# https://www.youtube.com/jmatthewturner/
#
# https://gist.github.com/jmatthewturner/6b7034d13f4027f73c7982ecadf006ec
#
@niraami
Copy link

niraami commented Jul 11, 2022

ask.sh doesn't exist anymore, got a local copy you can put up as a new gist?

@FruitCo
Copy link

FruitCo commented Apr 5, 2023

Ditto

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