mirror of
https://github.com/iib0011/omni-tools.git
synced 2025-09-19 14:09:31 +02:00
Merge pull request #117 from nevolodia/flip-video
Flip video tool added.
This commit is contained in:
16
.idea/workspace.xml
generated
16
.idea/workspace.xml
generated
@@ -6,7 +6,8 @@
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="b30e2810-c4c1-4aad-b134-794e52cc1c7d" name="Changes" comment="fix: misc">
|
||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/pages/tools/pdf/split-pdf/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/pdf/split-pdf/index.tsx" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/components/input/BaseFileInput.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/input/BaseFileInput.tsx" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/pages/tools/pdf/protect-pdf/service.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/pdf/protect-pdf/service.ts" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
@@ -23,7 +24,7 @@
|
||||
<option name="PUSH_AUTO_UPDATE" value="true" />
|
||||
<option name="RECENT_BRANCH_BY_REPOSITORY">
|
||||
<map>
|
||||
<entry key="$PROJECT_DIR$" value="main" />
|
||||
<entry key="$PROJECT_DIR$" value="fork/rohit267/feat/pdf-merge" />
|
||||
</map>
|
||||
</option>
|
||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||
@@ -134,6 +135,13 @@
|
||||
"number": 102
|
||||
},
|
||||
"lastSeen": 1747171977348
|
||||
},
|
||||
{
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6XPua_",
|
||||
"number": 117
|
||||
},
|
||||
"lastSeen": 1747929835864
|
||||
}
|
||||
]
|
||||
}]]></component>
|
||||
@@ -189,7 +197,7 @@
|
||||
"Vitest.replaceText function (regexp mode).should return the original text when passed an invalid regexp.executor": "Run",
|
||||
"Vitest.replaceText function.executor": "Run",
|
||||
"Vitest.timeBetweenDates.executor": "Run",
|
||||
"git-widget-placeholder": "#102 on fork/rohit267/feat/pdf-merge",
|
||||
"git-widget-placeholder": "#117 on fork/nevolodia/flip-video",
|
||||
"ignore.virus.scanning.warn.message": "true",
|
||||
"kotlin-language-version-configured": "true",
|
||||
"last_opened_file_path": "C:/Users/Ibrahima/IdeaProjects/omni-tools/src",
|
||||
@@ -423,6 +431,8 @@
|
||||
<workItem from="1745775228478" duration="1221000" />
|
||||
<workItem from="1745835676024" duration="68000" />
|
||||
<workItem from="1747171958176" duration="1105000" />
|
||||
<workItem from="1747217211469" duration="4000" />
|
||||
<workItem from="1747929815472" duration="843000" />
|
||||
</task>
|
||||
<task id="LOCAL-00147" summary="chore: update meta">
|
||||
<option name="closed" value="true" />
|
||||
|
@@ -33,17 +33,14 @@ export default function BaseFileInput({
|
||||
const { showSnackBar } = useContext(CustomSnackBarContext);
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
if (isArray(value)) {
|
||||
const objectUrl = createObjectURL(value[0]);
|
||||
if (value) {
|
||||
try {
|
||||
const objectUrl = createObjectURL(value);
|
||||
setPreview(objectUrl);
|
||||
|
||||
return () => revokeObjectURL(objectUrl);
|
||||
} else {
|
||||
setPreview(null);
|
||||
} catch (error) {
|
||||
console.error('Error previewing file:', error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error previewing file:', error);
|
||||
}
|
||||
}, [value]);
|
||||
|
||||
|
@@ -37,7 +37,6 @@ export async function protectPdf(
|
||||
password: options.password
|
||||
};
|
||||
const protectedFileUrl: string = await protectWithGhostScript(dataObject);
|
||||
console.log('protected', protectedFileUrl);
|
||||
return await loadPDFData(
|
||||
protectedFileUrl,
|
||||
pdfFile.name.replace('.pdf', '-protected.pdf')
|
||||
|
113
src/pages/tools/video/flip/index.tsx
Normal file
113
src/pages/tools/video/flip/index.tsx
Normal file
@@ -0,0 +1,113 @@
|
||||
import { Box } from '@mui/material';
|
||||
import { useCallback, useState } from 'react';
|
||||
import * as Yup from 'yup';
|
||||
import ToolFileResult from '@components/result/ToolFileResult';
|
||||
import ToolContent from '@components/ToolContent';
|
||||
import { ToolComponentProps } from '@tools/defineTool';
|
||||
import { GetGroupsType } from '@components/options/ToolOptions';
|
||||
import { debounce } from 'lodash';
|
||||
import ToolVideoInput from '@components/input/ToolVideoInput';
|
||||
import { flipVideo } from './service';
|
||||
import { FlipOrientation, InitialValuesType } from './types';
|
||||
import SimpleRadio from '@components/options/SimpleRadio';
|
||||
|
||||
export const initialValues: InitialValuesType = {
|
||||
orientation: 'horizontal'
|
||||
};
|
||||
|
||||
export const validationSchema = Yup.object({
|
||||
orientation: Yup.string()
|
||||
.oneOf(
|
||||
['horizontal', 'vertical'],
|
||||
'Orientation must be horizontal or vertical'
|
||||
)
|
||||
.required('Orientation is required')
|
||||
});
|
||||
|
||||
const orientationOptions: { value: FlipOrientation; label: string }[] = [
|
||||
{ value: 'horizontal', label: 'Horizontal (Mirror)' },
|
||||
{ value: 'vertical', label: 'Vertical (Upside Down)' }
|
||||
];
|
||||
|
||||
export default function FlipVideo({ title }: ToolComponentProps) {
|
||||
const [input, setInput] = useState<File | null>(null);
|
||||
const [result, setResult] = useState<File | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const compute = async (
|
||||
optionsValues: InitialValuesType,
|
||||
input: File | null
|
||||
) => {
|
||||
if (!input) return;
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
const flippedFile = await flipVideo(input, optionsValues.orientation);
|
||||
setResult(flippedFile);
|
||||
} catch (error) {
|
||||
console.error('Error flipping video:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const debouncedCompute = useCallback(debounce(compute, 1000), []);
|
||||
|
||||
const getGroups: GetGroupsType<InitialValuesType> = ({
|
||||
values,
|
||||
updateField
|
||||
}) => [
|
||||
{
|
||||
title: 'Orientation',
|
||||
component: (
|
||||
<Box>
|
||||
{orientationOptions.map((orientationOption) => (
|
||||
<SimpleRadio
|
||||
key={orientationOption.value}
|
||||
title={orientationOption.label}
|
||||
checked={values.orientation === orientationOption.value}
|
||||
onClick={() => {
|
||||
updateField('orientation', orientationOption.value);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<ToolContent
|
||||
title={title}
|
||||
input={input}
|
||||
inputComponent={
|
||||
<ToolVideoInput
|
||||
value={input}
|
||||
onChange={setInput}
|
||||
title={'Input Video'}
|
||||
/>
|
||||
}
|
||||
resultComponent={
|
||||
loading ? (
|
||||
<ToolFileResult
|
||||
title={'Flipping Video'}
|
||||
value={null}
|
||||
loading={true}
|
||||
extension={''}
|
||||
/>
|
||||
) : (
|
||||
<ToolFileResult
|
||||
title={'Flipped Video'}
|
||||
value={result}
|
||||
extension={'mp4'}
|
||||
/>
|
||||
)
|
||||
}
|
||||
initialValues={initialValues}
|
||||
getGroups={getGroups}
|
||||
compute={debouncedCompute}
|
||||
setInput={setInput}
|
||||
validationSchema={validationSchema}
|
||||
/>
|
||||
);
|
||||
}
|
15
src/pages/tools/video/flip/meta.ts
Normal file
15
src/pages/tools/video/flip/meta.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { defineTool } from '@tools/defineTool';
|
||||
import { lazy } from 'react';
|
||||
|
||||
export const tool = defineTool('video', {
|
||||
name: 'Flip Video',
|
||||
path: 'flip',
|
||||
icon: 'mdi:flip-horizontal',
|
||||
description:
|
||||
'This online utility allows you to flip videos horizontally or vertically. You can preview the flipped video before processing. Supports common video formats like MP4, WebM, and OGG.',
|
||||
shortDescription: 'Flip videos horizontally or vertically',
|
||||
keywords: ['flip', 'video', 'mirror', 'edit', 'horizontal', 'vertical'],
|
||||
longDescription:
|
||||
'Easily flip your videos horizontally (mirror) or vertically (upside down) with this simple online tool.',
|
||||
component: lazy(() => import('./index'))
|
||||
});
|
43
src/pages/tools/video/flip/service.ts
Normal file
43
src/pages/tools/video/flip/service.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { FFmpeg } from '@ffmpeg/ffmpeg';
|
||||
import { fetchFile } from '@ffmpeg/util';
|
||||
import { FlipOrientation } from './types';
|
||||
|
||||
const ffmpeg = new FFmpeg();
|
||||
|
||||
export async function flipVideo(
|
||||
input: File,
|
||||
orientation: FlipOrientation
|
||||
): 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 flipMap: Record<FlipOrientation, string> = {
|
||||
horizontal: 'hflip',
|
||||
vertical: 'vflip'
|
||||
};
|
||||
const flipFilter = flipMap[orientation];
|
||||
|
||||
const args = ['-i', inputName];
|
||||
if (flipFilter) {
|
||||
args.push('-vf', flipFilter);
|
||||
}
|
||||
|
||||
args.push('-c:v', 'libx264', '-preset', 'ultrafast', outputName);
|
||||
|
||||
await ffmpeg.exec(args);
|
||||
|
||||
const flippedData = await ffmpeg.readFile(outputName);
|
||||
return new File(
|
||||
[new Blob([flippedData], { type: 'video/mp4' })],
|
||||
`${input.name.replace(/\.[^/.]+$/, '')}_flipped.mp4`,
|
||||
{ type: 'video/mp4' }
|
||||
);
|
||||
}
|
5
src/pages/tools/video/flip/types.ts
Normal file
5
src/pages/tools/video/flip/types.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export type FlipOrientation = 'horizontal' | 'vertical';
|
||||
|
||||
export type InitialValuesType = {
|
||||
orientation: FlipOrientation;
|
||||
};
|
@@ -1,14 +1,17 @@
|
||||
import { tool as videoFlip } from './flip/meta';
|
||||
import { rotate } from '../string/rotate/service';
|
||||
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';
|
||||
|
||||
export const videoTools = [
|
||||
...gifTools,
|
||||
trimVideo,
|
||||
rotateVideo,
|
||||
compressVideo,
|
||||
loopVideo
|
||||
loopVideo,
|
||||
flipVideo
|
||||
];
|
||||
|
Reference in New Issue
Block a user