This commit is contained in:
Lovi 2025-01-04 10:40:08 +01:00
parent b40d202255
commit d03ece1cf2
33 changed files with 216 additions and 159 deletions

View File

@ -214,12 +214,14 @@ The configuration file is divided into several main sections:
"movie_folder_name": "Movie", "movie_folder_name": "Movie",
"serie_folder_name": "TV", "serie_folder_name": "TV",
"map_episode_name": "%(tv_name)_S%(season)E%(episode)_%(episode_name)", "map_episode_name": "%(tv_name)_S%(season)E%(episode)_%(episode_name)",
"add_siteName": false,
"disable_searchDomain": false,
"not_close": false "not_close": false
} }
``` ```
- `root_path`: Directory where all videos will be saved - `root_path`: Directory where all videos will be saved
### Path examples: ### Path examples:
* Windows: `C:\\MyLibrary\\Folder` or `\\\\MyServer\\MyLibrary` (if you want to use a network folder) * Windows: `C:\\MyLibrary\\Folder` or `\\\\MyServer\\MyLibrary` (if you want to use a network folder)
* Linux/MacOS: `Desktop/MyLibrary/Folder` * Linux/MacOS: `Desktop/MyLibrary/Folder`
@ -241,7 +243,10 @@ The configuration file is divided into several main sections:
* `%(episode_name)` : Is the name of the episode * `%(episode_name)` : Is the name of the episode
`<br/><br/>` `<br/><br/>`
- `not_close`: If true, continues running after downloading - `add_siteName`: If set to true, appends the site_name to the root path before the movie and serie folders.
- `disable_searchDomain`: If set to true, disables the search for a new domain for all sites.
- `not_close`: If set to true, keeps the program running after the download is complete.
### qBittorrent Configuration ### qBittorrent Configuration
@ -258,7 +263,6 @@ The configuration file is divided into several main sections:
To enable qBittorrent integration, follow the setup guide [here](https://github.com/lgallard/qBittorrent-Controller/wiki/How-to-enable-the-qBittorrent-Web-UI). To enable qBittorrent integration, follow the setup guide [here](https://github.com/lgallard/qBittorrent-Controller/wiki/How-to-enable-the-qBittorrent-Web-UI).
<br>
## REQUESTS Settings ## REQUESTS Settings
@ -272,7 +276,6 @@ The configuration file is divided into several main sections:
- `timeout`: Maximum timeout (in seconds) for each request - `timeout`: Maximum timeout (in seconds) for each request
- `max_retry`: Number of retry attempts per segment during M3U8 index download - `max_retry`: Number of retry attempts per segment during M3U8 index download
<br>
## M3U8_DOWNLOAD Settings ## M3U8_DOWNLOAD Settings
@ -293,10 +296,9 @@ The configuration file is divided into several main sections:
- `cleanup_tmp_folder`: Remove temporary .ts files after download - `cleanup_tmp_folder`: Remove temporary .ts files after download
> [!IMPORTANT] > [!IMPORTANT]
> Set `tqdm_use_large_bar` to `false` when using Termux or terminals with limited width to prevent display issues > Set `tqdm_use_large_bar` to `false` when using Termux or terminals with limited width to prevent network monitoring issues
<br>
### Language Settings ### Language Settings
@ -345,7 +347,6 @@ forced-ita hin - Hindi pol - Polish tur - Turkish
- `force_resolution`: Force specific resolution (-1 for best available, or specify 1080, 720, 360) - `force_resolution`: Force specific resolution (-1 for best available, or specify 1080, 720, 360)
- `get_only_link`: Return M3U8 playlist/index URL instead of downloading - `get_only_link`: Return M3U8 playlist/index URL instead of downloading
<br>
# COMMAND # COMMAND
@ -361,7 +362,6 @@ forced-ita hin - Hindi pol - Polish tur - Turkish
- Enter a season number followed by `-*` to download from that season to the end. - Enter a season number followed by `-*` to download from that season to the end.
* **Example:** `3-*` will download from *Season 3* to the final season. * **Example:** `3-*` will download from *Season 3* to the final season.
<br>
# Docker # Docker

View File

@ -5,20 +5,14 @@ from typing import Dict, Any, List, Union
class Episode: class Episode:
def __init__(self, data: Dict[str, Any]): def __init__(self, data: Dict[str, Any]):
self.images = None
self.data = data self.data = data
self.id: int = data.get('id') self.id: int = data.get('id', 0)
self.scws_id: int = data.get('scws_id') self.scws_id: int = data.get('scws_id', 0)
self.number: int = data.get('number') self.number: int = data.get('number', 1)
self.name: str = data.get('name') self.name: str = data.get('name', '')
self.plot: str = data.get('plot') self.plot: str = data.get('plot', '')
self.duration: int = data.get('duration') self.duration: int = data.get('duration', 0)
def collect_image(self, SITE_NAME, domain):
self.image = None
if len(self.data.get('images')) > 0:
self.image = f"https://cdn.{SITE_NAME}.{domain}/images/{self.data.get('images')[0]['filename']}"
def __str__(self): def __str__(self):
return f"Episode(id={self.id}, number={self.number}, name='{self.name}', plot='{self.plot}', duration={self.duration} sec)" return f"Episode(id={self.id}, number={self.number}, name='{self.name}', plot='{self.plot}', duration={self.duration} sec)"
@ -73,24 +67,19 @@ class EpisodeManager:
class Season: class Season:
def __init__(self, season_data: Dict[str, Union[int, str, None]]): def __init__(self, season_data: Dict[str, Union[int, str, None]]):
self.images = {}
self.season_data = season_data self.season_data = season_data
self.id: int = season_data.get('id') self.id: int = season_data.get('id', 0)
self.scws_id: int = season_data.get('scws_id') self.scws_id: int = season_data.get('scws_id', 0)
self.imdb_id: int = season_data.get('imdb_id') self.imdb_id: int = season_data.get('imdb_id', 0)
self.number: int = season_data.get('number') self.number: int = season_data.get('number', 0)
self.name: str = season_data.get('name') self.name: str = season_data.get('name', '')
self.slug: str = season_data.get('slug') self.slug: str = season_data.get('slug', '')
self.plot: str = season_data.get('plot') self.plot: str = season_data.get('plot', '')
self.type: str = season_data.get('type') self.type: str = season_data.get('type', '')
self.seasons_count: int = season_data.get('seasons_count') self.seasons_count: int = season_data.get('seasons_count', 0)
self.episodes: EpisodeManager = EpisodeManager() self.episodes: EpisodeManager = EpisodeManager()
def collect_images(self, SITE_NAME, domain):
for dict_image in self.season_data.get('images'):
self.images[dict_image.get('type')] = f"https://cdn.{SITE_NAME}.{domain}/images/{dict_image.get('filename')}"
class Stream: class Stream:
def __init__(self, name: str, url: str, active: bool): def __init__(self, name: str, url: str, active: bool):

View File

@ -19,9 +19,11 @@ from StreamingCommunity.Api.Template.Class.SearchType import MediaManager
# Variable # Variable
from .costant import SITE_NAME from .costant import SITE_NAME, DOMAIN_NOW
media_search_manager = MediaManager() media_search_manager = MediaManager()
table_show_manager = TVShowManager() table_show_manager = TVShowManager()
max_timeout = config_manager.get_int("REQUESTS", "timeout")
disable_searchDomain = config_manager.get_bool("DEFAULT", "disable_searchDomain")
def title_search(word_to_search: str) -> int: def title_search(word_to_search: str) -> int:
@ -38,8 +40,10 @@ def title_search(word_to_search: str) -> int:
table_show_manager.clear() table_show_manager.clear()
# Find new domain if prev dont work # Find new domain if prev dont work
max_timeout = config_manager.get_int("REQUESTS", "timeout") domain_to_use = DOMAIN_NOW
domain_to_use, _ = search_domain(SITE_NAME, f"https://{SITE_NAME}")
if not disable_searchDomain:
domain_to_use, base_url = search_domain(SITE_NAME, f"https://{SITE_NAME}")
# Construct the full site URL and load the search page # Construct the full site URL and load the search page
try: try:

View File

@ -21,7 +21,7 @@ from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
# Config # Config
from .costant import ROOT_PATH, DOMAIN_NOW, SITE_NAME, MOVIE_FOLDER from .costant import DOMAIN_NOW, SITE_NAME, MOVIE_FOLDER
def download_title(select_title: MediaItem): def download_title(select_title: MediaItem):
@ -39,7 +39,7 @@ def download_title(select_title: MediaItem):
# Define output path # Define output path
title_name = os_manager.get_sanitize_file(select_title.name) title_name = os_manager.get_sanitize_file(select_title.name)
mp4_path = os_manager.get_sanitize_path( mp4_path = os_manager.get_sanitize_path(
os.path.join(ROOT_PATH, MOVIE_FOLDER, title_name.replace(".mp4", "")) os.path.join(MOVIE_FOLDER, title_name.replace(".mp4", ""))
) )
# Create output folder # Create output folder

View File

@ -11,5 +11,9 @@ SITE_NAME = os.path.basename(os.path.dirname(os.path.abspath(__file__)))
ROOT_PATH = config_manager.get('DEFAULT', 'root_path') ROOT_PATH = config_manager.get('DEFAULT', 'root_path')
DOMAIN_NOW = config_manager.get_dict('SITE', SITE_NAME)['domain'] DOMAIN_NOW = config_manager.get_dict('SITE', SITE_NAME)['domain']
SERIES_FOLDER = config_manager.get('DEFAULT', 'serie_folder_name') SERIES_FOLDER = os.path.join(ROOT_PATH, config_manager.get('DEFAULT', 'serie_folder_name'))
MOVIE_FOLDER = config_manager.get('DEFAULT', 'movie_folder_name') MOVIE_FOLDER = os.path.join(ROOT_PATH, config_manager.get('DEFAULT', 'movie_folder_name'))
if config_manager.get_bool("DEFAULT", "add_siteName"):
SERIES_FOLDER = os.path.join(ROOT_PATH, SITE_NAME, config_manager.get('DEFAULT', 'serie_folder_name'))
MOVIE_FOLDER = os.path.join(ROOT_PATH, SITE_NAME, config_manager.get('DEFAULT', 'movie_folder_name'))

View File

@ -22,7 +22,7 @@ from StreamingCommunity.Api.Player.supervideo import VideoSource
# Config # Config
from .costant import ROOT_PATH, MOVIE_FOLDER from .costant import MOVIE_FOLDER
def download_film(select_title: MediaItem) -> str: def download_film(select_title: MediaItem) -> str:
@ -47,7 +47,7 @@ def download_film(select_title: MediaItem) -> str:
# Define output path # Define output path
title_name = os_manager.get_sanitize_file(select_title.name) + ".mp4" title_name = os_manager.get_sanitize_file(select_title.name) + ".mp4"
mp4_path = os_manager.get_sanitize_path( mp4_path = os_manager.get_sanitize_path(
os.path.join(ROOT_PATH, MOVIE_FOLDER, title_name.replace(".mp4", "")) os.path.join(MOVIE_FOLDER, title_name.replace(".mp4", ""))
) )
# Get m3u8 master playlist # Get m3u8 master playlist

View File

@ -19,9 +19,11 @@ from StreamingCommunity.Api.Template.Class.SearchType import MediaManager
# Variable # Variable
from .costant import SITE_NAME from .costant import SITE_NAME, DOMAIN_NOW
media_search_manager = MediaManager() media_search_manager = MediaManager()
table_show_manager = TVShowManager() table_show_manager = TVShowManager()
max_timeout = config_manager.get_int("REQUESTS", "timeout")
disable_searchDomain = config_manager.get_bool("DEFAULT", "disable_searchDomain")
def title_search(title_search: str) -> int: def title_search(title_search: str) -> int:
@ -38,9 +40,11 @@ def title_search(title_search: str) -> int:
table_show_manager.clear() table_show_manager.clear()
# Find new domain if prev dont work # Find new domain if prev dont work
max_timeout = config_manager.get_int("REQUESTS", "timeout") domain_to_use = DOMAIN_NOW
domain_to_use, _ = search_domain(SITE_NAME, f"https://{SITE_NAME}")
if not disable_searchDomain:
domain_to_use, base_url = search_domain(SITE_NAME, f"https://{SITE_NAME}")
# Send request to search for title # Send request to search for title
client = httpx.Client() client = httpx.Client()

View File

@ -11,5 +11,9 @@ SITE_NAME = os.path.basename(os.path.dirname(os.path.abspath(__file__)))
ROOT_PATH = config_manager.get('DEFAULT', 'root_path') ROOT_PATH = config_manager.get('DEFAULT', 'root_path')
DOMAIN_NOW = config_manager.get_dict('SITE', SITE_NAME)['domain'] DOMAIN_NOW = config_manager.get_dict('SITE', SITE_NAME)['domain']
SERIES_FOLDER = config_manager.get('DEFAULT', 'serie_folder_name') SERIES_FOLDER = os.path.join(ROOT_PATH, config_manager.get('DEFAULT', 'serie_folder_name'))
MOVIE_FOLDER = config_manager.get('DEFAULT', 'movie_folder_name') MOVIE_FOLDER = os.path.join(ROOT_PATH, config_manager.get('DEFAULT', 'movie_folder_name'))
if config_manager.get_bool("DEFAULT", "add_siteName"):
SERIES_FOLDER = os.path.join(ROOT_PATH, SITE_NAME, config_manager.get('DEFAULT', 'serie_folder_name'))
MOVIE_FOLDER = os.path.join(ROOT_PATH, SITE_NAME, config_manager.get('DEFAULT', 'movie_folder_name'))

View File

@ -23,7 +23,7 @@ from StreamingCommunity.Api.Player.vixcloud import VideoSourceAnime
# Variable # Variable
from .costant import ROOT_PATH, SITE_NAME, SERIES_FOLDER, MOVIE_FOLDER from .costant import SITE_NAME, SERIES_FOLDER, MOVIE_FOLDER
@ -54,11 +54,11 @@ def download_episode(index_select: int, scrape_serie: ScrapeSerieAnime, video_so
if scrape_serie.is_series: if scrape_serie.is_series:
mp4_path = os_manager.get_sanitize_path( mp4_path = os_manager.get_sanitize_path(
os.path.join(ROOT_PATH, SERIES_FOLDER, scrape_serie.series_name) os.path.join(SERIES_FOLDER, scrape_serie.series_name)
) )
else: else:
mp4_path = os_manager.get_sanitize_path( mp4_path = os_manager.get_sanitize_path(
os.path.join(ROOT_PATH, MOVIE_FOLDER, scrape_serie.series_name) os.path.join(MOVIE_FOLDER, scrape_serie.series_name)
) )
# Create output folder # Create output folder

View File

@ -21,10 +21,11 @@ from StreamingCommunity.Api.Template.Class.SearchType import MediaManager
# Variable # Variable
from .costant import SITE_NAME from .costant import SITE_NAME, DOMAIN_NOW
media_search_manager = MediaManager() media_search_manager = MediaManager()
table_show_manager = TVShowManager() table_show_manager = TVShowManager()
max_timeout = config_manager.get_int("REQUESTS", "timeout")
disable_searchDomain = config_manager.get_bool("DEFAULT", "disable_searchDomain")
def get_token(site_name: str, domain: str) -> dict: def get_token(site_name: str, domain: str) -> dict:
@ -40,7 +41,10 @@ def get_token(site_name: str, domain: str) -> dict:
""" """
# Send a GET request to the specified URL composed of the site name and domain # Send a GET request to the specified URL composed of the site name and domain
response = httpx.get(f"https://www.{site_name}.{domain}") response = httpx.get(
url=f"https://www.{site_name}.{domain}",
timeout=max_timeout
)
response.raise_for_status() response.raise_for_status()
# Initialize variables to store CSRF token # Initialize variables to store CSRF token
@ -103,8 +107,10 @@ def title_search(title: str) -> int:
table_show_manager.clear() table_show_manager.clear()
# Get token and session value from configuration # Get token and session value from configuration
max_timeout = config_manager.get_int("REQUESTS", "timeout") domain_to_use = DOMAIN_NOW
domain_to_use, _ = search_domain(SITE_NAME, f"https://www.{SITE_NAME}")
if not disable_searchDomain:
domain_to_use, base_url = search_domain(SITE_NAME, f"https://www.{SITE_NAME}")
data = get_token(SITE_NAME, domain_to_use) data = get_token(SITE_NAME, domain_to_use)

View File

@ -11,5 +11,9 @@ SITE_NAME = os.path.basename(os.path.dirname(os.path.abspath(__file__)))
ROOT_PATH = config_manager.get('DEFAULT', 'root_path') ROOT_PATH = config_manager.get('DEFAULT', 'root_path')
DOMAIN_NOW = config_manager.get_dict('SITE', SITE_NAME)['domain'] DOMAIN_NOW = config_manager.get_dict('SITE', SITE_NAME)['domain']
SERIES_FOLDER = config_manager.get('DEFAULT', 'serie_folder_name') SERIES_FOLDER = os.path.join(ROOT_PATH, config_manager.get('DEFAULT', 'serie_folder_name'))
MOVIE_FOLDER = config_manager.get('DEFAULT', 'movie_folder_name') MOVIE_FOLDER = os.path.join(ROOT_PATH, config_manager.get('DEFAULT', 'movie_folder_name'))
if config_manager.get_bool("DEFAULT", "add_siteName"):
SERIES_FOLDER = os.path.join(ROOT_PATH, SITE_NAME, config_manager.get('DEFAULT', 'serie_folder_name'))
MOVIE_FOLDER = os.path.join(ROOT_PATH, SITE_NAME, config_manager.get('DEFAULT', 'movie_folder_name'))

View File

@ -21,7 +21,7 @@ from StreamingCommunity.Api.Player.maxstream import VideoSource
# Config # Config
from .costant import ROOT_PATH, MOVIE_FOLDER from .costant import MOVIE_FOLDER
def download_film(select_title: MediaItem) -> str: def download_film(select_title: MediaItem) -> str:
@ -46,7 +46,7 @@ def download_film(select_title: MediaItem) -> str:
# Define output path # Define output path
title_name = os_manager.get_sanitize_file(select_title.name) +".mp4" title_name = os_manager.get_sanitize_file(select_title.name) +".mp4"
mp4_path = os_manager.get_sanitize_path( mp4_path = os_manager.get_sanitize_path(
os.path.join(ROOT_PATH, MOVIE_FOLDER, title_name.replace(".mp4", "")) os.path.join(MOVIE_FOLDER, title_name.replace(".mp4", ""))
) )
# Get m3u8 master playlist # Get m3u8 master playlist

View File

@ -18,9 +18,11 @@ from StreamingCommunity.Api.Template.Class.SearchType import MediaManager
# Variable # Variable
from .costant import SITE_NAME from .costant import SITE_NAME, DOMAIN_NOW
media_search_manager = MediaManager() media_search_manager = MediaManager()
table_show_manager = TVShowManager() table_show_manager = TVShowManager()
max_timeout = config_manager.get_int("REQUESTS", "timeout")
disable_searchDomain = config_manager.get_bool("DEFAULT", "disable_searchDomain")
def title_search(word_to_search: str) -> int: def title_search(word_to_search: str) -> int:
@ -37,8 +39,10 @@ def title_search(word_to_search: str) -> int:
table_show_manager.clear() table_show_manager.clear()
# Find new domain if prev dont work # Find new domain if prev dont work
max_timeout = config_manager.get_int("REQUESTS", "timeout") domain_to_use = DOMAIN_NOW
domain_to_use, _ = search_domain(SITE_NAME, f"https://{SITE_NAME}")
if not disable_searchDomain:
domain_to_use, base_url = search_domain(SITE_NAME, f"https://{SITE_NAME}")
response = httpx.get( response = httpx.get(
url=f"https://{SITE_NAME}.{domain_to_use}/?s={word_to_search}", url=f"https://{SITE_NAME}.{domain_to_use}/?s={word_to_search}",

View File

@ -12,5 +12,9 @@ ROOT_PATH = config_manager.get('DEFAULT', 'root_path')
DOMAIN_NOW = config_manager.get_dict('SITE', SITE_NAME)['domain'] DOMAIN_NOW = config_manager.get_dict('SITE', SITE_NAME)['domain']
COOKIE = config_manager.get_dict('SITE', SITE_NAME)['extra'] COOKIE = config_manager.get_dict('SITE', SITE_NAME)['extra']
SERIES_FOLDER = config_manager.get('DEFAULT', 'serie_folder_name') SERIES_FOLDER = os.path.join(ROOT_PATH, config_manager.get('DEFAULT', 'serie_folder_name'))
MOVIE_FOLDER = config_manager.get('DEFAULT', 'movie_folder_name') MOVIE_FOLDER = os.path.join(ROOT_PATH, config_manager.get('DEFAULT', 'movie_folder_name'))
if config_manager.get_bool("DEFAULT", "add_siteName"):
SERIES_FOLDER = os.path.join(ROOT_PATH, SITE_NAME, config_manager.get('DEFAULT', 'serie_folder_name'))
MOVIE_FOLDER = os.path.join(ROOT_PATH, SITE_NAME, config_manager.get('DEFAULT', 'movie_folder_name'))

View File

@ -24,7 +24,7 @@ from StreamingCommunity.Api.Player.ddl import VideoSource
# Variable # Variable
from .costant import ROOT_PATH, SERIES_FOLDER from .costant import SERIES_FOLDER
@ -51,7 +51,7 @@ def download_video(index_episode_selected: int, scape_info_serie: GetSerieInfo,
title_name = os_manager.get_sanitize_file( title_name = os_manager.get_sanitize_file(
f"{map_episode_title(scape_info_serie.tv_name, None, index_episode_selected, obj_episode.get('name'))}.mp4" f"{map_episode_title(scape_info_serie.tv_name, None, index_episode_selected, obj_episode.get('name'))}.mp4"
) )
mp4_path = os.path.join(ROOT_PATH, SERIES_FOLDER, scape_info_serie.tv_name) mp4_path = os.path.join(SERIES_FOLDER, scape_info_serie.tv_name)
# Create output folder # Create output folder
os_manager.create_path(mp4_path) os_manager.create_path(mp4_path)

View File

@ -22,9 +22,11 @@ from StreamingCommunity.Api.Template.Class.SearchType import MediaManager
# Variable # Variable
from .costant import SITE_NAME from .costant import SITE_NAME, DOMAIN_NOW
media_search_manager = MediaManager() media_search_manager = MediaManager()
table_show_manager = TVShowManager() table_show_manager = TVShowManager()
max_timeout = config_manager.get_int("REQUESTS", "timeout")
disable_searchDomain = config_manager.get_bool("DEFAULT", "disable_searchDomain")
def title_search(word_to_search: str) -> int: def title_search(word_to_search: str) -> int:
@ -41,8 +43,10 @@ def title_search(word_to_search: str) -> int:
table_show_manager.clear() table_show_manager.clear()
# Find new domain if prev dont work # Find new domain if prev dont work
max_timeout = config_manager.get_int("REQUESTS", "timeout") domain_to_use = DOMAIN_NOW
domain_to_use, _ = search_domain(SITE_NAME, f"https://{SITE_NAME}")
if not disable_searchDomain:
domain_to_use, base_url = search_domain(SITE_NAME, f"https://{SITE_NAME}")
# Send request to search for titles # Send request to search for titles
try: try:

View File

@ -11,5 +11,9 @@ SITE_NAME = os.path.basename(os.path.dirname(os.path.abspath(__file__)))
ROOT_PATH = config_manager.get('DEFAULT', 'root_path') ROOT_PATH = config_manager.get('DEFAULT', 'root_path')
DOMAIN_NOW = config_manager.get_dict('SITE', SITE_NAME)['domain'] DOMAIN_NOW = config_manager.get_dict('SITE', SITE_NAME)['domain']
SERIES_FOLDER = config_manager.get('DEFAULT', 'serie_folder_name') SERIES_FOLDER = os.path.join(ROOT_PATH, config_manager.get('DEFAULT', 'serie_folder_name'))
MOVIE_FOLDER = config_manager.get('DEFAULT', 'movie_folder_name') MOVIE_FOLDER = os.path.join(ROOT_PATH, config_manager.get('DEFAULT', 'movie_folder_name'))
if config_manager.get_bool("DEFAULT", "add_siteName"):
SERIES_FOLDER = os.path.join(ROOT_PATH, SITE_NAME, config_manager.get('DEFAULT', 'serie_folder_name'))
MOVIE_FOLDER = os.path.join(ROOT_PATH, SITE_NAME, config_manager.get('DEFAULT', 'movie_folder_name'))

View File

@ -2,7 +2,6 @@
import os import os
import sys import sys
import time
# Internal utilities # Internal utilities
@ -24,7 +23,7 @@ from StreamingCommunity.Api.Player.supervideo import VideoSource
# Variable # Variable
from .costant import ROOT_PATH, SERIES_FOLDER from .costant import SERIES_FOLDER
@ -50,7 +49,7 @@ def download_video(index_season_selected: int, index_episode_selected: int, scap
# Define filename and path for the downloaded video # Define filename and path for the downloaded video
mp4_name = f"{map_episode_title(scape_info_serie.tv_name, index_season_selected, index_episode_selected, obj_episode.get('name'))}.mp4" mp4_name = f"{map_episode_title(scape_info_serie.tv_name, index_season_selected, index_episode_selected, obj_episode.get('name'))}.mp4"
mp4_path = os.path.join(ROOT_PATH, SERIES_FOLDER, scape_info_serie.tv_name, f"S{index_season_selected}") mp4_path = os.path.join(SERIES_FOLDER, scape_info_serie.tv_name, f"S{index_season_selected}")
# Setup video source # Setup video source
video_source = VideoSource(obj_episode.get('url')) video_source = VideoSource(obj_episode.get('url'))

View File

@ -19,9 +19,11 @@ from StreamingCommunity.Api.Template.Class.SearchType import MediaManager
# Variable # Variable
from .costant import SITE_NAME from .costant import SITE_NAME, DOMAIN_NOW
media_search_manager = MediaManager() media_search_manager = MediaManager()
table_show_manager = TVShowManager() table_show_manager = TVShowManager()
max_timeout = config_manager.get_int("REQUESTS", "timeout")
disable_searchDomain = config_manager.get_bool("DEFAULT", "disable_searchDomain")
def title_search(word_to_search: str) -> int: def title_search(word_to_search: str) -> int:
@ -38,8 +40,10 @@ def title_search(word_to_search: str) -> int:
table_show_manager.clear() table_show_manager.clear()
# Find new domain if prev dont work # Find new domain if prev dont work
max_timeout = config_manager.get_int("REQUESTS", "timeout") domain_to_use = DOMAIN_NOW
domain_to_use, _ = search_domain(SITE_NAME, f"https://{SITE_NAME}")
if not disable_searchDomain:
domain_to_use, base_url = search_domain(SITE_NAME, f"https://{SITE_NAME}")
# Send request to search for titles # Send request to search for titles
try: try:

View File

@ -11,5 +11,9 @@ SITE_NAME = os.path.basename(os.path.dirname(os.path.abspath(__file__)))
ROOT_PATH = config_manager.get('DEFAULT', 'root_path') ROOT_PATH = config_manager.get('DEFAULT', 'root_path')
DOMAIN_NOW = config_manager.get_dict('SITE', SITE_NAME)['domain'] DOMAIN_NOW = config_manager.get_dict('SITE', SITE_NAME)['domain']
SERIES_FOLDER = config_manager.get('DEFAULT', 'serie_folder_name') SERIES_FOLDER = os.path.join(ROOT_PATH, config_manager.get('DEFAULT', 'serie_folder_name'))
MOVIE_FOLDER = config_manager.get('DEFAULT', 'movie_folder_name') MOVIE_FOLDER = os.path.join(ROOT_PATH, config_manager.get('DEFAULT', 'movie_folder_name'))
if config_manager.get_bool("DEFAULT", "add_siteName"):
SERIES_FOLDER = os.path.join(ROOT_PATH, SITE_NAME, config_manager.get('DEFAULT', 'serie_folder_name'))
MOVIE_FOLDER = os.path.join(ROOT_PATH, SITE_NAME, config_manager.get('DEFAULT', 'movie_folder_name'))

View File

@ -2,6 +2,7 @@
# Internal utilities # Internal utilities
from StreamingCommunity.Util._jsonConfig import config_manager
from StreamingCommunity.Util.table import TVShowManager from StreamingCommunity.Util.table import TVShowManager
@ -13,9 +14,11 @@ from .util.ilCorsarScraper import IlCorsaroNeroScraper
# Variable # Variable
from .costant import SITE_NAME from .costant import SITE_NAME, DOMAIN_NOW
media_search_manager = MediaManager() media_search_manager = MediaManager()
table_show_manager = TVShowManager() table_show_manager = TVShowManager()
max_timeout = config_manager.get_int("REQUESTS", "timeout")
disable_searchDomain = config_manager.get_bool("DEFAULT", "disable_searchDomain")
async def title_search(word_to_search: str) -> int: async def title_search(word_to_search: str) -> int:
@ -32,7 +35,10 @@ async def title_search(word_to_search: str) -> int:
table_show_manager.clear() table_show_manager.clear()
# Find new domain if prev dont work # Find new domain if prev dont work
domain_to_use, _ = search_domain(SITE_NAME, f"https://{SITE_NAME}") domain_to_use = DOMAIN_NOW
if not disable_searchDomain:
domain_to_use, base_url = search_domain(SITE_NAME, f"https://{SITE_NAME}")
# Create scraper and collect result # Create scraper and collect result
print("\n") print("\n")

View File

@ -15,7 +15,7 @@ from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
# Config # Config
from .costant import ROOT_PATH, MOVIE_FOLDER from .costant import MOVIE_FOLDER
def download_title(select_title: MediaItem): def download_title(select_title: MediaItem):
@ -27,13 +27,13 @@ def download_title(select_title: MediaItem):
""" """
start_message() start_message()
console.print(f"[yellow]Download: [red]{select_title.name} \n") console.print(f"[yellow]Download: [red]{select_title.name} \n")
print() print()
# Define output path # Define output path
title_name = os_manager.get_sanitize_file(select_title.name) title_name = os_manager.get_sanitize_file(select_title.name)
mp4_path = os_manager.get_sanitize_path( mp4_path = os_manager.get_sanitize_path(
os.path.join(ROOT_PATH, MOVIE_FOLDER, title_name.replace(".mp4", "")) os.path.join(MOVIE_FOLDER, title_name.replace(".mp4", ""))
) )
# Create output folder # Create output folder

View File

@ -11,5 +11,9 @@ SITE_NAME = os.path.basename(os.path.dirname(os.path.abspath(__file__)))
ROOT_PATH = config_manager.get('DEFAULT', 'root_path') ROOT_PATH = config_manager.get('DEFAULT', 'root_path')
DOMAIN_NOW = config_manager.get_dict('SITE', SITE_NAME)['domain'] DOMAIN_NOW = config_manager.get_dict('SITE', SITE_NAME)['domain']
SERIES_FOLDER = config_manager.get('DEFAULT', 'serie_folder_name') SERIES_FOLDER = os.path.join(ROOT_PATH, config_manager.get('DEFAULT', 'serie_folder_name'))
MOVIE_FOLDER = config_manager.get('DEFAULT', 'movie_folder_name') MOVIE_FOLDER = os.path.join(ROOT_PATH, config_manager.get('DEFAULT', 'movie_folder_name'))
if config_manager.get_bool("DEFAULT", "add_siteName"):
SERIES_FOLDER = os.path.join(ROOT_PATH, SITE_NAME, config_manager.get('DEFAULT', 'serie_folder_name'))
MOVIE_FOLDER = os.path.join(ROOT_PATH, SITE_NAME, config_manager.get('DEFAULT', 'movie_folder_name'))

View File

@ -33,7 +33,7 @@ from StreamingCommunity.Lib.TMBD import Json_film
# Config # Config
from .costant import ROOT_PATH, SITE_NAME, DOMAIN_NOW, MOVIE_FOLDER from .costant import SITE_NAME, DOMAIN_NOW, MOVIE_FOLDER
def download_film(movie_details: Json_film) -> str: def download_film(movie_details: Json_film) -> str:
@ -75,7 +75,7 @@ def download_film(movie_details: Json_film) -> str:
# Define output path # Define output path
title_name = os_manager.get_sanitize_file(movie_details.title) + ".mp4" title_name = os_manager.get_sanitize_file(movie_details.title) + ".mp4"
mp4_path = os.path.join(ROOT_PATH, MOVIE_FOLDER, title_name.replace(".mp4", "")) mp4_path = os.path.join(MOVIE_FOLDER, title_name.replace(".mp4", ""))
# Get m3u8 master playlist # Get m3u8 master playlist
master_playlist = video_source.get_playlist() master_playlist = video_source.get_playlist()

View File

@ -9,7 +9,11 @@ from StreamingCommunity.Util._jsonConfig import config_manager
SITE_NAME = os.path.basename(os.path.dirname(os.path.abspath(__file__))) SITE_NAME = os.path.basename(os.path.dirname(os.path.abspath(__file__)))
ROOT_PATH = config_manager.get('DEFAULT', 'root_path') ROOT_PATH = config_manager.get('DEFAULT', 'root_path')
DOMAIN_NOW = config_manager.get('SITE', SITE_NAME) DOMAIN_NOW = config_manager.get_dict('SITE', SITE_NAME)['domain']
SERIES_FOLDER = config_manager.get('DEFAULT', 'serie_folder_name') SERIES_FOLDER = os.path.join(ROOT_PATH, config_manager.get('DEFAULT', 'serie_folder_name'))
MOVIE_FOLDER = config_manager.get('DEFAULT', 'movie_folder_name') MOVIE_FOLDER = os.path.join(ROOT_PATH, config_manager.get('DEFAULT', 'movie_folder_name'))
if config_manager.get_bool("DEFAULT", "add_siteName"):
SERIES_FOLDER = os.path.join(ROOT_PATH, SITE_NAME, config_manager.get('DEFAULT', 'serie_folder_name'))
MOVIE_FOLDER = os.path.join(ROOT_PATH, SITE_NAME, config_manager.get('DEFAULT', 'movie_folder_name'))

View File

@ -22,7 +22,7 @@ from StreamingCommunity.Api.Player.vixcloud import VideoSource
# Variable # Variable
from .costant import ROOT_PATH, SITE_NAME, MOVIE_FOLDER from .costant import SITE_NAME, MOVIE_FOLDER
def download_film(select_title: MediaItem) -> str: def download_film(select_title: MediaItem) -> str:
@ -51,8 +51,8 @@ def download_film(select_title: MediaItem) -> str:
master_playlist = video_source.get_playlist() master_playlist = video_source.get_playlist()
# Define the filename and path for the downloaded film # Define the filename and path for the downloaded film
title_name = os_manager.get_sanitize_file(select_title.slug) + ".mp4" title_name = os_manager.get_sanitize_file(select_title.name) + ".mp4"
mp4_path = os.path.join(ROOT_PATH, MOVIE_FOLDER, select_title.slug) mp4_path = os.path.join(MOVIE_FOLDER, select_title.name)
# Download the film using the m3u8 playlist, and output filename # Download the film using the m3u8 playlist, and output filename
r_proc = HLS_Downloader( r_proc = HLS_Downloader(

View File

@ -24,7 +24,7 @@ from StreamingCommunity.Api.Player.vixcloud import VideoSource
# Variable # Variable
from .costant import ROOT_PATH, SITE_NAME, SERIES_FOLDER from .costant import SITE_NAME, SERIES_FOLDER
@ -48,7 +48,7 @@ def download_video(index_season_selected: int, index_episode_selected: int, scra
# Define filename and path for the downloaded video # Define filename and path for the downloaded video
mp4_name = f"{map_episode_title(scrape_serie.series_name, index_season_selected, index_episode_selected, obj_episode.name)}.mp4" mp4_name = f"{map_episode_title(scrape_serie.series_name, index_season_selected, index_episode_selected, obj_episode.name)}.mp4"
mp4_path = os.path.join(ROOT_PATH, SERIES_FOLDER, scrape_serie.series_name, f"S{index_season_selected}") mp4_path = os.path.join(SERIES_FOLDER, scrape_serie.series_name, f"S{index_season_selected}")
# Retrieve scws and if available master playlist # Retrieve scws and if available master playlist
video_source.get_iframe(obj_episode.id) video_source.get_iframe(obj_episode.id)

View File

@ -26,30 +26,36 @@ from StreamingCommunity.Api.Template.Class.SearchType import MediaManager
# Config # Config
from .costant import SITE_NAME from .costant import SITE_NAME, DOMAIN_NOW
# Variable # Variable
media_search_manager = MediaManager() media_search_manager = MediaManager()
table_show_manager = TVShowManager() table_show_manager = TVShowManager()
max_timeout = config_manager.get_int("REQUESTS", "timeout") max_timeout = config_manager.get_int("REQUESTS", "timeout")
disable_searchDomain = config_manager.get_bool("DEFAULT", "disable_searchDomain")
def get_version(text: str): def get_version(domain: str):
""" """
Extracts the version from the HTML text of a webpage. Extracts the version from the HTML text of a webpage.
Parameters: Parameters:
- text (str): The HTML text of the webpage. - domain (str): The domain of the site.
Returns: Returns:
str: The version extracted from the webpage. str: The version extracted from the webpage.
list: Top 10 titles headlines for today.
""" """
try: try:
response = httpx.get(
url=f"https://{SITE_NAME}.{domain}/",
headers={'User-Agent': get_headers()},
timeout=max_timeout
)
response.raise_for_status()
# Parse request to site # Parse request to site
soup = BeautifulSoup(text, "html.parser") soup = BeautifulSoup(response.text, "html.parser")
# Extract version # Extract version
version = json.loads(soup.find("div", {"id": "app"}).get("data-page"))['version'] version = json.loads(soup.find("div", {"id": "app"}).get("data-page"))['version']
@ -72,22 +78,13 @@ def get_version_and_domain():
""" """
# Find new domain if prev dont work # Find new domain if prev dont work
domain_to_use, base_url = search_domain(SITE_NAME, f"https://{SITE_NAME}") domain_to_use = DOMAIN_NOW
# Extract version from the response if not disable_searchDomain:
try: domain_to_use, base_url = search_domain(SITE_NAME, f"https://{SITE_NAME}")
version = get_version(
httpx.get( version = get_version(domain_to_use)
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)
return version, domain_to_use return version, domain_to_use

View File

@ -1,10 +1,12 @@
# 01.03.24 # 01.03.24
import json
import logging import logging
# External libraries # External libraries
import httpx import httpx
from bs4 import BeautifulSoup
# Internal utilities # Internal utilities
@ -56,33 +58,33 @@ class ScrapeSerie:
Raises: Raises:
Exception: If there's an error fetching season information Exception: If there's an error fetching season information
""" """
self.headers = {
'user-agent': get_headers(),
'x-inertia': 'true',
'x-inertia-version': self.version,
}
try: try:
response = httpx.get( response = httpx.get(
url=f"https://{self.base_name}.{self.domain}/titles/{self.media_id}-{self.series_name}", url=f"https://{self.base_name}.{self.domain}/titles/{self.media_id}-{self.series_name}",
headers=self.headers, headers=self.headers,
timeout=max_timeout timeout=max_timeout
) )
response.raise_for_status() response.raise_for_status()
# Extract seasons from JSON response # Extract seasons from JSON response
json_response = response.json().get('props') soup = BeautifulSoup(response.text, "html.parser")
json_response = json.loads(soup.find("div", {"id": "app"}).get("data-page"))
"""
response = httpx.post(
url=f'https://{self.base_name}.{self.domain}/api/titles/preview/{self.media_id}',
headers={'User-Agent': get_headers()}
)
response.raise_for_status()
# Extract seasons from JSON response
json_response = response.json()
"""
# Collect info about season # Collect info about season
self.season_manager = Season(json_response.get('title')) self.season_manager = Season(json_response.get("props").get("title"))
self.season_manager.collect_images(self.base_name, self.domain)
# Collect first episode info
for i, ep in enumerate(json_response.get('loadedSeason').get('episodes')):
self.season_manager.episodes.add(ep)
self.season_manager.episodes.get(i).collect_image(self.base_name, self.domain)
except Exception as e: except Exception as e:
logging.error(f"Error collecting season info: {e}") logging.error(f"Error collecting season info: {e}")
raise raise
@ -97,16 +99,14 @@ class ScrapeSerie:
Raises: Raises:
Exception: If there's an error fetching episode information Exception: If there's an error fetching episode information
""" """
self.headers = {
'user-agent': get_headers(),
'x-inertia': 'true',
'x-inertia-version': self.version,
}
try: try:
response = httpx.get( response = httpx.get(
url=f'https://{self.base_name}.{self.domain}/titles/{self.media_id}-{self.series_name}/stagione-{number_season}', url=f'https://{self.base_name}.{self.domain}/titles/{self.media_id}-{self.series_name}/stagione-{number_season}',
headers=self.headers, headers={
'User-Agent': get_headers(),
'x-inertia': 'true',
'x-inertia-version': self.version,
},
timeout=max_timeout timeout=max_timeout
) )
response.raise_for_status() response.raise_for_status()

View File

@ -104,14 +104,15 @@ class HttpClient:
response = httpx.get( response = httpx.get(
url=url, url=url,
headers=self.headers, headers=self.headers,
timeout=max_timeout timeout=max_timeout,
follow_redirects=True
) )
response.raise_for_status() response.raise_for_status()
return response.text return response.text
except Exception as e: except Exception as e:
logging.info(f"Request to {url} failed with error: {e}") console.print(f"Request to {url} failed with error: {e}")
return 404 return 404
def get_content(self, url): def get_content(self, url):

View File

@ -194,23 +194,28 @@ class M3U8_Segments:
""" """
if self.is_index_url: if self.is_index_url:
# Send a GET request to retrieve the index M3U8 file try:
response = httpx.get(
self.url,
headers={'User-Agent': get_headers()},
timeout=max_timeout
)
response.raise_for_status()
# Save the M3U8 file to the temporary folder # Send a GET request to retrieve the index M3U8 file
path_m3u8_file = os.path.join(self.tmp_folder, "playlist.m3u8") response = httpx.get(
open(path_m3u8_file, "w+").write(response.text) self.url,
headers={'User-Agent': get_headers()},
timeout=max_timeout,
follow_redirects=True
)
response.raise_for_status()
# Parse the text from the M3U8 index file # Save the M3U8 file to the temporary folder
self.parse_data(response.text) path_m3u8_file = os.path.join(self.tmp_folder, "playlist.m3u8")
open(path_m3u8_file, "w+").write(response.text)
# Parse the text from the M3U8 index file
self.parse_data(response.text)
except Exception as e:
print(f"Error during M3U8 index request: {e}")
else: else:
# Parser data of content of index pass in input to class # Parser data of content of index pass in input to class
self.parse_data(self.url) self.parse_data(self.url)
@ -385,7 +390,7 @@ class M3U8_Segments:
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) self.current_timeout = min(self.max_timeout, self.current_timeout * 1.25)
if self.stop_event.is_set(): if self.stop_event.is_set():
break break
@ -546,7 +551,7 @@ class M3U8_Segments:
raise Exception("Output file is empty") raise Exception("Output file is empty")
# Display additional # Display additional
if self.info_nRetry >= len(self.segments) * (1/3.33): if self.info_nRetry >= len(self.segments) * 0.3:
# Get expected time # Get expected time
ex_hours, ex_minutes, ex_seconds = format_duration(self.expected_real_time_s) ex_hours, ex_minutes, ex_seconds = format_duration(self.expected_real_time_s)

View File

@ -1,5 +1,5 @@
__title__ = 'StreamingCommunity' __title__ = 'StreamingCommunity'
__version__ = '2.2.0' __version__ = '2.3.0'
__author__ = 'Lovi-0' __author__ = 'Lovi-0'
__description__ = 'A command-line program to download film' __description__ = 'A command-line program to download film'
__copyright__ = 'Copyright 2024' __copyright__ = 'Copyright 2024'

View File

@ -15,6 +15,8 @@
"user": "admin", "user": "admin",
"pass": "adminadmin" "pass": "adminadmin"
}, },
"add_siteName": true,
"disable_searchDomain": true,
"not_close": false "not_close": false
}, },
"REQUESTS": { "REQUESTS": {
@ -24,7 +26,7 @@
"proxy_start_max": 0.5 "proxy_start_max": 0.5
}, },
"M3U8_DOWNLOAD": { "M3U8_DOWNLOAD": {
"tqdm_delay": 0.01, "tqdm_delay": 0.12,
"tqdm_use_large_bar": true, "tqdm_use_large_bar": true,
"default_video_workser": 12, "default_video_workser": 12,
"default_audio_workser": 12, "default_audio_workser": 12,