-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathphotoprism_client.py
More file actions
133 lines (117 loc) · 6.41 KB
/
Copy pathphotoprism_client.py
File metadata and controls
133 lines (117 loc) · 6.41 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
import requests
class PhotoPrismClient:
"""Ein Client für die Interaktion mit der PhotoPrism API."""
def __init__(self, base_url: str, username: str, password: str):
self.base_url = base_url.rstrip('/')
self.username = username
self.password = password
self.session = requests.Session()
self.session_id = None
def login(self) -> bool:
"""Meldet sich bei PhotoPrism an und speichert die Session-ID."""
login_url = f"{self.base_url}/api/v1/session"
try:
response = self.session.post(login_url, json={"username": self.username, "password": self.password})
response.raise_for_status()
self.session_id = response.json().get("id")
self.session.headers.update({"X-Session-ID": self.session_id})
print("Erfolgreich bei PhotoPrism angemeldet.")
return True
except requests.RequestException as e:
print(f"Fehler bei der Anmeldung an PhotoPrism: {e}")
return False
def search_photos(self, query: str) -> list:
"""Sucht nach Fotos basierend auf einem Such-Query."""
if not self.session_id:
print("Nicht angemeldet. Bitte zuerst login() aufrufen.")
return []
search_url = f"{self.base_url}/api/v1/photos"
# Erhöhe count (PhotoPrism akzeptiert typischerweise bis 500) um weniger Requests zu brauchen
params = {"count": 500, "offset": 0, "q": query, "files": 1}
all_photos = []
try:
batch_index = 0
while True:
response = self.session.get(search_url, params=params)
if response.status_code == 400:
print(f"⚠️ 400 Bad Request bei Fotosuche (Offset {params['offset']}). Response: {response.text[:500]}")
response.raise_for_status()
photos = response.json()
batch_len = len(photos)
if batch_index == 0:
print(f"Foto-Suche: erster Batch {batch_len} Elemente (count={params['count']}).")
else:
print(f"Foto-Suche: Batch {batch_index} -> {batch_len} Elemente (Offset {params['offset']}).")
if batch_len == 0:
break
all_photos.extend(photos)
# Wenn weniger als count zurückkamen, sind wir fertig
if batch_len < params['count']:
break
params["offset"] += batch_len
batch_index += 1
return all_photos
except requests.RequestException as e:
print(f"Fehler bei der Foto-Suche in PhotoPrism: {e}")
return []
def start_rescan(self, folder_path: str | None = None, force_full: bool = True, cleanup: bool = False, max_wait_minutes: int = 30):
"""Startet einen (optional gezielten) Rescan in PhotoPrism.
Parameter:
folder_path: Optional relativer Unterordner (wie in PhotoPrism 'Path'), z.B. '2025/08'.
Wird über 'path' an die API gesendet. Ist None => Root ("/").
force_full: Wenn True wird rescan=True gesetzt (alle Dateien im Zielordner erneut prüfen),
sonst rescan=False (nur neue / geänderte Dateien).
cleanup: Ob nach Indexlauf ein Cleanup erfolgen soll.
max_wait_minutes: Bei "Already running" maximal so viele Minuten warten und erneut versuchen.
/api/v1/index akzeptiert JSON { path, rescan, cleanup }.
"""
if not self.session_id:
print("Nicht angemeldet.")
return
index_url = f"{self.base_url}/api/v1/index"
path_value = folder_path if folder_path else "/"
if folder_path:
print(f"\nStarte gezielten PhotoPrism Rescan für Ordner '{folder_path}' (force_full={force_full})...")
else:
print(f"\nStarte allgemeinen PhotoPrism Rescan (force_full={force_full})...")
# Kanonische Haupt-Payload basierend auf Parametern
payload = {"path": path_value, "rescan": bool(force_full), "cleanup": bool(cleanup)}
import time
start_time = time.time()
attempt = 1
while True:
try:
# Timeout: PhotoPrism sollte innerhalb 10 Sekunden antworten
response = self.session.post(index_url, json=payload, timeout=10)
if response.status_code == 500:
# Extrahiere strukturierte Fehlermeldung für bekannte Fälle
try:
err_json = response.json()
error_msg = err_json.get('error', 'Unbekannter Serverfehler')
if 'already running' in error_msg.lower():
elapsed_minutes = (time.time() - start_time) / 60
if elapsed_minutes >= max_wait_minutes:
print(f"⏰ Maximale Wartezeit ({max_wait_minutes} min) erreicht.")
print(" → Rescan konnte nicht gestartet werden (läuft immer noch).")
return
print(f"ℹ️ Rescan läuft bereits (Versuch {attempt}, {elapsed_minutes:.1f}/{max_wait_minutes} min)")
print(" → Warte 60 Sekunden und versuche erneut...")
time.sleep(60)
attempt += 1
continue
else:
print(f"⚠️ Serverfehler: {error_msg}")
except Exception:
print(f"⚠️ Serverfehler (Status 500): {response.text.strip()[:200]}")
return
response.raise_for_status()
print(f"✅ Rescan erfolgreich gestartet (Status {response.status_code}, Versuch {attempt}).")
return
except requests.Timeout:
print("⏰ Timeout: PhotoPrism hat nicht innerhalb 10 Sekunden geantwortet.")
print(" → Rescan läuft wahrscheinlich trotzdem im Hintergrund.")
return
except requests.RequestException as e:
print(f"❌ Fehler beim Starten des Rescans: {e}")
print("Tipp: Prüfen Sie PhotoPrism Server-Logs oder starten Sie manuell in der Web-UI.")
return