gramps/gramps/gen/utils/thumbnails.py
2015-08-23 18:42:07 +01:00

357 lines
13 KiB
Python

#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2000-2007 Donald N. Allingham
#
# 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.
#
"""
Handles generation and access to thumbnails used in GRAMPS.
"""
#-------------------------------------------------------------------------
#
# Standard python modules
#
#-------------------------------------------------------------------------
import os
import logging
from hashlib import md5
#-------------------------------------------------------------------------
#
# GTK/Gnome modules
#
#-------------------------------------------------------------------------
from gi.repository import GObject
from gi.repository import GdkPixbuf
try:
from gi.repository import Gtk
_icon_theme = Gtk.IconTheme.get_default()
except:
_icon_theme = None
#-------------------------------------------------------------------------
#
# gramps modules
#
#-------------------------------------------------------------------------
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
#-------------------------------------------------------------------------
#
# Constants
#
#-------------------------------------------------------------------------
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.
:param key: GCONF key
:type key: unicode
:returns: Value associated with the GCONF key
:rtype: unicode
"""
try:
val = CLIENT.get_string(key)
except GObject.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 GObject.GError:
val = None
return val
#-------------------------------------------------------------------------
#
# __build_thumb_path
#
#-------------------------------------------------------------------------
def __build_thumb_path(path, rectangle=None, size=SIZE_NORMAL):
"""
Convert the specified path into a corresponding path for the thumbnail
image. We do this by converting the original path into an MD5SUM value
(which should be unique), adding the '.png' extension, and prepending
with the GRAMPS thumbnail directory.
:type path: unicode
:param path: filename of the source file
:type rectangle: tuple
:param rectangle: subsection rectangle
:rtype: unicode
:returns: full path name to the corresponding thumbnail file.
"""
extra = ""
if rectangle is not None:
extra = "?" + str(rectangle)
prehash = path + extra
prehash = prehash.encode('utf-8')
md5_hash = md5(prehash)
if size == SIZE_LARGE:
base_dir = THUMB_LARGE
else:
base_dir = THUMB_NORMAL
return os.path.join(base_dir, md5_hash.hexdigest()+'.png')
#-------------------------------------------------------------------------
#
# __create_thumbnail_image
#
#-------------------------------------------------------------------------
def __create_thumbnail_image(src_file, mtype=None, rectangle=None,
size=SIZE_NORMAL):
"""
Generates the thumbnail image for a file. If the mime type is specified,
and is not an 'image', then we attempt to find and run a thumbnailer
utility to create a thumbnail. For images, we simply create a smaller
image, scaled to thumbnail size.
:param src_file: filename of the source file
:type src_file: unicode
:param mtype: mime type of the specified file (optional)
:type mtype: unicode
:param rectangle: subsection rectangle
:type rectangle: tuple
:rtype: bool
:returns: True is the thumbnailwas successfully generated
"""
filename = __build_thumb_path(src_file, rectangle, size)
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()
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.warn("Error scaling image down: %s", str(err))
return False
#-------------------------------------------------------------------------
#
# find_mime_type_pixbuf
#
#-------------------------------------------------------------------------
def find_mime_type_pixbuf(mime_type):
try:
icontmp = mime_type.replace('/','-')
newicon = "gnome-mime-%s" % icontmp
try:
return _icon_theme.load_icon(newicon,48,0)
except:
icontmp = mime_type.split('/')[0]
try:
newicon = "gnome-mime-%s" % icontmp
return _icon_theme.load_icon(newicon,48,0)
except:
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
#-------------------------------------------------------------------------
#
# get_thumbnail_image
#
#-------------------------------------------------------------------------
def get_thumbnail_image(src_file, mtype=None, rectangle=None, size=SIZE_NORMAL):
"""
Return the thumbnail image (in GTK Pixbuf format) associated with the
source file passed to the function. If no thumbnail could be found,
the associated icon for the mime type is returned, or if that cannot be
found, a generic document icon is returned.
The image is not generated every time, but only if the thumbnail does not
exist, or if the source file is newer than the thumbnail.
:param src_file: Source media file
:type src_file: unicode
:param mime_type: mime type of the source file
:type mime_type: unicode
:param rectangle: subsection rectangle
:type rectangle: tuple
:returns: thumbnail representing the source file
:rtype: GdkPixbuf.Pixbuf
"""
try:
filename = get_thumbnail_path(src_file, mtype, rectangle, size)
return GdkPixbuf.Pixbuf.new_from_file(filename)
except (GObject.GError, OSError):
if mtype:
return find_mime_type_pixbuf(mtype)
else:
default = os.path.join(IMAGE_DIR, "document.png")
return GdkPixbuf.Pixbuf.new_from_file(default)
#-------------------------------------------------------------------------
#
# get_thumbnail_path
#
#-------------------------------------------------------------------------
def get_thumbnail_path(src_file, mtype=None, rectangle=None, size=SIZE_NORMAL):
"""
Return the path to the thumbnail image associated with the
source file passed to the function. If the thumbnail does not exist,
or if it is older than the source file, we create a new thumbnail image.
:param src_file: Source media file
:type src_file: unicode
:param mime_type: mime type of the source file
:type mime_type: unicode
:param rectangle: subsection rectangle
:type rectangle: tuple
:returns: thumbnail representing the source file
:rtype: GdkPixbuf.Pixbuf
"""
filename = __build_thumb_path(src_file, rectangle, size)
if not os.path.isfile(src_file):
return os.path.join(IMAGE_DIR, "image-missing.png")
else:
if (not os.path.isfile(filename)) or (
os.path.getmtime(src_file) > os.path.getmtime(filename)):
if not __create_thumbnail_image(src_file, mtype, rectangle, size):
return os.path.join(IMAGE_DIR, "document.png")
return os.path.abspath(filename)