|
10 | 10 | ## Key Files |
11 | 11 |
|
12 | 12 | ### Backend (Python) |
13 | | -- `opencut/server.py` (~800 lines) - Flask app creation, startup, port management, download_models, `_setup_system_site_packages()` for frozen builds. JSON structured logging via python-json-logger (file handler; console stays plain text). `[job_id]` correlation via _JobLogFilter. `/logs/tail` endpoint for filtered log viewing. |
| 13 | +- `opencut/server.py` (~800 lines) - `create_app(config)` factory function for isolated Flask instances, startup, port management, download_models, `_setup_system_site_packages()` for frozen builds. JSON structured logging via python-json-logger (file handler; console stays plain text). `[job_id]` correlation via _JobLogFilter. `/logs/tail` endpoint for filtered log viewing. |
| 14 | +- `opencut/config.py` — `OpenCutConfig` dataclass centralizing env var reads (host, port, debug, cors_origins, log_level, data_dir). Used by `create_app(config)` for testable/isolated instances. |
14 | 15 | - `opencut/security.py` (~280 lines) - Path validation, CSRF tokens, safe_pip_install (frozen-build aware via `_find_system_python()`), safe_float/safe_int (with range clamp + inf/nan rejection), validate_filepath, VALID_WHISPER_MODELS, rate_limit/require_rate_limit |
15 | | -- `opencut/jobs.py` (~240 lines) - Job state, _new_job, _update_job, _kill_job_process, _get_job_copy, _list_jobs_copy, _unregister_job_process, TooManyJobsError, MAX_CONCURRENT_JOBS=10, async_job decorator. Thread-local job_id for log correlation (_thread_local.job_id set in _process, cleared in finally). _safe_error delegates to errors.safe_error for structured classification. |
| 16 | +- `opencut/jobs.py` (~240 lines) - Job state, _new_job, _update_job, _kill_job_process, _get_job_copy, _list_jobs_copy, _unregister_job_process, TooManyJobsError, MAX_CONCURRENT_JOBS=10, async_job decorator (with `filepath_required` and `filepath_param` parameters), `make_install_route()` factory for identical install endpoints. Thread-local job_id for log correlation (_thread_local.job_id set in _process, cleared in finally). _safe_error delegates to errors.safe_error for structured classification. |
16 | 17 | - `opencut/helpers.py` (~530 lines) - _try_import, output paths, FFmpegCmd builder, FFmpeg progress runner, deferred temp cleanup, job time tracking, compute_estimate, `run_ffmpeg()`, `ensure_package()`, `get_video_info()` |
17 | 18 | - `opencut/errors.py` (~230 lines) - Structured error taxonomy. OpenCutError exception class with code/message/suggestion. `error_response()` helper for routes. `safe_error(exc, context)` classifies exceptions (MemoryError→GPU_OUT_OF_MEMORY, TimeoutError→OPERATION_TIMEOUT, PermissionError→PERMISSION_DENIED, ImportError→MISSING_DEPENDENCY, etc.) and returns structured JSON with recovery suggestions. Factory constructors: missing_dependency, file_not_found, gpu_out_of_memory, invalid_input, invalid_model, operation_failed, rate_limited, queue_full, module_not_available, file_permission_denied, too_many_items, server_busy, install_failed. All errors return `{error, code, suggestion}` JSON. |
18 | 19 | - `opencut/checks.py` (~90 lines) - Centralized dependency availability checks (demucs, watermark, pedalboard, audiocraft, edge_tts, rembg, upscale, scenedetect, auto-editor, transnetv2, resemble-enhance, ollama) |
|
42 | 43 | - `nlp_command.py` - Natural language → API route mapping. COMMAND_MAP with 19 entries. parse_command_keyword(text) → {route, params, confidence, matched_keyword} or None. parse_command_llm(text, config, routes). parse_command(text, llm_config) tries LLM then keyword. extract_params_from_text(text) extracts numbers/language/intensity hints. |
43 | 44 |
|
44 | 45 | ### Route Blueprints (`opencut/routes/`) |
45 | | -- `__init__.py` - `register_blueprints(app)` registers all 11 Blueprints (added workflow_bp) |
46 | | -- `system.py` (~1130 lines) - /health, /shutdown, /info, /gpu/*, /dependencies (includes color_match, auto_zoom, footage_search, loudness_match, deliverables, nlp_command checks), /file, /whisper/*, /llm/* |
47 | | -- `audio.py` (~2175 lines) - /silence, /fillers, /audio/*, /audio/beat-markers (→ beat timestamps for ExtendScript markers), /audio/loudness-match (async, on_progress) |
48 | | -- `captions.py` (~1590 lines) - /captions/*, /captions/chapters (LLMConfig object, not dict), /captions/repeat-detect (word-level timestamps → detect_repeated_takes) |
49 | | -- `video.py` (~4021 lines) - /video/*, /video/color-match (async, on_progress), /video/auto-zoom (dynamic resolution via probe — no hardcoded hd1080), /video/multicam-cuts (result.get("cuts") — dict not list) |
| 46 | +- `__init__.py` - `register_blueprints(app)` registers all 17 Blueprints (system, audio, captions, video_core, video_fx, video_ai, video_editing, video_specialty, jobs, settings, timeline, search, deliverables, nlp, workflow, context, plugins) + dynamic plugin blueprints |
| 47 | +- `system.py` (~1130 lines) - /health, /shutdown, /info, /gpu/*, /dependencies, /file, /whisper/*, /llm/*. Install routes use `make_install_route()` factory. |
| 48 | +- `audio.py` (~2175 lines) - /silence, /fillers, /audio/*, /audio/beat-markers, /audio/loudness-match. All async routes use `@async_job` decorator. |
| 49 | +- `captions.py` (~1590 lines) - /captions/*, /captions/chapters (LLMConfig object, not dict), /captions/repeat-detect. All async routes use `@async_job`. |
| 50 | +- `video_core.py` (~1395 lines) - /video/export, /video/reframe, /video/trim, /video/merge, /video/concat, /video/pip, /video/blend, /video/stabilize, /video/speed, /video/letterbox, /video/preview-frame, /video/color-match, /video/auto-zoom, /video/multicam-cuts, /video/multicam-xml, /video/highlights |
| 51 | +- `video_editing.py` (~750 lines) - /video/transitions/*, /video/title/*, /video/scenes, /video/denoise, batch processing routes |
| 52 | +- `video_fx.py` (~687 lines) - /video/fx/*, /video/chromakey, /video/lut/*, /video/style/*, /video/particles |
| 53 | +- `video_specialty.py` (~489 lines) - /video/face/*, /video/object/*, /video/watermark, /video/shorts, /video/rembg |
| 54 | +- `video_ai.py` (~443 lines) - /video/ai/*, AI model install routes (depth, emotion, multimodal-diarize, broll-generate, crisper-whisper) |
50 | 55 | - `jobs_routes.py` (~330 lines) - /status/*, /cancel/*, /cancel-all, /jobs, /stream/*, /queue/*, /jobs/history (SQLite-backed), /jobs/stats, /jobs/interrupted |
51 | 56 | - `settings.py` (~440 lines) - /presets/*, /favorites/*, /workflows/*, /settings/import|export, /settings/llm (GET masks key, POST preserves masked), /settings/loudness-target, /settings/auto-zoom, /settings/chapters, /settings/multicam, /settings/footage-index, /logs/tail (filtered log viewer), /templates/list, /templates/save, /templates/apply |
52 | 57 |
|
|
101 | 106 | - `ROADMAP.md` - 7-phase implementation roadmap with priority matrix and success metrics |
102 | 107 |
|
103 | 108 | ### Tests |
104 | | -- `tests/conftest.py` - Flask test client + CSRF fixtures + test media generators |
| 109 | +- `tests/conftest.py` - Flask test client via `create_app()` factory + CSRF fixtures + test media generators |
105 | 110 | - `tests/test_core.py` - Core module unit tests (silence, export, config) |
106 | 111 | - `tests/test_integration.py` - Route integration tests (health, CSRF, search, NLP, settings, timeline) |
107 | 112 | - `tests/test_new_modules.py` - v1.5 module tests (repeat_detect, chapter_gen, footage_search, deliverables, multicam, nlp_command, loudness_match, auto_zoom, color_match) |
|
116 | 121 | - `tests/test_batch_parallel.py` - Parallel batch processing (ThreadPoolExecutor, GPU/CPU workers, error isolation, cancellation) |
117 | 122 | - `tests/test_batch_executor.py` - BatchExecutor class tests (OperationSpec dispatch, combined progress, cancellation, partial failure) |
118 | 123 | - `tests/test_clip_notes_plugin.py` - Clip Notes plugin tests (CRUD notes, export text/CSV) |
| 124 | +- `tests/test_core_modules.py` - Core module unit tests batch 1 (15 modules: silence, fillers, scene_detect, auto_edit, highlights, etc.) |
| 125 | +- `tests/test_core_modules_batch2.py` - Core module unit tests batch 2 (135 tests across 28 modules) |
| 126 | +- `tests/test_integration_ffmpeg.py` - FFmpeg integration tests |
| 127 | +- `tests/test_integration_whisper.py` - Whisper integration tests |
| 128 | +- `tests/jsx_mock.js` - ExtendScript mock harness (38 JSX functions, 35 assertions under Node.js) |
119 | 129 |
|
120 | 130 | ### Example Plugins (`opencut/data/example_plugins/`) |
121 | 131 | - `timecode-watermark/` - FFmpeg drawtext timecode overlay plugin |
|
124 | 134 | ## Architecture |
125 | 135 | - Backend runs as standalone process (exe or `python -m opencut.server`) |
126 | 136 | - Panel communicates via XHR to localhost:5679 |
127 | | -- **Blueprint-based route organization**: 13 Blueprints (system, audio, captions, video, jobs, settings, timeline, search, deliverables, nlp, workflow, context, plugins) + dynamically loaded plugin blueprints |
| 137 | +- **Blueprint-based route organization**: 17 Blueprints (system, audio, captions, video_core, video_fx, video_ai, video_editing, video_specialty, jobs, settings, timeline, search, deliverables, nlp, workflow, context, plugins) + dynamically loaded plugin blueprints |
128 | 138 | - **Shared modules**: security.py (CSRF + path validation), jobs.py (job state), helpers.py (utilities + `run_ffmpeg` + `ensure_package` + `get_video_info`), user_data.py (thread-safe file I/O) |
129 | 139 | - **CSRF protection**: Token generated at startup in security.py, returned via /health, sent as `X-OpenCut-Token` header on mutations. `@require_csrf` decorator applied to ALL POST routes. |
130 | 140 | - **Path validation**: `validate_path()` checks realpath, null bytes, `..` components, symlinks. `validate_filepath()` adds isfile check. Applied to ALL routes accepting file paths. |
131 | 141 | - **Input validation**: `safe_float()`/`safe_int()` with optional `min_val`/`max_val` range clamping and inf/nan rejection, `VALID_WHISPER_MODELS` frozenset for model name validation |
132 | 142 | - **Rate limiting**: `require_rate_limit(key)` decorator prevents concurrent expensive ops (e.g. model installs share `"model_install"` key) |
133 | 143 | - **Error taxonomy**: `OpenCutError` with typed codes (`MISSING_DEPENDENCY`, `GPU_OUT_OF_MEMORY`, etc.) — frontend `enhanceError()` adds actionable hints |
134 | 144 | - **Job safety**: `TooManyJobsError` (429), `_get_job_copy()`/`_list_jobs_copy()` for thread-safe reads, `_unregister_job_process()` for cleanup |
135 | | -- **Async job decorator**: `@async_job("type")` wraps routes in standard thread + try/catch + update pattern. Sets thread-local job_id for log correlation. Auto-persists terminal jobs to SQLite via _persist_job. |
| 145 | +- **Async job decorator**: `@async_job("type")` wraps routes in standard thread + try/catch + update pattern. Extended with `filepath_required` (auto-validates input file) and `filepath_param` (custom param name) parameters. Sets thread-local job_id for log correlation. Auto-persists terminal jobs to SQLite via _persist_job. All 97 async routes now use this decorator (no manual _new_job/Thread patterns remain). |
| 146 | +- **Install route factory**: `make_install_route(package, pip_name, check_fn, key)` in jobs.py generates identical install endpoint handlers. Used by 6 routes (depth, emotion, multimodal-diarize, broll-generate, face, crisper-whisper). |
| 147 | +- **App factory**: `create_app(config)` in server.py creates isolated Flask instances. `OpenCutConfig` dataclass in config.py centralizes env var reads. Tests use independent app instances. Module-level `app = create_app()` preserved for backward compat. |
136 | 148 | - Job system: `_new_job()` creates job, thread pool processes, SSE/polling for status. SQLite persistence at `~/.opencut/jobs.db` (WAL mode). `mark_interrupted()` on startup recovers jobs from previous crashes. |
137 | 149 | - **Workflow engine**: `core/workflow.py` chains steps sequentially via Flask test client, polls sub-jobs to completion, checks parent cancellation between steps. `routes/workflow.py` serves 6 built-in presets + user custom workflows. |
138 | 150 | - **Request size limit**: 100 MB `MAX_CONTENT_LENGTH` with 413 error handler |
|
160 | 172 | - Optional deps: `pip install -e ".[ai]"` (CPU), `pip install -e ".[ai-gpu]"` (GPU with onnxruntime-gpu), `pip install -e ".[all]"` (everything) |
161 | 173 |
|
162 | 174 | ## Dev Tooling |
163 | | -- `scripts/sync_version.py` - Syncs version from `__init__.py` to all 6 target locations (`python scripts/sync_version.py --set X.Y.Z`) |
| 175 | +- `scripts/sync_version.py` - Syncs version from `__init__.py` to all 19 targets (`python scripts/sync_version.py --set X.Y.Z`). `--check` flag for CI enforcement. |
164 | 176 | - `.editorconfig` - Editor indent/encoding rules (4-space Python, 2-space JS/CSS/HTML) |
165 | 177 | - `.pre-commit-config.yaml` - ruff lint+format, trailing whitespace, EOF fixer, YAML/JSON checks |
166 | 178 | - `DEVELOPMENT.md` - Developer setup guide (backend, CEP, building, linting) |
167 | 179 | - CI: `.github/workflows/build.yml` includes ruff lint (`--select E,F,I --ignore E501`) + import smoke tests before PyInstaller build |
168 | 180 | - Lint: `ruff check opencut/` — codebase is fully clean, pre-commit enforces on every commit |
169 | 181 |
|
170 | 182 | ## Version |
171 | | -- Current: **v1.9.0** |
172 | | -- All version strings: `pyproject.toml`, `__init__.py`, `CSXS/manifest.xml` (ExtensionBundleVersion + Version), `com.opencut.uxp/manifest.json`, `com.opencut.uxp/main.js` (VERSION const), `index.html` version display, README badge |
173 | | -- Use `python scripts/sync_version.py --set X.Y.Z` to update all 18 targets at once (including UXP files) |
| 183 | +- Current: **v1.9.3** |
| 184 | +- All version strings: `pyproject.toml`, `__init__.py`, `CSXS/manifest.xml` (ExtensionBundleVersion + Version), `com.opencut.uxp/manifest.json`, `com.opencut.uxp/main.js` (VERSION const), `index.html` version display, README badge, `package.json` |
| 185 | +- Use `python scripts/sync_version.py --set X.Y.Z` to update all 19 targets at once (including UXP files and package.json) |
| 186 | +- Use `python scripts/sync_version.py --check` in CI to verify all targets match |
174 | 187 |
|
175 | 188 | ## Gotchas |
| 189 | +- **video.py was split** — `video.py` no longer exists. Routes are in `video_core.py`, `video_editing.py`, `video_fx.py`, `video_specialty.py`, `video_ai.py`. All URL paths unchanged. All 5 files share the `/video` URL prefix. |
| 190 | +- **No manual _new_job/Thread patterns** — All async routes MUST use `@async_job("type")` decorator. Never write manual `_new_job()` + `threading.Thread` + `_update_job(status="complete")`. The decorator handles job creation, thread spawning, error handling, cancellation race conditions, and SQLite persistence. |
| 191 | +- **Install routes use factory** — New install endpoints should use `make_install_route(package, pip_name, check_fn, key)` from jobs.py instead of duplicating the install handler pattern. |
176 | 192 | - `subprocess.run` must use `_sp.run` (the alias) in route files |
177 | 193 | - ExtendScript is ES3: no let/const, no arrow functions, no template literals |
178 | 194 | - CEP Chromium needs `user-select: text` override for inputs |
@@ -927,4 +943,4 @@ enhance = ["resemble-enhance>=0.0.1"] |
927 | 943 | - **NDJSON response mime type** — `application/x-ndjson`. Frontend must parse line-by-line, not `JSON.parse()` the whole body. Each line is a valid JSON object. |
928 | 944 | - **Context awareness `score_features` capability check** — The scoring checks for "audio" and "video" capabilities by inferring from tags (e.g., `audio_only` implies has audio but no video). Tags like `talking_head` imply both audio and video are present. |
929 | 945 | - **`rate_limit("ai_gpu")` scope** — Shared across all AI routes. If upscale is running, music generation returns 429. This is intentional to prevent GPU memory conflicts. |
930 | | -- **13 Blueprints** — Route registration list in `__init__.py` is now: system, audio, captions, video, jobs, settings, timeline, search, deliverables, nlp, workflow, context, plugins. Plus dynamic plugin blueprints. |
| 946 | +- **17 Blueprints** — Route registration list in `__init__.py` is now: system, audio, captions, video_core, video_fx, video_ai, video_editing, video_specialty, jobs, settings, timeline, search, deliverables, nlp, workflow, context, plugins. Plus dynamic plugin blueprints. |
0 commit comments