fix(immich): Tweak logging
This commit is contained in:
+14
-48
@@ -10,7 +10,6 @@ import argparse
|
|||||||
import uuid
|
import uuid
|
||||||
import hashlib
|
import hashlib
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from collections import defaultdict
|
|
||||||
from typing import List, Dict
|
from typing import List, Dict
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import httpx
|
import httpx
|
||||||
@@ -48,14 +47,11 @@ class ImmichClient:
|
|||||||
self.client.close()
|
self.client.close()
|
||||||
|
|
||||||
def get_or_create_album(self, name: str) -> str:
|
def get_or_create_album(self, name: str) -> str:
|
||||||
"""Get existing album or create new one."""
|
|
||||||
# Check cache first
|
|
||||||
try:
|
try:
|
||||||
return self._album_id_by_name[name]
|
return self._album_id_by_name[name]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Try to find existing album
|
|
||||||
try:
|
try:
|
||||||
res = self.client.get("/albums")
|
res = self.client.get("/albums")
|
||||||
res.raise_for_status()
|
res.raise_for_status()
|
||||||
@@ -70,7 +66,6 @@ class ImmichClient:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Error searching for album: {e}")
|
logger.warning(f"Error searching for album: {e}")
|
||||||
|
|
||||||
# Create new album
|
|
||||||
logger.info(f"Creating new album: {name}")
|
logger.info(f"Creating new album: {name}")
|
||||||
res = self.client.post("/albums", json={"albumName": name})
|
res = self.client.post("/albums", json={"albumName": name})
|
||||||
res.raise_for_status()
|
res.raise_for_status()
|
||||||
@@ -80,7 +75,6 @@ class ImmichClient:
|
|||||||
return album_id
|
return album_id
|
||||||
|
|
||||||
def create_tags(self, tags: list[str]) -> dict[str, str]:
|
def create_tags(self, tags: list[str]) -> dict[str, str]:
|
||||||
"""Create or get tags, return mapping of tag_name -> tag_id."""
|
|
||||||
if not tags:
|
if not tags:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
@@ -98,7 +92,7 @@ class ImmichClient:
|
|||||||
logger.debug(f"All tags found in cache: {tags}")
|
logger.debug(f"All tags found in cache: {tags}")
|
||||||
return cached_tags
|
return cached_tags
|
||||||
|
|
||||||
logger.info(f"Creating/retrieving tags: {missing_tags}")
|
logger.debug(f"Creating tags: {missing_tags}")
|
||||||
res = self.client.put("/tags", json={"tags": missing_tags})
|
res = self.client.put("/tags", json={"tags": missing_tags})
|
||||||
res.raise_for_status()
|
res.raise_for_status()
|
||||||
|
|
||||||
@@ -191,19 +185,17 @@ def upload_image(
|
|||||||
tags = tags or []
|
tags = tags or []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Upload the image
|
asset = client.upload_image(image_path)
|
||||||
asset_response = client.upload_image(image_path)
|
asset_id = asset["id"]
|
||||||
asset_id = asset_response["id"]
|
status = asset.get("status", "uploaded")
|
||||||
status = asset_response.get("status", "uploaded")
|
|
||||||
|
|
||||||
logger.info(f"Uploaded: {image_path.name} (status={status})")
|
logger.info(f"Uploaded: {image_path.name} (status={status})")
|
||||||
|
|
||||||
# Skip album/tag processing for duplicates
|
# Skip album/tag processing for duplicates
|
||||||
if status == "duplicate":
|
# if status == "duplicate":
|
||||||
logger.info(f"Skipping album/tag processing for duplicate: {image_path.name}")
|
# logger.info(f"Skipping album/tag processing for duplicate: {image_path.name}")
|
||||||
return
|
# return
|
||||||
|
|
||||||
# Handle albums
|
|
||||||
album_ids = []
|
album_ids = []
|
||||||
for album_name in albums:
|
for album_name in albums:
|
||||||
try:
|
try:
|
||||||
@@ -214,7 +206,6 @@ def upload_image(
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to add to album {album_name}: {e}")
|
logger.error(f"Failed to add to album {album_name}: {e}")
|
||||||
|
|
||||||
# Handle tags
|
|
||||||
if tags:
|
if tags:
|
||||||
try:
|
try:
|
||||||
tag_id_by_name = client.create_tags(tags)
|
tag_id_by_name = client.create_tags(tags)
|
||||||
@@ -235,25 +226,20 @@ class ParsedFilename:
|
|||||||
studio: str | None = None
|
studio: str | None = None
|
||||||
|
|
||||||
|
|
||||||
def parse_image_filename(image_path: Path) -> ParsedFilename | None:
|
def parse_filename(image_path: Path) -> ParsedFilename | None:
|
||||||
"""Parse image filename to extract actor names."""
|
|
||||||
# Remove extension and split by ' -- '
|
|
||||||
filename = image_path.stem
|
filename = image_path.stem
|
||||||
if " -- " in filename:
|
if " -- " in filename:
|
||||||
actors_part = filename.split(" -- ")[0]
|
actors_part = filename.split(" -- ")[0]
|
||||||
# Split by comma and clean up whitespace
|
|
||||||
actors = [actor.strip() for actor in actors_part.split(",")]
|
actors = [actor.strip() for actor in actors_part.split(",")]
|
||||||
studio: str | None = None
|
studio: str | None = None
|
||||||
if m := re.search(r" -- @(\S+)", filename):
|
if m := re.search(r" -- @(\S+)", filename):
|
||||||
studio = m.group(1)
|
studio = m.group(1)
|
||||||
return ParsedFilename(actors=actors, studio=studio)
|
return ParsedFilename(actors=actors, studio=studio)
|
||||||
|
|
||||||
logger.warning(f"Filename doesn't match expected format: {filename}")
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def list_images(folder_path: Path) -> List[Path]:
|
def list_images(folder_path: Path) -> List[Path]:
|
||||||
"""List all image files in the folder."""
|
|
||||||
image_extensions = {".jpg", ".jpeg", ".png", ".gif", ".bmp", ".tiff", ".webp"}
|
image_extensions = {".jpg", ".jpeg", ".png", ".gif", ".bmp", ".tiff", ".webp"}
|
||||||
images = []
|
images = []
|
||||||
|
|
||||||
@@ -263,25 +249,7 @@ def list_images(folder_path: Path) -> List[Path]:
|
|||||||
return images
|
return images
|
||||||
|
|
||||||
|
|
||||||
def group_images_by_actor(images: List[Path]) -> dict:
|
|
||||||
"""Group images by first actor name."""
|
|
||||||
actor_groups = defaultdict(list)
|
|
||||||
|
|
||||||
for image in images:
|
|
||||||
parsed = parse_image_filename(image)
|
|
||||||
if parsed:
|
|
||||||
first_actor = parsed.actors[0].lower()
|
|
||||||
actor_groups[first_actor].append(image)
|
|
||||||
logger.debug(f"Assigned {image.name} to actor: {first_actor}")
|
|
||||||
else:
|
|
||||||
logger.warning(f"Skipping image with no actors: {image.name}")
|
|
||||||
|
|
||||||
logger.info(f"Grouped images into {len(actor_groups)} actor categories")
|
|
||||||
return actor_groups
|
|
||||||
|
|
||||||
|
|
||||||
def parse_args():
|
def parse_args():
|
||||||
"""Parse command-line arguments."""
|
|
||||||
parser = argparse.ArgumentParser(description="Upload images to Immich, grouping them by actor name from filename")
|
parser = argparse.ArgumentParser(description="Upload images to Immich, grouping them by actor name from filename")
|
||||||
parser.add_argument("image_paths", type=Path, nargs="+", help="Path to the images to upload")
|
parser.add_argument("image_paths", type=Path, nargs="+", help="Path to the images to upload")
|
||||||
return parser.parse_args()
|
return parser.parse_args()
|
||||||
@@ -291,15 +259,12 @@ def main():
|
|||||||
args = parse_args()
|
args = parse_args()
|
||||||
image_paths: list[Path] = args.image_paths
|
image_paths: list[Path] = args.image_paths
|
||||||
|
|
||||||
"""Main function to process and upload images."""
|
|
||||||
if not image_paths:
|
if not image_paths:
|
||||||
logger.error("No images found to process")
|
logger.error("No images found to process")
|
||||||
return
|
return
|
||||||
|
|
||||||
logger.info(f"Found {len(image_paths)} images to process")
|
logger.info(f"Found {len(image_paths)} images to process")
|
||||||
|
|
||||||
# Create _uploaded directory
|
|
||||||
# if they're all in the same directory
|
|
||||||
if not all(image_paths[0].parent == path.parent for path in image_paths):
|
if not all(image_paths[0].parent == path.parent for path in image_paths):
|
||||||
logger.error("All images must be in the same directory")
|
logger.error("All images must be in the same directory")
|
||||||
return
|
return
|
||||||
@@ -309,15 +274,16 @@ def main():
|
|||||||
immich = ImmichClient(IMMICH_SERVER_URL, IMMICH_API_KEY)
|
immich = ImmichClient(IMMICH_SERVER_URL, IMMICH_API_KEY)
|
||||||
|
|
||||||
for image_path in image_paths:
|
for image_path in image_paths:
|
||||||
|
if not image_path.is_file():
|
||||||
|
logger.warning(f"Skipping non-file path: {image_path}")
|
||||||
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Parse filename to extract actor names
|
parsed = parse_filename(image_path)
|
||||||
parsed = parse_image_filename(image_path)
|
|
||||||
if not parsed:
|
if not parsed:
|
||||||
logger.warning(f"Skipping image with unparseable filename: {image_path.name}")
|
logger.warning(f"Skipping image with unparseable filename: {image_path.name}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Convert actor names to lowercase for albums and tags
|
|
||||||
|
|
||||||
logger.info(f"Processing {image_path.name} with actors: {', '.join(parsed.actors)}")
|
logger.info(f"Processing {image_path.name} with actors: {', '.join(parsed.actors)}")
|
||||||
|
|
||||||
actor_names = [actor.lower() for actor in parsed.actors]
|
actor_names = [actor.lower() for actor in parsed.actors]
|
||||||
@@ -334,7 +300,7 @@ def main():
|
|||||||
|
|
||||||
dest_path = uploaded_dir / image_path.name
|
dest_path = uploaded_dir / image_path.name
|
||||||
logger.info(f"Moving {image_path.name} to {dest_path}")
|
logger.info(f"Moving {image_path.name} to {dest_path}")
|
||||||
image_path.rename(dest_path) # Use rename to move the file
|
image_path.rename(dest_path)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error processing image {image_path.name}: {e}")
|
logger.error(f"Error processing image {image_path.name}: {e}")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user