StreamingCommunity 3.2.7__tar.gz → 3.2.8__tar.gz

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.

Files changed (119) hide show
  1. {streamingcommunity-3.2.7/StreamingCommunity.egg-info → streamingcommunity-3.2.8}/PKG-INFO +1 -1
  2. streamingcommunity-3.2.8/StreamingCommunity/Lib/Downloader/HLS/segments.py +464 -0
  3. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Upload/version.py +1 -1
  4. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8/StreamingCommunity.egg-info}/PKG-INFO +1 -1
  5. streamingcommunity-3.2.7/StreamingCommunity/Lib/Downloader/HLS/segments.py +0 -350
  6. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/LICENSE +0 -0
  7. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/MANIFEST.in +0 -0
  8. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/README.md +0 -0
  9. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Player/Helper/Vixcloud/js_parser.py +0 -0
  10. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Player/Helper/Vixcloud/util.py +0 -0
  11. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Player/ddl.py +0 -0
  12. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Player/hdplayer.py +0 -0
  13. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Player/maxstream.py +0 -0
  14. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Player/mediapolisvod.py +0 -0
  15. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Player/mixdrop.py +0 -0
  16. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Player/supervideo.py +0 -0
  17. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Player/sweetpixel.py +0 -0
  18. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Player/vixcloud.py +0 -0
  19. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Site/altadefinizione/__init__.py +0 -0
  20. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Site/altadefinizione/film.py +0 -0
  21. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Site/altadefinizione/series.py +0 -0
  22. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Site/altadefinizione/site.py +0 -0
  23. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Site/altadefinizione/util/ScrapeSerie.py +0 -0
  24. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Site/animeunity/__init__.py +0 -0
  25. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Site/animeunity/film.py +0 -0
  26. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Site/animeunity/serie.py +0 -0
  27. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Site/animeunity/site.py +0 -0
  28. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Site/animeunity/util/ScrapeSerie.py +0 -0
  29. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Site/animeworld/__init__.py +0 -0
  30. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Site/animeworld/film.py +0 -0
  31. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Site/animeworld/serie.py +0 -0
  32. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Site/animeworld/site.py +0 -0
  33. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Site/animeworld/util/ScrapeSerie.py +0 -0
  34. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Site/cb01new/__init__.py +0 -0
  35. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Site/cb01new/film.py +0 -0
  36. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Site/cb01new/site.py +0 -0
  37. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Site/crunchyroll/__init__.py +0 -0
  38. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Site/crunchyroll/film.py +0 -0
  39. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Site/crunchyroll/series.py +0 -0
  40. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Site/crunchyroll/site.py +0 -0
  41. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Site/crunchyroll/util/ScrapeSerie.py +0 -0
  42. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Site/crunchyroll/util/get_license.py +0 -0
  43. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Site/guardaserie/__init__.py +0 -0
  44. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Site/guardaserie/series.py +0 -0
  45. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Site/guardaserie/site.py +0 -0
  46. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Site/guardaserie/util/ScrapeSerie.py +0 -0
  47. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Site/mediasetinfinity/__init__.py +0 -0
  48. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Site/mediasetinfinity/film.py +0 -0
  49. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Site/mediasetinfinity/series.py +0 -0
  50. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Site/mediasetinfinity/site.py +0 -0
  51. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Site/mediasetinfinity/util/ScrapeSerie.py +0 -0
  52. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Site/mediasetinfinity/util/fix_mpd.py +0 -0
  53. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Site/mediasetinfinity/util/get_license.py +0 -0
  54. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Site/raiplay/__init__.py +0 -0
  55. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Site/raiplay/film.py +0 -0
  56. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Site/raiplay/series.py +0 -0
  57. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Site/raiplay/site.py +0 -0
  58. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Site/raiplay/util/ScrapeSerie.py +0 -0
  59. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Site/streamingcommunity/__init__.py +0 -0
  60. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Site/streamingcommunity/film.py +0 -0
  61. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Site/streamingcommunity/series.py +0 -0
  62. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Site/streamingcommunity/site.py +0 -0
  63. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Site/streamingcommunity/util/ScrapeSerie.py +0 -0
  64. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Site/streamingwatch/__init__.py +0 -0
  65. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Site/streamingwatch/film.py +0 -0
  66. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Site/streamingwatch/series.py +0 -0
  67. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Site/streamingwatch/site.py +0 -0
  68. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Site/streamingwatch/util/ScrapeSerie.py +0 -0
  69. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Template/Class/SearchType.py +0 -0
  70. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Template/Util/__init__.py +0 -0
  71. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Template/Util/manage_ep.py +0 -0
  72. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Template/__init__.py +0 -0
  73. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Template/config_loader.py +0 -0
  74. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Api/Template/site.py +0 -0
  75. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Lib/Downloader/DASH/cdm_helpher.py +0 -0
  76. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Lib/Downloader/DASH/decrypt.py +0 -0
  77. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Lib/Downloader/DASH/downloader.py +0 -0
  78. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Lib/Downloader/DASH/parser.py +0 -0
  79. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Lib/Downloader/DASH/segments.py +0 -0
  80. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Lib/Downloader/HLS/downloader.py +0 -0
  81. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Lib/Downloader/MP4/downloader.py +0 -0
  82. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Lib/Downloader/TOR/downloader.py +0 -0
  83. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Lib/Downloader/__init__.py +0 -0
  84. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Lib/FFmpeg/__init__.py +0 -0
  85. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Lib/FFmpeg/capture.py +0 -0
  86. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Lib/FFmpeg/command.py +0 -0
  87. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Lib/FFmpeg/util.py +0 -0
  88. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Lib/M3U8/__init__.py +0 -0
  89. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Lib/M3U8/decryptor.py +0 -0
  90. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Lib/M3U8/estimator.py +0 -0
  91. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Lib/M3U8/parser.py +0 -0
  92. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Lib/M3U8/url_fixer.py +0 -0
  93. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Lib/TMBD/__init__.py +0 -0
  94. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Lib/TMBD/obj_tmbd.py +0 -0
  95. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Lib/TMBD/tmdb.py +0 -0
  96. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/TelegramHelp/__init__.py +0 -0
  97. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/TelegramHelp/config.json +0 -0
  98. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/TelegramHelp/telegram_bot.py +0 -0
  99. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Upload/update.py +0 -0
  100. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Util/bento4_installer.py +0 -0
  101. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Util/color.py +0 -0
  102. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Util/config_json.py +0 -0
  103. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Util/ffmpeg_installer.py +0 -0
  104. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Util/headers.py +0 -0
  105. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Util/logger.py +0 -0
  106. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Util/message.py +0 -0
  107. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Util/os.py +0 -0
  108. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/Util/table.py +0 -0
  109. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/__init__.py +0 -0
  110. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/global_search.py +0 -0
  111. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity/run.py +0 -0
  112. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity.egg-info/SOURCES.txt +0 -0
  113. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity.egg-info/dependency_links.txt +0 -0
  114. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity.egg-info/entry_points.txt +0 -0
  115. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity.egg-info/requires.txt +0 -0
  116. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/StreamingCommunity.egg-info/top_level.txt +0 -0
  117. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/requirements.txt +0 -0
  118. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/setup.cfg +0 -0
  119. {streamingcommunity-3.2.7 → streamingcommunity-3.2.8}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: StreamingCommunity
3
- Version: 3.2.7
3
+ Version: 3.2.8
4
4
  Home-page: https://github.com/Lovi-0/StreamingCommunity
5
5
  Author: Lovi-0
6
6
  Project-URL: Bug Reports, https://github.com/Lovi-0/StreamingCommunity/issues
@@ -0,0 +1,464 @@
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
+ from rich.console import Console
21
+
22
+
23
+ # Internal utilities
24
+ from StreamingCommunity.Util.color import Colors
25
+ from StreamingCommunity.Util.headers import get_userAgent
26
+ from StreamingCommunity.Util.config_json import config_manager
27
+
28
+
29
+ # Logic class
30
+ from ...M3U8 import (
31
+ M3U8_Decryption,
32
+ M3U8_Ts_Estimator,
33
+ M3U8_Parser,
34
+ M3U8_UrlFix
35
+ )
36
+
37
+ # Config
38
+ TQDM_DELAY_WORKER = 0.01
39
+ REQUEST_MAX_RETRY = config_manager.get_int('REQUESTS', 'max_retry')
40
+ REQUEST_VERIFY = config_manager.get_bool('REQUESTS', 'verify')
41
+ DEFAULT_VIDEO_WORKERS = config_manager.get_int('M3U8_DOWNLOAD', 'default_video_workers')
42
+ DEFAULT_AUDIO_WORKERS = config_manager.get_int('M3U8_DOWNLOAD', 'default_audio_workers')
43
+ MAX_TIMEOOUT = config_manager.get_int("REQUESTS", "timeout")
44
+ SEGMENT_MAX_TIMEOUT = config_manager.get_int("M3U8_DOWNLOAD", "segment_timeout")
45
+ TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot')
46
+ MAX_INTERRUPT_COUNT = 3
47
+
48
+ # Variable
49
+ console = Console()
50
+
51
+
52
+ class M3U8_Segments:
53
+ def __init__(self, url: str, tmp_folder: str, is_index_url: bool = True):
54
+ """
55
+ Initializes the M3U8_Segments object.
56
+
57
+ Parameters:
58
+ - url (str): The URL of the M3U8 playlist.
59
+ - tmp_folder (str): The temporary folder to store downloaded segments.
60
+ - is_index_url (bool): Flag indicating if `m3u8_index` is a URL (default True).
61
+ """
62
+ self.url = url
63
+ self.tmp_folder = tmp_folder
64
+ self.is_index_url = is_index_url
65
+ self.expected_real_time = None
66
+ self.tmp_file_path = os.path.join(self.tmp_folder, "0.ts")
67
+ os.makedirs(self.tmp_folder, exist_ok=True)
68
+
69
+ # Util class
70
+ self.decryption: M3U8_Decryption = None
71
+ self.class_ts_estimator = M3U8_Ts_Estimator(0, self)
72
+ self.class_url_fixer = M3U8_UrlFix(url)
73
+
74
+ # Sync
75
+ self.queue = PriorityQueue()
76
+ self.buffer = {}
77
+ self.expected_index = 0
78
+
79
+ self.stop_event = threading.Event()
80
+ self.downloaded_segments = set()
81
+ self.base_timeout = 0.5
82
+ self.current_timeout = 3.0
83
+
84
+ # Stopping
85
+ self.interrupt_flag = threading.Event()
86
+ self.download_interrupted = False
87
+ self.interrupt_count = 0
88
+ self.force_stop = False
89
+ self.interrupt_lock = threading.Lock()
90
+
91
+ # OTHER INFO
92
+ self.info_maxRetry = 0
93
+ self.info_nRetry = 0
94
+ self.info_nFailed = 0
95
+ self.active_retries = 0
96
+ self.active_retries_lock = threading.Lock()
97
+
98
+ def __get_key__(self, m3u8_parser: M3U8_Parser) -> bytes:
99
+ """
100
+ Fetches the encryption key from the M3U8 playlist.
101
+
102
+ Args:
103
+ m3u8_parser (M3U8_Parser): An instance of M3U8_Parser containing parsed M3U8 data.
104
+
105
+ Returns:
106
+ bytes: The decryption key in byte format.
107
+ """
108
+ key_uri = urljoin(self.url, m3u8_parser.keys.get('uri'))
109
+ parsed_url = urlparse(key_uri)
110
+ self.key_base_url = f"{parsed_url.scheme}://{parsed_url.netloc}/"
111
+
112
+ try:
113
+ client_params = {'headers': {'User-Agent': get_userAgent()}, 'timeout': MAX_TIMEOOUT, 'verify': REQUEST_VERIFY}
114
+ response = httpx.get(url=key_uri, **client_params)
115
+ response.raise_for_status()
116
+
117
+ hex_content = binascii.hexlify(response.content).decode('utf-8')
118
+ return bytes.fromhex(hex_content)
119
+
120
+ except Exception as e:
121
+ raise Exception(f"Failed to fetch key: {e}")
122
+
123
+ def parse_data(self, m3u8_content: str) -> None:
124
+ """
125
+ Parses the M3U8 content and extracts necessary data.
126
+
127
+ Args:
128
+ m3u8_content (str): The raw M3U8 playlist content.
129
+ """
130
+ m3u8_parser = M3U8_Parser()
131
+ m3u8_parser.parse_data(uri=self.url, raw_content=m3u8_content)
132
+
133
+ self.expected_real_time_s = m3u8_parser.duration
134
+
135
+ if m3u8_parser.keys:
136
+ key = self.__get_key__(m3u8_parser)
137
+ self.decryption = M3U8_Decryption(
138
+ key,
139
+ m3u8_parser.keys.get('iv'),
140
+ m3u8_parser.keys.get('method')
141
+ )
142
+
143
+ self.segments = [
144
+ self.class_url_fixer.generate_full_url(seg)
145
+ if "http" not in seg else seg
146
+ for seg in m3u8_parser.segments
147
+ ]
148
+ self.class_ts_estimator.total_segments = len(self.segments)
149
+
150
+ def get_info(self) -> None:
151
+ """
152
+ Retrieves M3U8 playlist information from the given URL.
153
+
154
+ If the URL is an index URL, this method:
155
+ - Sends an HTTP GET request to fetch the M3U8 playlist.
156
+ - Parses the M3U8 content using `parse_data`.
157
+ - Saves the playlist to a temporary folder.
158
+ """
159
+ if self.is_index_url:
160
+ try:
161
+ client_params = {'headers': {'User-Agent': get_userAgent()}, 'timeout': MAX_TIMEOOUT, 'verify': REQUEST_VERIFY}
162
+ response = httpx.get(self.url, **client_params, follow_redirects=True)
163
+ response.raise_for_status()
164
+
165
+ self.parse_data(response.text)
166
+ with open(os.path.join(self.tmp_folder, "playlist.m3u8"), "w") as f:
167
+ f.write(response.text)
168
+
169
+ except Exception as e:
170
+ raise RuntimeError(f"M3U8 info retrieval failed: {e}")
171
+
172
+ def setup_interrupt_handler(self):
173
+ """
174
+ Set up a signal handler for graceful interruption.
175
+ """
176
+ def interrupt_handler(signum, frame):
177
+ with self.interrupt_lock:
178
+ self.interrupt_count += 1
179
+ if self.interrupt_count >= MAX_INTERRUPT_COUNT:
180
+ self.force_stop = True
181
+
182
+ if self.force_stop:
183
+ console.print("\n[red]Force stop triggered! Exiting immediately.")
184
+
185
+ else:
186
+ if not self.interrupt_flag.is_set():
187
+ remaining = MAX_INTERRUPT_COUNT - self.interrupt_count
188
+ console.print(f"\n[red]- Stopping gracefully... (Ctrl+C {remaining}x to force)")
189
+ self.download_interrupted = True
190
+
191
+ if remaining == 1:
192
+ self.interrupt_flag.set()
193
+
194
+
195
+ if threading.current_thread() is threading.main_thread():
196
+ signal.signal(signal.SIGINT, interrupt_handler)
197
+ else:
198
+ print("Signal handler must be set in the main thread")
199
+
200
+ def _get_http_client(self):
201
+ client_params = {
202
+ 'headers': {'User-Agent': get_userAgent()},
203
+ 'timeout': SEGMENT_MAX_TIMEOUT,
204
+ 'follow_redirects': True,
205
+ 'http2': False,
206
+ 'verify': REQUEST_VERIFY
207
+ }
208
+ return httpx.Client(**client_params)
209
+
210
+ def download_segment(self, ts_url: str, index: int, progress_bar: tqdm, backoff_factor: float = 1.1) -> None:
211
+ """
212
+ Downloads a TS segment and adds it to the segment queue with retry logic.
213
+
214
+ Parameters:
215
+ - ts_url (str): The URL of the TS segment.
216
+ - index (int): The index of the segment.
217
+ - progress_bar (tqdm): Progress counter for tracking download progress.
218
+ - backoff_factor (float): The backoff factor for exponential backoff (default is 1.5 seconds).
219
+ """
220
+ for attempt in range(REQUEST_MAX_RETRY):
221
+ if self.interrupt_flag.is_set():
222
+ return
223
+
224
+ try:
225
+ with self._get_http_client() as client:
226
+ response = client.get(ts_url)
227
+
228
+ # Validate response and content
229
+ response.raise_for_status()
230
+ segment_content = response.content
231
+ content_size = len(segment_content)
232
+
233
+ # Decrypt if needed and verify decrypted content
234
+ if self.decryption is not None:
235
+ try:
236
+ segment_content = self.decryption.decrypt(segment_content)
237
+
238
+ except Exception as e:
239
+ logging.error(f"Decryption failed for segment {index}: {str(e)}")
240
+ self.interrupt_flag.set() # Interrupt the download process
241
+ self.stop_event.set() # Trigger the stopping event for all threads
242
+ break # Stop the current task immediately
243
+
244
+ self.class_ts_estimator.update_progress_bar(content_size, progress_bar)
245
+ self.queue.put((index, segment_content))
246
+ self.downloaded_segments.add(index)
247
+ progress_bar.update(1)
248
+ return
249
+
250
+ except Exception as e:
251
+ logging.info(f"Attempt {attempt + 1} failed for segment {index} - '{ts_url}': {e}")
252
+
253
+ if attempt > self.info_maxRetry:
254
+ self.info_maxRetry = ( attempt + 1 )
255
+ self.info_nRetry += 1
256
+
257
+ if attempt + 1 == REQUEST_MAX_RETRY:
258
+ console.log(f"[red]Final retry failed for segment: {index}")
259
+ self.queue.put((index, None)) # Marker for failed segment
260
+ progress_bar.update(1)
261
+ self.info_nFailed += 1
262
+ return
263
+
264
+ with self.active_retries_lock:
265
+ self.active_retries += 1
266
+
267
+ sleep_time = backoff_factor * (2 ** attempt)
268
+ logging.info(f"Retrying segment {index} in {sleep_time} seconds...")
269
+ time.sleep(sleep_time)
270
+
271
+ with self.active_retries_lock:
272
+ self.active_retries -= 1
273
+
274
+ def write_segments_to_file(self):
275
+ """
276
+ Writes segments to file with additional verification.
277
+ """
278
+ with open(self.tmp_file_path, 'wb') as f:
279
+ while not self.stop_event.is_set() or not self.queue.empty():
280
+ if self.interrupt_flag.is_set():
281
+ break
282
+
283
+ try:
284
+ index, segment_content = self.queue.get(timeout=self.current_timeout)
285
+
286
+ # Successful queue retrieval: reduce timeout
287
+ self.current_timeout = max(self.base_timeout, self.current_timeout / 2)
288
+
289
+ # Handle failed segments
290
+ if segment_content is None:
291
+ if index == self.expected_index:
292
+ self.expected_index += 1
293
+ continue
294
+
295
+ # Write segment if it's the next expected one
296
+ if index == self.expected_index:
297
+ f.write(segment_content)
298
+ f.flush()
299
+ self.expected_index += 1
300
+
301
+ # Write any buffered segments that are now in order
302
+ while self.expected_index in self.buffer:
303
+ next_segment = self.buffer.pop(self.expected_index)
304
+
305
+ if next_segment is not None:
306
+ f.write(next_segment)
307
+ f.flush()
308
+
309
+ self.expected_index += 1
310
+
311
+ else:
312
+ self.buffer[index] = segment_content
313
+
314
+ except queue.Empty:
315
+ self.current_timeout = min(MAX_TIMEOOUT, self.current_timeout * 1.1)
316
+ time.sleep(0.05)
317
+
318
+ if self.stop_event.is_set():
319
+ break
320
+
321
+ except Exception as e:
322
+ logging.error(f"Error writing segment {index}: {str(e)}")
323
+
324
+ def download_streams(self, description: str, type: str):
325
+ """
326
+ Downloads all TS segments in parallel and writes them to a file.
327
+
328
+ Parameters:
329
+ - description: Description to insert on tqdm bar
330
+ - type (str): Type of download: 'video' or 'audio'
331
+ """
332
+ if TELEGRAM_BOT:
333
+
334
+ # Viene usato per lo screen
335
+ console.log("####")
336
+
337
+ self.get_info()
338
+ self.setup_interrupt_handler()
339
+
340
+ progress_bar = tqdm(
341
+ total=len(self.segments),
342
+ unit='s',
343
+ ascii='░▒█',
344
+ bar_format=self._get_bar_format(description),
345
+ mininterval=0.6,
346
+ maxinterval=1.0,
347
+ file=sys.stdout, # Using file=sys.stdout to force in-place updates because sys.stderr may not support carriage returns in this environment.
348
+ )
349
+
350
+ try:
351
+ writer_thread = threading.Thread(target=self.write_segments_to_file)
352
+ writer_thread.daemon = True
353
+ writer_thread.start()
354
+
355
+ # Configure workers and delay
356
+ max_workers = self._get_worker_count(type)
357
+
358
+ # Download segments with completion verification
359
+ with ThreadPoolExecutor(max_workers=max_workers) as executor:
360
+ futures = []
361
+ for index, segment_url in enumerate(self.segments):
362
+
363
+ # Check for interrupt before submitting each task
364
+ if self.interrupt_flag.is_set():
365
+ break
366
+
367
+ time.sleep(TQDM_DELAY_WORKER)
368
+ futures.append(executor.submit(self.download_segment, segment_url, index, progress_bar))
369
+
370
+ # Wait for futures with interrupt handling
371
+ for future in as_completed(futures):
372
+ if self.interrupt_flag.is_set():
373
+ break
374
+ try:
375
+ future.result()
376
+ except Exception as e:
377
+ logging.error(f"Error in download thread: {str(e)}")
378
+
379
+ # Interrupt handling for missing segments
380
+ if not self.interrupt_flag.is_set():
381
+ total_segments = len(self.segments)
382
+ completed_segments = len(self.downloaded_segments)
383
+
384
+ if completed_segments < total_segments:
385
+ missing_segments = set(range(total_segments)) - self.downloaded_segments
386
+ logging.warning(f"Missing segments: {sorted(missing_segments)}")
387
+
388
+ # Retry missing segments with interrupt check
389
+ for index in missing_segments:
390
+ if self.interrupt_flag.is_set():
391
+ break
392
+
393
+ try:
394
+ self.download_segment(self.segments[index], index, progress_bar)
395
+
396
+ except Exception as e:
397
+ logging.error(f"Failed to retry segment {index}: {str(e)}")
398
+
399
+ finally:
400
+ self._cleanup_resources(writer_thread, progress_bar)
401
+
402
+ if not self.interrupt_flag.is_set():
403
+ self._verify_download_completion()
404
+
405
+ return self._generate_results(type)
406
+
407
+ def _get_bar_format(self, description: str) -> str:
408
+ """
409
+ Generate platform-appropriate progress bar format.
410
+ """
411
+ return (
412
+ f"{Colors.YELLOW}[HLS] {Colors.WHITE}({Colors.CYAN}{description}{Colors.WHITE}): "
413
+ f"{Colors.RED}{{percentage:.2f}}% "
414
+ f"{Colors.MAGENTA}{{bar}} "
415
+ f"{Colors.YELLOW}{{elapsed}}{Colors.WHITE} < {Colors.CYAN}{{remaining}}{Colors.WHITE}{{postfix}}{Colors.WHITE}"
416
+ )
417
+
418
+ def _get_worker_count(self, stream_type: str) -> int:
419
+ """
420
+ Calculate optimal parallel workers based on stream type and infrastructure.
421
+ """
422
+ base_workers = {
423
+ 'video': DEFAULT_VIDEO_WORKERS,
424
+ 'audio': DEFAULT_AUDIO_WORKERS
425
+ }.get(stream_type.lower(), 1)
426
+
427
+ return base_workers
428
+
429
+ def _generate_results(self, stream_type: str) -> Dict:
430
+ """Package final download results."""
431
+ return {
432
+ 'type': stream_type,
433
+ 'nFailed': self.info_nFailed,
434
+ 'stopped': self.download_interrupted
435
+ }
436
+
437
+ def _verify_download_completion(self) -> None:
438
+ """Validate final download integrity."""
439
+ total = len(self.segments)
440
+ if len(self.downloaded_segments) / total < 0.999:
441
+ missing = sorted(set(range(total)) - self.downloaded_segments)
442
+ raise RuntimeError(f"Download incomplete ({len(self.downloaded_segments)/total:.1%}). Missing segments: {missing}")
443
+
444
+ def _cleanup_resources(self, writer_thread: threading.Thread, progress_bar: tqdm) -> None:
445
+ """Ensure resource cleanup and final reporting."""
446
+ self.stop_event.set()
447
+ writer_thread.join(timeout=30)
448
+ progress_bar.close()
449
+
450
+ if self.info_nFailed > 0:
451
+ self._display_error_summary()
452
+
453
+ self.buffer = {}
454
+ self.expected_index = 0
455
+
456
+ def _display_error_summary(self) -> None:
457
+ """Generate final error report."""
458
+ console.print(f"\n[cyan]Retry Summary: "
459
+ f"[white]Max retries: [green]{self.info_maxRetry} "
460
+ f"[white]Total retries: [green]{self.info_nRetry} "
461
+ f"[white]Failed segments: [red]{self.info_nFailed}")
462
+
463
+ if self.info_nRetry > len(self.segments) * 0.3:
464
+ console.print("[yellow]Warning: High retry count detected. Consider reducing worker count in config.")
@@ -1,5 +1,5 @@
1
1
  __title__ = 'StreamingCommunity'
2
- __version__ = '3.2.7'
2
+ __version__ = '3.2.8'
3
3
  __author__ = 'Arrowar'
4
4
  __description__ = 'A command-line program to download film'
5
5
  __copyright__ = 'Copyright 2025'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: StreamingCommunity
3
- Version: 3.2.7
3
+ Version: 3.2.8
4
4
  Home-page: https://github.com/Lovi-0/StreamingCommunity
5
5
  Author: Lovi-0
6
6
  Project-URL: Bug Reports, https://github.com/Lovi-0/StreamingCommunity/issues