Add qbit config.

Add separate cookie manager in config.
Add api to emulate node js .
Add 0 at the end of episode.
Add except time vs real time in HLS.
This commit is contained in:
Lovi-0 2024-06-23 19:18:06 +02:00
parent 433928b50f
commit 2b4d355d17
37 changed files with 599 additions and 359 deletions

View File

@ -29,8 +29,6 @@ Make sure you have the following prerequisites installed on your system:
* [ffmpeg](https://www.gyan.dev/ffmpeg/builds/)
* [opnessl](https://www.openssl.org) or [pycryptodome](https://pypi.org/project/pycryptodome/)
* [nodejs](https://nodejs.org/)
## Installation
Install the required Python libraries using the following command:

View File

@ -14,6 +14,27 @@ from Src.Util.os import remove_special_characters
MAP_EPISODE = config_manager.get('DEFAULT', 'map_episode_name')
def dynamic_format_number(n: int) -> str:
"""
Formats a number by adding a leading zero.
The width of the resulting string is dynamic, calculated as the number of digits in the number plus one.
Args:
- n (int): The number to format.
Returns:
- str: The formatted number as a string with a leading zero.
Examples:
>>> dynamic_format_number(1)
'01'
>>> dynamic_format_number(20)
'020'
"""
width = len(str(n)) + 1
return str(n).zfill(width)
def manage_selection(cmd_insert: str, max_count: int) -> List[int]:
"""
Manage user selection for seasons to download.
@ -45,6 +66,7 @@ def manage_selection(cmd_insert: str, max_count: int) -> List[int]:
logging.info(f"List return: {list_season_select}")
return list_season_select
def map_episode_title(tv_name: str, number_season: int, episode_number: int, episode_name: str) -> str:
"""
Maps the episode title to a specific format.
@ -60,8 +82,8 @@ def map_episode_title(tv_name: str, number_season: int, episode_number: int, epi
"""
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("%(season)", str(number_season))
map_episode_temp = map_episode_temp.replace("%(episode)", str(episode_number))
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_name)", remove_special_characters(episode_name))
# Additional fix

View File

@ -12,7 +12,7 @@ from bs4 import BeautifulSoup
# Internal utilities
from Src.Util.headers import get_headers
from Src.Util.os import run_node_script
from Src.Util.os import run_node_script, run_node_script_api
class VideoSource:
@ -118,9 +118,14 @@ class VideoSource:
"""
for script in soup.find_all("script"):
if "eval" in str(script):
new_script = str(script.text).replace("eval", "var a = ")
new_script = new_script.replace(")))", ")));console.log(a);")
return run_node_script(new_script)
# WITH INSTALL NODE JS
#new_script = str(script.text).replace("eval", "var a = ")
#new_script = new_script.replace(")))", ")));console.log(a);")
#return run_node_script(new_script)
# WITH API
return run_node_script_api(script.text)
return None
@ -155,7 +160,7 @@ class VideoSource:
pattern = r'data-link="(//supervideo[^"]+)"'
match = re.search(pattern, str(down_page_soup))
if not match:
logging.error("No match found for supervideo URL.")
logging.error("No player available for download.")
return None
supervideo_url = "https:" + match.group(1)

View File

@ -7,7 +7,7 @@ import logging
# Internal utilities
from Src.Util.console import console
from Src.Lib.Hls.downloader import Downloader
from Src.Lib.Downloader import HLS_Downloader
from Src.Util.message import start_message
@ -49,7 +49,7 @@ def download_film(title_name: str, url: str):
master_playlist = video_source.get_playlist()
# Download the film using the m3u8 playlist, and output filename
Downloader(
HLS_Downloader(
m3u8_playlist = master_playlist,
output_filename = os.path.join(mp4_path, mp4_name)
).start()

View File

@ -6,7 +6,7 @@ import logging
# Internal utilities
from Src.Util.console import console, msg
from Src.Lib.Hls.downloader import Downloader
from Src.Lib.Downloader import HLS_Downloader
from Src.Util.message import start_message
from ..Template import manage_selection
@ -50,7 +50,7 @@ def download_episode(index_select: int):
mp4_path = os.path.join(ROOT_PATH, SITE_NAME, MOVIE_FOLDER, video_source.series_name)
# Start downloading
Downloader(
HLS_Downloader(
m3u8_playlist = video_source.get_playlist(),
output_filename = os.path.join(mp4_path, mp4_name)
).start()

View File

@ -13,13 +13,16 @@ from bs4 import BeautifulSoup
# Internal utilities
from Src.Util.headers import get_headers
from Src.Util._jsonConfig import config_manager
# Logic class
from .SearchType import MediaItem
# Variable
from ...costant import COOKIE
class GetSerieInfo:
@ -28,10 +31,10 @@ class GetSerieInfo:
Initializes the GetSerieInfo object with default values.
Args:
dict_serie (MediaItem): Dictionary containing series information (optional).
- dict_serie (MediaItem): Dictionary containing series information (optional).
"""
self.headers = {'user-agent': get_headers()}
self.cookies = config_manager.get_dict('REQUESTS', 'index')
self.cookies = COOKIE
self.url = dict_serie.url
self.tv_name = None
self.list_episodes = None
@ -49,7 +52,7 @@ class GetSerieInfo:
# Make an HTTP request to the series URL
try:
response = httpx.get(self.url + "?area=online", cookies=self.cookies, headers=self.headers)
response = httpx.get(self.url + "?area=online", cookies=self.cookies, headers=self.headers, timeout=10)
response.raise_for_status()
except Exception as e:

View File

@ -14,6 +14,10 @@ from Src.Util.headers import get_headers
from Src.Util._jsonConfig import config_manager
# Variable
from ...costant import COOKIE
class VideoSource:
def __init__(self) -> None:
@ -25,7 +29,7 @@ class VideoSource:
cookie (dict): A dictionary to store cookies.
"""
self.headers = {'user-agent': get_headers()}
self.cookie = config_manager.get_dict('REQUESTS', 'index')
self.cookie = COOKIE
def setup(self, url: str) -> None:
"""

View File

@ -10,6 +10,7 @@ from Src.Util._jsonConfig import config_manager
SITE_NAME = os.path.basename(os.path.dirname(os.path.abspath(__file__)))
ROOT_PATH = config_manager.get('DEFAULT', 'root_path')
DOMAIN_NOW = config_manager.get('SITE', SITE_NAME)
COOKIE = config_manager.get_dict('SITE', SITE_NAME)['cookie']
MOVIE_FOLDER = "Movie"
SERIES_FOLDER = "Serie"

View File

@ -7,12 +7,11 @@ from urllib.parse import urlparse
# Internal utilities
from Src.Util.color import Colors
from Src.Util.console import console, msg
from Src.Util.console import console
from Src.Util.message import start_message
from Src.Util.os import create_folder, can_create_file
from Src.Util.table import TVShowManager
from Src.Lib.Hls.download_mp4 import MP4_downloader
from Src.Lib.Downloader import MP4_downloader
from ..Template import manage_selection, map_episode_title
@ -70,7 +69,6 @@ def donwload_video(scape_info_serie: GetSerieInfo, index_episode_selected: int)
url = master_playlist,
path = os.path.join(mp4_path, mp4_name),
referer = f"{parsed_url.scheme}://{parsed_url.netloc}/",
add_desc=f"{Colors.MAGENTA}video"
)

View File

@ -1,5 +1,6 @@
# 09.06.24
import sys
import logging
@ -21,7 +22,6 @@ from .Core.Class.SearchType import MediaManager
# Variable
from .costant import SITE_NAME
cookie_index = config_manager.get_dict('REQUESTS', 'index')
media_search_manager = MediaManager()
table_show_manager = TVShowManager()

View File

@ -11,7 +11,7 @@ from bs4 import BeautifulSoup
# Internal utilities
from Src.Util.headers import get_headers
from Src.Util.os import run_node_script
from Src.Util.os import run_node_script, run_node_script_api
class VideoSource:
@ -46,7 +46,7 @@ class VideoSource:
"""
try:
response = httpx.get(url, headers=self.headers, follow_redirects=True)
response = httpx.get(url, headers=self.headers, follow_redirects=True, timeout=10)
response.raise_for_status()
return response.text
@ -85,9 +85,14 @@ class VideoSource:
"""
for script in soup.find_all("script"):
if "eval" in str(script):
new_script = str(script.text).replace("eval", "var a = ")
new_script = new_script.replace(")))", ")));console.log(a);")
return run_node_script(new_script)
# WITH INSTALL NODE JS
#new_script = str(script.text).replace("eval", "var a = ")
#new_script = new_script.replace(")))", ")));console.log(a);")
#return run_node_script(new_script)
# WITH API
return run_node_script_api(script.text)
return None

View File

@ -9,7 +9,7 @@ import logging
from Src.Util.console import console, msg
from Src.Util.table import TVShowManager
from Src.Util.message import start_message
from Src.Lib.Hls.downloader import Downloader
from Src.Lib.Downloader import HLS_Downloader
from ..Template import manage_selection, map_episode_title
@ -53,7 +53,7 @@ def donwload_video(scape_info_serie: GetSerieInfo, index_season_selected: int, i
# Get m3u8 master playlist
master_playlist = video_source.get_playlist()
Downloader(
HLS_Downloader(
m3u8_playlist = master_playlist,
output_filename = os.path.join(mp4_path, mp4_name)
).start()

View File

@ -6,7 +6,7 @@ import logging
# Internal utilities
from Src.Util.console import console
from Src.Lib.Hls.downloader import Downloader
from Src.Lib.Downloader import HLS_Downloader
from Src.Util.message import start_message
@ -50,7 +50,7 @@ def download_film(id_film: str, title_name: str, domain: str):
mp4_path = os.path.join(ROOT_PATH, SITE_NAME, MOVIE_FOLDER, title_name)
# Download the film using the m3u8 playlist, and output filename
Downloader(
HLS_Downloader(
m3u8_playlist = master_playlist,
output_filename = os.path.join(mp4_path, mp4_format)
).start()

View File

@ -9,7 +9,7 @@ import logging
from Src.Util.console import console, msg
from Src.Util.message import start_message
from Src.Util.table import TVShowManager
from Src.Lib.Hls.downloader import Downloader
from Src.Lib.Downloader import HLS_Downloader
from ..Template import manage_selection, map_episode_title
@ -51,7 +51,7 @@ def donwload_video(tv_name: str, index_season_selected: int, index_episode_selec
master_playlist = video_source.get_playlist()
# Download the episode
Downloader(
HLS_Downloader(
m3u8_playlist = master_playlist,
output_filename = os.path.join(mp4_path, mp4_name)
).start()

View File

@ -0,0 +1,3 @@
# 20.02.24
from .downloader import HLS_Downloader

View File

@ -11,8 +11,6 @@ import httpx
from unidecode import unidecode
# Internal utilities
from Src.Util.headers import get_headers
from Src.Util._jsonConfig import config_manager
@ -22,7 +20,7 @@ from Src.Util.os import (
remove_folder,
delete_files_except_one,
compute_sha1_hash,
format_size,
format_file_size,
create_folder,
reduce_base_name,
remove_special_characters,
@ -31,19 +29,18 @@ from Src.Util.os import (
# Logic class
from ..FFmpeg import (
from ...FFmpeg import (
print_duration_table,
join_video,
join_audios,
join_subtitle
)
from ..M3U8 import (
from ...M3U8 import (
M3U8_Parser,
M3U8_Codec,
M3U8_UrlFix
)
from .segments import M3U8_Segments
from ..E_Table import report_table
# Config
@ -59,11 +56,11 @@ FILTER_CUSTOM_REOLUTION = config_manager.get_int('M3U8_PARSER', 'force_resolutio
# Variable
headers_index = config_manager.get_dict('REQUESTS', 'index')
headers_index = config_manager.get_dict('REQUESTS', 'user-agent')
class Downloader():
class HLS_Downloader():
def __init__(self, output_filename: str = None, m3u8_playlist:str = None, m3u8_index:str = None):
"""
@ -77,7 +74,8 @@ class Downloader():
self.m3u8_playlist = m3u8_playlist
self.m3u8_index = m3u8_index
self.output_filename = output_filename.replace(" ", "_")
self.output_filename = output_filename
self.expected_real_time = None
# Auto generate out file name if not present
if output_filename == None:
@ -87,6 +85,8 @@ class Downloader():
self.output_filename = os.path.join("missing", compute_sha1_hash(m3u8_index))
else:
# For missing output_filename
folder, base_name = os.path.split(self.output_filename) # Split file_folder output
base_name = reduce_base_name(remove_special_characters(base_name)) # Remove special char
create_folder(folder) # Create folder and check if exist
@ -94,6 +94,7 @@ class Downloader():
logging.error("Invalid mp4 name.")
sys.exit(0)
# Parse to only ascii for win, linux, mac and termux
self.output_filename = os.path.join(folder, base_name)
self.output_filename = unidecode(self.output_filename)
@ -134,23 +135,17 @@ class Downloader():
str: The text content of the response.
"""
# Send a GET request to the provided URL
logging.info(f"Test url: {url}")
headers_index = {'user-agent': get_headers()}
response = httpx.get(url, headers=headers_index)
try:
# Send a GET request to the provided URL
logging.info(f"Test url: {url}")
headers_index['user-agent'] = get_headers()
response = httpx.get(url, headers=headers_index)
response.raise_for_status()
if response.status_code == 200:
return response.text
return response.text
else:
logging.error(f"Test request to {url} failed with status code: {response.status_code}")
return None
except Exception as e:
logging.error(f"An unexpected error occurred with test request: {e}")
logging.error(f"Test request to {url} failed with error: {e}")
return None
def __manage_playlist__(self, m3u8_playlist_text):
@ -179,7 +174,7 @@ class Downloader():
# Check if there is some audios, else disable download
if self.list_available_audio != None:
console.print(f"[cyan]Find audios [white]=> [red]{[obj_audio.get('language') for obj_audio in self.list_available_audio]}")
console.print(f"[cyan]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")
@ -209,7 +204,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.print(f"[cyan]Find resolution [white]=> [red]{sorted(list_available_resolution, reverse=True)}")
console.print(f"[cyan]Resolutions [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:
@ -230,7 +225,7 @@ class Downloader():
logging.info(f"Find codec: {self.codec}")
if self.codec is not None:
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]))")
console.print(f"[cyan]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):
@ -260,6 +255,7 @@ class Downloader():
# Download the video segments
video_m3u8.download_streams(f"{Colors.MAGENTA}video")
self.expected_real_time = video_m3u8.expected_real_time
# Get time of output file
print_duration_table(os.path.join(full_path_video, "0.ts"))
@ -459,6 +455,10 @@ class Downloader():
- out_path (str): Path of the output file.
"""
def dict_to_seconds(d):
return d['h'] * 3600 + d['m'] * 60 + d['s']
# Check if file to rename exist
logging.info(f"Check if end file converted exist: {out_path}")
if out_path is None or not os.path.isfile(out_path):
@ -471,12 +471,29 @@ class Downloader():
# Rename file converted to original set in init
os.rename(out_path, self.output_filename)
# Print size of the file
console.print(Panel(
# Get dict with h m s for ouput file name
end_output_time = print_duration_table(self.output_filename, description=False, return_string=False)
# Calculate info for panel
formatted_size = format_file_size(os.path.getsize(self.output_filename))
formatted_duration = print_duration_table(self.output_filename, description=False, return_string=True)
expected_real_seconds = dict_to_seconds(self.expected_real_time)
end_output_seconds = dict_to_seconds(end_output_time)
missing_ts = not (expected_real_seconds - 3 <= end_output_seconds <= expected_real_seconds + 3)
panel_content = (
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"))
f"[cyan]File size: [bold red]{formatted_size}[/bold red]\n"
f"[cyan]Duration: [bold]{formatted_duration}[/bold]\n"
f"[cyan]Missing TS: [bold red]{missing_ts}[/bold red]"
)
console.print(Panel(
panel_content,
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))
@ -501,8 +518,6 @@ class Downloader():
if self.m3u8_playlist:
logging.info("Download from PLAYLIST")
m3u8_playlist_text = self.__df_make_req__(self.m3u8_playlist)
# Add full URL of the M3U8 playlist to fix next .ts without https if necessary

View File

@ -27,7 +27,7 @@ from Src.Util.call_stack import get_call_stack
# Logic class
from ..M3U8 import (
from ...M3U8 import (
M3U8_Decryption,
M3U8_Ts_Estimator,
M3U8_Parser,
@ -53,7 +53,7 @@ PROXY_START_MAX = config_manager.get_float('REQUESTS', 'proxy_start_max')
# Variable
headers_index = config_manager.get_dict('REQUESTS', 'index')
headers_index = config_manager.get_dict('REQUESTS', 'user-agent')
@ -68,6 +68,7 @@ class M3U8_Segments:
"""
self.url = url
self.tmp_folder = tmp_folder
self.expected_real_time = None
self.tmp_file_path = os.path.join(self.tmp_folder, "0.ts")
os.makedirs(self.tmp_folder, exist_ok=True)
@ -90,7 +91,7 @@ class M3U8_Segments:
Returns:
bytes: The encryption key in bytes.
"""
headers_index['user-agent'] = get_headers()
headers_index = {'user-agent': get_headers()}
# Construct the full URL of the key
key_uri = urljoin(self.url, m3u8_parser.keys.get('uri'))
@ -123,8 +124,9 @@ class M3U8_Segments:
m3u8_parser = M3U8_Parser()
m3u8_parser.parse_data(uri=self.url, raw_content=m3u8_content)
console.log(f"[red]Expected duration after download: {m3u8_parser.get_duration()}")
console.log(f"[red]There is key: [yellow]{m3u8_parser.keys is not None}")
#console.log(f"[red]Expected duration after download: {m3u8_parser.get_duration()}")
#console.log(f"[red]There is key: [yellow]{m3u8_parser.keys is not None}")
self.expected_real_time = m3u8_parser.get_duration(return_string=False)
# Check if there is an encryption key in the playlis
if m3u8_parser.keys is not None:
@ -170,7 +172,7 @@ class M3U8_Segments:
"""
Makes a request to the index M3U8 file to get information about segments.
"""
headers_index['user-agent'] = get_headers()
headers_index = {'user-agent': get_headers()}
# Send a GET request to retrieve the index M3U8 file
response = httpx.get(self.url, headers=headers_index)
@ -206,14 +208,14 @@ class M3U8_Segments:
proxy = self.valid_proxy[index % len(self.valid_proxy)]
logging.info(f"Use proxy: {proxy}")
with httpx.Client(transport=httpx.HTTPTransport(retries=3), proxies=proxy, verify=REQUEST_VERIFY) as client:
with httpx.Client(proxies=proxy, verify=REQUEST_VERIFY) as client:
if 'key_base_url' in self.__dict__:
response = client.get(ts_url, headers=random_headers(self.key_base_url), timeout=REQUEST_TIMEOUT)
else:
response = client.get(ts_url, headers={'user-agent': get_headers()}, timeout=REQUEST_TIMEOUT)
else:
with httpx.Client(transport=httpx.HTTPTransport(retries=3), verify=REQUEST_VERIFY) as client_2:
with httpx.Client(verify=REQUEST_VERIFY) as client_2:
if 'key_base_url' in self.__dict__:
response = client_2.get(ts_url, headers=random_headers(self.key_base_url), timeout=REQUEST_TIMEOUT)
else:
@ -298,16 +300,28 @@ class M3U8_Segments:
# Custom bar for mobile and pc
if TQDM_USE_LARGE_BAR:
bar_format=f"{Colors.YELLOW}Downloading {Colors.WHITE}({add_desc}{Colors.WHITE}): {Colors.RED}{{percentage:.2f}}% {Colors.MAGENTA}{{bar}} {Colors.WHITE}[ {Colors.YELLOW}{{n_fmt}}{Colors.WHITE} / {Colors.RED}{{total_fmt}} {Colors.WHITE}] {Colors.YELLOW}{{elapsed}} {Colors.WHITE}< {Colors.CYAN}{{remaining}}{{postfix}} {Colors.WHITE}]"
bar_format = (
f"{Colors.YELLOW}[HLS] {Colors.WHITE}({Colors.CYAN}{add_desc}{Colors.WHITE}): "
f"{Colors.RED}{{percentage:.2f}}% "
f"{Colors.MAGENTA}{{bar}} "
f"{Colors.WHITE}[ {Colors.YELLOW}{{n_fmt}}{Colors.WHITE} / {Colors.RED}{{total_fmt}} {Colors.WHITE}] "
f"{Colors.YELLOW}{{elapsed}} {Colors.WHITE}< {Colors.CYAN}{{remaining}}{{postfix}} {Colors.WHITE}]"
)
else:
bar_format=f"{Colors.YELLOW}Proc{Colors.WHITE}: {Colors.RED}{{percentage:.2f}}% {Colors.WHITE}| {Colors.CYAN}{{remaining}}{{postfix}} {Colors.WHITE}]"
bar_format = (
f"{Colors.YELLOW}Proc{Colors.WHITE}: "
f"{Colors.RED}{{percentage:.2f}}% "
f"{Colors.WHITE}| "
f"{Colors.CYAN}{{remaining}}{{postfix}} {Colors.WHITE}]"
)
# Create progress bar
progress_bar = tqdm(
total=len(self.segments),
unit='s',
ascii='░▒█',
bar_format=bar_format
bar_format=bar_format,
mininterval=0.05
)
# Start a separate thread to write segments to the file

View File

@ -0,0 +1,3 @@
# 23.06.24
from .downloader import MP4_downloader

View File

@ -15,11 +15,11 @@ from Src.Util.headers import get_headers
from Src.Util.color import Colors
from Src.Util.console import console, Panel
from Src.Util._jsonConfig import config_manager
from Src.Util.os import format_size
from Src.Util.os import format_file_size
# Logic class
from ..FFmpeg import print_duration_table
from ...FFmpeg import print_duration_table
# Config
@ -29,19 +29,32 @@ REQUEST_TIMEOUT = config_manager.get_float('REQUESTS', 'timeout')
def MP4_downloader(url: str, path: str, referer: str, add_desc: str):
def MP4_downloader(url: str, path: str, referer: str):
"""
Downloads an MP4 video from a given URL using the specified referer header.
Parameter:
- url (str): The URL of the MP4 video to download.
- path (str): The local path where the downloaded MP4 file will be saved.
- referer (str): The referer header value to include in the HTTP request headers.
"""
# Make request to get content of video
logging.info(f"Make request to fetch mp4 from: {url}")
headers = {'Referer': referer, 'user-agent': get_headers()}
if referer != None:
headers = {'Referer': referer, 'user-agent': get_headers()}
else:
headers = {'user-agent': get_headers()}
with httpx.Client(verify=REQUEST_VERIFY, timeout=REQUEST_TIMEOUT) as client:
with client.stream("GET", url, headers=headers, timeout=99) as response:
with client.stream("GET", url, headers=headers, timeout=10) as response:
total = int(response.headers.get('content-length', 0))
# Create bar format
if TQDM_USE_LARGE_BAR:
bar_format = (f"{Colors.YELLOW}Downloading {Colors.WHITE}({add_desc}{Colors.WHITE}): "
bar_format = (f"{Colors.YELLOW}[MP4] {Colors.WHITE}({Colors.CYAN}video{Colors.WHITE}): "
f"{Colors.RED}{{percentage:.2f}}% {Colors.MAGENTA}{{bar}} {Colors.WHITE}[ "
f"{Colors.YELLOW}{{n_fmt}}{Colors.WHITE} / {Colors.RED}{{total_fmt}} {Colors.WHITE}] "
f"{Colors.YELLOW}{{elapsed}} {Colors.WHITE}< {Colors.CYAN}{{remaining}} {Colors.WHITE}| "
@ -53,11 +66,11 @@ def MP4_downloader(url: str, path: str, referer: str, add_desc: str):
# Create progress bar
progress_bar = tqdm(
total=total,
unit='iB',
ascii='░▒█',
bar_format=bar_format,
unit_scale=True,
unit_divisor=1024
unit_divisor=1024,
mininterval=0.05
)
# Download file
@ -70,8 +83,8 @@ def MP4_downloader(url: str, path: str, referer: str, add_desc: str):
# Get summary
console.print(Panel(
f"[bold green]Download completed![/bold green]\n"
f"File size: [bold red]{format_size(os.path.getsize(path))}[/bold red]\n"
f"Duration: [bold]{print_duration_table(path, show=False)}[/bold]",
f"[cyan]File size: [bold red]{format_file_size(os.path.getsize(path))}[/bold red]\n"
f"[cyan]Duration: [bold]{print_duration_table(path, description=False, return_string=True)}[/bold]",
title=f"{os.path.basename(path.replace('.mp4', ''))}",
border_style="green"
))

View File

@ -0,0 +1,3 @@
# 23.06.24
from .downloader import TOR_downloader

View File

@ -0,0 +1,209 @@
# 23.06.24
import os
import time
import shutil
import logging
# Internal utilities
from Src.Util.color import Colors
from Src.Util.os import format_file_size, format_transfer_speed
from Src.Util._jsonConfig import config_manager
# External libraries
from tqdm import tqdm
from qbittorrent import Client
# Tor config
HOST = str(config_manager.get_dict('DEFAULT', 'config_qbit_tor')['host'])
PORT = str(config_manager.get_dict('DEFAULT', 'config_qbit_tor')['port'])
USERNAME = str(config_manager.get_dict('DEFAULT', 'config_qbit_tor')['user'])
PASSWORD = str(config_manager.get_dict('DEFAULT', 'config_qbit_tor')['pass'])
# Config
TQDM_USE_LARGE_BAR = config_manager.get_int('M3U8_DOWNLOAD', 'tqdm_use_large_bar')
REQUEST_VERIFY = config_manager.get_float('REQUESTS', 'verify_ssl')
REQUEST_TIMEOUT = config_manager.get_float('REQUESTS', 'timeout')
class TOR_downloader:
def __init__(self):
"""
Initializes the TorrentManager instance.
Parameters:
- host (str): IP address or hostname of the qBittorrent Web UI.
- port (int): Port number of the qBittorrent Web UI.
- username (str): Username for logging into qBittorrent.
- password (str): Password for logging into qBittorrent.
"""
self.qb = Client(f'http://{HOST}:{PORT}/')
self.username = USERNAME
self.password = PASSWORD
self.logged_in = False
self.save_path = None
self.torrent_name = None
self.login()
def login(self):
"""
Logs into the qBittorrent Web UI.
"""
try:
self.qb.login(self.username, self.password)
self.logged_in = True
logging.info("Successfully logged in to qBittorrent.")
except Exception as e:
logging.error(f"Failed to log in: {str(e)}")
self.logged_in = False
def add_magnet_link(self, magnet_link):
"""
Adds a torrent via magnet link to qBittorrent.
Parameters:
- magnet_link (str): Magnet link of the torrent to be added.
"""
try:
self.qb.download_from_link(magnet_link)
logging.info("Added magnet link to qBittorrent.")
# Get the hash of the latest added torrent
torrents = self.qb.torrents()
if torrents:
self.latest_torrent_hash = torrents[-1]['hash']
logging.info(f"Latest torrent hash: {self.latest_torrent_hash}")
except Exception as e:
logging.error(f"Failed to add magnet link: {str(e)}")
def start_download(self):
"""
Starts downloading the latest added torrent and monitors progress.
"""
try:
torrents = self.qb.torrents()
if not torrents:
logging.error("No torrents found.")
return
# Sleep to load magnet to qbit app
time.sleep(5)
latest_torrent = torrents[-1]
torrent_hash = latest_torrent['hash']
# Custom bar for mobile and pc
if TQDM_USE_LARGE_BAR:
bar_format = (
f"{Colors.YELLOW}[TOR] {Colors.WHITE}({Colors.CYAN}video{Colors.WHITE}): "
f"{Colors.RED}{{percentage:.2f}}% {Colors.MAGENTA}{{bar}} {Colors.WHITE}[ "
f"{Colors.YELLOW}{{elapsed}} {Colors.WHITE}< {Colors.CYAN}{{remaining}}{{postfix}} {Colors.WHITE}]"
)
else:
bar_format = (
f"{Colors.YELLOW}Proc{Colors.WHITE}: "
f"{Colors.RED}{{percentage:.2f}}% {Colors.WHITE}| "
f"{Colors.CYAN}{{remaining}}{{postfix}} {Colors.WHITE}]"
)
progress_bar = tqdm(
total=100,
ascii='░▒█',
bar_format=bar_format,
unit_scale=True,
unit_divisor=1024,
mininterval=0.05
)
with progress_bar as pbar:
while True:
# Get variable from qtorrent
torrent_info = self.qb.get_torrent(torrent_hash)
self.save_path = torrent_info['save_path']
self.torrent_name = torrent_info['name']
# Fetch important variable
pieces_have = torrent_info['pieces_have']
pieces_num = torrent_info['pieces_num']
progress = (pieces_have / pieces_num) * 100 if pieces_num else 0
pbar.n = progress
download_speed = torrent_info['dl_speed']
total_size = torrent_info['total_size']
downloaded_size = torrent_info['total_downloaded']
# Format variable
downloaded_size_str = format_file_size(downloaded_size)
downloaded_size = downloaded_size_str.split(' ')[0]
total_size_str = format_file_size(total_size)
total_size = total_size_str.split(' ')[0]
total_size_unit = total_size_str.split(' ')[1]
average_internet_str = format_transfer_speed(download_speed)
average_internet = average_internet_str.split(' ')[0]
average_internet_unit = average_internet_str.split(' ')[1]
# Update the progress bar's postfix
if TQDM_USE_LARGE_BAR:
pbar.set_postfix_str(
f"{Colors.WHITE}[ {Colors.GREEN}{downloaded_size} {Colors.WHITE}< {Colors.GREEN}{total_size} {Colors.RED}{total_size_unit} "
f"{Colors.WHITE}| {Colors.CYAN}{average_internet} {Colors.RED}{average_internet_unit}"
)
else:
pbar.set_postfix_str(
f"{Colors.WHITE}[ {Colors.GREEN}{downloaded_size}{Colors.RED} {total_size} "
f"{Colors.WHITE}| {Colors.CYAN}{average_internet} {Colors.RED}{average_internet_unit}"
)
pbar.refresh()
time.sleep(0.2)
# Break at the end
if int(progress) == 100:
break
except KeyboardInterrupt:
logging.info("Download process interrupted.")
except Exception as e:
logging.error(f"Download error: {str(e)}")
def move_downloaded_files(self, destination=None):
"""
Moves downloaded files of the latest torrent to another location.
Parameters:
- save_path (str): Current save path (output directory) of the torrent.
- destination (str, optional): Destination directory to move files. If None, moves to current directory.
Returns:
- bool: True if files are moved successfully, False otherwise.
"""
# List directories in the save path
dirs = [d for d in os.listdir(self.save_path) if os.path.isdir(os.path.join(self.save_path, d))]
for dir_name in dirs:
if dir_name in self.torrent_name :
dir_path = os.path.join(self.save_path, dir_name)
if destination:
destination_path = os.path.join(destination, dir_name)
else:
destination_path = os.path.join(os.getcwd(), dir_name)
shutil.move(dir_path, destination_path)
logging.info(f"Moved directory {dir_name} to {destination_path}")
break
return True

View File

@ -0,0 +1,5 @@
# 23.06.24
from .HLS import HLS_Downloader
from .MP4 import MP4_downloader
from .TOR import TOR_downloader

View File

@ -1,4 +0,0 @@
# 20.05.24
from .sql_table import SimpleDBManager, report_table
report_table: SimpleDBManager = report_table

View File

@ -1,212 +0,0 @@
# 20.05.24
import csv
import logging
# Internal utilities
from Src.Util._jsonConfig import config_manager
# Variable
CREATE_REPORT = config_manager.get_bool('M3U8_DOWNLOAD', 'create_report')
class SimpleDBManager:
def __init__(self, filename, columns):
"""
Initialize a new database manager.
Args:
- filename (str): The name of the CSV file containing the database.
- columns (list): List of database columns.
"""
self.filename = filename
self.db = []
self.columns = columns
logging.info("Database manager initialized.")
def load_database(self):
"""
Load the database from the specified CSV file.
If the file doesn't exist, initialize a new database.
"""
try:
with open(self.filename, 'r', newline='') as file:
reader = csv.reader(file)
self.db = list(reader)
logging.info(f"Database {self.filename} loaded successfully.")
except FileNotFoundError:
logging.warning(f"File {self.filename} not found, creating a new database...")
self.initialize_database()
def initialize_database(self, columns=None, rows=None):
"""
Initialize a new database with specified columns and rows.
Args:
- columns (list, optional): List of database columns. If not specified, uses the columns provided in the constructor.
- rows (list, optional): List of database rows. Each row should be a list of values. If not specified, the database will be empty.
"""
self.db = [self.columns]
if rows:
for row_data in rows:
self.add_row_to_database(row_data)
logging.info("Database initialized successfully.")
def add_row_to_database(self, *row_data):
"""
Add a new row to the database.
Args:
- row_data (list): List of values for the new row.
"""
self.db.append(list(row_data))
logging.info("New row added to the database.")
def add_column_to_database(self, column_name, default_value=''):
"""
Add a new column to the database.
Args:
- column_name (str): Name of the new column.
- default_value (str, optional): Default value to be inserted in cells of the new column. Default is an empty string.
"""
for row in self.db:
row.append(default_value)
self.db[0][-1] = column_name
logging.info(f"New column '{column_name}' added to the database.")
def update_row_in_database(self, row_index, new_row_data):
"""
Update an existing row in the database.
Args:
- row_index (int): Index of the row to update.
- new_row_data (list): List of the new values for the updated row.
"""
self.db[row_index] = new_row_data
logging.info(f"Row {row_index} of the database updated.")
def remove_row_from_database(self, column_index: int, search_value) -> list:
"""
Remove a row from the database based on a specific column value.
Args:
- column_index (int): Index of the column to search in.
- search_value: The value to search for in the specified column.
Returns:
list: The removed row from the database, if found; otherwise, an empty list.
"""
# Find the index of the row with the specified value in the specified column
row_index = None
for i, row in enumerate(self.db):
if row[column_index] == search_value:
row_index = i
break
# If the row with the specified value is found, remove it
remove_row = []
if row_index is not None:
remove_row = self.db[row_index]
del self.db[row_index]
logging.info(f"Row at index {row_index} with value {search_value} in column {column_index} removed from the database.")
else:
logging.warning(f"No row found with value {search_value} in column {column_index}. Nothing was removed from the database.")
return remove_row
def remove_column_from_database(self, col_index):
"""
Remove a column from the database.
Args:
- col_index (int): Index of the column to remove.
"""
for row in self.db:
del row[col_index]
logging.info(f"Column {col_index} of the database removed.")
def save_database(self):
"""
Save the database to the CSV file specified in the constructor.
"""
with open(self.filename, 'w', newline='') as file:
writer = csv.writer(file)
writer.writerows(self.db)
logging.info("Database saved to file.")
def print_database_as_sql(self):
"""
Print the database in SQL format to the console.
"""
max_lengths = [max(len(str(cell)) for cell in col) for col in zip(*self.db)]
line = "+-" + "-+-".join("-" * length for length in max_lengths) + "-+"
print(line)
print("| " + " | ".join(f"{cell:<{length}}" for cell, length in zip(self.db[0], max_lengths)) + " |")
print(line)
for row in self.db[1:]:
print("| " + " | ".join(f"{cell:<{length}}" for cell, length in zip(row, max_lengths)) + " |")
print(line)
logging.info("Database printed as SQL.")
def search_database(self, column_index, value):
"""
Search the database for rows matching the specified value in the given column.
Args:
- column_index (int): Index of the column to search on.
- value (str): Value to search for.
Returns:
list: List of rows matching the search.
"""
results = []
for row in self.db[1:]:
if row[column_index] == value:
results.append(row)
logging.info(f"Database searched for value '{value}' in column {column_index}. Found {len(results)} matches.")
return results
def sort_database(self, column_index):
"""
Sort the database based on values in the specified column.
Args:
- column_index (int): Index of the column to sort on.
"""
self.db[1:] = sorted(self.db[1:], key=lambda x: x[column_index])
logging.info(f"Database sorted based on column {column_index}.")
def filter_database(self, column_index, condition):
"""
Filter the database based on a condition on the specified column.
Args:
- column_index (int): Index of the column to apply the condition on.
- condition (function): Condition function to apply on the values of the column. Should return True or False.
Returns:
list: List of rows satisfying the condition.
"""
results = [self.db[0]] # Keep the header row
for row in self.db[1:]:
if condition(row[column_index]):
results.append(row)
logging.info(f"Filter applied on column {column_index}. Found {len(results) - 1} rows satisfying the condition.")
return results
# Output
if CREATE_REPORT:
report_table = SimpleDBManager("riepilogo.csv", ["Date", "Name", "Size"])
report_table.load_database()
report_table.save_database()
else:
report_table = None

View File

@ -12,7 +12,7 @@ from typing import Tuple
# Internal utilities
from Src.Util.console import console
from Src.Util.os import format_size
from Src.Util.os import format_file_size
# Variable
@ -61,12 +61,11 @@ def capture_output(process: subprocess.Popen, description: str) -> None:
else:
byte_size = int(re.findall(r'\d+', data.get('size', '0'))[0]) * 1000
time_now = datetime.now().strftime('%H:%M:%S')
# Construct the progress string with formatted output information
progress_string = (f"[blue][{time_now}][purple] FFmpeg [white][{description}[white]]: "
progress_string = (f"[yellow][FFmpeg] [white][{description}[white]]: "
f"([green]'speed': [yellow]{data.get('speed', 'N/A')}[white], "
f"[green]'size': [yellow]{format_size(byte_size)}[white])")
f"[green]'size': [yellow]{format_file_size(byte_size)}[white])")
max_length = max(max_length, len(progress_string))
# Print the progress string to the console, overwriting the previous line

View File

@ -92,22 +92,44 @@ def format_duration(seconds: float) -> Tuple[int, int, int]:
return int(hours), int(minutes), int(seconds)
def print_duration_table(file_path: str, show = True) -> None:
def print_duration_table(file_path: str, description: str = "Duration", return_string: bool = False):
"""
Print duration of a video file in hours, minutes, and seconds.
Print the duration of a video file in hours, minutes, and seconds, or return it as a formatted string.
Args:
- file_path (str): The path to the video file.
- description (str): Optional description to be included in the output. Defaults to "Duration". If not provided, the duration will not be printed.
- return_string (bool): If True, returns the formatted duration string. If False, returns a dictionary with hours, minutes, and seconds.
Returns:
- str: The formatted duration string if return_string is True.
- dict: A dictionary with keys 'h', 'm', 's' representing hours, minutes, and seconds if return_string is False.
Example usage:
>>> print_duration_table("path/to/video.mp4")
[cyan]Duration for [white]([green]video.mp4[white]): [yellow]1[red]h [yellow]1[red]m [yellow]1[red]s
>>> print_duration_table("path/to/video.mp4", description=None)
'[yellow]1[red]h [yellow]1[red]m [yellow]1[red]s'
>>> print_duration_table("path/to/video.mp4", description=None, return_string=False)
{'h': 1, 'm': 1, 's': 1}
"""
video_duration = get_video_duration(file_path)
if video_duration is not None:
hours, minutes, seconds = format_duration(video_duration)
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")
formatted_duration = f"[yellow]{int(hours)}[red]h [yellow]{int(minutes)}[red]m [yellow]{int(seconds)}[red]s"
duration_dict = {'h': hours, 'm': minutes, 's': seconds}
if description:
console.print(f"[cyan]{description} for [white]([green]{os.path.basename(file_path)}[white]): {formatted_duration}")
else:
return f"[yellow]{int(hours)}[red]h [yellow]{int(minutes)}[red]m [yellow]{int(seconds)}[red]s"
if return_string:
return formatted_duration
else:
return duration_dict
def get_ffprobe_info(file_path):

View File

@ -1,3 +0,0 @@
# 20.02.24
from .downloader import Downloader

View File

@ -14,7 +14,7 @@ from tqdm import tqdm
# Internal utilities
from Src.Util.color import Colors
from Src.Util.os import format_size
from Src.Util.os import format_file_size, format_transfer_speed
from Src.Util._jsonConfig import config_manager
@ -64,15 +64,6 @@ class M3U8_Ts_Estimator:
io_counters = psutil.net_io_counters()
return io_counters
def format_bytes(bytes):
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"
# Get proc id
pid = os.getpid()
@ -88,8 +79,8 @@ class M3U8_Ts_Estimator:
download_speed = (new_value.bytes_recv - old_value.bytes_recv) / interval
self.speed = ({
"upload": format_bytes(upload_speed),
"download": format_bytes(download_speed)
"upload": format_transfer_speed(upload_speed),
"download": format_transfer_speed(download_speed)
})
old_value = new_value
@ -103,7 +94,7 @@ class M3U8_Ts_Estimator:
float: The average internet speed in Mbps.
"""
with self.lock:
return self.speed['download'].split(" ")
return self.speed['download'].split(" ")
def calculate_total_size(self) -> str:
"""
@ -120,7 +111,7 @@ class M3U8_Ts_Estimator:
mean_size = total_size / len(self.ts_file_sizes)
# Return formatted mean size
return format_size(mean_size)
return format_file_size(mean_size)
except ZeroDivisionError as e:
logging.error("Division by zero error occurred: %s", e)
@ -137,7 +128,7 @@ class M3U8_Ts_Estimator:
Returns:
str: The total downloaded size as a human-readable string.
"""
return format_size(self.now_downloaded_size)
return format_file_size(self.now_downloaded_size)
def update_progress_bar(self, total_downloaded: int, duration: float, progress_counter: tqdm) -> None:
"""

View File

@ -584,20 +584,34 @@ class M3U8_Parser:
self._audio = M3U8_Audio(self.audio_playlist)
self._subtitle = M3U8_Subtitle(self.subtitle_playlist)
def get_duration(self):
def get_duration(self, return_string:bool = True):
"""
Convert duration from seconds to hours, minutes, and remaining seconds.
Parameters:
- seconds (float): Duration in seconds.
- return_string (bool): If True, returns the formatted duration string.
If False, returns a dictionary with hours, minutes, and seconds.
Returns:
- formatted_duration (str): Formatted duration string with hours, minutes, and seconds.
- formatted_duration (str): Formatted duration string with hours, minutes, and seconds if return_string is True.
- duration_dict (dict): Dictionary with keys 'h', 'm', 's' representing hours, minutes, and seconds respectively if return_string is False.
Example usage:
>>> obj = YourClass(duration=3661)
>>> obj.get_duration()
'[yellow]1[red]h [yellow]1[red]m [yellow]1[red]s'
>>> obj.get_duration(return_string=False)
{'h': 1, 'm': 1, 's': 1}
"""
# Calculate hours, minutes, and remaining seconds
hours = int(self.duration / 3600)
minutes = int((self.duration % 3600) / 60)
remaining_seconds = int(self.duration % 60)
hours, remainder = divmod(self.duration, 3600)
minutes, seconds = divmod(remainder, 60)
# Format the duration string with colors
return f"[yellow]{int(hours)}[red]h [yellow]{int(minutes)}[red]m [yellow]{int(remaining_seconds)}[red]s"
if return_string:
return f"[yellow]{int(hours)}[red]h [yellow]{int(minutes)}[red]m [yellow]{int(seconds)}[red]s"
else:
return {'h': int(hours), 'm': int(minutes), 's': int(seconds)}

View File

@ -23,11 +23,13 @@ from typing import List
# External library
import httpx
import unicodedata
# Internal utilities
from .console import console
from .headers import get_headers
@ -316,32 +318,45 @@ def clean_json(path: str) -> None:
# --> OS MANAGE SIZE FILE
def format_size(size_bytes: float) -> str:
# --> OS MANAGE SIZE FILE AND INTERNET SPEED
def format_file_size(size_bytes: float) -> str:
"""
Format the size in bytes into a human-readable format.
Formats a file size from bytes into a human-readable string representation.
Args:
- size_bytes (float): The size in bytes to be formatted.
size_bytes (float): Size in bytes to be formatted.
Returns:
str: The formatted size.
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
# Convert bytes to appropriate unit
while size_bytes >= 1024 and unit_index < len(units) - 1:
size_bytes /= 1024
unit_index += 1
# Round the size to two decimal places and return with the appropriate unit
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.
Args:
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"
@ -452,7 +467,6 @@ def get_system_summary():
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'])
@ -524,6 +538,79 @@ def run_node_script(script_content: str) -> str:
import os
os.remove('script.js')
def run_node_script_api(script_content: str) -> str:
"""
Runs a Node.js script and returns its output.
Args:
script_content (str): The content of the Node.js script to run.
Returns:
str: The output of the Node.js script.
"""
headers = {
'accept': '*/*',
'accept-language': 'it-IT,it;q=0.9,en-US;q=0.8,en;q=0.7',
'dnt': '1',
'origin': 'https://onecompiler.com',
'priority': 'u=1, i',
'referer': 'https://onecompiler.com/javascript',
'user-agent': get_headers()
}
json_data = {
'name': 'JavaScript',
'title': '42gyum6qn',
'version': 'ES6',
'mode': 'javascript',
'description': None,
'extension': 'js',
'languageType': 'programming',
'active': False,
'properties': {
'language': 'javascript',
'docs': False,
'tutorials': False,
'cheatsheets': False,
'filesEditable': False,
'filesDeletable': False,
'files': [
{
'name': 'index.js',
'content': script_content
},
],
'newFileOptions': [
{
'helpText': 'New JS file',
'name': 'script${i}.js',
'content': "/**\n * In main file\n * let script${i} = require('./script${i}');\n * console.log(script${i}.sum(1, 2));\n */\n\nfunction sum(a, b) {\n return a + b;\n}\n\nmodule.exports = { sum };",
},
{
'helpText': 'Add Dependencies',
'name': 'package.json',
'content': '{\n "name": "main_app",\n "version": "1.0.0",\n "description": "",\n "main": "HelloWorld.js",\n "dependencies": {\n "lodash": "^4.17.21"\n }\n}',
},
],
},
'_id': '42gcvpkbg_42gyuud7m',
'user': None,
'visibility': 'public',
}
# Return error
response = httpx.post('https://onecompiler.com/api/code/exec', headers=headers, json=json_data)
response.raise_for_status()
if response.status_code == 200:
return str(response.json()['stderr']).split("\n")[1]
else:
logging.error("Cant connect to site: onecompiler.com")
sys.exit(0)
# --> OS FILE VALIDATOR

View File

@ -1,8 +1,14 @@
import tkinter as tk
from threading import Thread, Lock
from collections import deque
import psutil
# 23.06.24
import time
from collections import deque
from threading import Thread, Lock
# External library
import psutil
import tkinter as tk
class NetworkMonitor:
def __init__(self, maxlen=10):
@ -38,6 +44,7 @@ class NetworkMonitor:
old_value = new_value
class NetworkMonitorApp:
def __init__(self, root):
self.monitor = NetworkMonitor()
@ -77,7 +84,8 @@ class NetworkMonitorApp:
self.monitor_thread = Thread(target=self.monitor.capture_speed, args=(0.5,), daemon=True)
self.monitor_thread.start()
if __name__ == "__main__":
root = tk.Tk()
app = NetworkMonitorApp(root)
root.mainloop()
root = tk.Tk()
app = NetworkMonitorApp(root)
root.mainloop()

9
Test/t_down_hls.py Normal file
View File

@ -0,0 +1,9 @@
# 23.06.24
from Src.Lib.Downloader import HLS_Downloader
HLS_Downloader(
output_filename="EP_1.mp4",
m3u8_playlist=""
)

8
Test/t_down_mp4.py Normal file
View File

@ -0,0 +1,8 @@
# 23.06.24
from Src.Lib.Downloader import MP4_downloader
MP4_downloader(
"",
"EP_1.mp4"
)

10
Test/t_down_tor.py Normal file
View File

@ -0,0 +1,10 @@
# 23.06.24
from Src.Lib.Downloader import TOR_downloader
manager = TOR_downloader()
magnet_link = "magnet:?x"
manager.add_magnet_link(magnet_link)
manager.start_download()
manager.move_downloaded_files()

View File

@ -8,15 +8,19 @@
"root_path": "Video",
"map_episode_name": "%(tv_name)_S%(season)E%(episode)_%(episode_name)",
"auto_update_domain": true,
"config_qbit_tor": {
"host": "192.168.1.1",
"port": "8080",
"user": "admin",
"pass": "admin"
},
"not_close": false
},
"REQUESTS": {
"timeout": 10,
"max_retry": 3,
"verify_ssl": false,
"index": {
"user-agent": ""
},
"user-agent": "",
"proxy_start_min": 0.1,
"proxy_start_max": 0.5,
"proxy": []
@ -75,7 +79,12 @@
"ddlstreamitaly": {
"video_workers": -1,
"audio_workers": -1,
"domain": "co"
"domain": "co",
"cookie": {
"ips4_device_key": "",
"ips4_member_id": "",
"ips4_login_key": ""
}
}
}
}

View File

@ -7,4 +7,5 @@ tqdm
m3u8
psutil
unidecode
fake-useragent
fake-useragent
qbittorrent-api