Merge pull request #489 from thsrite/main
fix 目录监控已处理逻辑 && feat 新增已入库媒体是否跟随TMDB信息变化开关,关闭则延用媒体库名称
This commit is contained in:
@ -65,6 +65,7 @@ docker pull jxxghp/moviepilot:latest
|
|||||||
- **DOWNLOAD_SUBTITLE:** 下载站点字幕,`true`/`false`,默认`true`
|
- **DOWNLOAD_SUBTITLE:** 下载站点字幕,`true`/`false`,默认`true`
|
||||||
- **REFRESH_MEDIASERVER:** 入库刷新媒体库,`true`/`false`,默认`true`
|
- **REFRESH_MEDIASERVER:** 入库刷新媒体库,`true`/`false`,默认`true`
|
||||||
- **SCRAP_METADATA:** 刮削入库的媒体文件,`true`/`false`,默认`true`
|
- **SCRAP_METADATA:** 刮削入库的媒体文件,`true`/`false`,默认`true`
|
||||||
|
- **SCRAP_FOLLOW_TMDB:** 新增已入库媒体是否跟随TMDB信息变化,`true`/`false`,默认`true`
|
||||||
- **TORRENT_TAG:** 种子标签,默认为`MOVIEPILOT`,设置后只有MoviePilot添加的下载才会处理,留空所有下载器中的任务均会处理
|
- **TORRENT_TAG:** 种子标签,默认为`MOVIEPILOT`,设置后只有MoviePilot添加的下载才会处理,留空所有下载器中的任务均会处理
|
||||||
- **LIBRARY_PATH:** 媒体库目录,多个目录使用`,`分隔
|
- **LIBRARY_PATH:** 媒体库目录,多个目录使用`,`分隔
|
||||||
- **LIBRARY_MOVIE_NAME:** 电影媒体库目录名,默认`电影`
|
- **LIBRARY_MOVIE_NAME:** 电影媒体库目录名,默认`电影`
|
||||||
|
@ -242,7 +242,12 @@ class TransferChain(ChainBase):
|
|||||||
f"回复:```\n/redo {his.id} [tmdbid]|[类型]\n``` 手动识别转移。"
|
f"回复:```\n/redo {his.id} [tmdbid]|[类型]\n``` 手动识别转移。"
|
||||||
))
|
))
|
||||||
continue
|
continue
|
||||||
|
# 如果未开启新增已入库媒体是否跟随TMDB信息变化则根据tmdbid查询之前的title
|
||||||
|
if not settings.SCRAP_FOLLOW_TMDB:
|
||||||
|
transfer_historys = self.transferhis.get_by(tmdbid=file_mediainfo.tmdb_id,
|
||||||
|
type=file_mediainfo.type.value)
|
||||||
|
if transfer_historys:
|
||||||
|
file_mediainfo.title = transfer_historys[0].title
|
||||||
logger.info(f"{file_path.name} 识别为:{file_mediainfo.type.value} {file_mediainfo.title_year}")
|
logger.info(f"{file_path.name} 识别为:{file_mediainfo.type.value} {file_mediainfo.title_year}")
|
||||||
|
|
||||||
# 电视剧没有集无法转移
|
# 电视剧没有集无法转移
|
||||||
|
@ -39,6 +39,8 @@ class Settings(BaseSettings):
|
|||||||
SEARCH_SOURCE: str = "themoviedb"
|
SEARCH_SOURCE: str = "themoviedb"
|
||||||
# 刮削入库的媒体文件
|
# 刮削入库的媒体文件
|
||||||
SCRAP_METADATA: bool = True
|
SCRAP_METADATA: bool = True
|
||||||
|
# 新增已入库媒体是否跟随TMDB信息变化
|
||||||
|
SCRAP_FOLLOW_TMDB: bool = True
|
||||||
# 刮削来源
|
# 刮削来源
|
||||||
SCRAP_SOURCE: str = "themoviedb"
|
SCRAP_SOURCE: str = "themoviedb"
|
||||||
# TMDB图片地址
|
# TMDB图片地址
|
||||||
|
@ -85,11 +85,14 @@ class TransferHistory(Base):
|
|||||||
return db.query(func.count(TransferHistory.id)).filter(TransferHistory.title.like(f'%{title}%')).first()[0]
|
return db.query(func.count(TransferHistory.id)).filter(TransferHistory.title.like(f'%{title}%')).first()[0]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def list_by(db: Session, title: str = None, year: int = None, season: str = None,
|
def list_by(db: Session, type: str = None, title: str = None, year: int = None, season: str = None,
|
||||||
episode: str = None, tmdbid: int = None):
|
episode: str = None, tmdbid: int = None):
|
||||||
"""
|
"""
|
||||||
据tmdbid、season、season_episode查询转移记录
|
据tmdbid、season、season_episode查询转移记录
|
||||||
"""
|
"""
|
||||||
|
if tmdbid and type:
|
||||||
|
return db.query(TransferHistory).filter(TransferHistory.tmdbid == tmdbid,
|
||||||
|
TransferHistory.type == type).all()
|
||||||
if tmdbid and not season and not episode:
|
if tmdbid and not season and not episode:
|
||||||
return db.query(TransferHistory).filter(TransferHistory.tmdbid == tmdbid).all()
|
return db.query(TransferHistory).filter(TransferHistory.tmdbid == tmdbid).all()
|
||||||
if tmdbid and season and not episode:
|
if tmdbid and season and not episode:
|
||||||
|
@ -51,12 +51,13 @@ class TransferHistoryOper(DbOper):
|
|||||||
"""
|
"""
|
||||||
return TransferHistory.statistic(self._db, days)
|
return TransferHistory.statistic(self._db, days)
|
||||||
|
|
||||||
def get_by(self, title: str = None, year: str = None,
|
def get_by(self, title: str = None, year: str = None, type: str = None,
|
||||||
season: str = None, episode: str = None, tmdbid: int = None) -> List[TransferHistory]:
|
season: str = None, episode: str = None, tmdbid: int = None) -> List[TransferHistory]:
|
||||||
"""
|
"""
|
||||||
按类型、标题、年份、季集查询转移记录
|
按类型、标题、年份、季集查询转移记录
|
||||||
"""
|
"""
|
||||||
return TransferHistory.list_by(db=self._db,
|
return TransferHistory.list_by(db=self._db,
|
||||||
|
type=type,
|
||||||
title=title,
|
title=title,
|
||||||
year=year,
|
year=year,
|
||||||
season=season,
|
season=season,
|
||||||
|
@ -69,9 +69,6 @@ class DirMonitor(_PluginBase):
|
|||||||
# 可使用的用户级别
|
# 可使用的用户级别
|
||||||
auth_level = 1
|
auth_level = 1
|
||||||
|
|
||||||
# 已处理的文件清单
|
|
||||||
_synced_files = []
|
|
||||||
|
|
||||||
# 私有属性
|
# 私有属性
|
||||||
_scheduler = None
|
_scheduler = None
|
||||||
transferhis = None
|
transferhis = None
|
||||||
@ -193,9 +190,8 @@ class DirMonitor(_PluginBase):
|
|||||||
|
|
||||||
# 全程加锁
|
# 全程加锁
|
||||||
with lock:
|
with lock:
|
||||||
if event_path not in self._synced_files:
|
transfer_history = self.transferhis.get_by_src(event_path)
|
||||||
self._synced_files.append(event_path)
|
if transfer_history:
|
||||||
else:
|
|
||||||
logger.debug("文件已处理过:%s" % event_path)
|
logger.debug("文件已处理过:%s" % event_path)
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -264,6 +260,13 @@ class DirMonitor(_PluginBase):
|
|||||||
meta=file_meta
|
meta=file_meta
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# 如果未开启新增已入库媒体是否跟随TMDB信息变化则根据tmdbid查询之前的title
|
||||||
|
if not settings.SCRAP_FOLLOW_TMDB:
|
||||||
|
transfer_historys = self.transferhis.get_by(tmdbid=mediainfo.tmdb_id,
|
||||||
|
type=mediainfo.type.value)
|
||||||
|
if transfer_historys:
|
||||||
|
mediainfo.title = transfer_historys[0].title
|
||||||
logger.info(f"{file_path.name} 识别为:{mediainfo.type.value} {mediainfo.title_year}")
|
logger.info(f"{file_path.name} 识别为:{mediainfo.type.value} {mediainfo.title_year}")
|
||||||
|
|
||||||
# 更新媒体图片
|
# 更新媒体图片
|
||||||
@ -482,149 +485,149 @@ class DirMonitor(_PluginBase):
|
|||||||
|
|
||||||
def get_form(self) -> Tuple[List[dict], Dict[str, Any]]:
|
def get_form(self) -> Tuple[List[dict], Dict[str, Any]]:
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
'component': 'VForm',
|
'component': 'VForm',
|
||||||
'content': [
|
'content': [
|
||||||
{
|
{
|
||||||
'component': 'VRow',
|
'component': 'VRow',
|
||||||
'content': [
|
'content': [
|
||||||
{
|
{
|
||||||
'component': 'VCol',
|
'component': 'VCol',
|
||||||
'props': {
|
'props': {
|
||||||
'cols': 12,
|
'cols': 12,
|
||||||
'md': 6
|
'md': 6
|
||||||
},
|
},
|
||||||
'content': [
|
'content': [
|
||||||
{
|
{
|
||||||
'component': 'VSwitch',
|
'component': 'VSwitch',
|
||||||
'props': {
|
'props': {
|
||||||
'model': 'enabled',
|
'model': 'enabled',
|
||||||
'label': '启用插件',
|
'label': '启用插件',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'component': 'VCol',
|
'component': 'VCol',
|
||||||
'props': {
|
'props': {
|
||||||
'cols': 12,
|
'cols': 12,
|
||||||
'md': 6
|
'md': 6
|
||||||
},
|
},
|
||||||
'content': [
|
'content': [
|
||||||
{
|
{
|
||||||
'component': 'VSwitch',
|
'component': 'VSwitch',
|
||||||
'props': {
|
'props': {
|
||||||
'model': 'notify',
|
'model': 'notify',
|
||||||
'label': '发送通知',
|
'label': '发送通知',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'component': 'VRow',
|
'component': 'VRow',
|
||||||
'content': [
|
'content': [
|
||||||
{
|
{
|
||||||
'component': 'VCol',
|
'component': 'VCol',
|
||||||
'props': {
|
'props': {
|
||||||
'cols': 12,
|
'cols': 12,
|
||||||
'md': 6
|
'md': 6
|
||||||
},
|
},
|
||||||
'content': [
|
'content': [
|
||||||
{
|
{
|
||||||
'component': 'VSelect',
|
'component': 'VSelect',
|
||||||
'props': {
|
'props': {
|
||||||
'model': 'mode',
|
'model': 'mode',
|
||||||
'label': '监控模式',
|
'label': '监控模式',
|
||||||
'items': [
|
'items': [
|
||||||
{'title': '兼容模式', 'value': 'compatibility'},
|
{'title': '兼容模式', 'value': 'compatibility'},
|
||||||
{'title': '性能模式', 'value': 'fast'}
|
{'title': '性能模式', 'value': 'fast'}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'component': 'VCol',
|
'component': 'VCol',
|
||||||
'props': {
|
'props': {
|
||||||
'cols': 12,
|
'cols': 12,
|
||||||
'md': 6
|
'md': 6
|
||||||
},
|
},
|
||||||
'content': [
|
'content': [
|
||||||
{
|
{
|
||||||
'component': 'VSelect',
|
'component': 'VSelect',
|
||||||
'props': {
|
'props': {
|
||||||
'model': 'transfer_type',
|
'model': 'transfer_type',
|
||||||
'label': '转移方式',
|
'label': '转移方式',
|
||||||
'items': [
|
'items': [
|
||||||
{'title': '移动', 'value': 'move'},
|
{'title': '移动', 'value': 'move'},
|
||||||
{'title': '复制', 'value': 'copy'},
|
{'title': '复制', 'value': 'copy'},
|
||||||
{'title': '硬链接', 'value': 'link'},
|
{'title': '硬链接', 'value': 'link'},
|
||||||
{'title': '软链接', 'value': 'softlink'}
|
{'title': '软链接', 'value': 'softlink'}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'component': 'VRow',
|
'component': 'VRow',
|
||||||
'content': [
|
'content': [
|
||||||
{
|
{
|
||||||
'component': 'VCol',
|
'component': 'VCol',
|
||||||
'props': {
|
'props': {
|
||||||
'cols': 12
|
'cols': 12
|
||||||
},
|
},
|
||||||
'content': [
|
'content': [
|
||||||
{
|
{
|
||||||
'component': 'VTextarea',
|
'component': 'VTextarea',
|
||||||
'props': {
|
'props': {
|
||||||
'model': 'monitor_dirs',
|
'model': 'monitor_dirs',
|
||||||
'label': '监控目录',
|
'label': '监控目录',
|
||||||
'rows': 5,
|
'rows': 5,
|
||||||
'placeholder': '每一行一个目录,支持两种配置方式:\n'
|
'placeholder': '每一行一个目录,支持两种配置方式:\n'
|
||||||
'监控目录\n'
|
'监控目录\n'
|
||||||
'监控目录:转移目的目录'
|
'监控目录:转移目的目录'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'component': 'VRow',
|
'component': 'VRow',
|
||||||
'content': [
|
'content': [
|
||||||
{
|
{
|
||||||
'component': 'VCol',
|
'component': 'VCol',
|
||||||
'props': {
|
'props': {
|
||||||
'cols': 12
|
'cols': 12
|
||||||
},
|
},
|
||||||
'content': [
|
'content': [
|
||||||
{
|
{
|
||||||
'component': 'VTextarea',
|
'component': 'VTextarea',
|
||||||
'props': {
|
'props': {
|
||||||
'model': 'exclude_keywords',
|
'model': 'exclude_keywords',
|
||||||
'label': '排除关键词',
|
'label': '排除关键词',
|
||||||
'rows': 2,
|
'rows': 2,
|
||||||
'placeholder': '每一行一个关键词'
|
'placeholder': '每一行一个关键词'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
], {
|
], {
|
||||||
"enabled": False,
|
"enabled": False,
|
||||||
"notify": False,
|
"notify": False,
|
||||||
"mode": "fast",
|
"mode": "fast",
|
||||||
"transfer_type": settings.TRANSFER_TYPE,
|
"transfer_type": settings.TRANSFER_TYPE,
|
||||||
"monitor_dirs": "",
|
"monitor_dirs": "",
|
||||||
"exclude_keywords": ""
|
"exclude_keywords": ""
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_page(self) -> List[dict]:
|
def get_page(self) -> List[dict]:
|
||||||
pass
|
pass
|
||||||
|
@ -9,6 +9,7 @@ from apscheduler.triggers.cron import CronTrigger
|
|||||||
|
|
||||||
from app.core.config import settings
|
from app.core.config import settings
|
||||||
from app.core.metainfo import MetaInfo
|
from app.core.metainfo import MetaInfo
|
||||||
|
from app.db.transferhistory_oper import TransferHistoryOper
|
||||||
from app.helper.nfo import NfoReader
|
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
|
||||||
@ -40,6 +41,7 @@ class LibraryScraper(_PluginBase):
|
|||||||
user_level = 1
|
user_level = 1
|
||||||
|
|
||||||
# 私有属性
|
# 私有属性
|
||||||
|
transferhis = None
|
||||||
_scheduler = None
|
_scheduler = None
|
||||||
_scraper = None
|
_scraper = None
|
||||||
# 限速开关
|
# 限速开关
|
||||||
@ -67,6 +69,7 @@ class LibraryScraper(_PluginBase):
|
|||||||
|
|
||||||
# 启动定时任务 & 立即运行一次
|
# 启动定时任务 & 立即运行一次
|
||||||
if self._enabled or self._onlyonce:
|
if self._enabled or self._onlyonce:
|
||||||
|
self.transferhis = TransferHistoryOper(self.db)
|
||||||
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}")
|
||||||
@ -346,6 +349,13 @@ class LibraryScraper(_PluginBase):
|
|||||||
if not mediainfo:
|
if not mediainfo:
|
||||||
logger.warn(f"未识别到媒体信息:{file}")
|
logger.warn(f"未识别到媒体信息:{file}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# 如果未开启新增已入库媒体是否跟随TMDB信息变化则根据tmdbid查询之前的title
|
||||||
|
if not settings.SCRAP_FOLLOW_TMDB:
|
||||||
|
transfer_historys = self.transferhis.get_by(tmdbid=mediainfo.tmdb_id,
|
||||||
|
type=mediainfo.type.value)
|
||||||
|
if transfer_historys:
|
||||||
|
mediainfo.title = transfer_historys[0].title
|
||||||
|
|
||||||
# 覆盖模式时,提前删除nfo
|
# 覆盖模式时,提前删除nfo
|
||||||
if self._mode in ["force_all", "force_nfo"]:
|
if self._mode in ["force_all", "force_nfo"]:
|
||||||
|
@ -303,7 +303,7 @@ class MessageForward(_PluginBase):
|
|||||||
"enable_id_trans": 0,
|
"enable_id_trans": 0,
|
||||||
"enable_duplicate_check": 0
|
"enable_duplicate_check": 0
|
||||||
}
|
}
|
||||||
return self.__post_request(message_url, req_json, i, title)
|
return self.__post_request(message_url=message_url, req_json=req_json, i=i, title=title)
|
||||||
|
|
||||||
def __send_image_message(self, title: str, text: str, image_url: str, userid: str = None, access_token: str = None,
|
def __send_image_message(self, title: str, text: str, image_url: str, userid: str = None, access_token: str = None,
|
||||||
appid: str = None, i: int = None) -> Optional[bool]:
|
appid: str = None, i: int = None) -> Optional[bool]:
|
||||||
@ -335,9 +335,9 @@ class MessageForward(_PluginBase):
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return self.__post_request(message_url, req_json, i, title)
|
return self.__post_request(message_url=message_url, req_json=req_json, i=i, title=title)
|
||||||
|
|
||||||
def __post_request(self, message_url: str, req_json: dict, i: int, title: str) -> bool:
|
def __post_request(self, message_url: str, req_json: dict, i: int, title: str, retry: int = 0) -> bool:
|
||||||
"""
|
"""
|
||||||
向微信发送请求
|
向微信发送请求
|
||||||
"""
|
"""
|
||||||
@ -355,6 +355,11 @@ class MessageForward(_PluginBase):
|
|||||||
if ret_json.get('errcode') == 42001:
|
if ret_json.get('errcode') == 42001:
|
||||||
# 重新获取token
|
# 重新获取token
|
||||||
self.__flush_access_token(i)
|
self.__flush_access_token(i)
|
||||||
|
retry += 1
|
||||||
|
# 重发请求
|
||||||
|
if retry <= 3:
|
||||||
|
self.__post_request(message_url=message_url, req_json=req_json, i=i, title=title,
|
||||||
|
retry=retry)
|
||||||
logger.error(f"转发消息 {title} 失败,错误信息:{ret_json}")
|
logger.error(f"转发消息 {title} 失败,错误信息:{ret_json}")
|
||||||
return False
|
return False
|
||||||
elif res is not None:
|
elif res is not None:
|
||||||
@ -364,7 +369,7 @@ class MessageForward(_PluginBase):
|
|||||||
logger.error(f"转发消息 {title} 失败,未获取到返回信息")
|
logger.error(f"转发消息 {title} 失败,未获取到返回信息")
|
||||||
return False
|
return False
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
logger.error(f"转发消息 {title} 失败,错误信息:{err}")
|
logger.error(f"转发消息 {title} 异常,错误信息:{err}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def __get_access_token(self, corpid, appsecret):
|
def __get_access_token(self, corpid, appsecret):
|
||||||
|
Reference in New Issue
Block a user