Last active
April 5, 2023 00:03
-
-
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
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 | |
# 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 | |
# |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
ask.sh
doesn't exist anymore, got a local copy you can put up as a new gist?