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
 
  26   eval $cleanup_commands 2>/dev/null
 
  27   trap - 1 2 3 4 5 6 7 8 10 11 12 13 14 15
 
  29 trap cleanup 1 2 3 4 5 6 7 8 10 11 12 13 14 15
 
  31   cleanup_commands="$@${cleanup_commands:+; $cleanup_commands}"
 
  34 if [ -z "${TMPDIR:-}" ]; then
 
  37 for i in $LOGNAME volatile; do
 
  38   if [ -d "${TMPDIR}/$i" ]; then
 
  44 TMPDIR=$(mktemp -dp "$TMPDIR" parec.XXXXXXXXXX)
 
  45 cleanup_hook rm -r $TMPDIR
 
  47 if command -v systemd-inhibit >/dev/null; then
 
  48   systemd-inhibit --who=pulserecorder --why=recording sleep 99d 2>/dev/null &
 
  52 state= outfile= index= clobber=0
 
  56     (i/*) index="$arg"; state=;;
 
  61     (o/*) outfile="$arg"; state=;;
 
  68   ( pactl subscribe 2>/dev/null & echo $! )
 
  72   type="${1:-*}" event="${2:-*}" id="${3:-*}"
 
  75     #echo >&2 pa_subscribe started with PID $pid, waiting for $type/$event/$id
 
  76     while read lead xevent on xtype xid; do
 
  78       #echo >&2 "pa_subscribe: $xtype/$xevent/$xid ($type/$event/$id)"
 
  79       case "$xtype/$xevent/$xid" in
 
  80         ($type/"'$event'"/$id) echo "$xtype $xevent $xid"; break;;
 
  88   pa_wait_for_event sink-input new | {
 
  97     index=$(pactl list short sink-inputs | sed -rne '$s,([[:digit:]]+)[[:space:]].*,\1,p')
 
 101     echo >&2 "E: non-numeric index: $index"
 
 108     echo >&2 "Listening for pulseaudio event… "
 
 109     index=$(pa_get_next_index)
 
 114 if [ -z "$index" ]; then
 
 116   echo >&2 E: no index specified or discernable.
 
 121   hascmd() { command -v "$@" >/dev/null;}
 
 122   if   hascmd uuid; then uuid
 
 123   elif hascmd uuidgen; then uuidgen
 
 124   elif hascmd python3; then
 
 125     python3 -c 'import uuid; print(uuid.uuid1())'
 
 126   elif hascmd python; then
 
 127     python -c 'import uuid; print(uuid.uuid1())'
 
 129     dd if=/dev/urandom bs=16 count=1 status=none | base64
 
 135 [ -n "$outfile" ] || outfile="${uuid}.ogg"
 
 137 if [ -f "$outfile" ] && [ $clobber -eq 0 ]; then
 
 138   echo >&2 "E: file exists, and -f not given: $outfile"
 
 142 devname="record-to-file-${uuid}"
 
 144 echo >&2 "Recording source $index to $outfile …"
 
 147   local id mod sink desc
 
 148   mod="$1" sink="$2" desc="$3"; shift 3
 
 149   id=$(pactl load-module "$mod" sink_name="$sink" "$@")
 
 150   cleanup_hook pactl unload-module $id
 
 151   pacmd update-sink-proplist "$sink" device.description="$desc"
 
 154 move_source_to_sink() {
 
 156   c=$(pactl list short sink-inputs | sed -rne "s,^${1}[[:space:]]+([[:digit:]]+).+,\1,p")
 
 157   cleanup_hook pactl move-sink-input $1 $c
 
 158   pactl move-sink-input $1 $2
 
 162   # This would be great, but it does not work. For instance, trying this on
 
 163   # audible meant that audible would play at maximum speed (3h played in 3
 
 164   # minutes), but the result would be full of skips, making me think that
 
 165   # something in the local pipeline can't handle the throughput. Rate-limiting
 
 166   # might work. Have not had the time to investigate further.
 
 168   load_module module-pipe-sink "$devname" "$devname" file="$TMPDIR/outfifo"
 
 169   move_source_to_sink $index "$devname"
 
 170   oggenc --raw -q5 -o "$outfile" "$TMPDIR/outfifo" || :
 
 171   #pv -pterb "$TMPDIR/outfifo" > $TMPDIR/outfile.wav || :
 
 174   # More traditional approach, which just takes 1:1 time.
 
 176   load_module module-null-sink "$devname" "$devname"
 
 177   move_source_to_sink $index "$devname"
 
 178   parec --format=s16le -d "${devname}.monitor" 2>/dev/null \
 
 179     | oggenc --raw -q5 -o "$outfile" - &
 
 182   pa_wait_for_event sink-input remove $index >/dev/null