mirror of
https://github.com/damp11113/IDRB.git
synced 2025-04-27 06:28:09 +00:00
new update 1.6.1
new 1. compression with Zlib 2. Buffer 3. Public Server Selector 4. spilt code
This commit is contained in:
parent
5f2c2c54c6
commit
b0c15aff71
BIN
Client/__pycache__/appcomponent.cpython-310.pyc
Normal file
BIN
Client/__pycache__/appcomponent.cpython-310.pyc
Normal file
Binary file not shown.
BIN
Client/__pycache__/utils.cpython-310.pyc
Normal file
BIN
Client/__pycache__/utils.cpython-310.pyc
Normal file
Binary file not shown.
166
Client/appcomponent.py
Normal file
166
Client/appcomponent.py
Normal file
@ -0,0 +1,166 @@
|
||||
import dearpygui.dearpygui as dpg
|
||||
|
||||
from utils import *
|
||||
|
||||
librarylist = ["Opencv (opencv.org)", "PyOgg (TeamPyOgg) (Forked)", "DearPyGui (hoffstadt)"]
|
||||
|
||||
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("Logo not available", tag="logostatus", color=(255, 0, 0), show=False)
|
||||
dpg.add_text("", tag="RDSinfo", show=False)
|
||||
|
||||
with dpg.child_window(tag="connectservergroup", label="Server", use_internal_label=True, height=130):
|
||||
dpg.add_button(label="select server", tag="selectserverbutton", callback=self.pubserverselectopen)
|
||||
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", "ZeroMQ", "ZeroMQ (WS)"], 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("Server: ...", tag="ServerNamedisp")
|
||||
dpg.add_text("Description: ...", tag="ServerDescdisp")
|
||||
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.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.6.1 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-2024 ThaiSDR All rights reserved. (GPLv3)")
|
||||
|
||||
with dpg.window(label="IDRB Public Server", tag="pubserverselectwindow", show=False, modal=True, popup=True, height=500, width=1200):
|
||||
dpg.add_text("N/A", tag="pubserverselectstatus")
|
||||
dpg.add_input_text(hint="search server here", tag="serversearchinput")
|
||||
dpg.add_button(label="search", callback=lambda: self.pubserverselectsearch(dpg.get_value("serversearchinput")))
|
||||
with dpg.table(header_row=True, tag="pubserverlist"):
|
||||
dpg.add_table_column(label="IP")
|
||||
dpg.add_table_column(label="Server")
|
||||
dpg.add_table_column(label="Description")
|
||||
dpg.add_table_column(label="listeners")
|
||||
dpg.add_spacer()
|
||||
dpg.add_button(label="connect", callback=self.connecttoserverwithpubselect, tag="connectbuttonpubserverselect", show=False)
|
||||
|
||||
|
||||
with dpg.window(label="IDRB Evaluation", tag="evaluationwindow", show=False):
|
||||
with dpg.tab_bar():
|
||||
with dpg.tab(label="Audio"):
|
||||
with dpg.plot(label="FFT Spectrum", height=250, width=500):
|
||||
# optionally create legend
|
||||
dpg.add_plot_legend()
|
||||
|
||||
# REQUIRED: create x and y axes
|
||||
dpg.add_plot_axis(dpg.mvXAxis, tag="x_axis_1", no_gridlines=True, label="Frequencies")
|
||||
dpg.add_plot_axis(dpg.mvYAxis, tag="audioL_y_axis", no_gridlines=True)
|
||||
dpg.add_plot_axis(dpg.mvYAxis, tag="audioR_y_axis", no_gridlines=True)
|
||||
|
||||
dpg.set_axis_limits("audioL_y_axis", 0, 2500000)
|
||||
dpg.set_axis_limits("audioR_y_axis", 0, 2500000)
|
||||
|
||||
# series belong to a y axis
|
||||
dpg.add_line_series([], [], label="Left Channel", parent="audioL_y_axis", tag="audioinfoleftplot")
|
||||
dpg.add_line_series([], [], label="Right Channel", parent="audioR_y_axis", tag="audioinforightplot")
|
||||
|
||||
with dpg.tab(label="Network"):
|
||||
dpg.add_text("NA", tag="codecbitratestatus", show=True)
|
||||
dpg.add_text("Buffer 0/0", tag="bufferstatus", show=True)
|
||||
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, tag="x_axis", no_gridlines=True)
|
||||
dpg.add_plot_axis(dpg.mvYAxis, tag="y_axis1", no_gridlines=True)
|
||||
dpg.add_plot_axis(dpg.mvYAxis, tag="y_axis2", no_gridlines=True)
|
||||
dpg.add_plot_axis(dpg.mvYAxis, 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="Password Required", tag="requestpasswordpopup", modal=True, no_resize=True,
|
||||
no_close=True, no_move=True, show=False):
|
||||
dpg.add_text("This channel is encrypt! Please enter password for decrypt.")
|
||||
dpg.add_spacer()
|
||||
dpg.add_input_text(label="password", tag="requestpasswordinputpopup")
|
||||
dpg.add_spacer()
|
||||
dpg.add_button(label="confirm", callback=self.submitpassworddecrypt)
|
||||
|
||||
with dpg.window(label="Config", tag="configwindow", show=False, width=500):
|
||||
dpg.add_text("Please restart software when configured")
|
||||
with dpg.tab_bar():
|
||||
with dpg.tab(label="Audio"):
|
||||
dpg.add_combo([], label="Output Device", tag="selectaudiooutputdevicecombo",
|
||||
callback=self.changeaudiodevice)
|
||||
|
||||
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="View"):
|
||||
dpg.add_menu_item(label="Evaluation", callback=lambda: dpg.configure_item("evaluationwindow", show=True))
|
||||
with dpg.menu(label="Settings"):
|
||||
dpg.add_menu_item(label="Config", callback=lambda: dpg.configure_item("configwindow", show=True))
|
||||
dpg.add_spacer()
|
||||
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))
|
429
Client/client.py
429
Client/client.py
@ -1,81 +1,38 @@
|
||||
"""
|
||||
This file is part of IDRB Project.
|
||||
|
||||
IDRB Project is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
IDRB Project is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with IDRB Project. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
import queue
|
||||
import time
|
||||
from datetime import datetime
|
||||
import cv2
|
||||
import dearpygui.dearpygui as dpg
|
||||
import threading
|
||||
import socket
|
||||
import numpy as np
|
||||
import requests
|
||||
import pickle
|
||||
import pyaudio
|
||||
import zmq
|
||||
from pyogg import OpusDecoder
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Protocol.KDF import scrypt
|
||||
import configparser
|
||||
import ctypes
|
||||
import zlib
|
||||
|
||||
librarylist = ["Opencv (opencv.org)", "PyOgg (TeamPyOgg)", "DearPyGui (hoffstadt)"]
|
||||
from utils import *
|
||||
import appcomponent
|
||||
|
||||
def CV22DPG(cv2_array):
|
||||
try:
|
||||
if cv2_array is None or len(cv2_array.shape) < 3:
|
||||
print("Invalid or empty array received.")
|
||||
return None
|
||||
|
||||
if len(cv2_array.shape) == 2:
|
||||
cv2_array = cv2_array[:, :, np.newaxis]
|
||||
|
||||
data = np.flip(cv2_array, 2)
|
||||
data = data.ravel()
|
||||
data = np.asfarray(data, dtype='f')
|
||||
return np.true_divide(data, 255.0)
|
||||
except Exception as e:
|
||||
print("Error in CV22DPG:", e)
|
||||
return None
|
||||
|
||||
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)
|
||||
|
||||
def unpad_message(padded_message):
|
||||
padding_length = padded_message[-1]
|
||||
return padded_message[:-padding_length]
|
||||
|
||||
def decrypt_data(encrypted_message, password, salt, iv):
|
||||
# Derive the key from the password and salt
|
||||
key = scrypt(password, salt, key_len=32, N=2 ** 14, r=8, p=1)
|
||||
|
||||
# Initialize AES cipher in CBC mode
|
||||
cipher = AES.new(key, AES.MODE_CBC, iv)
|
||||
|
||||
# Decrypt the message
|
||||
decrypted_message = cipher.decrypt(encrypted_message)
|
||||
|
||||
# Unpad the decrypted message
|
||||
unpadded_message = unpad_message(decrypted_message)
|
||||
|
||||
return unpadded_message
|
||||
|
||||
class App:
|
||||
def __init__(self):
|
||||
@ -83,6 +40,9 @@ class App:
|
||||
self.config = configparser.ConfigParser()
|
||||
self.config.read("config.ini")
|
||||
self.device_name_output = self.config["audio"]["device"]
|
||||
self.buffersize = 64 # can configable
|
||||
|
||||
|
||||
self.working = False
|
||||
self.readchannel = 1
|
||||
self.firstrun = True
|
||||
@ -96,14 +56,35 @@ class App:
|
||||
self.cprotocol = None
|
||||
self.cciswaitlogoim = True
|
||||
self.ccthreadlogorecisworking = False
|
||||
self.ccserversecount = 0
|
||||
self.lsitem = None
|
||||
self.ccconwithpubselect = False
|
||||
self.buffer = queue.Queue(maxsize=self.buffersize)
|
||||
self.okbuffer = False
|
||||
self.firstrunbuffer = True
|
||||
|
||||
def connecttoserverwithpubselect(self, sender, data):
|
||||
self.ccconwithpubselect = True
|
||||
dpg.configure_item("pubserverselectwindow", show=False)
|
||||
self.connecttoserver(None, None)
|
||||
|
||||
def connecttoserver(self, sender, data):
|
||||
dpg.configure_item("connectservergroup", show=False)
|
||||
protocol = dpg.get_value("serverprotocol")
|
||||
self.cprotocol = protocol
|
||||
dpg.configure_item("serverstatus", default_value='connecting...', color=(255, 255, 0))
|
||||
ip = dpg.get_value("serverip")
|
||||
port = dpg.get_value("serverport")
|
||||
if self.ccconwithpubselect:
|
||||
serverlabel = str(dpg.get_item_configuration(self.lsitem)["label"])
|
||||
protocol = serverlabel.split("|")[0].strip()
|
||||
ip = serverlabel.split("|")[1].split(":")[0].strip()
|
||||
port = serverlabel.split("|")[1].split(":")[1].strip()
|
||||
|
||||
self.ccconwithpubselect = False
|
||||
else:
|
||||
protocol = dpg.get_value("serverprotocol").strip()
|
||||
ip = dpg.get_value("serverip").strip()
|
||||
port = dpg.get_value("serverport")
|
||||
|
||||
self.cprotocol = protocol
|
||||
|
||||
if protocol == "TCP":
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
try:
|
||||
@ -145,6 +126,7 @@ class App:
|
||||
dpg.configure_item("disconnectbutton", show=False)
|
||||
dpg.configure_item("connectservergroup", show=True)
|
||||
dpg.configure_item("serverstatus", default_value='disconnected', color=(255, 0, 0))
|
||||
dpg.configure_item("logostatus", show=False)
|
||||
self.firstrun = True
|
||||
self.firststart = True
|
||||
self.ccdecryptpassword = None
|
||||
@ -153,12 +135,16 @@ class App:
|
||||
self.ccisdecryptpassword = None
|
||||
self.cciswaitlogoim = True
|
||||
self.ccthreadlogorecisworking = False
|
||||
self.buffer = queue.Queue(maxsize=self.buffersize)
|
||||
self.okbuffer = False
|
||||
self.firstrunbuffer = 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"]})',
|
||||
default_value=f'{self.RDS["PS"]} ({self.RDS["ContentInfo"]["Codec"].upper()} {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'))
|
||||
@ -175,10 +161,11 @@ class App:
|
||||
logoreciveprocessingthread.start()
|
||||
else:
|
||||
if not self.RDS["images"]["logo"]["lazy"]:
|
||||
dpg.set_value("station_logo", CV22DPG(cv2.imdecode(np.frombuffer(self.RDS["images"]["logo"], np.uint8), cv2.IMREAD_COLOR)))
|
||||
dpg.set_value("station_logo", CV22DPG(cv2.imdecode(np.frombuffer(self.RDS["images"]["logo"]["contents"], np.uint8), cv2.IMREAD_COLOR)))
|
||||
dpg.configure_item("logostatus", show=False)
|
||||
except Exception as e:
|
||||
dpg.configure_item("station_logo_config", show=False)
|
||||
print(e)
|
||||
#print(e)
|
||||
|
||||
except Exception as e:
|
||||
pass
|
||||
@ -243,6 +230,90 @@ class App:
|
||||
self.config["audio"]["device"] = dpg.get_value(sender)
|
||||
self.config.write(open('config.ini', 'w'))
|
||||
|
||||
def pubserverselectone(self, sender, data):
|
||||
if data == False:
|
||||
dpg.configure_item("connectbuttonpubserverselect", show=False)
|
||||
return
|
||||
else:
|
||||
dpg.configure_item("connectbuttonpubserverselect", show=True)
|
||||
|
||||
if self.lsitem == None:
|
||||
self.lsitem = sender
|
||||
|
||||
if self.lsitem != sender:
|
||||
dpg.set_value(self.lsitem, False)
|
||||
self.lsitem = sender
|
||||
|
||||
if dpg.get_item_configuration(self.lsitem)["label"] == "N/A":
|
||||
dpg.configure_item("connectbuttonpubserverselect", show=False)
|
||||
|
||||
|
||||
def pubserverselectsearch(self, serversearch="", limit=10):
|
||||
self.lsitem = None
|
||||
if serversearch == "":
|
||||
dpg.configure_item("pubserverselectstatus", default_value="Please wait...", color=(255, 255, 0))
|
||||
else:
|
||||
dpg.configure_item("pubserverselectstatus", default_value="Searching...", color=(255, 255, 0))
|
||||
|
||||
if self.ccserversecount != 0:
|
||||
# clear list
|
||||
for i in range(self.ccserversecount):
|
||||
dpg.delete_item(f"pubserverid{i}")
|
||||
|
||||
self.ccserversecount = 0
|
||||
|
||||
if serversearch == "":
|
||||
response = requests.get(f"https://thaisdr.damp11113.xyz/api/idrbdir/getallstation?limit={limit}")
|
||||
else:
|
||||
response = requests.get(f"https://thaisdr.damp11113.xyz/api/idrbdir/getallstation?limit={limit}&serversearch={serversearch}")
|
||||
|
||||
# Check if the request was successful (status code 200)
|
||||
if response.status_code == 200:
|
||||
# Parse JSON data
|
||||
allstationdata = response.json()
|
||||
|
||||
# Iterate over each station
|
||||
for server_id, server_details in allstationdata.items():
|
||||
with dpg.table_row(parent="pubserverlist", tag=f"pubserverid{self.ccserversecount}"):
|
||||
if server_details['ServerURL'] == "" or server_details['ServerPort'] == "" or server_details['ServerProtocol'] == "":
|
||||
dpg.add_selectable(label=f"N/A", span_columns=True, disable_popup_close=True, callback=self.pubserverselectone)
|
||||
else:
|
||||
dpg.add_selectable(label=f"{server_details['ServerProtocol']} | {server_details['ServerURL']}:{server_details['ServerPort']}", span_columns=True, disable_popup_close=True, callback=self.pubserverselectone)
|
||||
|
||||
dpg.add_selectable(label=server_details["ServerName"], span_columns=True, disable_popup_close=True, callback=self.pubserverselectone)
|
||||
dpg.add_selectable(label=server_details["ServerDesc"], span_columns=True, disable_popup_close=True, callback=self.pubserverselectone)
|
||||
dpg.add_selectable(label=server_details["ConnectionUser"], span_columns=True, disable_popup_close=True, callback=self.pubserverselectone)
|
||||
|
||||
|
||||
self.ccserversecount += 1
|
||||
|
||||
dpg.configure_item("pubserverselectstatus", default_value=f"Founded {self.ccserversecount} server", color=(0, 255, 0))
|
||||
|
||||
def pubserverselectopen(self, sender, data):
|
||||
dpg.configure_item("pubserverselectwindow", show=True)
|
||||
self.pubserverselectsearch()
|
||||
|
||||
def streambuffer(self, socket):
|
||||
consecutive_above_threshold = 0 # Counter to track consecutive iterations above threshold
|
||||
tolerance_iterations = 5 # Number of consecutive iterations required above threshold
|
||||
while self.working:
|
||||
if self.cprotocol == "TCP":
|
||||
tempdata = b''
|
||||
# data = socket.recv(1580152)
|
||||
while True:
|
||||
part = socket.recv(1024)
|
||||
tempdata += part
|
||||
if len(part) < 1024:
|
||||
# either 0 or end of data
|
||||
break
|
||||
self.buffer.put(tempdata)
|
||||
elif self.cprotocol == "ZeroMQ":
|
||||
self.buffer.put(socket.recv())
|
||||
else:
|
||||
self.buffer.put(b"")
|
||||
|
||||
dpg.configure_item("bufferstatus", default_value=f'Buffer: {self.buffer.qsize()}/{self.buffersize}')
|
||||
|
||||
def stream(self, socket):
|
||||
opus_decoder = None
|
||||
streamoutput = None
|
||||
@ -252,34 +323,41 @@ class App:
|
||||
imcctfrpy = [0] * 2500
|
||||
bytesconunt = 0
|
||||
bytesconunt_frame = 0
|
||||
codecbytesconunt = 0
|
||||
start_time = time.time()
|
||||
evaluation_audio_X = None
|
||||
decodecodec = None
|
||||
|
||||
|
||||
SBT = threading.Thread(target=self.streambuffer, args=(socket,))
|
||||
SBT.start()
|
||||
|
||||
while True:
|
||||
try:
|
||||
if self.working:
|
||||
if self.cprotocol == "TCP":
|
||||
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
|
||||
elif self.cprotocol == "ZeroMQ":
|
||||
data = socket.recv()
|
||||
if self.buffer.not_empty:
|
||||
data = self.buffer.get()
|
||||
else:
|
||||
data = b""
|
||||
continue
|
||||
|
||||
bytesconunt += len(data)
|
||||
|
||||
|
||||
if bytesconunt_frame >= 10:
|
||||
speed_kbps = calculate_speed(start_time, time.time(), bytesconunt)
|
||||
stoptime = time.time()
|
||||
speed_kbps = calculate_speed(start_time, stoptime, bytesconunt)
|
||||
dpg.configure_item("serverstatus", default_value=f'connected {int(speed_kbps)}Kbps ({len(data)})', color=(0, 255, 0))
|
||||
|
||||
codec_kbps = calculate_throughput(start_time, stoptime, codecbytesconunt)
|
||||
|
||||
dpg.configure_item("codecbitratestatus", default_value=f'{self.RDS["ContentInfo"]["Codec"].upper()} {self.RDS["ContentInfo"]["bitrate"] / 1000}/{codec_kbps:.2f} Kbps')
|
||||
|
||||
dpg.configure_item("bufferstatus", default_value=f'Buffer: {self.buffer.qsize()}/{self.buffersize}')
|
||||
|
||||
start_time = time.time()
|
||||
bytesconunt_frame = 0
|
||||
bytesconunt = 0
|
||||
codecbytesconunt = 0
|
||||
|
||||
if len(altfrpy) > 250:
|
||||
altfrpy.pop(0)
|
||||
@ -310,7 +388,9 @@ class App:
|
||||
break
|
||||
|
||||
try:
|
||||
datadecoded = pickle.loads(data)
|
||||
decompressed_data = zlib.decompress(data)
|
||||
|
||||
datadecoded = pickle.loads(decompressed_data)
|
||||
except:
|
||||
pass
|
||||
|
||||
@ -326,25 +406,33 @@ class App:
|
||||
rdshow.start()
|
||||
|
||||
dpg.configure_item("ServerListener", default_value="Listener: " + str(datadecoded["serverinfo"]["Listener"]) + " Users")
|
||||
dpg.configure_item("ServerNamedisp", default_value="Server: " + str(datadecoded["serverinfo"]["RDS"]["ServerName"]))
|
||||
dpg.configure_item("ServerDescdisp", default_value="Description: " + str(datadecoded["serverinfo"]["RDS"]["ServerDesc"]))
|
||||
|
||||
except:
|
||||
pass
|
||||
|
||||
if self.firstrun:
|
||||
decodecodec = datadecoded["channel"][self.readchannel]["RDS"]["ContentInfo"]["Codec"]
|
||||
|
||||
if decodecodec.upper() == "OPUS":
|
||||
opus_decoder = OpusDecoder()
|
||||
opus_decoder.set_channels(self.RDS["ContentInfo"]["channel"])
|
||||
opus_decoder.set_sampling_frequency(self.RDS["ContentInfo"]["samplerates"])
|
||||
|
||||
streamoutput = self.paudio.open(format=pyaudio.paInt16, channels=self.RDS["ContentInfo"]["channel"], rate=self.RDS["ContentInfo"]["samplerates"], output=True, output_device_index=self.device_index_output)
|
||||
evaluation_audio_X = np.fft.fftfreq(1024, 1.0 / self.RDS["ContentInfo"]["samplerates"])[:1024 // 2]
|
||||
|
||||
if len(datadecoded["channel"]) > 1:
|
||||
channel_info = []
|
||||
for i in range(1, len(datadecoded["channel"]) + 1):
|
||||
channel_info.append(f'{i} {"[Encrypt]" if datadecoded["channel"][i]["Encrypt"] else "[No Encrypt]"} {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)
|
||||
dpg.configure_item("logostatus", show=True)
|
||||
|
||||
try:
|
||||
if self.RDS["images"]["logo"]["lazy"] and not self.ccthreadlogorecisworking:
|
||||
if not self.RDS["images"]["logo"]["contents"] == b'' or \
|
||||
@ -357,8 +445,10 @@ class App:
|
||||
else:
|
||||
if not self.RDS["images"]["logo"]["lazy"]:
|
||||
dpg.set_value("station_logo", CV22DPG(
|
||||
cv2.imdecode(np.frombuffer(self.RDS["images"]["logo"], np.uint8),
|
||||
cv2.imdecode(np.frombuffer(self.RDS["images"]["logo"]["contents"], np.uint8),
|
||||
cv2.IMREAD_COLOR)))
|
||||
dpg.configure_item("station_logo_config", show=True)
|
||||
dpg.configure_item("logostatus", show=False)
|
||||
except:
|
||||
dpg.configure_item("station_logo_config", show=False)
|
||||
dpg.configure_item("disconnectbutton", show=True)
|
||||
@ -403,6 +493,8 @@ class App:
|
||||
dpg.configure_item("serverstatus", default_value="Decrypt Error", color=(255, 0, 0))
|
||||
|
||||
if self.ccisdecrypt or not self.ccisencrypt:
|
||||
codecbytesconunt += len(data)
|
||||
|
||||
if decodecodec.upper() == "OPUS":
|
||||
decoded_pcm = opus_decoder.decode(memoryview(bytearray(data)))
|
||||
else: # pcm
|
||||
@ -435,7 +527,9 @@ class App:
|
||||
dpg.set_value('transferateaudiodataoncchannelplot', [tfrpx, adcctfrpy])
|
||||
|
||||
bytesconunt_frame += 1
|
||||
|
||||
else:
|
||||
SBT.join()
|
||||
streamoutput.close()
|
||||
if self.cprotocol == "TCP":
|
||||
socket.close()
|
||||
@ -484,143 +578,9 @@ class App:
|
||||
else:
|
||||
socket.close()
|
||||
self.disconnectserver()
|
||||
raise
|
||||
|
||||
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("Logo not available", tag="logostatus", color=(255, 0, 0), show=False)
|
||||
dpg.add_text("", tag="RDSinfo", show=False)
|
||||
with dpg.child_window(tag="connectservergroup", label="Server", use_internal_label=True, height=130):
|
||||
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", "ZeroMQ", "ZeroMQ (WS)"], 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.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.5 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 damp11113 All rights reserved. (GPLv3)")
|
||||
|
||||
with dpg.window(label="Password Required", tag="requestpasswordpopup", modal=True, no_resize=True, no_close=True, no_move=True, show=False):
|
||||
dpg.add_text("This channel is encrypt! Please enter password for decrypt.")
|
||||
dpg.add_spacer()
|
||||
dpg.add_input_text(label="password", tag="requestpasswordinputpopup")
|
||||
dpg.add_spacer()
|
||||
dpg.add_button(label="confirm", callback=self.submitpassworddecrypt)
|
||||
|
||||
with dpg.window(label="IDRB Evaluation", tag="evaluationwindow", show=False):
|
||||
with dpg.tab_bar():
|
||||
with dpg.tab(label="Audio"):
|
||||
with dpg.plot(label="FFT Spectrum", height=250, width=500):
|
||||
# optionally create legend
|
||||
dpg.add_plot_legend()
|
||||
|
||||
# REQUIRED: create x and y axes
|
||||
dpg.add_plot_axis(dpg.mvXAxis, tag="x_axis_1", no_gridlines=True, label="Frequencies")
|
||||
dpg.add_plot_axis(dpg.mvYAxis, tag="audioL_y_axis", no_gridlines=True)
|
||||
dpg.add_plot_axis(dpg.mvYAxis, tag="audioR_y_axis", no_gridlines=True)
|
||||
|
||||
dpg.set_axis_limits("audioL_y_axis", 0, 2500000)
|
||||
dpg.set_axis_limits("audioR_y_axis", 0, 2500000)
|
||||
|
||||
# series belong to a y axis
|
||||
dpg.add_line_series([], [], label="Left Channel", parent="audioL_y_axis", tag="audioinfoleftplot")
|
||||
dpg.add_line_series([], [], label="Right Channel", parent="audioR_y_axis", tag="audioinforightplot")
|
||||
|
||||
with dpg.tab(label="Network"):
|
||||
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, tag="x_axis", no_gridlines=True)
|
||||
dpg.add_plot_axis(dpg.mvYAxis, tag="y_axis1", no_gridlines=True)
|
||||
dpg.add_plot_axis(dpg.mvYAxis, tag="y_axis2", no_gridlines=True)
|
||||
dpg.add_plot_axis(dpg.mvYAxis, 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="Config", tag="configwindow", show=False, width=500):
|
||||
dpg.add_text("Please restart software when configured")
|
||||
with dpg.tab_bar():
|
||||
with dpg.tab(label="Audio"):
|
||||
dpg.add_combo([], label="Output Device", tag="selectaudiooutputdevicecombo", callback=self.changeaudiodevice)
|
||||
|
||||
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="View"):
|
||||
dpg.add_menu_item(label="Evaluation", callback=lambda: dpg.configure_item("evaluationwindow", show=True))
|
||||
with dpg.menu(label="Settings"):
|
||||
dpg.add_menu_item(label="Config", callback=lambda: dpg.configure_item("configwindow", show=True))
|
||||
dpg.add_spacer()
|
||||
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):
|
||||
if self.config["debug"]["hideconsole"] == "true":
|
||||
ctypes.windll.user32.ShowWindow(ctypes.windll.kernel32.GetConsoleWindow(), 0)
|
||||
@ -628,7 +588,7 @@ class App:
|
||||
ctypes.CDLL("opus.dll")
|
||||
|
||||
dpg.create_context()
|
||||
dpg.create_viewport(title=f'IDRB Client v1.5 Beta', width=1280, height=720, large_icon="IDRBfavicon.ico") # set viewport window
|
||||
dpg.create_viewport(title=f'IDRB Client v1.6.1 Beta', width=1280, height=720, large_icon="IDRBfavicon.ico", clear_color=(43, 45, 48)) # set viewport window
|
||||
dpg.setup_dearpygui()
|
||||
# -------------- add code here --------------
|
||||
noimage_texture_data = []
|
||||
@ -643,9 +603,14 @@ class App:
|
||||
width, height, channels, data = dpg.load_image("IDRBlogo.png")
|
||||
|
||||
dpg.add_static_texture(width=512, height=256, default_value=data, tag="app_logo")
|
||||
dpg.add_static_texture(width=512, height=256, default_value=data, tag="app_logo_background")
|
||||
|
||||
self.window()
|
||||
self.menubar()
|
||||
with dpg.window(no_background=True, no_title_bar=True, no_move=True, no_resize=True, tag="backgroundviewportlogo"):
|
||||
dpg.add_image("app_logo_background")
|
||||
dpg.add_text("ThaiSDR Solutions", pos=(230, 230))
|
||||
|
||||
appcomponent.window(self)
|
||||
appcomponent.menubar(self)
|
||||
|
||||
num_devices = self.paudio.get_device_count()
|
||||
output_devices = []
|
||||
@ -672,16 +637,22 @@ class App:
|
||||
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")
|
||||
try:
|
||||
dpg.fit_axis_data("x_axis")
|
||||
dpg.fit_axis_data("y_axis1")
|
||||
dpg.fit_axis_data("y_axis2")
|
||||
dpg.fit_axis_data("y_axis3")
|
||||
|
||||
dpg.fit_axis_data("x_axis_1")
|
||||
dpg.fit_axis_data("x_axis_1")
|
||||
|
||||
dpg.configure_item("backgroundviewportlogo", pos=(dpg.get_viewport_width() - 550, dpg.get_viewport_height() - 300))
|
||||
except:
|
||||
pass
|
||||
|
||||
def exit(self):
|
||||
dpg.destroy_context()
|
||||
|
||||
|
||||
|
||||
app = App()
|
||||
app.init()
|
||||
|
69
Client/utils.py
Normal file
69
Client/utils.py
Normal file
@ -0,0 +1,69 @@
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Protocol.KDF import scrypt
|
||||
import numpy as np
|
||||
|
||||
def CV22DPG(cv2_array):
|
||||
try:
|
||||
if cv2_array is None or len(cv2_array.shape) < 3:
|
||||
print("Invalid or empty array received.")
|
||||
return None
|
||||
|
||||
if len(cv2_array.shape) == 2:
|
||||
cv2_array = cv2_array[:, :, np.newaxis]
|
||||
|
||||
data = np.flip(cv2_array, 2)
|
||||
data = data.ravel()
|
||||
data = np.asfarray(data, dtype='f')
|
||||
return np.true_divide(data, 255.0)
|
||||
except Exception as e:
|
||||
print("Error in CV22DPG:", e)
|
||||
return None
|
||||
|
||||
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 calculate_throughput(start_time, end_time, total_bytes):
|
||||
duration = end_time - start_time
|
||||
throughput_kbps = (total_bytes * 8) / (duration * 1000)
|
||||
return throughput_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)
|
||||
|
||||
def unpad_message(padded_message):
|
||||
padding_length = padded_message[-1]
|
||||
return padded_message[:-padding_length]
|
||||
|
||||
def decrypt_data(encrypted_message, password, salt, iv):
|
||||
# Derive the key from the password and salt
|
||||
key = scrypt(password, salt, key_len=32, N=2 ** 14, r=8, p=1)
|
||||
|
||||
# Initialize AES cipher in CBC mode
|
||||
cipher = AES.new(key, AES.MODE_CBC, iv)
|
||||
|
||||
# Decrypt the message
|
||||
decrypted_message = cipher.decrypt(encrypted_message)
|
||||
|
||||
# Unpad the decrypted message
|
||||
unpadded_message = unpad_message(decrypted_message)
|
||||
|
||||
return unpadded_message
|
100
Server/Encoder.py
Normal file
100
Server/Encoder.py
Normal file
@ -0,0 +1,100 @@
|
||||
"""
|
||||
This file is part of IDRB Project.
|
||||
|
||||
IDRB Project is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
IDRB Project is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with IDRB Project. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import threading
|
||||
from queue import Queue
|
||||
from pyogg import OpusBufferedEncoder
|
||||
import numpy as np
|
||||
import pyaudio
|
||||
import RDS as _RDS
|
||||
import logging
|
||||
|
||||
EncoderLog = logging.getLogger("Encoder")
|
||||
|
||||
EncoderLog.info("Init audio system...")
|
||||
|
||||
p = pyaudio.PyAudio()
|
||||
|
||||
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=48000, input=True, input_device_index=device_index_input)
|
||||
streaminput2 = p.open(format=pyaudio.paInt16, channels=2, rate=48000, input=True, input_device_index=device_index_input2)
|
||||
|
||||
# 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(_RDS.RDS["ContentInfo"]["samplerates"])
|
||||
encoder.set_channels(_RDS.RDS["ContentInfo"]["channel"])
|
||||
encoder.set_bitrates(_RDS.RDS["ContentInfo"]["bitrate"])
|
||||
encoder.set_frame_size(60)
|
||||
encoder.set_bitrate_mode("VBR")
|
||||
encoder.set_compresion_complex(10)
|
||||
|
||||
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(_RDS.RDS2["ContentInfo"]["samplerates"])
|
||||
encoder2.set_channels(_RDS.RDS2["ContentInfo"]["channel"])
|
||||
encoder2.set_bitrates(_RDS.RDS2["ContentInfo"]["bitrate"])
|
||||
encoder2.set_frame_size(60)
|
||||
|
||||
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())
|
||||
|
||||
#channel2.put(pcm2.tobytes()) # if you use pcm
|
||||
|
||||
def StartEncoder():
|
||||
EncoderLog.info("Starting encoder")
|
||||
audio_thread = threading.Thread(target=encode_audio)
|
||||
audio_thread2 = threading.Thread(target=encode_audio2)
|
||||
|
||||
audio_thread.start()
|
||||
audio_thread2.start()
|
@ -1,9 +1,30 @@
|
||||
"""
|
||||
This file is part of IDRB Project.
|
||||
|
||||
IDRB Project is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
IDRB Project is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with IDRB Project. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import time
|
||||
from datetime import datetime
|
||||
import cv2
|
||||
import numpy as np
|
||||
from damp11113 import scrollTextBySteps
|
||||
import threading
|
||||
import Settings
|
||||
import logging
|
||||
|
||||
RDSLog = logging.getLogger("RDS")
|
||||
|
||||
def encodelogoimage(path, quality=50):
|
||||
image = cv2.resize(cv2.imread(path), (128, 128))
|
||||
@ -28,13 +49,22 @@ def sendimagelazy(data, chunk_size, RDSimage, imagetype, delay=0.1):
|
||||
RDSimage["images"][imagetype]["contents"] = chunk
|
||||
RDSimage["images"][imagetype]["part"]["current"] = i
|
||||
|
||||
print(f"[contentpart={chunk}, currentpart={i}, totalpart={total_chunks}]")
|
||||
#print(f"[contentpart={chunk}, currentpart={i}, totalpart={total_chunks}]")
|
||||
time.sleep(delay)
|
||||
|
||||
RDSimage["images"][imagetype]["contents"] = b''
|
||||
RDSimage["images"][imagetype]["part"]["current"] = 0
|
||||
RDSimage["images"][imagetype]["part"]["total"] = 0
|
||||
|
||||
ServerRDS = {
|
||||
"ServerName": Settings.ServerName,
|
||||
"ServerDesc": Settings.ServerDesc,
|
||||
"Country": Settings.Country,
|
||||
"AS": [ # AS = Alternative Server
|
||||
# can add more server here
|
||||
]
|
||||
}
|
||||
|
||||
RDS = {
|
||||
"PS": "DPRadio",
|
||||
"RT": "Testing internet radio",
|
||||
@ -60,12 +90,12 @@ RDS = {
|
||||
"Compressed": False,
|
||||
"DyPTY": False,
|
||||
"EPG": None,
|
||||
"AS": [ # AS = Alternative Server
|
||||
# can add more server here
|
||||
],
|
||||
"EON": [
|
||||
# can add more here
|
||||
],
|
||||
"AS": [ # AS = Alternative Server
|
||||
# can add more server here
|
||||
],
|
||||
"ContentInfo": {
|
||||
"Codec": "opus",
|
||||
"bitrate": 64000,
|
||||
@ -74,7 +104,7 @@ RDS = {
|
||||
},
|
||||
"images": {
|
||||
"logo": {
|
||||
"lazy": True,
|
||||
"lazy": False,
|
||||
'contents': b'',
|
||||
"part": {
|
||||
"current": 0,
|
||||
@ -109,10 +139,10 @@ RDS2 = {
|
||||
"Compressed": False,
|
||||
"DyPTY": False,
|
||||
"EPG": None,
|
||||
"AS": [ # AS = Alternative Server
|
||||
"EON": [
|
||||
# can add more server here
|
||||
],
|
||||
"EON": [
|
||||
"AS": [ # AS = Alternative Server
|
||||
# can add more server here
|
||||
],
|
||||
"ContentInfo": {
|
||||
@ -126,8 +156,8 @@ RDS2 = {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def update_RDS():
|
||||
RDSLog.info("Starting RDS Users...")
|
||||
global RDS
|
||||
while True:
|
||||
pstext = "DPRadio Testing Broadcasting "
|
||||
@ -136,6 +166,7 @@ def update_RDS():
|
||||
time.sleep(1)
|
||||
|
||||
def update_RDS_time():
|
||||
RDSLog.info("Starting RDS Times...")
|
||||
global RDS
|
||||
while True:
|
||||
RDS["CT"]["Local"] = datetime.now().timestamp()
|
||||
@ -145,6 +176,7 @@ def update_RDS_time():
|
||||
time.sleep(1)
|
||||
|
||||
def update_RDS_images():
|
||||
RDSLog.info("Starting RDS Images...")
|
||||
global RDS
|
||||
while True:
|
||||
sendimagelazy(encodelogoimage(r"C:\Users\sansw\3D Objects\dpstream iptv logo.png", 25), 100, RDS, "logo")
|
||||
@ -154,3 +186,13 @@ def update_RDS_images():
|
||||
sendimagelazy(encodelogoimage(r"IDRBfavicon.jpg", 25), 100, RDS, "logo")
|
||||
time.sleep(10)
|
||||
|
||||
def startRDSThread():
|
||||
RDSLog.info("Starting RDS...")
|
||||
thread = threading.Thread(target=update_RDS)
|
||||
thread2 = threading.Thread(target=update_RDS_time)
|
||||
thread3 = threading.Thread(target=update_RDS_images)
|
||||
|
||||
thread.start()
|
||||
thread2.start()
|
||||
thread3.start()
|
||||
|
||||
|
43
Server/Settings.py
Normal file
43
Server/Settings.py
Normal file
@ -0,0 +1,43 @@
|
||||
"""
|
||||
This file is part of IDRB Project.
|
||||
|
||||
IDRB Project is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
IDRB Project is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with IDRB Project. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
# Server Settings
|
||||
protocol = "ZMQ_WS" # TCP ZMQ ZMQ_WS
|
||||
server_port = ('*', 6980) # if use other protocol ZMQ please use 0.0.0.0
|
||||
compression_level = 9 # 0-9
|
||||
|
||||
# Server Info
|
||||
ServerName = "DPCloudev"
|
||||
ServerDesc = "Testing Server"
|
||||
Country = "TH"
|
||||
|
||||
"""
|
||||
If you want your server to be listed publicly on ThaiSDR Directory, following this steps
|
||||
1. goto dashboard.damp11113.xyz and login or signup your account
|
||||
2. goto click "APIKey"
|
||||
3. click create
|
||||
4. select api type "ThaiSDR Directory"
|
||||
5. click done
|
||||
6. copy api key
|
||||
|
||||
"""
|
||||
public = True
|
||||
#ServerIP = "IDRB.damp11113.xyz" # do not add protocol before ip
|
||||
ServerIP = "localhost"
|
||||
#ServerPort = server_port[1]
|
||||
ServerPort = 6980
|
||||
ThaiSDRkey = "1N5LURICLIN1U9QNYZ4MHJ6FNXISFXFELZAX135CFM0HSD17O2.63E60BE9EEA2339C113A15EB"
|
97
Server/ThaiSDRDir.py
Normal file
97
Server/ThaiSDRDir.py
Normal file
@ -0,0 +1,97 @@
|
||||
"""
|
||||
This file is part of IDRB Project.
|
||||
|
||||
IDRB Project is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
IDRB Project is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with IDRB Project. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import requests
|
||||
import threading
|
||||
import time
|
||||
import Settings
|
||||
import json
|
||||
import logging
|
||||
|
||||
ThaiSDRDirLog = logging.getLogger("ThaiSDRDir")
|
||||
|
||||
content = {}
|
||||
|
||||
protocolclientconvert = {
|
||||
"TCP": "TCP",
|
||||
"ZMQ": "ZeroMQ",
|
||||
"ZMQ_WS": "ZeroMQ (WS)"
|
||||
}
|
||||
|
||||
def mainprocess():
|
||||
while True:
|
||||
currentcontent = content
|
||||
try:
|
||||
Station = {}
|
||||
|
||||
try:
|
||||
for channel_number, channel_info in currentcontent["channel"].items():
|
||||
Station.update(
|
||||
{
|
||||
channel_number: {
|
||||
"StationName": channel_info["Station"],
|
||||
"StationDesc": channel_info["StationDesc"],
|
||||
"StationEncrypted": channel_info["Encrypt"],
|
||||
"Audio": {
|
||||
"AudioChannel": channel_info["RDS"]["ContentInfo"]["channel"],
|
||||
"Codec": channel_info["RDS"]["ContentInfo"]["Codec"].upper(),
|
||||
"Bitrate": channel_info["RDS"]["ContentInfo"]["bitrate"],
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
except:
|
||||
continue
|
||||
|
||||
|
||||
|
||||
Sendcontent = {
|
||||
"ServerName": currentcontent["serverinfo"]["RDS"]["ServerName"],
|
||||
"ServerDesc": currentcontent["serverinfo"]["RDS"]["ServerDesc"],
|
||||
"ConnectionUser": currentcontent["serverinfo"]["Listener"],
|
||||
"ServerURL": Settings.ServerIP,
|
||||
"ServerPort": Settings.ServerPort,
|
||||
"ServerProtocol": protocolclientconvert.get(Settings.protocol.upper(), "Unknown"),
|
||||
"Station": Station
|
||||
}
|
||||
|
||||
jsondata = json.dumps(Sendcontent)
|
||||
|
||||
response = requests.post("https://thaisdr.damp11113.xyz/api/idrbdir/updateserver", data=jsondata, headers={"apikey": Settings.ThaiSDRkey, "Content-Type": "application/json"})
|
||||
|
||||
if response.status_code == 200:
|
||||
ThaiSDRDirLog.info("Update succeeded!")
|
||||
else:
|
||||
response_json = response.json()
|
||||
ThaiSDRDirLog.error(f"Update failed, your server cannot be listed on ThaiSDR Directory! Reason: {response_json['message']}")
|
||||
|
||||
time.sleep(300)
|
||||
except Exception as e:
|
||||
ThaiSDRDirLog.error(f"ThaiSDR Directory Error: {str(e)}")
|
||||
time.sleep(5)
|
||||
continue
|
||||
|
||||
def run():
|
||||
if Settings.protocol != None and Settings.ThaiSDRkey != "":
|
||||
ThaiSDRDirLog.info("server is soon getting listed on ThaiSDR Directory")
|
||||
TDIR = threading.Thread(target=mainprocess)
|
||||
TDIR.start()
|
||||
else:
|
||||
if Settings.ThaiSDRkey == "":
|
||||
ThaiSDRDirLog.error("ThaiSDR Directory can't work without APIKey.")
|
||||
else:
|
||||
ThaiSDRDirLog.error("ThaiSDR Directory can't work without protocol.")
|
BIN
Server/__pycache__/Encoder.cpython-310.pyc
Normal file
BIN
Server/__pycache__/Encoder.cpython-310.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
Server/__pycache__/Settings.cpython-310.pyc
Normal file
BIN
Server/__pycache__/Settings.cpython-310.pyc
Normal file
Binary file not shown.
BIN
Server/__pycache__/ThaiSDRDir.cpython-310.pyc
Normal file
BIN
Server/__pycache__/ThaiSDRDir.cpython-310.pyc
Normal file
Binary file not shown.
BIN
Server/__pycache__/utils.cpython-310.pyc
Normal file
BIN
Server/__pycache__/utils.cpython-310.pyc
Normal file
Binary file not shown.
206
Server/server.py
206
Server/server.py
@ -1,54 +1,45 @@
|
||||
"""
|
||||
This file is part of IDRB Project.
|
||||
|
||||
IDRB Project is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
IDRB Project is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with IDRB Project. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import socket
|
||||
import time
|
||||
import pyaudio
|
||||
from pyogg import OpusBufferedEncoder
|
||||
import numpy as np
|
||||
import pickle
|
||||
import threading
|
||||
import RDS as _RDS
|
||||
from queue import Queue
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Protocol.KDF import scrypt
|
||||
from Crypto.Random import get_random_bytes
|
||||
import zmq
|
||||
import logging
|
||||
import zlib
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||
logging.basicConfig(level=logging.INFO, format='[%(asctime)s] [%(name)s] [%(levelname)s] %(message)s')
|
||||
ServerLog = logging.getLogger("IDRBServer")
|
||||
|
||||
def pad_message(message_bytes):
|
||||
block_size = AES.block_size
|
||||
padding_length = block_size - (len(message_bytes) % block_size)
|
||||
padding = bytes([padding_length] * padding_length)
|
||||
return message_bytes + padding
|
||||
ServerLog.info("Server is starting...")
|
||||
|
||||
def encrypt_data(message_bytes, password):
|
||||
# Derive a key from the password
|
||||
salt = get_random_bytes(50)
|
||||
key = scrypt(password, salt, key_len=32, N=2 ** 14, r=8, p=1)
|
||||
import ThaiSDRDir
|
||||
import RDS as _RDS
|
||||
import Encoder
|
||||
import utils
|
||||
import Settings
|
||||
|
||||
# Generate an IV (Initialization Vector)
|
||||
iv = get_random_bytes(AES.block_size)
|
||||
|
||||
# Pad the message
|
||||
padded_message = pad_message(message_bytes)
|
||||
|
||||
# Initialize AES cipher in CBC mode
|
||||
cipher = AES.new(key, AES.MODE_CBC, iv)
|
||||
|
||||
# Encrypt the padded message
|
||||
encrypted_message = cipher.encrypt(padded_message)
|
||||
|
||||
# Return the encrypted message, salt, and IV (for decryption)
|
||||
return encrypted_message, salt, iv
|
||||
|
||||
|
||||
protocol = "ZMQ_WS"
|
||||
server_port = ('*', 6980)
|
||||
protocol = Settings.protocol
|
||||
server_port = Settings.server_port
|
||||
public = Settings.public
|
||||
|
||||
if protocol == "TCP":
|
||||
# create tcp
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
# wait for connection
|
||||
s.bind(server_port)
|
||||
s.listen(1)
|
||||
elif protocol == "ZMQ":
|
||||
@ -64,91 +55,12 @@ else:
|
||||
print(f"{protocol} not supported")
|
||||
exit()
|
||||
|
||||
p = pyaudio.PyAudio()
|
||||
ServerLog.info('starting RDS')
|
||||
_RDS.startRDSThread()
|
||||
|
||||
sample_rate = 48000
|
||||
bytes_per_sample = p.get_sample_size(pyaudio.paInt16)
|
||||
ServerLog.info('starting audio encoding')
|
||||
Encoder.StartEncoder()
|
||||
|
||||
logging.info('init audio device')
|
||||
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)
|
||||
|
||||
logging.info('starting RDS')
|
||||
thread = threading.Thread(target=_RDS.update_RDS)
|
||||
thread.start()
|
||||
|
||||
thread2 = threading.Thread(target=_RDS.update_RDS_time)
|
||||
thread2.start()
|
||||
|
||||
thread4 = threading.Thread(target=_RDS.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(_RDS.RDS["ContentInfo"]["channel"])
|
||||
encoder.set_bitrates(_RDS.RDS["ContentInfo"]["bitrate"])
|
||||
encoder.set_frame_size(60)
|
||||
|
||||
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(_RDS.RDS2["ContentInfo"]["channel"])
|
||||
encoder2.set_bitrates(_RDS.RDS2["ContentInfo"]["bitrate"])
|
||||
encoder2.set_frame_size(60)
|
||||
|
||||
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())
|
||||
|
||||
#channel2.put(pcm2.tobytes()) # if you use pcm
|
||||
|
||||
logging.info('starting audio encoding')
|
||||
|
||||
audio_thread = threading.Thread(target=encode_audio)
|
||||
audio_thread.start()
|
||||
|
||||
audio_thread2 = threading.Thread(target=encode_audio2)
|
||||
audio_thread2.start()
|
||||
|
||||
connectionlist = []
|
||||
|
||||
if protocol == "TCP":
|
||||
connected_users = 0
|
||||
@ -157,49 +69,31 @@ elif protocol == "ZMQ":
|
||||
else:
|
||||
print(f"{protocol} not supported")
|
||||
exit()
|
||||
|
||||
timestart = time.time()
|
||||
|
||||
connectionlist = []
|
||||
first = True
|
||||
|
||||
firstcontent = {
|
||||
"first": True,
|
||||
"mainchannel": 1,
|
||||
"channel": {
|
||||
1: {
|
||||
"Station": "DPRadio+",
|
||||
"RDS": _RDS.RDS
|
||||
},
|
||||
2: {
|
||||
"Station": "DPTest",
|
||||
"RDS": _RDS.RDS2
|
||||
}
|
||||
},
|
||||
"serverinfo": {
|
||||
"Listener": connected_users,
|
||||
"Country": "TH",
|
||||
"Startat": timestart
|
||||
}
|
||||
}
|
||||
|
||||
def handle_client():
|
||||
global connected_users, first
|
||||
try:
|
||||
while True:
|
||||
# Get the encoded audio from the buffer
|
||||
ENchannel1 = channel1.get()
|
||||
ENchannel1 = Encoder.channel1.get()
|
||||
|
||||
# encrypt data
|
||||
#ENC1encrypted, ENC1salt, ENC1iv = encrypt_data(ENchannel1, "password")
|
||||
#ENC1encrypted, ENC1salt, ENC1iv = utils.encrypt_data(ENchannel1, "password")
|
||||
|
||||
#ENchannel1 = ENC1encrypted + b'|||||' + ENC1salt + b'|||||' + ENC1iv
|
||||
|
||||
ENchannel2 = channel2.get()
|
||||
ENchannel2 = Encoder.channel2.get()
|
||||
content = {
|
||||
"first": False,
|
||||
"mainchannel": 1,
|
||||
"channel": {
|
||||
1: {
|
||||
"Station": "DPRadio+",
|
||||
"StationDesc": "The best station in the world!",
|
||||
"Encrypt": b'|||||' in ENchannel1, # check if encrypt
|
||||
"ContentSize": len(ENchannel1),
|
||||
"Content": ENchannel1,
|
||||
@ -207,6 +101,7 @@ def handle_client():
|
||||
},
|
||||
2: {
|
||||
"Station": "DPTest",
|
||||
"StationDesc": "",
|
||||
"Encrypt": b'|||||' in ENchannel2,
|
||||
"ContentSize": len(ENchannel2),
|
||||
"Content": ENchannel2,
|
||||
@ -215,15 +110,19 @@ def handle_client():
|
||||
},
|
||||
"serverinfo": {
|
||||
"Listener": connected_users,
|
||||
"Country": "TH",
|
||||
"Startat": timestart
|
||||
"Startat": timestart,
|
||||
"RDS": _RDS.ServerRDS
|
||||
}
|
||||
}
|
||||
ThaiSDRDir.content = content
|
||||
|
||||
compressedcontent = zlib.compress(pickle.dumps(content), level=Settings.compression_level)
|
||||
|
||||
#connection.sendall(pickle.dumps(content))
|
||||
if protocol == "TCP":
|
||||
for i in connectionlist:
|
||||
try:
|
||||
i.sendall(pickle.dumps(content))
|
||||
i.sendall(compressedcontent)
|
||||
except Exception as e:
|
||||
#print(f'Error sending data to {i.getpeername()}: {e}')
|
||||
# Remove disconnected client from the list
|
||||
@ -234,20 +133,23 @@ def handle_client():
|
||||
# check if no user
|
||||
if not connectionlist:
|
||||
first = True
|
||||
ServerLog.info('server is standby now')
|
||||
break
|
||||
elif protocol == "ZMQ":
|
||||
s.send(pickle.dumps(content))
|
||||
s.send(compressedcontent)
|
||||
except Exception as e:
|
||||
print(f'Error: {e}')
|
||||
|
||||
# Your main server logic using threading for handling connections
|
||||
if __name__ == "__main__":
|
||||
logging.info('server is running')
|
||||
if public:
|
||||
ServerLog.info('starting ThaiSDR Directory')
|
||||
ThaiSDRDir.run()
|
||||
ServerLog.info('server is running')
|
||||
if protocol == "TCP":
|
||||
while True:
|
||||
print("Waiting for a connection...")
|
||||
connection, client_address = s.accept()
|
||||
print(f"Connected to {client_address}")
|
||||
ServerLog.info(f'{client_address} is connected')
|
||||
|
||||
connectionlist.append(connection)
|
||||
connected_users += 1
|
||||
|
46
Server/utils.py
Normal file
46
Server/utils.py
Normal file
@ -0,0 +1,46 @@
|
||||
"""
|
||||
This file is part of IDRB Project.
|
||||
|
||||
IDRB Project is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
IDRB Project is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with IDRB Project. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Protocol.KDF import scrypt
|
||||
from Crypto.Random import get_random_bytes
|
||||
|
||||
def pad_message(message_bytes):
|
||||
block_size = AES.block_size
|
||||
padding_length = block_size - (len(message_bytes) % block_size)
|
||||
padding = bytes([padding_length] * padding_length)
|
||||
return message_bytes + padding
|
||||
|
||||
def encrypt_data(message_bytes, password):
|
||||
# Derive a key from the password
|
||||
salt = get_random_bytes(50)
|
||||
key = scrypt(password, salt, key_len=32, N=2 ** 14, r=8, p=1)
|
||||
|
||||
# Generate an IV (Initialization Vector)
|
||||
iv = get_random_bytes(AES.block_size)
|
||||
|
||||
# Pad the message
|
||||
padded_message = pad_message(message_bytes)
|
||||
|
||||
# Initialize AES cipher in CBC mode
|
||||
cipher = AES.new(key, AES.MODE_CBC, iv)
|
||||
|
||||
# Encrypt the padded message
|
||||
encrypted_message = cipher.encrypt(padded_message)
|
||||
|
||||
# Return the encrypted message, salt, and IV (for decryption)
|
||||
return encrypted_message, salt, iv
|
Loading…
x
Reference in New Issue
Block a user