Sort gallery media by capture date instead of file name
All checks were successful
Docker / build (release) Successful in 9s
All checks were successful
Docker / build (release) Successful in 9s
This commit is contained in:
parent
2f0c426dc1
commit
f7415fb5f6
11 changed files with 314 additions and 33 deletions
|
|
@ -4,7 +4,6 @@
|
|||
import logging
|
||||
import os
|
||||
import unicodedata
|
||||
from datetime import datetime
|
||||
from functools import partial
|
||||
from importlib import import_module
|
||||
from inspect import isclass
|
||||
|
|
@ -39,6 +38,19 @@ LATEST_LIMIT = getattr(settings, "PHOTOLOGUE_GALLERY_LATEST_LIMIT", None)
|
|||
# max_length setting for the ImageModel ImageField
|
||||
IMAGE_FIELD_MAX_LENGTH = getattr(settings, "PHOTOLOGUE_IMAGE_FIELD_MAX_LENGTH", 100)
|
||||
|
||||
# Where a capture date comes from, from the most to the least trustworthy. An
|
||||
# empty value means the date was never determined.
|
||||
DATE_FROM_EXIF = "exif"
|
||||
DATE_FROM_VIDEO = "video"
|
||||
DATE_FROM_FILE = "file"
|
||||
DATE_MANUAL = "manual"
|
||||
DATE_SOURCE_CHOICES = (
|
||||
(DATE_FROM_EXIF, _("EXIF metadata")),
|
||||
(DATE_FROM_VIDEO, _("video metadata")),
|
||||
(DATE_FROM_FILE, _("file date at upload")),
|
||||
(DATE_MANUAL, _("set by hand")),
|
||||
)
|
||||
|
||||
# Modify image file buffer size.
|
||||
ImageFile.MAXBLOCK = getattr(settings, "PHOTOLOGUE_MAXBLOCK", 256 * 2**10)
|
||||
|
||||
|
|
@ -265,6 +277,20 @@ class MediaModel(models.Model):
|
|||
)
|
||||
thumbnail_width = models.PositiveIntegerField(editable=False, null=True)
|
||||
thumbnail_height = models.PositiveIntegerField(editable=False, null=True)
|
||||
date_taken = models.DateTimeField(
|
||||
_("date taken"),
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text=_("Date the media was captured; read from its metadata."),
|
||||
)
|
||||
date_source = models.CharField(
|
||||
_("date source"),
|
||||
max_length=10,
|
||||
blank=True,
|
||||
editable=False,
|
||||
choices=DATE_SOURCE_CHOICES,
|
||||
help_text=_("Where the capture date comes from."),
|
||||
)
|
||||
|
||||
is_video = False
|
||||
|
||||
|
|
@ -288,9 +314,32 @@ class MediaModel(models.Model):
|
|||
def get_download_url(self):
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def date_is_approximate(self):
|
||||
"""True when the date was deduced rather than read from the capture itself."""
|
||||
return self.date_source == DATE_FROM_FILE
|
||||
|
||||
def set_capture_date(self, value, source):
|
||||
"""Record a capture date read from the file, and where it comes from."""
|
||||
self.date_taken = value
|
||||
self.date_source = source
|
||||
self._date_read_from_file = True
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.slug:
|
||||
self.slug = slugify(self.title)
|
||||
if self.pk and not getattr(self, "_date_read_from_file", False):
|
||||
# A date changed by anything but our own readers was set by a human,
|
||||
# and must survive a later re-read of the metadata.
|
||||
stored = (
|
||||
type(self)
|
||||
.objects.filter(pk=self.pk)
|
||||
.values_list("date_taken", flat=True)
|
||||
.first()
|
||||
)
|
||||
if stored != self.date_taken:
|
||||
self.date_source = DATE_MANUAL
|
||||
self._date_read_from_file = False
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
|
||||
|
|
@ -302,12 +351,6 @@ class Photo(MediaModel):
|
|||
width_field="thumbnail_width",
|
||||
height_field="thumbnail_height",
|
||||
)
|
||||
date_taken = models.DateTimeField(
|
||||
_("date taken"),
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text=_("Date image was taken; is obtained from the image EXIF data."),
|
||||
)
|
||||
view_count = models.PositiveIntegerField(_("view count"), default=0, editable=False)
|
||||
crop_from = models.CharField(
|
||||
_("crop from"),
|
||||
|
|
@ -577,21 +620,15 @@ class Photo(MediaModel):
|
|||
if (self.date_taken is None or image_has_changed) and self.image:
|
||||
# Attempt to get the date the photo was taken from the EXIF data.
|
||||
try:
|
||||
from .utils import capture_date_from_exif
|
||||
|
||||
exif_date = self.exif(self.image.file).get(
|
||||
"EXIF DateTimeOriginal", None
|
||||
)
|
||||
if exif_date is not None:
|
||||
d, t = exif_date.values.split()
|
||||
year, month, day = d.split(":")
|
||||
hour, minute, second = t.split(":")
|
||||
self.date_taken = datetime(
|
||||
int(year),
|
||||
int(month),
|
||||
int(day),
|
||||
int(hour),
|
||||
int(minute),
|
||||
int(second),
|
||||
)
|
||||
taken = capture_date_from_exif(exif_date.values)
|
||||
if taken is not None:
|
||||
self.set_capture_date(taken, DATE_FROM_EXIF)
|
||||
except Exception:
|
||||
logger.error("Failed to read EXIF DateTimeOriginal", exc_info=True)
|
||||
# If crop_from property has been changed on existing image,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue