Update:VCB辅助整理插件提高识别率&Fix

This commit is contained in:
Pixel-LH
2024-09-02 00:00:45 +08:00
parent f72f92ab37
commit bd9d1c413f
3 changed files with 83 additions and 94 deletions

View File

@@ -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": "修复偶尔安装失败问题"

View File

@@ -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": "",
}

View File

@@ -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"))