From 0ce344b1033b0f139f6d5287c367cff2430de929 Mon Sep 17 00:00:00 2001
From: webadderall <131426131+webadderall@users.noreply.github.com>
Date: Mon, 16 Mar 2026 21:48:53 +1100
Subject: [PATCH] feat: export quality UI and source-relative scaling
- Add 4th 'Original' quality tier to export UI
- Change non-source export to use source-relative scaling (0.6x, 0.75x, 0.9x)
- Update i18n for new quality label
- Accept 'high' in project normalization
Fixes blurry export and makes quality more intuitive
---
src/components/video-editor/SettingsPanel.tsx | 13 +++++++++++--
src/components/video-editor/VideoEditor.tsx | 19 ++++++++++++++-----
.../video-editor/projectPersistence.ts | 8 +++++++-
src/i18n/locales/en/settings.json | 3 ++-
src/i18n/locales/es/settings.json | 3 ++-
src/i18n/locales/zh-CN/settings.json | 3 ++-
src/lib/exporter/types.ts | 2 +-
7 files changed, 39 insertions(+), 12 deletions(-)
diff --git a/src/components/video-editor/SettingsPanel.tsx b/src/components/video-editor/SettingsPanel.tsx
index 1cbf39b7..c0498cdc 100644
--- a/src/components/video-editor/SettingsPanel.tsx
+++ b/src/components/video-editor/SettingsPanel.tsx
@@ -813,7 +813,7 @@ export function SettingsPanel({
{exportFormat === 'mp4' && (
-
+
+
)}
diff --git a/src/components/video-editor/VideoEditor.tsx b/src/components/video-editor/VideoEditor.tsx
index 97c3a3bf..e61af90d 100644
--- a/src/components/video-editor/VideoEditor.tsx
+++ b/src/components/video-editor/VideoEditor.tsx
@@ -1525,12 +1525,21 @@ export default function VideoEditor() {
bitrate = 80_000_000;
}
} else {
- // Use quality-based target resolution
- const targetHeight = quality === 'medium' ? 720 : 1080;
+ // Use source-relative quality scaling.
+ // "source" is handled above; this branch maps the remaining tiers.
+ const qualityScale =
+ quality === 'medium' ? 0.6 : quality === 'good' ? 0.75 : 0.9;
+ const maxWidth = Math.max(2, Math.floor((sourceWidth * qualityScale) / 2) * 2);
+ const maxHeight = Math.max(2, Math.floor((sourceHeight * qualityScale) / 2) * 2);
+ const maxAspect = maxWidth / maxHeight;
- // Calculate dimensions maintaining aspect ratio
- exportHeight = Math.floor(targetHeight / 2) * 2;
- exportWidth = Math.floor((exportHeight * aspectRatioValue) / 2) * 2;
+ if (aspectRatioValue >= maxAspect) {
+ exportWidth = maxWidth;
+ exportHeight = Math.max(2, Math.floor((exportWidth / aspectRatioValue) / 2) * 2);
+ } else {
+ exportHeight = maxHeight;
+ exportWidth = Math.max(2, Math.floor((exportHeight * aspectRatioValue) / 2) * 2);
+ }
// Adjust bitrate for lower resolutions
const totalPixels = exportWidth * exportHeight;
diff --git a/src/components/video-editor/projectPersistence.ts b/src/components/video-editor/projectPersistence.ts
index 0985fc72..af4e1aff 100644
--- a/src/components/video-editor/projectPersistence.ts
+++ b/src/components/video-editor/projectPersistence.ts
@@ -358,7 +358,13 @@ export function normalizeProjectEditor(editor: Partial
): Pro
(validAspectRatios.has(editor.aspectRatio as AspectRatio) || isCustomAspectRatio(editor.aspectRatio))
? (editor.aspectRatio as AspectRatio)
: "16:9",
- exportQuality: editor.exportQuality === "medium" || editor.exportQuality === "source" ? editor.exportQuality : "good",
+ exportQuality:
+ editor.exportQuality === "medium" ||
+ editor.exportQuality === "good" ||
+ editor.exportQuality === "high" ||
+ editor.exportQuality === "source"
+ ? editor.exportQuality
+ : "good",
exportFormat: editor.exportFormat === "gif" ? "gif" : "mp4",
gifFrameRate:
editor.gifFrameRate === 15 ||
diff --git a/src/i18n/locales/en/settings.json b/src/i18n/locales/en/settings.json
index 2405ce5f..2ffb67f0 100644
--- a/src/i18n/locales/en/settings.json
+++ b/src/i18n/locales/en/settings.json
@@ -47,7 +47,8 @@
"quality": {
"low": "Low",
"medium": "Medium",
- "high": "High"
+ "high": "High",
+ "original": "Original"
},
"loop": "Loop",
"outputDimensions": "Output: {{dimensions}}px",
diff --git a/src/i18n/locales/es/settings.json b/src/i18n/locales/es/settings.json
index 7b3a1379..335bb00d 100644
--- a/src/i18n/locales/es/settings.json
+++ b/src/i18n/locales/es/settings.json
@@ -47,7 +47,8 @@
"quality": {
"low": "Baja",
"medium": "Media",
- "high": "Alta"
+ "high": "Alta",
+ "original": "Original"
},
"loop": "Bucle",
"outputDimensions": "Salida: {{dimensions}}px",
diff --git a/src/i18n/locales/zh-CN/settings.json b/src/i18n/locales/zh-CN/settings.json
index 46106e60..5e2ef5bb 100644
--- a/src/i18n/locales/zh-CN/settings.json
+++ b/src/i18n/locales/zh-CN/settings.json
@@ -47,7 +47,8 @@
"quality": {
"low": "低",
"medium": "中",
- "high": "高"
+ "high": "高",
+ "original": "原始"
},
"loop": "循环",
"outputDimensions": "输出:{{dimensions}}px",
diff --git a/src/lib/exporter/types.ts b/src/lib/exporter/types.ts
index fa0d4eaa..812d850d 100644
--- a/src/lib/exporter/types.ts
+++ b/src/lib/exporter/types.ts
@@ -27,7 +27,7 @@ export interface VideoFrameData {
duration: number; // in microseconds
}
-export type ExportQuality = 'medium' | 'good' | 'source';
+export type ExportQuality = 'medium' | 'good' | 'high' | 'source';
// GIF Export Types
export type ExportFormat = 'mp4' | 'gif';