mirror of
https://github.com/d0zingcat/MoviePilot-Plugins.git
synced 2026-06-06 15:10:29 +00:00
Merge remote-tracking branch 'origin/main'
This commit is contained in:
12
package.json
12
package.json
@@ -34,7 +34,7 @@
|
|||||||
"DoubanSync": {
|
"DoubanSync": {
|
||||||
"name": "豆瓣想看",
|
"name": "豆瓣想看",
|
||||||
"description": "同步豆瓣想看数据,自动添加订阅。",
|
"description": "同步豆瓣想看数据,自动添加订阅。",
|
||||||
"version": "1.1",
|
"version": "1.2",
|
||||||
"icon": "douban.png",
|
"icon": "douban.png",
|
||||||
"author": "jxxghp",
|
"author": "jxxghp",
|
||||||
"level": 2
|
"level": 2
|
||||||
@@ -58,7 +58,7 @@
|
|||||||
"DoubanRank": {
|
"DoubanRank": {
|
||||||
"name": "豆瓣榜单订阅",
|
"name": "豆瓣榜单订阅",
|
||||||
"description": "监控豆瓣热门榜单,自动添加订阅。",
|
"description": "监控豆瓣热门榜单,自动添加订阅。",
|
||||||
"version": "1.2",
|
"version": "1.4",
|
||||||
"icon": "movie.jpg",
|
"icon": "movie.jpg",
|
||||||
"author": "jxxghp",
|
"author": "jxxghp",
|
||||||
"level": 2
|
"level": 2
|
||||||
@@ -66,7 +66,7 @@
|
|||||||
"LibraryScraper": {
|
"LibraryScraper": {
|
||||||
"name": "媒体库刮削",
|
"name": "媒体库刮削",
|
||||||
"description": "定时对媒体库进行刮削,补齐缺失元数据和图片。",
|
"description": "定时对媒体库进行刮削,补齐缺失元数据和图片。",
|
||||||
"version": "1.1",
|
"version": "1.2",
|
||||||
"icon": "scraper.png",
|
"icon": "scraper.png",
|
||||||
"author": "jxxghp",
|
"author": "jxxghp",
|
||||||
"level": 1
|
"level": 1
|
||||||
@@ -210,7 +210,7 @@
|
|||||||
"BrushFlow": {
|
"BrushFlow": {
|
||||||
"name": "站点刷流",
|
"name": "站点刷流",
|
||||||
"description": "自动托管刷流,将会提高对应站点的访问频率。",
|
"description": "自动托管刷流,将会提高对应站点的访问频率。",
|
||||||
"version": "1.0",
|
"version": "1.1",
|
||||||
"icon": "brush.jpg",
|
"icon": "brush.jpg",
|
||||||
"author": "jxxghp",
|
"author": "jxxghp",
|
||||||
"level": 2
|
"level": 2
|
||||||
@@ -258,7 +258,7 @@
|
|||||||
"CloudDiskDel": {
|
"CloudDiskDel": {
|
||||||
"name": "云盘文件删除",
|
"name": "云盘文件删除",
|
||||||
"description": "媒体库删除strm文件后同步删除云盘资源。",
|
"description": "媒体库删除strm文件后同步删除云盘资源。",
|
||||||
"version": "1.1",
|
"version": "1.2",
|
||||||
"icon": "clouddisk.png",
|
"icon": "clouddisk.png",
|
||||||
"author": "thsrite",
|
"author": "thsrite",
|
||||||
"level": 1
|
"level": 1
|
||||||
@@ -322,7 +322,7 @@
|
|||||||
"FFmpegThumb": {
|
"FFmpegThumb": {
|
||||||
"name": "FFmpeg缩略图",
|
"name": "FFmpeg缩略图",
|
||||||
"description": "TheMovieDb没有背景图片时使用FFmpeg截取视频文件缩略图",
|
"description": "TheMovieDb没有背景图片时使用FFmpeg截取视频文件缩略图",
|
||||||
"version": "1.0",
|
"version": "1.2",
|
||||||
"icon": "ffmpeg.png",
|
"icon": "ffmpeg.png",
|
||||||
"author": "jxxghp",
|
"author": "jxxghp",
|
||||||
"level": 1
|
"level": 1
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class BrushFlow(_PluginBase):
|
|||||||
# 插件图标
|
# 插件图标
|
||||||
plugin_icon = "brush.jpg"
|
plugin_icon = "brush.jpg"
|
||||||
# 插件版本
|
# 插件版本
|
||||||
plugin_version = "1.0"
|
plugin_version = "1.1"
|
||||||
# 插件作者
|
# 插件作者
|
||||||
plugin_author = "jxxghp"
|
plugin_author = "jxxghp"
|
||||||
# 作者主页
|
# 作者主页
|
||||||
@@ -64,6 +64,7 @@ class BrushFlow(_PluginBase):
|
|||||||
_downloader = "qbittorrent"
|
_downloader = "qbittorrent"
|
||||||
_disksize = 0
|
_disksize = 0
|
||||||
_freeleech = "free"
|
_freeleech = "free"
|
||||||
|
_hr = "no"
|
||||||
_maxupspeed = 0
|
_maxupspeed = 0
|
||||||
_maxdlspeed = 0
|
_maxdlspeed = 0
|
||||||
_maxdlcount = 0
|
_maxdlcount = 0
|
||||||
@@ -96,6 +97,7 @@ class BrushFlow(_PluginBase):
|
|||||||
self._downloader = config.get("downloader")
|
self._downloader = config.get("downloader")
|
||||||
self._disksize = config.get("disksize")
|
self._disksize = config.get("disksize")
|
||||||
self._freeleech = config.get("freeleech")
|
self._freeleech = config.get("freeleech")
|
||||||
|
self._hr = config.get("hr")
|
||||||
self._maxupspeed = config.get("maxupspeed")
|
self._maxupspeed = config.get("maxupspeed")
|
||||||
self._maxdlspeed = config.get("maxdlspeed")
|
self._maxdlspeed = config.get("maxdlspeed")
|
||||||
self._maxdlcount = config.get("maxdlcount")
|
self._maxdlcount = config.get("maxdlcount")
|
||||||
@@ -411,6 +413,26 @@ class BrushFlow(_PluginBase):
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'component': 'VCol',
|
||||||
|
'props': {
|
||||||
|
"cols": 12,
|
||||||
|
"md": 4
|
||||||
|
},
|
||||||
|
'content': [
|
||||||
|
{
|
||||||
|
'component': 'VSelect',
|
||||||
|
'props': {
|
||||||
|
'model': 'hr',
|
||||||
|
'label': '排除H&R',
|
||||||
|
'items': [
|
||||||
|
{'title': '是', 'value': 'yes'},
|
||||||
|
{'title': '否', 'value': 'no'},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
'component': 'VCol',
|
'component': 'VCol',
|
||||||
'props': {
|
'props': {
|
||||||
@@ -732,7 +754,28 @@ class BrushFlow(_PluginBase):
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
'component': 'VRow',
|
||||||
|
'content': [
|
||||||
|
{
|
||||||
|
'component': 'VCol',
|
||||||
|
'props': {
|
||||||
|
'cols': 12,
|
||||||
|
},
|
||||||
|
'content': [
|
||||||
|
{
|
||||||
|
'component': 'VAlert',
|
||||||
|
'props': {
|
||||||
|
'type': 'info',
|
||||||
|
'variant': 'tonal',
|
||||||
|
'text': '注意:排除H&R并不保证能完全适配所有站点(部分站点在列表页不显示H&R标志,但实际上是有H&R的),请注意核对使用!'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
], {
|
], {
|
||||||
@@ -740,7 +783,8 @@ class BrushFlow(_PluginBase):
|
|||||||
"notify": True,
|
"notify": True,
|
||||||
"onlyonce": False,
|
"onlyonce": False,
|
||||||
"clear_task": False,
|
"clear_task": False,
|
||||||
"freeleech": "free"
|
"freeleech": "free",
|
||||||
|
"hr": "yes",
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_page(self) -> List[dict]:
|
def get_page(self) -> List[dict]:
|
||||||
@@ -1215,6 +1259,7 @@ class BrushFlow(_PluginBase):
|
|||||||
"downloader": self._downloader,
|
"downloader": self._downloader,
|
||||||
"disksize": self._disksize,
|
"disksize": self._disksize,
|
||||||
"freeleech": self._freeleech,
|
"freeleech": self._freeleech,
|
||||||
|
"hr": self._hr,
|
||||||
"maxupspeed": self._maxupspeed,
|
"maxupspeed": self._maxupspeed,
|
||||||
"maxdlspeed": self._maxdlspeed,
|
"maxdlspeed": self._maxdlspeed,
|
||||||
"maxdlcount": self._maxdlcount,
|
"maxdlcount": self._maxdlcount,
|
||||||
@@ -1284,6 +1329,9 @@ class BrushFlow(_PluginBase):
|
|||||||
continue
|
continue
|
||||||
if self._freeleech == "2xfree" and torrent.uploadvolumefactor != 2:
|
if self._freeleech == "2xfree" and torrent.uploadvolumefactor != 2:
|
||||||
continue
|
continue
|
||||||
|
# H&R
|
||||||
|
if self._hr == "yes" and torrent.hit_and_run:
|
||||||
|
continue
|
||||||
# 包含规则
|
# 包含规则
|
||||||
if self._include and not re.search(r"%s" % self._include, torrent.title, re.I):
|
if self._include and not re.search(r"%s" % self._include, torrent.title, re.I):
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class CloudDiskDel(_PluginBase):
|
|||||||
# 插件图标
|
# 插件图标
|
||||||
plugin_icon = "clouddisk.png"
|
plugin_icon = "clouddisk.png"
|
||||||
# 插件版本
|
# 插件版本
|
||||||
plugin_version = "1.1"
|
plugin_version = "1.2"
|
||||||
# 插件作者
|
# 插件作者
|
||||||
plugin_author = "thsrite"
|
plugin_author = "thsrite"
|
||||||
# 作者主页
|
# 作者主页
|
||||||
@@ -84,9 +84,13 @@ class CloudDiskDel(_PluginBase):
|
|||||||
season_num = event_data.get("season_num")
|
season_num = event_data.get("season_num")
|
||||||
episode_num = event_data.get("episode_num")
|
episode_num = event_data.get("episode_num")
|
||||||
|
|
||||||
|
# 不是网盘监控路径,直接排除
|
||||||
|
cloud_file_flag = False
|
||||||
|
|
||||||
# 判断删除媒体路径是否与配置的媒体库路径相符,相符则继续删除,不符则跳过
|
# 判断删除媒体路径是否与配置的媒体库路径相符,相符则继续删除,不符则跳过
|
||||||
for library_path in list(self._paths.keys()):
|
for library_path in list(self._paths.keys()):
|
||||||
if str(media_path).startswith(library_path):
|
if str(media_path).startswith(library_path):
|
||||||
|
cloud_file_flag = True
|
||||||
# 替换网盘路径
|
# 替换网盘路径
|
||||||
media_path = str(media_path).replace(library_path, self._paths.get(library_path))
|
media_path = str(media_path).replace(library_path, self._paths.get(library_path))
|
||||||
logger.info(f"获取到moviepilot本地云盘挂载路径 {media_path}")
|
logger.info(f"获取到moviepilot本地云盘挂载路径 {media_path}")
|
||||||
@@ -128,63 +132,64 @@ class CloudDiskDel(_PluginBase):
|
|||||||
|
|
||||||
break
|
break
|
||||||
|
|
||||||
# 发送消息
|
if cloud_file_flag:
|
||||||
image = 'https://emby.media/notificationicon.png'
|
# 发送消息
|
||||||
media_type = MediaType.MOVIE if media_type in ["Movie", "MOV"] else MediaType.TV
|
image = 'https://emby.media/notificationicon.png'
|
||||||
if self._notify:
|
media_type = MediaType.MOVIE if media_type in ["Movie", "MOV"] else MediaType.TV
|
||||||
backrop_image = self.chain.obtain_specific_image(
|
if self._notify:
|
||||||
|
backrop_image = self.chain.obtain_specific_image(
|
||||||
|
mediaid=tmdb_id,
|
||||||
|
mtype=media_type,
|
||||||
|
image_type=MediaImageType.Backdrop,
|
||||||
|
season=season_num,
|
||||||
|
episode=episode_num
|
||||||
|
) or image
|
||||||
|
|
||||||
|
# 类型
|
||||||
|
if media_type == MediaType.MOVIE:
|
||||||
|
msg = f'电影 {media_name} {tmdb_id}'
|
||||||
|
# 删除电视剧
|
||||||
|
elif media_type == MediaType.TV and not season_num and not episode_num:
|
||||||
|
msg = f'剧集 {media_name} {tmdb_id}'
|
||||||
|
# 删除季 S02
|
||||||
|
elif media_type == MediaType.TV and season_num and not episode_num:
|
||||||
|
msg = f'剧集 {media_name} S{season_num} {tmdb_id}'
|
||||||
|
# 删除剧集S02E02
|
||||||
|
elif media_type == MediaType.TV and season_num and episode_num:
|
||||||
|
msg = f'剧集 {media_name} S{season_num}E{episode_num} {tmdb_id}'
|
||||||
|
else:
|
||||||
|
msg = media_name
|
||||||
|
|
||||||
|
# 发送通知
|
||||||
|
self.post_message(
|
||||||
|
mtype=NotificationType.MediaServer,
|
||||||
|
title="云盘同步删除任务完成",
|
||||||
|
image=backrop_image,
|
||||||
|
text=f"{msg}\n"
|
||||||
|
f"时间 {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time()))}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 读取历史记录
|
||||||
|
history = self.get_data('history') or []
|
||||||
|
|
||||||
|
# 获取poster
|
||||||
|
poster_image = self.chain.obtain_specific_image(
|
||||||
mediaid=tmdb_id,
|
mediaid=tmdb_id,
|
||||||
mtype=media_type,
|
mtype=media_type,
|
||||||
image_type=MediaImageType.Backdrop,
|
image_type=MediaImageType.Poster,
|
||||||
season=season_num,
|
|
||||||
episode=episode_num
|
|
||||||
) or image
|
) or image
|
||||||
|
history.append({
|
||||||
|
"type": media_type.value,
|
||||||
|
"title": media_name,
|
||||||
|
"path": media_path,
|
||||||
|
"season": season_num if season_num and str(season_num).isdigit() else None,
|
||||||
|
"episode": episode_num if episode_num and str(episode_num).isdigit() else None,
|
||||||
|
"image": poster_image,
|
||||||
|
"del_time": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))
|
||||||
|
})
|
||||||
|
|
||||||
# 类型
|
# 保存历史
|
||||||
if media_type == MediaType.MOVIE:
|
self.save_data("history", history)
|
||||||
msg = f'电影 {media_name} {tmdb_id}'
|
|
||||||
# 删除电视剧
|
|
||||||
elif media_type == MediaType.TV and not season_num and not episode_num:
|
|
||||||
msg = f'剧集 {media_name} {tmdb_id}'
|
|
||||||
# 删除季 S02
|
|
||||||
elif media_type == MediaType.TV and season_num and not episode_num:
|
|
||||||
msg = f'剧集 {media_name} S{season_num} {tmdb_id}'
|
|
||||||
# 删除剧集S02E02
|
|
||||||
elif media_type == MediaType.TV and season_num and episode_num:
|
|
||||||
msg = f'剧集 {media_name} S{season_num}E{episode_num} {tmdb_id}'
|
|
||||||
else:
|
|
||||||
msg = media_name
|
|
||||||
|
|
||||||
# 发送通知
|
|
||||||
self.post_message(
|
|
||||||
mtype=NotificationType.MediaServer,
|
|
||||||
title="云盘同步删除任务完成",
|
|
||||||
image=backrop_image,
|
|
||||||
text=f"{msg}\n"
|
|
||||||
f"时间 {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time()))}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# 读取历史记录
|
|
||||||
history = self.get_data('history') or []
|
|
||||||
|
|
||||||
# 获取poster
|
|
||||||
poster_image = self.chain.obtain_specific_image(
|
|
||||||
mediaid=tmdb_id,
|
|
||||||
mtype=media_type,
|
|
||||||
image_type=MediaImageType.Poster,
|
|
||||||
) or image
|
|
||||||
history.append({
|
|
||||||
"type": media_type.value,
|
|
||||||
"title": media_name,
|
|
||||||
"path": media_path,
|
|
||||||
"season": season_num if season_num and str(season_num).isdigit() else None,
|
|
||||||
"episode": episode_num if episode_num and str(episode_num).isdigit() else None,
|
|
||||||
"image": poster_image,
|
|
||||||
"del_time": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))
|
|
||||||
})
|
|
||||||
|
|
||||||
# 保存历史
|
|
||||||
self.save_data("history", history)
|
|
||||||
|
|
||||||
def get_state(self) -> bool:
|
def get_state(self) -> bool:
|
||||||
return self._enabled
|
return self._enabled
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class DoubanRank(_PluginBase):
|
|||||||
# 插件图标
|
# 插件图标
|
||||||
plugin_icon = "movie.jpg"
|
plugin_icon = "movie.jpg"
|
||||||
# 插件版本
|
# 插件版本
|
||||||
plugin_version = "1.2"
|
plugin_version = "1.4"
|
||||||
# 插件作者
|
# 插件作者
|
||||||
plugin_author = "jxxghp"
|
plugin_author = "jxxghp"
|
||||||
# 作者主页
|
# 作者主页
|
||||||
@@ -48,7 +48,7 @@ class DoubanRank(_PluginBase):
|
|||||||
_douban_address = {
|
_douban_address = {
|
||||||
'movie-ustop': 'https://rsshub.app/douban/movie/ustop',
|
'movie-ustop': 'https://rsshub.app/douban/movie/ustop',
|
||||||
'movie-weekly': 'https://rsshub.app/douban/movie/weekly',
|
'movie-weekly': 'https://rsshub.app/douban/movie/weekly',
|
||||||
'movie-real-time': 'https://rsshub.app/douban/movie/weekly/subject_real_time_hotest',
|
'movie-real-time': 'https://rsshub.app/douban/movie/weekly/movie_real_time_hotest',
|
||||||
'show-domestic': 'https://rsshub.app/douban/movie/weekly/show_domestic',
|
'show-domestic': 'https://rsshub.app/douban/movie/weekly/show_domestic',
|
||||||
'movie-hot-gaia': 'https://rsshub.app/douban/movie/weekly/movie_hot_gaia',
|
'movie-hot-gaia': 'https://rsshub.app/douban/movie/weekly/movie_hot_gaia',
|
||||||
'tv-hot': 'https://rsshub.app/douban/movie/weekly/tv_hot',
|
'tv-hot': 'https://rsshub.app/douban/movie/weekly/tv_hot',
|
||||||
@@ -101,10 +101,8 @@ class DoubanRank(_PluginBase):
|
|||||||
logger.error(f"豆瓣榜单订阅服务启动失败,错误信息:{str(e)}")
|
logger.error(f"豆瓣榜单订阅服务启动失败,错误信息:{str(e)}")
|
||||||
self.systemmessage.put(f"豆瓣榜单订阅服务启动失败,错误信息:{str(e)}")
|
self.systemmessage.put(f"豆瓣榜单订阅服务启动失败,错误信息:{str(e)}")
|
||||||
else:
|
else:
|
||||||
self._scheduler.add_job(func=self.__refresh_rss, trigger='date',
|
self._scheduler.add_job(func=self.__refresh_rss, trigger=CronTrigger.from_crontab("0 8 * * *"),
|
||||||
run_date=datetime.datetime.now(
|
name="豆瓣榜单订阅")
|
||||||
tz=pytz.timezone(settings.TZ)) + datetime.timedelta(seconds=3)
|
|
||||||
)
|
|
||||||
logger.info("豆瓣榜单订阅服务启动,周期:每天 08:00")
|
logger.info("豆瓣榜单订阅服务启动,周期:每天 08:00")
|
||||||
|
|
||||||
if self._onlyonce:
|
if self._onlyonce:
|
||||||
@@ -484,12 +482,14 @@ class DoubanRank(_PluginBase):
|
|||||||
|
|
||||||
title = rss_info.get('title')
|
title = rss_info.get('title')
|
||||||
douban_id = rss_info.get('doubanid')
|
douban_id = rss_info.get('doubanid')
|
||||||
|
year = rss_info.get('year')
|
||||||
unique_flag = f"doubanrank: {title} (DB:{douban_id})"
|
unique_flag = f"doubanrank: {title} (DB:{douban_id})"
|
||||||
# 检查是否已处理过
|
# 检查是否已处理过
|
||||||
if unique_flag in [h.get("unique") for h in history]:
|
if unique_flag in [h.get("unique") for h in history]:
|
||||||
continue
|
continue
|
||||||
# 元数据
|
# 元数据
|
||||||
meta = MetaInfo(title)
|
meta = MetaInfo(title)
|
||||||
|
meta.year = year
|
||||||
# 识别媒体信息
|
# 识别媒体信息
|
||||||
if douban_id:
|
if douban_id:
|
||||||
# 识别豆瓣信息
|
# 识别豆瓣信息
|
||||||
@@ -565,25 +565,36 @@ class DoubanRank(_PluginBase):
|
|||||||
items = rootNode.getElementsByTagName("item")
|
items = rootNode.getElementsByTagName("item")
|
||||||
for item in items:
|
for item in items:
|
||||||
try:
|
try:
|
||||||
|
rss_info = {}
|
||||||
|
|
||||||
# 标题
|
# 标题
|
||||||
title = DomUtils.tag_value(item, "title", default="")
|
title = DomUtils.tag_value(item, "title", default="")
|
||||||
# 链接
|
# 链接
|
||||||
link = DomUtils.tag_value(item, "link", default="")
|
link = DomUtils.tag_value(item, "link", default="")
|
||||||
|
# 年份
|
||||||
|
description = DomUtils.tag_value(item, "description", default="")
|
||||||
|
|
||||||
if not title and not link:
|
if not title and not link:
|
||||||
logger.warn(f"条目标题和链接均为空,无法处理")
|
logger.warn(f"条目标题和链接均为空,无法处理")
|
||||||
continue
|
continue
|
||||||
|
rss_info['title'] = title
|
||||||
|
rss_info['link'] = link
|
||||||
|
|
||||||
doubanid = re.findall(r"/(\d+)/", link)
|
doubanid = re.findall(r"/(\d+)/", link)
|
||||||
if doubanid:
|
if doubanid:
|
||||||
doubanid = doubanid[0]
|
doubanid = doubanid[0]
|
||||||
if doubanid and not str(doubanid).isdigit():
|
if doubanid and not str(doubanid).isdigit():
|
||||||
logger.warn(f"解析的豆瓣ID格式不正确:{doubanid}")
|
logger.warn(f"解析的豆瓣ID格式不正确:{doubanid}")
|
||||||
continue
|
continue
|
||||||
|
rss_info['doubanid'] = doubanid
|
||||||
|
|
||||||
|
# 匹配4位独立数字1900-2099年
|
||||||
|
year = re.findall(r"\b(19\d{2}|20\d{2})\b", description)
|
||||||
|
if year:
|
||||||
|
rss_info['year'] = year[0]
|
||||||
|
|
||||||
# 返回对象
|
# 返回对象
|
||||||
ret_array.append({
|
ret_array.append(rss_info)
|
||||||
'title': title,
|
|
||||||
'link': link,
|
|
||||||
'doubanid': doubanid
|
|
||||||
})
|
|
||||||
except Exception as e1:
|
except Exception as e1:
|
||||||
logger.error("解析RSS条目失败:" + str(e1))
|
logger.error("解析RSS条目失败:" + str(e1))
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from typing import Optional, Any, List, Dict, Tuple
|
|||||||
import pytz
|
import pytz
|
||||||
from apscheduler.schedulers.background import BackgroundScheduler
|
from apscheduler.schedulers.background import BackgroundScheduler
|
||||||
from apscheduler.triggers.cron import CronTrigger
|
from apscheduler.triggers.cron import CronTrigger
|
||||||
|
from app.schemas.types import MediaType
|
||||||
|
|
||||||
from app.chain.download import DownloadChain
|
from app.chain.download import DownloadChain
|
||||||
from app.chain.search import SearchChain
|
from app.chain.search import SearchChain
|
||||||
@@ -30,7 +31,7 @@ class DoubanSync(_PluginBase):
|
|||||||
# 插件图标
|
# 插件图标
|
||||||
plugin_icon = "douban.png"
|
plugin_icon = "douban.png"
|
||||||
# 插件版本
|
# 插件版本
|
||||||
plugin_version = "1.1"
|
plugin_version = "1.2"
|
||||||
# 插件作者
|
# 插件作者
|
||||||
plugin_author = "jxxghp"
|
plugin_author = "jxxghp"
|
||||||
# 作者主页
|
# 作者主页
|
||||||
@@ -475,6 +476,9 @@ class DoubanSync(_PluginBase):
|
|||||||
continue
|
continue
|
||||||
# 识别媒体信息
|
# 识别媒体信息
|
||||||
meta = MetaInfo(title=title)
|
meta = MetaInfo(title=title)
|
||||||
|
douban_info = self.chain.douban_info(doubanid=douban_id)
|
||||||
|
meta.year = douban_info.get("year")
|
||||||
|
meta.type = MediaType.MOVIE if douban_info.get("type") == "movie" else MediaType.TV
|
||||||
mediainfo = self.chain.recognize_media(meta=meta, doubanid=douban_id)
|
mediainfo = self.chain.recognize_media(meta=meta, doubanid=douban_id)
|
||||||
if not mediainfo:
|
if not mediainfo:
|
||||||
logger.warn(f'未识别到媒体信息,标题:{title},豆瓣ID:{douban_id}')
|
logger.warn(f'未识别到媒体信息,标题:{title},豆瓣ID:{douban_id}')
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import threading
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from threading import Event as ThreadEvent
|
from threading import Event as ThreadEvent
|
||||||
@@ -16,6 +17,8 @@ from app.schemas import TransferInfo
|
|||||||
from app.schemas.types import EventType
|
from app.schemas.types import EventType
|
||||||
from app.utils.system import SystemUtils
|
from app.utils.system import SystemUtils
|
||||||
|
|
||||||
|
ffmpeg_lock = threading.Lock()
|
||||||
|
|
||||||
|
|
||||||
class FFmpegThumb(_PluginBase):
|
class FFmpegThumb(_PluginBase):
|
||||||
# 插件名称
|
# 插件名称
|
||||||
@@ -25,7 +28,7 @@ class FFmpegThumb(_PluginBase):
|
|||||||
# 插件图标
|
# 插件图标
|
||||||
plugin_icon = "ffmpeg.png"
|
plugin_icon = "ffmpeg.png"
|
||||||
# 插件版本
|
# 插件版本
|
||||||
plugin_version = "1.0"
|
plugin_version = "1.2"
|
||||||
# 插件作者
|
# 插件作者
|
||||||
plugin_author = "jxxghp"
|
plugin_author = "jxxghp"
|
||||||
# 作者主页
|
# 作者主页
|
||||||
@@ -328,18 +331,18 @@ class FFmpegThumb(_PluginBase):
|
|||||||
"""
|
"""
|
||||||
处理一个文件
|
处理一个文件
|
||||||
"""
|
"""
|
||||||
try:
|
# 单线程处理
|
||||||
thumb_path = file_path.with_name(file_path.stem + "-thumb.jpg")
|
with ffmpeg_lock:
|
||||||
if thumb_path.exists():
|
try:
|
||||||
logger.info(f"缩略图已存在:{thumb_path}")
|
thumb_path = file_path.with_name(file_path.stem + "-thumb.jpg")
|
||||||
return
|
if thumb_path.exists():
|
||||||
if FfmpegHelper.get_thumb(video_path=str(file_path),
|
logger.info(f"缩略图已存在:{thumb_path}")
|
||||||
image_path=str(thumb_path), frames=self._timeline):
|
return
|
||||||
logger.info(f"{file_path} 缩略图已生成:{thumb_path}")
|
if FfmpegHelper.get_thumb(video_path=str(file_path),
|
||||||
else:
|
image_path=str(thumb_path), frames=self._timeline):
|
||||||
logger.warn(f"{file_path} 缩略图生成失败!")
|
logger.info(f"{file_path} 缩略图已生成:{thumb_path}")
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
logger.error(f"FFmpeg处理文件 {file_path} 时发生错误:{str(err)}")
|
logger.error(f"FFmpeg处理文件 {file_path} 时发生错误:{str(err)}")
|
||||||
|
|
||||||
def stop_service(self):
|
def stop_service(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -8,8 +8,7 @@ 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.meta import MetaBase
|
from app.core.metainfo import MetaInfoPath
|
||||||
from app.core.metainfo import MetaInfo
|
|
||||||
from app.db.transferhistory_oper import TransferHistoryOper
|
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
|
||||||
@@ -26,7 +25,7 @@ class LibraryScraper(_PluginBase):
|
|||||||
# 插件图标
|
# 插件图标
|
||||||
plugin_icon = "scraper.png"
|
plugin_icon = "scraper.png"
|
||||||
# 插件版本
|
# 插件版本
|
||||||
plugin_version = "1.1"
|
plugin_version = "1.2"
|
||||||
# 插件作者
|
# 插件作者
|
||||||
plugin_author = "jxxghp"
|
plugin_author = "jxxghp"
|
||||||
# 作者主页
|
# 作者主页
|
||||||
@@ -214,7 +213,7 @@ class LibraryScraper(_PluginBase):
|
|||||||
'model': 'scraper_paths',
|
'model': 'scraper_paths',
|
||||||
'label': '削刮路径',
|
'label': '削刮路径',
|
||||||
'rows': 5,
|
'rows': 5,
|
||||||
'placeholder': '每一行一个目录,需配置到媒体文件的上级目录,即开了二级分类时需要配置到二级分类目录'
|
'placeholder': '每一行一个目录'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -243,27 +242,6 @@ class LibraryScraper(_PluginBase):
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
'component': 'VRow',
|
|
||||||
'content': [
|
|
||||||
{
|
|
||||||
'component': 'VCol',
|
|
||||||
'props': {
|
|
||||||
'cols': 12,
|
|
||||||
},
|
|
||||||
'content': [
|
|
||||||
{
|
|
||||||
'component': 'VAlert',
|
|
||||||
'props': {
|
|
||||||
'type': 'info',
|
|
||||||
'variant': 'tonal',
|
|
||||||
'text': '刮削路径要配置到二级分类路径(如果配置了LIBRARY_CATEGORY=true);开启插件后默认会实时处理增量整理的媒体文件,需要处理存量媒体文件时才需开启定时。'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
'component': 'VRow',
|
'component': 'VRow',
|
||||||
'content': [
|
'content': [
|
||||||
@@ -325,8 +303,9 @@ class LibraryScraper(_PluginBase):
|
|||||||
logger.warning(f"媒体库刮削路径不存在:{path}")
|
logger.warning(f"媒体库刮削路径不存在:{path}")
|
||||||
continue
|
continue
|
||||||
logger.info(f"开始刮削媒体库:{path} {mtype} ...")
|
logger.info(f"开始刮削媒体库:{path} {mtype} ...")
|
||||||
# 遍历一层文件夹
|
# 遍历所有文件
|
||||||
for sub_path in scraper_path.iterdir():
|
files = SystemUtils.list_files(scraper_path, settings.RMT_MEDIAEXT)
|
||||||
|
for file_path in files:
|
||||||
if self._event.is_set():
|
if self._event.is_set():
|
||||||
logger.info(f"媒体库刮削服务停止")
|
logger.info(f"媒体库刮削服务停止")
|
||||||
return
|
return
|
||||||
@@ -334,115 +313,71 @@ class LibraryScraper(_PluginBase):
|
|||||||
exclude_flag = False
|
exclude_flag = False
|
||||||
for exclude_path in exclude_paths:
|
for exclude_path in exclude_paths:
|
||||||
try:
|
try:
|
||||||
if sub_path.is_relative_to(Path(exclude_path)):
|
if file_path.is_relative_to(Path(exclude_path)):
|
||||||
exclude_flag = True
|
exclude_flag = True
|
||||||
break
|
break
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
print(str(err))
|
print(str(err))
|
||||||
if exclude_flag:
|
if exclude_flag:
|
||||||
logger.debug(f"{sub_path} 在排除目录中,跳过 ...")
|
logger.debug(f"{file_path} 在排除目录中,跳过 ...")
|
||||||
continue
|
continue
|
||||||
# 开始刮削目录
|
# 开始刮削文件
|
||||||
if sub_path.is_dir():
|
self.__scrape_file(file=file_path, mtype=mtype)
|
||||||
# 判断目录是不是媒体目录
|
|
||||||
dir_meta = MetaInfo(sub_path.name)
|
|
||||||
if not dir_meta.name or not dir_meta.year:
|
|
||||||
logger.warn(f"{sub_path} 可能不是媒体目录,请检查刮削目录配置,跳过 ...")
|
|
||||||
continue
|
|
||||||
logger.info(f"开始刮削目录:{sub_path} ...")
|
|
||||||
self.__scrape_dir(path=sub_path, dir_meta=dir_meta, mtype=mtype)
|
|
||||||
logger.info(f"目录 {sub_path} 刮削完成")
|
|
||||||
logger.info(f"媒体库 {path} 刮削完成")
|
logger.info(f"媒体库 {path} 刮削完成")
|
||||||
|
|
||||||
def __scrape_dir(self, path: Path, dir_meta: MetaBase, mtype: MediaType = None):
|
def __scrape_file(self, file: Path, mtype: MediaType = None):
|
||||||
"""
|
"""
|
||||||
削刮一个目录,该目录必须是媒体文件目录
|
削刮一个目录,该目录必须是媒体文件目录
|
||||||
"""
|
"""
|
||||||
|
# 识别元数据
|
||||||
|
meta_info = MetaInfoPath(file)
|
||||||
|
# 强制指定类型
|
||||||
|
if mtype:
|
||||||
|
meta_info.type = mtype
|
||||||
|
|
||||||
# 媒体信息
|
# 是否刮削
|
||||||
mediainfo = None
|
force_nfo = self._mode in ["force_all", "force_nfo"]
|
||||||
|
force_img = self._mode in ["force_all", "force_image"]
|
||||||
|
|
||||||
# 查找目录下所有的文件
|
# 优先读取本地nfo文件
|
||||||
files = SystemUtils.list_files(path, settings.RMT_MEDIAEXT)
|
tmdbid = None
|
||||||
for file in files:
|
if meta_info.type == MediaType.MOVIE:
|
||||||
if self._event.is_set():
|
# 电影
|
||||||
logger.info(f"媒体库刮削服务停止")
|
movie_nfo = file.parent / "movie.nfo"
|
||||||
return
|
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:
|
||||||
|
# 按TMDBID识别
|
||||||
|
logger.info(f"读取到本地nfo文件的tmdbid:{tmdbid}")
|
||||||
|
mediainfo = self.chain.recognize_media(tmdbid=tmdbid, mtype=meta_info.type)
|
||||||
|
else:
|
||||||
|
# 按名称识别
|
||||||
|
mediainfo = self.chain.recognize_media(meta=meta_info)
|
||||||
|
if not mediainfo:
|
||||||
|
logger.warn(f"未识别到媒体信息:{file}")
|
||||||
|
return
|
||||||
|
|
||||||
# 识别元数据
|
# 如果未开启新增已入库媒体是否跟随TMDB信息变化则根据tmdbid查询之前的title
|
||||||
meta_info = MetaInfo(file.stem)
|
if not settings.SCRAP_FOLLOW_TMDB:
|
||||||
# 合并
|
transfer_history = self.transferhis.get_by_type_tmdbid(tmdbid=mediainfo.tmdb_id,
|
||||||
meta_info.merge(dir_meta)
|
mtype=mediainfo.type.value)
|
||||||
# 强制指定类型
|
if transfer_history:
|
||||||
if mtype:
|
mediainfo.title = transfer_history.title
|
||||||
meta_info.type = mtype
|
|
||||||
|
|
||||||
# 是否刮削
|
# 刮削
|
||||||
scrap_metadata = settings.SCRAP_METADATA
|
self.chain.scrape_metadata(path=file,
|
||||||
|
mediainfo=mediainfo,
|
||||||
# 没有媒体信息或者名字出现变化时,需要重新识别
|
transfer_type=settings.TRANSFER_TYPE,
|
||||||
if not mediainfo \
|
force_nfo=force_nfo,
|
||||||
or meta_info.name != dir_meta.name:
|
force_img=force_img)
|
||||||
# 优先读取本地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:
|
|
||||||
# 按TMDBID识别
|
|
||||||
logger.info(f"读取到本地nfo文件的tmdbid:{tmdbid}")
|
|
||||||
mediainfo = self.chain.recognize_media(tmdbid=tmdbid, mtype=meta_info.type)
|
|
||||||
else:
|
|
||||||
# 按名称识别
|
|
||||||
mediainfo = self.chain.recognize_media(meta=meta_info)
|
|
||||||
if not mediainfo:
|
|
||||||
logger.warn(f"未识别到媒体信息:{file}")
|
|
||||||
continue
|
|
||||||
|
|
||||||
# 如果未开启新增已入库媒体是否跟随TMDB信息变化则根据tmdbid查询之前的title
|
|
||||||
if not settings.SCRAP_FOLLOW_TMDB:
|
|
||||||
transfer_history = self.transferhis.get_by_type_tmdbid(tmdbid=mediainfo.tmdb_id,
|
|
||||||
mtype=mediainfo.type.value)
|
|
||||||
if transfer_history:
|
|
||||||
mediainfo.title = transfer_history.title
|
|
||||||
|
|
||||||
# 覆盖模式时,提前删除nfo
|
|
||||||
if self._mode in ["force_all", "force_nfo"]:
|
|
||||||
scrap_metadata = True
|
|
||||||
nfo_files = SystemUtils.list_files(path, [".nfo"])
|
|
||||||
for nfo_file in nfo_files:
|
|
||||||
try:
|
|
||||||
logger.warn(f"删除nfo文件:{nfo_file}")
|
|
||||||
nfo_file.unlink()
|
|
||||||
except Exception as err:
|
|
||||||
print(str(err))
|
|
||||||
|
|
||||||
# 覆盖模式时,提前删除图片文件
|
|
||||||
if self._mode in ["force_all", "force_image"]:
|
|
||||||
scrap_metadata = True
|
|
||||||
image_files = SystemUtils.list_files(path, [".jpg", ".png"])
|
|
||||||
for image_file in image_files:
|
|
||||||
if ".actors" in str(image_file):
|
|
||||||
continue
|
|
||||||
try:
|
|
||||||
logger.warn(f"删除图片文件:{image_file}")
|
|
||||||
image_file.unlink()
|
|
||||||
except Exception as err:
|
|
||||||
print(str(err))
|
|
||||||
|
|
||||||
# 刮削单个文件
|
|
||||||
if scrap_metadata:
|
|
||||||
self.chain.scrape_metadata(path=file, mediainfo=mediainfo, transfer_type=settings.TRANSFER_TYPE)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __get_tmdbid_from_nfo(file_path: Path):
|
def __get_tmdbid_from_nfo(file_path: Path):
|
||||||
|
|||||||
Reference in New Issue
Block a user