NewComicDownloader/src/sites/manager.py
2025-02-05 01:56:30 +08:00

211 lines
9.3 KiB
Python

from pathlib import Path
from typing import Dict, Type, Optional
from src.config import BASE_IMAGES_DIR
from src.sites.base import BaseSite
from src.sites.configs.rouman import RoumanSite
from src.common.utils import MangaDownloader, CBZUtils, MangaUtils
from src.common.naming import DirectoryNaming, FileNaming
from src.common.exceptions import MangaException
from src.common.item import MangaItem, MangaInfo
from src.common.logging import setup_logging
from src.common.ComicInfo import ComicInfoXml
logger = setup_logging(__name__)
class MangaManager:
"""漫画下载管理器"""
SITE_MAP: Dict[str, Type[BaseSite]] = {
# 'manhuagui.com': ManhuaguiSite,
'roum20.xyz': RoumanSite,
'rouman5.com': RoumanSite,
# 在这里添加更多网站支持
}
def __init__(self, base_dir: Path = BASE_IMAGES_DIR):
self.downloader = MangaDownloader(base_dir)
def get_site_handler(self, url: str) -> Optional[Type[BaseSite]]:
"""根据URL获取对应的网站处理器"""
for domain, handler in self.SITE_MAP.items():
if domain in url:
return handler
return None
async def process_manga(
self,
url: str,
volume_num: int = 1,
status_callback = None
):
"""处理漫画下载"""
# 获取网站处理器
site_handler = self.get_site_handler(url)
if not site_handler:
raise MangaException(f"不支持的网站: {url}")
async with site_handler() as site:
# 下载整部漫画
async for result in site.download_manga(url):
if result['type'] == 'info':
manga_info = result['data']
logger.debug(f"漫画信息: {manga_info}")
# 使用 MangaItem 保存数据
manga_item = MangaItem(info=manga_info, chapters=[])
manga_name = manga_info.title
# 创建命名策略
#self.manga_path = NamingStrategy.manga_volume_path(
# manga_name,
# volume_num=volume_num
#)
#self.manga_filename = NamingStrategy.custom_manga_filename(
# prefix="page",
# digits=3
#)
elif result['type'] == 'chapters':
chapters = result['data']
total = 0
for chapter in chapters:
if not chapter.status == "downloaded":
total += 1
total_chapters = total
logger.info(f"找到 {total_chapters} 个章节")
manga_item.chapters.extend(chapters) # 添加章节到 MangaItem
yield {
'type': 'progress',
'total_chapters': total_chapters
}
elif result['type'] == 'cover':
await self.downloader.download_cover(manga_info)
yield {
'type': 'cover_complete',
'item': manga_item
}
elif result['type'] == 'chapter':
manga_item = result['item']
chapter = manga_item.chapter
# 生成章节图像工作目录
chapter_dir = DirectoryNaming.chapter_images_dir(manga_info, chapter)
DirectoryNaming.ensure_dir(chapter_dir)
try:
# 下载章节
download_result = await self.downloader.download_chapter(
manga_item,
#filename_generator=self.manga_filename,
#path_generator=self.manga_path,
status_callback=status_callback
)
# 章节下载完成后处理流程 start
# 下载完成后生成 ComicInfo.xml
if int(download_result['success']) == int(download_result['total']):
cbz_path = FileNaming.chapter_cbz(manga_info, chapter)
# 解密图片
CBZUtils(cbz_path)._image_deScrambleByPath(chapter_dir)
ComicInfoXml().scrapy_xml_by_json(manga_item.get_comic_info_json(), chapter_dir)
# 打包成 CBZ 文件
CBZUtils(cbz_path).create_cbz(chapter_dir, clear_chapter=True)
# 章节下载完成后处理流程 end
yield {
'type': 'chapter_complete',
'chapter': chapter,
'result': download_result
}
except Exception as e:
logger.error(f"下载章节 {chapter['title']} 失败: {str(e)}")
yield {
'type': 'chapter_error',
'chapter': chapter,
'error': str(e)
}
elif result['type'] == 'error':
logger.error(f"错误: {result['error']}")
yield {
'type': 'error',
'error': result['error']
}
@staticmethod
def print_progress(status):
"""打印下载进度"""
progress_bar_length = 30 # 进度条长度
progress = int(status.progress * progress_bar_length)
bar = '#' * progress + '-' * (progress_bar_length - progress)
print(f"\r下载进度: |{bar}| {status.current}/{status.total} "
f"({status.progress:.1%})", end="")
async def download_list_manga(self, manga_url: str):
# 获取网站处理器
list_site_handler = self.get_site_handler(manga_url)
if not list_site_handler:
raise MangaException(f"不支持的网站: {manga_url}")
async with list_site_handler() as site:
manga_list = await site.get_manga_list(manga_url)
for title,url,created_at in zip(manga_list.title, manga_list.url, manga_list.created_at):
title = FileNaming.chinese_file_name(title)
save_manga = MangaUtils().search_manga(title)
created = None
if save_manga != None: created = save_manga.get('created_at', None)
if created != None and created_at == created:
created = save_manga.get('created_at', None)
logger.info(f"{save_manga} 已存在")
else:
logger.info(f"开始下载 漫画: {title}")
logger.info(f"{url}")
await self.download_manga(str(url), title = title, created_at = created_at)
@classmethod
async def download_manga(cls, url: str, title: str = None, created_at: str = None, save_dir: Path = BASE_IMAGES_DIR):
"""下载漫画"""
manager = MangaManager(save_dir)
try:
total_chapters = 0
completed_chapters = 0
success_chapters = 0
async for result in manager.process_manga(url, status_callback=cls.print_progress):
if result['type'] == 'progress':
total_chapters = result['total_chapters']
logger.info(f"开始下载,共 {total_chapters}")
elif result['type'] == 'chapter_complete':
completed_chapters += 1
chapter_result = result['result']
if chapter_result['failed']:
logger.warning(
f"章节 {result['chapter']} 完成: "
f"{chapter_result['success']}/{chapter_result['total']} 张图片成功, "
f"{chapter_result['failed']} 张失败"
)
else:
success_chapters += 1
logger.info(f"章节 {result['chapter']} 完成")
print(f"\n总进度: {completed_chapters}/{total_chapters}")
elif result['type'] == 'chapter_error':
logger.error(f"章节 {result['chapter']} 下载失败: {result['error']}")
elif result['type'] == 'error':
logger.error(f"下载出错: {result['error']}")
# 全部下载完成
if int(total_chapters) == int(success_chapters) and title != None and created_at != None:
MangaUtils().add_manga(title, created_at=created_at)
logger.info(f"全部完成 {title}, {created_at}")
except MangaException as e:
logger.error(f"下载失败: {str(e)}")
except Exception as e:
logger.error(f"未知错误: {str(e)}")