mirror of
https://github.com/Arrowar/StreamingCommunity.git
synced 2025-06-07 03:55:24 +00:00
201 lines
6.3 KiB
Python
201 lines
6.3 KiB
Python
# 4.08.2023 -> 14.09.2023 -> 17.09.2023 -> 3.12.2023
|
|
|
|
# Import
|
|
import re, os, sys, glob, time, requests, shutil, ffmpeg, subprocess
|
|
from functools import partial
|
|
from multiprocessing.dummy import Pool
|
|
from tqdm.rich import tqdm
|
|
import moviepy.editor as mp
|
|
|
|
# Class import
|
|
from Stream.util.console import console
|
|
|
|
# Disable warning
|
|
import warnings
|
|
from tqdm import TqdmExperimentalWarning
|
|
warnings.filterwarnings("ignore", category=TqdmExperimentalWarning)
|
|
|
|
|
|
# Variable
|
|
main_out_folder = "videos"
|
|
os.makedirs(main_out_folder, exist_ok=True)
|
|
path_out_without_key = "temp_ts"
|
|
path_out_with_key = "out_ts"
|
|
|
|
|
|
# [ 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 ""
|
|
|
|
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:")
|
|
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)}
|
|
return key_map
|
|
|
|
|
|
# [ util ]
|
|
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:
|
|
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")
|
|
open("part_list.txt", "wb")
|
|
|
|
# Variable for download
|
|
list_mp4_part = []
|
|
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"):
|
|
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")
|
|
|
|
if is_encryped and decrypt_key == "":
|
|
console.log(f"[red]M3U8 Is encryped")
|
|
sys.exit(0)
|
|
|
|
|
|
# Using multithreading to download all ts file
|
|
pool = Pool(15)
|
|
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:
|
|
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(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+"/")
|
|
|
|
# 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)
|
|
|
|
# Join all
|
|
final.write_videofile(out_path)
|
|
|