
Convert Video to GIF with FFmpeg
TL;DR: FFmpeg is a powerful multimedia processing tool, and thanks to WebAssembly, you can now run it entirely in the browser with no server-side processing needed. This post shows how to integrate
@ffmpeg/ffmpeg(the WASM build) into a Vue.js app to convert video files to GIFs client-side. It covers importing the library, loading the WASM script, running native FFmpeg commands in the browser, and handling the file input/output flow.
This post describes how to use Ffmpeg directly in browser, and use native commands. Ffmpeg loads web assembly script in browser, and gives APIs that we can consume.
What is Ffmpeg ?
FFmpeg is a free and open-source software project consisting of a large suite of libraries and programs for handling video, audio, and other multimedia files and streams.
"A complete, cross-platform solution to record, convert and stream audio and video." — ffmpeg.org
How do I add ffmpeg.wasm to a Vue app?
# Use npm
npm install @ffmpeg/ffmpeg
# Use yarn
yarn add @ffmpeg/ffmpeg
or you can use CDN directly, read more on the ffmpeg.wasm site.
Version note: this post uses the 0.11 API (createFFmpeg, ffmpeg.run, ffmpeg.FS). Version 0.12 replaced it with new FFmpeg(), ffmpeg.exec() and ffmpeg.writeFile(). To follow this post as written, install @ffmpeg/[email protected].
How do I convert a video to GIF in the browser?
- Import ffmpeg as below
const { createFFmpeg, fetchFile } = FFmpeg
const ffmpeg = createFFmpeg({ log: true })
- Load Load ffmpeg.wasm-core script in browser environment
await ffmpeg.load()
- Use following file command to do file operations in browser, all data is bound to browser and will be lost on page refresh
// ffmpeg.FS(method, ...args)
// Write file using below command
ffmpeg.FS('writeFile', 'test.mp4', await fetchFile(this.video))
// Read file (already available in FS memory)
ffmpeg.FS('readFile', 'out.gif')
- Run ffmpeg command, as ffmpeg native cli.
await ffmpeg.run(
'-i',
'test.mp4',
'-t',
'5',
'-ss',
'5',
'-f',
'gif',
'out.gif'
)
// -t ==> total time of gif
// -ss ⇒ starting seconds or offset
Read more on ffmpeg commands in the FFmpeg documentation.
How do I do the same with ffmpeg.wasm 0.12?
The code below follows the 0.12 API from the ffmpeg.wasm documentation. I originally tested the 0.11 code above, so treat this newer example as a starting point and check it against the docs for your version.
import { FFmpeg } from '@ffmpeg/ffmpeg'
import { fetchFile, toBlobURL } from '@ffmpeg/util'
const ffmpeg = new FFmpeg()
const baseURL = 'https://unpkg.com/@ffmpeg/[email protected]/dist/umd'
await ffmpeg.load({
coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, 'text/javascript'),
wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, 'application/wasm'),
})
await ffmpeg.writeFile('test.mp4', await fetchFile(videoFile))
await ffmpeg.exec(['-ss', '5', '-t', '5', '-i', 'test.mp4', '-vf', 'fps=10,scale=480:-1:flags=lanczos', 'out.gif'])
const data = await ffmpeg.readFile('out.gif')
const gifUrl = URL.createObjectURL(new Blob([data.buffer], { type: 'image/gif' }))
fps=10,scale=480:-1 makes the GIF smaller: 10 frames per second and 480 pixels wide, with the height kept in proportion. The default single-thread 0.12 core doesn't need SharedArrayBuffer; only the multi-thread core does.
What does the finished app look like?

Conclusion
I found ffmpeg-wasm was really helpful, I have explored just one use case, that's like exploring tip of iceberg. I will try out more use cases and keep updating in my GitHub repo.
Frequently Asked Questions
Is my video uploaded to a server?
No. ffmpeg.wasm runs inside the browser tab. The file is written to an in-memory file system and never leaves the device.
Why do I get a SharedArrayBuffer error?
ffmpeg.wasm 0.11 needs SharedArrayBuffer, which browsers only allow on cross-origin isolated pages. Serve your app with Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp.
What do the -t and -ss options do?
-ss 5 starts reading the video at 5 seconds, and -t 5 keeps 5 seconds of output. Together they make a 5-second GIF from seconds 5 to 10.