A long scan leaves hundreds of recordings, each with a JSON sidecar of measurements and, where a recogniser heard speech, a transcript. Reading that meant opening files one at a time and guessing which were worth playing. saunterbrowse is a second executable in the same package. Arrow keys move through the recordings; the transcript of whichever is highlighted fills the top of the screen, because that is the part anyone actually wants to read. Enter plays it, handing the file to whichever player is installed -- the recordings are ordinary WAVs, every desktop already has something that plays them, and a browser that cannot start would be worse than one that cannot play. t opens the whole transcript full screen when it is longer than the panel, and says so rather than cutting the end off silently. / filters on the frequency, the name, the identification, or anything that was said, which is the point of it: "was the repeater mentioned" is a question about content. Sidecars are read only for the rows on screen, so a directory of ten thousand recordings opens instantly. Where there is no transcript the panel says which of the reasons applies -- Morse (decoded, and shown), data, a bare carrier, or speech never offered to a recogniser -- because those want different things done about them. It only ever reads. Two things were only found by driving it through a real terminal. sys.stdin.read(1) goes through a buffered text wrapper, which in cbreak mode waits for more bytes than one keypress provides: the program drew its first frame and then hung, while tests against a stand-in stream object passed. It reads the file descriptor now, and the tests drive a pty. And stopping playback signalled only the direct child, so a player that is a wrapper script kept the sound going with nothing on screen to stop it; the whole process group is signalled instead, which is what start_new_session was there for. man saunterbrowse ships beside man bandsaunter, and the two point at each other. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016PsWPTweCT6pwxKngvVxcg
127 lines
4.8 KiB
Bash
Executable file
127 lines
4.8 KiB
Bash
Executable file
#!/bin/sh
|
|
# Build a .deb that pulls in every dependency through apt.
|
|
#
|
|
# Deliberately plain dpkg-deb rather than debhelper: everything this package
|
|
# needs is already in Debian, the payload is pure Python with no compiled
|
|
# parts, and this way the build needs nothing installed beyond dpkg itself.
|
|
set -eu
|
|
|
|
here=$(cd "$(dirname "$0")/.." && pwd)
|
|
version=$(cd "$here" && python3 -c 'import bandsaunter; print(bandsaunter.debian_version())')
|
|
revision=${DEB_REVISION:-1}
|
|
arch=all
|
|
stage=$(mktemp -d)
|
|
trap 'rm -rf "$stage"' EXIT
|
|
|
|
pkgdir="$stage/bandsaunter_${version}-${revision}_${arch}"
|
|
site="$pkgdir/usr/lib/python3/dist-packages"
|
|
mkdir -p "$site/bandsaunter" "$pkgdir/usr/bin" "$pkgdir/DEBIAN" \
|
|
"$pkgdir/usr/share/doc/bandsaunter"
|
|
|
|
cp "$here"/bandsaunter/*.py "$site/bandsaunter/"
|
|
cp "$here/README.md" "$pkgdir/usr/share/doc/bandsaunter/"
|
|
gzip -9n "$pkgdir/usr/share/doc/bandsaunter/README.md"
|
|
|
|
# Regenerated from the settings table rather than copied, so the manual
|
|
# cannot describe a version of the settings that no longer exists.
|
|
mkdir -p "$pkgdir/usr/share/man/man1"
|
|
python3 "$here/packaging/make-man.py" "$pkgdir/usr/share/man/man1/bandsaunter.1" \
|
|
>/dev/null
|
|
python3 "$here/packaging/make-browse-man.py" \
|
|
"$pkgdir/usr/share/man/man1/saunterbrowse.1" >/dev/null
|
|
gzip -9n "$pkgdir/usr/share/man/man1/bandsaunter.1"
|
|
gzip -9n "$pkgdir/usr/share/man/man1/saunterbrowse.1"
|
|
|
|
cat > "$pkgdir/usr/bin/bandsaunter" <<'EOF'
|
|
#!/usr/bin/python3
|
|
import sys
|
|
from bandsaunter.cli import main
|
|
sys.exit(main())
|
|
EOF
|
|
chmod 755 "$pkgdir/usr/bin/bandsaunter"
|
|
|
|
cat > "$pkgdir/usr/bin/saunterbrowse" <<'EOF'
|
|
#!/usr/bin/python3
|
|
import sys
|
|
from bandsaunter.browse import main
|
|
sys.exit(main())
|
|
EOF
|
|
chmod 755 "$pkgdir/usr/bin/saunterbrowse"
|
|
|
|
# Every one of these is in Debian, so apt resolves the lot. No speech
|
|
# recogniser is packaged for Debian, so that part ships as its own package
|
|
# (built by build-repo.sh) and is recommended rather than depended on: apt
|
|
# pulls it in from the repository, and a bare "dpkg -i" of this file still
|
|
# installs, without transcription.
|
|
cat > "$pkgdir/DEBIAN/control" <<EOF
|
|
Package: bandsaunter
|
|
Version: ${version}-${revision}
|
|
Section: hamradio
|
|
Priority: optional
|
|
Architecture: ${arch}
|
|
Depends: python3 (>= 3.10), python3-numpy, python3-scipy, python3-rich,
|
|
python3-yaml, librtlsdr0
|
|
Recommends: bandsaunter-transcribe, espeak-ng
|
|
Suggests: rtl-sdr
|
|
Maintainer: bandsaunter
|
|
Installed-Size: $(du -ks "$pkgdir" | cut -f1)
|
|
Description: signal scanner and recorder for RTL-SDR receivers
|
|
Sweeps any set of frequency ranges, or presets from a built-in US band plan,
|
|
records what it finds and works out what kind of signal it was.
|
|
.
|
|
Captures are kept only if they carry voice, decodable CW or an identified
|
|
digital keying scheme, so static and interference are discarded rather than
|
|
filling the disk. CW is decoded to text, and speech can be transcribed when
|
|
a recogniser is installed.
|
|
EOF
|
|
|
|
cat > "$pkgdir/DEBIAN/postinst" <<'EOF'
|
|
#!/bin/sh
|
|
set -e
|
|
if [ "$1" = configure ]; then
|
|
# The DVB-T driver claims the dongle on sight and has to be kept off it.
|
|
if [ ! -e /etc/modprobe.d/blacklist-rtlsdr.conf ]; then
|
|
echo 'blacklist dvb_usb_rtl28xxu' > /etc/modprobe.d/blacklist-rtlsdr.conf
|
|
echo 'bandsaunter: blacklisted dvb_usb_rtl28xxu; unplug and replug the'
|
|
echo ' receiver, or run: sudo rmmod dvb_usb_rtl28xxu'
|
|
fi
|
|
|
|
# Said once, at install time, only when there is genuinely nothing to
|
|
# transcribe with -- otherwise the recogniser package has already
|
|
# arrived and there is nothing to explain.
|
|
if ! python3 -c 'import importlib.util as u, sys
|
|
sys.path.append("/usr/lib/bandsaunter/vendor")
|
|
try:
|
|
found = any(u.find_spec(m) for m in
|
|
("faster_whisper", "whisper", "vosk", "pocketsphinx"))
|
|
except Exception:
|
|
found = True # something is there, however unhappy; say nothing
|
|
sys.exit(0 if found else 1)' \
|
|
2>/dev/null; then
|
|
echo 'bandsaunter: speech transcription is unavailable -- no recogniser'
|
|
echo ' is installed. Scanning, recording and CW decoding all'
|
|
echo ' work without one. To add transcription, either:'
|
|
echo ' apt install bandsaunter-transcribe'
|
|
echo ' (from the repository this package came from), or'
|
|
echo ' pip install --break-system-packages faster-whisper'
|
|
echo ' Then: bandsaunter transcribe --engines'
|
|
fi
|
|
fi
|
|
exit 0
|
|
EOF
|
|
chmod 755 "$pkgdir/DEBIAN/postinst"
|
|
|
|
cat > "$pkgdir/DEBIAN/prerm" <<'EOF'
|
|
#!/bin/sh
|
|
set -e
|
|
if [ "$1" = remove ] || [ "$1" = purge ]; then
|
|
rm -f /etc/modprobe.d/blacklist-rtlsdr.conf
|
|
fi
|
|
exit 0
|
|
EOF
|
|
chmod 755 "$pkgdir/DEBIAN/prerm"
|
|
|
|
out=${1:-$here/dist}
|
|
mkdir -p "$out"
|
|
fakeroot dpkg-deb --build "$pkgdir" "$out" >/dev/null
|
|
echo "$out/bandsaunter_${version}-${revision}_${arch}.deb"
|