mirror of
https://github.com/Arrowar/StreamingCommunity.git
synced 2025-06-05 02:55:25 +00:00
v2.0.0 - 2
This commit is contained in:
parent
983089c321
commit
001c3f09e8
15
.gitignore
vendored
15
.gitignore
vendored
@ -3,9 +3,6 @@ __pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
@ -22,11 +19,12 @@ share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
*.manifest
|
||||
*.spec
|
||||
setup.py
|
||||
MANIFEST.in
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
@ -36,15 +34,6 @@ pip-delete-this-directory.txt
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
|
@ -1,5 +1,8 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Spostarsi nella directory superiore rispetto a quella corrente
|
||||
cd "$(dirname "$0")/.." || exit 1
|
||||
|
||||
# Function to check if a command exists
|
||||
command_exists() {
|
||||
command -v "$1" > /dev/null 2>&1
|
@ -1,4 +1,8 @@
|
||||
@echo off
|
||||
|
||||
:: Spostarsi nella directory superiore rispetto a quella corrente
|
||||
cd ..
|
||||
|
||||
:: Check if the script is running as administrator
|
||||
net session >nul 2>&1
|
||||
if %errorlevel% neq 0 (
|
99
README.md
99
README.md
@ -1,35 +1,64 @@
|
||||
# StreamingCommunity Downloader
|
||||
<p align="center">
|
||||
<img src="https://i.ibb.co/PFnjvBc/immagine-2024-12-26-180318047.png" alt="Project Logo" width="700"/>
|
||||
</p>
|
||||
|
||||

|
||||
<p align="center">
|
||||
<a href="https://pypi.org/project/streamingcommunity">
|
||||
<img src="https://img.shields.io/pypi/v/streamingcommunity?logo=pypi&labelColor=555555&style=for-the-badge" alt="PyPI"/>
|
||||
</a>
|
||||
<a href="https://www.python.org">
|
||||
<img src="https://img.shields.io/badge/Python->=3.8-3776AB?style=for-the-badge&logo=python&logoColor=white" alt="Python"/>
|
||||
</a>
|
||||
<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>
|
||||
<a href="https://github.com/Lovi-0/StreamingCommunity/commits">
|
||||
<img src="https://img.shields.io/github/last-commit/Lovi-0/StreamingCommunity/main?label=&style=for-the-badge&display_timestamp=committer" alt="Last Commit"/>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
A versatile script designed to download films and series from various supported streaming platforms.
|
||||
|
||||
# 🤝 Join our Community
|
||||
|
||||
Chat, contribute, and have fun in our **Git_StreamingCommunity** Discord [Server](https://discord.com/invite/8vV68UGRc7)
|
||||
<p align="center">
|
||||
<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"/>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
# 📋 Table of Contents
|
||||
|
||||
- [Website available](#website-status)
|
||||
- [Installation](#installation)
|
||||
- [PyPI Installation](#1-pypi-installation)
|
||||
- [Automatic Installation](#2-automatic-installation)
|
||||
- [Manual Installation](#3-manual-installation)
|
||||
- [Win 7](https://github.com/Ghost6446/StreamingCommunity_api/wiki/Installation#win-7)
|
||||
- [Termux](https://github.com/Ghost6446/StreamingCommunity_api/wiki/Termux)
|
||||
- [Configuration](#configuration)
|
||||
- [Default](#default-settings)
|
||||
- [Request](#requests-settings)
|
||||
- [Download](#m3u8_download-settings)
|
||||
- [Parser](#m3u8_parser-settings)
|
||||
- [Docker](#docker)
|
||||
- [Tutorial](#tutorials)
|
||||
- [To Do](#to-do)
|
||||
- [Support](#support)
|
||||
- [Contribute](#contributing)
|
||||
- [Disclamer](#disclaimer)
|
||||
|
||||
|
||||
- 🌐 [Website available](#website-status)
|
||||
- 🛠️ [Installation](#installation)
|
||||
- 📦 [PyPI Installation](#1-pypi-installation)
|
||||
- 🔄 [Automatic Installation](#2-automatic-installation)
|
||||
- 📝 [Manual Installation](#3-manual-installation)
|
||||
- 💻 [Win 7](https://github.com/Ghost6446/StreamingCommunity_api/wiki/Installation#win-7)
|
||||
- 📱 [Termux](https://github.com/Ghost6446/StreamingCommunity_api/wiki/Termux)
|
||||
- ⚙️ [Configuration](#configuration)
|
||||
- 🔧 [Default](#default-settings)
|
||||
- 📩 [Request](#requests-settings)
|
||||
- 📥 [Download](#m3u8_download-settings)
|
||||
- 🔍 [Parser](#m3u8_parser-settings)
|
||||
- 🐳 [Docker](#docker)
|
||||
- 🎓 [Tutorial](#tutorials)
|
||||
- 📝 [To do](#to-do)
|
||||
- 💬 [Support](#support)
|
||||
- 🤝 [Contribute](#contributing)
|
||||
- ⚠️ [Disclaimer](#disclaimer)
|
||||
- ⚡ [Contributors](#contributors)
|
||||
|
||||
# Installation
|
||||
|
||||
@ -83,13 +112,13 @@ pip install --upgrade StreamingCommunity
|
||||
#### On Windows:
|
||||
|
||||
```powershell
|
||||
.\win_install.bat
|
||||
.\Installer\win_install.bat
|
||||
```
|
||||
|
||||
#### On Linux/MacOS/BSD:
|
||||
|
||||
```bash
|
||||
sudo chmod +x unix_install.sh && ./unix_install.sh
|
||||
sudo chmod +x Installer/unix_install.sh && ./Installer/unix_install.sh
|
||||
```
|
||||
|
||||
### Usage
|
||||
@ -378,12 +407,6 @@ The `run-container` command mounts also the `config.json` file, so any change to
|
||||
|
||||
- Create website API -> https://github.com/Lovi-0/StreamingCommunity/tree/test_gui_1
|
||||
|
||||
# Support
|
||||
|
||||
If you'd like to support this project, consider making a donation!
|
||||
|
||||
[](https://www.paypal.com/donate/?hosted_button_id=UXTWMT8P6HE2C)
|
||||
|
||||
# Contributing
|
||||
|
||||
Contributions are welcome! Steps:
|
||||
@ -397,3 +420,9 @@ Contributions are welcome! Steps:
|
||||
# Disclaimer
|
||||
|
||||
This software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and noninfringement. In no event shall the authors or copyright holders be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the software or the use or other dealings in the software.
|
||||
|
||||
## Contributors
|
||||
|
||||
<a href="https://github.com/Lovi-0/StreamingCommunity/graphs/contributors" alt="View Contributors">
|
||||
<img src="https://contrib.rocks/image?repo=Lovi-0/StreamingCommunity&max=1000&columns=10" alt="Contributors" />
|
||||
</a>
|
||||
|
@ -61,6 +61,10 @@ def download_film(movie_details: Json_film) -> str:
|
||||
logging.error(f"Not found in the server. Dict: {movie_details}")
|
||||
raise
|
||||
|
||||
if "not found" in str(response.text):
|
||||
logging.error(f"Cant find in the server, Element: {movie_details}")
|
||||
raise
|
||||
|
||||
# Extract supervideo url
|
||||
soup = BeautifulSoup(response.text, "html.parser")
|
||||
player_links = soup.find("ul", class_ = "_player-mirrors").find_all("li")
|
||||
|
@ -76,13 +76,14 @@ def get_version_and_domain():
|
||||
|
||||
# Extract version from the response
|
||||
try:
|
||||
version = get_version(httpx.get(
|
||||
url=base_url,
|
||||
headers={
|
||||
'user-agent': get_headers()
|
||||
},
|
||||
timeout=max_timeout
|
||||
).text)
|
||||
version = get_version(
|
||||
httpx.get(
|
||||
url=base_url,
|
||||
headers={'User-Agent': get_headers()},
|
||||
timeout=max_timeout
|
||||
).text
|
||||
)
|
||||
|
||||
except:
|
||||
console.print("[green]Auto generate version ...")
|
||||
version = secrets.token_hex(32 // 2)
|
||||
|
@ -87,7 +87,6 @@ def search_domain(site_name: str, base_url: str):
|
||||
Returns:
|
||||
tuple: The found domain and the complete URL.
|
||||
"""
|
||||
|
||||
# Extract config domain
|
||||
max_timeout = config_manager.get_int("REQUESTS", "timeout")
|
||||
domain = str(config_manager.get_dict("SITE", site_name)['domain'])
|
||||
@ -102,7 +101,6 @@ def search_domain(site_name: str, base_url: str):
|
||||
},
|
||||
follow_redirects=True,
|
||||
timeout=max_timeout
|
||||
|
||||
) as client:
|
||||
response_follow = client.get(f"{base_url}.{domain}")
|
||||
response_follow.raise_for_status()
|
||||
@ -111,51 +109,57 @@ def search_domain(site_name: str, base_url: str):
|
||||
query = base_url.split("/")[-1]
|
||||
|
||||
# Perform a Google search with multiple results
|
||||
search_results = list(search(query, num_results=5))
|
||||
#console.print(f"[green]Google search results[white]: {search_results}")
|
||||
search_results = list(search(query, num_results=10, lang="it"))
|
||||
console.print(f"\nGoogle search results: {search_results}")
|
||||
|
||||
def normalize_for_comparison(url):
|
||||
"""Normalize URL by removing protocol, www, and trailing slashes"""
|
||||
url = url.lower()
|
||||
url = url.replace("https://", "").replace("http://", "")
|
||||
url = url.replace("www.", "")
|
||||
return url.rstrip("/")
|
||||
|
||||
# Normalize the base_url we're looking for
|
||||
target_url = normalize_for_comparison(base_url)
|
||||
|
||||
# Iterate through search results
|
||||
for first_url in search_results:
|
||||
console.print(f"[green]Checking url[white]: [red]{first_url}")
|
||||
|
||||
# Check if the base URL matches the Google search result
|
||||
parsed_first_url = urlparse(first_url)
|
||||
|
||||
# Compare base url from google search with base url from config.json
|
||||
if parsed_first_url.netloc.split(".")[0] == base_url:
|
||||
console.print(f"[red]URL does not match base URL. Skipping.[/red]")
|
||||
continue
|
||||
|
||||
try:
|
||||
final_url = get_final_redirect_url(first_url, max_timeout)
|
||||
|
||||
if final_url is not None:
|
||||
|
||||
def extract_domain(url):
|
||||
parsed_url = urlparse(url)
|
||||
domain = parsed_url.netloc
|
||||
return domain.split(".")[-1]
|
||||
|
||||
new_domain_extract = extract_domain(str(final_url))
|
||||
|
||||
if msg.ask(f"[cyan]\nDo you want to auto site[white]: [red]{site_name}[cyan] with domain[white]: [red]{new_domain_extract}", choices=["y", "n"], default="y").lower() == "y":
|
||||
|
||||
# Update domain in config.json
|
||||
config_manager.config['SITE'][site_name]['domain'] = new_domain_extract
|
||||
config_manager.write_config()
|
||||
|
||||
# Return config domain
|
||||
return new_domain_extract, f"{base_url}.{new_domain_extract}"
|
||||
# Get just the domain part of the search result
|
||||
parsed_result = urlparse(first_url)
|
||||
result_domain = normalize_for_comparison(parsed_result.netloc)
|
||||
|
||||
except Exception as redirect_error:
|
||||
console.print(f"[red]Error following redirect for {first_url}: {redirect_error}")
|
||||
continue
|
||||
# Compare with our target URL (without the protocol part)
|
||||
if result_domain.startswith(target_url.split("/")[-1]):
|
||||
try:
|
||||
final_url = get_final_redirect_url(first_url, max_timeout)
|
||||
|
||||
if final_url is not None:
|
||||
def extract_domain(url):
|
||||
parsed_url = urlparse(url)
|
||||
domain = parsed_url.netloc
|
||||
return domain.split(".")[-1]
|
||||
|
||||
new_domain_extract = extract_domain(str(final_url))
|
||||
|
||||
if msg.ask(f"\n[cyan]Do you want to auto update site[white] [red]'{site_name}'[cyan] with domain[white] [red]'{new_domain_extract}'.", choices=["y", "n"], default="y").lower() == "y":
|
||||
|
||||
# Update domain in config.json
|
||||
config_manager.config['SITE'][site_name]['domain'] = new_domain_extract
|
||||
config_manager.write_config()
|
||||
|
||||
return new_domain_extract, f"{base_url}.{new_domain_extract}"
|
||||
|
||||
except Exception as redirect_error:
|
||||
console.print(f"[red]Error following redirect for {first_url}: {redirect_error}")
|
||||
continue
|
||||
|
||||
# If no matching URL is found
|
||||
console.print("[bold red]No valid URL found matching the base URL.[/bold red]")
|
||||
raise Exception("No matching domain found")
|
||||
|
||||
# Ensure the URL is in string format before parsing
|
||||
# Handle successful initial domain check
|
||||
parsed_url = urlparse(str(response_follow.url))
|
||||
parse_domain = parsed_url.netloc
|
||||
tld = parse_domain.split('.')[-1]
|
||||
|
@ -62,7 +62,7 @@ def join_video(video_path: str, out_path: str, codec: M3U8_Codec = None):
|
||||
|
||||
# Add mpegts to force to detect input file as ts file
|
||||
if need_to_force_to_ts(video_path):
|
||||
console.log("[red]Force input file to 'mpegts'.")
|
||||
#console.log("[red]Force input file to 'mpegts'.")
|
||||
ffmpeg_cmd.extend(['-f', 'mpegts'])
|
||||
vcodec = "libx264"
|
||||
|
||||
|
@ -74,8 +74,8 @@ def get_video_duration(file_path: str) -> float:
|
||||
# Extract duration from the video information
|
||||
try:
|
||||
return float(probe_result['format']['duration'])
|
||||
|
||||
except:
|
||||
logging.error("Cant get duration.")
|
||||
return 1
|
||||
|
||||
except Exception as e:
|
||||
@ -207,6 +207,8 @@ def is_png_format_or_codec(file_info):
|
||||
"""
|
||||
if not file_info:
|
||||
return False
|
||||
|
||||
console.print(f"[yellow][FFmpeg] [cyan]Avaiable codec[white]: [red]{file_info['codec_names']}")
|
||||
return file_info['format_name'] == 'png_pipe' or 'png' in file_info['codec_names']
|
||||
|
||||
|
||||
|
@ -17,7 +17,7 @@ crypto_installed = crypto_spec is not None
|
||||
|
||||
|
||||
if crypto_installed:
|
||||
console.print("[cyan]Decrypy use: Cryptodome")
|
||||
console.print("[cyan]Decrypy use: Cryptodomex")
|
||||
from Cryptodome.Cipher import AES
|
||||
from Cryptodome.Util.Padding import unpad
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
__title__ = 'StreamingCommunity'
|
||||
__version__ = '1.9.8'
|
||||
__version__ = '2.0.0'
|
||||
__author__ = 'Lovi-0'
|
||||
__description__ = 'A command-line program to download film'
|
||||
__copyright__ = 'Copyright 2024'
|
||||
|
@ -3,44 +3,62 @@
|
||||
import os
|
||||
import platform
|
||||
|
||||
|
||||
# Internal utilities
|
||||
from StreamingCommunity.Util.console import console
|
||||
from StreamingCommunity.Util._jsonConfig import config_manager
|
||||
|
||||
|
||||
# Variable
|
||||
CLEAN = config_manager.get_bool('DEFAULT', 'clean_console')
|
||||
SHOW = config_manager.get_bool('DEFAULT', 'show_message')
|
||||
|
||||
|
||||
def create_italian_flag_colored_text(text: str) -> str:
|
||||
"""Create text divided into three sections with Italian flag colors, splitting each line at two spaces."""
|
||||
|
||||
# Split the text into lines
|
||||
lines = text.splitlines()
|
||||
|
||||
colored_lines = []
|
||||
|
||||
for line in lines:
|
||||
# Split each line into parts using two spaces as a delimiter
|
||||
parts = line.split(" ")
|
||||
|
||||
# Ensure there are exactly 3 parts (add empty strings if necessary)
|
||||
parts += [''] * (4 - len(parts))
|
||||
|
||||
# Apply flag colors to the parts
|
||||
green_part = f"[green]{parts[0]}[/]"
|
||||
white_part = f"[white]{parts[1]}[/]"
|
||||
red_part = f"[red]{parts[2]}[/]"
|
||||
|
||||
# Reassemble the colored line
|
||||
colored_line = green_part + white_part + red_part
|
||||
colored_lines.append(colored_line)
|
||||
|
||||
# Join all colored lines back into a single string
|
||||
return "\n".join(colored_lines)
|
||||
|
||||
def start_message():
|
||||
"""
|
||||
Display a start message.
|
||||
"""
|
||||
|
||||
"""Display a stylized start message in the console."""
|
||||
|
||||
msg = r'''
|
||||
██╗ ██████╗ ██╗ ██╗██╗ ██╗ ██╗ ███████╗████████╗██████╗ ███████╗ █████╗ ███╗ ███╗██╗███╗ ██╗ ██████╗
|
||||
██║ ██╔═══██╗██║ ██║██║ ╚██╗██╔╝ ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██╔══██╗████╗ ████║██║████╗ ██║██╔════╝
|
||||
██║ ██║ ██║██║ ██║██║ ╚███╔╝ ███████╗ ██║ ██████╔╝█████╗ ███████║██╔████╔██║██║██╔██╗ ██║██║ ███╗
|
||||
██║ ██║ ██║╚██╗ ██╔╝██║ ██╔██╗ ╚════██║ ██║ ██╔══██╗██╔══╝ ██╔══██║██║╚██╔╝██║██║██║╚██╗██║██║ ██║
|
||||
███████╗╚██████╔╝ ╚████╔╝ ██║ ██╔╝ ██╗ ███████║ ██║ ██║ ██║███████╗██║ ██║██║ ╚═╝ ██║██║██║ ╚████║╚██████╔╝
|
||||
╚══════╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝ ╚═╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝ ╚═════╝
|
||||
'''.rstrip()
|
||||
|
||||
_____ _ _ _____ _ _
|
||||
/ ____| | (_) / ____| (_) |
|
||||
| (___ | |_ _ __ ___ __ _ _ __ ___ _ _ __ __ _ | | ___ _ __ ___ _ __ ___ _ _ _ __ _| |_ _ _
|
||||
\___ \| __| '__/ _ \/ _` | '_ ` _ \| | '_ \ / _` | | | / _ \| '_ ` _ \| '_ ` _ \| | | | '_ \| | __| | | |
|
||||
____) | |_| | | __/ (_| | | | | | | | | | | (_| | | |___| (_) | | | | | | | | | | | |_| | | | | | |_| |_| |
|
||||
|_____/ \__|_| \___|\__,_|_| |_| |_|_|_| |_|\__, | \_____\___/|_| |_| |_|_| |_| |_|\__,_|_| |_|_|\__|\__, |
|
||||
__/ | __/ |
|
||||
|___/ |___/
|
||||
|
||||
'''
|
||||
colored_msg = create_italian_flag_colored_text(msg)
|
||||
|
||||
if CLEAN:
|
||||
if platform.system() == 'Windows':
|
||||
os.system("cls")
|
||||
else:
|
||||
os.system("clear")
|
||||
os.system("cls" if platform.system() == 'Windows' else "clear")
|
||||
|
||||
if SHOW:
|
||||
console.print(f"[bold yellow]{msg}")
|
||||
console.print(f"[magenta]Created by: Lovi\n")
|
||||
console.print(colored_msg)
|
||||
|
||||
row = "-" * console.width
|
||||
console.print(f"[yellow]{row} \n")
|
||||
# Print a decorative separator line using asterisks
|
||||
separator = "_" * (console.width - 2) # Ridotto di 2 per il padding
|
||||
console.print(f"[yellow]{separator}[/yellow]\n")
|
||||
|
@ -27,28 +27,28 @@ 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': []
|
||||
}
|
||||
'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': []
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -66,6 +66,27 @@ class OsManager:
|
||||
|
||||
raise ValueError(f"Unsupported operating system: {system}")
|
||||
|
||||
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':
|
||||
return path
|
||||
|
||||
# Check if path starts with a drive letter
|
||||
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.
|
||||
@ -76,8 +97,6 @@ class OsManager:
|
||||
Returns:
|
||||
str: Processed filename.
|
||||
"""
|
||||
# Preserve file extension
|
||||
logging.info("_process_filename: ", filename)
|
||||
name, ext = os.path.splitext(filename)
|
||||
|
||||
# Handle length restrictions
|
||||
@ -118,7 +137,6 @@ class OsManager:
|
||||
Returns:
|
||||
str: Sanitized filename.
|
||||
"""
|
||||
logging.info("get_sanitize_file: ", filename)
|
||||
|
||||
# Decode unicode characters and sanitize
|
||||
decoded_filename = unidecode.unidecode(filename)
|
||||
@ -129,8 +147,8 @@ class OsManager:
|
||||
if len(name) > self.config['max_length']:
|
||||
name = self._truncate_filename(name)
|
||||
|
||||
logging.info("return :", name + ext)
|
||||
return name + ext
|
||||
result = name + ext
|
||||
return result
|
||||
|
||||
def get_sanitize_path(self, path: str) -> str:
|
||||
"""
|
||||
@ -142,7 +160,9 @@ class OsManager:
|
||||
Returns:
|
||||
str: Sanitized folder path.
|
||||
"""
|
||||
logging.info("get_sanitize_file: ", path)
|
||||
|
||||
# Normalize path for Windows drive letters first
|
||||
path = self._normalize_windows_path(path)
|
||||
|
||||
# Decode unicode characters and sanitize
|
||||
decoded_path = unidecode.unidecode(path)
|
||||
@ -150,16 +170,26 @@ class OsManager:
|
||||
|
||||
# Split path and process each component
|
||||
path_components = os.path.normpath(sanitized_path).split(os.sep)
|
||||
processed_components = []
|
||||
|
||||
for component in path_components:
|
||||
# Truncate component if necessary
|
||||
if len(component) > self.config['max_length']:
|
||||
component = self._truncate_filename(component)
|
||||
processed_components.append(component)
|
||||
# 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]
|
||||
|
||||
logging.info("return :", os.path.join(*processed_components))
|
||||
return os.path.join(*processed_components)
|
||||
else:
|
||||
processed_components = []
|
||||
|
||||
# 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:
|
||||
"""
|
||||
@ -173,10 +203,7 @@ class OsManager:
|
||||
bool: True if path created successfully, False otherwise.
|
||||
"""
|
||||
try:
|
||||
# Sanitize path first
|
||||
sanitized_path = self.get_sanitize_path(path)
|
||||
|
||||
# Create directory with recursive option
|
||||
os.makedirs(sanitized_path, mode=mode, exist_ok=True)
|
||||
return True
|
||||
|
||||
@ -197,6 +224,7 @@ class OsManager:
|
||||
try:
|
||||
shutil.rmtree(folder_path)
|
||||
return True
|
||||
|
||||
except OSError as e:
|
||||
logging.error(f"Folder removal error: {e}")
|
||||
return False
|
||||
@ -238,18 +266,12 @@ class OsManager:
|
||||
"""
|
||||
try:
|
||||
logging.info(f"Check if file exists: {file_path}")
|
||||
if os.path.exists(file_path):
|
||||
logging.info(f"The file '{file_path}' exists.")
|
||||
return True
|
||||
|
||||
else:
|
||||
return False
|
||||
return os.path.exists(file_path)
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"An error occurred while checking file existence: {e}")
|
||||
return False
|
||||
|
||||
|
||||
class InternManager():
|
||||
|
||||
def format_file_size(self, size_bytes: float) -> str:
|
||||
@ -296,7 +318,7 @@ class InternManager():
|
||||
while True:
|
||||
try:
|
||||
httpx.get("https://www.google.com")
|
||||
console.log("[bold green]Internet is available![/bold green]")
|
||||
#console.log("[bold green]Internet is available![/bold green]")
|
||||
break
|
||||
|
||||
except urllib.error.URLError:
|
||||
@ -374,21 +396,20 @@ class OsSummary:
|
||||
try:
|
||||
import requests
|
||||
|
||||
console.print(f"{filename} not found locally. Downloading from {url}...", style="bold yellow")
|
||||
logging.info(f"{filename} not found locally. Downloading from {url}...")
|
||||
response = requests.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)
|
||||
logging.error(f"Failed to download {filename}. HTTP Status code: {response.status_code}")
|
||||
sys.exit(0)
|
||||
|
||||
except Exception as e:
|
||||
console.print(f"Failed to download {filename}: {e}", style="bold red")
|
||||
sys.exit(1)
|
||||
logging.error(f"Failed to download {filename}: {e}")
|
||||
sys.exit(0)
|
||||
|
||||
def install_library(self, lib_name: str):
|
||||
"""
|
||||
|
@ -83,7 +83,57 @@ class TVShowManager:
|
||||
row_data = [entry.get(col_name, '') for col_name in self.column_info.keys()]
|
||||
table.add_row(*row_data)
|
||||
|
||||
self.console.print(table) # Use self.console.print instead of print
|
||||
self.console.print(table)
|
||||
|
||||
def run_back_command(self, research_func: dict):
|
||||
"""
|
||||
Executes a back-end search command by dynamically importing a module and invoking its search function.
|
||||
|
||||
Args:
|
||||
research_func (dict): A dictionary containing:
|
||||
- 'folder' (str): The absolute path to the directory containing the module to be executed.
|
||||
"""
|
||||
try:
|
||||
|
||||
# Get site name from folder
|
||||
site_name = (os.path.basename(research_func['folder']))
|
||||
|
||||
# Find the project root directory
|
||||
current_path = research_func['folder']
|
||||
while not os.path.exists(os.path.join(current_path, 'StreamingCommunity')):
|
||||
current_path = os.path.dirname(current_path)
|
||||
|
||||
# Add project root to Python path
|
||||
project_root = current_path
|
||||
#print(f"[DEBUG] Project Root: {project_root}")
|
||||
|
||||
if project_root not in sys.path:
|
||||
sys.path.insert(0, project_root)
|
||||
|
||||
# Import using full absolute import
|
||||
module_path = f'StreamingCommunity.Api.Site.{site_name}'
|
||||
#print(f"[DEBUG] Importing module: {module_path}")
|
||||
|
||||
# Import the module
|
||||
module = importlib.import_module(module_path)
|
||||
|
||||
# Get the search function
|
||||
search_func = getattr(module, 'search')
|
||||
|
||||
# Call the search function with the search string
|
||||
search_func(None)
|
||||
|
||||
except Exception as e:
|
||||
self.console.print(f"[red]Error during search: {e}")
|
||||
|
||||
# Print detailed traceback
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
# Optionally remove the path if you want to clean up
|
||||
if project_root in sys.path:
|
||||
sys.path.remove(project_root)
|
||||
|
||||
|
||||
def run(self, force_int_input: bool = False, max_int_input: int = 0) -> str:
|
||||
"""
|
||||
@ -114,7 +164,7 @@ class TVShowManager:
|
||||
|
||||
# Handling user input for loading more items or quitting
|
||||
if self.slice_end < total_items:
|
||||
self.console.print(f"\n\n[yellow][INFO] [green]Press [red]Enter [green]for next page, [red]'q' [green]to quit, or [red]'back' [green]to search.")
|
||||
self.console.print(f"\n[green]Press [red]Enter [green]for next page, [red]'q' [green]to quit, or [red]'back' [green]to search.")
|
||||
|
||||
if not force_int_input:
|
||||
key = Prompt.ask(
|
||||
@ -139,49 +189,14 @@ class TVShowManager:
|
||||
self.slice_end = total_items
|
||||
|
||||
elif key.lower() == "back" and research_func:
|
||||
try:
|
||||
# Find the project root directory
|
||||
current_path = research_func['folder']
|
||||
while not os.path.exists(os.path.join(current_path, 'StreamingCommunity')):
|
||||
current_path = os.path.dirname(current_path)
|
||||
|
||||
# Add project root to Python path
|
||||
project_root = current_path
|
||||
#print(f"[DEBUG] Project Root: {project_root}")
|
||||
|
||||
if project_root not in sys.path:
|
||||
sys.path.insert(0, project_root)
|
||||
|
||||
# Import using full absolute import
|
||||
module_path = 'StreamingCommunity.Api.Site.streamingcommunity'
|
||||
#print(f"[DEBUG] Importing module: {module_path}")
|
||||
|
||||
# Import the module
|
||||
module = importlib.import_module(module_path)
|
||||
|
||||
# Get the search function
|
||||
search_func = getattr(module, 'media_search_manager')
|
||||
|
||||
# Call the search function with the search string
|
||||
search_func(None)
|
||||
|
||||
except Exception as e:
|
||||
self.console.print(f"[red]Error during search: {e}")
|
||||
|
||||
# Print detailed traceback
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
# Optionally remove the path if you want to clean up
|
||||
if project_root in sys.path:
|
||||
sys.path.remove(project_root)
|
||||
self.run_back_command(research_func)
|
||||
|
||||
else:
|
||||
break
|
||||
|
||||
else:
|
||||
# Last slice, ensure all remaining items are shown
|
||||
self.console.print(f"\n\n[yellow][INFO] [green]You've reached the end. [red]Enter [green]for first page, [red]'q' [green]to quit, or [red]'back' [green]to search.")
|
||||
self.console.print(f"\n [green]You've reached the end. [red]Enter [green]for first page, [red]'q' [green]to quit, or [red]'back' [green]to search.")
|
||||
if not force_int_input:
|
||||
key = Prompt.ask(
|
||||
"\n[cyan]Insert media index [yellow](e.g., 1), [red]* [cyan]to download all media, "
|
||||
@ -203,42 +218,7 @@ class TVShowManager:
|
||||
self.slice_end = self.step
|
||||
|
||||
elif key.lower() == "back" and research_func:
|
||||
try:
|
||||
# Find the project root directory
|
||||
current_path = research_func['folder']
|
||||
while not os.path.exists(os.path.join(current_path, 'StreamingCommunity')):
|
||||
current_path = os.path.dirname(current_path)
|
||||
|
||||
# Add project root to Python path
|
||||
project_root = current_path
|
||||
#print(f"[DEBUG] Project Root: {project_root}")
|
||||
|
||||
if project_root not in sys.path:
|
||||
sys.path.insert(0, project_root)
|
||||
|
||||
# Import using full absolute import
|
||||
module_path = 'StreamingCommunity.Api.Site.streamingcommunity'
|
||||
#print(f"[DEBUG] Importing module: {module_path}")
|
||||
|
||||
# Import the module
|
||||
module = importlib.import_module(module_path)
|
||||
|
||||
# Get the search function
|
||||
search_func = getattr(module, 'search')
|
||||
|
||||
# Call the search function with the search string
|
||||
search_func(None)
|
||||
|
||||
except Exception as e:
|
||||
self.console.print(f"[red]Error during search: {e}")
|
||||
|
||||
# Print detailed traceback
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
# Optionally remove the path if you want to clean up
|
||||
if project_root in sys.path:
|
||||
sys.path.remove(project_root)
|
||||
self.run_back_command(research_func)
|
||||
|
||||
else:
|
||||
break
|
||||
|
0
StreamingCommunity/__init__.py
Normal file
0
StreamingCommunity/__init__.py
Normal file
@ -26,11 +26,11 @@
|
||||
"M3U8_DOWNLOAD": {
|
||||
"tqdm_delay": 0.01,
|
||||
"tqdm_use_large_bar": true,
|
||||
"default_video_workser": 12,
|
||||
"default_audio_workser": 12,
|
||||
"download_video": true,
|
||||
"download_audio": true,
|
||||
"merge_audio": true,
|
||||
"default_video_workser": 12,
|
||||
"default_audio_workser": 12,
|
||||
"specific_list_audio": [
|
||||
"ita"
|
||||
],
|
||||
@ -56,10 +56,10 @@
|
||||
},
|
||||
"SITE": {
|
||||
"streamingcommunity": {
|
||||
"domain": "family"
|
||||
"domain": "prof"
|
||||
},
|
||||
"altadefinizione": {
|
||||
"domain": "now"
|
||||
"domain": "deal"
|
||||
},
|
||||
"guardaserie": {
|
||||
"domain": "academy"
|
||||
|
Loading…
x
Reference in New Issue
Block a user