diff --git a/src/components/VideoEditor.tsx b/src/components/VideoEditor.tsx index 3ffb6a67..443770da 100644 --- a/src/components/VideoEditor.tsx +++ b/src/components/VideoEditor.tsx @@ -250,6 +250,30 @@ export default function VideoEditor() { className="w-full" /> + {/* Sharpness */} +
+
+ + +
+ updateRecipe({ sharpness: Number(e.target.value) })} + aria-label="Adjust sharpness" + className="w-full" + /> +
} title="Output format" delay={190}> diff --git a/src/hooks/useVideoEditor.ts b/src/hooks/useVideoEditor.ts index 2f94bcbf..7c9a066d 100644 --- a/src/hooks/useVideoEditor.ts +++ b/src/hooks/useVideoEditor.ts @@ -106,6 +106,10 @@ function validateRecipe(recipe: EditRecipe, duration: number ): string | null { recipe.saturation < 0 || recipe.saturation > 3, "Saturation must be between 0 and 3.", ], + [ + recipe.sharpness < 0 || recipe.sharpness > 3, + "Sharpness must be between 0 and 3.", + ], ]; return ( diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 976307a4..f0e618b9 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -19,5 +19,6 @@ export const DEFAULT_RECIPE: EditRecipe = { saturation: 1, stabilization: false, soundOnCompletion: false, + sharpness: 0, normalizeAudio: false, }; \ No newline at end of file diff --git a/src/lib/ffmpeg.ts b/src/lib/ffmpeg.ts index 4f215075..32d4acb3 100644 --- a/src/lib/ffmpeg.ts +++ b/src/lib/ffmpeg.ts @@ -120,6 +120,9 @@ function buildVideoFilter(recipe: EditRecipe, targetW: number, targetH: number): `crop=${targetW}:${targetH}` ); } + if (recipe.sharpness !== 0) { + filters.push(`unsharp=5:5:${recipe.sharpness}:5:5:0.0`); + } if (recipe.speed !== 1) { const pts = (1 / recipe.speed).toFixed(4); diff --git a/src/lib/types.ts b/src/lib/types.ts index 521a82c2..8054cf04 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -16,6 +16,7 @@ export interface EditRecipe { contrast: number; saturation: number; soundOnCompletion: boolean; + sharpness: number; } export type OverlayPosition = @@ -82,6 +83,7 @@ export const DEFAULT_RECIPE: EditRecipe = { contrast: 0, saturation: 0, soundOnCompletion: false, + sharpness: 1, }; export const MAX_FILE_SIZE =