diff --git a/README.md b/README.md index e58d790..49ffe63 100644 --- a/README.md +++ b/README.md @@ -79,9 +79,15 @@ Install directly from PyPI: pip install StreamingCommunity ``` -### Creating a Run Script +Update to the latest version: -Create `run_streaming.py`: +```bash +pip install --upgrade StreamingCommunity +``` + +## Quick Start + +Create a simple script (`run_streaming.py`) to launch the main application: ```python from StreamingCommunity.run import main @@ -91,16 +97,85 @@ if __name__ == "__main__": ``` Run the script: + ```bash python run_streaming.py ``` -### Updating via PyPI +## Modules -```bash -pip install --upgrade StreamingCommunity +### HLS Downloader + +Download HTTP Live Streaming (HLS) content from m3u8 URLs. + +```python +from StreamingCommunity.Download import HLS_Downloader + +# Initialize with m3u8 URL and optional output path +downloader = HLS_Downloader( + m3u8_url="https://example.com/stream.m3u8", + output_path="/downloads/video.mp4" # Optional +) + +# Start the download +downloader.download() ``` +See [HLS example](./Test/Download/HLS.py) for complete usage. + +### MP4 Downloader + +Direct MP4 file downloader with support for custom headers and referrer. + +```python +from StreamingCommunity.Download import MP4_downloader + +# Basic usage +downloader = MP4_downloader( + url="https://example.com/video.mp4", + path="/downloads/saved_video.mp4" +) + +# Advanced usage with custom headers and referrer +headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" +} +downloader = MP4_downloader( + url="https://example.com/video.mp4", + path="/downloads/saved_video.mp4", + referer="https://example.com", + headers_=headers +) + +# Start download +downloader.download() +``` + +See [MP4 example](./Test/Download/MP4.py) for complete usage. + +### Torrent Client + +Download content via torrent magnet links. + +```python +from StreamingCommunity.Download import TOR_downloader + +# Initialize torrent client +client = TOR_downloader() + +# Add magnet link +client.add_magnet_link("magnet:?xt=urn:btih:example_hash&dn=example_name") + +# Start download +client.start_download() + +# Move downloaded files to specific location +client.move_downloaded_files("/downloads/torrents/") +``` + +See [Torrent example](./Test/Download/TOR.py) for complete usage. + + ## 2. Automatic Installation ### Supported Operating Systems 💿 diff --git a/StreamingCommunity/Api/Player/Helper/Vixcloud/util.py b/StreamingCommunity/Api/Player/Helper/Vixcloud/util.py index 98c84a0..6c0ed1e 100644 --- a/StreamingCommunity/Api/Player/Helper/Vixcloud/util.py +++ b/StreamingCommunity/Api/Player/Helper/Vixcloud/util.py @@ -11,6 +11,7 @@ class Episode: self.number: int = data.get('number', 1) self.name: str = data.get('name', '') self.duration: int = data.get('duration', 0) + self.url: str = data.get('url', '') def __str__(self): return f"Episode(id={self.id}, number={self.number}, name='{self.name}', duration={self.duration} sec)" @@ -35,69 +36,70 @@ class EpisodeManager: Parameters: - index (int): The zero-based index of the episode to retrieve. - - Returns: - Episode: The Episode object at the specified index. """ return self.episodes[index] - def length(self) -> int: - """ - Get the number of episodes in the manager. - - Returns: - int: Number of episodes. - """ - return len(self.episodes) - def clear(self) -> None: """ This method clears the episodes list. - - Parameters: - - self: The object instance. """ self.episodes.clear() + def __len__(self) -> int: + """ + Get the number of episodes in the manager. + """ + return len(self.episodes) + def __str__(self): return f"EpisodeManager(num_episodes={len(self.episodes)})" -class SeasonData: +class Season: def __init__(self, data: Dict[str, Any]): self.id: int = data.get('id', 0) self.number: int = data.get('number', 0) + self.name: str = data.get('name', '') + self.slug: str = data.get('slug', '') + self.type: str = data.get('type', '') + self.episodes: EpisodeManager = EpisodeManager() def __str__(self): - return f"SeasonData(id={self.id}, number={self.number}, name='{self.name}'" + return f"Season(id={self.id}, number={self.number}, name='{self.name}', episodes={self.episodes.length()})" + class SeasonManager: def __init__(self): - self.seasons: List[SeasonData] = [] + self.seasons: List[Season] = [] - def add_season(self, season_data): - season = SeasonData(season_data) + def add_season(self, season_data: Dict[str, Any]) -> Season: + """ + Add a new season to the manager and return it. + + Parameters: + - season_data (Dict[str, Any]): A dictionary containing data for the new season. + """ + season = Season(season_data) self.seasons.append(season) + return season - def get_season_by_number(self, number: int) -> Optional[Dict]: - return self.seasons[number] - -class Season: - def __init__(self, season_data: Dict[str, Union[int, str, None]]): - self.season_data = season_data - - self.id: int = season_data.get('id', 0) - self.number: int = season_data.get('number', 0) - self.name: str = season_data.get('name', '') - self.slug: str = season_data.get('slug', '') - self.type: str = season_data.get('type', '') - self.seasons_count: int = season_data.get('seasons_count', 0) + def get_season_by_number(self, number: int) -> Optional[Season]: + """ + Get a season by its number. - self.episodes: EpisodeManager = EpisodeManager() - - self.seasonsData: SeasonManager = SeasonManager() - for element in season_data['seasons']: - self.seasonsData.add_season(element) + Parameters: + - number (int): The season number (1-based index) + """ + for season in self.seasons: + if season.number == number: + return season + return None + + def __len__(self) -> int: + """ + Return the number of seasons managed. + """ + return len(self.seasons) class Stream: diff --git a/StreamingCommunity/Api/Site/altadefinizione/__init__.py b/StreamingCommunity/Api/Site/altadefinizione/__init__.py index 22e4b35..8373379 100644 --- a/StreamingCommunity/Api/Site/altadefinizione/__init__.py +++ b/StreamingCommunity/Api/Site/altadefinizione/__init__.py @@ -14,15 +14,15 @@ from StreamingCommunity.Api.Template import get_select_title # Logic class from StreamingCommunity.Api.Template.config_loader import site_constant -#from .site import title_search, table_show_manager, media_search_manager -#from .film import download_film -#from .series import download_series +from .site import title_search, table_show_manager, media_search_manager +from .film import download_film +from .series import download_series # Variable -indice = 4 +indice = 2 _useFor = "film_serie" -_deprecate = True +_deprecate = False _priority = 1 _engineDownload = "hls" diff --git a/StreamingCommunity/Api/Site/altadefinizione/film.py b/StreamingCommunity/Api/Site/altadefinizione/film.py new file mode 100644 index 0000000..42a2e5a --- /dev/null +++ b/StreamingCommunity/Api/Site/altadefinizione/film.py @@ -0,0 +1,98 @@ +# 3.12.23 + +import os + + +# External library +import httpx +from bs4 import BeautifulSoup +from rich.console import Console + + +# Internal utilities +from StreamingCommunity.Util.os import os_manager +from StreamingCommunity.Util.message import start_message +from StreamingCommunity.Util.headers import get_headers +from StreamingCommunity.Util.config_json import config_manager +from StreamingCommunity.Lib.Downloader import HLS_Downloader + + +# Logic class +from StreamingCommunity.Api.Template.config_loader import site_constant +from StreamingCommunity.Api.Template.Class.SearchType import MediaItem + + +# Player +from StreamingCommunity.Api.Player.supervideo import VideoSource + + +# Variable +console = Console() +max_timeout = config_manager.get_int("REQUESTS", "timeout") + + +def download_film(select_title: MediaItem) -> str: + """ + Downloads a film using the provided film ID, title name, and domain. + + Parameters: + - select_title (MediaItem): The selected media item. + + Return: + - str: output path if successful, otherwise None + """ + start_message() + console.print(f"[yellow]Download: [red]{select_title.name} \n") + + # Extract mostraguarda link + try: + response = httpx.get(select_title.url, headers=get_headers(), timeout=10) + response.raise_for_status() + + except Exception as e: + console.print(f"[red]Error fetching the page: {e}") + return None + + # Create mostraguarda url + soup = BeautifulSoup(response.text, "html.parser") + iframe_tag = soup.find_all("iframe") + url_mostraGuarda = iframe_tag[0].get('data-src') + if not url_mostraGuarda: + console.print("Error: data-src attribute not found in iframe.") + + # Extract supervideo URL + try: + response = httpx.get(url_mostraGuarda, headers=get_headers(), timeout=10) + response.raise_for_status() + + except Exception as e: + console.print(f"[red]Error fetching mostraguarda link: {e}") + return None + + # Create supervio URL + soup = BeautifulSoup(response.text, "html.parser") + player_links = soup.find("ul", class_="_player-mirrors") + player_items = player_links.find_all("li") + supervideo_url = "https:" + player_items[0].get("data-link") + if not supervideo_url: + return None + + # Init class + video_source = VideoSource(url=supervideo_url) + master_playlist = video_source.get_playlist() + + # Define the filename and path for the downloaded film + title_name = os_manager.get_sanitize_file(select_title.name) + ".mp4" + mp4_path = os.path.join(site_constant.MOVIE_FOLDER, title_name.replace(".mp4", "")) + + # Download the film using the m3u8 playlist, and output filename + r_proc = HLS_Downloader( + m3u8_url=master_playlist, + output_path=os.path.join(mp4_path, title_name) + ).start() + + if r_proc['error'] is not None: + try: os.remove(r_proc['path']) + except: pass + + return r_proc['path'] \ No newline at end of file diff --git a/StreamingCommunity/Api/Site/altadefinizione/series.py b/StreamingCommunity/Api/Site/altadefinizione/series.py new file mode 100644 index 0000000..3db234d --- /dev/null +++ b/StreamingCommunity/Api/Site/altadefinizione/series.py @@ -0,0 +1,164 @@ +# 3.12.23 + +import os +from typing import Tuple + + +# External library +from rich.console import Console +from rich.prompt import Prompt + + +# Internal utilities +from StreamingCommunity.Util.message import start_message +from StreamingCommunity.Lib.Downloader import HLS_Downloader + +# Logic class +from .util.ScrapeSerie import GetSerieInfo +from StreamingCommunity.Api.Template.Util import ( + manage_selection, + map_episode_title, + dynamic_format_number, + validate_selection, + validate_episode_selection, + display_episodes_list +) +from StreamingCommunity.Api.Template.config_loader import site_constant +from StreamingCommunity.Api.Template.Class.SearchType import MediaItem + + +# Player +from StreamingCommunity.Api.Player.supervideo import VideoSource + + +# Variable +msg = Prompt() +console = Console() + + +def download_video(index_season_selected: int, index_episode_selected: int, scrape_serie: GetSerieInfo) -> Tuple[str,bool]: + """ + Download a single episode video. + + Parameters: + - index_season_selected (int): Index of the selected season. + - index_episode_selected (int): Index of the selected episode. + + Return: + - str: output path + - bool: kill handler status + """ + start_message() + index_season_selected = dynamic_format_number(str(index_season_selected)) + + # Get info about episode + obj_episode = scrape_serie.seasons_manager.get_season_by_number(int(index_season_selected)).episodes.get(index_episode_selected-1) + console.print(f"[yellow]Download: [red]{index_season_selected}:{index_episode_selected} {obj_episode.name}") + print() + + # Define filename and path for the downloaded video + mp4_name = f"{map_episode_title(scrape_serie.serie_name, index_season_selected, index_episode_selected, obj_episode.name)}.mp4" + mp4_path = os.path.join(site_constant.SERIES_FOLDER, scrape_serie.serie_name, f"S{index_season_selected}") + + # Retrieve scws and if available master playlist + video_source = VideoSource(obj_episode.url) + video_source.make_request(obj_episode.url) + master_playlist = video_source.get_playlist() + + # Download the episode + r_proc = HLS_Downloader( + m3u8_url=master_playlist, + output_path=os.path.join(mp4_path, mp4_name) + ).start() + + if r_proc['error'] is not None: + try: os.remove(r_proc['path']) + except: pass + + return r_proc['path'], r_proc['stopped'] + + +def download_episode(index_season_selected: int, scrape_serie: GetSerieInfo, download_all: bool = False) -> None: + """ + Download episodes of a selected season. + + Parameters: + - index_season_selected (int): Index of the selected season. + - download_all (bool): Download all episodes in the season. + """ + start_message() + obj_episodes = scrape_serie.seasons_manager.get_season_by_number(index_season_selected).episodes + episodes_count = len(obj_episodes.episodes) + + if download_all: + + # Download all episodes without asking + for i_episode in range(1, episodes_count + 1): + path, stopped = download_video(index_season_selected, i_episode, scrape_serie) + + if stopped: + break + + console.print(f"\n[red]End downloaded [yellow]season: [red]{index_season_selected}.") + + else: + + # Display episodes list and manage user selection + last_command = display_episodes_list(obj_episodes.episodes) + list_episode_select = manage_selection(last_command, episodes_count) + + try: + list_episode_select = validate_episode_selection(list_episode_select, episodes_count) + except ValueError as e: + console.print(f"[red]{str(e)}") + return + + # Download selected episodes if not stopped + for i_episode in list_episode_select: + path, stopped = download_video(index_season_selected, i_episode, scrape_serie) + + if stopped: + break + +def download_series(select_season: MediaItem) -> None: + """ + Download episodes of a TV series based on user selection. + + Parameters: + - select_season (MediaItem): Selected media item (TV series). + """ + start_message() + + # Init class + scrape_serie = GetSerieInfo(select_season.url) + + # Collect information about seasons + scrape_serie.collect_season() + seasons_count = len(scrape_serie.seasons_manager) + + # Prompt user for season selection and download episodes + console.print(f"\n[green]Seasons found: [red]{seasons_count}") + index_season_selected = msg.ask( + "\n[cyan]Insert season number [yellow](e.g., 1), [red]* [cyan]to download all seasons, " + "[yellow](e.g., 1-2) [cyan]for a range of seasons, or [yellow](e.g., 3-*) [cyan]to download from a specific season to the end" + ) + + # Manage and validate the selection + list_season_select = manage_selection(index_season_selected, seasons_count) + + try: + list_season_select = validate_selection(list_season_select, seasons_count) + except ValueError as e: + console.print(f"[red]{str(e)}") + return + + # Loop through the selected seasons and download episodes + for i_season in list_season_select: + if len(list_season_select) > 1 or index_season_selected == "*": + + # Download all episodes if multiple seasons are selected or if '*' is used + download_episode(i_season, scrape_serie, download_all=True) + else: + + # Otherwise, let the user select specific episodes for the single season + download_episode(i_season, scrape_serie, download_all=False) \ No newline at end of file diff --git a/StreamingCommunity/Api/Site/altadefinizione/site.py b/StreamingCommunity/Api/Site/altadefinizione/site.py new file mode 100644 index 0000000..64b1e0f --- /dev/null +++ b/StreamingCommunity/Api/Site/altadefinizione/site.py @@ -0,0 +1,75 @@ +# 10.12.23 + + +# External libraries +import httpx +from bs4 import BeautifulSoup +from rich.console import Console + + +# Internal utilities +from StreamingCommunity.Util.config_json import config_manager +from StreamingCommunity.Util.headers import get_userAgent +from StreamingCommunity.Util.table import TVShowManager + + +# Logic class +from StreamingCommunity.Api.Template.config_loader import site_constant +from StreamingCommunity.Api.Template.Class.SearchType import MediaManager + + +# Variable +console = Console() +media_search_manager = MediaManager() +table_show_manager = TVShowManager() +max_timeout = config_manager.get_int("REQUESTS", "timeout") + + +def title_search(title_search: str) -> int: + """ + Search for titles based on a search query. + + Parameters: + - title_search (str): The title to search for. + + Returns: + int: The number of titles found. + """ + media_search_manager.clear() + table_show_manager.clear() + + search_url = f"{site_constant.FULL_URL}/?story={title_search}&do=search&subaction=search" + console.print(f"[cyan]Search url: [yellow]{search_url}") + + try: + response = httpx.post(search_url, headers={'user-agent': get_userAgent()}, timeout=max_timeout, follow_redirects=True) + response.raise_for_status() + + except Exception as e: + console.print(f"Site: {site_constant.SITE_NAME}, request search error: {e}") + return 0 + + # Create soup istance + soup = BeautifulSoup(response.text, "html.parser") + + # Collect data from soup + for movie_div in soup.find_all("div", class_="movie"): + + title_tag = movie_div.find("h2", class_="movie-title") + title = title_tag.find("a").get_text(strip=True) + url = title_tag.find("a").get("href") + + # Define typo + if "/serie-tv/" in url: + tipo = "tv" + else: + tipo = "film" + + media_search_manager.add_media({ + 'url': url, + 'name': title, + 'type': tipo + }) + + # Return the number of titles found + return media_search_manager.get_length() \ No newline at end of file diff --git a/StreamingCommunity/Api/Site/altadefinizione/util/ScrapeSerie.py b/StreamingCommunity/Api/Site/altadefinizione/util/ScrapeSerie.py new file mode 100644 index 0000000..b57c15d --- /dev/null +++ b/StreamingCommunity/Api/Site/altadefinizione/util/ScrapeSerie.py @@ -0,0 +1,72 @@ +# 01.03.24 + +# External libraries +import httpx +from bs4 import BeautifulSoup + + +# Internal utilities +from StreamingCommunity.Util.headers import get_userAgent +from StreamingCommunity.Util.config_json import config_manager +from StreamingCommunity.Api.Player.Helper.Vixcloud.util import SeasonManager + + +# Variable +max_timeout = config_manager.get_int("REQUESTS", "timeout") + + + +class GetSerieInfo: + def __init__(self, url): + """ + Initialize the GetSerieInfo class for scraping TV series information. + + Args: + - url (str): The URL of the streaming site. + """ + self.headers = {'user-agent': get_userAgent()} + self.url = url + self.seasons_manager = SeasonManager() + + def collect_season(self) -> None: + """ + Retrieve all episodes for all seasons + """ + response = httpx.get(self.url, headers=self.headers) + soup = BeautifulSoup(response.text, "html.parser") + self.serie_name = soup.find("title").get_text(strip=True).split(" - ")[0] + + # Process all seasons + season_items = soup.find_all('div', class_='accordion-item') + + for season_idx, season_item in enumerate(season_items, 1): + season_header = season_item.find('div', class_='accordion-header') + if not season_header: + continue + + season_name = season_header.get_text(strip=True) + + # Create a new season and get a reference to it + current_season = self.seasons_manager.add_season({ + 'number': season_idx, + 'name': season_name + }) + + # Find episodes for this season + episode_divs = season_item.find_all('div', class_='down-episode') + for ep_idx, ep_div in enumerate(episode_divs, 1): + episode_name_tag = ep_div.find('b') + if not episode_name_tag: + continue + + episode_name = episode_name_tag.get_text(strip=True) + link_tag = ep_div.find('a', string=lambda text: text and "Supervideo" in text) + episode_url = link_tag['href'] if link_tag else None + + # Add episode to the season + if current_season: + current_season.episodes.add({ + 'number': ep_idx, + 'name': episode_name, + 'url': episode_url + }) \ No newline at end of file diff --git a/StreamingCommunity/Api/Site/streamingcommunity/series.py b/StreamingCommunity/Api/Site/streamingcommunity/series.py index b39046a..1b38cd3 100644 --- a/StreamingCommunity/Api/Site/streamingcommunity/series.py +++ b/StreamingCommunity/Api/Site/streamingcommunity/series.py @@ -52,8 +52,15 @@ def download_video(index_season_selected: int, index_episode_selected: int, scra start_message() index_season_selected = dynamic_format_number(str(index_season_selected)) + # SPECIAL: Get season number + season = None + for s in scrape_serie.seasons_manager.seasons: + if s.number == int(index_season_selected): + season = s + break + # Get info about episode - obj_episode = scrape_serie.episode_manager.get(index_episode_selected - 1) + obj_episode = season.episodes.get(index_episode_selected - 1) console.print(f"[yellow]Download: [red]{index_season_selected}:{index_episode_selected} {obj_episode.name}") print() @@ -100,14 +107,16 @@ def download_episode(index_season_selected: int, scrape_serie: GetSerieInfo, vid - index_season_selected (int): Index of the selected season. - download_all (bool): Download all episodes in the season. """ - - # Clean memory of all episodes and get the number of the season - scrape_serie.episode_manager.clear() - - # Start message and collect information about episodes start_message() scrape_serie.collect_info_season(index_season_selected) - episodes_count = scrape_serie.episode_manager.length() + + # SPECIAL: Get season number + season = None + for s in scrape_serie.seasons_manager.seasons: + if s.number == index_season_selected: + season = s + break + episodes_count = len(season.episodes.episodes) if download_all: @@ -123,7 +132,7 @@ def download_episode(index_season_selected: int, scrape_serie: GetSerieInfo, vid else: # Display episodes list and manage user selection - last_command = display_episodes_list(scrape_serie.episode_manager.episodes) + last_command = display_episodes_list(season.episodes.episodes) list_episode_select = manage_selection(last_command, episodes_count) try: @@ -163,7 +172,7 @@ def download_series(select_season: MediaItem) -> None: # Collect information about seasons scrape_serie.collect_info_title() - seasons_count = scrape_serie.season_manager.seasons_count + seasons_count = len(scrape_serie.seasons_manager) # Prompt user for season selection and download episodes console.print(f"\n[green]Seasons found: [red]{seasons_count}") @@ -197,14 +206,23 @@ def download_series(select_season: MediaItem) -> None: # Loop through the selected seasons and download episodes for i_season in list_season_select: + + # SPECIAL: Get season number + season = None + for s in scrape_serie.seasons_manager.seasons: + if s.number == i_season: + season = s + break + season_number = season.number + if len(list_season_select) > 1 or index_season_selected == "*": # Download all episodes if multiple seasons are selected or if '*' is used - download_episode(scrape_serie.season_manager.seasonsData.get_season_by_number(i_season-1).number, scrape_serie, video_source, download_all=True) + download_episode(season_number, scrape_serie, video_source, download_all=True) else: # Otherwise, let the user select specific episodes for the single season - download_episode(scrape_serie.season_manager.seasonsData.get_season_by_number(i_season-1).number, scrape_serie, video_source, download_all=False) + download_episode(season_number, scrape_serie, video_source, download_all=False) if site_constant.TELEGRAM_BOT: bot.send_message(f"Finito di scaricare tutte le serie e episodi", None) diff --git a/StreamingCommunity/Api/Site/streamingcommunity/util/ScrapeSerie.py b/StreamingCommunity/Api/Site/streamingcommunity/util/ScrapeSerie.py index f24c21e..259d2fc 100644 --- a/StreamingCommunity/Api/Site/streamingcommunity/util/ScrapeSerie.py +++ b/StreamingCommunity/Api/Site/streamingcommunity/util/ScrapeSerie.py @@ -12,7 +12,7 @@ from bs4 import BeautifulSoup # Internal utilities from StreamingCommunity.Util.headers import get_userAgent from StreamingCommunity.Util.config_json import config_manager -from StreamingCommunity.Api.Player.Helper.Vixcloud.util import Season, EpisodeManager +from StreamingCommunity.Api.Player.Helper.Vixcloud.util import SeasonManager # Variable @@ -22,7 +22,7 @@ max_timeout = config_manager.get_int("REQUESTS", "timeout") class GetSerieInfo: def __init__(self, url): """ - Initialize the ScrapeSerie class for scraping TV series information. + Initialize the GetSerieInfo class for scraping TV series information. Args: - url (str): The URL of the streaming site. @@ -31,6 +31,9 @@ class GetSerieInfo: self.headers = {'user-agent': get_userAgent()} self.url = url + # Initialize the SeasonManager + self.seasons_manager = SeasonManager() + def setup(self, media_id: int = None, series_name: str = None): """ Set up the scraper with specific media details. @@ -41,19 +44,17 @@ class GetSerieInfo: """ self.media_id = media_id - # If series name is provided, initialize series-specific managers + # If series name is provided, initialize series-specific properties if series_name is not None: self.is_series = True self.series_name = series_name - self.season_manager = None - self.episode_manager: EpisodeManager = EpisodeManager() def collect_info_title(self) -> None: """ - Retrieve season information for a TV series from the streaming site. + Retrieve general information about the TV series from the streaming site. Raises: - Exception: If there's an error fetching season information + Exception: If there's an error fetching series information """ try: response = httpx.get( @@ -63,16 +64,30 @@ class GetSerieInfo: ) response.raise_for_status() - # Extract seasons from JSON response + # Extract series info from JSON response soup = BeautifulSoup(response.text, "html.parser") json_response = json.loads(soup.find("div", {"id": "app"}).get("data-page")) self.version = json_response['version'] - - # Collect info about season - self.season_manager = Season(json_response.get("props").get("title")) + + # Extract information about available seasons + title_data = json_response.get("props", {}).get("title", {}) + + # Save general series information + self.title_info = title_data + + # Extract available seasons and add them to SeasonManager + seasons_data = title_data.get("seasons", []) + for season_data in seasons_data: + self.seasons_manager.add_season({ + 'id': season_data.get('id', 0), + 'number': season_data.get('number', 0), + 'name': f"Season {season_data.get('number', 0)}", + 'slug': season_data.get('slug', ''), + 'type': title_data.get('type', '') + }) except Exception as e: - logging.error(f"Error collecting season info: {e}") + logging.error(f"Error collecting series info: {e}") raise def collect_info_season(self, number_season: int) -> None: @@ -86,6 +101,12 @@ class GetSerieInfo: Exception: If there's an error fetching episode information """ try: + # Get the season object from SeasonManager + season = self.seasons_manager.get_season_by_number(number_season) + if not season: + logging.error(f"Season {number_season} not found") + return + response = httpx.get( url=f'{self.url}/titles/{self.media_id}-{self.series_name}/stagione-{number_season}', headers={ @@ -98,12 +119,12 @@ class GetSerieInfo: response.raise_for_status() # Extract episodes from JSON response - json_response = response.json().get('props').get('loadedSeason').get('episodes') + json_response = response.json().get('props', {}).get('loadedSeason', {}).get('episodes', []) - # Add each episode to the episode manager + # Add each episode to the corresponding season's episode manager for dict_episode in json_response: - self.episode_manager.add(dict_episode) + season.episodes.add(dict_episode) except Exception as e: - logging.error(f"Error collecting title season info: {e}") - raise + logging.error(f"Error collecting episodes for season {number_season}: {e}") + raise \ No newline at end of file diff --git a/StreamingCommunity/Lib/Downloader/MP4/downloader.py b/StreamingCommunity/Lib/Downloader/MP4/downloader.py index fd6856e..010fff7 100644 --- a/StreamingCommunity/Lib/Downloader/MP4/downloader.py +++ b/StreamingCommunity/Lib/Downloader/MP4/downloader.py @@ -31,7 +31,7 @@ from ...FFmpeg import print_duration_table # Config REQUEST_VERIFY = config_manager.get_bool('REQUESTS', 'verify') -REQUEST_HTTP2 = config_manager.get_bool('REQUEST', 'http2') +REQUEST_HTTP2 = config_manager.get_bool('REQUESTS', 'http2') GET_ONLY_LINK = config_manager.get_bool('M3U8_PARSER', 'get_only_link') REQUEST_TIMEOUT = config_manager.get_float('REQUESTS', 'timeout') TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot') diff --git a/StreamingCommunity/Lib/M3U8/estimator.py b/StreamingCommunity/Lib/M3U8/estimator.py index 8f4efe2..96e4e0f 100644 --- a/StreamingCommunity/Lib/M3U8/estimator.py +++ b/StreamingCommunity/Lib/M3U8/estimator.py @@ -119,15 +119,15 @@ class M3U8_Ts_Estimator: retry_count = self.segments_instance.active_retries if self.segments_instance else 0 progress_str = ( - f"{Colors.GREEN}{number_file_total_size} {Colors.WHITE}< {Colors.RED}{units_file_total_size}" - f"{Colors.WHITE} {Colors.CYAN}{average_internet_speed} {Colors.RED}{average_internet_unit}" + f"{Colors.GREEN}{number_file_total_size} {Colors.RED}{units_file_total_size}" + f"{Colors.WHITE}, {Colors.CYAN}{average_internet_speed} {Colors.RED}{average_internet_unit}" f"{Colors.WHITE}, {Colors.GREEN}CRR {Colors.RED}{retry_count} " ) else: retry_count = self.segments_instance.active_retries if self.segments_instance else 0 progress_str = ( - f"{Colors.GREEN}{number_file_total_size} {Colors.WHITE}< {Colors.RED}{units_file_total_size}" + f"{Colors.GREEN}{number_file_total_size} {Colors.RED}{units_file_total_size}" f"{Colors.WHITE}, {Colors.GREEN}CRR {Colors.RED}{retry_count} " ) diff --git a/StreamingCommunity/Upload/version.py b/StreamingCommunity/Upload/version.py index d49fd39..ab76dbc 100644 --- a/StreamingCommunity/Upload/version.py +++ b/StreamingCommunity/Upload/version.py @@ -1,5 +1,5 @@ __title__ = 'StreamingCommunity' -__version__ = '2.9.3' +__version__ = '2.9.4' __author__ = 'Arrowar' __description__ = 'A command-line program to download film' __copyright__ = 'Copyright 2024' diff --git a/StreamingCommunity/__init__.py b/StreamingCommunity/__init__.py index 3eaedf3..ec4f4c5 100644 --- a/StreamingCommunity/__init__.py +++ b/StreamingCommunity/__init__.py @@ -1,6 +1,6 @@ # 11.03.25 from .run import main -from .Lib.Downloader.HLS import downloader as HLS_Downloader -from .Lib.Downloader.MP4 import downloader as MP4_Downloader -from .Lib.Downloader.TOR import downloader as TOR_Downloader \ No newline at end of file +from .Lib.Downloader.HLS.downloader import HLS_Downloader +from .Lib.Downloader.MP4.downloader import MP4_downloader +from .Lib.Downloader.TOR.downloader import TOR_downloader \ No newline at end of file diff --git a/setup.py b/setup.py index 0dcadf7..6ed46d1 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ with open("requirements.txt", "r", encoding="utf-8-sig") as f: setup( name="StreamingCommunity", - version="2.9.3", + version="2.9.4", long_description=read_readme(), long_description_content_type="text/markdown", author="Lovi-0",