Add api costant

This commit is contained in:
Ghost 2024-05-26 18:45:37 +02:00
parent c8df23c0f7
commit 387ff64f4f
18 changed files with 229 additions and 97 deletions

View File

@ -73,9 +73,6 @@ You can change some behaviors by tweaking the configuration file.
* tqdm_show_progress: Whether to show progress during downloads or not. * tqdm_show_progress: Whether to show progress during downloads or not.
- Default Value: true - Default Value: true
* fake_proxy: Speed up download for streaming film and series. **Dont work for anime, need to set to FALSE, only streamingcommunity**
- Default Value: false
* create_report: When enabled, this option saves the name of the series or movie being downloaded along with the date and file size in a CSV file, providing a log of downloaded content. * create_report: When enabled, this option saves the name of the series or movie being downloaded along with the date and file size in a CSV file, providing a log of downloaded content.
- Default Value: false - Default Value: false

View File

@ -0,0 +1,4 @@
# 26.05.24
STREAMING_FOLDER = "altadefinizione"
MOVIE_FOLDER = "Movie"

View File

@ -18,8 +18,7 @@ from .Core.Player.supervideo import VideoSource
# Config # Config
ROOT_PATH = config_manager.get('DEFAULT', 'root_path') ROOT_PATH = config_manager.get('DEFAULT', 'root_path')
STREAMING_FOLDER = "altadefinizione" from .costant import STREAMING_FOLDER, MOVIE_FOLDER
MOVIE_FOLDER = "Movie"
# Variable # Variable

View File

@ -18,9 +18,7 @@ from .Core.Util import manage_selection
# Config # Config
ROOT_PATH = config_manager.get('DEFAULT', 'root_path') ROOT_PATH = config_manager.get('DEFAULT', 'root_path')
ANIME_FOLDER = "animeunity" from .costant import ANIME_FOLDER, SERIES_FOLDER, MOVIE_FOLDER
SERIES_FOLDER= "Serie"
MOVIE_FOLDER = "Movie"
# Variable # Variable

View File

@ -0,0 +1,5 @@
# 26.05.24
ANIME_FOLDER = "animeunity"
SERIES_FOLDER= "Serie"
MOVIE_FOLDER = "Movie"

View File

@ -0,0 +1,7 @@
# 26.05.24
STREAMING_FOLDER = "streamingcommunity"
MOVIE_FOLDER = "Movie"
SERIES_FOLDER = "Serie"
SERVER_IP = ["57.129.7.85","57.129.7.188","57.129.7.174","57.129.4.77","57.129.16.196","57.129.16.156","57.129.16.139","57.129.16.135","57.129.13.175","51.38.112.237","51.195.107.7","51.195.107.230"]

View File

@ -18,8 +18,7 @@ from .Core.Vix_player.player import VideoSource
# Config # Config
ROOT_PATH = config_manager.get('DEFAULT', 'root_path') ROOT_PATH = config_manager.get('DEFAULT', 'root_path')
STREAMING_FOLDER = "streamingcommunity" from .costant import STREAMING_FOLDER, MOVIE_FOLDER, SERVER_IP
MOVIE_FOLDER = "Movie"
# Variable # Variable
@ -60,4 +59,4 @@ def download_film(id_film: str, title_name: str, domain: str):
Downloader( Downloader(
m3u8_playlist = master_playlist, m3u8_playlist = master_playlist,
output_filename = os.path.join(mp4_path, mp4_format) output_filename = os.path.join(mp4_path, mp4_format)
).start() ).start(SERVER_IP)

View File

@ -20,8 +20,7 @@ from .Core.Util import manage_selection, map_episode_title
# Config # Config
ROOT_PATH = config_manager.get('DEFAULT', 'root_path') ROOT_PATH = config_manager.get('DEFAULT', 'root_path')
STREAMING_FOLDER = "streamingcommunity" from .costant import STREAMING_FOLDER, SERIES_FOLDER, SERVER_IP
SERIES_FOLDER = "Serie"
# Variable # Variable
@ -97,7 +96,7 @@ def donwload_video(tv_name: str, index_season_selected: int, index_episode_selec
Downloader( Downloader(
m3u8_playlist = master_playlist, m3u8_playlist = master_playlist,
output_filename = os.path.join(mp4_path, mp4_name) output_filename = os.path.join(mp4_path, mp4_name)
).start() ).start(SERVER_IP)
def donwload_episode(tv_name: str, index_season_selected: int, donwload_all: bool = False) -> None: def donwload_episode(tv_name: str, index_season_selected: int, donwload_all: bool = False) -> None:

View File

@ -3,7 +3,7 @@
from .command import ( from .command import (
join_video, join_video,
join_audios, join_audios,
join_subtitle join_subtitle,
) )
from .util import print_duration_table from .util import print_duration_table
from .installer import check_ffmpeg from .installer import check_ffmpeg

View File

@ -16,9 +16,9 @@ except: pass
# Internal utilities # Internal utilities
from Src.Util.os import check_file_existence
from Src.Util._jsonConfig import config_manager from Src.Util._jsonConfig import config_manager
from .util import has_audio_stream from Src.Util.os import check_file_existence
from .util import has_audio_stream, need_to_force_to_ts
from .capture import capture_ffmpeg_real_time from .capture import capture_ffmpeg_real_time
@ -257,7 +257,7 @@ def __transcode_with_subtitles(video: str, subtitles_list: List[Dict[str, str]],
# --> v 1.1 (new) # --> v 1.1 (new)
def join_video(video_path: str, out_path: str, vcodec: str = None, acodec: str = None, bitrate: str = None, force_ts = False): def join_video(video_path: str, out_path: str, vcodec: str = None, acodec: str = None, bitrate: str = None):
""" """
Joins single ts video file to mp4 Joins single ts video file to mp4
@ -281,8 +281,9 @@ def join_video(video_path: str, out_path: str, vcodec: str = None, acodec: str =
ffmpeg_cmd.extend(['-hwaccel', 'cuda', '-hwaccel_output_format', 'cuda']) ffmpeg_cmd.extend(['-hwaccel', 'cuda', '-hwaccel_output_format', 'cuda'])
# Add mpegts to force to detect input file as ts file # Add mpegts to force to detect input file as ts file
if force_ts: if need_to_force_to_ts(video_path):
ffmpeg_cmd.extend(['-f', 'mpegts']) ffmpeg_cmd.extend(['-f', 'mpegts'])
vcodec = "libx264"
# Insert input video path # Insert input video path
ffmpeg_cmd.extend(['-i', video_path]) ffmpeg_cmd.extend(['-i', video_path])

View File

@ -105,3 +105,66 @@ def print_duration_table(file_path: str) -> None:
if video_duration is not None: if video_duration is not None:
hours, minutes, seconds = format_duration(video_duration) 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") 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")
def get_ffprobe_info(file_path):
"""
Get format and codec information for a media file using ffprobe.
Args:
file_path (str): Path to the media file.
Returns:
dict: A dictionary containing the format name and a list of codec names.
"""
try:
result = subprocess.run(
['ffprobe', '-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)
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 subprocess.CalledProcessError as e:
logging.error(f"ffprobe failed for file {file_path}: {e}")
return None
except json.JSONDecodeError as e:
logging.error(f"Failed to parse JSON output from ffprobe for file {file_path}: {e}")
return None
def is_png_format_or_codec(file_info):
"""
Check if the format is 'png_pipe' or if any codec is 'png'.
Args:
file_info (dict): The dictionary containing file information.
Returns:
bool: True if the format is 'png_pipe' or any codec is 'png', otherwise False.
"""
if not file_info:
return False
return file_info['format_name'] == 'png_pipe' or 'png' in file_info['codec_names']
def need_to_force_to_ts(file_path):
"""
Get if a file to TS format if it is in PNG format or contains a PNG codec.
Args:
file_path (str): Path to the input media file.
"""
logging.info(f"Processing file: {file_path}")
file_info = get_ffprobe_info(file_path)
if is_png_format_or_codec(file_info):
return True
return False

View File

@ -46,7 +46,6 @@ from ..E_Table import report_table
DOWNLOAD_SPECIFIC_AUDIO = config_manager.get_list('M3U8_FILTER', 'specific_list_audio') DOWNLOAD_SPECIFIC_AUDIO = config_manager.get_list('M3U8_FILTER', 'specific_list_audio')
DOWNLOAD_SPECIFIC_SUBTITLE = config_manager.get_list('M3U8_FILTER', 'specific_list_subtitles') DOWNLOAD_SPECIFIC_SUBTITLE = config_manager.get_list('M3U8_FILTER', 'specific_list_subtitles')
REMOVE_SEGMENTS_FOLDER = config_manager.get_bool('M3U8_FILTER', 'cleanup_tmp_folder') REMOVE_SEGMENTS_FOLDER = config_manager.get_bool('M3U8_FILTER', 'cleanup_tmp_folder')
FORCE_TS = config_manager.get_dict('M3U8_FILTER', 'force_ts')
FILTER_CUSTOM_REOLUTION = config_manager.get_int('M3U8_PARSER', 'force_resolution') FILTER_CUSTOM_REOLUTION = config_manager.get_int('M3U8_PARSER', 'force_resolution')
CREATE_REPORT = config_manager.get_bool('M3U8_DOWNLOAD', 'create_report') CREATE_REPORT = config_manager.get_bool('M3U8_DOWNLOAD', 'create_report')
@ -80,13 +79,14 @@ class Downloader():
else: else:
folder, base_name = os.path.split(self.output_filename) # Split file_folder output folder, base_name = os.path.split(self.output_filename) # Split file_folder output
base_name = reduce_base_name(remove_special_characters(transliterate(base_name))) # Remove special char base_name = reduce_base_name(remove_special_characters(base_name)) # Remove special char
create_folder(folder) # Create folder and check if exist create_folder(folder) # Create folder and check if exist
if not can_create_file(base_name): # Check if folder file name can be create if not can_create_file(base_name): # Check if folder file name can be create
logging.error("Invalid mp4 name.") logging.error("Invalid mp4 name.")
sys.exit(0) sys.exit(0)
self.output_filename = os.path.join(folder, base_name) self.output_filename = os.path.join(folder, base_name)
self.output_filename = transliterate(self.output_filename)
logging.info(f"Output filename: {self.output_filename}") logging.info(f"Output filename: {self.output_filename}")
@ -222,12 +222,15 @@ class Downloader():
if self.codec is not None: 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.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})")
def __donwload_video__(self): def __donwload_video__(self, server_ip: list = None):
""" """
Downloads and manages video segments. Downloads and manages video segments.
This method downloads video segments if necessary and updates This method downloads video segments if necessary and updates
the list of downloaded video segments. the list of downloaded video segments.
Args:
- server_ip (list): A list of IP addresses to use in requests.
""" """
# Construct full path for the video segment directory # Construct full path for the video segment directory
@ -243,6 +246,7 @@ class Downloader():
# Create an instance of M3U8_Segments to handle video segments # Create an instance of M3U8_Segments to handle video segments
video_m3u8 = M3U8_Segments(self.m3u8_index, full_path_video) video_m3u8 = M3U8_Segments(self.m3u8_index, full_path_video)
video_m3u8.add_server_ip(server_ip)
# Get information about the video segments # Get information about the video segments
video_m3u8.get_info() video_m3u8.get_info()
@ -253,12 +257,15 @@ class Downloader():
else: else:
console.log("[cyan]Video [red]already exists.") console.log("[cyan]Video [red]already exists.")
def __donwload_audio__(self): def __donwload_audio__(self, server_ip: list = None):
""" """
Downloads and manages audio segments. Downloads and manages audio segments.
This method iterates over available audio tracks, downloads them if necessary, and updates This method iterates over available audio tracks, downloads them if necessary, and updates
the list of downloaded audio tracks. the list of downloaded audio tracks.
Args:
- server_ip (list): A list of IP addresses to use in requests.
""" """
# Iterate over each available audio track # Iterate over each available audio track
@ -285,6 +292,7 @@ class Downloader():
# If the audio segment directory doesn't exist, download audio segments # If the audio segment directory doesn't exist, download audio segments
audio_m3u8 = M3U8_Segments(obj_audio.get('uri'), full_path_audio) audio_m3u8 = M3U8_Segments(obj_audio.get('uri'), full_path_audio)
audio_m3u8.add_server_ip(server_ip)
# Get information about the audio segments # Get information about the audio segments
audio_m3u8.get_info() audio_m3u8.get_info()
@ -361,7 +369,7 @@ class Downloader():
for future in futures: for future in futures:
future.result() future.result()
def __join_video__(self, force_ts = False, vcodec = 'copy') -> str: def __join_video__(self, vcodec = 'copy') -> str:
""" """
Join downloaded video segments into a single video file. Join downloaded video segments into a single video file.
@ -378,7 +386,6 @@ class Downloader():
join_video( join_video(
video_path = self.downloaded_video[0].get('path'), video_path = self.downloaded_video[0].get('path'),
out_path = path_join_video, out_path = path_join_video,
force_ts = force_ts,
vcodec = vcodec vcodec = vcodec
) )
@ -459,9 +466,12 @@ class Downloader():
else: else:
logging.info("Video file converted already exist.") logging.info("Video file converted already exist.")
def start(self) -> None: def start(self, server_ip: list = None) -> None:
""" """
Start the process of fetching, downloading, joining, and cleaning up the video. Start the process of fetching, downloading, joining, and cleaning up the video.
Args:
- server_ip (list): A list of IP addresses to use in requests.
""" """
# Check if file already exist # Check if file already exist
@ -492,8 +502,8 @@ class Downloader():
self.__manage_playlist__(m3u8_playlist_text) self.__manage_playlist__(m3u8_playlist_text)
# Start all download ... # Start all download ...
self.__donwload_video__() self.__donwload_video__(server_ip)
self.__donwload_audio__() self.__donwload_audio__(server_ip)
self.__download_subtitle__() self.__download_subtitle__()
# Check file to convert # Check file to convert
@ -529,10 +539,7 @@ class Downloader():
self.__donwload_video__() self.__donwload_video__()
# Convert video # Convert video
if FORCE_TS: converted_out_path = self.__join_video__()
converted_out_path = self.__join_video__(force_ts=True, vcodec="libx264")
else:
converted_out_path = self.__join_video__()
# Clean all tmp file # Clean all tmp file
self.__clean__(converted_out_path) self.__clean__(converted_out_path)

View File

@ -35,8 +35,6 @@ from ..M3U8 import (
# Config # Config
TQDM_MAX_WORKER = config_manager.get_int('M3U8_DOWNLOAD', 'tdqm_workers') TQDM_MAX_WORKER = config_manager.get_int('M3U8_DOWNLOAD', 'tdqm_workers')
TQDM_SHOW_PROGRESS = config_manager.get_int('M3U8_DOWNLOAD', 'tqdm_show_progress') TQDM_SHOW_PROGRESS = config_manager.get_int('M3U8_DOWNLOAD', 'tqdm_show_progress')
FAKE_PROXY = config_manager.get_float('M3U8_DOWNLOAD', 'fake_proxy')
FAKE_PROXY_IP = config_manager.get_list('M3U8_DOWNLOAD', 'fake_proxy_ip')
REQUEST_TIMEOUT = config_manager.get_int('M3U8_REQUESTS', 'timeout') REQUEST_TIMEOUT = config_manager.get_int('M3U8_REQUESTS', 'timeout')
REQUEST_VERIFY_SSL = config_manager.get_bool('M3U8_REQUESTS', 'verify_ssl') REQUEST_VERIFY_SSL = config_manager.get_bool('M3U8_REQUESTS', 'verify_ssl')
REQUEST_DISABLE_ERROR = config_manager.get_bool('M3U8_REQUESTS', 'disable_error') REQUEST_DISABLE_ERROR = config_manager.get_bool('M3U8_REQUESTS', 'disable_error')
@ -67,8 +65,20 @@ class M3U8_Segments:
self.ctrl_c_detected = False # Global variable to track Ctrl+C detection self.ctrl_c_detected = False # Global variable to track Ctrl+C detection
os.makedirs(self.tmp_folder, exist_ok=True) # Create the temporary folder if it does not exist os.makedirs(self.tmp_folder, exist_ok=True) # Create the temporary folder if it does not exist
self.class_ts_estimator = M3U8_Ts_Estimator(TQDM_MAX_WORKER) self.class_ts_estimator = M3U8_Ts_Estimator(TQDM_MAX_WORKER, 0)
self.class_url_fixer = M3U8_UrlFix(url) self.class_url_fixer = M3U8_UrlFix(url)
self.fake_proxy = False
def add_server_ip(self, list_ip):
"""
Add server IP addresses
Args:
list_ip (list): A list of IP addresses to be added.
"""
if list_ip is not None:
self.fake_proxy = True
self.fake_proxy_ip = list_ip
def __get_key__(self, m3u8_parser: M3U8_Parser) -> bytes: def __get_key__(self, m3u8_parser: M3U8_Parser) -> bytes:
""" """
@ -143,7 +153,7 @@ class M3U8_Segments:
logging.info(f"Generated new URL: {self.segments[i]}, from: {segment_url}") logging.info(f"Generated new URL: {self.segments[i]}, from: {segment_url}")
# Change IP address of server # Change IP address of server
if FAKE_PROXY: if self.fake_proxy:
for i in range(len(self.segments)): for i in range(len(self.segments)):
segment_url = self.segments[i] segment_url = self.segments[i]
@ -155,6 +165,9 @@ class M3U8_Segments:
for item in self.segments: for item in self.segments:
file.write(f"{item}\n") file.write(f"{item}\n")
# Update segments for estimator
self.class_ts_estimator.total_segments = len(self.segments)
def get_info(self) -> None: def get_info(self) -> None:
""" """
Makes a request to the index M3U8 file to get information about segments. Makes a request to the index M3U8 file to get information about segments.
@ -183,42 +196,13 @@ class M3U8_Segments:
Returns: Returns:
str: The modified URL with the new IP address. str: The modified URL with the new IP address.
""" """
new_ip_address = FAKE_PROXY_IP[url_index % len(FAKE_PROXY_IP)] new_ip_address = self.fake_proxy_ip[url_index % len(self.fake_proxy_ip)]
# Parse the original URL and replace the hostname with the new IP address # Parse the original URL and replace the hostname with the new IP address
parsed_url = urlparse(url)._replace(netloc=new_ip_address) parsed_url = urlparse(url)._replace(netloc=new_ip_address)
return urlunparse(parsed_url) return urlunparse(parsed_url)
def update_progress_bar(self, segment_content: bytes, duration: float, progress_counter: tqdm) -> None:
"""
Updates the progress bar with information about the TS segment download.
Args:
segment_content (bytes): The content of the downloaded TS segment.
duration (float): The duration of the segment download in seconds.
progress_counter (tqdm): The tqdm object representing the progress bar.
"""
if TQDM_SHOW_PROGRESS:
total_downloaded = len(segment_content)
# Add the size of the downloaded segment to the estimator
self.class_ts_estimator.add_ts_file(total_downloaded * len(self.segments), total_downloaded, duration)
# Get downloaded size and total estimated size
downloaded_file_size_str = self.class_ts_estimator.get_downloaded_size().split(' ')[0]
file_total_size = self.class_ts_estimator.calculate_total_size()
number_file_total_size = file_total_size.split(' ')[0]
units_file_total_size = file_total_size.split(' ')[1]
average_internet_speed = self.class_ts_estimator.get_average_speed()
# Update the progress bar's postfix
progress_counter.set_postfix_str(
f"{Colors.WHITE}[ {Colors.GREEN}{downloaded_file_size_str} {Colors.WHITE}< {Colors.GREEN}{number_file_total_size} {Colors.RED}{units_file_total_size} "
f"{Colors.WHITE}| {Colors.CYAN}{average_internet_speed:.2f} {Colors.RED}MB/s"
)
def make_requests_stream(self, ts_url: str, index: int, stop_event: threading.Event, progress_bar: tqdm) -> None: def make_requests_stream(self, ts_url: str, index: int, stop_event: threading.Event, progress_bar: tqdm) -> None:
""" """
Downloads a TS segment and adds it to the segment queue. Downloads a TS segment and adds it to the segment queue.
@ -248,7 +232,8 @@ class M3U8_Segments:
# Get the content of the segment # Get the content of the segment
segment_content = response.content segment_content = response.content
self.update_progress_bar(segment_content, duration, progress_bar) if TQDM_SHOW_PROGRESS:
self.class_ts_estimator.update_progress_bar(segment_content, duration, progress_bar)
# Decrypt the segment content if decryption is needed # Decrypt the segment content if decryption is needed
if self.decryption is not None: if self.decryption is not None:
@ -315,8 +300,9 @@ class M3U8_Segments:
total=len(self.segments), total=len(self.segments),
unit='s', unit='s',
ascii=' #', ascii=' #',
bar_format=f"{Colors.YELLOW}Downloading {Colors.WHITE}({add_desc}{Colors.WHITE}): {Colors.RED}{{percentage:.0f}}% {Colors.MAGENTA}{{bar}} {Colors.YELLOW}{{elapsed}} {Colors.WHITE}< {Colors.CYAN}{{remaining}}{{postfix}} {Colors.WHITE}]", bar_format=f"{Colors.YELLOW}Downloading {Colors.WHITE}({add_desc}{Colors.WHITE}): {Colors.RED}{{percentage:.2f}}% {Colors.MAGENTA}{{bar}} {Colors.YELLOW}{{elapsed}} {Colors.WHITE}< {Colors.CYAN}{{remaining}}{{postfix}} {Colors.WHITE}]",
dynamic_ncols=True dynamic_ncols=True,
mininterval=0.01
) )
def signal_handler(sig, frame): def signal_handler(sig, frame):
@ -342,7 +328,7 @@ class M3U8_Segments:
time.sleep(0.025) time.sleep(0.025)
if self.ctrl_c_detected: if self.ctrl_c_detected:
console.log("[red]1. Ctrl+C detected. Stopping further downloads.") console.log("[red]Ctrl+C detected. Stopping further downloads.")
stop_event.set() stop_event.set()
with self.condition: with self.condition:

View File

@ -2,17 +2,24 @@
from collections import deque from collections import deque
# External libraries
from tqdm import tqdm
# Internal utilities # Internal utilities
from Src.Util.color import Colors
from Src.Util.os import format_size from Src.Util.os import format_size
class M3U8_Ts_Estimator: class M3U8_Ts_Estimator:
def __init__(self, workers: int): def __init__(self, workers: int, total_segments: int):
""" """
Initialize the TSFileSizeCalculator object. Initialize the TSFileSizeCalculator object.
Args: Args:
- workers (int): The number of workers using with ThreadPool. - workers (int): The number of workers using with ThreadPool.
- total_segments (int): Len of total segments to download
""" """
self.ts_file_sizes = [] self.ts_file_sizes = []
self.now_downloaded_size = 0 self.now_downloaded_size = 0
@ -20,6 +27,7 @@ class M3U8_Ts_Estimator:
self.list_speeds = deque(maxlen=self.average_over) self.list_speeds = deque(maxlen=self.average_over)
self.smoothed_speeds = [] self.smoothed_speeds = []
self.tqdm_workers = workers self.tqdm_workers = workers
self.total_segments = total_segments
def add_ts_file(self, size: int, size_download: int, duration: float): def add_ts_file(self, size: int, size_download: int, duration: float):
""" """
@ -33,6 +41,10 @@ class M3U8_Ts_Estimator:
self.ts_file_sizes.append(size) self.ts_file_sizes.append(size)
self.now_downloaded_size += size_download self.now_downloaded_size += size_download
# Only for the start
if len(self.smoothed_speeds) <= 3:
size_download = size_download / self.tqdm_workers
# Calculate mbps # Calculate mbps
speed_mbps = (size_download * 8) / (duration * 1_000_000) * self.tqdm_workers speed_mbps = (size_download * 8) / (duration * 1_000_000) * self.tqdm_workers
self.list_speeds.append(speed_mbps) self.list_speeds.append(speed_mbps)
@ -78,4 +90,33 @@ class M3U8_Ts_Estimator:
Returns: Returns:
str: The total downloaded size as a human-readable string. str: The total downloaded size as a human-readable string.
""" """
return format_size(self.now_downloaded_size) return format_size(self.now_downloaded_size)
def update_progress_bar(self, segment_content: bytes, duration: float, progress_counter: tqdm) -> None:
"""
Updates the progress bar with information about the TS segment download.
Args:
segment_content (bytes): The content of the downloaded TS segment.
duration (float): The duration of the segment download in seconds.
progress_counter (tqdm): The tqdm object representing the progress bar.
"""
total_downloaded = len(segment_content)
# Add the size of the downloaded segment to the estimator
self.add_ts_file(total_downloaded * self.total_segments, total_downloaded, duration)
# Get downloaded size and total estimated size
downloaded_file_size_str = self.get_downloaded_size().split(' ')[0]
file_total_size = self.calculate_total_size()
# Fix parameter for prefix
number_file_total_size = file_total_size.split(' ')[0]
units_file_total_size = file_total_size.split(' ')[1]
average_internet_speed = self.get_average_speed()
# Update the progress bar's postfix
progress_counter.set_postfix_str(
f"{Colors.WHITE}[ {Colors.GREEN}{downloaded_file_size_str} {Colors.WHITE}< {Colors.GREEN}{number_file_total_size} {Colors.RED}{units_file_total_size} "
f"{Colors.WHITE}| {Colors.CYAN}{average_internet_speed:.2f} {Colors.RED}MB/s"
)

View File

@ -1,5 +1,7 @@
# 16.05.24 # 16.05.24
from unidecode import unidecode
# Internal utilities # Internal utilities
from .alphabet import alpha_mappings from .alphabet import alpha_mappings
@ -31,5 +33,5 @@ all_mappings = {
def transliterate(text): def transliterate(text):
translated_text = ''.join(all_mappings.get(c, c) for c in text) translated_text = ''.join(all_mappings.get(c, c) for c in text)
return translated_text transliterated_text = unidecode(translated_text)
return transliterated_text

View File

@ -4,26 +4,49 @@ alpha_mappings = {
# Latin Alphabet # Latin Alphabet
'À': 'A', 'Á': 'A', 'Â': 'A', 'Ã': 'A', 'Ä': 'A', 'Å': 'A', 'Æ': 'AE', 'À': 'A', 'Á': 'A', 'Â': 'A', 'Ã': 'A', 'Ä': 'A', 'Å': 'A', 'Æ': 'AE',
'Ç': 'C', 'È': 'E', 'É': 'E', 'Ê': 'E', 'Ë': 'E', 'Ì': 'I', 'Í': 'I', 'Î': 'I', 'Ï': 'I', 'Ç': 'C', 'È': 'E', 'É': 'E', 'Ê': 'E', 'Ë': 'E',
'Ð': 'D', 'Ñ': 'N', 'Ò': 'O', 'Ó': 'O', 'Ô': 'O', 'Õ': 'O', 'Ö': 'O', 'Ø': 'O', 'Ì': 'I', 'Í': 'I', 'Î': 'I', 'Ï': 'I',
'Ù': 'U', 'Ú': 'U', 'Û': 'U', 'Ü': 'U', 'Ý': 'Y', 'Þ': 'Th', 'ß': 'ss', 'Ð': 'D', 'Ñ': 'N',
'Ò': 'O', 'Ó': 'O', 'Ô': 'O', 'Õ': 'O', 'Ö': 'O', 'Ø': 'O',
'Ù': 'U', 'Ú': 'U', 'Û': 'U', 'Ü': 'U',
'Ý': 'Y',
'Þ': 'TH',
'ß': 'ss',
'à': 'a', 'á': 'a', 'â': 'a', 'ã': 'a', 'ä': 'a', 'å': 'a', 'æ': 'ae', 'à': 'a', 'á': 'a', 'â': 'a', 'ã': 'a', 'ä': 'a', 'å': 'a', 'æ': 'ae',
'ç': 'c', 'è': 'e', 'é': 'e', 'ê': 'e', 'ë': 'e', 'ì': 'i', 'í': 'i', 'î': 'i', 'ï': 'i', 'ç': 'c', 'è': 'e', 'é': 'e', 'ê': 'e', 'ë': 'e',
'ð': 'd', 'ñ': 'n', 'ò': 'o', 'ó': 'o', 'ô': 'o', 'õ': 'o', 'ö': 'o', 'ø': 'o', 'ì': 'i', 'í': 'i', 'î': 'i', 'ï': 'i',
'ù': 'u', 'ú': 'u', 'û': 'u', 'ü': 'u', 'ý': 'y', 'þ': 'th', 'ÿ': 'y', 'ð': 'd', 'ñ': 'n',
'Ā': 'A', 'ā': 'a', 'Ă': 'A', 'ă': 'a', 'Ą': 'A', 'ą': 'a', 'Ć': 'C', 'ć': 'c', 'ò': 'o', 'ó': 'o', 'ô': 'o', 'õ': 'o', 'ö': 'o', 'ø': 'o',
'Ĉ': 'C', 'ĉ': 'c', 'Ċ': 'C', 'ċ': 'c', 'Č': 'C', 'č': 'c', 'Ď': 'D', 'ď': 'd', 'Đ': 'D', 'đ': 'd', 'ù': 'u', 'ú': 'u', 'û': 'u', 'ü': 'u',
'ý': 'y', 'þ': 'th', 'ÿ': 'y',
'Ā': 'A', 'ā': 'a', 'Ă': 'A', 'ă': 'a', 'Ą': 'A', 'ą': 'a',
'Ć': 'C', 'ć': 'c', 'Ĉ': 'C', 'ĉ': 'c', 'Ċ': 'C', 'ċ': 'c', 'Č': 'C', 'č': 'c',
'Ď': 'D', 'ď': 'd', 'Đ': 'D', 'đ': 'd',
'Ē': 'E', 'ē': 'e', 'Ĕ': 'E', 'ĕ': 'e', 'Ė': 'E', 'ė': 'e', 'Ę': 'E', 'ę': 'e', 'Ě': 'E', 'ě': 'e', 'Ē': 'E', 'ē': 'e', 'Ĕ': 'E', 'ĕ': 'e', 'Ė': 'E', 'ė': 'e', 'Ę': 'E', 'ę': 'e', 'Ě': 'E', 'ě': 'e',
'Ĝ': 'G', 'ĝ': 'g', 'Ğ': 'G', 'ğ': 'g', 'Ġ': 'G', 'ġ': 'g', 'Ģ': 'G', 'ģ': 'g', 'Ĥ': 'H', 'ĥ': 'h', 'Ĝ': 'G', 'ĝ': 'g', 'Ğ': 'G', 'ğ': 'g', 'Ġ': 'G', 'ġ': 'g', 'Ģ': 'G', 'ģ': 'g',
'Ħ': 'H', 'ħ': 'h', 'Ĩ': 'I', 'ĩ': 'i', 'Ī': 'I', 'ī': 'i', 'Ĭ': 'I', 'ĭ': 'i', 'Į': 'I', 'į': 'i', 'Ĥ': 'H', 'ĥ': 'h', 'Ħ': 'H', 'ħ': 'h',
'İ': 'I', 'ı': 'i', 'IJ': 'IJ', 'ij': 'ij', 'Ĵ': 'J', 'ĵ': 'j', 'Ķ': 'K', 'ķ': 'k', 'ĸ': 'k', 'Ĺ': 'L', 'Ĩ': 'I', 'ĩ': 'i', 'Ī': 'I', 'ī': 'i', 'Ĭ': 'I', 'ĭ': 'i', 'Į': 'I', 'į': 'i',
'ĺ': 'l', 'Ļ': 'L', 'ļ': 'l', 'Ľ': 'L', 'ľ': 'l', 'Ŀ': 'L', 'ŀ': 'l', 'Ł': 'L', 'ł': 'l', 'Ń': 'N', 'İ': 'I', 'ı': 'i',
'ń': 'n', 'Ņ': 'N', 'ņ': 'n', 'Ň': 'N', 'ň': 'n', 'ʼn': 'n', 'Ō': 'O', 'ō': 'o', 'Ŏ': 'O', 'ŏ': 'o', 'IJ': 'IJ', 'ij': 'ij',
'Ő': 'O', 'ő': 'o', 'Ŕ': 'R', 'ŕ': 'r', 'Ŗ': 'R', 'ŗ': 'r', 'Ř': 'R', 'ř': 'r', 'Ś': 'S', 'ś': 's', 'Ĵ': 'J', 'ĵ': 'j',
'Ŝ': 'S', 'ŝ': 's', 'Ş': 'S', 'ş': 's', 'Š': 'S', 'š': 's', 'Ţ': 'T', 'ţ': 't', 'Ť': 'T', 'ť': 't', 'Ķ': 'K', 'ķ': 'k', 'ĸ': 'k',
'Ŧ': 'T', 'ŧ': 't', 'Ũ': 'U', 'ũ': 'u', 'Ū': 'U', 'ū': 'u', 'Ŭ': 'U', 'ŭ': 'u', 'Ů': 'U', 'ů': 'u', 'Ĺ': 'L', 'ĺ': 'l', 'Ļ': 'L', 'ļ': 'l', 'Ľ': 'L', 'ľ': 'l', 'Ŀ': 'L', 'ŀ': 'l', 'Ł': 'L', 'ł': 'l',
'Ű': 'U', 'ű': 'u', 'Ų': 'U', 'ų': 'u', 'Ŵ': 'W', 'ŵ': 'w', 'Ŷ': 'Y', 'ŷ': 'y', 'Ÿ': 'Y', 'Ź': 'Z', 'Ń': 'N', 'ń': 'n', 'Ņ': 'N', 'ņ': 'n', 'Ň': 'N', 'ň': 'n', 'ʼn': 'n', 'Ŋ': 'N', 'ŋ': 'n',
'ź': 'z', 'Ż': 'Z', 'ż': 'z', 'Ž': 'Z', 'ž': 'z', 'Ō': 'O', 'ō': 'o', 'Ŏ': 'O', 'ŏ': 'o', 'Ő': 'O', 'ő': 'o',
'Œ': 'OE', 'œ': 'oe',
'Ŕ': 'R', 'ŕ': 'r', 'Ŗ': 'R', 'ŗ': 'r', 'Ř': 'R', 'ř': 'r',
'Ś': 'S', 'ś': 's', 'Ŝ': 'S', 'ŝ': 's', 'Ş': 'S', 'ş': 's', 'Š': 'S', 'š': 's',
'Ţ': 'T', 'ţ': 't', 'Ť': 'T', 'ť': 't', 'Ŧ': 'T', 'ŧ': 't',
'Ũ': 'U', 'ũ': 'u', 'Ū': 'U', 'ū': 'u', 'Ŭ': 'U', 'ŭ': 'u', 'Ů': 'U', 'ů': 'u',
'Ű': 'U', 'ű': 'u', 'Ų': 'U', 'ų': 'u',
'Ŵ': 'W', 'ŵ': 'w',
'Ŷ': 'Y', 'ŷ': 'y', 'Ÿ': 'Y',
'Ź': 'Z', 'ź': 'z', 'Ż': 'Z', 'ż': 'z', 'Ž': 'Z', 'ž': 'z',
'ƒ': 'f',
'Ơ': 'O', 'ơ': 'o', 'Ư': 'U', 'ư': 'u',
'Ǎ': 'A', 'ǎ': 'a', 'Ǐ': 'I', 'ǐ': 'i', 'Ǒ': 'O', 'ǒ': 'o', 'Ǔ': 'U', 'ǔ': 'u',
'Ǖ': 'U', 'ǖ': 'u', 'Ǘ': 'U', 'ǘ': 'u', 'Ǚ': 'U', 'ǚ': 'u', 'Ǜ': 'U', 'ǜ': 'u',
'Ǻ': 'A', 'ǻ': 'a', 'Ǽ': 'AE', 'ǽ': 'ae', 'Ǿ': 'O', 'ǿ': 'o',
# Cyrillic Alphabet # Cyrillic Alphabet
'А': 'A', 'Б': 'B', 'В': 'V', 'Г': 'G', 'Д': 'D', 'Е': 'E', 'Ё': 'E', 'Ж': 'Zh', 'З': 'Z', 'И': 'I', 'А': 'A', 'Б': 'B', 'В': 'V', 'Г': 'G', 'Д': 'D', 'Е': 'E', 'Ё': 'E', 'Ж': 'Zh', 'З': 'Z', 'И': 'I',
@ -109,4 +132,10 @@ alpha_mappings = {
'ù': 'u', 'ú': 'u', 'ũ': 'u', '': 'u', 'ù': 'u', 'ú': 'u', 'ũ': 'u', '': 'u',
'': 'u', '': 'u', '': 'u', '': 'u', '': 'u', '': 'u', '': 'u', '': 'u', '': 'u', '': 'u', '': 'u', '': 'u',
'': 'y', 'ý': 'y', '': 'y', '': 'y', '': 'y', '': 'y', 'ý': 'y', '': 'y', '': 'y', '': 'y',
# Devanagari
'': 'a', '': 'aa', '': 'i', '': 'ii', '': 'u', '': 'uu', '': 'e', '': 'ai', '': 'o', '': 'au', '': 'ka', '': 'kha', '': 'ga', '': 'gha', '': 'nga', '': 'cha',
'': 'chha', '': 'ja', '': 'jha', '': 'nya', '': 'ta', '': 'tha', '': 'da', '': 'dha', '': 'na', '': 'ta', '': 'tha', '': 'da', '': 'dha', '': 'na', '': 'pa',
'': 'pha', '': 'ba', '': 'bha', '': 'ma', '': 'ya', '': 'ra', '': 'la', '': 'va', '': 'sha', '': 'ssha', '': 'sa', '': 'ha', '': 'a',
} }

View File

@ -13,6 +13,4 @@ units_of_measurement_mapping = {
'mm³': 'cubic millimeters', 'mm³': 'cubic millimeters',
'km²': 'square kilometers', 'km²': 'square kilometers',
'km³': 'cubic kilometers', 'km³': 'cubic kilometers',
'ha': 'hectares',
'ha': 'hectares',
} }

View File

@ -18,14 +18,11 @@
"M3U8_DOWNLOAD": { "M3U8_DOWNLOAD": {
"tdqm_workers": 30, "tdqm_workers": 30,
"tqdm_show_progress": true, "tqdm_show_progress": true,
"fake_proxy": false,
"fake_proxy_ip": ["57.129.7.85","57.129.7.188","57.129.7.174","57.129.4.77","57.129.16.196","57.129.16.156","57.129.16.139","57.129.16.135","57.129.13.175","57.129.13.157","51.38.112.237","51.195.107.7","51.195.107.230"],
"create_report": false "create_report": false
}, },
"M3U8_FILTER": { "M3U8_FILTER": {
"use_codec": false, "use_codec": false,
"use_gpu": false, "use_gpu": false,
"force_ts": false,
"default_preset": "ultrafast", "default_preset": "ultrafast",
"cleanup_tmp_folder": true, "cleanup_tmp_folder": true,
"specific_list_audio": ["ita"], "specific_list_audio": ["ita"],