I am having a problem with ffmpeg wasm. I am trying to build a quick local compression website using ffmpeg wasm to compress audio, video and images using canvas api.
the flow of the issue is when i select a file that is .mp3, and select the compression/conversion codec to be opus which should produce .ogg file in the end. it ends up producing a 0kb file, which means something has failed silently. i have been searching for quite sometime now, and i dont know what the issue could be. I am not the well versed with ffmpeg
code flow
useFfmpeg hook, initializes and ffmeg instance when needed aka when a compression job that needs ffmpeg is called, then i have a hook called useCompression which is the main orchestrator for the other compression hooks, calls compress audio that is exposed from the useAudioCompression, it gets the compression options from a zustand store that i created, then calls a utility i created and by all means not the best one but it does the job called argsBuilder, it takes all the selected options, and then returns an array that has all the args in order then uses it on the ffmpeg instance
the useAudioCompression hook
"use client"
import { useCallback } from 'react';
import { AudioCompressionOptions } from '@/types';
import { formatFileSizeMB, calculateCompressionStats } from '@/lib/utils/compression-helpers';
import { buildAudioArgs, generateFFmpegFileNames } from '@/lib/utils/ffmpeg-args-builder';
import { logFFmpegDebugInfo, parseFFmpegError } from '@/lib/utils/ffmpeg-error-logger';
import { useFFmpeg } from './useFFmpeg';
import { getThreadConfigInfo } from './threadUtils';
import { useCompressionStore } from '@/store/compression-store';
// Codec to format mapping
const codecToFormat: Record<string, string> = {
'libmp3lame': 'mp3',
'mp3': 'mp3',
'aac': 'aac',
'libvorbis': 'ogg',
'libopus': 'ogg',
'opus': 'ogg',
'pcm_s16le': 'wav',
'flac': 'flac',
};
// Format to MIME type mapping
const mimeMap: Record<string, string> = {
mp3: 'audio/mp3',
aac: 'audio/aac',
ogg: 'audio/ogg',
wav: 'audio/wav',
flac: 'audio/flac',
m4a: 'audio/mp4',
};
export const useAudioCompression = () => {
const {
initializeFFmpeg,
getFFmpegUtils,
clearError,
setCompressionProgress
} = useFFmpeg();
const { audioOptions } = useCompressionStore();
const compressAudio = useCallback(async (
file: File,
): Promise<Blob> => {
clearError();
setCompressionProgress(0);
try {
console.log(' STARTING AUDIO COMPRESSION PROCESS');
console.log(' Input file details:', {
name: file.name,
size: `${formatFileSizeMB(file.size)} MB`,
type: file.type,
lastModified: new Date(file.lastModified).toISOString()
});
const ffmpegInstance = await initializeFFmpeg();
const { fetchFile: fetchFileUtil } = await getFFmpegUtils();
// Determine output format from codec if not explicitly set
let outputFormat = audioOptions.outputFormat;
if (!outputFormat && audioOptions.acodec) {
outputFormat = codecToFormat[audioOptions.acodec] || 'mp3';
}
outputFormat = outputFormat || 'mp3';
console.log(' Format determination:', {
originalFormat: audioOptions.outputFormat,
detectedFromCodec: audioOptions.acodec ? codecToFormat[audioOptions.acodec] : null,
finalFormat: outputFormat,
codecToFormatMap: codecToFormat
});
const { inputFileName, outputFileName } = generateFFmpegFileNames(file.name, outputFormat);
console.log(' Generated file names:', { inputFileName, outputFileName });
// Build compression arguments with the computed output format
const finalOptions = { ...audioOptions, outputFormat };
const args = buildAudioArgs(finalOptions, inputFileName, outputFileName);
// Log detailed debug information
logFFmpegDebugInfo(args, finalOptions, file.name);
console.log('🔧 Final FFmpeg arguments:', args);
console.log('⚙️ Thread configuration:', getThreadConfigInfo());
// For debugging: check codec support if using opus/vorbis
if (audioOptions.acodec === 'opus' || audioOptions.acodec === 'vorbis') {
console.log(` Codec Analysis - Attempting ${audioOptions.acodec} compression`);
console.log(' Critical codec checks:', {
codecNormalization: audioOptions.acodec === 'opus' ? 'opus → libopus' : 'vorbis → libvorbis',
targetContainer: outputFormat,
isValidCombination: (audioOptions.acodec === 'opus' && outputFormat === 'ogg') ||
(audioOptions.acodec === 'vorbis' && outputFormat === 'ogg'),
expectedFileExtension: `.${outputFormat}`,
mimeType: mimeMap[outputFormat]
});
}
console.log(' Writing input file to FFmpeg virtual filesystem...');
// Write input file to FFmpeg's virtual file system
await ffmpegInstance.writeFile(inputFileName, await fetchFileUtil(file));
console.log(' Input file written successfully');
// Reset progress to 0 before starting
setCompressionProgress(0);
console.log(' Executing FFmpeg compression...');
await ffmpegInstance.exec(args);
console.log(' FFmpeg execution completed');
// Check if the output file exists and has content
console.log(' Reading compressed output file...');
const compressedData = await ffmpegInstance.readFile(outputFileName);
console.log(` Output file analysis:`, {
outputFileName,
rawDataSize: compressedData.length,
sizeInBytes: `${compressedData.length} bytes`,
sizeInKB: `${(compressedData.length / 1024).toFixed(2)} KB`,
sizeInMB: `${(compressedData.length / (1024 * 1024)).toFixed(4)} MB`,
isEmpty: compressedData.length === 0,
dataType: typeof compressedData,
isArrayBuffer: compressedData instanceof ArrayBuffer,
isUint8Array: compressedData instanceof Uint8Array
});
if (compressedData.length === 0) {
console.error(' CRITICAL ERROR: FFmpeg produced empty output file!');
console.log(' Debugging information:');
console.log(' - Input file size:', file.size, 'bytes');
console.log(' - Codec used:', audioOptions.acodec);
console.log(' - Output format:', outputFormat);
console.log(' - Arguments passed:', args);
throw new Error(`FFmpeg produced empty output file. This indicates a codec incompatibility or invalid arguments. Codec: ${audioOptions.acodec}, Format: ${outputFormat}`);
}
const mimeType = mimeMap[outputFormat] || 'audio/mp3';
console.log(' MIME type mapping:', {
outputFormat,
detectedMimeType: mimeType,
availableMimeTypes: mimeMap
});
const compressedBlob = new Blob([compressedData], { type: mimeType });
console.log(' Blob creation successful:', {
blobSize: compressedBlob.size,
blobType: compressedBlob.type,
compressionRatio: `${((1 - compressedBlob.size / file.size) * 100).toFixed(1)}%`
});
// Calculate compression stats
const stats = calculateCompressionStats(file.size, compressedBlob.size);
console.log(' Compression statistics:', stats);
console.log(` Audio compression SUCCESS:`, {
inputSize: formatFileSizeMB(file.size),
outputSize: formatFileSizeMB(compressedBlob.size),
compressionRatio: `${stats.compressionRatio.toFixed(1)}% reduction`,
format: outputFormat,
codec: audioOptions.acodec,
mimeType: mimeType,
processingTime: 'completed'
});
// Cleanup virtual files
console.log(' Cleaning up virtual files...');
await ffmpegInstance.deleteFile(inputFileName);
await ffmpegInstance.deleteFile(outputFileName);
console.log(' Cleanup completed');
console.log(` Final compression summary: ${formatFileSizeMB(file.size)} → ${formatFileSizeMB(compressedBlob.size)} (${stats.compressionRatio.toFixed(1)}% smaller)`);
// Set progress to 100% when complete
setCompressionProgress(100);
return compressedBlob;
} catch (err) {
// Parse FFmpeg error for better debugging
const ffmpegError = parseFFmpegError(err);
console.error('FFmpeg Error Details:', ffmpegError);
const errorMessage = `Audio compression failed: ${ffmpegError.message}`;
throw new Error(errorMessage);
} finally {
// Reset progress after completion
setTimeout(() => setCompressionProgress(null), 1000);
}
}, [initializeFFmpeg, audioOptions, getFFmpegUtils, clearError, setCompressionProgress]);
return {
compressAudio
};
};
the args builder
/**
* FFmpeg arguments builder with multithreading support
* Provides optimized command line arguments for different compression types
*/
import { AudioCompressionOptions, CompressionOptions } from '@/types';
import { getFFmpegThreadArgs } from '@/lib/threadUtils';
/**
* Generates unique file names for FFmpeg operations
*/
export const generateFFmpegFileNames = (originalName: string, outputFormat: string) => {
const timestamp = Date.now();
const extension = originalName.split('.').pop() || 'tmp';
return {
inputFileName: `input_${timestamp}.${extension}`,
outputFileName: `output_${timestamp}.${outputFormat}`
};
};
/**
* Gets appropriate MIME type for output format and media type
*/
export const getOutputMimeType = (outputFormat: string, mediaType: 'audio' | 'video' | 'image'): string => {
switch (mediaType) {
case 'audio':
switch (outputFormat) {
case 'mp3': return 'audio/mp3';
case 'ogg': return 'audio/ogg';
case 'wav': return 'audio/wav';
case 'aac': return 'audio/aac';
default: return 'audio/mp3';
}
case 'video':
switch (outputFormat) {
case 'webm': return 'video/webm';
case 'mkv': return 'video/x-matroska';
case 'avi': return 'video/x-msvideo';
default: return 'video/mp4';
}
case 'image':
switch (outputFormat) {
case 'jpeg': case 'jpg': return 'image/jpeg';
case 'png': return 'image/png';
case 'webp': return 'image/webp';
case 'avif': return 'image/avif';
default: return 'image/jpeg';
}
default:
return 'application/octet-stream';
}
};
/**
* Builds FFmpeg arguments for audio compression with multithreading
*/
export const buildAudioArgs = (
options:AudioCompressionOptions,
inputFileName: string,
outputFileName: string
): string[] => {
let args = ['-i', inputFileName];
// Add threading arguments for optimal performance
const threadArgs = getFFmpegThreadArgs('audio');
// args = args.concat(threadArgs);
// Use custom args if provided, otherwise build from options
if (options.customArgs && options.customArgs.length > 0) {
args = args.concat(options.customArgs);
args.push(outputFileName);
} else {
const outputFormat = options.outputFormat || 'mp3';
// Format specification first (especially important for ogg container)
if (outputFormat === 'ogg') {
args.push('-f', 'ogg');
}
// Audio codec - normalize codec names
let codec = options.acodec;
if (codec === 'opus') codec = 'libopus';
if (codec === 'vorbis') codec = 'libvorbis';
if (codec) {
args.push('-acodec', codec);
} else {
args.push('-acodec', outputFormat === 'mp3' ? 'libmp3lame' : 'aac');
}
// Sample rate
if (options.sampleRate) {
args.push('-ar', options.sampleRate);
} else {
args.push('-ar', '44100');
}
// Channels
if (options.channels !== undefined) {
args.push('-ac', options.channels.toString());
} else {
args.push('-ac', '2');
}
// Codec-specific quality/bitrate settings
if (codec === 'libopus') {
// For Opus, use simpler settings that are more compatible with FFmpeg.wasm
args.push('-b:a', options.bitrate || '128k');
// Remove advanced Opus settings that might not be supported
} else if (codec === 'libvorbis') {
// For Vorbis, use quality-based encoding
args.push('-q:a', '5'); // Quality 5 is good for Vorbis (~160kbps)
} else {
// For other codecs (AAC, MP3), use bitrate
if (options.bitrate) {
args.push('-b:a', options.bitrate);
} else {
args.push('-b:a', '128k');
}
}
args.push('-y');
args.push(outputFileName);
}
return args;
};
/**
* Builds FFmpeg arguments for video compression with multithreading
*/
export const buildVideoArgs = (
options: CompressionOptions,
inputFileName: string,
outputFileName: string
): string[] => {
let args = ['-i', inputFileName];
// Add threading arguments for optimal performance
const threadArgs = getFFmpegThreadArgs('video');
args = args.concat(threadArgs);
// Use custom args if provided, otherwise build from options
if (options.customArgs && options.customArgs.length > 0) {
args = args.concat(options.customArgs);
args.push(outputFileName);
} else {
// Video codec
if (options.vcodec) {
args.push('-vcodec', options.vcodec);
} else {
args.push('-vcodec', 'libx264');
}
// CRF (quality)
if (options.crf !== undefined) {
args.push('-crf', options.crf.toString());
} else {
args.push('-crf', '27');
}
// Preset (ultrafast for better threading performance)
if (options.preset) {
args.push('-preset', options.preset);
} else {
args.push('-preset', 'ultrafast');
}
// Scale/resolution
if (options.scale) {
args.push('-vf', options.scale);
} else if (options.maxWidth) {
args.push('-vf', `scale='min(${options.maxWidth},iw)':-2`);
} else {
args.push('-vf', `scale='min(720,iw)':-2`);
}
// Audio codec
if (options.acodec) {
args.push('-acodec', options.acodec);
} else {
args.push('-acodec', 'aac');
}
// Audio bitrate
if (options.bitrate) {
args.push('-b:a', options.bitrate);
} else {
args.push('-b:a', '48k');
}
// Optimization for web
args.push('-movflags', '+faststart');
args.push(outputFileName);
}
return args;
};
/**
* Builds FFmpeg arguments for image compression with multithreading
*/
export const buildImageArgs = (
options: CompressionOptions,
inputFileName: string,
outputFileName: string
): string[] => {
let args = ['-i', inputFileName];
// Add threading arguments for optimal performance
const threadArgs = getFFmpegThreadArgs('image');
args = args.concat(threadArgs);
// Use custom args if provided, otherwise build from options
if (options.customArgs && options.customArgs.length > 0) {
args = args.concat(options.customArgs);
args.push(outputFileName);
} else {
const outputFormat = options.outputFormat || 'jpeg';
// Quality setting
if (options.quality !== undefined) {
if (outputFormat === 'jpeg' || outputFormat === 'jpg') {
args.push('-q:v', Math.round(31 - (options.quality * 0.31)).toString());
} else if (outputFormat === 'webp') {
args.push('-quality', options.quality.toString());
}
} else {
if (outputFormat === 'jpeg' || outputFormat === 'jpg') {
args.push('-q:v', '15'); // Good quality/size balance
} else if (outputFormat === 'webp') {
args.push('-quality', '75');
}
}
// Resolution scaling
if (options.maxWidth) {
args.push('-vf', `scale='min(${options.maxWidth},iw)':-2`);
}
args.push(outputFileName);
}
return args;
};
the logs I'm getting
STARTING AUDIO COMPRESSION PROCESS
E:\Projects\quick-compression\lib\useAudioCompression.ts:52 Input file details: {name: 'file_example_MP3_5MG.mp3', size: '5.07 MB MB', type: 'audio/mpeg', lastModified: '2025-10-08T22:57:49.687Z'}
E:\Projects\quick-compression\lib\useAudioCompression.ts:69 🔄 Format determination: {originalFormat: undefined, detectedFromCodec: 'ogg', finalFormat: 'ogg', codecToFormatMap: {…}}
E:\Projects\quick-compression\lib\useAudioCompression.ts:78 Generated file names: {inputFileName: 'input_1760199936545.mp3', outputFileName: 'output_1760199936545.ogg'}
E:\Projects\quick-compression\lib\utils\ffmpeg-error-logger.ts:223 FFmpeg Comprehensive Debug Information
E:\Projects\quick-compression\lib\utils\ffmpeg-error-logger.ts:224 Input File: file_example_MP3_5MG.mp3
E:\Projects\quick-compression\lib\utils\ffmpeg-error-logger.ts:225 Raw Compression Options: {
"bitrate": "128k",
"sampleRate": "44100",
"channels": 2,
"acodec": "opus",
"outputFormat": "ogg"
}
E:\Projects\quick-compression\lib\utils\ffmpeg-error-logger.ts:226 Complete FFmpeg Arguments: (14) ['-i', 'input_1760199936545.mp3', '-f', 'ogg', '-acodec', 'libopus', '-ar', '44100', '-ac', '2', '-b:a', '128k', '-y', 'output_1760199936545.ogg']
E:\Projects\quick-compression\lib\utils\ffmpeg-error-logger.ts:227 Detected Output Format: ogg
E:\Projects\quick-compression\lib\utils\ffmpeg-error-logger.ts:228 Video Codec: not specified
E:\Projects\quick-compression\lib\utils\ffmpeg-error-logger.ts:229 Audio Codec: opus
E:\Projects\quick-compression\lib\utils\ffmpeg-error-logger.ts:230 Audio Bitrate: 128k
E:\Projects\quick-compression\lib\utils\ffmpeg-error-logger.ts:231 Sample Rate: 44100
E:\Projects\quick-compression\lib\utils\ffmpeg-error-logger.ts:232 Channels: 2
E:\Projects\quick-compression\lib\utils\ffmpeg-error-logger.ts:233 Custom Args: none
E:\Projects\quick-compression\lib\utils\ffmpeg-error-logger.ts:236 System Information
E:\Projects\quick-compression\lib\utils\ffmpeg-error-logger.ts:237 User Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0
E:\Projects\quick-compression\lib\utils\ffmpeg-error-logger.ts:238 Platform: Win32
E:\Projects\quick-compression\lib\utils\ffmpeg-error-logger.ts:239 Available Memory: 8
E:\Projects\quick-compression\lib\utils\ffmpeg-error-logger.ts:240 Hardware Concurrency: 12
E:\Projects\quick-compression\lib\utils\ffmpeg-error-logger.ts:299 OPUS CODEC ANALYSIS:
E:\Projects\quick-compression\lib\utils\ffmpeg-error-logger.ts:300 - Using libopus encoder
E:\Projects\quick-compression\lib\utils\ffmpeg-error-logger.ts:301 - Target container: ogg
E:\Projects\quick-compression\lib\utils\ffmpeg-error-logger.ts:302 - Bitrate mode active: true
E:\Projects\quick-compression\lib\utils\ffmpeg-error-logger.ts:303 - Quality mode active: false
E:\Projects\quick-compression\lib\utils\ffmpeg-error-logger.ts:304 - Both modes active (PROBLEMATIC): false
E:\Projects\quick-compression\lib\utils\ffmpeg-error-logger.ts:329 No obvious compatibility issues detected
E:\Projects\quick-compression\lib\useAudioCompression.ts:87 🔧 Final FFmpeg arguments: (14) ['-i', 'input_1760199936545.mp3', '-f', 'ogg', '-acodec', 'libopus', '-ar', '44100', '-ac', '2', '-b:a', '128k', '-y', 'output_1760199936545.ogg']
E:\Projects\quick-compression\lib\useAudioCompression.ts:88 Thread configuration: Device: desktop | Cores: 12 | Recommended Threads: 9 | Max Concurrent: 6
E:\Projects\quick-compression\lib\useAudioCompression.ts:92 Codec Analysis - Attempting opus compression
E:\Projects\quick-compression\lib\useAudioCompression.ts:93 Critical codec checks: {codecNormalization: 'opus → libopus', targetContainer: 'ogg', isValidCombination: true, expectedFileExtension: '.ogg', mimeType: 'audio/ogg'}
E:\Projects\quick-compression\lib\useAudioCompression.ts:103 Writing input file to FFmpeg virtual filesystem...
E:\Projects\quick-compression\lib\useAudioCompression.ts:106 Input file written successfully
E:\Projects\quick-compression\lib\useAudioCompression.ts:110 Executing FFmpeg compression...
E:\Projects\quick-compression\lib\useAudioCompression.ts:114 FFmpeg execution completed
E:\Projects\quick-compression\lib\useAudioCompression.ts:117 Reading compressed output file...
E:\Projects\quick-compression\lib\useAudioCompression.ts:119 Output file analysis: {outputFileName: 'output_1760199936545.ogg', rawDataSize: 0, sizeInBytes: '0 bytes', sizeInKB: '0.00 KB', sizeInMB: '0.0000 MB', …}
E:\Projects\quick-compression\lib\useAudioCompression.ts:133 Debugging information:
E:\Projects\quick-compression\lib\useAudioCompression.ts:134 - Input file size: 5319693 bytes
E:\Projects\quick-compression\lib\useAudioCompression.ts:135 - Codec used: opus
E:\Projects\quick-compression\lib\useAudioCompression.ts:136 - Output format: ogg
E:\Projects\quick-compression\lib\useAudioCompression.ts:137 - Arguments passed: (14) ['-i', 'input_1760199936545.mp3', '-f', 'ogg', '-acodec', 'libopus', '-ar', '44100', '-ac', '2', '-b:a', '128k', '-y', 'output_1760199936545.ogg']
E:\Projects\quick-compression\lib\utils\ffmpeg-error-logger.ts:21 FFmpeg Comprehensive Error Analysis
E:\Projects\quick-compression\lib\utils\ffmpeg-error-logger.ts:22 Raw error object: Error: FFmpeg produced empty output file. This indicates a codec incompatibility or invalid arguments. Codec: opus, Format: ogg
at useAudioCompression.useCallback[compressAudio] (E:\Projects\quick-compression\lib\useAudioCompression.ts:138:15)
at async handleAudioCompression (E:\Projects\quick-compression\lib\useCompression.tsx:46:22)
at async compressFile (E:\Projects\quick-compression\app\page.tsx:107:26)
at async handleCompressFile (E:\Projects\quick-compression\app\page.tsx:153:5)
at async handleRetryFile (E:\Projects\quick-compression\app\page.tsx:171:5)
E:\Projects\quick-compression\lib\utils\ffmpeg-error-logger.ts:23 Error string: Error: FFmpeg produced empty output file. This indicates a codec incompatibility or invalid arguments. Codec: opus, Format: ogg
E:\Projects\quick-compression\lib\utils\ffmpeg-error-logger.ts:24 Error message: FFmpeg produced empty output file. This indicates a codec incompatibility or invalid arguments. Codec: opus, Format: ogg
E:\Projects\quick-compression\lib\utils\ffmpeg-error-logger.ts:25 Error type: object
E:\Projects\quick-compression\lib\utils\ffmpeg-error-logger.ts:26 Error stack: Error: FFmpeg produced empty output file. This indicates a codec incompatibility or invalid arguments. Codec: opus, Format: ogg
at useAudioCompression.useCallback[compressAudio] (webpack-internal:///(app-pages-browser)/./lib/useAudioCompression.ts:125:27)
at async handleAudioCompression (webpack-internal:///(app-pages-browser)/./lib/useCompression.tsx:53:28)
at async compressFile (webpack-internal:///(app-pages-browser)/./app/page.tsx:93:34)
at async handleCompressFile (webpack-internal:///(app-pages-browser)/./app/page.tsx:131:9)
at async handleRetryFile (webpack-internal:///(app-pages-browser)/./app/page.tsx:147:9)
E:\Projects\quick-compression\lib\utils\ffmpeg-error-logger.ts:30 All error properties:
i would really appreaciate it if someone would navigate me through this issue, and if anyone would like extra context feel free to tell me what extra context do you need and i would be happy to add it
I tried playing around with the args, read somewhere that ffmpeg wasm doesnt support the syntax of libopus and i should use opus, and the other way around, conflicts between bitrate setting and quality setting
Could it be that this is a known bug with ffmpeg.wasm? Other folks are also getting 0-byte empty files when attempting to convert and mp3 to ogg when using the libopus codec. You could try their suggestion, which is to use libvorbis intead. To do so, you could change the Zustand store from:
{
acodec: "opus",
outputFormat: "ogg",
bitrate: "128k",
// ... other settings
}
to:
{
acodec: "vorbis",
outputFormat: "ogg",
bitrate: "128k",
// ... other settings
}