FFmpeg take screenshots from videos with variable duration - ffmpeg

I have bunch of videos which are rather long, so I take screenshots of 10th second (-ss 00:00:10). Sometimes videos are very short, like 5 seconds, and -ss 00:00:10 fails.
I don't have an option to compute video size as don't have an option to download them whole (videos are hosted on S3 and used as streams through CloudFront).
Maybe there are some built-in options that I overlooked?
What I really don't want to do is shorten -ss option gradually on fails so it would be the last resort.

One liner:
ffprobe -show_entries format=filename,duration -of default=noprint_wrappers=1:nokey=1 /path/to/input/file -loglevel 0 | awk 'BEGIN {RS="";FS="\n"}{system("ffmpeg -ss "$2/2" -i "$1" -vframes 1 out.png") }'
meaning:
use ffprobe to get the file duration in seconds then pipe to awk and execute the frame extraction ffmpeg command using a seek time equal to duration/2

Related

ffmpeg: cutting 20 seconds of video starting from the middle time in a bash cycle

im in a bash script looping a dir full of .mp4 video files each one with a different lenght.
Now: how can i cut 20 seconds starting in the exact middle of any video?
For a single file i can read manually the duration of the video so i know at what second start cutting: e.g.
for a 120 sec. video i use
ffmpeg -i "tmpfile.mp4" -ss 60 -t 20 -c copy "outputfile.mp4" 2>/dev/null
but now the problem is that the "-ss" value should be a variable with the (total lenght of video/2)
some videos are long less than a minute, some are long many minutes, and some are more than one hour.
Thanks
Cant find a middle time value to start cutting
Use ffprobe
len=$(ffprobe -v error -show_entries format=duration -of compact=p=0:nk=1 input.mp4)
mid=$((${len%.*} / 2))
ffmpeg -v error -ss $mid -i input.mp4 -t 20 -c copy output.mp4

ffmpeg - avoid duration approximation of generated files

I'm trying to generate videos with high precision durations, but all of the results always end up with their duration approximated leaving out milliseconds.
E.g. ffmpeg -y -loop 1 -i "blank.png" -tune stillimage -t 9.983002003392 test.mkv
This example is supposed to give the file a duration of 9 seconds and 983002003392 milliseconds, though those milliseconds get lost when I check the duration using ffbrobe ffprobe test.mkv -show_entries format=duration -v quiet -of csv="p=0", it shows 10.000000 instead.
This doesn't happen when generating audio files instead, like ffmpeg -y -f lavfi -i anullsrc=sample_rate=48000 -t 9.983002003392 test.wav
When I check its duration it shows 9.98, which is not the precision I asked but at least it's not approximated to just seconds.
The duration is then further messed up by using concat on the videos and mixing audio files with the videos, but I suppose that's a problem related to the different settings of each file.
My question is: How can I generate a video with a high precision duration without approximating it to just seconds?

ffmpeg is cutting video length incorrectly

I use ffmpeg to check the length of the video and to cut video.
I have a video with the lenght of 19.458333 seconds that's why i want to cut it to only have 19 seconds because i don't want the floating point.
I check the video length using below command
ffprobe -i "video.avi" -show_entries format=duration -v quiet -of csv="p=0"
And I use below command to cut video the video
ffmpeg -i "video.avi" -ss 00:00:00 -t 00:00:19.000 -c copy out.mp4
or
ffmpeg -i "video.avi" -ss 00:00:00 -t 19 -c copy output.avi
The problem i have is when i cut the video with the above command and check the length the output file's length is 19.018 seconds. Can someone help me with this problem?
If your video is 30 frames per second, that means each frame is 1/30 or 0.0333 seconds per frame. So .018 is less than one frames duration.

Why does ffmpeg return a different framecount than ffprobe for the same file?

I'm trying to count the number of frames in a video but ffmpeg and ffprobe are giving me two different answers.
$ time ffprobe -v error -count_frames -select_streams v:0 -show_entries stream=nb_read_frames -of default=nokey=1:noprint_wrappers=1 myvideo.mp4
2858
real 0m2.987s
user 0m2.740s
sys 0m0.172s
When I check the same file with ffmpeg I get 2 more frames...
$ time ffmpeg -y -i myvideo.mp4 -vcodec copy -acodec copy -f null /dev/null 2>&1 | grep 'frame=' | awk '{print $2}'
2860
real 0m0.127s
user 0m0.080s
sys 0m0.032s
I used ffprobe to output all of the frames and count the number of "[FRAME]"s in the resultant output...
ffprobe -i myvideo.mp4 -show_frames -v error | grep -o '\[FRAME\]' | wc -l
2858
Which as you can see shows the number ffprobe thought there were.
Obviously I would prefer to use ffmpeg here because it is significantly faster than ffprobe and I am dealing with thousands of videos that need parsing and indexing. However the failure isn't consistent across multiple videos; sometimes it's 1 out, other times it's 2 or more...
Unfortunately, I have been counting frames for the past two years using the ffmpeg method, so I have a significant library of videos to reprocess now ... he gulps... I guess its a good way to verify the readability of the files on the cluster... even so, its going to take probably a few weeks to recalculate all of the existing video frame sizes.
Yes Mulvya was correct. I was able to get a consistent number by using FFprobe to query the nb_frames. By default ffprobe uses -count_frames which literally starts at the top of the file and counts the number of frames all the way to the bottom of the file. This is, of course, quite a slow operation, especially if the video file is quite large (which mine tend to be).
time ./ffprobe -v error -count_frames -select_streams v:0 -show_entries stream=nb_read_frames -of default=nokey=1:noprint_wrappers=1 big.mp4
real 1m4.531s
user 0m7.172s
sys 0m0.104s
FFmpeg writes to the stsz box, the number of frames it thinks is in the file when (in my case) it is converted from another format. So, I chose to use the value in stsz because although it is not correct, it is consistent and very quick to query
time ffprobe -v quiet -pretty -select_streams v:0 -show_entries "stream=nb_frames" big.mp4
real 0m0.023s
user 0m0.040s
sys 0m0.008s
Thanks Mulvya!

Specify percentage instead of time to ffmpeg

To get a thumbnail from an image halfway through the video I can do ffmpeg -ss 100 -i /tmp/video.mp4 -frames:v 1 -s 200x100 image.jpg. By using -ss 100 it gets a thumbnail at 100 seconds (which would be halfway through the video assuming the video is 200 seconds long).
But if I don't know the exact length of the video, in my application code I would need to use something like ffprobe to first determine the length of the video, and then divide it by 2 to get the thumbnail time.
Is there a way to get ffmpeg to get the thumbnail at the percentage of the video you want? So instead of specifying -ss 100, something like -ss 50% or -ss 20% to get a thumbnail from halfway or 20% into the file?
I know I can do this through application code, but it would be more efficient if there's a way for ffmpeg to handle this itself.
It's not pretty but:
ffmpeg -y -i logo-13748357.mp4 -vf "select=gte(n\,$(shuf -i 1-$(ffprobe -v error -select_streams v:0 -show_entries stream=nb_frames -of default=nokey=1:noprint_wrappers=1 logo-13748357.mp4) -n 1))" -vframes 1 out_img.png
The above command is an example of a one liner that grabs the number of frames in a video and passes that to the "shuf" command https://linux.die.net/man/1/shuf which randomly selects a number in the range of frames and the result of that is passed to ffmpeg.
So the above is able to randomly select a frame from a video with just one line.
You might be able to adapt this approach to your specific goal. Presuming of course it still matters seven years down the track.

Resources