mirror of
https://github.com/Arrowar/StreamingCommunity.git
synced 2025-06-05 02:55:25 +00:00
api: Add raiplay
This commit is contained in:
parent
64efc67e6a
commit
c3a0be0d85
@ -18,21 +18,13 @@ max_timeout = config_manager.get_int("REQUESTS", "timeout")
|
|||||||
|
|
||||||
|
|
||||||
class VideoSource:
|
class VideoSource:
|
||||||
def __init__(self, cookie) -> None:
|
def __init__(self, url, cookie) -> None:
|
||||||
"""
|
"""
|
||||||
Initializes the VideoSource object with default values.
|
Initializes the VideoSource object with default values.
|
||||||
"""
|
"""
|
||||||
self.headers = {'user-agent': get_userAgent()}
|
self.headers = {'user-agent': get_userAgent()}
|
||||||
self.cookie = cookie
|
|
||||||
|
|
||||||
def setup(self, url: str) -> None:
|
|
||||||
"""
|
|
||||||
Sets up the video source with the provided URL.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
- url (str): The URL of the video source.
|
|
||||||
"""
|
|
||||||
self.url = url
|
self.url = url
|
||||||
|
self.cookie = cookie
|
||||||
|
|
||||||
def make_request(self, url: str) -> str:
|
def make_request(self, url: str) -> str:
|
||||||
"""
|
"""
|
||||||
|
64
StreamingCommunity/Api/Player/mediapolisvod.py
Normal file
64
StreamingCommunity/Api/Player/mediapolisvod.py
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
# 11.04.25
|
||||||
|
|
||||||
|
|
||||||
|
# External libraries
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
|
||||||
|
# Internal utilities
|
||||||
|
from StreamingCommunity.Util.config_json import config_manager
|
||||||
|
from StreamingCommunity.Util.headers import get_headers
|
||||||
|
|
||||||
|
|
||||||
|
# Variable
|
||||||
|
MAX_TIMEOUT = config_manager.get_int("REQUESTS", "timeout")
|
||||||
|
|
||||||
|
|
||||||
|
class VideoSource:
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def extract_m3u8_url(video_url: str) -> str:
|
||||||
|
"""Extract the m3u8 streaming URL from a RaiPlay video URL."""
|
||||||
|
if not video_url.endswith('.json'):
|
||||||
|
if '/video/' in video_url:
|
||||||
|
video_id = video_url.split('/')[-1].split('.')[0]
|
||||||
|
video_path = '/'.join(video_url.split('/')[:-1])
|
||||||
|
video_url = f"{video_path}/{video_id}.json"
|
||||||
|
|
||||||
|
else:
|
||||||
|
return "Error: Unable to determine video JSON URL"
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = httpx.get(video_url, headers=get_headers(), timeout=MAX_TIMEOUT)
|
||||||
|
if response.status_code != 200:
|
||||||
|
return f"Error: Failed to fetch video data (Status: {response.status_code})"
|
||||||
|
|
||||||
|
video_data = response.json()
|
||||||
|
content_url = video_data.get("video").get("content_url")
|
||||||
|
|
||||||
|
if not content_url:
|
||||||
|
return "Error: No content URL found in video data"
|
||||||
|
|
||||||
|
# Extract the element key
|
||||||
|
if "=" in content_url:
|
||||||
|
element_key = content_url.split("=")[1]
|
||||||
|
else:
|
||||||
|
return "Error: Unable to extract element key"
|
||||||
|
|
||||||
|
# Request the stream URL
|
||||||
|
params = {
|
||||||
|
'cont': element_key,
|
||||||
|
'output': '62',
|
||||||
|
}
|
||||||
|
stream_response = httpx.get('https://mediapolisvod.rai.it/relinker/relinkerServlet.htm', params=params, headers=get_headers(), timeout=MAX_TIMEOUT)
|
||||||
|
|
||||||
|
if stream_response.status_code != 200:
|
||||||
|
return f"Error: Failed to fetch stream URL (Status: {stream_response.status_code})"
|
||||||
|
|
||||||
|
# Extract the m3u8 URL
|
||||||
|
stream_data = stream_response.json()
|
||||||
|
m3u8_url = stream_data.get("video")[0] if "video" in stream_data else None
|
||||||
|
return m3u8_url
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return f"Error: {str(e)}"
|
@ -16,9 +16,9 @@ from StreamingCommunity.Util.headers import get_userAgent
|
|||||||
MAX_TIMEOUT = config_manager.get_int("REQUESTS", "timeout")
|
MAX_TIMEOUT = config_manager.get_int("REQUESTS", "timeout")
|
||||||
|
|
||||||
|
|
||||||
class AnimeWorldPlayer:
|
class VideoSource:
|
||||||
def __init__(self, full_url, episode_data, session_id, csrf_token):
|
def __init__(self, full_url, episode_data, session_id, csrf_token):
|
||||||
"""Initialize the AnimeWorldPlayer with session details, episode data, and URL."""
|
"""Initialize the VideoSource with session details, episode data, and URL."""
|
||||||
self.session_id = session_id
|
self.session_id = session_id
|
||||||
self.csrf_token = csrf_token
|
self.csrf_token = csrf_token
|
||||||
self.episode_data = episode_data
|
self.episode_data = episode_data
|
||||||
@ -33,7 +33,7 @@ class AnimeWorldPlayer:
|
|||||||
timeout=MAX_TIMEOUT
|
timeout=MAX_TIMEOUT
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_download_link(self):
|
def get_playlist(self):
|
||||||
"""Fetch the download link from AnimeWorld using the episode link."""
|
"""Fetch the download link from AnimeWorld using the episode link."""
|
||||||
try:
|
try:
|
||||||
# Make a POST request to the episode link and follow any redirects
|
# Make a POST request to the episode link and follow any redirects
|
||||||
|
@ -24,26 +24,20 @@ console = Console()
|
|||||||
|
|
||||||
|
|
||||||
class VideoSource:
|
class VideoSource:
|
||||||
def __init__(self, url: str, is_series: bool):
|
def __init__(self, url: str, is_series: bool, media_id: int = None):
|
||||||
"""
|
"""
|
||||||
Initialize video source for streaming site.
|
Initialize video source for streaming site.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
- url (str): The URL of the streaming site.
|
- url (str): The URL of the streaming site.
|
||||||
- is_series (bool): Flag for series or movie content
|
- is_series (bool): Flag for series or movie content
|
||||||
|
- media_id (int, optional): Unique identifier for media item
|
||||||
"""
|
"""
|
||||||
self.headers = {'user-agent': get_userAgent()}
|
self.headers = {'user-agent': get_userAgent()}
|
||||||
self.url = url
|
self.url = url
|
||||||
self.is_series = is_series
|
self.is_series = is_series
|
||||||
|
|
||||||
def setup(self, media_id: int):
|
|
||||||
"""
|
|
||||||
Configure media-specific context.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
media_id (int): Unique identifier for media item
|
|
||||||
"""
|
|
||||||
self.media_id = media_id
|
self.media_id = media_id
|
||||||
|
self.iframe_src = None
|
||||||
|
|
||||||
def get_iframe(self, episode_id: int) -> None:
|
def get_iframe(self, episode_id: int) -> None:
|
||||||
"""
|
"""
|
||||||
@ -164,6 +158,7 @@ class VideoSourceAnime(VideoSource):
|
|||||||
self.headers = {'user-agent': get_userAgent()}
|
self.headers = {'user-agent': get_userAgent()}
|
||||||
self.url = url
|
self.url = url
|
||||||
self.src_mp4 = None
|
self.src_mp4 = None
|
||||||
|
self.iframe_src = None
|
||||||
|
|
||||||
def get_embed(self, episode_id: int):
|
def get_embed(self, episode_id: int):
|
||||||
"""
|
"""
|
||||||
|
@ -39,7 +39,7 @@ def process_search_result(select_title):
|
|||||||
|
|
||||||
def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_item: dict = None):
|
def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_item: dict = None):
|
||||||
"""
|
"""
|
||||||
Main function of the application for search film, series and anime.
|
Main function of the application for search.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
string_to_search (str, optional): String to search for
|
string_to_search (str, optional): String to search for
|
||||||
|
@ -57,27 +57,43 @@ def get_user_input(string_to_search: str = None):
|
|||||||
|
|
||||||
return string_to_search
|
return string_to_search
|
||||||
|
|
||||||
def process_search_result(select_title):
|
def process_search_result(select_title, selections=None):
|
||||||
"""
|
"""
|
||||||
Handles the search result and initiates the download for either a film or series.
|
Handles the search result and initiates the download for either a film or series.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
select_title (MediaItem): The selected media item
|
||||||
|
selections (dict, optional): Dictionary containing selection inputs that bypass manual input
|
||||||
|
{'season': season_selection, 'episode': episode_selection}
|
||||||
"""
|
"""
|
||||||
if select_title.type == 'tv':
|
if select_title.type == 'tv':
|
||||||
download_series(select_title)
|
season_selection = None
|
||||||
|
episode_selection = None
|
||||||
|
|
||||||
|
if selections:
|
||||||
|
season_selection = selections.get('season')
|
||||||
|
episode_selection = selections.get('episode')
|
||||||
|
|
||||||
|
download_series(select_title, season_selection, episode_selection)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
download_film(select_title)
|
download_film(select_title)
|
||||||
|
|
||||||
def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_item: dict = None):
|
# search("Game of Thrones", selections={"season": "1", "episode": "1-3"})
|
||||||
|
def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_item: dict = None, selections: dict = None):
|
||||||
"""
|
"""
|
||||||
Main function of the application for search film, series and anime.
|
Main function of the application for search.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
string_to_search (str, optional): String to search for
|
string_to_search (str, optional): String to search for
|
||||||
get_onylDatabase (bool, optional): If True, return only the database object
|
get_onylDatabase (bool, optional): If True, return only the database object
|
||||||
direct_item (dict, optional): Direct item to process (bypass search)
|
direct_item (dict, optional): Direct item to process (bypass search)
|
||||||
|
selections (dict, optional): Dictionary containing selection inputs that bypass manual input
|
||||||
|
{'season': season_selection, 'episode': episode_selection}
|
||||||
"""
|
"""
|
||||||
if direct_item:
|
if direct_item:
|
||||||
select_title = MediaItem(**direct_item)
|
select_title = MediaItem(**direct_item)
|
||||||
process_search_result(select_title)
|
process_search_result(select_title, selections)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Get the user input for the search term
|
# Get the user input for the search term
|
||||||
@ -95,7 +111,7 @@ def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_
|
|||||||
|
|
||||||
if len_database > 0:
|
if len_database > 0:
|
||||||
select_title = get_select_title(table_show_manager, media_search_manager)
|
select_title = get_select_title(table_show_manager, media_search_manager)
|
||||||
process_search_result(select_title)
|
process_search_result(select_title, selections)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
console.print(f"\n[red]Nothing matching was found for[white]: [purple]{string_to_search}")
|
console.print(f"\n[red]Nothing matching was found for[white]: [purple]{string_to_search}")
|
||||||
@ -105,4 +121,4 @@ def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_
|
|||||||
|
|
||||||
# If no results are found, ask again
|
# If no results are found, ask again
|
||||||
string_to_search = get_user_input()
|
string_to_search = get_user_input()
|
||||||
search()
|
search(string_to_search, get_onlyDatabase, None, selections)
|
@ -42,7 +42,6 @@ def download_film(select_title: MediaItem) -> str:
|
|||||||
Return:
|
Return:
|
||||||
- str: output path if successful, otherwise None
|
- str: output path if successful, otherwise None
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if site_constant.TELEGRAM_BOT:
|
if site_constant.TELEGRAM_BOT:
|
||||||
bot = get_bot_instance()
|
bot = get_bot_instance()
|
||||||
bot.send_message(f"Download in corso:\n{select_title.name}", None)
|
bot.send_message(f"Download in corso:\n{select_title.name}", None)
|
||||||
|
@ -19,8 +19,7 @@ from StreamingCommunity.TelegramHelp.telegram_bot import get_bot_instance, Teleg
|
|||||||
from .util.ScrapeSerie import GetSerieInfo
|
from .util.ScrapeSerie import GetSerieInfo
|
||||||
from StreamingCommunity.Api.Template.Util import (
|
from StreamingCommunity.Api.Template.Util import (
|
||||||
manage_selection,
|
manage_selection,
|
||||||
map_episode_title,
|
map_episode_title,
|
||||||
dynamic_format_number,
|
|
||||||
validate_selection,
|
validate_selection,
|
||||||
validate_episode_selection,
|
validate_episode_selection,
|
||||||
display_episodes_list
|
display_episodes_list
|
||||||
@ -40,23 +39,24 @@ console = Console()
|
|||||||
|
|
||||||
def download_video(index_season_selected: int, index_episode_selected: int, scrape_serie: GetSerieInfo) -> Tuple[str,bool]:
|
def download_video(index_season_selected: int, index_episode_selected: int, scrape_serie: GetSerieInfo) -> Tuple[str,bool]:
|
||||||
"""
|
"""
|
||||||
Download a single episode video.
|
Downloads a specific episode from a specified season.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
- index_season_selected (int): Index of the selected season.
|
- index_season_selected (int): Season number
|
||||||
- index_episode_selected (int): Index of the selected episode.
|
- index_episode_selected (int): Episode index
|
||||||
|
- scrape_serie (GetSerieInfo): Scraper object with series information
|
||||||
|
|
||||||
Return:
|
Returns:
|
||||||
- str: output path
|
- str: Path to downloaded file
|
||||||
- bool: kill handler status
|
- bool: Whether download was stopped
|
||||||
"""
|
"""
|
||||||
start_message()
|
start_message()
|
||||||
index_season_selected = dynamic_format_number(str(index_season_selected))
|
|
||||||
|
|
||||||
# Get info about episode
|
# Get episode information
|
||||||
obj_episode = scrape_serie.seasons_manager.get_season_by_number(int(index_season_selected)).episodes.get(index_episode_selected-1)
|
obj_episode = scrape_serie.selectEpisode(index_season_selected, index_episode_selected-1)
|
||||||
console.print(f"[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] → [bold magenta]{obj_episode.name}[/bold magenta] ([cyan]S{index_season_selected}E{index_episode_selected}[/cyan]) \n")
|
console.print(f"[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] → [bold magenta]{obj_episode.name}[/bold magenta] ([cyan]S{index_season_selected}E{index_episode_selected}[/cyan]) \n")
|
||||||
|
|
||||||
|
# Telegram integration
|
||||||
if site_constant.TELEGRAM_BOT:
|
if site_constant.TELEGRAM_BOT:
|
||||||
bot = get_bot_instance()
|
bot = get_bot_instance()
|
||||||
|
|
||||||
@ -93,21 +93,21 @@ def download_video(index_season_selected: int, index_episode_selected: int, scra
|
|||||||
return r_proc['path'], r_proc['stopped']
|
return r_proc['path'], r_proc['stopped']
|
||||||
|
|
||||||
|
|
||||||
def download_episode(index_season_selected: int, scrape_serie: GetSerieInfo, download_all: bool = False) -> None:
|
def download_episode(index_season_selected: int, scrape_serie: GetSerieInfo, download_all: bool = False, episode_selection: str = None) -> None:
|
||||||
"""
|
"""
|
||||||
Download episodes of a selected season.
|
Handle downloading episodes for a specific season.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
- index_season_selected (int): Index of the selected season.
|
- index_season_selected (int): Season number
|
||||||
- download_all (bool): Download all episodes in the season.
|
- scrape_serie (GetSerieInfo): Scraper object with series information
|
||||||
|
- download_all (bool): Whether to download all episodes
|
||||||
|
- episode_selection (str, optional): Pre-defined episode selection that bypasses manual input
|
||||||
"""
|
"""
|
||||||
start_message()
|
# Get episodes for the selected season
|
||||||
obj_episodes = scrape_serie.seasons_manager.get_season_by_number(index_season_selected).episodes
|
episodes = scrape_serie.getEpisodeSeasons(index_season_selected)
|
||||||
episodes_count = len(obj_episodes.episodes)
|
episodes_count = len(episodes)
|
||||||
|
|
||||||
if download_all:
|
if download_all:
|
||||||
|
|
||||||
# Download all episodes without asking
|
|
||||||
for i_episode in range(1, episodes_count + 1):
|
for i_episode in range(1, episodes_count + 1):
|
||||||
path, stopped = download_video(index_season_selected, i_episode, scrape_serie)
|
path, stopped = download_video(index_season_selected, i_episode, scrape_serie)
|
||||||
|
|
||||||
@ -117,16 +117,16 @@ def download_episode(index_season_selected: int, scrape_serie: GetSerieInfo, dow
|
|||||||
console.print(f"\n[red]End downloaded [yellow]season: [red]{index_season_selected}.")
|
console.print(f"\n[red]End downloaded [yellow]season: [red]{index_season_selected}.")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
if episode_selection is not None:
|
||||||
|
last_command = episode_selection
|
||||||
|
console.print(f"\n[cyan]Using provided episode selection: [yellow]{episode_selection}")
|
||||||
|
|
||||||
# Display episodes list and manage user selection
|
else:
|
||||||
last_command = display_episodes_list(obj_episodes.episodes)
|
last_command = display_episodes_list(episodes)
|
||||||
|
|
||||||
|
# Prompt user for episode selection
|
||||||
list_episode_select = manage_selection(last_command, episodes_count)
|
list_episode_select = manage_selection(last_command, episodes_count)
|
||||||
|
list_episode_select = validate_episode_selection(list_episode_select, episodes_count)
|
||||||
try:
|
|
||||||
list_episode_select = validate_episode_selection(list_episode_select, episodes_count)
|
|
||||||
except ValueError as e:
|
|
||||||
console.print(f"[red]{str(e)}")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Download selected episodes if not stopped
|
# Download selected episodes if not stopped
|
||||||
for i_episode in list_episode_select:
|
for i_episode in list_episode_select:
|
||||||
@ -135,69 +135,65 @@ def download_episode(index_season_selected: int, scrape_serie: GetSerieInfo, dow
|
|||||||
if stopped:
|
if stopped:
|
||||||
break
|
break
|
||||||
|
|
||||||
def download_series(select_season: MediaItem) -> None:
|
def download_series(select_season: MediaItem, season_selection: str = None, episode_selection: str = None) -> None:
|
||||||
"""
|
"""
|
||||||
Download episodes of a TV series based on user selection.
|
Handle downloading a complete series.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
- select_season (MediaItem): Selected media item (TV series).
|
- select_season (MediaItem): Series metadata from search
|
||||||
|
- season_selection (str, optional): Pre-defined season selection that bypasses manual input
|
||||||
|
- episode_selection (str, optional): Pre-defined episode selection that bypasses manual input
|
||||||
"""
|
"""
|
||||||
if site_constant.TELEGRAM_BOT:
|
|
||||||
bot = get_bot_instance()
|
|
||||||
|
|
||||||
start_message()
|
|
||||||
|
|
||||||
# Init class
|
|
||||||
scrape_serie = GetSerieInfo(select_season.url)
|
scrape_serie = GetSerieInfo(select_season.url)
|
||||||
|
|
||||||
# Collect information about seasons
|
# Get total number of seasons
|
||||||
scrape_serie.collect_season()
|
seasons_count = scrape_serie.getNumberSeason()
|
||||||
seasons_count = len(scrape_serie.seasons_manager)
|
|
||||||
|
if site_constant.TELEGRAM_BOT:
|
||||||
|
bot = get_bot_instance()
|
||||||
|
|
||||||
# Prompt user for season selection and download episodes
|
# Prompt user for season selection and download episodes
|
||||||
console.print(f"\n[green]Seasons found: [red]{seasons_count}")
|
console.print(f"\n[green]Seasons found: [red]{seasons_count}")
|
||||||
|
|
||||||
if site_constant.TELEGRAM_BOT:
|
# If season_selection is provided, use it instead of asking for input
|
||||||
console.print("\n[cyan]Insert season number [yellow](e.g., 1), [red]* [cyan]to download all seasons, "
|
if season_selection is None:
|
||||||
"[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")
|
if site_constant.TELEGRAM_BOT:
|
||||||
|
console.print("\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")
|
||||||
|
|
||||||
bot.send_message(f"Stagioni trovate: {seasons_count}", None)
|
bot.send_message(f"Stagioni trovate: {seasons_count}", None)
|
||||||
|
|
||||||
index_season_selected = bot.ask(
|
index_season_selected = bot.ask(
|
||||||
"select_title_episode",
|
"select_title_episode",
|
||||||
"Menu di selezione delle stagioni\n\n"
|
"Menu di selezione delle stagioni\n\n"
|
||||||
"- Inserisci il numero della stagione (ad esempio, 1)\n"
|
"- Inserisci il numero della stagione (ad esempio, 1)\n"
|
||||||
"- Inserisci * per scaricare tutte le stagioni\n"
|
"- Inserisci * per scaricare tutte le stagioni\n"
|
||||||
"- Inserisci un intervallo di stagioni (ad esempio, 1-2) per scaricare da una stagione all'altra\n"
|
"- Inserisci un intervallo di stagioni (ad esempio, 1-2) per scaricare da una stagione all'altra\n"
|
||||||
"- Inserisci (ad esempio, 3-*) per scaricare dalla stagione specificata fino alla fine della serie",
|
"- Inserisci (ad esempio, 3-*) per scaricare dalla stagione specificata fino alla fine della serie",
|
||||||
None
|
None
|
||||||
)
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
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"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
index_season_selected = msg.ask(
|
index_season_selected = season_selection
|
||||||
"\n[cyan]Insert season number [yellow](e.g., 1), [red]* [cyan]to download all seasons, "
|
console.print(f"\n[cyan]Using provided season selection: [yellow]{season_selection}")
|
||||||
"[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
|
# Validate the selection
|
||||||
list_season_select = manage_selection(index_season_selected, seasons_count)
|
list_season_select = manage_selection(index_season_selected, seasons_count)
|
||||||
|
list_season_select = validate_selection(list_season_select, seasons_count)
|
||||||
try:
|
|
||||||
list_season_select = validate_selection(list_season_select, seasons_count)
|
|
||||||
except ValueError as e:
|
|
||||||
console.print(f"[red]{str(e)}")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Loop through the selected seasons and download episodes
|
# Loop through the selected seasons and download episodes
|
||||||
for i_season in list_season_select:
|
for i_season in list_season_select:
|
||||||
if len(list_season_select) > 1 or index_season_selected == "*":
|
if len(list_season_select) > 1 or index_season_selected == "*":
|
||||||
|
|
||||||
# Download all episodes if multiple seasons are selected or if '*' is used
|
# Download all episodes if multiple seasons are selected or if '*' is used
|
||||||
download_episode(i_season, scrape_serie, download_all=True)
|
download_episode(i_season, scrape_serie, download_all=True)
|
||||||
else:
|
else:
|
||||||
|
|
||||||
# Otherwise, let the user select specific episodes for the single season
|
# Otherwise, let the user select specific episodes for the single season
|
||||||
download_episode(i_season, scrape_serie, download_all=False)
|
download_episode(i_season, scrape_serie, download_all=False, episode_selection=episode_selection)
|
||||||
|
|
||||||
if site_constant.TELEGRAM_BOT:
|
if site_constant.TELEGRAM_BOT:
|
||||||
bot.send_message(f"Finito di scaricare tutte le serie e episodi", None)
|
bot.send_message(f"Finito di scaricare tutte le serie e episodi", None)
|
||||||
@ -205,4 +201,4 @@ def download_series(select_season: MediaItem) -> None:
|
|||||||
# Get script_id
|
# Get script_id
|
||||||
script_id = TelegramSession.get_session()
|
script_id = TelegramSession.get_session()
|
||||||
if script_id != "unknown":
|
if script_id != "unknown":
|
||||||
TelegramSession.deleteScriptId(script_id)
|
TelegramSession.deleteScriptId(script_id)
|
@ -78,7 +78,8 @@ def title_search(query: str) -> int:
|
|||||||
media_search_manager.add_media({
|
media_search_manager.add_media({
|
||||||
'url': url,
|
'url': url,
|
||||||
'name': title,
|
'name': title,
|
||||||
'type': tipo
|
'type': tipo,
|
||||||
|
'image': f"{site_constant.FULL_URL}{movie_div.find("img", class_="layer-image").get("data-src")}"
|
||||||
})
|
})
|
||||||
|
|
||||||
if site_constant.TELEGRAM_BOT:
|
if site_constant.TELEGRAM_BOT:
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
# 16.03.25
|
# 16.03.25
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
# External libraries
|
# External libraries
|
||||||
import httpx
|
import httpx
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
@ -15,7 +18,6 @@ from StreamingCommunity.Api.Player.Helper.Vixcloud.util import SeasonManager
|
|||||||
max_timeout = config_manager.get_int("REQUESTS", "timeout")
|
max_timeout = config_manager.get_int("REQUESTS", "timeout")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class GetSerieInfo:
|
class GetSerieInfo:
|
||||||
def __init__(self, url):
|
def __init__(self, url):
|
||||||
"""
|
"""
|
||||||
@ -69,4 +71,37 @@ class GetSerieInfo:
|
|||||||
'number': ep_idx,
|
'number': ep_idx,
|
||||||
'name': episode_name,
|
'name': episode_name,
|
||||||
'url': episode_url
|
'url': episode_url
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
# ------------- FOR GUI -------------
|
||||||
|
def getNumberSeason(self) -> int:
|
||||||
|
"""
|
||||||
|
Get the total number of seasons available for the series.
|
||||||
|
"""
|
||||||
|
if not self.seasons_manager.seasons:
|
||||||
|
self.collect_season()
|
||||||
|
|
||||||
|
return len(self.seasons_manager.seasons)
|
||||||
|
|
||||||
|
def getEpisodeSeasons(self, season_number: int) -> list:
|
||||||
|
"""
|
||||||
|
Get all episodes for a specific season.
|
||||||
|
"""
|
||||||
|
if not self.seasons_manager.seasons:
|
||||||
|
self.collect_season()
|
||||||
|
|
||||||
|
# Get season directly by its number
|
||||||
|
season = self.seasons_manager.get_season_by_number(season_number)
|
||||||
|
return season.episodes.episodes if season else []
|
||||||
|
|
||||||
|
def selectEpisode(self, season_number: int, episode_index: int) -> dict:
|
||||||
|
"""
|
||||||
|
Get information for a specific episode in a specific season.
|
||||||
|
"""
|
||||||
|
episodes = self.getEpisodeSeasons(season_number)
|
||||||
|
if not episodes or episode_index < 0 or episode_index >= len(episodes):
|
||||||
|
logging.error(f"Episode index {episode_index} is out of range for season {season_number}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
return episodes[episode_index]
|
@ -18,7 +18,8 @@ from StreamingCommunity.TelegramHelp.telegram_bot import get_bot_instance
|
|||||||
|
|
||||||
# Logic class
|
# Logic class
|
||||||
from .site import title_search, media_search_manager, table_show_manager
|
from .site import title_search, media_search_manager, table_show_manager
|
||||||
from .film_serie import download_film, download_series
|
from .film import download_film
|
||||||
|
from .serie import download_series
|
||||||
|
|
||||||
|
|
||||||
# Variable
|
# Variable
|
||||||
@ -56,24 +57,42 @@ def get_user_input(string_to_search: str = None):
|
|||||||
|
|
||||||
return string_to_search
|
return string_to_search
|
||||||
|
|
||||||
def process_search_result(select_title):
|
def process_search_result(select_title, selections=None):
|
||||||
"""
|
"""
|
||||||
Handles the search result and initiates the download for either a film or series.
|
Handles the search result and initiates the download for either a film or series.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
select_title (MediaItem): The selected media item
|
||||||
|
selections (dict, optional): Dictionary containing selection inputs that bypass manual input
|
||||||
|
{'season': season_selection, 'episode': episode_selection}
|
||||||
"""
|
"""
|
||||||
download_series(select_title)
|
if select_title.type == 'Movie' or select_title.type == 'OVA':
|
||||||
|
download_film(select_title)
|
||||||
|
|
||||||
def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_item: dict = None):
|
else:
|
||||||
|
season_selection = None
|
||||||
|
episode_selection = None
|
||||||
|
|
||||||
|
if selections:
|
||||||
|
season_selection = selections.get('season')
|
||||||
|
episode_selection = selections.get('episode')
|
||||||
|
|
||||||
|
download_series(select_title, season_selection, episode_selection)
|
||||||
|
|
||||||
|
def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_item: dict = None, selections: dict = None):
|
||||||
"""
|
"""
|
||||||
Main function of the application for search film, series and anime.
|
Main function of the application for search.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
string_to_search (str, optional): String to search for
|
string_to_search (str, optional): String to search for
|
||||||
get_onlyDatabase (bool, optional): If True, return only the database object
|
get_onlyDatabase (bool, optional): If True, return only the database object
|
||||||
direct_item (dict, optional): Direct item to process (bypass search)
|
direct_item (dict, optional): Direct item to process (bypass search)
|
||||||
|
selections (dict, optional): Dictionary containing selection inputs that bypass manual input
|
||||||
|
{'season': season_selection, 'episode': episode_selection}
|
||||||
"""
|
"""
|
||||||
if direct_item:
|
if direct_item:
|
||||||
select_title = MediaItem(**direct_item)
|
select_title = MediaItem(**direct_item)
|
||||||
process_search_result(select_title)
|
process_search_result(select_title, selections)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Get the user input for the search term
|
# Get the user input for the search term
|
||||||
@ -82,7 +101,7 @@ def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_
|
|||||||
# Perform the database search
|
# Perform the database search
|
||||||
len_database = title_search(string_to_search)
|
len_database = title_search(string_to_search)
|
||||||
|
|
||||||
##If only the database is needed, return the manager
|
# If only the database is needed, return the manager
|
||||||
if get_onlyDatabase:
|
if get_onlyDatabase:
|
||||||
return media_search_manager
|
return media_search_manager
|
||||||
|
|
||||||
@ -91,8 +110,8 @@ def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_
|
|||||||
|
|
||||||
if len_database > 0:
|
if len_database > 0:
|
||||||
select_title = get_select_title(table_show_manager, media_search_manager)
|
select_title = get_select_title(table_show_manager, media_search_manager)
|
||||||
process_search_result(select_title)
|
process_search_result(select_title, selections)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
console.print(f"\n[red]Nothing matching was found for[white]: [purple]{string_to_search}")
|
console.print(f"\n[red]Nothing matching was found for[white]: [purple]{string_to_search}")
|
||||||
|
|
||||||
@ -101,4 +120,4 @@ def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_
|
|||||||
|
|
||||||
# If no results are found, ask again
|
# If no results are found, ask again
|
||||||
string_to_search = get_user_input()
|
string_to_search = get_user_input()
|
||||||
search()
|
search(string_to_search, get_onlyDatabase, None, selections)
|
40
StreamingCommunity/Api/Site/animeunity/film.py
Normal file
40
StreamingCommunity/Api/Site/animeunity/film.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
# 11.03.24
|
||||||
|
|
||||||
|
# External library
|
||||||
|
from rich.console import Console
|
||||||
|
|
||||||
|
|
||||||
|
# Logic class
|
||||||
|
from .serie import download_episode
|
||||||
|
from .util.ScrapeSerie import ScrapeSerieAnime
|
||||||
|
from StreamingCommunity.Api.Template.config_loader import site_constant
|
||||||
|
from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
|
||||||
|
|
||||||
|
|
||||||
|
# Player
|
||||||
|
from StreamingCommunity.Api.Player.vixcloud import VideoSourceAnime
|
||||||
|
|
||||||
|
|
||||||
|
# Variable
|
||||||
|
console = Console()
|
||||||
|
|
||||||
|
|
||||||
|
def download_film(select_title: MediaItem):
|
||||||
|
"""
|
||||||
|
Function to download a film.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- id_film (int): The ID of the film.
|
||||||
|
- title_name (str): The title of the film.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Init class
|
||||||
|
scrape_serie = ScrapeSerieAnime(site_constant.FULL_URL)
|
||||||
|
video_source = VideoSourceAnime(site_constant.FULL_URL)
|
||||||
|
|
||||||
|
# Set up video source (only configure scrape_serie now)
|
||||||
|
scrape_serie.setup(None, select_title.id, select_title.slug)
|
||||||
|
scrape_serie.is_series = False
|
||||||
|
|
||||||
|
# Start download
|
||||||
|
download_episode(0, scrape_serie, video_source)
|
@ -1,181 +0,0 @@
|
|||||||
# 11.03.24
|
|
||||||
|
|
||||||
import os
|
|
||||||
import logging
|
|
||||||
from typing import Tuple
|
|
||||||
|
|
||||||
|
|
||||||
# External library
|
|
||||||
from rich.console import Console
|
|
||||||
from rich.prompt import Prompt
|
|
||||||
|
|
||||||
|
|
||||||
# Internal utilities
|
|
||||||
from StreamingCommunity.Util.os import os_manager
|
|
||||||
from StreamingCommunity.Util.message import start_message
|
|
||||||
from StreamingCommunity.Lib.Downloader import MP4_downloader
|
|
||||||
from StreamingCommunity.TelegramHelp.telegram_bot import TelegramSession, get_bot_instance
|
|
||||||
|
|
||||||
|
|
||||||
# Logic class
|
|
||||||
from .util.ScrapeSerie import ScrapeSerieAnime
|
|
||||||
from StreamingCommunity.Api.Template.config_loader import site_constant
|
|
||||||
from StreamingCommunity.Api.Template.Util import manage_selection, dynamic_format_number
|
|
||||||
from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
|
|
||||||
|
|
||||||
|
|
||||||
# Player
|
|
||||||
from StreamingCommunity.Api.Player.vixcloud import VideoSourceAnime
|
|
||||||
|
|
||||||
|
|
||||||
# Variable
|
|
||||||
console = Console()
|
|
||||||
msg = Prompt()
|
|
||||||
KILL_HANDLER = bool(False)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def download_episode(index_select: int, scrape_serie: ScrapeSerieAnime, video_source: VideoSourceAnime) -> Tuple[str,bool]:
|
|
||||||
"""
|
|
||||||
Downloads the selected episode.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
- index_select (int): Index of the episode to download.
|
|
||||||
|
|
||||||
Return:
|
|
||||||
- str: output path
|
|
||||||
- bool: kill handler status
|
|
||||||
"""
|
|
||||||
if site_constant.TELEGRAM_BOT:
|
|
||||||
bot = get_bot_instance()
|
|
||||||
|
|
||||||
# Get information about the selected episode
|
|
||||||
obj_episode = scrape_serie.get_info_episode(index_select)
|
|
||||||
|
|
||||||
if obj_episode is not None:
|
|
||||||
|
|
||||||
start_message()
|
|
||||||
console.print(f"[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] ([cyan]E{obj_episode.number}[/cyan]) \n")
|
|
||||||
|
|
||||||
if site_constant.TELEGRAM_BOT:
|
|
||||||
bot.send_message(f"Download in corso:\nTitolo:{scrape_serie.series_name}\nEpisodio: {obj_episode.number}", None)
|
|
||||||
|
|
||||||
# Get script_id
|
|
||||||
script_id = TelegramSession.get_session()
|
|
||||||
if script_id != "unknown":
|
|
||||||
TelegramSession.updateScriptId(script_id, f"{scrape_serie.series_name} - E{obj_episode.number}")
|
|
||||||
|
|
||||||
# Collect mp4 url
|
|
||||||
video_source.get_embed(obj_episode.id)
|
|
||||||
|
|
||||||
# Create output path
|
|
||||||
mp4_name = f"{scrape_serie.series_name}_EP_{dynamic_format_number(str(obj_episode.number))}.mp4"
|
|
||||||
|
|
||||||
if scrape_serie.is_series:
|
|
||||||
mp4_path = os_manager.get_sanitize_path(os.path.join(site_constant.ANIME_FOLDER, scrape_serie.series_name))
|
|
||||||
|
|
||||||
else:
|
|
||||||
mp4_path = os_manager.get_sanitize_path(os.path.join(site_constant.MOVIE_FOLDER, scrape_serie.series_name))
|
|
||||||
|
|
||||||
# Create output folder
|
|
||||||
os_manager.create_path(mp4_path)
|
|
||||||
|
|
||||||
# Start downloading
|
|
||||||
path, kill_handler = MP4_downloader(
|
|
||||||
url=str(video_source.src_mp4).strip(),
|
|
||||||
path=os.path.join(mp4_path, mp4_name)
|
|
||||||
)
|
|
||||||
|
|
||||||
return path, kill_handler
|
|
||||||
|
|
||||||
else:
|
|
||||||
logging.error(f"Skip index: {index_select} cant find info with api.")
|
|
||||||
return None, True
|
|
||||||
|
|
||||||
|
|
||||||
def download_series(select_title: MediaItem):
|
|
||||||
"""
|
|
||||||
Function to download episodes of a TV series.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
- tv_id (int): The ID of the TV series.
|
|
||||||
- tv_name (str): The name of the TV series.
|
|
||||||
"""
|
|
||||||
start_message()
|
|
||||||
|
|
||||||
if site_constant.TELEGRAM_BOT:
|
|
||||||
bot = get_bot_instance()
|
|
||||||
|
|
||||||
scrape_serie = ScrapeSerieAnime(site_constant.FULL_URL)
|
|
||||||
video_source = VideoSourceAnime(site_constant.FULL_URL)
|
|
||||||
|
|
||||||
# Set up video source
|
|
||||||
scrape_serie.setup(None, select_title.id, select_title.slug)
|
|
||||||
|
|
||||||
# Get the count of episodes for the TV series
|
|
||||||
episoded_count = scrape_serie.get_count_episodes()
|
|
||||||
console.print(f"[cyan]Episodes find: [red]{episoded_count}")
|
|
||||||
|
|
||||||
if site_constant.TELEGRAM_BOT:
|
|
||||||
console.print(f"\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")
|
|
||||||
bot.send_message(f"Episodi trovati: {episoded_count}", None)
|
|
||||||
|
|
||||||
last_command = bot.ask(
|
|
||||||
"select_title",
|
|
||||||
"Menu di selezione degli episodi: \n\n"
|
|
||||||
"- Inserisci il numero dell'episodio (ad esempio, 1)\n"
|
|
||||||
"- Inserisci * per scaricare tutti gli episodi\n"
|
|
||||||
"- Inserisci un intervallo di episodi (ad esempio, 1-2) per scaricare da un episodio all'altro\n"
|
|
||||||
"- Inserisci (ad esempio, 3-*) per scaricare dall'episodio specificato fino alla fine della serie",
|
|
||||||
None
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
|
||||||
|
|
||||||
# Prompt user to select an episode index
|
|
||||||
last_command = 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 user selection
|
|
||||||
list_episode_select = manage_selection(last_command, episoded_count)
|
|
||||||
|
|
||||||
# Download selected episodes
|
|
||||||
if len(list_episode_select) == 1 and last_command != "*":
|
|
||||||
path, _ = download_episode(list_episode_select[0]-1, scrape_serie, video_source)
|
|
||||||
return path
|
|
||||||
|
|
||||||
# Download all other episodes selecter
|
|
||||||
else:
|
|
||||||
kill_handler = False
|
|
||||||
for i_episode in list_episode_select:
|
|
||||||
if kill_handler:
|
|
||||||
break
|
|
||||||
_, kill_handler = download_episode(i_episode-1, scrape_serie, video_source)
|
|
||||||
|
|
||||||
if site_constant.TELEGRAM_BOT:
|
|
||||||
bot.send_message(f"Finito di scaricare tutte le serie e episodi", None)
|
|
||||||
|
|
||||||
# Get script_id
|
|
||||||
script_id = TelegramSession.get_session()
|
|
||||||
if script_id != "unknown":
|
|
||||||
TelegramSession.deleteScriptId(script_id)
|
|
||||||
|
|
||||||
|
|
||||||
def download_film(select_title: MediaItem):
|
|
||||||
"""
|
|
||||||
Function to download a film.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
- id_film (int): The ID of the film.
|
|
||||||
- title_name (str): The title of the film.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Init class
|
|
||||||
scrape_serie = ScrapeSerieAnime(site_constant.FULL_URL)
|
|
||||||
video_source = VideoSourceAnime(site_constant.FULL_URL)
|
|
||||||
|
|
||||||
# Set up video source
|
|
||||||
scrape_serie.setup(None, select_title.id, select_title.slug)
|
|
||||||
scrape_serie.is_series = False
|
|
||||||
|
|
||||||
# Start download
|
|
||||||
download_episode(0, scrape_serie, video_source)
|
|
153
StreamingCommunity/Api/Site/animeunity/serie.py
Normal file
153
StreamingCommunity/Api/Site/animeunity/serie.py
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
# 11.03.24
|
||||||
|
|
||||||
|
import os
|
||||||
|
from typing import Tuple
|
||||||
|
|
||||||
|
|
||||||
|
# External library
|
||||||
|
from rich.console import Console
|
||||||
|
from rich.prompt import Prompt
|
||||||
|
|
||||||
|
|
||||||
|
# Internal utilities
|
||||||
|
from StreamingCommunity.Util.os import os_manager
|
||||||
|
from StreamingCommunity.Util.message import start_message
|
||||||
|
from StreamingCommunity.Lib.Downloader import MP4_downloader
|
||||||
|
from StreamingCommunity.TelegramHelp.telegram_bot import TelegramSession, get_bot_instance
|
||||||
|
|
||||||
|
|
||||||
|
# Logic class
|
||||||
|
from .util.ScrapeSerie import ScrapeSerieAnime
|
||||||
|
from StreamingCommunity.Api.Template.config_loader import site_constant
|
||||||
|
from StreamingCommunity.Api.Template.Util import manage_selection, dynamic_format_number
|
||||||
|
from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
|
||||||
|
|
||||||
|
|
||||||
|
# Player
|
||||||
|
from StreamingCommunity.Api.Player.vixcloud import VideoSourceAnime
|
||||||
|
|
||||||
|
|
||||||
|
# Variable
|
||||||
|
console = Console()
|
||||||
|
msg = Prompt()
|
||||||
|
KILL_HANDLER = bool(False)
|
||||||
|
|
||||||
|
|
||||||
|
def download_episode(index_select: int, scrape_serie: ScrapeSerieAnime, video_source: VideoSourceAnime) -> Tuple[str,bool]:
|
||||||
|
"""
|
||||||
|
Downloads the selected episode.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- index_select (int): Index of the episode to download.
|
||||||
|
|
||||||
|
Return:
|
||||||
|
- str: output path
|
||||||
|
- bool: kill handler status
|
||||||
|
"""
|
||||||
|
start_message()
|
||||||
|
|
||||||
|
# Get episode information
|
||||||
|
obj_episode = scrape_serie.selectEpisode(1, index_select)
|
||||||
|
console.print(f"[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] ([cyan]E{obj_episode.number}[/cyan]) \n")
|
||||||
|
|
||||||
|
if site_constant.TELEGRAM_BOT:
|
||||||
|
bot = get_bot_instance()
|
||||||
|
bot.send_message(f"Download in corso\nAnime: {scrape_serie.series_name}\nEpisodio: {obj_episode.number}", None)
|
||||||
|
|
||||||
|
# Get script_id and update it
|
||||||
|
script_id = TelegramSession.get_session()
|
||||||
|
if script_id != "unknown":
|
||||||
|
TelegramSession.updateScriptId(script_id, f"{scrape_serie.series_name} - E{obj_episode.number}")
|
||||||
|
|
||||||
|
# Collect mp4 url
|
||||||
|
video_source.get_embed(obj_episode.id)
|
||||||
|
|
||||||
|
# Create output path
|
||||||
|
mp4_name = f"{scrape_serie.series_name}_EP_{dynamic_format_number(str(obj_episode.number))}.mp4"
|
||||||
|
|
||||||
|
if scrape_serie.is_series:
|
||||||
|
mp4_path = os_manager.get_sanitize_path(os.path.join(site_constant.ANIME_FOLDER, scrape_serie.series_name))
|
||||||
|
else:
|
||||||
|
mp4_path = os_manager.get_sanitize_path(os.path.join(site_constant.MOVIE_FOLDER, scrape_serie.series_name))
|
||||||
|
|
||||||
|
# Create output folder
|
||||||
|
os_manager.create_path(mp4_path)
|
||||||
|
|
||||||
|
# Start downloading
|
||||||
|
path, kill_handler = MP4_downloader(
|
||||||
|
url=str(video_source.src_mp4).strip(),
|
||||||
|
path=os.path.join(mp4_path, mp4_name)
|
||||||
|
)
|
||||||
|
|
||||||
|
return path, kill_handler
|
||||||
|
|
||||||
|
|
||||||
|
def download_series(select_title: MediaItem, season_selection: str = None, episode_selection: str = None):
|
||||||
|
"""
|
||||||
|
Function to download episodes of a TV series.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- select_title (MediaItem): The selected media item
|
||||||
|
- season_selection (str, optional): Season selection input that bypasses manual input (usually '1' for anime)
|
||||||
|
- episode_selection (str, optional): Episode selection input that bypasses manual input
|
||||||
|
"""
|
||||||
|
start_message()
|
||||||
|
|
||||||
|
if site_constant.TELEGRAM_BOT:
|
||||||
|
bot = get_bot_instance()
|
||||||
|
|
||||||
|
scrape_serie = ScrapeSerieAnime(site_constant.FULL_URL)
|
||||||
|
video_source = VideoSourceAnime(site_constant.FULL_URL)
|
||||||
|
|
||||||
|
# Set up video source (only configure scrape_serie now)
|
||||||
|
scrape_serie.setup(None, select_title.id, select_title.slug)
|
||||||
|
|
||||||
|
# Get episode information
|
||||||
|
episoded_count = scrape_serie.get_count_episodes()
|
||||||
|
console.print(f"[green]Episodes count:[/green] [red]{episoded_count}[/red]")
|
||||||
|
|
||||||
|
# Telegram bot integration
|
||||||
|
if episode_selection is None:
|
||||||
|
if site_constant.TELEGRAM_BOT:
|
||||||
|
console.print(f"\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")
|
||||||
|
bot.send_message(f"Episodi trovati: {episoded_count}", None)
|
||||||
|
|
||||||
|
last_command = bot.ask(
|
||||||
|
"select_title",
|
||||||
|
"Menu di selezione degli episodi: \n\n"
|
||||||
|
"- Inserisci il numero dell'episodio (ad esempio, 1)\n"
|
||||||
|
"- Inserisci * per scaricare tutti gli episodi\n"
|
||||||
|
"- Inserisci un intervallo di episodi (ad esempio, 1-2) per scaricare da un episodio all'altro\n"
|
||||||
|
"- Inserisci (ad esempio, 3-*) per scaricare dall'episodio specificato fino alla fine della serie",
|
||||||
|
None
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Prompt user to select an episode index
|
||||||
|
last_command = 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")
|
||||||
|
else:
|
||||||
|
last_command = episode_selection
|
||||||
|
console.print(f"\n[cyan]Using provided episode selection: [yellow]{episode_selection}")
|
||||||
|
|
||||||
|
# Manage user selection
|
||||||
|
list_episode_select = manage_selection(last_command, episoded_count)
|
||||||
|
|
||||||
|
# Download selected episodes
|
||||||
|
if len(list_episode_select) == 1 and last_command != "*":
|
||||||
|
path, _ = download_episode(list_episode_select[0]-1, scrape_serie, video_source)
|
||||||
|
return path
|
||||||
|
|
||||||
|
# Download all other episodes selected
|
||||||
|
else:
|
||||||
|
kill_handler = False
|
||||||
|
for i_episode in list_episode_select:
|
||||||
|
if kill_handler:
|
||||||
|
break
|
||||||
|
_, kill_handler = download_episode(i_episode-1, scrape_serie, video_source)
|
||||||
|
|
||||||
|
if site_constant.TELEGRAM_BOT:
|
||||||
|
bot.send_message(f"Finito di scaricare tutte le serie e episodi", None)
|
||||||
|
|
||||||
|
# Get script_id
|
||||||
|
script_id = TelegramSession.get_session()
|
||||||
|
if script_id != "unknown":
|
||||||
|
TelegramSession.deleteScriptId(script_id)
|
@ -1,6 +1,5 @@
|
|||||||
# 10.12.23
|
# 10.12.23
|
||||||
|
|
||||||
import sys
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
|
||||||
@ -140,7 +139,7 @@ def title_search(query: str) -> int:
|
|||||||
'type': dict_title.get('type'),
|
'type': dict_title.get('type'),
|
||||||
'status': dict_title.get('status'),
|
'status': dict_title.get('status'),
|
||||||
'episodes_count': dict_title.get('episodes_count'),
|
'episodes_count': dict_title.get('episodes_count'),
|
||||||
'plot': ' '.join((words := str(dict_title.get('plot', '')).split())[:10]) + ('...' if len(words) > 10 else '')
|
'image': dict_title.get('imageurl')
|
||||||
})
|
})
|
||||||
|
|
||||||
if site_constant.TELEGRAM_BOT:
|
if site_constant.TELEGRAM_BOT:
|
||||||
|
@ -94,3 +94,18 @@ class ScrapeSerieAnime:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error fetching episode information: {e}")
|
logging.error(f"Error fetching episode information: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# ------------- FOR GUI -------------
|
||||||
|
def getNumberSeason(self) -> int:
|
||||||
|
"""
|
||||||
|
Get the total number of seasons available for the anime.
|
||||||
|
Note: AnimeUnity typically doesn't have seasons, so returns 1.
|
||||||
|
"""
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def selectEpisode(self, season_number: int = 1, episode_index: int = 0) -> Episode:
|
||||||
|
"""
|
||||||
|
Get information for a specific episode.
|
||||||
|
"""
|
||||||
|
return self.get_info_episode(episode_index)
|
@ -14,6 +14,7 @@ from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
|
|||||||
# Logic class
|
# Logic class
|
||||||
from .site import title_search, media_search_manager, table_show_manager
|
from .site import title_search, media_search_manager, table_show_manager
|
||||||
from .serie import download_series
|
from .serie import download_series
|
||||||
|
from .film import download_film
|
||||||
|
|
||||||
|
|
||||||
# Variable
|
# Variable
|
||||||
@ -28,44 +29,56 @@ console = Console()
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
def process_search_result(select_title):
|
def process_search_result(select_title, selections=None):
|
||||||
"""
|
"""
|
||||||
Handles the search result and initiates the download for either a film or series.
|
Handles the search result and initiates the download for either a film or series.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
select_title (MediaItem): The selected media item
|
||||||
|
selections (dict, optional): Dictionary containing selection inputs that bypass manual input
|
||||||
|
{'season': season_selection, 'episode': episode_selection}
|
||||||
"""
|
"""
|
||||||
if select_title.type == "TV":
|
if select_title.type == "TV":
|
||||||
download_series(select_title)
|
episode_selection = None
|
||||||
|
if selections:
|
||||||
|
episode_selection = selections.get('episode')
|
||||||
|
download_series(select_title, episode_selection)
|
||||||
|
|
||||||
def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_item: dict = None):
|
else:
|
||||||
|
download_film(select_title)
|
||||||
|
|
||||||
|
def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_item: dict = None, selections: dict = None):
|
||||||
"""
|
"""
|
||||||
Main function of the application for search film, series and anime.
|
Main function of the application for search.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
string_to_search (str, optional): String to search for
|
string_to_search (str, optional): String to search for
|
||||||
get_onlyDatabase (bool, optional): If True, return only the database object
|
get_onlyDatabase (bool, optional): If True, return only the database object
|
||||||
direct_item (dict, optional): Direct item to process (bypass search)
|
direct_item (dict, optional): Direct item to process (bypass search)
|
||||||
|
selections (dict, optional): Dictionary containing selection inputs that bypass manual input
|
||||||
|
{'season': season_selection, 'episode': episode_selection}
|
||||||
"""
|
"""
|
||||||
if direct_item:
|
if direct_item:
|
||||||
select_title = MediaItem(**direct_item)
|
select_title = MediaItem(**direct_item)
|
||||||
process_search_result(select_title)
|
process_search_result(select_title, selections)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Get the user input for the search term
|
# Get the user input for the search term
|
||||||
string_to_search = msg.ask(f"\n[purple]Insert a word to search in [green]{site_constant.SITE_NAME}").strip()
|
if string_to_search is None:
|
||||||
|
string_to_search = msg.ask(f"\n[purple]Insert a word to search in [green]{site_constant.SITE_NAME}").strip()
|
||||||
|
|
||||||
# Perform the database search
|
# Perform the database search
|
||||||
len_database = title_search(string_to_search)
|
len_database = title_search(string_to_search)
|
||||||
|
|
||||||
##If only the database is needed, return the manager
|
# If only the database is needed, return the manager
|
||||||
if get_onlyDatabase:
|
if get_onlyDatabase:
|
||||||
return media_search_manager
|
return media_search_manager
|
||||||
|
|
||||||
if len_database > 0:
|
if len_database > 0:
|
||||||
select_title = get_select_title(table_show_manager, media_search_manager)
|
select_title = get_select_title(table_show_manager, media_search_manager)
|
||||||
process_search_result(select_title)
|
process_search_result(select_title, selections)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
console.print(f"\n[red]Nothing matching was found for[white]: [purple]{string_to_search}")
|
|
||||||
|
|
||||||
# If no results are found, ask again
|
# If no results are found, ask again
|
||||||
string_to_search = msg.ask(f"\n[purple]Insert a word to search in [green]{site_constant.SITE_NAME}").strip()
|
console.print(f"\n[red]Nothing matching was found for[white]: [purple]{string_to_search}")
|
||||||
search()
|
search()
|
63
StreamingCommunity/Api/Site/animeworld/film.py
Normal file
63
StreamingCommunity/Api/Site/animeworld/film.py
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
# 11.03.24
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
# External library
|
||||||
|
from rich.console import Console
|
||||||
|
|
||||||
|
|
||||||
|
# Internal utilities
|
||||||
|
from StreamingCommunity.Util.os import os_manager
|
||||||
|
from StreamingCommunity.Util.message import start_message
|
||||||
|
from StreamingCommunity.Lib.Downloader import MP4_downloader
|
||||||
|
|
||||||
|
|
||||||
|
# Logic class
|
||||||
|
from .util.ScrapeSerie import ScrapSerie
|
||||||
|
from StreamingCommunity.Api.Template.config_loader import site_constant
|
||||||
|
from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
|
||||||
|
|
||||||
|
|
||||||
|
# Player
|
||||||
|
from StreamingCommunity.Api.Player.sweetpixel import VideoSource
|
||||||
|
|
||||||
|
|
||||||
|
# Variable
|
||||||
|
console = Console()
|
||||||
|
|
||||||
|
|
||||||
|
def download_film(select_title: MediaItem):
|
||||||
|
"""
|
||||||
|
Function to download a film.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- id_film (int): The ID of the film.
|
||||||
|
- title_name (str): The title of the film.
|
||||||
|
"""
|
||||||
|
start_message()
|
||||||
|
|
||||||
|
scrape_serie = ScrapSerie(select_title.url, site_constant.FULL_URL)
|
||||||
|
episodes = scrape_serie.get_episodes()
|
||||||
|
|
||||||
|
# Get episode information
|
||||||
|
episode_data = episodes[0]
|
||||||
|
console.print(f"[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] ([cyan]{scrape_serie.get_name()}[/cyan]) \n")
|
||||||
|
|
||||||
|
# Define filename and path for the downloaded video
|
||||||
|
mp4_name = f"{scrape_serie.get_name()}.mp4"
|
||||||
|
mp4_path = os.path.join(site_constant.ANIME_FOLDER, scrape_serie.get_name())
|
||||||
|
|
||||||
|
# Create output folder
|
||||||
|
os_manager.create_path(mp4_path)
|
||||||
|
|
||||||
|
# Get video source for the episode
|
||||||
|
video_source = VideoSource(site_constant.FULL_URL, episode_data, scrape_serie.session_id, scrape_serie.csrf_token)
|
||||||
|
mp4_link = video_source.get_playlist()
|
||||||
|
|
||||||
|
# Start downloading
|
||||||
|
path, kill_handler = MP4_downloader(
|
||||||
|
url=str(mp4_link).strip(),
|
||||||
|
path=os.path.join(mp4_path, mp4_name)
|
||||||
|
)
|
||||||
|
|
||||||
|
return path, kill_handler
|
@ -19,12 +19,12 @@ from StreamingCommunity.Lib.Downloader import MP4_downloader
|
|||||||
# Logic class
|
# Logic class
|
||||||
from .util.ScrapeSerie import ScrapSerie
|
from .util.ScrapeSerie import ScrapSerie
|
||||||
from StreamingCommunity.Api.Template.config_loader import site_constant
|
from StreamingCommunity.Api.Template.config_loader import site_constant
|
||||||
from StreamingCommunity.Api.Template.Util import manage_selection, dynamic_format_number, map_episode_title
|
from StreamingCommunity.Api.Template.Util import manage_selection, dynamic_format_number
|
||||||
from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
|
from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
|
||||||
|
|
||||||
|
|
||||||
# Player
|
# Player
|
||||||
from StreamingCommunity.Api.Player.sweetpixel import AnimeWorldPlayer
|
from StreamingCommunity.Api.Player.sweetpixel import VideoSource
|
||||||
|
|
||||||
|
|
||||||
# Variable
|
# Variable
|
||||||
@ -33,8 +33,7 @@ msg = Prompt()
|
|||||||
KILL_HANDLER = bool(False)
|
KILL_HANDLER = bool(False)
|
||||||
|
|
||||||
|
|
||||||
|
def download_episode(index_select: int, scrape_serie: ScrapSerie) -> Tuple[str,bool]:
|
||||||
def download_episode(index_select: int, scrape_serie: ScrapSerie, episodes) -> Tuple[str,bool]:
|
|
||||||
"""
|
"""
|
||||||
Downloads the selected episode.
|
Downloads the selected episode.
|
||||||
|
|
||||||
@ -47,7 +46,8 @@ def download_episode(index_select: int, scrape_serie: ScrapSerie, episodes) -> T
|
|||||||
"""
|
"""
|
||||||
start_message()
|
start_message()
|
||||||
|
|
||||||
# Get information about the selected episode
|
# Get episode information
|
||||||
|
episode_data = scrape_serie.selectEpisode(1, index_select)
|
||||||
console.print(f"[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] ([cyan]E{index_select+1}[/cyan]) \n")
|
console.print(f"[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] ([cyan]E{index_select+1}[/cyan]) \n")
|
||||||
|
|
||||||
# Define filename and path for the downloaded video
|
# Define filename and path for the downloaded video
|
||||||
@ -57,9 +57,9 @@ def download_episode(index_select: int, scrape_serie: ScrapSerie, episodes) -> T
|
|||||||
# Create output folder
|
# Create output folder
|
||||||
os_manager.create_path(mp4_path)
|
os_manager.create_path(mp4_path)
|
||||||
|
|
||||||
# Collect mp4 link
|
# Get video source for the episode
|
||||||
video_source = AnimeWorldPlayer(site_constant.FULL_URL, episodes[index_select], scrape_serie.session_id, scrape_serie.csrf_token)
|
video_source = VideoSource(site_constant.FULL_URL, episode_data, scrape_serie.session_id, scrape_serie.csrf_token)
|
||||||
mp4_link = video_source.get_download_link()
|
mp4_link = video_source.get_playlist()
|
||||||
|
|
||||||
# Start downloading
|
# Start downloading
|
||||||
path, kill_handler = MP4_downloader(
|
path, kill_handler = MP4_downloader(
|
||||||
@ -70,38 +70,41 @@ def download_episode(index_select: int, scrape_serie: ScrapSerie, episodes) -> T
|
|||||||
return path, kill_handler
|
return path, kill_handler
|
||||||
|
|
||||||
|
|
||||||
def download_series(select_title: MediaItem):
|
def download_series(select_title: MediaItem, episode_selection: str = None):
|
||||||
"""
|
"""
|
||||||
Function to download episodes of a TV series.
|
Function to download episodes of a TV series.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
- tv_id (int): The ID of the TV series.
|
- select_title (MediaItem): The selected media item
|
||||||
- tv_name (str): The name of the TV series.
|
- episode_selection (str, optional): Episode selection input that bypasses manual input
|
||||||
"""
|
"""
|
||||||
start_message()
|
start_message()
|
||||||
|
|
||||||
|
# Create scrap instance
|
||||||
scrape_serie = ScrapSerie(select_title.url, site_constant.FULL_URL)
|
scrape_serie = ScrapSerie(select_title.url, site_constant.FULL_URL)
|
||||||
|
episodes = scrape_serie.get_episodes()
|
||||||
|
|
||||||
# Get the count of episodes for the TV series
|
# Get episode count
|
||||||
episodes = scrape_serie.get_episodes()
|
console.print(f"[green]Episodes found:[/green] [red]{len(episodes)}[/red]")
|
||||||
episoded_count = len(episodes)
|
|
||||||
console.print(f"[cyan]Episodes find: [red]{episoded_count}")
|
|
||||||
|
|
||||||
# Prompt user to select an episode index
|
# Display episodes list and get user selection
|
||||||
last_command = 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")
|
if episode_selection is None:
|
||||||
|
last_command = 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")
|
||||||
|
else:
|
||||||
|
last_command = episode_selection
|
||||||
|
console.print(f"\n[cyan]Using provided episode selection: [yellow]{episode_selection}")
|
||||||
|
|
||||||
# Manage user selection
|
list_episode_select = manage_selection(last_command, len(episodes))
|
||||||
list_episode_select = manage_selection(last_command, episoded_count)
|
|
||||||
|
|
||||||
# Download selected episodes
|
# Download selected episodes
|
||||||
if len(list_episode_select) == 1 and last_command != "*":
|
if len(list_episode_select) == 1 and last_command != "*":
|
||||||
path, _ = download_episode(list_episode_select[0]-1, scrape_serie, episodes)
|
path, _ = download_episode(list_episode_select[0]-1, scrape_serie)
|
||||||
return path
|
return path
|
||||||
|
|
||||||
# Download all other episodes selecter
|
# Download all selected episodes
|
||||||
else:
|
else:
|
||||||
kill_handler = False
|
kill_handler = False
|
||||||
for i_episode in list_episode_select:
|
for i_episode in list_episode_select:
|
||||||
if kill_handler:
|
if kill_handler:
|
||||||
break
|
break
|
||||||
_, kill_handler = download_episode(i_episode-1, scrape_serie, episodes)
|
_, kill_handler = download_episode(i_episode-1, scrape_serie)
|
@ -101,7 +101,8 @@ def title_search(query: str) -> int:
|
|||||||
'name': title,
|
'name': title,
|
||||||
'type': anime_type,
|
'type': anime_type,
|
||||||
'DUB': is_dubbed,
|
'DUB': is_dubbed,
|
||||||
'url': url
|
'url': url,
|
||||||
|
'image': element.find('img').get('src')
|
||||||
})
|
})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
# 21.03.25
|
# 21.03.25
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
# External libraries
|
# External libraries
|
||||||
import httpx
|
import httpx
|
||||||
@ -14,7 +15,7 @@ from StreamingCommunity.Util.os import os_manager
|
|||||||
|
|
||||||
# Player
|
# Player
|
||||||
from ..site import get_session_and_csrf
|
from ..site import get_session_and_csrf
|
||||||
from StreamingCommunity.Api.Player.sweetpixel import AnimeWorldPlayer
|
from StreamingCommunity.Api.Player.sweetpixel import VideoSource
|
||||||
|
|
||||||
|
|
||||||
# Variable
|
# Variable
|
||||||
@ -40,7 +41,6 @@ class ScrapSerie:
|
|||||||
except:
|
except:
|
||||||
raise Exception(f"Failed to retrieve anime page.")
|
raise Exception(f"Failed to retrieve anime page.")
|
||||||
|
|
||||||
|
|
||||||
def get_name(self):
|
def get_name(self):
|
||||||
"""Extract and return the name of the anime series."""
|
"""Extract and return the name of the anime series."""
|
||||||
soup = BeautifulSoup(self.response.content, "html.parser")
|
soup = BeautifulSoup(self.response.content, "html.parser")
|
||||||
@ -68,12 +68,39 @@ class ScrapSerie:
|
|||||||
return episodes
|
return episodes
|
||||||
|
|
||||||
def get_episode(self, index):
|
def get_episode(self, index):
|
||||||
"""Fetch a specific episode based on the index, and return an AnimeWorldPlayer instance."""
|
"""Fetch a specific episode based on the index, and return an VideoSource instance."""
|
||||||
episodes = self.get_episodes()
|
episodes = self.get_episodes()
|
||||||
|
|
||||||
if 0 <= index < len(episodes):
|
if 0 <= index < len(episodes):
|
||||||
episode_data = episodes[index]
|
episode_data = episodes[index]
|
||||||
return AnimeWorldPlayer(episode_data, self.session_id, self.csrf_token)
|
return VideoSource(episode_data, self.session_id, self.csrf_token)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise IndexError("Episode index out of range")
|
raise IndexError("Episode index out of range")
|
||||||
|
|
||||||
|
|
||||||
|
# ------------- FOR GUI -------------
|
||||||
|
def getNumberSeason(self) -> int:
|
||||||
|
"""
|
||||||
|
Get the total number of seasons available for the anime.
|
||||||
|
Note: AnimeWorld typically doesn't have seasons, so returns 1.
|
||||||
|
"""
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def getEpisodeSeasons(self, season_number: int = 1) -> list:
|
||||||
|
"""
|
||||||
|
Get all episodes for a specific season.
|
||||||
|
Note: For AnimeWorld, this returns all episodes as they're typically in one season.
|
||||||
|
"""
|
||||||
|
return self.get_episodes()
|
||||||
|
|
||||||
|
def selectEpisode(self, season_number: int = 1, episode_index: int = 0) -> dict:
|
||||||
|
"""
|
||||||
|
Get information for a specific episode.
|
||||||
|
"""
|
||||||
|
episodes = self.get_episodes()
|
||||||
|
if not episodes or episode_index < 0 or episode_index >= len(episodes):
|
||||||
|
logging.error(f"Episode index {episode_index} is out of range")
|
||||||
|
return None
|
||||||
|
|
||||||
|
return episodes[episode_index]
|
@ -39,7 +39,7 @@ def process_search_result(select_title):
|
|||||||
|
|
||||||
def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_item: dict = None):
|
def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_item: dict = None):
|
||||||
"""
|
"""
|
||||||
Main function of the application for search film, series and anime.
|
Main function of the application for search.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
string_to_search (str, optional): String to search for
|
string_to_search (str, optional): String to search for
|
||||||
|
@ -42,7 +42,7 @@ def process_search_result(select_title):
|
|||||||
|
|
||||||
def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_item: dict = None):
|
def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_item: dict = None):
|
||||||
"""
|
"""
|
||||||
Main function of the application for search film, series and anime.
|
Main function of the application for search.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
string_to_search (str, optional): String to search for
|
string_to_search (str, optional): String to search for
|
||||||
|
@ -35,22 +35,22 @@ from StreamingCommunity.Api.Player.ddl import VideoSource
|
|||||||
console = Console()
|
console = Console()
|
||||||
|
|
||||||
|
|
||||||
def download_video(index_episode_selected: int, scape_info_serie: GetSerieInfo, video_source: VideoSource) -> Tuple[str,bool]:
|
def download_video(index_episode_selected: int, scape_info_serie: GetSerieInfo) -> Tuple[str,bool]:
|
||||||
"""
|
"""
|
||||||
Download a single episode video.
|
Downloads a specific episode.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
- tv_name (str): Name of the TV series.
|
- index_episode_selected (int): Episode index
|
||||||
- index_episode_selected (int): Index of the selected episode.
|
- scape_info_serie (GetSerieInfo): Scraper object with series information
|
||||||
|
|
||||||
Return:
|
Returns:
|
||||||
- str: output path
|
- str: Path to downloaded file
|
||||||
- bool: kill handler status
|
- bool: Whether download was stopped
|
||||||
"""
|
"""
|
||||||
start_message()
|
start_message()
|
||||||
|
|
||||||
# Get info about episode
|
# Get episode information
|
||||||
obj_episode = scape_info_serie.list_episodes[index_episode_selected - 1]
|
obj_episode = scape_info_serie.selectEpisode(1, index_episode_selected-1)
|
||||||
console.print(f"[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] → [bold magenta]{obj_episode.get('name')}[/bold magenta] ([cyan]E{index_episode_selected}[/cyan]) \n")
|
console.print(f"[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] → [bold magenta]{obj_episode.get('name')}[/bold magenta] ([cyan]E{index_episode_selected}[/cyan]) \n")
|
||||||
|
|
||||||
# Define filename and path for the downloaded video
|
# Define filename and path for the downloaded video
|
||||||
@ -63,7 +63,7 @@ def download_video(index_episode_selected: int, scape_info_serie: GetSerieInfo,
|
|||||||
os_manager.create_path(mp4_path)
|
os_manager.create_path(mp4_path)
|
||||||
|
|
||||||
# Setup video source
|
# Setup video source
|
||||||
video_source.setup(obj_episode.get('url'))
|
video_source = VideoSource(site_constant.COOKIE, obj_episode.get('url'))
|
||||||
|
|
||||||
# Get m3u8 master playlist
|
# Get m3u8 master playlist
|
||||||
master_playlist = video_source.get_playlist()
|
master_playlist = video_source.get_playlist()
|
||||||
@ -82,38 +82,37 @@ def download_video(index_episode_selected: int, scape_info_serie: GetSerieInfo,
|
|||||||
console.print("[green]Result: ")
|
console.print("[green]Result: ")
|
||||||
console.print(r_proc)
|
console.print(r_proc)
|
||||||
|
|
||||||
return os.path.join(mp4_path, title_name)
|
return os.path.join(mp4_path, title_name), False
|
||||||
|
|
||||||
|
|
||||||
def download_thread(dict_serie: MediaItem):
|
def download_thread(dict_serie: MediaItem, episode_selection: str = None):
|
||||||
"""
|
"""
|
||||||
Download all episode of a thread
|
Download all episode of a thread
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
dict_serie (MediaItem): The selected media item
|
||||||
|
episode_selection (str, optional): Episode selection input that bypasses manual input
|
||||||
"""
|
"""
|
||||||
start_message()
|
scrape_serie = GetSerieInfo(dict_serie, site_constant.COOKIE)
|
||||||
|
|
||||||
# Init class
|
# Get episode list
|
||||||
scape_info_serie = GetSerieInfo(dict_serie, site_constant.COOKIE)
|
episodes = scrape_serie.getEpisodeSeasons()
|
||||||
video_source = VideoSource(site_constant.COOKIE)
|
episodes_count = len(episodes)
|
||||||
|
|
||||||
# Collect information about thread
|
|
||||||
list_dict_episode = scape_info_serie.get_episode_number()
|
|
||||||
episodes_count = len(list_dict_episode)
|
|
||||||
|
|
||||||
# Display episodes list and manage user selection
|
# Display episodes list and manage user selection
|
||||||
last_command = display_episodes_list(scape_info_serie.list_episodes)
|
if episode_selection is None:
|
||||||
|
last_command = display_episodes_list(scrape_serie.list_episodes)
|
||||||
|
else:
|
||||||
|
last_command = episode_selection
|
||||||
|
console.print(f"\n[cyan]Using provided episode selection: [yellow]{episode_selection}")
|
||||||
|
|
||||||
|
# Validate episode selection
|
||||||
list_episode_select = manage_selection(last_command, episodes_count)
|
list_episode_select = manage_selection(last_command, episodes_count)
|
||||||
|
list_episode_select = validate_episode_selection(list_episode_select, episodes_count)
|
||||||
try:
|
|
||||||
list_episode_select = validate_episode_selection(list_episode_select, episodes_count)
|
|
||||||
|
|
||||||
except ValueError as e:
|
|
||||||
console.print(f"[red]{str(e)}")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Download selected episodes
|
# Download selected episodes
|
||||||
kill_handler = bool(False)
|
kill_handler = bool(False)
|
||||||
for i_episode in list_episode_select:
|
for i_episode in list_episode_select:
|
||||||
if kill_handler:
|
if kill_handler:
|
||||||
break
|
break
|
||||||
|
kill_handler = download_video(i_episode, scrape_serie)[1]
|
||||||
kill_handler = download_video(i_episode, scape_info_serie, video_source)[1]
|
|
@ -1,6 +1,5 @@
|
|||||||
# 09.06.24
|
# 09.06.24
|
||||||
|
|
||||||
import sys
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
|
||||||
@ -67,7 +66,8 @@ def title_search(query: str) -> int:
|
|||||||
title_info = {
|
title_info = {
|
||||||
'name': name,
|
'name': name,
|
||||||
'url': link,
|
'url': link,
|
||||||
'type': title_type
|
'type': title_type,
|
||||||
|
'image': title_div.find("div", class_="ipsColumn").find("img").get("src")
|
||||||
}
|
}
|
||||||
|
|
||||||
media_search_manager.add_media(title_info)
|
media_search_manager.add_media(title_info)
|
||||||
|
@ -47,7 +47,6 @@ class GetSerieInfo:
|
|||||||
Returns:
|
Returns:
|
||||||
List[Dict[str, str]]: List of dictionaries containing episode information.
|
List[Dict[str, str]]: List of dictionaries containing episode information.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = httpx.get(f"{self.url}?area=online", cookies=self.cookies, headers=self.headers, timeout=max_timeout)
|
response = httpx.get(f"{self.url}?area=online", cookies=self.cookies, headers=self.headers, timeout=max_timeout)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
@ -81,4 +80,33 @@ class GetSerieInfo:
|
|||||||
|
|
||||||
self.list_episodes = list_dict_episode
|
self.list_episodes = list_dict_episode
|
||||||
return list_dict_episode
|
return list_dict_episode
|
||||||
|
|
||||||
|
|
||||||
|
# ------------- FOR GUI -------------
|
||||||
|
def getNumberSeason(self) -> int:
|
||||||
|
"""
|
||||||
|
Get the total number of seasons available for the series.
|
||||||
|
Note: DDLStreamItaly typically provides content organized as threads, not seasons.
|
||||||
|
"""
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def getEpisodeSeasons(self, season_number: int = 1) -> list:
|
||||||
|
"""
|
||||||
|
Get all episodes for a specific season.
|
||||||
|
Note: For DDLStreamItaly, this returns all episodes as they're typically in one list.
|
||||||
|
"""
|
||||||
|
if not self.list_episodes:
|
||||||
|
self.list_episodes = self.get_episode_number()
|
||||||
|
|
||||||
|
return self.list_episodes
|
||||||
|
|
||||||
|
def selectEpisode(self, season_number: int = 1, episode_index: int = 0) -> dict:
|
||||||
|
"""
|
||||||
|
Get information for a specific episode.
|
||||||
|
"""
|
||||||
|
episodes = self.getEpisodeSeasons()
|
||||||
|
if not episodes or episode_index < 0 or episode_index >= len(episodes):
|
||||||
|
logging.error(f"Episode index {episode_index} is out of range")
|
||||||
|
return None
|
||||||
|
|
||||||
|
return episodes[episode_index]
|
@ -30,24 +30,38 @@ msg = Prompt()
|
|||||||
console = Console()
|
console = Console()
|
||||||
|
|
||||||
|
|
||||||
def process_search_result(select_title):
|
def process_search_result(select_title, selections=None):
|
||||||
"""
|
"""
|
||||||
Handles the search result and initiates the download for either a film or series.
|
Handles the search result and initiates the download for either a film or series.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
select_title (MediaItem): The selected media item
|
||||||
|
selections (dict, optional): Dictionary containing selection inputs that bypass manual input
|
||||||
|
{'season': season_selection, 'episode': episode_selection}
|
||||||
"""
|
"""
|
||||||
download_series(select_title)
|
season_selection = None
|
||||||
|
episode_selection = None
|
||||||
|
|
||||||
|
if selections:
|
||||||
|
season_selection = selections.get('season')
|
||||||
|
episode_selection = selections.get('episode')
|
||||||
|
|
||||||
|
download_series(select_title, season_selection, episode_selection)
|
||||||
|
|
||||||
def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_item: dict = None):
|
def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_item: dict = None, selections: dict = None):
|
||||||
"""
|
"""
|
||||||
Main function of the application for search film, series and anime.
|
Main function of the application for search.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
string_to_search (str, optional): String to search for
|
string_to_search (str, optional): String to search for
|
||||||
get_onylDatabase (bool, optional): If True, return only the database object
|
get_onlyDatabase (bool, optional): If True, return only the database object
|
||||||
direct_item (dict, optional): Direct item to process (bypass search)
|
direct_item (dict, optional): Direct item to process (bypass search)
|
||||||
|
selections (dict, optional): Dictionary containing selection inputs that bypass manual input
|
||||||
|
{'season': season_selection, 'episode': episode_selection}
|
||||||
"""
|
"""
|
||||||
if direct_item:
|
if direct_item:
|
||||||
select_title = MediaItem(**direct_item)
|
select_title = MediaItem(**direct_item)
|
||||||
process_search_result(select_title)
|
process_search_result(select_title, selections)
|
||||||
return
|
return
|
||||||
|
|
||||||
if string_to_search is None:
|
if string_to_search is None:
|
||||||
@ -62,7 +76,7 @@ def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_
|
|||||||
|
|
||||||
if len_database > 0:
|
if len_database > 0:
|
||||||
select_title = get_select_title(table_show_manager, media_search_manager)
|
select_title = get_select_title(table_show_manager, media_search_manager)
|
||||||
process_search_result(select_title)
|
process_search_result(select_title, selections)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
# 13.06.24
|
# 13.06.24
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import logging
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
|
|
||||||
|
|
||||||
@ -39,22 +40,22 @@ console = Console()
|
|||||||
|
|
||||||
def download_video(index_season_selected: int, index_episode_selected: int, scape_info_serie: GetSerieInfo) -> Tuple[str,bool]:
|
def download_video(index_season_selected: int, index_episode_selected: int, scape_info_serie: GetSerieInfo) -> Tuple[str,bool]:
|
||||||
"""
|
"""
|
||||||
Download a single episode video.
|
Downloads a specific episode from a specified season.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
- tv_name (str): Name of the TV series.
|
- index_season_selected (int): Season number
|
||||||
- index_season_selected (int): Index of the selected season.
|
- index_episode_selected (int): Episode index
|
||||||
- index_episode_selected (int): Index of the selected episode.
|
- scape_info_serie (GetSerieInfo): Scraper object with series information
|
||||||
|
|
||||||
Return:
|
Returns:
|
||||||
- str: output path
|
- str: Path to downloaded file
|
||||||
- bool: kill handler status
|
- bool: Whether download was stopped
|
||||||
"""
|
"""
|
||||||
start_message()
|
start_message()
|
||||||
index_season_selected = dynamic_format_number(str(index_season_selected))
|
|
||||||
|
|
||||||
# Get info about episode
|
# Get episode information
|
||||||
obj_episode = scape_info_serie.list_episodes[index_episode_selected - 1]
|
obj_episode = scape_info_serie.selectEpisode(index_season_selected, index_episode_selected-1)
|
||||||
|
index_season_selected = dynamic_format_number(str(index_season_selected))
|
||||||
console.print(f"[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] → [bold magenta]{obj_episode.get('name')}[/bold magenta] ([cyan]S{index_season_selected}E{index_episode_selected}[/cyan]) \n")
|
console.print(f"[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] → [bold magenta]{obj_episode.get('name')}[/bold magenta] ([cyan]S{index_season_selected}E{index_episode_selected}[/cyan]) \n")
|
||||||
|
|
||||||
# Define filename and path for the downloaded video
|
# Define filename and path for the downloaded video
|
||||||
@ -80,24 +81,23 @@ def download_video(index_season_selected: int, index_episode_selected: int, scap
|
|||||||
return r_proc['path'], r_proc['stopped']
|
return r_proc['path'], r_proc['stopped']
|
||||||
|
|
||||||
|
|
||||||
def download_episode(scape_info_serie: GetSerieInfo, index_season_selected: int, download_all: bool = False) -> None:
|
def download_episode(scape_info_serie: GetSerieInfo, index_season_selected: int, download_all: bool = False, episode_selection: str = None) -> None:
|
||||||
"""
|
"""
|
||||||
Download all episodes of a season.
|
Handle downloading episodes for a specific season.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
- tv_name (str): Name of the TV series.
|
- scape_info_serie (GetSerieInfo): Scraper object with series information
|
||||||
- index_season_selected (int): Index of the selected season.
|
- index_season_selected (int): Season number
|
||||||
- download_all (bool): Download all seasons episodes
|
- download_all (bool): Whether to download all episodes
|
||||||
|
- episode_selection (str, optional): Pre-defined episode selection that bypasses manual input
|
||||||
"""
|
"""
|
||||||
|
# Get episodes for the selected season
|
||||||
# Start message and collect information about episodes
|
episodes = scape_info_serie.get_episode_number(index_season_selected)
|
||||||
start_message()
|
episodes_count = len(episodes)
|
||||||
list_dict_episode = scape_info_serie.get_episode_number(index_season_selected)
|
|
||||||
episodes_count = len(list_dict_episode)
|
|
||||||
|
|
||||||
if download_all:
|
if download_all:
|
||||||
|
|
||||||
# Download all episodes without asking
|
# Download all episodes in the season
|
||||||
for i_episode in range(1, episodes_count + 1):
|
for i_episode in range(1, episodes_count + 1):
|
||||||
path, stopped = download_video(index_season_selected, i_episode, scape_info_serie)
|
path, stopped = download_video(index_season_selected, i_episode, scape_info_serie)
|
||||||
|
|
||||||
@ -109,14 +109,15 @@ def download_episode(scape_info_serie: GetSerieInfo, index_season_selected: int,
|
|||||||
else:
|
else:
|
||||||
|
|
||||||
# Display episodes list and manage user selection
|
# Display episodes list and manage user selection
|
||||||
last_command = display_episodes_list(scape_info_serie.list_episodes)
|
if episode_selection is None:
|
||||||
|
last_command = display_episodes_list(scape_info_serie.list_episodes)
|
||||||
|
else:
|
||||||
|
last_command = episode_selection
|
||||||
|
console.print(f"\n[cyan]Using provided episode selection: [yellow]{episode_selection}")
|
||||||
|
|
||||||
|
# Validate the selection
|
||||||
list_episode_select = manage_selection(last_command, episodes_count)
|
list_episode_select = manage_selection(last_command, episodes_count)
|
||||||
|
list_episode_select = validate_episode_selection(list_episode_select, episodes_count)
|
||||||
try:
|
|
||||||
list_episode_select = validate_episode_selection(list_episode_select, episodes_count)
|
|
||||||
except ValueError as e:
|
|
||||||
console.print(f"[red]{str(e)}")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Download selected episodes
|
# Download selected episodes
|
||||||
for i_episode in list_episode_select:
|
for i_episode in list_episode_select:
|
||||||
@ -126,46 +127,47 @@ def download_episode(scape_info_serie: GetSerieInfo, index_season_selected: int,
|
|||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
def download_series(dict_serie: MediaItem) -> None:
|
def download_series(dict_serie: MediaItem, season_selection: str = None, episode_selection: str = None) -> None:
|
||||||
"""
|
"""
|
||||||
Download all episodes of a TV series.
|
Handle downloading a complete series.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
- dict_serie (MediaItem): obj with url name type and score
|
- dict_serie (MediaItem): Series metadata from search
|
||||||
|
- season_selection (str, optional): Pre-defined season selection that bypasses manual input
|
||||||
|
- episode_selection (str, optional): Pre-defined episode selection that bypasses manual input
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Start message and set up video source
|
|
||||||
start_message()
|
start_message()
|
||||||
|
|
||||||
# Init class
|
# Create class
|
||||||
scape_info_serie = GetSerieInfo(dict_serie)
|
scrape_serie = GetSerieInfo(dict_serie)
|
||||||
|
|
||||||
# Collect information about seasons
|
|
||||||
seasons_count = scape_info_serie.get_seasons_number()
|
|
||||||
|
|
||||||
|
# Get season count
|
||||||
|
seasons_count = scrape_serie.get_seasons_number()
|
||||||
|
|
||||||
# Prompt user for season selection and download episodes
|
# Prompt user for season selection and download episodes
|
||||||
console.print(f"\n[green]Seasons found: [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"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Manage and validate the selection
|
|
||||||
list_season_select = manage_selection(index_season_selected, seasons_count)
|
|
||||||
|
|
||||||
try:
|
# If season_selection is provided, use it instead of asking for input
|
||||||
list_season_select = validate_selection(list_season_select, seasons_count)
|
if season_selection is None:
|
||||||
except ValueError as e:
|
index_season_selected = msg.ask(
|
||||||
console.print(f"[red]{str(e)}")
|
"\n[cyan]Insert season number [yellow](e.g., 1), [red]* [cyan]to download all seasons, "
|
||||||
return
|
"[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"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
index_season_selected = season_selection
|
||||||
|
console.print(f"\n[cyan]Using provided season selection: [yellow]{season_selection}")
|
||||||
|
|
||||||
|
# Validate the selection
|
||||||
|
list_season_select = manage_selection(index_season_selected, seasons_count)
|
||||||
|
list_season_select = validate_selection(list_season_select, seasons_count)
|
||||||
|
|
||||||
# Loop through the selected seasons and download episodes
|
# Loop through the selected seasons and download episodes
|
||||||
for i_season in list_season_select:
|
for i_season in list_season_select:
|
||||||
if len(list_season_select) > 1 or index_season_selected == "*":
|
if len(list_season_select) > 1 or index_season_selected == "*":
|
||||||
|
|
||||||
# Download all episodes if multiple seasons are selected or if '*' is used
|
# Download all episodes if multiple seasons are selected or if '*' is used
|
||||||
download_episode(scape_info_serie, i_season, download_all=True)
|
download_episode(scrape_serie, i_season, download_all=True)
|
||||||
else:
|
else:
|
||||||
|
|
||||||
# Otherwise, let the user select specific episodes for the single season
|
# Otherwise, let the user select specific episodes for the single season
|
||||||
download_episode(scape_info_serie, i_season, download_all=False)
|
download_episode(scrape_serie, i_season, download_all=False, episode_selection=episode_selection)
|
@ -62,9 +62,10 @@ def title_search(query: str) -> int:
|
|||||||
link = serie_div.find('a').get("href")
|
link = serie_div.find('a').get("href")
|
||||||
|
|
||||||
serie_info = {
|
serie_info = {
|
||||||
'name': title,
|
'name': title.replace("streaming guardaserie", ""),
|
||||||
'url': link,
|
'url': link,
|
||||||
'type': 'tv'
|
'type': 'tv',
|
||||||
|
'image': f"{site_constant.FULL_URL}/{serie_div.find('img').get("src")}",
|
||||||
}
|
}
|
||||||
|
|
||||||
media_search_manager.add_media(serie_info)
|
media_search_manager.add_media(serie_info)
|
||||||
|
@ -104,4 +104,30 @@ class GetSerieInfo:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error parsing HTML page: {e}")
|
logging.error(f"Error parsing HTML page: {e}")
|
||||||
|
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
# ------------- FOR GUI -------------
|
||||||
|
def getNumberSeason(self) -> int:
|
||||||
|
"""
|
||||||
|
Get the total number of seasons available for the series.
|
||||||
|
"""
|
||||||
|
return self.get_seasons_number()
|
||||||
|
|
||||||
|
def getEpisodeSeasons(self, season_number: int) -> list:
|
||||||
|
"""
|
||||||
|
Get all episodes for a specific season.
|
||||||
|
"""
|
||||||
|
episodes = self.get_episode_number(season_number)
|
||||||
|
return episodes
|
||||||
|
|
||||||
|
def selectEpisode(self, season_number: int, episode_index: int) -> dict:
|
||||||
|
"""
|
||||||
|
Get information for a specific episode in a specific season.
|
||||||
|
"""
|
||||||
|
episodes = self.getEpisodeSeasons(season_number)
|
||||||
|
if not episodes or episode_index < 0 or episode_index >= len(episodes):
|
||||||
|
logging.error(f"Episode index {episode_index} is out of range for season {season_number}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
return episodes[episode_index]
|
@ -1,73 +0,0 @@
|
|||||||
# 26.05.24
|
|
||||||
|
|
||||||
from urllib.parse import quote_plus
|
|
||||||
|
|
||||||
|
|
||||||
# External library
|
|
||||||
from rich.console import Console
|
|
||||||
from rich.prompt import Prompt
|
|
||||||
|
|
||||||
|
|
||||||
# Internal utilities
|
|
||||||
from StreamingCommunity.Api.Template.config_loader import site_constant
|
|
||||||
from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
|
|
||||||
from StreamingCommunity.Lib.TMBD import tmdb, Json_film
|
|
||||||
|
|
||||||
|
|
||||||
# Logic class
|
|
||||||
from .film import download_film
|
|
||||||
|
|
||||||
|
|
||||||
# Variable
|
|
||||||
indice = 7
|
|
||||||
_useFor = "film"
|
|
||||||
_deprecate = True
|
|
||||||
_priority = 2
|
|
||||||
_engineDownload = "hls"
|
|
||||||
|
|
||||||
msg = Prompt()
|
|
||||||
console = Console()
|
|
||||||
|
|
||||||
|
|
||||||
def process_search_result(select_title):
|
|
||||||
"""
|
|
||||||
Handles the search result and initiates the download for either a film or series.
|
|
||||||
"""
|
|
||||||
download_film(select_title)
|
|
||||||
|
|
||||||
|
|
||||||
def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_item: dict = None):
|
|
||||||
"""
|
|
||||||
Main function of the application for search film, series and anime.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
string_to_search (str, optional): String to search for
|
|
||||||
get_onylDatabase (bool, optional): If True, return only the database object
|
|
||||||
direct_item (dict, optional): Direct item to process (bypass search)
|
|
||||||
"""
|
|
||||||
if direct_item:
|
|
||||||
select_title = MediaItem(**direct_item)
|
|
||||||
process_search_result(select_title)
|
|
||||||
return
|
|
||||||
|
|
||||||
if string_to_search is None:
|
|
||||||
string_to_search = msg.ask(f"\n[purple]Insert word to search in [green]{site_constant.SITE_NAME}").strip()
|
|
||||||
|
|
||||||
# Not available for the moment
|
|
||||||
if get_onlyDatabase:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
# Search on database
|
|
||||||
movie_id = tmdb.search_movie(quote_plus(string_to_search))
|
|
||||||
|
|
||||||
if movie_id is not None:
|
|
||||||
movie_details: Json_film = tmdb.get_movie_details(tmdb_id=movie_id)
|
|
||||||
|
|
||||||
# Download only film
|
|
||||||
download_film(movie_details)
|
|
||||||
|
|
||||||
else:
|
|
||||||
|
|
||||||
# If no results are found, ask again
|
|
||||||
console.print(f"\n[red]Nothing matching was found for[white]: [purple]{string_to_search}")
|
|
||||||
search()
|
|
@ -1,93 +0,0 @@
|
|||||||
# 17.09.24
|
|
||||||
|
|
||||||
import os
|
|
||||||
import logging
|
|
||||||
|
|
||||||
|
|
||||||
# External libraries
|
|
||||||
import httpx
|
|
||||||
from bs4 import BeautifulSoup
|
|
||||||
from rich.console import Console
|
|
||||||
|
|
||||||
|
|
||||||
# Internal utilities
|
|
||||||
from StreamingCommunity.Util.os import os_manager, get_call_stack
|
|
||||||
from StreamingCommunity.Util.message import start_message
|
|
||||||
from StreamingCommunity.Util.headers import get_userAgent
|
|
||||||
from StreamingCommunity.Util.table import TVShowManager
|
|
||||||
from StreamingCommunity.Lib.Downloader import HLS_Downloader
|
|
||||||
|
|
||||||
|
|
||||||
# Player
|
|
||||||
from StreamingCommunity.Api.Player.supervideo import VideoSource
|
|
||||||
|
|
||||||
|
|
||||||
# Logic class
|
|
||||||
from StreamingCommunity.Api.Template.config_loader import site_constant
|
|
||||||
from StreamingCommunity.Lib.TMBD import Json_film
|
|
||||||
|
|
||||||
|
|
||||||
# Variable
|
|
||||||
console = Console()
|
|
||||||
|
|
||||||
|
|
||||||
def download_film(movie_details: Json_film) -> str:
|
|
||||||
"""
|
|
||||||
Downloads a film using the provided tmbd id.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
- movie_details (Json_film): Class with info about film title.
|
|
||||||
|
|
||||||
Return:
|
|
||||||
- str: output path
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Start message and display film information
|
|
||||||
start_message()
|
|
||||||
console.print(f"[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] → [cyan]{movie_details.title}[/cyan] \n")
|
|
||||||
|
|
||||||
# Make request to main site
|
|
||||||
try:
|
|
||||||
url = f"{site_constant.FULL_URL}/set-movie-a/{movie_details.imdb_id}"
|
|
||||||
response = httpx.get(url, headers={'User-Agent': get_userAgent()})
|
|
||||||
response.raise_for_status()
|
|
||||||
|
|
||||||
except:
|
|
||||||
logging.error(f"Not found in the server. Dict: {movie_details}")
|
|
||||||
raise
|
|
||||||
|
|
||||||
if "not found" in str(response.text):
|
|
||||||
logging.error(f"Cant find in the server: {movie_details.title}.")
|
|
||||||
|
|
||||||
research_func = next((
|
|
||||||
f for f in get_call_stack()
|
|
||||||
if f['function'] == 'search' and f['script'] == '__init__.py'
|
|
||||||
), None)
|
|
||||||
TVShowManager.run_back_command(research_func)
|
|
||||||
|
|
||||||
# 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(url=supervideo_url)
|
|
||||||
|
|
||||||
# Define output path
|
|
||||||
title_name = os_manager.get_sanitize_file(movie_details.title) + ".mp4"
|
|
||||||
mp4_path = os.path.join(site_constant.MOVIE_FOLDER, title_name.replace(".mp4", ""))
|
|
||||||
|
|
||||||
# Get m3u8 master playlist
|
|
||||||
master_playlist = video_source.get_playlist()
|
|
||||||
|
|
||||||
# Download the film using the m3u8 playlist, and output filename
|
|
||||||
r_proc = HLS_Downloader(
|
|
||||||
m3u8_url=master_playlist,
|
|
||||||
output_path=os.path.join(mp4_path, title_name)
|
|
||||||
).start()
|
|
||||||
|
|
||||||
if r_proc['error'] is not None:
|
|
||||||
try: os.remove(r_proc['path'])
|
|
||||||
except: pass
|
|
||||||
|
|
||||||
return r_proc['path']
|
|
93
StreamingCommunity/Api/Site/raiplay/__init__.py
Normal file
93
StreamingCommunity/Api/Site/raiplay/__init__.py
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
# 21.05.24
|
||||||
|
|
||||||
|
|
||||||
|
# External library
|
||||||
|
from rich.console import Console
|
||||||
|
from rich.prompt import Prompt
|
||||||
|
|
||||||
|
|
||||||
|
# Internal utilities
|
||||||
|
from StreamingCommunity.Api.Template import get_select_title
|
||||||
|
from StreamingCommunity.Api.Template.config_loader import site_constant
|
||||||
|
from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
|
||||||
|
|
||||||
|
|
||||||
|
# Logic class
|
||||||
|
from .site import title_search, table_show_manager, media_search_manager
|
||||||
|
from .series import download_series
|
||||||
|
from .film import download_film
|
||||||
|
|
||||||
|
|
||||||
|
# Variable
|
||||||
|
indice = 8
|
||||||
|
_useFor = "film_serie"
|
||||||
|
_deprecate = False
|
||||||
|
_priority = 3
|
||||||
|
_engineDownload = "hls"
|
||||||
|
|
||||||
|
msg = Prompt()
|
||||||
|
console = Console()
|
||||||
|
|
||||||
|
|
||||||
|
def get_user_input(string_to_search: str = None):
|
||||||
|
"""
|
||||||
|
Asks the user to input a search term.
|
||||||
|
"""
|
||||||
|
return msg.ask(f"\n[purple]Insert a word to search in [green]{site_constant.SITE_NAME}").strip()
|
||||||
|
|
||||||
|
def process_search_result(select_title, selections=None):
|
||||||
|
"""
|
||||||
|
Handles the search result and initiates the download for either a film or series.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
select_title (MediaItem): The selected media item
|
||||||
|
selections (dict, optional): Dictionary containing selection inputs that bypass manual input
|
||||||
|
{'season': season_selection, 'episode': episode_selection}
|
||||||
|
"""
|
||||||
|
if select_title.type == 'tv':
|
||||||
|
season_selection = None
|
||||||
|
episode_selection = None
|
||||||
|
|
||||||
|
if selections:
|
||||||
|
season_selection = selections.get('season')
|
||||||
|
episode_selection = selections.get('episode')
|
||||||
|
|
||||||
|
download_series(select_title, season_selection, episode_selection)
|
||||||
|
|
||||||
|
else:
|
||||||
|
download_film(select_title)
|
||||||
|
|
||||||
|
def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_item: dict = None, selections: dict = None):
|
||||||
|
"""
|
||||||
|
Main function of the application for search.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
string_to_search (str, optional): String to search for
|
||||||
|
get_onlyDatabase (bool, optional): If True, return only the database object
|
||||||
|
direct_item (dict, optional): Direct item to process (bypass search)
|
||||||
|
selections (dict, optional): Dictionary containing selection inputs that bypass manual input
|
||||||
|
{'season': season_selection, 'episode': episode_selection}
|
||||||
|
"""
|
||||||
|
if direct_item:
|
||||||
|
select_title = MediaItem(**direct_item)
|
||||||
|
process_search_result(select_title, selections)
|
||||||
|
return
|
||||||
|
|
||||||
|
if string_to_search is None:
|
||||||
|
string_to_search = msg.ask(f"\n[purple]Insert a word to search in [green]{site_constant.SITE_NAME}").strip()
|
||||||
|
|
||||||
|
# Search on database
|
||||||
|
len_database = title_search(string_to_search)
|
||||||
|
|
||||||
|
# If only the database is needed, return the manager
|
||||||
|
if get_onlyDatabase:
|
||||||
|
return media_search_manager
|
||||||
|
|
||||||
|
if len_database > 0:
|
||||||
|
select_title = get_select_title(table_show_manager, media_search_manager)
|
||||||
|
process_search_result(select_title, selections)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# If no results are found, ask again
|
||||||
|
console.print(f"\n[red]Nothing matching was found for[white]: [purple]{string_to_search}")
|
||||||
|
search()
|
65
StreamingCommunity/Api/Site/raiplay/film.py
Normal file
65
StreamingCommunity/Api/Site/raiplay/film.py
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
# 3.12.23
|
||||||
|
|
||||||
|
import os
|
||||||
|
from typing import Tuple
|
||||||
|
|
||||||
|
|
||||||
|
# External library
|
||||||
|
import httpx
|
||||||
|
from rich.console import Console
|
||||||
|
|
||||||
|
|
||||||
|
# Internal utilities
|
||||||
|
from StreamingCommunity.Util.os import os_manager
|
||||||
|
from StreamingCommunity.Util.message import start_message
|
||||||
|
from StreamingCommunity.Lib.Downloader import HLS_Downloader
|
||||||
|
from StreamingCommunity.Util.headers import get_headers
|
||||||
|
|
||||||
|
|
||||||
|
# Logic class
|
||||||
|
from StreamingCommunity.Api.Template.config_loader import site_constant
|
||||||
|
from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
|
||||||
|
|
||||||
|
|
||||||
|
# Player
|
||||||
|
from StreamingCommunity.Api.Player.mediapolisvod import VideoSource
|
||||||
|
|
||||||
|
|
||||||
|
# Variable
|
||||||
|
console = Console()
|
||||||
|
|
||||||
|
|
||||||
|
def download_film(select_title: MediaItem) -> Tuple[str, bool]:
|
||||||
|
"""
|
||||||
|
Downloads a film using the provided MediaItem information.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- select_title (MediaItem): The media item containing film information
|
||||||
|
|
||||||
|
Return:
|
||||||
|
- str: Path to downloaded file
|
||||||
|
- bool: Whether download was stopped
|
||||||
|
"""
|
||||||
|
start_message()
|
||||||
|
console.print(f"[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] → [cyan]{select_title.name}[/cyan] \n")
|
||||||
|
|
||||||
|
# Extract m3u8 URL from the film's URL
|
||||||
|
response = httpx.get(select_title.url + ".json", headers=get_headers(), timeout=10)
|
||||||
|
first_item_path = "https://www.raiplay.it" + response.json().get("first_item_path")
|
||||||
|
master_playlist = VideoSource.extract_m3u8_url(first_item_path)
|
||||||
|
|
||||||
|
# Define the filename and path for the downloaded film
|
||||||
|
title_name = os_manager.get_sanitize_file(select_title.name) + ".mp4"
|
||||||
|
mp4_path = os.path.join(site_constant.MOVIE_FOLDER, title_name.replace(".mp4", ""))
|
||||||
|
|
||||||
|
# Download the film using the m3u8 playlist, and output filename
|
||||||
|
r_proc = HLS_Downloader(
|
||||||
|
m3u8_url=master_playlist,
|
||||||
|
output_path=os.path.join(mp4_path, title_name)
|
||||||
|
).start()
|
||||||
|
|
||||||
|
if r_proc['error'] is not None:
|
||||||
|
try: os.remove(r_proc['path'])
|
||||||
|
except: pass
|
||||||
|
|
||||||
|
return r_proc['path'], r_proc['stopped']
|
162
StreamingCommunity/Api/Site/raiplay/series.py
Normal file
162
StreamingCommunity/Api/Site/raiplay/series.py
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
# 3.12.23
|
||||||
|
|
||||||
|
import os
|
||||||
|
from typing import Tuple
|
||||||
|
|
||||||
|
|
||||||
|
# External library
|
||||||
|
from rich.console import Console
|
||||||
|
from rich.prompt import Prompt
|
||||||
|
|
||||||
|
|
||||||
|
# Internal utilities
|
||||||
|
from StreamingCommunity.Util.message import start_message
|
||||||
|
from StreamingCommunity.Lib.Downloader import HLS_Downloader
|
||||||
|
|
||||||
|
# Logic class
|
||||||
|
from .util.ScrapeSerie import GetSerieInfo
|
||||||
|
from StreamingCommunity.Api.Template.Util import (
|
||||||
|
manage_selection,
|
||||||
|
map_episode_title,
|
||||||
|
validate_selection,
|
||||||
|
validate_episode_selection,
|
||||||
|
display_episodes_list
|
||||||
|
)
|
||||||
|
from StreamingCommunity.Api.Template.config_loader import site_constant
|
||||||
|
from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
|
||||||
|
|
||||||
|
|
||||||
|
# Player
|
||||||
|
from StreamingCommunity.Api.Player.mediapolisvod import VideoSource
|
||||||
|
|
||||||
|
|
||||||
|
# Variable
|
||||||
|
msg = Prompt()
|
||||||
|
console = Console()
|
||||||
|
|
||||||
|
|
||||||
|
def download_video(index_season_selected: int, index_episode_selected: int, scrape_serie: GetSerieInfo) -> Tuple[str,bool]:
|
||||||
|
"""
|
||||||
|
Downloads a specific episode from the specified season.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- index_season_selected (int): Season number
|
||||||
|
- index_episode_selected (int): Episode index
|
||||||
|
- scrape_serie (GetSerieInfo): Scraper object with series information
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- str: Path to downloaded file
|
||||||
|
- bool: Whether download was stopped
|
||||||
|
"""
|
||||||
|
start_message()
|
||||||
|
|
||||||
|
# Get episode information
|
||||||
|
obj_episode = scrape_serie.selectEpisode(index_season_selected, index_episode_selected-1)
|
||||||
|
console.print(f"[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] → [bold magenta]{obj_episode.name}[/bold magenta] ([cyan]S{index_season_selected}E{index_episode_selected}[/cyan]) \n")
|
||||||
|
|
||||||
|
# Get streaming URL
|
||||||
|
master_playlist = VideoSource.extract_m3u8_url(obj_episode.url)
|
||||||
|
|
||||||
|
# Define filename and path
|
||||||
|
mp4_name = f"{map_episode_title(scrape_serie.series_name, index_season_selected, index_episode_selected, obj_episode.name)}.mp4"
|
||||||
|
mp4_path = os.path.join(site_constant.SERIES_FOLDER, scrape_serie.series_name, f"S{index_season_selected}")
|
||||||
|
|
||||||
|
# Download the episode
|
||||||
|
r_proc = HLS_Downloader(
|
||||||
|
m3u8_url=master_playlist,
|
||||||
|
output_path=os.path.join(mp4_path, mp4_name)
|
||||||
|
).start()
|
||||||
|
|
||||||
|
if r_proc['error'] is not None:
|
||||||
|
try: os.remove(r_proc['path'])
|
||||||
|
except: pass
|
||||||
|
|
||||||
|
return r_proc['path'], r_proc['stopped']
|
||||||
|
|
||||||
|
|
||||||
|
def download_episode(index_season_selected: int, scrape_serie: GetSerieInfo, download_all: bool = False, episode_selection: str = None) -> None:
|
||||||
|
"""
|
||||||
|
Handle downloading episodes for a specific season.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- index_season_selected (int): Season number
|
||||||
|
- scrape_serie (GetSerieInfo): Scraper object with series information
|
||||||
|
- download_all (bool): Whether to download all episodes
|
||||||
|
- episode_selection (str, optional): Pre-defined episode selection that bypasses manual input
|
||||||
|
"""
|
||||||
|
# Get episodes for the selected season
|
||||||
|
episodes = scrape_serie.getEpisodeSeasons(index_season_selected)
|
||||||
|
episodes_count = len(episodes)
|
||||||
|
|
||||||
|
if download_all:
|
||||||
|
for i_episode in range(1, episodes_count + 1):
|
||||||
|
path, stopped = download_video(index_season_selected, i_episode, scrape_serie)
|
||||||
|
if stopped:
|
||||||
|
break
|
||||||
|
console.print(f"\n[red]End downloaded [yellow]season: [red]{index_season_selected}.")
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Display episodes list and manage user selection
|
||||||
|
if episode_selection is None:
|
||||||
|
last_command = display_episodes_list(episodes)
|
||||||
|
else:
|
||||||
|
last_command = episode_selection
|
||||||
|
console.print(f"\n[cyan]Using provided episode selection: [yellow]{episode_selection}")
|
||||||
|
|
||||||
|
# Validate the selection
|
||||||
|
list_episode_select = manage_selection(last_command, episodes_count)
|
||||||
|
list_episode_select = validate_episode_selection(list_episode_select, episodes_count)
|
||||||
|
|
||||||
|
# Download selected episodes if not stopped
|
||||||
|
for i_episode in list_episode_select:
|
||||||
|
path, stopped = download_video(index_season_selected, i_episode, scrape_serie)
|
||||||
|
if stopped:
|
||||||
|
break
|
||||||
|
|
||||||
|
def download_series(select_season: MediaItem, season_selection: str = None, episode_selection: str = None) -> None:
|
||||||
|
"""
|
||||||
|
Handle downloading a complete series.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- select_season (MediaItem): Series metadata from search
|
||||||
|
- season_selection (str, optional): Pre-defined season selection that bypasses manual input
|
||||||
|
- episode_selection (str, optional): Pre-defined episode selection that bypasses manual input
|
||||||
|
"""
|
||||||
|
start_message()
|
||||||
|
|
||||||
|
# Extract program name from path_id
|
||||||
|
program_name = None
|
||||||
|
if select_season.path_id:
|
||||||
|
parts = select_season.path_id.strip('/').split('/')
|
||||||
|
if len(parts) >= 2:
|
||||||
|
program_name = parts[-1].split('.')[0]
|
||||||
|
|
||||||
|
# Init scraper
|
||||||
|
scrape_serie = GetSerieInfo(program_name)
|
||||||
|
|
||||||
|
# Get seasons info
|
||||||
|
scrape_serie.collect_info_title()
|
||||||
|
seasons_count = len(scrape_serie.seasons_manager)
|
||||||
|
console.print(f"\n[green]Seasons found: [red]{seasons_count}")
|
||||||
|
|
||||||
|
# If season_selection is provided, use it instead of asking for input
|
||||||
|
if season_selection is None:
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
index_season_selected = season_selection
|
||||||
|
console.print(f"\n[cyan]Using provided season selection: [yellow]{season_selection}")
|
||||||
|
|
||||||
|
# Validate the selection
|
||||||
|
list_season_select = manage_selection(index_season_selected, seasons_count)
|
||||||
|
list_season_select = validate_selection(list_season_select, seasons_count)
|
||||||
|
|
||||||
|
# Loop through the selected seasons and download episodes
|
||||||
|
for season_number in list_season_select:
|
||||||
|
if len(list_season_select) > 1 or index_season_selected == "*":
|
||||||
|
download_episode(season_number, scrape_serie, download_all=True)
|
||||||
|
else:
|
||||||
|
download_episode(season_number, scrape_serie, download_all=False, episode_selection=episode_selection)
|
166
StreamingCommunity/Api/Site/raiplay/site.py
Normal file
166
StreamingCommunity/Api/Site/raiplay/site.py
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
# 10.12.23
|
||||||
|
|
||||||
|
import threading
|
||||||
|
import queue
|
||||||
|
|
||||||
|
# External libraries
|
||||||
|
import httpx
|
||||||
|
from rich.console import Console
|
||||||
|
|
||||||
|
|
||||||
|
# Internal utilities
|
||||||
|
from StreamingCommunity.Util.config_json import config_manager
|
||||||
|
from StreamingCommunity.Util.headers import get_userAgent
|
||||||
|
from StreamingCommunity.Util.table import TVShowManager
|
||||||
|
from StreamingCommunity.Lib.TMBD.tmdb import tmdb
|
||||||
|
|
||||||
|
|
||||||
|
# Logic class
|
||||||
|
from StreamingCommunity.Api.Template.config_loader import site_constant
|
||||||
|
from StreamingCommunity.Api.Template.Class.SearchType import MediaManager
|
||||||
|
|
||||||
|
|
||||||
|
# Variable
|
||||||
|
console = Console()
|
||||||
|
media_search_manager = MediaManager()
|
||||||
|
table_show_manager = TVShowManager()
|
||||||
|
max_timeout = config_manager.get_int("REQUESTS", "timeout")
|
||||||
|
MAX_THREADS = 4
|
||||||
|
|
||||||
|
|
||||||
|
def determine_media_type(title):
|
||||||
|
"""
|
||||||
|
Use TMDB to determine if a title is a movie or TV show.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# First search as a movie
|
||||||
|
movie_results = tmdb._make_request("search/movie", {"query": title})
|
||||||
|
movie_count = len(movie_results.get("results", []))
|
||||||
|
|
||||||
|
# Then search as a TV show
|
||||||
|
tv_results = tmdb._make_request("search/tv", {"query": title})
|
||||||
|
tv_count = len(tv_results.get("results", []))
|
||||||
|
|
||||||
|
# If results found in only one category, use that
|
||||||
|
if movie_count > 0 and tv_count == 0:
|
||||||
|
return "film"
|
||||||
|
elif tv_count > 0 and movie_count == 0:
|
||||||
|
return "tv"
|
||||||
|
|
||||||
|
# If both have results, compare popularity
|
||||||
|
if movie_count > 0 and tv_count > 0:
|
||||||
|
top_movie = movie_results["results"][0]
|
||||||
|
top_tv = tv_results["results"][0]
|
||||||
|
|
||||||
|
return "film" if top_movie.get("popularity", 0) > top_tv.get("popularity", 0) else "tv"
|
||||||
|
|
||||||
|
return "film"
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
console.log(f"Error determining media type with TMDB: {e}")
|
||||||
|
return "film"
|
||||||
|
|
||||||
|
|
||||||
|
def worker_determine_type(work_queue, result_dict, worker_id):
|
||||||
|
"""
|
||||||
|
Worker function to process items from queue and determine media types.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- work_queue: Queue containing items to process
|
||||||
|
- result_dict: Dictionary to store results
|
||||||
|
- worker_id: ID of the worker thread
|
||||||
|
"""
|
||||||
|
while not work_queue.empty():
|
||||||
|
try:
|
||||||
|
index, item = work_queue.get(block=False)
|
||||||
|
title = item.get('titolo', '')
|
||||||
|
media_type = determine_media_type(title)
|
||||||
|
|
||||||
|
result_dict[index] = {
|
||||||
|
'id': item.get('id', ''),
|
||||||
|
'name': title,
|
||||||
|
'type': media_type,
|
||||||
|
'path_id': item.get('path_id', ''),
|
||||||
|
'url': f"https://www.raiplay.it{item.get('url', '')}",
|
||||||
|
'image': f"https://www.raiplay.it{item.get('immagine', '')}",
|
||||||
|
}
|
||||||
|
|
||||||
|
work_queue.task_done()
|
||||||
|
|
||||||
|
except queue.Empty:
|
||||||
|
break
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
console.log(f"Worker {worker_id} error: {e}")
|
||||||
|
work_queue.task_done()
|
||||||
|
|
||||||
|
|
||||||
|
def title_search(query: str) -> int:
|
||||||
|
"""
|
||||||
|
Search for titles based on a search query.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- query (str): The query to search for.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: The number of titles found.
|
||||||
|
"""
|
||||||
|
media_search_manager.clear()
|
||||||
|
table_show_manager.clear()
|
||||||
|
|
||||||
|
search_url = f"https://www.raiplay.it/atomatic/raiplay-search-service/api/v1/msearch"
|
||||||
|
console.print(f"[cyan]Search url: [yellow]{search_url}")
|
||||||
|
|
||||||
|
json_data = {
|
||||||
|
'templateIn': '6470a982e4e0301afe1f81f1',
|
||||||
|
'templateOut': '6516ac5d40da6c377b151642',
|
||||||
|
'params': {
|
||||||
|
'param': query,
|
||||||
|
'from': None,
|
||||||
|
'sort': 'relevance',
|
||||||
|
'onlyVideoQuery': False,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = httpx.post(search_url, headers={'user-agent': get_userAgent()}, json=json_data, timeout=max_timeout, follow_redirects=True)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
console.print(f"Site: {site_constant.SITE_NAME}, request search error: {e}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# Limit to only 15 results for performance
|
||||||
|
data = response.json().get('agg').get('titoli').get('cards')
|
||||||
|
data = data[:15] if len(data) > 15 else data
|
||||||
|
|
||||||
|
# Use multithreading to determine media types in parallel
|
||||||
|
work_queue = queue.Queue()
|
||||||
|
result_dict = {}
|
||||||
|
|
||||||
|
# Add items to the work queue
|
||||||
|
for i, item in enumerate(data):
|
||||||
|
work_queue.put((i, item))
|
||||||
|
|
||||||
|
# Create and start worker threads
|
||||||
|
threads = []
|
||||||
|
for i in range(min(MAX_THREADS, len(data))):
|
||||||
|
thread = threading.Thread(
|
||||||
|
target=worker_determine_type,
|
||||||
|
args=(work_queue, result_dict, i),
|
||||||
|
daemon=True
|
||||||
|
)
|
||||||
|
threads.append(thread)
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
# Wait for all threads to complete
|
||||||
|
for thread in threads:
|
||||||
|
thread.join()
|
||||||
|
|
||||||
|
# Add all results to media manager in correct order
|
||||||
|
for i in range(len(data)):
|
||||||
|
if i in result_dict:
|
||||||
|
media_search_manager.add_media(result_dict[i])
|
||||||
|
|
||||||
|
# Return the number of titles found
|
||||||
|
return media_search_manager.get_length()
|
127
StreamingCommunity/Api/Site/raiplay/util/ScrapeSerie.py
Normal file
127
StreamingCommunity/Api/Site/raiplay/util/ScrapeSerie.py
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
# 01.03.24
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
# External libraries
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
|
||||||
|
# Internal utilities
|
||||||
|
from StreamingCommunity.Util.headers import get_headers
|
||||||
|
from StreamingCommunity.Util.config_json import config_manager
|
||||||
|
from StreamingCommunity.Api.Player.Helper.Vixcloud.util import SeasonManager
|
||||||
|
|
||||||
|
|
||||||
|
# Variable
|
||||||
|
max_timeout = config_manager.get_int("REQUESTS", "timeout")
|
||||||
|
|
||||||
|
|
||||||
|
class GetSerieInfo:
|
||||||
|
def __init__(self, program_name: str):
|
||||||
|
"""Initialize the GetSerieInfo class."""
|
||||||
|
self.base_url = "https://www.raiplay.it"
|
||||||
|
self.program_name = program_name
|
||||||
|
self.series_name = program_name
|
||||||
|
self.seasons_manager = SeasonManager()
|
||||||
|
|
||||||
|
def collect_info_title(self) -> None:
|
||||||
|
"""Get series info including seasons."""
|
||||||
|
try:
|
||||||
|
program_url = f"{self.base_url}/programmi/{self.program_name}.json"
|
||||||
|
response = httpx.get(url=program_url, headers=get_headers(), timeout=max_timeout)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
json_data = response.json()
|
||||||
|
|
||||||
|
# Look for seasons in the 'blocks' property
|
||||||
|
for block in json_data.get('blocks'):
|
||||||
|
if block.get('type') == 'RaiPlay Multimedia Block' and block.get('name', '').lower() == 'episodi':
|
||||||
|
self.publishing_block_id = block.get('id')
|
||||||
|
|
||||||
|
# Extract seasons from sets array
|
||||||
|
for season_set in block.get('sets', []):
|
||||||
|
if 'stagione' in season_set.get('name', '').lower():
|
||||||
|
self.seasons_manager.add_season({
|
||||||
|
'id': season_set.get('id', ''),
|
||||||
|
'number': len(self.seasons_manager.seasons) + 1,
|
||||||
|
'name': season_set.get('name', ''),
|
||||||
|
'path': season_set.get('path_id', ''),
|
||||||
|
'episodes_count': season_set.get('episode_size', {}).get('number', 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Error collecting series info: {e}")
|
||||||
|
|
||||||
|
def collect_info_season(self, number_season: int) -> None:
|
||||||
|
"""Get episodes for a specific season."""
|
||||||
|
try:
|
||||||
|
season = self.seasons_manager.get_season_by_number(number_season)
|
||||||
|
|
||||||
|
url = f"{self.base_url}/programmi/{self.program_name}/{self.publishing_block_id}/{season.id}/episodes.json"
|
||||||
|
response = httpx.get(url=url, headers=get_headers(), timeout=max_timeout)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
episodes_data = response.json()
|
||||||
|
cards = []
|
||||||
|
|
||||||
|
# Extract episodes from different possible structures
|
||||||
|
if 'seasons' in episodes_data:
|
||||||
|
for season_data in episodes_data.get('seasons', []):
|
||||||
|
for episode_set in season_data.get('episodes', []):
|
||||||
|
cards.extend(episode_set.get('cards', []))
|
||||||
|
|
||||||
|
if not cards:
|
||||||
|
cards = episodes_data.get('cards', [])
|
||||||
|
|
||||||
|
# Add episodes to season
|
||||||
|
for ep in cards:
|
||||||
|
episode = {
|
||||||
|
'id': ep.get('id', ''),
|
||||||
|
'number': ep.get('episode', ''),
|
||||||
|
'name': ep.get('episode_title', '') or ep.get('toptitle', ''),
|
||||||
|
'duration': ep.get('duration', ''),
|
||||||
|
'url': f"{self.base_url}{ep.get('weblink', '')}" if 'weblink' in ep else f"{self.base_url}{ep.get('url', '')}"
|
||||||
|
}
|
||||||
|
season.episodes.add(episode)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Error collecting episodes for season {number_season}: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
# ------------- FOR GUI -------------
|
||||||
|
def getNumberSeason(self) -> int:
|
||||||
|
"""
|
||||||
|
Get the total number of seasons available for the series.
|
||||||
|
"""
|
||||||
|
if not self.seasons_manager.seasons:
|
||||||
|
self.collect_info_title()
|
||||||
|
|
||||||
|
return len(self.seasons_manager.seasons)
|
||||||
|
|
||||||
|
def getEpisodeSeasons(self, season_number: int) -> list:
|
||||||
|
"""
|
||||||
|
Get all episodes for a specific season.
|
||||||
|
"""
|
||||||
|
season = self.seasons_manager.get_season_by_number(season_number)
|
||||||
|
|
||||||
|
if not season:
|
||||||
|
logging.error(f"Season {season_number} not found")
|
||||||
|
return []
|
||||||
|
|
||||||
|
if not season.episodes.episodes:
|
||||||
|
self.collect_info_season(season_number)
|
||||||
|
|
||||||
|
return season.episodes.episodes
|
||||||
|
|
||||||
|
def selectEpisode(self, season_number: int, episode_index: int) -> dict:
|
||||||
|
"""
|
||||||
|
Get information for a specific episode in a specific season.
|
||||||
|
"""
|
||||||
|
episodes = self.getEpisodeSeasons(season_number)
|
||||||
|
if not episodes or episode_index < 0 or episode_index >= len(episodes):
|
||||||
|
logging.error(f"Episode index {episode_index} is out of range for season {season_number}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
return episodes[episode_index]
|
@ -58,52 +58,59 @@ def get_user_input(string_to_search: str = None):
|
|||||||
|
|
||||||
return string_to_search
|
return string_to_search
|
||||||
|
|
||||||
def process_search_result(select_title):
|
def process_search_result(select_title, selections=None):
|
||||||
"""
|
"""
|
||||||
Handles the search result and initiates the download for either a film or series.
|
Handles the search result and initiates the download for either a film or series.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
select_title (MediaItem): The selected media item
|
||||||
|
selections (dict, optional): Dictionary containing selection inputs that bypass manual input
|
||||||
|
{'season': season_selection, 'episode': episode_selection}
|
||||||
"""
|
"""
|
||||||
if select_title.type == 'tv':
|
if select_title.type == 'tv':
|
||||||
download_series(select_title)
|
season_selection = None
|
||||||
|
episode_selection = None
|
||||||
|
|
||||||
|
if selections:
|
||||||
|
season_selection = selections.get('season')
|
||||||
|
episode_selection = selections.get('episode')
|
||||||
|
|
||||||
|
download_series(select_title, season_selection, episode_selection)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
download_film(select_title)
|
download_film(select_title)
|
||||||
|
|
||||||
def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_item: dict = None):
|
def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_item: dict = None, selections: dict = None):
|
||||||
"""
|
"""
|
||||||
Main function of the application for search film, series and anime.
|
Main function of the application for search.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
string_to_search (str, optional): String to search for
|
string_to_search (str, optional): String to search for
|
||||||
get_onylDatabase (bool, optional): If True, return only the database object
|
get_onlyDatabase (bool, optional): If True, return only the database object
|
||||||
direct_item (dict, optional): Direct item to process (bypass search)
|
direct_item (dict, optional): Direct item to process (bypass search)
|
||||||
|
selections (dict, optional): Dictionary containing selection inputs that bypass manual input
|
||||||
|
{'season': season_selection, 'episode': episode_selection}
|
||||||
"""
|
"""
|
||||||
if direct_item:
|
if direct_item:
|
||||||
select_title = MediaItem(**direct_item)
|
select_title = MediaItem(**direct_item)
|
||||||
process_search_result(select_title)
|
process_search_result(select_title, selections)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Get the user input for the search term
|
if string_to_search is None:
|
||||||
string_to_search = get_user_input(string_to_search)
|
string_to_search = msg.ask(f"\n[purple]Insert a word to search in [green]{site_constant.SITE_NAME}").strip()
|
||||||
|
|
||||||
# Perform the database search
|
# Search on database
|
||||||
len_database = title_search(quote_plus(string_to_search))
|
len_database = title_search(string_to_search)
|
||||||
|
|
||||||
# If only the database is needed, return the manager
|
# If only the database is needed, return the manager
|
||||||
if get_onlyDatabase:
|
if get_onlyDatabase:
|
||||||
return media_search_manager
|
return media_search_manager
|
||||||
|
|
||||||
if site_constant.TELEGRAM_BOT:
|
|
||||||
bot = get_bot_instance()
|
|
||||||
|
|
||||||
if len_database > 0:
|
if len_database > 0:
|
||||||
select_title = get_select_title(table_show_manager, media_search_manager)
|
select_title = get_select_title(table_show_manager, media_search_manager)
|
||||||
process_search_result(select_title)
|
process_search_result(select_title, selections)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
console.print(f"\n[red]Nothing matching was found for[white]: [purple]{string_to_search}")
|
|
||||||
|
|
||||||
if site_constant.TELEGRAM_BOT:
|
|
||||||
bot.send_message(f"No results found, please try again", None)
|
|
||||||
|
|
||||||
# If no results are found, ask again
|
# If no results are found, ask again
|
||||||
string_to_search = get_user_input()
|
console.print(f"\n[red]Nothing matching was found for[white]: [purple]{string_to_search}")
|
||||||
search()
|
search()
|
@ -55,8 +55,7 @@ def download_film(select_title: MediaItem) -> str:
|
|||||||
console.print(f"[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] → [cyan]{select_title.name}[/cyan] \n")
|
console.print(f"[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] → [cyan]{select_title.name}[/cyan] \n")
|
||||||
|
|
||||||
# Init class
|
# Init class
|
||||||
video_source = VideoSource(site_constant.FULL_URL, False)
|
video_source = VideoSource(site_constant.FULL_URL, False, select_title.id)
|
||||||
video_source.setup(select_title.id)
|
|
||||||
|
|
||||||
# Retrieve scws and if available master playlist
|
# Retrieve scws and if available master playlist
|
||||||
video_source.get_iframe(select_title.id)
|
video_source.get_iframe(select_title.id)
|
||||||
|
@ -39,28 +39,22 @@ console = Console()
|
|||||||
|
|
||||||
def download_video(index_season_selected: int, index_episode_selected: int, scrape_serie: GetSerieInfo, video_source: VideoSource) -> Tuple[str,bool]:
|
def download_video(index_season_selected: int, index_episode_selected: int, scrape_serie: GetSerieInfo, video_source: VideoSource) -> Tuple[str,bool]:
|
||||||
"""
|
"""
|
||||||
Download a single episode video.
|
Downloads a specific episode from the specified season.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
- index_season_selected (int): Index of the selected season.
|
- index_season_selected (int): Season number
|
||||||
- index_episode_selected (int): Index of the selected episode.
|
- index_episode_selected (int): Episode index
|
||||||
|
- scrape_serie (GetSerieInfo): Scraper object with series information
|
||||||
|
- video_source (VideoSource): Video source handler
|
||||||
|
|
||||||
Return:
|
Returns:
|
||||||
- str: output path
|
- str: Path to downloaded file
|
||||||
- bool: kill handler status
|
- bool: Whether download was stopped
|
||||||
"""
|
"""
|
||||||
start_message()
|
start_message()
|
||||||
index_season_selected = dynamic_format_number(str(index_season_selected))
|
|
||||||
|
|
||||||
# SPECIAL: Get season number
|
# Get episode information
|
||||||
season = None
|
obj_episode = scrape_serie.selectEpisode(index_season_selected, index_episode_selected-1)
|
||||||
for s in scrape_serie.seasons_manager.seasons:
|
|
||||||
if s.number == int(index_season_selected):
|
|
||||||
season = s
|
|
||||||
break
|
|
||||||
|
|
||||||
# Get info about episode
|
|
||||||
obj_episode = season.episodes.get(index_episode_selected - 1)
|
|
||||||
console.print(f"[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] → [bold magenta]{obj_episode.name}[/bold magenta] ([cyan]S{index_season_selected}E{index_episode_selected}[/cyan]) \n")
|
console.print(f"[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] → [bold magenta]{obj_episode.name}[/bold magenta] ([cyan]S{index_season_selected}E{index_episode_selected}[/cyan]) \n")
|
||||||
|
|
||||||
if site_constant.TELEGRAM_BOT:
|
if site_constant.TELEGRAM_BOT:
|
||||||
@ -98,28 +92,28 @@ def download_video(index_season_selected: int, index_episode_selected: int, scra
|
|||||||
|
|
||||||
return r_proc['path'], r_proc['stopped']
|
return r_proc['path'], r_proc['stopped']
|
||||||
|
|
||||||
def download_episode(index_season_selected: int, scrape_serie: GetSerieInfo, video_source: VideoSource, download_all: bool = False) -> None:
|
|
||||||
|
def download_episode(index_season_selected: int, scrape_serie: GetSerieInfo, video_source: VideoSource, download_all: bool = False, episode_selection: str = None) -> None:
|
||||||
"""
|
"""
|
||||||
Download episodes of a selected season.
|
Handle downloading episodes for a specific season.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
- index_season_selected (int): Index of the selected season.
|
- index_season_selected (int): Season number
|
||||||
- download_all (bool): Download all episodes in the season.
|
- scrape_serie (GetSerieInfo): Scraper object with series information
|
||||||
|
- video_source (VideoSource): Video source object
|
||||||
|
- download_all (bool): Whether to download all episodes
|
||||||
|
- episode_selection (str, optional): Pre-defined episode selection that bypasses manual input
|
||||||
"""
|
"""
|
||||||
start_message()
|
# Get episodes for the selected season
|
||||||
scrape_serie.collect_info_season(index_season_selected)
|
episodes = scrape_serie.getEpisodeSeasons(index_season_selected)
|
||||||
|
episodes_count = len(episodes)
|
||||||
# SPECIAL: Get season number
|
|
||||||
season = None
|
if episodes_count == 0:
|
||||||
for s in scrape_serie.seasons_manager.seasons:
|
console.print(f"[red]No episodes found for season {index_season_selected}")
|
||||||
if s.number == index_season_selected:
|
return
|
||||||
season = s
|
|
||||||
break
|
|
||||||
episodes_count = len(season.episodes.episodes)
|
|
||||||
|
|
||||||
if download_all:
|
if download_all:
|
||||||
|
# Download all episodes in the season
|
||||||
# Download all episodes without asking
|
|
||||||
for i_episode in range(1, episodes_count + 1):
|
for i_episode in range(1, episodes_count + 1):
|
||||||
path, stopped = download_video(index_season_selected, i_episode, scrape_serie, video_source)
|
path, stopped = download_video(index_season_selected, i_episode, scrape_serie, video_source)
|
||||||
|
|
||||||
@ -129,16 +123,16 @@ def download_episode(index_season_selected: int, scrape_serie: GetSerieInfo, vid
|
|||||||
console.print(f"\n[red]End downloaded [yellow]season: [red]{index_season_selected}.")
|
console.print(f"\n[red]End downloaded [yellow]season: [red]{index_season_selected}.")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
# Display episodes list and manage user selection
|
# Display episodes list and manage user selection
|
||||||
last_command = display_episodes_list(season.episodes.episodes)
|
if episode_selection is None:
|
||||||
|
last_command = display_episodes_list(episodes)
|
||||||
|
else:
|
||||||
|
last_command = episode_selection
|
||||||
|
console.print(f"\n[cyan]Using provided episode selection: [yellow]{episode_selection}")
|
||||||
|
|
||||||
|
# Validate the selection
|
||||||
list_episode_select = manage_selection(last_command, episodes_count)
|
list_episode_select = manage_selection(last_command, episodes_count)
|
||||||
|
list_episode_select = validate_episode_selection(list_episode_select, episodes_count)
|
||||||
try:
|
|
||||||
list_episode_select = validate_episode_selection(list_episode_select, episodes_count)
|
|
||||||
except ValueError as e:
|
|
||||||
console.print(f"[red]{str(e)}")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Download selected episodes if not stopped
|
# Download selected episodes if not stopped
|
||||||
for i_episode in list_episode_select:
|
for i_episode in list_episode_select:
|
||||||
@ -147,70 +141,65 @@ def download_episode(index_season_selected: int, scrape_serie: GetSerieInfo, vid
|
|||||||
if stopped:
|
if stopped:
|
||||||
break
|
break
|
||||||
|
|
||||||
def download_series(select_season: MediaItem) -> None:
|
|
||||||
|
def download_series(select_season: MediaItem, season_selection: str = None, episode_selection: str = None) -> None:
|
||||||
"""
|
"""
|
||||||
Download episodes of a TV series based on user selection.
|
Handle downloading a complete series.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
- select_season (MediaItem): Selected media item (TV series).
|
- select_season (MediaItem): Series metadata from search
|
||||||
- domain (str): Domain from which to download.
|
- season_selection (str, optional): Pre-defined season selection that bypasses manual input
|
||||||
|
- episode_selection (str, optional): Pre-defined episode selection that bypasses manual input
|
||||||
"""
|
"""
|
||||||
if site_constant.TELEGRAM_BOT:
|
|
||||||
bot = get_bot_instance()
|
|
||||||
|
|
||||||
# Start message and set up video source
|
|
||||||
start_message()
|
start_message()
|
||||||
|
|
||||||
# Init class
|
# Init class
|
||||||
scrape_serie = GetSerieInfo(site_constant.FULL_URL)
|
video_source = VideoSource(site_constant.FULL_URL, True, select_season.id)
|
||||||
video_source = VideoSource(site_constant.FULL_URL, True)
|
scrape_serie = GetSerieInfo(site_constant.FULL_URL, select_season.id, select_season.name)
|
||||||
|
|
||||||
# Setup video source
|
# Collect information about season
|
||||||
scrape_serie.setup(select_season.id, select_season.slug)
|
scrape_serie.getNumberSeason()
|
||||||
video_source.setup(select_season.id)
|
|
||||||
|
|
||||||
# Collect information about seasons
|
|
||||||
scrape_serie.collect_info_title()
|
|
||||||
seasons_count = len(scrape_serie.seasons_manager)
|
seasons_count = len(scrape_serie.seasons_manager)
|
||||||
|
|
||||||
|
if site_constant.TELEGRAM_BOT:
|
||||||
|
bot = get_bot_instance()
|
||||||
|
|
||||||
# Prompt user for season selection and download episodes
|
# Prompt user for season selection and download episodes
|
||||||
console.print(f"\n[green]Seasons found: [red]{seasons_count}")
|
console.print(f"\n[green]Seasons found: [red]{seasons_count}")
|
||||||
|
|
||||||
if site_constant.TELEGRAM_BOT:
|
# If season_selection is provided, use it instead of asking for input
|
||||||
console.print("\n[cyan]Insert season number [yellow](e.g., 1), [red]* [cyan]to download all seasons, "
|
if season_selection is None:
|
||||||
"[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")
|
if site_constant.TELEGRAM_BOT:
|
||||||
|
console.print("\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")
|
||||||
|
|
||||||
bot.send_message(f"Stagioni trovate: {seasons_count}", None)
|
bot.send_message(f"Stagioni trovate: {seasons_count}", None)
|
||||||
|
|
||||||
index_season_selected = bot.ask(
|
index_season_selected = bot.ask(
|
||||||
"select_title_episode",
|
"select_title_episode",
|
||||||
"Menu di selezione delle stagioni\n\n"
|
"Menu di selezione delle stagioni\n\n"
|
||||||
"- Inserisci il numero della stagione (ad esempio, 1)\n"
|
"- Inserisci il numero della stagione (ad esempio, 1)\n"
|
||||||
"- Inserisci * per scaricare tutte le stagioni\n"
|
"- Inserisci * per scaricare tutte le stagioni\n"
|
||||||
"- Inserisci un intervallo di stagioni (ad esempio, 1-2) per scaricare da una stagione all'altra\n"
|
"- Inserisci un intervallo di stagioni (ad esempio, 1-2) per scaricare da una stagione all'altra\n"
|
||||||
"- Inserisci (ad esempio, 3-*) per scaricare dalla stagione specificata fino alla fine della serie",
|
"- Inserisci (ad esempio, 3-*) per scaricare dalla stagione specificata fino alla fine della serie",
|
||||||
None
|
None
|
||||||
)
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
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"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
index_season_selected = msg.ask(
|
index_season_selected = season_selection
|
||||||
"\n[cyan]Insert season number [yellow](e.g., 1), [red]* [cyan]to download all seasons, "
|
console.print(f"\n[cyan]Using provided season selection: [yellow]{season_selection}")
|
||||||
"[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
|
# Validate the selection
|
||||||
list_season_select = manage_selection(index_season_selected, seasons_count)
|
list_season_select = manage_selection(index_season_selected, seasons_count)
|
||||||
|
list_season_select = validate_selection(list_season_select, seasons_count)
|
||||||
try:
|
|
||||||
list_season_select = validate_selection(list_season_select, seasons_count)
|
|
||||||
except ValueError as e:
|
|
||||||
console.print(f"[red]{str(e)}")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Loop through the selected seasons and download episodes
|
# Loop through the selected seasons and download episodes
|
||||||
for i_season in list_season_select:
|
for i_season in list_season_select:
|
||||||
|
|
||||||
# SPECIAL: Get season number
|
|
||||||
season = None
|
season = None
|
||||||
for s in scrape_serie.seasons_manager.seasons:
|
for s in scrape_serie.seasons_manager.seasons:
|
||||||
if s.number == i_season:
|
if s.number == i_season:
|
||||||
@ -219,13 +208,10 @@ def download_series(select_season: MediaItem) -> None:
|
|||||||
season_number = season.number
|
season_number = season.number
|
||||||
|
|
||||||
if len(list_season_select) > 1 or index_season_selected == "*":
|
if len(list_season_select) > 1 or index_season_selected == "*":
|
||||||
|
|
||||||
# Download all episodes if multiple seasons are selected or if '*' is used
|
|
||||||
download_episode(season_number, scrape_serie, video_source, download_all=True)
|
download_episode(season_number, scrape_serie, video_source, download_all=True)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
download_episode(season_number, scrape_serie, video_source, download_all=False, episode_selection=episode_selection)
|
||||||
# Otherwise, let the user select specific episodes for the single season
|
|
||||||
download_episode(season_number, scrape_serie, video_source, download_all=False)
|
|
||||||
|
|
||||||
if site_constant.TELEGRAM_BOT:
|
if site_constant.TELEGRAM_BOT:
|
||||||
bot.send_message(f"Finito di scaricare tutte le serie e episodi", None)
|
bot.send_message(f"Finito di scaricare tutte le serie e episodi", None)
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# 10.12.23
|
# 10.12.23
|
||||||
|
|
||||||
import sys
|
|
||||||
|
|
||||||
|
|
||||||
# External libraries
|
# External libraries
|
||||||
import httpx
|
import httpx
|
||||||
@ -75,7 +73,7 @@ def title_search(query: str) -> int:
|
|||||||
'name': dict_title.get('name'),
|
'name': dict_title.get('name'),
|
||||||
'type': dict_title.get('type'),
|
'type': dict_title.get('type'),
|
||||||
'date': dict_title.get('last_air_date'),
|
'date': dict_title.get('last_air_date'),
|
||||||
'score': dict_title.get('score')
|
'image': f"{site_constant.FULL_URL.replace("stream", "cdn.stream")}/images/{dict_title.get('images')[0].get('filename')}"
|
||||||
})
|
})
|
||||||
|
|
||||||
if site_constant.TELEGRAM_BOT:
|
if site_constant.TELEGRAM_BOT:
|
||||||
|
@ -20,31 +20,21 @@ max_timeout = config_manager.get_int("REQUESTS", "timeout")
|
|||||||
|
|
||||||
|
|
||||||
class GetSerieInfo:
|
class GetSerieInfo:
|
||||||
def __init__(self, url):
|
def __init__(self, url, media_id: int = None, series_name: str = None):
|
||||||
"""
|
"""
|
||||||
Initialize the GetSerieInfo class for scraping TV series information.
|
Initialize the GetSerieInfo class for scraping TV series information.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
- url (str): The URL of the streaming site.
|
- url (str): The URL of the streaming site.
|
||||||
|
- media_id (int, optional): Unique identifier for the media
|
||||||
|
- series_name (str, optional): Name of the TV series
|
||||||
"""
|
"""
|
||||||
self.is_series = False
|
self.is_series = False
|
||||||
self.headers = {'user-agent': get_userAgent()}
|
self.headers = {'user-agent': get_userAgent()}
|
||||||
self.url = url
|
self.url = url
|
||||||
|
self.media_id = media_id
|
||||||
# Initialize the SeasonManager
|
|
||||||
self.seasons_manager = SeasonManager()
|
self.seasons_manager = SeasonManager()
|
||||||
|
|
||||||
def setup(self, media_id: int = None, series_name: str = None):
|
|
||||||
"""
|
|
||||||
Set up the scraper with specific media details.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
media_id (int, optional): Unique identifier for the media
|
|
||||||
series_name (str, optional): Name of the TV series
|
|
||||||
"""
|
|
||||||
self.media_id = media_id
|
|
||||||
|
|
||||||
# If series name is provided, initialize series-specific properties
|
|
||||||
if series_name is not None:
|
if series_name is not None:
|
||||||
self.is_series = True
|
self.is_series = True
|
||||||
self.series_name = series_name
|
self.series_name = series_name
|
||||||
@ -127,4 +117,40 @@ class GetSerieInfo:
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error collecting episodes for season {number_season}: {e}")
|
logging.error(f"Error collecting episodes for season {number_season}: {e}")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
# ------------- FOR GUI -------------
|
||||||
|
def getNumberSeason(self) -> int:
|
||||||
|
"""
|
||||||
|
Get the total number of seasons available for the series.
|
||||||
|
"""
|
||||||
|
if not self.seasons_manager.seasons:
|
||||||
|
self.collect_info_title()
|
||||||
|
|
||||||
|
return len(self.seasons_manager.seasons)
|
||||||
|
|
||||||
|
def getEpisodeSeasons(self, season_number: int) -> list:
|
||||||
|
"""
|
||||||
|
Get all episodes for a specific season.
|
||||||
|
"""
|
||||||
|
season = self.seasons_manager.get_season_by_number(season_number)
|
||||||
|
|
||||||
|
if not season:
|
||||||
|
logging.error(f"Season {season_number} not found")
|
||||||
|
return []
|
||||||
|
|
||||||
|
if not season.episodes.episodes:
|
||||||
|
self.collect_info_season(season_number)
|
||||||
|
|
||||||
|
return season.episodes.episodes
|
||||||
|
|
||||||
|
def selectEpisode(self, season_number: int, episode_index: int) -> dict:
|
||||||
|
"""
|
||||||
|
Get information for a specific episode in a specific season.
|
||||||
|
"""
|
||||||
|
episodes = self.getEpisodeSeasons(season_number)
|
||||||
|
if not episodes or episode_index < 0 or episode_index >= len(episodes):
|
||||||
|
logging.error(f"Episode index {episode_index} is out of range for season {season_number}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
return episodes[episode_index]
|
@ -10,7 +10,7 @@ from rich.console import Console
|
|||||||
# Variable
|
# Variable
|
||||||
console = Console()
|
console = Console()
|
||||||
available_colors = ['red', 'magenta', 'yellow', 'cyan', 'green', 'blue', 'white']
|
available_colors = ['red', 'magenta', 'yellow', 'cyan', 'green', 'blue', 'white']
|
||||||
column_to_hide = ['Slug', 'Sub_ita', 'Last_air_date', 'Seasons_count', 'Url']
|
column_to_hide = ['Slug', 'Sub_ita', 'Last_air_date', 'Seasons_count', 'Url', 'Image', 'Path_id']
|
||||||
|
|
||||||
|
|
||||||
def get_select_title(table_show_manager, media_search_manager):
|
def get_select_title(table_show_manager, media_search_manager):
|
||||||
@ -81,4 +81,4 @@ def get_select_title(table_show_manager, media_search_manager):
|
|||||||
|
|
||||||
else:
|
else:
|
||||||
console.print("\n[red]Wrong index")
|
console.print("\n[red]Wrong index")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
@ -514,7 +514,7 @@ class HLS_Downloader:
|
|||||||
for item in self.download_manager.missing_segments:
|
for item in self.download_manager.missing_segments:
|
||||||
if int(item['nFailed']) >= 1:
|
if int(item['nFailed']) >= 1:
|
||||||
missing_ts = True
|
missing_ts = True
|
||||||
missing_info += f"[red]TS Failed: {item['nFailed']} {item['type']} tracks[/red]\n"
|
missing_info += f"[red]TS Failed: {item['nFailed']} {item['type']} tracks[/red]"
|
||||||
|
|
||||||
file_size = internet_manager.format_file_size(os.path.getsize(self.path_manager.output_path))
|
file_size = internet_manager.format_file_size(os.path.getsize(self.path_manager.output_path))
|
||||||
duration = print_duration_table(self.path_manager.output_path, description=False, return_string=True)
|
duration = print_duration_table(self.path_manager.output_path, description=False, return_string=True)
|
||||||
|
@ -41,10 +41,9 @@ REQUEST_VERIFY = config_manager.get_bool('REQUESTS', 'verify')
|
|||||||
DEFAULT_VIDEO_WORKERS = config_manager.get_int('M3U8_DOWNLOAD', 'default_video_workser')
|
DEFAULT_VIDEO_WORKERS = config_manager.get_int('M3U8_DOWNLOAD', 'default_video_workser')
|
||||||
DEFAULT_AUDIO_WORKERS = config_manager.get_int('M3U8_DOWNLOAD', 'default_audio_workser')
|
DEFAULT_AUDIO_WORKERS = config_manager.get_int('M3U8_DOWNLOAD', 'default_audio_workser')
|
||||||
MAX_TIMEOOUT = config_manager.get_int("REQUESTS", "timeout")
|
MAX_TIMEOOUT = config_manager.get_int("REQUESTS", "timeout")
|
||||||
MAX_INTERRUPT_COUNT = 3
|
|
||||||
SEGMENT_MAX_TIMEOUT = config_manager.get_int("M3U8_DOWNLOAD", "segment_timeout")
|
SEGMENT_MAX_TIMEOUT = config_manager.get_int("M3U8_DOWNLOAD", "segment_timeout")
|
||||||
TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot')
|
TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot')
|
||||||
|
MAX_INTERRUPT_COUNT = 3
|
||||||
|
|
||||||
# Variable
|
# Variable
|
||||||
console = Console()
|
console = Console()
|
||||||
@ -160,7 +159,7 @@ class M3U8_Segments:
|
|||||||
if self.is_index_url:
|
if self.is_index_url:
|
||||||
try:
|
try:
|
||||||
client_params = {'headers': {'User-Agent': get_userAgent()}, 'timeout': MAX_TIMEOOUT}
|
client_params = {'headers': {'User-Agent': get_userAgent()}, 'timeout': MAX_TIMEOOUT}
|
||||||
response = httpx.get(self.url, **client_params)
|
response = httpx.get(self.url, **client_params, follow_redirects=True)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
||||||
self.parse_data(response.text)
|
self.parse_data(response.text)
|
||||||
|
@ -138,26 +138,53 @@ def get_ffprobe_info(file_path):
|
|||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict: A dictionary containing the format name and a list of codec names.
|
dict: A dictionary containing the format name and a list of codec names.
|
||||||
|
Returns None if file does not exist or ffprobe crashes.
|
||||||
"""
|
"""
|
||||||
|
if not os.path.exists(file_path):
|
||||||
|
logging.error(f"File not found: {file_path}")
|
||||||
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = subprocess.run(
|
# Use subprocess.Popen instead of run to better handle crashes
|
||||||
[get_ffprobe_path(), '-v', 'error', '-show_format', '-show_streams', '-print_format', 'json', file_path],
|
cmd = [get_ffprobe_path(), '-v', 'error', '-show_format', '-show_streams', '-print_format', 'json', file_path]
|
||||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=True
|
logging.info(f"FFmpeg command: {cmd}")
|
||||||
)
|
|
||||||
output = result.stdout
|
|
||||||
info = json.loads(output)
|
|
||||||
|
|
||||||
format_name = info['format']['format_name'] if 'format' in info else None
|
with subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) as proc:
|
||||||
codec_names = [stream['codec_name'] for stream in info['streams']] if 'streams' in info else []
|
stdout, stderr = proc.communicate()
|
||||||
|
|
||||||
return {
|
if proc.returncode != 0:
|
||||||
'format_name': format_name,
|
logging.error(f"FFprobe failed with return code {proc.returncode} for file {file_path}")
|
||||||
'codec_names': codec_names
|
if stderr:
|
||||||
}
|
logging.error(f"FFprobe stderr: {stderr}")
|
||||||
|
return {
|
||||||
|
'format_name': None,
|
||||||
|
'codec_names': []
|
||||||
|
}
|
||||||
|
|
||||||
|
# Make sure we have valid JSON before parsing
|
||||||
|
if not stdout or not stdout.strip():
|
||||||
|
logging.warning(f"FFprobe returned empty output for file {file_path}")
|
||||||
|
return {
|
||||||
|
'format_name': None,
|
||||||
|
'codec_names': []
|
||||||
|
}
|
||||||
|
|
||||||
|
info = json.loads(stdout)
|
||||||
|
|
||||||
|
format_name = info['format']['format_name'] if 'format' in info else None
|
||||||
|
codec_names = [stream['codec_name'] for stream in info['streams']] if 'streams' in info else []
|
||||||
|
|
||||||
|
return {
|
||||||
|
'format_name': format_name,
|
||||||
|
'codec_names': codec_names
|
||||||
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Failed to parse JSON output from ffprobe for file {file_path}: {e}")
|
logging.error(f"Failed to get ffprobe info for file {file_path}: {e}")
|
||||||
return None
|
return {
|
||||||
|
'format_name': None,
|
||||||
|
'codec_names': []
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def is_png_format_or_codec(file_info):
|
def is_png_format_or_codec(file_info):
|
||||||
@ -173,8 +200,11 @@ def is_png_format_or_codec(file_info):
|
|||||||
if not file_info:
|
if not file_info:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
#console.print(f"[yellow][FFmpeg] [cyan]Avaiable codec[white]: [red]{file_info['codec_names']}")
|
# Handle None values in format_name gracefully
|
||||||
return file_info['format_name'] == 'png_pipe' or 'png' in file_info['codec_names']
|
format_name = file_info.get('format_name')
|
||||||
|
codec_names = file_info.get('codec_names', [])
|
||||||
|
|
||||||
|
return format_name == 'png_pipe' or 'png' in codec_names
|
||||||
|
|
||||||
|
|
||||||
def need_to_force_to_ts(file_path):
|
def need_to_force_to_ts(file_path):
|
||||||
|
@ -31,16 +31,20 @@ class M3U8_Ts_Estimator:
|
|||||||
self.segments_instance = segments_instance
|
self.segments_instance = segments_instance
|
||||||
self.lock = threading.Lock()
|
self.lock = threading.Lock()
|
||||||
self.speed = {"upload": "N/A", "download": "N/A"}
|
self.speed = {"upload": "N/A", "download": "N/A"}
|
||||||
|
self._running = True
|
||||||
|
|
||||||
if get_use_large_bar():
|
if get_use_large_bar():
|
||||||
logging.debug("USE_LARGE_BAR is True, starting speed capture thread")
|
logging.debug("USE_LARGE_BAR is True, starting speed capture thread")
|
||||||
self.speed_thread = threading.Thread(target=self.capture_speed)
|
self.speed_thread = threading.Thread(target=self.capture_speed)
|
||||||
self.speed_thread.daemon = True
|
self.speed_thread.daemon = True
|
||||||
self.speed_thread.start()
|
self.speed_thread.start()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
logging.debug("USE_LARGE_BAR is False, speed capture thread not started")
|
logging.debug("USE_LARGE_BAR is False, speed capture thread not started")
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
"""Ensure thread is properly stopped when the object is destroyed."""
|
||||||
|
self._running = False
|
||||||
|
|
||||||
def add_ts_file(self, size: int):
|
def add_ts_file(self, size: int):
|
||||||
"""Add a file size to the list of file sizes."""
|
"""Add a file size to the list of file sizes."""
|
||||||
if size <= 0:
|
if size <= 0:
|
||||||
@ -50,32 +54,44 @@ class M3U8_Ts_Estimator:
|
|||||||
self.ts_file_sizes.append(size)
|
self.ts_file_sizes.append(size)
|
||||||
|
|
||||||
def capture_speed(self, interval: float = 1.5):
|
def capture_speed(self, interval: float = 1.5):
|
||||||
"""Capture the internet speed periodically."""
|
"""Capture the internet speed periodically with improved efficiency."""
|
||||||
last_upload, last_download = 0, 0
|
last_upload, last_download = 0, 0
|
||||||
speed_buffer = deque(maxlen=3)
|
speed_buffer = deque(maxlen=3)
|
||||||
|
|
||||||
while True:
|
while self._running:
|
||||||
try:
|
try:
|
||||||
|
# Get IO counters only once per loop to reduce function calls
|
||||||
io_counters = psutil.net_io_counters()
|
io_counters = psutil.net_io_counters()
|
||||||
if not io_counters:
|
if not io_counters:
|
||||||
raise ValueError("No IO counters available")
|
raise ValueError("No IO counters available")
|
||||||
|
|
||||||
current_upload, current_download = io_counters.bytes_sent, io_counters.bytes_recv
|
current_upload, current_download = io_counters.bytes_sent, io_counters.bytes_recv
|
||||||
|
|
||||||
if last_upload and last_download:
|
if last_upload and last_download:
|
||||||
upload_speed = (current_upload - last_upload) / interval
|
upload_speed = (current_upload - last_upload) / interval
|
||||||
download_speed = (current_download - last_download) / interval
|
download_speed = (current_download - last_download) / interval
|
||||||
speed_buffer.append(max(0, download_speed))
|
|
||||||
|
|
||||||
|
# Only update buffer when we have valid data
|
||||||
|
if download_speed > 0:
|
||||||
|
speed_buffer.append(download_speed)
|
||||||
|
|
||||||
|
# Use a more efficient approach for thread synchronization
|
||||||
|
avg_speed = sum(speed_buffer) / len(speed_buffer) if speed_buffer else 0
|
||||||
|
formatted_upload = internet_manager.format_transfer_speed(max(0, upload_speed))
|
||||||
|
formatted_download = internet_manager.format_transfer_speed(avg_speed)
|
||||||
|
|
||||||
|
# Minimize lock time by preparing data outside the lock
|
||||||
with self.lock:
|
with self.lock:
|
||||||
self.speed = {
|
self.speed = {
|
||||||
"upload": internet_manager.format_transfer_speed(max(0, upload_speed)),
|
"upload": formatted_upload,
|
||||||
"download": internet_manager.format_transfer_speed(sum(speed_buffer) / len(speed_buffer))
|
"download": formatted_download
|
||||||
}
|
}
|
||||||
logging.debug(f"Updated speeds - Upload: {self.speed['upload']}, Download: {self.speed['download']}")
|
|
||||||
|
|
||||||
last_upload, last_download = current_upload, current_download
|
last_upload, last_download = current_upload, current_download
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error in speed capture: {str(e)}")
|
if self._running: # Only log if we're still supposed to be running
|
||||||
|
logging.error(f"Error in speed capture: {str(e)}")
|
||||||
self.speed = {"upload": "N/A", "download": "N/A"}
|
self.speed = {"upload": "N/A", "download": "N/A"}
|
||||||
|
|
||||||
time.sleep(interval)
|
time.sleep(interval)
|
||||||
@ -88,6 +104,10 @@ class M3U8_Ts_Estimator:
|
|||||||
str: The mean size of the files in a human-readable format.
|
str: The mean size of the files in a human-readable format.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
|
# Only do calculations if we have data
|
||||||
|
if not self.ts_file_sizes:
|
||||||
|
return "0 B"
|
||||||
|
|
||||||
total_size = sum(self.ts_file_sizes)
|
total_size = sum(self.ts_file_sizes)
|
||||||
mean_size = total_size / len(self.ts_file_sizes)
|
mean_size = total_size / len(self.ts_file_sizes)
|
||||||
return internet_manager.format_file_size(mean_size)
|
return internet_manager.format_file_size(mean_size)
|
||||||
@ -101,31 +121,40 @@ class M3U8_Ts_Estimator:
|
|||||||
self.add_ts_file(total_downloaded * self.total_segments)
|
self.add_ts_file(total_downloaded * self.total_segments)
|
||||||
|
|
||||||
file_total_size = self.calculate_total_size()
|
file_total_size = self.calculate_total_size()
|
||||||
|
if file_total_size == "Error":
|
||||||
|
return
|
||||||
|
|
||||||
number_file_total_size = file_total_size.split(' ')[0]
|
number_file_total_size = file_total_size.split(' ')[0]
|
||||||
units_file_total_size = file_total_size.split(' ')[1]
|
units_file_total_size = file_total_size.split(' ')[1]
|
||||||
|
|
||||||
|
# Reduce lock contention by acquiring data with minimal synchronization
|
||||||
|
retry_count = 0
|
||||||
|
if self.segments_instance:
|
||||||
|
with self.segments_instance.active_retries_lock:
|
||||||
|
retry_count = self.segments_instance.active_retries
|
||||||
|
|
||||||
if get_use_large_bar():
|
if get_use_large_bar():
|
||||||
speed_data = self.speed['download'].split(" ")
|
# Get speed data outside of any locks
|
||||||
|
speed_data = ["N/A", ""]
|
||||||
|
with self.lock:
|
||||||
|
download_speed = self.speed['download']
|
||||||
|
|
||||||
if len(speed_data) >= 2:
|
if download_speed != "N/A":
|
||||||
average_internet_speed = speed_data[0]
|
speed_data = download_speed.split(" ")
|
||||||
average_internet_unit = speed_data[1]
|
|
||||||
else:
|
average_internet_speed = speed_data[0] if len(speed_data) >= 1 else "N/A"
|
||||||
average_internet_speed = "N/A"
|
average_internet_unit = speed_data[1] if len(speed_data) >= 2 else ""
|
||||||
average_internet_unit = ""
|
|
||||||
|
|
||||||
retry_count = self.segments_instance.active_retries if self.segments_instance else 0
|
|
||||||
progress_str = (
|
progress_str = (
|
||||||
f"{Colors.GREEN}{number_file_total_size} {Colors.RED}{units_file_total_size}"
|
f"{Colors.GREEN}{number_file_total_size} {Colors.RED}{units_file_total_size}"
|
||||||
f"{Colors.WHITE}, {Colors.CYAN}{average_internet_speed} {Colors.RED}{average_internet_unit}"
|
f"{Colors.WHITE}, {Colors.CYAN}{average_internet_speed} {Colors.RED}{average_internet_unit} "
|
||||||
f"{Colors.WHITE}, {Colors.GREEN}CRR {Colors.RED}{retry_count} "
|
#f"{Colors.WHITE}, {Colors.GREEN}CRR {Colors.RED}{retry_count} "
|
||||||
)
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
retry_count = self.segments_instance.active_retries if self.segments_instance else 0
|
|
||||||
progress_str = (
|
progress_str = (
|
||||||
f"{Colors.GREEN}{number_file_total_size} {Colors.RED}{units_file_total_size}"
|
f"{Colors.GREEN}{number_file_total_size} {Colors.RED}{units_file_total_size} "
|
||||||
f"{Colors.WHITE}, {Colors.GREEN}CRR {Colors.RED}{retry_count} "
|
#f"{Colors.WHITE}, {Colors.GREEN}CRR {Colors.RED}{retry_count} "
|
||||||
)
|
)
|
||||||
|
|
||||||
progress_counter.set_postfix_str(progress_str)
|
progress_counter.set_postfix_str(progress_str)
|
||||||
|
@ -158,7 +158,8 @@ class TVShowManager:
|
|||||||
else:
|
else:
|
||||||
key = Prompt.ask(prompt_msg)
|
key = Prompt.ask(prompt_msg)
|
||||||
else:
|
else:
|
||||||
choices = [str(i) for i in range(max_int_input + 1)] + ["q", "quit", "b", "back"]
|
# Include empty string in choices to allow pagination with Enter key
|
||||||
|
choices = [""] + [str(i) for i in range(max_int_input + 1)] + ["q", "quit", "b", "back"]
|
||||||
prompt_msg = "[cyan]Insert media [red]index"
|
prompt_msg = "[cyan]Insert media [red]index"
|
||||||
telegram_msg = "Scegli il contenuto da scaricare:\n Serie TV - Film - Anime\noppure `back` per tornare indietro"
|
telegram_msg = "Scegli il contenuto da scaricare:\n Serie TV - Film - Anime\noppure `back` per tornare indietro"
|
||||||
|
|
||||||
@ -199,7 +200,8 @@ class TVShowManager:
|
|||||||
else:
|
else:
|
||||||
key = Prompt.ask(prompt_msg)
|
key = Prompt.ask(prompt_msg)
|
||||||
else:
|
else:
|
||||||
choices = [str(i) for i in range(max_int_input + 1)] + ["q", "quit", "b", "back"]
|
# Include empty string in choices to allow pagination with Enter key
|
||||||
|
choices = [""] + [str(i) for i in range(max_int_input + 1)] + ["q", "quit", "b", "back"]
|
||||||
prompt_msg = "[cyan]Insert media [red]index"
|
prompt_msg = "[cyan]Insert media [red]index"
|
||||||
telegram_msg = "Scegli il contenuto da scaricare:\n Serie TV - Film - Anime\noppure `back` per tornare indietro"
|
telegram_msg = "Scegli il contenuto da scaricare:\n Serie TV - Film - Anime\noppure `back` per tornare indietro"
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user