diff --git a/.gitignore b/.gitignore index 5634a16..2db1c93 100644 --- a/.gitignore +++ b/.gitignore @@ -68,4 +68,4 @@ ENV.bak/ # Generovaná data (velké soubory, nepatří do gitu) output/ -photo_exploration.json +30 SběrDat/photo_exploration.json diff --git a/10_collect_metadata.py b/30 SběrDat/10_collect_metadata.py similarity index 100% rename from 10_collect_metadata.py rename to 30 SběrDat/10_collect_metadata.py diff --git a/create_schema.py b/30 SběrDat/create_schema.py similarity index 100% rename from create_schema.py rename to 30 SběrDat/create_schema.py diff --git a/explore_photos.py b/30 SběrDat/explore_photos.py similarity index 100% rename from explore_photos.py rename to 30 SběrDat/explore_photos.py diff --git a/import_to_db.py b/30 SběrDat/import_to_db.py similarity index 100% rename from import_to_db.py rename to 30 SběrDat/import_to_db.py diff --git a/NAVRH.md b/NAVRH.md index 0ab2e8a..1b6854f 100644 --- a/NAVRH.md +++ b/NAVRH.md @@ -216,84 +216,13 @@ with Image.open(path) as img: --- -## 7. Návrh databázového schématu (draft) +## 7. Databázové schéma -```sql -CREATE TABLE photos ( - id BIGSERIAL PRIMARY KEY, - - -- identita (3 úrovně) - sha256_file CHAR(64) UNIQUE NOT NULL, -- byte identita - sha256_pixels CHAR(64), -- pixel identita - phash BIGINT, -- vizuální podobnost - - -- soubor - file_path VARCHAR(1000) NOT NULL, - file_name VARCHAR(255) NOT NULL, - file_size BIGINT, - mime_type VARCHAR(50), - format VARCHAR(20), -- JPEG, PNG, HEIC, ... - width INT, - height INT, - - -- pořízení - taken_at TIMESTAMPTZ, -- s timezone (máme OffsetTime!) - taken_at_source VARCHAR(20), -- 'exif' / 'mtime' / 'iptc' / 'unknown' - - -- technika (z EXIF) - camera_make VARCHAR(100), - camera_model VARCHAR(255), - lens_model VARCHAR(255), - iso INT, - aperture NUMERIC(4,2), - exposure_time VARCHAR(20), -- "1/500" - focal_length_mm NUMERIC(5,2), - - -- GPS (NULL pokud chybí) - gps_lat NUMERIC(10,7), - gps_lon NUMERIC(10,7), - gps_altitude NUMERIC(7,2), - - -- klasifikace - is_screenshot BOOLEAN DEFAULT FALSE, - face_count INT, -- z XMP, rozšířit AI - - -- flexibilní JSONB pro celý dump - exif_raw JSONB, - iptc_raw JSONB, - xmp_raw JSONB, - - -- import / zpracování - imported_at TIMESTAMPTZ DEFAULT NOW(), - processed_at TIMESTAMPTZ, - processing_status VARCHAR(50) DEFAULT 'pending' -); - -CREATE INDEX idx_photos_sha256_pixels ON photos(sha256_pixels); -CREATE INDEX idx_photos_phash ON photos(phash); -CREATE INDEX idx_photos_taken_at ON photos(taken_at); -CREATE INDEX idx_photos_camera_model ON photos(camera_model); -CREATE INDEX idx_photos_exif_gin ON photos USING GIN (exif_raw); - -CREATE TABLE tags ( - id SERIAL PRIMARY KEY, - name VARCHAR(100) NOT NULL, - parent_tag_id INT REFERENCES tags(id), -- hierarchie "místo > Praha > Karlův most" - UNIQUE(name, parent_tag_id) -); - -CREATE TABLE photo_tags ( - photo_id BIGINT REFERENCES photos(id) ON DELETE CASCADE, - tag_id INT REFERENCES tags(id) ON DELETE CASCADE, - source VARCHAR(20), -- 'manual' / 'iptc' / 'xmp' / 'auto' - created_at TIMESTAMPTZ DEFAULT NOW(), - PRIMARY KEY (photo_id, tag_id) -); -``` +Kompletní aktuální schéma (tabulky, sloupce, indexy) viz **[SCHEMA.md](SCHEMA.md)**. --- -## 8. Otevřené otázky pro příště +## 8. Otevřené otázky ### Foto bez EXIF (sirotek typu [7]) @@ -326,52 +255,6 @@ CREATE TABLE photo_tags ( --- -## 10. Aktuální stav projektu (k 2026-05-21) +## 10. Aktuální stav a struktura projektu -### Soubory v projektu - -``` -FotkyBuzalkovi/ -├── demo_fotky/ # 7 ukázkových fotek -├── explore_photos.py # Explorační skript (hashe, EXIF, IPTC, XMP) -├── photo_exploration.json # Výstup exploreru -├── create_schema.py # ZASTARALÉ - obsahuje MySQL syntaxi a hardcoded hesla -├── test_db_connection.py # Test PG + Mongo + Redis (Mongo/Redis nebudou potřeba) -├── test_mongo.py # ZASTARALÉ - Mongo nepoužijeme -├── README.md -└── NAVRH.md # Tento dokument -``` - -### Co bude potřeba udělat - -- [ ] Smazat nebo přepsat `create_schema.py` (MySQL syntaxe `INDEX` uvnitř `CREATE TABLE` v PG nefunguje) -- [ ] Migrovat hesla do `.env` + `python-dotenv` -- [ ] Smazat / archivovat `test_mongo.py` -- [ ] Vytvořit migraci podle schématu v sekci 7 -- [ ] Skript pro import fotek s deduplikací (workflow v sekci 4) -- [ ] Rozhodnout otevřené otázky ze sekce 8 - -### Nainstalované Python balíčky - -``` -psycopg2-binary 2.9.12 # PostgreSQL driver -pymongo 4.17.0 # (nepotřebujeme) -redis 7.4.0 # (zatím nepotřebujeme) -pillow 12.2.0 # Základní práce s obrázky -ExifRead 3.5.1 # Primární EXIF parser (lepší než Pillow) -imagehash 4.3.2 # Perceptuální hashe (pHash, dHash, wHash) -+ numpy, scipy, PyWavelets (závislosti imagehash) -``` - ---- - -## 11. Užitečné odkazy - -- [Pillow](https://pillow.readthedocs.io/) — Python Imaging Library -- [ExifRead](https://github.com/ianare/exif-py) — EXIF parser -- [imagehash](https://github.com/JohannesBuchner/imagehash) — perceptuální hashe -- [exiftool](https://exiftool.org/) — gold standard pro metadata (Perl, ale `pyexiftool` wrapper) -- [pgvector](https://github.com/pgvector/pgvector) — vektory v PostgreSQL pro sémantické hledání -- [PostgreSQL JSONB docs](https://www.postgresql.org/docs/current/datatype-json.html) -- [IPTC Photo Metadata Standard](https://www.iptc.org/std/photometadata/specification/IPTC-PhotoMetadata) -- [XMP Specification (Adobe)](https://www.adobe.com/devnet/xmp.html) +Viz **[README.md](README.md)** — hlavní rozcestník projektu. diff --git a/README.md b/README.md index fd62a16..ae52529 100644 --- a/README.md +++ b/README.md @@ -1,153 +1,98 @@ # FotkyBuzalkovi -Systém pro zpracování, ukládání a vyhledávání rodinných fotografií s automatickou extrakcí EXIF metadat. +Systém pro organizaci a tagování ~200 000 rodinných fotek. Lokální nasazení, bez cloudu. -## Architektura +## Kde jsme -Projekt používá kombinaci tří databází, kde každá řeší specifickou úlohu: +- **85 833 fotek** zpracovaných v DB (EXIF, hashe, metadata) +- **39 961 unikátních souborů** fyzicky zálohovaných na Tower1 +- Sběr fotek běží ze dvou serverů (tower, Tower1) i z Windows PC +- Průzkumný Streamlit dashboard hotový (`run_webreport.py`) -``` -┌─────────────────────────────────────────────────────────┐ -│ Python aplikace │ -│ (zpracování fotek, EXIF, hash) │ -└────────────┬──────────────┬──────────────┬───────────────┘ - │ │ │ - ▼ ▼ ▼ - ┌────────────┐ ┌────────────┐ ┌────────────┐ - │ PostgreSQL │ │ MongoDB │ │ Redis │ - │ (relační) │ │ (EXIF/doc) │ │ (cache) │ - └────────────┘ └────────────┘ └────────────┘ - 192.168.1.76 192.168.1.76 localhost - :5432 :27017 :6379 -``` +## Stack -### Proč tři databáze? - -| Databáze | Role | Co ukládá | -|----------|------|-----------| -| **PostgreSQL** | Strukturovaná data, relace | `photos`, `cameras`, `photo_tags` - ID, cesty, hashe, FK | -| **MongoDB** | Flexibilní dokumenty | Plná EXIF metadata (různé fotoaparáty = různá pole) | -| **Redis** | Cache + fronty | Miniatury, výsledky vyhledávání, fronta zpracování | - -## Datové úložiště - -### PostgreSQL - `fotky_buzalkovi` - -- **cameras** - seznam fotoaparátů (model, vyrobce) -- **photos** - hlavní tabulka (file_name, file_path, file_hash, taken_at, rozměry, FK na camera) -- **photo_tags** - tagy ke každé fotce (many-to-many) - -### MongoDB - `fotky_buzalkovi` - -- **photos** kolekce - kompletní EXIF data, GPS souřadnice, nastavení clony, ISO, atd. - -### Redis (plánováno) - -- **cache:thumb:{photo_id}** - cached miniatury (TTL 1h) -- **queue:process** - fronta nezpracovaných fotek -- **session:{user_id}** - session data - -## K čemu Redis - -1. **Cache miniatur** - generování miniatur je drahé, Redis je drží v RAM (rychlost ~0.1ms vs ~50ms z disku) -2. **Cache vyhledávání** - "fotky z dovolené 2025" se může opakovat, výsledek se cachuje -3. **Fronta zpracování** - když nahrajete 1000 fotek, Redis funguje jako worker queue -4. **Deduplikace** - rychlá kontrola, zda hash fotky už existuje -5. **Rate limiting** - omezení uploadů - -## Instalace - -### 1. Python prostředí - -```powershell -python -m venv .venv -.venv\Scripts\Activate.ps1 -pip install psycopg2-binary pymongo redis pillow exifread python-dotenv -``` - -### 2. PostgreSQL - -Předpoklad: PostgreSQL běží na `192.168.1.76:5432`. - -```powershell -python create_schema.py -``` - -### 3. MongoDB - -Předpoklad: MongoDB běží na `192.168.1.76:27017`. - -```powershell -python test_mongo.py -``` - -### 4. Redis (Windows) - -Redis oficiálně Windows nepodporuje. Tři možnosti: - -**Možnost A - WSL2 (doporučeno):** -```powershell -wsl --install -# v WSL: -sudo apt update -sudo apt install redis-server -sudo service redis-server start -``` - -**Možnost B - Docker:** -```powershell -docker run -d --name redis -p 6379:6379 redis:latest -``` - -**Možnost C - Memurai (Windows-native Redis-kompatibilní):** -- Stáhnout z https://www.memurai.com/ - -Test: -```powershell -python test_db_connection.py -``` - -## Konfigurace - -**⚠️ Hesla v současných skriptech jsou v plain textu - před nasazením přesunout do `.env`:** - -```env -PG_HOST=192.168.1.76 -PG_PORT=5432 -PG_USER=vladimir.buzalka -PG_PASSWORD=... -PG_DB=fotky_buzalkovi - -MONGO_URI=mongodb://192.168.1.76:27017/ -MONGO_DB=fotky_buzalkovi - -REDIS_HOST=localhost -REDIS_PORT=6379 -``` +- **PostgreSQL** (192.168.1.76:5432) — jediná databáze, JSONB nahrazuje MongoDB +- **Filesystem** — fotky zůstávají na disku, v DB jen cesty a metadata +- **Python** — veškeré skripty (ExifRead pro EXIF, imagehash pro pHash, blake3 pro zálohy) ## Struktura projektu ``` FotkyBuzalkovi/ -├── demo_fotky/ # Testovací fotografie -├── create_schema.py # Vytvoření PostgreSQL schématu -├── test_db_connection.py # Test všech tří databází -├── test_mongo.py # Test MongoDB + vytvoření kolekcí +├── 00 PictureCollector/ # Sběr fotek ze všech počítačů na Tower1 +│ ├── collect_pictures.py # Hlavní skript (Linux, běží na tower + Tower1) +│ ├── collect_pictures_windows.py # Windows verze (pro libovolný PC v síti) +│ ├── create_tables.py # Vytvoření tabulek zaloha_obrazku + zdrojove_soubory +│ ├── verify_tables.py # Ověření struktury tabulek +│ ├── clear_tables.py # TRUNCATE obou tabulek (pozor!) +│ ├── stats.py # Statistiky sběru +│ ├── ssh_deploy.py # Nasazení skriptu na tower +│ ├── ssh_deploy_tower1.py # Nasazení skriptu na Tower1 +│ ├── ssh_install_deps.py # Instalace závislostí na tower +│ ├── ssh_install_tower1.py # Instalace závislostí na Tower1 +│ ├── ssh_check_mount.py # Kontrola NFS mountu +│ ├── ssh_check_tower1.py # Kontrola Tower1 +│ └── ssh_find_userscripts.py # Hledání Unraid user scriptů +│ +├── 20 PrůzkumFotek/ # Průzkum a vizualizace dat +│ ├── report.py # Streamlit dashboard (spouštět přes run_webreport.py) +│ └── analyze_all.py # Konzolová analýza (jednorázová) +│ +├── 30 SběrDat/ # Zpracování fotek — EXIF parsing, import do DB +│ ├── explore_photos.py # Explorační skript (hashe, EXIF, IPTC, XMP) +│ ├── 10_collect_metadata.py # Sběr metadat z fotek +│ ├── import_to_db.py # Import do tabulky photos +│ ├── create_schema.py # Vytvoření tabulek photos/tags/photo_tags +│ └── photo_exploration.json # Výstup exploreru (7 demo fotek) +│ +├── demo_fotky/ # 7 testovacích fotek (iPhone, screenshot, sirotek) +├── output/ # Výstupy skriptů +│ └── 10_metadata.jsonl # Nasbíraná metadata +├── trash/ # Zastaralé skripty (test_mongo.py, test_db_connection.py) +│ +├── run_webreport.py # Spustí Streamlit dashboard (1 klik v PyCharm) +├── .env # Konfigurace (hesla) ├── .gitignore -└── README.md +│ +├── NAVRH.md # → viz docs/NAVRH.md (architektonická rozhodnutí, hashe, metadata) +└── SCHEMA.md # → viz docs/SCHEMA.md (kompletní DB schéma) ``` -## Známé problémy +## Detailní dokumentace -- `create_schema.py` používá MySQL syntaxi `INDEX idx_x` uvnitř `CREATE TABLE` - v PostgreSQL je potřeba `CREATE INDEX` zvlášť po `CREATE TABLE` -- Hesla jsou hardcodovaná v Python souborech - migrovat do `.env` + `python-dotenv` +| Soubor | Obsah | +|--------|-------| +| [SCHEMA.md](SCHEMA.md) | Kompletní DB schéma — všechny tabulky, sloupce, indexy, constrainty | +| [NAVRH.md](NAVRH.md) | Architektonická rozhodnutí, 4 úrovně hashů, EXIF/IPTC/XMP, workflow importu | -## Roadmap +## Infrastruktura -- [ ] Opravit PostgreSQL schéma (INDEX syntaxe) -- [ ] Migrace hesel do `.env` -- [ ] Instalace Redis -- [ ] Skript pro hromadný import fotek z `demo_fotky/` -- [ ] EXIF parser (pillow + exifread) -- [ ] Generování miniatur s Redis cache -- [ ] Web UI pro prohlížení galerie +| Server | IP | Role | +|--------|----|------| +| **tower** | 192.168.1.76 | Hlavní NAS, PostgreSQL, spouští skripty | +| **Tower1** | 192.168.1.50 | Archivní NAS, fyzická záloha fotek (`/mnt/user/ZalohaVsechObrazku`) | + +SSH klíče sdílené mezi servery. Z tower přístup na Tower1 přes NFS mount. + +## Rychlý start + +```powershell +# Spustit dashboard +python run_webreport.py + +# Nasadit collect_pictures na server +python "00 PictureCollector/ssh_deploy.py" +python "00 PictureCollector/ssh_deploy_tower1.py" + +# Spustit sběr fotek z Windows PC +python "00 PictureCollector/collect_pictures_windows.py" +``` + +## Otevřené otázky + +1. Fotky bez EXIF (7 % fotek) — importovat s mtime / odmítnout / označit? +2. Shoda `sha256_pixels` — přeskočit / sloučit metadata / uložit oba? +3. Storage layout — in-place / `archiv/YYYY/MM/` / content-addressable? +4. Propojení tabulek skupiny 1 (photos) a skupiny 2 (zaloha_obrazku) — zatím žádný FK +5. Fotky 2013–2021 — velký propad, chybí zdroje (mobily, iCloud?) +6. 4 210 fotek z 2022 bez kamery — pravděpodobně hromadný export z iCloudu (25.9.2023) diff --git a/photo_exploration.json b/photo_exploration.json deleted file mode 100644 index 21470e4..0000000 --- a/photo_exploration.json +++ /dev/null @@ -1,906 +0,0 @@ -[ - { - "filesystem": { - "file_name": "2026-04-22 09.05.08.jpg", - "file_path": "U:\\PycharmProjects\\FotkyBuzalkovi\\demo_fotky\\2026-04-22 09.05.08.jpg", - "file_size_bytes": 10186179, - "file_size_mb": 9.71, - "mtime": "2026-04-22T09:05:08", - "ctime": "2026-05-19T06:22:12.659966", - "extension": ".jpg" - }, - "hashes": { - "sha256_file": "4ea38e22f815c4512e6bfe06d9203180e5920ac7324d2e0e9271e918d53adff5", - "sha256_pixels": "b95b72d63a300cde544091c11f8e884896d583afa0dc380cdebc47b3ade8d05f", - "phash": "f7a2c8606d4c76d4", - "dhash": "c4884c4c4c4cc010", - "ahash": "feffe6a702246098", - "whash": "feffe7a702006098" - }, - "pillow": { - "format": "JPEG", - "mode": "RGB", - "width": 5712, - "height": 4284, - "megapixels": 24.47, - "has_transparency": false, - "dpi": [ - "72.0", - "72.0" - ], - "icc_profile_present": true, - "exif_present": true, - "xmp_snippet": " \n \n \n Screenshot