Video editing using ffmpeg and ImageMagick

Sometimes, you want to edit multiple videos in a similar way. For that, I've found that the best way is to create a script that you can run consistently on those videos and get a consistent ouptut. For example, recordings from a conference or a day by day video log of your family vacation.

I have created some scripts using ffmpeg and ImageMagick for my some of my videos, saving me hours in the process.

The main advantages I've found are:

  • Consistency: I don't have to remember what I did to a video. It is all in the script
  • Time savings: Execute the script with the right parameters and go for dinner, let the computer work.
  • Control: I can execute just a portion of the script, play around with the options and be limited only by my imagination.

In this posts I will show some of the commands I've found most useful, these are very basic commands that can be improved by people more creative than me.

For this example I will use a script I created to edit my Toastmasters videos. You can find the full script attached to this post along with an explanation of each one of the components of the script

To test the script, just extract the attached sample file and execute the following command

./VideoEdit.sh sample.prm

This script does the following:

  1. Creates a few seconds of introduction video with a still image and information about the video (Title, speaker, date, etc)
  2. Creates a few seconds of closing video with a still image and credits and disclaimers
  3. Extracts a portion of the input video for editing as individual PNG frames
  4. Fades out the introduction image into the first 3 seconds of video
  5. Overlays some slides at certain points in the video
  6. Reassembles the video from the modified frames. adds the audio and attaches the introduction and closing.

It receives as parameters a file that contains the actual editing parameters:

  • INPUTFILE : Path to the file we want to edit
  • LOGO : Path to a file that will serve as the cover for the introduction
  • LOGOLENGHT : Lenght in seconds in which the introduction should show
  • SPEAKER : Name of the speaker
  • TITLE : Title of the speach
  • DATE : Date of the speach
  • SCENESTART : Time where the section of video I want to extract starts (can be as ss or as hh:mm:ss.fff)
  • SCENEDURATION : Time where the section of video I want to extract ends (can be as ss or as hh:mm:ss.fff)
  • PROJECT : Short name for the project. It will be used to name temporary files and the output file

Here is an example of my parameters file:

INPUTFILE="/media/cdrom0/VIDEO_TS/VTS_01_1.VOB"
LOGO="../TM-titleLogo.png"
LOGOLENGHT="5"
SPEAKER="Raul Suarez"
TITLE="The dreaded empty page"
DATE="July 28, 2011"
SCENESTART="00:06:58"
SCENEDURATION="00:07:20"
OUTPUTFILE="TM2"

Extract Video:
Extracts a portion of video from the input file converting the video to frame files one PNG file per frame and splitting the audio to to an mp3 file

mkdir -p "${OUTPUTFILE}-frames"
ffmpeg -loglevel quiet -threads 4 \
-i ${INPUTFILE} -ss ${SCENESTART} -t ${SCENEDURATION} \
-f image2 -y "${OUTPUTFILE}-frames"/frame%d.png \
-acodec copy -sameq -y "${OUTPUTFILE}.mp3"


-loglevel : defines how verbose I want the console output
-threads : If allows using multiple cores in a multicore processor. Can speed up some tasks
-i : The name of the input file
-ss : Indicates when does the segment of video we want start
-t : Indicates the duration of the segment of video we want
-f : Format of the output. In this case the format is image as we are extracting to one frame per file
-y : Overwrites files without asking
"${OUTPUTFILE}-frames"/frame%d.png : This part of the command tells ffmpeg to extract each frame to a file named frame1.png, frame2.png, etc
-acodec copy : extract the audio without re-encoding
-sameq : do not loose quality (may not be necessary here but I left it just in case)
"${OUTPUTFILE}.mp3" : is the name of the audio file we are saving

I could have done this with three commands, which is more readable, but takes more time

# Extract the video
ffmpeg -i ${INPUTFILE} -ss ${SCENESTART} -t ${SCENEDURATION} \
-acodec copy -vcodec copy -sameq -y "${OUTPUTFILE}-1.mpg"

# Split the audio to preserve it
ffmpeg -i "${OUTPUTFILE}-1.mpg" -acodec copy -sameq -y "${OUTPUTFILE}.mp3"

# Convert the video to frame files one PNG file per frame
mkdir -p frames
ffmpeg -i "${OUTPUTFILE}-1.mpg" -f image2 -y frames/frame%d.png

Create Introduction
Uses the ImageMagick convert command to create the introduction image by merging the logo file with the titles for the video

convert ${LOGO} -gravity Center -font DejaVu-Sans-Book \
-pointsize 20 -fill gray -draw "text 1,21 'Talk of the Town Toastmasters'" \
-fill white -draw "text 0,20 'Talk of the Town Toastmasters'" \
-pointsize 50 -fill gray -draw "text 2,72 '${SPEAKER}'" \
-fill white -draw "text 0,70 '${SPEAKER}'" \
-pointsize 30 -fill gray -draw "text 1,131 '${TITLE}'" \
-fill white -draw "text 0,130 '${TITLE}'" \
-pointsize 20 -fill gray -draw "text 1,171 '${DATE}'" \
-fill white -draw "text 0,170 '${DATE}'" \
"${OUTPUTFILE}-intro.png"

ImageMagick can, in a single instruction add multiple lines of text. It has different ways of doing this. For this example I used the "draw" command. To see other methods you can go to http://www.imagemagick.org/Usage/text/

This command gets the ${LOGO} file and overlays the text on top of it.

As you can see in this example, I have two "draw" commands for each line, I do this to create a "gray shadow" effect on the text.

The resulting image will be saved as "${OUTPUTFILE}-intro.png"

Create video from an image adding a silent sound track

ffmpeg -loglevel quiet -threads 4 \
-loop_input -i "${OUTPUTFILE}-intro.png" -qscale 1 -r 29.97 -t ${LOGOLENGHT} \
-ar 48000 -t ${LOGOLENGHT} -f s16le -acodec pcm_s16le -i /dev/zero -ab 64K -f mp2 -acodec mp2 \
-map 0.0 -map 1.0 -sameq -f mpegts -y "${OUTPUTFILE}-intro.mpg"

The first section of the command defines the video portion of the file

-loop_input will loop over the following file to create the output
-r : The output video will be 29.97 frames per second. This is the framerate for NTSC

The second portion defines the audio portion of the file: The silence. It is important to add a sound track or we will have problems concatenating at the end.

-ar : audio sampling frequency of the audio
-f : format, Note how when the format and codec are before -i, they refer to the input format, if they are after, they refer to the ouptut format.
-acodec : Audio codec
-i /dev/zero : This is where we take the "silence". Of course, if you want a real audio file you can use it here.
-ab : Audio bitrate

Finally we assemble the input video and audio into the ouput file

-map : These map parameters say: take the first channel of the first input (video) and the first channel of the second input (audio)
(I've explained the rest of the parameters in previous commands)

This is equivalent to the following three commands

# create proper lenght of silence
ffmpeg -ar 48000 -t ${LOGOLENGHT} -f s16le -acodec pcm_s16le -i /dev/zero -ab 64K -f mp2 -acodec ac3 -y silence.mp2

# Create still logo video
ffmpeg -loop_input -i "${OUTPUTFILE}-Logo.png" -qscale 1 -r 29.97 -t ${LOGOLENGHT} -y -f mpegts "${OUTPUTFILE}-logo1.mpg"

# Assemble still logo and silence
ffmpeg -loglevel error -i "${OUTPUTFILE}-logo1.mpg" -i "silence.mp2" \
-vcodec copy -acodec copy -map 0.0 -map 1.0 -sameq -threads 4 \
-y -f mpegts "${OUTPUTFILE}-intro.mpg"

The exit video is created in a similar way

FadeOut introduction
You may be wondering why we extracted all the frames to PNG files. The main reason is that it allows us to manipulate them however we want using ImageMagick. We can rotate them, mix, change colors, add overlays. Your imagination is the limit.

In this case I am disolving the intro image progressivelly into the corresponding input video frames.

mkdir -p "${OUTPUTFILE}-frames2"
for i in {1..90}; do
percent=$(echo "scale=3; ${i}*100/90" | bc )
convert -compose dissolve -gravity center -define compose:args=${percent} \
-composite "${OUTPUTFILE}-intro.png" "${OUTPUTFILE}-frames"/frame${i}.png "${OUTPUTFILE}-frames2"/frame${i}.png
done


for i in {1..90} : The loop will operate in the first 90 frames (3 seconds at 29.97 frames per second ~ 90 )
percent=$(echo "scale=3; ${i}*100/90" | bc ) : calculates the percentage we want to use to disolve. From 0 to 100% in 90 steps
The convert command disolves the "intro" image into each of the frames applying the corresponding disolve percentage.
Each of the resulting frames is written to another temporary folder.

Overlay an image
If you want to overlay an image on a section of video, you need to calculate the starting and ending frames so you can loop through those images doing the overlay:

I created a function to do the overlay, so I can overlay different images at different points in the video:

overlayImage () {
# Overlays an image on top of each frame in a range.
FILE="${1}"
GRAVITY=$2
START_FRAME=$(echo "scale=0; ${3}*29.97/1" | bc )
END_FRAME=$(echo "scale=0; ${4}*29.97/1" | bc )

for (( i=${START_FRAME} ; i<=${END_FRAME} ; i++ )) ; do
convert -compose dissolve -gravity ${GRAVITY} -define compose:args=90 \
-composite "${OUTPUTFILE}-frames"/frame${i}.png "${FILE}" "${OUTPUTFILE}-frames2"/frame${i}.png
done
}

The overlayImage function receives as parameters

  • The image we want to overlay
  • The relative positioning (gravity)
  • The start time on the section of video in seconds
  • The end time of the section of video in seconds

The loop disolves the overlay image into each of the frames for the section of video we want

Reassemble the video

# Copy the modified frames on top of the original frames
cp "${OUTPUTFILE}-frames2"/frame*.png "${OUTPUTFILE}-frames"

# Reassembles the frames and the audio into an output video
ffmpeg -loglevel quiet -threads 4 \
-r 29.97 -f image2 -i "${OUTPUTFILE}-frames"/frame%d.png \
-i "${OUTPUTFILE}.mp3" -acodec copy \
-map 0.0 -map 1.0 -sameq -f mpegts -y "${OUTPUTFILE}.mpg"

# Concatenates the introduction, video and closing
ffmpeg -loglevel quiet -threads 4 \
-i concat:"${OUTPUTFILE}-intro.mpg"\|"${OUTPUTFILE}.mpg"\|"${OUTPUTFILE}-exit.mpg" \
-r 29.97 -sameq -y "${OUTPUTFILE}.mp4"

And that's it. As you can see, with small modifications to these commands you can edit your video however you want.

If you search the internet you will find plenty of examples for ffmpeg for example
http://www.catswhocode.com/blog/19-ffmpeg-commands-for-all-needs

While the ImageMagick documentation is the best source for more examples.
http://www.imagemagick.org/Usage/

While you can do it using a GUI video editor such as Kino, Cinelerra or final cut. I found that the flexibility and repeatability of scripting it simplified my life.

I hope it simplifies yours too.

If you have any comments you can email me at

rarsa --at-- yahoo.com

File Attachments

Attachment Size
sample.tar_.gz 4.83 MB