vxh.viet
5/11/2016 - 6:55 AM

FFmpeg commands

FFmpeg commands

FFmpeg commands:

General Info Useful Commands FFmpeg Docs

1. -y: Overwrite output files without asking.


2. Sample command to show GOP / I frame structure using ffprobe (hit q to cancel the output): Source

ffprobe -i input.mp4 -select_streams v -show_frames -of csv -show_entries frame=pict_type

or Source

ffprobe -select_streams v:0 -show_frames goptest.mov |grep key_frame|less

or Source

ffprobe -show_packets -print_format compact -select_streams v:0 filename | grep flags=K

3. Add more key frame: Source

ffmpeg -i Android_Test_Source.mp4 -force_key_frames "expr:gte(t,n_forced*0.05)" out5.mp4

4. Show metadata: Source, Source

ffprobe -show_streams 720p_VID_20160509_103821.mp4

5. Set video rotation without reencoding: Source

ffmpeg -i input.mp4 -c copy -metadata:s:v:0 rotate=90 output.mp4

6. Convert and set video rotation: Source

ffmpeg -noautorotate -i input.mp4 -force_key_frames "expr:gte(t,n_forced*0.05)" -metadata:s:v:0 rotate=90 output.mp4

7. Fast convert (reduce resolution, use x264 -preset ultrafast, remove audio -an): Source, Source

ffmpeg -i Android_Test_Source.mp4 -force_key_frames "expr:gte(t,n_forced*0.05)" -c:v libx264 -preset ultrafast -s 640*480 -y -an out.mp4

Possible enhancement Stack


8. Change the container from .mov to .mp4 (if the video bitstream is valid for mp4): Source, Source

ffmpeg -i input.avi -c:v copy -y output.mp4` or `ffmpeg -i input.mov -vcodec copy -y output.mp4

9.1 Trim Video with Input Seeking (both fast and accurate SOURCE), without reencoding SOURCE

ffmpeg.exe -y -ss 00:00:03 -i INPUT.MP4  -t 00:00:02 -c copy OUTPUT.MP4

-y: overwrite output.

-ss: Used before -i for input seeking, this seeks in the input file (INPUT.MP4) to position.

-i: This specifies the input file. In that case, it is (INPUT.MP4).

00:01:00: This is the time your trimmed video will start with.

-t: This specifies duration, like -ss 60 -t 10 to capture from second 60 to 70.

or

-to: This specify an out point, like -ss 60 -to 70 to capture from second 60 to 70.

00:02:00: This is the time your trimmed video will end with.

-c copy: This is an option to trim via stream copy. (NB: Very fast)


9.2 Trim and resize video DOC

ffmpeg.exe -y -ss 00:00:03 -i orig1.mp4 -t 00:00:10 -vf scale=w=-2:h=720 -preset ultrafast OUTPUT.mp4

scale=w=-2:h=720: see DOC or section 10.2 for explanation. -preset ultrafast: some Android FFmpeg builds don't play nicely with this option, so obmit it if it crashs your app.


10.1 Add an animated image (GIF) to video as Overlay: Source, Source, not really relevant Source

FFmpeg is able to place the overlay in some particular coordinates in the video at each particular moment of time. Putting it simply you can set x=F(t) and y=G(t), where х and у are coordinates and F(t) and G(t) are general time functions. The command for simple adding looks like:


ffmpeg -y -i INPUT.mp4
-itsoffset 00:00:02 
-ignore_loop 0 
-i IMAGE.gif 
-filter_complex [1:v]scale=h=-1:w=300[overlay_scaled],[0:v][overlay_scaled]overlay=eval=init:x=W*0.5:y=H*0.5:shortest=1 
-preset ultrafast 
-g 120 
OUTPUT.mp4

String strFilter = "[1:v]scale=h=-1:w=" + widthOverlay + "[overlay_scaled],"
  + "[0:v][overlay_scaled]overlay=eval=init:x=W*" + xPositionPercent
  + ":y=H*" + yPositionPercent + ":shortest=1";

String[] сmd = new String[] {
  "-y",
  "-i",
  strPathSrcVideo,
  "-itsoffset",
  String.valueOf(timeSecStartAnimation),
  -ignore_loop",
  "0",
  "-i",
  strPathOverlay,
  "-filter_complex",
  strFilter,
  "-preset",
  "ultrafast",
  "-g",
  "120",
  strPathDstVideo
};

Where:

widthOverlay is the final width of the overlay just as it will be in the video.

xPositionPercent is the left margin in the percent from the video width.

yPositionPercent is the top margin in the percent from the video height.

strPathSrcVideo is the full way to the source video.

timeSecStartAnimation is the time when the overlay appears on the video, for example "00:00:02" (appear after 2 seconds).

strPathOverlay is the complete way to the source overlay.

strPathDstVideo is the way for saving the result.

-ignore_loop 0 to keep the animation looping, it should be used with shortest=1, otherwise the encoding will run indefinitely since one of the inputs is looping (See source 2).


10.2 Reduce resolution of a video and add an animated image (GIF) to it as Overlay: SOURCE, SOURCE, SOURCE

ffmpeg.exe -y -i test1.mp4 -ignore_loop 0 -i banana.gif -filter_complex \
        "[0:v]scale=w=-2:h=720[a]; [1:v]scale=w=300:h=-2[b]; \
         [a][b]overlay=eval=init:x=W*0.5:y=H*0.5:shortest=1[out]" \
       -map "[out]" -preset ultrafast -map 0:a -c:a copy OUT1.mp4
        
//some Android FFmpeg build doesn't have the preset option and will drammatically decrease
//the output video, thus we need to manually improve it
//improve the output video quality
ffmpeg.exe ...
       -map "[out]" -q:v 7 OUT1.mp4

Where:

[0:v]: refers to the first video stream in the first input file (test1.mp4), which is linked to the first (main) input of the overlay filter (see DOC, find filter_complex filtergraph and DOC, find :stream_specifier)

[1:v]: similarly the first video stream in the second input is linked to the second (overlay) input of overlay.

[0:v]scale=w=-2:h=720[a]: means we take test1.mp4 (1080p video), referred to as [0:v] and scale the height to 720. If we need to keep the aspect ratio, we leave the other component as -1. But some codecs require the size of width and height to be a multiple of n. You can achieve this by setting the width or height to -n (-2 in this case) (DOC. Then use an output link label named [a] so we can refer to this output later.

There are also some useful variables which can be used instead of numbers, to specify width and height of the output image.

For example, if you want to stretch the image in such a way to only double the width of the input image, you can use something like this (iw = input width, ih = input height) (See DOC above):

ffmpeg -i input.jpg -vf scale=iw*2:ih input_double_width.png

[1:v]scale=w=300:h=-2[b]: same as above but for banana.gif image and labeled as [b].

[a][b]overlay=eval=init:x=W*0.5:y=H*0.5:shortest=1[out]: Use [a] as the first overlay input and [b] as the second overlay input and label the out put as [out]. For overlay parameters see DOC.

-map "[out]": map the output from the filter to the output file. The [out] link label at the end of the filtergraph is not necessarily required but it is recommended to be explicit with mapping.

-map 0:a -c:a copy: copy the audio from the input video to the output file. See SOURCE

-q:v 7: Some Android Library such as VideoKit FFmpeg (FFmpeg 3.2.4) doesn't have the -preset option and also decrease the output quality. Thus we need top manually set the output quality with -qscale:v value from 1-31 where a lower value results in a higher bitrate and therefore usually better quality (7 will give an acceptable quality for 1080p videos in my experiment). See SOURCE, DOC.

EXTRA: Some Android FFmpeg library won't play nicely with multiline command. Thus we need to change to single line command. Tested with inFullMobile's videokit (FFmpeg version 3.2.4):

final Command overlayCommand = videoKit.createCommand()
                .inputPath(INPUT)
                .customCommand("-ignore_loop 0")
                .customCommand("-i " + GIF)
                .customCommand(
                        "-filter_complex [0:v]scale=w=-2:h=720[a],[1:v]scale=w=300:h=-2[b],[a][b]overlay=eval=init:x=W*0.5:y=H*0.5:shortest=1[out]"
                )
                .customCommand("-map [out]")
                .customCommand("-q:v 1")
                .outputPath(OUTPUT)
                .build();

, and ; in -filter_complex work similarly.

no " " in [out].

For some reason the output of of Videokit has very low quality, we need to expicitly set the quality with -q:v 1, see SOURCE, DOC.

EXTRA 2: this one work with Bravobit's FFmpeg (FFmpeg version 3.4.1):

private static final String[] OVERLAY_COMMAND = {
            "-y", "-i", INPUT, "-ignore_loop", "0", "-i", GIF, "-filter_complex",
            "[0:v]scale=w=-2:h=720[a],[1:v]scale=h=300:w=-2[b],[a][b]overlay=eval=init:x=W*0.5:y=H*0.5:shortest=1[out]",
            "-map", "[out]", "-preset", "ultrafast", "-map", "0:a", "-c:a", "copy", OUTPUT
    };

10.3 Add rotation to the GIF overlay: SOURCE, SOURCE, DOC

//Scale before rotate
ffmpeg.exe -y -i orig1.mp4 -ignore_loop 0 -i banana.gif -filter_complex \
        "[0:v]scale=w=-2:h=720[a]; [1:v]scale=w=300:h=-2[scale]; \
        [scale]rotate=PI/6:ow=rotw(PI/6):oh=roth(PI/6):c=none[b];
         [a][b]overlay=eval=init:x=W*0.5:y=H*0.5:shortest=1[out]" \
       -map "[out]" -preset ultrafast -map 0:a -c:a copy OUT1.mp4
       
//Rotate before scale
ffmpeg.exe -y -i orig1.mp4 -ignore_loop 0 -i banana.gif -filter_complex \
        "[0:v]scale=w=-2:h=720[a]; [1:v]rotate=PI/6:ow=rotw(PI/6):oh=roth(PI/6):c=none[rotate]; \
        [rotate]scale=w=300:h=-2[b]; \
         [a][b]overlay=eval=init:x=W*0.5:y=H*0.5:shortest=1[out]" \
       -map "[out]" -preset ultrafast -map 0:a -c:a copy OUT1.mp4
       
//complex rotation, see DOC for more detail
rotate=30*PI/180:c=none:ow=rotw(iw):oh=roth(ih)

Where:

PI/6: Rotate the input by PI/6 radians clockwise. The rotate angle always use radian.

c=none: transparency

ow, oh: Set the output width, height expression, default value is iw and ih. This expression is evaluated just once during configuration.

rotw(a), roth(a): the minimal width/height required for completely containing the input video rotated by a radians. These are only available when computing the ow and oh expressions.

ow=rotw(PI/6):oh=roth(PI/6): ensure that the out put doesn't get clipped. SOURCE

For more complex parameters, see DOC.

Sample command (work with Bravobit's ffmpeg):

//for portrait video, for landscape video, swap w and h in [0:v]scale=h=-2:w=720[a]
String[] overlayCommand = {
                "-y", "-i", mVideoRepository.getTrimmedVideoPath(), "-ignore_loop", "0", "-i", stickerPath,
                "-filter_complex",
                "[0:v]scale=" + orientationParam + "[a]," +
                        "[1:v]scale=iw*" + stickerScale + ":-2[scale]," +
                        "[scale]rotate=" + stickerRotation + ":ow=rotw(" + stickerRotation + "):oh=roth(" + stickerRotation + "):c=none[b]," +
                        "[a][b]overlay=eval=init:x=" + stickerX + ":y=" + stickerY + ":shortest=1[out]",
                "-map", "[out]",
                "-preset", "ultrafast", "-map", "0:a", "-c:a", "copy",
                mVideoRepository.getOutputVideoPath()
        };

Important:

Because the rotw(a), roth(a) command in FFmpeg rotation will calculate the the minimal width/height required for completely containing the input video rotated by a radians, thus we need to precalculate that rectangle in order to supply FFmpeg with correct X, Y position.

So we need to create a Rectangle that represent the output sticker. Rotate it with the current rotation (degree not radian) and take advantage of the fact that RectF doesn't actually rotate but instead create a new RectF wrapping the new corners.

// sample code
RectF boundRect = new RectF(outputStickerX, outputStickerY, outputStickerX + outputStickerWidth, outputStickerY + outputStickerHeight);
        Matrix m = new Matrix();
        m.setRotate(MatrixUtil.getMatrixAngle(matrix), stickerCenterX, stickerCenterY);
        m.mapRect(boundRect);


10.4 Copy metadata: SOURCE

ffmpeg.exe -y -i orig1.mp4 -ignore_loop 0 -i banana.gif -filter_complex \
        "[0:v]scale=w=-2:h=720[a]; [1:v]scale=w=300:h=-2[scale]; \
        [scale]rotate=PI/6:ow=rotw(PI/6):oh=roth(PI/6):c=none[b];
         [a][b]overlay=eval=init:x=W*0.5:y=H*0.5:shortest=1[out]" \
       -map "[out]" -preset ultrafast -map 0:a -c:a copy \
       -map_metadata 0 \
       -map_metadata:s:v 0:s:v \
       -map_metadata:s:a 0:s:a \
       OUT1.mp4

Where:

-map_metadata 0: copies all global metadata from orig1.mp4 to OUT1.mp4

-map_metadata:s:v 0:s:v: copies video stream metadata from orig1.mp4 to OUT1.mp4

-map_metadata:s:a 0:s:a: copies audio stream metadata from orig1.mp4 to OUT1.mp4

to mimic the non quote " " syntax of -filter_complex in Bravobit, drop the space within the -filter_complex command, replace ; with , and remove the "". Example:

ffmpeg.exe -y -i VID-20160504-WA0001.mp4 -ignore_loop 0 -i banana.gif -filter_complex \
    [0:v]scale=w=-2:h=720[a],\
    [1:v]scale=w=300:h=-2[scale],\
    [scale]rotate=PI/6:ow=rotw(PI/6):oh=roth(PI/6):c=none[b],\
    [a][b]overlay=eval=init:x=W*0.5:y=H*0.5:shortest=1[out] \
    -map [out] -preset ultrafast -map 0:a -c:a copy \
    -map_metadata 0 \
    -map_metadata:s:v 0:s:v \
    -map_metadata:s:a 0:s:a \
    OUT1.mp4

Important:

We don't necessarily need to map metadata for Android videos. Based on my experiment with Samsung, GoPro, Android 6.0.1, cwac cam 0.5.11, the videos are all landscape and the player will use the rotation metadata to actually apply rotation during playback.

For example:

Samsung, GoPro          Android Cam Video               Shutta Cam Video
Portrait            Portrait                              Portrait
- Rotation: 90          - Rotation: 90                - Rotation: 90
- Width: 1920               - Width: 1920                       - Width: 1920
- Height: 1080          - Height: 1080                - Height: 1080

Landscape Video     Landscape                           Landscape
- Rotation: 0               - Rotation: 0                       - Rotation: 0
- Width: 1920               - Width: 1920                       - Width: 1920
- Height: 1080          - Height: 1080                - Height: 1080

Portrait Video after being converted with FFmpeg
- Rotation: 0               
- Width: 720    
- Height: 1280

However, FFmpeg will actually auto apply the rotation to the converted video and reset the Rotation metadata to 0. Thus, for video with Rotation 0 we need to distinguish the orientation based on width and height. (This also work for snapchat videos).

To disable auto rotation add -noautorotate before the input (for example, ffmpeg -y -noautorotate -i input.mp4...), however this will mess up the overlay.


10.5 Overlay multiple GIF: SOURCE, OTHER

// 2 GIFs
ffmpeg.exe -y -i VID-20160504-WA0001.mp4 -ignore_loop 0 -i 1explosion.gif -ignore_loop 0 -i 2explosion.gif -filter_complex \
        "[0:v]scale=w=720:h=-2[sv]; \
        [1:v]scale=w=300:h=-2[ss1]; \
        [ss1]rotate=PI/6:ow=rotw(PI/6):oh=roth(PI/6):c=none[rs1]; \
         [sv][rs1]overlay=eval=init:x=W*0.5:y=H*0.5:shortest=1[first]; \
         [2:v]scale=w=350:h=-2[ss2]; \
         [ss2]rotate=PI/3:ow=rotw(PI/3):oh=roth(PI/3):c=none[rs2]; \
         [first][rs2]overlay=eval=init:x=W*0.2:y=H*0.2:shortest=1[out]" \
         -map "[out]" -preset ultrafast -map 0:a -c:a copy OUT1.mp4

Java code for Bravobit's FFMPEG:

private static final int DEFAULT_OUTPUT_VIDEO_RESOLUTION = 720; //for smallest side

...

if (dataList.get(0).isLandscapeVideo()) {
            orientationParam = "w=-2:h=" + DEFAULT_OUTPUT_VIDEO_RESOLUTION;
        } else {
            orientationParam = "h=-2:w=" + DEFAULT_OUTPUT_VIDEO_RESOLUTION;
}

...

// 2 GIFs
String[] overlayCommand = {
                "-y", "-i", mVideoRepository.getTrimmedVideoPath(),
                "-ignore_loop", "0", "-i", stickerPath1,
                "-ignore_loop", "0", "-i", stickerPath2,
                "-filter_complex",
                "[0:v]scale=" + orientationParam + "[sv]," + // sv: scaled video
                        "[1:v]scale=iw*" + stickerScale1 + ":-2[ss1]," + // ss1: scaled sticker 1
                        "[ss1]rotate=" + stickerRotation1 + ":ow=rotw(" + stickerRotation1 + "):oh=roth(" + stickerRotation1 + "):c=none[rs1]," + // rs1: rotated sticker 1
                        "[sv][rs1]overlay=eval=init:x=" + stickerX1 + ":y=" + stickerY1 + ":shortest=1[first]," +
                        "[2:v]scale=iw*" + stickerScale2 + ":-2[ss2]," +
                        "[ss2]rotate=" + stickerRotation2 + ":ow=rotw(" + stickerRotation2 + "):oh=roth(" + stickerRotation2 + "):c=none[rs2]," +
                        "[first][rs2]overlay=eval=init:x=" + stickerX2 + ":y=" + stickerY2 + ":shortest=1[out]",
                "-map", "[out]",
                "-preset", "ultrafast", "-map", "0:a", "-c:a", "copy",
                mVideoRepository.getOutputVideoPath()
        };
        
// 4 GIFs
String[] overlayCommand = {
                "-y", "-i", mVideoRepository.getTrimmedVideoPath(),
                "-ignore_loop", "0", "-i", stickerPath1,
                "-ignore_loop", "0", "-i", stickerPath2,
                "-ignore_loop", "0", "-i", stickerPath3,
                "-ignore_loop", "0", "-i", stickerPath4,
                "-filter_complex",
                "[0:v]scale=" + orientationParam + "[sv]," + // sv: scaled video
                        "[1:v]scale=iw*" + stickerScale1 + ":-2[ss1]," + // ss1: scaled sticker 1
                        "[ss1]rotate=" + stickerRotation1 + ":ow=rotw(" + stickerRotation1 + "):oh=roth(" + stickerRotation1 + "):c=none[rs1]," + // rs1: rotated sticker 1
                        "[sv][rs1]overlay=eval=init:x=" + stickerX1 + ":y=" + stickerY1 + ":shortest=1[first]," +
                        "[2:v]scale=iw*" + stickerScale2 + ":-2[ss2]," +
                        "[ss2]rotate=" + stickerRotation2 + ":ow=rotw(" + stickerRotation2 + "):oh=roth(" + stickerRotation2 + "):c=none[rs2]," +
                        "[first][rs2]overlay=eval=init:x=" + stickerX2 + ":y=" + stickerY2 + ":shortest=1[second]," +
                        "[3:v]scale=iw*" + stickerScale3 + ":-2[ss3]," +
                        "[ss3]rotate=" + stickerRotation3 + ":ow=rotw(" + stickerRotation3 + "):oh=roth(" + stickerRotation3 + "):c=none[rs3]," +
                        "[second][rs3]overlay=eval=init:x=" + stickerX3 + ":y=" + stickerY3 + ":shortest=1[third]," +
                        "[4:v]scale=iw*" + stickerScale4 + ":-2[ss4]," +
                        "[ss4]rotate=" + stickerRotation4 + ":ow=rotw(" + stickerRotation4 + "):oh=roth(" + stickerRotation4 + "):c=none[rs4]," +
                        "[third][rs4]overlay=eval=init:x=" + stickerX4 + ":y=" + stickerY4 + ":shortest=1[out]",
                "-map", "[out]",
                "-preset", "ultrafast", "-map", "0:a", "-c:a", "copy",
                mVideoRepository.getOutputVideoPath()
        };