fix: add media cache limits and restore multi-image video references#477
fix: add media cache limits and restore multi-image video references#477PigeonXianxian wants to merge 1 commit intochenyme:mainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR improves local media handling by adding cache size limits with eviction, restoring multi-image reference support for video generation, and making local proxy image URLs work more consistently through the image/video flows.
Changes:
- Add configurable local media cache limits (total + per-type) with oldest-first eviction and expose settings in the admin config UI/i18n.
- Restore multi-image reference extraction and payload construction for video generation (including placeholder replacement).
- Adjust image output resolution and local caching behavior (including better file-id extraction from upstream asset URLs).
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/test_video_reference_helpers.py | Adds coverage for placeholder replacement, multi-image extraction, and video payload reference fields. |
| tests/test_media_cache_limits.py | Adds coverage for cache eviction when per-type or total limits are exceeded. |
| tests/test_image_output_format.py | Adds coverage for local proxy URL output and local save behavior. |
| config.defaults.toml | Introduces new [storage] cache limit defaults. |
| app/statics/i18n/zh.json | Adds i18n strings for storage config group and cache limit fields (ZH). |
| app/statics/i18n/en.json | Adds i18n strings for storage config group and cache limit fields (EN). |
| app/statics/admin/config.html | Exposes new storage/cache-limit settings in the admin config UI. |
| app/products/openai/video.py | Restores multi-reference image support for video generation; integrates cache-saving helper for videos. |
| app/products/openai/router.py | Updates /v1/videos to accept multiple uploaded reference images (including input_reference[]). |
| app/products/openai/images.py | Improves upstream asset ID extraction; integrates cache-saving helper; adjusts output formatting logic. |
| app/products/openai/chat.py | Integrates cache-saving helper for locally stored chat images. |
| app/products/openai/init.py | Switches to lazy export of router via __getattr__. |
| app/platform/storage/media_cache.py | Adds the new cache writer + eviction logic based on configured MB limits. |
| app/platform/storage/init.py | Exports save_media_bytes from the storage package. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| def _enforce_limits_locked(media_type: MediaType) -> None: | ||
| removed = _prune_paths(_list_files(media_type), _specific_limit_bytes(media_type)) | ||
| if removed: | ||
| logger.info( | ||
| "media cache pruned: scope={} removed_count={}", | ||
| media_type, | ||
| len(removed), | ||
| ) | ||
|
|
||
| removed = _prune_paths(_list_files(), _total_limit_bytes()) | ||
| if removed: |
There was a problem hiding this comment.
_enforce_limits_locked() always calls _list_files() (directory glob) even when the configured limit is disabled (0). This adds unnecessary O(N) filesystem scans on every media write. Consider computing the specific/total limit first and skipping _list_files/_prune_paths entirely when the corresponding limit_bytes <= 0.
|
|
||
| def save_media_bytes(raw: bytes, path: Path, *, media_type: MediaType) -> Path: | ||
| path.parent.mkdir(parents=True, exist_ok=True) | ||
| with _CACHE_LOCK: | ||
| if not path.exists(): | ||
| path.write_bytes(raw) | ||
| _enforce_limits_locked(media_type) |
There was a problem hiding this comment.
save_media_bytes() writes the file and then prunes the cache. If the newly written file itself causes the cache to exceed the limit (e.g., file size > limit), _prune_paths() can delete the just-written path, but save_media_bytes() still returns that Path to callers. Consider pruning before writing to make room, and/or ensuring the returned path still exists (or raising a clear error) when the file cannot be retained under the configured limits.
| def save_media_bytes(raw: bytes, path: Path, *, media_type: MediaType) -> Path: | |
| path.parent.mkdir(parents=True, exist_ok=True) | |
| with _CACHE_LOCK: | |
| if not path.exists(): | |
| path.write_bytes(raw) | |
| _enforce_limits_locked(media_type) | |
| def _prepare_capacity_locked(media_type: MediaType, incoming_size: int) -> None: | |
| specific_limit = _specific_limit_bytes(media_type) | |
| if 0 < specific_limit < incoming_size: | |
| raise OSError( | |
| f"media cache write exceeds configured {media_type} cache limit: " | |
| f"size={incoming_size} limit={specific_limit}" | |
| ) | |
| total_limit = _total_limit_bytes() | |
| if 0 < total_limit < incoming_size: | |
| raise OSError( | |
| "media cache write exceeds configured total cache limit: " | |
| f"size={incoming_size} limit={total_limit}" | |
| ) | |
| if specific_limit > 0: | |
| removed = _prune_paths(_list_files(media_type), max(specific_limit - incoming_size, 0)) | |
| if removed: | |
| logger.info( | |
| "media cache pruned before write: scope={} removed_count={}", | |
| media_type, | |
| len(removed), | |
| ) | |
| if total_limit > 0: | |
| removed = _prune_paths(_list_files(), max(total_limit - incoming_size, 0)) | |
| if removed: | |
| logger.info("media cache pruned before write: scope=all removed_count={}", len(removed)) | |
| def save_media_bytes(raw: bytes, path: Path, *, media_type: MediaType) -> Path: | |
| path.parent.mkdir(parents=True, exist_ok=True) | |
| with _CACHE_LOCK: | |
| if not path.exists(): | |
| _prepare_capacity_locked(media_type, len(raw)) | |
| path.write_bytes(raw) | |
| _enforce_limits_locked(media_type) | |
| if not path.exists(): | |
| raise OSError(f"media cache could not retain file within configured limits: {path}") |
Summary
This PR bundles three related improvements around local media handling and video generation inputs:
storage.media_max_mbstorage.image_max_mbstorage.video_max_mbThe cache limit change is mainly to prevent local cached media from accumulating indefinitely and eventually exhausting disk space.
Testing
pytest -q tests/test_media_cache_limits.py tests/test_image_output_format.py tests/test_video_reference_helpers.pyloguruorjsonRelated