How to split video files upon detected change in resolution? (ie. recorded streams containing multiple widths)
I have many video files (flv/mp4) that change resolution mid-stream (720p/1080p/640).
I would like to use something like ffmpeg to automatically detect the change in resolution and split the videos into their individual scenes, keeping their existing encoding quality: segment without re-encoding (Video1-scene1.flv, Video1-scene2.flv, etc).
I have found various methods to split upon silence, upon scene change, upon black scenes, but nothing as simple as a change in resolution width.
These files often cause video editors/trimmers to crash, and manually identifying and removing low resolution scenes is very time consuming if I simply convert the entire video to 1080p.
The algorithm is probably as simple as comparing the current frame to the previous frame, and if they are different resolutions, split the video and start a new segment.
Can anyone help please?
EXAMPLE FILE (temporarily available)
: Example.flv
Below are a few related answers:
Cut part from video file from start position to end position with FFmpeg
https://stackoverflow.com/questions/36074224/how-to-split-video-or-audio-by-silent-parts
Automatically split large .mov video files into smaller files at black frames (scene changes)?
Split Up a Video Using FFMPEG through Scene Detection
Additional Resources:
Using Lord Neckbeard's solution, I am attaching the (unfortunately very manual) process that I eventually got to work on Windows10. Perhaps it can spawn other ideas around ffmpeg.
(I posted a separate question for anyone who is GURU enough to: Automate this process...)
drag video onto a batch file that contains:
ffprobe -v error -show_entries frame=pkt_pts_time,width,height -select_streams v -of csv=p=0 %1 > allkeyframes.txt
(allkeyframes.txt sample...)
13.652000,640,480
13.755000,640,480
13.597000,480,360
13.652000,480,360
paste this text in POWERSHELL:
$o=""
$n=0
foreach($line in (Get-Content allkeyframes.txt)){
$nline = $line.Split(",")
$nline2=($nline[1]+$nline[2])
if ($nline2 -ne $preventry) {
$n0=$nline[0]
$o="$o,$n0"
$n=$n0
}
$preventry = $nline2
}
if ($o.length -gt 1){
echo $o.Remove(0,1) > resolutionchanges.txt
} else {
Write-Host "No resolution changes found" -ForegroundColor Red
}
(resolutionchanges.txt:)
13.597000,25.618000,33.628000,45.598000
enter the following in the command window, after pasting contents of resolutionchanges.txt and changing "input.flv" to the video name (Win10 refused my attempts at batching this):
ffmpeg -i input.flv -map 0 -c copy -segment_times 13.597000,25.618000,33.628000,45.598000 -reset_timestamps 1 -f segment output_%03d.flv
Solution 1:
Lazyman solution is to use the scale filter to conform each input to a desired width x height.
For your proposed solution you can use ffprobe
to determine when size changes occur.
ffprobe -v error -show_entries frame=pkt_pts_time,width,height -select_streams v -of csv=p=0 input.flv
Example output excerpt during width and height change (key_frame, pkt_pts_time, width, height):
0,89.081000,480,640
0,89.281000,480,640
0,89.481000,480,640
1,89.568000,640,480
0,89.592000,640,480
0,89.632000,640,480
1,97.749000,480,640
The size changes will likely occur on key frames which is where you can make the cuts if stream copying is a requirement. If you just want the key frames add the -skip_frame nokey
option.
Then use the segment muxer:
ffmpeg -i input.flv -map 0 -c copy -segment_times 89.568,97.749 -reset_timestamps 1 -f segment output_%03d.flv