2016-05-31 02:27:59 +00:00
|
|
|
# Copyright 2016 Hardcoded Software (http://www.hardcoded.net)
|
2014-10-13 19:08:59 +00:00
|
|
|
#
|
2015-01-03 21:33:16 +00:00
|
|
|
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
2014-10-13 19:08:59 +00:00
|
|
|
# which should be included with this package. The terms are also available at
|
2015-01-03 21:33:16 +00:00
|
|
|
# http://www.gnu.org/licenses/gpl-3.0.html
|
2011-05-29 14:18:03 +00:00
|
|
|
|
2012-08-10 20:34:27 +00:00
|
|
|
import logging
|
2013-07-14 21:43:58 +00:00
|
|
|
from hscommon.util import get_file_ext, format_size
|
|
|
|
|
2016-06-01 01:43:24 +00:00
|
|
|
from core.util import format_timestamp, format_perc, format_dupe_count
|
2011-05-29 14:18:03 +00:00
|
|
|
from core import fs
|
2022-05-09 06:40:08 +00:00
|
|
|
from core.pe import exif
|
2011-05-29 14:18:03 +00:00
|
|
|
|
2016-05-31 02:27:59 +00:00
|
|
|
# This global value is set by the platform-specific subclasser of the Photo base class
|
|
|
|
PLAT_SPECIFIC_PHOTO_CLASS = None
|
|
|
|
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2013-07-14 21:43:58 +00:00
|
|
|
def format_dimensions(dimensions):
|
2020-01-01 02:16:27 +00:00
|
|
|
return "%d x %d" % (dimensions[0], dimensions[1])
|
|
|
|
|
2013-07-14 21:43:58 +00:00
|
|
|
|
|
|
|
def get_delta_dimensions(value, ref_value):
|
2020-01-01 02:16:27 +00:00
|
|
|
return (value[0] - ref_value[0], value[1] - ref_value[1])
|
2013-07-14 21:43:58 +00:00
|
|
|
|
|
|
|
|
2011-05-29 14:18:03 +00:00
|
|
|
class Photo(fs.File):
|
|
|
|
INITIAL_INFO = fs.File.INITIAL_INFO.copy()
|
2020-01-01 02:16:27 +00:00
|
|
|
INITIAL_INFO.update({"dimensions": (0, 0), "exif_timestamp": ""})
|
2012-05-29 21:39:54 +00:00
|
|
|
__slots__ = fs.File.__slots__ + tuple(INITIAL_INFO.keys())
|
2014-10-13 19:08:59 +00:00
|
|
|
|
2011-05-29 14:18:03 +00:00
|
|
|
# These extensions are supported on all platforms
|
2022-11-24 20:53:27 +00:00
|
|
|
HANDLED_EXTS = {"png", "jpg", "jpeg", "gif", "bmp", "tiff", "tif", "webp"}
|
2014-10-13 19:08:59 +00:00
|
|
|
|
2011-05-31 14:05:12 +00:00
|
|
|
def _plat_get_dimensions(self):
|
|
|
|
raise NotImplementedError()
|
2014-10-13 19:08:59 +00:00
|
|
|
|
2011-05-31 14:05:12 +00:00
|
|
|
def _plat_get_blocks(self, block_count_per_side, orientation):
|
|
|
|
raise NotImplementedError()
|
2014-10-13 19:08:59 +00:00
|
|
|
|
2011-05-31 14:05:12 +00:00
|
|
|
def _get_orientation(self):
|
2020-01-01 02:16:27 +00:00
|
|
|
if not hasattr(self, "_cached_orientation"):
|
2011-05-31 14:05:12 +00:00
|
|
|
try:
|
2020-01-01 02:16:27 +00:00
|
|
|
with self.path.open("rb") as fp:
|
2011-05-31 14:05:12 +00:00
|
|
|
exifdata = exif.get_fields(fp)
|
|
|
|
# the value is a list (probably one-sized) of ints
|
2020-01-01 02:16:27 +00:00
|
|
|
orientations = exifdata["Orientation"]
|
2011-05-31 14:05:12 +00:00
|
|
|
self._cached_orientation = orientations[0]
|
2020-01-01 02:16:27 +00:00
|
|
|
except Exception: # Couldn't read EXIF data, no transforms
|
2011-05-31 14:05:12 +00:00
|
|
|
self._cached_orientation = 0
|
|
|
|
return self._cached_orientation
|
2014-10-13 19:08:59 +00:00
|
|
|
|
2013-11-10 17:00:16 +00:00
|
|
|
def _get_exif_timestamp(self):
|
|
|
|
try:
|
2020-01-01 02:16:27 +00:00
|
|
|
with self.path.open("rb") as fp:
|
2013-11-10 17:00:16 +00:00
|
|
|
exifdata = exif.get_fields(fp)
|
2020-01-01 02:16:27 +00:00
|
|
|
return exifdata["DateTimeOriginal"]
|
2013-11-10 17:00:16 +00:00
|
|
|
except Exception:
|
|
|
|
logging.info("Couldn't read EXIF of picture: %s", self.path)
|
2020-01-01 02:16:27 +00:00
|
|
|
return ""
|
2014-10-13 19:08:59 +00:00
|
|
|
|
2011-05-29 14:18:03 +00:00
|
|
|
@classmethod
|
|
|
|
def can_handle(cls, path):
|
2013-11-16 17:06:16 +00:00
|
|
|
return fs.File.can_handle(path) and get_file_ext(path.name) in cls.HANDLED_EXTS
|
2014-10-13 19:08:59 +00:00
|
|
|
|
2013-07-14 21:43:58 +00:00
|
|
|
def get_display_info(self, group, delta):
|
|
|
|
size = self.size
|
|
|
|
mtime = self.mtime
|
|
|
|
dimensions = self.dimensions
|
|
|
|
m = group.get_match_of(self)
|
|
|
|
if m:
|
|
|
|
percentage = m.percentage
|
|
|
|
dupe_count = 0
|
|
|
|
if delta:
|
|
|
|
r = group.ref
|
|
|
|
size -= r.size
|
|
|
|
mtime -= r.mtime
|
|
|
|
dimensions = get_delta_dimensions(dimensions, r.dimensions)
|
|
|
|
else:
|
|
|
|
percentage = group.percentage
|
|
|
|
dupe_count = len(group.dupes)
|
2020-01-01 02:16:27 +00:00
|
|
|
dupe_folder_path = getattr(self, "display_folder_path", self.folder_path)
|
2013-07-14 21:43:58 +00:00
|
|
|
return {
|
2020-01-01 02:16:27 +00:00
|
|
|
"name": self.name,
|
|
|
|
"folder_path": str(dupe_folder_path),
|
|
|
|
"size": format_size(size, 0, 1, False),
|
|
|
|
"extension": self.extension,
|
|
|
|
"dimensions": format_dimensions(dimensions),
|
|
|
|
"exif_timestamp": self.exif_timestamp,
|
|
|
|
"mtime": format_timestamp(mtime, delta and m),
|
|
|
|
"percentage": format_perc(percentage),
|
|
|
|
"dupe_count": format_dupe_count(dupe_count),
|
2013-07-14 21:43:58 +00:00
|
|
|
}
|
2014-10-13 19:08:59 +00:00
|
|
|
|
2011-05-31 14:05:12 +00:00
|
|
|
def _read_info(self, field):
|
|
|
|
fs.File._read_info(self, field)
|
2020-01-01 02:16:27 +00:00
|
|
|
if field == "dimensions":
|
2011-05-31 14:05:12 +00:00
|
|
|
self.dimensions = self._plat_get_dimensions()
|
|
|
|
if self._get_orientation() in {5, 6, 7, 8}:
|
|
|
|
self.dimensions = (self.dimensions[1], self.dimensions[0])
|
2020-01-01 02:16:27 +00:00
|
|
|
elif field == "exif_timestamp":
|
2013-11-10 17:00:16 +00:00
|
|
|
self.exif_timestamp = self._get_exif_timestamp()
|
2014-10-13 19:08:59 +00:00
|
|
|
|
2023-05-14 01:03:13 +00:00
|
|
|
def get_blocks(self, block_count_per_side, orientation: int = None):
|
|
|
|
if orientation is None:
|
|
|
|
return self._plat_get_blocks(block_count_per_side, self._get_orientation())
|
|
|
|
else:
|
|
|
|
return self._plat_get_blocks(block_count_per_side, orientation)
|