Stop handling for MP$ and HLS formats (#238)

* Added MP4 stop signal handler for series

* Upgraded stopped option handling for HLS

* Upgraded stop handling for HLS and MP4
This commit is contained in:
Graziano Nobile 2025-02-01 15:33:34 +01:00 committed by GitHub
parent 535bc7b257
commit dc2263f008
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 127 additions and 34 deletions

View File

@ -40,7 +40,8 @@ def download_film(select_title: MediaItem) -> str:
# Start message and display film information
start_message()
console.print(f"[yellow]Download: [red]{select_title.name} \n")
console.print(f"[cyan]You can safely stop the download with [bold]Ctrl+c[bold] [cyan] \n")
# Set domain and media ID for the video source
video_source = VideoSource(select_title.url)
@ -64,6 +65,9 @@ def download_film(select_title: MediaItem) -> str:
if msg.ask("[green]Do you want to continue [white]([red]y[white])[green] or return at home[white]([red]n[white]) ", choices=['y', 'n'], default='y', show_choices=True) == "n":
frames = get_call_stack()
execute_search(frames[-4])"""
if r_proc == None:
if os.path.exists(os.path.join(mp4_path, title_name)):
os.remove(os.path.join(mp4_path, title_name))
if r_proc != None:
console.print("[green]Result: ")

View File

@ -24,10 +24,10 @@ from StreamingCommunity.Api.Player.vixcloud import VideoSourceAnime
# Variable
from .costant import SITE_NAME, ANIME_FOLDER, MOVIE_FOLDER
KILL_HANDLER = bool(False)
def download_episode(index_select: int, scrape_serie: ScrapeSerieAnime, video_source: VideoSourceAnime) -> str:
def download_episode(index_select: int, scrape_serie: ScrapeSerieAnime, video_source: VideoSourceAnime) -> tuple[str,bool]:
"""
Downloads the selected episode.
@ -36,6 +36,7 @@ def download_episode(index_select: int, scrape_serie: ScrapeSerieAnime, video_so
Return:
- str: output path
- bool: kill handler status
"""
# Get information about the selected episode
@ -45,7 +46,8 @@ def download_episode(index_select: int, scrape_serie: ScrapeSerieAnime, video_so
start_message()
console.print(f"[yellow]Download: [red]EP_{obj_episode.number} \n")
console.print("[cyan]You can safely stop the download with [bold]Ctrl+c[bold] [cyan] \n")
# Collect mp4 url
video_source.get_embed(obj_episode.id)
@ -65,11 +67,18 @@ def download_episode(index_select: int, scrape_serie: ScrapeSerieAnime, video_so
os_manager.create_path(mp4_path)
# Start downloading
r_proc = MP4_downloader(
url=str(video_source.src_mp4).strip(),
path=os.path.join(mp4_path, title_name)
)
# If download fails do not create the file
if r_proc == None:
if os.path.exists(os.path.join(mp4_path, title_name)):
os.remove(os.path.join(mp4_path, title_name))
return "",True
if r_proc != None:
console.print("[green]Result: ")
console.print(r_proc)
@ -106,12 +115,15 @@ def download_series(select_title: MediaItem):
# Download selected episodes
if len(list_episode_select) == 1 and last_command != "*":
download_episode(list_episode_select[0]-1, scrape_serie, video_source)
download_episode(list_episode_select[0]-1, scrape_serie, video_source)[0]
# Download all other episodes selecter
else:
kill_handler=bool(False)
for i_episode in list_episode_select:
download_episode(i_episode-1, scrape_serie, video_source)
if kill_handler:
break
kill_handler= download_episode(i_episode-1, scrape_serie, video_source)[1]
def download_film(select_title: MediaItem):

View File

@ -38,6 +38,7 @@ def download_film(select_title: MediaItem) -> str:
# Start message and display film information
start_message()
console.print(f"[yellow]Download: [red]{select_title.name} \n")
console.print(f"[cyan]You can safely stop the download with [bold]Ctrl+c[bold] [cyan] \n")
# Setup api manger
print(select_title.url)
@ -64,6 +65,10 @@ def download_film(select_title: MediaItem) -> str:
frames = get_call_stack()
execute_search(frames[-4])"""
if r_proc == None:
if os.path.exists(os.path.join(mp4_path, title_name)):
os.remove(os.path.join(mp4_path, title_name))
if r_proc != None:
console.print("[green]Result: ")
console.print(r_proc)

View File

@ -28,7 +28,7 @@ from .costant import SERIES_FOLDER
def download_video(index_episode_selected: int, scape_info_serie: GetSerieInfo, video_source: VideoSource) -> str:
def download_video(index_episode_selected: int, scape_info_serie: GetSerieInfo, video_source: VideoSource) -> tuple[str,bool]:
"""
Download a single episode video.
@ -38,13 +38,14 @@ def download_video(index_episode_selected: int, scape_info_serie: GetSerieInfo,
Return:
- str: output path
- bool: kill handler status
"""
start_message()
# Get info about episode
obj_episode = scape_info_serie.list_episodes[index_episode_selected - 1]
console.print(f"[yellow]Download: [red]{obj_episode.get('name')}")
print()
console.print(f"[yellow]Download: [red]{obj_episode.get('name')}\n")
console.print(f"[cyan]You can safely stop the download with [bold]Ctrl+c[bold] [cyan] \n")
# Define filename and path for the downloaded video
title_name = os_manager.get_sanitize_file(
@ -70,6 +71,11 @@ def download_video(index_episode_selected: int, scape_info_serie: GetSerieInfo,
path=os.path.join(mp4_path, title_name),
referer=f"{parsed_url.scheme}://{parsed_url.netloc}/",
)
if r_proc == None:
if os.path.exists(os.path.join(mp4_path, title_name)):
os.remove(os.path.join(mp4_path, title_name))
return "",True
if r_proc != None:
console.print("[green]Result: ")
@ -105,8 +111,11 @@ def download_thread(dict_serie: MediaItem):
return
# Download selected episodes
kill_handler = bool(False)
for i_episode in list_episode_select:
download_video(i_episode, scape_info_serie, video_source)
if kill_handler:
break
kill_handler = download_video(i_episode, scape_info_serie, video_source)[1]
def display_episodes_list(obj_episode_manager) -> str:

View File

@ -44,9 +44,8 @@ def download_video(index_season_selected: int, index_episode_selected: int, scap
# Get info about episode
obj_episode = scape_info_serie.list_episodes[index_episode_selected - 1]
console.print(f"[yellow]Download: [red]{index_season_selected}:{index_episode_selected} {obj_episode.get('name')}")
print()
console.print(f"[yellow]Download: [red]{index_season_selected}:{index_episode_selected} {obj_episode.get('name')}\n")
console.print(f"[cyan]You can safely stop the download with [bold]Ctrl+c[bold] [cyan] \n")
# Define filename and path for the downloaded video
mp4_name = f"{map_episode_title(scape_info_serie.tv_name, index_season_selected, index_episode_selected, obj_episode.get('name'))}.mp4"
mp4_path = os.path.join(SERIES_FOLDER, scape_info_serie.tv_name, f"S{index_season_selected}")
@ -70,6 +69,13 @@ def download_video(index_season_selected: int, index_episode_selected: int, scap
if msg.ask("[green]Do you want to continue [white]([red]y[white])[green] or return at home[white]([red]n[white]) ", choices=['y', 'n'], default='y', show_choices=True) == "n":
frames = get_call_stack()
execute_search(frames[-4])"""
# Removes file not completed and stops other downloads
if r_proc == None:
if os.path.exists(os.path.join(mp4_path, mp4_name)):
os.remove(os.path.join(mp4_path, mp4_name))
return "",True
if r_proc != None:
console.print("[green]Result: ")
@ -113,7 +119,10 @@ def download_episode(scape_info_serie: GetSerieInfo, index_season_selected: int,
return
# Download selected episodes
stopped = bool(False)
for i_episode in list_episode_select:
if stopped:
break
download_video(index_season_selected, i_episode, scape_info_serie)

View File

@ -50,7 +50,7 @@ def download_film(movie_details: Json_film) -> str:
# Start message and display film information
start_message()
console.print(f"[yellow]Download: [red]{movie_details.title} \n")
console.print(f"[cyan]You can safely stop the download with [bold]Ctrl+c[bold] [cyan] \n")
# Make request to main site
try:
url = f"https://{SITE_NAME}.{DOMAIN_NOW}/set-movie-a/{movie_details.imdb_id}"
@ -94,6 +94,10 @@ def download_film(movie_details: Json_film) -> str:
frames = get_call_stack()
execute_search(frames[-4])"""
if r_proc == None:
if os.path.exists(os.path.join(mp4_path, title_name)):
os.remove(os.path.join(mp4_path, title_name))
if r_proc != None:
console.print("[green]Result: ")
console.print(r_proc)

View File

@ -40,6 +40,7 @@ def download_film(select_title: MediaItem) -> str:
# Start message and display film information
start_message()
console.print(f"[yellow]Download: [red]{select_title.name} \n")
console.print(f"[cyan]You can safely stop the download with [bold]Ctrl+c[bold] [cyan] \n")
# Init class
video_source = VideoSource(SITE_NAME, False)
@ -68,6 +69,10 @@ def download_film(select_title: MediaItem) -> str:
frames = get_call_stack()
execute_search(frames[-4])"""
if r_proc == None:
if os.path.exists(os.path.join(mp4_path, title_name)):
os.remove(os.path.join(mp4_path, title_name))
if r_proc != None:
console.print("[green]Result: ")
console.print(r_proc)

View File

@ -28,7 +28,7 @@ from .costant import SITE_NAME, SERIES_FOLDER
def download_video(index_season_selected: int, index_episode_selected: int, scrape_serie: ScrapeSerie, video_source: VideoSource) -> str:
def download_video(index_season_selected: int, index_episode_selected: int, scrape_serie: ScrapeSerie, video_source: VideoSource) -> tuple[str,bool]:
"""
Download a single episode video.
@ -38,15 +38,16 @@ def download_video(index_season_selected: int, index_episode_selected: int, scra
Return:
- str: output path
- bool: kill handler status
"""
start_message()
index_season_selected = dynamic_format_number(index_season_selected)
# Get info about episode
obj_episode = scrape_serie.episode_manager.get(index_episode_selected - 1)
console.print(f"[yellow]Download: [red]{index_season_selected}:{index_episode_selected} {obj_episode.name}")
print()
console.print(f"[yellow]Download: [red]{index_season_selected}:{index_episode_selected} {obj_episode.name}\n")
console.print(f"[cyan]You can safely stop the download with [bold]Ctrl+c[bold] [cyan] \n")
# Define filename and path for the downloaded video
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(SERIES_FOLDER, scrape_serie.series_name, f"S{index_season_selected}")
@ -70,6 +71,12 @@ def download_video(index_season_selected: int, index_episode_selected: int, scra
frames = get_call_stack()
execute_search(frames[-4])"""
if r_proc == None:
if os.path.exists(os.path.join(mp4_path, mp4_name)):
os.remove(os.path.join(mp4_path, mp4_name))
return "",True
if r_proc != None:
console.print("[green]Result: ")
console.print(r_proc)
@ -112,9 +119,12 @@ def download_episode(index_season_selected: int, scrape_serie: ScrapeSerie, vide
console.print(f"[red]{str(e)}")
return
# Download selected episodes
# Download selected episodes if not stopped
stopped = bool(False)
for i_episode in list_episode_select:
download_video(index_season_selected, i_episode, scrape_serie, video_source)
if stopped:
break
stopped=download_video(index_season_selected, i_episode, scrape_serie, video_source)[1]
def download_series(select_season: MediaItem, version: str) -> None:
"""

View File

@ -416,7 +416,7 @@ class ContentDownloader:
# Download the video streams and print status
info_dw = video_m3u8.download_streams(f"{Colors.MAGENTA}video", "video")
list_MissingTs.append(info_dw)
self.stopped=list_MissingTs.pop()
# Print duration information of the downloaded video
#print_duration_table(downloaded_video[0].get('path'))
@ -447,7 +447,7 @@ class ContentDownloader:
# Download the audio segments and print status
info_dw = audio_m3u8.download_streams(f"{Colors.MAGENTA}audio {Colors.RED}{obj_audio.get('language')}", f"audio_{obj_audio.get('language')}")
list_MissingTs.append(info_dw)
self.stopped=list_MissingTs.pop()
# Print duration information of the downloaded audio
#print_duration_table(obj_audio.get('path'))
@ -710,6 +710,8 @@ class ContentJoiner:
class HLS_Downloader:
stopped = bool(False)
def __init__(self, output_filename: str=None, m3u8_playlist: str=None, m3u8_index: str=None, is_playlist_url: bool=True, is_index_url: bool=True):
"""
Initializes the HLS_Downloader class.
@ -820,9 +822,11 @@ class HLS_Downloader:
return None
else:
if self.stopped:
return self.stopped
return {
'path': self.output_filename,
'url': self.m3u8_playlist
'url': self.m3u8_playlist,
}
else:
@ -847,9 +851,11 @@ class HLS_Downloader:
return None
else:
if self.stopped:
return None
return {
'path': self.output_filename,
'url': self.m3u8_index
'url': self.m3u8_index,
}
else:

View File

@ -570,4 +570,6 @@ class M3U8_Segments:
console.print("[yellow]⚠ Warning:[/yellow] Too many retries detected! Consider reducing the number of [cyan]workers[/cyan] in the [magenta]config.json[/magenta] file. This will impact [bold]performance[/bold]. \n")
# Info to return
return {'type': type, 'nFailed': self.info_nFailed}
if self.download_interrupted:
return {'type': type, 'nFailed': self.info_nFailed, 'stopped': bool(True)}
return {'type': type, 'nFailed': self.info_nFailed, 'stopped': bool(False)}

View File

@ -1,11 +1,12 @@
# 09.06.24
import os
import signal
import sys
import ssl
import certifi
import logging
import atexit
# External libraries
import httpx
@ -34,7 +35,9 @@ GET_ONLY_LINK = config_manager.get_bool('M3U8_PARSER', 'get_only_link')
TQDM_USE_LARGE_BAR = config_manager.get_int('M3U8_DOWNLOAD', 'tqdm_use_large_bar')
REQUEST_TIMEOUT = config_manager.get_float('REQUESTS', 'timeout')
#Ending constant
KILL_HANDLER = bool(False)
def MP4_downloader(url: str, path: str, referer: str = None, headers_: dict = None):
"""
@ -46,6 +49,7 @@ def MP4_downloader(url: str, path: str, referer: str = None, headers_: dict = No
- referer (str, optional): The referer header value.
- headers_ (dict, optional): Custom headers for the request.
"""
# Early return for link-only mode
if GET_ONLY_LINK:
return {'path': path, 'url': url}
@ -111,15 +115,34 @@ def MP4_downloader(url: str, path: str, referer: str = None, headers_: dict = No
# Ensure directory exists
os.makedirs(os.path.dirname(path), exist_ok=True)
def signal_handler(*args):
"""
Signal handler for SIGINT
Parameters:
- args (tuple): The signal arguments (to prevent errors).
"""
if(downloaded<total/2):
raise KeyboardInterrupt
else:
console.print("[bold green]Download almost completed, will exit next[/bold green]")
print("KILL_HANDLER: ", KILL_HANDLER)
# Download file
with open(path, 'wb') as file, progress_bar as bar:
downloaded = 0
for chunk in response.iter_bytes(chunk_size=1024):
if chunk:
size = file.write(chunk)
downloaded += size
bar.update(size)
#Test check stop download
#atexit.register(quit_gracefully)
for chunk in response.iter_bytes(chunk_size=1024):
signal.signal(signal.SIGINT,signal_handler)
if chunk:
size = file.write(chunk)
downloaded += size
bar.update(size)
# Optional: Add a check to stop download if needed
# if downloaded > MAX_DOWNLOAD_SIZE:
# break
@ -133,7 +156,7 @@ def MP4_downloader(url: str, path: str, referer: str = None, headers_: dict = No
title=f"{os.path.basename(path.replace('.mp4', ''))}",
border_style="green"
))
return path
return path,KILL_HANDLER
else:
console.print("[bold red]Download failed or file is empty.[/bold red]")
@ -153,3 +176,7 @@ def MP4_downloader(url: str, path: str, referer: str = None, headers_: dict = No
logging.error(f"Unexpected error during download: {e}")
console.print(f"[bold red]Unexpected Error: {e}[/bold red]")
return None
except KeyboardInterrupt:
console.print("[bold red]Download stopped by user.[/bold red]")
return None