notebookVb
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
"""
|
||||
migrate_add_wanted.py — přidá sloupec wanted BOOLEAN NOT NULL DEFAULT FALSE do tabulky photos.
|
||||
Bezpečné spustit vícekrát (idempotentní).
|
||||
"""
|
||||
|
||||
import psycopg2
|
||||
|
||||
DB = dict(host="192.168.1.76", port=5432, user="vladimir.buzalka",
|
||||
password="Vlado7309208104++", database="fotky_buzalkovi")
|
||||
|
||||
SQL = """
|
||||
ALTER TABLE photos
|
||||
ADD COLUMN IF NOT EXISTS wanted BOOLEAN NOT NULL DEFAULT FALSE;
|
||||
"""
|
||||
|
||||
conn = psycopg2.connect(**DB)
|
||||
conn.autocommit = True
|
||||
cur = conn.cursor()
|
||||
|
||||
print("Přidávám sloupec wanted…")
|
||||
cur.execute(SQL)
|
||||
print("Hotovo.")
|
||||
|
||||
cur.execute("SELECT COUNT(*) FROM photos WHERE wanted = FALSE")
|
||||
n = cur.fetchone()[0]
|
||||
print(f"Fotek s wanted=FALSE: {n:,}")
|
||||
|
||||
conn.close()
|
||||
@@ -0,0 +1,31 @@
|
||||
"""
|
||||
migrate_category.py — přidá sloupec category do photos (idempotentní)
|
||||
a provede konkrétní UPDATE podle pravidel.
|
||||
"""
|
||||
import psycopg2
|
||||
|
||||
DB = dict(host="192.168.1.76", port=5432, user="vladimir.buzalka",
|
||||
password="Vlado7309208104++", database="fotky_buzalkovi")
|
||||
|
||||
conn = psycopg2.connect(**DB)
|
||||
conn.autocommit = True
|
||||
cur = conn.cursor()
|
||||
|
||||
# 1. Přidat sloupec category pokud neexistuje
|
||||
cur.execute("""
|
||||
ALTER TABLE photos
|
||||
ADD COLUMN IF NOT EXISTS category VARCHAR(100);
|
||||
""")
|
||||
print("Sloupec category OK.")
|
||||
|
||||
# 2. BolyMedia SG520 → wanted=TRUE, category='Fotopast'
|
||||
cur.execute("""
|
||||
UPDATE photos
|
||||
SET wanted = TRUE,
|
||||
category = 'Fotopast'
|
||||
WHERE camera_make = 'BolyMedia'
|
||||
AND camera_model = 'SG520';
|
||||
""")
|
||||
print(f"SG520 aktualizováno: {cur.rowcount:,} řádků → wanted=TRUE, category='Fotopast'")
|
||||
|
||||
conn.close()
|
||||
@@ -0,0 +1,230 @@
|
||||
"""
|
||||
preview_sample.py — zobrazí náhled fotek podle ID z databáze.
|
||||
|
||||
Použití:
|
||||
python preview_sample.py 101 202 303 ...
|
||||
python preview_sample.py --file ids.txt # jedno ID na řádek
|
||||
python preview_sample.py --file ids.txt --thumb 400
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import base64
|
||||
import io
|
||||
import sys
|
||||
import tempfile
|
||||
import webbrowser
|
||||
from pathlib import Path
|
||||
|
||||
import psycopg2
|
||||
from PIL import Image
|
||||
|
||||
# ── DB ────────────────────────────────────────────────────────────────────────
|
||||
|
||||
DB = dict(host="192.168.1.76", port=5432, user="vladimir.buzalka",
|
||||
password="Vlado7309208104++", database="fotky_buzalkovi")
|
||||
|
||||
# ── Cesty ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
NAS_LINUX_PREFIX = "/mnt/user/ZalohaVsechObrazku/"
|
||||
NAS_WIN_UNC = r"\\Tower1\ZalohaVsechObrazku\\"
|
||||
|
||||
|
||||
def linux_to_win(p: str) -> Path:
|
||||
rel = p.removeprefix(NAS_LINUX_PREFIX).replace("/", "\\")
|
||||
return Path(NAS_WIN_UNC + rel)
|
||||
|
||||
|
||||
# ── Načtení dat z DB ──────────────────────────────────────────────────────────
|
||||
|
||||
QUERY = """
|
||||
SELECT
|
||||
z.id,
|
||||
z.cesta_zalohy,
|
||||
z.velikost,
|
||||
p.taken_at,
|
||||
p.taken_at_source,
|
||||
p.camera_make,
|
||||
p.camera_model,
|
||||
p.width,
|
||||
p.height,
|
||||
p.megapixels,
|
||||
p.gps_lat,
|
||||
p.gps_lon,
|
||||
COALESCE(
|
||||
(SELECT string_agg(e.error_code, ', ')
|
||||
FROM photo_errors e WHERE e.photo_id = p.id),
|
||||
''
|
||||
) AS chyby
|
||||
FROM zaloha_obrazku z
|
||||
JOIN photos p ON p.zaloha_id = z.id
|
||||
WHERE z.id = ANY(%s)
|
||||
"""
|
||||
|
||||
|
||||
def fetch_rows(ids: list[int]) -> list:
|
||||
conn = psycopg2.connect(**DB)
|
||||
cur = conn.cursor()
|
||||
cur.execute(QUERY, (ids,))
|
||||
rows = cur.fetchall()
|
||||
conn.close()
|
||||
# zachovej původní pořadí ID
|
||||
order = {rid: i for i, rid in enumerate(ids)}
|
||||
return sorted(rows, key=lambda r: order.get(r[0], 9999))
|
||||
|
||||
|
||||
# ── Thumbnail ─────────────────────────────────────────────────────────────────
|
||||
|
||||
def make_thumb_b64(win_path: Path, size: int) -> str | None:
|
||||
try:
|
||||
img = Image.open(win_path)
|
||||
img.thumbnail((size, size), Image.LANCZOS)
|
||||
if img.mode not in ("RGB", "L"):
|
||||
img = img.convert("RGB")
|
||||
buf = io.BytesIO()
|
||||
img.save(buf, format="JPEG", quality=82)
|
||||
return base64.b64encode(buf.getvalue()).decode()
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
# ── HTML ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
def format_size(n) -> str:
|
||||
if n is None:
|
||||
return "?"
|
||||
for unit in ("B", "kB", "MB", "GB"):
|
||||
if n < 1024:
|
||||
return f"{n:.0f} {unit}"
|
||||
n /= 1024
|
||||
return f"{n:.1f} GB"
|
||||
|
||||
|
||||
def render_html(rows: list, ids: list[int], thumb_size: int) -> str:
|
||||
cards = []
|
||||
ok = 0
|
||||
|
||||
for i, row in enumerate(rows, 1):
|
||||
(rid, cesta, velikost, taken_at, taken_src,
|
||||
make_, model, width, height, mp, lat, lon, chyby) = row
|
||||
|
||||
win_path = linux_to_win(cesta)
|
||||
b64 = make_thumb_b64(win_path, thumb_size)
|
||||
|
||||
if b64:
|
||||
ok += 1
|
||||
img_tag = f'<img src="data:image/jpeg;base64,{b64}" alt="foto">'
|
||||
card_cls = "card"
|
||||
else:
|
||||
img_tag = '<div class="no-img">⚠ soubor nedostupný<br><small>zkontroluj přístup na Tower1</small></div>'
|
||||
card_cls = "card broken"
|
||||
|
||||
camera = " ".join(filter(None, [make_, model])) or "<em>neznámá</em>"
|
||||
date_str = taken_at.strftime("%Y-%m-%d %H:%M") if taken_at else "?"
|
||||
res = f"{width}×{height}" if width and height else "?"
|
||||
mp_str = f"{mp:.1f} Mpx" if mp else ""
|
||||
gps_link = (
|
||||
f'<a href="https://maps.google.com/?q={lat},{lon}" target="_blank">📍 {lat:.4f}, {lon:.4f}</a>'
|
||||
if lat and lon else ""
|
||||
)
|
||||
chyby_html = f'<div class="err">{chyby}</div>' if chyby else ""
|
||||
path_short = str(win_path).replace(NAS_WIN_UNC, "…\\")
|
||||
|
||||
cards.append(f"""
|
||||
<div class="{card_cls}">
|
||||
<div class="thumb">{img_tag}</div>
|
||||
<div class="meta">
|
||||
<div class="idx">id={rid} #{i}</div>
|
||||
<div><strong>{date_str}</strong> <span class="src">({taken_src})</span></div>
|
||||
<div>📷 {camera}</div>
|
||||
<div>{res} {mp_str} {format_size(velikost)}</div>
|
||||
{"<div>" + gps_link + "</div>" if gps_link else ""}
|
||||
{chyby_html}
|
||||
<div class="path" title="{win_path}">{path_short}</div>
|
||||
</div>
|
||||
</div>""")
|
||||
|
||||
missing_ids = set(ids) - {r[0] for r in rows}
|
||||
missing_note = ""
|
||||
if missing_ids:
|
||||
missing_note = f'<div class="warn">ID nenalezena v DB: {sorted(missing_ids)}</div>'
|
||||
|
||||
return f"""<!DOCTYPE html>
|
||||
<html lang="cs">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Preview fotek ({len(rows)})</title>
|
||||
<style>
|
||||
body {{ font-family: sans-serif; background: #1a1a2e; color: #eee; margin: 0; padding: 16px; }}
|
||||
h1 {{ color: #e0c97f; margin-bottom: 4px; }}
|
||||
.info {{ color: #aaa; margin-bottom: 16px; font-size: .9em; }}
|
||||
.warn {{ color: #f99; margin-bottom: 12px; }}
|
||||
.grid {{ display: flex; flex-wrap: wrap; gap: 16px; }}
|
||||
.card {{ background: #16213e; border-radius: 8px; overflow: hidden;
|
||||
width: {thumb_size + 24}px; box-shadow: 0 2px 8px #0006; }}
|
||||
.card.broken {{ opacity: .55; }}
|
||||
.thumb {{ display:flex; align-items:center; justify-content:center;
|
||||
background:#0f3460; min-height: 120px; }}
|
||||
.thumb img {{ max-width:100%; max-height:{thumb_size}px; display:block; }}
|
||||
.no-img {{ color:#f88; padding:20px; text-align:center; font-size:.85em; }}
|
||||
.meta {{ padding: 8px 12px; font-size: .78em; line-height: 1.75; }}
|
||||
.meta strong {{ font-size: 1em; }}
|
||||
.idx {{ float:right; color:#666; font-size:.75em; }}
|
||||
.src {{ color:#888; }}
|
||||
.err {{ color:#f99; }}
|
||||
.path {{ color:#555; word-break:break-all; margin-top:4px; font-size:.7em; }}
|
||||
a {{ color:#7ec8e3; }}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Preview fotek</h1>
|
||||
<div class="info">
|
||||
Celkem ID: <strong>{len(ids)}</strong> ·
|
||||
Načteno z DB: <strong>{len(rows)}</strong> ·
|
||||
Obrázky dostupné: <strong>{ok}/{len(rows)}</strong>
|
||||
</div>
|
||||
{missing_note}
|
||||
<div class="grid">
|
||||
{"".join(cards)}
|
||||
</div>
|
||||
</body>
|
||||
</html>"""
|
||||
|
||||
|
||||
# ── Main ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Preview fotek podle ID z DB")
|
||||
parser.add_argument("ids", nargs="*", type=int, help="ID fotek (zaloha_obrazku.id)")
|
||||
parser.add_argument("--file", "-f", help="Soubor s ID (jedno na řádek)")
|
||||
parser.add_argument("--thumb", "-t", type=int, default=320, help="Velikost thumbnailů v px (default 320)")
|
||||
args = parser.parse_args()
|
||||
|
||||
ids = list(args.ids)
|
||||
|
||||
if args.file:
|
||||
with open(args.file) as fh:
|
||||
for line in fh:
|
||||
line = line.strip()
|
||||
if line and line.isdigit():
|
||||
ids.append(int(line))
|
||||
|
||||
if not ids:
|
||||
print("Žádná ID. Zadej je jako argumenty nebo přes --file.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
print(f"Načítám {len(ids)} fotek z DB…")
|
||||
rows = fetch_rows(ids)
|
||||
print(f"Generuji thumbnaily (--thumb {args.thumb}px)…")
|
||||
html = render_html(rows, ids, args.thumb)
|
||||
|
||||
tmp = tempfile.NamedTemporaryFile(
|
||||
suffix=".html", delete=False, mode="w", encoding="utf-8"
|
||||
)
|
||||
tmp.write(html)
|
||||
tmp.close()
|
||||
print(f"Otevírám: {tmp.name}")
|
||||
webbrowser.open(f"file:///{tmp.name}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,136 @@
|
||||
"""
|
||||
update_wanted.py — označuje fotky wanted=TRUE a nastavuje category.
|
||||
Spusť a uprav seznam pravidel podle potřeby.
|
||||
"""
|
||||
import psycopg2
|
||||
|
||||
DB = dict(host="192.168.1.76", port=5432, user="vladimir.buzalka",
|
||||
password="Vlado7309208104++", database="fotky_buzalkovi")
|
||||
|
||||
conn = psycopg2.connect(**DB)
|
||||
conn.autocommit = True
|
||||
cur = conn.cursor()
|
||||
|
||||
rules = [
|
||||
# (popis, SQL WHERE, category)
|
||||
# ── Samsung ──────────────────────────────────────────────────────────────
|
||||
(
|
||||
"Samsung SM-G935F / A520F / A525F / J500FN / I8190N / S7560 / S5230",
|
||||
"camera_make IN ('samsung', 'SAMSUNG', 'Samsung') AND camera_model IN "
|
||||
"('SM-G935F','SM-A520F','SM-A525F','SM-J500FN','GT-I8190N','GT-S7560','GT-S5230')",
|
||||
"Rodina",
|
||||
),
|
||||
# ── Profesionální / cizí ─────────────────────────────────────────────────
|
||||
(
|
||||
"Nikon D5 / D4 / D700 / D300 / D800 / D500",
|
||||
"camera_make = 'NIKON CORPORATION' AND camera_model IN "
|
||||
"('NIKON D5','NIKON D4','NIKON D700','NIKON D300','NIKON D800','NIKON D500')",
|
||||
"Rodina",
|
||||
),
|
||||
(
|
||||
"Canon EOS-1D X III / 450D / 600D / R6 / 5D IV / 5D / 40D / 20D",
|
||||
"camera_make = 'Canon' AND camera_model IN "
|
||||
"('Canon EOS-1D X Mark III','Canon EOS 450D','Canon EOS 600D','Canon EOS R6',"
|
||||
"'Canon EOS 5D Mark IV','Canon EOS 5D','Canon EOS 40D','Canon EOS 20D')",
|
||||
"Rodina",
|
||||
),
|
||||
(
|
||||
"Sony ILCE-1 (Alpha 1)",
|
||||
"camera_make = 'SONY' AND camera_model = 'ILCE-1'",
|
||||
"Rodina",
|
||||
),
|
||||
(
|
||||
"Panasonic DC-S5",
|
||||
"camera_make = 'Panasonic' AND camera_model = 'DC-S5'",
|
||||
"Rodina",
|
||||
),
|
||||
# ── Ostatní kompakty ─────────────────────────────────────────────────────
|
||||
(
|
||||
"Panasonic DMC-FZ5",
|
||||
"camera_make = 'Panasonic' AND camera_model = 'DMC-FZ5'",
|
||||
"Rodina",
|
||||
),
|
||||
(
|
||||
"Canon PowerShot S40",
|
||||
"camera_make = 'Canon' AND camera_model = 'Canon PowerShot S40'",
|
||||
"Rodina",
|
||||
),
|
||||
(
|
||||
"Canon PowerShot A40",
|
||||
"camera_make = 'Canon' AND camera_model = 'Canon PowerShot A40'",
|
||||
"Rodina",
|
||||
),
|
||||
(
|
||||
"Panasonic DMC-FX33",
|
||||
"camera_make = 'Panasonic' AND camera_model = 'DMC-FX33'",
|
||||
"Rodina",
|
||||
),
|
||||
(
|
||||
"NIKON D80",
|
||||
"camera_make = 'NIKON CORPORATION' AND camera_model = 'NIKON D80'",
|
||||
"Rodina",
|
||||
),
|
||||
(
|
||||
"GoPro HERO6 Black",
|
||||
"camera_make = 'GoPro' AND camera_model = 'HERO6 Black'",
|
||||
"Rodina",
|
||||
),
|
||||
(
|
||||
"Microsoft Lumia 930",
|
||||
"camera_make = 'Microsoft' AND camera_model = 'Lumia 930'",
|
||||
"Rodina",
|
||||
),
|
||||
(
|
||||
"Panasonic DMC-FX3",
|
||||
"camera_make = 'Panasonic' AND camera_model = 'DMC-FX3'",
|
||||
"Rodina",
|
||||
),
|
||||
(
|
||||
"Sony DSC-T77",
|
||||
"camera_make = 'SONY' AND camera_model = 'DSC-T77'",
|
||||
"Rodina",
|
||||
),
|
||||
(
|
||||
"Olympus C120/D380",
|
||||
"camera_make = 'OLYMPUS OPTICAL CO.,LTD' AND camera_model = 'C120,D380'",
|
||||
"Rodina",
|
||||
),
|
||||
(
|
||||
"Sony DSC-WX50",
|
||||
"camera_make = 'SONY' AND camera_model = 'DSC-WX50'",
|
||||
"Rodina",
|
||||
),
|
||||
(
|
||||
"Sony CYBERSHOT",
|
||||
"camera_make = 'SONY' AND camera_model = 'CYBERSHOT'",
|
||||
"Rodina",
|
||||
),
|
||||
(
|
||||
"Casio EX-Z6",
|
||||
"camera_make = 'CASIO COMPUTER CO.,LTD.' AND camera_model = 'EX-Z6'",
|
||||
"Rodina",
|
||||
),
|
||||
(
|
||||
"Olympus C765UZ",
|
||||
"camera_make = 'OLYMPUS CORPORATION' AND camera_model = 'C765UZ'",
|
||||
"Rodina",
|
||||
),
|
||||
(
|
||||
"Nikon E990",
|
||||
"camera_make = 'NIKON' AND camera_model = 'E990'",
|
||||
"Rodina",
|
||||
),
|
||||
]
|
||||
|
||||
for popis, where, category in rules:
|
||||
cur.execute(f"""
|
||||
UPDATE photos
|
||||
SET wanted = TRUE,
|
||||
category = %s
|
||||
WHERE {where}
|
||||
AND (wanted = FALSE OR category IS DISTINCT FROM %s)
|
||||
""", (category, category))
|
||||
print(f"{popis}: {cur.rowcount:,} řádků → wanted=TRUE, category='{category}'")
|
||||
|
||||
conn.close()
|
||||
print("Hotovo.")
|
||||
@@ -0,0 +1,58 @@
|
||||
"""update_wanted_batch2.py — označí další vlnu foťáků wanted=TRUE, category=Rodina"""
|
||||
import psycopg2
|
||||
|
||||
DB = dict(host="192.168.1.76", port=5432, user="vladimir.buzalka",
|
||||
password="Vlado7309208104++", database="fotky_buzalkovi")
|
||||
|
||||
# (make, model)
|
||||
CAMERAS = [
|
||||
("Canon", "Canon DIGITAL IXUS 30"),
|
||||
("Xiaomi", "2201117TY"),
|
||||
("SONY", "DSC-H70"),
|
||||
("Panasonic", "DMC-FZ20"),
|
||||
("Panasonic", "DMC-FZ50"),
|
||||
("Panasonic", "NV-GS180"),
|
||||
("EASTMAN KODAK COMPANY", "KODAK C875 ZOOM DIGITAL CAMERA"),
|
||||
("SONY", "D2005"),
|
||||
("OLYMPUS IMAGING CORP.", "XZ-10"),
|
||||
("OLYMPUS CORPORATION", "C460ZdelSol"),
|
||||
("Canon", "Canon EOS DIGITAL REBEL"),
|
||||
("HTC", "HTC HD2 T8585"),
|
||||
("OLYMPUS OPTICAL CO.,LTD", "C4040Z"),
|
||||
("NIKON", "E950"),
|
||||
("NIKON", "E5400"),
|
||||
("Canon", "Canon PowerShot S300"),
|
||||
("Emgeton", "Flexaret Mini"),
|
||||
("SONY", "DCR-PC115E"),
|
||||
("Canon", "Canon PowerShot A720 IS"),
|
||||
("SONY", "HDR-TG3E"),
|
||||
("Canon", "Canon PowerShot A75"),
|
||||
("OLYMPUS OPTICAL CO.,LTD", "C3030Z"),
|
||||
("FUJIFILM", "FinePix F20"),
|
||||
("vivo", "vivo X100 Pro"),
|
||||
("NIKON", "E3200"),
|
||||
("Canon", "Canon PowerShot S3 IS"),
|
||||
("Canon", "Canon PowerShot G3"),
|
||||
("OLYMPUS OPTICAL CO.,LTD", "X-2,C-50Z"),
|
||||
("Canon", "Canon PowerShot S2 IS"),
|
||||
("Canon", "Canon PowerShot S500"),
|
||||
("Canon", "Canon DIGITAL IXUS 400"),
|
||||
("OLYMPUS OPTICAL CO.,LTD", "C5050Z"),
|
||||
]
|
||||
|
||||
conn = psycopg2.connect(**DB)
|
||||
conn.autocommit = True
|
||||
cur = conn.cursor()
|
||||
|
||||
placeholders = ",".join(["(%s,%s)"] * len(CAMERAS))
|
||||
params = [v for pair in CAMERAS for v in pair]
|
||||
cur.execute(f"""
|
||||
UPDATE photos
|
||||
SET wanted = TRUE,
|
||||
category = 'Rodina'
|
||||
WHERE (camera_make, camera_model) IN ({placeholders})
|
||||
AND wanted = FALSE
|
||||
""", params)
|
||||
|
||||
print(f"Označeno: {cur.rowcount:,} řádků → wanted=TRUE, category='Rodina'")
|
||||
conn.close()
|
||||
@@ -0,0 +1,100 @@
|
||||
# FotkyBuzalkovi — pracovní deník
|
||||
|
||||
> Živý dokument. Zapisujeme sem co jsme zjistili, co jsme rozhodli a co je na řadě.
|
||||
> Technická reference → `CONTEXT.md`, návrh architektury → `NAVRH.md`.
|
||||
|
||||
---
|
||||
|
||||
## Session 2026-06-04
|
||||
|
||||
### Stav DB na začátku
|
||||
|
||||
| Tabulka | Řádky |
|
||||
|---------|-------|
|
||||
| `zaloha_obrazku` | 1 717 182 |
|
||||
| `zdrojove_soubory` | 3 573 846 |
|
||||
| `photos` | 1 717 175 |
|
||||
| `photo_errors` | 3 185 319 |
|
||||
|
||||
Všechny fotky mají `processing_status = 'pending'` — pipeline doběhla, další zpracování nezačalo.
|
||||
|
||||
### Co jsme zjistili
|
||||
|
||||
**Problém 1 — V záloze je spousta odpadu, ne jen rodinné fotky.**
|
||||
Pipeline sebrala vše co má příponu `.jpg/.jpeg` — včetně:
|
||||
- PhotoPrism cache thumbnailů (`appdata/photoprism/cache/`) — 229 521 ks, <10 kB
|
||||
- Plex / Immich cache
|
||||
- MP3 a LP obaly
|
||||
- DVD obaly, eBook obálky
|
||||
- ABC vystřihovánky (skenované na 1200 DPI → soubory 60–100 MB)
|
||||
- Reprodukce obrazů z torrentů (Raffael, Rembrandt... v muzejní kvalitě)
|
||||
- Stažené obrázky z webu (Dropbox/!!!Days/Stefajir/...)
|
||||
- Windows AppData (Kindle covers, .NET watermark...)
|
||||
|
||||
**Problém 2 — Rok pořízení je hodně porušený.**
|
||||
- Rok 2024 má 985 754 fotek (>57 % všech) — zřejmě chybný fallback na mtime místo EXIF
|
||||
- Rok 1863, 2031–4501 — garbage v EXIF
|
||||
- Rok 2026 má 93 492 — suspektní
|
||||
|
||||
**Sloupec `wanted`:**
|
||||
Přidán `photos.wanted BOOLEAN NOT NULL DEFAULT FALSE` — všech 1 717 175 fotek má FALSE.
|
||||
Účel: budeme označovat fotky které chceme zachovat / zpracovat.
|
||||
|
||||
### Nástroje
|
||||
|
||||
- `00 PictureCollector/preview_sample.py` — zobrazí náhled fotek podle ID
|
||||
Použití: `python preview_sample.py 101 202 303 ...`
|
||||
Claude vybere ID přes MCP dotazy, předá příkaz ke spuštění.
|
||||
|
||||
- `00 PictureCollector/migrate_add_wanted.py` — přidal sloupec `wanted` (idempotentní)
|
||||
|
||||
### Rozhodnutí
|
||||
|
||||
#### Pravidla vyloučení cest — část 1 (2026-06-04)
|
||||
|
||||
Tyto cesty **nechceme** — `wanted` zůstane FALSE, nezpracovávat:
|
||||
|
||||
| Vzor cesty (obsahuje) | Důvod | Počet |
|
||||
|---|---|---|
|
||||
| `Torrents/Downloads/OOPS!!! International` | porno screenshoty | ~7 105 |
|
||||
| `Torrents/Downloads/Tampons Pads Period` | porno | ~9 600 |
|
||||
| `#ColdData/Porno/` | porno screenlists | — |
|
||||
| `Porno1/` | porno | ~2 730 |
|
||||
| `#ColdData/000 TORENT OBRAZKY/National Geographic Wallpapers` | stažené wallpapery | ~7 188 |
|
||||
| `#ColdData/000 TORENT OBRAZKY/[OnlyFans]` | OnlyFans | ~1 377 |
|
||||
| `#ColdData/000 TORENT OBRAZKY/Great Painters` | reprodukce obrazů | — |
|
||||
| `UltraCC/` a obsahuje `/jpg` | Hot Wheels katalog a jiné torrent obrázky | ~3 484 |
|
||||
| `Magentic/Runtime/UserPhotos/css` | webové ikonky | ~1 034 |
|
||||
| `.Icecream Ebook Reader/` | obrázky z epub knih | — |
|
||||
| `photoprism/sidecar/` | XMP sidecar soubory | — |
|
||||
|
||||
> Otevřené: appdata/photoprism/cache, Immich thumbs, MP3/LP obaly, eBooks — vyřeší se v další části pravidel.
|
||||
|
||||
### Schéma — nové sloupce v `photos`
|
||||
|
||||
| Sloupec | Typ | Popis |
|
||||
|---|---|---|
|
||||
| `wanted` | `BOOLEAN NOT NULL DEFAULT FALSE` | chceme tuto fotku zachovat/zpracovat |
|
||||
| `category` | `VARCHAR(100)` | kategorie: Fotopast, Rodina, Skeny, … |
|
||||
|
||||
### Označené kategorie
|
||||
|
||||
| Kamera / kritérium | wanted | category | Počet |
|
||||
|---|---|---|---|
|
||||
| BolyMedia SG520 | TRUE | Fotopast | 42 688 |
|
||||
|
||||
### Na řadě
|
||||
|
||||
- [ ] Prozkoumat co přesně je v záloze — jaký podíl jsou skutečné rodinné fotky
|
||||
- [ ] Rozhodnout jak filtrovat odpad (path blacklist? size? absence kamery?)
|
||||
- [ ] Vyřešit problém s roky — proč 57 % fotek padá do 2024
|
||||
- [ ] Označit první várku fotek jako `wanted = TRUE`
|
||||
|
||||
---
|
||||
|
||||
## Backlog otevřených otázek
|
||||
|
||||
1. Co s "sirotky" bez EXIF — `mtime` / odmítnout / označit?
|
||||
2. Při shodě `sha256_pixels` — přeskočit / sloučit metadata / uložit oba?
|
||||
3. Storage layout — nechat in-place / `archiv/YYYY/MM/` / content-addressable?
|
||||
4. Jak poznat "rodinná fotka" od odpadu bez ruční kontroly?
|
||||
Reference in New Issue
Block a user