mirror of
https://github.com/Arrowar/StreamingCommunity.git
synced 2025-06-05 02:55:25 +00:00
v2.4.0
This commit is contained in:
parent
fd0add424d
commit
3b8af89c35
3
.gitignore
vendored
3
.gitignore
vendored
@ -47,5 +47,4 @@ venv.bak/
|
||||
Video
|
||||
note.txt
|
||||
list_proxy.txt
|
||||
cmd.txt
|
||||
downloaded_files
|
||||
cmd.txt
|
21
README.md
21
README.md
@ -9,9 +9,6 @@
|
||||
<a href="https://www.paypal.com/donate/?hosted_button_id=UXTWMT8P6HE2C">
|
||||
<img src="https://img.shields.io/badge/_-Donate-red.svg?logo=githubsponsors&labelColor=555555&style=for-the-badge" alt="Donate"/>
|
||||
</a>
|
||||
<a href="https://github.com/Lovi-0/StreamingCommunity/blob/main/LICENSE">
|
||||
<img src="https://img.shields.io/badge/License-GPL_3.0-blue.svg?style=for-the-badge" alt="License"/>
|
||||
</a>
|
||||
<a href="https://github.com/Lovi-0/StreamingCommunity/commits">
|
||||
<img src="https://img.shields.io/github/commit-activity/m/Lovi-0/StreamingCommunity?label=commits&style=for-the-badge" alt="Commits"/>
|
||||
</a>
|
||||
@ -21,17 +18,11 @@
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/Lovi-0/StreamingCommunity/blob/main/LICENSE">
|
||||
<img src="https://img.shields.io/badge/License-GPL_3.0-blue.svg?style=for-the-badge" alt="License"/>
|
||||
</a>
|
||||
<a href="https://pypi.org/project/streamingcommunity">
|
||||
<img src="https://img.shields.io/pypi/dm/streamingcommunity?style=for-the-badge" alt="PyPI Downloads"/>
|
||||
</a>
|
||||
<a href="https://github.com/Lovi-0/StreamingCommunity/network/members">
|
||||
<img src="https://img.shields.io/github/forks/Lovi-0/StreamingCommunity?style=for-the-badge" alt="Forks"/>
|
||||
</a>
|
||||
<a href="https://github.com/Lovi-0/StreamingCommunity">
|
||||
<img src="https://img.shields.io/github/languages/code-size/Lovi-0/StreamingCommunity?style=for-the-badge" alt="Code Size"/>
|
||||
</a>
|
||||
<a href="https://github.com/Lovi-0/StreamingCommunity">
|
||||
<img src="https://img.shields.io/github/repo-size/Lovi-0/StreamingCommunity?style=for-the-badge" alt="Repo Size"/>
|
||||
<img src="https://img.shields.io/pypi/dw/streamingcommunity?style=for-the-badge" alt="PyPI Downloads"/>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
@ -477,10 +468,10 @@ The `run-container` command mounts also the `config.json` file, so any change to
|
||||
| Website | Status |
|
||||
|:-------------------|:------:|
|
||||
| [1337xx](https://1337xx.to/) | ✅ |
|
||||
| [Altadefinizione](https://altadefinizione.florist/) | ✅ |
|
||||
| [AltadefinizioneGratis](https://altadefinizionegratis.info/) | ✅ |
|
||||
| [AnimeUnity](https://animeunity.so/) | ✅ |
|
||||
| [Ilcorsaronero](https://ilcorsaronero.link/) | ✅ |
|
||||
| [CB01New](https://cb01new.lol/) | ✅ |
|
||||
| [CB01New](https://cb01new.video/) | ✅ |
|
||||
| [DDLStreamItaly](https://ddlstreamitaly.co/) | ✅ |
|
||||
| [GuardaSerie](https://guardaserie.academy/) | ✅ |
|
||||
| [MostraGuarda](https://mostraguarda.stream/) | ✅ |
|
||||
|
@ -199,7 +199,7 @@ def display_episodes_list(scrape_serie) -> str:
|
||||
# Run the table and handle user input
|
||||
last_command = table_show_manager.run()
|
||||
|
||||
if last_command == "q":
|
||||
if last_command == "q" or last_command == "quit":
|
||||
console.print("\n[red]Quit [white]...")
|
||||
sys.exit(0)
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
# 10.12.23
|
||||
|
||||
import sys
|
||||
import json
|
||||
import logging
|
||||
import secrets
|
||||
@ -83,8 +82,12 @@ def get_version_and_domain():
|
||||
if not disable_searchDomain:
|
||||
domain_to_use, base_url = search_domain(SITE_NAME, f"https://{SITE_NAME}.{DOMAIN_NOW}")
|
||||
|
||||
version = get_version(domain_to_use)
|
||||
|
||||
try:
|
||||
version = get_version(domain_to_use)
|
||||
except:
|
||||
console.print("[green]Auto generate version ...")
|
||||
version = secrets.token_hex(32 // 2)
|
||||
|
||||
return version, domain_to_use
|
||||
|
||||
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
import ssl
|
||||
import time
|
||||
import certifi
|
||||
from urllib.parse import urlparse, unquote
|
||||
|
||||
|
||||
@ -20,14 +19,14 @@ from StreamingCommunity.Util._jsonConfig import config_manager
|
||||
base_headers = {
|
||||
'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
|
||||
'accept-language': 'it-IT,it;q=0.9,en-US;q=0.8,en;q=0.7',
|
||||
'cache-control': 'max-age=0',
|
||||
'dnt': '1',
|
||||
'priority': 'u=0, i',
|
||||
'referer': '',
|
||||
'sec-ch-ua-mobile': '?0',
|
||||
'sec-ch-ua-platform': '"Windows"',
|
||||
'sec-fetch-dest': 'document',
|
||||
'sec-fetch-mode': 'navigate',
|
||||
'sec-fetch-site': 'none',
|
||||
'sec-fetch-site': 'same-origin',
|
||||
'sec-fetch-user': '?1',
|
||||
'upgrade-insecure-requests': '1',
|
||||
'user-agent': ''
|
||||
@ -84,6 +83,8 @@ def validate_url(url, base_url, max_timeout, max_retries=3, sleep=3):
|
||||
# Verify URL structure matches base_url structure
|
||||
base_domain = get_base_domain(base_url)
|
||||
url_domain = get_base_domain(url)
|
||||
|
||||
base_headers['referer'] = url
|
||||
base_headers['user-agent'] = get_headers()
|
||||
|
||||
if base_domain != url_domain:
|
||||
@ -98,7 +99,7 @@ def validate_url(url, base_url, max_timeout, max_retries=3, sleep=3):
|
||||
return False, None
|
||||
|
||||
client = httpx.Client(
|
||||
verify=certifi.where(),
|
||||
verify=False,
|
||||
headers=base_headers,
|
||||
timeout=max_timeout
|
||||
)
|
||||
|
@ -74,7 +74,7 @@ def get_select_title(table_show_manager, media_search_manager):
|
||||
table_show_manager.clear()
|
||||
|
||||
# Handle user's quit command
|
||||
if last_command == "q":
|
||||
if last_command == "q" or last_command == "quit":
|
||||
console.print("\n[red]Quit [white]...")
|
||||
sys.exit(0)
|
||||
|
||||
|
@ -75,7 +75,7 @@ def get_select_title(table_show_manager, generic_obj):
|
||||
table_show_manager.clear()
|
||||
|
||||
# Handle user's quit command
|
||||
if last_command == "q":
|
||||
if last_command == "q" or last_command == "quit":
|
||||
Console.print("\n[red]Quit [white]...")
|
||||
sys.exit(0)
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
__title__ = 'StreamingCommunity'
|
||||
__version__ = '2.3.0'
|
||||
__version__ = '2.4.0'
|
||||
__author__ = 'Lovi-0'
|
||||
__description__ = 'A command-line program to download film'
|
||||
__copyright__ = 'Copyright 2024'
|
||||
|
@ -19,6 +19,8 @@ from rich.progress import Progress, SpinnerColumn, BarColumn, TextColumn, TimeRe
|
||||
|
||||
# Variable
|
||||
console = Console()
|
||||
|
||||
# https://github.com/eugeneware/ffmpeg-static/releases
|
||||
FFMPEG_CONFIGURATION = {
|
||||
'windows': {
|
||||
'base_dir': lambda home: os.path.join(os.path.splitdrive(home)[0] + os.path.sep, 'binary'),
|
||||
@ -28,13 +30,13 @@ FFMPEG_CONFIGURATION = {
|
||||
},
|
||||
'darwin': {
|
||||
'base_dir': lambda home: os.path.join(home, 'Applications', 'binary'),
|
||||
'download_url': 'https://evermeet.cx/ffmpeg/ffmpeg-{version}.zip',
|
||||
'download_url': 'https://github.com/eugeneware/ffmpeg-static/releases/download/b{version}/ffmpeg-macOS-{arch}.zip',
|
||||
'file_extension': '.zip',
|
||||
'executables': ['ffmpeg', 'ffprobe', 'ffplay']
|
||||
},
|
||||
'linux': {
|
||||
'base_dir': lambda home: os.path.join(home, '.local', 'bin', 'binary'),
|
||||
'download_url': 'https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-{arch}-static.tar.xz',
|
||||
'download_url': 'https://github.com/eugeneware/ffmpeg-static/releases/download/b{version}/ffmpeg-linux-{arch}.tar.xz',
|
||||
'file_extension': '.tar.xz',
|
||||
'executables': ['ffmpeg', 'ffprobe', 'ffplay']
|
||||
}
|
||||
@ -150,19 +152,26 @@ class FFMPEGDownloader:
|
||||
|
||||
def _get_latest_version(self) -> Optional[str]:
|
||||
"""
|
||||
Get the latest FFmpeg version from the official website.
|
||||
Get the latest FFmpeg version from the GitHub releases page.
|
||||
|
||||
Returns:
|
||||
Optional[str]: The latest version string, or None if retrieval fails
|
||||
Optional[str]: The latest version string, or None if retrieval fails.
|
||||
|
||||
Raises:
|
||||
requests.exceptions.RequestException: If there are network-related errors
|
||||
requests.exceptions.RequestException: If there are network-related errors.
|
||||
"""
|
||||
try:
|
||||
version_url = 'https://www.gyan.dev/ffmpeg/builds/release-version'
|
||||
return requests.get(version_url).text.strip()
|
||||
# Use GitHub API to fetch the latest release
|
||||
response = requests.get(
|
||||
'https://api.github.com/repos/eugeneware/ffmpeg-static/releases/latest'
|
||||
)
|
||||
response.raise_for_status()
|
||||
latest_release = response.json()
|
||||
|
||||
# Extract the tag name or version from the release
|
||||
return latest_release.get('tag_name')
|
||||
except Exception as e:
|
||||
logging.error(f"Unable to get version: {e}")
|
||||
logging.error(f"Unable to get version from GitHub: {e}")
|
||||
return None
|
||||
|
||||
def _download_file(self, url: str, destination: str) -> bool:
|
||||
|
@ -1,5 +1,6 @@
|
||||
# 26.03.24
|
||||
|
||||
import os
|
||||
import logging
|
||||
from logging.handlers import RotatingFileHandler
|
||||
|
||||
@ -26,6 +27,7 @@ class Logger:
|
||||
|
||||
# Configure file logging if debug mode and logging to file are both enabled
|
||||
if self.log_to_file:
|
||||
self.remove_existing_log_file()
|
||||
self.configure_file_logging()
|
||||
else:
|
||||
|
||||
@ -51,3 +53,10 @@ class Logger:
|
||||
formatter = logging.Formatter('[%(filename)s:%(lineno)s - %(funcName)20s() ] %(asctime)s - %(levelname)s - %(message)s')
|
||||
file_handler.setFormatter(formatter)
|
||||
logging.getLogger('').addHandler(file_handler)
|
||||
|
||||
def remove_existing_log_file(self):
|
||||
"""
|
||||
Remove the log file if it already exists.
|
||||
"""
|
||||
if os.path.exists(self.log_file):
|
||||
os.remove(self.log_file)
|
@ -8,16 +8,16 @@ import shutil
|
||||
import hashlib
|
||||
import logging
|
||||
import platform
|
||||
import unidecode
|
||||
import subprocess
|
||||
import contextlib
|
||||
import pathvalidate
|
||||
import urllib.request
|
||||
import importlib.metadata
|
||||
|
||||
|
||||
# External library
|
||||
import httpx
|
||||
from unidecode import unidecode
|
||||
from pathvalidate import sanitize_filename, sanitize_filepath
|
||||
|
||||
|
||||
# Internal utilities
|
||||
@ -25,172 +25,133 @@ from .ffmpeg_installer import check_ffmpeg
|
||||
from StreamingCommunity.Util.console import console, msg
|
||||
|
||||
|
||||
# Variable
|
||||
OS_CONFIGURATIONS = {
|
||||
'windows': {
|
||||
'max_length': 255,
|
||||
'invalid_chars': '<>:"/\\|?*',
|
||||
'reserved_names': [
|
||||
"CON", "PRN", "AUX", "NUL",
|
||||
"COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
|
||||
"LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"
|
||||
],
|
||||
'max_path': 255
|
||||
},
|
||||
'darwin': {
|
||||
'max_length': 4096,
|
||||
'invalid_chars': '/:',
|
||||
'reserved_names': [],
|
||||
'hidden_file_restriction': True
|
||||
},
|
||||
'linux': {
|
||||
'max_length': 4096,
|
||||
'invalid_chars': '/\0',
|
||||
'reserved_names': []
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
class OsManager:
|
||||
def __init__(self):
|
||||
self.system = self._detect_system()
|
||||
self.config = OS_CONFIGURATIONS.get(self.system, {})
|
||||
self.max_length = self._get_max_length()
|
||||
|
||||
def _detect_system(self) -> str:
|
||||
"""Detect and normalize operating system name."""
|
||||
system = platform.system().lower()
|
||||
if system not in ['windows', 'darwin', 'linux']:
|
||||
raise ValueError(f"Unsupported operating system: {system}")
|
||||
return system
|
||||
|
||||
if system in OS_CONFIGURATIONS:
|
||||
return system
|
||||
|
||||
raise ValueError(f"Unsupported operating system: {system}")
|
||||
def _get_max_length(self) -> int:
|
||||
"""Get max filename length based on OS."""
|
||||
return 255 if self.system == 'windows' else 4096
|
||||
|
||||
def _normalize_windows_path(self, path: str) -> str:
|
||||
"""
|
||||
Normalize Windows paths to handle drive letters correctly.
|
||||
|
||||
Args:
|
||||
path (str): Original path that might contain a drive letter.
|
||||
|
||||
Returns:
|
||||
str: Properly normalized absolute path.
|
||||
"""
|
||||
if self.system != 'windows':
|
||||
"""Normalize Windows paths."""
|
||||
if not path or self.system != 'windows':
|
||||
return path
|
||||
|
||||
# Check if path starts with a drive letter
|
||||
|
||||
# Preserve network paths (UNC and IP-based)
|
||||
if path.startswith('\\\\') or path.startswith('//'):
|
||||
return path.replace('/', '\\')
|
||||
|
||||
# Handle drive letters
|
||||
if len(path) >= 2 and path[1] == ':':
|
||||
drive = path[0:2]
|
||||
rest = path[2:].lstrip(os.sep)
|
||||
# Ensure proper absolute path format
|
||||
return os.path.join(drive + os.sep, rest)
|
||||
return path
|
||||
|
||||
def _process_filename(self, filename: str) -> str:
|
||||
"""
|
||||
Comprehensively process filename with cross-platform considerations.
|
||||
|
||||
Args:
|
||||
filename (str): Original filename.
|
||||
|
||||
Returns:
|
||||
str: Processed filename.
|
||||
"""
|
||||
name, ext = os.path.splitext(filename)
|
||||
|
||||
# Handle length restrictions
|
||||
if len(name) > self.config['max_length']:
|
||||
name = self._truncate_filename(name)
|
||||
|
||||
# Reconstruct filename
|
||||
processed_filename = name + ext
|
||||
|
||||
return processed_filename
|
||||
rest = path[2:].replace('/', '\\').lstrip('\\')
|
||||
return f"{drive}\\{rest}"
|
||||
|
||||
def _truncate_filename(self, name: str) -> str:
|
||||
"""
|
||||
Truncate filename based on OS-specific rules.
|
||||
|
||||
Args:
|
||||
name (str): Original filename.
|
||||
|
||||
Returns:
|
||||
str: Truncated filename.
|
||||
"""
|
||||
logging.info("_truncate_filename: ", name)
|
||||
return path.replace('/', '\\')
|
||||
|
||||
if self.system == 'windows':
|
||||
return name[:self.config['max_length'] - 3] + '___'
|
||||
elif self.system == 'darwin':
|
||||
return name[:self.config['max_length']]
|
||||
elif self.system == 'linux':
|
||||
return name[:self.config['max_length'] - 2] + '___'
|
||||
def _normalize_mac_path(self, path: str) -> str:
|
||||
"""Normalize macOS paths."""
|
||||
if not path or self.system != 'darwin':
|
||||
return path
|
||||
|
||||
# Convert Windows separators to Unix
|
||||
normalized = path.replace('\\', '/')
|
||||
|
||||
# Ensure absolute paths start with /
|
||||
if normalized.startswith('/'):
|
||||
return os.path.normpath(normalized)
|
||||
|
||||
return normalized
|
||||
|
||||
def get_sanitize_file(self, filename: str) -> str:
|
||||
"""
|
||||
Sanitize filename using pathvalidate with unidecode.
|
||||
|
||||
Args:
|
||||
filename (str): Original filename.
|
||||
|
||||
Returns:
|
||||
str: Sanitized filename.
|
||||
"""
|
||||
"""Sanitize filename."""
|
||||
if not filename:
|
||||
return filename
|
||||
|
||||
# Decode unicode characters and sanitize
|
||||
decoded_filename = unidecode.unidecode(filename)
|
||||
sanitized_filename = pathvalidate.sanitize_filename(decoded_filename)
|
||||
# Decode and sanitize
|
||||
decoded = unidecode(filename)
|
||||
sanitized = sanitize_filename(decoded)
|
||||
|
||||
# Truncate if necessary based on OS configuration
|
||||
name, ext = os.path.splitext(sanitized_filename)
|
||||
if len(name) > self.config['max_length']:
|
||||
name = self._truncate_filename(name)
|
||||
# Split name and extension
|
||||
name, ext = os.path.splitext(sanitized)
|
||||
|
||||
result = name + ext
|
||||
return result
|
||||
# Calculate available length for name considering the '...' and extension
|
||||
max_name_length = self.max_length - len('...') - len(ext)
|
||||
|
||||
# Truncate name if it exceeds the max name length
|
||||
if len(name) > max_name_length:
|
||||
name = name[:max_name_length] + '...'
|
||||
|
||||
# Ensure the final file name includes the extension
|
||||
return name + ext
|
||||
|
||||
def get_sanitize_path(self, path: str) -> str:
|
||||
"""
|
||||
Sanitize folder path using pathvalidate with unidecode.
|
||||
|
||||
Args:
|
||||
path (str): Original folder path.
|
||||
|
||||
Returns:
|
||||
str: Sanitized folder path.
|
||||
"""
|
||||
"""Sanitize complete path."""
|
||||
if not path:
|
||||
return path
|
||||
|
||||
# Normalize path for Windows drive letters first
|
||||
path = self._normalize_windows_path(path)
|
||||
|
||||
# Decode unicode characters and sanitize
|
||||
decoded_path = unidecode.unidecode(path)
|
||||
sanitized_path = pathvalidate.sanitize_filepath(decoded_path)
|
||||
# Decode unicode characters
|
||||
decoded = unidecode(path)
|
||||
|
||||
# Split path and process each component
|
||||
path_components = os.path.normpath(sanitized_path).split(os.sep)
|
||||
|
||||
# Handle Windows drive letter specially
|
||||
if self.system == 'windows' and len(path_components[0]) == 2 and path_components[0][1] == ':':
|
||||
drive = path_components.pop(0)
|
||||
processed_components = [drive + os.sep]
|
||||
# Basic path sanitization
|
||||
sanitized = sanitize_filepath(decoded)
|
||||
|
||||
if self.system == 'windows':
|
||||
# Handle network paths (UNC or IP-based)
|
||||
if path.startswith('\\\\') or path.startswith('//'):
|
||||
parts = path.replace('/', '\\').split('\\')
|
||||
# Keep server/IP and share name as is
|
||||
sanitized_parts = parts[:4]
|
||||
# Sanitize remaining parts
|
||||
if len(parts) > 4:
|
||||
sanitized_parts.extend([
|
||||
self.get_sanitize_file(part)
|
||||
for part in parts[4:]
|
||||
if part
|
||||
])
|
||||
return '\\'.join(sanitized_parts)
|
||||
|
||||
# Handle drive letters
|
||||
elif len(path) >= 2 and path[1] == ':':
|
||||
drive = path[:2]
|
||||
rest = path[2:].lstrip('\\').lstrip('/')
|
||||
path_parts = [drive] + [
|
||||
self.get_sanitize_file(part)
|
||||
for part in rest.replace('/', '\\').split('\\')
|
||||
if part
|
||||
]
|
||||
return '\\'.join(path_parts)
|
||||
|
||||
# Regular path
|
||||
else:
|
||||
parts = path.replace('/', '\\').split('\\')
|
||||
return '\\'.join(p for p in parts if p)
|
||||
else:
|
||||
processed_components = []
|
||||
# Handle Unix-like paths (Linux and macOS)
|
||||
is_absolute = path.startswith('/')
|
||||
parts = path.replace('\\', '/').split('/')
|
||||
sanitized_parts = [
|
||||
self.get_sanitize_file(part)
|
||||
for part in parts
|
||||
if part
|
||||
]
|
||||
|
||||
result = '/'.join(sanitized_parts)
|
||||
if is_absolute:
|
||||
result = '/' + result
|
||||
|
||||
return result
|
||||
|
||||
# Process remaining components
|
||||
for component in path_components:
|
||||
if component: # Skip empty components
|
||||
if len(component) > self.config['max_length']:
|
||||
component = self._truncate_filename(component)
|
||||
|
||||
processed_components.append(component)
|
||||
|
||||
# Join with proper separator and normalize
|
||||
result = os.path.normpath(os.path.join(*processed_components))
|
||||
return result
|
||||
|
||||
def create_path(self, path: str, mode: int = 0o755) -> bool:
|
||||
"""
|
||||
Create directory path with specified permissions.
|
||||
@ -272,6 +233,7 @@ class OsManager:
|
||||
logging.error(f"An error occurred while checking file existence: {e}")
|
||||
return False
|
||||
|
||||
|
||||
class InternManager():
|
||||
|
||||
def format_file_size(self, size_bytes: float) -> str:
|
||||
|
@ -174,12 +174,12 @@ class TVShowManager:
|
||||
|
||||
else:
|
||||
choices = [str(i) for i in range(0, max_int_input)]
|
||||
choices.extend(["q", "", "back"])
|
||||
choices.extend(["q", "quit", "b", "back"])
|
||||
|
||||
key = Prompt.ask("[cyan]Insert media [red]index", choices=choices, show_choices=False)
|
||||
last_command = key
|
||||
|
||||
if key.lower() == "q":
|
||||
if key.lower() == "q" or key.lower() == "quit":
|
||||
break
|
||||
|
||||
elif key == "":
|
||||
@ -188,7 +188,7 @@ class TVShowManager:
|
||||
if self.slice_end > total_items:
|
||||
self.slice_end = total_items
|
||||
|
||||
elif key.lower() == "back" and research_func:
|
||||
elif (key.lower() == "b" or key.lower() == "back") and research_func:
|
||||
self.run_back_command(research_func)
|
||||
|
||||
else:
|
||||
@ -205,19 +205,19 @@ class TVShowManager:
|
||||
|
||||
else:
|
||||
choices = [str(i) for i in range(0, max_int_input)]
|
||||
choices.extend(["q", "", "back"])
|
||||
choices.extend(["q", "quit", "b", "back"])
|
||||
|
||||
key = Prompt.ask("[cyan]Insert media [red]index", choices=choices, show_choices=False)
|
||||
last_command = key
|
||||
|
||||
if key.lower() == "q":
|
||||
if key.lower() == "q" or key.lower() == "quit":
|
||||
break
|
||||
|
||||
elif key == "":
|
||||
self.slice_start = 0
|
||||
self.slice_end = self.step
|
||||
|
||||
elif key.lower() == "back" and research_func:
|
||||
elif (key.lower() == "b" or key.lower() == "back") and research_func:
|
||||
self.run_back_command(research_func)
|
||||
|
||||
else:
|
||||
|
@ -125,7 +125,6 @@ def initialize():
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
start = time.time()
|
||||
|
||||
# Create logger
|
||||
@ -136,9 +135,39 @@ def main():
|
||||
search_functions = load_search_functions()
|
||||
logging.info(f"Load module in: {time.time() - start} s")
|
||||
|
||||
# Create dynamic argument parser
|
||||
parser = argparse.ArgumentParser(description='Script to download film and series from the internet.')
|
||||
# Create argument parser
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Script to download movies and series from the internet. Use these commands to configure the script and control its behavior.'
|
||||
)
|
||||
|
||||
# Add arguments for the main configuration parameters
|
||||
parser.add_argument(
|
||||
'--add_siteName', type=bool, help='Enable or disable adding the site name to the file name (e.g., true/false).'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--disable_searchDomain', type=bool, help='Enable or disable searching in configured domains (e.g., true/false).'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--not_close', type=bool, help='If set to true, the script will not close the console after execution (e.g., true/false).'
|
||||
)
|
||||
|
||||
# Add arguments for M3U8 configuration
|
||||
parser.add_argument(
|
||||
'--default_video_worker', type=int, help='Number of workers for video during M3U8 download (default: 12).'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--default_audio_worker', type=int, help='Number of workers for audio during M3U8 download (default: 12).'
|
||||
)
|
||||
|
||||
# Add options for audio and subtitles
|
||||
parser.add_argument(
|
||||
'--specific_list_audio', type=str, help='Comma-separated list of specific audio languages to download (e.g., ita,eng).'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--specific_list_subtitles', type=str, help='Comma-separated list of specific subtitle languages to download (e.g., eng,spa).'
|
||||
)
|
||||
|
||||
# Add arguments for search functions
|
||||
color_map = {
|
||||
"anime": "red",
|
||||
"film_serie": "yellow",
|
||||
@ -153,10 +182,35 @@ def main():
|
||||
long_option = alias
|
||||
parser.add_argument(f'-{short_option}', f'--{long_option}', action='store_true', help=f'Search for {alias.split("_")[0]} on streaming platforms.')
|
||||
|
||||
# Parse command line arguments
|
||||
# Parse command-line arguments
|
||||
args = parser.parse_args()
|
||||
|
||||
# Mapping command-line arguments to functions
|
||||
# Map command-line arguments to the config values
|
||||
config_updates = {}
|
||||
|
||||
if args.add_siteName is not None:
|
||||
config_updates['DEFAULT.add_siteName'] = args.add_siteName
|
||||
if args.disable_searchDomain is not None:
|
||||
config_updates['DEFAULT.disable_searchDomain'] = args.disable_searchDomain
|
||||
if args.not_close is not None:
|
||||
config_updates['DEFAULT.not_close'] = args.not_close
|
||||
if args.default_video_worker is not None:
|
||||
config_updates['M3U8_DOWNLOAD.default_video_worker'] = args.default_video_worker
|
||||
if args.default_audio_worker is not None:
|
||||
config_updates['M3U8_DOWNLOAD.default_audio_worker'] = args.default_audio_worker
|
||||
if args.specific_list_audio is not None:
|
||||
config_updates['M3U8_DOWNLOAD.specific_list_audio'] = args.specific_list_audio.split(',')
|
||||
if args.specific_list_subtitles is not None:
|
||||
config_updates['M3U8_DOWNLOAD.specific_list_subtitles'] = args.specific_list_subtitles.split(',')
|
||||
|
||||
# Apply the updates to the config file
|
||||
for key, value in config_updates.items():
|
||||
section, option = key.split('.')
|
||||
config_manager.set_key(section, option, value)
|
||||
|
||||
config_manager.write_config()
|
||||
|
||||
# Map command-line arguments to functions
|
||||
arg_to_function = {alias: func for alias, (func, _) in search_functions.items()}
|
||||
|
||||
# Check which argument is provided and run the corresponding function
|
||||
@ -188,4 +242,4 @@ def main():
|
||||
run_function(input_to_function[category])
|
||||
else:
|
||||
console.print("[red]Invalid category.")
|
||||
sys.exit(0)
|
||||
sys.exit(0)
|
146
Test/Util/oss.py
Normal file
146
Test/Util/oss.py
Normal file
@ -0,0 +1,146 @@
|
||||
# 22.01.25
|
||||
|
||||
# Fix import
|
||||
import sys
|
||||
import os
|
||||
src_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
|
||||
sys.path.append(src_path)
|
||||
|
||||
|
||||
|
||||
# Import
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
from StreamingCommunity.Util.os import OsManager
|
||||
|
||||
class TestOsManager(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.test_paths = {
|
||||
'windows': {
|
||||
'network': [
|
||||
(r'\\server\share\folder\file.txt', r'\\server\share\folder\file.txt'),
|
||||
(r'\\192.168.1.100\share\folder\file.txt', r'\\192.168.1.100\share\folder\file.txt'),
|
||||
(r'\\server\share', r'\\server\share'),
|
||||
(r'\\server\share\\folder//subfolder\file.txt', r'\\server\share\folder\subfolder\file.txt')
|
||||
],
|
||||
'drive': [
|
||||
('C:\\folder\\file.txt', 'C:\\folder\\file.txt'),
|
||||
('C:/folder/file.txt', 'C:\\folder\\file.txt'),
|
||||
('D:\\Test\\file.txt', 'D:\\Test\\file.txt'),
|
||||
('D:/Test/file.txt', 'D:\\Test\\file.txt')
|
||||
],
|
||||
'relative': [
|
||||
('folder\\file.txt', 'folder\\file.txt'),
|
||||
('folder/file.txt', 'folder\\file.txt'),
|
||||
('.\\folder\\file.txt', 'folder\\file.txt')
|
||||
]
|
||||
},
|
||||
'darwin': {
|
||||
'absolute': [
|
||||
('/media/TV/show.mp4', '/media/TV/show.mp4'),
|
||||
('/Users/name/Documents/file.txt', '/Users/name/Documents/file.txt'),
|
||||
('/media/TV/show.mp4', '/media/TV/show.mp4')
|
||||
],
|
||||
'relative': [
|
||||
('folder/file.txt', 'folder/file.txt'),
|
||||
('folder/file.txt', 'folder/file.txt')
|
||||
]
|
||||
},
|
||||
'linux': {
|
||||
'absolute': [
|
||||
('/home/user/file.txt', '/home/user/file.txt'),
|
||||
('/mnt/data/file.txt', '/mnt/data/file.txt'),
|
||||
('/home/user/file.txt', '/home/user/file.txt')
|
||||
],
|
||||
'relative': [
|
||||
('folder/file.txt', 'folder/file.txt'),
|
||||
('folder/file.txt', 'folder/file.txt')
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
def test_sanitize_file(self):
|
||||
with patch('platform.system', return_value='Windows'):
|
||||
manager = OsManager()
|
||||
test_cases = [
|
||||
('file.txt', 'file.txt'),
|
||||
('filéš.txt', 'files.txt')
|
||||
]
|
||||
for input_name, expected in test_cases:
|
||||
with self.subTest(input_name=input_name):
|
||||
result = manager.get_sanitize_file(input_name)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_windows_paths(self):
|
||||
with patch('platform.system', return_value='Windows'):
|
||||
manager = OsManager()
|
||||
|
||||
# Test network paths (including IP)
|
||||
for input_path, expected in self.test_paths['windows']['network']:
|
||||
with self.subTest(input_path=input_path):
|
||||
result = manager.get_sanitize_path(input_path)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
# Test drive paths
|
||||
for input_path, expected in self.test_paths['windows']['drive']:
|
||||
with self.subTest(input_path=input_path):
|
||||
result = manager.get_sanitize_path(input_path)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_macos_paths(self):
|
||||
with patch('platform.system', return_value='Darwin'):
|
||||
manager = OsManager()
|
||||
|
||||
# Test absolute paths
|
||||
for input_path, expected in self.test_paths['darwin']['absolute']:
|
||||
with self.subTest(input_path=input_path):
|
||||
result = manager.get_sanitize_path(input_path)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
# Test relative paths
|
||||
for input_path, expected in self.test_paths['darwin']['relative']:
|
||||
with self.subTest(input_path=input_path):
|
||||
result = manager.get_sanitize_path(input_path)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_linux_paths(self):
|
||||
with patch('platform.system', return_value='Linux'):
|
||||
manager = OsManager()
|
||||
|
||||
for input_path, expected in self.test_paths['linux']['absolute']:
|
||||
with self.subTest(input_path=input_path):
|
||||
result = manager.get_sanitize_path(input_path)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_special_characters(self):
|
||||
with patch('platform.system', return_value='Windows'):
|
||||
manager = OsManager()
|
||||
special_cases = [
|
||||
('\\\\server\\share\\àèìòù\\file.txt', '\\\\server\\share\\aeiou\\file.txt'),
|
||||
('D:\\Test\\åäö\\file.txt', 'D:\\Test\\aao\\file.txt'),
|
||||
('\\\\192.168.1.100\\share\\tést\\file.txt', '\\\\192.168.1.100\\share\\test\\file.txt')
|
||||
]
|
||||
|
||||
for input_path, expected in special_cases:
|
||||
with self.subTest(input_path=input_path):
|
||||
result = manager.get_sanitize_path(input_path)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_network_paths_with_ip(self):
|
||||
with patch('platform.system', return_value='Windows'):
|
||||
manager = OsManager()
|
||||
ip_paths = [
|
||||
('\\\\192.168.1.100\\share\\folder', '\\\\192.168.1.100\\share\\folder'),
|
||||
('\\\\10.0.0.50\\public\\data.txt', '\\\\10.0.0.50\\public\\data.txt'),
|
||||
('\\\\172.16.254.1\\backup\\test.txt', '\\\\172.16.254.1\\backup\\test.txt'),
|
||||
('\\\\192.168.1.100\\share\\folder\\sub dir\\file.txt',
|
||||
'\\\\192.168.1.100\\share\\folder\\sub dir\\file.txt'),
|
||||
]
|
||||
|
||||
for input_path, expected in ip_paths:
|
||||
with self.subTest(input_path=input_path):
|
||||
result = manager.get_sanitize_path(input_path)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
@ -122,5 +122,5 @@ if __name__ == "__main__":
|
||||
domain_to_use, _ = search_domain(site_name=site_name, base_url=f"https://{site_name}.{original_domain}", get_first=True)
|
||||
|
||||
update_readme(alias, domain_to_use)
|
||||
print("------------------------------------")
|
||||
time.sleep(2)
|
||||
print("\n------------------------------------")
|
||||
time.sleep(1)
|
@ -61,8 +61,8 @@
|
||||
"streamingcommunity": {
|
||||
"domain": "ooo"
|
||||
},
|
||||
"altadefinizione": {
|
||||
"domain": "florist"
|
||||
"altadefinizionegratis": {
|
||||
"domain": "info"
|
||||
},
|
||||
"guardaserie": {
|
||||
"domain": "academy"
|
||||
@ -82,7 +82,7 @@
|
||||
"domain": "so"
|
||||
},
|
||||
"cb01new": {
|
||||
"domain": "lol"
|
||||
"domain": "video"
|
||||
},
|
||||
"1337xx": {
|
||||
"domain": "to"
|
||||
|
Loading…
x
Reference in New Issue
Block a user