Records audio_duration (as chars) + latency_ms to cost tracker after each
successful transcription; wrapped in try/except so it never fails the task.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds BRIEF_CREATE/UPDATE/SUBMIT/APPROVE audit calls to routes_briefs.py
and SHARE_TOKEN_CREATE/REVOKE/SHARE_CLIENT_DECISION to routes_share.py;
public client_decision endpoint passes user=None per convention.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds audit log entries for all write endpoints in routes_organizations.py
(ORG_CREATE, ORG_UPDATE, ORG_MEMBER_ADD, ORG_MEMBER_UPDATE, ORG_MEMBER_REMOVE)
and routes_invitations.py (INVITATION_CREATE, INVITATION_REVOKE, INVITATION_ACCEPT).
The public accept endpoint passes user=None per the no-auth contract.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All 13 write endpoints in routes_clients.py now emit audit log entries
(CLIENT_CREATE, CLIENT_UPDATE, CLIENT_DEACTIVATE, CLIENT_PM_ASSIGN/REMOVE,
CLIENT_TEAM_CREATE/UPDATE/DELETE, CLIENT_TEAM_MEMBER_ADD/REMOVE,
CLIENT_PROJECT_CREATE/UPDATE/ARCHIVE). request: Request added to each
endpoint signature; resource_name and relevant details included in every call.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds audit_logger.log_action calls to all 13 write endpoints in
routes_language_qc.py using existing AuditAction enum values. Also
adds missing http_request: Request parameter to mark_cue_reviewed.
tts_synthesis.parse_ad_cues() was passing "00:00:02.500 line:0%" to
_parse_timestamp() — cue settings were not stripped from the end-time part
of the timing line. Split on whitespace and take first token only.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove ';' from command-injection pattern — semicolons are common in French
and other European languages, not a shell injection risk in JSON context
- Skip security pattern scanning for free-text fields (captions_vtt,
audio_description_vtt, notes, etc.) — natural language always generates
false positives against injection regexes
- Add GET/HEAD to GCS CORS config so browsers can load signed VTT URLs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add current_version_embedding_status/embedded_count/term_count to GlossaryResponse
- Batch-fetch current versions in list endpoint (single extra query, not N queries)
- Add get_versions_by_ids() helper to glossary_service
- Fix GlossaryList.tsx: embeddingBadge('') → embeddingBadge(g) with real status + pct
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
archive_glossary() now deletes terms, versions, and the glossary document
instead of soft-deleting. Prevents orphaned 34k-term datasets from consuming
embedding quota and storage after a glossary is removed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace AI Studio gemini-embedding-001 with Vertex AI text-multilingual-embedding-002
via google-genai SDK (vertexai=True). Vertex AI uses ADC (already configured) and
has significantly higher per-project quotas than AI Studio per-user limits.
Same 768-dim output; multilingual model better suited for 50+ language glossaries.
Add gcp_location config field (default us-central1).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Parse retryDelay from 429 error body and sleep for server_delay+1s
instead of our own 2s/4s backoff (which was shorter than API requires)
- Reduce embed concurrency 5→2 to halve burst when multiple glossary
versions embed simultaneously
- Increase max_retries 3→5 and initial backoff 2s→8s for headroom
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- glossary_service: _get_translation now handles bare→specific fallback (fr→fr-FR);
previously only specific→bare worked, causing zero term matches when job uses
bare locale codes ("fr") but XLSX has region columns ("fr_fr" → "fr-FR")
- ingest_and_ai: use title + brand_context as glossary source text; previously
empty brand_context caused glossary to be skipped entirely during AI ingestion
- routes_jobs.py: apply fix_overlapping_cues before validating PATCH /vtt;
mirrors what AI generation already does — prevents save errors for minor overlaps
- frontend/vtt.ts: preserve raw cue settings (line:0%, align:end, etc.) through
parse→build round-trip; previously settings were parsed into positionTop flag
only and dropped on serialization, losing caption positioning after edit
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- caption_aligner: lower match ratio 0.5→0.35, widen search window 60→150, add time-based cursor fallback on miss
- gemini.py: explicit 'MUST use glossary terms' requirement in translate_vtt prompt; source_has_ad prompt now instructs not to include AD narration in captions
- ingest_and_ai: load glossary for source language and pass to extract_accessibility
- render_accessible_video: handle source_has_ad=True via caption-embed path (ffmpeg subtitle inject, no AD pipeline)
- translate_and_synthesize: track failed languages, write translation_errors to DB, add exc_info to error log
- vtt.py: expand _FILLER_PATTERNS to nl/pt/pl/uk/ru, widen EN/ES/FR/DE/IT lists
- gemini_ingestion.md: strengthen line:0% placement rule, expand disfluency examples per language
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2.27.0 (previously locked) lacks VoiceSelectionParams.model_name field
required for Gemini TTS model selection via Cloud TTS API.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The AI Studio API (generativelanguage.googleapis.com) enforces a hard 10 RPM
quota on preview models regardless of billing tier. Switching to Cloud TTS API
(texttospeech.googleapis.com) with the same Gemini models uses a separate,
production-grade quota that scales on paid plans.
Changes:
- Replace genai.Client + generate_content(AUDIO) with texttospeech.TextToSpeechClient
- Style prompt now goes to SynthesisInput.prompt (dedicated field, not prepended text)
- Speed goes to AudioConfig.speaking_rate (no longer encoded in prompt text)
- Cloud TTS returns MP3 directly — remove PCM→MP3 lameenc conversion
- config: update pro model from gemini-2.5-pro-preview-tts → gemini-2.5-pro-tts (GA)
- Service account already has roles/aiplatform.user (granted today)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Gemini TTS allows 10 RPM; with concurrency=8 the rate limit is hit quickly.
The previous backoff (1-3s) was far too short — the API returns retryDelay ~37s.
Both synthesize_cue_task (Celery retry countdown) and GeminiTTSService
(_synthesize_cue_with_retry sleep) now parse the retryDelay from the 429
error message and use it (+ 5s buffer) instead of the exponential guess.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When EN is approved on a source-only job (no target languages), the translation
branch was skipped entirely, leaving the accessible video render pipeline never
dispatched. Added elif branch: if accessible_video_mp4 is requested and there
are no target languages to translate, dispatch translate_and_synthesize_task
(which will skip translation, run TTS for source language, and dispatch the
render task).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Used wrong field name sdh_captions instead of sdh_vtt, causing
TypeScript build failure on optical-dev.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
accessible_video_mp4 and sdh_captions were missing from the
Outputs section render — only 3 of 5 fields were displayed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
get_redis() is an async function but was called without await in
_cached_memberships(), causing RuntimeWarning and silently bypassing
the Redis membership cache on every request — all membership lookups
were hitting MongoDB instead of cache.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Per-cue MP3s (ad_cue_manifest) are required by render_accessible_video_task regardless
of whether the assembled ad.mp3 is requested as a client download. Previously, jobs with
accessible_video_mp4=True but audio_description_mp3=False would silently skip TTS, leaving
render tasks never dispatched and jobs stuck in tts_generating indefinitely.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Gemini occasionally produces captions where a cue's start_time is
earlier than the previous cue's end_time. Add VTTEditor.fix_overlapping_cues()
that trims each cue's end_time to 1ms before the next cue's start, applied
to both captions and AD VTT immediately after AI generation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
job.error persists after retry succeeds — only show it when status is
tts_failed / render_failed / processing_failed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Gemini occasionally returns response.text=None under load or safety filters.
Treat it as a retriable error so the fallback chain is used.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- gemini-3.1-flash-preview doesn't exist; replace with gemini-3-flash-preview
- GET /jobs/{id}/downloads: return empty {} instead of 400 when job has no
outputs (e.g. processing_failed before AI stage completes)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Routes all generate_content calls through _generate() which retries
gemini-3.1-flash-preview then gemini-2.5-pro when primary model hits
RESOURCE_EXHAUSTED. Cost tracker records actual model used.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When retranslate=True, _generate_tts_for_languages was receiving
the full outputs dict (all 9 languages) and regenerating TTS + render
for every language on every single-language retranslation task.
That multiplied API calls by 8x and triggered unnecessary renders.
Now passes only the target language outputs when retranslate=True.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
concurrent retranslation tasks (concurrency:2) were each replacing the
entire outputs doc, so the last writer silently overwrote the others.
Now each task only writes outputs.<lang> for the languages it processed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds "↺ Retranslate broken (N)" button in the Languages panel header.
Visible to production/admin when EN is approved and there are languages
with video_native origin or missing captions_vtt_gcs.
Confirm modal shows each broken language with its failure reason,
then queues individual retranslation tasks sequentially.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Bug fixes:
- Bug 1a: source_has_ad flag prevents AI generating AD over existing professional AD;
JobBrief/Job models, gemini service prompt conditional, NewBrief UI checkbox
- Bug 1b: disable native textTracks on video element to prevent double captions
- Bug 2: caption ALL audible speech including off-screen narrators (prompt fix)
- Bug 3: DCMP §6.01 disfluency removal for EN/ES/FR/DE/IT (prompt + post-pass)
- Bug 4: VTT cue settings (line:0%, position:) preserved through parser round-trip
- Bug 5: Whisper word-level timestamp alignment via new caption_aligner service
- Bug 6: assert_cue_alignment used .start/.end; renamed to .start_time/.end_time
- New migration: backfill source_has_ad=False on existing jobs and job_briefs
Also fix retranslation error handling to preserve existing GCS URIs on failure
so video_native captions remain accessible if retranslation fails.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add §6 EN-First Translation Pipeline to Production, Linguist, Admin, and
Project Manager guides explaining the new flow: translations are generated
only after English QC is approved, preserving 1:1 cue structure.
Documents origin badges (⚠ video-native), the amber TranslationGateBanner
on target-language cards, the ↺ Re-translate from EN button, and the
blue info note on the New Job form. Adds 5 new screenshots captured from
the deployed optical-dev environment.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add §6 EN-First Translation Pipeline to Production, Linguist, Admin, and
Project Manager guides explaining the new flow: translations are generated
only after English QC is approved, preserving 1:1 cue structure.
Documents origin badges (⚠ video-native), the amber TranslationGateBanner
on target-language cards, the ↺ Re-translate from EN button, and the
blue info note on the New Job form. Adds 5 new screenshots captured from
the deployed optical-dev environment.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
approve_source is callable by any qualified role (reviewer, linguist,
production, admin) — not just linguists. Now correctly dispatches the
translation pipeline when target languages are untranslated, regardless
of who approves the source. Without this fix, only language_qc.approve_language
(EN path) would trigger translation, leaving other roles stuck.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All translations now derive strictly from the approved English master VTT,
eliminating the cue-count and timestamp drift reported by linguists
(e.g. PL AD = 11 cues vs EN AD = 17 cues).
Key changes:
- Remove video_native translation mode entirely; all languages go through
translate_vtt() which guarantees 1:1 cue alignment with EN master
- Transcreation languages now use translate_vtt(style="transcreate") —
same cue-preserving contract, culturally-adapted instructions
- Post-translation cue alignment validator added (VTTEditor.assert_cue_alignment)
- After ingestion, job moves to PENDING_QC (EN-only) instead of TRANSLATING;
translation pipeline dispatches automatically when EN QC is approved
- New POST /jobs/{id}/retranslate-language endpoint for PM/admin to fix
legacy video_native jobs on demand
- Frontend: origin badge (EN-aligned / transcreated / video-native warning),
EN-first gate banner on target-language cards, Re-translate from EN button
with confirm modal, removed translation mode selector from NewJob
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wrap the render condition in parentheses and include 'submitting' so
TypeScript does not narrow decisionState to 'idle'|'error' inside the
review form block, eliminating four TS2367 comparison errors.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- POST /public/share/{token}/decision — unauthenticated approve/reject via share token
- approve: validates assets, sets status completed, triggers notification
- reject: sets status qc_feedback, stores client name + notes in review history
- ShareView: review form (name, comments, Approve / Return for Corrections)
- shows only when job is pending_final_review
- confirmation screen after decision
- api.ts: submitShareDecision()
- Hide 'client' role from UserList/UserDetail dropdowns
- Hide 'Client' guide tab from Help
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add \b word boundaries to SQL injection and command injection regex patterns
to prevent false positives on names like "Josh Smith" (sh\s+), "Norm " (rm\s+)
- Change default role in CreateUserModal from 'client' to 'admin'
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
MongoDB 7.0 rejects the invalid key with code 28811, causing 500 on
GET /organizations/{id}/members.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>