All patches and comments are welcome. Please squash your changes to logical
commits before using git-format-patch and git-send-email to
patches@git.madduck.net.
If you'd read over the Git project's submission guidelines and adhered to them,
I'd be especially grateful.
3 # pulserecorder — record a source via Pulse
6 # pulserecorder -i <index> [-o filename.ogg] [-f]
7 # pulserecorder -l [-o filename.ogg] [-f]
8 # pulserecorder [-o filename.ogg] [-f]
10 # The first form records sound from the sink-input with index <index> to the
11 # filename specified, or one generated using a UUID.
13 # The second form records sound from the sink-input with the highest index,
14 # i.e. the latest one to start playing.
16 # The third form waits for a new sink-input to appear, and will record that.
18 # Copyright © 2020 martin f. krafft <madduck@madduck.net>
19 # Released under the teams of the Artistic Licence 2.0
23 if [ -z "${TMPDIR:-}" ]; then
26 for i in $LOGNAME volatile; do
27 if [ -d "${TMPDIR}/$i" ]; then
33 TMPDIR=$(mktemp -dp "$TMPDIR" parec.XXXXXXXXXX)
35 cleanup_commands="rm -r $TMPDIR"
38 eval $cleanup_commands 2>/dev/null
39 trap - 1 2 3 4 5 6 7 8 10 11 12 13 14 15
41 trap cleanup 1 2 3 4 5 6 7 8 10 11 12 13 14 15
43 cleanup_commands="$@${cleanup_commands:+; $cleanup_commands}"
46 state= outfile= index= clobber=0
50 (i/*) index="$arg"; state=;;
55 (o/*) outfile="$arg"; state=;;
62 ( pactl subscribe 2>/dev/null & echo $! )
66 type="${1:-*}" event="${2:-*}" id="${3:-*}"
69 #echo >&2 pa_subscribe started with PID $pid
70 while read lead xevent on xtype xid; do
72 #echo >&2 "pa_subscribe: $xtype/$xevent/$xid ($type/$event/$id)"
73 case "$xtype/$xevent/$xid" in
74 ($type/"'$event'"/$id) echo "$xtype $xevent $xid"; break;;
82 pa_wait_for_event sink-input new | {
91 index=$(pactl list short sink-inputs | sed -rne '$s,([[:digit:]]+)[[:space:]].*,\1,p')
95 echo >&2 "E: non-numeric index: $index"
102 echo >&2 "Listening for pulseaudio event… "
103 index=$(pa_get_next_index)
108 if [ -z "$index" ]; then
110 echo >&2 E: no index specified or discernable.
115 hascmd() { command -v "$@" >/dev/null;}
116 if hascmd uuid; then uuid
117 elif hascmd uuidgen; then uuidgen
118 elif hascmd python3; then
119 python3 -c 'import uuid; print(uuid.uuid1())'
120 elif hascmd python; then
121 python -c 'import uuid; print(uuid.uuid1())'
123 dd if=/dev/urandom bs=16 count=1 status=none | base64
129 [ -n "$outfile" ] || outfile="${uuid}.ogg"
131 if [ -f "$outfile" ] && [ $clobber -eq 0 ]; then
132 echo >&2 "E: file exists, and -f not given: $outfile"
136 devname="record-to-file-${uuid}"
138 echo >&2 "Recording source $index to $outfile …"
141 local id mod sink desc
142 mod="$1" sink="$2" desc="$3"; shift 3
143 id=$(pactl load-module "$mod" sink_name="$sink" "$@")
144 cleanup_hook pactl unload-module $id
145 pacmd update-sink-proplist "$sink" device.description="$desc"
148 move_source_to_sink() {
150 c=$(pactl list short sink-inputs | sed -rne "s,^${1}[[:space:]]+([[:digit:]]+).+,\1,p")
151 cleanup_hook pactl move-sink-input $1 $c
152 pactl move-sink-input $1 $2
156 # This would be great, but it does not work. For instance, trying this on
157 # audible meant that audible would play at maximum speed (3h played in 3
158 # minutes), but the result would be full of skips, making me think that
159 # something in the local pipeline can't handle the throughput. Rate-limiting
160 # might work. Have not had the time to investigate further.
162 load_module module-pipe-sink "$devname" "$devname" file="$TMPDIR/outfifo"
163 move_source_to_sink $index "$devname"
164 oggenc --raw -q5 -o "$outfile" "$TMPDIR/outfifo" || :
165 #pv -pterb "$TMPDIR/outfifo" > $TMPDIR/outfile.wav || :
168 # More traditional approach, which just takes 1:1 time.
170 load_module module-null-sink "$devname" "$devname"
171 move_source_to_sink $index "$devname"
172 parec --format=s16le -d "${devname}.monitor" 2>/dev/null \
173 | oggenc --raw -q5 -o "$outfile" - &
176 pa_wait_for_event sink-input remove $index >/dev/null