77 lines
3.1 KiB
Python
77 lines
3.1 KiB
Python
# This file is part of photo21
|
|
# Copyright (C) 2022 Amicale des élèves de l'ENS Paris-Saclay
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
from django.core.management.base import BaseCommand
|
|
|
|
from photologue.models import DATE_FROM_EXIF, DATE_FROM_VIDEO, DATE_MANUAL, Photo, Video
|
|
from photologue.utils import capture_date_from_exif, capture_date_from_video
|
|
|
|
|
|
class Command(BaseCommand):
|
|
help = (
|
|
"Read the capture date of existing media from their metadata. Media whose "
|
|
"date was set by hand are never touched. By default only media without a "
|
|
"date, and media imported before dates were tracked, are read."
|
|
)
|
|
|
|
def add_arguments(self, parser):
|
|
parser.add_argument(
|
|
"--force",
|
|
action="store_true",
|
|
help="Re-read every media, even those with a known date source.",
|
|
)
|
|
parser.add_argument(
|
|
"--dry-run",
|
|
action="store_true",
|
|
help="Report what would change without writing anything.",
|
|
)
|
|
|
|
def handle(self, *args, **options):
|
|
for model, reader in ((Photo, self.photo_date), (Video, self.video_date)):
|
|
queryset = model.objects.exclude(date_source=DATE_MANUAL)
|
|
if not options["force"]:
|
|
# An empty source means the media predates this field, and its date
|
|
# may have been stored in the wrong time zone.
|
|
queryset = queryset.filter(date_taken__isnull=True) | queryset.filter(date_source="")
|
|
self.process(model, queryset.distinct(), reader, options)
|
|
|
|
def process(self, model, queryset, reader, options):
|
|
name = model._meta.verbose_name_plural
|
|
found = unchanged = missing = 0
|
|
for media in queryset.iterator():
|
|
taken, source = reader(media)
|
|
if taken is None:
|
|
missing += 1
|
|
self.stdout.write(f" no date in {media.title}")
|
|
continue
|
|
if taken == media.date_taken and source == media.date_source:
|
|
unchanged += 1
|
|
continue
|
|
found += 1
|
|
self.stdout.write(f" {media.title}: {media.date_taken} -> {taken} ({source})")
|
|
if not options["dry_run"]:
|
|
media.set_capture_date(taken, source)
|
|
media.save(update_fields=["date_taken", "date_source"])
|
|
done = "would be updated" if options["dry_run"] else "updated"
|
|
self.stdout.write(
|
|
self.style.SUCCESS(f"{name}: {found} {done}, {unchanged} already correct, {missing} without metadata")
|
|
)
|
|
|
|
@staticmethod
|
|
def photo_date(photo):
|
|
if not photo.image:
|
|
return None, None
|
|
try:
|
|
exif_date = photo.exif(photo.image.file).get("EXIF DateTimeOriginal", None)
|
|
except Exception:
|
|
return None, None
|
|
if exif_date is None:
|
|
return None, None
|
|
return capture_date_from_exif(exif_date.values), DATE_FROM_EXIF
|
|
|
|
@staticmethod
|
|
def video_date(video):
|
|
if not video.file:
|
|
return None, None
|
|
return capture_date_from_video(video.file.path), DATE_FROM_VIDEO
|