From 01050277ea2cceab549d41d71bee3258af2ca26a Mon Sep 17 00:00:00 2001 From: kevin314 Date: Fri, 25 Sep 2026 11:05:41 +0000 Subject: [PATCH] Add dialogue/shot editor --- apps/fastvideo_studio/mock_server.py | 256 ++++++- .../src/app/scenes/page.test.tsx | 282 +++++++ apps/fastvideo_studio/src/app/scenes/page.tsx | 132 ++++ .../components/jobs/CreateJobButton.test.tsx | 86 ++- .../src/components/jobs/CreateJobButton.tsx | 54 +- .../components/jobs/CreateJobModal.test.tsx | 178 +++++ .../src/components/jobs/CreateJobModal.tsx | 192 ++++- .../src/components/jobs/H3DialogueEditor.tsx | 346 +++++++++ .../src/components/jobs/H3DialoguePreview.tsx | 44 ++ .../src/components/jobs/H3Editors.test.tsx | 184 +++++ .../jobs/H3ScriptImportDialog.test.tsx | 123 ++++ .../components/jobs/H3ScriptImportDialog.tsx | 247 +++++++ .../components/jobs/H3ShotDialogueField.tsx | 92 +++ .../src/components/jobs/H3ShotListEditor.tsx | 690 ++++++++++++++++++ .../src/components/jobs/H3ShotPanel.tsx | 214 ++++++ .../components/jobs/H3ShotStoryboard.test.tsx | 51 ++ .../src/components/jobs/H3ShotStoryboard.tsx | 61 ++ .../src/components/jobs/H3TagConsole.tsx | 57 ++ .../src/components/jobs/H3TagPalette.tsx | 74 ++ .../components/jobs/nestedDialogs.test.tsx | 158 ++++ .../src/components/jobs/shotListUndo.test.tsx | 253 +++++++ .../src/components/scenes/SceneBoard.tsx | 249 +++++++ .../src/components/shell/Header.tsx | 1 + .../src/components/shell/PrimarySidebar.tsx | 8 + .../src/components/ui/dialog.test.tsx | 30 + .../src/components/ui/dialog.tsx | 6 +- .../src/hooks/useUndoableState.test.ts | 140 ++++ .../src/hooks/useUndoableState.ts | 86 +++ .../src/hooks/useUndoableText.test.ts | 84 +++ .../src/hooks/useUndoableText.ts | 109 +++ .../src/lib/h3DialogueEstimate.ts | 171 +++++ .../src/lib/h3PromptFields.test.ts | 87 +++ .../src/lib/h3PromptFields.ts | 77 ++ .../src/lib/h3ScriptImport.test.ts | 238 ++++++ .../src/lib/h3ScriptImport.ts | 332 +++++++++ apps/fastvideo_studio/src/lib/h3Shots.test.ts | 336 +++++++++ apps/fastvideo_studio/src/lib/h3Shots.ts | 478 ++++++++++++ apps/fastvideo_studio/src/lib/h3Tags.test.ts | 117 +++ apps/fastvideo_studio/src/lib/h3Tags.ts | 409 +++++++++++ apps/fastvideo_studio/src/lib/jobJson.test.ts | 78 ++ apps/fastvideo_studio/src/lib/jobJson.ts | 102 +++ apps/fastvideo_studio/src/lib/scenes.test.ts | 250 +++++++ apps/fastvideo_studio/src/lib/scenes.ts | 182 +++++ apps/fastvideo_studio/src/lib/types.ts | 2 + apps/fastvideo_studio/src/test/setup.ts | 17 + 45 files changed, 7324 insertions(+), 39 deletions(-) create mode 100644 apps/fastvideo_studio/src/app/scenes/page.test.tsx create mode 100644 apps/fastvideo_studio/src/app/scenes/page.tsx create mode 100644 apps/fastvideo_studio/src/components/jobs/H3DialogueEditor.tsx create mode 100644 apps/fastvideo_studio/src/components/jobs/H3DialoguePreview.tsx create mode 100644 apps/fastvideo_studio/src/components/jobs/H3Editors.test.tsx create mode 100644 apps/fastvideo_studio/src/components/jobs/H3ScriptImportDialog.test.tsx create mode 100644 apps/fastvideo_studio/src/components/jobs/H3ScriptImportDialog.tsx create mode 100644 apps/fastvideo_studio/src/components/jobs/H3ShotDialogueField.tsx create mode 100644 apps/fastvideo_studio/src/components/jobs/H3ShotListEditor.tsx create mode 100644 apps/fastvideo_studio/src/components/jobs/H3ShotPanel.tsx create mode 100644 apps/fastvideo_studio/src/components/jobs/H3ShotStoryboard.test.tsx create mode 100644 apps/fastvideo_studio/src/components/jobs/H3ShotStoryboard.tsx create mode 100644 apps/fastvideo_studio/src/components/jobs/H3TagConsole.tsx create mode 100644 apps/fastvideo_studio/src/components/jobs/H3TagPalette.tsx create mode 100644 apps/fastvideo_studio/src/components/jobs/nestedDialogs.test.tsx create mode 100644 apps/fastvideo_studio/src/components/jobs/shotListUndo.test.tsx create mode 100644 apps/fastvideo_studio/src/components/scenes/SceneBoard.tsx create mode 100644 apps/fastvideo_studio/src/components/ui/dialog.test.tsx create mode 100644 apps/fastvideo_studio/src/hooks/useUndoableState.test.ts create mode 100644 apps/fastvideo_studio/src/hooks/useUndoableState.ts create mode 100644 apps/fastvideo_studio/src/hooks/useUndoableText.test.ts create mode 100644 apps/fastvideo_studio/src/hooks/useUndoableText.ts create mode 100644 apps/fastvideo_studio/src/lib/h3DialogueEstimate.ts create mode 100644 apps/fastvideo_studio/src/lib/h3PromptFields.test.ts create mode 100644 apps/fastvideo_studio/src/lib/h3PromptFields.ts create mode 100644 apps/fastvideo_studio/src/lib/h3ScriptImport.test.ts create mode 100644 apps/fastvideo_studio/src/lib/h3ScriptImport.ts create mode 100644 apps/fastvideo_studio/src/lib/h3Shots.test.ts create mode 100644 apps/fastvideo_studio/src/lib/h3Shots.ts create mode 100644 apps/fastvideo_studio/src/lib/h3Tags.test.ts create mode 100644 apps/fastvideo_studio/src/lib/h3Tags.ts create mode 100644 apps/fastvideo_studio/src/lib/jobJson.test.ts create mode 100644 apps/fastvideo_studio/src/lib/jobJson.ts create mode 100644 apps/fastvideo_studio/src/lib/scenes.test.ts create mode 100644 apps/fastvideo_studio/src/lib/scenes.ts diff --git a/apps/fastvideo_studio/mock_server.py b/apps/fastvideo_studio/mock_server.py index 2af805b91..98e241cf7 100644 --- a/apps/fastvideo_studio/mock_server.py +++ b/apps/fastvideo_studio/mock_server.py @@ -4,8 +4,8 @@ In-memory mock of the FastVideo Studio API (``server.py``). This mock implements the same ``/api`` routes and response shapes as the real FastAPI server but keeps everything in memory and never touches the FastVideo -library, a GPU, or a database. It is meant to back the Playwright e2e suite so -the Next.js frontend can be exercised end-to-end without a real backend. +library or a GPU. It is meant to back the Playwright e2e suite so the Next.js +frontend can be exercised end-to-end without a real backend. Job lifecycle is simulated by recording a start timestamp and *computing* the status on read: a started job reports ``running`` for a few seconds and then @@ -13,14 +13,24 @@ flips to ``completed`` with an ``output_path``, so polling the job list / logs shows progression. Generated media is a tiny 1s ``testsrc`` MP4 built lazily with ffmpeg and cached. +Pass ``--data-dir`` to seed jobs/datasets/settings from a real server's +persisted SQLite database instead of the built-in fake demo data, so you can +browse real job history through the mock without a GPU. This is a read-only +snapshot taken once at startup: nothing created or changed while the mock +runs is ever written back to that database, so it's safe to point at the +same ``--data-dir`` a real ``server.py`` is using. + Usage (from the ``apps/`` directory):: PYTHONPATH=.. python -m fastvideo_studio.mock_server --port 8190 + PYTHONPATH=.. python -m fastvideo_studio.mock_server --port 8190 --data-dir ../outputs/ui_data """ from __future__ import annotations import contextlib +import json +import logging import os import random import shutil @@ -29,22 +39,31 @@ import tempfile import threading import time import uuid +from pathlib import Path from typing import Annotated, Any from fastapi import FastAPI, File, HTTPException, UploadFile from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import FileResponse, PlainTextResponse -from fastvideo_studio.database import default_settings_dict +from fastvideo_studio.database import Database, default_settings_dict from fastvideo_studio.models import (CreateDatasetRequest, CreateJobRequest, SettingsUpdate, UpdateCaptionRequest, model_label) +logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s") +logger = logging.getLogger("fastvideo.studio.mock") + # --- Config ----------------------------------------------------------------- # How long a started job stays "running" before it flips to "completed". COMPLETE_AFTER_SECONDS = 3.0 FFMPEG_BIN = shutil.which(os.getenv("FASTVIDEO_FFMPEG_BIN", "ffmpeg")) +# Marks a job dict as a frozen row loaded from a real --data-dir snapshot +# (see _load_snapshot_from_db) rather than something this process is +# simulating. Stripped from API responses by _public_job. +_FROM_SNAPSHOT_KEY = "_from_db_snapshot" + # A small catalogue of fake models keyed by workload type. Mirrors the real # server's {id, label} shape (label derived from the HF-style path). _MODELS_BY_WORKLOAD: dict[str, list[str]] = { @@ -54,6 +73,10 @@ _MODELS_BY_WORKLOAD: dict[str, list[str]] = { ], "i2v": [ "Wan-AI/Wan2.1-I2V-14B-480P-Diffusers", + # Real registered id (fastvideo/registry.py) -- needed so the frontend's + # `modelId.toLowerCase().includes('minimax-h3')` gate (speech-tag + # console, references, last-frame upload) actually lights up in the mock. + "MiniMaxAI/MiniMax-H3", ], "t2i": [ "black-forest-labs/FLUX.1-schnell", @@ -98,16 +121,76 @@ _mock_media_cache: dict[str, str] = {} _mock_media_lock = threading.Lock() +def _testsrc_frame(width: int, height: int, index: int): + """A cheap, dependency-light stand-in for ffmpeg's `testsrc` pattern: + a gradient that shifts over `index` so an encoded clip visibly animates.""" + import numpy as np + + frame = np.zeros((height, width, 3), dtype=np.uint8) + frame[:, :, 0] = np.linspace(0, 255, width, dtype=np.uint8)[None, :] + frame[:, :, 1] = np.linspace(0, 255, height, dtype=np.uint8)[:, None] + frame[:, :, 2] = (index * 10) % 256 + return frame + + +def _build_mock_media_with_pyav(kind: str, path: str) -> None: + """ffmpeg isn't installed on every dev box; PyAV (a real FastVideo + dependency already, used for reference-video decoding) can encode the + same tiny clip directly via its bundled libav, no external binary.""" + from PIL import Image + + if kind == "png": + Image.fromarray(_testsrc_frame(320, 240, 0)).save(path) + return + + import av as pyav + + # h264 is what every browser's