Files
administrator 7e05384c1f notebookVb
2026-05-24 07:59:25 +02:00

261 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# FotkyBuzalkovi — návrh systému a poznámky z analýzy
> Pracovní dokument shrnující rozhodnutí a poznatky z prvotní analýzy projektu.
> Datum diskuze: 2026-05-21
---
## 1. Cíl projektu
- Organizovat a tagovat **cca 200 000** rodinných fotografií
- Lokální nasazení (žádný cloud, žádní externí uživatelé)
- Současný stav: prázdný projekt s ukázkovými fotkami v `demo_fotky/`
---
## 2. Architektonické rozhodnutí
### Finální stack: **jen PostgreSQL + filesystem**
```
┌─────────────────────┐
│ Python aplikace │
└──────────┬──────────┘
┌──────┴──────┐
▼ ▼
┌─────────┐ ┌──────────┐
│PostgreSQL│ │Filesystem│
│ metadata │ │ .jpg/.png│
└─────────┘ └──────────┘
```
### Co bylo zvažováno a zavrženo
| Technologie | Verdikt | Důvod |
|-------------|---------|-------|
| MongoDB | ❌ vynecháno | `JSONB` v PostgreSQL nahradí veškerou potřebnou funkcionalitu |
| Redis | ❌ vynecháno (zatím) | Pro lokální 200k fotek bez webových uživatelů zbytečný |
| MySQL | ❌ vynecháno | Projekt používá PostgreSQL (jiná databáze byla v původním plánu) |
### Kdy přidat Redis (pozdější fáze)
- Web UI s rychlým vyhledáváním (cache výsledků)
- Paralelní workery pro import / generování thumbnailů
- Background fronty (zpracování AI tagů)
### Kdy přidat něco dalšího
- **`pgvector`** extension pro PostgreSQL — až budeme chtít sémantické hledání (CLIP embeddings)
- **Elasticsearch** — kdyby PostgreSQL fulltext nestačil (u 200k řádků nestane)
---
## 3. JSONB v PostgreSQL vs MongoDB BSON
### Terminologie
- **BSON** = binární JSON od MongoDB (s typy jako `ObjectId`, `Date`, `Decimal128`)
- **JSONB** = binární JSON v PostgreSQL (vlastní formát, ne BSON, ale funkčně podobný)
### Srovnání vyhledávání
**MongoDB:**
```javascript
db.photos.find({ "exif.camera": "Canon EOS 5D" })
db.photos.find({ "exif.iso": { $gte: 800 } })
db.photos.find({ "tags": { $in: ["dovolená", "moře"] } })
```
**PostgreSQL JSONB:**
```sql
SELECT * FROM photos WHERE exif_data->>'camera' = 'Canon EOS 5D';
SELECT * FROM photos WHERE (exif_data->>'iso')::int >= 800;
SELECT * FROM photos WHERE exif_data @> '{"camera": "Canon EOS 5D"}';
```
### Co umí stejně
| Funkce | MongoDB | PostgreSQL JSONB |
|--------|---------|------------------|
| Filtr podle pole v JSON | ano | ano (`->>`, `@>`) |
| Vnořené cesty | `"a.b.c"` | `data#>>'{a,b,c}'` |
| Existence klíče | `$exists` | `?` operátor |
| Index na JSON cestu | ano | ano (GIN) |
| Fulltext | ano | ano |
### Výhody PostgreSQL JSONB pro náš případ
1. **JOINy** s tabulkami tagů a kamer — v Mongo by to vyžadovalo `$lookup` (pomalé)
2. **Transakce** napříč JSONB a relačními tabulkami
3. **Jeden backup nástroj** (`pg_dump`)
---
## 4. Identita fotky — 4 úrovně hashů
**Klíčový problém:** Když změníte byť jediný keyword v IPTC, SHA256 souboru se kompletně změní. Jak detekovat, že je to stále ta samá fotka?
```
┌─────────────────────────────────────────────────────────────┐
│ Stejná identita Stejný obsah │
│ ←─────────────────────────────────────────────────────→ │
│ │
│ SHA256 Pixel hash Perceptual Embedding │
│ souboru (jen pixely) hash (pHash) (CLIP/DINO) │
│ │
│ byte-by-byte metadata +recomprese +sémantika │
│ identické ignorováno +resize podobnost │
│ +crop │
└─────────────────────────────────────────────────────────────┘
```
### 1. SHA256 souboru (`sha256_file`)
- Detekuje **přesnou kopii** souboru
- Změna IPTC/EXIF, otočení, znovuuložení → jiný hash
### 2. Pixel hash (`sha256_pixels`)
- SHA256 dekódovaných pixelů (po aplikaci EXIF orientation)
- **Stejná fotka po změně metadat** → identický hash
- Toto je odpověď na otázku "jak detekovat, že je to ta samá fotka po změně keywords"
```python
with Image.open(path) as img:
img = ImageOps.exif_transpose(img)
if img.mode != "RGB":
img = img.convert("RGB")
pixel_hash = hashlib.sha256(img.tobytes()).hexdigest()
```
### 3. Perceptual hash (`pHash`, `dHash`, `wHash`)
- 64-bit "otisk obsahu" — vizuálně podobné fotky → blízké hashe
- Porovnává se přes **Hamming distance** (`bin(h1 ^ h2).count("1")`)
- < 10 = velmi podobné, > 20 = odlišné
- Detekuje: recompresi, resize, drobné úpravy, watermark, crop
- Knihovna: [`imagehash`](https://pypi.org/project/ImageHash/)
### 4. Deep learning embedding (budoucnost)
- Vektor 5121024 dimenzí z CLIP / DINOv2
- Sémantická podobnost ("dvě fotky stejné scény z různých úhlů")
- Uložení v PostgreSQL přes `pgvector` extension
### Workflow při importu nové fotky
```
1. Spočítej sha256_file
2. Najdi v DB stejný sha256_file?
ANO → identická kopie, přeskočit
NE → ↓
3. Spočítej sha256_pixels
4. Najdi v DB stejný sha256_pixels?
ANO → stejná fotka, jen jiná metadata → updatuj, nepřidávej duplikát
NE → ↓
5. Spočítej phash, ulož do DB
6. (Volitelně) najdi podobné: WHERE hamming(phash, ?) < 10
```
### Poznámka k JPEG
- Před hashingem **vždy aplikovat EXIF orientation** (`ImageOps.exif_transpose`)
- Konvertovat do RGB pro konzistenci
- Jinak by se rotovaná fotka vs nerotovaná lišila
---
## 5. Tři vrstvy metadat ve fotce
| Standard | Co tam je | Kdo to vyplňuje |
|----------|-----------|-----------------|
| **EXIF** | Technická data (clona, ISO, čas, GPS, model kamery) | Kamera automaticky |
| **IPTC** | Popisná data (titulek, popis, klíčová slova, autor, copyright) | Člověk / software |
| **XMP** | Modernější nástupce IPTC od Adobe, často duplikuje + edits, ratings, regions | Software (Lightroom, Apple Photos) |
### Pro náš případ
- iPhone vyplňuje hlavně **EXIF** a **XMP** (ne IPTC)
- Apple Photos do XMP ukládá **rozpoznané obličeje** (`mwg-rs:Regions`) — i se jmény, pokud je pojmenuješ!
- iOS screenshoty mají v XMP `description: "Screenshot"` → automatická detekce
- Pokud budeme tagovat, je dobré tagy ukládat **i do IPTC `Keywords`** — přežijí export do jiné aplikace
---
## 6. Výsledky exploration na 7 demo fotkách
### Souhrn dat z `explore_photos.py`
| # | Soubor | Mpx | EXIF tagů | IPTC | XMP | GPS | Kamera |
|---|--------|-----|-----------|------|-----|-----|--------|
| 1 | 2026-04-22 09.05.08.jpg | 24.47 | 121 | 0 | 4 | ano | iPhone 16 Pro Max |
| 2 | 2026-04-24 15.15.47.jpg | 12.19 | 107 | 0 | 0 | ne | iPhone 13 Pro Max |
| 3 | 2026-05-02 10.04.44.png | 3.57 | 12 | 0 | 1 | ne | Screenshot |
| 4 | 2026-05-18 06.22.37.jpg | 12.19 | 105 | 0 | 4 | ne | iPhone 13 Pro Max |
| 5 | 2026-05-18 06.22.41.jpg | 12.19 | 105 | 0 | 4 | ne | iPhone 13 Pro Max |
| 6 | 2026-05-18 13.54.47.jpg | 12.19 | 98 | 6 | 0 | ne | iPhone 13 Pro Max |
| 7 | 2026-05-18 14.10.59.jpg | 2.36 | 0 | 0 | 0 | ne | (sirotek) |
### Klíčové objevy
1. **Perceptual hash funguje** — fotky [4] a [5] (pořízené 4 sekundy po sobě) mají Hamming distance pouze **4** = klasický burst snapshot.
2. **Apple face detection v XMP** — fotky 1, 4, 5 mají `face_regions_count: 1`. iPhone už **detekoval obličej** a uložil to do XMP. Můžeme číst přímo, bez vlastní AI.
3. **Screenshot rozpoznán** — foto [3] má v XMP `description: "Screenshot"` → automatická kategorizace.
4. **Foto [7] = sirotek** — žádné metadata. Pravděpodobně přeposlané přes WhatsApp/Messenger, kde se EXIF maže. **Pro 200k fotek bude tato kategorie významná.**
5. **GPS jen u 1/7 fotek** — u většiny iPhone fotek máte vypnuté lokační služby.
6. **Time zone** — fotky mají `OffsetTime: +02:00`. **Datum pořízení ukládat jako `TIMESTAMPTZ`** (s timezone).
7. **ExifRead > Pillow** — Pillow má GPS bug (`'int' object has no attribute 'items'`). **Primární parser bude ExifRead.**
8. **MakerNote (Apple binary blob)** — obsahuje burst ID, HDR příznak, focus distance. Pro rozluštění potřeba [`exiftool`](https://exiftool.org/) (volat přes `pyexiftool`).
---
## 7. Databázové schéma
Kompletní aktuální schéma (tabulky, sloupce, indexy) viz **[SCHEMA.md](SCHEMA.md)**.
---
## 8. Otevřené otázky
### Foto bez EXIF (sirotek typu [7])
- **a)** Importovat (s `taken_at` z `mtime`)
- **b)** Odmítnout jako "nezpracovatelnou"
- **c)** Importovat, ale označit `processing_status = 'no_metadata'`
### Detekce duplikátů (shoda `sha256_pixels`)
- **a)** Přeskočit (nepřidávat duplicitu)
- **b)** Sloučit (aktualizovat metadata u existujícího záznamu)
- **c)** Uložit oba, jen označit jako související
### Storage layout fotek
- **a)** Necháme v aktuálním umístění, do DB jen cestu
- **b)** Kopie do `archiv/YYYY/MM/původní_název.jpg`
- **c)** Kopie do `archiv/{sha[:2]}/{sha}.jpg` (content-addressable = auto-dedup na FS)
---
## 9. Co dál — nápady k prozkoumání
- **`exiftool`** — rozluští Apple MakerNote (burst ID by potvrdilo, že [4] a [5] patří k sobě)
- **Embedded thumbnail z EXIF** — telefon ukládá malou náhledovku přímo v souboru → rychlejší galerie bez generování
- **CLIP embeddings + pgvector** — sémantické vyhledávání ("ukaž fotky pejsků na pláži")
- **Reverse geocoding** — z GPS souřadnic na čitelné místo (Nominatim / Photon, lokálně)
- **Apple Photos jména osob** — z XMP `mwg-rs:Regions` jdou číst i jména, pokud jsou v Photos pojmenovaná
- **Datum z názvu souboru** — fallback když chybí EXIF i sensible `mtime` (regex z "2026-05-18 13.54.47.jpg")
---
## 10. Aktuální stav a struktura projektu
Viz **[README.md](README.md)** — hlavní rozcestník projektu.