mirror of
https://github.com/Arrowar/StreamingCommunity.git
synced 2025-06-03 10:00:10 +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:
|
||||
def __init__(self, cookie) -> None:
|
||||
def __init__(self, url, cookie) -> None:
|
||||
"""
|
||||
Initializes the VideoSource object with default values.
|
||||
"""
|
||||
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.cookie = cookie
|
||||
|
||||
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")
|
||||
|
||||
|
||||
class AnimeWorldPlayer:
|
||||
class VideoSource:
|
||||
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.csrf_token = csrf_token
|
||||
self.episode_data = episode_data
|
||||
@ -33,7 +33,7 @@ class AnimeWorldPlayer:
|
||||
timeout=MAX_TIMEOUT
|
||||
)
|
||||
|
||||
def get_download_link(self):
|
||||
def get_playlist(self):
|
||||
"""Fetch the download link from AnimeWorld using the episode link."""
|
||||
try:
|
||||
# Make a POST request to the episode link and follow any redirects
|
||||
|
@ -24,26 +24,20 @@ console = Console()
|
||||
|
||||
|
||||
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.
|
||||
|
||||
Args:
|
||||
- url (str): The URL of the streaming site.
|
||||
- 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.url = url
|
||||
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.iframe_src = None
|
||||
|
||||
def get_iframe(self, episode_id: int) -> None:
|
||||
"""
|
||||
@ -164,6 +158,7 @@ class VideoSourceAnime(VideoSource):
|
||||
self.headers = {'user-agent': get_userAgent()}
|
||||
self.url = url
|
||||
self.src_mp4 = None
|
||||
self.iframe_src = None
|
||||
|
||||
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):
|
||||
"""
|
||||
Main function of the application for search film, series and anime.
|
||||
Main function of the application for search.
|
||||
|
||||
Parameters:
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
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':
|
||||
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:
|
||||
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:
|
||||
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)
|
||||
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)
|
||||
process_search_result(select_title, selections)
|
||||
return
|
||||
|
||||
# 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:
|
||||
select_title = get_select_title(table_show_manager, media_search_manager)
|
||||
process_search_result(select_title)
|
||||
process_search_result(select_title, selections)
|
||||
|
||||
else:
|
||||
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
|
||||
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:
|
||||
- str: output path if successful, otherwise None
|
||||
"""
|
||||
|
||||
if site_constant.TELEGRAM_BOT:
|
||||
bot = get_bot_instance()
|
||||
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 StreamingCommunity.Api.Template.Util import (
|
||||
manage_selection,
|
||||
map_episode_title,
|
||||
dynamic_format_number,
|
||||
map_episode_title,
|
||||
validate_selection,
|
||||
validate_episode_selection,
|
||||
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]:
|
||||
"""
|
||||
Download a single episode video.
|
||||
Downloads a specific episode from a specified season.
|
||||
|
||||
Parameters:
|
||||
- index_season_selected (int): Index of the selected season.
|
||||
- index_episode_selected (int): Index of the selected episode.
|
||||
- index_season_selected (int): Season number
|
||||
- index_episode_selected (int): Episode index
|
||||
- scrape_serie (GetSerieInfo): Scraper object with series information
|
||||
|
||||
Return:
|
||||
- str: output path
|
||||
- bool: kill handler status
|
||||
Returns:
|
||||
- str: Path to downloaded file
|
||||
- bool: Whether download was stopped
|
||||
"""
|
||||
start_message()
|
||||
index_season_selected = dynamic_format_number(str(index_season_selected))
|
||||
|
||||
# Get info about episode
|
||||
obj_episode = scrape_serie.seasons_manager.get_season_by_number(int(index_season_selected)).episodes.get(index_episode_selected-1)
|
||||
# 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")
|
||||
|
||||
# Telegram integration
|
||||
if site_constant.TELEGRAM_BOT:
|
||||
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']
|
||||
|
||||
|
||||
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:
|
||||
- index_season_selected (int): Index of the selected season.
|
||||
- download_all (bool): Download all episodes in the season.
|
||||
- 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
|
||||
"""
|
||||
start_message()
|
||||
obj_episodes = scrape_serie.seasons_manager.get_season_by_number(index_season_selected).episodes
|
||||
episodes_count = len(obj_episodes.episodes)
|
||||
# Get episodes for the selected season
|
||||
episodes = scrape_serie.getEpisodeSeasons(index_season_selected)
|
||||
episodes_count = len(episodes)
|
||||
|
||||
if download_all:
|
||||
|
||||
# Download all episodes without asking
|
||||
for i_episode in range(1, episodes_count + 1):
|
||||
path, stopped = download_video(index_season_selected, i_episode, scrape_serie)
|
||||
|
||||
@ -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}.")
|
||||
|
||||
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
|
||||
last_command = display_episodes_list(obj_episodes.episodes)
|
||||
else:
|
||||
last_command = display_episodes_list(episodes)
|
||||
|
||||
# Prompt user for episode selection
|
||||
list_episode_select = manage_selection(last_command, episodes_count)
|
||||
|
||||
try:
|
||||
list_episode_select = validate_episode_selection(list_episode_select, episodes_count)
|
||||
except ValueError as e:
|
||||
console.print(f"[red]{str(e)}")
|
||||
return
|
||||
list_episode_select = validate_episode_selection(list_episode_select, episodes_count)
|
||||
|
||||
# Download selected episodes if not stopped
|
||||
for i_episode in list_episode_select:
|
||||
@ -135,69 +135,65 @@ def download_episode(index_season_selected: int, scrape_serie: GetSerieInfo, dow
|
||||
if stopped:
|
||||
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:
|
||||
- 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)
|
||||
|
||||
# Collect information about seasons
|
||||
scrape_serie.collect_season()
|
||||
seasons_count = len(scrape_serie.seasons_manager)
|
||||
# Get total number of seasons
|
||||
seasons_count = scrape_serie.getNumberSeason()
|
||||
|
||||
if site_constant.TELEGRAM_BOT:
|
||||
bot = get_bot_instance()
|
||||
|
||||
# Prompt user for season selection and download episodes
|
||||
console.print(f"\n[green]Seasons found: [red]{seasons_count}")
|
||||
|
||||
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")
|
||||
# If season_selection is provided, use it instead of asking for input
|
||||
if season_selection is None:
|
||||
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(
|
||||
"select_title_episode",
|
||||
"Menu di selezione delle stagioni\n\n"
|
||||
"- Inserisci il numero della stagione (ad esempio, 1)\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 (ad esempio, 3-*) per scaricare dalla stagione specificata fino alla fine della serie",
|
||||
None
|
||||
)
|
||||
index_season_selected = bot.ask(
|
||||
"select_title_episode",
|
||||
"Menu di selezione delle stagioni\n\n"
|
||||
"- Inserisci il numero della stagione (ad esempio, 1)\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 (ad esempio, 3-*) per scaricare dalla stagione specificata fino alla fine della serie",
|
||||
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:
|
||||
index_season_selected = msg.ask(
|
||||
"\n[cyan]Insert season number [yellow](e.g., 1), [red]* [cyan]to download all seasons, "
|
||||
"[yellow](e.g., 1-2) [cyan]for a range of seasons, or [yellow](e.g., 3-*) [cyan]to download from a specific season to the end"
|
||||
)
|
||||
index_season_selected = season_selection
|
||||
console.print(f"\n[cyan]Using provided season selection: [yellow]{season_selection}")
|
||||
|
||||
# Manage and validate the selection
|
||||
# Validate the selection
|
||||
list_season_select = manage_selection(index_season_selected, seasons_count)
|
||||
|
||||
try:
|
||||
list_season_select = validate_selection(list_season_select, seasons_count)
|
||||
except ValueError as e:
|
||||
console.print(f"[red]{str(e)}")
|
||||
return
|
||||
list_season_select = validate_selection(list_season_select, seasons_count)
|
||||
|
||||
# Loop through the selected seasons and download episodes
|
||||
for i_season in list_season_select:
|
||||
if len(list_season_select) > 1 or index_season_selected == "*":
|
||||
|
||||
# Download all episodes if multiple seasons are selected or if '*' is used
|
||||
download_episode(i_season, scrape_serie, download_all=True)
|
||||
else:
|
||||
|
||||
# Otherwise, let the user select specific episodes for the single season
|
||||
download_episode(i_season, scrape_serie, download_all=False)
|
||||
download_episode(i_season, scrape_serie, download_all=False, episode_selection=episode_selection)
|
||||
|
||||
if site_constant.TELEGRAM_BOT:
|
||||
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
|
||||
script_id = TelegramSession.get_session()
|
||||
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({
|
||||
'url': url,
|
||||
'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:
|
||||
|
@ -1,5 +1,8 @@
|
||||
# 16.03.25
|
||||
|
||||
import logging
|
||||
|
||||
|
||||
# External libraries
|
||||
import httpx
|
||||
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")
|
||||
|
||||
|
||||
|
||||
class GetSerieInfo:
|
||||
def __init__(self, url):
|
||||
"""
|
||||
@ -69,4 +71,37 @@ class GetSerieInfo:
|
||||
'number': ep_idx,
|
||||
'name': episode_name,
|
||||
'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
|
||||
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
|
||||
@ -56,24 +57,42 @@ def get_user_input(string_to_search: str = None):
|
||||
|
||||
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.
|
||||
|
||||
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:
|
||||
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)
|
||||
process_search_result(select_title, selections)
|
||||
return
|
||||
|
||||
# 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
|
||||
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:
|
||||
return media_search_manager
|
||||
|
||||
@ -91,8 +110,8 @@ def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_
|
||||
|
||||
if len_database > 0:
|
||||
select_title = get_select_title(table_show_manager, media_search_manager)
|
||||
process_search_result(select_title)
|
||||
|
||||
process_search_result(select_title, selections)
|
||||
|
||||
else:
|
||||
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
|
||||
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
|
||||
|
||||
import sys
|
||||
import logging
|
||||
|
||||
|
||||
@ -140,7 +139,7 @@ def title_search(query: str) -> int:
|
||||
'type': dict_title.get('type'),
|
||||
'status': dict_title.get('status'),
|
||||
'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:
|
||||
|
@ -94,3 +94,18 @@ class ScrapeSerieAnime:
|
||||
except Exception as e:
|
||||
logging.error(f"Error fetching episode information: {e}")
|
||||
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
|
||||
from .site import title_search, media_search_manager, table_show_manager
|
||||
from .serie import download_series
|
||||
from .film import download_film
|
||||
|
||||
|
||||
# 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.
|
||||
|
||||
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":
|
||||
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:
|
||||
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)
|
||||
process_search_result(select_title, selections)
|
||||
return
|
||||
|
||||
# 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
|
||||
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:
|
||||
return media_search_manager
|
||||
|
||||
if len_database > 0:
|
||||
select_title = get_select_title(table_show_manager, media_search_manager)
|
||||
process_search_result(select_title)
|
||||
|
||||
process_search_result(select_title, selections)
|
||||
|
||||
else:
|
||||
console.print(f"\n[red]Nothing matching was found for[white]: [purple]{string_to_search}")
|
||||
|
||||
# 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()
|
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
|
||||
from .util.ScrapeSerie import ScrapSerie
|
||||
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
|
||||
|
||||
|
||||
# Player
|
||||
from StreamingCommunity.Api.Player.sweetpixel import AnimeWorldPlayer
|
||||
from StreamingCommunity.Api.Player.sweetpixel import VideoSource
|
||||
|
||||
|
||||
# Variable
|
||||
@ -33,8 +33,7 @@ msg = Prompt()
|
||||
KILL_HANDLER = bool(False)
|
||||
|
||||
|
||||
|
||||
def download_episode(index_select: int, scrape_serie: ScrapSerie, episodes) -> Tuple[str,bool]:
|
||||
def download_episode(index_select: int, scrape_serie: ScrapSerie) -> Tuple[str,bool]:
|
||||
"""
|
||||
Downloads the selected episode.
|
||||
|
||||
@ -47,7 +46,8 @@ def download_episode(index_select: int, scrape_serie: ScrapSerie, episodes) -> T
|
||||
"""
|
||||
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")
|
||||
|
||||
# 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
|
||||
os_manager.create_path(mp4_path)
|
||||
|
||||
# Collect mp4 link
|
||||
video_source = AnimeWorldPlayer(site_constant.FULL_URL, episodes[index_select], scrape_serie.session_id, scrape_serie.csrf_token)
|
||||
mp4_link = video_source.get_download_link()
|
||||
# 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(
|
||||
@ -70,38 +70,41 @@ def download_episode(index_select: int, scrape_serie: ScrapSerie, episodes) -> T
|
||||
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.
|
||||
|
||||
Parameters:
|
||||
- tv_id (int): The ID of the TV series.
|
||||
- tv_name (str): The name of the TV series.
|
||||
- select_title (MediaItem): The selected media item
|
||||
- episode_selection (str, optional): Episode selection input that bypasses manual input
|
||||
"""
|
||||
start_message()
|
||||
|
||||
# Create scrap instance
|
||||
scrape_serie = ScrapSerie(select_title.url, site_constant.FULL_URL)
|
||||
episodes = scrape_serie.get_episodes()
|
||||
|
||||
# Get the count of episodes for the TV series
|
||||
episodes = scrape_serie.get_episodes()
|
||||
episoded_count = len(episodes)
|
||||
console.print(f"[cyan]Episodes find: [red]{episoded_count}")
|
||||
# Get episode count
|
||||
console.print(f"[green]Episodes found:[/green] [red]{len(episodes)}[/red]")
|
||||
|
||||
# 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")
|
||||
# Display episodes list and get user selection
|
||||
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, episoded_count)
|
||||
list_episode_select = manage_selection(last_command, len(episodes))
|
||||
|
||||
# Download selected episodes
|
||||
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
|
||||
|
||||
# Download all other episodes selecter
|
||||
# Download all selected episodes
|
||||
else:
|
||||
kill_handler = False
|
||||
for i_episode in list_episode_select:
|
||||
if kill_handler:
|
||||
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,
|
||||
'type': anime_type,
|
||||
'DUB': is_dubbed,
|
||||
'url': url
|
||||
'url': url,
|
||||
'image': element.find('img').get('src')
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
|
@ -1,5 +1,6 @@
|
||||
# 21.03.25
|
||||
|
||||
import logging
|
||||
|
||||
# External libraries
|
||||
import httpx
|
||||
@ -14,7 +15,7 @@ from StreamingCommunity.Util.os import os_manager
|
||||
|
||||
# Player
|
||||
from ..site import get_session_and_csrf
|
||||
from StreamingCommunity.Api.Player.sweetpixel import AnimeWorldPlayer
|
||||
from StreamingCommunity.Api.Player.sweetpixel import VideoSource
|
||||
|
||||
|
||||
# Variable
|
||||
@ -40,7 +41,6 @@ class ScrapSerie:
|
||||
except:
|
||||
raise Exception(f"Failed to retrieve anime page.")
|
||||
|
||||
|
||||
def get_name(self):
|
||||
"""Extract and return the name of the anime series."""
|
||||
soup = BeautifulSoup(self.response.content, "html.parser")
|
||||
@ -68,12 +68,39 @@ class ScrapSerie:
|
||||
return episodes
|
||||
|
||||
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()
|
||||
|
||||
if 0 <= index < len(episodes):
|
||||
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:
|
||||
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):
|
||||
"""
|
||||
Main function of the application for search film, series and anime.
|
||||
Main function of the application for search.
|
||||
|
||||
Parameters:
|
||||
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):
|
||||
"""
|
||||
Main function of the application for search film, series and anime.
|
||||
Main function of the application for search.
|
||||
|
||||
Parameters:
|
||||
string_to_search (str, optional): String to search for
|
||||
|
@ -35,22 +35,22 @@ from StreamingCommunity.Api.Player.ddl import VideoSource
|
||||
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:
|
||||
- tv_name (str): Name of the TV series.
|
||||
- index_episode_selected (int): Index of the selected episode.
|
||||
- index_episode_selected (int): Episode index
|
||||
- scape_info_serie (GetSerieInfo): Scraper object with series information
|
||||
|
||||
Return:
|
||||
- str: output path
|
||||
- bool: kill handler status
|
||||
Returns:
|
||||
- str: Path to downloaded file
|
||||
- bool: Whether download was stopped
|
||||
"""
|
||||
start_message()
|
||||
|
||||
# Get info about episode
|
||||
obj_episode = scape_info_serie.list_episodes[index_episode_selected - 1]
|
||||
# Get episode information
|
||||
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")
|
||||
|
||||
# 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)
|
||||
|
||||
# Setup video source
|
||||
video_source.setup(obj_episode.get('url'))
|
||||
video_source = VideoSource(site_constant.COOKIE, obj_episode.get('url'))
|
||||
|
||||
# Get m3u8 master 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(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
|
||||
|
||||
Parameters:
|
||||
dict_serie (MediaItem): The selected media item
|
||||
episode_selection (str, optional): Episode selection input that bypasses manual input
|
||||
"""
|
||||
start_message()
|
||||
|
||||
# Init class
|
||||
scape_info_serie = GetSerieInfo(dict_serie, site_constant.COOKIE)
|
||||
video_source = VideoSource(site_constant.COOKIE)
|
||||
|
||||
# Collect information about thread
|
||||
list_dict_episode = scape_info_serie.get_episode_number()
|
||||
episodes_count = len(list_dict_episode)
|
||||
|
||||
scrape_serie = GetSerieInfo(dict_serie, site_constant.COOKIE)
|
||||
|
||||
# Get episode list
|
||||
episodes = scrape_serie.getEpisodeSeasons()
|
||||
episodes_count = len(episodes)
|
||||
|
||||
# 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)
|
||||
|
||||
try:
|
||||
list_episode_select = validate_episode_selection(list_episode_select, episodes_count)
|
||||
|
||||
except ValueError as e:
|
||||
console.print(f"[red]{str(e)}")
|
||||
return
|
||||
list_episode_select = validate_episode_selection(list_episode_select, episodes_count)
|
||||
|
||||
# Download selected episodes
|
||||
kill_handler = bool(False)
|
||||
for i_episode in list_episode_select:
|
||||
if kill_handler:
|
||||
break
|
||||
|
||||
kill_handler = download_video(i_episode, scape_info_serie, video_source)[1]
|
||||
kill_handler = download_video(i_episode, scrape_serie)[1]
|
@ -1,6 +1,5 @@
|
||||
# 09.06.24
|
||||
|
||||
import sys
|
||||
import logging
|
||||
|
||||
|
||||
@ -67,7 +66,8 @@ def title_search(query: str) -> int:
|
||||
title_info = {
|
||||
'name': name,
|
||||
'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)
|
||||
|
@ -47,7 +47,6 @@ class GetSerieInfo:
|
||||
Returns:
|
||||
List[Dict[str, str]]: List of dictionaries containing episode information.
|
||||
"""
|
||||
|
||||
try:
|
||||
response = httpx.get(f"{self.url}?area=online", cookies=self.cookies, headers=self.headers, timeout=max_timeout)
|
||||
response.raise_for_status()
|
||||
@ -81,4 +80,33 @@ class GetSerieInfo:
|
||||
|
||||
self.list_episodes = 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()
|
||||
|
||||
|
||||
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.
|
||||
|
||||
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:
|
||||
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)
|
||||
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)
|
||||
process_search_result(select_title, selections)
|
||||
return
|
||||
|
||||
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:
|
||||
select_title = get_select_title(table_show_manager, media_search_manager)
|
||||
process_search_result(select_title)
|
||||
process_search_result(select_title, selections)
|
||||
|
||||
else:
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
# 13.06.24
|
||||
|
||||
import os
|
||||
import logging
|
||||
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]:
|
||||
"""
|
||||
Download a single episode video.
|
||||
Downloads a specific episode from a specified season.
|
||||
|
||||
Parameters:
|
||||
- tv_name (str): Name of the TV series.
|
||||
- index_season_selected (int): Index of the selected season.
|
||||
- index_episode_selected (int): Index of the selected episode.
|
||||
- index_season_selected (int): Season number
|
||||
- index_episode_selected (int): Episode index
|
||||
- scape_info_serie (GetSerieInfo): Scraper object with series information
|
||||
|
||||
Return:
|
||||
- str: output path
|
||||
- bool: kill handler status
|
||||
Returns:
|
||||
- str: Path to downloaded file
|
||||
- bool: Whether download was stopped
|
||||
"""
|
||||
start_message()
|
||||
index_season_selected = dynamic_format_number(str(index_season_selected))
|
||||
|
||||
# Get info about episode
|
||||
obj_episode = scape_info_serie.list_episodes[index_episode_selected - 1]
|
||||
# Get episode information
|
||||
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")
|
||||
|
||||
# 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']
|
||||
|
||||
|
||||
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:
|
||||
- tv_name (str): Name of the TV series.
|
||||
- index_season_selected (int): Index of the selected season.
|
||||
- download_all (bool): Download all seasons episodes
|
||||
- scape_info_serie (GetSerieInfo): Scraper object with series information
|
||||
- index_season_selected (int): Season number
|
||||
- download_all (bool): Whether to download all episodes
|
||||
- episode_selection (str, optional): Pre-defined episode selection that bypasses manual input
|
||||
"""
|
||||
|
||||
# Start message and collect information about episodes
|
||||
start_message()
|
||||
list_dict_episode = scape_info_serie.get_episode_number(index_season_selected)
|
||||
episodes_count = len(list_dict_episode)
|
||||
# Get episodes for the selected season
|
||||
episodes = scape_info_serie.get_episode_number(index_season_selected)
|
||||
episodes_count = len(episodes)
|
||||
|
||||
if download_all:
|
||||
|
||||
# Download all episodes without asking
|
||||
|
||||
# Download all episodes in the season
|
||||
for i_episode in range(1, episodes_count + 1):
|
||||
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:
|
||||
|
||||
# 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)
|
||||
|
||||
try:
|
||||
list_episode_select = validate_episode_selection(list_episode_select, episodes_count)
|
||||
except ValueError as e:
|
||||
console.print(f"[red]{str(e)}")
|
||||
return
|
||||
list_episode_select = validate_episode_selection(list_episode_select, episodes_count)
|
||||
|
||||
# Download selected episodes
|
||||
for i_episode in list_episode_select:
|
||||
@ -126,46 +127,47 @@ def download_episode(scape_info_serie: GetSerieInfo, index_season_selected: int,
|
||||
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:
|
||||
- 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()
|
||||
|
||||
# Init class
|
||||
scape_info_serie = GetSerieInfo(dict_serie)
|
||||
|
||||
# Collect information about seasons
|
||||
seasons_count = scape_info_serie.get_seasons_number()
|
||||
# Create class
|
||||
scrape_serie = GetSerieInfo(dict_serie)
|
||||
|
||||
# Get season count
|
||||
seasons_count = scrape_serie.get_seasons_number()
|
||||
|
||||
# Prompt user for season selection and download episodes
|
||||
console.print(f"\n[green]Seasons found: [red]{seasons_count}")
|
||||
index_season_selected = msg.ask(
|
||||
"\n[cyan]Insert season number [yellow](e.g., 1), [red]* [cyan]to download all seasons, "
|
||||
"[yellow](e.g., 1-2) [cyan]for a range of seasons, or [yellow](e.g., 3-*) [cyan]to download from a specific season to the end"
|
||||
)
|
||||
|
||||
# Manage and validate the selection
|
||||
list_season_select = manage_selection(index_season_selected, seasons_count)
|
||||
|
||||
try:
|
||||
list_season_select = validate_selection(list_season_select, seasons_count)
|
||||
except ValueError as e:
|
||||
console.print(f"[red]{str(e)}")
|
||||
return
|
||||
# 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 i_season in list_season_select:
|
||||
if len(list_season_select) > 1 or index_season_selected == "*":
|
||||
|
||||
# Download all episodes if multiple seasons are selected or if '*' is used
|
||||
download_episode(scape_info_serie, i_season, download_all=True)
|
||||
download_episode(scrape_serie, i_season, download_all=True)
|
||||
else:
|
||||
|
||||
# Otherwise, let the user select specific episodes for the single season
|
||||
download_episode(scape_info_serie, i_season, download_all=False)
|
||||
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")
|
||||
|
||||
serie_info = {
|
||||
'name': title,
|
||||
'name': title.replace("streaming guardaserie", ""),
|
||||
'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)
|
||||
|
@ -104,4 +104,30 @@ class GetSerieInfo:
|
||||
except Exception as 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
|
||||
|
||||
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.
|
||||
|
||||
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':
|
||||
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:
|
||||
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:
|
||||
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)
|
||||
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)
|
||||
process_search_result(select_title, selections)
|
||||
return
|
||||
|
||||
# Get the user input for the search term
|
||||
string_to_search = get_user_input(string_to_search)
|
||||
|
||||
# Perform the database search
|
||||
len_database = title_search(quote_plus(string_to_search))
|
||||
|
||||
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 site_constant.TELEGRAM_BOT:
|
||||
bot = get_bot_instance()
|
||||
|
||||
if len_database > 0:
|
||||
select_title = get_select_title(table_show_manager, media_search_manager)
|
||||
process_search_result(select_title)
|
||||
process_search_result(select_title, selections)
|
||||
|
||||
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
|
||||
string_to_search = get_user_input()
|
||||
console.print(f"\n[red]Nothing matching was found for[white]: [purple]{string_to_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")
|
||||
|
||||
# Init class
|
||||
video_source = VideoSource(site_constant.FULL_URL, False)
|
||||
video_source.setup(select_title.id)
|
||||
video_source = VideoSource(site_constant.FULL_URL, False, select_title.id)
|
||||
|
||||
# Retrieve scws and if available master playlist
|
||||
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]:
|
||||
"""
|
||||
Download a single episode video.
|
||||
Downloads a specific episode from the specified season.
|
||||
|
||||
Parameters:
|
||||
- index_season_selected (int): Index of the selected season.
|
||||
- index_episode_selected (int): Index of the selected episode.
|
||||
- index_season_selected (int): Season number
|
||||
- index_episode_selected (int): Episode index
|
||||
- scrape_serie (GetSerieInfo): Scraper object with series information
|
||||
- video_source (VideoSource): Video source handler
|
||||
|
||||
Return:
|
||||
- str: output path
|
||||
- bool: kill handler status
|
||||
Returns:
|
||||
- str: Path to downloaded file
|
||||
- bool: Whether download was stopped
|
||||
"""
|
||||
start_message()
|
||||
index_season_selected = dynamic_format_number(str(index_season_selected))
|
||||
|
||||
# SPECIAL: Get season number
|
||||
season = None
|
||||
for s in scrape_serie.seasons_manager.seasons:
|
||||
if s.number == int(index_season_selected):
|
||||
season = s
|
||||
break
|
||||
|
||||
# Get info about episode
|
||||
obj_episode = season.episodes.get(index_episode_selected - 1)
|
||||
# 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")
|
||||
|
||||
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']
|
||||
|
||||
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:
|
||||
- index_season_selected (int): Index of the selected season.
|
||||
- download_all (bool): Download all episodes in the season.
|
||||
- index_season_selected (int): Season number
|
||||
- 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()
|
||||
scrape_serie.collect_info_season(index_season_selected)
|
||||
|
||||
# SPECIAL: Get season number
|
||||
season = None
|
||||
for s in scrape_serie.seasons_manager.seasons:
|
||||
if s.number == index_season_selected:
|
||||
season = s
|
||||
break
|
||||
episodes_count = len(season.episodes.episodes)
|
||||
# Get episodes for the selected season
|
||||
episodes = scrape_serie.getEpisodeSeasons(index_season_selected)
|
||||
episodes_count = len(episodes)
|
||||
|
||||
if episodes_count == 0:
|
||||
console.print(f"[red]No episodes found for season {index_season_selected}")
|
||||
return
|
||||
|
||||
if download_all:
|
||||
|
||||
# Download all episodes without asking
|
||||
# Download all episodes in the season
|
||||
for i_episode in range(1, episodes_count + 1):
|
||||
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}.")
|
||||
|
||||
else:
|
||||
|
||||
# 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)
|
||||
|
||||
try:
|
||||
list_episode_select = validate_episode_selection(list_episode_select, episodes_count)
|
||||
except ValueError as e:
|
||||
console.print(f"[red]{str(e)}")
|
||||
return
|
||||
list_episode_select = validate_episode_selection(list_episode_select, episodes_count)
|
||||
|
||||
# Download selected episodes if not stopped
|
||||
for i_episode in list_episode_select:
|
||||
@ -147,70 +141,65 @@ def download_episode(index_season_selected: int, scrape_serie: GetSerieInfo, vid
|
||||
if stopped:
|
||||
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:
|
||||
- select_season (MediaItem): Selected media item (TV series).
|
||||
- domain (str): Domain from which to download.
|
||||
- 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 and set up video source
|
||||
start_message()
|
||||
|
||||
# Init class
|
||||
scrape_serie = GetSerieInfo(site_constant.FULL_URL)
|
||||
video_source = VideoSource(site_constant.FULL_URL, True)
|
||||
video_source = VideoSource(site_constant.FULL_URL, True, select_season.id)
|
||||
scrape_serie = GetSerieInfo(site_constant.FULL_URL, select_season.id, select_season.name)
|
||||
|
||||
# Setup video source
|
||||
scrape_serie.setup(select_season.id, select_season.slug)
|
||||
video_source.setup(select_season.id)
|
||||
|
||||
# Collect information about seasons
|
||||
scrape_serie.collect_info_title()
|
||||
# Collect information about season
|
||||
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
|
||||
console.print(f"\n[green]Seasons found: [red]{seasons_count}")
|
||||
|
||||
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")
|
||||
# If season_selection is provided, use it instead of asking for input
|
||||
if season_selection is None:
|
||||
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(
|
||||
"select_title_episode",
|
||||
"Menu di selezione delle stagioni\n\n"
|
||||
"- Inserisci il numero della stagione (ad esempio, 1)\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 (ad esempio, 3-*) per scaricare dalla stagione specificata fino alla fine della serie",
|
||||
None
|
||||
)
|
||||
index_season_selected = bot.ask(
|
||||
"select_title_episode",
|
||||
"Menu di selezione delle stagioni\n\n"
|
||||
"- Inserisci il numero della stagione (ad esempio, 1)\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 (ad esempio, 3-*) per scaricare dalla stagione specificata fino alla fine della serie",
|
||||
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:
|
||||
index_season_selected = msg.ask(
|
||||
"\n[cyan]Insert season number [yellow](e.g., 1), [red]* [cyan]to download all seasons, "
|
||||
"[yellow](e.g., 1-2) [cyan]for a range of seasons, or [yellow](e.g., 3-*) [cyan]to download from a specific season to the end"
|
||||
)
|
||||
index_season_selected = season_selection
|
||||
console.print(f"\n[cyan]Using provided season selection: [yellow]{season_selection}")
|
||||
|
||||
# Manage and validate the selection
|
||||
# Validate the selection
|
||||
list_season_select = manage_selection(index_season_selected, seasons_count)
|
||||
|
||||
try:
|
||||
list_season_select = validate_selection(list_season_select, seasons_count)
|
||||
except ValueError as e:
|
||||
console.print(f"[red]{str(e)}")
|
||||
return
|
||||
list_season_select = validate_selection(list_season_select, seasons_count)
|
||||
|
||||
# Loop through the selected seasons and download episodes
|
||||
for i_season in list_season_select:
|
||||
|
||||
# SPECIAL: Get season number
|
||||
season = None
|
||||
for s in scrape_serie.seasons_manager.seasons:
|
||||
if s.number == i_season:
|
||||
@ -219,13 +208,10 @@ def download_series(select_season: MediaItem) -> None:
|
||||
season_number = season.number
|
||||
|
||||
if len(list_season_select) > 1 or index_season_selected == "*":
|
||||
|
||||
# Download all episodes if multiple seasons are selected or if '*' is used
|
||||
download_episode(season_number, scrape_serie, video_source, download_all=True)
|
||||
|
||||
else:
|
||||
|
||||
# Otherwise, let the user select specific episodes for the single season
|
||||
download_episode(season_number, scrape_serie, video_source, download_all=False)
|
||||
download_episode(season_number, scrape_serie, video_source, download_all=False, episode_selection=episode_selection)
|
||||
|
||||
if site_constant.TELEGRAM_BOT:
|
||||
bot.send_message(f"Finito di scaricare tutte le serie e episodi", None)
|
||||
|
@ -1,7 +1,5 @@
|
||||
# 10.12.23
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
# External libraries
|
||||
import httpx
|
||||
@ -75,7 +73,7 @@ def title_search(query: str) -> int:
|
||||
'name': dict_title.get('name'),
|
||||
'type': dict_title.get('type'),
|
||||
'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:
|
||||
|
@ -20,31 +20,21 @@ max_timeout = config_manager.get_int("REQUESTS", "timeout")
|
||||
|
||||
|
||||
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.
|
||||
|
||||
Args:
|
||||
- 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.headers = {'user-agent': get_userAgent()}
|
||||
self.url = url
|
||||
|
||||
# Initialize the SeasonManager
|
||||
self.media_id = media_id
|
||||
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:
|
||||
self.is_series = True
|
||||
self.series_name = series_name
|
||||
@ -127,4 +117,40 @@ class GetSerieInfo:
|
||||
|
||||
except Exception as 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
|
||||
console = Console()
|
||||
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):
|
||||
@ -81,4 +81,4 @@ def get_select_title(table_show_manager, media_search_manager):
|
||||
|
||||
else:
|
||||
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:
|
||||
if int(item['nFailed']) >= 1:
|
||||
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))
|
||||
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_AUDIO_WORKERS = config_manager.get_int('M3U8_DOWNLOAD', 'default_audio_workser')
|
||||
MAX_TIMEOOUT = config_manager.get_int("REQUESTS", "timeout")
|
||||
MAX_INTERRUPT_COUNT = 3
|
||||
SEGMENT_MAX_TIMEOUT = config_manager.get_int("M3U8_DOWNLOAD", "segment_timeout")
|
||||
TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot')
|
||||
|
||||
MAX_INTERRUPT_COUNT = 3
|
||||
|
||||
# Variable
|
||||
console = Console()
|
||||
@ -160,7 +159,7 @@ class M3U8_Segments:
|
||||
if self.is_index_url:
|
||||
try:
|
||||
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()
|
||||
|
||||
self.parse_data(response.text)
|
||||
|
@ -138,26 +138,53 @@ def get_ffprobe_info(file_path):
|
||||
|
||||
Returns:
|
||||
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:
|
||||
result = subprocess.run(
|
||||
[get_ffprobe_path(), '-v', 'error', '-show_format', '-show_streams', '-print_format', 'json', file_path],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=True
|
||||
)
|
||||
output = result.stdout
|
||||
info = json.loads(output)
|
||||
# Use subprocess.Popen instead of run to better handle crashes
|
||||
cmd = [get_ffprobe_path(), '-v', 'error', '-show_format', '-show_streams', '-print_format', 'json', file_path]
|
||||
logging.info(f"FFmpeg command: {cmd}")
|
||||
|
||||
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
|
||||
}
|
||||
with subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) as proc:
|
||||
stdout, stderr = proc.communicate()
|
||||
|
||||
if proc.returncode != 0:
|
||||
logging.error(f"FFprobe failed with return code {proc.returncode} for file {file_path}")
|
||||
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:
|
||||
logging.error(f"Failed to parse JSON output from ffprobe for file {file_path}: {e}")
|
||||
return None
|
||||
logging.error(f"Failed to get ffprobe info for file {file_path}: {e}")
|
||||
return {
|
||||
'format_name': None,
|
||||
'codec_names': []
|
||||
}
|
||||
|
||||
|
||||
def is_png_format_or_codec(file_info):
|
||||
@ -173,8 +200,11 @@ def is_png_format_or_codec(file_info):
|
||||
if not file_info:
|
||||
return False
|
||||
|
||||
#console.print(f"[yellow][FFmpeg] [cyan]Avaiable codec[white]: [red]{file_info['codec_names']}")
|
||||
return file_info['format_name'] == 'png_pipe' or 'png' in file_info['codec_names']
|
||||
# Handle None values in format_name gracefully
|
||||
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):
|
||||
|
@ -31,16 +31,20 @@ class M3U8_Ts_Estimator:
|
||||
self.segments_instance = segments_instance
|
||||
self.lock = threading.Lock()
|
||||
self.speed = {"upload": "N/A", "download": "N/A"}
|
||||
self._running = True
|
||||
|
||||
if get_use_large_bar():
|
||||
logging.debug("USE_LARGE_BAR is True, starting speed capture thread")
|
||||
self.speed_thread = threading.Thread(target=self.capture_speed)
|
||||
self.speed_thread.daemon = True
|
||||
self.speed_thread.start()
|
||||
|
||||
else:
|
||||
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):
|
||||
"""Add a file size to the list of file sizes."""
|
||||
if size <= 0:
|
||||
@ -50,32 +54,44 @@ class M3U8_Ts_Estimator:
|
||||
self.ts_file_sizes.append(size)
|
||||
|
||||
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
|
||||
speed_buffer = deque(maxlen=3)
|
||||
|
||||
while True:
|
||||
while self._running:
|
||||
try:
|
||||
# Get IO counters only once per loop to reduce function calls
|
||||
io_counters = psutil.net_io_counters()
|
||||
if not io_counters:
|
||||
raise ValueError("No IO counters available")
|
||||
|
||||
current_upload, current_download = io_counters.bytes_sent, io_counters.bytes_recv
|
||||
|
||||
if last_upload and last_download:
|
||||
upload_speed = (current_upload - last_upload) / 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:
|
||||
self.speed = {
|
||||
"upload": internet_manager.format_transfer_speed(max(0, upload_speed)),
|
||||
"download": internet_manager.format_transfer_speed(sum(speed_buffer) / len(speed_buffer))
|
||||
"upload": formatted_upload,
|
||||
"download": formatted_download
|
||||
}
|
||||
logging.debug(f"Updated speeds - Upload: {self.speed['upload']}, Download: {self.speed['download']}")
|
||||
|
||||
last_upload, last_download = current_upload, current_download
|
||||
|
||||
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"}
|
||||
|
||||
time.sleep(interval)
|
||||
@ -88,6 +104,10 @@ class M3U8_Ts_Estimator:
|
||||
str: The mean size of the files in a human-readable format.
|
||||
"""
|
||||
try:
|
||||
# Only do calculations if we have data
|
||||
if not self.ts_file_sizes:
|
||||
return "0 B"
|
||||
|
||||
total_size = sum(self.ts_file_sizes)
|
||||
mean_size = total_size / len(self.ts_file_sizes)
|
||||
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)
|
||||
|
||||
file_total_size = self.calculate_total_size()
|
||||
if file_total_size == "Error":
|
||||
return
|
||||
|
||||
number_file_total_size = file_total_size.split(' ')[0]
|
||||
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():
|
||||
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:
|
||||
average_internet_speed = speed_data[0]
|
||||
average_internet_unit = speed_data[1]
|
||||
else:
|
||||
average_internet_speed = "N/A"
|
||||
average_internet_unit = ""
|
||||
if download_speed != "N/A":
|
||||
speed_data = download_speed.split(" ")
|
||||
|
||||
average_internet_speed = speed_data[0] if len(speed_data) >= 1 else "N/A"
|
||||
average_internet_unit = speed_data[1] if len(speed_data) >= 2 else ""
|
||||
|
||||
retry_count = self.segments_instance.active_retries if self.segments_instance else 0
|
||||
progress_str = (
|
||||
f"{Colors.GREEN}{number_file_total_size} {Colors.RED}{units_file_total_size}"
|
||||
f"{Colors.WHITE}, {Colors.CYAN}{average_internet_speed} {Colors.RED}{average_internet_unit}"
|
||||
f"{Colors.WHITE}, {Colors.GREEN}CRR {Colors.RED}{retry_count} "
|
||||
f"{Colors.WHITE}, {Colors.CYAN}{average_internet_speed} {Colors.RED}{average_internet_unit} "
|
||||
#f"{Colors.WHITE}, {Colors.GREEN}CRR {Colors.RED}{retry_count} "
|
||||
)
|
||||
|
||||
else:
|
||||
retry_count = self.segments_instance.active_retries if self.segments_instance else 0
|
||||
progress_str = (
|
||||
f"{Colors.GREEN}{number_file_total_size} {Colors.RED}{units_file_total_size}"
|
||||
f"{Colors.WHITE}, {Colors.GREEN}CRR {Colors.RED}{retry_count} "
|
||||
f"{Colors.GREEN}{number_file_total_size} {Colors.RED}{units_file_total_size} "
|
||||
#f"{Colors.WHITE}, {Colors.GREEN}CRR {Colors.RED}{retry_count} "
|
||||
)
|
||||
|
||||
progress_counter.set_postfix_str(progress_str)
|
||||
|
@ -158,7 +158,8 @@ class TVShowManager:
|
||||
else:
|
||||
key = Prompt.ask(prompt_msg)
|
||||
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"
|
||||
telegram_msg = "Scegli il contenuto da scaricare:\n Serie TV - Film - Anime\noppure `back` per tornare indietro"
|
||||
|
||||
@ -199,7 +200,8 @@ class TVShowManager:
|
||||
else:
|
||||
key = Prompt.ask(prompt_msg)
|
||||
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"
|
||||
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