Merge pull request #256 from njfletcher215/trim-on-video-to-gif

added option to concurrently trim video
This commit is contained in:
Ibrahima G. Coulibaly 2025-10-02 20:45:24 +01:00 committed by GitHub
commit f889de8068
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1,8 +1,11 @@
import { Box } from '@mui/material'; import { Box } from '@mui/material';
import React, { useState } from 'react'; import React, { useState } from 'react';
import * as Yup from 'yup';
import ToolContent from '@components/ToolContent'; import ToolContent from '@components/ToolContent';
import { ToolComponentProps } from '@tools/defineTool'; import { ToolComponentProps } from '@tools/defineTool';
import { GetGroupsType } from '@components/options/ToolOptions'; import { GetGroupsType } from '@components/options/ToolOptions';
import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
import { updateNumberField } from '@utils/string';
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';
@ -13,9 +16,19 @@ import { fetchFile } from '@ffmpeg/util';
const initialValues: InitialValuesType = { const initialValues: InitialValuesType = {
quality: 'mid', quality: 'mid',
fps: '10', fps: '10',
scale: '320:-1:flags=bicubic' scale: '320:-1:flags=bicubic',
start: 0,
end: 100
}; };
const validationSchema = Yup.object({
start: Yup.number().min(0, 'Start time must be positive'),
end: Yup.number().min(
Yup.ref('start'),
'End time must be greater than start time'
)
});
export default function VideoToGif({ export default function VideoToGif({
title, title,
longDescription longDescription
@ -26,14 +39,16 @@ export default function VideoToGif({
const compute = (values: InitialValuesType, input: File | null) => { const compute = (values: InitialValuesType, input: File | null) => {
if (!input) return; if (!input) return;
const { fps, scale } = values; const { fps, scale, start, end } = values;
let ffmpeg: FFmpeg | null = null; let ffmpeg: FFmpeg | null = null;
let ffmpegLoaded = false; let ffmpegLoaded = false;
const convertVideoToGif = async ( const convertVideoToGif = async (
file: File, file: File,
fps: string, fps: string,
scale: string scale: string,
start: number,
end: number
): Promise<void> => { ): Promise<void> => {
setLoading(true); setLoading(true);
@ -58,6 +73,10 @@ export default function VideoToGif({
await ffmpeg.exec([ await ffmpeg.exec([
'-i', '-i',
fileName, fileName,
'-ss',
start.toString(),
'-to',
end.toString(),
'-vf', '-vf',
`fps=${fps},scale=${scale},palettegen`, `fps=${fps},scale=${scale},palettegen`,
'palette.png' 'palette.png'
@ -68,6 +87,10 @@ export default function VideoToGif({
fileName, fileName,
'-i', '-i',
'palette.png', 'palette.png',
'-ss',
start.toString(),
'-to',
end.toString(),
'-filter_complex', '-filter_complex',
`fps=${fps},scale=${scale}[x];[x][1:v]paletteuse`, `fps=${fps},scale=${scale}[x];[x][1:v]paletteuse`,
outputName outputName
@ -92,7 +115,7 @@ export default function VideoToGif({
} }
}; };
convertVideoToGif(input, fps, scale); convertVideoToGif(input, fps, scale, start, end);
}; };
const getGroups: GetGroupsType<InitialValuesType> | null = ({ const getGroups: GetGroupsType<InitialValuesType> | null = ({
@ -141,6 +164,28 @@ export default function VideoToGif({
/> />
</Box> </Box>
) )
},
{
title: 'Timestamps',
component: (
<Box>
<TextFieldWithDesc
onOwnChange={(value) =>
updateNumberField(value, 'start', updateField)
}
value={values.start}
label="Start Time"
sx={{ mb: 2, backgroundColor: 'background.paper' }}
/>
<TextFieldWithDesc
onOwnChange={(value) =>
updateNumberField(value, 'end', updateField)
}
value={values.end}
label="End Time"
/>
</Box>
)
} }
]; ];
@ -148,9 +193,22 @@ export default function VideoToGif({
<ToolContent <ToolContent
title={title} title={title}
input={input} input={input}
inputComponent={ renderCustomInput={({ start, end }, setFieldValue) => {
<ToolVideoInput value={input} onChange={setInput} title="Input Video" /> return (
} <ToolVideoInput
value={input}
onChange={setInput}
title={'Input Video'}
showTrimControls={true}
onTrimChange={(start, end) => {
setFieldValue('start', start);
setFieldValue('end', end);
}}
trimStart={start}
trimEnd={end}
/>
);
}}
resultComponent={ resultComponent={
loading ? ( loading ? (
<ToolFileResult <ToolFileResult