2025-07-25 13:54:48 +05:30
|
|
|
import { FFmpeg } from '@ffmpeg/ffmpeg';
|
|
|
|
|
import { fetchFile } from '@ffmpeg/util';
|
2025-12-10 16:47:51 +01:00
|
|
|
import { InitialValuesType, AUDIO_FORMATS } from './types';
|
|
|
|
|
import { getFileExtension } from 'utils/file';
|
2025-07-25 13:54:48 +05:30
|
|
|
|
2025-12-10 16:47:51 +01:00
|
|
|
/**
|
|
|
|
|
* optimzed call for FFmpeg instance creation,
|
|
|
|
|
* avoiding to download required FFmpeg binaries on every reload
|
|
|
|
|
*/
|
2025-07-25 13:54:48 +05:30
|
|
|
const ffmpeg = new FFmpeg();
|
|
|
|
|
let isLoaded = false;
|
|
|
|
|
|
|
|
|
|
async function loadFFmpeg() {
|
|
|
|
|
if (!isLoaded) {
|
|
|
|
|
await ffmpeg.load();
|
|
|
|
|
isLoaded = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Converts input audio file to selected output format ('mp3', 'aac', or 'wav').
|
|
|
|
|
* Supports any input audio file type accepted by FFmpeg.
|
|
|
|
|
*
|
|
|
|
|
* @param input - Source audio File
|
|
|
|
|
* @param outputFormat - 'mp3' | 'aac' | 'wav'
|
|
|
|
|
* @returns Converted audio File
|
|
|
|
|
*/
|
|
|
|
|
export async function convertAudio(
|
|
|
|
|
input: File,
|
2025-12-10 16:47:51 +01:00
|
|
|
options: InitialValuesType
|
2025-07-25 13:54:48 +05:30
|
|
|
): Promise<File> {
|
|
|
|
|
await loadFFmpeg();
|
|
|
|
|
|
|
|
|
|
// Use the original input extension for input filename
|
2025-12-10 16:47:51 +01:00
|
|
|
const inputExt = getFileExtension(input.name);
|
|
|
|
|
|
|
|
|
|
if (inputExt === options.outputFormat) return input;
|
2025-07-25 13:54:48 +05:30
|
|
|
|
2025-12-10 16:47:51 +01:00
|
|
|
const inputFileName = inputExt ? `input.${inputExt}` : 'input';
|
|
|
|
|
const outputFileName = `output.${options.outputFormat}`;
|
2025-07-25 13:54:48 +05:30
|
|
|
|
|
|
|
|
// Write the input file to FFmpeg FS
|
|
|
|
|
await ffmpeg.writeFile(inputFileName, await fetchFile(input));
|
|
|
|
|
|
|
|
|
|
// Build the FFmpeg args depending on the output format
|
|
|
|
|
// You can customize the codec and bitrate options per format here
|
|
|
|
|
|
2025-12-10 16:47:51 +01:00
|
|
|
const format = AUDIO_FORMATS[options.outputFormat];
|
|
|
|
|
const { codec, bitrate, mimeType } = format;
|
2025-07-25 13:54:48 +05:30
|
|
|
|
2025-12-10 16:47:51 +01:00
|
|
|
const args = bitrate
|
|
|
|
|
? ['-i', inputFileName, '-c:a', codec, '-b:a', bitrate, outputFileName]
|
|
|
|
|
: ['-i', inputFileName, '-c:a', codec, outputFileName];
|
2025-07-25 13:54:48 +05:30
|
|
|
|
|
|
|
|
// Execute ffmpeg with arguments
|
2025-12-10 16:47:51 +01:00
|
|
|
try {
|
|
|
|
|
await ffmpeg.exec(args);
|
|
|
|
|
|
|
|
|
|
// Read the output file from FFmpeg FS
|
|
|
|
|
const data = await ffmpeg.readFile(outputFileName);
|
|
|
|
|
|
|
|
|
|
// Create a new File with the original name but new extension
|
|
|
|
|
const baseName = input.name.replace(/\.[^.]+$/, '');
|
|
|
|
|
const convertedFileName = `${baseName}.${options.outputFormat}`;
|
|
|
|
|
|
2025-12-10 18:16:20 +01:00
|
|
|
return new File(
|
|
|
|
|
[new Blob([data as any], { type: mimeType })],
|
|
|
|
|
convertedFileName,
|
|
|
|
|
{
|
|
|
|
|
type: mimeType
|
|
|
|
|
}
|
|
|
|
|
);
|
2025-12-10 16:47:51 +01:00
|
|
|
} finally {
|
|
|
|
|
// Clean up FFmpeg virtual filesystem
|
|
|
|
|
try {
|
|
|
|
|
await ffmpeg.deleteFile(inputFileName);
|
|
|
|
|
await ffmpeg.deleteFile(outputFileName);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
// Ignore cleanup errors
|
|
|
|
|
}
|
2025-07-25 13:54:48 +05:30
|
|
|
}
|
|
|
|
|
}
|