changes to mobile styling in modal view, complete reword of backend worker system
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
# 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}
|
||||
Reference in New Issue
Block a user