sdrplay-fm-radio/microservice.py
2023-06-27 18:46:41 +09:30

323 lines
8.7 KiB
Python
Executable File

#! /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?