126 lines
4.1 KiB
Python
126 lines
4.1 KiB
Python
#!/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()
|