STL Hub — Gerador de Scripts de Rádio

Transmissão multiponto GStreamer · Opus · Tailscale VPN

Configuração Global (TX)

Liste GUIDs com gst-device-monitor-1.0 Audio/Source. Compatível com WASAPI, DirectSound e PulseAudio; ignorado em autoaudiosrc.

Afiliadas (RX) — 1

Vazio = dispositivo padrão. Liste GUIDs com gst-device-monitor-1.0 Audio/Sink.

Scripts Gerados (GStreamer)
import os
import sys
import time
import traceback

def aguardar_saida():
    print("")
    if os.name == "nt":
        os.system("pause")
        return
    try:
        input("Pressione ENTER para sair...")
    except EOFError:
        print("Sem terminal interativo; mantendo a janela por 60 segundos para leitura do erro.")
        time.sleep(60)

def erro_global(exc_type, exc_value, exc_traceback):
    if issubclass(exc_type, KeyboardInterrupt):
        sys.__excepthook__(exc_type, exc_value, exc_traceback)
        return
    print("[ERRO NAO TRATADO]", exc_value)
    traceback.print_exception(exc_type, exc_value, exc_traceback)
    aguardar_saida()

sys.excepthook = erro_global

try:
    import gi
    gi.require_version('Gst', '1.0')
    from gi.repository import Gst, GLib
except Exception as e:
    print("[ERRO] Nao foi possivel carregar o GStreamer/PyGObject:", e)
    traceback.print_exc()
    aguardar_saida()
    raise SystemExit(1)

Gst.init(None)

def validar_elemento(nome):
    if Gst.ElementFactory.find(nome) is None:
        raise RuntimeError(f"Elemento GStreamer nao encontrado: {nome}. Verifique plugins e PATH do GStreamer.")

def normalizar_afiliadas(lista):
    validas = []
    for index, rx in enumerate(lista, start=1):
        nome = (rx.get("nome") or f"Afiliada #{index}").strip()
        ip = (rx.get("ip") or "").strip()
        try:
            porta = int(rx.get("porta") or 5004)
        except (TypeError, ValueError):
            porta = 5004
        if not ip:
            print(f"[AVISO] {nome} ignorada: IP Tailscale vazio.")
            continue
        if porta < 1 or porta > 65535:
            print(f"[AVISO] {nome} ignorada: porta UDP invalida ({porta}).")
            continue
        validas.append({"nome": nome, "ip": ip, "porta": porta})
    if not validas:
        raise RuntimeError("Nenhuma afiliada RX valida. Preencha pelo menos um IP Tailscale antes de executar o TX.")
    return validas

# --- CONFIGURAÇÕES GERADAS PELO STL HUB ---
afiliadas = [
    {"nome": "", "ip": "", "porta": 5004},
]

SRC = "wasapisrc"
for elemento in [SRC, "audioconvert", "audioresample", "opusenc", "rtpopuspay", "tee", "queue", "udpsink"]:
    validar_elemento(elemento)

afiliadas_validas = normalizar_afiliadas(afiliadas)

# Pipeline Base (Captura e Encode)
pipeline_str = "wasapisrc ! audioconvert ! audioresample ! opusenc bitrate=128000 ! rtpopuspay pt=96 ! tee name=t "

# Pipeline Multi-Destino (Fan-out)
for index, rx in enumerate(afiliadas_validas):
    pipeline_str += f"t. ! queue max-size-buffers=0 max-size-time=0 max-size-bytes=0 ! udpsink host={rx['ip']} port={rx['porta']} sync=false async=false "

print("Pipeline:", pipeline_str)
print("Iniciando transmissao multiponto para", len(afiliadas_validas), "emissoras...")

loop = GLib.MainLoop()

def on_bus(bus, msg):
    t = msg.type
    if t == Gst.MessageType.ERROR:
        err, dbg = msg.parse_error()
        print("[GST ERROR]", err.message)
        if dbg: print("  debug:", dbg)
        loop.quit()
    elif t == Gst.MessageType.EOS:
        print("[GST] EOS recebido")
        loop.quit()
    elif t == Gst.MessageType.WARNING:
        w, dbg = msg.parse_warning()
        print("[GST WARN]", w.message)
    return True

pipeline = None
try:
    pipeline = Gst.parse_launch(pipeline_str)
    bus = pipeline.get_bus()
    bus.add_signal_watch()
    bus.connect("message", on_bus)
    ret = pipeline.set_state(Gst.State.PLAYING)
    if ret == Gst.StateChangeReturn.FAILURE:
        raise RuntimeError("Falha ao iniciar pipeline. Verifique device ID, plugin de audio, porta e permissao do dispositivo.")
    print("Pipeline iniciado. Pressione Ctrl+C para parar.")
    loop.run()
except KeyboardInterrupt:
    print("Interrompido pelo usuario.")
except GLib.Error as e:
    print("[GLIB ERROR]", e.message if hasattr(e, "message") else e)
    traceback.print_exc()
except Exception as e:
    print("[EXCECAO]", e)
    traceback.print_exc()
finally:
    if pipeline is not None:
        pipeline.set_state(Gst.State.NULL)
    aguardar_saida()