For a project I'm working on I have a small bash script that loops over an input csv file of timecodes, and uses ffmpeg to create screenshots of a given film at each timecode. The csv file is in the format hh:mm:ss,id - it looks like this (extract)
00:00:08,1
00:00:49,2
00:01:30,3
00:02:38,4
00:03:46,5
00:04:08,6
00:04:26,7
00:04:37,8
00:04:49,9
00:05:29,10
00:05:52,11
00:06:00,12
00:06:44,13
00:07:49,14
00:08:32,15
00:09:28,16
00:10:17,17
00:10:44,18
00:11:48,19
00:12:07,20
I've used it without issue in the past, but today I've come to update some of the films and I'm getting a weird issue where ffmpeg is complaining that my input timecode is invalid, despite being in the right format.
The new input csv files are the same format as the old ones, but it seems like every so often ffmpeg drops the hours from the hh:mm:ss timestamp. If I comment out the ffmpeg line, everything prints to the terminal as expected (but obviously I get no screenshots).
This is my loop code:
while read code a
do
echo $code
f="$(printf "%03d" $i)"
ffmpeg -loglevel error -y -ss $code -i $FILM -vframes 1 -q:v 2 $OUTPUT/$f.jpg
((i++))
done < $INPUT
I've tried all sorts, including padding the csv with extra 0s - which works until the hours tick over to 01.
Does anyone have any ideas? I'm scratching my head.
Cheers
Ok - I'm not sure why, but I duplicated the id column to the left of the timestamp column.... so it goes id timestamp id. And it now works as expected.
Related
How can I extract every frame of an video with the respective timestamp?
ffmpeg -r 1 -i in.mp4 images/frame-%d.jpg
This code extracts all frames, but without timestamp. Since Windows do not allow ":" in filenames, I'll replace it for ".". I would like something like this:
frame-h.m.s.ms.random_value.jpg
frame-00.10.15.17.1.jpg
frame-00.11.16.04.2.jpg
frame-00.11.17.11.3.jpg
frame-00.11.17.22.4.jpg
frame-00.12.04.01.5.jpg
...
The reason of "random id" is to allow repeated frames without replacing them.
I have tried:
ffmpeg -r 1 -i rabbit.mp4 images/frame-%{pts\:hms}.jpg
But this doesn't work! => Could not open file : images/
I have no clue how can I do that! Can you help me? Thank you.
How do I cut a section out of a video with ffmpeg?
Imagine I have a 60 second mp4 A.
I want to remove all the stuff from 0:15 to 0:45.
The result should be a 30-second mp4, which is composed of the first 15 seconds of A directly followed by the last 15 seconds of A.
How can I do this without using concat?
I know how I could do it by creating two intermediary files and then using ffmpeg to concat them. I don't want to have to perform so much manual work for this (simple?) operation.
I have also seen the trim filder used for removing multiple parts from a video. All the usages I've found show that it seems to be very verbose, and I haven't found an example for a case as simple as I would like (just a single section removed).
Do I have to use trim for this operation? Or are there other less verbose solutions?
The ideal would of course be something at least simple as -ss 0:15 -to 0:45 which removes the ends of a video (-cut 0:15-0:45 for example).
I started from
https://stackoverflow.com/a/54192662/3499840 (currently the only answer to "FFmpeg remove 2 sec from middle of video and concat the parts. Single line solution").
Working from that example, the following works for me:
# In order to keep <start-15s> and <45s-end>, you need to
# keep all the frames which are "not between 15s and 45s":
ffmpeg -i input.mp4 \
-vf "select='not(between(t,15,45))', setpts=N/FRAME_RATE/TB" \
-af "aselect='not(between(t,15,45))', asetpts=N/SR/TB" \
output.mp4
This is a one-line linux command, but I've used the bash line-continuation character ('\') so that I can vertically align the equals-signs as this helps me to understand what is going on.
I had never seen ffmpeg's not and between operators before, but I found their documentation here.
Regarding the usual ffmpeg "copy vs re-encode" dichotomy, I was hoping to be able to use ffmpeg's "copy" "codec" (yeah, I know that it's not really a codec) so that ffmpeg would not re-encode my video, but if I specify "copy", then ffmpeg starts and stops at the nearest keyframes which are not sufficiently close to my desired start and stop points. (I want to remove a piece of video that is approximately 20 seconds long, but my source video only has one keyframe every 45 seconds!). Hence I am obliged to re-encode. See https://trac.ffmpeg.org/wiki/Seeking#Seekingwhiledoingacodeccopy for more info.
The setpts/asetpts filters set the timestamps on each frame to the correct values so that your media player will play each frame at the correct time.
HTH.
If you want to use the copy "codec", consider the following approach:
ffmpeg -i input.mp4 -t "$start_cut_section" -c copy part1.mp4&
ffmpeg -i input.mp4 -ss "$end_cut_section" -c copy part2.mp4&
echo "file 'part1.mp4'" > filelist;
echo "file 'part2.mp4'" >> filelist;
wait;
ffmpeg -f concat -i filelist -c copy output.mp4;
rm filelist;
This creates two files from before and after the cut, then combines them into a new trimmed final video. Obviously, this can be used to create as many cuts as you like. It may seem like a longer approach than the accepted answer, but it likely will execute much faster because of the use of the copy codec.
I am trying perform a batch operation to extract specific frames from multiple video files and save as a PNG, using a bash script. I hope to do this by using ffmpeg within a bash script, supplemented by a csv file that contains the name of the input video, the specific frame number to be extracted from the input video, and the output name of the PNG file.
The video files and the csv file would all be placed within the same folder. The script can also be placed in there if necessary.
My csv - called "select.csv" - currently takes the following format (input,output,frame):
mad0.m4v,mad0_out1,9950
mad0.m4v,mad0_out2,4500
mad1.m4v,mad1_out1,3200
My current script - called "frame.sh" - takes the following form:
#!/bin/bash
OLDIFS=$IFS
IFS=“,”
SDIR=/Users/myuser/Desktop/f-input/
cd $SDIR;
while read input output frame
do
echo "$input"
echo "$output"
echo "$frame"
input1=$input
output1=$output
frame1=$frame
ffmpeg -i "$input1" -vf select='eq(n\,'"$frame1"')' -vsync 0
"$output1".png
done < $1
IFS=$OLDIFS
This should allow me to run ./frame.sh select.csv to then process all relevant files in the "f-input" folder on my desktop and extract the specified frames.
I ended up echoing the variables read from the csv so that they could actually be used as variables and looped in the ffmpeg command because carrying out the ffmpeg command using $input, $frame and $output directly after the read operation only ever completed the process on the first line of the csv, without progressing further.
Essentially I would like the following to actually loop through each csv entry, instead of only the first line:
#!/bin/bash
OLDIFS=$IFS
IFS=“,”
SDIR=/Users/myuser/Desktop/f-input/
cd $SDIR;
while read input output frame
do
ffmpeg -i "$input" -vf select='eq(n\,'"$frame"')' -vsync 0 "$output".png
done < $1
IFS=$OLDIFS
Any and all advice appreciated!
Many thanks
Replace IFS=“,” with IFS=",".
I wrote a similar script that reads csv and process movies by ffmpeg.
It works well on the first line of the csv but fails after the second lines.
I found ffmpeg in a loop seems to affect the "read" command and trim the first character of the lines after the second line.
So I ended up with adding extra "garbage" column on the left-most side of the csv and let ffmpeg trim it.
my csv is like:
101,movie1.mp4
102,movie2.mp4
103,movie3.mp4
...
and the (simplified) script is like:
while IFS="," read id movie; do
ffmpeg -v quiet -s 1280x720 -i "$movie" "$id-$movie" </dev/null
done
it generates "101-movie1.mp4" for the first line of the csv just like I expect
but after the second line it generates "02-movie1.mp4" "03-movie3.mp4" and so force because ffmpeg (seems to have) trimmed the first character of the lines.
I added a garbage column on the 1st column like this
x,101,movie1.mp4
x,102,movie2.mp4
x,103,movie3.mp4
and fix the script:
while IFS="," read garbage id movie; do
ffmpeg -v quiet -s 1280x720 -i "$movie" "$id-$movie" </dev/null
done
this worked for me.
I got pictures named as
pic_0_new.jpg
pic_10_new.jpg
pic_20_new.jpg
...
pic_1050_new.jpg
which I want to turn into a video (Ubuntu ffmpeg). I tried the following
ffmpeg -start_number 0 -i pic_%d_new.jpg -vcodec mpeg4 test.avi
but I don't know how to set the step size and the end number. How to do this?
Thanks for help :)
If your files are named with leading zeroes then you can use the built-in globbing functionality. If not (like your's), you can create a file list and supply that as the input, just like explained here.
The other thing you need to set is the input framerate that tells FFmpeg what framerate should assume for the images (note that the option is ahead of the -i input option).
So the command should look like this:
ffmpeg -framerate 25 -i pic_%04d_new.jpg <encoding_settings> test.avi
Also note that you can use the filename expansion on files with or without leading zeroes (Thanks for #Gyan to pointing it out):
match regularly numbered files: %d (this is what you're using)
img_%d.png // This will match files with any number as a postfix starting from img_0.png
match leading zeroes: %0<number_of_digits>d
img_%03d.png // This will match files ranging from img_000.png to img_999.png
In addition, mpeg4/avi is not the most convenient encoder/container to use...
I have series of images with sorted names, like 0000000354 ... 0000008591
I have tried using ffmpeg or MEncoder to convert theme. In ffmpeg the problem is this that it will operate while the names are like 0000000001 ... 00000000009 with %010d syntax.I don't know what syntax i should use for my images names.in mencoder it will cover all images But when i play the output video it doesn't show images,I want to show every image in 5 seconds or somthing like this, any one can help?
ffmpeg can't read in arbitrarily named images. You'll have to rename them or do something clever with symlinks to get ffmpeg to take them as input.
From the man page:
If the pattern contains "%d" or "%0Nd", the first filename of the file
list specified by the pattern must contain a number inclusively
contained between 0 and 4, all the following numbers must be
sequential. This limitation may be hopefully fixed.
Let's say you have:
img000001.jpg
....
img000140.jpg
....
img010040.jpg
....
To encode it into a movie using ffmpeg just use the filename pattern:
ffmpeg -i "img%06d.png" -vcodec libx264 -vpre ipod640 output.mp4