Bulk video conversion using PowerShell script
This Windows PowerShell script performs bulk video files compression with result filename configuration.
It is supplied with an additional Windows Batch script to run from the command line easily and a Windows Registry script to configure execution from the Windows folder background context menu.
Features
- Conversion to mp4 format
- Multiple input file path wildcard patterns in a single invocation
- 90, 180 and 270 degree clockwise rotation
- Resizing with a single maximum length of the video frame for both horizontal and vertical sides
- Truncation using start and end time points
- Awareness of slow motion videos where the real frame rate is 120/1 fps (e.g. Apple Slow Motion)
- Output filename prefix presenting the video creation date with an optional instruction to use a specific time zone
- Optional filename prefix for videos related to Apple Live Photos
- Modification of creation date/time by specifying delta values for hours, minutes and seconds
- Configurable output directory
- Result file last modification time equality to the video creation time
- Debug mode showing available time zones for usage in filename prefix and detailed video properties for each input file during the process
Overview
All script features are reflected in its input parameters with their default values suitable for some home media library (of cause they can be changed easily). All parameters, default values and some description can be found at the head of the PowerShell script.
The script uses ffmpeg
and ffprobe
free tools to convert and analyze video files, respectively. These tools should be downloaded separately and unpacked to be ready for usage from the script. Paths to these tools are hard-coded in the script and should be manually changed.
No administration rights required to install or run the tools and the script itself.
The complete set of files:
ffmpeg.exe
- free tool to convert videosffprobe.exe
- free tool to analyze videosany2mpeg.ps1
- Windows PowerShell script, has hard-coded paths to free tools mentioned aboveany2mpeg.cmd
- Windows Batch script to run Windows PowerShell script mentioned above, has hard-coded path to Windows PowerShell script mentioned aboveany2mpeg_install.reg
- Windows Registry script to add useful Windows folder background context menu item, has hard-coded path to Windows Batch script mentioned aboveany2mpeg_uninstall.reg
- Windows Reistry script to remove Windows folder background context menu item added using script mentioned above
Installation
- Download and unpack
ffmpeg
andffprobe
tools from: https://www.ffmpeg.org/download.html - Create scripts using sources below
- Update hard-coded paths to
ffmpeg.exe
andffprobe.exe
inany2mpeg.ps1
script - Update hard-coded path to
any2mpeg.ps1
inany2mpeg.cmd
script - Update hard-coded path to
any2mpeg.cmd
inany2mpeg_install.reg
script - Add
any2mpeg.cmd
parent directory to thePATH
Windows user variable in order to be able to use the script without specifying its full location path - Execute
any2mpeg_install.reg
in order to be able to run the script from Windows context menu with default parameters in the current Windows folder with specified output directory
Usage
Below are some some usage examples for video processing.
Example 1
any2mpeg
The above command converts all video files in the current folder with default script parameter values (i.e. max length of video frame side size is 720, creation date and iPhone Live Photo filename prefixes enabled, output folder is the current one).
Example 2
any2mpeg -files IMG001.mov -rotate 90 -startTime 00:00:03 -duration 00:00:06
This command converts IMG001.mov video file in the current folder with 90 degree clockwise rotation, starts from the 3rd second and 6 seconds long.
Example 3
any2mpeg -files *.avi -noFileNameTimePrefix -noFileNamePhotoPrefix -noUpdateLastWriteTime
This one converts all AVI video files in the current folder without output filename prefixes and file last modification time manipulation.
Example 4
any2mpeg -files *.mov -shiftHours 3 -fileNameTimeZone 'Pacific Standard Time'
And this one converts all MOV video files in the current folder with creation time shift by 3 hours ahead and using standard Pacific time zone in filename prefix.
Example 5
Windows context menu item converts all video files in the current folder with default parameters and places results into the nested 'out' folder.
Output
The execution progress looks like below:
Parameters:
files = D:\docs\photo\Import\test
rotate = 0
maxSide = 720
startTime =
duration =
shiftHours = 0
shiftMinutes = 0
shiftSeconds = 0
noFileNameTimePrefix = False
noFileNamePhotoPrefix = False
fileNameTimeZone = Russian Standard Time
outDir = D:\docs\photo\Import\test\out
noUpdateLastWriteTime = False
debug = False
------------------------------------------------------
Input path: D:\docs\photo\Import\test\IMG_3645.MOV
Input created at: 2017-05-04 20:41:47 +03:00
Input resolution: 1920 x 1080
Input duration: 8.706667 sec
Output created at: 2017-05-04 20:41:47 +03:00
Output size ratio: 0.375
Output path: D:\docs\photo\Import\test\out\20170504-204147-IMG_3645.MOV.mp4
Process time: 8.1214645 sec
------------------------------------------------------
Input path: D:\docs\photo\Import\test\IMG_3672.MOV
Input created at: 2017-05-06 15:42:23 +03:00
Input resolution: 1440 x 1080
Input duration: 3.536667 sec
Output created at: 2017-05-06 15:42:23 +03:00
Output size ratio: 0.5
Output path: D:\docs\photo\Import\test\out\20170506-154223-photo-IMG_3672.MOV.mp4
Process time: 3.7352136 sec
Total process time: 00:00:13.2367571
Press any key to continue . . .
Sources
any2mpeg.ps1
Param (
[string[]] $files = '*.*', # Multiple values separated by comma. Ex.: 1.mov,2.mp4,somedir/*.mov
[int] $rotate = 0, # Ex.: 0, 90, 180, 270
[int] $maxSide = 720,
[string] $startTime = $Null, # Ex.: 00:00:05.000
[string] $duration = $Null, # Ex.: 00:01:12.500
[int] $shiftHours = 0, # 0Ex.: 0, -1, 3
[int] $shiftMinutes = 0, # Ex.: 0, -1, 3
[int] $shiftSeconds = 0, # Ex.: 0, -1, 3
[switch] $noFileNameTimePrefix = $False, # If parameter specified without value, its value is $True
[switch] $noFileNamePhotoPrefix = $False, # If parameter specified without value, its value is $True
[string] $fileNameTimeZone = [System.TimeZoneInfo]::Local.Id, # Ex.: 'Russian Standard Time'
[string] $outDir = $Null,
[switch] $noUpdateLastWriteTime = $False, # If parameter specified without value, its value is $True
[switch] $debug = $False # If parameter specified without value, its value is $True
)
# Stop script execution if errors occur
$ErrorActionPreference = "Stop"
# Define tools' locations
$ffmpegExe = 'D:\inst\UTILS\ffmpeg\ffmpeg-20161208-3ab1311-win64-static\bin\ffmpeg.exe'
$ffprobeExe = 'D:\inst\UTILS\ffmpeg\ffmpeg-20161208-3ab1311-win64-static\bin\ffprobe.exe'
# Define ffprobe parameters
$ffprobeParams = '-hide_banner -loglevel fatal'
$ffprobeParamsRawValue = $ffprobeParams + ' -print_format default=noprint_wrappers=1:nokey=1 -select_streams v:0 -show_entries'
# Show actual script parameters
""
"Parameters:"
""
" files = " + $files
" rotate = " + $rotate
" maxSide = " + $maxSide
" startTime = " + $startTime
" duration = " + $duration
" shiftHours = " + $shiftHours
" shiftMinutes = " + $shiftMinutes
" shiftSeconds = " + $shiftSeconds
" noFileNameTimePrefix = " + $noFileNameTimePrefix
" noFileNamePhotoPrefix = " + $noFileNamePhotoPrefix
" fileNameTimeZone = " + $fileNameTimeZone
" outDir = " + $outDir
" noUpdateLastWriteTime = " + $noUpdateLastWriteTime
" debug = " + $debug
# Define rotation angle, if required
For ($i = 0; $i -lt ($rotate / 90); $i++)
{
$ffmpegParamsRotate = $ffmpegParamsRotate + 'transpose=1,'
}
# Define start point of input movie, if required
if ($startTime -ne $Null -and $startTime -ne '')
{
$ffmpegParamsStartTime = '-ss ' + $startTime
}
# Define end point of input movie, if required
if ($duration -ne $Null -and $duration -ne '')
{
$ffmpegParamsDuration = '-t ' + $duration
}
# Get the list of input files excluding directories
$movies = @()
ForEach ($file in $files)
{
$movies += Get-ChildItem -Path "$file" | Where-Object { !$_.PSIsContainer }
}
# Show available time zones, if in debug mode
if ($debug)
{
""
"----------------"
[System.TimeZoneInfo]::GetSystemTimeZones() |
ForEach-Object {'ID = [' + $_.Id + '] OFFSET = [' + $_.BaseUtcOffset + ']'}
"----------------"
}
# Remember start time of all items conversion
$scriptProcessStartTime = Get-Date
# Iterate over the input files and perform conversion for each found movie
ForEach ($movie in $movies)
{
# Skip files without video content (i.e. must be 2 or more frames)
$duration_ts = (& $ffprobeExe $ffprobeParamsRawValue.Split() stream=duration_ts $movie.FullName)
if ($duration_ts -notmatch '^[0-9]+$' -or ([int] $duration_ts) -le 1)
{
continue
}
# Remember start time of the current item conversion
$itemProcessStartTime = Get-Date
""
"------------------------------------------------------"
"Input path: " + $movie.FullName
# Show movie detailed info, if in debug mode
if ($debug)
{
$info = (& $ffprobeExe $ffprobeParams.Split() -show_format -show_streams $movie.FullName)
"----------------"
$info
"----------------"
}
# Define movie creation date/time, try to get it from Apple metadata
$createdAt = (& $ffprobeExe $ffprobeParamsRawValue.Split() format_tags=com.apple.quicktime.creationdate $movie.FullName)
if ($createdAt -eq $Null)
{
# If Apple metadata is not present, try to get it from standard property
$createdAt = (& $ffprobeExe $ffprobeParamsRawValue.Split() format_tags=creation_time $movie.FullName)
if ($createdAt -eq $Null)
{
# If standard property is not available, use last write date/time of the movie file
$createdAt = (ls $movie.FullName).LastWriteTime
}
}
$createdAt = [datetime] $createdAt
"Input created at: " + $createdAt.ToString("yyyy-MM-dd HH:mm:ss zzz")
# Modify creation date/time by adding hours, minutes and/or seconds, if required
$createdAt = $createdAt + (New-TimeSpan -Hours $shiftHours -Minutes $shiftMinutes -Seconds $shiftSeconds)
# Define date/time prefix, if required
if (!$noFileNameTimePrefix)
{
$timeZone = [System.TimeZoneInfo]::FindSystemTimeZoneById($fileNameTimeZone)
$timePrefix = ([system.TimeZoneInfo]::ConvertTime($createdAt, $timeZone)).ToString("yyyyMMdd-HHmmss") + '-'
}
# Define Apple live photo awareness prefix, if required
if (!$noFileNamePhotoPrefix)
{
$photoPrefix = ""
$livePhoto = (& $ffprobeExe $ffprobeParamsRawValue.Split() format_tags=com.apple.quicktime.content.identifier $movie.FullName)
if ($livePhoto -ne $Null)
{
$photoPrefix = "photo-"
}
}
# Define Apple slow motion (slomo) awareness (where there is a need to 4x slow down video and audio streams)
$sloMotion4x = ""
$realFrameRate = (& $ffprobeExe $ffprobeParamsRawValue.Split() stream=r_frame_rate $movie.FullName)
if ($realFrameRate -ne $Null -and $realFrameRate -match '^120/1$')
{
$sloMotion4x = " -filter:v setpts=4*PTS -filter:a atempo=0.5,atempo=0.5"
}
# Get movie frame width
$width = [int] (& $ffprobeExe $ffprobeParamsRawValue.Split() stream=width $movie.FullName)
# Get movie frame height
$height = [int] (& $ffprobeExe $ffprobeParamsRawValue.Split() stream=height $movie.FullName)
# Get movie duration
$fullDuration = [double] (& $ffprobeExe $ffprobeParamsRawValue.Split() format=duration $movie.FullName)
# Define scale ratio for the movie according to defined max side size parameter
$sizeRatio = $maxSide / [math]::Max($width, $height)
# Define output directory
$newPath = $movie.Directory.FullName
if ($outDir -ne $Null -and $outDir -ne '')
{
New-Item -ItemType Directory -Force -Path $outDir | Out-Null
$newPath = $outDir
}
# Define output path
$newPath = $newPath + '\' + $timePrefix + $photoPrefix + $movie.Name + ".mp4"
"Input resolution: " + $width + " x " + $height
"Input duration: " + $fullDuration + " sec"
"Output created at: " + $createdAt.ToString("yyyy-MM-dd HH:mm:ss zzz")
"Output size ratio: " + $sizeRatio
"Output path: " + $newPath
# Deleted previous result of conversion, if present
if (Test-Path $newPath)
{
Remove-Item $newPath
}
# Define conversion parameters
$ffmpegParams = "-loglevel fatal -pix_fmt yuv420p " +
$ffmpegParamsStartTime + " " + $ffmpegParamsDuration +
" -vf " + $ffmpegParamsRotate + "scale=trunc(iw*" + $sizeRatio + "/2)*2:trunc(ih*" + $sizeRatio + "/2)*2" +
$sloMotion4x
# Perform conversion
& $ffmpegExe -hide_banner -i $movie.FullName $ffmpegParams.Split() -map_metadata 0:g $newPath
# Set last write date for output movie filem if required
if (!$noUpdateLastWriteTime)
{
(ls $newPath).LastWriteTime = $createdAt
}
# Show time taken to process current movie
"Process time: " + ($(Get-Date) - $itemProcessStartTime).TotalSeconds + " sec"
}
""
"Total process time: " + ($(Get-Date) - $scriptProcessStartTime).ToString()
any2mpeg.cmd
@echo off
powershell -ExecutionPolicy Bypass -NoProfile -Command any2mpeg.ps1 %*
any2mpeg_install.reg
Windows Registry Editor Version 5.00
[HKEY_CURRENT_USER\Software\Classes\Directory\Background\shell\any2mpeg]
@="&any2mpeg to 'out\\...'"
"Icon"="%SystemRoot%\\System32\\shell32.dll,71"
[HKEY_CURRENT_USER\Software\Classes\Directory\shell\any2mpeg]
@="&any2mpeg to its 'out\\...'"
"Icon"="%SystemRoot%\\System32\\shell32.dll,71"
[HKEY_CURRENT_USER\Software\Classes\Directory\Background\shell\any2mpeg\command]
@="D:\\INST\\UTILS\\util\\any2mpeg.cmd -files \"'%V'\" -outDir \"'%V\\out'\" & pause"
[HKEY_CURRENT_USER\Software\Classes\Directory\shell\any2mpeg\command]
@="D:\\INST\\UTILS\\util\\any2mpeg.cmd -files \"'%V'\" -outDir \"'%V\\out'\" & pause"
any2mpeg_uninstall.reg
Windows Registry Editor Version 5.00
[-HKEY_CURRENT_USER\Software\Classes\Directory\Background\shell\any2mpeg]
[-HKEY_CURRENT_USER\Software\Classes\Directory\shell\any2mpeg]