feat:目录结构重大调整,谨慎更新到dev

This commit is contained in:
jxxghp
2024-05-22 20:02:14 +08:00
parent f1f187fc77
commit d9c6375252
17 changed files with 359 additions and 245 deletions

View File

@ -1,3 +1,4 @@
from pathlib import Path
from typing import Any, List, Optional
from fastapi import APIRouter, Depends
@ -5,10 +6,10 @@ from sqlalchemy.orm import Session
from app import schemas
from app.chain.dashboard import DashboardChain
from app.core.config import settings
from app.core.security import verify_token, verify_uri_token
from app.db import get_db
from app.db.models.transferhistory import TransferHistory
from app.helper.directory import DirectoryHelper
from app.scheduler import Scheduler
from app.utils.system import SystemUtils
@ -47,7 +48,8 @@ def storage(_: schemas.TokenPayload = Depends(verify_token)) -> Any:
"""
查询存储空间信息
"""
total_storage, free_storage = SystemUtils.space_usage(settings.LIBRARY_PATHS)
library_dirs = DirectoryHelper().get_library_dirs()
total_storage, free_storage = SystemUtils.space_usage([Path(d.path) for d in library_dirs if d.path])
return schemas.Storage(
total_storage=total_storage,
used_storage=total_storage - free_storage
@ -75,6 +77,10 @@ def downloader(_: schemas.TokenPayload = Depends(verify_token)) -> Any:
"""
查询下载器信息
"""
# 下载目录空间
download_dirs = DirectoryHelper().get_download_dirs()
_, free_space = SystemUtils.space_usage([Path(d.path) for d in download_dirs if d.path])
# 下载器信息
downloader_info = schemas.DownloaderInfo()
transfer_infos = DashboardChain().downloader_info()
if transfer_infos:
@ -83,7 +89,7 @@ def downloader(_: schemas.TokenPayload = Depends(verify_token)) -> Any:
downloader_info.upload_speed += transfer_info.upload_speed
downloader_info.download_size += transfer_info.download_size
downloader_info.upload_size += transfer_info.upload_size
downloader_info.free_space = SystemUtils.free_space(settings.SAVE_PATH)
downloader_info.free_space = free_space
return downloader_info

View File

@ -6,7 +6,6 @@ from sqlalchemy.orm import Session
from app import schemas
from app.chain.media import MediaChain
from app.chain.subscribe import SubscribeChain
from app.core.config import settings
from app.core.metainfo import MetaInfo
from app.core.security import verify_uri_apikey
from app.db import get_db
@ -121,7 +120,7 @@ def arr_rootfolder(_: str = Depends(verify_uri_apikey)) -> Any:
return [
{
"id": 1,
"path": "/" if not settings.LIBRARY_PATHS else str(settings.LIBRARY_PATHS[0]),
"path": "/",
"accessible": True,
"freeSpace": 0,
"unmappedFolders": []

View File

@ -14,6 +14,8 @@ from app.core.meta import MetaBase
from app.core.metainfo import MetaInfo
from app.db.downloadhistory_oper import DownloadHistoryOper
from app.db.mediaserver_oper import MediaServerOper
from app.helper.directory import DirectoryHelper
from app.helper.message import MessageHelper
from app.helper.torrent import TorrentHelper
from app.log import logger
from app.schemas import ExistMediaInfo, NotExistMediaInfo, DownloadingTorrent, Notification
@ -32,6 +34,8 @@ class DownloadChain(ChainBase):
self.torrent = TorrentHelper()
self.downloadhis = DownloadHistoryOper()
self.mediaserver = MediaServerOper()
self.directoryhelper = DirectoryHelper()
self.messagehelper = MessageHelper()
def post_download_message(self, meta: MetaBase, mediainfo: MediaInfo, torrent: TorrentInfo,
channel: MessageChannel = None, userid: str = None, username: str = None,
@ -227,35 +231,23 @@ class DownloadChain(ChainBase):
# 下载目录
if not save_path:
if settings.DOWNLOAD_CATEGORY and _media and _media.category:
# 获取下载目录
dir_info = self.directoryhelper.get_download_dir(_media)
if not dir_info or not dir_info.path:
logger.error(f"未找到下载目录:{_media.type.value} {_media.title_year}")
self.messagehelper.put(f"{_media.type.value} {_media.title_year} 未找到下载目录!",
title="下载失败", role="system")
return None
if (dir_info.category or dir_info.auto_category) and _media and _media.category:
# 开启下载二级目录
if _media.type != MediaType.TV:
# 电影
download_dir = settings.SAVE_MOVIE_PATH / _media.category
else:
if _media.genre_ids \
and set(_media.genre_ids).intersection(set(settings.ANIME_GENREIDS)):
# 动漫
download_dir = settings.SAVE_ANIME_PATH / _media.category
else:
# 电视剧
download_dir = settings.SAVE_TV_PATH / _media.category
download_dir = Path(dir_info.path) / _media.category
elif _media:
# 未开启下载二级目录
if _media.type != MediaType.TV:
# 电影
download_dir = settings.SAVE_MOVIE_PATH
else:
if _media.genre_ids \
and set(_media.genre_ids).intersection(set(settings.ANIME_GENREIDS)):
# 动漫
download_dir = settings.SAVE_ANIME_PATH
else:
# 电视剧
download_dir = settings.SAVE_TV_PATH
download_dir = Path(dir_info.path)
else:
# 未识别
download_dir = settings.SAVE_PATH
download_dir = Path(dir_info.path)
else:
# 自定义下载目录
download_dir = Path(save_path)

View File

@ -34,7 +34,7 @@ class MediaChain(ChainBase, metaclass=Singleton):
# 识别媒体信息
mediainfo: MediaInfo = self.recognize_media(meta=metainfo)
if not mediainfo:
# 试使用辅助识别,如果有注册响应事件的话
# 试使用辅助识别,如果有注册响应事件的话
if eventmanager.check(EventType.NameRecognize):
logger.info(f'请求辅助识别,标题:{title} ...')
mediainfo = self.recognize_help(title=title, org_meta=metainfo)
@ -143,7 +143,7 @@ class MediaChain(ChainBase, metaclass=Singleton):
# 识别媒体信息
mediainfo = self.recognize_media(meta=file_meta)
if not mediainfo:
# 试使用辅助识别,如果有注册响应事件的话
# 试使用辅助识别,如果有注册响应事件的话
if eventmanager.check(EventType.NameRecognize):
logger.info(f'请求辅助识别,标题:{file_path.name} ...')
mediainfo = self.recognize_help(title=path, org_meta=file_meta)

View File

@ -600,11 +600,11 @@ class SubscribeChain(ChainBase):
# 先判断是否有没识别的种子
if not torrent_mediainfo or (not torrent_mediainfo.tmdb_id and not torrent_mediainfo.douban_id):
logger.info(f'{torrent_info.site_name} - {torrent_info.title} 订阅缓存为未识别状态,试重新识别...')
logger.info(f'{torrent_info.site_name} - {torrent_info.title} 订阅缓存为未识别状态,试重新识别...')
# 重新识别(不使用缓存)
torrent_mediainfo = self.recognize_media(meta=torrent_meta, cache=False)
if not torrent_mediainfo:
logger.warn(f'{torrent_info.site_name} - {torrent_info.title} 重新识别失败,试通过标题匹配...')
logger.warn(f'{torrent_info.site_name} - {torrent_info.title} 重新识别失败,试通过标题匹配...')
if self.torrenthelper.match_torrent(mediainfo=mediainfo,
torrent_meta=torrent_meta,
torrent=torrent_info):

View File

@ -357,7 +357,7 @@ class TransferChain(ChainBase):
transferinfo=transferinfo
)
# 刮削单个文件
if settings.SCRAP_METADATA:
if transferinfo.need_scrape:
self.scrape_metadata(path=transferinfo.target_path,
mediainfo=file_mediainfo,
transfer_type=transfer_type,

View File

@ -2,7 +2,7 @@ import secrets
import sys
import threading
from pathlib import Path
from typing import List, Optional
from typing import Optional
from pydantic import BaseSettings, validator
@ -55,8 +55,6 @@ class Settings(BaseSettings):
RECOGNIZE_SOURCE: str = "themoviedb"
# 刮削来源 themoviedb/douban
SCRAP_SOURCE: str = "themoviedb"
# 刮削入库的媒体文件
SCRAP_METADATA: bool = True
# 新增已入库媒体是否跟随TMDB信息变化
SCRAP_FOLLOW_TMDB: bool = True
# TMDB图片地址
@ -159,16 +157,6 @@ class Settings(BaseSettings):
TR_PASSWORD: Optional[str] = None
# 种子标签
TORRENT_TAG: str = "MOVIEPILOT"
# 下载保存目录,容器内映射路径需要一致
DOWNLOAD_PATH: Optional[str] = None
# 电影下载保存目录,容器内映射路径需要一致
DOWNLOAD_MOVIE_PATH: Optional[str] = None
# 电视剧下载保存目录,容器内映射路径需要一致
DOWNLOAD_TV_PATH: Optional[str] = None
# 动漫下载保存目录,容器内映射路径需要一致
DOWNLOAD_ANIME_PATH: Optional[str] = None
# 下载目录二级分类
DOWNLOAD_CATEGORY: bool = False
# 下载站点字幕
DOWNLOAD_SUBTITLE: bool = True
# 媒体服务器 emby/jellyfin/plex多个媒体服务器,分割
@ -211,16 +199,6 @@ class Settings(BaseSettings):
OCR_HOST: str = "https://movie-pilot.org"
# CookieCloud对应的浏览器UA
USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.57"
# 媒体库目录,多个目录使用,分隔
LIBRARY_PATH: Optional[str] = None
# 电影媒体库目录名
LIBRARY_MOVIE_NAME: str = "电影"
# 电视剧媒体库目录名
LIBRARY_TV_NAME: str = "电视剧"
# 动漫媒体库目录名,不设置时使用电视剧目录
LIBRARY_ANIME_NAME: Optional[str] = None
# 二级分类
LIBRARY_CATEGORY: bool = True
# 电视剧动漫的分类genre_ids
ANIME_GENREIDS = [16]
# 电影重命名格式
@ -255,6 +233,29 @@ class Settings(BaseSettings):
# 服务器地址,对应 https://github.com/jxxghp/MoviePilot-Server 项目
MP_SERVER_HOST: str = "https://movie-pilot.org"
# 【已弃用】刮削入库的媒体文件
SCRAP_METADATA: bool = True
# 【已弃用】下载保存目录,容器内映射路径需要一致
DOWNLOAD_PATH: Optional[str] = None
# 【已弃用】电影下载保存目录,容器内映射路径需要一致
DOWNLOAD_MOVIE_PATH: Optional[str] = None
# 【已弃用】电视剧下载保存目录,容器内映射路径需要一致
DOWNLOAD_TV_PATH: Optional[str] = None
# 【已弃用】动漫下载保存目录,容器内映射路径需要一致
DOWNLOAD_ANIME_PATH: Optional[str] = None
# 【已弃用】下载目录二级分类
DOWNLOAD_CATEGORY: bool = False
# 【已弃用】媒体库目录,多个目录使用,分隔
LIBRARY_PATH: Optional[str] = None
# 【已弃用】电影媒体库目录名
LIBRARY_MOVIE_NAME: str = "电影"
# 【已弃用】电视剧媒体库目录名
LIBRARY_TV_NAME: str = "电视剧"
# 【已弃用】动漫媒体库目录名,不设置时使用电视剧目录
LIBRARY_ANIME_NAME: Optional[str] = None
# 【已弃用】二级分类
LIBRARY_CATEGORY: bool = True
@validator("SUBSCRIBE_RSS_INTERVAL",
"COOKIECLOUD_INTERVAL",
"MEDIASERVER_SYNC_INTERVAL",
@ -338,48 +339,6 @@ class Settings(BaseSettings):
"server": self.PROXY_HOST
}
@property
def LIBRARY_PATHS(self) -> List[Path]:
if self.LIBRARY_PATH:
return [Path(path) for path in self.LIBRARY_PATH.split(",")]
return [self.CONFIG_PATH / "library"]
@property
def SAVE_PATH(self) -> Path:
"""
获取下载保存目录
"""
if self.DOWNLOAD_PATH:
return Path(self.DOWNLOAD_PATH)
return self.CONFIG_PATH / "downloads"
@property
def SAVE_MOVIE_PATH(self) -> Path:
"""
获取电影下载保存目录
"""
if self.DOWNLOAD_MOVIE_PATH:
return Path(self.DOWNLOAD_MOVIE_PATH)
return self.SAVE_PATH
@property
def SAVE_TV_PATH(self) -> Path:
"""
获取电视剧下载保存目录
"""
if self.DOWNLOAD_TV_PATH:
return Path(self.DOWNLOAD_TV_PATH)
return self.SAVE_PATH
@property
def SAVE_ANIME_PATH(self) -> Path:
"""
获取动漫下载保存目录
"""
if self.DOWNLOAD_ANIME_PATH:
return Path(self.DOWNLOAD_ANIME_PATH)
return self.SAVE_TV_PATH
@property
def GITHUB_HEADERS(self):
"""

View File

@ -1,5 +1,4 @@
import re
from pathlib import Path
from typing import Optional
from Pinyin2Hanzi import is_pinyin
@ -138,7 +137,7 @@ class MetaVideo(MetaBase):
# 处理part
if self.part and self.part.upper() == "PART":
self.part = None
# 没有中文标题时,试中描述中获取中文名
# 没有中文标题时,试中描述中获取中文名
if not self.cn_name and self.en_name and self.subtitle:
if self.__is_pinyin(self.en_name):
# 英文名是拼音

View File

@ -1,4 +1,3 @@
import logging
from pathlib import Path
from typing import Tuple

View File

@ -7,4 +7,4 @@ from .subscribe import Subscribe
from .systemconfig import SystemConfig
from .transferhistory import TransferHistory
from .user import User
from .userconfig import UserConfig
from .userconfig import UserConfig

93
app/helper/directory.py Normal file
View File

@ -0,0 +1,93 @@
from typing import List, Optional
from app import schemas
from app.core.config import settings
from app.core.context import MediaInfo
from app.db.systemconfig_oper import SystemConfigOper
from app.schemas.types import SystemConfigKey, MediaType
class DirectoryHelper:
"""
下载目录/媒体库目录帮助类
"""
def __init__(self):
self.systemconfig = SystemConfigOper()
def get_download_dirs(self) -> List[schemas.MediaDirectory]:
"""
获取下载目录
"""
dir_conf: List[dict] = self.systemconfig.get(SystemConfigKey.DownloadDirectories)
if not dir_conf:
return []
return [schemas.MediaDirectory(**d) for d in dir_conf]
def get_library_dirs(self) -> List[schemas.MediaDirectory]:
"""
获取媒体库目录
"""
dir_conf: List[dict] = self.systemconfig.get(SystemConfigKey.LibraryDirectories)
if not dir_conf:
return []
return [schemas.MediaDirectory(**d) for d in dir_conf]
def get_download_dir(self, media: MediaInfo = None) -> Optional[schemas.MediaDirectory]:
"""
根据媒体信息获取下载目录
:param media: 媒体信息
"""
media_dirs = self.get_download_dirs()
# 按照配置顺序查找(保存后的数据已经排序)
for media_dir in media_dirs:
# 没有媒体信息时,返回第一个类型为全部的目录
if (not media or media.type == MediaType.UNKNOWN) and not media_dir.media_type:
return media_dir
# 目录类型为全部的,符合条件
if not media_dir.media_type:
return media_dir
# 处理类型
if media.genre_ids \
and set(media.genre_ids).intersection(set(settings.ANIME_GENREIDS)):
media_type = "动漫"
else:
media_type = media.type.value
# 目录类型相等,目录类别为全部,符合条件
if media_dir.media_type == media_type and not media_dir.category:
return media_dir
# 目录类型相等,目录类别相等,符合条件
if media_dir.media_type == media_type and media_dir.category == media.category:
return media_dir
return None
def get_library_dir(self, media: MediaInfo = None) -> Optional[schemas.MediaDirectory]:
"""
根据媒体信息获取媒体库目录
:param media: 媒体信息
"""
library_dirs = self.get_library_dirs()
# 按照配置顺序查找(保存后的数据已经排序)
for library_dir in library_dirs:
# 没有媒体信息时,返回第一个类型为全部的目录
if (not media or media.type == MediaType.UNKNOWN) and not library_dir.media_type:
return library_dir
# 目录类型为全部的,符合条件
if not library_dir.media_type:
return library_dir
# 处理类型
if media.genre_ids \
and set(media.genre_ids).intersection(set(settings.ANIME_GENREIDS)):
media_type = "动漫"
else:
media_type = media.type.value
# 目录类型相等,目录类别为全部,符合条件
if library_dir.media_type == media_type and not library_dir.category:
return library_dir
# 目录类型相等,目录类别相等,符合条件
if library_dir.media_type == media_type and library_dir.category == media.category:
return library_dir
return None

View File

@ -9,17 +9,26 @@ from app.core.config import settings
from app.core.context import MediaInfo
from app.core.meta import MetaBase
from app.core.metainfo import MetaInfo, MetaInfoPath
from app.helper.directory import DirectoryHelper
from app.helper.message import MessageHelper
from app.log import logger
from app.modules import _ModuleBase
from app.schemas import TransferInfo, ExistMediaInfo, TmdbEpisode
from app.schemas import TransferInfo, ExistMediaInfo, TmdbEpisode, MediaDirectory
from app.schemas.types import MediaType
from app.utils.string import StringUtils
from app.utils.system import SystemUtils
lock = Lock()
class FileTransferModule(_ModuleBase):
"""
文件整理模块
"""
def __init__(self):
super().__init__()
self.directoryhelper = DirectoryHelper()
self.messagehelper = MessageHelper()
def init_module(self) -> None:
pass
@ -35,34 +44,37 @@ class FileTransferModule(_ModuleBase):
"""
测试模块连接性
"""
if not settings.DOWNLOAD_PATH:
return False, "下载目录未设置"
directoryhelper = DirectoryHelper()
# 检查下载目录
download_paths: List[str] = []
for path in [settings.DOWNLOAD_PATH,
settings.DOWNLOAD_MOVIE_PATH,
settings.DOWNLOAD_TV_PATH,
settings.DOWNLOAD_ANIME_PATH]:
download_paths = directoryhelper.get_download_dirs()
if not download_paths:
return False, "下载目录未设置"
for d_path in download_paths:
path = d_path.path
if not path:
continue
return False, f"下载目录 {d_path.name} 对应路径未设置"
download_path = Path(path)
if not download_path.exists():
return False, f"下载目录 {download_path} 不存在"
download_paths.append(path)
# 下载目录的设备ID
download_devids = [Path(path).stat().st_dev for path in download_paths]
return False, f"下载目录 {d_path.name} 对应路径 {path} 不存在"
# 检查媒体库目录
if not settings.LIBRARY_PATH:
libaray_dirs = directoryhelper.get_library_dirs()
if not libaray_dirs:
return False, "媒体库目录未设置"
# 比较媒体库目录的设备ID
for path in settings.LIBRARY_PATHS:
for l_path in libaray_dirs:
path = l_path.path
if not path:
return False, f"媒体库目录 {l_path.name} 对应路径未设置"
library_path = Path(path)
if not library_path.exists():
return False, f"媒体库目录不存在:{library_path}"
return False, f"媒体库目录{l_path.name} 对应的路径 {path} 不存在"
if settings.DOWNLOADER_MONITOR and settings.TRANSFER_TYPE == "link":
if library_path.stat().st_dev not in download_devids:
return False, f"媒体库目录 {library_path} " \
f"与下载目录 {','.join(download_paths)} 不在同一设备,将无法硬链接"
for d_path in download_paths:
download_path = Path(d_path.path)
if l_path.media_type == d_path.media_type and l_path.category == d_path.category:
if library_path.stat().st_dev != download_path.stat().st_dev:
return False, f"媒体库目录 {library_path} " \
f"与下载目录 {download_path} 不在同一设备,将无法硬链接"
return True, ""
def init_setting(self) -> Tuple[str, Union[str, bool]]:
@ -83,10 +95,16 @@ class FileTransferModule(_ModuleBase):
"""
# 获取目标路径
if not target:
# 未指定目的目录,根据源目录选择一个媒体库
target = self.get_target_path(in_path=path)
# 未指定目的目录,选择一个媒体库
dir_info = DirectoryHelper().get_library_dir(mediainfo)
if not dir_info or not dir_info.path:
logger.error(f"{mediainfo.type.value} {mediainfo.title_year} 未找到有效的媒体库目录,无法转移文件,源路径:{path}")
return TransferInfo(success=False,
path=path,
message="未找到有效的媒体库目录")
# 拼装媒体库一、二级子目录
target = self.__get_dest_dir(mediainfo=mediainfo, target_dir=target)
need_scrape = dir_info.scrape
target = self.__get_dest_dir(mediainfo=mediainfo, target_dir=dir_info)
else:
# 指定了目的目录
if target.is_file():
@ -94,24 +112,18 @@ class FileTransferModule(_ModuleBase):
return TransferInfo(success=False,
path=path,
message=f"{target} 不是有效目录")
# 只拼装二级子目录(不要一级目录)
target = self.__get_dest_dir(mediainfo=mediainfo, target_dir=target, typename_dir=False)
if not target:
logger.error("未找到媒体库目录,无法转移文件")
return TransferInfo(success=False,
path=path,
message="未找到媒体库目录")
else:
logger.info(f"获取转移目标路径:{target}")
# FIXME 指定了目的目录时,拿不到是否需要刮削的配置了
need_scrape = False
logger.info(f"获取转移目标路径:{target}")
# 转移
return self.transfer_media(in_path=path,
in_meta=meta,
mediainfo=mediainfo,
transfer_type=transfer_type,
target_dir=target,
episodes_info=episodes_info)
episodes_info=episodes_info,
need_scrape=need_scrape)
@staticmethod
def __transfer_command(file_item: Path, target_file: Path, transfer_type: str) -> int:
@ -384,43 +396,17 @@ class FileTransferModule(_ModuleBase):
over_flag=over_flag)
@staticmethod
def __get_dest_dir(mediainfo: MediaInfo, target_dir: Path, typename_dir: bool = True) -> Path:
def __get_dest_dir(mediainfo: MediaInfo, target_dir: MediaDirectory) -> Path:
"""
根据设置并装媒体库目录
:param mediainfo: 媒体信息
:target_dir: 媒体库根目录
:typename_dir: 是否加上类型目录
"""
if not target_dir:
return target_dir
if mediainfo.type == MediaType.MOVIE:
# 电影
if typename_dir:
# 目的目录加上类型和二级分类
target_dir = target_dir / settings.LIBRARY_MOVIE_NAME / mediainfo.category
else:
# 目的目录加上二级分类
target_dir = target_dir / mediainfo.category
if mediainfo.type == MediaType.TV:
# 电视剧
if mediainfo.genre_ids \
and set(mediainfo.genre_ids).intersection(set(settings.ANIME_GENREIDS)):
# 动漫
if typename_dir:
target_dir = target_dir / (settings.LIBRARY_ANIME_NAME
or settings.LIBRARY_TV_NAME) / mediainfo.category
else:
target_dir = target_dir / mediainfo.category
else:
# 电视剧
if typename_dir:
target_dir = target_dir / settings.LIBRARY_TV_NAME / mediainfo.category
else:
target_dir = target_dir / mediainfo.category
return target_dir
if target_dir.auto_category or target_dir.category:
return Path(target_dir.path) / mediainfo.category
else:
return Path(target_dir.path)
def transfer_media(self,
in_path: Path,
@ -428,7 +414,8 @@ class FileTransferModule(_ModuleBase):
mediainfo: MediaInfo,
transfer_type: str,
target_dir: Path,
episodes_info: List[TmdbEpisode] = None
episodes_info: List[TmdbEpisode] = None,
need_scrape: bool = False
) -> TransferInfo:
"""
识别并转移一个文件或者一个目录下的所有文件
@ -438,6 +425,7 @@ class FileTransferModule(_ModuleBase):
:param target_dir: 媒体库根目录
:param transfer_type: 文件转移方式
:param episodes_info: 当前季的全部集信息
:param need_scrape: 是否需要刮削
:return: TransferInfo、错误信息
"""
# 检查目录路径
@ -490,7 +478,8 @@ class FileTransferModule(_ModuleBase):
path=in_path,
target_path=new_path,
total_size=file_size,
is_bluray=bluray_flag)
is_bluray=bluray_flag,
need_scrape=need_scrape)
else:
# 转移单个文件
if mediainfo.type == MediaType.TV:
@ -589,7 +578,8 @@ class FileTransferModule(_ModuleBase):
total_size=file_size,
is_bluray=False,
file_list=[str(in_path)],
file_list_new=[str(new_file)])
file_list_new=[str(new_file)],
need_scrape=need_scrape)
@staticmethod
def __get_naming_dict(meta: MetaBase, mediainfo: MediaInfo, file_ext: str = None,
@ -689,86 +679,21 @@ class FileTransferModule(_ModuleBase):
else:
return Path(render_str)
@staticmethod
def get_library_path(path: Path):
"""
根据文件路径查询其所在的媒体库目录,查询不到的返回输入目录
"""
if not path:
return None
if not settings.LIBRARY_PATHS:
return path
# 目的路径,多路径以,分隔
dest_paths = settings.LIBRARY_PATHS
for libpath in dest_paths:
try:
if path.is_relative_to(libpath):
return libpath
except Exception as e:
logger.debug(f"计算媒体库路径时出错:{str(e)}")
continue
return path
@staticmethod
def get_target_path(in_path: Path = None) -> Optional[Path]:
"""
计算一个最好的目的目录有in_path时找与in_path同路径的没有in_path时顺序查找1个符合大小要求的没有in_path和size时返回第1个
:param in_path: 源目录
"""
if not settings.LIBRARY_PATHS:
return None
# 目的路径,多路径以,分隔
dest_paths = settings.LIBRARY_PATHS
# 只有一个路径,直接返回
if len(dest_paths) == 1:
return dest_paths[0]
# 匹配有最长共同上级路径的目录
max_length = 0
target_path = None
if in_path:
for path in dest_paths:
try:
# 计算in_path和path的公共字符串长度
relative = StringUtils.find_common_prefix(str(in_path), str(path))
if len(str(path)) == len(relative):
# 目录完整匹配的,直接返回
return path
if len(relative) > max_length:
# 更新最大长度
max_length = len(relative)
target_path = path
except Exception as e:
logger.debug(f"计算目标路径时出错:{str(e)}")
continue
if target_path:
return target_path
# 顺序匹配第1个满足空间存储要求的目录
if in_path.exists():
file_size = in_path.stat().st_size
for path in dest_paths:
if SystemUtils.free_space(path) > file_size:
return path
# 默认返回第1个
return dest_paths[0]
def media_exists(self, mediainfo: MediaInfo, **kwargs) -> Optional[ExistMediaInfo]:
"""
判断媒体文件是否存在于本地文件系统,只支持标准媒体库结构
:param mediainfo: 识别的媒体信息
:return: 如不存在返回None存在时返回信息包括每季已存在所有集{type: movie/tv, seasons: {season: [episodes]}}
"""
if not settings.LIBRARY_PATHS:
return None
# 目的路径
dest_paths = settings.LIBRARY_PATHS
dest_paths = DirectoryHelper().get_library_dirs()
# 检查每一个媒体库目录
for dest_path in dest_paths:
# 媒体库路径
target_dir = self.get_target_path(dest_path)
if not target_dir:
if not dest_path.path:
continue
# 媒体分类路径
target_dir = self.__get_dest_dir(mediainfo=mediainfo, target_dir=target_dir)
target_dir = self.__get_dest_dir(mediainfo=mediainfo, target_dir=dest_path)
# 重命名格式
rename_format = settings.TV_RENAME_FORMAT \
if mediainfo.type == MediaType.TV else settings.MOVIE_RENAME_FORMAT

View File

@ -192,7 +192,7 @@ class QbittorrentModule(_ModuleBase):
if content_path:
torrent_path = Path(content_path)
else:
torrent_path = settings.SAVE_PATH / torrent.get('name')
torrent_path = torrent.get('save_path') / torrent.get('name')
ret_torrents.append(TransferTorrent(
title=torrent.get('name'),
path=torrent_path,
@ -211,7 +211,7 @@ class QbittorrentModule(_ModuleBase):
if content_path:
torrent_path = Path(content_path)
else:
torrent_path = settings.SAVE_PATH / torrent.get('name')
torrent_path = torrent.get('save_path') / torrent.get('name')
ret_torrents.append(TransferTorrent(
title=torrent.get('name'),
path=torrent_path,

View File

@ -76,10 +76,10 @@ class Scheduler(metaclass=Singleton):
__max_try__ = 30
if self._auth_count > __max_try__:
SchedulerChain().messagehelper.put(title=f"用户认证失败",
message="用户认证失败次数过多,将不再试认证!",
message="用户认证失败次数过多,将不再试认证!",
role="system")
return
logger.info("用户未认证,正在试重新认证...")
logger.info("用户未认证,正在试重新认证...")
status, msg = SitesHelper().check_user()
if status:
self._auth_count = 0
@ -95,7 +95,7 @@ class Scheduler(metaclass=Singleton):
self._auth_count += 1
logger.error(f"用户认证失败:{msg},共失败 {self._auth_count}")
if self._auth_count >= __max_try__:
logger.error("用户认证失败次数过多,将不再试认证!")
logger.error("用户认证失败次数过多,将不再试认证!")
# 各服务的运行状态
self._jobs = {

View File

@ -18,7 +18,7 @@ class MediaDirectory(BaseModel):
# 媒体类别 动画电影/国产剧
category: Optional[str] = None
# 刮削媒体信息
scrape: Optional[bool] = True
scrape: Optional[bool] = False
# 自动二级分类,未指定类别时自动分类
auto_category: Optional[bool] = False
# 优先级

View File

@ -59,6 +59,8 @@ class TransferInfo(BaseModel):
fail_list: Optional[list] = []
# 错误信息
message: Optional[str] = None
# 是否需要刮削
need_scrape: Optional[bool] = False
def to_dict(self):
"""

View File

@ -0,0 +1,140 @@
"""1.0.20
Revision ID: a40261701909
Revises: ae9d8ed8df97
Create Date: 2024-05-22 19:16:21.374806
"""
import json
from pathlib import Path
from alembic import op
import sqlalchemy as sa
from app.core.config import Settings
# revision identifiers, used by Alembic.
revision = 'a40261701909'
down_revision = 'ae9d8ed8df97'
branch_labels = None
depends_on = None
def upgrade() -> None:
"""
升级目录配置
"""
# 实例化配置
_settings = Settings(
_env_file=Settings().CONFIG_PATH / "app.env",
_env_file_encoding="utf-8"
)
# 下载目录配置升级
download_dirs = []
if _settings.DOWNLOAD_MOVIE_PATH:
download_dirs.append({
"type": "download",
"name": "电影目录",
"path": _settings.DOWNLOAD_MOVIE_PATH,
"media_type": "电影",
"category": "",
"auto_category": True if _settings.DOWNLOAD_CATEGORY else False,
"priority": 1
})
if _settings.DOWNLOAD_TV_PATH:
download_dirs.append({
"type": "download",
"name": "电视剧目录",
"path": _settings.DOWNLOAD_TV_PATH,
"media_type": "电视剧",
"category": "",
"auto_category": True if _settings.DOWNLOAD_CATEGORY else False,
"priority": 2
})
if _settings.DOWNLOAD_ANIME_PATH:
download_dirs.append({
"type": "download",
"name": "动漫目录",
"path": _settings.DOWNLOAD_ANIME_PATH,
"media_type": "动漫",
"category": "",
"auto_category": True if _settings.DOWNLOAD_CATEGORY else False,
"priority": 3
})
if _settings.DOWNLOAD_PATH:
download_dirs.append({
"type": "download",
"name": "下载目录",
"path": _settings.DOWNLOAD_PATH,
"media_type": "",
"category": "",
"auto_category": True if _settings.DOWNLOAD_CATEGORY else False,
"priority": 4
})
# 插入数据库,报错的话则更新
if download_dirs:
download_dirs_value = json.dumps(download_dirs)
try:
op.execute(f"INSERT INTO systemconfig (key, value) VALUES ('DownloadDirectories', '{download_dirs_value}');")
except Exception as e:
op.execute(f"UPDATE systemconfig SET value = '{download_dirs_value}' WHERE key = 'DownloadDirectories';")
# 媒体库目录配置升级
library_dirs = []
if _settings.LIBRARY_PATH:
for library_path in _settings.LIBRARY_PATH.split(","):
if _settings.LIBRARY_MOVIE_NAME:
library_dirs.append({
"type": "library",
"name": "电影目录",
"path": str(Path(library_path) / _settings.LIBRARY_MOVIE_NAME),
"media_type": "电影",
"category": "",
"auto_category": True if _settings.LIBRARY_CATEGORY else False,
"scrape": True if _settings.SCRAP_METADATA else False,
"priority": 1
})
if _settings.LIBRARY_TV_NAME:
library_dirs.append({
"type": "library",
"name": "电视剧目录",
"path": str(Path(library_path) / _settings.LIBRARY_TV_NAME),
"media_type": "电视剧",
"category": "",
"auto_category": True if _settings.LIBRARY_CATEGORY else False,
"scrape": True if _settings.SCRAP_METADATA else False,
"priority": 2
})
if _settings.LIBRARY_ANIME_NAME:
library_dirs.append({
"type": "library",
"name": "动漫目录",
"path": str(Path(library_path) / _settings.LIBRARY_ANIME_NAME),
"media_type": "动漫",
"category": "",
"auto_category": True if _settings.LIBRARY_CATEGORY else False,
"scrape": True if _settings.SCRAP_METADATA else False,
"priority": 3
})
library_dirs.append({
"type": "library",
"name": "媒体库目录",
"path": library_path,
"media_type": "",
"category": "",
"auto_category": True if _settings.LIBRARY_CATEGORY else False,
"scrape": True if _settings.SCRAP_METADATA else False,
"priority": 4
})
# 插入数据库,报错的话则更新
if library_dirs:
library_dirs_value = json.dumps(library_dirs)
try:
op.execute(f"INSERT INTO systemconfig (key, value) VALUES ('LibraryDirectories', '{library_dirs_value}');")
except Exception as e:
op.execute(f"UPDATE systemconfig SET value = '{library_dirs_value}' WHERE key = 'LibraryDirectories';")
def downgrade() -> None:
pass