# 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 512–1024 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.
