mirror of
https://github.com/jxxghp/MoviePilot-Plugins.git
synced 2026-03-27 10:05:57 +00:00
新增整理VCB-Studio压制组作品插件
This commit is contained in:
BIN
icons/vcbmonitor.png
Normal file
BIN
icons/vcbmonitor.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 530 KiB |
@@ -191,6 +191,14 @@
|
||||
"author": "233@qingwa",
|
||||
"level": 2
|
||||
},
|
||||
"VCBAnimeMonitor": {
|
||||
"name": "整理VCB-Studio压制组作品",
|
||||
"description": "提高部分VCB-Studio作品的识别准确率,将VCB-Studio的作品统一转移到指定目录同时进行刮削整理",
|
||||
"version": "1.6.5",
|
||||
"icon": "qingwa.png",
|
||||
"author": "pixel@qingwa",
|
||||
"level": 2
|
||||
},
|
||||
"TorrentTransfer": {
|
||||
"name": "自动转移做种",
|
||||
"description": "定期转移下载器中的做种任务到另一个下载器。",
|
||||
|
||||
1089
plugins/vcbanimemonitor/__init__.py
Normal file
1089
plugins/vcbanimemonitor/__init__.py
Normal file
File diff suppressed because it is too large
Load Diff
188
plugins/vcbanimemonitor/remeta.py
Normal file
188
plugins/vcbanimemonitor/remeta.py
Normal file
@@ -0,0 +1,188 @@
|
||||
import concurrent
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
import threading
|
||||
import roman
|
||||
from app.chain.media import MediaChain
|
||||
from app.chain.tmdb import TmdbChain
|
||||
from app.core.metainfo import MetaInfoPath
|
||||
from app.log import logger
|
||||
from app.schemas import MediaType
|
||||
|
||||
|
||||
class ReMeta():
|
||||
# 解析之后的标题:
|
||||
title: str = None
|
||||
# 识别出来的集数
|
||||
ep: int = None
|
||||
# 识别出来的季度
|
||||
season: int = None
|
||||
# 特殊季识别开关
|
||||
is_special = False
|
||||
# OVA/OAD识别开关
|
||||
ova_switch: bool = False
|
||||
# 高性能处理开关
|
||||
high_performance = False
|
||||
|
||||
season_patterns = [
|
||||
{"pattern": re.compile(r"S(\d+)$"), "group": 1},
|
||||
{"pattern": re.compile(r"(\d+)$"), "group": 1},
|
||||
{"pattern": re.compile(r"(\d+)(st|nd|rd|th)?\s*[Ss][Ee][Aa][Ss][Oo][Nn]"), "group": 1},
|
||||
{"pattern": re.compile(r"(.*) ?\s*[Ss][Ee][Aa][Ss][Oo][Nn] (\d+)"), "group": 2},
|
||||
{"pattern": re.compile(r"\s(II|III|IV|V|VI|VII|VIII|IX|X)$"), "group": "1"}
|
||||
]
|
||||
episode_patterns = [
|
||||
{"pattern": re.compile(r"\[(\d+)\((\d+)\)]"), "group": 2},
|
||||
{"pattern": re.compile(r"\[(\d+)]"), "group": 1},
|
||||
{"pattern": re.compile(r'\[(\d+)v\d+]'), "group": 1},
|
||||
|
||||
]
|
||||
_ova_patterns = [re.compile(r"\[.*?(OVA|OAD).*?]"),
|
||||
re.compile(r"\[\d+\.5]"),
|
||||
re.compile(r"\[00\]")]
|
||||
|
||||
final_season_patterns = [re.compile('final season', re.IGNORECASE),
|
||||
re.compile('The Final', re.IGNORECASE),
|
||||
re.compile(r'\sFinal')
|
||||
]
|
||||
# 自定义添加的季度正则表达式
|
||||
_custom_season_patterns = []
|
||||
|
||||
def __init__(self, ova_switch: bool = False,high_performance: bool = False):
|
||||
self.ova_switch = ova_switch
|
||||
self.high_performance = high_performance
|
||||
|
||||
def handel_file(self, file_path: Path):
|
||||
meta = MetaInfoPath(file_path)
|
||||
self.title = meta.title
|
||||
if 'VCB-Studio' not in meta.title:
|
||||
logger.warn("不属于VCB的作品,不处理!")
|
||||
return None
|
||||
if meta.title.count("[") != 4 and meta.title.count("]") != 4:
|
||||
# 可能是电影,电影只有三组[],因此去除所有[]后只剩下电影名
|
||||
logger.warn("不符合VCB-Studio的剧集命名规范,跳过剧集模块处理!交给默认处理逻辑")
|
||||
meta.title = re.sub(r'\[.*?\]', '', meta.title).strip()
|
||||
meta.en_name = meta.title
|
||||
return meta
|
||||
split_title: List[str] | None = self.split_season_ep(self.title)
|
||||
if split_title:
|
||||
self.handle_season_ep(split_title)
|
||||
if self.season is not None:
|
||||
meta.begin_season = self.season
|
||||
else:
|
||||
logger.warn("未识别出季度,默认处理逻辑返回第一季")
|
||||
if self.ep is not None:
|
||||
meta.begin_episode = self.ep
|
||||
else:
|
||||
logger.warn("未识别出集数,默认处理逻辑返回第一集")
|
||||
meta.title = self.title
|
||||
meta.en_name = self.title
|
||||
logger.info(f"识别出季度为{self.season},集数为{self.ep},标题为:{self.title}")
|
||||
|
||||
return meta
|
||||
|
||||
# 分离季度部分和集数部分
|
||||
def split_season_ep(self, pre_title: str):
|
||||
split_ep = re.findall(r"(\[.*?])", pre_title)[1]
|
||||
if not split_ep:
|
||||
logger.warn("未识别出集数位置信息,结束识别!")
|
||||
return None
|
||||
split_title = re.sub(r"\[.*?\]", "", pre_title).strip()
|
||||
logger.info(f"分离出包含季度的部分:{split_title} \n 分离出包含集数的部分: {split_ep}")
|
||||
return [split_title, split_ep]
|
||||
|
||||
def handle_season_ep(self, title: List[str]):
|
||||
if self.high_performance:
|
||||
with concurrent.futures.ProcessPoolExecutor(max_workers=2) as executor:
|
||||
title_season_result = executor.submit(self.handle_season, title[0])
|
||||
ep_result = executor.submit(self.re_ep, title[1],)
|
||||
try:
|
||||
title_season_result = title_season_result.result() # Blocks until the task is complete.
|
||||
ep_result = ep_result.result() # Blocks until the task is complete.
|
||||
except Exception as exc:
|
||||
print('Generated an exception: %s' % exc)
|
||||
else:
|
||||
title_season_result = self.handle_season(title[0])
|
||||
ep_result = self.re_ep(title[1])
|
||||
self.title = title_season_result["title"]
|
||||
is_ova = ep_result["is_ova"]
|
||||
if ep_result["ep"] is not None:
|
||||
self.ep = ep_result["ep"]
|
||||
if title_season_result["season"]:
|
||||
self.season = title_season_result["season"]
|
||||
if is_ova:
|
||||
self.season = 0
|
||||
self.is_special = True
|
||||
|
||||
# 处理季度
|
||||
def handle_season(self, pre_title: str) -> dict:
|
||||
title_season = {"title": pre_title, "season": 1}
|
||||
for season_pattern in self.season_patterns:
|
||||
pattern = season_pattern["pattern"]
|
||||
group = season_pattern["group"]
|
||||
match = pattern.search(pre_title)
|
||||
if match:
|
||||
if type(group) == str:
|
||||
title_season["season"] = int(roman.fromRoman(match.group(int(group))))
|
||||
title_season["title"] = re.sub(pattern, "", pre_title).strip()
|
||||
else:
|
||||
title_season["season"] = int(match.group(group))
|
||||
title_season["title"] = re.sub(pattern, "", pre_title).strip()
|
||||
return title_season
|
||||
for final_season_pattern in self.final_season_patterns:
|
||||
match = final_season_pattern.search(pre_title)
|
||||
if match:
|
||||
logger.info("识别出最终季度,开始处理!")
|
||||
title_season["title"] = re.sub(final_season_pattern, "", pre_title).strip()
|
||||
title_season["season"] = self.handle_final_season(title=pre_title)
|
||||
break
|
||||
return title_season
|
||||
|
||||
# 处理存在“Final”字样命名的季度
|
||||
def handle_final_season(self, title: str) -> int | None:
|
||||
medias = MediaChain().search(title=title)[1]
|
||||
if not medias:
|
||||
logger.warn("没有找到对应的媒体信息!")
|
||||
return
|
||||
# 根据类型进行过滤,只取类型是电视剧和动漫的media
|
||||
medias = [media for media in medias if media.type == MediaType.TV]
|
||||
if not medias:
|
||||
logger.warn("没有找到动漫或电视剧的媒体信息!")
|
||||
return
|
||||
media = sorted(medias, key=lambda x: x.popularity, reverse=True)[0]
|
||||
media_tmdb_id = media.tmdb_id
|
||||
seasons_info = TmdbChain().tmdb_seasons(tmdbid=media_tmdb_id)
|
||||
if seasons_info is None:
|
||||
logger.warn("无法获取最终季")
|
||||
else:
|
||||
logger.info(f"获取到最终季,季度为{len(seasons_info)}")
|
||||
return len(seasons_info)
|
||||
|
||||
def re_ep(self, ep_title: str,) -> dict:
|
||||
"""
|
||||
# 集数匹配处理模块
|
||||
:param ep_title: 从title解析出的集数,ep_title固定格式[集数]
|
||||
1.先判断是否存在OVA/OAD,形如:[OVA],[12(OVA)],[12.5]这种形式都是属于OVA/OAD,交给处理OVA模块处理
|
||||
2.集数通常有两种情况一种:[12]直接性,另一种:[12(24)],这一种应该去括号内的为集数
|
||||
:return: 集数(int)
|
||||
"""
|
||||
ep_ova = {"ep": None, "is_ova": False}
|
||||
for ova_pattern in self._ova_patterns:
|
||||
match = ova_pattern.search(ep_title)
|
||||
if match:
|
||||
ep_ova["is_ova"] = True
|
||||
ep_ova["ep"] = 1
|
||||
return ep_ova
|
||||
for ep_pattern in self.episode_patterns:
|
||||
pattern = ep_pattern["pattern"]
|
||||
group = ep_pattern["group"]
|
||||
match = pattern.search(ep_title)
|
||||
if match:
|
||||
ep_ova["ep"] = int(match.group(group))
|
||||
return ep_ova
|
||||
return ep_ova
|
||||
|
||||
|
||||
|
||||
|
||||
1
plugins/vcbanimemonitor/requirements.txt
Normal file
1
plugins/vcbanimemonitor/requirements.txt
Normal file
@@ -0,0 +1 @@
|
||||
roman~=4.1
|
||||
Reference in New Issue
Block a user