FFMpeg: how to use between() to select last second? - ffmpeg

I'm trying to apply a caption using drawtext that should disappear one second before the video end:
ffmpeg -i input.mp4 -vf "drawtext=enable='between(t,0,5)':fontfile=font.ttf:text='Some caption':x=60:y=640:fontsize=40:fontcolor=#f0f0f0#0.9" -vcodec libx264 -crf 27 -preset ultrafast -strict -2 -acodec copy output.mp4
The problem is that I don't know the video length beforehand. I've tried using 'between(t,0,-1)' but it doesn't work, the caption never shows up.
Anyone knows if is there a way to do this without having to open the video first to check length and only after that draw the caption?
Thanks in advance!

FFmpeg does not convey the stream duration to filters, so this has to be done in a roundabout manner.
FFmpeg has a sseof function that can seek from the end of a file. It also has a copyts option to maintain timestamps. So, we load the input twice, once the full input, and the other, just the last second. Then we draw the text on the entire 1st input but overlay the last second from the 2nd input, which because of the retained timestamps, will be burnt in place.
ffmpeg -copyts -i input.mp4 -sseof -1 -i input.mp4 -filter_complex "[0]drawtext=fontfile=font.ttf:text='Some caption':x=60:y=640:fontsize=40:fontcolor=#f0f0f0#0.9[txt];[txt][1]overlay" -vcodec libx264 -crf 27 -preset ultrafast -acodec copy output.mp4

Related

concatenate audio files with an image

I am trying to concatenate multiple audio files and a single image into one video file using one command.
I have list of mp3 files and a playlist file (.m3u) in a direcotry.
I managed to do this but my solution is bad:
reading the playlist file and creating a new .txt in the ffmpeg required format
concatenating the audio files using the .txt into an .mp3
concatenating the large audio file and the static image into a video
This creates 2 unnecessary files that I have to delete.
I tried a different command
ffmpeg -loop 1 -framerate 1 -i myImage.jpg -i file1.mp3 -i file2.mp3 -i file3.mp3 -filter_complex '[0:0][1:0][2:0]concat=n=3:v=0:a=1' -tune stillimage -shortest output.mp4
however im getting a Error initializing complex filters.
Invalid argument error
Another kick in the nuts is that the system im working on has spaces in the folder names.
i tried using -i "concat:file1.mp3|file2.mp3|..." however i cannot use double quote marks to quote out the path so I get an invalid argument error.
Thank you very much for your help.
Method 1: concat demuxer
Make input.txt containing the following:
file 'file1.mp3'
file 'file2.mp3'
file 'file3.mp3'
Run ffmpeg:
ffmpeg -loop 1 -framerate 1 -i myImage.jpg -f concat -i input.txt -filter_complex "[0]scale='iw-mod(iw,2)':'ih-mod(ih,2)',format=yuv420p[v]" -map "[v]" -r 15 -tune stillimage -map 1:a -shortest -movflags +faststart output.mp4
All MP3 files being input to the concat demuxer must have the same channel layout and sample rate. If they do not then convert them using the -ac and -ar options so they are all the same.
Method 2: concat filter
Update: There seems to be a bug with -shortest not working with the concat filter (I keep forgetting about that). See the method above using the concat demuxer, or replace -shortest with -t. The value for -t should equal the total duration of all three MP3 files.
ffmpeg -loop 1 -framerate 1 -i myImage.jpg -i file1.mp3 -i file2.mp3 -i file3.mp3 -filter_complex "[0]scale='iw-mod(iw,2)':'ih-mod(ih,2)',format=yuv420p[v];[1:a][2:a][3:a]concat=n=3:v=0:a=1[a]" -map "[v]" -r 15 -map "[a]" -tune stillimage -shortest -movflags +faststart output.mp4
Option descriptions
scale filter makes image have even width and height which is required when outputting YUV 4:2:0 with libx264.
format filter sets chroma subsampling to 4:2:0, otherwise libx264 will try to limit subsampling, but most players can only handle 4:2:0.
concat filter is accepting file1.mp3, file2.mp3, and file3.mp3 as inputs. Your original command was trying to concat the video to the audio resulting in Invalid argument.
-map "[v]" chooses the video output from -filter_complex.
-r 15 sets output frame rate to 15 because most players can't handle 1 fps. This is faster than setting -framerate 15.
-map "[a]" chooses the audio output from -filter_complex.
-map 1:a chooses the audio from input #1 (the second input as counting starts from 0).
-movflags +faststart after encoding finishes this option moves some data from the end of the MP4 output file to the beginning. This allows playback to begin faster otherwise the complete file will have to be downloaded first.

FFMPEG: How to avoid audio/video desync in output of crossfaded clips when input is variable frame rate video

I'm doing screen recordings of gameplay (Dota2) using my NVIDIA graphics card GeForce experience hardware recording (NVEC Encoder). This creates a variable frame rate output video. My NVIDIA settings are 60 fps 15000 kbps. I have paid a guy to make a program that generates scripts that given start/stop timepoints can extract clips from the video and merge them with crossfade. See example code below. The script works for many input recordings but fails often: The audio and video are desynchronized (usually audio delay) in many of the clips, ca 0.5 seconds. I think it fails more when frame rate dropped more during recording. He does not know how to fix the problem, and I wonder if anyone could point out if anything could be fixed in the script (example below)?
Processing speed is quite important (now making a 10 min 'highlight' video takes ca 7-10 min). Solutions increasing that amount very much more is not of too big interest, unfortunately. His approach has been to work separately with audio and video and merge in the end. He already has a program to make ffmpeg code for working with different scenarios (also adding overlays, adding music, intro/outro) so it would be preferable with some easy fixes to his code and not dramatic redesigning of the logic. But if nothing else can fix the problem, a redesign in logic is ok. Using other tools than ffmpeg is also ok, but should be automatable (scripts/cli) and not increase processing times too much.
Running the program "mediainfo" on the input video shows that framerate dropped quite low for this input video:
Frame rate mode: Variable
Frame rate : 60.000 FPS
Minimum frame rate: 3.059 FPS
Maximum frame rate: 63.739 FPS
Full report here: https://pastebin.com/TX061Wih
The input video can be downloaded from dropbox here (6 GB):
https://www.dropbox.com/s/ftwdgapazbi62pr/fullgame.mp4?dl=0
Here the example of a script when asked to extract two clips from input video at 9:57 (41 sec length) and 15:45 (28 sec length) and crossfade merge them with a 0.5 crossfade time. There might be some code-remnants from options that are not used in this example (overlays, music, intro/outro). Using the input video above, this creates audio/video desync.
6 commands excecuted in sequence:
ffmpeg.exe -loglevel warning -ss 00:09:57 -i fullgame.mp4 -t 00:00:41 -filter_complex "[0:a]afade=t=out:st=40.5:d=0.5[a1]" -map "[a1]" -y out_temp_00.mp4.wav
ffmpeg.exe -loglevel warning -i fullgame.mp4 -ss 00:09:57 -t 00:00:41 -an -vcodec copy -f mpegts -avoid_negative_ts make_zero -y out_temp_00.mp4.ts
ffmpeg.exe -loglevel warning -ss 00:15:45 -i fullgame.mp4 -t 00:00:28 -filter_complex "[0:a]afade=t=in:st=0:d=0.5[a1]" -map "[a1]" -y out_temp_01.mp4.wav
ffmpeg.exe -loglevel warning -i fullgame.mp4 -ss 00:15:45 -t 00:00:28 -an -vcodec copy -f mpegts -avoid_negative_ts make_zero -y out_temp_01.mp4.ts
ffmpeg.exe -loglevel warning -i out_temp_00.mp4.wav -i out_temp_01.mp4.wav -y -filter_complex "[0:a]adelay=0|0[a0];[1:a]adelay=40500|40500[a1];[a0][a1]amix=inputs=2:dropout_transition=68.5,atrim=duration=68.5[outa0];[outa0]loudnorm[outa]" -map "[outa]" -ar 48000 -acodec aac -strict -2 fullgame_Output.mp4.aac
ffmpeg.exe -loglevel warning -i out_temp_00.mp4.ts -i out_temp_01.mp4.ts -y -i fullgame_Output.mp4.aac -filter_complex "[0:v]trim=start=0.5,setpts=PTS-STARTPTS[0c];[1:v]trim=start=0.5,setpts=PTS-STARTPTS[1c];[0:v]trim=40.5:41,setpts=PTS-STARTPTS[fo];[1:v]trim=0:0.5[fi];[fi]format=pix_fmts=yuva420p,fade=t=in:st=0:d=0.5:alpha=1[z];[fo]format=pix_fmts=yuva420p,fade=t=out:st=0:d=0.5:alpha=1[x];[z]fifo[w];[x]fifo[q];[q][w]overlay[r];[0c][r][1c]concat=n=3[outv]" -map "[outv]" -map 2:a -shortest -acodec copy -vcodec libx264 -preset ultrafast -b 15000k -aspect 1920:1080 fullgame_Output.mp4
P.S.
I already asked for help at an ffmpeg chat room. One guy said he knew what the problem was, but didnt know how to fix it(?):
[00:10] <kepstin> oh, wait, you're using -vcodec copy
[00:10] <kepstin> that explains everything.
[00:10] <kepstin> when you're using -vcodec copy, the start time (set with -ss) is rounded to the nearest keyframe
[00:10] <kepstin> it's not exact
[00:11] <kepstin> depending on the keyframe interval, this will result in possibly quite large shifts
[00:11] <kepstin> (also, your commands are applying audio filters on commands with -an, which is confusing/contradictory)
[00:12] <birdboy88> so the problem is that the audio temporary clips are not being extracted from the same excat timepoints?
[00:13] <kepstin> birdboy88: yeah, your audio is being re-encoded to wav so it's being cut sample-accurate, but the video's not being precisely cut.
[00:16] <birdboy88> kepstin: so I need to use slow seek (?) to extract video accurately? Or somehow extract audio only where there are video keyframes?
[00:17] <kepstin> birdboy88: i don't know how to extract audio starting at video keyframes with ffmpeg cli. You're already doing slow seek, which doesn't help (you should move the -ss option to before the -i option to speed it up)
[00:17] <kepstin> if you want accurate video cutting when saving to a file, you have to re-encode the video
[00:18] <kepstin> (doing this in a single ffmpeg command means you don't have to save to a file, so you can avoid the issue)
[00:18] * kepstin is off for a bit now
EDIT:
Everything is done with the latest ffmpeg version.
I was unable to get Gyan's code to work. It always loses some audio (audio is either 40.5 or 27.5, so only one audio is used). This is the only one working for me (changes were adelay=40500|40500 and amix=inputs=2[a0];[a0]loudnorm):
ffmpeg -i fullgame.mp4 -filter_complex "[0]split=2[vpre][vpost];
[0]asplit=2[apre][apost];
[vpre]trim=start='00:09:57':duration='00:00:41',setpts=PTS-STARTPTS[vpre-t];
[apre]atrim=start='00:09:57':duration='00:00:41',asetpts=PTS-STARTPTS,afade=t=out:st=40.5:d=0.5[apre-t];
[vpost]trim=start='00:15:45':duration='00:00:28',setpts=PTS-STARTPTS,format=yuva420p,fade=t=in:st=0:d=0.5:alpha=1,setpts=PTS+40.5/TB[vpost-t];
[apost]atrim=start='00:15:45':duration='00:00:28',asetpts=PTS-STARTPTS,afade=t=in:st=0:d=0.5,adelay=40500|40500[apost-t];
[vpre-t][vpost-t]overlay[v];
[apre-t][apost-t]amix=inputs=2[a0];[a0]loudnorm[a]" -map "[v]" -map "[a]" -y -c:v libx264 -preset ultrafast -b:v 15000k -aspect 1920:1080 -c:a aac fullgame_Output.mp4
Then I tried using a similar setup but with 3 clips, but on one machine I got error: "Error while filtering: Cannot allocate memory". And my 16 GB memory machine the processing speed is 0.02x! Any way to avoid this? This is the code I tried:
ffmpeg -i fullgame.mp4 -filter_complex "[0]split=3[vpre][vpost][v3];
[0]asplit=3[apre][apost][a3];
[vpre]trim=start=357:duration=41,setpts=PTS-STARTPTS[vpre-t];
[apre]atrim=start=357:duration=41,asetpts=PTS-STARTPTS,afade=t=out:st=40.5:d=0.5[apre-t];
[vpost]trim=start=795:duration=28,setpts=PTS-STARTPTS,format=yuva420p,fade=t=in:st=0:d=0.5:alpha=1,fade=t=out:st=40.5:d=0.5:alpha=1,setpts=PTS+40.5/TB[vpost-t];
[apost]atrim=start=795:duration=28,asetpts=PTS-STARTPTS,afade=t=in:st=0:d=0.5,afade=t=out:st=27.5:d=0.5,adelay=40500|40500[apost-t];
[v3]trim=start=95:duration=30,setpts=PTS-STARTPTS,format=yuva420p,fade=t=in:st=0:d=0.5,setpts=PTS+41+28-0.5/TB[v3-t];
[a3]atrim=start=95:duration=30,asetpts=PTS-STARTPTS,afade=t=in:st=0:d=0.5,adelay=68500|68500[a3-t];
[vpre-t][vpost-t]overlay[v1];
[v1][v3-t]overlay[v];
[apre-t][apost-t][a3-t]amix=inputs=3[a0];
[a0]loudnorm[a]" -map "[v]" -map "[a]" -y -c:v libx264 -preset ultrafast -b:v 15000k -aspect 1920:1080 -c:a aac fullgame_Output.mp4
Just do it in one command.
Besides the keyframe seek issue, which is true, your present sequence has an error in the last command. You have [0:v]trim=start=0.5...[0c] which trims out the first 0.5 seconds and will cause a desync of its own. Since this is the first clip, it should be [0:v]trim=0:40.5.
The full single command should be
ffmpeg -i fullgame.mp4 -filter_complex
"[0]split=2[vpre][vpost];[0]asplit=2[apre][apost];
[vpre]trim=start='00:09:57':duration='00:00:41',setpts=PTS-STARTPTS[vpre-t];
[apre]atrim=start='00:09:57':duration='00:00:41',asetpts=PTS-STARTPTS,afade=t=out:st=40.5:d=0.5[apre-t];
[vpost]trim=start='00:15:45':duration='00:00:28',setpts=PTS-STARTPTS,format=yuva420p,fade=t=in:st=0:d=0.5:alpha=1,setpts=PTS+40.5/TB[vpost-t];
[apost]atrim=start='00:15:45':duration='00:00:28',asetpts=PTS-STARTPTS,afade=t=in:st=0:d=0.5[apost-t];
[vpre-t][vpost-t]overlay[v];
[apre-t][apost-t]acrossfade=d=0.5,loudnorm,aresample=48000[a]"
-map "[v]" -map "[a]" -c:v libx264 -preset ultrafast -b:v 15000k -aspect 1920:1080 -c:a aac fullgame_Output.mp4
Your original sequence had -strict -2 for audio AAC encoding. That hasn't been needed since Dec 2015. You have a very old version of ffmpeg if your ffmpeg throws an error without it. Upgrade first.
I did not test the above with your file, as it will take too long to filter 16 min of Full HD 60 fps video, but I tested the below faster command and it works fine with the latest git build of ffmpeg:
ffmpeg -ss 00:09:57 -t 00:00:41 -i fullgame.mp4 -ss 00:15:45 -t 00:00:28 -i fullgame.mp4 -filter_complex
"[0]afade=t=out:st=40.5:d=0.5[apre-t];
[1]format=yuva420p,fade=t=in:st=0:d=0.5:alpha=1,setpts=PTS+40.5/TB[vpost-t];
[1]afade=t=in:st=0:d=0.5[apost-t];
[0][vpost-t]overlay[v];
[apre-t][apost-t]acrossfade=d=0.5,loudnorm,aresample=48000:ocl=stereo[a]"
-map "[v]" -map "[a]" -c:v libx264 -preset ultrafast -b:v 15000k -aspect 1920:1080 -c:a aac fullgame_Output.mp4

Crop, Resize and Cut all in one command - FFMPEG

I am trying to do three tasks with FFMPEG
Crop a video without losing quality
Resize (upscale) the cropped video with good quality
Cut specific part of a the upscaled vided without losing quality
Here are the command line I use:
to crop: video og.mp4 to video og1.mp4
ffmpeg -i og.mp4 -vf "crop=1330:615:22:120" -c:v libx264 -crf 1 -preset veryslow -c:a copy og1.mp4
to resize: video og1.mp4 (converted above) to video og2.mp4
ffmpeg -i og1.mp4 -vf scale=1920:-1 -c:v libx264 -crf 1 -preset veryslow -c:a copy og2.mp4
to cut: video og2.mp4 (converted above) to og3.mp4
ffmpeg -i og2.mp4 -ss 00:00:08.190 -t 00:00:11.680 -c:v libx264 -crf 1 -preset veryslow -c:a copy og3.mp4
I want to achieve highest quality of 1920 width video (irrespective of height and size of the file)
Is there a way to get the above tasks in one command or shorter time with best quality?
Also advice if there is a better command or parameters to be used.
Thanks
You can combine all commands by using a single filterchain, and adding the trim as well
ffmpeg -ss 8.190 -t 11.680 -i og.mp4 -vf "crop=1330:615:22:120,scale=1920:-2" -c:v libx264 -crf 1 -c:a copy og1.mp4
With crf 1, a slow preset is unnecessary.

Efficient command line to crop a video, overlay another crop from it and scale the result with ffmpeg

I need to convert many videos in such a way that I take 2 different crops from each frame of a single video, stack them one over the other and scale down the result, creating a new smaller video.
I want to convert this fullHD frame (two crop areas are marked red) to this small stacked frame.
Right now I use the following code:
ffmpeg -i "video.mkv" -filter:v "crop=560:416:0:0" out1.mp4
ffmpeg -i "video.mkv" -filter:v "crop=560:384:1060:128" out2.mp4
ffmpeg -i out1.mp4 -vf "movie=out2.mp4[inner]; [in][inner] overlay=0:32,scale=280:208[out]" -c:v libx264 -preset veryfast -crf 30 result.mp4
It works but it is very inefficient and requires temporary files (out1 and out2). And the problem is I have over 100.000 of such videos (they are big and stored on a NAS and not directly on my computer's HDD). Converting all of them with a Windows batch script (for loop) will take...48 days. Can you help me to optimize the script?
Use the crop, vstack, scale, and format filters:
ffmpeg -i input.mkv -filter_complex "[0:v]crop=560:24:0:0[top];[0:v]crop=560:384:1076:128[bottom];[top][bottom]vstack,scale=280:-2[out]" -map "[out]" -c:v libx264 -preset veryfast -crf 30 -movflags +faststart result.mp4
If you want to complicate it somewhat for faster filtering (maybe) then you can try scaling first:
ffmpeg -i input.mkv -filter_complex "[0:v]scale=iw/2:-1,split[v0][v1];[v0]crop=560/2:24/2:0:0[top];[v1]crop=560/2:384/2:1076/2:128/2[bottom];[top][bottom]vstack[out]" -map "[out]" -c:v libx264 -preset veryfast -crf 30 -movflags +faststart result.mp4
You'll have to experiment to see which is fastest for you.

How to improve quality after putting two videos side by side vertically?

I am putting two videos side by side vertically with ffmpeg.
Both of the videos are in high quality.
There is a loss of quality and the output looks very bad after this command:
ffmpeg -i input1t.avi -i input2 -filter_complex vstack output.avi
You can see an screen capture of the output movie here:
Any idea why? How to solve it?
FFmpeg will reencode your video, without additional parameters it will use default value resulting in a poor quality. Here's an example with a more modern format and codec:
ffmpeg -i input1.avi -i input2 -filter_complex vstack -c:v libx264 -preset slow -crf 20 -c:a aac -movflags +faststart output.mp4
If you need better quality, lower the -crf value.

Resources