SaigonTech Vault
Loại: knowledge-doc 2026-09-29 active #wiki-import

Edit Video using FFmpeg

Yêu cầu

bài toán:
- merge nhiều ảnh tĩnh thành 1 video
- merge 1 video A vào 1 video B
- merge 1 file mp3 tạo thành background music vào 1 video A
- chỉnh âm lượng của background music từ nhỏ tới lớn dần
- vẽ 1 dòng text lên 1 video
- hiển thị dòng text trên video với hiệu ứng: chạy từ trái qua phải, chạy zoom out, zoom in

sử dụng:
https://github.com/kkroening/ffmpeg-python
https://github.com/binoculars/aws-lambda-ffmpeg
https://github.com/cmxl/FFmpeg.NET
https://github.com/hokiedsp/node-kiss-ffmpeg
https://github.com/fluent-ffmpeg/node-fluent-ffmpeg

Giải pháp

  • Mỗi yêu cầu trả lời những câu hỏi sau
    • Có làm được không?
    • Làm như thế nào?
    • Có hạn chế gì không?
    • Giải pháp nào khác cho những hạn chế ở trên?
  • Sau khi trả lời những câu hỏi trên, chốt stack sẽ sử dụng?
  • Document lại những gì đã tìm hiểu
  • Triển demo xác nhận lại những thứ ở trên

Demo

  • Thiết lập môi trường
  • Ví dụ đơn giản cho từng case ở trên
  • Ví dụ phức tạp, mix nhiều case (mix cố định không cần tùy chỉnh)

Demo

Package : https://www.npmjs.com/package/fluent-ffmpeg - Thiết lập môi trường (nodejs) - Ví dụ đơn giản cho từng case ở trên - Ví dụ phức tạp, mix nhiều case (mix cố định không cần tùy chỉnh)

Thực hiện - Chú ý : Resize image hoặc video theo cùng 1 kích sỡ trước + image const outputOptions = { filter: "scale", options: { w: width, h: height, force_original_aspect_ratio: "decrease" }, }; + video Ffmpeg(videoPath) .outputFormat("mp4") .videoCodec("libx264") .audioCodec("aac") .size(`${width}x${height}`) .fps(30) .save(outputVideoPath) + video có backgroud là transparent: `` .outputOptions([-vf scale=${width}:${height}`, "-c:v prores_ks", "-pix_fmt yuva444p10le", ])

  ```
  • merge nhiều ảnh tĩnh thành 1 video

    • Để toàn bộ ảnh trong 1 folder (lưu ý tên phải đồng bộ vidu: image_1.jpg)
    • Convert tên file => image_%d.jpg
    • Option cơ bản để tạo 1 video fps: 25, inputFPS: 1 / 5, transition: true, transitionDuration: 1, videoCodec: "libx264", outputOptions: ["-pix_fmt yuv420p"], complexFilter:["scale=1280:720"] or
    • Chuyển image thành video => merge các video đó lại

    const video = Ffmpeg({ source: imagePath }); video .inputOptions("-loop 1") .outputOptions("-c:v libx264") .outputOptions("-t 6") .outputOptions("-pix_fmt yuv420p") .videoFilters("scale=720:720") .save(outputVideoPath)

  • merge 1 video A vào 1 video B (có thể n video thành 1 video)

    • option cơ bản -y -filter_complex [0:v:0][0:a:0][1:v:0][1:a:0]concat=n=2:v=1:a=1[v][a] -acodec aac -vcodec libx264 -map [v] map [a] With fluent-ffmpeg `` const filterComplex = [ { filter: "concat", options: { n: videoPaths.length, v: 1, a: 1, }, inputs: videoPaths.flatMap((_, index) => [${index}:v:0,${index}:a:0`]), outputs: ["v", "a"], }, ];

    mergedVideo .complexFilter(filterComplex) .outputOptions(["-map", "[v]", "-map", "[a]"]) .videoCodec("libx264") .audioCodec("aac") or const mergedVideo = Ffmpeg(); videoPaths.forEach((path) => mergedVideo.input(path));

    mergedVideo .outputFormat("mp4") .mergeToFile(outputVideoPath) - merge 1 file mp3 tạo thành background music vào 1 video A - chỉnh âm lượng của background music từ nhỏ tới lớn dần - Option cơ bản `volumeFactor:volume (min:0 -> max:1)` .audioFilters(volume=${volumeFactor}) .outputOptions("-c:v", "copy", "-c:a", "aac", "-strict", "experimental") ```

  • vẽ 1 dòng text lên 1 video

    • Option cơ bản (vị trí , màu , kích cỡ , font ,...) default: x=0, y=0 Position: -top-left : "x=${x}:y=${x}" -top-center : "x=(w-text_w)/2:y=${y}" -top-right : "x=w-tw-${x}:y=${y}" -center : "x=(w-text_w)/2:y=(h-text_h)/2" -bottom-left : "x=${x}:y=h-th-${y}" -bottom-right : "x=(w-text_w)/2:y=h-th-${y}" -bottom-center : "x=w-tw-${x}:y=h-th-${y}" .outputOptions([ "-vf", `drawtext=text='${text}':${positionText}:fontsize=${fontSize}:fontcolor=${color}`, ]) or .videoFilters({ filter: 'drawtext', options: { fontfile: '/path/to/your/font.ttf', text: text, fontcolor: 'white', fontsize: '24', x: '(w-text_w)/2', y: '(h-text_h)/2', } })
    • Multiple lines break Trường hợp cho nhập nhiều dòng Chỉ định nhiều 'drawtext' ffmpeg -I file_source_path.extension -vf "drawtext=fontfile=path/to/font/:fontsize=30:fontcolor=white:x=(w-text_w)/2:y=(h-text_h-text_h)/2:text='custom-text',drawtext=fontfile=/path/to/font:fontsize=30:fontcolor=white:x= (w-text_w)/2:y=(h+text_h)/2:text='custom-text'" file_destination_path.extension
  • hiển thị dòng text trên video với hiệu ứng: chạy từ trái qua phải, chạy zoom out, zoom in

    • option tương tự vẽ 1 dòng text
      • chạy từ trái qua phải : x='(mod(${speed}*n,w+tw)-tw)':y=h/2 với speed là tốc độ của hiệu ứng
      • chạy zoom out, zoom in,
        • thay đổi fontsize text: fontsize: '24+2*t', t là thơi gian
        • tao ảnh với text ở trung tâm và background là transparent -> affect giống như option của ảnh trên video
  • sử lý ảnh

  • blur: ffmpeg -ss 0 -i Image-25-overlayImage.png -y -filter_complex [0:v]avgblur=10[bg];[0:v]crop=370:450:x:y[fg];[bg][fg]overlay=(main_w-overlay_w)/2:(main_h-overlay_h)/2 -acodec copy -vframes 25 -movflags faststart Image-29-blur.png

  • mix hiệu ứng

  • fade in , out : [ '[1]format=yuva420p,fade=in:st=8:d=1:alpha=1[in],[in]fade=out:st=13:d=1:alpha=1[fadeOut]', "[0:v][fadeOut]overlay=x='if(lte(t,8), (W-overlay_w)/2 + 20, max((W-overlay_w)/2 + 20 - (t-9) * 20, 0 - overlay_w))':y='(main_h-overlay_h)/2':enable='between(t,8,14)':shortest = 1" ]

  • fade in, out + text :

[ '[1]format=yuva420p,fade=in:st=28:d=1:alpha=1[in],[in]fade=out:st=33:d=1:alpha=1[fadeOut]', "[0:v][fadeOut]overlay=x='(main_w-overlay_w)/2':y='(main_h-overlay_h)/2':shortest=1[overlay]", "[overlay]drawtext=fontfile=D:\\01_SGT_WORK\\FFMPEG\\Ffmpeg\\font\\mincho.otf:text='text in bottom center':fontsize=30:fontcolor=white:x=(w-text_w)/2:y=h-th-20:enable='between(t, 29, 33 )'" ] +

  • khó khăn đang gặp:
    • Khi mix các Animation để tạo video, bị conflict với các option trước đó
    • Giải pháp hiện tại , chia nhỏ các animation ra rồi gộp lại, nhưng vẫn chưa ok , phải test nhiều chỉnh option phù hợp
    • Chưa tìm ra cách giải quyết cho các hiệu ứng smooth , với đúng yêu cầu đề bài