ffmpeg to split mp4 file into segments... after first segment, audio unsynced - ffmpeg

I've used the ffmpeg command line shown in this question to split MKV files perfectly for a long time. Now i have some MP4 files that i'd like to split and at first it seemed to work, but every subsequent segment after the first has the audio not synced! And by several seconds.
I've tried forcing keyframes (advice I found on some other sites) and that didn't help.
I tried a different program entirely (Avidemux) and it was able to split the file with proper output, but it was a LOT slower, taking upwards of 3 minutes vs less than 2 seconds with ffmpeg. With Avidemux I was able to determine the exact position of the i-frame where I wanted to split, so thinking perhaps that was the syncing problem I tried that exact position (ie. 00:12:17.111 instead of 00:12:16 or whatever) but that didn't help either.
Is there an option I'm missing with ffmpeg to get it to properly sync audio to the video when splitting?

I'm not sure I understand WHY, but the issue was order of parameters.
In the linked example, the command is as follows:
ffmpeg -i input.avi -vcodec copy -acodec copy -ss 00:30:00 -t 00:30:00 output2.avi
Of course, I'm using mp4 instead of avi, but otherwise I was entering the command exactly as above and (with mp4) I was getting an out-of-sync audio result. I accidentally stumpled onto this "fix"... if I instead enter the command thusly:
ffmpeg -ss 00:30:00 -i input.mp4 -vcodec copy -acodec copy -t 00:30:00 output2.mp4
I don't get the sync issues. Why? No idea. But it works. I've tried it a few times to confirm... making only that order of parameters change corrects the issue.

Related

I would like to batch-trim mp4 videos in a folder with command line (ffmpeg?), however the sound is delayed

I found the following command line to batch-trim videos in a folder, however it delays the sound of all the videos by a few seconds (sound comes after the video):
for file in /path/to/folder/*.mp4; do ffmpeg -i "$file" -ss 00:00:08 -c copy -avoid_negative_ts 1 "${file%.*}_trimmed.mp4"; done
These are the alternatives I found, however none of them solve the problem:
for file in /path/to/folder/*.mp4; do ffmpeg -i "$file" -itsoffset -0.5 -ss 8 -c:v copy -c:a copy "${file%.*}_trimmed.mp4"; done
for file in /path/to/folder/*.mp4; do ffmpeg -i "$file" -ss 8 -async 1 -c:v copy -c:a copy "${file%.*}_trimmed.mp4"; done
for file in /path/to/folder/*.mp4; do ffmpeg -i "$file" -ss 8 -map 0:v -map 0:a -c:v copy -c:a copy "${file%.*}_trimmed.mp4"; done
I have a hard time finding by how much time the sound is delayed.
My questions:
what would be the command line to batch-trim the beginning of all the videos in a folder with the sound properly "aligned" to the video?
in other words, can't the sound be by default trimmed by as much as the video in the first place? If yes, how?
System used:
macOS Ventura (13.1, Intel)
Shell and version: zsh 5.8.1 (x86_64-apple-darwin22.0)
ffmpeg version 5.0 built with Apple clang version 13.0.0 (clang-1300.0.29.30)
from the FFMPEG(1) manual page:
-ss position (input/output)
When used as an input option (before "-i"), seeks in this input
file to position. Note that in most formats it is not possible
to seek exactly, so ffmpeg will seek to the closest seek point
before position. When transcoding and -accurate_seek is enabled
(the default), this extra segment between the seek point and
position will be decoded and discarded. When doing stream copy
or when -noaccurate_seek is used, it will be preserved.
When used as an output option (before an output url), decodes but
discards input until the timestamps reach position.
position must be a time duration specification, see the Time
duration section in the ffmpeg-utils(1) manual.
(quoted under license, For details about the authorship, see the Git history of the project (git://source.ffmpeg.org/ffmpeg), e.g. by typing the command git log in the FFmpeg source directory, or browsing the online repository at http://source.ffmpeg.org.)
So first of all I would not be astonished that something is out of sync as it is documented and also reasonable.
As you're doing a stream copy (and not transcoding) the "overlap" will be preserved and from your description this leads to the async.
If you can easily reproduce with a single file I'd try to tweak with command line switch(es). E.g. if accurate seek can be used with copy as you perhaps don't want to re-encode and if it helps with the sync already. I'd still keep it in a file so that I can have it under version control.
This also might depend on the seek points in the original material, therefore I'd also test if re-encoding (transcoding) can help and with which options. Getting results with that can help to decide on the concrete material which way is to be favored.
When that has been decided, increase the batch size and check the output if its still as expected as with the smaller test. Then rinse and repeat until you get your wanted results.
The zsh shell scripting is helping you to formulate the command as they can become complex and you can write them down in a file. It also makes it easier for batch handling. Strictly speaking about what you ask for, this is purely about ffmpeg and technically the zsh shell scripting is unrelated to it. Just saying so it is easier to keep different issue apart (if you're later on missing files, it is likely a flaw in the scripting or error handling in the script for example).

ffmpeg: crop video into two grayscale sub-videos; guarantee monotonical frames; and get timestamps

The need
Hello, I need to extract two regions of a .h264 video file via the crop filter into two files. The output videos need to be monochrome and extension .mp4. The encoding (or format?) should guarantee that video frames are organized monotonically. Finally, I need to get the timestamps for both files (which I'd bet are the same timestamps that I would get from the input file, see below).
In the end I will be happy to do everything in one command via an elegant one liner (via a complex filter I guess), but I start doing it in multiple steps to break it down in simpler problems.
In this path I get into many difficulties and despite having searched in many places I don't seem to find solutions that work. Unfortunately I'm no expert of ffmpeg or video conversion, so the more I search, the more details I discover, the less I solve problems.
Below you find some of my attempts to work with the following options:
-filter:v "crop=400:ih:260:0,format=gray" to do the crop and the monochrome conversion
-vf showinfo possibly combined with -vsync 0 or -copyts to get the timestamps via stderr redirection &> filename
-c:v mjpeg to force monotony of frames (are there other ways?)
1. cropping each region and obtaining monochrome videos
$ ffmpeg -y -hide_banner -i inVideo.h264 -filter:v "crop=400:ih:260:0,format=gray" outL.mp4
$ ffmpeg -y -hide_banner -i inVideo.h264 -filter:v "crop=400:ih:1280:0,format=gray" outR.mp4
The issue here is that in the output files the frames are not organized monotonically (I don't understand why; how come would that make sense in any video format? I can't say if that comes from the input file).
EDIT. Maybe it is not frames, but packets, as returned by av .demux() method that are not monotonic (see below "instructions to reproduce...")
I have got the advice to do a ffmpeg -i outL.mp4 outL.mjpeg after, but this produces two videos that look very pixellated (at least playing them with ffplay) despite being surprisingly 4x bigger than the input. Needless to say, I need both monotonic frames and lossless conversion.
EDIT. I acknowledge the advice to specify -q:v 1; this fixes the pixellation effect but produces a file even bigger, ~12x in size. Is it necessary? (see below "instructions to reproduce...")
2. getting the timestamps
I found this piece of advice, but I don't want to generate hundreds of image files, so I tried the following:
$ ffmpeg -y -hide_banner -i outL.mp4 -vf showinfo -vsync 0 &>tsL.txt
$ ffmpeg -y -hide_banner -i outR.mp4 -vf showinfo -vsync 0 &>tsR.txt
The issue here is that I don't get any output because ffmpeg claims it needs an output file.
The need to produce an output file, and the doubt that the timestamps could be lost in the previous conversions, leads me back to making a first attempt of a one liner, where I am testing also the -copyts option, and the forcing the encoding with -c:v mjpeg option as per the advice mentioned above (don't know if in the right position though)
ffmpeg -y -hide_banner -i testTex2.h264 -copyts -filter:v "crop=400:ih:1280:0,format=gray" -vf showinfo -c:v mjpeg eyeL.mp4 &>tsL.txt
This does not work because surprisingly the output .mp4 I get is the same as the input. If instead I put the -vf showinfo option just before the stderr redirection, I get no redirected output
ffmpeg -y -hide_banner -i testTex2.h264 -copyts -filter:v "crop=400:ih:260:0,format=gray" -c:v mjpeg outR.mp4 -vf showinfo dummy.mp4 &>tsR.txt
In this case I get the desired timestamps output (too much: I will need some solution to grab only the pts and pts_time data out of it) but I have to produce a big dummy file. The worst thing is anyway, that the mjpeg encoding produces a low resolution very pixellated video again
I admit that the logic how to place the options and the output files on the command line is obscure to me. Possible combinations are many, and the more options I try the more complicated it gets, and I am not getting much closer to the solution.
3. [EDIT] instructions how to reproduce this
get a .h264 video
turn it into .mp by ffmpeg command $ ffmpeg -i inVideo.h264 out.mp4
run the following python cell in a jupyter-notebook
see that the packets timestamps have diffs greater and less than zero
%matplotlib inline
import av
import numpy as np
import matplotlib.pyplot as mpl
fname, ext="outL.direct", "mp4"
cont=av.open(f"{fname}.{ext}")
pk_pts=np.array([p.pts for p in cont.demux(video=0) if p.pts is not None])
cont=av.open(f"{fname}.{ext}")
fm_pts=np.array([f.pts for f in cont.decode(video=0) if f.pts is not None])
print(pk_pts.shape,fm_pts.shape)
mpl.subplot(211)
mpl.plot(np.diff(pk_pts))
mpl.subplot(212)
mpl.plot(np.diff(fm_pts))
finally create also the mjpeg encoded files in various ways, and check packets monotony with the same script (see also file size)
$ ffmpeg -i inVideo.h264 out.mjpeg
$ ffmpeg -i inVideo.h264 -c:v mjpeg out.c_mjpeg.mp4
$ ffmpeg -i inVideo.h264 -c:v mjpeg -q:v 1 out.c_mjpeg_q1.mp4
Finally, the question
What is a working way / the right way to do it?
Any hints, even about single steps and how to rightly combine them will be appreciated. Also, I am not limited tio the command line, and I would be able to try some more programmatic solution in python (jupyter notebook) instead of the command line if someone points me in that direction.

Using ffmpeg to convert an SEC file

I need to convert an SEC file into any video format that I can share and/or upload to Youtube. MP4, etc.
I'm a complete newbie at all things terminal. I've tried:
ffmpeg -i video.sec video.mp4
ffmpeg -i video.sec -bsf:v h264_mp4toannexb -c:v copy video.avi
ffmpeg -i video.sec -b 256k -vcodec h264 -acodec aac video.mp4
I don't understand what any of these mean, they're just examples I found online. However, whatever I try returns this error:
Invalid data found when processing input
Any thoughts? Thanks!
I had to add the following option so it would skip the SEC's custom header.
-skip_initial_bytes 48
i know this is old, but i was trying to figure this out as well, what ended up finally working for me was this command.
./ffmpeg -f h264 -i INPUT.sec -filter:v "setpts=4*PTS" OUTPUT.avi
the -f h264 was the part i was missing. and the -filter:v "setpts=4*PTS" part is to slow it back down to the original speed. you can also change the .avi at the end to whichever format works best for you.
i hope this helps someone out :)
OK, just to clear up some recent threads…
The Samsung DVR used here was an SRD-440. RB kindly sent me a file to test and he sent me a .BU file with an associated .db2 file. This was a bit of a surprise as in all older Samsung DVR’s, the .bu files can only be played back in the DVR. I mentioned this here, https://spreadys.wordpress.com/2014/07/21/ifsec-samsung-exports/
It appears that Samsung have caught on, and the BU file is now playable due to it being a H264/AVC Stream conforming to a standard profile. I have updated the IFSEC Post mentioned above to highlight this change.
Back to RB’s stream and the challenge was to get these files viewable in WMV format. They were all field based, at 704×288.
The speed of playback is controlled by the Samsung software, using the .db2 file. As such, the metadata and timing information in the video stream was wrong. This caused speed issues and then quality issues when attempting to correct this.
As a result, I found it necessary to force an input rate and generate a new Presentation Time Stamp BEFORE the input file.
The following FFmpeg string did the job…
ffmpeg -r 12 -fflags genpts -i FILE.bu -vf scale=704:528 -sws_flags lanczos -q:v 2 FILE.wmv
Remember, this is for preview – analysis would be completed differently due to the scaling, the interpolation method, and the WMV compression!
As its likely that RB may have quite a few .bu files in a folder, I placed this into a batch file to transcode the whole lot within a few minutes… more on batch files coming in a new post soon!
https://spreadys.wordpress.com/2014/07/21/ifsec-samsung-exports/
or
ffmpeg -i (name of file).sec (name of final file).mp4
ffmpeg -i (name of file).sec -filter:v "setpts=3.3*PTS" (name of final_file).mp4

Use FFmpeg to split a video into equal-length segments

I need to split many videos of unknown length into 2-minute chunks. I can use the accepted answer from https://unix.stackexchange.com/questions/1670/how-can-i-use-ffmpeg-to-split-mpeg-video-into-10-minute-chunks to do this, but this would require copying, pasting, and modifying many lines and manually calculating how many 2-minute parts are in each video (not a big deal for a few videos, but after a while it gets really tedious).
I have used code in the past in which all I have to do is specify the input video, the start time, segment length, and the output name and by running it in a .sh file in the same folder as the input video it generates all the necessary separate videos which are labeled "output_video01," "output_video02," etc. However, somehow it doesn't want to work on my new computer. Other answers which claim to be able to do this don't work for me, either (when run as either a .bat or .sh file). I believe the code I previously used was:
ffmpeg -i "input_video.MTS" -ss 164 -f segment -segment_time 120 -vcodec copy -reset_timestamps 1 -map 0:0 -an output_video%d.MTS
Another suggestion that doesn't work for me but apparently works for others (see https://superuser.com/a/820773/313829):
ffmpeg -i input_video.MTS -c copy -map 0 -segment_time 8 -f segment output_video%03d.MTS
I currently have Windows 10 with the anniversary update build in which I have enabled Bash, but for some reason it doesn't want to work. I can't even see the error it gives me because the window closes so abruptly.
The original code (that didn't work for me) was:
ffmpeg -i input_video.MTS -c copy -map 0 -segment_time 8 -f segment output_video%03d.MTS
The code that did end up working for me:
ffmpeg -i input_video.MTS -c copy -map 0 -segment_time 8 -f segment output_video%%03d.MTS
The "%" needed to be "%%". Also, it worked in a .bat file (not .sh).

Can you splice a 1 min clip out of a larger file, without transcoding it?

I have a site that allows people to upload large video files in various formats (avi, mp4, mkv and flv). I need to generate a 1 minute "sample" from the larger file that has been uploaded, and the sample needs to be in the same format, have the same frame dimensions and bit-rate as the original file. Is there a way to simply cut out a section of the file into a new file? Preferably in ffmpeg (or any other tool if ffmpeg is impossible).
First you'll want to understand how video files actually work. Here's a set of tutorials explaining that: Overly Simplistic Guide to Internet Video.
Then, you can try a variety of tools that may help with slicing out a sample. One is flvtool (if your input is FLV) or FFmpeg. With FFmpeg you can specify a start time and stop time, and it will attempt to cut out just what you ask for (but it will have to find the nearest key-frame to begin slicing at).
Here's the FFmpeg command to read a file called input.flv, start 15 seconds into the video, and then cut out the next 60 seconds, but otherwise keep the same parameters for the audio code and video codec, and write it to an output file:
ffmpeg -i input.flv -ss 15 -t 60 -acodec copy -vcodec copy output.flv
Finally if you want you can write computer code in C or C++ (using FFmpeg's libav libraries) or Java (using Xuggler) to programatically do this, but that's pretty advanced for your use case.
If you are having problems keeping auto and video synced up as I was, the following may help (found on another website):
ffmpeg -sameq -i file.flv -ss 00:01:00 -t 00:00:30 -ac 2 -r 25 -copyts output.flv
As Evan notes, the approach in the accepted answer can result in loss of A/V sync. However his solution is not correct because -sameq was removed.
As stated at https://trac.ffmpeg.org/wiki/Seeking the -ss option should come before -i not after. This fixed the issue for me.
Next option is to use -fs switch. Example:
ff -i ip.mkv -fs 500M -c copy ~/Movies/reservoir/carbohydrates.mkv
Extract 500 megabytes (500×1000×1000 bytes + ‘muxing overhead’) off selected source.
–based on filesize, as you can tell
One love. And respect.

Resources