feat(loop): add loop video tool to video tools list

This commit is contained in:
Yousef Mahmoud
2025-04-20 11:14:41 +02:00
parent 1cc774e75d
commit 2b7ffb49f6
5 changed files with 160 additions and 1 deletions

View File

@@ -3,5 +3,12 @@ import { gifTools } from './gif';
import { tool as trimVideo } from './trim/meta'; import { tool as trimVideo } from './trim/meta';
import { tool as rotateVideo } from './rotate/meta'; import { tool as rotateVideo } from './rotate/meta';
import { tool as compressVideo } from './compress/meta'; import { tool as compressVideo } from './compress/meta';
import { tool as loopVideo } from './loop/meta';
export const videoTools = [...gifTools, trimVideo, rotateVideo, compressVideo]; export const videoTools = [
...gifTools,
trimVideo,
rotateVideo,
compressVideo,
loopVideo
];

View File

@@ -0,0 +1,95 @@
import { Box } from '@mui/material';
import { useState } from 'react';
import ToolContent from '@components/ToolContent';
import { ToolComponentProps } from '@tools/defineTool';
import { GetGroupsType } from '@components/options/ToolOptions';
import { CardExampleType } from '@components/examples/ToolExamples';
import { loopVideo } from './service';
import { InitialValuesType } from './types';
import ToolVideoInput from '@components/input/ToolVideoInput';
import ToolFileResult from '@components/result/ToolFileResult';
import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
import { updateNumberField } from '@utils/string';
import * as Yup from 'yup';
const initialValues: InitialValuesType = {
loops: 1
};
const validationSchema = Yup.object({
loops: Yup.number().min(1, 'Number of loops must be greater than 1')
});
export default function Loop({ title, longDescription }: ToolComponentProps) {
const [input, setInput] = useState<File | null>(null);
const [result, setResult] = useState<File | null>(null);
const [loading, setLoading] = useState(false);
const compute = async (values: InitialValuesType, input: File | null) => {
if (!input) return;
try {
setLoading(true);
const resultFile = await loopVideo(input, values);
await setResult(resultFile);
} catch (error) {
console.error(error);
} finally {
setLoading(false);
}
};
const getGroups: GetGroupsType<InitialValuesType> | null = ({
values,
updateField
}) => [
{
title: 'Loops',
component: (
<Box>
<TextFieldWithDesc
onOwnChange={(value) =>
updateNumberField(value, 'loops', updateField)
}
value={values.loops}
label={'Number of Loops'}
/>
</Box>
)
}
];
return (
<ToolContent
title={title}
input={input}
inputComponent={
<ToolVideoInput
value={input}
onChange={setInput}
accept={['video/*']}
/>
}
resultComponent={
loading ? (
<ToolFileResult
value={null}
title={'Looping Video'}
loading={true}
extension={''}
/>
) : (
<ToolFileResult
value={result}
title={'Looped Video'}
extension={'mp4'}
/>
)
}
initialValues={initialValues}
validationSchema={validationSchema}
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: 'Loop Video',
path: 'loop',
icon: 'ic:baseline-loop',
description:
'This online utility lets you loop videos by specifying the number of repetitions. You can preview the looped video before processing. Supports common video formats like MP4, WebM, and OGG.',
shortDescription: 'Loop videos multiple times',
keywords: ['loop', 'video', 'repeat', 'duplicate', 'sequence', 'playback'],
component: lazy(() => import('./index'))
});

View File

@@ -0,0 +1,41 @@
import { InitialValuesType } from './types';
import { FFmpeg } from '@ffmpeg/ffmpeg';
import { fetchFile } from '@ffmpeg/util';
const ffmpeg = new FFmpeg();
export async function loopVideo(
input: File,
options: InitialValuesType
): Promise<File> {
if (!ffmpeg.loaded) {
await ffmpeg.load({
wasmURL:
'https://cdn.jsdelivr.net/npm/@ffmpeg/core@0.12.9/dist/esm/ffmpeg-core.wasm'
});
}
const inputName = 'input.mp4';
const outputName = 'output.mp4';
await ffmpeg.writeFile(inputName, await fetchFile(input));
const args = [];
const loopCount = options.loops - 1;
if (loopCount <= 0) {
return input;
}
args.push('-stream_loop', loopCount.toString());
args.push('-i', inputName);
args.push('-c:v', 'libx264', '-preset', 'ultrafast', outputName);
await ffmpeg.exec(args);
const loopedData = await ffmpeg.readFile(outputName);
return await new File(
[new Blob([loopedData], { type: 'video/mp4' })],
`${input.name.replace(/\.[^/.]+$/, '')}_looped.mp4`,
{ type: 'video/mp4' }
);
}

View File

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