From 377dbc3685c19c406fc0b27a1736132be66a6fff Mon Sep 17 00:00:00 2001 From: C043 Date: Mon, 9 Jun 2025 15:25:14 +0200 Subject: [PATCH 1/8] Initial commit for new tool video to gif --- src/pages/tools/video/index.ts | 5 +- src/pages/tools/video/video-to-gif/index.tsx | 69 +++++++++++++++++++ src/pages/tools/video/video-to-gif/meta.ts | 12 ++++ src/pages/tools/video/video-to-gif/service.ts | 5 ++ src/pages/tools/video/video-to-gif/types.ts | 3 + .../video-to-gif/video-to-gif.service.test.ts | 6 ++ 6 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 src/pages/tools/video/video-to-gif/index.tsx create mode 100644 src/pages/tools/video/video-to-gif/meta.ts create mode 100644 src/pages/tools/video/video-to-gif/service.ts create mode 100644 src/pages/tools/video/video-to-gif/types.ts create mode 100644 src/pages/tools/video/video-to-gif/video-to-gif.service.test.ts diff --git a/src/pages/tools/video/index.ts b/src/pages/tools/video/index.ts index 99bb8e7..4a7c3b8 100644 --- a/src/pages/tools/video/index.ts +++ b/src/pages/tools/video/index.ts @@ -1,3 +1,4 @@ +import { tool as videoVideoToGif } from './video-to-gif/meta'; import { tool as videoChangeSpeed } from './change-speed/meta'; import { tool as videoFlip } from './flip/meta'; import { rotate } from '../string/rotate/service'; @@ -9,6 +10,7 @@ import { tool as loopVideo } from './loop/meta'; import { tool as flipVideo } from './flip/meta'; import { tool as cropVideo } from './crop-video/meta'; import { tool as changeSpeed } from './change-speed/meta'; +import { tool as videoToGif } from './video-to-gif/meta'; export const videoTools = [ ...gifTools, @@ -18,5 +20,6 @@ export const videoTools = [ loopVideo, flipVideo, cropVideo, - changeSpeed + changeSpeed, + videoToGif ]; diff --git a/src/pages/tools/video/video-to-gif/index.tsx b/src/pages/tools/video/video-to-gif/index.tsx new file mode 100644 index 0000000..86c066b --- /dev/null +++ b/src/pages/tools/video/video-to-gif/index.tsx @@ -0,0 +1,69 @@ +import { Box } from '@mui/material'; +import React, { useState } from 'react'; +import ToolContent from '@components/ToolContent'; +import { ToolComponentProps } from '@tools/defineTool'; +import ToolTextInput from '@components/input/ToolTextInput'; +import ToolTextResult from '@components/result/ToolTextResult'; +import { GetGroupsType } from '@components/options/ToolOptions'; +import { CardExampleType } from '@components/examples/ToolExamples'; +import { main } from './service'; +import { InitialValuesType } from './types'; +import ToolVideoInput from '@components/input/ToolVideoInput'; +import ToolFileResult from '@components/result/ToolFileResult'; + +const initialValues: InitialValuesType = { + // splitSeparator: '\n' +}; + +export default function VideoToGif({ + title, + longDescription +}: ToolComponentProps) { + const [input, setInput] = useState(null); + const [result, setResult] = useState(null); + const [loading, setIsLoading] = useState(false); + + const compute = (values: InitialValuesType, input: File | null) => { + setResult(main(input, values)); + }; + + const getGroups: GetGroupsType | null = ({ + values, + updateField + }) => [ + { + title: 'Example Settings', + component: + } + ]; + + return ( + + } + resultComponent={ + loading ? ( + + ) : ( + + ) + } + initialValues={initialValues} + getGroups={getGroups} + setInput={setInput} + compute={compute} + toolInfo={{ title: `What is a ${title}?`, description: longDescription }} + /> + ); +} diff --git a/src/pages/tools/video/video-to-gif/meta.ts b/src/pages/tools/video/video-to-gif/meta.ts new file mode 100644 index 0000000..06f1f3f --- /dev/null +++ b/src/pages/tools/video/video-to-gif/meta.ts @@ -0,0 +1,12 @@ +import { defineTool } from '@tools/defineTool'; +import { lazy } from 'react'; + +export const tool = defineTool('video', { + name: 'Video to Gif', + path: 'video-to-gif', + icon: 'fluent:gif-16-regular', + description: 'This online utility lets you convert a short video to gif.', + shortDescription: 'Quickly convert a short video to gif', + keywords: ['video', 'to', 'gif', 'convert'], + component: lazy(() => import('./index')) +}); diff --git a/src/pages/tools/video/video-to-gif/service.ts b/src/pages/tools/video/video-to-gif/service.ts new file mode 100644 index 0000000..dabc369 --- /dev/null +++ b/src/pages/tools/video/video-to-gif/service.ts @@ -0,0 +1,5 @@ +import { InitialValuesType } from './types'; + +export function main(input: File, options: InitialValuesType): File { + return input; +} diff --git a/src/pages/tools/video/video-to-gif/types.ts b/src/pages/tools/video/video-to-gif/types.ts new file mode 100644 index 0000000..d4135c9 --- /dev/null +++ b/src/pages/tools/video/video-to-gif/types.ts @@ -0,0 +1,3 @@ +export type InitialValuesType = { + // splitSeparator: string; +}; diff --git a/src/pages/tools/video/video-to-gif/video-to-gif.service.test.ts b/src/pages/tools/video/video-to-gif/video-to-gif.service.test.ts new file mode 100644 index 0000000..50e3c16 --- /dev/null +++ b/src/pages/tools/video/video-to-gif/video-to-gif.service.test.ts @@ -0,0 +1,6 @@ +import { expect, describe, it } from 'vitest'; +// import { main } from './service'; +// +// describe('video-to-gif', () => { +// +// }) From c629c7b07c98e6396b7f0bc7522a9780414b6910 Mon Sep 17 00:00:00 2001 From: C043 Date: Mon, 9 Jun 2025 16:00:54 +0200 Subject: [PATCH 2/8] Adds quality options to video to gif tool --- src/pages/tools/video/video-to-gif/index.tsx | 45 +++++++++++++++++--- src/pages/tools/video/video-to-gif/types.ts | 4 +- 2 files changed, 42 insertions(+), 7 deletions(-) diff --git a/src/pages/tools/video/video-to-gif/index.tsx b/src/pages/tools/video/video-to-gif/index.tsx index 86c066b..1c39542 100644 --- a/src/pages/tools/video/video-to-gif/index.tsx +++ b/src/pages/tools/video/video-to-gif/index.tsx @@ -1,18 +1,21 @@ +/* eslint-disable prettier/prettier */ import { Box } from '@mui/material'; import React, { useState } from 'react'; import ToolContent from '@components/ToolContent'; import { ToolComponentProps } from '@tools/defineTool'; -import ToolTextInput from '@components/input/ToolTextInput'; -import ToolTextResult from '@components/result/ToolTextResult'; import { GetGroupsType } from '@components/options/ToolOptions'; -import { CardExampleType } from '@components/examples/ToolExamples'; import { main } from './service'; import { InitialValuesType } from './types'; import ToolVideoInput from '@components/input/ToolVideoInput'; import ToolFileResult from '@components/result/ToolFileResult'; +import CheckboxWithDesc from '@components/options/CheckboxWithDesc'; +import RadioWithTextField from '@components/options/RadioWithTextField'; +import SimpleRadio from '@components/options/SimpleRadio'; const initialValues: InitialValuesType = { - // splitSeparator: '\n' + quality: 'mid', + fps: '10', + scale: 'scale=320:-1:flags=bicubic' }; export default function VideoToGif({ @@ -32,8 +35,38 @@ export default function VideoToGif({ updateField }) => [ { - title: 'Example Settings', - component: + title: 'Set Quality', + component: ( + + { + updateField('quality', 'low'); + updateField('fps', '5'); + updateField('scale', 'scale=240:-1:flags=bilinear'); + }} + checked={values.quality === 'low'} + /> + { + updateField('quality', 'mid'); + updateField('fps', '10'); + updateField('scale', 'scale=320:-1:flags=bicubic'); + }} + checked={values.quality === 'mid'} + /> + { + updateField('quality', 'high'); + updateField('fps', '15'); + updateField('scale', 'scale=480:-1:flags=lanczos'); + }} + checked={values.quality === 'high'} + /> + + ) } ]; diff --git a/src/pages/tools/video/video-to-gif/types.ts b/src/pages/tools/video/video-to-gif/types.ts index d4135c9..b56264e 100644 --- a/src/pages/tools/video/video-to-gif/types.ts +++ b/src/pages/tools/video/video-to-gif/types.ts @@ -1,3 +1,5 @@ export type InitialValuesType = { - // splitSeparator: string; + quality: string; + fps: string; + scale: string; }; From 22cd6def76f20f82c119e1159b596cde145ea9b4 Mon Sep 17 00:00:00 2001 From: C043 Date: Mon, 9 Jun 2025 16:44:47 +0200 Subject: [PATCH 3/8] Adds main logic --- src/pages/tools/video/video-to-gif/index.tsx | 84 +++++++++++++++++-- src/pages/tools/video/video-to-gif/service.ts | 5 -- 2 files changed, 76 insertions(+), 13 deletions(-) delete mode 100644 src/pages/tools/video/video-to-gif/service.ts diff --git a/src/pages/tools/video/video-to-gif/index.tsx b/src/pages/tools/video/video-to-gif/index.tsx index 1c39542..858eed5 100644 --- a/src/pages/tools/video/video-to-gif/index.tsx +++ b/src/pages/tools/video/video-to-gif/index.tsx @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable prettier/prettier */ import { Box } from '@mui/material'; import React, { useState } from 'react'; @@ -8,14 +9,14 @@ import { main } from './service'; import { InitialValuesType } from './types'; import ToolVideoInput from '@components/input/ToolVideoInput'; import ToolFileResult from '@components/result/ToolFileResult'; -import CheckboxWithDesc from '@components/options/CheckboxWithDesc'; -import RadioWithTextField from '@components/options/RadioWithTextField'; import SimpleRadio from '@components/options/SimpleRadio'; +import { FFmpeg } from '@ffmpeg/ffmpeg'; +import { fetchFile } from '@ffmpeg/util'; const initialValues: InitialValuesType = { quality: 'mid', fps: '10', - scale: 'scale=320:-1:flags=bicubic' + scale: '320:-1:flags=bicubic' }; export default function VideoToGif({ @@ -24,10 +25,77 @@ export default function VideoToGif({ }: ToolComponentProps) { const [input, setInput] = useState(null); const [result, setResult] = useState(null); - const [loading, setIsLoading] = useState(false); + const [loading, setLoading] = useState(false); const compute = (values: InitialValuesType, input: File | null) => { - setResult(main(input, values)); + if (!input) return; + const { fps, scale } = values; + let ffmpeg: FFmpeg | null = null; + let ffmpegLoaded = false; + + const convertVideoToGif = async ( + file: File, + fps: string, + scale: string + ): Promise => { + 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; + } + + const fileName = file.name; + const outputName = 'output.gif'; + + try { + ffmpeg.writeFile(fileName, await fetchFile(file)); + + await ffmpeg.exec([ + '-i', + fileName, + '-vf', + `fps=${fps},scale=${scale},palettegen`, + 'palette.png' + ]); + + await ffmpeg.exec([ + '-i', + fileName, + '-i', + 'palette.png', + '-filter_complex', + `fps=${fps},scale=${scale}[x];[x][1:v]paletteuse`, + outputName + ]); + + const data = await ffmpeg.readFile(outputName); + + const blob = new Blob([data], { type: 'image/gif' }); + const convertedFile = new File([blob], outputName, { + type: 'image/gif' + }); + + await ffmpeg.deleteFile(fileName); + await ffmpeg.deleteFile(outputName); + + setResult(convertedFile); + } catch (err) { + console.error(`Failed to convert video: ${err}`); + throw err; + } finally { + setLoading(false); + } + }; + + convertVideoToGif(input, fps, scale); }; const getGroups: GetGroupsType | null = ({ @@ -43,7 +111,7 @@ export default function VideoToGif({ onClick={() => { updateField('quality', 'low'); updateField('fps', '5'); - updateField('scale', 'scale=240:-1:flags=bilinear'); + updateField('scale', '240:-1:flags=bilinear'); }} checked={values.quality === 'low'} /> @@ -52,7 +120,7 @@ export default function VideoToGif({ onClick={() => { updateField('quality', 'mid'); updateField('fps', '10'); - updateField('scale', 'scale=320:-1:flags=bicubic'); + updateField('scale', '320:-1:flags=bicubic'); }} checked={values.quality === 'mid'} /> @@ -61,7 +129,7 @@ export default function VideoToGif({ onClick={() => { updateField('quality', 'high'); updateField('fps', '15'); - updateField('scale', 'scale=480:-1:flags=lanczos'); + updateField('scale', '480:-1:flags=lanczos'); }} checked={values.quality === 'high'} /> diff --git a/src/pages/tools/video/video-to-gif/service.ts b/src/pages/tools/video/video-to-gif/service.ts deleted file mode 100644 index dabc369..0000000 --- a/src/pages/tools/video/video-to-gif/service.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { InitialValuesType } from './types'; - -export function main(input: File, options: InitialValuesType): File { - return input; -} From 3633420229a9a425d6c08988b7bb655fd35b0d64 Mon Sep 17 00:00:00 2001 From: C043 Date: Mon, 9 Jun 2025 16:54:48 +0200 Subject: [PATCH 4/8] Adds ultra quality option --- src/pages/tools/video/video-to-gif/index.tsx | 16 +++++++++++++--- src/pages/tools/video/video-to-gif/types.ts | 2 ++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/pages/tools/video/video-to-gif/index.tsx b/src/pages/tools/video/video-to-gif/index.tsx index 858eed5..22764b2 100644 --- a/src/pages/tools/video/video-to-gif/index.tsx +++ b/src/pages/tools/video/video-to-gif/index.tsx @@ -5,7 +5,6 @@ import React, { useState } from 'react'; import ToolContent from '@components/ToolContent'; import { ToolComponentProps } from '@tools/defineTool'; import { GetGroupsType } from '@components/options/ToolOptions'; -import { main } from './service'; import { InitialValuesType } from './types'; import ToolVideoInput from '@components/input/ToolVideoInput'; import ToolFileResult from '@components/result/ToolFileResult'; @@ -16,7 +15,9 @@ import { fetchFile } from '@ffmpeg/util'; const initialValues: InitialValuesType = { quality: 'mid', fps: '10', - scale: '320:-1:flags=bicubic' + scale: '320:-1:flags=bicubic', + starting: '0', + duration: '' }; export default function VideoToGif({ @@ -29,7 +30,7 @@ export default function VideoToGif({ const compute = (values: InitialValuesType, input: File | null) => { if (!input) return; - const { fps, scale } = values; + const { fps, scale, starting, duration } = values; let ffmpeg: FFmpeg | null = null; let ffmpegLoaded = false; @@ -133,6 +134,15 @@ export default function VideoToGif({ }} checked={values.quality === 'high'} /> + { + updateField('quality', 'ultra'); + updateField('fps', '15'); + updateField('scale', '640:-1:flags=lanczos'); + }} + checked={values.quality === 'ultra'} + /> ) } diff --git a/src/pages/tools/video/video-to-gif/types.ts b/src/pages/tools/video/video-to-gif/types.ts index b56264e..5a754d8 100644 --- a/src/pages/tools/video/video-to-gif/types.ts +++ b/src/pages/tools/video/video-to-gif/types.ts @@ -2,4 +2,6 @@ export type InitialValuesType = { quality: string; fps: string; scale: string; + starting: string; + duration: string; }; From 899797cd48326fed33bed1bd65d234e53f6c33a0 Mon Sep 17 00:00:00 2001 From: C043 Date: Mon, 9 Jun 2025 16:55:59 +0200 Subject: [PATCH 5/8] Removed linting comments --- src/pages/tools/video/video-to-gif/index.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/pages/tools/video/video-to-gif/index.tsx b/src/pages/tools/video/video-to-gif/index.tsx index 22764b2..93130fa 100644 --- a/src/pages/tools/video/video-to-gif/index.tsx +++ b/src/pages/tools/video/video-to-gif/index.tsx @@ -1,5 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -/* eslint-disable prettier/prettier */ import { Box } from '@mui/material'; import React, { useState } from 'react'; import ToolContent from '@components/ToolContent'; From 9b4d739f19c52e89b828e9bd0b5c8f11d3b09fd9 Mon Sep 17 00:00:00 2001 From: C043 Date: Mon, 9 Jun 2025 16:57:47 +0200 Subject: [PATCH 6/8] Removed unused configuration properties --- src/pages/tools/video/video-to-gif/index.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/pages/tools/video/video-to-gif/index.tsx b/src/pages/tools/video/video-to-gif/index.tsx index 93130fa..a8eba09 100644 --- a/src/pages/tools/video/video-to-gif/index.tsx +++ b/src/pages/tools/video/video-to-gif/index.tsx @@ -13,9 +13,7 @@ import { fetchFile } from '@ffmpeg/util'; const initialValues: InitialValuesType = { quality: 'mid', fps: '10', - scale: '320:-1:flags=bicubic', - starting: '0', - duration: '' + scale: '320:-1:flags=bicubic' }; export default function VideoToGif({ @@ -28,7 +26,7 @@ export default function VideoToGif({ const compute = (values: InitialValuesType, input: File | null) => { if (!input) return; - const { fps, scale, starting, duration } = values; + const { fps, scale } = values; let ffmpeg: FFmpeg | null = null; let ffmpegLoaded = false; From a1cfccdbbaf3524d84bf4e6968d0b9031fe18a3d Mon Sep 17 00:00:00 2001 From: C043 Date: Mon, 9 Jun 2025 16:58:43 +0200 Subject: [PATCH 7/8] Removed the properties from the type file too --- src/pages/tools/video/video-to-gif/types.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/pages/tools/video/video-to-gif/types.ts b/src/pages/tools/video/video-to-gif/types.ts index 5a754d8..b56264e 100644 --- a/src/pages/tools/video/video-to-gif/types.ts +++ b/src/pages/tools/video/video-to-gif/types.ts @@ -2,6 +2,4 @@ export type InitialValuesType = { quality: string; fps: string; scale: string; - starting: string; - duration: string; }; From b8c6e9fdd11c35b4021f4965ea56cde68d52715e Mon Sep 17 00:00:00 2001 From: "Ibrahima G. Coulibaly" Date: Mon, 7 Jul 2025 02:02:27 +0100 Subject: [PATCH 8/8] fix: tsc --- src/pages/tools/video/index.ts | 10 +++------- src/pages/tools/video/video-to-gif/types.ts | 2 +- .../video/video-to-gif/video-to-gif.service.test.ts | 6 ------ 3 files changed, 4 insertions(+), 14 deletions(-) delete mode 100644 src/pages/tools/video/video-to-gif/video-to-gif.service.test.ts diff --git a/src/pages/tools/video/index.ts b/src/pages/tools/video/index.ts index 4a7c3b8..7cbf9f9 100644 --- a/src/pages/tools/video/index.ts +++ b/src/pages/tools/video/index.ts @@ -1,16 +1,12 @@ -import { tool as videoVideoToGif } from './video-to-gif/meta'; -import { tool as videoChangeSpeed } from './change-speed/meta'; -import { tool as videoFlip } from './flip/meta'; -import { rotate } from '../string/rotate/service'; +import { tool as videoToGif } from './video-to-gif/meta'; +import { tool as changeSpeed } from './change-speed/meta'; +import { tool as flipVideo } from './flip/meta'; import { gifTools } from './gif'; import { tool as trimVideo } from './trim/meta'; 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 cropVideo } from './crop-video/meta'; -import { tool as changeSpeed } from './change-speed/meta'; -import { tool as videoToGif } from './video-to-gif/meta'; export const videoTools = [ ...gifTools, diff --git a/src/pages/tools/video/video-to-gif/types.ts b/src/pages/tools/video/video-to-gif/types.ts index b56264e..f07ab51 100644 --- a/src/pages/tools/video/video-to-gif/types.ts +++ b/src/pages/tools/video/video-to-gif/types.ts @@ -1,5 +1,5 @@ export type InitialValuesType = { - quality: string; + quality: 'mid' | 'high' | 'low' | 'ultra'; fps: string; scale: string; }; diff --git a/src/pages/tools/video/video-to-gif/video-to-gif.service.test.ts b/src/pages/tools/video/video-to-gif/video-to-gif.service.test.ts deleted file mode 100644 index 50e3c16..0000000 --- a/src/pages/tools/video/video-to-gif/video-to-gif.service.test.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { expect, describe, it } from 'vitest'; -// import { main } from './service'; -// -// describe('video-to-gif', () => { -// -// })