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