diff --git a/README.md b/README.md index 72d824e..208e8c8 100644 --- a/README.md +++ b/README.md @@ -11,26 +11,32 @@ Script to download film from streaming community without selenium, select movie

## Requirement + * python [3.9](https://www.python.org/downloads/release/python-390/) * ffmpeg [win](https://www.gyan.dev/ffmpeg/builds/) ## Installation library + ```bash pip install -r requirements.txt ``` ## Run + ```bash python run.py ``` ## Features + - Search for movies. - Search and download TV series episode. -- Add audio if missing. +- Auto add audio if missing ## Tutorial + https://www.youtube.com/watch?v=Ok7hQCgxqLg&ab_channel=Nothing ## Authors + - [@Ghost6446](https://www.github.com/Ghost6446) diff --git a/Stream/api/tv.py b/Stream/api/tv.py index fb95697..6d670ad 100644 --- a/Stream/api/tv.py +++ b/Stream/api/tv.py @@ -4,7 +4,7 @@ from Stream.util.headers import get_headers 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 +from Stream.util.util import convert_utf8_name # General import import requests, os, re, json @@ -96,20 +96,13 @@ def main_dw_tv(tv_id, tv_name, version, domain): 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 = lower_tv_name.replace("+", "_") + "_"+str(season_select)+"__"+str(index_ep_select+1) + mp4_name = f"{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") + mp4_path = os.path.join("videos", mp4_format) - dw_m3u8(m3u8_url, m3u8_key, base_path_mp4) + 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 not check_audio_presence(base_path_mp4): - - console.print("[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']) + if m3u8_url_audio != None: + console.print("[red]=> Use m3u8 audio") - dw_m3u8(m3u8_url_audio, m3u8_key, base_audio_path) - - join_audio_to_video(base_audio_path, base_path_mp4, base_audio_path) - os.remove(base_audio_path) - os.remove(base_path_mp4) \ No newline at end of file + dw_m3u8(m3u8_url, m3u8_url_audio, m3u8_key, mp4_path) diff --git a/Stream/upload/__version__.py b/Stream/upload/__version__.py index 736d540..717f476 100644 --- a/Stream/upload/__version__.py +++ b/Stream/upload/__version__.py @@ -1,5 +1,5 @@ __title__ = 'Streaming_community' -__version__ = 'v0.6.1' +__version__ = 'v0.7.0' __author__ = 'Ghost6446' __description__ = 'A command-line program to download film' __license__ = 'MIT License' diff --git a/Stream/util/m3u8.py b/Stream/util/m3u8.py index 68ab13a..57a02c4 100644 --- a/Stream/util/m3u8.py +++ b/Stream/util/m3u8.py @@ -11,6 +11,7 @@ import moviepy.editor as mp # Class import from Stream.util.console import console from Stream.util.headers import get_headers +from Stream.util.util import there_is_audio, merge_ts_files # Variable os.makedirs("videos", exist_ok=True) @@ -19,17 +20,20 @@ os.makedirs("videos", exist_ok=True) # [ main class ] class M3U8Downloader: - def __init__(self, m3u8_url, key=None, output_filename="output.mp4"): + 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.output_filename = output_filename self.segments = [] + self.segments_audio = [] self.iv = None self.key = bytes.fromhex(key) self.temp_folder = "tmp" os.makedirs(self.temp_folder, exist_ok=True) + self.download_audio = False def decode_ext_x_key(self, key_str): key_str = key_str.replace('"', '').lstrip("#EXT-X-KEY:") @@ -42,6 +46,9 @@ class M3U8Downloader: self.iv = bytes.fromhex(raw_iv.replace("0x", "")) def parse_m3u8(self, m3u8_content): + if self.m3u8_audio != None: + m3u8_audio_line = str(requests.get(self.m3u8_audio).content).split("\\n") + m3u8_base_url = self.m3u8_url.rstrip(self.m3u8_url.split("/")[-1]) lines = m3u8_content.split('\n') @@ -57,9 +64,11 @@ class M3U8Downloader: if not ts_url.startswith("http"): ts_url = m3u8_base_url + ts_url - self.segments.append(ts_url) - console.print(f"[cyan]Find: {len(self.segments)} ts file to download") + self.segments.append(ts_url) + if self.m3u8_audio != None: self.segments_audio.append(m3u8_audio_line[i+1]) + + console.log(f"[cyan]Find: {len(self.segments)} ts file to download") def download_m3u8(self): response = requests.get(self.m3u8_url, headers={'user-agent': get_headers()}) @@ -68,6 +77,19 @@ class M3U8Downloader: m3u8_content = response.text self.parse_m3u8(m3u8_content) + # Check there is audio in first ts file + path_test_ts_file = os.path.join(self.temp_folder, "ts_test.ts") + if self.key and self.iv: + open(path_test_ts_file, "wb").write(self.decrypt_ts(requests.get(self.segments[0]).content)) + else: + open(path_test_ts_file, "wb").write(requests.get(self.segments[0]).content) + + if not there_is_audio(path_test_ts_file): + self.download_audio = True + console.log("[cyan]=> Make req to get video and audio file") + + os.remove(path_test_ts_file) + def decrypt_ts(self, encrypted_data): cipher = Cipher(algorithms.AES(self.key), modes.CBC(self.iv), backend=default_backend()) decryptor = cipher.decryptor() @@ -75,27 +97,50 @@ class M3U8Downloader: return decrypted_data - def decrypt_and_save(self, args): - ts_url, index = args - ts_filename = os.path.join(self.temp_folder, f"{index}.ts") + def decrypt_and_save(self, index): + + video_ts_url = self.segments[index] + video_ts_filename = os.path.join(self.temp_folder, f"{index}_v.ts") - if not os.path.exists(ts_filename): - ts_response = requests.get(ts_url, headers={'user-agent': get_headers()}).content + if self.download_audio: + audio_ts_url = self.segments_audio[index] + audio_ts_filename = os.path.join(self.temp_folder, f"{index}_a.ts") + video_audio_ts_filename = os.path.join(self.temp_folder, f"{index}_v_a.ts") + + # Download video or audio ts file + if not os.path.exists(video_ts_url): + ts_response = requests.get(video_ts_url, headers={'user-agent': get_headers()}).content if self.key and self.iv: decrypted_data = self.decrypt_ts(ts_response) - with open(ts_filename, "wb") as ts_file: + with open(video_ts_filename, "wb") as ts_file: ts_file.write(decrypted_data) else: - with open(ts_filename, "wb") as ts_file: + with open(video_ts_filename, "wb") as ts_file: ts_file.write(ts_response) + # Donwload only audio ts file + if self.download_audio: + ts_response = requests.get(audio_ts_url, headers={'user-agent': get_headers()}).content + + if self.key and self.iv: + decrypted_data = self.decrypt_ts(ts_response) + with open(audio_ts_filename, "wb") as ts_file: + ts_file.write(decrypted_data) + + else: + with open(audio_ts_filename, "wb") as ts_file: + ts_file.write(ts_response) + + # Join ts video and audio + merge_ts_files(video_ts_filename, audio_ts_filename, video_audio_ts_filename) + os.remove(video_ts_filename) + os.remove(audio_ts_filename) + def download_and_save_ts(self): with ThreadPoolExecutor(max_workers=30) as executor: - - list(tqdm(executor.map(self.decrypt_and_save, zip(self.segments, range(len(self.segments)))), - total=len(self.segments), unit="bytes", unit_scale=True, unit_divisor=1024, desc="[yellow]Download")) + list(tqdm(executor.map(self.decrypt_and_save, range(len(self.segments)) ), total=len(self.segments), unit="bytes", unit_scale=True, unit_divisor=1024, desc="[yellow]Download")) def join_ts_files(self): @@ -110,12 +155,12 @@ class M3U8Downloader: relative_path = os.path.relpath(os.path.join(self.temp_folder, ts_file), current_dir) f.write(f"file '{relative_path}'\n") - console.print("[cyan]Start join all file") + console.log("[cyan]Start join all file") try: ( - ffmpeg.input(file_list_path, format='concat', safe=0).output(self.output_filename, c="copy", loglevel="quiet").run() + ffmpeg.input(file_list_path, format='concat', safe=0).output(self.output_filename, c='copy', loglevel='quiet').run() ) - console.print(f"[cyan]Clean ...") + console.log(f"[cyan]Clean ...") except ffmpeg.Error as e: print(f"Errore durante il salvataggio del file MP4: {e}") finally: @@ -124,9 +169,9 @@ class M3U8Downloader: # [ function ] -def dw_m3u8(url, key=None, output_filename="output.mp4"): +def dw_m3u8(url, audio_url=None, key=None, output_filename="output.mp4"): - downloader = M3U8Downloader(url, key, output_filename) + downloader = M3U8Downloader(url, audio_url, key, output_filename) downloader.download_m3u8() downloader.download_and_save_ts() diff --git a/Stream/util/util.py b/Stream/util/util.py index 0f18b22..3e5bd05 100644 --- a/Stream/util/util.py +++ b/Stream/util/util.py @@ -1,15 +1,31 @@ # 4.01.2023 # Import -from moviepy.editor import VideoFileClip +import ffmpeg, subprocess 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 +def there_is_audio(ts_file_path): + probe = ffmpeg.probe(ts_file_path) + return any(stream['codec_type'] == 'audio' for stream in probe['streams']) + + +def merge_ts_files(video_path, audio_path, output_path): + + """command = [ + 'ffmpeg', + '-i', video_path, + '-i', audio_path, + '-c', 'copy', + '-map', '0', + '-map', '1', + '-y', output_path + ] + + subprocess.run(command)""" + + input_video = ffmpeg.input(video_path) + input_audio = ffmpeg.input(audio_path) + + ffmpeg.output(input_video, input_audio, output_path, format='mpegts', acodec='copy', vcodec='copy', loglevel='quiet').run() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index fc2c1be..eee296d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,5 +5,4 @@ tqdm rich random-user-agent ffmpeg-python -moviepy cryptography \ No newline at end of file