diff --git a/Stream/api/film.py b/Stream/api/film.py index 85ae1b2..41e2cef 100644 --- a/Stream/api/film.py +++ b/Stream/api/film.py @@ -2,8 +2,8 @@ # Class import from Stream.util.headers import get_headers -from Stream.util.console import console from Stream.util.m3u8 import dw_m3u8 +from Stream.util.util import convert_utf8_name # General import import requests, sys, re, json @@ -43,9 +43,12 @@ def get_m3u8_key(json_win_video, json_win_param, title_name): def main_dw_film(id_film, title_name, domain): + lower_title_name = str(title_name).lower() + title_name = convert_utf8_name(lower_title_name) # ERROR LATIN 1 IN REQ WITH ò à ù ... + embed_content = get_iframe(id_film, domain) 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(json_win_video, json_win_param, title_name) - dw_m3u8(m3u8_url, requests.get(m3u8_url, headers={"User-agent": get_headers()}).text, "", m3u8_key, str(title_name).replace("+", " ").replace(",", "") + ".mp4") + dw_m3u8(m3u8_url, requests.get(m3u8_url, headers={"User-agent": get_headers()}).text, "", m3u8_key, lower_title_name.replace("+", " ").replace(",", "") + ".mp4") diff --git a/Stream/api/page.py b/Stream/api/page.py index 13a1a32..6ab00bf 100644 --- a/Stream/api/page.py +++ b/Stream/api/page.py @@ -21,7 +21,6 @@ def get_version(domain): return json.loads(info_data_page)['version'] except: - console.log("[red]UPDATE DOMANIN") sys.exit(0) diff --git a/Stream/api/tv.py b/Stream/api/tv.py index d262fa3..3649830 100644 --- a/Stream/api/tv.py +++ b/Stream/api/tv.py @@ -2,8 +2,9 @@ # Class import from Stream.util.headers import get_headers -from Stream.util.console import console, msg, console_print +from Stream.util.console import console, msg from Stream.util.m3u8 import dw_m3u8, join_audio_to_video +from Stream.util.util import convert_utf8_name, check_audio_presence # General import import requests, os, re, json @@ -80,27 +81,34 @@ def main_dw_tv(tv_id, tv_name, version, domain): token = get_token(tv_id, domain) - tv_name = str(tv_name.replace('+', '-')).lower() - console.log(f"[blue]Season find: [red]{get_info_tv(tv_id, tv_name, version, domain)}") - season_select = msg.ask("[green]Insert season number: ") + 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 = int(msg.ask("[green]Insert ep number: ")) - 1 + console.print(f"[green]Ep: [blue]{ep['n']} [green]=> [purple]{ep['name']}") + index_ep_select = int(msg.ask("\n[green]Insert ep number: ")) - 1 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']) - dw_m3u8(m3u8_url, requests.get(m3u8_url, headers={"User-agent": get_headers()}).text, "", m3u8_key, tv_name.replace("+", "_") + "_"+str(season_select)+"__"+str(index_ep_select+1) + ".mp4") + mp4_name = lower_tv_name.replace("+", "_") + "_"+str(season_select)+"__"+str(index_ep_select+1) + mp4_format = mp4_name + ".mp4" + base_path_mp4 = os.path.join("videos", mp4_format) + base_audio_path = os.path.join("videos", mp4_format + "_audio.mp4") - is_down_audio = msg.ask("[blue]Download audio [red](!!! Only for recent upload, !!! Use all CPU) [blue][y \ n]").strip() - if str(is_down_audio) == "y": + dw_m3u8(m3u8_url, requests.get(m3u8_url, headers={"User-agent": get_headers()}).text, "", m3u8_key, mp4_format) + + if not check_audio_presence(base_path_mp4): + console.log("[red]Audio is not present, start download (Use all CPU)") 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']) dw_m3u8(m3u8_url_audio, requests.get(m3u8_url_audio, headers={"User-agent": get_headers()}).text, "", m3u8_key, "audio.mp4") - join_audio_to_video("videos//audio.mp4", "videos//" + tv_name.replace("+", "_") + "_"+str(season_select)+"__"+str(index_ep_select+1) + ".mp4", "videos//" + tv_name.replace("+", "_") + "_"+str(season_select)+"__"+str(index_ep_select+1) + "_audio.mp4") - os.remove("videos//audio.mp4") - os.remove("videos//" + tv_name.replace("+", "_") + "_"+str(season_select)+"__"+str(index_ep_select+1) + ".mp4") \ No newline at end of file + temp_audio_path = os.path.join("videos", "audio.mp4") + join_audio_to_video(temp_audio_path, base_path_mp4, base_audio_path) + os.remove(temp_audio_path) + os.remove(base_path_mp4) \ No newline at end of file diff --git a/Stream/upload/__version__.py b/Stream/upload/__version__.py index ec73a1e..14a4476 100644 --- a/Stream/upload/__version__.py +++ b/Stream/upload/__version__.py @@ -1,5 +1,5 @@ __title__ = 'Streaming_community' -__version__ = 'v0.5.0' +__version__ = 'v0.6.0' __author__ = 'Ghost6446' __description__ = 'A command-line program to download film' __license__ = 'MIT License' diff --git a/Stream/upload/update.py b/Stream/upload/update.py index 730e132..20ca08e 100644 --- a/Stream/upload/update.py +++ b/Stream/upload/update.py @@ -1,13 +1,12 @@ -# 26.12.2023 - -# Class import -from Stream.util.console import console +# 13.09.2023 # General import +from Stream.util.console import console import os, requests, time # Variable -github_repo_name = "StreamingCommunity_api" +repo_name = "StreamingCommunity_api" +repo_user = "ghost6446" main = os.path.abspath(os.path.dirname(__file__)) base = "\\".join(main.split("\\")[:-1]) @@ -19,24 +18,19 @@ def get_install_version(): def main_update(): - json = requests.get(f"https://api.github.com/repos/ghost6446/{github_repo_name}/releases").json()[0] - stargazers_count = requests.get(f"https://api.github.com/repos/ghost6446/{github_repo_name}").json()['stargazers_count'] + json = requests.get(f"https://api.github.com/repos/{repo_user}/{repo_name}/releases").json()[0] + stargazers_count = requests.get(f"https://api.github.com/repos/{repo_user}/{repo_name}").json()['stargazers_count'] last_version = json['name'] down_count = json['assets'][0]['download_count'] - # Get percentaul star - if down_count > 0 and stargazers_count > 0: - percentual_stars = round(stargazers_count / down_count * 100, 2) + if down_count > 0 and stargazers_count > 0: percentual_stars = round(stargazers_count / down_count * 100, 2) else: percentual_stars = 0 - if get_install_version() != last_version: - console.log(f"[red]A new version is available") - - else: - console.log("[red]Everything up to date") + if get_install_version() != last_version: console.print(f"[red]=> A new version is available") + else: console.print("[red]=> Everything up to date") print("\n") - console.log(f"[red]Only was downloaded [yellow]{down_count} [red]times, but only [yellow]{percentual_stars} [red]of You(!) have starred it. \n\ - [cyan]Help the repository grow today, by leaving a [yellow]star [cyan]on it and sharing it to others online!") - time.sleep(5) + console.print(f"[red]{repo_name} was downloaded [yellow]{down_count} [red]times, but only [yellow]{percentual_stars}% [red]of You(!!) have starred it. \n\ + [cyan]Help the repository grow today, by leaving a [yellow]star [cyan]and [yellow]sharing [cyan]it to others online!") + time.sleep(2.5) print("\n") \ No newline at end of file diff --git a/Stream/util/console.py b/Stream/util/console.py index d55be56..cbc2d66 100644 --- a/Stream/util/console.py +++ b/Stream/util/console.py @@ -3,7 +3,6 @@ # Import from rich.console import Console from rich.prompt import Prompt -from rich import print as console_print # Variable msg = Prompt() diff --git a/Stream/util/headers.py b/Stream/util/headers.py index 07322ef..cf7f512 100644 --- a/Stream/util/headers.py +++ b/Stream/util/headers.py @@ -9,4 +9,5 @@ def get_headers(): software_names = [SoftwareName.CHROME.value] operating_systems = [OperatingSystem.WINDOWS.value, OperatingSystem.LINUX.value] user_agent_rotator = UserAgent(software_names=software_names, operating_systems=operating_systems, limit=10) + return user_agent_rotator.get_random_user_agent() \ No newline at end of file diff --git a/Stream/util/m3u8.py b/Stream/util/m3u8.py index 29364d5..4428246 100644 --- a/Stream/util/m3u8.py +++ b/Stream/util/m3u8.py @@ -6,10 +6,8 @@ from functools import partial from multiprocessing.dummy import Pool from tqdm.rich import tqdm import moviepy.editor as mp -from Stream.util.platform import * # Class import -#from Stream.util.console import console from Stream.util.console import console # Disable warning @@ -17,35 +15,27 @@ import warnings from tqdm import TqdmExperimentalWarning warnings.filterwarnings("ignore", category=TqdmExperimentalWarning) + # Variable main_out_folder = "videos" -os.makedirs("videos", exist_ok=True) +os.makedirs(main_out_folder, exist_ok=True) +path_out_without_key = "temp_ts" +path_out_with_key = "out_ts" -# [ decoder ] -> costant = ou_ts -class Video_Decoder(object): - iv = "" - uri = "" - method = "" +# [ decoder ] +def decode_aes_128(path_video_frame, decript_key, x_key): + frame_name = path_video_frame.split("\\")[-1].split("-")[0] + ".ts" + iv = x_key["IV"].lstrip("0x") if "IV" in x_key.keys() else "" - def __init__(self, x_key, uri): - self.method = x_key["METHOD"] if "METHOD" in x_key.keys() else "" - self.uri = uri - self.iv = x_key["IV"].lstrip("0x") if "IV" in x_key.keys() else "" - - def decode_aes_128(self, video_fname: str): - if is_platform_linux(): - frame_name = video_fname.split("/")[-1].split("-")[0] + ".ts" - else: - frame_name = video_fname.split("\\")[-1].split("-")[0] + ".ts" - res_cmd = subprocess.run(["openssl","aes-128-cbc","-d","-in", video_fname,"-out", "ou_ts/"+frame_name,"-nosalt","-iv", self.iv,"-K", self.uri ], capture_output=True) - - res_cmd_str = res_cmd.stderr.decode("utf-8") - res_cmd_fix = res_cmd_str.replace("b", "").replace("\n", "").replace("\r", "") - - if "lengthad" in res_cmd_fix: - console.log("[red]Wrong m3u8 key or remove key from input !!!") - sys.exit(0) + out = subprocess.run([ + "openssl", + "aes-128-cbc", "-d", + "-in", path_video_frame, + "-out", os.path.join(path_out_with_key, frame_name), + "-nosalt","-iv", iv, + "-K", decript_key + ], capture_output=True) def decode_ext_x_key(key_str: str): key_str = key_str.replace('"', '').lstrip("#EXT-X-KEY:") @@ -60,7 +50,6 @@ def save_in_part(folder_ts, merged_mp4, file_extension = ".ts"): # Get list of ts file in order os.chdir(folder_ts) - # Order all ts file try: ordered_ts_names = sorted(glob.glob(f"*{file_extension}"), key=lambda x:float(re.findall("(\d+)", x.split("_")[1])[0])) except: @@ -78,10 +67,8 @@ def save_in_part(folder_ts, merged_mp4, file_extension = ".ts"): # Create mp4 from start ts to end def save_part_ts(start, end, part): - #console.log(f"[blue]Process part [green][[red]{part}[green]]") list_mp4_part.append(f"{part}.mp4") - with open(f"{part}_concat.txt", "w") as f: for i in range(start, end): f.write(f"file {ordered_ts_names[i]} \n") @@ -119,12 +106,9 @@ def download_ts_file(ts_url: str, store_dir: str, headers): # Get ts name and folder ts_name = ts_url.split('/')[-1].split("?")[0] - ts_dir = store_dir + "/" + ts_name + ts_dir = os.path.join(store_dir, ts_name) - # Check if exist if(not os.path.isfile(ts_dir)): - - # Download ts_res = requests.get(ts_url, headers=headers) if(ts_res.status_code == 200): @@ -135,187 +119,82 @@ def download_ts_file(ts_url: str, store_dir: str, headers): time.sleep(0.5) -def download_vvt_sub(content, language, folder_id): - - # Get content of vvt - url_main_sub = "" - vvt = content.split("\n") - - # Find main url or vvt - for i in range(len(vvt)): - line = str(vvt[i]) - - if line.startswith("#EXTINF"): - url_main_sub = vvt[i+1] - - # Save subtitle to main folder out - path = os.path.join(main_out_folder, str(folder_id)) - os.makedirs(path, exist_ok=True) - open(os.path.join(path, "sub_"+str(language)+".vtt"), "wb").write(requests.get(url_main_sub).content) # [ donwload ] def dw_m3u8(m3u8_link, m3u8_content, m3u8_headers="", decrypt_key="", merged_mp4="test.mp4"): # Reading the m3u8 file - m3u8_http_base = m3u8_link.rstrip(m3u8_link.split("/")[-1]) + m3u8_base_url = m3u8_link.rstrip(m3u8_link.split("/")[-1]) m3u8 = m3u8_content.split('\n') + ts_url_list = [] ts_names = [] x_key_dict = dict() + is_encryped = False + os.makedirs(path_out_without_key, exist_ok=True) + os.makedirs(path_out_with_key, exist_ok=True) # Parsing the content in m3u8 with creation of url_list with url of ts file - for i_str in range(len(m3u8)): - line_str = m3u8[i_str] - - if "AES-128" in str(line_str): + for i in range(len(m3u8)): + if "AES-128" in str(m3u8[i]): is_encryped = True - if line_str.startswith("#EXT-X-KEY:"): - x_key_dict = decode_ext_x_key(line_str) + if m3u8[i].startswith("#EXT-X-KEY:"): + x_key_dict = decode_ext_x_key(m3u8[i]) - if line_str.startswith("#EXTINF"): - ts_url = m3u8[i_str+1] + if m3u8[i].startswith("#EXTINF"): + ts_url = m3u8[i+1] ts_names.append(ts_url.split('/')[-1]) if not ts_url.startswith("http"): - ts_url = m3u8_http_base + ts_url + ts_url = m3u8_base_url + ts_url ts_url_list.append(ts_url) - #console.log(f"[blue]Find [white]=> [red]{len(ts_url_list)}[blue] ts file to download") - #console.log(f"[green]Is m3u8 encryped => [red]{is_encryped}") + console.log(f"[blue]Find [white]=> [red]{len(ts_url_list)}[blue] ts file to download") if is_encryped and decrypt_key == "": console.log(f"[red]M3U8 Is encryped") sys.exit(0) - if is_encryped: - #console.log(f"[blue]Use decrypting") - - video_decoder = Video_Decoder(x_key=x_key_dict, uri=decrypt_key) - os.makedirs("ou_ts", exist_ok=True) # Using multithreading to download all ts file - os.makedirs("temp_ts", exist_ok=True) pool = Pool(15) - gen = pool.imap(partial(download_ts_file, store_dir="temp_ts", headers=m3u8_headers), ts_url_list) - for _ in tqdm(gen, total=len(ts_url_list), unit="bytes", unit_scale=True, unit_divisor=1024, desc="[yellow]Download"): + gen = pool.imap(partial(download_ts_file, store_dir=path_out_without_key, headers=m3u8_headers), ts_url_list) + for _ in tqdm(gen, total=len(ts_url_list), unit="bytes", unit_scale=True, unit_divisor=1024, desc="[yellow]Download m3u8"): pass pool.close() pool.join() + # Merge all ts if is_encryped: - path = "" - if is_platform_linux(): - path = "temp_ts/*.ts" - else: - path = "temp_ts\*.ts" - - for ts_fname in tqdm(glob.glob(path), desc="[yellow]Decoding"): - video_decoder.decode_aes_128(ts_fname) - - # Start to merge all *.ts files - save_in_part("ou_ts", merged_mp4) + for ts_fname in tqdm(glob.glob(f"{path_out_without_key}/*.ts"), desc="[yellow]Decoding m3u8"): + decode_aes_128(ts_fname, decrypt_key, x_key_dict) + save_in_part(path_out_with_key, merged_mp4) else: - save_in_part("temp_ts", merged_mp4) - + save_in_part(path_out_without_key, merged_mp4) # Clean temp file os.chdir("..") console.log("[green]Clean") + # Move mp4 file to main folder + if is_encryped: shutil.move(path_out_with_key+"/"+merged_mp4 , main_out_folder+"/") + else: shutil.move(path_out_without_key+"/"+merged_mp4 , main_out_folder+"/") - if is_platform_linux(): - if is_encryped: - shutil.move("ou_ts/"+merged_mp4 , main_out_folder+"/") - else: - shutil.move("temp_ts/"+merged_mp4 , main_out_folder+"/") - - else: - if is_encryped: - shutil.move("ou_ts\\"+merged_mp4 , main_out_folder+"\\") - else: - shutil.move("temp_ts\\"+merged_mp4 , main_out_folder+"\\") - - shutil.rmtree("ou_ts", ignore_errors=True) - shutil.rmtree("temp_ts", ignore_errors=True) - -def dw_aac(m3u8_link, m3u8_content, m3u8_headers, merged_mp3): - - # Reading the m3u8 file - url_base = m3u8_link.rstrip(m3u8_link.split("/")[-1]) - m3u8 = m3u8_content.split('\n') - ts_url_list = [] - ts_names = [] - - # Parsing the content in m3u8 with creation of url_list with url of ts file - for i in range(len(m3u8)): - line = m3u8[i] - - if line.startswith("#EXTINF"): - ts_url = m3u8[i+1] - ts_names.append(ts_url.split('/')[-1]) - - if not ts_url.startswith("http"): - ts_url = url_base + ts_url - - ts_url_list.append(ts_url) - console.log(f"[blue]Find [white]=> [red]{len(ts_url_list)}[blue] ts file to download") - - # Using multithreading to download all ts file - os.makedirs("temp_ts", exist_ok=True) - pool = Pool(15) - gen = pool.imap(partial(download_ts_file, store_dir="temp_ts", headers=m3u8_headers), ts_url_list) - for _ in tqdm(gen, total=len(ts_url_list), unit="bytes", unit_scale=True, unit_divisor=1024, desc="[yellow]Download"): - pass - pool.close() - pool.join() - - save_in_part("temp_ts", merged_mp3, file_extension=".aac") - - # Clean temp file - os.chdir("..") - console.log("[green]Clean") - shutil.move("temp_ts\\"+merged_mp3 , ".") - - shutil.rmtree("ou_ts", ignore_errors=True) - shutil.rmtree("temp_ts", ignore_errors=True) - -def dw_vvt_sub(url, headers, folder_id) -> (None): - - print(url, headers, folder_id) - - # Get content of m3u8 vvt - req = requests.get(url, headers=headers) - vvts = req.text.split('\n') - vvt_data = [] - - # Parsing the content in m3u8 of vvt with creation of url_list with url and name of language - for line in vvts: - line = line.split(",") - if line[0] == "#EXT-X-MEDIA:TYPE=SUBTITLES": - - vvt_data.append({ - 'language': line[2].split("=")[1].replace('"', ""), - 'url': line[-1].split("URI=")[1].replace('"', "") - }) - - - # Check array is not empty - if len(vvt_data) > 0: - - # Download all subtitle - for i in range(len(vvts)): - console.log(f"[blue]Download [red]sub => [green]{vvt_data[i]['language']}") - download_vvt_sub(requests.get(vvt_data[i]['url']).text, vvt_data[i]['language'], folder_id) - - else: - console.log("[red]Cant find info of subtitle [SKIP]") + # Remove folder out_ts and temp_ts + shutil.rmtree(path_out_with_key, ignore_errors=True) + shutil.rmtree(path_out_without_key, ignore_errors=True) def join_audio_to_video(audio_path, video_path, out_path): + # Get audio and video audio = mp.AudioFileClip(audio_path) video1 = mp.VideoFileClip(video_path) + + # Add audio final = video1.set_audio(audio) - final.write_videofile(out_path) \ No newline at end of file + # Join all + final.write_videofile(out_path) + diff --git a/Stream/util/platform.py b/Stream/util/platform.py deleted file mode 100644 index 32e6073..0000000 --- a/Stream/util/platform.py +++ /dev/null @@ -1,7 +0,0 @@ -import platform - -def is_platform_windows(): - return platform.system() == "Windows" - -def is_platform_linux(): - return platform.system() == "Linux" diff --git a/Stream/util/util.py b/Stream/util/util.py new file mode 100644 index 0000000..0f18b22 --- /dev/null +++ b/Stream/util/util.py @@ -0,0 +1,15 @@ +# 4.01.2023 + +# Import +from moviepy.editor import VideoFileClip + +def convert_utf8_name(name): + return str(name).encode('utf-8').decode('latin-1') + +def check_audio_presence(file_path): + try: + video_clip = VideoFileClip(file_path) + audio = video_clip.audio + return audio is not None + except Exception as e: + print(f"Si è verificato un errore: {str(e)}") \ No newline at end of file diff --git a/run.py b/run.py index dd18145..395d646 100644 --- a/run.py +++ b/run.py @@ -4,23 +4,29 @@ import Stream.api.page as Page from Stream.util.message import msg_start from Stream.upload.update import main_update -from Stream.util.console import console, msg, console_print +from Stream.util.console import console, msg from Stream.api.film import main_dw_film as download_film from Stream.api.tv import main_dw_tv as download_tv +# General import +import sys + +# Variable domain = "cz" site_version = Page.get_version(domain) def main(): + msg_start() main_update() - - film_search = msg.ask("[blue]Insert film to search: ").strip() + console.print(f"[blue]Find system [white]=> [red]{sys.platform} \n") + + film_search = msg.ask("[blue]Insert word to search in all site: ").strip() db_title = Page.search(film_search, domain) for i in range(len(db_title)): - console_print(f"[yellow]{i} [white]-> [green]{db_title[i]['name']} [white]- [cyan]{db_title[i]['type']}") - index_select = int(msg.ask("[blue]Index to download: ")) + console.print(f"[yellow]{i} [white]-> [green]{db_title[i]['name']} [white]- [cyan]{db_title[i]['type']}") + index_select = int(msg.ask("\n[blue]Index to download: ")) if db_title[index_select]['type'] == "movie": console.log(f"[green]Movie select: {db_title[index_select]['name']}")