2023-06-28 21:50:08 +01:00

673 lines
30 KiB
Python

# -*- coding: utf-8 -*-
#!/usr/bin/env python
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2000-2007 Donald N. Allingham
# Copyright (C) 2007 Johan Gonqvist <johan.gronqvist@gmail.com>
# Copyright (C) 2007-2009 Gary Burton <gary.burton@zen.co.uk>
# Copyright (C) 2007-2009 Stephane Charette <stephanecharette@gmail.com>
# Copyright (C) 2008-2009 Brian G. Matherly
# Copyright (C) 2008 Jason M. Simanek <jason@bohemianalps.com>
# Copyright (C) 2008-2011 Rob G. Healey <robhealey1@gmail.com>
# Copyright (C) 2010 Doug Blank <doug.blank@gmail.com>
# Copyright (C) 2010 Jakim Friant
# Copyright (C) 2010- Serge Noiraud
# Copyright (C) 2011 Tim G L Lyons
# Copyright (C) 2013 Benny Malengier
# Copyright (C) 2016 Allen Crider
# Copyright (C) 2018 Theo van Rijn
#
# 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.
#
"""
Narrative Web Page generator.
Classe:
MediaPage - Media index page and individual Media pages
"""
#------------------------------------------------
# python modules
#------------------------------------------------
import gc
import os
import shutil
import tempfile
from collections import defaultdict
from decimal import getcontext
import logging
#------------------------------------------------
# Gramps module
#------------------------------------------------
from gramps.gen.const import GRAMPS_LOCALE as glocale
from gramps.gen.lib import (Date, Media)
from gramps.gen.plug.report import Bibliography
from gramps.gen.utils.file import media_path_full
from gramps.gen.utils.thumbnails import run_thumbnailer
from gramps.gen.utils.image import image_size # , resize_to_jpeg_buffer
from gramps.plugins.lib.libhtml import Html
#------------------------------------------------
# specific narrative web import
#------------------------------------------------
from gramps.plugins.webreport.basepage import BasePage
from gramps.plugins.webreport.common import (FULLCLEAR, _WRONGMEDIAPATH,
html_escape)
_ = glocale.translation.sgettext
LOG = logging.getLogger(".NarrativeWeb")
getcontext().prec = 8
#################################################
#
# creates the Media List Page and Media Pages
#
#################################################
class MediaPages(BasePage):
"""
This class is responsible for displaying information about the 'Media'
database objects. It displays this information under the 'Individuals'
tab. It is told by the 'add_instances' call which 'Media's to display,
and remembers the list of persons. A single call to 'display_pages'
displays both the Individual List (Index) page and all the Individual
pages.
The base class 'BasePage' is initialised once for each page that is
displayed.
"""
def __init__(self, report):
"""
@param: report -- The instance of the main report class for this report
"""
BasePage.__init__(self, report, title="")
self.media_dict = defaultdict(set)
self.unused_media_handles = []
self.cur_fname = None
def display_pages(self, title):
"""
Generate and output the pages under the Media tab, namely the media
index and the individual media pages.
@param: title -- Is the title of the web page
"""
LOG.debug("obj_dict[Media]")
for item in self.report.obj_dict[Media].items():
LOG.debug(" %s", str(item))
if self.create_unused_media:
media_count = len(self.r_db.get_media_handles())
else:
media_count = len(self.report.obj_dict[Media])
message = _("Creating media pages")
with self.r_user.progress(_("Narrated Web Site Report"), message,
media_count + 1
) as step:
# bug 8950 : it seems it's better to sort on desc + gid.
def sort_by_desc_and_gid(obj):
"""
Sort by media description and gramps ID
"""
return (obj.desc.lower(), obj.gramps_id)
self.unused_media_handles = []
if self.create_unused_media:
# add unused media
media_list = self.r_db.get_media_handles()
for media_ref in media_list:
if media_ref not in self.report.obj_dict[Media]:
self.unused_media_handles.append(media_ref)
self.unused_media_handles = sorted(
self.unused_media_handles,
key=lambda x: sort_by_desc_and_gid(
self.r_db.get_media_from_handle(x)))
sorted_media_handles = sorted(
self.report.obj_dict[Media].keys(),
key=lambda x: sort_by_desc_and_gid(
self.r_db.get_media_from_handle(x)))
prev = None
total = len(sorted_media_handles)
index = 1
for handle in sorted_media_handles:
gc.collect() # Reduce memory usage when there are many images.
if index == media_count:
next_ = None
elif index < total:
next_ = sorted_media_handles[index]
elif self.unused_media_handles:
next_ = self.unused_media_handles[0]
else:
next_ = None
self.mediapage(self.report, title,
handle, (prev, next_, index, media_count))
prev = handle
step()
index += 1
total = len(self.unused_media_handles)
idx = 1
total_m = len(sorted_media_handles)
prev = sorted_media_handles[total_m-1] if total_m > 0 else 0
if total > 0:
for media_handle in self.unused_media_handles:
gc.collect() # Reduce memory usage when many images.
if index == media_count:
next_ = None
else:
next_ = self.unused_media_handles[idx]
self.mediapage(self.report, title, media_handle,
(prev, next_, index, media_count))
prev = media_handle
step()
index += 1
idx += 1
self.medialistpage(self.report, title, sorted_media_handles)
def medialistpage(self, report, title, sorted_media_handles):
"""
Generate and output the Media index page.
@param: report -- The instance of the main report class
for this report
@param: title -- Is the title of the web page
@param: sorted_media_handles -- A list of the handles of the media to be
displayed sorted by the media title
"""
BasePage.__init__(self, report, title)
output_file, sio = self.report.create_file("media")
# save the media file name in case we create unused media pages
self.cur_fname = self.report.cur_fname
result = self.write_header(self._('Media'))
medialistpage, dummy_head, dummy_body, outerwrapper = result
ldatec = 0
# begin gallery division
with Html("div", class_="content", id="Gallery") as medialist:
outerwrapper += medialist
msg = self._("This page contains an index of all the media objects "
"in the database, sorted by their title. Clicking on "
"the title will take you to that "
"media object&#8217;s page. "
"If you see media size dimensions "
"above an image, click on the "
"image to see the full sized version. ")
medialist += Html("p", msg, id="description")
# begin gallery table and table head
with Html("table",
class_="infolist primobjlist gallerylist") as table:
medialist += table
# begin table head
thead = Html("thead")
table += thead
trow = Html("tr")
thead += trow
trow.extend(
Html("th", trans, class_=colclass, inline=True)
for trans, colclass in [("&nbsp;", "ColumnRowLabel"),
(self._("Media | Name"),
"ColumnName"),
(self._("Date"), "ColumnDate"),
(self._("Mime Type"), "ColumnMime")]
)
# begin table body
tbody = Html("tbody")
table += tbody
index = 1
if self.create_unused_media:
media_count = len(self.r_db.get_media_handles())
else:
media_count = len(self.report.obj_dict[Media])
message = _("Creating list of media pages")
with self.r_user.progress(_("Narrated Web Site Report"),
message, media_count + 1
) as step:
for media_handle in sorted_media_handles:
media = self.r_db.get_media_from_handle(media_handle)
if media:
if media.get_change_time() > ldatec:
ldatec = media.get_change_time()
title = media.get_description() or "[untitled]"
trow = Html("tr")
tbody += trow
media_data_row = [
[index, "ColumnRowLabel"],
[self.media_ref_link(media_handle,
title), "ColumnName"],
[self.rlocale.get_date(media.get_date_object()),
"ColumnDate"],
[media.get_mime_type(), "ColumnMime"]]
trow.extend(
Html("td", data, class_=colclass)
for data, colclass in media_data_row
)
step()
index += 1
idx = 1
total = len(self.unused_media_handles)
if total > 0:
trow += Html("tr")
trow.extend(
Html("td", Html("h4", " "), inline=True) +
Html("td",
Html("h4",
self._("Below unused media objects"),
inline=True),
class_="") +
Html("td", Html("h4", " "), inline=True) +
Html("td", Html("h4", " "), inline=True)
)
for media_handle in self.unused_media_handles:
gmfh = self.r_db.get_media_from_handle
media = gmfh(media_handle)
gc.collect() # Reduce memory usage when many images.
if idx != total:
self.unused_media_handles[idx]
trow += Html("tr")
media_data_row = [
[index, "ColumnRowLabel"],
[self.media_ref_link(media_handle,
media.get_description()),
"ColumnName"],
[self.rlocale.get_date(media.get_date_object()),
"ColumnDate"],
[media.get_mime_type(), "ColumnMime"]]
trow.extend(
Html("td", data, class_=colclass)
for data, colclass in media_data_row
)
step()
index += 1
idx += 1
# add footer section
# add clearline for proper styling
footer = self.write_footer(ldatec)
outerwrapper += (FULLCLEAR, footer)
# send page out for processing
# and close the file
self.report.cur_fname = self.cur_fname
self.xhtml_writer(medialistpage, output_file, sio, ldatec)
def media_ref_link(self, handle, name, uplink=False):
"""
Create a reference link to a media
@param: handle -- The media handle
@param: name -- The name to use for the link
@param: uplink -- If True, then "../../../" is inserted in front of the
result.
"""
# get media url
url = self.report.build_url_fname_html(handle, "img", uplink)
# get name
name = html_escape(name)
# begin hyper link
hyper = Html("a", name, href=url, title=name)
# return hyperlink to its callers
return hyper
def mediapage(self, report, title, media_handle, info):
"""
Generate and output an individual Media page.
@param: report -- The instance of the main report class
for this report
@param: title -- Is the title of the web page
@param: media_handle -- The media handle to use
@param: info -- A tuple containing the media handle for the
next and previous media, the current page
number, and the total number of media pages
"""
media = report.database.get_media_from_handle(media_handle)
BasePage.__init__(self, report, title, media.gramps_id)
(prev, next_, page_number, total_pages) = info
ldatec = media.get_change_time()
# get media rectangles
_region_items = self.media_ref_rect_regions(media_handle)
output_file, sio = self.report.create_file(media_handle, "img")
self.uplink = True
self.bibli = Bibliography()
# get media type to be used primarily with "img" tags
mime_type = media.get_mime_type()
if mime_type:
newpath = self.copy_source_file(media_handle, media)
target_exists = newpath is not None
else:
target_exists = False
self.copy_thumbnail(media_handle, media)
self.page_title = media.get_description()
esc_page_title = html_escape(self.page_title)
result = self.write_header("%s - %s" % (self._("Media"),
self.page_title))
mediapage, head, dummy_body, outerwrapper = result
# if there are media rectangle regions, attach behaviour style sheet
if _region_items:
fname = "/".join(["css", "behaviour.css"])
url = self.report.build_url_fname(fname, None, self.uplink)
head += Html("link", href=url, type="text/css",
media="screen", rel="stylesheet")
# begin MediaDetail division
with Html("div", class_="content", id="GalleryDetail") as mediadetail:
outerwrapper += mediadetail
# media navigation
with Html("div", id="GalleryNav", role="navigation") as medianav:
mediadetail += medianav
if prev:
medianav += self.media_nav_link(prev,
self._("Previous"), True)
data = self._('%(strong1_strt)s%(page_number)d%(strong_end)s '
'of %(strong2_strt)s%(total_pages)d%(strong_end)s'
) % {'strong1_strt' :
'<strong id="GalleryCurrent">',
'strong2_strt' : '<strong id="GalleryTotal">',
'strong_end' : '</strong>',
'page_number' : page_number,
'total_pages' : total_pages}
medianav += Html("span", data, id="GalleryPages")
if next_:
medianav += self.media_nav_link(next_, self._("Next"), True)
# missing media error message
errormsg = self._("The file has been moved or deleted.")
# begin summaryarea division
with Html("div", id="summaryarea") as summaryarea:
mediadetail += summaryarea
if mime_type:
if mime_type.startswith("image"):
if not target_exists:
with Html("div", id="MediaDisplay") as mediadisplay:
summaryarea += mediadisplay
mediadisplay += Html("span", errormsg,
class_="MissingImage")
else:
# Check how big the image is relative to the
# requested 'initial' image size.
# If it's significantly bigger, scale it down to
# improve the site's responsiveness. We don't want
# the user to have to await a large download
# unnecessarily. Either way, set the display image
# size as requested.
orig_image_path = media_path_full(self.r_db,
media.get_path())
(width, height) = image_size(orig_image_path)
max_width = self.report.options[
'maxinitialimagewidth']
# TODO. Convert disk path to URL.
url = self.report.build_url_fname(orig_image_path,
None, self.uplink)
with Html("div", id="GalleryDisplay",
style='max-width: %dpx; height: auto' % (
max_width)) as mediadisplay:
summaryarea += mediadisplay
# Feature #2634; display the mouse-selectable
# regions. See the large block at the top of
# this function where the various regions are
# stored in _region_items
if _region_items:
ordered = Html("ol", class_="RegionBox")
mediadisplay += ordered
while _region_items:
(name, coord_x, coord_y,
width, height, linkurl
) = _region_items.pop()
ordered += Html(
"li",
style="left:%d%%; "
"top:%d%%; "
"width:%d%%; "
"height:%d%%;" % (
coord_x, coord_y,
width, height)) + (
Html("a", name,
href=linkurl)
)
# display the image
if orig_image_path != newpath:
url = self.report.build_url_fname(
newpath, None, self.uplink)
regions = self.media_ref_rect_regions(media_handle)
if regions:
s_width = 'width: %dpx;' % max_width
elif width < max_width:
s_width = 'width: %dpx;' % width
else:
s_width = 'width: %dpx;' % max_width
mediadisplay += Html("a", href=url) + (
Html("img", src=url,
style=s_width,
alt=esc_page_title)
)
else:
dirname = tempfile.mkdtemp()
thmb_path = os.path.join(dirname, "document.png")
if run_thumbnailer(mime_type,
media_path_full(self.r_db,
media.get_path()),
thmb_path, 320):
try:
path = self.report.build_path(
"preview", media.get_handle())
npath = os.path.join(path, media.get_handle())
npath += ".png"
self.report.copy_file(thmb_path, npath)
path = npath
os.unlink(thmb_path)
except EnvironmentError:
path = os.path.join("images", "document.png")
else:
path = os.path.join("images", "document.png")
os.rmdir(dirname)
with Html("div", id="GalleryDisplay") as mediadisplay:
summaryarea += mediadisplay
img_url = self.report.build_url_fname(path,
None,
self.uplink)
if target_exists:
# TODO. Convert disk path to URL
url = self.report.build_url_fname(newpath,
None,
self.uplink)
s_width = 'width: 48px;'
hyper = Html("a", href=url,
title=esc_page_title) + (
Html("img", src=img_url,
style=s_width,
alt=esc_page_title)
)
mediadisplay += hyper
else:
mediadisplay += Html("span", errormsg,
class_="MissingImage")
else:
with Html("div", id="GalleryDisplay") as mediadisplay:
summaryarea += mediadisplay
url = self.report.build_url_image("document.png",
"images", self.uplink)
s_width = 'width: 48px;'
mediadisplay += Html("img", src=url,
style=s_width,
alt=esc_page_title,
title=esc_page_title)
# media title
title = Html("h3", html_escape(self.page_title.strip()),
inline=True)
summaryarea += title
# begin media table
with Html("table", class_="infolist gallery") as table:
summaryarea += table
# Gramps ID
media_gid = media.gramps_id
if not self.noid and media_gid:
trow = Html("tr") + (
Html("td", self._("Gramps ID"),
class_="ColumnAttribute",
inline=True),
Html("td", media_gid, class_="ColumnValue",
inline=True)
)
table += trow
# mime type
if mime_type:
trow = Html("tr") + (
Html("td", self._("File Type"),
class_="ColumnAttribute",
inline=True),
Html("td", mime_type, class_="ColumnValue",
inline=True)
)
table += trow
# media date
date = media.get_date_object()
if date and date is not Date.EMPTY:
trow = Html("tr") + (
Html("td", self._("Date"), class_="ColumnAttribute",
inline=True),
Html("td", self.rlocale.get_date(date),
class_="ColumnValue",
inline=True)
)
table += trow
# get media notes
notelist = self.display_note_list(media.get_note_list())
if notelist is not None:
mediadetail += notelist
# get attribute list
attrlist = media.get_attribute_list()
if attrlist:
attrsection, attrtable = self.display_attribute_header()
self.display_attr_list(attrlist, attrtable)
mediadetail += attrsection
# get media sources
srclist = self.display_media_sources(media)
if srclist is not None:
mediadetail += srclist
# get media references
reflist = self.display_bkref_list(Media, media_handle)
if reflist is not None:
mediadetail += reflist
# add clearline for proper styling
# add footer section
footer = self.write_footer(ldatec)
outerwrapper += (FULLCLEAR, footer)
# send page out for processing
# and close the file
self.xhtml_writer(mediapage, output_file, sio, ldatec)
def media_nav_link(self, handle, name, uplink=False):
"""
Creates the Media Page Navigation hyperlinks for Next and Prev
"""
url = self.report.build_url_fname_html(handle, "img", uplink)
name = html_escape(name)
return Html("a", name, name=name, id=name, href=url,
title=name, inline=True)
def display_media_sources(self, photo):
"""
Display media sources
@param: photo -- The source object (image, pdf, ...)
"""
list(map(
lambda i: self.bibli.add_reference(
self.r_db.get_citation_from_handle(i)),
photo.get_citation_list()))
sourcerefs = self.display_source_refs(self.bibli)
# return source references to its caller
return sourcerefs
def copy_source_file(self, handle, photo):
"""
Copy source file in the web tree.
@param: handle -- Handle of the source
@param: photo -- The source object (image, pdf, ...)
"""
ext = os.path.splitext(photo.get_path())[1]
to_dir = self.report.build_path('images', handle)
newpath = os.path.join(to_dir, handle) + ext
fullpath = media_path_full(self.r_db, photo.get_path())
if not os.path.isfile(fullpath):
_WRONGMEDIAPATH.append([photo.get_gramps_id(), fullpath])
return None
try:
mtime = os.stat(fullpath).st_mtime
if self.report.archive:
self.report.archive.add(fullpath, str(newpath))
else:
to_dir = os.path.join(self.html_dir, to_dir)
if not os.path.isdir(to_dir):
os.makedirs(to_dir)
new_file = os.path.join(self.html_dir, newpath)
shutil.copyfile(fullpath, new_file)
os.utime(new_file, (mtime, mtime))
return newpath
except (IOError, OSError) as msg:
error = _("Missing media object:"
) + "%s (%s)" % (photo.get_description(),
photo.get_gramps_id())
self.r_user.warn(error, str(msg))
return None