StreamingCommunity 2.5.2__py3-none-any.whl → 2.5.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of StreamingCommunity might be problematic. Click here for more details.
- StreamingCommunity/Api/Player/Helper/Vixcloud/js_parser.py +143 -143
- StreamingCommunity/Api/Player/Helper/Vixcloud/util.py +136 -136
- StreamingCommunity/Api/Player/ddl.py +89 -89
- StreamingCommunity/Api/Player/maxstream.py +151 -151
- StreamingCommunity/Api/Player/supervideo.py +193 -193
- StreamingCommunity/Api/Player/vixcloud.py +272 -272
- StreamingCommunity/Api/Site/1337xx/__init__.py +51 -50
- StreamingCommunity/Api/Site/1337xx/costant.py +14 -14
- StreamingCommunity/Api/Site/1337xx/site.py +87 -89
- StreamingCommunity/Api/Site/1337xx/title.py +63 -64
- StreamingCommunity/Api/Site/altadefinizionegratis/__init__.py +74 -50
- StreamingCommunity/Api/Site/altadefinizionegratis/costant.py +21 -19
- StreamingCommunity/Api/Site/altadefinizionegratis/film.py +81 -72
- StreamingCommunity/Api/Site/altadefinizionegratis/site.py +116 -94
- StreamingCommunity/Api/Site/animeunity/__init__.py +75 -50
- StreamingCommunity/Api/Site/animeunity/costant.py +21 -19
- StreamingCommunity/Api/Site/animeunity/film_serie.py +171 -134
- StreamingCommunity/Api/Site/animeunity/site.py +191 -174
- StreamingCommunity/Api/Site/animeunity/util/ScrapeSerie.py +97 -97
- StreamingCommunity/Api/Site/cb01new/__init__.py +51 -51
- StreamingCommunity/Api/Site/cb01new/costant.py +19 -19
- StreamingCommunity/Api/Site/cb01new/film.py +61 -71
- StreamingCommunity/Api/Site/cb01new/site.py +82 -82
- StreamingCommunity/Api/Site/ddlstreamitaly/__init__.py +55 -55
- StreamingCommunity/Api/Site/ddlstreamitaly/costant.py +20 -20
- StreamingCommunity/Api/Site/ddlstreamitaly/series.py +149 -145
- StreamingCommunity/Api/Site/ddlstreamitaly/site.py +98 -98
- StreamingCommunity/Api/Site/ddlstreamitaly/util/ScrapeSerie.py +84 -84
- StreamingCommunity/Api/Site/guardaserie/__init__.py +50 -50
- StreamingCommunity/Api/Site/guardaserie/costant.py +19 -19
- StreamingCommunity/Api/Site/guardaserie/series.py +199 -198
- StreamingCommunity/Api/Site/guardaserie/site.py +89 -89
- StreamingCommunity/Api/Site/guardaserie/util/ScrapeSerie.py +110 -110
- StreamingCommunity/Api/Site/ilcorsaronero/__init__.py +51 -51
- StreamingCommunity/Api/Site/ilcorsaronero/costant.py +18 -18
- StreamingCommunity/Api/Site/ilcorsaronero/site.py +71 -71
- StreamingCommunity/Api/Site/ilcorsaronero/title.py +44 -44
- StreamingCommunity/Api/Site/ilcorsaronero/util/ilCorsarScraper.py +149 -149
- StreamingCommunity/Api/Site/mostraguarda/__init__.py +48 -48
- StreamingCommunity/Api/Site/mostraguarda/costant.py +18 -18
- StreamingCommunity/Api/Site/mostraguarda/film.py +90 -101
- StreamingCommunity/Api/Site/streamingcommunity/__init__.py +79 -55
- StreamingCommunity/Api/Site/streamingcommunity/costant.py +21 -19
- StreamingCommunity/Api/Site/streamingcommunity/film.py +86 -75
- StreamingCommunity/Api/Site/streamingcommunity/series.py +259 -207
- StreamingCommunity/Api/Site/streamingcommunity/site.py +156 -142
- StreamingCommunity/Api/Site/streamingcommunity/util/ScrapeSerie.py +124 -124
- StreamingCommunity/Api/Template/Class/SearchType.py +101 -101
- StreamingCommunity/Api/Template/Util/__init__.py +4 -4
- StreamingCommunity/Api/Template/Util/get_domain.py +201 -201
- StreamingCommunity/Api/Template/Util/manage_ep.py +178 -178
- StreamingCommunity/Api/Template/Util/recall_search.py +37 -37
- StreamingCommunity/Api/Template/__init__.py +2 -2
- StreamingCommunity/Api/Template/site.py +87 -87
- StreamingCommunity/Lib/Downloader/HLS/downloader.py +529 -1008
- StreamingCommunity/Lib/Downloader/HLS/proxyes.py +110 -110
- StreamingCommunity/Lib/Downloader/HLS/segments.py +446 -573
- StreamingCommunity/Lib/Downloader/MP4/downloader.py +181 -155
- StreamingCommunity/Lib/Downloader/TOR/downloader.py +297 -295
- StreamingCommunity/Lib/Downloader/__init__.py +4 -4
- StreamingCommunity/Lib/FFmpeg/__init__.py +4 -4
- StreamingCommunity/Lib/FFmpeg/capture.py +170 -170
- StreamingCommunity/Lib/FFmpeg/command.py +264 -296
- StreamingCommunity/Lib/FFmpeg/util.py +248 -248
- StreamingCommunity/Lib/M3U8/__init__.py +5 -5
- StreamingCommunity/Lib/M3U8/decryptor.py +164 -164
- StreamingCommunity/Lib/M3U8/estimator.py +146 -228
- StreamingCommunity/Lib/M3U8/parser.py +666 -666
- StreamingCommunity/Lib/M3U8/url_fixer.py +57 -57
- StreamingCommunity/Lib/TMBD/__init__.py +1 -1
- StreamingCommunity/Lib/TMBD/obj_tmbd.py +39 -39
- StreamingCommunity/Lib/TMBD/tmdb.py +345 -345
- StreamingCommunity/TelegramHelp/__init__.py +0 -0
- StreamingCommunity/TelegramHelp/request_manager.py +82 -0
- StreamingCommunity/TelegramHelp/session.py +56 -0
- StreamingCommunity/TelegramHelp/telegram_bot.py +561 -0
- StreamingCommunity/Upload/update.py +75 -67
- StreamingCommunity/Upload/version.py +5 -5
- StreamingCommunity/Util/_jsonConfig.py +227 -228
- StreamingCommunity/Util/call_stack.py +42 -42
- StreamingCommunity/Util/color.py +20 -20
- StreamingCommunity/Util/console.py +12 -12
- StreamingCommunity/Util/ffmpeg_installer.py +342 -370
- StreamingCommunity/Util/headers.py +159 -159
- StreamingCommunity/Util/logger.py +61 -61
- StreamingCommunity/Util/message.py +36 -64
- StreamingCommunity/Util/os.py +500 -507
- StreamingCommunity/Util/table.py +271 -228
- StreamingCommunity/run.py +352 -245
- {StreamingCommunity-2.5.2.dist-info → StreamingCommunity-2.5.5.dist-info}/LICENSE +674 -674
- {StreamingCommunity-2.5.2.dist-info → StreamingCommunity-2.5.5.dist-info}/METADATA +601 -543
- StreamingCommunity-2.5.5.dist-info/RECORD +96 -0
- {StreamingCommunity-2.5.2.dist-info → StreamingCommunity-2.5.5.dist-info}/entry_points.txt +0 -1
- StreamingCommunity/Api/Player/Helper/Vixcloud/__pycache__/js_parser.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Player/Helper/Vixcloud/__pycache__/js_parser.cpython-39.pyc +0 -0
- StreamingCommunity/Api/Player/Helper/Vixcloud/__pycache__/util.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Player/Helper/Vixcloud/__pycache__/util.cpython-39.pyc +0 -0
- StreamingCommunity/Api/Player/__pycache__/ddl.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Player/__pycache__/ddl.cpython-39.pyc +0 -0
- StreamingCommunity/Api/Player/__pycache__/maxstream.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Player/__pycache__/maxstream.cpython-39.pyc +0 -0
- StreamingCommunity/Api/Player/__pycache__/supervideo.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Player/__pycache__/supervideo.cpython-39.pyc +0 -0
- StreamingCommunity/Api/Player/__pycache__/vixcloud.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Player/__pycache__/vixcloud.cpython-39.pyc +0 -0
- StreamingCommunity/Api/Site/1337xx/__pycache__/__init__.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Site/1337xx/__pycache__/__init__.cpython-39.pyc +0 -0
- StreamingCommunity/Api/Site/1337xx/__pycache__/costant.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Site/1337xx/__pycache__/costant.cpython-39.pyc +0 -0
- StreamingCommunity/Api/Site/1337xx/__pycache__/site.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Site/1337xx/__pycache__/site.cpython-39.pyc +0 -0
- StreamingCommunity/Api/Site/1337xx/__pycache__/title.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Site/1337xx/__pycache__/title.cpython-39.pyc +0 -0
- StreamingCommunity/Api/Site/altadefinizionegratis/__pycache__/__init__.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Site/altadefinizionegratis/__pycache__/__init__.cpython-39.pyc +0 -0
- StreamingCommunity/Api/Site/altadefinizionegratis/__pycache__/costant.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Site/altadefinizionegratis/__pycache__/costant.cpython-39.pyc +0 -0
- StreamingCommunity/Api/Site/altadefinizionegratis/__pycache__/film.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Site/altadefinizionegratis/__pycache__/film.cpython-39.pyc +0 -0
- StreamingCommunity/Api/Site/altadefinizionegratis/__pycache__/site.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Site/altadefinizionegratis/__pycache__/site.cpython-39.pyc +0 -0
- StreamingCommunity/Api/Site/animeunity/__pycache__/__init__.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Site/animeunity/__pycache__/__init__.cpython-39.pyc +0 -0
- StreamingCommunity/Api/Site/animeunity/__pycache__/costant.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Site/animeunity/__pycache__/costant.cpython-39.pyc +0 -0
- StreamingCommunity/Api/Site/animeunity/__pycache__/film_serie.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Site/animeunity/__pycache__/film_serie.cpython-39.pyc +0 -0
- StreamingCommunity/Api/Site/animeunity/__pycache__/site.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Site/animeunity/__pycache__/site.cpython-39.pyc +0 -0
- StreamingCommunity/Api/Site/animeunity/util/__pycache__/ScrapeSerie.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Site/animeunity/util/__pycache__/ScrapeSerie.cpython-39.pyc +0 -0
- StreamingCommunity/Api/Site/cb01new/__pycache__/__init__.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Site/cb01new/__pycache__/__init__.cpython-39.pyc +0 -0
- StreamingCommunity/Api/Site/cb01new/__pycache__/costant.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Site/cb01new/__pycache__/costant.cpython-39.pyc +0 -0
- StreamingCommunity/Api/Site/cb01new/__pycache__/film.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Site/cb01new/__pycache__/film.cpython-39.pyc +0 -0
- StreamingCommunity/Api/Site/cb01new/__pycache__/site.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Site/cb01new/__pycache__/site.cpython-39.pyc +0 -0
- StreamingCommunity/Api/Site/ddlstreamitaly/__pycache__/__init__.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Site/ddlstreamitaly/__pycache__/__init__.cpython-39.pyc +0 -0
- StreamingCommunity/Api/Site/ddlstreamitaly/__pycache__/costant.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Site/ddlstreamitaly/__pycache__/costant.cpython-39.pyc +0 -0
- StreamingCommunity/Api/Site/ddlstreamitaly/__pycache__/series.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Site/ddlstreamitaly/__pycache__/series.cpython-39.pyc +0 -0
- StreamingCommunity/Api/Site/ddlstreamitaly/__pycache__/site.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Site/ddlstreamitaly/__pycache__/site.cpython-39.pyc +0 -0
- StreamingCommunity/Api/Site/ddlstreamitaly/util/__pycache__/ScrapeSerie.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Site/ddlstreamitaly/util/__pycache__/ScrapeSerie.cpython-39.pyc +0 -0
- StreamingCommunity/Api/Site/guardaserie/__pycache__/__init__.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Site/guardaserie/__pycache__/__init__.cpython-39.pyc +0 -0
- StreamingCommunity/Api/Site/guardaserie/__pycache__/costant.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Site/guardaserie/__pycache__/costant.cpython-39.pyc +0 -0
- StreamingCommunity/Api/Site/guardaserie/__pycache__/series.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Site/guardaserie/__pycache__/series.cpython-39.pyc +0 -0
- StreamingCommunity/Api/Site/guardaserie/__pycache__/site.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Site/guardaserie/__pycache__/site.cpython-39.pyc +0 -0
- StreamingCommunity/Api/Site/guardaserie/util/__pycache__/ScrapeSerie.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Site/guardaserie/util/__pycache__/ScrapeSerie.cpython-39.pyc +0 -0
- StreamingCommunity/Api/Site/ilcorsaronero/__pycache__/__init__.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Site/ilcorsaronero/__pycache__/__init__.cpython-39.pyc +0 -0
- StreamingCommunity/Api/Site/ilcorsaronero/__pycache__/costant.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Site/ilcorsaronero/__pycache__/costant.cpython-39.pyc +0 -0
- StreamingCommunity/Api/Site/ilcorsaronero/__pycache__/site.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Site/ilcorsaronero/__pycache__/site.cpython-39.pyc +0 -0
- StreamingCommunity/Api/Site/ilcorsaronero/__pycache__/title.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Site/ilcorsaronero/__pycache__/title.cpython-39.pyc +0 -0
- StreamingCommunity/Api/Site/ilcorsaronero/util/__pycache__/ilCorsarScraper.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Site/ilcorsaronero/util/__pycache__/ilCorsarScraper.cpython-39.pyc +0 -0
- StreamingCommunity/Api/Site/mostraguarda/__pycache__/__init__.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Site/mostraguarda/__pycache__/__init__.cpython-39.pyc +0 -0
- StreamingCommunity/Api/Site/mostraguarda/__pycache__/costant.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Site/mostraguarda/__pycache__/costant.cpython-39.pyc +0 -0
- StreamingCommunity/Api/Site/mostraguarda/__pycache__/film.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Site/mostraguarda/__pycache__/film.cpython-39.pyc +0 -0
- StreamingCommunity/Api/Site/streamingcommunity/__pycache__/__init__.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Site/streamingcommunity/__pycache__/__init__.cpython-39.pyc +0 -0
- StreamingCommunity/Api/Site/streamingcommunity/__pycache__/costant.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Site/streamingcommunity/__pycache__/costant.cpython-39.pyc +0 -0
- StreamingCommunity/Api/Site/streamingcommunity/__pycache__/film.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Site/streamingcommunity/__pycache__/film.cpython-39.pyc +0 -0
- StreamingCommunity/Api/Site/streamingcommunity/__pycache__/series.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Site/streamingcommunity/__pycache__/series.cpython-39.pyc +0 -0
- StreamingCommunity/Api/Site/streamingcommunity/__pycache__/site.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Site/streamingcommunity/__pycache__/site.cpython-39.pyc +0 -0
- StreamingCommunity/Api/Site/streamingcommunity/util/__pycache__/ScrapeSerie.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Site/streamingcommunity/util/__pycache__/ScrapeSerie.cpython-39.pyc +0 -0
- StreamingCommunity/Api/Template/Class/__pycache__/SearchType.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Template/Class/__pycache__/SearchType.cpython-39.pyc +0 -0
- StreamingCommunity/Api/Template/Util/__pycache__/__init__.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Template/Util/__pycache__/__init__.cpython-39.pyc +0 -0
- StreamingCommunity/Api/Template/Util/__pycache__/get_domain.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Template/Util/__pycache__/get_domain.cpython-39.pyc +0 -0
- StreamingCommunity/Api/Template/Util/__pycache__/manage_ep.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Template/Util/__pycache__/manage_ep.cpython-39.pyc +0 -0
- StreamingCommunity/Api/Template/Util/__pycache__/recall_search.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Template/Util/__pycache__/recall_search.cpython-39.pyc +0 -0
- StreamingCommunity/Api/Template/__pycache__/__init__.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Template/__pycache__/__init__.cpython-39.pyc +0 -0
- StreamingCommunity/Api/Template/__pycache__/site.cpython-313.pyc +0 -0
- StreamingCommunity/Api/Template/__pycache__/site.cpython-39.pyc +0 -0
- StreamingCommunity/Lib/Downloader/HLS/__pycache__/downloader.cpython-313.pyc +0 -0
- StreamingCommunity/Lib/Downloader/HLS/__pycache__/downloader.cpython-39.pyc +0 -0
- StreamingCommunity/Lib/Downloader/HLS/__pycache__/proxyes.cpython-313.pyc +0 -0
- StreamingCommunity/Lib/Downloader/HLS/__pycache__/proxyes.cpython-39.pyc +0 -0
- StreamingCommunity/Lib/Downloader/HLS/__pycache__/segments.cpython-313.pyc +0 -0
- StreamingCommunity/Lib/Downloader/HLS/__pycache__/segments.cpython-39.pyc +0 -0
- StreamingCommunity/Lib/Downloader/MP4/__pycache__/downloader.cpython-313.pyc +0 -0
- StreamingCommunity/Lib/Downloader/MP4/__pycache__/downloader.cpython-39.pyc +0 -0
- StreamingCommunity/Lib/Downloader/TOR/__pycache__/downloader.cpython-313.pyc +0 -0
- StreamingCommunity/Lib/Downloader/TOR/__pycache__/downloader.cpython-39.pyc +0 -0
- StreamingCommunity/Lib/Downloader/__pycache__/__init__.cpython-313.pyc +0 -0
- StreamingCommunity/Lib/Downloader/__pycache__/__init__.cpython-39.pyc +0 -0
- StreamingCommunity/Lib/FFmpeg/__pycache__/__init__.cpython-313.pyc +0 -0
- StreamingCommunity/Lib/FFmpeg/__pycache__/__init__.cpython-39.pyc +0 -0
- StreamingCommunity/Lib/FFmpeg/__pycache__/capture.cpython-313.pyc +0 -0
- StreamingCommunity/Lib/FFmpeg/__pycache__/capture.cpython-39.pyc +0 -0
- StreamingCommunity/Lib/FFmpeg/__pycache__/command.cpython-313.pyc +0 -0
- StreamingCommunity/Lib/FFmpeg/__pycache__/command.cpython-39.pyc +0 -0
- StreamingCommunity/Lib/FFmpeg/__pycache__/util.cpython-313.pyc +0 -0
- StreamingCommunity/Lib/FFmpeg/__pycache__/util.cpython-39.pyc +0 -0
- StreamingCommunity/Lib/M3U8/__pycache__/__init__.cpython-313.pyc +0 -0
- StreamingCommunity/Lib/M3U8/__pycache__/__init__.cpython-39.pyc +0 -0
- StreamingCommunity/Lib/M3U8/__pycache__/decryptor.cpython-313.pyc +0 -0
- StreamingCommunity/Lib/M3U8/__pycache__/decryptor.cpython-39.pyc +0 -0
- StreamingCommunity/Lib/M3U8/__pycache__/estimator.cpython-313.pyc +0 -0
- StreamingCommunity/Lib/M3U8/__pycache__/estimator.cpython-39.pyc +0 -0
- StreamingCommunity/Lib/M3U8/__pycache__/parser.cpython-313.pyc +0 -0
- StreamingCommunity/Lib/M3U8/__pycache__/parser.cpython-39.pyc +0 -0
- StreamingCommunity/Lib/M3U8/__pycache__/url_fixer.cpython-313.pyc +0 -0
- StreamingCommunity/Lib/M3U8/__pycache__/url_fixer.cpython-39.pyc +0 -0
- StreamingCommunity/Lib/TMBD/__pycache__/__init__.cpython-313.pyc +0 -0
- StreamingCommunity/Lib/TMBD/__pycache__/__init__.cpython-39.pyc +0 -0
- StreamingCommunity/Lib/TMBD/__pycache__/obj_tmbd.cpython-313.pyc +0 -0
- StreamingCommunity/Lib/TMBD/__pycache__/obj_tmbd.cpython-39.pyc +0 -0
- StreamingCommunity/Lib/TMBD/__pycache__/tmdb.cpython-313.pyc +0 -0
- StreamingCommunity/Lib/TMBD/__pycache__/tmdb.cpython-39.pyc +0 -0
- StreamingCommunity/Upload/__pycache__/update.cpython-313.pyc +0 -0
- StreamingCommunity/Upload/__pycache__/update.cpython-39.pyc +0 -0
- StreamingCommunity/Upload/__pycache__/version.cpython-313.pyc +0 -0
- StreamingCommunity/Upload/__pycache__/version.cpython-39.pyc +0 -0
- StreamingCommunity/Util/__pycache__/_jsonConfig.cpython-313.pyc +0 -0
- StreamingCommunity/Util/__pycache__/_jsonConfig.cpython-39.pyc +0 -0
- StreamingCommunity/Util/__pycache__/call_stack.cpython-313.pyc +0 -0
- StreamingCommunity/Util/__pycache__/call_stack.cpython-39.pyc +0 -0
- StreamingCommunity/Util/__pycache__/color.cpython-313.pyc +0 -0
- StreamingCommunity/Util/__pycache__/color.cpython-39.pyc +0 -0
- StreamingCommunity/Util/__pycache__/console.cpython-313.pyc +0 -0
- StreamingCommunity/Util/__pycache__/console.cpython-39.pyc +0 -0
- StreamingCommunity/Util/__pycache__/ffmpeg_installer.cpython-313.pyc +0 -0
- StreamingCommunity/Util/__pycache__/ffmpeg_installer.cpython-39.pyc +0 -0
- StreamingCommunity/Util/__pycache__/headers.cpython-313.pyc +0 -0
- StreamingCommunity/Util/__pycache__/headers.cpython-39.pyc +0 -0
- StreamingCommunity/Util/__pycache__/logger.cpython-313.pyc +0 -0
- StreamingCommunity/Util/__pycache__/logger.cpython-39.pyc +0 -0
- StreamingCommunity/Util/__pycache__/message.cpython-313.pyc +0 -0
- StreamingCommunity/Util/__pycache__/message.cpython-39.pyc +0 -0
- StreamingCommunity/Util/__pycache__/os.cpython-313.pyc +0 -0
- StreamingCommunity/Util/__pycache__/os.cpython-39.pyc +0 -0
- StreamingCommunity/Util/__pycache__/table.cpython-313.pyc +0 -0
- StreamingCommunity/Util/__pycache__/table.cpython-39.pyc +0 -0
- StreamingCommunity/__pycache__/__init__.cpython-313.pyc +0 -0
- StreamingCommunity/__pycache__/__init__.cpython-39.pyc +0 -0
- StreamingCommunity/__pycache__/run.cpython-313.pyc +0 -0
- StreamingCommunity/__pycache__/run.cpython-39.pyc +0 -0
- StreamingCommunity-2.5.2.dist-info/RECORD +0 -264
- {StreamingCommunity-2.5.2.dist-info → StreamingCommunity-2.5.5.dist-info}/WHEEL +0 -0
- {StreamingCommunity-2.5.2.dist-info → StreamingCommunity-2.5.5.dist-info}/top_level.txt +0 -0
|
@@ -1,573 +1,446 @@
|
|
|
1
|
-
# 18.04.24
|
|
2
|
-
|
|
3
|
-
import os
|
|
4
|
-
import sys
|
|
5
|
-
import time
|
|
6
|
-
import queue
|
|
7
|
-
import signal
|
|
8
|
-
import logging
|
|
9
|
-
import binascii
|
|
10
|
-
import threading
|
|
11
|
-
|
|
12
|
-
from
|
|
13
|
-
from
|
|
14
|
-
from
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
# External libraries
|
|
18
|
-
import httpx
|
|
19
|
-
from tqdm import tqdm
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
# Internal utilities
|
|
23
|
-
from StreamingCommunity.Util.
|
|
24
|
-
from StreamingCommunity.Util.
|
|
25
|
-
from StreamingCommunity.Util.
|
|
26
|
-
from StreamingCommunity.Util._jsonConfig import config_manager
|
|
27
|
-
from StreamingCommunity.Util.os import os_manager
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
self.
|
|
73
|
-
self.
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
self.
|
|
77
|
-
|
|
78
|
-
self.
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
self.
|
|
84
|
-
self.
|
|
85
|
-
|
|
86
|
-
#
|
|
87
|
-
self.
|
|
88
|
-
self.
|
|
89
|
-
self.
|
|
90
|
-
|
|
91
|
-
self.
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
self.
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
)
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
#
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
"""
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
self.
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
"""
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
if TQDM_USE_LARGE_BAR:
|
|
448
|
-
bar_format = (
|
|
449
|
-
f"{Colors.YELLOW}[HLS] {Colors.WHITE}({Colors.CYAN}{description}{Colors.WHITE}): "
|
|
450
|
-
f"{Colors.RED}{{percentage:.2f}}% "
|
|
451
|
-
f"{Colors.MAGENTA}{{bar}} "
|
|
452
|
-
f"{Colors.WHITE}[ {Colors.YELLOW}{{n_fmt}}{Colors.WHITE} / {Colors.RED}{{total_fmt}} {Colors.WHITE}] "
|
|
453
|
-
f"{Colors.YELLOW}{{elapsed}} {Colors.WHITE}< {Colors.CYAN}{{remaining}}{{postfix}} {Colors.WHITE}]"
|
|
454
|
-
)
|
|
455
|
-
else:
|
|
456
|
-
bar_format = (
|
|
457
|
-
f"{Colors.YELLOW}Proc{Colors.WHITE}: "
|
|
458
|
-
f"{Colors.RED}{{percentage:.2f}}% "
|
|
459
|
-
f"{Colors.WHITE}| "
|
|
460
|
-
f"{Colors.CYAN}{{remaining}}{{postfix}} {Colors.WHITE}]"
|
|
461
|
-
)
|
|
462
|
-
|
|
463
|
-
# Create progress bar
|
|
464
|
-
progress_bar = tqdm(
|
|
465
|
-
total=len(self.segments),
|
|
466
|
-
unit='s',
|
|
467
|
-
ascii='░▒█',
|
|
468
|
-
bar_format=bar_format,
|
|
469
|
-
mininterval=0.05
|
|
470
|
-
)
|
|
471
|
-
|
|
472
|
-
try:
|
|
473
|
-
|
|
474
|
-
# Start writer thread
|
|
475
|
-
writer_thread = threading.Thread(target=self.write_segments_to_file)
|
|
476
|
-
writer_thread.daemon = True
|
|
477
|
-
writer_thread.start()
|
|
478
|
-
|
|
479
|
-
# Configure workers and delay
|
|
480
|
-
max_workers = len(self.valid_proxy) if THERE_IS_PROXY_LIST else TQDM_MAX_WORKER
|
|
481
|
-
delay = max(PROXY_START_MIN, min(PROXY_START_MAX, 1 / (len(self.valid_proxy) + 1))) if THERE_IS_PROXY_LIST else TQDM_DELAY_WORKER
|
|
482
|
-
|
|
483
|
-
# Download segments with completion verification
|
|
484
|
-
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
485
|
-
futures = []
|
|
486
|
-
for index, segment_url in enumerate(self.segments):
|
|
487
|
-
# Check for interrupt before submitting each task
|
|
488
|
-
if self.interrupt_flag.is_set():
|
|
489
|
-
break
|
|
490
|
-
|
|
491
|
-
time.sleep(delay)
|
|
492
|
-
futures.append(executor.submit(self.make_requests_stream, segment_url, index, progress_bar))
|
|
493
|
-
|
|
494
|
-
# Wait for futures with interrupt handling
|
|
495
|
-
for future in as_completed(futures):
|
|
496
|
-
if self.interrupt_flag.is_set():
|
|
497
|
-
break
|
|
498
|
-
try:
|
|
499
|
-
future.result()
|
|
500
|
-
except Exception as e:
|
|
501
|
-
logging.error(f"Error in download thread: {str(e)}")
|
|
502
|
-
|
|
503
|
-
# Interrupt handling for missing segments
|
|
504
|
-
if not self.interrupt_flag.is_set():
|
|
505
|
-
total_segments = len(self.segments)
|
|
506
|
-
completed_segments = len(self.downloaded_segments)
|
|
507
|
-
|
|
508
|
-
if completed_segments < total_segments:
|
|
509
|
-
missing_segments = set(range(total_segments)) - self.downloaded_segments
|
|
510
|
-
logging.warning(f"Missing segments: {sorted(missing_segments)}")
|
|
511
|
-
|
|
512
|
-
# Retry missing segments with interrupt check
|
|
513
|
-
for index in missing_segments:
|
|
514
|
-
if self.interrupt_flag.is_set():
|
|
515
|
-
break
|
|
516
|
-
|
|
517
|
-
try:
|
|
518
|
-
self.make_requests_stream(self.segments[index], index, progress_bar)
|
|
519
|
-
|
|
520
|
-
except Exception as e:
|
|
521
|
-
logging.error(f"Failed to retry segment {index}: {str(e)}")
|
|
522
|
-
|
|
523
|
-
except Exception as e:
|
|
524
|
-
logging.error(f"Download failed: {str(e)}")
|
|
525
|
-
raise
|
|
526
|
-
|
|
527
|
-
finally:
|
|
528
|
-
|
|
529
|
-
# Clean up resources
|
|
530
|
-
self.stop_event.set()
|
|
531
|
-
writer_thread.join(timeout=30)
|
|
532
|
-
progress_bar.close()
|
|
533
|
-
|
|
534
|
-
# Check if download was interrupted
|
|
535
|
-
if self.download_interrupted:
|
|
536
|
-
console.log("[red] Download was manually stopped.")
|
|
537
|
-
|
|
538
|
-
# Clean up
|
|
539
|
-
self.stop_event.set()
|
|
540
|
-
writer_thread.join(timeout=30)
|
|
541
|
-
progress_bar.close()
|
|
542
|
-
|
|
543
|
-
# Final verification
|
|
544
|
-
try:
|
|
545
|
-
final_completion = (len(self.downloaded_segments) / total_segments) * 100
|
|
546
|
-
if final_completion < 99.9: # Less than 99.9% complete
|
|
547
|
-
missing = set(range(total_segments)) - self.downloaded_segments
|
|
548
|
-
raise Exception(f"Download incomplete ({final_completion:.1f}%). Missing segments: {sorted(missing)}")
|
|
549
|
-
|
|
550
|
-
except:
|
|
551
|
-
pass
|
|
552
|
-
|
|
553
|
-
# Verify output file
|
|
554
|
-
if not os.path.exists(self.tmp_file_path):
|
|
555
|
-
raise Exception("Output file missing")
|
|
556
|
-
|
|
557
|
-
file_size = os.path.getsize(self.tmp_file_path)
|
|
558
|
-
if file_size == 0:
|
|
559
|
-
raise Exception("Output file is empty")
|
|
560
|
-
|
|
561
|
-
# Display additional info when there is missing stream file
|
|
562
|
-
if self.info_nFailed > 0:
|
|
563
|
-
|
|
564
|
-
# Get expected time
|
|
565
|
-
ex_hours, ex_minutes, ex_seconds = format_duration(self.expected_real_time_s)
|
|
566
|
-
ex_formatted_duration = f"[yellow]{int(ex_hours)}[red]h [yellow]{int(ex_minutes)}[red]m [yellow]{int(ex_seconds)}[red]s"
|
|
567
|
-
console.print(f"[cyan]Max retry per URL[white]: [green]{self.info_maxRetry}[green] [white]| [cyan]Total retry done[white]: [green]{self.info_nRetry}[green] [white]| [cyan]Missing TS: [red]{self.info_nFailed} [white]| [cyan]Duration: {print_duration_table(self.tmp_file_path, None, True)} [white]| [cyan]Expected duation: {ex_formatted_duration} \n")
|
|
568
|
-
|
|
569
|
-
if self.info_nRetry >= len(self.segments) * 0.3:
|
|
570
|
-
console.print("[yellow]⚠ Warning:[/yellow] Too many retries detected! Consider reducing the number of [cyan]workers[/cyan] in the [magenta]config.json[/magenta] file. This will impact [bold]performance[/bold]. \n")
|
|
571
|
-
|
|
572
|
-
# Info to return
|
|
573
|
-
return {'type': type, 'nFailed': self.info_nFailed}
|
|
1
|
+
# 18.04.24
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
import time
|
|
6
|
+
import queue
|
|
7
|
+
import signal
|
|
8
|
+
import logging
|
|
9
|
+
import binascii
|
|
10
|
+
import threading
|
|
11
|
+
from queue import PriorityQueue
|
|
12
|
+
from urllib.parse import urljoin, urlparse
|
|
13
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
14
|
+
from typing import Dict
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# External libraries
|
|
18
|
+
import httpx
|
|
19
|
+
from tqdm import tqdm
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# Internal utilities
|
|
23
|
+
from StreamingCommunity.Util.color import Colors
|
|
24
|
+
from StreamingCommunity.Util.console import console
|
|
25
|
+
from StreamingCommunity.Util.headers import get_headers, random_headers
|
|
26
|
+
from StreamingCommunity.Util._jsonConfig import config_manager
|
|
27
|
+
from StreamingCommunity.Util.os import os_manager
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# Logic class
|
|
31
|
+
from ...M3U8 import (
|
|
32
|
+
M3U8_Decryption,
|
|
33
|
+
M3U8_Ts_Estimator,
|
|
34
|
+
M3U8_Parser,
|
|
35
|
+
M3U8_UrlFix
|
|
36
|
+
)
|
|
37
|
+
from .proxyes import main_test_proxy
|
|
38
|
+
|
|
39
|
+
# Config
|
|
40
|
+
TQDM_DELAY_WORKER = config_manager.get_float('M3U8_DOWNLOAD', 'tqdm_delay')
|
|
41
|
+
USE_LARGE_BAR = not ("android" in sys.platform or "ios" in sys.platform)
|
|
42
|
+
REQUEST_MAX_RETRY = config_manager.get_int('REQUESTS', 'max_retry')
|
|
43
|
+
REQUEST_VERIFY = False
|
|
44
|
+
THERE_IS_PROXY_LIST = os_manager.check_file("list_proxy.txt")
|
|
45
|
+
PROXY_START_MIN = config_manager.get_float('REQUESTS', 'proxy_start_min')
|
|
46
|
+
PROXY_START_MAX = config_manager.get_float('REQUESTS', 'proxy_start_max')
|
|
47
|
+
DEFAULT_VIDEO_WORKERS = config_manager.get_int('M3U8_DOWNLOAD', 'default_video_workser')
|
|
48
|
+
DEFAULT_AUDIO_WORKERS = config_manager.get_int('M3U8_DOWNLOAD', 'default_audio_workser')
|
|
49
|
+
MAX_TIMEOOUT = config_manager.get_int("REQUESTS", "timeout")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class M3U8_Segments:
|
|
54
|
+
def __init__(self, url: str, tmp_folder: str, is_index_url: bool = True):
|
|
55
|
+
"""
|
|
56
|
+
Initializes the M3U8_Segments object.
|
|
57
|
+
|
|
58
|
+
Parameters:
|
|
59
|
+
- url (str): The URL of the M3U8 playlist.
|
|
60
|
+
- tmp_folder (str): The temporary folder to store downloaded segments.
|
|
61
|
+
- is_index_url (bool): Flag indicating if `m3u8_index` is a URL (default True).
|
|
62
|
+
"""
|
|
63
|
+
self.url = url
|
|
64
|
+
self.tmp_folder = tmp_folder
|
|
65
|
+
self.is_index_url = is_index_url
|
|
66
|
+
self.expected_real_time = None
|
|
67
|
+
self.tmp_file_path = os.path.join(self.tmp_folder, "0.ts")
|
|
68
|
+
os.makedirs(self.tmp_folder, exist_ok=True)
|
|
69
|
+
|
|
70
|
+
# Util class
|
|
71
|
+
self.decryption: M3U8_Decryption = None
|
|
72
|
+
self.class_ts_estimator = M3U8_Ts_Estimator(0, self)
|
|
73
|
+
self.class_url_fixer = M3U8_UrlFix(url)
|
|
74
|
+
|
|
75
|
+
# Sync
|
|
76
|
+
self.queue = PriorityQueue()
|
|
77
|
+
self.stop_event = threading.Event()
|
|
78
|
+
self.downloaded_segments = set()
|
|
79
|
+
self.base_timeout = 0.5
|
|
80
|
+
self.current_timeout = 3.0
|
|
81
|
+
|
|
82
|
+
# Stopping
|
|
83
|
+
self.interrupt_flag = threading.Event()
|
|
84
|
+
self.download_interrupted = False
|
|
85
|
+
|
|
86
|
+
# OTHER INFO
|
|
87
|
+
self.info_maxRetry = 0
|
|
88
|
+
self.info_nRetry = 0
|
|
89
|
+
self.info_nFailed = 0
|
|
90
|
+
|
|
91
|
+
self.active_retries = 0
|
|
92
|
+
self.active_retries_lock = threading.Lock()
|
|
93
|
+
|
|
94
|
+
def __get_key__(self, m3u8_parser: M3U8_Parser) -> bytes:
|
|
95
|
+
key_uri = urljoin(self.url, m3u8_parser.keys.get('uri'))
|
|
96
|
+
parsed_url = urlparse(key_uri)
|
|
97
|
+
self.key_base_url = f"{parsed_url.scheme}://{parsed_url.netloc}/"
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
client_params = {'headers': {'User-Agent': get_headers()}, 'timeout': MAX_TIMEOOUT}
|
|
101
|
+
response = httpx.get(url=key_uri, **client_params)
|
|
102
|
+
response.raise_for_status()
|
|
103
|
+
|
|
104
|
+
hex_content = binascii.hexlify(response.content).decode('utf-8')
|
|
105
|
+
return bytes.fromhex(hex_content)
|
|
106
|
+
|
|
107
|
+
except Exception as e:
|
|
108
|
+
raise Exception(f"Failed to fetch key: {e}")
|
|
109
|
+
|
|
110
|
+
def parse_data(self, m3u8_content: str) -> None:
|
|
111
|
+
m3u8_parser = M3U8_Parser()
|
|
112
|
+
m3u8_parser.parse_data(uri=self.url, raw_content=m3u8_content)
|
|
113
|
+
|
|
114
|
+
self.expected_real_time_s = m3u8_parser.duration
|
|
115
|
+
|
|
116
|
+
if m3u8_parser.keys:
|
|
117
|
+
key = self.__get_key__(m3u8_parser)
|
|
118
|
+
self.decryption = M3U8_Decryption(
|
|
119
|
+
key,
|
|
120
|
+
m3u8_parser.keys.get('iv'),
|
|
121
|
+
m3u8_parser.keys.get('method')
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
self.segments = [
|
|
125
|
+
self.class_url_fixer.generate_full_url(seg)
|
|
126
|
+
if "http" not in seg else seg
|
|
127
|
+
for seg in m3u8_parser.segments
|
|
128
|
+
]
|
|
129
|
+
self.class_ts_estimator.total_segments = len(self.segments)
|
|
130
|
+
|
|
131
|
+
# Proxy
|
|
132
|
+
if THERE_IS_PROXY_LIST:
|
|
133
|
+
console.log("[red]Start validation proxy.")
|
|
134
|
+
self.valid_proxy = main_test_proxy(self.segments[0])
|
|
135
|
+
console.log(f"[cyan]N. Valid ip: [red]{len(self.valid_proxy)}")
|
|
136
|
+
|
|
137
|
+
if len(self.valid_proxy) == 0:
|
|
138
|
+
sys.exit(0)
|
|
139
|
+
|
|
140
|
+
def get_info(self) -> None:
|
|
141
|
+
if self.is_index_url:
|
|
142
|
+
try:
|
|
143
|
+
client_params = {'headers': {'User-Agent': get_headers()}, 'timeout': MAX_TIMEOOUT}
|
|
144
|
+
response = httpx.get(self.url, **client_params)
|
|
145
|
+
response.raise_for_status()
|
|
146
|
+
|
|
147
|
+
self.parse_data(response.text)
|
|
148
|
+
with open(os.path.join(self.tmp_folder, "playlist.m3u8"), "w") as f:
|
|
149
|
+
f.write(response.text)
|
|
150
|
+
|
|
151
|
+
except Exception as e:
|
|
152
|
+
raise RuntimeError(f"M3U8 info retrieval failed: {e}")
|
|
153
|
+
|
|
154
|
+
def setup_interrupt_handler(self):
|
|
155
|
+
"""
|
|
156
|
+
Set up a signal handler for graceful interruption.
|
|
157
|
+
"""
|
|
158
|
+
def interrupt_handler(signum, frame):
|
|
159
|
+
if not self.interrupt_flag.is_set():
|
|
160
|
+
console.log("\n[red] Stopping download gracefully...")
|
|
161
|
+
self.interrupt_flag.set()
|
|
162
|
+
self.download_interrupted = True
|
|
163
|
+
self.stop_event.set()
|
|
164
|
+
|
|
165
|
+
if threading.current_thread() is threading.main_thread():
|
|
166
|
+
signal.signal(signal.SIGINT, interrupt_handler)
|
|
167
|
+
else:
|
|
168
|
+
print("Signal handler must be set in the main thread")
|
|
169
|
+
|
|
170
|
+
def _get_http_client(self, index: int = None):
|
|
171
|
+
client_params = {
|
|
172
|
+
'headers': random_headers(self.key_base_url) if hasattr(self, 'key_base_url') else {'User-Agent': get_headers()},
|
|
173
|
+
'timeout': MAX_TIMEOOUT,
|
|
174
|
+
'follow_redirects': True,
|
|
175
|
+
'http2': False
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if THERE_IS_PROXY_LIST and index is not None and hasattr(self, 'valid_proxy'):
|
|
179
|
+
client_params['proxies'] = self.valid_proxy[index % len(self.valid_proxy)]
|
|
180
|
+
|
|
181
|
+
return httpx.Client(**client_params)
|
|
182
|
+
|
|
183
|
+
def download_segment(self, ts_url: str, index: int, progress_bar: tqdm, backoff_factor: float = 1.1) -> None:
|
|
184
|
+
"""
|
|
185
|
+
Downloads a TS segment and adds it to the segment queue with retry logic.
|
|
186
|
+
|
|
187
|
+
Parameters:
|
|
188
|
+
- ts_url (str): The URL of the TS segment.
|
|
189
|
+
- index (int): The index of the segment.
|
|
190
|
+
- progress_bar (tqdm): Progress counter for tracking download progress.
|
|
191
|
+
- backoff_factor (float): The backoff factor for exponential backoff (default is 1.5 seconds).
|
|
192
|
+
"""
|
|
193
|
+
for attempt in range(REQUEST_MAX_RETRY):
|
|
194
|
+
if self.interrupt_flag.is_set():
|
|
195
|
+
return
|
|
196
|
+
|
|
197
|
+
try:
|
|
198
|
+
with self._get_http_client(index) as client:
|
|
199
|
+
start_time = time.time()
|
|
200
|
+
response = client.get(ts_url)
|
|
201
|
+
|
|
202
|
+
# Validate response and content
|
|
203
|
+
response.raise_for_status()
|
|
204
|
+
segment_content = response.content
|
|
205
|
+
content_size = len(segment_content)
|
|
206
|
+
duration = time.time() - start_time
|
|
207
|
+
|
|
208
|
+
# Decrypt if needed and verify decrypted content
|
|
209
|
+
if self.decryption is not None:
|
|
210
|
+
try:
|
|
211
|
+
segment_content = self.decryption.decrypt(segment_content)
|
|
212
|
+
|
|
213
|
+
except Exception as e:
|
|
214
|
+
logging.error(f"Decryption failed for segment {index}: {str(e)}")
|
|
215
|
+
self.interrupt_flag.set() # Interrupt the download process
|
|
216
|
+
self.stop_event.set() # Trigger the stopping event for all threads
|
|
217
|
+
break # Stop the current task immediately
|
|
218
|
+
|
|
219
|
+
self.class_ts_estimator.update_progress_bar(content_size, duration, progress_bar)
|
|
220
|
+
self.queue.put((index, segment_content))
|
|
221
|
+
self.downloaded_segments.add(index)
|
|
222
|
+
progress_bar.update(1)
|
|
223
|
+
return
|
|
224
|
+
|
|
225
|
+
except Exception as e:
|
|
226
|
+
logging.info(f"Attempt {attempt + 1} failed for segment {index} - '{ts_url}': {e}")
|
|
227
|
+
|
|
228
|
+
if attempt > self.info_maxRetry:
|
|
229
|
+
self.info_maxRetry = ( attempt + 1 )
|
|
230
|
+
self.info_nRetry += 1
|
|
231
|
+
|
|
232
|
+
if attempt + 1 == REQUEST_MAX_RETRY:
|
|
233
|
+
console.log(f"[red]Final retry failed for segment: {index}")
|
|
234
|
+
self.queue.put((index, None)) # Marker for failed segment
|
|
235
|
+
progress_bar.update(1)
|
|
236
|
+
self.info_nFailed += 1
|
|
237
|
+
return
|
|
238
|
+
|
|
239
|
+
with self.active_retries_lock:
|
|
240
|
+
self.active_retries += 1
|
|
241
|
+
|
|
242
|
+
sleep_time = backoff_factor * (2 ** attempt)
|
|
243
|
+
logging.info(f"Retrying segment {index} in {sleep_time} seconds...")
|
|
244
|
+
time.sleep(sleep_time)
|
|
245
|
+
|
|
246
|
+
with self.active_retries_lock:
|
|
247
|
+
self.active_retries -= 1
|
|
248
|
+
|
|
249
|
+
def write_segments_to_file(self):
|
|
250
|
+
"""
|
|
251
|
+
Writes segments to file with additional verification.
|
|
252
|
+
"""
|
|
253
|
+
buffer = {}
|
|
254
|
+
expected_index = 0
|
|
255
|
+
|
|
256
|
+
with open(self.tmp_file_path, 'wb') as f:
|
|
257
|
+
while not self.stop_event.is_set() or not self.queue.empty():
|
|
258
|
+
if self.interrupt_flag.is_set():
|
|
259
|
+
break
|
|
260
|
+
|
|
261
|
+
try:
|
|
262
|
+
index, segment_content = self.queue.get(timeout=self.current_timeout)
|
|
263
|
+
|
|
264
|
+
# Successful queue retrieval: reduce timeout
|
|
265
|
+
self.current_timeout = max(self.base_timeout, self.current_timeout / 2)
|
|
266
|
+
|
|
267
|
+
# Handle failed segments
|
|
268
|
+
if segment_content is None:
|
|
269
|
+
if index == expected_index:
|
|
270
|
+
expected_index += 1
|
|
271
|
+
continue
|
|
272
|
+
|
|
273
|
+
# Write segment if it's the next expected one
|
|
274
|
+
if index == expected_index:
|
|
275
|
+
f.write(segment_content)
|
|
276
|
+
f.flush()
|
|
277
|
+
expected_index += 1
|
|
278
|
+
|
|
279
|
+
# Write any buffered segments that are now in order
|
|
280
|
+
while expected_index in buffer:
|
|
281
|
+
next_segment = buffer.pop(expected_index)
|
|
282
|
+
|
|
283
|
+
if next_segment is not None:
|
|
284
|
+
f.write(next_segment)
|
|
285
|
+
f.flush()
|
|
286
|
+
|
|
287
|
+
expected_index += 1
|
|
288
|
+
|
|
289
|
+
else:
|
|
290
|
+
buffer[index] = segment_content
|
|
291
|
+
|
|
292
|
+
except queue.Empty:
|
|
293
|
+
self.current_timeout = min(MAX_TIMEOOUT, self.current_timeout * 1.1)
|
|
294
|
+
if self.stop_event.is_set():
|
|
295
|
+
break
|
|
296
|
+
|
|
297
|
+
except Exception as e:
|
|
298
|
+
logging.error(f"Error writing segment {index}: {str(e)}")
|
|
299
|
+
|
|
300
|
+
def download_streams(self, description: str, type: str):
|
|
301
|
+
"""
|
|
302
|
+
Downloads all TS segments in parallel and writes them to a file.
|
|
303
|
+
|
|
304
|
+
Parameters:
|
|
305
|
+
- description: Description to insert on tqdm bar
|
|
306
|
+
- type (str): Type of download: 'video' or 'audio'
|
|
307
|
+
"""
|
|
308
|
+
self.get_info()
|
|
309
|
+
self.setup_interrupt_handler()
|
|
310
|
+
|
|
311
|
+
progress_bar = tqdm(
|
|
312
|
+
total=len(self.segments),
|
|
313
|
+
unit='s',
|
|
314
|
+
ascii='░▒█',
|
|
315
|
+
bar_format=self._get_bar_format(description),
|
|
316
|
+
mininterval=0.05
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
try:
|
|
320
|
+
writer_thread = threading.Thread(target=self.write_segments_to_file)
|
|
321
|
+
writer_thread.daemon = True
|
|
322
|
+
writer_thread.start()
|
|
323
|
+
|
|
324
|
+
# Configure workers and delay
|
|
325
|
+
max_workers = self._get_worker_count(type)
|
|
326
|
+
delay = max(PROXY_START_MIN, min(PROXY_START_MAX, 1 / (len(self.valid_proxy) + 1))) if THERE_IS_PROXY_LIST else TQDM_DELAY_WORKER
|
|
327
|
+
|
|
328
|
+
# Download segments with completion verification
|
|
329
|
+
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
330
|
+
futures = []
|
|
331
|
+
for index, segment_url in enumerate(self.segments):
|
|
332
|
+
|
|
333
|
+
# Check for interrupt before submitting each task
|
|
334
|
+
if self.interrupt_flag.is_set():
|
|
335
|
+
break
|
|
336
|
+
|
|
337
|
+
time.sleep(delay)
|
|
338
|
+
futures.append(executor.submit(self.download_segment, segment_url, index, progress_bar))
|
|
339
|
+
|
|
340
|
+
# Wait for futures with interrupt handling
|
|
341
|
+
for future in as_completed(futures):
|
|
342
|
+
if self.interrupt_flag.is_set():
|
|
343
|
+
break
|
|
344
|
+
try:
|
|
345
|
+
future.result()
|
|
346
|
+
except Exception as e:
|
|
347
|
+
logging.error(f"Error in download thread: {str(e)}")
|
|
348
|
+
|
|
349
|
+
# Interrupt handling for missing segments
|
|
350
|
+
if not self.interrupt_flag.is_set():
|
|
351
|
+
total_segments = len(self.segments)
|
|
352
|
+
completed_segments = len(self.downloaded_segments)
|
|
353
|
+
|
|
354
|
+
if completed_segments < total_segments:
|
|
355
|
+
missing_segments = set(range(total_segments)) - self.downloaded_segments
|
|
356
|
+
logging.warning(f"Missing segments: {sorted(missing_segments)}")
|
|
357
|
+
|
|
358
|
+
# Retry missing segments with interrupt check
|
|
359
|
+
for index in missing_segments:
|
|
360
|
+
if self.interrupt_flag.is_set():
|
|
361
|
+
break
|
|
362
|
+
|
|
363
|
+
try:
|
|
364
|
+
self.download_segment(self.segments[index], index, progress_bar)
|
|
365
|
+
|
|
366
|
+
except Exception as e:
|
|
367
|
+
logging.error(f"Failed to retry segment {index}: {str(e)}")
|
|
368
|
+
|
|
369
|
+
finally:
|
|
370
|
+
self._cleanup_resources(writer_thread, progress_bar)
|
|
371
|
+
|
|
372
|
+
if not self.interrupt_flag.is_set():
|
|
373
|
+
self._verify_download_completion()
|
|
374
|
+
|
|
375
|
+
return self._generate_results(type)
|
|
376
|
+
|
|
377
|
+
def _get_bar_format(self, description: str) -> str:
|
|
378
|
+
"""
|
|
379
|
+
Generate platform-appropriate progress bar format.
|
|
380
|
+
"""
|
|
381
|
+
if not USE_LARGE_BAR:
|
|
382
|
+
return (
|
|
383
|
+
f"{Colors.YELLOW}Proc{Colors.WHITE}: "
|
|
384
|
+
f"{Colors.RED}{{percentage:.2f}}% "
|
|
385
|
+
f"{Colors.WHITE}| "
|
|
386
|
+
f"{Colors.CYAN}{{remaining}}{{postfix}} {Colors.WHITE}]"
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
else:
|
|
390
|
+
return (
|
|
391
|
+
f"{Colors.YELLOW}[HLS] {Colors.WHITE}({Colors.CYAN}{description}{Colors.WHITE}): "
|
|
392
|
+
f"{Colors.RED}{{percentage:.2f}}% "
|
|
393
|
+
f"{Colors.MAGENTA}{{bar}} "
|
|
394
|
+
f"{Colors.WHITE}[ {Colors.YELLOW}{{n_fmt}}{Colors.WHITE} / {Colors.RED}{{total_fmt}} {Colors.WHITE}] "
|
|
395
|
+
f"{Colors.YELLOW}{{elapsed}} {Colors.WHITE}< {Colors.CYAN}{{remaining}}{{postfix}} {Colors.WHITE}]"
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
def _get_worker_count(self, stream_type: str) -> int:
|
|
399
|
+
"""
|
|
400
|
+
Calculate optimal parallel workers based on stream type and infrastructure.
|
|
401
|
+
"""
|
|
402
|
+
base_workers = {
|
|
403
|
+
'video': DEFAULT_VIDEO_WORKERS,
|
|
404
|
+
'audio': DEFAULT_AUDIO_WORKERS
|
|
405
|
+
}.get(stream_type.lower(), 1)
|
|
406
|
+
|
|
407
|
+
if THERE_IS_PROXY_LIST:
|
|
408
|
+
return min(len(self.valid_proxy), base_workers * 2)
|
|
409
|
+
return base_workers
|
|
410
|
+
|
|
411
|
+
def _generate_results(self, stream_type: str) -> Dict:
|
|
412
|
+
"""Package final download results."""
|
|
413
|
+
return {
|
|
414
|
+
'type': stream_type,
|
|
415
|
+
'nFailed': self.info_nFailed,
|
|
416
|
+
'stopped': self.download_interrupted
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
def _verify_download_completion(self) -> None:
|
|
420
|
+
"""Validate final download integrity."""
|
|
421
|
+
total = len(self.segments)
|
|
422
|
+
if len(self.downloaded_segments) / total < 0.999:
|
|
423
|
+
missing = sorted(set(range(total)) - self.downloaded_segments)
|
|
424
|
+
raise RuntimeError(f"Download incomplete ({len(self.downloaded_segments)/total:.1%}). Missing segments: {missing}")
|
|
425
|
+
|
|
426
|
+
def _cleanup_resources(self, writer_thread: threading.Thread, progress_bar: tqdm) -> None:
|
|
427
|
+
"""Ensure resource cleanup and final reporting."""
|
|
428
|
+
self.stop_event.set()
|
|
429
|
+
writer_thread.join(timeout=30)
|
|
430
|
+
progress_bar.close()
|
|
431
|
+
|
|
432
|
+
if self.download_interrupted:
|
|
433
|
+
console.print("\n[red]Download terminated by user")
|
|
434
|
+
|
|
435
|
+
if self.info_nFailed > 0:
|
|
436
|
+
self._display_error_summary()
|
|
437
|
+
|
|
438
|
+
def _display_error_summary(self) -> None:
|
|
439
|
+
"""Generate final error report."""
|
|
440
|
+
console.print(f"\n[cyan]Retry Summary: "
|
|
441
|
+
f"[white]Max retries: [green]{self.info_maxRetry} "
|
|
442
|
+
f"[white]Total retries: [green]{self.info_nRetry} "
|
|
443
|
+
f"[white]Failed segments: [red]{self.info_nFailed}")
|
|
444
|
+
|
|
445
|
+
if self.info_nRetry > len(self.segments) * 0.3:
|
|
446
|
+
console.print("[yellow]Warning: High retry count detected. Consider reducing worker count in config.")
|