At it's most simple, extract single image from frame of video:
ffmpeg -i video.mp4 -ss HH:MM:SS -vframes 1 image.jpg
#!/bin/bash
file="$1"
name="${file%.*}"
# Extract the first frame
ffmpeg -hide_banner -i "$file" -vf "select=eq(n\,0)" -vsync vfr "$name-first_frame.jpg"
# Set the frame interval
frame_interval=30
# Calculate digits required for frame numbering
total=$(ffmpeg -i "$file" -map 0:v:0 -c copy -f null -y /dev/null 2>&1 | grep -Eo 'frame= *[0-9]+' | grep -Eo '[0-9]+' | tail -1)
digits=${#total}
# Extract every nth frame and rename to include actual frame number
ffmpeg -hide_banner -i "$file" -vf "select='not(mod(n\,$frame_interval))',showinfo" -vsync vfr temp_%0${digits}d.jpg 2>&1 | grep 'pts_time' | awk -v name="$name" -v digits="$digits" -F'|' '{
split($5, a, "=");
split($8, b, "=");
printf "mv temp_%0" digits "d.jpg " name "-frame_%0" digits "d.jpg\n", b[2], b[2]*30
}' | bash
# Remove temporary files
rm temp_*.jpg
exit 0
Save as: frame_extract.sh
Change mode executable: chmod +x frame_extract.sh
Usage: ./frame_extract.sh input_file.mkv
# Define the file path as a parameter
param(
[Parameter(Mandatory=$true)]
[string]$FilePath
)
# Get the file name without extension
$name = [System.IO.Path]::GetFileNameWithoutExtension($FilePath)
# Extract the first frame
& ffmpeg -hide_banner -i $FilePath -vf "select=eq(n\,0)" -vsync vfr "$name-first_frame.jpg"
# Set the frame interval
$frameInterval = 30
# Calculate digits required for frame numbering with zero padding
$totalFramesOutput = & ffmpeg -i $FilePath -hide_banner -map 0:v:0 -c copy -f null - 2>&1
$totalFrames = ($totalFramesOutput | Select-String -Pattern 'frame=\s*[0-9]+' | ForEach-Object { $_.Matches } | ForEach-Object { $_.Value.TrimStart("frame=") } | Select-Object -Last 1)
$digits = $totalFrames.Length
# Extract every nth frame and rename to include actual frame number
$output = & ffmpeg -hide_banner -i $FilePath -vf "select='not(mod(n\,$frameInterval))',showinfo" -vsync vfr "temp_%0${digits}d.jpg" 2>&1
$output | Select-String -Pattern 'pts_time' | ForEach-Object {
if ($_ -match 'pts_time:(\d+)') {
$frameNumber = [int]($matches[1] * $frameInterval)
$paddedFrameNumber = $frameNumber.ToString("D$digits")
$tempFileName = "temp_" + $frameNumber.ToString("D$digits") + ".jpg"
$newFileName = "$name-frame_$paddedFrameNumber.jpg"
Rename-Item -Path $tempFileName -NewName $newFileName
}
}
# Remove temporary files
Remove-Item "temp_*.jpg"
I'm not the greatest at PowerShell, but this should be close.
I have no way to test it, but it should help. (Hopefully!)
Added logic to grab the first frame.
Output filename uses frame count, not sequential numbers.