diff --git a/app/chain/__init__.py b/app/chain/__init__.py index 98b0d42b2..8dce6ae3e 100644 --- a/app/chain/__init__.py +++ b/app/chain/__init__.py @@ -532,13 +532,14 @@ def post_torrents_message(self, message: Notification, torrents: List[Context]) self.messageoper.add(**message.dict(), note=note_list) return self.run_module("post_torrents_message", message=message, torrents=torrents) - def metadata_img(self, mediainfo: MediaInfo, season: int = None) -> Optional[dict]: + def metadata_img(self, mediainfo: MediaInfo, season: int = None, episode: int = None) -> Optional[dict]: """ 获取图片名称和url :param mediainfo: 媒体信息 :param season: 季号 + :param episode: 集号 """ - return self.run_module("metadata_img", mediainfo=mediainfo, season=season) + return self.run_module("metadata_img", mediainfo=mediainfo, season=season, episode=episode) def media_category(self) -> Optional[Dict[str, list]]: """ diff --git a/app/chain/media.py b/app/chain/media.py index 6c537e37a..e6d95a779 100644 --- a/app/chain/media.py +++ b/app/chain/media.py @@ -358,10 +358,17 @@ def __list_files(_fileitem: schemas.FileItem): """ return StorageChain().list_files(fileitem=_fileitem) - def __save_file(_fileitem: schemas.FileItem, _path: Path, _content: Union[bytes, str]): + def __save_file(_fileitem: schemas.FileItem, _path: Path, _content: Union[bytes, str], + overwrite: bool = False): """ 保存或上传文件 + :param _fileitem: 关联的媒体文件项 + :param _path: 元数据文件路径 + :param _content: 文件内容 + :param overwrite: 是否覆盖 """ + if not overwrite and _path.exists(): + return tmp_file = settings.TEMP_PATH / _path.name tmp_file.write_bytes(_content) upload_item = copy.deepcopy(_fileitem) @@ -369,6 +376,7 @@ def __save_file(_fileitem: schemas.FileItem, _path: Path, _content: Union[bytes, upload_item.name = _path.name upload_item.basename = _path.stem upload_item.extension = _path.suffix + logger.info(f"保存文件:{_path}") StorageChain().upload_file(fileitem=upload_item, path=tmp_file) if tmp_file.exists(): tmp_file.unlink() @@ -431,7 +439,7 @@ def __download_image(_url: str) -> Optional[bytes]: image_path = filepath / image_name # 下载图片 content = __download_image(_url=attr_value) - # 写入nfo到根目录 + # 写入图片到根目录 __save_file(_fileitem=fileitem, _path=image_path, _content=content) else: # 电视剧 @@ -453,6 +461,17 @@ def __download_image(_url: str) -> Optional[bytes]: return # 保存或上传nfo文件 __save_file(_fileitem=fileitem, _path=filepath.with_suffix(".nfo"), _content=episode_nfo) + # 获取集的图片 + image_dict = self.metadata_img(mediainfo=file_mediainfo, + season=file_meta.begin_season, episode=file_meta.begin_episode) + if image_dict: + for episode, image_url in image_dict.items(): + image_path = filepath.with_suffix(Path(image_url).suffix) + # 下载图片 + content = __download_image(image_url) + # 保存图片文件到当前目录 + __save_file(_fileitem=fileitem, _path=image_path, _content=content) + else: # 当前为目录,处理目录内的文件 files = __list_files(_fileitem=fileitem) @@ -495,7 +514,7 @@ def __download_image(_url: str) -> Optional[bytes]: image_dict = self.metadata_img(mediainfo=mediainfo) if image_dict: for image_name, image_url in image_dict.items(): - image_path = filepath.parent.with_name(image_name) + image_path = filepath / image_name # 下载图片 content = __download_image(image_url) # 保存图片文件到当前目录 diff --git a/app/chain/transfer.py b/app/chain/transfer.py index c3ae4016a..c24ed26f8 100644 --- a/app/chain/transfer.py +++ b/app/chain/transfer.py @@ -432,14 +432,6 @@ def __do_transfer(self, fileitem: FileItem, transferinfo=transferinfo ) - # 刮削元数据事件 - if scrape or transferinfo.need_scrape: - self.eventmanager.send_event(EventType.MetadataScrape, { - 'meta': file_meta, - 'mediainfo': file_mediainfo, - 'fileitem': transferinfo.target_item - }) - # 更新进度 processed_num += 1 self.progress.update(value=processed_num / total_num * 100, @@ -462,12 +454,20 @@ def __do_transfer(self, fileitem: FileItem, mediainfo=media, transferinfo=transfer_info, season_episode=se_str) + # 刮削事件 + if scrape or transfer_info.need_scrape: + self.eventmanager.send_event(EventType.MetadataScrape, { + 'meta': transfer_meta, + 'mediainfo': media, + 'fileitem': transfer_info.target_diritem + }) # 整理完成事件 self.eventmanager.send_event(EventType.TransferComplete, { 'meta': transfer_meta, 'mediainfo': media, 'transferinfo': transfer_info }) + # 结束进度 logger.info(f"{fileitem.path} 整理完成,共 {total_num} 个文件," f"失败 {fail_num} 个,跳过 {skip_num} 个") diff --git a/app/modules/douban/__init__.py b/app/modules/douban/__init__.py index 41f9cd277..12a09e350 100644 --- a/app/modules/douban/__init__.py +++ b/app/modules/douban/__init__.py @@ -684,15 +684,16 @@ def metadata_nfo(self, mediainfo: MediaInfo, season: int = None, **kwargs) -> Op return None return self.scraper.get_metadata_nfo(mediainfo=mediainfo, season=season) - def metadata_img(self, mediainfo: MediaInfo, season: int = None) -> Optional[dict]: + def metadata_img(self, mediainfo: MediaInfo, season: int = None, episode: int = None) -> Optional[dict]: """ 获取图片名称和url :param mediainfo: 媒体信息 :param season: 季号 + :param episode: 集号 """ if settings.SCRAP_SOURCE != "douban": return None - return self.scraper.get_metadata_img(mediainfo=mediainfo, season=season) + return self.scraper.get_metadata_img(mediainfo=mediainfo, season=season, episode=episode) def obtain_images(self, mediainfo: MediaInfo) -> Optional[MediaInfo]: """ diff --git a/app/modules/douban/scraper.py b/app/modules/douban/scraper.py index 1182ed5a5..b1b628c1e 100644 --- a/app/modules/douban/scraper.py +++ b/app/modules/douban/scraper.py @@ -33,16 +33,20 @@ def get_metadata_nfo(self, mediainfo: MediaInfo, season: int = None) -> Optional return None @staticmethod - def get_metadata_img(mediainfo: MediaInfo, season: int = None) -> Optional[dict]: + def get_metadata_img(mediainfo: MediaInfo, season: int = None, episode: int = None) -> Optional[dict]: """ 获取图片内容 :param mediainfo: 媒体信息 :param season: 季号 + :param episode: 集号 """ ret_dict = {} if season: # 豆瓣无季图片 return {} + if episode: + # 豆瓣无集图片 + return {} if mediainfo.poster_path: ret_dict[f"poster{Path(mediainfo.poster_path).suffix}"] = mediainfo.poster_path if mediainfo.backdrop_path: diff --git a/app/modules/filemanager/__init__.py b/app/modules/filemanager/__init__.py index 79cb61535..48a19c23a 100644 --- a/app/modules/filemanager/__init__.py +++ b/app/modules/filemanager/__init__.py @@ -682,20 +682,19 @@ def __transfer_dir(self, fileitem: FileItem, transfer_type: str, logger.info(f"获取目标目录失败:{target_path}") return None, f"获取目标目录失败:{target_path}" # 处理所有文件 - new_item, errmsg = self.__transfer_dir_files(fileitem=fileitem, - target_storage=target_storage, - target_path=target_path, - transfer_type=transfer_type) - if new_item: + state, errmsg = self.__transfer_dir_files(fileitem=fileitem, + target_storage=target_storage, + target_path=target_path, + transfer_type=transfer_type) + if state: logger.info(f"文件 {fileitem.path} {transfer_type}完成") - return new_item, errmsg + return target_item, errmsg else: logger.error(f"文件{fileitem.path} {transfer_type}失败:{errmsg}") - - return None, errmsg + return None, errmsg def __transfer_dir_files(self, fileitem: FileItem, transfer_type: str, - target_storage: str, target_path: Path) -> Tuple[Optional[FileItem], str]: + target_storage: str, target_path: Path) -> Tuple[bool, str]: """ 按目录结构整理目录下所有文件 :param fileitem: 源文件 @@ -707,19 +706,19 @@ def __transfer_dir_files(self, fileitem: FileItem, transfer_type: str, storage_oper = self.__get_storage_oper(fileitem.storage) if not storage_oper: logger.error(f"不支持 {fileitem.storage} 的文件整理") - return None, f"不支持的文件存储:{fileitem.storage}" + return False, f"不支持的文件存储:{fileitem.storage}" file_list: List[FileItem] = storage_oper.list(fileitem) # 整理文件 for item in file_list: if item.type == "dir": # 递归整理目录 new_path = target_path / item.name - new_item, errmsg = self.__transfer_dir_files(fileitem=item, - transfer_type=transfer_type, - target_storage=target_storage, - target_path=new_path) - if not new_item: - return None, errmsg + state, errmsg = self.__transfer_dir_files(fileitem=item, + transfer_type=transfer_type, + target_storage=target_storage, + target_path=new_path) + if not state: + return False, errmsg else: # 整理文件 new_file = target_path / item.name @@ -728,9 +727,9 @@ def __transfer_dir_files(self, fileitem: FileItem, transfer_type: str, target_file=new_file, transfer_type=transfer_type) if not new_item: - return None, errmsg + return False, errmsg # 返回成功 - return storage_oper.get_item(target_path), "" + return True, "" def __transfer_file(self, fileitem: FileItem, target_storage: str, target_file: Path, transfer_type: str, over_flag: bool = False) -> Tuple[Optional[FileItem], str]: @@ -813,21 +812,6 @@ def transfer_media(self, :return: TransferInfo、错误信息 """ - def __get_targetitem(_path: Path) -> FileItem: - """ - 获取文件信息 - """ - return FileItem( - storage=target_storage, - path=str(_path).replace("\\", "/"), - name=_path.name, - basename=_path.stem, - type="file", - size=_path.stat().st_size, - extension=_path.suffix.lstrip('.'), - modify_time=_path.stat().st_mtime - ) - # 重命名格式 rename_format = settings.TV_RENAME_FORMAT \ if mediainfo.type == MediaType.TV else settings.MOVIE_RENAME_FORMAT @@ -845,23 +829,23 @@ def __get_targetitem(_path: Path) -> FileItem: else: new_path = target_path / fileitem.name # 整理目录 - new_item, errmsg = self.__transfer_dir(fileitem=fileitem, - target_storage=target_storage, - target_path=new_path, - transfer_type=transfer_type) - if not new_item: + new_diritem, errmsg = self.__transfer_dir(fileitem=fileitem, + target_storage=target_storage, + target_path=new_path, + transfer_type=transfer_type) + if not new_diritem: logger.error(f"文件夹 {fileitem.path} 整理失败:{errmsg}") return TransferInfo(success=False, message=errmsg, fileitem=fileitem, - target_path=new_path, transfer_type=transfer_type) logger.info(f"文件夹 {fileitem.path} 整理成功") # 返回整理后的路径 return TransferInfo(success=True, fileitem=fileitem, - target_item=new_item, + target_item=new_diritem, + target_diritem=new_diritem, total_size=fileitem.size, need_scrape=need_scrape, transfer_type=transfer_type) @@ -906,6 +890,9 @@ def __get_targetitem(_path: Path) -> FileItem: overflag = False # 目的操作对象 target_oper: StorageBase = self.__get_storage_oper(target_storage) + # 目标目录 + target_diritem = target_oper.get_folder(new_file.parent) + # 目标文件 target_item = target_oper.get_item(new_file) if target_item: # 目标文件已存在 @@ -930,7 +917,8 @@ def __get_targetitem(_path: Path) -> FileItem: return TransferInfo(success=False, message=f"媒体库存在同名文件,且质量更好", fileitem=fileitem, - target_item=__get_targetitem(target_file), + target_item=target_item, + target_diritem=target_diritem, fail_list=[fileitem.path], transfer_type=transfer_type) case 'never': @@ -938,7 +926,8 @@ def __get_targetitem(_path: Path) -> FileItem: return TransferInfo(success=False, message=f"媒体库存在同名文件,当前覆盖模式为不覆盖", fileitem=fileitem, - target_item=__get_targetitem(target_file), + target_item=target_item, + target_diritem=target_diritem, fail_list=[fileitem.path], transfer_type=transfer_type) case 'latest': @@ -968,6 +957,7 @@ def __get_targetitem(_path: Path) -> FileItem: return TransferInfo(success=True, fileitem=fileitem, target_item=new_item, + target_diritem=target_diritem, file_count=1, total_size=fileitem.size, file_list=[fileitem.path], diff --git a/app/modules/themoviedb/__init__.py b/app/modules/themoviedb/__init__.py index 79595629b..3a1f693a7 100644 --- a/app/modules/themoviedb/__init__.py +++ b/app/modules/themoviedb/__init__.py @@ -300,15 +300,16 @@ def metadata_nfo(self, meta: MetaBase, mediainfo: MediaInfo, return None return self.scraper.get_metadata_nfo(meta=meta, mediainfo=mediainfo, season=season, episode=episode) - def metadata_img(self, mediainfo: MediaInfo, season: int = None) -> Optional[dict]: + def metadata_img(self, mediainfo: MediaInfo, season: int = None, episode: int = None) -> Optional[dict]: """ 获取图片名称和url :param mediainfo: 媒体信息 :param season: 季号 + :param episode: 集号 """ if settings.SCRAP_SOURCE != "themoviedb": return None - return self.scraper.get_metadata_img(mediainfo=mediainfo, season=season) + return self.scraper.get_metadata_img(mediainfo=mediainfo, season=season, episode=episode) def tmdb_discover(self, mtype: MediaType, sort_by: str, with_genres: str, with_original_language: str, page: int = 1) -> Optional[List[MediaInfo]]: diff --git a/app/modules/themoviedb/scraper.py b/app/modules/themoviedb/scraper.py index 6ebdd8c81..e58426d54 100644 --- a/app/modules/themoviedb/scraper.py +++ b/app/modules/themoviedb/scraper.py @@ -49,32 +49,46 @@ def get_metadata_nfo(self, meta: MetaBase, mediainfo: MediaInfo, return None - def get_metadata_img(self, mediainfo: MediaInfo, season: int = None) -> dict: + def get_metadata_img(self, mediainfo: MediaInfo, season: int = None, episode: int = None) -> dict: """ 获取图片名称和url :param mediainfo: 媒体信息 :param season: 季号 + :param episode: 集号 """ images = {} if season: # 只需要季的图片 - seasoninfo = self.tmdb.get_tv_season_detail(mediainfo.tmdb_id, season) - if seasoninfo: - # TMDB季poster图片 - poster_name, poster_url = self.get_season_poster(seasoninfo, season) - if poster_name and poster_url: - images[poster_name] = poster_url + if episode: + # 季的图片 + seasoninfo = self.tmdb.get_tv_season_detail(mediainfo.tmdb_id, season) + if seasoninfo: + episodeinfo = self.__get_episode_detail(seasoninfo, episode) + if episodeinfo: + # TMDB集still图片 + still_name = f"{episode}" + still_url = f"https://{settings.TMDB_IMAGE_DOMAIN}/t/p/original{episodeinfo.get('still_path')}" + images[still_name] = still_url + else: + # 集的图片 + seasoninfo = self.tmdb.get_tv_season_detail(mediainfo.tmdb_id, season) + if seasoninfo: + # TMDB季poster图片 + poster_name, poster_url = self.get_season_poster(seasoninfo, season) + if poster_name and poster_url: + images[poster_name] = poster_url + return images + else: + # 主媒体图片 + for attr_name, attr_value in vars(mediainfo).items(): + if attr_value \ + and attr_name.endswith("_path") \ + and attr_value \ + and isinstance(attr_value, str) \ + and attr_value.startswith("http"): + image_name = attr_name.replace("_path", "") + Path(attr_value).suffix + images[image_name] = attr_value return images - # 主媒体图片 - for attr_name, attr_value in vars(mediainfo).items(): - if attr_value \ - and attr_name.endswith("_path") \ - and attr_value \ - and isinstance(attr_value, str) \ - and attr_value.startswith("http"): - image_name = attr_name.replace("_path", "") + Path(attr_value).suffix - images[image_name] = attr_value - return images @staticmethod def get_season_poster(seasoninfo: dict, season: int) -> Tuple[str, str]: diff --git a/app/schemas/transfer.py b/app/schemas/transfer.py index 972cbd1ac..a5681e6bb 100644 --- a/app/schemas/transfer.py +++ b/app/schemas/transfer.py @@ -46,6 +46,8 @@ class TransferInfo(BaseModel): success: bool = True # 整理⼁路径 fileitem: Optional[FileItem] = None + # 转移后的目录项 + target_diritem: Optional[FileItem] = None # 转移后路径 target_item: Optional[FileItem] = None # 整理方式