Spaces:
Sleeping
Sleeping
Download src/utils/validation.py from so-21-si/spot-the-diff: direct link, hf CLI and curl.
- Browser
- Download file 2.87 kB
-
https://huggingface.co/spaces/so-21-si/spot-the-diff/resolve/main/src/utils/validation.py
- Command line
-
hf download hf://spaces/so-21-si/spot-the-diff/src/utils/validation.py
-
curl -L -o validation.py https://huggingface.co/spaces/so-21-si/spot-the-diff/resolve/main/src/utils/validation.py
2.87 kB
| """File upload validation utilities.""" | |
| from __future__ import annotations | |
| import uuid | |
| from pathlib import Path | |
| from PIL import Image | |
| from src.exceptions import ValidationError | |
| # Magic bytes for allowed image formats | |
| _MAGIC_BYTES = { | |
| b"\xff\xd8\xff": "jpg", | |
| b"\x89PNG": "png", | |
| } | |
| ALLOWED_EXTENSIONS = {"png", "jpg", "jpeg"} | |
| def validate_upload_file( | |
| file, | |
| max_size: int = 10 * 1024 * 1024, | |
| min_dim: int = 512, | |
| max_dim: int = 4096, | |
| ) -> dict: | |
| """Validate an uploaded image file. | |
| Returns a dict with file info on success: | |
| {"extension": str, "width": int, "height": int, "size": int} | |
| Raises ValidationError on failure. | |
| """ | |
| if file is None or file.filename == "": | |
| raise ValidationError("ファイルが選択されていません") | |
| # Check extension | |
| ext = _get_extension(file.filename) | |
| if ext not in ALLOWED_EXTENSIONS: | |
| raise ValidationError(f"対応していないファイル形式です。PNG/JPEG のみ対応しています。") | |
| # Check file size | |
| file.seek(0, 2) | |
| size = file.tell() | |
| file.seek(0) | |
| if size > max_size: | |
| mb = max_size // (1024 * 1024) | |
| raise ValidationError(f"ファイルサイズが大きすぎます(上限: {mb}MB)") | |
| if size == 0: | |
| raise ValidationError("空のファイルです") | |
| # Check magic bytes | |
| header = file.read(8) | |
| file.seek(0) | |
| if not any(header.startswith(magic) for magic in _MAGIC_BYTES): | |
| raise ValidationError("ファイルの内容が画像形式と一致しません") | |
| # Verify as valid image and check dimensions | |
| try: | |
| img = Image.open(file) | |
| img.verify() | |
| file.seek(0) | |
| img = Image.open(file) | |
| width, height = img.size | |
| file.seek(0) | |
| except Exception: | |
| raise ValidationError("画像ファイルを読み込めません") | |
| if width < min_dim or height < min_dim: | |
| raise ValidationError(f"画像が小さすぎます(最小: {min_dim}x{min_dim}px)") | |
| if width > max_dim or height > max_dim: | |
| raise ValidationError(f"画像が大きすぎます(最大: {max_dim}x{max_dim}px)") | |
| return {"extension": ext, "width": width, "height": height, "size": size} | |
| def generate_safe_filename(extension: str) -> str: | |
| """Generate a UUID-based safe filename.""" | |
| return f"{uuid.uuid4().hex}.{extension}" | |
| def validate_difficulty(value: str) -> str: | |
| """Validate difficulty parameter. Returns normalised value.""" | |
| valid = {"easy", "medium", "hard"} | |
| normalised = value.strip().lower() | |
| if normalised not in valid: | |
| raise ValidationError(f"無効な難易度です。選択可能: {', '.join(sorted(valid))}") | |
| return normalised | |
| def _get_extension(filename: str) -> str: | |
| if "." not in filename: | |
| return "" | |
| return filename.rsplit(".", 1)[1].lower() | |