73 lines
3.3 KiB
Markdown
73 lines
3.3 KiB
Markdown
---
|
|
title: "VTT Edit → Descriptive Transcript Regeneration"
|
|
description: "Pattern for keeping descriptive_transcript.txt in sync when captions or audio description VTTs are edited via PATCH /vtt"
|
|
tags: [fastapi, gcs, vtt, accessibility, celery]
|
|
created: 2026-05-01
|
|
updated: 2026-05-01
|
|
projects: [video-accessibility]
|
|
---
|
|
|
|
# VTT Edit → Descriptive Transcript Regeneration
|
|
|
|
## Problem
|
|
|
|
When a reviewer edits either captions.vtt or ad.vtt via `PATCH /jobs/{id}/vtt`, the descriptive transcript (`descriptive_transcript.txt`) becomes stale — it still reflects the pre-edit VTT content. This goes undetected because the transcript is not re-generated in the PATCH handler.
|
|
|
|
## Pattern
|
|
|
|
In the PATCH handler, after writing the edited VTT(s) to GCS but before the MongoDB update:
|
|
|
|
1. Determine which stream was edited (request body) and which was not
|
|
2. Read the unchanged stream from GCS
|
|
3. Merge both streams via `generate_descriptive_transcript(captions_text, ad_text)`
|
|
4. Upload the new transcript to GCS
|
|
5. Update `lang_output["descriptive_transcript_gcs"]` so the MongoDB doc points to the fresh file
|
|
|
|
Wrap in a broad `except` so a transcript failure never blocks the VTT save.
|
|
|
|
```python
|
|
# After GCS uploads for captions/AD:
|
|
if request.captions_vtt or request.audio_description_vtt:
|
|
try:
|
|
from ...services.descriptive_transcript import (
|
|
generate_descriptive_transcript as _gen_transcript,
|
|
)
|
|
captions_text = request.captions_vtt
|
|
if not captions_text:
|
|
cc_gcs = lang_output.get("captions_vtt_gcs")
|
|
if cc_gcs:
|
|
_blob = gcs_service.bucket.blob(
|
|
cc_gcs.replace(f"gs://{settings.gcs_bucket}/", "")
|
|
)
|
|
captions_text = await asyncio.get_event_loop().run_in_executor(
|
|
gcs_service.executor, _blob.download_as_text
|
|
)
|
|
ad_text = request.audio_description_vtt
|
|
if not ad_text:
|
|
ad_gcs = lang_output.get("ad_vtt_gcs")
|
|
if ad_gcs:
|
|
_blob = gcs_service.bucket.blob(
|
|
ad_gcs.replace(f"gs://{settings.gcs_bucket}/", "")
|
|
)
|
|
ad_text = await asyncio.get_event_loop().run_in_executor(
|
|
gcs_service.executor, _blob.download_as_text
|
|
)
|
|
transcript_text = _gen_transcript(captions_text or "", ad_text or "")
|
|
if transcript_text:
|
|
transcript_uri = await upload_vtt_to_gcs(
|
|
transcript_text,
|
|
f"{job_id}/{target_language}/descriptive_transcript.txt",
|
|
)
|
|
lang_output["descriptive_transcript_gcs"] = transcript_uri
|
|
except Exception as _tr_err:
|
|
logger.warning(
|
|
f"Failed to regenerate descriptive transcript for job {job_id}: {_tr_err}"
|
|
)
|
|
```
|
|
|
|
## Notes
|
|
|
|
- `asyncio.get_event_loop().run_in_executor(gcs_service.executor, blob.download_as_text)` — use the GCS service's thread pool executor to keep GCS SDK calls off the async loop
|
|
- The local import inside try/except avoids circular import issues if the module is conditionally present
|
|
- Always update the GCS pointer in `lang_output` before the MongoDB update — the write is atomic at the document level, so both the VTT path and the transcript path update together
|
|
- This pattern applies to any derived artifact that depends on two source VTT files
|