This commit is contained in:
Lovi 2024-09-22 10:27:53 +02:00
parent cdbaae3c01
commit 590533a141
28 changed files with 853 additions and 401 deletions

View File

@ -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`
</details>
<details>
<summary><strong>M3U8_CONVERSION</strong></summary>
* **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`
</details>
<details>

View File

@ -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}"

View File

@ -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

View File

@ -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
from .Util.manage_ep import manage_selection, map_episode_title, validate_episode_selection, validate_selection

View File

@ -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

View File

@ -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()

View File

@ -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:

View File

@ -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

View File

@ -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:

View File

@ -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()

View File

@ -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"

View File

@ -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()

View File

@ -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:

View File

@ -12,7 +12,7 @@ from .serie import download_serie
# Variable
indice = 6
_deprecate = False
_deprecate = True
def search():

View File

@ -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:

View File

@ -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):
"""

View File

@ -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:

2
Src/Lib/TMBD/__init__.py Normal file
View File

@ -0,0 +1,2 @@
from .tmdb import tmdb
from .obj_tmbd import Json_film

40
Src/Lib/TMBD/obj_tmbd.py Normal file
View File

@ -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})")

View File

@ -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)

346
Src/Lib/TMBD/tmdb.py Normal file
View File

@ -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)
"""

View File

@ -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)

View File

@ -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'

View File

@ -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)

View File

@ -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)]

View File

@ -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"
}
}
}

View File

@ -1,6 +1,4 @@
urllib3
certifi
httpx
httpx
bs4
rich
tqdm
@ -11,4 +9,5 @@ jsbeautifier
seleniumbase
fake-useragent
qbittorrent-api
python-qbittorrent
python-qbittorrent
googlesearch-python

2
run.py
View File

@ -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