From 1b1458e7f3673c43bcbf12b5e382d6dc186bf926 Mon Sep 17 00:00:00 2001 From: UrloMythus Date: Tue, 10 Jun 2025 22:42:56 +0200 Subject: [PATCH] New version --- .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 175 bytes .../__pycache__/configs.cpython-313.pyc | Bin 0 -> 4023 bytes .../__pycache__/const.cpython-313.pyc | Bin 0 -> 419 bytes .../__pycache__/handlers.cpython-313.pyc | Bin 0 -> 16747 bytes .../__pycache__/main.cpython-313.pyc | Bin 0 -> 7829 bytes .../__pycache__/middleware.cpython-313.pyc | Bin 0 -> 1650 bytes .../__pycache__/mpd_processor.cpython-313.pyc | Bin 0 -> 9964 bytes .../__pycache__/schemas.cpython-313.pyc | Bin 0 -> 6961 bytes mediaflow_proxy/configs.py | 6 +- .../drm/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 992 bytes .../drm/__pycache__/decrypter.cpython-313.pyc | Bin 0 -> 34567 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 186 bytes .../__pycache__/base.cpython-313.pyc | Bin 0 -> 3076 bytes .../__pycache__/dlhd.cpython-313.pyc | Bin 0 -> 13564 bytes .../__pycache__/doodstream.cpython-313.pyc | Bin 0 -> 2349 bytes .../__pycache__/factory.cpython-313.pyc | Bin 0 -> 2236 bytes .../__pycache__/livetv.cpython-313.pyc | Bin 0 -> 12206 bytes .../__pycache__/maxstream.cpython-313.pyc | Bin 0 -> 3630 bytes .../__pycache__/mixdrop.cpython-313.pyc | Bin 0 -> 2601 bytes .../__pycache__/okru.cpython-313.pyc | Bin 0 -> 2088 bytes .../__pycache__/streamtape.cpython-313.pyc | Bin 0 -> 1937 bytes .../__pycache__/supervideo.cpython-313.pyc | Bin 0 -> 2264 bytes .../__pycache__/uqload.cpython-313.pyc | Bin 0 -> 1538 bytes .../__pycache__/vixcloud.cpython-313.pyc | Bin 0 -> 4882 bytes mediaflow_proxy/extractors/dlhd.py | 99 +- mediaflow_proxy/extractors/vixcloud.py | 35 +- mediaflow_proxy/middleware.py | 1 - mediaflow_proxy/mpd_processor.py | 22 +- .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 368 bytes .../__pycache__/extractor.cpython-313.pyc | Bin 0 -> 3645 bytes .../routes/__pycache__/proxy.cpython-313.pyc | Bin 0 -> 6661 bytes .../__pycache__/speedtest.cpython-313.pyc | Bin 0 -> 2067 bytes mediaflow_proxy/routes/speedtest.py | 54 +- mediaflow_proxy/schemas.py | 1 - .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 185 bytes .../__pycache__/models.cpython-313.pyc | Bin 0 -> 2313 bytes .../__pycache__/service.cpython-313.pyc | Bin 0 -> 1875 bytes mediaflow_proxy/speedtest/models.py | 35 +- .../__pycache__/all_debrid.cpython-312.pyc | Bin 3317 -> 0 bytes .../__pycache__/all_debrid.cpython-313.pyc | Bin 0 -> 3388 bytes .../__pycache__/base.cpython-312.pyc | Bin 1534 -> 0 bytes .../__pycache__/base.cpython-313.pyc | Bin 0 -> 1628 bytes .../__pycache__/real_debrid.cpython-312.pyc | Bin 2498 -> 0 bytes .../__pycache__/real_debrid.cpython-313.pyc | Bin 0 -> 2552 bytes mediaflow_proxy/speedtest/service.py | 107 +- mediaflow_proxy/static/index.html | 49 + mediaflow_proxy/static/speedtest.html | 869 ++++-------- mediaflow_proxy/static/speedtest.js | 1239 +++++++++++++++++ .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 181 bytes .../__pycache__/cache_utils.cpython-313.pyc | Bin 0 -> 18492 bytes .../__pycache__/crypto_utils.cpython-313.pyc | Bin 0 -> 8348 bytes .../__pycache__/http_utils.cpython-313.pyc | Bin 0 -> 32309 bytes .../m3u8_processor.cpython-313.pyc | Bin 0 -> 9293 bytes .../__pycache__/mpd_utils.cpython-313.pyc | Bin 0 -> 23940 bytes mediaflow_proxy/utils/cache_utils.py | 29 - mediaflow_proxy/utils/http_utils.py | 72 +- mediaflow_proxy/utils/m3u8_processor.py | 56 +- mediaflow_proxy/utils/mpd_utils.py | 16 + 58 files changed, 1843 insertions(+), 847 deletions(-) create mode 100644 mediaflow_proxy/__pycache__/__init__.cpython-313.pyc create mode 100644 mediaflow_proxy/__pycache__/configs.cpython-313.pyc create mode 100644 mediaflow_proxy/__pycache__/const.cpython-313.pyc create mode 100644 mediaflow_proxy/__pycache__/handlers.cpython-313.pyc create mode 100644 mediaflow_proxy/__pycache__/main.cpython-313.pyc create mode 100644 mediaflow_proxy/__pycache__/middleware.cpython-313.pyc create mode 100644 mediaflow_proxy/__pycache__/mpd_processor.cpython-313.pyc create mode 100644 mediaflow_proxy/__pycache__/schemas.cpython-313.pyc create mode 100644 mediaflow_proxy/drm/__pycache__/__init__.cpython-313.pyc create mode 100644 mediaflow_proxy/drm/__pycache__/decrypter.cpython-313.pyc create mode 100644 mediaflow_proxy/extractors/__pycache__/__init__.cpython-313.pyc create mode 100644 mediaflow_proxy/extractors/__pycache__/base.cpython-313.pyc create mode 100644 mediaflow_proxy/extractors/__pycache__/dlhd.cpython-313.pyc create mode 100644 mediaflow_proxy/extractors/__pycache__/doodstream.cpython-313.pyc create mode 100644 mediaflow_proxy/extractors/__pycache__/factory.cpython-313.pyc create mode 100644 mediaflow_proxy/extractors/__pycache__/livetv.cpython-313.pyc create mode 100644 mediaflow_proxy/extractors/__pycache__/maxstream.cpython-313.pyc create mode 100644 mediaflow_proxy/extractors/__pycache__/mixdrop.cpython-313.pyc create mode 100644 mediaflow_proxy/extractors/__pycache__/okru.cpython-313.pyc create mode 100644 mediaflow_proxy/extractors/__pycache__/streamtape.cpython-313.pyc create mode 100644 mediaflow_proxy/extractors/__pycache__/supervideo.cpython-313.pyc create mode 100644 mediaflow_proxy/extractors/__pycache__/uqload.cpython-313.pyc create mode 100644 mediaflow_proxy/extractors/__pycache__/vixcloud.cpython-313.pyc create mode 100644 mediaflow_proxy/routes/__pycache__/__init__.cpython-313.pyc create mode 100644 mediaflow_proxy/routes/__pycache__/extractor.cpython-313.pyc create mode 100644 mediaflow_proxy/routes/__pycache__/proxy.cpython-313.pyc create mode 100644 mediaflow_proxy/routes/__pycache__/speedtest.cpython-313.pyc create mode 100644 mediaflow_proxy/speedtest/__pycache__/__init__.cpython-313.pyc create mode 100644 mediaflow_proxy/speedtest/__pycache__/models.cpython-313.pyc create mode 100644 mediaflow_proxy/speedtest/__pycache__/service.cpython-313.pyc delete mode 100644 mediaflow_proxy/speedtest/providers/__pycache__/all_debrid.cpython-312.pyc create mode 100644 mediaflow_proxy/speedtest/providers/__pycache__/all_debrid.cpython-313.pyc delete mode 100644 mediaflow_proxy/speedtest/providers/__pycache__/base.cpython-312.pyc create mode 100644 mediaflow_proxy/speedtest/providers/__pycache__/base.cpython-313.pyc delete mode 100644 mediaflow_proxy/speedtest/providers/__pycache__/real_debrid.cpython-312.pyc create mode 100644 mediaflow_proxy/speedtest/providers/__pycache__/real_debrid.cpython-313.pyc create mode 100644 mediaflow_proxy/static/speedtest.js create mode 100644 mediaflow_proxy/utils/__pycache__/__init__.cpython-313.pyc create mode 100644 mediaflow_proxy/utils/__pycache__/cache_utils.cpython-313.pyc create mode 100644 mediaflow_proxy/utils/__pycache__/crypto_utils.cpython-313.pyc create mode 100644 mediaflow_proxy/utils/__pycache__/http_utils.cpython-313.pyc create mode 100644 mediaflow_proxy/utils/__pycache__/m3u8_processor.cpython-313.pyc create mode 100644 mediaflow_proxy/utils/__pycache__/mpd_utils.cpython-313.pyc diff --git a/mediaflow_proxy/__pycache__/__init__.cpython-313.pyc b/mediaflow_proxy/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d1dafe9a42c82434a711db66e04b86c21ae6faf9 GIT binary patch literal 175 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 literal 0 HcmV?d00001 diff --git a/mediaflow_proxy/__pycache__/handlers.cpython-313.pyc b/mediaflow_proxy/__pycache__/handlers.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1835c175bb8773fb2d567d772472836d537e2aab GIT binary patch literal 16747 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 literal 0 HcmV?d00001 diff --git a/mediaflow_proxy/__pycache__/main.cpython-313.pyc b/mediaflow_proxy/__pycache__/main.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d42c45ed53500bc213564905b0a45b38a53299f6 GIT binary patch literal 7829 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 literal 0 HcmV?d00001 diff --git a/mediaflow_proxy/__pycache__/middleware.cpython-313.pyc b/mediaflow_proxy/__pycache__/middleware.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bebb7b06c77562c7204253d08b3eb0e67a3a4fdc GIT binary patch literal 1650 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 PTFd1)`=$C)75aYzgk`G# literal 0 HcmV?d00001 diff --git a/mediaflow_proxy/__pycache__/schemas.cpython-313.pyc b/mediaflow_proxy/__pycache__/schemas.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cd0a210b112705a0d8584476760e6a0911409733 GIT binary patch literal 6961 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 literal 0 HcmV?d00001 diff --git a/mediaflow_proxy/drm/__pycache__/decrypter.cpython-313.pyc b/mediaflow_proxy/drm/__pycache__/decrypter.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..613938ded163f48b2b155fa3ab79636eb51aa2a8 GIT binary patch literal 34567 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 literal 0 HcmV?d00001 diff --git a/mediaflow_proxy/extractors/__pycache__/__init__.cpython-313.pyc b/mediaflow_proxy/extractors/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3756a31e7719c033b9c9d068ccc6c5ba5c0717c5 GIT binary patch literal 186 zcmey&%ge<81Q$F#GC=fW5CH>>P{wB#AY&>+I)f&o-%5reCLr%KNa|Lkenx(7s(xuv zPQJcNesXDUYFY~=b!NY~q)3U@7bQ8CR#K{DT}n!2DRqO0s3^O&rBrRX=AjUQ#hP4F8&gYV zb{X3OiWET+H0U8l3kN;)VDysq=AbR=rT86aaslZJ>ED<$Xn;C`0!u`*!eC3=MI ztnq5%39_i1$1mg;y-wr0<1(Y{uAA;!qoVSjaLFpWp8UfN*J{{C9oX#KD=PJ5$8=rG zt~mhrl$mL`roQI78@Kdw-7;;r9DF&LX}D<*0C1O-2%{QdoJJY1aZJ#7Ce{R1^g1u! z^1t3-ml$b6O>bjTE_4P)j zSw}dj>+dy{-2ie-*DH-O0DZddxQ1($b;EU;b-n4Dj;^y5h|osy zz2fv*W8Iu?vU+3sQls2l2Q8gxt;~!Ku*3AaS+R_2y>WALgEem5o?JI9`xvSF$mwI5 zr>}!nQyaHg2i)yXsH`Ky^bz@+ko~71v)+hw%9wG$;%I*a)60xm5@$J07$WZqOd2Ly zN|Q=4h=!OZm*i3$Lk^-KQRv4@PLEkxOW?V8DNB}ubMaEV6f1Ska(Hf5&^nMl7an6> zT9Tz}qT1=D&`U?|dy)o@&~xRw;W$TLD;C~4={c{9neR1C$JN(Nqhd0r%$*Pk!x#VU z_hEwcpsPp3!b!dm`vxtN`Vn5^V27vuQ6vQ+f{)iiWXxj}Dbl$xUKLU!D!;OsXgVgF zG-}{*ug`ZRgbIZGx^CK)jfQ2rs>slDp3kWVoJsU>q?Xrx6dy;2JgK=+fxLm_6CAT% zWrJ{qgYqj1>%;3HqAlBUAv*g{nRhC}_uvWi#?Q!?$${U#zCSd%H#FJexpRqM2#@5! zmPE$iLTKbbPVUQtd-CAEtnA6ky^+7l-|_=&#p5BQJt@d|FBba2lbGo?nO#0Je=F25osi7kTtk30i6gu|AKQXwjW5fauS zKTlr|_Z2v&kT=M&SBhc*3h9euj1;*dh0y1U5=4Sp46r2>6rk|amcgLSH$LPkIbh$lMFkB}-I zC98f@KphIn7d0UyMvPYovE|jSh^FzYXCk;LN4DrB7q|#EL1tTCkgIgHJK}uFJ;MKg zPn87Hew(jSRotBTfpNoBu3WqJBPC2)<)-DXDJIrxWzDcFP*rPFE9@Ka`OUslj8(&` zo0Yee&2%BicO}B835H3?&fzoylNSqb_tMO;Acdgd`=z<*c%t88yg1(Nw+O#-dOW0~ z7rz{}2sO?Wylot(S8+m#Jn6|rbkT&1yQ0Aq+m*42d(u; zq_Z#N;Ys2UoOb>W(-TO?mfRK3Jsxr5QyJ{$hs@Imj;L2u@%D2{bQ;-1U4VhG73bM9#zpl3vUrTdQDN|G~! z`)PGAtv*VB^B^c-$VtEE0s3@-W--n`h^GL4(4SeH z~FSl(GG* z{E2Xy0)&HI1;QkVkXYE%y}r}25u&H@04BR16%vW3{^MXx{g4x=$^~IR410WnegE>c z74;13ge}9&XV^v=<~_rd_Jj&N#@HzCvp8WDWLQ4gd6+!85XLf_K^%UCVaaA#-Wc`@ zU!UMz(TCeiG~tJw22B3rqa}#q%#ZbFE-nd?7 zXj@-&PjYYL%PLUNSJhX>S2c6oPW^^FRc_c-tL6l4Sb34!bgjBGg~dbn!Jy|03CcknbK!kRNw`a!6n~ QOpejioyD&SEdGW616{ehp#T5? literal 0 HcmV?d00001 diff --git a/mediaflow_proxy/extractors/__pycache__/dlhd.cpython-313.pyc b/mediaflow_proxy/extractors/__pycache__/dlhd.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1e7ffc55790c1adf60e983b46c58500783baf5de GIT binary patch literal 13564 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 I;gi(yzx4ZC1ONa4 literal 0 HcmV?d00001 diff --git a/mediaflow_proxy/extractors/__pycache__/doodstream.cpython-313.pyc b/mediaflow_proxy/extractors/__pycache__/doodstream.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0e8127470461bf1eb0c9f6ce79580f108bea3460 GIT binary patch literal 2349 zcmZ`)QBNC35Z?3M`R)t`0;J%isa+s}3&n;6n$iXm5{CweNXx|%6;4kVdjSXg&e=OR zfD|d}L*=TflvIj9ZKRT?lD9;vRGy>$03#|vFSLr(hl)Ot(kkg&XU{&nP1BWbW_D+1 zZ)WzJ**(AChhP*&&d;{+2>n7Q?tr;7_J)DoK?+isF+_@-Aq>ntW1dl#upzp}j&Y+r z;YYp18$!*f1u5JCr0{XRk1ZZUgc(Nm?$F7&%-CEsm9S)w?LVWL_}sijw1j04yQVCj zBg7zyqHU3SOOw6#K==?`iX-X<@hA*o6%XMQHqQ5YVyKs)Kffwo<%kzt5EO4*j0;NrRFzaeA6aaHuV*Y9g(f zrmC9MjZo;OJr$WXGB^TTjY!l;jPl?2=icPXXrUpJ7q|8YmM-7EeCy&$Poe&BUf6o|$b%E>_3z|`$}i6s z0-c4x@MgnsUfgbMy35{;em(N#$X8<<%^e$!a$eeM3NGEaedCs~^1*8E!Ta~;SFf%G z)~X7P(Y*AeJ-jIf*2TcGye_sq33hA+TQ`H<>%s1=mLr=j{p&6LPX(^dw~IK*_w+Do z?0I?sHNMOIWwt`>AMcNPnI$Ib;g-)J5Fa%&QI2~Q7&;1*?>Ppj%s7_}$bbhx*D@F$ z0H3O6jASlNG!h_iNqB;to=w`|&xAe^Bm%_V z4-bX~*i5JZdXl=51Ehn2zx13Wa5}B3;D&Q!v>;A9*GvR@0wuINn)K0o{t5b#Yu)8M z{2Qy>GX!i`Z03C{4Z8@~gWffEO<5Bk4nCvP-zg!q`#6WlA_-CX2oT$AEzk$bancS} z>cn}G!X*S5#0*oU6WY^@-mf^`k9@!)f`L%FNZCaSsYC^>C`k)8P$_0d*p&dPvzL&@+b692;$H%YT82&6}Pl|=(RKB{X&&?WIez4=GpV+_kQ!HuPAv$ z$HDDetuJJR{$$2*NCTz6PnGA$M5bUMT>wE(K}utG$`Ex4Bs~pjZi|MjXCT9M$;j$C z$Z~>lLVQJ#WnE^Dxi#g0Y}4!Xnpa zj1&4vIH{o%=o~VYDl%u+Qc1>8-BxSGx6G)eh_vpzF_G@?buqP+Pn-yE?8e}@aR_8~ zq;3F&P!oufa4wnuE=+m2zES}-3Kb3qbAx+{O`!k$u9;oO<_$) z)Ie;C?+?7*EJ9x~bonrarZ9_EQ^QCsG{tkMIkJ~2v!8tM8ICol+oj|J--+=UHq8&B z*a=+Bp^aNu+oo&{G`73?lX5eNdfjdaG4{CYL#-yl5?z>x+c*Z=2vAKTqT7z(4Antn zQ|bsN+ig2h6fpoaI>?ueR@lLf9<;;8dg%5#IEbT$>4MXx1#NV&=Q~^N@S7Ey>F(Z2 z$MJ)))8@{`=;u*mE8$4JyGIn7&bT~eX=BKX0dwl_%(FD3t5iHi$1*y#^kd_P#;?_t zSJjoDyw}y$@73ScrQgch!TGg)Z~wu;*=w)M*AHf{zfqT7eEV8$9Aq1P`Z4+$-K(7> zS=$br4z_KQx9v{o_1a9IvF*n_r#+ZaZF|dyC~o@!4njJev28DOZ5y)mKcF(fP97*s z&;lc1#D^IsSmT8p6B#BL{J_{kL`rpF-~yuqp^G>{5XSU$gh50XDRB5ZIBta=kv!Vn zfSpD%Z`-u!vG3YW9E1O`7h@U@m)M_bOuWZLc_>&W(IYCpLvN%y1#I!sVfpM~<^177 zjXiT04vVJ`=Pxp6@$}#Gg;n{f@oesy`{K$=<<+VB!9x92i6T}=%g0qDmyT2E^yQb8 zV?2#k>CwZ_G?$u5aIo@Lg;k)J=CTB)mNoY|Y)1v;6Hg1jx=jYT= zuif%Es^r3yA9|y%!plX@PKj;P;}h@ja*^dJah>4}z{^X{O$$w~1x}Euy9sIg;*^=X zo1{Oo3xu<6dhDo8a#Bv+d5>Q;oXh0=lB{^zqP4JU9N87lh0IASyVG{sY&RlTZKv literal 0 HcmV?d00001 diff --git a/mediaflow_proxy/extractors/__pycache__/livetv.cpython-313.pyc b/mediaflow_proxy/extractors/__pycache__/livetv.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8a843f36b4d23fa6755e854e8b9a0b7742cf79f2 GIT binary patch literal 12206 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 literal 0 HcmV?d00001 diff --git a/mediaflow_proxy/extractors/__pycache__/mixdrop.cpython-313.pyc b/mediaflow_proxy/extractors/__pycache__/mixdrop.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f95bcc1a55f780f2ecdc42017f04c38e010e6d63 GIT binary patch literal 2601 zcmZ`5T})fo`JDUbYl8#Wz%Yd1kPPBD7}5a|`9Y9`rg0Xe_|6p)tamrp2jkSf*E#pP zAt;q9Rhqo@rL>A*dl{7`t$EAd*j}czowP^0RDzvWTckZqys^@0%hUE9+ZR}r9dXb3 ze$M&+zt8Lkg8>Aqa`TO)fQ-=JIN~k9YimCV$77@+4c|hveqxFN7j6kjk%~!)N>NT1 zZ^=o8DoG#pMNt>(N1D`)G>meY)tA;~nmR)L? zu1(po18{~?o0@e2^<8No`=bCnMhl2rLj?^}Q4^@7i3_qOEhr1}f^SsdZj4Hr+>P!@ zRMiyf&-!92YrUOY&QQB}pk`Nviz}TCax|U#VU&2*B$!yDd8Sa}mZ)V44gLJ!wtN8J z{_lbMR`V$o<*~wPd4+8gf#N=p4PGiDQjdn#4V@a2ln&j2yX` z#+s}tBUtlI2se?YE`h9f@MCdSZbp_k??nowxOXot$d5lQrfL=Nzbwx(OG-;;8mvqc zLi0iIo)+-zBRDOkT0HvUsWoZMo}3bRT6{AtztYp7CrNAO4r<4=771CtwDQW^t=?@* zop1HDr9vKbcpl-lA5uMN2ms4J|2+?%k9bg8^)z^A7`+*Agc0NjVf01^LiUq1Zx%H7 zbG~1}2N9AGWPk3LoT>}AL3R`1j$j1}&03olO7(a;4$pM+DAvMIn`+t@Mq>?zr@3Zj z(_Cxn_~8o=(%J_BOgBMYyn+kV6W-0kQ|m~Gr;&>_Zu`4ZxX_BHllsBww$;gtGW5gOtYv+h{>Hd|G#8?b7-x4u0 zggUb;-R~}(89bAS#|F)n_}khf0WLq8H;)r)g& zUY)*!9?Ma5yQ`kLY&|k~790|@#EpqeNQ^LSGSyr%s9rFN4sYFP^gT8=ykr;1FiZ)1 z__}SD3f%l*&7?*#XJv;AB$G21^Y(+GBDI$v4i$`?bx71}#NmT+;|v$-!zodG$o!YH z#3IW@I?3WM&#Xk_N#b(eHjTV9nK-!I2ZlMlu>E@%y@x6T$19EP(Az|BJZ1tVOmT=o%_Sze+;Tphn4G0{sR$gGLZq0I$z^k{ z!-9rm=5l(TxGvNYlPMG%VPc+GOu7dOS@T`^z3?oe{U9n9WRgSjiYpjn5h zlsHgM4wzLiT+l5z=iz$|G@47KU@#dJ6ddN`IdzE3aE|#G!Tmh58&$|9!Vqj(>=NeRKEep?^+CJ1ixUv)KSc|X5*B3W3TcP-N=zKMF{^{wh(1lH0 z4P7i>+llmUN8;5;{L^cdi?=Gbe_WY(s}hMObx?X9%{=BVot!K4o{mus7YU}@*QWkdLRo80Q`l*k5*Y8)` zPHvpsxLRo&EGs*$p*3|?T@Qa8EXyE099bJ$9s2dacK>)4{zBtr-#1e;UzmUT{^$3; zn!2+UPJKCbXY>5}^lEZL{zTo5j(>XNN%G6~@#@r_YB*I^_Y~C8yH0=g;1>_R8c04H z-x^46^(CwA$+G|X;Mr|8QdJ}Cv8sCddEc3xzEj(MBh|i>`&m| zivUw~-7*S9*I7W<3wEZI=Ww&G-!B>Y2B$^W7jx8c^EnteHn4rVp0P~;L?}qg+ne$p zp_gG}9lGwg&=;m|xKOTlOD=JAolXL8A3jto3(9|LYa0igC~QAPf0m+qQjZ+i2=5^{ zo{nyco7$%O+sS`%l=5*^dlK_g?j}788&lkee0m&O!TJsxuQoHh0bo-n!TW*o0a)v+ zx)t2Ay6{@Ts+a3wn4~v>!3Sq;MC^$e4P4t)6TKM@?Y=l=yt CgK(Mv literal 0 HcmV?d00001 diff --git a/mediaflow_proxy/extractors/__pycache__/okru.cpython-313.pyc b/mediaflow_proxy/extractors/__pycache__/okru.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f769a9733c7645bc87e923ee313f6164a826561d GIT binary patch literal 2088 zcmah~U2GIp6uz_bv$NaXZfOf`DYa|c6voPK)rAVN1!*zRLNPmzA!%_k?A~n$W@nar zXMt89h%si3FG?gWPxKLA48HN8?>=^EOzX8#5@QT+#Y)6C&)q+yA@L-0?!D)nd+xdC zp7YIiG#W-gtEW%R&r1mX#us&xYm@D1nA}1-(uFBR_!yWHCIcieDH1Wx*W#2kDHC~8 zAxdQ}O$8@aqQ+4t>O;D`7wO8ZdPuDLBcVA#3qInDX)VB{2|MF6@ojej7SR*fD*AS= z=;&Utz#^RMKCx^U6HNfMMe)h&AjtS0$yEJSxl&tfpMuE;5bi7@f{sW)7l^0_h-9FY zpo@FaC6&m!G^^(waz=#%v-LV!_+qm#Jj$riA<)&0wzo~P2077(`2oAoVBf-<6Secn=N>@>uVB| zD0Xs8vdA1|%H?a7F=Uz<$D-6UDFlGx@%Rm9BU`!#{FA_J?$PZ|O@O=*7_E(VieiVlXH&FV&ZvaY8 zhH$7RJq=l&hZ?*p+!Ckd2y(@lT1A4DnC2%LuOhNq`OX`aL#1XZInY=i%68`M=^ih79i z#y-_h^)Q#Xq4O>2mK{fM`=ntobWi8)2nD??p2eaV0srT(H_7)=*saNoA&!sGs zgYuof3Y(ak$G*jttR33qtVMlH-gT@6$ELmkH4`K_hY5g&$YNROAKSIK50lqZjNmIp z-c$3~%3?wZZ&fYKqR?%Zv02j+o*^dryt+BKFezudStib5pGlXf=Q7#xtSn`bivUVH zd=Z{E(!m+e6;lbO1-Jr+#so+s-CS6?oLRq|#BW1-xgrE7Sq_hta&(49tr2PG>R_$m z*zV^WtS071)9-;jLO0OEuAcR-p>o&I-SM@q;q|uRlDZk&w|xH2h1(bIzVqEjKb%}U zmM+KilJrYcY@>D0lJ=#xd~)UNy(6prqid~WrSXmKm+p+*9$6V%>wdj-X0xqh>FCX) z%g$QcP-$YbqkC!b=Hkl0%7^8?H||ZA`;M)3950>P=!~zvJW}p_vvm65P+~pOQ;zg3 z+vUiChyAZ^^zUEqPnG*q8-uT`4~~`xM;`~J_U0`lMVq&Fp-9VP8MVLm59QbRc=xy} zED7U^w0t57Q%$J8RfOZ8YvnQEU)VIORRi;gXPE8Xt&74iB@4(-~^*|jo&8(L( z%_=;J)F*@^0O74qhJmr&fQ>#>O2)K&pV$|RJ_fji%BUh|20M;vLKJOaOgXL`uRICi z&0k@BLWh7o{S^Hq^*oUR^2nCjA~&zJZ6O%$rS6OO_513#V}JAIpPZ3B_1#*Sa8zN6 zzrf-5S#(UiNK=)Htkz<01DLBAoT$CMM`(hVBjLDJ3FvE7bzi9*^{r~E#>g1xdHfY1 h-Vy~t_!%|*iuV11hJHs!{!%)G<}c1ZK`>Od{|hbX1>OJv literal 0 HcmV?d00001 diff --git a/mediaflow_proxy/extractors/__pycache__/streamtape.cpython-313.pyc b/mediaflow_proxy/extractors/__pycache__/streamtape.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..977a971339c4706981027e35f0dcb9135bc6ead0 GIT binary patch literal 1937 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 R@~5kX^XB^g0(2?a{tFo?C;0#X literal 0 HcmV?d00001 diff --git a/mediaflow_proxy/extractors/__pycache__/uqload.cpython-313.pyc b/mediaflow_proxy/extractors/__pycache__/uqload.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5292dba129a08d6156e266ed6a00951c2c6c3a2d GIT binary patch literal 1538 zcmZux&2Jk;6rc5e+Bi7mL&bsCiyM({OYI;zDveMg(iSxijYL)!hZv1E>q)$=z3aSL zw@pr{2Na15mxziB9Lp`-!ij$&Rt|0liYieF<(4WS#Elty*Fh0a+IhcuZ)V=x_kJ@c zLqjRV-8EHBAr1(Y6EmejHaT3H9( zL~o)zvegM>YgKKApDhH84aw4v1YEWxrrh-0kZZjSjk|&8TM0I_;81ewVF->J2Ebk) zz*_(TxMF84S_Do?9PFYh0?9^@uqBY~1Sqy#Rc)oJRn@9mN{F8+DI%Y{3h1`RGtn)B zWh$GEz^M(Q92tDw_ITfFR#rYSNt{c8$vjP_0SVjSyNM{ePzwLP;@`<-389O~J*<;l$Fe>Ek?E$pZWiLxpg>Rd#Y`{AWtF# zEpJ_u*Cor?9*rt*o>siDygf>THn@b&n$^|(rNZTTEC2cSTOT`KgVfA0h%^%MplqZ`YM!&+2p(YK*IJx_ zp_ODqxar&@I9kjSK$u2}18$uup zCpQkW)7{bO9pmN1+uvE=S_hM}`?r6-^V6NfiSkb7x6FlKN6dqF<_<^Z57P55rwUK# z-i^bl4|bHUk?k1S$5zLfe32`3bEQtMwD;*@?%LDb#cpo8lbe2;f47^z-pOBor7Od! zW27WguQF)t{eP(7&ZF^#QRy3L;SJ^SN3$IMz~77{Gcfj@Cc&7cFm495c0=Ga#+z-Y z5qUBguX{klhUXJM;BFn`THtaZ(-1&BMsR@-Y#d`6a)!9r2}AJK+99DBgUNj-{6lbF z4B}1b4FXV;4_~5ZO72)0Qd8fL9wV;yO8fG@y>I+D_qUjWxQM+iRuaZIW?Hx<`qzui z(!9G3Z>`XBfRa9ya)ZY9dl)w*09p_%1Hq6!^?K=;V%m!$Qe?)5{Zh=qdweQx02TIQ eS(2n*Q1UrC_Z*pjpv6D6VJY=>^Do3zZ~MO#mVRFV literal 0 HcmV?d00001 diff --git a/mediaflow_proxy/extractors/__pycache__/vixcloud.cpython-313.pyc b/mediaflow_proxy/extractors/__pycache__/vixcloud.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..faea6f3edc4fb7cb0777d22f61faa3129bf67839 GIT binary patch literal 4882 zcma)AT~Hg>6~3!o{r_P=2w?=WvB5G1*#_GaC)maY3^>IZt=F{;j=D$-tSwgJ?h1q2 znP$>yN0~g-&P>Ln?Mz7e5b{Pkeegpch}{Q2N>MvfH%#N{&vg7oLNZCG?M%F_3Y)i}@HS!*OI<=(&nZkn-E_$` zN@IGI!Ayv>)0fPn7Hk={VyjkXF4;!y*gopO4y|s!Q|x_Ma`T!4nSlESK|S{%eUh2#3FI<-_Pn>V2FDr|THkxgO~OJSNdVTMD! z6iYXuTQ+QFnF$MPp0M;XWDB<#Y=v!*7Tbhn!rp5lx`RHO*5_dD*vX+j3cC)VutVK* zRb04`Ol1;PX`VV^eAaKYhq&m=A;FNkJ5E(ZBN^fBg>SPL$QkNwG!D6*uFxF1$so`x zM;%3+X@ELOf!-+&B~8YdX%se%skJjnIX25n;*=mOu{2ICEQV>-EN9XJR&BAED2Yle zrq<3136Y;lrtZW9DUnWzk`iWAMi!D&D#PPxS+(50qviHkES}_LITn*a02B&^UQ@3} zW>T|41dc5g8A!!5vx20^5jKwbv?xtSs=T|j^}A-_%(fbJp(0f!$dP$*Ax@OmonFLy zU=6j&P*+ur-G<~I=$C^)zIhF;+uUzhKe06~9mw0l%k7`o!sY!ZbIc!Yt(!0asWw?u zgcwMvTCkvGuoSN<3%c#_{RF65`EPTxU@hYK-o7$BI)iOM+mRLLMV4X*)6P0yX*9)}7aLl1fKi`K?3mON**He_}ZAXI3#6*d{cch)X zqjft5K$UeHavQm1=FF^zZ0rDmpc;&xmmUyE0OjA}Xn5N~I#;Qmj#7O2A+zmdjY27Nrj<)jA{a3E(*P^f8E- zbzROV30@HrAr5X8lU$fhs&qn}S50Co;0-F zy^(J{p0Df4*~)=nDbQI6be3K9Pg>fSZY<9&ohb(oE{%R}rhT3bGio}rfhj%%C4B8q<#zRd-2#Ja6g4<`ApK)jA=eje>Se>g_&)7}qnPB!U zdvEy8Yq(fH=hEyJ0y^gc(9y`*fd)7S1Uj?izU}(3m~86*x)w1Kh8n>3C`u;uJxkM?xdgJf9R3p=c$3GLxyqqH2-j zSWGL~+72akTaY?VPsoQm4o%$Y40k*w=qp>_AuOat2zW+U0=lv-cOaUk?o4#YdFhoT zzc@5J02BM*)8?vFf3^mEw9ak()sCBF5_5L4?^xE_uC3IUwYOI|_hqeTD{YNB-)Go{ z`a(oYS*Cra@0e=EIt&5a?o9v+5G7s#M59GzZv`2|D!2rb=o+p8fS*o^iWZ9TVQA8D z0zN`Y1Sa4JAx8=6B!oZ-)r195jlkphjB1|7B*>f77+wOZOG5$Mmhk8x;WU};BZR;O zeZ3Q;bdr#l2ssZ#HEVlSt(D!Yjx8Ok4q3o(?%ICrfLQn>Dar()YN(=0GHgxq#pOWJ-?`FX@OS5i%gv#8Uw-H1mBwQ8@lwO_ z+{JQmUnv+V1S6~Tn!*>SB8te6D8m2g70*0XuZDS{>WP+`Ies5tHt`4O7-Up_2<`)73&9cgX;~A_rtmN7%T(^*Z5*!I5%4M`j(oC-nMdUcqzHkv3hXT|G>W1u@=ZT zU%F?1vP;n;F4Hy9eUDsq}IuIwXJxmCBCd&OlzWEvY z6SMCt#$-OVVXHR}QOofSMDmsRms-AYWS`lyT(^NBTkU;7KVTo&K05O?seVBSCP7@| z8TR5XQXT=KT9ifd(#KW7RTr3WQk?A8o-~-iMNIy7QRzv!S9eC*bEUQV-+=x%KzGH0 zQ&{dMP6v}$x&G228~G6F0+4rxYI{l7^BFt|RkClHL<|7h6!j@`ev0g$q2|v}=Re4A S str: + """Handle playnow URLs.""" + # Set up headers for the playnow request + playnow_headers = {"referer": channel_origin + "/", "user-agent": self.base_headers["user-agent"]} + + # Make the playnow request + playnow_response = await self._make_request(player_iframe, headers=playnow_headers) + player_url = self._extract_player_url(playnow_response.text) + if not player_url: + raise ExtractorError("Could not extract player URL from playnow response") + return player_url + def _extract_player_url(self, html_content: str) -> Optional[str]: """Extract player iframe URL from channel page HTML.""" try: # Look for iframe with allowfullscreen attribute iframe_match = re.search( - r']*src=["\']([^"\']+)["\'][^>]*allowfullscreen', - html_content, - re.IGNORECASE + r']*src=["\']([^"\']+)["\'][^>]*allowfullscreen', html_content, re.IGNORECASE ) if not iframe_match: @@ -215,7 +235,7 @@ class DLHDExtractor(BaseExtractor): iframe_match = re.search( r']*src=["\']([^"\']+(?:premiumtv|daddylivehd|vecloud)[^"\']*)["\']', html_content, - re.IGNORECASE + re.IGNORECASE, ) if iframe_match: @@ -225,17 +245,16 @@ class DLHDExtractor(BaseExtractor): except Exception: return None - async def _lookup_server(self, lookup_url_base: str, auth_url_base: str, auth_data: Dict[str, str], headers: Dict[str, str]) -> str: + async def _lookup_server( + self, lookup_url_base: str, auth_url_base: str, auth_data: Dict[str, str], headers: Dict[str, str] + ) -> str: """Lookup server information and generate stream URL.""" try: # Construct server lookup URL server_lookup_url = f"{lookup_url_base}/server_lookup.php?channel_id={quote(auth_data['channel_key'])}" # Make server lookup request - server_response = await self._make_request( - server_lookup_url, - headers=headers - ) + server_response = await self._make_request(server_lookup_url, headers=headers) server_data = server_response.json() server_key = server_data.get("server_key") @@ -244,13 +263,13 @@ class DLHDExtractor(BaseExtractor): raise ExtractorError("Failed to get server key") # Extract domain parts from auth URL for constructing stream URL - auth_domain_parts = urlparse(auth_url_base).netloc.split('.') - domain_suffix = '.'.join(auth_domain_parts[1:]) if len(auth_domain_parts) > 1 else auth_domain_parts[0] + auth_domain_parts = urlparse(auth_url_base).netloc.split(".") + domain_suffix = ".".join(auth_domain_parts[1:]) if len(auth_domain_parts) > 1 else auth_domain_parts[0] # Generate the m3u8 URL based on server response pattern - if '/' in server_key: + if "/" in server_key: # Handle special case like "top1/cdn" - parts = server_key.split('/') + parts = server_key.split("/") return f"https://{parts[0]}.{domain_suffix}/{server_key}/{auth_data['channel_key']}/mono.m3u8" else: # Handle normal case @@ -278,7 +297,7 @@ class DLHDExtractor(BaseExtractor): "channel_key": channel_key_match.group(1), "auth_ts": auth_ts_match.group(1), "auth_rnd": auth_rnd_match.group(1), - "auth_sig": auth_sig_match.group(1) + "auth_sig": auth_sig_match.group(1), } except Exception: return {} @@ -287,21 +306,15 @@ class DLHDExtractor(BaseExtractor): """Extract auth URL base from player page script content.""" try: # Look for auth URL or domain in fetchWithRetry call or similar patterns - auth_url_match = re.search( - r'fetchWithRetry\([\'"]([^\'"]*/auth\.php)', - html_content - ) + auth_url_match = re.search(r'fetchWithRetry\([\'"]([^\'"]*/auth\.php)', html_content) if auth_url_match: auth_url = auth_url_match.group(1) # Extract base URL up to the auth.php part - return auth_url.split('/auth.php')[0] + return auth_url.split("/auth.php")[0] # Try finding domain directly - domain_match = re.search( - r'[\'"]https://([^/\'\"]+)(?:/[^\'\"]*)?/auth\.php', - html_content - ) + domain_match = re.search(r'[\'"]https://([^/\'\"]+)(?:/[^\'\"]*)?/auth\.php', html_content) if domain_match: return f"https://{domain_match.group(1)}" @@ -320,13 +333,13 @@ class DLHDExtractor(BaseExtractor): try: # Typical pattern is to use a subdomain for auth domain parsed = urlparse(player_domain) - domain_parts = parsed.netloc.split('.') + domain_parts = parsed.netloc.split(".") # Get the top-level domain and second-level domain if len(domain_parts) >= 2: - base_domain = '.'.join(domain_parts[-2:]) + base_domain = ".".join(domain_parts[-2:]) # Try common subdomains for auth - for prefix in ['auth', 'api', 'cdn']: + for prefix in ["auth", "api", "cdn"]: potential_auth_domain = f"https://{prefix}.{base_domain}" return potential_auth_domain diff --git a/mediaflow_proxy/extractors/vixcloud.py b/mediaflow_proxy/extractors/vixcloud.py index 21118a7..b480da5 100644 --- a/mediaflow_proxy/extractors/vixcloud.py +++ b/mediaflow_proxy/extractors/vixcloud.py @@ -39,15 +39,16 @@ class VixCloudExtractor(BaseExtractor): async def extract(self, url: str, **kwargs) -> Dict[str, Any]: """Extract Vixcloud URL.""" - site_url = url.split("/iframe")[0] - version = await self.version(site_url) - response = await self._make_request(url, headers={"x-inertia": "true", "x-inertia-version": version}) - soup = BeautifulSoup(response.text, "lxml", parse_only=SoupStrainer("iframe")) - iframe = soup.find("iframe").get("src") - parsed_url = urlparse(iframe) - query_params = parse_qs(parsed_url.query) - response = await self._make_request(iframe, headers={"x-inertia": "true", "x-inertia-version": version}) - + if "iframe" in url: + site_url = url.split("/iframe")[0] + version = await self.version(site_url) + response = await self._make_request(url, headers={"x-inertia": "true", "x-inertia-version": version}) + soup = BeautifulSoup(response.text, "lxml", parse_only=SoupStrainer("iframe")) + iframe = soup.find("iframe").get("src") + response = await self._make_request(iframe, headers={"x-inertia": "true", "x-inertia-version": version}) + elif "movie" in url or "tv" in url: + response = await self._make_request(url) + if response.status_code != 200: raise ExtractorError("Failed to extract URL components, Invalid Request") soup = BeautifulSoup(response.text, "lxml", parse_only=SoupStrainer("body")) @@ -55,15 +56,15 @@ class VixCloudExtractor(BaseExtractor): script = soup.find("body").find("script").text token = re.search(r"'token':\s*'(\w+)'", script).group(1) expires = re.search(r"'expires':\s*'(\d+)'", script).group(1) - vixid = iframe.split("/embed/")[1].split("?")[0] - base_url = iframe.split("://")[1].split("/")[0] - final_url = f"https://{base_url}/playlist/{vixid}.m3u8?token={token}&expires={expires}" - if "canPlayFHD" in query_params: - # canPlayFHD = "h=1" + canPlayFHD = re.search(r"window\.canPlayFHD\s*=\s*(\w+)", script).group(1) + print(script,"A") + server_url = re.search(r"url:\s*'([^']+)'", script).group(1) + if "?b=1" in server_url: + final_url = f'{server_url}&token={token}&expires={expires}' + else: + final_url = f"{server_url}?token={token}&expires={expires}" + if "window.canPlayFHD = true" in script: final_url += "&h=1" - if "b" in query_params: - # b = "b=1" - final_url += "&b=1" self.base_headers["referer"] = url return { "destination_url": final_url, diff --git a/mediaflow_proxy/middleware.py b/mediaflow_proxy/middleware.py index d217587..25eebbb 100644 --- a/mediaflow_proxy/middleware.py +++ b/mediaflow_proxy/middleware.py @@ -23,4 +23,3 @@ class UIAccessControlMiddleware(BaseHTTPMiddleware): return Response(status_code=403, content="Forbidden") return await call_next(request) - diff --git a/mediaflow_proxy/mpd_processor.py b/mediaflow_proxy/mpd_processor.py index 76fba69..a6569f7 100644 --- a/mediaflow_proxy/mpd_processor.py +++ b/mediaflow_proxy/mpd_processor.py @@ -172,9 +172,29 @@ def build_hls_playlist(mpd_dict: dict, profiles: list[dict], request: Request) - # Add headers for only the first profile if index == 0: - sequence = segments[0]["number"] + first_segment = segments[0] extinf_values = [f["extinf"] for f in segments if "extinf" in f] target_duration = math.ceil(max(extinf_values)) if extinf_values else 3 + + # Calculate media sequence using adaptive logic for different MPD types + mpd_start_number = profile.get("segment_template_start_number") + if mpd_start_number and mpd_start_number >= 1000: + # Amazon-style: Use absolute segment numbering + sequence = first_segment.get("number", mpd_start_number) + else: + # Sky-style: Use time-based calculation if available + time_val = first_segment.get("time") + duration_val = first_segment.get("duration_mpd_timescale") + if time_val is not None and duration_val and duration_val > 0: + calculated_sequence = math.floor(time_val / duration_val) + # For live streams with very large sequence numbers, use modulo to keep reasonable range + if mpd_dict.get("isLive", False) and calculated_sequence > 100000: + sequence = calculated_sequence % 100000 + else: + sequence = calculated_sequence + else: + sequence = first_segment.get("number", 1) + hls.extend( [ f"#EXT-X-TARGETDURATION:{target_duration}", diff --git a/mediaflow_proxy/routes/__pycache__/__init__.cpython-313.pyc b/mediaflow_proxy/routes/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5bbc171964013004b48de9da9e4263f8e2416ae7 GIT binary patch literal 368 zcmYjM%}T>S5Z=vCW2*td2WW4SLtP(0JctKPK#QG5YG#} z)RQ+MFW_Xc)nWPen{Q^mVfrwf5R8xeyVX1XpF^?_=a=-CNM1=wQksyM0-X)qNu1aP z*K#-UVjuh&VI=h)DU6nWH2R`A7EyE3fLd=js7?#O{8X$pWVu%GPiq??3N2c_*U%cr z!5hFwc!&byTPIsET QbW6_h?It%b0UV?L1JvqgG5`Po literal 0 HcmV?d00001 diff --git a/mediaflow_proxy/routes/__pycache__/extractor.cpython-313.pyc b/mediaflow_proxy/routes/__pycache__/extractor.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..92bbf0ecd23a9011fb896f1d107eaef9d36b0bae GIT binary patch literal 3645 zcmb7GO>7&-6`tktXDO{HiljtJvZ9r(*j8+jN+^rAEZY?TF7_lx1u>u2p2bZnn)_zkW4- zD=U}Ec~vo8Df!)std&jIlw6XPoCYI!Q*thkf-f^|A zy6udt<#V!XdYcZFEPqWGZtO_gGd;3w{S>W|gYqZv-w)UkI)((e&99*}N*#hw(F3;} zEQfnHO!WLbD9S4ABDnz8muBg?&q8FZLeDkkBX-`wBgLM!pKPFV3POv{MgygYN6&pn zU*S>Oma<~>S@;=Sy{$OXjueg5LlA2;&ml!a9A!qxJl+>AQ_L9i;tCf16#2tZI_L8! z0WG|iwu>y;l2j+fH|tldpb?(}S`1Qbnn`t2xml;}sorLbN}`?aJFQcF)YhyQ`>C8} zC{A>oJCMO|=Lvwo90L^DtVP!dqN@g^%R(}#qfG>rUfP?ZGm^d*V{q08C^bm?HEViP zq+@VX2+aCKFPxWsiV@wgixfhQP>(R=+!W{HDXL66#TK#koNW;L4I&OrGxJwCH2*dH z|1^LWPg9%=N#_t!T#I3W-aoEPRB*p12JHuGMrzO;>EU?p+(O*o6Lwd#Wm?# zW)=_l8a$YI&Vv{osagM@9<;w2|EwR&=mOi1mYEp4%nWthKHK0zAzP3oMJVKzo0_nU z)uON_;k;VW1dD-$RaGnJl@&pg@mfABYhjbg%$w~E7RodT%k(uV5NNuj=9MxKu9MPU z)7m6R9+(}kXBxc5OQk%>n)aTGbEX&Q*Wz|MK|T!O17yt-YHigZfmE~%;ojT+#zG)n z2+LBwAm_$~{2dF81=v>8nUl+)&p!p-uhGyE(@xf8wilsyA(N{VOPL#*s^B&N<5tq6 zjS%ty^Wp%}xf*S=H3#_!INi!myrpfd0Tcc)ot07!(woU@Wo){@-ORFzO}3<#2zQqX zQdY)9$f_=BB)kN38b20?n5Cv{u?;WQ~xu(D=+BL{` zDauM&i-=ha=cTMf4i0wMI@sZ&lvkR{dRB{IlH#?)3wc2y*u`ZJ8N~)$7a7ewk7@H z_*O_CO&K0h=fv;!9Hi%ev^6;CA2Tx?^V8w@MRbpe$LznE6hM67W8&lX2Yw%sUuWVI z_6Nr=d!X^fD1m=*h9UBpCC4mz+$txm@?y)3gZ+{ly)wamInf64@7=K(#BL!6$XgB; zMi>%I7l|Oaxu&zcPHr$JPao5@ECHvK@)#aW2$R%h#TIrEiNu51(LjW0RBBdOn*9e6 zP`~WqgIlhJ-;sbdt++k+1p=D@imepni+C2A5ZOq(55*qKFw9?&=P^3+HH!Zc#UG>aKhgMObm=h~ z`x}z~WpgmLeFVi54+`}C?7WV=+iq{o-Sg1h^YPiLd+>cH#E;EaW4a8c>&~qzBWyDr zHKx;GI`6Djnf^W7>kP0*SHCX2buWH@S{Ke6T@!mO(eAbd_8dfjAp(7R-^jfa`gpSX d)|}oqXZXLr?;^H+7TE++3_P)uHl6{${|i~8aghK3 literal 0 HcmV?d00001 diff --git a/mediaflow_proxy/routes/__pycache__/proxy.cpython-313.pyc b/mediaflow_proxy/routes/__pycache__/proxy.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..451ae2c7b794011d314e7f42f4e538ba2a792f2b GIT binary patch literal 6661 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 F{{?!C&iViV literal 0 HcmV?d00001 diff --git a/mediaflow_proxy/routes/__pycache__/speedtest.cpython-313.pyc b/mediaflow_proxy/routes/__pycache__/speedtest.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..53d5a7ad99efa4873ff462c191ad06046dec799f GIT binary patch literal 2067 zcma)6O-y4|6u$TU^@kP;SYV*atNbJ-(uP5X5SYKBGf=R>K9~(8O)0nZY5QKi?-d5( z!i=+ENn&R0*4dfp!iAe*<+2Tn;)_O&Ze~}~F=}+-xv#&XlX#MQ@44rmd(OG%JLlHC zUN-_Nt*&JAB0@j2O_N}6Q|)V5o*)G&cn!rc#W9X@GTU=&d`zH%>zwOrlcEK^^v9 zSaZf))Fq=G{5qOzn4<0-SI4ev$DO#=IOFL+t%&c zaVVZ0=}kfL4x{&kmdBp`?oiKjrXAue*WHcPgkCfWwY)2vn;UOGOp}7C>e^Fm`4VJL zr6g@8h*8irgMj`(l}y-i}6%et0P_pQOJ@&x&y2*)8X)6!K4Jy6rc!ja>k zlGP98-MTT^VAf<4^kr3pSTd z9L@6?9O92>6T~c1O*XTn>1%p7Wq_{^k1Lz+the2Uk9#8aA&VxQHS;+LgfG$@W#~Ue zHRj5C5XtI!5&^Luc}q_h^F%X^h?1tMf~xID@{qVxCZ``x7pVU5XgZ%#wYD&63nP@J z-RN*uII{W;NK3NiKUqla6WR+NeK3rEn4Y7L&ryhDS71f;A2N5 zZNqUqexI1l7k9q3eq5v}2C?N~Z%ooUQkF#RWTjil1gj1hcCsIUw4UtO&^8F`l;E3O zd)-0_7S9y|sdPmI+c~Z-)9@Bfxc=k3AzFn_%Yv^1{DhB-9#n*Tg)z9(_5bf^!nU`m z{gN2DEg{j})M>9mSg_8J#xq`&Y$=;~~LvLJ=LPd(gKo8$~EZ!}8kf zUK3we5jr)gY8hQUg*c#ny9_gB7L8K@_lk-dD&UptD9}>;Zs&HbdYJgT3-oe^4jMlXit%5bhqdjc0 zfpso{*J6&`qSGK_{bD?Z337$;59p2WQTR83$K0<@gm3@UANXXkgnXA0@=u(0% X7pn5G{wR*rL|ECa1ioZPc9eeuKco#X literal 0 HcmV?d00001 diff --git a/mediaflow_proxy/routes/speedtest.py b/mediaflow_proxy/routes/speedtest.py index ec33bfd..8a6abfe 100644 --- a/mediaflow_proxy/routes/speedtest.py +++ b/mediaflow_proxy/routes/speedtest.py @@ -1,9 +1,11 @@ -import uuid - -from fastapi import APIRouter, BackgroundTasks, HTTPException, Request +from fastapi import APIRouter, HTTPException from fastapi.responses import RedirectResponse -from mediaflow_proxy.speedtest.service import SpeedTestService, SpeedTestProvider +from mediaflow_proxy.speedtest.models import ( + BrowserSpeedTestConfig, + BrowserSpeedTestRequest, +) +from mediaflow_proxy.speedtest.service import SpeedTestService speedtest_router = APIRouter() @@ -11,33 +13,29 @@ speedtest_router = APIRouter() speedtest_service = SpeedTestService() -@speedtest_router.get("/", summary="Show speed test interface") +@speedtest_router.get("/", summary="Show browser speed test interface") async def show_speedtest_page(): - """Return the speed test HTML interface.""" + """Return the browser-based speed test HTML interface.""" return RedirectResponse(url="/speedtest.html") -@speedtest_router.post("/start", summary="Start a new speed test", response_model=dict) -async def start_speedtest(background_tasks: BackgroundTasks, provider: SpeedTestProvider, request: Request): - """Start a new speed test for the specified provider.""" - task_id = str(uuid.uuid4()) - api_key = request.headers.get("api_key") +@speedtest_router.post("/config", summary="Get browser speed test configuration") +async def get_browser_speedtest_config( + test_request: BrowserSpeedTestRequest, +) -> BrowserSpeedTestConfig: + """Get configuration for browser-based speed test.""" + try: + provider_impl = speedtest_service.get_provider(test_request.provider, test_request.api_key) - # Create and initialize the task - await speedtest_service.create_test(task_id, provider, api_key) + # Get test URLs and user info + test_urls, user_info = await provider_impl.get_test_urls() + config = await provider_impl.get_config() - # Schedule the speed test - background_tasks.add_task(speedtest_service.run_speedtest, task_id, provider, api_key) - - return {"task_id": task_id} - - -@speedtest_router.get("/results/{task_id}", summary="Get speed test results") -async def get_speedtest_results(task_id: str): - """Get the results or current status of a speed test.""" - task = await speedtest_service.get_test_results(task_id) - - if not task: - raise HTTPException(status_code=404, detail="Speed test task not found or expired") - - return task.dict() + return BrowserSpeedTestConfig( + provider=test_request.provider, + test_urls=test_urls, + test_duration=config.test_duration, + user_info=user_info, + ) + except Exception as e: + raise HTTPException(status_code=400, detail=str(e)) diff --git a/mediaflow_proxy/schemas.py b/mediaflow_proxy/schemas.py index 9883883..d621754 100644 --- a/mediaflow_proxy/schemas.py +++ b/mediaflow_proxy/schemas.py @@ -98,7 +98,6 @@ class ExtractorURLParams(GenericParams): description="Additional parameters required for specific extractors (e.g., stream_title for LiveTV)", ) - @field_validator("extra_params", mode="before") def validate_extra_params(cls, value: Any): if isinstance(value, str): diff --git a/mediaflow_proxy/speedtest/__pycache__/__init__.cpython-313.pyc b/mediaflow_proxy/speedtest/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..82bf3c91c41f0692e6beecc940c53a443c3a191a GIT binary patch literal 185 zcmey&%ge<81Q$F#GC=fW5CH>>P{wB#AY&>+I)f&o-%5reCLr%KNa|LEenx(7s(xuv zPQJcNesXDUYFY|IvyGpqGo^x%$Ag8O&;`q#W?mhROZ@zP{&*Jf@ zfX}k=rs3?W?sbw89=*^pkg?ZRW*555NPl~b^xrQs|#0Jc>$ z3-)tx3M^_B&;4J7Tv}sc({!zpZJI1`VoUUS`e9 zS4y@U_!&J9R@rg)GbOv=SbIh9V4)1&>*_+ua@%D-U!VPW@=l_p{c_xjGTwBu-g@ZaP|PhSJ|Bt48;iP(tV6@PbTkz>!@zSD;qZ5 zJ+DQJF&Qgj3h~8@MP?y^I3&pok{RN|`~VozX}PXPJb5nBbA#fhw$s0X4l%5y# zgV2n`4mX<7_?yknKpNA(pk0hyFNg$VgbX!=F+d7(5DKpve%S_9A6*qt?uOmIsuX;$~6rkmSaax9`?Iuxk?Sm7 zes*jw)zFME=L_46^B^NPm)IpYGIEpLA@zQN>g}kbbqn*uIGP-bp}x(s<@*O76kvh^ z^ugalltyt*4=vh58K2mXs5u%(hSv0nPm;^v;dXQOUTwQR-&p!_;Y7LD9G|YO9nII5 zju%dq=?*_Mt3N+aG4g(X3}Q!nD0TTcZ;-Ho>d`NlF$RoScZ9|n3`2ZhO-I2EKxAvDofdHi_b zgwl1;k>4VvlN}UohA`SBms(#TrYTB>lK3+J5@FDuAo>n`p9K17pa8fo%7yUK3l zD*$*9n^#2oANstc^C6|<)bW;Hrq_P@{Fnk{XnlVU%>{p#EqkJ*nhzG=DWi?~pO=0@ zg)Vkjqj|Y>k|{RcqafoOQ-dm97{EU#t&fta7AzO99-#jYz*~s#iEox4r~EXbwfUFi z+qrh^vhb&!JMc631jo_!>pw&Dw=9a{@50C*Lh3K!ep{8q>4w~R+!oMjM

)A0t1} qj>_WfQME1LPdg@yGj)gjnRZMO(~WQ10{*lUvY2T&{~(^>_5BO{ndkEW literal 0 HcmV?d00001 diff --git a/mediaflow_proxy/speedtest/__pycache__/service.cpython-313.pyc b/mediaflow_proxy/speedtest/__pycache__/service.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5dd28fed6f1a6bce292519cbb058787bf1622b08 GIT binary patch literal 1875 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 R6JKS(dmYJtAqu$O`!BK7O~C*F diff --git a/mediaflow_proxy/speedtest/providers/__pycache__/all_debrid.cpython-313.pyc b/mediaflow_proxy/speedtest/providers/__pycache__/all_debrid.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..789cb1e7989d0b736884632f4df5738d223ddb6f GIT binary patch literal 3388 zcmZ`+O>7&-6`uVex%?Al{aCd8NB`6{ZHts+S+-I`jvT77q*!&eq+mw`)+=%;twZiI zGfO)X5V%N-Kt@v37q?C?QGp(8pa=El-qaw6 zfU`4i-n@D5&3oS)Zid4F0OcsBg=SPu*H;~vusbgQZ8z?^s}<1n}%A9bGF1Z%hHV* z7U%7NtkF9f9We@~EoGQH0($2am1%NW)ADJJS=Xs~N6%~2?%G9q+cXOL3`lLG+6NWj zRqpE6tU@)5&gEQf-JYvYHalSOkYowvWI}nFqk_y+aYl%Xc1u(IFr_A4N5?+_V?4H` zD27_n6vYlGO3BPuiZ~7_$_Ev-SYL@KNEmUwo7@(uSN3FmSt;v58g9(`vjP0NGvM<&d_mv~!3@F(c7ko9sVd*GI!$7*#vk`TE@8Gra7?fgJn+IiYPiQnmoF;d}rHd)9pHuj2?Suru!;CjX z2n6)J)20v_oHl_sy`upPcF3LQ>vtgUjgX0bJ6K>UcgfKPkAb(*wwq^S|882tcVLMZ zY!T|VfF2b%v7En=pE$64R|}E%Wjrn9oB&ouU8@YA;Wfk?tdBoVBr?9qc-PVb*b5`U z5DjmETo4LeeE+yDyfd7hPt00YnZ21r;Hcw9&MYN$!_p{VHe^@wPTw}t%m)yudxmBp z1KottbJ~39Y@)a4T>rU?{R8R+z<-bGu46VqmfBtr133zb<(t)1Kyzk$%PLh%Z2fDn zZ2rhkRlTU?W0n~!XjX1E*37P0Jtu81!fA!UnU!2lV{HDwHqX%Yk(irSAVWj=w#B?^ zsWz{dZ9!*c+m|yd29$-kKof8vO=8!J-9hMV5n+5CLwp_YcwS7r(O0m!giYpy+qNei z_>M;?<^{!6vjY{-PjReuF(l}!spL$s67$3sZ!^=Nm}nGAc>O>xRgJt^vV9dpFPIbt zR4W(NoMwA;ODi#3fWi=$DB_nbQH_;N1O1LuzjLOJWz*|=#}2#V6r)m_2K&zWoo_O9Cs~$>a6s%6yb^!v3;vDQ&q@Nam@yWDlIdVTVnOVu0dv$5%wt{n8s zdmr8TyyZ`U+Q~~xlh8ejUaE~v*Sc~G;%6Z^J_{ig*uk24QiPi4&tI@$) zbnq!#zOoX1XCb_nzOkCVRZHJmO&4nE!nf(!ZzKA%^z2{XT~0j^zv%wF`%!3dY$^Qs zUUl!~XRViO>DdLb7SX?pv@hJNN{3#a1#i6i&v6pXbFVff(t4YF#V`mzPNdFqOI+%> z_~)ThF#K|VG<8D!^4w4hOupg{rcQgmI^@Igi6Hc^l?_BWr)*5Zz%3g9I(481QnNJx zW@ia-;H7N}ih|o@0C$-maKWS1feB%pVkLvj4LdQDuTZD(+Wuw;#d-QRuuyzrKz?D! z(ksx}oWf8vM4uaZ0gS7h^JudVHnaEjuydC&GubKVFy_{x2Ueq}YSB|G(KD-UXYTtJ z#OIL{OB2g|waC?~boHe`+IwEH5U_vT5$Ar({aF0no;XJlGshuYTj5Kw7*NDx3Iza4 zy`tpJ9Mo}vS(F||I+j(6`cm|(o3Ks0=m^rVMmQMYzMrsAP(WU{LpL$p@Co_5cws~2 zJ!cn%EdrknX`kotV&-w*lCUH%Nlyl!UR=If`^mNH>1(wgjc*ZH-RS2%BV6_9!1u6W zgmdMi=y)3)fRF99=I~wUD(I++TH0OBccS|wOt}7IE;_|_t~w7Yyto9sW3?i45eYHE zKKcK78*eX`n1+`*#hU3{w!KS!#nOw+sVIswbdb{a4}X}`oc{psd(?3mR{w1`+R`QW zWj#n`Sil%&7_}P$$8rB4;TPm&jhy^9IrD-Hy&$=NlFM6Oj`Kgbxkcc!B^~F$d;1dq JB{*=z{tstsMvDLd literal 0 HcmV?d00001 diff --git a/mediaflow_proxy/speedtest/providers/__pycache__/base.cpython-312.pyc b/mediaflow_proxy/speedtest/providers/__pycache__/base.cpython-312.pyc deleted file mode 100644 index 01373e6b9b82a53ae378abedcbfcf0e60e31f262..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1534 zcma)6O=ule6ux)<-n^HLQPNs!H4bTo3~k>lsGwK^HicF+Piq>%*$k7pcankmdES|4 zeOXA+xNw(Uvvg@FLRR9^y_*JI)C*Ff6znebrJ$}n=g!MZg9=`lZ_YXQ%(>?~_ni5` zaasgc_rmPjw;Cb$Q8`%VNEoId+#-xHnv#tAG}C;IqOPTSX81;C`etVNma^$-BeQ*5 z$wu1DT7HWXja(tj+#t;2_HDh6>rW_a1FSZuW9#Ig>9roBi>FEb%}WdNsc@yNL>N^W zuht6Y>C(8IM3pqJ_j@Uq_Pf0*De^G&sBB&eOTJhzj*7ipa`9Fk7g2p+jib}a@B-+! z2q!*e#MhRAov(9)n@qb1JKW+8-iWo6D4R@2+raQld19%@IlIow>OE0>oG>mHiabtM zA7UY;Q^7{S`Xa=fWK*eqk(OQ0mUa;2Va9_%wt^rlSU*L1G6+8EhiSbd9E_qTc_pWU zAk6ckQjy9a5O^O_hf-{lA&BI!4?Al`#yb$c=v*$Me#Y~v>?}nh>?QeXC*v#$m?Gw1UsNBCS_*@>eucK#011wV1rOT2k-^Cd-BxH#m$$0G)~=}n%-R4 zn%=zn<)=RxFNkLkoR1XR0xPcJ2LOkKuFK3cZXPMI&6`;2y5OLOWpOtvj8!K=Q=BU&G%5T<~94$p8B z;B7)7^#T1xzd=21ef|os+%$n8MO+74e*c~NfNG78BfDYF++x&~ZXdePO`r|iU2z;@ zZc}NLJdH~{)fFcIrL)SbVB9O~r~j2y`-s!i@ZAuv={NGnO|oZ^rt{~}0R1z~p#PiE z8Dw-&k@HZcD2bEEjZ{mIx_Fegcpid3nRE;DsXf=-|IWDfK<45_1Q(#|IbsSGWFejb zkc}WVTeEZ#Z0xcgG~cLKXZomj_|-=2LS-I;y;aPJYusJk5x z0uoXWS0aJG6qUBLssWB(ow?$GnM=DjU}0V*k-%}1?szimNoF(o|ZLL#Zoak z%2L`F>z~KGn1gWGM&&txJ)Kf|kDR_o=61>4FJ$Hcd3E2?=*(8KPvEuRnxM|s!eat2 G#o}*%hle`= diff --git a/mediaflow_proxy/speedtest/providers/__pycache__/base.cpython-313.pyc b/mediaflow_proxy/speedtest/providers/__pycache__/base.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b8f9a0c5fc2b3400dc5e6c6674fab3fea0b49bfb GIT binary patch literal 1628 zcma)6&2QXP5P$wyuXmG8fHZ2GLS^Mc)Pn3DNJJ4R5=|x24OwX2+$=1w?cEr8?Pcr* z(gUhM0&&ZwQjQ$SEhog0KOqqkDo;X1LOtv)lEZ-;Gtax*Mu5bV{PTM=HeS}1zi82pM^>6MyA9ox7;+?N&J8g`feJ)ow$>DMZf|_(OG^I z&ySeRK(ZLfmITTQG6K~u*_x%iidwsD>$bioQz1~MlFTYWrK_1`e%a~=fqy?pvkxKp zH1q@5OyW-19qMQ`q^j~-hFZrTfJ+JTiZ~7+N{?8}ta9CPVz(DK4zD^+FYyNv?WY~* zlYtwJG_#J=2_em*Fb?9xabSiVD`A{*RJn1SWWsrhDoT?pSSmj6I4Q|J$IUVb+XIY@ z1}~sNjt}D>HTIHT(7;@h#_hx#^ny4`8=DKlg{uWH%*OB0)`-O5PM_7zRooWvH`@NBtjN|Q^mf* zC!a=GVg_>QN^dG1$&?>#ycc9f6rz))Q#jkb_kIx|+Y2V14LA0UWYp!x0K3`ZU zTteVw$MF*n+n?9~wUw9@xJs`cMX zb7!(#s(rWn{o1i|Y#r-I8$aIsY3uauos-vgPFGqvLv5}w?UJxN)5p7$#kZd6a`I6` zqd+aQ#uc~i0sXK-~M-jd9w6ri~ku=`9AeJ{iXt7Jma)6q=3z literal 0 HcmV?d00001 diff --git a/mediaflow_proxy/speedtest/providers/__pycache__/real_debrid.cpython-312.pyc b/mediaflow_proxy/speedtest/providers/__pycache__/real_debrid.cpython-312.pyc deleted file mode 100644 index 6aa4e67bcca840082943341125297753ea1dbddf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2498 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 BaseSpeedTestProvider: + def get_provider(self, provider: SpeedTestProvider, api_key: Optional[str] = None) -> BaseSpeedTestProvider: """Get the appropriate provider implementation.""" provider_class = self._providers.get(provider) if not provider_class: @@ -33,97 +26,3 @@ class SpeedTestService: raise ValueError("API key required for AllDebrid") return provider_class(api_key) if provider == SpeedTestProvider.ALL_DEBRID else provider_class() - - async def create_test( - self, task_id: str, provider: SpeedTestProvider, api_key: Optional[str] = None - ) -> SpeedTestTask: - """Create a new speed test task.""" - provider_impl = self._get_provider(provider, api_key) - - # Get initial URLs and user info - urls, user_info = await provider_impl.get_test_urls() - - task = SpeedTestTask( - task_id=task_id, provider=provider, started_at=datetime.now(tz=timezone.utc), user_info=user_info - ) - - await set_cache_speedtest(task_id, task) - return task - - @staticmethod - async def get_test_results(task_id: str) -> Optional[SpeedTestTask]: - """Get results for a specific task.""" - return await get_cached_speedtest(task_id) - - async def run_speedtest(self, task_id: str, provider: SpeedTestProvider, api_key: Optional[str] = None): - """Run the speed test with real-time updates.""" - try: - task = await get_cached_speedtest(task_id) - if not task: - raise ValueError(f"Task {task_id} not found") - - provider_impl = self._get_provider(provider, api_key) - config = await provider_impl.get_config() - - async with create_httpx_client() as client: - streamer = Streamer(client) - - for location, url in config.test_urls.items(): - try: - task.current_location = location - await set_cache_speedtest(task_id, task) - result = await self._test_location(location, url, streamer, config.test_duration, provider_impl) - task.results[location] = result - await set_cache_speedtest(task_id, task) - except Exception as e: - logger.error(f"Error testing {location}: {str(e)}") - task.results[location] = LocationResult( - error=str(e), server_name=location, server_url=config.test_urls[location] - ) - await set_cache_speedtest(task_id, task) - - # Mark task as completed - task.completed_at = datetime.now(tz=timezone.utc) - task.status = "completed" - task.current_location = None - await set_cache_speedtest(task_id, task) - - except Exception as e: - logger.error(f"Error in speed test task {task_id}: {str(e)}") - if task := await get_cached_speedtest(task_id): - task.status = "failed" - await set_cache_speedtest(task_id, task) - - async def _test_location( - self, location: str, url: str, streamer: Streamer, test_duration: int, provider: BaseSpeedTestProvider - ) -> LocationResult: - """Test speed for a specific location.""" - try: - start_time = time.time() - total_bytes = 0 - - await streamer.create_streaming_response(url, headers={}) - - async for chunk in streamer.stream_content(): - if time.time() - start_time >= test_duration: - break - total_bytes += len(chunk) - - duration = time.time() - start_time - speed_mbps = (total_bytes * 8) / (duration * 1_000_000) - - # Get server info if available (for AllDebrid) - server_info = getattr(provider, "servers", {}).get(location) - server_url = server_info.url if server_info else url - - return LocationResult( - result=SpeedTestResult( - speed_mbps=round(speed_mbps, 2), duration=round(duration, 2), data_transferred=total_bytes - ), - server_name=location, - server_url=server_url, - ) - - except Exception as e: - logger.error(f"Error testing {location}: {str(e)}") - raise # Re-raise to be handled by run_speedtest diff --git a/mediaflow_proxy/static/index.html b/mediaflow_proxy/static/index.html index ea185a0..9885320 100644 --- a/mediaflow_proxy/static/index.html +++ b/mediaflow_proxy/static/index.html @@ -43,6 +43,44 @@ margin-bottom: 10px; } + .speed-test-section { + background-color: #e8f4fd; + border-left: 4px solid #2196f3; + padding: 15px; + margin: 20px 0; + border-radius: 5px; + } + + .speed-test-links { + display: flex; + gap: 15px; + margin-top: 10px; + flex-wrap: wrap; + } + + .speed-test-link { + display: inline-block; + padding: 10px 20px; + background-color: #2196f3; + color: white; + text-decoration: none; + border-radius: 5px; + transition: background-color 0.3s; + } + + .speed-test-link:hover { + background-color: #1976d2; + color: white; + } + + .speed-test-link.browser { + background-color: #4caf50; + } + + .speed-test-link.browser:hover { + background-color: #388e3c; + } + a { color: #3498db; } @@ -63,6 +101,17 @@

Proxy and modify HLS (M3U8) streams in real-time with custom headers and key URL modifications for bypassing some sneaky restrictions.
Protect against unauthorized access and network bandwidth abuses
+
+

🚀 Speed Test Tool

+

Test your connection speed with debrid services to optimize your streaming experience:

+ +

+ Browser Speed Test: Tests your actual connection speed through MediaFlow proxy vs direct connection with support for multiple servers, interactive charts, and comprehensive analytics. +

+
+

Getting Started

Visit the GitHub repository for installation instructions and documentation.

diff --git a/mediaflow_proxy/static/speedtest.html b/mediaflow_proxy/static/speedtest.html index 386fa8e..f6375b5 100644 --- a/mediaflow_proxy/static/speedtest.html +++ b/mediaflow_proxy/static/speedtest.html @@ -3,38 +3,39 @@ - Debrid Speed Test + MediaFlow Speed Test + + @@ -63,634 +77,301 @@
- -
- -
-

- Enter API Password -

+ +
+

+ 🚀 MediaFlow Speed Test +

+

+ Compare your connection speed through MediaFlow proxy vs direct connection +

+
-
-
+ +
+
+

Test Configuration

+ + + +
-
-
- - -
- - -
-
- - - - -
diff --git a/mediaflow_proxy/static/speedtest.js b/mediaflow_proxy/static/speedtest.js new file mode 100644 index 0000000..8973b1d --- /dev/null +++ b/mediaflow_proxy/static/speedtest.js @@ -0,0 +1,1239 @@ +// Speed test functionality +class MediaFlowSpeedTest { + constructor() { + this.config = null; + this.results = { + proxy: {}, + direct: {} + }; + this.servers = []; + this.currentTestIndex = 0; + this.totalTests = 0; + this.charts = {}; + this.testCancelled = false; + this.selectedCdns = new Set(); + this.activeAbortControllers = new Set(); + + this.initializeEventListeners(); + this.initializeForm(); + this.setupResizeHandler(); + } + + initializeEventListeners() { + document.getElementById('configForm').addEventListener('submit', (e) => { + e.preventDefault(); + this.startSpeedTest(); + }); + + document.getElementById('provider').addEventListener('change', (e) => { + const apiKeySection = document.getElementById('apiKeySection'); + if (e.target.value === 'all_debrid') { + apiKeySection.classList.remove('hidden'); + } else { + apiKeySection.classList.add('hidden'); + } + + // Clear CDN selection when provider changes + this.config = null; + this.selectedCdns.clear(); + this.showPlaceholderCdnSelection(); + this.updateCdnButtonStates(); + }); + + document.getElementById('addServerBtn').addEventListener('click', () => { + this.addServerInput(); + }); + + document.getElementById('runAgainBtn').addEventListener('click', () => { + this.resetTest(); + }); + + document.getElementById('cancelTestBtn').addEventListener('click', () => { + this.cancelTest(); + }); + + document.getElementById('selectAllCdn').addEventListener('click', () => { + this.selectAllCdns(true); + }); + + document.getElementById('selectNoneCdn').addEventListener('click', () => { + this.selectAllCdns(false); + }); + + document.getElementById('refreshCdnBtn').addEventListener('click', async () => { + await this.refreshCdnLocations(); + }); + + // Handle server removal + document.addEventListener('click', (e) => { + if (e.target.classList.contains('remove-server')) { + e.target.closest('.server-input').remove(); + this.updateRemoveButtons(); + } + }); + } + + initializeForm() { + // Set current URL as default MediaFlow URL + const currentUrl = new URL(window.location.href); + const baseUrl = `${currentUrl.protocol}//${currentUrl.host}`; + const firstServerUrl = document.querySelector('.server-url'); + firstServerUrl.value = baseUrl; + firstServerUrl.placeholder = `${baseUrl} (Current Instance)`; + + // Show placeholder CDN selection initially + this.showPlaceholderCdnSelection(); + this.updateCdnButtonStates(); + } + + showPlaceholderCdnSelection() { + const cdnStatusContainer = document.getElementById('cdnStatusContainer'); + const cdnContainer = document.getElementById('cdnSelection'); + + // Clear status container + cdnStatusContainer.innerHTML = ''; + + // Show placeholder in main CDN container + cdnContainer.innerHTML = ` +
+
🌐
+

CDN Locations Not Loaded

+

+ Configure your debrid provider settings above, then click "🔄 Refresh CDNs" to load available locations. +

+
+ Steps:
+ 1. Select your debrid provider
+ 2. Enter API key (if required)
+ 3. Click "🔄 Refresh CDNs"
+ 4. Select desired locations
+ 5. Start speed test +
+
+ `; + } + + addServerInput() { + const container = document.getElementById('serversContainer'); + const serverDiv = document.createElement('div'); + serverDiv.className = 'server-input grid grid-cols-1 md:grid-cols-3 gap-3 p-3 bg-gray-50 dark:bg-gray-700 rounded-lg'; + + serverDiv.innerHTML = ` + + +
+ + +
+ `; + + container.appendChild(serverDiv); + this.updateRemoveButtons(); + } + + updateRemoveButtons() { + const serverInputs = document.querySelectorAll('.server-input'); + serverInputs.forEach((input, index) => { + const removeBtn = input.querySelector('.remove-server'); + if (index === 0) { + removeBtn.classList.add('hidden'); + } else { + removeBtn.classList.remove('hidden'); + } + }); + } + + async startSpeedTest() { + try { + this.testCancelled = false; + + // Check if CDN configuration is loaded + if (!this.config || !this.config.test_urls) { + alert('Please fetch CDN locations first by clicking "🔄 Refresh CDNs" button.'); + return; + } + + // Collect server configurations + this.collectServerConfigurations(); + + // Validate server configurations + if (this.servers.length === 0) { + alert('Please add at least one MediaFlow server to test.'); + return; + } + + // Validate CDN selections + if (this.selectedCdns.size === 0) { + alert('Please select at least one CDN location to test.'); + return; + } + + // Validate test options + const testProxy = document.getElementById('testProxy').checked; + const testDirect = document.getElementById('testDirect').checked; + + if (!testProxy && !testDirect) { + alert('Please select at least one test option (Proxy or Direct).'); + return; + } + + // Calculate total tests + this.calculateTotalTests(); + + this.showTestingView(); + await this.runTests(); + if (!this.testCancelled) { + this.showResults(); + } + } catch (error) { + console.error('Speed test failed:', error); + if (!this.testCancelled) { + alert('Speed test failed: ' + error.message); + this.resetTest(); + } + } + } + + collectServerConfigurations() { + // Collect server configurations + this.servers = []; + const serverInputs = document.querySelectorAll('.server-input'); + + serverInputs.forEach(input => { + const url = input.querySelector('.server-url').value.trim(); + const name = input.querySelector('.server-name').value.trim(); + const password = input.querySelector('.server-password').value.trim(); + + if (url) { + this.servers.push({ + url: url, + name: name || new URL(url).host, + api_password: password || null + }); + } + }); + } + + async refreshCdnLocations() { + const refreshBtn = document.getElementById('refreshCdnBtn'); + const originalText = refreshBtn.textContent; + + try { + // Show loading state + refreshBtn.textContent = '⏳ Loading...'; + refreshBtn.disabled = true; + + // Show loading in CDN container + const cdnStatusContainer = document.getElementById('cdnStatusContainer'); + const cdnContainer = document.getElementById('cdnSelection'); + + cdnStatusContainer.innerHTML = ''; + cdnContainer.innerHTML = ` +
+
+

Loading CDN locations...

+
+ `; + + await this.loadConfiguration(); + this.populateCdnSelection(); + + // Show success message + const successMsg = document.createElement('div'); + successMsg.className = 'fixed top-4 right-4 bg-green-500 text-white px-4 py-2 rounded-lg shadow-lg z-50'; + successMsg.textContent = `✅ Loaded ${Object.keys(this.config.test_urls).length} CDN locations`; + document.body.appendChild(successMsg); + + setTimeout(() => { + successMsg.remove(); + }, 3000); + + } catch (error) { + console.error('Failed to refresh CDN locations:', error); + + // Show error message + const errorMsg = document.createElement('div'); + errorMsg.className = 'fixed top-4 right-4 bg-red-500 text-white px-4 py-2 rounded-lg shadow-lg z-50'; + errorMsg.textContent = `❌ Failed: ${error.message}`; + document.body.appendChild(errorMsg); + + setTimeout(() => { + errorMsg.remove(); + }, 5000); + + this.showPlaceholderCdnSelection(); + this.updateCdnButtonStates(); + } finally { + // Restore button state + refreshBtn.textContent = originalText; + refreshBtn.disabled = false; + } + } + + cancelTest() { + this.testCancelled = true; + document.getElementById('currentTest').textContent = 'Test cancelled by user'; + document.getElementById('progressText').textContent = 'Cancelling...'; + + // Cancel all active network requests + this.activeAbortControllers.forEach(controller => { + try { + controller.abort(); + } catch (e) { + console.warn('Error aborting request:', e); + } + }); + this.activeAbortControllers.clear(); + + setTimeout(() => { + this.resetTest(); + }, 1000); + } + + populateCdnSelection() { + const cdnStatusContainer = document.getElementById('cdnStatusContainer'); + const cdnContainer = document.getElementById('cdnSelection'); + const locations = Object.keys(this.config.test_urls); + + // Initialize all CDNs as selected if none are selected + if (this.selectedCdns.size === 0) { + locations.forEach(location => this.selectedCdns.add(location)); + } + + // Show success status + cdnStatusContainer.innerHTML = ` +
+
+ + CDN Locations Loaded Successfully + ${locations.length} locations +
+
+ `; + + // Populate CDN checkboxes in the grid + cdnContainer.innerHTML = locations.map(location => ` +
+ + +
+ `).join(''); + + // Add event listeners to checkboxes + document.querySelectorAll('.cdn-checkbox').forEach(checkbox => { + checkbox.addEventListener('change', (e) => { + const location = e.target.dataset.location; + this.toggleCdn(location); + }); + }); + + // Update button states + this.updateCdnButtonStates(); + } + + toggleCdn(location) { + if (this.selectedCdns.has(location)) { + this.selectedCdns.delete(location); + } else { + this.selectedCdns.add(location); + } + } + + selectAllCdns(selectAll) { + if (!this.config || !this.config.test_urls) { + // Show a brief message if CDNs aren't loaded + const message = document.createElement('div'); + message.className = 'fixed top-4 right-4 bg-yellow-500 text-white px-4 py-2 rounded-lg shadow-lg z-50'; + message.textContent = '⚠️ Please load CDN locations first'; + document.body.appendChild(message); + setTimeout(() => message.remove(), 2000); + return; + } + + const checkboxes = document.querySelectorAll('.cdn-checkbox'); + const locations = Object.keys(this.config.test_urls); + + if (selectAll) { + this.selectedCdns = new Set(locations); + checkboxes.forEach(cb => cb.checked = true); + } else { + this.selectedCdns.clear(); + checkboxes.forEach(cb => cb.checked = false); + } + } + + updateCdnButtonStates() { + const selectAllBtn = document.getElementById('selectAllCdn'); + const selectNoneBtn = document.getElementById('selectNoneCdn'); + const hasConfig = this.config && this.config.test_urls; + + if (hasConfig) { + selectAllBtn.disabled = false; + selectNoneBtn.disabled = false; + selectAllBtn.classList.remove('opacity-50', 'cursor-not-allowed'); + selectNoneBtn.classList.remove('opacity-50', 'cursor-not-allowed'); + } else { + selectAllBtn.disabled = true; + selectNoneBtn.disabled = true; + selectAllBtn.classList.add('opacity-50', 'cursor-not-allowed'); + selectNoneBtn.classList.add('opacity-50', 'cursor-not-allowed'); + } + } + + async loadConfiguration() { + const provider = document.getElementById('provider').value; + const apiKey = document.getElementById('apiKey').value; + const currentApiPassword = document.getElementById('currentApiPassword').value; + + // Use current MediaFlow instance for fetching CDN configuration + const currentUrl = new URL(window.location.href); + const baseUrl = `${currentUrl.protocol}//${currentUrl.host}`; + + const requestBody = { + provider: provider, + api_key: apiKey || null, + current_api_password: currentApiPassword || null + }; + + const headers = { + 'Content-Type': 'application/json', + }; + + // Add current API password to headers if provided + if (currentApiPassword) { + headers['api_password'] = currentApiPassword; + } + + const response = await fetch('/speedtest/config', { + method: 'POST', + headers: headers, + body: JSON.stringify(requestBody) + }); + + if (!response.ok) { + const error = await response.json(); + throw new Error(error.detail || 'Failed to load configuration'); + } + + this.config = await response.json(); + } + + calculateTotalTests() { + // Calculate total tests based on selected CDNs and servers + const testProxy = document.getElementById('testProxy').checked; + const testDirect = document.getElementById('testDirect').checked; + const selectedLocationCount = this.selectedCdns.size; + const serverCount = this.servers.length; + + this.totalTests = 0; + if (testProxy) this.totalTests += selectedLocationCount * serverCount; + if (testDirect) this.totalTests += selectedLocationCount; + } + + showTestingView() { + document.getElementById('configView').classList.add('hidden'); + document.getElementById('testingView').classList.remove('hidden'); + document.getElementById('resultsView').classList.add('hidden'); + + // Clear previous results and any existing abort controllers + this.results = {proxy: {}, direct: {}}; + this.activeAbortControllers.forEach(controller => { + try { + controller.abort(); + } catch (e) { + console.warn('Error aborting request:', e); + } + }); + this.activeAbortControllers.clear(); + + document.getElementById('liveResults').innerHTML = ''; + } + + showResults() { + document.getElementById('configView').classList.add('hidden'); + document.getElementById('testingView').classList.add('hidden'); + document.getElementById('resultsView').classList.remove('hidden'); + + this.renderMetrics(); + this.renderCharts(); + this.renderDetailedResults(); + } + + resetTest() { + this.results = {proxy: {}, direct: {}}; + this.currentTestIndex = 0; + this.totalTests = 0; + this.testCancelled = false; + + // Cancel and clear any remaining abort controllers + this.activeAbortControllers.forEach(controller => { + try { + controller.abort(); + } catch (e) { + console.warn('Error aborting request:', e); + } + }); + this.activeAbortControllers.clear(); + + // Destroy existing charts safely + Object.values(this.charts).forEach(chart => { + if (chart && typeof chart.destroy === 'function') { + try { + chart.destroy(); + } catch (e) { + console.warn('Error destroying chart:', e); + } + } + }); + this.charts = {}; + + // Clear canvas elements + ['speedChart', 'serverChart'].forEach(id => { + const canvas = document.getElementById(id); + if (canvas) { + const ctx = canvas.getContext('2d'); + ctx.clearRect(0, 0, canvas.width, canvas.height); + } + }); + + // Clear live results + document.getElementById('liveResults').innerHTML = ''; + + document.getElementById('configView').classList.remove('hidden'); + document.getElementById('testingView').classList.add('hidden'); + document.getElementById('resultsView').classList.add('hidden'); + } + + async runTests() { + const testProxy = document.getElementById('testProxy').checked; + const testDirect = document.getElementById('testDirect').checked; + const testDuration = parseInt(document.getElementById('testDuration').value) || 10; + + this.currentTestIndex = 0; + + // Validate selected CDNs + if (this.selectedCdns.size === 0) { + throw new Error('Please select at least one CDN location to test'); + } + + // Filter test URLs to only selected CDNs + const selectedTestUrls = Object.fromEntries( + Object.entries(this.config.test_urls).filter(([location]) => + this.selectedCdns.has(location) + ) + ); + + // Run proxy tests for each server + if (testProxy && !this.testCancelled) { + for (const server of this.servers) { + if (this.testCancelled) break; + for (const [location, url] of Object.entries(selectedTestUrls)) { + if (this.testCancelled) break; + await this.runSingleTest(location, url, 'proxy', server, testDuration); + } + } + } + + // Run direct tests + if (testDirect && !this.testCancelled) { + for (const [location, url] of Object.entries(selectedTestUrls)) { + if (this.testCancelled) break; + await this.runSingleTest(location, url, 'direct', null, testDuration); + } + } + } + + async runSingleTest(location, url, testType, server, duration) { + if (this.testCancelled) return; + + let testUrl; + let testKey = location; + + if (testType === 'proxy') { + testUrl = `${server.url.replace(/\/$/, '')}/proxy/stream?d=${encodeURIComponent(url)}`; + if (server.api_password) { + testUrl += `&api_password=${encodeURIComponent(server.api_password)}`; + } + testKey = `${location}_${server.name}`; + } else { + testUrl = url; + } + + this.updateProgress(location, testType, server); + + try { + const result = await this.measureSpeed(testUrl, duration); + if (this.testCancelled) return; + + this.results[testType][testKey] = { + ...result, + server_url: url, + test_url: testUrl, + server_name: server ? server.name : 'Direct', + location: location + }; + + this.updateLiveResults(testKey, testType, this.results[testType][testKey]); + } catch (error) { + if (this.testCancelled) return; + + // Don't log or update UI for cancelled tests + if (error.message === 'Test cancelled') { + return; + } + + console.error(`Test failed for ${location} (${testType}):`, error); + this.results[testType][testKey] = { + error: error.message, + server_url: url, + test_url: testUrl, + server_name: server ? server.name : 'Direct', + location: location + }; + + this.updateLiveResults(testKey, testType, this.results[testType][testKey]); + } + + this.currentTestIndex++; + } + + async measureSpeed(url, duration) { + const startTime = performance.now(); + let totalBytes = 0; + + // Create AbortController for this request + const abortController = new AbortController(); + this.activeAbortControllers.add(abortController); + + try { + // Check if test was cancelled before starting + if (this.testCancelled) { + throw new Error('Test cancelled'); + } + + const response = await fetch(url, { + method: 'GET', + headers: { + 'Range': 'bytes=0-' + }, + signal: abortController.signal + }); + + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + + const reader = response.body.getReader(); + + while (true) { + // Check for cancellation in the reading loop + if (this.testCancelled) { + reader.cancel(); + throw new Error('Test cancelled'); + } + + const {done, value} = await reader.read(); + + if (done) break; + + totalBytes += value.length; + const currentTime = performance.now(); + + // Check if duration exceeded + if (currentTime - startTime >= duration * 1000) { + reader.cancel(); + break; + } + } + + // Final cancellation check before returning results + if (this.testCancelled) { + throw new Error('Test cancelled'); + } + + const actualDuration = (performance.now() - startTime) / 1000; + const speedMbps = (totalBytes * 8) / (actualDuration * 1_000_000); + + return { + speed_mbps: Math.round(speedMbps * 100) / 100, + duration: Math.round(actualDuration * 100) / 100, + data_transferred: totalBytes, + timestamp: new Date().toISOString() + }; + } catch (error) { + // If it's an abort error and test was cancelled, don't propagate the error + if (error.name === 'AbortError' && this.testCancelled) { + throw new Error('Test cancelled'); + } + throw new Error(`Network error: ${error.message}`); + } finally { + // Clean up the abort controller + this.activeAbortControllers.delete(abortController); + } + } + + updateProgress(location, testType, server) { + const progress = this.totalTests > 0 ? (this.currentTestIndex / this.totalTests) * 100 : 0; + const progressPercent = Math.min(Math.round(progress), 100); // Cap at 100% + + document.getElementById('progressBar').style.width = `${progressPercent}%`; + document.getElementById('progressText').textContent = `${progressPercent}% complete`; + + const serverName = server ? server.name : 'Direct'; + document.getElementById('currentTest').textContent = `Testing ${location} via ${serverName} (${testType})...`; + } + + updateLiveResults(testKey, testType, result) { + const liveResults = document.getElementById('liveResults'); + + let card = document.getElementById(`result-${testKey}-${testType}`); + if (!card) { + card = document.createElement('div'); + card.id = `result-${testKey}-${testType}`; + card.className = 'result-card bg-white dark:bg-gray-800 rounded-lg shadow-lg p-4'; + liveResults.appendChild(card); + } + + const speedText = result.error + ? `Error: ${result.error}` + : `${result.speed_mbps} Mbps`; + + const badgeColor = testType === 'proxy' + ? 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200' + : 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-200'; + + card.innerHTML = ` +
+

${result.location}

+ + ${result.server_name} + +
+
+ ${speedText} +
+ ${!result.error ? ` +
+ ${(result.data_transferred / 1024 / 1024).toFixed(2)} MB in ${result.duration}s +
+ ` : ''} + `; + } + + renderMetrics() { + const proxyResults = Object.values(this.results.proxy).filter(r => !r.error); + const directResults = Object.values(this.results.direct).filter(r => !r.error); + + // Find best proxy result + const bestProxyResult = proxyResults.length > 0 + ? proxyResults.reduce((best, current) => + current.speed_mbps > best.speed_mbps ? current : best) + : null; + + // Find best direct result + const bestDirectResult = directResults.length > 0 + ? directResults.reduce((best, current) => + current.speed_mbps > best.speed_mbps ? current : best) + : null; + + const bestProxySpeed = bestProxyResult ? bestProxyResult.speed_mbps : 0; + const bestDirectSpeed = bestDirectResult ? bestDirectResult.speed_mbps : 0; + + // Calculate averages + const avgProxySpeed = proxyResults.length > 0 + ? proxyResults.reduce((sum, r) => sum + r.speed_mbps, 0) / proxyResults.length + : 0; + + // Speed difference based on best speeds (more relevant) + const speedDifference = bestDirectSpeed > 0 + ? ((bestProxySpeed - bestDirectSpeed) / bestDirectSpeed * 100) + : 0; + + // Update metrics + document.getElementById('bestProxySpeed').textContent = `${bestProxySpeed.toFixed(2)} Mbps`; + document.getElementById('bestDirectSpeed').textContent = `${bestDirectSpeed.toFixed(2)} Mbps`; + document.getElementById('avgProxySpeed').textContent = `${avgProxySpeed.toFixed(2)} Mbps`; + document.getElementById('speedDifference').textContent = `${speedDifference >= 0 ? '+' : ''}${speedDifference.toFixed(1)}%`; + + // Update additional info + document.getElementById('bestProxyServer').textContent = bestProxyResult + ? `${bestProxyResult.server_name} - ${bestProxyResult.location}` + : '--'; + document.getElementById('bestDirectLocation').textContent = bestDirectResult + ? bestDirectResult.location + : '--'; + document.getElementById('proxyTestCount').textContent = `${proxyResults.length} tests`; + + // Update metric card colors based on performance + const bestProxyMetric = document.getElementById('bestProxyMetric'); + const speedDiffMetric = document.getElementById('speedDiffMetric'); + + // Reset classes + bestProxyMetric.className = 'metric-card text-white p-4 rounded-lg text-center'; + speedDiffMetric.className = 'metric-card text-white p-4 rounded-lg text-center'; + + if (bestProxySpeed >= bestDirectSpeed * 0.8) { + bestProxyMetric.className += ' success'; + } else if (bestProxySpeed >= bestDirectSpeed * 0.5) { + bestProxyMetric.className += ' warning'; + } + + if (speedDifference >= -20) { + speedDiffMetric.className += ' success'; + } else { + speedDiffMetric.className += ' warning'; + } + + // Render server comparison metrics + this.renderServerMetrics(); + } + + renderServerMetrics() { + const serverComparisonGrid = document.getElementById('serverComparisonGrid'); + const proxyResults = Object.values(this.results.proxy).filter(r => !r.error); + + // Group results by server + const serverStats = {}; + proxyResults.forEach(result => { + if (!serverStats[result.server_name]) { + serverStats[result.server_name] = []; + } + serverStats[result.server_name].push(result); + }); + + const serverMetrics = Object.entries(serverStats).map(([serverName, results]) => { + const speeds = results.map(r => r.speed_mbps); + const avgSpeed = speeds.reduce((sum, speed) => sum + speed, 0) / speeds.length; + const bestSpeed = Math.max(...speeds); + const testCount = results.length; + const bestLocation = results.find(r => r.speed_mbps === bestSpeed)?.location || '--'; + + return { + name: serverName, + avgSpeed, + bestSpeed, + testCount, + bestLocation + }; + }).sort((a, b) => b.bestSpeed - a.bestSpeed); + + serverComparisonGrid.innerHTML = serverMetrics.map((server, index) => { + const rankClass = index === 0 ? 'border-green-500 bg-green-50 dark:bg-green-900/20' : + index === 1 ? 'border-blue-500 bg-blue-50 dark:bg-blue-900/20' : + 'border-gray-300 dark:border-gray-600'; + + const rankIcon = index === 0 ? '🥇' : index === 1 ? '🥈' : index === 2 ? '🥉' : `#${index + 1}`; + + return ` +
+
+

${server.name}

+ ${rankIcon} +
+
+
+ Best Speed: + ${server.bestSpeed.toFixed(2)} Mbps +
+
+ Avg Speed: + ${server.avgSpeed.toFixed(2)} Mbps +
+
+ Best Location: + ${server.bestLocation} +
+
+ Tests: + ${server.testCount} +
+
+
+ `; + }).join(''); + + if (serverMetrics.length === 0) { + serverComparisonGrid.innerHTML = ` +
+ No proxy test results available +
+ `; + } + } + + renderCharts() { + const isDark = html.classList.contains('dark'); + const textColor = isDark ? '#e5e7eb' : '#374151'; + const gridColor = isDark ? '#374151' : '#e5e7eb'; + + // Add a small delay to ensure DOM is ready + setTimeout(() => { + // Speed Comparison Chart + this.renderSpeedChart(textColor, gridColor); + + // Server Performance Chart + this.renderServerChart(textColor, gridColor); + }, 100); + } + + setupResizeHandler() { + let resizeTimeout; + window.addEventListener('resize', () => { + clearTimeout(resizeTimeout); + resizeTimeout = setTimeout(() => { + if (document.getElementById('resultsView').classList.contains('hidden')) { + return; // Don't resize if results view is not visible + } + this.renderCharts(); + }, 250); + }); + } + + renderSpeedChart(textColor, gridColor) { + const canvas = document.getElementById('speedChart'); + const ctx = canvas.getContext('2d'); + + if (this.charts.speedChart) { + this.charts.speedChart.destroy(); + } + + // Ensure proper canvas sizing + const container = canvas.parentElement; + const containerRect = container.getBoundingClientRect(); + canvas.style.width = '100%'; + canvas.style.height = '100%'; + + const locations = [...new Set([ + ...Object.values(this.results.proxy).map(r => r.location), + ...Object.values(this.results.direct).map(r => r.location) + ])].filter(Boolean); + + if (locations.length === 0) { + return; + } + + // Group proxy results by server and location + const serverNames = [...new Set(Object.values(this.results.proxy).map(r => r.server_name))]; + + // Create datasets for each server + direct + const datasets = []; + + // Color palette for different servers + const colors = [ + {bg: 'rgba(59, 130, 246, 0.8)', border: 'rgba(59, 130, 246, 1)'}, // Blue + {bg: 'rgba(16, 185, 129, 0.8)', border: 'rgba(16, 185, 129, 1)'}, // Green + {bg: 'rgba(245, 158, 11, 0.8)', border: 'rgba(245, 158, 11, 1)'}, // Yellow + {bg: 'rgba(239, 68, 68, 0.8)', border: 'rgba(239, 68, 68, 1)'}, // Red + {bg: 'rgba(168, 85, 247, 0.8)', border: 'rgba(168, 85, 247, 1)'}, // Purple + ]; + + // Add datasets for each server + serverNames.forEach((serverName, index) => { + const color = colors[index % colors.length]; + const data = locations.map(location => { + const result = Object.values(this.results.proxy).find(r => + r.location === location && r.server_name === serverName && !r.error + ); + return result ? result.speed_mbps : 0; + }); + + datasets.push({ + label: `${serverName} (Proxy)`, + data: data, + backgroundColor: color.bg, + borderColor: color.border, + borderWidth: 1 + }); + }); + + // Add direct speed dataset + const directData = locations.map(location => { + const result = this.results.direct[location]; + return result && !result.error ? result.speed_mbps : 0; + }); + + datasets.push({ + label: 'Direct Connection', + data: directData, + backgroundColor: 'rgba(107, 114, 128, 0.8)', + borderColor: 'rgba(107, 114, 128, 1)', + borderWidth: 1 + }); + + this.charts.speedChart = new Chart(ctx, { + type: 'bar', + data: { + labels: locations, + datasets: datasets + }, + options: { + responsive: true, + maintainAspectRatio: false, + interaction: { + intersect: false, + }, + plugins: { + legend: { + labels: { + color: textColor, + usePointStyle: true, + padding: 15, + font: { + size: 12 + } + } + } + }, + scales: { + y: { + beginAtZero: true, + title: { + display: true, + text: 'Speed (Mbps)', + color: textColor + }, + ticks: { + color: textColor, + maxTicksLimit: 8 + }, + grid: { + color: gridColor + } + }, + x: { + ticks: { + color: textColor, + maxRotation: 45 + }, + grid: { + color: gridColor + } + } + } + } + }); + } + + renderServerChart(textColor, gridColor) { + const canvas = document.getElementById('serverChart'); + const ctx = canvas.getContext('2d'); + + if (!ctx) { + console.error('Failed to get canvas context'); + return; + } + + if (this.charts.serverChart) { + this.charts.serverChart.destroy(); + } + + // Ensure proper canvas sizing + const container = canvas.parentElement; + const containerRect = container.getBoundingClientRect(); + canvas.style.width = '100%'; + canvas.style.height = '100%'; + + // Group results by server + const serverStats = {}; + + Object.values(this.results.proxy).forEach(result => { + if (!result.error && result.server_name) { + if (!serverStats[result.server_name]) { + serverStats[result.server_name] = []; + } + serverStats[result.server_name].push(result.speed_mbps); + } + }); + + const serverNames = Object.keys(serverStats); + + if (serverNames.length === 0) { + // Show a message for no data + ctx.fillStyle = textColor; + ctx.font = '16px Arial'; + ctx.textAlign = 'center'; + ctx.fillText('No server data available', canvas.width / 2, canvas.height / 2); + return; + } + + // Use a bar chart instead of radar for better clarity + const avgSpeeds = serverNames.map(name => { + const speeds = serverStats[name]; + return speeds.reduce((sum, speed) => sum + speed, 0) / speeds.length; + }); + const maxSpeeds = serverNames.map(name => Math.max(...serverStats[name])); + const minSpeeds = serverNames.map(name => Math.min(...serverStats[name])); + + this.charts.serverChart = new Chart(ctx, { + type: 'bar', + data: { + labels: serverNames, + datasets: [ + { + label: 'Best Speed', + data: maxSpeeds, + backgroundColor: 'rgba(34, 197, 94, 0.8)', + borderColor: 'rgba(34, 197, 94, 1)', + borderWidth: 1, + order: 1 + }, + { + label: 'Average Speed', + data: avgSpeeds, + backgroundColor: 'rgba(59, 130, 246, 0.8)', + borderColor: 'rgba(59, 130, 246, 1)', + borderWidth: 1, + order: 2 + }, + { + label: 'Worst Speed', + data: minSpeeds, + backgroundColor: 'rgba(239, 68, 68, 0.8)', + borderColor: 'rgba(239, 68, 68, 1)', + borderWidth: 1, + order: 3 + } + ] + }, + options: { + responsive: true, + maintainAspectRatio: false, + interaction: { + intersect: false, + }, + plugins: { + legend: { + labels: { + color: textColor, + usePointStyle: true, + padding: 15, + font: { + size: 12 + } + } + }, + tooltip: { + callbacks: { + afterLabel: function (context) { + const serverName = context.label; + const speeds = serverStats[serverName]; + return `Tests: ${speeds.length}`; + } + } + } + }, + scales: { + y: { + beginAtZero: true, + title: { + display: true, + text: 'Speed (Mbps)', + color: textColor + }, + ticks: { + color: textColor, + maxTicksLimit: 8 + }, + grid: { + color: gridColor + } + }, + x: { + ticks: { + color: textColor, + maxRotation: 45 + }, + grid: { + color: gridColor + } + } + } + } + }); + } + + renderDetailedResults() { + const detailedResults = document.getElementById('detailedResults'); + const locations = [...new Set([ + ...Object.values(this.results.proxy).map(r => r.location), + ...Object.values(this.results.direct).map(r => r.location) + ])].filter(Boolean); + + detailedResults.innerHTML = locations.map(location => { + const proxyResults = Object.values(this.results.proxy).filter(r => r.location === location); + const directResult = this.results.direct[location]; + + return ` +
+

${location}

+
+ ${proxyResults.map(result => ` +
+
+ ${result.server_name} +
+ ${result.error ? ` +
Error: ${result.error}
+ ` : ` +
${result.speed_mbps} Mbps
+
+ ${(result.data_transferred / 1024 / 1024).toFixed(2)} MB in ${result.duration}s +
+ `} +
+ `).join('')} + + ${directResult ? ` +
+
+ Direct +
+ ${directResult.error ? ` +
Error: ${directResult.error}
+ ` : ` +
${directResult.speed_mbps} Mbps
+
+ ${(directResult.data_transferred / 1024 / 1024).toFixed(2)} MB in ${directResult.duration}s +
+ `} +
+ ` : '
Direct test not performed
'} +
+
+ `; + }).join(''); + } +} + + +// Initialize the speed test when the page loads +let speedTest; +document.addEventListener('DOMContentLoaded', () => { + speedTest = new MediaFlowSpeedTest(); +}); \ No newline at end of file diff --git a/mediaflow_proxy/utils/__pycache__/__init__.cpython-313.pyc b/mediaflow_proxy/utils/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..148e6c7500b3e4e025cb75a28d16109b55129437 GIT binary patch literal 181 zcmey&%ge<81Q$F#GC=fW5CH>>P{wB#AY&>+I)f&o-%5reCLr%KNa|LIenx(7s(xuv zPQJcNesXDUYFFhT$T literal 0 HcmV?d00001 diff --git a/mediaflow_proxy/utils/__pycache__/cache_utils.cpython-313.pyc b/mediaflow_proxy/utils/__pycache__/cache_utils.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c0fa4fa38d5532997fa5d20b3e2796bb3a0ed369 GIT binary patch literal 18492 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 literal 0 HcmV?d00001 diff --git a/mediaflow_proxy/utils/__pycache__/crypto_utils.cpython-313.pyc b/mediaflow_proxy/utils/__pycache__/crypto_utils.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..03997f18236aef41bffc09ce90ea48607cd61b62 GIT binary patch literal 8348 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%0A^Pmw^ zy!rGd%Wv|~{3&Zz{T?`{zm_=_de5O~jtB3$Smy>|=@I&esLuJ7C9I)n~}QkL2Bu(;^TFKGmUp%}A$I zAxenB2VALRw(ya+13#e965N@)*skFKS;a&Zg)fro$HxvpnbXmWGPUF6Xg@PjDmGXU z@P;feBxpp1U}M+__A$u=hoh>y>lu0j>hL~f3K6)_IPODY|B#e?NE|G7eMG81B8|T! zwI7ka|H^(Jk=~C;+b>DmuZ_F8aqbg>@#C}H^Bi}QOI5c&CM>scMMq9>BV4xs9Q@Md aEvr|utw+FM5r%73A>P3jzp8f}TI3J1t literal 0 HcmV?d00001 diff --git a/mediaflow_proxy/utils/__pycache__/http_utils.cpython-313.pyc b/mediaflow_proxy/utils/__pycache__/http_utils.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..950f4088a7e9ab87dda3fe375e533c2b4f75d8f1 GIT binary patch literal 32309 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 Qkb$pTvi*{yH=|1bA4+pA?f?J) literal 0 HcmV?d00001 diff --git a/mediaflow_proxy/utils/__pycache__/m3u8_processor.cpython-313.pyc b/mediaflow_proxy/utils/__pycache__/m3u8_processor.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d16658900c12c7b499651e697b8d60cd0dd303a0 GIT binary patch literal 9293 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 z Optional[SpeedTestTask]: - """Get speed test results from cache.""" - cached_data = await SPEEDTEST_CACHE.get(task_id) - if cached_data is not None: - try: - return SpeedTestTask.model_validate_json(cached_data.decode()) - except ValidationError as e: - logger.error(f"Error parsing cached speed test data: {e}") - await SPEEDTEST_CACHE.delete(task_id) - return None - - -async def set_cache_speedtest(task_id: str, task: SpeedTestTask) -> bool: - """Cache speed test results.""" - try: - return await SPEEDTEST_CACHE.set(task_id, task.model_dump_json().encode()) - except Exception as e: - logger.error(f"Error caching speed test data: {e}") - return False - - async def get_cached_extractor_result(key: str) -> Optional[dict]: """Get extractor result from cache.""" cached_data = await EXTRACTOR_CACHE.get(key) diff --git a/mediaflow_proxy/utils/http_utils.py b/mediaflow_proxy/utils/http_utils.py index 76cfe00..cb23d21 100644 --- a/mediaflow_proxy/utils/http_utils.py +++ b/mediaflow_proxy/utils/http_utils.py @@ -175,7 +175,9 @@ class Streamer: logger.warning(f"Remote server closed connection prematurely: {e}") # If we've received some data, just log the warning and return normally if self.bytes_transferred > 0: - logger.info(f"Partial content received ({self.bytes_transferred} bytes). Continuing with available data.") + logger.info( + f"Partial content received ({self.bytes_transferred} bytes). Continuing with available data." + ) return else: # If we haven't received any data, raise an error @@ -375,6 +377,70 @@ def encode_mediaflow_proxy_url( return url +def encode_stremio_proxy_url( + stremio_proxy_url: str, + destination_url: str, + request_headers: typing.Optional[dict] = None, + response_headers: typing.Optional[dict] = None, +) -> str: + """ + Encodes a Stremio proxy URL with destination URL and headers. + + Format: http://127.0.0.1:11470/proxy/d=&h=&r=/ + + Args: + stremio_proxy_url (str): The base Stremio proxy URL. + destination_url (str): The destination URL to proxy. + request_headers (dict, optional): Headers to include as query parameters. Defaults to None. + response_headers (dict, optional): Response headers to include as query parameters. Defaults to None. + + Returns: + str: The encoded Stremio proxy URL. + """ + # Parse the destination URL to separate origin, path, and query + parsed_dest = parse.urlparse(destination_url) + dest_origin = f"{parsed_dest.scheme}://{parsed_dest.netloc}" + dest_path = parsed_dest.path.lstrip("/") + dest_query = parsed_dest.query + + # Prepare query parameters list for proper handling of multiple headers + query_parts = [] + + # Add destination origin (scheme + netloc only) with proper encoding + query_parts.append(f"d={parse.quote_plus(dest_origin)}") + + # Add request headers + if request_headers: + for key, value in request_headers.items(): + header_string = f"{key}:{value}" + query_parts.append(f"h={parse.quote_plus(header_string)}") + + # Add response headers + if response_headers: + for key, value in response_headers.items(): + header_string = f"{key}:{value}" + query_parts.append(f"r={parse.quote_plus(header_string)}") + + # Ensure base_url doesn't end with a slash for consistent handling + base_url = stremio_proxy_url.rstrip("/") + + # Construct the URL path with query string + query_string = "&".join(query_parts) + + # Build the final URL: /proxy/{opts}/{pathname}{search} + url_path = f"/proxy/{query_string}" + + # Append the path from destination URL + if dest_path: + url_path = f"{url_path}/{dest_path}" + + # Append the query string from destination URL + if dest_query: + url_path = f"{url_path}?{dest_query}" + + return f"{base_url}{url_path}" + + def get_original_scheme(request: Request) -> str: """ Determines the original scheme (http or https) of the request. @@ -509,7 +575,9 @@ class EnhancedStreamingResponse(Response): logger.warning(f"Remote protocol error after partial streaming: {e}") try: await send({"type": "http.response.body", "body": b"", "more_body": False}) - logger.info(f"Response finalized after partial content ({self.actual_content_length} bytes transferred)") + logger.info( + f"Response finalized after partial content ({self.actual_content_length} bytes transferred)" + ) except Exception as close_err: logger.warning(f"Could not finalize response after remote error: {close_err}") else: diff --git a/mediaflow_proxy/utils/m3u8_processor.py b/mediaflow_proxy/utils/m3u8_processor.py index 1c6e743..b71fe51 100644 --- a/mediaflow_proxy/utils/m3u8_processor.py +++ b/mediaflow_proxy/utils/m3u8_processor.py @@ -3,8 +3,9 @@ import re from typing import AsyncGenerator from urllib import parse +from mediaflow_proxy.configs import settings from mediaflow_proxy.utils.crypto_utils import encryption_handler -from mediaflow_proxy.utils.http_utils import encode_mediaflow_proxy_url, get_original_scheme +from mediaflow_proxy.utils.http_utils import encode_mediaflow_proxy_url, encode_stremio_proxy_url, get_original_scheme class M3U8Processor: @@ -39,7 +40,7 @@ class M3U8Processor: if "URI=" in line: processed_lines.append(await self.process_key_line(line, base_url)) elif not line.startswith("#") and line.strip(): - processed_lines.append(await self.proxy_url(line, base_url)) + processed_lines.append(await self.proxy_content_url(line, base_url)) else: processed_lines.append(line) return "\n".join(processed_lines) @@ -104,7 +105,7 @@ class M3U8Processor: if "URI=" in line: return await self.process_key_line(line, base_url) elif not line.startswith("#") and line.strip(): - return await self.proxy_url(line, base_url) + return await self.proxy_content_url(line, base_url) else: return line @@ -129,9 +130,9 @@ class M3U8Processor: line = line.replace(f'URI="{original_uri}"', f'URI="{new_uri}"') return line - async def proxy_url(self, url: str, base_url: str) -> str: + async def proxy_content_url(self, url: str, base_url: str) -> str: """ - Proxies a URL, encoding it with the MediaFlow proxy URL. + Proxies a content URL based on the configured routing strategy. Args: url (str): The URL to proxy. @@ -141,6 +142,51 @@ class M3U8Processor: str: The proxied URL. """ full_url = parse.urljoin(base_url, url) + + # Determine routing strategy based on configuration + routing_strategy = settings.m3u8_content_routing + + # For playlist URLs, always use MediaFlow proxy regardless of strategy + if ".m3u" in full_url: + return await self.proxy_url(full_url, base_url, use_full_url=True) + + # Route non-playlist content URLs based on strategy + if routing_strategy == "direct": + # Return the URL directly without any proxying + return full_url + elif routing_strategy == "stremio" and settings.stremio_proxy_url: + # Use Stremio proxy for content URLs + query_params = dict(self.request.query_params) + request_headers = {k[2:]: v for k, v in query_params.items() if k.startswith("h_")} + response_headers = {k[2:]: v for k, v in query_params.items() if k.startswith("r_")} + + return encode_stremio_proxy_url( + settings.stremio_proxy_url, + full_url, + request_headers=request_headers if request_headers else None, + response_headers=response_headers if response_headers else None, + ) + else: + # Default to MediaFlow proxy (routing_strategy == "mediaflow" or fallback) + return await self.proxy_url(full_url, base_url, use_full_url=True) + + async def proxy_url(self, url: str, base_url: str, use_full_url: bool = False) -> str: + """ + Proxies a URL, encoding it with the MediaFlow proxy URL. + + Args: + url (str): The URL to proxy. + base_url (str): The base URL to resolve relative URLs. + use_full_url (bool): Whether to use the URL as-is (True) or join with base_url (False). + + Returns: + str: The proxied URL. + """ + if use_full_url: + full_url = url + else: + full_url = parse.urljoin(base_url, url) + query_params = dict(self.request.query_params) has_encrypted = query_params.pop("has_encrypted", False) # Remove the response headers from the query params to avoid it being added to the consecutive requests diff --git a/mediaflow_proxy/utils/mpd_utils.py b/mediaflow_proxy/utils/mpd_utils.py index 7e14e26..28cb3c3 100644 --- a/mediaflow_proxy/utils/mpd_utils.py +++ b/mediaflow_proxy/utils/mpd_utils.py @@ -253,6 +253,16 @@ def parse_representation( profile["frameRate"] = round(int(frame_rate.split("/")[0]) / int(frame_rate.split("/")[1]), 3) profile["sar"] = representation.get("@sar", "1:1") + # Extract segment template start number for adaptive sequence calculation + segment_template_data = adaptation.get("SegmentTemplate") or representation.get("SegmentTemplate") + if segment_template_data: + try: + profile["segment_template_start_number"] = int(segment_template_data.get("@startNumber", 1)) + except (ValueError, TypeError): + profile["segment_template_start_number"] = 1 + else: + profile["segment_template_start_number"] = 1 + if parse_segment_profile_id is None or profile["id"] != parse_segment_profile_id: return profile @@ -502,6 +512,12 @@ def create_segment_data(segment: Dict, item: dict, profile: dict, source: str, t "number": segment["number"], } + # Add time and duration metadata for adaptive sequence calculation + if "time" in segment: + segment_data["time"] = segment["time"] + if "duration" in segment: + segment_data["duration_mpd_timescale"] = segment["duration"] + if "start_time" in segment and "end_time" in segment: segment_data.update( {