Celery doesn't allow calling result.get() within a task by default to
prevent deadlocks. Use allow_join_result() context manager since we've
already confirmed the task is complete via ready() polling.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add a dedicated Celery queue (ffmpeg) with concurrency=1 to serialize
all FFmpeg operations. This prevents CPU spikes when multiple render
tasks run in parallel with multiple languages.
Changes:
- Add ffmpeg_operations.py with run_ffmpeg_command and run_ffprobe_command tasks
- Update VideoRendererService to dispatch ffmpeg commands via the queue
- Add ffmpeg-worker service to docker-compose with --concurrency=1
- Configure main worker to exclude the ffmpeg queue
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Clarifies the UI when a job has no additional translation languages
requested, making it clearer that the job contains only the original
language assets.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When a video's source language is non-English (e.g., German), the
language asset was incorrectly marked as 'Translated'. Now correctly
shows 'Original' with a green badge for the primary/source language.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add validation for accessible_video_gcs (file exists, size 0.1MB-5GB)
- Add validation for retimed_captions_vtt_gcs when accessible video exists
- Add AD Videos count to asset validation panel
- Include retimed captions in VTT file count
- Remove AI confidence from validation panel and backend checks
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix video event listeners not re-attaching when video element remounts
(add activeTab?.videoUrl to useEffect dependency array)
- Add retimed_captions_vtt to VTT API response for accessible videos
- Use retimed captions for accessible video tab in VideoReviewPlayer
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add a comprehensive video review feature to the Final Review page that allows
reviewers to watch videos with caption overlays and add timestamped notes.
Backend:
- New ReviewNote model for MongoDB with job_id, asset_key, timestamp, content
- CRUD API endpoints at /jobs/{job_id}/review-notes
- Owner-only edit/delete permissions (admins can bypass)
- Database indexes for efficient querying
Frontend:
- VideoReviewPlayer component with video player and caption overlay
- NotesSidebar for viewing/adding notes with auto-highlight when video reaches timestamp
- SyncedCaptionList with auto-scroll and click-to-seek
- AssetTabs for switching between languages and accessible videos
- React Query hooks with 30s polling for collaborative updates
Features:
- Notes persist to database and are shared across all reviewers
- Notes highlight for 5 seconds when video playback reaches their timestamp
- Click note to seek video to that position
- Pause video to add note at current timestamp
- Accessible videos use retimed captions when available
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The method field (overlay/pause_insert) is metadata, not a downloadable
file. Including it in the downloads dict caused the frontend to render
a broken download link.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Update _get_video_properties() to extract audio sample_rate, channels,
and pix_fmt in addition to video properties
- Add _extract_segment_reencoded() for frame-accurate cuts using
re-encoding instead of stream copy (fixes keyframe-only cut limitation)
- Add _create_freeze_segment_matched() to enforce source audio property
matching (fixes silent pauses caused by sample rate mismatch)
- Update _render_pause_insert_method() to use new methods with uniform
encoding parameters
- Add -video_track_timescale 90000 for consistent timebase across segments
Root causes fixed:
1. -c copy could only cut at keyframes, causing audio dropouts
2. Sample rate mismatch (48kHz source vs 44.1kHz MP3) caused silent
freeze-frame segments when concatenated
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The accessible video render task was being dispatched to the 'render' queue
but no worker was listening to it. Added 'render' to:
- Dockerfile CMD args for worker queue list
- celery_worker.py import and log message
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add new deliverable type that renders video with audio descriptions embedded.
Supports two AI-determined methods:
- Direct Overlay: ducks original audio and overlays AD TTS (for minimal dialogue)
- Pause-Insert: freeze-frame video, insert AD, re-time subtitles (for significant dialogue)
Backend:
- Add Pydantic schemas for Gemini analysis response
- Add Gemini prompt and analyze_accessible_video_placement() method
- Add video_renderer.py service using FFmpeg for both rendering methods
- Add vtt_retimer.py service for pause-insert subtitle adjustment
- Add render_accessible_video.py Celery task
- Modify TTS service to return individual per-cue segments
- Update translate_and_synthesize.py to save segments and trigger rendering
- Update download endpoint to include accessible video outputs
Frontend:
- Add accessible_video_mp4 checkbox to NewJob form
- Update TypeScript types for new deliverable
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add checkbox selection for pending final review jobs
- Add bulk actions toolbar with Complete/Return to QC options
- Mirror QC Review bulk action pattern with parallel API calls
- Rejection notes required, completion notes optional
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The zod schema required title to be non-empty, but in multi-upload mode
the title field is hidden (titles are auto-generated from filenames).
This caused form validation to fail silently when clicking upload.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add multi-file drag-and-drop to upload multiple videos at once
- Each video creates its own job using filename as title
- Single file upload preserves current UX with editable pre-filled title
- Multi-upload mode shows file list, individual progress bars, and summary
- Parallel uploads (max 3 concurrent) with error handling and retry
- Settings (language, outputs, TTS) apply to all jobs in batch
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add "Download All Files" option to the bulk actions menu on the All Jobs page.
When selected, downloads all assets (source video, VTTs, MP3s for all languages)
from jobs in approved_english, approved_source, or completed status.
- Shows confirmation modal with eligible/ineligible job counts
- Downloads files sequentially with progress indicator
- Skips jobs not in approved/completed status with warning
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Updated Gemini ingestion prompt to explicitly require:
- Detect the spoken language first
- Write ALL outputs (summary, transcript, captions, audio_description) in that language
- Do NOT translate to English - keep everything in the original language
This fixes the issue where German videos would get English audio descriptions.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- QCDetail passes sourceLanguage to VoiceSelector instead of hardcoded 'en'
- VoiceSelector uses first language in selectedLanguages for default voice preview
- Removes hardcoded English assumption in displayLanguages logic
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add model selection (flash vs pro) for quality control
- Add speed slider (0.5x - 2.0x) for pacing adjustment
- Add style presets (neutral, calm, energetic, professional, warm, documentary)
- Add custom style prompt option for advanced customization
- New /tts/options endpoint returns available TTS options
- Voice preview now tests all settings so users hear exact output
- Backward compatible: all new fields have sensible defaults
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The preview button was caching audio but not invalidating the cache
when the user selected a different voice, causing the same audio to
play regardless of which voice was selected.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The Gemini TTS service uses pydub which requires ffmpeg to convert
audio formats. Previously only the Worker container had ffmpeg.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The import was using a non-existent module path `..deps` instead of
`...core.dependencies`, causing the API container to fail on startup.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add Gemini TTS service with 30 voices and 24 languages
- Add TTS API endpoints for voice listing and preview
- Add per-language voice selection in job creation form
- Add voice override at QC approval stage
- Add VoiceSelector and VoicePreviewButton components
- Update TTSPreferences model with provider and voice mapping
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Change model from gemini-2.5-pro to gemini-3-pro-preview
- Upgrade google-genai package from ^1.31.0 to ^1.56.0
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Configure WiredTiger directly via storage.wiredTiger.engineConfig.configString
to disable verbose logging including checkpoint progress messages.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Disable WiredTiger verbose logging via wiredTigerEngineRuntimeConfig
to filter out checkpoint progress messages from INFO level logs.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
For databases set up before migration tracking existed, this script
marks old migrations as "applied" in migration_history collection
so only new migrations will run.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add migration to update jobs collection validator with new statuses
- Update mongodb-init.js for fresh deployments
- Fix deploy.sh to properly run migrations with 'python migrate.py up'
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add sourceLanguage prop to VideoWithCaptions component
- Create shared getLanguageLabel utility for language code mapping
- Update QCDetail and JobDetail to pass source language
- Fix status messages to say "source content" instead of "English content"
- Update Downloads page to display proper language names
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add all logging components with verbosity 0 in mongod.conf
- Add WiredTiger verbose=[] to suppress checkpoint messages
- Add --quiet flag to mongod command
- Change healthcheck from mongosh to TCP port check to avoid
connection metadata spam every 30s
- Increase healthcheck interval from 30s to 60s
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The setValueAs transformation wasn't working correctly with radio
buttons in react-hook-form, causing the form value to remain a string
instead of being converted to a boolean. This caused Zod validation
to fail silently since the schema expects a boolean.
Fixed by using controlled radio buttons with explicit onChange handlers
that call setValue() with the proper boolean value.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The npm ci --only=production flag was skipping dev dependencies,
but TypeScript is needed to compile the frontend build.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Upload form now has "English / Different language" radio with optional language hint
- Gemini auto-detects language and saves outputs to outputs.{detected_language}
- QC review dynamically loads/saves VTT for source language
- New APPROVED_SOURCE status for non-English videos (APPROVED_ENGLISH kept for backwards compat)
- Translation pipeline reads from source language and passes source_language to Google Translate
- All existing English jobs continue to work unchanged
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>