From 590533a141762e0f403a9bc1922ec57098712474 Mon Sep 17 00:00:00 2001 From: Lovi <62809003+Lovi-0@users.noreply.github.com> Date: Sun, 22 Sep 2024 10:27:53 +0200 Subject: [PATCH] Nuovo PC --- README.md | 34 --- Src/Api/Template/Util/get_domain.py | 71 +++++- Src/Api/Template/Util/manage_ep.py | 99 +++++--- Src/Api/Template/__init__.py | 2 +- Src/Api/altadefinizione/site.py | 4 +- Src/Api/cb01/site.py | 38 ++- Src/Api/ddlstreamitaly/series.py | 21 +- Src/Api/ddlstreamitaly/site.py | 58 +++-- Src/Api/guardaserie/series.py | 64 ++--- Src/Api/mostraguarda/__init__.py | 36 +++ Src/Api/mostraguarda/costant.py | 15 ++ Src/Api/mostraguarda/film.py | 74 ++++++ Src/Api/streamingcommunity/series.py | 79 +++--- Src/Api/uhdmovies/__init__.py | 2 +- Src/Api/uhdmovies/serie.py | 69 +++--- Src/Lib/Downloader/HLS/segments.py | 8 +- Src/Lib/FFmpeg/command.py | 2 +- Src/Lib/TMBD/__init__.py | 2 + Src/Lib/TMBD/obj_tmbd.py | 40 ++++ Src/Lib/TMBD/tmbd.py | 138 ----------- Src/Lib/TMBD/tmdb.py | 346 +++++++++++++++++++++++++++ Src/Upload/update.py | 1 + Src/Upload/version.py | 2 +- Src/Util/headers.py | 8 +- Src/Util/table.py | 15 +- config.json | 17 +- requirements.txt | 7 +- run.py | 2 +- 28 files changed, 853 insertions(+), 401 deletions(-) create mode 100644 Src/Api/mostraguarda/__init__.py create mode 100644 Src/Api/mostraguarda/costant.py create mode 100644 Src/Api/mostraguarda/film.py create mode 100644 Src/Lib/TMBD/__init__.py create mode 100644 Src/Lib/TMBD/obj_tmbd.py delete mode 100644 Src/Lib/TMBD/tmbd.py create mode 100644 Src/Lib/TMBD/tmdb.py diff --git a/README.md b/README.md index 024bbba..7862770 100644 --- a/README.md +++ b/README.md @@ -82,18 +82,6 @@ You can change some behaviors by tweaking the configuration file. * **debug**: Enables or disables debug mode. - **Default Value**: `false` - * **log_file**: The file where logs will be written. - - **Default Value**: `app.log` - - * **log_to_file**: Whether to log messages to a file. - - **Default Value**: `true` - - * **show_message**: Whether to show messages. - - **Default Value**: `false` - - * **clean_console**: Clears the console before the script runs. - - **Default Value**: `false` - * **root_path**: Path where the script will add movies and TV series folders (see [Path Examples](#Path-examples)). - **Default Value**: `Video` @@ -146,28 +134,6 @@ You can change some behaviors by tweaking the configuration file. * **cleanup_tmp_folder**: Upon final conversion, ensures the removal of all unformatted audio, video tracks, and subtitles from the temporary folder, thereby maintaining cleanliness and efficiency. - **Default Value**: `false` - * **create_report**: When enabled, saves the name of the series or movie being downloaded along with the date and file size in a CSV file, providing a log of downloaded content. - - **Default Value**: `false` - - - -
- M3U8_CONVERSION - - * **use_codec**: Whether to use a specific codec for processing. - - **Default Value**: `false` - - **Example Value**: `libx264` - - * **use_gpu**: Whether to use GPU acceleration. - - **Default Value**: `false` - - * **default_preset**: The default preset for ffmpeg conversion. - - **Default Value**: `ultrafast` - - **Example Value**: `slow` - - * **check_output_after_ffmpeg**: Verify if the conversion run by ffmpeg is free from corruption. - - **Default Value**: `false` -
diff --git a/Src/Api/Template/Util/get_domain.py b/Src/Api/Template/Util/get_domain.py index 817a471..4e85fbf 100644 --- a/Src/Api/Template/Util/get_domain.py +++ b/Src/Api/Template/Util/get_domain.py @@ -1,14 +1,12 @@ # 18.06.24 -import os import sys -import time -import logging from urllib.parse import urlparse # External libraries import httpx +from googlesearch import search # Internal utilities @@ -17,6 +15,46 @@ from Src.Util.console import console from Src.Util._jsonConfig import config_manager +def google_search(query): + """ + Perform a Google search and return the first result. + + Args: + query (str): The search query to execute on Google. + + Returns: + str: The first URL result from the search, or None if no result is found. + """ + # Perform the search on Google and limit to 1 result + search_results = search(query, num_results=1) + + # Extract the first result + first_result = next(search_results, None) + + if not first_result: + console.print("[red]No results found.[/red]") + + return first_result + +def get_final_redirect_url(initial_url): + """ + Follow redirects from the initial URL and return the final URL after all redirects. + + Args: + initial_url (str): The URL to start with and follow redirects. + + Returns: + str: The final URL after all redirects are followed. + """ + + # Create a client with redirects enabled + with httpx.Client(follow_redirects=True) as client: + response = client.get(initial_url) + + # Capture the final URL after all redirects + final_url = response.url + + return final_url def search_domain(site_name: str, base_url: str): """ @@ -35,10 +73,27 @@ def search_domain(site_name: str, base_url: str): domain = str(config_manager.get_dict("SITE", site_name)['domain']) console.print(f"[cyan]Test site[white]: [red]{base_url}.{domain}") - # Test the current domain - response_follow = httpx.get(f"{base_url}.{domain}", headers={'user-agent': get_headers()}, timeout=5, follow_redirects=True) - #console.print(f"[cyan]Test response site[white]: [red]{response_follow.status_code}") - response_follow.raise_for_status() + try: + + # Test the current domain + response_follow = httpx.get(f"{base_url}.{domain}", headers={'user-agent': get_headers()}, timeout=2) + console.print(f"[cyan]Response site[white]: [red]{response_follow.status_code}") + response_follow.raise_for_status() + + except Exception as e: + console.print(f"[cyan]Change domain for site[white]: [red]{base_url}.{domain}, [cyan]error[white]: [red]{e}") + + query = base_url.split("/")[-1] + first_url = google_search(query) + + if first_url: + final_url = get_final_redirect_url(first_url) + console.print(f"\n[bold yellow]Suggestion:[/bold yellow] [white](Experimental)\n" + f"[cyan]New final URL[white]: [green]{final_url}") + else: + console.print("[bold red]No valid URL to follow redirects.[/bold red]") + + sys.exit(0) # Ensure the URL is in string format before parsing parsed_url = urlparse(str(response_follow.url)) @@ -52,5 +107,5 @@ def search_domain(site_name: str, base_url: str): config_manager.write_config() # Return config domain - console.print(f"[cyan]Use domain: [red]{tld} \n") + console.print(f"[cyan]Return domain: [red]{tld} \n") return tld, f"{base_url}.{tld}" diff --git a/Src/Api/Template/Util/manage_ep.py b/Src/Api/Template/Util/manage_ep.py index fa4992c..81e177d 100644 --- a/Src/Api/Template/Util/manage_ep.py +++ b/Src/Api/Template/Util/manage_ep.py @@ -37,54 +37,39 @@ def dynamic_format_number(n: int) -> str: def manage_selection(cmd_insert: str, max_count: int) -> List[int]: """ - Manage user selection for seasons to download. + Manage user selection for seasons or episodes to download. Parameters: - - cmd_insert (str): User input for season selection. - - max_count (int): Maximum count of seasons available. + - cmd_insert (str): User input for selection. + - max_count (int): Maximum count available. Returns: - list_season_select (List[int]): List of selected seasons. + list_selection (List[int]): List of selected items. """ - list_season_select = [] + list_selection = [] logging.info(f"Command insert: {cmd_insert}, end index: {max_count + 1}") # For a single number (e.g., '5') if cmd_insert.isnumeric(): - list_season_select.append(int(cmd_insert)) + list_selection.append(int(cmd_insert)) - # For a range (e.g., '[5-12]') - elif "[" in cmd_insert: - - # Extract the start and end parts - start, end = map(str.strip, cmd_insert[1:-1].split('-')) + # For a range (e.g., '5-12') + elif "-" in cmd_insert: + start, end = map(str.strip, cmd_insert.split('-')) start = int(start) + end = int(end) if end.isnumeric() else max_count - # If end is an integer, convert it - try: - end = int(end) - - except ValueError: - # end remains a string if conversion fails - pass - - # Generate the list_season_select based on the type of end - if isinstance(end, int): - list_season_select = list(range(start, end + 1)) - - elif end == "*": - list_season_select = list(range(start, max_count + 1)) - - else: - raise ValueError("Invalid end value") + list_selection = list(range(start, end + 1)) - # For all seasons + # For all items ('*') elif cmd_insert == "*": - list_season_select = list(range(1, max_count+1)) + list_selection = list(range(1, max_count + 1)) - # Return list of selected seasons) - logging.info(f"List return: {list_season_select}") - return list_season_select + else: + raise ValueError("Invalid input format") + + logging.info(f"List return: {list_selection}") + return list_selection def map_episode_title(tv_name: str, number_season: int, episode_number: int, episode_name: str) -> str: @@ -111,3 +96,51 @@ def map_episode_title(tv_name: str, number_season: int, episode_number: int, epi logging.info(f"Map episode string return: {map_episode_temp}") return map_episode_temp + + +# --> for season +def validate_selection(list_season_select: List[int], seasons_count: int) -> List[int]: + """ + Validates and adjusts the selected seasons based on the available seasons. + + Parameters: + - list_season_select (List[int]): List of seasons selected by the user. + - seasons_count (int): Total number of available seasons. + + Returns: + - List[int]: Adjusted list of valid season numbers. + """ + + # Remove any seasons greater than the available seasons + valid_seasons = [season for season in list_season_select if 1 <= season <= seasons_count] + + # If the list is empty, the input was completely invalid + if not valid_seasons: + print() + raise ValueError(f"Invalid selection: The selected seasons are outside the available range (1-{seasons_count}).") + + return valid_seasons + + +# --> for episode +def validate_episode_selection(list_episode_select: List[int], episodes_count: int) -> List[int]: + """ + Validates and adjusts the selected episodes based on the available episodes. + + Parameters: + - list_episode_select (List[int]): List of episodes selected by the user. + - episodes_count (int): Total number of available episodes in the season. + + Returns: + - List[int]: Adjusted list of valid episode numbers. + """ + + # Remove any episodes greater than the available episodes + valid_episodes = [episode for episode in list_episode_select if 1 <= episode <= episodes_count] + + # If the list is empty, the input was completely invalid + if not valid_episodes: + print() + raise ValueError(f"Invalid selection: The selected episodes are outside the available range (1-{episodes_count}).") + + return valid_episodes diff --git a/Src/Api/Template/__init__.py b/Src/Api/Template/__init__.py index 5fd5ea0..38a9119 100644 --- a/Src/Api/Template/__init__.py +++ b/Src/Api/Template/__init__.py @@ -2,4 +2,4 @@ from .site import get_select_title from .Util.get_domain import search_domain -from .Util.manage_ep import manage_selection, map_episode_title \ No newline at end of file +from .Util.manage_ep import manage_selection, map_episode_title, validate_episode_selection, validate_selection \ No newline at end of file diff --git a/Src/Api/altadefinizione/site.py b/Src/Api/altadefinizione/site.py index 019348b..24bfa11 100644 --- a/Src/Api/altadefinizione/site.py +++ b/Src/Api/altadefinizione/site.py @@ -38,8 +38,8 @@ def title_search(title_search: str) -> int: # Find new domain if prev dont work domain_to_use, _ = search_domain(SITE_NAME, f"https://{SITE_NAME}") - # Send request to search for titles - response = httpx.get(f"https://{SITE_NAME}.{domain_to_use}/page/1/?story={unidecode(title_search.replace(' ', '+'))}&do=search&subaction=search&titleonly=3", headers={'user-agent': get_headers()}, follow_redirects=True) + # Send request to search for title + response = httpx.get(f"https://{SITE_NAME}.{domain_to_use}/?story={unidecode(title_search.replace(' ', '+'))}&do=search&subaction=search&titleonly=3", headers={'user-agent': get_headers()}) response.raise_for_status() # Create soup and find table diff --git a/Src/Api/cb01/site.py b/Src/Api/cb01/site.py index 9448536..c7354e8 100644 --- a/Src/Api/cb01/site.py +++ b/Src/Api/cb01/site.py @@ -36,34 +36,30 @@ def title_search(word_to_search: str) -> int: Returns: - int: The number of titles found. """ - try: - # Find new domain if prev dont work - domain_to_use, _ = search_domain(SITE_NAME, f"https://{SITE_NAME}") + # Find new domain if prev dont work + domain_to_use, _ = search_domain(SITE_NAME, f"https://{SITE_NAME}") - # Send request to search for titles - response = httpx.get(f"https://{SITE_NAME}.{domain_to_use}/?s={unidecode(word_to_search)}", headers={'user-agent': get_headers()}, follow_redirects=True) - response.raise_for_status() + # Send request to search for titles + response = httpx.get(f"https://{SITE_NAME}.{domain_to_use}/?s={unidecode(word_to_search)}", headers={'user-agent': get_headers()}, follow_redirects=True) + response.raise_for_status() - # Create soup and find table - soup = BeautifulSoup(response.text, "html.parser") + # Create soup and find table + soup = BeautifulSoup(response.text, "html.parser") - for div_title in soup.find_all("div", class_ = "card"): + for div_title in soup.find_all("div", class_ = "card"): - url = div_title.find("h3").find("a").get("href") - title = div_title.find("h3").find("a").get_text(strip=True) - desc = div_title.find("p").find("strong").text + url = div_title.find("h3").find("a").get("href") + title = div_title.find("h3").find("a").get_text(strip=True) + desc = div_title.find("p").find("strong").text - title_info = { - 'name': title, - 'desc': desc, - 'url': url - } + title_info = { + 'name': title, + 'desc': desc, + 'url': url + } - media_search_manager.add_media(title_info) - - except Exception as err: - logging.error(f"An error occurred: {err}") + media_search_manager.add_media(title_info) # Return the number of titles found return media_search_manager.get_length() diff --git a/Src/Api/ddlstreamitaly/series.py b/Src/Api/ddlstreamitaly/series.py index e8de373..88b0847 100644 --- a/Src/Api/ddlstreamitaly/series.py +++ b/Src/Api/ddlstreamitaly/series.py @@ -12,7 +12,7 @@ from Src.Util.message import start_message from Src.Util.os import create_folder, can_create_file from Src.Util.table import TVShowManager from Src.Lib.Downloader import MP4_downloader -from ..Template import manage_selection, map_episode_title +from ..Template import manage_selection, map_episode_title, validate_episode_selection # Logic class @@ -27,6 +27,7 @@ table_show_manager = TVShowManager() video_source = VideoSource() + def download_video(scape_info_serie: GetSerieInfo, index_episode_selected: int) -> None: """ Download a single episode video. @@ -85,17 +86,19 @@ def download_thread(dict_serie: MediaItem): episodes_count = len(list_dict_episode) # Display episodes list and manage user selection - last_command = display_episodes_list(list_dict_episode) + last_command = display_episodes_list() list_episode_select = manage_selection(last_command, episodes_count) - # Download selected episodes - if len(list_episode_select) == 1 and last_command != "*": - download_video(scape_info_serie, list_episode_select[0]) + 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 + for i_episode in list_episode_select: + download_video(scape_info_serie, i_episode) - # Download all other episodes selecter - else: - for i_episode in list_episode_select: - download_video(scape_info_serie, i_episode) def display_episodes_list(obj_episode_manager) -> str: diff --git a/Src/Api/ddlstreamitaly/site.py b/Src/Api/ddlstreamitaly/site.py index c222b50..786e348 100644 --- a/Src/Api/ddlstreamitaly/site.py +++ b/Src/Api/ddlstreamitaly/site.py @@ -36,46 +36,42 @@ def title_search(word_to_search: str) -> int: Returns: - int: The number of titles found. """ - try: - # Find new domain if prev dont work - domain_to_use, _ = search_domain(SITE_NAME, f"https://{SITE_NAME}") + # Find new domain if prev dont work + domain_to_use, _ = search_domain(SITE_NAME, f"https://{SITE_NAME}") - # Send request to search for titles - response = httpx.get(f"https://{SITE_NAME}.{domain_to_use}/search/?&q={unidecode(word_to_search)}&quick=1&type=videobox_video&nodes=11", headers={'user-agent': get_headers()}) - response.raise_for_status() + # Send request to search for titles + response = httpx.get(f"https://{SITE_NAME}.{domain_to_use}/search/?&q={unidecode(word_to_search)}&quick=1&type=videobox_video&nodes=11", headers={'user-agent': get_headers()}) + response.raise_for_status() - # Create soup and find table - soup = BeautifulSoup(response.text, "html.parser") - table_content = soup.find('ol', class_="ipsStream") + # Create soup and find table + soup = BeautifulSoup(response.text, "html.parser") + table_content = soup.find('ol', class_="ipsStream") - if table_content: - for title_div in table_content.find_all('li', class_='ipsStreamItem'): - try: + if table_content: + for title_div in table_content.find_all('li', class_='ipsStreamItem'): + try: - title_type = title_div.find("p", class_="ipsType_reset").find_all("a")[-1].get_text(strip=True) - name = title_div.find("span", class_="ipsContained").find("a").get_text(strip=True) - link = title_div.find("span", class_="ipsContained").find("a").get("href") + title_type = title_div.find("p", class_="ipsType_reset").find_all("a")[-1].get_text(strip=True) + name = title_div.find("span", class_="ipsContained").find("a").get_text(strip=True) + link = title_div.find("span", class_="ipsContained").find("a").get("href") - title_info = { - 'name': name, - 'url': link, - 'type': title_type - } + title_info = { + 'name': name, + 'url': link, + 'type': title_type + } - media_search_manager.add_media(title_info) + media_search_manager.add_media(title_info) - except Exception as e: - logging.error(f"Error processing title div: {e}") + except Exception as e: + logging.error(f"Error processing title div: {e}") - return media_search_manager.get_length() - - else: - logging.error("No table content found.") - return -999 - - except Exception as err: - logging.error(f"An error occurred: {err}") + return media_search_manager.get_length() + + else: + logging.error("No table content found.") + return -999 return -9999 diff --git a/Src/Api/guardaserie/series.py b/Src/Api/guardaserie/series.py index 726ba62..c7f3752 100644 --- a/Src/Api/guardaserie/series.py +++ b/Src/Api/guardaserie/series.py @@ -11,7 +11,7 @@ from Src.Util.os import create_folder, can_create_file from Src.Util.message import start_message from Src.Util.table import TVShowManager from Src.Lib.Downloader import HLS_Downloader -from ..Template import manage_selection, map_episode_title +from ..Template import manage_selection, map_episode_title, validate_selection, validate_episode_selection # Logic class @@ -26,6 +26,7 @@ table_show_manager = TVShowManager() video_source = VideoSource() + def download_video(scape_info_serie: GetSerieInfo, index_season_selected: int, index_episode_selected: int) -> None: """ Download a single episode video. @@ -82,28 +83,28 @@ def download_episode(scape_info_serie: GetSerieInfo, index_season_selected: int, list_dict_episode = scape_info_serie.get_episode_number(index_season_selected) episodes_count = len(list_dict_episode) - # Download all episodes wihtout ask if download_all: - for i_episode in range(1, episodes_count+1): + + # Download all episodes without asking + for i_episode in range(1, episodes_count + 1): download_video(scape_info_serie, index_season_selected, i_episode) + console.print(f"\n[red]End downloaded [yellow]season: [red]{index_season_selected}.") - console.print(f"\n[red]Download [yellow]season: [red]{index_season_selected}.") - - # If not download all episode but a single season - if not download_all: + else: # Display episodes list and manage user selection last_command = display_episodes_list(scape_info_serie.list_episodes) list_episode_select = manage_selection(last_command, episodes_count) - # Download selected episodes - if len(list_episode_select) == 1 and last_command != "*": - download_video(scape_info_serie, index_season_selected, list_episode_select[0]) + try: + list_episode_select = validate_episode_selection(list_episode_select, episodes_count) + except ValueError as e: + console.print(f"[red]{str(e)}") + return - # Download all other episodes selecter - else: - for i_episode in list_episode_select: - download_video(scape_info_serie, index_season_selected, i_episode) + # Download selected episodes + for i_episode in list_episode_select: + download_video(scape_info_serie, index_season_selected, i_episode) def download_series(dict_serie: MediaItem) -> None: @@ -124,24 +125,31 @@ def download_series(dict_serie: MediaItem) -> None: seasons_count = scape_info_serie.get_seasons_number() # Prompt user for season selection and download episodes - console.print(f"\n[green]Season find: [red]{seasons_count}") - index_season_selected = msg.ask("\n[cyan]Insert media [red]index [yellow]or [red](*) [cyan]to download all media [yellow]or [red][1-2] [cyan]or [red][3-*] [cyan]for a range of media") + 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) - # Download selected episodes - if len(list_season_select) == 1 and index_season_selected != "*": - if 1 <= int(index_season_selected) <= seasons_count: - download_episode(scape_info_serie, list_season_select[0]) + try: + list_season_select = validate_selection(list_season_select, seasons_count) + except ValueError as e: + console.print(f"[red]{str(e)}") + return - # Dowload all seasons and episodes - elif index_season_selected == "*": - for i_season in list_season_select: - download_episode(scape_info_serie, i_season, True) + # 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 other season selecter - else: - for i_season in list_season_select: - download_episode(scape_info_serie, i_season) + # Download all episodes if multiple seasons are selected or if '*' is used + download_episode(scape_info_serie, i_season, download_all=True) + else: + + # Otherwise, let the user select specific episodes for the single season + download_episode(scape_info_serie, i_season, download_all=False) def display_episodes_list(obj_episode_manager) -> str: diff --git a/Src/Api/mostraguarda/__init__.py b/Src/Api/mostraguarda/__init__.py new file mode 100644 index 0000000..ba075f0 --- /dev/null +++ b/Src/Api/mostraguarda/__init__.py @@ -0,0 +1,36 @@ +# 26.05.24 + +# Internal utilities +from Src.Util.console import console, msg + + +# Logic class +from Src.Lib.TMBD import tmdb, Json_film +from .film import download_film + + +# Variable +indice = 9 +_deprecate = False + + +def search(): + """ + Main function of the application for film and series. + """ + + # Make request to site to get content that corrsisponde to that string + string_to_search = msg.ask("\n[purple]Insert word to search in all site").strip() + movie_id = tmdb.search_movie(string_to_search) + + if movie_id: + movie_details: Json_film = tmdb.get_movie_details(tmdb_id=movie_id) + + # Download only film + download_film(movie_details) + + else: + console.print(f"\n[red]Nothing matching was found for[white]: [purple]{string_to_search}") + + # Retry + search() diff --git a/Src/Api/mostraguarda/costant.py b/Src/Api/mostraguarda/costant.py new file mode 100644 index 0000000..9173fc4 --- /dev/null +++ b/Src/Api/mostraguarda/costant.py @@ -0,0 +1,15 @@ +# 26.05.24 + +import os + + +# Internal utilities +from Src.Util._jsonConfig import config_manager + + +SITE_NAME = os.path.basename(os.path.dirname(os.path.abspath(__file__))) +ROOT_PATH = config_manager.get('DEFAULT', 'root_path') +DOMAIN_NOW = config_manager.get_dict('SITE', SITE_NAME)['domain'] + +MOVIE_FOLDER = "Movie" +SERIES_FOLDER= "Serie" \ No newline at end of file diff --git a/Src/Api/mostraguarda/film.py b/Src/Api/mostraguarda/film.py new file mode 100644 index 0000000..3bdeec1 --- /dev/null +++ b/Src/Api/mostraguarda/film.py @@ -0,0 +1,74 @@ +# 17.09.24 + +import os +import sys +import logging + + +# External libraries +import httpx +from bs4 import BeautifulSoup + + +# Internal utilities +from Src.Util.message import start_message +from Src.Util.console import console +from Src.Util.os import create_folder, can_create_file, remove_special_characters +from Src.Util.headers import get_headers +from Src.Lib.Downloader import HLS_Downloader + + +# Logic class +from ..Template.Class.SearchType import MediaItem +from ..guardaserie.Player.supervideo import VideoSource +from Src.Lib.TMBD import Json_film + + +# Config +from .costant import ROOT_PATH, SITE_NAME, DOMAIN_NOW, MOVIE_FOLDER + + + +def download_film(movie_details: Json_film): + """ + Downloads a film using the provided tmbd id. + + Parameters: + - movie_details (Json_film): Class with info about film title. + """ + + # Start message and display film information + start_message() + console.print(f"[yellow]Download: [red]{movie_details.title} \n") + + # Make request to main site + url = f"https://{SITE_NAME}.{DOMAIN_NOW}/set-movie-a/{movie_details.imdb_id}" + response = httpx.get(url, headers={'User-Agent': get_headers()}) + response.raise_for_status() + + # Extract supervideo url + soup = BeautifulSoup(response.text, "html.parser") + player_links = soup.find("ul", class_ = "_player-mirrors").find_all("li") + supervideo_url = "https:" + player_links[0].get("data-link") + + # Set domain and media ID for the video source + video_source = VideoSource() + video_source.setup(supervideo_url) + + # Define output path + mp4_name = remove_special_characters(movie_details.title) + ".mp4" + mp4_path = os.path.join(ROOT_PATH, SITE_NAME, MOVIE_FOLDER, remove_special_characters(movie_details.title)) + + # Check if the MP4 file can be created + if not can_create_file(mp4_name): + logging.error("Invalid mp4 name.") + sys.exit(0) + + # Get m3u8 master playlist + master_playlist = video_source.get_playlist() + + # Download the film using the m3u8 playlist, and output filename + HLS_Downloader( + m3u8_playlist = master_playlist, + output_filename = os.path.join(mp4_path, mp4_name) + ).start() diff --git a/Src/Api/streamingcommunity/series.py b/Src/Api/streamingcommunity/series.py index fbd2733..36db672 100644 --- a/Src/Api/streamingcommunity/series.py +++ b/Src/Api/streamingcommunity/series.py @@ -10,7 +10,7 @@ from Src.Util.console import console, msg from Src.Util.message import start_message from Src.Util.table import TVShowManager from Src.Lib.Downloader import HLS_Downloader -from ..Template import manage_selection, map_episode_title +from ..Template import manage_selection, map_episode_title, validate_selection, validate_episode_selection # Logic class @@ -23,7 +23,8 @@ from .costant import ROOT_PATH, SITE_NAME, SERIES_FOLDER video_source = VideoSource() table_show_manager = TVShowManager() -# download_video + + def download_video(tv_name: str, index_season_selected: int, index_episode_selected: int) -> None: """ Download a single episode video. @@ -59,54 +60,55 @@ def download_video(tv_name: str, index_season_selected: int, index_episode_selec def download_episode(tv_name: str, index_season_selected: int, download_all: bool = False) -> None: """ - Download all episodes of a season. + Download episodes of a selected season. Parameters: - tv_name (str): Name of the TV series. - index_season_selected (int): Index of the selected season. - - download_all (bool): Download all seasons episodes + - download_all (bool): Download all episodes in the season. """ - # Clean memory of all episodes and get the number of the season (some dont follow rule of [1,2,3,4,5] but [1,2,3,145,5,6,7]). + # Clean memory of all episodes and get the number of the season video_source.obj_episode_manager.clear() - season_number = (video_source.obj_season_manager.seasons[index_season_selected-1].number) + season_number = video_source.obj_season_manager.seasons[index_season_selected - 1].number # Start message and collect information about episodes start_message() video_source.collect_title_season(season_number) episodes_count = video_source.obj_episode_manager.get_length() - # Download all episodes wihtout ask if download_all: - for i_episode in range(1, episodes_count+1): + + # Download all episodes without asking + for i_episode in range(1, episodes_count + 1): download_video(tv_name, index_season_selected, i_episode) + console.print(f"\n[red]End downloaded [yellow]season: [red]{index_season_selected}.") - console.print(f"\n[red]Download [yellow]season: [red]{index_season_selected}.") - - # If not download all episode but a single season - if not download_all: + else: # Display episodes list and manage user selection last_command = display_episodes_list() list_episode_select = manage_selection(last_command, episodes_count) - # Download selected episodes - if len(list_episode_select) == 1 and last_command != "*": - download_video(tv_name, index_season_selected, list_episode_select[0]) + try: + list_episode_select = validate_episode_selection(list_episode_select, episodes_count) + except ValueError as e: + console.print(f"[red]{str(e)}") + return - # Download all other episodes selecter - else: - for i_episode in list_episode_select: - download_video(tv_name, index_season_selected, i_episode) + # Download selected episodes + for i_episode in list_episode_select: + download_video(tv_name, index_season_selected, i_episode) def download_series(select_title: MediaItem, domain: str, version: str) -> None: """ - Download all episodes of a TV series. + Download episodes of a TV series based on user selection. Parameters: - - version (str): Version of site. + - select_title (MediaItem): Selected media item (TV series). - domain (str): Domain from which to download. + - version (str): Version of the site. """ # Start message and set up video source @@ -120,24 +122,31 @@ def download_series(select_title: MediaItem, domain: str, version: str) -> None: seasons_count = video_source.obj_season_manager.get_length() # Prompt user for season selection and download episodes - console.print(f"\n[green]Season find: [red]{seasons_count}") - index_season_selected = msg.ask("\n[cyan]Insert media [red]index [yellow]or [red](*) [cyan]to download all media [yellow]or [red][1-2] [cyan]or [red][3-*] [cyan]for a range of media") + 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) - # Download selected episodes - if len(list_season_select) == 1 and index_season_selected != "*": - if 1 <= int(index_season_selected) <= seasons_count: - download_episode(select_title.slug, list_season_select[0]) + try: + list_season_select = validate_selection(list_season_select, seasons_count) + except ValueError as e: + console.print(f"[red]{str(e)}") + return - # Dowload all seasons and episodes - elif index_season_selected == "*": - for i_season in list_season_select: - download_episode(select_title.slug, i_season, True) + # 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 other season selecter - else: - for i_season in list_season_select: - download_episode(select_title.slug, i_season) + # Download all episodes if multiple seasons are selected or if '*' is used + download_episode(select_title.slug, i_season, download_all=True) + else: + + # Otherwise, let the user select specific episodes for the single season + download_episode(select_title.slug, i_season, download_all=False) def display_episodes_list() -> str: diff --git a/Src/Api/uhdmovies/__init__.py b/Src/Api/uhdmovies/__init__.py index b67aa7a..49cc4b3 100644 --- a/Src/Api/uhdmovies/__init__.py +++ b/Src/Api/uhdmovies/__init__.py @@ -12,7 +12,7 @@ from .serie import download_serie # Variable indice = 6 -_deprecate = False +_deprecate = True def search(): diff --git a/Src/Api/uhdmovies/serie.py b/Src/Api/uhdmovies/serie.py index 2cad7a9..b972bf8 100644 --- a/Src/Api/uhdmovies/serie.py +++ b/Src/Api/uhdmovies/serie.py @@ -12,7 +12,7 @@ from Src.Util.message import start_message from Src.Util.os import create_folder, can_create_file from Src.Util.table import TVShowManager from Src.Lib.Downloader import MP4_downloader -from ..Template import manage_selection, map_episode_title +from ..Template import manage_selection, map_episode_title, validate_selection, validate_episode_selection # Logic class @@ -27,6 +27,7 @@ from .costant import ROOT_PATH, SITE_NAME, SERIES_FOLDER table_show_manager = TVShowManager() + def download_video(api_manager: ApiManager, index_season_selected: int, index_episode_selected: int) -> None: """ Download a single episode video. @@ -87,34 +88,32 @@ def download_episode(api_manager: ApiManager, index_season_selected: int, downlo season_name = api_manager.obj_season_manager.seasons[index_season_selected-1].name # Collect all best episode + start_message() api_manager.collect_episode(season_name) episodes_count = api_manager.obj_episode_manager.get_length() - # Start message - start_message() - - # Download all episodes wihtout ask if download_all: - for i_episode in range(1, episodes_count+1): + + # Download all episodes without asking + for i_episode in range(1, episodes_count + 1): download_video(api_manager, index_season_selected, i_episode) + console.print(f"\n[red]End downloaded [yellow]season: [red]{index_season_selected}.") - console.print(f"\n[red]Download [yellow]season: [red]{index_season_selected}.") - - # If not download all episode but a single season - if not download_all: + else: # Display episodes list and manage user selection - last_command = display_episodes_list(api_manager) + last_command = display_episodes_list() list_episode_select = manage_selection(last_command, episodes_count) - # Download selected episodes - if len(list_episode_select) == 1 and last_command != "*": - download_video(api_manager, index_season_selected, list_episode_select[0]) + try: + list_episode_select = validate_episode_selection(list_episode_select, episodes_count) + except ValueError as e: + console.print(f"[red]{str(e)}") + return - # Download all other episodes selecter - else: - for i_episode in list_episode_select: - download_video(api_manager, index_season_selected, i_episode) + # Download selected episodes + for i_episode in list_episode_select: + download_video(api_manager, index_season_selected, i_episode) def download_serie(media: MediaItem): @@ -137,25 +136,31 @@ def download_serie(media: MediaItem): if seasons_count > 0: # Prompt user for season selection and download episodes - console.print(f"\n[green]Season find: [red]{seasons_count}") + 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" + ) - index_season_selected = msg.ask("\n[cyan]Insert media [red]index [yellow]or [red](*) [cyan]to download all media [yellow]or [red][1-2] [cyan]or [red][3-*] [cyan]for a range of media") + # Manage and validate the selection list_season_select = manage_selection(index_season_selected, seasons_count) - # Download selected episodes - if len(list_season_select) == 1 and index_season_selected != "*": - if 1 <= int(index_season_selected) <= seasons_count: - download_episode(api_manager, list_season_select[0]) + try: + list_season_select = validate_selection(list_season_select, seasons_count) + except ValueError as e: + console.print(f"[red]{str(e)}") + return - # Dowload all seasons and episodes - elif index_season_selected == "*": - for i_season in list_season_select: - download_episode(api_manager, i_season, True) + # 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 other season selecter - else: - for i_season in list_season_select: - download_episode(api_manager, i_season) + # Download all episodes if multiple seasons are selected or if '*' is used + download_episode(api_manager, i_season, download_all=True) + else: + + # Otherwise, let the user select specific episodes for the single season + download_episode(api_manager, i_season, download_all=False) else: diff --git a/Src/Lib/Downloader/HLS/segments.py b/Src/Lib/Downloader/HLS/segments.py index 004eb23..e7c45f6 100644 --- a/Src/Lib/Downloader/HLS/segments.py +++ b/Src/Lib/Downloader/HLS/segments.py @@ -36,12 +36,6 @@ from ...M3U8 import ( ) from .proxyes import main_test_proxy - -# Warning -import urllib3 -urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) - - # Config TQDM_DELAY_WORKER = config_manager.get_float('M3U8_DOWNLOAD', 'tqdm_delay') TQDM_USE_LARGE_BAR = config_manager.get_int('M3U8_DOWNLOAD', 'tqdm_use_large_bar') @@ -300,7 +294,7 @@ class M3U8_Segments: progress_bar.update(1) except Exception as e: - console.print(f"Failed to download '{ts_url}', status error: {e}.") + console.print(f"Failed download: '{ts_url}' with error: {e}") def write_segments_to_file(self): """ diff --git a/Src/Lib/FFmpeg/command.py b/Src/Lib/FFmpeg/command.py index 7485d83..6cb0cb6 100644 --- a/Src/Lib/FFmpeg/command.py +++ b/Src/Lib/FFmpeg/command.py @@ -254,7 +254,7 @@ def join_subtitle(video_path: str, subtitles_list: List[Dict[str, str]], out_pat # Add subtitle maps and metadata for idx, subtitle in enumerate(subtitles_list): ffmpeg_cmd += ["-map", f"{idx + 1}:s"] - ffmpeg_cmd += ["-metadata:s:s:{}".format(idx), "title={}".format(subtitle['name'])] + ffmpeg_cmd += ["-metadata:s:s:{}".format(idx), "title={}".format(subtitle['language'])] # Add output Parameters if USE_CODEC: diff --git a/Src/Lib/TMBD/__init__.py b/Src/Lib/TMBD/__init__.py new file mode 100644 index 0000000..c73b5f1 --- /dev/null +++ b/Src/Lib/TMBD/__init__.py @@ -0,0 +1,2 @@ +from .tmdb import tmdb +from .obj_tmbd import Json_film \ No newline at end of file diff --git a/Src/Lib/TMBD/obj_tmbd.py b/Src/Lib/TMBD/obj_tmbd.py new file mode 100644 index 0000000..2948e14 --- /dev/null +++ b/Src/Lib/TMBD/obj_tmbd.py @@ -0,0 +1,40 @@ +# 17.09.24 + +import json +from typing import List, Dict, Optional + + +class Json_film: + def __init__(self, data: Dict): + self.adult = data.get('adult', False) + self.backdrop_path = data.get('backdrop_path') + self.budget = data.get('budget', 0) + self.homepage = data.get('homepage') + self.id = data.get('id', 0) + self.imdb_id = data.get('imdb_id') + self.origin_country = data.get('origin_country', []) + self.original_language = data.get('original_language') + self.original_title = data.get('original_title') + self.overview = data.get('overview') + self.popularity = data.get('popularity', 0.0) + self.poster_path = data.get('poster_path') + self.release_date = data.get('release_date') + self.revenue = data.get('revenue', 0) + self.runtime = data.get('runtime', 0) + self.status = data.get('status') + self.tagline = data.get('tagline') + self.title = data.get('title') + self.video = data.get('video', False) + self.vote_average = data.get('vote_average', 0.0) + self.vote_count = data.get('vote_count', 0) + + def __repr__(self): + return (f"Film(adult={self.adult}, backdrop_path='{self.backdrop_path}', " + f"budget={self.budget}, " + f"homepage='{self.homepage}', id={self.id}, " + f"imdb_id='{self.imdb_id}', origin_country={self.origin_country}, " + f"original_language='{self.original_language}', original_title='{self.original_title}', " + f"overview='{self.overview}', popularity={self.popularity}, poster_path='{self.poster_path}', " + f"release_date='{self.release_date}', revenue={self.revenue}, runtime={self.runtime}, " + f"status='{self.status}', tagline='{self.tagline}', " + f"title='{self.title}', video={self.video}, vote_average={self.vote_average}, vote_count={self.vote_count})") diff --git a/Src/Lib/TMBD/tmbd.py b/Src/Lib/TMBD/tmbd.py deleted file mode 100644 index 44d49e1..0000000 --- a/Src/Lib/TMBD/tmbd.py +++ /dev/null @@ -1,138 +0,0 @@ -# 24.08.24 - -from typing import Dict - - -# External libraries -import httpx -from rich.console import Console - - -# Internal utilities -from Src.Util.table import TVShowManager - - -# Variable -tv_show_manager = TVShowManager() -api_key = "a800ed6c93274fb857ea61bd9e7256c5" - - -class TheMovieDB: - def __init__(self, api_key): - """ - Initialize the class with the API key and TV show manager. - - Parameters: - - api_key (str): The API key for authenticating requests to TheMovieDB. - - tv_show_manager (TVShowManager): An instance of the TVShowManager for handling TV show items. - """ - self.api_key = api_key - self.base_url = "https://api.themoviedb.org/3" - self.console = Console() - self.genres = self._fetch_genres() - - def _make_request(self, endpoint, params=None): - """ - Make a request to the given API endpoint with optional parameters. - - Parameters: - - endpoint (str): The API endpoint to hit. - - params (dict): Additional parameters for the request. - - Returns: - dict: JSON response as a dictionary. - """ - if params is None: - params = {} - - params['api_key'] = self.api_key - url = f"{self.base_url}/{endpoint}" - response = httpx.get(url, params=params) - response.raise_for_status() - - return response.json() - - def _fetch_genres(self) -> Dict[int, str]: - """ - Fetch and return the genre names from TheMovieDB. - - Returns: - Dict[int, str]: A dictionary mapping genre IDs to genre names. - """ - genres = self._make_request("genre/tv/list") - return {genre['id']: genre['name'] for genre in genres.get('genres', [])} - - def _process_and_add_tv_shows(self, data, columns): - """ - Process TV show data and add it to the TV show manager. - - Parameters: - - data (list): List of dictionaries containing the data to process. - - columns (list): A list of tuples, where each tuple contains the column name and the key to fetch the data from the dictionary. - """ - # Define column styles with colors - tv_show_manager = TVShowManager() - column_info = { - col[0]: {'color': col[2] if len(col) > 2 else 'white'} - for col in columns - } - tv_show_manager.add_column(column_info) - - # Add each item to the TV show manager, including rank - for index, item in enumerate(data): - - # Convert genre IDs to genre names - genre_names = [self.genres.get(genre_id, 'Unknown') for genre_id in item.get('genre_ids', [])] - tv_show = { - col[0]: str(item.get(col[1], 'N/A')) if col[1] != 'genre_ids' else ', '.join(genre_names) - for col in columns - } - - tv_show_manager.add_tv_show(tv_show) - - # Display the processed TV show data - tv_show_manager.display_data(tv_show_manager.tv_shows[tv_show_manager.slice_start:tv_show_manager.slice_end]) - - def _display_with_title(self, title: str, data, columns): - """ - Display data with a title. - - Parameters: - - title (str): The title to display. - - data (list): List of dictionaries containing the data to process. - - columns (list): A list of tuples, where each tuple contains the column name and the key to fetch the data from the dictionary. - """ - self.console.print(f"\n{title}", style="bold underline") - self._process_and_add_tv_shows(data, columns) - - def display_trending_tv_shows(self): - """ - Fetch and display the trending TV shows of the week. - """ - data = self._make_request("trending/tv/week").get("results", []) - columns = [ - ("Title", "name", 'cyan'), - ("First Air Date", "first_air_date", 'green'), - ("Popularity", "popularity", 'magenta'), - ("Genres", "genre_ids", 'blue'), - ("Origin Country", "origin_country", 'red'), - ("Vote Average", "vote_average", 'yellow') - ] - self._display_with_title("Trending TV Shows of the Week", data, columns) - - def display_trending_films(self): - """ - Fetch and display the trending films of the week. - """ - data = self._make_request("trending/movie/week").get("results", []) - columns = [ - ("Title", "title", 'cyan'), - ("Release Date", "release_date", 'green'), - ("Popularity", "popularity", 'magenta'), - ("Genres", "genre_ids", 'blue'), - ("Vote Average", "vote_average", 'yellow') - ] - self._display_with_title("Trending Films of the Week", data, columns) - -# Output -tmdb = TheMovieDB(api_key) \ No newline at end of file diff --git a/Src/Lib/TMBD/tmdb.py b/Src/Lib/TMBD/tmdb.py new file mode 100644 index 0000000..7629d18 --- /dev/null +++ b/Src/Lib/TMBD/tmdb.py @@ -0,0 +1,346 @@ +# 24.08.24 + +import sys +from typing import Dict + + +# External libraries +import httpx +from rich.console import Console + + +# Internal utilities +from .obj_tmbd import Json_film +from Src.Util.table import TVShowManager + + +# Variable +table_show_manager = TVShowManager() +api_key = "a800ed6c93274fb857ea61bd9e7256c5" + + + +def get_select_title(table_show_manager, generic_obj): + """ + Display a selection of titles and prompt the user to choose one. + + Returns: + dict: The selected media item. + """ + + # Set up table for displaying titles + table_show_manager.set_slice_end(10) + + # Check if the generic_obj list is empty + if not generic_obj: + Console.print("\n[red]No media items available.") + return None + + # Example of available colors for columns + available_colors = ['red', 'magenta', 'yellow', 'cyan', 'green', 'blue', 'white'] + + # Retrieve the keys of the first item as column headers + first_item = generic_obj[0] + column_info = {"Index": {'color': available_colors[0]}} # Always include Index with a fixed color + + # Assign colors to the remaining keys dynamically + color_index = 1 + for key in first_item.keys(): + if key in ('name', 'date', 'number'): # Custom prioritization of colors + if key == 'name': + column_info["Name"] = {'color': 'magenta'} + elif key == 'date': + column_info["Date"] = {'color': 'cyan'} + elif key == 'number': + column_info["Number"] = {'color': 'yellow'} + + else: + column_info[key.capitalize()] = {'color': available_colors[color_index % len(available_colors)]} + color_index += 1 + + table_show_manager.add_column(column_info) + + # Populate the table with title information + for i, item in enumerate(generic_obj): + item_dict = {'Index': str(i)} + + for key in item.keys(): + # Ensure all values are strings for rich add table + item_dict[key.capitalize()] = str(item[key]) + + table_show_manager.add_tv_show(item_dict) + + # Run the table and handle user input + last_command = table_show_manager.run(force_int_input=True, max_int_input=len(generic_obj)) + table_show_manager.clear() + + # Handle user's quit command + if last_command == "q": + Console.print("\n[red]Quit [white]...") + sys.exit(0) + + # Check if the selected index is within range + if 0 <= int(last_command) < len(generic_obj): + return generic_obj[int(last_command)] + + else: + Console.print("\n[red]Wrong index") + sys.exit(0) + + +class TheMovieDB: + def __init__(self, api_key): + """ + Initialize the class with the API key. + + Parameters: + - api_key (str): The API key for authenticating requests to TheMovieDB. + """ + self.api_key = api_key + self.base_url = "https://api.themoviedb.org/3" + self.console = Console() + self.genres = self._fetch_genres() + + def _make_request(self, endpoint, params=None): + """ + Make a request to the given API endpoint with optional parameters. + + Parameters: + - endpoint (str): The API endpoint to hit. + - params (dict): Additional parameters for the request. + + Returns: + dict: JSON response as a dictionary. + """ + if params is None: + params = {} + + params['api_key'] = self.api_key + url = f"{self.base_url}/{endpoint}" + response = httpx.get(url, params=params) + response.raise_for_status() + + return response.json() + + def _fetch_genres(self) -> Dict[int, str]: + """ + Fetch and return the genre names from TheMovieDB. + + Returns: + Dict[int, str]: A dictionary mapping genre IDs to genre names. + """ + genres = self._make_request("genre/movie/list") + return {genre['id']: genre['name'] for genre in genres.get('genres', [])} + + def _process_and_add_tv_shows(self, data, columns): + """ + Process TV show data and add it to the TV show manager. + + Parameters: + - data (list): List of dictionaries containing the data to process. + - columns (list): A list of tuples, where each tuple contains the column name and the key to fetch the data from the dictionary. + """ + # Define column styles with colors + tv_show_manager = TVShowManager() + column_info = { + col[0]: {'color': col[2] if len(col) > 2 else 'white'} + for col in columns + } + tv_show_manager.add_column(column_info) + + # Add each item to the TV show manager, including rank + for index, item in enumerate(data): + + # Convert genre IDs to genre names + genre_names = [self.genres.get(genre_id, 'Unknown') for genre_id in item.get('genre_ids', [])] + tv_show = { + col[0]: str(item.get(col[1], 'N/A')) if col[1] != 'genre_ids' else ', '.join(genre_names) + for col in columns + } + + tv_show_manager.add_tv_show(tv_show) + + # Display the processed TV show data + tv_show_manager.display_data(tv_show_manager.tv_shows[tv_show_manager.slice_start:tv_show_manager.slice_end]) + + def _display_with_title(self, title: str, data, columns): + """ + Display data with a title. + + Parameters: + - title (str): The title to display. + - data (list): List of dictionaries containing the data to process. + - columns (list): A list of tuples, where each tuple contains the column name and the key to fetch the data from the dictionary. + """ + self.console.print(f"\n{title}", style="bold underline") + self._process_and_add_tv_shows(data, columns) + + def display_trending_tv_shows(self): + """ + Fetch and display the trending TV shows of the week. + """ + data = self._make_request("trending/tv/week").get("results", []) + columns = [ + ("Title", "name", 'cyan'), + ("First Air Date", "first_air_date", 'green'), + ("Popularity", "popularity", 'magenta'), + ("Genres", "genre_ids", 'blue'), + ("Origin Country", "origin_country", 'red'), + ("Vote Average", "vote_average", 'yellow') + ] + self._display_with_title("Trending TV Shows of the Week", data, columns) + + def display_trending_films(self): + """ + Fetch and display the trending films of the week. + """ + data = self._make_request("trending/movie/week").get("results", []) + columns = [ + ("Title", "title", 'cyan'), + ("Release Date", "release_date", 'green'), + ("Popularity", "popularity", 'magenta'), + ("Genres", "genre_ids", 'blue'), + ("Vote Average", "vote_average", 'yellow') + ] + self._display_with_title("Trending Films of the Week", data, columns) + + def search_movie(self, movie_name: str): + """ + Search for a movie by name and return its TMDB ID. + + Parameters: + - movie_name (str): The name of the movie to search for. + + Returns: + int: The TMDB ID of the selected movie. + """ + generic_obj = [] + data = self._make_request("search/movie", {"query": movie_name}).get("results", []) + if not data: + self.console.print("No movies found with that name.", style="red") + return None + + self.console.print("\nSelect a Movie:") + for i, movie in enumerate(data, start=1): + generic_obj.append({ + 'name': movie['title'], + 'date': movie.get('release_date', 'N/A'), + 'id': movie['id'] + }) + + choice = get_select_title(table_show_manager, generic_obj) + return choice["id"] + + def get_movie_details(self, tmdb_id: int) -> Json_film: + """ + Fetch and display details for a specific movie using its TMDB ID. + + Parameters: + - tmdb_id (int): The TMDB ID of the movie. + + Returns: + - Json_film: The movie details as a class. + """ + movie = self._make_request(f"movie/{tmdb_id}") + if not movie: + self.console.print("Movie not found.", style="red") + return None + + return Json_film(movie) + + def search_tv_show(self, tv_name: str): + """ + Search for a TV show by name and return its TMDB ID. + + Parameters: + - tv_name (str): The name of the TV show to search for. + + Returns: + int: The TMDB ID of the selected TV show. + """ + data = self._make_request("search/tv", {"query": tv_name}).get("results", []) + if not data: + self.console.print("No TV shows found with that name.", style="red") + return None + + self.console.print("\nSelect a TV Show:") + for i, show in enumerate(data, start=1): + self.console.print(f"{i}. {show['name']} (First Air Date: {show.get('first_air_date', 'N/A')})") + + choice = int(input("Enter the number of the show you want: ")) - 1 + selected_show = data[choice] + return selected_show["id"] # Return the TMDB ID of the selected TV show + + def get_seasons(self, tv_show_id: int): + """ + Get seasons for a given TV show. + + Parameters: + - tv_show_id (int): The TMDB ID of the TV show. + + Returns: + int: The season number selected by the user. + """ + data = self._make_request(f"tv/{tv_show_id}").get("seasons", []) + if not data: + self.console.print("No seasons found for this TV show.", style="red") + return None + + self.console.print("\nSelect a Season:") + for i, season in enumerate(data, start=1): + self.console.print(f"{i}. {season['name']} (Episodes: {season['episode_count']})") + + choice = int(input("Enter the number of the season you want: ")) - 1 + return data[choice]["season_number"] + + def get_episodes(self, tv_show_id: int, season_number: int): + """ + Get episodes for a given season of a TV show. + + Parameters: + - tv_show_id (int): The TMDB ID of the TV show. + - season_number (int): The season number. + + Returns: + dict: The details of the selected episode. + """ + data = self._make_request(f"tv/{tv_show_id}/season/{season_number}").get("episodes", []) + if not data: + self.console.print("No episodes found for this season.", style="red") + return None + + self.console.print("\nSelect an Episode:") + for i, episode in enumerate(data, start=1): + self.console.print(f"{i}. {episode['name']} (Air Date: {episode.get('air_date', 'N/A')})") + + choice = int(input("Enter the number of the episode you want: ")) - 1 + return data[choice] + + + +# Output +tmdb = TheMovieDB(api_key) + + +""" +Example: + + +@ movie +movie_name = "Interstellar" +movie_id = tmdb.search_movie(movie_name) + +if movie_id: + movie_details = tmdb.get_movie_details(tmdb_id=movie_id) + print(movie_details) + + +@ series +tv_name = "Game of Thrones" +tv_show_id = tmdb.search_tv_show(tv_name) +if tv_show_id: + season_number = tmdb.get_seasons(tv_show_id=tv_show_id) + if season_number: + episode = tmdb.get_episodes(tv_show_id=tv_show_id, season_number=season_number) + print(episode) +""" \ No newline at end of file diff --git a/Src/Upload/update.py b/Src/Upload/update.py index d408854..9f20270 100644 --- a/Src/Upload/update.py +++ b/Src/Upload/update.py @@ -62,3 +62,4 @@ def update(): [cyan]Help the repository grow today by leaving a [yellow]star [cyan]and [yellow]sharing [cyan]it with others online!") console.print("\n") + time.sleep(4) diff --git a/Src/Upload/version.py b/Src/Upload/version.py index a8e15ba..49ce8c6 100644 --- a/Src/Upload/version.py +++ b/Src/Upload/version.py @@ -1,5 +1,5 @@ __title__ = 'StreamingCommunity' -__version__ = 'v1.3.0' +__version__ = 'v1.4.0' __author__ = 'Lovi-0' __description__ = 'A command-line program to download film' __copyright__ = 'Copyright 2024' diff --git a/Src/Util/headers.py b/Src/Util/headers.py index 81fecdb..ab63a5f 100644 --- a/Src/Util/headers.py +++ b/Src/Util/headers.py @@ -6,11 +6,11 @@ import logging # External library -import fake_useragent +from fake_useragent import UserAgent # Variable -useragent = fake_useragent.UserAgent() +ua = UserAgent() def extract_versions(user_agent): @@ -95,7 +95,7 @@ def random_headers(referer: str = None): Returns: dict: Generated HTTP headers. """ - user_agent = useragent.random + user_agent = ua.random versions = extract_versions(user_agent) platform = get_platform(user_agent) model = get_model(user_agent) @@ -145,4 +145,4 @@ def get_headers() -> str: """ # Get a random user agent string from the user agent rotator - return useragent.random + return str(ua.chrome) diff --git a/Src/Util/table.py b/Src/Util/table.py index f08b7d1..9d5da81 100644 --- a/Src/Util/table.py +++ b/Src/Util/table.py @@ -75,11 +75,13 @@ class TVShowManager: # Add rows dynamically based on available TV show data for entry in data_slice: - row_data = [entry[col_name] for col_name in self.column_info.keys()] + # Create row data while handling missing keys + 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 + def run(self, force_int_input: bool = False, max_int_input: int = 0) -> str: """ Run the TV show manager application. @@ -105,7 +107,10 @@ class TVShowManager: self.console.print(f"\n\n[yellow][INFO] [green]Press [red]Enter [green]for next page, or [red]'q' [green]to quit.") if not force_int_input: - key = Prompt.ask("\n[cyan]Insert media [red]index [yellow]or [red](*) [cyan]to download all media [yellow]or [red][1-2] [cyan]or [red][3-*] [cyan]for a range of media") + key = Prompt.ask( + "\n[cyan]Insert media index [yellow](e.g., 1), [red]* [cyan]to download all media, " + "[yellow](e.g., 1-2) [cyan]for a range of media, or [yellow](e.g., 3-*) [cyan]to download from a specific index to the end" + ) else: choices = [str(i) for i in range(0, max_int_input)] @@ -130,7 +135,11 @@ class TVShowManager: # Last slice, ensure all remaining items are shown self.console.print(f"\n\n[yellow][INFO] [red]You've reached the end. [green]Press [red]Enter [green]for next page, or [red]'q' [green]to quit.") if not force_int_input: - key = Prompt.ask("\n[cyan]Insert media [red]index [yellow]or [red](*) [cyan]to download all media [yellow]or [red][1-2] [cyan]or [red][3-*] [cyan]for a range of media") + key = Prompt.ask( + "\n[cyan]Insert media index [yellow](e.g., 1), [red]* [cyan]to download all media, " + "[yellow](e.g., 1-2) [cyan]for a range of media, or [yellow](e.g., 3-*) [cyan]to download from a specific index to the end" + ) + else: choices = [str(i) for i in range(0, max_int_input)] diff --git a/config.json b/config.json index e9e6d02..ec0eb5e 100644 --- a/config.json +++ b/config.json @@ -58,13 +58,11 @@ }, "SITE": { "streamingcommunity": { - "video_workers": 2, - "audio_workers": 2, + "video_workers": 6, + "audio_workers": 6, "domain": "buzz" }, "animeunity": { - "video_workers": 2, - "audio_workers": 2, "domain": "to" }, "altadefinizione": { @@ -75,7 +73,7 @@ "guardaserie": { "video_workers": -1, "audio_workers": -1, - "domain": "my" + "domain": "dev" }, "ddlstreamitaly": { "domain": "co", @@ -89,7 +87,7 @@ "domain": "ru" }, "uhdmovies": { - "domain": "dad" + "domain": "mov" }, "bitsearch": { "domain": "to" @@ -98,7 +96,12 @@ "domain": "to" }, "cb01": { - "domain": "software" + "domain": "broker" + }, + "mostraguarda": { + "video_workers": -1, + "audio_workers": -1, + "domain": "stream" } } } \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index eeb9b0e..bb4a7be 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,4 @@ -urllib3 -certifi -httpx +httpx bs4 rich tqdm @@ -11,4 +9,5 @@ jsbeautifier seleniumbase fake-useragent qbittorrent-api -python-qbittorrent \ No newline at end of file +python-qbittorrent +googlesearch-python \ No newline at end of file diff --git a/run.py b/run.py index 22d32d9..76c3881 100644 --- a/run.py +++ b/run.py @@ -17,7 +17,7 @@ from Src.Util.console import console, msg from Src.Util._jsonConfig import config_manager from Src.Upload.update import update as git_update from Src.Util.os import get_system_summary, create_folder -from Src.Lib.TMBD.tmbd import tmdb +from Src.Lib.TMBD import tmdb from Src.Util.logger import Logger