Skip to content

feat: six AI features across two phases — dubbing, bg removal, B-roll - #4

Open
Ekaanth wants to merge 2 commits into
mainfrom
feat/ai-dubbing-broll-multicam-speaker-captions
Open

feat: six AI features across two phases — dubbing, bg removal, B-roll#4
Ekaanth wants to merge 2 commits into
mainfrom
feat/ai-dubbing-broll-multicam-speaker-captions

Conversation

@Ekaanth

@Ekaanth Ekaanth commented Jul 20, 2026

Copy link
Copy Markdown
Owner

Phase 1 (new AI capabilities, privacy-first):

  • AI Dubbing (local engine upgrade): new NLLB-200 translate-service (services/translate-service, port 8427) gives the existing dubbing flow a fully-local translation path. The "local" engine now batch-translates via NLLB with LLM fallback. Also adds a full-pipeline alternative at /api/dub/* (dubbing_service orchestrates whisper → translate → XTTS → ffmpeg).

  • Video Background Removal: per-frame rembg matting via a new /remove-bg-video endpoint on image-service (in-process job store + status polling). Proxied through ai-backend /api/background/*. Result inserted non-destructively as an alpha WebM above the source clip.

  • Auto B-roll from your footage: matches each transcript segment to the existing CLIP embedding index (IndexedDB) and auto-inserts the best clip. New /api/broll/* backend route. Reuses the existing visual-search index — no parallel store.

Phase 2 (workflow features, reuse-first — zero new services, zero new deps):

  • Multilingual captions: existing translate flow upgraded to NLLB-first with multi-language quick-add chips and per-track SRT/VTT export via the existing subtitle endpoint (now accepts an optional filename).

  • Edit by speaker: scope operations (remove / tighten gaps / isolate) to a single speaker using diarization. Cut/compact helpers extracted from Smart Cut into lib/timeline-edits.ts (shared, no behavior change).

  • Auto multicam sync: client-side audio cross-correlation aligns camera angles without timecode. Pick a reference, preview offsets with scores, apply in one undoable transaction. Pure Web Audio, no backend.

Infra: job_queue.py parameterized per-domain Redis prefix (dub_job: / vbg_job: singletons) so jobs don't collide with YouTube's. translate-service added to docker-compose + gpu override. README + roadmap updated.

Verification: biome clean on all 22 changed/new frontend files; python3 -m py_compile clean on all changed backend files.

⚠️ READ BEFORE SUBMITTING ⚠️

We are not currently accepting PRs except for critical bugs.

If this is a bug fix:

  • I've opened an issue first
  • This was approved by a maintainer

If this is a feature:

This PR will be closed. Please open an issue to discuss first.


Session Details

  • Session: View Session
  • Requested by: Unknown
  • Address comments on this PR. Add (aside) to your comment to have me ignore it.

…, captions, speakers, multicam sync

Phase 1 (new AI capabilities, privacy-first):

- AI Dubbing (local engine upgrade): new NLLB-200 translate-service
  (services/translate-service, port 8427) gives the existing dubbing flow a
  fully-local translation path. The "local" engine now batch-translates via
  NLLB with LLM fallback. Also adds a full-pipeline alternative at
  /api/dub/* (dubbing_service orchestrates whisper → translate → XTTS → ffmpeg).

- Video Background Removal: per-frame rembg matting via a new
  /remove-bg-video endpoint on image-service (in-process job store + status
  polling). Proxied through ai-backend /api/background/*. Result inserted
  non-destructively as an alpha WebM above the source clip.

- Auto B-roll from your footage: matches each transcript segment to the
  existing CLIP embedding index (IndexedDB) and auto-inserts the best clip.
  New /api/broll/* backend route. Reuses the existing visual-search index —
  no parallel store.

Phase 2 (workflow features, reuse-first — zero new services, zero new deps):

- Multilingual captions: existing translate flow upgraded to NLLB-first
  with multi-language quick-add chips and per-track SRT/VTT export via the
  existing subtitle endpoint (now accepts an optional filename).

- Edit by speaker: scope operations (remove / tighten gaps / isolate) to a
  single speaker using diarization. Cut/compact helpers extracted from
  Smart Cut into lib/timeline-edits.ts (shared, no behavior change).

- Auto multicam sync: client-side audio cross-correlation aligns camera
  angles without timecode. Pick a reference, preview offsets with scores,
  apply in one undoable transaction. Pure Web Audio, no backend.

Infra: job_queue.py parameterized per-domain Redis prefix (dub_job: /
vbg_job: singletons) so jobs don't collide with YouTube's. translate-service
added to docker-compose + gpu override. README + roadmap updated.

Verification: biome clean on all 22 changed/new frontend files;
python3 -m py_compile clean on all changed backend files.

@vorflux vorflux Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security Review

Overall the code quality is solid — path-traversal guards are present, file extensions are validated on most endpoints, job IDs use UUIDs, no secrets or API keys are hardcoded, and FFmpeg commands are properly constructed with asyncio.create_subprocess_exec(*cmd) (list-based, no shell injection risk). The architecture of proxying through the ai-backend rather than exposing internal services directly to the browser is a good pattern.

Found a few hardening opportunities — none critical given this is a local-first Docker app, but worth addressing:

Summary of findings

  1. No file size limits on upload endpoints (background, dub, broll, image-service) — disk/memory exhaustion risk
  2. Missing extension allowlist in background.py proxy — shouldn't forward unsupported files to the downstream service
  3. Internal service URLs leaked in 503 error messages — reveals infrastructure topology
  4. Permissive CORS on translate-serviceallow_credentials=True with allow_origins limited to localhost is acceptable for a local service, but worth being explicit about

See inline comments for details on each.


Review with Vorflux


@router.post("/remove-video")
async def remove_video_background(
file: UploadFile = File(...),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing file extension allowlist. Unlike dub.py (line ~82) and broll.py (line ~111), this proxy endpoint does not validate the uploaded file's extension before forwarding to the image-service. While the image-service does its own validation, validating early here avoids forwarding unsupported files unnecessarily and provides a clearer error to the caller.

def _service_down(url: str) -> HTTPException:
return HTTPException(
status_code=503,
detail=(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Info leakage: internal service URL exposed. The _service_down helper (background.py line 26-33, broll.py line 41, translate.py line 85, dub.py line 51) returns the internal service URL directly to the caller in a 503 detail message. For a local Docker app this is low severity, but it reveals infrastructure topology. Consider logging the URL on the server side and returning a generic 'service unavailable' message.

"""Stream a produced transparent WebM back to the browser.

The image-service owns the file; we proxy the bytes so the browser only
needs the ai-backend origin.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good: explicit path-traversal guard here (/, \\, ..). Consider adding this same guard pattern to the filename field in transcribe.py line 99, which accepts arbitrary user input and echoes it back in the response as a download suggestion.


@router.post("/create")
async def create_dub(
file: UploadFile = File(...),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No file size limit on upload. Reads the entire file into memory via await file.read() (line ~90) with no size check. A multi-GB video could exhaust memory or crash the backend. Same applies to broll.py line ~120, background.py line ~46, and image-service/app.py line ~610. Consider checking file.size (if available from the client) or limiting at the middleware/Starlette level with Request(max_size=...).

default=None,
description="Optional style settings for ASS format (font_name, font_size, primary_color, etc.)",
)
filename: str | None = Field(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unvalidated user input echoed in response. The new filename field accepts arbitrary strings and is returned verbatim in the JSON response (line ~136). The client uses this as a download filename, so it's low risk, but consider sanitizing it — at minimum strip path separators (/, \\) to prevent any confusion if a future client uses it differently.

"http://localhost:3100",
"http://localhost:5173",
],
allow_credentials=True,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Permissive CORS configuration. allow_credentials=True with allow_methods=["*"] and allow_headers=["*"] works for a local-only service but is a broad grant. Consider narrowing allow_headers to only the headers the frontend actually sends, and adding an explicit expose_headers list.

…sts, generic 503 messages, filename sanitization, and narrowed CORS

- Add file size checks (HTTP 413, 500MB) to all upload endpoints:
  background.py, dub.py (main file + speaker_wav), broll.py,
  transcribe.py _proxy_file_upload, image-service (remove_bg +
  remove_bg_video)
- Add video extension allowlist (ALLOWED_VIDEO_EXTS) to
  background.py proxy endpoint
- Replace internal service URLs in 503 detail messages with
  server-side logging + generic user-facing messages across all
  routes (background, broll, dub, translate, transcribe)
- Strip path separators from user-supplied filename in
  transcribe.py subtitle generation
- Narrow translate-service CORS: explicit methods + headers
  instead of catch-all wildcards

@vorflux vorflux Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security Review — All Issues Resolved ✅

Commit 8f989a0 addresses all 6 findings from the initial review, plus one additional URL leak caught during follow-up:

# Finding Resolution File(s)
1 No file size limits HTTP 413 checks against 500MB MAX_UPLOAD_SIZE background.py, dub.py, broll.py, transcribe.py, image-service/app.py
2 Missing extension allowlist ALLOWED_VIDEO_EXTS validated in proxy before forwarding background.py
3 URL leakage in 503 messages logger.error(...) server-side, generic detail to client background.py, broll.py, dub.py, translate.py, transcribe.py
4 Unsanitized filename Path separators (/, \) stripped transcribe.py
5 Permissive CORS Narrowed to GET,POST,OPTIONS / Content-Type,Authorization translate-service/app.py
Whisper URL in 503 (caught during re-review) Logged instead of leaked transcribe.py

All 7 files pass python3 -m py_compile. The changes follow existing patterns in the codebase (audio.py, engagement.py). No blocking issues remain.

Verdict: Approve


Review with Vorflux

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant