Skip to content

Instantly share code, notes, and snippets.

@douxxtech
Last active November 16, 2025 22:51
Show Gist options
  • Select an option

  • Save douxxtech/73e2999588405910ba85dba2378659d3 to your computer and use it in GitHub Desktop.

Select an option

Save douxxtech/73e2999588405910ba85dba2378659d3 to your computer and use it in GitHub Desktop.
A simple python script that broadcasts via fm radio SSTV images taken from a RTSP stream.
piwave==2.1.3
pysstv==0.5.7
pillow==9.5.0
ffmpeg-python==0.2.0
dlogger==1.0.2
# This is a simple python script that broadcasts via fm radio SSTV images taken from a RTSP stream.
# This script uses piwave, check piwave.xyz for installation instructions
# This script uses ffmpeg, check ffmpeg.org for installation instructions
# blog post : https://douxx.blog/?p=5-a-video-stream-to-sstv-radio-broadcast
import ffmpeg
from PIL import Image
from pysstv import color, grayscale
from argparse import ArgumentParser
from piwave import PiWave
from dlogger import DLogger
SSTV_MODULES = [color, grayscale]
Log = DLogger(
icons={
"success": "OK",
"error": "ERR",
"warning": "WARN",
"ffmpeg": "FFMPEG",
"pysstv": "PySSTV"
},
styles={
"success": "green",
"error": "red",
"warning": "yellow",
"ffmpeg": "magenta",
"pysstv": "bright_red"
}
)
def main():
module_map = build_module_map()
parser = ArgumentParser(
description='Takes an rtsp stream, and stream it frame-by-frame through radio')
parser.add_argument('rtsp_host',
help='Server hostname or IP address')
parser.add_argument('--mode', dest='mode', default='Robot8BW', choices=module_map,
help='image mode (default: Robot8BW)')
parser.add_argument('--freq', type=float, default=88.5,
help='radio frequency to broadcast to (default: 88.5)')
args = parser.parse_args()
host = args.rtsp_host
mode = module_map[args.mode]
freq = args.freq
pw = PiWave(freq, "RTSPSSTV")
try:
while not pw.get_status()["is_playing"]:
Log.ffmpeg("Taking a snapshot of ffmpeg stream")
snap(host)
Log.success("Snapshot saved as snapshot.png")
Log.pysstv(f"Converting to {args.mode}")
convert(mode)
Log.success("SSTV saved as snapshot.wav")
Log.warning("Broadcasting..")
pw.play("snapshot.wav", blocking=True)
except KeyboardInterrupt:
Log.warning("^C recieved, exiting..")
except Exception as e:
Log.error(f"Got an exception: {e}")
finally:
pw.cleanup()
# 1. take a snapshot of the rtsp stream with ffmpeg
def snap(host):
(
ffmpeg
.input(f'rtsp://{host}')
.output('snapshot.png', vframes=1)
.run(overwrite_output=True, quiet=True)
)
# 2. convert to sstv
def convert(mode):
image = Image.open("snapshot.png")
if any(i != m for i, m in zip(image.size, (mode.WIDTH, mode.HEIGHT))):
w = mode.WIDTH
h = mode.HEIGHT
resample = getattr(Image, "LANCZOS")
image = image.resize((w, h), resample)
s = mode(image, 48000, 16)
s.write_wav("snapshot.wav")
def build_module_map():
try:
from collections import OrderedDict
module_map = OrderedDict()
except ImportError:
module_map = {}
for module in SSTV_MODULES:
for mode in module.MODES:
module_map[mode.__name__] = mode
return module_map
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment