React and Vue automate video workflows like upload, transcoding, thumbnail extraction, and format conversion using minimal components integrated with APIs or wasm libraries, handling processing server-side or client-side without custom logic.
Prerequisites
- JavaScript, HTML/CSS Knowledge: File APIs, async handling, responsive forms.
- React/Vue Fundamentals: Components, props, state (useState/data()), lifecycle (useEffect/mounted()).
- Node.js/npm: LTS v18+, VS Code with ES7 React/JS snippets (React), Volar (Vue).
- FFmpeg.wasm: Install via npm for client-side video manipulation; local MP4/WebM for testing.
- Set up a React Project using this guide: Creating a React App
- Set up a Vue Project using this guide: Quick Start
Automate Basic Video Upload in React Components
Install @ffmpeg/ffmpeg @ffmpeg/util via npm install @ffmpeg/ffmpeg @ffmpeg/util. Create src/components/VideoUploader.jsx with useState for file and FFmpeg for pre-upload trim:
Example: React Component that Trims & Uploads Video Using FFmpeg in-browser
import React, { useState } from 'react';
import { FFmpeg } from '@ffmpeg/ffmpeg';
import { toBlobURL, fetchFile } from '@ffmpeg/util';
const VideoUploader = ({ uploadUrl }) => {
const [file, setFile] = useState(null);
const [processedUrl, setProcessedUrl] = useState('');
const loadFFmpeg = async () => {
const baseURL = 'https://unpkg.com/@ffmpeg/core@0.12.6/dist/umd';
const ff = new FFmpeg();
await ff.load({
coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, 'text/javascript'),
wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, 'application/wasm')
});
return ff;
};
const handleProcessAndUpload = async () => {
if (!file) {
console.error('No file selected');
return;
}
try {
const ffmpeg = await loadFFmpeg();
const inputExt = file.name.split('.').pop();
const inputName = `input.${inputExt}`;
await ffmpeg.writeFile(inputName, await fetchFile(file));
await ffmpeg.exec(['-i', inputName, '-t', '30', 'output.mp4']); // Trim to 30s
const data = await ffmpeg.readFile('output.mp4');
const processedBlob = new Blob([data.buffer], { type: 'video/mp4' });
setProcessedUrl(URL.createObjectURL(processedBlob));
const formData = new FormData();
formData.append('file', processedBlob, 'processed.mp4');
const res = await fetch(uploadUrl, { method: 'POST', body: formData });
if (!res.ok) throw new Error('Upload failed');
} catch (error) {
console.error('Processing or upload error:', error);
}
};
return (
<div>
<input type="file" accept="video/*" onChange={(e) => setFile(e.target.files[0])} />
<button onClick={handleProcessAndUpload}>Process & Upload</button>
{processedUrl && <video src={processedUrl} controls width="640" height="360" />}
</div>
);
};
export default VideoUploader;
Use in App.jsx: <VideoUploader uploadUrl="/api/upload" />. Assumes backend endpoint for upload.
Explanation:
- ffmpeg.writeFile(inputName, await fetchFile(file)): Loads the selected video file into FFmpeg's in-memory filesystem for processing.
- setProcessedUrl(URL.createObjectURL(processedBlob)): Creates a temporary URL for the processed video so it can be previewed in a <video> element.
- formData.append('file', processedBlob, 'processed.mp4'): Adds the processed video to a FormData object for uploading.
- fetch(uploadUrl, { method: 'POST', body: formData }): Sends the processed video to the server via a POST request.
- <button onClick={handleProcessAndUpload}>: Triggers the video processing and upload workflow when clicked.
Automate Basic Video Upload in Vue Components
Install @ffmpeg/ffmpeg @ffmpeg/util via npm install @ffmpeg/ffmpeg @ffmpeg/util. Create src/components/VideoUploader.vue:
Example: Vue Component for Trimming & Uploading Video Using FFmpeg in-browser
<template>
<div>
<input type="file" accept="video/*" @change="setFile" />
<button @click="handleProcessAndUpload">Process & Upload</button>
<video v-if="processedUrl" :src="processedUrl" controls width="640" height="360" />
</div>
</template>
<script>
import { FFmpeg } from '@ffmpeg/ffmpeg';
import { toBlobURL, fetchFile } from '@ffmpeg/util';
export default {
name: 'VideoUploader',
props: { uploadUrl: { type: String, required: true } },
data() {
return { file: null, processedUrl: '', ffmpeg: null };
},
methods: {
setFile(e) {
this.file = e.target.files[0];
},
async loadFFmpeg() {
const baseURL = 'https://unpkg.com/@ffmpeg/core@0.12.6/dist/umd';
this.ffmpeg = new FFmpeg();
await this.ffmpeg.load({
coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, 'text/javascript'),
wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, 'application/wasm')
});
},
async handleProcessAndUpload() {
await this.loadFFmpeg();
await this.ffmpeg.writeFile('input.mp4', await fetchFile(this.file));
await this.ffmpeg.exec(['-i', 'input.mp4', '-t', '30', 'output.mp4']);
const data = await this.ffmpeg.readFile('output.mp4');
const processedBlob = new Blob([data.buffer], { type: 'video/mp4' });
this.processedUrl = URL.createObjectURL(processedBlob);
const formData = new FormData();
formData.append('file', processedBlob, 'processed.mp4');
await fetch(this.uploadUrl, { method: 'POST', body: formData });
}
}
};
</script>Use in src/App.vue: <VideoUploader uploadUrl="/api/upload" />.
Explanation:
- <input type="file" accept="video/" @change="setFile">: A file input element that triggers setFile when a video file is selected.
- formData.append('file', processedBlob, 'processed.mp4'): Adds the processed video to a FormData object with the filename processed.mp4.
- fetch(this.uploadUrl, { method: 'POST', body: formData }): Sends the processed video to the provided upload endpoint via a POST request.
- <video v-if="processedUrl" :src="processedUrl" controls>: Displays the processed video only if a URL is available.
Integrate FFmpeg Transformations in React
Create src/components/TransformedVideo.jsx for auto-conversion and thumbnail extraction:
Example: React Component that Transcodes Video and Generates Thumbnail Using FFmpeg
import React, { useState } from 'react';
import { FFmpeg } from '@ffmpeg/ffmpeg';
import { toBlobURL, fetchFile } from '@ffmpeg/util';
const TransformedVideo = ({ file }) => {
const [outputUrl, setOutputUrl] = useState('');
const [thumbnailUrl, setThumbnailUrl] = useState('');
const processVideo = async () => {
const baseURL = 'https://unpkg.com/@ffmpeg/core@0.12.6/dist/umd';
const ffmpeg = new FFmpeg();
await ffmpeg.load({
coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, 'text/javascript'),
wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, 'application/wasm')
});
await ffmpeg.writeFile('input.mp4', await fetchFile(file));
await ffmpeg.exec(['-i', 'input.mp4', '-c:v', 'libx264', 'output.mp4']); // Transcode
const videoData = await ffmpeg.readFile('output.mp4');
setOutputUrl(URL.createObjectURL(new Blob([videoData.buffer], { type: 'video/mp4' })));
await ffmpeg.exec(['-i', 'input.mp4', '-ss', '00:00:01', '-vframes', '1', 'thumb.jpg']);
const thumbData = await ffmpeg.readFile('thumb.jpg');
setThumbnailUrl(URL.createObjectURL(new Blob([thumbData.buffer], { type: 'image/jpeg' })));
};
return (
<div>
<button onClick={processVideo}>Transform</button>
{outputUrl && <video src={outputUrl} controls width="640" height="360" />}
{thumbnailUrl && <img src={thumbnailUrl} alt="Thumbnail" width="320" />}
</div>
);
};
export default TransformedVideo;
Use in App.jsx: Pass file prop from input.
Explanation:
- const ffmpeg = new FFmpeg(): Creates a new instance of FFmpeg to run video processing commands in the browser.
- ffmpeg.writeFile('input.mp4', await fetchFile(file)): Loads the input video into FFmpeg’s virtual filesystem.
- ffmpeg.exec(['-i', 'input.mp4', '-c:v', 'libx264', 'output.mp4']): Transcodes the input video using the H.264 codec and saves it as output.mp4.
- setThumbnailUrl(URL.createObjectURL(new Blob([thumbData.buffer], { type: 'image/jpeg' }))): Creates an object URL for the thumbnail image to display it in the browser.
- <button onClick={processVideo}>Transform</button>: A button that triggers the video transformation and thumbnail extraction process.
- <video src={outputUrl} controls>: Displays the transcoded video for playback once available.
- <img src={thumbnailUrl} alt="Thumbnail" />: Displays the extracted thumbnail image once available.
Integrate FFmpeg Transformations in Vue
Create src/components/TransformedVideo.vue:
Example: Vue Component Transcoding Video and Generating Thumbnail Using FFmpeg
<template>
<div>
<button @click="processVideo">Transform</button>
<video v-if="outputUrl" :src="outputUrl" controls width="640" height="360" />
<img v-if="thumbnailUrl" :src="thumbnailUrl" alt="Thumbnail" width="320" />
</div>
</template>
<script>
import { FFmpeg } from '@ffmpeg/ffmpeg';
import { toBlobURL, fetchFile } from '@ffmpeg/util';
export default {
name: 'TransformedVideo',
props: { file: File },
data() {
return { outputUrl: '', thumbnailUrl: '' };
},
methods: {
async processVideo() {
const baseURL = 'https://unpkg.com/@ffmpeg/core@0.12.6/dist/umd';
const ffmpeg = new FFmpeg();
await ffmpeg.load({
coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, 'text/javascript'),
wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, 'application/wasm')
});
await ffmpeg.writeFile('input.mp4', await fetchFile(this.file));
await ffmpeg.exec(['-i', 'input.mp4', '-c:v', 'libx264', 'output.mp4']);
const videoData = await ffmpeg.readFile('output.mp4');
this.outputUrl = URL.createObjectURL(new Blob([videoData.buffer], { type: 'video/mp4' }));
await ffmpeg.exec(['-i', 'input.mp4', '-ss', '00:00:01', '-vframes', '1', 'thumb.jpg']);
const thumbData = await ffmpeg.readFile('thumb.jpg');
this.thumbnailUrl = URL.createObjectURL(new Blob([thumbData.buffer], { type: 'image/jpeg' }));
}
}
};
</script>
export default TransformedVideo;Use in App.vue: Pass file prop.
Explanation:
- <button @click="processVideo">Transform</button>: A button that triggers the video processing and thumbnail extraction when clicked.
- <video v-if="outputUrl" :src="outputUrl" controls>: Displays the transcoded video if available.
- <img v-if="thumbnailUrl" :src="thumbnailUrl">: Displays the thumbnail image if it has been generated.
- this.outputUrl = URL.createObjectURL(...): Creates a temporary object URL for the transcoded video so it can be played in the browser.
- ffmpeg.exec(['-i', 'input.mp4', '-ss', '00:00:01', '-vframes', '1', 'thumb.jpg']): Extracts a single frame at 1 second into the video to generate a thumbnail.
Handle Workflow Events and Progress
In React, extend VideoUploader.jsx with progress via FFmpeg callback and full integration:
Example: React Video Uploader with FFmpeg Processing, Progress, and Status Updates
import React, { useState } from 'react';
import { FFmpeg } from '@ffmpeg/ffmpeg';
import { toBlobURL, fetchFile } from '@ffmpeg/util';
const VideoUploader = ({ uploadUrl }) => {
const [file, setFile] = useState(null);
const [processedUrl, setProcessedUrl] = useState('');
const [progress, setProgress] = useState(0);
const [status, setStatus] = useState('');
const loadFFmpeg = async () => {
const baseURL = 'https://unpkg.com/@ffmpeg/core@0.12.6/dist/umd';
const ff = new FFmpeg();
await ff.load({
coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, 'text/javascript'),
wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, 'application/wasm')
});
return ff;
};
const handleProcessAndUpload = async () => {
if (!file) {
setStatus('No file selected');
return;
}
setStatus('Loading FFmpeg...');
setProgress(0);
try {
const ffmpeg = await loadFFmpeg();
const inputExt = file.name.split('.').pop();
const inputName = `input.${inputExt}`;
await ffmpeg.writeFile(inputName, await fetchFile(file));
setStatus('Processing video...');
await ffmpeg.exec(['-i', inputName, '-t', '30', 'output.mp4'], (prog) => {
setProgress(prog.ratio * 100);
setStatus(`Processing... ${Math.round(prog.ratio * 100)}%`);
});
const data = await ffmpeg.readFile('output.mp4');
const processedBlob = new Blob([data.buffer], { type: 'video/mp4' });
setProcessedUrl(URL.createObjectURL(processedBlob));
setStatus('Uploading...');
setProgress(100);
const formData = new FormData();
formData.append('file', processedBlob, 'processed.mp4');
const res = await fetch(uploadUrl, { method: 'POST', body: formData });
if (!res.ok) throw new Error('Upload failed');
setStatus('Uploaded successfully');
} catch (error) {
setStatus('Error occurred');
console.error('Processing or upload error:', error);
}
};
return (
<div>
<input type="file" accept="video/*" onChange={(e) => setFile(e.target.files[0])} />
<button onClick={handleProcessAndUpload} disabled={!file}>Process & Upload</button>
{progress > 0 && <progress value={progress} max="100" />}
<p>{status}</p>
{processedUrl && <video src={processedUrl} controls width="640" height="360" />}
</div>
);
};
export default VideoUploader;
Explanation:
- ffmpeg.exec([...], (prog) => {...}): Executes the FFmpeg command to trim the video to 30 seconds and provides progress updates through a callback.
- formData.append('file', processedBlob, 'processed.mp4'): Appends the processed video to a FormData object for HTTP upload.
- fetch(uploadUrl, { method: 'POST', body: formData }): Uploads the video to the specified server endpoint using a POST request.
- <button onClick={handleProcessAndUpload} disabled={!file}>: Initiates processing and uploading only if a file has been selected.
- <progress value={progress} max="100" />: Displays a progress bar to visually indicate how much of the processing is complete.
Use in App.jsx: <VideoUploader uploadUrl="/api/upload" />. In Vue, update VideoUploader.vue with progress via FFmpeg callback and full integration:
Example: Vue Video Uploader with FFmpeg Processing, Progress, and Status Feedback
<template>
<div>
<input type="file" accept="video/*" @change="setFile" />
<button @click="handleProcessAndUpload" :disabled="!file">Process & Upload</button>
<progress v-if="progress > 0" :value="progress" max="100" />
<p>{{ status }}</p>
<video v-if="processedUrl" :src="processedUrl" controls width="640" height="360" />
</div>
</template>
<script>
import { FFmpeg } from '@ffmpeg/ffmpeg';
import { toBlobURL, fetchFile } from '@ffmpeg/util';
export default {
name: 'VideoUploader',
props: { uploadUrl: { type: String, required: true } },
data() {
return {
file: null,
processedUrl: '',
progress: 0,
status: ''
};
},
methods: {
setFile(e) {
this.file = e.target.files[0];
},
async loadFFmpeg() {
const baseURL = 'https://unpkg.com/@ffmpeg/core@0.12.6/dist/umd';
const ffmpeg = new FFmpeg();
await ffmpeg.load({
coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, 'text/javascript'),
wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, 'application/wasm')
});
return ffmpeg;
},
async handleProcessAndUpload() {
if (!this.file) {
this.status = 'No file selected';
return;
}
this.status = 'Loading FFmpeg...';
this.progress = 0;
try {
const ffmpeg = await this.loadFFmpeg();
const inputExt = this.file.name.split('.').pop();
const inputName = `input.${inputExt}`;
await ffmpeg.writeFile(inputName, await fetchFile(this.file));
this.status = 'Processing video...';
await ffmpeg.exec(['-i', inputName, '-t', '30', 'output.mp4'], (prog) => {
this.progress = prog.ratio * 100;
this.status = `Processing... ${Math.round(prog.ratio * 100)}%`;
});
const data = await ffmpeg.readFile('output.mp4');
const processedBlob = new Blob([data.buffer], { type: 'video/mp4' });
this.processedUrl = URL.createObjectURL(processedBlob);
this.status = 'Uploading...';
this.progress = 100;
const formData = new FormData();
formData.append('file', processedBlob, 'processed.mp4');
const res = await fetch(this.uploadUrl, { method: 'POST', body: formData });
if (!res.ok) throw new Error('Upload failed');
this.status = 'Uploaded successfully';
} catch (error) {
this.status = 'Error occurred';
console.error('Processing or upload error:', error);
}
}
},
beforeUnmount() {
if (this.processedUrl) URL.revokeObjectURL(this.processedUrl);
}
};
</script>
Use in App.vue: <VideoUploader uploadUrl="/api/upload" />.
Explanation:
- ffmpeg.exec([...], (prog) => {...}): Runs the FFmpeg command to trim the video to 30 seconds and reports progress via a callback.
- this.progress = prog.ratio * 100: Updates the progress value based on FFmpeg’s processing progress.
- <button @click="handleProcessAndUpload" :disabled="!file">: Button to start processing and uploading; disabled until a file is selected.
- <progress :value="progress" max="100" />: Displays a progress bar reflecting the current processing progress.
- <video v-if="processedUrl" :src="processedUrl" controls width="640" height="360" />: Displays the processed video for preview once ready.
Automate Client-Side Processing in React
Create src/components/VideoProcessor.jsx for format conversion:
Example: React Component Converting Video to MP4 Using FFmpeg
import React, { useState } from 'react';
import { FFmpeg } from '@ffmpeg/ffmpeg';
import { toBlobURL, fetchFile } from '@ffmpeg/util';
const VideoProcessor = () => {
const [output, setOutput] = useState('');
const processVideo = async (file) => {
const baseURL = 'https://unpkg.com/@ffmpeg/core@0.12.6/dist/umd';
const ffmpeg = new FFmpeg();
await ffmpeg.load({
coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, 'text/javascript'),
wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, 'application/wasm')
});
await ffmpeg.writeFile('input.webm', await fetchFile(file));
await ffmpeg.exec(['-i', 'input.webm', '-c:v', 'libx264', 'output.mp4']); // Convert to MP4
const data = await ffmpeg.readFile('output.mp4');
const url = URL.createObjectURL(new Blob([data.buffer], { type: 'video/mp4' }));
setOutput(url);
};
return (
<div>
<input type="file" accept="video/*" onChange={(e) => processVideo(e.target.files[0])} />
{output && <video src={output} controls width="640" height="360" />}
</div>
);
};
export default VideoProcessor;
Use in App.jsx: <VideoProcessor />.
Explanation:
- ffmpeg.exec(['-i', 'input.webm', '-c:v', 'libx264', 'output.mp4']): Executes the FFmpeg command to convert the input video from WebM format to MP4 using H.264 codec.
- <video src={output} controls ...>: Video player element that plays the processed MP4 video once available.
Automate Client-Side Processing in Vue
Create src/components/VideoProcessor.vue:
Example: Vue Component Converting Video to MP4 Using FFmpeg
<template>
<div>
<input type="file" accept="video/*" @change="processVideo" />
<video v-if="output" :src="output" controls width="640" height="360" />
</div>
</template>
<script>
import { FFmpeg } from '@ffmpeg/ffmpeg';
import { toBlobURL, fetchFile } from '@ffmpeg/util';
export default {
data() {
return { output: '' };
},
methods: {
async processVideo(e) {
const baseURL = 'https://unpkg.com/@ffmpeg/core@0.12.6/dist/umd';
const ffmpeg = new FFmpeg();
await ffmpeg.load({
coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, 'text/javascript'),
wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, 'application/wasm')
});
await ffmpeg.writeFile('input.webm', await fetchFile(e.target.files[0]));
await ffmpeg.exec(['-i', 'input.webm', '-c:v', 'libx264', 'output.mp4']);
const data = await ffmpeg.readFile('output.mp4');
this.output = URL.createObjectURL(new Blob([data.buffer], { type: 'video/mp4' }));
}
}
};
</script>
Use in App.vue: <VideoProcessor />.
Explanation:
- ffmpeg.writeFile('input.webm', ...): Writes the uploaded video file into FFmpeg’s virtual filesystem as 'input.webm'.
- ffmpeg.exec(['-i', 'input.webm', '-c:v', 'libx264', 'output.mp4']): Runs the FFmpeg command to transcode the input WebM video to MP4 format using H.264 codec.
- <video v-if="output" :src="output" controls ...>: Video element that plays the processed video once available.
Optimize and Secure Workflows
Optimize Performance
Use Web Workers for FFmpeg to avoid UI blocking. In React (VideoProcessor.jsx):
Example: React Video Processing UI with FFmpeg Worker and Loading State
import React, { useState } from 'react';
// FFmpeg with worker config
const VideoProcessor = () => {
const [isProcessing, setIsProcessing] = useState(false);
const processVideo = async (file) => {
setIsProcessing(true);
// Load FFmpeg with worker: new FFmpeg({ corePath: '/ffmpeg-core.js' })
await ffmpeg.exec(['-i', 'input.webm', 'output.mp4']);
setIsProcessing(false);
};
return (
<div>
<input type="file" accept="video/*" onChange={(e) => processVideo(e.target.files[0])} disabled={isProcessing} />
{isProcessing && <p>Processing...</p>}
</div>
);
};Explanation:
- ffmpeg.exec(['-i', 'input.webm', 'output.mp4']): Command that runs FFmpeg to convert the input WebM video file to MP4 format.
- {isProcessing && <p>Processing...</p>}: Conditional UI message shown while the video is being processed.
- new FFmpeg({ corePath: '/ffmpeg-core.js' }): Indicates FFmpeg can be initialized with a specific core script path supporting worker threads for better performance.
In Vue (VideoProcessor.vue):
<template>
<div>
<input type="file" accept="video/*" @change="processVideo" :disabled="isProcessing" />
<p v-if="isProcessing">Processing...</p>
</div>
</template>
<script>
// FFmpeg with worker
export default {
data() {
return { isProcessing: false };
},
methods: {
async processVideo(e) {
this.isProcessing = true;
// Load with worker
await this.ffmpeg.exec(['-i', 'input.webm', 'output.mp4']);
this.isProcessing = false;
}
}
};
</script>Limit file sizes; use IndexedDB for caching processed files.
Explanation:
- <input type="file" accept="video/" @change="processVideo" :disabled="isProcessing" />: File input element disabled while processing to prevent multiple uploads.*
- this.ffmpeg.exec(['-i', 'input.webm', 'output.mp4']): Command that runs FFmpeg to convert the input WebM video to MP4 format, using a worker for better performance.
Secure Workflows
Validate Files Client-Side: Check MIME types, sizes before processing.
Use HTTPS for All Fetches: Ensure WASM loads over a secure protocol.
Sanitize Inputs: Avoid command injection in FFmpeg args (use whitelisted options).
Store Outputs Temporarily: Revoke URLs with URL.revokeObjectURL after use to free memory.
Protect Backend: Implement auth tokens for upload endpoints; validate server-side.
