Steg 9 (R4): unified migrate-corpus.mjs --write over engineering/governance/ infrastructure/security. 327 filer mutert, verified=null, prosa byte-identisk (fra første ## seksjon), advisor urørt (0 endringer). To applier-fixes oppdaget under kjøring (TDD, RED→GREEN): - insertHeaderFields: anker faller nå tilbake når en meta-linje selv passerer 500B (2 filer pakket et avsnitt i **Status:** → Type/Source landet utenfor scan-vinduet, applierens post-write-assertion fanget + restaurerte). - normalizeStaleVerified: fjerner nå ALLE stale non-date **Verified:** i 500B-vinduet, inkl. stray body-dup rett under --- (9 mlops-genaiops-filer var ellers falskt "verified"/fresh, droppet fra worklist). Operatør-godkjent utvidelse av carve-out; kun stray metadata-linjer, aldri prosa. test-transform-criterion: precondition oppdatert til post-migrasjons-sannhet (fila bærer nå Source). Suite 728/728 grønn.
547 lines
18 KiB
Markdown
547 lines
18 KiB
Markdown
# Audio and Video Transcription Workflow Architecture
|
|
|
|
**Last updated:** 2026-02
|
|
**Status:** GA
|
|
**Category:** Multi-Modal AI
|
|
**Type:** reference
|
|
|
|
---
|
|
|
|
## Innhold
|
|
|
|
- [Introduksjon](#introduksjon)
|
|
- [Kjernekomponentar](#kjernekomponentar)
|
|
- [Batch Transcription at Scale](#batch-transcription-at-scale)
|
|
- [Speaker Attribution og Diarization](#speaker-attribution-og-diarization)
|
|
- [Automatic Translation with Context Preservation](#automatic-translation-with-context-preservation)
|
|
- [Quality Assurance og Human-in-the-Loop Workflows](#quality-assurance-og-human-in-the-loop-workflows)
|
|
- [Implementeringsmønstre](#implementeringsmønstre)
|
|
- [Norsk offentleg sektor](#norsk-offentleg-sektor)
|
|
- [Beslutningsrammeverk](#beslutningsrammeverk)
|
|
- [For Cosmo](#for-cosmo)
|
|
|
|
## Introduksjon
|
|
|
|
Azure Speech Services tilbyr omfattande kapabilitetar for transkribering og oversettelse av audio- og videoinnhald. Tenesta støttar sanntids- og batch-transkribering med funksjonar som taleridentifisering (diarization), automatisk språkdeteksjon, ordnivå-tidsstempel og tilpassa talemodeller. For norsk offentleg sektor er dette relevant for møtetranskribering, arkivering av telefonsamtalar, tekstforming av video, og tilgjengeleggjering av audioinnnhald.
|
|
|
|
Batch Transcription API er designa for å transkribere store mengder lydfilar lagra i Azure Blob Storage eller tilgjengelege via URL. Prosesseringa skjer asynkront og er optimal for arkivtranskribering, callcenter-analyse og untertekstproduksjon. Fast Transcription API (preview) tilbyr synkron prosessering med lågare latency for kortare lydfiler. Sanntidstranskribering via Speech SDK er eigna for live-scenario.
|
|
|
|
Azure Video Indexer (tidlegare Azure Media Services Video Indexer) tilbyr AI-driven analyse av videoinnhald, inkludert automatisk transkribering, omsetjing, emnedeteksjon, ansiktsdeteksjon og scene-segmentering. For heilskaplege audio/video-transkripsjonsworkflowar bør ein vurdere å kombinere Speech Services, Video Indexer og Azure OpenAI for oppsummering og innsikt.
|
|
|
|
---
|
|
|
|
## Kjernekomponentar
|
|
|
|
| Komponent | Formål | Teknologi |
|
|
|-----------|--------|-----------|
|
|
| Real-time STT | Sanntids tale-til-tekst | Azure Speech SDK |
|
|
| Batch Transcription | Volumbasert asynkron transkribering | Speech to text REST API v3.1+ |
|
|
| Fast Transcription | Synkron rask transkribering | Speech REST API (preview) |
|
|
| Diarization | Taleridentifisering og -separering | Speech Services |
|
|
| Speech Translation | Sanntids taleoversettelse | Translation SDK |
|
|
| Custom Speech | Tilpassa talegjenkjenning | Azure Speech Studio |
|
|
| Video Indexer | AI-driven videoanalyse | Azure Video Indexer |
|
|
| Batch Processing Kit | Open-source container for volum-transkribering | GitHub/Docker Hub |
|
|
|
|
---
|
|
|
|
## Batch Transcription at Scale
|
|
|
|
### Batch Transcription API
|
|
|
|
```python
|
|
import requests
|
|
import json
|
|
import time
|
|
|
|
speech_key = os.environ["SPEECH_KEY"]
|
|
speech_region = os.environ["SPEECH_REGION"]
|
|
|
|
base_url = (f"https://{speech_region}.api.cognitive.microsoft.com"
|
|
"/speechtotext/v3.1")
|
|
|
|
headers = {
|
|
"Ocp-Apim-Subscription-Key": speech_key,
|
|
"Content-Type": "application/json"
|
|
}
|
|
|
|
# Opprett batch-transkribering
|
|
transcription_payload = {
|
|
"contentUrls": [
|
|
"https://storage.blob.core.windows.net/audio/meeting1.wav",
|
|
"https://storage.blob.core.windows.net/audio/meeting2.wav",
|
|
"https://storage.blob.core.windows.net/audio/meeting3.wav"
|
|
],
|
|
"locale": "nb-NO",
|
|
"displayName": "Kommunestyremøte Q4 2025",
|
|
"properties": {
|
|
"wordLevelTimestampsEnabled": True,
|
|
"diarizationEnabled": True,
|
|
"diarization": {
|
|
"speakers": {
|
|
"minCount": 2,
|
|
"maxCount": 15
|
|
}
|
|
},
|
|
"punctuationMode": "DictatedAndAutomatic",
|
|
"profanityFilterMode": "Masked",
|
|
"destinationContainerUrl": (
|
|
"https://mystorage.blob.core.windows.net/transcripts"
|
|
"?sp=rwl&st=...&sig=..."
|
|
)
|
|
}
|
|
}
|
|
|
|
# Start transkribering
|
|
response = requests.post(
|
|
f"{base_url}/transcriptions",
|
|
headers=headers,
|
|
data=json.dumps(transcription_payload)
|
|
)
|
|
|
|
transcription_url = response.headers["Location"]
|
|
print(f"Transkribering starta: {transcription_url}")
|
|
|
|
# Poll for ferdigstilling
|
|
while True:
|
|
status = requests.get(transcription_url, headers=headers).json()
|
|
if status["status"] in ["Succeeded", "Failed"]:
|
|
break
|
|
print(f"Status: {status['status']}")
|
|
time.sleep(30)
|
|
|
|
# Hent resultat
|
|
if status["status"] == "Succeeded":
|
|
files_url = f"{transcription_url}/files"
|
|
files = requests.get(files_url, headers=headers).json()
|
|
for file in files["values"]:
|
|
if file["kind"] == "Transcription":
|
|
result = requests.get(
|
|
file["links"]["contentUrl"], headers=headers
|
|
).json()
|
|
for phrase in result["recognizedPhrases"]:
|
|
speaker = phrase.get("speaker", "Ukjent")
|
|
text = phrase["nBest"][0]["display"]
|
|
offset = phrase["offsetInTicks"] / 10_000_000
|
|
print(f"[{offset:.1f}s] Taler {speaker}: {text}")
|
|
```
|
|
|
|
### Batch Processing Kit (open-source)
|
|
|
|
For store volum med fleire Speech-containers:
|
|
|
|
```yaml
|
|
# docker-compose.yml for batch processing kit
|
|
version: "3"
|
|
services:
|
|
batch-kit:
|
|
image: batchkit/speech-batch-kit:latest
|
|
environment:
|
|
- SPEECH_ENDPOINT=http://speech-container:5000
|
|
- INPUT_FOLDER=/input
|
|
- OUTPUT_FOLDER=/output
|
|
- DIARIZATION=true
|
|
- LANGUAGE=nb-NO
|
|
volumes:
|
|
- ./audio-files:/input
|
|
- ./transcripts:/output
|
|
|
|
speech-container:
|
|
image: mcr.microsoft.com/azure-cognitive-services/speechservices/speech-to-text
|
|
environment:
|
|
- EULA=accept
|
|
- Billing=https://northeurope.api.cognitive.microsoft.com
|
|
- ApiKey=${SPEECH_KEY}
|
|
ports:
|
|
- "5000:5000"
|
|
```
|
|
|
|
### Skaleringsstrategiar
|
|
|
|
| Volum | Strategi | Gjennomstrøyming |
|
|
|-------|----------|-----------------|
|
|
| < 100 filer/dag | Batch Transcription API direkte | Standard quota |
|
|
| 100-1000 filer/dag | Batch API med auka quota | Kontakt Microsoft |
|
|
| > 1000 filer/dag | Batch Processing Kit + fleire containers | Lineær skalering |
|
|
| Sanntid + batch | Hybrid: SDK for live, API for arkiv | Ulike endpoints |
|
|
|
|
---
|
|
|
|
## Speaker Attribution og Diarization
|
|
|
|
### Sanntids diarization
|
|
|
|
```python
|
|
import azure.cognitiveservices.speech as speechsdk
|
|
|
|
speech_config = speechsdk.SpeechConfig(
|
|
subscription=os.environ["SPEECH_KEY"],
|
|
region=os.environ["SPEECH_REGION"]
|
|
)
|
|
|
|
# Konfigurer for diarization
|
|
speech_config.speech_recognition_language = "nb-NO"
|
|
speech_config.set_property(
|
|
speechsdk.PropertyId.SpeechServiceConnection_LanguageIdMode,
|
|
"Continuous"
|
|
)
|
|
|
|
audio_config = speechsdk.audio.AudioConfig(
|
|
filename="meeting_recording.wav"
|
|
)
|
|
|
|
# Opprett conversation transcriber
|
|
conversation_transcriber = speechsdk.transcription.ConversationTranscriber(
|
|
speech_config=speech_config,
|
|
audio_config=audio_config
|
|
)
|
|
|
|
transcript = []
|
|
done = False
|
|
|
|
def transcribed_cb(evt):
|
|
if evt.result.reason == speechsdk.ResultReason.RecognizedSpeech:
|
|
transcript.append({
|
|
"speaker": evt.result.speaker_id,
|
|
"text": evt.result.text,
|
|
"offset": evt.result.offset,
|
|
"duration": evt.result.duration
|
|
})
|
|
print(f"Taler {evt.result.speaker_id}: {evt.result.text}")
|
|
|
|
def canceled_cb(evt):
|
|
nonlocal done
|
|
done = True
|
|
|
|
def stopped_cb(evt):
|
|
nonlocal done
|
|
done = True
|
|
|
|
conversation_transcriber.transcribed.connect(transcribed_cb)
|
|
conversation_transcriber.canceled.connect(canceled_cb)
|
|
conversation_transcriber.session_stopped.connect(stopped_cb)
|
|
|
|
# Start transkribering
|
|
conversation_transcriber.start_transcribing_async()
|
|
|
|
while not done:
|
|
time.sleep(0.5)
|
|
|
|
conversation_transcriber.stop_transcribing_async()
|
|
```
|
|
|
|
### Diarization i batch-modus
|
|
|
|
Batch Transcription API støttar opp til 35 talarar:
|
|
|
|
```json
|
|
{
|
|
"locale": "nb-NO",
|
|
"properties": {
|
|
"diarizationEnabled": true,
|
|
"diarization": {
|
|
"speakers": {
|
|
"minCount": 2,
|
|
"maxCount": 35
|
|
}
|
|
},
|
|
"wordLevelTimestampsEnabled": true
|
|
}
|
|
}
|
|
```
|
|
|
|
### Resultatformat med talarinformasjon
|
|
|
|
```json
|
|
{
|
|
"recognizedPhrases": [
|
|
{
|
|
"speaker": 1,
|
|
"offsetInTicks": 15000000,
|
|
"durationInTicks": 35000000,
|
|
"nBest": [{
|
|
"confidence": 0.92,
|
|
"display": "Eg vil starte med å gå gjennom sakslista.",
|
|
"words": [
|
|
{"word": "Eg", "offset": "PT1.5S", "duration": "PT0.2S"},
|
|
{"word": "vil", "offset": "PT1.7S", "duration": "PT0.15S"}
|
|
]
|
|
}]
|
|
},
|
|
{
|
|
"speaker": 2,
|
|
"offsetInTicks": 52000000,
|
|
"durationInTicks": 28000000,
|
|
"nBest": [{
|
|
"confidence": 0.88,
|
|
"display": "Takk. Eg har eit spørsmål til sak nummer tre.",
|
|
"words": []
|
|
}]
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Automatic Translation with Context Preservation
|
|
|
|
### Sanntids taleoversettelse
|
|
|
|
```python
|
|
import azure.cognitiveservices.speech as speechsdk
|
|
|
|
translation_config = speechsdk.translation.SpeechTranslationConfig(
|
|
subscription=os.environ["SPEECH_KEY"],
|
|
region=os.environ["SPEECH_REGION"]
|
|
)
|
|
|
|
# Gjenkjenn norsk, omset til fleire språk
|
|
translation_config.speech_recognition_language = "nb-NO"
|
|
translation_config.add_target_language("en") # Engelsk
|
|
translation_config.add_target_language("ar") # Arabisk
|
|
translation_config.add_target_language("so") # Somali
|
|
translation_config.add_target_language("pl") # Polsk
|
|
|
|
audio_config = speechsdk.audio.AudioConfig(
|
|
filename="info_meeting.wav"
|
|
)
|
|
|
|
recognizer = speechsdk.translation.TranslationRecognizer(
|
|
translation_config=translation_config,
|
|
audio_config=audio_config
|
|
)
|
|
|
|
def recognized_cb(evt):
|
|
if evt.result.reason == speechsdk.ResultReason.TranslatedSpeech:
|
|
print(f"NORSK: {evt.result.text}")
|
|
for lang, translation in evt.result.translations.items():
|
|
print(f" → {lang}: {translation}")
|
|
|
|
recognizer.recognized.connect(recognized_cb)
|
|
recognizer.start_continuous_recognition()
|
|
|
|
import time
|
|
time.sleep(300) # Køyr i 5 minutt
|
|
recognizer.stop_continuous_recognition()
|
|
```
|
|
|
|
### Post-transkribering AI-powered oversettelse
|
|
|
|
For betre kontekstuell kvalitet, kombiner transkribering + Azure OpenAI:
|
|
|
|
```python
|
|
def translate_transcript_with_context(
|
|
transcript: list[dict],
|
|
target_language: str
|
|
) -> list[dict]:
|
|
"""Omset transkripsjon med kontekst via GPT-4o."""
|
|
|
|
# Samle heile transkripsjonen for kontekst
|
|
full_text = "\n".join(
|
|
f"[Taler {t['speaker']}]: {t['text']}"
|
|
for t in transcript
|
|
)
|
|
|
|
response = openai_client.chat.completions.create(
|
|
model="gpt-4o",
|
|
messages=[
|
|
{
|
|
"role": "system",
|
|
"content": f"""Omset følgjande møtereferat frå norsk
|
|
til {target_language}. Bevar:
|
|
- Talaridentifikasjon ([Taler X])
|
|
- Fagterminologi (omset korrekt eller behold)
|
|
- Formell tone (dette er offentleg forvaltning)
|
|
- Kontekst mellom ytringane"""
|
|
},
|
|
{"role": "user", "content": full_text}
|
|
],
|
|
max_tokens=4000
|
|
)
|
|
|
|
return response.choices[0].message.content
|
|
```
|
|
|
|
---
|
|
|
|
## Quality Assurance og Human-in-the-Loop Workflows
|
|
|
|
### Confidence-basert QA
|
|
|
|
```python
|
|
def qa_transcript(transcript_result: dict,
|
|
confidence_threshold: float = 0.7) -> dict:
|
|
"""QA av transkriberingsresultat med flagging."""
|
|
|
|
qa_report = {
|
|
"total_phrases": 0,
|
|
"high_confidence": 0,
|
|
"low_confidence": 0,
|
|
"flagged_phrases": [],
|
|
"average_confidence": 0.0
|
|
}
|
|
|
|
confidences = []
|
|
|
|
for phrase in transcript_result["recognizedPhrases"]:
|
|
qa_report["total_phrases"] += 1
|
|
conf = phrase["nBest"][0]["confidence"]
|
|
confidences.append(conf)
|
|
|
|
if conf >= confidence_threshold:
|
|
qa_report["high_confidence"] += 1
|
|
else:
|
|
qa_report["low_confidence"] += 1
|
|
qa_report["flagged_phrases"].append({
|
|
"text": phrase["nBest"][0]["display"],
|
|
"confidence": conf,
|
|
"offset": phrase["offsetInTicks"] / 10_000_000,
|
|
"speaker": phrase.get("speaker", "?"),
|
|
"alternatives": [
|
|
n["display"] for n in phrase["nBest"][1:3]
|
|
]
|
|
})
|
|
|
|
qa_report["average_confidence"] = (
|
|
sum(confidences) / len(confidences) if confidences else 0
|
|
)
|
|
|
|
return qa_report
|
|
```
|
|
|
|
### Human-in-the-Loop workflow med Power Automate
|
|
|
|
```
|
|
Batch Transcription API → Azure Blob Storage (transcript.json)
|
|
→ Azure Function: QA-analyse
|
|
→ IF avg_confidence >= 0.85:
|
|
→ Automatisk godkjenning → Arkivsystem
|
|
→ IF avg_confidence 0.6-0.85:
|
|
→ Power Automate: Send flagga segment til korrekturles
|
|
→ Teams Adaptive Card til saksbehandlar
|
|
→ Manuell korrigering → Arkivsystem
|
|
→ IF avg_confidence < 0.6:
|
|
→ Varsling: Lydkvalitet for låg
|
|
→ Anbefal ny innspeling eller manuell transkribering
|
|
```
|
|
|
|
### Custom Speech for betre norsk gjenkjenning
|
|
|
|
```python
|
|
# Tren tilpassa modell med norsk fagterminologi
|
|
# Steg 1: Førebu treningsdata
|
|
training_data = {
|
|
"plain_text": [
|
|
"Reguleringsplan for Stortorvet",
|
|
"Detaljreguleringsplan med konsekvensutgreiing",
|
|
"Klage på vedtak etter plan- og bygningslova"
|
|
],
|
|
"structured_text": [
|
|
{"phrase": "bygningslova", "pronunciation": "BYG-NINGS-LO-VA"},
|
|
{"phrase": "detaljreguleringsplan",
|
|
"pronunciation": "DE-TALJ-RE-GU-LE-RINGS-PLAN"}
|
|
]
|
|
}
|
|
|
|
# Steg 2: Opplasting via Speech Studio eller REST API
|
|
# Steg 3: Tren modell
|
|
# Steg 4: Deploy som custom endpoint
|
|
# Steg 5: Bruk endpoint-ID i transkribering
|
|
```
|
|
|
|
---
|
|
|
|
## Implementeringsmønstre
|
|
|
|
### Mønster 1: Møtetranskribering for kommunestyre
|
|
|
|
```
|
|
Teams-møte → Opptak (MP4/WAV)
|
|
→ Azure Blob Storage
|
|
→ Batch Transcription API (diarization ON)
|
|
→ QA-funksjon (confidence check)
|
|
→ Azure OpenAI: Oppsummering + vedtaksliste
|
|
→ Power Automate: Distribusjon
|
|
→ Saksbehandlingssystem (møteprotokoll)
|
|
→ Offentleg nettside (med tidskoda tekst)
|
|
→ Arkiv (eInnsyn-kompatibelt)
|
|
```
|
|
|
|
### Mønster 2: Callcenter-analyse
|
|
|
|
```
|
|
Telefonopptak → Event Grid trigger
|
|
→ Azure Function → Batch Transcription
|
|
→ Cosmos DB (transcript + metadata)
|
|
→ Azure OpenAI: Sentiment + kategorisering
|
|
→ Power BI: Dashboard med KPI-ar
|
|
→ Gjennomsnittleg behandlingstid
|
|
→ Innbyggjartilfredsheit (sentiment)
|
|
→ Hyppigaste henvendelsestypar
|
|
```
|
|
|
|
### Mønster 3: Untertekstproduksjon for offentleg video
|
|
|
|
```
|
|
Video → Azure Media Services (encode)
|
|
→ Speech Services: Transkribering (nb-NO)
|
|
→ Speech Translation: Omsetjing (en, ar, so)
|
|
→ VTT/SRT-filgenerering per språk
|
|
→ Azure CDN: Distribusjon
|
|
→ Videospelar med fleirspråklege untertekstar
|
|
```
|
|
|
|
---
|
|
|
|
## Norsk offentleg sektor
|
|
|
|
### Lovkrav
|
|
- **Offentlegheitslova**: Møteprotokoll skal vere tilgjengelege
|
|
- **Likestillings- og diskrimineringslova**: Lydopptak må tekstast for tilgjengelegheit
|
|
- **Arkivlova**: Transkripsjoner er arkivverdig materiale
|
|
- **WCAG 2.1 AA**: Video skal ha teksting/untertekstar
|
|
|
|
### Språkstøtte for norsk
|
|
- Norsk bokmål (`nb-NO`) fullt støtta for STT og TTS
|
|
- Custom Speech med fagterminologi for betre resultat
|
|
- Speech Translation frå norsk til 30+ målspråk
|
|
- Diarization fungerer med norsk tale (opp til 35 talarar)
|
|
|
|
### Personvern
|
|
- Lydopptak er personopplysingar — krev rettsleg grunnlag
|
|
- Batch Transcription: Resultat kan lagrast i eigen Azure Storage
|
|
- Ikkje lagring hos Microsoft etter prosessering (stateless)
|
|
- Custom Speech-treningsdata: Kontroller plassering og tilgang
|
|
- Anbefaling: Sletting av lydfilar etter transkribering om ikkje arkivpliktig
|
|
|
|
### DPIA-vurdering
|
|
- Transkribering av møte/telefonar krev DPIA
|
|
- Vurder: Samtykke, informasjonsplikt, lagringsbegrensing
|
|
- Diarization identifiserer talarar med generisk ID — ikkje biometrisk identifikasjon
|
|
- Container-deployment for ekstra datakontroll
|
|
|
|
---
|
|
|
|
## Beslutningsrammeverk
|
|
|
|
| Scenario | Anbefaling | Grunngjeving |
|
|
|----------|------------|--------------|
|
|
| Live møtetranskribering | Speech SDK med ConversationTranscriber | Sanntid + diarization |
|
|
| Arkiv-transkribering (100+ filer) | Batch Transcription API | Asynkron, skalerbar |
|
|
| Kort lydfil (< 5 min) | Fast Transcription API | Synkron, låg latency |
|
|
| Fleirspråkleg teneste | Speech Translation SDK | Sanntid omsetjing |
|
|
| Kontekstuell omsetjing | Transkribering + GPT-4o | Betre kvalitet |
|
|
| Callcenter-analyse | Batch + sentiment + oppsummering | End-to-end innsikt |
|
|
| Video-untertekstar | Batch + VTT-generering | WCAG-kompatibelt |
|
|
| On-premises krav | Speech containers + Batch Kit | Ingen data ut av nettverk |
|
|
|
|
---
|
|
|
|
## For Cosmo
|
|
|
|
- **Batch Transcription API er standard for volumbasert transkribering** — asynkron prosessering av store lydarkiv med diarization (opp til 35 talarar), ordnivå-tidsstempel og automatisk interpunksjon, resultat til eigen Azure Storage.
|
|
- **Diarization er tilgjengeleg i både sanntid og batch** — ConversationTranscriber (SDK) for live-møte, og `diarizationEnabled` + `diarization.speakers` i Batch API for opptak, med speaker-ID per frase i output.
|
|
- **Norsk bokmål (`nb-NO`) er fullt støtta for STT** — Custom Speech med plain text og structured text-treningsdata forbetrar gjenkjenning av fagterminologi (t.d. "detaljreguleringsplan", "konsekvensutgreiing").
|
|
- **Kombiner transkribering med Azure OpenAI for verdiskaping** — GPT-4o kan oppsummere møtereferat, trekke ut vedtakspunkt, kategorisere henvendelsestypar og utføre kontekstuell omsetjing med betre kvalitet enn direkte taleoversetting.
|
|
- **Human-in-the-loop basert på confidence scores** er påkravd for juridisk bindande transkripsjoner — automatisk godkjenning over 0.85, manuell korrekturlesing for 0.6-0.85, og re-innspeling under 0.6.
|