This commit is contained in:
Dark1291 2025-02-05 12:45:04 +01:00
parent 1ede152923
commit f7aee0e922
18 changed files with 149 additions and 100 deletions

View File

@ -61,8 +61,8 @@ def download_film(select_title: MediaItem) -> str:
# Download the film using the m3u8 playlist, and output filename
r_proc = HLS_Downloader(
m3u8_playlist=master_playlist,
output_filename=os.path.join(mp4_path, title_name)
m3u8_url=master_playlist,
output_path=os.path.join(mp4_path, title_name)
).start()
if TELEGRAM_BOT:

View File

@ -49,8 +49,8 @@ def download_film(select_title: MediaItem) -> str:
# Download the film using the m3u8 playlist, and output filename
r_proc = HLS_Downloader(
m3u8_playlist=master_playlist,
output_filename=os.path.join(mp4_path, title_name)
m3u8_url=master_playlist,
output_path=os.path.join(mp4_path, title_name)
).start()
if "error" in r_proc.keys():

View File

@ -58,8 +58,8 @@ def download_video(index_season_selected: int, index_episode_selected: int, scap
# Download the film using the m3u8 playlist, and output filename
r_proc = HLS_Downloader(
m3u8_playlist=master_playlist,
output_filename=os.path.join(mp4_path, mp4_name)
m3u8_url=master_playlist,
output_path=os.path.join(mp4_path, mp4_name)
).start()
@ -69,7 +69,7 @@ def download_video(index_season_selected: int, index_episode_selected: int, scap
except:
pass
return r_proc['path']
return r_proc['path'], r_proc['stopped']
def download_episode(scape_info_serie: GetSerieInfo, index_season_selected: int, download_all: bool = False) -> None:
@ -91,7 +91,11 @@ def download_episode(scape_info_serie: GetSerieInfo, index_season_selected: int,
# Download all episodes without asking
for i_episode in range(1, episodes_count + 1):
download_video(index_season_selected, i_episode, scape_info_serie)
path, stopped = download_video(index_season_selected, i_episode, scape_info_serie)
if stopped:
break
console.print(f"\n[red]End downloaded [yellow]season: [red]{index_season_selected}.")
else:
@ -107,11 +111,11 @@ 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:
path, stopped = download_video(index_season_selected, i_episode, scape_info_serie)
if stopped:
break
download_video(index_season_selected, i_episode, scape_info_serie)
def download_series(dict_serie: MediaItem) -> None:

View File

@ -77,8 +77,8 @@ def download_film(movie_details: Json_film) -> str:
# Download the film using the m3u8 playlist, and output filename
r_proc = HLS_Downloader(
m3u8_playlist=master_playlist,
output_filename=os.path.join(mp4_path, title_name)
m3u8_url=master_playlist,
output_path=os.path.join(mp4_path, title_name)
).start()
if "error" in r_proc.keys():

View File

@ -66,8 +66,8 @@ def download_film(select_title: MediaItem) -> str:
# Download the film using the m3u8 playlist, and output filename
r_proc = HLS_Downloader(
m3u8_playlist=master_playlist,
output_filename=os.path.join(mp4_path, title_name)
m3u8_url=master_playlist,
output_path=os.path.join(mp4_path, title_name)
).start()
if TELEGRAM_BOT:

View File

@ -72,8 +72,8 @@ def download_video(index_season_selected: int, index_episode_selected: int, scra
# Download the episode
r_proc = HLS_Downloader(
m3u8_playlist=master_playlist,
output_filename=os.path.join(mp4_path, mp4_name)
m3u8_url=master_playlist,
output_path=os.path.join(mp4_path, mp4_name)
).start()
if "error" in r_proc.keys():
@ -82,7 +82,7 @@ def download_video(index_season_selected: int, index_episode_selected: int, scra
except:
pass
return r_proc['path']
return r_proc['path'], r_proc['stopped']
def download_episode(index_season_selected: int, scrape_serie: ScrapeSerie, video_source: VideoSource, download_all: bool = False) -> None:
"""
@ -105,7 +105,11 @@ def download_episode(index_season_selected: int, scrape_serie: ScrapeSerie, vide
# Download all episodes without asking
for i_episode in range(1, episodes_count + 1):
download_video(index_season_selected, i_episode, scrape_serie, video_source)
path, stopped = download_video(index_season_selected, i_episode, scrape_serie, video_source)
if stopped:
break
console.print(f"\n[red]End downloaded [yellow]season: [red]{index_season_selected}.")
else:
@ -122,7 +126,10 @@ def download_episode(index_season_selected: int, scrape_serie: ScrapeSerie, vide
# Download selected episodes if not stopped
for i_episode in list_episode_select:
download_video(index_season_selected, i_episode, scrape_serie, video_source)[1]
path, stopped = download_video(index_season_selected, i_episode, scrape_serie, video_source)
if stopped:
break
def download_series(select_season: MediaItem, version: str) -> None:
"""

View File

@ -72,6 +72,7 @@ class HLSClient:
response = client.get(url)
response.raise_for_status()
return response.content if return_content else response.text
except Exception as e:
logging.error(f"Attempt {attempt+1} failed: {str(e)}")
time.sleep(1.5 ** attempt)
@ -100,8 +101,10 @@ class PathManager:
root = config_manager.get('DEFAULT', 'root_path')
hash_name = compute_sha1_hash(self.m3u8_url) + ".mp4"
return os.path.join(root, "undefined", hash_name)
if not path.endswith(".mp4"):
path += ".mp4"
return os_manager.get_sanitize_path(path)
def setup_directories(self):
@ -112,7 +115,6 @@ class PathManager:
def move_final_file(self, final_file: str):
"""Moves the final merged file to the desired output location."""
os.makedirs(os.path.dirname(self.output_path), exist_ok=True)
if os.path.exists(self.output_path):
os.remove(self.output_path)
shutil.move(final_file, self.output_path)
@ -144,6 +146,7 @@ class M3U8Manager:
content = self.client.request(self.m3u8_url)
if not content:
raise ValueError("Failed to fetch M3U8 content")
self.parser.parse_data(uri=self.m3u8_url, raw_content=content)
self.url_fixer.set_playlist(self.m3u8_url)
self.is_master = self.parser.is_master_playlist
@ -245,49 +248,78 @@ class DownloadManager:
self.client = client
self.url_fixer = url_fixer
self.missing_segments = []
self.stopped = False
def download_video(self, video_url: str):
"""Downloads video segments from the M3U8 playlist."""
video_full_url = self.url_fixer.generate_full_url(video_url)
video_tmp_dir = os.path.join(self.temp_dir, 'video')
downloader = M3U8_Segments(url=video_full_url, tmp_folder=video_tmp_dir)
result = downloader.download_streams("Video", "video")
self.missing_segments.append(result)
if result.get('stopped', False):
self.stopped = True
return self.stopped
def download_audio(self, audio: Dict):
"""Downloads audio segments for a specific language track."""
if self.stopped:
return True
audio_full_url = self.url_fixer.generate_full_url(audio['uri'])
audio_tmp_dir = os.path.join(self.temp_dir, 'audio', audio['language'])
downloader = M3U8_Segments(url=audio_full_url, tmp_folder=audio_tmp_dir)
result = downloader.download_streams(f"Audio {audio['language']}", "audio")
self.missing_segments.append(result)
if result.get('stopped', False):
self.stopped = True
return self.stopped
def download_subtitle(self, sub: Dict):
"""Downloads and saves subtitle file for a specific language."""
if self.stopped:
return True
content = self.client.request(sub['uri'])
if content:
sub_path = os.path.join(self.temp_dir, 'subs', f"{sub['language']}.vtt")
with open(sub_path, 'w', encoding='utf-8') as f:
f.write(content)
return self.stopped
def download_all(self, video_url: str, audio_streams: List[Dict], sub_streams: List[Dict]):
"""
Downloads all selected streams (video, audio, subtitles).
Skips already downloaded content to support resume functionality.
"""
video_file = os.path.join(self.temp_dir, 'video', '0.ts')
if not os.path.exists(video_file):
self.download_video(video_url)
if self.download_video(video_url):
return True
for audio in audio_streams:
if self.stopped:
break
audio_file = os.path.join(self.temp_dir, 'audio', audio['language'], '0.ts')
if not os.path.exists(audio_file):
self.download_audio(audio)
if self.download_audio(audio):
return True
for sub in sub_streams:
if self.stopped:
break
sub_file = os.path.join(self.temp_dir, 'subs', f"{sub['language']}.vtt")
if not os.path.exists(sub_file):
self.download_subtitle(sub)
if self.download_subtitle(sub):
return True
return self.stopped
class MergeManager:
@ -324,12 +356,14 @@ class MergeManager:
out_path=os.path.join(self.temp_dir, 'video.mp4'),
codec=self.parser.codec
)
else:
if MERGE_AUDIO and self.audio_streams:
audio_tracks = [{
'path': os.path.join(self.temp_dir, 'audio', a['language'], '0.ts'),
'name': a['language']
} for a in self.audio_streams]
merged_audio_path = os.path.join(self.temp_dir, 'merged_audio.mp4')
merged_file = join_audios(
video_path=video_file,
@ -337,17 +371,20 @@ class MergeManager:
out_path=merged_audio_path,
codec=self.parser.codec
)
if MERGE_SUBTITLE and self.sub_streams:
sub_tracks = [{
'path': os.path.join(self.temp_dir, 'subs', f"{s['language']}.vtt"),
'language': s['language']
} for s in self.sub_streams]
merged_subs_path = os.path.join(self.temp_dir, 'final.mp4')
merged_file = join_subtitle(
video_path=merged_file,
subtitles_list=sub_tracks,
out_path=merged_subs_path
)
return merged_file
@ -382,7 +419,8 @@ class HLS_Downloader:
'path': self.path_manager.output_path,
'url': self.m3u8_url,
'is_master': False,
'error': 'File already exists'
'error': 'File already exists',
'stopped': False
}
if TELEGRAM_BOT:
bot.send_message(response)
@ -400,12 +438,23 @@ class HLS_Downloader:
client=self.client,
url_fixer=self.m3u8_manager.url_fixer
)
self.download_manager.download_all(
# Check if download was stopped
download_stopped = self.download_manager.download_all(
video_url=self.m3u8_manager.video_url,
audio_streams=self.m3u8_manager.audio_streams,
sub_streams=self.m3u8_manager.sub_streams
)
if download_stopped:
return {
'path': None,
'url': self.m3u8_url,
'is_master': self.m3u8_manager.is_master,
'error': 'Download stopped by user',
'stopped': True
}
self.merge_manager = MergeManager(
temp_dir=self.path_manager.temp_dir,
parser=self.m3u8_manager.parser,
@ -422,20 +471,23 @@ class HLS_Downloader:
return {
'path': self.path_manager.output_path,
'url': self.m3u8_url,
'is_master': self.m3u8_manager.is_master
'is_master': self.m3u8_manager.is_master,
'stopped': False
}
except Exception as e:
error_msg = str(e)
console.print(f"[red]Download failed: {error_msg}[/red]")
logging.error("Download error", exc_info=True)
return {
'path': None,
'url': self.m3u8_url,
'is_master': getattr(self.m3u8_manager, 'is_master', None),
'error': error_msg
'error': error_msg,
'stopped': False
}
def _print_summary(self):
"""Prints download summary including file size, duration, and any missing segments."""
if TELEGRAM_BOT:
@ -466,6 +518,7 @@ class HLS_Downloader:
if missing_ts:
panel_content += f"\n{missing_info}"
os.rename(self.path_manager.output_path, self.path_manager.output_path.replace(".mp4", "_failed.mp4"))
console.print(Panel(
panel_content,
title=f"{os.path.basename(self.path_manager.output_path.replace('.mp4', ''))}",

View File

@ -47,7 +47,6 @@ PROXY_START_MAX = config_manager.get_float('REQUESTS', 'proxy_start_max')
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")
TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot')
@ -70,7 +69,7 @@ class M3U8_Segments:
# Util class
self.decryption: M3U8_Decryption = None
self.class_ts_estimator = M3U8_Ts_Estimator(0)
self.class_ts_estimator = M3U8_Ts_Estimator(0, self)
self.class_url_fixer = M3U8_UrlFix(url)
# Sync
@ -88,6 +87,8 @@ class M3U8_Segments:
self.info_maxRetry = 0
self.info_nRetry = 0
self.info_nFailed = 0
self.active_retries = 0
self.active_retries_lock = threading.Lock()
def __get_key__(self, m3u8_parser: M3U8_Parser) -> bytes:
key_uri = urljoin(self.url, m3u8_parser.keys.get('uri'))
@ -232,10 +233,17 @@ class M3U8_Segments:
self.queue.put((index, None)) # Marker for failed segment
progress_bar.update(1)
self.info_nFailed += 1
return
with self.active_retries_lock:
self.active_retries += 1
sleep_time = backoff_factor * (2 ** attempt)
logging.info(f"Retrying segment {index} in {sleep_time} seconds...")
time.sleep(sleep_time)
with self.active_retries_lock:
self.active_retries -= 1
def write_segments_to_file(self):
"""
@ -296,11 +304,7 @@ class M3U8_Segments:
- description: Description to insert on tqdm bar
- type (str): Type of download: 'video' or 'audio'
"""
if TELEGRAM_BOT:
# Viene usato per lo screen
console.log("####")
self.get_info()
self.setup_interrupt_handler()
progress_bar = tqdm(

View File

@ -110,6 +110,8 @@ def join_video(video_path: str, out_path: str, codec: M3U8_Codec = None):
capture_ffmpeg_real_time(ffmpeg_cmd, "[cyan]Join video")
print()
return out_path
def join_audios(video_path: str, audio_tracks: List[Dict[str, str]], out_path: str, codec: M3U8_Codec = None):
"""
@ -204,6 +206,8 @@ def join_audios(video_path: str, audio_tracks: List[Dict[str, str]], out_path: s
capture_ffmpeg_real_time(ffmpeg_cmd, "[cyan]Join audio")
print()
return out_path
def join_subtitle(video_path: str, subtitles_list: List[Dict[str, str]], out_path: str):
"""
@ -255,4 +259,6 @@ def join_subtitle(video_path: str, subtitles_list: List[Dict[str, str]], out_pat
console.log(f"[purple]FFmpeg [white][[cyan]Join subtitle[white]] ...")
with suppress_output():
capture_ffmpeg_real_time(ffmpeg_cmd, "[cyan]Join subtitle")
print()
print()
return out_path

View File

@ -22,7 +22,7 @@ TQDM_USE_LARGE_BAR = not ("android" in sys.platform or "ios" in sys.platform)
class M3U8_Ts_Estimator:
def __init__(self, total_segments: int):
def __init__(self, total_segments: int, segments_instance=None):
"""
Initialize the M3U8_Ts_Estimator object.
@ -32,6 +32,7 @@ class M3U8_Ts_Estimator:
self.ts_file_sizes = []
self.now_downloaded_size = 0
self.total_segments = total_segments
self.segments_instance = segments_instance
self.lock = threading.Lock()
self.speed = {"upload": "N/A", "download": "N/A"}
@ -102,7 +103,6 @@ class M3U8_Ts_Estimator:
return "Error"
def update_progress_bar(self, total_downloaded: int, duration: float, progress_counter: tqdm) -> None:
"""Updates the progress bar with download information."""
try:
self.add_ts_file(total_downloaded * self.total_segments, total_downloaded, duration)
@ -120,21 +120,25 @@ class M3U8_Ts_Estimator:
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 = ""
# Retrieve retry count from segments_instance
retry_count = self.segments_instance.active_retries if self.segments_instance else 0
progress_str = (
f"{Colors.WHITE}[ {Colors.GREEN}{number_file_downloaded} {Colors.WHITE}< "
f"{Colors.GREEN}{number_file_total_size} {Colors.RED}{units_file_total_size} "
f"{Colors.WHITE}| {Colors.CYAN}{average_internet_speed} {Colors.RED}{average_internet_unit}"
f"{Colors.WHITE}| {Colors.CYAN}{average_internet_speed} {Colors.RED}{average_internet_unit} "
f"{Colors.WHITE}| {Colors.GREEN}CRR {Colors.RED}{retry_count}"
)
else:
# Retrieve retry count from segments_instance
retry_count = self.segments_instance.active_retries if self.segments_instance else 0
progress_str = (
f"{Colors.WHITE}[ {Colors.GREEN}{number_file_downloaded} {Colors.WHITE}< "
f"{Colors.GREEN}{number_file_total_size} {Colors.RED}{units_file_total_size}"
f"{Colors.GREEN}{number_file_total_size} {Colors.RED}{units_file_total_size} "
f"{Colors.WHITE}| {Colors.GREEN}CRR {Colors.RED}{retry_count}"
)
progress_counter.set_postfix_str(progress_str)

View File

@ -17,7 +17,7 @@ class RequestManager:
if not hasattr(self, 'initialized'):
self.json_file = json_file
self.initialized = True
self.on_response_callback = None # Aggiungi un campo per il callback
self.on_response_callback = None
def create_request(self, type: str) -> str:
request_data = {

View File

@ -6,40 +6,26 @@ import json
session_data = {}
def set_session(value):
"""
Salva lo script_id nella sessione.
"""
session_data['script_id'] = value
def get_session():
"""
Restituisce lo script_id dalla sessione, o 'unknown' se non presente.
"""
return session_data.get('script_id', 'unknown')
def update_script_id(screen_id, titolo):
"""
Aggiorna il titolo di uno script in base allo screen_id.
"""
def updateScriptId(screen_id, titolo):
json_file = "scripts.json"
try:
# Apre il file JSON e carica i dati
with open(json_file, 'r') as f:
scripts_data = json.load(f)
except FileNotFoundError:
scripts_data = []
# Cerca lo script con lo screen_id corrispondente
# cerco lo script con lo screen_id
for script in scripts_data:
if script["screen_id"] == screen_id:
# Se trovato, aggiorna il titolo
# se trovo il match, aggiorno il titolo
script["titolo"] = titolo
# Salva i dati aggiornati nel file JSON
# aggiorno il file json
with open(json_file, 'w') as f:
json.dump(scripts_data, f, indent=4)
@ -47,26 +33,20 @@ def update_script_id(screen_id, titolo):
print(f"Screen_id {screen_id} non trovato.")
def delete_script_id(screen_id):
"""
Elimina uno script in base allo screen_id.
"""
def deleteScriptId(screen_id):
json_file = "scripts.json"
try:
with open(json_file, 'r') as f:
scripts_data = json.load(f)
except FileNotFoundError:
scripts_data = []
# Cerca lo script con lo screen_id corrispondente
for script in scripts_data:
if script["screen_id"] == screen_id:
# Se trovato, rimuove lo script
# se trovo il match, elimino lo script
scripts_data.remove(script)
# aggiorno il file json
with open(json_file, 'w') as f:
json.dump(scripts_data, f, indent=4)

View File

@ -558,19 +558,4 @@ class TelegramBot:
def get_bot_instance():
return TelegramBot.get_instance()
# Esempio di utilizzo
if __name__ == "__main__":
# Usa le variabili
token = os.getenv("TOKEN_TELEGRAM")
authorized_user_id = os.getenv("AUTHORIZED_USER_ID")
TOKEN = token
AUTHORIZED_USER_ID = int(authorized_user_id)
# Inizializza il bot
bot = TelegramBot.init_bot(TOKEN, AUTHORIZED_USER_ID)
bot.run()
return TelegramBot.get_instance()

View File

@ -25,8 +25,7 @@ def update():
"""
Check for updates on GitHub and display relevant information.
"""
console.print("[green]Checking GitHub version [white]...")
console.print("\n[cyan]→ [green]Checking GitHub version ...")
# Make the GitHub API requests and handle potential errors
try:
@ -59,7 +58,7 @@ def update():
if str(__version__).replace('v', '') != str(last_version).replace('v', '') :
console.print(f"[red]New version available: [yellow]{last_version} \n")
else:
console.print(f" [yellow]Everything is up to date \n")
console.print(f" [red]Everything is up to date \n")
console.print(f"[red]{__title__} has been downloaded [yellow]{total_download_count} [red]times, but only [yellow]{percentual_stars}% [red]of users have starred it.\n\
[cyan]Help the repository grow today by leaving a [yellow]star [cyan]and [yellow]sharing [cyan]it with others online!")

View File

@ -492,7 +492,7 @@ class OsSummary:
else:
logging.info(f"Library: {installed_version}")
console.print(f"[cyan]Libraries[white]: [bold red]{', '.join([self.get_library_version(lib) for lib in optional_libraries])}[/bold red]\n")
#console.print(f"[cyan]Libraries[white]: [bold red]{', '.join([self.get_library_version(lib) for lib in optional_libraries])}[/bold red]\n")
logging.info(f"Libraries: {', '.join([self.get_library_version(lib) for lib in optional_libraries])}")

View File

@ -20,6 +20,6 @@ from StreamingCommunity.Lib.Downloader import HLS_Downloader
start_message()
logger = Logger()
print("Return: ", HLS_Downloader(
output_filename="test.mp4",
m3u8_playlist="https://acdn.ak-stream-videoplatform.sky.it/hls/2024/11/21/968275/master.m3u8"
output_path="test.mp4",
m3u8_url="https://acdn.ak-stream-videoplatform.sky.it/hls/2024/11/21/968275/master.m3u8"
).start())

View File

@ -3,7 +3,7 @@
"debug": false,
"log_file": "app.log",
"log_to_file": false,
"show_message": false,
"show_message": true,
"clean_console": true,
"root_path": "Video",
"movie_folder_name": "Movie",
@ -28,7 +28,7 @@
"proxy_start_max": 0.5
},
"M3U8_DOWNLOAD": {
"tqdm_delay": 0.15,
"tqdm_delay": 0.12,
"default_video_workser": 12,
"default_audio_workser": 12,
"merge_audio": true,

View File

@ -2,13 +2,20 @@
import sys
from StreamingCommunity.run import main
from StreamingCommunity.Util._jsonConfig import config_manager
from StreamingCommunity.TelegramHelp.request_manager import RequestManager
from StreamingCommunity.TelegramHelp.session import set_session
# Svuoto il file
request_manager = RequestManager()
request_manager.clear_file()
script_id = sys.argv[1] if len(sys.argv) > 1 else "unknown"
TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot')
set_session(script_id)
main(script_id)
if TELEGRAM_BOT:
request_manager = RequestManager()
request_manager.clear_file()
script_id = sys.argv[1] if len(sys.argv) > 1 else "unknown"
set_session(script_id)
main(script_id)
else:
main(0)