notebookVb

This commit is contained in:
administrator
2026-06-06 06:26:42 +02:00
parent 82de38f02a
commit ee14efbd48
21 changed files with 923 additions and 0 deletions
+125
View File
@@ -0,0 +1,125 @@
#!/usr/bin/env python3
"""
immich_upload.py — nahrava wanted fotky z DB fotky_buzalkovi do Immiche.
Cte primo ze zalohy (NFS), nahrava KOPII pres Immich API. Zalohu nesaha.
Resumable: tabulka immich_upload (zaloha_id -> asset id, status).
Spousti se v kontejneru python-runner:
docker exec python-runner python3 /scripts/immich_upload.py --like '...%' --limit 200
"""
import os
import sys
import argparse
from datetime import datetime, timezone
import requests
import psycopg
sys.stdout.reconfigure(encoding="utf-8")
DB = dict(host="192.168.1.76", port=5432, user="vladimir.buzalka",
password="Vlado7309208104++", dbname="fotky_buzalkovi")
IMMICH = "http://192.168.1.76:8888"
APIKEY = "UQV5PS1Td50hKOZTItnXEcXVkfQSUxcUH0XHZYxc"
SRC_PREFIX = "/mnt/user/ZalohaVsechObrazku" # jak je cesta v DB
DST_PREFIX = "/mnt/ZalohaVsechObrazku" # jak ji vidi kontejner
ap = argparse.ArgumentParser()
ap.add_argument("--like", default=None, help="filtr na z.cesta_zalohy LIKE")
ap.add_argument("--category", default=None, help="filtr na p.category")
ap.add_argument("--limit", type=int, default=0)
ap.add_argument("--dry-run", action="store_true")
args = ap.parse_args()
conn = psycopg.connect(**DB, autocommit=True)
cur = conn.cursor()
cur.execute("""
CREATE TABLE IF NOT EXISTS immich_upload(
zaloha_id bigint PRIMARY KEY,
immich_id uuid,
status text,
uploaded_at timestamptz DEFAULT now()
)
""")
sql = """
SELECT z.id, z.cesta_zalohy, p.taken_at
FROM photos p
JOIN zaloha_obrazku z ON p.zaloha_id = z.id
LEFT JOIN immich_upload iu ON iu.zaloha_id = z.id
WHERE p.wanted = TRUE AND iu.zaloha_id IS NULL
"""
params = []
if args.category:
sql += " AND p.category = %s"; params.append(args.category)
if args.like:
sql += " AND z.cesta_zalohy LIKE %s"; params.append(args.like)
sql += " ORDER BY z.cesta_zalohy"
if args.limit:
sql += " LIMIT %s"; params.append(args.limit)
cur.execute(sql, params)
rows = cur.fetchall()
total = len(rows)
print(f"K nahrani (jeste nenahrano): {total:,}")
if args.dry_run:
miss = sum(1 for _, c, _ in rows if not os.path.isfile(c.replace(SRC_PREFIX, DST_PREFIX, 1)))
print(f"DRY-RUN: chybejicich souboru: {miss}")
sys.exit(0)
sess = requests.Session()
sess.headers.update({"x-api-key": APIKEY, "Accept": "application/json"})
created = dup = err = 0
for i, (zid, cesta, taken) in enumerate(rows, 1):
path = cesta.replace(SRC_PREFIX, DST_PREFIX, 1)
if not os.path.isfile(path):
err += 1
print("MISSING:", path)
continue
st = os.stat(path)
fmod = datetime.fromtimestamp(st.st_mtime, tz=timezone.utc)
fcreated = taken if taken else fmod
if fcreated.tzinfo is None:
fcreated = fcreated.replace(tzinfo=timezone.utc)
try:
with open(path, "rb") as fh:
r = sess.post(
IMMICH + "/api/assets",
files={"assetData": (os.path.basename(path), fh, "application/octet-stream")},
data={
"deviceAssetId": f"fb-{zid}",
"deviceId": "fotky-buzalkovi",
"fileCreatedAt": fcreated.isoformat(),
"fileModifiedAt": fmod.isoformat(),
"isFavorite": "false",
},
timeout=300,
)
j = r.json()
status = j.get("status")
aid = j.get("id")
cur.execute(
"""INSERT INTO immich_upload(zaloha_id, immich_id, status)
VALUES (%s,%s,%s)
ON CONFLICT (zaloha_id) DO UPDATE
SET immich_id=EXCLUDED.immich_id, status=EXCLUDED.status, uploaded_at=now()""",
(zid, aid, status),
)
if status == "created":
created += 1
elif status == "duplicate":
dup += 1
else:
err += 1
print("? neznamy status:", status, j)
except Exception as e:
err += 1
print("ERR", zid, repr(e))
if i % 50 == 0 or i == total:
print(f"... {i}/{total} created={created} dup={dup} err={err}", flush=True)
print(f"\nHOTOVO. created={created} dup={dup} err={err}")
conn.close()
@@ -0,0 +1,47 @@
"""
mark_cache_unwanted.py — Označí celé adresáře Tower/appdata a Tower/Sabnzbd
jako nechceme (cache PhotoPrism/Immich + usenet download cache).
wanted=FALSE, category='Odpad-cache'
Idempotentní: díky filtru `category IS DISTINCT FROM 'Odpad-cache'` přepíše
jen řádky, které ještě nejsou označené (vč. těch, co camera-rules omylem
označily jako Rodina — jde o duplicitní cache kopie, ne originály).
Jediná DB session, autocommit — žádné paralelní běhy (deadlock).
"""
import sys
import psycopg2
sys.stdout.reconfigure(encoding="utf-8")
DB = dict(host="192.168.1.76", port=5432, user="vladimir.buzalka",
password="Vlado7309208104++", database="fotky_buzalkovi")
DIRS = [
("Tower/appdata (PhotoPrism/Immich cache)",
"/mnt/user/ZalohaVsechObrazku/Tower/appdata%"),
("Tower/Sabnzbd (usenet download cache)",
"/mnt/user/ZalohaVsechObrazku/Tower/Sabnzbd%"),
]
conn = psycopg2.connect(**DB)
conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
cur = conn.cursor()
total = 0
for popis, like in DIRS:
cur.execute("""
UPDATE photos p
SET wanted = FALSE,
category = 'Odpad-cache'
FROM zaloha_obrazku z
WHERE p.zaloha_id = z.id
AND z.cesta_zalohy LIKE %s
AND p.category IS DISTINCT FROM 'Odpad-cache'
""", (like,))
print(f" {popis}: {cur.rowcount:,} řádků aktualizováno")
total += cur.rowcount
print(f"\nHotovo. Celkem aktualizováno: {total:,} řádků.")
conn.close()
+48
View File
@@ -0,0 +1,48 @@
"""
mark_colddata_junk.py — Označí jasně odpadní podsložky v Tower1/#ColdData
jako nechceme. NEoznačuje SynologyMaly, Honza, VladkoSoubory, Qnap, Tatinek,
DedupPhotos (ty se zkoumají zvlášť).
Kategorie:
Porno*, MadelineIsWicked -> 'Porno'
000 TORENT OBRAZKY, MoMA, Dali,
hudební alba, eBooky -> 'Odpad-torrent'
"""
import sys
import psycopg2
sys.stdout.reconfigure(encoding="utf-8")
DB = dict(host="192.168.1.76", port=5432, user="vladimir.buzalka",
password="Vlado7309208104++", database="fotky_buzalkovi")
BASE = "/mnt/user/ZalohaVsechObrazku/Tower1/#ColdData/"
# (popis, LIKE vzor, kategorie)
RULES = [
("Porno", BASE + "Porno/%", "Porno"),
("Porno1", BASE + "Porno1/%", "Porno"),
("MadelineIsWicked", BASE + "MadelineIsWicked/%", "Porno"),
("000 TORENT OBRAZKY",BASE + "000 TORENT OBRAZKY/%","Odpad-torrent"),
("Museum of Modern Art NY", BASE + "Museum of Modern Art NY/%", "Odpad-torrent"),
]
conn = psycopg2.connect(**DB)
conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
cur = conn.cursor()
total = 0
for popis, like, kat in RULES:
cur.execute("""
UPDATE photos p
SET wanted = FALSE, category = %s
FROM zaloha_obrazku z
WHERE p.zaloha_id = z.id
AND z.cesta_zalohy LIKE %s
AND p.category IS DISTINCT FROM %s
""", (kat, like, kat))
print(f" {popis:28s} -> {kat:14s}: {cur.rowcount:,}")
total += cur.rowcount
print(f"\nHotovo. Celkem: {total:,} řádků označeno jako odpad.")
conn.close()
@@ -0,0 +1,29 @@
"""
mark_colddata_zbytek.py — Zbytek #ColdData (vše dosud neroztříděné):
DropBox (software help obrázky), hudební alba, Salvador Dali, eBooky,
Truecrypt, Delikatesy ... -> odpad.
wanted=FALSE, category='Odpad-cache'
"""
import sys
import psycopg2
sys.stdout.reconfigure(encoding="utf-8")
DB = dict(host="192.168.1.76", port=5432, user="vladimir.buzalka",
password="Vlado7309208104++", database="fotky_buzalkovi")
conn = psycopg2.connect(**DB)
conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
cur = conn.cursor()
cur.execute("""
UPDATE photos p
SET wanted = FALSE, category = 'Odpad-cache'
FROM zaloha_obrazku z
WHERE p.zaloha_id = z.id
AND z.cesta_zalohy LIKE '/mnt/user/ZalohaVsechObrazku/Tower1/#ColdData/%'
AND p.category IS NULL
""")
print(f"#ColdData zbytek -> Odpad-cache: {cur.rowcount:,} řádků")
conn.close()
+27
View File
@@ -0,0 +1,27 @@
"""
mark_dedup_rodina.py — DedupPhotos (Samsung S7 Edge 2019, 285 ks).
Ověřeno: všechny unikátní, žádné duplikáty jinde -> Rodina.
"""
import sys
import psycopg2
sys.stdout.reconfigure(encoding="utf-8")
DB = dict(host="192.168.1.76", port=5432, user="vladimir.buzalka",
password="Vlado7309208104++", database="fotky_buzalkovi")
conn = psycopg2.connect(**DB)
conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
cur = conn.cursor()
cur.execute("""
UPDATE photos p
SET wanted = TRUE, category = 'Rodina'
FROM zaloha_obrazku z
WHERE p.zaloha_id = z.id
AND z.cesta_zalohy LIKE %s
AND (p.wanted = FALSE OR p.category IS DISTINCT FROM 'Rodina')
""", ('/mnt/user/ZalohaVsechObrazku/Tower1/#ColdData/DedupPhotos/%',))
print(f"DedupPhotos -> Rodina: {cur.rowcount:,} řádků")
conn.close()
+32
View File
@@ -0,0 +1,32 @@
"""
mark_ltbs_212.py — Potvrzeno uživatelem:
LTBS -> Rodina (rodinné fotky, názvy obsahují BUZALKA)
212 -> Odpad-dokumenty (skeny smluv Confidentiality Agreement)
"""
import sys
import psycopg2
sys.stdout.reconfigure(encoding="utf-8")
DB = dict(host="192.168.1.76", port=5432, user="vladimir.buzalka",
password="Vlado7309208104++", database="fotky_buzalkovi")
B = "/mnt/user/ZalohaVsechObrazku/Tower1/#ColdData/"
RULES = [
("LTBS", B + "LTBS/%", True, "Rodina"),
("212", B + "212/%", False, "Odpad-dokumenty"),
]
conn = psycopg2.connect(**DB)
conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
cur = conn.cursor()
for popis, like, wanted, kat in RULES:
cur.execute("""
UPDATE photos p SET wanted=%s, category=%s
FROM zaloha_obrazku z
WHERE p.zaloha_id=z.id AND z.cesta_zalohy LIKE %s
AND (p.wanted IS DISTINCT FROM %s OR p.category IS DISTINCT FROM %s)
""", (wanted, kat, like, wanted, kat))
print(f" {popis:6s} -> {kat:16s} (wanted={wanted}): {cur.rowcount:,}")
conn.close()
print("Hotovo.")
+41
View File
@@ -0,0 +1,41 @@
"""
mark_pomoc.py — Tower1/#ColdData/pomoc, potvrzeno uživatelem:
Samsung GT-S5230 (s EXIF) -> Rodina
porno snapshoty (bez EXIF) -> Porno
"""
import sys
import psycopg2
sys.stdout.reconfigure(encoding="utf-8")
DB = dict(host="192.168.1.76", port=5432, user="vladimir.buzalka",
password="Vlado7309208104++", database="fotky_buzalkovi")
P = "/mnt/user/ZalohaVsechObrazku/Tower1/#ColdData/pomoc/%"
conn = psycopg2.connect(**DB)
conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
cur = conn.cursor()
# Samsung (s EXIF) -> Rodina
cur.execute("""
UPDATE photos p SET wanted=TRUE, category='Rodina'
FROM zaloha_obrazku z
WHERE p.zaloha_id=z.id AND z.cesta_zalohy LIKE %s
AND p.camera_make IS NOT NULL
AND (p.wanted=FALSE OR p.category IS DISTINCT FROM 'Rodina')
""", (P,))
print(f" pomoc Samsung -> Rodina: {cur.rowcount:,}")
# porno (bez EXIF) -> Porno
cur.execute("""
UPDATE photos p SET wanted=FALSE, category='Porno'
FROM zaloha_obrazku z
WHERE p.zaloha_id=z.id AND z.cesta_zalohy LIKE %s
AND p.camera_make IS NULL
AND p.category IS DISTINCT FROM 'Porno'
""", (P,))
print(f" pomoc porno -> Porno : {cur.rowcount:,}")
conn.close()
print("Hotovo.")
@@ -0,0 +1,29 @@
"""
mark_qnap_exif_odpad.py — Qnap fotky s EXIF jsou CD inlety/obaly -> odpad.
Potvrzeno uživatelem přes náhled (81 ks).
wanted=FALSE, category='Odpad-obaly'
"""
import sys
import psycopg2
sys.stdout.reconfigure(encoding="utf-8")
DB = dict(host="192.168.1.76", port=5432, user="vladimir.buzalka",
password="Vlado7309208104++", database="fotky_buzalkovi")
conn = psycopg2.connect(**DB)
conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
cur = conn.cursor()
cur.execute("""
UPDATE photos p
SET wanted = FALSE, category = 'Odpad-obaly'
FROM zaloha_obrazku z
WHERE p.zaloha_id = z.id
AND z.cesta_zalohy LIKE %s
AND p.camera_make IS NOT NULL
AND p.category IS DISTINCT FROM 'Odpad-obaly'
""", ('/mnt/user/ZalohaVsechObrazku/Tower1/#ColdData/Qnap/%',))
print(f"Qnap (EXIF) -> Odpad-obaly: {cur.rowcount:,} řádků")
conn.close()
@@ -0,0 +1,28 @@
"""
mark_qnap_nepotrebne.py — Qnap_nepotrebne (COMPLETED TORRENTS: komiksy,
učebnice, software) -> odpad. Potvrzeno uživatelem.
wanted=FALSE, category='Odpad-torrent'
"""
import sys
import psycopg2
sys.stdout.reconfigure(encoding="utf-8")
DB = dict(host="192.168.1.76", port=5432, user="vladimir.buzalka",
password="Vlado7309208104++", database="fotky_buzalkovi")
conn = psycopg2.connect(**DB)
conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
cur = conn.cursor()
cur.execute("""
UPDATE photos p
SET wanted = FALSE, category = 'Odpad-torrent'
FROM zaloha_obrazku z
WHERE p.zaloha_id = z.id
AND z.cesta_zalohy LIKE %s
AND p.category IS DISTINCT FROM 'Odpad-torrent'
""", ('/mnt/user/ZalohaVsechObrazku/Tower1/#ColdData/Qnap_nepotrebne/%',))
print(f"Qnap_nepotrebne -> Odpad-torrent: {cur.rowcount:,} řádků")
conn.close()
@@ -0,0 +1,29 @@
"""
mark_qnap_zbytek_odpad.py — Zbytek Qnap bez EXIF (obaly alb, downloads,
kurzové thumbnaily) -> odpad. Potvrzeno uživatelem.
wanted=FALSE, category='Odpad-cache'
"""
import sys
import psycopg2
sys.stdout.reconfigure(encoding="utf-8")
DB = dict(host="192.168.1.76", port=5432, user="vladimir.buzalka",
password="Vlado7309208104++", database="fotky_buzalkovi")
conn = psycopg2.connect(**DB)
conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
cur = conn.cursor()
cur.execute("""
UPDATE photos p
SET wanted = FALSE, category = 'Odpad-cache'
FROM zaloha_obrazku z
WHERE p.zaloha_id = z.id
AND z.cesta_zalohy LIKE %s
AND p.camera_make IS NULL
AND p.category IS DISTINCT FROM 'Odpad-cache'
""", ('/mnt/user/ZalohaVsechObrazku/Tower1/#ColdData/Qnap/%',))
print(f"Qnap (zbytek bez EXIF) -> Odpad-cache: {cur.rowcount:,} řádků")
conn.close()
@@ -0,0 +1,32 @@
"""
mark_synology_photo_rodina.py — Označí celý rodinný fotoarchiv
Tower1/#ColdData/SynologyMaly/photo/ jako Rodina.
Potvrzeno uživatelem přes náhled. Foťáky 1998-2015 (Canon S40/A40/IXUS,
Nikon D80, iPhony, Lumia...). 99 % má EXIF, archiv je unikátní (bez duplikátů).
wanted=TRUE, category='Rodina'
"""
import sys
import psycopg2
sys.stdout.reconfigure(encoding="utf-8")
DB = dict(host="192.168.1.76", port=5432, user="vladimir.buzalka",
password="Vlado7309208104++", database="fotky_buzalkovi")
conn = psycopg2.connect(**DB)
conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
cur = conn.cursor()
cur.execute("""
UPDATE photos p
SET wanted = TRUE, category = 'Rodina'
FROM zaloha_obrazku z
WHERE p.zaloha_id = z.id
AND z.cesta_zalohy LIKE %s
AND (p.wanted = FALSE OR p.category IS DISTINCT FROM 'Rodina')
""", ('/mnt/user/ZalohaVsechObrazku/Tower1/#ColdData/SynologyMaly/photo/%',))
print(f"SynologyMaly/photo -> Rodina: {cur.rowcount:,} řádků (wanted=TRUE)")
conn.close()
@@ -0,0 +1,42 @@
"""
mark_synology_rodina.py — #Synology fotoarchivy potvrzené uživatelem -> Rodina:
#SERVER/Y/HD02#FOTKY (~30 789)
#SERVER/Y/HD02#FOTKY MAMKA (~1 167)
DropboxFotky (~14 882)
HD02#DVD OSOBNI (~5 834)
Fotky (~2 138)
"""
import sys
import psycopg2
sys.stdout.reconfigure(encoding="utf-8")
DB = dict(host="192.168.1.76", port=5432, user="vladimir.buzalka",
password="Vlado7309208104++", database="fotky_buzalkovi")
S = "/mnt/user/ZalohaVsechObrazku/Tower1/#Synology/Public/"
LIKES = [
("HD02#FOTKY", S + "#SERVER/Y/HD02#FOTKY/%"),
("HD02#FOTKY MAMKA", S + "#SERVER/Y/HD02#FOTKY MAMKA/%"),
("DropboxFotky", S + "DropboxFotky/%"),
("HD02#DVD OSOBNI", S + "HD02#DVD OSOBNI/%"),
("Fotky", S + "Fotky/%"),
]
conn = psycopg2.connect(**DB)
conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
cur = conn.cursor()
total = 0
for popis, like in LIKES:
cur.execute("""
UPDATE photos p SET wanted=TRUE, category='Rodina'
FROM zaloha_obrazku z
WHERE p.zaloha_id=z.id AND z.cesta_zalohy LIKE %s
AND (p.wanted=FALSE OR p.category IS DISTINCT FROM 'Rodina')
""", (like,))
print(f" {popis:18s}: {cur.rowcount:,}")
total += cur.rowcount
print(f"\nHotovo. Celkem nově/změněno na Rodina: {total:,}")
conn.close()
@@ -0,0 +1,30 @@
"""
mark_synology_x_odpad.py — Označí Tower1/#ColdData/SynologyMaly/Public/X/
jako odpad (skenovaná série publikací e0xxx, 3744x5616, bez EXIF).
Potvrzeno uživatelem přes náhled.
wanted=FALSE, category='Odpad-sken'
"""
import sys
import psycopg2
sys.stdout.reconfigure(encoding="utf-8")
DB = dict(host="192.168.1.76", port=5432, user="vladimir.buzalka",
password="Vlado7309208104++", database="fotky_buzalkovi")
conn = psycopg2.connect(**DB)
conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
cur = conn.cursor()
cur.execute("""
UPDATE photos p
SET wanted = FALSE, category = 'Odpad-sken'
FROM zaloha_obrazku z
WHERE p.zaloha_id = z.id
AND z.cesta_zalohy LIKE %s
AND p.category IS DISTINCT FROM 'Odpad-sken'
""", ('/mnt/user/ZalohaVsechObrazku/Tower1/#ColdData/SynologyMaly/Public/X/%',))
print(f"SynologyMaly/Public/X -> Odpad-sken: {cur.rowcount:,} řádků")
conn.close()
@@ -0,0 +1,39 @@
"""
mark_synology_zbytek.py — Dokončí SynologyMaly:
Public/Dropbox -> Rodina (chceme, potvrzeno uživatelem)
music/web/video -> Odpad-cache (obaly, web obrázky)
"""
import sys
import psycopg2
sys.stdout.reconfigure(encoding="utf-8")
DB = dict(host="192.168.1.76", port=5432, user="vladimir.buzalka",
password="Vlado7309208104++", database="fotky_buzalkovi")
BASE = "/mnt/user/ZalohaVsechObrazku/Tower1/#ColdData/SynologyMaly/"
RULES = [
("Public/Dropbox", BASE + "Public/Dropbox/%", True, "Rodina"),
("music", BASE + "music/%", False, "Odpad-cache"),
("web", BASE + "web/%", False, "Odpad-cache"),
("video", BASE + "video/%", False, "Odpad-cache"),
]
conn = psycopg2.connect(**DB)
conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
cur = conn.cursor()
for popis, like, wanted, kat in RULES:
cur.execute("""
UPDATE photos p
SET wanted = %s, category = %s
FROM zaloha_obrazku z
WHERE p.zaloha_id = z.id
AND z.cesta_zalohy LIKE %s
AND (p.wanted IS DISTINCT FROM %s OR p.category IS DISTINCT FROM %s)
""", (wanted, kat, like, wanted, kat))
print(f" {popis:16s} -> {kat:12s} (wanted={wanted}): {cur.rowcount:,}")
conn.close()
print("Hotovo.")
+40
View File
@@ -0,0 +1,40 @@
"""
mark_tatinek_cast1.py — Tatinek U, rozhodnuté části (potvrzeno uživatelem):
Fractal (iPhone 13) -> Rodina (chceme)
DarthAnihilator + Screenshots-> Odpad-screenshot
Duplicates/Compass (dokumenty)-> Odpad-dokumenty
"""
import sys
import psycopg2
sys.stdout.reconfigure(encoding="utf-8")
DB = dict(host="192.168.1.76", port=5432, user="vladimir.buzalka",
password="Vlado7309208104++", database="fotky_buzalkovi")
T = "/mnt/user/ZalohaVsechObrazku/Tower1/#ColdData/Tatinek U/"
RULES = [
("Fractal (iPhone)", T + "D/!!!Days/%Fractal%", True, "Rodina"),
("DarthAnihilator", T + "DarthAnihilator/%", False, "Odpad-screenshot"),
("Screenshots", T + "Screenshots/%", False, "Odpad-screenshot"),
("Compass dokumenty", T + "Duplicates/Compass/%",False, "Odpad-dokumenty"),
]
conn = psycopg2.connect(**DB)
conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
cur = conn.cursor()
for popis, like, wanted, kat in RULES:
cur.execute("""
UPDATE photos p
SET wanted = %s, category = %s
FROM zaloha_obrazku z
WHERE p.zaloha_id = z.id
AND z.cesta_zalohy LIKE %s
AND (p.wanted IS DISTINCT FROM %s OR p.category IS DISTINCT FROM %s)
""", (wanted, kat, like, wanted, kat))
print(f" {popis:20s} -> {kat:18s} (wanted={wanted}): {cur.rowcount:,}")
conn.close()
print("Hotovo.")
@@ -0,0 +1,32 @@
"""
mark_tatinek_zbytek.py — Zbytek Tatinek U (produktové letáky, Sudoku,
screenshoty, web obrázky) -> odpad. Potvrzeno uživatelem.
Vše krom Fractal/DarthAnihilator/Screenshots/Compass -> Odpad-cache.
"""
import sys
import psycopg2
sys.stdout.reconfigure(encoding="utf-8")
DB = dict(host="192.168.1.76", port=5432, user="vladimir.buzalka",
password="Vlado7309208104++", database="fotky_buzalkovi")
conn = psycopg2.connect(**DB)
conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
cur = conn.cursor()
cur.execute("""
UPDATE photos p
SET wanted = FALSE, category = 'Odpad-cache'
FROM zaloha_obrazku z
WHERE p.zaloha_id = z.id
AND z.cesta_zalohy LIKE '/mnt/user/ZalohaVsechObrazku/Tower1/#ColdData/Tatinek U/%'
AND z.cesta_zalohy NOT LIKE '%Fractal%'
AND z.cesta_zalohy NOT LIKE '%/DarthAnihilator/%'
AND z.cesta_zalohy NOT LIKE '%/Screenshots/%'
AND z.cesta_zalohy NOT LIKE '%/Duplicates/Compass/%'
AND p.category IS DISTINCT FROM 'Odpad-cache'
""")
print(f"Tatinek U (zbytek) -> Odpad-cache: {cur.rowcount:,} řádků")
conn.close()
@@ -0,0 +1,42 @@
"""
mark_torrents_unwanted.py — Označí celé Tower/Torrents a Tower1/Torrents
jako nechceme (stažený torrent obsah: komiksy, vystřihovánky, speleologie,
porno, textury...). Žádné rodinné originály (ověřeno pixel-hashem + adresáři).
wanted=FALSE, category='Odpad-torrent'
Idempotentní + jedna session, autocommit (žádné paralelní běhy → bez deadlocku).
"""
import sys
import psycopg2
sys.stdout.reconfigure(encoding="utf-8")
DB = dict(host="192.168.1.76", port=5432, user="vladimir.buzalka",
password="Vlado7309208104++", database="fotky_buzalkovi")
DIRS = [
("Tower/Torrents", "/mnt/user/ZalohaVsechObrazku/Tower/Torrents%"),
("Tower1/Torrents", "/mnt/user/ZalohaVsechObrazku/Tower1/Torrents%"),
]
conn = psycopg2.connect(**DB)
conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
cur = conn.cursor()
total = 0
for popis, like in DIRS:
cur.execute("""
UPDATE photos p
SET wanted = FALSE,
category = 'Odpad-torrent'
FROM zaloha_obrazku z
WHERE p.zaloha_id = z.id
AND z.cesta_zalohy LIKE %s
AND p.category IS DISTINCT FROM 'Odpad-torrent'
""", (like,))
print(f" {popis}: {cur.rowcount:,} řádků")
total += cur.rowcount
print(f"\nHotovo. Celkem: {total:,} řádků → wanted=FALSE, category='Odpad-torrent'")
conn.close()
+44
View File
@@ -0,0 +1,44 @@
"""
mark_webs.py — #Synology/.../#SERVER/Y/WEBS (starý web Buzalka.cz/.com):
galerijní alba (g1/Albums, g2data/albums) -> Rodina (potvrzeno uživatelem)
zbytek (web šablony, moduly, skiny, e-shop) -> Odpad-cache
"""
import sys
import psycopg2
sys.stdout.reconfigure(encoding="utf-8")
DB = dict(host="192.168.1.76", port=5432, user="vladimir.buzalka",
password="Vlado7309208104++", database="fotky_buzalkovi")
WEBS = "/mnt/user/ZalohaVsechObrazku/Tower1/#Synology/Public/#SERVER/Y/WEBS/%"
conn = psycopg2.connect(**DB)
conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
cur = conn.cursor()
# Galerijní alba -> Rodina
cur.execute("""
UPDATE photos p SET wanted=TRUE, category='Rodina'
FROM zaloha_obrazku z
WHERE p.zaloha_id=z.id
AND z.cesta_zalohy LIKE %s
AND (z.cesta_zalohy LIKE '%%/Albums/%%' OR z.cesta_zalohy LIKE '%%/g2data/albums/%%')
AND (p.wanted=FALSE OR p.category IS DISTINCT FROM 'Rodina')
""", (WEBS,))
print(f" WEBS galerie -> Rodina: {cur.rowcount:,}")
# Zbytek WEBS -> Odpad-cache
cur.execute("""
UPDATE photos p SET wanted=FALSE, category='Odpad-cache'
FROM zaloha_obrazku z
WHERE p.zaloha_id=z.id
AND z.cesta_zalohy LIKE %s
AND z.cesta_zalohy NOT LIKE '%%/Albums/%%'
AND z.cesta_zalohy NOT LIKE '%%/g2data/albums/%%'
AND p.category IS DISTINCT FROM 'Odpad-cache'
""", (WEBS,))
print(f" WEBS zbytek -> Odpad-cache: {cur.rowcount:,}")
conn.close()
print("Hotovo.")
+38
View File
@@ -0,0 +1,38 @@
"""
mark_xperie_vladko.py — Osobní složky v #ColdData, chceme zachovat:
Honza fotky z Xperie -> kamarad Jan Luxemburk -> category 'Kamaradi'
VladkoSoubory -> syn Vladimir Buzalka ml -> category 'Rodina'
Obojí wanted=TRUE (potvrzeno uživatelem).
"""
import sys
import psycopg2
sys.stdout.reconfigure(encoding="utf-8")
DB = dict(host="192.168.1.76", port=5432, user="vladimir.buzalka",
password="Vlado7309208104++", database="fotky_buzalkovi")
BASE = "/mnt/user/ZalohaVsechObrazku/Tower1/#ColdData/"
RULES = [
("Honza fotky z Xperie", BASE + "Honza fotky z Xperie/%", "Kamaradi"),
("VladkoSoubory", BASE + "VladkoSoubory/%", "Rodina"),
]
conn = psycopg2.connect(**DB)
conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
cur = conn.cursor()
for popis, like, kat in RULES:
cur.execute("""
UPDATE photos p
SET wanted = TRUE, category = %s
FROM zaloha_obrazku z
WHERE p.zaloha_id = z.id
AND z.cesta_zalohy LIKE %s
AND (p.wanted = FALSE OR p.category IS DISTINCT FROM %s)
""", (kat, like, kat))
print(f" {popis:22s} -> {kat:10s}: {cur.rowcount:,}")
conn.close()
print("Hotovo.")
+34
View File
@@ -0,0 +1,34 @@
"""
restore_maminka.py — Vrátí jedinečné iPhone fotky z
Tower/appdata/photoprism/sidecar/Maminka zpět na wanted=TRUE, category='Rodina'.
Tyto soubory jsem omylem označil jako Odpad-cache, ačkoli jde o jediné kopie
rodinných fotek v záloze (žádný originál jinde neexistuje).
Filtr camera_make IS NOT NULL = bereme jen skutečné fotky, ne případné
technické soubory v té složce.
"""
import sys
import psycopg2
sys.stdout.reconfigure(encoding="utf-8")
DB = dict(host="192.168.1.76", port=5432, user="vladimir.buzalka",
password="Vlado7309208104++", database="fotky_buzalkovi")
conn = psycopg2.connect(**DB)
conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
cur = conn.cursor()
cur.execute("""
UPDATE photos p
SET wanted = TRUE,
category = 'Rodina'
FROM zaloha_obrazku z
WHERE p.zaloha_id = z.id
AND z.cesta_zalohy LIKE %s
AND p.camera_make IS NOT NULL
""", ('/mnt/user/ZalohaVsechObrazku/Tower/appdata/photoprism/sidecar/Maminka%',))
print(f"Vráceno na Rodina: {cur.rowcount:,} řádků (wanted=TRUE, category='Rodina')")
conn.close()
+115
View File
@@ -104,6 +104,121 @@ Tyto cesty **nechceme** — `wanted` zůstane FALSE, nezpracovávat:
---
## Session 2026-06-05
### Nástroje — nový web preview na Toweru
- **`00 PictureCollector/preview_server.py`** — HTTP server běžící v Docker kontejneru
`python-runner` na Toweru. Renderuje thumbnaily lokálně (rychlé), má lightbox
(klik = plné rozlišení přes `/image?id=`).
- URL: `http://192.168.1.76:8766/preview?ids=101,202,303&thumb=320`
- Kontejner má mount `/mnt/remotes/TOWER1.LAN_ZalohaVsechObrazku``/mnt/ZalohaVsechObrazku:ro`
- Knihovny v image: `psycopg` (v3!), `Pillow` (zabakováno přes `docker commit`)
- **MCP konektor `fotky-buzalkovi`** — `query/tables/describe_table/stats`. SQL dotazy
pouštím přímo, bez mezikroku.
### Pracovní metodika (důležité!)
- **NEoznačovat nechtěné proaktivně** — vždy jen po explicitním pokynu uživatele.
- Před každým hromadným označením složky za odpad: **ověřit jedinečnost přes
pixel/file-hash** (`sha256_pixels`, `sha256_file`). V cache/odpadních složkách se
můžou skrývat unikátní rodinné fotky (viz „Maminka" níže).
- UPDATE pouštět vždy jako **jedna session, autocommit** — paralelní běhy způsobují
deadlock. Před každým během kontrola `pg_stat_activity` na zaseklé UPDATE.
- Marker skripty: `mark_*.py` v `00 PictureCollector/` (idempotentní přes filtr
`category IS DISTINCT FROM ...`).
### Kategorie (rozšířený číselník)
| category | wanted | význam |
|---|---|---|
| `Rodina` | TRUE | rodinné fotky |
| `Kamaradi` | TRUE | fotky kamarádů (Jan Luxemburk Xperie) |
| `Fotopast` | TRUE | fotopast |
| `Odpad-cache` | FALSE | cache aplikací, obaly, web obrázky |
| `Odpad-torrent` | FALSE | stažené torrenty (komiksy, vystřihovánky, software) |
| `Odpad-sken` | FALSE | skenované publikace (série stránek) |
| `Odpad-obaly` | FALSE | CD inlety/obaly s EXIF |
| `Odpad-dokumenty` | FALSE | skeny dokumentů/smluv |
| `Odpad-screenshot` | FALSE | screenshoty z her/aplikací |
| `Porno` | FALSE | porno |
| `Skener-nechceme` | FALSE | skenery (viz session 2026-06-04) |
### Zpracované velké složky
| Složka | Fotek | Výsledek |
|---|---:|---|
| **Tower/appdata** | 1 006 032 | odpad (Odpad-cache); **zachráněno 2 274** fotek Maminky (iPhone 12) z `photoprism/sidecar/Maminka` → Rodina |
| **Tower/Sabnzbd** | 48 442 | celé odpad (usenet downloads, profi Nikon D5/D4 = stažené, ne rodina) |
| **Tower/Torrents + Tower1/Torrents** | 100 938 | celé odpad (Odpad-torrent) |
| **Tower1/#ColdData** | 84 374 | roztříděno 100 %, viz níže |
| **Tower1/#Synology** | 66 462 | rozpracováno (~95 %), viz níže |
### #Synology — rozpracováno (Public/...)
Velký rodinný NAS fotoarchiv. Hash-check potvrdil, že `HD02#FOTKY` je z 99,6 %
**unikátní** (jen 132 fotek má kopii jinde) — není to duplikát jiné zálohy.
**Rodina (chceme) — HOTOVO:**
- `#SERVER/Y/HD02#FOTKY` (30 789) — hlavní archiv 20082020
- `#SERVER/Y/HD02#FOTKY MAMKA` (1 167)
- `DropboxFotky` (14 882)
- `HD02#DVD OSOBNI` (5 834)
- `Fotky` (2 138)
- `#SERVER/Y/WEBS` galerijní alba (906) — starý web Buzalka.com/Gallery2,
alba akcí (USA 2006, lyžování Stoderzinken 2007, Orlík, Střetávka…)
**Odpad — HOTOVO:**
- `#SERVER/Y/WEBS` zbytek (2 153) — web šablony/skiny/moduly Buzalka.cz e-shop → Odpad-cache
**JEŠTĚ NEROZHODNUTO (zbývá ~9 200):**
- `AFotkyFotky` (4 550, 2 245 EXIF) — pravděpodobně fotky
- `#SERVER/JNJ` (1 835, 45 modelů) — smíšené
- `#SERVER/E` (1 629) — smíšené
- `VideoMichalkaiCloud` (277) — Michalka iCloud, pravděpodobně rodina
- `GoPro` (70) — pravděpodobně rodina
- drobnosti (`###StatSoft`, `#SOFTWARE`, `HD02#AUDIO CD A DVD`, `#Kurzy`, `X`…) ~220 → odpad
### #ColdData — detailní rozpad
**Rodina (chceme):**
- `SynologyMaly/photo` (24 198) + `SynologyMaly/Public/Dropbox` (447) — rodinný archiv 19982015 (Canon S40/A40/IXUS, Nikon D80, iPhony, Lumia). Unikátní (bez duplikátů).
- `VladkoSoubory` (871) — syn Vladimír Buzalka ml.
- `DedupPhotos` (285) — Samsung S7 Edge 2019, ověřeno unikátní.
- `LTBS` (40) — rodina (názvy souborů obsahují BUZALKA).
- `Tatinek U/.../Fractal` (19) — iPhone 13, táta.
- `pomoc` Samsung GT-S5230 (26) — rodina.
**Kamaradi (chceme):** `Honza fotky z Xperie` (2 047) — kamarád Jan Luxemburk.
**Odpad:**
- `Porno`, `Porno1`, `MadelineIsWicked` → Porno
- `000 TORENT OBRAZKY`, `Qnap_nepotrebne` (X-Men komiksy, učebnice, software) → Odpad-torrent
- `SynologyMaly/Public/X` (9 134, série e0xxx skenů) → Odpad-sken
- `Qnap` (MP3 obaly, downloads, 81 EXIF CD inletů) → Odpad-cache/Odpad-obaly
- `Museum of Modern Art NY`, hudební alba, Salvador Dali, eBooky, Truecrypt, `DropBox` (software help obrázky) → Odpad-cache
- `Tatinek U` Compass dokumenty + `212` smlouvy → Odpad-dokumenty; DarthAnihilator/Screenshots → Odpad-screenshot
- `pomoc` LegalPorno/Kink snapshoty (28) → Porno
**Výsledek #ColdData:** Rodina 25 889 · Kamaradi 2 047 · Porno 23 502 · Odpad-torrent 21 300 · Odpad-sken 9 134 · Odpad-cache 2 269 · ostatní odpad 228.
### Poučení
- **Cache/download složky ≠ čistý odpad.** PhotoPrism si do `sidecar/` ukládá unikátní
JPEG kopie fotek, jejichž originály v záloze nejsou → před smazáním vždy hash-check.
- Camera-rules (např. „všechny Nikon D5/D4 = Rodina") dávají **false-positives** ve
stažených složkách → path má přednost, ale ověřit obsah.
### Na řadě
- [ ] **Dokončit #Synology** — zbývá ~9 200: `AFotkyFotky`, `JNJ`, `E`,
`VideoMichalkaiCloud`, `GoPro` (náhledy → rozhodnout), drobnosti → odpad
- [ ] Velké zbývající: `TW22/D` (63 706), `Tower1/#Pomoc` (34 500), `Tower1/#Synologymaly` (29 934)
- [ ] Dokončit Canony (a pak ostatní značky foťáků)
- [ ] „BEZ KAMERY" skupina (path-based)
---
## Backlog otevřených otázek
1. Co s "sirotky" bez EXIF — `mtime` / odmítnout / označit?