'update'
This commit is contained in:
148
cover_mixin.py
Normal file
148
cover_mixin.py
Normal file
@@ -0,0 +1,148 @@
|
||||
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
|
||||
Reference in New Issue
Block a user