update
This commit is contained in:
1 parent
4874600a07
commit
3a45a11a65
7 files changed
+352
-70
No files matched your search
+121
-14
@@ -1,20 +1,16 @@
|
||||
import os, re, requests, hashlib
|
||||
import xml.etree.ElementTree as ET
|
||||
from xml.dom import minidom
|
||||
from typing import List
|
||||
import os
|
||||
from lxml import etree
|
||||
from src.config import XSD_FILE
|
||||
from src.common.logging import setup_logging
|
||||
import logging
|
||||
from zipfile import ZipFile
|
||||
from pathlib import Path
|
||||
import re
|
||||
import requests
|
||||
from urllib.parse import urlparse
|
||||
from PIL import Image
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
import hashlib
|
||||
from io import BytesIO
|
||||
from dataclasses import dataclass
|
||||
|
||||
logger = setup_logging(__name__)
|
||||
|
||||
@@ -207,8 +203,12 @@ class ImageInfo:
|
||||
class ComicInfo:
|
||||
# ComicInfo.xml 中的选项
|
||||
def __init__(self):
|
||||
self._init_default_values()
|
||||
|
||||
def _init_default_values(self):
|
||||
"""初始化默认值"""
|
||||
self.Title: str = ""
|
||||
"""标题"""
|
||||
"""<h2>标题</h2>"""
|
||||
self.Series: str = ""
|
||||
self.Number: str = ""
|
||||
self.Count: int = -1
|
||||
@@ -245,11 +245,20 @@ class ComicInfo:
|
||||
self.StoryArc: str = ""
|
||||
self.SeriesGroup: str = ""
|
||||
self.AgeRating: str = ""
|
||||
self.Pages: List[ComicPageInfo] = []
|
||||
|
||||
self.Pages: List[ComicPageInfo] = []
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
values = kwargs.values()
|
||||
self._init_default_values()
|
||||
if len(values) > 0:
|
||||
for key, value in kwargs.items():
|
||||
if hasattr(self, key):
|
||||
setattr(self, key, value)
|
||||
@dataclass
|
||||
class ComicPageInfo:
|
||||
# ComicInfo.xml 中的<Page>
|
||||
def __init__(self):
|
||||
|
||||
def _init_default_values(self):
|
||||
self.Image: int = -1
|
||||
self.Type: str = "Story"
|
||||
self.DoublePage: bool = False
|
||||
@@ -257,8 +266,19 @@ class ComicPageInfo:
|
||||
self.Key: str = ""
|
||||
self.Bookmark: str = ""
|
||||
self.ImageWidth: int = -1
|
||||
self.ImageHeight: int = -1
|
||||
self.ImageHeight: int = -1
|
||||
|
||||
def __init__(self):
|
||||
self._init_default_values()
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
values = kwargs.values()
|
||||
self._init_default_values()
|
||||
if len(values) > 0:
|
||||
for key, value in kwargs.items():
|
||||
if hasattr(self, key):
|
||||
setattr(self, key, value)
|
||||
|
||||
def toString(self):
|
||||
data = {}
|
||||
def add(key, value):
|
||||
@@ -314,7 +334,7 @@ class ComicInfoXml:
|
||||
logger.debug(f"zip_file={zip_file} PageCount: {page_count}")
|
||||
return page_count
|
||||
|
||||
def _parse_comicinfo(self, comic: ComicInfo, save_dir=None, xml_filename="ComicInfo.xml", xsd_filename="ComicInfo.xsd"):
|
||||
def _parse_comicinfo(self, comic: ComicInfo, save_dir=None, xml_filename="ComicInfo.xml", xsd_filename=XSD_FILE) -> Path:
|
||||
"""_summary_
|
||||
|
||||
Args:
|
||||
@@ -368,12 +388,13 @@ class ComicInfoXml:
|
||||
self._validate_xml_with_xsd_file(xml_filename, xsd_filename) # 将 JSON 转换为 XML
|
||||
#xml_data = json_to_xml_with_declaration(json_data)
|
||||
#print(xml_data)
|
||||
return Path(xml_filename)
|
||||
|
||||
def _required_attributes(self):
|
||||
"""
|
||||
必需值,如果为空刚报错
|
||||
"""
|
||||
return ["Title", "Series", "Number", "PageCount", "Writer"]
|
||||
return ["Title", "Series", "Number", "Writer", "PageCount", "Pages" ]
|
||||
|
||||
def _gen_pageinfo(self, image_names, save_dir):
|
||||
""" 获取PageInfo数据
|
||||
@@ -387,7 +408,92 @@ class ComicInfoXml:
|
||||
# 图像属性 文件名 大小 长
|
||||
pages.append(page)
|
||||
return pages
|
||||
|
||||
|
||||
def _xml_file_to_comicinfo(self, cbz_path=None, xml_file=None) -> ComicInfo:
|
||||
""" 读取 CBZ 文件或 XML 文件中的 ComicInfo.xml 元数据,返回 ComicInfo 对象
|
||||
Args:
|
||||
以下参数任意一个都可以
|
||||
cbz_path (_type_, optional): 任选择CBZ文件路径或XML文件路径. Defaults to None.
|
||||
xml_file (_type_, optional): XML文件路径. Defaults to None
|
||||
Returns:
|
||||
ci: returns a ComicInfo object with the updated page count
|
||||
"""
|
||||
|
||||
def xml_parse(xml_str) -> ComicInfo:
|
||||
""" 解析 XML 字符串并提取指定字段的值
|
||||
|
||||
Args:
|
||||
xml_str (_type_): xml文件内容字符串
|
||||
keys (_type_): 需要提取的字段列表
|
||||
|
||||
Returns:
|
||||
ci: returns a ComicInfo object with the extracted values
|
||||
"""
|
||||
|
||||
import xmltodict
|
||||
xml_dict = xmltodict.parse(xml_str)
|
||||
# 解析 XML 元数据
|
||||
metadata = {}
|
||||
# 获取comicinfo.xml 字段
|
||||
keys = ComicInfo().__dict__.keys()
|
||||
for key in keys:
|
||||
key_element = xml_dict.get("ComicInfo", {}).get(key, "")
|
||||
if key == "Pages":
|
||||
pages = []
|
||||
page_list_element = xml_dict.get("ComicInfo", {}).get("Pages", []).get("Page", "")
|
||||
for page_element in page_list_element:
|
||||
pages.append(ComicPageInfo(**{ "Image": page_element['@Image'],
|
||||
"ImageSize": int(page_element['@ImageSize']),
|
||||
"Key": page_element['@Key'],
|
||||
"ImageWidth": int(page_element['@ImageWidth']),
|
||||
"ImageHeight": int(page_element['@ImageHeight'])}))
|
||||
key_element = pages
|
||||
if key_element is not None:
|
||||
metadata[key] = key_element if key_element else ""
|
||||
else:
|
||||
metadata[key] = ""
|
||||
return ComicInfo(**metadata)
|
||||
|
||||
def read_zip_file(zip_file_path):
|
||||
"""读取 ZIP 文件并返回其内容"""
|
||||
try:
|
||||
with ZipFile(zip_file_path, 'r') as zip_ref:
|
||||
# 获取 ZIP 文件中的所有文件名
|
||||
file_list = zip_ref.namelist()
|
||||
# 读取 ComicInfo.xml 文件内容
|
||||
if 'ComicInfo.xml' in file_list:
|
||||
with zip_ref.open('ComicInfo.xml') as xml_file:
|
||||
return xml_file.read().decode('utf-8')
|
||||
else:
|
||||
raise FileNotFoundError("ComicInfo.xml not found in the ZIP file.")
|
||||
except Exception as e:
|
||||
print(f"处理 CBZ 文件时出错: {e}")
|
||||
raise exit(f"处理CBZ出错")
|
||||
|
||||
if cbz_path is not None:
|
||||
xml_content = read_zip_file(cbz_path)
|
||||
elif xml_file is not None:
|
||||
with open(xml_file, 'r', encoding='utf-8') as f:
|
||||
xml_content = f.read()
|
||||
else:
|
||||
raise ValueError("请提供 cbz_path 或 xml_file 参数, 否则无法处理 XML 文件")
|
||||
return xml_parse(xml_content)
|
||||
|
||||
def update_comicinfo_count(self, count, cbz_path: Path) -> Path:
|
||||
""" 更新 ComicInfo.xml 中的 PageCount 字段
|
||||
Args:
|
||||
cbz_path (Path): CBZ 文件路径
|
||||
xml_filename (str, optional): XML 文件名. Defaults to "ComicInfo.xml".
|
||||
xsd_filename (str, optional): XSD 文件名. Defaults to "ComicInfo.xsd".
|
||||
Returns:
|
||||
pages: 返回更新后的页面信息列表
|
||||
"""
|
||||
# 读取 ComicInfo.xml 文件
|
||||
comicinfo = self._xml_file_to_comicinfo(cbz_path=cbz_path)
|
||||
comicinfo.Count = count
|
||||
# 保存更新后的 ComicInfo.xml 文件
|
||||
return self._parse_comicinfo(comicinfo, save_dir=os.path.dirname(cbz_path))
|
||||
|
||||
def scrapy_xml_by_json(self, json_data, save_dir=None, xsd_file=XSD_FILE):
|
||||
""" 根据Json数据生成ComicInfo.xml
|
||||
"""
|
||||
@@ -395,6 +501,7 @@ class ComicInfoXml:
|
||||
comic.Title = json_data.get("chapter", "")
|
||||
comic.Series = json_data.get("name", "")
|
||||
comic.Writer = json_data.get("author", "")
|
||||
comic.Count = json_data.get("count", -1)
|
||||
comic.AgeRating = json_data.get("age_rating", "")
|
||||
comic.Tags = json_data.get("tags", "")
|
||||
comic.Summary = json_data.get("description", "")
|
||||
|
||||
@@ -122,6 +122,14 @@ class MangaInfo(BaseModel):
|
||||
list_value.append(val)
|
||||
return FileNaming.chinese_file_name(",".join(list_value))
|
||||
|
||||
status: str
|
||||
"""漫画状态"""
|
||||
@field_validator('status', mode='before')
|
||||
def validate_status(cls, v):
|
||||
if isinstance(v, str):
|
||||
return FileNaming.chinese_file_name(v)
|
||||
return v
|
||||
|
||||
description: Optional[str] = None
|
||||
"""漫画描述"""
|
||||
@field_validator('description', mode='before')
|
||||
@@ -235,11 +243,17 @@ class MangaItem(BaseModel):
|
||||
filename_list = []
|
||||
for image in cls.chapter_images:
|
||||
filename_list.append(image.filename)
|
||||
count = -1
|
||||
if cls.info.status == "已完结" and len(cls.chapters) > 1:
|
||||
# 本章节为最终章节刚添加Count字段
|
||||
if cls.number > 0 and cls.number == len(cls.chapters):
|
||||
count = len(cls.chapters)
|
||||
|
||||
return {
|
||||
"name": cls.info.title,
|
||||
"chapter": cls.chapter.title,
|
||||
"author": cls.info.author,
|
||||
"count" : count,
|
||||
"tags": cls.info.tags,
|
||||
"images": filename_list,
|
||||
"description": cls.info.description,
|
||||
|
||||
+31
-1
@@ -468,7 +468,37 @@ class CBZUtils:
|
||||
if old_img > 0:
|
||||
#os.remove(cbz_path)
|
||||
print(f"remove cbz {cbz_path}")
|
||||
|
||||
|
||||
def update_cbz_with_new_xml(self, cbz_path, new_xml_content, output_path=None):
|
||||
"""将新生成的 ComicInfo.xml 更新到 CBZ 文件中"""
|
||||
try:
|
||||
# 默认输出路径为原文件路径(覆盖原文件)
|
||||
if output_path is None:
|
||||
output_path = cbz_path
|
||||
|
||||
# 创建临时文件处理覆盖操作
|
||||
with NamedTemporaryFile(delete=False) as tmp:
|
||||
tmp.close()
|
||||
shutil.move(cbz_path, tmp.name)
|
||||
# 读取原文件并替换 ComicInfo.xml
|
||||
with ZipFile(tmp.name, 'r') as source_zip:
|
||||
with ZipFile(output_path, 'w') as new_zip:
|
||||
# 复制原文件(跳过旧 XML)
|
||||
for item in source_zip.infolist():
|
||||
if item.filename.lower() != 'comicinfo.xml':
|
||||
new_zip.writestr(item, source_zip.read(item.filename))
|
||||
|
||||
# 添加新 XML
|
||||
new_zip.writestr("ComicInfo.xml", new_xml_content)
|
||||
|
||||
os.remove(tmp.name) # 清理临时文件
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"更新 CBZ 文件失败: {e}")
|
||||
if os.path.exists(tmp.name):
|
||||
shutil.move(tmp.name, cbz_path) # 恢复备份
|
||||
raise exit(f"更新失败")
|
||||
|
||||
class ImageUtils:
|
||||
|
||||
@classmethod
|
||||
|
||||
+18
-1
@@ -11,7 +11,7 @@ from src.common.item import Chapter, MangaItem, MangaInfo,CoverItem
|
||||
from src.common.exceptions import SiteError, NetworkError, ParseError
|
||||
from src.common.logging import setup_logging
|
||||
from src.common.naming import DirectoryNaming,FileNaming
|
||||
from src.common.ComicInfo import ComicInfo, ImageInfo
|
||||
from src.common.ComicInfo import ComicInfo, ImageInfo, ComicInfoXml
|
||||
|
||||
logger = setup_logging(__name__)
|
||||
|
||||
@@ -88,10 +88,27 @@ class BaseSite(ABC):
|
||||
try:
|
||||
# result_type list[Chapter]
|
||||
list_chapter = manga_info.get_list_chapter()
|
||||
# 临时添加begin
|
||||
# 获取最新章节
|
||||
last_chapter = list_chapter[-1] if list_chapter else []
|
||||
# 临时添加end
|
||||
down_chapter = []
|
||||
for chapter in list_chapter:
|
||||
cbz_path = FileNaming.chapter_cbz(manga_info=manga_info,chapter=chapter)
|
||||
if os.path.exists(cbz_path):
|
||||
# 临时添加begin
|
||||
if chapter.title == last_chapter.title and manga_info.status == "已完结":
|
||||
# 如果是最新章节且漫画已完结,则不再下
|
||||
ci = ComicInfoXml()._xml_file_to_comicinfo(cbz_path=cbz_path)
|
||||
if ci.Count == "":
|
||||
# 生成ComicInfo.xml
|
||||
xml_path = ComicInfoXml().update_comicinfo_count(count=len(list_chapter), cbz_path=cbz_path)
|
||||
# 更新ComicInfo.xml至CBZ文件中
|
||||
CBZUtils().update_cbz_with_new_xml(cbz_path, xml_path.read_text(encoding="utf-8"))
|
||||
# 更新完成后删除临时生成的ComicInfo.xml
|
||||
xml_path.unlink()
|
||||
logger.debug(f"更新 {cbz_path} 的 ComicInfo.xml Count完成")
|
||||
# 临时添加end
|
||||
logger.debug(f"{chapter.title} 章节已存在")
|
||||
chapter.status = "downloaded"
|
||||
down_chapter.append(chapter)
|
||||
|
||||
@@ -13,6 +13,9 @@ selectors:
|
||||
author:
|
||||
selector: '//div[@class="basis-3/5 text-sm sm:text-base"]//span[@class="text-foreground"]/text()'
|
||||
index: 0
|
||||
status:
|
||||
selector: '//div[@class="basis-3/5 text-sm sm:text-base"]//span[@class="text-foreground"]/text()'
|
||||
index: 1
|
||||
description:
|
||||
selector: '//div[@class="my-2 text-foreground text-sm sm:text-base"]/p/text()'
|
||||
index: 1
|
||||
|
||||
Reference in new issue
Block a user