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()
};