#! /usr/bin/env python3 import routes import os import sys import requests import SoapySDR as soapy from radio import Radio from tuuube import Tuuube from fileradio import FileRadio from flask import Flask, jsonify from flasgger import Swagger app = Flask(__name__) swag = Swagger(app) radios = {} @app.route(routes.endpoint_routes["b"]) def report(): """Get streams report from the RTSP relay. --- responses: 200: description: JSON with streams report. 400: description: JSON with error message. """ try: r = requests.get("http://localhost:9997/v1/paths/list") j = r.json() for item in j["items"]: del j["items"][item]["conf"] del j["items"][item]["confName"] return jsonify(j) except Exception as e: return _error_message_json(e.message), 500 @app.route(routes.endpoint_routes["a"]) def radio_report(): """List radio devices available to the system. --- responses: 200: description: A list of radio devices. """ devices = [dict(device) for device in soapy.Device.enumerate()] return jsonify(devices) def connect(radio): """Connect to a radio device, by driver name or serial number. --- parameters: - name: radio description: Radio device driver name, or serial number. in: path type: string required: true responses: 200: description: JSON with message successfully connected to a radio. 400: description: JSON with error message No radio device by that name is available. 500: description: JSON with error message failed to connect to radio. """ if radio in radios: return _error_message_json("Radio device already connected"), 400 devices = [dict(device) for device in soapy.Device.enumerate()] for device in devices: if radio in device.values(): try: radios[radio] = Radio(radio, device) return jsonify("message", "successfully connected radio"), 200 except Exception as e: radios.pop(radio) return _error_message_json(e.message), 500 return "Radio device not found", 400 @app.route(routes.endpoint_routes["f"]) def disconnect(radio): """Disconnect from a radio device. --- parameters: - name: radio description: Radio device driver name, or serial number. in: path type: string required: true responses: 200: description: Successfully connected to a radio. 400: description: No radio device by that name is available. 500: description: An unknown error occurred. """ if radio in radios: radios.pop(radio) return jsonify("message", "succesfully disconnected radio"), 200 else: return _error_message_json("Radio not connected"), 400 # TODO: add post parameters... @app.route(routes.endpoint_routes["c"]) def configure(): """Tune the radio to a frequency. You must connect to the radio before attempting to configure it. The radio must be configured before streaming can be started. --- parameters: - name: radio description: Radio device driver name, or serial number. in: path type: string required: true - name: frequency description: Frequency (in Hz) to tune the radio to. Suffixing is also supported, ie 87.5M, 108.0M, etc. in: path type: string required: true responses: 200: description: JSON with radio configuration. 400: description: The specified radio is not connected. """ if radio in radios: return jsonify(radios[radio].configure(frequency)) else: return _error_message_json("Radio not connected"), 400 @app.route(routes.endpoint_routes["d"]) def start_stream(radio): """Start the radio stream. Once the stream has been started, connect to the stream at: rtsp://[host]:8554/radio/[radio] --- parameters: - name: radio description: Radio device driver name, or serial number. in: path type: string required: true responses: 200: description: JSON with message successful start of radio stream. 400: description: JSON with error message. """ if id is not None: _start_tuuube_stream(id) else: try: radios[radio].start_stream() return jsonify("message", "successfully started radio stream"), 200 except Exception as e: return _error_message_json(e.message), 400 @app.route(routes.endpoint_routes["e"]) def end_stream(radio): """Terminate the radio stream. --- parameters: - name: radio description: Radio device driver name, or serial number. in: path type: string required: true responses: 200: description: JSON with message successful termination of radio stream. 400: description: JSON with error message. """ if id is not None: _end_tuuube_stream(id) else: try: radios[radio].end_stream() return jsonify("message", "successfully ended radio stream"), 200 except Exception as e: return _error_message_json(e.message), 400 @app.route(routes.endpoint_routes["g"]) def radio_info(radio): """Get information about a radio. --- parameters: - name: radio description: Radio device driver name, or serial number. in: path type: string required: true responses: 200: description: JSON with radio information. 400: description: JSON with error message. """ try: return jsonify(radios[radio].get_info()) except Exception as e: return _error_message_json(e.message), 400 tubes = {} def _start_tuuube_stream(id): """Start streaming from a youtube source. Once the stream has been started, connect to the stream at: rtsp://[host]:8554/tuuube/[id] --- parameters: - name: id description: The youtube video ID. That is the part following the `watch?v=` in the URL. For example, `dQw4w9WgXcQ`.\n Other good options are - \n `BaW_jenozKc`, yt_dlp package test video.\n `b2je8uBlgFM`, stereo audio test.\n `LDU_Txk06tM`, crab rave, a commonly used audio fidelity test.\n `sPT_epMLkwQ`, Kilsyth CFA major factory fire dispatch radio traffic. in: path type: string required: true responses: 200: description: JSON with message successful start of youtube stream. 400: description: JSON with error message. """ if id not in tubes: tubes[id] = Tuuube(id) try: tubes[id].start_stream() return jsonify("message", "successfully started youtube stream"), 200 except Exception as e: return _error_message_json(e.message), 400 def _end_tuuube_stream(id): """Terminate the youtube stream. --- parameters: - name: id description: The youtube video ID. in: path type: string required: true responses: 200: description: JSON with message successful termination of youtube stream. 400: description: JSON with error message. """ try: tubes[id].end_stream() return jsonify("message", "succesfully ended youtube stream"), 200 except Exception as e: return _error_message_json(e.message), 400 """ Helper function to return a JSON error message parameters: error_message - the error message to return returns: a JSON object with the error message """ def _error_message_json(error_message: str): error_message = {"error_message": error_message} return jsonify(error_message) if __name__ == "__main__": import subprocess rtsp_relay = subprocess.Popen( ["./dependencies/mediamtx/mediamtx", "./mediamtx.yml"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) for path, _, files in os.walk("./data/sampleaudio"): for file in files: name, ext = os.path.splitext(file) if ext == ".mp3": tubes[name] = FileRadio(f"{path}/{file}") tubes[name].start_stream() app.run(host="0.0.0.0", threaded=True, debug=False) print("Stopping any currently streaming radios...") for radio in radios: if radios[radio].is_streaming(): radios[radio].end_stream() radios = None for tube in tubes: if tubes[tube].is_streaming(): tubes[tube].end_stream() tubes = None print("Killing RTSP relay...") rtsp_relay.kill() rtsp_relay.wait() # Necessary?