370 lines
13 KiB
Python
370 lines
13 KiB
Python
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)
|