ABAB-Annotation / pipeline.py
Vishal Patil
Update pipeline.py
57f0b58 verified
Raw
History Blame Contribute Delete
4.55 kB
import os
import json
import csv
import time
import logging
import httpx
from pathlib import Path
from dataclasses import dataclass
from typing import List, Optional
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
DEEPGRAM_API_KEY = os.environ.get("DEEPGRAM_API_KEY", "")
@dataclass
class AnnotationSegment:
speaker: str
start: float
end: float
text: str
@property
def start_fmt(self):
m, s = divmod(int(self.start), 60)
return f"{m:02d}:{s:02d}"
@property
def end_fmt(self):
m, s = divmod(int(self.end), 60)
return f"{m:02d}:{s:02d}"
def to_abab(self):
return f"{self.speaker} {self.start_fmt} {self.text}"
SPEAKER_LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
def _speaker_label(idx):
return SPEAKER_LETTERS[idx] if idx < len(SPEAKER_LETTERS) else f"SPK{idx}"
class SpeechAnnotationPipeline:
def process(self, audio_path, num_speakers=0, **kwargs):
if not DEEPGRAM_API_KEY:
raise EnvironmentError("DEEPGRAM_API_KEY not set.")
logger.info(f"Sending to Deepgram: {audio_path}")
t0 = time.time()
audio_bytes = Path(audio_path).read_bytes()
ext = Path(audio_path).suffix.lower().lstrip(".")
mime = {"wav":"audio/wav","mp3":"audio/mpeg","mp4":"audio/mp4",
"m4a":"audio/mp4","flac":"audio/flac","ogg":"audio/ogg"}.get(ext,"audio/wav")
params = {"model":"nova-2","diarize":"true","punctuate":"true",
"utterances":"true","smart_format":"true"}
if num_speakers and num_speakers > 0:
params["diarize_version"] = "latest"
params["num_speakers"] = str(num_speakers)
headers = {"Authorization": f"Token {DEEPGRAM_API_KEY}", "Content-Type": mime}
response = httpx.post("https://api.deepgram.com/v1/listen",
params=params, headers=headers,
content=audio_bytes, timeout=300)
response.raise_for_status()
data = response.json()
utterances = data.get("results", {}).get("utterances", [])
if not utterances:
words = data.get("results",{}).get("channels",[{}])[0]\
.get("alternatives",[{}])[0].get("words",[])
utterances = self._words_to_utterances(words)
segments = []
for utt in utterances:
segments.append(AnnotationSegment(
speaker=_speaker_label(utt.get("speaker", 0)),
start=utt.get("start", 0),
end=utt.get("end", 0),
text=utt.get("transcript", "").strip(),
))
logger.info(f"Done in {time.time()-t0:.1f}s — {len(segments)} segments")
return segments
def _words_to_utterances(self, words):
if not words:
return []
utterances = []
current = {"speaker": words[0].get("speaker",0), "start": words[0].get("start",0),
"end": words[0].get("end",0),
"transcript": words[0].get("punctuated_word", words[0].get("word",""))}
for word in words[1:]:
if word.get("speaker") == current["speaker"]:
current["end"] = word.get("end", current["end"])
current["transcript"] += " " + word.get("punctuated_word", word.get("word",""))
else:
utterances.append(current)
current = {"speaker": word.get("speaker",0), "start": word.get("start",0),
"end": word.get("end",0),
"transcript": word.get("punctuated_word", word.get("word",""))}
utterances.append(current)
return utterances
def to_json(segments, path):
data = [{"speaker": s.speaker, "start": round(s.start,3), "end": round(s.end,3),
"start_fmt": s.start_fmt, "end_fmt": s.end_fmt, "text": s.text} for s in segments]
with open(path, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
def to_csv(segments, path):
with open(path, "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerow(["speaker","start","end","text"])
for s in segments:
writer.writerow([s.speaker, s.start_fmt, s.end_fmt, s.text])
def to_abab_text(segments):
return "\n".join(s.to_abab() for s in segments)
_instance = None
def get_pipeline():
global _instance
if _instance is None:
_instance = SpeechAnnotationPipeline()
return _instance