TUTORIAL 3
Making Gifs Purely with FFmpeg Scripts
FULL TUTORIALS | RECOLORED GIFS
Directly opposite to the last tutorial, this tutorial is for girls who LOVE command lines. You can make a goddamned motherfucking pristine, RECOLORED gif set from your command line, and feel cool as fuck while you're at it. And doing so is probably not quite as devoid of visual stimulus as you think!!
FULL SIZE EXAMPLE SET HERE | FULL CODE FOR THE SET HERE
Operating systems: Mac, Windows, and Linux
Quality potential: High
Software needed: FFmpeg
Difficulty: Advanced as far as gif-making, but this is actually a good first bash scripting project in my opinion if you've ever wanted to learn how.
Requirements: General familiarity with your computer's shell (Powershell on Windows, Terminal on Mac) helps a lot! I will try to make it as easy as possible to follow.
LIMITATIONS: 1) Frame by frame gif-making methods take up a lot of space on your drive in the interim. 2) The captioning method currently provided in the tutorial is not good for longer captions (there is a better method I plan to append eventually). 3) Recoloring options are minimal in this tutorial. Curves and many other color adjustments are available in FFmpeg but I haven't yet explored them.
First, let me get this response out of the way:
"I don’t understand how I can possibly do this if I can’t see what I’m doing!"
That’s the neat part—you DO get to see what you’re doing!
The first visual is your file browser.
If you were using Photoshop or GIMP to make these gifs, you’d view all of your frames in a sidebar as small thumbnails and select and scroll through them to add delete, and group select and manipulate them. The FRAMES folder in our file browser will serve this function, letting us see every frame in our set, delete any frames we don't want, and group them to apply the same crop, coloring, or caption to the same selection of frames.
The second visual is provided by FFplay.
FFplay is part of FFmpeg. When you use this command in your system shell, it opens an image or gif so you can see the results of a crop, recolor, caption (or all three!) on your work before actually applying it! This is how we will actually see, at full resolution on our screen, exactly how our image is looking.
WINDOWS USERS: CHANGE ALL FORWARD SLASHES IN THIS SCRIPT TO BACK SLASHES!!!
___________________________________________
1. Installing FFmpeg
___________________________________________
I recommend you install via Homebrew if you're on Mac or Linux by pasting this into your terminal (if/once Homebrew is installed):
brew install ffmpeg
Windows users or other users who don't want Homebrew can follow directions on the FFmpeg website.
___________________________________________
2. Screencapping Frames with FFmpeg
___________________________________________
There are many ways to do this. However, since this is the Pure FFmpeg tutorial, I'm going to point you to these instructions on how to automate this process with a script template I have already written for you and an example (screencapping for this exact set).
If you follow that tutorial exactly, you should come back for step 3 with a folder on your Desktop called FRAMES.
___________________________________________
3. Organize in the FRAMES Folder
___________________________________________
Go to your FRAMES folder in your file browser. Your frames are already ordered for you in the correct time sequence.
Delete any unwanted frames.
I need to "trim" some frames from the beginning and end of my set. For example, at the beginning of my set, the first 17 screencaps are of Sam from the first 564 milliseconds of the start of the 8.02 time stamp. I don't want this these frames in my gifset. I want my first gif to start on Dean when he appears starting in the 18th screencap.
If I hold shift and select all of the unwanted frames, I can delete them. Similarly, my screencaps end at the 8:11 mark, and only the first 156 milliseconds are on Dean. After that, the shot switches back to the purple guy. I don't want those shots of the purple guy, so I'll delete frames 247 through 280 at the end of the set, leaving me with frame_0018.png through frame_0246.png as all the frames of my gifset.
Make "Shot Folders".
As an example of what I mean by shots, look at the three example gifs at the top of the tutorial. The first gif has just one shot (i.e., the camera stays on Dean). The second gif has two shots (the camera is on the guy painted purple, and then it's on Dean again. In the third gif, there are two shots again—one on Sam, then one on Dean. So that's 5 shots. I want to separate frames belonging to each of the 5 shots into subfolders labeled 1 through 5 (see gif below).
______________________________
4. Setting up FFplay
______________________________
Now that we have all of our frames organized, we want to set up FFplay—the method we'll use to view our image manipulations before applying them.
NOTE: Type down all of the commands as you run through this whole tutorial!! In a word document, a text editor, etc. Do NOT just type your commands in the shell without keeping track of what you typed! You can also see an example of how to map out your scripts by following how I wrote them for Github here, and even download/copy paste my fulls script there and use it as a template for your own set.
Template for Creating Our Preliminary Gifs for FFplay
The first thing we need to do is make a preliminary test gif of every shot in our gifset. To do this, use the template below, changing only the parts in red:
cd ~/Desktop/FRAMES/ShotNumber ffmpeg -start_number FileNumberOnFirstFrame -i frame_%04d.png -vf "fps=30" ffplay -vf Test.gif
The first line, beginning with "cd", tells me the file path to my shot folder where my frames are located.
The line below that combines the frames of my shot into a gif called Test.gif.
ffmpeg envokes the FFmpeg utility.
-start_number FileNumberOnFirstFrame -i frame_%04d.png tells FFmpeg to order the frames in the gif by the 4-digit number at the end of their file name, starting with FileNumberOnFirstFrame, which you should replace with the 4-digit number in the file name of the first frame in your shot folder.
-vf "fps=30" tells FFmpeg I want the gif to move at 30 frames per second (if you screencapped at a rate other than 30 FPS, you can simply change the number to match your frame rate).
In my example:
Say I want to generate a gif of my first shot. Then I need ShotNumber to be 1, and FileNumberOnFirstFrame to be 0018 (that's the 4-digit number on the first frame in my first shot, after deleting the first 17 frames). So my command looks like this:
cd ~/Desktop/FRAMES/1 ffmpeg -start_number 0018 -i frame_%04d.png -vf "fps=30" Test.gif
See Step 4 in my script on Github to see the script for all 5 shots.
Playing a gif from FFplay
Now if I want to play the gif I just made in FFplayer. I just type the following:
ffplay -loop 0 Test.gif
A window should pop up and loop through my gif (edit: I have adjusted this tutorial so the FFplay gif loops infinitely, by adding the setting -loop 0).
After repeating the above commands to generate a Test.gif in every shot folder, we're ready to move on to the rest of the steps.
__________________
5. Test Crop
_________________
Now that FFplay is set up, it's time to start manipulating our shots by applying changes to Test.gif in every shot folder. The first thing we want to do is crop.
NOTE 1: Cropping is different from scaling. At the very end of the tutorial, when we export, we will scale our gifs down to 540 px. For now, we want to work in full resolution, which for Supernatural is 1080p.
NOTE 2: Throughout this tutorial, when I add to an existing command, I will bold the new portion, and mark all the parts you can/should change in red.
Cropping Template
To crop a gif in FFplay, we add -vf "crop=w:h:x:y" to our FFplay command as follows:
cd ~/Desktop/FRAMES/ShotNumber ffplay -vf "crop=w:h:x:y" -loop 0 Test.gif
Where
w is the width you want your cropped frames to be (in pixels)
h is the height you want your cropped frames to be (in pixels)
x and y are coordinates of the top left corner of your crop selection (in pixels)
To understand the x and y coordinates, think about if you wanted to use crop an image in a drawing software. You would generally select your crop tool, start in the top left corner, and drag your cursor down and right (blue dot in the illustration below represents where the crop starts in an example). So FFmpeg asks you to specify the top left corner where your crop starts with an x and y coordinate (in pixels), and uses the w and h variables to determine how large the crop should be.
My frames are all 1920x1080 pixels. I would like my gifset to be square—1080x1080 pixels. So I already know I want my w and h to both be 1080. Since I want to start my crop at the top of a frame, losing no height, my y-coordinate (height to start my crop from) should also 1080. My x-coordinate is the only think I'm not sure about. I only know it needs to be bigger than 0 (0 would start my crop on the left edge) and smaller than 960, which would be the midway point.
So what am I going to do? ...I'm gonna make an educated guess of what x-coordinate would center Dean in a square frame in my FFplayer command, and if I don't like how he's centered, I'll simply move the x-coordinate over a little (decrease to move left, increase to move right).
So in my Example...
In FFmpeg, for my first shot, I'm going to guess x=300.
cd ~/Desktop/FRAMES/1 ffplay -vf "crop=1080:1080:300:1080" -loop 0 Test.gif
The FFplayer window shows me this:
And I feel like the crop starts a little too far to the right. So I'm going to decrease my x-coordinate just a little—to 250. After replacing the 300 with a 250 and running again in FFplay, I feel good about 250, so that's the crop I'll set: -vf "crop=1080:1080:250:1080".
I need to follow this same process to determine the crop of my other shots (and keep a copy of the commands I used later!). What is useful however, is that 3 of my shots are all on Dean sitting (shots 1, 3, and 5). This means I can apply the same crop I made for shot 1 to shots 3 and 5.
See Step 5 in my script on Github to see all 5 shots.
___________________________________________
6. Test Coloring and Sharpening
___________________________________________
Because the lighting in this scene is pretty good, I did a very simple recoloring on this set. I may update later with a more extensive coloring tutorial with more options later (I'll link the post to this section if I do). Previously, we added a crop to our command: -vf "crop=1080:1080:250:1080" and now we want to test coloring options and a sharpening effect.
Coloring Template
I'm going to throw in the basics, and give you an updated FFplay template (new parts are bolded, variables you can adjust are in red and set at their default values).
cd ~/Desktop/FRAMES/ShotNumber ffplay -vf "crop=w:h:x:y,eq=brightness=0.00:saturation=1.00:contrast=1.00,smartblur=1.5:-0.35:-3.5:0.65:0.25:2.0" -loop 0 Test.gif
NOTE: Notice that inside the quotes, a comma separates the command options crop, eq, and smartblur. Equals signs and colons distinguish between sub-options belonging to each of those three categories.
EQ:
brightness=0.00. Values can be adjusted anywhere between -1 and 1, with 0 as the default). I would adjust this setting in 0.01 unit increments.
saturation=1.0. Values between 0 and 3 are possible. 1 is the default value. I would adjust this setting in 0.1 unit increments at a time.
contrast=1.00. Increases contrast between light and shadow in the image (values between -1000 and 1000 are possible. I recommend you change this setting in 0.1 unit increments.
There are more color and image adjustment options than these available in FFmpeg. You can see a full list of properties you can adjust from eq here.
Smartblur:
smartblur=1.5:-0.35:-3.5:0.65:0.25:2.0 sharpens the frames. I recommend you not touch this setting. If you do want to adjust it, check the FFmpeg documentation. You can also remove this whole portion (and the associated comma) if you don't want a sharpening adjustment.
NOTE: It is also possible to add a curves adjustment to brighten certain parts of an image instead of the whole image. I haven’t worked with curves enough from the command line to give you good ideas on setting your curve and this set didn’t really need it, but if I get into it more in the future, I'll include it in a supplementary tutorial and link it here.
If you feel like you are losing all sense of objectivity and just want to see the images without your coloring at any point, simply re-run your ffplay from the end of the cropping section.
ffplay -vf "crop=w:h:x:y" Test.gif
In my example (shot 1):
Because all my characters are in the same room with similar lighting, I ended up applying the same color adjustment to all of my shots. But here's all my settings tested together on my first shot in FFplay:
cd ~/Desktop/FRAMES/1 ffplay -vf "crop=1080:1080:250:1080,eq=brightness=0.06:saturation=1.70:contrast=1.10,smartblur=1.5:-0.35:-3.5:0.65:0.25:2.0" -loop 0 Test.gif
See Step 6 in my script on Github to see all 5 shots.
___________________________________________
7. Apply crop and coloring to frames
___________________________________________
Up until this point, you have not actually applied your crop and color adjustments to your frames. You have simply tested your crop and coloring manipulations on a gif of all your frames that is still, if you go look at it in your file browser, not cropped.
So let's actually apply our adjustments to these frames!
NOTE: Captioning needs to be done after this step because auto centering the text won't work properly without actually cropping first). This is probably all for the better as our command is getting long and difficult to decipher!
Template to ACTUALLY Crop and Color
cd ~/Desktop/FRAMES/ShotNumber mkdir crop for i in *.png; do ffmpeg -y -i "$i" -vf "fps=30,crop=w:h:x:y,eq=brightness=0.00:saturation=1.00:contrast=1.00,smartblur=1.5:-0.35:-3.5:0.65:0.25:2.0" crop/${i%.png}.png; done
The first line (beginning with cd) tells us to go to our shot folder.
mkdir crop tells our computer to make a new subfolder in our shot folder called "crop".
for i in *.png; do ffmpeg -y -i "$i"; and the closing: crop/${i%.png}.png; done tells FFmpeg to do the same crop and color on every .png file in our shot folder, and save these adjusted shots into the crop subfolder.
In my example (shot 1)
cd ~/Desktop/FRAMES/1 mkdir crop for i in *.png; do ffmpeg -y -i "$i" -vf "crop=1080:1080:250:1080,eq=brightness=0.06:saturation=1.7:contrast=1.1,smartblur=1.5:-0.35:-3.5:0.65:0.25:2.0" crop/${i%.png}.png; done
See Step 7 in my script on Github to see all 5 shots.
___________________________________________
8. Captioning
___________________________________________
Now we need to apply captions. You can also do an FFplay command here, but I'm going to show you something a little different with FFplay this time to see your caption. Instead of looking at captions over a whole gif, let's just test how our captions look on the first frame in our shot, since that's really all we need for captioning.
Captioning FFplay Template
Not all sets (and not all shots!) need captions (and if your set doesn't, you can skip down to the next section!) However, two of my shots need captions: Shot 1 and Shot 3. If you want to add captions, you can test them in FFplay on a single shot using this command:
cd ~/Desktop/FRAMES/ShotNumber/crop ffplay -vf "drawtext=text='Your Text Goes Here':x=(w-text_w)/2:y=(h-text_h*2):fontsize=40:bordercolor=black:borderw=3:fontcolor=white" input.png
The default font is Arial. There are ways to set other fonts, but I haven't looked into them much because I'm pretty happy with Arial. This basic template with the existing numbers should give you a decent result, but if you want to change font sizing or colors or anything:
drawtext=text='Your Text Goes Here' | This is the most important bit. You place your caption in the red part. Note that your text will appear on just one line. If you have a longer statement from a character that will need two lines, you can draw your box twice. (I will add a link here for this alternative when I eventually try it).
x=(w-text_w)/2:y=(h-text_h*2) | This part centers the text on the bottom of the screen. I recommend you leave it alone as it’s intended to auto-center your text for you no matter what your crop ratio is. You will need to change the the y=(h-text_h*2) argument only if you have two lines of text to caption your frames with.
fontsize=40 | Changes the font size, of course.
bordercolor=black | changes the border color.
borderw=3 | changes the border weight.
fontcolor=white | This changes the font color.
input.png is the file name of the frame you want to view your caption on.
By the way, when changing text or border colors, you can type in colors coded into the documentation (the default is black) or insert a HEX color code in this field. For example, I always use yellow captions for Dean, so I found a yellow HEX code (#FDDA0D) I liked on htmlcolorcodes.com.
Here's how I FFplay my caption "You missed a spot."
cd ~/Desktop/FRAMES/1/crop ffplay -vf "drawtext=text='You missed a spot.':x=(w-text_w)/2:y=(h-text_h*2):fontsize=40:bordercolor=black:borderw=3:fontcolor=#FDDA0D" frame_0018.png
See Step 8 in my script on Github to see both captioned shots.
Template for applying your caption:
If you like the way your caption looks in FFplay, you can apply it to all the frames in your shot folder with:
cd ~/Desktop/FRAMES/ShotNumber/crop mkdir captioned for i in .png; do ffmpeg -y -i "$i" -vf "drawtext=text='Your Text Goes Here.':x=(w-text_w)/2:y=(h-text_h2):fontsize=40:bordercolor=black:borderw=3:fontcolor=white" captioned/${i%.png}.png; done
Where
mkdir captioned makes a folder in the crop folder called "captioned"
for i in *.png; do ffmpeg -y -i "$i"; and the closing: crop/${i%.png}.png; done tells FFmpeg to place the same caption over every .png file in our shot folder, and save these adjusted shots into the captioned subfolder.
Here's the settings to apply captions to my first shot.
cd ~/Desktop/FRAMES/1/crop mkdir captioned for i in .png; do ffmpeg -y -i "$i" -vf "drawtext=text='You missed a spot.':x=(w-text_w)/2:y=(h-text_h2):fontsize=40:bordercolor=black:borderw=3:fontcolor=#FDDA0D" captioned/${i%.png}.png; done
See Step 8 in my script on Github to see both captioned shots.
___________________________________________
9. Organize shots into GIF folders
___________________________________________
We're almost done!!! It's time to go back to our file browser! Now that all of our frames in all of our shots are prepared how we want, we need to reorganize our shots into GIF folders. This is our way of group selecting all the frames that go into one gif for export.
In my case, I want three gifs, so I'm going to make three new folders in the FRAMES folder: gif_1, gif_2, and gif_3. My FRAMES folder should now look like this:
Now I want to take my finished shots from my shot folders and copy their frames into the gif folder they are associated with. For example:
Gif 1 just contains the first shot. I also captioned shot 1. So I'm going to copy all the frames in FRAMES/1/crop/captioned into the gif_1 folder.
Gif 2 contains shots 2 and 3. Shot 2 has captions, so I need to take all the frames from FRAMES/2/crop/captioned and all the frames in FRAMES/3/crop and copy them in the gif_2 folder.
Gif 3 contains shots 4 and 5. I'm going to copy the contents of the crop folders for shots 4 and 5 into this folder (no captions on this gif).
___________________________________________
10. Compiling into GIF
___________________________________________
To combine all the frames from one gif folder into a gif, I'm going to use this template script:
cd ~/Desktop/FRAMES/GifFolder ffmpeg -y -start_number FileNumberOnFirstFrame -i frame_%04d.png -vf "fps=30,scale=540:-1:flags=lanczos" ~/Desktop/FRAMES/GifName.gif
This is very similar to the script in step 4. This template opens one of our new gif folders, and takes all the frames, starting with the first frame in the folder (recall: you need to tell FFmpeg the 4-digit number on that first frame with FileNumberOnFirstFrame) and then turns it into a gif running at 30 FPS, scaled down to 540 by 540 pixels.
The parts we can/should adjust are as follows:
GifFolder is the name of our gif folder where our frames for our gif are located.
scale=540:-1:flags=lanczos is the command to scale down our gif (using what's called the lanczos method) to 540 x 540 pixels so it'll be small enough to upload to Tumblr.
fps=30 tells FFmpeg the proper FPS for our set (if you made your frames using this tutorial, 30 FPS is correct. If you took screencaps manually, it will be the frame rate of whatever you giffed. For TV, this might be 23 FPS for example).
~/Desktop/FRAMES/GifName.gif names our gif (specify with GifName) and outputs it to the FRAMES main folder.
Here's the command for Gif 1 in my set:
cd ~/Desktop/FRAMES/gif_1 ffmpeg -y -start_number 0018 -i frame_%04d.png -vf "fps=30,scale=540:-1:flags=lanczos" ~/Desktop/FRAMES/1.gif
See Step 10 in my script on Github to the script on all three gifs.
THE END!!!!
Do this step on all your gif folders and you're done!!!
You can view my full script for this example gifset on Github here and if you'd like, simply modify that script to make your own gifset!











