iBook/cover_mixin.py

149 lines
6.0 KiB
Python

import os
import re
from urllib.parse import unquote
import config
class CoverMixin:
"""封面处理相关方法抽取。依赖 self.book_toc_textedit / self._cover_labels / self._cover_pixmaps_original / self.cover_ratio / self.cover_elastic."""
def find_book_cover(self, assetid, book_info):
book_path = book_info.get('path', '')
if not book_path:
return None
book_dir = os.path.join(config.IBOOKS_BOOKS_DIR, book_path)
if os.path.isfile(book_dir):
book_dir = os.path.dirname(book_dir)
if not os.path.isdir(book_dir):
return None
for fname in os.listdir(book_dir):
if re.fullmatch(r'cover\.(?i:jpg|jpeg|png)', fname):
p = os.path.join(book_dir, fname)
if os.path.isfile(p):
return p
cover_htmls = []
for root, dirs, files in os.walk(book_dir):
for fname in files:
if re.match(r'cover.*\.(?i:x?html?)', fname):
cover_htmls.append(os.path.join(root, fname))
for html_path in cover_htmls:
filename = self.extract_cover_filename_from_html(html_path)
if not filename:
continue
found = self.search_filename_in_tree(book_dir, filename)
if found:
return found
opf_paths = []
for root, dirs, files in os.walk(book_dir):
for fname in files:
if fname.lower().endswith('.opf'):
opf_paths.append(os.path.join(root, fname))
for opf in opf_paths:
candidate = self.extract_cover_from_opf(opf, book_dir)
if candidate and os.path.exists(candidate):
return candidate
return None
def search_filename_in_tree(self, root_dir, target_name):
candidates = {target_name}
decoded = unquote(target_name)
candidates.add(decoded)
lower_set = {c.lower() for c in candidates}
for root, dirs, files in os.walk(root_dir):
for fname in files:
if fname in candidates:
return os.path.join(root, fname)
for root, dirs, files in os.walk(root_dir):
for fname in files:
if fname.lower() in lower_set:
return os.path.join(root, fname)
return None
def extract_cover_filename_from_html(self, html_path):
try:
with open(html_path, 'r', encoding='utf-8') as f:
content = f.read()
m = re.search(r'xlink:href="([^"]+\.(?:jpg|jpeg|png))"', content, re.IGNORECASE)
if not m:
m = re.search(r'="([^"]+\.(?:jpg|jpeg|png))"', content, re.IGNORECASE)
if not m:
return None
rel = m.group(1).strip()
filename = os.path.basename(unquote(rel))
return filename
except Exception as e:
print(f"解析 {html_path} 提取封面文件名失败: {e}")
return None
def extract_cover_from_opf(self, opf_path, base_root):
import re as _re
try:
with open(opf_path, 'r', encoding='utf-8') as f:
text = f.read()
m_meta = _re.search(r'<meta[^>]*name=["\']cover["\'][^>]*content=["\']([^"\']+)["\']', text, _re.IGNORECASE)
if not m_meta:
return None
cover_id = m_meta.group(1).strip()
item_pattern = _re.compile(r'<item\b[^>]*id=["\']%s["\'][^>]*>' % _re.escape(cover_id), _re.IGNORECASE)
m_item = item_pattern.search(text)
if not m_item:
return None
tag = m_item.group(0)
m_href = _re.search(r'href=["\']([^"\']+)["\']', tag, _re.IGNORECASE)
if not m_href:
return None
href = unquote(m_href.group(1).strip())
img_path = os.path.normpath(os.path.join(os.path.dirname(opf_path), href))
if os.path.exists(img_path):
return img_path
fallback = self.search_filename_in_tree(base_root, os.path.basename(href))
return fallback
except Exception as e:
print(f"解析 OPF 封面失败 {opf_path}: {e}")
return None
# 封面缩放与控制
def _apply_cover_scale(self):
if not hasattr(self, '_cover_pixmaps_original'):
return
from PyQt6.QtCore import Qt
fixed_w = 180
ratio_h = int(fixed_w * getattr(self, 'cover_ratio', 1.2))
hard_cap = 400
if not getattr(self, 'cover_elastic', False):
target_h = min(ratio_h, hard_cap)
else:
container_cap = int(self.book_toc_textedit.height() * 0.45) if hasattr(self, 'book_toc_textedit') else ratio_h
if getattr(self, '_force_ratio_height', False):
target_h = min(ratio_h, hard_cap)
else:
target_h = min(ratio_h, container_cap, hard_cap)
if target_h < ratio_h and getattr(self, 'debug_cover_ratio', False):
print(f"[cover] ratio_h={ratio_h} 被限制为 {target_h}")
for pm, lab in zip(self._cover_pixmaps_original, self._cover_labels):
lab.setMinimumSize(fixed_w, target_h)
lab.setMaximumWidth(fixed_w)
lab.setScaledContents(True)
if pm:
scaled = pm.scaled(fixed_w, target_h, Qt.AspectRatioMode.IgnoreAspectRatio, Qt.TransformationMode.SmoothTransformation)
lab.setPixmap(scaled)
else:
lab.setText('无封面')
def set_cover_ratio(self, ratio: float, force: bool = False):
try:
r = float(ratio)
if r <= 0:
return
self.cover_ratio = r
self._force_ratio_height = bool(force)
self._apply_cover_scale()
except Exception:
pass
def set_cover_elastic(self, elastic: bool):
try:
self.cover_elastic = bool(elastic)
self._apply_cover_scale()
except Exception:
pass