Skip to main content

Bash script that uses ffmpeg and qtfaststart to re-encode a directory of avi files to x264 mp4 container with streaming support (fast start).

#!/usr/bin/env bash

##
# encode.sh
#
# Bash script that uses `ffmpeg` and `qtfaststart` to re-encode a directory
# of avi files to x264 mp4 container with streaming support (fast start).
#
# Dependenices
#
#   - ffmpeg (v0.7.15)
#   - qtfaststart (https://github.com/danielgtaylor/qtfaststart)
#
# Usage
#
#   $ ./encode.sh <INPUT_DIR> [OUTPUT_DIR]
#
# Jon LaBelle <jon@tech0.com>
# Tue Jul 30 2013 12:54:10 GMT-0500 (CDT)
##

###############################################
# CONFIG
###############################################

function show_usage
{
  echo -e "\tUsage:\t$PROGNAME <INPUT_DIR> [OUTPUT_DIR]" 1>&2
}

function cleanup
{

  for lf in "$CWD/"*pass.log*
  do
    if [ -f "$lf" ]; then
      rm -rf "$lf"
    fi
  done

  echo ""
  echo "$SUCCESS_COUNT of $SOURCE_FILE_COUNT file(s) encoded."

  exit $1
}

function error_exit
{
  echo "ERROR: ${1:-"Unknown Error"}" 1>&2

  cleanup 1
}

function get_duration
{
  local infile="$1"

  # ffmpeg duration output format: `00:00:00.000`
  #   note: using sed to strip frames (.000+) from output
  $FFMPEG_CMD -i "$infile" 2>&1 | grep Duration | awk '{print $2}' | tr -d , | sed -e 's/\..*//';
}

function encode_file
{
  local infile="$1"
  local outfile="$2"

  $FFMPEG_CMD -loglevel quiet -y \
    -i "$infile" \
    -vcodec libx264 -preset slow -crf 22 -profile baseline \
    -x264opts level=3.0:vbv-maxrate=10000:vbv-bufsize=10000:ref=1 \
    -b 700k -force_fps -acodec libfaac -ab 128k -ac 2 -pass 1 -threads 0 "$outfile" >/dev/null 2>&1;

  # maintain source file's mod date
  # touch -r "$infile" "$outfile"
}

function make_fast_start
{
  local mp4file="$1"

  # note: qtfaststart installed globally so ensure everyone can
  # read file (chmod 0644)
  $QTFASTSTART_CMD "$mp4file" && chmod 0644 "$mp4file"
}

################################################
# Main
################################################

DEBUG=0
PROGNAME=$(basename $0)
PROC_ID=$$
CWD=$(pwd)

INPUT_FILE_EXTENSION='avi'
OUTPUT_FILE_EXTENSION='mp4'

if [ $DEBUG -ne 0 ]; then
  set -x
fi

# check dependencies
FFMPEG_CMD=`which ffmpeg 2>&1`
if [ $? -ne 0 ]; then
  echo "ffmpeg not found. Please install and try again."
  exit 1
fi
QTFASTSTART_CMD=`which qtfaststart 2>&1`
if [ $? -ne 0 ]; then
  echo "qtfaststart not found. Please install and try again."
  exit 1
fi

# input directory must be specfied and exist
if [ -z "$1" ]; then
  show_usage
  exit 1
fi
if [ ! -d "${1}" ]; then
  echo "Input directory not found!"
  show_usage
  exit 1
fi

INPUTDIR="$1"
OUTPUTDIR="$INPUTDIR"

# try to set output dir if specified, with fallback as input dir
if [ ! -z "$2" ]; then
  if [ -d "$2" ]; then
    OUTPUTDIR="$2"
  else
    echo "Output directory not found."
    echo "note: you can leave the output directory (arg2) option empty and encoded items created in input directory (ar1)."
    exit 1
  fi
fi

#
# store a count of files that require encoding
# (-maxdepth 1 = non-recursive search)
# note: using sed to remove whitespace in output
#
SOURCE_FILE_COUNT=`find "$INPUTDIR" -maxdepth 1 -type f -name *.$INPUT_FILE_EXTENSION | wc -l | sed -e 's/^[ \t]*//' 2>&1;`
if [[ $? -ne 0 || $SOURCE_FILE_COUNT -eq 0 ]]; then
  echo "No $INPUT_FILE_EXTENSION files found in input directory."
  exit 1
fi

ATTEMPT_COUNT=0
FAIL_COUNT=0
SUCCESS_COUNT=0

# handle kill signals (ctl+c)
trap cleanup SIGHUP SIGINT SIGTERM

echo""
echo "Encoding ${SOURCE_FILE_COUNT} $INPUT_FILE_EXTENSION file(s) to $OUTPUT_FILE_EXTENSION..."

for src_file in "$INPUTDIR/"*.$INPUT_FILE_EXTENSION; do

  let 'ATTEMPT_COUNT++'

  src_basename=$(basename "$src_file")
  src_duration=$(get_duration "$src_file")

  out_basename=$(echo "$src_basename" | sed -e s/."${INPUT_FILE_EXTENSION}"/."${OUTPUT_FILE_EXTENSION}"/g)
  out_file="$OUTPUTDIR/$out_basename"

  echo "-> (${ATTEMPT_COUNT}/${SOURCE_FILE_COUNT}) source: $src_basename, output: $out_basename"

  encode_file "$src_file" "$out_file"
  out_duration=$(get_duration "$out_file")

  if [ "$src_duration" != "$out_duration" ]; then
    let 'FAIL_COUNT++'

    echo "${ATTEMPT_COUNT}/${SOURCE_FILE_COUNT} encoded, however the output file duration differs from the source."
    echo -e "\t $src_basename (source file): '$src_duration'\n\t $out_basename (output file): '$out_duration'"
    echo "This item will be marked as FAILED."
    echo ""
  else
    let 'SUCCESS_COUNT++'

    make_fast_start "$out_file"
  fi
done

cleanup 0