Spaces:
Running on Zero
Running on Zero
Full-audio transcription, language detection, save with speaker names
Browse files
app.py
CHANGED
|
@@ -71,15 +71,41 @@ def _analyse_gpu(audio_path, num_speakers):
|
|
| 71 |
output = diarizer(audio_path, **kwargs)
|
| 72 |
dia = getattr(output, "speaker_diarization", output)
|
| 73 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
segmente = []
|
| 75 |
-
for
|
| 76 |
-
text = None
|
| 77 |
-
if turn.duration >= 0.3:
|
| 78 |
-
wellenform, sr = loader.crop(audio_path, Segment(turn.start, turn.end))
|
| 79 |
-
text = asr({"array": wellenform.squeeze(0).numpy(),
|
| 80 |
-
"sampling_rate": sr})["text"].strip()
|
| 81 |
segmente.append({"start": round(turn.start, 2),
|
| 82 |
-
"ende": round(turn.end, 2), "label": lb,
|
|
|
|
| 83 |
|
| 84 |
stats = {lb: {"dauer": round(dia.label_duration(lb), 1),
|
| 85 |
"turns": len(dia.label_timeline(lb))}
|
|
@@ -92,18 +118,21 @@ def _analyse_gpu(audio_path, num_speakers):
|
|
| 92 |
"ende": round(seg.end, 2), "wer": wer})
|
| 93 |
|
| 94 |
return {"dauer": round(loader.get_duration(audio_path), 1),
|
| 95 |
-
"segmente": segmente, "stats": stats, "overlaps": overlaps
|
|
|
|
| 96 |
|
| 97 |
|
| 98 |
# ---------- Markdown-Ablage ----------
|
| 99 |
|
| 100 |
-
def _markdown(daten, quelle, zeitpunkt):
|
| 101 |
-
|
|
|
|
| 102 |
frontmatter = {
|
| 103 |
"titel": f"Aufnahme {zeitpunkt:%Y-%m-%d %H:%M}",
|
| 104 |
"datum": zeitpunkt.isoformat(timespec="seconds"),
|
| 105 |
"dauer_s": daten["dauer"],
|
| 106 |
"sprecher": len(daten["stats"]),
|
|
|
|
| 107 |
"quelle": quelle,
|
| 108 |
"redeanteile_s": {namen[lb]: st["dauer"]
|
| 109 |
for lb, st in daten["stats"].items()},
|
|
@@ -185,15 +214,30 @@ def analysieren_api(key, audio, num_speakers):
|
|
| 185 |
except Exception as e:
|
| 186 |
return {"ok": False, "fehler": f"Analyse fehlgeschlagen: {e}"}
|
| 187 |
zeitpunkt = datetime.now(ZEITZONE)
|
| 188 |
-
md_text = _markdown(daten, quelle, zeitpunkt)
|
| 189 |
-
try:
|
| 190 |
-
pfad = _speichern(md_text, zeitpunkt)
|
| 191 |
-
except Exception:
|
| 192 |
-
pfad = None
|
| 193 |
namen, farben = _namen_farben(daten["stats"])
|
| 194 |
return {"ok": True, "daten": daten, "namen": namen, "farben": farben,
|
| 195 |
-
"zeitpunkt": zeitpunkt.isoformat(timespec="seconds"),
|
| 196 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 197 |
|
| 198 |
|
| 199 |
def verlauf_api(key):
|
|
@@ -242,6 +286,12 @@ with gr.Blocks(title="Sprecher-Analyse API") as demo:
|
|
| 242 |
out_analyse = gr.JSON()
|
| 243 |
b_analyse.click(analysieren_api, [key, audio, num_speakers],
|
| 244 |
out_analyse, api_name="analysieren")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 245 |
with gr.Tab("Verlauf"):
|
| 246 |
b_verlauf = gr.Button("Verlauf laden")
|
| 247 |
out_verlauf = gr.JSON()
|
|
|
|
| 71 |
output = diarizer(audio_path, **kwargs)
|
| 72 |
dia = getattr(output, "speaker_diarization", output)
|
| 73 |
|
| 74 |
+
# Die ganze Aufnahme in einem Stück transkribieren: voller Kontext liefert
|
| 75 |
+
# deutlich bessere Texte als Einzelsegmente; die Sprache erkennt Whisper
|
| 76 |
+
# dabei automatisch.
|
| 77 |
+
transkript = asr(audio_path, return_timestamps=True, return_language=True)
|
| 78 |
+
chunks = []
|
| 79 |
+
for c in transkript.get("chunks", []):
|
| 80 |
+
start, ende = c.get("timestamp") or (None, None)
|
| 81 |
+
if start is None or not c.get("text", "").strip():
|
| 82 |
+
continue
|
| 83 |
+
chunks.append({"start": float(start),
|
| 84 |
+
"ende": float(ende if ende is not None else start + 30.0),
|
| 85 |
+
"text": c["text"].strip(),
|
| 86 |
+
"sprache": c.get("language")})
|
| 87 |
+
sprache = next((c["sprache"] for c in chunks if c.get("sprache")), None)
|
| 88 |
+
|
| 89 |
+
turns = [(turn, lb) for turn, _, lb in dia.itertracks(yield_label=True)]
|
| 90 |
+
texte = [[] for _ in turns]
|
| 91 |
+
for c in chunks:
|
| 92 |
+
mitte = (c["start"] + c["ende"]) / 2
|
| 93 |
+
best, best_wert = None, None
|
| 94 |
+
for i, (turn, _) in enumerate(turns):
|
| 95 |
+
ueberlappung = max(0.0, min(c["ende"], turn.end)
|
| 96 |
+
- max(c["start"], turn.start))
|
| 97 |
+
abstand = abs(mitte - (turn.start + turn.end) / 2)
|
| 98 |
+
wert = (-ueberlappung, abstand)
|
| 99 |
+
if best_wert is None or wert < best_wert:
|
| 100 |
+
best, best_wert = i, wert
|
| 101 |
+
if best is not None:
|
| 102 |
+
texte[best].append(c["text"])
|
| 103 |
+
|
| 104 |
segmente = []
|
| 105 |
+
for i, (turn, lb) in enumerate(turns):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
segmente.append({"start": round(turn.start, 2),
|
| 107 |
+
"ende": round(turn.end, 2), "label": lb,
|
| 108 |
+
"text": " ".join(texte[i]) or None})
|
| 109 |
|
| 110 |
stats = {lb: {"dauer": round(dia.label_duration(lb), 1),
|
| 111 |
"turns": len(dia.label_timeline(lb))}
|
|
|
|
| 118 |
"ende": round(seg.end, 2), "wer": wer})
|
| 119 |
|
| 120 |
return {"dauer": round(loader.get_duration(audio_path), 1),
|
| 121 |
+
"segmente": segmente, "stats": stats, "overlaps": overlaps,
|
| 122 |
+
"sprache": sprache}
|
| 123 |
|
| 124 |
|
| 125 |
# ---------- Markdown-Ablage ----------
|
| 126 |
|
| 127 |
+
def _markdown(daten, quelle, zeitpunkt, namen=None):
|
| 128 |
+
std_namen, _ = _namen_farben(daten["stats"])
|
| 129 |
+
namen = {**std_namen, **(namen or {})}
|
| 130 |
frontmatter = {
|
| 131 |
"titel": f"Aufnahme {zeitpunkt:%Y-%m-%d %H:%M}",
|
| 132 |
"datum": zeitpunkt.isoformat(timespec="seconds"),
|
| 133 |
"dauer_s": daten["dauer"],
|
| 134 |
"sprecher": len(daten["stats"]),
|
| 135 |
+
"sprache": daten.get("sprache"),
|
| 136 |
"quelle": quelle,
|
| 137 |
"redeanteile_s": {namen[lb]: st["dauer"]
|
| 138 |
for lb, st in daten["stats"].items()},
|
|
|
|
| 214 |
except Exception as e:
|
| 215 |
return {"ok": False, "fehler": f"Analyse fehlgeschlagen: {e}"}
|
| 216 |
zeitpunkt = datetime.now(ZEITZONE)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 217 |
namen, farben = _namen_farben(daten["stats"])
|
| 218 |
return {"ok": True, "daten": daten, "namen": namen, "farben": farben,
|
| 219 |
+
"zeitpunkt": zeitpunkt.isoformat(timespec="seconds"),
|
| 220 |
+
"quelle": quelle, "sprache": daten.get("sprache")}
|
| 221 |
+
|
| 222 |
+
|
| 223 |
+
def speichern_api(key, analyse, namen):
|
| 224 |
+
if not _pruefe(key):
|
| 225 |
+
return {"ok": False, "fehler": "Ungültiger Zugangsschlüssel."}
|
| 226 |
+
try:
|
| 227 |
+
daten = analyse["daten"]
|
| 228 |
+
zeitpunkt = datetime.fromisoformat(analyse["zeitpunkt"])
|
| 229 |
+
quelle = analyse.get("quelle") or "aufnahme"
|
| 230 |
+
daten["stats"]
|
| 231 |
+
except (KeyError, TypeError, ValueError):
|
| 232 |
+
return {"ok": False, "fehler": "Ungültige Analysedaten."}
|
| 233 |
+
eigene = {k: v.strip() for k, v in (namen or {}).items()
|
| 234 |
+
if isinstance(v, str) and v.strip()}
|
| 235 |
+
md_text = _markdown(daten, quelle, zeitpunkt, eigene)
|
| 236 |
+
try:
|
| 237 |
+
pfad = _speichern(md_text, zeitpunkt)
|
| 238 |
+
except Exception as e:
|
| 239 |
+
return {"ok": False, "fehler": f"Speichern fehlgeschlagen: {e}"}
|
| 240 |
+
return {"ok": True, "pfad": pfad, "markdown": md_text}
|
| 241 |
|
| 242 |
|
| 243 |
def verlauf_api(key):
|
|
|
|
| 286 |
out_analyse = gr.JSON()
|
| 287 |
b_analyse.click(analysieren_api, [key, audio, num_speakers],
|
| 288 |
out_analyse, api_name="analysieren")
|
| 289 |
+
analyse_json = gr.JSON(label="Analyse-Objekt (aus /analysieren)")
|
| 290 |
+
namen_json = gr.JSON(label='Sprechernamen, z. B. {"SPEAKER_00": "Nils"}')
|
| 291 |
+
b_speichern = gr.Button("Als Markdown speichern")
|
| 292 |
+
out_speichern = gr.JSON()
|
| 293 |
+
b_speichern.click(speichern_api, [key, analyse_json, namen_json],
|
| 294 |
+
out_speichern, api_name="speichern")
|
| 295 |
with gr.Tab("Verlauf"):
|
| 296 |
b_verlauf = gr.Button("Verlauf laden")
|
| 297 |
out_verlauf = gr.JSON()
|