How to Script for FFmpeg Using PowerShell and BASH
FFmpeg was designed as a cross-platform solution for video and audio recording, conversion, and streaming. Its About page describes the command-line tool as “the leading multimedia framework, able to decode, encode, transcode, mux, demux, stream, filter and play pretty much anything that humans and machines have created.” Capable of running on Linux, Mac OS X, Windows, and a range of other build environments, FFmpeg 5.1.2 is available for download at ffmpeg.org. You can find an introduction to FFmpeg encoding with downloadable scripts here.
Most FFmpeg users start by creating a simple static command line which, after much debugging, works just fine for the single input file and selected encoding parameters. Attempting to reuse that command line for different files or parameters is often an error-prone experience of searching, copying, and pasting and then rinsing and repeating to correct any missteps.
Using variables and “for loops” in a command string simplifies the reuse of existing scripts and helps automate their operation. While you can’t use these scripts in the Windows Command window, you can use Microsoft PowerShell in Windows and Bash on Linux and the Mac. In this tutorial, you’ll learn how to create and run such scripts with PowerShell and Bash.
Specifically, you’ll learn how to convert a command string like this:
ffmpeg -y -i input.mp4 -c:v libx264 -vf scale=-1:1080 -b:v 3M -maxrate 6M -bufsize 6M -g 48 -preset ultrafast output.mp4
into a script like the one shown in Figure 1 (below).
![script2-variables.sh](http://dzceab466r34n.cloudfront.net/Images/ArticleImages/InlineImages/151969-Ozer-FFmpeg-Fig1-ORG.png)
Figure 1. This simple Bash script produces the same output as the command string above. This is script2-variables.sh, which you can download here
With the new script, all of the adjustable parameters are on top, simplifying modification for testing and production. You’ll also learn how to apply this script to multiple files in a folder and how to customize file names with the source file and selected variables. Finally, I’ll show you how to store the output files in a separate custom folder—all for Bash and PowerShell. For this article, I’ll assume that you know the basics of FFmpeg programming for the Windows Command window, but are unfamiliar with Bash or PowerShell.
You can download all of the scripts here. Please unzip the file, keeping all folders intact; later scripts look for input files in folders other than where the script is located. I’ve included a 10-second segment of the Sintel film from Blender to simplify working along with the examples in this article. Much appreciation to the Blender Foundation for graciously creating this clip and allowing it to be used for such purposes.
What Are Bash and PowerShell?
According to GNU.org, “Bash is the shell, or command language interpreter, for the GNU operating system. The name is an acronym for the ‘Bourne-Again SHell’, a pun on Stephen Bourne, the author of the direct ancestor of the current Unix shell sh, which appeared in the Seventh Edition Bell Labs Research version of Unix.” According to Microsoft, PowerShell is “a cross-platform task automation solution made up of a command-line shell, a scripting language, and a configuration management framework.”
If you’re familiar with the Windows Command window, both shells are simply more powerful Command windows. Bash is the terminal that opens when you press Ctr-Alt-T on Ubuntu or run Terminal on the Mac. Type “Powershell” into the Windows search bar, and the Windows PowerShell window appears in blue. Both systems have their peculiarities. If you check the Bash script in Figure 1, you’ll see the string #!/bin/bash at the top. This is the required Shebang that tells Ubuntu which interpreter to use to execute the script. Bash scripts are typically saved with the .sh extension, and that’s the convention followed here. PowerShell scripts don’t need a Shebang and use the .PS1 extension, which lets you run it from Windows Explorer like a batch file. All of the PowerShell scripts used and included here use the .PS1 extension.
Permission to Run the Script
Unlike the Command window, you must ask each shell’s permission to run a script. For Bash, the first time you run a script like S1-minimal.sh in a session, you request permission to execute the script via this command:
chmod +x S1-minimal.sh
Then you run the script with this command:
./S1-minimal.sh
You can see both commands inserted in Figure 2 (below) in the Terminal window. For the record, this script simply contains the FFmpeg command string shown earlier, essentially the “before” command that we’ll be transforming throughout this article.
![Bash](http://dzceab466r34n.cloudfront.net/Images/ArticleImages/InlineImages/151970-Ozer-FFmpeg-Fig2-ORG.png)
Figure 2. Bash--requesting permission to run the first script and then running it
If you edit and update the script, you don’t need to re-request the authority to execute it; it should just run. With PowerShell, you have to request authority to run any script using this command:
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
This covers all scripts that are run during that session, but you must repeat it every session. Then you run the script using this command:
.\S1-minimal.ps1
Note the backslash as opposed to the forward slash used for Bash. Alternatively, you can run the script by right-clicking it in Explorer and choosing Run with PowerShell (Figure 3, below).
![PowerShell](http://dzceab466r34n.cloudfront.net/Images/ArticleImages/InlineImages/151971-Ozer-FFmpeg-Fig3-ORG.png)
Figure 3. PowerShell--requesting to run all scripts and then running the first
You can run either or both of the scripts now. You’ll obviously need FFmpeg installed on your system. Both scripts should create a file called output .mp4 in the same folder as the script and source file.
Deploying Variables in Bash
Variables are a mechanism to label data with a descriptive name and then reference it later in the script. They function identically in Bash and PowerShell, although the operation is slightly different. Figure 4 (below) is the same as Figure 1 and shows a Bash script using variables to run the same FFmpeg command string shown at the beginning of this article.
![Bash script](http://dzceab466r34n.cloudfront.net/Images/ArticleImages/InlineImages/151972-Ozer-FFmpeg-Fig4-ORG.png)
Figure 4. A simple Bash script (S2-variables.sh)
Here are the Bash rules relating to variables:
- You define a variable with an alphanumeric name (like
height
). - You assign a value to the variable with the equal sign (=) without spaces.
- If the variable is a text string, surround it with quotes (
bitrate=”3M”
). - If the variable is an integer, don’t use quotes (
height=1080
). - You reference the variable in the script via the variable name preceded by a dollar sign. So, rather than -
b:v 3M
, it’s -b:v $bitrate.
Note that the backslashes at the end of lines 10–12 are line continuation characters that tell Bash not to insert a line feed character between these lines. This lets you break the command string into readable lines that are easier to create and debug. If you divide your script into these readable lines but don’t use the line continuation characters, your text editor will insert invisible line feed characters that will interrupt script operation.
Speaking of line feeds, if you create your scripts on an operating system different than the one you will run the script on, be sure to save the script for the target operating system. You can see this in the bottom center of Figure 4. Although I’m running Notepad++ on Windows, I’ve saved the script for the UNIX operating system. If you forget to do this and save the script in Windows format, the script will look perfect, but you’ll get spurious errors when you try to run it.
Now would be a good time to run script2-variables.sh. This should produce a file very similar, if not identical, to the file created using the first script.
Next we’ll look at how variables work for PowerShell.
Deploying Variables in PowerShell
Figure 5 (below) shows the same script for PowerShell.
![The S2-variables.ps1 script in PowerShell (S2-variables.ps1)](http://dzceab466r34n.cloudfront.net/Images/ArticleImages/InlineImages/151973-Ozer-FFmpeg-Fig5-ORG.png)
Figure 5. The S2-variables.ps1 script in PowerShell (S2-variables.ps1)
Here are the equivalent rules for creating and deploying variables in PowerShell.
- You define a variable with an alphanumeric name preceded by a dollar sign (
$height
). - You assign a value to the variable with the equal sign (=) without spaces around it.
- If the variable is a text string, surround it with quotes (
$bitrate = "3M"
). - If the variable is an integer, no quotes are needed (
$height = 1080
). - You reference the variable in the script via the variable name preceded by a dollar sign. So, rather than
-b:v 3M
, it’s -b:v $bitrate
.
In PowerShell, the line continuation character is a backtick (the key above the Tab key on the upper lefthand side of the keyboard). If you break your script into multiple lines for easier readability, be sure to insert them as the last character of all script lines until the final line.
Try running script2-variables.ps1 with your short 1080p30 input file. FFmpeg should produce a file very similar, if not identical, to the file produced from script1-minimal.ps1.
Customizing Output File Name and Folders in Bash
Whether for testing or production, you often want to create descriptive file names that detail how the files are encoded. When testing presets, for example, you may want _ultrafast in the file name. For production, you may want the resolution and bitrate. Let’s explore how to accomplish this in Bash (Figure 6, below) and PowerShell.
![Bash--customizing output file names and output paths](http://dzceab466r34n.cloudfront.net/Images/ArticleImages/InlineImages/151974-Ozer-FFmpeg-Fig6-ORG.png)
Figure 6. Bash--customizing output file names and output paths
The top variables section is identical; here’s an explanation of the new code under the comment #folders and filenames and where it impacts the updated script (S3-filenames.sh):
input="./sources/sintel.mp4"
creates a new variable called input that identifies the file name and folder. We use this new variable in the command string instead of the name and location of the input file. We’ll expand on this input variable in the next lesson.base=$(basename ${input%.*}
) creates a new variable called base to use when naming the output folder and output file.- The basename command grabs the input file name without any path information.
${input%.*}
removes the file extension from the basename in the output file and output folder.$()
wraps the expression to execute it and store its output in the base variable when you run the script.outputfolder="./outputs/$base"
creates another variable that sets the output folder as the target folder and the base variable as the subfolder.mkdir -p "$outputfolder"
creates any folder and subfolder in the output folder variable that don’t yet exist.output="${outputfolder}/${base} _ ${height}p _ ${bitrate} _ ${preset}.mp4"
creates the output file name and storage location. The file name can consist of any variable and static text (like the p after ${height}
). This variable is substituted into the command line for the output file name.- The FFmpeg script is identical except that the variables input and output have been substituted for the input and output file directly specified in the previous script.
When you run this script, Sintel.mp4 must be in the sources subfolder in the script source folder, presumably Lesson_3. After running the script, you will see an output folder with a Sintel subfolder and the output file named sintel_1080p_3M _ultrafast.mp4. You can see the file in Figure 7 (below), with the folder structure in the line above the file bin. So, you organized the encoded file into a distinct subfolder and named the file using the desired variables and text.
![The S3-Filenames script produced the folder structure and output file shown here](http://dzceab466r34n.cloudfront.net/Images/ArticleImages/InlineImages/151975-Ozer-FFmpeg-Fig7-ORG.png)
Figure 7. The S3-Filenames script produced the folder structure and output file shown here
Customizing Output File Names and Folders in PowerShell
Now, let’s run through the same analysis for PowerShell with the S3-filenames.ps1 script shown in Figure 8.
![PowerShell--customizing output file names and output paths](http://dzceab466r34n.cloudfront.net/Images/ArticleImages/InlineImages/151976-Ozer-FFmpeg-Fig8-ORG.png)
Figure 8. PowerShell--customizing output file names and output paths
The top variables section is identical; here’s the new code under the comment #folders and filenames and where it impacts the updated script.
$input = ".\sources\input.mp4"
creates a new variable called input that identifies the file name and folder. We use this in the command string instead of the name and location of the input file. We’ll expand on this input variable in the next section.$base = (Get-Item "${input}"
).Basename assigns the value of the base name of the input file to the variable “$base”. Specifically, the script uses the Get-Item cmdlet to retrieve the item at the path specified by the "$input"
variable and then uses the .Basename
property to retrieve the base name of the file.$outputfolder=".\outputs\$base"
creates another variable that sets the output folder as the target folder and the base as the subfolder.- N
ew-Item -type Directory -path $output folder -Force
creates any folder and subfolder in the outputfolder variable that doesn’t yet exist. $output="${outputfolder}\${base} _ ${height}p _ ${bitrate} _ ${preset}.mp4"
creates the output file name and storage location. The
file name can contain any variable and static text (like the p after ${height}). This variable is substituted into the command line for the output file name.- As with the Bash script, the FFmpeg script is identical except that the variables input and output have been substituted for the input and output file directly specified in the previous script.
As with the Bash script, Sintel.mp4 must be in the sources subfolder of Lesson_3. After running the script, you will see an output folder with a Sintel subfolder and the output file named sintel _ 1080p _ 3M _ ultrafast.mp4.
Encoding Multiple Files in a Folder in Bash
These last two sections will teach you how to encode multiple files in a folder using a for loop in Bash and the for each command in PowerShell. To save a bit of size on the screenshots, I’ll exclude the top variables that are unchanged and available in the downloadable scripts. Note that for this exercise, the sources folder has three copies of Sintel (Sintel1.mp4, Sintel2.mp4, and Sintel3.mp4).
Figure 9 (below) shows the relevant section from S4-filelist.sh. Most of the script is identical to the previous script, including the base, output folder, and output file name.
![Executing a for loop in Bash](http://dzceab466r34n.cloudfront.net/Images/ArticleImages/InlineImages/151977-Ozer-FFmpeg-Fig9-ORG.png)
Figure 9. Executing a for loop in Bash
What’s new is the for loop on line 14. Operationally, here’s what it entails:
inputfolder="./sources"
identifies the input folder as sources.files=$(ls $inputfolder/*.mp4)
tells Bash to list (ls) all files in the input folder with an MP4 extension. These are the three Sintel files the script will process.- Surrounding it with
$()
makes Bash execute that command and store its output into an array variable defined as ${files[@]}
. - Bash then runs each input file in the file list through the FFmpeg command below the output line in Figure 9, encoding each file and
placing it into its respective output folder.
After running this script, you’ll see the output folder with three separate subfolders for each input file (Figure 10, below). Each subfolder will contain an output file with the source name followed by _ 1080p _ 3M _ ultrafast.mp4.
![S4-filelist.sh will create output folders for each source file and store the encoded file in that folder.](http://dzceab466r34n.cloudfront.net/Images/ArticleImages/InlineImages/151978-Ozer-FFmpeg-Fig10-ORG.png)
Figure 10. S4-filelist.sh will create output folders for each source file and store the encoded file in that folder.
Encoding Multiple Files in a Folder in PowerShell
Now let’s perform the same operation in PowerShell using the script shown in Figure 11 (below).
![Running all MP4 files in the sources folder through thye FFmpeg command script](http://dzceab466r34n.cloudfront.net/Images/ArticleImages/InlineImages/151979-Ozer-FFmpeg-Fig11-ORG.png)
Figure 11. S4-filelist.sh will create output folders for each source file and store the encoded file in that folder.
Again, much of the script is unchanged. Here are the critical elements.
$files = Get-ChildItem-Path ".\sources" -Filter*.mp4
uses the Get-ChildItemcmdlet to grab all of the mp4 files in the “sources” folder and store the list in the $files variable.- foreach ($file in $files) tells PowerShell to apply the FFmpeg command script to all files in the
$files
variable, which are the three Sintel files the script will process. - PowerShell then runs each input file in the file list through the FFmpeg command below the output line in Figure 11, encoding each file and placing it into its own output folder.
When you run the script, you should see three output folders, each with the encoded and appropriately named file. If you dump a bunch of other MP4 files into the source folder, the script will encode those as well.
Between the descriptions herein and the sample scripts, you should be able to adopt these techniques to a range of codecs and encoding tasks.
The author wishes to thank Fabre Lambeau for his substantial technological contribution to this material.
Related Articles
Anytime you start working with a new codec, there are some fundamental tests that you should run to achieve optimal performance/quality optimization. In this article, I'll take you through those tests while encoding VVC using a version of FFmpeg that includes the Fraunhofer VVC codec.
21 Mar 2023
Jan Ozer will lead workshops, presentations, and panels covering advanced codecs, gear for remote productions, WebRTC, low latency, and reducing bandwidth and storage costs at Streaming Media East in Boston May 23-25.
05 May 2022
Robert Reinhardt's workshops will cover the latest tips and tricks for using FFmpeg and managing inputs and outputs for videoconferencing, while presentations and panel discussions will look at taking your webcasting and event streaming efforts to the next level.
26 Apr 2022
Rather than focusing on random tasks, this tutorial will walk you through the fundamentals of encoding with the latest version of FFmpeg.
27 Mar 2022
The good news: As always, Moscow State's codec studies are some of the most comprehensive available. The bad news: Unless you're TikTok or Tencent, you won't have access to some of the best performers.
11 Jun 2021
Anyone who does performance or benchmark testing, please take a look: The six commands in this article help with essential tasks that crop up in any studio or encoding facility.
22 Jul 2019
With just a few beginner-level scripts, you can encode and package multiple filds to HLS and DASH output using open source tools.
20 Feb 2019
AV1 delivers equivalent quality to HEVC, but with a lower data rate. For now, though, it's slow. A five-second clip took 23 hours and 46 minutes to encode.
27 Sep 2018
Companies and Suppliers Mentioned