T-HUB/docs/design/2026-10-11-video-ai-architecture.md
glass beadian eb1b60f67d Add video-AI architecture research (transcription, diarization, faces, GDPR)
Research: use PeerTube
native transcription (core since 6.2, whisper-ctranslate2, remote runners on
GPU nodes; large-v3-turbo MIT via model_path); pyannote community-1
diarization (CC-BY-4.0, clean); WhisperX fusion.

Face recognition license audit: NO accurate open FR model has clean weights
(InsightFace/SCRFD non-commercial, yolo-face GPL, AdaFace unstated) —
posture is detection+tracking+blur by default; identity matching opt-in,
human-review-gated, collective decision + DPIA required (Art. 9 biometric
data; automated FR over non-consenting third parties effectively unlawful).

DB DDL sketch: persons/person_face_embeddings/video_face_detections/
transcript_segments/video_speaker_person/identity_audit with review gating,
visibility tiers, erasure workflow.
2026-10-11 07:55:01 +00:00

53 lines
8.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

Report saved to `research/2026-10-11-av-transcription-diarization-faces-gdpr.md`. Full findings below — everything marked "verified" was fetched from primary sources today (2026-10-11); everything else is explicitly flagged.
# Decision-ready summary
## (a) Transcription — use PeerTube's, don't build one
**Verified from PeerTube v8.3.1 source today** (current release, 2026-09-28):
- Transcription is **core since 6.2** (`video_transcription:` in `config/default.yaml`), default **off**. Engines: **`whisper-ctranslate2`** (default) or `openai-whisper`; models `tiny|base|small|medium|large-v2|large-v3` — **no `turbo` in the list**, but `model_path:` accepts a CTranslate2 model directory, so drop in `faster-whisper-large-v3-turbo` (MIT) via path. Set `remote_runners.enabled: true` and run the official `peertube-runner` on a Linux 3090 node — same runner contract as your transcode pool.
- Subtitles land as **VTT** in the caption system (storage prefix `captions/` on R2 is supported — recent changelog fixes caption CORS for object storage). API verified in `server/core/controllers/api/videos/captions.ts`: `GET /api/v1/videos/{id}/captions`, `PUT/DELETE .../captions/{lang}`, `POST .../captions/generate`.
- **No webhooks in PeerTube (verified — zero hits in repo tree)** → your NestJS api should poll captions-on-publish, or write a tiny plugin (plugin `Hooks` are imported by the caption controller; specific transcription hook names unverified — flagged).
- Model licensing is clean: **whisper-large-v3 = Apache-2.0, large-v3-turbo = MIT, faster-whisper-large-v3 = MIT** (HF API today). distil-whisper is **English-only → excluded** for ES (card check: marked UNVERIFIED-MEMORY, but nothing suggests multilingual).
- **Spanish numbers honestly missing**: current HF cards no longer publish FLEURS/CV per-language tables (checked both — absent). From memory: FLEURS-es ~5–7% for large-v3; **expect 15–35% on street audio** (wind/PA/chants). Whisper cards explicitly warn of *"repetitive texts"* (hallucinations) — a real risk on chants/music; VAD + human spot-check needed.
- VRAM (faster-whisper README, measured on a 3070 Ti): large-v3 int8 batched = **4500 MB, 5× realtime**. Turbo ≈ half that (ESTIMATE). whisper.cpp v1.9.5 (2026-10-06) verified as CPU/Windows fallback with `-ovtt` VTT output, CUDA/Vulkan, Q5_0.
- Fallback serving if you outgrow the runner: **whisper-asr-webservice v1.10.0** (faster_whisper/whisperx engines, VTT/SRT/word timestamps — README-verified; ideal for media-plane blobs that never enter PeerTube) or **Speaches** (MIT, but v0.9.0-rc.3 — pre-1.0).
## (b) Diarization — pyannote is fine for this use; the gate is not a license
- **`pyannote/speaker-diarization-community-1`: license CC-BY-4.0, gated `auto`** (HF API today); 3.1/segmentation-3.0 are **MIT, gated `auto`**; pyannote.audio code is **MIT** (CNRS). The gate = HF account + accept conditions + read token (card fetch without auth returned 401 — verified empirically). **No non-commercial clause anywhere** → OK for you, add CC attribution to a credits page.
- Quality (official DER table, community-1 vs 3.1): AMI 17.0/18.8, DIHARD 3 20.2/21.4, **MSDWild 22.8/25.4, Ego4D 46.8/51.2** — the two "wild" benchmarks are the honest proxies for street footage; **no Spanish benchmark exists** (flagged).
- **WhisperX** (BSD-2, v3.8.6) fuses whisper + word alignment + diarization; its README now requires accepting **`speaker-diarization-community-1`** + `--hf_token`. Spanish alignment model = `VOXPOPULI_ASR_BASE_10K_ES` (verified in `alignment.py`; that model's license unverified — flagged).
- **NeMo Sortformer weights are CC-BY-NC-4.0 → excluded** (non-commercial; NeMo toolkit itself is Apache-2.0). diart (MIT) is streaming-oriented, maintenance unclear.
- Speaker→name UX: `speaker_key` per video + one `video_speaker_person` row; confirmed mappings + face-track co-occurrence give cross-video suggestions (label once per speaker).
## (c) Faces — the license audit is the headline
| Component | Code | **Weights** | Verdict |
|---|---|---|---|
| InsightFace buffalo_l / ArcFace-w600k | MIT | **NON-COMMERCIAL** — README: models *"available for non-commercial research purposes only"*, applies to pip auto-download too; **2025-11-24 update: contact recognition-oss-pack@insightface.ai for licensing** (both quoted from README today) | ❌ for production index |
| SCRFD detector weights | MIT code | same non-commercial policy | ❌ if strict / ⚠️ pragmatic |
| AdaFace | MIT | weights on Google Drive, **no license stated**; WebFace4M/MS1MV2-lineage | ⚠️ gray zone |
| deepface | MIT | explicitly **inherits** wrapped-model licenses (README quote) — launders nothing | ⚠️ |
| MediaPipe (BlazeFace detect) | Apache-2.0 | per-file license not clearly stated (flag) | ✅ detection; **no recognition model exists in MediaPipe** |
| yolov8-face / yolov5-face | **GPL-3.0** | — | ❌ copyleft hazard |
**No accurate FR model in the open ecosystem has clean weights today.** Recommended posture: **detection+tracking+blur by default; identity matching opt-in, human-review-gated, collective decision recorded in writing** (non-commercial terms bar *commercialization* — whether a self-hosted activist tool qualifies is a legal question, not a technical one). Quality limits on your footage: masks (NIST IR 8331 PDF confirmed live at nvlpubs; the "5×–50× more errors" figure = UNVERIFIED-MEMORY, don't quote yet), motion blur, low light, side profiles — these push embeddings into the band where clustering **silently merges the wrong person**, which is your most dangerous failure mode (§GDPR).
Pipeline: PySceneDetect (0.7.1, BSD-3) → 1–2 keyframes/shot capped at ~2 fps → detect → track → 1 embedding/track → HDBSCAN/Chinese-Whispers offline → incremental assignment vs reviewed centroids (≥τ auto, τ-band → review queue). Cosine threshold ~0.4–0.5 same-person = UNVERIFIED-MEMORY; calibrate on your data. Store centroids in pgvector (512-d fits fine) — reuse the existing pattern.
## (d) Identity model + GDPR (the part that gates everything)
- DDL in the report: `persons` (status `auto|member_confirmed|hidden|erased`, visibility tiers), `person_face_embeddings` (reviewed centroids only), `video_face_detections` (bbox + timestamps + `assignment` state machine + **transient** embedding column wiped by retention), `transcript_segments`, `video_speaker_person`, `identity_audit` (append-only; merge/undo/erasure all reversible). Label once → `UPDATE ... WHERE match_sim >= τ` propagates; τ-band goes to `pending_review`; nothing `auto` ever renders in UI/API.
- **Verified legal text** (EUR-Lex today): Art. 4(14) definition; **Art. 9(1) prohibits processing biometric data "for the purpose of uniquely identifying"** absent a 9(2) condition — and for bystanders/police, none of (a) explicit consent, (e) "manifestly made public", (f) vital interests, (g) substantial public interest (requires specific law) applies cleanly to an identity index → **an automated FR gallery over non-consenting third parties is effectively unlawful by default; detection+blur that doesn't uniquely identify arguably isn't Art. 9 data at all**. Art. 35(3)(b) makes the **DPIA mandatory** before phase 3. Design mitigations mapped: minimization (coords-not-crops, transient embeddings, pinned models/thresholds), human-review gating, visibility tiers enforced server-side, per-person erasure workflow (index-level; originals stay), audit trail, "high-risk person" flag for police-adjacent IDs, Tailscale-only exposure.
- **Could not verify this session (flagged):** EDPB Guidelines 05/2022 (all URL attempts 404'd), AEPD guidance + Zaragoza enforcement case (**aepd.es returned 500/503 all day**), NIST mask headline figure. Re-verify before citing any of them.
## VRAM / cost (3090)
TEI+bge-m3 ~2–3 GB persistent + turbo int8 ~3 GB burst + pyannote ~2–4 GB + faces ~1–2 GB ≈ **11–13 GB concurrent worst case — fits 24 GB, but serialize jobs anyway** (queue, reuse pool-manager pressure signals; keep NVENC transcode floors). Throughput ESTIMATES: turbo ≈ 20–40× RT, large-v3 ≈ 8–12× RT; full pipeline ≈ **5–25 GPU-min per video-hour**; 500-h backfill ≈ 2–4 GPU-days for transcription alone.
## Top unknowns before you commit
1. One smoke test: does PeerTube's **runner** transcription path pick up CUDA for whisper-ctranslate2, and does `model_path` accept a turbo CT2 dir there?
2. Internal ES/EN eval set (20–30 real clips) — no published benchmark matches protest audio.
3. Collective decision + legal advice on FR weights (non-commercial) and on Art. 9 posture **before any phase-3 code**.
4. WhisperX VoxPopuli-ES alignment weights license; EDPB/AEPD doc URLs (fetch failed today).
Full details, all quotes, DDL, and per-claim source URLs are in the saved report.