new update 1.2

This commit is contained in:
dharm pimsen 2023-12-17 22:40:49 +07:00
commit 5720928c28
7 changed files with 683 additions and 0 deletions

BIN
IDRBfavicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

BIN
IDRBfavicon.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
IDRBfavicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
IDRBlogo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

388
client.py Normal file
View File

@ -0,0 +1,388 @@
import time
from datetime import datetime
import cv2
import dearpygui.dearpygui as dpg
import threading
import socket
import numpy as np
import pickle
import pyaudio
from pyogg import OpusDecoder
from damp11113 import CV22DPG
librarylist = ["Opencv (opencv.org)", "PyOgg (TeamPyOgg)", "DearPyGui (hoffstadt)"]
def calculate_speed(start_time, end_time, data_size):
elapsed_time = end_time - start_time
speed_kbps = (data_size / elapsed_time) / 1024 # Convert bytes to kilobytes
return speed_kbps
def limit_string_in_line(text, limit):
lines = text.split('\n')
new_lines = []
for line in lines:
words = line.split()
new_line = ''
for word in words:
if len(new_line) + len(word) <= limit:
new_line += word + ' '
else:
new_lines.append(new_line.strip())
new_line = word + ' '
if new_line:
new_lines.append(new_line.strip())
return '\n'.join(new_lines)
class App:
def __init__(self):
self.RDS = None
self.device_name_output = "Speakers (2- USB Audio DAC )"
self.working = False
self.readchannel = 1
self.firstrun = True
self.firststart = True
self.device_index_output = 0
def connecttoserver(self, sender, data):
dpg.configure_item("connectservergroup", show=False)
#protocol = dpg.get_value("serverprotocol")
dpg.configure_item("serverstatus", default_value='connecting...', color=(255, 255, 0))
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()
self.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:
self.device_index_output = dev['index']
break
thread = threading.Thread(target=self.stream, args=(s, ))
thread.start()
def disconnectserver(self, sender=None, data=None):
dpg.configure_item("disconnectbutton", show=False)
dpg.configure_item("serverstatus", default_value='disconnecting...', color=(255, 255, 0))
self.working = False
dpg.configure_item("serverinfobutton", show=False)
dpg.configure_item("mediachannelselect", show=False)
dpg.configure_item("morerdsbutton", show=False)
dpg.configure_item("station_logo_config", show=False)
dpg.configure_item("RDSinfo", show=False)
dpg.configure_item("disconnectbutton", show=False)
dpg.configure_item("connectservergroup", show=True)
dpg.configure_item("serverstatus", default_value='disconnected', color=(255, 0, 0))
self.firstrun = True
self.firststart = True
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: " + limit_string_in_line(self.RDS["RT"], 120))
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'))
try:
dpg.set_value("station_logo", CV22DPG(
cv2.imdecode(np.frombuffer(self.RDS["images"]["logo"], np.uint8),
cv2.IMREAD_COLOR)))
except:
dpg.configure_item("station_logo_config", show=False)
except Exception as e:
pass
def changechannel(self, sender, data):
dpg.configure_item("serverstatus", default_value='please wait...', color=(255, 255, 0))
dpg.configure_item("station_logo_config", show=False)
self.readchannel = int(dpg.get_value(sender).split(" ")[0])
self.firstrun = True
p = pyaudio.PyAudio()
self.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:
self.device_index_output = dev['index']
break
def stream(self, socket):
opus_decoder = None
streamoutput = None
tfrpx = list(range(250))
altfrpy = [0] * 250
adcctfrpy = [0] * 250
imcctfrpy = [0] * 250
bytesconunt = 0
bytesconunt_frame = 0
start_time = time.time()
while True:
try:
if self.working:
#data = b''
data = socket.recv(1580152)
#while True:
# part = socket.recv(1024)
# data += part
# if len(part) < 1024:
# # either 0 or end of data
# break
bytesconunt += len(data)
if bytesconunt_frame >= 10:
speed_kbps = calculate_speed(start_time, time.time(), bytesconunt)
dpg.configure_item("serverstatus", default_value=f'connected {int(speed_kbps)}Kbps ({len(data)})', color=(0, 255, 0))
start_time = time.time()
bytesconunt_frame = 0
bytesconunt = 0
if len(altfrpy) > 250:
altfrpy.pop(0)
altfrpy.append(len(data))
dpg.set_value('transferatealldataplot', [tfrpx, altfrpy])
if len(data) == 0:
dpg.configure_item("serverstatus", default_value='lost connected', color=(255, 0, 0))
socket.close()
self.disconnectserver()
break
try:
datadecoded = pickle.loads(data)
except:
pass
if len(imcctfrpy) > 250:
imcctfrpy.pop(0)
imcctfrpy.append(len(str(datadecoded["channel"][self.readchannel]["RDS"]["images"])))
dpg.set_value('transferateimagesoncchannelplot', [tfrpx, imcctfrpy])
try:
if datadecoded["channel"][self.readchannel]["RDS"] != self.RDS:
self.RDS = datadecoded["channel"][self.readchannel]["RDS"]
rdshow = threading.Thread(target=self.RDSshow)
rdshow.start()
dpg.configure_item("ServerListener", default_value="Listener: " + str(datadecoded["serverinfo"]["Listener"]) + " Users")
except:
pass
if self.firstrun:
p = pyaudio.PyAudio()
opus_decoder = OpusDecoder()
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=self.device_index_output)
if len(datadecoded["channel"]) > 1:
channel_info = []
for i in range(1, len(datadecoded["channel"]) + 1):
channel_info.append(f'{i} {datadecoded["channel"][i]["Station"]} ({datadecoded["channel"][i]["RDS"]["ContentInfo"]["Codec"]} {datadecoded["channel"][i]["RDS"]["ContentInfo"]["bitrate"] / 1000}Kbps {datadecoded["channel"][i]["RDS"]["AudioMode"]})')
dpg.configure_item("mediachannelselect", show=True, items=channel_info)
dpg.configure_item("morerdsbutton", show=True)
dpg.configure_item("serverinfobutton", show=True)
try:
dpg.set_value("station_logo", CV22DPG(cv2.imdecode(np.frombuffer(datadecoded["channel"][self.readchannel]["RDS"]["images"]["logo"], np.uint8), cv2.IMREAD_COLOR)))
dpg.configure_item("station_logo_config", show=True)
except:
dpg.configure_item("station_logo_config", show=False)
dpg.configure_item("disconnectbutton", show=True)
dpg.configure_item("RDSPI", default_value=f"PI: {hex(self.RDS['PI'])[2:].upper()}")
if self.firststart:
self.readchannel = datadecoded["mainchannel"]
dpg.configure_item("mediachannelselect", show=True, default_value="mainchannel")
dpg.configure_item("serverstatus", default_value='connected --Kbps (----)', color=(0, 255, 0))
self.firstrun = False
self.firststart = False
if not self.firstrun:
decoded_pcm = opus_decoder.decode(memoryview(bytearray(datadecoded["channel"][self.readchannel]["Content"])))
if len(adcctfrpy) > 250:
adcctfrpy.pop(0)
adcctfrpy.append(len(datadecoded["channel"][self.readchannel]["Content"]))
dpg.set_value('transferateaudiodataoncchannelplot', [tfrpx, adcctfrpy])
# 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")
bytesconunt_frame += 1
else:
streamoutput.close()
socket.close()
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)
try:
streamoutput.close()
except:
pass
socket.close()
self.disconnectserver()
break
def window(self):
with dpg.window(label="IDRB", width=320, height=520, no_close=True):
dpg.add_button(label="Server info", callback=lambda: dpg.configure_item("Serverinfowindow", show=True), tag="serverinfobutton", show=False)
dpg.add_button(label="disconnect", callback=self.disconnectserver, tag="disconnectbutton", show=False)
dpg.add_text("not connect", tag="serverstatus", color=(255, 0, 0))
dpg.add_combo([], label="Channel", tag="mediachannelselect", default_value="Main Channel", show=False, callback=self.changechannel)
dpg.add_spacer()
dpg.add_image("station_logo", show=False, tag="station_logo_config")
dpg.add_text("", tag="RDSinfo", show=False)
with dpg.child_window(tag="connectservergroup", label="Server", use_internal_label=True, height=105):
dpg.add_button(label="select server", tag="selectserverbutton")
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_spacer()
dpg.add_button(label="More RDS info", callback=lambda: dpg.configure_item("RDSwindow", show=True), tag="morerdsbutton", show=False)
with dpg.window(label="IDRB RDS Info", tag="RDSwindow", show=False, width=250):
with dpg.tab_bar():
with dpg.tab(label="Program"):
with dpg.child_window(label="Basic", use_internal_label=True, height=100):
dpg.add_text("PS: ...", tag="RDSPS")
dpg.add_text("PI: ...", tag="RDSPI")
dpg.add_text("RT: ...", tag="RDSRT")
dpg.add_text("Time Local: ...", tag="RDSCTlocal")
dpg.add_text("Time UTC: ...", tag="RDSCTUTC")
with dpg.tab(label="EPG"):
pass
with dpg.tab(label="Images"):
pass
with dpg.tab(label="AS"):
pass
with dpg.tab(label="EOM"):
pass
with dpg.window(label="IDRB Server Info", tag="Serverinfowindow", show=False):
dpg.add_text("Listener: ...", tag="ServerListener")
dpg.add_spacer()
#dpg.add_simple_plot(label="Transfer Rates", autosize=True, height=250, width=500, tag="transferateplot")
with dpg.plot(label="Transfer Rates", height=250, width=500):
# optionally create legend
dpg.add_plot_legend()
# REQUIRED: create x and y axes
dpg.add_plot_axis(dpg.mvXAxis, label="x", tag="x_axis", no_gridlines=True)
dpg.add_plot_axis(dpg.mvYAxis, label="y", tag="y_axis1", no_gridlines=True)
dpg.add_plot_axis(dpg.mvYAxis, label="y", tag="y_axis2", no_gridlines=True)
dpg.add_plot_axis(dpg.mvYAxis, label="y", tag="y_axis3", no_gridlines=True)
# series belong to a y axis
dpg.add_line_series([], [], label="All Data", parent="y_axis1", tag="transferatealldataplot")
dpg.add_line_series([], [], label="Audio Data", parent="y_axis2", tag="transferateaudiodataoncchannelplot")
dpg.add_line_series([], [], label="Images Data", parent="y_axis3", tag="transferateimagesoncchannelplot")
with dpg.window(label="IDRB About", tag="aboutwindow", show=False, no_resize=True):
dpg.add_image("app_logo")
dpg.add_spacer()
dpg.add_text("IDRB (Internet Digital Radio Broadcasting System) Client")
dpg.add_spacer()
dpg.add_text(f"IDRB Client v1.2 Beta")
dpg.add_spacer()
desc = "IDRB is a novel internet radio broadcasting alternative that uses HLS/DASH/HTTP streams, transferring over TCP/IP. This system supports images and RDS (Dynamic update) capabilities, enabling the transmission of station information. Additionally, it allows for setting station logos and images. IDRB offers multi-broadcasting functionalities and currently supports the Opus codec, with plans to incorporate PCM, MP2/3, AAC/AAC+, and more in the future, ensuring low delay. If you find this project intriguing, you can support it at damp11113.xyz/support."
dpg.add_text(limit_string_in_line(desc, 75))
dpg.add_spacer()
with dpg.table(header_row=True):
# use add_table_column to add columns to the table,
# table columns use slot 0
dpg.add_table_column(label="Libraries")
# add_table_next_column will jump to the next row
# once it reaches the end of the columns
# table next column use slot 1
for i in librarylist:
with dpg.table_row():
dpg.add_text(i)
dpg.add_spacer(height=20)
dpg.add_text(f"Copyright (C) 2023 ThaiSDR All rights reserved. (MIT)")
def menubar(self):
with dpg.viewport_menu_bar():
with dpg.menu(label="File"):
dpg.add_menu_item(label="Exit", callback=lambda: self.exit())
with dpg.menu(label="Settings"):
dpg.add_menu_item(label="StyleEditor", callback=dpg.show_style_editor)
with dpg.menu(label="Help"):
dpg.add_menu_item(label="About", callback=lambda: dpg.configure_item("aboutwindow", show=True))
def init(self):
dpg.create_context()
dpg.create_viewport(title=f'IDRB Client V1.2 Beta', width=1280, height=720, large_icon="IDRBfavicon.ico") # set viewport window
dpg.setup_dearpygui()
# -------------- add code here --------------
noimage_texture_data = []
for i in range(0, 128 * 128):
noimage_texture_data.append(20 / 255)
noimage_texture_data.append(0)
noimage_texture_data.append(20 / 255)
noimage_texture_data.append(20 / 255)
with dpg.texture_registry():
dpg.add_raw_texture(128, 128, noimage_texture_data, tag="station_logo", format=dpg.mvFormat_Float_rgb)
width, height, channels, data = dpg.load_image("IDRBlogo.png")
dpg.add_static_texture(width=512, height=256, default_value=data, tag="app_logo")
self.window()
self.menubar()
# -------------------------------------------
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()
dpg.fit_axis_data("x_axis")
dpg.fit_axis_data("y_axis1")
dpg.fit_axis_data("y_axis2")
dpg.fit_axis_data("y_axis3")
def exit(self):
dpg.destroy_context()
app = App()
app.init()

BIN
favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

295
server.py Normal file
View File

@ -0,0 +1,295 @@
import socket
import time
import pyaudio
from pyogg import OpusBufferedEncoder
import numpy as np
import pickle
import threading
from damp11113 import scrollTextBySteps
from queue import Queue
from datetime import datetime, timezone
import cv2
# create tcp
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# wait for connection
server_port = ('localhost', 6980)
s.bind(server_port)
s.listen(1)
p = pyaudio.PyAudio()
sample_rate = 48000
bytes_per_sample = p.get_sample_size(pyaudio.paInt16)
codec = "opus" # opus, pcm, aac
# Create an Opus encoder
bitrates = 64000 #Kbps
channel = 2 # Stereo
framesize = 60
if bitrates >= 500000:
bitrates = 500000
device_name_input = "Line 5 (Virtual Audio Cable)"
device_index_input = 0
for i in range(p.get_device_count()):
dev = p.get_device_info_by_index(i)
if dev['name'] == device_name_input:
device_index_input = dev['index']
break
device_name_input = "Line 4 (Virtual Audio Cable)"
device_index_input2 = 0
for i in range(p.get_device_count()):
dev = p.get_device_info_by_index(i)
if dev['name'] == device_name_input:
device_index_input2 = dev['index']
break
streaminput = p.open(format=pyaudio.paInt16, channels=2, rate=sample_rate, input=True, input_device_index=device_index_input)
streaminput2 = p.open(format=pyaudio.paInt16, channels=2, rate=sample_rate, input=True, input_device_index=device_index_input2)
def encodelogoimage(path, quality=50):
image = cv2.resize(cv2.imread(path), (128, 128))
# Encode the image as JPEG with higher compression (lower quality)
encode_param = [int(cv2.IMWRITE_JPEG_QUALITY), quality] # Adjust quality (50 is just an example)
result, encoded_image = cv2.imencode('.jpg', image, encode_param)
encoded_bytes = np.array(encoded_image).tobytes()
return encoded_bytes
RDS = {
"PS": "DPRadio",
"RT": "Testing internet radio",
"PI": 0x27C8, # static
"PTY": 0,
"PTY+": "Testing",
"Country": "TH",
"Coverage": "All",
"CT": {
"Local": None,
"UTC": None,
},
"PIN": 12345,
"TMC": {
"TP": False,
"TA": False,
"Messages": None
},
"ECC": None,
"LIC": None,
"AudioMode": "Stereo", # mono, stereo, surround 5.1/7.1, HRTF
"ArtificialHead": False,
"Compressed": False,
"DyPTY": False,
"EPG": None,
"AS": [ # AS = Alternative Server
# can add more server here
],
"EON": [
# can add more here
],
"ContentInfo": {
"Codec": codec,
"bitrate": bitrates,
"channel": channel,
"samplerates": sample_rate
},
"images": {
"logo": encodelogoimage(r"C:\Users\sansw\3D Objects\dpstream iptv logo.png")
}
}
RDS2 = {
"PS": "DPTest",
"RT": "Testing internet radio",
"PI": 0x27C6,
"PTY": 0,
"PTY+": "Testing",
"Country": "TH",
"Coverage": "All",
"CT": {
"Local": None,
"UTC": None,
},
"PIN": 12345,
"TMC": {
"TP": False,
"TA": False,
"Messages": None
},
"ECC": None,
"LIC": None,
"AudioMode": "Stereo", # mono, stereo, surround 5.1/7.1, HRTF
"ArtificialHead": False,
"Compressed": False,
"DyPTY": False,
"EPG": None,
"AS": [ # AS = Alternative Server
# can add more server here
],
"EON": [
# can add more server here
],
"ContentInfo": {
"Codec": codec,
"bitrate": 8000,
"channel": channel,
"samplerates": sample_rate
},
"images": {
"logo": None
}
}
lock = threading.Lock()
def update_RDS():
global RDS
with lock:
while True:
pstext = "DPRadio Testing Broadcasting "
for i in range(0, len(pstext)):
RDS["PS"] = scrollTextBySteps(pstext, i)
time.sleep(1)
def update_RDS_time():
global RDS
while True:
RDS["CT"]["Local"] = datetime.now().timestamp()
RDS["CT"]["UTC"] = datetime.utcnow().timestamp()
time.sleep(1)
def update_RDS_images():
global RDS
while True:
RDS["images"]["logo"] = encodelogoimage(r"C:\Users\sansw\3D Objects\dpstream iptv logo.png", 25)
time.sleep(10)
RDS["images"]["logo"] = encodelogoimage(r"C:\Users\sansw\3D Objects\140702_hi-res-logo.jpg", 25)
time.sleep(10)
RDS["images"]["logo"] = encodelogoimage(r"IDRBfavicon.jpg", 25)
time.sleep(10)
thread = threading.Thread(target=update_RDS)
thread.start()
thread2 = threading.Thread(target=update_RDS_time)
thread2.start()
thread4 = threading.Thread(target=update_RDS_images)
thread4.start()
# Create a shared queue for encoded audio packets
channel1 = Queue()
channel2 = Queue()
# Function to continuously encode audio and put it into the queue
def encode_audio():
encoder = OpusBufferedEncoder()
encoder.set_application("audio")
encoder.set_sampling_frequency(sample_rate)
encoder.set_channels(channel)
encoder.set_bitrates(bitrates)
encoder.set_frame_size(framesize)
while True:
pcm = np.frombuffer(streaminput.read(1024, exception_on_overflow=False), dtype=np.int16)
encoded_packets = encoder.buffered_encode(memoryview(bytearray(pcm)))
for encoded_packet, _, _ in encoded_packets:
# Put the encoded audio into the buffer
channel1.put(encoded_packet.tobytes())
def encode_audio2():
encoder2 = OpusBufferedEncoder()
encoder2.set_application("audio")
encoder2.set_sampling_frequency(sample_rate)
encoder2.set_channels(channel)
encoder2.set_bitrates(8000)
encoder2.set_frame_size(framesize)
while True:
pcm2 = np.frombuffer(streaminput2.read(1024, exception_on_overflow=False), dtype=np.int16)
encoded_packets = encoder2.buffered_encode(memoryview(bytearray(pcm2)))
for encoded_packet, _, _ in encoded_packets:
# Put the encoded audio into the buffer
channel2.put(encoded_packet.tobytes())
audio_thread = threading.Thread(target=encode_audio)
audio_thread.start()
audio_thread2 = threading.Thread(target=encode_audio2)
audio_thread2.start()
connectionlist = []
connected_users = 0
def handle_client():
global connected_users
try:
while True:
# Get the encoded audio from the buffer
ENchannel1 = channel1.get()
ENchannel2 = channel2.get()
content = {
"mainchannel": 1,
"channel": {
1: {
"Station": "DPRadio+",
"ContentSize": len(ENchannel1),
"Content": ENchannel1,
"RDS": RDS
},
2: {
"Station": "DPTest",
"ContentSize": len(ENchannel2),
"Content": ENchannel2,
"RDS": RDS2
}
},
"serverinfo": {
"Listener": connected_users,
"Country": "TH",
"Startat": time.time()
}
}
#connection.sendall(pickle.dumps(content))
for i in connectionlist:
try:
i.sendall(pickle.dumps(content))
except Exception as e:
#print(f'Error sending data to {i.getpeername()}: {e}')
# Remove disconnected client from the list
if i in connectionlist:
i.close()
connectionlist.remove(i)
connected_users -= 1
except Exception as e:
print(f'Error: {e}')
first = True
# Your main server logic using threading for handling connections
while True:
print("Waiting for a connection...")
connection, client_address = s.accept()
print(f"Connected to {client_address}")
connectionlist.append(connection)
connected_users += 1
if first:
# Start a: new thread to handle the client
client_thread = threading.Thread(target=handle_client)
#client_thread.daemon = True # Set the thread as a daemon so it exits when the main thread exits
client_thread.start()
first = False