211 lines
9.3 KiB
Python
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)}") |