fix LibraryScraper
This commit is contained in:
parent
1125cfa6ee
commit
10a240e581
13
app/helper/nfo.py
Normal file
13
app/helper/nfo.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
class NfoReader:
|
||||||
|
def __init__(self, xml_file_path: Path):
|
||||||
|
self.xml_file_path = xml_file_path
|
||||||
|
self.tree = ET.parse(xml_file_path)
|
||||||
|
self.root = self.tree.getroot()
|
||||||
|
|
||||||
|
def get_element_value(self, element_path):
|
||||||
|
element = self.root.find(element_path)
|
||||||
|
return element.text if element is not None else None
|
@ -1,3 +1,4 @@
|
|||||||
|
from pathlib import Path
|
||||||
from threading import Event
|
from threading import Event
|
||||||
from typing import List, Tuple, Dict, Any
|
from typing import List, Tuple, Dict, Any
|
||||||
|
|
||||||
@ -5,8 +6,13 @@ from apscheduler.schedulers.background import BackgroundScheduler
|
|||||||
from apscheduler.triggers.cron import CronTrigger
|
from apscheduler.triggers.cron import CronTrigger
|
||||||
|
|
||||||
from app.core.config import settings
|
from app.core.config import settings
|
||||||
|
from app.core.context import MediaInfo
|
||||||
|
from app.core.metainfo import MetaInfo
|
||||||
|
from app.helper.nfo import NfoReader
|
||||||
from app.log import logger
|
from app.log import logger
|
||||||
from app.plugins import _PluginBase
|
from app.plugins import _PluginBase
|
||||||
|
from app.schemas import MediaType
|
||||||
|
from app.utils.system import SystemUtils
|
||||||
|
|
||||||
|
|
||||||
class LibraryScraper(_PluginBase):
|
class LibraryScraper(_PluginBase):
|
||||||
@ -38,8 +44,8 @@ class LibraryScraper(_PluginBase):
|
|||||||
# 限速开关
|
# 限速开关
|
||||||
_enabled = False
|
_enabled = False
|
||||||
_cron = None
|
_cron = None
|
||||||
_scraper_paths = None
|
_scraper_paths = ""
|
||||||
_exclude_paths = None
|
_exclude_paths = ""
|
||||||
# 退出事件
|
# 退出事件
|
||||||
_event = Event()
|
_event = Event()
|
||||||
|
|
||||||
@ -59,8 +65,12 @@ class LibraryScraper(_PluginBase):
|
|||||||
self._scheduler = BackgroundScheduler(timezone=settings.TZ)
|
self._scheduler = BackgroundScheduler(timezone=settings.TZ)
|
||||||
if self._cron:
|
if self._cron:
|
||||||
logger.info(f"媒体库刮削服务启动,周期:{self._cron}")
|
logger.info(f"媒体库刮削服务启动,周期:{self._cron}")
|
||||||
self._scheduler.add_job(self.__libraryscraper,
|
try:
|
||||||
CronTrigger.from_crontab(self._cron))
|
self._scheduler.add_job(self.__libraryscraper,
|
||||||
|
CronTrigger.from_crontab(self._cron))
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"媒体库刮削服务启动失败,原因:{e}")
|
||||||
|
self.systemmessage.put(f"媒体库刮削服务启动失败,原因:{e}")
|
||||||
else:
|
else:
|
||||||
logger.info(f"媒体库刮削服务启动,周期:每7天")
|
logger.info(f"媒体库刮削服务启动,周期:每7天")
|
||||||
self._scheduler.add_job(self.__libraryscraper,
|
self._scheduler.add_job(self.__libraryscraper,
|
||||||
@ -81,7 +91,105 @@ class LibraryScraper(_PluginBase):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def get_form(self) -> Tuple[List[dict], Dict[str, Any]]:
|
def get_form(self) -> Tuple[List[dict], Dict[str, Any]]:
|
||||||
pass
|
return [
|
||||||
|
{
|
||||||
|
'component': 'VForm',
|
||||||
|
'content': [
|
||||||
|
{
|
||||||
|
'component': 'VRow',
|
||||||
|
'content': [
|
||||||
|
{
|
||||||
|
'component': 'VCol',
|
||||||
|
'props': {
|
||||||
|
'cols': 12,
|
||||||
|
'md': 6
|
||||||
|
},
|
||||||
|
'content': [
|
||||||
|
{
|
||||||
|
'component': 'VSwitch',
|
||||||
|
'props': {
|
||||||
|
'model': 'enabled',
|
||||||
|
'label': '启用插件',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'component': 'VRow',
|
||||||
|
'content': [
|
||||||
|
{
|
||||||
|
'component': 'VCol',
|
||||||
|
'props': {
|
||||||
|
'cols': 12,
|
||||||
|
'md': 6
|
||||||
|
},
|
||||||
|
'content': [
|
||||||
|
{
|
||||||
|
'component': 'VTextField',
|
||||||
|
'props': {
|
||||||
|
'model': 'cron',
|
||||||
|
'label': '执行周期',
|
||||||
|
'placeholder': '5位cron表达式,留空自动'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'component': 'VRow',
|
||||||
|
'content': [
|
||||||
|
{
|
||||||
|
'component': 'VCol',
|
||||||
|
'props': {
|
||||||
|
'cols': 12
|
||||||
|
},
|
||||||
|
'content': [
|
||||||
|
{
|
||||||
|
'component': 'VTextarea',
|
||||||
|
'props': {
|
||||||
|
'model': 'scraper_paths',
|
||||||
|
'label': '削刮路径',
|
||||||
|
'rows': 5,
|
||||||
|
'placeholder': '每一行一个目录'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'component': 'VRow',
|
||||||
|
'content': [
|
||||||
|
{
|
||||||
|
'component': 'VCol',
|
||||||
|
'props': {
|
||||||
|
'cols': 12
|
||||||
|
},
|
||||||
|
'content': [
|
||||||
|
{
|
||||||
|
'component': 'VTextarea',
|
||||||
|
'props': {
|
||||||
|
'model': 'exclude_paths',
|
||||||
|
'label': '排除路径',
|
||||||
|
'rows': 2,
|
||||||
|
'placeholder': '每一行一个目录'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
], {
|
||||||
|
"enabled": False,
|
||||||
|
"cron": "0 0 */7 * *",
|
||||||
|
"scraper_paths": "",
|
||||||
|
"err_hosts": ""
|
||||||
|
}
|
||||||
|
|
||||||
def get_page(self) -> List[dict]:
|
def get_page(self) -> List[dict]:
|
||||||
pass
|
pass
|
||||||
@ -90,16 +198,95 @@ class LibraryScraper(_PluginBase):
|
|||||||
"""
|
"""
|
||||||
开始刮削媒体库
|
开始刮削媒体库
|
||||||
"""
|
"""
|
||||||
|
if not self._scraper_paths:
|
||||||
|
return
|
||||||
# 已选择的目录
|
# 已选择的目录
|
||||||
logger.info(f"开始刮削媒体库:{self._scraper_path} ...")
|
paths = self._scraper_paths.split("\n")
|
||||||
for path in self._scraper_path:
|
for path in paths:
|
||||||
if not path:
|
if not path:
|
||||||
continue
|
continue
|
||||||
|
if not Path(path).exists():
|
||||||
|
logger.warning(f"媒体库刮削路径不存在:{path}")
|
||||||
|
continue
|
||||||
|
logger.info(f"开始刮削媒体库:{path} ...")
|
||||||
if self._event.is_set():
|
if self._event.is_set():
|
||||||
logger.info(f"媒体库刮削服务停止")
|
logger.info(f"媒体库刮削服务停止")
|
||||||
return
|
return
|
||||||
# TODO 刮削目录
|
# 刮削目录
|
||||||
logger.info(f"媒体库刮削完成")
|
self.__scrape_dir(Path(path))
|
||||||
|
logger.info(f"媒体库刮削完成")
|
||||||
|
|
||||||
|
def __scrape_dir(self, path: Path):
|
||||||
|
"""
|
||||||
|
削刮一个目录
|
||||||
|
"""
|
||||||
|
exclude_paths = self._exclude_paths.split("\n")
|
||||||
|
# 查找目录下所有的文件
|
||||||
|
files = SystemUtils.list_files_with_extensions(path, settings.RMT_MEDIAEXT)
|
||||||
|
for file in files:
|
||||||
|
# 排除目录
|
||||||
|
exclude_flag = False
|
||||||
|
for exclude_path in exclude_paths:
|
||||||
|
if file.is_relative_to(Path(exclude_path)):
|
||||||
|
exclude_flag = True
|
||||||
|
break
|
||||||
|
if exclude_flag:
|
||||||
|
logger.debug(f"{file} 在排除目录中,跳过 ...")
|
||||||
|
continue
|
||||||
|
# 识别媒体文件
|
||||||
|
meta_info = MetaInfo(file.name)
|
||||||
|
# 优先读取本地nfo文件
|
||||||
|
tmdbid = None
|
||||||
|
if meta_info.type == MediaType.MOVIE:
|
||||||
|
# 电影
|
||||||
|
movie_nfo = file.parent / "movie.nfo"
|
||||||
|
if movie_nfo.exists():
|
||||||
|
tmdbid = self.__get_tmdbid_from_nfo(movie_nfo)
|
||||||
|
file_nfo = file.with_suffix(".nfo")
|
||||||
|
if not tmdbid and file_nfo.exists():
|
||||||
|
tmdbid = self.__get_tmdbid_from_nfo(file_nfo)
|
||||||
|
else:
|
||||||
|
# 电视剧
|
||||||
|
tv_nfo = file.parent.parent / "tvshow.nfo"
|
||||||
|
if tv_nfo.exists():
|
||||||
|
tmdbid = self.__get_tmdbid_from_nfo(tv_nfo)
|
||||||
|
if tmdbid:
|
||||||
|
logger.info(f"读取到本地nfo文件的tmdbid:{tmdbid}")
|
||||||
|
# 识别媒体信息
|
||||||
|
mediainfo: MediaInfo = self.chain.recognize_media(tmdbid=tmdbid, mtype=meta_info.type)
|
||||||
|
else:
|
||||||
|
# 识别媒体信息
|
||||||
|
mediainfo: MediaInfo = self.chain.recognize_media(meta=meta_info)
|
||||||
|
if not mediainfo:
|
||||||
|
logger.warn(f"未识别到媒体信息:{file}")
|
||||||
|
continue
|
||||||
|
# 开始刮削
|
||||||
|
self.chain.scrape_metadata(path=path, mediainfo=mediainfo)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def __get_tmdbid_from_nfo(file_path: Path):
|
||||||
|
"""
|
||||||
|
从nfo文件中获取信息
|
||||||
|
:param file_path:
|
||||||
|
:return: tmdbid
|
||||||
|
"""
|
||||||
|
if not file_path:
|
||||||
|
return None
|
||||||
|
xpaths = [
|
||||||
|
"uniqueid[@type='Tmdb']",
|
||||||
|
"uniqueid[@type='tmdb']",
|
||||||
|
"uniqueid[@type='TMDB']",
|
||||||
|
"tmdbid"
|
||||||
|
]
|
||||||
|
reader = NfoReader(file_path)
|
||||||
|
for xpath in xpaths:
|
||||||
|
try:
|
||||||
|
tmdbid = reader.get_element_value(xpath)
|
||||||
|
if tmdbid:
|
||||||
|
return tmdbid
|
||||||
|
except Exception as err:
|
||||||
|
print(str(err))
|
||||||
|
return None
|
||||||
|
|
||||||
def stop_service(self):
|
def stop_service(self):
|
||||||
"""
|
"""
|
||||||
|
Loading…
x
Reference in New Issue
Block a user