mirror of
https://github.com/Arrowar/StreamingCommunity.git
synced 2025-06-07 12:05:35 +00:00
Fix shorter video and small bar, remove ctrl+c.
This commit is contained in:
parent
2abfe82815
commit
2960b810cd
@ -1,5 +1,6 @@
|
||||
# 01.03.24
|
||||
|
||||
import sys
|
||||
import logging
|
||||
from urllib.parse import urljoin, urlparse, parse_qs, urlencode, urlunparse
|
||||
|
||||
@ -11,6 +12,7 @@ from bs4 import BeautifulSoup
|
||||
|
||||
# Internal utilities
|
||||
from Src.Util.headers import get_headers
|
||||
from Src.Util.console import console
|
||||
from Src.Util._jsonConfig import config_manager
|
||||
|
||||
|
||||
@ -204,4 +206,4 @@ class VideoSource:
|
||||
new_url = m._replace(query=new_query) # Replace the old query string with the new one
|
||||
final_url = urlunparse(new_url) # Construct the final URL from the modified parts
|
||||
|
||||
return final_url
|
||||
return final_url
|
||||
|
@ -202,7 +202,6 @@ class VideoSource:
|
||||
logging.error(f"Error getting content: {e}")
|
||||
raise
|
||||
|
||||
|
||||
def get_playlist(self) -> str:
|
||||
"""
|
||||
Get playlist.
|
||||
@ -240,4 +239,4 @@ class VideoSource:
|
||||
new_url = m._replace(query=new_query) # Replace the old query string with the new one
|
||||
final_url = urlunparse(new_url) # Construct the final URL from the modified parts
|
||||
|
||||
return final_url
|
||||
return final_url
|
||||
|
@ -4,4 +4,7 @@ STREAMING_FOLDER = "streamingcommunity"
|
||||
MOVIE_FOLDER = "Movie"
|
||||
SERIES_FOLDER = "Serie"
|
||||
|
||||
SERVER_IP = ['162.19.231.20', '162.19.255.224', '162.19.254.232', '162.19.254.230', '51.195.107.230', '162.19.255.36', '162.19.228.128', '51.195.107.7', '162.19.253.242', '141.95.0.248', '57.129.4.77', '57.129.7.85']
|
||||
SERVER_IP = ['162.19.255.224', '162.19.255.223', '162.19.254.244', '162.19.254.232', '162.19.254.230',
|
||||
'162.19.253.242', '162.19.249.48', '162.19.245.142', '162.19.231.20', '162.19.229.177',
|
||||
'162.19.228.128', '162.19.228.127', '162.19.228.105', '141.95.1.32', '141.95.1.196',
|
||||
'141.95.1.102', '141.95.0.50', '141.95.0.248', '135.125.237.84', '135.125.233.236']
|
@ -40,6 +40,10 @@ def capture_output(process: subprocess.Popen, description: str) -> None:
|
||||
|
||||
logging.info(f"FFMPEG line: {line}")
|
||||
|
||||
# Capture only error
|
||||
if "rror" in str(line):
|
||||
console.log(f"[red]FFMPEG: {str(line).strip()}")
|
||||
|
||||
# Check if termination is requested
|
||||
if terminate_flag.is_set():
|
||||
break
|
||||
|
@ -19,14 +19,18 @@ except: pass
|
||||
from Src.Util._jsonConfig import config_manager
|
||||
from Src.Util.os import check_file_existence, suppress_output
|
||||
from Src.Util.console import console
|
||||
from .util import has_audio_stream, need_to_force_to_ts, check_ffmpeg_input
|
||||
from .util import has_audio_stream, need_to_force_to_ts, check_ffmpeg_input, check_duration_v_a
|
||||
from .capture import capture_ffmpeg_real_time
|
||||
from ..M3U8.parser import M3U8_Codec
|
||||
|
||||
|
||||
# Config
|
||||
DEBUG_MODE = config_manager.get_bool("DEFAULT", "debug")
|
||||
DEBUG_FFMPEG = "debug" if DEBUG_MODE else "error"
|
||||
USE_CODECS = config_manager.get_bool("M3U8_CONVERSION", "use_codec")
|
||||
USE_CODEC = config_manager.get_bool("M3U8_CONVERSION", "use_codec")
|
||||
USE_VCODEC = config_manager.get_bool("M3U8_CONVERSION", "use_vcodec")
|
||||
USE_ACODEC = config_manager.get_bool("M3U8_CONVERSION", "use_acodec")
|
||||
USE_BITRATE = config_manager.get_bool("M3U8_CONVERSION", "use_bitrate")
|
||||
USE_GPU = config_manager.get_bool("M3U8_CONVERSION", "use_gpu")
|
||||
FFMPEG_DEFAULT_PRESET = config_manager.get("M3U8_CONVERSION", "default_preset")
|
||||
CHECK_OUTPUT_CONVERSION = config_manager.get_bool("M3U8_CONVERSION", "check_output_after_ffmpeg")
|
||||
@ -263,7 +267,7 @@ def __transcode_with_subtitles(video: str, subtitles_list: List[Dict[str, str]],
|
||||
|
||||
|
||||
# --> v 1.1 (new)
|
||||
def join_video(video_path: str, out_path: str, vcodec: str = None, acodec: str = None, bitrate: str = None):
|
||||
def join_video(video_path: str, out_path: str, codec: M3U8_Codec = None):
|
||||
|
||||
"""
|
||||
Joins single ts video file to mp4
|
||||
@ -281,12 +285,12 @@ def join_video(video_path: str, out_path: str, vcodec: str = None, acodec: str =
|
||||
logging.error("Missing input video for ffmpeg conversion.")
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
# Start command
|
||||
ffmpeg_cmd = ['ffmpeg']
|
||||
|
||||
# Enabled the use of gpu
|
||||
ffmpeg_cmd.extend(['-hwaccel', 'cuda', '-hwaccel_output_format', 'cuda'])
|
||||
if USE_GPU:
|
||||
ffmpeg_cmd.extend(['-hwaccel', 'cuda', '-hwaccel_output_format', 'cuda'])
|
||||
|
||||
# Add mpegts to force to detect input file as ts file
|
||||
if need_to_force_to_ts(video_path):
|
||||
@ -294,15 +298,30 @@ def join_video(video_path: str, out_path: str, vcodec: str = None, acodec: str =
|
||||
ffmpeg_cmd.extend(['-f', 'mpegts'])
|
||||
vcodec = "libx264"
|
||||
|
||||
|
||||
# Insert input video path
|
||||
ffmpeg_cmd.extend(['-i', video_path])
|
||||
|
||||
# Add output args
|
||||
if USE_CODECS:
|
||||
if vcodec: ffmpeg_cmd.extend(['-c:v', vcodec])
|
||||
if acodec: ffmpeg_cmd.extend(['-c:a', acodec])
|
||||
if bitrate: ffmpeg_cmd.extend(['-b:a', str(bitrate)])
|
||||
if USE_CODEC:
|
||||
if USE_VCODEC:
|
||||
if codec.video_codec_name:
|
||||
if not USE_GPU:
|
||||
ffmpeg_cmd.extend(['-c:v', codec.video_codec_name])
|
||||
else:
|
||||
ffmpeg_cmd.extend(['-c:v', 'h264_nvenc'])
|
||||
else:
|
||||
console.log("[red]Cant find vcodec for 'join_audios'")
|
||||
|
||||
if USE_ACODEC:
|
||||
if codec.audio_codec_name:
|
||||
ffmpeg_cmd.extend(['-c:a', codec.audio_codec_name])
|
||||
else:
|
||||
console.log("[red]Cant find acodec for 'join_audios'")
|
||||
|
||||
if USE_BITRATE:
|
||||
ffmpeg_cmd.extend(['-b:v', f'{codec.video_bitrate // 1000}k'])
|
||||
ffmpeg_cmd.extend(['-b:a', f'{codec.audio_bitrate // 1000}k'])
|
||||
|
||||
else:
|
||||
ffmpeg_cmd.extend(['-c', 'copy'])
|
||||
|
||||
@ -312,12 +331,10 @@ def join_video(video_path: str, out_path: str, vcodec: str = None, acodec: str =
|
||||
else:
|
||||
ffmpeg_cmd.extend(['-preset', 'fast'])
|
||||
|
||||
|
||||
# Overwrite
|
||||
ffmpeg_cmd += [out_path, "-y"]
|
||||
logging.info(f"FFmpeg command: {ffmpeg_cmd}")
|
||||
|
||||
|
||||
# Run join
|
||||
if DEBUG_MODE:
|
||||
subprocess.run(ffmpeg_cmd, check=True)
|
||||
@ -333,8 +350,6 @@ def join_video(video_path: str, out_path: str, vcodec: str = None, acodec: str =
|
||||
capture_ffmpeg_real_time(ffmpeg_cmd, "[cyan]Join video")
|
||||
print()
|
||||
|
||||
|
||||
|
||||
# Check file output
|
||||
if CHECK_OUTPUT_CONVERSION:
|
||||
console.log("[red]Check output ffmpeg")
|
||||
@ -347,7 +362,7 @@ def join_video(video_path: str, out_path: str, vcodec: str = None, acodec: str =
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
def join_audios(video_path: str, audio_tracks: List[Dict[str, str]], out_path: str):
|
||||
def join_audios(video_path: str, audio_tracks: List[Dict[str, str]], out_path: str, codec: M3U8_Codec = None):
|
||||
"""
|
||||
Joins audio tracks with a video file using FFmpeg.
|
||||
|
||||
@ -362,9 +377,17 @@ def join_audios(video_path: str, audio_tracks: List[Dict[str, str]], out_path: s
|
||||
logging.error("Missing input video for ffmpeg conversion.")
|
||||
sys.exit(0)
|
||||
|
||||
video_audio_same_duration = check_duration_v_a(video_path, audio_tracks[0].get('path'))
|
||||
|
||||
# Start command
|
||||
ffmpeg_cmd = ['ffmpeg', '-i', video_path]
|
||||
ffmpeg_cmd = ['ffmpeg']
|
||||
|
||||
# Enabled the use of gpu
|
||||
if USE_GPU:
|
||||
ffmpeg_cmd.extend(['-hwaccel', 'cuda', '-hwaccel_output_format', 'cuda'])
|
||||
|
||||
# Insert input video path
|
||||
ffmpeg_cmd.extend(['-i', video_path])
|
||||
|
||||
# Add audio tracks as input
|
||||
for i, audio_track in enumerate(audio_tracks):
|
||||
@ -373,7 +396,6 @@ def join_audios(video_path: str, audio_tracks: List[Dict[str, str]], out_path: s
|
||||
else:
|
||||
logging.error(f"Skip audio join: {audio_track.get('path')} dont exist")
|
||||
|
||||
|
||||
# Map the video and audio streams
|
||||
ffmpeg_cmd.append('-map')
|
||||
ffmpeg_cmd.append('0:v') # Map video stream from the first input (video_path)
|
||||
@ -382,18 +404,45 @@ def join_audios(video_path: str, audio_tracks: List[Dict[str, str]], out_path: s
|
||||
ffmpeg_cmd.append('-map')
|
||||
ffmpeg_cmd.append(f'{i}:a') # Map audio streams from subsequent inputs
|
||||
|
||||
|
||||
# Add output args
|
||||
if USE_CODECS:
|
||||
ffmpeg_cmd.extend(['-c:v', 'copy', '-c:a', 'copy'])
|
||||
if USE_CODEC:
|
||||
if USE_VCODEC:
|
||||
if codec.video_codec_name:
|
||||
if not USE_GPU:
|
||||
ffmpeg_cmd.extend(['-c:v', codec.video_codec_name])
|
||||
else:
|
||||
ffmpeg_cmd.extend(['-c:v', 'h264_nvenc'])
|
||||
else:
|
||||
console.log("[red]Cant find vcodec for 'join_audios'")
|
||||
|
||||
if USE_ACODEC:
|
||||
if codec.audio_codec_name:
|
||||
ffmpeg_cmd.extend(['-c:a', codec.audio_codec_name])
|
||||
else:
|
||||
console.log("[red]Cant find acodec for 'join_audios'")
|
||||
|
||||
if USE_BITRATE:
|
||||
ffmpeg_cmd.extend(['-b:v', f'{codec.video_bitrate // 1000}k'])
|
||||
ffmpeg_cmd.extend(['-b:a', f'{codec.audio_bitrate // 1000}k'])
|
||||
|
||||
else:
|
||||
ffmpeg_cmd.extend(['-c', 'copy'])
|
||||
|
||||
# Ultrafast preset always or fast for gpu
|
||||
if not USE_GPU:
|
||||
ffmpeg_cmd.extend(['-preset', FFMPEG_DEFAULT_PRESET])
|
||||
else:
|
||||
ffmpeg_cmd.extend(['-preset', 'fast'])
|
||||
|
||||
# Use shortest input path for video and audios
|
||||
if not video_audio_same_duration:
|
||||
console.log("[red]Use shortest input.")
|
||||
ffmpeg_cmd.extend(['-shortest', '-strict', 'experimental'])
|
||||
|
||||
# Overwrite
|
||||
ffmpeg_cmd += [out_path, "-y"]
|
||||
logging.info(f"FFmpeg command: {ffmpeg_cmd}")
|
||||
|
||||
|
||||
# Run join
|
||||
if DEBUG_MODE:
|
||||
subprocess.run(ffmpeg_cmd, check=True)
|
||||
@ -409,7 +458,6 @@ 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()
|
||||
|
||||
|
||||
# Check file output
|
||||
if CHECK_OUTPUT_CONVERSION:
|
||||
console.log("[red]Check output ffmpeg")
|
||||
@ -456,7 +504,7 @@ def join_subtitle(video_path: str, subtitles_list: List[Dict[str, str]], out_pat
|
||||
ffmpeg_cmd += ["-metadata:s:s:{}".format(idx), "title={}".format(subtitle['name'])]
|
||||
|
||||
# Add output args
|
||||
if USE_CODECS:
|
||||
if USE_CODEC:
|
||||
ffmpeg_cmd.extend(['-c:v', 'copy', '-c:a', 'copy', '-c:s', 'mov_text'])
|
||||
else:
|
||||
ffmpeg_cmd.extend(['-c', 'copy', '-c:s', 'mov_text'])
|
||||
|
@ -92,7 +92,7 @@ def format_duration(seconds: float) -> Tuple[int, int, int]:
|
||||
return int(hours), int(minutes), int(seconds)
|
||||
|
||||
|
||||
def print_duration_table(file_path: str) -> None:
|
||||
def print_duration_table(file_path: str, show = True) -> None:
|
||||
"""
|
||||
Print duration of a video file in hours, minutes, and seconds.
|
||||
|
||||
@ -104,7 +104,10 @@ def print_duration_table(file_path: str) -> None:
|
||||
|
||||
if video_duration is not None:
|
||||
hours, minutes, seconds = format_duration(video_duration)
|
||||
console.log(f"[cyan]Duration for [white]([green]{os.path.basename(file_path)}[white]): [yellow]{int(hours)}[red]h [yellow]{int(minutes)}[red]m [yellow]{int(seconds)}[red]s")
|
||||
if show:
|
||||
console.print(f"[cyan]Duration for [white]([green]{os.path.basename(file_path)}[white]): [yellow]{int(hours)}[red]h [yellow]{int(minutes)}[red]m [yellow]{int(seconds)}[red]s")
|
||||
else:
|
||||
return f"[yellow]{int(hours)}[red]h [yellow]{int(minutes)}[red]m [yellow]{int(seconds)}[red]s"
|
||||
|
||||
|
||||
def get_ffprobe_info(file_path):
|
||||
@ -210,3 +213,24 @@ def check_ffmpeg_input(input_file):
|
||||
except Exception as e:
|
||||
logging.error(f"An unexpected error occurred: {e}")
|
||||
return False
|
||||
|
||||
def check_duration_v_a(video_path, audio_path):
|
||||
"""
|
||||
Check if the duration of the video and audio matches.
|
||||
|
||||
Args:
|
||||
- video_path (str): Path to the video file.
|
||||
- audio_path (str): Path to the audio file.
|
||||
|
||||
Returns:
|
||||
- bool: True if the duration of the video and audio matches, False otherwise.
|
||||
"""
|
||||
|
||||
# Ottieni la durata del video
|
||||
video_duration = get_video_duration(video_path)
|
||||
|
||||
# Ottieni la durata dell'audio
|
||||
audio_duration = get_video_duration(audio_path)
|
||||
|
||||
# Verifica se le durate corrispondono
|
||||
return video_duration == audio_duration
|
@ -178,7 +178,7 @@ class Downloader():
|
||||
|
||||
# Check if there is some audios, else disable download
|
||||
if self.list_available_audio != None:
|
||||
console.log(f"[cyan]Find audios [white]=> [red]{[obj_audio.get('language') for obj_audio in self.list_available_audio]}")
|
||||
console.print(f"[cyan]Find audios [white]=> [red]{[obj_audio.get('language') for obj_audio in self.list_available_audio]}")
|
||||
else:
|
||||
console.log("[red]Cant find a list of audios")
|
||||
|
||||
@ -191,7 +191,7 @@ class Downloader():
|
||||
|
||||
# Check if there is some subtitles, else disable download
|
||||
if self.list_available_subtitles != None:
|
||||
console.log(f"[cyan]Find subtitles [white]=> [red]{[obj_sub.get('language') for obj_sub in self.list_available_subtitles]}")
|
||||
console.print(f"[cyan]Find subtitles [white]=> [red]{[obj_sub.get('language') for obj_sub in self.list_available_subtitles]}")
|
||||
else:
|
||||
console.log("[red]Cant find a list of audios")
|
||||
|
||||
@ -208,7 +208,7 @@ class Downloader():
|
||||
logging.info(f"M3U8 index select: {self.m3u8_index}, with resolution: {video_res}")
|
||||
|
||||
# Get URI of the best quality and codecs parameters
|
||||
console.log(f"[cyan]Find resolution [white]=> [red]{sorted(list_available_resolution, reverse=True)}")
|
||||
console.print(f"[cyan]Find resolution [white]=> [red]{sorted(list_available_resolution, reverse=True)}")
|
||||
|
||||
# Fix URL if it is not complete with http:\\site_name.domain\...
|
||||
if "http" not in self.m3u8_index:
|
||||
@ -219,7 +219,7 @@ class Downloader():
|
||||
|
||||
# Check if a valid HTTPS URL is obtained
|
||||
if self.m3u8_index is not None and "https" in self.m3u8_index:
|
||||
console.log(f"[cyan]Found m3u8 index [white]=> [red]{self.m3u8_index}")
|
||||
console.print(f"[cyan]Found m3u8 index [white]=> [red]{self.m3u8_index}")
|
||||
else:
|
||||
logging.error("[download_m3u8] Can't find a valid m3u8 index")
|
||||
raise
|
||||
@ -229,7 +229,8 @@ class Downloader():
|
||||
logging.info(f"Find codec: {self.codec}")
|
||||
|
||||
if self.codec is not None:
|
||||
console.log(f"[cyan]Find codec [white]=> ([green]'v'[white]: [yellow]{self.codec.video_codec_name}[white], [green]'a'[white]: [yellow]{self.codec.audio_codec_name}[white], [green]'b'[white]: [yellow]{self.codec.bandwidth})")
|
||||
console.print(f"[cyan]Find codec [white]=> ([green]'v'[white]: [yellow]{self.codec.video_codec_name}[white] ([green]b[white]: [yellow]{self.codec.video_bitrate // 1000}k[white]), [green]'a'[white]: [yellow]{self.codec.audio_codec_name}[white] ([green]b[white]: [yellow]{self.codec.audio_bitrate // 1000}k[white]))")
|
||||
|
||||
|
||||
def __donwload_video__(self, server_ip: list = None):
|
||||
"""
|
||||
@ -263,6 +264,9 @@ class Downloader():
|
||||
# Download the video segments
|
||||
video_m3u8.download_streams(f"{Colors.MAGENTA}video")
|
||||
|
||||
# Get time of output file
|
||||
print_duration_table(os.path.join(full_path_video, "0.ts"))
|
||||
|
||||
else:
|
||||
console.log("[cyan]Video [red]already exists.")
|
||||
|
||||
@ -309,6 +313,9 @@ class Downloader():
|
||||
# Download the audio segments
|
||||
audio_m3u8.download_streams(f"{Colors.MAGENTA}audio {Colors.RED}{obj_audio.get('language')}")
|
||||
|
||||
# Get time of output file
|
||||
print_duration_table(os.path.join(full_path_audio, "0.ts"))
|
||||
|
||||
else:
|
||||
console.log(f"[cyan]Audio [white]([green]{obj_audio.get('language')}[white]) [red]already exists.")
|
||||
|
||||
@ -373,14 +380,14 @@ class Downloader():
|
||||
)
|
||||
|
||||
# Initiate the download of the subtitle content
|
||||
console.log(f"[cyan]Downloading subtitle: [red]{sub_language.lower()}")
|
||||
console.print(f"[cyan]Downloading subtitle: [red]{sub_language.lower()}")
|
||||
futures.append(executor.submit(self.__save_subtitle_content, m3u8_sub_parser.subtitle[-1], sub_full_path))
|
||||
|
||||
# Wait for all downloads to finish
|
||||
for future in futures:
|
||||
future.result()
|
||||
|
||||
def __join_video__(self, vcodec = 'copy') -> str:
|
||||
def __join_video__(self) -> str:
|
||||
"""
|
||||
Join downloaded video segments into a single video file.
|
||||
|
||||
@ -397,10 +404,9 @@ class Downloader():
|
||||
join_video(
|
||||
video_path = self.downloaded_video[0].get('path'),
|
||||
out_path = path_join_video,
|
||||
vcodec = vcodec
|
||||
codec = self.codec
|
||||
)
|
||||
|
||||
print_duration_table(path_join_video)
|
||||
return path_join_video
|
||||
|
||||
def __join_video_audio__(self) -> str:
|
||||
@ -420,10 +426,10 @@ class Downloader():
|
||||
join_audios(
|
||||
video_path = self.downloaded_video[0].get('path'),
|
||||
audio_tracks = self.downloaded_audio,
|
||||
out_path = path_join_video_audio
|
||||
out_path = path_join_video_audio,
|
||||
codec = self.codec
|
||||
)
|
||||
|
||||
print_duration_table(path_join_video_audio)
|
||||
return path_join_video_audio
|
||||
|
||||
def __join_video_subtitles__(self, input_path: str) -> str:
|
||||
@ -449,7 +455,6 @@ class Downloader():
|
||||
path_join_video_subtitle
|
||||
)
|
||||
|
||||
print_duration_table(path_join_video_subtitle)
|
||||
return path_join_video_subtitle
|
||||
|
||||
def __clean__(self, out_path: str) -> None:
|
||||
@ -473,7 +478,11 @@ class Downloader():
|
||||
os.rename(out_path, self.output_filename)
|
||||
|
||||
# Print size of the file
|
||||
console.print(Panel(f"[bold green]Download completed![/bold green]\nFile size: [bold red]{format_size(os.path.getsize(self.output_filename))}[/bold red]", title=f"{os.path.basename(self.output_filename.replace('.mp4', ''))}", border_style="green"))
|
||||
console.print(Panel(
|
||||
f"[bold green]Download completed![/bold green]\n"
|
||||
f"File size: [bold red]{format_size(os.path.getsize(self.output_filename))}[/bold red]\n"
|
||||
f"Duration: [bold]{print_duration_table(self.output_filename, show=False)}[/bold]",
|
||||
title=f"{os.path.basename(self.output_filename.replace('.mp4', ''))}", border_style="green"))
|
||||
|
||||
# Delete all files except the output file
|
||||
delete_files_except_one(self.base_path, os.path.basename(self.output_filename))
|
||||
@ -535,7 +544,7 @@ class Downloader():
|
||||
there_is_video: bool = (len(self.downloaded_video) > 0)
|
||||
there_is_audio: bool = (len(self.downloaded_audio) > 0)
|
||||
there_is_subtitle: bool = (len(self.downloaded_subtitle) > 0)
|
||||
console.log(f"[cyan]Conversion [white]=> ([green]Audio: [yellow]{there_is_audio}[white], [green]Subtitle: [yellow]{there_is_subtitle}[white])")
|
||||
console.print(f"[cyan]Conversion [white]=> ([green]Audio: [yellow]{there_is_audio}[white], [green]Subtitle: [yellow]{there_is_subtitle}[white])")
|
||||
|
||||
|
||||
# Join audio and video
|
||||
|
@ -31,7 +31,6 @@ from ..M3U8 import (
|
||||
M3U8_UrlFix
|
||||
)
|
||||
|
||||
|
||||
# Config
|
||||
TQDM_MAX_WORKER = config_manager.get_int('M3U8_DOWNLOAD', 'tdqm_workers')
|
||||
TQDM_USE_LARGE_BAR = config_manager.get_int('M3U8_DOWNLOAD', 'tqdm_use_large_bar')
|
||||
@ -122,7 +121,7 @@ class M3U8_Segments:
|
||||
m3u8_parser = M3U8_Parser()
|
||||
m3u8_parser.parse_data(uri=self.url, raw_content=m3u8_content) # Parse the content of the M3U8 playlist
|
||||
|
||||
console.log(f"[cyan]There is key: [yellow]{m3u8_parser.keys is not None}")
|
||||
console.print(f"[red]There is key: [yellow]{m3u8_parser.keys is not None}")
|
||||
|
||||
# Check if there is an encryption key in the playlis
|
||||
if m3u8_parser.keys is not None:
|
||||
@ -166,6 +165,7 @@ class M3U8_Segments:
|
||||
|
||||
# Update segments for estimator
|
||||
self.class_ts_estimator.total_segments = len(self.segments)
|
||||
logging.info(f"fSegmnets to donwload: [{len(self.segments)}]")
|
||||
|
||||
def get_info(self) -> None:
|
||||
"""
|
||||
@ -220,12 +220,13 @@ class M3U8_Segments:
|
||||
|
||||
# Generate new user agent
|
||||
headers_segments['user-agent'] = get_headers()
|
||||
logging.info(f"Make request to get segmenet: [{index} - {len(self.segments)}]")
|
||||
|
||||
try:
|
||||
|
||||
# Make request and calculate time duration
|
||||
start_time = time.time()
|
||||
response = requests.get(ts_url, headers=headers_segments, verify_ssl=REQUEST_VERIFY_SSL)
|
||||
response = requests.get(ts_url, headers=headers_segments, verify=REQUEST_VERIFY_SSL, timeout=30)
|
||||
duration = time.time() - start_time
|
||||
|
||||
if response.ok:
|
||||
@ -267,7 +268,7 @@ class M3U8_Segments:
|
||||
while not stop_event.is_set() or not self.segment_queue.empty():
|
||||
with self.condition:
|
||||
while self.segment_queue.empty() and not stop_event.is_set():
|
||||
self.condition.wait(timeout=1) # Wait until a new segment is available or stop_event is set
|
||||
self.condition.wait() # Wait until a new segment is available or stop_event is set
|
||||
|
||||
if stop_event.is_set():
|
||||
break
|
||||
@ -311,16 +312,6 @@ class M3U8_Segments:
|
||||
mininterval=0.01
|
||||
)
|
||||
|
||||
def signal_handler(sig, frame):
|
||||
self.ctrl_c_detected = True # Set global variable to indicate Ctrl+C detection
|
||||
|
||||
stop_event.set()
|
||||
with self.condition:
|
||||
self.condition.notify_all() # Wake up the writer thread if it's waiting
|
||||
|
||||
# Register the signal handler for Ctrl+C
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
|
||||
with ThreadPoolExecutor(max_workers=TQDM_MAX_WORKER) as executor:
|
||||
|
||||
# Start a separate thread to write segments to the file
|
||||
@ -330,24 +321,13 @@ class M3U8_Segments:
|
||||
# Delay the start of each worker
|
||||
for index, segment_url in enumerate(self.segments):
|
||||
|
||||
# Check for Ctrl+C before starting each download task
|
||||
time.sleep(0.03)
|
||||
|
||||
if self.ctrl_c_detected:
|
||||
console.log("[red]Ctrl+C detected. Stopping further downloads.")
|
||||
|
||||
stop_event.set()
|
||||
with self.condition:
|
||||
self.condition.notify_all() # Wake up the writer thread if it's waiting
|
||||
|
||||
break
|
||||
|
||||
# Submit the download task to the executor
|
||||
executor.submit(self.make_requests_stream, segment_url, index, stop_event, progress_bar)
|
||||
|
||||
# Wait for all segments to be downloaded
|
||||
executor.shutdown(wait=True)
|
||||
executor.shutdown()
|
||||
stop_event.set() # Set the stop event to halt the writer thread
|
||||
with self.condition:
|
||||
self.condition.notify_all() # Wake up the writer thread if it's waiting
|
||||
writer_thread.join() # Wait for the writer thread to finish
|
||||
|
||||
|
@ -48,10 +48,6 @@ RESOLUTIONS = [
|
||||
|
||||
|
||||
class M3U8_Codec:
|
||||
"""
|
||||
Represents codec information for an M3U8 playlist.
|
||||
"""
|
||||
|
||||
def __init__(self, bandwidth, codecs):
|
||||
"""
|
||||
Initializes the M3U8Codec object with the provided parameters.
|
||||
@ -64,20 +60,23 @@ class M3U8_Codec:
|
||||
self.codecs = codecs
|
||||
self.audio_codec = None
|
||||
self.video_codec = None
|
||||
self.video_codec_name = None
|
||||
self.audio_codec_name = None
|
||||
self.extract_codecs()
|
||||
self.parse_codecs()
|
||||
self.calculate_bitrates()
|
||||
|
||||
def extract_codecs(self):
|
||||
"""
|
||||
Parses the codecs information to extract audio and video codecs.
|
||||
Extracted codecs are set as attributes: audio_codec and video_codec.
|
||||
"""
|
||||
|
||||
# Split the codecs string by comma
|
||||
try:
|
||||
# Split the codecs string by comma
|
||||
codecs_list = self.codecs.split(',')
|
||||
except Exception as e:
|
||||
logging.error(f"Cant split codec list: {self.codecs} with error {e}")
|
||||
logging.error(f"Can't split codec list: {self.codecs} with error {e}")
|
||||
return
|
||||
|
||||
# Separate audio and video codecs
|
||||
for codec in codecs_list:
|
||||
@ -87,7 +86,6 @@ class M3U8_Codec:
|
||||
self.audio_codec = codec
|
||||
|
||||
def convert_video_codec(self, video_codec_identifier) -> str:
|
||||
|
||||
"""
|
||||
Convert video codec identifier to codec name.
|
||||
|
||||
@ -97,6 +95,9 @@ class M3U8_Codec:
|
||||
Returns:
|
||||
str: Codec name corresponding to the identifier.
|
||||
"""
|
||||
if not video_codec_identifier:
|
||||
logging.warning("No video codec identifier provided. Using default codec libx264.")
|
||||
return "libx264" # Default
|
||||
|
||||
# Extract codec type from the identifier
|
||||
codec_type = video_codec_identifier.split('.')[0]
|
||||
@ -107,13 +108,11 @@ class M3U8_Codec:
|
||||
|
||||
if codec_name:
|
||||
return codec_name
|
||||
|
||||
else:
|
||||
logging.warning(f"No corresponding video codec found for {video_codec_identifier}. Using default codec libx264.")
|
||||
return "libx264" # Default
|
||||
|
||||
def convert_audio_codec(self, audio_codec_identifier) -> str:
|
||||
return "libx264" # Default
|
||||
|
||||
def convert_audio_codec(self, audio_codec_identifier) -> str:
|
||||
"""
|
||||
Convert audio codec identifier to codec name.
|
||||
|
||||
@ -123,6 +122,9 @@ class M3U8_Codec:
|
||||
Returns:
|
||||
str: Codec name corresponding to the identifier.
|
||||
"""
|
||||
if not audio_codec_identifier:
|
||||
logging.warning("No audio codec identifier provided. Using default codec aac.")
|
||||
return "aac" # Default
|
||||
|
||||
# Extract codec type from the identifier
|
||||
codec_type = audio_codec_identifier.split('.')[0]
|
||||
@ -133,25 +135,33 @@ class M3U8_Codec:
|
||||
|
||||
if codec_name:
|
||||
return codec_name
|
||||
|
||||
else:
|
||||
logging.warning(f"No corresponding audio codec found for {audio_codec_identifier}. Using default codec aac.")
|
||||
return "aac" # Default
|
||||
|
||||
return "aac" # Default
|
||||
|
||||
def parse_codecs(self):
|
||||
"""
|
||||
Parse video and audio codecs.
|
||||
This method updates `video_codec_name` and `audio_codec_name` attributes.
|
||||
"""
|
||||
|
||||
self.video_codec_name = self.convert_video_codec(self.video_codec)
|
||||
self.audio_codec_name = self.convert_audio_codec(self.audio_codec)
|
||||
|
||||
def __str__(self):
|
||||
def calculate_bitrates(self):
|
||||
"""
|
||||
Returns a string representation of the M3U8Codec object.
|
||||
Calculate video and audio bitrates based on the available bandwidth.
|
||||
"""
|
||||
return f"BANDWIDTH={self.bandwidth},RESOLUTION={self.resolution},CODECS=\"{self.codecs}\""
|
||||
if self.bandwidth:
|
||||
|
||||
# Define the video and audio bitrates
|
||||
video_bitrate = int(self.bandwidth * 0.8) # Using 80% of bandwidth for video
|
||||
audio_bitrate = self.bandwidth - video_bitrate
|
||||
|
||||
self.video_bitrate = video_bitrate
|
||||
self.audio_bitrate = audio_bitrate
|
||||
else:
|
||||
logging.warning("No bandwidth provided. Bitrates cannot be calculated.")
|
||||
|
||||
|
||||
|
||||
class M3U8_Video:
|
||||
|
@ -189,7 +189,7 @@ class ManageRequests:
|
||||
timeout: float = HTTP_TIMEOUT,
|
||||
retries: int = HTTP_RETRIES,
|
||||
params: Optional[Dict[str, str]] = None,
|
||||
verify_ssl: bool = True,
|
||||
verify: bool = True,
|
||||
auth: Optional[tuple] = None,
|
||||
proxy: Optional[str] = None,
|
||||
cookies: Optional[Dict[str, str]] = None,
|
||||
@ -206,7 +206,7 @@ class ManageRequests:
|
||||
- timeout (float, optional): The request timeout. Defaults to HTTP_TIMEOUT.
|
||||
- retries (int, optional): The number of retries in case of request failure. Defaults to HTTP_RETRIES.
|
||||
- params (Optional[Dict[str, str]], optional): The query parameters for the request. Defaults to None.
|
||||
- verify_ssl (bool, optional): Indicates whether SSL certificate verification should be performed. Defaults to True.
|
||||
- verify (bool, optional): Indicates whether SSL certificate verification should be performed. Defaults to True.
|
||||
- auth (Optional[tuple], optional): Tuple containing the username and password for basic authentication. Defaults to None.
|
||||
- proxy (Optional[str], optional): The proxy URL. Defaults to None.
|
||||
- cookies (Optional[Dict[str, str]], optional): The cookies to be included in the request. Defaults to None.
|
||||
@ -218,7 +218,7 @@ class ManageRequests:
|
||||
self.timeout = timeout
|
||||
self.retries = retries
|
||||
self.params = params
|
||||
self.verify_ssl = verify_ssl
|
||||
self.verify_ssl = verify
|
||||
self.auth = auth
|
||||
self.proxy = proxy
|
||||
self.cookies = cookies
|
||||
|
@ -16,6 +16,7 @@ import platform
|
||||
import importlib
|
||||
import subprocess
|
||||
import contextlib
|
||||
import urllib.request
|
||||
import importlib.metadata
|
||||
|
||||
from typing import List
|
||||
@ -403,6 +404,19 @@ def convert_to_hex(bytes_data: bytes) -> str:
|
||||
|
||||
|
||||
# --> OS GET SUMMARY
|
||||
def check_internet():
|
||||
while True:
|
||||
try:
|
||||
# Attempt to open a connection to a website to check for internet connection
|
||||
urllib.request.urlopen("http://www.google.com", timeout=1)
|
||||
console.log("[bold green]Internet is available![/bold green]")
|
||||
break
|
||||
|
||||
except urllib.error.URLError:
|
||||
console.log("[bold red]Internet is not available. Waiting...[/bold red]")
|
||||
time.sleep(5)
|
||||
print()
|
||||
|
||||
def get_executable_version(command):
|
||||
try:
|
||||
version_output = subprocess.check_output(command, stderr=subprocess.STDOUT).decode().split('\n')[0]
|
||||
@ -419,7 +433,8 @@ def get_library_version(lib_name):
|
||||
return f"{lib_name}-not installed"
|
||||
|
||||
def get_system_summary():
|
||||
|
||||
|
||||
check_internet()
|
||||
console.print("[bold blue]System Summary[/bold blue][white]:")
|
||||
|
||||
# Python version and platform
|
||||
|
@ -32,7 +32,10 @@
|
||||
},
|
||||
"M3U8_CONVERSION": {
|
||||
"use_codec": false,
|
||||
"use_gpu": false,
|
||||
"use_vcodec": true,
|
||||
"use_acodec": true,
|
||||
"use_bitrate": true,
|
||||
"use_gpu": true,
|
||||
"default_preset": "ultrafast",
|
||||
"check_output_after_ffmpeg": false
|
||||
},
|
||||
|
64
run.py
64
run.py
@ -27,34 +27,6 @@ from Src.Api.Altadefinizione import main_film as altadefinizione_film
|
||||
CLOSE_CONSOLE = config_manager.get_bool('DEFAULT', 'not_close')
|
||||
|
||||
|
||||
def initialize():
|
||||
"""
|
||||
Initialize the application.
|
||||
Checks Python version, removes temporary folder, and displays start message.
|
||||
"""
|
||||
|
||||
# Set terminal size for win 7
|
||||
if platform.system() == "Windows" and "7" in platform.version():
|
||||
os.system('mode 120, 40')
|
||||
|
||||
|
||||
# Check python version
|
||||
if sys.version_info < (3, 7):
|
||||
console.log("[red]Install python version > 3.7.16")
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
# Removing temporary folder
|
||||
start_message()
|
||||
|
||||
|
||||
# Attempting GitHub update
|
||||
"""try:
|
||||
git_update()
|
||||
except Exception as e:
|
||||
console.print(f"[blue]Req github [white]=> [red]Failed: {e}")"""
|
||||
|
||||
|
||||
def run_function(func: Callable[..., None], close_console: bool = False) -> None:
|
||||
"""
|
||||
Run a given function indefinitely or once, depending on the value of close_console.
|
||||
@ -63,9 +35,6 @@ def run_function(func: Callable[..., None], close_console: bool = False) -> None
|
||||
func (Callable[..., None]): The function to run.
|
||||
close_console (bool, optional): Whether to close the console after running the function once. Defaults to False.
|
||||
"""
|
||||
|
||||
initialize()
|
||||
|
||||
if close_console:
|
||||
while 1:
|
||||
func()
|
||||
@ -73,11 +42,40 @@ def run_function(func: Callable[..., None], close_console: bool = False) -> None
|
||||
func()
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
def initialize():
|
||||
"""
|
||||
Initialize the application.
|
||||
Checks Python version, removes temporary folder, and displays start message.
|
||||
"""
|
||||
|
||||
start_message()
|
||||
|
||||
# Create logger
|
||||
log_not = Logger()
|
||||
|
||||
# Get system info
|
||||
get_system_summary()
|
||||
|
||||
# Set terminal size for win 7
|
||||
if platform.system() == "Windows" and "7" in platform.version():
|
||||
os.system('mode 120, 40')
|
||||
|
||||
# Check python version
|
||||
if sys.version_info < (3, 7):
|
||||
console.log("[red]Install python version > 3.7.16")
|
||||
sys.exit(0)
|
||||
|
||||
# Attempting GitHub update
|
||||
try:
|
||||
git_update()
|
||||
except Exception as e:
|
||||
console.print(f"[blue]Req github [white]=> [red]Failed: {e}")
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
initialize()
|
||||
|
||||
# Parse command line arguments
|
||||
parser = argparse.ArgumentParser(description='Script to download film and series from the internet.')
|
||||
parser.add_argument('-sa', '--streaming_anime', action='store_true', help='Check into anime category')
|
||||
|
Loading…
x
Reference in New Issue
Block a user