diff --git a/api/metadata.py b/api/metadata.py index 448e620..150a431 100644 --- a/api/metadata.py +++ b/api/metadata.py @@ -33,10 +33,6 @@ def _extract_safetensors_hash(file_path): if 'modelspec.hash.blake3' in metadata: return metadata['modelspec.hash.blake3'] - for k, v in metadata.items(): - if ('hash' in k.lower() or 'civitai' in k.lower()) and isinstance(v, str): - if len(v) == 64 and all(c in '0123456789abcdefABCDEF' for c in v): - return v except Exception: pass return None diff --git a/api/scanner.py b/api/scanner.py index 04816e2..2501e0f 100644 --- a/api/scanner.py +++ b/api/scanner.py @@ -91,6 +91,7 @@ async def api_scan_folder(request): skip_rename = data.get("skip_rename", False) virtual_rename = data.get("virtual_rename", False) physical_rename = data.get("physical_rename", False) + force_overwrite = data.get("force_overwrite", False) try: marker_file = os.path.join(target_dir, '.scan_in_progress') @@ -106,6 +107,8 @@ async def api_scan_folder(request): cmd.append("--virtual-rename") if physical_rename: cmd.append("--physical-rename") + if force_overwrite: + cmd.append("--force-overwrite") subprocess.run( cmd, @@ -147,6 +150,7 @@ async def api_scan_all(request): skip_rename = data.get("skip_rename", True) virtual_rename = data.get("virtual_rename", False) physical_rename = data.get("physical_rename", False) + force_overwrite = data.get("force_overwrite", False) try: with open(marker_file, 'w') as f: f.write('1') @@ -170,6 +174,8 @@ async def api_scan_all(request): cmd.append("--virtual-rename") if physical_rename: cmd.append("--physical-rename") + if force_overwrite: + cmd.append("--force-overwrite") if not use_local_metadata: cmd.append("--skip-local-metadata") subprocess.run( @@ -254,6 +260,13 @@ async def api_scan_missing_models(request): except ImportError: return web.json_response({"status": "error", "message": "Failed to load scraper module"}) + try: + data = await request.json() + except Exception: + data = {} + + force_overwrite = data.get("force_overwrite", False) + def run_deep_scan(): GLOBAL_SCAN_STATE["scanning"] = True GLOBAL_SCAN_STATE["total"] = 0 @@ -282,6 +295,10 @@ async def api_scan_missing_models(request): # Check if a valid info file exists has_valid_info = False actual_info = info_path if os.path.exists(info_path) else (civitai_info_path if os.path.exists(civitai_info_path) else None) + + if force_overwrite: + actual_info = None + if actual_info: try: with open(actual_info, 'r', encoding='utf-8') as f: diff --git a/scraper.py b/scraper.py index a3bcc01..36637bd 100644 --- a/scraper.py +++ b/scraper.py @@ -72,11 +72,6 @@ def extract_safetensors_hash(file_path: str) -> Optional[str]: if 'modelspec.hash.blake3' in metadata: return metadata['modelspec.hash.blake3'] - # 优先级2: 其他带 hash 的键 - for k, v in metadata.items(): - if ('hash' in k.lower() or 'civitai' in k.lower()) and isinstance(v, str): - if len(v) == 64 and all(c in '0123456789abcdefABCDEF' for c in v): - return v except Exception as e: pass return None @@ -200,6 +195,7 @@ def main(): parser.add_argument("--physical-rename", action="store_true", help="物理重命名:真实修改底层的 safetensors 及其附属文件名") parser.add_argument("--skip-media", action="store_true", help="不下载预览图或视频") parser.add_argument("--offline-only", action="store_true", help="跳过 Civitai 联网获取,强制使用本地脱机张量推断提取 Base Model") + parser.add_argument("--force-overwrite", action="store_true", help="强制覆盖已存在的信息文件") args = parser.parse_args() target_folder = args.folder @@ -268,6 +264,9 @@ def main(): old_base = os.path.splitext(file_path)[0] info_exists = os.path.exists(old_base + ".info") or os.path.exists(old_base + ".civitai.info") + if args.force_overwrite: + info_exists = False + preview_exists = args.skip_media if not preview_exists: for ext in [".png", ".jpg", ".jpeg", ".webp", ".gif", ".avif", ".mp4", ".webm", ".mov", ".avi", ".preview.png", ".preview.jpg", ".preview.jpeg", ".preview.webp", ".preview.gif", ".preview.avif", ".preview.mp4", ".preview.webm", ".preview.mov", ".preview.avi"]: diff --git a/web/main.js b/web/main.js index 4a2874a..a18d340 100644 --- a/web/main.js +++ b/web/main.js @@ -476,6 +476,7 @@ class AnomalousBrowser { let scanMode = 'civitai'; let enableRename = true; let enableAutoCheck = true; + let enableForceOverwrite = false; let updateSections = () => { }; const formGroup = document.createElement('div'); @@ -735,6 +736,7 @@ class AnomalousBrowser { const s3List = document.createElement('div'); const isInject = localStorage.getItem('anomalous_inject_hash') !== 'false'; s3List.appendChild(createListRow('📦', '模型溯源绑定', 'Model Provenance Binding', '全局生效。每次生成图片或保存工作流时,将模型的精确哈希值隐式写入到图像元数据与工作流 JSON 中,防止未来模型改名或换环境后报错找不到。', 'Global effect. Implicitly embed exact model hashes into generated images and saved workflows, ensuring missing models can always be auto-fixed.', createMaterialSwitch(isInject, (s) => localStorage.setItem('anomalous_inject_hash', s ? 'true' : 'false')))); + s3List.appendChild(createListRow('⚠️', '强制覆盖已有配置', 'Force Overwrite Configs', '扫描时忽略本地已存在的 .info 文件,强制重新计算哈希并覆盖下载。用于修复匹配错乱的模型。', 'Ignore existing .info files and force re-scan/overwrite. Useful for fixing mismatched models.', createMaterialSwitch(enableForceOverwrite, (s) => enableForceOverwrite = s))); s3List.appendChild(createListRow('🪄', '智能修复当前工作流', 'Smart Fix Current Workflow', '扫描完成后,自动尝试用最新数据修复当前画布中提示缺失的报错模型节点。', 'After scanning, automatically attempt to fix missing model errors on the current canvas.', createMaterialSwitch(enableAutoCheck, (s) => enableAutoCheck = s))); s3List.lastChild.style.borderBottom = 'none'; @@ -764,7 +766,8 @@ class AnomalousBrowser { offline_only: scanMode === 'offline', skip_rename: !enableRename, virtual_rename: enableRename ? enableVirtualRename : false, - physical_rename: enableRename ? enablePhysicalRename : false + physical_rename: enableRename ? enablePhysicalRename : false, + force_overwrite: enableForceOverwrite }; let targetUrl = '';