diff --git a/Src/Api/Template/Util/get_domain.py b/Src/Api/Template/Util/get_domain.py
index f2e75a0..db3997c 100644
--- a/Src/Api/Template/Util/get_domain.py
+++ b/Src/Api/Template/Util/get_domain.py
@@ -21,6 +21,10 @@ from Src.Util.console import console
from Src.Util._jsonConfig import config_manager
+# Variable
+AUTO_UPDATE_DOMAIN = config_manager.get_bool('DEFAULT', 'auto_update_domain')
+
+
def check_url_for_content(url: str, content: str, timeout: int = 1) -> bool:
"""
@@ -57,6 +61,7 @@ def check_url_for_content(url: str, content: str, timeout: int = 1) -> bool:
return False
+
def get_top_level_domain(base_url: str, target_content: str, max_workers: int = os.cpu_count(), timeout: int = 2, retries: int = 1) -> str:
"""
Get the top-level domain (TLD) from a list of URLs.
@@ -156,8 +161,10 @@ def search_domain(site_name: str, target_content: str, base_url: str):
tuple: The found domain and the complete URL.
"""
+
+
# Extract config domain
- domain = config_manager.get("SITE", site_name)
+ domain = str(config_manager.get_dict("SITE", site_name)['domain'])
console.print(f"[cyan]Test site[white]: [red]{base_url}.{domain}")
try:
@@ -170,23 +177,31 @@ def search_domain(site_name: str, target_content: str, base_url: str):
console.print(f"[cyan]Use domain: [red]{domain}")
return domain, f"{base_url}.{domain}"
- except:
+ except Exception as e:
# If the current domain fails, find a new one
+ console.print(f"[cyan]Error test response site[white]: [red]{e}")
print()
- console.print("[red]Extract new DOMAIN from TLD list.")
- new_domain = get_top_level_domain(base_url=base_url, target_content=target_content)
- if new_domain is not None:
+ if AUTO_UPDATE_DOMAIN:
+ console.print("[red]Extract new DOMAIN from TLD list.")
+ new_domain = get_top_level_domain(base_url=base_url, target_content=target_content)
- # Update domain in config.json
- config_manager.set_key('SITE', site_name, new_domain)
- config_manager.write_config()
+ if new_domain is not None:
+
+ # Update domain in config.json
+ config_manager.config['SITE'][site_name]['domain'] = new_domain
+ config_manager.write_config()
+
+ # Return new config domain
+ console.print(f"[cyan]Use domain: [red]{new_domain}")
+ return new_domain, f"{base_url}.{new_domain}"
+
+ else:
+ logging.error(f"Failed to find a new domain for: {base_url}")
+ sys.exit(0)
- # Return new config domain
- console.print(f"[cyan]Use domain: [red]{new_domain}")
- return new_domain, f"{base_url}.{new_domain}"
-
else:
- logging.error(f"Failed to find a new domain for: {base_url}")
+ logging.error(f"Update domain manually in config.json")
sys.exit(0)
+
diff --git a/Src/Api/altadefinizione/site.py b/Src/Api/altadefinizione/site.py
index 6657a3c..1b047b4 100644
--- a/Src/Api/altadefinizione/site.py
+++ b/Src/Api/altadefinizione/site.py
@@ -21,7 +21,7 @@ from .Core.Class.SearchType import MediaManager
# Variable
-from .costant import SITE_NAME, DOMAIN_NOW
+from .costant import SITE_NAME
media_search_manager = MediaManager()
table_show_manager = TVShowManager()
diff --git a/Src/Api/animeunity/Core/Class/EpisodeType.py b/Src/Api/animeunity/Core/Class/EpisodeType.py
index f63a358..9f19cac 100644
--- a/Src/Api/animeunity/Core/Class/EpisodeType.py
+++ b/Src/Api/animeunity/Core/Class/EpisodeType.py
@@ -3,12 +3,6 @@
from typing import Dict, Any, List
-# Variable
-from ...costant import SITE_NAME, DOMAIN_NOW
-
-
-
-
class Episode:
def __init__(self, data: Dict[str, Any]):
self.id: int = data.get('id', '')
diff --git a/Src/Api/animeunity/Core/Class/PreviewType.py b/Src/Api/animeunity/Core/Class/PreviewType.py
index 28d741e..f16b41e 100644
--- a/Src/Api/animeunity/Core/Class/PreviewType.py
+++ b/Src/Api/animeunity/Core/Class/PreviewType.py
@@ -15,6 +15,7 @@ class Preview:
def __str__(self):
return f"Preview: ID={self.id}, Title ID={self.title_id}, Created At={self.created_at}, Updated At={self.updated_at}, Video ID={self.video_id}, Viewable={self.is_viewable}, Zoom Factor={self.zoom_factor}, Filename={self.filename}, Embed URL={self.embed_url}"
+
class Genre:
def __init__(self, data):
self.id = data.get("id")
@@ -28,19 +29,6 @@ class Genre:
def __str__(self):
return f"Genre: ID={self.id}, Name={self.name}, Type={self.type}, Hidden={self.hidden}, Created At={self.created_at}, Updated At={self.updated_at}, Pivot={self.pivot}"
-class Image:
- def __init__(self, data):
- self.id = data.get("id")
- self.filename = data.get("filename")
- self.type = data.get("type")
- self.imageable_type = data.get("imageable_type")
- self.imageable_id = data.get("imageable_id")
- self.created_at = data.get("created_at")
- self.updated_at = data.get("updated_at")
- self.original_url_field = data.get("original_url_field")
-
- def __str__(self):
- return f"Image: ID={self.id}, Filename={self.filename}, Type={self.type}, Imageable Type={self.imageable_type}, Imageable ID={self.imageable_id}, Created At={self.created_at}, Updated At={self.updated_at}, Original URL Field={self.original_url_field}"
class PreviewManager:
def __init__(self, json_data):
@@ -53,11 +41,7 @@ class PreviewManager:
self.seasons_count = json_data.get("seasons_count")
self.genres = [Genre(genre_data) for genre_data in json_data.get("genres", [])]
self.preview = Preview(json_data.get("preview"))
- self.images = [Image(image_data) for image_data in json_data.get("images", [])]
def __str__(self):
genres_str = "\n".join(str(genre) for genre in self.genres)
- images_str = "\n".join(str(image) for image in self.images)
- return f"Title: ID={self.id}, Type={self.type}, Runtime={self.runtime}, Release Date={self.release_date}, Quality={self.quality}, Plot={self.plot}, Seasons Count={self.seasons_count}\nGenres:\n{genres_str}\nPreview:\n{self.preview}\nImages:\n{images_str}"
-
-
+ return f"Title: ID={self.id}, Type={self.type}, Runtime={self.runtime}, Release Date={self.release_date}, Quality={self.quality}, Plot={self.plot}, Seasons Count={self.seasons_count}\nGenres:\n{genres_str}\nPreview:\n{self.preview}\n."
diff --git a/Src/Api/animeunity/Core/Class/SearchType.py b/Src/Api/animeunity/Core/Class/SearchType.py
index 1b7355f..8423175 100644
--- a/Src/Api/animeunity/Core/Class/SearchType.py
+++ b/Src/Api/animeunity/Core/Class/SearchType.py
@@ -3,24 +3,6 @@
from typing import List
-# Variable
-from ...costant import SITE_NAME, DOMAIN_NOW
-
-
-
-class Image:
- def __init__(self, data: dict):
- self.imageable_id: int = data.get('imageable_id')
- self.imageable_type: str = data.get('imageable_type')
- self.filename: str = data.get('filename')
- self.type: str = data.get('type')
- self.original_url_field: str = data.get('original_url_field')
- self.url: str = f"https://cdn.{SITE_NAME}.{DOMAIN_NOW}/images/{self.filename}"
-
- def __str__(self):
- return f"Image(imageable_id={self.imageable_id}, imageable_type='{self.imageable_type}', filename='{self.filename}', type='{self.type}', url='{self.url}')"
-
-
class MediaItem:
def __init__(self, data: dict):
self.id: int = data.get('id')
@@ -31,10 +13,9 @@ class MediaItem:
self.sub_ita: int = data.get('sub_ita')
self.last_air_date: str = data.get('last_air_date')
self.seasons_count: int = data.get('seasons_count')
- self.images: List[Image] = [Image(image_data) for image_data in data.get('images', [])]
def __str__(self):
- return f"MediaItem(id={self.id}, slug='{self.slug}', name='{self.name}', type='{self.type}', score='{self.score}', sub_ita={self.sub_ita}, last_air_date='{self.last_air_date}', seasons_count={self.seasons_count}, images={self.images})"
+ return f"MediaItem(id={self.id}, slug='{self.slug}', name='{self.name}', type='{self.type}', score='{self.score}', sub_ita={self.sub_ita}, last_air_date='{self.last_air_date}', seasons_count={self.seasons_count})"
class MediaManager:
@@ -82,4 +63,3 @@ class MediaManager:
def __str__(self):
return f"MediaManager(num_media={len(self.media_list)})"
-
diff --git a/Src/Api/animeunity/Core/Class/WindowType.py b/Src/Api/animeunity/Core/Class/WindowType.py
index 07acca8..9f000b2 100644
--- a/Src/Api/animeunity/Core/Class/WindowType.py
+++ b/Src/Api/animeunity/Core/Class/WindowType.py
@@ -26,6 +26,7 @@ class WindowVideo:
def __str__(self):
return f"WindowVideo(id={self.id}, name='{self.name}', filename='{self.filename}', size='{self.size}', quality='{self.quality}', duration='{self.duration}', views={self.views}, is_viewable={self.is_viewable}, status='{self.status}', fps={self.fps}, legacy={self.legacy}, folder_id={self.folder_id}, created_at_diff='{self.created_at_diff}')"
+
class WindowParameter:
def __init__(self, data: Dict[str, Any]):
self.data = data
diff --git a/Src/Api/ddlstreamitaly/site.py b/Src/Api/ddlstreamitaly/site.py
index 694ac01..8175fa8 100644
--- a/Src/Api/ddlstreamitaly/site.py
+++ b/Src/Api/ddlstreamitaly/site.py
@@ -20,7 +20,7 @@ from .Core.Class.SearchType import MediaManager
# Variable
-from .costant import SITE_NAME, DOMAIN_NOW
+from .costant import SITE_NAME
cookie_index = config_manager.get_dict('REQUESTS', 'index')
media_search_manager = MediaManager()
table_show_manager = TVShowManager()
@@ -33,8 +33,11 @@ def title_search(word_to_search) -> int:
"""
try:
+ # Find new domain if prev dont work
+ domain_to_use, _ = search_domain(SITE_NAME, ' int:
Search for titles based on a search query.
"""
+ # Find new domain if prev dont work
+ domain_to_use, _ = search_domain(SITE_NAME, ' 10.12.23
-
-# Class import
-from Src.Util.Helper.headers import get_headers
-from Src.Util.Helper.util import convert_utf8_name
-from Src.Util.Helper.console import console, msg
-from Src.Util.m3u8 import dw_m3u8
-
-# General import
-import requests, os, re, json, sys
-from bs4 import BeautifulSoup
-
-# [func]
-def get_token(id_tv, domain):
- session = requests.Session()
- session.get(f"https://streamingcommunity.{domain}/watch/{id_tv}")
- return session.cookies['XSRF-TOKEN']
-
-def get_info_tv(id_film, title_name, site_version, domain):
- req = requests.get(f"https://streamingcommunity.{domain}/titles/{id_film}-{title_name}", headers={
- 'X-Inertia': 'true',
- 'X-Inertia-Version': site_version,
- 'User-Agent': get_headers()
- })
-
- if req.ok():
- return req.json()['props']['title']['seasons_count']
- else:
- console.log(f"[red]Error: {req.status_code}")
- sys.exit(0)
-
-def get_info_season(tv_id, tv_name, domain, version, token, n_stagione):
- req = requests.get(f'https://streamingcommunity.{domain}/titles/{tv_id}-{tv_name}/stagione-{n_stagione}', headers={
- 'authority': f'streamingcommunity.{domain}', 'referer': f'https://streamingcommunity.broker/titles/{tv_id}-{tv_name}',
- 'user-agent': get_headers(), 'x-inertia': 'true', 'x-inertia-version': version, 'x-xsrf-token': token,
- })
-
- if req.ok:
- return [{'id': ep['id'], 'n': ep['number'], 'name': ep['name']} for ep in req.json()['props']['loadedSeason']['episodes']]
- else:
- console.log(f"[red]Error: {req.status_code}")
- sys.exit(0)
-
-def get_iframe(tv_id, ep_id, domain, token):
- req = requests.get(f'https://streamingcommunity.{domain}/iframe/{tv_id}', params={'episode_id': ep_id, 'next_episode': '1'}, cookies={'XSRF-TOKEN': token}, headers={
- 'referer': f'https://streamingcommunity.{domain}/watch/{tv_id}?e={ep_id}',
- 'user-agent': get_headers()
- })
-
- if req.ok:
- url_embed = BeautifulSoup(req.text, "lxml").find("iframe").get("src")
- req_embed = requests.get(url_embed, headers = {"User-agent": get_headers()}).text
- return BeautifulSoup(req_embed, "lxml").find("body").find("script").text
- else:
- console.log(f"[red]Error: {req.status_code}")
- sys.exit(0)
-
-def parse_content(embed_content):
-
- # Parse parameter from req embed content
- win_video = re.search(r"window.video = {.*}", str(embed_content)).group()
- win_param = re.search(r"params: {[\s\S]*}", str(embed_content)).group()
-
- # Parse parameter to make read for json
- json_win_video = "{"+win_video.split("{")[1].split("}")[0]+"}"
- json_win_param = "{"+win_param.split("{")[1].split("}")[0].replace("\n", "").replace(" ", "") + "}"
- json_win_param = json_win_param.replace(",}", "}").replace("'", '"')
- return json.loads(json_win_video), json.loads(json_win_param)
-
-def get_m3u8_url(json_win_video, json_win_param):
- return f"https://vixcloud.co/playlist/{json_win_video['id']}?type=video&rendition=720p&token={json_win_param['token720p']}&expires={json_win_param['expires']}"
-
-def get_m3u8_key_ep(json_win_video, json_win_param, tv_name, n_stagione, n_ep, ep_title):
- req = requests.get('https://vixcloud.co/storage/enc.key', headers={
- 'referer': f'https://vixcloud.co/embed/{json_win_video["id"]}?token={json_win_param["token720p"]}&title={tv_name.replace("-", "+")}&referer=1&expires={json_win_param["expires"]}&description=S{n_stagione}%3AE{n_ep}+{ep_title.replace(" ", "+")}&nextEpisode=1',
- })
-
- if req.ok:
- return "".join(["{:02x}".format(c) for c in req.content])
- else:
- console.log(f"[red]Error: {req.status_code}")
- sys.exit(0)
-
-def get_m3u8_audio(json_win_video, json_win_param, tv_name, n_stagione, n_ep, ep_title):
-
- req = requests.get(f'https://vixcloud.co/playlist/{json_win_video["id"]}', params={'token': json_win_param['token'], 'expires': json_win_param["expires"] }, headers={
- 'referer': f'https://vixcloud.co/embed/{json_win_video["id"]}?token={json_win_param["token720p"]}&title={tv_name.replace("-", "+")}&referer=1&expires={json_win_param["expires"]}&description=S{n_stagione}%3AE{n_ep}+{ep_title.replace(" ", "+")}&nextEpisode=1'
- })
-
- if req.ok:
- m3u8_cont = req.text.split()
- for row in m3u8_cont:
- if "audio" in str(row) and "ita" in str(row):
- return row.split(",")[-1].split('"')[-2]
- else:
- console.log(f"[red]Error: {req.status_code}")
- sys.exit(0)
-
-
-def actually_dw(tv_id, eps, index_ep_select, domain, token, tv_name, season_select, lower_tv_name):
- embed_content = get_iframe(tv_id, eps[index_ep_select]['id'], domain, token)
- json_win_video, json_win_param = parse_content(embed_content)
- m3u8_url = get_m3u8_url(json_win_video, json_win_param)
- m3u8_key = get_m3u8_key_ep(json_win_video, json_win_param, tv_name, season_select, index_ep_select+1, eps[index_ep_select]['name'])
-
- mp4_name = f"{lower_tv_name.replace('+', '_')}_{str(season_select)}_{str(index_ep_select+1)}"
- mp4_format = mp4_name + ".mp4"
- mp4_path = os.path.join("videos", mp4_format)
-
- m3u8_url_audio = get_m3u8_audio(json_win_video, json_win_param, tv_name, season_select, index_ep_select+1, eps[index_ep_select]['name'])
-
- if m3u8_url_audio != None:
- console.print("[red]=> Use m3u8 audio")
-
- dw_m3u8(m3u8_url, m3u8_url_audio, m3u8_key, mp4_path)
-
-
-
-def main_dw_tv(tv_id, tv_name, version, domain):
-
- token = get_token(tv_id, domain)
-
- lower_tv_name = str(tv_name).lower()
- tv_name = convert_utf8_name(lower_tv_name) # ERROR LATIN 1 IN REQ WITH ò à ù ...
- console.print(f"[blue]Season find: [red]{get_info_tv(tv_id, tv_name, version, domain)}")
- season_select = msg.ask("\n[green]Insert season number: ")
-
- eps = get_info_season(tv_id, tv_name, domain, version, token, season_select)
- for ep in eps:
- console.print(f"[green]Ep: [blue]{ep['n']} [green]=> [purple]{ep['name']}")
- index_ep_select = msg.ask("\n[green]Insert ep number (use * for all episodes): ")
-
- if(index_ep_select == '*'):
- for ep in eps:
- index_ep_select = int(ep['n']) - 1
- actually_dw(tv_id, eps, index_ep_select, domain, token, tv_name, season_select, lower_tv_name)
- return
-
- index_ep_select = int(index_ep_select) - 1
- actually_dw(tv_id, eps, index_ep_select, domain, token, tv_name, season_select, lower_tv_name)
\ No newline at end of file
diff --git a/Src/Lib/Hls/downloader.py b/Src/Lib/Hls/downloader.py
index a29127a..973cc7d 100644
--- a/Src/Lib/Hls/downloader.py
+++ b/Src/Lib/Hls/downloader.py
@@ -3,7 +3,6 @@
import os
import sys
import logging
-from datetime import datetime
from concurrent.futures import ThreadPoolExecutor
@@ -12,6 +11,8 @@ import httpx
from unidecode import unidecode
+
+
# Internal utilities
from Src.Util.headers import get_headers
from Src.Util._jsonConfig import config_manager
diff --git a/Src/Lib/Hls/segments.py b/Src/Lib/Hls/segments.py
index a0986b0..a89178c 100644
--- a/Src/Lib/Hls/segments.py
+++ b/Src/Lib/Hls/segments.py
@@ -4,9 +4,9 @@ import os
import sys
import time
import queue
-import threading
import logging
import binascii
+import threading
from queue import PriorityQueue
from urllib.parse import urljoin, urlparse
from concurrent.futures import ThreadPoolExecutor
@@ -23,6 +23,7 @@ from Src.Util.headers import get_headers, random_headers
from Src.Util.color import Colors
from Src.Util._jsonConfig import config_manager
from Src.Util.os import check_file_existence
+from Src.Util.call_stack import get_call_stack
# Logic class
@@ -41,7 +42,6 @@ urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# Config
-TQDM_MAX_WORKER = config_manager.get_int('M3U8_DOWNLOAD', 'tdqm_workers')
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')
REQUEST_TIMEOUT = config_manager.get_float('REQUESTS', 'timeout')
@@ -274,6 +274,27 @@ class M3U8_Segments:
Args:
- add_desc (str): Additional description for the progress bar.
"""
+
+ # Get config site from prev stack
+ frames = get_call_stack()
+ config_site = str(os.path.basename(frames[-1]['folder'])).lower()
+
+ # Workers to use for downloading
+ TQDM_MAX_WORKER = 0
+
+ # Select audio workers from folder of frames stack prev call.
+ VIDEO_WORKERS = int(config_manager.get_dict('SITE', config_site)['video_workers'])
+ if VIDEO_WORKERS == -1: VIDEO_WORKERS = os.cpu_count()
+ AUDIO_WORKERS = int(config_manager.get_dict('SITE', config_site)['audio_workers'])
+ if AUDIO_WORKERS == -1: AUDIO_WORKERS = os.cpu_count()
+
+ # Differnt workers for audio and video
+ if "video" in str(add_desc):
+ TQDM_MAX_WORKER = VIDEO_WORKERS
+ if "audio" in str(add_desc):
+ TQDM_MAX_WORKER = AUDIO_WORKERS
+
+ # 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}]"
else:
diff --git a/Src/Lib/M3U8/estimator.py b/Src/Lib/M3U8/estimator.py
index c8f2058..6af05bd 100644
--- a/Src/Lib/M3U8/estimator.py
+++ b/Src/Lib/M3U8/estimator.py
@@ -34,7 +34,7 @@ class M3U8_Ts_Estimator:
self.now_downloaded_size = 0
self.total_segments = total_segments
self.lock = threading.Lock()
- self.speeds = deque(maxlen=3)
+ self.speed = 0
self.speed_thread = threading.Thread(target=self.capture_speed)
self.speed_thread.daemon = True
self.speed_thread.start()
@@ -56,30 +56,44 @@ class M3U8_Ts_Estimator:
self.ts_file_sizes.append(size)
self.now_downloaded_size += size_download
- def capture_speed(self, interval: float = 1.0):
+ def capture_speed(self, interval: float = 0.8):
"""
Capture the internet speed periodically and store the values in a deque.
"""
- def get_process_network_io(pid):
- process = psutil.Process(pid)
- io_counters = process.io_counters()
+ def get_network_io():
+ io_counters = psutil.net_io_counters()
return io_counters
- def convert_bytes_to_mbps(bytes):
- return (bytes * 8) / (1024 * 1024)
-
+ 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()
-
+
while True:
- old_value = get_process_network_io(pid)
+
+ # Get value
+ old_value = get_network_io()
time.sleep(interval)
- new_value = get_process_network_io(pid)
- bytes_sent = new_value[2] - old_value[2]
- bytes_recv = new_value[3] - old_value[3]
- mbps_recv = convert_bytes_to_mbps(bytes_recv) / interval
+ new_value = get_network_io()
with self.lock:
- self.speeds.append(mbps_recv)
+ upload_speed = (new_value.bytes_sent - old_value.bytes_sent) / interval
+ download_speed = (new_value.bytes_recv - old_value.bytes_recv) / interval
+
+ self.speed = ({
+ "upload": format_bytes(upload_speed),
+ "download": format_bytes(download_speed)
+ })
+
+ old_value = new_value
+
def get_average_speed(self) -> float:
"""
@@ -89,9 +103,7 @@ class M3U8_Ts_Estimator:
float: The average internet speed in Mbps.
"""
with self.lock:
- if len(self.speeds) == 0:
- return 0.0
- return sum(self.speeds) / len(self.speeds)
+ return self.speed['download'].split(" ")
def calculate_total_size(self) -> str:
"""
@@ -148,16 +160,18 @@ class M3U8_Ts_Estimator:
number_file_total_size = file_total_size.split(' ')[0]
units_file_downloaded = downloaded_file_size_str.split(' ')[1]
units_file_total_size = file_total_size.split(' ')[1]
- average_internet_speed = self.get_average_speed() / 8 # Mbps -> MB\s
+
+ average_internet_speed = self.get_average_speed()[0]
+ average_internet_unit = self.get_average_speed()[1]
# Update the progress bar's postfix
if TQDM_USE_LARGE_BAR:
progress_counter.set_postfix_str(
f"{Colors.WHITE}[ {Colors.GREEN}{number_file_downloaded} {Colors.WHITE}< {Colors.GREEN}{number_file_total_size} {Colors.RED}{units_file_total_size} "
- f"{Colors.WHITE}| {Colors.CYAN}{average_internet_speed:.2f} {Colors.RED}MB/s"
+ f"{Colors.WHITE}| {Colors.CYAN}{average_internet_speed} {Colors.RED}{average_internet_unit}"
)
else:
progress_counter.set_postfix_str(
f"{Colors.WHITE}[ {Colors.GREEN}{number_file_downloaded}{Colors.RED} {units_file_downloaded} "
- f"{Colors.WHITE}| {Colors.CYAN}{average_internet_speed:.2f} {Colors.RED}Mbps"
+ f"{Colors.WHITE}| {Colors.CYAN}{average_internet_speed} {Colors.RED}{average_internet_unit}"
)
diff --git a/Src/Util/call_stack.py b/Src/Util/call_stack.py
new file mode 100644
index 0000000..fba3aae
--- /dev/null
+++ b/Src/Util/call_stack.py
@@ -0,0 +1,53 @@
+# 21.06.24
+
+import os
+import inspect
+
+
+def get_call_stack():
+ """
+ Retrieves the current call stack with details about each call.
+
+ This function inspects the current call stack and returns a list of dictionaries,
+ where each dictionary contains details about a function call in the stack.
+
+ Returns:
+ list: A list of dictionaries, each containing the following keys:
+ - function (str): The name of the function.
+ - folder (str): The directory path of the script containing the function.
+ - script (str): The name of the script file containing the function.
+ - line (int): The line number in the script where the function is defined.
+
+ Example:
+ >>> def func_a():
+ ... return func_b()
+ ...
+ >>> def func_b():
+ ... return func_c()
+ ...
+ >>> def func_c():
+ ... return get_call_stack()
+ ...
+ >>> stack_trace = func_a()
+ >>> for frame in stack_trace:
+ ... print(f"Function: {frame['function']}, Folder: {frame['folder']}, "
+ ... f"Script: {frame['script']}, Line: {frame['line']}")
+ """
+ stack = inspect.stack()
+ call_stack = []
+
+ for frame_info in stack:
+ function_name = frame_info.function
+ filename = frame_info.filename
+ lineno = frame_info.lineno
+ folder_name = os.path.dirname(filename)
+ script_name = os.path.basename(filename)
+
+ call_stack.append({
+ "function": function_name,
+ "folder": folder_name,
+ "script": script_name,
+ "line": lineno
+ })
+
+ return call_stack
diff --git a/Test/bandwidth_gui.py b/Test/bandwidth_gui.py
new file mode 100644
index 0000000..15bd0b9
--- /dev/null
+++ b/Test/bandwidth_gui.py
@@ -0,0 +1,83 @@
+import tkinter as tk
+from threading import Thread, Lock
+from collections import deque
+import psutil
+import time
+
+class NetworkMonitor:
+ def __init__(self, maxlen=10):
+ self.speeds = deque(maxlen=maxlen)
+ self.lock = Lock()
+
+ def capture_speed(self, interval: float = 0.5):
+ def get_network_io():
+ 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"
+
+ old_value = get_network_io()
+ while True:
+ time.sleep(interval)
+ new_value = get_network_io()
+
+ with self.lock:
+ upload_speed = (new_value.bytes_sent - old_value.bytes_sent) / interval
+ download_speed = (new_value.bytes_recv - old_value.bytes_recv) / interval
+
+ self.speeds.append({
+ "upload": format_bytes(upload_speed),
+ "download": format_bytes(download_speed)
+ })
+
+ old_value = new_value
+
+class NetworkMonitorApp:
+ def __init__(self, root):
+ self.monitor = NetworkMonitor()
+ self.root = root
+ self.root.title("Network Bandwidth Monitor")
+ self.root.geometry("400x200")
+ self.root.resizable(False, False)
+
+ self.label_upload_header = tk.Label(text="Upload Speed:", font="Quicksand 12 bold")
+ self.label_upload_header.pack()
+
+ self.label_upload = tk.Label(text="Calculating...", font="Quicksand 12")
+ self.label_upload.pack()
+
+ self.label_download_header = tk.Label(text="Download Speed:", font="Quicksand 12 bold")
+ self.label_download_header.pack()
+
+ self.label_download = tk.Label(text="Calculating...", font="Quicksand 12")
+ self.label_download.pack()
+
+ self.attribution = tk.Label(text="\n~ WaterrMalann ~", font="Quicksand 11 italic")
+ self.attribution.pack()
+
+ self.update_gui()
+ self.start_monitoring()
+
+ def update_gui(self):
+ with self.monitor.lock:
+ if self.monitor.speeds:
+ latest_speeds = self.monitor.speeds[-1]
+ self.label_upload.config(text=latest_speeds["upload"])
+ self.label_download.config(text=latest_speeds["download"])
+
+ self.root.after(250, self.update_gui) # Update every 0.25 seconds
+
+ def start_monitoring(self):
+ 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()
diff --git a/Test/data/TLD/tld_list.txt b/Test/data/TLD/tld_list.txt
deleted file mode 100644
index 1675ad0..0000000
--- a/Test/data/TLD/tld_list.txt
+++ /dev/null
@@ -1,401 +0,0 @@
-ads
-africa
-analytics
-apartments
-app
-arab
-are
-art
-auction
-audio
-author
-auto
-autoinsurance
-autos
-band
-banque
-bargains
-baseball
-bcn
-beauty
-best
-bet
-bid
-bike
-bingo
-black
-blackfriday
-blog
-boats
-boo
-book
-booking
-bot
-boutique
-box
-broadway
-broker
-builders
-business
-buy
-buzz
-cab
-cafe
-call
-camera
-camp
-cancerresearch
-car
-cards
-care
-careers
-carinsurance
-cars
-casa
-cash
-cashbackbonus
-catering
-center
-channel
-chat
-cheap
-christmas
-church
-circle
-claims
-cleaning
-click
-clothing
-cloud
-club
-codes
-coffee
-college
-community
-company
-compare
-computer
-condos
-connectors
-construction
-consulting
-contact
-contractors
-cool
-corp
-country
-coupon
-coupons
-cpa
-cricket
-cruises
-dad
-dance
-data
-dating
-day
-dds
-deal
-deals
-delivery
-democrat
-desi
-design
-dev
-diamonds
-diet
-digital
-directory
-docs
-dog
-domains
-dot
-download
-earth
-eat
-email
-energy
-enterprises
-epost
-esq
-est
-estate
-events
-exchange
-expert
-exposed
-faith
-family
-fan
-fans
-farm
-fashion
-feedback
-film
-final
-fish
-fishing
-fit
-fitness
-flights
-florist
-flowers
-fly
-foo
-food
-football
-forsale
-forum
-foundation
-free
-fun
-fund
-furniture
-futbol
-fyi
-gallery
-game
-games
-garden
-gay
-gift
-gifts
-gives
-giving
-glass
-global
-golf
-got
-graphics
-group
-guide
-guitars
-guru
-hair
-haus
-help
-here
-hiphop
-hockey
-holdings
-holiday
-home
-homes
-hosting
-hot
-house
-how
-imamat
-immo
-inc
-industries
-ing
-institute
-international
-irish
-jewelry
-jot
-joy
-ketchup
-kim
-kitchen
-kosher
-krd
-land
-lat
-latino
-law
-lease
-legal
-lgbt
-life
-lifeinsurance
-lighting
-like
-limited
-limo
-link
-live
-living
-llc
-llp
-loans
-lol
-lotto
-love
-ltd
-ltda
-luxury
-mail
-maison
-management
-map
-market
-marketing
-mba
-media
-meet
-meme
-memorial
-menu
-mobile
-moi
-mom
-money
-mormon
-moto
-mov
-movie
-movistar
-network
-new
-news
-ninja
-now
-nowruz
-one
-onl
-online
-ott
-page
-partners
-parts
-party
-pay
-pet
-pets
-phd
-phone
-photo
-photography
-photos
-pics
-pictures
-pid
-pin
-pink
-pizza
-place
-play
-plumbing
-poker
-productions
-prof
-promo
-properties
-property
-pub
-qpon
-racing
-radio
-read
-realestate
-realty
-recipes
-red
-rehab
-rent
-rentals
-repair
-report
-republican
-rest
-restaurant
-review
-reviews
-rich
-rocks
-room
-rsvp
-ruhr
-run
-safe
-salon
-save
-scholarships
-school
-science
-search
-secure
-services
-shoes
-shop
-shopping
-show
-singles
-site
-ski
-smile
-soccer
-social
-software
-solar
-solutions
-spa
-spot
-srl
-storage
-store
-stream
-studio
-style
-supplies
-supply
-support
-surgery
-systems
-talk
-tattoo
-taxi
-team
-tech
-technology
-tennis
-theater
-tickets
-tienda
-tips
-tires
-today
-tools
-top
-tour
-toys
-trading
-training
-translations
-trust
-tube
-uno
-vacations
-ventures
-vet
-video
-villas
-vin
-vip
-vision
-vivo
-voyage
-wanggou
-watch
-watches
-web
-webcam
-webs
-wed
-whoswho
-win
-wine
-winners
-works
-world
-wow
-xin
-xyz
-yoga
-you
-yun
-zero
-zip
-to
\ No newline at end of file
diff --git a/config.json b/config.json
index 96466fe..c9c7489 100644
--- a/config.json
+++ b/config.json
@@ -7,6 +7,7 @@
"clean_console": true,
"root_path": "Video",
"map_episode_name": "%(tv_name)_S%(season)E%(episode)_%(episode_name)",
+ "auto_update_domain": true,
"not_close": false
},
"REQUESTS": {
@@ -21,7 +22,6 @@
"proxy": []
},
"M3U8_DOWNLOAD": {
- "tdqm_workers": 2,
"tqdm_delay": 0.01,
"tqdm_use_large_bar": true,
"download_video": true,
@@ -52,10 +52,30 @@
"force_resolution": -1
},
"SITE": {
- "streamingcommunity": "boston",
- "animeunity": "to",
- "altadefinizione": "vodka",
- "guardaserie": "ceo",
- "ddlstreamitaly": "co"
+ "streamingcommunity": {
+ "video_workers": 4,
+ "audio_workers": 2,
+ "domain": "boston"
+ },
+ "animeunity": {
+ "video_workers": 4,
+ "audio_workers": 2,
+ "domain": "to"
+ },
+ "altadefinizione": {
+ "video_workers": -1,
+ "audio_workers": -1,
+ "domain": "vodka"
+ },
+ "guardaserie": {
+ "video_workers": -1,
+ "audio_workers": -1,
+ "domain": "ceo"
+ },
+ "ddlstreamitaly": {
+ "video_workers": -1,
+ "audio_workers": -1,
+ "domain": "co"
+ }
}
}
\ No newline at end of file