mirror of
https://github.com/damp11113/IDRB.git
synced 2025-04-27 22:48:09 +00:00
185 lines
8.0 KiB
Python
185 lines
8.0 KiB
Python
import asyncio
|
|
from datetime import datetime
|
|
import dearpygui.dearpygui as dpg
|
|
import threading
|
|
import socket
|
|
import numpy as np
|
|
import pickle
|
|
import pyaudio
|
|
import websockets
|
|
from pyogg import OpusDecoder
|
|
|
|
class App:
|
|
def __init__(self):
|
|
self.RDS = None
|
|
self.device_name_output = "Speakers (4- USB Audio DAC )"
|
|
self.working = False
|
|
|
|
def connecttoserver(self, sender, data):
|
|
dpg.configure_item("connectbutton", show=False)
|
|
protocol = dpg.get_value("serverprotocol")
|
|
dpg.configure_item("serverstatus", default_value='connecting...', color=(255, 255, 0))
|
|
if protocol == "Websocket":
|
|
asyncio.create_task(self.WSstream())
|
|
return
|
|
|
|
ip = dpg.get_value("serverip")
|
|
port = dpg.get_value("serverport")
|
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
try:
|
|
s.connect((ip, port))
|
|
except:
|
|
dpg.configure_item("connectbutton", show=True)
|
|
self.working = True
|
|
p = pyaudio.PyAudio()
|
|
|
|
device_index_output = 0
|
|
for i in range(p.get_device_count()):
|
|
dev = p.get_device_info_by_index(i)
|
|
if dev['name'] == self.device_name_output:
|
|
device_index_output = dev['index']
|
|
break
|
|
|
|
thread = threading.Thread(target=self.stream, args=(s, device_index_output))
|
|
thread.start()
|
|
|
|
def disconnectserver(self, sender, data):
|
|
dpg.configure_item("disconnectbutton", show=False)
|
|
dpg.configure_item("serverstatus", default_value='disconnecting...', color=(255, 255, 0))
|
|
self.working = False
|
|
|
|
def RDSshow(self):
|
|
try:
|
|
dpg.configure_item("RDSinfo",
|
|
default_value=f'{self.RDS["PS"]} ({self.RDS["ContentInfo"]["Codec"]} {self.RDS["ContentInfo"]["bitrate"] / 1000}Kbps {self.RDS["AudioMode"]})',
|
|
show=True)
|
|
dpg.configure_item("RDSPS", default_value="PS: " + self.RDS["PS"])
|
|
dpg.configure_item("RDSRT", default_value="RT: " + self.RDS["RT"])
|
|
dpg.configure_item("RDSCTlocal", default_value="Time Local: " + datetime.fromtimestamp(self.RDS["CT"]["Local"]).strftime('%H:%M:%S'))
|
|
dpg.configure_item("RDSCTUTC", default_value="Time UTC: " + datetime.fromtimestamp(self.RDS["CT"]["UTC"]).strftime('%H:%M:%S'))
|
|
dpg.configure_item("RDSListener", default_value="Listener: " + str(self.RDS["Listener"]) + " Users")
|
|
|
|
except Exception as e:
|
|
pass
|
|
|
|
def stream(self, socket, deviceindex):
|
|
opus_decoder = OpusDecoder()
|
|
streamoutput = None
|
|
firstrun = True
|
|
while True:
|
|
try:
|
|
if self.working:
|
|
data = socket.recv(650000)
|
|
|
|
if len(data) == 0:
|
|
dpg.configure_item("serverstatus", default_value='lost connected', color=(255, 0, 0))
|
|
socket.close()
|
|
dpg.configure_item("showRDS", show=False)
|
|
dpg.configure_item("RDSinfo", show=False)
|
|
dpg.configure_item("disconnectbutton", show=False)
|
|
dpg.configure_item("connectbutton", show=True)
|
|
break
|
|
|
|
try:
|
|
datadecoded = pickle.loads(data)
|
|
except:
|
|
pass
|
|
|
|
if datadecoded["RDS"] != self.RDS:
|
|
self.RDS = datadecoded["RDS"]
|
|
rdshow = threading.Thread(target=self.RDSshow)
|
|
rdshow.start()
|
|
|
|
if firstrun:
|
|
p = pyaudio.PyAudio()
|
|
opus_decoder.set_channels(self.RDS["ContentInfo"]["channel"])
|
|
opus_decoder.set_sampling_frequency(self.RDS["ContentInfo"]["samplerates"])
|
|
streamoutput = p.open(format=pyaudio.paInt16, channels=self.RDS["ContentInfo"]["channel"], rate=self.RDS["ContentInfo"]["samplerates"], output=True, output_device_index=deviceindex)
|
|
dpg.configure_item("showRDS", show=True)
|
|
dpg.configure_item("serverstatus", default_value='connected', color=(0, 255, 0))
|
|
dpg.configure_item("disconnectbutton", show=True)
|
|
firstrun = False
|
|
|
|
decoded_pcm = opus_decoder.decode(memoryview(bytearray(datadecoded["Content"])))
|
|
|
|
# Check if the decoded PCM is empty or not
|
|
if len(decoded_pcm) > 0:
|
|
pcm_to_write = np.frombuffer(decoded_pcm, dtype=np.int16)
|
|
|
|
streamoutput.write(pcm_to_write.tobytes())
|
|
else:
|
|
print("Decoded PCM is empty")
|
|
else:
|
|
streamoutput.close()
|
|
socket.close()
|
|
self.working = False
|
|
dpg.configure_item("showRDS", show=False)
|
|
dpg.configure_item("RDSinfo", show=False)
|
|
dpg.configure_item("connectbutton", show=True)
|
|
dpg.configure_item("serverstatus", default_value='disconnected', color=(255, 0, 0))
|
|
break
|
|
except Exception as e:
|
|
if str(e) == "An error occurred while decoding an Opus-encoded packet: corrupted stream":
|
|
dpg.configure_item("serverstatus", default_value="Unable to decode audio data", color=(255, 0, 0))
|
|
else:
|
|
print("connection lost", e)
|
|
self.working = False
|
|
streamoutput.close()
|
|
socket.close()
|
|
dpg.configure_item("showRDS", show=False)
|
|
dpg.configure_item("RDSinfo", show=False)
|
|
dpg.configure_item("connectbutton", show=True)
|
|
dpg.configure_item("serverstatus", default_value='disconnected', color=(255, 0, 0))
|
|
break
|
|
|
|
def window(self):
|
|
with dpg.window(label="IDRB", width=320):
|
|
dpg.add_text("", tag="RDSinfo", show=False)
|
|
dpg.add_input_text(label="server ip", tag="serverip", default_value="localhost")
|
|
dpg.add_input_int(label="port", tag="serverport", max_value=65535, default_value=6980)
|
|
dpg.add_combo(["TCP", "Websocket"], label="protocol", tag="serverprotocol", default_value="TCP")
|
|
dpg.add_button(label="connect", callback=self.connecttoserver, tag="connectbutton")
|
|
dpg.add_button(label="disconnect", callback=self.disconnectserver, tag="disconnectbutton", show=False)
|
|
dpg.add_spacer()
|
|
dpg.add_text("not connect", tag="serverstatus", color=(255, 0, 0))
|
|
dpg.add_spacer()
|
|
with dpg.collapsing_header(label="RDS", show=False, tag="showRDS"):
|
|
dpg.add_text("PS: ...", tag="RDSPS")
|
|
dpg.add_text("RT: ...", tag="RDSRT")
|
|
dpg.add_text("Listener: ...", tag="RDSListener")
|
|
dpg.add_text("Time Local: ...", tag="RDSCTlocal")
|
|
dpg.add_text("Time UTC: ...", tag="RDSCTUTC")
|
|
|
|
|
|
|
|
def init(self):
|
|
dpg.create_context()
|
|
dpg.create_viewport(title='[ThaiSDR] IDRB (Internet Digital Radio Broadcasting) V1 Beta', width=1280, height=720, small_icon="favicon.ico") # set viewport window
|
|
dpg.setup_dearpygui()
|
|
# -------------- add code here --------------
|
|
self.window()
|
|
# -------------------------------------------
|
|
dpg.show_viewport()
|
|
# Start a separate thread for a task
|
|
self.thread_stop_event = threading.Event()
|
|
|
|
while dpg.is_dearpygui_running():
|
|
self.render()
|
|
dpg.render_dearpygui_frame()
|
|
|
|
# Signal the thread to stop and wait for it to finish
|
|
self.thread_stop_event.set()
|
|
dpg.destroy_context()
|
|
|
|
def render(self):
|
|
# insert here any code you would like to run in the render loop
|
|
# you can manually stop by using stop_dearpygui() or self.exit()
|
|
pass
|
|
|
|
def exit(self):
|
|
dpg.destroy_context()
|
|
|
|
|
|
app = App()
|
|
app.init()
|