Js uses ffmpeg to add png or gif to the video
ffmpeg
The usage scenario is to edit the video on the web and add pictures and gifs.
Note:
All the following use cases are based on vue3 setup.
At the same time, different @ffmpeg versions will lead to different APIs used. You need to pay attention to @ffmpeg version issues
before using the case.
If you are using 0.12+ you need to use the new API, please see the documentation for details
npm
npm install @ffmpeg/ffmpeg@^0.11.0 npm install @ffmpeg/core@^0.11.0
Video add png
<template></template> <script setup> import {<!-- --> ref, onUnmounted, onMounted } from 'vue' import {<!-- --> createFFmpeg, fetchFile } from '@ffmpeg/ffmpeg'; const ffmpeg = createFFmpeg({<!-- --> log: true }); const fileType = ref("") //Video file type /** * Combine pictures into videos * @param {string} url video online address * @param {object} picItem picture material object * @param {string} picItem.startT The start time when the picture material appears * @param {number} picItem.duration The appearance duration of the material * @param {number} picItem.scale The magnification ratio of the material * @param {string} picItem.url picture material url address * @param {number} picItem.x The distance between the material and the top of the video * @param {number} picItem.y The distance between the material and the left side of the video * @return {Promise<{outputName: string, fileUrl: string}> | undefined} */ const videoCompose = async (url, picItem) => {<!-- --> if (!ffmpeg.isLoaded()) {<!-- --> await ffmpeg.load(); } if (!url) return; const {<!-- --> duration, scale, startT, url: picUrl, x, y } = picItem; fileType.value = url.split(".").pop(); const inputName = `input.${<!-- -->fileType.value}`; const outputName = `output.${<!-- -->fileType.value}`; const imageType = picUrl.split(".").pop(); const imageFileName = `image.${<!-- -->imageType}`; await ffmpeg.FS('writeFile', inputName, await fetchFile(url)); await ffmpeg.FS('writeFile', imageFileName, await fetchFile(picUrl)); //Run FFmpeg command try {<!-- --> await ffmpeg.run( `-i`, `${<!-- -->inputName}`, `-i`, `${<!-- -->imageFileName}`, `-filter_complex`, `[1:v]scale=iw*${<!-- -->(scale).toFixed(1)}:ih*${<!-- -->(scale).toFixed (1)}[scaled];[0:v][scaled]overlay=${<!-- -->x}:${<!-- -->y}:enable='between(t, ${<!-- --> + startT},${<!-- --> + startT + duration})'`, `${<!-- -->outputName}`, "-hide_banner" ); //Read the output file let arrayBuffer = ffmpeg.FS('readFile', outputName).buffer; // Read cache //Create a download link and download and save it locally through callback const fileUrl = URL.createObjectURL(new Blob([arrayBuffer])); // Convert to Blob URL // release memory ffmpeg.FS('unlink', inputName); ffmpeg.FS('unlink', outputName); return {<!-- --> fileUrl, outputName }; } catch (e) {<!-- --> console.log(e); } } const downloadFile = (url, fileName = `clip.mp4`) => {<!-- --> const link = document.createElement('a'); link.href = url; link.download = fileName; link.click(); } onMounted(async () => {<!-- --> const {<!-- -->fileUrl} = await videoCompose("http://xxx.mp4", {<!-- --> duration: 3, scale: 1, startT: "0.00", url: 'http://xxx.png', x: 100, y: 100 }) downloadFile(fileUrl) }) onUnmounted(() => {<!-- --> ffmpeg.exit(); }) </script>
Add gif to video
The process is similar to adding a picture, but the commands for adding filters are different.
/* Perform partial replacement of FFmpeg command `-ignore_loop`, `0` makes the gif image play in a loop, otherwise it will only play once `-itsoffset`, `${ + startT}` The time when the gif image appears in the video fade=t=in:st=${ + startT}:d=1:alpha=1[wm]; The time when the gif picture fades in the video :shortest=1 The length of the video is the initial video length. Otherwise, the video time will increase due to the addition of gif. :enable='between(t,${ + startT},${ + startT + duration})' The appearance time of gif "-hide_banner" hides some information of ffmpeg */ await ffmpeg.run( `-i`, `${<!-- -->inputName}`, `-ignore_loop`, `0`, `-itsoffset`, `${<!-- --> + startT}`, `-i`, `${<!-- -->imageFileName}`, `-filter_complex`, `[0:0]scale=iw:ih[a];[1:0]scale=iw*${<!-- -->(scale).toFixed(1)}:ih* ${<!-- -->(scale).toFixed(1)},fade=t=in:st=${<!-- --> + startT}:d=1:alpha=1[wm] ;[a][wm]overlay=x=${<!-- -->x}:y=${<!-- -->y}:shortest=1:enable='between(t,$ {<!-- --> + startT},${<!-- --> + startT + duration})'`, `${<!-- -->outputName}`, "-hide_banner" );
Integration
You can judge the type of image when adding and execute different adding logic.
/** * Combine pictures into videos * @param {string} url video online address * @param {object} picItem picture material object * @param {string} picItem.startT The start time when the picture material appears * @param {number} picItem.duration The appearance duration of the material * @param {number} picItem.scale The magnification ratio of the material * @param {string} picItem.url picture material url address * @param {number} picItem.x The distance between the material and the top of the video * @param {number} picItem.y The distance between the material and the left side of the video * @return {Promise<{outputName: string, fileUrl: string}> | undefined} */ const videoCompose = async (url, picItem) => {<!-- --> if (!ffmpeg.isLoaded()) {<!-- --> await ffmpeg.load(); } if (!url) return; const {<!-- -->duration, scale, startT, url: picUrl, x, y} = picItem; const type = url.split(".").pop(); const inputName = `input.${<!-- -->type}`; const outputName = `output.${<!-- -->type}`; const imageType = picUrl.split(".").pop(); const imageFileName = `image.${<!-- -->imageType}`; // Save the input file to the virtual file system if (url.startsWith('blob:')) {<!-- --> // Handle Blob URL const arrayBuffer = await fetchBlobAsArrayBuffer(url); ffmpeg.FS('writeFile', inputName, new Uint8Array(arrayBuffer)); } else if (url.startsWith('http://') || url.startsWith('https://')) {<!-- --> // handle network address await ffmpeg.FS('writeFile', inputName, await fetchFile(url)); } await ffmpeg.FS('writeFile', imageFileName, await fetchFile(picUrl)); //Run FFmpeg command try {<!-- --> if (imageType === 'gif') {<!-- --> await ffmpeg.run( `-i`, `${<!-- -->inputName}`, `-ignore_loop`, `0`, `-itsoffset`, `${<!-- --> + startT}`, `-i`, `${<!-- -->imageFileName}`, `-filter_complex`, `[0:0]scale=iw:ih[a];[1:0]scale=iw*${<!-- -->(scale).toFixed(1)}:ih* ${<!-- -->(scale).toFixed(1)},fade=t=in:st=${<!-- --> + startT}:d=1:alpha=1[wm] ;[a][wm]overlay=x=${<!-- -->x}:y=${<!-- -->y}:shortest=1:enable='between(t,$ {<!-- --> + startT},${<!-- --> + startT + duration})'`, `${<!-- -->outputName}`, "-hide_banner" ); } else {<!-- --> await ffmpeg.run( `-i`, `${<!-- -->inputName}`, `-i`, `${<!-- -->imageFileName}`, `-filter_complex`, `[1:v]scale=iw*${<!-- -->(scale).toFixed(1)}:ih*${<!-- -->(scale).toFixed (1)}[scaled];[0:v][scaled]overlay=${<!-- -->x}:${<!-- -->y}:enable='between(t, ${<!-- --> + startT},${<!-- --> + startT + duration})'`, `${<!-- -->outputName}`, "-hide_banner" ); } //Read the output file let arrayBuffer = ffmpeg.FS('readFile', outputName).buffer; // Read cache //Create a download link and download and save it locally through callback const fileUrl = URL.createObjectURL(new Blob([arrayBuffer])); // Convert to Blob URL // release memory ffmpeg.FS('unlink', inputName); ffmpeg.FS('unlink', outputName); return {<!-- --> fileUrl, outputName }; } catch (e) {<!-- --> console.log(e); } }