Merge pull request #1263 from brucejackson/Imagemetadata-grampet
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 289 KiB |
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 607 KiB |
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 408 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 145 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 645 KiB |
22
example/gramps/image_credits.md
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# Credits for Images used in Gramps Example database #
|
||||||
|
|
||||||
|
# O0.jpg ##
|
||||||
|
"Riga Old Man" by liber is marked with CC BY-SA 2.0. To view the terms, visit https://creativecommons.org/licenses/by-sa/2.0/?ref=openverse
|
||||||
|
- https://www.flickr.com/photos/51035655291@N01/200858281
|
||||||
|
|
||||||
|
## O2.jpg ##
|
||||||
|
"Brothers by Robert Boning (c.1870)" by pellethepoet is marked with CC BY 2.0. To view the terms, visit https://creativecommons.org
|
||||||
|
- https://www.flickr.com/photos/47201412@N02/12675131334
|
||||||
|
|
||||||
|
## O3.jpg ##
|
||||||
|
"Portrait of a Canon-man" by mescon is marked with CC BY 2.0. To view the terms, visit https://creativecommons.org/licenses/by/2.0/?ref=openverse
|
||||||
|
- https://www.flickr.com/photos/23666014@N08/3790571906
|
||||||
|
|
||||||
|
## 04.jpg ##
|
||||||
|
"Woman Photographer" by pedrosimoes7 is marked with CC BY 2.0. To view the terms, visit https://creativecommons.org/licenses/by/2.0/?ref=openverse
|
||||||
|
- https://www.flickr.com/photos/46944516@N00/6872425924
|
||||||
|
|
||||||
|
## O5.jpg ##
|
||||||
|
"Happy couple" by pedrosimoes7 is marked with CC BY 2.0. To view the terms, visit https://creativecommons.org/licenses/by/2.0/?ref=openverse
|
||||||
|
- https://www.flickr.com/photos/46944516@N00/4198095383
|
||||||
|
|
@ -2,8 +2,9 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Gramps - a GTK+/GNOME based genealogy program
|
# Gramps - a GTK+/GNOME based genealogy program
|
||||||
#
|
#
|
||||||
# Copyright (C) 2011 Nick Hall
|
# Copyright (C) 2011,2014 Nick Hall
|
||||||
# Copyright (C) 2011 Rob G. Healey <robhealey1@gmail.com>
|
# Copyright (C) 2011 Rob G. Healey <robhealey1@gmail.com>
|
||||||
|
# Copyright (C) 2022 Bruce Jackson
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
@ -25,6 +26,10 @@
|
|||||||
#
|
#
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
import os
|
import os
|
||||||
|
import logging
|
||||||
|
|
||||||
|
_LOG = logging.getLogger(".libmetadata")
|
||||||
|
|
||||||
|
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
#
|
#
|
||||||
@ -35,6 +40,10 @@ from gi.repository import Gtk
|
|||||||
import gi
|
import gi
|
||||||
gi.require_version('GExiv2', '0.10')
|
gi.require_version('GExiv2', '0.10')
|
||||||
from gi.repository import GExiv2
|
from gi.repository import GExiv2
|
||||||
|
from gi.repository import Gdk
|
||||||
|
from gi.repository import GdkPixbuf
|
||||||
|
from gi.repository import GObject
|
||||||
|
|
||||||
|
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
#
|
#
|
||||||
@ -42,7 +51,7 @@ from gi.repository import GExiv2
|
|||||||
#
|
#
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
|
|
||||||
from gramps.gui.listmodel import ListModel
|
from gramps.gui.listmodel import ListModel, NOSORT, IMAGE as COL_IMAGE
|
||||||
from gramps.gen.const import GRAMPS_LOCALE as glocale
|
from gramps.gen.const import GRAMPS_LOCALE as glocale
|
||||||
_ = glocale.translation.gettext
|
_ = glocale.translation.gettext
|
||||||
from gramps.gen.utils.place import conv_lat_lon
|
from gramps.gen.utils.place import conv_lat_lon
|
||||||
@ -51,21 +60,25 @@ from gramps.gen.lib import Date
|
|||||||
from gramps.gen.datehandler import displayer
|
from gramps.gen.datehandler import displayer
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
THUMBNAIL_IMAGE_SIZE = (50, 50)
|
||||||
|
|
||||||
def format_datetime(datestring):
|
def format_datetime(datestring):
|
||||||
"""
|
"""
|
||||||
Convert an exif timestamp into a string for display, using the
|
Convert an exif timestamp into a string for display, using the
|
||||||
standard Gramps date format.
|
standard Gramps date format. Function not used for XMP Date Metatags:
|
||||||
|
https://www.iptc.org/std/photometadata/specification/IPTC-PhotoMetadata#date-value-type
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
timestamp = datetime.strptime(datestring, '%Y:%m:%d %H:%M:%S')
|
timestamp = datetime.strptime(datestring, '%Y:%m:%d %H:%M:%S')
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return _('Invalid format')
|
return _('Invalid format')
|
||||||
|
|
||||||
date_part = Date()
|
date_part = Date()
|
||||||
date_part.set_yr_mon_day(timestamp.year, timestamp.month, timestamp.day)
|
date_part.set_yr_mon_day(timestamp.year, timestamp.month, timestamp.day)
|
||||||
date_str = displayer.display(date_part)
|
date_str = displayer.display(date_part)
|
||||||
time_str = _('%(hr)02d:%(min)02d:%(sec)02d') % {'hr': timestamp.hour,
|
time_str = _('%(hr)02d:%(min)02d:%(sec)02d') % {'hr': timestamp.hour,
|
||||||
'min': timestamp.minute,
|
'min': timestamp.minute,
|
||||||
'sec': timestamp.second}
|
'sec': timestamp.second}
|
||||||
return _('%(date)s %(time)s') % {'date': date_str, 'time': time_str}
|
return _('%(date)s %(time)s') % {'date': date_str, 'time': time_str}
|
||||||
|
|
||||||
def format_gps(raw_dms, nsew):
|
def format_gps(raw_dms, nsew):
|
||||||
@ -97,23 +110,61 @@ def format_gps(raw_dms, nsew):
|
|||||||
|
|
||||||
return result if result is not None else _('Invalid format')
|
return result if result is not None else _('Invalid format')
|
||||||
|
|
||||||
DESCRIPTION = _('Description')
|
|
||||||
IMAGE = _('Image')
|
|
||||||
CAMERA = _('Camera')
|
DESCRIPTION = _('Descriptive Tags')
|
||||||
GPS = _('GPS')
|
DATE = _('Date and Time Tags')
|
||||||
ADVANCED = _('Advanced')
|
PEOPLE = _('People Tags')
|
||||||
|
EVENT = _('Event Tags')
|
||||||
|
IMAGE = _('Image Tags')
|
||||||
|
CAMERA = _('Camera Information')
|
||||||
|
LOCATION = _('Location Tags')
|
||||||
|
ADVANCED = _('Advanced Tags')
|
||||||
|
RIGHTS = _('Rights Tags')
|
||||||
|
TAGGING = _('Keyword Tags')
|
||||||
|
|
||||||
|
"""
|
||||||
|
List of tags available to plugin can be found at the Exiv2 project
|
||||||
|
https://www.exiv2.org/metadata.html
|
||||||
|
"""
|
||||||
|
|
||||||
TAGS = [(DESCRIPTION, 'Exif.Image.ImageDescription', None, None),
|
TAGS = [(DESCRIPTION, 'Exif.Image.ImageDescription', None, None),
|
||||||
(DESCRIPTION, 'Exif.Image.Artist', None, None),
|
|
||||||
(DESCRIPTION, 'Exif.Image.Copyright', None, None),
|
|
||||||
(DESCRIPTION, 'Exif.Photo.DateTimeOriginal', None, format_datetime),
|
|
||||||
(DESCRIPTION, 'Exif.Photo.DateTimeDigitized', None, format_datetime),
|
|
||||||
(DESCRIPTION, 'Exif.Image.DateTime', None, format_datetime),
|
|
||||||
(DESCRIPTION, 'Exif.Image.TimeZoneOffset', None, None),
|
|
||||||
(DESCRIPTION, 'Exif.Image.XPSubject', None, None),
|
(DESCRIPTION, 'Exif.Image.XPSubject', None, None),
|
||||||
(DESCRIPTION, 'Exif.Image.XPComment', None, None),
|
(DESCRIPTION, 'Exif.Image.XPComment', None, None),
|
||||||
(DESCRIPTION, 'Exif.Image.XPKeywords', None, None),
|
|
||||||
(DESCRIPTION, 'Exif.Image.Rating', None, None),
|
(DESCRIPTION, 'Exif.Image.Rating', None, None),
|
||||||
|
(DESCRIPTION, 'Xmp.dc.title', None, None),
|
||||||
|
(DESCRIPTION, 'Xmp.dc.description', None, None),
|
||||||
|
(DESCRIPTION, 'Xmp.dc.subject', None, None),
|
||||||
|
(DESCRIPTION, 'Xmp.acdsee.caption', None, None),
|
||||||
|
(DESCRIPTION, 'Xmp.acdsee.notes', None, None),
|
||||||
|
(DESCRIPTION, 'Iptc.Application2.Caption', None, None),
|
||||||
|
(DESCRIPTION, 'Exif.Photo.UserComment', None, None),
|
||||||
|
(DESCRIPTION, 'Xmp.iptcExt.AOTitle', None, None),
|
||||||
|
(DATE, 'Exif.Photo.DateTimeOriginal', None, format_datetime),
|
||||||
|
(DATE, 'Exif.Photo.DateTimeDigitized', None, format_datetime),
|
||||||
|
(DATE, 'Exif.Image.DateTime', None, format_datetime),
|
||||||
|
(DATE, 'Exif.Image.TimeZoneOffset', None, None),
|
||||||
|
(DATE, 'Xmp.Xmp.CreateDate', None, None),
|
||||||
|
(DATE, 'Xmp.photoshop.DateCreated', None, None),
|
||||||
|
(PEOPLE, 'Xmp.mwg-rs.Regions/mwg-rs:RegionList[1]/mwg-rs:Name', None, None),
|
||||||
|
(PEOPLE, 'Xmp.mwg-rs.Regions', None, None),
|
||||||
|
(PEOPLE, 'Xmp.iptcExt.PersonInImage', None, None),
|
||||||
|
(EVENT, 'Xmp.iptcExt.Event', None, None),
|
||||||
|
(LOCATION, 'Xmp.iptcExt.LocationShown', None, None),
|
||||||
|
(LOCATION, 'Exif.GPSInfo.GPSLatitude', 'Exif.GPSInfo.GPSLatitudeRef', format_gps),
|
||||||
|
(LOCATION, 'Exif.GPSInfo.GPSLongitude', 'Exif.GPSInfo.GPSLongitudeRef', format_gps),
|
||||||
|
(LOCATION, 'Exif.GPSInfo.GPSAltitude', 'Exif.GPSInfo.GPSAltitudeRef', None),
|
||||||
|
(LOCATION, 'Exif.GPSInfo.GPSTimeStamp', None, None),
|
||||||
|
(LOCATION, 'Exif.GPSInfo.GPSSatellites', None, None),
|
||||||
|
(TAGGING, 'Exif.Image.XPKeywords', None, None),
|
||||||
|
(TAGGING, 'Iptc.Application2.Keywords', None, None),
|
||||||
|
(TAGGING, 'Xmp.mwg-kw.Hierarchy', None, None),
|
||||||
|
(TAGGING, 'Xmp.mwg-kw.Keywords', None, None),
|
||||||
|
(TAGGING, 'Xmp.digiKam.TagsList', None, None),
|
||||||
|
(TAGGING, 'Xmp.MicrosoftPhoto.LastKeywordXMP', None, None),
|
||||||
|
(TAGGING, 'Xmp.MicrosoftPhoto.LastKeywordIPTC', None, None),
|
||||||
|
(TAGGING, 'Xmp.lr.hierarchicalSubject', None, None),
|
||||||
|
(TAGGING, 'Xmp.acdsee.categories', None, None),
|
||||||
(IMAGE, 'Exif.Image.DocumentName', None, None),
|
(IMAGE, 'Exif.Image.DocumentName', None, None),
|
||||||
(IMAGE, 'Exif.Photo.PixelXDimension', None, None),
|
(IMAGE, 'Exif.Photo.PixelXDimension', None, None),
|
||||||
(IMAGE, 'Exif.Photo.PixelYDimension', None, None),
|
(IMAGE, 'Exif.Photo.PixelYDimension', None, None),
|
||||||
@ -126,6 +177,11 @@ TAGS = [(DESCRIPTION, 'Exif.Image.ImageDescription', None, None),
|
|||||||
(IMAGE, 'Exif.Image.Compression', None, None),
|
(IMAGE, 'Exif.Image.Compression', None, None),
|
||||||
(IMAGE, 'Exif.Photo.CompressedBitsPerPixel', None, None),
|
(IMAGE, 'Exif.Photo.CompressedBitsPerPixel', None, None),
|
||||||
(IMAGE, 'Exif.Image.PhotometricInterpretation', None, None),
|
(IMAGE, 'Exif.Image.PhotometricInterpretation', None, None),
|
||||||
|
(RIGHTS, 'Exif.Image.Copyright', None, None),
|
||||||
|
(RIGHTS, 'Exif.Image.Artist', None, None),
|
||||||
|
(RIGHTS, 'Xmp.xmpRights.Owner', None, None),
|
||||||
|
(RIGHTS, 'Xmp.xmpRights.UsageTerms', None, None),
|
||||||
|
(RIGHTS, 'Xmp.xmpRights.WebStatement', None, None),
|
||||||
(CAMERA, 'Exif.Image.Make', None, None),
|
(CAMERA, 'Exif.Image.Make', None, None),
|
||||||
(CAMERA, 'Exif.Image.Model', None, None),
|
(CAMERA, 'Exif.Image.Model', None, None),
|
||||||
(CAMERA, 'Exif.Photo.FNumber', None, None),
|
(CAMERA, 'Exif.Photo.FNumber', None, None),
|
||||||
@ -147,14 +203,6 @@ TAGS = [(DESCRIPTION, 'Exif.Image.ImageDescription', None, None),
|
|||||||
(CAMERA, 'Exif.Photo.Sharpness', None, None),
|
(CAMERA, 'Exif.Photo.Sharpness', None, None),
|
||||||
(CAMERA, 'Exif.Photo.WhiteBalance', None, None),
|
(CAMERA, 'Exif.Photo.WhiteBalance', None, None),
|
||||||
(CAMERA, 'Exif.Photo.DigitalZoomRatio', None, None),
|
(CAMERA, 'Exif.Photo.DigitalZoomRatio', None, None),
|
||||||
(GPS, 'Exif.GPSInfo.GPSLatitude',
|
|
||||||
'Exif.GPSInfo.GPSLatitudeRef', format_gps),
|
|
||||||
(GPS, 'Exif.GPSInfo.GPSLongitude',
|
|
||||||
'Exif.GPSInfo.GPSLongitudeRef', format_gps),
|
|
||||||
(GPS, 'Exif.GPSInfo.GPSAltitude',
|
|
||||||
'Exif.GPSInfo.GPSAltitudeRef', None),
|
|
||||||
(GPS, 'Exif.GPSInfo.GPSTimeStamp', None, None),
|
|
||||||
(GPS, 'Exif.GPSInfo.GPSSatellites', None, None),
|
|
||||||
(ADVANCED, 'Exif.Image.Software', None, None),
|
(ADVANCED, 'Exif.Image.Software', None, None),
|
||||||
(ADVANCED, 'Exif.Photo.ImageUniqueID', None, None),
|
(ADVANCED, 'Exif.Photo.ImageUniqueID', None, None),
|
||||||
(ADVANCED, 'Exif.Image.CameraSerialNumber', None, None),
|
(ADVANCED, 'Exif.Image.CameraSerialNumber', None, None),
|
||||||
@ -169,51 +217,66 @@ class MetadataView(Gtk.TreeView):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
Gtk.TreeView.__init__(self)
|
Gtk.TreeView.__init__(self)
|
||||||
self.sections = {}
|
self.sections = {}
|
||||||
titles = [(_('Key'), 1, 235),
|
titles = [(_('Namespace'), 0, 150),
|
||||||
(_('Value'), 2, 325)]
|
(_('Label'), 1, 150),
|
||||||
|
(_(' '), NOSORT, 60, COL_IMAGE),
|
||||||
|
(_('Value'), NOSORT, 325)]
|
||||||
|
|
||||||
self.model = ListModel(self, titles, list_mode="tree")
|
self.model = ListModel(self, titles, list_mode="tree")
|
||||||
|
|
||||||
def display_exif_tags(self, full_path):
|
|
||||||
|
def display_exif_tags(self, image_path):
|
||||||
"""
|
"""
|
||||||
Display the exif tags.
|
Display the exif tags.
|
||||||
"""
|
"""
|
||||||
self.sections = {}
|
self.sections = {}
|
||||||
|
|
||||||
|
# set fixed_height_mode to FALSE so thumbnails are not truncated.
|
||||||
|
self.model.tree.set_fixed_height_mode(False)
|
||||||
self.model.clear()
|
self.model.clear()
|
||||||
|
|
||||||
if not os.path.exists(full_path):
|
if not os.path.exists(image_path):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
retval = False
|
retval = False
|
||||||
with open(full_path, 'rb') as fd:
|
with open(image_path, 'rb') as fd:
|
||||||
try:
|
try:
|
||||||
buf = fd.read()
|
buf = fd.read()
|
||||||
metadata = GExiv2.Metadata()
|
metadata = GExiv2.Metadata()
|
||||||
metadata.open_buf(buf)
|
metadata.open_buf(buf)
|
||||||
|
self.pixbuf = GdkPixbuf.Pixbuf.new_from_file(image_path)
|
||||||
get_human = metadata.get_tag_interpreted_string
|
get_human = metadata.get_tag_interpreted_string
|
||||||
|
|
||||||
for section, key, key2, func in TAGS:
|
for section, key, key2, func in TAGS:
|
||||||
if not key in metadata.get_exif_tags():
|
if not key in self.__get_all_tags(metadata):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if func is not None:
|
if func is not None:
|
||||||
if key2 is None:
|
if key2 is None:
|
||||||
human_value = func(metadata[key])
|
human_value = func(metadata[key])
|
||||||
else:
|
else:
|
||||||
if key2 in metadata.get_exif_tags():
|
if key2 in self.__get_all_tags(metadata):
|
||||||
human_value = func(metadata[key], metadata[key2])
|
human_value = func(metadata[key], metadata[key2])
|
||||||
else:
|
else:
|
||||||
human_value = func(metadata[key], None)
|
human_value = func(metadata[key], None)
|
||||||
else:
|
else:
|
||||||
human_value = get_human(key)
|
human_value = get_human(key)
|
||||||
if key2 in metadata.get_exif_tags():
|
if key2 in self.__get_all_tags(metadata):
|
||||||
human_value += ' ' + get_human(key2)
|
human_value += ' ' + get_human(key2)
|
||||||
|
|
||||||
label = metadata.get_tag_label(key)
|
|
||||||
node = self.__add_section(section)
|
|
||||||
if human_value is None:
|
if human_value is None:
|
||||||
human_value = ''
|
human_value = ''
|
||||||
self.model.add((label, human_value), node=node)
|
|
||||||
|
# If first named region is found - find all named regions
|
||||||
|
if key == 'Xmp.mwg-rs.Regions/mwg-rs:RegionList[1]/mwg-rs:Name':
|
||||||
|
self.__get_named_regions(metadata)
|
||||||
|
continue
|
||||||
|
|
||||||
|
label = metadata.get_tag_label(key)
|
||||||
|
namespace = self.__get_tag_namespace(key)
|
||||||
|
|
||||||
|
node = self.__add_section(section)
|
||||||
|
self.model.add([namespace, label, None, human_value], node=node)
|
||||||
|
|
||||||
self.model.tree.expand_all()
|
self.model.tree.expand_all()
|
||||||
retval = self.model.count > 0
|
retval = self.model.count > 0
|
||||||
@ -227,29 +290,131 @@ class MetadataView(Gtk.TreeView):
|
|||||||
Add the section heading node to the model.
|
Add the section heading node to the model.
|
||||||
"""
|
"""
|
||||||
if section not in self.sections:
|
if section not in self.sections:
|
||||||
node = self.model.add([section, ''])
|
node = self.model.add([section, '', None, ''])
|
||||||
self.sections[section] = node
|
self.sections[section] = node
|
||||||
else:
|
else:
|
||||||
node = self.sections[section]
|
node = self.sections[section]
|
||||||
return node
|
return node
|
||||||
|
|
||||||
def get_has_data(self, full_path):
|
def get_has_data(self, image_path):
|
||||||
"""
|
"""
|
||||||
Return True if the gramplet has data, else return False.
|
Return True if the gramplet has data, else return False.
|
||||||
"""
|
"""
|
||||||
if not os.path.exists(full_path):
|
if not os.path.exists(image_path):
|
||||||
return False
|
return False
|
||||||
with open(full_path, 'rb') as fd:
|
with open(image_path, 'rb') as fd:
|
||||||
retval = False
|
retval = False
|
||||||
try:
|
try:
|
||||||
buf = fd.read()
|
buf = fd.read()
|
||||||
metadata = GExiv2.Metadata()
|
metadata = GExiv2.Metadata()
|
||||||
metadata.open_buf(buf)
|
metadata.open_buf(buf)
|
||||||
for tag in TAGS:
|
for tag in TAGS:
|
||||||
if tag in metadata.get_exif_tags():
|
if tag in self.__get_all_tags(metadata):
|
||||||
retval = True
|
retval = True
|
||||||
break
|
break
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return retval
|
return retval
|
||||||
|
|
||||||
|
def __get_all_tags(self, metadata):
|
||||||
|
"""
|
||||||
|
Return a list of all XMP, IPTC and EXIF tags in the media file
|
||||||
|
"""
|
||||||
|
tag_list = metadata.get_exif_tags() + metadata.get_xmp_tags() + metadata.get_iptc_tags()
|
||||||
|
|
||||||
|
return tag_list
|
||||||
|
|
||||||
|
def __get_named_regions(self, metadata):
|
||||||
|
"""
|
||||||
|
Retrieve all XMP named regions in an image and populate the treeview row.
|
||||||
|
"""
|
||||||
|
|
||||||
|
region_tag = 'Xmp.mwg-rs.Regions/mwg-rs:RegionList[%s]/'
|
||||||
|
region_name = region_tag + 'mwg-rs:Name'
|
||||||
|
region_x = region_tag + 'mwg-rs:Area/stArea:x'
|
||||||
|
region_y = region_tag + 'mwg-rs:Area/stArea:y'
|
||||||
|
region_w = region_tag + 'mwg-rs:Area/stArea:w'
|
||||||
|
region_h = region_tag + 'mwg-rs:Area/stArea:h'
|
||||||
|
|
||||||
|
pixbuf_width = self.pixbuf.get_width()
|
||||||
|
pixbuf_height = self.pixbuf.get_height()
|
||||||
|
|
||||||
|
i = 1
|
||||||
|
while True:
|
||||||
|
name = metadata.get(region_name % i)
|
||||||
|
region_name_display = region_name % i
|
||||||
|
|
||||||
|
if name is None:
|
||||||
|
break
|
||||||
|
try:
|
||||||
|
x = float(metadata.get(region_x % i)) * pixbuf_width
|
||||||
|
y = float(metadata.get(region_y % i)) * pixbuf_height
|
||||||
|
w = float(metadata.get(region_w % i)) * pixbuf_width
|
||||||
|
h = float(metadata.get(region_h % i)) * pixbuf_height
|
||||||
|
except ValueError:
|
||||||
|
x = pixbuf_width /2
|
||||||
|
y = pixbuf_height / 2
|
||||||
|
w = pixbuf_width
|
||||||
|
h = pixbuf_height
|
||||||
|
|
||||||
|
# ensure region does not exceed bounds of image
|
||||||
|
region_p1 = x - (w / 2)
|
||||||
|
if region_p1 < 0:
|
||||||
|
region_p1 = 0
|
||||||
|
region_p2 = y - (h / 2)
|
||||||
|
if region_p2 < 0:
|
||||||
|
region_p2 = 0
|
||||||
|
region_p3 = x + (w / 2)
|
||||||
|
if region_p3 > pixbuf_width:
|
||||||
|
region_p3 = pixbuf_width
|
||||||
|
region_p4 = y + (h / 2)
|
||||||
|
if region_p4 > pixbuf_height:
|
||||||
|
region_p4 = pixbuf_height
|
||||||
|
|
||||||
|
region = (region_p1, region_p2, region_p3, region_p4)
|
||||||
|
person_thumbnail = self.__get_thumbnail(region, THUMBNAIL_IMAGE_SIZE)
|
||||||
|
|
||||||
|
label = metadata.get_tag_label(region_name % i)
|
||||||
|
namespace = self.__get_tag_namespace(region_name % i)
|
||||||
|
|
||||||
|
node = self.__add_section(PEOPLE)
|
||||||
|
self.model.add([namespace, label, person_thumbnail, name], node=node)
|
||||||
|
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
def __get_thumbnail(self, region, thumbnail_size):
|
||||||
|
"""
|
||||||
|
Returns the thumbnail of the given region.
|
||||||
|
"""
|
||||||
|
w = region[2] - region[0]
|
||||||
|
h = region[3] - region[1]
|
||||||
|
|
||||||
|
if w <= self.pixbuf.get_width() and h <= self.pixbuf.get_height() and self.pixbuf:
|
||||||
|
subpixbuf = self.pixbuf.new_subpixbuf(region[0], region[1], w, h)
|
||||||
|
size = self.__resize_keep_aspect(w, h, *thumbnail_size)
|
||||||
|
return subpixbuf.scale_simple(size[0], size[1],
|
||||||
|
GdkPixbuf.InterpType.BILINEAR)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def __resize_keep_aspect(self, orig_x, orig_y, target_x, target_y):
|
||||||
|
"""
|
||||||
|
Calculates the dimensions of the rectangle obtained from
|
||||||
|
the rectangle orig_x * orig_y by scaling to fit
|
||||||
|
target_x * target_y keeping the aspect ratio.
|
||||||
|
"""
|
||||||
|
orig_aspect = orig_x / orig_y
|
||||||
|
target_aspect = target_x / target_y
|
||||||
|
if orig_aspect > target_aspect:
|
||||||
|
return (target_x, target_x * orig_y // orig_x)
|
||||||
|
else:
|
||||||
|
return (target_y * orig_x // orig_y, target_y)
|
||||||
|
|
||||||
|
def __get_tag_namespace(self, key):
|
||||||
|
|
||||||
|
x = key.split(".")
|
||||||
|
del x[-1]
|
||||||
|
namespace = '.'.join(x)
|
||||||
|
|
||||||
|
return namespace
|
||||||
|