feat: trim video

This commit is contained in:
Ibrahima G. Coulibaly
2025-03-10 04:13:10 +00:00
parent e2c6d02fe6
commit d76abec8c0
16 changed files with 535 additions and 169 deletions

View File

@@ -31,7 +31,6 @@ describe('shuffle function', () => {
joinSeparator,
length
);
console.log(result);
expect(result.split(joinSeparator).length).toBe(2);
});
@@ -49,7 +48,6 @@ describe('shuffle function', () => {
joinSeparator,
length
);
console.log(result);
expect(result.split(joinSeparator).length).toBe(4);
});
@@ -66,7 +64,6 @@ describe('shuffle function', () => {
joinSeparator,
length
);
console.log(result);
expect(result.split(joinSeparator)).toContain('apple');
});
@@ -83,7 +80,6 @@ describe('shuffle function', () => {
joinSeparator,
length
);
console.log(result);
expect(result).toBe('');
});
});

View File

@@ -13,7 +13,6 @@ const initialValues = {
export default function ToMorse() {
const [input, setInput] = useState<string>('');
const [result, setResult] = useState<string>('');
// const formRef = useRef<FormikProps<typeof initialValues>>(null);
const computeOptions = (optionsValues: typeof initialValues, input: any) => {
const { dotSymbol, dashSymbol } = optionsValues;
setResult(compute(input, dotSymbol, dashSymbol));

View File

@@ -1,3 +1,4 @@
import { gifTools } from './gif';
import { tool as trimVideo } from './trim/meta';
export const videoTools = [...gifTools];
export const videoTools = [...gifTools, trimVideo];

View File

@@ -0,0 +1,143 @@
import { Box } from '@mui/material';
import React, { useCallback, useEffect, useState } from 'react';
import * as Yup from 'yup';
import ToolFileInput from '@components/input/ToolFileInput';
import ToolFileResult from '@components/result/ToolFileResult';
import ToolContent from '@components/ToolContent';
import { ToolComponentProps } from '@tools/defineTool';
import { GetGroupsType } from '@components/options/ToolOptions';
import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
import { updateNumberField } from '@utils/string';
import { FFmpeg } from '@ffmpeg/ffmpeg';
import { fetchFile } from '@ffmpeg/util';
import { debounce } from 'lodash';
const ffmpeg = new FFmpeg();
const initialValues = {
trimStart: 0,
trimEnd: 100
};
const validationSchema = Yup.object({
trimStart: Yup.number().min(0, 'Start time must be positive'),
trimEnd: Yup.number().min(
Yup.ref('trimStart'),
'End time must be greater than start time'
)
});
export default function TrimVideo({ title }: ToolComponentProps) {
const [input, setInput] = useState<File | null>(null);
const [result, setResult] = useState<File | null>(null);
const compute = async (
optionsValues: typeof initialValues,
input: File | null
) => {
console.log('compute', optionsValues, input);
if (!input) return;
const { trimStart, trimEnd } = optionsValues;
try {
if (!ffmpeg.loaded) {
await ffmpeg.load();
}
const inputName = 'input.mp4';
const outputName = 'output.mp4';
// Load file into FFmpeg's virtual filesystem
await ffmpeg.writeFile(inputName, await fetchFile(input));
// Run FFmpeg command to trim video
await ffmpeg.exec([
'-i',
inputName,
'-ss',
trimStart.toString(),
'-to',
trimEnd.toString(),
'-c',
'copy',
outputName
]);
// Retrieve the processed file
const trimmedData = await ffmpeg.readFile(outputName);
const trimmedBlob = new Blob([trimmedData], { type: 'video/mp4' });
const trimmedFile = new File(
[trimmedBlob],
`${input.name.replace(/\.[^/.]+$/, '')}_trimmed.mp4`,
{
type: 'video/mp4'
}
);
setResult(trimmedFile);
} catch (error) {
console.error('Error trimming video:', error);
}
};
const debouncedCompute = useCallback(debounce(compute, 1000), []);
const getGroups: GetGroupsType<typeof initialValues> = ({
values,
updateField
}) => [
{
title: 'Timestamps',
component: (
<Box>
<TextFieldWithDesc
onOwnChange={(value) =>
updateNumberField(value, 'trimStart', updateField)
}
value={values.trimStart}
label={'Start Time'}
/>
<TextFieldWithDesc
onOwnChange={(value) =>
updateNumberField(value, 'trimEnd', updateField)
}
value={values.trimEnd}
label={'End Time'}
/>
</Box>
)
}
];
return (
<ToolContent
title={title}
input={input}
renderCustomInput={({ trimStart, trimEnd }, setFieldValue) => {
return (
<ToolFileInput
value={input}
onChange={setInput}
accept={['video/mp4', 'video/webm', 'video/ogg']}
title={'Input Video'}
type="video"
showTrimControls={true}
onTrimChange={(trimStart, trimEnd) => {
setFieldValue('trimStart', trimStart);
setFieldValue('trimEnd', trimEnd);
}}
trimStart={trimStart}
trimEnd={trimEnd}
/>
);
}}
resultComponent={
<ToolFileResult
title={'Trimmed Video'}
value={result}
extension={'webm'}
/>
}
initialValues={initialValues}
getGroups={getGroups}
compute={debouncedCompute}
setInput={setInput}
validationSchema={validationSchema}
/>
);
}

View File

@@ -0,0 +1,13 @@
import { defineTool } from '@tools/defineTool';
import { lazy } from 'react';
export const tool = defineTool('video', {
name: 'Trim Video',
path: 'trim',
icon: 'mdi:scissors',
description:
'This online utility lets you trim videos by setting start and end points. You can preview the trimmed section before processing. Supports common video formats like MP4, WebM, and OGG.',
shortDescription: 'Trim videos by setting start and end points',
keywords: ['trim', 'cut', 'video', 'clip', 'edit'],
component: lazy(() => import('./index'))
});