Add path validator

This commit is contained in:
Lovi 2024-11-23 15:36:30 +01:00
parent c288d56b04
commit 3836148ff3
28 changed files with 488 additions and 497 deletions

View File

@ -1,8 +1,6 @@
# 02.07.24 # 02.07.24
import os import os
import sys
import logging
# External libraries # External libraries
@ -12,9 +10,9 @@ from bs4 import BeautifulSoup
# Internal utilities # Internal utilities
from Src.Util.console import console from Src.Util.console import console
from Src.Util.os import os_manager
from Src.Util.message import start_message from Src.Util.message import start_message
from Src.Util.headers import get_headers from Src.Util.headers import get_headers
from Src.Util.os import create_folder, can_create_file, remove_special_characters
from Src.Lib.Downloader import TOR_downloader from Src.Lib.Downloader import TOR_downloader
@ -39,19 +37,23 @@ def download_title(select_title: MediaItem):
print() print()
# Define output path # Define output path
title_name = remove_special_characters(select_title.name) title_name = os_manager.get_sanitize_file(select_title.name)
mp4_name = title_name.replace("-", "_") + ".mp4" mp4_path = os_manager.get_sanitize_path(
mp4_path = os.path.join(ROOT_PATH, SITE_NAME, MOVIE_FOLDER, remove_special_characters(title_name.replace(".mp4", ""))) os.path.join(ROOT_PATH, SITE_NAME, MOVIE_FOLDER, title_name.replace(".mp4", ""))
)
# Check if can create file output # Create output folder
create_folder(mp4_path) os_manager.create_path(mp4_path)
if not can_create_file(mp4_name):
logging.error("Invalid mp4 name.")
sys.exit(0)
# Make request to page with magnet # Make request to page with magnet
full_site_name = f"{SITE_NAME}.{DOMAIN_NOW}" full_site_name = f"{SITE_NAME}.{DOMAIN_NOW}"
response = httpx.get("https://" + full_site_name + select_title.url, headers={'user-agent': get_headers()}, follow_redirects=True) response = httpx.get(
url="https://" + full_site_name + select_title.url,
headers={
'user-agent': get_headers()
},
follow_redirects=True
)
# Create soup and find table # Create soup and find table
soup = BeautifulSoup(response.text, "html.parser") soup = BeautifulSoup(response.text, "html.parser")

View File

@ -7,7 +7,7 @@ from typing import List
# Internal utilities # Internal utilities
from Src.Util._jsonConfig import config_manager from Src.Util._jsonConfig import config_manager
from Src.Util.os import remove_special_characters from Src.Util.os import os_manager
# Config # Config
@ -85,10 +85,10 @@ def map_episode_title(tv_name: str, number_season: int, episode_number: int, epi
str: The mapped episode title. str: The mapped episode title.
""" """
map_episode_temp = MAP_EPISODE map_episode_temp = MAP_EPISODE
map_episode_temp = map_episode_temp.replace("%(tv_name)", remove_special_characters(tv_name)) map_episode_temp = map_episode_temp.replace("%(tv_name)", os_manager.get_sanitize_file(tv_name))
map_episode_temp = map_episode_temp.replace("%(season)", dynamic_format_number(number_season)) map_episode_temp = map_episode_temp.replace("%(season)", dynamic_format_number(number_season))
map_episode_temp = map_episode_temp.replace("%(episode)", dynamic_format_number(episode_number)) map_episode_temp = map_episode_temp.replace("%(episode)", dynamic_format_number(episode_number))
map_episode_temp = map_episode_temp.replace("%(episode_name)", remove_special_characters(episode_name)) map_episode_temp = map_episode_temp.replace("%(episode_name)", os_manager.get_sanitize_file(episode_name))
logging.info(f"Map episode string return: {map_episode_temp}") logging.info(f"Map episode string return: {map_episode_temp}")
return map_episode_temp return map_episode_temp

View File

@ -11,10 +11,10 @@ def execute_search(info):
info (dict): A dictionary containing the function name, folder, and module information. info (dict): A dictionary containing the function name, folder, and module information.
""" """
# Step 1: Define the project path using the folder from the info dictionary # Define the project path using the folder from the info dictionary
project_path = os.path.dirname(info['folder']) # Get the base path for the project project_path = os.path.dirname(info['folder']) # Get the base path for the project
# Step 2: Add the project path to sys.path # Add the project path to sys.path
if project_path not in sys.path: if project_path not in sys.path:
sys.path.append(project_path) sys.path.append(project_path)
@ -29,7 +29,9 @@ def execute_search(info):
except ModuleNotFoundError as e: except ModuleNotFoundError as e:
print(f"ModuleNotFoundError: {e}") print(f"ModuleNotFoundError: {e}")
except ImportError as e: except ImportError as e:
print(f"ImportError: {e}") print(f"ImportError: {e}")
except Exception as e: except Exception as e:
print(f"An error occurred: {e}") print(f"An error occurred: {e}")

View File

@ -6,7 +6,7 @@ import time
# Internal utilities # Internal utilities
from Src.Util.console import console, msg from Src.Util.console import console, msg
from Src.Util.os import remove_special_characters from Src.Util.os import os_manager
from Src.Util.message import start_message from Src.Util.message import start_message
from Src.Util.call_stack import get_call_stack from Src.Util.call_stack import get_call_stack
from Src.Lib.Downloader import HLS_Downloader from Src.Lib.Downloader import HLS_Downloader
@ -39,14 +39,19 @@ def download_film(select_title: MediaItem):
video_source = VideoSource(select_title.url) video_source = VideoSource(select_title.url)
# Define output path # Define output path
mp4_name = remove_special_characters(select_title.name) + ".mp4" title_name = os_manager.get_sanitize_file(select_title.name) + ".mp4"
mp4_path = os.path.join(ROOT_PATH, SITE_NAME, MOVIE_FOLDER, remove_special_characters(select_title.name)) mp4_path = os_manager.get_sanitize_path(
os.path.join(ROOT_PATH, SITE_NAME, MOVIE_FOLDER, title_name.replace(".mp4", ""))
)
# Get m3u8 master playlist # Get m3u8 master playlist
master_playlist = video_source.get_playlist() master_playlist = video_source.get_playlist()
# Download the film using the m3u8 playlist, and output filename # 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)).start() r_proc = HLS_Downloader(
m3u8_playlist=master_playlist,
output_filename=os.path.join(mp4_path, title_name)
).start()
if r_proc == 404: if r_proc == 404:
time.sleep(2) time.sleep(2)

View File

@ -7,8 +7,8 @@ import logging
# Internal utilities # Internal utilities
from Src.Util.console import console, msg from Src.Util.console import console, msg
from Src.Util.os import os_manager
from Src.Util.message import start_message from Src.Util.message import start_message
from Src.Util.os import create_folder, can_create_file
from Src.Lib.Downloader import MP4_downloader from Src.Lib.Downloader import MP4_downloader
@ -46,23 +46,24 @@ def download_episode(index_select: int):
video_source.parse_script(js_script) video_source.parse_script(js_script)
# Create output path # Create output path
mp4_path = None title_name = f"{obj_episode.number}.mp4"
mp4_name = f"{obj_episode.number}.mp4"
if video_source.is_series:
mp4_path = os.path.join(ROOT_PATH, SITE_NAME, SERIES_FOLDER, video_source.series_name)
else:
mp4_path = os.path.join(ROOT_PATH, SITE_NAME, MOVIE_FOLDER, video_source.series_name)
# Check if can create file output if video_source.is_series:
create_folder(mp4_path) mp4_path = os_manager.get_sanitize_path(
if not can_create_file(mp4_name): os.path.join(ROOT_PATH, SITE_NAME, SERIES_FOLDER, video_source.series_name)
logging.error("Invalid mp4 name.") )
sys.exit(0) else:
mp4_path = os_manager.get_sanitize_path(
os.path.join(ROOT_PATH, SITE_NAME, MOVIE_FOLDER, video_source.series_name)
)
# Create output folder
os_manager.create_path(mp4_path)
# Start downloading # Start downloading
MP4_downloader( MP4_downloader(
str(video_source.src_mp4).strip(), url = str(video_source.src_mp4).strip(),
os.path.join(mp4_path, mp4_name) path = os.path.join(mp4_path, title_name)
) )
else: else:

View File

@ -1,14 +1,12 @@
# 01.07.24 # 01.07.24
import os import os
import sys
import logging
# Internal utilities # Internal utilities
from Src.Util.console import console from Src.Util.console import console
from Src.Util.message import start_message from Src.Util.message import start_message
from Src.Util.os import create_folder, can_create_file, remove_special_characters from Src.Util.os import os_manager
from Src.Lib.Downloader import TOR_downloader from Src.Lib.Downloader import TOR_downloader
@ -34,15 +32,13 @@ def download_title(select_title: MediaItem):
print() print()
# Define output path # Define output path
title_name = remove_special_characters(select_title.name) title_name = os_manager.get_sanitize_file(select_title.name.replace("-", "_") + ".mp4")
mp4_name = title_name.replace("-", "_") + ".mp4" mp4_path = os_manager.get_sanitize_path(
mp4_path = os.path.join(ROOT_PATH, SITE_NAME, MOVIE_FOLDER, remove_special_characters(title_name.replace(".mp4", ""))) os.path.join(ROOT_PATH, SITE_NAME, MOVIE_FOLDER, title_name.replace(".mp4", ""))
)
# Check if can create file output
create_folder(mp4_path) # Create output folder
if not can_create_file(mp4_name): os_manager.create_path(mp4_path)
logging.error("Invalid mp4 name.")
sys.exit(0)
# Tor manager # Tor manager
manager = TOR_downloader() manager = TOR_downloader()

View File

@ -8,7 +8,7 @@ import logging
# Internal utilities # Internal utilities
from Src.Util.console import console, msg from Src.Util.console import console, msg
from Src.Util.os import remove_special_characters from Src.Util.os import os_manager
from Src.Util.message import start_message from Src.Util.message import start_message
from Src.Util.call_stack import get_call_stack from Src.Util.call_stack import get_call_stack
from Src.Lib.Downloader import HLS_Downloader from Src.Lib.Downloader import HLS_Downloader
@ -40,15 +40,19 @@ def download_film(select_title: MediaItem):
video_source = VideoSource(select_title.url) video_source = VideoSource(select_title.url)
# Define output path # Define output path
title_name = remove_special_characters(select_title.name) title_name = os_manager.get_sanitize_file(select_title.name) +".mp4"
mp4_name = title_name +".mp4" mp4_path = os_manager.get_sanitize_path(
mp4_path = os.path.join(ROOT_PATH, SITE_NAME, MOVIE_FOLDER, title_name) os.path.join(ROOT_PATH, SITE_NAME, MOVIE_FOLDER, title_name.replace(".mp4", ""))
)
# Get m3u8 master playlist # Get m3u8 master playlist
master_playlist = video_source.get_playlist() master_playlist = video_source.get_playlist()
# Download the film using the m3u8 playlist, and output filename # 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)).start() r_proc = HLS_Downloader(
m3u8_playlist=master_playlist,
output_filename=os.path.join(mp4_path, title_name)
).start()
if r_proc == 404: if r_proc == 404:
time.sleep(2) time.sleep(2)

View File

@ -9,7 +9,7 @@ from urllib.parse import urlparse
# Internal utilities # Internal utilities
from Src.Util.console import console from Src.Util.console import console
from Src.Util.message import start_message from Src.Util.message import start_message
from Src.Util.os import create_folder, can_create_file from Src.Util.os import os_manager
from Src.Util.table import TVShowManager from Src.Util.table import TVShowManager
from Src.Lib.Downloader import MP4_downloader from Src.Lib.Downloader import MP4_downloader
from ..Template import manage_selection, map_episode_title, validate_episode_selection from ..Template import manage_selection, map_episode_title, validate_episode_selection
@ -45,14 +45,13 @@ def download_video(scape_info_serie: GetSerieInfo, index_episode_selected: int)
print() print()
# Define filename and path for the downloaded video # Define filename and path for the downloaded video
mp4_name = f"{map_episode_title(scape_info_serie.tv_name, None, index_episode_selected, obj_episode.get('name'))}.mp4" title_name = os_manager.get_sanitize_file(
f"{map_episode_title(scape_info_serie.tv_name, None, index_episode_selected, obj_episode.get('name'))}.mp4"
)
mp4_path = os.path.join(ROOT_PATH, SITE_NAME, SERIES_FOLDER, scape_info_serie.tv_name) mp4_path = os.path.join(ROOT_PATH, SITE_NAME, SERIES_FOLDER, scape_info_serie.tv_name)
# Check if can create file output # Create output folder
create_folder(mp4_path) os_manager.create_path(mp4_path)
if not can_create_file(mp4_name):
logging.error("Invalid mp4 name.")
sys.exit(0)
# Setup video source # Setup video source
video_source.setup(obj_episode.get('url')) video_source.setup(obj_episode.get('url'))
@ -65,7 +64,7 @@ def download_video(scape_info_serie: GetSerieInfo, index_episode_selected: int)
MP4_downloader( MP4_downloader(
url = master_playlist, url = master_playlist,
path = os.path.join(mp4_path, mp4_name), path = os.path.join(mp4_path, title_name),
referer = f"{parsed_url.scheme}://{parsed_url.netloc}/", referer = f"{parsed_url.scheme}://{parsed_url.netloc}/",
) )

View File

@ -55,7 +55,10 @@ def download_video(scape_info_serie: GetSerieInfo, index_season_selected: int, i
master_playlist = video_source.get_playlist() master_playlist = video_source.get_playlist()
# Download the film using the m3u8 playlist, and output filename # 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)).start() r_proc = HLS_Downloader(
m3u8_playlist=master_playlist,
output_filename=os.path.join(mp4_path, mp4_name)
).start()
if r_proc == 404: if r_proc == 404:
time.sleep(2) time.sleep(2)

View File

@ -13,7 +13,7 @@ from bs4 import BeautifulSoup
# Internal utilities # Internal utilities
from Src.Util.console import console, msg from Src.Util.console import console, msg
from Src.Util.os import remove_special_characters from Src.Util.os import os_manager
from Src.Util.message import start_message from Src.Util.message import start_message
from Src.Util.call_stack import get_call_stack from Src.Util.call_stack import get_call_stack
from Src.Util.headers import get_headers from Src.Util.headers import get_headers
@ -64,14 +64,17 @@ def download_film(movie_details: Json_film):
video_source.setup(supervideo_url) video_source.setup(supervideo_url)
# Define output path # Define output path
mp4_name = remove_special_characters(movie_details.title) + ".mp4" title_name = os_manager.get_sanitize_file(movie_details.title) + ".mp4"
mp4_path = os.path.join(ROOT_PATH, SITE_NAME, MOVIE_FOLDER, remove_special_characters(movie_details.title)) mp4_path = os.path.join(ROOT_PATH, SITE_NAME, MOVIE_FOLDER, title_name.replace(".mp4", ""))
# Get m3u8 master playlist # Get m3u8 master playlist
master_playlist = video_source.get_playlist() master_playlist = video_source.get_playlist()
# Download the film using the m3u8 playlist, and output filename # 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)).start() r_proc = HLS_Downloader(
m3u8_playlist=master_playlist,
output_filename=os.path.join(mp4_path, title_name)
).start()
if r_proc == 404: if r_proc == 404:
time.sleep(2) time.sleep(2)

View File

@ -5,16 +5,10 @@ import sys
import logging import logging
# External libraries
import httpx
from bs4 import BeautifulSoup
# Internal utilities # Internal utilities
from Src.Util.console import console from Src.Util.console import console
from Src.Util.message import start_message from Src.Util.message import start_message
from Src.Util.headers import get_headers from Src.Util.os import os_manager
from Src.Util.os import create_folder, can_create_file, remove_special_characters
from Src.Lib.Downloader import TOR_downloader from Src.Lib.Downloader import TOR_downloader
@ -39,15 +33,11 @@ def download_title(select_title: MediaItem):
print() print()
# Define output path # Define output path
title_name = remove_special_characters(select_title.name) title_name = os_manager.get_sanitize_file(select_title.name.replace("-", "_") + ".mp4")
mp4_name = title_name.replace("-", "_") + ".mp4" mp4_path = os.path.join(ROOT_PATH, SITE_NAME, MOVIE_FOLDER, title_name.replace(".mp4", ""))
mp4_path = os.path.join(ROOT_PATH, SITE_NAME, MOVIE_FOLDER, remove_special_characters(title_name.replace(".mp4", "")))
# Check if can create file output # Create output folder
create_folder(mp4_path) os_manager.create_path(mp4_path)
if not can_create_file(mp4_name):
logging.error("Invalid mp4 name.")
sys.exit(0)
# Tor manager # Tor manager
manager = TOR_downloader() manager = TOR_downloader()

View File

@ -6,7 +6,7 @@ import time
# Internal utilities # Internal utilities
from Src.Util.console import console, msg from Src.Util.console import console, msg
from Src.Util.os import remove_special_characters from Src.Util.os import os_manager
from Src.Util.message import start_message from Src.Util.message import start_message
from Src.Util.call_stack import get_call_stack from Src.Util.call_stack import get_call_stack
from Src.Lib.Downloader import HLS_Downloader from Src.Lib.Downloader import HLS_Downloader
@ -45,11 +45,14 @@ def download_film(select_title: MediaItem, domain: str, version: str):
master_playlist = video_source.get_playlist() master_playlist = video_source.get_playlist()
# Define the filename and path for the downloaded film # Define the filename and path for the downloaded film
mp4_format = remove_special_characters(select_title.slug) + ".mp4" title_name = os_manager.get_sanitize_file(select_title.slug) + ".mp4"
mp4_path = os.path.join(ROOT_PATH, SITE_NAME, MOVIE_FOLDER, select_title.slug) mp4_path = os.path.join(ROOT_PATH, SITE_NAME, MOVIE_FOLDER, select_title.slug)
# Download the film using the m3u8 playlist, and output filename # 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_format)).start() r_proc = HLS_Downloader(
m3u8_playlist=master_playlist,
output_filename=os.path.join(mp4_path, title_name)
).start()
if r_proc == 404: if r_proc == 404:
time.sleep(2) time.sleep(2)

View File

@ -53,7 +53,10 @@ def download_video(tv_name: str, index_season_selected: int, index_episode_selec
master_playlist = video_source.get_playlist() master_playlist = video_source.get_playlist()
# Download the episode # Download the episode
r_proc = HLS_Downloader(os.path.join(mp4_path, mp4_name), master_playlist).start() r_proc = HLS_Downloader(
master_playlist=master_playlist,
output_filename=os.path.join(mp4_path, mp4_name)
).start()
if r_proc == 404: if r_proc == 404:
time.sleep(2) time.sleep(2)

View File

@ -7,7 +7,6 @@ import logging
# External libraries # External libraries
import httpx import httpx
from unidecode import unidecode
# Internal utilities # Internal utilities
@ -15,17 +14,11 @@ from Src.Util._jsonConfig import config_manager
from Src.Util.console import console, Panel, Table from Src.Util.console import console, Panel, Table
from Src.Util.color import Colors from Src.Util.color import Colors
from Src.Util.os import ( from Src.Util.os import (
remove_folder,
delete_files_except_one,
compute_sha1_hash, compute_sha1_hash,
format_file_size, os_manager,
create_folder, internet_manager
reduce_base_name,
remove_special_characters,
can_create_file
) )
# Logic class # Logic class
from ...FFmpeg import ( from ...FFmpeg import (
print_duration_table, print_duration_table,
@ -744,17 +737,13 @@ class HLS_Downloader:
if not folder: if not folder:
folder = new_folder folder = new_folder
# Sanitize base name # Sanitize base name and folder
base_name = reduce_base_name(remove_special_characters(base_name)) folder = os_manager.get_sanitize_path(folder)
create_folder(folder) base_name = os_manager.get_sanitize_file(base_name)
os_manager.create_path(folder)
if not can_create_file(base_name):
logging.error("Invalid mp4 name.")
sys.exit(0)
# Parse to only ASCII for compatibility across platforms # Parse to only ASCII for compatibility across platforms
new_filename = os.path.join(folder, base_name) new_filename = os.path.join(folder, base_name)
new_filename = unidecode(new_filename)
logging.info(f"class 'HLS_Downloader'; call _generate_output_filename(); return path: {new_filename}") logging.info(f"class 'HLS_Downloader'; call _generate_output_filename(); return path: {new_filename}")
return new_filename return new_filename
@ -857,7 +846,7 @@ class HLS_Downloader:
end_output_time = print_duration_table(self.output_filename, description=False, return_string=False) end_output_time = print_duration_table(self.output_filename, description=False, return_string=False)
# Calculate file size and duration for reporting # Calculate file size and duration for reporting
formatted_size = format_file_size(os.path.getsize(self.output_filename)) formatted_size = internet_manager.format_file_size(os.path.getsize(self.output_filename))
formatted_duration = print_duration_table(self.output_filename, description=False, return_string=True) formatted_duration = print_duration_table(self.output_filename, description=False, return_string=True)
expected_real_seconds = dict_to_seconds(self.content_downloader.expected_real_time) expected_real_seconds = dict_to_seconds(self.content_downloader.expected_real_time)
@ -895,11 +884,11 @@ class HLS_Downloader:
os.rename(self.output_filename, self.output_filename.replace(".mp4", "_failed.mp4")) os.rename(self.output_filename, self.output_filename.replace(".mp4", "_failed.mp4"))
# Delete all temporary files except for the output file # Delete all temporary files except for the output file
delete_files_except_one(self.path_manager.base_path, os.path.basename(self.output_filename.replace(".mp4", "_failed.mp4"))) os_manager.remove_files_except_one(self.path_manager.base_path, os.path.basename(self.output_filename.replace(".mp4", "_failed.mp4")))
# Remove the base folder if specified # Remove the base folder if specified
if REMOVE_SEGMENTS_FOLDER: if REMOVE_SEGMENTS_FOLDER:
remove_folder(self.path_manager.base_path) os_manager.remove_folder(self.path_manager.base_path)
else: else:
logging.info("Video file converted already exists.") logging.info("Video file converted already exists.")

View File

@ -13,7 +13,7 @@ import httpx
# Internal utilities # Internal utilities
from Src.Util._jsonConfig import config_manager from Src.Util._jsonConfig import config_manager
from Src.Util.headers import get_headers from Src.Util.headers import get_headers
from Src.Util.os import check_file_existence from Src.Util.os import os_manager
class ProxyManager: class ProxyManager:
@ -84,7 +84,7 @@ def main_test_proxy(url_test):
path_file_proxt_list = "list_proxy.txt" path_file_proxt_list = "list_proxy.txt"
if check_file_existence(path_file_proxt_list): if os_manager.check_file(path_file_proxt_list):
# Read file # Read file
with open(path_file_proxt_list, 'r') as file: with open(path_file_proxt_list, 'r') as file:

View File

@ -22,7 +22,7 @@ from Src.Util.console import console
from Src.Util.headers import get_headers, random_headers from Src.Util.headers import get_headers, random_headers
from Src.Util.color import Colors from Src.Util.color import Colors
from Src.Util._jsonConfig import config_manager from Src.Util._jsonConfig import config_manager
from Src.Util.os import check_file_existence from Src.Util.os import os_manager
from Src.Util.call_stack import get_call_stack from Src.Util.call_stack import get_call_stack
@ -40,7 +40,7 @@ TQDM_DELAY_WORKER = config_manager.get_float('M3U8_DOWNLOAD', 'tqdm_delay')
TQDM_USE_LARGE_BAR = config_manager.get_int('M3U8_DOWNLOAD', 'tqdm_use_large_bar') TQDM_USE_LARGE_BAR = config_manager.get_int('M3U8_DOWNLOAD', 'tqdm_use_large_bar')
REQUEST_MAX_RETRY = config_manager.get_int('REQUESTS', 'max_retry') REQUEST_MAX_RETRY = config_manager.get_int('REQUESTS', 'max_retry')
REQUEST_VERIFY = config_manager.get_bool('REQUESTS', 'verify_ssl') REQUEST_VERIFY = config_manager.get_bool('REQUESTS', 'verify_ssl')
THERE_IS_PROXY_LIST = check_file_existence("list_proxy.txt") THERE_IS_PROXY_LIST = os_manager.check_file("list_proxy.txt")
PROXY_START_MIN = config_manager.get_float('REQUESTS', 'proxy_start_min') PROXY_START_MIN = config_manager.get_float('REQUESTS', 'proxy_start_min')
PROXY_START_MAX = config_manager.get_float('REQUESTS', 'proxy_start_max') PROXY_START_MAX = config_manager.get_float('REQUESTS', 'proxy_start_max')
DEFAULT_VIDEO_WORKERS = config_manager.get_int('M3U8_DOWNLOAD', 'default_video_workser') DEFAULT_VIDEO_WORKERS = config_manager.get_int('M3U8_DOWNLOAD', 'default_video_workser')

View File

@ -15,7 +15,7 @@ from Src.Util.headers import get_headers
from Src.Util.color import Colors from Src.Util.color import Colors
from Src.Util.console import console, Panel from Src.Util.console import console, Panel
from Src.Util._jsonConfig import config_manager from Src.Util._jsonConfig import config_manager
from Src.Util.os import format_file_size from Src.Util.os import internet_manager
# Logic class # Logic class
@ -95,7 +95,7 @@ def MP4_downloader(url: str, path: str, referer: str = None, headers_: str = Non
if total != 0: if total != 0:
console.print(Panel( console.print(Panel(
f"[bold green]Download completed![/bold green]\n" f"[bold green]Download completed![/bold green]\n"
f"[cyan]File size: [bold red]{format_file_size(os.path.getsize(path))}[/bold red]\n" f"[cyan]File size: [bold red]{internet_manager.format_file_size(os.path.getsize(path))}[/bold red]\n"
f"[cyan]Duration: [bold]{print_duration_table(path, description=False, return_string=True)}[/bold]", f"[cyan]Duration: [bold]{print_duration_table(path, description=False, return_string=True)}[/bold]",
title=f"{os.path.basename(path.replace('.mp4', ''))}", title=f"{os.path.basename(path.replace('.mp4', ''))}",
border_style="green" border_style="green"

View File

@ -9,7 +9,7 @@ import logging
# Internal utilities # Internal utilities
from Src.Util.color import Colors from Src.Util.color import Colors
from Src.Util.os import format_file_size, format_transfer_speed from Src.Util.os import internet_manager
from Src.Util._jsonConfig import config_manager from Src.Util._jsonConfig import config_manager
@ -146,14 +146,14 @@ class TOR_downloader:
downloaded_size = torrent_info['total_downloaded'] downloaded_size = torrent_info['total_downloaded']
# Format variable # Format variable
downloaded_size_str = format_file_size(downloaded_size) downloaded_size_str = internet_manager.format_file_size(downloaded_size)
downloaded_size = downloaded_size_str.split(' ')[0] downloaded_size = downloaded_size_str.split(' ')[0]
total_size_str = format_file_size(total_size) total_size_str = internet_manager.format_file_size(total_size)
total_size = total_size_str.split(' ')[0] total_size = total_size_str.split(' ')[0]
total_size_unit = total_size_str.split(' ')[1] total_size_unit = total_size_str.split(' ')[1]
average_internet_str = format_transfer_speed(download_speed) average_internet_str = internet_manager.format_transfer_speed(download_speed)
average_internet = average_internet_str.split(' ')[0] average_internet = average_internet_str.split(' ')[0]
average_internet_unit = average_internet_str.split(' ')[1] average_internet_unit = average_internet_str.split(' ')[1]

View File

@ -8,7 +8,7 @@ import subprocess
# Internal utilities # Internal utilities
from Src.Util.console import console from Src.Util.console import console
from Src.Util.os import format_file_size from Src.Util.os import internet_manager
# Variable # Variable
@ -61,7 +61,7 @@ def capture_output(process: subprocess.Popen, description: str) -> None:
# Construct the progress string with formatted output information # Construct the progress string with formatted output information
progress_string = (f"[yellow][FFmpeg] [white][{description}[white]]: " progress_string = (f"[yellow][FFmpeg] [white][{description}[white]]: "
f"([green]'speed': [yellow]{data.get('speed', 'N/A')}[white], " f"([green]'speed': [yellow]{data.get('speed', 'N/A')}[white], "
f"[green]'size': [yellow]{format_file_size(byte_size)}[white])") f"[green]'size': [yellow]{internet_manager.format_file_size(byte_size)}[white])")
max_length = max(max_length, len(progress_string)) max_length = max(max_length, len(progress_string))
# Print the progress string to the console, overwriting the previous line # Print the progress string to the console, overwriting the previous line

View File

@ -10,7 +10,7 @@ from typing import List, Dict
# Internal utilities # Internal utilities
from Src.Util._jsonConfig import config_manager from Src.Util._jsonConfig import config_manager
from Src.Util.os import check_file_existence, suppress_output from Src.Util.os import os_manager, suppress_output
from Src.Util.console import console from Src.Util.console import console
from .util import need_to_force_to_ts, check_duration_v_a from .util import need_to_force_to_ts, check_duration_v_a
from .capture import capture_ffmpeg_real_time from .capture import capture_ffmpeg_real_time
@ -46,7 +46,7 @@ def join_video(video_path: str, out_path: str, codec: M3U8_Codec = None):
- force_ts (bool): Force video path to be mpegts as input. - force_ts (bool): Force video path to be mpegts as input.
""" """
if not check_file_existence(video_path): if not os_manager.check_file(video_path):
logging.error("Missing input video for ffmpeg conversion.") logging.error("Missing input video for ffmpeg conversion.")
sys.exit(0) sys.exit(0)
@ -119,7 +119,7 @@ def join_video(video_path: str, out_path: str, codec: M3U8_Codec = None):
print() print()
time.sleep(0.5) time.sleep(0.5)
if not check_file_existence(out_path): if not os_manager.check_file(out_path):
logging.error("Missing output video for ffmpeg conversion video.") logging.error("Missing output video for ffmpeg conversion video.")
sys.exit(0) sys.exit(0)
@ -135,7 +135,7 @@ def join_audios(video_path: str, audio_tracks: List[Dict[str, str]], out_path: s
- out_path (str): The path to save the output file. - out_path (str): The path to save the output file.
""" """
if not check_file_existence(video_path): if not os_manager.check_file(video_path):
logging.error("Missing input video for ffmpeg conversion.") logging.error("Missing input video for ffmpeg conversion.")
sys.exit(0) sys.exit(0)
@ -153,7 +153,7 @@ def join_audios(video_path: str, audio_tracks: List[Dict[str, str]], out_path: s
# Add audio tracks as input # Add audio tracks as input
for i, audio_track in enumerate(audio_tracks): for i, audio_track in enumerate(audio_tracks):
if check_file_existence(audio_track.get('path')): if os_manager.check_file(audio_track.get('path')):
ffmpeg_cmd.extend(['-i', audio_track.get('path')]) ffmpeg_cmd.extend(['-i', audio_track.get('path')])
else: else:
logging.error(f"Skip audio join: {audio_track.get('path')} dont exist") logging.error(f"Skip audio join: {audio_track.get('path')} dont exist")
@ -223,7 +223,7 @@ def join_audios(video_path: str, audio_tracks: List[Dict[str, str]], out_path: s
print() print()
time.sleep(0.5) time.sleep(0.5)
if not check_file_existence(out_path): if not os_manager.check_file(out_path):
logging.error("Missing output video for ffmpeg conversion audio.") logging.error("Missing output video for ffmpeg conversion audio.")
sys.exit(0) sys.exit(0)
@ -239,7 +239,7 @@ def join_subtitle(video_path: str, subtitles_list: List[Dict[str, str]], out_pat
- out_path (str): The path to save the output file. - out_path (str): The path to save the output file.
""" """
if not check_file_existence(video_path): if not os_manager.check_file(video_path):
logging.error("Missing input video for ffmpeg conversion.") logging.error("Missing input video for ffmpeg conversion.")
sys.exit(0) sys.exit(0)
@ -248,7 +248,7 @@ def join_subtitle(video_path: str, subtitles_list: List[Dict[str, str]], out_pat
# Add subtitle input files first # Add subtitle input files first
for subtitle in subtitles_list: for subtitle in subtitles_list:
if check_file_existence(subtitle.get('path')): if os_manager.check_file(subtitle.get('path')):
ffmpeg_cmd += ["-i", subtitle['path']] ffmpeg_cmd += ["-i", subtitle['path']]
else: else:
logging.error(f"Skip subtitle join: {subtitle.get('path')} doesn't exist") logging.error(f"Skip subtitle join: {subtitle.get('path')} doesn't exist")
@ -288,6 +288,6 @@ def join_subtitle(video_path: str, subtitles_list: List[Dict[str, str]], out_pat
print() print()
time.sleep(0.5) time.sleep(0.5)
if not check_file_existence(out_path): if not os_manager.check_file(out_path):
logging.error("Missing output video for ffmpeg conversion subtitle.") logging.error("Missing output video for ffmpeg conversion subtitle.")
sys.exit(0) sys.exit(0)

View File

@ -14,7 +14,7 @@ from tqdm import tqdm
# Internal utilities # Internal utilities
from Src.Util.color import Colors from Src.Util.color import Colors
from Src.Util.os import format_file_size, format_transfer_speed from Src.Util.os import internet_manager
from Src.Util._jsonConfig import config_manager from Src.Util._jsonConfig import config_manager
@ -86,8 +86,8 @@ class M3U8_Ts_Estimator:
download_speed = (new_value.bytes_recv - old_value.bytes_recv) / interval download_speed = (new_value.bytes_recv - old_value.bytes_recv) / interval
self.speed = { self.speed = {
"upload": format_transfer_speed(upload_speed), "upload": internet_manager.format_transfer_speed(upload_speed),
"download": format_transfer_speed(download_speed) "download": internet_manager.format_transfer_speed(download_speed)
} }
def get_average_speed(self) -> float: def get_average_speed(self) -> float:
@ -115,7 +115,7 @@ class M3U8_Ts_Estimator:
mean_size = total_size / len(self.ts_file_sizes) mean_size = total_size / len(self.ts_file_sizes)
# Return formatted mean size # Return formatted mean size
return format_file_size(mean_size) return internet_manager.format_file_size(mean_size)
except ZeroDivisionError as e: except ZeroDivisionError as e:
logging.error("Division by zero error occurred: %s", e) logging.error("Division by zero error occurred: %s", e)
@ -132,7 +132,7 @@ 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_file_size(self.now_downloaded_size) return internet_manager.format_file_size(self.now_downloaded_size)
def update_progress_bar(self, total_downloaded: int, duration: float, progress_counter: tqdm) -> None: def update_progress_bar(self, total_downloaded: int, duration: float, progress_counter: tqdm) -> None:
""" """

View File

@ -6,7 +6,7 @@ import logging
# Internal utilities # Internal utilities
from m3u8 import loads from m3u8 import loads
from Src.Util.os import format_file_size from Src.Util.os import internet_manager
# External libraries # External libraries
@ -250,7 +250,7 @@ class M3U8_Video:
result = [] result = []
for video in self.video_playlist: for video in self.video_playlist:
video_size = format_file_size((video['bandwidth'] * duration) / 8) video_size = internet_manager.format_file_size((video['bandwidth'] * duration) / 8)
result.append((video_size)) result.append((video_size))
return result return result

View File

@ -4,6 +4,7 @@ from rich.console import Console
from rich.prompt import Prompt, Confirm from rich.prompt import Prompt, Confirm
from rich.panel import Panel from rich.panel import Panel
from rich.table import Table from rich.table import Table
from rich.text import Text
# Variable # Variable

View File

@ -1,6 +1,5 @@
# 3.12.23 # 3.12.23
import warnings
import os import os
import platform import platform

View File

@ -1,268 +1,407 @@
# 24.01.24 # 24.01.24
import re
import io import io
import os import os
import sys import sys
import ssl import ssl
import time import time
import errno
import shutil import shutil
import hashlib import hashlib
import logging import logging
import platform import platform
import unidecode
import importlib import importlib
import subprocess import subprocess
import contextlib import contextlib
import pathvalidate
import urllib.request import urllib.request
import importlib.metadata import importlib.metadata
# External library # External library
import httpx import httpx
import unicodedata
# Internal utilities # Internal utilities
from .console import console from Src.Util.console import console
from ._jsonConfig import config_manager
# --> OS FILE ASCII # Variable
special_chars_to_remove = config_manager.get("DEFAULT", "special_chars_to_remove") OS_CONFIGURATIONS = {
'windows': {
'max_length': 255,
'invalid_chars': '<>:"/\\|?*',
'reserved_names': [
"CON", "PRN", "AUX", "NUL",
"COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
"LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"
],
'max_path': 255
},
'darwin': {
'max_length': 4096,
'invalid_chars': '/:',
'reserved_names': [],
'hidden_file_restriction': True
},
'linux': {
'max_length': 4096,
'invalid_chars': '/\0',
'reserved_names': []
}
}
def get_max_length_by_os(system: str) -> int:
"""
Determines the maximum length for a base name based on the operating system.
Parameters: class OsManager:
system (str): The operating system name. def __init__(self):
self.system = self._detect_system()
self.config = OS_CONFIGURATIONS.get(self.system, {})
Returns: def _detect_system(self) -> str:
int: The maximum length for the base name. """Detect and normalize operating system name."""
""" system = platform.system().lower()
if system == 'windows':
return 255 # NTFS and other common Windows filesystems support 255 characters for filenames if system in OS_CONFIGURATIONS:
elif system == 'darwin': # macOS return system
return 255 # HFS+ and APFS support 255 characters for filenames
elif system == 'linux':
return 255 # Most Linux filesystems (e.g., ext4) support 255 characters for filenames
else:
raise ValueError(f"Unsupported operating system: {system}") raise ValueError(f"Unsupported operating system: {system}")
def reduce_base_name(base_name: str) -> str: def _process_filename(self, filename: str) -> str:
""" """
Splits the file path into folder and base name, and reduces the base name based on the operating system. Comprehensively process filename with cross-platform considerations.
Args:
filename (str): Original filename.
Returns:
str: Processed filename.
"""
# Preserve file extension
logging.info("_process_filename: ", filename)
name, ext = os.path.splitext(filename)
# Handle length restrictions
if len(name) > self.config['max_length']:
name = self._truncate_filename(name)
# Reconstruct filename
processed_filename = name + ext
return processed_filename
Parameters: def _truncate_filename(self, name: str) -> str:
base_name (str): The name of the file. """
Truncate filename based on OS-specific rules.
Args:
name (str): Original filename.
Returns:
str: Truncated filename.
"""
logging.info("_truncate_filename: ", name)
Returns: if self.system == 'windows':
str: The reduced base name. return name[:self.config['max_length'] - 3] + '___'
""" elif self.system == 'darwin':
return name[:self.config['max_length']]
elif self.system == 'linux':
return name[:self.config['max_length'] - 2] + '___'
# Determine the operating system def get_sanitize_file(self, filename: str) -> str:
system = platform.system().lower() """
Sanitize filename using pathvalidate with unidecode.
Args:
filename (str): Original filename.
Returns:
str: Sanitized filename.
"""
logging.info("get_sanitize_file: ", filename)
# Decode unicode characters and sanitize
decoded_filename = unidecode.unidecode(filename)
sanitized_filename = pathvalidate.sanitize_filename(decoded_filename)
# Truncate if necessary based on OS configuration
name, ext = os.path.splitext(sanitized_filename)
if len(name) > self.config['max_length']:
name = self._truncate_filename(name)
logging.info("return :", name + ext)
return name + ext
def get_sanitize_path(self, path: str) -> str:
"""
Sanitize folder path using pathvalidate with unidecode.
Args:
path (str): Original folder path.
Returns:
str: Sanitized folder path.
"""
logging.info("get_sanitize_file: ", path)
# Decode unicode characters and sanitize
decoded_path = unidecode.unidecode(path)
sanitized_path = pathvalidate.sanitize_filepath(decoded_path)
# Split path and process each component
path_components = os.path.normpath(sanitized_path).split(os.sep)
processed_components = []
for component in path_components:
# Truncate component if necessary
if len(component) > self.config['max_length']:
component = self._truncate_filename(component)
processed_components.append(component)
logging.info("return :", os.path.join(*processed_components))
return os.path.join(*processed_components)
def create_path(self, path: str, mode: int = 0o755) -> bool:
"""
Create directory path with specified permissions.
Args:
path (str): Path to create.
mode (int, optional): Directory permissions. Defaults to 0o755.
Returns:
bool: True if path created successfully, False otherwise.
"""
try:
# Sanitize path first
sanitized_path = self.get_sanitize_path(path)
# Create directory with recursive option
os.makedirs(sanitized_path, mode=mode, exist_ok=True)
return True
except Exception as e:
logging.error(f"Path creation error: {e}")
return False
def remove_folder(self, folder_path: str) -> bool:
"""
Safely remove a folder.
Args:
folder_path (str): Path of directory to remove.
Returns:
bool: Removal status.
"""
try:
shutil.rmtree(folder_path)
return True
except OSError as e:
logging.error(f"Folder removal error: {e}")
return False
# Get the maximum length for the base name based on the operating system def remove_files_except_one(self, folder_path: str, keep_file: str) -> None:
max_length = get_max_length_by_os(system) """
Delete all files in a folder except for one specified file.
Parameters:
- folder_path (str): The path to the folder containing the files.
- keep_file (str): The filename to keep in the folder.
"""
try:
# List all files in the folder
files_in_folder = os.listdir(folder_path)
# Iterate over each file in the folder
for file_name in files_in_folder:
file_path = os.path.join(folder_path, file_name)
# Check if the file is not the one to keep and is a regular file
if file_name != keep_file and os.path.isfile(file_path):
os.remove(file_path) # Delete the file
except Exception as e:
logging.error(f"An error occurred: {e}")
raise
# Reduce the base name if necessary def check_file(self, file_path: str) -> bool:
if len(base_name) > max_length: """
if system == 'windows': Check if a file exists at the given file path.
# For Windows, truncate and add a suffix if needed
base_name = base_name[:max_length - 3] + '___'
elif system == 'darwin': # macOS
# For macOS, truncate without adding suffix
base_name = base_name[:max_length]
elif system == 'linux':
# For Linux, truncate and add a numeric suffix if needed
base_name = base_name[:max_length - 2] + '___'
return base_name
def remove_special_characters(input_string): Parameters:
""" file_path (str): The path to the file.
Remove specified special characters from a string.
Parameters: Returns:
- input_string (str): The input string containing special characters. bool: True if the file exists, False otherwise.
"""
try:
logging.info(f"Check if file exists: {file_path}")
if os.path.exists(file_path):
logging.info(f"The file '{file_path}' exists.")
return True
else:
return False
except Exception as e:
logging.error(f"An error occurred while checking file existence: {e}")
return False
Returns:
str: A new string with specified special characters removed.
"""
if input_string is None:
return "None"
# Check if the string ends with '.mp4' class InternManager():
# If it does, we temporarily remove the '.mp4' extension for processing
ends_with_mp4 = input_string.endswith('.mp4')
if ends_with_mp4:
input_string = input_string[:-4] # Remove the last 4 characters ('.mp4')
# Compile regular expression pattern to match special characters def format_file_size(self, size_bytes: float) -> str:
pattern = re.compile('[' + re.escape(special_chars_to_remove) + ']') """
Formats a file size from bytes into a human-readable string representation.
# Use compiled pattern to replace special characters with an empty string Parameters:
cleaned_string = pattern.sub('', input_string) size_bytes (float): Size in bytes to be formatted.
# If the original string had the '.mp4' extension, re-add it to the cleaned string Returns:
if ends_with_mp4: str: Formatted string representing the file size with appropriate unit (B, KB, MB, GB, TB).
cleaned_string += '.mp4' """
if size_bytes <= 0:
return "0B"
return cleaned_string.strip() units = ['B', 'KB', 'MB', 'GB', 'TB']
unit_index = 0
while size_bytes >= 1024 and unit_index < len(units) - 1:
size_bytes /= 1024
unit_index += 1
return f"{size_bytes:.2f} {units[unit_index]}"
def format_transfer_speed(self, bytes: float) -> str:
"""
Formats a transfer speed from bytes per second into a human-readable string representation.
Parameters:
bytes (float): Speed in bytes per second to be formatted.
Returns:
str: Formatted string representing the transfer speed with appropriate unit (Bytes/s, KB/s, MB/s).
"""
if bytes < 1024:
return f"{bytes:.2f} Bytes/s"
elif bytes < 1024 * 1024:
return f"{bytes / 1024:.2f} KB/s"
else:
return f"{bytes / (1024 * 1024):.2f} MB/s"
@staticmethod
def check_internet():
while True:
try:
httpx.get("https://www.google.com")
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()
class OsSummary():
def get_executable_version(self, command: list):
"""
Get the version of a given command-line executable.
Args:
command (list): The command to run, e.g., `['ffmpeg', '-version']`.
Returns:
str: The version string of the executable.
Raises:
SystemExit: If the command is not found or fails to execute.
"""
try:
version_output = subprocess.check_output(command, stderr=subprocess.STDOUT).decode().split('\n')[0]
return version_output.split(" ")[2]
except (FileNotFoundError, subprocess.CalledProcessError):
print(f"{command[0]} not found")
sys.exit(0)
def get_library_version(self, lib_name: str):
"""
Retrieve the version of a Python library.
Args:
lib_name (str): The name of the Python library.
Returns:
str: The library name followed by its version, or `-not installed` if not found.
"""
try:
version = importlib.metadata.version(lib_name)
return f"{lib_name}-{version}"
except importlib.metadata.PackageNotFoundError:
return f"{lib_name}-not installed"
def get_system_summary(self):
"""
Generate a summary of the system environment.
Includes:
- Python version and implementation details.
- Operating system and architecture.
- OpenSSL and glibc versions.
- Versions of `ffmpeg` and `ffprobe` executables.
- Installed Python libraries as listed in `requirements.txt`.
"""
# Check internet connectivity
InternManager().check_internet()
console.print("[bold blue]System Summary[/bold blue][white]:")
# Python version and platform
python_version = sys.version.split()[0]
python_implementation = platform.python_implementation()
arch = platform.machine()
os_info = platform.platform()
openssl_version = ssl.OPENSSL_VERSION
glibc_version = 'glibc ' + '.'.join(map(str, platform.libc_ver()[1]))
console.print(f"[cyan]Python[white]: [bold red]{python_version} ({python_implementation} {arch}) - {os_info} ({openssl_version}, {glibc_version})[/bold red]")
logging.info(f"Python: {python_version} ({python_implementation} {arch}) - {os_info} ({openssl_version}, {glibc_version})")
# ffmpeg and ffprobe versions
ffmpeg_version = self.get_executable_version(['ffmpeg', '-version'])
ffprobe_version = self.get_executable_version(['ffprobe', '-version'])
console.print(f"[cyan]Exe versions[white]: [bold red]ffmpeg {ffmpeg_version}, ffprobe {ffprobe_version}[/bold red]")
logging.info(f"Dependencies: ffmpeg {ffmpeg_version}, ffprobe {ffprobe_version}")
# Optional libraries versions
optional_libraries = [line.strip() for line in open('requirements.txt', 'r', encoding='utf-8-sig')]
optional_libs_versions = [self.get_library_version(lib) for lib in optional_libraries]
console.print(f"[cyan]Libraries[white]: [bold red]{', '.join(optional_libs_versions)}[/bold red]\n")
logging.info(f"Libraries: {', '.join(optional_libs_versions)}")
# --> OS MANAGE OUTPUT # OTHER
os_manager = OsManager()
internet_manager = InternManager()
os_summary = OsSummary()
@contextlib.contextmanager @contextlib.contextmanager
def suppress_output(): def suppress_output():
with contextlib.redirect_stdout(io.StringIO()): with contextlib.redirect_stdout(io.StringIO()):
yield yield
# --> OS MANAGE FOLDER
def create_folder(folder_name: str) -> None:
"""
Create a directory if it does not exist, and log the result.
Parameters:
folder_name (str): The path of the directory to be created.
"""
if platform.system() == 'Windows':
max_path_length = 260
else:
max_path_length = 4096
try:
logging.info(f"Try create folder: {folder_name}")
# Check if path length exceeds the maximum allowed
if len(folder_name) > max_path_length:
logging.error(f"Path length exceeds the maximum allowed limit: {len(folder_name)} characters (Max: {max_path_length})")
raise OSError(f"Path length exceeds the maximum allowed limit: {len(folder_name)} characters (Max: {max_path_length})")
os.makedirs(folder_name, exist_ok=True)
if os.path.exists(folder_name) and os.path.isdir(folder_name):
logging.info(f"Directory successfully created or already exists: {folder_name}")
else:
logging.error(f"Failed to create directory: {folder_name}")
except Exception as e:
logging.error(f"An unexpected error occurred while creating the directory {folder_name}: {e}")
raise
def check_file_existence(file_path):
"""
Check if a file exists at the given file path.
Parameters:
file_path (str): The path to the file.
Returns:
bool: True if the file exists, False otherwise.
"""
try:
logging.info(f"Check if file exists: {file_path}")
if os.path.exists(file_path):
logging.info(f"The file '{file_path}' exists.")
return True
else:
return False
except Exception as e:
logging.error(f"An error occurred while checking file existence: {e}")
return False
def remove_folder(folder_path: str) -> None:
"""
Remove a folder if it exists.
Parameters:
- folder_path (str): The path to the folder to be removed.
"""
if os.path.exists(folder_path):
try:
shutil.rmtree(folder_path)
except OSError as e:
print(f"Error removing folder '{folder_path}': {e}")
def delete_files_except_one(folder_path: str, keep_file: str) -> None:
"""
Delete all files in a folder except for one specified file.
Parameters:
- folder_path (str): The path to the folder containing the files.
- keep_file (str): The filename to keep in the folder.
"""
try:
# List all files in the folder
files_in_folder = os.listdir(folder_path)
# Iterate over each file in the folder
for file_name in files_in_folder:
file_path = os.path.join(folder_path, file_name)
# Check if the file is not the one to keep and is a regular file
if file_name != keep_file and os.path.isfile(file_path):
os.remove(file_path) # Delete the file
except Exception as e:
logging.error(f"An error occurred: {e}")
# --> OS MANAGE SIZE FILE AND INTERNET SPEED
def format_file_size(size_bytes: float) -> str:
"""
Formats a file size from bytes into a human-readable string representation.
Parameters:
size_bytes (float): Size in bytes to be formatted.
Returns:
str: Formatted string representing the file size with appropriate unit (B, KB, MB, GB, TB).
"""
if size_bytes <= 0:
return "0B"
units = ['B', 'KB', 'MB', 'GB', 'TB']
unit_index = 0
while size_bytes >= 1024 and unit_index < len(units) - 1:
size_bytes /= 1024
unit_index += 1
return f"{size_bytes:.2f} {units[unit_index]}"
def format_transfer_speed(bytes: float) -> str:
"""
Formats a transfer speed from bytes per second into a human-readable string representation.
Parameters:
bytes (float): Speed in bytes per second to be formatted.
Returns:
str: Formatted string representing the transfer speed with appropriate unit (Bytes/s, KB/s, MB/s).
"""
if bytes < 1024:
return f"{bytes:.2f} Bytes/s"
elif bytes < 1024 * 1024:
return f"{bytes / 1024:.2f} KB/s"
else:
return f"{bytes / (1024 * 1024):.2f} MB/s"
# --> OS MANAGE KEY AND IV HEX
def compute_sha1_hash(input_string: str) -> str: def compute_sha1_hash(input_string: str) -> str:
""" """
Computes the SHA-1 hash of the input string. Computes the SHA-1 hash of the input string.
@ -278,151 +417,3 @@ def compute_sha1_hash(input_string: str) -> str:
# Return the hashed string # Return the hashed string
return hashed_string return hashed_string
# --> OS GET SUMMARY
def check_internet():
while True:
try:
httpx.get("https://www.google.com")
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]
return version_output.split(" ")[2]
except (FileNotFoundError, subprocess.CalledProcessError):
print(f"{command[0]} not found")
sys.exit(0)
def get_library_version(lib_name):
try:
version = importlib.metadata.version(lib_name)
return f"{lib_name}-{version}"
except importlib.metadata.PackageNotFoundError:
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
python_version = sys.version.split()[0]
python_implementation = platform.python_implementation()
arch = platform.machine()
os_info = platform.platform()
openssl_version = ssl.OPENSSL_VERSION
glibc_version = 'glibc ' + '.'.join(map(str, platform.libc_ver()[1]))
console.print(f"[cyan]Python[white]: [bold red]{python_version} ({python_implementation} {arch}) - {os_info} ({openssl_version}, {glibc_version})[/bold red]")
logging.info(f"Python: {python_version} ({python_implementation} {arch}) - {os_info} ({openssl_version}, {glibc_version})")
# ffmpeg and ffprobe versions
ffmpeg_version = get_executable_version(['ffmpeg', '-version'])
ffprobe_version = get_executable_version(['ffprobe', '-version'])
console.print(f"[cyan]Exe versions[white]: [bold red]ffmpeg {ffmpeg_version}, ffprobe {ffprobe_version}[/bold red]")
logging.info(f"Dependencies: ffmpeg {ffmpeg_version}, ffprobe {ffprobe_version}")
# Optional libraries versions
optional_libraries = [line.strip() for line in open('requirements.txt', 'r', encoding='utf-8-sig')]
optional_libs_versions = [get_library_version(lib) for lib in optional_libraries]
console.print(f"[cyan]Libraries[white]: [bold red]{', '.join(optional_libs_versions)}[/bold red]\n")
logging.info(f"Libraries: {', '.join(optional_libs_versions)}")
# --> OS FILE VALIDATOR
# List of invalid characters for Windows filenames
WINDOWS_INVALID_CHARS = '<>:"/\\|?*'
WINDOWS_RESERVED_NAMES = [
"CON", "PRN", "AUX", "NUL",
"COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
"LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"
]
# Invalid characters for macOS filenames
MACOS_INVALID_CHARS = '/:'
# Invalid characters for Linux/Android filenames
LINUX_INVALID_CHARS = '/\0'
# Maximum path length for Windows
WINDOWS_MAX_PATH = 260
def is_valid_filename(filename, system):
"""
Validates if the given filename is valid for the specified system.
Parameters:
- filename (str): The filename to validate.
- system (str): The operating system, e.g., 'Windows', 'Darwin' (macOS), or others for Linux/Android.
Returns:
bool: True if the filename is valid, False otherwise.
"""
# Normalize Unicode
filename = unicodedata.normalize('NFC', filename)
# Common checks across all systems
if filename.endswith(' ') or filename.endswith('.') or filename.endswith('/'):
return False
if filename.startswith('.') and system == "Darwin":
return False
# System-specific checks
if system == "Windows":
if len(filename) > WINDOWS_MAX_PATH:
return False
if any(char in filename for char in WINDOWS_INVALID_CHARS):
return False
name, ext = os.path.splitext(filename)
if name.upper() in WINDOWS_RESERVED_NAMES:
return False
elif system == "Darwin": # macOS
if any(char in filename for char in MACOS_INVALID_CHARS):
return False
else: # Linux and Android
if any(char in filename for char in LINUX_INVALID_CHARS):
return False
return True
def can_create_file(file_path):
"""
Checks if a file can be created at the given file path.
Parameters:
- file_path (str): The path where the file is to be created.
Returns:
bool: True if the file can be created, False otherwise.
"""
current_system = platform.system()
if not is_valid_filename(os.path.basename(file_path), current_system):
return False
try:
with open(file_path, 'w') as file:
pass
os.remove(file_path) # Cleanup if the file was created
return True
except OSError as e:
if e.errno in (errno.EACCES, errno.ENOENT, errno.EEXIST, errno.ENOTDIR):
return False
raise

View File

@ -7,7 +7,6 @@
"clean_console": true, "clean_console": true,
"root_path": "Video", "root_path": "Video",
"map_episode_name": "%(tv_name)_S%(season)E%(episode)_%(episode_name)", "map_episode_name": "%(tv_name)_S%(season)E%(episode)_%(episode_name)",
"special_chars_to_remove": ".-!@#$%^&*()[]{}<>|`~'\";:,?=+\u00e2\u20ac\u00a6",
"config_qbit_tor": { "config_qbit_tor": {
"host": "192.168.1.59", "host": "192.168.1.59",
"port": "8080", "port": "8080",

View File

@ -6,6 +6,7 @@ m3u8
psutil psutil
unidecode unidecode
jsbeautifier jsbeautifier
pathvalidate
fake-useragent fake-useragent
qbittorrent-api qbittorrent-api
python-qbittorrent python-qbittorrent

4
run.py
View File

@ -16,7 +16,7 @@ from Src.Util.message import start_message
from Src.Util.console import console, msg from Src.Util.console import console, msg
from Src.Util._jsonConfig import config_manager from Src.Util._jsonConfig import config_manager
from Src.Upload.update import update as git_update from Src.Upload.update import update as git_update
from Src.Util.os import get_system_summary from Src.Util.os import os_summary
from Src.Lib.TMBD import tmdb from Src.Lib.TMBD import tmdb
from Src.Util.logger import Logger from Src.Util.logger import Logger
@ -108,7 +108,7 @@ def initialize():
log_not = Logger() log_not = Logger()
# Get system info # Get system info
get_system_summary() os_summary.get_system_summary()
# Set terminal size for win 7 # Set terminal size for win 7
if platform.system() == "Windows" and "7" in platform.version(): if platform.system() == "Windows" and "7" in platform.version():