From 7cbddd9cf7b3164f0fdd062d9a7b2bc4a477dfd0 Mon Sep 17 00:00:00 2001 From: Will Miao Date: Wed, 10 Jun 2026 15:06:56 +0800 Subject: [PATCH] fix(recipe): fall back to original image for metadata extraction when optimized lacks embedded data (#968) When CivitAI API returns meta=null and the optimized CDN image has no embedded generation parameters (e.g. PNG tEXt chunks stripped by Cloudflare Images), download the original image as fallback to recover full recipe metadata (prompt, seed, LoRAs, etc.). Also fixes Chrome password manager popping up on recipe save by adding autocomplete="new-password" to the settings API key and proxy password fields. --- py/routes/handlers/recipe_handlers.py | 57 ++++++++++++++++++- py/services/recipes/analysis_service.py | 18 ++++++ .../components/modals/settings_modal.html | 2 + 3 files changed, 75 insertions(+), 2 deletions(-) diff --git a/py/routes/handlers/recipe_handlers.py b/py/routes/handlers/recipe_handlers.py index e9402b97..ac48a325 100644 --- a/py/routes/handlers/recipe_handlers.py +++ b/py/routes/handlers/recipe_handlers.py @@ -907,6 +907,7 @@ class RecipeManagementHandler: extension, civitai_meta_raw, model_version_id, + _original_image_url, ) = await self._download_remote_media(image_url) # Extract embedded EXIF metadata (offloaded to thread pool in this call) @@ -1319,7 +1320,9 @@ class RecipeManagementHandler: "exclude": False, } - async def _download_remote_media(self, image_url: str) -> tuple[bytes, str, Any, Any]: + async def _download_remote_media( + self, image_url: str + ) -> tuple[bytes, str, Any, Any, Optional[str]]: civitai_client = self._civitai_client_getter() downloader = await self._downloader_factory() temp_path = None @@ -1394,11 +1397,16 @@ class RecipeManagementHandler: if mvids and isinstance(civitai_meta_raw, dict): civitai_meta_raw["modelVersionIds"] = mvids + original_url = ( + image_info.get("url") if civitai_image_id and image_info else None + ) + return ( file_obj.read(), extension, civitai_meta_raw, model_ver_id, + original_url, ) except RecipeDownloadError: raise @@ -1550,7 +1558,7 @@ class RecipeManagementHandler: "Could not extract Civitai image ID from URL" ) - image_bytes, extension, civitai_meta_raw, model_version_id = ( + image_bytes, extension, civitai_meta_raw, model_version_id, original_image_url = ( await self._download_remote_media(image_url) ) @@ -1588,6 +1596,51 @@ class RecipeManagementHandler: "Failed to extract embedded metadata: %s", exc ) + if not parsed_embedded and original_image_url: + self._logger.debug( + "Optimized image has no embedded metadata, " + "falling back to original: %s", + original_image_url, + ) + try: + downloader = await self._downloader_factory() + with tempfile.NamedTemporaryFile( + suffix=".png", delete=False + ) as tmp: + orig_tmp_path = tmp.name + try: + success, _ = await downloader.download_file( + original_image_url, orig_tmp_path, use_auth=False + ) + if success: + raw_orig = await asyncio.to_thread( + ExifUtils.extract_image_metadata, orig_tmp_path + ) + if raw_orig: + parser = ( + self._analysis_service._recipe_parser_factory.create_parser( + raw_orig + ) + ) + if parser: + parsed_embedded = await parser.parse_metadata( + raw_orig, recipe_scanner=recipe_scanner + ) + if ( + parsed_embedded + and "gen_params" in parsed_embedded + ): + embedded_gen_params = parsed_embedded[ + "gen_params" + ] + finally: + if os.path.exists(orig_tmp_path): + os.unlink(orig_tmp_path) + except Exception as exc: + self._logger.warning( + "Failed to extract metadata from original image: %s", exc + ) + # Parse CivitAI API meta to discover all resources from modelVersionIds. # Run unconditionally — EXIF parsing succeeds for gen_params but misses # LoRAs (modelVersionIds is NOT in the image EXIF). diff --git a/py/services/recipes/analysis_service.py b/py/services/recipes/analysis_service.py index 04bca09c..5e503424 100644 --- a/py/services/recipes/analysis_service.py +++ b/py/services/recipes/analysis_service.py @@ -176,6 +176,24 @@ class RecipeAnalysisService: self._exif_utils.extract_image_metadata, temp_path ) + if not metadata and civitai_image_id and image_info: + original_url = image_info.get("url") + if original_url: + self._logger.debug( + "Optimized image lacks embedded metadata, " + "falling back to original image: %s", + original_url, + ) + orig_temp_path = self._create_temp_path(suffix=".png") + try: + await self._download_image(original_url, orig_temp_path) + metadata = await asyncio.to_thread( + self._exif_utils.extract_image_metadata, + orig_temp_path, + ) + finally: + self._safe_cleanup(orig_temp_path) + result = await self._parse_metadata( metadata or {}, recipe_scanner=recipe_scanner, diff --git a/templates/components/modals/settings_modal.html b/templates/components/modals/settings_modal.html index f6bbf73b..1dd221de 100644 --- a/templates/components/modals/settings_modal.html +++ b/templates/components/modals/settings_modal.html @@ -104,6 +104,7 @@ id="civitaiApiKey" placeholder="{{ t('settings.civitaiApiKeyPlaceholder') }}" value="{{ settings.get('civitai_api_key', '') }}" + autocomplete="new-password" onblur="settingsManager.saveInputSetting('civitaiApiKey', 'civitai_api_key')" onkeydown="if(event.key === 'Enter') { this.blur(); }" />