Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/components/ai-edition/ExportDialog.params.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,14 @@ describe("ExportDialog format settings", () => {
vi.clearAllMocks();
});

it("opens on MP4, 1080p, 60 fps, H.264", async () => {
it("opens on MP4, 1080p, 60 fps, H.264, with no codec choice to make", async () => {
renderDialog();
// No picker, on purpose: H.265 is software-only on Linux, slower than software on
// the measured Macs, and unreadable in half the players. Every export is H.264.
expect(screen.queryByRole("button", { name: "H.265" })).toBeNull();
expect(screen.queryByRole("button", { name: "H.264" })).toBeNull();
// And no idle hint plate: the format is the first control on screen and already picked.
expect(screen.queryByText(/pick a format/i)).toBeNull();
expect(await exportMp4()).toMatchObject({ width: 1920, height: 1080, fps: 60, codec: "h264" });
});

Expand Down
101 changes: 23 additions & 78 deletions src/components/ai-edition/ExportDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import {
type ExportFormat,
type ExportProgress,
type ExportQuality,
type ExportVideoCodec,
GIF_FRAME_RATES,
GIF_SIZE_PRESETS,
type GifFrameRate,
Expand Down Expand Up @@ -162,7 +161,6 @@ export function ExportDialog({ open, onClose, document }: ExportDialogProps) {
const [format, setFormat] = useState<ExportFormat>("mp4");
const [quality, setQuality] = useState<ExportQuality>("good");
const [fps, setFps] = useState<24 | 30 | 60>(60);
const [codec, setCodec] = useState<ExportVideoCodec>("h264");
const [gifFrameRate, setGifFrameRate] = useState<GifFrameRate>(15);
const [gifSize, setGifSize] = useState<GifSizePreset>("medium");
const [gifLoop, setGifLoop] = useState(true);
Expand Down Expand Up @@ -434,7 +432,10 @@ export function ExportDialog({ open, onClose, document }: ExportDialogProps) {
width: outDims?.width,
height: outDims?.height,
fps,
codec,
// H.264 only. The native pipeline still encodes H.265, but nothing
// offers it: it is software-only on Linux, slower than software on the
// measured Macs, and the files half the players cannot open.
codec: "h264",
bitrate: outDims?.bitrate,
});
if (activeExport.current !== job) return;
Expand Down Expand Up @@ -572,65 +573,20 @@ export function ExportDialog({ open, onClose, document }: ExportDialogProps) {
</button>
))}
</div>
<div
style={{
display: "grid",
gridTemplateColumns: "1fr 1fr",
gap: 12,
marginTop: 12,
}}
>
<div>
<div className={styles.groupLabel}>{t("exportDialog.frameRate")}</div>
<div style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 6 }}>
{([24, 30, 60] as const).map((r) => (
<button
type="button"
key={r}
disabled={isBusy}
onClick={() => setFps(r)}
style={segStyle(fps === r)}
>
{r}
</button>
))}
</div>
</div>
<div>
<div className={styles.groupLabel}>{t("exportDialog.codec")}</div>
<div
style={{
display: "grid",
gridTemplateColumns: "repeat(2, 1fr)",
gap: 6,
}}
>
{(
[
["h264", "H.264"],
["h265", "H.265"],
// VP9 has no AMF hardware encoder on this GPU — the native pipeline
// (the only MP4 export path now) rejects it outright (tested: a
// software libvpx-vp9 fallback worked but was too slow to ship).
// Hidden here rather than left selectable-then-erroring.
] as Array<[ExportVideoCodec, string]>
).map(([value, label]) => (
<button
type="button"
key={value}
disabled={isBusy}
onClick={() => setCodec(value)}
style={segStyle(codec === value)}
title={
value === "h264"
? t("exportDialog.codecBestCompatibility")
: t("exportDialog.codecMaySupportVary")
}
>
{label}
</button>
))}
</div>
<div style={{ marginTop: 12 }}>
<div className={styles.groupLabel}>{t("exportDialog.frameRate")}</div>
<div style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 6 }}>
{([24, 30, 60] as const).map((r) => (
<button
type="button"
key={r}
disabled={isBusy}
onClick={() => setFps(r)}
style={segStyle(fps === r)}
>
{r}
</button>
))}
</div>
</div>
</section>
Expand Down Expand Up @@ -838,22 +794,11 @@ function ProgressBlock({
// rather than in the orphaned `settings.support` block because the main process only bundles
// `common` and `dialogs`, and one label split across two namespaces is one label that drifts.
const tCommon = useScopedT("common");
if (phase === "idle" || phase === "configuring") {
return (
<div
style={{
padding: "16px",
borderRadius: 12,
background: "color-mix(in oklab, var(--fg) 5%, transparent)",
color: "var(--muted)",
font: "500 13px var(--font-body)",
textAlign: "center",
}}
>
{t("exportDialog.pickFormatAndExport")}
</div>
);
}
// Nothing to say before an export: the format is the first control on screen and
// already picked. The old "Pick a format and press Export to start" plate was written
// for the UI that hid the format toggle under Advanced, and stayed up through the save
// picker where it was simply false.
if (phase === "idle" || phase === "configuring") return null;
if (phase === "done") {
return (
<div
Expand Down
4 changes: 0 additions & 4 deletions src/i18n/locales/ar/editor.json
Original file line number Diff line number Diff line change
Expand Up @@ -177,14 +177,10 @@
"quality": "الجودة",
"qualityMatchRecording": "مطابقة التسجيل",
"frameRate": "معدل الإطارات",
"codec": "الترميز",
"codecBestCompatibility": "أفضل توافق",
"codecMaySupportVary": "قد لا يكون مدعومًا من جميع مرمّزات النظام",
"size": "الحجم",
"loopGif": "تكرار GIF",
"loopOn": "التكرار مفعّل",
"loopOff": "التكرار معطّل",
"pickFormatAndExport": "اختر صيغة واضغط على تصدير للبدء.",
"savedTo": "تم الحفظ في",
"exportFailedGeneric": "فشل التصدير.",
"writingFile": "جارٍ كتابة الملف...",
Expand Down
4 changes: 0 additions & 4 deletions src/i18n/locales/cs/editor.json
Original file line number Diff line number Diff line change
Expand Up @@ -177,14 +177,10 @@
"quality": "Kvalita",
"qualityMatchRecording": "Odpovídat nahrávce",
"frameRate": "Snímkový kmitočet",
"codec": "Kodek",
"codecBestCompatibility": "Nejlepší kompatibilita",
"codecMaySupportVary": "Nemusí být podporováno každým systémovým enkodérem",
"size": "Velikost",
"loopGif": "Opakovat GIF",
"loopOn": "Opakování zapnuto",
"loopOff": "Opakování vypnuto",
"pickFormatAndExport": "Vyberte formát a stiskněte Exportovat pro zahájení.",
"savedTo": "Uloženo do",
"exportFailedGeneric": "Export selhal.",
"writingFile": "Zapisování souboru…",
Expand Down
4 changes: 0 additions & 4 deletions src/i18n/locales/de/editor.json
Original file line number Diff line number Diff line change
Expand Up @@ -177,14 +177,10 @@
"quality": "Qualität",
"qualityMatchRecording": "Wie die Aufnahme",
"frameRate": "Bildrate",
"codec": "Codec",
"codecBestCompatibility": "Beste Kompatibilität",
"codecMaySupportVary": "Wird möglicherweise nicht von jedem System-Encoder unterstützt",
"size": "Größe",
"loopGif": "GIF wiederholen",
"loopOn": "Wiederholung an",
"loopOff": "Wiederholung aus",
"pickFormatAndExport": "Wähle ein Format und drücke auf Exportieren, um zu starten.",
"savedTo": "Gespeichert unter",
"exportFailedGeneric": "Export fehlgeschlagen.",
"writingFile": "Datei wird geschrieben…",
Expand Down
4 changes: 0 additions & 4 deletions src/i18n/locales/en/editor.json
Original file line number Diff line number Diff line change
Expand Up @@ -177,14 +177,10 @@
"quality": "Quality",
"qualityMatchRecording": "Match recording",
"frameRate": "Frame rate",
"codec": "Codec",
"codecBestCompatibility": "Best compatibility",
"codecMaySupportVary": "May not be supported by every system encoder",
"size": "Size",
"loopGif": "Loop GIF",
"loopOn": "Loop on",
"loopOff": "Loop off",
"pickFormatAndExport": "Pick a format and press Export to start.",
"savedTo": "Saved to",
"exportFailedGeneric": "Export failed.",
"writingFile": "Writing file…",
Expand Down
4 changes: 0 additions & 4 deletions src/i18n/locales/es/editor.json
Original file line number Diff line number Diff line change
Expand Up @@ -177,14 +177,10 @@
"quality": "Calidad",
"qualityMatchRecording": "Igual que la grabación",
"frameRate": "Fotogramas por segundo",
"codec": "Códec",
"codecBestCompatibility": "Mejor compatibilidad",
"codecMaySupportVary": "Puede que no sea compatible con todos los codificadores del sistema",
"size": "Tamaño",
"loopGif": "Bucle de GIF",
"loopOn": "Bucle activado",
"loopOff": "Bucle desactivado",
"pickFormatAndExport": "Elige un formato y pulsa Exportar para empezar.",
"savedTo": "Guardado en",
"exportFailedGeneric": "Error al exportar.",
"writingFile": "Escribiendo archivo…",
Expand Down
4 changes: 0 additions & 4 deletions src/i18n/locales/fr/editor.json
Original file line number Diff line number Diff line change
Expand Up @@ -177,14 +177,10 @@
"quality": "Qualité",
"qualityMatchRecording": "Comme l'enregistrement",
"frameRate": "Fréquence d'images",
"codec": "Codec",
"codecBestCompatibility": "Meilleure compatibilité",
"codecMaySupportVary": "Peut ne pas être pris en charge par tous les encodeurs système",
"size": "Taille",
"loopGif": "Boucle GIF",
"loopOn": "Boucle activée",
"loopOff": "Boucle désactivée",
"pickFormatAndExport": "Choisissez un format et cliquez sur Exporter pour commencer.",
"savedTo": "Enregistré dans",
"exportFailedGeneric": "Échec de l'export.",
"writingFile": "Écriture du fichier…",
Expand Down
4 changes: 0 additions & 4 deletions src/i18n/locales/it/editor.json
Original file line number Diff line number Diff line change
Expand Up @@ -177,14 +177,10 @@
"quality": "Qualità",
"qualityMatchRecording": "Come la registrazione",
"frameRate": "Frame rate",
"codec": "Codec",
"codecBestCompatibility": "Compatibilità migliore",
"codecMaySupportVary": "Potrebbe non essere supportato da tutti gli encoder di sistema",
"size": "Dimensione",
"loopGif": "Loop GIF",
"loopOn": "Loop attivo",
"loopOff": "Loop disattivo",
"pickFormatAndExport": "Scegli un formato e premi Esporta per iniziare.",
"savedTo": "Salvato in",
"exportFailedGeneric": "Esportazione non riuscita.",
"writingFile": "Scrittura del file…",
Expand Down
4 changes: 0 additions & 4 deletions src/i18n/locales/ja-JP/editor.json
Original file line number Diff line number Diff line change
Expand Up @@ -177,14 +177,10 @@
"quality": "画質",
"qualityMatchRecording": "録画と同じ",
"frameRate": "フレームレート",
"codec": "コーデック",
"codecBestCompatibility": "互換性が最も高い",
"codecMaySupportVary": "システムのエンコーダーによってはサポートされない場合があります",
"size": "サイズ",
"loopGif": "GIFをループ",
"loopOn": "ループ オン",
"loopOff": "ループ オフ",
"pickFormatAndExport": "形式を選択し、エクスポートを押して開始してください。",
"savedTo": "保存先",
"exportFailedGeneric": "エクスポートに失敗しました。",
"writingFile": "ファイルを書き込み中…",
Expand Down
4 changes: 0 additions & 4 deletions src/i18n/locales/ko-KR/editor.json
Original file line number Diff line number Diff line change
Expand Up @@ -177,14 +177,10 @@
"quality": "화질",
"qualityMatchRecording": "녹화와 동일",
"frameRate": "프레임 속도",
"codec": "코덱",
"codecBestCompatibility": "호환성이 가장 좋음",
"codecMaySupportVary": "시스템 인코더에 따라 지원되지 않을 수 있습니다",
"size": "크기",
"loopGif": "GIF 반복",
"loopOn": "반복 켜짐",
"loopOff": "반복 꺼짐",
"pickFormatAndExport": "형식을 선택하고 내보내기를 눌러 시작하세요.",
"savedTo": "저장 위치",
"exportFailedGeneric": "내보내기에 실패했습니다.",
"writingFile": "파일 쓰는 중…",
Expand Down
4 changes: 0 additions & 4 deletions src/i18n/locales/pt-BR/editor.json
Original file line number Diff line number Diff line change
Expand Up @@ -177,14 +177,10 @@
"quality": "Qualidade",
"qualityMatchRecording": "Igual à gravação",
"frameRate": "Taxa de quadros",
"codec": "Codec",
"codecBestCompatibility": "Melhor compatibilidade",
"codecMaySupportVary": "Pode não ser suportado por todos os codificadores do sistema",
"size": "Tamanho",
"loopGif": "Repetir GIF",
"loopOn": "Repetição ativada",
"loopOff": "Repetição desativada",
"pickFormatAndExport": "Escolha um formato e clique em Exportar para começar.",
"savedTo": "Salvo em",
"exportFailedGeneric": "Falha na exportação.",
"writingFile": "Gravando arquivo…",
Expand Down
4 changes: 0 additions & 4 deletions src/i18n/locales/ru/editor.json
Original file line number Diff line number Diff line change
Expand Up @@ -177,14 +177,10 @@
"quality": "Качество",
"qualityMatchRecording": "Как в записи",
"frameRate": "Частота кадров",
"codec": "Кодек",
"codecBestCompatibility": "Лучшая совместимость",
"codecMaySupportVary": "Может не поддерживаться некоторыми системными кодировщиками",
"size": "Размер",
"loopGif": "Зациклить GIF",
"loopOn": "Зацикливание включено",
"loopOff": "Зацикливание выключено",
"pickFormatAndExport": "Выберите формат и нажмите «Экспорт», чтобы начать.",
"savedTo": "Сохранено в",
"exportFailedGeneric": "Ошибка экспорта.",
"writingFile": "Запись файла…",
Expand Down
4 changes: 0 additions & 4 deletions src/i18n/locales/tr/editor.json
Original file line number Diff line number Diff line change
Expand Up @@ -177,14 +177,10 @@
"quality": "Kalite",
"qualityMatchRecording": "Kayıtla eşleştir",
"frameRate": "Kare hızı",
"codec": "Codec",
"codecBestCompatibility": "En iyi uyumluluk",
"codecMaySupportVary": "Her sistem kodlayıcısı tarafından desteklenmeyebilir",
"size": "Boyut",
"loopGif": "GIF'i döngüye al",
"loopOn": "Döngü açık",
"loopOff": "Döngü kapalı",
"pickFormatAndExport": "Bir format seçin ve başlamak için Dışa Aktar'a basın.",
"savedTo": "Kaydedildiği yer",
"exportFailedGeneric": "Dışa aktarma başarısız oldu.",
"writingFile": "Dosya yazılıyor…",
Expand Down
4 changes: 0 additions & 4 deletions src/i18n/locales/vi/editor.json
Original file line number Diff line number Diff line change
Expand Up @@ -177,14 +177,10 @@
"quality": "Chất lượng",
"qualityMatchRecording": "Khớp với bản ghi",
"frameRate": "Tốc độ khung hình",
"codec": "Codec",
"codecBestCompatibility": "Tương thích tốt nhất",
"codecMaySupportVary": "Có thể không được hỗ trợ bởi mọi bộ mã hóa hệ thống",
"size": "Kích thước",
"loopGif": "Lặp GIF",
"loopOn": "Lặp bật",
"loopOff": "Lặp tắt",
"pickFormatAndExport": "Chọn định dạng và nhấn Xuất để bắt đầu.",
"savedTo": "Đã lưu vào",
"exportFailedGeneric": "Xuất thất bại.",
"writingFile": "Đang ghi tệp…",
Expand Down
4 changes: 0 additions & 4 deletions src/i18n/locales/zh-CN/editor.json
Original file line number Diff line number Diff line change
Expand Up @@ -177,14 +177,10 @@
"quality": "画质",
"qualityMatchRecording": "与录制一致",
"frameRate": "帧率",
"codec": "编码格式",
"codecBestCompatibility": "兼容性最佳",
"codecMaySupportVary": "并非所有系统编码器都支持",
"size": "尺寸",
"loopGif": "循环播放 GIF",
"loopOn": "循环已开启",
"loopOff": "循环已关闭",
"pickFormatAndExport": "选择格式并点击导出以开始。",
"savedTo": "已保存至",
"exportFailedGeneric": "导出失败。",
"writingFile": "正在写入文件…",
Expand Down
4 changes: 0 additions & 4 deletions src/i18n/locales/zh-TW/editor.json
Original file line number Diff line number Diff line change
Expand Up @@ -177,14 +177,10 @@
"quality": "畫質",
"qualityMatchRecording": "與錄製內容相符",
"frameRate": "幀率",
"codec": "編碼格式",
"codecBestCompatibility": "相容性最佳",
"codecMaySupportVary": "並非所有系統編碼器都支援",
"size": "尺寸",
"loopGif": "循環播放 GIF",
"loopOn": "循環開啟",
"loopOff": "循環關閉",
"pickFormatAndExport": "選擇格式並按下匯出以開始。",
"savedTo": "已儲存至",
"exportFailedGeneric": "匯出失敗。",
"writingFile": "正在寫入檔案…",
Expand Down
2 changes: 1 addition & 1 deletion technical-documentation/architecture/editor-shell.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ of those are local React state, with the document itself read through
| **Floating inspector** | `src/components/ai-edition/v4/FloatingInspector.tsx` | Floating facet rail over the stage; the open panel either shows the `FacetBody` for the current facet (`effects` / `layout` / `audio` / `cursor` / `captions` / `transcript`) or, when a region is selected, a `SelectionPane` (`:434`) that edits the selected pill by id. The "pencil" rail button opens `EditClipModal` for crop + trim. |
| **Left chat column** (`ChatStripPanel`) | `src/components/ai-edition/LeftPanel.tsx` | Only mounted when `mode === "edit"` and `chatOpen` is true (`NewEditorShell.tsx` `:1191-1195`). Sends user messages to the LLM via IPC. Resize handle is `v4.chatResizeHandle`; width persists in `localStorage` as `os-editor-chat-width`. |
| **Modals** | `src/components/ai-edition/Modals.tsx` | `OpenProjectModal`, `NewProjectModal`, `EditClipModal` (per-clip crop + in/out), `UnsavedChangesModal`. Mounted at the shell level (`:1333-1385`) so every trigger site reuses the same instance. |
| **Export dialog** | `src/components/ai-edition/ExportDialog.tsx` | Named destinations (Web / YouTube, Social, Studio, README GIF) that set format / quality / frame-rate / codec / size together, all of which stay under **Advanced**. Both formats go through the native compositor with the same clip list (`resolveVisibleClips`) and scene (`buildSceneDescription`): `exportGifNative` for GIF, `exportMultiNative` for MP4. |
| **Export dialog** | `src/components/ai-edition/ExportDialog.tsx` | One settings panel — format (MP4 / GIF), quality, frame rate, and the GIF size and loop controls — with no codec choice and no idle hint plate: every MP4 is H.264. Both formats go through the native compositor with the same clip list (`resolveVisibleClips`) and scene (`buildSceneDescription`): `exportGifNative` for GIF, `exportMultiNative` for MP4. |
| **Captions pane** | `src/components/ai-edition/CaptionsPane.tsx` | Mounted as a facet body from `FloatingInspector.tsx` (`:1062`). Controls caption appearance + translations; the cues themselves are a derived view over `document.transcripts` (see [`src/lib/ai-edition/captions/`](../../src/lib/ai-edition/captions/)). |

## Modes and facets
Expand Down
Loading
Loading