diff --git a/Src/Api/film.py b/Src/Api/film.py index 2e60f74..8cd369f 100644 --- a/Src/Api/film.py +++ b/Src/Api/film.py @@ -9,15 +9,16 @@ import requests import sys from bs4 import BeautifulSoup + +# Class import from Src.Lib.FFmpeg.my_m3u8 import download_m3u8 from Src.Lib.FFmpeg.util import audio_extractor_m3u8 from Src.Util.config import config from Src.Util.console import console -# Class import from Src.Util.headers import get_headers -# [func] + def get_iframe(id_title, domain): req = requests.get(url=f"https://streamingcommunity.{domain}/iframe/{id_title}", headers={ "User-agent": get_headers() @@ -72,10 +73,8 @@ def get_playlist(json_win_video, json_win_param, render_quality): token_render = f"token{render_quality}" return f"https://vixcloud.co/playlist/{json_win_video['id']}?token={json_win_param['token']}&{token_render}={json_win_param[token_render]}&expires={json_win_param['expires']}" -def get_m3u8_key(json_win_video, json_win_param, title_name, token_render): - response = requests.get('https://vixcloud.co/storage/enc.key', headers={ - 'referer': f'https://vixcloud.co/embed/{json_win_video["id"]}?token={json_win_param[token_render]}&title={title_name}&referer=1&expires={json_win_param["expires"]}', - }) +def get_m3u8_key(): + response = requests.get('https://vixcloud.co/storage/enc.key') if response.ok: return binascii.hexlify(response.content).decode('utf-8') @@ -111,7 +110,7 @@ def main_dw_film(id_film, title_name, domain): console.print(f"[blue]Selected quality => [red]{render_quality}") m3u8_url = get_m3u8_url(json_win_video, json_win_param, render_quality) - m3u8_key = get_m3u8_key(json_win_video, json_win_param, title_name, token_render) + m3u8_key = get_m3u8_key() m3u8_playlist = get_playlist(json_win_video, json_win_param, render_quality) mp4_name = title_name.replace("+", " ").replace(",", "").replace("-", "_") @@ -123,5 +122,6 @@ def main_dw_film(id_film, title_name, domain): if m3u8_url_audio is not None: console.print("[blue]Using m3u8 audio => [red]True") subtitle_path = os.path.join(config['root_path'], config['movies_folder_name'], mp4_name) + download_m3u8(m3u8_index=m3u8_url, m3u8_audio=m3u8_url_audio, m3u8_subtitle=m3u8_playlist, key=m3u8_key, output_filename=mp4_path, subtitle_folder=subtitle_path, content_name=mp4_name) diff --git a/Src/Api/tv.py b/Src/Api/tv.py index d29abcb..6ceb366 100644 --- a/Src/Api/tv.py +++ b/Src/Api/tv.py @@ -10,26 +10,19 @@ import sys import urllib from bs4 import BeautifulSoup +# Class import from Src.Lib.FFmpeg.my_m3u8 import download_m3u8 from Src.Lib.FFmpeg.util import audio_extractor_m3u8 from Src.Util.config import config from Src.Util.console import console, msg -# Class import from Src.Util.headers import get_headers -# [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={ + 'user-agent': get_headers(), 'X-Inertia': 'true', 'X-Inertia-Version': site_version, - 'User-Agent': get_headers() }) if req.ok: @@ -39,16 +32,13 @@ def get_info_tv(id_film, title_name, site_version, domain): sys.exit(0) -def get_info_season(tv_id, tv_name, domain, version, token, n_season): +def get_info_season(tv_id, tv_name, domain, version, n_season): req = requests.get( f'https://streamingcommunity.{domain}/titles/{tv_id}-{tv_name}/stagione-{n_season}', 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: @@ -59,22 +49,11 @@ def get_info_season(tv_id, tv_name, domain, version, token, n_season): sys.exit(0) -def get_iframe(tv_id, ep_id, domain, token): +def get_iframe(tv_id, ep_id, domain): 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() - }) - - # Change user agent m3u8 (unused) - """ - custom_headers_req = { - 'referer': f'https://streamingcommunity.{domain}/watch/{tv_id}?e={ep_id}', - 'user-agent': get_headers() - } - """ + headers={'user-agent': get_headers()} + ) if req.ok: try: @@ -122,10 +101,8 @@ def get_m3u8_url(json_win_video, json_win_param, render_quality): return f"https://vixcloud.co/playlist/{json_win_video['id']}?type=video&rendition={render_quality}&token={json_win_param[token_render]}&expires={json_win_param['expires']}" -def get_m3u8_key_ep(json_win_video, json_win_param, tv_name, n_season, n_ep, ep_title, token_render): - response = requests.get('https://vixcloud.co/storage/enc.key', 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_season}%3AE{n_ep}+{ep_title}&nextEpisode=1', - }) +def get_m3u8_key_ep(): + response = requests.get('https://vixcloud.co/storage/enc.key') if response.ok: return binascii.hexlify(response.content).decode('utf-8') @@ -151,12 +128,12 @@ def get_m3u8_audio(json_win_video, json_win_param, tv_name, n_season, n_ep, ep_t # [func \ main] -def dw_single_ep(tv_id, eps, index_ep_select, domain, token, tv_name, season_select): +def dw_single_ep(tv_id, eps, index_ep_select, domain, tv_name, season_select): encoded_name = urllib.parse.quote(eps[index_ep_select]['name']) console.print( f"[green]Downloading episode: [blue]{eps[index_ep_select]['n']} [green]=> [purple]{eps[index_ep_select]['name']}") - embed_content = get_iframe(tv_id, eps[index_ep_select]['id'], domain, token) + embed_content = get_iframe(tv_id, eps[index_ep_select]['id'], domain) if embed_content is None: return json_win_video, json_win_param, render_quality = parse_content(embed_content) @@ -166,8 +143,7 @@ def dw_single_ep(tv_id, eps, index_ep_select, domain, token, tv_name, season_sel m3u8_playlist = get_playlist(json_win_video, json_win_param, render_quality) m3u8_url = get_m3u8_url(json_win_video, json_win_param, render_quality) - m3u8_key = get_m3u8_key_ep(json_win_video, json_win_param, tv_name, season_select, index_ep_select + 1, - encoded_name, token_render) + m3u8_key = get_m3u8_key_ep() mp4_name = f"{tv_name.replace('+', '_')}_S{str(season_select).zfill(2)}E{str(index_ep_select + 1).zfill(2)}" mp4_format = f"{mp4_name}.mp4" @@ -186,7 +162,6 @@ def dw_single_ep(tv_id, eps, index_ep_select, domain, token, tv_name, season_sel def main_dw_tv(tv_id, tv_name, version, domain): - token = get_token(tv_id, domain) num_season_find = get_info_tv(tv_id, tv_name, version, domain) console.print( @@ -197,14 +172,14 @@ def main_dw_tv(tv_id, tv_name, version, domain): start, end = map(int, season_select[1:-1].split('-')) result = list(range(start, end + 1)) for n_season in result: - eps = get_info_season(tv_id, tv_name, domain, version, token, n_season) + eps = get_info_season(tv_id, tv_name, domain, version, n_season) for ep in eps: - dw_single_ep(tv_id, eps, int(ep['n']) - 1, domain, token, tv_name, n_season) + dw_single_ep(tv_id, eps, int(ep['n']) - 1, domain, tv_name, n_season) print("\n") elif season_select != "*": season_select = int(season_select) if 1 <= season_select <= num_season_find: - eps = get_info_season(tv_id, tv_name, domain, version, token, season_select) + eps = get_info_season(tv_id, tv_name, domain, version, season_select) for ep in eps: console.print(f"[green]Episode: [blue]{ep['n']} [green]=> [purple]{ep['name']}") @@ -218,27 +193,27 @@ def main_dw_tv(tv_id, tv_name, version, domain): for n_range_ep in result: # index_ep_select = int(n_range_ep) # Unused - dw_single_ep(tv_id, eps, n_range_ep - 1, domain, token, tv_name, season_select) + dw_single_ep(tv_id, eps, n_range_ep - 1, domain, tv_name, season_select) # Download single ep elif index_ep_select != "*": if 1 <= int(index_ep_select) <= len(eps): index_ep_select = int(index_ep_select) - 1 - dw_single_ep(tv_id, eps, index_ep_select, domain, token, tv_name, season_select) + dw_single_ep(tv_id, eps, index_ep_select, domain, tv_name, season_select) else: console.print("[red]Wrong [yellow]INDEX [red]for the selected Episode") # Download all else: for ep in eps: - dw_single_ep(tv_id, eps, int(ep['n']) - 1, domain, token, tv_name, season_select) + dw_single_ep(tv_id, eps, int(ep['n']) - 1, domain, tv_name, season_select) print("\n") else: console.print("[red]Wrong [yellow]INDEX for the selected Season") else: for n_season in range(1, num_season_find + 1): - eps = get_info_season(tv_id, tv_name, domain, version, token, n_season) + eps = get_info_season(tv_id, tv_name, domain, version, n_season) for ep in eps: - dw_single_ep(tv_id, eps, int(ep['n']) - 1, domain, token, tv_name, n_season) + dw_single_ep(tv_id, eps, int(ep['n']) - 1, domain, tv_name, n_season) print("\n") diff --git a/Src/Lib/FFmpeg/my_m3u8.py b/Src/Lib/FFmpeg/my_m3u8.py index 36786f3..2d80fe5 100644 --- a/Src/Lib/FFmpeg/my_m3u8.py +++ b/Src/Lib/FFmpeg/my_m3u8.py @@ -126,10 +126,6 @@ class M3U8_Parser: sub_parse.parse_data(req_sub_content.text) url_subtitle = sub_parse.subtitle[0] - # Subtitles convention: - # Movie_Name.[Language_Code].vtt - # Movie_Name.[Language_Code].forced.vtt # If forced - if "forced" in name_language.lower(): name_language = name_language.lower().replace("forced", "").strip() name_language = name_language.lower().replace("-", "").strip() @@ -161,7 +157,6 @@ class M3U8_Parser: else: console.log("[red]Couldn't find any playlist with audio") - class M3U8_Segments: def __init__(self, url, key=None): self.url = url @@ -195,7 +190,6 @@ class M3U8_Segments: if response.ok: self.parse_data(response.text) - # console.log(f"[red]Ts segments find [white]=> [yellow]{len(self.segments)}") if len(self.segments) == 0: console.log("[red]Couldn't find any segments to download, retry") @@ -224,17 +218,14 @@ class M3U8_Segments: if response.status_code == 200: return response.content else: - # print(f"Failed to fetch {ts_url}: {response.status_code}") failed_segments.append(str(url_number)) return None except Exception as e: - # print(f"Failed to fetch {ts_url}: {str(e)}") failed_segments.append(str(url_number)) return None else: - # print("Skip ", ts_url, " arr ", failed_segments) return None def save_ts(self, index, progress_counter, quit_event): @@ -257,11 +248,8 @@ class M3U8_Segments: progress_counter.update(1) def download_ts(self): - """Loop to download all segment of playlist m3u8 and break it if there is no progress""" - progress_counter = tqdm(total=len(self.segments), unit="bytes", desc="[yellow]Download") - # Event to signal when to quit quit_event = threading.Event() timeout_occurred = False @@ -272,7 +260,7 @@ class M3U8_Segments: with ThreadPoolExecutor(max_workers=MAX_WORKER) as executor: futures = [] for index in range(len(self.segments)): - if timeout_occurred: # Check if timeout occurred before submitting tasks + if timeout_occurred: break future = executor.submit(self.save_ts, index, progress_counter, quit_event) futures.append(future) @@ -282,13 +270,12 @@ class M3U8_Segments: future.result() except Exception as e: print(f"An error occurred: {str(e)}") - # Handle the error as needed + finally: progress_counter.close() - quit_event.set() # Signal the timer thread to quit - timer_thread.join() # Ensure the timer thread is terminated + quit_event.set() + timer_thread.join() - # Continue with the rest of your code here... def timer(self, progress_counter, quit_event, timeout_checker): start_time = time.time() @@ -298,21 +285,20 @@ class M3U8_Segments: current_count = progress_counter.n if current_count != last_count: - start_time = time.time() # Update start time when progress is made + start_time = time.time() last_count = current_count elapsed_time = time.time() - start_time if elapsed_time > self.progress_timeout: console.log(f"[red]No progress for {self.progress_timeout} seconds.") console.log("[red]Breaking ThreadPoolExecutor...") - timeout_checker() # Set the flag indicating a timeout - quit_event.set() # Signal the main thread to quit - break # Break the loop instead of exiting + timeout_checker() + quit_event.set() + break time.sleep(1) - # Execution reaches here when the loop is broken - progress_counter.refresh() # Refresh the progress bar to reflect the last progress + progress_counter.refresh() def join(self, output_filename): """Join all segments file to a mp4 file name""" @@ -340,13 +326,11 @@ class M3U8_Segments: ffmpeg.input(file_list_path, format='concat', safe=0).output(output_filename, map_metadata='-1', c='copy', loglevel='error').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 M3U8_Downloader: def __init__(self, m3u8_url, m3u8_audio = None, key=None, output_filename="output.mp4"): self.m3u8_url = m3u8_url @@ -378,10 +362,7 @@ class M3U8_Downloader: if os.path.exists(f"{self.video_path}.mp4"): os.renames(f"{self.video_path}.mp4", self.video_path) - - def join_audio(self): - """Join audio with video and sync it""" console.log("[cyan]Join audio and video") try: @@ -414,11 +395,8 @@ class M3U8_Downloader: os.remove(self.audio_path) - # [ main function ] def df_make_req(url): - """Make req to get text""" - response = requests.get(url) if response.ok: @@ -427,19 +405,16 @@ def df_make_req(url): console.log(f"[red]Wrong url, error: {response.status_code}") sys.exit(0) - def download_subtitle(url, name_language): - """Make req to vtt url and save to video subtitle folder""" - path = os.path.join("videos", "subtitle") os.makedirs(path, exist_ok=True) console.log(f"[green]Downloading subtitle: [red]{name_language}") open(os.path.join(path, name_language + ".vtt"), "wb").write(requests.get(url).content) - def download_m3u8(m3u8_playlist=None, m3u8_index = None, m3u8_audio=None, m3u8_subtitle=None, key=None, output_filename=os.path.join("videos", "output.mp4"), log=False, subtitle_folder="subtitles", content_name=""): m3u8_audio_url=None + # m3u8_playlist never use in this version key = bytes.fromhex(key) if key is not None else key @@ -454,6 +429,7 @@ def download_m3u8(m3u8_playlist=None, m3u8_index = None, m3u8_audio=None, m3u8_s sys.exit(0) m3u8_audio_url = m3u8_audio_obj["url"] console.log(f"[green]Select language => [purple]{m3u8_audio_obj['lang']}") + if m3u8_subtitle != None: parse_class_m3u8_sub = M3U8_Parser() diff --git a/run.py b/run.py index ddf9a99..db618f8 100644 --- a/run.py +++ b/run.py @@ -31,7 +31,6 @@ def initialize(): check_ffmpeg() print("\n") - def main(): initialize() @@ -90,6 +89,5 @@ def main(): console.print("[red]Done!") - if __name__ == '__main__': main()