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; -}