Last active
November 16, 2025 22:51
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| piwave==2.1.3 | |
| pysstv==0.5.7 | |
| pillow==9.5.0 | |
| ffmpeg-python==0.2.0 | |
| dlogger==1.0.2 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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