diff --git a/src/components/VideoEditor.tsx b/src/components/VideoEditor.tsx
index db5c3793..dd16b41f 100644
--- a/src/components/VideoEditor.tsx
+++ b/src/components/VideoEditor.tsx
@@ -282,7 +282,35 @@ export default function VideoEditor() {
/>
} title="Rotate" delay={100}>
-
+
+
+ {/* Reverse video toggle */}
+
+
+
+
diff --git a/src/hooks/useVideoEditor.ts b/src/hooks/useVideoEditor.ts
index d894ed07..67758543 100644
--- a/src/hooks/useVideoEditor.ts
+++ b/src/hooks/useVideoEditor.ts
@@ -397,6 +397,13 @@ export function useVideoEditor() {
setStatus("error");
return;
}
+ const REVERSE_SIZE_LIMIT = 100 * 1024 * 1024; // 100MB
+ if (recipe.reverse && file.size > REVERSE_SIZE_LIMIT) {
+ const confirmed = window.confirm(
+ "⚠️ Warning: Reversing files larger than 100MB loads the entire video into memory and may be slow or crash your browser. Continue anyway?"
+ );
+ if (!confirmed) return;
+ }
const abortController = new AbortController();
exportAbortControllerRef.current = abortController;
diff --git a/src/lib/constants.ts b/src/lib/constants.ts
index fddfe9f2..7221d202 100644
--- a/src/lib/constants.ts
+++ b/src/lib/constants.ts
@@ -22,4 +22,5 @@ export const DEFAULT_RECIPE: EditRecipe = {
soundOnCompletion: false,
normalizeAudio: false,
version: RECIPE_VERSION,
+ reverse: false,
};
\ No newline at end of file
diff --git a/src/lib/ffmpeg.ts b/src/lib/ffmpeg.ts
index 4f215075..89971bf0 100644
--- a/src/lib/ffmpeg.ts
+++ b/src/lib/ffmpeg.ts
@@ -128,11 +128,17 @@ function buildVideoFilter(recipe: EditRecipe, targetW: number, targetH: number):
filters.push(
`eq=brightness=${recipe.brightness}:contrast=${recipe.contrast}:saturation=${recipe.saturation}`
);
+ // ADD THIS before the return:
+ if (recipe.reverse) {
+ filters.push("reverse");
+ }
+
return filters.join(",");
}
- export function buildAudioFilter(speed: number, normalizeAudio: boolean): string {
+ export function buildAudioFilter(speed: number, normalizeAudio: boolean, reverse = false): string {
const filters: string[] = [];
+ if (reverse) filters.push("areverse");
let remaining = speed;
while (remaining < 0.5) {
@@ -177,7 +183,7 @@ function buildArguments(
): string[] {
const vf = buildVideoFilter(recipe, targetW, targetH);
const audioTrim = hasOriginalAudio ? buildAudioTrimFilter(recipe) : "";
-const audioSpeed = hasOriginalAudio ? buildAudioFilter(recipe.speed, recipe.normalizeAudio ?? false) : "";
+const audioSpeed = hasOriginalAudio ? buildAudioFilter(recipe.speed, recipe.normalizeAudio ?? false, recipe.reverse) : "";
const afParts = [audioTrim, audioSpeed].filter(Boolean);
const af = afParts.join(",");
@@ -334,8 +340,7 @@ export async function exportVideo(
const vf = buildVideoFilter(recipe, targetW, targetH);
const audioTrim = buildAudioTrimFilter(recipe);
- const audioSpeed = buildAudioFilter(recipe.speed, recipe.normalizeAudio ?? false);
-
+ const audioSpeed = buildAudioFilter(recipe.speed, recipe.normalizeAudio ?? false, recipe.reverse);
const afParts = [audioTrim, audioSpeed].filter(Boolean);
const af = afParts.join(",");
const hasMusicTrack = !!(musicOptions?.file && recipe.keepAudio);
diff --git a/src/lib/types.ts b/src/lib/types.ts
index 91040259..c912e4ee 100644
--- a/src/lib/types.ts
+++ b/src/lib/types.ts
@@ -18,6 +18,7 @@ export interface EditRecipe {
contrast: number;
saturation: number;
soundOnCompletion: boolean;
+ reverse: boolean;
version: number;
}
@@ -81,6 +82,7 @@ export const DEFAULT_RECIPE: EditRecipe = {
quality: 23,
format: "mp4",
stabilization: false,
+ reverse: false,
brightness: 0,
contrast: 0,
saturation: 0,
@@ -116,6 +118,7 @@ export function isValidRecipe(value: unknown): value is EditRecipe {
if (typeof v.contrast !== "number" || !isFinite(v.contrast)) return false;
if (typeof v.saturation !== "number" || !isFinite(v.saturation)) return false;
if (typeof v.soundOnCompletion !== "boolean") return false;
+ if (typeof v.reverse !== "boolean") return false;
return true;
}
\ No newline at end of file