Processing Media
Quick Start
import ffmpeg from 'fluent-ffmpeg';
import { path as ffmpegPath } from '@ffmpeg-installer/ffmpeg';
ffmpeg.setFfmpegPath(ffmpegPath);
// Transcode video to web-optimized MP4
async function transcodeVideo(inputPath: string, outputPath: string): Promise<void> {
return new Promise((resolve, reject) => {
ffmpeg(inputPath)
.videoCodec('libx264')
.audioCodec('aac')
.size('1280x720')
.outputOptions(['-preset medium', '-movflags +faststart'])
.on('end', resolve)
.on('error', reject)
.save(outputPath);
});
}
// Generate thumbnail at specific timestamp
async function generateThumbnail(videoPath: string, outputPath: string, timestamp: number): Promise<void> {
return new Promise((resolve, reject) => {
ffmpeg(videoPath)
.screenshots({ timestamps: [timestamp], filename: 'thumb.jpg', folder: outputPath, size: '320x180' })
.on('end', resolve)
.on('error', reject);
});
}
// Extract audio from video
async function extractAudio(videoPath: string, audioPath: string): Promise<void> {
return new Promise((resolve, reject) => {
ffmpeg(videoPath)
.audioCodec('libmp3lame')
.audioBitrate('192k')
.on('end', resolve)
.on('error', reject)
.save(audioPath);
});
}
Features
| Feature | Description | Guide | |---------|-------------|-------| | Video Transcoding | Convert between formats (MP4, WebM, MOV) | Use libx264/libx265 for H.264/H.265 encoding | | Resolution Scaling | Resize videos to target resolutions | Use size() with aspect ratio preservation | | Thumbnail Generation | Create preview images from videos | Use screenshots() with timestamps | | Audio Extraction | Extract audio tracks from video files | Use audioCodec() without video output | | Audio Processing | Convert, normalize, and merge audio | Use audioFilters() for normalization | | HLS Streaming | Generate adaptive bitrate streams | Create multi-quality variants with m3u8 playlists | | DASH Streaming | MPEG-DASH manifest generation | Use -f dash with segment configuration | | Batch Processing | Process multiple files concurrently | Use p-queue for controlled parallelism | | Progress Tracking | Monitor transcoding progress | Listen to 'progress' events | | Metadata Extraction | Read video/audio metadata | Use ffprobe for duration, resolution, codec info |
Common Patterns
HLS Adaptive Streaming
const QUALITIES = [
{ name: '1080p', resolution: '1920x1080', bitrate: '5000k' },
{ name: '720p', resolution: '1280x720', bitrate: '2500k' },
{ name: '480p', resolution: '854x480', bitrate: '1000k' },
];
async function generateHLS(inputPath: string, outputDir: string): Promise<string> {
for (const quality of QUALITIES) {
const qualityDir = path.join(outputDir, quality.name);
await fs.mkdir(qualityDir, { recursive: true });
await new Promise<void>((resolve, reject) => {
ffmpeg(inputPath)
.videoCodec('libx264')
.size(quality.resolution)
.videoBitrate(quality.bitrate)
.outputOptions(['-hls_time 6', '-hls_playlist_type vod', '-f hls'])
.on('end', resolve)
.on('error', reject)
.save(path.join(qualityDir, 'playlist.m3u8'));
});
}
// Generate master playlist
const master = '#EXTM3U\n' + QUALITIES.map(q =>
`#EXT-X-STREAM-INF:BANDWIDTH=${parseInt(q.bitrate) * 1000}\n${q.name}/playlist.m3u8`
).join('\n');
await fs.writeFile(path.join(outputDir, 'master.m3u8'), master);
return path.join(outputDir, 'master.m3u8');
}
Video Upload Processing Pipeline
async function processVideoUpload(inputPath: string, videoId: string): Promise<VideoAssets> {
const outputDir = path.join(MEDIA_DIR, videoId);
await fs.mkdir(outputDir, { recursive: true });
// Get metadata
const metadata = await getVideoMetadata(inputPath);
// Generate thumbnails
const thumbnails = await generateThumbnails(inputPath, path.join(outputDir, 'thumbs'), 10);
// Transcode to web format
await transcodeVideo(inputPath, path.join(outputDir, 'video.mp4'));
// Generate HLS for streaming
const streamUrl = await generateHLS(inputPath, path.join(outputDir, 'hls'));
return { metadata, thumbnails, videoUrl: `/media/${videoId}/video.mp4`, streamUrl };
}
Audio Normalization and Merge
async function normalizeAndMerge(tracks: string[], outputPath: string): Promise<void> {
return new Promise((resolve, reject) => {
let command = ffmpeg();
tracks.forEach(track => { command = command.input(track); });
const filterInputs = tracks.map((_, i) => `[${i}:a]`).join('');
command
.complexFilter(`${filterInputs}concat=n=${tracks.length}:v=0:a=1,loudnorm=I=-16:TP=-1.5[out]`)
.outputOptions(['-map', '[out]'])
.on('end', resolve)
.on('error', reject)
.save(outputPath);
});
}
Batch Processing with Progress
import PQueue from 'p-queue';
async function batchTranscode(
files: string[],
outputDir: string,
onProgress?: (completed: number, total: number) => void
): Promise<BatchResult[]> {
const queue = new PQueue({ concurrency: 2 });
const results: BatchResult[] = [];
let completed = 0;
for (const file of files) {
queue.add(async () => {
const outputPath = path.join(outputDir, `${path.basename(file, path.extname(file))}.mp4`);
try {
await transcodeVideo(file, outputPath);
results.push({ file, success: true, outputPath });
} catch (error) {
results.push({ file, success: false, error: error.message });
}
completed++;
onProgress?.(completed, files.length);
});
}
await queue.onIdle();
return results;
}
Best Practices
| Do | Avoid | |----|-------| | Enable hardware acceleration (NVENC/VAAPI) when available | Using software encoding on capable hardware | | Implement progress tracking for long operations | Running transcodes without user feedback | | Use streaming for large file processing | Loading entire videos into memory | | Set reasonable timeouts for processing | Allowing indefinite process hangs | | Validate input formats before processing | Processing arbitrary untrusted files | | Clean up temporary files after processing | Leaving temp files on disk | | Use -movflags +faststart for web videos | Serving videos without fast-start optimization | | Limit concurrent processing based on resources | Running unlimited parallel transcodes | | Handle ffmpeg exit codes properly | Ignoring process errors | | Set explicit output formats | Relying on auto-detection |
Related Skills
- image-processing - Image manipulation with Sharp
- document-processing - Office document handling
Scan to join WeChat group