Add Immich JSON sidecar generator for metadata integration
This commit is contained in:
parent
6bff87c98c
commit
e110967094
1 changed files with 150 additions and 0 deletions
150
src/sidecar.py
Normal file
150
src/sidecar.py
Normal file
|
|
@ -0,0 +1,150 @@
|
||||||
|
"""Sidecar file generator for Immich integration."""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
logger = logging.getLogger("reddit_collector")
|
||||||
|
|
||||||
|
|
||||||
|
def write_immich_sidecar(
|
||||||
|
filepath: str,
|
||||||
|
subreddit: str,
|
||||||
|
author: str,
|
||||||
|
title: str,
|
||||||
|
score: int,
|
||||||
|
created_utc: float,
|
||||||
|
media_type: str,
|
||||||
|
permalink: Optional[str] = None,
|
||||||
|
flair: Optional[str] = None,
|
||||||
|
source_type: Optional[str] = None,
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
Write a JSON sidecar file for Immich.
|
||||||
|
|
||||||
|
Immich reads .json sidecar files with the same base name as the media file.
|
||||||
|
This enables proper date sorting, album creation, and metadata display.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filepath: Path to the media file
|
||||||
|
subreddit: Source subreddit name
|
||||||
|
author: Post author username
|
||||||
|
title: Post title
|
||||||
|
score: Reddit score/upvotes
|
||||||
|
created_utc: Unix timestamp of post creation
|
||||||
|
media_type: Type of media (image, video, gif)
|
||||||
|
permalink: Reddit permalink (optional)
|
||||||
|
flair: Post flair (optional)
|
||||||
|
source_type: 'subreddit' or 'user' (optional)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Path to the created sidecar file
|
||||||
|
"""
|
||||||
|
sidecar_path = f"{filepath}.json"
|
||||||
|
|
||||||
|
# Convert timestamp to ISO format
|
||||||
|
dt = datetime.fromtimestamp(created_utc, tz=timezone.utc)
|
||||||
|
date_iso = dt.isoformat()
|
||||||
|
|
||||||
|
# Build tags list
|
||||||
|
tags = ["reddit", subreddit, media_type]
|
||||||
|
if flair:
|
||||||
|
tags.append(flair)
|
||||||
|
if source_type:
|
||||||
|
tags.append(f"from_{source_type}")
|
||||||
|
|
||||||
|
# Calculate rating (1-5 scale based on score)
|
||||||
|
# 0-10: 1, 10-50: 2, 50-200: 3, 200-1000: 4, 1000+: 5
|
||||||
|
if score >= 1000:
|
||||||
|
rating = 5
|
||||||
|
elif score >= 200:
|
||||||
|
rating = 4
|
||||||
|
elif score >= 50:
|
||||||
|
rating = 3
|
||||||
|
elif score >= 10:
|
||||||
|
rating = 2
|
||||||
|
else:
|
||||||
|
rating = 1
|
||||||
|
|
||||||
|
# Build sidecar structure
|
||||||
|
sidecar = {
|
||||||
|
"dateTimeOriginal": date_iso,
|
||||||
|
"description": title[:500] if title else "", # Limit description length
|
||||||
|
"albums": [f"r/{subreddit}"],
|
||||||
|
"tags": tags,
|
||||||
|
"rating": rating,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add people (author) if not deleted
|
||||||
|
if author and author not in ("[deleted]", "AutoModerator"):
|
||||||
|
sidecar["people"] = [author]
|
||||||
|
|
||||||
|
# Add external URL if permalink available
|
||||||
|
if permalink:
|
||||||
|
sidecar["externalUrl"] = f"https://reddit.com{permalink}"
|
||||||
|
|
||||||
|
# Write sidecar file
|
||||||
|
with open(sidecar_path, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(sidecar, f, indent=2, ensure_ascii=False)
|
||||||
|
|
||||||
|
logger.debug(f"Created sidecar: {sidecar_path}")
|
||||||
|
return sidecar_path
|
||||||
|
|
||||||
|
|
||||||
|
def generate_filename(
|
||||||
|
subreddit: str,
|
||||||
|
author: str,
|
||||||
|
created_utc: float,
|
||||||
|
post_id: str,
|
||||||
|
ext: str,
|
||||||
|
gallery_index: Optional[int] = None,
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
Generate a descriptive filename with metadata embedded.
|
||||||
|
|
||||||
|
Format: {subreddit}_{author}_{YYYYMMDD}_{HHmmss}_{post_id}[_{index}].{ext}
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
CuteGirlsinPanties_username123_20260113_235346_1qb8nil_1.jpg
|
||||||
|
FitNakedGirls_deleted_20260113_001814_1qbbrev.mp4
|
||||||
|
|
||||||
|
Args:
|
||||||
|
subreddit: Source subreddit name
|
||||||
|
author: Post author username
|
||||||
|
created_utc: Unix timestamp of post creation
|
||||||
|
post_id: Reddit post ID
|
||||||
|
ext: File extension (including dot)
|
||||||
|
gallery_index: Index for gallery items (optional)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Generated filename
|
||||||
|
"""
|
||||||
|
# Convert timestamp to datetime
|
||||||
|
dt = datetime.fromtimestamp(created_utc, tz=timezone.utc)
|
||||||
|
date_str = dt.strftime("%Y%m%d_%H%M%S")
|
||||||
|
|
||||||
|
# Sanitize components
|
||||||
|
safe_sub = _sanitize_name(subreddit)[:30]
|
||||||
|
safe_author = _sanitize_name(author)[:20]
|
||||||
|
|
||||||
|
# Handle deleted/unknown authors
|
||||||
|
if not safe_author or safe_author in ("deleted", "AutoModerator"):
|
||||||
|
safe_author = "unknown"
|
||||||
|
|
||||||
|
# Build filename
|
||||||
|
if gallery_index is not None:
|
||||||
|
filename = f"{safe_sub}_{safe_author}_{date_str}_{post_id}_{gallery_index}{ext}"
|
||||||
|
else:
|
||||||
|
filename = f"{safe_sub}_{safe_author}_{date_str}_{post_id}{ext}"
|
||||||
|
|
||||||
|
return filename
|
||||||
|
|
||||||
|
|
||||||
|
def _sanitize_name(name: str) -> str:
|
||||||
|
"""Sanitize a name for use in filename."""
|
||||||
|
if not name:
|
||||||
|
return ""
|
||||||
|
# Replace brackets and special chars, keep alphanumeric and basic punctuation
|
||||||
|
return "".join(c if c.isalnum() or c in "-_" else "" for c in name)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue