diff --git a/Src/Api/film.py b/Src/Api/film.py index 0f1649f..376146d 100644 --- a/Src/Api/film.py +++ b/Src/Api/film.py @@ -1,9 +1,9 @@ # 3.12.23 -> 10.12.23 # Class import -from Src.Util.Helper.headers import get_headers -from Src.Util.Helper.console import console -from Src.Util.FFmpeg.m3u8 import dw_m3u8 +from Src.Util.headers import get_headers +from Src.Util.console import console +from Src.Lib.FFmpeg.my_m3u8 import download_m3u8 # General import import requests, os, re, json, sys @@ -102,5 +102,5 @@ def main_dw_film(id_film, title_name, domain): if m3u8_url_audio != None: console.print("[blue]Use m3u8 audio => [red]True") - - dw_m3u8(m3u8_url, m3u8_url_audio, m3u8_key, mp4_path) + + download_m3u8(m3u8_index=m3u8_url, m3u8_audio=m3u8_url_audio, m3u8_subtitle=m3u8_url, key=m3u8_key, output_filename=mp4_path) diff --git a/Src/Api/page.py b/Src/Api/page.py index cd299a9..f2bb5b0 100644 --- a/Src/Api/page.py +++ b/Src/Api/page.py @@ -1,8 +1,8 @@ # 10.12.23 # Class import -from Src.Util.Helper.headers import get_headers -from Src.Util.Helper.console import console +from Src.Util.headers import get_headers +from Src.Util.console import console # General import import requests, sys, json @@ -11,15 +11,24 @@ from bs4 import BeautifulSoup def domain_version(): console.print("[green]Get rules ...") - req_repo = requests.get("https://raw.githubusercontent.com/Ghost6446/Streaming_comunity_data/main/data.json") + req_repo = requests.get("https://raw.githubusercontent.com/Ghost6446/Streaming_comunity_data/main/data.json", headers={'user-agent': get_headers()}, timeout=5) + domain = req_repo.json()['domain'] if req_repo.ok: - site_req = requests.get(f"https://streamingcommunity.{req_repo.json()['domain']}/", headers={'user-agent': get_headers()}).text - soup = BeautifulSoup(site_req, "lxml") - version = json.loads(soup.find("div", {"id": "app"}).get("data-page"))['version'] - console.print(f"[blue]Rules [white]=> [red].{req_repo.json()['domain']}") + console.print(f"[blue]Test domain [white]=> [red]{domain}") + site_url = f"https://streamingcommunity.{domain}" + site_request = requests.get(site_url, headers={'user-agent': get_headers()}) - return req_repo.json()['domain'], version + try: + soup = BeautifulSoup(site_request.text, "lxml") + version = json.loads(soup.find("div", {"id": "app"}).get("data-page"))['version'] + console.print(f"[blue]Rules [white]=> [red].{domain}") + + return domain, version + + except: + console.log("[red]Cant get version, problem with domain") + sys.exit(0) else: console.log(f"[red]Error: {req_repo.status_code}") @@ -30,7 +39,11 @@ def search(title_search, domain): req = requests.get(f"https://streamingcommunity.{domain}/api/search?q={title_search}", headers={'user-agent': get_headers()}) if req.ok: - return [{'name': title['name'], 'type': title['type'], 'id': title['id'], 'slug': title['slug']} for title in req.json()['data']] + return [{'name': title['name'], 'type': title['type'], 'id': title['id'], 'slug': title['slug']} for title in req.json()['data']][0:21] else: console.log(f"[red]Error: {req.status_code}") - sys.exit(0) \ No newline at end of file + sys.exit(0) + +def display_search_results(db_title): + for i, title in enumerate(db_title): + console.print(f"[yellow]{i} [white]-> [green]{title['name']} [white]- [cyan]{title['type']}") diff --git a/Src/Api/tv.py b/Src/Api/tv.py index f600fbe..cf047e2 100644 --- a/Src/Api/tv.py +++ b/Src/Api/tv.py @@ -1,14 +1,15 @@ # 3.12.23 -> 10.12.23 # Class import -from Src.Util.Helper.headers import get_headers -from Src.Util.Helper.console import console, msg -from Src.Util.FFmpeg.m3u8 import dw_m3u8 +from Src.Util.headers import get_headers +from Src.Util.console import console, msg +from Src.Lib.FFmpeg.my_m3u8 import download_m3u8 # General import import requests, os, re, json, sys from bs4 import BeautifulSoup + # [func] def get_token(id_tv, domain): session = requests.Session() @@ -47,6 +48,12 @@ def get_iframe(tv_id, ep_id, domain, token): 'user-agent': get_headers() }) + # Change user agent m3u8 + custom_headers_req = { + '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 @@ -93,7 +100,7 @@ def get_m3u8_key_ep(json_win_video, json_win_param, tv_name, n_stagione, n_ep, e 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, token_render): +def get_m3u8_playlist(json_win_video, json_win_param, tv_name, n_stagione, n_ep, ep_title, token_render): 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[token_render]}&title={tv_name}&referer=1&expires={json_win_param["expires"]}&description=S{n_stagione}%3AE{n_ep}+{ep_title}&nextEpisode=1' }) @@ -102,7 +109,7 @@ def get_m3u8_audio(json_win_video, json_win_param, tv_name, n_stagione, n_ep, ep 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] + return row.split(",")[-1].split('"')[-2], req.text else: console.log(f"[red]Error: {req.status_code}") sys.exit(0) @@ -125,12 +132,12 @@ def dw_single_ep(tv_id, eps, index_ep_select, domain, token, tv_name, season_sel 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'], token_render) + m3u8_url_audio, m3u8_playlist_content = get_m3u8_playlist(json_win_video, json_win_param, tv_name, season_select, index_ep_select+1, eps[index_ep_select]['name'], token_render) if m3u8_url_audio != None: console.print("[blue]Use m3u8 audio => [red]True") - dw_m3u8(m3u8_url, m3u8_url_audio, m3u8_key, mp4_path) + download_m3u8(m3u8_playlist=m3u8_playlist_content, m3u8_index=m3u8_url, m3u8_audio=m3u8_url_audio, m3u8_subtitle=m3u8_url, key=m3u8_key, output_filename=mp4_path) def main_dw_tv(tv_id, tv_name, version, domain): diff --git a/Src/Upload/__version__.py b/Src/Upload/__version__.py index ed7dd5a..28ac16f 100644 --- a/Src/Upload/__version__.py +++ b/Src/Upload/__version__.py @@ -1,5 +1,5 @@ __title__ = 'Streaming_community' -__version__ = 'v0.8.3' +__version__ = 'v0.8.5' __author__ = 'Ghost6446' __description__ = 'A command-line program to download film' __license__ = 'MIT License' diff --git a/Src/Upload/update.py b/Src/Upload/update.py index 5e7afbb..4847a6f 100644 --- a/Src/Upload/update.py +++ b/Src/Upload/update.py @@ -1,7 +1,7 @@ # 13.09.2023 # Class import -from Src.Util.Helper.console import console +from Src.Util.console import console # General import import os, requests, time diff --git a/Src/Util/FFmpeg/installer.py b/Src/Util/FFmpeg/installer.py deleted file mode 100644 index 7474b39..0000000 --- a/Src/Util/FFmpeg/installer.py +++ /dev/null @@ -1,63 +0,0 @@ -# 24.01.2023 - -# Class import -from Src.Util.Helper.console import console - -# Import -import subprocess, os, requests, zipfile, sys - -import ctypes, os, sys - -def isAdmin(): - try: - is_admin = (os.getuid() == 0) - except AttributeError: - is_admin = ctypes.windll.shell32.IsUserAnAdmin() != 0 - return is_admin - -def download_ffmpeg(): - - # Specify the URL for the FFmpeg binary zip file for Windows - ffmpeg_url = "https://www.gyan.dev/ffmpeg/builds/ffmpeg-git-full.7z" - - # Name of the directory where FFmpeg will be extracted - ffmpeg_dir = "ffmpeg" - console.print("[yellow]Downloading FFmpeg...[/yellow]") - - # Download the FFmpeg zip file - response = requests.get(ffmpeg_url) - os.makedirs(ffmpeg_dir, exist_ok=True) - - # Save the zip file - zip_file_path = os.path.join(ffmpeg_dir, "ffmpeg.zip") - with open(zip_file_path, "wb") as zip_file: - zip_file.write(response.content) - - with zipfile.ZipFile(zip_file_path, "r") as zip_ref: - zip_ref.extractall(ffmpeg_dir) - - # Add the FFmpeg directory to the system PATH - ffmpeg_bin_dir = os.path.join(os.getcwd(), ffmpeg_dir, "bin") - os.environ["PATH"] += os.pathsep + ffmpeg_bin_dir - os.remove(zip_file_path) - -def check_ffmpeg(): - - console.print("[green]Checking ffmpeg ...") - - try: - subprocess.run(["ffmpeg", "-version"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True) - console.print("[blue]FFmpeg [white]=> [red]Add to system path") - except: - try: - console.print("[cyan]FFmpeg is not in the PATH. Downloading and adding to the PATH...[/cyan]") - - if not isAdmin(): - console.log("[red]You need to be admin to proceed!") - sys.exit(0) - - download_ffmpeg() - sys.exit(0) # Exit - except: - console.print("[red]Unable to download or add FFmpeg to the PATH.[/red]") - sys.exit(0) # Exit diff --git a/Src/Util/FFmpeg/m3u8.py b/Src/Util/FFmpeg/m3u8.py deleted file mode 100644 index da52157..0000000 --- a/Src/Util/FFmpeg/m3u8.py +++ /dev/null @@ -1,195 +0,0 @@ -# 5.01.24 -> 7.01.24 - -# Class import -from Src.Util.Helper.console import console, config_logger -from Src.Util.Helper.headers import get_headers -from Src.Util.FFmpeg.util import print_duration_table - -# Import -import requests, re, os, ffmpeg, time, sys, warnings, logging, shutil, subprocess -from tqdm.rich import tqdm -from concurrent.futures import ThreadPoolExecutor -from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes -from cryptography.hazmat.backends import default_backend - -# Disable warning -from tqdm import TqdmExperimentalWarning -warnings.filterwarnings("ignore", category=TqdmExperimentalWarning) -warnings.filterwarnings("ignore", category=UserWarning, module="cryptography") - -# Variable -os.makedirs("videos", exist_ok=True) -DOWNLOAD_WORKERS = 30 - - -# [ main class ] -class Decryption(): - def __init__(self, key): - self.iv = None - self.key = key - - def decode_ext_x_key(self, key_str): - logging.debug(f"String to decode: {key_str}") - key_str = key_str.replace('"', '').lstrip("#EXT-X-KEY:") - v_list = re.findall(r"[^,=]+", key_str) - key_map = {v_list[i]: v_list[i+1] for i in range(0, len(v_list), 2)} - logging.debug(f"Output string: {key_map}") - return key_map - - def parse_key(self, raw_iv): - self.iv = bytes.fromhex(raw_iv.replace("0x", "")) - - def decrypt_ts(self, encrypted_data): - cipher = Cipher(algorithms.AES(self.key), modes.CBC(self.iv), backend=default_backend()) - decryptor = cipher.decryptor() - decrypted_data = decryptor.update(encrypted_data) + decryptor.finalize() - - return decrypted_data - -class M3U8(): - def __init__(self, url, key=None): - self.url = url - self.key = bytes.fromhex(key) if key is not None else key - self.temp_folder = "tmp" - os.makedirs(self.temp_folder, exist_ok=True) - - def parse_data(self, m3u8_content): - self.decription = Decryption(self.key) - self.segments = [] - base_url = self.url.rstrip(self.url.split("/")[-1]) - lines = m3u8_content.split('\n') - - for i in range(len(lines)): - line = str(lines[i]) - - if line.startswith("#EXT-X-KEY:"): - x_key_dict = self.decription.decode_ext_x_key(line) - self.decription.parse_key(x_key_dict['IV']) - - if line.startswith("#EXTINF"): - ts_url = lines[i+1] - - if not ts_url.startswith("http"): - ts_url = base_url + ts_url - - logging.debug(f"Add to segment: {ts_url}") - self.segments.append(ts_url) - - def get_info(self): - self.max_retry = 3 - response = requests.get(self.url, headers={'user-agent': get_headers()}) - - if response.ok: - self.parse_data(response.text) - console.log(f"[red]Ts segments find [white]=> [yellow]{len(self.segments)}") - else: - console.log("[red]Wrong m3u8 url") - sys.exit(0) - - def get_req_ts(self, ts_url): - - try: - response = requests.get(ts_url, headers={'user-agent': get_headers()}) - - if response.status_code == 200: - return response.content - else: - print(f"Failed: {ts_url}, with error: {response.status_code}") - self.segments.remove(ts_url) - logging.error(f"Failed download: {ts_url}") - return None - - except Exception as e: - print(f"Failed: {ts_url}, with error: {e}") - self.segments.remove(ts_url) - logging.error(f"Failed download: {ts_url}") - return None - - def save_ts(self, index): - ts_url = self.segments[index] - ts_filename = os.path.join(self.temp_folder, f"{index}.ts") - - if not os.path.exists(ts_filename): - ts_content = self.get_req_ts(ts_url) - - if ts_content is not None: - with open(ts_filename, "wb") as ts_file: - if self.key and self.decription.iv: - decrypted_data = self.decription.decrypt_ts(ts_content) - ts_file.write(decrypted_data) - else: - ts_file.write(ts_content) - - def download_ts(self): - with ThreadPoolExecutor(max_workers=DOWNLOAD_WORKERS) as executor: - list(tqdm(executor.map(self.save_ts, range(len(self.segments)) ), total=len(self.segments), unit="bytes", unit_scale=True, unit_divisor=1024, desc="[yellow]Download")) - - def join(self, output_filename): - - current_dir = os.path.dirname(os.path.realpath(__file__)) - file_list_path = os.path.join(current_dir, 'file_list.txt') - - ts_files = [f for f in os.listdir(self.temp_folder) if f.endswith(".ts")] - def extract_number(file_name): - return int(''.join(filter(str.isdigit, file_name))) - ts_files.sort(key=extract_number) - - with open(file_list_path, 'w') as f: - for ts_file in ts_files: - relative_path = os.path.relpath(os.path.join(self.temp_folder, ts_file), current_dir) - f.write(f"file '{relative_path}'\n") - - console.log("[cyan]Start join all file") - try: - ffmpeg.input(file_list_path, format='concat', safe=0).output(output_filename, c='copy', loglevel='quiet').run() - except ffmpeg.Error as e: - console.log(f"[red]Error saving MP4: {e.stdout}") - sys.exit(0) - - console.log(f"[cyan]Clean ...") - os.remove(file_list_path) - shutil.rmtree("tmp", ignore_errors=True) - -class M3U8Downloader: - def __init__(self, m3u8_url, m3u8_audio = None, key=None, output_filename="output.mp4"): - self.m3u8_url = m3u8_url - self.m3u8_audio = m3u8_audio - self.key = key - self.video_path = output_filename - self.audio_path = os.path.join("videos", "audio.mp4") - - def start(self): - video_m3u8 = M3U8(self.m3u8_url, self.key) - console.log("[green]Download video ts") - video_m3u8.get_info() - video_m3u8.download_ts() - video_m3u8.join(self.video_path) - print_duration_table(self.video_path) - print("\n") - - if self.m3u8_audio != None: - audio_m3u8 = M3U8(self.m3u8_audio, self.key) - console.log("[green]Download audio ts") - audio_m3u8.get_info() - audio_m3u8.download_ts() - audio_m3u8.join(self.audio_path) - print_duration_table(self.audio_path) - - self.join_audio() - - def join_audio(self): - output_path = self.video_path + ".mp4" - try: - video_stream = ffmpeg.input(self.video_path) - audio_stream = ffmpeg.input(self.audio_path) - ffmpeg.output(video_stream, audio_stream, output_path, vcodec="copy", acodec="copy").global_args('-map', '0:v:0', '-map', '1:a:0', '-shortest', '-strict', 'experimental').run(overwrite_output=True) - except ffmpeg.Error as e: - print("ffmpeg error:", e.stderr) - - os.remove(self.video_path) - os.remove(self.audio_path) - -# [ main function ] -def dw_m3u8(url, audio_url=None, key=None, output_filename="output.mp4"): - print("\n") - M3U8Downloader(url, audio_url, key, output_filename).start() diff --git a/Src/Util/FFmpeg/util.py b/Src/Util/FFmpeg/util.py deleted file mode 100644 index 9829c15..0000000 --- a/Src/Util/FFmpeg/util.py +++ /dev/null @@ -1,28 +0,0 @@ -# 31.01.24 - -# Class import -from Src.Util.Helper.console import console - -# Import -import ffmpeg - -def get_video_duration(file_path): - try: - probe = ffmpeg.probe(file_path) - duration = float(probe['format']['duration']) - return duration - except ffmpeg.Error as e: - print(f"Error: {e.stderr}") - return None - -def format_duration(seconds): - hours, remainder = divmod(seconds, 3600) - minutes, seconds = divmod(remainder, 60) - return int(hours), int(minutes), int(seconds) - -def print_duration_table(file_path): - video_duration = get_video_duration(file_path) - - if video_duration is not None: - hours, minutes, seconds = format_duration(video_duration) - console.log(f"[cyan]Info [green]'{file_path}': [purple]{int(hours)}[red]h [purple]{int(minutes)}[red]m [purple]{int(seconds)}[red]s") \ No newline at end of file diff --git a/Src/Util/Helper/console.py b/Src/Util/console.py similarity index 100% rename from Src/Util/Helper/console.py rename to Src/Util/console.py diff --git a/Src/Util/Helper/headers.py b/Src/Util/headers.py similarity index 100% rename from Src/Util/Helper/headers.py rename to Src/Util/headers.py diff --git a/Src/Util/Helper/message.py b/Src/Util/message.py similarity index 95% rename from Src/Util/Helper/message.py rename to Src/Util/message.py index 76de034..b9dba4d 100644 --- a/Src/Util/Helper/message.py +++ b/Src/Util/message.py @@ -1,7 +1,7 @@ # 3.12.23 # Import -from Src.Util.Helper.console import console +from Src.Util.console import console def msg_start(): diff --git a/Src/Util/Helper/os.py b/Src/Util/os.py similarity index 100% rename from Src/Util/Helper/os.py rename to Src/Util/os.py diff --git a/requirements.txt b/requirements.txt index 99c4b3d..811359c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,3 +6,4 @@ rich random-user-agent ffmpeg-python cryptography==3.4.8 # Problem with new version +m3u8 diff --git a/run.py b/run.py index ce6ca7b..f4ef1dc 100644 --- a/run.py +++ b/run.py @@ -1,18 +1,25 @@ # 10.12.23 -> 31.01.24 -# Class import +# Class import Src.Api.page as Page from Src.Api.film import main_dw_film as download_film from Src.Api.tv import main_dw_tv as download_tv -from Src.Util.Helper.message import msg_start -from Src.Util.Helper.console import console, msg +from Src.Util.message import msg_start +from Src.Util.console import console, msg +from Src.Util.os import remove_folder from Src.Upload.update import main_update -from Src.Util.FFmpeg.installer import check_ffmpeg -from Src.Util.Helper.os import remove_folder +from Src.Lib.FFmpeg.installer import check_ffmpeg + +# Import +import sys # [ main ] def initialize(): - + + if sys.version_info < (3, 11): + console.log("Install python version > 3.11") + sys.exit(0) + remove_folder("tmp") msg_start() @@ -24,10 +31,6 @@ def initialize(): check_ffmpeg() print("\n") -def display_search_results(db_title): - for i, title in enumerate(db_title): - console.print(f"[yellow]{i} [white]-> [green]{title['name']} [white]- [cyan]{title['type']}") - def main(): initialize() @@ -35,7 +38,7 @@ def main(): film_search = msg.ask("\n[blue]Insert word to search in all site: ").strip() db_title = Page.search(film_search, domain) - display_search_results(db_title) + Page.display_search_results(db_title) if len(db_title) != 0: index_select = int(msg.ask("\n[blue]Index to download: ")) @@ -55,7 +58,7 @@ def main(): else: console.print("[red]Cant find a single element") - console.print("\n[red]Done") + console.print("[red]Done") if __name__ == '__main__': main()