Add support for thumbnailer plugins

This commit is contained in:
Nick Hall 2022-03-09 00:06:03 +00:00
parent a45d86f0ad
commit b4536666b0
13 changed files with 429 additions and 166 deletions

View File

@ -163,11 +163,6 @@ PLUGINS_DIR = os.path.join(ROOT_DIR, "plugins")
USE_TIPS = False
if sys.platform == 'win32':
USE_THUMBNAILER = False
else:
USE_THUMBNAILER = True
#-------------------------------------------------------------------------
#
# Paths to data files.
@ -254,6 +249,8 @@ DOCUMENTERS = [
#-------------------------------------------------------------------------
THUMBSCALE = 96.0
THUMBSCALE_LARGE = 180.0
SIZE_NORMAL = 0
SIZE_LARGE = 1
XMLFILE = "data.gramps"
NO_SURNAME = "(%s)" % _("none", "surname")
NO_GIVEN = "(%s)" % _("none", "given-name")

View File

@ -41,6 +41,7 @@ from ._export import ExportPlugin
from ._docgenplugin import DocGenPlugin
from ._manager import BasePluginManager
from ._gramplet import Gramplet
from ._thumbnailer import Thumbnailer
from .utils import *
from ._options import (Options, OptionListCollection, OptionList,
OptionHandler, MenuOptions)

View File

@ -448,6 +448,11 @@ class BasePluginManager:
"""
return self.__pgr.database_plugins()
def get_reg_thumbnailers(self):
""" Return list of registered thumbnailers.
"""
return self.__pgr.thumbnailer_plugins()
def get_external_opt_dict(self):
""" Return the dictionary of external options. """
return self.__external_opt_dict

View File

@ -73,8 +73,10 @@ GRAMPLET = 10
SIDEBAR = 11
DATABASE = 12
RULE = 13
THUMBNAILER = 14
PTYPE = [REPORT, QUICKREPORT, TOOL, IMPORT, EXPORT, DOCGEN, GENERAL,
MAPSERVICE, VIEW, RELCALC, GRAMPLET, SIDEBAR, DATABASE, RULE]
MAPSERVICE, VIEW, RELCALC, GRAMPLET, SIDEBAR, DATABASE, RULE,
THUMBNAILER]
PTYPE_STR = {
REPORT: _('Report') ,
QUICKREPORT: _('Quickreport'),
@ -89,7 +91,8 @@ PTYPE_STR = {
GRAMPLET: _('Gramplet'),
SIDEBAR: _('Sidebar'),
DATABASE: _('Database'),
RULE: _('Rule')
RULE: _('Rule'),
THUMBNAILER: _('Thumbnailer'),
}
#possible report categories
@ -371,6 +374,11 @@ class PluginData:
The class (Person, Event, Media, etc.) the rule applies to.
.. attribute:: ruleclass
The exact class name of the rule; ex: HasSourceParameter
Attributes for THUMBNAILER plugins
.. attribute:: thumbnailer
The exact class name of the thumbnailer
"""
def __init__(self):
@ -452,6 +460,8 @@ class PluginData:
#RULE attr
self._ruleclass = None
self._namespace = None
#THUMBNAILER attr
self._thumbnailer = None
def _set_id(self, id):
self._id = id
@ -948,10 +958,10 @@ class PluginData:
sidebarclass = property(_get_sidebarclass, _set_sidebarclass)
menu_label = property(_get_menu_label, _set_menu_label)
#VIEW and SIDEBAR attributes
#VIEW, SIDEBAR and THUMBNAILER attributes
def _set_order(self, order):
if not self._ptype in (VIEW, SIDEBAR):
raise ValueError('order may only be set for VIEW and SIDEBAR plugins')
if not self._ptype in (VIEW, SIDEBAR, THUMBNAILER):
raise ValueError('order may only be set for VIEW/SIDEBAR/THUMBNAILER plugins')
self._order = order
def _get_order(self):
@ -1019,6 +1029,17 @@ class PluginData:
ruleclass = property(_get_ruleclass, _set_ruleclass)
namespace = property(_get_namespace, _set_namespace)
#THUMBNAILER attr
def _set_thumbnailer(self, data):
if self._ptype != THUMBNAILER:
raise ValueError('thumbnailer may only be set for THUMBNAILER plugins')
self._thumbnailer = data
def _get_thumbnailer(self):
return self._thumbnailer
thumbnailer = property(_get_thumbnailer, _set_thumbnailer)
def newplugin():
"""
Function to create a new plugindata object, add it to list of
@ -1072,6 +1093,7 @@ def make_environment(**kwargs):
'RELCALC': RELCALC,
'GRAMPLET': GRAMPLET,
'SIDEBAR': SIDEBAR,
'THUMBNAILER': THUMBNAILER,
'CATEGORY_TEXT': CATEGORY_TEXT,
'CATEGORY_DRAW': CATEGORY_DRAW,
'CATEGORY_CODE': CATEGORY_CODE,
@ -1389,6 +1411,12 @@ class PluginRegister:
"""
return self.type_plugins(RULE)
def thumbnailer_plugins(self):
"""
Return a list of :class:`PluginData` that are of type THUMBNAILER
"""
return self.type_plugins(THUMBNAILER)
def filter_load_on_reg(self):
"""
Return a list of :class:`PluginData` that have load_on_reg == True

View File

@ -0,0 +1,67 @@
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2022 Nick Hall
#
# 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
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
"""
This module provides the base class for thumbnailer plugins.
"""
#-------------------------------------------------------------------------
#
# Standard python modules
#
#-------------------------------------------------------------------------
from abc import ABCMeta, abstractmethod
#-------------------------------------------------------------------------
#
# Thumbnailer class
#
#-------------------------------------------------------------------------
class Thumbnailer(metaclass=ABCMeta):
@abstractmethod
def is_supported(self, mime_type):
"""
Indicates if a mime type is supported by this thumbnailer.
:param mime_type: mime type of the source file
:type mime_type: unicode
:rtype: bool
:returns: True if the mime type is supported by this thumbnailer
"""
@abstractmethod
def run(self, mime_type, src_file, dest_file, size, rectangle):
"""
Generates a thumbnail image for the specified file.
:param mime_type: mime type of the source file
:type mime_type: unicode
:param src_file: filename of the source file
:type src_file: unicode
:param dest_file: filename of the destination file
:type dest_file: unicode
:param size: required size of the thumbnail in pixels
:type size: int
:param rectangle: subsection rectangle
:type rectangle: tuple
:rtype: bool
:returns: True if the thumbnail was successfully generated
"""

View File

@ -2,6 +2,7 @@
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2000-2007 Donald N. Allingham
# Copyright (C) 2022 Nick Hall
#
# 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
@ -51,26 +52,9 @@ except:
#
#-------------------------------------------------------------------------
from gramps.gen.const import (ICON, IMAGE_DIR, THUMB_LARGE, THUMB_NORMAL,
THUMBSCALE, THUMBSCALE_LARGE, USE_THUMBNAILER)
from gramps.gen.constfunc import win
#-------------------------------------------------------------------------
#
# gconf - try loading gconf for GNOME based systems. If we find it, we
# might be able to generate thumbnails for non-image files.
#
#-------------------------------------------------------------------------
try:
#test first for the key to avoid an error in the importer that causes
#the error logger to activate
##TODO GTK3: Is this the best way to avoid error?
import gi.repository as repo
repo.__dict__['GConf']
from gi.repository import GConf
GCONF = True
CLIENT = GConf.Client.get_default()
except (ImportError, KeyError) as msg:
GCONF = False
SIZE_NORMAL, SIZE_LARGE)
from gramps.gen.plug import BasePluginManager, START
from gramps.gen.mime import get_type
#-------------------------------------------------------------------------
#
@ -78,50 +62,25 @@ except (ImportError, KeyError) as msg:
#
#-------------------------------------------------------------------------
LOG = logging.getLogger(".thumbnail")
SIZE_NORMAL = 0
SIZE_LARGE = 1
#-------------------------------------------------------------------------
#
# __get_gconf_string
#
#-------------------------------------------------------------------------
def __get_gconf_string(key):
"""
Attempt to retrieve a value from the GNOME gconf database based of the
passed key.
THUMBNAILERS = []
def get_thumbnailers():
if len(THUMBNAILERS):
return THUMBNAILERS
:param key: GCONF key
:type key: unicode
:returns: Value associated with the GCONF key
:rtype: unicode
"""
try:
val = CLIENT.get_string(key)
except GLib.GError:
val = None
return str(val)
#-------------------------------------------------------------------------
#
# __get_gconf_bool
#
#-------------------------------------------------------------------------
def __get_gconf_bool(key):
"""
Attempt to retrieve a value from the GNOME gconf database based of the
passed key.
:param key: GCONF key
:type key: unicode
:returns: Value associated with the GCONF key
:rtype: bool
"""
try:
val = CLIENT.get_bool(key)
except GLib.GError:
val = None
return val
plugman = BasePluginManager.get_instance()
for pdata in plugman.get_reg_thumbnailers():
module = plugman.load_plugin(pdata)
if not module:
print("Error loading thumbnailer '%s': skipping content"
% pdata.name)
continue
thumbnailer = getattr(module, pdata.thumbnailer)()
if pdata.order == START:
THUMBNAILERS.insert(0, thumbnailer)
else:
THUMBNAILERS.append(thumbnailer)
return THUMBNAILERS
#-------------------------------------------------------------------------
#
@ -176,49 +135,35 @@ def __create_thumbnail_image(src_file, mtype=None, rectangle=None,
:rtype: bool
:returns: True is the thumbnailwas successfully generated
"""
if mtype is None:
mtype = get_type(src_file)
filename = __build_thumb_path(src_file, rectangle, size)
return run_thumbnailer(mtype, src_file, filename, size, rectangle)
if mtype and not mtype.startswith('image/'):
# Not an image, so run the thumbnailer
return run_thumbnailer(mtype, src_file, filename)
else:
# build a thumbnail by scaling the image using GTK's built in
# routines.
try:
pixbuf = GdkPixbuf.Pixbuf.new_from_file(src_file)
width = pixbuf.get_width()
height = pixbuf.get_height()
def run_thumbnailer(mime_type, src_file, dest_file, size, rectangle=None):
"""
This function attempts to generate a thumbnail image. It runs the first
thumbnailer plugin that supports the given mime type.
if rectangle is not None:
upper_x = min(rectangle[0], rectangle[2])/100.
lower_x = max(rectangle[0], rectangle[2])/100.
upper_y = min(rectangle[1], rectangle[3])/100.
lower_y = max(rectangle[1], rectangle[3])/100.
sub_x = int(upper_x * width)
sub_y = int(upper_y * height)
sub_width = int((lower_x - upper_x) * width)
sub_height = int((lower_y - upper_y) * height)
if sub_width > 0 and sub_height > 0:
pixbuf = pixbuf.new_subpixbuf(sub_x, sub_y, sub_width, sub_height)
width = sub_width
height = sub_height
if size == SIZE_LARGE:
thumbscale = THUMBSCALE_LARGE
else:
thumbscale = THUMBSCALE
scale = thumbscale / (float(max(width, height)))
scaled_width = int(width * scale)
scaled_height = int(height * scale)
pixbuf = pixbuf.scale_simple(scaled_width, scaled_height,
GdkPixbuf.InterpType.BILINEAR)
pixbuf.savev(filename, "png", "", "")
return True
except Exception as err:
LOG.warning("Error scaling image down: %s", str(err))
return False
:param mime_type: mime type of the source file
:type mime_type: unicode
:param src_file: filename of the source file
:type src_file: unicode
:param dest_file: destination file for the thumbnail image
:type dest_file: unicode
:param size: option parameters specifying the desired size of the
thumbnail
:type size: int
:param rectangle: subsection rectangle (optional)
:type rectangle: tuple
:returns: True if the thumbnail was successfully generated
:rtype: bool
"""
for thumbnailer in get_thumbnailers():
if thumbnailer.is_supported(mime_type):
if thumbnailer.run(mime_type, src_file, dest_file, size, rectangle):
return True
return False
#-------------------------------------------------------------------------
#
@ -241,55 +186,6 @@ def find_mime_type_pixbuf(mime_type):
return GdkPixbuf.Pixbuf.new_from_file(ICON)
except:
return GdkPixbuf.Pixbuf.new_from_file(ICON)
#-------------------------------------------------------------------------
#
# run_thumbnailer
#
#-------------------------------------------------------------------------
def run_thumbnailer(mime_type, src_file, dest_file, size=SIZE_NORMAL):
"""
This function attempts to generate a thumbnail image for a non-image.
This includes things such as video and PDF files. This will currently
only succeed if the GNOME environment is installed, since at this point,
only the GNOME environment has the ability to generate thumbnails.
:param mime_type: mime type of the source file
:type mime_type: unicode
:param src_file: filename of the source file
:type src_file: unicode
:param dest_file: destination file for the thumbnail image
:type dest_file: unicode
:param size: option parameters specifying the desired size of the
thumbnail
:type size: int
:returns: True if the thumbnail was successfully generated
:rtype: bool
"""
# only try this if GCONF is present, the thumbnailer has not been
# disabled, and if the src_file actually exists
if GCONF and USE_THUMBNAILER and os.path.isfile(src_file):
# find the command and enable for the associated mime types by
# querying the gconf database
base = '/desktop/gnome/thumbnailers/%s' % mime_type.replace('/', '@')
cmd = __get_gconf_string(base + '/command')
enable = __get_gconf_bool(base + '/enable')
# if we found the command and it has been enabled, then spawn
# of the command to build the thumbnail
if cmd and enable:
if size == SIZE_LARGE:
thumbscale = THUMBSCALE_LARGE
else:
thumbscale = THUMBSCALE
sublist = {
'%s' : "%d" % int(thumbscale),
'%u' : src_file,
'%o' : dest_file,
}
cmdlist = [ sublist.get(x, x) for x in cmd.split() ]
return os.spawnvpe(os.P_WAIT, cmdlist[0], cmdlist, os.environ) == 0
return False
#-------------------------------------------------------------------------
#

View File

@ -54,7 +54,7 @@ from gramps.gen.config import config
from gramps.gen.utils.file import (media_path_full, media_path, relative_path,
find_file)
from gramps.gen.mime import get_type
from gramps.gen.utils.thumbnails import find_mime_type_pixbuf
from gramps.gen.utils.thumbnails import get_thumbnail_image
from ..display import display_help
from ..managedwindow import ManagedWindow
from ..dialog import ErrorDialog, WarningDialog
@ -214,10 +214,7 @@ class AddMedia(ManagedWindow):
filename = find_file( filename)
if filename:
mtype = get_type(filename)
if mtype and mtype.startswith("image"):
image = self.scale_image(filename, THUMBSCALE)
else:
image = find_mime_type_pixbuf(mtype)
image = get_thumbnail_image(filename, mtype)
self.image.set_from_pixbuf(image)
def _cleanup_on_exit(self):

View File

View File

@ -0,0 +1,105 @@
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2000-2007 Donald N. Allingham
# Copyright (C) 2022 Nick Hall
#
# 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
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
"""
A thumbnailer that uses other system thumbnailers already installed on a
Gmone desktop.
"""
#-------------------------------------------------------------------------
#
# Standard python modules
#
#-------------------------------------------------------------------------
import os
import glob
import logging
#-------------------------------------------------------------------------
#
# GTK/Gnome modules
#
#-------------------------------------------------------------------------
from gi.repository import GLib
#-------------------------------------------------------------------------
#
# Gramps modules
#
#-------------------------------------------------------------------------
from gramps.gen.const import THUMBSCALE, THUMBSCALE_LARGE, SIZE_LARGE
from gramps.gen.plug import Thumbnailer
from gramps.gen.constfunc import get_env_var, lin
#-------------------------------------------------------------------------
#
# Constants
#
#-------------------------------------------------------------------------
LOG = logging.getLogger(".thumbnail")
class GnomeThumb(Thumbnailer):
def __init__(self):
self.all_mime_types = {}
if lin():
self.__find_thumbnailers()
def __find_thumbnailers(self):
path_list = GLib.get_system_data_dirs()
path_list.append(GLib.get_user_data_dir())
file_list = []
for path in path_list:
file_list.extend(
glob.glob(os.path.join(path, "thumbnailers", "*.thumbnailer"))
)
for filename in file_list:
kf = GLib.KeyFile()
try:
kf.load_from_file(filename, GLib.KeyFileFlags.NONE)
cmd = kf.get_string("Thumbnailer Entry", "Exec")
mime_types = kf.get_string_list("Thumbnailer Entry", "MimeType")
except GLib.GError:
continue
if cmd and mime_types:
for mime_type in mime_types:
self.all_mime_types[mime_type] = cmd
def is_supported(self, mime_type):
return mime_type in self.all_mime_types
def run(self, mime_type, src_file, dest_file, size, rectangle):
cmd = self.all_mime_types.get(mime_type)
if cmd:
if size == SIZE_LARGE:
thumbscale = THUMBSCALE_LARGE
else:
thumbscale = THUMBSCALE
sublist = {
"%s": "%d" % int(thumbscale),
"%u": "file://" + src_file,
"%i": src_file,
"%o": dest_file,
}
cmdlist = [sublist.get(x, x) for x in cmd.split()]
return os.spawnvpe(os.P_WAIT, cmdlist[0], cmdlist, os.environ) == 0
return False

View File

@ -0,0 +1,101 @@
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2000-2007 Donald N. Allingham
# Copyright (C) 2022 Nick Hall
#
# 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
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
"""
A thumbnailer for images and sub-sections of images.
"""
#-------------------------------------------------------------------------
#
# Standard python modules
#
#-------------------------------------------------------------------------
import logging
#-------------------------------------------------------------------------
#
# GTK/Gnome modules
#
#-------------------------------------------------------------------------
from gi.repository import GdkPixbuf
#-------------------------------------------------------------------------
#
# gramps modules
#
#-------------------------------------------------------------------------
from gramps.gen.const import THUMBSCALE, THUMBSCALE_LARGE, SIZE_LARGE
from gramps.gen.plug import Thumbnailer
#-------------------------------------------------------------------------
#
# Constants
#
#-------------------------------------------------------------------------
LOG = logging.getLogger(".thumbnail")
class ImageThumb(Thumbnailer):
def is_supported(self, mime_type):
if mime_type:
return mime_type.startswith("image")
else:
return False
def run(self, mime_type, src_file, dest_file, size, rectangle):
# build a thumbnail by scaling the image using GTK's built in
# routines.
try:
pixbuf = GdkPixbuf.Pixbuf.new_from_file(src_file)
width = pixbuf.get_width()
height = pixbuf.get_height()
if rectangle is not None:
upper_x = min(rectangle[0], rectangle[2]) / 100.0
lower_x = max(rectangle[0], rectangle[2]) / 100.0
upper_y = min(rectangle[1], rectangle[3]) / 100.0
lower_y = max(rectangle[1], rectangle[3]) / 100.0
sub_x = int(upper_x * width)
sub_y = int(upper_y * height)
sub_width = int((lower_x - upper_x) * width)
sub_height = int((lower_y - upper_y) * height)
if sub_width > 0 and sub_height > 0:
pixbuf = pixbuf.new_subpixbuf(sub_x, sub_y, sub_width, sub_height)
width = sub_width
height = sub_height
if size == SIZE_LARGE:
thumbscale = THUMBSCALE_LARGE
else:
thumbscale = THUMBSCALE
scale = thumbscale / (float(max(width, height)))
scaled_width = int(width * scale)
scaled_height = int(height * scale)
pixbuf = pixbuf.scale_simple(
scaled_width, scaled_height, GdkPixbuf.InterpType.BILINEAR
)
pixbuf.savev(dest_file, "png", "", "")
return True
except Exception as err:
LOG.warning("Error scaling image down: %s", str(err))
return False

View File

@ -0,0 +1,59 @@
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2022 Nick Hall
#
# 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
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
from gramps.gen.const import GRAMPS_LOCALE as glocale
_ = glocale.translation.gettext
MODULE_VERSION = "5.2"
#------------------------------------------------------------------------
#
# Default thumbnailers for Gramps
#
#------------------------------------------------------------------------
register(
THUMBNAILER,
id="gnomethumb",
name=_("Gnome Thumbnailer"),
description=_("Gnome Thumbnailer"),
version="1.0",
gramps_target_version=MODULE_VERSION,
status=STABLE,
fname="gnomethumb.py",
thumbnailer="GnomeThumb",
authors=["The Gramps project"],
authors_email=["http://gramps-project.org"],
)
register(
THUMBNAILER,
id="imagethumb",
name=_("Image Thumbnailer"),
description=_("Image Thumbnailer"),
version="1.0",
gramps_target_version=MODULE_VERSION,
status=STABLE,
order=START,
fname="imagethumb.py",
thumbnailer="ImageThumb",
authors=["The Gramps project"],
authors_email=["http://gramps-project.org"],
)

View File

@ -713,6 +713,7 @@ gramps/plugins/textreport/summary.py
gramps/plugins/textreport/tableofcontents.py
gramps/plugins/textreport/tagreport.py
gramps/plugins/textreport/textplugins.gpr.py
gramps/plugins/thumbnailer/thumb.gpr.py
gramps/plugins/tool/changenames.glade
gramps/plugins/tool/changenames.py
gramps/plugins/tool/changetypes.glade

View File

@ -592,6 +592,12 @@ gramps/plugins/test/imports_test.py
gramps/plugins/test/reports_test.py
gramps/plugins/test/tools_test.py
#
# Thumbnailers
#
gramps/plugins/thumbnailer/__init__.py
gramps/plugins/thumbnailer/gnomethumb.py
gramps/plugins/thumbnailer/imagethumb.py
#
# Development tools
#
gramps/plugins/tool/__init__.py