- name
- ffmpeg-webcli-browser-video-editor
- description
- A browser-based video editor powered by ffmpeg.wasm for client-side video processing without server uploads
- triggers
- ["how do I use ffmpeg webCLI to process videos in the browser","set up ffmpeg.wasm video editor in my web app","convert videos to GIF using ffmpeg webCLI","process videos client-side with WebAssembly","integrate browser-based video editing with ffmpeg","use ffmpeg webCLI API for video operations","implement offline video processing in the browser","create video editor with ffmpeg.wasm"]
# ffmpeg webCLI Browser Video Editor
> Skill by [ara.so](https://ara.so) — Devtools Skills collection.
ffmpeg webCLI is a browser-based video editor powered by ffmpeg.wasm that processes videos entirely client-side using WebAssembly. No server uploads, no backend infrastructure — all video processing happens locally in the user's browser. It supports 30+ video operations including GIF creation, format conversion, compression, trimming, filters, effects, and more.
## Installation
### Option 1: Use the Live App
Access the hosted version directly:
```
https://tejaswigowda.com/ffmpeg-webCLI/
```
### Option 2: Clone and Run Locally
```bash
git clone https://github.com/tejaswigowda/ffmpeg-webCLI.git
cd ffmpeg-webCLI
# Serve the files with any static server
python -m http.server 8000
# or
npx serve .
```
### Option 3: Integrate into Your Project
The project uses ffmpeg.wasm as its core dependency. To integrate similar functionality:
```html
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/@ffmpeg/ffmpeg@0.12.7/dist/umd/ffmpeg.js"></script>
</head>
<body>
<input type="file" id="videoInput" accept="video/*">
<button id="processBtn">Process Video</button>
<video id="output" controls></video>
<script src="app.js"></script>
</body>
</html>
```
## Core Architecture
ffmpeg webCLI uses:
- **ffmpeg.wasm** — WebAssembly port of ffmpeg for browser execution
- **Web Workers** — Background processing to keep UI responsive
- **PWA** — Progressive Web App for offline support
- **Wake Lock API** — Prevents screen sleep during processing
## Key Components
### 1. Initialize ffmpeg.wasm
```javascript
const { FFmpeg } = FFmpegWASM;
const { fetchFile } = FFmpegWASM;
let ffmpeg = null;
let loaded = false;
async function loadFFmpeg() {
if (loaded) return;
ffmpeg = new FFmpeg();
// Log ffmpeg output
ffmpeg.on('log', ({ message }) => {
console.log(message);
});
// Track progress
ffmpeg.on('progress', ({ progress, time }) => {
console.log(`Progress: ${Math.round(progress * 100)}%`);
updateProgressBar(progress);
});
// Load the core and wasm files
await ffmpeg.load({
coreURL: 'https://unpkg.com/@ffmpeg/core@0.12.4/dist/umd/ffmpeg-core.js',
wasmURL: 'https://unpkg.com/@ffmpeg/core@0.12.4/dist/umd/ffmpeg-core.wasm',
});
loaded = true;
console.log('FFmpeg loaded successfully');
}
```
### 2. Load Video File into Virtual Filesystem
```javascript
async function loadVideoFile(file) {
await loadFFmpeg();
// Write file to ffmpeg's virtual filesystem
await ffmpeg.writeFile('input.mp4', await fetchFile(file));
console.log('Video loaded into virtual filesystem');
}
```
### 3. Convert Video to GIF
```javascript
async function convertToGIF(inputFile, width = 480, fps = 10) {
await loadVideoFile(inputFile);
// Two-pass palette generation for best quality
// Pass 1: Generate palette
await ffmpeg.exec([
'-i', 'input.mp4',
'-vf', `fps=${fps},scale=${width}:-1:flags=lanczos,palettegen`,
'palette.png'
]);
// Pass 2: Use palette to create GIF
await ffmpeg.exec([
'-i', 'input.mp4',
'-i', 'palette.png',
'-filter_complex', `fps=${fps},scale=${width}:-1:flags=lanczos[x];[x][1:v]paletteuse`,
'output.gif'
]);
// Read the output file
const data = await ffmpeg.readFile('output.gif');
return new Blob([data.buffer], { type: 'image/gif' });
}
```
### 4. Convert Video Format
```javascript
async function convertFormat(inputFile, outputFormat = 'mp4') {
await loadVideoFile(inputFile);
const outputFile = `output.${outputFormat}`;
const formatConfigs = {
mp4: ['-c:v', 'libx264', '-c:a', 'aac'],
webm: ['-c:v', 'libvpx-vp9', '-c:a', 'libopus'],
mkv: ['-c:v', 'libx264', '-c:a', 'aac'],
mov: ['-c:v', 'libx264', '-c:a', 'aac'],
avi: ['-c:v', 'libx264', '-c:a', 'aac']
};
await ffmpeg.exec([
'-i', 'input.mp4',
...formatConfigs[outputFormat],
outputFile
]);
const data = await ffmpeg.readFile(outputFile);
return new Blob([data.buffer], { type: `video/${outputFormat}` });
}
```
### 5. Compress Video with CRF
```javascript
async function compressVideo(inputFile, crf = 23, preset = 'medium') {
await loadVideoFile(inputFile);
// CRF: 18 (near lossless) to 51 (maximum compression)
// Preset: ultrafast, superfast, veryfast, faster, fast, medium, slow, slower, veryslow
await ffmpeg.exec([
'-i', 'input.mp4',
'-c:v', 'libx264',
'-crf', crf.toString(),
'-preset', preset,
'-c:a', 'aac',
'output.mp4'
]);
const data = await ffmpeg.readFile('output.mp4');
return new Blob([data.buffer], { type: 'video/mp4' });
}
```
### 6. Trim Video
```javascript
async function trimVideo(inputFile, startTime, endTime) {
await loadVideoFile(inputFile);
const duration = endTime - startTime;
await ffmpeg.exec([
'-ss', startTime.toString(),
'-i', 'input.mp4',
'-t', duration.toString(),
'-c', 'copy', // Stream copy for fast, lossless trim
'output.mp4'
]);
const data = await ffmpeg.readFile('output.mp4');
return new Blob([data.buffer], { type: 'video/mp4' });
}
```
### 7. Extract Audio
```javascript
async function extractAudio(inputFile, format = 'mp3') {
await loadVideoFile(inputFile);
const outputFile = `output.${format}`;
const audioConfigs = {
mp3: ['-c:a', 'libmp3lame', '-q:a', '2'],
aac: ['-c:a', 'aac', '-b:a', '192k'],
wav: ['-c:a', 'pcm_s16le'],
ogg: ['-c:a', 'libvorbis', '-q:a', '5'],
flac: ['-c:a', 'flac']
};
await ffmpeg.exec([
'-i', 'input.mp4',
'-vn', // No video
...audioConfigs[format],
outputFile
]);
const data = await ffmpeg.readFile(outputFile);
return new Blob([data.buffer], { type: `audio/${format}` });
}
```
### 8. Resize Video
```javascript
async function resizeVideo(inputFile, width, height = -1) {
await loadVideoFile(inputFile);
// height = -1 maintains aspect ratio
await ffmpeg.exec([
'-i', 'input.mp4',
'-vf', `scale=${width}:${height}`,
'-c:v', 'libx264',
'-crf', '23',
'-c:a', 'copy',
'output.mp4'
]);
const data = await ffmpeg.readFile('output.mp4');
return new Blob([data.buffer], { type: 'video/mp4' });
}
```
### 9. Change Video Speed
```javascript
async function changeSpeed(inputFile, speedMultiplier) {
await loadVideoFile(inputFile);
const videoPTS = 1 / speedMultiplier;
// Build atempo filter chain (each atempo can only do 0.5-2.0x)
let atempoChain = '';
let remaining = speedMultiplier;
while (remaining > 2.0) {
atempoChain += 'atempo=2.0,';
remaining /= 2.0;
}
while (remaining < 0.5) {
atempoChain += 'atempo=0.5,';
remaining /= 0.5;
}
atempoChain += `atempo=${remaining.toFixed(3)}`;
await ffmpeg.exec([
'-i', 'input.mp4',
'-filter:v', `setpts=${videoPTS}*PTS`,
'-filter:a', atempoChain,
'-c:v', 'libx264',
'-crf', '23',
'output.mp4'
]);
const data = await ffmpeg.readFile('output.mp4');
return new Blob([data.buffer], { type: 'video/mp4' });
}
```
### 10. Rotate Video
```javascript
async function rotateVideo(inputFile, rotation) {
await loadVideoFile(inputFile);
const rotations = {
'90cw': 'transpose=1',
'90ccw': 'transpose=2',
'180': 'transpose=1,transpose=1',
'hflip': 'hflip',
'vflip': 'vflip',
'both': 'hflip,vflip'
};
await ffmpeg.exec([
'-i', 'input.mp4',
'-vf', rotations[rotation],
'-c:a', 'copy',
'output.mp4'
]);
const data = await ffmpeg.readFile('output.mp4');
return new Blob([data.buffer], { type: 'video/mp4' });
}
```
### 11. Add Watermark/Logo
```javascript
async function addWatermark(inputFile, logoFile, position = 'bottom-right', widthPercent = 15) {
await loadVideoFile(inputFile);
await ffmpeg.writeFile('logo.png', await fetchFile(logoFile));
const positions = {
'top-left': '10:10',
'top-right': 'W-w-10:10',
'bottom-left': '10:H-h-10',
'bottom-right': 'W-w-10:H-h-10',
'center': '(W-w)/2:(H-h)/2'
};
const scaleFilter = `[1:v]scale=iw*${widthPercent/100}:-1[logo]`;
const overlayFilter = `[0:v][logo]overlay=${positions[position]}`;
await ffmpeg.exec([
'-i', 'input.mp4',
'-i', 'logo.png',
'-filter_complex', `${scaleFilter};${overlayFilter}`,
'-c:a', 'copy',
'output.mp4'
]);
const data = await ffmpeg.readFile('output.mp4');
return new Blob([data.buffer], { type: 'video/mp4' });
}
```
### 12. Adjust Brightness/Contrast/Saturation
```javascript
async function adjustColors(inputFile, brightness = 0, contrast = 1, saturation = 1, grayscale = false) {
await loadVideoFile(inputFile);
const sat = grayscale ? 0 : saturation;
await ffmpeg.exec([
'-i', 'input.mp4',
GitHubで見る