diff --git a/backend/app/api/v1/routes_jobs.py b/backend/app/api/v1/routes_jobs.py index e90a6df..274e362 100644 --- a/backend/app/api/v1/routes_jobs.py +++ b/backend/app/api/v1/routes_jobs.py @@ -771,6 +771,15 @@ async def get_job_vtt_content( except Exception as e: logger.warning(f"Failed to fetch AD VTT: {e}") + # Fetch retimed captions VTT if available (for accessible videos) + if "retimed_captions_vtt_gcs" in lang_output: + blob_path = lang_output["retimed_captions_vtt_gcs"].replace(f"gs://{settings.gcs_bucket}/", "") + try: + blob = gcs_service.bucket.blob(blob_path) + response.retimed_captions_vtt = blob.download_as_text() + except Exception as e: + logger.warning(f"Failed to fetch retimed captions VTT: {e}") + return response diff --git a/backend/app/schemas/job.py b/backend/app/schemas/job.py index 92462b7..7a853e6 100644 --- a/backend/app/schemas/job.py +++ b/backend/app/schemas/job.py @@ -82,6 +82,7 @@ class JobDownloadsResponse(BaseModel): class VttContentResponse(BaseModel): captions_vtt: Optional[str] = None audio_description_vtt: Optional[str] = None + retimed_captions_vtt: Optional[str] = None # Re-timed captions for accessible videos class AssetValidationResponse(BaseModel): diff --git a/frontend/src/components/VideoReview/VideoReviewPlayer.tsx b/frontend/src/components/VideoReview/VideoReviewPlayer.tsx index 701ee7d..2232c49 100644 --- a/frontend/src/components/VideoReview/VideoReviewPlayer.tsx +++ b/frontend/src/components/VideoReview/VideoReviewPlayer.tsx @@ -54,9 +54,9 @@ export function VideoReviewPlayer({ job, downloads }: VideoReviewPlayerProps) { const captions = useMemo(() => { if (!vttContent) return []; - // For accessible videos, use retimed captions if available - const vttSource = activeTab?.isAccessible && activeTab.captionsVttField === 'retimed_captions_vtt_gcs' - ? vttContent.captions_vtt // The API returns captions_vtt regardless of source + // For accessible videos, use retimed captions if available, fall back to regular captions + const vttSource = activeTab?.isAccessible + ? (vttContent.retimed_captions_vtt || vttContent.captions_vtt) : vttContent.captions_vtt; if (!vttSource) return []; @@ -114,7 +114,7 @@ export function VideoReviewPlayer({ job, downloads }: VideoReviewPlayerProps) { }; }, []); - // Video time update handler + // Video time update handler - re-attach listeners when video element changes useEffect(() => { const video = videoRef.current; if (!video) return; @@ -140,7 +140,7 @@ export function VideoReviewPlayer({ job, downloads }: VideoReviewPlayerProps) { video.removeEventListener('pause', handlePause); clearTimeout(timeoutId); }; - }, []); + }, [activeTab?.videoUrl]); // Re-run when video URL changes (causes element remount) // Reset video when tab changes useEffect(() => { diff --git a/frontend/src/types/api.ts b/frontend/src/types/api.ts index 08f9a9f..e3a87e7 100644 --- a/frontend/src/types/api.ts +++ b/frontend/src/types/api.ts @@ -204,6 +204,7 @@ export interface JobDownloadsResponse { export interface VttContentResponse { captions_vtt?: string; audio_description_vtt?: string; + retimed_captions_vtt?: string; // Re-timed captions for accessible videos } export interface VttUpdateRequest {