Initial commit: call recorder with MP3 output and --show-inputs monitor

This commit is contained in:
2026-03-24 10:47:08 +01:00
commit eda2c5c49d
5 changed files with 251 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
recordings/
+49
View File
@@ -0,0 +1,49 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
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 <https://www.gnu.org/licenses/>.
+57
View File
@@ -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).
+9
View File
@@ -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
Executable
+135
View File
@@ -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<filled; i++ )); do bar+="█"; done
for (( i=0; i<empty; i++ )); do bar+="░"; done
bar+="\033[0m"
local db_str="${db:- ---}"
printf " %-13s: [%b] %6s dB\n" "$label" "$bar" "$db_str"
}
echo "=========================================="
echo " Input Monitor (Ctrl+C to exit)"
echo "=========================================="
echo ""
TMP_MIC=$(mktemp)
TMP_SYS=$(mktemp)
ffmpeg -loglevel info -f pulse -i default \
-af ebur128 -f null - 2>"$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"