- 新增自定义域名

- 增加连接报错域名提示 #195
- 修复 Dev-debug 开启的错误 #192
- 重做重命名逻辑,支持合集重命名以及文件夹内重命名,支持字幕重命名。
This commit is contained in:
EstrellaXD
2023-04-23 21:35:57 +08:00
parent 7622010803
commit ee8f7dd1a2
9 changed files with 276 additions and 98 deletions

View File

@@ -2,9 +2,8 @@ import json
import os
import logging
from dataclasses import dataclass
from .const import DEFAULT_SETTINGS, ENV_TO_ATTR
from module.conf.const import ENV_TO_ATTR
from module.models import Config
logger = logging.getLogger(__name__)
@@ -15,70 +14,48 @@ except ImportError:
VERSION = "DEV_VERSION"
class ConfLoad(dict):
def __getattr__(self, item):
return self.get(item)
def __setattr__(self, key, value):
self[key] = value
def save_config_to_file(config: Config, path: str):
with open(path, "w", encoding="utf-8") as f:
json.dump(config, f, indent=4)
logger.info(f"Config saved")
@dataclass
class Settings:
program: ConfLoad
downloader: ConfLoad
rss_parser: ConfLoad
bangumi_manage: ConfLoad
debug: ConfLoad
proxy: ConfLoad
notification: ConfLoad
def load_config_from_file(path: str) -> Config:
with open(path, "r", encoding="utf-8") as f:
config = json.load(f)
return config
def __init__(self, path: str | None):
self.load(path)
def load(self, path: str | None):
if path is None:
conf = DEFAULT_SETTINGS
elif os.path.isfile(path):
with open(path, "r") as f:
# Use utf-8 to avoid encoding error
conf = json.load(f, encoding="utf-8")
def _val_from_env(env: str, attr: tuple):
if isinstance(attr, tuple):
if attr[1] == "bool":
return os.environ[env].lower() == "true"
elif attr[1] == "int":
return int(os.environ[env])
elif attr[1] == "float":
return float(os.environ[env])
else:
conf = self._create_config()
for key, section in conf.items():
setattr(self, key, ConfLoad(section))
return os.environ[env]
else:
return os.environ[env]
@staticmethod
def _val_from_env(env, attr):
val = os.environ[env]
if isinstance(attr, tuple):
conv_func = attr[1]
val = conv_func(val)
return val
def _create_config(self):
_settings = DEFAULT_SETTINGS
for key, section in ENV_TO_ATTR.items():
for env, attr in section.items():
if env in os.environ:
attr_name = attr[0] if isinstance(attr, tuple) else attr
_settings[key][attr_name] = self._val_from_env(env, attr)
with open(CONFIG_PATH, "w") as f:
# Save utf-8 to avoid encoding error
json.dump(_settings, f, indent=4, ensure_ascii=False)
logger.warning(f"Config file had been transferred from environment variables to {CONFIG_PATH}, some settings may be lost.")
logger.warning("Please check the config file and restart the program.")
logger.warning("Please check github wiki (https://github.com/EstrellaXD/Auto_Bangumi/#/wiki) for more information.")
return _settings
def env_to_config() -> Config:
_settings = Config()
for key, section in ENV_TO_ATTR.items():
for env, attr in section.items():
if env in os.environ:
attr_name = attr[0] if isinstance(attr, tuple) else attr
setattr(_settings, attr_name, _val_from_env(env, attr))
return _settings
if os.path.isdir("config") and VERSION == "DEV_VERSION":
CONFIG_PATH = "config/config_dev.json"
settings = load_config_from_file(CONFIG_PATH)
print(dict(settings))
elif os.path.isdir("config") and VERSION != "DEV_VERSION":
CONFIG_PATH = "config/config.json"
else:
CONFIG_PATH = None
settings = Settings(CONFIG_PATH)

View File

@@ -74,16 +74,16 @@ class DownloadClient:
# logger.info("to rule.")
logger.debug("Finished.")
def get_torrent_info(self):
def get_torrent_info(self, category="Bangumi"):
return self.client.torrents_info(
status_filter="completed", category="Bangumi"
status_filter="completed", category=category
)
def rename_torrent_file(self, hash, new_file_name, old_path, new_path):
def rename_torrent_file(self, _hash, old_path, new_path):
self.client.torrents_rename_file(
torrent_hash=hash, new_file_name=new_file_name, old_path=old_path, new_path=new_path
torrent_hash=_hash, old_path=old_path, new_path=new_path
)
logger.info(f"{old_path} >> {new_path}, new name {new_file_name}")
logger.info(f"{old_path} >> {new_path}")
def delete_torrent(self, hashes):
self.client.torrents_delete(
@@ -114,3 +114,6 @@ class DownloadClient:
def get_torrent_path(self, hashes):
return self.client.get_torrent_path(hashes)
def set_category(self, hashes, category):
self.client.set_category(hashes, category)

View File

@@ -19,6 +19,8 @@ class QbDownloader:
host=host,
username=username,
password=password,
VERIFY_WEBUI_CERTIFICATE=settings.downloader.ssl,
RAISE_ERROR_FOR_UNSUPPORTED_QBITTORRENT_VERSIONS=True,
)
while True:
try:
@@ -56,9 +58,8 @@ class QbDownloader:
torrent_hashes=hash
)
def torrents_rename_file(self, torrent_hash, new_file_name, old_path, new_path):
self._client.torrents_rename_file(torrent_hash=torrent_hash, new_file_name=new_file_name,
old_path=old_path, new_path=new_path)
def torrents_rename_file(self, torrent_hash, old_path, new_path):
self._client.torrents_rename_file(torrent_hash=torrent_hash, old_path=old_path, new_path=new_path)
def get_rss_info(self):
item = self._client.rss_items().get("Mikan_RSS")
@@ -92,5 +93,8 @@ class QbDownloader:
def get_download_rule(self):
return self._client.rss_rules()
def get_torrent_path(self, hash):
return self._client.torrents_info(hashes=hash)[0].save_path
def get_torrent_path(self, _hash):
return self._client.torrents_info(hashes=_hash)[0].save_path
def set_category(self, _hash, category):
self._client.torrents_set_category(category, hashes=_hash)

View File

@@ -7,7 +7,6 @@ from module.core.download_client import DownloadClient
from module.conf import settings
from module.parser import TitleParser
from module.network import PostNotification, ServerChanNotification
logger = logging.getLogger(__name__)
@@ -23,8 +22,8 @@ class Renamer:
logger.info(f"Finished checking {torrent_count} files' name, renamed {rename_count} files.")
logger.debug(f"Checked {torrent_count} files")
def get_torrent_info(self):
recent_info = self.client.get_torrent_info()
def get_torrent_info(self, category="Bangumi"):
recent_info = self.client.get_torrent_info(category=category)
torrent_count = len(recent_info)
return recent_info, torrent_count
@@ -65,9 +64,8 @@ class Renamer:
try:
new_name = self._renamer.download_parser(name, folder_name, season, suffix, settings.bangumi_manage.rename_method)
if path_name != new_name:
old_path = info.content_path.replace(info.save_path, "")
old_path = old_path[len(os.path.sep):]
self.client.rename_torrent_file(torrent_hash, new_name, old_path, new_name)
old_path = info.content_path.replace(info.save_path, "")[len(os.path.sep):]
self.client.rename_torrent_file(torrent_hash, old_path, new_name)
rename_count += 1
else:
continue
@@ -85,6 +83,111 @@ class Renamer:
torrent_hash = info.hash
_, season, folder_name, _, download_path = self.split_path(info.content_path)
new_path = os.path.join(settings.downloader.path, folder_name, f"Season {season}")
# print(new_path)
self.client.move_torrent(torrent_hash, new_path)
@staticmethod
def check_files(info, suffix_type: str = "media"):
if suffix_type == "subtitle":
suffix_list = [".ass", ".srt"]
else:
suffix_list = [".mp4", ".mkv"]
file_list = []
for f in info.files:
file_name = f.name
suffix = os.path.splitext(file_name)[-1]
if suffix in suffix_list:
file_list.append(file_name)
return file_list
def rename_file(self, info, media_path):
old_name = info.name
suffix = os.path.splitext(media_path)[-1]
compare_name = media_path.split(os.path.sep)[-1]
folder_name, season = self.get_folder_and_season(info.save_path)
new_path = self._renamer.download_parser(old_name, folder_name, season, suffix)
if compare_name != new_path:
try:
self.client.rename_torrent_file(_hash=info.hash, old_path=media_path, new_path=new_path)
except Exception as e:
logger.warning(f"{old_name} rename failed")
logger.warning(f"Folder name: {folder_name}, Season: {season}, Suffix: {suffix}")
logger.debug(e)
def rename_collection(self, info, media_list: list[str]):
folder_name, season = self.get_folder_and_season(info.save_path)
_hash = info.hash
for media_path in media_list:
path_len = len(media_path.split(os.path.sep))
if path_len <= 2:
suffix = os.path.splitext(media_path)[-1]
old_name = media_path.split(os.path.sep)[-1]
new_name = self._renamer.download_parser(old_name, folder_name, season, suffix)
if old_name != new_name:
try:
self.client.rename_torrent_file(_hash=_hash, old_path=media_path, new_path=new_name)
except Exception as e:
logger.warning(f"{old_name} rename failed")
logger.warning(f"Folder name: {folder_name}, Season: {season}, Suffix: {suffix}")
logger.debug(e)
self.client.set_category(category="BangumiCollection", hashes=_hash)
def rename_subtitles(self, subtitle_list: list[str], media_old_name, media_new_name, _hash):
for subtitle_file in subtitle_list:
if re.search(media_old_name, subtitle_file) is not None:
subtitle_lang = subtitle_file.split(".")[-2]
new_subtitle_name = f"{media_new_name}.{subtitle_lang}.ass"
self.client.rename_torrent_file(_hash, subtitle_file, new_subtitle_name)
logger.info(f"Rename subtitles for {media_old_name} to {media_new_name}")
@staticmethod
def get_folder_and_season(save_path: str):
# Remove default save path
save_path = save_path.replace(settings.downloader.path, "")
# Check windows or linux path
path_parts = PurePath(save_path).parts \
if PurePath(save_path).name != save_path \
else PureWindowsPath(save_path).parts
# Get folder name
folder_name = path_parts[1] if path_parts[0] == "/" else path_parts[0]
# Get season
try:
if re.search(r"S\d{1,2}|[Ss]eason", path_parts[-1]) is not None:
season = int(re.search(r"\d{1,2}", path_parts[-1]).group())
else:
season = 1
except Exception as e:
logger.debug(e)
logger.debug("No Season info")
season = 1
return folder_name, season
def rename(self):
# Get torrent info
recent_info, torrent_count = self.get_torrent_info()
rename_count = 0
for info in recent_info:
try:
media_list = self.check_files(info)
if len(media_list) == 1:
self.rename_file(info, media_list[0])
rename_count += 1
# TODO: Rename subtitles
elif len(media_list) > 1:
logger.info("Start rename collection")
self.rename_collection(info, media_list)
rename_count += len(media_list)
else:
logger.warning(f"{info.name} has no media file")
except Exception as e:
logger.warning(f"{info.name} rename failed")
logger.debug(e)
if settings.bangumi_manage.remove_bad_torrent:
self.client.delete_torrent(info.hash)
if __name__ == '__main__':
client = DownloadClient()
rn = Renamer(client)
rn.rename()

View File

@@ -1 +1,2 @@
from .bangumi import *
from .config import Config

View File

@@ -0,0 +1,69 @@
from pydantic import BaseModel, Field
# Sub config
class Program(BaseModel):
sleep_time: int = Field(7200, description="Sleep time")
rename_times: int = Field(20, description="Rename times in one loop")
webui_port: int = Field(7892, description="WebUI port")
class Downloader(BaseModel):
type: str = Field("qbittorrent", description="Downloader type")
host: str = Field("172.17.0.1:8080", description="Downloader host")
username: str = Field("admin", description="Downloader username")
password: str = Field("adminadmin", description="Downloader password")
path: str = Field("/downloads/Bangumi", description="Downloader path")
ssl: bool = Field(False, description="Downloader ssl")
class RSSParser(BaseModel):
enable: bool = Field(True, description="Enable RSS parser")
type: str = Field("mikan", description="RSS parser type")
token: str = Field("token", description="RSS parser token")
custom_url: str = Field("mikanani.me", description="Custom RSS host url")
enable_tmdb: bool = Field(False, description="Enable TMDB")
filter: list[str] = Field(["720", r"\d+-\d"], description="Filter")
language: str = "zh"
class BangumiManage(BaseModel):
enable: bool = Field(True, description="Enable bangumi manage")
eps_complete: bool = Field(False, description="Enable eps complete")
rename_method: str = Field("pn", description="Rename method")
group_tag: bool = Field(False, description="Enable group tag")
remove_bad_torrent: bool = Field(False, description="Remove bad torrent")
class Debug(BaseModel):
enable: bool = Field(False, description="Enable debug")
level: str = Field("debug", description="Debug level")
file: str = Field("debug.log", description="Debug file")
dev_debug: bool = Field(False, description="Enable dev debug")
class Proxy(BaseModel):
enable: bool = Field(False, description="Enable proxy")
type: str = Field("http", description="Proxy type")
host: str = Field("", description="Proxy host")
port: int = Field(0, description="Proxy port")
username: str = Field("", description="Proxy username")
password: str = Field("", description="Proxy password")
class Notification(BaseModel):
enable: bool = Field(False, description="Enable notification")
type: str = Field("telegram", description="Notification type")
token: str = Field("", description="Notification token")
chat_id: str = Field("", description="Notification chat id")
class Config(BaseModel):
data_version: float = Field(4.0, description="Data version")
program: Program
downloader: Downloader
rss_parser: RSSParser
bangumi_manage: BangumiManage
debug: Debug
proxy: Proxy
notification: Notification

View File

@@ -43,9 +43,9 @@ class RequestURL:
req.raise_for_status()
return req
except requests.RequestException as e:
logger.debug(f"URL: {url}")
logger.debug(e)
logger.warning("ERROR with Connection.Please check DNS/Connection settings")
logger.warning(f"Cannot connect to {url}.")
logger.warning("Please check DNS/Connection settings")
time.sleep(5)
try_time += 1
except Exception as e:

View File

@@ -15,28 +15,42 @@ class DownloadInfo:
folder_name: str
RULES = [
r"(.*) - (\d{1,4}|\d{1,4}\.\d{1,2})(?:v\d{1,2})?(?: )?(?:END)?(.*)",
r"(.*)[\[ E](\d{1,3}|\d{1,3}\.\d{1,2})(?:v\d{1,2})?(?: )?(?:END)?[\] ](.*)",
r"(.*)\[第(\d*\.*\d*)话(?:END)?\](.*)",
r"(.*)\[第(\d*\.*\d*)話(?:END)?\](.*)",
r"(.*)第(\d*\.*\d*)话(?:END)?(.*)",
r"(.*)第(\d*\.*\d*)話(?:END)?(.*)",
r"(.*)E(\d{1,3})(.*)",
]
class DownloadParser:
def __init__(self):
self.rules = [
r"(.*) - (\d{1,4}|\d{1,4}\.\d{1,2})(?:v\d{1,2})?(?: )?(?:END)?(.*)",
r"(.*)[\[ E](\d{1,3}|\d{1,3}\.\d{1,2})(?:v\d{1,2})?(?: )?(?:END)?[\] ](.*)",
r"(.*)\[第(\d*\.*\d*)话(?:END)?\](.*)",
r"(.*)\[第(\d*\.*\d*)話(?:END)?\](.*)",
r"(.*)第(\d*\.*\d*)话(?:END)?(.*)",
r"(.*)第(\d*\.*\d*)話(?:END)?(.*)",
]
self.method_dict = {
"normal": self.rename_normal,
"pn": self.rename_pn,
"advance": self.rename_advance,
"no_season_pn": self.rename_no_season_pn,
"none": self.rename_none
}
@staticmethod
def rename_init(name, folder_name, season, suffix) -> DownloadInfo:
n = re.split(r"[\[\]()【】()]", name)
suffix = suffix if suffix is not None else n[-1]
file_name = name.replace(f"[{n[1]}]", "")
suffix = suffix if suffix else n[-1]
if len(n) > 1:
file_name = name.replace(f"[{n[1]}]", "")
else:
file_name = name
if season < 10:
season = f"0{season}"
return DownloadInfo(name, season, suffix, file_name, folder_name)
def rename_normal(self, info: DownloadInfo):
for rule in self.rules:
for rule in RULES:
match_obj = re.match(rule, info.name, re.I)
if match_obj is not None:
title = re.sub(r"([Ss]|Season )\d{1,3}", "", match_obj.group(1)).strip()
@@ -44,7 +58,7 @@ class DownloadParser:
return new_name
def rename_pn(self, info: DownloadInfo):
for rule in self.rules:
for rule in RULES:
match_obj = re.match(rule, info.file_name, re.I)
if match_obj is not None:
title = re.sub(r"([Ss]|Season )\d{1,3}", "", match_obj.group(1)).strip()
@@ -57,7 +71,7 @@ class DownloadParser:
return new_name
def rename_advance(self, info: DownloadInfo):
for rule in self.rules:
for rule in RULES:
match_obj = re.match(rule, info.file_name, re.I)
if match_obj is not None:
new_name = re.sub(
@@ -68,7 +82,7 @@ class DownloadParser:
return new_name
def rename_no_season_pn(self, info: DownloadInfo):
for rule in self.rules:
for rule in RULES:
match_obj = re.match(rule, info.file_name, re.I)
if match_obj is not None:
title = match_obj.group(1).strip()
@@ -83,16 +97,16 @@ class DownloadParser:
def rename_none(info: DownloadInfo):
return info.name
def download_rename(self, name, folder_name, season, suffix, method):
def download_rename(
self,
name: str,
folder_name,
season,
suffix,
method
):
rename_info = self.rename_init(name, folder_name, season, suffix)
method_dict = {
"normal": self.rename_normal,
"pn": self.rename_pn,
"advance": self.rename_advance,
"no_season_pn": self.rename_no_season_pn,
"none": self.rename_none
}
return method_dict[method.lower()](rename_info)
return self.method_dict[method.lower()](rename_info)
if __name__ == "__main__":

View File

@@ -18,7 +18,14 @@ class TitleParser:
def raw_parser(self, raw: str):
return self._raw_parser.analyse(raw)
def download_parser(self, download_raw, folder_name, season, suffix, method=settings.bangumi_manage.method):
def download_parser(
self,
download_raw: str,
folder_name: str | None = None,
season: int | None = None,
suffix: str | None = None,
method: str = settings.bangumi_manage.rename_method
):
return self._download_parser.download_rename(download_raw, folder_name, season, suffix, method)
def tmdb_parser(self, title: str, season: int):