Netflix-videos-downloader/helpers/aria2.py

421 wiersze
16 KiB
Python
Czysty Wina Historia

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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,
auto_file_renaming=False,
async_dns=False,
retry_wait=5,
enable_color=True,
concurrent_downloads=5,
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=[],
# 单服务器最大连接线程数
connection=64,
# 单任务最大连接线程数
split=64,
# 代理地址,根据需要修改
http_proxy_aria2c="http://127.0.0.1:7890",
https_proxy_aria2c="http://127.0.0.1:7890",
# 保存会话进度,用于断点续传
save_session_interval=1,
auto_save_interval=30,
force_save="false",
# 文件最小分段大小
min_split_size="4M",
# 磁盘缓存
max_tries="0",
# HTTP/FTP下载分片大小
piece_length="1M",
# 下载进度摘要输出间隔时间
summary_interval=0,
# 断点续传
continue_aria2c="true",
# 文件预分配方式 可选none, prealloc, trunc, falloc
# 机械硬盘falloc
# 固态硬盘none
# prealloc 分配速度慢, trunc 无实际作用,不推荐使用。
file_allocation="none",
# 磁盘缓存
disk_cache="64M",
):
options = [] + extra_commands
allow_overwrite = self.convert_args(allow_overwrite)
quiet = self.convert_args(quiet)
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)
concurrent_downloads = self.convert_args(concurrent_downloads)
header = self.convert_args(header)
user_agent = self.convert_args(user_agent)
uri_selector = self.convert_args(uri_selector)
console_log_level = self.convert_args(console_log_level)
download_result = self.convert_args(download_result)
connection = self.convert_args(connection)
split = self.convert_args(split)
http_proxy_aria2c = self.convert_args(http_proxy_aria2c)
https_proxy_aria2c = self.convert_args(https_proxy_aria2c)
save_session_interval = self.convert_args(save_session_interval)
auto_save_interval = self.convert_args(auto_save_interval)
force_save = self.convert_args(force_save)
min_split_size = self.convert_args(min_split_size)
max_tries = self.convert_args(max_tries)
piece_length = self.convert_args(piece_length)
summary_interval = self.convert_args(summary_interval)
continue_aria2c = self.convert_args(continue_aria2c)
file_allocation = self.convert_args(file_allocation)
disk_cache = self.convert_args(disk_cache)
##############################################################################
options += self.append_commands(options, "--allow-overwrite=", allow_overwrite)
options += self.append_commands(options, "--quiet=", quiet)
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-concurrent-downloads=", concurrent_downloads
)
options += self.append_commands(options, "--header=", header)
options += self.append_commands(options, "--user-agent=", user_agent)
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)
options += self.append_commands(
options, "--max-connection-per-server=", connection
)
options += self.append_commands(options, "--split=", split)
options += self.append_commands(options, "--http-proxy=", http_proxy_aria2c)
options += self.append_commands(options, "--https-proxy=", https_proxy_aria2c)
options += self.append_commands(options, "--save-session-interval=", save_session_interval)
options += self.append_commands(options, "--auto-save-interval=", auto_save_interval)
options += self.append_commands(options, "--force-save=", force_save)
options += self.append_commands(options, "--min-split-size=", min_split_size)
options += self.append_commands(options, "--max-tries=", max_tries)
options += self.append_commands(options, "--piece-length=", piece_length)
options += self.append_commands(options, "--summary-interval=", summary_interval)
options += self.append_commands(options, "--continue=", continue_aria2c)
options += self.append_commands(options, "--file-allocation=", file_allocation)
options += self.append_commands(options, "--disk-cache=", disk_cache)
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)