notebookVb
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
# 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);
|
||||
```
|
||||
Reference in New Issue
Block a user