From 5c22cbece1005c0d6b24de4c34614b70ba316255 Mon Sep 17 00:00:00 2001 From: BernhardKoschicek Date: Sat, 16 Sep 2023 12:56:39 +0200 Subject: [PATCH 001/102] added button and transform command --- config/default.py | 6 ++++++ openatlas/display/display.py | 6 +++++- openatlas/display/util.py | 7 +++++++ openatlas/views/file.py | 12 +++++++++++- 4 files changed, 29 insertions(+), 2 deletions(-) diff --git a/config/default.py b/config/default.py index 1903906c1..f774eec5a 100644 --- a/config/default.py +++ b/config/default.py @@ -45,12 +45,18 @@ ALLOWED_IMAGE_EXT = DISPLAY_FILE_EXTENSIONS + NONE_DISPLAY_EXT PROCESSED_EXT = '.jpeg' +IIIF_ACTIVATE = False +IIIF_DIR = '' + # For system checks WRITABLE_DIRS = [ UPLOAD_DIR, EXPORT_DIR, RESIZED_IMAGES] +if IIIF_ACTIVATE: + WRITABLE_DIRS.append(IIIF_DIR) + # Security SESSION_COOKIE_SECURE = False # Should be True in production.py if using HTTPS REMEMBER_COOKIE_SECURE = True diff --git a/openatlas/display/display.py b/openatlas/display/display.py index ac2727700..bd836405f 100644 --- a/openatlas/display/display.py +++ b/openatlas/display/display.py @@ -12,7 +12,7 @@ from openatlas.display.table import Table from openatlas.display.util import ( button, description, edit_link, format_entity_date, get_base_table_data, - get_file_path, is_authorized, link, remove_link, uc_first) + get_file_path, is_authorized, link, remove_link, uc_first, check_iiif) from openatlas.models.entity import Entity from openatlas.views.tools import carbon_result, sex_result @@ -75,6 +75,10 @@ def add_button_others(self) -> None: self.buttons.append(button( _('download'), url_for('download_file', filename=path.name))) + if check_iiif(): + self.buttons.append(button( + _('make_iiif_available'), + url_for('make_iiif_available', id_=self.entity.id))) return self.buttons.append( '' + uc_first(_("missing file")) + '') diff --git a/openatlas/display/util.py b/openatlas/display/util.py index a01b8f5f3..395d9a579 100644 --- a/openatlas/display/util.py +++ b/openatlas/display/util.py @@ -746,3 +746,10 @@ def get_entities_linked_to_type_recursive( for sub_id in g.types[id_].subs: get_entities_linked_to_type_recursive(sub_id, data) return data + + +def check_iiif() -> bool: + return True \ + if (app.config['IIIF_ACTIVATE'] + and os.access(Path(app.config['IIIF_DIR']), os.W_OK)) \ + else False diff --git a/openatlas/views/file.py b/openatlas/views/file.py index 268004cf9..a0149a9cd 100644 --- a/openatlas/views/file.py +++ b/openatlas/views/file.py @@ -1,3 +1,4 @@ +from subprocess import call, run from typing import Any, Union from flask import g, render_template, request, send_from_directory, url_for @@ -6,7 +7,7 @@ from werkzeug.wrappers import Response from openatlas import app -from openatlas.display.util import required_group +from openatlas.display.util import required_group, get_file_path from openatlas.forms.form import get_table_form from openatlas.models.entity import Entity @@ -66,3 +67,12 @@ def file_add(id_: int, view: str) -> Union[str, Response]: [_(entity.class_.view), url_for('index', view=entity.class_.view)], entity, f"{_('link')} {_(view)}"]) + + +@app.route('/file/iiif/', methods=['GET', 'POST']) +@required_group('contributor') +def make_iiif_available(id_: int): + call(f"convert {get_file_path(id_)} " + f"-define tiff:tile-geometry=256x256 -compress jpeg " + f"'ptif:{app.config['IIIF_DIR']}/{id_}.tiff'", shell=True) + return redirect(url_for('view', id_=id_)) From d2fae9108ddf42c24315f264ffa82d0f75e1fdd2 Mon Sep 17 00:00:00 2001 From: bkoschicek Date: Thu, 21 Sep 2023 16:10:47 +0200 Subject: [PATCH 002/102] added check if iiif dir is writeable --- config/default.py | 3 --- openatlas/display/util.py | 2 ++ 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/config/default.py b/config/default.py index f774eec5a..a68e1fbe4 100644 --- a/config/default.py +++ b/config/default.py @@ -54,9 +54,6 @@ EXPORT_DIR, RESIZED_IMAGES] -if IIIF_ACTIVATE: - WRITABLE_DIRS.append(IIIF_DIR) - # Security SESSION_COOKIE_SECURE = False # Should be True in production.py if using HTTPS REMEMBER_COOKIE_SECURE = True diff --git a/openatlas/display/util.py b/openatlas/display/util.py index 7c05e4af0..41029efb5 100644 --- a/openatlas/display/util.py +++ b/openatlas/display/util.py @@ -431,6 +431,8 @@ def system_warnings(_context: str, _unneeded_string: str) -> str: warnings.append( f"Database version {app.config['DATABASE_VERSION']} is needed but " f"current version is {g.settings['database_version']}") + if app.config['IIIF_ACTIVATE']: + app.config['WRITABLE_DIRS'].append(app.config['IIIF_DIR']) for path in app.config['WRITABLE_DIRS']: if not os.access(path, os.W_OK): warnings.append( From 7b7c6a00ec81d4cf8015881b71236324de2442e4 Mon Sep 17 00:00:00 2001 From: bkoschicek Date: Thu, 21 Sep 2023 16:35:00 +0200 Subject: [PATCH 003/102] added button if file exist --- openatlas/display/display.py | 16 +++++++++++----- openatlas/display/util.py | 7 ++++++- openatlas/views/file.py | 3 ++- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/openatlas/display/display.py b/openatlas/display/display.py index bd836405f..d0c41662c 100644 --- a/openatlas/display/display.py +++ b/openatlas/display/display.py @@ -12,7 +12,8 @@ from openatlas.display.table import Table from openatlas.display.util import ( button, description, edit_link, format_entity_date, get_base_table_data, - get_file_path, is_authorized, link, remove_link, uc_first, check_iiif) + get_file_path, is_authorized, link, remove_link, uc_first, check_iiif_activation, + check_iiif_file_exist) from openatlas.models.entity import Entity from openatlas.views.tools import carbon_result, sex_result @@ -75,10 +76,15 @@ def add_button_others(self) -> None: self.buttons.append(button( _('download'), url_for('download_file', filename=path.name))) - if check_iiif(): - self.buttons.append(button( - _('make_iiif_available'), - url_for('make_iiif_available', id_=self.entity.id))) + if check_iiif_activation(): + if check_iiif_file_exist(self.entity.id): + self.buttons.append(button( + _('iiif'), + url_for('iiif', id_=self.entity.id))) + else: + self.buttons.append(button( + _('make_iiif_available'), + url_for('make_iiif_available', id_=self.entity.id))) return self.buttons.append( '' + uc_first(_("missing file")) + '') diff --git a/openatlas/display/util.py b/openatlas/display/util.py index 41029efb5..bd67dc883 100644 --- a/openatlas/display/util.py +++ b/openatlas/display/util.py @@ -751,8 +751,13 @@ def get_entities_linked_to_type_recursive( return data -def check_iiif() -> bool: +def check_iiif_activation() -> bool: return True \ if (app.config['IIIF_ACTIVATE'] and os.access(Path(app.config['IIIF_DIR']), os.W_OK)) \ else False + + +def check_iiif_file_exist(id_: int) -> bool: + file_to_check = Path(app.config['IIIF_DIR']) / str(id_) + return file_to_check.is_file() diff --git a/openatlas/views/file.py b/openatlas/views/file.py index a0149a9cd..6dd5557ba 100644 --- a/openatlas/views/file.py +++ b/openatlas/views/file.py @@ -72,7 +72,8 @@ def file_add(id_: int, view: str) -> Union[str, Response]: @app.route('/file/iiif/', methods=['GET', 'POST']) @required_group('contributor') def make_iiif_available(id_: int): - call(f"convert {get_file_path(id_)} " + call_ = call(f"convert {get_file_path(id_)} " f"-define tiff:tile-geometry=256x256 -compress jpeg " f"'ptif:{app.config['IIIF_DIR']}/{id_}.tiff'", shell=True) + print(call_) return redirect(url_for('view', id_=id_)) From 8eeec44313803f023bd82917f2215c8fd1129ee8 Mon Sep 17 00:00:00 2001 From: BernhardKoschicek Date: Fri, 22 Sep 2023 09:46:20 +0200 Subject: [PATCH 004/102] try popen --- openatlas/views/file.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/openatlas/views/file.py b/openatlas/views/file.py index 6dd5557ba..11b8352b3 100644 --- a/openatlas/views/file.py +++ b/openatlas/views/file.py @@ -1,3 +1,4 @@ +import subprocess from subprocess import call, run from typing import Any, Union @@ -72,8 +73,9 @@ def file_add(id_: int, view: str) -> Union[str, Response]: @app.route('/file/iiif/', methods=['GET', 'POST']) @required_group('contributor') def make_iiif_available(id_: int): - call_ = call(f"convert {get_file_path(id_)} " - f"-define tiff:tile-geometry=256x256 -compress jpeg " - f"'ptif:{app.config['IIIF_DIR']}/{id_}.tiff'", shell=True) - print(call_) + command =f"convert {get_file_path(id_)} -define tiff:tile-geometry=256x256 -compress jpeg 'ptif:{app.config['IIIF_DIR'] / str(id_)}'" + # call_ = call(f"convert {get_file_path(id_)} " + # f"-define tiff:tile-geometry=256x256 -compress jpeg " + # f"'ptif:{app.config['IIIF_DIR'] / str(id_)}'", shell=True) + subprocess.Popen( command, shell=True) return redirect(url_for('view', id_=id_)) From af61a92321153c03cb4b54a145eb714d40477fcb Mon Sep 17 00:00:00 2001 From: BernhardKoschicek Date: Fri, 22 Sep 2023 10:09:50 +0200 Subject: [PATCH 005/102] added iiif dir as path --- openatlas/views/file.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openatlas/views/file.py b/openatlas/views/file.py index 11b8352b3..a51605875 100644 --- a/openatlas/views/file.py +++ b/openatlas/views/file.py @@ -1,4 +1,5 @@ import subprocess +from pathlib import Path from subprocess import call, run from typing import Any, Union @@ -73,7 +74,8 @@ def file_add(id_: int, view: str) -> Union[str, Response]: @app.route('/file/iiif/', methods=['GET', 'POST']) @required_group('contributor') def make_iiif_available(id_: int): - command =f"convert {get_file_path(id_)} -define tiff:tile-geometry=256x256 -compress jpeg 'ptif:{app.config['IIIF_DIR'] / str(id_)}'" + + command =f"convert {get_file_path(id_)} -define tiff:tile-geometry=256x256 -compress jpeg 'ptif:{Path(app.config['IIIF_DIR']) / str(id_)}'" # call_ = call(f"convert {get_file_path(id_)} " # f"-define tiff:tile-geometry=256x256 -compress jpeg " # f"'ptif:{app.config['IIIF_DIR'] / str(id_)}'", shell=True) From 1d76e7ed3789bc5f82ec29976197fd3abdd9aee5 Mon Sep 17 00:00:00 2001 From: BernhardKoschicek Date: Fri, 22 Sep 2023 10:43:37 +0200 Subject: [PATCH 006/102] changed to vips --- openatlas/display/display.py | 2 +- openatlas/views/file.py | 18 ++++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/openatlas/display/display.py b/openatlas/display/display.py index d0c41662c..812c13441 100644 --- a/openatlas/display/display.py +++ b/openatlas/display/display.py @@ -80,7 +80,7 @@ def add_button_others(self) -> None: if check_iiif_file_exist(self.entity.id): self.buttons.append(button( _('iiif'), - url_for('iiif', id_=self.entity.id))) + url_for('view_iiif', id_=self.entity.id))) else: self.buttons.append(button( _('make_iiif_available'), diff --git a/openatlas/views/file.py b/openatlas/views/file.py index a51605875..87ce7a110 100644 --- a/openatlas/views/file.py +++ b/openatlas/views/file.py @@ -71,13 +71,23 @@ def file_add(id_: int, view: str) -> Union[str, Response]: f"{_('link')} {_(view)}"]) -@app.route('/file/iiif/', methods=['GET', 'POST']) +@app.route('/file/iiif/', methods=['GET']) @required_group('contributor') def make_iiif_available(id_: int): - - command =f"convert {get_file_path(id_)} -define tiff:tile-geometry=256x256 -compress jpeg 'ptif:{Path(app.config['IIIF_DIR']) / str(id_)}'" + command = f"vips {get_file_path(id_)} {Path(app.config['IIIF_DIR']) / str(id_)} --tile --pyramid --compression deflate --tile-width 256 --tile-height 256" + # command =f"convert {get_file_path(id_)} -define tiff:tile-geometry=256x256 -compress jpeg 'ptif:{Path(app.config['IIIF_DIR']) / str(id_)}'" # call_ = call(f"convert {get_file_path(id_)} " # f"-define tiff:tile-geometry=256x256 -compress jpeg " # f"'ptif:{app.config['IIIF_DIR'] / str(id_)}'", shell=True) - subprocess.Popen( command, shell=True) + subprocess.Popen(command, shell=True) return redirect(url_for('view', id_=id_)) + + + +@app.route('/iiif/', methods=['GET']) +@required_group('contributor') +def view_iiif(id_: int): + + return redirect(url_for('view', id_=id_)) + + From ad44bbc5a0deb6436824f2acd6935dbdc2dacd07 Mon Sep 17 00:00:00 2001 From: BernhardKoschicek Date: Fri, 22 Sep 2023 11:02:24 +0200 Subject: [PATCH 007/102] forgot tiffsave --- openatlas/views/file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openatlas/views/file.py b/openatlas/views/file.py index 87ce7a110..31afb4b68 100644 --- a/openatlas/views/file.py +++ b/openatlas/views/file.py @@ -74,7 +74,7 @@ def file_add(id_: int, view: str) -> Union[str, Response]: @app.route('/file/iiif/', methods=['GET']) @required_group('contributor') def make_iiif_available(id_: int): - command = f"vips {get_file_path(id_)} {Path(app.config['IIIF_DIR']) / str(id_)} --tile --pyramid --compression deflate --tile-width 256 --tile-height 256" + command = f"vips tiffsave {get_file_path(id_)} {Path(app.config['IIIF_DIR']) / str(id_)} --tile --pyramid --compression deflate --tile-width 256 --tile-height 256" # command =f"convert {get_file_path(id_)} -define tiff:tile-geometry=256x256 -compress jpeg 'ptif:{Path(app.config['IIIF_DIR']) / str(id_)}'" # call_ = call(f"convert {get_file_path(id_)} " # f"-define tiff:tile-geometry=256x256 -compress jpeg " From 2533ed98d2006998a6e487b448b7723ab388daf5 Mon Sep 17 00:00:00 2001 From: BernhardKoschicek Date: Fri, 22 Sep 2023 12:16:45 +0200 Subject: [PATCH 008/102] refactor --- openatlas/views/file.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/openatlas/views/file.py b/openatlas/views/file.py index 31afb4b68..ea310a172 100644 --- a/openatlas/views/file.py +++ b/openatlas/views/file.py @@ -74,20 +74,18 @@ def file_add(id_: int, view: str) -> Union[str, Response]: @app.route('/file/iiif/', methods=['GET']) @required_group('contributor') def make_iiif_available(id_: int): - command = f"vips tiffsave {get_file_path(id_)} {Path(app.config['IIIF_DIR']) / str(id_)} --tile --pyramid --compression deflate --tile-width 256 --tile-height 256" - # command =f"convert {get_file_path(id_)} -define tiff:tile-geometry=256x256 -compress jpeg 'ptif:{Path(app.config['IIIF_DIR']) / str(id_)}'" - # call_ = call(f"convert {get_file_path(id_)} " - # f"-define tiff:tile-geometry=256x256 -compress jpeg " - # f"'ptif:{app.config['IIIF_DIR'] / str(id_)}'", shell=True) + command = \ + (f"vips.exe tiffsave " + f"{get_file_path(id_)} {Path(app.config['IIIF_DIR']) / str(id_)} " + f"--tile --pyramid --compression deflate " + f"--tile-width 256 --tile-height 256") subprocess.Popen(command, shell=True) return redirect(url_for('view', id_=id_)) - @app.route('/iiif/', methods=['GET']) @required_group('contributor') def view_iiif(id_: int): - return redirect(url_for('view', id_=id_)) From c8a5b7c57a9ab460ea6ecb0b936acd4801f28e06 Mon Sep 17 00:00:00 2001 From: BernhardKoschicek Date: Fri, 22 Sep 2023 12:57:07 +0200 Subject: [PATCH 009/102] convert all, prefix and check write access --- config/default.py | 1 + openatlas/display/util.py | 18 ++++++++++++------ openatlas/templates/admin/data.html | 1 + openatlas/views/admin.py | 12 ++++++++++++ openatlas/views/file.py | 13 +++++++------ 5 files changed, 33 insertions(+), 12 deletions(-) diff --git a/config/default.py b/config/default.py index a68e1fbe4..b3c99bbb8 100644 --- a/config/default.py +++ b/config/default.py @@ -47,6 +47,7 @@ IIIF_ACTIVATE = False IIIF_DIR = '' +IIIF_PREFIX = '' # For system checks WRITABLE_DIRS = [ diff --git a/openatlas/display/util.py b/openatlas/display/util.py index bd67dc883..e197c00b1 100644 --- a/openatlas/display/util.py +++ b/openatlas/display/util.py @@ -431,13 +431,11 @@ def system_warnings(_context: str, _unneeded_string: str) -> str: warnings.append( f"Database version {app.config['DATABASE_VERSION']} is needed but " f"current version is {g.settings['database_version']}") - if app.config['IIIF_ACTIVATE']: - app.config['WRITABLE_DIRS'].append(app.config['IIIF_DIR']) + if app.config['IIIF_ACTIVATE'] and app.config['IIIF_DIR']: + path = Path(app.config['IIIF_DIR']) / app.config['IIIF_PREFIX'] + check_write_access(path, warnings) for path in app.config['WRITABLE_DIRS']: - if not os.access(path, os.W_OK): - warnings.append( - '

' + _('directory not writable') + - f" {str(path).replace(app.root_path, '')}

") + check_write_access(path, warnings) if is_authorized('admin'): from openatlas.models.user import User user = User.get_by_username('OpenAtlas') @@ -455,6 +453,14 @@ def system_warnings(_context: str, _unneeded_string: str) -> str: f'{"
".join(warnings)}' if warnings else '' +def check_write_access(path, warnings): + if not os.access(path, os.W_OK): + warnings.append( + '

' + _('directory not writable') + + f" {str(path).replace(app.root_path, '')}

") + return warnings + + @app.template_filter() def tooltip(text: str) -> str: if not text: diff --git a/openatlas/templates/admin/data.html b/openatlas/templates/admin/data.html index c2c1d269e..e062e735d 100644 --- a/openatlas/templates/admin/data.html +++ b/openatlas/templates/admin/data.html @@ -32,6 +32,7 @@

{{ _('image processing')|uc_first }}

{{ 'admin/file'|manual|safe }}
{{ _('create resized images')|button(url_for('admin_resize_images'))|safe }}
{{ _('delete orphaned resized images')|button(url_for('admin_delete_orphaned_resized_images'))|safe }}
+
{{ _('convert all images to iiif')|button(url_for('admin_convert_all_to_iiif'))|safe }}
{% endif %} {% if 'manager'|is_authorized %} diff --git a/openatlas/views/admin.py b/openatlas/views/admin.py index 39a4be31f..1240d7914 100644 --- a/openatlas/views/admin.py +++ b/openatlas/views/admin.py @@ -38,6 +38,7 @@ from openatlas.models.settings import Settings from openatlas.models.type import Type from openatlas.models.user import User +from openatlas.views.file import convert_image_to_iiif @app.route('/admin', methods=['GET', 'POST'], strict_slashes=False) @@ -777,3 +778,14 @@ def get_disk_space_info() -> Optional[dict[str, Any]]: 'percent_used': percent_free, 'percent_project': percent_files, 'percent_other': 100 - (percent_files + percent_free)} + + +@app.route('/admin/admin_convert_all_to_iiif') +@required_group('admin') +def admin_convert_all_to_iiif() -> Response: + for entity in Entity.get_by_class('file'): + if entity.id in g.file_stats \ + and g.file_stats[entity.id]['ext'] \ + in app.config['ALLOWED_IMAGE_EXT']: + convert_image_to_iiif(entity.id) + return redirect(url_for('admin_index') + '#tab-data') diff --git a/openatlas/views/file.py b/openatlas/views/file.py index ea310a172..15d230d2a 100644 --- a/openatlas/views/file.py +++ b/openatlas/views/file.py @@ -1,6 +1,5 @@ import subprocess from pathlib import Path -from subprocess import call, run from typing import Any, Union from flask import g, render_template, request, send_from_directory, url_for @@ -74,18 +73,20 @@ def file_add(id_: int, view: str) -> Union[str, Response]: @app.route('/file/iiif/', methods=['GET']) @required_group('contributor') def make_iiif_available(id_: int): + convert_image_to_iiif(id_) + return redirect(url_for('view', id_=id_)) + + +def convert_image_to_iiif(id_): + path = Path(app.config['IIIF_DIR']) / app.config['IIIF_PREFIX'] / str(id_) command = \ - (f"vips.exe tiffsave " - f"{get_file_path(id_)} {Path(app.config['IIIF_DIR']) / str(id_)} " + (f"vips.exe tiffsave {get_file_path(id_)} {path} " f"--tile --pyramid --compression deflate " f"--tile-width 256 --tile-height 256") subprocess.Popen(command, shell=True) - return redirect(url_for('view', id_=id_)) @app.route('/iiif/', methods=['GET']) @required_group('contributor') def view_iiif(id_: int): return redirect(url_for('view', id_=id_)) - - From 955e808cb824b6e70305d70ef75d6307855b5fe2 Mon Sep 17 00:00:00 2001 From: BernhardKoschicek Date: Fri, 22 Sep 2023 13:46:45 +0200 Subject: [PATCH 010/102] trying prezi --- openatlas/display/display.py | 16 ++++++++++------ openatlas/display/util.py | 3 ++- openatlas/views/file.py | 29 ++++++++++++++++++++++++++--- 3 files changed, 38 insertions(+), 10 deletions(-) diff --git a/openatlas/display/display.py b/openatlas/display/display.py index 812c13441..9ae37da28 100644 --- a/openatlas/display/display.py +++ b/openatlas/display/display.py @@ -5,6 +5,7 @@ from flask import g, url_for from flask_babel import lazy_gettext as _ +from openatlas import app from openatlas.display.base_display import ( ActorDisplay, BaseDisplay, EventsDisplay, PlaceBaseDisplay, ReferenceBaseDisplay, TypeBaseDisplay) @@ -12,7 +13,8 @@ from openatlas.display.table import Table from openatlas.display.util import ( button, description, edit_link, format_entity_date, get_base_table_data, - get_file_path, is_authorized, link, remove_link, uc_first, check_iiif_activation, + get_file_path, is_authorized, link, remove_link, uc_first, + check_iiif_activation, check_iiif_file_exist) from openatlas.models.entity import Entity from openatlas.views.tools import carbon_result, sex_result @@ -80,7 +82,9 @@ def add_button_others(self) -> None: if check_iiif_file_exist(self.entity.id): self.buttons.append(button( _('iiif'), - url_for('view_iiif', id_=self.entity.id))) + url_for('view_iiif', + prefix=app.config['IIIF_PREFIX'], + id_=self.entity.id))) else: self.buttons.append(button( _('make_iiif_available'), @@ -99,8 +103,8 @@ def add_tabs(self) -> None: super().add_tabs() entity = self.entity for name in [ - 'source', 'event', 'actor', 'place', 'feature', - 'stratigraphic_unit', 'artifact', 'reference', 'type']: + 'source', 'event', 'actor', 'place', 'feature', + 'stratigraphic_unit', 'artifact', 'reference', 'type']: self.tabs[name] = Tab(name, entity=entity) entity.image_id = entity.id if get_file_path(entity.id) else None for link_ in entity.get_links('P67'): @@ -296,8 +300,8 @@ def add_tabs(self) -> None: super().add_tabs() entity = self.entity for name in [ - 'actor', 'artifact', 'feature', 'event', 'place', - 'stratigraphic_unit', 'text', 'reference', 'file']: + 'actor', 'artifact', 'feature', 'event', 'place', + 'stratigraphic_unit', 'text', 'reference', 'file']: self.tabs[name] = Tab(name, entity=entity) for text in entity.get_linked_entities('P73', types=True): self.tabs['text'].table.rows.append([ diff --git a/openatlas/display/util.py b/openatlas/display/util.py index e197c00b1..2b4943a22 100644 --- a/openatlas/display/util.py +++ b/openatlas/display/util.py @@ -765,5 +765,6 @@ def check_iiif_activation() -> bool: def check_iiif_file_exist(id_: int) -> bool: - file_to_check = Path(app.config['IIIF_DIR']) / str(id_) + file_to_check = (Path(app.config['IIIF_DIR']) + / app.config['IIIF_PREFIX'] / str(id_)) return file_to_check.is_file() diff --git a/openatlas/views/file.py b/openatlas/views/file.py index 15d230d2a..37cda21fd 100644 --- a/openatlas/views/file.py +++ b/openatlas/views/file.py @@ -1,7 +1,10 @@ +import json import subprocess from pathlib import Path -from typing import Any, Union +from typing import Any, Union, Optional +from iiif_prezi import factory +from iiif_prezi.factory import ManifestFactory from flask import g, render_template, request, send_from_directory, url_for from flask_babel import lazy_gettext as _ from werkzeug.utils import redirect @@ -87,6 +90,26 @@ def convert_image_to_iiif(id_): @app.route('/iiif/', methods=['GET']) +@app.route('/iiif//', methods=['GET']) @required_group('contributor') -def view_iiif(id_: int): - return redirect(url_for('view', id_=id_)) +def view_iiif(id_: int, prefix: Optional[str] = None): + fac = ManifestFactory() + # Where the resources live on the web + fac.set_base_prezi_uri(request.url) + # Where the resources live on disk + image_path = Path(app.config['IIIF_DIR']) / app.config['IIIF_PREFIX'] + fac.set_base_prezi_dir(image_path) + + # Default Image API information + fac.set_base_image_uri(request.url) + fac.set_iiif_image_info(2.0, 2) # Version, ComplianceLevel + + # 'warn' will print warnings, default level + # 'error' will turn off warnings + # 'error_on_warning' will make warnings into errors + fac.set_debug("warn") + entity = Entity.get_by_id(id_) + manifest = fac.manifest(label=entity.name) + manifest.description = entity.description + mfst = manifest.toJSON(top=True) + return json.dumps(mfst) From 7d917a8da7f39cb87fc20507e175575a7147544a Mon Sep 17 00:00:00 2001 From: BernhardKoschicek Date: Fri, 22 Sep 2023 16:29:58 +0200 Subject: [PATCH 011/102] trying prezi3 --- openatlas/views/file.py | 47 ++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/openatlas/views/file.py b/openatlas/views/file.py index 37cda21fd..7e9bbd8d1 100644 --- a/openatlas/views/file.py +++ b/openatlas/views/file.py @@ -1,11 +1,10 @@ -import json import subprocess from pathlib import Path from typing import Any, Union, Optional -from iiif_prezi import factory -from iiif_prezi.factory import ManifestFactory -from flask import g, render_template, request, send_from_directory, url_for +from iiif_prezi3 import Manifest, config +from flask import g, render_template, request, send_from_directory, url_for, \ + session from flask_babel import lazy_gettext as _ from werkzeug.utils import redirect from werkzeug.wrappers import Response @@ -88,28 +87,28 @@ def convert_image_to_iiif(id_): f"--tile-width 256 --tile-height 256") subprocess.Popen(command, shell=True) +def getManifest(img_id): + + path = request.base_url + + + config.configs['helpers.auto_fields.AutoLang'].auto_lang = session['language'] + + manifest = Manifest( + id=path, + label=str(img_id)) + canvas = manifest.make_canvas_from_iiif( + url=app.config['IIIF_URL'] + str(img_id)) + + return manifest.json(indent=2) + +@app.route('/iiif/.json') +def iiif(img_id: int): + return getManifest(img_id) @app.route('/iiif/', methods=['GET']) @app.route('/iiif//', methods=['GET']) @required_group('contributor') def view_iiif(id_: int, prefix: Optional[str] = None): - fac = ManifestFactory() - # Where the resources live on the web - fac.set_base_prezi_uri(request.url) - # Where the resources live on disk - image_path = Path(app.config['IIIF_DIR']) / app.config['IIIF_PREFIX'] - fac.set_base_prezi_dir(image_path) - - # Default Image API information - fac.set_base_image_uri(request.url) - fac.set_iiif_image_info(2.0, 2) # Version, ComplianceLevel - - # 'warn' will print warnings, default level - # 'error' will turn off warnings - # 'error_on_warning' will make warnings into errors - fac.set_debug("warn") - entity = Entity.get_by_id(id_) - manifest = fac.manifest(label=entity.name) - manifest.description = entity.description - mfst = manifest.toJSON(top=True) - return json.dumps(mfst) + + return redirect(url_for('view', id_=id_)) From 491494b8950c8284d43671ac6c18a0034f86ef27 Mon Sep 17 00:00:00 2001 From: BernhardKoschicek Date: Fri, 22 Sep 2023 16:31:30 +0200 Subject: [PATCH 012/102] trying prezi3 --- openatlas/views/file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openatlas/views/file.py b/openatlas/views/file.py index 7e9bbd8d1..e64a2d192 100644 --- a/openatlas/views/file.py +++ b/openatlas/views/file.py @@ -102,7 +102,7 @@ def getManifest(img_id): return manifest.json(indent=2) -@app.route('/iiif/.json') +@app.route('/iiif_/.json') def iiif(img_id: int): return getManifest(img_id) From a9078126ed61cf08859dd67226c39666456227ca Mon Sep 17 00:00:00 2001 From: BernhardKoschicek Date: Fri, 22 Sep 2023 16:40:58 +0200 Subject: [PATCH 013/102] made vips for both os --- openatlas/views/file.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openatlas/views/file.py b/openatlas/views/file.py index e64a2d192..d46f04556 100644 --- a/openatlas/views/file.py +++ b/openatlas/views/file.py @@ -1,3 +1,4 @@ +import os import subprocess from pathlib import Path from typing import Any, Union, Optional @@ -81,8 +82,9 @@ def make_iiif_available(id_: int): def convert_image_to_iiif(id_): path = Path(app.config['IIIF_DIR']) / app.config['IIIF_PREFIX'] / str(id_) + vips = "vips" if os.name == 'posix' else "vips.exe" command = \ - (f"vips.exe tiffsave {get_file_path(id_)} {path} " + (f"{vips} tiffsave {get_file_path(id_)} {path} " f"--tile --pyramid --compression deflate " f"--tile-width 256 --tile-height 256") subprocess.Popen(command, shell=True) From 20258201763383bb34405b507193ebd40604d77d Mon Sep 17 00:00:00 2001 From: BernhardKoschicek Date: Fri, 22 Sep 2023 16:58:34 +0200 Subject: [PATCH 014/102] trying prezi2 --- openatlas/views/file.py | 43 +++++++++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/openatlas/views/file.py b/openatlas/views/file.py index d46f04556..3ff7ada0f 100644 --- a/openatlas/views/file.py +++ b/openatlas/views/file.py @@ -3,9 +3,8 @@ from pathlib import Path from typing import Any, Union, Optional -from iiif_prezi3 import Manifest, config -from flask import g, render_template, request, send_from_directory, url_for, \ - session +from iiif_prezi.factory import ManifestFactory +from flask import g, render_template, request, send_from_directory, url_for from flask_babel import lazy_gettext as _ from werkzeug.utils import redirect from werkzeug.wrappers import Response @@ -89,28 +88,48 @@ def convert_image_to_iiif(id_): f"--tile-width 256 --tile-height 256") subprocess.Popen(command, shell=True) -def getManifest(img_id): +def getManifest(img_id): path = request.base_url + fac = ManifestFactory() + # Where the resources live on the web + fac.set_base_prezi_uri(path) + # Where the resources live on disk + fac.set_base_prezi_dir(Path(app.config['IIIF_DIR'])) + + # Default Image API information + fac.set_base_image_uri("http://www.example.org/path/to/image/api/") + fac.set_iiif_image_info(2.0, 2) # Version, ComplianceLevel + + # 'warn' will print warnings, default level + # 'error' will turn off warnings + # 'error_on_warning' will make warnings into errors - config.configs['helpers.auto_fields.AutoLang'].auto_lang = session['language'] + entity = Entity.get_by_id(img_id) + fac.set_debug("warn") + manifest = fac.manifest(label="Example Manifest") + manifest.set_metadata({ + "Title": entity.name, + "Author": "John Doe", + "Date": "2023-09-22" + }) + canvas = manifest.canvas(ident="canvas1") + image = canvas.image(ident=img_id, iiif=True) + image.set_hw(2000, 1500) - manifest = Manifest( - id=path, - label=str(img_id)) - canvas = manifest.make_canvas_from_iiif( - url=app.config['IIIF_URL'] + str(img_id)) + manifest_json = manifest.toJSON() + + return manifest_json - return manifest.json(indent=2) @app.route('/iiif_/.json') def iiif(img_id: int): return getManifest(img_id) + @app.route('/iiif/', methods=['GET']) @app.route('/iiif//', methods=['GET']) @required_group('contributor') def view_iiif(id_: int, prefix: Optional[str] = None): - return redirect(url_for('view', id_=id_)) From 9f9064ba16767466da859f8f6c1b17e15fb4cc84 Mon Sep 17 00:00:00 2001 From: BernhardKoschicek Date: Fri, 22 Sep 2023 16:59:39 +0200 Subject: [PATCH 015/102] trying prezi2 --- openatlas/views/file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openatlas/views/file.py b/openatlas/views/file.py index 3ff7ada0f..e41ecb2ca 100644 --- a/openatlas/views/file.py +++ b/openatlas/views/file.py @@ -96,7 +96,7 @@ def getManifest(img_id): # Where the resources live on the web fac.set_base_prezi_uri(path) # Where the resources live on disk - fac.set_base_prezi_dir(Path(app.config['IIIF_DIR'])) + fac.set_base_prezi_dir(app.config['IIIF_DIR']) # Default Image API information fac.set_base_image_uri("http://www.example.org/path/to/image/api/") From 21ba295b26eac696f3a6d5e2338e687d31c36389 Mon Sep 17 00:00:00 2001 From: BernhardKoschicek Date: Fri, 22 Sep 2023 17:01:13 +0200 Subject: [PATCH 016/102] trying prezi2 --- openatlas/views/file.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openatlas/views/file.py b/openatlas/views/file.py index e41ecb2ca..6adb14c52 100644 --- a/openatlas/views/file.py +++ b/openatlas/views/file.py @@ -114,7 +114,8 @@ def getManifest(img_id): "Author": "John Doe", "Date": "2023-09-22" }) - canvas = manifest.canvas(ident="canvas1") + sequence = manifest.sequence(label="Example Sequence") + canvas = sequence.canvas(ident="canvas1") image = canvas.image(ident=img_id, iiif=True) image.set_hw(2000, 1500) From e5ddd725eff6f0662501990afb554e79ca90e3cc Mon Sep 17 00:00:00 2001 From: BernhardKoschicek Date: Fri, 22 Sep 2023 18:06:47 +0200 Subject: [PATCH 017/102] manual manifest --- openatlas/views/file.py | 94 +++++++++++++++++++++++++++-------------- 1 file changed, 62 insertions(+), 32 deletions(-) diff --git a/openatlas/views/file.py b/openatlas/views/file.py index 6adb14c52..af08e35db 100644 --- a/openatlas/views/file.py +++ b/openatlas/views/file.py @@ -3,7 +3,6 @@ from pathlib import Path from typing import Any, Union, Optional -from iiif_prezi.factory import ManifestFactory from flask import g, render_template, request, send_from_directory, url_for from flask_babel import lazy_gettext as _ from werkzeug.utils import redirect @@ -90,38 +89,69 @@ def convert_image_to_iiif(id_): def getManifest(img_id): - path = request.base_url - - fac = ManifestFactory() - # Where the resources live on the web - fac.set_base_prezi_uri(path) - # Where the resources live on disk - fac.set_base_prezi_dir(app.config['IIIF_DIR']) - - # Default Image API information - fac.set_base_image_uri("http://www.example.org/path/to/image/api/") - fac.set_iiif_image_info(2.0, 2) # Version, ComplianceLevel - - # 'warn' will print warnings, default level - # 'error' will turn off warnings - # 'error_on_warning' will make warnings into errors - entity = Entity.get_by_id(img_id) - fac.set_debug("warn") - manifest = fac.manifest(label="Example Manifest") - manifest.set_metadata({ - "Title": entity.name, - "Author": "John Doe", - "Date": "2023-09-22" - }) - sequence = manifest.sequence(label="Example Sequence") - canvas = sequence.canvas(ident="canvas1") - image = canvas.image(ident=img_id, iiif=True) - image.set_hw(2000, 1500) - - manifest_json = manifest.toJSON() - - return manifest_json + path = request.base_url + manifest = { + "@context": "http://iiif.io/api/presentation/2/context.json", + "@id": f"{path}{img_id}.json", + "@type": "sc:Manifest", + "label": entity.name, + "metadata": [], + "description": [{ + "@value": entity.description, + "@language": "en"}], + "license": "https://creativecommons.org/licenses/by/3.0/", + "attribution": "By OpenAtlas", + "sequences": [{ + "@id": "http://c8b09ce6-df6d-4d5e-9eba-17507dc5c185", + "@type": "sc:Sequence", + "label": [{ + "@value": "Normal Sequence", + "@language": "en"}], + "canvases": [{ + "@id": "http://251a31df-761d-46df-85c3-66cb967b8a67", + "@type": "sc:Canvas", + "label": "Ring", + "height": 450, + "width": "0", + "images": [{ + "@context": "http://iiif.io/api/presentation/2/context.json", + "@id": "http://a0a3ec3e-2084-4253-b0f9-a5f87645e15d", + "@type": "oa:Annotation", + "motivation": "sc:painting", + "resource": { + "@id": f"{path}/iiif/{img_id}/full/full/0/default.jpg", + "@type": "dctypes:Image", + "format": "image/jpeg", + "service": { + "@context": "http://iiif.io/api/image/2/context.json", + "@id": "{path}/iiif/{img_id}", + "profile": [ + "http://iiif.io/api/image/2/level1.json", + {"formats": [ + "jpg"], + "qualities": [ + "native", + "color", + "gray", + "bitonal"], + "supports": [ + "regionByPct", + "regionSquare", + "sizeByForcedWh", + "sizeByWh", + "sizeAboveFull", + "rotationBy90s", + "mirroring"], + "maxWidth": 5000, + "maxHeight": 5000}]}, + "height": 450, + "width": 600}, + "on": "http://251a31df-761d-46df-85c3-66cb967b8a67"}], + "related": ""}]}], + "structures": []} + + return manifest @app.route('/iiif_/.json') From 923485c8ae8834ca5533744af4585a064671830a Mon Sep 17 00:00:00 2001 From: BernhardKoschicek Date: Fri, 22 Sep 2023 18:13:48 +0200 Subject: [PATCH 018/102] manual manifest --- openatlas/views/file.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openatlas/views/file.py b/openatlas/views/file.py index af08e35db..5981cc8f7 100644 --- a/openatlas/views/file.py +++ b/openatlas/views/file.py @@ -90,10 +90,10 @@ def convert_image_to_iiif(id_): def getManifest(img_id): entity = Entity.get_by_id(img_id) - path = request.base_url + path = request.url_root manifest = { "@context": "http://iiif.io/api/presentation/2/context.json", - "@id": f"{path}{img_id}.json", + "@id": f"{request.base_url}", "@type": "sc:Manifest", "label": entity.name, "metadata": [], @@ -120,12 +120,12 @@ def getManifest(img_id): "@type": "oa:Annotation", "motivation": "sc:painting", "resource": { - "@id": f"{path}/iiif/{img_id}/full/full/0/default.jpg", + "@id": f"{path}iiif/{img_id}/full/full/0/default.jpg", "@type": "dctypes:Image", "format": "image/jpeg", "service": { "@context": "http://iiif.io/api/image/2/context.json", - "@id": "{path}/iiif/{img_id}", + "@id": f"{path}iiif/{img_id}", "profile": [ "http://iiif.io/api/image/2/level1.json", {"formats": [ From ad61afb5c1c3f92a3b52f2a6bd915f506349db2d Mon Sep 17 00:00:00 2001 From: BernhardKoschicek Date: Fri, 22 Sep 2023 18:22:59 +0200 Subject: [PATCH 019/102] changed manifest route --- openatlas/views/file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openatlas/views/file.py b/openatlas/views/file.py index 5981cc8f7..a7c40c3e3 100644 --- a/openatlas/views/file.py +++ b/openatlas/views/file.py @@ -154,7 +154,7 @@ def getManifest(img_id): return manifest -@app.route('/iiif_/.json') +@app.route('/iiif/.json') def iiif(img_id: int): return getManifest(img_id) From 1a8c2d25f6346787a14ed69e8417328639813e81 Mon Sep 17 00:00:00 2001 From: BernhardKoschicek Date: Sat, 23 Sep 2023 08:34:38 +0200 Subject: [PATCH 020/102] added requests for image api data --- openatlas/views/file.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/openatlas/views/file.py b/openatlas/views/file.py index a7c40c3e3..6ac6f3869 100644 --- a/openatlas/views/file.py +++ b/openatlas/views/file.py @@ -3,6 +3,7 @@ from pathlib import Path from typing import Any, Union, Optional +import requests from flask import g, render_template, request, send_from_directory, url_for from flask_babel import lazy_gettext as _ from werkzeug.utils import redirect @@ -88,9 +89,14 @@ def convert_image_to_iiif(id_): subprocess.Popen(command, shell=True) -def getManifest(img_id): - entity = Entity.get_by_id(img_id) - path = request.url_root +def getManifest(id_): + entity = Entity.get_by_id(id_) + url_root = request.url_root + + # get metadata from the image api + req = requests.get( + f"{url_root}iiif/{app.config['IIIF_PREFIX']}/{id_}/info.json") + image_api = req.json() manifest = { "@context": "http://iiif.io/api/presentation/2/context.json", "@id": f"{request.base_url}", @@ -120,12 +126,12 @@ def getManifest(img_id): "@type": "oa:Annotation", "motivation": "sc:painting", "resource": { - "@id": f"{path}iiif/{img_id}/full/full/0/default.jpg", + "@id": f"{url_root}iiif/{id_}/full/full/0/default.jpg", "@type": "dctypes:Image", "format": "image/jpeg", "service": { "@context": "http://iiif.io/api/image/2/context.json", - "@id": f"{path}iiif/{img_id}", + "@id": f"{url_root}iiif/{id_}", "profile": [ "http://iiif.io/api/image/2/level1.json", {"formats": [ @@ -144,7 +150,8 @@ def getManifest(img_id): "rotationBy90s", "mirroring"], "maxWidth": 5000, - "maxHeight": 5000}]}, + "maxHeight": 5000} + ]}, "height": 450, "width": 600}, "on": "http://251a31df-761d-46df-85c3-66cb967b8a67"}], @@ -154,9 +161,9 @@ def getManifest(img_id): return manifest -@app.route('/iiif/.json') -def iiif(img_id: int): - return getManifest(img_id) +@app.route('/iiif_manifest/.json') +def iiif_manifest(id_: int): + return getManifest(id_) @app.route('/iiif/', methods=['GET']) From f107b496382531846213ad51220a865d216b4efc Mon Sep 17 00:00:00 2001 From: BernhardKoschicek Date: Mon, 25 Sep 2023 17:19:13 +0200 Subject: [PATCH 021/102] commented out the req --- openatlas/views/file.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openatlas/views/file.py b/openatlas/views/file.py index 6ac6f3869..0302d7718 100644 --- a/openatlas/views/file.py +++ b/openatlas/views/file.py @@ -94,9 +94,9 @@ def getManifest(id_): url_root = request.url_root # get metadata from the image api - req = requests.get( - f"{url_root}iiif/{app.config['IIIF_PREFIX']}/{id_}/info.json") - image_api = req.json() + # req = requests.get( + # f"{url_root}iiif/{app.config['IIIF_PREFIX']}/{id_}/") + # image_api = req.json() manifest = { "@context": "http://iiif.io/api/presentation/2/context.json", "@id": f"{request.base_url}", From e58ce66178b28d24279801094b7a886310ca7aac Mon Sep 17 00:00:00 2001 From: BernhardKoschicek Date: Mon, 25 Sep 2023 17:37:12 +0200 Subject: [PATCH 022/102] changed canvas with --- openatlas/views/file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openatlas/views/file.py b/openatlas/views/file.py index 0302d7718..0d443fd4b 100644 --- a/openatlas/views/file.py +++ b/openatlas/views/file.py @@ -119,7 +119,7 @@ def getManifest(id_): "@type": "sc:Canvas", "label": "Ring", "height": 450, - "width": "0", + "width": 600, "images": [{ "@context": "http://iiif.io/api/presentation/2/context.json", "@id": "http://a0a3ec3e-2084-4253-b0f9-a5f87645e15d", From b45d08a98b62bd4b93489bd8c5f16f020962a887 Mon Sep 17 00:00:00 2001 From: BernhardKoschicek Date: Mon, 25 Sep 2023 17:44:32 +0200 Subject: [PATCH 023/102] added cors to manifest route --- openatlas/views/file.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openatlas/views/file.py b/openatlas/views/file.py index 0d443fd4b..c94e7f142 100644 --- a/openatlas/views/file.py +++ b/openatlas/views/file.py @@ -1,3 +1,4 @@ +import json import os import subprocess from pathlib import Path @@ -6,6 +7,7 @@ import requests from flask import g, render_template, request, send_from_directory, url_for from flask_babel import lazy_gettext as _ +from flask_cors import cross_origin from werkzeug.utils import redirect from werkzeug.wrappers import Response @@ -162,8 +164,9 @@ def getManifest(id_): @app.route('/iiif_manifest/.json') +@cross_origin() def iiif_manifest(id_: int): - return getManifest(id_) + return json.dumps(getManifest(id_)) @app.route('/iiif/', methods=['GET']) From 7a85b4b0c37d1c532775b7ac9681b97e9214a9a8 Mon Sep 17 00:00:00 2001 From: BernhardKoschicek Date: Mon, 25 Sep 2023 17:48:23 +0200 Subject: [PATCH 024/102] try jsonify --- openatlas/views/file.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/openatlas/views/file.py b/openatlas/views/file.py index c94e7f142..25a654e84 100644 --- a/openatlas/views/file.py +++ b/openatlas/views/file.py @@ -1,11 +1,10 @@ -import json import os import subprocess from pathlib import Path from typing import Any, Union, Optional -import requests -from flask import g, render_template, request, send_from_directory, url_for +from flask import g, render_template, request, send_from_directory, url_for, \ + jsonify from flask_babel import lazy_gettext as _ from flask_cors import cross_origin from werkzeug.utils import redirect @@ -166,7 +165,7 @@ def getManifest(id_): @app.route('/iiif_manifest/.json') @cross_origin() def iiif_manifest(id_: int): - return json.dumps(getManifest(id_)) + return jsonify(getManifest(id_)) @app.route('/iiif/', methods=['GET']) From 35b86efd3f0381ce6c85cb0bb1160e5974ef5cc0 Mon Sep 17 00:00:00 2001 From: BernhardKoschicek Date: Mon, 25 Sep 2023 17:52:01 +0200 Subject: [PATCH 025/102] try with gzip --- openatlas/views/file.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/openatlas/views/file.py b/openatlas/views/file.py index 25a654e84..4ebc7924d 100644 --- a/openatlas/views/file.py +++ b/openatlas/views/file.py @@ -1,3 +1,5 @@ +import gzip +import json import os import subprocess from pathlib import Path @@ -165,7 +167,11 @@ def getManifest(id_): @app.route('/iiif_manifest/.json') @cross_origin() def iiif_manifest(id_: int): - return jsonify(getManifest(id_)) + content = gzip.compress(json.dumps(getManifest(id_)).encode('utf8'), 5) + response = jsonify(content) + response.headers['Content-length'] = len(content) + response.headers['Content-Encoding'] = 'gzip' + return response @app.route('/iiif/', methods=['GET']) From 9d6d95eb63f7e2adefedbc67559ac3c11e3b4eb6 Mon Sep 17 00:00:00 2001 From: BernhardKoschicek Date: Mon, 25 Sep 2023 17:55:15 +0200 Subject: [PATCH 026/102] try with gzip --- openatlas/views/file.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openatlas/views/file.py b/openatlas/views/file.py index 4ebc7924d..933ea9ae3 100644 --- a/openatlas/views/file.py +++ b/openatlas/views/file.py @@ -168,10 +168,10 @@ def getManifest(id_): @cross_origin() def iiif_manifest(id_: int): content = gzip.compress(json.dumps(getManifest(id_)).encode('utf8'), 5) - response = jsonify(content) + response = Response(content) response.headers['Content-length'] = len(content) response.headers['Content-Encoding'] = 'gzip' - return response + return jsonify(getManifest(id_)) @app.route('/iiif/', methods=['GET']) From 1945fe87f6059f9e7269d7c6932fb4e7ed405832 Mon Sep 17 00:00:00 2001 From: BernhardKoschicek Date: Mon, 25 Sep 2023 18:25:28 +0200 Subject: [PATCH 027/102] added image properties --- config/default.py | 3 ++- openatlas/views/file.py | 47 +++++++++++++++-------------------------- 2 files changed, 19 insertions(+), 31 deletions(-) diff --git a/config/default.py b/config/default.py index b3c99bbb8..376b8f18e 100644 --- a/config/default.py +++ b/config/default.py @@ -47,7 +47,8 @@ IIIF_ACTIVATE = False IIIF_DIR = '' -IIIF_PREFIX = '' +IIIF_PREFIX = '' # has to end with / +IIIF_SERVER = '' # For system checks WRITABLE_DIRS = [ diff --git a/openatlas/views/file.py b/openatlas/views/file.py index 933ea9ae3..fd1cc7613 100644 --- a/openatlas/views/file.py +++ b/openatlas/views/file.py @@ -5,6 +5,7 @@ from pathlib import Path from typing import Any, Union, Optional +import requests from flask import g, render_template, request, send_from_directory, url_for, \ jsonify from flask_babel import lazy_gettext as _ @@ -94,12 +95,13 @@ def convert_image_to_iiif(id_): def getManifest(id_): entity = Entity.get_by_id(id_) - url_root = request.url_root + url_root = app.config['IIIF_SERVER'] or request.url_root # get metadata from the image api - # req = requests.get( - # f"{url_root}iiif/{app.config['IIIF_PREFIX']}/{id_}/") - # image_api = req.json() + req = requests.get( + f"{url_root}iiif/{app.config['IIIF_PREFIX']}{id_}/info.json") + image_api = req.json() + print(image_api) manifest = { "@context": "http://iiif.io/api/presentation/2/context.json", "@id": f"{request.base_url}", @@ -120,9 +122,12 @@ def getManifest(id_): "canvases": [{ "@id": "http://251a31df-761d-46df-85c3-66cb967b8a67", "@type": "sc:Canvas", - "label": "Ring", + "label": entity.name, "height": 450, "width": 600, + "description": { + "@value": entity.description, + "@language": "en"}, "images": [{ "@context": "http://iiif.io/api/presentation/2/context.json", "@id": "http://a0a3ec3e-2084-4253-b0f9-a5f87645e15d", @@ -135,26 +140,8 @@ def getManifest(id_): "service": { "@context": "http://iiif.io/api/image/2/context.json", "@id": f"{url_root}iiif/{id_}", - "profile": [ - "http://iiif.io/api/image/2/level1.json", - {"formats": [ - "jpg"], - "qualities": [ - "native", - "color", - "gray", - "bitonal"], - "supports": [ - "regionByPct", - "regionSquare", - "sizeByForcedWh", - "sizeByWh", - "sizeAboveFull", - "rotationBy90s", - "mirroring"], - "maxWidth": 5000, - "maxHeight": 5000} - ]}, + "profile": image_api['profile'] + }, "height": 450, "width": 600}, "on": "http://251a31df-761d-46df-85c3-66cb967b8a67"}], @@ -164,13 +151,13 @@ def getManifest(id_): return manifest -@app.route('/iiif_manifest/.json') +@app.route('/iiif_manifest/') @cross_origin() def iiif_manifest(id_: int): - content = gzip.compress(json.dumps(getManifest(id_)).encode('utf8'), 5) - response = Response(content) - response.headers['Content-length'] = len(content) - response.headers['Content-Encoding'] = 'gzip' + # content = gzip.compress(json.dumps(getManifest(id_)).encode('utf8'), 5) + # response = Response(content) + # response.headers['Content-length'] = len(content) + # response.headers['Content-Encoding'] = 'gzip' return jsonify(getManifest(id_)) From 9ebbb9bff97e92997946f0b1689a01f2ed629c1c Mon Sep 17 00:00:00 2001 From: bkoschicek Date: Tue, 26 Sep 2023 15:57:29 +0200 Subject: [PATCH 028/102] moved to api and fixed some issue --- openatlas/api/endpoints/iiif.py | 83 +++++++++++++++++++++++++++++ openatlas/api/routes.py | 6 +++ openatlas/display/util.py | 11 ++++ openatlas/views/admin.py | 4 +- openatlas/views/file.py | 94 ++------------------------------- 5 files changed, 107 insertions(+), 91 deletions(-) create mode 100644 openatlas/api/endpoints/iiif.py diff --git a/openatlas/api/endpoints/iiif.py b/openatlas/api/endpoints/iiif.py new file mode 100644 index 000000000..c4459347f --- /dev/null +++ b/openatlas/api/endpoints/iiif.py @@ -0,0 +1,83 @@ +import requests +from flask import request, jsonify, Response +from flask_restful import Resource + +from api.resources.util import get_license_name +from models.entity import Entity +from openatlas import app + + +def getManifest(id_): + entity = Entity.get_by_id(id_) + url_root = app.config['IIIF_URL'] or f"{request.url_root}iiif/" + # get metadata from the image api + req = requests.get( + f"{url_root}{app.config['IIIF_PREFIX']}{id_}/info.json") + image_api = req.json() + iiif_id = f"{url_root}iiif/{id_}" + manifest = { + "@context": "http://iiif.io/api/presentation/2/context.json", + "@id": f"{request.base_url}", + "@type": "sc:Manifest", + "label": entity.name, + "metadata": [], + "description": [{ + "@value": entity.description, + "@language": "en"}], + "thumbnail": { + "@id": f"{iiif_id}/full/250,250/0/default.jpg", + "service": { + "@context": "http://iiif.io/api/image/2/context.json", + "@id": iiif_id, + "profile": "http://iiif.io/api/image/2/level1.json" + }}, + "license": get_license_name(entity), + "attribution": "By OpenAtlas", + "sequences": [{ + "@id": "http://c8b09ce6-df6d-4d5e-9eba-17507dc5c185", + "@type": "sc:Sequence", + "label": [{ + "@value": "Normal Sequence", + "@language": "en"}], + "canvases": [{ + "@id": "http://251a31df-761d-46df-85c3-66cb967b8a67", + "@type": "sc:Canvas", + "label": entity.name, + "height": image_api['height'], + "width": image_api['width'], + "description": { + "@value": entity.description, + "@language": "en"}, + "images": [{ + "@context": + "http://iiif.io/api/presentation/2/context.json", + "@id": "http://a0a3ec3e-2084-4253-b0f9-a5f87645e15d", + "@type": "oa:Annotation", + "motivation": "sc:painting", + "resource": { + "@id": f"{iiif_id}/full/full/0/default.jpg", + "@type": "dctypes:Image", + "format": "image/jpeg", + "service": { + "@context": + "http://iiif.io/api/image/2/context.json", + "@id": iiif_id, + "profile": image_api['profile'] + }, + "height": image_api['height'], + "width": image_api['width']}, + "on": "http://251a31df-761d-46df-85c3-66cb967b8a67"}], + "related": ""}]}], + "structures": []} + return manifest + + +class IIIFManifest(Resource): + @staticmethod + def get(id_: int) -> Response: + # content = gzip.compress(json.dumps(getManifest(id_)).encode( + # 'utf8'), 5) + # response = Response(content) + # response.headers['Content-length'] = len(content) + # response.headers['Content-Encoding'] = 'gzip' + return jsonify(getManifest(id_)) diff --git a/openatlas/api/routes.py b/openatlas/api/routes.py index 7228bbfc3..16aed8c58 100644 --- a/openatlas/api/routes.py +++ b/openatlas/api/routes.py @@ -1,5 +1,6 @@ from flask_restful import Api +from api.endpoints.iiif import IIIFManifest from openatlas.api.endpoints.content import ClassMapping, \ GetContent, SystemClassCount from openatlas.api.endpoints.special import GetGeometricEntities, \ @@ -93,3 +94,8 @@ def add_routes_v03(api: Api) -> None: DisplayImage, '/display/', endpoint='display') + + api.add_resource( + IIIFManifest, + '/iiif_manifest/', + endpoint='iiif_manifest') diff --git a/openatlas/display/util.py b/openatlas/display/util.py index 2b4943a22..865ebc7ba 100644 --- a/openatlas/display/util.py +++ b/openatlas/display/util.py @@ -4,6 +4,7 @@ import os import re import smtplib +import subprocess from datetime import datetime, timedelta from email.header import Header from email.mime.text import MIMEText @@ -768,3 +769,13 @@ def check_iiif_file_exist(id_: int) -> bool: file_to_check = (Path(app.config['IIIF_DIR']) / app.config['IIIF_PREFIX'] / str(id_)) return file_to_check.is_file() + + +def convert_image_to_iiif(id_): + path = Path(app.config['IIIF_DIR']) / app.config['IIIF_PREFIX'] / str(id_) + vips = "vips" if os.name == 'posix' else "vips.exe" + command = \ + (f"{vips} tiffsave {get_file_path(id_)} {path} " + f"--tile --pyramid --compression deflate " + f"--tile-width 256 --tile-height 256") + subprocess.Popen(command, shell=True) diff --git a/openatlas/views/admin.py b/openatlas/views/admin.py index 1240d7914..d4342f988 100644 --- a/openatlas/views/admin.py +++ b/openatlas/views/admin.py @@ -38,7 +38,7 @@ from openatlas.models.settings import Settings from openatlas.models.type import Type from openatlas.models.user import User -from openatlas.views.file import convert_image_to_iiif +from display.util import convert_image_to_iiif @app.route('/admin', methods=['GET', 'POST'], strict_slashes=False) @@ -781,7 +781,7 @@ def get_disk_space_info() -> Optional[dict[str, Any]]: @app.route('/admin/admin_convert_all_to_iiif') -@required_group('admin') +@required_group('manager') def admin_convert_all_to_iiif() -> Response: for entity in Entity.get_by_class('file'): if entity.id in g.file_stats \ diff --git a/openatlas/views/file.py b/openatlas/views/file.py index fd1cc7613..0bf505448 100644 --- a/openatlas/views/file.py +++ b/openatlas/views/file.py @@ -1,20 +1,14 @@ -import gzip -import json -import os -import subprocess -from pathlib import Path from typing import Any, Union, Optional -import requests from flask import g, render_template, request, send_from_directory, url_for, \ - jsonify + redirect from flask_babel import lazy_gettext as _ -from flask_cors import cross_origin from werkzeug.utils import redirect from werkzeug.wrappers import Response +from display.util import convert_image_to_iiif, required_group from openatlas import app -from openatlas.display.util import required_group, get_file_path +from openatlas.display.util import required_group from openatlas.forms.form import get_table_form from openatlas.models.entity import Entity @@ -76,93 +70,15 @@ def file_add(id_: int, view: str) -> Union[str, Response]: f"{_('link')} {_(view)}"]) -@app.route('/file/iiif/', methods=['GET']) +@app.route('/file/convert_iiif/', methods=['GET']) @required_group('contributor') def make_iiif_available(id_: int): convert_image_to_iiif(id_) return redirect(url_for('view', id_=id_)) -def convert_image_to_iiif(id_): - path = Path(app.config['IIIF_DIR']) / app.config['IIIF_PREFIX'] / str(id_) - vips = "vips" if os.name == 'posix' else "vips.exe" - command = \ - (f"{vips} tiffsave {get_file_path(id_)} {path} " - f"--tile --pyramid --compression deflate " - f"--tile-width 256 --tile-height 256") - subprocess.Popen(command, shell=True) - - -def getManifest(id_): - entity = Entity.get_by_id(id_) - url_root = app.config['IIIF_SERVER'] or request.url_root - - # get metadata from the image api - req = requests.get( - f"{url_root}iiif/{app.config['IIIF_PREFIX']}{id_}/info.json") - image_api = req.json() - print(image_api) - manifest = { - "@context": "http://iiif.io/api/presentation/2/context.json", - "@id": f"{request.base_url}", - "@type": "sc:Manifest", - "label": entity.name, - "metadata": [], - "description": [{ - "@value": entity.description, - "@language": "en"}], - "license": "https://creativecommons.org/licenses/by/3.0/", - "attribution": "By OpenAtlas", - "sequences": [{ - "@id": "http://c8b09ce6-df6d-4d5e-9eba-17507dc5c185", - "@type": "sc:Sequence", - "label": [{ - "@value": "Normal Sequence", - "@language": "en"}], - "canvases": [{ - "@id": "http://251a31df-761d-46df-85c3-66cb967b8a67", - "@type": "sc:Canvas", - "label": entity.name, - "height": 450, - "width": 600, - "description": { - "@value": entity.description, - "@language": "en"}, - "images": [{ - "@context": "http://iiif.io/api/presentation/2/context.json", - "@id": "http://a0a3ec3e-2084-4253-b0f9-a5f87645e15d", - "@type": "oa:Annotation", - "motivation": "sc:painting", - "resource": { - "@id": f"{url_root}iiif/{id_}/full/full/0/default.jpg", - "@type": "dctypes:Image", - "format": "image/jpeg", - "service": { - "@context": "http://iiif.io/api/image/2/context.json", - "@id": f"{url_root}iiif/{id_}", - "profile": image_api['profile'] - }, - "height": 450, - "width": 600}, - "on": "http://251a31df-761d-46df-85c3-66cb967b8a67"}], - "related": ""}]}], - "structures": []} - - return manifest - - -@app.route('/iiif_manifest/') -@cross_origin() -def iiif_manifest(id_: int): - # content = gzip.compress(json.dumps(getManifest(id_)).encode('utf8'), 5) - # response = Response(content) - # response.headers['Content-length'] = len(content) - # response.headers['Content-Encoding'] = 'gzip' - return jsonify(getManifest(id_)) - - @app.route('/iiif/', methods=['GET']) @app.route('/iiif//', methods=['GET']) @required_group('contributor') def view_iiif(id_: int, prefix: Optional[str] = None): - return redirect(url_for('view', id_=id_)) + return redirect(url_for('api.iiif_manifest', id_=id_)) From 520776e1cbeb5044292bb1201b0eeca885bf674e Mon Sep 17 00:00:00 2001 From: bkoschicek Date: Tue, 26 Sep 2023 15:59:55 +0200 Subject: [PATCH 029/102] fixed imports --- openatlas/api/endpoints/iiif.py | 2 +- openatlas/api/routes.py | 2 +- openatlas/views/file.py | 5 ++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/openatlas/api/endpoints/iiif.py b/openatlas/api/endpoints/iiif.py index c4459347f..e9a8c7797 100644 --- a/openatlas/api/endpoints/iiif.py +++ b/openatlas/api/endpoints/iiif.py @@ -2,7 +2,7 @@ from flask import request, jsonify, Response from flask_restful import Resource -from api.resources.util import get_license_name +from openatlas.api.resources.util import get_license_name from models.entity import Entity from openatlas import app diff --git a/openatlas/api/routes.py b/openatlas/api/routes.py index 16aed8c58..a0d86f492 100644 --- a/openatlas/api/routes.py +++ b/openatlas/api/routes.py @@ -1,6 +1,6 @@ from flask_restful import Api -from api.endpoints.iiif import IIIFManifest +from openatlas.api.endpoints.iiif import IIIFManifest from openatlas.api.endpoints.content import ClassMapping, \ GetContent, SystemClassCount from openatlas.api.endpoints.special import GetGeometricEntities, \ diff --git a/openatlas/views/file.py b/openatlas/views/file.py index 0bf505448..e943e5bd0 100644 --- a/openatlas/views/file.py +++ b/openatlas/views/file.py @@ -1,12 +1,11 @@ from typing import Any, Union, Optional -from flask import g, render_template, request, send_from_directory, url_for, \ - redirect +from flask import g, render_template, request, send_from_directory, url_for from flask_babel import lazy_gettext as _ from werkzeug.utils import redirect from werkzeug.wrappers import Response -from display.util import convert_image_to_iiif, required_group +from display.util import convert_image_to_iiif from openatlas import app from openatlas.display.util import required_group from openatlas.forms.form import get_table_form From 702033fb5e8903d7ca0d3812d031b2d481b25115 Mon Sep 17 00:00:00 2001 From: bkoschicek Date: Tue, 26 Sep 2023 16:01:19 +0200 Subject: [PATCH 030/102] fixed imports --- openatlas/api/endpoints/iiif.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openatlas/api/endpoints/iiif.py b/openatlas/api/endpoints/iiif.py index e9a8c7797..f691c9aed 100644 --- a/openatlas/api/endpoints/iiif.py +++ b/openatlas/api/endpoints/iiif.py @@ -3,7 +3,7 @@ from flask_restful import Resource from openatlas.api.resources.util import get_license_name -from models.entity import Entity +from openatlas.models.entity import Entity from openatlas import app From fdb1ea905af89b590443b1a90c3a0029248f2681 Mon Sep 17 00:00:00 2001 From: bkoschicek Date: Tue, 26 Sep 2023 16:02:24 +0200 Subject: [PATCH 031/102] fixed imports --- openatlas/views/admin.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openatlas/views/admin.py b/openatlas/views/admin.py index d4342f988..e4da68887 100644 --- a/openatlas/views/admin.py +++ b/openatlas/views/admin.py @@ -25,7 +25,7 @@ from openatlas.display.util import ( button, convert_size, display_form, display_info, format_date, get_file_path, is_authorized, link, manual, required_group, sanitize, - send_mail, uc_first) + send_mail, uc_first, convert_image_to_iiif) from openatlas.forms.field import SubmitField from openatlas.forms.setting import ( ApiForm, ContentForm, FilesForm, GeneralForm, LogForm, MailForm, MapForm, @@ -38,7 +38,6 @@ from openatlas.models.settings import Settings from openatlas.models.type import Type from openatlas.models.user import User -from display.util import convert_image_to_iiif @app.route('/admin', methods=['GET', 'POST'], strict_slashes=False) From b3bd11419c08c98a81aebe0789f96baad84d8084 Mon Sep 17 00:00:00 2001 From: bkoschicek Date: Tue, 26 Sep 2023 16:03:17 +0200 Subject: [PATCH 032/102] fixed imports --- openatlas/views/file.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openatlas/views/file.py b/openatlas/views/file.py index e943e5bd0..3fdceac18 100644 --- a/openatlas/views/file.py +++ b/openatlas/views/file.py @@ -5,9 +5,8 @@ from werkzeug.utils import redirect from werkzeug.wrappers import Response -from display.util import convert_image_to_iiif from openatlas import app -from openatlas.display.util import required_group +from openatlas.display.util import required_group, convert_image_to_iiif from openatlas.forms.form import get_table_form from openatlas.models.entity import Entity From f557d86df484371ddb4e02f5edc61b8293ea4397 Mon Sep 17 00:00:00 2001 From: bkoschicek Date: Tue, 26 Sep 2023 16:08:33 +0200 Subject: [PATCH 033/102] trying to fix manifest --- openatlas/api/endpoints/iiif.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/openatlas/api/endpoints/iiif.py b/openatlas/api/endpoints/iiif.py index f691c9aed..326977d1b 100644 --- a/openatlas/api/endpoints/iiif.py +++ b/openatlas/api/endpoints/iiif.py @@ -24,13 +24,6 @@ def getManifest(id_): "description": [{ "@value": entity.description, "@language": "en"}], - "thumbnail": { - "@id": f"{iiif_id}/full/250,250/0/default.jpg", - "service": { - "@context": "http://iiif.io/api/image/2/context.json", - "@id": iiif_id, - "profile": "http://iiif.io/api/image/2/level1.json" - }}, "license": get_license_name(entity), "attribution": "By OpenAtlas", "sequences": [{ @@ -43,8 +36,8 @@ def getManifest(id_): "@id": "http://251a31df-761d-46df-85c3-66cb967b8a67", "@type": "sc:Canvas", "label": entity.name, - "height": image_api['height'], - "width": image_api['width'], + "height": int(image_api['height']), + "width": int(image_api['width']), "description": { "@value": entity.description, "@language": "en"}, @@ -64,8 +57,8 @@ def getManifest(id_): "@id": iiif_id, "profile": image_api['profile'] }, - "height": image_api['height'], - "width": image_api['width']}, + "height": int(image_api['height']), + "width": int(image_api['width'])}, "on": "http://251a31df-761d-46df-85c3-66cb967b8a67"}], "related": ""}]}], "structures": []} From 163bfa16ab6ade8d11004a078e76638d97c98870 Mon Sep 17 00:00:00 2001 From: bkoschicek Date: Tue, 26 Sep 2023 16:10:48 +0200 Subject: [PATCH 034/102] fixed iiif_id --- openatlas/api/endpoints/iiif.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openatlas/api/endpoints/iiif.py b/openatlas/api/endpoints/iiif.py index 326977d1b..fcfb06d43 100644 --- a/openatlas/api/endpoints/iiif.py +++ b/openatlas/api/endpoints/iiif.py @@ -14,7 +14,7 @@ def getManifest(id_): req = requests.get( f"{url_root}{app.config['IIIF_PREFIX']}{id_}/info.json") image_api = req.json() - iiif_id = f"{url_root}iiif/{id_}" + iiif_id = f"{url_root}{id_}" manifest = { "@context": "http://iiif.io/api/presentation/2/context.json", "@id": f"{request.base_url}", From 3013f977cac2e5d139ae0fcdd46832042c851282 Mon Sep 17 00:00:00 2001 From: bkoschicek Date: Tue, 26 Sep 2023 16:26:40 +0200 Subject: [PATCH 035/102] fixed licence --- openatlas/api/endpoints/iiif.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openatlas/api/endpoints/iiif.py b/openatlas/api/endpoints/iiif.py index fcfb06d43..ded2f7538 100644 --- a/openatlas/api/endpoints/iiif.py +++ b/openatlas/api/endpoints/iiif.py @@ -2,13 +2,13 @@ from flask import request, jsonify, Response from flask_restful import Resource +from openatlas.api.resources.model_mapper import get_entity_by_id from openatlas.api.resources.util import get_license_name -from openatlas.models.entity import Entity from openatlas import app def getManifest(id_): - entity = Entity.get_by_id(id_) + entity = get_entity_by_id(id_) url_root = app.config['IIIF_URL'] or f"{request.url_root}iiif/" # get metadata from the image api req = requests.get( From 0ac859bfa1dc89c0829d6397924bf6c7f2d2488a Mon Sep 17 00:00:00 2001 From: bkoschicek Date: Tue, 26 Sep 2023 16:28:12 +0200 Subject: [PATCH 036/102] fixed licence --- openatlas/api/endpoints/iiif.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openatlas/api/endpoints/iiif.py b/openatlas/api/endpoints/iiif.py index ded2f7538..2d16851cb 100644 --- a/openatlas/api/endpoints/iiif.py +++ b/openatlas/api/endpoints/iiif.py @@ -24,7 +24,7 @@ def getManifest(id_): "description": [{ "@value": entity.description, "@language": "en"}], - "license": get_license_name(entity), + "license": "http://rightsstatements.org/vocab/NoC-NC/1.0/", "attribution": "By OpenAtlas", "sequences": [{ "@id": "http://c8b09ce6-df6d-4d5e-9eba-17507dc5c185", From fbd73a03e3e245a68a4e7f3bcad6001508aefe95 Mon Sep 17 00:00:00 2001 From: bkoschicek Date: Tue, 26 Sep 2023 16:33:04 +0200 Subject: [PATCH 037/102] revert manifest --- openatlas/api/endpoints/iiif.py | 83 +++++++++++++++++++++++++++------ 1 file changed, 69 insertions(+), 14 deletions(-) diff --git a/openatlas/api/endpoints/iiif.py b/openatlas/api/endpoints/iiif.py index 2d16851cb..a8f3fc043 100644 --- a/openatlas/api/endpoints/iiif.py +++ b/openatlas/api/endpoints/iiif.py @@ -8,13 +8,69 @@ def getManifest(id_): + # entity = get_entity_by_id(id_) + # url_root = app.config['IIIF_URL'] or f"{request.url_root}iiif/" + # # get metadata from the image api + # req = requests.get( + # f"{url_root}{app.config['IIIF_PREFIX']}{id_}/info.json") + # image_api = req.json() + # iiif_id = f"{url_root}{id_}" + # manifest = { + # "@context": "http://iiif.io/api/presentation/2/context.json", + # "@id": f"{request.base_url}", + # "@type": "sc:Manifest", + # "label": entity.name, + # "metadata": [], + # "description": [{ + # "@value": entity.description, + # "@language": "en"}], + # "license": "http://rightsstatements.org/vocab/NoC-NC/1.0/", + # "attribution": "By OpenAtlas", + # "sequences": [{ + # "@id": "http://c8b09ce6-df6d-4d5e-9eba-17507dc5c185", + # "@type": "sc:Sequence", + # "label": [{ + # "@value": "Normal Sequence", + # "@language": "en"}], + # "canvases": [{ + # "@id": "http://251a31df-761d-46df-85c3-66cb967b8a67", + # "@type": "sc:Canvas", + # "label": entity.name, + # "height": int(image_api['height']), + # "width": int(image_api['width']), + # "description": { + # "@value": entity.description, + # "@language": "en"}, + # "images": [{ + # "@context": + # "http://iiif.io/api/presentation/2/context.json", + # "@id": "http://a0a3ec3e-2084-4253-b0f9-a5f87645e15d", + # "@type": "oa:Annotation", + # "motivation": "sc:painting", + # "resource": { + # "@id": f"{iiif_id}/full/full/0/default.jpg", + # "@type": "dctypes:Image", + # "format": "image/jpeg", + # "service": { + # "@context": + # "http://iiif.io/api/image/2/context.json", + # "@id": iiif_id, + # "profile": image_api['profile'] + # }, + # "height": int(image_api['height']), + # "width": int(image_api['width'])}, + # "on": "http://251a31df-761d-46df-85c3-66cb967b8a67"}], + # "related": ""}]}], + # "structures": []} + # entity = get_entity_by_id(id_) - url_root = app.config['IIIF_URL'] or f"{request.url_root}iiif/" + url_root = app.config['IIIF_SERVER'] or request.url_root + # get metadata from the image api req = requests.get( - f"{url_root}{app.config['IIIF_PREFIX']}{id_}/info.json") + f"{url_root}iiif/{app.config['IIIF_PREFIX']}{id_}/info.json") image_api = req.json() - iiif_id = f"{url_root}{id_}" + print(image_api) manifest = { "@context": "http://iiif.io/api/presentation/2/context.json", "@id": f"{request.base_url}", @@ -24,7 +80,7 @@ def getManifest(id_): "description": [{ "@value": entity.description, "@language": "en"}], - "license": "http://rightsstatements.org/vocab/NoC-NC/1.0/", + "license": "https://creativecommons.org/licenses/by/3.0/", "attribution": "By OpenAtlas", "sequences": [{ "@id": "http://c8b09ce6-df6d-4d5e-9eba-17507dc5c185", @@ -36,32 +92,31 @@ def getManifest(id_): "@id": "http://251a31df-761d-46df-85c3-66cb967b8a67", "@type": "sc:Canvas", "label": entity.name, - "height": int(image_api['height']), - "width": int(image_api['width']), + "height": 450, + "width": 600, "description": { "@value": entity.description, "@language": "en"}, "images": [{ - "@context": - "http://iiif.io/api/presentation/2/context.json", + "@context": "http://iiif.io/api/presentation/2/context.json", "@id": "http://a0a3ec3e-2084-4253-b0f9-a5f87645e15d", "@type": "oa:Annotation", "motivation": "sc:painting", "resource": { - "@id": f"{iiif_id}/full/full/0/default.jpg", + "@id": f"{url_root}iiif/{id_}/full/full/0/default.jpg", "@type": "dctypes:Image", "format": "image/jpeg", "service": { - "@context": - "http://iiif.io/api/image/2/context.json", - "@id": iiif_id, + "@context": "http://iiif.io/api/image/2/context.json", + "@id": f"{url_root}iiif/{id_}", "profile": image_api['profile'] }, - "height": int(image_api['height']), - "width": int(image_api['width'])}, + "height": 450, + "width": 600}, "on": "http://251a31df-761d-46df-85c3-66cb967b8a67"}], "related": ""}]}], "structures": []} + return manifest From e962f1b9b336b37e82661fee82ac29b680b55129 Mon Sep 17 00:00:00 2001 From: bkoschicek Date: Tue, 26 Sep 2023 16:38:57 +0200 Subject: [PATCH 038/102] trying to get host with subdomain --- openatlas/api/endpoints/iiif.py | 83 ++++++--------------------------- 1 file changed, 15 insertions(+), 68 deletions(-) diff --git a/openatlas/api/endpoints/iiif.py b/openatlas/api/endpoints/iiif.py index a8f3fc043..4acb82f30 100644 --- a/openatlas/api/endpoints/iiif.py +++ b/openatlas/api/endpoints/iiif.py @@ -8,69 +8,13 @@ def getManifest(id_): - # entity = get_entity_by_id(id_) - # url_root = app.config['IIIF_URL'] or f"{request.url_root}iiif/" - # # get metadata from the image api - # req = requests.get( - # f"{url_root}{app.config['IIIF_PREFIX']}{id_}/info.json") - # image_api = req.json() - # iiif_id = f"{url_root}{id_}" - # manifest = { - # "@context": "http://iiif.io/api/presentation/2/context.json", - # "@id": f"{request.base_url}", - # "@type": "sc:Manifest", - # "label": entity.name, - # "metadata": [], - # "description": [{ - # "@value": entity.description, - # "@language": "en"}], - # "license": "http://rightsstatements.org/vocab/NoC-NC/1.0/", - # "attribution": "By OpenAtlas", - # "sequences": [{ - # "@id": "http://c8b09ce6-df6d-4d5e-9eba-17507dc5c185", - # "@type": "sc:Sequence", - # "label": [{ - # "@value": "Normal Sequence", - # "@language": "en"}], - # "canvases": [{ - # "@id": "http://251a31df-761d-46df-85c3-66cb967b8a67", - # "@type": "sc:Canvas", - # "label": entity.name, - # "height": int(image_api['height']), - # "width": int(image_api['width']), - # "description": { - # "@value": entity.description, - # "@language": "en"}, - # "images": [{ - # "@context": - # "http://iiif.io/api/presentation/2/context.json", - # "@id": "http://a0a3ec3e-2084-4253-b0f9-a5f87645e15d", - # "@type": "oa:Annotation", - # "motivation": "sc:painting", - # "resource": { - # "@id": f"{iiif_id}/full/full/0/default.jpg", - # "@type": "dctypes:Image", - # "format": "image/jpeg", - # "service": { - # "@context": - # "http://iiif.io/api/image/2/context.json", - # "@id": iiif_id, - # "profile": image_api['profile'] - # }, - # "height": int(image_api['height']), - # "width": int(image_api['width'])}, - # "on": "http://251a31df-761d-46df-85c3-66cb967b8a67"}], - # "related": ""}]}], - # "structures": []} - # entity = get_entity_by_id(id_) - url_root = app.config['IIIF_SERVER'] or request.url_root - + url_root = app.config['IIIF_URL'] or f"{request.headers.get('Host')}iiif/" # get metadata from the image api req = requests.get( - f"{url_root}iiif/{app.config['IIIF_PREFIX']}{id_}/info.json") + f"{url_root}{app.config['IIIF_PREFIX']}{id_}/info.json") image_api = req.json() - print(image_api) + iiif_id = f"{url_root}{id_}" manifest = { "@context": "http://iiif.io/api/presentation/2/context.json", "@id": f"{request.base_url}", @@ -80,7 +24,7 @@ def getManifest(id_): "description": [{ "@value": entity.description, "@language": "en"}], - "license": "https://creativecommons.org/licenses/by/3.0/", + "license": get_license_name(entity), "attribution": "By OpenAtlas", "sequences": [{ "@id": "http://c8b09ce6-df6d-4d5e-9eba-17507dc5c185", @@ -92,31 +36,34 @@ def getManifest(id_): "@id": "http://251a31df-761d-46df-85c3-66cb967b8a67", "@type": "sc:Canvas", "label": entity.name, - "height": 450, - "width": 600, + "height": int(image_api['height']), + "width": int(image_api['width']), "description": { "@value": entity.description, "@language": "en"}, "images": [{ - "@context": "http://iiif.io/api/presentation/2/context.json", + "@context": + "http://iiif.io/api/presentation/2/context.json", "@id": "http://a0a3ec3e-2084-4253-b0f9-a5f87645e15d", "@type": "oa:Annotation", "motivation": "sc:painting", "resource": { - "@id": f"{url_root}iiif/{id_}/full/full/0/default.jpg", + "@id": f"{iiif_id}/full/full/0/default.jpg", "@type": "dctypes:Image", "format": "image/jpeg", "service": { - "@context": "http://iiif.io/api/image/2/context.json", - "@id": f"{url_root}iiif/{id_}", + "@context": + "http://iiif.io/api/image/2/context.json", + "@id": iiif_id, "profile": image_api['profile'] }, - "height": 450, - "width": 600}, + "height": int(image_api['height']), + "width": int(image_api['width'])}, "on": "http://251a31df-761d-46df-85c3-66cb967b8a67"}], "related": ""}]}], "structures": []} + return manifest From fc9563fbd58e6c3ba55a59fe17e12b79e0d5aabc Mon Sep 17 00:00:00 2001 From: bkoschicek Date: Tue, 26 Sep 2023 16:40:39 +0200 Subject: [PATCH 039/102] revert changes --- openatlas/api/endpoints/iiif.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openatlas/api/endpoints/iiif.py b/openatlas/api/endpoints/iiif.py index 4acb82f30..0aafa6d8c 100644 --- a/openatlas/api/endpoints/iiif.py +++ b/openatlas/api/endpoints/iiif.py @@ -9,7 +9,7 @@ def getManifest(id_): entity = get_entity_by_id(id_) - url_root = app.config['IIIF_URL'] or f"{request.headers.get('Host')}iiif/" + url_root = app.config['IIIF_URL'] or f"{request.url_root}iiif/" # get metadata from the image api req = requests.get( f"{url_root}{app.config['IIIF_PREFIX']}{id_}/info.json") From ca93f6bf15efa9ea02e2e1b21c755de1f2c7b3b8 Mon Sep 17 00:00:00 2001 From: bkoschicek Date: Tue, 26 Sep 2023 16:42:13 +0200 Subject: [PATCH 040/102] fixed config --- config/default.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/default.py b/config/default.py index 376b8f18e..529e65053 100644 --- a/config/default.py +++ b/config/default.py @@ -48,7 +48,7 @@ IIIF_ACTIVATE = False IIIF_DIR = '' IIIF_PREFIX = '' # has to end with / -IIIF_SERVER = '' +IIIF_URL = '' # For system checks WRITABLE_DIRS = [ From f8db20f54c331aa17b3aaf922e90fad274133d0a Mon Sep 17 00:00:00 2001 From: bkoschicek Date: Tue, 26 Sep 2023 16:44:45 +0200 Subject: [PATCH 041/102] removed int for w and h --- openatlas/api/endpoints/iiif.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openatlas/api/endpoints/iiif.py b/openatlas/api/endpoints/iiif.py index 0aafa6d8c..b3e2c9578 100644 --- a/openatlas/api/endpoints/iiif.py +++ b/openatlas/api/endpoints/iiif.py @@ -36,8 +36,8 @@ def getManifest(id_): "@id": "http://251a31df-761d-46df-85c3-66cb967b8a67", "@type": "sc:Canvas", "label": entity.name, - "height": int(image_api['height']), - "width": int(image_api['width']), + "height": image_api['height'], + "width": image_api['width'], "description": { "@value": entity.description, "@language": "en"}, @@ -57,8 +57,8 @@ def getManifest(id_): "@id": iiif_id, "profile": image_api['profile'] }, - "height": int(image_api['height']), - "width": int(image_api['width'])}, + "height": image_api['height'], + "width": image_api['width']}, "on": "http://251a31df-761d-46df-85c3-66cb967b8a67"}], "related": ""}]}], "structures": []} From d6a7a8f322334c04cc045e6b915265410788aa12 Mon Sep 17 00:00:00 2001 From: bkoschicek Date: Tue, 26 Sep 2023 17:01:50 +0200 Subject: [PATCH 042/102] refactor --- openatlas/api/endpoints/iiif.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/openatlas/api/endpoints/iiif.py b/openatlas/api/endpoints/iiif.py index b3e2c9578..f4bb04d56 100644 --- a/openatlas/api/endpoints/iiif.py +++ b/openatlas/api/endpoints/iiif.py @@ -1,3 +1,5 @@ +from typing import Any + import requests from flask import request, jsonify, Response from flask_restful import Resource @@ -7,7 +9,7 @@ from openatlas import app -def getManifest(id_): +def get_manifest(id_: int) -> dict[str, Any]: entity = get_entity_by_id(id_) url_root = app.config['IIIF_URL'] or f"{request.url_root}iiif/" # get metadata from the image api @@ -55,24 +57,17 @@ def getManifest(id_): "@context": "http://iiif.io/api/image/2/context.json", "@id": iiif_id, - "profile": image_api['profile'] - }, + "profile": image_api['profile']}, "height": image_api['height'], "width": image_api['width']}, "on": "http://251a31df-761d-46df-85c3-66cb967b8a67"}], "related": ""}]}], "structures": []} - return manifest class IIIFManifest(Resource): @staticmethod def get(id_: int) -> Response: - # content = gzip.compress(json.dumps(getManifest(id_)).encode( - # 'utf8'), 5) - # response = Response(content) - # response.headers['Content-length'] = len(content) - # response.headers['Content-Encoding'] = 'gzip' - return jsonify(getManifest(id_)) + return jsonify(get_manifest(id_)) From 0a2056d2b3c89908c3509420ab5572902a9f4e1d Mon Sep 17 00:00:00 2001 From: bkoschicek Date: Wed, 27 Sep 2023 13:47:46 +0200 Subject: [PATCH 043/102] removed wrong path --- openatlas/api/endpoints/iiif.py | 1 + openatlas/views/file.py | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openatlas/api/endpoints/iiif.py b/openatlas/api/endpoints/iiif.py index f4bb04d56..834445d77 100644 --- a/openatlas/api/endpoints/iiif.py +++ b/openatlas/api/endpoints/iiif.py @@ -13,6 +13,7 @@ def get_manifest(id_: int) -> dict[str, Any]: entity = get_entity_by_id(id_) url_root = app.config['IIIF_URL'] or f"{request.url_root}iiif/" # get metadata from the image api + print(f"{url_root}{app.config['IIIF_PREFIX']}{id_}/info.json") req = requests.get( f"{url_root}{app.config['IIIF_PREFIX']}{id_}/info.json") image_api = req.json() diff --git a/openatlas/views/file.py b/openatlas/views/file.py index 3fdceac18..40b99586f 100644 --- a/openatlas/views/file.py +++ b/openatlas/views/file.py @@ -76,7 +76,6 @@ def make_iiif_available(id_: int): @app.route('/iiif/', methods=['GET']) -@app.route('/iiif//', methods=['GET']) @required_group('contributor') -def view_iiif(id_: int, prefix: Optional[str] = None): +def view_iiif(id_: int): return redirect(url_for('api.iiif_manifest', id_=id_)) From be0660ee7872d09ff40835b4162ddcba635d70b4 Mon Sep 17 00:00:00 2001 From: bkoschicek Date: Wed, 27 Sep 2023 13:49:18 +0200 Subject: [PATCH 044/102] cleaned url_for --- openatlas/display/display.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/openatlas/display/display.py b/openatlas/display/display.py index 9ae37da28..33610e195 100644 --- a/openatlas/display/display.py +++ b/openatlas/display/display.py @@ -82,9 +82,7 @@ def add_button_others(self) -> None: if check_iiif_file_exist(self.entity.id): self.buttons.append(button( _('iiif'), - url_for('view_iiif', - prefix=app.config['IIIF_PREFIX'], - id_=self.entity.id))) + url_for('view_iiif', id_=self.entity.id))) else: self.buttons.append(button( _('make_iiif_available'), From 11085a7b1b6eca0bb68bba861551de6606570dfb Mon Sep 17 00:00:00 2001 From: bkoschicek Date: Wed, 27 Sep 2023 13:54:26 +0200 Subject: [PATCH 045/102] renamed view path --- openatlas/views/file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openatlas/views/file.py b/openatlas/views/file.py index 40b99586f..f6360c4a0 100644 --- a/openatlas/views/file.py +++ b/openatlas/views/file.py @@ -75,7 +75,7 @@ def make_iiif_available(id_: int): return redirect(url_for('view', id_=id_)) -@app.route('/iiif/', methods=['GET']) +@app.route('/view_iiif/', methods=['GET']) @required_group('contributor') def view_iiif(id_: int): return redirect(url_for('api.iiif_manifest', id_=id_)) From 7ee41d83db033aad1009eccaa2af0a26e0754912 Mon Sep 17 00:00:00 2001 From: bkoschicek Date: Wed, 27 Sep 2023 15:29:35 +0200 Subject: [PATCH 046/102] implemented mirandor --- openatlas/api/endpoints/iiif.py | 1 - openatlas/static/setup.py | 1 + openatlas/templates/entity/view.html | 5 ++++ openatlas/templates/iiif.html | 35 ++++++++++++++++++++++++++++ openatlas/views/file.py | 7 ++++-- 5 files changed, 46 insertions(+), 3 deletions(-) create mode 100644 openatlas/templates/iiif.html diff --git a/openatlas/api/endpoints/iiif.py b/openatlas/api/endpoints/iiif.py index 834445d77..f4bb04d56 100644 --- a/openatlas/api/endpoints/iiif.py +++ b/openatlas/api/endpoints/iiif.py @@ -13,7 +13,6 @@ def get_manifest(id_: int) -> dict[str, Any]: entity = get_entity_by_id(id_) url_root = app.config['IIIF_URL'] or f"{request.url_root}iiif/" # get metadata from the image api - print(f"{url_root}{app.config['IIIF_PREFIX']}{id_}/info.json") req = requests.get( f"{url_root}{app.config['IIIF_PREFIX']}{id_}/info.json") image_api = req.json() diff --git a/openatlas/static/setup.py b/openatlas/static/setup.py index 608106bff..58e2f2ab1 100644 --- a/openatlas/static/setup.py +++ b/openatlas/static/setup.py @@ -26,6 +26,7 @@ "leaflet.fullscreen": "2.2.0", "leaflet.markercluster": "^1.5.3", "leaflet": "^1.7.1", + "mirador": "^3.3.0", "save-svg-as-png": "^1.4.17", "tinymce": "^5.10.3", } diff --git a/openatlas/templates/entity/view.html b/openatlas/templates/entity/view.html index 05b48f6d9..a68dcc47f 100644 --- a/openatlas/templates/entity/view.html +++ b/openatlas/templates/entity/view.html @@ -20,6 +20,11 @@ {{ entity.reference_systems|ext_references|safe }} {{ description_html|safe }} + {% if config.IIIF_ACTIVATE and entity.class_.name == 'file' %} +
+ +
+ {% endif %} {% if gis_data %}
diff --git a/openatlas/templates/iiif.html b/openatlas/templates/iiif.html new file mode 100644 index 000000000..0151228ec --- /dev/null +++ b/openatlas/templates/iiif.html @@ -0,0 +1,35 @@ +
+
+ + +
\ No newline at end of file diff --git a/openatlas/views/file.py b/openatlas/views/file.py index f6360c4a0..41d6abd08 100644 --- a/openatlas/views/file.py +++ b/openatlas/views/file.py @@ -1,4 +1,4 @@ -from typing import Any, Union, Optional +from typing import Any, Union from flask import g, render_template, request, send_from_directory, url_for from flask_babel import lazy_gettext as _ @@ -78,4 +78,7 @@ def make_iiif_available(id_: int): @app.route('/view_iiif/', methods=['GET']) @required_group('contributor') def view_iiif(id_: int): - return redirect(url_for('api.iiif_manifest', id_=id_)) + #return redirect(url_for('api.iiif_manifest', id_=id_)) + return render_template( + 'iiif.html', + manifest_url = url_for('api.iiif_manifest', id_=id_, _external=True)) From 7b79c8bb9905dd5aacc4bd4cd4d867226196f110 Mon Sep 17 00:00:00 2001 From: bkoschicek Date: Wed, 27 Sep 2023 16:28:19 +0200 Subject: [PATCH 047/102] check if iiif file exist and wait for process to finish --- openatlas/display/base_display.py | 5 +++-- openatlas/display/util.py | 16 +++++++++----- openatlas/templates/entity/view.html | 32 ++++++++++++++-------------- openatlas/templates/iiif.html | 14 +++++------- openatlas/views/file.py | 3 ++- 5 files changed, 37 insertions(+), 33 deletions(-) diff --git a/openatlas/display/base_display.py b/openatlas/display/base_display.py index 2c45cd518..f7577acb4 100644 --- a/openatlas/display/base_display.py +++ b/openatlas/display/base_display.py @@ -13,7 +13,7 @@ bookmark_toggle, button, description, edit_link, format_date, format_entity_date, get_appearance, get_base_table_data, get_system_data, is_authorized, link, manual, profile_image_table_link, remove_link, - get_chart_data) + get_chart_data, check_iiif_file_exist) from openatlas.models.entity import Entity from openatlas.models.gis import Gis from openatlas.models.link import Link @@ -101,7 +101,8 @@ def add_info_tab_content(self) -> None: overlays=self.overlays, chart_data=self.get_chart_data(), description_html=self.description_html(), - problematic_type_id=self.problematic_type) + problematic_type_id=self.problematic_type, + iiif_image=check_iiif_file_exist(self.entity.image_id)) def description_html(self) -> str: return description(self.entity.description) diff --git a/openatlas/display/util.py b/openatlas/display/util.py index 28b9ae9e2..7781cfff4 100644 --- a/openatlas/display/util.py +++ b/openatlas/display/util.py @@ -763,16 +763,22 @@ def check_iiif_activation() -> bool: def check_iiif_file_exist(id_: int) -> bool: - file_to_check = (Path(app.config['IIIF_DIR']) - / app.config['IIIF_PREFIX'] / str(id_)) + file_to_check = \ + Path(app.config['IIIF_DIR']) / app.config['IIIF_PREFIX'] / str(id_) return file_to_check.is_file() -def convert_image_to_iiif(id_): +def convert_image_to_iiif(id_: int) -> None: path = Path(app.config['IIIF_DIR']) / app.config['IIIF_PREFIX'] / str(id_) vips = "vips" if os.name == 'posix' else "vips.exe" command = \ (f"{vips} tiffsave {get_file_path(id_)} {path} " - f"--tile --pyramid --compression deflate " + f"--tile --pyramid --compression jpeg " f"--tile-width 256 --tile-height 256") - subprocess.Popen(command, shell=True) + try: + process = subprocess.Popen(command, shell=True) + process.wait() + flash(_('iiif converted'), 'info') + except Exception: + flash(_('failed to convert image'), 'error') + diff --git a/openatlas/templates/entity/view.html b/openatlas/templates/entity/view.html index a68dcc47f..1c9cbee0e 100644 --- a/openatlas/templates/entity/view.html +++ b/openatlas/templates/entity/view.html @@ -20,9 +20,9 @@ {{ entity.reference_systems|ext_references|safe }} {{ description_html|safe }}
- {% if config.IIIF_ACTIVATE and entity.class_.name == 'file' %} + {% if iiif_image and entity.class_.name == 'file' %}
- +
{% endif %} {% if gis_data %} @@ -40,20 +40,20 @@ {% endif %} diff --git a/openatlas/templates/iiif.html b/openatlas/templates/iiif.html index 0151228ec..0d8a46be5 100644 --- a/openatlas/templates/iiif.html +++ b/openatlas/templates/iiif.html @@ -16,19 +16,15 @@ "defaultSideBarPanel": 'attribution', "sideBarOpenByDefault": true, }, - "language": "{{ session.language }}", // Set default language display here. - "availableLanguages": { // All the languages available in the language switcher - "ca": "Català", + "language": "{{ session.language }}", + "availableLanguages": { "de": "Deutsch", "en": "English", - "es": "Español", "fr": "Français" }, - "windows": [ - { - "loadedManifest": "{{ manifest_url }}" - } - ] + "windows": [{ + "loadedManifest": "{{ manifest_url }}" + }] }; Mirador.viewer(config); diff --git a/openatlas/views/file.py b/openatlas/views/file.py index 41d6abd08..fa8005706 100644 --- a/openatlas/views/file.py +++ b/openatlas/views/file.py @@ -1,6 +1,7 @@ from typing import Any, Union -from flask import g, render_template, request, send_from_directory, url_for +from flask import g, render_template, request, send_from_directory, url_for, \ + flash from flask_babel import lazy_gettext as _ from werkzeug.utils import redirect from werkzeug.wrappers import Response From 78b673a0709419389a373083932f197bec3f0820 Mon Sep 17 00:00:00 2001 From: bkoschicek Date: Wed, 27 Sep 2023 16:56:39 +0200 Subject: [PATCH 048/102] added upgrade notes --- install/upgrade/upgrade.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/install/upgrade/upgrade.md b/install/upgrade/upgrade.md index eeb706b7e..73f5ca431 100644 --- a/install/upgrade/upgrade.md +++ b/install/upgrade/upgrade.md @@ -19,6 +19,16 @@ then run the database upgrade script, then restart Apache: sudo python3 install/upgrade/database_upgrade.py sudo service apache2 restart +### 7.16.x to 7.17.0 +For the IIIF implementation new NPM packages are needed: + + $ cd openatlas/static + $ rm package.json + $ pip3 install -e ./ + $ ~/.local/bin/calmjs npm --install openatlas + +If you want to use IIIF, please read the [instructions](https://redmine.openatlas.eu/projects/uni/wiki/IIIF). + ### 7.16.0 to 7.16.1 A code base update (e.g. with git pull) and a webserver restart is sufficient. From 66dbb3b9ebf1d18f847e53b39c32ad5cbd674aa7 Mon Sep 17 00:00:00 2001 From: bkoschicek Date: Mon, 2 Oct 2023 15:13:11 +0200 Subject: [PATCH 049/102] refactor --- openatlas/display/display.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/openatlas/display/display.py b/openatlas/display/display.py index 33610e195..fbd445327 100644 --- a/openatlas/display/display.py +++ b/openatlas/display/display.py @@ -5,7 +5,6 @@ from flask import g, url_for from flask_babel import lazy_gettext as _ -from openatlas import app from openatlas.display.base_display import ( ActorDisplay, BaseDisplay, EventsDisplay, PlaceBaseDisplay, ReferenceBaseDisplay, TypeBaseDisplay) @@ -101,8 +100,8 @@ def add_tabs(self) -> None: super().add_tabs() entity = self.entity for name in [ - 'source', 'event', 'actor', 'place', 'feature', - 'stratigraphic_unit', 'artifact', 'reference', 'type']: + 'source', 'event', 'actor', 'place', 'feature', + 'stratigraphic_unit', 'artifact', 'reference', 'type']: self.tabs[name] = Tab(name, entity=entity) entity.image_id = entity.id if get_file_path(entity.id) else None for link_ in entity.get_links('P67'): @@ -298,8 +297,8 @@ def add_tabs(self) -> None: super().add_tabs() entity = self.entity for name in [ - 'actor', 'artifact', 'feature', 'event', 'place', - 'stratigraphic_unit', 'text', 'reference', 'file']: + 'actor', 'artifact', 'feature', 'event', 'place', + 'stratigraphic_unit', 'text', 'reference', 'file']: self.tabs[name] = Tab(name, entity=entity) for text in entity.get_linked_entities('P73', types=True): self.tabs['text'].table.rows.append([ From 55a35893d5040764f73c16ff98878a6b41936fd2 Mon Sep 17 00:00:00 2001 From: bkoschicek Date: Mon, 2 Oct 2023 17:23:43 +0200 Subject: [PATCH 050/102] refactor --- openatlas/api/endpoints/iiif.py | 6 +++--- openatlas/views/file.py | 6 ++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/openatlas/api/endpoints/iiif.py b/openatlas/api/endpoints/iiif.py index f4bb04d56..caac19c1b 100644 --- a/openatlas/api/endpoints/iiif.py +++ b/openatlas/api/endpoints/iiif.py @@ -29,13 +29,13 @@ def get_manifest(id_: int) -> dict[str, Any]: "license": get_license_name(entity), "attribution": "By OpenAtlas", "sequences": [{ - "@id": "http://c8b09ce6-df6d-4d5e-9eba-17507dc5c185", + "@id": "https://c8b09ce6-df6d-4d5e-9eba-17507dc5c185", "@type": "sc:Sequence", "label": [{ "@value": "Normal Sequence", "@language": "en"}], "canvases": [{ - "@id": "http://251a31df-761d-46df-85c3-66cb967b8a67", + "@id": "https://251a31df-761d-46df-85c3-66cb967b8a67", "@type": "sc:Canvas", "label": entity.name, "height": image_api['height'], @@ -45,7 +45,7 @@ def get_manifest(id_: int) -> dict[str, Any]: "@language": "en"}, "images": [{ "@context": - "http://iiif.io/api/presentation/2/context.json", + "https://iiif.io/api/presentation/2/context.json", "@id": "http://a0a3ec3e-2084-4253-b0f9-a5f87645e15d", "@type": "oa:Annotation", "motivation": "sc:painting", diff --git a/openatlas/views/file.py b/openatlas/views/file.py index fa8005706..365692cfa 100644 --- a/openatlas/views/file.py +++ b/openatlas/views/file.py @@ -1,7 +1,6 @@ from typing import Any, Union -from flask import g, render_template, request, send_from_directory, url_for, \ - flash +from flask import g, render_template, request, send_from_directory, url_for from flask_babel import lazy_gettext as _ from werkzeug.utils import redirect from werkzeug.wrappers import Response @@ -79,7 +78,6 @@ def make_iiif_available(id_: int): @app.route('/view_iiif/', methods=['GET']) @required_group('contributor') def view_iiif(id_: int): - #return redirect(url_for('api.iiif_manifest', id_=id_)) return render_template( 'iiif.html', - manifest_url = url_for('api.iiif_manifest', id_=id_, _external=True)) + manifest_url=url_for('api.iiif_manifest', id_=id_, _external=True)) From a02f0ea565b1615be879e45b41ccb63cf29c6dc5 Mon Sep 17 00:00:00 2001 From: BernhardKoschicek Date: Wed, 4 Oct 2023 09:59:55 +0200 Subject: [PATCH 051/102] refactor DIR to PATH in IIIF --- config/default.py | 2 +- openatlas/display/util.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/config/default.py b/config/default.py index beb518bca..86a7a69b3 100644 --- a/config/default.py +++ b/config/default.py @@ -46,7 +46,7 @@ PROCESSED_EXT = '.jpeg' IIIF_ACTIVATE = False -IIIF_DIR = '' +IIIF_PATH = '' IIIF_PREFIX = '' # has to end with / IIIF_URL = '' diff --git a/openatlas/display/util.py b/openatlas/display/util.py index 11af7f534..788506ee4 100644 --- a/openatlas/display/util.py +++ b/openatlas/display/util.py @@ -427,8 +427,8 @@ def system_warnings(_context: str, _unneeded_string: str) -> str: warnings.append( f"Database version {app.config['DATABASE_VERSION']} is needed but " f"current version is {g.settings['database_version']}") - if app.config['IIIF_ACTIVATE'] and app.config['IIIF_DIR']: - path = Path(app.config['IIIF_DIR']) / app.config['IIIF_PREFIX'] + if app.config['IIIF_ACTIVATE'] and app.config['IIIF_PATH']: + path = Path(app.config['IIIF_PATH']) / app.config['IIIF_PREFIX'] check_write_access(path, warnings) for path in app.config['WRITABLE_PATHS']: check_write_access(path, warnings) @@ -754,18 +754,18 @@ def get_entities_linked_to_type_recursive( def check_iiif_activation() -> bool: return True \ if (app.config['IIIF_ACTIVATE'] - and os.access(Path(app.config['IIIF_DIR']), os.W_OK)) \ + and os.access(Path(app.config['IIIF_PATH']), os.W_OK)) \ else False def check_iiif_file_exist(id_: int) -> bool: file_to_check = \ - Path(app.config['IIIF_DIR']) / app.config['IIIF_PREFIX'] / str(id_) + Path(app.config['IIIF_PATH']) / app.config['IIIF_PREFIX'] / str(id_) return file_to_check.is_file() def convert_image_to_iiif(id_: int) -> None: - path = Path(app.config['IIIF_DIR']) / app.config['IIIF_PREFIX'] / str(id_) + path = Path(app.config['IIIF_PATH']) / app.config['IIIF_PREFIX'] / str(id_) vips = "vips" if os.name == 'posix' else "vips.exe" command = \ (f"{vips} tiffsave {get_file_path(id_)} {path} " From 6d5aeaa7ae470a807dc3fbf0928d450f494c70fa Mon Sep 17 00:00:00 2001 From: BernhardKoschicek Date: Wed, 4 Oct 2023 10:25:21 +0200 Subject: [PATCH 052/102] fixed file stats --- openatlas/display/display.py | 3 +-- openatlas/views/admin.py | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/openatlas/display/display.py b/openatlas/display/display.py index 862589aee..46ccf4cef 100644 --- a/openatlas/display/display.py +++ b/openatlas/display/display.py @@ -13,8 +13,7 @@ from openatlas.display.util import ( button, description, edit_link, format_entity_date, get_base_table_data, get_file_path, is_authorized, link, remove_link, uc_first, - check_iiif_activation, - check_iiif_file_exist) + check_iiif_activation, check_iiif_file_exist) from openatlas.models.entity import Entity from openatlas.views.tools import carbon_result, sex_result diff --git a/openatlas/views/admin.py b/openatlas/views/admin.py index 0ee36a127..6110317ae 100644 --- a/openatlas/views/admin.py +++ b/openatlas/views/admin.py @@ -782,8 +782,8 @@ def get_disk_space_info() -> Optional[dict[str, Any]]: @required_group('manager') def admin_convert_all_to_iiif() -> Response: for entity in Entity.get_by_class('file'): - if entity.id in g.file_stats \ - and g.file_stats[entity.id]['ext'] \ + if entity.id in g.files \ + and entity.get_file_extension() \ in app.config['ALLOWED_IMAGE_EXT']: convert_image_to_iiif(entity.id) return redirect(url_for('admin_index') + '#tab-data') From 2b9df3b7af31ef28a5291c7463281cb3c5bcea31 Mon Sep 17 00:00:00 2001 From: BernhardKoschicek Date: Wed, 4 Oct 2023 12:41:53 +0200 Subject: [PATCH 053/102] use dict as config variable --- config/{api_config.py => api.py} | 0 config/default.py | 5 ----- config/iiif.py | 4 ++++ openatlas/__init__.py | 3 ++- openatlas/api/endpoints/iiif.py | 11 +++++------ openatlas/api/routes.py | 2 +- openatlas/display/util.py | 10 ++++------ 7 files changed, 16 insertions(+), 19 deletions(-) rename config/{api_config.py => api.py} (100%) create mode 100644 config/iiif.py diff --git a/config/api_config.py b/config/api.py similarity index 100% rename from config/api_config.py rename to config/api.py diff --git a/config/default.py b/config/default.py index 064c188ab..aab3fed06 100644 --- a/config/default.py +++ b/config/default.py @@ -45,11 +45,6 @@ ALLOWED_IMAGE_EXT = DISPLAY_FILE_EXTENSIONS + NONE_DISPLAY_EXT PROCESSED_EXT = '.jpeg' -IIIF_ACTIVATE = False -IIIF_PATH = '' -IIIF_PREFIX = '' # has to end with / -IIIF_URL = '' - # For system checks WRITABLE_PATHS = [ UPLOAD_PATH, diff --git a/config/iiif.py b/config/iiif.py new file mode 100644 index 000000000..c23e58bba --- /dev/null +++ b/config/iiif.py @@ -0,0 +1,4 @@ +IIIF = { + 'activate': False, + 'path': '', + 'url': ''} \ No newline at end of file diff --git a/openatlas/__init__.py b/openatlas/__init__.py index a100dce16..6228f3f9a 100644 --- a/openatlas/__init__.py +++ b/openatlas/__init__.py @@ -13,7 +13,8 @@ app: Flask = Flask(__name__, instance_relative_config=True) csrf = CSRFProtect(app) # Make sure all forms are CSRF protected app.config.from_object('config.default') -app.config.from_object('config.api_config') +app.config.from_object('config.api') +app.config.from_object('config.iiif') app.config.from_pyfile('production.py') app.config['WTF_CSRF_TIME_LIMIT'] = None # Set CSRF token valid for session diff --git a/openatlas/api/endpoints/iiif.py b/openatlas/api/endpoints/iiif.py index caac19c1b..9c50c1fec 100644 --- a/openatlas/api/endpoints/iiif.py +++ b/openatlas/api/endpoints/iiif.py @@ -9,12 +9,11 @@ from openatlas import app -def get_manifest(id_: int) -> dict[str, Any]: +def get_manifest(id_: int, version: int) -> dict[str, Any]: entity = get_entity_by_id(id_) - url_root = app.config['IIIF_URL'] or f"{request.url_root}iiif/" + url_root = app.config['IIIF']['URL'] # get metadata from the image api - req = requests.get( - f"{url_root}{app.config['IIIF_PREFIX']}{id_}/info.json") + req = requests.get(f"{url_root}{id_}/info.json") image_api = req.json() iiif_id = f"{url_root}{id_}" manifest = { @@ -69,5 +68,5 @@ def get_manifest(id_: int) -> dict[str, Any]: class IIIFManifest(Resource): @staticmethod - def get(id_: int) -> Response: - return jsonify(get_manifest(id_)) + def get(version: int, id_: int) -> Response: + return jsonify(get_manifest(id_, version)) diff --git a/openatlas/api/routes.py b/openatlas/api/routes.py index 598f0a255..c14b3c18c 100644 --- a/openatlas/api/routes.py +++ b/openatlas/api/routes.py @@ -102,5 +102,5 @@ def add_routes_v03(api: Api) -> None: api.add_resource( IIIFManifest, - '/iiif_manifest/', + '/iiif_manifest//', endpoint='iiif_manifest') diff --git a/openatlas/display/util.py b/openatlas/display/util.py index 788506ee4..b26990dcc 100644 --- a/openatlas/display/util.py +++ b/openatlas/display/util.py @@ -752,20 +752,18 @@ def get_entities_linked_to_type_recursive( def check_iiif_activation() -> bool: + iiif = app.config['IIIF'] return True \ - if (app.config['IIIF_ACTIVATE'] - and os.access(Path(app.config['IIIF_PATH']), os.W_OK)) \ + if (iiif['activate'] and os.access(Path(iiif['path']), os.W_OK)) \ else False def check_iiif_file_exist(id_: int) -> bool: - file_to_check = \ - Path(app.config['IIIF_PATH']) / app.config['IIIF_PREFIX'] / str(id_) - return file_to_check.is_file() + return (Path(app.config['IIIF']['path']) / str(id_)).is_file() def convert_image_to_iiif(id_: int) -> None: - path = Path(app.config['IIIF_PATH']) / app.config['IIIF_PREFIX'] / str(id_) + path = Path(app.config['IIIF']['path']) / str(id_) vips = "vips" if os.name == 'posix' else "vips.exe" command = \ (f"{vips} tiffsave {get_file_path(id_)} {path} " From cbee52f291876766f803dfca3e88f1828e7e5084 Mon Sep 17 00:00:00 2001 From: BernhardKoschicek Date: Wed, 4 Oct 2023 12:54:13 +0200 Subject: [PATCH 054/102] fixed tests --- openatlas/display/util.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openatlas/display/util.py b/openatlas/display/util.py index b26990dcc..c521a909b 100644 --- a/openatlas/display/util.py +++ b/openatlas/display/util.py @@ -427,9 +427,9 @@ def system_warnings(_context: str, _unneeded_string: str) -> str: warnings.append( f"Database version {app.config['DATABASE_VERSION']} is needed but " f"current version is {g.settings['database_version']}") - if app.config['IIIF_ACTIVATE'] and app.config['IIIF_PATH']: - path = Path(app.config['IIIF_PATH']) / app.config['IIIF_PREFIX'] - check_write_access(path, warnings) + if app.config['IIIF']['activate']: + if path := app.config['IIIF']['path']: + check_write_access(path, warnings) for path in app.config['WRITABLE_PATHS']: check_write_access(path, warnings) if is_authorized('admin'): From bcb9460d164073f5432b63b084e4a72bdd1d8532 Mon Sep 17 00:00:00 2001 From: BernhardKoschicek Date: Wed, 4 Oct 2023 15:26:54 +0200 Subject: [PATCH 055/102] added dynamic creation of manifest --- config/iiif.py | 3 +- openatlas/api/endpoints/iiif.py | 123 +++++++++++++++++---------- openatlas/api/routes.py | 14 ++- openatlas/templates/entity/view.html | 2 +- openatlas/views/file.py | 8 +- 5 files changed, 99 insertions(+), 51 deletions(-) diff --git a/config/iiif.py b/config/iiif.py index c23e58bba..26db8788b 100644 --- a/config/iiif.py +++ b/config/iiif.py @@ -1,4 +1,5 @@ IIIF = { 'activate': False, 'path': '', - 'url': ''} \ No newline at end of file + 'url': '', + 'version': 2} \ No newline at end of file diff --git a/openatlas/api/endpoints/iiif.py b/openatlas/api/endpoints/iiif.py index 9c50c1fec..7fe056d87 100644 --- a/openatlas/api/endpoints/iiif.py +++ b/openatlas/api/endpoints/iiif.py @@ -1,72 +1,103 @@ from typing import Any import requests -from flask import request, jsonify, Response +from flask import request, jsonify, Response, url_for from flask_restful import Resource from openatlas.api.resources.model_mapper import get_entity_by_id from openatlas.api.resources.util import get_license_name from openatlas import app +from openatlas.models.entity import Entity -def get_manifest(id_: int, version: int) -> dict[str, Any]: - entity = get_entity_by_id(id_) - url_root = app.config['IIIF']['URL'] - # get metadata from the image api - req = requests.get(f"{url_root}{id_}/info.json") - image_api = req.json() - iiif_id = f"{url_root}{id_}" - manifest = { - "@context": "http://iiif.io/api/presentation/2/context.json", - "@id": f"{request.base_url}", - "@type": "sc:Manifest", - "label": entity.name, - "metadata": [], - "description": [{ - "@value": entity.description, - "@language": "en"}], - "license": get_license_name(entity), - "attribution": "By OpenAtlas", - "sequences": [{ - "@id": "https://c8b09ce6-df6d-4d5e-9eba-17507dc5c185", +class IIIFCanvas(Resource): + @staticmethod + def get(id_: int) -> Response: + img_url = f"{app.config['IIIF']['url']}{id_}" + req = requests.get(f"{img_url}/info.json") + img_api = req.json() + entity = get_entity_by_id(id_) + return jsonify( + {"@context": "https://iiif.io/api/presentation/2/context.json"} | + IIIFCanvas.build_canvas(entity, img_url, img_api)) + + @staticmethod + def build_canvas(entity: Entity, img_url: str, img_api: dict[str, Any]): + return { + "@id": url_for('api.iiif_canvas', id_=entity.id, _external=True), "@type": "sc:Sequence", "label": [{ "@value": "Normal Sequence", "@language": "en"}], "canvases": [{ - "@id": "https://251a31df-761d-46df-85c3-66cb967b8a67", + "@id": "https://id.canvas", "@type": "sc:Canvas", "label": entity.name, - "height": image_api['height'], - "width": image_api['width'], + "height": img_api['height'], + "width": img_api['width'], "description": { "@value": entity.description, "@language": "en"}, - "images": [{ - "@context": - "https://iiif.io/api/presentation/2/context.json", - "@id": "http://a0a3ec3e-2084-4253-b0f9-a5f87645e15d", - "@type": "oa:Annotation", - "motivation": "sc:painting", - "resource": { - "@id": f"{iiif_id}/full/full/0/default.jpg", - "@type": "dctypes:Image", - "format": "image/jpeg", - "service": { - "@context": - "http://iiif.io/api/image/2/context.json", - "@id": iiif_id, - "profile": image_api['profile']}, - "height": image_api['height'], - "width": image_api['width']}, - "on": "http://251a31df-761d-46df-85c3-66cb967b8a67"}], - "related": ""}]}], - "structures": []} + "images": [ + IIIFImage.build_image(entity.id, img_url, img_api)], + "related": ""}]} - return manifest + +class IIIFImage(Resource): + @staticmethod + def get(id_: int) -> Response: + image_url = f"{app.config['IIIF']['url']}{id_}" + req = requests.get(f"{image_url}/info.json") + image_api = req.json() + return jsonify(IIIFImage.build_image(id_, image_url, image_api)) + + @staticmethod + def build_image(id_: int, img_url: str, img_api: dict[str, Any]): + return { + "@context": "https://iiif.io/api/presentation/2/context.json", + "@id": url_for('api.iiif_image', id_=id_, _external=True), + "@type": "oa:Annotation", + "motivation": "sc:painting", + "resource": { + "@id": img_url, + "@type": "dctypes:Image", + "format": "image/jpeg", + "service": { + "@context": "http://iiif.io/api/image/2/context.json", + "@id": img_url, + "profile": img_api['profile']}, + "height": img_api['height'], + "width": img_api['width']}, + "on": url_for('api.iiif_canvas', id_=id_, _external=True) } class IIIFManifest(Resource): @staticmethod def get(version: int, id_: int) -> Response: - return jsonify(get_manifest(id_, version)) + operation = getattr(IIIFManifest, f'get_manifest_version_{version}') + return jsonify(operation(id_)) + + @staticmethod + def get_manifest_version_2(id_: int) -> dict[str, Any]: + entity = get_entity_by_id(id_) + image_url = f"{app.config['IIIF']['url']}{id_}" + # get metadata from the image api + req = requests.get(f"{image_url}/info.json") + image_api = req.json() + manifest = { + "@context": "http://iiif.io/api/presentation/2/context.json", + "@id": f"{request.base_url}", + "@type": "sc:Manifest", + "label": entity.name, + "metadata": [], + "description": [{ + "@value": entity.description, + "@language": "en"}], + "license": get_license_name(entity), + "attribution": "By OpenAtlas", + "sequences": [ + IIIFCanvas.build_canvas(entity, image_url, image_api) + ], + "structures": []} + + return manifest diff --git a/openatlas/api/routes.py b/openatlas/api/routes.py index c14b3c18c..e8175eff1 100644 --- a/openatlas/api/routes.py +++ b/openatlas/api/routes.py @@ -1,6 +1,6 @@ from flask_restful import Api -from openatlas.api.endpoints.iiif import IIIFManifest +from openatlas.api.endpoints.iiif import IIIFManifest, IIIFImage, IIIFCanvas from openatlas.api.endpoints.content import ClassMapping, \ GetContent, SystemClassCount from openatlas.api.endpoints.special import GetGeometricEntities, \ @@ -104,3 +104,15 @@ def add_routes_v03(api: Api) -> None: IIIFManifest, '/iiif_manifest//', endpoint='iiif_manifest') + api.add_resource( + IIIFImage, + '/iiif_image/', + endpoint='iiif_image') + api.add_resource( + IIIFCanvas, + '/iiif_canvas/', + endpoint='iiif_canvas') + # api.add_resource( + # IIIFSequence, + # '/iiif_sequence/', + # endpoint='iiif_sequence') diff --git a/openatlas/templates/entity/view.html b/openatlas/templates/entity/view.html index 1c9cbee0e..a5061a1a2 100644 --- a/openatlas/templates/entity/view.html +++ b/openatlas/templates/entity/view.html @@ -22,7 +22,7 @@ {% if iiif_image and entity.class_.name == 'file' %}
- +
{% endif %} {% if gis_data %} diff --git a/openatlas/views/file.py b/openatlas/views/file.py index 150bd2538..7b5ac9261 100644 --- a/openatlas/views/file.py +++ b/openatlas/views/file.py @@ -79,5 +79,9 @@ def make_iiif_available(id_: int): @required_group('contributor') def view_iiif(id_: int): return render_template( - 'iiif.html', - manifest_url=url_for('api.iiif_manifest', id_=id_, _external=True)) + 'iiif.html', + manifest_url=url_for( + 'api.iiif_manifest', + id_=id_, + version=app.config['IIIF']['version'], + _external=True)) From f25aacc4b8e35b396788ca98a0008e2e2a2570d8 Mon Sep 17 00:00:00 2001 From: BernhardKoschicek Date: Wed, 4 Oct 2023 16:28:39 +0200 Subject: [PATCH 056/102] added dynamic creation of manifest --- openatlas/api/endpoints/iiif.py | 66 ++++++++++++++++++++------------- openatlas/api/routes.py | 15 ++++---- 2 files changed, 49 insertions(+), 32 deletions(-) diff --git a/openatlas/api/endpoints/iiif.py b/openatlas/api/endpoints/iiif.py index 7fe056d87..a98ce6efc 100644 --- a/openatlas/api/endpoints/iiif.py +++ b/openatlas/api/endpoints/iiif.py @@ -1,7 +1,7 @@ from typing import Any import requests -from flask import request, jsonify, Response, url_for +from flask import jsonify, Response, url_for from flask_restful import Resource from openatlas.api.resources.model_mapper import get_entity_by_id @@ -10,7 +10,7 @@ from openatlas.models.entity import Entity -class IIIFCanvas(Resource): +class IIIFSequence(Resource): @staticmethod def get(id_: int) -> Response: img_url = f"{app.config['IIIF']['url']}{id_}" @@ -19,28 +19,48 @@ def get(id_: int) -> Response: entity = get_entity_by_id(id_) return jsonify( {"@context": "https://iiif.io/api/presentation/2/context.json"} | - IIIFCanvas.build_canvas(entity, img_url, img_api)) + IIIFSequence.build_sequence(entity, img_url, img_api)) @staticmethod - def build_canvas(entity: Entity, img_url: str, img_api: dict[str, Any]): + def build_sequence(entity: Entity, img_url: str, img_api: dict[str, Any]): return { - "@id": url_for('api.iiif_canvas', id_=entity.id, _external=True), + "@id": url_for( + 'api.iiif_sequence', + id_=entity.id, + _external=True), "@type": "sc:Sequence", "label": [{ "@value": "Normal Sequence", "@language": "en"}], - "canvases": [{ - "@id": "https://id.canvas", - "@type": "sc:Canvas", - "label": entity.name, - "height": img_api['height'], - "width": img_api['width'], - "description": { - "@value": entity.description, - "@language": "en"}, - "images": [ - IIIFImage.build_image(entity.id, img_url, img_api)], - "related": ""}]} + "canvases": [ + IIIFCanvas.build_canvas(entity, img_url, img_api)]} + + +class IIIFCanvas(Resource): + @staticmethod + def get(id_: int) -> Response: + img_url = f"{app.config['IIIF']['url']}{id_}" + req = requests.get(f"{img_url}/info.json") + img_api = req.json() + entity = get_entity_by_id(id_) + return jsonify( + {"@context": "https://iiif.io/api/presentation/2/context.json"} | + IIIFCanvas.build_canvas(entity, img_url, img_api)) + + @staticmethod + def build_canvas(entity: Entity, img_url: str, img_api: dict[str, Any]): + return { + "@id": url_for('api.iiif_canvas', id_=entity.id, _external=True), + "@type": "sc:Canvas", + "label": entity.name, + "height": img_api['height'], + "width": img_api['width'], + "description": { + "@value": entity.description, + "@language": "en"}, + "images": [ + IIIFImage.build_image(entity.id, img_url, img_api)], + "related": ""} class IIIFImage(Resource): @@ -68,7 +88,7 @@ def build_image(id_: int, img_url: str, img_api: dict[str, Any]): "profile": img_api['profile']}, "height": img_api['height'], "width": img_api['width']}, - "on": url_for('api.iiif_canvas', id_=id_, _external=True) } + "on": url_for('api.iiif_canvas', id_=id_, _external=True)} class IIIFManifest(Resource): @@ -81,12 +101,11 @@ def get(version: int, id_: int) -> Response: def get_manifest_version_2(id_: int) -> dict[str, Any]: entity = get_entity_by_id(id_) image_url = f"{app.config['IIIF']['url']}{id_}" - # get metadata from the image api req = requests.get(f"{image_url}/info.json") image_api = req.json() - manifest = { + return { "@context": "http://iiif.io/api/presentation/2/context.json", - "@id": f"{request.base_url}", + "@id": url_for('api.iiif_manifest', id_=id_, version=2), "@type": "sc:Manifest", "label": entity.name, "metadata": [], @@ -96,8 +115,5 @@ def get_manifest_version_2(id_: int) -> dict[str, Any]: "license": get_license_name(entity), "attribution": "By OpenAtlas", "sequences": [ - IIIFCanvas.build_canvas(entity, image_url, image_api) - ], + IIIFSequence.build_sequence(entity, image_url, image_api)], "structures": []} - - return manifest diff --git a/openatlas/api/routes.py b/openatlas/api/routes.py index e8175eff1..bee91cf82 100644 --- a/openatlas/api/routes.py +++ b/openatlas/api/routes.py @@ -1,6 +1,7 @@ from flask_restful import Api -from openatlas.api.endpoints.iiif import IIIFManifest, IIIFImage, IIIFCanvas +from openatlas.api.endpoints.iiif import IIIFManifest, IIIFImage, IIIFCanvas, \ + IIIFSequence from openatlas.api.endpoints.content import ClassMapping, \ GetContent, SystemClassCount from openatlas.api.endpoints.special import GetGeometricEntities, \ @@ -106,13 +107,13 @@ def add_routes_v03(api: Api) -> None: endpoint='iiif_manifest') api.add_resource( IIIFImage, - '/iiif_image/', + '/iiif_image/.json', endpoint='iiif_image') api.add_resource( IIIFCanvas, - '/iiif_canvas/', + '/iiif_canvas/.json', endpoint='iiif_canvas') - # api.add_resource( - # IIIFSequence, - # '/iiif_sequence/', - # endpoint='iiif_sequence') + api.add_resource( + IIIFSequence, + '/iiif_sequence/.json', + endpoint='iiif_sequence') From 5107ecfa8cefb10316c0d2ba9b1e9879a64e56e9 Mon Sep 17 00:00:00 2001 From: BernhardKoschicek Date: Wed, 4 Oct 2023 17:41:32 +0200 Subject: [PATCH 057/102] added thumbnail to manifest --- openatlas/api/endpoints/iiif.py | 30 +++++++++++++++++++++++------- openatlas/api/routes.py | 6 +++--- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/openatlas/api/endpoints/iiif.py b/openatlas/api/endpoints/iiif.py index a98ce6efc..000ecb3c9 100644 --- a/openatlas/api/endpoints/iiif.py +++ b/openatlas/api/endpoints/iiif.py @@ -12,7 +12,7 @@ class IIIFSequence(Resource): @staticmethod - def get(id_: int) -> Response: + def get(version: int, id_: int) -> Response: img_url = f"{app.config['IIIF']['url']}{id_}" req = requests.get(f"{img_url}/info.json") img_api = req.json() @@ -27,6 +27,7 @@ def build_sequence(entity: Entity, img_url: str, img_api: dict[str, Any]): "@id": url_for( 'api.iiif_sequence', id_=entity.id, + version=2, _external=True), "@type": "sc:Sequence", "label": [{ @@ -38,7 +39,7 @@ def build_sequence(entity: Entity, img_url: str, img_api: dict[str, Any]): class IIIFCanvas(Resource): @staticmethod - def get(id_: int) -> Response: + def get(version: int, id_: int) -> Response: img_url = f"{app.config['IIIF']['url']}{id_}" req = requests.get(f"{img_url}/info.json") img_api = req.json() @@ -50,7 +51,8 @@ def get(id_: int) -> Response: @staticmethod def build_canvas(entity: Entity, img_url: str, img_api: dict[str, Any]): return { - "@id": url_for('api.iiif_canvas', id_=entity.id, _external=True), + "@id": url_for( + 'api.iiif_canvas', id_=entity.id, version=2, _external=True), "@type": "sc:Canvas", "label": entity.name, "height": img_api['height'], @@ -60,12 +62,24 @@ def build_canvas(entity: Entity, img_url: str, img_api: dict[str, Any]): "@language": "en"}, "images": [ IIIFImage.build_image(entity.id, img_url, img_api)], - "related": ""} + "related": "", + "thumbnail": { + "@id": f'{img_url}/full/!200,200/0/default.jpg', + "@type": "dctypes:Image", + "format": "image/jpeg", + "height": 200, + "width": 200, + "service": { + "@context": "http://iiif.io/api/image/2/context.json", + "@id": img_url, + "profile": img_api['profile']}, + }, + } class IIIFImage(Resource): @staticmethod - def get(id_: int) -> Response: + def get(version: int, id_: int) -> Response: image_url = f"{app.config['IIIF']['url']}{id_}" req = requests.get(f"{image_url}/info.json") image_api = req.json() @@ -75,7 +89,8 @@ def get(id_: int) -> Response: def build_image(id_: int, img_url: str, img_api: dict[str, Any]): return { "@context": "https://iiif.io/api/presentation/2/context.json", - "@id": url_for('api.iiif_image', id_=id_, _external=True), + "@id": + url_for('api.iiif_image', id_=id_, version=2, _external=True), "@type": "oa:Annotation", "motivation": "sc:painting", "resource": { @@ -88,7 +103,8 @@ def build_image(id_: int, img_url: str, img_api: dict[str, Any]): "profile": img_api['profile']}, "height": img_api['height'], "width": img_api['width']}, - "on": url_for('api.iiif_canvas', id_=id_, _external=True)} + "on": + url_for('api.iiif_canvas', id_=id_, version=2, _external=True)} class IIIFManifest(Resource): diff --git a/openatlas/api/routes.py b/openatlas/api/routes.py index bee91cf82..a2834ca86 100644 --- a/openatlas/api/routes.py +++ b/openatlas/api/routes.py @@ -107,13 +107,13 @@ def add_routes_v03(api: Api) -> None: endpoint='iiif_manifest') api.add_resource( IIIFImage, - '/iiif_image/.json', + '/iiif_image//.json', endpoint='iiif_image') api.add_resource( IIIFCanvas, - '/iiif_canvas/.json', + '/iiif_canvas//.json', endpoint='iiif_canvas') api.add_resource( IIIFSequence, - '/iiif_sequence/.json', + '/iiif_sequence//.json', endpoint='iiif_sequence') From 5036f93fb58ec10c95f81d0cc51258f85f54a9af Mon Sep 17 00:00:00 2001 From: BernhardKoschicek Date: Wed, 4 Oct 2023 18:22:54 +0200 Subject: [PATCH 058/102] IIIF images as thumbnails --- openatlas/display/util.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/openatlas/display/util.py b/openatlas/display/util.py index c521a909b..539f77ff3 100644 --- a/openatlas/display/util.py +++ b/openatlas/display/util.py @@ -235,25 +235,28 @@ def profile_image(entity: Entity) -> str: path = get_file_path(entity.image_id) if not path: return '' # pragma: no cover - resized = None - size = app.config['IMAGE_SIZE']['thumbnail'] - if g.settings['image_processing'] and check_processed_image(path.name): - if path_ := get_file_path(entity.image_id, size): - resized = url_for('display_file', filename=path_.name, size=size) - url = url_for('display_file', filename=path.name) - src = resized or url - style = f'max-width:{g.settings["profile_image_width"]}px;' ext = app.config["DISPLAY_FILE_EXTENSIONS"] - if resized: - style = f'max-width:{app.config["IMAGE_SIZE"]["thumbnail"]}px;' - ext = app.config["ALLOWED_IMAGE_EXT"] + src = url_for('display_file', filename=path.name) + style = f'max-width:{g.settings["profile_image_width"]}px;' + if app.config['IIIF']['activate'] and check_iiif_file_exist(entity.id): + style = f'max-width:200px;' + ext = app.config["DISPLAY_FILE_EXTENSIONS"] + src = \ + (f"{app.config['IIIF']['url']}{entity.id}" + f"/full/!200,200/0/default.png") + elif g.settings['image_processing'] and check_processed_image(path.name): + size = app.config['IMAGE_SIZE']['thumbnail'] + if path_ := get_file_path(entity.image_id, size): + src = url_for('display_file', filename=path_.name, size=size) + style = f'max-width:{app.config["IMAGE_SIZE"]["thumbnail"]}px;' + ext = app.config["ALLOWED_IMAGE_EXT"] if entity.class_.view == 'file': html = \ '

' + _('no preview available') + '

' if path.suffix.lower() in ext: html = link( f'image', - url, + src, external=True) else: html = link( From 7e0349891fe3e0c895da588889312d630bf03bec Mon Sep 17 00:00:00 2001 From: bkoschicek Date: Thu, 5 Oct 2023 16:17:32 +0200 Subject: [PATCH 059/102] changed extension to tiff and refactor manifest --- openatlas/api/endpoints/iiif.py | 65 ++++++++++++++++----------------- openatlas/display/util.py | 6 +-- 2 files changed, 34 insertions(+), 37 deletions(-) diff --git a/openatlas/api/endpoints/iiif.py b/openatlas/api/endpoints/iiif.py index 000ecb3c9..c39eed32c 100644 --- a/openatlas/api/endpoints/iiif.py +++ b/openatlas/api/endpoints/iiif.py @@ -13,16 +13,14 @@ class IIIFSequence(Resource): @staticmethod def get(version: int, id_: int) -> Response: - img_url = f"{app.config['IIIF']['url']}{id_}" - req = requests.get(f"{img_url}/info.json") - img_api = req.json() - entity = get_entity_by_id(id_) return jsonify( {"@context": "https://iiif.io/api/presentation/2/context.json"} | - IIIFSequence.build_sequence(entity, img_url, img_api)) + IIIFSequence.build_sequence( + get_entity_by_id(id_), + get_iiif_metadata(id_))) @staticmethod - def build_sequence(entity: Entity, img_url: str, img_api: dict[str, Any]): + def build_sequence(entity: Entity, metadata: dict[str, Any]): return { "@id": url_for( 'api.iiif_sequence', @@ -34,45 +32,43 @@ def build_sequence(entity: Entity, img_url: str, img_api: dict[str, Any]): "@value": "Normal Sequence", "@language": "en"}], "canvases": [ - IIIFCanvas.build_canvas(entity, img_url, img_api)]} + IIIFCanvas.build_canvas(entity, metadata)]} class IIIFCanvas(Resource): @staticmethod def get(version: int, id_: int) -> Response: - img_url = f"{app.config['IIIF']['url']}{id_}" - req = requests.get(f"{img_url}/info.json") - img_api = req.json() - entity = get_entity_by_id(id_) return jsonify( {"@context": "https://iiif.io/api/presentation/2/context.json"} | - IIIFCanvas.build_canvas(entity, img_url, img_api)) + IIIFCanvas.build_canvas( + get_entity_by_id(id_), + get_iiif_metadata(id_))) @staticmethod - def build_canvas(entity: Entity, img_url: str, img_api: dict[str, Any]): + def build_canvas(entity: Entity, metadata: dict[str, Any]): return { "@id": url_for( 'api.iiif_canvas', id_=entity.id, version=2, _external=True), "@type": "sc:Canvas", "label": entity.name, - "height": img_api['height'], - "width": img_api['width'], + "height": metadata['img_api']['height'], + "width": metadata['img_api']['width'], "description": { "@value": entity.description, "@language": "en"}, "images": [ - IIIFImage.build_image(entity.id, img_url, img_api)], + IIIFImage.build_image(entity.id, metadata)], "related": "", "thumbnail": { - "@id": f'{img_url}/full/!200,200/0/default.jpg', + "@id": f'{metadata["img_url"]}/full/!200,200/0/default.jpg', "@type": "dctypes:Image", "format": "image/jpeg", "height": 200, "width": 200, "service": { "@context": "http://iiif.io/api/image/2/context.json", - "@id": img_url, - "profile": img_api['profile']}, + "@id": metadata['img_url'], + "profile": metadata['img_api']['profile']}, }, } @@ -80,13 +76,10 @@ def build_canvas(entity: Entity, img_url: str, img_api: dict[str, Any]): class IIIFImage(Resource): @staticmethod def get(version: int, id_: int) -> Response: - image_url = f"{app.config['IIIF']['url']}{id_}" - req = requests.get(f"{image_url}/info.json") - image_api = req.json() - return jsonify(IIIFImage.build_image(id_, image_url, image_api)) + return jsonify(IIIFImage.build_image(id_, get_iiif_metadata(id_))) @staticmethod - def build_image(id_: int, img_url: str, img_api: dict[str, Any]): + def build_image(id_: int, metadata: dict[str, Any]): return { "@context": "https://iiif.io/api/presentation/2/context.json", "@id": @@ -94,15 +87,15 @@ def build_image(id_: int, img_url: str, img_api: dict[str, Any]): "@type": "oa:Annotation", "motivation": "sc:painting", "resource": { - "@id": img_url, + "@id": metadata['img_url'], "@type": "dctypes:Image", "format": "image/jpeg", "service": { "@context": "http://iiif.io/api/image/2/context.json", - "@id": img_url, - "profile": img_api['profile']}, - "height": img_api['height'], - "width": img_api['width']}, + "@id": metadata['img_url'], + "profile": metadata['img_api']['profile']}, + "height": metadata['img_api']['height'], + "width": metadata['img_api']['width']}, "on": url_for('api.iiif_canvas', id_=id_, version=2, _external=True)} @@ -116,11 +109,8 @@ def get(version: int, id_: int) -> Response: @staticmethod def get_manifest_version_2(id_: int) -> dict[str, Any]: entity = get_entity_by_id(id_) - image_url = f"{app.config['IIIF']['url']}{id_}" - req = requests.get(f"{image_url}/info.json") - image_api = req.json() return { - "@context": "http://iiif.io/api/presentation/2/context.json", + "@context": "https://iiif.io/api/presentation/2/context.json", "@id": url_for('api.iiif_manifest', id_=id_, version=2), "@type": "sc:Manifest", "label": entity.name, @@ -131,5 +121,12 @@ def get_manifest_version_2(id_: int) -> dict[str, Any]: "license": get_license_name(entity), "attribution": "By OpenAtlas", "sequences": [ - IIIFSequence.build_sequence(entity, image_url, image_api)], + IIIFSequence.build_sequence(entity, get_iiif_metadata(id_))], "structures": []} + + +def get_iiif_metadata(id_: int) -> dict[str, Any]: + image_url = f"{app.config['IIIF']['url']}{id_}.tiff" + req = requests.get(f"{image_url}/info.json") + image_api = req.json() + return {'img_url': image_url, 'img_api': image_api} diff --git a/openatlas/display/util.py b/openatlas/display/util.py index 539f77ff3..c86837dd3 100644 --- a/openatlas/display/util.py +++ b/openatlas/display/util.py @@ -242,7 +242,7 @@ def profile_image(entity: Entity) -> str: style = f'max-width:200px;' ext = app.config["DISPLAY_FILE_EXTENSIONS"] src = \ - (f"{app.config['IIIF']['url']}{entity.id}" + (f"{app.config['IIIF']['url']}{entity.id}.tiff" f"/full/!200,200/0/default.png") elif g.settings['image_processing'] and check_processed_image(path.name): size = app.config['IMAGE_SIZE']['thumbnail'] @@ -762,14 +762,14 @@ def check_iiif_activation() -> bool: def check_iiif_file_exist(id_: int) -> bool: - return (Path(app.config['IIIF']['path']) / str(id_)).is_file() + return (Path(app.config['IIIF']['path']) / f'{id_}.tiff').is_file() def convert_image_to_iiif(id_: int) -> None: path = Path(app.config['IIIF']['path']) / str(id_) vips = "vips" if os.name == 'posix' else "vips.exe" command = \ - (f"{vips} tiffsave {get_file_path(id_)} {path} " + (f"{vips} tiffsave {get_file_path(id_)} {path}.tiff " f"--tile --pyramid --compression jpeg " f"--tile-width 256 --tile-height 256") try: From d33403f8bc842e756129755c7e830d869579ced8 Mon Sep 17 00:00:00 2001 From: bkoschicek Date: Thu, 5 Oct 2023 17:15:25 +0200 Subject: [PATCH 060/102] added external storage possibility --- config/iiif.py | 3 +- openatlas/api/endpoints/iiif.py | 47 ++++++++++++++-------------- openatlas/display/display.py | 4 ++- openatlas/templates/entity/view.html | 1 + 4 files changed, 30 insertions(+), 25 deletions(-) diff --git a/config/iiif.py b/config/iiif.py index 26db8788b..6b918c1e0 100644 --- a/config/iiif.py +++ b/config/iiif.py @@ -2,4 +2,5 @@ 'activate': False, 'path': '', 'url': '', - 'version': 2} \ No newline at end of file + 'version': 2, + 'conversion': True} \ No newline at end of file diff --git a/openatlas/api/endpoints/iiif.py b/openatlas/api/endpoints/iiif.py index c39eed32c..9abcc0412 100644 --- a/openatlas/api/endpoints/iiif.py +++ b/openatlas/api/endpoints/iiif.py @@ -4,9 +4,9 @@ from flask import jsonify, Response, url_for from flask_restful import Resource +from openatlas import app from openatlas.api.resources.model_mapper import get_entity_by_id from openatlas.api.resources.util import get_license_name -from openatlas import app from openatlas.models.entity import Entity @@ -15,16 +15,14 @@ class IIIFSequence(Resource): def get(version: int, id_: int) -> Response: return jsonify( {"@context": "https://iiif.io/api/presentation/2/context.json"} | - IIIFSequence.build_sequence( - get_entity_by_id(id_), - get_iiif_metadata(id_))) + IIIFSequence.build_sequence(get_metadata(get_entity_by_id(id_)))) @staticmethod - def build_sequence(entity: Entity, metadata: dict[str, Any]): + def build_sequence(metadata: dict[str, Any]): return { "@id": url_for( 'api.iiif_sequence', - id_=entity.id, + id_=metadata['entity'].id, version=2, _external=True), "@type": "sc:Sequence", @@ -32,7 +30,7 @@ def build_sequence(entity: Entity, metadata: dict[str, Any]): "@value": "Normal Sequence", "@language": "en"}], "canvases": [ - IIIFCanvas.build_canvas(entity, metadata)]} + IIIFCanvas.build_canvas(metadata)]} class IIIFCanvas(Resource): @@ -40,12 +38,11 @@ class IIIFCanvas(Resource): def get(version: int, id_: int) -> Response: return jsonify( {"@context": "https://iiif.io/api/presentation/2/context.json"} | - IIIFCanvas.build_canvas( - get_entity_by_id(id_), - get_iiif_metadata(id_))) + IIIFCanvas.build_canvas(get_metadata(get_entity_by_id(id_)))) @staticmethod - def build_canvas(entity: Entity, metadata: dict[str, Any]): + def build_canvas(metadata: dict[str, Any]): + entity = metadata['entity'] return { "@id": url_for( 'api.iiif_canvas', id_=entity.id, version=2, _external=True), @@ -57,7 +54,7 @@ def build_canvas(entity: Entity, metadata: dict[str, Any]): "@value": entity.description, "@language": "en"}, "images": [ - IIIFImage.build_image(entity.id, metadata)], + IIIFImage.build_image(metadata)], "related": "", "thumbnail": { "@id": f'{metadata["img_url"]}/full/!200,200/0/default.jpg', @@ -66,7 +63,7 @@ def build_canvas(entity: Entity, metadata: dict[str, Any]): "height": 200, "width": 200, "service": { - "@context": "http://iiif.io/api/image/2/context.json", + "@context": "https://iiif.io/api/image/2/context.json", "@id": metadata['img_url'], "profile": metadata['img_api']['profile']}, }, @@ -76,10 +73,12 @@ def build_canvas(entity: Entity, metadata: dict[str, Any]): class IIIFImage(Resource): @staticmethod def get(version: int, id_: int) -> Response: - return jsonify(IIIFImage.build_image(id_, get_iiif_metadata(id_))) + return jsonify( + IIIFImage.build_image(get_metadata(get_entity_by_id(id_)))) @staticmethod - def build_image(id_: int, metadata: dict[str, Any]): + def build_image(metadata: dict[str, Any]): + id_ = metadata['entity'].id return { "@context": "https://iiif.io/api/presentation/2/context.json", "@id": @@ -91,11 +90,11 @@ def build_image(id_: int, metadata: dict[str, Any]): "@type": "dctypes:Image", "format": "image/jpeg", "service": { - "@context": "http://iiif.io/api/image/2/context.json", - "@id": metadata['img_url'], + "@context": "https://iiif.io/api/image/2/context.json", + "@id": metadata['img_url'], "profile": metadata['img_api']['profile']}, - "height": metadata['img_api']['height'], - "width": metadata['img_api']['width']}, + "height": metadata['img_api']['height'], + "width": metadata['img_api']['width']}, "on": url_for('api.iiif_canvas', id_=id_, version=2, _external=True)} @@ -121,12 +120,14 @@ def get_manifest_version_2(id_: int) -> dict[str, Any]: "license": get_license_name(entity), "attribution": "By OpenAtlas", "sequences": [ - IIIFSequence.build_sequence(entity, get_iiif_metadata(id_))], + IIIFSequence.build_sequence(get_metadata(entity))], "structures": []} -def get_iiif_metadata(id_: int) -> dict[str, Any]: - image_url = f"{app.config['IIIF']['url']}{id_}.tiff" +def get_metadata(entity: Entity) -> dict[str, Any]: + ext = '.tiff' if app.config['IIIF']['conversion'] \ + else entity.get_file_extension() + image_url = f"{app.config['IIIF']['url']}{entity.id}{ext}" req = requests.get(f"{image_url}/info.json") image_api = req.json() - return {'img_url': image_url, 'img_api': image_api} + return {'entity': entity, 'img_url': image_url, 'img_api': image_api} diff --git a/openatlas/display/display.py b/openatlas/display/display.py index 46ccf4cef..4fdc333f6 100644 --- a/openatlas/display/display.py +++ b/openatlas/display/display.py @@ -5,6 +5,7 @@ from flask import g, url_for from flask_babel import lazy_gettext as _ +from openatlas import app from openatlas.display.base_display import ( ActorDisplay, BaseDisplay, EventsDisplay, PlaceBaseDisplay, ReferenceBaseDisplay, TypeBaseDisplay) @@ -75,7 +76,8 @@ def add_button_others(self) -> None: _('download'), url_for('download_file', filename=path.name))) if check_iiif_activation(): - if check_iiif_file_exist(self.entity.id): + if (check_iiif_file_exist(self.entity.id) + or not app.config['IIIF']['conversion']): self.buttons.append(button( _('iiif'), url_for('view_iiif', id_=self.entity.id))) diff --git a/openatlas/templates/entity/view.html b/openatlas/templates/entity/view.html index a5061a1a2..9fa437bf0 100644 --- a/openatlas/templates/entity/view.html +++ b/openatlas/templates/entity/view.html @@ -20,6 +20,7 @@ {{ entity.reference_systems|ext_references|safe }} {{ description_html|safe }} +{# todo: delete after development #} {% if iiif_image and entity.class_.name == 'file' %}
From 9bd24c20821919239283d88ca2bae007c1309d6f Mon Sep 17 00:00:00 2001 From: bkoschicek Date: Thu, 5 Oct 2023 17:47:22 +0200 Subject: [PATCH 061/102] iipsrv cannot convert to png --- openatlas/display/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openatlas/display/util.py b/openatlas/display/util.py index c86837dd3..3395800b6 100644 --- a/openatlas/display/util.py +++ b/openatlas/display/util.py @@ -243,7 +243,7 @@ def profile_image(entity: Entity) -> str: ext = app.config["DISPLAY_FILE_EXTENSIONS"] src = \ (f"{app.config['IIIF']['url']}{entity.id}.tiff" - f"/full/!200,200/0/default.png") + f"/full/!200,200/0/default.jpg") elif g.settings['image_processing'] and check_processed_image(path.name): size = app.config['IMAGE_SIZE']['thumbnail'] if path_ := get_file_path(entity.image_id, size): From 1e48fc81a6b81b30335090f4b10dbed36e2a01d3 Mon Sep 17 00:00:00 2001 From: BernhardKoschicek Date: Fri, 6 Oct 2023 10:56:24 +0200 Subject: [PATCH 062/102] IIIF images as table icon --- openatlas/display/base_display.py | 18 +++++++++--------- openatlas/display/util.py | 6 +++--- openatlas/views/entity_index.py | 15 +++++++++++---- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/openatlas/display/base_display.py b/openatlas/display/base_display.py index f7577acb4..34c1dc0fd 100644 --- a/openatlas/display/base_display.py +++ b/openatlas/display/base_display.py @@ -78,15 +78,15 @@ def get_type_data(self) -> dict[str, Any]: return {key: data[key] for key in sorted(data.keys())} def add_file_tab_thumbnails(self) -> None: - if 'file' in self.tabs \ - and current_user.settings['table_show_icons'] \ - and g.settings['image_processing']: - self.tabs['file'].table.header.insert(1, _('icon')) - for row in self.tabs['file'].table.rows: - row.insert(1, file_preview( - int(row[0] - .replace(' None: self.add_data() diff --git a/openatlas/display/util.py b/openatlas/display/util.py index 3395800b6..06219e229 100644 --- a/openatlas/display/util.py +++ b/openatlas/display/util.py @@ -240,10 +240,10 @@ def profile_image(entity: Entity) -> str: style = f'max-width:{g.settings["profile_image_width"]}px;' if app.config['IIIF']['activate'] and check_iiif_file_exist(entity.id): style = f'max-width:200px;' - ext = app.config["DISPLAY_FILE_EXTENSIONS"] src = \ (f"{app.config['IIIF']['url']}{entity.id}.tiff" f"/full/!200,200/0/default.jpg") + ext = app.config["ALLOWED_IMAGE_EXT"] elif g.settings['image_processing'] and check_processed_image(path.name): size = app.config['IMAGE_SIZE']['thumbnail'] if path_ := get_file_path(entity.image_id, size): @@ -770,8 +770,8 @@ def convert_image_to_iiif(id_: int) -> None: vips = "vips" if os.name == 'posix' else "vips.exe" command = \ (f"{vips} tiffsave {get_file_path(id_)} {path}.tiff " - f"--tile --pyramid --compression jpeg " - f"--tile-width 256 --tile-height 256") + f"--tile --pyramid --compression deflate --premultiply " + f"--tile-width 128 --tile-height 128") try: process = subprocess.Popen(command, shell=True) process.wait() diff --git a/openatlas/views/entity_index.py b/openatlas/views/entity_index.py index 6ca2cf84f..6c04d2487 100644 --- a/openatlas/views/entity_index.py +++ b/openatlas/views/entity_index.py @@ -10,7 +10,7 @@ from openatlas.display.table import Table from openatlas.display.util import ( button, format_date, get_base_table_data, get_file_path, is_authorized, - link, manual, required_group) + link, manual, required_group, check_iiif_file_exist) from openatlas.models.entity import Entity from openatlas.models.gis import Gis @@ -42,7 +42,7 @@ def get_table(view: str) -> Table: if view == 'file': table.order = [[0, 'desc']] table.header = ['date'] + table.header - if g.settings['image_processing'] \ + if (g.settings['image_processing'] or app.config['IIIF']['activate']) \ and current_user.settings['table_show_icons']: table.header.insert(1, _('icon')) for entity in Entity.get_by_class('file', types=True): @@ -53,7 +53,8 @@ def get_table(view: str) -> Table: entity.get_file_size(), entity.get_file_extension(), entity.description] - if g.settings['image_processing'] \ + if (g.settings['image_processing'] + or app.config['IIIF']['activate']) \ and current_user.settings['table_show_icons']: data.insert(1, file_preview(entity.id)) table.rows.append(data) @@ -77,7 +78,13 @@ def get_table(view: str) -> Table: def file_preview(entity_id: int) -> str: size = app.config['IMAGE_SIZE']['table'] - parameter = f"loading='lazy' alt='image' width='{size}'" + parameter = f"loading='lazy' alt='image' width='100px'" + if app.config['IIIF']['activate'] and check_iiif_file_exist(entity_id): + ext = '.tiff' if app.config['IIIF']['conversion'] \ + else g.files[entity_id].suffix + url = (f"{app.config['IIIF']['url']}{entity_id}{ext}" + f"/full/!100,100/0/default.jpg") + return f"" if icon_path := get_file_path( entity_id, app.config['IMAGE_SIZE']['table']): From 2d889125202897f716c866c194321cb883392213 Mon Sep 17 00:00:00 2001 From: BernhardKoschicek Date: Fri, 6 Oct 2023 10:59:54 +0200 Subject: [PATCH 063/102] fixed iiif thumbnail conversion false --- openatlas/display/util.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openatlas/display/util.py b/openatlas/display/util.py index 06219e229..ac776a94d 100644 --- a/openatlas/display/util.py +++ b/openatlas/display/util.py @@ -240,8 +240,10 @@ def profile_image(entity: Entity) -> str: style = f'max-width:{g.settings["profile_image_width"]}px;' if app.config['IIIF']['activate'] and check_iiif_file_exist(entity.id): style = f'max-width:200px;' + ext = '.tiff' if app.config['IIIF']['conversion'] \ + else g.files[entity.id].suffix src = \ - (f"{app.config['IIIF']['url']}{entity.id}.tiff" + (f"{app.config['IIIF']['url']}{entity.id}{ext}" f"/full/!200,200/0/default.jpg") ext = app.config["ALLOWED_IMAGE_EXT"] elif g.settings['image_processing'] and check_processed_image(path.name): From 4872e3f6f340c718df9db0d1510ea10eda2fb764 Mon Sep 17 00:00:00 2001 From: BernhardKoschicek Date: Fri, 6 Oct 2023 11:34:56 +0200 Subject: [PATCH 064/102] added compression option --- config/iiif.py | 4 +++- openatlas/display/util.py | 7 +++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/config/iiif.py b/config/iiif.py index 6b918c1e0..2562dfc47 100644 --- a/config/iiif.py +++ b/config/iiif.py @@ -3,4 +3,6 @@ 'path': '', 'url': '', 'version': 2, - 'conversion': True} \ No newline at end of file + 'conversion': True, + 'compression': 'deflate' # 'deflate' or 'jpeg' +} \ No newline at end of file diff --git a/openatlas/display/util.py b/openatlas/display/util.py index ac776a94d..a52c5793e 100644 --- a/openatlas/display/util.py +++ b/openatlas/display/util.py @@ -769,11 +769,14 @@ def check_iiif_file_exist(id_: int) -> bool: def convert_image_to_iiif(id_: int) -> None: path = Path(app.config['IIIF']['path']) / str(id_) + compression = app.config['IIIF']['compression'] \ + if app.config['IIIF']['compression'] in ['deflate', 'jpeg'] \ + else 'deflate' vips = "vips" if os.name == 'posix' else "vips.exe" command = \ (f"{vips} tiffsave {get_file_path(id_)} {path}.tiff " - f"--tile --pyramid --compression deflate --premultiply " - f"--tile-width 128 --tile-height 128") + f"--tile --pyramid --compression {compression} " + f"--premultiply --tile-width 128 --tile-height 128") try: process = subprocess.Popen(command, shell=True) process.wait() From 4a833cabb95377e6bb3434567f0babb3ec2b42d7 Mon Sep 17 00:00:00 2001 From: BernhardKoschicek Date: Fri, 6 Oct 2023 11:51:58 +0200 Subject: [PATCH 065/102] added deletion of iiif files --- openatlas/display/util.py | 7 +++++-- openatlas/views/entity.py | 5 ++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/openatlas/display/util.py b/openatlas/display/util.py index a52c5793e..61f1ece9f 100644 --- a/openatlas/display/util.py +++ b/openatlas/display/util.py @@ -767,14 +767,17 @@ def check_iiif_file_exist(id_: int) -> bool: return (Path(app.config['IIIF']['path']) / f'{id_}.tiff').is_file() +def get_iiif_file_path(id_: int) -> Path: + return Path(app.config['IIIF']['path']) / f'{id_}.tiff' + + def convert_image_to_iiif(id_: int) -> None: - path = Path(app.config['IIIF']['path']) / str(id_) compression = app.config['IIIF']['compression'] \ if app.config['IIIF']['compression'] in ['deflate', 'jpeg'] \ else 'deflate' vips = "vips" if os.name == 'posix' else "vips.exe" command = \ - (f"{vips} tiffsave {get_file_path(id_)} {path}.tiff " + (f"{vips} tiffsave {get_file_path(id_)} {get_iiif_file_path} " f"--tile --pyramid --compression {compression} " f"--premultiply --tile-width 128 --tile-height 128") try: diff --git a/openatlas/views/entity.py b/openatlas/views/entity.py index 518346168..97241d817 100644 --- a/openatlas/views/entity.py +++ b/openatlas/views/entity.py @@ -15,7 +15,7 @@ from openatlas.display.image_processing import resize_image from openatlas.display.util import ( button, get_base_table_data, get_file_path, is_authorized, link, - required_group) + required_group, get_iiif_file_path, check_iiif_file_exist) from openatlas.forms.base_manager import BaseManager from openatlas.forms.form import get_manager from openatlas.forms.util import was_modified @@ -331,3 +331,6 @@ def delete_files(id_: int) -> None: path.unlink() for resized_path in app.config['RESIZED_IMAGES'].glob(f'**/{id_}.*'): resized_path.unlink() + if app.config['IIIF']['activate'] and check_iiif_file_exist(id_): + if path := get_iiif_file_path(id_): + path.unlink() From 35888bb601c54089c01dfed170cbfee7d0ec9a14 Mon Sep 17 00:00:00 2001 From: BernhardKoschicek Date: Fri, 6 Oct 2023 13:24:39 +0200 Subject: [PATCH 066/102] fixed bug in creation False --- config/default.py | 1 + openatlas/display/base_display.py | 2 +- openatlas/display/util.py | 13 ++++++++++--- openatlas/views/entity_index.py | 3 ++- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/config/default.py b/config/default.py index aab3fed06..3860ecbcc 100644 --- a/config/default.py +++ b/config/default.py @@ -44,6 +44,7 @@ NONE_DISPLAY_EXT = ['.tiff', '.tif'] ALLOWED_IMAGE_EXT = DISPLAY_FILE_EXTENSIONS + NONE_DISPLAY_EXT PROCESSED_EXT = '.jpeg' +IIIF_IMAGE_EXT = ALLOWED_IMAGE_EXT + ['.pdf'] # For system checks WRITABLE_PATHS = [ diff --git a/openatlas/display/base_display.py b/openatlas/display/base_display.py index 34c1dc0fd..a3f6e7b11 100644 --- a/openatlas/display/base_display.py +++ b/openatlas/display/base_display.py @@ -102,7 +102,7 @@ def add_info_tab_content(self) -> None: chart_data=self.get_chart_data(), description_html=self.description_html(), problematic_type_id=self.problematic_type, - iiif_image=check_iiif_file_exist(self.entity.image_id)) + iiif_image=check_iiif_file_exist(self.entity.id)) def description_html(self) -> str: return description(self.entity.description) diff --git a/openatlas/display/util.py b/openatlas/display/util.py index 61f1ece9f..b2c941897 100644 --- a/openatlas/display/util.py +++ b/openatlas/display/util.py @@ -255,7 +255,9 @@ def profile_image(entity: Entity) -> str: if entity.class_.view == 'file': html = \ '

' + _('no preview available') + '

' - if path.suffix.lower() in ext: + if (path.suffix.lower() in ext or + (app.config['IIIF']['activate'] + and path.suffix.lower() == '.pdf')): html = link( f'image', src, @@ -764,11 +766,16 @@ def check_iiif_activation() -> bool: def check_iiif_file_exist(id_: int) -> bool: - return (Path(app.config['IIIF']['path']) / f'{id_}.tiff').is_file() + if app.config['IIIF']['conversion']: + return get_iiif_file_path(id_).is_file() + else: + return bool(get_file_path(id_)) def get_iiif_file_path(id_: int) -> Path: - return Path(app.config['IIIF']['path']) / f'{id_}.tiff' + ext = '.tiff' if app.config['IIIF']['conversion'] \ + else g.files[id_].suffix + return Path(app.config['IIIF']['path']) / f'{id_}{ext}' def convert_image_to_iiif(id_: int) -> None: diff --git a/openatlas/views/entity_index.py b/openatlas/views/entity_index.py index 6c04d2487..99e39c10c 100644 --- a/openatlas/views/entity_index.py +++ b/openatlas/views/entity_index.py @@ -84,7 +84,8 @@ def file_preview(entity_id: int) -> str: else g.files[entity_id].suffix url = (f"{app.config['IIIF']['url']}{entity_id}{ext}" f"/full/!100,100/0/default.jpg") - return f"" + return f"" \ + if ext in app.config["IIIF_IMAGE_EXT"] else '' if icon_path := get_file_path( entity_id, app.config['IMAGE_SIZE']['table']): From f523aef1bd9f6094f504fed0095b130158e5588f Mon Sep 17 00:00:00 2001 From: BernhardKoschicek Date: Fri, 6 Oct 2023 13:28:26 +0200 Subject: [PATCH 067/102] removed IIIF button, if not valid ext --- openatlas/display/display.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openatlas/display/display.py b/openatlas/display/display.py index 4fdc333f6..9ed577281 100644 --- a/openatlas/display/display.py +++ b/openatlas/display/display.py @@ -75,7 +75,9 @@ def add_button_others(self) -> None: self.buttons.append(button( _('download'), url_for('download_file', filename=path.name))) - if check_iiif_activation(): + if (check_iiif_activation() + and self.entity.get_file_extension() + in app.config['IIIF_IMAGE_EXT']): if (check_iiif_file_exist(self.entity.id) or not app.config['IIIF']['conversion']): self.buttons.append(button( From 8a4f0d5024afeee0310de24f1954a96d8e7133f8 Mon Sep 17 00:00:00 2001 From: BernhardKoschicek Date: Fri, 6 Oct 2023 14:09:15 +0200 Subject: [PATCH 068/102] removed embedded iiif and added thumbnail link --- openatlas/display/base_display.py | 3 +-- openatlas/display/util.py | 8 +++++++- openatlas/templates/entity/view.html | 7 ------- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/openatlas/display/base_display.py b/openatlas/display/base_display.py index a3f6e7b11..d53a2d70e 100644 --- a/openatlas/display/base_display.py +++ b/openatlas/display/base_display.py @@ -101,8 +101,7 @@ def add_info_tab_content(self) -> None: overlays=self.overlays, chart_data=self.get_chart_data(), description_html=self.description_html(), - problematic_type_id=self.problematic_type, - iiif_image=check_iiif_file_exist(self.entity.id)) + problematic_type_id=self.problematic_type) def description_html(self) -> str: return description(self.entity.description) diff --git a/openatlas/display/util.py b/openatlas/display/util.py index b2c941897..7c1187296 100644 --- a/openatlas/display/util.py +++ b/openatlas/display/util.py @@ -237,6 +237,7 @@ def profile_image(entity: Entity) -> str: return '' # pragma: no cover ext = app.config["DISPLAY_FILE_EXTENSIONS"] src = url_for('display_file', filename=path.name) + url = src style = f'max-width:{g.settings["profile_image_width"]}px;' if app.config['IIIF']['activate'] and check_iiif_file_exist(entity.id): style = f'max-width:200px;' @@ -245,12 +246,17 @@ def profile_image(entity: Entity) -> str: src = \ (f"{app.config['IIIF']['url']}{entity.id}{ext}" f"/full/!200,200/0/default.jpg") + url = url_for( + 'view_iiif', + id_=entity.id, + version=app.config['IIIF']['version']) ext = app.config["ALLOWED_IMAGE_EXT"] elif g.settings['image_processing'] and check_processed_image(path.name): size = app.config['IMAGE_SIZE']['thumbnail'] if path_ := get_file_path(entity.image_id, size): src = url_for('display_file', filename=path_.name, size=size) style = f'max-width:{app.config["IMAGE_SIZE"]["thumbnail"]}px;' + url = url_for('display_file', filename=path_.name, size=size) ext = app.config["ALLOWED_IMAGE_EXT"] if entity.class_.view == 'file': html = \ @@ -260,7 +266,7 @@ def profile_image(entity: Entity) -> str: and path.suffix.lower() == '.pdf')): html = link( f'image', - src, + url, external=True) else: html = link( diff --git a/openatlas/templates/entity/view.html b/openatlas/templates/entity/view.html index 9fa437bf0..f250617c6 100644 --- a/openatlas/templates/entity/view.html +++ b/openatlas/templates/entity/view.html @@ -19,13 +19,6 @@
{{ entity.reference_systems|ext_references|safe }} {{ description_html|safe }} -
-{# todo: delete after development #} - {% if iiif_image and entity.class_.name == 'file' %} -
- -
- {% endif %} {% if gis_data %}
From 91709f261dab94c6cd328299a96d695e70eb751c Mon Sep 17 00:00:00 2001 From: BernhardKoschicek Date: Fri, 6 Oct 2023 14:14:24 +0200 Subject: [PATCH 069/102] added subprocess exception --- openatlas/display/util.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/openatlas/display/util.py b/openatlas/display/util.py index 7c1187296..da8ce78a7 100644 --- a/openatlas/display/util.py +++ b/openatlas/display/util.py @@ -66,8 +66,8 @@ def ext_references(links: list[Link]) -> str: f'{system.resolver_url}{link_.description}', external=True) if system.resolver_url else link_.description html += \ - f' ({ g.types[link_.type.id].name } ' + _('at') + \ - f' { link(link_.domain) })
' + f' ({g.types[link_.type.id].name} ' + _('at') + \ + f' {link(link_.domain)})
' return html @@ -246,10 +246,7 @@ def profile_image(entity: Entity) -> str: src = \ (f"{app.config['IIIF']['url']}{entity.id}{ext}" f"/full/!200,200/0/default.jpg") - url = url_for( - 'view_iiif', - id_=entity.id, - version=app.config['IIIF']['version']) + url = url_for('view_iiif', id_=entity.id) ext = app.config["ALLOWED_IMAGE_EXT"] elif g.settings['image_processing'] and check_processed_image(path.name): size = app.config['IMAGE_SIZE']['thumbnail'] @@ -797,6 +794,5 @@ def convert_image_to_iiif(id_: int) -> None: process = subprocess.Popen(command, shell=True) process.wait() flash(_('iiif converted'), 'info') - except Exception: - flash(_('failed to convert image'), 'error') - + except subprocess.CalledProcessError as e: + flash(f"{_('failed to convert image')}: {e}", 'error') From 69839342907399c478f58b8b0f42c175b189885b Mon Sep 17 00:00:00 2001 From: BernhardKoschicek Date: Fri, 6 Oct 2023 15:58:03 +0200 Subject: [PATCH 070/102] fixed convert and table images --- openatlas/api/endpoints/iiif.py | 7 ++++++- openatlas/display/util.py | 4 ++-- openatlas/views/entity_index.py | 8 ++++---- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/openatlas/api/endpoints/iiif.py b/openatlas/api/endpoints/iiif.py index 9abcc0412..3e2260c0b 100644 --- a/openatlas/api/endpoints/iiif.py +++ b/openatlas/api/endpoints/iiif.py @@ -110,7 +110,12 @@ def get_manifest_version_2(id_: int) -> dict[str, Any]: entity = get_entity_by_id(id_) return { "@context": "https://iiif.io/api/presentation/2/context.json", - "@id": url_for('api.iiif_manifest', id_=id_, version=2), + "@id": + url_for( + 'api.iiif_manifest', + id_=id_, + version=2, + _external=True), "@type": "sc:Manifest", "label": entity.name, "metadata": [], diff --git a/openatlas/display/util.py b/openatlas/display/util.py index da8ce78a7..a3f2a93f7 100644 --- a/openatlas/display/util.py +++ b/openatlas/display/util.py @@ -787,12 +787,12 @@ def convert_image_to_iiif(id_: int) -> None: else 'deflate' vips = "vips" if os.name == 'posix' else "vips.exe" command = \ - (f"{vips} tiffsave {get_file_path(id_)} {get_iiif_file_path} " + (f"{vips} tiffsave {get_file_path(id_)} {get_iiif_file_path(id_)} " f"--tile --pyramid --compression {compression} " f"--premultiply --tile-width 128 --tile-height 128") try: process = subprocess.Popen(command, shell=True) process.wait() flash(_('iiif converted'), 'info') - except subprocess.CalledProcessError as e: + except Exception as e: flash(f"{_('failed to convert image')}: {e}", 'error') diff --git a/openatlas/views/entity_index.py b/openatlas/views/entity_index.py index 99e39c10c..48724c9fc 100644 --- a/openatlas/views/entity_index.py +++ b/openatlas/views/entity_index.py @@ -78,22 +78,22 @@ def get_table(view: str) -> Table: def file_preview(entity_id: int) -> str: size = app.config['IMAGE_SIZE']['table'] - parameter = f"loading='lazy' alt='image' width='100px'" + param = f"loading='lazy' alt='image' max-width='100px' max-height='100px'" if app.config['IIIF']['activate'] and check_iiif_file_exist(entity_id): ext = '.tiff' if app.config['IIIF']['conversion'] \ else g.files[entity_id].suffix url = (f"{app.config['IIIF']['url']}{entity_id}{ext}" f"/full/!100,100/0/default.jpg") - return f"" \ + return f"" \ if ext in app.config["IIIF_IMAGE_EXT"] else '' if icon_path := get_file_path( entity_id, app.config['IMAGE_SIZE']['table']): url = url_for('display_file', filename=icon_path.name, size=size) - return f"" + return f"" path = get_file_path(entity_id) if path and check_processed_image(path.name): if icon := get_file_path(entity_id, app.config['IMAGE_SIZE']['table']): url = url_for('display_file', filename=icon.name, size=size) - return f"" + return f"" return '' From 83f5938e8b2aa43eee1bae73a58669896a4bdf68 Mon Sep 17 00:00:00 2001 From: BernhardKoschicek Date: Fri, 6 Oct 2023 16:39:29 +0200 Subject: [PATCH 071/102] moved config variable --- config/default.py | 1 - config/iiif.py | 6 +++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/config/default.py b/config/default.py index 3860ecbcc..aab3fed06 100644 --- a/config/default.py +++ b/config/default.py @@ -44,7 +44,6 @@ NONE_DISPLAY_EXT = ['.tiff', '.tif'] ALLOWED_IMAGE_EXT = DISPLAY_FILE_EXTENSIONS + NONE_DISPLAY_EXT PROCESSED_EXT = '.jpeg' -IIIF_IMAGE_EXT = ALLOWED_IMAGE_EXT + ['.pdf'] # For system checks WRITABLE_PATHS = [ diff --git a/config/iiif.py b/config/iiif.py index 2562dfc47..574796e07 100644 --- a/config/iiif.py +++ b/config/iiif.py @@ -1,3 +1,5 @@ +from config.default import ALLOWED_IMAGE_EXT + IIIF = { 'activate': False, 'path': '', @@ -5,4 +7,6 @@ 'version': 2, 'conversion': True, 'compression': 'deflate' # 'deflate' or 'jpeg' -} \ No newline at end of file +} + +IIIF_IMAGE_EXT = ALLOWED_IMAGE_EXT + ['.pdf'] \ No newline at end of file From 75f00526fe125f26e6ac6b8fd75e001fb63ada76 Mon Sep 17 00:00:00 2001 From: bkoschicek Date: Mon, 9 Oct 2023 12:20:38 +0200 Subject: [PATCH 072/102] added Logo to manifest --- config/iiif.py | 2 +- openatlas/api/endpoints/iiif.py | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/config/iiif.py b/config/iiif.py index 574796e07..9ef935d74 100644 --- a/config/iiif.py +++ b/config/iiif.py @@ -9,4 +9,4 @@ 'compression': 'deflate' # 'deflate' or 'jpeg' } -IIIF_IMAGE_EXT = ALLOWED_IMAGE_EXT + ['.pdf'] \ No newline at end of file +IIIF_IMAGE_EXT = ALLOWED_IMAGE_EXT + ['.pdf'] diff --git a/openatlas/api/endpoints/iiif.py b/openatlas/api/endpoints/iiif.py index 3e2260c0b..cbbbe8c7f 100644 --- a/openatlas/api/endpoints/iiif.py +++ b/openatlas/api/endpoints/iiif.py @@ -1,7 +1,7 @@ from typing import Any import requests -from flask import jsonify, Response, url_for +from flask import jsonify, Response, url_for, g, request from flask_restful import Resource from openatlas import app @@ -124,6 +124,7 @@ def get_manifest_version_2(id_: int) -> dict[str, Any]: "@language": "en"}], "license": get_license_name(entity), "attribution": "By OpenAtlas", + "logo": get_logo(), "sequences": [ IIIFSequence.build_sequence(get_metadata(entity))], "structures": []} @@ -136,3 +137,17 @@ def get_metadata(entity: Entity) -> dict[str, Any]: req = requests.get(f"{image_url}/info.json") image_api = req.json() return {'entity': entity, 'img_url': image_url, 'img_api': image_api} + + +def get_logo() -> dict[str, Any]: + return { + "@id": url_for( + 'api.display', + filename=g.settings['logo_file_id'], + _external=True), + "service": { + "@context": "http://iiif.io/api/image/2/context.json", + "@id": url_for('overview', _external=True), + "profile": "http://iiif.io/api/image/2/level2.json" + } + } From 03b9d831ee5e39425af921a61a1dc7e10a9f60fc Mon Sep 17 00:00:00 2001 From: bkoschicek Date: Mon, 9 Oct 2023 13:01:36 +0200 Subject: [PATCH 073/102] fixed tables in entity view --- openatlas/templates/entity/view.html | 29 ++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/openatlas/templates/entity/view.html b/openatlas/templates/entity/view.html index f250617c6..05b48f6d9 100644 --- a/openatlas/templates/entity/view.html +++ b/openatlas/templates/entity/view.html @@ -19,6 +19,7 @@
{{ entity.reference_systems|ext_references|safe }} {{ description_html|safe }} +
{% if gis_data %}
@@ -34,20 +35,20 @@ {% endif %}
From 2a66ef62eb1a6c08b9d7a552494450ab3cfc9740 Mon Sep 17 00:00:00 2001 From: bkoschicek Date: Mon, 9 Oct 2023 13:17:38 +0200 Subject: [PATCH 074/102] added version number to class name --- openatlas/api/endpoints/iiif.py | 32 +++++++++++++++----------------- openatlas/api/routes.py | 19 +++++++++---------- 2 files changed, 24 insertions(+), 27 deletions(-) diff --git a/openatlas/api/endpoints/iiif.py b/openatlas/api/endpoints/iiif.py index cbbbe8c7f..30aa16a76 100644 --- a/openatlas/api/endpoints/iiif.py +++ b/openatlas/api/endpoints/iiif.py @@ -1,7 +1,7 @@ from typing import Any import requests -from flask import jsonify, Response, url_for, g, request +from flask import jsonify, Response, url_for, g from flask_restful import Resource from openatlas import app @@ -10,12 +10,12 @@ from openatlas.models.entity import Entity -class IIIFSequence(Resource): +class IIIFSequenceV2(Resource): @staticmethod - def get(version: int, id_: int) -> Response: + def get(id_: int) -> Response: return jsonify( {"@context": "https://iiif.io/api/presentation/2/context.json"} | - IIIFSequence.build_sequence(get_metadata(get_entity_by_id(id_)))) + IIIFSequenceV2.build_sequence(get_metadata(get_entity_by_id(id_)))) @staticmethod def build_sequence(metadata: dict[str, Any]): @@ -30,15 +30,15 @@ def build_sequence(metadata: dict[str, Any]): "@value": "Normal Sequence", "@language": "en"}], "canvases": [ - IIIFCanvas.build_canvas(metadata)]} + IIIFCanvasV2.build_canvas(metadata)]} -class IIIFCanvas(Resource): +class IIIFCanvasV2(Resource): @staticmethod - def get(version: int, id_: int) -> Response: + def get(id_: int) -> Response: return jsonify( {"@context": "https://iiif.io/api/presentation/2/context.json"} | - IIIFCanvas.build_canvas(get_metadata(get_entity_by_id(id_)))) + IIIFCanvasV2.build_canvas(get_metadata(get_entity_by_id(id_)))) @staticmethod def build_canvas(metadata: dict[str, Any]): @@ -54,7 +54,7 @@ def build_canvas(metadata: dict[str, Any]): "@value": entity.description, "@language": "en"}, "images": [ - IIIFImage.build_image(metadata)], + IIIFImageV2.build_image(metadata)], "related": "", "thumbnail": { "@id": f'{metadata["img_url"]}/full/!200,200/0/default.jpg', @@ -70,11 +70,11 @@ def build_canvas(metadata: dict[str, Any]): } -class IIIFImage(Resource): +class IIIFImageV2(Resource): @staticmethod - def get(version: int, id_: int) -> Response: + def get(id_: int) -> Response: return jsonify( - IIIFImage.build_image(get_metadata(get_entity_by_id(id_)))) + IIIFImageV2.build_image(get_metadata(get_entity_by_id(id_)))) @staticmethod def build_image(metadata: dict[str, Any]): @@ -126,7 +126,7 @@ def get_manifest_version_2(id_: int) -> dict[str, Any]: "attribution": "By OpenAtlas", "logo": get_logo(), "sequences": [ - IIIFSequence.build_sequence(get_metadata(entity))], + IIIFSequenceV2.build_sequence(get_metadata(entity))], "structures": []} @@ -146,8 +146,6 @@ def get_logo() -> dict[str, Any]: filename=g.settings['logo_file_id'], _external=True), "service": { - "@context": "http://iiif.io/api/image/2/context.json", + "@context": "https://iiif.io/api/image/2/context.json", "@id": url_for('overview', _external=True), - "profile": "http://iiif.io/api/image/2/level2.json" - } - } + "profile": "https://iiif.io/api/image/2/level2.json"}} diff --git a/openatlas/api/routes.py b/openatlas/api/routes.py index a2834ca86..bf18cb7ae 100644 --- a/openatlas/api/routes.py +++ b/openatlas/api/routes.py @@ -1,19 +1,18 @@ from flask_restful import Api -from openatlas.api.endpoints.iiif import IIIFManifest, IIIFImage, IIIFCanvas, \ - IIIFSequence +from openatlas.api.endpoints.iiif import \ + (IIIFManifest, IIIFImageV2, IIIFCanvasV2, IIIFSequenceV2) from openatlas.api.endpoints.content import ClassMapping, \ GetContent, SystemClassCount from openatlas.api.endpoints.special import GetGeometricEntities, \ ExportDatabase, GetSubunits -from openatlas.api.endpoints.display_image import (DisplayImage, - LicensedFileOverview) +from openatlas.api.endpoints.display_image import \ + (DisplayImage, LicensedFileOverview) from openatlas.api.endpoints.entities import GetByCidocClass, \ GetBySystemClass, GetByViewClass, GetEntitiesLinkedToEntity, GetEntity, \ GetLatest, GetQuery, GetTypeEntities, GetTypeEntitiesAll -from openatlas.api.endpoints.type import GetTypeByViewClass, \ - GetTypeOverview, \ - GetTypeTree +from openatlas.api.endpoints.type import \ + (GetTypeByViewClass, GetTypeOverview, GetTypeTree) def add_routes_v03(api: Api) -> None: @@ -106,14 +105,14 @@ def add_routes_v03(api: Api) -> None: '/iiif_manifest//', endpoint='iiif_manifest') api.add_resource( - IIIFImage, + IIIFImageV2, '/iiif_image//.json', endpoint='iiif_image') api.add_resource( - IIIFCanvas, + IIIFCanvasV2, '/iiif_canvas//.json', endpoint='iiif_canvas') api.add_resource( - IIIFSequence, + IIIFSequenceV2, '/iiif_sequence//.json', endpoint='iiif_sequence') From 3b064dfe788e9eae57229ca6fab0126ac452741e Mon Sep 17 00:00:00 2001 From: bkoschicek Date: Mon, 9 Oct 2023 17:19:49 +0200 Subject: [PATCH 075/102] refactored profile_image function with Alex --- openatlas/display/util.py | 60 +++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/openatlas/display/util.py b/openatlas/display/util.py index a3f2a93f7..f96f64af3 100644 --- a/openatlas/display/util.py +++ b/openatlas/display/util.py @@ -235,47 +235,44 @@ def profile_image(entity: Entity) -> str: path = get_file_path(entity.image_id) if not path: return '' # pragma: no cover - ext = app.config["DISPLAY_FILE_EXTENSIONS"] + display_ext = app.config["DISPLAY_FILE_EXTENSIONS"] src = url_for('display_file', filename=path.name) url = src - style = f'max-width:{g.settings["profile_image_width"]}px;' + width = g.settings["profile_image_width"] if app.config['IIIF']['activate'] and check_iiif_file_exist(entity.id): - style = f'max-width:200px;' - ext = '.tiff' if app.config['IIIF']['conversion'] \ + url = url_for('view_iiif', id_=entity.id) + display_ext = app.config["IIIF_IMAGE_EXT"] + iiif_ext = '.tiff' if app.config['IIIF']['conversion'] \ else g.files[entity.id].suffix src = \ - (f"{app.config['IIIF']['url']}{entity.id}{ext}" - f"/full/!200,200/0/default.jpg") - url = url_for('view_iiif', id_=entity.id) - ext = app.config["ALLOWED_IMAGE_EXT"] + f"{app.config['IIIF']['url']}{entity.id}{iiif_ext}" \ + f"/full/!{width},{width}/0/default.jpg" elif g.settings['image_processing'] and check_processed_image(path.name): - size = app.config['IMAGE_SIZE']['thumbnail'] - if path_ := get_file_path(entity.image_id, size): - src = url_for('display_file', filename=path_.name, size=size) - style = f'max-width:{app.config["IMAGE_SIZE"]["thumbnail"]}px;' - url = url_for('display_file', filename=path_.name, size=size) - ext = app.config["ALLOWED_IMAGE_EXT"] + display_ext = app.config["ALLOWED_IMAGE_EXT"] + src = url_for( + 'display_file', + size=app.config['IMAGE_SIZE']['thumbnail'], + filename=get_file_path( + entity.image_id, + app.config['IMAGE_SIZE']['thumbnail']).name) + external = False if entity.class_.view == 'file': - html = \ - '

' + _('no preview available') + '

' - if (path.suffix.lower() in ext or - (app.config['IIIF']['activate'] - and path.suffix.lower() == '.pdf')): - html = link( - f'image', - url, - external=True) + external = True + if path.suffix.lower() not in display_ext: + return '

' + _('no preview available') + '

' else: - html = link( - f'image', - url_for('view', id_=entity.image_id)) - return f'{html}' + url = url_for('view', id_=entity.image_id) + html = link( + f'{entity.name}', + url, + external=external) + return html @app.template_filter() def get_js_messages(lang: str) -> str: js_message_file = Path('static') / 'vendor' / 'jquery_validation_plugin' \ - / f'messages_{lang}.js' + / f'messages_{lang}.js' if not (Path(app.root_path) / js_message_file).is_file(): return '' return f'' @@ -324,7 +321,7 @@ def get_backup_file_data() -> dict[str, Any]: latest_file = None latest_file_date = None for file in [ - f for f in path.iterdir() + f for f in path.iterdir() if (path / f).is_file() and f.name != '.gitignore']: file_date = datetime.utcfromtimestamp((path / file).stat().st_ctime) if not latest_file_date or file_date > latest_file_date: @@ -658,8 +655,9 @@ def manual(site: str) -> str: return '' return \ '
' + f'href="/static/manual/{site}.html" class="manual" ' \ + f'target="_blank" rel="noopener noreferrer">' \ + f'' @app.template_filter() From 8300ad5b9655ad074a24cee87f750dbcdd067c3b Mon Sep 17 00:00:00 2001 From: bkoschicek Date: Tue, 10 Oct 2023 12:46:09 +0200 Subject: [PATCH 076/102] mypy fixes --- openatlas/api/endpoints/iiif.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openatlas/api/endpoints/iiif.py b/openatlas/api/endpoints/iiif.py index 30aa16a76..4ab09b13a 100644 --- a/openatlas/api/endpoints/iiif.py +++ b/openatlas/api/endpoints/iiif.py @@ -18,7 +18,7 @@ def get(id_: int) -> Response: IIIFSequenceV2.build_sequence(get_metadata(get_entity_by_id(id_)))) @staticmethod - def build_sequence(metadata: dict[str, Any]): + def build_sequence(metadata: dict[str, Any]) -> dict[str, str]: return { "@id": url_for( 'api.iiif_sequence', @@ -41,7 +41,7 @@ def get(id_: int) -> Response: IIIFCanvasV2.build_canvas(get_metadata(get_entity_by_id(id_)))) @staticmethod - def build_canvas(metadata: dict[str, Any]): + def build_canvas(metadata: dict[str, Any]) -> dict[str, str]: entity = metadata['entity'] return { "@id": url_for( @@ -77,7 +77,7 @@ def get(id_: int) -> Response: IIIFImageV2.build_image(get_metadata(get_entity_by_id(id_)))) @staticmethod - def build_image(metadata: dict[str, Any]): + def build_image(metadata: dict[str, Any]) -> dict[str, str]: id_ = metadata['entity'].id return { "@context": "https://iiif.io/api/presentation/2/context.json", From 72ef09858231266e0e1784ac723c13c8f54f620f Mon Sep 17 00:00:00 2001 From: Alexander Watzinger Date: Tue, 10 Oct 2023 12:54:54 +0200 Subject: [PATCH 077/102] Mypy checks --- openatlas/display/util.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/openatlas/display/util.py b/openatlas/display/util.py index f96f64af3..55892e629 100644 --- a/openatlas/display/util.py +++ b/openatlas/display/util.py @@ -249,12 +249,13 @@ def profile_image(entity: Entity) -> str: f"/full/!{width},{width}/0/default.jpg" elif g.settings['image_processing'] and check_processed_image(path.name): display_ext = app.config["ALLOWED_IMAGE_EXT"] - src = url_for( - 'display_file', - size=app.config['IMAGE_SIZE']['thumbnail'], - filename=get_file_path( + if path_ := get_file_path( entity.image_id, - app.config['IMAGE_SIZE']['thumbnail']).name) + app.config['IMAGE_SIZE']['thumbnail']): + src = url_for( + 'display_file', + size=app.config['IMAGE_SIZE']['thumbnail'], + filename=path_.name) external = False if entity.class_.view == 'file': external = True @@ -456,7 +457,7 @@ def system_warnings(_context: str, _unneeded_string: str) -> str: f'{"
".join(warnings)}' if warnings else '' -def check_write_access(path, warnings): +def check_write_access(path: Path, warnings: list[str]) -> list[str]: if not os.access(path, os.W_OK): warnings.append( '

' + _('directory not writable') + From a722bb87b654716091d35c478ef710c890ce384c Mon Sep 17 00:00:00 2001 From: Alexander Watzinger Date: Tue, 10 Oct 2023 13:06:31 +0200 Subject: [PATCH 078/102] Tests --- openatlas/display/util.py | 3 +-- tests/test_file.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/openatlas/display/util.py b/openatlas/display/util.py index 55892e629..6aad26b68 100644 --- a/openatlas/display/util.py +++ b/openatlas/display/util.py @@ -232,8 +232,7 @@ def display_menu(entity: Optional[Entity], origin: Optional[Entity]) -> str: def profile_image(entity: Entity) -> str: if not entity.image_id: return '' - path = get_file_path(entity.image_id) - if not path: + if not (path := get_file_path(entity.image_id)): return '' # pragma: no cover display_ext = app.config["DISPLAY_FILE_EXTENSIONS"] src = url_for('display_file', filename=path.name) diff --git a/tests/test_file.py b/tests/test_file.py index c949381c4..378ba35fb 100644 --- a/tests/test_file.py +++ b/tests/test_file.py @@ -93,7 +93,7 @@ def test_file(self) -> None: assert b'File keeper' in rv.data rv = self.app.get(url_for('update', id_=place.id)) - assert b'alt="image"' in rv.data + assert b'File keeper' in rv.data rv = self.app.post( url_for('entity_add_file', id_=get_hierarchy('Sex').subs[0]), From 8864339e073d058134673933faff22ca47ba1160 Mon Sep 17 00:00:00 2001 From: Alexander Watzinger Date: Tue, 10 Oct 2023 15:02:37 +0200 Subject: [PATCH 079/102] Refactored image processing config with Bernhard --- config/default.py | 17 ++++++++---- config/iiif.py | 12 -------- openatlas/__init__.py | 5 +++- openatlas/api/endpoints/iiif.py | 2 +- openatlas/api/import_scripts/vocabs.py | 8 +++--- openatlas/display/base_display.py | 22 ++++++--------- openatlas/display/display.py | 11 ++++---- openatlas/display/image_processing.py | 38 ++++++++++++-------------- openatlas/display/tab.py | 4 +-- openatlas/display/util.py | 23 +++++----------- openatlas/models/entity.py | 7 ++--- openatlas/views/admin.py | 5 ++-- openatlas/views/entity.py | 12 ++++---- openatlas/views/entity_index.py | 2 +- sphinx/source/entity/file.rst | 2 +- 15 files changed, 74 insertions(+), 96 deletions(-) delete mode 100644 config/iiif.py diff --git a/config/default.py b/config/default.py index aab3fed06..18973cd33 100644 --- a/config/default.py +++ b/config/default.py @@ -23,9 +23,6 @@ 'es': 'Español', 'fr': 'Français'} -# Files with these extensions are can be displayed in the browser -DISPLAY_FILE_EXTENSIONS = \ - ['.bmp', '.gif', '.ico', '.jpeg', '.jpg', '.png', '.svg'] # Paths are implemented operating system independent using pathlib. # To override them (in instance/production.py) either use them like here @@ -36,14 +33,22 @@ TMP_PATH = Path('/tmp') # used e.g. for processing imports and export files # Image processing +DISPLAY_FILE_EXT = ['.bmp', '.gif', '.ico', '.jpeg', '.jpg', '.png', '.svg'] +PROCESSABLE_EXT = ['.tiff', '.tif'] +PROCESSED_EXT = '.jpeg' + PROCESSED_IMAGE_PATH = Path(FILES_PATH) / 'processed_images' RESIZED_IMAGES = Path(PROCESSED_IMAGE_PATH) / 'resized' IMAGE_SIZE = { 'thumbnail': '200', 'table': '100'} -NONE_DISPLAY_EXT = ['.tiff', '.tif'] -ALLOWED_IMAGE_EXT = DISPLAY_FILE_EXTENSIONS + NONE_DISPLAY_EXT -PROCESSED_EXT = '.jpeg' +IIIF = { + 'activate': False, + 'path': '', + 'url': '', + 'version': 2, + 'conversion': True, + 'compression': 'deflate'} # 'deflate' or 'jpeg' # For system checks WRITABLE_PATHS = [ diff --git a/config/iiif.py b/config/iiif.py deleted file mode 100644 index 9ef935d74..000000000 --- a/config/iiif.py +++ /dev/null @@ -1,12 +0,0 @@ -from config.default import ALLOWED_IMAGE_EXT - -IIIF = { - 'activate': False, - 'path': '', - 'url': '', - 'version': 2, - 'conversion': True, - 'compression': 'deflate' # 'deflate' or 'jpeg' -} - -IIIF_IMAGE_EXT = ALLOWED_IMAGE_EXT + ['.pdf'] diff --git a/openatlas/__init__.py b/openatlas/__init__.py index 6228f3f9a..6325f9040 100644 --- a/openatlas/__init__.py +++ b/openatlas/__init__.py @@ -14,7 +14,6 @@ csrf = CSRFProtect(app) # Make sure all forms are CSRF protected app.config.from_object('config.default') app.config.from_object('config.api') -app.config.from_object('config.iiif') app.config.from_pyfile('production.py') app.config['WTF_CSRF_TIME_LIMIT'] = None # Set CSRF token valid for session @@ -74,6 +73,10 @@ def before_request() -> None: app.config['MAX_CONTENT_LENGTH'] = \ g.settings['file_upload_max_size'] * 1024 * 1024 + g.display_file_ext = app.config['DISPLAY_FILE_EXT'] + if g.settings['image_processing']: + g.display_file_ext += app.config['PROCESSABLE_EXT'] + if request.path.startswith('/api/'): ip = request.environ.get('HTTP_X_REAL_IP', request.remote_addr) if not current_user.is_authenticated \ diff --git a/openatlas/api/endpoints/iiif.py b/openatlas/api/endpoints/iiif.py index 4ab09b13a..8bf0aba91 100644 --- a/openatlas/api/endpoints/iiif.py +++ b/openatlas/api/endpoints/iiif.py @@ -132,7 +132,7 @@ def get_manifest_version_2(id_: int) -> dict[str, Any]: def get_metadata(entity: Entity) -> dict[str, Any]: ext = '.tiff' if app.config['IIIF']['conversion'] \ - else entity.get_file_extension() + else entity.get_file_ext() image_url = f"{app.config['IIIF']['url']}{entity.id}{ext}" req = requests.get(f"{image_url}/info.json") image_api = req.json() diff --git a/openatlas/api/import_scripts/vocabs.py b/openatlas/api/import_scripts/vocabs.py index 48b1efc47..3f1ebd950 100644 --- a/openatlas/api/import_scripts/vocabs.py +++ b/openatlas/api/import_scripts/vocabs.py @@ -27,8 +27,8 @@ def fetch_top_groups( duplicates = [] if ref := get_vocabs_reference_system(details): for group in form_data['choices']: - if (not Type.check_hierarchy_exists(group[1]) and - group[0] in form_data['top_concepts']): + if not Type.check_hierarchy_exists(group[1]) and \ + group[0] in form_data['top_concepts']: hierarchy = Entity.insert( 'type', group[1], @@ -47,8 +47,8 @@ def fetch_top_groups( ref, hierarchy) count.append(group[0]) - if (Type.check_hierarchy_exists(group[1]) - and group[0] in form_data['top_concepts']): + if Type.check_hierarchy_exists(group[1]) \ + and group[0] in form_data['top_concepts']: duplicates.append(group[1]) return count, duplicates diff --git a/openatlas/display/base_display.py b/openatlas/display/base_display.py index 3477f1684..21cd35d56 100644 --- a/openatlas/display/base_display.py +++ b/openatlas/display/base_display.py @@ -79,8 +79,8 @@ def get_type_data(self) -> dict[str, Any]: def add_file_tab_thumbnails(self) -> None: if 'file' in self.tabs and current_user.settings['table_show_icons']: - if (app.config['IIIF']['activate'] - or g.settings['image_processing']): + if app.config['IIIF']['activate'] \ + or g.settings['image_processing']: self.tabs['file'].table.header.insert(1, _('icon')) for row in self.tabs['file'].table.rows: row.insert(1, file_preview( @@ -202,11 +202,9 @@ def add_reference_tables_data(self) -> None: domain = link_.domain data = get_base_table_data(domain) if domain.class_.view == 'file': - extension = data[3] - data.append( - profile_image_table_link(entity, domain, extension)) - if not entity.image_id \ - and extension in app.config['DISPLAY_FILE_EXTENSIONS']: + ext = data[3] + data.append(profile_image_table_link(entity, domain, ext)) + if not entity.image_id and ext in g.display_file_ext: entity.image_id = domain.id elif domain.class_.view != 'source': data.append(link_.description) @@ -382,17 +380,15 @@ def add_tabs(self) -> None: domain = link_.domain data = get_base_table_data(domain) if domain.class_.view == 'file': - extension = data[3] - data.append( - profile_image_table_link(entity, domain, extension)) - if not entity.image_id \ - and extension in app.config['DISPLAY_FILE_EXTENSIONS']: + ext = data[3] + data.append(profile_image_table_link(entity, domain, ext)) + if not entity.image_id and ext in g.display_file_ext: entity.image_id = domain.id if entity.class_.view == 'place' \ and is_authorized('editor') \ and current_user.settings['module_map_overlay']: content = '' - if extension in app.config['DISPLAY_FILE_EXTENSIONS']: + if ext in app.config['DISPLAY_FILE_EXT']: overlays = Overlay.get_by_object(entity) if domain.id in overlays and (html_link := edit_link( url_for( diff --git a/openatlas/display/display.py b/openatlas/display/display.py index 215471f1d..beda4d4e6 100644 --- a/openatlas/display/display.py +++ b/openatlas/display/display.py @@ -68,18 +68,17 @@ class FileDisplay(BaseDisplay): def add_data(self) -> None: super().add_data() self.data[_('size')] = self.entity.get_file_size() - self.data[_('extension')] = self.entity.get_file_extension() + self.data[_('extension')] = self.entity.get_file_ext() def add_button_others(self) -> None: if path := get_file_path(self.entity.id): self.buttons.append(button( _('download'), url_for('download_file', filename=path.name))) - if (check_iiif_activation() - and self.entity.get_file_extension() - in app.config['IIIF_IMAGE_EXT']): - if (check_iiif_file_exist(self.entity.id) - or not app.config['IIIF']['conversion']): + if check_iiif_activation() \ + and self.entity.get_file_ext() in g.display_image_ext: + if check_iiif_file_exist(self.entity.id) \ + or not app.config['IIIF']['conversion']: self.buttons.append(button( _('iiif'), url_for('view_iiif', id_=self.entity.id))) diff --git a/openatlas/display/image_processing.py b/openatlas/display/image_processing.py index 6021960b5..cfa91f813 100644 --- a/openatlas/display/image_processing.py +++ b/openatlas/display/image_processing.py @@ -8,13 +8,11 @@ def resize_image(filename: str) -> None: file_format = '.' + filename.split('.', 1)[1].lower() - if file_format in app.config['ALLOWED_IMAGE_EXT']: - loop_resize_image(filename.rsplit('.', 1)[0].lower(), file_format) - - -def loop_resize_image(name: str, file_format: str) -> None: - for size in app.config['IMAGE_SIZE'].values(): - safe_resize_image(name, file_format, size) + if file_format in g.display_file_ext: + for size in app.config['IMAGE_SIZE'].values(): + safe_resize_image( + filename.rsplit('.', 1)[0].lower(), + file_format, size) def safe_resize_image(name: str, file_format: str, size: str) -> bool: @@ -32,23 +30,23 @@ def safe_resize_image(name: str, file_format: str, size: str) -> bool: def image_resizing(name: str, format_: str, size: str) -> bool: - conf = app.config - filename = Path(conf['UPLOAD_PATH']) / f"{name}{format_}[0]" + filename = Path(app.config['UPLOAD_PATH']) / f"{name}{format_}[0]" with Image(filename=filename) as src: - ext = conf['PROCESSED_EXT'] \ - if format_ in conf['NONE_DISPLAY_EXT'] else format_ - with src.convert(ext.replace('.', '')) as img: + if format_ in app.config['PROCESSABLE_EXT']: + format_ = app.config['PROCESSED_EXT'] + with src.convert(format_.replace('.', '')) as img: img.transform(resize=f"{size}x{size}>") img.compression_quality = 75 img.save( - filename=Path(conf['RESIZED_IMAGES']) / size / f"{name}{ext}") + filename=Path( + app.config['RESIZED_IMAGES']) / size / f"{name}{format_}") return True def check_processed_image(filename: str) -> bool: file_format = '.' + filename.split('.', 1)[1].lower() try: - if file_format in app.config['ALLOWED_IMAGE_EXT']: + if file_format in g.display_file_ext: return loop_through_processed_folders( filename.rsplit('.', 1)[0].lower(), file_format) @@ -62,8 +60,9 @@ def check_processed_image(filename: str) -> bool: def loop_through_processed_folders(name: str, file_format: str) -> bool: - ext = app.config['PROCESSED_EXT'] \ - if file_format in app.config['NONE_DISPLAY_EXT'] else file_format + ext = file_format + if file_format in app.config['PROCESSABLE_EXT']: + ext = app.config['PROCESSED_EXT'] for size in app.config['IMAGE_SIZE'].values(): path = Path(app.config['RESIZED_IMAGES']) / size / f"{name}{ext}" if not path.is_file() \ @@ -97,7 +96,6 @@ def delete_orphaned_resized_images() -> None: def create_resized_images() -> None: from openatlas.models.entity import Entity - for entity in Entity.get_by_class('file'): - if entity.id in g.files: - if entity.get_file_extension() in app.config['ALLOWED_IMAGE_EXT']: - resize_image(f"{entity.id}{entity.get_file_extension()}") + for e in Entity.get_by_class('file'): + if e.id in g.files and e.get_file_ext() in g.display_file_ext: + resize_image(f"{e.id}{e.get_file_ext()}") diff --git a/openatlas/display/tab.py b/openatlas/display/tab.py index 246430f55..6dfb4cd9e 100644 --- a/openatlas/display/tab.py +++ b/openatlas/display/tab.py @@ -118,12 +118,12 @@ def set_buttons(self, name: str, entity: Optional[Entity] = None) -> None: g.classes[item].label, url_for('insert', class_=item, origin_id=id_))) elif name == 'artifact': - if (entity and entity.class_.name in + if entity and entity.class_.name in \ ['place', 'artifact', 'human_remains', 'feature', - 'stratigraphic_unit']): + 'stratigraphic_unit']: self.buttons.append( button(_('add subunit'), url_for('add_subunit', super_id=id_))) diff --git a/openatlas/display/util.py b/openatlas/display/util.py index 6aad26b68..2257564eb 100644 --- a/openatlas/display/util.py +++ b/openatlas/display/util.py @@ -128,17 +128,12 @@ def format_entity_date( return html + (f" ({comment})" if comment else '') -def profile_image_table_link( - entity: Entity, - file: Entity, - extension: str) -> str: +def profile_image_table_link(entity: Entity, file: Entity, ext: str) -> str: if file.id == entity.image_id: return link( _('unset'), url_for('file_remove_profile_image', entity_id=entity.id)) - if extension in app.config['DISPLAY_FILE_EXTENSIONS'] or ( - g.settings['image_processing'] - and extension in app.config['ALLOWED_IMAGE_EXT']): + if ext in g.display_file_ext: return link( _('set'), url_for('set_profile_image', id_=file.id, origin_id=entity.id)) @@ -234,20 +229,18 @@ def profile_image(entity: Entity) -> str: return '' if not (path := get_file_path(entity.image_id)): return '' # pragma: no cover - display_ext = app.config["DISPLAY_FILE_EXTENSIONS"] + src = url_for('display_file', filename=path.name) url = src width = g.settings["profile_image_width"] if app.config['IIIF']['activate'] and check_iiif_file_exist(entity.id): url = url_for('view_iiif', id_=entity.id) - display_ext = app.config["IIIF_IMAGE_EXT"] iiif_ext = '.tiff' if app.config['IIIF']['conversion'] \ else g.files[entity.id].suffix src = \ f"{app.config['IIIF']['url']}{entity.id}{iiif_ext}" \ f"/full/!{width},{width}/0/default.jpg" elif g.settings['image_processing'] and check_processed_image(path.name): - display_ext = app.config["ALLOWED_IMAGE_EXT"] if path_ := get_file_path( entity.image_id, app.config['IMAGE_SIZE']['thumbnail']): @@ -258,7 +251,7 @@ def profile_image(entity: Entity) -> str: external = False if entity.class_.view == 'file': external = True - if path.suffix.lower() not in display_ext: + if path.suffix.lower() not in g.display_file_ext: return '

' + _('no preview available') + '

' else: url = url_for('view', id_=entity.image_id) @@ -347,7 +340,7 @@ def get_base_table_data(entity: Entity, show_links: bool = True) -> list[Any]: data.append(entity.standard_type.name if entity.standard_type else '') if entity.class_.name == 'file': data.append(entity.get_file_size()) - data.append(entity.get_file_extension()) + data.append(entity.get_file_ext()) if entity.class_.view in ['actor', 'artifact', 'event', 'place']: data.append(entity.first) data.append(entity.last) @@ -482,7 +475,7 @@ def get_file_path( return None ext = g.files[id_].suffix if size: - if ext in app.config['NONE_DISPLAY_EXT']: + if ext in app.config['PROCESSABLE_EXT']: ext = app.config['PROCESSED_EXT'] # pragma: no cover path = app.config['RESIZED_IMAGES'] / size / f"{id_}{ext}" return path if os.path.exists(path) else None @@ -761,9 +754,7 @@ def get_entities_linked_to_type_recursive( def check_iiif_activation() -> bool: iiif = app.config['IIIF'] - return True \ - if (iiif['activate'] and os.access(Path(iiif['path']), os.W_OK)) \ - else False + return bool(iiif['activate'] and os.access(Path(iiif['path']), os.W_OK)) def check_iiif_file_exist(id_: int) -> bool: diff --git a/openatlas/models/entity.py b/openatlas/models/entity.py index 286358b9b..443735154 100644 --- a/openatlas/models/entity.py +++ b/openatlas/models/entity.py @@ -335,7 +335,7 @@ def get_file_size(self) -> str: return convert_size(g.files[self.id].stat().st_size) \ if self.id in g.files else 'N/A' - def get_file_extension(self) -> str: + def get_file_ext(self) -> str: return g.files[self.id].suffix if self.id in g.files else 'N/A' @staticmethod @@ -379,9 +379,8 @@ def get_by_view( def get_display_files() -> list[Entity]: entities = [] for row in Db.get_by_class('file', types=True): - ext = g.files[row['id']].suffix \ - if row['id'] in g.files else 'N/A' - if ext in app.config['DISPLAY_FILE_EXTENSIONS']: + ext = g.files[row['id']].suffix if row['id'] in g.files else 'N/A' + if ext in app.config['DISPLAY_FILE_EXT']: entities.append(Entity(row)) return entities diff --git a/openatlas/views/admin.py b/openatlas/views/admin.py index 6110317ae..492bcb587 100644 --- a/openatlas/views/admin.py +++ b/openatlas/views/admin.py @@ -616,7 +616,7 @@ def admin_logo(id_: Optional[int] = None) -> Union[str, Response]: entity.name, link(entity.standard_type), entity.get_file_size(), - entity.get_file_extension(), + entity.get_file_ext(), entity.description, date]) return render_template( @@ -783,7 +783,6 @@ def get_disk_space_info() -> Optional[dict[str, Any]]: def admin_convert_all_to_iiif() -> Response: for entity in Entity.get_by_class('file'): if entity.id in g.files \ - and entity.get_file_extension() \ - in app.config['ALLOWED_IMAGE_EXT']: + and entity.get_file_ext() in g.display_file_ext: convert_image_to_iiif(entity.id) return redirect(url_for('admin_index') + '#tab-data') diff --git a/openatlas/views/entity.py b/openatlas/views/entity.py index 97241d817..6f2f8747f 100644 --- a/openatlas/views/entity.py +++ b/openatlas/views/entity.py @@ -123,11 +123,11 @@ def update(id_: int, copy: Optional[str] = None) -> Union[str, Response]: if entity.class_.view in ['artifact', 'place']: manager.entity.image_id = manager.entity.get_profile_image_id() if not manager.entity.image_id: - for l in manager.entity.get_links('P67', inverse=True): - if l.domain.class_.view == 'file' \ - and get_base_table_data(l.domain)[3] \ - in app.config['DISPLAY_FILE_EXTENSIONS']: - manager.entity.image_id = l.domain.id + for link_ in manager.entity.get_links('P67', inverse=True): + if link_.domain.class_.view == 'file' \ + and get_base_table_data(link_.domain)[3] \ + in g.display_file_ext: + manager.entity.image_id = link_.domain.id break return render_template( 'entity/update.html', @@ -211,7 +211,7 @@ def insert_files(manager: BaseManager) -> Union[str, Response]: ext = secure_filename(file.filename).rsplit('.', 1)[1].lower() path = app.config['UPLOAD_PATH'] / name file.save(str(path)) - if f'.{ext}' in app.config['DISPLAY_FILE_EXTENSIONS']: + if f'.{ext}' in g.display_file_ext: call(f'exiftran -ai {path}', shell=True) # Fix rotation filenames.append(name) if g.settings['image_processing']: diff --git a/openatlas/views/entity_index.py b/openatlas/views/entity_index.py index 48724c9fc..c4c052913 100644 --- a/openatlas/views/entity_index.py +++ b/openatlas/views/entity_index.py @@ -51,7 +51,7 @@ def get_table(view: str) -> Table: link(entity), link(entity.standard_type), entity.get_file_size(), - entity.get_file_extension(), + entity.get_file_ext(), entity.description] if (g.settings['image_processing'] or app.config['IIIF']['activate']) \ diff --git a/sphinx/source/entity/file.rst b/sphinx/source/entity/file.rst index befcc6aa1..abf1ab0ba 100644 --- a/sphinx/source/entity/file.rst +++ b/sphinx/source/entity/file.rst @@ -52,7 +52,7 @@ extensions and can be changed in the configuration file .. code-block:: python - DISPLAY_FILE_EXTENSIONS = ['.bmp', '.gif', '.ico', '.jpeg', '.jpg', '.png', '.svg'] + DISPLAY_FILE_EXT = ['.bmp', '.gif', '.ico', '.jpeg', '.jpg', '.png', '.svg'] Logo ---- From 1a4cd586f077f8ca493539fe3eda65c614180d51 Mon Sep 17 00:00:00 2001 From: bkoschicek Date: Tue, 10 Oct 2023 15:07:51 +0200 Subject: [PATCH 080/102] mypy fixes --- openatlas/api/endpoints/iiif.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openatlas/api/endpoints/iiif.py b/openatlas/api/endpoints/iiif.py index 8bf0aba91..8d7d4d403 100644 --- a/openatlas/api/endpoints/iiif.py +++ b/openatlas/api/endpoints/iiif.py @@ -18,7 +18,7 @@ def get(id_: int) -> Response: IIIFSequenceV2.build_sequence(get_metadata(get_entity_by_id(id_)))) @staticmethod - def build_sequence(metadata: dict[str, Any]) -> dict[str, str]: + def build_sequence(metadata: dict[str, Any]) -> dict[str, Any]: return { "@id": url_for( 'api.iiif_sequence', @@ -41,7 +41,7 @@ def get(id_: int) -> Response: IIIFCanvasV2.build_canvas(get_metadata(get_entity_by_id(id_)))) @staticmethod - def build_canvas(metadata: dict[str, Any]) -> dict[str, str]: + def build_canvas(metadata: dict[str, Any]) -> dict[str, Any]: entity = metadata['entity'] return { "@id": url_for( @@ -77,7 +77,7 @@ def get(id_: int) -> Response: IIIFImageV2.build_image(get_metadata(get_entity_by_id(id_)))) @staticmethod - def build_image(metadata: dict[str, Any]) -> dict[str, str]: + def build_image(metadata: dict[str, Any]) -> dict[str, Any]: id_ = metadata['entity'].id return { "@context": "https://iiif.io/api/presentation/2/context.json", From 93ca942e9efa561c80b8ee508e0ca20eaa277564 Mon Sep 17 00:00:00 2001 From: Alexander Watzinger Date: Tue, 10 Oct 2023 15:10:59 +0200 Subject: [PATCH 081/102] Mypy checks --- openatlas/api/endpoints/iiif.py | 13 +++++-------- openatlas/views/file.py | 4 ++-- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/openatlas/api/endpoints/iiif.py b/openatlas/api/endpoints/iiif.py index 8bf0aba91..7c2bfcdd4 100644 --- a/openatlas/api/endpoints/iiif.py +++ b/openatlas/api/endpoints/iiif.py @@ -18,7 +18,7 @@ def get(id_: int) -> Response: IIIFSequenceV2.build_sequence(get_metadata(get_entity_by_id(id_)))) @staticmethod - def build_sequence(metadata: dict[str, Any]) -> dict[str, str]: + def build_sequence(metadata: dict[str, Any]) -> dict[str, Any]: return { "@id": url_for( 'api.iiif_sequence', @@ -41,7 +41,7 @@ def get(id_: int) -> Response: IIIFCanvasV2.build_canvas(get_metadata(get_entity_by_id(id_)))) @staticmethod - def build_canvas(metadata: dict[str, Any]) -> dict[str, str]: + def build_canvas(metadata: dict[str, Any]) -> dict[str, Any]: entity = metadata['entity'] return { "@id": url_for( @@ -53,8 +53,7 @@ def build_canvas(metadata: dict[str, Any]) -> dict[str, str]: "description": { "@value": entity.description, "@language": "en"}, - "images": [ - IIIFImageV2.build_image(metadata)], + "images": [IIIFImageV2.build_image(metadata)], "related": "", "thumbnail": { "@id": f'{metadata["img_url"]}/full/!200,200/0/default.jpg', @@ -65,9 +64,7 @@ def build_canvas(metadata: dict[str, Any]) -> dict[str, str]: "service": { "@context": "https://iiif.io/api/image/2/context.json", "@id": metadata['img_url'], - "profile": metadata['img_api']['profile']}, - }, - } + "profile": metadata['img_api']['profile']}}} class IIIFImageV2(Resource): @@ -77,7 +74,7 @@ def get(id_: int) -> Response: IIIFImageV2.build_image(get_metadata(get_entity_by_id(id_)))) @staticmethod - def build_image(metadata: dict[str, Any]) -> dict[str, str]: + def build_image(metadata: dict[str, Any]) -> dict[str, Any]: id_ = metadata['entity'].id return { "@context": "https://iiif.io/api/presentation/2/context.json", diff --git a/openatlas/views/file.py b/openatlas/views/file.py index 7b5ac9261..639467b11 100644 --- a/openatlas/views/file.py +++ b/openatlas/views/file.py @@ -70,14 +70,14 @@ def file_add(id_: int, view: str) -> Union[str, Response]: @app.route('/file/convert_iiif/', methods=['GET']) @required_group('contributor') -def make_iiif_available(id_: int): +def make_iiif_available(id_: int) -> Response: convert_image_to_iiif(id_) return redirect(url_for('view', id_=id_)) @app.route('/view_iiif/', methods=['GET']) @required_group('contributor') -def view_iiif(id_: int): +def view_iiif(id_: int) -> str: return render_template( 'iiif.html', manifest_url=url_for( From b15c38645146723a4b5e72c3e473ef7fd849b384 Mon Sep 17 00:00:00 2001 From: bkoschicek Date: Tue, 10 Oct 2023 15:37:33 +0200 Subject: [PATCH 082/102] first tests --- openatlas/display/display.py | 2 +- openatlas/display/util.py | 2 +- openatlas/templates/admin/data.html | 1 - openatlas/views/admin.py | 10 ---------- tests/test_file.py | 15 ++++++++++++++- 5 files changed, 16 insertions(+), 14 deletions(-) diff --git a/openatlas/display/display.py b/openatlas/display/display.py index beda4d4e6..1c3d38719 100644 --- a/openatlas/display/display.py +++ b/openatlas/display/display.py @@ -76,7 +76,7 @@ def add_button_others(self) -> None: _('download'), url_for('download_file', filename=path.name))) if check_iiif_activation() \ - and self.entity.get_file_ext() in g.display_image_ext: + and self.entity.get_file_ext() in g.display_file_ext: if check_iiif_file_exist(self.entity.id) \ or not app.config['IIIF']['conversion']: self.buttons.append(button( diff --git a/openatlas/display/util.py b/openatlas/display/util.py index 2257564eb..d2dbf8e70 100644 --- a/openatlas/display/util.py +++ b/openatlas/display/util.py @@ -782,6 +782,6 @@ def convert_image_to_iiif(id_: int) -> None: try: process = subprocess.Popen(command, shell=True) process.wait() - flash(_('iiif converted'), 'info') + flash(_('IIIF converted'), 'info') except Exception as e: flash(f"{_('failed to convert image')}: {e}", 'error') diff --git a/openatlas/templates/admin/data.html b/openatlas/templates/admin/data.html index e062e735d..c2c1d269e 100644 --- a/openatlas/templates/admin/data.html +++ b/openatlas/templates/admin/data.html @@ -32,7 +32,6 @@

{{ _('image processing')|uc_first }}

{{ 'admin/file'|manual|safe }}
{{ _('create resized images')|button(url_for('admin_resize_images'))|safe }}
{{ _('delete orphaned resized images')|button(url_for('admin_delete_orphaned_resized_images'))|safe }}
-
{{ _('convert all images to iiif')|button(url_for('admin_convert_all_to_iiif'))|safe }}
{% endif %} {% if 'manager'|is_authorized %} diff --git a/openatlas/views/admin.py b/openatlas/views/admin.py index 492bcb587..8571b33b4 100644 --- a/openatlas/views/admin.py +++ b/openatlas/views/admin.py @@ -776,13 +776,3 @@ def get_disk_space_info() -> Optional[dict[str, Any]]: 'percent_used': percent_free, 'percent_project': percent_files, 'percent_other': 100 - (percent_files + percent_free)} - - -@app.route('/admin/admin_convert_all_to_iiif') -@required_group('manager') -def admin_convert_all_to_iiif() -> Response: - for entity in Entity.get_by_class('file'): - if entity.id in g.files \ - and entity.get_file_ext() in g.display_file_ext: - convert_image_to_iiif(entity.id) - return redirect(url_for('admin_index') + '#tab-data') diff --git a/tests/test_file.py b/tests/test_file.py index 378ba35fb..4be5826bd 100644 --- a/tests/test_file.py +++ b/tests/test_file.py @@ -17,7 +17,7 @@ def test_file(self) -> None: reference = insert('edition', 'Ancient Books') logo = Path(app.root_path) \ - / 'static' / 'images' / 'layout' / 'logo.png' + / 'static' / 'images' / 'layout' / 'logo.png' with open(logo, 'rb') as img_1, open(logo, 'rb') as img_2: rv = self.app.post( @@ -101,8 +101,21 @@ def test_file(self) -> None: follow_redirects=True) assert b'Updated file' in rv.data + rv = self.app.get( + url_for('make_iiif_available', id_=file_id), + follow_redirects=True) + assert b'IIIF converted' in rv.data + + rv = self.app.get(url_for('view', id_=file_id)) + assert b'Logo' in rv.data + + rv = self.app.get(url_for('view_iiif', id_=file_id)) + assert b'Mirador' in rv.data + for file in files: rv = self.app.get( url_for('delete', id_=file.id), follow_redirects=True) assert b'The entry has been deleted' in rv.data + + From c3510ab358c3d023a405b4c3abf0eee459fac021 Mon Sep 17 00:00:00 2001 From: bkoschicek Date: Tue, 10 Oct 2023 17:41:52 +0200 Subject: [PATCH 083/102] complete IIIF tests --- openatlas/api/routes.py | 6 ++--- openatlas/display/image_processing.py | 4 +-- openatlas/display/util.py | 2 +- openatlas/views/entity_index.py | 2 +- openatlas/views/file.py | 11 ++++---- tests/test_file.py | 38 ++++++++++++++++++++++++++- 6 files changed, 50 insertions(+), 13 deletions(-) diff --git a/openatlas/api/routes.py b/openatlas/api/routes.py index bf18cb7ae..f9afe15b6 100644 --- a/openatlas/api/routes.py +++ b/openatlas/api/routes.py @@ -106,13 +106,13 @@ def add_routes_v03(api: Api) -> None: endpoint='iiif_manifest') api.add_resource( IIIFImageV2, - '/iiif_image//.json', + '/iiif_image/.json', endpoint='iiif_image') api.add_resource( IIIFCanvasV2, - '/iiif_canvas//.json', + '/iiif_canvas/.json', endpoint='iiif_canvas') api.add_resource( IIIFSequenceV2, - '/iiif_sequence//.json', + '/iiif_sequence/.json', endpoint='iiif_sequence') diff --git a/openatlas/display/image_processing.py b/openatlas/display/image_processing.py index cfa91f813..748951037 100644 --- a/openatlas/display/image_processing.py +++ b/openatlas/display/image_processing.py @@ -33,7 +33,7 @@ def image_resizing(name: str, format_: str, size: str) -> bool: filename = Path(app.config['UPLOAD_PATH']) / f"{name}{format_}[0]" with Image(filename=filename) as src: if format_ in app.config['PROCESSABLE_EXT']: - format_ = app.config['PROCESSED_EXT'] + format_ = app.config['PROCESSED_EXT'] # pragma: no cover with src.convert(format_.replace('.', '')) as img: img.transform(resize=f"{size}x{size}>") img.compression_quality = 75 @@ -62,7 +62,7 @@ def check_processed_image(filename: str) -> bool: def loop_through_processed_folders(name: str, file_format: str) -> bool: ext = file_format if file_format in app.config['PROCESSABLE_EXT']: - ext = app.config['PROCESSED_EXT'] + ext = app.config['PROCESSED_EXT'] # pragma: no cover for size in app.config['IMAGE_SIZE'].values(): path = Path(app.config['RESIZED_IMAGES']) / size / f"{name}{ext}" if not path.is_file() \ diff --git a/openatlas/display/util.py b/openatlas/display/util.py index d2dbf8e70..92781c987 100644 --- a/openatlas/display/util.py +++ b/openatlas/display/util.py @@ -783,5 +783,5 @@ def convert_image_to_iiif(id_: int) -> None: process = subprocess.Popen(command, shell=True) process.wait() flash(_('IIIF converted'), 'info') - except Exception as e: + except Exception as e: # pragma: no cover flash(f"{_('failed to convert image')}: {e}", 'error') diff --git a/openatlas/views/entity_index.py b/openatlas/views/entity_index.py index c4c052913..4da44ba4d 100644 --- a/openatlas/views/entity_index.py +++ b/openatlas/views/entity_index.py @@ -85,7 +85,7 @@ def file_preview(entity_id: int) -> str: url = (f"{app.config['IIIF']['url']}{entity_id}{ext}" f"/full/!100,100/0/default.jpg") return f"" \ - if ext in app.config["IIIF_IMAGE_EXT"] else '' + if ext in g.display_file_ext else '' if icon_path := get_file_path( entity_id, app.config['IMAGE_SIZE']['table']): diff --git a/openatlas/views/file.py b/openatlas/views/file.py index 639467b11..1bd295d49 100644 --- a/openatlas/views/file.py +++ b/openatlas/views/file.py @@ -80,8 +80,9 @@ def make_iiif_available(id_: int) -> Response: def view_iiif(id_: int) -> str: return render_template( 'iiif.html', - manifest_url=url_for( - 'api.iiif_manifest', - id_=id_, - version=app.config['IIIF']['version'], - _external=True)) + manifest_url= + url_for( + 'api.iiif_manifest', + id_=id_, + version=app.config['IIIF']['version'], + _external=True)) diff --git a/tests/test_file.py b/tests/test_file.py index 4be5826bd..e3489b0e1 100644 --- a/tests/test_file.py +++ b/tests/test_file.py @@ -101,17 +101,53 @@ def test_file(self) -> None: follow_redirects=True) assert b'Updated file' in rv.data + rv = self.app.get(url_for('view', id_=file_id)) + assert b'Logo' in rv.data + + rv = self.app.get(url_for('view', id_=file_id)) + assert b'make_iiif_available' in rv.data + rv = self.app.get( url_for('make_iiif_available', id_=file_id), follow_redirects=True) assert b'IIIF converted' in rv.data rv = self.app.get(url_for('view', id_=file_id)) - assert b'Logo' in rv.data + assert b'iiif' in rv.data + + rv = self.app.get(url_for( + 'api.iiif_manifest', + id_=file_id, + version=app.config['IIIF']['version'], + _external=True)) + assert b'/iiif/2/145.tiff' in rv.data + + rv = self.app.get( + url_for('api.iiif_sequence', id_=file_id)) + assert b'/iiif/2/145.tiff' in rv.data + rv = self.app.get( + url_for('api.iiif_image', id_=file_id)) + assert b'/iiif/2/145.tiff' in rv.data + rv = self.app.get( + url_for('api.iiif_canvas', id_=file_id)) + assert b'/iiif/2/145.tiff' in rv.data rv = self.app.get(url_for('view_iiif', id_=file_id)) assert b'Mirador' in rv.data + rv = self.app.get(url_for('view', id_=place.id)) + assert b'/full/!100,100/0/default.jpg' in rv.data + + app.config['IIIF']['conversion'] = False + rv = self.app.get(url_for('view', id_=place.id)) + assert b'/full/!100,100/0/default.jpg' in rv.data + + app.config['IIIF']['activate'] = False + rv = self.app.get(url_for('view', id_=place.id)) + assert b'Logo' in rv.data + + app.config['IIIF']['activate'] = True + app.config['IIIF']['conversion'] = True for file in files: rv = self.app.get( url_for('delete', id_=file.id), From 08bff1c1db63a9198eeacdb4ce333733e2d667c8 Mon Sep 17 00:00:00 2001 From: BernhardKoschicek Date: Wed, 11 Oct 2023 14:59:49 +0200 Subject: [PATCH 084/102] refactor --- openatlas/display/util.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openatlas/display/util.py b/openatlas/display/util.py index 92781c987..2251b2ecc 100644 --- a/openatlas/display/util.py +++ b/openatlas/display/util.py @@ -760,8 +760,7 @@ def check_iiif_activation() -> bool: def check_iiif_file_exist(id_: int) -> bool: if app.config['IIIF']['conversion']: return get_iiif_file_path(id_).is_file() - else: - return bool(get_file_path(id_)) + return bool(get_file_path(id_)) def get_iiif_file_path(id_: int) -> Path: From 72fc321cd59588f38b5f0d8f4bc42be8e48e4af9 Mon Sep 17 00:00:00 2001 From: Alexander Watzinger Date: Wed, 11 Oct 2023 17:05:29 +0200 Subject: [PATCH 085/102] Fix path checks --- config/default.py | 6 ------ openatlas/__init__.py | 5 +++++ openatlas/display/util.py | 2 +- tests/test_index.py | 4 ++-- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/config/default.py b/config/default.py index 18973cd33..db3fac619 100644 --- a/config/default.py +++ b/config/default.py @@ -50,12 +50,6 @@ 'conversion': True, 'compression': 'deflate'} # 'deflate' or 'jpeg' -# For system checks -WRITABLE_PATHS = [ - UPLOAD_PATH, - EXPORT_PATH, - RESIZED_IMAGES] - # Security SESSION_COOKIE_SECURE = False # Should be True in production.py if using HTTPS REMEMBER_COOKIE_SECURE = True diff --git a/openatlas/__init__.py b/openatlas/__init__.py index 6325f9040..1fac3694a 100644 --- a/openatlas/__init__.py +++ b/openatlas/__init__.py @@ -77,6 +77,11 @@ def before_request() -> None: if g.settings['image_processing']: g.display_file_ext += app.config['PROCESSABLE_EXT'] + g.writable_paths = [ + app.config['UPLOAD_PATH'], + app.config['EXPORT_PATH'], + app.config['RESIZED_IMAGES']] # For system checks + if request.path.startswith('/api/'): ip = request.environ.get('HTTP_X_REAL_IP', request.remote_addr) if not current_user.is_authenticated \ diff --git a/openatlas/display/util.py b/openatlas/display/util.py index d2dbf8e70..6c11ddd84 100644 --- a/openatlas/display/util.py +++ b/openatlas/display/util.py @@ -430,7 +430,7 @@ def system_warnings(_context: str, _unneeded_string: str) -> str: if app.config['IIIF']['activate']: if path := app.config['IIIF']['path']: check_write_access(path, warnings) - for path in app.config['WRITABLE_PATHS']: + for path in g.writable_paths: check_write_access(path, warnings) if is_authorized('admin'): from openatlas.models.user import User diff --git a/tests/test_index.py b/tests/test_index.py index 863fdc007..516e6c06a 100644 --- a/tests/test_index.py +++ b/tests/test_index.py @@ -1,6 +1,6 @@ from pathlib import Path -from flask import url_for +from flask import url_for, g from openatlas import app from tests.base import TestBaseCase @@ -27,7 +27,7 @@ def test_index(self) -> None: self.app.get(url_for('set_locale', language='en')) - app.config['WRITABLE_PATHS'].append(Path(app.root_path) / 'error') + g.writable_paths.append(Path(app.root_path) / 'error') app.config['DATABASE_VERSION'] = 'error' rv = self.app.get(url_for('view', id_=666), follow_redirects=True) assert b'teapot' in rv.data From 252e39ccd2097910a3a71cd9d00dd5734a3702c8 Mon Sep 17 00:00:00 2001 From: bkoschicek Date: Thu, 12 Oct 2023 13:30:00 +0200 Subject: [PATCH 086/102] added test for GitHub actions --- .github/workflows/starter.yaml | 6 ++++++ install/Dockerfile | 8 ++++++++ install/iiif.conf | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+) create mode 100644 install/iiif.conf diff --git a/.github/workflows/starter.yaml b/.github/workflows/starter.yaml index 38f36930b..e70de80c6 100644 --- a/.github/workflows/starter.yaml +++ b/.github/workflows/starter.yaml @@ -115,6 +115,12 @@ jobs: 'collection_ids': [0], 'base_url': 'https://arche-curation.acdh-dev.oeaw.ac.at/', 'thumbnail_url': 'https://arche-thumbnails.acdh.oeaw.ac.at/'} + IIIF = { + 'activate': True, + 'path': '/var/www/iipsrv/', + 'url': 'http://localhost/iiif/', + 'version': 2, + 'conversion': True} EOF # production.py is recreated on every start sudo chown 33:33 testing.py diff --git a/install/Dockerfile b/install/Dockerfile index 05f96f649..2e894c279 100644 --- a/install/Dockerfile +++ b/install/Dockerfile @@ -8,10 +8,16 @@ RUN --mount=type=cache,target=/var/cache/apt \ apt install -y --no-install-recommends python3-pandas python3-jinja2 python3-flask-cors python3-flask-restful p7zip-full &&\ apt install -y --no-install-recommends python3-wand python3-rdflib python3-requests python3-dicttoxml python3-rdflib-jsonld python3-flasgger &&\ apt install -y --no-install-recommends apache2 libapache2-mod-wsgi-py3 python3-coverage python3-nose exiftran &&\ + apt install -y --no-install-recommends apache2 iipimage-server libvips &&\ apt install -y --no-install-recommends gettext npm python3-pip git postgresql-client-13 &&\ apt install -y --no-install-recommends dos2unix locales locales-all &&\ mkdir -p /var/www/openatlas /var/www/.cache /var/www/.local /var/www/.npm &&\ chown -R www-data:www-data /var/www/.cache /var/www/.local /var/www/.npm /var/log/apache2 /var/run/apache2 +RUN cp -rp /usr/lib/iipimage-server/ /var/www/iipsrv/ &&\ + chown -R www-data /var/www/iipsrv/ &&\ + chmod 755 -R /var/www/iipsrv/ &&\ +RUN rm /etc/apache2/mods-available/iipsrv.conf +COPY /install/iiif.conf /etc/apache2/mods-available/ COPY --chown=www-data:www-data / /var/www/openatlas/ RUN cd /var/www/openatlas && cp install/entrypoint.sh /entrypoint.sh &&\ cp install/example_apache.conf /etc/apache2/sites-available/000-default.conf &&\ @@ -26,6 +32,8 @@ RUN cd /var/www/openatlas &&\ cd openatlas/static &&\ pip3 install -e ./ &&\ ~/.local/bin/calmjs npm --install openatlas +RUN a2enmod iipsrv &&\ + service apache2 restart EXPOSE 8080 ENV APACHE_CONFDIR /etc/apache2 diff --git a/install/iiif.conf b/install/iiif.conf new file mode 100644 index 000000000..2837b84fd --- /dev/null +++ b/install/iiif.conf @@ -0,0 +1,34 @@ +# Create a directory for the iipsrv binary +ScriptAlias /iiif "/var/www/iipsrv/iipsrv.fcgi" + +# Set the options on that directory + + AllowOverride None + Options None + + + Order allow,deny + Allow from all + + = 2.4> + Require all granted + + + # Set the module handler + AddHandler fcgid-script .fcgi + + +# Set our environment variables for the IIP server +FcgidInitialEnv VERBOSITY "6" +FcgidInitialEnv LOGFILE "/var/log/iipsrv.log" +FcgidInitialEnv MAX_IMAGE_CACHE_SIZE "10" +FcgidInitialEnv JPEG_QUALITY "90" +FcgidInitialEnv MAX_CVT "5000" +FcgidInitialEnv MEMCACHED_SERVERS "localhost" +FcgidInitialEnv CORS "*" +FcgidInitialEnv URI_MAP "iiif=>IIIF" + +# Define the idle timeout as unlimited and the number of +# processes we want +FcgidIdleTimeout 0 +FcgidMaxProcessesPerClass 1 \ No newline at end of file From b537ea7b4788d098b3cff4706d4e9f93b62ad8ab Mon Sep 17 00:00:00 2001 From: bkoschicek Date: Thu, 12 Oct 2023 13:31:27 +0200 Subject: [PATCH 087/102] fixed dockerfile --- install/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/Dockerfile b/install/Dockerfile index 2e894c279..115ce8b8a 100644 --- a/install/Dockerfile +++ b/install/Dockerfile @@ -15,7 +15,7 @@ RUN --mount=type=cache,target=/var/cache/apt \ chown -R www-data:www-data /var/www/.cache /var/www/.local /var/www/.npm /var/log/apache2 /var/run/apache2 RUN cp -rp /usr/lib/iipimage-server/ /var/www/iipsrv/ &&\ chown -R www-data /var/www/iipsrv/ &&\ - chmod 755 -R /var/www/iipsrv/ &&\ + chmod 755 -R /var/www/iipsrv/ RUN rm /etc/apache2/mods-available/iipsrv.conf COPY /install/iiif.conf /etc/apache2/mods-available/ COPY --chown=www-data:www-data / /var/www/openatlas/ From 9f032d5d43439897a212e57df305666c924aa8a3 Mon Sep 17 00:00:00 2001 From: bkoschicek Date: Thu, 12 Oct 2023 13:39:10 +0200 Subject: [PATCH 088/102] added compression to config --- .github/workflows/starter.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/starter.yaml b/.github/workflows/starter.yaml index e70de80c6..fee8d2c15 100644 --- a/.github/workflows/starter.yaml +++ b/.github/workflows/starter.yaml @@ -120,7 +120,8 @@ jobs: 'path': '/var/www/iipsrv/', 'url': 'http://localhost/iiif/', 'version': 2, - 'conversion': True} + 'conversion': True, + 'compression': 'jpeg'} EOF # production.py is recreated on every start sudo chown 33:33 testing.py From 3654c4dd172214f06d218decd24b8ae3e01b6853 Mon Sep 17 00:00:00 2001 From: Alexander Watzinger Date: Thu, 12 Oct 2023 15:38:41 +0200 Subject: [PATCH 089/102] Added tmp path to writeable_paths --- config/default.py | 4 +--- openatlas/__init__.py | 11 ++++------- tests/test_index.py | 1 + 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/config/default.py b/config/default.py index db3fac619..d34d75a26 100644 --- a/config/default.py +++ b/config/default.py @@ -23,20 +23,18 @@ 'es': 'Español', 'fr': 'Français'} - # Paths are implemented operating system independent using pathlib. # To override them (in instance/production.py) either use them like here # or use absolute paths like e.g. pathlib.Path('/some/location/somewhere') FILES_PATH = Path(__file__).parent.parent / 'files' EXPORT_PATH = Path(FILES_PATH) / 'export' UPLOAD_PATH = Path(FILES_PATH) / 'uploads' -TMP_PATH = Path('/tmp') # used e.g. for processing imports and export files +TMP_PATH = Path('/tmp') # For processing files e.g. at import and export # Image processing DISPLAY_FILE_EXT = ['.bmp', '.gif', '.ico', '.jpeg', '.jpg', '.png', '.svg'] PROCESSABLE_EXT = ['.tiff', '.tif'] PROCESSED_EXT = '.jpeg' - PROCESSED_IMAGE_PATH = Path(FILES_PATH) / 'processed_images' RESIZED_IMAGES = Path(PROCESSED_IMAGE_PATH) / 'resized' IMAGE_SIZE = { diff --git a/openatlas/__init__.py b/openatlas/__init__.py index 1fac3694a..80a995c1e 100644 --- a/openatlas/__init__.py +++ b/openatlas/__init__.py @@ -69,19 +69,16 @@ def before_request() -> None: for file_ in app.config['UPLOAD_PATH'].iterdir(): if file_.stem.isdigit(): g.files[int(file_.stem)] = file_ - # Set max file upload in MB app.config['MAX_CONTENT_LENGTH'] = \ - g.settings['file_upload_max_size'] * 1024 * 1024 - + g.settings['file_upload_max_size'] * 1024 * 1024 # Max upload in MB g.display_file_ext = app.config['DISPLAY_FILE_EXT'] if g.settings['image_processing']: g.display_file_ext += app.config['PROCESSABLE_EXT'] - g.writable_paths = [ - app.config['UPLOAD_PATH'], app.config['EXPORT_PATH'], - app.config['RESIZED_IMAGES']] # For system checks - + app.config['RESIZED_IMAGES'], + app.config['UPLOAD_PATH'], + app.config['TMP_PATH']] if request.path.startswith('/api/'): ip = request.environ.get('HTTP_X_REAL_IP', request.remote_addr) if not current_user.is_authenticated \ diff --git a/tests/test_index.py b/tests/test_index.py index 516e6c06a..bd2db64ea 100644 --- a/tests/test_index.py +++ b/tests/test_index.py @@ -29,6 +29,7 @@ def test_index(self) -> None: g.writable_paths.append(Path(app.root_path) / 'error') app.config['DATABASE_VERSION'] = 'error' + app.config['EXPORT_PATH'] = Path('error') rv = self.app.get(url_for('view', id_=666), follow_redirects=True) assert b'teapot' in rv.data assert b'OpenAtlas with default password is still' in rv.data From fd70739520117e75552116f91d8fa4981309b24f Mon Sep 17 00:00:00 2001 From: Alexander Watzinger Date: Thu, 12 Oct 2023 15:54:29 +0200 Subject: [PATCH 090/102] Update upgrade notes --- install/upgrade/upgrade.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/install/upgrade/upgrade.md b/install/upgrade/upgrade.md index 73f5ca431..e4b87156f 100644 --- a/install/upgrade/upgrade.md +++ b/install/upgrade/upgrade.md @@ -20,6 +20,15 @@ then run the database upgrade script, then restart Apache: sudo service apache2 restart ### 7.16.x to 7.17.0 + +#### Configuration + +The configuration was refactored, especially path parameters. You should +compare the instance/production.py with config/default.py in case you made +adaptions to these. The same goes for instance/tests.py in case you are also +using tests. + +#### IIIF For the IIIF implementation new NPM packages are needed: $ cd openatlas/static @@ -27,7 +36,8 @@ For the IIIF implementation new NPM packages are needed: $ pip3 install -e ./ $ ~/.local/bin/calmjs npm --install openatlas -If you want to use IIIF, please read the [instructions](https://redmine.openatlas.eu/projects/uni/wiki/IIIF). +If you want to use IIIF, please read the +[instructions](https://redmine.openatlas.eu/projects/uni/wiki/IIIF). ### 7.16.0 to 7.16.1 A code base update (e.g. with git pull) and a webserver restart is sufficient. From 96963e1f2944e8a76dea39de3846e5a3aa13093e Mon Sep 17 00:00:00 2001 From: bkoschicek Date: Thu, 12 Oct 2023 15:58:38 +0200 Subject: [PATCH 091/102] fixed install libvips and config file --- install/Dockerfile | 2 +- install/{iiif.conf => iipsrv.conf} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename install/{iiif.conf => iipsrv.conf} (100%) diff --git a/install/Dockerfile b/install/Dockerfile index 115ce8b8a..5980577d6 100644 --- a/install/Dockerfile +++ b/install/Dockerfile @@ -8,7 +8,7 @@ RUN --mount=type=cache,target=/var/cache/apt \ apt install -y --no-install-recommends python3-pandas python3-jinja2 python3-flask-cors python3-flask-restful p7zip-full &&\ apt install -y --no-install-recommends python3-wand python3-rdflib python3-requests python3-dicttoxml python3-rdflib-jsonld python3-flasgger &&\ apt install -y --no-install-recommends apache2 libapache2-mod-wsgi-py3 python3-coverage python3-nose exiftran &&\ - apt install -y --no-install-recommends apache2 iipimage-server libvips &&\ + apt install -y --no-install-recommends apache2 iipimage-server libvips42 &&\ apt install -y --no-install-recommends gettext npm python3-pip git postgresql-client-13 &&\ apt install -y --no-install-recommends dos2unix locales locales-all &&\ mkdir -p /var/www/openatlas /var/www/.cache /var/www/.local /var/www/.npm &&\ diff --git a/install/iiif.conf b/install/iipsrv.conf similarity index 100% rename from install/iiif.conf rename to install/iipsrv.conf From 41a3bf8191a9a6f0b97f66e92b174d92ac0c95d9 Mon Sep 17 00:00:00 2001 From: bkoschicek Date: Thu, 12 Oct 2023 15:59:55 +0200 Subject: [PATCH 092/102] fixed install libvips and config file --- install/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/Dockerfile b/install/Dockerfile index 5980577d6..8f3daa2d0 100644 --- a/install/Dockerfile +++ b/install/Dockerfile @@ -17,7 +17,7 @@ RUN cp -rp /usr/lib/iipimage-server/ /var/www/iipsrv/ &&\ chown -R www-data /var/www/iipsrv/ &&\ chmod 755 -R /var/www/iipsrv/ RUN rm /etc/apache2/mods-available/iipsrv.conf -COPY /install/iiif.conf /etc/apache2/mods-available/ +COPY /install/iipsrv.conf /etc/apache2/mods-available/ COPY --chown=www-data:www-data / /var/www/openatlas/ RUN cd /var/www/openatlas && cp install/entrypoint.sh /entrypoint.sh &&\ cp install/example_apache.conf /etc/apache2/sites-available/000-default.conf &&\ From a0a9df4a5537182cb1391d3bb583486c9ba6d069 Mon Sep 17 00:00:00 2001 From: bkoschicek Date: Thu, 12 Oct 2023 16:53:14 +0200 Subject: [PATCH 093/102] libvips problems --- install/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/Dockerfile b/install/Dockerfile index 8f3daa2d0..914049a36 100644 --- a/install/Dockerfile +++ b/install/Dockerfile @@ -8,7 +8,7 @@ RUN --mount=type=cache,target=/var/cache/apt \ apt install -y --no-install-recommends python3-pandas python3-jinja2 python3-flask-cors python3-flask-restful p7zip-full &&\ apt install -y --no-install-recommends python3-wand python3-rdflib python3-requests python3-dicttoxml python3-rdflib-jsonld python3-flasgger &&\ apt install -y --no-install-recommends apache2 libapache2-mod-wsgi-py3 python3-coverage python3-nose exiftran &&\ - apt install -y --no-install-recommends apache2 iipimage-server libvips42 &&\ + apt install -y iipimage-server libvips42 libvips-tools &&\ apt install -y --no-install-recommends gettext npm python3-pip git postgresql-client-13 &&\ apt install -y --no-install-recommends dos2unix locales locales-all &&\ mkdir -p /var/www/openatlas /var/www/.cache /var/www/.local /var/www/.npm &&\ From 1dc005edf07dc6595382184c26bf5c55ebb87e52 Mon Sep 17 00:00:00 2001 From: bkoschicek Date: Thu, 12 Oct 2023 17:01:28 +0200 Subject: [PATCH 094/102] libvips problems --- install/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/Dockerfile b/install/Dockerfile index 914049a36..b37596849 100644 --- a/install/Dockerfile +++ b/install/Dockerfile @@ -8,7 +8,7 @@ RUN --mount=type=cache,target=/var/cache/apt \ apt install -y --no-install-recommends python3-pandas python3-jinja2 python3-flask-cors python3-flask-restful p7zip-full &&\ apt install -y --no-install-recommends python3-wand python3-rdflib python3-requests python3-dicttoxml python3-rdflib-jsonld python3-flasgger &&\ apt install -y --no-install-recommends apache2 libapache2-mod-wsgi-py3 python3-coverage python3-nose exiftran &&\ - apt install -y iipimage-server libvips42 libvips-tools &&\ + apt install -y --no-install-recommends iipimage-server libvips42 libvips-tools &&\ apt install -y --no-install-recommends gettext npm python3-pip git postgresql-client-13 &&\ apt install -y --no-install-recommends dos2unix locales locales-all &&\ mkdir -p /var/www/openatlas /var/www/.cache /var/www/.local /var/www/.npm &&\ From e75ad1f5ec13b4e6dd5ae3093b7a08dd27b5faa0 Mon Sep 17 00:00:00 2001 From: bkoschicek Date: Thu, 12 Oct 2023 17:09:22 +0200 Subject: [PATCH 095/102] remove premultiply --- openatlas/display/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openatlas/display/util.py b/openatlas/display/util.py index 2251b2ecc..705322e05 100644 --- a/openatlas/display/util.py +++ b/openatlas/display/util.py @@ -777,7 +777,7 @@ def convert_image_to_iiif(id_: int) -> None: command = \ (f"{vips} tiffsave {get_file_path(id_)} {get_iiif_file_path(id_)} " f"--tile --pyramid --compression {compression} " - f"--premultiply --tile-width 128 --tile-height 128") + f"--tile-width 128 --tile-height 128") try: process = subprocess.Popen(command, shell=True) process.wait() From 180ffca631ed12e97fdc071d538f751fc0ecd262 Mon Sep 17 00:00:00 2001 From: BernhardKoschicek Date: Fri, 13 Oct 2023 10:28:50 +0200 Subject: [PATCH 096/102] vips and iiif run in docker --- .github/workflows/starter.yaml | 2 +- install/Dockerfile | 6 +++--- install/iipsrv.conf | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/starter.yaml b/.github/workflows/starter.yaml index fee8d2c15..6b2b27328 100644 --- a/.github/workflows/starter.yaml +++ b/.github/workflows/starter.yaml @@ -118,7 +118,7 @@ jobs: IIIF = { 'activate': True, 'path': '/var/www/iipsrv/', - 'url': 'http://localhost/iiif/', + 'url': 'localhost:8080/iiif/', 'version': 2, 'conversion': True, 'compression': 'jpeg'} diff --git a/install/Dockerfile b/install/Dockerfile index b37596849..17f895733 100644 --- a/install/Dockerfile +++ b/install/Dockerfile @@ -8,16 +8,16 @@ RUN --mount=type=cache,target=/var/cache/apt \ apt install -y --no-install-recommends python3-pandas python3-jinja2 python3-flask-cors python3-flask-restful p7zip-full &&\ apt install -y --no-install-recommends python3-wand python3-rdflib python3-requests python3-dicttoxml python3-rdflib-jsonld python3-flasgger &&\ apt install -y --no-install-recommends apache2 libapache2-mod-wsgi-py3 python3-coverage python3-nose exiftran &&\ - apt install -y --no-install-recommends iipimage-server libvips42 libvips-tools &&\ + apt install -y --no-install-recommends iipimage-server libvips-tools &&\ apt install -y --no-install-recommends gettext npm python3-pip git postgresql-client-13 &&\ apt install -y --no-install-recommends dos2unix locales locales-all &&\ mkdir -p /var/www/openatlas /var/www/.cache /var/www/.local /var/www/.npm &&\ chown -R www-data:www-data /var/www/.cache /var/www/.local /var/www/.npm /var/log/apache2 /var/run/apache2 RUN cp -rp /usr/lib/iipimage-server/ /var/www/iipsrv/ &&\ chown -R www-data /var/www/iipsrv/ &&\ - chmod 755 -R /var/www/iipsrv/ + chmod 777 -R /var/www/iipsrv/ RUN rm /etc/apache2/mods-available/iipsrv.conf -COPY /install/iipsrv.conf /etc/apache2/mods-available/ +COPY /install/iipsrv.conf /etc/apache2/mods-available/iipsrv.conf COPY --chown=www-data:www-data / /var/www/openatlas/ RUN cd /var/www/openatlas && cp install/entrypoint.sh /entrypoint.sh &&\ cp install/example_apache.conf /etc/apache2/sites-available/000-default.conf &&\ diff --git a/install/iipsrv.conf b/install/iipsrv.conf index 2837b84fd..fc393c860 100644 --- a/install/iipsrv.conf +++ b/install/iipsrv.conf @@ -2,7 +2,7 @@ ScriptAlias /iiif "/var/www/iipsrv/iipsrv.fcgi" # Set the options on that directory - + AllowOverride None Options None From 922f3aab27f3fd16ed0f86f4ea9f6d0b0a1c1faf Mon Sep 17 00:00:00 2001 From: BernhardKoschicek Date: Fri, 13 Oct 2023 10:52:04 +0200 Subject: [PATCH 097/102] added http to localhost --- .github/workflows/starter.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/starter.yaml b/.github/workflows/starter.yaml index 6b2b27328..7808eaf34 100644 --- a/.github/workflows/starter.yaml +++ b/.github/workflows/starter.yaml @@ -118,7 +118,7 @@ jobs: IIIF = { 'activate': True, 'path': '/var/www/iipsrv/', - 'url': 'localhost:8080/iiif/', + 'url': 'http://localhost:8080/iiif/', 'version': 2, 'conversion': True, 'compression': 'jpeg'} From 9bc0a8cc6a96e0f83155ba989818963fec9b943c Mon Sep 17 00:00:00 2001 From: BernhardKoschicek Date: Fri, 13 Oct 2023 11:12:56 +0200 Subject: [PATCH 098/102] rewrote file test asserts --- tests/test_file.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/tests/test_file.py b/tests/test_file.py index e3489b0e1..ec2056fba 100644 --- a/tests/test_file.py +++ b/tests/test_file.py @@ -120,17 +120,21 @@ def test_file(self) -> None: id_=file_id, version=app.config['IIIF']['version'], _external=True)) - assert b'/iiif/2/145.tiff' in rv.data + rv = rv.get_json() + assert bool(rv['label'] == 'Updated file') rv = self.app.get( url_for('api.iiif_sequence', id_=file_id)) - assert b'/iiif/2/145.tiff' in rv.data + rv = rv.get_json() + assert bool(str(file_id) in rv['@id']) rv = self.app.get( url_for('api.iiif_image', id_=file_id)) - assert b'/iiif/2/145.tiff' in rv.data + rv = rv.get_json() + assert bool(str(file_id) in rv['@id']) rv = self.app.get( url_for('api.iiif_canvas', id_=file_id)) - assert b'/iiif/2/145.tiff' in rv.data + rv = rv.get_json() + assert bool(str(file_id) in rv['@id']) rv = self.app.get(url_for('view_iiif', id_=file_id)) assert b'Mirador' in rv.data @@ -153,5 +157,3 @@ def test_file(self) -> None: url_for('delete', id_=file.id), follow_redirects=True) assert b'The entry has been deleted' in rv.data - - From 8959a92a7ed54cab05d9b569b209115eb098e9ca Mon Sep 17 00:00:00 2001 From: bkoschicek Date: Fri, 13 Oct 2023 11:49:27 +0200 Subject: [PATCH 099/102] check if initial config also works --- install/iipsrv.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/install/iipsrv.conf b/install/iipsrv.conf index fc393c860..caa64ee86 100644 --- a/install/iipsrv.conf +++ b/install/iipsrv.conf @@ -2,7 +2,7 @@ ScriptAlias /iiif "/var/www/iipsrv/iipsrv.fcgi" # Set the options on that directory - + AllowOverride None Options None @@ -31,4 +31,4 @@ FcgidInitialEnv URI_MAP "iiif=>IIIF" # Define the idle timeout as unlimited and the number of # processes we want FcgidIdleTimeout 0 -FcgidMaxProcessesPerClass 1 \ No newline at end of file +FcgidMaxProcessesPerClass 1 From 3cb1f71b53ea91238339a6c7e1e76accf9065e98 Mon Sep 17 00:00:00 2001 From: Alexander Watzinger Date: Mon, 16 Oct 2023 18:04:53 +0200 Subject: [PATCH 100/102] IIIF clean up with Bernhard --- config/default.py | 2 +- openatlas/display/base_display.py | 2 +- openatlas/display/display.py | 4 ++-- openatlas/display/util.py | 6 +++--- openatlas/views/entity.py | 2 +- openatlas/views/entity_index.py | 6 +++--- tests/test_file.py | 28 +++++++++++++--------------- 7 files changed, 24 insertions(+), 26 deletions(-) diff --git a/config/default.py b/config/default.py index d34d75a26..7f2aeb328 100644 --- a/config/default.py +++ b/config/default.py @@ -41,7 +41,7 @@ 'thumbnail': '200', 'table': '100'} IIIF = { - 'activate': False, + 'enabled': False, 'path': '', 'url': '', 'version': 2, diff --git a/openatlas/display/base_display.py b/openatlas/display/base_display.py index 21cd35d56..5d026696a 100644 --- a/openatlas/display/base_display.py +++ b/openatlas/display/base_display.py @@ -79,7 +79,7 @@ def get_type_data(self) -> dict[str, Any]: def add_file_tab_thumbnails(self) -> None: if 'file' in self.tabs and current_user.settings['table_show_icons']: - if app.config['IIIF']['activate'] \ + if app.config['IIIF']['enabled'] \ or g.settings['image_processing']: self.tabs['file'].table.header.insert(1, _('icon')) for row in self.tabs['file'].table.rows: diff --git a/openatlas/display/display.py b/openatlas/display/display.py index 1c3d38719..e5ce54fef 100644 --- a/openatlas/display/display.py +++ b/openatlas/display/display.py @@ -80,11 +80,11 @@ def add_button_others(self) -> None: if check_iiif_file_exist(self.entity.id) \ or not app.config['IIIF']['conversion']: self.buttons.append(button( - _('iiif'), + _('view in IIIF'), url_for('view_iiif', id_=self.entity.id))) else: self.buttons.append(button( - _('make_iiif_available'), + _('enable IIIF view'), url_for('make_iiif_available', id_=self.entity.id))) return self.buttons.append( diff --git a/openatlas/display/util.py b/openatlas/display/util.py index 71b235e4b..9a1a7b89c 100644 --- a/openatlas/display/util.py +++ b/openatlas/display/util.py @@ -233,7 +233,7 @@ def profile_image(entity: Entity) -> str: src = url_for('display_file', filename=path.name) url = src width = g.settings["profile_image_width"] - if app.config['IIIF']['activate'] and check_iiif_file_exist(entity.id): + if app.config['IIIF']['enabled'] and check_iiif_file_exist(entity.id): url = url_for('view_iiif', id_=entity.id) iiif_ext = '.tiff' if app.config['IIIF']['conversion'] \ else g.files[entity.id].suffix @@ -427,7 +427,7 @@ def system_warnings(_context: str, _unneeded_string: str) -> str: warnings.append( f"Database version {app.config['DATABASE_VERSION']} is needed but " f"current version is {g.settings['database_version']}") - if app.config['IIIF']['activate']: + if app.config['IIIF']['enabled']: if path := app.config['IIIF']['path']: check_write_access(path, warnings) for path in g.writable_paths: @@ -754,7 +754,7 @@ def get_entities_linked_to_type_recursive( def check_iiif_activation() -> bool: iiif = app.config['IIIF'] - return bool(iiif['activate'] and os.access(Path(iiif['path']), os.W_OK)) + return bool(iiif['enabled'] and os.access(Path(iiif['path']), os.W_OK)) def check_iiif_file_exist(id_: int) -> bool: diff --git a/openatlas/views/entity.py b/openatlas/views/entity.py index 6f2f8747f..778a96304 100644 --- a/openatlas/views/entity.py +++ b/openatlas/views/entity.py @@ -331,6 +331,6 @@ def delete_files(id_: int) -> None: path.unlink() for resized_path in app.config['RESIZED_IMAGES'].glob(f'**/{id_}.*'): resized_path.unlink() - if app.config['IIIF']['activate'] and check_iiif_file_exist(id_): + if app.config['IIIF']['enabled'] and check_iiif_file_exist(id_): if path := get_iiif_file_path(id_): path.unlink() diff --git a/openatlas/views/entity_index.py b/openatlas/views/entity_index.py index 4da44ba4d..d24df5b7b 100644 --- a/openatlas/views/entity_index.py +++ b/openatlas/views/entity_index.py @@ -42,7 +42,7 @@ def get_table(view: str) -> Table: if view == 'file': table.order = [[0, 'desc']] table.header = ['date'] + table.header - if (g.settings['image_processing'] or app.config['IIIF']['activate']) \ + if (g.settings['image_processing'] or app.config['IIIF']['enabled']) \ and current_user.settings['table_show_icons']: table.header.insert(1, _('icon')) for entity in Entity.get_by_class('file', types=True): @@ -54,7 +54,7 @@ def get_table(view: str) -> Table: entity.get_file_ext(), entity.description] if (g.settings['image_processing'] - or app.config['IIIF']['activate']) \ + or app.config['IIIF']['enabled']) \ and current_user.settings['table_show_icons']: data.insert(1, file_preview(entity.id)) table.rows.append(data) @@ -79,7 +79,7 @@ def get_table(view: str) -> Table: def file_preview(entity_id: int) -> str: size = app.config['IMAGE_SIZE']['table'] param = f"loading='lazy' alt='image' max-width='100px' max-height='100px'" - if app.config['IIIF']['activate'] and check_iiif_file_exist(entity_id): + if app.config['IIIF']['enabled'] and check_iiif_file_exist(entity_id): ext = '.tiff' if app.config['IIIF']['conversion'] \ else g.files[entity_id].suffix url = (f"{app.config['IIIF']['url']}{entity_id}{ext}" diff --git a/tests/test_file.py b/tests/test_file.py index ec2056fba..815d3cfcc 100644 --- a/tests/test_file.py +++ b/tests/test_file.py @@ -1,4 +1,5 @@ from pathlib import Path +from typing import Any from flask import url_for @@ -17,10 +18,10 @@ def test_file(self) -> None: reference = insert('edition', 'Ancient Books') logo = Path(app.root_path) \ - / 'static' / 'images' / 'layout' / 'logo.png' + / 'static' / 'images' / 'layout' / 'logo.png' with open(logo, 'rb') as img_1, open(logo, 'rb') as img_2: - rv = self.app.post( + rv: Any = self.app.post( url_for('insert', class_='file', origin_id=place.id), data={'name': 'OpenAtlas logo', 'file': [img_1, img_2]}, follow_redirects=True) @@ -105,7 +106,7 @@ def test_file(self) -> None: assert b'Logo' in rv.data rv = self.app.get(url_for('view', id_=file_id)) - assert b'make_iiif_available' in rv.data + assert b'enable IIIF view' in rv.data rv = self.app.get( url_for('make_iiif_available', id_=file_id), @@ -113,26 +114,23 @@ def test_file(self) -> None: assert b'IIIF converted' in rv.data rv = self.app.get(url_for('view', id_=file_id)) - assert b'iiif' in rv.data + assert b'view in IIIF' in rv.data - rv = self.app.get(url_for( - 'api.iiif_manifest', - id_=file_id, - version=app.config['IIIF']['version'], - _external=True)) + rv = self.app.get( + url_for( + 'api.iiif_manifest', + id_=file_id, + version=app.config['IIIF']['version'])) rv = rv.get_json() assert bool(rv['label'] == 'Updated file') - rv = self.app.get( - url_for('api.iiif_sequence', id_=file_id)) + rv = self.app.get(url_for('api.iiif_sequence', id_=file_id)) rv = rv.get_json() assert bool(str(file_id) in rv['@id']) - rv = self.app.get( - url_for('api.iiif_image', id_=file_id)) + rv = self.app.get(url_for('api.iiif_image', id_=file_id)) rv = rv.get_json() assert bool(str(file_id) in rv['@id']) - rv = self.app.get( - url_for('api.iiif_canvas', id_=file_id)) + rv = self.app.get(url_for('api.iiif_canvas', id_=file_id)) rv = rv.get_json() assert bool(str(file_id) in rv['@id']) From 0e6d57710afed447c90f852023bedf0fd609c063 Mon Sep 17 00:00:00 2001 From: bkoschicek Date: Wed, 18 Oct 2023 09:54:06 +0200 Subject: [PATCH 101/102] fixed arche import --- openatlas/api/import_scripts/util.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openatlas/api/import_scripts/util.py b/openatlas/api/import_scripts/util.py index b41eeefc2..278927498 100644 --- a/openatlas/api/import_scripts/util.py +++ b/openatlas/api/import_scripts/util.py @@ -50,6 +50,8 @@ def vocabs_requests( def request_arche_metadata(id_: int) -> dict[str, Any]: req = requests.get( f"{app.config['ARCHE']['url']}/api/{id_}/metadata", - headers={'Accept': 'application/ld+json'}, + headers={ + 'Accept': 'application/ld+json', + 'X-METADATA-READ-MODE': '1_1_0_0'}, timeout=60) return req.json() From d4eb118d07e97dee7fbe67b24f7536d4b2ca13de Mon Sep 17 00:00:00 2001 From: bkoschicek Date: Wed, 18 Oct 2023 13:03:25 +0200 Subject: [PATCH 102/102] adopted for iiif test --- .github/workflows/starter.yaml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/starter.yaml b/.github/workflows/starter.yaml index 7808eaf34..3a9bcaec8 100644 --- a/.github/workflows/starter.yaml +++ b/.github/workflows/starter.yaml @@ -112,11 +112,9 @@ jobs: WTF_CSRF_METHODS: list[str] = [] ARCHE = { 'id': 0, - 'collection_ids': [0], - 'base_url': 'https://arche-curation.acdh-dev.oeaw.ac.at/', - 'thumbnail_url': 'https://arche-thumbnails.acdh.oeaw.ac.at/'} + 'url': 'https://arche-curation.acdh-dev.oeaw.ac.at/'} IIIF = { - 'activate': True, + 'enabled': True, 'path': '/var/www/iipsrv/', 'url': 'http://localhost:8080/iiif/', 'version': 2,