149 lines
6.0 KiB
Python
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
|