From 11a41201248f2cfa1a3d101271d53fdbb5f2b8fe Mon Sep 17 00:00:00 2001 From: Ghost <62809003+Ghost6446@users.noreply.github.com> Date: Sun, 7 Jan 2024 13:24:28 +0100 Subject: [PATCH] fix bug m3u8 url --- {Stream/api => Src/Api}/film.py | 110 ++--- {Stream/api => Src/Api}/page.py | 72 ++-- {Stream/api => Src/Api}/tv.py | 216 +++++----- {Stream/assets => Src/Assets}/min_logo.png | Bin {Stream/assets => Src/Assets}/run.gif | Bin {Stream/upload => Src/Upload}/__version__.py | 2 +- {Stream/upload => Src/Upload}/update.py | 2 +- {Stream/util => Src/Util/Helper}/console.py | 20 +- {Stream/util => Src/Util/Helper}/headers.py | 23 +- {Stream/util => Src/Util/Helper}/message.py | 39 +- {Stream/util => Src/Util/Helper}/util.py | 16 +- {Stream/util => Src/Util}/m3u8.py | 415 ++++++++++--------- run.py | 12 +- 13 files changed, 468 insertions(+), 459 deletions(-) rename {Stream/api => Src/Api}/film.py (92%) rename {Stream/api => Src/Api}/page.py (88%) rename {Stream/api => Src/Api}/tv.py (94%) rename {Stream/assets => Src/Assets}/min_logo.png (100%) rename {Stream/assets => Src/Assets}/run.gif (100%) rename {Stream/upload => Src/Upload}/__version__.py (88%) rename {Stream/upload => Src/Upload}/update.py (96%) rename {Stream/util => Src/Util/Helper}/console.py (93%) rename {Stream/util => Src/Util/Helper}/headers.py (95%) rename {Stream/util => Src/Util/Helper}/message.py (88%) rename {Stream/util => Src/Util/Helper}/util.py (65%) rename {Stream/util => Src/Util}/m3u8.py (77%) diff --git a/Stream/api/film.py b/Src/Api/film.py similarity index 92% rename from Stream/api/film.py rename to Src/Api/film.py index c18899a..f9a022c 100644 --- a/Stream/api/film.py +++ b/Src/Api/film.py @@ -1,55 +1,55 @@ -# 3.12.23 -> 10.12.23 - -# Class import -from Stream.util.headers import get_headers -from Stream.util.m3u8 import dw_m3u8 -from Stream.util.util import convert_utf8_name - -# General import -import requests, os, re, json -from bs4 import BeautifulSoup - -# [func] -def get_iframe(id_title, domain): - req_iframe = requests.get(url = f"https://streamingcommunity.{domain}/iframe/{id_title}", headers = { - "User-agent": get_headers() - }) - - url_embed = BeautifulSoup(req_iframe.text, "lxml").find("iframe").get("src") - req_embed = requests.get(url_embed, headers = {"User-agent": get_headers()}).text - return BeautifulSoup(req_embed, "lxml").find("body").find("script").text - -def parse_content(embed_content): - - # Parse parameter from req embed content - win_video = re.search(r"window.video = {.*}", str(embed_content)).group() - win_param = re.search(r"params: {[\s\S]*}", str(embed_content)).group() - - # Parse parameter to make read for json - json_win_video = "{"+win_video.split("{")[1].split("}")[0]+"}" - json_win_param = "{"+win_param.split("{")[1].split("}")[0].replace("\n", "").replace(" ", "") + "}" - json_win_param = json_win_param.replace(",}", "}").replace("'", '"') - return json.loads(json_win_video), json.loads(json_win_param) - -def get_m3u8_url(json_win_video, json_win_param): - return f"https://vixcloud.co/playlist/{json_win_video['id']}?type=video&rendition=720p&token={json_win_param['token720p']}&expires={json_win_param['expires']}" - -def get_m3u8_key(json_win_video, json_win_param, title_name): - req_key = requests.get('https://vixcloud.co/storage/enc.key', headers={ - 'referer': f'https://vixcloud.co/embed/{json_win_video["id"]}?token={json_win_param["token720p"]}&title={title_name.replace(" ", "+")}&referer=1&expires={json_win_param["expires"]}', - }).content - - return "".join(["{:02x}".format(c) for c in req_key]) - -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) - - path_film = os.path.join("videos", lower_title_name.replace("+", " ").replace(",", "") + ".mp4") - dw_m3u8(m3u8_url, None, m3u8_key, path_film) +# 3.12.23 -> 10.12.23 + +# Class import +from Src.Util.Helper.headers import get_headers +from Src.Util.Helper.util import convert_utf8_name +from Src.Util.m3u8 import dw_m3u8 + +# General import +import requests, os, re, json +from bs4 import BeautifulSoup + +# [func] +def get_iframe(id_title, domain): + req_iframe = requests.get(url = f"https://streamingcommunity.{domain}/iframe/{id_title}", headers = { + "User-agent": get_headers() + }) + + url_embed = BeautifulSoup(req_iframe.text, "lxml").find("iframe").get("src") + req_embed = requests.get(url_embed, headers = {"User-agent": get_headers()}).text + return BeautifulSoup(req_embed, "lxml").find("body").find("script").text + +def parse_content(embed_content): + + # Parse parameter from req embed content + win_video = re.search(r"window.video = {.*}", str(embed_content)).group() + win_param = re.search(r"params: {[\s\S]*}", str(embed_content)).group() + + # Parse parameter to make read for json + json_win_video = "{"+win_video.split("{")[1].split("}")[0]+"}" + json_win_param = "{"+win_param.split("{")[1].split("}")[0].replace("\n", "").replace(" ", "") + "}" + json_win_param = json_win_param.replace(",}", "}").replace("'", '"') + return json.loads(json_win_video), json.loads(json_win_param) + +def get_m3u8_url(json_win_video, json_win_param): + return f"https://vixcloud.co/playlist/{json_win_video['id']}?type=video&rendition=720p&token={json_win_param['token720p']}&expires={json_win_param['expires']}" + +def get_m3u8_key(json_win_video, json_win_param, title_name): + req_key = requests.get('https://vixcloud.co/storage/enc.key', headers={ + 'referer': f'https://vixcloud.co/embed/{json_win_video["id"]}?token={json_win_param["token720p"]}&title={title_name.replace(" ", "+")}&referer=1&expires={json_win_param["expires"]}', + }).content + + return "".join(["{:02x}".format(c) for c in req_key]) + +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) + + path_film = os.path.join("videos", lower_title_name.replace("+", " ").replace(",", "") + ".mp4") + dw_m3u8(m3u8_url, None, m3u8_key, path_film) diff --git a/Stream/api/page.py b/Src/Api/page.py similarity index 88% rename from Stream/api/page.py rename to Src/Api/page.py index 6ab00bf..2b0a235 100644 --- a/Stream/api/page.py +++ b/Src/Api/page.py @@ -1,36 +1,36 @@ -# 10.12.23 - -# Class import -from Stream.util.headers import get_headers -from Stream.util.console import console - -# General import -import requests, json, sys -from bs4 import BeautifulSoup - -def get_version(domain): - - try: - r = requests.get(f'https://streamingcommunity.{domain}/', headers={ - 'Authority': f'streamingcommunity.{domain}', - 'User-Agent': get_headers(), - }) - soup = BeautifulSoup(r.text, "lxml") - info_data_page = soup.find("div", {'id': 'app'}).attrs["data-page"] - - return json.loads(info_data_page)['version'] - - except: - console.log("[red]UPDATE DOMANIN") - sys.exit(0) - - -def search(title_search, domain): - - title_search = str(title_search).replace(" ", "+") - r = requests.get( - url = f"https://streamingcommunity.{domain}/api/search?q={title_search}", - headers = {"User-agent": get_headers()} - ) - - return [{'name': title['name'], 'type': title['type'], 'id': title['id']} for title in r.json()['data']] +# 10.12.23 + +# Class import +from Src.Util.Helper.headers import get_headers +from Src.Util.Helper.console import console + +# General import +import requests, json, sys +from bs4 import BeautifulSoup + +def get_version(domain): + + try: + r = requests.get(f'https://streamingcommunity.{domain}/', headers={ + 'Authority': f'streamingcommunity.{domain}', + 'User-Agent': get_headers(), + }) + soup = BeautifulSoup(r.text, "lxml") + info_data_page = soup.find("div", {'id': 'app'}).attrs["data-page"] + + return json.loads(info_data_page)['version'] + + except: + console.log("[red]UPDATE DOMANIN") + sys.exit(0) + + +def search(title_search, domain): + + title_search = str(title_search).replace(" ", "+") + r = requests.get( + url = f"https://streamingcommunity.{domain}/api/search?q={title_search}", + headers = {"User-agent": get_headers()} + ) + + return [{'name': title['name'], 'type': title['type'], 'id': title['id']} for title in r.json()['data']] diff --git a/Stream/api/tv.py b/Src/Api/tv.py similarity index 94% rename from Stream/api/tv.py rename to Src/Api/tv.py index 6d670ad..e674967 100644 --- a/Stream/api/tv.py +++ b/Src/Api/tv.py @@ -1,108 +1,108 @@ -# 3.12.23 -> 10.12.23 - -# Class import -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 - -# General import -import requests, os, re, json -from bs4 import BeautifulSoup - -# [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): - r = requests.get(f"https://streamingcommunity.{domain}/titles/{id_film}-{title_name}", headers={ - 'X-Inertia': 'true', - 'X-Inertia-Version': site_version, - 'User-Agent': get_headers() - }) - - return r.json()['props']['title']['seasons_count'] - -def get_info_season(tv_id, tv_name, domain, version, token, n_stagione): - r = requests.get(f'https://streamingcommunity.{domain}/titles/{tv_id}-{tv_name}/stagione-{n_stagione}', 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, - }) - - return [{'id': ep['id'], 'n': ep['number'], 'name': ep['name']} for ep in r.json()['props']['loadedSeason']['episodes']] - -def get_iframe(tv_id, ep_id, domain, token): - r = 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() - }) - - url_embed = BeautifulSoup(r.text, "lxml").find("iframe").get("src") - req_embed = requests.get(url_embed, headers = {"User-agent": get_headers()}).text - return BeautifulSoup(req_embed, "lxml").find("body").find("script").text - -def parse_content(embed_content): - - # Parse parameter from req embed content - win_video = re.search(r"window.video = {.*}", str(embed_content)).group() - win_param = re.search(r"params: {[\s\S]*}", str(embed_content)).group() - - # Parse parameter to make read for json - json_win_video = "{"+win_video.split("{")[1].split("}")[0]+"}" - json_win_param = "{"+win_param.split("{")[1].split("}")[0].replace("\n", "").replace(" ", "") + "}" - json_win_param = json_win_param.replace(",}", "}").replace("'", '"') - return json.loads(json_win_video), json.loads(json_win_param) - -def get_m3u8_url(json_win_video, json_win_param): - return f"https://vixcloud.co/playlist/{json_win_video['id']}?type=video&rendition=720p&token={json_win_param['token720p']}&expires={json_win_param['expires']}" - -def get_m3u8_key_ep(json_win_video, json_win_param, tv_name, n_stagione, n_ep, ep_title): - req_key = requests.get('https://vixcloud.co/storage/enc.key', headers={ - 'referer': f'https://vixcloud.co/embed/{json_win_video["id"]}?token={json_win_param["token720p"]}&title={tv_name.replace("-", "+")}&referer=1&expires={json_win_param["expires"]}&description=S{n_stagione}%3AE{n_ep}+{ep_title.replace(" ", "+")}&nextEpisode=1', - }).content - - return "".join(["{:02x}".format(c) for c in req_key]) - -def get_m3u8_audio(json_win_video, json_win_param, tv_name, n_stagione, n_ep, ep_title): - - response = requests.get(f'https://vixcloud.co/playlist/{json_win_video["id"]}', params={'token': json_win_param['token'], 'expires': json_win_param["expires"] }, headers={ - 'referer': f'https://vixcloud.co/embed/{json_win_video["id"]}?token={json_win_param["token720p"]}&title={tv_name.replace("-", "+")}&referer=1&expires={json_win_param["expires"]}&description=S{n_stagione}%3AE{n_ep}+{ep_title.replace(" ", "+")}&nextEpisode=1' - }) - - m3u8_cont = response.text.split() - for row in m3u8_cont: - if "audio" in str(row) and "ita" in str(row): - return row.split(",")[-1].split('"')[-2] - - -def main_dw_tv(tv_id, tv_name, version, domain): - - token = get_token(tv_id, domain) - - 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("\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']) - - mp4_name = f"{lower_tv_name.replace('+', '_')}_{str(season_select)}_{str(index_ep_select+1)}" - mp4_format = mp4_name + ".mp4" - mp4_path = os.path.join("videos", mp4_format) - - 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, m3u8_url_audio, m3u8_key, mp4_path) +# 3.12.23 -> 10.12.23 + +# Class import +from Src.Util.Helper.headers import get_headers +from Src.Util.Helper.util import convert_utf8_name +from Src.Util.Helper.console import console, msg +from Src.Util.m3u8 import dw_m3u8 + +# General import +import requests, os, re, json +from bs4 import BeautifulSoup + +# [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): + r = requests.get(f"https://streamingcommunity.{domain}/titles/{id_film}-{title_name}", headers={ + 'X-Inertia': 'true', + 'X-Inertia-Version': site_version, + 'User-Agent': get_headers() + }) + + return r.json()['props']['title']['seasons_count'] + +def get_info_season(tv_id, tv_name, domain, version, token, n_stagione): + r = requests.get(f'https://streamingcommunity.{domain}/titles/{tv_id}-{tv_name}/stagione-{n_stagione}', 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, + }) + + return [{'id': ep['id'], 'n': ep['number'], 'name': ep['name']} for ep in r.json()['props']['loadedSeason']['episodes']] + +def get_iframe(tv_id, ep_id, domain, token): + r = 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() + }) + + url_embed = BeautifulSoup(r.text, "lxml").find("iframe").get("src") + req_embed = requests.get(url_embed, headers = {"User-agent": get_headers()}).text + return BeautifulSoup(req_embed, "lxml").find("body").find("script").text + +def parse_content(embed_content): + + # Parse parameter from req embed content + win_video = re.search(r"window.video = {.*}", str(embed_content)).group() + win_param = re.search(r"params: {[\s\S]*}", str(embed_content)).group() + + # Parse parameter to make read for json + json_win_video = "{"+win_video.split("{")[1].split("}")[0]+"}" + json_win_param = "{"+win_param.split("{")[1].split("}")[0].replace("\n", "").replace(" ", "") + "}" + json_win_param = json_win_param.replace(",}", "}").replace("'", '"') + return json.loads(json_win_video), json.loads(json_win_param) + +def get_m3u8_url(json_win_video, json_win_param): + return f"https://vixcloud.co/playlist/{json_win_video['id']}?type=video&rendition=720p&token={json_win_param['token720p']}&expires={json_win_param['expires']}" + +def get_m3u8_key_ep(json_win_video, json_win_param, tv_name, n_stagione, n_ep, ep_title): + req_key = requests.get('https://vixcloud.co/storage/enc.key', headers={ + 'referer': f'https://vixcloud.co/embed/{json_win_video["id"]}?token={json_win_param["token720p"]}&title={tv_name.replace("-", "+")}&referer=1&expires={json_win_param["expires"]}&description=S{n_stagione}%3AE{n_ep}+{ep_title.replace(" ", "+")}&nextEpisode=1', + }).content + + return "".join(["{:02x}".format(c) for c in req_key]) + +def get_m3u8_audio(json_win_video, json_win_param, tv_name, n_stagione, n_ep, ep_title): + + response = requests.get(f'https://vixcloud.co/playlist/{json_win_video["id"]}', params={'token': json_win_param['token'], 'expires': json_win_param["expires"] }, headers={ + 'referer': f'https://vixcloud.co/embed/{json_win_video["id"]}?token={json_win_param["token720p"]}&title={tv_name.replace("-", "+")}&referer=1&expires={json_win_param["expires"]}&description=S{n_stagione}%3AE{n_ep}+{ep_title.replace(" ", "+")}&nextEpisode=1' + }) + + m3u8_cont = response.text.split() + for row in m3u8_cont: + if "audio" in str(row) and "ita" in str(row): + return row.split(",")[-1].split('"')[-2] + + +def main_dw_tv(tv_id, tv_name, version, domain): + + token = get_token(tv_id, domain) + + 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("\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']) + + mp4_name = f"{lower_tv_name.replace('+', '_')}_{str(season_select)}_{str(index_ep_select+1)}" + mp4_format = mp4_name + ".mp4" + mp4_path = os.path.join("videos", mp4_format) + + 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, m3u8_url_audio, m3u8_key, mp4_path) diff --git a/Stream/assets/min_logo.png b/Src/Assets/min_logo.png similarity index 100% rename from Stream/assets/min_logo.png rename to Src/Assets/min_logo.png diff --git a/Stream/assets/run.gif b/Src/Assets/run.gif similarity index 100% rename from Stream/assets/run.gif rename to Src/Assets/run.gif diff --git a/Stream/upload/__version__.py b/Src/Upload/__version__.py similarity index 88% rename from Stream/upload/__version__.py rename to Src/Upload/__version__.py index 50ab36a..717f476 100644 --- a/Stream/upload/__version__.py +++ b/Src/Upload/__version__.py @@ -1,5 +1,5 @@ __title__ = 'Streaming_community' -__version__ = 'v0.7.1' +__version__ = 'v0.7.0' __author__ = 'Ghost6446' __description__ = 'A command-line program to download film' __license__ = 'MIT License' diff --git a/Stream/upload/update.py b/Src/Upload/update.py similarity index 96% rename from Stream/upload/update.py rename to Src/Upload/update.py index 20ca08e..151e345 100644 --- a/Stream/upload/update.py +++ b/Src/Upload/update.py @@ -1,7 +1,7 @@ # 13.09.2023 # General import -from Stream.util.console import console +from Src.Util.Helper.console import console import os, requests, time # Variable diff --git a/Stream/util/console.py b/Src/Util/Helper/console.py similarity index 93% rename from Stream/util/console.py rename to Src/Util/Helper/console.py index cbc2d66..3872bf3 100644 --- a/Stream/util/console.py +++ b/Src/Util/Helper/console.py @@ -1,10 +1,10 @@ -# 17.09.2023 -> 3.12.23 - -# Import -from rich.console import Console -from rich.prompt import Prompt - -# Variable -msg = Prompt() -console = Console() - +# 17.09.2023 -> 3.12.23 + +# Import +from rich.console import Console +from rich.prompt import Prompt + +# Variable +msg = Prompt() +console = Console() + diff --git a/Stream/util/headers.py b/Src/Util/Helper/headers.py similarity index 95% rename from Stream/util/headers.py rename to Src/Util/Helper/headers.py index cf7f512..9a1e915 100644 --- a/Stream/util/headers.py +++ b/Src/Util/Helper/headers.py @@ -1,13 +1,12 @@ -# 3.12.23 -> 10.12.23 - -# Import -from random_user_agent.user_agent import UserAgent -from random_user_agent.params import SoftwareName, OperatingSystem - -# [func] -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) - +# 3.12.23 -> 10.12.23 + +# Import +from random_user_agent.user_agent import UserAgent +from random_user_agent.params import SoftwareName, OperatingSystem + +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/message.py b/Src/Util/Helper/message.py similarity index 88% rename from Stream/util/message.py rename to Src/Util/Helper/message.py index 4202c86..76de034 100644 --- a/Stream/util/message.py +++ b/Src/Util/Helper/message.py @@ -1,20 +1,19 @@ -# 3.12.23 - -# Import -from Stream.util.console import console - -# [Function] -def msg_start(): - - msg = """ - _____ _ _ _ _ - / ____| | (_) (_) | - | (___ | |_ _ __ ___ __ _ _ __ ___ _ _ __ __ _ ___ ___ _ __ ___ _ _ _ __ _| |_ _ _ - \___ \| __| '__/ _ \/ _` | '_ ` _ \| | '_ \ / _` | / __/ _ \| '_ ` _ \| | | | '_ \| | __| | | | - ____) | |_| | | __/ (_| | | | | | | | | | | (_| | | (_| (_) | | | | | | |_| | | | | | |_| |_| | - |_____/ \__|_| \___|\__,_|_| |_| |_|_|_| |_|\__, | \___\___/|_| |_| |_|\__,_|_| |_|_|\__|\__, | - __/ | __/ | - |___/ |___/ - """ - - console.log(f"[purple]{msg}") \ No newline at end of file +# 3.12.23 + +# Import +from Src.Util.Helper.console import console + +def msg_start(): + + msg = """ + _____ _ _ _ _ + / ____| | (_) (_) | + | (___ | |_ _ __ ___ __ _ _ __ ___ _ _ __ __ _ ___ ___ _ __ ___ _ _ _ __ _| |_ _ _ + \___ \| __| '__/ _ \/ _` | '_ ` _ \| | '_ \ / _` | / __/ _ \| '_ ` _ \| | | | '_ \| | __| | | | + ____) | |_| | | __/ (_| | | | | | | | | | | (_| | | (_| (_) | | | | | | |_| | | | | | |_| |_| | + |_____/ \__|_| \___|\__,_|_| |_| |_|_|_| |_|\__, | \___\___/|_| |_| |_|\__,_|_| |_|_|\__|\__, | + __/ | __/ | + |___/ |___/ + """ + + console.print(f"[purple]{msg}") \ No newline at end of file diff --git a/Stream/util/util.py b/Src/Util/Helper/util.py similarity index 65% rename from Stream/util/util.py rename to Src/Util/Helper/util.py index 3e5bd05..2f689df 100644 --- a/Stream/util/util.py +++ b/Src/Util/Helper/util.py @@ -1,7 +1,7 @@ # 4.01.2023 # Import -import ffmpeg, subprocess +import ffmpeg def convert_utf8_name(name): return str(name).encode('utf-8').decode('latin-1') @@ -12,20 +12,6 @@ def there_is_audio(ts_file_path): 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/Stream/util/m3u8.py b/Src/Util/m3u8.py similarity index 77% rename from Stream/util/m3u8.py rename to Src/Util/m3u8.py index 112be43..a10a38c 100644 --- a/Stream/util/m3u8.py +++ b/Src/Util/m3u8.py @@ -1,195 +1,220 @@ -# 5.01.24 - -# Import -import requests, re, os, ffmpeg, shutil, time -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 - -# 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) - - -# [ main class ] -class M3U8Downloader: - - 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 - if key != 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:") - 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 # URI | METHOD | IV - - def parse_key(self, raw_iv): - 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') - - for i in range(len(lines)): - line = str(lines[i]) - - if line.startswith("#EXT-X-KEY:"): - x_key_dict = self.decode_ext_x_key(line) - self.parse_key(x_key_dict['IV']) - - if line.startswith("#EXTINF"): - ts_url = lines[i+1] - - if not ts_url.startswith("http"): - ts_url = m3u8_base_url + ts_url - - 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()}) - - if response.ok: - 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() - decrypted_data = decryptor.update(encrypted_data) + decryptor.finalize() - - return decrypted_data - - 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") - - # Download video or audio ts file - if not os.path.exists(video_ts_filename): # Only for media that not use audio - 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(video_ts_filename, "wb") as ts_file: - ts_file.write(decrypted_data) - - else: - with open(video_ts_filename, "wb") as ts_file: - ts_file.write(ts_response) - - # Donwload only audio ts file - 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") - - if not os.path.exists(video_audio_ts_filename): # Only for media use 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, range(len(self.segments)) ), total=len(self.segments), unit="bytes", unit_scale=True, unit_divisor=1024, desc="[yellow]Download")) - - def join_ts_files(self): - - current_dir = os.path.dirname(os.path.realpath(__file__)) - file_list_path = os.path.join(current_dir, 'file_list.txt') - - # Make sort by number - ts_files = [f for f in os.listdir(self.temp_folder) if f.endswith(".ts")] - def extract_number(file_name): - return int(''.join(filter(str.isdigit, file_name))) - - ts_files.sort(key=extract_number) - - 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.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() - ) - console.log(f"[cyan]Clean ...") - except ffmpeg.Error as e: - print(f"Errore durante il salvataggio del file MP4: {e}") - finally: - time.sleep(2) - os.remove(file_list_path) - shutil.rmtree("tmp", ignore_errors=True) - - -# [ function ] -def dw_m3u8(url, audio_url=None, key=None, output_filename="output.mp4"): - - downloader = M3U8Downloader(url, audio_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): - - # 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) +# 5.01.24 -> 7.01.24 + +# Import +import requests, re, os, ffmpeg, shutil, time, sys +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 + +# Class import +from Src.Util.Helper.console import console +from Src.Util.Helper.headers import get_headers +from Src.Util.Helper.util import there_is_audio, merge_ts_files + + +# Variable +os.makedirs("videos", exist_ok=True) + + +# [ main class ] +class M3U8Downloader: + + 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 + if key != 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:") + 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 # URI | METHOD | IV + + def parse_key(self, raw_iv): + 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') + + for i in range(len(lines)): + line = str(lines[i]) + + if line.startswith("#EXT-X-KEY:"): + x_key_dict = self.decode_ext_x_key(line) + self.parse_key(x_key_dict['IV']) + + if line.startswith("#EXTINF"): + ts_url = lines[i+1] + + if not ts_url.startswith("http"): + ts_url = m3u8_base_url + ts_url + + 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") + + # Check video ts segment + if len(self.segments) == 0: + console.log("[red]No ts files to download") + sys.exit(0) + + # Check audio ts segment + if self.m3u8_audio != None: + console.log(f"[cyan]Find: {len(self.segments_audio)} ts audio file to download") + + if len(self.segments_audio) == 0: + console.log("[red]No ts audio files to download") + sys.exit(0) + + def download_m3u8(self): + response = requests.get(self.m3u8_url, headers={'user-agent': get_headers()}) + + if response.ok: + m3u8_content = response.text + self.parse_m3u8(m3u8_content) + + else: + console.log("[red]Wrong m3u8 url") + sys.exit(0) + + + if self.m3u8_audio != None: + + # 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") + + console.log("[cyan]=> Download audio") + 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() + decrypted_data = decryptor.update(encrypted_data) + decryptor.finalize() + + return decrypted_data + + 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") + + # Download video or audio ts file + if not os.path.exists(video_ts_filename): # Only for media that not use audio + 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(video_ts_filename, "wb") as ts_file: + ts_file.write(decrypted_data) + + else: + with open(video_ts_filename, "wb") as ts_file: + ts_file.write(ts_response) + + # Donwload only audio ts file + 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") + + if not os.path.exists(video_audio_ts_filename): # Only for media use 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, range(len(self.segments)) ), total=len(self.segments), unit="bytes", unit_scale=True, unit_divisor=1024, desc="[yellow]Download")) + + def join_ts_files(self): + + current_dir = os.path.dirname(os.path.realpath(__file__)) + file_list_path = os.path.join(current_dir, 'file_list.txt') + + # Make sort by number + ts_files = [f for f in os.listdir(self.temp_folder) if f.endswith(".ts")] + def extract_number(file_name): + return int(''.join(filter(str.isdigit, file_name))) + + ts_files.sort(key=extract_number) + + 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.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() + ) + console.log(f"[cyan]Clean ...") + except ffmpeg.Error as e: + console.log(f"[red]Error saving MP4: {e.stdout}") + sys.exit(0) + + time.sleep(2) + os.remove(file_list_path) + shutil.rmtree("tmp", ignore_errors=True) + + +# [ main function ] +def dw_m3u8(url, audio_url=None, key=None, output_filename="output.mp4"): + + downloader = M3U8Downloader(url, audio_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): + + # 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) diff --git a/run.py b/run.py index 5c6a3b8..d2fbf56 100644 --- a/run.py +++ b/run.py @@ -1,12 +1,12 @@ # 10.12.23 # Class import -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 -from Stream.api.film import main_dw_film as download_film -from Stream.api.tv import main_dw_tv as download_tv +import Src.Api.page as Page +from Src.Api.film import main_dw_film as download_film +from Src.Api.tv import main_dw_tv as download_tv +from Src.Util.Helper.message import msg_start +from Src.Util.Helper.console import console, msg +from Src.Upload.update import main_update # General import import sys