from movies import * from series import * import os import re import requests import subprocess import signal import sys import pyfiglet from urllib.parse import urlparse from collections import defaultdict from colorama import Fore, Style USER_AGENT = "VLC/3.0.9 LibVLC/3.0.9" HEADERS = {'User-Agent': USER_AGENT} SUPPORTED_EXTENSIONS = ('.mkv', '.mp4', '.avi') max_connections = 1 def handle_exit(sig, frame): print("\nScript exit with CTRL+C by user") sys.exit(0) signal.signal(signal.SIGINT, handle_exit) def sanitize_filename(name): return re.sub(r'[\\/*?:\"<>|]', "", name).strip() def aria2c_exists(): return os.path.exists("./aria2c") or os.path.exists("./aria2c.exe") def get_remote_file_size(url): try: with requests.head(url, headers=HEADERS, timeout=10) as head: head.raise_for_status() content_length = head.headers.get('content-length') return int(content_length) if content_length else 0 except Exception: return 0 def is_series(title): match = re.search(r'(.*?)\s+[Ss](\d{2})[Ee](\d{2})', title) if match: series_name = match.group(1).strip() season = f"S{match.group(2)}" episode = f"E{match.group(3)}" return True, series_name, season, episode return False, None, None, None def parse_m3u(m3u_content): entries = [] lines = m3u_content.splitlines() i = 0 while i < len(lines): line = lines[i] if line.startswith("#EXTINF"): info_line = line i += 1 if i < len(lines): url_line = lines[i] entries.append((info_line, url_line)) i += 1 return entries def extract_metadata(info_line): tvg_name_match = re.search(r'tvg-name="([^"]+)"', info_line) tvg_logo_match = re.search(r'tvg-logo="([^"]+)"', info_line) group_title_match = re.search(r'group-title="([^"]+)"', info_line) title = tvg_name_match.group(1) if tvg_name_match else "Unknown Title" logo_url = tvg_logo_match.group(1) if tvg_logo_match else None group_title = group_title_match.group(1) if group_title_match else "Unknown Group" return title, logo_url, group_title def input_range(prompt, max_val): inp = input(prompt) selected = set() for part in inp.split(','): if '-' in part: start, end = map(int, part.split('-')) selected.update(range(start, end + 1)) else: selected.add(int(part)) return [i for i in selected if 1 <= i <= max_val] def show_banner(): print("=" * 72) banner = pyfiglet.figlet_format("XC Downloader", font="slant") print(banner) print(" by redhat@woi ") print("=" * 72 + "\n\n") def get_credentials(): print("Enter your server credentials below") print("-----------------------------------\n") server_url = input("Server URL & Port (e.g., http://serverip_or_url:port): ").strip().rstrip('/') username = input("Username: ").strip() password = input("Password: ").strip() try: conn_limit = int(input("Server connection limit (max concurrent downloads): ").strip()) if conn_limit < 1: conn_limit = 1 except ValueError: conn_limit = 1 return server_url, username, password, conn_limit def main(): show_banner() global max_connections # Loop until valid credentials are provided while True: server_url, username, password, max_connections = get_credentials() m3u_url = f"{server_url}/get.php?username={username}&password={password}&type=m3u_plus&output=mpegts" try: response = requests.get(m3u_url, headers=HEADERS, timeout=30) response.raise_for_status() m3u_content = response.text break # ✅ Exit loop if successful except Exception: print(f"{Fore.RED}Error: Check your credentials, connection could not be established{Style.RESET_ALL}\n") entries = parse_m3u(m3u_content) movies = defaultdict(list) series = defaultdict(lambda: defaultdict(lambda: defaultdict(list))) for info_line, media_url in entries: if not media_url.lower().endswith(SUPPORTED_EXTENSIONS): continue title, logo_url, group_title = extract_metadata(info_line) is_series_flag, series_name, season, episode = is_series(title) if is_series_flag: series[group_title][series_name][season].append({ 'title': title, 'logo_url': logo_url, 'media_url': media_url, 'season': season, 'episode': episode }) else: movies[group_title].append({ 'title': title, 'logo_url': logo_url, 'media_url': media_url }) while True: print("\n" + "*" * 21) print("* * Main Menu * *") print("*" * 21 + "\n") print("Results:") print("-----------\n") print(f"Movies found: {sum(len(v) for v in movies.values())}") print(f"Movies Categories found: {len(movies)}") print("-----------\n") print("-----------") print(f"Series found: {sum(len(series[g][s][se]) for g in series for s in series[g] for se in series[g][s])}") print(f"Series Categories found: {len(series)}") print("-----------\n") print("1. Show Movies Categories") print("2. Show Series Categories") print("3. Download all Movies") print("4. Download all Series") print("5. Exit\n") choice = input("Select: ").strip() if choice == '1': show_movie_categories(movies) elif choice == '2': show_series_categories(series) elif choice == '3': for category in movies: for movie in movies[category]: download_movie(category, movie) elif choice == '4': for category in series: for sname in series[category]: for season in series[category][sname]: for ep in series[category][sname][season]: download_series_episode(category, sname, season, ep) elif choice == '5': break else: print("Invalid choice.") if __name__ == "__main__": main()