Notable changes from the June 2026 hardening/audit pass. The authoritative record also lives in the git commit messages.
- New test files:
test_coverage_infra.py(169 tests covering errors, schemas, workers, config, helpers, checks),test_coverage_core_logic.py(137 tests covering timecode_utils, metadata_tools, safe_zones, hdr_tools, dead_time, stream_chapters, timelapse, highlight_detect, soft_subtitles, adr_cueing),test_coverage_core_extra.py(159 tests covering analytics, selects_bin, show_notes, ffmpeg_builder, ab_av1, ab_compare, cinemagraph, surround_mix, gpu_dashboard, display_calibration, apple_silicon, amd_gpu),test_coverage_schemas_mcp.py(72 tests covering schemas, MCP tool catalog validation, error factories, config, helpers, checks, workers). CI coverage floor raised from 55% to 58%.
- New
register_config_schema()andread_user_file_versioned()inopencut/user_data.py. Config files can declare a schema version and register migration functions.read_user_file_versioned()auto- migrates old configs on read, stamps_schema_version, and persists the upgraded data. Unknown keys are preserved, not stripped.
- New
tests/test_coverage_expansion.pyadds focused unit tests for: url_ingest (16), multimodal_index (7), edl_aaf (14), media_relink (9), auto_update (14), structured_ingest (12), batch_conform (5), project_organizer (15), storage_tiering (10), render_queue (9). All tests are fast (<1s total), require no external services, and run without optional ML/GPU dependencies. CI coverage floor raised to 55%.
- Added CPU/memory resource limits (CPU: 4c/4G, GPU: 8c/16G), JSON-file
log rotation (50-100MB × 3-5 files), and an opt-in MCP sidecar profile
(
docker compose --profile mcp up). GPU service now readsNVIDIA_VISIBLE_DEVICESfrom environment instead of hardcodingall.
CaptionConfig.modeldefault changed frombasetolarge-v3-turbo. Within 1-2% WER of large-v3 at 5.4x speed. Existing users keep their configured model.
opencut config export [-o backup.json]dumps all~/.opencut/*.jsonsettings to a single JSON bundle.opencut config import backup.jsonrestores them.opencut config validatechecks all config files for JSON parse errors (exits non-zero on failure).
- Neon Glow, Slide Up, Word Pop, Emoji React, Speaker Colored, Retro VHS, Podcast, Gaming, Luxury, TikTok Bold, Handwritten, and RTL Arabic. Caption style count now exceeds 30, matching paid competitor expectations.
- New
smart_pause=trueoption on/silenceroute anddetect_speech()that preserves dramatic pauses (0.8-3.0s silences preceded by sentence-ending speech) instead of cutting them. When a transcript is available, uses sentence-final punctuation as context; without transcript, falls back to a duration heuristic that preserves short mid-speech pauses. - New
filter_smart_pauses()function inopencut/core/silence.pywith 2 unit tests.
- Added 47 new curated MCP tools covering video editing (trim, merge, concat, reframe, stabilize, speed, PiP, blend), video effects (chromakey, LUT, FX, denoise, interpolation, transitions), captions (burn-in, translate, styled, animated, karaoke, SRT import), audio (effects, duck, isolate, normalize, filler removal, beat markers, enhance), timeline/workflow (run, presets, batch rename, smart bins, beat cut, export), deliverables (VFX sheet, ADR list, music cue sheet), search (NLP command, URL ingest), and system (info, GPU, dependencies, feature state, social upload).
- Agent clients (Claude, Cursor, etc.) now discover 86 OpenCut capabilities vs 39 previously. Extended tools (1,482 route-level) remain opt-in.
- Bumped Werkzeug floor from >=3.1.5 to >=3.1.6 in both
pyproject.tomlandrequirements.txt. CVE-2026-27199 is asafe_join()Windows device-name bypass that causes hanging file reads insend_from_directory.
- Regenerated
route_manifest.jsonandfeature_readiness.jsonto reflect recent url_ingest, multimodal_index, and AutoShot additions. README badges and inline counts updated: 1,545 shipped routes, 6 stubs.
- Generated feature-readiness records now cross-reference the route
manifest's
readinessfield instead of defaulting to"available". Routes classified asdependency-gatedproduce"missing_dependency"features; stub routes produce"stub"features. Panels disable buttons for these features before the user clicks them (was: all generated features showed as available regardless of route status).
- New
url_ingestmodule (opencut/core/url_ingest.py): fetches video from a URL to~/.opencut/media_ingest/so existing routes consume it as a local file. Uses yt-dlp when installed (YouTube, Vimeo, Zoom Clips, Medal, etc.); falls back to plain HTTP download for direct-link URLs. SHA-256 content- addressed caching avoids re-downloads. - New routes:
POST /search/ingest(async job),GET /search/ingest/cache,DELETE /search/ingest/cache.
- Footage index schema upgraded to v2:
ocr_textandaudio_tagscolumns added to thefootagetable, FTS5 index rebuilt to cover all four fields (file_path, transcript, ocr_text, audio_tags). Existing v1 databases auto-migrate on first access. - New
multimodal_indexmodule (opencut/core/multimodal_index.py): extracts on-screen text from key frames via pytesseract or easyocr, and classifies audio events (speech, music, silence, tempo) via librosa spectral heuristics with a basic fallback. - New
POST /search/multimodal-indexroute: indexes files with transcript + OCR text + audio event tags in a single pass. OCR and audio tags are opt-in (default on) viaocrandaudio_tagsbody params. - FTS5 search now matches across transcript, on-screen text, and audio event tags — a query for "music" or "logo" hits audio events and OCR text respectively, not just spoken words.
check_ocr_available()added tochecks.py.
- Added AutoShot as a selectable scene-detection engine with priority 85
(above TransNetV2's 70).
detect_scenes_auto()now tries AutoShot first for gradual-transition-aware detection, falling back to TransNetV2 → PySceneDetect → FFmpeg threshold.check_autoshot_available(), engine registry entry, and 5 new tests added.
- Feature-state entries now expose
privacy(local-only / cloud),license(SPDX token), andadvisory_notesfrom the model-card registry. The CEP panel annotates gated buttons with data-feature-privacy/license attributes, privacy chip badges ("Local" / "Cloud"), and advisory notes in tooltips.
/video/trailer/generatenow delegates totrailer_gen.generate()./video/outpaintnow delegates toframe_extension.outpaint_aspect_ratio()./agent/search-footagenow delegates tosemantic_video_search.semantic_search()./agent/storyboardnow delegates toai_storyboard.generate_storyboard_from_script().- Shipped route count rises from 1,536 to 1,541. Users no longer hit 501 for capabilities that already have working local implementations.
- New
depth_anything_3module + adepth-anything/DA3-SMALL(Apache-2.0, single-transformer SOTA, ~+25% geometry) default for CineFocus bokeh/parallax, withDepth-Anything-V2-Smallretained as the automatic fallback. CineFocus now routes both depth-load sites through a central_load_depth_backend()that resolves DA3 first and degrades to DA2 on any load failure. - New
depth_anything_3engine-registry entry (priority 85 > DA2's 80) +check_depth_anything_3_available(), ada3-smallmodel entry, andtests/test_depth_engines.py.
relight_iclight.pywas titled "IC-Light V2" but IC-Light v2 is non-commercial and its full weights were never publicly released — un-shippable under MIT. Re-targeted it at IC-Light v1 (lllyasviel/IC-Light, Apache-2.0, real public weights). Removed the v2 framing from docstrings/hints, addedMODEL_ID/NAME/ LICENSE,check_iclight_available(), anic-light-v1model entry, and a newrelightengine-registry domain. The/video/relight/iclightroute now reports the v1 Apache-2.0 licence. Registry test suite added.
diarize()now defaults topyannote/speaker-diarization-community-1(CC-BY-4.0, "always freely accessible", lower DER) and automatically falls back to legacyspeaker-diarization-3.1when the community model can't be loaded (terms not accepted, offline cache miss). The model is configurable viaDiarizeConfig.model.- New
diarizationengine-registry domain:pyannote_community1(default, priority 80) >pyannote_legacy(60, fallback) >sortformer(50, optional, NVIDIA NeMo-gated). Addedcheck_diarization_available()/check_sortformer_available(), apyannote-community-1model entry, andtests/test_diarization_engines.py.
EngagementScore.compute_virality()maps the existing engagement dimensions (hook / emotional / pacing / quotability) to a single deterministic, hook-forward 0–100 virality score — the headline number Opus Clip paywalls — so creators can sort and threshold clips at a glance. It is a heuristic, not a performance guarantee, and is distinct from the editing-relevanceoverallcomposite.- Surfaced as a top-level
viralityfield (plus inside theengagementblock) onPOST /video/highlightsand the magic-clips/shorts response. Deterministic unit tests added.
- Wired a new
lipsync_latentsyncengine into a newlip_syncengine-registry domain. The dub pipeline (transcribe→translate→voice-clone→render) can now re-animate the speaker's mouth to the new audio — the visual lip-sync stage Rask/HeyGen/sync.so paywall — viaDubConfig.lip_sync_backend="latentsync"andPOST /ai/lip-sync(backendparam). - Opt-in by design (RESEARCH Open Question 3): LatentSync's code is Apache-2.0 but its checkpoint licence is unconfirmed, so the engine is registered below the always-available heuristic and is never auto-selected. The dub stage degrades gracefully: LatentSync → heuristic jaw-overlay → audio-only dub, so it never hard-fails.
- Added
check_latentsync_available(), alatentsync-1.6model-download entry, andtests/test_lipsync_engines.py(registry, opt-in ordering, availability gating, license hygiene, dub-config plumbing). Full diffusion forward activates once local weights are present.
- Wired the previously-dark
upscale_seedvr2stub into the upscaling engine registry asseedvr2, gated bycheck_seedvr2_available()(diffusers + torch). It is registered at higher priority than Real-ESRGAN, so it auto-selects when its Apache-2.0 SeedVR2-3B weights are installed and falls back to Real-ESRGAN otherwise.POST /video/ai/upscalegained anengineparam (auto/seedvr2/realesrgan) that tries SeedVR2 first and degrades to Real-ESRGAN on any unavailability (recorded in the responsenotes), never hard-failing. - Added a
seedvr2-3bmodel-download catalog entry (Apache-2.0, ~6 GB) and a registry test suite (tests/test_upscaling_engines.py). The heavy diffusion forward activates once the local weights are present; the entry point raises a structured error when absent (matching the NeMo ASR engine contract).
- New timeline-native beat-cut path: detect beats in a clip's audio and emit
beat-aligned clip boundaries as a cut list the panel reviews and ripple-applies
to the active Premiere sequence. Unlike
/audio/beat-sync(which renders a new montage file), this produces cut points for the sequence already open. core/beat_sync_edit.py::plan_timeline_beat_cuts()— pure planner over detected beats + a clip duration, with mode selection (every beat / 2 beats / bar / 2 bars / accents) andmin_cut_durationmerging. Returns{start, end, duration, beat_number, strength, label}segments (the shapeocApplySequenceCutsand the cut-review panel already consume).- CEP panel: a "Beat-Synced Cuts" card on the Timeline → Beat Markers sub-tab, wired through the shared cut-review preview + ripple-apply pipeline (the same one silence/filler/auto-edit use). Added to the queue allowlist and i18n.
- Tests: 4 planner unit tests + 3 route smoke tests; i18n/a11y/parity green.
- Resolved the carried brand/namespace question (RESEARCH Open Question 1). The
product keeps the name OpenCut, but the package-manager distribution token
is now
opencut-pproto stay unambiguous against the unrelated browser editor opencut.app (~48K stars) across PyPI/Homebrew/winget/SEO. The-pprosuffix marks the Premiere Pro integration that uniquely identifies this tool. pyproject.toml[project].name→opencut-ppro; SBOM root component and READMEpip installexamples updated to match. Unchanged: the import package (import opencut), CLI commands (opencut,opencut-server,opencut-mcp-server), and CEP/UXP extension IDs. Decision recorded in the README "Naming & distribution" section. (All candidate names were free on PyPI; the actual publish remains a credential-gated roadmap item.)
- Reconciled the bundled-FFmpeg version pin to
8.1.1-essentials_build-www.gyan.devacrossAppConstants.cs,OpenCut.iss, and the manifest test (the Inno pin had drifted to8.1, leavingtest_inno_installer_writes_ffmpeg_manifestred). - New
opencut/core/ffmpeg_provenance.pyis the single source of truth for the acceptable bundled build. It parsesffmpeg -version(gyan.dev release, gyan git-master, BtbNgit describe, distron-prefixed) and grades it against two lanes: a release floor (>= 8.1.1) or a git-master snapshot dated>= 2026-06-10(reference commitb29bdd3715). This asserts a security patch level — the June-2026 FFmpeg zero-days (CVE-2026-6385 + CVE-2026-39210..39218, crafted-media heap/stack overflows) landed as post-release master commits, so a bare8.1.xtag is insufficient on its own. GET /system/capabilitiesnow carriesffmpeg.security(graded result) and affmpeg_below_security_floorfinding so a stale bundled binary is visible at runtime.scripts/verify_ffmpeg_provenance.pyis a stdlib build/CI gate that fails closed when the bundled binary is below the floor and records ground-truth provenance (version, git commit/snapshot date, lane, CVE list) to a manifest.- Installer manifests (
installer.json, both WPF + Inno) now recordbundled_ffmpeg_security_floor. Provenance requirements documented indocs/RELEASE_PROVENANCE.md.
- Wired the previously-dark
asr_parakeet(streaming) andasr_canary(batch) stubs into the transcription engine registry asparakeet_tdtandcanary_1b_flash. Both are gated by a newcheck_nemo_asr_available()check (accepts thenemo/nemo_toolkit[asr]install), retain faster-whisper and CrisperWhisper as fallbacks, and shipparakeet-tdt-0.6b-v3/canary-1b-flashmodel-catalog entries for on-demand download. Engines report unavailable (never crash) when NeMo is absent.
dump_route_manifestnow classifies every route asimplemented/dependency-gated/stubvia static handler inspection, and recordsreadiness_countsplus ashipped_route_count(total minus the 10 HTTP-501 strategic stubs). Manifest schema bumped to version 3.- New
GET /system/route-readinessendpoint surfaces the shipped count and the explicit stub / dependency-gated rule lists so panels and tooling stop presenting 501 stubs as shipped capabilities. (Panels already gatedata-feature-idactions via F100feature-state.js; the Tier-3 stub actions are not surfaced as panel buttons, so none are presented as shipped.) - README route badge and route-count claims now advertise the shipped count
(1,536) instead of the raw total;
scripts/sync_badges.pybinds the badge toshipped_route_count.
- Migrated the remaining 35 raw
jsonify({"error": ...})responses inroutes/video_editing.py,routes/video_core.py, androutes/audio.pyto the structurederror_response()helper. Every error now returns a machine-readablecode, asuggestion, and request-id correlation, and is logged through the typed-error path — matching the rest of the API surface the frontend already consumes.
- Replaced the deny-list AST check in
expression_engine.pywith a closed node whitelist: only arithmetic/boolean/comparison nodes, calls to known sandbox functions, and ternaries are permitted. Everyast.Attributenode is now rejected outright, so dunder-walk escapes (().__class__.__subclasses__) and obfuscated variants cannot be expressed at all. Call targets are constrained to the sandbox's callable table; unknown calls (breakpoint(),eval()) are rejected at validation time.
- Added an obfuscation-proof structural check to
scripting_console.pythat parses the script and rejects any dunder attribute access (__base__,__flags__,__delattr__, ... — including escape vectors absent from the hand-maintained substring list) and any reference to a blocked builtin (getattr,eval,globals, ...), regardless of source formatting. The lowercased substring scan is retained as a first-pass net.
- Floored Werkzeug (
>=3.1.5) and Jinja2 (>=3.1.6) in corepyproject.tomldependencies — they ship transitively with flask and are always installed, so a clean resolver (norequirements-lock.txt) can no longer pull Werkzeug CVE-2026-21860 / CVE-2025-66221 (safe_joinWindows device-name DoS) or Jinja2 CVE-2025-27516 (sandbox breakout). - Floored
requests>=2.33.0andurllib3>=2.6.3in thestandardandallextras, where they enter via faster-whisper's huggingface-hub fetch path — closing urllib3 CVE-2026-21441 (decompression-bomb DoS) and requests CVE-2026-25645 in the release-audited lane. - Synced
requirements.txtflask/waitress floors to the pyproject values and added the new security pins;tests/test_dependency_surface.pynow asserts the transitive floors and the resolver lane matches the lockfile.
- Added script-aware font fallback chains to
styled_captions.pyfor CJK (Noto Sans CJK, MS Gothic, SimSun, Malgun, Meiryo, Yu Gothic), Indic (Noto Sans Devanagari/Bengali, Mangal, Vrinda, Nirmala), and RTL (Noto Sans Arabic/Hebrew, Tahoma) scripts in both Pillow and Skia renderers. _load_font()and_load_skia_typeface()now accepttext_hintto detect caption text scripts and try script-capable fonts before falling back to Western fonts.- Added
get_caption_font_info(text, style_name)for the UI to surface which fallback font was selected or warn when no capable font is available for the detected scripts. - Added Bengali fixture (
bengali_indic) to caption Unicode validation (F223), bringing complex-script fixture count from 5 to 6. - Added
rtl_arabicburn-in style preset with right-aligned bottom positioning (ASS alignment 6) for natural RTL reading direction.
- Added D3D12VA encoders (h264_d3d12va, hevc_d3d12va, av1_d3d12va) to hw_accel.py — vendor-agnostic Windows hardware encoding for non-NVIDIA users when FFmpeg 8.x is available.
- Added Vulkan Video encoders (h264_vulkan, hevc_vulkan, av1_vulkan) for cross-platform GPU encoding via Vulkan Video API.
- Updated priority order: nvenc > qsv > amf > d3d12va > videotoolbox > vulkan.
- Added quality presets and test-encode options for both new HW types.
- Added 5 new tests for D3D12VA/Vulkan parsing, priority, and verification.
- Wired D3D12VA and Vulkan into
/hw/encoderoute validation and all HW export preset descriptions. Installer version strings updated for FFmpeg 8.1.
- video_editing.py: Fixed
speaker_map=→speaker_to_track=ingenerate_multicam_cuts()call. The route passed a keyword argument that didn't match the function signature, causing a TypeError at runtime on every multicam cut request.
- job_store.py: Wrapped PRAGMA setup in try/except to close the connection on failure instead of leaking it (lines 134-140).
- job_store.py: Added
PRAGMA wal_checkpoint(TRUNCATE)before closing connections inclose_all_connections()to prevent orphaned WAL/SHM files on Windows shutdown.
- segment_sam2.py: Added
cap.isOpened()check aftercv2.VideoCapture()to fail early with a clear error instead of silently producing corrupt output. - multicam_visual.py: Added
cap.isOpened()check inanalyze_frames().
- segment_sam3.py: Fixed unreliable
frame_countscope check ("frame_count" in dir()→ initialized at top), addedVideoCapture.isOpened()/VideoWriter.isOpened()validation, moved GPU cleanup (del predictor, state+empty_cache()) into afinallyblock to guarantee cleanup on exception paths. - semantic_video_search.py: Fixed unused
fpsvariable in_extract_key_frames(), fixed progress callback always reporting 0% (now computes actual percentage), fixed temp directory leak inbuild_clip_index()andsemantic_search()(addedfinally: shutil.rmtree), removed deadfamilyvariable from_compute_frame_embeddings. - object_intel_routes.py: Fixed 4 instances of
validate_filepath()misused on output directories (should bevalidate_path()— files vs. directories use different validation functions).
- Added
SEARCH_ENGINESregistry tosemantic_video_search.pywith 4 engines: CLIP ViT-B/32 (default), CLIP ViT-L/14, SigLIP ViT-B/16, and SigLIP 2 ViT-B/16. - Cache keys now include engine ID and schema version — different models use separate cache directories, preventing embedding dimension mismatches.
build_clip_index()andsemantic_search()accept anengineparameter.- Model loading supports both CLIP (CLIPModel/CLIPProcessor) and SigLIP (AutoModel/AutoProcessor) families.
- Added
list_search_engines()function andGET /search/semantic/enginesroute for UI engine discovery. - Routes
/search/semanticand/search/semantic/indexnow acceptengineparameter to select among registered models.
- Added
core/multicam_visual.pywith MediaPipe Face Mesh lip movement scoring and shot type classification (wide/medium/close by face-to-frame ratio) for multicam cut refinement. /video/multicam-cutsnow acceptsmode: "audio+visual"to combine diarization with visual lip movement analysis. Cross-mic bleed is resolved by checking which on-camera face is actually speaking.- Each cut in visual mode gains
visual_confidence,shot_type, and optionalvisual_overrideannotations. - Added
check_visual_multicam_available()tochecks.py.
- Added
core/segment_sam3.pywith text-prompted object segmentation and tracking via SAM 3, plussegment_video_auto()with auto-fallback to CLIP+SAM2 when SAM3 is not installed. - Added
POST /video/object-remove/sam3route accepting text queries (e.g., "the watermark in the corner") with SAM2 fallback. - Added
GET /video/object-remove/engineslisting available segmentation backends with capability flags (text_prompts, click_prompts, box_prompts). - Updated
get_removal_capabilities()to includesam3availability. - Added
check_sam3_available()tochecks.py. - Added
/video/object-remove/sam3to queue allowlist.
- Updated README tagline to lead with silence-cut-to-timeline, stem separation, voice cloning, animated captions, local LLM, and social export.
- Added "What OpenCut adds beyond Premiere 26" comparison table contrasting 10 OpenCut-unique capabilities against Adobe's native feature set.
- Added Descript to cost comparison table with stem separation and voice clone columns across all competitors.
- Documented Premiere 26.x manifest compatibility in UXP panel section (CEP [13.0,99.9], UXP minVersion 25.6).
- Raised onnxruntime floor from
>=1.25to>=1.26in both[ai]and[ai-gpu]extras (pyproject.toml). 1.26.0 hardens multiple OOB/overflow scenarios and replaces unrestricted Pythonsetattrconfig with an allowlist. - Documented the floor raise rationale in
docs/PYTHON_ADVISORIES.md.
- Added Spanish diacritics to 400 keys in UXP es.json (507 accent/tilde characters introduced; zero existed before).
- Replaced hardcoded English "-- Select a clip --" in main.js with i18n
t()call and added corresponding en.json/es.json keys. - Added
scripts/lint_locales.py— locale lint checking key uniqueness, placeholder parity between en/es, missing keys, and diacritics regression. Supports--checkflag for CI enforcement.
- Merged duplicate
@media (prefers-reduced-motion: reduce)blocks into one. - Completed light theme variable block with missing accent gradients, mesh background effects, and glow-opacity tokens (8 new variables).
- Retokenized 14 hardcoded hex color literals in light theme overrides to use
CSS custom properties (
--text-primary,--text-secondary,--bg-surface,--bg-raised).
- Flask floor raised to >=3.1.3 (CVE-2026-27205 Vary:Cookie info disclosure).
- Waitress floor raised to >=3.0.1 (CVE-2024-49768/49769 request smuggling).
- PyInstaller pinned to >=6.0 in CI (CVE-2025-59042 local privilege escalation).
- Added Python 3.13 test matrix job running core test suite on Ubuntu.
- Added Python 3.13 classifier to pyproject.toml.
- Deduplicated 113 duplicate keys in UXP en.json (1497 → 1384 unique keys).
formatI18nnow uses a function replacement to prevent$&and$'in substitution values from corrupting output.
- Caption export preflight (
POST /captions/export/preflight) checks segment validity, timecode range, QC compliance, and host version before export. Returns a fallback strategy (native, srt_sidecar, or burnin) when native Premiere caption export is risky. Premiere 26.0/26.0.1 flagged based on community reports.
- Wizard and audio-preview dialogs now route through the overlay stack (activateOverlay/deactivateOverlay) for proper focus trap, inert background, and topmost-only Escape dispatch. Removes the waterfall Escape listener that closed every visible surface at once. Audio preview gains aria-modal="true".
detect_auto_editor_generation()distinguishes native v30+ Nim binary from legacy v29 pip package. Install hints across model cards and feature registry now list both install paths.
- Local-only privacy mode via
OPENCUT_LOCAL_ONLY=1env var or/settings/local-onlyUI toggle. Blocks cloud LLM providers (keeps Ollama), cloud vision APIs, cloud TTS, social uploads, stock search, and telemetry with structured local-alternative error messages.
- Removed stale ROADMAP-NEXT.md and PROJECT_CONTEXT.md references from CONTRIBUTING.md and docs/ROADMAP.md pointer file.
- CEP first-run, launcher, disabled-action, and settings copy now frames the workflow around clear workspace actions instead of shortcut-first language.
- CEP and UXP panel surfaces now share tighter 8px geometry, clearer focus and disabled states, calmer empty-state feedback, and a more cohesive blue/teal premium accent system across onboarding, workspace, tabs, cards, and controls.
- UXP tab navigation now binds before backend discovery so the panel remains navigable while the local backend is offline or still being detected.
- Settings no longer exposes non-English UI language choices before matching locale files ship, preventing a selection that cannot persistently localize the panel.
- Settings import now sanitizes local panel preferences before writing
localStorage, ignoring unknown keys and unsupported preference values while surfacing storage failures as a warning toast.
- Standalone TTS, auto-dubbing, and overdub workflows now default to local-first TTS selection; Edge TTS and external API providers run only when explicitly selected.
- Release smoke now catches MCP registry version drift, the committed MCP registry manifest is back on the package version, and stale UXP harness/i18n guardrails have been brought back in sync.
- README test-count badge now syncs to the live suite count so the release readiness gate stays green after new coverage lands.
- Release smoke and doc-size drift checks now target only live documentation
surfaces (
CLAUDE.mdandREADME.md) instead of removed planning files. - Version sync now covers the security support table, CEP package-lock root metadata, and the C2PA claim-generator string so release smoke fails when those public version surfaces drift.
- Character embeddings and depth-compositor archives now call
np.loadwith pickle explicitly disabled, with regression coverage for both paths.
- Preview-cache metadata is now scoped to the active cache directory and ignores metadata entries whose files resolve outside that directory before eviction, invalidation, or flush can delete them.
- Docker HTTP containers now set
OPENCUT_ALLOW_REMOTE=1alongside the intentional0.0.0.0bind, preventing startup crash-loops while preserving the non-loopback auth gate.
- README planning links now resolve in a fresh clone and name installer artifacts by the release version pattern instead of a stale filename.
- Standalone depth/model-loading installs now require
transformers>=5.3; the lower Transformers floor is confined to the explicit, documentedtorch-stack/WhisperX exception and the advisory audit allow-list now covers the current Transformers config-injection CVE only in that opt-in lane.
- Default CORS is now closed instead of allowing
null/file://, and/healthwithholds CSRF bootstrap tokens from those origins even if they are explicitly added back for compatibility. - Non-loopback server binds now serve through Waitress instead of Flask's Werkzeug development server, covering the Docker/remote runtime path while keeping local loopback/debug launches on the dev server.
- MCP HTTP sidecar binds on non-loopback interfaces now require the persistent
X-OpenCut-Authtoken before serving health, tool-list, or JSON-RPC calls. - Opt-in generated MCP route tools now run the same fail-fast path validation
as curated tools, including nested
queryandbodypath fields. - Remote HTTP startup now fails closed when
OPENCUT_ALLOW_REMOTE=1is set but the auth-token middleware cannot be installed. - Every mutating Flask request now passes through a global CSRF gate, so new POST/PUT/PATCH/DELETE routes stay protected even if a handler omits the per-route decorator.
- Kinetic text, data-animation, shape-animation, and IMF export routes now clamp render dimensions and font sizes before dispatching to allocation-heavy render code.
- The writable
~/.opencut/packagesfallback install directory is now appended tosys.pathinstead of taking priority over stdlib and bundled modules.
- UXP job submission now rejects concurrent backend jobs before they overwrite the shared tracker/SSE stream, and job-action buttons stay locked while a backend job is starting or running.
- The UXP Refresh button now rescans backend ports 5679-5689, and failed background health checks retry against a newly detected port before staying offline.
- The UXP live-updates WebSocket now consumes the bridge URL reported by the backend, uses a capped reconnect backoff, and honors manual disconnects so the Stop button actually stops reconnect attempts.
- UXP job polling now tolerates short
/status/<job_id>interruptions before failing a running job, preventing one dropped request from abandoning work. - Periodic UXP backend health checks no longer repaint the workspace as connecting/offline before each background probe completes.
- FCC caption display settings now unwrap BackendClient response payloads before populating token selects or applying preview CSS, making the compliance card functional again.
- Cancelling an active job now clears any button left in the panel's loading state, so the initiating action is usable again without reloading the panel.
- Closed the unvalidated
output_pathwrite sinks.@async_jobvalidates only the primary input filepath; eight route modules forwarded a user-suppliedoutput_path/output_file/db_pathstraight into an FFmpeg-yoropen(..., "w")sink, allowing arbitrary local file overwrite via a crafted request (CSRF-protected, but reachable by any local caller). All secondary write targets now passvalidate_output_path/validate_path:audio_production_routes(declip/dehum/decrackle/dewind/dereverb, room-tone generate/fill, M&E mix, fingerprintdb_pathread+write)integration_routes(adjustment-layers apply, flight-map, jog-mapping save)overlay_routes(safe-zones + the threeoutput_path_overrideroutes)solver_agent_routes(3D overlay),timeline_auto_routes(auto-mix — replaced the brittle startswith/colon check that let absolute paths and..\traversal through),cloud_distrib_routes(farm render),editing_workflow_routes(7 export/assemble routes),color_mam_routes(22 output sinks via a shared validated helper).
- Cut Review panel was completely broken. A
for (var t = ...)loop counter inshowCutReview()shadowed thet()i18n function in the same scope, so every silence / filler / auto-edit / highlights / repeat-detect completion threwTypeError: t is not a functionand the review panel never opened. Renamed the loop variable. - A throwing job-done listener could lock the UI forever. The completion
dispatch loop ran listeners with no isolation before
hideProgress(), so any listener exception left the processing banner andbody.job-activepointer lock stuck on. Each listener is now wrapped in try/catch. - Escape no longer silently cancels a running job when it was pressed only to dismiss a dropdown, menu, wizard, modal, or popover. Added a single "any dismissible surface open" guard to the cancel-job shortcut.
- Enter no longer hijacks focused controls. The "Enter runs the primary action" shortcut now bails when focus is on a button/link/tab/dropdown/ menuitem, so keyboard activation of those controls works and an unrelated job isn't launched.
- ExtendScript string safety: migrated the five remaining PremiereBridge
call sites (importFiles, removeSequenceMarkers, unrenameItems,
removeImportedSequence, removeImportedItem) from the narrow
replace(/'/g,"\\'")escape toescSingleQuote(), which also handles newlines and U+2028/U+2029 line separators in clip/marker names. - Accessibility:
document.documentElement.langnow tracks the active locale so assistive tech uses correct pronunciation after a language switch. - Stale per-run time estimate is cleared at job start instead of lingering from the previous run until the new estimate resolves.
- Styled-caption overlay encoding now drains FFmpeg stderr in a background thread while raw frames are piped to stdin, preventing stderr backpressure from deadlocking long renders.
- The Real-ESRGAN upscale tier now resolves and caches official model weights
before constructing
RealESRGANer, instead of passingmodel_path=None. - Caption burn-in failed on every Windows drive-letter path. The
subtitles=/ass=filter value dropped colon escaping, soC:/...was parsed as filenameC. Addedescape_filter_path()with the verified two-level escaping (drive colons, apostrophes, spaces) and applied it. Verified end-to-end against real ffmpeg. - Large edits crashed on Windows with "filename or extension is too long".
Silence-removal export and
speed_up_silencesbuilt-filter_complexas a single argv string; past ~220 segments this exceeds the 32 KB command-line limit (OSError WinError 206). Both now spill large graphs to a temp file and use-filter_complex_script. Verified: 300-segment graph (43 KB) fails inline, succeeds via script file. - Merge/concat broke on filenames with apostrophes and corrupted non-ASCII
names. The concat-demuxer escaping used
\'(rejected by ffmpeg) and the default text codec (cp1252 on Windows). Addedwrite_concat_list()(UTF-8 + the correct'\''idiom). Verified: apostrophe and Cyrillic/CJK names. - All core concat-demuxer writers found by the repo-wide scan now use
write_concat_list()or the shared concat-line formatter instead of local platform-codec path writes. - New shared escaping helpers in
opencut/helpers.py(escape_filter_path,escape_drawtext,write_concat_list) with a string-level regression test (tests/test_ffmpeg_escaping.py) locking the ffmpeg-verified behavior for CI. escape_drawtextdocuments/requiresexpansion=none: under default drawtext expansion a literal%cannot be escaped at all ("Stray %") and caption text containing%{...}would be interpreted as an expression.- Caption styles, click/keystroke overlays, tickers, quiz cards, telemetry
overlays, callouts, audiograms, brand watermarks, end screens, and guest
lower-thirds now use the shared
escape_drawtext()contract withexpansion=none. - Audio-only export to
.aacand.oggno longer fails — those containers were handed the mp3 encoder; now mapped toaac/libvorbis. - Progress-parsing FFmpeg subprocess now decodes stdout/stderr as UTF-8 with
replacement, preventing a
UnicodeDecodeErrorin the stderr drain thread from deadlocking the progress loop on non-ASCII output.
- Transcription timeout was a no-op. The
ThreadPoolExecutorwas used as a context manager, whose exit blocks onshutdown(wait=True)until the worker finishes — so a timed-out transcription still pinned the caller for the full run. Switched to manual non-blocking shutdown withcancel_futures. smart_trimnow detects trailing silence (a clip that ends in silence emits an unmatchedsilence_start), so "trim trailing silence" actually works.- Stem recombination uses
amix=...:normalize=0; the defaultnormalize=1scaled each stem by 1/N, making a recombined mix ~12 dB too quiet. loudness_matchclamps non-finite (-inf) measurements from silent input (previously produced invalid-InfinityJSON and out-of-range pass-2 args) and anchors on the last loudnorm JSON block.smart_reframeguards against ZeroDivisionError from a zero aspect-ratio component (e.g. "16:0") or a zero source dimension from a probe failure.extract_audio_wavno longer leaks its temp WAV when extraction fails.speed_up_silencesnow treats MP3/M4A cover art as attached pictures, not real video streams, so audio-only files stay on the audio concat path.- Waveform image uses a unique temp name instead of
waveform_<pid>.png, which collided across requests in the long-lived server. waveform_timelineusesarray.arrayinstead of Python list for PCM samples (~8x less memory for long audio files).smart_trimnow surfaces subprocess timeout/errors instead of silently returning an empty result as "no speech detected".- Export cancel now calls
proc.wait()after killing FFmpeg before unlinking the partial output file, preventing silent failure on Windows. preview_framechecks FFmpeg return code and output file size before returning success.- Captions temp WAV cleanup uses
_schedule_temp_cleanupwhen immediate unlink fails from a timed-out Whisper thread on Windows. - auth.json now applies a restrictive DACL via
icaclson Windows (was POSIX-only chmod). - Multiview/repurpose routes now validate secondary file paths
(
video_paths,content_path,reaction_path) before forwarding to core. - Installers prefer
requirements.txt/requirements-lock.txtover loose pip specs in fallback paths. - CLI
loudness-matchresolves bare filenames to absolute paths before passing output_dir. - CLI
auto-zoomuses.get()for width/height to avoid KeyError. - Fixed placeholder
github.com/opencutURLs in CLI banner and InstallerBuilder.
- Job history dedupe now compares by
job_id(ortype+createdAtfallback) instead of(type, status). - NLP auto-execute raised confidence threshold to 0.8 with a confirm step.
body.job-activeUI lock now setsinerton main content for keyboard and AT users (was pointer-events-only).showAlert()accepts an explicit tone parameter, bypassing English-only regex inference for non-English locales.- Time estimate reads numeric clip duration from state instead of parsing rendered DOM text.
- Command palette section labels ("Recent", "Favorites", etc.) are now
i18n-configurable via
sectionLabelson the palette context. - Language dropdown reduced to only
en(the only shipped locale).
- Await precedence fix:
(await getOutPoint?.())?.secondsinstead ofawait getOutPoint?.()?.seconds. addMarkersnow awaitsgetFirstMarkerAtTimeso names/colors apply.- Enter key handlers guarded with
hasActiveJob()to prevent duplicate submissions. - 10 handlers changed
elsetoelse if (!r.ok)so ok-without-job_id synchronous responses aren't reported as failures. udt-smoke.jsgated behindlocalStorage opencut_debug=1.postMessagetargetOrigin restricted from"*"tolocation.origin.cep_node.execpassthrough blocked in the WebView shim.
- Inno Setup PlayerDebugMode registry writes use
runasoriginaluserviareg.exeso keys land in the invoking user's HKCU, not the admin's. - VBS launcher uses
WshShell.Environmentinstead ofcmd /cstring concatenation, preventing injection from paths containing&or^.
- Background sweep threads (temp cleanup, disk monitor) are skipped during
test runs via
create_app(testing=True). - README: added June 2026 cost comparison table vs CapCut Pro, Submagic, AutoCut, and FireCut.