Vishal Patil commited on
Commit
5db6b38
·
verified ·
1 Parent(s): 3bb4a6a

Update pipeline.py

Browse files
Files changed (1) hide show
  1. pipeline.py +107 -59
pipeline.py CHANGED
@@ -1,68 +1,116 @@
1
- import tempfile
2
- import traceback
 
 
 
 
3
  from pathlib import Path
4
- import gradio as gr
5
- from pipeline import get_pipeline, to_json, to_csv, to_abab_text
6
 
7
- UPLOAD_DIR = Path(tempfile.gettempdir()) / "speech_annotation"
8
- UPLOAD_DIR.mkdir(parents=True, exist_ok=True)
9
 
10
- _last_segments = []
11
 
12
- def process_audio(audio_path, num_speakers):
13
- global _last_segments
14
- if audio_path is None:
15
- return "⚠️ Please upload an audio file first.", ""
16
- try:
17
- pipeline = get_pipeline()
18
- n = int(num_speakers) if num_speakers and int(num_speakers) > 0 else 0
19
- segments = pipeline.process(audio_path, num_speakers=n)
20
- except Exception as e:
21
- return f"❌ Error: {e}\n{traceback.format_exc()}", ""
22
- if not segments:
23
- return "⚠️ No speech detected.", ""
24
- _last_segments = segments
25
- abab = to_abab_text(segments)
26
- unique = len(set(s.speaker for s in segments))
27
- return f"✅ Done — {len(segments)} segments, {unique} speaker(s) detected", abab
28
 
29
- def export_json():
30
- if not _last_segments:
31
- return None
32
- out = str(UPLOAD_DIR / "annotation.json")
33
- to_json(_last_segments, out)
34
- return out
35
 
36
- def export_csv():
37
- if not _last_segments:
38
- return None
39
- out = str(UPLOAD_DIR / "annotation.csv")
40
- to_csv(_last_segments, out)
41
- return out
42
 
43
- with gr.Blocks(title="Speech Annotation Pipeline") as demo:
44
- gr.Markdown("# 🎙️ Speech Annotation Pipeline")
45
- audio_input = gr.Audio(label="Upload Audio (.wav/.mp3/.flac)", type="filepath")
46
- num_speakers = gr.Slider(minimum=0, maximum=10, step=1, value=0,
47
- label="Number of speakers (0 = auto-detect)")
48
- run_btn = gr.Button("▶ Run Annotation", variant="primary")
49
- status_box = gr.Textbox(label="Status", value="Ready.", interactive=False)
50
- abab_box = gr.Textbox(label="Transcript (ABAB)", lines=15, value="",
51
- placeholder="Annotations will appear here...")
52
- with gr.Row():
53
- json_btn = gr.Button("Export JSON")
54
- csv_btn = gr.Button("Export CSV")
55
- json_file = gr.File(label="JSON")
56
- csv_file = gr.File(label="CSV")
57
 
58
- run_btn.click(
59
- fn=process_audio,
60
- inputs=[audio_input, num_speakers],
61
- outputs=[status_box, abab_box],
62
- queue=False
63
- )
64
- json_btn.click(fn=export_json, inputs=[], outputs=[json_file])
65
- csv_btn.click(fn=export_csv, inputs=[], outputs=[csv_file])
66
 
67
- if __name__ == "__main__":
68
- demo.launch(server_name="0.0.0.0", server_port=7860, show_error=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import csv
4
+ import time
5
+ import logging
6
+ import httpx
7
  from pathlib import Path
8
+ from dataclasses import dataclass
9
+ from typing import List, Optional
10
 
11
+ logging.basicConfig(level=logging.INFO)
12
+ logger = logging.getLogger(__name__)
13
 
14
+ DEEPGRAM_API_KEY = os.environ.get("DEEPGRAM_API_KEY", "")
15
 
16
+ @dataclass
17
+ class AnnotationSegment:
18
+ speaker: str
19
+ start: float
20
+ end: float
21
+ text: str
 
 
 
 
 
 
 
 
 
 
22
 
23
+ @property
24
+ def start_fmt(self):
25
+ m, s = divmod(int(self.start), 60)
26
+ return f"{m:02d}:{s:02d}"
 
 
27
 
28
+ @property
29
+ def end_fmt(self):
30
+ m, s = divmod(int(self.end), 60)
31
+ return f"{m:02d}:{s:02d}"
 
 
32
 
33
+ def to_abab(self):
34
+ return f"{self.speaker} {self.start_fmt} {self.text}"
 
 
 
 
 
 
 
 
 
 
 
 
35
 
36
+ SPEAKER_LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
 
 
 
 
 
 
 
37
 
38
+ def _speaker_label(idx):
39
+ return SPEAKER_LETTERS[idx] if idx < len(SPEAKER_LETTERS) else f"SPK{idx}"
40
+
41
+ class SpeechAnnotationPipeline:
42
+ def process(self, audio_path, num_speakers=0, **kwargs):
43
+ if not DEEPGRAM_API_KEY:
44
+ raise EnvironmentError("DEEPGRAM_API_KEY not set.")
45
+ logger.info(f"Sending to Deepgram: {audio_path}")
46
+ t0 = time.time()
47
+ audio_bytes = Path(audio_path).read_bytes()
48
+ ext = Path(audio_path).suffix.lower().lstrip(".")
49
+ mime = {"wav":"audio/wav","mp3":"audio/mpeg","mp4":"audio/mp4",
50
+ "m4a":"audio/mp4","flac":"audio/flac","ogg":"audio/ogg"}.get(ext,"audio/wav")
51
+ params = {"model":"nova-2","diarize":"true","punctuate":"true",
52
+ "utterances":"true","smart_format":"true"}
53
+ headers = {"Authorization": f"Token {DEEPGRAM_API_KEY}", "Content-Type": mime}
54
+ response = httpx.post("https://api.deepgram.com/v1/listen",
55
+ params=params, headers=headers,
56
+ content=audio_bytes, timeout=300)
57
+ response.raise_for_status()
58
+ data = response.json()
59
+ utterances = data.get("results", {}).get("utterances", [])
60
+ if not utterances:
61
+ words = data.get("results",{}).get("channels",[{}])[0]\
62
+ .get("alternatives",[{}])[0].get("words",[])
63
+ utterances = self._words_to_utterances(words)
64
+ segments = []
65
+ for utt in utterances:
66
+ segments.append(AnnotationSegment(
67
+ speaker=_speaker_label(utt.get("speaker", 0)),
68
+ start=utt.get("start", 0),
69
+ end=utt.get("end", 0),
70
+ text=utt.get("transcript", "").strip(),
71
+ ))
72
+ logger.info(f"Done in {time.time()-t0:.1f}s — {len(segments)} segments")
73
+ return segments
74
+
75
+ def _words_to_utterances(self, words):
76
+ if not words:
77
+ return []
78
+ utterances = []
79
+ current = {"speaker": words[0].get("speaker",0), "start": words[0].get("start",0),
80
+ "end": words[0].get("end",0),
81
+ "transcript": words[0].get("punctuated_word", words[0].get("word",""))}
82
+ for word in words[1:]:
83
+ if word.get("speaker") == current["speaker"]:
84
+ current["end"] = word.get("end", current["end"])
85
+ current["transcript"] += " " + word.get("punctuated_word", word.get("word",""))
86
+ else:
87
+ utterances.append(current)
88
+ current = {"speaker": word.get("speaker",0), "start": word.get("start",0),
89
+ "end": word.get("end",0),
90
+ "transcript": word.get("punctuated_word", word.get("word",""))}
91
+ utterances.append(current)
92
+ return utterances
93
+
94
+ def to_json(segments, path):
95
+ data = [{"speaker": s.speaker, "start": round(s.start,3), "end": round(s.end,3),
96
+ "start_fmt": s.start_fmt, "end_fmt": s.end_fmt, "text": s.text} for s in segments]
97
+ with open(path, "w", encoding="utf-8") as f:
98
+ json.dump(data, f, indent=2, ensure_ascii=False)
99
+
100
+ def to_csv(segments, path):
101
+ with open(path, "w", newline="", encoding="utf-8") as f:
102
+ writer = csv.writer(f)
103
+ writer.writerow(["speaker","start","end","text"])
104
+ for s in segments:
105
+ writer.writerow([s.speaker, s.start_fmt, s.end_fmt, s.text])
106
+
107
+ def to_abab_text(segments):
108
+ return "\n".join(s.to_abab() for s in segments)
109
+
110
+ _instance = None
111
+
112
+ def get_pipeline():
113
+ global _instance
114
+ if _instance is None:
115
+ _instance = SpeechAnnotationPipeline()
116
+ return _instance