106 lines
3.6 KiB
Python
106 lines
3.6 KiB
Python
import httpx
|
|
import os
|
|
from datetime import datetime
|
|
from collections import defaultdict
|
|
from typing import List, Dict, Any
|
|
|
|
# Get environment variables
|
|
IMMICH_API_KEY = os.environ.get("IMMICH_API_KEY")
|
|
IMMICH_SERVER_URL = os.environ.get("IMMICH_SERVER_URL")
|
|
|
|
if not IMMICH_API_KEY or not IMMICH_SERVER_URL:
|
|
raise ValueError("IMMICH_API_KEY and IMMICH_SERVER_URL environment variables must be set")
|
|
|
|
# Setup HTTP client with auth headers
|
|
headers = {"Content-Type": "application/json", "Accept": "application/json", "x-api-key": IMMICH_API_KEY}
|
|
|
|
# Global HTTP client
|
|
client = httpx.Client(headers=headers)
|
|
|
|
|
|
def get_albums() -> List[Dict[str, Any]]:
|
|
"""Fetch all albums from Immich API"""
|
|
response = client.get(f"{IMMICH_SERVER_URL}/api/albums")
|
|
response.raise_for_status()
|
|
return response.json()
|
|
|
|
|
|
def get_album_assets(album_id: str) -> List[str]:
|
|
"""Fetch all asset IDs from a specific album"""
|
|
response = client.get(f"{IMMICH_SERVER_URL}/api/albums/{album_id}?withoutAssets=false")
|
|
response.raise_for_status()
|
|
album_data = response.json()
|
|
return [asset["id"] for asset in album_data.get("assets", [])]
|
|
|
|
|
|
def add_assets_to_album(album_id: str, asset_ids: List[str]) -> None:
|
|
"""Add assets to an album"""
|
|
if not asset_ids:
|
|
return
|
|
|
|
response = client.put(f"{IMMICH_SERVER_URL}/api/albums/{album_id}/assets", json={"ids": asset_ids})
|
|
response.raise_for_status()
|
|
|
|
|
|
def delete_album(album_id: str) -> None:
|
|
"""Delete an album"""
|
|
response = client.delete(f"{IMMICH_SERVER_URL}/api/albums/{album_id}")
|
|
response.raise_for_status()
|
|
|
|
|
|
def deduplicate_albums():
|
|
"""Main function to deduplicate albums"""
|
|
print("Fetching albums...")
|
|
albums = get_albums()
|
|
|
|
# Group albums by name (case-insensitive)
|
|
album_groups = defaultdict(list)
|
|
for album in albums:
|
|
album_name = album["albumName"].lower().strip()
|
|
album_groups[album_name].append(album)
|
|
|
|
# Process groups with duplicates
|
|
for album_name, album_list in album_groups.items():
|
|
if len(album_list) <= 1:
|
|
continue # Skip groups with only one album
|
|
|
|
print(f"\nProcessing duplicate albums for: '{album_name}' ({len(album_list)} albums)")
|
|
|
|
# Sort by creation date to find the oldest
|
|
album_list.sort(key=lambda x: datetime.fromisoformat(x["createdAt"].replace("Z", "+00:00")))
|
|
oldest_album = album_list[0]
|
|
duplicate_albums = album_list[1:]
|
|
|
|
print(f" Oldest album: {oldest_album['id']} (created: {oldest_album['createdAt']})")
|
|
|
|
# Collect assets from duplicate albums
|
|
all_asset_ids = []
|
|
for dup_album in duplicate_albums:
|
|
print(f" Processing duplicate: {dup_album['id']} (created: {dup_album['createdAt']})")
|
|
asset_ids = get_album_assets(dup_album["id"])
|
|
all_asset_ids.extend(asset_ids)
|
|
print(f" Found {len(asset_ids)} assets")
|
|
|
|
# Add assets to oldest album
|
|
if all_asset_ids:
|
|
print(f" Adding {len(all_asset_ids)} assets to oldest album...")
|
|
add_assets_to_album(oldest_album["id"], all_asset_ids)
|
|
|
|
# Delete duplicate albums
|
|
for dup_album in duplicate_albums:
|
|
print(f" Deleting duplicate album: {dup_album['id']}")
|
|
delete_album(dup_album["id"])
|
|
|
|
print(f" ✓ Merged {len(duplicate_albums)} duplicate albums into {oldest_album['id']}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
deduplicate_albums()
|
|
print("\n✓ Album deduplication completed successfully!")
|
|
except Exception as e:
|
|
print(f"\n✗ Error: {e}")
|
|
raise
|
|
finally:
|
|
client.close()
|