# verify_photos.py Verifikace fotek v databázi `fotky_buzalkovi` — pro každý záznam v tabulce `photos` zkusí soubor otevřít a dekódovat, zapíše nalezené chyby do tabulky `photo_errors` a označí `photos.verified_at = NOW()`. ## Závislosti - Migrace `migrations/002_add_photo_errors.sql` (tabulka `photo_errors` + sloupec `photos.verified_at` + partial index). - `.env` s `DB_HOST`, `DB_PORT`, `DB_NAME`, `DB_USER`, `DB_PASSWORD` (hledá se vedle skriptu i v rodičovském adresáři). - Python balíčky: `psycopg2-binary`, `Pillow`, `python-dotenv`. ## Cesty napříč prostředími Stejný princip jako `collect_pictures_*.py` a `generate_thumbnails.py`: | Prostředí | Lokální mount | |---|---| | Windows | `\\Tower1\ZalohaVsechObrazku\...` | | Tower1 (Unraid) | `/mnt/user/ZalohaVsechObrazku/...` | | tower / ostatní Linux | `/mnt/remotes/TOWER1.LAN_ZalohaVsechObrazku/...` | V DB jsou cesty vždy v nativním Tower1 formátu (`/mnt/user/...`) — skript si je za běhu přemapuje. ## Konfigurace (na začátku skriptu) ```python MAX_PHOTOS = 0 # 0 = všechny, jinak limit RESET = False # True = TRUNCATE photo_errors + verified_at = NULL REVERIFY_ALL = False # True = ignoruj verified_at, projdi všechny znovu BATCH_SIZE = 500 SIZE_MIN_BYTES = 10 * 1024 # < 10 KB → size_suspicious SIZE_MAX_BYTES = 50 * 1024 * 1024 # > 50 MB → size_suspicious ``` ## Spuštění ```bash python verify_photos.py # produkce python verify_photos.py --dry-run # nic neukládá, jen loguje python verify_photos.py --batch-size 200 ``` ## Co skript dělá s každou fotkou 1. **Existence souboru** — `local_path.is_file()` 2. **Velikost** — `stat().st_size` 3. **Pillow `verify()`** — kontrola JPEG/PNG hlavičky 4. **Pillow `load()` (strict)** — plný dekód pixelů. Pokud spadne na truncation, zkusí znovu s `ImageFile.LOAD_TRUNCATED_IMAGES = True`. 5. **`dimension_mismatch`** — porovná `photos.width/height` s hlavičkou dekódovaného obrázku. 6. **Info checks** — `missing_exif`, `missing_datetime_original`, `missing_gps`. 7. Pro každou fotku v jedné transakci: - `DELETE FROM photo_errors WHERE photo_id = ?` - `INSERT INTO photo_errors (...)` — všechny nalezené chyby - `UPDATE photos SET verified_at = NOW() WHERE id = ?` ## Kategorie chyb ### 🔴 `critical` — soubor je nepoužitelný | `error_code` | Význam | |---|---| | `file_missing` | Cesta v DB existuje, soubor na disku ne | | `read_error` | IOError / Permission / SMB timeout / `stat()` selhal | | `invalid_format` | Pillow neumí soubor rozpoznat ani otevřít | | `decode_failed` | Hlavička OK, ale `load()` praskne (vážně poškozená data) | | `zero_size` | `file_size == 0` | ### 🟡 `warning` — soubor lze použít, ale má vadu | `error_code` | Význam | |---|---| | `truncated` | `load()` strict praskne, ale s `LOAD_TRUNCATED_IMAGES=True` dojede | | `exif_parse_error` | EXIF blok má vadu (zatím rezervováno) | | `dimension_mismatch` | DB `width/height` ≠ skutečné rozměry souboru | | `size_suspicious` | < 10 KB nebo > 50 MB | ### 🔵 `info` — jen poznámka, nic není rozbité | `error_code` | Význam | |---|---| | `missing_exif` | Žádný `exif_raw` | | `missing_datetime_original` | Není `EXIF DateTimeOriginal` | | `missing_gps` | Žádné `gps_lat` / `gps_lon` | ## Výběr fotek - Default: `WHERE verified_at IS NULL` (díky partial indexu rychlé) - `REVERIFY_ALL=True` → projede všechny - `RESET=True` → `TRUNCATE photo_errors` + `verified_at = NULL`, pak plná verifikace ## Idempotence Před zápisem se vždy smažou všechny `photo_errors` pro danou `photo_id`, takže každý běh nahradí předchozí výsledek. Historie chyb se neudržuje. ## Užitečné dotazy ### Přehled chyb podle typu ```sql SELECT severity, error_code, COUNT(*) AS cnt FROM photo_errors GROUP BY 1, 2 ORDER BY 1, 3 DESC; ``` ### Počet unikátních postižených fotek ```sql SELECT severity, COUNT(DISTINCT photo_id) AS photos FROM photo_errors GROUP BY severity; ``` ### Truncated fotky ```sql SELECT p.id, p.file_path, p.file_size FROM photo_errors e JOIN photos p ON p.id = e.photo_id WHERE e.error_code = 'truncated' ORDER BY p.file_size DESC; ``` ### Kritické chyby ```sql SELECT p.id, p.file_path, e.error_code, e.error_message FROM photo_errors e JOIN photos p ON p.id = e.photo_id WHERE e.severity = 'critical' ORDER BY e.error_code, p.id; ``` ### Pokrok verifikace ```sql SELECT COUNT(*) FILTER (WHERE verified_at IS NOT NULL) AS verified, COUNT(*) FILTER (WHERE verified_at IS NULL) AS remaining, COUNT(*) AS total FROM photos; ``` ### Fotky bez nalezených chyb (čisté) ```sql SELECT COUNT(*) FROM photos p WHERE p.verified_at IS NOT NULL AND NOT EXISTS (SELECT 1 FROM photo_errors e WHERE e.photo_id = p.id); ``` ## Doporučený workflow při prvním spuštění 1. Spustit migraci `002_add_photo_errors.sql` v Navicatu. 2. `MAX_PHOTOS=20`, `--dry-run` — vidíš v logu, co by se zapsalo. 3. `MAX_PHOTOS=100`, ostrý běh — ověříš zápis do DB. 4. `MAX_PHOTOS=0`, ostrý běh — plná verifikace. ## Schéma `photo_errors` ```sql CREATE TABLE photo_errors ( id BIGSERIAL PRIMARY KEY, photo_id BIGINT NOT NULL REFERENCES photos(id) ON DELETE CASCADE, severity VARCHAR(20) NOT NULL CHECK (severity IN ('critical', 'warning', 'info')), error_code VARCHAR(50) NOT NULL, error_message TEXT, detected_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX idx_photo_errors_photo_id ON photo_errors (photo_id); CREATE INDEX idx_photo_errors_severity ON photo_errors (severity); CREATE INDEX idx_photo_errors_code ON photo_errors (error_code); ```