Adds ffmpeg logic

This commit is contained in:
Mario Fragnito
2025-05-23 09:55:33 +02:00
parent e064689e35
commit 338bd8d937

View File

@@ -3,21 +3,19 @@ import { Box } from '@mui/material';
import React, { useState } from 'react'; import React, { useState } from 'react';
import ToolContent from '@components/ToolContent'; import ToolContent from '@components/ToolContent';
import { ToolComponentProps } from '@tools/defineTool'; import { ToolComponentProps } from '@tools/defineTool';
import ToolTextInput from '@components/input/ToolTextInput';
import ToolTextResult from '@components/result/ToolTextResult';
import { GetGroupsType } from '@components/options/ToolOptions'; import { GetGroupsType } from '@components/options/ToolOptions';
import { CardExampleType } from '@components/examples/ToolExamples';
import { main } from './service'; import { main } from './service';
import { InitialValuesType } from './types'; import { InitialValuesType } from './types';
import ToolVideoInput from '@components/input/ToolVideoInput'; import ToolVideoInput from '@components/input/ToolVideoInput';
import ToolFileResult from '@components/result/ToolFileResult'; import ToolFileResult from '@components/result/ToolFileResult';
import TextFieldWithDesc from '@components/options/TextFieldWithDesc'; import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
import { FFmpeg } from '@ffmpeg/ffmpeg';
import { fetchFile } from '@ffmpeg/util';
const initialValues: InitialValuesType = { const initialValues: InitialValuesType = {
newSpeed: 2 newSpeed: 2
}; };
// TODO - Add the ffmpeg logic
export default function ChangeSpeed({ export default function ChangeSpeed({
title, title,
longDescription longDescription
@@ -27,8 +25,103 @@ export default function ChangeSpeed({
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const compute = (optionsValues: InitialValuesType, input: File | null) => { const compute = (optionsValues: InitialValuesType, input: File | null) => {
setLoading(true);
if (!input) return; if (!input) return;
const { newSpeed } = optionsValues; const { newSpeed } = optionsValues;
let ffmpeg: FFmpeg | null = null;
let ffmpegLoaded = false;
const processVideo = async (
file: File,
newSpeed: number
): Promise<void> => {
if (!ffmpeg) {
ffmpeg = new FFmpeg();
}
if (!ffmpegLoaded) {
await ffmpeg.load({
wasmURL:
'https://cdn.jsdelivr.net/npm/@ffmpeg/core@0.12.9/dist/esm/ffmpeg-core.wasm'
});
ffmpegLoaded = true;
}
// Write file to FFmpeg FS
const fileName = file.name;
const outputName = 'output.mp4';
try {
ffmpeg.writeFile(fileName, await fetchFile(file));
const videoFilter = `setpts=${1 / newSpeed}*PTS`;
const audioFilter = computeAudioFilter(newSpeed);
// Run FFmpeg command
await ffmpeg.exec([
'-i',
fileName,
'-vf',
videoFilter,
'-filter:a',
audioFilter,
'-c:v',
'libx264',
'-crf',
'18',
'-preset',
'slow',
'-c:a',
'aac',
'-b:a',
'192k',
outputName
]);
const data = await ffmpeg.readFile(outputName);
// Create new file from processed data
const blob = new Blob([data], { type: 'video/mp4' });
const newFile = new File(
[blob],
file.name.replace('.mp4', `-${newSpeed}x.mp4`),
{ type: 'video/mp4' }
);
// Clean up to free memory
await ffmpeg.deleteFile(fileName);
await ffmpeg.deleteFile(outputName);
setResult(newFile);
} catch (err) {
console.error(`Failed to process video: ${err}`);
throw err;
} finally {
setLoading(false);
}
// FFmpeg only supports atempo between 0.5 and 2.0, so we chain filters
function computeAudioFilter(speed: number): string {
if (speed <= 2 && speed >= 0.5) {
return `atempo=${speed}`;
}
// Break into supported chunks
const filters = [];
let remainingSpeed = speed;
while (remainingSpeed > 2.0) {
filters.push('atempo=2.0');
remainingSpeed /= 2.0;
}
while (remainingSpeed < 0.5) {
filters.push('atempo=0.5');
remainingSpeed /= 0.5;
}
filters.push(`atempo=${remainingSpeed.toFixed(2)}`);
return filters.join(',');
}
};
// Here we set the output video // Here we set the output video
setResult(main(input, optionsValues)); setResult(main(input, optionsValues));