From 5720928c282cf58994bda1d0d79e389982f2e9e9 Mon Sep 17 00:00:00 2001 From: damp11113 Date: Sun, 17 Dec 2023 22:40:49 +0700 Subject: [PATCH] new update 1.2 --- IDRBfavicon.ico | Bin 0 -> 3980 bytes IDRBfavicon.jpg | Bin 0 -> 12724 bytes IDRBfavicon.png | Bin 0 -> 5282 bytes IDRBlogo.png | Bin 0 -> 10679 bytes client.py | 388 ++++++++++++++++++++++++++++++++++++++++++++++++ favicon.ico | Bin 0 -> 100033 bytes server.py | 295 ++++++++++++++++++++++++++++++++++++ 7 files changed, 683 insertions(+) create mode 100644 IDRBfavicon.ico create mode 100644 IDRBfavicon.jpg create mode 100644 IDRBfavicon.png create mode 100644 IDRBlogo.png create mode 100644 client.py create mode 100644 favicon.ico create mode 100644 server.py diff --git a/IDRBfavicon.ico b/IDRBfavicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..a104ca77d195f54b6c92eff90875141a958e44b5 GIT binary patch literal 3980 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 literal 0 HcmV?d00001 diff --git a/IDRBfavicon.png b/IDRBfavicon.png new file mode 100644 index 0000000000000000000000000000000000000000..ae59b7bc4962e233409a206bd119dc04e4a91a90 GIT binary patch literal 5282 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 literal 0 HcmV?d00001 diff --git a/IDRBlogo.png b/IDRBlogo.png new file mode 100644 index 0000000000000000000000000000000000000000..938a5e9e23ac70efc698454422d4978e29abfd57 GIT binary patch literal 10679 zcmc(FWmr_vxAy^*5C;ULOFE>KZX_K^MUd_g0Y|z+rAruEQpBJcVqoYP36T&bh9RY6 z7(ifvVc_EZ-}`=izrD}9znt@&XT?5y{npxR@3rEM40J#@SZ)9S00KYMd;$Q(SFc0> zIqB8oTk41_0NeqaK zR2V$A$vUFkRDneZwAEZ`{r@`VCi~e1=klR0SyHj3Y0%H$V9W7V=zRI#XdDjbYIFk_ zFtoMBadt|sKNHQA>?}3mtKxTa1M7k<)W_dI*V3h4#8JK%-lPZ9@hbCeg29FlTMWku zP@CPO@7IBhw;Vv{Uh#t3xB#Z!EPg#$dtCGw(al1d`G6V#8nQH~0Z`;p^h>-xpQq=g zbwi?akmXktov`Ox8+H)@uL64zQ0ns(fe&A(Z_VnIs z>MQ^-#sZ4mbyqsUH980T(PS}Sl%qjJ|NEVS4&V1kq4E#pyq7|mI9B(}PnQ4Y_Ayjxbt-!2Vp@r+Bw?;7x<+ukSL&r*l zv&sMmXG-a|>g(JV%}8&uh_r_EQpO=Uxqp1gq-sHvslGvA>kF&}?zD*4T4)nSTjMAX z&I6ydfgCf^+JvM`;iu`~X>~&k;9#u2I2n_0=;x?YkwuSjtVM`5z1z<#TghEP+l! zpoJq#Ju?l>r972nrR334#Lodh&0Pve*r%s_l`ibNj}_X4&{roO@fJ8}Tq{#IMgDq{ z^5CBk9$LE=Q9gpqzTmH&ElaCRX7nK{dAQhvXO62&+}A6J5mz2t)WSDs#BB201SsLx_xt2O;Y0ujJh;XvIb z&WB+{V4qqlUdFx|`h9E5CkTAkoV0^pgx4#Oj+4jDADqss-w^vZF{@lQlmmMBRWho! zpHbxVgPphM*T4&hJ8J*jsqa?g!n!CDk~GM00N)S^q(nI(MlI%FQ}eNHYbB0yKEc_z z%we@=2DZfg=aCEy#0b&z6v-&UBuOE_!cAKgrr?tmF z^C%OPAz%=>e0#Hq;o{^w(vidIl&Y<;?u1xFM*Y9R zAVT6X+MPyy=|jhP@lmvXkv>=X%G8LS@3IO@a;o*g2NpfMlt6KbHJuD8OMU@k+Kiw*nlZC3#1jR#gSPFCDp|+{q32 zYrxkWt99Uq_~vGu-xe3=1%_rFtvfzQgI|3C0WWiZhriT(v~tVU-U}=)dl@Nz72YhU zd;pi-U%#&&r|QRI2V7(cIyGwf0TJ$;%lldhuIW{HU6ilq}x+N*Ux z%A0+8)STy7WR3}E=diW_)cRMaNhH+A1Hp;%#}tqcL*U@PplXwy103;pNql?v zSmk`ZHr_`|>0HBX4pd3w{dJY?d4MV5Df+?)9YJE)Q^19^S&*dZB}+twkpNFMQ$Ue( z1!%*>Bo~~I?o{3%Q?R>vRvRnj!&Nk-AJvl*;{Ipfw08SY;rrG^Z^!}wSZ7$0*p0-= z4yKA?gVt5Jm`4gn!bsmc(UEDmw71fxNM^L|oP?lG9WZWF+xu-375!`emsgfcy<*FY z8;lT;YvOc<5ge7B9+MghEhb0E5&R1qF>Nq|y6xk=$?Zyt?l=Ho&$=SlT|W+Z*wT+M zB+sgE^C^nS;|){Vw`W`CQpD<~;l(*=csDpIY^kvX62KR10E(oMp_^Y07{T@1+pbRo+m`$4H%dG~286%lAmD>i>6W zzrEL4xjTEou+DN0;&$n}ynluE+)^E0LPIiFET2cKj)!Uug{i?iRHe$y1mp;_)LsNc zQVo3~s)hQ5Kn|6$&A8GyGWXifG7Hh;5qmEn}e<-j&J7~8ld=lt36ydj_#yL$D1-w}k~6ix6pio;U?8?{y}Kq2#*xB|~X)rQEBeu~va_c~wIrc(c_$%bXP z`wPuNz_7i|@n~LblyD93eef7kRi;Fp^elhfViG~Xj9}WA{FM9KXJKK2xX&q=_M?LY zQBIHk%lT2ZEJV)OTbL6A-)S+E5f$ZZx#gN#g+dj8C>S`Cy&A(w&_ls^B;@f-M zUC^*@LSB64Jbxel6i37QD5TwCYN9d$rw#vcRzS1>+rx>3_7^sclK2-r`BV4QfC>~T zU$D9-11NPzJK6sBkLJioF6f6$_4%LsM{p^xnrgNOZiTtD*4%@Ef(P|T9OQ0Jk%xaH z)|qo0x_|lPZ7jIpheOdTRYs0KE$Tdrrg*DN`;4D0?|-TMrS=M2!cK(Alm;iyeH3c9 z@p8^XPrh%$@5g*rMg^T8vGv=05AwkFOXj{*UWXa_PcGAsEQio4W_pGW;>uWyHnVPT z#nPl(`2+-muv2zLw`dL(>EFx#(#7#hk&mPuB3fMJhwZ7l9S$fQLzc-1MZJ-Kpm+F&o|m0NT|!r(A}YR2aEg5B z;M6s53=iH?yvy}sn_|)k|HS&DV|5JJ>o6Z#nw`hn9qy4*e&0Nq56+!KwBtHTn0Q2u zR~GoJ7p->Vm7Dm0s7?86&8*Xz&W)4qO zfP;pbUixx$TFcJtqgRJj7q1qCq&LL&amrZZxhNR-tI7)efozc$tXi?H*>Ho^5x0gn zX^*lZCJM^R5nWL_{yTzs=Jhfn%LiYVvW?j~noRuidda#x{vd3cC3C?wT$_*mxx8wd zhL_;NJ6=Ld!a~KB6Nt;ft-B`C@S#?faMAVSbrdp{TiLDu$gCGZa7^*+ZMnG4jteI3r_qtcKq#`lMa%p03Hz4q1J}BEYqWE%M={RPj8a}44IvJF* zl;r1sNmlggWkiu9TC1RQy8U)zbppIDcTMAZ<%2>MvCVgf*PPrSmRXj}h$!Ol5d=Yp z4N_ZUDLCP;u`uFFZEWa4xXZ`xIp>YO);nLG@_DS!!5TllJL* z8qkKnhi9++g6Qgt1ldoNA1Z8{nwhGiTo-isJnbu-6WVmXpzhCpxuN7z<+*(GXJA_W zeSa^G%i$;^INsv?B4;eURVTk@w4^I&WLe+NH|y^STGL7=*Fl4*fA-z|VPQ92Fyo{5 zrgj6rTz5c1jIbq_cSAej(m!0ObA3eHJkpk4$YS>ze`DsQDj~`>T62sL@t1~{edY*p z`To*=>+0k);@=U)^LQE+amA@5sdtqW-cC{Zp|u4lq5Z!GBah6swxOyO}QdDShiK zHBG-`c9j2&!E2!~(Tt^_WCW)@$+M^a0OwM1v-%6IOWm%i;c9T}r}nDWc#6l(3P)X= z^-^7hKC_ET{SG6`hToriyJ@hq0-!`oWGDB9-JC=F%{fzCY?4UrWcT$W@<6(fPaDx8 zk`bB*^`=E+lINBuM2OqF?Qu%ytny8&69|Hp441g8BG{teyhAb{9np6ck=wJ3TZej> z)~RHud8IGZbI#B5zE3@rjxs{&pyUqQ{f9h{5Tp$xj#F|53q(_gvd z7uG|eVYnD!veV+j81o1hA87x=^pJL6ef2l6=dTZ0WW&E_L4o`}OYW)(9HNj)*c^<| zB>D3x5gO2)E0JD>@Ik$7K92pIxMtd`1S`w&f^Uh*GP_}>IQz&F`+b3S%kE>G1cp8R zDitwxB}L>`ZvR^5Wkut1MlXujZij6@#J^p-Ct5<(Q09X|J#BrrQ3#eW$YVVYDlZM1Gxk5b#5O}eOy zPtpvA zWOyBFzu)f;&|l+;npaQ5cRCNXv~3@Bx>(zA%;RH5D1s!z^IpLr^6C0q1+wgcy|fH$ zH@>xY2TwSNaHEh+w9T(F=g!kOIy`){peymX zoz?uA6p+80l27i_%%>IaRXQA5B!le0^PSTogPW4~(Z(V?RzJ)_10ChK96E<6AF8|^ z!F#Fq^|8~9JN5P}9!@O|rV$sVr5(|Si0fwhRXc#mw0)gpmC)-m9>+}f<8z%LS~&rv zPUk&BUCHe8b%@Mxmeeji3bPQ0-wkumd5I&N`1>x(is&P^x+c#^#sHy?4N@KdGc9`w zgq);7$O0U2S<9ML&(3M_9#EETh~3LKP4_o0l)eh(Eod+l>>;BZIk+~BY48OOSE3FR zqn>=Z#-M)s9LNxmk@U!VFzyWj23ii=y@;F~XJ8+E-mX^)*hM7L-)hk>(wDQLDQxuC zgMky>`Te`GFNQAK_-Dc%_z|R+0$k<6+um4RS>c0g<|(pMNbj`lv{lVbVJW~S$?W~) ztEl9}{`W5-kM0U|+@mjhT&O`eHD6P7%W+W^(@{M0gxDPOqg~v3XJ%hk*f-hazK`aC zJj#c6Xny=kf|I>Vd)XV-3(N7o_;t>rRO*)J#-be|2@)QAUj`^dr|6EGWJYvP+UMa@ zcWX!-TJwyds`jrq>u=`S;9L*SPa?~ufHP4=YrX2zw5*}abiRy=1k*+o{vPu@KPs#^ z5yqh&Q}51G;{?tVczb{(Jq$_-d6FQ?L%pT{YU-Ye8!*sk)Th16`UY)ftY)Y-5%yz# zas6Fe3hWx8tRUm2YaT9mHPZt#(A!UXt7ow%aph&&k&k}$k;NxcwO7GTp&{lxiwQ`1 zA`^I7={D_~sC>txcfnu^klq&RKqPz2=$*dV+inbP8jqF?n~&cWUn6OxNU!xyR6-g< z%dw&|{OMZWBnAumi{7i9P~2K)2EWs^nK;Xeqx^;zUW&tLg0%21elZE08*TQ)Lw+Xo ztN;XM_M;*Vk27EETF^1r_zE@0R1pFxPw19SDzHs29`ih36;ErtudDuB|E6c`z*&$e zmm|9c-d?liG^Ud7lL?II4r}bZ49vMP-5FiJ%~?BvIdkZ*ayn`iUA|~!L<6Z+f)8Mm zdS9ouX?v6$we$E_vJrAPJ2w+gU_gEOvo^2gPvR0bwp=X<9Z9tK4hCup7Fl@K%5=t5 z;A7mA`y99}e*BuwDtBAUWXxhRnp4t>&8CoQkN~Z?x`7#i9ws9llj?ZY0w(XYpjwU> zRTa1r%z9-j!yiYC@>jB7l6P`OTPSi3jQ@^eqdqA%w0LopvpU}W7|Jo+Zcs4~q^{N) zy4{nle5qUFP_OKVet0*6MuUaSti2>)mNV$nUDFfA^t@cUzrj`<%rfkp4SC=CI9ek=ro)5V`~GvWmriQQjDFFy2|;~S_l~dwh&lSGdGm0h@`Ki z@oUk4mAv03DqVy0DHQ_&=1J$)bix;(AB|v(vTLC;^UYa$BNSF`S_}Pq`BnMdSSP7)Z5b!~F!$2^3)baRiU%!D zA6)7|vX*swC6d8iLTh!Gs(p1rV#03s@a)z<`KwaInje0^IkyV8pW@yiJe9sg&`+OM)!7RjITx`d$R>w8SVbtWpT+RFtA|A8#-uRes!)uV36&RIX#|H~HGZTJi3) z!0(U~J%fSAoIJ<#zxO5QOs!00SX>I$0I86<9ybmNJ>fp=ghbeyyTA3@+!sf6nGiaM~h(txk{wknr>Sf1sFrtIJ3-IzKCkD z+eZxH%D-uu_tV;0n`Tp1Ec14C>%kUpa#dl(`P%PnLV_3?C6>+%(i>8puZ6bpPepqp zsfcbXmzCdeY4SQpc@i(Yk@@LCllL4RLy+}XJ~gswVmcDX>3IgsC&DYwZdlsL|CaD} zK6*k@yyRuMoy#KJBz#=9EiQfo=f6T^>{83PX?a{$FS~l zs~s+z89lhh#%93zoj*nVL zJ}}v(Sxi*?C@fCcYIJrNG~J=>W}S_FqU4!I9I(7p^l7ihFZ-KS*~*aAmB7qb7*pPzXnJT@@8!phzK4kGD(8+?&_H zu4Zql73Agbq3x3%lXw&V@ErP(o!U@ZnkjO};_*EZ75jD~OA|LJKPd88$?0(usYMsD zmz3ggi)J=#SFu>w<(-J@9EWMaZ-`LyXw~V?g^=|PMHTy;B5_x9B3W4Hl#mxegJgLW z3WqztMSUb1E4=jtht{{OzW0!1F&0j2$I5bbZ#@v%q@v74% z0U|{*JVn&UKcl!{7mq6g_y`u`I-WM#NkLb8w%!OX=4u5V<#(T}^Gi=B$GecQi*F9m z;FB{B{V2uWhAO!XM)W*N^5c2bm;n;q$=O!^bTwr@550)uPoyXxy1c+T8|_uHn0gO& zka8v%4@iY2?ePNybd!`RR_dc>gu3wqmM82Gv26gMMf1mfmv@^lRiwLoYX7n&G z2aTJnEm9xuUT(`|i-YecQw>uu10t=iq z*?*sTioB7@d}{XVLhV~~EMD%d7e)H1x_XWLBN5hdC5Y}d$51}585g1QNxtI#l8OK4j?m@s- zi*{rk*PK^cZ`IO#IMzzb2FW+lFNiNY0zzu11fBrMcs$zJDsL7z3D`z3Lo; zvW3FR-VAMKHQbMF+nN4K5(WSi-v4?5)}4$vp7ttG47v1TXGCY$?F(|o3Kj;a_g<0> zB_0!a>Z-GbKBZjAA$KPjVi!;0+lz<Q>;zdraNb0}rx#?MQC1G(ks*&293>K@Kr1QhA< zZ3LS$^hUXT4ntY-h~$p1bhx6o>S!D2DCNq1~i!A5MN74EIjZ$y7=Z74vcq=vTi9K6h}E08Jy95i!)t{UCUTAN$>Q^v`>Uvj;zGk_)ep zCwIU3B!wD1+T@kd_>~%gv}ENp?`&NUym$Q}VJ>Mf0P7X(gh&}GgXW1m_u+S_c6ZP| zCE5yvICvKAv0DhoQht4snxE?M*QWnCf@5vnT~|SO{MvQ`1$DdtAHmW={`D+7)TpDj ze642IzKLSehn6Ra8Ea9Xi(&pqCnl9h(O{AY#d#z~gaoJV4w#}z>&@WRiXu<qeclN7X*F06sx`D3mn@Y zHcEP1k2{q0W-}XR;tv(_ESWJfltpYjKZMSFkHip@S^CY*vff{F%Jqmnn5Kb~FQq=1 zh}fmq3n=dgp#9@Zc5t3F!Ez(vG*5pvy`6n{qG9;EDF1D$$uqQL7q8kKfdvD$kekcR z#?2qW2DP0g_teF^k|)}BEG*;?N^F`mEnevnXH;!M^h$DOGFVQiNy7d85);@Rw`Ir- zkSCmAN?FeUFe5+q9!0H{9&u;{beol;(^6dbeZ7Wr)nCne)XsITwX@GIV;Y0JUASSl z3Pp!4?!4PGCLF{}Zjjw&k;%*Bw;ofqCcpG%bUbAa|E@AHZ?vf3dkgNnZMr&L+(kNp zzi4wjUyhWv((hhyo zwU*&TUWTGJVKWkZE$X#;Z8;?^>seD{+?#k`d->IMo_BTP$%N{&*Pwo6i)c3G<|=8I zkpXv;im+IvI^y%U1crA3qI{kn0n(%ajn3(o{3R*s4j(dkl{E?eRwp)L%1fmImWKWe zL$%a|xzXCSBF#UaLqkJ4K2??gReWTgDDqDK>+v#||*w+M5r0FuA zB$`)E+#mi>O~yaf*N2Gul8l$V&r@__lfvh&KX$QBudNfh_M7-mYd*S9hTXWI1DL<~ znb?CK+-HD3oxY3%RZPC%>4m9xdm_!8c7re3NB3$+oQeETO{&AmS< zezvsi2TgouW6dF+XsG3jKoh z5RALSJ@6YI!LJvpswA7MnV-VQ>?FeCRUy+~DV@b1UB$Q$!;rburkN)tb2oPPbM3nI zEu4R9BE%l$G`EE%hVsYJ1wS=tP}2`#JPB=6XhL0rOsIrV=h4h!L*{=+$?!dmXrqD0 zS8}Lt1l^r%l$gph&-z}+vSWX;+J@*|If_5+ljcSlAlH*%raM6PyA4MD%n6@8ScD)&p?r?NR`#axw z-B9?a@FurCmD(%QUvfjb$NZ9GUp(9vyD(y`e{!nEiELd(k*|v`nVc$J4bf{~taC`z z6c#5P)`8yy$;adUl5Mu_@4b)TL@hLl`+iM=;XX5}>iBIGK7HmuV#jP*F?&9Al&ZXf zh3wZ$z#NabQc&eYuYwaXlT0g&XT8UNJ)Z+kZ%Mn7iWp-~Ied+4WB{3m%|%lIUz4PO z8GOERw7kDb(KV(l;_y%3I%@>o5*dUdo#Ul;F>0ttt2xiPqEPAs@3&fZNih4@x)0LU zLUkF>ull;oVod-pV-y@}!|Br6#EW?Zy*q|52$E48Kw4JInNasG(>xj6@7>%X6)Q$P^@^kxuBLD61lEn6~vc`Wg>1&PN( zxzf4Gz6mgF)3m}Di}Fi`@iG0nA$Qy&+Q-PqA1wB2)4d42!<@wnOi*20Z;<^eG-&2Aqac3^zsYfj_sgsS;bA1O9EMBiy$l`E0rD&1O$zcjFX5rVQ zYIJ$NfzGm=pWo_fYC-~s)DJtHn9mApM|raH@^jDIm!F2jy-R3zxJrPALf{Q`3oK*e zXxz3IUPalV-Su4>i0=P+E8^JS|Emf$9q`#Dx8#R!Cm|(T|0uy)2Ab9Cc5nU**!`f7 literal 0 HcmV?d00001 diff --git a/client.py b/client.py new file mode 100644 index 0000000..cc39a8d --- /dev/null +++ b/client.py @@ -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() \ No newline at end of file diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..7a13ba9105fbe75ce9a52db9555da66592f41d13 GIT binary patch literal 100033 zcmeI5&x;&I6vumQpyYe(F?X)zh7-nsMKIz5ep%n>*Dhw{Cp=((*)AeZQttZQ0Ua{_y^) zTC>|(j$K+Fsj80;cd8Nh>GyYFRaNaj+Ns`}+_Q82+B?^}U9aEu{l07a6MJ7h z^UJqeH+(TZGXB-Rxf|~IsJHro>CP?t<{td};4AN~J9YbSpPu^WjoE|mymZ%RKc6^v za`w!v&T`3g=#lw%|GeSX*&{D3S?Rv^zU+M6+xhwj&zwCs``BR7-1+LczxLcV?GIzg zG=HS`(1rK^d3$nl;~z&JpZ?KU^%i>1KYU^Bfk(f4ZM8q#CDVzUc6aN$m#nzL*SVMH zHXeTS?EV+m-}{p<-nC=T_LCFSM>o6gJ-@r}1GD!;1#)c957M{U1* zi-j~P+6}`N{k8F{B)&5qhK9;M&>#I32GsmX{@40%CHB3oqP@38f2{{l^;7N9pZu>d zK>QQ`3Ip{2^#2M2od0Y5D7l~UU!^5~&j01>|HMD>uP~sDUA(P2spoI1J^7#WKZOA` zew4AFw^d@_+p6s+{6uV^<6TlClR zhsw_|G*tG1{^+kTK>pYGQDS!(wu<)N7X8s*VSw?UHhx!g0P(NIFXKP${(It|_*WQE z03ERqI#dSG8BP_qOP-x4tqsDJ(dxafuTYCJj)?eEHrRzukve)m>U(CPa`%_B$zjXb?Kk=_H zp!UDYxtF(9>n|ODrR!I;_qMA4(Z6i|LI1M;C;l1#m1KbUC;k-%=>OZzkD|S|MSt{H z7-0NgmLKB3tp7`n{e0c%kNye+O78czIw+Iw47Kh+-n(O+Re?LXUY zPyB1~gZ_;F+spv*Py8zksN=WN_HB;cVYFTKzxw_OwSLup7(Q(CK2-mqf4ln+^l!I* zYVJXQ^j8=l|F@eTMSE|H{^+kTpz^2MtNmx&?N$FN+Iw5Ie$~G1`l;<#{fGYKe}w^+ zKh<81pSIhp{!_Fk|Ci4l=wDugk~swZ(O+SJ{NHYV6z#n&`lG+dfHP;p19$)r-~l{< z2d*a%Y<34b?&hAGJJp!`>!$IGe+T}k?`gtbd(R#I!+$UU126ysFaQHE00S@p126ys zE5ShSIm?TSi*;&t7ZZ}dRjJ32+kdKUzAX0~aE^Z_`K;}ne{=evzm~^x{>|wF|LOO@ z5&y)0ih*2w<>K@Ip8s<1X;|*#&*i6_e{;`R$Q@s7vH&Z z;-C0WF>tNtKQ%to{Fk%OwQsr0Q*8LMT>G>6%Z-te`*82tUG8H{?VsaM=f|XW|6%-_ zvrlQW5@qO*{wW4>{F49D*Z*?wt=nXY3G_~%4yar3mBFFz;Z zKDS_|-0xhJr|$#6Klld*U;qYS00!E}KEBls^Fu7|O7RoB$6{FRkL8V+A7a0|#M&8KPy73QA^z_Z72_nf9)4DapV)QE zm@ctA85@Vh_;0#C*3Q^^+TZU7oBBDneaz3X_4ptEFJ&plNz?T)uA1hb*!E4^({y{< z-@h*w|6kqzcfikZ`HB6`8oM{HX}mYxK6d^UYiDdd?Z3MC!vEci_fEi1S$<-1-87#x z-9A=t?0Qjb``G%X?PUN5n~kGOO(a9=`h|8{%3zopmi{CDafd|rq3 zm-Xkc{co@PG_Bw2Y1?bhdu@GonfeExA87q)^;i9G$AMN4Tc3?rTYqlZ)@PTgfAIMr zEA9WB9y$GOx!qsWWy^!zzva!AH?7ZZf34SV&+F{jotU?$rSUT@2x zUkl@7=bnq_W!4X2eb_GakE(s>2g80tJ)!= 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 \ No newline at end of file