Merge pull request #119 from C043/main

Adds change speed video tool
This commit is contained in:
Ibrahima G. Coulibaly
2025-05-23 20:21:21 +01:00
committed by GitHub
6 changed files with 199 additions and 12 deletions

View File

@@ -41,7 +41,7 @@ export default function BaseFileInput({
} catch (error) {
console.error('Error previewing file:', error);
}
}
} else setPreview(null);
}, [value]);
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
@@ -67,11 +67,6 @@ export default function BaseFileInput({
}
};
function handleClear() {
// @ts-ignore
onChange(null);
}
const handleDrop = (event: React.DragEvent<HTMLDivElement>) => {
event.preventDefault();
event.stopPropagation();
@@ -213,11 +208,7 @@ export default function BaseFileInput({
</Box>
)}
</Box>
<InputFooter
handleCopy={handleCopy}
handleImport={handleImportClick}
handleClear={handleClear}
/>
<InputFooter handleCopy={handleCopy} handleImport={handleImportClick} />
<input
ref={fileInputRef}
style={{ display: 'none' }}

View File

@@ -0,0 +1,169 @@
import { Box } from '@mui/material';
import React, { useState } from 'react';
import ToolContent from '@components/ToolContent';
import { ToolComponentProps } from '@tools/defineTool';
import { GetGroupsType } from '@components/options/ToolOptions';
import { InitialValuesType } from './types';
import ToolVideoInput from '@components/input/ToolVideoInput';
import ToolFileResult from '@components/result/ToolFileResult';
import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
import { FFmpeg } from '@ffmpeg/ffmpeg';
import { fetchFile } from '@ffmpeg/util';
const initialValues: InitialValuesType = {
newSpeed: 2
};
export default function ChangeSpeed({
title,
longDescription
}: ToolComponentProps) {
const [input, setInput] = useState<File | null>(null);
const [result, setResult] = useState<File | null>(null);
const [loading, setLoading] = useState(false);
// FFmpeg only supports a tempo between 0.5 and 2.0, so we chain filters
const computeAudioFilter = (speed: number): string => {
if (speed <= 2 && speed >= 0.5) {
return `atempo=${speed}`;
}
// Break into supported chunks
const filters: string[] = [];
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(',');
};
const compute = (optionsValues: InitialValuesType, input: File | null) => {
if (!input) return;
const { newSpeed } = optionsValues;
let ffmpeg: FFmpeg | null = null;
let ffmpegLoaded = false;
const processVideo = async (
file: File,
newSpeed: number
): Promise<void> => {
if (newSpeed === 0) return;
setLoading(true);
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',
'-preset',
'ultrafast',
'-c:a',
'aac',
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);
}
};
// Here we set the output video
processVideo(input, newSpeed);
};
const getGroups: GetGroupsType<InitialValuesType> | null = ({
values,
updateField
}) => [
{
title: 'New Video Speed',
component: (
<Box>
<TextFieldWithDesc
value={values.newSpeed.toString()}
onOwnChange={(val) => updateField('newSpeed', Number(val))}
description="Default multiplier: 2 means 2x faster"
type="number"
/>
</Box>
)
}
];
return (
<ToolContent
title={title}
input={input}
inputComponent={
<ToolVideoInput
value={input}
onChange={setInput}
title={'Input Video'}
/>
}
resultComponent={
loading ? (
<ToolFileResult title="Setting Speed" value={null} loading={true} />
) : (
<ToolFileResult title="Edited Video" value={result} extension="mp4" />
)
}
initialValues={initialValues}
getGroups={getGroups}
setInput={setInput}
compute={compute}
toolInfo={{ title: `What is a ${title}?`, description: longDescription }}
/>
);
}

View File

@@ -0,0 +1,13 @@
import { defineTool } from '@tools/defineTool';
import { lazy } from 'react';
export const tool = defineTool('video', {
name: 'Change speed',
path: 'change-speed',
icon: 'material-symbols-light:speed-outline',
description:
'This online utility lets you change the speed of a video. You can speed it up or slow it down.',
shortDescription: 'Quickly change video speed',
keywords: ['change', 'speed'],
component: lazy(() => import('./index'))
});

View File

@@ -0,0 +1,8 @@
import { InitialValuesType } from './types';
export function main(
input: File | null,
options: InitialValuesType
): File | null {
return input;
}

View File

@@ -0,0 +1,3 @@
export type InitialValuesType = {
newSpeed: number;
};

View File

@@ -1,3 +1,4 @@
import { tool as videoChangeSpeed } from './change-speed/meta';
import { tool as videoFlip } from './flip/meta';
import { rotate } from '../string/rotate/service';
import { gifTools } from './gif';
@@ -6,6 +7,7 @@ import { tool as rotateVideo } from './rotate/meta';
import { tool as compressVideo } from './compress/meta';
import { tool as loopVideo } from './loop/meta';
import { tool as flipVideo } from './flip/meta';
import { tool as changeSpeed } from './change-speed/meta';
export const videoTools = [
...gifTools,
@@ -13,5 +15,6 @@ export const videoTools = [
rotateVideo,
compressVideo,
loopVideo,
flipVideo
flipVideo,
changeSpeed
];