Script for cutting videos based on edit list

Solution 1:

For this you need FFmpeg (download a static build) and Bash.

Create a CSV-like list of input and output points, e.g.:

00:01:00,00:02:00
00:03:00,00:03:02
…

You can then run the following Bash script to cut the video into individual parts:

#!/usr/bin/env bash

if [[ $# -ne 2 ]]; then
  echo "Usage: $0 <input> <editList>"
  exit 1
fi

inputFile=$1
editFile=$2

inputFileBase="${inputFile%.*}"
extension="${inputFile##*.}"

cnt=0
while IFS=, read -r start end; do
  suffix="$(printf "%05d" $cnt)"
  outfile="${inputFileBase}-${suffix}.${extension}"
  ffmpeg -nostdin -ss "$start" -i "$inputFile" -to "$end" -c copy -map 0 "$outfile"
  cnt=$((cnt+1))
done < "$editFile"

Save this file as cut.sh (or similar), then run:

chmod +x cut.sh
./cut.sh /path/to/input.mp4 /path/to/editList.csv

It'll copy the video and audio bitstreams over to output files, sequentially numbered with the suffix 00001, 00002 and so on.


Old answer below …

I once wrote a Ruby script which does exactly that. You need Ruby ≥1.9.2 (e.g. through RVM) and a recent version of FFmpeg installed (see here on how to install from source).

  1. My script is available here: video-extract.rb

  2. You need to feed it with a CSV input list of edits, most importantly containing the following columns:

  • a prefix (can be empty)
  • a video ID (some sequential number)
  • the input file name
  • the in point in HH:MM:SS.ms or seconds
  • the length of the edit in HH:MM:SS.ms or seconds

For example (note that the Out column is unused):

  1. Then, adjust the variables in the script header. Most importantly, change COPY to true if you want a bitstream copy and no re-encode. Also change the index of the CSV columns and the CSV separator.

Feel free to improve the script or suggest changes (especially if you already know Ruby). I've used this script very often and haven't run into problems yet. The only thing that's missing is proper audio support – it'll just copy the audio stream, which might or might not work in your case. In case of trouble, report back.

If you need to calculate the difference between an in and out-point, you can do this with this little Ruby script, based on this Stack Overflow Q&A:

require "Time"
def time_diff(time1_str, time2_str)
  t = Time.at( Time.parse(time2_str) - Time.parse(time1_str) )
  (t - t.gmt_offset).strftime("%H:%M:%S.%L")
end
ins, outs, diffs = File.open("ins.txt"), File.open("outs.txt"), File.new("diffs.txt", "w")
inlines, outlines = [], []
ins.each  { |l| inlines << l }
outs.each { |l| outlines << l }
inlines.zip(outlines).each { |ins, outs| diffs.puts time_diff(ins, outs) }
diffs.close

You just create a file called ins.txt and outs.txt where each line corresponds to an in- and out-point (see the screenshot above). The difference will be written to diffs.txt. Simple as that.