Merge pull request #45 from thsrite/main
This commit is contained in:
commit
9df5fabf92
@ -83,10 +83,20 @@ class TransferHistory(Base):
|
||||
return db.query(func.count(TransferHistory.id)).filter(TransferHistory.title.like(f'%{title}%')).first()[0]
|
||||
|
||||
@staticmethod
|
||||
def list_by(db: Session, mtype: str, title: str, year: int, season=None, episode=None):
|
||||
def list_by(db: Session, mtype: str = None, title: str = None, year: int = None, season: str = None,
|
||||
episode: str = None, tmdbid: str = None):
|
||||
"""
|
||||
据tmdbid、season、season_episode查询转移记录
|
||||
"""
|
||||
if tmdbid and not season and not episode:
|
||||
return db.query(TransferHistory).filter(TransferHistory.tmdbid == tmdbid).all()
|
||||
if tmdbid and season and not episode:
|
||||
return db.query(TransferHistory).filter(TransferHistory.tmdbid == tmdbid,
|
||||
TransferHistory.seasons == season).all()
|
||||
if tmdbid and season and episode:
|
||||
return db.query(TransferHistory).filter(TransferHistory.tmdbid == tmdbid,
|
||||
TransferHistory.seasons == season,
|
||||
TransferHistory.episodes == episode).all()
|
||||
# 电视剧所有季集|电影
|
||||
if not season and not episode:
|
||||
return db.query(TransferHistory).filter(TransferHistory.type == mtype,
|
||||
|
@ -43,8 +43,8 @@ class TransferHistoryOper(DbOper):
|
||||
"""
|
||||
return TransferHistory.statistic(self._db, days)
|
||||
|
||||
def get_by(self, mtype: str, title: str, year: int,
|
||||
season: str = None, episode: str = None) -> Any:
|
||||
def get_by(self, mtype: str = None, title: str = None, year: int = None,
|
||||
season: str = None, episode: str = None, tmdbid: str = None) -> Any:
|
||||
"""
|
||||
按类型、标题、年份、季集查询转移记录
|
||||
"""
|
||||
@ -53,7 +53,8 @@ class TransferHistoryOper(DbOper):
|
||||
title=title,
|
||||
year=year,
|
||||
season=season,
|
||||
episode=episode)
|
||||
episode=episode,
|
||||
tmdbid=tmdbid)
|
||||
|
||||
def delete(self, historyid):
|
||||
"""
|
||||
|
@ -2,6 +2,7 @@ import datetime
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import time
|
||||
from typing import List, Tuple, Dict, Any, Optional
|
||||
|
||||
@ -15,8 +16,10 @@ from app.db.transferhistory_oper import TransferHistoryOper
|
||||
from app.log import logger
|
||||
from app.modules.emby import Emby
|
||||
from app.modules.jellyfin import Jellyfin
|
||||
from app.modules.themoviedb.tmdbv3api import Episode
|
||||
from app.plugins import _PluginBase
|
||||
from app.schemas.types import NotificationType, EventType
|
||||
from app.utils.path_utils import PathUtils
|
||||
|
||||
|
||||
class MediaSyncDel(_PluginBase):
|
||||
@ -44,15 +47,17 @@ class MediaSyncDel(_PluginBase):
|
||||
# 私有属性
|
||||
_scheduler: Optional[BackgroundScheduler] = None
|
||||
_enabled = False
|
||||
_sync_type: str = ""
|
||||
_cron: str = ""
|
||||
_notify = False
|
||||
_del_source = False
|
||||
_exclude_path = None
|
||||
|
||||
_episode = None
|
||||
_transferhis = None
|
||||
|
||||
def init_plugin(self, config: dict = None):
|
||||
self._transferhis = TransferHistoryOper()
|
||||
self.episode = Episode()
|
||||
|
||||
# 停止现有任务
|
||||
self.stop_service()
|
||||
@ -60,16 +65,17 @@ class MediaSyncDel(_PluginBase):
|
||||
# 读取配置
|
||||
if config:
|
||||
self._enabled = config.get("enabled")
|
||||
self._sync_type = config.get("sync_type")
|
||||
self._cron = config.get("cron")
|
||||
self._notify = config.get("notify")
|
||||
self._del_source = config.get("del_source")
|
||||
self._exclude_path = config.get("exclude_path")
|
||||
|
||||
if self._enabled:
|
||||
if self._enabled and str(self._sync_type) == "log":
|
||||
self._scheduler = BackgroundScheduler(timezone=settings.TZ)
|
||||
if self._cron:
|
||||
try:
|
||||
self._scheduler.add_job(func=self.sync_del,
|
||||
self._scheduler.add_job(func=self.sync_del_by_log,
|
||||
trigger=CronTrigger.from_crontab(self._cron),
|
||||
name="媒体库同步删除")
|
||||
except Exception as err:
|
||||
@ -77,7 +83,7 @@ class MediaSyncDel(_PluginBase):
|
||||
# 推送实时消息
|
||||
self.systemmessage.put(f"执行周期配置错误:{err}")
|
||||
else:
|
||||
self._scheduler.add_job(self.sync_del, "interval", minutes=30, name="媒体库同步删除")
|
||||
self._scheduler.add_job(self.sync_del_by_log, "interval", minutes=30, name="媒体库同步删除")
|
||||
|
||||
# 启动任务
|
||||
if self._scheduler.get_jobs():
|
||||
@ -105,115 +111,150 @@ class MediaSyncDel(_PluginBase):
|
||||
拼装插件配置页面,需要返回两块数据:1、页面配置;2、数据结构
|
||||
"""
|
||||
return [
|
||||
{
|
||||
'component': 'VForm',
|
||||
'content': [
|
||||
{
|
||||
'component': 'VRow',
|
||||
'content': [
|
||||
{
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
'cols': 12,
|
||||
'md': 6
|
||||
},
|
||||
'content': [
|
||||
{
|
||||
'component': 'VSwitch',
|
||||
'props': {
|
||||
'model': 'enabled',
|
||||
'label': '启用插件',
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
'cols': 12,
|
||||
'md': 6
|
||||
},
|
||||
'content': [
|
||||
{
|
||||
'component': 'VSwitch',
|
||||
'props': {
|
||||
'model': 'notify',
|
||||
'label': '发送通知',
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'component': 'VRow',
|
||||
'content': [
|
||||
{
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
'cols': 12,
|
||||
'md': 6
|
||||
},
|
||||
'content': [
|
||||
{
|
||||
'component': 'VSwitch',
|
||||
'props': {
|
||||
'model': 'del_source',
|
||||
'label': '删除源文件',
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'component': 'VRow',
|
||||
'content': [
|
||||
{
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
'cols': 12,
|
||||
'md': 6
|
||||
},
|
||||
'content': [
|
||||
{
|
||||
'component': 'VTextField',
|
||||
'props': {
|
||||
'model': 'cron',
|
||||
'label': '执行周期',
|
||||
'placeholder': '5位cron表达式,留空自动'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
'cols': 12,
|
||||
'md': 6
|
||||
},
|
||||
'content': [
|
||||
{
|
||||
'component': 'VTextField',
|
||||
'props': {
|
||||
'model': 'exclude_path',
|
||||
'label': '排除路径'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
]
|
||||
}
|
||||
], {
|
||||
"enabled": False,
|
||||
"notify": True,
|
||||
"del_source": False,
|
||||
"cron": "*/30 * * * *",
|
||||
"exclude_path": "",
|
||||
}
|
||||
{
|
||||
'component': 'VForm',
|
||||
'content': [
|
||||
{
|
||||
'component': 'VRow',
|
||||
'content': [
|
||||
{
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
'cols': 12,
|
||||
'md': 4
|
||||
},
|
||||
'content': [
|
||||
{
|
||||
'component': 'VSwitch',
|
||||
'props': {
|
||||
'model': 'enabled',
|
||||
'label': '启用插件',
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
'cols': 12,
|
||||
'md': 4
|
||||
},
|
||||
'content': [
|
||||
{
|
||||
'component': 'VSwitch',
|
||||
'props': {
|
||||
'model': 'notify',
|
||||
'label': '发送通知',
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
'cols': 12,
|
||||
'md': 4
|
||||
},
|
||||
'content': [
|
||||
{
|
||||
'component': 'VSwitch',
|
||||
'props': {
|
||||
'model': 'del_source',
|
||||
'label': '删除源文件',
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'component': 'VRow',
|
||||
'content': [
|
||||
{
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
'cols': 12,
|
||||
'md': 4
|
||||
},
|
||||
'content': [
|
||||
{
|
||||
'component': 'VSelect',
|
||||
'props': {
|
||||
'model': 'sync_type',
|
||||
'label': '同步方式',
|
||||
'items': [
|
||||
{'title': '日志', 'value': 'log'},
|
||||
{'title': 'Scripter X', 'value': 'plugin'}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
'cols': 12,
|
||||
'md': 4
|
||||
},
|
||||
'content': [
|
||||
{
|
||||
'component': 'VTextField',
|
||||
'props': {
|
||||
'model': 'cron',
|
||||
'label': '执行周期',
|
||||
'placeholder': '5位cron表达式,留空自动'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
'cols': 12,
|
||||
'md': 4
|
||||
},
|
||||
'content': [
|
||||
{
|
||||
'component': 'VTextField',
|
||||
'props': {
|
||||
'model': 'exclude_path',
|
||||
'label': '排除路径'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'component': 'VRow',
|
||||
'content': [
|
||||
{
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
'cols': 12,
|
||||
},
|
||||
'content': [
|
||||
{
|
||||
'component': 'VAlert',
|
||||
'props': {
|
||||
'text': '同步方式分为日志同步和Scripter X。日志同步需要配置执行周期,默认30分钟执行一次。'
|
||||
'Scripter X方式需要emby安装并配置Scripter X插件,无需配置执行周期。'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
], {
|
||||
"enabled": False,
|
||||
"notify": True,
|
||||
"del_source": False,
|
||||
"sync_type": "log",
|
||||
"cron": "*/30 * * * *",
|
||||
"exclude_path": "",
|
||||
}
|
||||
|
||||
def get_page(self) -> List[dict]:
|
||||
"""
|
||||
@ -341,9 +382,178 @@ class MediaSyncDel(_PluginBase):
|
||||
}
|
||||
]
|
||||
|
||||
def sync_del(self):
|
||||
@eventmanager.register(EventType.WebhookMessage)
|
||||
def sync_del_by_plugin(self, event):
|
||||
"""
|
||||
emby删除媒体库同步删除历史记录
|
||||
Scripter X插件
|
||||
"""
|
||||
if not self._enabled:
|
||||
return
|
||||
event_data = event.event_data
|
||||
event_type = event_data.get("event_type")
|
||||
if not event_type or str(event_type) != 'media_del':
|
||||
return
|
||||
|
||||
# 是否虚拟标识
|
||||
item_isvirtual = event_data.get("item_isvirtual")
|
||||
if not item_isvirtual:
|
||||
logger.error("item_isvirtual参数未配置,为防止误删除,暂停插件运行")
|
||||
self.update_config({
|
||||
"enable": False,
|
||||
"del_source": self._del_source,
|
||||
"exclude_path": self._exclude_path,
|
||||
"notify": self._notify,
|
||||
"cron": self._cron,
|
||||
"sync_type": self._sync_type,
|
||||
})
|
||||
return
|
||||
|
||||
# 如果是虚拟item,则直接return,不进行删除
|
||||
if item_isvirtual == 'True':
|
||||
return
|
||||
|
||||
# 读取历史记录
|
||||
history = self.get_data('history') or []
|
||||
|
||||
# 媒体类型
|
||||
media_type = event_data.get("media_type")
|
||||
# 媒体名称
|
||||
media_name = event_data.get("media_name")
|
||||
# 媒体路径
|
||||
media_path = event_data.get("media_path")
|
||||
# tmdb_id
|
||||
tmdb_id = event_data.get("tmdb_id")
|
||||
# 季数
|
||||
season_num = event_data.get("season_num")
|
||||
if season_num and str(season_num).isdigit() and int(season_num) < 10:
|
||||
season_num = f'S0{season_num}'
|
||||
else:
|
||||
season_num = f'S{season_num}'
|
||||
# 集数
|
||||
episode_num = event_data.get("episode_num")
|
||||
if episode_num and str(episode_num).isdigit() and int(episode_num) < 10:
|
||||
episode_num = f'E0{episode_num}'
|
||||
else:
|
||||
episode_num = f'E{episode_num}'
|
||||
|
||||
if not media_type:
|
||||
logger.error(f"{media_name} 同步删除失败,未获取到媒体类型")
|
||||
return
|
||||
if not tmdb_id or not str(tmdb_id).isdigit():
|
||||
logger.error(f"{media_name} 同步删除失败,未获取到TMDB ID")
|
||||
return
|
||||
|
||||
if self._exclude_path and media_path and any(
|
||||
os.path.abspath(media_path).startswith(os.path.abspath(path)) for path in
|
||||
self._exclude_path.split(",")):
|
||||
logger.info(f"媒体路径 {media_path} 已被排除,暂不处理")
|
||||
return
|
||||
|
||||
# 删除电影
|
||||
if media_type == "Movie":
|
||||
msg = f'电影 {media_name} {tmdb_id}'
|
||||
transfer_history: List[TransferHistory] = self._transferhis.get_by(tmdbid=tmdb_id)
|
||||
# 删除电视剧
|
||||
elif media_type == "Series":
|
||||
msg = f'剧集 {media_name} {tmdb_id}'
|
||||
transfer_history: List[TransferHistory] = self._transferhis.get_by(tmdbid=tmdb_id)
|
||||
# 删除季 S02
|
||||
elif media_type == "Season":
|
||||
if not season_num or not str(season_num).isdigit():
|
||||
logger.error(f"{media_name} 季同步删除失败,未获取到具体季")
|
||||
return
|
||||
msg = f'剧集 {media_name} S{season_num} {tmdb_id}'
|
||||
transfer_history: List[TransferHistory] = self._transferhis.get_by(tmdbid=tmdb_id,
|
||||
season=season_num)
|
||||
# 删除剧集S02E02
|
||||
elif media_type == "Episode":
|
||||
if not season_num or not str(season_num).isdigit() or not episode_num or not str(episode_num).isdigit():
|
||||
logger.error(f"{media_name} 集同步删除失败,未获取到具体集")
|
||||
return
|
||||
msg = f'剧集 {media_name} S{season_num}E{episode_num} {tmdb_id}'
|
||||
transfer_history: List[TransferHistory] = self._transferhis.get_by(tmdbid=tmdb_id,
|
||||
season=season_num,
|
||||
episode=episode_num)
|
||||
else:
|
||||
return
|
||||
|
||||
logger.info(f"正在同步删除{msg}")
|
||||
|
||||
if not transfer_history:
|
||||
logger.warn(f"{media_type} {media_name} 未获取到可删除数据")
|
||||
return
|
||||
|
||||
# 开始删除
|
||||
del_cnt = 0
|
||||
image = 'https://emby.media/notificationicon.png'
|
||||
year = None
|
||||
for transferhis in transfer_history:
|
||||
image = transferhis.image
|
||||
year = transferhis.year
|
||||
if media_type == "Episode" or media_type == "Movie":
|
||||
# 如果有剧集或者电影有多个版本的话,需要根据名称筛选下要删除的版本
|
||||
if os.path.basename(transferhis.dest) != os.path.basename(media_path):
|
||||
continue
|
||||
self._transferhis.delete(transferhis.id)
|
||||
del_cnt += 1
|
||||
# 删除种子任务
|
||||
if self._del_source:
|
||||
del_source = False
|
||||
if transferhis.download_hash:
|
||||
try:
|
||||
self.chain.remove_torrents(transferhis.download_hash)
|
||||
except Exception as e:
|
||||
logger.error("删除种子失败,尝试删除源文件:%s" % str(e))
|
||||
del_source = True
|
||||
|
||||
# 直接删除源文件
|
||||
if del_source:
|
||||
source_name = os.path.basename(transferhis.src)
|
||||
source_path = str(transferhis.src).replace(source_name, "")
|
||||
self.delete_media_file(filedir=source_path,
|
||||
filename=source_name)
|
||||
|
||||
logger.info(f"同步删除 {msg} 完成!")
|
||||
|
||||
# 发送消息
|
||||
if self._notify:
|
||||
if media_type == "Episode":
|
||||
# 根据tmdbid获取图片
|
||||
image = self._episode().images(tv_id=tmdb_id,
|
||||
season_id=season_num,
|
||||
episode_id=episode_num,
|
||||
orginal=True)
|
||||
# 发送通知
|
||||
self.post_message(
|
||||
mtype=NotificationType.MediaServer,
|
||||
title="媒体库同步删除任务完成",
|
||||
image=image,
|
||||
text=f"{msg}\n"
|
||||
f"数量 {del_cnt}\n"
|
||||
f"时间 {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time()))}"
|
||||
)
|
||||
|
||||
history.append({
|
||||
"type": "电影" if media_type == "Movie" else "电视剧",
|
||||
"title": media_name,
|
||||
"year": year,
|
||||
"path": media_path,
|
||||
"season": season_num,
|
||||
"episode": episode_num,
|
||||
"image": image,
|
||||
"del_time": str(datetime.datetime.now())
|
||||
})
|
||||
|
||||
# 保存历史
|
||||
self.save_data("history", history)
|
||||
|
||||
self.save_data("last_time", datetime.datetime.now())
|
||||
|
||||
def sync_del_by_log(self):
|
||||
"""
|
||||
emby删除媒体库同步删除历史记录
|
||||
日志方式
|
||||
"""
|
||||
# 读取历史记录
|
||||
history = self.get_data('history') or []
|
||||
@ -437,8 +647,21 @@ class MediaSyncDel(_PluginBase):
|
||||
image = transferhis.image
|
||||
self._transferhis.delete(transferhis.id)
|
||||
# 删除种子任务
|
||||
if self._del_source and transferhis.download_hash:
|
||||
self.chain.remove_torrents(transferhis.download_hash)
|
||||
if self._del_source:
|
||||
del_source = False
|
||||
if transferhis.download_hash:
|
||||
try:
|
||||
self.chain.remove_torrents(transferhis.download_hash)
|
||||
except Exception as e:
|
||||
logger.error("删除种子失败,尝试删除源文件:%s" % str(e))
|
||||
del_source = True
|
||||
|
||||
# 直接删除源文件
|
||||
if del_source:
|
||||
source_name = os.path.basename(transferhis.src)
|
||||
source_path = str(transferhis.src).replace(source_name, "")
|
||||
self.delete_media_file(filedir=source_path,
|
||||
filename=source_name)
|
||||
|
||||
logger.info(f"同步删除 {msg} 完成!")
|
||||
|
||||
@ -613,6 +836,42 @@ class MediaSyncDel(_PluginBase):
|
||||
|
||||
return del_medias
|
||||
|
||||
@staticmethod
|
||||
def delete_media_file(filedir, filename):
|
||||
"""
|
||||
删除媒体文件,空目录也会被删除
|
||||
"""
|
||||
filedir = os.path.normpath(filedir).replace("\\", "/")
|
||||
file = os.path.join(filedir, filename)
|
||||
try:
|
||||
if not os.path.exists(file):
|
||||
return False, f"{file} 不存在"
|
||||
os.remove(file)
|
||||
nfoname = f"{os.path.splitext(filename)[0]}.nfo"
|
||||
nfofile = os.path.join(filedir, nfoname)
|
||||
if os.path.exists(nfofile):
|
||||
os.remove(nfofile)
|
||||
# 检查空目录并删除
|
||||
if re.findall(r"^S\d{2}|^Season", os.path.basename(filedir), re.I):
|
||||
# 当前是季文件夹,判断并删除
|
||||
seaon_dir = filedir
|
||||
if seaon_dir.count('/') > 1 and not PathUtils.get_dir_files(seaon_dir, exts=settings.RMT_MEDIAEXT):
|
||||
shutil.rmtree(seaon_dir)
|
||||
# 媒体文件夹
|
||||
media_dir = os.path.dirname(seaon_dir)
|
||||
else:
|
||||
media_dir = filedir
|
||||
# 检查并删除媒体文件夹,非根目录且目录大于二级,且没有媒体文件时才会删除
|
||||
if media_dir != '/' \
|
||||
and media_dir.count('/') > 1 \
|
||||
and not re.search(r'[a-zA-Z]:/$', media_dir) \
|
||||
and not PathUtils.get_dir_files(media_dir, exts=settings.RMT_MEDIAEXT):
|
||||
shutil.rmtree(media_dir)
|
||||
return True, f"{file} 删除成功"
|
||||
except Exception as e:
|
||||
logger.error("删除源文件失败:%s" % str(e))
|
||||
return True, f"{file} 删除失败"
|
||||
|
||||
def get_state(self):
|
||||
return self._enabled
|
||||
|
||||
@ -639,7 +898,7 @@ class MediaSyncDel(_PluginBase):
|
||||
self.post_message(channel=event.event_data.get("channel"),
|
||||
title="开始媒体库同步删除 ...",
|
||||
userid=event.event_data.get("user"))
|
||||
self.sync_del()
|
||||
self.sync_del_by_log()
|
||||
|
||||
if event:
|
||||
self.post_message(channel=event.event_data.get("channel"),
|
||||
|
155
app/utils/path_utils.py
Normal file
155
app/utils/path_utils.py
Normal file
@ -0,0 +1,155 @@
|
||||
import os
|
||||
|
||||
|
||||
class PathUtils:
|
||||
|
||||
@staticmethod
|
||||
def get_dir_files(in_path, exts="", filesize=0, episode_format=None):
|
||||
"""
|
||||
获得目录下的媒体文件列表List ,按后缀、大小、格式过滤
|
||||
"""
|
||||
if not in_path:
|
||||
return []
|
||||
if not os.path.exists(in_path):
|
||||
return []
|
||||
ret_list = []
|
||||
if os.path.isdir(in_path):
|
||||
for root, dirs, files in os.walk(in_path):
|
||||
for file in files:
|
||||
cur_path = os.path.join(root, file)
|
||||
# 检查路径是否合法
|
||||
if PathUtils.is_invalid_path(cur_path):
|
||||
continue
|
||||
# 检查格式匹配
|
||||
if episode_format and not episode_format.match(file):
|
||||
continue
|
||||
# 检查后缀
|
||||
if exts and os.path.splitext(file)[-1].lower() not in exts:
|
||||
continue
|
||||
# 检查文件大小
|
||||
if filesize and os.path.getsize(cur_path) < filesize:
|
||||
continue
|
||||
# 命中
|
||||
if cur_path not in ret_list:
|
||||
ret_list.append(cur_path)
|
||||
else:
|
||||
# 检查路径是否合法
|
||||
if PathUtils.is_invalid_path(in_path):
|
||||
return []
|
||||
# 检查后缀
|
||||
if exts and os.path.splitext(in_path)[-1].lower() not in exts:
|
||||
return []
|
||||
# 检查格式
|
||||
if episode_format and not episode_format.match(os.path.basename(in_path)):
|
||||
return []
|
||||
# 检查文件大小
|
||||
if filesize and os.path.getsize(in_path) < filesize:
|
||||
return []
|
||||
ret_list.append(in_path)
|
||||
return ret_list
|
||||
|
||||
@staticmethod
|
||||
def get_dir_level1_files(in_path, exts=""):
|
||||
"""
|
||||
查询目录下的文件(只查询一级)
|
||||
"""
|
||||
ret_list = []
|
||||
if not os.path.exists(in_path):
|
||||
return []
|
||||
for file in os.listdir(in_path):
|
||||
path = os.path.join(in_path, file)
|
||||
if os.path.isfile(path):
|
||||
if not exts or os.path.splitext(file)[-1].lower() in exts:
|
||||
ret_list.append(path)
|
||||
return ret_list
|
||||
|
||||
@staticmethod
|
||||
def get_dir_level1_medias(in_path, exts=""):
|
||||
"""
|
||||
根据后缀,返回目录下所有的文件及文件夹列表(只查询一级)
|
||||
"""
|
||||
ret_list = []
|
||||
if not os.path.exists(in_path):
|
||||
return []
|
||||
if os.path.isdir(in_path):
|
||||
for file in os.listdir(in_path):
|
||||
path = os.path.join(in_path, file)
|
||||
if os.path.isfile(path):
|
||||
if not exts or os.path.splitext(file)[-1].lower() in exts:
|
||||
ret_list.append(path)
|
||||
else:
|
||||
ret_list.append(path)
|
||||
else:
|
||||
ret_list.append(in_path)
|
||||
return ret_list
|
||||
|
||||
@staticmethod
|
||||
def is_invalid_path(path):
|
||||
"""
|
||||
判断是否不能处理的路径
|
||||
"""
|
||||
if not path:
|
||||
return True
|
||||
if path.find('/@Recycle/') != -1 or path.find('/#recycle/') != -1 or path.find('/.') != -1 or path.find(
|
||||
'/@eaDir') != -1:
|
||||
return True
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def is_path_in_path(path1, path2):
|
||||
"""
|
||||
判断两个路径是否包含关系 path1 in path2
|
||||
"""
|
||||
if not path1 or not path2:
|
||||
return False
|
||||
path1 = os.path.normpath(path1).replace("\\", "/")
|
||||
path2 = os.path.normpath(path2).replace("\\", "/")
|
||||
if path1 == path2:
|
||||
return True
|
||||
path = os.path.dirname(path2)
|
||||
while True:
|
||||
if path == path1:
|
||||
return True
|
||||
path = os.path.dirname(path)
|
||||
if path == os.path.dirname(path):
|
||||
break
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def get_bluray_dir(path):
|
||||
"""
|
||||
判断是否蓝光原盘目录,是则返回原盘的根目录,否则返回空
|
||||
"""
|
||||
if not path or not os.path.exists(path):
|
||||
return None
|
||||
if os.path.isdir(path):
|
||||
if os.path.exists(os.path.join(path, "BDMV", "index.bdmv")):
|
||||
return path
|
||||
elif os.path.normpath(path).endswith("BDMV") \
|
||||
and os.path.exists(os.path.join(path, "index.bdmv")):
|
||||
return os.path.dirname(path)
|
||||
elif os.path.normpath(path).endswith("STREAM") \
|
||||
and os.path.exists(os.path.join(os.path.dirname(path), "index.bdmv")):
|
||||
return PathUtils.get_parent_paths(path, 2)
|
||||
else:
|
||||
# 电视剧原盘下会存在多个目录形如:Spider Man 2021/DIsc1, Spider Man 2021/Disc2
|
||||
for level1 in PathUtils.get_dir_level1_medias(path):
|
||||
if os.path.exists(os.path.join(level1, "BDMV", "index.bdmv")):
|
||||
return path
|
||||
return None
|
||||
else:
|
||||
if str(os.path.splitext(path)[-1]).lower() in [".m2ts", ".ts"] \
|
||||
and os.path.normpath(os.path.dirname(path)).endswith("STREAM") \
|
||||
and os.path.exists(os.path.join(PathUtils.get_parent_paths(path, 2), "index.bdmv")):
|
||||
return PathUtils.get_parent_paths(path, 3)
|
||||
else:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def get_parent_paths(path, level: int = 1):
|
||||
"""
|
||||
获取父目录路径,level为向上查找的层数
|
||||
"""
|
||||
for lv in range(0, level):
|
||||
path = os.path.dirname(path)
|
||||
return path
|
Loading…
x
Reference in New Issue
Block a user