mirror of
https://github.com/d0zingcat/MoviePilot-Plugins.git
synced 2026-05-16 23:16:48 +00:00
Update:VCB辅助整理插件提高识别率&Fix
This commit is contained in:
@@ -317,13 +317,14 @@
|
||||
},
|
||||
"VCBAnimeMonitor": {
|
||||
"name": "整理VCB动漫压制组作品",
|
||||
"description": "提高部分VCB-Studio作品的识别准确率,将VCB-Studio的作品统一转移到指定目录同时进行刮削整理",
|
||||
"description": "一款辅助整理&提高识别VCB-Stuido动漫压制组作品的插件",
|
||||
"labels": "文件整理,识别",
|
||||
"version": "1.8",
|
||||
"icon": "vcbmonitor.png",
|
||||
"author": "pixel@qingwa",
|
||||
"level": 2,
|
||||
"history": {
|
||||
"v1.8.2": "提高识别率",
|
||||
"v1.8.1": "重构插件,测试版",
|
||||
"v1.8": "增加了元数据刮削开关,升级后需要手动打开,否则默认不刮削",
|
||||
"v1.7.1": "修复偶尔安装失败问题"
|
||||
|
||||
@@ -5,6 +5,7 @@ import threading
|
||||
import time
|
||||
import traceback
|
||||
from pathlib import Path
|
||||
from time import sleep
|
||||
from typing import List, Tuple, Dict, Any, Optional
|
||||
import pytz
|
||||
import qbittorrentapi
|
||||
@@ -70,11 +71,11 @@ class VCBAnimeMonitor(_PluginBase):
|
||||
# 插件名称
|
||||
plugin_name = "整理VCB动漫压制组作品"
|
||||
# 插件描述
|
||||
plugin_desc = "提高部分VCB-Studio作品的识别准确率,将VCB-Studio的作品统一转移到指定目录同时进行刮削整理"
|
||||
plugin_desc = "一款辅助整理&提高识别VCB-Stuido动漫压制组作品的插件"
|
||||
# 插件图标
|
||||
plugin_icon = "vcbmonitor.png"
|
||||
# 插件版本
|
||||
plugin_version = "1.8.1"
|
||||
plugin_version = "1.8.2"
|
||||
# 插件作者
|
||||
plugin_author = "pixel@qingwa"
|
||||
# 作者主页
|
||||
@@ -88,7 +89,6 @@ class VCBAnimeMonitor(_PluginBase):
|
||||
|
||||
# 私有属性
|
||||
_switch_ova = False
|
||||
_high_mode = False
|
||||
_torrents_path = None
|
||||
new_save_path = None
|
||||
qb = None
|
||||
@@ -142,7 +142,6 @@ class VCBAnimeMonitor(_PluginBase):
|
||||
self._size = config.get("size") or 0
|
||||
self._scrape = config.get("scrape")
|
||||
self._switch_ova = config.get("ova")
|
||||
self._high_mode = config.get("high_mode")
|
||||
self._torrents_path = config.get("torrents_path") or ""
|
||||
|
||||
# 停止现有任务
|
||||
@@ -161,13 +160,16 @@ class VCBAnimeMonitor(_PluginBase):
|
||||
return
|
||||
|
||||
# 启用种子目录监控
|
||||
if self._torrents_path is not None and Path(self._torrents_path).exists() and self._enabled:
|
||||
if self._torrents_path and Path(self._torrents_path).exists() and self._enabled:
|
||||
# 只取第一个目录作为新的保存
|
||||
first_path = monitor_dirs[0]
|
||||
if SystemUtils.is_windows():
|
||||
self.new_save_path = first_path.split(':')[0] + ":" + first_path.split(':')[1]
|
||||
else:
|
||||
self.new_save_path = first_path.split(':')[0]
|
||||
try:
|
||||
first_path = monitor_dirs[0]
|
||||
if SystemUtils.is_windows():
|
||||
self.new_save_path = first_path.split(':')[0] + ":" + first_path.split(':')[1]
|
||||
else:
|
||||
self.new_save_path = first_path.split(':')[0]
|
||||
except Exception:
|
||||
logger.error(f"目录保存失败,请检查输入目录是否合法")
|
||||
# print(self.new_save_path)
|
||||
try:
|
||||
observer = Observer()
|
||||
@@ -288,7 +290,6 @@ class VCBAnimeMonitor(_PluginBase):
|
||||
"size": self._size,
|
||||
"scrape": self._scrape,
|
||||
"ova": self._switch_ova,
|
||||
"high_mode": self._high_mode,
|
||||
"torrents_path": self._torrents_path
|
||||
})
|
||||
|
||||
@@ -388,7 +389,7 @@ class VCBAnimeMonitor(_PluginBase):
|
||||
logger.warn("不属于VCB的作品,不处理!")
|
||||
return
|
||||
|
||||
remeta = ReMeta(ova_switch=self._switch_ova, )
|
||||
remeta = ReMeta(ova_switch=self._switch_ova,)
|
||||
file_meta = remeta.handel_file(file_path=file_path)
|
||||
if file_meta:
|
||||
if not file_meta.name:
|
||||
@@ -397,15 +398,6 @@ class VCBAnimeMonitor(_PluginBase):
|
||||
if remeta.is_ova and not self._switch_ova:
|
||||
logger.warn(f"{file_path.name} 为OVA资源,未开启OVA开关,不处理")
|
||||
return
|
||||
# if remeta.is_ova and self._switch_ova:
|
||||
# logger.info(f"{file_path.name} 为OVA资源,开始处理")
|
||||
# if self.get_data(key=f"OVA_{file_meta.title}") is not None:
|
||||
# ova_history_ep = int(self.get_data(key=f"OVA_{file_meta.title}")) + 1
|
||||
# file_meta.begin_episode = ova_history_ep
|
||||
# self.save_data(key=f"OVA_{file_meta.title}", value=ova_history_ep)
|
||||
# else:
|
||||
# file_meta.begin_episode = 1
|
||||
# self.save_data(key=f"OVA_{file_meta.title}", value=1)
|
||||
if remeta.is_ova and self._switch_ova:
|
||||
logger.info(f"{file_path.name} 为OVA资源,开始历史记录处理")
|
||||
ova_history_ep_list = self.plugindata.get(file_meta.title, [])
|
||||
@@ -833,22 +825,6 @@ class VCBAnimeMonitor(_PluginBase):
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
'cols': 12,
|
||||
'md': 4
|
||||
},
|
||||
'content': [
|
||||
{
|
||||
'component': 'VSwitch',
|
||||
'props': {
|
||||
'model': 'high_mode',
|
||||
'label': '高性能处理模式',
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
@@ -1003,7 +979,7 @@ class VCBAnimeMonitor(_PluginBase):
|
||||
'props': {
|
||||
'model': 'monitor_dirs',
|
||||
'label': '监控目录',
|
||||
'rows': 5,
|
||||
'rows': 4,
|
||||
'placeholder': '每一行一个目录,支持以下几种配置方式,转移方式支持 move、copy、link、softlink、rclone_copy、rclone_move:\n'
|
||||
'监控目录\n'
|
||||
'监控目录#转移方式\n'
|
||||
@@ -1051,8 +1027,10 @@ class VCBAnimeMonitor(_PluginBase):
|
||||
'props': {
|
||||
'type': 'info',
|
||||
'variant': 'tonal',
|
||||
'text': '核心用法与目录同步插件相同,不同点在于只识别处理VCB-Studio资源,\n'
|
||||
'不处理SPs目录下的文件,OVA/OAD集数根据入库顺序累加命名,不保证与TMDB集数匹配'
|
||||
'text': '核心用法与目录同步插件相同,不同点在于只识别处理VCB-Studio资源。'
|
||||
'默认不处理SPs、CDs、SCans目录下的文件,OVA/OAD集数暂时根据入库顺序累加命名,'
|
||||
'因此不保证与TMDB集数匹配。部分季度以罗马音音译为名的作品暂时无法识别出准确季度。'
|
||||
'有想法,有问题欢迎点击插件作者主页提issue!'
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -1073,9 +1051,9 @@ class VCBAnimeMonitor(_PluginBase):
|
||||
'props': {
|
||||
'type': 'info',
|
||||
'variant': 'tonal',
|
||||
'text': '最佳使用方式:监控目录单独设置一个作为保存VCB-Studio资源的目录,\n'
|
||||
'填入监控种子目录,开启后会将正在QB(仅支持QB)下载器内的VCB-Studio资源转移到监控目录实现自动整理('
|
||||
'仅支持第一个监控目录),\n'
|
||||
'text': '最佳使用方式:监控目录单独设置一个作为保存VCB-Studio资源的目录,'
|
||||
'填入监控种子目录,开启后会将正在QB(仅支持QB)下载器内正在下载的VCB-Studio资源转移到监控目录实现自动整理('
|
||||
'仅支持第一个监控目录),'
|
||||
'监控种子目录为空则不转移文件'
|
||||
}
|
||||
}
|
||||
@@ -1097,7 +1075,6 @@ class VCBAnimeMonitor(_PluginBase):
|
||||
"cron": "",
|
||||
"size": 0,
|
||||
"ova": False,
|
||||
"high_mode": False,
|
||||
"torrents_path": "",
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
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
|
||||
@@ -33,6 +34,11 @@ final_season_patterns = [
|
||||
re.compile(r'\sFinal')
|
||||
]
|
||||
|
||||
movie_patterns = [
|
||||
re.compile("Movie", re.IGNORECASE),
|
||||
re.compile("the Movie", re.IGNORECASE),
|
||||
]
|
||||
|
||||
|
||||
@dataclass
|
||||
class VCBMetaBase:
|
||||
@@ -52,6 +58,8 @@ class VCBMetaBase:
|
||||
ep: int = None
|
||||
# 是否是OVA/OAD
|
||||
is_ova: bool = False
|
||||
# TMDB ID
|
||||
tmdb_id: int = None
|
||||
|
||||
|
||||
blocked_words = ["vcb-studio", "360p", "480p", "720p", "1080p", "2160p", "hdr", "x265", "x264", "aac", "flac"]
|
||||
@@ -83,7 +91,8 @@ class ReMeta:
|
||||
self.vcb_meta.original_title = file_name
|
||||
if not self.is_tv(file_name):
|
||||
logger.warn(
|
||||
"不符合VCB-Studio的剧集命名规范,归类为电影,跳过剧集模块处理。注意:年份较为久远的作品可能会判断错误")
|
||||
"不符合VCB-Studio的剧集命名规范,归类为电影,跳过剧集模块处理。注意:年份较为久远的作品可能在此会判断错误")
|
||||
self.parse_movie()
|
||||
else:
|
||||
self.tv_mode()
|
||||
self.is_ova = self.vcb_meta.is_ova
|
||||
@@ -95,6 +104,8 @@ class ReMeta:
|
||||
meta.begin_episode = self.vcb_meta.ep
|
||||
if self.vcb_meta.type == "Movie":
|
||||
meta.type = MediaType.MOVIE
|
||||
else:
|
||||
meta.type = MediaType.TV
|
||||
return meta
|
||||
|
||||
def split_season_ep(self):
|
||||
@@ -179,7 +190,8 @@ class ReMeta:
|
||||
self.vcb_meta.is_ova = True
|
||||
# 直接获取数字
|
||||
self.vcb_meta.ep = int(re.search(r"\d+", ep).group()) or 1
|
||||
logger.info(f"识别出集数为{self.vcb_meta.ep}")
|
||||
logger.info(f"OVA模式下识别出集数为{self.vcb_meta.ep}")
|
||||
self.vcb_meta.season = 0
|
||||
return
|
||||
|
||||
def culling_blocked_words(self):
|
||||
@@ -192,42 +204,53 @@ class ReMeta:
|
||||
|
||||
def handle_final_season(self):
|
||||
|
||||
meta, medias = MediaChain().search(title=self.vcb_meta.title)
|
||||
_, medias = MediaChain().search(title=self.vcb_meta.title)
|
||||
if not medias:
|
||||
logger.warning("匹配到最终季时无法找到对应的媒体信息!季度返回默认值:1")
|
||||
self.vcb_meta.season = 1
|
||||
return
|
||||
|
||||
max_season_number = 1
|
||||
# 当没有季度参考时用评分来决定
|
||||
vote_average = 0
|
||||
season_info = False
|
||||
for media in medias:
|
||||
if media.type != MediaType.TV:
|
||||
logger.info(f"搜索到的: {media.title}, 媒体类型为 {media.type},跳过")
|
||||
continue
|
||||
if media.season_info:
|
||||
season_info = True
|
||||
last_season_number = int(media.season_info[-1].get("season_number", 1))
|
||||
if last_season_number > max_season_number:
|
||||
max_season_number = last_season_number
|
||||
else:
|
||||
logger.info(f"媒体: {media.title} 没有季信息,跳过")
|
||||
if not season_info:
|
||||
# 备用方案
|
||||
for media in medias:
|
||||
if media.seasons:
|
||||
seasons: dict
|
||||
# 获取最大的键,即最大季度
|
||||
last_season_number = max(media.seasons.keys())
|
||||
if last_season_number > max_season_number:
|
||||
max_season_number = last_season_number
|
||||
logger.info(f"获取到最终季,季度为 {max_season_number},标题为 {media.title},年份为 {media.year}")
|
||||
else:
|
||||
logger.info(f"媒体: {media.title} 没有季信息,跳过")
|
||||
filter_medias = [media for media in medias if media.type == MediaType.TV]
|
||||
if not filter_medias:
|
||||
logger.warning("匹配到最终季时无法找到对应的媒体信息!季度返回默认值:1")
|
||||
self.vcb_meta.season = 1
|
||||
return
|
||||
medias = [media for media in filter_medias if media.popularity or media.vote_average]
|
||||
if not medias:
|
||||
logger.warning("匹配到最终季时无法找到对应的媒体信息!季度返回默认值:1")
|
||||
self.vcb_meta.season = 1
|
||||
return
|
||||
# 获取欢迎度最高或者评分最高的媒体
|
||||
medias_sorted = sorted(medias, key=lambda x: x.popularity or x.vote_average, reverse=True)[0]
|
||||
self.vcb_meta.tmdb_id = medias_sorted.tmdb_id
|
||||
if medias_sorted.tmdb_id:
|
||||
seasons_info = TmdbChain().tmdb_seasons(tmdbid=medias_sorted.tmdb_id)
|
||||
if seasons_info:
|
||||
self.vcb_meta.season = len(seasons_info)
|
||||
logger.info(f"获取到最终季度,季度为{self.vcb_meta.season}")
|
||||
return
|
||||
logger.warning("无法获取到最终季度信息,季度返回默认值:1")
|
||||
self.vcb_meta.season = 1
|
||||
|
||||
|
||||
|
||||
def parse_movie(self):
|
||||
logger.info("开始尝试剧场版模式解析")
|
||||
for pattern in movie_patterns:
|
||||
if pattern.search(self.vcb_meta.title):
|
||||
logger.info("命中剧场版匹配规则,加上剧场版标识辅助识别")
|
||||
self.vcb_meta.type = "Movie"
|
||||
self.vcb_meta.title = pattern.sub("", self.vcb_meta.title).strip()
|
||||
self.vcb_meta.title = self.vcb_meta.title
|
||||
return
|
||||
|
||||
def find_ova_episode(self):
|
||||
"""
|
||||
搜索OVA的集数
|
||||
TODO:模糊匹配OVA的集数
|
||||
"""
|
||||
pass
|
||||
|
||||
self.vcb_meta.season = max_season_number
|
||||
logger.info(f"获取到最终季,季度为 {self.vcb_meta.season}")
|
||||
|
||||
@staticmethod
|
||||
def roman_to_int(s) -> int:
|
||||
@@ -250,21 +273,9 @@ class ReMeta:
|
||||
return total
|
||||
|
||||
|
||||
def test(title: str):
|
||||
# 示例文件名
|
||||
pre_title = title
|
||||
|
||||
# 提取方括号内的内容,不包括方括号
|
||||
content = re.findall(r'\[(.*?)\]', pre_title)
|
||||
|
||||
print(content)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# title = "[BeanSub&VCB-Studio] Jujutsu Kaisen [26][Ma10p_1080p][x265_flac].mkv "
|
||||
# test(title)
|
||||
|
||||
ReMeta(
|
||||
ova_switch=True,
|
||||
).handel_file(Path(
|
||||
r"[Nekomoe kissaten&VCB-Studio] Fruits Basket The Final [08][Ma10p_1080p][x265_flac].mkv"))
|
||||
# if __name__ == '__main__':
|
||||
# ReMeta(
|
||||
# ova_switch=True,
|
||||
# ).handel_file(Path(
|
||||
# r"[Airota&Nekomoe kissaten&VCB-Studio] Yuru Camp [Heya Camp EP00][Ma10p_1080p][x265_flac].mkv"))
|
||||
|
||||
Reference in New Issue
Block a user