From 8134936d59e0fa1a566de35d50f7da0380c18ee8 Mon Sep 17 00:00:00 2001 From: UrloMythus Date: Wed, 15 Apr 2026 19:23:14 +0200 Subject: [PATCH] new version --- .../__pycache__/__init__.cpython-313.pyc | Bin 162 -> 162 bytes .../__pycache__/configs.cpython-313.pyc | Bin 5823 -> 6253 bytes .../__pycache__/const.cpython-313.pyc | Bin 406 -> 406 bytes .../__pycache__/handlers.cpython-313.pyc | Bin 44613 -> 53264 bytes .../__pycache__/main.cpython-313.pyc | Bin 13874 -> 14006 bytes .../__pycache__/middleware.cpython-313.pyc | Bin 1637 -> 1637 bytes .../__pycache__/mpd_processor.cpython-313.pyc | Bin 23146 -> 32467 bytes .../__pycache__/schemas.cpython-313.pyc | Bin 15442 -> 15659 bytes mediaflow_proxy/configs.py | 18 +- .../drm/__pycache__/__init__.cpython-313.pyc | Bin 979 -> 979 bytes .../drm/__pycache__/decrypter.cpython-313.pyc | Bin 63239 -> 63897 bytes mediaflow_proxy/drm/decrypter.py | 45 +- .../__pycache__/F16Px.cpython-313.pyc | Bin 5489 -> 5489 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 173 -> 173 bytes .../__pycache__/base.cpython-313.pyc | Bin 7600 -> 8176 bytes .../__pycache__/city.cpython-313.pyc | Bin 0 -> 7049 bytes .../__pycache__/dlhd.cpython-313.pyc | Bin 32191 -> 0 bytes .../__pycache__/doodstream.cpython-313.pyc | Bin 2906 -> 10974 bytes .../__pycache__/factory.cpython-313.pyc | Bin 3729 -> 3824 bytes .../__pycache__/fastream.cpython-313.pyc | Bin 2248 -> 2248 bytes .../__pycache__/filelions.cpython-313.pyc | Bin 1630 -> 1630 bytes .../__pycache__/filemoon.cpython-313.pyc | Bin 2794 -> 2794 bytes .../__pycache__/gupload.cpython-313.pyc | Bin 3134 -> 3134 bytes .../__pycache__/livetv.cpython-313.pyc | Bin 12554 -> 12554 bytes .../__pycache__/lulustream.cpython-313.pyc | Bin 1805 -> 2655 bytes .../__pycache__/maxstream.cpython-313.pyc | Bin 3617 -> 3617 bytes .../__pycache__/mixdrop.cpython-313.pyc | Bin 1457 -> 1457 bytes .../__pycache__/okru.cpython-313.pyc | Bin 2266 -> 2266 bytes .../__pycache__/sportsonline.cpython-313.pyc | Bin 8095 -> 10552 bytes .../__pycache__/streamtape.cpython-313.pyc | Bin 1695 -> 1695 bytes .../__pycache__/streamwish.cpython-313.pyc | Bin 3695 -> 3695 bytes .../__pycache__/supervideo.cpython-313.pyc | Bin 3712 -> 3871 bytes .../__pycache__/turbovidplay.cpython-313.pyc | Bin 2330 -> 2330 bytes .../__pycache__/uqload.cpython-313.pyc | Bin 1525 -> 2449 bytes .../__pycache__/vavoo.cpython-313.pyc | Bin 11295 -> 16612 bytes .../__pycache__/vidfast.cpython-313.pyc | Bin 0 -> 6333 bytes .../__pycache__/vidmoly.cpython-313.pyc | Bin 2805 -> 2805 bytes .../__pycache__/vidoza.cpython-313.pyc | Bin 2930 -> 2930 bytes .../__pycache__/vixcloud.cpython-313.pyc | Bin 4596 -> 5039 bytes .../__pycache__/voe.cpython-313.pyc | Bin 4680 -> 4680 bytes mediaflow_proxy/extractors/base.py | 12 +- mediaflow_proxy/extractors/city.py | 158 ++++ mediaflow_proxy/extractors/dlhd.py | 704 ------------------ mediaflow_proxy/extractors/doodstream.py | 202 ++++- mediaflow_proxy/extractors/factory.py | 6 +- mediaflow_proxy/extractors/lulustream.py | 25 +- mediaflow_proxy/extractors/sportsonline.py | 186 +++-- mediaflow_proxy/extractors/supervideo.py | 9 +- mediaflow_proxy/extractors/uqload.py | 33 +- mediaflow_proxy/extractors/vavoo.py | 418 ++++++----- mediaflow_proxy/extractors/vidfast.py | 110 +++ mediaflow_proxy/extractors/vixcloud.py | 12 +- mediaflow_proxy/handlers.py | 248 +++++- mediaflow_proxy/main.py | 61 +- mediaflow_proxy/mpd_processor.py | 274 ++++++- .../__pycache__/__init__.cpython-313.pyc | Bin 955 -> 955 bytes .../audio_transcoder.cpython-313.pyc | Bin 13556 -> 0 bytes .../__pycache__/codec_utils.cpython-313.pyc | Bin 14817 -> 0 bytes .../container_probe.cpython-313.pyc | Bin 21810 -> 0 bytes .../__pycache__/ebml_parser.cpython-313.pyc | Bin 39016 -> 0 bytes .../__pycache__/hls_manifest.cpython-313.pyc | Bin 5474 -> 0 bytes .../__pycache__/media_source.cpython-313.pyc | Bin 12034 -> 0 bytes .../__pycache__/mkv_demuxer.cpython-313.pyc | Bin 20403 -> 0 bytes .../__pycache__/mp4_muxer.cpython-313.pyc | Bin 57356 -> 0 bytes .../__pycache__/mp4_parser.cpython-313.pyc | Bin 26375 -> 0 bytes .../__pycache__/pyav_demuxer.cpython-313.pyc | Bin 26222 -> 0 bytes .../transcode_handler.cpython-313.pyc | Bin 37682 -> 0 bytes .../transcode_pipeline.cpython-313.pyc | Bin 41184 -> 0 bytes .../__pycache__/ts_muxer.cpython-313.pyc | Bin 53600 -> 53600 bytes .../video_transcoder.cpython-313.pyc | Bin 16885 -> 0 bytes mediaflow_proxy/remuxer/media_source.py | 6 +- mediaflow_proxy/routes/__init__.py | 42 +- .../__pycache__/__init__.cpython-313.pyc | Bin 592 -> 1142 bytes .../__pycache__/acestream.cpython-313.pyc | Bin 22426 -> 0 bytes .../routes/__pycache__/epg.cpython-313.pyc | Bin 0 -> 8494 bytes .../__pycache__/extractor.cpython-313.pyc | Bin 11686 -> 12143 bytes .../playlist_builder.cpython-313.pyc | Bin 16020 -> 16020 bytes .../routes/__pycache__/proxy.cpython-313.pyc | Bin 35170 -> 27853 bytes .../__pycache__/speedtest.cpython-313.pyc | Bin 2054 -> 2054 bytes .../__pycache__/telegram.cpython-313.pyc | Bin 36104 -> 0 bytes .../routes/__pycache__/xtream.cpython-313.pyc | Bin 44682 -> 45092 bytes mediaflow_proxy/routes/acestream.py | 28 +- mediaflow_proxy/routes/epg.py | 203 +++++ mediaflow_proxy/routes/extractor.py | 25 +- mediaflow_proxy/routes/proxy.py | 240 +----- mediaflow_proxy/routes/telegram.py | 320 +++++--- mediaflow_proxy/routes/xtream.py | 31 +- mediaflow_proxy/schemas.py | 8 +- .../__pycache__/__init__.cpython-313.pyc | Bin 172 -> 172 bytes .../__pycache__/models.cpython-313.pyc | Bin 2300 -> 2300 bytes .../__pycache__/service.cpython-313.pyc | Bin 1862 -> 1862 bytes .../__pycache__/all_debrid.cpython-313.pyc | Bin 3410 -> 3410 bytes .../__pycache__/base.cpython-313.pyc | Bin 1615 -> 1615 bytes .../__pycache__/real_debrid.cpython-313.pyc | Bin 2539 -> 2539 bytes mediaflow_proxy/static/url_generator.html | 345 ++++++++- .../__pycache__/__init__.cpython-313.pyc | Bin 168 -> 168 bytes .../__pycache__/acestream.cpython-313.pyc | Bin 36813 -> 0 bytes .../utils/__pycache__/aes.cpython-313.pyc | Bin 1736 -> 1736 bytes .../utils/__pycache__/aesgcm.cpython-313.pyc | Bin 7016 -> 7016 bytes .../__pycache__/base64_utils.cpython-313.pyc | Bin 4500 -> 4500 bytes .../base_prebuffer.cpython-313.pyc | Bin 18246 -> 18449 bytes .../__pycache__/cache_utils.cpython-313.pyc | Bin 9426 -> 9426 bytes .../utils/__pycache__/codec.cpython-313.pyc | Bin 14197 -> 14197 bytes .../utils/__pycache__/compat.cpython-313.pyc | Bin 4727 -> 4727 bytes .../__pycache__/constanttime.cpython-313.pyc | Bin 6973 -> 6973 bytes .../__pycache__/crypto_utils.cpython-313.pyc | Bin 8308 -> 8308 bytes .../__pycache__/cryptomath.cpython-313.pyc | Bin 11866 -> 11866 bytes .../dash_prebuffer.cpython-313.pyc | Bin 17029 -> 18699 bytes .../__pycache__/deprecations.cpython-313.pyc | Bin 10680 -> 10680 bytes .../extractor_helpers.cpython-313.pyc | Bin 7752 -> 4501 bytes .../__pycache__/hls_prebuffer.cpython-313.pyc | Bin 23113 -> 23113 bytes .../__pycache__/hls_utils.cpython-313.pyc | Bin 4642 -> 0 bytes .../__pycache__/http_client.cpython-313.pyc | Bin 12690 -> 12680 bytes .../__pycache__/http_utils.cpython-313.pyc | Bin 42417 -> 42957 bytes .../m3u8_processor.cpython-313.pyc | Bin 30760 -> 31591 bytes .../__pycache__/mpd_utils.cpython-313.pyc | Bin 29258 -> 36941 bytes .../utils/__pycache__/packed.cpython-313.pyc | Bin 7812 -> 7812 bytes .../__pycache__/python_aes.cpython-313.pyc | Bin 5982 -> 5982 bytes .../__pycache__/python_aesgcm.cpython-313.pyc | Bin 636 -> 636 bytes .../rate_limit_handlers.cpython-313.pyc | Bin 8092 -> 8092 bytes .../__pycache__/redis_utils.cpython-313.pyc | Bin 33861 -> 35901 bytes .../__pycache__/rijndael.cpython-313.pyc | Bin 36300 -> 36300 bytes .../stream_transformers.cpython-313.pyc | Bin 9379 -> 9379 bytes .../__pycache__/telegram.cpython-313.pyc | Bin 53186 -> 0 bytes .../__pycache__/tlshashlib.cpython-313.pyc | Bin 1485 -> 1485 bytes .../utils/__pycache__/tlshmac.cpython-313.pyc | Bin 4666 -> 4666 bytes mediaflow_proxy/utils/base_prebuffer.py | 5 +- mediaflow_proxy/utils/dash_prebuffer.py | 64 +- mediaflow_proxy/utils/extractor_helpers.py | 93 +-- mediaflow_proxy/utils/http_client.py | 1 - mediaflow_proxy/utils/http_utils.py | 17 +- mediaflow_proxy/utils/m3u8_processor.py | 105 ++- mediaflow_proxy/utils/mpd_utils.py | 273 ++++++- mediaflow_proxy/utils/redis_utils.py | 52 ++ mediaflow_proxy/utils/telegram.py | 223 +++++- 135 files changed, 3013 insertions(+), 1589 deletions(-) create mode 100644 mediaflow_proxy/extractors/__pycache__/city.cpython-313.pyc delete mode 100644 mediaflow_proxy/extractors/__pycache__/dlhd.cpython-313.pyc create mode 100644 mediaflow_proxy/extractors/__pycache__/vidfast.cpython-313.pyc create mode 100644 mediaflow_proxy/extractors/city.py delete mode 100644 mediaflow_proxy/extractors/dlhd.py create mode 100644 mediaflow_proxy/extractors/vidfast.py delete mode 100644 mediaflow_proxy/remuxer/__pycache__/audio_transcoder.cpython-313.pyc delete mode 100644 mediaflow_proxy/remuxer/__pycache__/codec_utils.cpython-313.pyc delete mode 100644 mediaflow_proxy/remuxer/__pycache__/container_probe.cpython-313.pyc delete mode 100644 mediaflow_proxy/remuxer/__pycache__/ebml_parser.cpython-313.pyc delete mode 100644 mediaflow_proxy/remuxer/__pycache__/hls_manifest.cpython-313.pyc delete mode 100644 mediaflow_proxy/remuxer/__pycache__/media_source.cpython-313.pyc delete mode 100644 mediaflow_proxy/remuxer/__pycache__/mkv_demuxer.cpython-313.pyc delete mode 100644 mediaflow_proxy/remuxer/__pycache__/mp4_muxer.cpython-313.pyc delete mode 100644 mediaflow_proxy/remuxer/__pycache__/mp4_parser.cpython-313.pyc delete mode 100644 mediaflow_proxy/remuxer/__pycache__/pyav_demuxer.cpython-313.pyc delete mode 100644 mediaflow_proxy/remuxer/__pycache__/transcode_handler.cpython-313.pyc delete mode 100644 mediaflow_proxy/remuxer/__pycache__/transcode_pipeline.cpython-313.pyc delete mode 100644 mediaflow_proxy/remuxer/__pycache__/video_transcoder.cpython-313.pyc delete mode 100644 mediaflow_proxy/routes/__pycache__/acestream.cpython-313.pyc create mode 100644 mediaflow_proxy/routes/__pycache__/epg.cpython-313.pyc delete mode 100644 mediaflow_proxy/routes/__pycache__/telegram.cpython-313.pyc create mode 100644 mediaflow_proxy/routes/epg.py delete mode 100644 mediaflow_proxy/utils/__pycache__/acestream.cpython-313.pyc delete mode 100644 mediaflow_proxy/utils/__pycache__/hls_utils.cpython-313.pyc delete mode 100644 mediaflow_proxy/utils/__pycache__/telegram.cpython-313.pyc diff --git a/mediaflow_proxy/__pycache__/__init__.cpython-313.pyc b/mediaflow_proxy/__pycache__/__init__.cpython-313.pyc index c0093f8f2cff7835a5f1dab06516873344250d8e..6000a2a88d39fa53a8c8d3ab74b501bd28eed78e 100644 GIT binary patch delta 19 ZcmZ3)xQLPaGcPX}0}x1^yg!k9Ish>Q1zrFE delta 19 ZcmZ3)xQLPaGcPX}0}xD4m_CtvIsh=m1u6gl diff --git a/mediaflow_proxy/__pycache__/configs.cpython-313.pyc b/mediaflow_proxy/__pycache__/configs.cpython-313.pyc index 1da72df326833f837a0ac638e03fc94f39cd9cf6..522ec1b3705bad09b16e7269f8e2c318e12437bc 100644 GIT binary patch delta 1265 zcmZXT%TE(g6o+S~15{#bDUXy=>l-MgJOq58yhKny@KN6kj>B|XM*H$GRWWKYZj5d) z>e{$8aY52e(~Xavi6$8M1H9)j5~8#C=6>h??wNaMG9N;}I-IKxM(!IBB5f2oKxT(_T7Ho15Ohdo zEP9x%7dk0a4U!E(57{v?I~<@V3^!%*)B+roJR5sDP7whP zNryLTBUY_vfMc+%U zAO?zo(6YEAS6dK6#b#FBncKsvU+s<+8&}qtGgdjFtbC=M+uLYLHGz6N=ij|JzGPd9 zJg+Q>yNX`l;?&Y`LG+oqnpbtj=AhLfSZrd$9l7d)7&QB8?``q=7blmx3gUM2UG01R fsd=NWw_%3o>z~|TLPH*!M5_LHw delta 843 zcmYk3OHUI~7=~w#rA!oREfq&DjVWU3om(lCJCuSq5XZY?xTPYdf$)`rsIifii5tyX zzykdb+O3IU-6oqxeJk$#7fhDL7)(g|9 z9WDA`f;vpS{7L<#tkaB@vw>39RnGEuV-R70;#hbH7N>65Fw8|gXfsl>1Z)(Rq+aZ6 z43?rk%o~SIP(Q{l!Y@v(ngS<-KxB|UOLzp-To1$T});g?frP2{JoQ4S+J#z5Z zOIh+rW^kq}Ud6akrx-V5f`7lF(3o+HQj5ZW`#>|&Yimo(i`gHA$rF1*zIV(wIv-}{ zD4ku&tfz@)a+Eo6|4$01PNvktu5(qD4X(6Ga?2I=JmW?Ajxy>P5r)%(e_Vcb?RDH? zzstDCxX&;d4;V9yS;ib=p0OZ5)#$1wi#3grWe!#ts|@tY@>`Cv&e&i)VQk6?cTjv0 zg`9g>6-~|Z=V^bvT{+)Uw7K6b>?Gel-naR_o%0rL`cBin%_~E-@8tK|r!sx!qq-$^ bJyAccs%Kw4|Euux@R3le+^-WaHSFvk3s$z{ diff --git a/mediaflow_proxy/__pycache__/const.cpython-313.pyc b/mediaflow_proxy/__pycache__/const.cpython-313.pyc index 173fd13b79b8946f837641c4d28d6f4d362f38d3..82642d950ba26e592d7068c0ed840878a9135d5a 100644 GIT binary patch delta 20 acmbQnJdK(AGcPX}0}x1^yuXpVn-KstO9fv5 delta 20 acmbQnJdK(AGcPX}0}xD4n7)y_n-Kss*aa&9 diff --git a/mediaflow_proxy/__pycache__/handlers.cpython-313.pyc b/mediaflow_proxy/__pycache__/handlers.cpython-313.pyc index d3857cf3e49bda03bc7b90fd5867d60b0a208502..948bfca2542a066e419b31b3e82eec6ac8981e63 100644 GIT binary patch delta 16239 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

wI z*Ym~Baz4XsTY6!DU_XOPvkAP5`hPMA-x?$ka$wa~oL)D$txEM8mW? zolf=;ymf0VI-zPap*ZV@A%e79gx@Oen7S+HeQ72p+YA27{#0?}ZMo^LrD)aSg#R_` z@Mre=Rr`)*`;Ha6Yt`yXSzUj#+F!9Xfr**|s|+@BWHW9(SnHDSq4WPoN)Elou!n#%~tbY5w;Lm+2hv&+($l z6E-LYggu62Y-zIcR|aV>udn%o>l8@bB}}BP3bhkJbUB=unP<~Db|F!Dac%uV9kR7Z z!bl=WCXq}bi6X(TDi%W$2jVs}jEJ2_A{?8eGeJ1zOhw5PKr}NDIGON>tg|3$kX%D8 zfyLm$?-i5brJvQ-m=;8&v{c=HTGL^mK)jSRg}5H8{&+8haM>zghaQdC(v|!A&l2{}B+3xMhZ0 zqUR0Ih1Hp46wV)>e6eLv0&r_Zj&(_0F91C5a+tmgs?YiNT!-sIMBEoqI^bT%s4;>pr+k%KXqsAjD zBwE#UBQ!58wz*20=}>xJP6FzpYyr(^`OEFYW#0#HOt>_s8x~ZQG5(MC9%+ErbW}!O z2K_ZcB})7xo_e%^@D+uA7{xCxObu;FOpB?!cXOryEgla?3IgG zF1~W<%BAZQHzrn!I#z6+#i37RmuMHS8?U?HZob*_`-VmTU*zMWj_TGRG`wg$7<7-Z z`K4NqBuO>A-dkSi5#Jqb8_cIyp~U8a&0{4vcH(%OC-?2awr`N;zCKtfN?nMp@I3ha z!OBosi^?ufdP7+`iX79>A43zd#M5}6=jJ^c9I@$;#vGt!6-X-izr5A_y0_3Q>R~ni ziMQG;TC5ZMa=}zGITM^sM&Vl40<_g8%A&X+PJW}aZO2ZW$PNh)MK$c~Q;EcR){3t2 zGe`7X*1_*}?t9#YW9&cd9VFOdLV;z`IL}p{^ zezpulY%SUqzTD+?t&yHJ(zQnRiGTkZ8Ms3t%OrA#JhDbQ*2vL2L|rB-@7(DxuaVlX zTkaEh-8T|L**ybrU+iDHy7QW0;nn^dQ}3$pKKk*|)amh+=yR~O!%yLs_u zZ24>`H9oU)_NmlkOu)5Ih;bF>;jx}~YE9A!2|A2i@Z!Z4W946E%X7o`j6|*yhxosr CHpE2$ delta 4419 zcmb7Hdu*H46~CYT`4QVmocBBV;yg{>X_Asa+oUvo*o{+sNgAlZb?vW-TgNu{bJC~EZ(xgoSwxRuzG)U<_sERZ+snU=NWvcjR+Bw&8+R$_o zPW|(_=bqm^=bn4-xwpTa{Y@+@p4|d;Z^g?t*@m$RjkTW&8j^$ ztj1HzYCUzV&Z96zA(T*ux8CDoE>8n%@HDbUq099)d74?Xr-ikMcAmG@<7RFl=X>w- zw6QiJJH73m4%Xr6WSyQa)+KZW-fmA1>k)FHx7V|SrFVGxSf5Z7dHX#BY(OCa$sfm1 zw|-nCQ0h-^)nSVk(~?1hV6nSOlh=3FO_;+8Y?=Hw?w)0Vl2L zl4z|!l4VzBN>A1uE$Qdaz4M2XbKbN3-h)(H|O=ano!|ssoazOZr+I1vuV&a?;y7s-lPK z*gZ+xbE;_z^?glFO`49=BQWcS2aWffbuIOS1r^o6?0@6#WWw=$B%4A1FYfNA6WLxi z0h#^P9TwvoGMx+*!$RzX@1`;A{-c8iIzgu}IQl5mlB0nk`qldW-2{@+K*-H% zBmb%Wxq)Lj*@R|GP+Xdj{Ni59_GEsKIkIhk9_AZ8CQ6>Ly>_bItn2w{J?6NcFObQqDkSac11GtWNyD?;&0@3 z@M{i-RLP&tb!XaT*6iL;$g%1Soe+O5e=fIfgOSL!7f+k4#PW!6Z{p!WNrO&grK4P;5@s9W%WHeZh^8nG0E_a4eyztO;aR zg9^J)1eqF%YHkDTMV(>hjD}fP6xKz`L1B$WPpVon6lZ%tjXOfKOT5C_k^aI=Y*lMH z)k}M?SaO~}cmCWD?d#>OtL3e0meyZRt#^3f-$&NG7ws49R~&nep-y z{0~mlwS8i%T(>EgZOWR>wJy6>WYfA$A-W~DXSt@pXV^ImV=yQi z1X6bp!jWzX4auN*y6I z#uppx6aINwi%A8U^V;$;`{N+7ClHP=&zJwsAPw`*s`pzaLE|oD!`M-Ua2i0D!-?b~ zW7uv-IK}I#>(X^7)*{RxL=j>Lvk3U?WC?^P5i|g|nPEf>U!IwP&Cz5KYAQR0nlON7 z22jLDWSvE@2jS<~OJFiM@f%Juy8OrLYEya``==_=>oKF@gFq*-i!+qmiW&Kig~Q%U+L$ddA9OU9BNoJqMQh6>Oh~e5w$YmKg`fE z+6R&9t)f`6y zM?_7k@d(T>s%m=9=Jn41(l%Q1J+NTH%$#{KK;=2k>)ZR=N1z8MxcR}XV}frE0bC?h zqQpz$WPR^b%YUY=GBwv`rgz%N$l1 z7T1kzE-r2baQ!;iXU{u`44-WR#xQ%hHzBNj<-?-X z>DFLyHWZBqgYGHz%<_#6N!nRD3wKZGWGrz8H~qq5r+8s$466_;L?}WiMkqljMJVH| zT{TX)oCvExz@wnM_}lrVnOnWWMWuNDt9l?+R9(om)^@=X^AxCW4!{F%!48xT*PD`Sns0 z|50~a6W*M99wamr)j}s?>P#YWnsuO;ZUnqsSug*zd)K6>gFZAjLctf;fvENj%Q?eJ zt;@*-z8A3_LBIpb@KlKnE%uq77mbHwa}jmpMatKEnp?%H`O&TiL2Og&gR_aqTuj}~ z-Ul75Cea?{O}$;+w+vRvaEkzZhH$HpNUF3-BG<^|Rnl>l9Jof*Ria)cyRH#>l{~h+d2O6{*&ZW8#pX(Wb{8wQX*^GVAe^tKECoqY;AIOWg;$268w7q+cidMOuFG9vg2E4*Nhea QkS*UFy;>Zh delta 20 acmaFL^OT4CGcPX}0}xD4n7)xaiVXlkc?GWk diff --git a/mediaflow_proxy/__pycache__/mpd_processor.cpython-313.pyc b/mediaflow_proxy/__pycache__/mpd_processor.cpython-313.pyc index 925c50d0229d76ddd45f77cffe26eb7502d527e1..22904d12b759d261012038e0f38cad90581c344b 100644 GIT binary patch delta 15595 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 delta 6884 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 diff --git a/mediaflow_proxy/__pycache__/schemas.cpython-313.pyc b/mediaflow_proxy/__pycache__/schemas.cpython-313.pyc index e15268db5a6609f9e2fb598f922dbeffbecafd8e..076d8f3cff045c6084ce1ada3a996af86cd4e76c 100644 GIT binary patch delta 598 zcmcaqvAT-yGcPX}0}x1^yq{@jv60V6pHXUZA(z4A1;!GSJM}lJ#jq$b1ak$82eVi* z6$!?0$FQ*imB+9LOQ-|sV96q(bS_P)%}xd<8SAS`ohnOG6^at`(o+@E@{1HoGExW_g;VF3|-Ac7f0_=1SzAR-7v_=AWOAOaMyMQk8~ z2Sl6&5y2qB14M{|2w@Ph7DmW2GB6ZR0rJ`zKAJFa@^vI%kaxPs;rzjvft9mE{R5ML zIH&MOW=U?g&kT~RE{q>cCWlxRae(w|$`+lTe9o#*1?*3-iKw>R;;_lhPbtkwwJW+f pxxsq1x-cW#gxW6*K`0Z4(J2mtYLr62$R delta 326 zcmZ2ob*X~yGcPX}0}xD4n4alxzLC#JfAS(NvB`cW#*;hsH*y4v7YU|wX-aHPFgVFL znO9tK@&zNC$(v0@_-6w(6@iFfKwNxqvWfBj&B7*(jEp>!MNKzxtpZ7d2%gCoO&2h( znp|Y|h*5HKsJSkq*yK+0X{;@V->`Bc=CKJ36&!ty+I(t zA4D7l5v(AB4MgyOh*Oi-n@U)%0Wm>@BqPubQ-F+ihK~jeoO~V07v!BTayWm`XJF;* zQ2)Rr$|>=YS)7~gGlMv*3*!fa$-34>93Y*VvPCB*FSqVv13P%lWNRB)#&eS+ZC0!E lGqO#n{lWmGzNl~k*}h*GfYcW$6-Ln+vR@g1RFNpq005O{S(pF- diff --git a/mediaflow_proxy/configs.py b/mediaflow_proxy/configs.py index fbae17f..d80c3a1 100644 --- a/mediaflow_proxy/configs.py +++ b/mediaflow_proxy/configs.py @@ -64,14 +64,21 @@ class Settings(BaseSettings): dash_prebuffer_emergency_threshold: int = 90 # Emergency threshold percentage to trigger aggressive cache cleanup. dash_prebuffer_inactivity_timeout: int = 60 # Seconds of inactivity before cleaning up stream state. dash_segment_cache_ttl: int = 60 # TTL (seconds) for cached media segments; longer = better for slow playback. + dash_player_lock_timeout: float = 2.5 # Max wait (seconds) for player requests when a segment lock is busy. + dash_prebuffer_lock_timeout: float = 0.25 # Max wait (seconds) for background prebuffer lock acquisition. + dash_prefetch_max_concurrent: int = 1 # Max concurrent live DASH prefetch downloads to reduce lock contention. + dash_live_initial_media_prebuffer: bool = ( + False # Whether manifest-time prebuffer should fetch live media segments (init segments are still prewarmed). + ) mpd_live_init_cache_ttl: int = 60 # TTL (seconds) for live init segment cache; 0 disables caching. mpd_live_playlist_depth: int = 8 # Number of recent segments to expose per live playlist variant. remux_to_ts: bool = False # Remux fMP4 segments to MPEG-TS for ExoPlayer/VLC compatibility. processed_segment_cache_ttl: int = 60 # TTL (seconds) for caching processed (decrypted/remuxed) segments. - # FlareSolverr settings (for Cloudflare bypass) - flaresolverr_url: str | None = None # FlareSolverr service URL. Example: http://localhost:8191 - flaresolverr_timeout: int = 60 # Timeout (seconds) for FlareSolverr requests. + # Byparr settings — Firefox/Camoufox-based solver for Cloudflare bypass and chevy IP whitelist. + # https://github.com/ThePhaseless/Byparr (drop-in FlareSolverr-compatible API) + byparr_url: str | None = None # Byparr service URL. Example: http://localhost:8192 + byparr_timeout: int = 60 # Timeout (seconds) for Byparr requests. # Acestream settings enable_acestream: bool = False # Whether to enable Acestream proxy support. @@ -89,6 +96,8 @@ class Settings(BaseSettings): telegram_session_string: SecretStr | None = None # Persistent session string (avoids re-authentication). telegram_max_connections: int = 8 # Max parallel DC connections for downloads (max 20, careful of floods). telegram_request_timeout: int = 30 # Request timeout in seconds. + telegram_document_scan_limit: int = 500 # Max recent messages to scan when resolving chat_id+document_id. + telegram_document_cache_ttl: int = 3600 # TTL (seconds) for cached document_id->message_id mappings. # Transcode settings enable_transcode: bool = True # Whether to enable on-the-fly transcoding endpoints (MKV→fMP4, HLS VOD). @@ -105,6 +114,9 @@ class Settings(BaseSettings): upstream_retry_delay: float = 1.0 # Delay (seconds) between retry attempts. graceful_stream_end: bool = True # Return valid empty playlist instead of error when upstream fails. + # EPG proxy settings + epg_cache_ttl: int = 3600 # TTL (seconds) for cached EPG/XMLTV data. Default 1 hour. + # Redis settings redis_url: str | None = None # Redis URL for distributed locking and caching. None = disabled. cache_namespace: str | None = ( diff --git a/mediaflow_proxy/drm/__pycache__/__init__.cpython-313.pyc b/mediaflow_proxy/drm/__pycache__/__init__.cpython-313.pyc index ab17842d77f80c08edef7302906aaa88521745f6..0a378aa6804d31dcbd97dba3ff892b6b867064ab 100644 GIT binary patch delta 20 acmcc2ewm&7GcPX}0}x1^yuXqA1Tz3XWd1943fFss3=+q)v821G%eBb}N=bpUuB|mbLUwm`XB0I+(*N0b>=Xr8;@y0^(I7o&6s&oVd3)^Tg z`-@#%x0&V#oPKw}l*J?lI{0;4Poj3Cxjmte@+~F#QoXhCXt7Y z;bQY+MB?@BVJjiKA>LiHry&ClD{lDeu)W!|>S>OXp5(YKCcoKl@e9k0{@KdP)(a~i znl%&;^Pd@mdazfj(R57y>n`|bYBMpN1u6EHb^_pIGz z-eRswJ2{E%ZZVf}T^3JP@*NY*bf4|pBx^|;Q^gJOm@HONNejzV)WT9sq>`d4u`Z9e zBP_?nSX_)sS}@E!VvpP_1{tG67CYm~SV+9O`=?AJ+k;Xh!sLo7%0EcRK}`;cT3lqo zLJ>Kgp`%w+dz3^1!-V7@?dwfwN<7Bm7+Ez@qf)TP&Jl*XxQXf`EP&8|cf5jiTyG|5PVai!9>7C=byu!IGuE4$DJK~*oRbOybzuP$OIMj5} zwQ6YHUmUI(cj>VGmcV5#hOM#9S(({0IrqQZI%ngSMY$LUJLq_bR!g zf7yH4y2Qa6NZFozs=1|o-P(232^0H$K>C{m7u%pBwDkXq6vN;9D@lbezVM8dsdo*O z8!}pk6uk48OdqISVo1u-S(O&g59AX!*k9dDcI)l0UgI-M(2t)ZEr1P!t>mOWGWal0 zR>Gw{b;5$*4hb@)?}XE4mg`%?dj)qoSZcUXc$k@~I(Yku`{BkOC%LJ6N6JWM1rjP5 z1O?dmP_7<-tJ#u?jioEZo@&2;mqh%TenIZvL2?WdPR>V!yw@Q)KK zOzi}Y9(38GAw~Mi%yBmdVcR2ldi2-Zcp@`uBk&W&qz6hTR+u^$s7kE!KK3QhZoO|} z-bA8|<|h=+oL*syr75b`5vfX3NYG@5( zt4EC78-5oxlh@(o@3)d4LH@bTWG6(=RgyOSjdQK$%-68rpCAmvjgKD5d=t%=uxEhA z$z@~+!jqNchx*Xu-v~PtyQV71KKSKS85xFiQ$=L2K06gN^VCM5ZMrb)0ID@qXAtP0 z?jmo)jp>!-06Z`gCnHSZw6LovIHn(+Ibk9^bbee>R)8Ag6c(dp9JxPXi0cS=yl4{~ z``BIHj%Fu93?YfI6JZEp7-59Kz5)j<`6Qdv>F!T*NWnT{$sJoh$Kmhz%Hu6Z*W5Df zxmJ_@=%2@hA{})nS*jH2Nm<#Y#qJa}WoCv-4?xbfGRymnaYtW$?J7^+2lw?nVFD#O zth&D5lp*LZU+*?mActlZIrhz?igmXQfq zI$vwK#M}*f%e;?wyvIyWQOYLKE_V5>wn*U-YS^b(kE wuW_G6vlg8Wgj@zG+v;dMl#Iwt^fTCYd*kBudE|R!o-=;to~_nT-F}Sx2YbQ7=Kufz delta 1858 zcma)64NOy46n>}ed#(JH@KS}J7-TBC>Hb_%H>iOE6$;YA16*)!x=p7t zn3H2=&N(x2*~TUjUKUwwoGxY~+cYR~YS}Pqg42n^vFNg7%kF70#w?k;NxysUx!?KD zx%ZvhJrl~Ux0Kq6(9j@`jn1-8r}rYE+WW7Hc)y;;f=A z+L~k&tZJLy7H2cqglr|xLbEm2Wa8zJ4jWSZL)cUOstHyT(;~eTzox)+QvExL6uADn$CV1HkO!*|JPx{tke8)Y03XD5L*+kL6R&PuRgBXq~JIY z_k1|oW24l0Ox^ww*YUOvcUJ!jCVbUt#kQK)@IY>4m{n!va~PY3i)ULI`Fsvcu2m~k z=77mnhH+d;v6Bf>zqsHaiL>$t_s~e8HU=73R-S>Z}Yf8U-ec z2i>zHrmz!k;>Pt{NYr^<`WapN5Z-iehY-A7ze=x-_%R}W&^V{x+^-)@STqz-I2clR zK^w6xNG@s^RY9Mex2~9naNPaU6&RLVHvFItk7R;A%~Q`TDlA>NV4)O^rJHMo80M7X zad2~=pr`$03@?ho_qT*Vu>9$k)oLa&r9H(TD~ZIV2f$+U+q1&`u}S1K1ynY)$ASSp z?L}&blH(e?0UukpSf2gXJIde$V$2}W;l-Ux;RD&!k*9=gEb~kkCjA~yJsgpR-6vEm z>%hC6W`kdrK_mOJocO?yqNtEuw&|OW1>G??(jJ5xJzBURAK7aHA(>*Q%9r0;%?H!T z1J%=^GmE9bCVB6dAGitfr4XbNq!E}2@+q5ztaO45f>!cPBP)|2ivilVBq`{zg;i$Z{|C&-!47hwwEH3lTg;IROn+qklf55=kRXXjLd75@=xiMli`=4s2CHD=s-z0xx6NsUqP;c5v1>fsddT9Ihs*Wxgz)UaAV- zP8YL{;7x2kTM*nq=2p759eDigOlZT2vuR+L4MStVnqkVhH1@n^oHM~LOdO8m8!M{o zHporqT&j%g^atdBbl0_H)e|%joFUjx!cEM&uI4b*R|Wg9+4m^y#~z;>JWMPB+U0q} zUnua;pOP&)V*6W?nm*8%7fFV-Ehqg)6a&?EF|=GD0?o7Xz>rOWu+-R0VbC|CuzIsY_XZIPe*V+s5N D)#D5Z diff --git a/mediaflow_proxy/drm/decrypter.py b/mediaflow_proxy/drm/decrypter.py index 8b6a354..486a0e5 100644 --- a/mediaflow_proxy/drm/decrypter.py +++ b/mediaflow_proxy/drm/decrypter.py @@ -645,17 +645,23 @@ class MP4Decrypter: return sample_info - def _get_key_for_track(self, track_id: int) -> bytes: + def _get_key_for_track(self, track_id: int) -> Optional[bytes]: """ Retrieves the decryption key for a given track ID from the key map. Uses the KID extracted from the tenc box if available, otherwise falls back to using the first key if only one key is provided. + Returns None (rather than raising) when no matching key can be found — the + caller is expected to handle None by skipping decryption for that track + and passing the encrypted data through unchanged. This mirrors the Rust + implementation and avoids crashing the whole moof/mdat pipeline when + content uses slightly different KID byte-ordering than the URL parameters. + Args: track_id (int): The track ID. Returns: - bytes: The decryption key for the specified track ID. + Optional[bytes]: The decryption key, or None if no key found. """ # If we have an extracted KID for this track, use it to look up the key if track_id in self.extracted_kids: @@ -668,13 +674,31 @@ class MP4Decrypter: if len(self.key_map) == 1: return next(iter(self.key_map.values())) else: - # Use the extracted KID to look up the key + # Direct lookup: tenc KID matches a provided key_id byte-for-byte key = self.key_map.get(extracted_kid) if key: return key - # If KID doesn't match, try fallback - # Note: This is expected when KID in file doesn't match provided key_id - # The provided key_id should still work if it's the correct decryption key + + # PlayReady GUID fallback: some content packagers store the KID in + # the tenc box using little-endian byte order for the first three UUID + # components (the PlayReady GUID format), while the MPD advertises + # @cenc:default_KID in standard big-endian UUID order. + # + # UUID: AABBCCDD-EEFF-GGHH-II... + # LE GUID: DDCCBBAA-FFEE-HHGG-II... (first 4, next 2, next 2 swapped) + # + # Try both directions so that audio-only or video-only init segments + # whose tenc KID was written in the opposite format can still match. + if len(extracted_kid) == 16: + swapped = ( + extracted_kid[3::-1] # bytes 0-3 reversed + + extracted_kid[5:3:-1] # bytes 4-5 reversed + + extracted_kid[7:5:-1] # bytes 6-7 reversed + + extracted_kid[8:] # bytes 8-15 unchanged + ) + key = self.key_map.get(bytes(swapped)) + if key: + return key # Fallback: if only one key provided, use it (backward compatibility) if len(self.key_map) == 1: @@ -683,9 +707,12 @@ class MP4Decrypter: # Try using track_id as KID (for multi-key scenarios) track_id_bytes = track_id.to_bytes(4, "big") key = self.key_map.get(track_id_bytes) - if not key: - raise ValueError(f"No key found for track ID {track_id}") - return key + if key: + return key + + # No key found — return None so callers can pass encrypted data through + # rather than aborting the entire segment stream. + return None @staticmethod def _process_sample( diff --git a/mediaflow_proxy/extractors/__pycache__/F16Px.cpython-313.pyc b/mediaflow_proxy/extractors/__pycache__/F16Px.cpython-313.pyc index 77bccc4c61e3bd5b70c95ae0a8914a044b61884a..e39618520b43e923e87ceab73ee5326e8cbc5b26 100644 GIT binary patch delta 20 acmeyU^-+uaGcPX}0}x1^yuXnxR#OoGcPX}0}x1^yg!k95dboq1%Chl delta 19 ZcmZ3>xR#OoGcPX}0}xD4m_Ctv5dbn=1xo+` diff --git a/mediaflow_proxy/extractors/__pycache__/base.cpython-313.pyc b/mediaflow_proxy/extractors/__pycache__/base.cpython-313.pyc index 5f22939e74bd976c1603f0bd49369a12acff1b4a..099f95af4caa9755657b3bfd0d77d1f37faafe41 100644 GIT binary patch delta 1777 zcmZuxO>7%Q6rT0Z`rk?XmyP4Z8>dNdX%pHe1+{5X8bV4ES=8GgAS=w)9y{AOUNgHc zX%7Td0wHlI(Fnn@oVf&r0}`slr642@R7HZUB7%@OaElZnp>ko~*iNA8TJz2G`0kA1h9%+A=E&R0`6!9b$4E>@3R1ia@%aQv$Vd(6M>n_P zOVWe@F$j?!31yJtOCrUuCMTRNZLjL$`T)}hCmc=fL~}wU#cd&G3#-CmOdm}OZ_GRG zM3G?4!Uat?RxGPtFtKUsM$PUm;F{SWxIm1CrPnG2y{22bR@HCek{zjF>(Ed%YGu7b zH-#M`LRTHiKqq7h5MjggGI$^DxK8};@{%5I+TL?K?dXx2oE2=BiK}H34k(i(n1|?~ zdqU`;uet9F8JZRo@e!uB#kxjJTr%N!GIOQ#;&O(s2ixSbVG@Pt7(g5#0nkmq6Q4OY z1mZ~m2>=3`sqhU`@Ha(!Jui!Vy%c@R(<6LIKlBW4s2;_wdKHlk&q)tL<1WRUMAu!1 z)%EqGqn{Gt-ah8-SN&?h%~n$*TX)vne)0oAKU z#vS0#_Ui8Z-)2#zi^M9PY;-&0?Qle(F61nOqoU0C^$mWA4A=%4%%q%A!?IqM zYevgOtJkYKHb-+=Hwm(hHb{t}?OW5V;!1(V**?BjQ=DBd;=08co^=qYk?jV8P1xDVW`j4bMG{J@s?!^n=*x ztuqf}&weobOH}$~dUN{6VCL`L4(R|1K4~Y~z3*i6BNH;dJ$ed#;h%Ee6Q-QDxQ4A2 zqeRm58~@qufBdr7JI8!jx@kHXeJ|e6{*W|)3@`wYq4%QS2G23kb`_2KO)^RE#N@G4 z+y($awyUHUEgou%RijeD#CDhPwMK=6!6!w3ja{4}m%!DB+hyVx*>+<>43d=yryS?j zix9)}fb_^^fR_N~X(gV%2(+{VmcE8rc?&nRs_kh#E#!GHc?mY_M$N>{6Ud=Cgip+CtR*H+}f=3KN-Bq6edtr=I_C3a!~l1-bwavKq1I^23gT| z&dpxU<^BY>3LEVEZTf09{^ZAkS;-Q>1qOD|v@}aEw)(+~2~8&+0HC|=E>{iBB4Z%C zt{Fy^z+3VhSp^TcY6QqnY5-4qdB(v2aE1-@4|=7yd!!SIk$s29Eq&_TM{K(5yj%Lr ze*ofvvu)d#sNVZ|fZue8&-T-FYBBICtoJ&=5WSJgd-#1IA$mVGd-Kp<}5m&@m zxV+HXV$P*Z@@;PY^&^&IE4F^T^H>*jS&EYy-GEO2jvmFy_LC(YYUv156~jyq<5 z_?LF&+?Qf5FHMFpr5y6dw2JkRi-fQn#ANTt2a;xwLiXyE2n2+Tn7%IHw(>#U^hwnr zCryiHcw=Z~Nyt^gRiC zZ1l7xx5IT^%s&J7#qoWfy^dC9OUm+Rci0gpYDdM?9o>m(!p)8eDK4eBlduy`+)g@4 z+i;A&w4KT;*VJnSQqBq)X*e+_>ZJQZ=-;Q>E70DLr-=a_b{aBGt(xJ+{+dq@O$Sh0 zyrZk345kG71T5#{VZ7Ca7dQ!)Kpq*V!BwvTMfN_qLxa^qw@3_2*9V0pl3HZd?pxgj zv4A21z7LcUEcUAVZE~j&xmx$yGcdsJXRY2|X+yvut~OfrK!);&>ouDcxYnRoCYz8% zpFI1V9X`rqhoJQrl~HPAst!0v%2!v89D8yXO{D@s`LU$TqE9Lcf=#it2bxoD~u z*l1}aaSC0}AXM0s(kRo)l{5G*Jl3SahWZ7LH4*j(6KK>jC%)uDEOD%^%KB|?r;DA diff --git a/mediaflow_proxy/extractors/__pycache__/city.cpython-313.pyc b/mediaflow_proxy/extractors/__pycache__/city.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..baca599b29e994e4b6923e713f9f4db3aa3d0248 GIT binary patch literal 7049 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 B4D|p2 literal 2906 zcma)8U2NOd6}}WHiTbl-%aIkOo@vL`(0OLNAki=X?q}4KnfxhpmZ-FK3 z0!KK3jX2$GpC@$iY7{W?E!b$A1f6^zT zNroc1&EMuo0PqASeNq7W9O0!P3CU=NA>m<^<(=f5X=;*9uv+T8cb^CEmFX~@5=7zJ z<_%rDUQ{;)?12pDv1JzTVoR`1LDLB?*uvX$i^8?*lVK-L@OvBBvXynLYM59B-!mc5 z2k@*TkaE}VhPOfjuN;Fyt7v9o^-Wqpg46YI8Jwl0WbY4fhGl>u0; zV3xA1q}vkEW`lbnT%tArlD6H=5yQMD{Zu_zQl_|RDt*Ub_Z zVW}z3nT3rKHf&3j3PdgI#+oP_-`6#)i6yM*>T1z^Unvvw-lo_g#1b_i%W`?ll{ZO@ zzUP&faGUeMb-@a&52~*}L|^i;p9SlDdVAoHe0DD_R=Hoqj$jHoEFgN0KYBDjhv(>Z zfPNYYa0iy!Y(2}I!OCpD!y9$gDtYgYdjrr)9lrCyXNKG6@vP*dn+;CpM%!~*1fivn zw?|^VbHi|Ic{k6zxmKH}w7gIx&f7J^bo>HxID0>Mp)|cvx&ZM6+~=In_~sQsnd?Fm ztPubo-7h$R?oBN5=Y3Zohu49}yUcxd(H}+mp8s5F1ebcf(R|9Yj$6q2kQ|(9b8YZ4 z--+URcEVnqm{~gM?SFZMGM_)?tvGI_i07g6Hpt|cMtA@JjV|AR?BBDd+J+JSpOHtO zi98}lz9I6Mha2uiVjJX?B&#_AEM#xc@IGmBba}u3FSJ!Bsms|AT*qWebyYZ>@m@R3g{R zie4mzA+rxVQMRZK9cup$<-j1z3@TqQZWES9ORl(ea3ap~5#-yO?V($wz- z!5bG0(-v0E4MW?fX2Sow*rLISxHJJey2kHzoX{$eQKT*`3SM}w5GoN)ur24# zsh0_Ndy-0oWy9I+BM|QHliR0%**+@?9 zuGEv~tNi15-6`t01N z=e7rSm|EZ2LqD4gSAz#(lzwIFgO5M>$b2B}q<6x#-ic~xFO_MgM(e53os+fHXe~9@ zOw2Xo3-$Oym3x}#*$RCUs%6G^hZ~9Ms{e7k*F|u#(Q~O5z4SDh*`EAt=F^#-kzbE} zKDMj=D%YSupY#tl`^9>{xXai2#ajPDvu~l9x?WFRZ>Db4Q#Tr^o7KqERC-JQM6U_d zd1|^Ec^uEU&R=WvOxL2*PZFn^iNShe@WEgsFqo6$!TkL*8e zKHU7>j~Z`Fwb^%Sy>c}Kj0oAifirtU=MRHiB7A_jQ21~JbYYMV8evzprUAI0Sr#l3wG;!&cgK~)AXjCfL{k2Qmde-HeBnK z4I9JX1SCZ3$D)9XMMVJw33^#*o`281VT1?Mt*Bv*K=t)c(V>s^^9Ny{e|(4g3PE$g z5Bb9nl3yWc{z@y7^!8;#gi=7ppmL&cnTz^L&dnJ&iCY;raGd0>I^>RjQ*ty-2>p(` zsqJF<9JTzrIly0PG4uwRheZ^+ML!V-EWk zQZgRYgE8?R&=BK=gC32S;>Fs8;LYm<4qlxX)`NR^_W8}cACq}!*W_mCBpCF8_)=ew z8Z@vQN|2i$O5>zxgPmHeF4Wvw4iYc`b~*)irq+Y80Pg6TS+>|?GR8iWxDuRcY(Clh zM)rxnOuBtH8Y^>mnw#vY6!X!u1UUK5-bg!Fos4Z%v{~Ysj^$*XoRe2XK(z*=jIT$n|hqb05( z=s+WFV~|q>AJD#*#r^05K*Q}wfd>&nK>J(6yjS=w-skP~<4zc86e~v%3=g6RF%RMh z383+IVu&XZQeMwALI!BBIHZZ*@=lgT$es1fqaW~U6_O)kDlbAf*NGH5;e5v!?DVEP zi6McX}nD72Sg|1$Iu(9%ReRayc qFLuQcpm|#emk1$8VE%^rLzq2;aW)g04j;nwAGJs>9)pn6QT_$NmA=^k delta 716 zcmY+CO=uHA7>2*;ZZ^A{zc#U%NT_>`+4j3EtHGzwO%G$-l+q_}P5P5KgDHaxYBTebT0F4^4bwn-k11GRR$ zVRcD!b9e9!9U}Tc^62Wc56*`4i}d=Y6U+ zc8S7WULH$Ztq!ShPuA#(@&>KZ7tTBA9R2F7QLlOzIq5?+jmmUST}RjHXEnB86N_&M zxh5p>PfTt)Wd!gFj^q_4ct1s^W#D{LOLkeIt5O8yxH1O#Ug5)cM0GQ`U)A^^|!Toe!m z&DZm=nD7Q@L2)uuKzzDqLiprtR!Ts6+L6Hptgt2UCr5$I$e$Vovm;+@gqKfOG5w`Q z9N3|-BGvWt?ny!-oO}5at?E_!KsQ{C)=q;s`NwgVrra@igI|o!xbnz6pO#akg DPDZkI diff --git a/mediaflow_proxy/extractors/__pycache__/fastream.cpython-313.pyc b/mediaflow_proxy/extractors/__pycache__/fastream.cpython-313.pyc index 22dd5fbb9c92a4a0c65ec54bfd857df64cc27ade..b0d77db13f240918acc42928a78af8bb4c9fd516 100644 GIT binary patch delta 20 acmX>hctVi-GcPX}0}x1^yuXoqF9!fW8U`2u delta 20 acmX>hctVi-GcPX}0}xD4n7)yFF9!fVrv>By diff --git a/mediaflow_proxy/extractors/__pycache__/filelions.cpython-313.pyc b/mediaflow_proxy/extractors/__pycache__/filelions.cpython-313.pyc index f8f0cd9e036d71dd150a287421d062ceb91d4667..27c191caef78c2c767036ba245653c876d62ecd9 100644 GIT binary patch delta 20 acmcb|bB~AnGcPX}0}x1^yuXnPq7N%*)Hg00dGe?{DP(ZvX&6_6Dc` delta 20 acmeB5>Pq7N%*)Hg00h$$rf=l_ZvX&6g9dZ} diff --git a/mediaflow_proxy/extractors/__pycache__/lulustream.cpython-313.pyc b/mediaflow_proxy/extractors/__pycache__/lulustream.cpython-313.pyc index 15f04bf06bb370e63c3091128f1bf4cb257c0329..bc02460fe9eace95538fa3cf94d957dbe2c084ef 100644 GIT binary patch literal 2655 zcmZuzO>7&-6`tk(@<$XaDUGby;hI)tZ5&fBA|;4nF?Gzewj?NlRtpE9lEr#Clr~-N zGBZm%QZGgig^FIBB8G%ssO*@lphJ3-A`}v)Mq{7|3g4uvsdMO|Ze-KaZ;L)i5%0?q%xrrbwVSWj`~pnneqTKl{s}xY@J7v^Mav1;*)73!KAY1MohdM zEE)pKT&9bGYhp^hFaT!P8xF;9ei#wQjY7h*2jrWCgv4zf*y=(1xb0ts;XTlI4iUjb zBw-3fG!sO!(5PUF1L%E?$fh)>nDU%Dr_5=i3I5Pg$y9(Rk))}Tl!e9xk{(2c#(Jlk ze$$KycIpSWKT5)mrH>Cex-~PUTZ+xwp*)t`Z@i|0txF__v#HyXc#yV zJW!89{l;R$p;W)(hs|oucL>%SB#f}j`NfnEchmUpq8rwAT)%;rUX+FpX@`?!!exWHN2SOPFTcUf@Nx&3fv%>Nzz(yk+B{+6cWMG9)HZ z?AMs&ka^0KAKi+_q;0!CJleJ?D1dZbzs;sc7Qj3sV6bqc9J0mfr^f#6-}GDFjDN61WHE0geXW&*9sjh1D}ihp@BI%uyErlQ&Bk zYp(`krJV=}jkTCaK-VBt7hwISa95m`GboTOX@*&DVkDIw8Y`Igagv1 zY>5|yx4@y?o!1~WpEy&7RE6JI`vun$Px5tiU7qQr(UE&i<>H?aa-Kt|!s9+}rfNM^ zHj7Nn60+!JsqP+vk17K>VI}(BRPHv{}DH55puxM%E*Qan{^}A2m(^V7)AvmVLhVL)%{oyov z3B~pXFSUHEp$t0E8e*fC&`};DNJrD_<6<7wgjdCyU}UG6;>OtV=y|wk7E&*L2qKuN zkypoIGh*G2&vTDJ77~CD%H%jjygP9Al6-Y`_HBJBr{7xee5}`SII zRDx@mU;+wvR{=S^7UtPJmAVQ3pvlo>0XtQ|L&8DI$gq?>kJ}JKICe1+<}6JkCu&mL z4MA4r%L#&+La{^K1tvwH0aMCv&0e0GV)8r*n+=w^0d?ca)02dUP^=s2;Z&ZVqykR! zTEjGgX(J3MShV3p5hekX>kgQSC2OF%&-JD}`~j|7jqq?_YWw}{xkK|Df=!#rC20~I z@d({UJGs7x)ZniwzpU)!dUr+T`Rv1!gFF2LJNn>W z5@!ERq2cp)ezg&2)b4<*oG4gC2ct z?9={jV|>#X-xw}!l%~I+U*L_?vs*pZM#kDzQ12CCS$&Y}SxNpPxq9}K@n4VsvG6D3 zb7L!aX<7b9cOM+Fae8vAXKEuewbOTEW%8rRHF>LVXgT@t?2FsliB0Xqs=cH!3$JY!UfWZpTxu6d$<&?!3hpI9*|w@Hr8}kn(d%H6pXlXb;Wt8A zQ+_J~^}C^RR=l4P$|>c3m&EDsq+$8~NuivP?w@+&C77(I1YoR#5YSnr{DQjPCzPMf ztUr_C^l25=4WScHP@Mv&j-fiGY`Y#-n?9#Aw*5iV@!Onk+pc+pM!px|AOyB*+ttvu z?N$vDq5KjK*o31o`5tua8K_(klAdcrq!8~$Gr~~JSXaBQx!Ct@8?@kfL&59l`*Exg z9zJo$33?v7Z-0vR5|XTalS#<0?rH;aYOQw{!SLDWJ@KA-Py4*|m{0%BJHnAB-fyJH z5YSAC7I}F*)Q|NX{O2gPbBwk!DSxbV7Tcj25?bWRLOAfp*%jaLd(^ruj&bc$%XTI5 oItcLx#f5WM6a?Y#DESpS`87KCFLdoc$_qkj_53#ohIsq`0ezT_VE_OC delta 883 zcmYjP&rcIU6n?WmX16~nv?a8nA`m2(F&y=5K<#+BMFVzlm)7^#pzbz1o1#j zjE6cQ5itQ_qzgfKtP)8Vld>))aZ*kyVL@i>u%yeZClOW0#Fl`Yh}hde zQ!MXjajvMF#7JdtFPJ2iHuI#SFhv?Ms0BZwj;;MLg9!bBz9Sd4gbPTee}$NXw_ovg zKcXXIsDBuAm}Nim46TZB*$FvOM>%OiV3cZ%Nww^BZ-wz8S}*yW|It+XK{}(!2|0Aa zn{oh5uyq`|(0ekZffDkVi`L8DIu0@(Kz*^fND>XEy|Hc%nH z+$T;muj6lUCu*kGakEfC^a1V(yO~O}S+2=!>WMK)jHgATU|Iq(EL<>BBt2_MrmMY#PWM6cR9ml{}F;4TgjFR!XK;FA`(@4r{z zP!Qz4J+)4@uY31^Z6CuM;)cGVev16!pFcSee@&{Xl)?kIO~# delta 20 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Ⓢ-C|-n(?XoXe79N9;L(Ajg(iypFPM_;wcGu(Ov8Df)JPQPP2mOQtsy zECcGr;?;{iif>p+STnxiP_9&W;XbxcAXKWO8OSu@v?vrStvUz_0O@kv86%dAcLC@k zC)vv}1tsSx*_CHJE2ldlkSwP{fpFP>NwT|&%kE0DWBY*~ZY9fbfK0B+StT`2fmlI`w)k?eMgpkUE+o1YtmJm;?I*M&m zOs9<3j1@r+eOVMeji*!hCA;FuA@q%vNYcPBEg59Zlw1p-yE1}G=#IlDHXPwQ;8FsA zrn2@){%VCQ>s6MGl)GOCSRBrxe}*7-uAkJ_jcc}josY=BpIMzL1^UFmpb!}3+lJOB zHtgbwE5eB@{O}C#n}uF~yXcPy{)p&5D)^7CpDg-EMgRRxw>TaX#$)364PpES|0k~( z#}m-ZFXj2gd;GmOdHVk9Y^l{Rw)P0EJz{G_XpKC6yV!bKY|U-z#k1Fhv)9D4lyH{f zZ`>}PO+qujm|LBCVMO+ZznT8_H1CN#<{s00%g~nXSSi>o28V>;P|4ZwG|*pa>n#QQ zN^SAd;j^W#=rg;i)wSb9t=^qR)NyPFSv%@~i>&_o)tQ|Z)Y>h!^aw3ItCw;7XjJSN z5;}&8o?{S2@`pr!RPaZ6-;s6Q`m{K7Q5d?&4_@LuldID}iLdSBlOLVryC|67*6oyd zJ1gAI7QHL0S4sd?p4sYD$w8wd?Z~=k-NySyx13{;$sqd0z!4#Eq|^pU)IHM*y4LD}!FPQ9t)g#Yb>`PL7nat;?tf@_bhFq#$P+{BvwX|M zmhJp+8VEdL8DVV8Nj?p9h=Ef=;MB%+F)+Di`=lXI!c8ZIz{!oiA6tq6a&2N5zq3Bix18IuP3#yA_A8ogqWjT1>#qri z&lZXCHG4%)!10Yf-gjxsIf>h3t{(sC^7=cQ*Ef^G@D!g|;*Y0!|D7%8-7g^e?Js!) zqIX#E4sY0t-ivEyNaeVFDmdTOE$1uSMEG(4FNhQGO|FfvK@`2^;na7&`AZ0%Yt4=A z+o8zg{6E74^q@k-67+9P9O@nv4sgv)D6`M#NgwgC3 zn+JsEf%VYFRI&Ne>SS4xyyw)$Bq&YjUgQ@+U3CmpcX+^5u&Na<&2VI#BbAcWP^-BE}dMab4%HjvJE&3?W`W(AbN_}9NZw03mE`BFb;cPHhnt} zp8^43d-(&EB%R5`<3KKsbaJr8LucisiiPtpl5hCKEbhB-``urlXIi_#ywj#N3_sTY z7Qti3+-~qb?$|-_Snpitz8`*pi#woaM%RwkX>dPE?jU$PPOhK--ks;T_*|#|u+hk*Z^PF~@Ex9N>}AOrqC+-0B( literal 8095 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 delta 20 acmbQwJD->PGcPX}0}xD4n7)yF3L5}7tp#5I diff --git a/mediaflow_proxy/extractors/__pycache__/streamwish.cpython-313.pyc b/mediaflow_proxy/extractors/__pycache__/streamwish.cpython-313.pyc index 3e4e085698a5c089f775535753918baa44b6e7f5..82e4139ab959da0b41f465c8b0b03dbbcff134cc 100644 GIT binary patch delta 20 acmaDa^InGgGcPX}0}x1^yuXnA355T5bAf4}$qm&8t;IEmxfj!N4=>Ou<*3W{uVph$jNDUwx1MmRKwyi~^g z6cxMTR0#=MEg?BU#DOACjl>~B;!pujdjlb%D5})nN)Jd*3(cWYVV~Qr+X(is^Ucin z&CdRv8~ie1-?c0Q_}F*t#=@GhZl5JbvJ${348t-wcnDoWp@bgg=|M5~DC$ZHC=>Pt z8D%>p+Drn)!{`u4a^!lo#L@EpI;Ip$H~s#Gn7 zM6PSQELWpi1d2BST%GDAlYJ+RS`oqQL|mlSZqVkEBviipL{;y^#3g}568(6%;WQ9 z9>*iBuAWWZPVlUZAQZ8!&Q1A0@xL{jIqtzgee z;9_8n{1!}|m)lZCFW~j)=t|?W#y8Qek?f`TSMjZ$Xq(8%@Xg`u)<9xMp|G%9Y^sbKLn+iFtD}$LF8o z(<9vc{F2G3Ee-QyN=uhKY$31JvO_%E8bU9UL=TU3a7M{bsjUN;pD`Ede?q-vchJ+@Tz`>Oi5-+3> zFM9BRKCyZd6BF^^->pZZp~S=kQ4|~8I!GY$6gM?6I-}Cwe$$bLHKx}w2;>b8 z&1ueK7XXqnK!UTW8ml{1#?FdS0*%v=1YJxN6{;zeaM^u2W1Y>;3sK_}qFvEk_MGPK z#RjXhYFmljEXE?u;B?Lhr#1i#UguRe`RSC)9RNcW^th$=gw+{yR@a4pSI(N0PD=6Z zqsT)p9aBL<1>K1kJMo%7T0-2b*4y=B)PxqA=zXKo$!l8*|mOU;PpGmUUr4SDa8lE7p*a1Cg zi*k^p7hs5c9=1dm>GL^?bDF~t2gGrd2<@4QSX0y&OcxU@ncxD4i<`aw(;0_89Ds4u zgU3;%l;pVCk_cM&OrD!crKisu$(a<1^Pf>UIqh1kmePK@Azf#>D(cqxspQN|Ds4<( zbB9nfX>msxiPlNd-QRnc{&HVo9ySFi53c!wm-$Vm(WRjG{-$i^dS==Gt|5F+z9TPd zxrrrttwDJ!D+~OFPbQq_%aI6FhO(Kfne9plcYNa@5ZP%2fAd;%{B_01ns;O?N(t+- zvWqQZHnWiVv2&D!1$FKbJ;6mr=|GpHSO6B$$v*Z5rs!5#%!wbM7_biqIw^b>tfc-q zL`~m2&_KgVwLpn0Ar!;xN(*D+Rz#In4wjnTK13^dKosY(g`I4yhRb_UOcL^fVB*Ry zb5~wA$MQ864!D&zoR3Jc9-QwHDPCoQN*J$(ohEL>N(VV4j-nIfia6SFz^R}m-zJ#x wad!^qv>gAWf7i;lET675@83&Zq%7S|s-zd_4EZ3nmUbfr-E8{~X0%AZ0L|IN-~a#s diff --git a/mediaflow_proxy/extractors/__pycache__/turbovidplay.cpython-313.pyc b/mediaflow_proxy/extractors/__pycache__/turbovidplay.cpython-313.pyc index 0055a02dad71d0269cbcfeb5971560339906c270..b10b73c528ae39ed4074f519f07cba684dc8826a 100644 GIT binary patch delta 20 acmbOwG)sv4GcPX}0}x1^yuXo~pA!H#4+Rkb delta 20 acmbOwG)sv4GcPX}0}xD4n7)ylpA!H!oCMtf diff --git a/mediaflow_proxy/extractors/__pycache__/uqload.cpython-313.pyc b/mediaflow_proxy/extractors/__pycache__/uqload.cpython-313.pyc index 4d802f68751c13262f6371a3509896b117c2f503..e86aeaedef15d2e3d7d0b769ed0f258039a4b441 100644 GIT binary patch literal 2449 zcmZuyO>7&-6`tk(@nq`%PLvqO| z$Sii|y*F>(yf^c{HwOa)NdztP>6hMJ6`_9#CThU9+rh73xP?q)N^^*cF*Ya7#%OF- zrZN`1Jg3a6RBhABoHnady-llg@!14T%qD3PqfwMYrj|jbzLc1jJEm!BS<1(E1v8(I zai#3JA(vnASMn0qTeSXm;Q2rrudo&0H3?&0;Dg@4s}3U<-wUbZh5_Y+J@O)@0d+gJ zJH6Ir4yJ&(g_aPNOhjX*L}fEZ6;obPO=U@2QkV3pn22~vG1UxuTcL4NqlslbALnW7 zdOdI|y^a2dhp0Y6Co%dM2kmNG>!!=667WLg&_e{hPhyM@oA2dNajxd~&g%%_+b+}p5 zbXV%ji{0x(Xu7K;q04ZtHITn6-IC{31Nn-jEGUO<2rh>#X-YCBGiJ)BQjjfqva95P zwy9e3S?LYwEGkPcNtYqH4%t*8+2EV*MsPWHcpjsx>Oyqm-`U#KX8wwh^CN^76Oq`| ztsm4KL}uKQ2GKbsikC=>ZIFnS3r4iQKSEk3z}8@25>b(#zGi7rl$F#WFOuKc<(X(5 zSR0P${upWc`r@;Z)Y8pVvGnr+upm!B z;>;hw|9ANRb{zc}weJFMuyg-$WT0NekT=yBMp?)%jOIVPAZJlkdReYY`Sd*3+%`OR zkHH34@-k2LaYDErdJPh^LY{`~4sO^zFmM&H^4^Mf6Kt=lm#$oS6R!^88#NCARVATY z^ZaGJnq@(Yx`fRvvC~V7xyf1>HrY>Sip9m>a1v0xvoC=_w4>th5PF46$ z{7MtbS(qQ>Nqd=ub~^$p(v{1=^Aro6u*GaQfTaSLJVpuE7;&gu<4OochRZCZROBEP zwTc_ppb+*pQeaR`*WS6|&}BwP#2Ovylc=LSP6=xUJ|jHc4qrrI zLxF+K5><$*E(A}D#ver_vu^2sn>pZ;icJAYv_e_>;?v{9OW%pQ}C<5#wZtqsHa zIx}`Je>cA|@%rQVKL$P>*vkBJE&b2*F<7^8{1;op^Bcze*CWs0iQhB+Y;293SW7%P zRrqM-!T8px(wedzKe`z|dMCdbpZF?U*v?LEW~c7IwUvE&Cp*5KJ-eAbyOTS)ojbpo zJHM|hL&-g)B$E3lz|;L0c-{5+cIjs6f9xup&F^seCFy;s{G9fI4Ah4w%VY9GLn@DI z4+j)MKa&FS!!fCxQ69eV>I^WCMkSbdl#v8Irj^g>k8)D^gz;$H5Oh%o`Cs5EPV%^I z`%Z({HYBUv2r8|*pbgu;-g4^QnY3+JJ<7tm=Mz7GY2CIffot3JIJif}BSFOrK+izO zpS5j~;GSzcVMx7et&lKKeG%pk;79bd=tQM$cYL$@bOlJq%Be2K=sK=>PU=38|6 Q-&$5m-fa8_!O*twe|N)NUjP6A delta 937 zcmYLHO-vI(6rS0g-EIq|*ixYUE<^(hrojXvA*5(gBdzq%O~av>y0i-{lon>TD7|1f z7!og%4Z(QPBi`^<6BF;AAUzOQF&;<^HxeYq#Dg=X=p^&r_vX#}X6Bo>8q0u%_feNDlGtWg~gu|T7FdyY32%`!{Q6VB?G2*}u88m}#pb9NO z6{p1^o-qP|r8%fLP8f_S&_;KRv`M|AfU^sEBd2h-JFF4??h_MhX)}-QItsTwh5f=H zC(xon#26P4cBmqjtS;DiOQOp~e_|z@*D`V<{y^3*i-R^#d2JEvW&!8Y96Pl-5cSXL zJnMs&o9k_xpl_R}cU*^;T&zH2@@?)qqMd z1TU}-lQeTN5>Q1$RkDON!24aQ&_h!R6!%yMyw4?GR#mbn0pj&+eaaE!uL23g)^75% zP?3thsw(pTw+3THAITSRS|=m&bh5iQ*f*whKPjHTF>Ma(wn%ghr{`^CG6_X`M~tiwLLlI!P!ViI^z1BVyGM7@jKkS1$Fnk5Q zygfqUTiQWRDOY>LiuIGR>XQ32-|ztZyoY z0U}fL417kNr^qe1-uO-db$f#)zND6!Inms@|i+b58>-HWX(g^`Y8_~`~sXupy>$6 TKfuIKu^zf!ES&@DD&>CxrzYIi diff --git a/mediaflow_proxy/extractors/__pycache__/vavoo.cpython-313.pyc b/mediaflow_proxy/extractors/__pycache__/vavoo.cpython-313.pyc index 777789660bef76c50090b7a7c65080443f44c7d3..65feec620ed91fc6f382c9cedd0fe30234b5998d 100644 GIT binary patch literal 16612 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 e_kXVK-Zt;n4!$XVAI(7C( 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 pY0~~5d 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 literal 0 HcmV?d00001 diff --git a/mediaflow_proxy/extractors/__pycache__/vidmoly.cpython-313.pyc b/mediaflow_proxy/extractors/__pycache__/vidmoly.cpython-313.pyc index adafcde4adc5fc43aabcedf1df0574fd4d06312e..a48481de8da72fe0b013e051b8c46a9e92ade14e 100644 GIT binary patch delta 20 acmew=`c;(sGcPX}0}x1^yuXqA6&CajRPnMTYAY2BLLHSl z$v6Zlmr9)6V>GHT7l_1xA|Xf`RpeNgNKmPVSdp5@2+?xmfK&nqDskw%ZBnVt9KLz) z&HtV}@6Gz}!{7BNe-R=hzS{5pI(>nxDu0Oh>$U&IJjO@YvhFg*|E(>zOycZy%V*;u zE_~mOkmY}B7q4-f?Ab1`&tgj{du&|5>|skd>gmT&oSz61XIa=g>5P=UwxA#@kaIbE zFykuAtnN{J1uN9crMA~|%c<^I{iavzSNB48B2%GT^;3)gbo8$c1tK&BI*sBxY`L=yc zL1Qy=l&H3^pWhr-p@Y}ciT{8`JD?#4G>2Z)j4M{Vtw6`&x7iABR)_89?6ir3a#A^P zR(7KL^HHnI_BmPnzIMRs?j^kMfyrQ@6K?B^_3(;zz((BX6B1CN3*zZ`MtBKb5XaJ{19xRwVg-$fxSBmuV6m3egMLJ)hF*rEMz9s3FAU9*N82g0` zxu}b^E8kp=r*2qZb}vn?OjS?sXdPd8Kl3gpx3tuaY(wp>j_yR%Pt7H>R@#d6R8KwV zO4iOT&(%)s=tpXA{wWEnQXP3DqprlZZZvdb#k2luqi^{3sh{HY(MI1}Tl$%8^-Oj2 zfgXP*$gL{3wIB7m{zLcX_&cqQ;fY4y#OAw~Hg#iDFKw%(wfjm8Uri%5*Hq_r-AMZg z??#X|hJRB_k1OZ+6+SV{LA;91w0N_fTXjD+Gh&?#YuveW1e;m$PI5TOoeh4H7dJ=- z>>+Fph#SY>=;O}a3%zLV?pZzYFSxq^s)7>!Vp9I6(;HzEq090`9)WjINw9mNL!r|cD>d>Dg561Z0}Degjgj~jqE^`=-o1G7 zHV3?DxN9QN3yCM-pdnt22VyxGy%1we2x5#02j?vZqR!zr|M$6XX6IFI`>^pt*A)k79xKUO|i>z z#pIJ}yhHDQ2r#Y!jK!%pE!W^9N-wtZ$4W`?wPK^mKPt8q8EfZPSlT=UZkrc5)v}vj zNs30c6x|EP{|hR6!S;WHmsGdIQ)ylr!&41#_jyr4%To*}$u(_63A~BFy>8Nr1XZ%A z(%e*+4p7rZ%}R<6rtZ&)j-KX$us?_65>(RU&!)SjBXEmFpp3(-0T%*R1NQYwH!*MX zXPTEeAR73cgsebQs{7ZP^4)vW6E~VIQ`3HiJNl^n6-A6c(VusQe2!~uct7y~WekoUE| zH0{ncyZGKN&Pg9^XU%dN(`E*%7FeaGHMtdn!~^_dIlt>=QTM1g6vlZh53q%lP)iz? z2icN-E+LHNEDj-CKAIJJMa3b;Ra&oCdk#8YX$>KG!`Qa^IB}O=b&uN z#y3H1bIzi)=q^U)2Y->dMer@kq}Q9*`~v9<+>GBoSrw8lu$~dvln`+p)OCn{g7gLy SHsIoR=nNW9^`9WNpvfPkeVil! diff --git a/mediaflow_proxy/extractors/__pycache__/voe.cpython-313.pyc b/mediaflow_proxy/extractors/__pycache__/voe.cpython-313.pyc index ceaeb5474e1e77776b57ae38f9e84347fc043b01..f75c0d51a3a093b85772dde111d83a0ed6d54ff2 100644 GIT binary patch delta 20 acmX@1azcgsGcPX}0}x1^yuXp#RtNw<&;`f< delta 20 acmX@1azcgsGcPX}0}xD4n7)zQRtNw str | None: + """Return the configured proxy URL for *url*, or None if no proxy applies.""" + try: + _ensure_routing_initialized() + route = get_routing_config().match_url(url) + return route.proxy_url + except Exception: + return None + async def _make_request( self, url: str, diff --git a/mediaflow_proxy/extractors/city.py b/mediaflow_proxy/extractors/city.py new file mode 100644 index 0000000..5394e8e --- /dev/null +++ b/mediaflow_proxy/extractors/city.py @@ -0,0 +1,158 @@ +import re +import json +import base64 +from typing import Dict, Any +from urllib.parse import urlparse, parse_qs +from bs4 import BeautifulSoup + +from mediaflow_proxy.extractors.base import BaseExtractor, ExtractorError + + +class CityExtractor(BaseExtractor): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.mediaflow_endpoint = "hls_manifest_proxy" + + def atob_fixed(self, data: str) -> str: + try: + return base64.b64decode(data).decode("utf-8", errors="ignore") + except Exception: + return "" + + def extract_json_array(self, decoded: str): + start = decoded.find("file:") + if start == -1: + start = decoded.find("sources:") + if start == -1: + return None + + start = decoded.find("[", start) + if start == -1: + return None + + depth = 0 + for i in range(start, len(decoded)): + if decoded[i] == "[": + depth += 1 + elif decoded[i] == "]": + depth -= 1 + if depth == 0: + return decoded[start : i + 1] + + return None + + def pick_stream(self, file_data, season: int = 1, episode: int = 1): + + if isinstance(file_data, str): + return file_data + + if isinstance(file_data, list): + if all(isinstance(x, dict) and "file" in x for x in file_data): + idx = max(0, episode - 1) + return file_data[idx]["file"] + + selected_season = None + for s in file_data: + if not isinstance(s, dict): + continue + folder = s.get("folder") + if not folder: + continue + title = (s.get("title") or "").lower() + if re.search(rf"(season|s)\s*0*{season}\b", title): + selected_season = folder + break + + if not selected_season: + for s in file_data: + folder = s.get("folder") + if folder: + selected_season = folder + break + + if not selected_season: + return None + + idx = max(0, episode - 1) + return selected_season[idx].get("file") if idx < len(selected_season) else selected_season[0].get("file") + + return None + + async def extract(self, url: str, season: int = 1, episode: int = 1, **kwargs) -> Dict[str, Any]: + """Main extraction entry point""" + + parsed = urlparse(url) + query = parse_qs(parsed.query) + if "s" in query: + try: + season = int(query["s"][0]) + except Exception: + pass + if "e" in query: + try: + episode = int(query["e"][0]) + except Exception: + pass + + clean_url = f"{parsed.scheme}://{parsed.netloc}{parsed.path}" + + cookie_b64 = "ZGxlX3VzZXJfaWQ9MzI3Mjk7IGRsZV9wYXNzd29yZD04OTQxNzFjNmE4ZGFiMThlZTU5NGQ1YzY1MjAwOWEzNTs=" + cookie = base64.b64decode(cookie_b64).decode() + + headers = { + "User-Agent": self.base_headers.get("user-agent"), + "Referer": clean_url, + "Cookie": cookie, + } + + response = await self._make_request(clean_url, headers=headers) + if response.status != 200: + raise ExtractorError("Failed to load City page") + + soup = BeautifulSoup(response.text, "lxml") + file_data = None + + for script in soup.find_all("script"): + if file_data: + break + + script_html = script.string or script.text or "" + if "atob" not in script_html: + continue + + matches = re.finditer(r'atob\(\s*[\'"](.*?)[\'"]\s*\)', script_html) + for match in matches: + encoded = match.group(1) + decoded = self.atob_fixed(encoded) + if not decoded: + continue + + raw_json = self.extract_json_array(decoded) + if raw_json: + try: + raw_json = re.sub(r"\\(.)", r"\1", raw_json) + file_data = json.loads(raw_json) + except Exception: + file_data = raw_json + break + + file_match = re.search(r'file\s*:\s*[\'"](.*?)[\'"]', decoded, re.S) + if file_match: + file_data = file_match.group(1) + break + + if not file_data: + raise ExtractorError("No stream found") + + stream_url = self.pick_stream(file_data, season=season, episode=episode) + if not stream_url: + raise ExtractorError("Stream extraction failed") + + return { + "destination_url": stream_url, + "request_headers": { + "Referer": clean_url, + "User-Agent": self.base_headers.get("user-agent"), + }, + "mediaflow_endpoint": self.mediaflow_endpoint, + } diff --git a/mediaflow_proxy/extractors/dlhd.py b/mediaflow_proxy/extractors/dlhd.py deleted file mode 100644 index 07a1d87..0000000 --- a/mediaflow_proxy/extractors/dlhd.py +++ /dev/null @@ -1,704 +0,0 @@ -import hashlib -import hmac -import re -import time -import logging - -from typing import Any, Dict, Optional -from urllib.parse import urlparse - -import aiohttp - -from mediaflow_proxy.extractors.base import BaseExtractor, ExtractorError, HttpResponse -from mediaflow_proxy.utils.http_client import create_aiohttp_session -from mediaflow_proxy.configs import settings - - -logger = logging.getLogger(__name__) - -# Silenzia l'errore ConnectionResetError su Windows -logging.getLogger("asyncio").setLevel(logging.CRITICAL) - -# Default fingerprint parameters -DEFAULT_DLHD_USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64; rv:146.0) Gecko/20100101 Firefox/146.0" -DEFAULT_DLHD_SCREEN_RESOLUTION = "1920x1080" -DEFAULT_DLHD_TIMEZONE = "UTC" -DEFAULT_DLHD_LANGUAGE = "en" - - -def compute_fingerprint( - user_agent: str = DEFAULT_DLHD_USER_AGENT, - screen_resolution: str = DEFAULT_DLHD_SCREEN_RESOLUTION, - timezone: str = DEFAULT_DLHD_TIMEZONE, - language: str = DEFAULT_DLHD_LANGUAGE, -) -> str: - """ - Compute the X-Fingerprint header value. - - Algorithm: - fingerprint = SHA256(useragent + screen_resolution + timezone + language).hex()[:16] - - Args: - user_agent: The user agent string - screen_resolution: The screen resolution (e.g., "1920x1080") - timezone: The timezone (e.g., "UTC") - language: The language code (e.g., "en") - - Returns: - The 16-character fingerprint - """ - combined = f"{user_agent}{screen_resolution}{timezone}{language}" - return hashlib.sha256(combined.encode("utf-8")).hexdigest()[:16] - - -def compute_key_path(resource: str, number: str, timestamp: int, fingerprint: str, secret_key: str) -> str: - """ - Compute the X-Key-Path header value. - - Algorithm: - key_path = HMAC-SHA256("resource|number|timestamp|fingerprint", secret_key).hex()[:16] - - Args: - resource: The resource from the key URL - number: The number from the key URL - timestamp: The Unix timestamp - fingerprint: The fingerprint value - secret_key: The HMAC secret key (channel_salt) - - Returns: - The 16-character key path - """ - combined = f"{resource}|{number}|{timestamp}|{fingerprint}" - hmac_hash = hmac.new(secret_key.encode("utf-8"), combined.encode("utf-8"), hashlib.sha256).hexdigest() - return hmac_hash[:16] - - -def compute_key_headers(key_url: str, secret_key: str) -> tuple[int, int, str, str] | None: - """ - Compute X-Key-Timestamp, X-Key-Nonce, X-Key-Path, and X-Fingerprint for a /key/ URL. - - Algorithm: - 1. Extract resource and number from URL pattern /key/{resource}/{number} - 2. ts = Unix timestamp in seconds - 3. hmac_hash = HMAC-SHA256(resource, secret_key).hex() - 4. nonce = proof-of-work: find i where MD5(hmac+resource+number+ts+i)[:4] < 0x1000 - 5. fingerprint = compute_fingerprint() - 6. key_path = HMAC-SHA256("resource|number|ts|fingerprint", secret_key).hex()[:16] - - Args: - key_url: The key URL containing /key/{resource}/{number} - secret_key: The HMAC secret key (channel_salt) - - Returns: - Tuple of (timestamp, nonce, key_path, fingerprint) or None if URL doesn't match pattern - """ - # Extract resource and number from URL - pattern = r"/key/([^/]+)/(\d+)" - match = re.search(pattern, key_url) - - if not match: - return None - - resource = match.group(1) - number = match.group(2) - - ts = int(time.time()) - - # Compute HMAC-SHA256 - hmac_hash = hmac.new(secret_key.encode("utf-8"), resource.encode("utf-8"), hashlib.sha256).hexdigest() - - # Proof-of-work loop - nonce = 0 - for i in range(100000): - combined = f"{hmac_hash}{resource}{number}{ts}{i}" - md5_hash = hashlib.md5(combined.encode("utf-8")).hexdigest() - prefix_value = int(md5_hash[:4], 16) - - if prefix_value < 0x1000: # < 4096 - nonce = i - break - - fingerprint = compute_fingerprint() - key_path = compute_key_path(resource, number, ts, fingerprint, secret_key) - - return ts, nonce, key_path, fingerprint - - -class DLHDExtractor(BaseExtractor): - """DLHD (DaddyLive) URL extractor for M3U8 streams. - - Supports the new authentication flow with: - - EPlayerAuth extraction (auth_token, channel_key, channel_salt) - - Server lookup for dynamic server selection - - Dynamic key header computation for AES-128 encrypted streams - """ - - def __init__(self, request_headers: dict): - super().__init__(request_headers) - self.mediaflow_endpoint = "hls_key_proxy" - self._iframe_context: Optional[str] = None - self._flaresolverr_cookies: Optional[str] = None - self._flaresolverr_user_agent: Optional[str] = None - - async def _fetch_via_flaresolverr(self, url: str) -> HttpResponse: - """Fetch a URL using FlareSolverr to bypass Cloudflare protection.""" - if not settings.flaresolverr_url: - raise ExtractorError("FlareSolverr URL not configured. Set FLARESOLVERR_URL in environment.") - - flaresolverr_endpoint = f"{settings.flaresolverr_url.rstrip('/')}/v1" - payload = { - "cmd": "request.get", - "url": url, - "maxTimeout": settings.flaresolverr_timeout * 1000, - } - - logger.info(f"Using FlareSolverr to fetch: {url}") - - async with aiohttp.ClientSession() as session: - async with session.post( - flaresolverr_endpoint, - json=payload, - timeout=aiohttp.ClientTimeout(total=settings.flaresolverr_timeout + 10), - ) as response: - if response.status != 200: - raise ExtractorError(f"FlareSolverr returned status {response.status}") - - data = await response.json() - - if data.get("status") != "ok": - raise ExtractorError(f"FlareSolverr failed: {data.get('message', 'Unknown error')}") - - solution = data.get("solution", {}) - html_content = solution.get("response", "") - final_url = solution.get("url", url) - status = solution.get("status", 200) - - # Store cookies and user-agent for subsequent requests - cookies = solution.get("cookies", []) - if cookies: - cookie_str = "; ".join([f"{c['name']}={c['value']}" for c in cookies]) - self._flaresolverr_cookies = cookie_str - logger.info(f"FlareSolverr provided {len(cookies)} cookies") - - user_agent = solution.get("userAgent") - if user_agent: - self._flaresolverr_user_agent = user_agent - logger.info(f"FlareSolverr user-agent: {user_agent}") - - logger.info(f"FlareSolverr successfully bypassed Cloudflare for: {url}") - - return HttpResponse( - status=status, - headers={}, - text=html_content, - content=html_content.encode("utf-8", errors="replace"), - url=final_url, - ) - - async def _make_request( - self, url: str, method: str = "GET", headers: Optional[Dict] = None, use_flaresolverr: bool = False, **kwargs - ) -> HttpResponse: - """Override to disable SSL verification and optionally use FlareSolverr.""" - # Use FlareSolverr for Cloudflare-protected pages - if use_flaresolverr and settings.flaresolverr_url: - return await self._fetch_via_flaresolverr(url) - - timeout = kwargs.pop("timeout", 15) - kwargs.pop("retries", 3) # consumed but not used directly - kwargs.pop("backoff_factor", 0.5) # consumed but not used directly - - # Merge headers - request_headers = self.base_headers.copy() - if headers: - request_headers.update(headers) - - # Add FlareSolverr cookies if available - if self._flaresolverr_cookies: - existing_cookies = request_headers.get("Cookie", "") - if existing_cookies: - request_headers["Cookie"] = f"{existing_cookies}; {self._flaresolverr_cookies}" - else: - request_headers["Cookie"] = self._flaresolverr_cookies - - # Use FlareSolverr user-agent if available - if self._flaresolverr_user_agent: - request_headers["User-Agent"] = self._flaresolverr_user_agent - - # Use create_aiohttp_session with verify=False for SSL bypass - async with create_aiohttp_session(url, timeout=timeout, verify=False) as (session, proxy_url): - async with session.request(method, url, headers=request_headers, proxy=proxy_url, **kwargs) as response: - content = await response.read() - final_url = str(response.url) - status = response.status - resp_headers = dict(response.headers) - - if status >= 400: - raise ExtractorError(f"HTTP error {status} while requesting {url}") - - return HttpResponse( - status=status, - headers=resp_headers, - text=content.decode("utf-8", errors="replace"), - content=content, - url=final_url, - ) - - async def _extract_session_data(self, iframe_url: str, main_url: str) -> dict | None: - """ - Fetch the iframe URL and extract auth_token, channel_key, and channel_salt. - - Args: - iframe_url: The iframe URL to fetch - main_url: The main site domain for Referer header - - Returns: - Dict with auth_token, channel_key, channel_salt, or None if not found - """ - headers = { - "User-Agent": self._flaresolverr_user_agent or DEFAULT_DLHD_USER_AGENT, - "Referer": f"https://{main_url}/", - } - - try: - resp = await self._make_request(iframe_url, headers=headers, timeout=12) - html = resp.text - except Exception as e: - logger.warning(f"Error fetching iframe URL: {e}") - return None - - # Pattern to extract EPlayerAuth.init block with authToken, channelKey, channelSalt - # Matches: EPlayerAuth.init({ authToken: '...', channelKey: '...', ..., channelSalt: '...' }); - auth_pattern = r"EPlayerAuth\.init\s*\(\s*\{\s*authToken:\s*'([^']+)'" - channel_key_pattern = r"channelKey:\s*'([^']+)'" - channel_salt_pattern = r"channelSalt:\s*'([^']+)'" - - # Pattern to extract server lookup base URL from fetchWithRetry call - lookup_pattern = r"fetchWithRetry\s*\(\s*'([^']+server_lookup\?channel_id=)" - - auth_match = re.search(auth_pattern, html) - channel_key_match = re.search(channel_key_pattern, html) - channel_salt_match = re.search(channel_salt_pattern, html) - lookup_match = re.search(lookup_pattern, html) - - if auth_match and channel_key_match and channel_salt_match: - result = { - "auth_token": auth_match.group(1), - "channel_key": channel_key_match.group(1), - "channel_salt": channel_salt_match.group(1), - } - if lookup_match: - result["server_lookup_url"] = lookup_match.group(1) + result["channel_key"] - - return result - - return None - - async def _get_server_key(self, server_lookup_url: str, iframe_url: str) -> str | None: - """ - Fetch the server lookup URL and extract the server_key. - - Args: - server_lookup_url: The server lookup URL - iframe_url: The iframe URL for extracting the host for headers - - Returns: - The server_key or None if not found - """ - parsed = urlparse(iframe_url) - iframe_host = parsed.netloc - - headers = { - "User-Agent": self._flaresolverr_user_agent or DEFAULT_DLHD_USER_AGENT, - "Referer": f"https://{iframe_host}/", - "Origin": f"https://{iframe_host}", - } - - try: - resp = await self._make_request(server_lookup_url, headers=headers, timeout=10) - data = resp.json() - return data.get("server_key") - except Exception as e: - logger.warning(f"Error fetching server lookup: {e}") - return None - - def _build_m3u8_url(self, server_key: str, channel_key: str) -> str: - """ - Build the m3u8 URL based on the server_key. - - Args: - server_key: The server key from server lookup - channel_key: The channel key - - Returns: - The m3u8 URL (with .css extension as per the original implementation) - """ - if server_key == "top1/cdn": - return f"https://top1.dvalna.ru/top1/cdn/{channel_key}/mono.css" - else: - return f"https://{server_key}new.dvalna.ru/{server_key}/{channel_key}/mono.css" - - async def _extract_new_auth_flow(self, iframe_url: str, iframe_content: str, headers: dict) -> Dict[str, Any]: - """Handles the new authentication flow found in recent updates.""" - - def _extract_params(js: str) -> Dict[str, Optional[str]]: - params = {} - patterns = { - "channel_key": r'(?:const|var|let)\s+(?:CHANNEL_KEY|channelKey)\s*=\s*["\']([^"\']+)["\']', - "auth_token": r'(?:const|var|let)\s+AUTH_TOKEN\s*=\s*["\']([^"\']+)["\']', - "auth_country": r'(?:const|var|let)\s+AUTH_COUNTRY\s*=\s*["\']([^"\']+)["\']', - "auth_ts": r'(?:const|var|let)\s+AUTH_TS\s*=\s*["\']([^"\']+)["\']', - "auth_expiry": r'(?:const|var|let)\s+AUTH_EXPIRY\s*=\s*["\']([^"\']+)["\']', - } - for key, pattern in patterns.items(): - match = re.search(pattern, js) - params[key] = match.group(1) if match else None - return params - - params = _extract_params(iframe_content) - - missing_params = [k for k, v in params.items() if not v] - if missing_params: - # This is not an error, just means it's not the new flow - raise ExtractorError(f"Not the new auth flow: missing params {missing_params}") - - logger.info("New auth flow detected. Proceeding with POST auth.") - - # 1. Initial Auth POST - auth_url = "https://security.newkso.ru/auth2.php" - - iframe_origin = f"https://{urlparse(iframe_url).netloc}" - auth_headers = headers.copy() - auth_headers.update( - { - "Accept": "*/*", - "Accept-Language": "en-US,en;q=0.9", - "Origin": iframe_origin, - "Referer": iframe_url, - "Sec-Fetch-Dest": "empty", - "Sec-Fetch-Mode": "cors", - "Sec-Fetch-Site": "cross-site", - "Priority": "u=1, i", - } - ) - - # Build form data for multipart/form-data - form_data = aiohttp.FormData() - form_data.add_field("channelKey", params["channel_key"]) - form_data.add_field("country", params["auth_country"]) - form_data.add_field("timestamp", params["auth_ts"]) - form_data.add_field("expiry", params["auth_expiry"]) - form_data.add_field("token", params["auth_token"]) - - try: - async with create_aiohttp_session(auth_url, timeout=12, verify=False) as (session, proxy_url): - async with session.post( - auth_url, - headers=auth_headers, - data=form_data, - proxy=proxy_url, - ) as response: - content = await response.read() - response.raise_for_status() - import json - - auth_data = json.loads(content.decode("utf-8")) - if not (auth_data.get("valid") or auth_data.get("success")): - raise ExtractorError(f"Initial auth failed with response: {auth_data}") - logger.info("New auth flow: Initial auth successful.") - except ExtractorError: - raise - except Exception as e: - raise ExtractorError(f"New auth flow failed during initial auth POST: {e}") - - # 2. Server Lookup - server_lookup_url = f"https://{urlparse(iframe_url).netloc}/server_lookup.js?channel_id={params['channel_key']}" - try: - # Use _make_request as it handles retries - lookup_resp = await self._make_request(server_lookup_url, headers=headers, timeout=10) - server_data = lookup_resp.json() - server_key = server_data.get("server_key") - if not server_key: - raise ExtractorError(f"No server_key in lookup response: {server_data}") - logger.info(f"New auth flow: Server lookup successful - Server key: {server_key}") - except ExtractorError: - raise - except Exception as e: - raise ExtractorError(f"New auth flow failed during server lookup: {e}") - - # 3. Build final stream URL - channel_key = params["channel_key"] - auth_token = params["auth_token"] - # The JS logic uses .css, not .m3u8 - if server_key == "top1/cdn": - stream_url = f"https://top1.newkso.ru/top1/cdn/{channel_key}/mono.css" - else: - stream_url = f"https://{server_key}new.newkso.ru/{server_key}/{channel_key}/mono.css" - - logger.info(f"New auth flow: Constructed stream URL: {stream_url}") - - stream_headers = { - "User-Agent": headers["User-Agent"], - "Referer": iframe_url, - "Origin": iframe_origin, - "Authorization": f"Bearer {auth_token}", - "X-Channel-Key": channel_key, - } - - return { - "destination_url": stream_url, - "request_headers": stream_headers, - "mediaflow_endpoint": "hls_manifest_proxy", - } - - async def _extract_lovecdn_stream(self, iframe_url: str, iframe_content: str, headers: dict) -> Dict[str, Any]: - """ - Alternative extractor for lovecdn.ru iframe that uses a different format. - """ - try: - # Look for direct stream URL patterns - m3u8_patterns = [ - r'["\']([^"\']*\.m3u8[^"\']*)["\']', - r'source[:\s]+["\']([^"\']+)["\']', - r'file[:\s]+["\']([^"\']+\.m3u8[^"\']*)["\']', - r'hlsManifestUrl[:\s]*["\']([^"\']+)["\']', - ] - - stream_url = None - for pattern in m3u8_patterns: - matches = re.findall(pattern, iframe_content) - for match in matches: - if ".m3u8" in match and match.startswith("http"): - stream_url = match - logger.info(f"Found direct m3u8 URL: {stream_url}") - break - if stream_url: - break - - # Pattern 2: Look for dynamic URL construction - if not stream_url: - channel_match = re.search(r'(?:stream|channel)["\s:=]+["\']([^"\']+)["\']', iframe_content) - server_match = re.search(r'(?:server|domain|host)["\s:=]+["\']([^"\']+)["\']', iframe_content) - - if channel_match: - channel_name = channel_match.group(1) - server = server_match.group(1) if server_match else "newkso.ru" - stream_url = f"https://{server}/{channel_name}/mono.m3u8" - logger.info(f"Constructed stream URL: {stream_url}") - - if not stream_url: - # Fallback: look for any URL that looks like a stream - url_pattern = r'https?://[^\s"\'<>]+\.m3u8[^\s"\'<>]*' - matches = re.findall(url_pattern, iframe_content) - if matches: - stream_url = matches[0] - logger.info(f"Found fallback stream URL: {stream_url}") - - if not stream_url: - raise ExtractorError("Could not find stream URL in lovecdn.ru iframe") - - # Use iframe URL as referer - iframe_origin = f"https://{urlparse(iframe_url).netloc}" - stream_headers = {"User-Agent": headers["User-Agent"], "Referer": iframe_url, "Origin": iframe_origin} - - # Determine endpoint based on the stream domain - endpoint = "hls_key_proxy" - - logger.info(f"Using lovecdn.ru stream with endpoint: {endpoint}") - - return { - "destination_url": stream_url, - "request_headers": stream_headers, - "mediaflow_endpoint": endpoint, - } - - except Exception as e: - raise ExtractorError(f"Failed to extract lovecdn.ru stream: {e}") - - async def _extract_direct_stream(self, channel_id: str) -> Dict[str, Any]: - """ - Direct stream extraction using server lookup API with the new auth flow. - This extracts auth_token, channel_key, channel_salt and computes key headers. - """ - # Common iframe domains for DLHD - iframe_domains = ["lefttoplay.xyz"] - - for iframe_domain in iframe_domains: - try: - iframe_url = f"https://{iframe_domain}/premiumtv/daddyhd.php?id={channel_id}" - logger.info(f"Attempting extraction via {iframe_domain}") - - session_data = await self._extract_session_data(iframe_url, "dlhd.link") - - if not session_data: - logger.debug(f"No session data from {iframe_domain}") - continue - - logger.info(f"Got session data from {iframe_domain}: channel_key={session_data['channel_key']}") - - # Get server key - if "server_lookup_url" not in session_data: - logger.debug(f"No server lookup URL from {iframe_domain}") - continue - - server_key = await self._get_server_key(session_data["server_lookup_url"], iframe_url) - - if not server_key: - logger.debug(f"No server key from {iframe_domain}") - continue - - logger.info(f"Got server key: {server_key}") - - # Build m3u8 URL - m3u8_url = self._build_m3u8_url(server_key, session_data["channel_key"]) - logger.info(f"M3U8 URL: {m3u8_url}") - - # Build stream headers with auth - iframe_origin = f"https://{iframe_domain}" - stream_headers = { - "User-Agent": self._flaresolverr_user_agent or DEFAULT_DLHD_USER_AGENT, - "Referer": iframe_url, - "Origin": iframe_origin, - "Authorization": f"Bearer {session_data['auth_token']}", - } - - # Return the result with key header parameters - # These will be used to compute headers when fetching keys - return { - "destination_url": m3u8_url, - "request_headers": stream_headers, - "mediaflow_endpoint": "hls_key_proxy", - # Force playlist processing since DLHD uses .css extension for m3u8 - "force_playlist_proxy": True, - # Key header computation parameters - "dlhd_key_params": { - "channel_salt": session_data["channel_salt"], - "auth_token": session_data["auth_token"], - "iframe_url": iframe_url, - }, - } - - except Exception as e: - logger.warning(f"Failed extraction via {iframe_domain}: {e}") - continue - - raise ExtractorError(f"Failed to extract stream from all iframe domains for channel {channel_id}") - - async def extract(self, url: str, **kwargs) -> Dict[str, Any]: - """Main extraction flow - uses direct server lookup with new auth flow.""" - - def extract_channel_id(u: str) -> Optional[str]: - match_watch_id = re.search(r"watch\.php\?id=(\d+)", u) - if match_watch_id: - return match_watch_id.group(1) - # Also try stream-XXX pattern - match_stream = re.search(r"stream-(\d+)", u) - if match_stream: - return match_stream.group(1) - return None - - try: - channel_id = extract_channel_id(url) - if not channel_id: - raise ExtractorError(f"Unable to extract channel ID from {url}") - - logger.info(f"Extracting DLHD stream for channel ID: {channel_id}") - - # Try direct stream extraction with new auth flow - try: - return await self._extract_direct_stream(channel_id) - except ExtractorError as e: - logger.warning(f"Direct stream extraction failed: {e}") - - # Fallback to legacy iframe-based extraction if direct fails - logger.info("Falling back to iframe-based extraction...") - return await self._extract_via_iframe(url, channel_id) - - except Exception as e: - raise ExtractorError(f"Extraction failed: {str(e)}") - - async def _extract_via_iframe(self, url: str, channel_id: str) -> Dict[str, Any]: - """Legacy iframe-based extraction flow - used as fallback.""" - baseurl = "https://dlhd.dad/" - - daddy_origin = urlparse(baseurl).scheme + "://" + urlparse(baseurl).netloc - daddylive_headers = { - "User-Agent": self._flaresolverr_user_agent - or "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36", - "Referer": baseurl, - "Origin": daddy_origin, - } - - # 1. Request initial page - use FlareSolverr if available to bypass Cloudflare - use_flaresolverr = settings.flaresolverr_url is not None - resp1 = await self._make_request(url, headers=daddylive_headers, timeout=15, use_flaresolverr=use_flaresolverr) - resp1_text = resp1.text - - # Update headers with FlareSolverr user-agent after initial request - if self._flaresolverr_user_agent: - daddylive_headers["User-Agent"] = self._flaresolverr_user_agent - - player_links = re.findall(r']*data-url="([^"]+)"[^>]*>Player\s*\d+', resp1_text) - if not player_links: - raise ExtractorError("No player links found on the page.") - - # Try all players and collect all valid iframes - last_player_error = None - iframe_candidates = [] - - for player_url in player_links: - try: - if not player_url.startswith("http"): - player_url = baseurl + player_url.lstrip("/") - - daddylive_headers["Referer"] = player_url - daddylive_headers["Origin"] = player_url - resp2 = await self._make_request(player_url, headers=daddylive_headers, timeout=12) - resp2_text = resp2.text - iframes2 = re.findall(r' dict: + """ + Use Byparr to bypass Cloudflare protection on the DoodStream embed page. + + Strategy: fetch the embed page without any injected script. Byparr's + Firefox/Camoufox browser auto-passes Cloudflare's bot checks and often + bypasses the Turnstile CAPTCHA gate directly, returning the embed HTML + with pass_md5. If the response doesn't contain pass_md5, reuse the CF + cookies + UA from Byparr in a follow-up curl_cffi request (which avoids + re-triggering the bot check). + """ + endpoint = f"{settings.byparr_url.rstrip('/')}/v1" + embed_url = url if "/e/" in url else f"https://{urlparse(url).netloc}/e/{video_id}" + payload = { + "cmd": "request.get", + "url": embed_url, + "maxTimeout": settings.byparr_timeout * 1000, } - embed_url = f"{self.base_url}/e/{video_id}" - html = (await self._make_request(embed_url, headers=headers)).text + async with aiohttp.ClientSession() as session: + async with session.post( + endpoint, + json=payload, + timeout=aiohttp.ClientTimeout(total=settings.byparr_timeout + 15), + ) as resp: + if resp.status != 200: + raise ExtractorError(f"Byparr HTTP {resp.status}") + data = await resp.json() - match = re.search(r"(\/pass_md5\/[^']+)", html) - if not match: - raise ExtractorError("Dood: pass_md5 not found") + if data.get("status") != "ok": + raise ExtractorError(f"Byparr: {data.get('message', 'unknown error')}") - pass_url = urljoin(self.base_url, match.group(1)) + solution = data.get("solution", {}) + final_url = solution.get("url", embed_url) + if not final_url.startswith("http"): + final_url = embed_url + base_url = f"https://{urlparse(final_url).netloc}" + html = solution.get("response", "") - base_stream = (await self._make_request(pass_url, headers=headers)).text.strip() + if "pass_md5" not in html: + # Byparr may not have the pass_md5 in the initial response. + # Try two recovery strategies in order: + # + # 1. Cookie reuse — if Byparr collected CF clearance cookies before + # the page loaded fully, inject them into a curl_cffi request. + # 2. Plain curl_cffi — Chrome TLS impersonation without JS execution. + raw_cookies = solution.get("cookies", []) + cookies = {c["name"]: c["value"] for c in raw_cookies} + ua = solution.get("userAgent", _DOOD_UA) - token_match = re.search(r"token=([^&]+)", html) + if cookies: + cf_domain = ( + next( + (c.get("domain", "").lstrip(".") for c in raw_cookies if c.get("name") == "cf_clearance"), + None, + ) + or "playmogo.com" + ) + retry_url = f"https://{cf_domain}/e/{video_id}" + logger.debug( + "Byparr response lacked pass_md5 (final_url=%s); retrying %s with CF cookies via curl_cffi", + final_url, + retry_url, + ) + proxy = self._get_proxy(retry_url) + async with AsyncSession() as s: + r = await s.get( + retry_url, + impersonate="chrome", + cookies=cookies, + headers={"User-Agent": ua, "Referer": f"https://{cf_domain}/"}, + timeout=20, + **({"proxy": proxy} if proxy else {}), + ) + html = r.text + final_url = str(r.url) + base_url = f"https://{urlparse(final_url).netloc}" + + if "pass_md5" not in html: + logger.debug("Byparr cookie reuse also failed; falling back to curl_cffi for %s", embed_url) + return await self._extract_via_curl_cffi(embed_url, video_id) + + return await self._parse_embed_html(html, base_url) + + # ------------------------------------------------------------------ + # Path 2 – curl_cffi (bypasses CF bot protection; Turnstile may block) + # ------------------------------------------------------------------ + + async def _extract_via_curl_cffi(self, url: str, video_id: str) -> dict: + proxy = self._get_proxy(url) + async with AsyncSession() as s: + r = await s.get( + url, + impersonate="chrome", + headers={"Referer": f"https://{urlparse(url).netloc}/"}, + timeout=30, + allow_redirects=True, + **({"proxy": proxy} if proxy else {}), + ) + final_url = str(r.url) + html = r.text + base_url = f"https://{urlparse(final_url).netloc}" + + if "pass_md5" not in html: + if "turnstile" in html.lower() or "captcha_l" in html: + raise ExtractorError( + "DoodStream: site is serving a Turnstile CAPTCHA that requires " + "browser interaction — cannot be bypassed automatically from this " + "network location. Try a residential IP or a VPN/proxy." + ) + raise ExtractorError(f"DoodStream: pass_md5 not found in embed HTML ({final_url})") + + return await self._parse_embed_html(html, base_url) + + # ------------------------------------------------------------------ + # Common HTML parser + # ------------------------------------------------------------------ + + async def _parse_embed_html(self, html: str, base_url: str) -> dict: + pass_match = re.search(r"(/pass_md5/[^'\"<>\s]+)", html) + if not pass_match: + raise ExtractorError("DoodStream: pass_md5 path not found in embed HTML") + + pass_url = urljoin(base_url, pass_match.group(1)) + ua = self.base_headers.get("user-agent") or _DOOD_UA + headers = { + "user-agent": ua, + "referer": f"{base_url}/", + } + + proxy = self._get_proxy(pass_url) + async with AsyncSession() as s: + r = await s.get( + pass_url, + impersonate="chrome", + headers=headers, + timeout=20, + **({"proxy": proxy} if proxy else {}), + ) + + base_stream = r.text.strip() + if not base_stream or "RELOAD" in base_stream: + raise ExtractorError( + "DoodStream: pass_md5 endpoint returned no stream URL " + "(captcha session may have expired). " + "Ensure BYPARR_URL is set for reliable extraction." + ) + + token_match = re.search(r"token=([^&\s'\"]+)", html) if not token_match: - raise ExtractorError("Dood: token missing") + raise ExtractorError("DoodStream: token not found in embed HTML") token = token_match.group(1) - - final_url = f"{base_stream}123456789?token={token}&expiry={int(time.time())}" + expiry = int(time.time()) + final_url = f"{base_stream}123456789?token={token}&expiry={expiry}" return { "destination_url": final_url, diff --git a/mediaflow_proxy/extractors/factory.py b/mediaflow_proxy/extractors/factory.py index e3158bc..8f02737 100644 --- a/mediaflow_proxy/extractors/factory.py +++ b/mediaflow_proxy/extractors/factory.py @@ -1,8 +1,8 @@ from typing import Dict, Type from mediaflow_proxy.extractors.base import BaseExtractor, ExtractorError -from mediaflow_proxy.extractors.dlhd import DLHDExtractor from mediaflow_proxy.extractors.doodstream import DoodStreamExtractor +from mediaflow_proxy.extractors.city import CityExtractor from mediaflow_proxy.extractors.sportsonline import SportsonlineExtractor from mediaflow_proxy.extractors.filelions import FileLionsExtractor from mediaflow_proxy.extractors.filemoon import FileMoonExtractor @@ -24,12 +24,14 @@ from mediaflow_proxy.extractors.vidoza import VidozaExtractor from mediaflow_proxy.extractors.vixcloud import VixCloudExtractor from mediaflow_proxy.extractors.fastream import FastreamExtractor from mediaflow_proxy.extractors.voe import VoeExtractor +from mediaflow_proxy.extractors.vidfast import VidFastExtractor class ExtractorFactory: """Factory for creating URL extractors.""" _extractors: Dict[str, Type[BaseExtractor]] = { + "City": CityExtractor, "Doodstream": DoodStreamExtractor, "FileLions": FileLionsExtractor, "FileMoon": FileMoonExtractor, @@ -46,13 +48,13 @@ class ExtractorFactory: "Maxstream": MaxstreamExtractor, "LiveTV": LiveTVExtractor, "LuluStream": LuluStreamExtractor, - "DLHD": DLHDExtractor, "Vavoo": VavooExtractor, "Vidmoly": VidmolyExtractor, "Vidoza": VidozaExtractor, "Fastream": FastreamExtractor, "Voe": VoeExtractor, "Sportsonline": SportsonlineExtractor, + "VidFast": VidFastExtractor, } @classmethod diff --git a/mediaflow_proxy/extractors/lulustream.py b/mediaflow_proxy/extractors/lulustream.py index 63aaf7d..bdd18e9 100644 --- a/mediaflow_proxy/extractors/lulustream.py +++ b/mediaflow_proxy/extractors/lulustream.py @@ -1,23 +1,42 @@ import re from typing import Dict, Any +from curl_cffi.requests import AsyncSession + from mediaflow_proxy.extractors.base import BaseExtractor, ExtractorError class LuluStreamExtractor(BaseExtractor): + """LuluStream URL extractor. + + Uses curl_cffi + Chrome impersonation to bypass Cloudflare protection. + lulustream.com embeds are served via luluvdo.com. + """ + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.mediaflow_endpoint = "hls_manifest_proxy" async def extract(self, url: str, **kwargs) -> Dict[str, Any]: - response = await self._make_request(url) + proxy = self._get_proxy(url) + async with AsyncSession() as session: + response = await session.get( + url, + impersonate="chrome", + timeout=30, + allow_redirects=True, + **({"proxy": proxy} if proxy else {}), + ) + + if response.status_code >= 400: + raise ExtractorError(f"HTTP {response.status_code} while fetching {url}") # See https://github.com/Gujal00/ResolveURL/blob/master/script.module.resolveurl/lib/resolveurl/plugins/lulustream.py pattern = r"""sources:\s*\[{file:\s*["'](?P[^"']+)""" match = re.search(pattern, response.text, re.DOTALL) if not match: - raise ExtractorError("Failed to extract source URL") - final_url = match.group(1) + raise ExtractorError("LuluStream: Failed to extract source URL") + final_url = match.group("url") self.base_headers["referer"] = url return { diff --git a/mediaflow_proxy/extractors/sportsonline.py b/mediaflow_proxy/extractors/sportsonline.py index a72a62b..fb747ff 100644 --- a/mediaflow_proxy/extractors/sportsonline.py +++ b/mediaflow_proxy/extractors/sportsonline.py @@ -1,7 +1,7 @@ import re import logging from typing import Any, Dict -from urllib.parse import urlparse +from urllib.parse import urljoin, urlparse from mediaflow_proxy.extractors.base import BaseExtractor, ExtractorError from mediaflow_proxy.utils.packed import unpack @@ -14,7 +14,7 @@ class SportsonlineExtractor(BaseExtractor): Strategy: 1. Fetch page -> find first