|
| 1 | +#!/usr/bin/env bash |
| 2 | +set -euo pipefail |
| 3 | + |
| 4 | +list_sources() { |
| 5 | + if [[ "$OSTYPE" == "darwin"* ]]; then |
| 6 | + # macOS: list audio devices via ffmpeg avfoundation |
| 7 | + echo "system: System Audio (via ScreenCaptureKit)" |
| 8 | + ffmpeg -f avfoundation -list_devices true -i "" 2>&1 | \ |
| 9 | + sed -n '/AVFoundation audio devices:/,/^[^[]/p' | \ |
| 10 | + grep -E '^\[AVFoundation.*\[[0-9]+\]' | \ |
| 11 | + sed 's/.*\[\([0-9]*\)\] \(.*\)/\1: \2/' |
| 12 | + else |
| 13 | + # Linux: list PulseAudio/PipeWire sources including monitors (for system audio) |
| 14 | + # Sources ending in .monitor capture the output of that sink (system audio) |
| 15 | + pactl list sources short | awk '{print $2}' | while read -r source; do |
| 16 | + if [[ "$source" == *.monitor ]]; then |
| 17 | + echo "$source (system audio)" |
| 18 | + else |
| 19 | + echo "$source" |
| 20 | + fi |
| 21 | + done |
| 22 | + fi |
| 23 | +} |
| 24 | + |
| 25 | +get_source_name() { |
| 26 | + # Strip the description suffix for actual recording |
| 27 | + local name="$1" |
| 28 | + echo "${name% (system audio)}" |
| 29 | +} |
| 30 | + |
| 31 | +record_source() { |
| 32 | + local source="$1" |
| 33 | + |
| 34 | + if [[ "$OSTYPE" == "darwin"* ]]; then |
| 35 | + if [[ "$source" == "system" ]]; then |
| 36 | + # Use system-audio-dump for system audio (ScreenCaptureKit) |
| 37 | + # Output is raw PCM 24kHz 16-bit stereo, convert to WAV for compatibility |
| 38 | + system-audio-dump | ffmpeg -f s16le -ar 24000 -ac 2 -i - -f wav -acodec pcm_s16le - 2>/dev/null |
| 39 | + else |
| 40 | + # Use avfoundation for mic input, source is the device index |
| 41 | + ffmpeg -f avfoundation -i ":${source}" -f wav -acodec pcm_s16le - 2>/dev/null |
| 42 | + fi |
| 43 | + else |
| 44 | + # Linux: use pulse |
| 45 | + ffmpeg -f pulse -i "$source" -f wav -acodec pcm_s16le - 2>/dev/null |
| 46 | + fi |
| 47 | +} |
| 48 | + |
| 49 | +# Show help |
| 50 | +if [[ "${1:-}" == "-h" ]] || [[ "${1:-}" == "--help" ]]; then |
| 51 | + echo "Usage: record [SOURCE]" |
| 52 | + echo "" |
| 53 | + echo "Record audio from a source and output WAV to stdout." |
| 54 | + echo "If no source is provided, prompts interactively with gum." |
| 55 | + echo "" |
| 56 | + echo "Options:" |
| 57 | + echo " -l, --list List available audio sources" |
| 58 | + echo " -h, --help Show this help" |
| 59 | + echo "" |
| 60 | + echo "System audio:" |
| 61 | + echo " Linux: Select a .monitor source (captures sink output)" |
| 62 | + echo " macOS: Use 'system' source (requires Screen Recording permission)" |
| 63 | + echo "" |
| 64 | + echo "Examples:" |
| 65 | + echo " record | mpv -" |
| 66 | + echo " record system | mpv - # macOS system audio" |
| 67 | + echo " record 0 | mpv - # macOS device index" |
| 68 | + echo " record alsa_output.pci-xxx.monitor | mpv - # Linux system audio" |
| 69 | + exit 0 |
| 70 | +fi |
| 71 | + |
| 72 | +# List sources |
| 73 | +if [[ "${1:-}" == "-l" ]] || [[ "${1:-}" == "--list" ]]; then |
| 74 | + list_sources |
| 75 | + exit 0 |
| 76 | +fi |
| 77 | + |
| 78 | +# Get source from argument or prompt |
| 79 | +if [[ -n "${1:-}" ]]; then |
| 80 | + SOURCE="$1" |
| 81 | +else |
| 82 | + # Use gum to select a source |
| 83 | + if [[ "$OSTYPE" == "darwin"* ]]; then |
| 84 | + SOURCE=$(list_sources | gum choose --header "Select audio source:" | cut -d: -f1) |
| 85 | + else |
| 86 | + SELECTION=$(list_sources | gum choose --header "Select audio source:") |
| 87 | + SOURCE=$(get_source_name "$SELECTION") |
| 88 | + fi |
| 89 | +fi |
| 90 | + |
| 91 | +if [[ -z "$SOURCE" ]]; then |
| 92 | + echo "Error: No source selected" >&2 |
| 93 | + exit 1 |
| 94 | +fi |
| 95 | + |
| 96 | +echo "Recording from: $SOURCE (Ctrl+C to stop)" >&2 |
| 97 | +record_source "$SOURCE" |
0 commit comments