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']*name=["\']cover["\'][^>]*content=["\']([^"\']+)["\']', text, _re.IGNORECASE) if not m_meta: return None cover_id = m_meta.group(1).strip() item_pattern = _re.compile(r']*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