1 # FFmpeg Operations Reference
2
3 Complete command reference for video editing operations in Replit. All examples use Node.js with `child_process.execFile`or`fluent-ffmpeg`.
4
5 ## Table of Contents
6
7 1. [Trimming and Cutting](#trimming-and-cutting)
8 2. [Concatenation](#concatenation)
9
10 3. [Transitions](#transitions)
11 4. [Text Overlays](#text-overlays)
12
13 5. [Subtitles](#subtitles)
14 6. [Audio](#audio)
15
16 7. [Effects](#effects)
17 8. [Format Conversion](#format-conversion)
18
19 9. [Image Extraction](#image-extraction)
20 10. [GIF Creation](#gif-creation)
21
22 11. [Resizing and Cropping](#resizing-and-cropping)
23 12. [Watermarks and Overlays](#watermarks-and-overlays)
24
25 13. [Compression](#compression)
26 14. [Stabilization](#stabilization)
27
28 15. [Looping](#looping)
29 16. [Rotation and Flipping](#rotation-and-flipping)
30
31 17. [Split Screen](#split-screen)
32 18. [Muting and Audio Removal](#muting-and-audio-removal)
33
34 19. [Thumbnail Generation](#thumbnail-generation)
35 20. [Watermark Removal (Crop)](#watermark-removal-crop)
36
37 21. [Logo Overlay (Branding)](#logo-overlay-branding)
38 22. [Social Media Reframing](#social-media-reframing)
39
40 ---
41
42 ## Trimming and Cutting
43
44 ### Trim without re-encoding (fast, frame-accurate at keyframes)
45
46 ```bash
47
48 ffmpeg -i input.mp4 -ss 00:00:30 -to 00:01:15 -c copy output.mp4
49
50 ```
51
52 Place `-ss`before`-i`for faster seeking (input seeking), or after`-i`for frame-accurate seeking (output seeking). Using`-c copy` avoids re-encoding but may have imprecise start points at non-keyframe positions.
53
54 ### Trim with re-encoding (slower, frame-accurate)
55
56 ```bash
57
58 ffmpeg -i input.mp4 -ss 00:00:30 -to 00:01:15 -c:v libx264 -c:a aac output.mp4
59
60 ```
61
62 ### Remove a section (keep before and after)
63
64 Split into two parts and concatenate:
65
66 ```bash
67
68 ffmpeg -i input.mp4 -t 00:00:30 -c copy part1.mp4
69
70 ffmpeg -i input.mp4 -ss 00:01:00 -c copy part2.mp4
71
72 ```
73
74 Then concatenate (see Concatenation section).
75
76 ---
77
78 ## Concatenation
79
80 ### Concat demuxer (same codec, resolution, frame rate)
81
82 Create a text file `concat_list.txt`:
83
84 ```text
85
86 file 'clip1.mp4'
87
88 file 'clip2.mp4'
89
90 file 'clip3.mp4'
91
92 ```
93
94 ```bash
95
96 ffmpeg -f concat -safe 0 -i concat_list.txt -c copy output.mp4
97
98 ```
99
100 This is the fastest method because it avoids re-encoding, but all clips must share the same codec, resolution, and frame rate.
101
102 ### Concat filter (different codecs/resolutions)
103
104 ```bash
105
106 ffmpeg -i clip1.mp4 -i clip2.mp4 \\
107
108 -filter_complex "[0:v][0:a][1:v][1:a]concat=n=2:v=1:a=1[outv][outa]" \\
109
110 -map "[outv]" -map "[outa]" output.mp4
111
112 ```
113
114 This re-encodes everything but handles mismatched inputs. Scale inputs to a common resolution first if they differ.
115
116 ---
117
118 ## Transitions
119
120 ### Crossfade between two clips
121
122 ```bash
123
124 ffmpeg -i clip1.mp4 -i clip2.mp4 \\
125
126 -filter_complex "\\
127
128 [0:v]trim=duration=5,setpts=PTS-STARTPTS[v0]; \\
129
130 [1:v]trim=duration=5,setpts=PTS-STARTPTS[v1]; \\
131
132 [v0][v1]xfade=transition=fade:duration=1:offset=4[outv]; \\
133
134 [0:a]atrim=duration=5,asetpts=PTS-STARTPTS[a0]; \\
135
136 [1:a]atrim=duration=5,asetpts=PTS-STARTPTS[a1]; \\
137
138 [a0][a1]acrossfade=d=1[outa]" \\
139
140 -map "[outv]" -map "[outa]" output.mp4
141
142 ```
143
144 ### Available xfade transitions
145
146 `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`
147
148 ### Fade in/out
149
150 ```bash
151
152 # Fade in first 2 seconds
153
154 ffmpeg -i input.mp4 -vf "fade=t=in:st=0:d=2" -c:a copy output.mp4
155
156 # Fade out last 2 seconds (requires knowing duration)
157
158 ffmpeg -i input.mp4 -vf "fade=t=out:st=8:d=2" -c:a copy output.mp4
159
160 # Audio fade in/out
161
162 ffmpeg -i input.mp4 -af "afade=t=in:st=0:d=2,afade=t=out:st=8:d=2" output.mp4
163
164 ```
165
166 ---
167
168 ## Text Overlays
169
170 ### Static text overlay
171
172 ```bash
173
174 ffmpeg -i input.mp4 \\
175
176 -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" \\
177
178 output.mp4
179
180 ```
181
182 ### Text with background box
183
184 ```bash
185
186 ffmpeg -i input.mp4 \\
187
188 -vf "drawtext=text='Title':fontsize=64:fontcolor=white:box=1:boxcolor=black@0.6:boxborderw=10:x=(w-text_w)/2:y=50" \\
189
190 output.mp4
191
192 ```
193
194 ### Timed text (appears and disappears)
195
196 ```bash
197
198 ffmpeg -i input.mp4 \\
199
200 -vf "drawtext=text='Scene 1':fontsize=48:fontcolor=white:x=(w-text_w)/2:y=(h-text_h)/2:enable='between(t,2,5)'" \\
201
202 output.mp4
203
204 ```
205
206 ### Font discovery
207
208 List available fonts in the Replit environment:
209
210 ```bash
211
212 fc-list : family style | sort
213
214 ```
215
216 Or use a custom font file by downloading it to the project and referencing it with `fontfile=`.
217
218 ---
219
220 ## Subtitles
221
222 ### Burn subtitles from SRT file
223
224 ```bash
225
226 ffmpeg -i input.mp4 -vf "subtitles=subs.srt" output.mp4
227
228 ```
229
230 ### Burn subtitles with styling
231
232 ```bash
233
234 ffmpeg -i input.mp4 \\
235
236 -vf "subtitles=subs.srt:force_style='FontSize=24,FontName=Arial,PrimaryColour=&Hffffff&,OutlineColour=&H000000&,Outline=2'" \\
237
238 output.mp4
239
240 ```
241
242 ### Burn ASS/SSA subtitles (preserves advanced styling)
243
244 ```bash
245
246 ffmpeg -i input.mp4 -vf "ass=subs.ass" output.mp4
247
248 ```
249
250 ### Embed subtitles as a stream (soft subs, MKV only)
251
252 ```bash
253
254 ffmpeg -i input.mp4 -i subs.srt -c copy -c:s srt output.mkv
255
256 ```
257
258 ---
259
260 ## Audio
261
262 ### Add background music
263
264 ```bash
265
266 ffmpeg -i video.mp4 -i music.mp3 \\
267
268 -filter_complex "[1:a]volume=0.3[music];[0:a][music]amix=inputs=2:duration=first[outa]" \\
269
270 -map 0:v -map "[outa]" -c:v copy output.mp4
271
272 ```
273
274 ### Replace audio entirely
275
276 ```bash
277
278 ffmpeg -i video.mp4 -i new_audio.mp3 \\
279
280 -map 0:v -map 1:a -c:v copy -shortest output.mp4
281
282 ```
283
284 ### Extract audio
285
286 ```bash
287
288 ffmpeg -i video.mp4 -vn -c:a libmp3lame -q:a 2 output.mp3
289
290 ffmpeg -i video.mp4 -vn -c:a copy output.aac
291
292 ```
293
294 ### Remove audio
295
296 ```bash
297
298 ffmpeg -i input.mp4 -an -c:v copy output.mp4
299
300 ```
301
302 ### Adjust volume
303
304 ```bash
305
306 # Set to 50% volume
307
308 ffmpeg -i input.mp4 -af "volume=0.5" -c:v copy output.mp4
309
310 # Increase by 10dB
311
312 ffmpeg -i input.mp4 -af "volume=10dB" -c:v copy output.mp4
313
314 # Normalize audio
315
316 ffmpeg -i input.mp4 -af "loudnorm" -c:v copy output.mp4
317
318 ```
319
320 ### Audio delay/offset
321
322 ```bash
323
324 # Delay audio by 1 second
325
326 ffmpeg -i input.mp4 -itsoffset 1 -i input.mp4 \\
327
328 -map 0:v -map 1:a -c copy output.mp4
329
330 ```
331
332 ---
333
334 ## Effects
335
336 ### Speed change
337
338 ```bash
339
340 # 2x speed
341
342 ffmpeg -i input.mp4 -filter_complex "[0:v]setpts=0.5*PTS[v];[0:a]atempo=2.0[a]" \\
343
344 -map "[v]" -map "[a]" output.mp4
345
346 # 0.5x speed (slow motion)
347
348 ffmpeg -i input.mp4 -filter_complex "[0:v]setpts=2.0*PTS[v];[0:a]atempo=0.5[a]" \\
349
350 -map "[v]" -map "[a]" output.mp4
351
352 ```
353
354 Note: `atempo`only 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.
355
356 ### Reverse
357
358 ```bash
359
360 ffmpeg -i input.mp4 -vf reverse -af areverse output.mp4
361
362 ```
363
364 Warning: `reverse` loads the entire video into memory. Only use on short clips.
365
366 ### Rotate
367
368 ```bash
369
370 # 90 degrees clockwise
371
372 ffmpeg -i input.mp4 -vf "transpose=1" output.mp4
373
374 # 90 degrees counter-clockwise
375
376 ffmpeg -i input.mp4 -vf "transpose=2" output.mp4
377
378 # 180 degrees
379
380 ffmpeg -i input.mp4 -vf "transpose=1,transpose=1" output.mp4
381
382 # Arbitrary angle (in radians)
383
384 ffmpeg -i input.mp4 -vf "rotate=PI/6" output.mp4
385
386 ```
387
388 ### Color adjustments
389
390 ```bash
391
392 # Brightness, contrast, saturation
393
394 ffmpeg -i input.mp4 -vf "eq=brightness=0.1:contrast=1.2:saturation=1.3" output.mp4
395
396 # Convert to grayscale
397
398 ffmpeg -i input.mp4 -vf "hue=s=0" output.mp4
399
400 # Color curves (cinematic look)
401
402 ffmpeg -i input.mp4 -vf "curves=preset=cross_process" output.mp4
403
404 ```
405
406 Available curve presets: `none`, `color_negative`, `cross_process`, `darker`, `increase_contrast`, `lighter`, `linear_contrast`, `medium_contrast`, `negative`, `strong_contrast`, `vintage`
407
408 ### Blur
409
410 ```bash
411
412 # Box blur
413
414 ffmpeg -i input.mp4 -vf "boxblur=5:1" output.mp4
415
416 # Gaussian blur
417
418 ffmpeg -i input.mp4 -vf "gblur=sigma=10" output.mp4
419
420 ```
421
422 ### Picture-in-picture
423
424 ```bash
425
426 ffmpeg -i main.mp4 -i overlay.mp4 \\
427
428 -filter_complex "[1:v]scale=320:240[pip];[0:v][pip]overlay=W-w-10:H-h-10" \\
429
430 output.mp4
431
432 ```
433
434 ### Stabilize shaky video
435
436 ```bash
437
438 # Step 1: Analyze
439
440 ffmpeg -i input.mp4 -vf "vidstabdetect=shakiness=5:accuracy=15" -f null -
441
442 # Step 2: Apply stabilization
443
444 ffmpeg -i input.mp4 -vf "vidstabtransform=smoothing=10:input=transforms.trf" output.mp4
445
446 ```
447
448 ---
449
450 ## Format Conversion
451
452 ### MP4 (H.264 + AAC — most compatible)
453
454 ```bash
455
456 ffmpeg -i input.webm -c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k output.mp4
457
458 ```
459
460 CRF values: 18 (high quality, large file) to 28 (lower quality, small file). 23 is a good default.
461
462 ### WebM (VP9 + Opus — web-optimized)
463
464 ```bash
465
466 ffmpeg -i input.mp4 -c:v libvpx-vp9 -crf 30 -b:v 0 -c:a libopus output.webm
467
468 ```
469
470 ### MOV (ProRes — editing friendly)
471
472 ```bash
473
474 ffmpeg -i input.mp4 -c:v prores_ks -profile:v 3 -c:a pcm_s16le output.mov
475
476 ```
477
478 ### Audio only
479
480 ```bash
481
482 ffmpeg -i input.mp4 -vn -c:a libmp3lame -q:a 2 output.mp3
483
484 ffmpeg -i input.mp4 -vn -c:a libopus -b:a 128k output.opus
485
486 ffmpeg -i input.mp4 -vn -c:a flac output.flac
487
488 ```
489
490 ---
491
492 ## Image Extraction
493
494 ### Extract a single frame
495
496 ```bash
497
498 ffmpeg -i input.mp4 -ss 00:00:05 -frames:v 1 frame.png
499
500 ```
501
502 ### Extract frames at interval
503
504 ```bash
505
506 # One frame per second
507
508 ffmpeg -i input.mp4 -vf "fps=1" frames/frame_%04d.png
509
510 # One frame every 10 seconds
511
512 ffmpeg -i input.mp4 -vf "fps=1/10" frames/frame_%04d.png
513
514 ```
515
516 ### Create a thumbnail sheet (contact sheet)
517
518 ```bash
519
520 ffmpeg -i input.mp4 -frames:v 1 -vf "select=not(mod(n\\100)),scale=160:90,tile=5x4" thumbnail.png
521
522 ```
523
524 ---
525
526 ## GIF Creation
527
528 ### Basic GIF
529
530 ```bash
531
532 ffmpeg -i input.mp4 -ss 00:00:02 -t 5 -vf "fps=15,scale=480:-1" output.gif
533
534 ```
535
536 ### High-quality GIF with palette
537
538 ```bash
539
540 # Step 1: Generate palette
541
542 ffmpeg -i input.mp4 -ss 2 -t 5 -vf "fps=15,scale=480:-1:flags=lanczos,palettegen" palette.png
543
544 # Step 2: Use palette for GIF
545
546 ffmpeg -i input.mp4 -i palette.png -ss 2 -t 5 \\
547
548 -filter_complex "[0:v]fps=15,scale=480:-1:flags=lanczos[v];[v][1:v]paletteuse" output.gif
549
550 ```
551
552 The two-pass palette method produces significantly better GIFs with less banding.
553
554 ---
555
556 ## Resizing and Cropping
557
558 ### Scale to specific dimensions
559
560 ```bash
561
562 # Exact size
563
564 ffmpeg -i input.mp4 -vf "scale=1280:720" -c:a copy output.mp4
565
566 # Scale width, auto height (maintain aspect ratio)
567
568 ffmpeg -i input.mp4 -vf "scale=1280:-2" -c:a copy output.mp4
569
570 # Scale to fit within bounds (maintain aspect ratio)
571
572 ffmpeg -i input.mp4 -vf "scale='min(1280,iw)':'min(720,ih)':force_original_aspect_ratio=decrease" output.mp4
573
574 ```
575
576 ### Crop
577
578 ```bash
579
580 # Crop to 1280x720 from center
581
582 ffmpeg -i input.mp4 -vf "crop=1280:720" -c:a copy output.mp4
583
584 # Crop with offset (x=100, y=50)
585
586 ffmpeg -i input.mp4 -vf "crop=1280:720:100:50" -c:a copy output.mp4
587
588 # Crop to 16:9 from center
589
590 ffmpeg -i input.mp4 -vf "crop=ih*16/9:ih" -c:a copy output.mp4
591
592 ```
593
594 ### Add padding/letterbox
595
596 ```bash
597
598 # Add black bars to make 16:9
599
600 ffmpeg -i input.mp4 -vf "pad=ih*16/9:ih:(ow-iw)/2:(oh-ih)/2:black" output.mp4
601
602 ```
603
604 ---
605
606 ## Watermarks and Overlays
607
608 ### Image watermark
609
610 ```bash
611
612 # Bottom-right corner with padding
613
614 ffmpeg -i video.mp4 -i watermark.png \\
615
616 -filter_complex "overlay=W-w-10:H-h-10" output.mp4
617
618 # With opacity
619
620 ffmpeg -i video.mp4 -i watermark.png \\
621
622 -filter_complex "[1:v]format=rgba,colorchannelmixer=aa=0.5[wm];[0:v][wm]overlay=W-w-10:H-h-10" output.mp4
623
624 ```
625
626 ### Timed overlay (appears at specific time)
627
628 ```bash
629
630 ffmpeg -i video.mp4 -i overlay.png \\
631
632 -filter_complex "overlay=10:10:enable='between(t,3,8)'" output.mp4
633
634 ```
635
636 ---
637
638 ## Compression
639
640 Reduce file size while maintaining acceptable quality.
641
642 ### Quick compress (reduce bitrate)
643
644 ```bash
645
646 # Moderate compression — good balance of size vs quality
647
648 ffmpeg -y -i input.mp4 \\
649
650 -c:v libx264 -crf 28 -preset medium \\
651
652 -c:a aac -b:a 96k \\
653
654 output_compressed.mp4
655
656 # Aggressive compression — much smaller file, noticeable quality loss
657
658 ffmpeg -y -i input.mp4 \\
659
660 -c:v libx264 -crf 32 -preset slow \\
661
662 -c:a aac -b:a 64k \\
663
664 output_small.mp4
665
666 # Target a specific file size (e.g., 25MB for Discord/email)
667
668 # Calculate bitrate: bitrate = (target_size_MB * 8192) / duration_seconds
669
670 # For a 60s video targeting 25MB: bitrate = (25 * 8192) / 60 ≈ 3413 kbps
671
672 ffmpeg -y -i input.mp4 \\
673
674 -c:v libx264 -b:v 3000k -maxrate 3400k -bufsize 6800k -preset medium \\
675
676 -c:a aac -b:a 128k \\
677
678 output_25mb.mp4
679
680 ```
681
682 ### Two-pass encoding (best quality at target size)
683
684 ```bash
685
686 ffmpeg -y -i input.mp4 \\
687
688 -c:v libx264 -b:v 2000k -preset medium -pass 1 \\
689
690 -an -f null /dev/null
691
692 ffmpeg -y -i input.mp4 \\
693
694 -c:v libx264 -b:v 2000k -preset medium -pass 2 \\
695
696 -c:a aac -b:a 128k \\
697
698 output_2pass.mp4
699
700 ```
701
702 ### Reduce resolution for smaller file
703
704 ```bash
705
706 # Downscale to 720p (keeps aspect ratio)
707
708 ffmpeg -y -i input.mp4 \\
709
710 -vf "scale=-2:720" \\
711
712 -c:v libx264 -crf 23 -preset medium \\
713
714 -c:a aac -b:a 128k \\
715
716 output_720p.mp4
717
718 # Downscale to 480p
719
720 ffmpeg -y -i input.mp4 \\
721
722 -vf "scale=-2:480" \\
723
724 -c:v libx264 -crf 23 -preset medium \\
725
726 -c:a aac -b:a 96k \\
727
728 output_480p.mp4
729
730 ```
731
732 **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.
733
734 ---
735
736 ## Stabilization
737
738 Stabilize shaky footage using FFmpeg's vidstab filter (two-pass process).
739
740 ### Basic stabilization
741
742 ```bash
743
744 # Pass 1: Analyze motion (creates transforms.trf)
745
746 ffmpeg -y -i shaky.mp4 \\
747
748 -vf vidstabdetect=shakiness=5:accuracy=15:result=transforms.trf \\
749
750 -f null -
751
752 # Pass 2: Apply stabilization
753
754 ffmpeg -y -i shaky.mp4 \\
755
756 -vf vidstabtransform=input=transforms.trf:smoothing=10:crop=black:zoom=1 \\
757
758 -c:v libx264 -crf 23 -preset medium \\
759
760 -c:a copy \\
761
762 stabilized.mp4
763
764 ```
765
766 ### Node.js wrapper
767
768 ```typescript
769
770 async function stabilizeVideo(inputPath: string, outputPath: string, smoothing = 10) {
771
772 const transformsFile = inputPath + '.transforms.trf';
773
774 // Pass 1: detect motion
775
776 await execFileAsync('ffmpeg', [
777
778 '-y', '-i', inputPath,
779
780 '-vf', `vidstabdetect=shakiness=5:accuracy=15:result=${transformsFile}`,
781
782 '-f', 'null', '-'
783
784 ]);
785
786 // Pass 2: apply stabilization
787
788 await execFileAsync('ffmpeg', [
789
790 '-y', '-i', inputPath,
791
792 '-vf', `vidstabtransform=input=${transformsFile}:smoothing=${smoothing}:crop=black:zoom=1`,
793
794 '-c:v', 'libx264', '-crf', '23', '-preset', 'medium',
795
796 '-c:a', 'copy',
797
798 outputPath
799
800 ]);
801
802 await fs.promises.unlink(transformsFile).catch(() => {});
803
804 }
805
806 ```
807
808 ### Parameters
809
810 - `shakiness`(1-10): How shaky the video is. Default 5, use 8-10 for very shaky footage.
811 -`smoothing`(0-30): How much to smooth the camera path. Higher = smoother but may crop more. Default 10.
812
813 -`zoom` (0-10): Extra zoom to hide black borders from stabilization. 1 = slight zoom, 0 = no zoom (may show borders).
814
815 ---
816
817 ## Looping
818
819 Repeat a clip multiple times.
820
821 ```bash
822
823 # Loop a clip 3 times using stream_loop
824
825 ffmpeg -y -stream_loop 2 -i input.mp4 \\
826
827 -c copy \\
828
829 output_looped.mp4
830
831 # Loop with re-encoding (more reliable for some formats)
832
833 ffmpeg -y -stream_loop 2 -i input.mp4 \\
834
835 -c:v libx264 -crf 23 -preset medium \\
836
837 -c:a aac -b:a 128k \\
838
839 output_looped.mp4
840
841 ```
842
843 **Note:** `-stream_loop 2`means play the input 3 times total (original + 2 loops). Use`-stream_loop 4` for 5 times, etc.
844
845 ### Create a boomerang effect (forward + reverse)
846
847 ```bash
848
849 # Forward then reverse
850
851 ffmpeg -y -i input.mp4 \\
852
853 -filter_complex "[0:v]reverse[rv];[0:v][rv]concat=n=2:v=1:a=0[outv]" \\
854
855 -map "[outv]" \\
856
857 -c:v libx264 -crf 23 -preset medium \\
858
859 boomerang.mp4
860
861 ```
862
863 ---
864
865 ## Rotation and Flipping
866
867 ### Rotate (2)
868
869 ```bash
870
871 # Rotate 90 degrees clockwise
872
873 ffmpeg -y -i input.mp4 \\
874
875 -vf "transpose=1" \\
876
877 -c:v libx264 -crf 23 -preset medium -c:a copy \\
878
879 rotated_90.mp4
880
881 # Rotate 90 degrees counter-clockwise
882
883 ffmpeg -y -i input.mp4 \\
884
885 -vf "transpose=2" \\
886
887 -c:v libx264 -crf 23 -preset medium -c:a copy \\
888
889 rotated_270.mp4
890
891 # Rotate 180 degrees
892
893 ffmpeg -y -i input.mp4 \\
894
895 -vf "transpose=1,transpose=1" \\
896
897 -c:v libx264 -crf 23 -preset medium -c:a copy \\
898
899 rotated_180.mp4
900
901 # Rotate by arbitrary angle (e.g., 15 degrees) — adds black borders
902
903 ffmpeg -y -i input.mp4 \\
904
905 -vf "rotate=15*PI/180:c=black:ow=rotw(15*PI/180):oh=roth(15*PI/180)" \\
906
907 -c:v libx264 -crf 23 -preset medium -c:a copy \\
908
909 rotated_15deg.mp4
910
911 ```
912
913 ### Flip
914
915 ```bash
916
917 # Flip horizontally (mirror)
918
919 ffmpeg -y -i input.mp4 \\
920
921 -vf "hflip" \\
922
923 -c:v libx264 -crf 23 -preset medium -c:a copy \\
924
925 flipped_horizontal.mp4
926
927 # Flip vertically
928
929 ffmpeg -y -i input.mp4 \\
930
931 -vf "vflip" \\
932
933 -c:v libx264 -crf 23 -preset medium -c:a copy \\
934
935 flipped_vertical.mp4
936
937 ```
938
939 **`transpose` values:** 0 = 90° counter-clockwise + vertical flip, 1 = 90° clockwise, 2 = 90° counter-clockwise, 3 = 90° clockwise + vertical flip.
940
941 ---
942
943 ## Split Screen
944
945 Put two or more videos side by side or in a grid.
946
947 ### Two videos side by side (horizontal)
948
949 ```bash
950
951 ffmpeg -y -i left.mp4 -i right.mp4 \\
952
953 -filter_complex "\\
954
955 [0:v]scale=640:360[left];\\
956
957 [1:v]scale=640:360[right];\\
958
959 [left][right]hstack=inputs=2[outv]" \\
960
961 -map "[outv]" -map 0:a? \\
962
963 -c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k \\
964
965 split_screen.mp4
966
967 ```
968
969 ### Two videos stacked (vertical)
970
971 ```bash
972
973 ffmpeg -y -i top.mp4 -i bottom.mp4 \\
974
975 -filter_complex "\\
976
977 [0:v]scale=1080:540[top];\\
978
979 [1:v]scale=1080:540[bottom];\\
980
981 [top][bottom]vstack=inputs=2[outv]" \\
982
983 -map "[outv]" -map 0:a? \\
984
985 -c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k \\
986
987 stacked.mp4
988
989 ```
990
991 ### 2x2 grid (four videos)
992
993 ```bash
994
995 ffmpeg -y -i v1.mp4 -i v2.mp4 -i v3.mp4 -i v4.mp4 \\
996
997 -filter_complex "\\
998
999 [0:v]scale=540:360[a];[1:v]scale=540:360[b];\\
1000
1001 [2:v]scale=540:360[c];[3:v]scale=540:360[d];\\
1002
1003 [a][b]hstack=inputs=2[top];\\
1004
1005 [c][d]hstack=inputs=2[bottom];\\
1006
1007 [top][bottom]vstack=inputs=2[outv]" \\
1008
1009 -map "[outv]" -map 0:a? \\
1010
1011 -c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k \\
1012
1013 grid_2x2.mp4
1014
1015 ```
1016
1017 ### Picture-in-picture (2)
1018
1019 ```bash
1020
1021 # Small video in bottom-right corner (25% size)
1022
1023 ffmpeg -y -i main.mp4 -i pip.mp4 \\
1024
1025 -filter_complex "\\
1026
1027 [1:v]scale=iw/4:ih/4[pip];\\
1028
1029 [0:v][pip]overlay=W-w-20:H-h-20[outv]" \\
1030
1031 -map "[outv]" -map 0:a \\
1032
1033 -c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k \\
1034
1035 pip_output.mp4
1036
1037 # PiP with rounded corners (border radius)
1038
1039 ffmpeg -y -i main.mp4 -i pip.mp4 \\
1040
1041 -filter_complex "\\
1042
1043 [1:v]scale=320:180,format=yuva420p,\\
1044
1045 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];\\
1046
1047 [0:v][pip]overlay=W-w-20:H-h-20[outv]" \\
1048
1049 -map "[outv]" -map 0:a \\
1050
1051 -c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k \\
1052
1053 pip_rounded.mp4
1054
1055 ```
1056
1057 ---
1058
1059 ## Muting and Audio Removal
1060
1061 ```bash
1062
1063 # Remove audio track entirely
1064
1065 ffmpeg -y -i input.mp4 -an \\
1066
1067 -c:v copy \\
1068
1069 output_muted.mp4
1070
1071 # Replace audio with silence (keeps audio stream but silent)
1072
1073 ffmpeg -y -i input.mp4 \\
1074
1075 -f lavfi -i anullsrc=channel_layout=stereo:sample_rate=44100 \\
1076
1077 -c:v copy -c:a aac -shortest \\
1078
1079 output_silent.mp4
1080
1081 # Keep only audio (extract as MP3)
1082
1083 ffmpeg -y -i input.mp4 \\
1084
1085 -vn -c:a libmp3lame -q:a 2 \\
1086
1087 audio_only.mp3
1088
1089 # Keep only audio (extract as AAC/M4A)
1090
1091 ffmpeg -y -i input.mp4 \\
1092
1093 -vn -c:a copy \\
1094
1095 audio_only.m4a
1096
1097 ```
1098
1099 ---
1100
1101 ## Thumbnail Generation
1102
1103 Extract a single frame as an image file.
1104
1105 ```bash
1106
1107 # Frame at a specific timestamp
1108
1109 ffmpeg -y -ss 00:00:05 -i input.mp4 \\
1110
1111 -frames:v 1 -q:v 2 \\
1112
1113 thumbnail.jpg
1114
1115 # Frame at 25% through the video
1116
1117 # (calculate timestamp from duration first with ffprobe)
1118
1119 ffmpeg -y -ss 30 -i input.mp4 \\
1120
1121 -frames:v 1 -q:v 2 \\
1122
1123 thumbnail_mid.jpg
1124
1125 # Multiple thumbnails at regular intervals (e.g., every 10 seconds)
1126
1127 ffmpeg -y -i input.mp4 \\
1128
1129 -vf "fps=1/10" -q:v 2 \\
1130
1131 thumbnails_%03d.jpg
1132
1133 # Best quality PNG thumbnail
1134
1135 ffmpeg -y -ss 00:00:05 -i input.mp4 \\
1136
1137 -frames:v 1 \\
1138
1139 thumbnail.png
1140
1141 ```
1142
1143 ### Node.js: Smart thumbnail (pick the most interesting frame)
1144
1145 ```typescript
1146
1147 async function generateThumbnail(
1148
1149 videoPath: string,
1150
1151 outputPath: string,
1152
1153 timestamp?: number
1154
1155 ): Promise<string> {
1156
1157 if (timestamp === undefined) {
1158
1159 // Default to 25% through the video — usually more interesting than the first frame
1160
1161 const { stdout } = await execFileAsync('ffprobe', [
1162
1163 '-v', 'quiet', '-print_format', 'json', '-show_format', videoPath
1164
1165 ]);
1166
1167 const duration = parseFloat(JSON.parse(stdout).format.duration);
1168
1169 timestamp = duration * 0.25;
1170
1171 }
1172
1173 await execFileAsync('ffmpeg', [
1174
1175 '-y', '-ss', timestamp.toFixed(3), '-i', videoPath,
1176
1177 '-frames:v', '1', '-q:v', '2',
1178
1179 outputPath
1180
1181 ]);
1182
1183 return outputPath;
1184
1185 }
1186
1187 ```
1188
1189 ---
1190
1191 ## Watermark Removal (Crop)
1192
1193 Remove a watermark by cropping it out. This is the only reliable FFmpeg approach — there is no "inpainting" filter.
1194
1195 ```bash
1196
1197 # Crop out a watermark in the bottom-right corner
1198
1199 # Removes 60px from the bottom and 120px from the right
1200
1201 ffmpeg -y -i input.mp4 \\
1202
1203 -vf "crop=iw-120:ih-60:0:0" \\
1204
1205 -c:v libx264 -crf 23 -preset medium -c:a copy \\
1206
1207 output_no_watermark.mp4
1208
1209 # Crop out a watermark at the top-left
1210
1211 # Removes 80px from the top and 200px from the left
1212
1213 ffmpeg -y -i input.mp4 \\
1214
1215 -vf "crop=iw-200:ih-80:200:80" \\
1216
1217 -c:v libx264 -crf 23 -preset medium -c:a copy \\
1218
1219 output_no_watermark.mp4
1220
1221 # Crop + scale back to original resolution (fills the gap)
1222
1223 ffmpeg -y -i input.mp4 \\
1224
1225 -vf "crop=iw-120:ih-60:0:0,scale=1920:1080" \\
1226
1227 -c:v libx264 -crf 23 -preset medium -c:a copy \\
1228
1229 output_cleaned.mp4
1230
1231 ```
1232
1233 **Crop filter syntax:** `crop=out_w:out_h:x:y` — output width, output height, x offset from left, y offset from top.
1234
1235 **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.
1236
1237 ---
1238
1239 ## Logo Overlay (Branding)
1240
1241 Add a persistent logo/brand image to a video.
1242
1243 ```bash
1244
1245 # Logo in the top-left corner with padding
1246
1247 ffmpeg -y -i video.mp4 -i logo.png \\
1248
1249 -filter_complex "overlay=20:20" \\
1250
1251 -c:v libx264 -crf 23 -preset medium -c:a copy \\
1252
1253 branded.mp4
1254
1255 # Logo in the bottom-right corner
1256
1257 ffmpeg -y -i video.mp4 -i logo.png \\
1258
1259 -filter_complex "overlay=W-w-20:H-h-20" \\
1260
1261 -c:v libx264 -crf 23 -preset medium -c:a copy \\
1262
1263 branded.mp4
1264
1265 # Logo with transparency (50% opacity)
1266
1267 ffmpeg -y -i video.mp4 -i logo.png \\
1268
1269 -filter_complex "[1:v]format=rgba,colorchannelmixer=aa=0.5[logo];[0:v][logo]overlay=W-w-20:20" \\
1270
1271 -c:v libx264 -crf 23 -preset medium -c:a copy \\
1272
1273 branded_subtle.mp4
1274
1275 # Logo scaled to a specific size (e.g., 100px wide, keep aspect ratio)
1276
1277 ffmpeg -y -i video.mp4 -i logo.png \\
1278
1279 -filter_complex "[1:v]scale=100:-1[logo];[0:v][logo]overlay=W-w-20:20" \\
1280
1281 -c:v libx264 -crf 23 -preset medium -c:a copy \\
1282
1283 branded_small.mp4
1284
1285 # Logo that appears only during the first 5 seconds
1286
1287 ffmpeg -y -i video.mp4 -i logo.png \\
1288
1289 -filter_complex "[1:v]scale=100:-1[logo];[0:v][logo]overlay=20:20:enable='between(t,0,5)'" \\
1290
1291 -c:v libx264 -crf 23 -preset medium -c:a copy \\
1292
1293 branded_intro.mp4
1294
1295 ```
1296
1297 ### Overlay position shortcuts
1298
1299 - Top-left: `overlay=20:20`
1300 - Top-right: `overlay=W-w-20:20`
1301
1302 - Bottom-left: `overlay=20:H-h-20`
1303 - Bottom-right: `overlay=W-w-20:H-h-20`
1304
1305 - Center: `overlay=(W-w)/2:(H-h)/2`
1306
1307 ---
1308
1309 ## Social Media Reframing
1310
1311 Reframe videos for different platforms with the correct aspect ratio and resolution.
1312
1313 ### Platform Reference
1314
1315 | Platform | Aspect Ratio | Resolution | Duration | Codec |
1316
1317 |----------|-------------|------------|----------|-------|
1318
1319 | TikTok | 9:16 | 1080×1920 | 10–45s | H.264 |
1320
1321 | Instagram Reels | 9:16 | 1080×1920 | 10–30s | H.264 |
1322
1323 | Instagram Stories | 9:16 | 1080×1920 | 1–60s | H.264 |
1324
1325 | Instagram Feed | 1:1 or 4:5 | 1080×1080 or 1080×1350 | 3–60s | H.264 |
1326
1327 | YouTube Shorts | 9:16 | 1080×1920 | 15–60s | H.264 |
1328
1329 | YouTube (standard) | 16:9 | 1920×1080 | any | H.264 |
1330
1331 | X / Twitter | 16:9 | 1280×720 | 10–45s | H.264 |
1332
1333 | X / Twitter (square) | 1:1 | 720×720 | 10–45s | H.264 |
1334
1335 | Facebook Reels | 9:16 | 1080×1920 | 10–30s | H.264 |
1336
1337 | Facebook Feed | 16:9 or 1:1 | 1920×1080 or 1080×1080 | any | H.264 |
1338
1339 | LinkedIn | 16:9 or 1:1 | 1920×1080 or 1080×1080 | 10–60s | H.264 |
1340
1341 | Pinterest | 9:16 or 2:3 | 1080×1920 or 1000×1500 | 6–60s | H.264 |
1342
1343 | Snapchat Spotlight | 9:16 | 1080×1920 | 5–60s | H.264 |
1344
1345 ### Strategy selection by conversion type
1346
1347 - **Landscape → Vertical (9:16):** Use blurred fill (Strategy 3) — looks polished on TikTok/Reels/Shorts
1348 - **Landscape → Square (1:1):** Use blurred fill or center crop depending on framing
1349
1350 - **Vertical → Landscape (16:9):** Use blurred fill to avoid large black bars
1351 - **Same orientation:** Use center crop or scale directly
1352
1353 - **4:5 portrait (Instagram Feed):** Use letterbox or blurred fill from 16:9
1354
1355 ### Strategy 1: Center Crop
1356
1357 Crops from the center to fill the target ratio. Best when the subject is center-framed.
1358
1359 ```bash
1360
1361 # 16:9 landscape → 9:16 vertical (TikTok/Reels) — center crop
1362
1363 ffmpeg -y -i input.mp4 \\
1364
1365 -vf "crop=ih*9/16:ih,scale=1080:1920" \\
1366
1367 -c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k \\
1368
1369 output_tiktok.mp4
1370
1371 # 16:9 landscape → 1:1 square (Instagram) — center crop
1372
1373 ffmpeg -y -i input.mp4 \\
1374
1375 -vf "crop=ih:ih,scale=1080:1080" \\
1376
1377 -c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k \\
1378
1379 output_instagram.mp4
1380
1381 # 9:16 vertical → 16:9 landscape — center crop
1382
1383 ffmpeg -y -i input.mp4 \\
1384
1385 -vf "crop=iw:iw*9/16,scale=1920:1080" \\
1386
1387 -c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k \\
1388
1389 output_youtube.mp4
1390
1391 # 9:16 vertical → 1:1 square — center crop
1392
1393 ffmpeg -y -i input.mp4 \\
1394
1395 -vf "crop=iw:iw,scale=1080:1080" \\
1396
1397 -c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k \\
1398
1399 output_square.mp4
1400
1401 ```
1402
1403 ### Strategy 2: Letterbox / Pillarbox (black bars)
1404
1405 Fits the entire video inside the target frame with black bars filling the gaps. No content is lost.
1406
1407 ```bash
1408
1409 # 16:9 → 9:16 with black bars (pillarbox)
1410
1411 ffmpeg -y -i input.mp4 \\
1412
1413 -vf "scale=1080:-2,pad=1080:1920:(ow-iw)/2:(oh-ih)/2:black" \\
1414
1415 -c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k \\
1416
1417 output_tiktok_letterbox.mp4
1418
1419 # 16:9 → 1:1 with black bars
1420
1421 ffmpeg -y -i input.mp4 \\
1422
1423 -vf "scale=1080:-2,pad=1080:1080:(ow-iw)/2:(oh-ih)/2:black" \\
1424
1425 -c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k \\
1426
1427 output_square_letterbox.mp4
1428
1429 # 9:16 → 16:9 with black bars (pillarbox)
1430
1431 ffmpeg -y -i input.mp4 \\
1432
1433 -vf "scale=-2:1080,pad=1920:1080:(ow-iw)/2:(oh-ih)/2:black" \\
1434
1435 -c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k \\
1436
1437 output_youtube_letterbox.mp4
1438
1439 ```
1440
1441 ### Strategy 3: Blurred Fill (recommended for landscape → portrait)
1442
1443 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.
1444
1445 ```bash
1446
1447 # 16:9 → 9:16 with blurred background fill (best for TikTok/Reels)
1448
1449 ffmpeg -y -i input.mp4 \\
1450
1451 -filter_complex "\\
1452
1453 [0:v]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,gblur=sigma=50[bg];\\
1454
1455 [0:v]scale=1080:1920:force_original_aspect_ratio=decrease[fg];\\
1456
1457 [bg][fg]overlay=(W-w)/2:(H-h)/2[outv]" \\
1458
1459 -map "[outv]" -map 0:a? \\
1460
1461 -c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k \\
1462
1463 output_tiktok_blur.mp4
1464
1465 # 16:9 → 1:1 with blurred background fill
1466
1467 ffmpeg -y -i input.mp4 \\
1468
1469 -filter_complex "\\
1470
1471 [0:v]scale=1080:1080:force_original_aspect_ratio=increase,crop=1080:1080,gblur=sigma=50[bg];\\
1472
1473 [0:v]scale=1080:1080:force_original_aspect_ratio=decrease[fg];\\
1474
1475 [bg][fg]overlay=(W-w)/2:(H-h)/2[outv]" \\
1476
1477 -map "[outv]" -map 0:a? \\
1478
1479 -c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k \\
1480
1481 output_square_blur.mp4
1482
1483 # 9:16 → 16:9 with blurred background fill
1484
1485 ffmpeg -y -i input.mp4 \\
1486
1487 -filter_complex "\\
1488
1489 [0:v]scale=1920:1080:force_original_aspect_ratio=increase,crop=1920:1080,gblur=sigma=50[bg];\\
1490
1491 [0:v]scale=1920:1080:force_original_aspect_ratio=decrease[fg];\\
1492
1493 [bg][fg]overlay=(W-w)/2:(H-h)/2[outv]" \\
1494
1495 -map "[outv]" -map 0:a? \\
1496
1497 -c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k \\
1498
1499 output_youtube_blur.mp4
1500
1501 # 16:9 → 4:5 portrait with blurred fill (Instagram Feed — preferred over 1:1)
1502
1503 ffmpeg -y -i input.mp4 \\
1504
1505 -filter_complex "\\
1506
1507 [0:v]scale=1080:1350:force_original_aspect_ratio=increase,crop=1080:1350,gblur=sigma=50[bg];\\
1508
1509 [0:v]scale=1080:1350:force_original_aspect_ratio=decrease[fg];\\
1510
1511 [bg][fg]overlay=(W-w)/2:(H-h)/2[outv]" \\
1512
1513 -map "[outv]" -map 0:a? \\
1514
1515 -c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k \\
1516
1517 output_instagram_feed_4x5.mp4
1518
1519 # 16:9 → 2:3 portrait with blurred fill (Pinterest)
1520
1521 ffmpeg -y -i input.mp4 \\
1522
1523 -filter_complex "\\
1524
1525 [0:v]scale=1000:1500:force_original_aspect_ratio=increase,crop=1000:1500,gblur=sigma=50[bg];\\
1526
1527 [0:v]scale=1000:1500:force_original_aspect_ratio=decrease[fg];\\
1528
1529 [bg][fg]overlay=(W-w)/2:(H-h)/2[outv]" \\
1530
1531 -map "[outv]" -map 0:a? \\
1532
1533 -c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k \\
1534
1535 output_pinterest_blur.mp4
1536
1537 ```
1538
1539 ### Multi-platform Export (Node.js)
1540
1541 Export to all platforms in one go:
1542
1543 ```typescript
1544
1545 import { execFile } from 'child_process';
1546
1547 import { promisify } from 'util';
1548
1549 import * as path from 'path';
1550
1551 const execFileAsync = promisify(execFile);
1552
1553 interface PlatformSpec {
1554
1555 name: string;
1556
1557 width: number;
1558
1559 height: number;
1560
1561 suffix: string;
1562
1563 }
1564
1565 const PLATFORMS: Record<string, PlatformSpec> = {
1566
1567 tiktok: { name: 'TikTok', width: 1080, height: 1920, suffix: 'tiktok' },
1568
1569 instagram_reels: { name: 'Instagram Reels', width: 1080, height: 1920, suffix: 'ig_reels' },
1570
1571 reels: { name: 'Instagram Reels', width: 1080, height: 1920, suffix: 'ig_reels' },
1572
1573 instagram_stories: { name: 'Instagram Stories', width: 1080, height: 1920, suffix: 'ig_stories' },
1574
1575 instagram_feed: { name: 'Instagram Feed', width: 1080, height: 1080, suffix: 'ig_feed' },
1576
1577 instagram_feed_45: { name: 'Instagram Feed 4:5', width: 1080, height: 1350, suffix: 'ig_feed_4x5' },
1578
1579 youtube_shorts: { name: 'YouTube Shorts', width: 1080, height: 1920, suffix: 'yt_shorts' },
1580
1581 shorts: { name: 'YouTube Shorts', width: 1080, height: 1920, suffix: 'yt_shorts' },
1582
1583 youtube: { name: 'YouTube', width: 1920, height: 1080, suffix: 'youtube' },
1584
1585 twitter: { name: 'X / Twitter', width: 1280, height: 720, suffix: 'twitter' },
1586
1587 x: { name: 'X / Twitter', width: 1280, height: 720, suffix: 'twitter' },
1588
1589 twitter_sq: { name: 'X / Twitter Square', width: 720, height: 720, suffix: 'twitter_sq' },
1590
1591 facebook_reels: { name: 'Facebook Reels', width: 1080, height: 1920, suffix: 'fb_reels' },
1592
1593 facebook_feed: { name: 'Facebook Feed', width: 1920, height: 1080, suffix: 'fb_feed' },
1594
1595 linkedin: { name: 'LinkedIn', width: 1920, height: 1080, suffix: 'linkedin' },
1596
1597 pinterest: { name: 'Pinterest', width: 1080, height: 1920, suffix: 'pinterest' },
1598
1599 pinterest_2x3: { name: 'Pinterest 2:3', width: 1000, height: 1500, suffix: 'pinterest_2x3' },
1600
1601 snapchat: { name: 'Snapchat Spotlight', width: 1080, height: 1920, suffix: 'snapchat' },
1602
1603 };
1604
1605 async function reframeForPlatform(
1606
1607 inputPath: string,
1608
1609 platform: PlatformSpec,
1610
1611 outputDir: string,
1612
1613 strategy: 'crop' | 'letterbox' | 'blur' = 'blur'
1614
1615 ): Promise<string> {
1616
1617 const ext = path.extname(inputPath);
1618
1619 const base = path.basename(inputPath, ext);
1620
1621 const outputPath = path.join(outputDir, `${base}_${platform.suffix}${ext}`);
1622
1623 const { width, height } = platform;
1624
1625 let filterComplex: string;
1626
1627 switch (strategy) {
1628
1629 case 'crop':
1630
1631 filterComplex = `scale=${width}:${height}:force_original_aspect_ratio=increase,crop=${width}:${height}`;
1632
1633 break;
1634
1635 case 'letterbox':
1636
1637 filterComplex = `scale=${width}:${height}:force_original_aspect_ratio=decrease,pad=${width}:${height}:(ow-iw)/2:(oh-ih)/2:black`;
1638
1639 break;
1640
1641 case 'blur':
1642
1643 filterComplex = [
1644
1645 `[0:v]scale=${width}:${height}:force_original_aspect_ratio=increase,crop=${width}:${height},gblur=sigma=50[bg]`,
1646
1647 `[0:v]scale=${width}:${height}:force_original_aspect_ratio=decrease[fg]`,
1648
1649 `[bg][fg]overlay=(W-w)/2:(H-h)/2[outv]`
1650
1651 ].join(';');
1652
1653 break;
1654
1655 }
1656
1657 const args = ['-y', '-i', inputPath];
1658
1659 if (strategy === 'blur') {
1660
1661 args.push('-filter_complex', filterComplex, '-map', '[outv]', '-map', '0:a?');
1662
1663 } else {
1664
1665 args.push('-vf', filterComplex);
1666
1667 }
1668
1669 args.push(
1670
1671 '-c:v', 'libx264', '-crf', '23', '-preset', 'medium',
1672
1673 '-c:a', 'aac', '-b:a', '128k',
1674
1675 outputPath
1676
1677 );
1678
1679 await execFileAsync('ffmpeg', args);
1680
1681 return outputPath;
1682
1683 }
1684
1685 async function exportAllPlatforms(
1686
1687 inputPath: string,
1688
1689 outputDir: string,
1690
1691 platforms: string[] = ['youtube', 'tiktok', 'instagram', 'twitter'],
1692
1693 strategy: 'crop' | 'letterbox' | 'blur' = 'blur'
1694
1695 ) {
1696
1697 const results: Array<{ platform: string; path: string }> = [];
1698
1699 for (const key of platforms) {
1700
1701 const spec = PLATFORMS[key];
1702
1703 if (!spec) continue;
1704
1705 console.log(`Reframing for ${spec.name} (${spec.width}x${spec.height})...`);
1706
1707 const outPath = await reframeForPlatform(inputPath, spec, outputDir, strategy);
1708
1709 results.push({ platform: spec.name, path: outPath });
1710
1711 console.log(` → ${outPath}`);
1712
1713 }
1714
1715 return results;
1716
1717 }
1718
1719 ```