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

code
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

code
<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

code
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

code
<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

code
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

code
<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

code
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

code
<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

code
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):

code
<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.