This repository has been archived on 2026-05-31. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
imagerepo/app/tasks/sidecar.py
T

182 lines
5.7 KiB
Python

# app/tasks/sidecar.py
"""
Sidecar metadata extraction and application tasks.
Handles finding and applying metadata from Gallery-DL sidecar JSON files
to imported images.
"""
import logging
from datetime import datetime
from app.celery_app import celery
from app import db
from app.models import ImageRecord, Tag
log = logging.getLogger('celery.tasks.sidecar')
@celery.task(bind=True, name='app.tasks.sidecar.apply_sidecar_metadata',
max_retries=2, default_retry_delay=30)
def apply_sidecar_metadata(self, image_id: int, sidecar_path: str):
"""
Apply metadata from a sidecar JSON file to an image record.
Extracts platform info, artist, post ID, dates, and generates
appropriate tags (source:platform, post:platform:artist:id).
Args:
image_id: ID of the ImageRecord to update
sidecar_path: Path to the sidecar JSON file
Returns:
dict with status and applied metadata info
"""
from app.utils.metadata_enrichment import (
enrich_from_sidecar, resolve_date_conflict, load_sidecar_metadata
)
record = ImageRecord.query.get(image_id)
if not record:
log.warning(f"Image {image_id} not found")
return {'error': 'Image not found'}
try:
# Load sidecar data
sidecar_data = load_sidecar_metadata(sidecar_path)
if not sidecar_data:
return {'status': 'no_metadata', 'reason': 'Failed to load sidecar'}
# Get enriched metadata
enriched = enrich_from_sidecar(sidecar_path, {'taken_at': record.taken_at})
if not enriched.get('raw_metadata'):
return {'status': 'no_metadata', 'reason': 'No metadata extracted'}
tags_added = 0
# Update date if earlier
new_date = resolve_date_conflict(record.taken_at, enriched.get('taken_at'))
if new_date != record.taken_at:
record.taken_at = new_date
log.debug(f"Updated date for {image_id}: {new_date}")
# Apply tags from metadata
for tag_info in enriched.get('tags', []):
tag_name = tag_info.get('name')
tag_kind = tag_info.get('kind')
if not tag_name:
continue
tag = Tag.query.filter_by(name=tag_name).first()
if not tag:
tag = Tag(name=tag_name, kind=tag_kind)
db.session.add(tag)
db.session.flush()
log.debug(f"Created tag: {tag_name} ({tag_kind})")
if tag not in record.tags:
record.tags.append(tag)
tags_added += 1
db.session.commit()
log.info(f"Applied sidecar metadata to {image_id}: "
f"platform={enriched.get('platform')}, tags_added={tags_added}")
return {
'status': 'applied',
'platform': enriched.get('platform'),
'artist': enriched.get('artist'),
'post_id': enriched.get('post_id'),
'tags_added': tags_added
}
except Exception as e:
db.session.rollback()
log.error(f"Sidecar application failed for {image_id}: {e}", exc_info=True)
raise self.retry(exc=e)
@celery.task(name='app.tasks.sidecar.scan_and_match_sidecars')
def scan_and_match_sidecars(batch_id: int = None, limit: int = 1000):
"""
Scan for sidecar files and match them to imported images.
Looks at recently imported images and tries to find corresponding
sidecar JSON files for metadata enrichment.
Args:
batch_id: Optional batch ID to limit scope
limit: Maximum number of images to process
Returns:
dict with count of matched sidecars
"""
from app.utils.metadata_enrichment import find_sidecar_json
log.info(f"Scanning for unmatched sidecars (batch={batch_id}, limit={limit})")
# Build query for images that might need sidecar matching
query = ImageRecord.query.filter(
ImageRecord.filepath.isnot(None)
).order_by(ImageRecord.imported_at.desc())
if limit:
query = query.limit(limit)
matched = 0
already_has_source = 0
for record in query.all():
# Check if already has source tag (indicates sidecar was processed)
has_source_tag = any(
tag.kind == 'source' or tag.kind == 'post'
for tag in record.tags
)
if has_source_tag:
already_has_source += 1
continue
# Try to find sidecar
sidecar_path = find_sidecar_json(record.filepath)
if sidecar_path:
apply_sidecar_metadata.delay(record.id, sidecar_path)
matched += 1
log.info(f"Sidecar scan complete: matched={matched}, already_tagged={already_has_source}")
return {'matched': matched, 'already_tagged': already_has_source}
@celery.task(name='app.tasks.sidecar.reprocess_sidecars_for_tag')
def reprocess_sidecars_for_tag(tag_name: str):
"""
Reprocess sidecar metadata for all images with a specific tag.
Useful for re-running sidecar extraction after updates to
the metadata extraction logic.
Args:
tag_name: Name of the tag to filter by
Returns:
dict with count of queued tasks
"""
from app.utils.metadata_enrichment import find_sidecar_json
tag = Tag.query.filter_by(name=tag_name).first()
if not tag:
return {'error': f'Tag not found: {tag_name}'}
queued = 0
for record in tag.images:
if not record.filepath:
continue
sidecar_path = find_sidecar_json(record.filepath)
if sidecar_path:
apply_sidecar_metadata.delay(record.id, sidecar_path)
queued += 1
log.info(f"Queued {queued} sidecar reprocessing tasks for tag: {tag_name}")
return {'queued': queued, 'tag': tag_name}