mirror of
https://github.com/Kakune55/ComiPy.git
synced 2025-09-16 04:09:41 +08:00
267 lines
9.1 KiB
Python
267 lines
9.1 KiB
Python
import shutil, os, zipfile, io, cv2, numpy as np
|
||
import hashlib
|
||
import time
|
||
from functools import lru_cache
|
||
from pathlib import Path
|
||
import logging
|
||
|
||
import db.file, app_conf
|
||
from utils.logger import get_logger
|
||
from utils.cache_manager import get_cache_manager, cache_image
|
||
from utils.performance_monitor import monitor_performance, timing_context
|
||
|
||
# 获取配置对象
|
||
conf = app_conf.conf()
|
||
logger = get_logger(__name__)
|
||
cache_manager = get_cache_manager()
|
||
|
||
# 内存缓存 - 存储最近访问的ZIP文件列表
|
||
_zip_cache = {}
|
||
_cache_timeout = 300 # 5分钟缓存超时
|
||
|
||
|
||
def init():
|
||
"""初始化文件目录"""
|
||
paths = ("inputdir", "storedir", "tmpdir")
|
||
for path in paths:
|
||
try:
|
||
dir_path = Path(conf.get("file", path))
|
||
dir_path.mkdir(parents=True, exist_ok=True)
|
||
logger.info(f"创建目录: {dir_path}")
|
||
except Exception as e:
|
||
logger.error(f"创建目录失败 {path}: {e}")
|
||
|
||
|
||
@monitor_performance("file.get_image_files_from_zip")
|
||
def get_image_files_from_zip(zip_path: str) -> tuple:
|
||
"""
|
||
从ZIP文件中获取图片文件列表,使用缓存提高性能
|
||
返回: (image_files_list, cache_key)
|
||
"""
|
||
cache_key = f"{zip_path}_{os.path.getmtime(zip_path)}"
|
||
current_time = time.time()
|
||
|
||
# 检查缓存
|
||
if cache_key in _zip_cache:
|
||
cache_data = _zip_cache[cache_key]
|
||
if current_time - cache_data['timestamp'] < _cache_timeout:
|
||
logger.debug(f"使用缓存的ZIP文件列表: {zip_path}")
|
||
return cache_data['files'], cache_key
|
||
|
||
# 读取ZIP文件
|
||
try:
|
||
with zipfile.ZipFile(zip_path, "r") as zip_ref:
|
||
image_files = [
|
||
file for file in zip_ref.namelist()
|
||
if file.lower().endswith((".png", ".jpg", ".jpeg", ".gif", ".bmp", ".webp"))
|
||
]
|
||
|
||
# 缓存结果
|
||
_zip_cache[cache_key] = {
|
||
'files': image_files,
|
||
'timestamp': current_time
|
||
}
|
||
|
||
# 清理过期缓存
|
||
_cleanup_cache()
|
||
|
||
logger.debug(f"缓存ZIP文件列表: {zip_path}, 图片数量: {len(image_files)}")
|
||
return image_files, cache_key
|
||
|
||
except Exception as e:
|
||
logger.error(f"读取ZIP文件失败 {zip_path}: {e}")
|
||
return [], cache_key
|
||
|
||
|
||
def _cleanup_cache():
|
||
"""清理过期缓存"""
|
||
current_time = time.time()
|
||
expired_keys = [
|
||
key for key, data in _zip_cache.items()
|
||
if current_time - data['timestamp'] > _cache_timeout
|
||
]
|
||
for key in expired_keys:
|
||
del _zip_cache[key]
|
||
|
||
if expired_keys:
|
||
logger.debug(f"清理过期缓存: {len(expired_keys)} 项")
|
||
|
||
|
||
@monitor_performance("file.autoLoadFile")
|
||
def autoLoadFile():
|
||
"""自动加载文件,优化路径处理和错误处理"""
|
||
input_dir = Path(conf.get("file", "inputdir"))
|
||
store_dir = Path(conf.get("file", "storedir"))
|
||
|
||
if not input_dir.exists():
|
||
logger.warning(f"输入目录不存在: {input_dir}")
|
||
return
|
||
|
||
file_list = []
|
||
try:
|
||
file_list = [f for f in input_dir.iterdir() if f.is_file()]
|
||
except Exception as e:
|
||
logger.error(f"读取输入目录失败: {e}")
|
||
return
|
||
|
||
processed_count = 0
|
||
for file_path in file_list:
|
||
try:
|
||
if zipfile.is_zipfile(file_path):
|
||
with zipfile.ZipFile(file_path, "r") as zip_ref:
|
||
page_count = len([f for f in zip_ref.namelist()
|
||
if f.lower().endswith((".png", ".jpg", ".jpeg", ".gif", ".bmp", ".webp"))])
|
||
if page_count > 0:
|
||
db.file.new(file_path.name, page_count)
|
||
|
||
# 移动文件到存储目录
|
||
target_path = store_dir / file_path.name
|
||
shutil.move(str(file_path), str(target_path))
|
||
|
||
logger.info(f"已添加漫画: {file_path.name}, 页数: {page_count}")
|
||
processed_count += 1
|
||
else:
|
||
logger.warning(f"ZIP文件中没有图片: {file_path.name}")
|
||
else:
|
||
logger.info(f"非ZIP文件,跳过: {file_path.name}")
|
||
except Exception as e:
|
||
logger.error(f"处理文件失败 {file_path.name}: {e}")
|
||
|
||
logger.info(f"自动加载完成,处理了 {processed_count} 个文件")
|
||
|
||
|
||
@monitor_performance("file.readZip")
|
||
def readZip(bookid: str, index: int) -> tuple:
|
||
"""
|
||
从ZIP文件中读取指定索引的图片
|
||
优化:使用缓存的文件列表,改进错误处理
|
||
返回: (image_data, filename) 或 (error_message, "")
|
||
"""
|
||
try:
|
||
bookinfo = db.file.searchByid(bookid)
|
||
if not bookinfo:
|
||
logger.warning(f"未找到书籍ID: {bookid}")
|
||
return "Book not found", ""
|
||
|
||
zip_path = Path(conf.get("file", "storedir")) / bookinfo[0][2]
|
||
|
||
if not zip_path.exists():
|
||
logger.error(f"ZIP文件不存在: {zip_path}")
|
||
return "ZIP file not found", ""
|
||
|
||
# 使用缓存获取图片文件列表
|
||
image_files, _ = get_image_files_from_zip(str(zip_path))
|
||
|
||
if not image_files:
|
||
logger.warning(f"ZIP文件中没有图片: {zip_path}")
|
||
return "No image files in zip", ""
|
||
|
||
if int(index) >= len(image_files):
|
||
logger.warning(f"图片索引超出范围: {index}, 总数: {len(image_files)}")
|
||
return "Image index out of range", ""
|
||
|
||
# 读取指定图片
|
||
with zipfile.ZipFile(zip_path, "r") as zip_ref:
|
||
image_filename = image_files[int(index)]
|
||
image_data = zip_ref.read(image_filename)
|
||
|
||
logger.debug(f"读取图片: {bookid}/{index} -> {image_filename}")
|
||
return image_data, image_filename
|
||
|
||
except zipfile.BadZipFile:
|
||
logger.error(f"损坏的ZIP文件: {bookid}")
|
||
return "Bad ZipFile", ""
|
||
except Exception as e:
|
||
logger.error(f"读取ZIP文件失败 {bookid}/{index}: {e}")
|
||
return f"Error: {str(e)}", ""
|
||
|
||
|
||
@lru_cache(maxsize=128)
|
||
def _get_image_hash(image_data: bytes) -> str:
|
||
"""生成图片数据的哈希值用于缓存"""
|
||
return hashlib.md5(image_data).hexdigest()
|
||
|
||
|
||
@cache_image
|
||
def thumbnail(input_data: bytes, min_size: int = 600, encode: str = "webp", quality: int = 75) -> bytes:
|
||
"""
|
||
生成缩略图,优化编码逻辑和性能
|
||
"""
|
||
if not input_data:
|
||
logger.warning("输入图片数据为空")
|
||
return input_data
|
||
|
||
try:
|
||
# 解码图片
|
||
img = cv2.imdecode(np.frombuffer(input_data, np.uint8), cv2.IMREAD_COLOR)
|
||
if img is None:
|
||
logger.warning("无法解码图片数据")
|
||
return input_data
|
||
|
||
height, width = img.shape[:2]
|
||
logger.debug(f"原始图片尺寸: {width}x{height}")
|
||
|
||
# 判断是否需要缩放
|
||
min_dimension = min(height, width)
|
||
if min_size < min_dimension:
|
||
# 计算新尺寸
|
||
if height > width:
|
||
new_width = min_size
|
||
new_height = int(min_size * height / width)
|
||
else:
|
||
new_height = min_size
|
||
new_width = int(min_size * width / height)
|
||
|
||
img = cv2.resize(img, (new_width, new_height), interpolation=cv2.INTER_AREA)
|
||
logger.debug(f"缩放后图片尺寸: {new_width}x{new_height}")
|
||
|
||
# 编码图片
|
||
if encode.lower() == "webp":
|
||
success, encoded_image = cv2.imencode(
|
||
".webp", img, [cv2.IMWRITE_WEBP_QUALITY, quality]
|
||
)
|
||
elif encode.lower() in ("jpg", "jpeg"):
|
||
success, encoded_image = cv2.imencode(
|
||
".jpg", img, [cv2.IMWRITE_JPEG_QUALITY, quality]
|
||
)
|
||
elif encode.lower() == "png":
|
||
success, encoded_image = cv2.imencode(
|
||
".png", img, [cv2.IMWRITE_PNG_COMPRESSION, 6]
|
||
)
|
||
else:
|
||
logger.warning(f"不支持的编码格式: {encode}, 返回原始数据")
|
||
return input_data
|
||
|
||
if not success:
|
||
logger.error(f"图片编码失败: {encode}")
|
||
return input_data
|
||
|
||
result = encoded_image.tobytes()
|
||
logger.debug(f"图片处理完成: 原始 {len(input_data)} bytes -> 处理后 {len(result)} bytes")
|
||
return result
|
||
|
||
except Exception as e:
|
||
logger.error(f"图片处理异常: {e}")
|
||
return input_data
|
||
|
||
|
||
def get_zip_image_count(bookid: str) -> int:
|
||
"""
|
||
获取ZIP文件中的图片数量,使用缓存
|
||
"""
|
||
try:
|
||
bookinfo = db.file.searchByid(bookid)
|
||
if not bookinfo:
|
||
return 0
|
||
|
||
zip_path = Path(conf.get("file", "storedir")) / bookinfo[0][2]
|
||
if not zip_path.exists():
|
||
return 0
|
||
|
||
image_files, _ = get_image_files_from_zip(str(zip_path))
|
||
return len(image_files)
|
||
|
||
except Exception as e:
|
||
logger.error(f"获取图片数量失败 {bookid}: {e}")
|
||
return 0
|