FFmpeg Operations Reference

Complete command reference for video editing operations in Replit. All examples use Node.js with child_process.execFileorfluent-ffmpeg.

Table of Contents

  1. Trimming and Cutting
  2. Concatenation

  3. Transitions

  4. Text Overlays

  5. Subtitles

  6. Audio

  7. Effects

  8. Format Conversion

  9. Image Extraction

  10. GIF Creation

  11. Resizing and Cropping

  12. Watermarks and Overlays

  13. Compression

  14. Stabilization

  15. Looping

  16. Rotation and Flipping

  17. Split Screen

  18. Muting and Audio Removal

  19. Thumbnail Generation

  20. Watermark Removal (Crop)

  21. Logo Overlay (Branding)

  22. Social Media Reframing


Trimming and Cutting

Trim without re-encoding (fast, frame-accurate at keyframes)


ffmpeg -i input.mp4 -ss 00:00:30 -to 00:01:15 -c copy output.mp4

Place -ssbefore-ifor faster seeking (input seeking), or after-ifor frame-accurate seeking (output seeking). Using-c copy avoids re-encoding but may have imprecise start points at non-keyframe positions.

Trim with re-encoding (slower, frame-accurate)


ffmpeg -i input.mp4 -ss 00:00:30 -to 00:01:15 -c:v libx264 -c:a aac output.mp4

Remove a section (keep before and after)

Split into two parts and concatenate:


ffmpeg -i input.mp4 -t 00:00:30 -c copy part1.mp4

ffmpeg -i input.mp4 -ss 00:01:00 -c copy part2.mp4

Then concatenate (see Concatenation section).


Concatenation

Concat demuxer (same codec, resolution, frame rate)

Create a text file concat_list.txt:


file 'clip1.mp4'

file 'clip2.mp4'

file 'clip3.mp4'


ffmpeg -f concat -safe 0 -i concat_list.txt -c copy output.mp4

This is the fastest method because it avoids re-encoding, but all clips must share the same codec, resolution, and frame rate.

Concat filter (different codecs/resolutions)


ffmpeg -i clip1.mp4 -i clip2.mp4 \\

-filter_complex "[0:v][0:a][1:v][1:a]concat=n=2:v=1:a=1[outv][outa]" \\

-map "[outv]" -map "[outa]" output.mp4

This re-encodes everything but handles mismatched inputs. Scale inputs to a common resolution first if they differ.


Transitions

Crossfade between two clips


ffmpeg -i clip1.mp4 -i clip2.mp4 \\

-filter_complex "\\

[0:v]trim=duration=5,setpts=PTS-STARTPTS[v0]; \\

[1:v]trim=duration=5,setpts=PTS-STARTPTS[v1]; \\

[v0][v1]xfade=transition=fade:duration=1:offset=4[outv]; \\

[0:a]atrim=duration=5,asetpts=PTS-STARTPTS[a0]; \\

[1:a]atrim=duration=5,asetpts=PTS-STARTPTS[a1]; \\

[a0][a1]acrossfade=d=1[outa]" \\

-map "[outv]" -map "[outa]" output.mp4

Available xfade transitions

fade, wipeleft, wiperight, wipeup, wipedown, slideleft, slideright, slideup, slidedown, circlecrop, rectcrop, distance, fadeblack, fadewhite, radial, smoothleft, smoothright, smoothup, smoothdown, circleopen, circleclose, vertopen, vertclose, horzopen, horzclose, dissolve, pixelize, diagtl, diagtr, diagbl, diagbr, hlslice, hrslice, vuslice, vdslice, hblur, fadegrays, squeezev, squeezeh, zoomin, hlwind, hrwind, vuwind, vdwind, coverleft, coverright, coverup, coverdown, revealleft, revealright, revealup, revealdown

Fade in/out


# Fade in first 2 seconds

ffmpeg -i input.mp4 -vf "fade=t=in:st=0:d=2" -c:a copy output.mp4

# Fade out last 2 seconds (requires knowing duration)

ffmpeg -i input.mp4 -vf "fade=t=out:st=8:d=2" -c:a copy output.mp4

# Audio fade in/out

ffmpeg -i input.mp4 -af "afade=t=in:st=0:d=2,afade=t=out:st=8:d=2" output.mp4


Text Overlays

Static text overlay


ffmpeg -i input.mp4 \\

-vf "drawtext=text='Hello World':fontsize=48:fontcolor=white:x=(w-text_w)/2:y=(h-text_h)/2:fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf" \\

output.mp4

Text with background box


ffmpeg -i input.mp4 \\

-vf "drawtext=text='Title':fontsize=64:fontcolor=white:box=1:boxcolor=black@0.6:boxborderw=10:x=(w-text_w)/2:y=50" \\

output.mp4

Timed text (appears and disappears)


ffmpeg -i input.mp4 \\

-vf "drawtext=text='Scene 1':fontsize=48:fontcolor=white:x=(w-text_w)/2:y=(h-text_h)/2:enable='between(t,2,5)'" \\

output.mp4

Font discovery

List available fonts in the Replit environment:


fc-list : family style | sort

Or use a custom font file by downloading it to the project and referencing it with fontfile=.


Subtitles

Burn subtitles from SRT file


ffmpeg -i input.mp4 -vf "subtitles=subs.srt" output.mp4

Burn subtitles with styling


ffmpeg -i input.mp4 \\

-vf "subtitles=subs.srt:force_style='FontSize=24,FontName=Arial,PrimaryColour=&Hffffff&,OutlineColour=&H000000&,Outline=2'" \\

output.mp4

Burn ASS/SSA subtitles (preserves advanced styling)


ffmpeg -i input.mp4 -vf "ass=subs.ass" output.mp4

Embed subtitles as a stream (soft subs, MKV only)


ffmpeg -i input.mp4 -i subs.srt -c copy -c:s srt output.mkv


Audio

Add background music


ffmpeg -i video.mp4 -i music.mp3 \\

-filter_complex "[1:a]volume=0.3[music];[0:a][music]amix=inputs=2:duration=first[outa]" \\

-map 0:v -map "[outa]" -c:v copy output.mp4

Replace audio entirely


ffmpeg -i video.mp4 -i new_audio.mp3 \\

-map 0:v -map 1:a -c:v copy -shortest output.mp4

Extract audio


ffmpeg -i video.mp4 -vn -c:a libmp3lame -q:a 2 output.mp3

ffmpeg -i video.mp4 -vn -c:a copy output.aac

Remove audio


ffmpeg -i input.mp4 -an -c:v copy output.mp4

Adjust volume


# Set to 50% volume

ffmpeg -i input.mp4 -af "volume=0.5" -c:v copy output.mp4

# Increase by 10dB

ffmpeg -i input.mp4 -af "volume=10dB" -c:v copy output.mp4

# Normalize audio

ffmpeg -i input.mp4 -af "loudnorm" -c:v copy output.mp4

Audio delay/offset


# Delay audio by 1 second

ffmpeg -i input.mp4 -itsoffset 1 -i input.mp4 \\

-map 0:v -map 1:a -c copy output.mp4


Effects

Speed change


# 2x speed

ffmpeg -i input.mp4 -filter_complex "[0:v]setpts=0.5*PTS[v];[0:a]atempo=2.0[a]" \\

-map "[v]" -map "[a]" output.mp4

# 0.5x speed (slow motion)

ffmpeg -i input.mp4 -filter_complex "[0:v]setpts=2.0*PTS[v];[0:a]atempo=0.5[a]" \\

-map "[v]" -map "[a]" output.mp4

Note: atempoonly accepts values between 0.5 and 100.0. For extreme slow-motion, chain multiple atempo filters:atempo=0.5,atempo=0.5 for 0.25x.

Reverse


ffmpeg -i input.mp4 -vf reverse -af areverse output.mp4

Warning: reverse loads the entire video into memory. Only use on short clips.

Rotate


# 90 degrees clockwise

ffmpeg -i input.mp4 -vf "transpose=1" output.mp4

# 90 degrees counter-clockwise

ffmpeg -i input.mp4 -vf "transpose=2" output.mp4

# 180 degrees

ffmpeg -i input.mp4 -vf "transpose=1,transpose=1" output.mp4

# Arbitrary angle (in radians)

ffmpeg -i input.mp4 -vf "rotate=PI/6" output.mp4

Color adjustments


# Brightness, contrast, saturation

ffmpeg -i input.mp4 -vf "eq=brightness=0.1:contrast=1.2:saturation=1.3" output.mp4

# Convert to grayscale

ffmpeg -i input.mp4 -vf "hue=s=0" output.mp4

# Color curves (cinematic look)

ffmpeg -i input.mp4 -vf "curves=preset=cross_process" output.mp4

Available curve presets: none, color_negative, cross_process, darker, increase_contrast, lighter, linear_contrast, medium_contrast, negative, strong_contrast, vintage

Blur


# Box blur

ffmpeg -i input.mp4 -vf "boxblur=5:1" output.mp4

# Gaussian blur

ffmpeg -i input.mp4 -vf "gblur=sigma=10" output.mp4

Picture-in-picture


ffmpeg -i main.mp4 -i overlay.mp4 \\

-filter_complex "[1:v]scale=320:240[pip];[0:v][pip]overlay=W-w-10:H-h-10" \\

output.mp4

Stabilize shaky video


# Step 1: Analyze

ffmpeg -i input.mp4 -vf "vidstabdetect=shakiness=5:accuracy=15" -f null -

# Step 2: Apply stabilization

ffmpeg -i input.mp4 -vf "vidstabtransform=smoothing=10:input=transforms.trf" output.mp4


Format Conversion

MP4 (H.264 + AAC — most compatible)


ffmpeg -i input.webm -c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k output.mp4

CRF values: 18 (high quality, large file) to 28 (lower quality, small file). 23 is a good default.

WebM (VP9 + Opus — web-optimized)


ffmpeg -i input.mp4 -c:v libvpx-vp9 -crf 30 -b:v 0 -c:a libopus output.webm

MOV (ProRes — editing friendly)


ffmpeg -i input.mp4 -c:v prores_ks -profile:v 3 -c:a pcm_s16le output.mov

Audio only


ffmpeg -i input.mp4 -vn -c:a libmp3lame -q:a 2 output.mp3

ffmpeg -i input.mp4 -vn -c:a libopus -b:a 128k output.opus

ffmpeg -i input.mp4 -vn -c:a flac output.flac


Image Extraction

Extract a single frame


ffmpeg -i input.mp4 -ss 00:00:05 -frames:v 1 frame.png

Extract frames at interval


# One frame per second

ffmpeg -i input.mp4 -vf "fps=1" frames/frame_%04d.png

# One frame every 10 seconds

ffmpeg -i input.mp4 -vf "fps=1/10" frames/frame_%04d.png

Create a thumbnail sheet (contact sheet)


ffmpeg -i input.mp4 -frames:v 1 -vf "select=not(mod(n\\100)),scale=160:90,tile=5x4" thumbnail.png


GIF Creation

Basic GIF


ffmpeg -i input.mp4 -ss 00:00:02 -t 5 -vf "fps=15,scale=480:-1" output.gif

High-quality GIF with palette


# Step 1: Generate palette

ffmpeg -i input.mp4 -ss 2 -t 5 -vf "fps=15,scale=480:-1:flags=lanczos,palettegen" palette.png

# Step 2: Use palette for GIF

ffmpeg -i input.mp4 -i palette.png -ss 2 -t 5 \\

-filter_complex "[0:v]fps=15,scale=480:-1:flags=lanczos[v];[v][1:v]paletteuse" output.gif

The two-pass palette method produces significantly better GIFs with less banding.


Resizing and Cropping

Scale to specific dimensions


# Exact size

ffmpeg -i input.mp4 -vf "scale=1280:720" -c:a copy output.mp4

# Scale width, auto height (maintain aspect ratio)

ffmpeg -i input.mp4 -vf "scale=1280:-2" -c:a copy output.mp4

# Scale to fit within bounds (maintain aspect ratio)

ffmpeg -i input.mp4 -vf "scale='min(1280,iw)':'min(720,ih)':force_original_aspect_ratio=decrease" output.mp4

Crop


# Crop to 1280x720 from center

ffmpeg -i input.mp4 -vf "crop=1280:720" -c:a copy output.mp4

# Crop with offset (x=100, y=50)

ffmpeg -i input.mp4 -vf "crop=1280:720:100:50" -c:a copy output.mp4

# Crop to 16:9 from center

ffmpeg -i input.mp4 -vf "crop=ih*16/9:ih" -c:a copy output.mp4

Add padding/letterbox


# Add black bars to make 16:9

ffmpeg -i input.mp4 -vf "pad=ih*16/9:ih:(ow-iw)/2:(oh-ih)/2:black" output.mp4


Watermarks and Overlays

Image watermark


# Bottom-right corner with padding

ffmpeg -i video.mp4 -i watermark.png \\

-filter_complex "overlay=W-w-10:H-h-10" output.mp4

# With opacity

ffmpeg -i video.mp4 -i watermark.png \\

-filter_complex "[1:v]format=rgba,colorchannelmixer=aa=0.5[wm];[0:v][wm]overlay=W-w-10:H-h-10" output.mp4

Timed overlay (appears at specific time)


ffmpeg -i video.mp4 -i overlay.png \\

-filter_complex "overlay=10:10:enable='between(t,3,8)'" output.mp4


Compression

Reduce file size while maintaining acceptable quality.

Quick compress (reduce bitrate)


# Moderate compression — good balance of size vs quality

ffmpeg -y -i input.mp4 \\

-c:v libx264 -crf 28 -preset medium \\

-c:a aac -b:a 96k \\

output_compressed.mp4

# Aggressive compression — much smaller file, noticeable quality loss

ffmpeg -y -i input.mp4 \\

-c:v libx264 -crf 32 -preset slow \\

-c:a aac -b:a 64k \\

output_small.mp4

# Target a specific file size (e.g., 25MB for Discord/email)

# Calculate bitrate: bitrate = (target_size_MB * 8192) / duration_seconds

# For a 60s video targeting 25MB: bitrate = (25 * 8192) / 60 ≈ 3413 kbps

ffmpeg -y -i input.mp4 \\

-c:v libx264 -b:v 3000k -maxrate 3400k -bufsize 6800k -preset medium \\

-c:a aac -b:a 128k \\

output_25mb.mp4

Two-pass encoding (best quality at target size)


ffmpeg -y -i input.mp4 \\

-c:v libx264 -b:v 2000k -preset medium -pass 1 \\

-an -f null /dev/null

ffmpeg -y -i input.mp4 \\

-c:v libx264 -b:v 2000k -preset medium -pass 2 \\

-c:a aac -b:a 128k \\

output_2pass.mp4

Reduce resolution for smaller file


# Downscale to 720p (keeps aspect ratio)

ffmpeg -y -i input.mp4 \\

-vf "scale=-2:720" \\

-c:v libx264 -crf 23 -preset medium \\

-c:a aac -b:a 128k \\

output_720p.mp4

# Downscale to 480p

ffmpeg -y -i input.mp4 \\

-vf "scale=-2:480" \\

-c:v libx264 -crf 23 -preset medium \\

-c:a aac -b:a 96k \\

output_480p.mp4

CRF guide: 18 = visually lossless, 23 = default (good quality), 28 = moderate compression, 32 = heavy compression, 40+ = very low quality. Each +6 roughly halves the file size.


Stabilization

Stabilize shaky footage using FFmpeg's vidstab filter (two-pass process).

Basic stabilization


# Pass 1: Analyze motion (creates transforms.trf)

ffmpeg -y -i shaky.mp4 \\

-vf vidstabdetect=shakiness=5:accuracy=15:result=transforms.trf \\

-f null -

# Pass 2: Apply stabilization

ffmpeg -y -i shaky.mp4 \\

-vf vidstabtransform=input=transforms.trf:smoothing=10:crop=black:zoom=1 \\

-c:v libx264 -crf 23 -preset medium \\

-c:a copy \\

stabilized.mp4

Node.js wrapper


async function stabilizeVideo(inputPath: string, outputPath: string, smoothing = 10) {

const transformsFile = inputPath + '.transforms.trf';

// Pass 1: detect motion

await execFileAsync('ffmpeg', [

'-y', '-i', inputPath,

'-vf', `vidstabdetect=shakiness=5:accuracy=15:result=${transformsFile}`,

'-f', 'null', '-'

]);

// Pass 2: apply stabilization

await execFileAsync('ffmpeg', [

'-y', '-i', inputPath,

'-vf', `vidstabtransform=input=${transformsFile}:smoothing=${smoothing}:crop=black:zoom=1`,

'-c:v', 'libx264', '-crf', '23', '-preset', 'medium',

'-c:a', 'copy',

outputPath

]);

await fs.promises.unlink(transformsFile).catch(() => {});

}

Parameters

  • shakiness(1-10): How shaky the video is. Default 5, use 8-10 for very shaky footage. -smoothing(0-30): How much to smooth the camera path. Higher = smoother but may crop more. Default 10.

-zoom (0-10): Extra zoom to hide black borders from stabilization. 1 = slight zoom, 0 = no zoom (may show borders).


Looping

Repeat a clip multiple times.


# Loop a clip 3 times using stream_loop

ffmpeg -y -stream_loop 2 -i input.mp4 \\

-c copy \\

output_looped.mp4

# Loop with re-encoding (more reliable for some formats)

ffmpeg -y -stream_loop 2 -i input.mp4 \\

-c:v libx264 -crf 23 -preset medium \\

-c:a aac -b:a 128k \\

output_looped.mp4

Note: -stream_loop 2means play the input 3 times total (original + 2 loops). Use-stream_loop 4 for 5 times, etc.

Create a boomerang effect (forward + reverse)


# Forward then reverse

ffmpeg -y -i input.mp4 \\

-filter_complex "[0:v]reverse[rv];[0:v][rv]concat=n=2:v=1:a=0[outv]" \\

-map "[outv]" \\

-c:v libx264 -crf 23 -preset medium \\

boomerang.mp4


Rotation and Flipping

Rotate (2)


# Rotate 90 degrees clockwise

ffmpeg -y -i input.mp4 \\

-vf "transpose=1" \\

-c:v libx264 -crf 23 -preset medium -c:a copy \\

rotated_90.mp4

# Rotate 90 degrees counter-clockwise

ffmpeg -y -i input.mp4 \\

-vf "transpose=2" \\

-c:v libx264 -crf 23 -preset medium -c:a copy \\

rotated_270.mp4

# Rotate 180 degrees

ffmpeg -y -i input.mp4 \\

-vf "transpose=1,transpose=1" \\

-c:v libx264 -crf 23 -preset medium -c:a copy \\

rotated_180.mp4

# Rotate by arbitrary angle (e.g., 15 degrees) — adds black borders

ffmpeg -y -i input.mp4 \\

-vf "rotate=15*PI/180:c=black:ow=rotw(15*PI/180):oh=roth(15*PI/180)" \\

-c:v libx264 -crf 23 -preset medium -c:a copy \\

rotated_15deg.mp4

Flip


# Flip horizontally (mirror)

ffmpeg -y -i input.mp4 \\

-vf "hflip" \\

-c:v libx264 -crf 23 -preset medium -c:a copy \\

flipped_horizontal.mp4

# Flip vertically

ffmpeg -y -i input.mp4 \\

-vf "vflip" \\

-c:v libx264 -crf 23 -preset medium -c:a copy \\

flipped_vertical.mp4

transpose values: 0 = 90° counter-clockwise + vertical flip, 1 = 90° clockwise, 2 = 90° counter-clockwise, 3 = 90° clockwise + vertical flip.


Split Screen

Put two or more videos side by side or in a grid.

Two videos side by side (horizontal)


ffmpeg -y -i left.mp4 -i right.mp4 \\

-filter_complex "\\

[0:v]scale=640:360[left];\\

[1:v]scale=640:360[right];\\

[left][right]hstack=inputs=2[outv]" \\

-map "[outv]" -map 0:a? \\

-c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k \\

split_screen.mp4

Two videos stacked (vertical)


ffmpeg -y -i top.mp4 -i bottom.mp4 \\

-filter_complex "\\

[0:v]scale=1080:540[top];\\

[1:v]scale=1080:540[bottom];\\

[top][bottom]vstack=inputs=2[outv]" \\

-map "[outv]" -map 0:a? \\

-c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k \\

stacked.mp4

2x2 grid (four videos)


ffmpeg -y -i v1.mp4 -i v2.mp4 -i v3.mp4 -i v4.mp4 \\

-filter_complex "\\

[0:v]scale=540:360[a];[1:v]scale=540:360[b];\\

[2:v]scale=540:360[c];[3:v]scale=540:360[d];\\

[a][b]hstack=inputs=2[top];\\

[c][d]hstack=inputs=2[bottom];\\

[top][bottom]vstack=inputs=2[outv]" \\

-map "[outv]" -map 0:a? \\

-c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k \\

grid_2x2.mp4

Picture-in-picture (2)


# Small video in bottom-right corner (25% size)

ffmpeg -y -i main.mp4 -i pip.mp4 \\

-filter_complex "\\

[1:v]scale=iw/4:ih/4[pip];\\

[0:v][pip]overlay=W-w-20:H-h-20[outv]" \\

-map "[outv]" -map 0:a \\

-c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k \\

pip_output.mp4

# PiP with rounded corners (border radius)

ffmpeg -y -i main.mp4 -i pip.mp4 \\

-filter_complex "\\

[1:v]scale=320:180,format=yuva420p,\\

geq='lum=lum(X,Y):a=if(gt(abs(X-W/2),W/2-10)*gt(abs(Y-H/2),H/2-10),0,255)'[pip];\\

[0:v][pip]overlay=W-w-20:H-h-20[outv]" \\

-map "[outv]" -map 0:a \\

-c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k \\

pip_rounded.mp4


Muting and Audio Removal


# Remove audio track entirely

ffmpeg -y -i input.mp4 -an \\

-c:v copy \\

output_muted.mp4

# Replace audio with silence (keeps audio stream but silent)

ffmpeg -y -i input.mp4 \\

-f lavfi -i anullsrc=channel_layout=stereo:sample_rate=44100 \\

-c:v copy -c:a aac -shortest \\

output_silent.mp4

# Keep only audio (extract as MP3)

ffmpeg -y -i input.mp4 \\

-vn -c:a libmp3lame -q:a 2 \\

audio_only.mp3

# Keep only audio (extract as AAC/M4A)

ffmpeg -y -i input.mp4 \\

-vn -c:a copy \\

audio_only.m4a


Thumbnail Generation

Extract a single frame as an image file.


# Frame at a specific timestamp

ffmpeg -y -ss 00:00:05 -i input.mp4 \\

-frames:v 1 -q:v 2 \\

thumbnail.jpg

# Frame at 25% through the video

# (calculate timestamp from duration first with ffprobe)

ffmpeg -y -ss 30 -i input.mp4 \\

-frames:v 1 -q:v 2 \\

thumbnail_mid.jpg

# Multiple thumbnails at regular intervals (e.g., every 10 seconds)

ffmpeg -y -i input.mp4 \\

-vf "fps=1/10" -q:v 2 \\

thumbnails_%03d.jpg

# Best quality PNG thumbnail

ffmpeg -y -ss 00:00:05 -i input.mp4 \\

-frames:v 1 \\

thumbnail.png

Node.js: Smart thumbnail (pick the most interesting frame)


async function generateThumbnail(

videoPath: string,

outputPath: string,

timestamp?: number

): Promise<string> {

if (timestamp === undefined) {

// Default to 25% through the video — usually more interesting than the first frame

const { stdout } = await execFileAsync('ffprobe', [

'-v', 'quiet', '-print_format', 'json', '-show_format', videoPath

]);

const duration = parseFloat(JSON.parse(stdout).format.duration);

timestamp = duration * 0.25;

}

await execFileAsync('ffmpeg', [

'-y', '-ss', timestamp.toFixed(3), '-i', videoPath,

'-frames:v', '1', '-q:v', '2',

outputPath

]);

return outputPath;

}


Watermark Removal (Crop)

Remove a watermark by cropping it out. This is the only reliable FFmpeg approach — there is no "inpainting" filter.


# Crop out a watermark in the bottom-right corner

# Removes 60px from the bottom and 120px from the right

ffmpeg -y -i input.mp4 \\

-vf "crop=iw-120:ih-60:0:0" \\

-c:v libx264 -crf 23 -preset medium -c:a copy \\

output_no_watermark.mp4

# Crop out a watermark at the top-left

# Removes 80px from the top and 200px from the left

ffmpeg -y -i input.mp4 \\

-vf "crop=iw-200:ih-80:200:80" \\

-c:v libx264 -crf 23 -preset medium -c:a copy \\

output_no_watermark.mp4

# Crop + scale back to original resolution (fills the gap)

ffmpeg -y -i input.mp4 \\

-vf "crop=iw-120:ih-60:0:0,scale=1920:1080" \\

-c:v libx264 -crf 23 -preset medium -c:a copy \\

output_cleaned.mp4

Crop filter syntax: crop=out_w:out_h:x:y — output width, output height, x offset from left, y offset from top.

Note: Cropping changes the aspect ratio/resolution. If the user needs the original dimensions, scale back up after cropping, though this may introduce slight softness.


Logo Overlay (Branding)

Add a persistent logo/brand image to a video.


# Logo in the top-left corner with padding

ffmpeg -y -i video.mp4 -i logo.png \\

-filter_complex "overlay=20:20" \\

-c:v libx264 -crf 23 -preset medium -c:a copy \\

branded.mp4

# Logo in the bottom-right corner

ffmpeg -y -i video.mp4 -i logo.png \\

-filter_complex "overlay=W-w-20:H-h-20" \\

-c:v libx264 -crf 23 -preset medium -c:a copy \\

branded.mp4

# Logo with transparency (50% opacity)

ffmpeg -y -i video.mp4 -i logo.png \\

-filter_complex "[1:v]format=rgba,colorchannelmixer=aa=0.5[logo];[0:v][logo]overlay=W-w-20:20" \\

-c:v libx264 -crf 23 -preset medium -c:a copy \\

branded_subtle.mp4

# Logo scaled to a specific size (e.g., 100px wide, keep aspect ratio)

ffmpeg -y -i video.mp4 -i logo.png \\

-filter_complex "[1:v]scale=100:-1[logo];[0:v][logo]overlay=W-w-20:20" \\

-c:v libx264 -crf 23 -preset medium -c:a copy \\

branded_small.mp4

# Logo that appears only during the first 5 seconds

ffmpeg -y -i video.mp4 -i logo.png \\

-filter_complex "[1:v]scale=100:-1[logo];[0:v][logo]overlay=20:20:enable='between(t,0,5)'" \\

-c:v libx264 -crf 23 -preset medium -c:a copy \\

branded_intro.mp4

Overlay position shortcuts

  • Top-left: overlay=20:20
  • Top-right: overlay=W-w-20:20

  • Bottom-left: overlay=20:H-h-20

  • Bottom-right: overlay=W-w-20:H-h-20

  • Center: overlay=(W-w)/2:(H-h)/2


Social Media Reframing

Reframe videos for different platforms with the correct aspect ratio and resolution.

Platform Reference

| Platform | Aspect Ratio | Resolution | Duration | Codec |

|----------|-------------|------------|----------|-------|

| TikTok | 9:16 | 1080×1920 | 10–45s | H.264 |

| Instagram Reels | 9:16 | 1080×1920 | 10–30s | H.264 |

| Instagram Stories | 9:16 | 1080×1920 | 1–60s | H.264 |

| Instagram Feed | 1:1 or 4:5 | 1080×1080 or 1080×1350 | 3–60s | H.264 |

| YouTube Shorts | 9:16 | 1080×1920 | 15–60s | H.264 |

| YouTube (standard) | 16:9 | 1920×1080 | any | H.264 |

| X / Twitter | 16:9 | 1280×720 | 10–45s | H.264 |

| X / Twitter (square) | 1:1 | 720×720 | 10–45s | H.264 |

| Facebook Reels | 9:16 | 1080×1920 | 10–30s | H.264 |

| Facebook Feed | 16:9 or 1:1 | 1920×1080 or 1080×1080 | any | H.264 |

| LinkedIn | 16:9 or 1:1 | 1920×1080 or 1080×1080 | 10–60s | H.264 |

| Pinterest | 9:16 or 2:3 | 1080×1920 or 1000×1500 | 6–60s | H.264 |

| Snapchat Spotlight | 9:16 | 1080×1920 | 5–60s | H.264 |

Strategy selection by conversion type

  • Landscape → Vertical (9:16): Use blurred fill (Strategy 3) — looks polished on TikTok/Reels/Shorts
  • Landscape → Square (1:1): Use blurred fill or center crop depending on framing

  • Vertical → Landscape (16:9): Use blurred fill to avoid large black bars

  • Same orientation: Use center crop or scale directly

  • 4:5 portrait (Instagram Feed): Use letterbox or blurred fill from 16:9

Strategy 1: Center Crop

Crops from the center to fill the target ratio. Best when the subject is center-framed.


# 16:9 landscape → 9:16 vertical (TikTok/Reels) — center crop

ffmpeg -y -i input.mp4 \\

-vf "crop=ih*9/16:ih,scale=1080:1920" \\

-c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k \\

output_tiktok.mp4

# 16:9 landscape → 1:1 square (Instagram) — center crop

ffmpeg -y -i input.mp4 \\

-vf "crop=ih:ih,scale=1080:1080" \\

-c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k \\

output_instagram.mp4

# 9:16 vertical → 16:9 landscape — center crop

ffmpeg -y -i input.mp4 \\

-vf "crop=iw:iw*9/16,scale=1920:1080" \\

-c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k \\

output_youtube.mp4

# 9:16 vertical → 1:1 square — center crop

ffmpeg -y -i input.mp4 \\

-vf "crop=iw:iw,scale=1080:1080" \\

-c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k \\

output_square.mp4

Strategy 2: Letterbox / Pillarbox (black bars)

Fits the entire video inside the target frame with black bars filling the gaps. No content is lost.


# 16:9 → 9:16 with black bars (pillarbox)

ffmpeg -y -i input.mp4 \\

-vf "scale=1080:-2,pad=1080:1920:(ow-iw)/2:(oh-ih)/2:black" \\

-c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k \\

output_tiktok_letterbox.mp4

# 16:9 → 1:1 with black bars

ffmpeg -y -i input.mp4 \\

-vf "scale=1080:-2,pad=1080:1080:(ow-iw)/2:(oh-ih)/2:black" \\

-c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k \\

output_square_letterbox.mp4

# 9:16 → 16:9 with black bars (pillarbox)

ffmpeg -y -i input.mp4 \\

-vf "scale=-2:1080,pad=1920:1080:(ow-iw)/2:(oh-ih)/2:black" \\

-c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k \\

output_youtube_letterbox.mp4

Uses a blurred, scaled-up version of the video as background behind the original. Looks much more polished than black bars, especially for landscape-to-portrait conversion.


# 16:9 → 9:16 with blurred background fill (best for TikTok/Reels)

ffmpeg -y -i input.mp4 \\

-filter_complex "\\

[0:v]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,gblur=sigma=50[bg];\\

[0:v]scale=1080:1920:force_original_aspect_ratio=decrease[fg];\\

[bg][fg]overlay=(W-w)/2:(H-h)/2[outv]" \\

-map "[outv]" -map 0:a? \\

-c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k \\

output_tiktok_blur.mp4

# 16:9 → 1:1 with blurred background fill

ffmpeg -y -i input.mp4 \\

-filter_complex "\\

[0:v]scale=1080:1080:force_original_aspect_ratio=increase,crop=1080:1080,gblur=sigma=50[bg];\\

[0:v]scale=1080:1080:force_original_aspect_ratio=decrease[fg];\\

[bg][fg]overlay=(W-w)/2:(H-h)/2[outv]" \\

-map "[outv]" -map 0:a? \\

-c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k \\

output_square_blur.mp4

# 9:16 → 16:9 with blurred background fill

ffmpeg -y -i input.mp4 \\

-filter_complex "\\

[0:v]scale=1920:1080:force_original_aspect_ratio=increase,crop=1920:1080,gblur=sigma=50[bg];\\

[0:v]scale=1920:1080:force_original_aspect_ratio=decrease[fg];\\

[bg][fg]overlay=(W-w)/2:(H-h)/2[outv]" \\

-map "[outv]" -map 0:a? \\

-c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k \\

output_youtube_blur.mp4

# 16:9 → 4:5 portrait with blurred fill (Instagram Feed — preferred over 1:1)

ffmpeg -y -i input.mp4 \\

-filter_complex "\\

[0:v]scale=1080:1350:force_original_aspect_ratio=increase,crop=1080:1350,gblur=sigma=50[bg];\\

[0:v]scale=1080:1350:force_original_aspect_ratio=decrease[fg];\\

[bg][fg]overlay=(W-w)/2:(H-h)/2[outv]" \\

-map "[outv]" -map 0:a? \\

-c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k \\

output_instagram_feed_4x5.mp4

# 16:9 → 2:3 portrait with blurred fill (Pinterest)

ffmpeg -y -i input.mp4 \\

-filter_complex "\\

[0:v]scale=1000:1500:force_original_aspect_ratio=increase,crop=1000:1500,gblur=sigma=50[bg];\\

[0:v]scale=1000:1500:force_original_aspect_ratio=decrease[fg];\\

[bg][fg]overlay=(W-w)/2:(H-h)/2[outv]" \\

-map "[outv]" -map 0:a? \\

-c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k \\

output_pinterest_blur.mp4

Multi-platform Export (Node.js)

Export to all platforms in one go:


import { execFile } from 'child_process';

import { promisify } from 'util';

import * as path from 'path';

const execFileAsync = promisify(execFile);

interface PlatformSpec {

name: string;

width: number;

height: number;

suffix: string;

}

const PLATFORMS: Record<string, PlatformSpec> = {

tiktok: { name: 'TikTok', width: 1080, height: 1920, suffix: 'tiktok' },

instagram_reels: { name: 'Instagram Reels', width: 1080, height: 1920, suffix: 'ig_reels' },

reels: { name: 'Instagram Reels', width: 1080, height: 1920, suffix: 'ig_reels' },

instagram_stories: { name: 'Instagram Stories', width: 1080, height: 1920, suffix: 'ig_stories' },

instagram_feed: { name: 'Instagram Feed', width: 1080, height: 1080, suffix: 'ig_feed' },

instagram_feed_45: { name: 'Instagram Feed 4:5', width: 1080, height: 1350, suffix: 'ig_feed_4x5' },

youtube_shorts: { name: 'YouTube Shorts', width: 1080, height: 1920, suffix: 'yt_shorts' },

shorts: { name: 'YouTube Shorts', width: 1080, height: 1920, suffix: 'yt_shorts' },

youtube: { name: 'YouTube', width: 1920, height: 1080, suffix: 'youtube' },

twitter: { name: 'X / Twitter', width: 1280, height: 720, suffix: 'twitter' },

x: { name: 'X / Twitter', width: 1280, height: 720, suffix: 'twitter' },

twitter_sq: { name: 'X / Twitter Square', width: 720, height: 720, suffix: 'twitter_sq' },

facebook_reels: { name: 'Facebook Reels', width: 1080, height: 1920, suffix: 'fb_reels' },

facebook_feed: { name: 'Facebook Feed', width: 1920, height: 1080, suffix: 'fb_feed' },

linkedin: { name: 'LinkedIn', width: 1920, height: 1080, suffix: 'linkedin' },

pinterest: { name: 'Pinterest', width: 1080, height: 1920, suffix: 'pinterest' },

pinterest_2x3: { name: 'Pinterest 2:3', width: 1000, height: 1500, suffix: 'pinterest_2x3' },

snapchat: { name: 'Snapchat Spotlight', width: 1080, height: 1920, suffix: 'snapchat' },

};

async function reframeForPlatform(

inputPath: string,

platform: PlatformSpec,

outputDir: string,

strategy: 'crop' | 'letterbox' | 'blur' = 'blur'

): Promise<string> {

const ext = path.extname(inputPath);

const base = path.basename(inputPath, ext);

const outputPath = path.join(outputDir, `${base}_${platform.suffix}${ext}`);

const { width, height } = platform;

let filterComplex: string;

switch (strategy) {

case 'crop':

filterComplex = `scale=${width}:${height}:force_original_aspect_ratio=increase,crop=${width}:${height}`;

break;

case 'letterbox':

filterComplex = `scale=${width}:${height}:force_original_aspect_ratio=decrease,pad=${width}:${height}:(ow-iw)/2:(oh-ih)/2:black`;

break;

case 'blur':

filterComplex = [

`[0:v]scale=${width}:${height}:force_original_aspect_ratio=increase,crop=${width}:${height},gblur=sigma=50[bg]`,

`[0:v]scale=${width}:${height}:force_original_aspect_ratio=decrease[fg]`,

`[bg][fg]overlay=(W-w)/2:(H-h)/2[outv]`

].join(';');

break;

}

const args = ['-y', '-i', inputPath];

if (strategy === 'blur') {

args.push('-filter_complex', filterComplex, '-map', '[outv]', '-map', '0:a?');

} else {

args.push('-vf', filterComplex);

}

args.push(

'-c:v', 'libx264', '-crf', '23', '-preset', 'medium',

'-c:a', 'aac', '-b:a', '128k',

outputPath

);

await execFileAsync('ffmpeg', args);

return outputPath;

}

async function exportAllPlatforms(

inputPath: string,

outputDir: string,

platforms: string[] = ['youtube', 'tiktok', 'instagram', 'twitter'],

strategy: 'crop' | 'letterbox' | 'blur' = 'blur'

) {

const results: Array<{ platform: string; path: string }> = [];

for (const key of platforms) {

const spec = PLATFORMS[key];

if (!spec) continue;

console.log(`Reframing for ${spec.name} (${spec.width}x${spec.height})...`);

const outPath = await reframeForPlatform(inputPath, spec, outputDir, strategy);

results.push({ platform: spec.name, path: outPath });

console.log(` → ${outPath}`);

}

return results;

}