commit eda2c5c49d688c08754d691b177397bdff55e54b Author: Markus Graf Date: Tue Mar 24 10:47:08 2026 +0100 Initial commit: call recorder with MP3 output and --show-inputs monitor diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..181952e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +recordings/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..32ea6cc --- /dev/null +++ b/LICENSE @@ -0,0 +1,49 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + For the precise terms and conditions for copying, distribution and +modification, follow below. Terms and Conditions for copying, +distribution and modification are not listed here, but the full text +is available at https://www.gnu.org/licenses/gpl-3.0.txt + + The GNU General Public License is published by the Free Software +Foundation. You can apply it to your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new new free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or preventing you from asking you to surrender the rights. +Therefore, you have certain responsibilities if you distribute copies +of the software, or if you modify it: responsibilities to respect the +freedom of others. + + For the full license text, see: https://www.gnu.org/licenses/gpl-3.0.txt + + Copyright (C) 2026 Markus Graf + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . diff --git a/README.md b/README.md new file mode 100644 index 0000000..8f0df16 --- /dev/null +++ b/README.md @@ -0,0 +1,57 @@ +# Call Recorder + +Records microphone and system audio (everything playing on your speakers/headphones) into a single MP3 file — ideal for transcription with Whisper, Gemini, or similar services. + +## Requirements + +- Linux with PipeWire / PulseAudio +- `ffmpeg` + +## Usage + +```bash +# Start recording before your call +./record.sh + +# Join your call normally, then press Ctrl+C when done. +# Recording is saved to recordings/call_YYYYMMDD_HHMMSS.mp3 +``` + +### Check which inputs are active + +```bash +./record.sh --show-inputs +``` + +Displays a live bar chart of both inputs in the terminal: + +``` + Mikrofon : [████████████░░░░░░░░░░░░░░░░░░] -18 dB + System Audio : [░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░] -60 dB +``` + +Colour coding: green (normal) → yellow (−12 dB) → red (−3 dB). Press Ctrl+C to exit. + +### List available audio sources + +```bash +./list-sources.sh +``` + +## How it works + +`record.sh` detects the default PipeWire/PulseAudio sink, then runs `ffmpeg` to simultaneously capture: + +- **Microphone** — PulseAudio default source +- **System audio** — monitor of the default sink (everything you hear) + +Both streams are mixed and encoded as MP3 at 128 kbps (~11 MB / 15 min). + +## Troubleshooting + +**Missing system audio or microphone?** +Run `./list-sources.sh` to inspect available devices. The script auto-detects the default sink at startup — if you switched audio output after launching (e.g. plugged in headphones), restart the script. + +## License + +This project is licensed under the [GNU General Public License v3.0](LICENSE). diff --git a/list-sources.sh b/list-sources.sh new file mode 100755 index 0000000..5cd1aa4 --- /dev/null +++ b/list-sources.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +echo "=== Default devices ===" +pw-metadata 0 2>/dev/null | awk -F"'" '/default\.audio\.(sink|source)/ { print $2, $4 }' + +echo "" +echo "=== All audio nodes (sinks, sources, monitors) ===" +pw-cli ls Node 2>/dev/null \ + | awk '/id [0-9]/ { id=$2 } /node\.name/ { print id, $0 }' \ + | grep -i "alsa\|bluez\|monitor" || true diff --git a/record.sh b/record.sh new file mode 100755 index 0000000..a7fdf5d --- /dev/null +++ b/record.sh @@ -0,0 +1,135 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +RECORDINGS_DIR="$SCRIPT_DIR/recordings" +mkdir -p "$RECORDINGS_DIR" + +OUTPUT="$RECORDINGS_DIR/call_$(date +%Y%m%d_%H%M%S).mp3" + +# Argument parsing +SHOW_INPUTS=false +for arg in "$@"; do + [[ "$arg" == "--show-inputs" ]] && SHOW_INPUTS=true +done + +# Detect the default audio sink via PipeWire metadata +DEFAULT_SINK=$(pw-metadata 0 2>/dev/null \ + | awk -F"'" '/default\.audio\.sink/ { print $4 }' \ + | grep -o '"name":"[^"]*"' \ + | cut -d'"' -f4) + +if [[ -z "$DEFAULT_SINK" ]]; then + echo "ERROR: Could not detect default audio sink." >&2 + echo "Run ./list-sources.sh to see available devices." >&2 + exit 1 +fi + +MONITOR_SOURCE="${DEFAULT_SINK}.monitor" + +# --- show-inputs mode --- +if $SHOW_INPUTS; then + BAR_WIDTH=30 + DB_MIN=-60 + DB_MAX=0 + + render_bar() { + local label="$1" + local db="$2" + local bar="" + local filled=0 + + if [[ -n "$db" ]]; then + # Clamp to range + local clamped + clamped=$(awk -v v="$db" -v lo="$DB_MIN" -v hi="$DB_MAX" \ + 'BEGIN { if (v < lo) v=lo; if (v > hi) v=hi; print v }') + filled=$(awk -v v="$clamped" -v lo="$DB_MIN" -v hi="$DB_MAX" -v w="$BAR_WIDTH" \ + 'BEGIN { printf "%d", (v - lo) / (hi - lo) * w }') + fi + + local empty=$(( BAR_WIDTH - filled )) + + # Color based on level + local color="\033[32m" # green + if [[ -n "$db" ]]; then + color=$(awk -v v="$db" 'BEGIN { + if (v >= -3) { print "\033[31m" } + else if (v >= -12) { print "\033[33m" } + else { print "\033[32m" } + }') + fi + + bar="${color}" + local i + for (( i=0; i"$TMP_MIC" & + PID_MIC=$! + + ffmpeg -loglevel info -f pulse -i "$MONITOR_SOURCE" \ + -af ebur128 -f null - 2>"$TMP_SYS" & + PID_SYS=$! + + cleanup() { + kill "$PID_MIC" "$PID_SYS" 2>/dev/null || true + rm -f "$TMP_MIC" "$TMP_SYS" + tput cnorm + echo "" + exit 0 + } + trap cleanup INT TERM + + tput civis # hide cursor + + # Reserve two lines + printf "\n\n" + + while true; do + MIC_DB=$(grep -oP 'M:\s*\K[-0-9.]+' "$TMP_MIC" 2>/dev/null | tail -1 || true) + SYS_DB=$(grep -oP 'M:\s*\K[-0-9.]+' "$TMP_SYS" 2>/dev/null | tail -1 || true) + + printf "\033[2A" # move up 2 lines + render_bar "Mikrofon" "$MIC_DB" + render_bar "System Audio" "$SYS_DB" + + sleep 0.1 + done +fi + +# --- Recording mode --- +echo "==========================================" +echo " Call Recorder" +echo "==========================================" +echo " Microphone : default" +echo " System audio: $MONITOR_SOURCE" +echo " Output file : $OUTPUT" +echo "------------------------------------------" +echo " Press Ctrl+C to stop recording." +echo "==========================================" +echo "" + +ffmpeg -loglevel warning \ + -f pulse -i default \ + -f pulse -i "$MONITOR_SOURCE" \ + -filter_complex "amix=inputs=2:duration=longest:normalize=0" \ + -c:a libmp3lame -b:a 128k \ + "$OUTPUT" + +echo "" +echo "Recording saved to: $OUTPUT"