jens328's picture
Update app.py
26fecd2 verified
import gradio as gr
from transformers import pipeline
import librosa
import numpy as np
# ---------------------------------------------------------
# 1. AUDIO-MODELL LADEN (für genaue Erkennung)
# ---------------------------------------------------------
classifier = pipeline(
task="zero-shot-audio-classification",
model="laion/clap-htsat-unfused"
)
CANDIDATE_LABELS = [
"dog barking",
"dog growling",
"people talking",
"traffic noise",
"car passing",
"bird singing",
"music",
"silence",
]
# ---------------------------------------------------------
# 2. FESTE PARAMETER FÜR ENERGIE-ANALYSE
# ---------------------------------------------------------
ENERGY_FRAME_MS = 25 # Frame-Länge für Energie (ms)
ENERGY_HOP_MS = 10 # Schrittweite (ms)
ENERGY_QUANTILE = 0.80 # Lautheitsschwelle (oberes 20%-Quantil)
MIN_EVENT_DURATION = 0.25 # min. Dauer eines lauten Events (Sek.)
MIN_SEGMENT_FOR_CLAP = 0.15 # minimale Segmentlänge, damit CLAP Sinn macht (Sek.)
# ---------------------------------------------------------
# 3. FUNKTION: LAUTE EVENTS FINDEN
# ---------------------------------------------------------
def find_loud_events(y, sr):
frame_length = int(sr * ENERGY_FRAME_MS / 1000)
hop_length = int(sr * ENERGY_HOP_MS / 1000)
frame_length = max(frame_length, 512)
hop_length = max(hop_length, 128)
rms = librosa.feature.rms(
y=y,
frame_length=frame_length,
hop_length=hop_length
)[0]
times = librosa.frames_to_time(
np.arange(len(rms)),
sr=sr,
hop_length=hop_length
)
thr = np.quantile(rms, ENERGY_QUANTILE)
mask = rms > thr
events = []
in_event = False
start_t = 0.0
for i, is_loud in enumerate(mask):
t = times[i]
if is_loud and not in_event:
in_event = True
start_t = t
elif not is_loud and in_event:
end_t = t
if end_t - start_t >= MIN_EVENT_DURATION:
events.append((start_t, end_t))
in_event = False
if in_event:
end_t = times[-1]
if end_t - start_t >= MIN_EVENT_DURATION:
events.append((start_t, end_t))
return events
# ---------------------------------------------------------
# 4. BELL-SEGMENTE ERKENNEN
# - entweder „fast mode“ (nur Energie)
# - oder CLAP-basiert, aber gebatcht
# ---------------------------------------------------------
def detect_bark_windows(y, sr, loud_events, bark_prob_threshold, use_clap):
"""
Gibt eine Liste von (start_s, end_s) zurück, die als Bellen gewertet werden.
Wenn use_clap=False: jedes laute Event = Bellen (reine Lautstärke-Logik).
Wenn use_clap=True: CLAP bewertet die Events.
"""
if not use_clap:
# Fast Mode: alles, was laut ist, wird als Bellen gezählt
return [(s, e, 1.0) for (s, e) in loud_events]
# CLAP-Mode: wir batchen die Events für die Pipeline
segments = []
meta = [] # (start_s, end_s) zu jedem Segment
for (s, e) in loud_events:
if e - s < MIN_SEGMENT_FOR_CLAP:
continue
start_idx = int(s * sr)
end_idx = int(e * sr)
seg = y[start_idx:end_idx]
if len(seg) == 0:
continue
segments.append(seg)
meta.append((s, e))
if not segments:
return []
# Batch-Aufruf der Pipeline (deutlich schneller als Einzel-Aufrufe)
results_list = classifier(
segments,
candidate_labels=CANDIDATE_LABELS,
multi_label=True,
batch_size=4,
)
bark_windows = []
for (s, e), results in zip(meta, results_list):
bark_score = 0.0
for r in results:
if r["label"].lower() == "dog barking":
bark_score = float(r["score"])
break
if bark_score >= bark_prob_threshold:
bark_windows.append((s, e, bark_score))
return bark_windows
# ---------------------------------------------------------
# 5. HAUPT-ANALYSEFUNKTION
# ---------------------------------------------------------
def analyze_barking(audio_path, max_pause_sec, bark_prob_threshold, fast_mode):
# 0. Upload prüfen
if audio_path is None or audio_path == "":
return "Es wurde keine Audiodatei hochgeladen."
# 1. Audio laden
try:
y, sr = librosa.load(audio_path, sr=16000, mono=True)
except Exception as e:
return f"Fehler beim Laden der Audiodatei: {e}"
duration = len(y) / sr
if duration == 0:
return "Die Audiodatei ist leer."
# 2. Laute Ereignisse finden
loud_events = find_loud_events(y, sr)
if not loud_events:
return "Keine lauten Ereignisse gefunden – vermutlich kein Bellen."
# 3. Bellen-Segmente finden
bark_windows = detect_bark_windows(
y,
sr,
loud_events,
bark_prob_threshold,
use_clap=not fast_mode
)
if not bark_windows:
if fast_mode:
return (
"Fast Mode (ohne KI): keine ausreichend lauten Ereignisse, "
"die als Bellen interpretiert wurden."
)
else:
return (
"Es wurde kein Hundebellen mit ausreichend hoher Sicherheit erkannt.\n\n"
f"(Schwellwert für 'dog barking' = {bark_prob_threshold:.2f})"
)
# 4. Bell-Segmente zu Episoden zusammenfassen
bark_windows.sort(key=lambda x: x[0]) # nach Startzeit
episodes = []
cur_start, cur_end, _ = bark_windows[0]
for s, e, _ in bark_windows[1:]:
if s - cur_end <= max_pause_sec:
cur_end = max(cur_end, e)
else:
episodes.append((cur_start, cur_end))
cur_start, cur_end = s, e
episodes.append((cur_start, cur_end))
# 5. Kennzahlen
count = len(episodes)
total_seconds = sum(e2 - e1 for (e1, e2) in episodes)
lines = []
mode_text = "Fast Mode (nur Energie)" if fast_mode else "CLAP-KI-Modus"
lines.append(f"**Modus:** {mode_text}")
lines.append(f"**A: Anzahl der Bell-Ereignisse:** {count}")
lines.append(f"**B: Gesamtdauer des Bellens:** {total_seconds:.1f} Sekunden\n")
lines.append(
f"_Regel_: > {max_pause_sec:.1f} Sekunden Pause = neues Ereignis\n"
f"_Schwellwert 'dog barking'_: {bark_prob_threshold:.2f}\n"
)
lines.append("**Details:**")
for i, (s, e) in enumerate(episodes, start=1):
dur = e - s
lines.append(f"- Ereignis {i}: {s:.1f}s bis {e:.1f}s — Dauer: {dur:.1f}s")
return "\n".join(lines)
# ---------------------------------------------------------
# 6. GRADIO UI – MIT SLIDERN & FAST-MODE
# ---------------------------------------------------------
audio_input = gr.Audio(type="filepath", label="Audio hochladen (.wav, .mp3)")
pause_slider = gr.Slider(
minimum=1.0,
maximum=10.0,
value=3.0,
step=0.5,
label="Maximale Pause zwischen Bellen (Sekunden)",
)
threshold_slider = gr.Slider(
minimum=0.000001, # 1e-6
maximum=0.9,
value=0.35,
step=0.000001,
label="Schwellwert für 'dog barking' (0.000001–1)",
info="Je kleiner, desto empfindlicher (erkennt sehr leises/fernes Bellen)."
)
fast_checkbox = gr.Checkbox(
value=False,
label="Fast Mode (nur Lautstärke, ohne KI-Modell – sehr schnell, aber ungenauer)",
)
demo = gr.Interface(
fn=analyze_barking,
inputs=[audio_input, pause_slider, threshold_slider, fast_checkbox],
outputs=gr.Markdown(),
title="Barking Episode Analyzer (mit Parametern & Fast Mode)",
description=(
"Erkennt Hundebellen in Aufnahmen.\n\n"
"Optionen:\n"
"- **Maximale Pause**: ab welcher Pause ein neues Bell-Ereignis gezählt wird.\n"
"- **Schwellwert**: ab welcher Wahrscheinlichkeit 'dog barking' gezählt wird.\n"
"- **Fast Mode**: nur Lautstärke-Analyse (schnell), ohne 'dog barking'-Modell."
),
)
if __name__ == "__main__":
demo.launch()