api: Add raiplay

This commit is contained in:
Prova45 2025-04-11 16:30:42 +02:00
parent 64efc67e6a
commit c3a0be0d85
48 changed files with 1620 additions and 777 deletions

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

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

View File

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

View File

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

View File

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

View 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View 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']

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

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

View 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]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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