change m3u8 class

This commit is contained in:
Ghost 2024-01-05 16:47:09 +01:00
parent 38be06a7c6
commit d764625d9c
7 changed files with 118 additions and 168 deletions

View File

@ -13,7 +13,6 @@ Script to download film from streaming community without selenium, select movie
## Requirement ## Requirement
* python [3.9](https://www.python.org/downloads/release/python-390/) * python [3.9](https://www.python.org/downloads/release/python-390/)
* ffmpeg [win](https://www.gyan.dev/ffmpeg/builds/) * ffmpeg [win](https://www.gyan.dev/ffmpeg/builds/)
* open ssl [win](https://slproweb.com/products/Win32OpenSSL.html)
## Installation library ## Installation library
```bash ```bash
@ -27,7 +26,8 @@ python run.py
## Features ## Features
- Search for movies. - Search for movies.
- Search and download TV series episodes. - Search and download TV series episode.
- Add audio if missing.
## Authors ## Authors

View File

@ -6,7 +6,7 @@ from Stream.util.m3u8 import dw_m3u8
from Stream.util.util import convert_utf8_name from Stream.util.util import convert_utf8_name
# General import # General import
import requests, sys, re, json import requests, os, re, json
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
# [func] # [func]
@ -51,4 +51,5 @@ def main_dw_film(id_film, title_name, domain):
m3u8_url = get_m3u8_url(json_win_video, json_win_param) m3u8_url = get_m3u8_url(json_win_video, json_win_param)
m3u8_key = get_m3u8_key(json_win_video, json_win_param, title_name) 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, lower_title_name.replace("+", " ").replace(",", "") + ".mp4") path_film = os.path.join("videos", lower_title_name.replace("+", " ").replace(",", "") + ".mp4")
dw_m3u8(m3u8_url, m3u8_key, path_film)

View File

@ -101,14 +101,15 @@ def main_dw_tv(tv_id, tv_name, version, domain):
base_path_mp4 = os.path.join("videos", mp4_format) base_path_mp4 = os.path.join("videos", mp4_format)
base_audio_path = os.path.join("videos", mp4_format + "_audio.mp4") base_audio_path = os.path.join("videos", mp4_format + "_audio.mp4")
dw_m3u8(m3u8_url, requests.get(m3u8_url, headers={"User-agent": get_headers()}).text, "", m3u8_key, mp4_format) dw_m3u8(m3u8_url, m3u8_key, base_path_mp4)
if not check_audio_presence(base_path_mp4): 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")
temp_audio_path = os.path.join("videos", "audio.mp4") console.print("[red]Audio is not present, start download (Use all CPU)")
join_audio_to_video(temp_audio_path, base_path_mp4, base_audio_path) 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'])
os.remove(temp_audio_path)
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) os.remove(base_path_mp4)

View File

@ -1,5 +1,5 @@
__title__ = 'Streaming_community' __title__ = 'Streaming_community'
__version__ = 'v0.6.0' __version__ = 'v0.6.1'
__author__ = 'Ghost6446' __author__ = 'Ghost6446'
__description__ = 'A command-line program to download film' __description__ = 'A command-line program to download film'
__license__ = 'MIT License' __license__ = 'MIT License'

View File

@ -1,190 +1,136 @@
# 4.08.2023 -> 14.09.2023 -> 17.09.2023 -> 3.12.2023 # 5.01.24
# Import # Import
import re, os, sys, glob, time, requests, shutil, ffmpeg, subprocess import requests, re, os, ffmpeg, shutil
from functools import partial
from multiprocessing.dummy import Pool
from tqdm.rich import tqdm 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
import moviepy.editor as mp import moviepy.editor as mp
# Class import # Class import
from Stream.util.console import console from Stream.util.console import console
from Stream.util.headers import get_headers
# Disable warning
import warnings
from tqdm import TqdmExperimentalWarning
warnings.filterwarnings("ignore", category=TqdmExperimentalWarning)
# Variable # 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 ] # [ main class ]
def decode_aes_128(path_video_frame, decript_key, x_key): class M3U8Downloader:
frame_name = path_video_frame.split("\\")[-1].split("-")[0] + ".ts"
iv = x_key["IV"].lstrip("0x") if "IV" in x_key.keys() else ""
out = subprocess.run([ def __init__(self, m3u8_url, key=None, output_filename="output.mp4"):
"openssl", self.m3u8_url = m3u8_url
"aes-128-cbc", "-d", self.key = key
"-in", path_video_frame, self.output_filename = output_filename
"-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): self.segments = []
self.iv = None
self.key = bytes.fromhex(key)
self.temp_folder = "tmp"
os.makedirs(self.temp_folder, exist_ok=True)
def decode_ext_x_key(self, key_str):
key_str = key_str.replace('"', '').lstrip("#EXT-X-KEY:") key_str = key_str.replace('"', '').lstrip("#EXT-X-KEY:")
v_list = re.findall(r"[^,=]+", key_str) 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)} key_map = {v_list[i]: v_list[i+1] for i in range(0, len(v_list), 2)}
return key_map
return key_map # URI | METHOD | IV
# [ util ] def parse_key(self, raw_iv):
def save_in_part(folder_ts, merged_mp4, file_extension = ".ts"): self.iv = bytes.fromhex(raw_iv.replace("0x", ""))
# Get list of ts file in order def parse_m3u8(self, m3u8_content):
os.chdir(folder_ts) m3u8_base_url = self.m3u8_url.rstrip(self.m3u8_url.split("/")[-1])
lines = m3u8_content.split('\n')
# Order all ts file for i in range(len(lines)):
try: ordered_ts_names = sorted(glob.glob(f"*{file_extension}"), key=lambda x:float(re.findall("(\d+)", x.split("_")[1])[0])) line = str(lines[i])
except:
try: ordered_ts_names = sorted(glob.glob(f"*{file_extension}"), key=lambda x:float(re.findall("(\d+)", x.split("-")[1])[0]))
except: ordered_ts_names = sorted(glob.glob(f"*{file_extension}"))
open("concat.txt", "wb") if line.startswith("#EXT-X-KEY:"):
open("part_list.txt", "wb") x_key_dict = self.decode_ext_x_key(line)
self.parse_key(x_key_dict['IV'])
# Variable for download if line.startswith("#EXTINF"):
list_mp4_part = [] ts_url = lines[i+1]
part = 0
start = 0
end = 200
# Create mp4 from start ts to end
def save_part_ts(start, end, part):
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")
ffmpeg.input(f"{part}_concat.txt", format='concat', safe=0).output(f"{part}.mp4", c='copy', loglevel="quiet").run()
# Save first part
save_part_ts(start, end, part)
# Save all other part
for _ in range(start, end):
# Increment progress ts file
start+= 200
end += 200
part+=1
# Check if end or not
if(end < len(ordered_ts_names)):
save_part_ts(start, end, part)
else:
save_part_ts(start, len(ordered_ts_names), part)
break
# Merge all part
console.log(f"[purple]Merge all: {file_extension} file")
with open("part_list.txt", 'w') as f:
for mp4_fname in list_mp4_part:
f.write(f"file {mp4_fname}\n")
ffmpeg.input("part_list.txt", format='concat', safe=0).output(merged_mp4, c='copy', loglevel="quiet").run()
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 = os.path.join(store_dir, ts_name)
if(not os.path.isfile(ts_dir)):
ts_res = requests.get(ts_url, headers=headers)
if(ts_res.status_code == 200):
with open(ts_dir, 'wb+') as f:
f.write(ts_res.content)
else:
print(f"Failed to download streaming file: {ts_name}.")
time.sleep(0.5)
# [ donwload ]
def dw_m3u8(m3u8_link, m3u8_content, m3u8_headers="", decrypt_key="", merged_mp4="test.mp4"):
# Reading the m3u8 file
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 in range(len(m3u8)):
if "AES-128" in str(m3u8[i]):
is_encryped = True
if m3u8[i].startswith("#EXT-X-KEY:"):
x_key_dict = decode_ext_x_key(m3u8[i])
if m3u8[i].startswith("#EXTINF"):
ts_url = m3u8[i+1]
ts_names.append(ts_url.split('/')[-1])
if not ts_url.startswith("http"): if not ts_url.startswith("http"):
ts_url = m3u8_base_url + ts_url ts_url = m3u8_base_url + ts_url
self.segments.append(ts_url)
ts_url_list.append(ts_url) console.print(f"[cyan]Find: {len(self.segments)} ts file to download")
console.log(f"[blue]Find [white]=> [red]{len(ts_url_list)}[blue] ts file to download")
if is_encryped and decrypt_key == "": def download_m3u8(self):
console.log(f"[red]M3U8 Is encryped") response = requests.get(self.m3u8_url, headers={'user-agent': get_headers()})
sys.exit(0)
if response.ok:
m3u8_content = response.text
self.parse_m3u8(m3u8_content)
# Using multithreading to download all ts file def decrypt_ts(self, encrypted_data):
pool = Pool(15) cipher = Cipher(algorithms.AES(self.key), modes.CBC(self.iv), backend=default_backend())
gen = pool.imap(partial(download_ts_file, store_dir=path_out_without_key, headers=m3u8_headers), ts_url_list) decryptor = cipher.decryptor()
for _ in tqdm(gen, total=len(ts_url_list), unit="bytes", unit_scale=True, unit_divisor=1024, desc="[yellow]Download m3u8"): decrypted_data = decryptor.update(encrypted_data) + decryptor.finalize()
pass
pool.close() return decrypted_data
pool.join()
def decrypt_and_save(self, args):
ts_url, index = args
ts_filename = os.path.join(self.temp_folder, f"{index}.ts")
if not os.path.exists(ts_filename):
ts_response = requests.get(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:
ts_file.write(decrypted_data)
# Merge all ts
if is_encryped:
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: else:
save_in_part(path_out_without_key, merged_mp4) with open(ts_filename, "wb") as ts_file:
ts_file.write(ts_response)
# Clean temp file def download_and_save_ts(self):
os.chdir("..") with ThreadPoolExecutor(max_workers=30) as executor:
console.log("[green]Clean")
# Move mp4 file to main folder list(tqdm(executor.map(self.decrypt_and_save, zip(self.segments, range(len(self.segments)))),
if is_encryped: shutil.move(path_out_with_key+"/"+merged_mp4 , main_out_folder+"/") total=len(self.segments), unit="bytes", unit_scale=True, unit_divisor=1024, desc="[yellow]Download"))
else: shutil.move(path_out_without_key+"/"+merged_mp4 , main_out_folder+"/")
# Remove folder out_ts and temp_ts def join_ts_files(self):
shutil.rmtree(path_out_with_key, ignore_errors=True)
shutil.rmtree(path_out_without_key, ignore_errors=True) 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")]
ts_files.sort()
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.print("[cyan]Start join all file")
try:
(
ffmpeg.input(file_list_path, format='concat', safe=0).output(self.output_filename, c="copy", loglevel="quiet").run()
)
console.print(f"[cyan]Clean ...")
except ffmpeg.Error as e:
print(f"Errore durante il salvataggio del file MP4: {e}")
finally:
os.remove(file_list_path)
shutil.rmtree("tmp", ignore_errors=True)
# [ function ]
def dw_m3u8(url, key=None, output_filename="output.mp4"):
downloader = M3U8Downloader(url, key, output_filename)
downloader.download_m3u8()
downloader.download_and_save_ts()
downloader.join_ts_files()
def join_audio_to_video(audio_path, video_path, out_path): def join_audio_to_video(audio_path, video_path, out_path):
@ -197,4 +143,3 @@ def join_audio_to_video(audio_path, video_path, out_path):
# Join all # Join all
final.write_videofile(out_path) final.write_videofile(out_path)

View File

@ -6,3 +6,4 @@ rich
random-user-agent random-user-agent
ffmpeg-python ffmpeg-python
moviepy moviepy
cryptography

8
run.py
View File

@ -21,7 +21,7 @@ def main():
main_update() main_update()
console.print(f"[blue]Find system [white]=> [red]{sys.platform} \n") console.print(f"[blue]Find system [white]=> [red]{sys.platform} \n")
film_search = msg.ask("[blue]Insert word to search in all site: ").strip() film_search = msg.ask("\n[blue]Insert word to search in all site: ").strip()
db_title = Page.search(film_search, domain) db_title = Page.search(film_search, domain)
for i in range(len(db_title)): for i in range(len(db_title)):
@ -29,12 +29,14 @@ def main():
index_select = int(msg.ask("\n[blue]Index to download: ")) index_select = int(msg.ask("\n[blue]Index to download: "))
if db_title[index_select]['type'] == "movie": if db_title[index_select]['type'] == "movie":
console.log(f"[green]Movie select: {db_title[index_select]['name']}") console.print(f"[green]\nMovie select: {db_title[index_select]['name']}")
download_film(db_title[index_select]['id'], db_title[index_select]['name'].replace(" ", "+"), domain) download_film(db_title[index_select]['id'], db_title[index_select]['name'].replace(" ", "+"), domain)
else: else:
console.log(f"[green]Tv select: {db_title[index_select]['name']}") console.print(f"[green]\nTv select: {db_title[index_select]['name']}")
download_tv(db_title[index_select]['id'], db_title[index_select]['name'].replace(" ", "+"), site_version, domain) download_tv(db_title[index_select]['id'], db_title[index_select]['name'].replace(" ", "+"), site_version, domain)
console.print("\n[red]Done")
if __name__ == '__main__': if __name__ == '__main__':
main() main()