Commit graph

186 commits

Author SHA1 Message Date
michael
577ed44dab fix: queue TTS regeneration for shifted cues when inserting AD cue
When a new AD cue is inserted in the middle of existing cues, the system
now automatically queues TTS regeneration for the new cue AND all cues
that shifted positions. This ensures MP3 file indices stay synchronized
with VTT cue indices, preventing cues from being silently dropped during
re-render.

Changes:
- VttEditor: Add onCueInserted callback to notify parent of insertions
- QCDetail: Track insertion context and queue TTS for all shifted cues
- rerender_accessible_video: Add warning log when cue/MP3 count mismatch

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 14:24:36 -06:00
michael
dab294f18a feat: streamline QC approval to skip translation pipeline
QC approval now transitions jobs directly to pending_final_review
since translation, TTS, and accessible video rendering happen before
QC review. Removes unnecessary translate_and_synthesize_task trigger
on approval.

- Update approve_source() to use PENDING_FINAL_REVIEW status
- Update bulk_approve_jobs() to use PENDING_FINAL_REVIEW status
- Remove translate_and_synthesize_task.delay() calls from both endpoints
- Update JobDetail progress indicator to reflect new flow
- Update CLAUDE.md state machine documentation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 10:37:37 -06:00
michael
48bcea349e feat: add cue numbering to AD cues in VttEditor
Display 0-based cue index badges in the editable AD cue list to match
the numbering shown on the timeline preview, helping users associate
timeline markers with their corresponding editable cues.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 10:28:48 -06:00
michael
0919dbf7bd feat: move accessible video method selection to job creation
Since accessible video is now rendered immediately on upload, the method
selection (pause_insert vs overlay) is moved from QC Review to the New
Job panel. The bulk approval modal for selecting the method is removed.

- Add method selector UI to NewJob.tsx below accessible video checkbox
- Remove method selector from QCDetail.tsx approval flow
- Remove bulk approval modal from QCList.tsx

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 10:25:40 -06:00
michael
30483b3ec1 fix: preserve cue order when consolidated AD cues share same pause point
Add ad_cue_index as secondary sort key when sorting placements, ensuring
that consolidated cues maintain their original VTT order (cue 0 before cue 1).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 10:15:12 -06:00
michael
e371dc401a feat: add save button to voice settings panel for TTS regeneration
Add ability to save voice settings changes in QC Review screen without
needing to approve the job. When saved, all TTS segments are regenerated
across all languages with the new voice settings.

Changes:
- Add PUT /jobs/{id}/tts-preferences endpoint to update TTS preferences
- Add UpdateTTSPreferencesRequest schema
- Add updateTTSPreferences API method and useUpdateTTSPreferences hook
- Add Save Voice Settings button with change detection to QCDetail

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 09:05:56 -06:00
michael
b9c2fd93ac feat: streamline VTT cue editing to single-step save
Eliminate the two-step save process for VTT cue edits. Previously users
had to (1) save individual cue edits, then (2) click "Save Changes" in
a separate yellow notification box. Now saving a cue immediately persists
to the database and queues TTS regeneration for AD cues.

Changes:
- Add onCueSave callback prop to VttEditor for immediate persistence
- Add per-cue saving indicators and error handling with retry
- Remove hasUnsavedChanges state and yellow "Unsaved Changes" box
- Remove Ctrl+S keyboard shortcut (no longer needed)
- AD cue saves automatically queue TTS regeneration

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 08:52:23 -06:00
michael
9d56306052 fix: display pause point timing in seconds instead of milliseconds
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 08:32:34 -06:00
michael
f47820a6a4 fix: make source_ms optional for backward compatibility with existing jobs
Existing jobs in the database don't have source_ms field. Making it
optional allows the API to load these jobs without validation errors.
The re-render task already handles the fallback to original_ms when
source_ms is None.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 07:04:08 -06:00
michael
a6cd4cde07 fix: store source video coordinates in pause points for correct re-rendering
The re-render task was using pause point coordinates from the accessible
video timeline (which includes freeze frame durations) instead of the
original source video coordinates. This caused pause points to exceed
the source video duration and get clamped incorrectly.

Changes:
- Add source_ms field to PausePointData model to store source video cut point
- Update video_renderer.py to populate source_ms when building pause points
- Update rerender_accessible_video.py to use source_ms for placement calculations
- Apply user adjustments as relative offsets (delta-based adjustment)
- Update API responses and TypeScript types to include source_ms
- Add backward compatibility fallback for jobs without source_ms

Note: Existing jobs need to be re-processed from initial render to populate
the new source_ms field.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 10:48:41 -06:00
michael
a59dbb60ac fix: register rerender_accessible_video task with Celery worker
The task was created but not imported in the Celery task registry,
causing "Received unregistered task" error when triggering re-render.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 10:12:50 -06:00
michael
bcfc026e61 feat: add migration for rendering_qc status in MongoDB schema
The rendering_qc status was added to the Python model but was missing
from the MongoDB schema validator, causing WriteError when setting
job status during QC re-rendering.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 10:05:05 -06:00
michael
676490ac65 feat: auto-queue TTS regeneration when AD script cues are edited
When the user edits the audio description VTT and saves, the system now:
1. Compares original vs current AD cue text
2. Identifies which cues were modified
3. Automatically queues TTS regeneration for modified cues
4. Updates the Render Changes panel to show queued regenerations

This enables the "Render Changes" button when AD script edits are saved,
ensuring the accessible video can be re-rendered with updated TTS audio.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 09:59:04 -06:00
michael
d965d1467a fix: use rendered video coordinates for pause point positions
Pause points were being stored with source video timestamps instead of
rendered video timeline coordinates. This caused misalignment between
the pause point markers and freeze frame segments in the timeline UI.

Now pause points are calculated from the freeze frame segment start
positions in the rendered timeline, ensuring they align correctly
with the AD audio segments.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 09:37:31 -06:00
michael
65a7404c87 fix: use proper signed URL generation for accessible video preview
The generate_signed_url() was called with expiration=3600 as an integer,
but GCS expects a datetime or timedelta. Now uses gcs_service.get_signed_url()
which properly calculates the expiration timestamp.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 09:28:51 -06:00
michael
81d4e6a3cc fix: convert datetime fields to ISO strings in edit state response
The AccessibleVideoEditStateResponse schema expects string timestamps
but the API was passing raw datetime objects from MongoDB. Now converts
last_render_at and requested_at to ISO format strings.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 08:49:29 -06:00
michael
aa6777d2c2 feat: add QC accessible video review and editing capabilities
- Reorder workflow: translations now happen BEFORE QC Review step
- Add language tabs to switch between translated languages in QC
- Add video mode tabs (Original Video / Accessible Video)
- Add interactive timeline preview showing video segments and AD cues
- Enable pause point adjustment with millisecond precision
- Add TTS regeneration queue for selective cue re-synthesis
- Add re-render controls with optional Whisper refinement
- Persist video segments and TTS MP3s to GCS for editability
- Add new RENDERING_QC job status for re-render operations
- Create 5 new API endpoints for accessible video editing
- Add rerender_accessible_video.py Celery task

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 08:32:27 -06:00
michael
c5f59b1079 fix: use local ffprobe for freeze segment duration measurement
The previous implementation incorrectly used _get_video_duration which
in Cloud Run mode uses the cached source video URI instead of actually
measuring the freeze segment files. This caused all freeze segments to
report the source video duration (~78s) instead of their actual duration.

Changed to use _get_video_duration_local directly since freeze segments
are local files and need to be measured directly via ffprobe.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-05 16:11:03 -06:00
michael
add958008a fix: use actual freeze segment durations for VTT subtitle retiming
Subtitles were appearing progressively out of sync (~1.0s early per AD)
because the VTT retimer calculated freeze durations theoretically
rather than using actual rendered segment durations.

Changes:
- video_renderer: Measure actual freeze segment duration after creation
- video_renderer: Return updated placements with actual_freeze_duration
- vtt_retimer: Prefer actual_freeze_duration over calculated values
- render_task: Pass actual durations to VTT retimer

This ensures subtitle timing matches the real video timeline regardless
of any FFmpeg encoding variations.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-05 15:52:57 -06:00
michael
e44210ea64 feat: auto-rewrite TTS cues that fail synthesis
When TTS synthesis fails after 3 retries, the system now:
- Sends problematic cue text to Gemini for TTS-safe rewriting
- Updates the VTT file in GCS with rewritten text
- Retries TTS synthesis with the new text
- Records successful rewrites in job.tts_rewrites field

UI changes:
- JobDetail shows amber caution box with original/rewritten text
- JobsList shows warning icon next to jobs with rewrites
- Error display clarifies text shown is "after rewrite attempt"

Files changed:
- backend/app/models/job.py: Add tts_rewrites field
- backend/app/prompts/gemini_tts_rewrite.md: New prompt template
- backend/app/services/gemini.py: Add rewrite_tts_cue method
- backend/app/tasks/tts_synthesis.py: Add VTT update utilities
- backend/app/tasks/translate_and_synthesize.py: Rewrite+retry logic
- frontend/src/types/api.ts: Add TTSRewriteItem type
- frontend/src/routes/jobs/JobDetail.tsx: Caution display
- frontend/src/routes/jobs/JobsList.tsx: Warning indicator

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-05 14:42:50 -06:00
michael
76c4c60b0d fix: add tts_failed and render_failed to MongoDB schema validator
MongoDB was rejecting status updates to 'tts_failed' and 'render_failed'
because these values weren't in the schema validator's enum, even though
they were defined in the Python JobStatus model.

This caused TTS failures to leave jobs stuck in 'tts_generating' status
with no error feedback to users - the WriteError from MongoDB prevented
the status and error fields from being updated.

The migration adds both failed statuses to the jobs collection validator.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-05 14:09:41 -06:00
michael
83e4752327 feat: add server-side zip download for bulk job downloads
Replace sequential browser-based bulk download with server-side zip
generation. When users select "Download All Files" from bulk actions,
the system now creates a single organized .zip file containing all
job assets.

Changes:
- Add POST /jobs/bulk/download endpoint that streams zip to client
- Add BulkDownloadRequest schema for the new endpoint
- Create zip_download.py service with streaming zip generation
- Update frontend to call new endpoint and download single zip file
- Organize files in zip by job title and language subdirectories

Zip structure:
accessible_video_YYYYMMDD_HHMMSS.zip
└── {job_title}/
    ├── source.mp4
    └── {lang}/
        ├── captions.vtt
        ├── ad.vtt
        ├── ad.mp3
        ├── accessible_video.mp4
        └── accessible_captions.vtt

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-04 15:57:57 -06:00
michael
8606877d01 fix: properly set tts_failed status when TTS synthesis fails
The TTS error handling had a bug where failed jobs stayed in
'tts_generating' status instead of being set to 'tts_failed'.

Root cause: synthesize_cue_task used autoretry_for=(Exception,)
which raises the original exception after max retries, not
MaxRetriesExceededError. The exception handler never fired.

Changes:
- tts_synthesis.py: Replace autoretry_for with manual retry logic
  that returns a failure dict on final failure instead of raising
- translate_and_synthesize.py: Add propagate=False to group.get()
  to safely retrieve all results including failures
- translate_and_synthesize.py: Update outer exception handler to
  set job status to tts_failed, store error details, and broadcast
  status update via WebSocket

Now TTS failures will:
1. Set job status to 'tts_failed'
2. Store detailed error info (cue index, text, message)
3. Show error in UI with retry button

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-04 10:45:33 -06:00
michael
8bd9be6353 fix: TypeScript type narrowing for TTS error display
Use typeof checks for proper type narrowing of unknown error fields

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-03 21:41:12 -06:00
michael
6915cf46af feat: add TTS retry functionality with detailed error reporting
- Add POST /jobs/{id}/actions/retry_tts endpoint for retrying TTS
- Frontend shows TTS-specific error details (cue index, blocked text)
- Add "Retry TTS Generation" button on failed jobs
- Guides users to edit problematic AD text before retrying

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-03 21:39:59 -06:00
michael
9436aa4c6b fix: remove useMemo hook after early returns in QCList
The useMemo hook was placed after early returns (isLoading, error),
which violates React's rules of hooks. Hooks must be called
unconditionally on every render.

Replaced with simple inline computation since the operation is cheap.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-03 19:22:29 -06:00
michael
c512bdc184 feat: use AD VTT pause points instead of Gemini video analysis
Optimize the accessible video workflow by eliminating the dedicated
Gemini video analysis call for pause point estimation. Instead:

- Use AD VTT cue start times as initial pause points for Whisper refinement
- Add user-selectable accessible video method (pause_insert/overlay) at QC approval
- Add bulk approval API endpoint with method selection
- Add method selector UI to QCDetail page
- Add bulk approval modal to QCList for jobs with accessible video

Benefits:
- Eliminates expensive Gemini API call with video upload
- Faster workflow (~5-15 seconds saved per job)
- Cost savings on Gemini video analysis
- User control over accessible video integration method

Backend changes:
- Add accessible_video_method to RequestedOutputs and ApproveSourceRequest
- Add POST /jobs/bulk/approve endpoint
- Replace Gemini call with _build_placements_from_ad_vtt() helper
- Mark analyze_accessible_video_placement() as deprecated

Frontend changes:
- Add method selector radio buttons to QCDetail
- Add bulk approval modal with method selection to QCList
- Update API client and React Query hooks

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-03 19:05:45 -06:00
michael
3689653135 refactor: remove manual language selection, always auto-detect
Remove the "Original Video Language" control from job upload form.
All videos now use AI auto-detection for source language, simplifying
the UX and eliminating potential for incorrect manual selection.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-03 18:42:45 -06:00
michael
5342ab1a28 fix: prevent event loop closed error in video renderer Cloud Run calls
Use context manager for AsyncClient instead of caching on singleton.
Each asyncio.run() creates a new event loop, so cached clients bound
to previous event loops fail when reused across jobs.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-03 08:44:27 -06:00
michael
3e2099515a fix: use async httpx client for true parallel Cloud Run calls
Changed from httpx.Client (sync) to httpx.AsyncClient so that
asyncio.gather() actually executes HTTP calls in parallel instead
of blocking the event loop sequentially.

Before: ~5 min for 18 segments (serial HTTP calls despite gather)
After: ~30 sec for 18 segments (truly parallel HTTP calls)

Changes:
- _http_client: httpx.Client -> httpx.AsyncClient
- _call_cloud_run_probe: sync -> async
- _call_cloud_run_endpoint: sync -> async
- Added await to all Cloud Run HTTP calls

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-03 08:11:46 -06:00
michael
95852f1357 fix: update Cloud Run service configs for compatibility
- FFmpeg: Enable CPU throttling to reduce idle costs
- Whisper: Keep CPU throttling disabled (model loading needs full CPU)
- Remove readinessProbe (requires BETA launch stage)
- Both services scale to zero when idle for cost savings

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-02 17:34:10 -06:00
michael
593d3bf346 cost: enable CPU throttling for Whisper and FFmpeg Cloud Run services
Changed cpu-throttling from "false" to "true" for both services.
This reduces costs when instances are idle between requests:
- Idle CPU billed at ~10% of active rate instead of 100%
- Instances still scale to zero after ~15 min of no traffic

Trade-off: Slightly slower response when resuming from throttled state,
but startup-cpu-boost is still enabled to mitigate cold starts.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-02 17:22:14 -06:00
michael
e2302d497d perf: parallelize FFmpeg Cloud Run calls using asyncio.gather()
Refactored _render_pause_insert to execute FFmpeg operations in parallel
phases instead of sequentially:

Phase 1: Parallel extraction
- Generate shared silence (once, reused by all)
- Extract ALL video segments simultaneously
- Extract ALL freeze frames simultaneously
- Extract final segment

Phase 2: Parallel audio concatenation
- Concatenate ALL audio tracks (silence + AD + silence) simultaneously

Phase 3: Parallel freeze segment creation
- Create ALL freeze segments simultaneously

Phase 4: Assemble segments in correct order for final concatenation

This reduces render time from ~3 minutes (serial) to ~30 seconds (parallel)
for an 8-cue video when using Cloud Run FFmpeg service.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-02 17:18:23 -06:00
michael
87a4b1ab77 fix: use command_template instead of ffmpeg_args in _generate_silence_cloud_run
The /run-ffmpeg Cloud Run endpoint expects command_template field with
ffmpeg command placeholders, not ffmpeg_args. This fixes 422 validation
errors when generating silence audio via Cloud Run.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-02 16:57:17 -06:00
michael
e68bac2f60 fix: correct FFmpeg probe request parameter name
The /probe endpoint expects 'gcs_uri' but we were sending 'source_gcs_uri'.
Fixed to match the ProbeRequest model in ffmpeg_http_service.py.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-02 16:43:01 -06:00
Michael Clervi
97b582fed1 .env.production 2026-01-02 22:32:40 +00:00
michael
7d2366d0f4 fix: add authentication for Cloud Run service calls
Cloud Run services are deployed with --no-allow-unauthenticated,
requiring an ID token in the Authorization header.

- Add _get_cloud_run_id_token() helper using google-auth library
- Update whisper_transcribe.py to include Bearer token in Cloud Run calls
- Update video_renderer.py to include Bearer token in FFmpeg Cloud Run calls

The ID token is fetched using the service account credentials
(GOOGLE_APPLICATION_CREDENTIALS) and targets the Cloud Run service URL.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-02 11:41:07 -06:00
michael
77dc58b124 fix: add Cloud Run URLs to .env.local for docker-compose
Docker-compose reads from root .env (symlinked to .env.local), not
backend/.env. Added WHISPER_SERVICE_URL, FFMPEG_SERVICE_URL, and
worker concurrency settings to enable Cloud Run autoscaling.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-02 10:54:27 -06:00
michael
b6cec63656 chore: remove .env from git tracking
The .env file was tracked before .gitignore rules were added.
Running `git rm --cached` removes it from the index while keeping
the local file. Future changes to .env will now be ignored.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-02 10:29:51 -06:00
michael
9580979ac8 feat: add environment-based worker concurrency for Cloud Run mode
Allow configuring Celery worker concurrency via environment variables
to take advantage of Cloud Run autoscaling:

- Add WORKER_CONCURRENCY, WHISPER_WORKER_CONCURRENCY, FFMPEG_WORKER_CONCURRENCY
  settings to config.py with recommended values documented
- Update Dockerfile to use ${WORKER_CONCURRENCY} and ${WHISPER_WORKER_CONCURRENCY}
  environment variables instead of hardcoded values
- Update docker-compose.yml to pass concurrency env vars to worker commands
- Add WHISPER_SERVICE_URL and FFMPEG_SERVICE_URL to relevant workers

Recommended settings:
  Local mode:     WHISPER=1, FFMPEG=1 (CPU/RAM constrained)
  Cloud Run mode: WHISPER=10, FFMPEG=20 (match autoscaling limits)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-02 10:27:07 -06:00
Michael Clervi
6881565d75 .env and .gitignore 2026-01-02 16:21:41 +00:00
michael
79440929f4 feat: add Cloud Run HTTP services for Whisper and FFmpeg
Migrate CPU-intensive workloads to Cloud Run for autoscaling:

- Add Whisper HTTP service (FastAPI) with /transcribe endpoint
- Add FFmpeg HTTP service (FastAPI) with /encode, /probe, /extract-frame, etc.
- Add Dockerfiles for both services (8 vCPU, 32GB RAM, Gen2)
- Add Cloud Build config for CI/CD deployment
- Add Cloud Run service YAML configs with scale-to-zero
- Update whisper_transcribe.py to call Cloud Run when WHISPER_SERVICE_URL set
- Update video_renderer.py to call Cloud Run when FFMPEG_SERVICE_URL set
- Update whisper_service.py for Cloud Run compatibility (no settings dependency)
- Add ffmpeg_service_url and whisper_service_url to config.py

Services scale 0→N based on request load, falling back to local
execution when service URLs are not configured (hybrid mode).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-02 10:12:50 -06:00
michael
a8cc0b65a4 chore: update page title to Video Accessibility Platform
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 10:28:00 -06:00
michael
5e22f15e76 fix: TypeScript errors in JobDetail error display
Use explicit null returns and String() casts for unknown types
from job.error Record to satisfy TypeScript strict mode.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 10:22:20 -06:00
michael
c1c0b876fc feat: add RENDER_FAILED status with error propagation to GUI
- Add RENDER_FAILED job status for when video rendering fails
- Fix _check_accessible_video_completion to detect failures and transition
  job status accordingly (was stuck in RENDERING_VIDEO forever)
- Store detailed error info in job.error including failed_languages array
- Call completion check after failures to properly update job status
- Broadcast WebSocket notification on render failures

Frontend:
- Add render_failed to JobStatus type and StatusBadge (red styling)
- Add tts_failed and render_failed to JobsList STATUS_LABELS
- Enhance JobDetail error display with:
  - Warning icon and prominent styling
  - Error type and message
  - Failed languages list with per-language errors
  - Timestamp of when error occurred
- Update ProgressIndicator to handle failed states with red dot

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 10:18:27 -06:00
michael
0c1c115a8f fix: clarify language selector label
Changed "Target Languages" to "Target Languages for Translation"
for clarity in the new job form.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 09:38:29 -06:00
michael
e0850ca307 fix: include source language in jobs list language count
The Languages column now shows total languages (source + translations)
instead of just translation count, matching other parts of the UI.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 09:33:30 -06:00
michael
77be93b526 perf: parallelize video-native translations with asyncio.gather
Video-native translation mode now processes all target languages in parallel
using asyncio.gather() with a semaphore (max 3 concurrent) for rate limiting.
This significantly reduces total translation time when multiple languages
are selected.

- Add MAX_CONCURRENT_VIDEO_NATIVE constant for rate limiting
- Refactor video-native path to use parallel coroutines
- Keep traditional VTT translation mode sequential
- Handle per-language errors without stopping other translations

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 09:21:07 -06:00
michael
d08c20914e fix: use delete_blob() to avoid read-only generation property error
The google-cloud-storage library made blob.generation read-only,
causing job deletion to fail silently (0 GCS files deleted).
Using bucket.delete_blob(name) instead avoids generation checking.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 13:57:59 -06:00
michael
d2d8e32819 feat: add video-native translation mode for multi-language content
Add a new "Video Native Mode" translation option that re-processes the
video through Gemini for each target language, generating captions and
audio descriptions directly from visual context. This produces more
natural and culturally appropriate content compared to traditional VTT
text translation.

Changes:
- Add translation_mode field to RequestedOutputs (video_native | traditional)
- Create gemini_ingestion_targeted.md prompt for target language generation
- Add extract_accessibility_targeted() method to Gemini service
- Modify translate_and_synthesize task to handle both translation modes
- Add Translation Mode UI selector in NewJob screen (video_native is default)
- Remove transcreation UI (replaced by video_native mode)
- Remove Google Translate service (replaced by Gemini translation)
- Add LanguageSelector component with searchable dropdown

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 13:50:05 -06:00