mirror of
https://github.com/Arrowar/StreamingCommunity.git
synced 2025-06-06 19:45:24 +00:00
Add check for cpython, requirements
This commit is contained in:
parent
a0b8234433
commit
4aa103b4ad
@ -55,7 +55,7 @@ max_timeout = config_manager.get_int("REQUESTS", "timeout")
|
|||||||
|
|
||||||
|
|
||||||
class M3U8_Segments:
|
class M3U8_Segments:
|
||||||
def __init__(self, url: str, tmp_folder: str, is_index_url: bool = True):
|
def __init__(self, url: str, tmp_folder: str, is_index_url: bool = True, base_timeout=1.0, max_timeout=5.0):
|
||||||
"""
|
"""
|
||||||
Initializes the M3U8_Segments object.
|
Initializes the M3U8_Segments object.
|
||||||
|
|
||||||
@ -81,6 +81,9 @@ class M3U8_Segments:
|
|||||||
self.queue = PriorityQueue()
|
self.queue = PriorityQueue()
|
||||||
self.stop_event = threading.Event()
|
self.stop_event = threading.Event()
|
||||||
self.downloaded_segments = set()
|
self.downloaded_segments = set()
|
||||||
|
self.base_timeout = base_timeout
|
||||||
|
self.max_timeout = max_timeout
|
||||||
|
self.current_timeout = base_timeout
|
||||||
|
|
||||||
# Stopping
|
# Stopping
|
||||||
self.interrupt_flag = threading.Event()
|
self.interrupt_flag = threading.Event()
|
||||||
@ -226,13 +229,7 @@ class M3U8_Segments:
|
|||||||
- progress_bar (tqdm): Progress counter for tracking download progress.
|
- progress_bar (tqdm): Progress counter for tracking download progress.
|
||||||
- retries (int): The number of times to retry on failure (default is 3).
|
- retries (int): The number of times to retry on failure (default is 3).
|
||||||
- backoff_factor (float): The backoff factor for exponential backoff (default is 1.5 seconds).
|
- backoff_factor (float): The backoff factor for exponential backoff (default is 1.5 seconds).
|
||||||
"""
|
"""
|
||||||
if self.interrupt_flag.is_set():
|
|
||||||
return
|
|
||||||
|
|
||||||
need_verify = REQUEST_VERIFY
|
|
||||||
min_segment_size = 100 # Minimum acceptable size for a TS segment in bytes
|
|
||||||
|
|
||||||
for attempt in range(retries):
|
for attempt in range(retries):
|
||||||
if self.interrupt_flag.is_set():
|
if self.interrupt_flag.is_set():
|
||||||
return
|
return
|
||||||
@ -247,7 +244,7 @@ class M3U8_Segments:
|
|||||||
proxy = self.valid_proxy[index % len(self.valid_proxy)]
|
proxy = self.valid_proxy[index % len(self.valid_proxy)]
|
||||||
logging.info(f"Use proxy: {proxy}")
|
logging.info(f"Use proxy: {proxy}")
|
||||||
|
|
||||||
with httpx.Client(proxies=proxy, verify=need_verify) as client:
|
with httpx.Client(proxies=proxy, verify=REQUEST_VERIFY) as client:
|
||||||
if 'key_base_url' in self.__dict__:
|
if 'key_base_url' in self.__dict__:
|
||||||
response = client.get(
|
response = client.get(
|
||||||
url=ts_url,
|
url=ts_url,
|
||||||
@ -265,7 +262,7 @@ class M3U8_Segments:
|
|||||||
)
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
with httpx.Client(verify=need_verify) as client_2:
|
with httpx.Client(verify=REQUEST_VERIFY) as client_2:
|
||||||
if 'key_base_url' in self.__dict__:
|
if 'key_base_url' in self.__dict__:
|
||||||
response = client_2.get(
|
response = client_2.get(
|
||||||
url=ts_url,
|
url=ts_url,
|
||||||
@ -286,19 +283,12 @@ class M3U8_Segments:
|
|||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
segment_content = response.content
|
segment_content = response.content
|
||||||
content_size = len(segment_content)
|
content_size = len(segment_content)
|
||||||
|
|
||||||
# Check if segment is too small (possibly corrupted or empty)
|
|
||||||
if content_size < min_segment_size:
|
|
||||||
raise httpx.RequestError(f"Segment {index} too small ({content_size} bytes)")
|
|
||||||
|
|
||||||
duration = time.time() - start_time
|
duration = time.time() - start_time
|
||||||
|
|
||||||
# Decrypt if needed and verify decrypted content
|
# Decrypt if needed and verify decrypted content
|
||||||
if self.decryption is not None:
|
if self.decryption is not None:
|
||||||
try:
|
try:
|
||||||
segment_content = self.decryption.decrypt(segment_content)
|
segment_content = self.decryption.decrypt(segment_content)
|
||||||
if len(segment_content) < min_segment_size:
|
|
||||||
raise Exception(f"Decrypted segment {index} too small ({len(segment_content)} bytes)")
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Decryption failed for segment {index}: {str(e)}")
|
logging.error(f"Decryption failed for segment {index}: {str(e)}")
|
||||||
@ -334,19 +324,20 @@ class M3U8_Segments:
|
|||||||
"""
|
"""
|
||||||
Writes segments to file with additional verification.
|
Writes segments to file with additional verification.
|
||||||
"""
|
"""
|
||||||
|
buffer = {}
|
||||||
|
expected_index = 0
|
||||||
|
segments_written = set()
|
||||||
|
|
||||||
with open(self.tmp_file_path, 'wb') as f:
|
with open(self.tmp_file_path, 'wb') as f:
|
||||||
expected_index = 0
|
|
||||||
buffer = {}
|
|
||||||
total_written = 0
|
|
||||||
segments_written = set()
|
|
||||||
|
|
||||||
while not self.stop_event.is_set() or not self.queue.empty():
|
while not self.stop_event.is_set() or not self.queue.empty():
|
||||||
|
|
||||||
if self.interrupt_flag.is_set():
|
if self.interrupt_flag.is_set():
|
||||||
break
|
break
|
||||||
|
|
||||||
try:
|
try:
|
||||||
index, segment_content = self.queue.get(timeout=1)
|
index, segment_content = self.queue.get(timeout=self.current_timeout)
|
||||||
|
|
||||||
|
# Successful queue retrieval: reduce timeout
|
||||||
|
self.current_timeout = max(self.base_timeout, self.current_timeout / 2)
|
||||||
|
|
||||||
# Handle failed segments
|
# Handle failed segments
|
||||||
if segment_content is None:
|
if segment_content is None:
|
||||||
@ -357,7 +348,6 @@ class M3U8_Segments:
|
|||||||
# Write segment if it's the next expected one
|
# Write segment if it's the next expected one
|
||||||
if index == expected_index:
|
if index == expected_index:
|
||||||
f.write(segment_content)
|
f.write(segment_content)
|
||||||
total_written += len(segment_content)
|
|
||||||
segments_written.add(index)
|
segments_written.add(index)
|
||||||
f.flush()
|
f.flush()
|
||||||
expected_index += 1
|
expected_index += 1
|
||||||
@ -365,26 +355,25 @@ class M3U8_Segments:
|
|||||||
# Write any buffered segments that are now in order
|
# Write any buffered segments that are now in order
|
||||||
while expected_index in buffer:
|
while expected_index in buffer:
|
||||||
next_segment = buffer.pop(expected_index)
|
next_segment = buffer.pop(expected_index)
|
||||||
|
|
||||||
if next_segment is not None:
|
if next_segment is not None:
|
||||||
f.write(next_segment)
|
f.write(next_segment)
|
||||||
total_written += len(next_segment)
|
|
||||||
segments_written.add(expected_index)
|
segments_written.add(expected_index)
|
||||||
f.flush()
|
f.flush()
|
||||||
|
|
||||||
expected_index += 1
|
expected_index += 1
|
||||||
|
|
||||||
else:
|
else:
|
||||||
buffer[index] = segment_content
|
buffer[index] = segment_content
|
||||||
|
|
||||||
except queue.Empty:
|
except queue.Empty:
|
||||||
|
self.current_timeout = min(self.max_timeout, self.current_timeout * 1.5)
|
||||||
|
|
||||||
if self.stop_event.is_set():
|
if self.stop_event.is_set():
|
||||||
break
|
break
|
||||||
continue
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error writing segment {index}: {str(e)}")
|
logging.error(f"Error writing segment {index}: {str(e)}")
|
||||||
continue
|
|
||||||
|
|
||||||
# Final verification
|
|
||||||
if total_written == 0:
|
|
||||||
raise Exception("No data written to file")
|
|
||||||
|
|
||||||
def download_streams(self, add_desc):
|
def download_streams(self, add_desc):
|
||||||
"""
|
"""
|
||||||
|
@ -65,18 +65,21 @@ class M3U8_Ts_Estimator:
|
|||||||
try:
|
try:
|
||||||
io_counters = psutil.net_io_counters()
|
io_counters = psutil.net_io_counters()
|
||||||
return io_counters
|
return io_counters
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.warning(f"Unable to access network I/O counters: {e}")
|
logging.warning(f"Unable to access network I/O counters: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
old_value = get_network_io()
|
old_value = get_network_io()
|
||||||
|
|
||||||
if old_value is None: # If psutil is not available, continue with default values
|
if old_value is None: # If psutil is not available, continue with default values
|
||||||
time.sleep(interval)
|
time.sleep(interval)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
time.sleep(interval)
|
time.sleep(interval)
|
||||||
new_value = get_network_io()
|
new_value = get_network_io()
|
||||||
|
|
||||||
if new_value is None: # Handle again if psutil fails in the next call
|
if new_value is None: # Handle again if psutil fails in the next call
|
||||||
time.sleep(interval)
|
time.sleep(interval)
|
||||||
continue
|
continue
|
||||||
|
@ -23,7 +23,7 @@ import httpx
|
|||||||
|
|
||||||
|
|
||||||
# Internal utilities
|
# Internal utilities
|
||||||
from StreamingCommunity.Src.Util.console import console
|
from StreamingCommunity.Src.Util.console import console, msg
|
||||||
|
|
||||||
|
|
||||||
# Variable
|
# Variable
|
||||||
@ -318,9 +318,6 @@ class OsSummary():
|
|||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str: The version string of the executable.
|
str: The version string of the executable.
|
||||||
|
|
||||||
Raises:
|
|
||||||
SystemExit: If the command is not found or fails to execute.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -328,7 +325,7 @@ class OsSummary():
|
|||||||
return version_output.split(" ")[2]
|
return version_output.split(" ")[2]
|
||||||
|
|
||||||
except (FileNotFoundError, subprocess.CalledProcessError):
|
except (FileNotFoundError, subprocess.CalledProcessError):
|
||||||
print(f"{command[0]} not found")
|
console.print(f"{command[0]} not found", style="bold red")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
def get_library_version(self, lib_name: str):
|
def get_library_version(self, lib_name: str):
|
||||||
@ -341,7 +338,6 @@ class OsSummary():
|
|||||||
Returns:
|
Returns:
|
||||||
str: The library name followed by its version, or `-not installed` if not found.
|
str: The library name followed by its version, or `-not installed` if not found.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
version = importlib.metadata.version(lib_name)
|
version = importlib.metadata.version(lib_name)
|
||||||
return f"{lib_name}-{version}"
|
return f"{lib_name}-{version}"
|
||||||
@ -349,7 +345,62 @@ class OsSummary():
|
|||||||
except importlib.metadata.PackageNotFoundError:
|
except importlib.metadata.PackageNotFoundError:
|
||||||
return f"{lib_name}-not installed"
|
return f"{lib_name}-not installed"
|
||||||
|
|
||||||
def get_system_summary(self):
|
async def download_requirements(self, url: str, filename: str):
|
||||||
|
"""
|
||||||
|
Download the requirements.txt file from the specified URL if not found locally using httpx.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
url (str): The URL to download the requirements file from.
|
||||||
|
filename (str): The local filename to save the requirements file as.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
console.print(f"{filename} not found locally. Downloading from {url}...", style="bold yellow")
|
||||||
|
response = await client.get(url)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
with open(filename, 'wb') as f:
|
||||||
|
f.write(response.content)
|
||||||
|
|
||||||
|
console.print(f"{filename} successfully downloaded.", style="bold green")
|
||||||
|
|
||||||
|
else:
|
||||||
|
console.print(f"Failed to download {filename}. HTTP Status code: {response.status_code}", style="bold red")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
console.print(f"Failed to download {filename}: {e}", style="bold red")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def install_library(self, lib_name: str):
|
||||||
|
"""
|
||||||
|
Install a Python library using pip.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
lib_name (str): The name of the library to install.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
console.print(f"Installing {lib_name}...", style="bold yellow")
|
||||||
|
subprocess.check_call([sys.executable, "-m", "pip", "install", lib_name])
|
||||||
|
console.print(f"{lib_name} installed successfully!", style="bold green")
|
||||||
|
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
console.print(f"Failed to install {lib_name}: {e}", style="bold red")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def check_python_version(self):
|
||||||
|
"""
|
||||||
|
Check if the installed Python is the official CPython distribution.
|
||||||
|
Exits with a message if not the official version.
|
||||||
|
"""
|
||||||
|
python_implementation = platform.python_implementation()
|
||||||
|
|
||||||
|
if python_implementation != "CPython":
|
||||||
|
console.print(f"[bold red]Warning: You are using a non-official Python distribution: {python_implementation}.[/bold red]")
|
||||||
|
console.print("Please install the official Python from [bold blue]https://www.python.org[/bold blue] and try again.", style="bold yellow")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
async def get_system_summary(self):
|
||||||
"""
|
"""
|
||||||
Generate a summary of the system environment.
|
Generate a summary of the system environment.
|
||||||
|
|
||||||
@ -360,6 +411,9 @@ class OsSummary():
|
|||||||
- Installed Python libraries as listed in `requirements.txt`.
|
- Installed Python libraries as listed in `requirements.txt`.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# Check if Python is the official CPython
|
||||||
|
self.check_python_version()
|
||||||
|
|
||||||
# Check internet connectivity
|
# Check internet connectivity
|
||||||
InternManager().check_internet()
|
InternManager().check_internet()
|
||||||
console.print("[bold blue]System Summary[/bold blue][white]:")
|
console.print("[bold blue]System Summary[/bold blue][white]:")
|
||||||
@ -381,12 +435,32 @@ class OsSummary():
|
|||||||
console.print(f"[cyan]Exe versions[white]: [bold red]ffmpeg {ffmpeg_version}, ffprobe {ffprobe_version}[/bold red]")
|
console.print(f"[cyan]Exe versions[white]: [bold red]ffmpeg {ffmpeg_version}, ffprobe {ffprobe_version}[/bold red]")
|
||||||
logging.info(f"Dependencies: ffmpeg {ffmpeg_version}, ffprobe {ffprobe_version}")
|
logging.info(f"Dependencies: ffmpeg {ffmpeg_version}, ffprobe {ffprobe_version}")
|
||||||
|
|
||||||
# Optional libraries versions
|
# Check if requirements.txt exists, if not download it
|
||||||
"""optional_libraries = [line.strip() for line in open('requirements.txt', 'r', encoding='utf-8-sig')]
|
requirements_file = 'requirements.txt'
|
||||||
optional_libs_versions = [self.get_library_version(lib) for lib in optional_libraries]
|
if not os.path.exists(requirements_file):
|
||||||
|
await self.download_requirements(
|
||||||
|
'https://raw.githubusercontent.com/Lovi-0/StreamingCommunity/refs/heads/main/requirements.txt',
|
||||||
|
requirements_file
|
||||||
|
)
|
||||||
|
|
||||||
|
# Read the optional libraries from the requirements file
|
||||||
|
optional_libraries = [line.strip() for line in open(requirements_file, 'r', encoding='utf-8-sig')]
|
||||||
|
|
||||||
console.print(f"[cyan]Libraries[white]: [bold red]{', '.join(optional_libs_versions)}[/bold red]\n")
|
# Check if libraries are installed and prompt to install missing ones
|
||||||
logging.info(f"Libraries: {', '.join(optional_libs_versions)}")"""
|
for lib in optional_libraries:
|
||||||
|
installed_version = self.get_library_version(lib)
|
||||||
|
if 'not installed' in installed_version:
|
||||||
|
# Prompt user to install missing library using Prompt.ask()
|
||||||
|
user_response = msg.ask(f"{lib} is not installed. Do you want to install it? (yes/no)", default="y")
|
||||||
|
|
||||||
|
if user_response.lower().strip() in ["yes", "y"]:
|
||||||
|
self.install_library(lib)
|
||||||
|
else:
|
||||||
|
#console.print(f"[cyan]Library[white]: [bold red]{installed_version}[/bold red]")
|
||||||
|
logging.info(f"Library: {installed_version}")
|
||||||
|
|
||||||
|
console.print(f"[cyan]Libraries[white]: [bold red]{', '.join([self.get_library_version(lib) for lib in optional_libraries])}[/bold red]\n")
|
||||||
|
logging.info(f"Libraries: {', '.join([self.get_library_version(lib) for lib in optional_libraries])}")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import glob
|
import glob
|
||||||
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
import platform
|
import platform
|
||||||
import argparse
|
import argparse
|
||||||
@ -16,7 +17,7 @@ from StreamingCommunity.Src.Util.message import start_message
|
|||||||
from StreamingCommunity.Src.Util.console import console, msg
|
from StreamingCommunity.Src.Util.console import console, msg
|
||||||
from StreamingCommunity.Src.Util._jsonConfig import config_manager
|
from StreamingCommunity.Src.Util._jsonConfig import config_manager
|
||||||
from StreamingCommunity.Src.Upload.update import update as git_update
|
from StreamingCommunity.Src.Upload.update import update as git_update
|
||||||
from StreamingCommunity.Src.Util.os import os_summary
|
from StreamingCommunity.Src.Util.os import OsSummary
|
||||||
from StreamingCommunity.Src.Lib.TMBD import tmdb
|
from StreamingCommunity.Src.Lib.TMBD import tmdb
|
||||||
from StreamingCommunity.Src.Util.logger import Logger
|
from StreamingCommunity.Src.Util.logger import Logger
|
||||||
|
|
||||||
@ -103,7 +104,8 @@ def initialize():
|
|||||||
start_message()
|
start_message()
|
||||||
|
|
||||||
# Get system info
|
# Get system info
|
||||||
os_summary.get_system_summary()
|
os_summary = OsSummary()
|
||||||
|
asyncio.run(os_summary.get_system_summary())
|
||||||
|
|
||||||
# Set terminal size for win 7
|
# Set terminal size for win 7
|
||||||
if platform.system() == "Windows" and "7" in platform.version():
|
if platform.system() == "Windows" and "7" in platform.version():
|
||||||
|
Loading…
x
Reference in New Issue
Block a user