Initial commit: call recorder with MP3 output and --show-inputs monitor
This commit is contained in:
@@ -0,0 +1 @@
|
||||
recordings/
|
||||
@@ -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/>.
|
||||
@@ -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).
|
||||
Executable
+9
@@ -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
|
||||
@@ -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"
|
||||
Reference in New Issue
Block a user