Netflix-videos-downloader/helpers/aria2.py

370 lines
13 KiB
Python
Raw Normal View History

2021-08-31 21:57:54 +00:00
import os
import shutil
import subprocess
import sys
import re
import logging
from configs.config import tool
from helpers.ripprocess import ripprocess
class aria2Error(Exception):
pass
class aria2_moded:
def __init__(self, aria2_download_command):
self.logger = logging.getLogger(__name__)
self.aria2_download_command = aria2_download_command
self.env = self.aria2DisableProxies()
self.ripprocess = ripprocess()
self.tool = tool()
self.LOGA_PATH = self.tool.paths()["LOGA_PATH"]
self.bin = self.tool.bin()
self.aria2c_exe = self.bin["aria2c"]
self.last_message_printed = 0
self.speed_radar = "0kbps"
def aria2DisableProxies(self):
env = os.environ.copy()
if env.get("http_proxy"):
del env["http_proxy"]
if env.get("HTTP_PROXY"):
del env["HTTP_PROXY"]
if env.get("https_proxy"):
del env["https_proxy"]
if env.get("HTTPS_PROXY"):
del env["HTTPS_PROXY"]
return env
def read_stdout(self, line):
speed = re.search(r"DL:(.+?)ETA", line)
eta = re.search(r"ETA:(.+?)]", line)
connection = re.search(r"CN:(.+?)DL", line)
percent = re.search(r"\((.*?)\)", line)
size = re.search(r" (.*?)/(.*?)\(", line)
if speed and eta and connection and percent and size:
percent = percent.group().strip().replace(")", "").replace("(", "")
size = size.group().strip().replace(")", "").replace("(", "")
complete, total = size.split("/")
connection = connection.group(1).strip()
eta = eta.group(1).strip()
speed = speed.group(1).strip()
self.speed_radar = speed
stdout_data = {
"percent": str(percent),
"size": str(total),
"complete": str(complete),
"total": str(total),
"connection": str(connection),
"eta": str(eta),
"speed": str(speed),
}
return stdout_data
return None
def if_errors(self, line):
if "exception" in str(line).lower() or "errorcode" in str(line).lower():
return line
return None
def delete_last_message_printed(self):
print(" " * len(str(self.last_message_printed)), end="\r")
def get_status(self, stdout_data: dict):
return "Aria2c_Status; Size: {Size} | Speed: {Speed} | ETA: {ETA} | Progress: {Complete} -> {Total} ({Percent})".format(
Size=stdout_data.get("size"),
Speed=stdout_data.get("speed"),
ETA=stdout_data.get("eta"),
Complete=stdout_data.get("complete"),
Total=stdout_data.get("total"),
Percent=stdout_data.get("percent"),
)
def is_download_completed(self, line):
if "(ok):download completed." in str(line).lower():
return "Download completed: (OK) ({}\\s)".format(self.speed_radar)
return None
def start_download(self):
proc = subprocess.Popen(
self.aria2_download_command,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
bufsize=1,
universal_newlines=True,
env=self.env,
)
check_errors = True
for line in getattr(proc, "stdout"):
if check_errors:
if self.if_errors(line):
raise aria2Error("Aria2c Error {}".format(self.if_errors(line)))
check_errors = False
stdout_data = self.read_stdout(line)
if stdout_data:
status_text = self.get_status(stdout_data)
self.delete_last_message_printed()
print(status_text, end="\r", flush=True)
self.last_message_printed = status_text
else:
download_finished = self.is_download_completed(line)
if download_finished:
self.delete_last_message_printed()
print(download_finished, end="\r", flush=True)
self.last_message_printed = download_finished
self.logger.info("")
return
class aria2:
def __init__(self,):
self.env = self.aria2DisableProxies()
self.ripprocess = ripprocess()
self.tool = tool()
self.bin = self.tool.bin()
self.LOGA_PATH = self.tool.paths()["LOGA_PATH"]
self.config = self.tool.aria2c()
self.aria2c_exe = self.bin["aria2c"]
self.logger = logging.getLogger(__name__)
def convert_args(self, arg):
if arg is True:
return "true"
elif arg is False:
return "false"
elif arg is None:
return "none"
else:
return str(arg)
def append_commands(self, command, option_define, option):
if option == "skip":
return []
return ["{}{}".format(option_define, option)]
def append_two_commands(self, command, cmd1, cmd2):
if cmd2 == "skip":
return []
return [cmd1] + [cmd2]
def aria2Options(
self,
allow_overwrite=True,
file_allocation=None,
auto_file_renaming=False,
async_dns=False,
retry_wait=5,
summary_interval=0,
enable_color=False,
connection=16,
concurrent_downloads=16,
split=16,
header="skip",
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36",
uri_selector="inorder",
console_log_level="skip",
download_result="hide",
quiet="false",
extra_commands=[],
):
options = [] + extra_commands
allow_overwrite = self.convert_args(allow_overwrite)
quiet = self.convert_args(quiet)
file_allocation = self.convert_args(file_allocation)
auto_file_renaming = self.convert_args(auto_file_renaming)
async_dns = self.convert_args(async_dns)
retry_wait = self.convert_args(retry_wait)
enable_color = self.convert_args(enable_color)
connection = self.convert_args(connection)
concurrent_downloads = self.convert_args(concurrent_downloads)
split = self.convert_args(split)
header = self.convert_args(header)
uri_selector = self.convert_args(uri_selector)
console_log_level = self.convert_args(console_log_level)
download_result = self.convert_args(download_result)
##############################################################################
options += self.append_commands(options, "--allow-overwrite=", allow_overwrite)
options += self.append_commands(options, "--quiet=", quiet)
options += self.append_commands(options, "--file-allocation=", file_allocation)
options += self.append_commands(
options, "--auto-file-renaming=", auto_file_renaming
)
options += self.append_commands(options, "--async-dns=", async_dns)
options += self.append_commands(options, "--retry-wait=", retry_wait)
options += self.append_commands(options, "--enable-color=", enable_color)
options += self.append_commands(
options, "--max-connection-per-server=", connection
)
options += self.append_commands(
options, "--max-concurrent-downloads=", concurrent_downloads
)
options += self.append_commands(options, "--split=", split)
options += self.append_commands(options, "--header=", header)
options += self.append_commands(options, "--uri-selector=", uri_selector)
options += self.append_commands(
options, "--console-log-level=", console_log_level
)
options += self.append_commands(options, "--download-result=", download_result)
return options
def aria2DisableProxies(self):
env = os.environ.copy()
if env.get("http_proxy"):
del env["http_proxy"]
if env.get("HTTP_PROXY"):
del env["HTTP_PROXY"]
if env.get("https_proxy"):
del env["https_proxy"]
if env.get("HTTPS_PROXY"):
del env["HTTPS_PROXY"]
return env
def aria2DownloadUrl(self, url, output, options, debug=False, moded=False):
self.debug = debug
aria2_download_command = [self.aria2c_exe] + options
if self.config["enable_logging"]:
LogFile = os.path.join(self.LOGA_PATH, output.replace(".mp4", ".log"))
if os.path.isfile(LogFile):
os.remove(LogFile)
aria2_download_command.append("--log={}".format(LogFile))
if not url.startswith("http"):
raise aria2Error("Url does not start with http/https: {}".format(url))
aria2_download_command.append(url)
aria2_download_command += self.append_two_commands(
aria2_download_command, "-o", output
)
self.aria2Debug("Sending Commands to aria2c...")
self.aria2Debug(aria2_download_command)
self.logger.debug("aria2_download_command: {}".format(aria2_download_command))
if moded:
aria2_moded_download = aria2_moded(aria2_download_command)
aria2_moded_download.start_download()
else:
try:
aria = subprocess.call(aria2_download_command, env=self.env)
except FileNotFoundError:
self.logger.info("UNABLE TO FIND {}".format(self.aria2c_exe))
exit(-1)
if aria != 0:
raise aria2Error("Aria2c exited with code {}".format(aria))
return
def aria2DownloadDash(
self, segments, output, options, debug=False, moded=False, fixbytes=False
):
self.debug = debug
aria2_download_command = [self.aria2c_exe] + options
if self.config["enable_logging"]:
LogFile = os.path.join(self.LOGA_PATH, output.replace(".mp4", ".log"))
if os.path.isfile(LogFile):
os.remove(LogFile)
aria2_download_command.append("--log={}".format(LogFile))
if not isinstance(segments, list) or segments == []:
raise aria2Error("invalid list of urls: {}".format(segments))
if moded:
raise aria2Error("moded version not supported for dash downloads atm...")
txt = output.replace(".mp4", ".txt")
folder = output.replace(".mp4", "")
segments = list(dict.fromkeys(segments))
if os.path.exists(folder):
shutil.rmtree(folder)
if not os.path.exists(folder):
os.makedirs(folder)
segments_location = []
opened_txt = open(txt, "w+")
for num, url in enumerate(segments, start=1):
segment_name = str(num).zfill(5) + ".mp4"
segments_location.append(os.path.join(*[os.getcwd(), folder, segment_name]))
opened_txt.write(url + f"\n out={segment_name}" + f"\n dir={folder}" + "\n")
opened_txt.close()
aria2_download_command += self.append_commands(
aria2_download_command, "--input-file=", txt
)
try:
aria = subprocess.call(aria2_download_command, env=self.env)
except FileNotFoundError:
self.logger.info("UNABLE TO FIND {}".format(self.aria2c_exe))
exit(-1)
if aria != 0:
raise aria2Error("Aria2c exited with code {}".format(aria))
self.logger.info("\nJoining files...")
openfile = open(output, "wb")
total = int(len(segments_location))
for current, fragment in enumerate(segments_location):
if os.path.isfile(fragment):
if fixbytes:
with open(fragment, "rb") as f:
wvdll = f.read()
if (
re.search(
b"tfhd\x00\x02\x00\x1a\x00\x00\x00\x01\x00\x00\x00\x02",
wvdll,
re.MULTILINE | re.DOTALL,
)
is not None
):
fw = open(fragment, "wb")
m = re.search(
b"tfhd\x00\x02\x00\x1a\x00\x00\x00\x01\x00\x00\x00",
wvdll,
re.MULTILINE | re.DOTALL,
)
segment_fixed = (
wvdll[: m.end()] + b"\x01" + wvdll[m.end() + 1 :]
)
fw.write(segment_fixed)
fw.close()
shutil.copyfileobj(open(fragment, "rb"), openfile)
os.remove(fragment)
self.ripprocess.updt(total, current + 1)
openfile.close()
if os.path.isfile(txt):
os.remove(txt)
if os.path.exists(folder):
shutil.rmtree(folder)
def aria2Debug(self, txt):
if self.debug:
self.logger.info(txt)