Initialer Push, Grundsätzlich funktioniert das Programm
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env bash
|
||||
# ~/bin/call-recorder.sh
|
||||
set -euo pipefail
|
||||
|
||||
REC_DIR="$HOME"
|
||||
PIDFILE="/tmp/call-recorder.pid"
|
||||
LOGFILE="/tmp/call-recorder.log"
|
||||
|
||||
start_recording() {
|
||||
if [[ -f "$PIDFILE" ]] && kill -0 "$(cat "$PIDFILE")" 2>/dev/null; then
|
||||
notify-send "Call Recorder" "Läuft bereits (PID $(cat "$PIDFILE"))"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local sink source outfile
|
||||
sink="$(pactl get-default-sink).monitor"
|
||||
source="$(pactl get-default-source)"
|
||||
outfile="${REC_DIR}/call_$(date +%Y-%m-%d_%H-%M-%S).mp3"
|
||||
|
||||
ffmpeg -nostdin -y \
|
||||
-f pulse -i "$sink" \
|
||||
-f pulse -i "$source" \
|
||||
-filter_complex "[0:a]pan=mono|c0=0.5*c0+0.5*c1[browser];[1:a]pan=mono|c0=c0[mic];[browser][mic]amerge=inputs=2[aout]" \
|
||||
-map "[aout]" -ac 2 -c:a libmp3lame -q:a 2 \
|
||||
"$outfile" \
|
||||
> "$LOGFILE" 2>&1 &
|
||||
|
||||
echo $! > "$PIDFILE"
|
||||
notify-send "Call Recorder" "Aufnahme gestartet: $(basename "$outfile")"
|
||||
}
|
||||
|
||||
stop_recording() {
|
||||
if [[ ! -f "$PIDFILE" ]]; then
|
||||
notify-send "Call Recorder" "Keine laufende Aufnahme."
|
||||
exit 1
|
||||
fi
|
||||
local pid
|
||||
pid="$(cat "$PIDFILE")"
|
||||
kill -INT "$pid" 2>/dev/null || true
|
||||
# ffmpeg braucht einen Moment, um den MP3-Frame sauber abzuschließen
|
||||
for _ in {1..20}; do
|
||||
kill -0 "$pid" 2>/dev/null || break
|
||||
sleep 0.2
|
||||
done
|
||||
rm -f "$PIDFILE"
|
||||
notify-send "Call Recorder" "Aufnahme gestoppt."
|
||||
}
|
||||
|
||||
case "${1:-toggle}" in
|
||||
start) start_recording ;;
|
||||
stop) stop_recording ;;
|
||||
toggle) if [[ -f "$PIDFILE" ]]; then stop_recording; else start_recording; fi ;;
|
||||
*) echo "Usage: $0 {start|stop|toggle}"; exit 1 ;;
|
||||
esac
|
||||
@@ -0,0 +1,65 @@
|
||||
import whisperx
|
||||
import gc
|
||||
import os
|
||||
import sys
|
||||
|
||||
HF_TOKEN="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"
|
||||
DEVICE = "cuda"
|
||||
COMPUTE_TYPE = "float16"
|
||||
BATCH_SIZE = 4
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
print("Verwendung: python transkribiere_datei.py <audiodatei> [...]")
|
||||
sys.exit(1)
|
||||
|
||||
audio_files = sys.argv[1:]
|
||||
print(f"{len(audio_files)} Datei(en) zu transkribieren\n")
|
||||
|
||||
print("Lade Whisper large-v3...")
|
||||
model = whisperx.load_model("large-v3", DEVICE, compute_type=COMPUTE_TYPE, language="de")
|
||||
|
||||
print("Lade Diarization-Pipeline...")
|
||||
diarize_model = whisperx.diarize.DiarizationPipeline(token=HF_TOKEN, device=DEVICE)
|
||||
|
||||
for audio_path in audio_files:
|
||||
base = os.path.splitext(audio_path)[0]
|
||||
out_path = base + ".txt"
|
||||
fname = os.path.basename(audio_path)
|
||||
|
||||
print(f"\n{'='*60}")
|
||||
print(f"Verarbeite: {fname}")
|
||||
print(f"{'='*60}")
|
||||
|
||||
audio = whisperx.load_audio(audio_path)
|
||||
|
||||
print(" 1/4 Transkription...")
|
||||
result = model.transcribe(audio, batch_size=BATCH_SIZE, language="de")
|
||||
|
||||
print(" 2/4 Alignment...")
|
||||
model_a, metadata = whisperx.load_align_model(language_code="de", device=DEVICE)
|
||||
result = whisperx.align(result["segments"], model_a, metadata, audio, DEVICE,
|
||||
return_char_alignments=False)
|
||||
del model_a; gc.collect()
|
||||
|
||||
print(" 3/4 Speaker Diarization...")
|
||||
diarize_segments = diarize_model(audio)
|
||||
result = whisperx.assign_word_speakers(diarize_segments, result)
|
||||
|
||||
print(" 4/4 Speichere Transkript...")
|
||||
with open(out_path, "w", encoding="utf-8") as f:
|
||||
f.write(f"Transkript: {fname}\n")
|
||||
f.write("=" * 60 + "\n\n")
|
||||
current_speaker = None
|
||||
for seg in result["segments"]:
|
||||
speaker = seg.get("speaker", "UNBEKANNT")
|
||||
text = seg["text"].strip()
|
||||
start = seg["start"]
|
||||
end = seg["end"]
|
||||
if speaker != current_speaker:
|
||||
f.write(f"\n[{speaker}]\n")
|
||||
current_speaker = speaker
|
||||
f.write(f" [{start:6.1f}s – {end:5.1f}s] {text}\n")
|
||||
|
||||
print(f" -> Gespeichert: {out_path}")
|
||||
|
||||
print("\nAlle Dateien fertig!")
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
source /home/timbfrank/whisperx-env/bin/activate
|
||||
python /home/timbfrank/transkribiere_datei.py "$@"
|
||||
echo ""
|
||||
echo "Fertig! Fenster schließt in 10 Sekunden..."
|
||||
sleep 10
|
||||
@@ -0,0 +1,65 @@
|
||||
import whisperx
|
||||
import gc
|
||||
import os
|
||||
import sys
|
||||
|
||||
HF_TOKEN = "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"
|
||||
DEVICE = "cuda"
|
||||
COMPUTE_TYPE = "float16"
|
||||
BATCH_SIZE = 4
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
print("Verwendung: python transkribiere_datei.py <audiodatei> [...]")
|
||||
sys.exit(1)
|
||||
|
||||
audio_files = sys.argv[1:]
|
||||
print(f"{len(audio_files)} Datei(en) zu transkribieren\n")
|
||||
|
||||
print("Lade Whisper large-v3...")
|
||||
model = whisperx.load_model("large-v3", DEVICE, compute_type=COMPUTE_TYPE, language="de")
|
||||
|
||||
print("Lade Diarization-Pipeline...")
|
||||
diarize_model = whisperx.diarize.DiarizationPipeline(token=HF_TOKEN, device=DEVICE)
|
||||
|
||||
for audio_path in audio_files:
|
||||
base = os.path.splitext(audio_path)[0]
|
||||
out_path = base + ".txt"
|
||||
fname = os.path.basename(audio_path)
|
||||
|
||||
print(f"\n{'='*60}")
|
||||
print(f"Verarbeite: {fname}")
|
||||
print(f"{'='*60}")
|
||||
|
||||
audio = whisperx.load_audio(audio_path)
|
||||
|
||||
print(" 1/4 Transkription...")
|
||||
result = model.transcribe(audio, batch_size=BATCH_SIZE, language="de")
|
||||
|
||||
print(" 2/4 Alignment...")
|
||||
model_a, metadata = whisperx.load_align_model(language_code="de", device=DEVICE)
|
||||
result = whisperx.align(result["segments"], model_a, metadata, audio, DEVICE,
|
||||
return_char_alignments=False)
|
||||
del model_a; gc.collect()
|
||||
|
||||
print(" 3/4 Speaker Diarization...")
|
||||
diarize_segments = diarize_model(audio)
|
||||
result = whisperx.assign_word_speakers(diarize_segments, result)
|
||||
|
||||
print(" 4/4 Speichere Transkript...")
|
||||
with open(out_path, "w", encoding="utf-8") as f:
|
||||
f.write(f"Transkript: {fname}\n")
|
||||
f.write("=" * 60 + "\n\n")
|
||||
current_speaker = None
|
||||
for seg in result["segments"]:
|
||||
speaker = seg.get("speaker", "UNBEKANNT")
|
||||
text = seg["text"].strip()
|
||||
start = seg["start"]
|
||||
end = seg["end"]
|
||||
if speaker != current_speaker:
|
||||
f.write(f"\n[{speaker}]\n")
|
||||
current_speaker = speaker
|
||||
f.write(f" [{start:6.1f}s – {end:5.1f}s] {text}\n")
|
||||
|
||||
print(f" -> Gespeichert: {out_path}")
|
||||
|
||||
print("\nAlle Dateien fertig!")
|
||||
@@ -0,0 +1,66 @@
|
||||
import whisperx
|
||||
import gc
|
||||
import os
|
||||
|
||||
HF_TOKEN = "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"
|
||||
DEVICE = "cuda"
|
||||
COMPUTE_TYPE = "float16"
|
||||
BATCH_SIZE = 4
|
||||
|
||||
AUDIO_DIR = os.path.expanduser(
|
||||
"~/VRtual X Dropbox/Tim B. Frank/Apps/Tims Hermes/Projekte/battenfeld-cincinnati/2026-06-30_Audionotizen"
|
||||
)
|
||||
OUTPUT_DIR = os.path.expanduser("~/Transkripte/battenfeld-cincinnati")
|
||||
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||
|
||||
files = sorted([f for f in os.listdir(AUDIO_DIR) if f.endswith(".mp3")])
|
||||
print(f"{len(files)} Dateien gefunden: {files}\n")
|
||||
|
||||
print("Lade Whisper large-v3...")
|
||||
model = whisperx.load_model("large-v3", DEVICE, compute_type=COMPUTE_TYPE, language="de")
|
||||
|
||||
print("Lade Diarization-Pipeline...")
|
||||
diarize_model = whisperx.diarize.DiarizationPipeline(token=HF_TOKEN, device=DEVICE)
|
||||
|
||||
for fname in files:
|
||||
audio_path = os.path.join(AUDIO_DIR, fname)
|
||||
out_name = fname.replace(".mp3", ".txt")
|
||||
out_path = os.path.join(OUTPUT_DIR, out_name)
|
||||
|
||||
print(f"\n{'='*60}")
|
||||
print(f"Verarbeite: {fname}")
|
||||
print(f"{'='*60}")
|
||||
|
||||
audio = whisperx.load_audio(audio_path)
|
||||
|
||||
print(" 1/4 Transkription...")
|
||||
result = model.transcribe(audio, batch_size=BATCH_SIZE, language="de")
|
||||
|
||||
print(" 2/4 Alignment...")
|
||||
model_a, metadata = whisperx.load_align_model(language_code="de", device=DEVICE)
|
||||
result = whisperx.align(result["segments"], model_a, metadata, audio, DEVICE,
|
||||
return_char_alignments=False)
|
||||
del model_a; gc.collect()
|
||||
|
||||
print(" 3/4 Speaker Diarization...")
|
||||
diarize_segments = diarize_model(audio)
|
||||
result = whisperx.assign_word_speakers(diarize_segments, result)
|
||||
|
||||
print(" 4/4 Speichere Transkript...")
|
||||
with open(out_path, "w", encoding="utf-8") as f:
|
||||
f.write(f"Transkript: {fname}\n")
|
||||
f.write("="*60 + "\n\n")
|
||||
current_speaker = None
|
||||
for seg in result["segments"]:
|
||||
speaker = seg.get("speaker", "UNBEKANNT")
|
||||
text = seg["text"].strip()
|
||||
start = seg["start"]
|
||||
end = seg["end"]
|
||||
if speaker != current_speaker:
|
||||
f.write(f"\n[{speaker}]\n")
|
||||
current_speaker = speaker
|
||||
f.write(f" [{start:6.1f}s – {end:5.1f}s] {text}\n")
|
||||
|
||||
print(f" -> Gespeichert: {out_path}")
|
||||
|
||||
print("\nAlle Dateien fertig!")
|
||||
@@ -0,0 +1,94 @@
|
||||
|
||||
HERMES:
|
||||
Hier der vollständige Inhalt:
|
||||
|
||||
---
|
||||
|
||||
Trigger-Keywords:
|
||||
- transkript verarbeiten
|
||||
- gesprächsprotokoll
|
||||
- gespräch aufbereiten
|
||||
- meeting zusammenfassen
|
||||
- kundengespräch
|
||||
|
||||
---
|
||||
|
||||
Pflicht: Fehlende Infos zuerst klären
|
||||
|
||||
Bevor du mit der Verarbeitung beginnst, prüfe ob folgende Infos vorhanden sind. Wenn nicht → explizit nachfragen:
|
||||
|
||||
- Datum des Gesprächs
|
||||
- Gesprächsort / Kanal (Telefon, vor Ort, Video)
|
||||
- Gesprächsteilnehmer (Name + Rolle / Unternehmen) — wer spricht für wen?
|
||||
- Projektzuordnung (welches Projekt oder welche Kampagne?)
|
||||
- Falls Personen im Transkript nicht namentlich zugeordnet sind: Wer ist wer?
|
||||
|
||||
Erst wenn diese Infos vollständig sind, mit dem Template beginnen.
|
||||
|
||||
---
|
||||
|
||||
Output-Template
|
||||
|
||||
Ausgabe immer in sauberem Markdown. Dateiname-Vorschlag: YYYY-MM-DD_Gesprächsprotokoll_[Kundenname].md
|
||||
|
||||
# Gesprächsprotokoll – [Kundenname / Projekt]
|
||||
|
||||
---
|
||||
|
||||
## Basisinfos
|
||||
- **Datum:**
|
||||
- **Kanal:** (Telefon / vor Ort / Video)
|
||||
- **Ort / Plattform:**
|
||||
- **Teilnehmer:**
|
||||
- [Name] – [Rolle], [Unternehmen]
|
||||
- [Name] – [Rolle], [Unternehmen]
|
||||
- **Projekt / Kontext:**
|
||||
- **Gesprächstyp:** (Erstgespräch / Follow-up / Demo / Projektgespräch / Abschluss)
|
||||
|
||||
---
|
||||
|
||||
## Gesprächszusammenfassung
|
||||
[Fließtext, maximal zwei Absätze. Die wichtigsten Themen, Stimmung, roter Faden des Gesprächs. Kein Aufzählungs-Stil — echte Prosa.]
|
||||
|
||||
---
|
||||
|
||||
## Entscheidungen & Fakten
|
||||
|
||||
**Getroffene Entscheidungen:**
|
||||
- [Entscheidung, ggf. mit wer entschieden hat]
|
||||
|
||||
**Genannte Fakten & Rahmenbedingungen:**
|
||||
- [Budget, Timeline, Teamgröße, technische Vorgaben, Vertragsinfos etc.]
|
||||
|
||||
---
|
||||
|
||||
## ToDos & Nächste Schritte
|
||||
- [ ] [Aufgabe] — [Person / Unternehmen] — [Datum falls genannt]
|
||||
- [ ] [Aufgabe] — [Person / Unternehmen] — [Datum falls genannt]
|
||||
|
||||
---
|
||||
|
||||
## Nebeninformationen *(intern, nicht zur Weitergabe)*
|
||||
- [Interessante Kontextinfos, persönliche Details, Brancheninfos, die zufällig erwähnt wurden — nicht projektbezogen, aber möglicherweise nützlich]
|
||||
|
||||
|
||||
---
|
||||
|
||||
Verarbeitungshinweise
|
||||
|
||||
1. Transkript-Input: Kann roher Text, korrigiertes Transkript oder Stichpunkte sein.
|
||||
2. Sprache: Immer auf Deutsch ausgeben, auch wenn das Transkript teilweise Englisch enthält.
|
||||
3. Block 2: Kein Marketing-Sprech, kein „stell dir vor" — sachlich, direkt, wie Tim es formulieren würde.
|
||||
4. Block 4: Format [ ] Aufgabe — Person/Unternehmen — Datum konsequent einhalten. Wenn keine Person zuordenbar → [ ] Aufgabe — unklar, klären
|
||||
5. Block 5: Nur wirklich interessante Nebenfakten, keine Füller. Lieber leer lassen als auffüllen.
|
||||
6. Datei speichern: Wenn möglich, als .md-Datei unter einem sinnvollen Pfad ablegen (z.B. Obsidian-Vault oder lokaler Projektordner). Tim entscheidet ob er sie weiterleitet.
|
||||
|
||||
---
|
||||
|
||||
Typischer Workflow
|
||||
|
||||
1. Tim gibt Transkript-Text (oder Datei) + ggf. Kontextinfos
|
||||
2. Fehlende Pflichtinfos abfragen
|
||||
3. 5-Block-Template befüllen
|
||||
4. Als Markdown-Datei ausgeben (MEDIA-Link wenn lokal gespeichert)
|
||||
5. Tim kann Block 5 für sich behalten und den Rest weitergeben
|
||||
Reference in New Issue
Block a user