├── tests ├── __init__.py ├── input_data │ ├── collection-basic.json │ ├── manifest-services.json │ ├── manifest-annos.json │ ├── manifest-sequences.json │ └── manifest-basic.json ├── remote_cache │ ├── tokyo.annolist.json │ ├── tokyo.manifest.json │ ├── getty.manifest.edu │ ├── ghent.manifest.edu │ ├── dublin.manifest.json │ ├── harvard-art.manifest.json │ ├── ycba.manifest.json │ ├── ncsu.annolist.json │ ├── ncsu.manifest.json │ └── nlw-newspaper.manifest.json └── test_upgrader.py ├── requirements.txt ├── iiif_prezi_upgrader ├── __init__.py ├── _version.py └── prezi_upgrader.py ├── Dockerfile ├── prezi2to3.wsgi ├── .travis.yml ├── setup.py ├── .gitignore ├── prezi2to3.py ├── README.md ├── twoToThreeUpgraderService.py ├── templates └── index.tpl └── LICENSE /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | bottle 2 | requests 3 | -------------------------------------------------------------------------------- /iiif_prezi_upgrader/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | from ._version import __version__ 3 | from .prezi_upgrader import Upgrader -------------------------------------------------------------------------------- /iiif_prezi_upgrader/_version.py: -------------------------------------------------------------------------------- 1 | """Version number for this IIIF Presentation API Upgrader library.""" 2 | __version__ = '0.1.0' 3 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM grahamdumpleton/mod-wsgi-docker:python-3.5-onbuild 2 | # Run tests: 3 | RUN python setup.py test 4 | 5 | CMD [ "prezi2to3.wsgi" ] 6 | -------------------------------------------------------------------------------- /prezi2to3.wsgi: -------------------------------------------------------------------------------- 1 | import os 2 | # Change working directory so relative paths (and template lookup) work again 3 | os.chdir(os.path.dirname(__file__)) 4 | 5 | import bottle 6 | from twoToThreeUpgraderService import Service 7 | # ... build or import your bottle application here ... 8 | # Do NOT use bottle.run() with mod_wsgi 9 | s = Service() 10 | application = s.get_bottle_app() 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | - "3.5" 5 | - "3.6" 6 | install: 7 | - pip install coveralls 8 | - python setup.py install 9 | script: 10 | - python setup.py test 11 | - python prezi2to3.py tests/input_data/manifest-basic.json --output /dev/null 12 | after_success: 13 | - coverage run --source=iiif_prezi_upgrader setup.py test 14 | - coveralls 15 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """Setup for iiif-prezi-upgrader.""" 2 | from setuptools import setup 3 | import sys 4 | 5 | setup( 6 | name='iiif-prezi-upgrader', 7 | packages=['iiif_prezi_upgrader'], 8 | test_suite="tests", 9 | version='0.1.0', 10 | description='A library to upgrade ', 11 | author='Rob Sanderson, Simeon Warner, Glen Robson', 12 | author_email='rsanderson@getty.edu', 13 | url='https://github.com/iiif-prezi/prezi-2-to-3', 14 | classifiers=[ 15 | "Programming Language :: Python", 16 | "Programming Language :: Python :: 3", 17 | "Programming Language :: Python :: 2", 18 | "License :: OSI Approved :: Apache Software License", 19 | "Development Status :: 3 - Alpha", 20 | "Intended Audience :: Developers", 21 | "Operating System :: OS Independent", 22 | "Topic :: Software Development :: Libraries :: Python Modules", 23 | ] 24 | ) 25 | -------------------------------------------------------------------------------- /tests/input_data/collection-basic.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/2/context.json", 3 | "@id": "http://example.org/iiif/collection/top", 4 | "@type": "sc:Collection", 5 | "label": "Top Level Collection for Example Organization", 6 | "viewingHint": "top", 7 | "description": "Description of Collection", 8 | "attribution": "Provided by Example Organization", 9 | 10 | "collections": [ 11 | { 12 | "@id": "http://example.org/iiif/collection/sub1", 13 | "@type": "sc:Collection", 14 | "label": "Sub-Collection 1", 15 | 16 | "members": [ 17 | { 18 | "@id": "http://example.org/iiif/collection/part1", 19 | "@type": "sc:Collection", 20 | "label": "My Multi-volume Set", 21 | "viewingHint": "multi-part" 22 | }, 23 | { 24 | "@id": "http://example.org/iiif/book1/manifest1", 25 | "@type": "sc:Manifest", 26 | "label": "My Book" 27 | }, 28 | { 29 | "@id": "http://example.org/iiif/collection/part2", 30 | "@type": "sc:Collection", 31 | "label": "My Sub Collection", 32 | "viewingHint": "individuals" 33 | } 34 | ] 35 | }, 36 | { 37 | "@id": "http://example.org/iiif/collection/part2", 38 | "@type": "sc:Collection", 39 | "label": "Sub Collection 2" 40 | } 41 | ], 42 | 43 | "manifests": [ 44 | { 45 | "@id": "http://example.org/iiif/book1/manifest", 46 | "@type": "sc:Manifest", 47 | "label": "Book 1" 48 | } 49 | ] 50 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | -------------------------------------------------------------------------------- /prezi2to3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | """IIIF Convertor Service""" 4 | 5 | import argparse 6 | import json 7 | import os 8 | import sys 9 | 10 | from iiif_prezi_upgrader import Upgrader 11 | from iiif_prezi_upgrader.prezi_upgrader import FLAGS 12 | 13 | if __name__ == "__main__": 14 | #if len(sys.argv) != 2 and len(sys.argv) != 3: 15 | # print ('Usage:\n\tpython prezi2to3.py [file_path or url to manifest] [optional output file name]') 16 | # sys.exit(0) 17 | 18 | parser = argparse.ArgumentParser(description=__doc__.strip(), 19 | formatter_class=argparse.ArgumentDefaultsHelpFormatter) 20 | parser.add_argument('manifest', metavar='manifest', type=str, help='file_path or url to manifest') 21 | parser.add_argument('--output', metavar='filename', type=str, help='output file name') 22 | for key in FLAGS: 23 | name=key 24 | description=FLAGS[key]['description'] 25 | default=FLAGS[key]['default'] 26 | type=type(default) 27 | if FLAGS[key]['default'] is not str: 28 | type=bool 29 | parser.add_argument('--%s' % name, default=default, type=type, help=description) 30 | 31 | 32 | args = parser.parse_args() 33 | 34 | manifest = args.manifest 35 | # default flags currently but if flags are assessible then could convert them to use --ext_ok=true using ArgumentParser 36 | upgrader = Upgrader(flags=vars(args)) # create an upgrader 37 | if 'http' in manifest: # should catch https as well 38 | v3 = upgrader.process_uri(manifest) 39 | else: 40 | v3 = upgrader.process_cached(manifest) 41 | 42 | v3 = upgrader.reorder(v3) 43 | 44 | if args.output: 45 | # output to filename 46 | with open(args.output, 'w') as outfile: 47 | json.dump(v3, outfile, indent=2) 48 | else: 49 | print(json.dumps(v3, indent=2)) 50 | -------------------------------------------------------------------------------- /tests/remote_cache/tokyo.annolist.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/2/context.json", 3 | "@id": "https://dzkimgs.l.u-tokyo.ac.jp/iiif/zuzoubu/12b02/list/p0001-0025.json", 4 | "@type": "sc:AnnotationList", 5 | "resources": [ 6 | { 7 | "@id": "https://dzkimgs.l.u-tokyo.ac.jp/iiif/zuzoubu/12b02/p0001-0025/1", 8 | "@type": "oa:Annotation", 9 | "motivation": "oa:classifying", 10 | "resource": { 11 | "@id": "https://dzkimgs.l.u-tokyo.ac.jp/iiif/zuzoubu/12b02/p0001-0025/1/1", 12 | "@type": "cnt:ContentAsText", 13 | "language": "ja", 14 | "format": "text/html", 15 | "chars": "
大日如來
(仏・如來)
目の数: 二目 : 白毫無 持物: 輪宝 , 臂数: 二臂 台座: 蓮華 , 光背: 炎光 , 顔の向き: 正面
タグ付け担当: 永崎研宣
" 16 | }, 17 | "on": "https://dzkimgs.l.u-tokyo.ac.jp/iiif/zuzoubu/12b02/p0001-0025#xywh=9912,13519,2583,2663" 18 | }, 19 | { 20 | "@id": "https://dzkimgs.l.u-tokyo.ac.jp/iiif/zuzoubu/12b02/p0001-0025/2", 21 | "@type": "oa:Annotation", 22 | "motivation": "oa:classifying", 23 | "resource": { 24 | "@id": "https://dzkimgs.l.u-tokyo.ac.jp/iiif/zuzoubu/12b02/p0001-0025/2/1", 25 | "@type": "cnt:ContentAsText", 26 | "language": "ja", 27 | "format": "text/html", 28 | "chars": "
八葉院
(曼荼羅)
目の数: 二目 : 白毫無 臂数: 二臂
タグ付け担当: 永崎研宣
" 29 | }, 30 | "on": "https://dzkimgs.l.u-tokyo.ac.jp/iiif/zuzoubu/12b02/p0001-0025#xywh=8457,11791,5583,6317" 31 | } 32 | ] 33 | } -------------------------------------------------------------------------------- /tests/input_data/manifest-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/2/context.json", 3 | "@id": "http://iiif.io/api/presentation/2.1/example/fixtures/1/manifest.json", 4 | "@type": "sc:Manifest", 5 | "label": "Manifest Label", 6 | 7 | "service": { 8 | "@context": "http://iiif.io/api/search/1/context.json", 9 | "@id": "http://example.org/services/identifier/search", 10 | "profile": "http://iiif.io/api/search/1/search", 11 | "service": { 12 | "@id": "http://example.org/services/identifier/autocomplete", 13 | "profile": "http://iiif.io/api/search/1/autocomplete" 14 | } 15 | }, 16 | 17 | "sequences": [ 18 | { 19 | "@type": "sc:Sequence", 20 | "label": "Current Order", 21 | "canvases": [ 22 | { 23 | "@id": "http://iiif.io/api/presentation/2.1/example/fixtures/canvas/1/c1.json", 24 | "@type": "sc:Canvas", 25 | "label": "Test 1 Canvas: 1", 26 | "height": 1800, 27 | "width": 1200, 28 | "images": [ 29 | { 30 | "@type": "oa:Annotation", 31 | "motivation": "sc:painting", 32 | "resource": { 33 | "@id": "http://iiif.io/api/presentation/2.1/example/fixtures/resources/page1-full.png", 34 | "@type": "dctypes:Image", 35 | 36 | "service": { 37 | "@context": "http://iiif.io/api/image/2/context.json", 38 | "@id": "http://example.org/iiif/book1-page1", 39 | "profile": "http://iiif.io/api/image/2/level2.json", 40 | "service": { 41 | "@context": "http://iiif.io/api/auth/1/context.json", 42 | "@id": "https://authentication.example.org/login", 43 | "profile": "http://iiif.io/api/auth/1/login", 44 | "label": "Login to Example Institution", 45 | "header": "Please Log In", 46 | "description": "Example Institution requires that you log in with your example account to view this content.", 47 | "confirmLabel": "Login", 48 | "failureHeader": "Authentication Failed", 49 | "failureDescription": "Access Policy", 50 | "service": [ 51 | { 52 | "@id": "https://authentication.example.org/token", 53 | "profile": "http://iiif.io/api/auth/1/token" 54 | }, 55 | { 56 | "@id": "https://authentication.example.org/logout", 57 | "profile": "http://iiif.io/api/auth/1/logout", 58 | "label": "Logout from Example Institution" 59 | } 60 | ] 61 | } 62 | } 63 | 64 | }, 65 | "on": "http://iiif.io/api/presentation/2.1/example/fixtures/canvas/1/c1.json" 66 | } 67 | ] 68 | } 69 | ] 70 | } 71 | ] 72 | } -------------------------------------------------------------------------------- /tests/remote_cache/tokyo.manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/2/context.json", 3 | "@type": "sc:Manifest", 4 | "@id": "https://dzkimgs.l.u-tokyo.ac.jp/iiif/zuzoubu/12b02/manifest.json", 5 | "label": "大正新脩大藏經図像部第12b02巻", 6 | "metadata": [ 7 | { 8 | "label": "Author", 9 | "value": "高楠順次郎" 10 | }, 11 | { 12 | "label": "published", 13 | "value": [ 14 | { 15 | "@value": "大蔵出版", 16 | "@language": "ja" 17 | } 18 | ] 19 | }, 20 | { 21 | "label": "Source", 22 | "value": "大正新脩大藏經 図像部" 23 | }, 24 | { 25 | "label": "manifest URI", 26 | "value": "https://dzkimgs.l.u-tokyo.ac.jp/iiif/zuzoubu/12b02/manifest.json" 27 | } 28 | ], 29 | "description": "大正新脩大藏經図像部", 30 | "viewingDirection": "right-to-left", 31 | "viewingHint": "paged", 32 | "license": "http://creativecommons.org/licenses/by-sa/4.0/", 33 | "attribution": "大蔵出版(Daizo shuppan) and SAT大蔵経テキストデータベース研究会(SAT Daizōkyō Text Database Committee) ", 34 | "logo": "https://dzkimgs.l.u-tokyo.ac.jp/iiif/zuzoubu/satlogo80.png", 35 | "sequences": [ 36 | { 37 | "@id": "https://dzkimgs.l.u-tokyo.ac.jp/zuzoubu/12b02/sequence.json", 38 | "@type": "sc:Sequence", 39 | "label": "Current Page Order", 40 | "canvases": [ 41 | { 42 | "@id": "https://dzkimgs.l.u-tokyo.ac.jp/iiif/zuzoubu/12b02/p0001-0025", 43 | "@type": "sc:Canvas", 44 | "label": "", 45 | "width": 22779, 46 | "height": 30000, 47 | "images": [ 48 | { 49 | "@id": "https://dzkimgs.l.u-tokyo.ac.jp/iiif/zuzoubu/12b02/ano0001-0025", 50 | "@type": "oa:Annotation", 51 | "motivation": "sc:painting", 52 | "resource": { 53 | "@id": "https://dzkimgs.l.u-tokyo.ac.jp/iiifimgs/zuzoubu/12b02/0001-0025.tif/full/full/0/default.jpg", 54 | "@type": "dctypes:Image", 55 | "format": "image/jpeg", 56 | "width": 22779, 57 | "height": 30000, 58 | "service": { 59 | "@context": "http://iiif.io/api/image/2/context.json", 60 | "@id": "https://dzkimgs.l.u-tokyo.ac.jp/iiifimgs/zuzoubu/12b02/0001-0025.tif", 61 | "profile": "http://iiif.io/api/image/2/level1.json" 62 | } 63 | }, 64 | "on": "https://dzkimgs.l.u-tokyo.ac.jp/iiif/zuzoubu/12b02/p0001-0025" 65 | } 66 | ], 67 | "otherContent": [ 68 | { 69 | "@id": "https://dzkimgs.l.u-tokyo.ac.jp/iiif/zuzoubu/12b02/list/p0001-0025.json", 70 | "@type": "sc:AnnotationList" 71 | } 72 | ] 73 | } 74 | ] 75 | } 76 | ] 77 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # prezi-2-to-3 2 | 3 | [![Build Status](https://travis-ci.org/iiif/prezi-2-to-3.svg?branch=master)](https://travis-ci.org/iiif/prezi-2-to-3) 4 | [![Coverage Status](https://coveralls.io/repos/github/iiif/prezi-2-to-3/badge.svg?branch=master)](https://coveralls.io/github/iiif/prezi-2-to-3?branch=master) 5 | 6 | Libraries to upgrade IIIF Presentation API manifest from v2 to v3 automatically 7 | 8 | 9 | # Usage: 10 | 11 | There are three options on how to use this program either through Docker, installed locally or programatically. Details below: 12 | 13 | ## Using Docker 14 | 15 | Build the docker image: 16 | 17 | ``` 18 | docker build -t prezi-2-to-3 . 19 | ``` 20 | 21 | Run the image: 22 | 23 | ``` 24 | docker run -it --rm -p 8000:80 --name upgrader prezi-2-to-3:latest 25 | ``` 26 | 27 | or run both with the following command: 28 | 29 | ``` 30 | docker build -t prezi-2-to-3 . && docker run -it --rm -p 8000:80 --name upgrader prezi-2-to-3:latest 31 | ``` 32 | 33 | Then navigating to the following page: . 34 | 35 | ## Installing locally 36 | 37 | ``` 38 | sudo python setup.py install 39 | ``` 40 | 41 | You can then either run a web version or run it from the command line. 42 | 43 | ### Command line 44 | 45 | Usage: 46 | 47 | ``` 48 | python prezi2to3.py 49 | Usage: 50 | python prezi2to3.py [file_path or url to manifest] [optional output file name] 51 | ``` 52 | 53 | Examples: 54 | 55 | ``` 56 | # Convert manifest from filesystem and print results to screen 57 | python prezi2to3.py tests/input_data/manifest-services.json 58 | 59 | # Convert remote manfiest and save results to /tmp/upgraded.json 60 | python prezi2to3.py http://iiif.io/api/presentation/2.1/example/fixtures/1/manifest.json /tmp/upgraded.json 61 | ``` 62 | 63 | ### Web version 64 | To run the web version: 65 | 66 | ``` 67 | ./twoToThreeUpgraderService.py --port 8000 68 | ``` 69 | 70 | and navigate to . Note the default port if not specified is 8080. 71 | 72 | ## Using programatically 73 | 74 | Create an Upgrader, and then call `process_cached` with the path to a version 2.x IIIF Presentation API resource on disk, or `process_uri` with a URI to the same. If the JSON is already in memory, then call `process_resource` instead. The results of the call will be the JSON of the equivalent version 3.0 resource. 75 | 76 | ```python 77 | from iiif_prezi_upgrader import Upgrader 78 | upgrader = Upgrader(flags={"flag_name" : "flag_value"}) # create an upgrader 79 | v3 = upgrader.process_cached("/path/to/iiif/v2/file.json") 80 | v3 = upgrader.process_uri("http://example.org/iiif/v2/file.json") 81 | v3 = upgrader.process_resource(json, top=True) 82 | ``` 83 | 84 | ## Flags 85 | 86 | * `desc_2_md` : The `description` property is not a summary, and hence should be put in as a `metadata` pair. The label generated will be "Description". The default is `True`. 87 | * `related_2_md` : The `related` property is not a homepage, and hence should be put in as a `metadata` pair. The label generated will be "Related". The default is `False` (and hence the property will simply be renamed as `homepage`) 88 | * `ext_ok` : Should extensions be copied through to the new version. The default is `False`. 89 | * `default_lang` : The default language to use for language maps. The default is "@none". 90 | * `deref_links` : Should links without a `format` property be dereferenced and the HTTP response inspected for the media type? The default is `True`. 91 | * `debug` : Are we in debug mode and should spit out more warnings than normal? The default is `False` 92 | 93 | 94 | # FAQ 95 | 96 | * Does this rely on iiif-prezi's ManifestFactory? No. It has as few dependencies as possible to allow it to be ported to other languages. 97 | * Is it up to date? It is developed by two of the IIIF Editors (@azaroth42 and @zimeon) and we try to keep it up to date with the latest draft version 3.0 Presentation API specs. 98 | * Are PRs welcome? Yes :) 99 | -------------------------------------------------------------------------------- /tests/remote_cache/getty.manifest.edu: -------------------------------------------------------------------------------- 1 | {"@context":"http:\/\/iiif.io\/api\/presentation\/2\/context.json","@id":"https:\/\/data.getty.edu\/museum\/api\/iiif\/298147\/manifest.json","@type":"sc:Manifest","label":"La Surprise (1718\u20131719), Jean-Antoine Watteau (French, 1684 - 1721)","description":"In a verdant park at sunset, a young woman abandons herself to her tousle-haired companion\u2019s ardent embrace. Coiled up in a pose of centrifugal energy, the impulsive lovers are oblivious to the third figure: Mezzetin, sitting on the same rocky outcrop. Drawn from the theatrical tradition of the commedia dell\u2019arte, this character represents a poignant foil to the couple\u2019s unbridled passion. Introverted and with a melancholy air, he tunes his guitar, knowing that his serenading will mean nothing to the lovers and serve only to heighten his own sense of lonely longing as he gazes upon them. His costume, a rose-coloured jacket and knee-britches slashed with yellow and adorned with blue ribbons as well as a lace ruff and cuffs, is reminiscent of the paintings of Anthony van Dyck. The small dog at lower right, a quotation from Rubens, watches the couple with considerably more appreciation than Mezzetin can muster.","metadata":[{"label":"Artist \/ Maker","value":"Jean-Antoine Watteau (French, 1684 - 1721)"},{"label":"Culture & Date","value":"French, 1718\u20131719"},{"label":"Medium","value":"Oil on panel"},{"label":"Dimensions","value":"36.4 \u00d7 28.2 cm (14 5\/16 \u00d7 11 1\/8 in.)"},{"label":"Object Number","value":"2017.72"},{"label":"Object Type","value":"Painting"},{"label":"Place Created","value":"France"},{"label":"Collection","value":"The J. Paul Getty Museum<\/a>"},{"label":"Rights Statement","value":"
Images provided here are believed to be in the public domain and are made available under the terms of the Getty's Open Content Program<\/a><\/nobr>. Texts provided here are \u00a9 J. Paul Getty Trust, licensed under CC BY 4.0<\/a>. Terms of use for the Getty logo can be found here<\/a>.<\/div>"}],"thumbnail":"https:\/\/data.getty.edu\/museum\/api\/iiif\/633385\/full\/231,300\/0\/default.jpg","viewingDirection":"left-to-right","license":"http:\/\/www.getty.edu\/legal\/copyright.html#oc","attribution":"Digital image courtesy of the Getty's Open Content Program<\/a>.","logo":"http:\/\/www.getty.edu\/museum\/media\/graphics\/web\/logos\/getty-logo.png","within":"http:\/\/www.getty.edu\/art\/collection\/","sequences":[{"@id":"https:\/\/data.getty.edu\/museum\/api\/iiif\/298147\/sequence\/main","@type":"sc:Sequence","label":"Object","viewingDirection":"left-to-right","viewingHint":"paged","startCanvas":"https:\/\/data.getty.edu\/museum\/api\/iiif\/298147\/canvas\/main","canvases":[{"@id":"https:\/\/data.getty.edu\/museum\/api\/iiif\/298147\/canvas\/main","@type":"sc:Canvas","label":"Main Image","width":4937,"height":6406,"images":[{"@id":"https:\/\/data.getty.edu\/museum\/api\/iiif\/298147\/annotation\/main-image","@type":"oa:Annotation","motivation":"sc:painting","resource":{"@id":"https:\/\/data.getty.edu\/museum\/api\/iiif\/633385\/full\/789,1024\/0\/default.jpg","@type":"dctypes:Image","format":"image\/jpeg","service":{"@context":"http:\/\/iiif.io\/api\/image\/2\/context.json","@id":"https:\/\/data.getty.edu\/museum\/api\/iiif\/633385","profile":"http:\/\/iiif.io\/api\/image\/2\/level0.json"},"width":789,"height":1024},"on":"https:\/\/data.getty.edu\/museum\/api\/iiif\/298147\/canvas\/main"}],"thumbnail":"https:\/\/data.getty.edu\/museum\/api\/iiif\/633385\/full\/231,300\/0\/default.jpg"}]}],"related":"https:\/\/www.getty.edu\/art\/collection\/objects\/298147\/jean-antoine-watteau-la-surprise-french-1718-1719\/"} -------------------------------------------------------------------------------- /tests/input_data/manifest-annos.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/2/context.json", 3 | "@id": "http://iiif.io/api/presentation/2.1/example/fixtures/1/manifest.json", 4 | "@type": "sc:Manifest", 5 | "label": "Manifest Label", 6 | "sequences": [ 7 | { 8 | "@type": "sc:Sequence", 9 | "label": "Current Order", 10 | "canvases": [ 11 | { 12 | "@id": "http://iiif.io/api/presentation/2.1/example/fixtures/canvas/1/c1.json", 13 | "@type": "sc:Canvas", 14 | "label": "Test 1 Canvas: 1", 15 | "height": 1800, 16 | "width": 1200, 17 | "images": [ 18 | { 19 | "@type": "oa:Annotation", 20 | "motivation": "sc:painting", 21 | "resource": { 22 | "@id": "http://iiif.io/api/presentation/2.1/example/fixtures/resources/page1-full.png", 23 | "@type": "dctypes:Image" 24 | }, 25 | "on": "http://iiif.io/api/presentation/2.1/example/fixtures/canvas/1/c1.json" 26 | }, 27 | { 28 | "@id": "http://example.org/iiif/book1/annotation/anno1", 29 | "@type": "oa:Annotation", 30 | "motivation": "sc:painting", 31 | "resource": { 32 | "@id": "http://www.example.org/iiif/book1-page1/50,50,1250,1850/full/0/default.jpg", 33 | "@type": "oa:SpecificResource", 34 | "full": { 35 | "@id": "http://example.org/iiif/book1-page1/full/full/0/default.jpg", 36 | "@type": "dctypes:Image" 37 | }, 38 | "selector": { 39 | "@context": "http://iiif.io/api/annex/openannotation/context.json", 40 | "@type": "iiif:ImageApiSelector", 41 | "region": "50,50,1250,1850" 42 | } 43 | }, 44 | "on": "http://www.example.org/iiif/book1/canvas/p1#xywh=0,0,600,900" 45 | }, 46 | { 47 | "@id": "http://example.org/iiif/book1/annotation/p1", 48 | "@type": "oa:Annotation", 49 | "motivation": "sc:painting", 50 | "resource":{ 51 | "@type": "cnt:ContentAsText", 52 | "chars": "Here starts book one...", 53 | "format": "text/plain", 54 | "language": "en" 55 | }, 56 | "on": "http://example.org/iiif/book1/canvas/p1#xywh=100,150,500,25" 57 | }, 58 | { 59 | "@id": "http://example.org/iiif/book1/annotation/anno1", 60 | "@type": "oa:Annotation", 61 | "motivation": "sc:painting", 62 | "resource":{ 63 | "@type": "oa:Choice", 64 | "default":{ 65 | "@id": "http://example.org/iiif/book1/res/page1.jpg", 66 | "@type": "dctypes:Image", 67 | "label": "Color" 68 | }, 69 | "item": [ 70 | { 71 | "@id": "http://example.org/iiif/book1/res/page1-blackandwhite.jpg", 72 | "@type": "dctypes:Image", 73 | "label": "Black and White" 74 | } 75 | ] 76 | }, 77 | "on": "http://example.org/iiif/book1/canvas/p1" 78 | }, 79 | { 80 | "@id": "http://example.org/iiif/book1/annotation/anno1", 81 | "@type": "oa:Annotation", 82 | "motivation": "sc:painting", 83 | "stylesheet":{ 84 | "@type": ["oa:CssStyle", "cnt:ContentAsText"], 85 | "chars": ".red {color: red;}" 86 | }, 87 | "resource":{ 88 | "@type": "oa:SpecificResource", 89 | "style": "red", 90 | "full": { 91 | "@type": "cnt:ContentAsText", 92 | "chars": "Rubrics are Red, ..." 93 | } 94 | }, 95 | "on": "http://example.org/iiif/book1/canvas/p1#xywh=100,150,500,30" 96 | } 97 | 98 | ] 99 | } 100 | ] 101 | } 102 | ] 103 | } -------------------------------------------------------------------------------- /tests/input_data/manifest-sequences.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/2/context.json", 3 | "@id": "http://iiif.io/api/presentation/2.1/example/fixtures/1/manifest.json", 4 | "@type": "sc:Manifest", 5 | "label": "Manifest Label", 6 | "description": "This is a description of the Manifest", 7 | 8 | "viewingDirection": "left-to-right", 9 | "viewingHint": "paged", 10 | "startCanvas": "http://iiif.io/api/presentation/2.1/example/fixtures/canvas/1/c1.json", 11 | 12 | "sequences": [ 13 | { 14 | "@type": "sc:Sequence", 15 | "label": "Current Order", 16 | "startCanvas": "http://iiif.io/api/presentation/2.1/example/fixtures/canvas/1/c1.json", 17 | "viewingHint": "paged", 18 | "viewingDirection": "right-to-left", 19 | "canvases": [ 20 | { 21 | "@id": "http://iiif.io/api/presentation/2.1/example/fixtures/canvas/1/c1.json", 22 | "@type": "sc:Canvas", 23 | "label": "Test 1 Canvas: 1", 24 | "height": 1800, 25 | "width": 1200, 26 | "images": [ 27 | { 28 | "@type": "oa:Annotation", 29 | "motivation": "sc:painting", 30 | "resource": { 31 | "@id": "http://iiif.io/api/presentation/2.1/example/fixtures/resources/page1-full.png", 32 | "@type": "dctypes:Image", 33 | "height": 1800, 34 | "width": 1200 35 | }, 36 | "on": "http://iiif.io/api/presentation/2.1/example/fixtures/canvas/1/c1.json" 37 | } 38 | ] 39 | }, 40 | { 41 | "@id": "http://iiif.io/api/presentation/2.1/example/fixtures/canvas/1/c2.json", 42 | "@type": "sc:Canvas", 43 | "label": "Test 1 Canvas: 2", 44 | "height": 1800, 45 | "width": 1200, 46 | "images": [ 47 | { 48 | "@type": "oa:Annotation", 49 | "motivation": "sc:painting", 50 | "resource": { 51 | "@id": "http://iiif.io/api/presentation/2.1/example/fixtures/resources/page1-full.png", 52 | "@type": "dctypes:Image", 53 | "height": 1800, 54 | "width": 1200 55 | }, 56 | "on": "http://iiif.io/api/presentation/2.1/example/fixtures/canvas/1/c2.json" 57 | } 58 | ] 59 | } 60 | ] 61 | }, 62 | { 63 | "@type": "sc:Sequence", 64 | "label": "Another Order", 65 | "description": "This should actually be external, but for testing we inline it", 66 | "startCanvas": "http://iiif.io/api/presentation/2.1/example/fixtures/canvas/1/c1.json", 67 | "viewingHint": "continuous", 68 | "viewingDirection": "left-to-right", 69 | "canvases": [ 70 | "http://iiif.io/api/presentation/2.1/example/fixtures/canvas/1/c1.json", 71 | "http://iiif.io/api/presentation/2.1/example/fixtures/canvas/1/c2.json" 72 | ] 73 | } 74 | ], 75 | 76 | "structures": [ 77 | { 78 | "@id": "http://iiif.io/api/presentation/2.1/example/fixtures/range/1", 79 | "@type": "sc:Range", 80 | "label": "Top Range", 81 | "viewingHint": "top", 82 | "members": [ 83 | { 84 | "@id": "http://iiif.io/api/presentation/2.1/example/fixtures/canvas/1/c1.json", 85 | "@type": "sc:Canvas" 86 | }, 87 | { 88 | "@id": "http://iiif.io/api/presentation/2.1/example/fixtures/range/1.1", 89 | "@type": "sc:Range" 90 | }, 91 | { 92 | "@id": "http://iiif.io/api/presentation/2.1/example/fixtures/range/1.2", 93 | "@type": "sc:Range" 94 | } 95 | ] 96 | }, 97 | { 98 | "@id": "http://iiif.io/api/presentation/2.1/example/fixtures/range/1.1", 99 | "@type": "sc:Range", 100 | "label": "Intermediary Range", 101 | "ranges": [ 102 | "http://iiif.io/api/presentation/2.1/example/fixtures/range/1.1.1" 103 | ] 104 | }, 105 | { 106 | "@id": "http://iiif.io/api/presentation/2.1/example/fixtures/range/1.1.1", 107 | "@type": "sc:Range", 108 | "label": "Small Range", 109 | "canvases": [ 110 | "http://iiif.io/api/presentation/2.1/example/fixtures/canvas/1/c2.json#xywh=0,0,10,10" 111 | ] 112 | }, 113 | { 114 | "@id": "http://iiif.io/api/presentation/2.1/example/fixtures/range/1.2", 115 | "@type": "sc:Range", 116 | "label": "End Range", 117 | "canvases": [ 118 | "http://iiif.io/api/presentation/2.1/example/fixtures/canvas/1/c2.json" 119 | ] 120 | } 121 | ] 122 | } -------------------------------------------------------------------------------- /tests/remote_cache/ghent.manifest.edu: -------------------------------------------------------------------------------- 1 | {"related":{"@id":"http://lib.ugent.be/viewer/archive.ugent.be%3A4B39C8CA-6FF9-11E1-8C42-C8A93B7C8C91","format":"text/html"},"license":"http://rightsstatements.org/vocab/UND/1.0/","@id":"http://adore.ugent.be/IIIF/manifests/archive.ugent.be%3A4B39C8CA-6FF9-11E1-8C42-C8A93B7C8C91","sequences":[{"rendering":[{"@id":"https://lib.ugent.be/catalog/rug01%3A001484515/requests/new?objectid=archive.ugent.be%3A4B39C8CA-6FF9-11E1-8C42-C8A93B7C8C91","format":"application/zip","label":"Download all images (562.38 MB)"}],"canvases":[{"attribution":"Provided by Ghent University Library","label":"1","@type":"sc:Canvas","thumbnail":{"@id":"http://adore.ugent.be/IIIF/images/archive.ugent.be%3A4B39C8CA-6FF9-11E1-8C42-C8A93B7C8C91%3ADS.1/0,0,6215,15076/226,/0/default.jpg","@type":"dctypes:Image"},"rendering":[{"label":"Download as jpeg2000 (78.77 MB)","format":"image/jp2","@id":"http://adore.ugent.be/OpenURL/resolve?rft_id=archive.ugent.be%3A4B39C8CA-6FF9-11E1-8C42-C8A93B7C8C91%3ADS.1&svc_id=original"}],"license":"http://rightsstatements.org/vocab/UND/1.0/","width":6215,"height":15076,"images":[{"on":"http://adore.ugent.be/IIIF/manifests/archive.ugent.be%3A4B39C8CA-6FF9-11E1-8C42-C8A93B7C8C91/canvases/DS.1","@id":"http://adore.ugent.be/IIIF/manifests/archive.ugent.be%3A4B39C8CA-6FF9-11E1-8C42-C8A93B7C8C91/canvases/DS.1/image-annotations/0","@type":"oa:Annotation","motivation":"sc:painting","resource":{"width":6215,"label":"BHSL-PAP-000044_2010_0001_AC.jp2","height":15076,"@type":"dctypes:Image","format":"image/jpeg","@id":"http://adore.ugent.be/IIIF/images/archive.ugent.be%3A4B39C8CA-6FF9-11E1-8C42-C8A93B7C8C91%3ADS.1/full/full/0/native.jpg","service":{"profile":"http://iiif.io/api/image/2/level1.json","@context":"http://iiif.io/api/image/2/context.json","@id":"http://adore.ugent.be/IIIF/images/archive.ugent.be%3A4B39C8CA-6FF9-11E1-8C42-C8A93B7C8C91%3ADS.1"}}}],"@id":"http://adore.ugent.be/IIIF/manifests/archive.ugent.be%3A4B39C8CA-6FF9-11E1-8C42-C8A93B7C8C91/canvases/DS.1"},{"@id":"http://adore.ugent.be/IIIF/manifests/archive.ugent.be%3A4B39C8CA-6FF9-11E1-8C42-C8A93B7C8C91/canvases/DS.3","images":[{"resource":{"service":{"profile":"http://iiif.io/api/image/2/level1.json","@context":"http://iiif.io/api/image/2/context.json","@id":"http://adore.ugent.be/IIIF/images/archive.ugent.be%3A4B39C8CA-6FF9-11E1-8C42-C8A93B7C8C91%3ADS.3"},"@id":"http://adore.ugent.be/IIIF/images/archive.ugent.be%3A4B39C8CA-6FF9-11E1-8C42-C8A93B7C8C91%3ADS.3/full/full/0/native.jpg","format":"image/jpeg","@type":"dctypes:Image","height":15036,"label":"BHSL-PAP-000044_2010_0002_AC.jp2","width":6235},"motivation":"sc:painting","@type":"oa:Annotation","on":"http://adore.ugent.be/IIIF/manifests/archive.ugent.be%3A4B39C8CA-6FF9-11E1-8C42-C8A93B7C8C91/canvases/DS.3","@id":"http://adore.ugent.be/IIIF/manifests/archive.ugent.be%3A4B39C8CA-6FF9-11E1-8C42-C8A93B7C8C91/canvases/DS.3/image-annotations/0"}],"width":6235,"height":15036,"license":"http://rightsstatements.org/vocab/UND/1.0/","thumbnail":{"@type":"dctypes:Image","@id":"http://adore.ugent.be/IIIF/images/archive.ugent.be%3A4B39C8CA-6FF9-11E1-8C42-C8A93B7C8C91%3ADS.3/0,0,6235,15036/226,/0/default.jpg"},"rendering":[{"label":"Download as jpeg2000 (78.29 MB)","@id":"http://adore.ugent.be/OpenURL/resolve?rft_id=archive.ugent.be%3A4B39C8CA-6FF9-11E1-8C42-C8A93B7C8C91%3ADS.3&svc_id=original","format":"image/jp2"}],"label":"2","@type":"sc:Canvas","attribution":"Provided by Ghent University Library"}],"@id":"http://adore.ugent.be/IIIF/manifests/archive.ugent.be%3A4B39C8CA-6FF9-11E1-8C42-C8A93B7C8C91/sequences/0","viewingDirection":"left-to-right","@type":"sc:Sequence"}],"metadata":[{"label":"Record","value":"https://lib.ugent.be/catalog/rug01%3A001484515/items/800000096237"},{"label":"Title","value":"[papyrus] Document uit het archief van de dichter Dioskoros van Aphrodite (?)."},{"value":"34 regels ; 29 x 72,5 cm.","label":"Description"},{"label":"Description","value":"Papyrus"},{"value":"537-538?","label":"Publisher"},{"value":"BHSL.PAP.000044 Herkomst: Aphroditô","label":"Provenance"},{"label":"Contents","value":"Het document bevat een overeenkomst tussen een corporatie en haar leiders. De leden van de corporatie van jagers (en waarschijnlijk ook van vissers) gaan de verbintenis aan t.o.v. Flavius Hermauos en Flavius Dios, hen als hun chefs te erkennen voor de duu"},{"value":"archive.ugent.be:4B39C8CA-6FF9-11E1-8C42-C8A93B7C8C91","label":"Object ID"}],"attribution":"Provided by Ghent University Library","thumbnail":{"@id":"https://adore.ugent.be/IIIF/images/archive.ugent.be%3A4B39C8CA-6FF9-11E1-8C42-C8A93B7C8C91%3ADS.1/0,0,6215,15076/226,/0/default.jpg","@type":"dctypes:Image"},"within":[],"@context":"http://iiif.io/api/presentation/2/context.json","label":"Document uit het archief van de dichter Dioskoros van Aphrodite (?)[manuscript]","description":"Het document bevat een overeenkomst tussen een corporatie en haar leiders. De leden van de corporatie van jagers (en waarschijnlijk ook van vissers) gaan de verbintenis aan t.o.v. Flavius Hermauos en Flavius Dios, hen als hun chefs te erkennen voor de duu","@type":"sc:Manifest","seeAlso":{"dcterms:format":"application/marcxml+xml","@id":"https://lib.ugent.be/catalog/rug01%3A001484515.marcxml"},"logo":"http://adore.ugent.be/IIIF/img/logo_i.svg"} -------------------------------------------------------------------------------- /tests/input_data/manifest-basic.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/2/context.json", 3 | "@id": "http://iiif.io/api/presentation/2.1/example/fixtures/1/manifest.json", 4 | "@type": "sc:Manifest", 5 | "label": "Manifest Label", 6 | "metadata": [{"label": "MD Label 1", "value": "MD Value 1"}, 7 | {"label": "MD Label 2", "value": ["MD Value 2.1", "MD Value 2.2"]}, 8 | {"label": "MD Label 3", "value": [ 9 | {"@language": "en", "@value": "MD Value 3.en"}, 10 | {"@language": "fr", "@value": "MD Value 3.fr"} 11 | ]} 12 | ], 13 | "description": "This is a description of the Manifest", 14 | "thumbnail": "http://iiif.io/img/logo-iiif-34x30.png", 15 | 16 | "viewingDirection": "left-to-right", 17 | "viewingHint": "paged", 18 | "navDate": "1900-01-01T00:00:00Z", 19 | 20 | "license": [ 21 | "http://iiif.io/event/conduct/", 22 | "https://creativecommons.org/licenses/by/4.0/" 23 | ], 24 | "logo": "http://iiif.io/img/logo-iiif-34x30.png", 25 | "attribution": "Provided by Testing Organization", 26 | 27 | "startCanvas": "http://iiif.io/api/presentation/2.1/example/fixtures/canvas/1/c1.json", 28 | 29 | "seeAlso": { 30 | "@id": "http://example.org/description/record.xml", 31 | "format": "text/xml" 32 | }, 33 | "rendering": [ 34 | "http://example.org/docs/record.doc", 35 | { 36 | "@id": "http://example.org/docs/record.pdf", 37 | "format": "application/pdf" 38 | } 39 | ], 40 | 41 | "related": { 42 | "@id": "http://example.org/somewhere/foo.html", 43 | "format": "text/html" 44 | }, 45 | 46 | "sequences": [ 47 | { 48 | "@type": "sc:Sequence", 49 | "label": "Current Order", 50 | "startCanvas": "http://iiif.io/api/presentation/2.1/example/fixtures/canvas/1/c1.json", 51 | "viewingHint": "paged", 52 | "viewingDirection": "right-to-left", 53 | "canvases": [ 54 | { 55 | "@id": "http://iiif.io/api/presentation/2.1/example/fixtures/canvas/1/c1.json", 56 | "@type": "sc:Canvas", 57 | "label": "Test 1 Canvas: 1", 58 | "height": 1800, 59 | "width": 1200, 60 | "images": [ 61 | { 62 | "@type": "oa:Annotation", 63 | "motivation": "sc:painting", 64 | "resource": { 65 | "@id": "http://iiif.io/api/presentation/2.1/example/fixtures/resources/page1-full.png", 66 | "@type": "dctypes:Image", 67 | "height": 1800, 68 | "width": 1200 69 | }, 70 | "on": "http://iiif.io/api/presentation/2.1/example/fixtures/canvas/1/c1.json" 71 | } 72 | ] 73 | }, 74 | { 75 | "@id": "http://iiif.io/api/presentation/2.1/example/fixtures/canvas/1/c2.json", 76 | "@type": "sc:Canvas", 77 | "label": "Test 1 Canvas: 2", 78 | "height": 1800, 79 | "width": 1200, 80 | "images": [ 81 | { 82 | "@type": "oa:Annotation", 83 | "motivation": "sc:painting", 84 | "resource": { 85 | "@id": "http://iiif.io/api/presentation/2.1/example/fixtures/resources/page1-full.png", 86 | "@type": "dctypes:Image", 87 | "height": 1800, 88 | "width": 1200 89 | }, 90 | "on": "http://iiif.io/api/presentation/2.1/example/fixtures/canvas/1/c2.json" 91 | } 92 | ] 93 | } 94 | 95 | 96 | ] 97 | } 98 | ], 99 | 100 | "structures": [ 101 | { 102 | "@id": "http://iiif.io/api/presentation/2.1/example/fixtures/range/1", 103 | "@type": "sc:Range", 104 | "label": "Top Range", 105 | "viewingHint": "top", 106 | "members": [ 107 | { 108 | "@id": "http://iiif.io/api/presentation/2.1/example/fixtures/canvas/1/c1.json", 109 | "@type": "sc:Canvas" 110 | }, 111 | { 112 | "@id": "http://iiif.io/api/presentation/2.1/example/fixtures/range/1.1", 113 | "@type": "sc:Range" 114 | }, 115 | { 116 | "@id": "http://iiif.io/api/presentation/2.1/example/fixtures/range/1.2", 117 | "@type": "sc:Range" 118 | } 119 | ] 120 | }, 121 | { 122 | "@id": "http://iiif.io/api/presentation/2.1/example/fixtures/range/1.1", 123 | "@type": "sc:Range", 124 | "label": "Intermediary Range", 125 | "ranges": [ 126 | "http://iiif.io/api/presentation/2.1/example/fixtures/range/1.1.1" 127 | ] 128 | }, 129 | { 130 | "@id": "http://iiif.io/api/presentation/2.1/example/fixtures/range/1.1.1", 131 | "@type": "sc:Range", 132 | "label": "Small Range", 133 | "canvases": [ 134 | "http://iiif.io/api/presentation/2.1/example/fixtures/canvas/1/c2.json#xywh=0,0,10,10" 135 | ] 136 | }, 137 | { 138 | "@id": "http://iiif.io/api/presentation/2.1/example/fixtures/range/1.2", 139 | "@type": "sc:Range", 140 | "label": "End Range", 141 | "canvases": [ 142 | "http://iiif.io/api/presentation/2.1/example/fixtures/canvas/1/c2.json" 143 | ] 144 | } 145 | ] 146 | } -------------------------------------------------------------------------------- /twoToThreeUpgraderService.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | """IIIF Presentation Validation Service""" 4 | 5 | import argparse 6 | import json 7 | import os 8 | import sys 9 | try: 10 | # python3 11 | from urllib.request import urlopen, HTTPError 12 | from urllib.parse import urlparse 13 | except ImportError: 14 | # fall back to python2 15 | from urllib2 import urlopen, HTTPError 16 | from urlparse import urlparse 17 | 18 | from bottle import Bottle, abort, request, response, run, template 19 | 20 | #egg_cache = "/path/to/web/egg_cache" 21 | #os.environ['PYTHON_EGG_CACHE'] = egg_cache 22 | 23 | from iiif_prezi_upgrader import Upgrader 24 | from iiif_prezi_upgrader.prezi_upgrader import FLAGS 25 | 26 | class Service(object): 27 | 28 | def __init__(self): 29 | self.default_flags = {} 30 | 31 | def fetch(self, url): 32 | try: 33 | wh = urlopen(url) 34 | except HTTPError as wh: 35 | pass 36 | data = wh.read() 37 | wh.close() 38 | try: 39 | data = data.decode('utf-8') 40 | except: 41 | pass 42 | return (data, wh) 43 | 44 | def return_json(self, js): 45 | response.content_type = "application/ld+json;profile=\"http://iiif.io/api/presentation/3/context.json\"" 46 | return json.dumps(js, indent=2) 47 | 48 | def do_upgrade(self, js, flags={}): 49 | up = Upgrader(flags=flags) 50 | results = up.process_resource(js, top=True) 51 | results = up.reorder(results) 52 | return self.return_json(results) 53 | 54 | def do_POST_upgrade(self): 55 | data = request.json 56 | if not data: 57 | b = request._get_body_string() 58 | try: 59 | b = b.decode('utf-8') 60 | except: 61 | pass 62 | data = json.loads(b) 63 | return self.do_upgrade(data) 64 | 65 | def do_GET_upgrade(self): 66 | url = request.query.get('url', '') 67 | url = url.strip() 68 | parsed_url = urlparse(url) 69 | if not parsed_url.scheme.startswith('http'): 70 | return self.return_json({'okay': 0, 'error': 'URLs must use HTTP or HTTPS', 'url': url}) 71 | try: 72 | (data, webhandle) = self.fetch(url) 73 | except: 74 | return self.return_json({'okay': 0, 'error': 'Cannot fetch url', 'url': url}) 75 | 76 | # catch if this is invalid JSON e.g. using a non IIIF resoruces like www.google.com 77 | try: 78 | data = json.loads(data) 79 | except Exception as error: 80 | return self.return_json({'okay': 0, 'error': 'Invalid JSON for supplied url.', 'url': url, 'json_error': str(error)}) 81 | 82 | # And look for flags 83 | fs = FLAGS 84 | flags = {} 85 | for f in fs: 86 | if request.query.get(f, None): 87 | val = request.query[f] 88 | if val == "True": 89 | val = True 90 | elif val == "False": 91 | val = False 92 | flags[f] = val 93 | 94 | try: 95 | response = self.do_upgrade(data, flags) 96 | except Exception as e: 97 | response = {'okay': 0, 'error': "Error: %s" % e } 98 | return response 99 | 100 | def index_route(self): 101 | output = template('templates/index.tpl', flags=FLAGS) 102 | return output 103 | 104 | def dispatch_views(self): 105 | self.app.route("/", "GET", self.index_route) 106 | self.app.route("/upgrade", "OPTIONS", self.empty_response) 107 | self.app.route("/upgrade", "GET", self.do_GET_upgrade) 108 | self.app.route("/upgrade", "POST", self.do_POST_upgrade) 109 | 110 | def after_request(self): 111 | methods = 'GET,POST,OPTIONS' 112 | headers = 'Origin, Accept, Content-Type, X-Requested-With, X-CSRF-Token' 113 | response.headers['Access-Control-Allow-Origin'] = '*' 114 | response.headers['Access-Control-Allow-Methods'] = methods 115 | response.headers['Access-Control-Allow-Headers'] = headers 116 | response.headers['Allow'] = methods 117 | 118 | def empty_response(self, *args, **kwargs): 119 | """Empty response""" 120 | 121 | def get_bottle_app(self): 122 | """Returns bottle instance""" 123 | self.app = Bottle() 124 | self.dispatch_views() 125 | self.app.hook('after_request')(self.after_request) 126 | return self.app 127 | 128 | def main(): 129 | parser = argparse.ArgumentParser(description=__doc__.strip(), 130 | formatter_class=argparse.ArgumentDefaultsHelpFormatter) 131 | 132 | parser.add_argument('--hostname', default='localhost', 133 | help='Hostname or IP address to bind to (use 0.0.0.0 for all)') 134 | parser.add_argument('--port', default=8080, type=int, 135 | help='Server port to bind to. Values below 1024 require root privileges.') 136 | 137 | args = parser.parse_args() 138 | 139 | s = Service() 140 | run(host=args.hostname, port=args.port, app=s.get_bottle_app()) 141 | 142 | if __name__ == "__main__": 143 | main() 144 | else: 145 | s = Service() 146 | application = s.get_bottle_app() 147 | -------------------------------------------------------------------------------- /tests/remote_cache/dublin.manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/2/context.json", 3 | "@id": "https://data.ucd.ie/api/img/manifests/ucdlib:33064", 4 | "rendering": { 5 | "@id": "https://digital.ucd.ie/get/ucdlib:33064/content", 6 | "format": "application/pdf", 7 | "label": "Download as PDF" 8 | }, 9 | "seeAlso": [ 10 | { 11 | "@id": "https://digital.ucd.ie/view/ucdlib:33064.xml", 12 | "format": "text/xml", 13 | "profile": "http://www.loc.gov/mods/v3", 14 | "label": "MODS metadata describing this object" 15 | }, 16 | { 17 | "@id": "https://digital.ucd.ie/view/ucdlib:33064.n3", 18 | "format": "text/rdf+n3", 19 | "label": "RDF n3 serialisation of metadata describing this object" 20 | }, 21 | { 22 | "@id": "https://data.ucd.ie/api/edm/v1/ucdlib:33064", 23 | "format": "application/x.europeana-edm+xml", 24 | "label": "EDM (Europeana Data Model) RDF metadata", 25 | "profile": "http://www.europeana.eu/schemas/edm/" 26 | }, 27 | { 28 | "@id": "https://digital.ucd.ie/view/ucdlib:33064.rdf", 29 | "format": "application/rdf+xml", 30 | "label": "RDF-XML metadata describing this object" 31 | } ], 32 | "within": { 33 | "@id": "https://data.ucd.ie/api/img/collection/ucdlib:33058", 34 | 35 | "@type": "sc:Collection", 36 | 37 | "label": "Dublin Town Planning Competition 1914" 38 | }, 39 | "attribution": "Irish Architectural Archive", 40 | "logo": "https://digital.ucd.ie/images/logos/ucd_logo_sm.png", 41 | "description": "Drawing submitted by F.A. Cushing Smith to the town plan for Dublin international competition organised by the Civics Institute of Ireland in 1914. Cushing Smith was the sole US entrant and also one of only two single-person entrants. His address at the time of the competition was the University Club, Urbana, Illinois. To ensure anonymity during the adjudication process his entry was give the designation 'B'. Aside from the winners, the adjudicators were unanimous in giving Honourable Mention to four entries including Cushing Smith's. This drawing includes plans and elevations for various types of housing and a block plan of suburban house arrangements.", 42 | "label": "Housing Plans for Greater Dublin", 43 | "@type": "sc:Manifest", 44 | "thumbnail": "https://digital.ucd.ie/get/ucdlib:33064/thumbnail", 45 | "metadata": [ 46 | { "label": "title","value": "Housing Plans for Greater Dublin" }, 47 | { 48 | "label": "Type of resource", "value": [ 49 | { "@value": "dctypes:StillImage" } 50 | ] 51 | }, 52 | { 53 | "label": "published", "value": [ 54 | {"@value": "Urbana, Ill."} 55 | ] 56 | }, 57 | { 58 | "label": "created", "value": "1914" 59 | }, 60 | { 61 | "label": "Exhibitions", 62 | "value": "A label included with the drawings indicates that Cushing Smith later exhibited the drawings in the Thirtieth Annual Chicago Architectural Exhibition, Art Institute of Chicago, 5-29 April, 1917." 63 | }, 64 | { 65 | "label": "Ownership/custodial history", 66 | "value": "The drawings were donated to the Wilmette Historical Museum, Wilmette, Illinois by Cushing Smith's granddaughter, Mary Duke Smith, and daughter-in-law, Joan Smith. With the Smiths permission, Wilmette Historical Museum donated the drawings to the Irish Architectural Archive in 2011." 67 | }, 68 | { "label" : "permalink", "value" : "doi:10.7925/drs1.ucdlib_33064" }, 69 | { 70 | "label" : "topic-LCSH", "value": 71 | [ 72 | { "@value": "City planning" }, 73 | { "@value": "Architecture, Domestic" } 74 | ] 75 | }, 76 | { 77 | "label": "genre", "value": "Competition drawings" 78 | }, 79 | { 80 | "label": "genre", "value": "Architectural drawings" 81 | } 82 | ], 83 | "sequences": [{ 84 | "@context":"http://iiif.io/api/presentation/2/context.json", 85 | "@id": "https://data.ucd.ie/api/img/manifests/ucdlib:33064/sequence/normal", 86 | "@type": "sc:Sequence", 87 | 88 | "label": "Current Page Order", 89 | "viewingHint": "individuals", 90 | "canvases": [ 91 | { 92 | "@id": "https://data.ucd.ie/api/img/ucdlib:33064/canvas/ucdlib:33543", 93 | "@type": "sc:Canvas", 94 | "label": "recto", 95 | "width": 14451, 96 | "height": 14214, 97 | "service": { "@context": "http://iiif.io/api/annex/services/physdim/1/context.json", "profile": "http://iiif.io/api/annex/services/physdim", 98 | "physicalScale": 0.00333333333333333, "physicalUnits": "in" }, 99 | "images": [{ 100 | "resource": { 101 | "service": { 102 | "@id": "https://iiif.ucd.ie/loris/ucdlib:33543", 103 | "@context": "http://iiif.io/api/image/2/context.json", 104 | "profile": "http://iiif.io/api/image/2/level2.json" 105 | }, 106 | "format": "image/jpeg", 107 | "height": 14214, 108 | "width": 14451, 109 | "@id": "https://iiif.ucd.ie/loris/ucdlib:33543/full/full/0/default.jpg", 110 | "@type": "dcTypes:Image" 111 | }, 112 | "on": "https://data.ucd.ie/api/img/ucdlib:33064/canvas/ucdlib:33543", 113 | "motivation": "sc:painting", 114 | "@id": "https://data.ucd.ie/api/img/ucdlib:33064/annotation/ucdlib:33543", 115 | "@type": "oa:Annotation" 116 | }] 117 | } 118 | ] 119 | }] 120 | } -------------------------------------------------------------------------------- /tests/remote_cache/harvard-art.manifest.json: -------------------------------------------------------------------------------- 1 | {"@context": "http://iiif.io/api/presentation/2/context.json", "@id": "https://iiif.harvardartmuseums.org/manifests/object/299843", "@type": "sc:Manifest", "attribution": "Harvard Art Museums", "description": "", "label": "Self-Portrait Dedicated to Paul Gauguin", "logo": "https://www.harvardartmuseums.org/assets/images/logo.png", "metadata": [{"label": "Date", "value": "1888"}, {"label": "Classification", "value": "Paintings"}, {"label": "Credit Line", "value": "Harvard Art Museums/Fogg Museum, Bequest from the Collection of Maurice Wertheim, Class of 1906"}, {"label": "Object Number", "value": "1951.65"}, {"label": "People", "value": ["Artist: Vincent van Gogh, Dutch, 1853 - 1890"]}, {"label": "Medium", "value": "Oil on canvas"}, {"label": "Dimensions", "value": "61.5 x 50.3 cm (24 3/16 x 19 13/16 in.)\r\nframed: 90.4 x 79.7 x 8.3 cm (35 9/16 x 31 3/8 x 3 1/4 in.)"}, {"label": "Provenance", "value": "Vincent van Gogh, Arles, (1888,) gift; to Paul Gauguin, (1888-1897) sold. [Ambroise Vollard, Paris.] [Paul Cassirer Gallery, Berlin.] Dr. Hugo von Tschudi, Berlin, (1906-1911), by descent; to his widow, Angela von Tschudi, Munich (1911-1919), to Neue Staatsgalerie, Munich (1919-1938); removed from the collection by the National Socialist (Nazi) authorities in 1938, consigned; to [Theodor Fischer Gallery, Lucerne, Switzerland, for sale June 30, 1939, lot 45]; to Maurice Wertheim (1939-1951) bequest; to Fogg Art Museum, 1951.\r\n\r\n \r\n\r\nNotes:\r\nGauguin sold the painting for Fr 300\r\nHugo von Tschudi bought the painting for the Nationalgalerie, Berlin, with funds from sponsors, but did not submit it to the Kaiser for pre-approval. He took the painting to Munich when he assumed a post there.\r\nAccording to Stephanie Barron, the van Gogh was removed from the Neue Staatsgalerie on March 27, 1938 and stored at Schloss Niedersch\u00f6nhausen in August of that year. (Barron, 1990,pp. 135-146)\r\n"}], "rendering": {"@id": "https://www.harvardartmuseums.org/collections/object/299843", "format": "text/html", "label": "Full record view"}, "sequences": [{"@id": "https://iiif.harvardartmuseums.org/manifests/object/299843/sequence/normal", "@type": "sc:Sequence", "canvases": [{"@id": "https://iiif.harvardartmuseums.org/manifests/object/299843/canvas/canvas-47174896", "@type": "sc:Canvas", "height": 2550, "images": [{"@id": "https://iiif.harvardartmuseums.org/manifests/object/299843/annotation/anno-47174896", "@type": "oa:Annotation", "motivation": "sc:painting", "on": "https://iiif.harvardartmuseums.org/manifests/object/299843/canvas/canvas-47174896", "resource": {"@id": "https://ids.lib.harvard.edu/ids/iiif/47174896/full/full/0/native.jpg", "@type": "dctypes:Image", "format": "image/jpeg", "height": 2550, "service": {"@context": "http://iiif.io/api/image/1/context.json", "@id": "https://ids.lib.harvard.edu/ids/iiif/47174896", "profile": "http://library.stanford.edu/iiif/image-api/1.1/conformance.html#level1"}, "width": 2087}}], "label": "1", "width": 2087}, {"@id": "https://iiif.harvardartmuseums.org/manifests/object/299843/canvas/canvas-18737483", "@type": "sc:Canvas", "height": 2550, "images": [{"@id": "https://iiif.harvardartmuseums.org/manifests/object/299843/annotation/anno-18737483", "@type": "oa:Annotation", "motivation": "sc:painting", "on": "https://iiif.harvardartmuseums.org/manifests/object/299843/canvas/canvas-18737483", "resource": {"@id": "https://ids.lib.harvard.edu/ids/iiif/18737483/full/full/0/native.jpg", "@type": "dctypes:Image", "format": "image/jpeg", "height": 2550, "service": {"@context": "http://iiif.io/api/image/1/context.json", "@id": "https://ids.lib.harvard.edu/ids/iiif/18737483", "profile": "http://library.stanford.edu/iiif/image-api/1.1/conformance.html#level1"}, "width": 2088}}], "label": "2", "width": 2088}, {"@id": "https://iiif.harvardartmuseums.org/manifests/object/299843/canvas/canvas-47174892", "@type": "sc:Canvas", "height": 2550, "images": [{"@id": "https://iiif.harvardartmuseums.org/manifests/object/299843/annotation/anno-47174892", "@type": "oa:Annotation", "motivation": "sc:painting", "on": "https://iiif.harvardartmuseums.org/manifests/object/299843/canvas/canvas-47174892", "resource": {"@id": "https://ids.lib.harvard.edu/ids/iiif/47174892/full/full/0/native.jpg", "@type": "dctypes:Image", "format": "image/jpeg", "height": 2550, "service": {"@context": "http://iiif.io/api/image/1/context.json", "@id": "https://ids.lib.harvard.edu/ids/iiif/47174892", "profile": "http://library.stanford.edu/iiif/image-api/1.1/conformance.html#level1"}, "width": 2259}}], "label": "3", "width": 2259}, {"@id": "https://iiif.harvardartmuseums.org/manifests/object/299843/canvas/canvas-43182083", "@type": "sc:Canvas", "height": 2550, "images": [{"@id": "https://iiif.harvardartmuseums.org/manifests/object/299843/annotation/anno-43182083", "@type": "oa:Annotation", "motivation": "sc:painting", "on": "https://iiif.harvardartmuseums.org/manifests/object/299843/canvas/canvas-43182083", "resource": {"@id": "https://ids.lib.harvard.edu/ids/iiif/43182083/full/full/0/native.jpg", "@type": "dctypes:Image", "format": "image/jpeg", "height": 2550, "service": {"@context": "http://iiif.io/api/image/1/context.json", "@id": "https://ids.lib.harvard.edu/ids/iiif/43182083", "profile": "http://library.stanford.edu/iiif/image-api/1.1/conformance.html#level1"}, "width": 2100}}], "label": "4", "width": 2100}, {"@id": "https://iiif.harvardartmuseums.org/manifests/object/299843/canvas/canvas-43183405", "@type": "sc:Canvas", "height": 2550, "images": [{"@id": "https://iiif.harvardartmuseums.org/manifests/object/299843/annotation/anno-43183405", "@type": "oa:Annotation", "motivation": "sc:painting", "on": "https://iiif.harvardartmuseums.org/manifests/object/299843/canvas/canvas-43183405", "resource": {"@id": "https://ids.lib.harvard.edu/ids/iiif/43183405/full/full/0/native.jpg", "@type": "dctypes:Image", "format": "image/jpeg", "height": 2550, "service": {"@context": "http://iiif.io/api/image/1/context.json", "@id": "https://ids.lib.harvard.edu/ids/iiif/43183405", "profile": "http://library.stanford.edu/iiif/image-api/1.1/conformance.html#level1"}, "width": 2082}}], "label": "5", "width": 2082}, {"@id": "https://iiif.harvardartmuseums.org/manifests/object/299843/canvas/canvas-43183422", "@type": "sc:Canvas", "height": 2550, "images": [{"@id": "https://iiif.harvardartmuseums.org/manifests/object/299843/annotation/anno-43183422", "@type": "oa:Annotation", "motivation": "sc:painting", "on": "https://iiif.harvardartmuseums.org/manifests/object/299843/canvas/canvas-43183422", "resource": {"@id": "https://ids.lib.harvard.edu/ids/iiif/43183422/full/full/0/native.jpg", "@type": "dctypes:Image", "format": "image/jpeg", "height": 2550, "service": {"@context": "http://iiif.io/api/image/1/context.json", "@id": "https://ids.lib.harvard.edu/ids/iiif/43183422", "profile": "http://library.stanford.edu/iiif/image-api/1.1/conformance.html#level1"}, "width": 2093}}], "label": "6", "width": 2093}], "viewingHint": "individuals"}], "within": "https://www.harvardartmuseums.org/collections"} -------------------------------------------------------------------------------- /tests/remote_cache/ycba.manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "@type" : "sc:Manifest", 3 | "@context" : "http://iiif.io/api/presentation/2/context.json", 4 | "@id" : "https://manifests.britishart.yale.edu/manifest/1474", 5 | "label" : "Peter Gaspar Scheemakers, 1691–1781, Flemish, active in Britain (from ca. 1720), Alexander Pope, ca. 1740, Marble, Yale Center for British Art, B1977.14.29, Paintings and Sculpture", 6 | "description" : "Overall: 27 x 18 x 9 inches (68.6 x 45.7 x 22.9 cm), Chiseled on front of socle: \"POPE.\"", 7 | "attribution" : "Yale Center for British Art, Paul Mellon Collection,
Public Domain
", 8 | "metadata" : [ { 9 | "label" : "Creator(s)", 10 | "value" : [ "Peter Gaspar Scheemakers, 1691–1781, Flemish, active in Britain (from ca. 1720)" ] 11 | }, { 12 | "label" : "Titles", 13 | "value" : [ "Alexander Pope" ] 14 | }, { 15 | "label" : "Date", 16 | "value" : [ "ca. 1740" ] 17 | }, { 18 | "label" : "Medium", 19 | "value" : [ "Marble" ] 20 | }, { 21 | "label" : "Dimensions", 22 | "value" : [ "Overall: 27 x 18 x 9 inches (68.6 x 45.7 x 22.9 cm)" ] 23 | }, { 24 | "label" : "Inscriptions", 25 | "value" : [ "Chiseled on front of socle: \"POPE.\"" ] 26 | }, { 27 | "label" : "Credit line", 28 | "value" : [ "Yale Center for British Art, Paul Mellon Collection" ] 29 | }, { 30 | "label" : "Institution", 31 | "value" : [ "Yale Center for British Art" ] 32 | }, { 33 | "label" : "Collection", 34 | "value" : [ "Paintings and Sculpture" ] 35 | }, { 36 | "label" : "Accession number", 37 | "value" : [ "B1977.14.29" ] 38 | }, { 39 | "label" : "Bibliography", 40 | "value" : [ "Prof. Maynard Mack, The World of Alexander Pope, Yale University Press, New Haven, no. 20, Z8704 M37", "Malcolm Baker, Literary Figures, Apollo, 179, June 2014, p. 76, N1 A54+ 179:2" ] 41 | } ], 42 | "logo" : "https://static.britishart.yale.edu/images/ycba_logo.jpg", 43 | "related" : [ { 44 | "@id" : "http://collections.britishart.yale.edu/vufind/Record/1666375", 45 | "label" : "catalog entry at the Yale Center for British Art", 46 | "format" : "text/html" 47 | } ], 48 | "seeAlso" : [ { 49 | "@id" : "https://manifests.britishart.yale.edu/xml/1474.xml", 50 | "format" : "text/xml", 51 | "profile" : "http://www.lido-schema.org/schema/v1.0/lido-v1.0.xsd" 52 | }, { 53 | "@id" : "http://collection.britishart.yale.edu/id/data/object/1474", 54 | "format" : "text/rdf+n3" 55 | } ], 56 | "sequences" : [ { 57 | "@id" : "https://manifests.britishart.yale.edu/sequence/1474", 58 | "@type" : "sc:Sequence", 59 | "label" : "default sequence", 60 | "viewingHint" : "individuals", 61 | "canvases" : [ { 62 | "images" : [ { 63 | "resource" : { 64 | "@type" : "dctypes:Image", 65 | "service" : { 66 | "profile" : "http://library.stanford.edu/iiif/image-api/1.1/conformance.html#level1", 67 | "@id" : "https://images.britishart.yale.edu/iiif/1778fd7f-0dd6-4229-b667-fb682255a8f1", 68 | "@context" : "http://iiif.io/api/image/1/context.json" 69 | }, 70 | "format" : "image/jpeg", 71 | "width" : 6127, 72 | "@id" : "https://images.britishart.yale.edu/iiif/1778fd7f-0dd6-4229-b667-fb682255a8f1/full/full/0/native.jpg", 73 | "label" : "front", 74 | "height" : 8173 75 | }, 76 | "@type" : "oa:Annotation", 77 | "motivation" : "sc:painting", 78 | "@id" : "https://manifests.britishart.yale.edu/annotation/ba-obj-1474-0001-pub", 79 | "on" : "https://manifests.britishart.yale.edu/canvas/ba-obj-1474-0001-pub" 80 | } ], 81 | "@type" : "sc:Canvas", 82 | "width" : 6127, 83 | "@id" : "https://manifests.britishart.yale.edu/canvas/ba-obj-1474-0001-pub", 84 | "label" : "front", 85 | "height" : 8173 86 | }, { 87 | "images" : [ { 88 | "resource" : { 89 | "@type" : "dctypes:Image", 90 | "service" : { 91 | "profile" : "http://library.stanford.edu/iiif/image-api/1.1/conformance.html#level1", 92 | "@id" : "https://images.britishart.yale.edu/iiif/915ba5d3-e38a-4d3c-a81a-ca858dc5cdb6", 93 | "@context" : "http://iiif.io/api/image/1/context.json" 94 | }, 95 | "format" : "image/jpeg", 96 | "width" : 6127, 97 | "@id" : "https://images.britishart.yale.edu/iiif/915ba5d3-e38a-4d3c-a81a-ca858dc5cdb6/full/full/0/native.jpg", 98 | "label" : "front", 99 | "height" : 8955 100 | }, 101 | "@type" : "oa:Annotation", 102 | "motivation" : "sc:painting", 103 | "@id" : "https://manifests.britishart.yale.edu/annotation/ba-obj-1474-0001-bar", 104 | "on" : "https://manifests.britishart.yale.edu/canvas/ba-obj-1474-0001-bar" 105 | } ], 106 | "@type" : "sc:Canvas", 107 | "width" : 6127, 108 | "@id" : "https://manifests.britishart.yale.edu/canvas/ba-obj-1474-0001-bar", 109 | "label" : "front", 110 | "height" : 8955 111 | }, { 112 | "images" : [ { 113 | "resource" : { 114 | "@type" : "dctypes:Image", 115 | "service" : { 116 | "profile" : "http://library.stanford.edu/iiif/image-api/1.1/conformance.html#level1", 117 | "@id" : "https://images.britishart.yale.edu/iiif/2057e87a-de16-4e59-ba15-3bcdaae7d8b0", 118 | "@context" : "http://iiif.io/api/image/1/context.json" 119 | }, 120 | "format" : "image/jpeg", 121 | "width" : 6127, 122 | "@id" : "https://images.britishart.yale.edu/iiif/2057e87a-de16-4e59-ba15-3bcdaae7d8b0/full/full/0/native.jpg", 123 | "label" : "proper left", 124 | "height" : 8173 125 | }, 126 | "@type" : "oa:Annotation", 127 | "motivation" : "sc:painting", 128 | "@id" : "https://manifests.britishart.yale.edu/annotation/ba-obj-1474-0002-pub", 129 | "on" : "https://manifests.britishart.yale.edu/canvas/ba-obj-1474-0002-pub" 130 | } ], 131 | "@type" : "sc:Canvas", 132 | "width" : 6127, 133 | "@id" : "https://manifests.britishart.yale.edu/canvas/ba-obj-1474-0002-pub", 134 | "label" : "proper left", 135 | "height" : 8173 136 | }, { 137 | "images" : [ { 138 | "resource" : { 139 | "@type" : "dctypes:Image", 140 | "service" : { 141 | "profile" : "http://library.stanford.edu/iiif/image-api/1.1/conformance.html#level1", 142 | "@id" : "https://images.britishart.yale.edu/iiif/4c630d19-d059-40ca-ba8c-0646bec2ab13", 143 | "@context" : "http://iiif.io/api/image/1/context.json" 144 | }, 145 | "format" : "image/jpeg", 146 | "width" : 6127, 147 | "@id" : "https://images.britishart.yale.edu/iiif/4c630d19-d059-40ca-ba8c-0646bec2ab13/full/full/0/native.jpg", 148 | "label" : "back", 149 | "height" : 8173 150 | }, 151 | "@type" : "oa:Annotation", 152 | "motivation" : "sc:painting", 153 | "@id" : "https://manifests.britishart.yale.edu/annotation/ba-obj-1474-0003-pub", 154 | "on" : "https://manifests.britishart.yale.edu/canvas/ba-obj-1474-0003-pub" 155 | } ], 156 | "@type" : "sc:Canvas", 157 | "width" : 6127, 158 | "@id" : "https://manifests.britishart.yale.edu/canvas/ba-obj-1474-0003-pub", 159 | "label" : "back", 160 | "height" : 8173 161 | }, { 162 | "images" : [ { 163 | "resource" : { 164 | "@type" : "dctypes:Image", 165 | "service" : { 166 | "profile" : "http://library.stanford.edu/iiif/image-api/1.1/conformance.html#level1", 167 | "@id" : "https://images.britishart.yale.edu/iiif/0d02621e-57b7-46c8-9674-2c1e419b8b91", 168 | "@context" : "http://iiif.io/api/image/1/context.json" 169 | }, 170 | "format" : "image/jpeg", 171 | "width" : 6127, 172 | "@id" : "https://images.britishart.yale.edu/iiif/0d02621e-57b7-46c8-9674-2c1e419b8b91/full/full/0/native.jpg", 173 | "label" : "proper right", 174 | "height" : 8173 175 | }, 176 | "@type" : "oa:Annotation", 177 | "motivation" : "sc:painting", 178 | "@id" : "https://manifests.britishart.yale.edu/annotation/ba-obj-1474-0004-pub", 179 | "on" : "https://manifests.britishart.yale.edu/canvas/ba-obj-1474-0004-pub" 180 | } ], 181 | "@type" : "sc:Canvas", 182 | "width" : 6127, 183 | "@id" : "https://manifests.britishart.yale.edu/canvas/ba-obj-1474-0004-pub", 184 | "label" : "proper right", 185 | "height" : 8173 186 | } ] 187 | } ] 188 | } -------------------------------------------------------------------------------- /templates/index.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Presentation API 2 to 3 converter — IIIF | International Image Interoperability Framework 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 60 | 61 | 62 | 63 | 66 | 67 |
68 |
69 | Home 70 | 79 |
80 |
81 | 82 |
83 |
84 |
85 | 86 |
87 |

Prezi 2 to 3 Converter

88 |
89 | 90 |
91 |
92 | This service will convert a version 2.0 or 2.1 Manifest to version 3.0. Fill in the URL of your manifest, and it will provide a converted 3.0 Manifest. 93 |
94 | 95 |
96 |
97 | 98 | URL of Manifest to convert:
99 |
100 | 101 |
102 | Options: 103 |
104 |
    105 | <% for key in flags: 106 | if isinstance(flags[key]['default'], bool): 107 | type='checkbox' 108 | if flags[key]['default']: 109 | checked = 'checked' 110 | else: 111 | checked = '' 112 | end 113 | value = '' 114 | else: 115 | type = 'input' 116 | value = 'value=%s' % flags[key]['default'] 117 | checked = '' 118 | end 119 | %> 120 |
  • 121 |
    122 | {{ flags[key]['prop'].replace('_',' ').title() }}   
    123 | {{ flags[key]['description'] }} 124 |
    125 |
  • 126 | % end 127 |
128 |
129 | 130 | 131 |
132 |
133 | 134 |
135 | 136 |
137 | Technical Note 138 |

139 | If you would like to use the converter programatically, there are two options: 140 |

141 |
    142 |
  • Download the code from github and run it locally.
  • 143 |
  • Use it online with JSON based output, by an HTTP GET to this endpoint:
    http://<url_to_be_determined>;url=manifest-url-here
  • 144 |
145 |
146 |
147 |
148 |
149 |
150 | 151 | 160 | 161 | 195 | 204 | 205 | 206 | 207 | -------------------------------------------------------------------------------- /tests/test_upgrader.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import json 3 | import os 4 | 5 | from iiif_prezi_upgrader import prezi_upgrader 6 | 7 | ### 8 | ### Basic Manifest Tests 9 | ### 10 | 11 | class TestManifest(unittest.TestCase): 12 | 13 | def setUp(self): 14 | flags= {"ext_ok": False, "deref_links": False} 15 | self.upgrader = prezi_upgrader.Upgrader(flags) 16 | self.results = self.upgrader.process_cached('tests/input_data/manifest-basic.json') 17 | 18 | def test_context(self): 19 | newctxt = ["http://www.w3.org/ns/anno.jsonld", 20 | "http://iiif.io/api/presentation/3/context.json"] 21 | self.assertTrue('@context' in self.results) 22 | self.assertEqual(self.results['@context'], newctxt) 23 | 24 | def test_items(self): 25 | self.assertTrue('items' in self.results) 26 | self.assertTrue('items' in self.results['items'][0]) 27 | self.assertTrue('items' in self.results['items'][0]['items'][0]) 28 | self.assertTrue('items' in self.results['structures'][0]) 29 | self.assertTrue('items' in self.results['structures'][0]['items'][1]) 30 | 31 | def test_id(self): 32 | self.assertTrue('id' in self.results) 33 | self.assertEqual(self.results['id'], \ 34 | "http://iiif.io/api/presentation/2.1/example/fixtures/1/manifest.json") 35 | self.assertTrue('id' in self.results['structures'][0]) 36 | self.assertTrue('id' in self.results['items'][0]) 37 | 38 | def test_type(self): 39 | # Also tests values of type 40 | self.assertTrue('type' in self.results) 41 | self.assertEqual(self.results['type'], "Manifest") 42 | self.assertTrue('type' in self.results['items'][0]) 43 | cvs = self.results['items'][0] 44 | self.assertEqual(cvs['type'], 'Canvas') 45 | self.assertEqual(cvs['items'][0]['type'], "AnnotationPage") 46 | self.assertEqual(cvs['items'][0]['items'][0]['type'], "Annotation") 47 | 48 | def test_startCanvas(self): 49 | cvs = "http://iiif.io/api/presentation/2.1/example/fixtures/canvas/1/c1.json" 50 | self.assertTrue('start' in self.results) 51 | self.assertEqual(self.results['start']['id'], cvs) 52 | self.assertEqual(self.results['start']['type'], 'Canvas') 53 | 54 | def test_license(self): 55 | lic = "http://iiif.io/event/conduct/" 56 | lic2 = "https://creativecommons.org/licenses/by/4.0/" 57 | self.assertTrue('rights' in self.results) 58 | self.assertEqual(self.results['rights'], lic2) 59 | self.assertTrue('metadata' in self.results) 60 | # Find lic as a value in @none 61 | found = False 62 | for pair in self.results['metadata']: 63 | if '@none' in pair['value'] and lic in pair['value']['@none']: 64 | found = True 65 | self.assertTrue(found) 66 | 67 | def test_viewingHint(self): 68 | self.assertTrue('behavior' in self.results) 69 | self.assertEqual(self.results['behavior'], ["paged"]) 70 | 71 | def test_arrays(self): 72 | self.assertEqual(type(self.results['behavior']), list) 73 | self.assertEqual(type(self.results['logo']), list) 74 | self.assertEqual(type(self.results['seeAlso']), list) 75 | 76 | def test_uri_string(self): 77 | self.assertEqual(type(self.results['rendering'][0]), dict) 78 | self.assertEqual(type(self.results['start']), dict) 79 | 80 | def test_languagemap(self): 81 | self.assertEqual(type(self.results['label']), dict) 82 | self.assertTrue('@none' in self.results['label']) 83 | self.assertEqual(self.results['label']['@none'], ["Manifest Label"]) 84 | self.assertTrue('metadata' in self.results) 85 | md = self.results['metadata'] 86 | self.assertEqual(type(md[0]['label']), dict) 87 | self.assertEqual(type(md[0]['label']['@none']), list) 88 | self.assertEqual(md[0]['label']['@none'][0], "MD Label 1") 89 | self.assertEqual(type(md[0]['value']), dict) 90 | self.assertEqual(type(md[0]['value']['@none']), list) 91 | self.assertEqual(md[0]['value']['@none'][0], "MD Value 1") 92 | 93 | # md[1] has two values 94 | self.assertEqual(len(md[1]['value']['@none']), 2) 95 | # md[2] has en and fr values 96 | self.assertTrue('en' in md[2]['value']) 97 | self.assertTrue('fr' in md[2]['value']) 98 | 99 | def test_description(self): 100 | if self.upgrader.description_is_metadata: 101 | # look in metadata 102 | found = 0 103 | for md in self.results['metadata']: 104 | if md['label']['@none'][0] == "Description": 105 | found = 1 106 | self.assertEqual(md['value']['@none'][0], 107 | "This is a description of the Manifest") 108 | # ensure it was generated 109 | self.assertEqual(found, 1) 110 | else: 111 | # look in summary 112 | self.assertTrue('summary' in self.results) 113 | self.assertEqual(type(self.results['summary']), dict) 114 | self.assertTrue('@none' in self.results['summary']) 115 | self.assertEqual(self.results['summary']['@none'][0], 116 | "This is a description of the Manifest") 117 | 118 | def test_ranges(self): 119 | ranges = self.results['structures'] 120 | self.assertEqual(len(ranges), 1) 121 | rng = ranges[0] 122 | # print(json.dumps(rng, indent=2, sort_keys=True)) 123 | self.assertTrue(not "behavior" in rng) 124 | self.assertEqual(rng['type'], "Range") 125 | self.assertTrue("items" in rng) 126 | self.assertEqual(len(rng['items']), 3) 127 | # [0] is a Canvas 128 | self.assertTrue("items" in rng['items'][1]) 129 | self.assertTrue("items" in rng['items'][1]['items'][0]) 130 | self.assertTrue("items" in rng['items'][2]) 131 | 132 | 133 | ### 134 | ### Annotation Tests 135 | ### 136 | 137 | class TestAnnotations(unittest.TestCase): 138 | 139 | def setUp(self): 140 | flags= {"ext_ok": False, "deref_links": False} 141 | self.upgrader = prezi_upgrader.Upgrader(flags) 142 | self.results = self.upgrader.process_cached('tests/input_data/manifest-annos.json') 143 | self.annotations = self.results['items'][0]['items'][0]['items'] 144 | 145 | def test_body(self): 146 | anno = self.annotations[0] 147 | self.assertTrue('body' in anno) 148 | self.assertEqual(anno['body']['id'], 149 | "http://iiif.io/api/presentation/2.1/example/fixtures/resources/page1-full.png") 150 | 151 | def test_target(self): 152 | anno = self.annotations[0] 153 | self.assertTrue('target' in anno) 154 | self.assertEqual(anno['target'], 155 | "http://iiif.io/api/presentation/2.1/example/fixtures/canvas/1/c1.json") 156 | 157 | def test_type(self): 158 | anno = self.annotations[0] 159 | self.assertTrue('type' in anno) 160 | self.assertEqual(anno['type'], "Annotation") 161 | 162 | def test_motivation(self): 163 | anno = self.annotations[0] 164 | self.assertTrue('motivation' in anno) 165 | self.assertEqual(anno['motivation'], "painting") 166 | 167 | def test_source(self): 168 | anno = self.annotations[1] 169 | self.assertEqual(anno['body']['type'], 'SpecificResource') 170 | self.assertTrue('source' in anno['body']) 171 | 172 | def test_ContentAsText(self): 173 | anno = self.annotations[2] 174 | self.assertEqual(anno['body']['type'], 'TextualBody') 175 | self.assertTrue('value' in anno['body']) 176 | 177 | def test_choice(self): 178 | anno = self.annotations[3] 179 | self.assertEqual(anno['body']['type'], 'Choice') 180 | self.assertTrue('items' in anno['body']) 181 | self.assertEqual(len(anno['body']['items']), 2) 182 | 183 | def test_style(self): 184 | anno = self.annotations[4] 185 | # print(json.dumps(anno, indent=2, sort_keys=True)) 186 | self.assertTrue('stylesheet' in anno) 187 | self.assertEqual(anno['stylesheet']['type'], "CssStylesheet") 188 | self.assertTrue("value" in anno['stylesheet']) 189 | self.assertEqual(anno['stylesheet']['value'], ".red {color: red;}") 190 | self.assertTrue("styleClass" in anno['body']) 191 | self.assertEqual(anno['body']['styleClass'], "red") 192 | 193 | 194 | ### 195 | ### Service Tests 196 | ### 197 | 198 | 199 | class TestServices(unittest.TestCase): 200 | 201 | def setUp(self): 202 | flags= {"ext_ok": False, "deref_links": False} 203 | self.upgrader = prezi_upgrader.Upgrader(flags) 204 | self.results = self.upgrader.process_cached('tests/input_data/manifest-services.json') 205 | 206 | def test_search(self): 207 | # Search and Autocomplete are on the Manifest 208 | manifest = self.results 209 | self.assertTrue('service' in manifest) 210 | self.assertEqual(type(manifest['service']), list) 211 | svc = manifest['service'][0] 212 | self.assertTrue(not '@context' in svc) 213 | self.assertEqual(svc['@id'], "http://example.org/services/identifier/search") 214 | self.assertEqual(svc['@type'], "SearchService1") 215 | self.assertTrue('service' in svc) 216 | self.assertEqual(svc['service'][0]['@type'], "AutoCompleteService1") 217 | 218 | def test_image(self): 219 | svc = self.results['items'][0]['items'][0]['items'][0]['body']['service'][0] 220 | self.assertTrue('@id' in svc) 221 | self.assertTrue('@type' in svc) 222 | self.assertEqual(svc['@type'], "ImageService2") 223 | self.assertTrue('profile' in svc) 224 | 225 | def test_auth(self): 226 | svc = self.results['items'][0]['items'][0]['items'][0]['body']['service'][0]['service'][0] 227 | self.assertTrue('@id' in svc) 228 | self.assertTrue('@type' in svc) 229 | self.assertEqual(svc['@type'], "AuthCookieService1") 230 | self.assertTrue('profile' in svc) 231 | self.assertTrue('service' in svc) 232 | token = svc['service'][0] 233 | self.assertTrue('@id' in token) 234 | self.assertTrue('@type' in token) 235 | self.assertEqual(token['@type'], "AuthTokenService1") 236 | logout = svc['service'][1] 237 | self.assertTrue('@id' in logout) 238 | self.assertTrue('@type' in logout) 239 | self.assertEqual(logout['@type'], "AuthLogoutService1") 240 | 241 | ### 242 | ### Collection Tests 243 | ### 244 | 245 | class TestCollection(unittest.TestCase): 246 | 247 | def setUp(self): 248 | flags= {"ext_ok": False, "deref_links": False} 249 | self.upgrader = prezi_upgrader.Upgrader(flags) 250 | self.results = self.upgrader.process_cached('tests/input_data/collection-basic.json') 251 | 252 | def test_items(self): 253 | self.assertTrue('items' in self.results) 254 | items = self.results['items'] 255 | # print(json.dumps(items, indent=2, sort_keys=True)) 256 | # Two Collections, then One Manifest 257 | self.assertEqual(len(items), 3) 258 | self.assertEqual(items[0]['type'], "Collection") 259 | self.assertEqual(items[2]['type'], "Manifest") 260 | self.assertTrue('items' in items[0]) 261 | # Three Members: Collection, Manifest, Collection 262 | items2 = items[0]['items'] 263 | self.assertEqual(len(items2), 3) 264 | self.assertEqual(items2[0]['type'], "Collection") 265 | self.assertEqual(items2[1]['type'], "Manifest") 266 | self.assertTrue('behavior' in items2[0]) 267 | self.assertTrue('multi-part' in items2[0]['behavior']) 268 | 269 | class TestRemote(unittest.TestCase): 270 | 271 | def test_remotes(self): 272 | uris = [ 273 | "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100054149545.0x000001/manifest.json", 274 | "https://d.lib.ncsu.edu/collections/catalog/nubian-message-1992-11-30/manifest", 275 | "https://sinai-images.library.ucla.edu/iiif/ark%3A%252F21198%252Fz1bc4wfw/manifest" 276 | ] 277 | 278 | uris = [] 279 | 280 | for u in uris: 281 | flags = {"deref_links": False} 282 | up = prezi_upgrader.Upgrader(flags) 283 | res = up.process_uri(u) 284 | 285 | class TestRealData(unittest.TestCase): 286 | 287 | def test_real_data(self): 288 | files = os.listdir('tests/remote_cache') 289 | flags= {"ext_ok": False, "deref_links": False} 290 | for f in files: 291 | fn = os.path.join('tests/remote_cache', f) 292 | upgrader = prezi_upgrader.Upgrader(flags) 293 | try: 294 | upgrader.process_cached(fn) 295 | except: 296 | print("Failed to process %s" % fn) 297 | raise 298 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /tests/remote_cache/ncsu.annolist.json: -------------------------------------------------------------------------------- 1 | {"@context":"http://iiif.io/api/presentation/2/context.json","@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0010/nubian-message-1992-11-30_0010-annotation-list-paragraph.json","@type":"sc:AnnotationList","@label":"OCR text granularity of paragraph","resources":[{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0010/nubian-message-1992-11-30_0010-annotation-list-paragraph/715,678,813,88","@type":"oa:Annotation","motivation":"sc:painting","resource":{"@type":"cnt:ContentAsText","format":"text/plain","chars":"November 30, 1992"},"on":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0010#xywh=715,678,813,88"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0010/nubian-message-1992-11-30_0010-annotation-list-paragraph/714,822,865,350","@type":"oa:Annotation","motivation":"sc:painting","resource":{"@type":"cnt:ContentAsText","format":"text/plain","chars":"Halloween... Aggie Style!"},"on":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0010#xywh=714,822,865,350"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0010/nubian-message-1992-11-30_0010-annotation-list-paragraph/707,1247,580,590","@type":"oa:Annotation","motivation":"sc:painting","resource":{"@type":"cnt:ContentAsText","format":"text/plain","chars":"Slammin’! North Carolina A \u0026 T’s homecomming was stacked with top talent from the hip—hop in- dustry; for all of you who missed it, it was all that!"},"on":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0010#xywh=707,1247,580,590"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0010/nubian-message-1992-11-30_0010-annotation-list-paragraph/699,1867,585,982","@type":"oa:Annotation","motivation":"sc:painting","resource":{"@type":"cnt:ContentAsText","format":"text/plain","chars":"Musical flavors for all tastes were present on the Aggies’ cam- pus, as four different groups gave live per- formances (not includ- ing R. Kelley,the pre- vious night.) It was Halloween and Pete Rock \u0026 C. L. Smooth stormed the stage wearing masks to open things up."},"on":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0010#xywh=699,1867,585,982"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0010/nubian-message-1992-11-30_0010-annotation-list-paragraph/868,2865,404,50","@type":"oa:Annotation","motivation":"sc:painting","resource":{"@type":"cnt:ContentAsText","format":"text/plain","chars":"After the soul"},"on":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0010#xywh=868,2865,404,50"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0010/nubian-message-1992-11-30_0010-annotation-list-paragraph/696,2941,577,122","@type":"oa:Annotation","motivation":"sc:painting","resource":{"@type":"cnt:ContentAsText","format":"text/plain","chars":"brothers frOm M t . V e r n o n"},"on":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0010#xywh=696,2941,577,122"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0010/nubian-message-1992-11-30_0010-annotation-list-paragraph/695,3089,577,61","@type":"oa:Annotation","motivation":"sc:painting","resource":{"@type":"cnt:ContentAsText","format":"text/plain","chars":"“Straighted It Out,”"},"on":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0010#xywh=695,3089,577,61"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0010/nubian-message-1992-11-30_0010-annotation-list-paragraph/697,3165,618,62","@type":"oa:Annotation","motivation":"sc:painting","resource":{"@type":"cnt:ContentAsText","format":"text/plain","chars":"sultry songstress Ce Ce ‘"},"on":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0010#xywh=697,3165,618,62"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0010/nubian-message-1992-11-30_0010-annotation-list-paragraph/678,3243,594,1914","@type":"oa:Annotation","motivation":"sc:painting","resource":{"@type":"cnt:ContentAsText","format":"text/plain","chars":"Peniston took the stage. She had the floor j umpin g with her smash hit, “Finally,” and swooned the crowd with slow fa- vorites such as “As We Lay” by Shirley Murdock. She defi- nitely came off. She was looking good too. Speaking of looks, the next group, TLC, would have had you thinking their name stood for “They Look Corny,” because they sure did. Of all the groups, they were the only gig that got no props. The songs were alright, but they lacked stage skills, and the ploys they used were juvenile. Oh, Well."},"on":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0010#xywh=678,3243,594,1914"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0010/nubian-message-1992-11-30_0010-annotation-list-paragraph/1344,1256,590,1450","@type":"oa:Annotation","motivation":"sc:painting","resource":{"@type":"cnt:ContentAsText","format":"text/plain","chars":"Any defeciencies TLC left were made up for, and then some, by the next group, Naughty By Nature. The bad boys from 1 18th Streetrocked the house. They per- formed old hits, as well as two slammin’ new hits off their new a1- bumdue outnext year. Old school lyricist Freddie Foxx was with Treach and his crew. He got busy overa beat box and dropped bombs. They both get much dap."},"on":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0010#xywh=1344,1256,590,1450"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0010/nubian-message-1992-11-30_0010-annotation-list-paragraph/1334,2722,585,600","@type":"oa:Annotation","motivation":"sc:painting","resource":{"@type":"cnt:ContentAsText","format":"text/plain","chars":"All in all, Home- coming was the joint. People came in peace and everybody en- joyed the show. State was deep, so if you missed it, check it out next year. Peace"},"on":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0010#xywh=1334,2722,585,600"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0010/nubian-message-1992-11-30_0010-annotation-list-paragraph/1337,3413,363,50","@type":"oa:Annotation","motivation":"sc:painting","resource":{"@type":"cnt:ContentAsText","format":"text/plain","chars":"Glenn French"},"on":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0010#xywh=1337,3413,363,50"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0010/nubian-message-1992-11-30_0010-annotation-list-paragraph/1338,3568,260,48","@type":"oa:Annotation","motivation":"sc:painting","resource":{"@type":"cnt:ContentAsText","format":"text/plain","chars":"Freshman"},"on":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0010#xywh=1338,3568,260,48"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0010/nubian-message-1992-11-30_0010-annotation-list-paragraph/1760,682,879,94","@type":"oa:Annotation","motivation":"sc:painting","resource":{"@type":"cnt:ContentAsText","format":"text/plain","chars":"The Nubian Message"},"on":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0010#xywh=1760,682,879,94"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0010/nubian-message-1992-11-30_0010-annotation-list-paragraph/2956,677,848,74","@type":"oa:Annotation","motivation":"sc:painting","resource":{"@type":"cnt:ContentAsText","format":"text/plain","chars":"Entertainment 10"},"on":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0010#xywh=2956,677,848,74"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0010/nubian-message-1992-11-30_0010-annotation-list-paragraph/2026,826,1828,277","@type":"oa:Annotation","motivation":"sc:painting","resource":{"@type":"cnt:ContentAsText","format":"text/plain","chars":"Sister Souljah... ...A Sister with a Cause."},"on":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0010#xywh=2026,826,1828,277"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0010/nubian-message-1992-11-30_0010-annotation-list-paragraph/1992,1339,589,1207","@type":"oa:Annotation","motivation":"sc:painting","resource":{"@type":"cnt:ContentAsText","format":"text/plain","chars":"Sister Souljah, rap artist, lecturer and activist, drops bombs every time she opens her mouth. She puts the fury and frustra- tion of our people into words with a style and eloquence few speak- ers can achieve. When addressingourpeople, she refers to us as “Af- ricans” for our lost brothers and sisters who have forgotten their roots."},"on":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0010#xywh=1992,1339,589,1207"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0010/nubian-message-1992-11-30_0010-annotation-list-paragraph/1981,2574,590,1756","@type":"oa:Annotation","motivation":"sc:painting","resource":{"@type":"cnt:ContentAsText","format":"text/plain","chars":"Born Lisa Williamson in Englewood, New Jer- sey, Souljah grew up on welfare in a public housing project. De- termined not to letpov- erty hold her back, she won many scholar- ships while in high school and attended Rutgers University. While she was there, she was also involved in many community activities. Her wealth of knowledge caught the attention of rapper Carlton Ridenhour, a.k.a. Chuck D. This incident resulted in the birth of Sister Souljah’s rap career."},"on":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0010#xywh=1981,2574,590,1756"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0010/nubian-message-1992-11-30_0010-annotation-list-paragraph/1975,4346,588,749","@type":"oa:Annotation","motivation":"sc:painting","resource":{"@type":"cnt:ContentAsText","format":"text/plain","chars":"Souljah has lec- tured all over the world. The Soviet Union, Spain and Zim- babwe are just a few of the countries she has toured. Since 1986, she has founded two youth organizations, coordinated numerous"},"on":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0010#xywh=1975,4346,588,749"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0010/nubian-message-1992-11-30_0010-annotation-list-paragraph/2640,1265,586,676","@type":"oa:Annotation","motivation":"sc:painting","resource":{"@type":"cnt:ContentAsText","format":"text/plain","chars":"youth conferences and established a free six- week summer camp for homeless children. Souljah has also ap- peared on numberous talk shows such as Oprah, Donahue and Geraldo; her"},"on":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0010#xywh=2640,1265,586,676"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0010/nubian-message-1992-11-30_0010-annotation-list-paragraph/2641,1972,597,127","@type":"oa:Annotation","motivation":"sc:painting","resource":{"@type":"cnt:ContentAsText","format":"text/plain","chars":"arguements were solid! She states, “my"},"on":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0010#xywh=2641,1972,597,127"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0010/nubian-message-1992-11-30_0010-annotation-list-paragraph/2660,2108,574,54","@type":"oa:Annotation","motivation":"sc:painting","resource":{"@type":"cnt:ContentAsText","format":"text/plain","chars":"studies make me se-"},"on":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0010#xywh=2660,2108,574,54"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0010/nubian-message-1992-11-30_0010-annotation-list-paragraph/2616,3193,614,1917","@type":"oa:Annotation","motivation":"sc:painting","resource":{"@type":"cnt:ContentAsText","format":"text/plain","chars":"cure intellectually, so that nobody can get me on Nightline and have me start wobbling.” Sister Souljah speaks for her people, in her lectures as well as on her records. Fa- mous for saying “We are at war,” in her de- but on Terminator X ’5 single, Buckwilin’. Since then it has be- come her slogan. President elect, Bill Clinton did not know “who he was messin’ with” when he took her comments on the riot out of context and called her racist. Millions of people got the chance to hear Souljah take on the press and in the end,"},"on":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0010#xywh=2616,3193,614,1917"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0010/nubian-message-1992-11-30_0010-annotation-list-paragraph/3290,1268,581,362","@type":"oa:Annotation","motivation":"sc:painting","resource":{"@type":"cnt:ContentAsText","format":"text/plain","chars":"they all got served. She further ex- plains why she cannot be called a racist in her work. Take this ex-"},"on":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0010#xywh=3290,1268,581,362"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0010/nubian-message-1992-11-30_0010-annotation-list-paragraph/3288,1656,577,139","@type":"oa:Annotation","motivation":"sc:painting","resource":{"@type":"cnt:ContentAsText","format":"text/plain","chars":"cerpt from “360 De- grees of Power:”"},"on":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0010#xywh=3288,1656,577,139"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0010/nubian-message-1992-11-30_0010-annotation-list-paragraph/3450,2074,431,60","@type":"oa:Annotation","motivation":"sc:painting","resource":{"@type":"cnt:ContentAsText","format":"text/plain","chars":"Black racist,"},"on":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0010#xywh=3450,2074,431,60"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0010/nubian-message-1992-11-30_0010-annotation-list-paragraph/3300,2149,510,121","@type":"oa:Annotation","motivation":"sc:painting","resource":{"@type":"cnt:ContentAsText","format":"text/plain","chars":"Black racist, Black racists!"},"on":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0010#xywh=3300,2149,510,121"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0010/nubian-message-1992-11-30_0010-annotation-list-paragraph/3289,2298,595,526","@type":"oa:Annotation","motivation":"sc:painting","resource":{"@type":"cnt:ContentAsText","format":"text/plain","chars":"No Black person or group ofBlack people any place in the world had the power to deny white people or Euro- peans access to any- thing."},"on":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0010#xywh=3289,2298,595,526"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0010/nubian-message-1992-11-30_0010-annotation-list-paragraph/3280,2839,596,446","@type":"oa:Annotation","motivation":"sc:painting","resource":{"@type":"cnt:ContentAsText","format":"text/plain","chars":"What can you call me? Call me preju- dice, because I pre- judge situations based on my understanding of history."},"on":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0010#xywh=3280,2839,596,446"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0010/nubian-message-1992-11-30_0010-annotation-list-paragraph/3273,3300,594,992","@type":"oa:Annotation","motivation":"sc:painting","resource":{"@type":"cnt:ContentAsText","format":"text/plain","chars":"Some of the Indians trusted white people. If you ask any brother or sitster today how many Native Ameri- cans do they know, they’ll tell you that they don't know any. Whit e people killed them and they did so in the name of friend- ship, civilization and Christianty!"},"on":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0010#xywh=3273,3300,594,992"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0010/nubian-message-1992-11-30_0010-annotation-list-paragraph/3271,4461,443,211","@type":"oa:Annotation","motivation":"sc:painting","resource":{"@type":"cnt:ContentAsText","format":"text/plain","chars":"-- Jeff Hudgens and . Glenn French"},"on":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0010#xywh=3271,4461,443,211"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0010/nubian-message-1992-11-30_0010-annotation-list-paragraph/4564,191,74,1175","@type":"oa:Annotation","motivation":"sc:painting","resource":{"@type":"cnt:ContentAsText","format":"text/plain","chars":"a. _...L,,_._r,fl_,., 7 “ _.—,...._‘.—...rq,‘ ,r m\" ‘ 1\"”. m mwnz-yrfp‘j‘fflw, mr mm W. . Gym“ ‘ am “new r—rr"},"on":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0010#xywh=4564,191,74,1175"}]} 2 | -------------------------------------------------------------------------------- /tests/remote_cache/ncsu.manifest.json: -------------------------------------------------------------------------------- 1 | {"@context":"http://iiif.io/api/presentation/2/context.json","@id":"https://d.lib.ncsu.edu/collections/catalog/nubian-message-1992-11-30/manifest","@type":"sc:Manifest","label":"Nubian Message, November 30, 1992","related":{"@id":"https://d.lib.ncsu.edu/collections/catalog/nubian-message-1992-11-30","format":"text/html","label":"HTML page for the resource","dcterms:modified":"2017-10-12T13:23:07.145Z"},"logo":{"@id":"https://d.lib.ncsu.edu/collections/assets/ncsu-libraries-white-logo-placement-8e3a4e918262aa5993b0e0475989b02f.jpg"},"attribution":"The Nubian Message (LH1 .H6 N83), Special Collections Research Center at NCSU Libraries","license":["https://d.lib.ncsu.edu/collections/about#rights_and_use"],"metadata":[{"label":"title","value":"Nubian Message, November 30, 1992"},{"label":"Creator","value":"\u003cspan\u003e\u003ca href=\"https://d.lib.ncsu.edu/collections/catalog?f%5Bnames_facet%5D%5B%5D=Nubian+Message+%28Raleigh%2C+N.C.%29\"\u003eNubian Message (Raleigh, N.C.)\u003c/a\u003e (Publisher)\u003c/span\u003e"},{"label":"Created Date","value":"1992-11-30"},{"label":"URL","value":"\u003cspan\u003e\u003ca href=\"https://d.lib.ncsu.edu/collections/catalog/nubian-message-1992-11-30\"\u003ehttps://d.lib.ncsu.edu/collections/catalog/nubian-message-1992-11-30\u003c/a\u003e\u003c/span\u003e"},{"label":"","value":"\u003cspan\u003e\u003ca href=\"https://d.lib.ncsu.edu/collections/catalog/nubian-message-1992-11-30?manifest=https%3A%2F%2Fd.lib.ncsu.edu%2Fcollections%2Fcatalog%2Fnubian-message-1992-11-30%2Fmanifest.json\" title=\"IIIF drag \u0026amp; drop\"\u003eIIIF drag \u0026amp; drop\u003c/a\u003e (\u003ca href=\"https://d.lib.ncsu.edu/collections/about-iiif\"\u003eAbout IIIF\u003c/a\u003e)\u003c/span\u003e"}],"thumbnail":{"@id":"https://iiif.lib.ncsu.edu/iiif/nubian-message-1992-11-30/full/150,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.lib.ncsu.edu/iiif/nubian-message-1992-11-30","profile":"http://iiif.io/api/image/2/level2.json"}},"sequences":[{"canvases":[{"@type":"sc:Canvas","@id":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0001","width":4703,"height":5639,"label":"1","seeAlso":[{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0001/nubian-message-1992-11-30_0001.hocr","format":"text/vnd.hocr+html","profile":"https://github.com/kba/hocr-spec/blob/master/hocr-spec.md","label":"hOCR"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0001/nubian-message-1992-11-30_0001.txt","format":"text/plain","label":"plain text OCR"}],"images":[{"@id":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0001/image","@type":"oa:Annotation","on":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0001","motivation":"sc:painting","resource":{"@id":"https://iiif.lib.ncsu.edu/iiif/nubian-message-1992-11-30_0001/full/1170,/0/default.jpg","width":1170,"height":1403,"service":{"profile":"http://iiif.io/api/image/2/level2.json","@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.lib.ncsu.edu/iiif/nubian-message-1992-11-30_0001"},"@type":"dctypes:Image"}}],"otherContent":[{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0001/nubian-message-1992-11-30_0001-annotation-list-word.json","@type":"sc:AnnotationList","label":"Text of this page (word level)"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0001/nubian-message-1992-11-30_0001-annotation-list-line.json","@type":"sc:AnnotationList","label":"Text of this page (line level)"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0001/nubian-message-1992-11-30_0001-annotation-list-paragraph.json","@type":"sc:AnnotationList","label":"Text of this page (paragraph level)"}]},{"@type":"sc:Canvas","@id":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0002","width":4703,"height":5630,"label":"2","seeAlso":[{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0002/nubian-message-1992-11-30_0002.hocr","format":"text/vnd.hocr+html","profile":"https://github.com/kba/hocr-spec/blob/master/hocr-spec.md","label":"hOCR"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0002/nubian-message-1992-11-30_0002.txt","format":"text/plain","label":"plain text OCR"}],"images":[{"@id":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0002/image","@type":"oa:Annotation","on":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0002","motivation":"sc:painting","resource":{"@id":"https://iiif.lib.ncsu.edu/iiif/nubian-message-1992-11-30_0002/full/1170,/0/default.jpg","width":1170,"height":1401,"service":{"profile":"http://iiif.io/api/image/2/level2.json","@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.lib.ncsu.edu/iiif/nubian-message-1992-11-30_0002"},"@type":"dctypes:Image"}}],"otherContent":[{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0002/nubian-message-1992-11-30_0002-annotation-list-word.json","@type":"sc:AnnotationList","label":"Text of this page (word level)"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0002/nubian-message-1992-11-30_0002-annotation-list-line.json","@type":"sc:AnnotationList","label":"Text of this page (line level)"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0002/nubian-message-1992-11-30_0002-annotation-list-paragraph.json","@type":"sc:AnnotationList","label":"Text of this page (paragraph level)"}]},{"@type":"sc:Canvas","@id":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0003","width":4722,"height":5646,"label":"3","seeAlso":[{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0003/nubian-message-1992-11-30_0003.hocr","format":"text/vnd.hocr+html","profile":"https://github.com/kba/hocr-spec/blob/master/hocr-spec.md","label":"hOCR"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0003/nubian-message-1992-11-30_0003.txt","format":"text/plain","label":"plain text OCR"}],"images":[{"@id":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0003/image","@type":"oa:Annotation","on":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0003","motivation":"sc:painting","resource":{"@id":"https://iiif.lib.ncsu.edu/iiif/nubian-message-1992-11-30_0003/full/1170,/0/default.jpg","width":1170,"height":1399,"service":{"profile":"http://iiif.io/api/image/2/level2.json","@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.lib.ncsu.edu/iiif/nubian-message-1992-11-30_0003"},"@type":"dctypes:Image"}}],"otherContent":[{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0003/nubian-message-1992-11-30_0003-annotation-list-word.json","@type":"sc:AnnotationList","label":"Text of this page (word level)"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0003/nubian-message-1992-11-30_0003-annotation-list-line.json","@type":"sc:AnnotationList","label":"Text of this page (line level)"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0003/nubian-message-1992-11-30_0003-annotation-list-paragraph.json","@type":"sc:AnnotationList","label":"Text of this page (paragraph level)"}]},{"@type":"sc:Canvas","@id":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0004","width":4697,"height":5622,"label":"4","seeAlso":[{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0004/nubian-message-1992-11-30_0004.hocr","format":"text/vnd.hocr+html","profile":"https://github.com/kba/hocr-spec/blob/master/hocr-spec.md","label":"hOCR"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0004/nubian-message-1992-11-30_0004.txt","format":"text/plain","label":"plain text OCR"}],"images":[{"@id":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0004/image","@type":"oa:Annotation","on":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0004","motivation":"sc:painting","resource":{"@id":"https://iiif.lib.ncsu.edu/iiif/nubian-message-1992-11-30_0004/full/1170,/0/default.jpg","width":1170,"height":1400,"service":{"profile":"http://iiif.io/api/image/2/level2.json","@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.lib.ncsu.edu/iiif/nubian-message-1992-11-30_0004"},"@type":"dctypes:Image"}}],"otherContent":[{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0004/nubian-message-1992-11-30_0004-annotation-list-word.json","@type":"sc:AnnotationList","label":"Text of this page (word level)"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0004/nubian-message-1992-11-30_0004-annotation-list-line.json","@type":"sc:AnnotationList","label":"Text of this page (line level)"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0004/nubian-message-1992-11-30_0004-annotation-list-paragraph.json","@type":"sc:AnnotationList","label":"Text of this page (paragraph level)"}]},{"@type":"sc:Canvas","@id":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0005","width":4712,"height":5644,"label":"5","seeAlso":[{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0005/nubian-message-1992-11-30_0005.hocr","format":"text/vnd.hocr+html","profile":"https://github.com/kba/hocr-spec/blob/master/hocr-spec.md","label":"hOCR"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0005/nubian-message-1992-11-30_0005.txt","format":"text/plain","label":"plain text OCR"}],"images":[{"@id":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0005/image","@type":"oa:Annotation","on":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0005","motivation":"sc:painting","resource":{"@id":"https://iiif.lib.ncsu.edu/iiif/nubian-message-1992-11-30_0005/full/1170,/0/default.jpg","width":1170,"height":1401,"service":{"profile":"http://iiif.io/api/image/2/level2.json","@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.lib.ncsu.edu/iiif/nubian-message-1992-11-30_0005"},"@type":"dctypes:Image"}}],"otherContent":[{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0005/nubian-message-1992-11-30_0005-annotation-list-word.json","@type":"sc:AnnotationList","label":"Text of this page (word level)"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0005/nubian-message-1992-11-30_0005-annotation-list-line.json","@type":"sc:AnnotationList","label":"Text of this page (line level)"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0005/nubian-message-1992-11-30_0005-annotation-list-paragraph.json","@type":"sc:AnnotationList","label":"Text of this page (paragraph level)"}]},{"@type":"sc:Canvas","@id":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0006","width":4692,"height":5619,"label":"6","seeAlso":[{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0006/nubian-message-1992-11-30_0006.hocr","format":"text/vnd.hocr+html","profile":"https://github.com/kba/hocr-spec/blob/master/hocr-spec.md","label":"hOCR"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0006/nubian-message-1992-11-30_0006.txt","format":"text/plain","label":"plain text OCR"}],"images":[{"@id":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0006/image","@type":"oa:Annotation","on":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0006","motivation":"sc:painting","resource":{"@id":"https://iiif.lib.ncsu.edu/iiif/nubian-message-1992-11-30_0006/full/1170,/0/default.jpg","width":1170,"height":1401,"service":{"profile":"http://iiif.io/api/image/2/level2.json","@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.lib.ncsu.edu/iiif/nubian-message-1992-11-30_0006"},"@type":"dctypes:Image"}}],"otherContent":[{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0006/nubian-message-1992-11-30_0006-annotation-list-word.json","@type":"sc:AnnotationList","label":"Text of this page (word level)"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0006/nubian-message-1992-11-30_0006-annotation-list-line.json","@type":"sc:AnnotationList","label":"Text of this page (line level)"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0006/nubian-message-1992-11-30_0006-annotation-list-paragraph.json","@type":"sc:AnnotationList","label":"Text of this page (paragraph level)"}]},{"@type":"sc:Canvas","@id":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0007","width":4716,"height":5632,"label":"7","seeAlso":[{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0007/nubian-message-1992-11-30_0007.hocr","format":"text/vnd.hocr+html","profile":"https://github.com/kba/hocr-spec/blob/master/hocr-spec.md","label":"hOCR"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0007/nubian-message-1992-11-30_0007.txt","format":"text/plain","label":"plain text OCR"}],"images":[{"@id":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0007/image","@type":"oa:Annotation","on":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0007","motivation":"sc:painting","resource":{"@id":"https://iiif.lib.ncsu.edu/iiif/nubian-message-1992-11-30_0007/full/1170,/0/default.jpg","width":1170,"height":1397,"service":{"profile":"http://iiif.io/api/image/2/level2.json","@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.lib.ncsu.edu/iiif/nubian-message-1992-11-30_0007"},"@type":"dctypes:Image"}}],"otherContent":[{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0007/nubian-message-1992-11-30_0007-annotation-list-word.json","@type":"sc:AnnotationList","label":"Text of this page (word level)"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0007/nubian-message-1992-11-30_0007-annotation-list-line.json","@type":"sc:AnnotationList","label":"Text of this page (line level)"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0007/nubian-message-1992-11-30_0007-annotation-list-paragraph.json","@type":"sc:AnnotationList","label":"Text of this page (paragraph level)"}]},{"@type":"sc:Canvas","@id":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0008","width":4678,"height":5615,"label":"8","seeAlso":[{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0008/nubian-message-1992-11-30_0008.hocr","format":"text/vnd.hocr+html","profile":"https://github.com/kba/hocr-spec/blob/master/hocr-spec.md","label":"hOCR"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0008/nubian-message-1992-11-30_0008.txt","format":"text/plain","label":"plain text OCR"}],"images":[{"@id":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0008/image","@type":"oa:Annotation","on":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0008","motivation":"sc:painting","resource":{"@id":"https://iiif.lib.ncsu.edu/iiif/nubian-message-1992-11-30_0008/full/1170,/0/default.jpg","width":1170,"height":1404,"service":{"profile":"http://iiif.io/api/image/2/level2.json","@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.lib.ncsu.edu/iiif/nubian-message-1992-11-30_0008"},"@type":"dctypes:Image"}}],"otherContent":[{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0008/nubian-message-1992-11-30_0008-annotation-list-word.json","@type":"sc:AnnotationList","label":"Text of this page (word level)"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0008/nubian-message-1992-11-30_0008-annotation-list-line.json","@type":"sc:AnnotationList","label":"Text of this page (line level)"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0008/nubian-message-1992-11-30_0008-annotation-list-paragraph.json","@type":"sc:AnnotationList","label":"Text of this page (paragraph level)"}]},{"@type":"sc:Canvas","@id":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0009","width":4726,"height":5622,"label":"9","seeAlso":[{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0009/nubian-message-1992-11-30_0009.hocr","format":"text/vnd.hocr+html","profile":"https://github.com/kba/hocr-spec/blob/master/hocr-spec.md","label":"hOCR"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0009/nubian-message-1992-11-30_0009.txt","format":"text/plain","label":"plain text OCR"}],"images":[{"@id":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0009/image","@type":"oa:Annotation","on":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0009","motivation":"sc:painting","resource":{"@id":"https://iiif.lib.ncsu.edu/iiif/nubian-message-1992-11-30_0009/full/1170,/0/default.jpg","width":1170,"height":1392,"service":{"profile":"http://iiif.io/api/image/2/level2.json","@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.lib.ncsu.edu/iiif/nubian-message-1992-11-30_0009"},"@type":"dctypes:Image"}}],"otherContent":[{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0009/nubian-message-1992-11-30_0009-annotation-list-word.json","@type":"sc:AnnotationList","label":"Text of this page (word level)"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0009/nubian-message-1992-11-30_0009-annotation-list-line.json","@type":"sc:AnnotationList","label":"Text of this page (line level)"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0009/nubian-message-1992-11-30_0009-annotation-list-paragraph.json","@type":"sc:AnnotationList","label":"Text of this page (paragraph level)"}]},{"@type":"sc:Canvas","@id":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0010","width":4698,"height":5614,"label":"10","seeAlso":[{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0010/nubian-message-1992-11-30_0010.hocr","format":"text/vnd.hocr+html","profile":"https://github.com/kba/hocr-spec/blob/master/hocr-spec.md","label":"hOCR"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0010/nubian-message-1992-11-30_0010.txt","format":"text/plain","label":"plain text OCR"}],"images":[{"@id":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0010/image","@type":"oa:Annotation","on":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0010","motivation":"sc:painting","resource":{"@id":"https://iiif.lib.ncsu.edu/iiif/nubian-message-1992-11-30_0010/full/1170,/0/default.jpg","width":1170,"height":1398,"service":{"profile":"http://iiif.io/api/image/2/level2.json","@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.lib.ncsu.edu/iiif/nubian-message-1992-11-30_0010"},"@type":"dctypes:Image"}}],"otherContent":[{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0010/nubian-message-1992-11-30_0010-annotation-list-word.json","@type":"sc:AnnotationList","label":"Text of this page (word level)"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0010/nubian-message-1992-11-30_0010-annotation-list-line.json","@type":"sc:AnnotationList","label":"Text of this page (line level)"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0010/nubian-message-1992-11-30_0010-annotation-list-paragraph.json","@type":"sc:AnnotationList","label":"Text of this page (paragraph level)"}]},{"@type":"sc:Canvas","@id":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0011","width":4718,"height":5620,"label":"11","seeAlso":[{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0011/nubian-message-1992-11-30_0011.hocr","format":"text/vnd.hocr+html","profile":"https://github.com/kba/hocr-spec/blob/master/hocr-spec.md","label":"hOCR"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0011/nubian-message-1992-11-30_0011.txt","format":"text/plain","label":"plain text OCR"}],"images":[{"@id":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0011/image","@type":"oa:Annotation","on":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0011","motivation":"sc:painting","resource":{"@id":"https://iiif.lib.ncsu.edu/iiif/nubian-message-1992-11-30_0011/full/1170,/0/default.jpg","width":1170,"height":1394,"service":{"profile":"http://iiif.io/api/image/2/level2.json","@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.lib.ncsu.edu/iiif/nubian-message-1992-11-30_0011"},"@type":"dctypes:Image"}}],"otherContent":[{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0011/nubian-message-1992-11-30_0011-annotation-list-word.json","@type":"sc:AnnotationList","label":"Text of this page (word level)"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0011/nubian-message-1992-11-30_0011-annotation-list-line.json","@type":"sc:AnnotationList","label":"Text of this page (line level)"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0011/nubian-message-1992-11-30_0011-annotation-list-paragraph.json","@type":"sc:AnnotationList","label":"Text of this page (paragraph level)"}]},{"@type":"sc:Canvas","@id":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0012","width":4716,"height":5626,"label":"12","seeAlso":[{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0012/nubian-message-1992-11-30_0012.hocr","format":"text/vnd.hocr+html","profile":"https://github.com/kba/hocr-spec/blob/master/hocr-spec.md","label":"hOCR"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0012/nubian-message-1992-11-30_0012.txt","format":"text/plain","label":"plain text OCR"}],"images":[{"@id":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0012/image","@type":"oa:Annotation","on":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0012","motivation":"sc:painting","resource":{"@id":"https://iiif.lib.ncsu.edu/iiif/nubian-message-1992-11-30_0012/full/1170,/0/default.jpg","width":1170,"height":1396,"service":{"profile":"http://iiif.io/api/image/2/level2.json","@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.lib.ncsu.edu/iiif/nubian-message-1992-11-30_0012"},"@type":"dctypes:Image"}}],"otherContent":[{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0012/nubian-message-1992-11-30_0012-annotation-list-word.json","@type":"sc:AnnotationList","label":"Text of this page (word level)"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0012/nubian-message-1992-11-30_0012-annotation-list-line.json","@type":"sc:AnnotationList","label":"Text of this page (line level)"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0012/nubian-message-1992-11-30_0012-annotation-list-paragraph.json","@type":"sc:AnnotationList","label":"Text of this page (paragraph level)"}]},{"@type":"sc:Canvas","@id":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0013","width":4726,"height":5612,"label":"13","seeAlso":[{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0013/nubian-message-1992-11-30_0013.hocr","format":"text/vnd.hocr+html","profile":"https://github.com/kba/hocr-spec/blob/master/hocr-spec.md","label":"hOCR"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0013/nubian-message-1992-11-30_0013.txt","format":"text/plain","label":"plain text OCR"}],"images":[{"@id":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0013/image","@type":"oa:Annotation","on":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0013","motivation":"sc:painting","resource":{"@id":"https://iiif.lib.ncsu.edu/iiif/nubian-message-1992-11-30_0013/full/1170,/0/default.jpg","width":1170,"height":1389,"service":{"profile":"http://iiif.io/api/image/2/level2.json","@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.lib.ncsu.edu/iiif/nubian-message-1992-11-30_0013"},"@type":"dctypes:Image"}}],"otherContent":[{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0013/nubian-message-1992-11-30_0013-annotation-list-word.json","@type":"sc:AnnotationList","label":"Text of this page (word level)"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0013/nubian-message-1992-11-30_0013-annotation-list-line.json","@type":"sc:AnnotationList","label":"Text of this page (line level)"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0013/nubian-message-1992-11-30_0013-annotation-list-paragraph.json","@type":"sc:AnnotationList","label":"Text of this page (paragraph level)"}]},{"@type":"sc:Canvas","@id":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0014","width":4720,"height":5614,"label":"14","seeAlso":[{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0014/nubian-message-1992-11-30_0014.hocr","format":"text/vnd.hocr+html","profile":"https://github.com/kba/hocr-spec/blob/master/hocr-spec.md","label":"hOCR"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0014/nubian-message-1992-11-30_0014.txt","format":"text/plain","label":"plain text OCR"}],"images":[{"@id":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0014/image","@type":"oa:Annotation","on":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0014","motivation":"sc:painting","resource":{"@id":"https://iiif.lib.ncsu.edu/iiif/nubian-message-1992-11-30_0014/full/1170,/0/default.jpg","width":1170,"height":1392,"service":{"profile":"http://iiif.io/api/image/2/level2.json","@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.lib.ncsu.edu/iiif/nubian-message-1992-11-30_0014"},"@type":"dctypes:Image"}}],"otherContent":[{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0014/nubian-message-1992-11-30_0014-annotation-list-word.json","@type":"sc:AnnotationList","label":"Text of this page (word level)"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0014/nubian-message-1992-11-30_0014-annotation-list-line.json","@type":"sc:AnnotationList","label":"Text of this page (line level)"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0014/nubian-message-1992-11-30_0014-annotation-list-paragraph.json","@type":"sc:AnnotationList","label":"Text of this page (paragraph level)"}]},{"@type":"sc:Canvas","@id":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0015","width":4710,"height":5612,"label":"15","seeAlso":[{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0015/nubian-message-1992-11-30_0015.hocr","format":"text/vnd.hocr+html","profile":"https://github.com/kba/hocr-spec/blob/master/hocr-spec.md","label":"hOCR"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0015/nubian-message-1992-11-30_0015.txt","format":"text/plain","label":"plain text OCR"}],"images":[{"@id":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0015/image","@type":"oa:Annotation","on":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0015","motivation":"sc:painting","resource":{"@id":"https://iiif.lib.ncsu.edu/iiif/nubian-message-1992-11-30_0015/full/1170,/0/default.jpg","width":1170,"height":1394,"service":{"profile":"http://iiif.io/api/image/2/level2.json","@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.lib.ncsu.edu/iiif/nubian-message-1992-11-30_0015"},"@type":"dctypes:Image"}}],"otherContent":[{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0015/nubian-message-1992-11-30_0015-annotation-list-word.json","@type":"sc:AnnotationList","label":"Text of this page (word level)"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0015/nubian-message-1992-11-30_0015-annotation-list-line.json","@type":"sc:AnnotationList","label":"Text of this page (line level)"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0015/nubian-message-1992-11-30_0015-annotation-list-paragraph.json","@type":"sc:AnnotationList","label":"Text of this page (paragraph level)"}]},{"@type":"sc:Canvas","@id":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0016","width":4682,"height":5628,"label":"16","seeAlso":[{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0016/nubian-message-1992-11-30_0016.hocr","format":"text/vnd.hocr+html","profile":"https://github.com/kba/hocr-spec/blob/master/hocr-spec.md","label":"hOCR"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0016/nubian-message-1992-11-30_0016.txt","format":"text/plain","label":"plain text OCR"}],"images":[{"@id":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0016/image","@type":"oa:Annotation","on":"https://d.lib.ncsu.edu/collections/canvas/nubian-message-1992-11-30_0016","motivation":"sc:painting","resource":{"@id":"https://iiif.lib.ncsu.edu/iiif/nubian-message-1992-11-30_0016/full/1170,/0/default.jpg","width":1170,"height":1406,"service":{"profile":"http://iiif.io/api/image/2/level2.json","@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.lib.ncsu.edu/iiif/nubian-message-1992-11-30_0016"},"@type":"dctypes:Image"}}],"otherContent":[{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0016/nubian-message-1992-11-30_0016-annotation-list-word.json","@type":"sc:AnnotationList","label":"Text of this page (word level)"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0016/nubian-message-1992-11-30_0016-annotation-list-line.json","@type":"sc:AnnotationList","label":"Text of this page (line level)"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0016/nubian-message-1992-11-30_0016-annotation-list-paragraph.json","@type":"sc:AnnotationList","label":"Text of this page (paragraph level)"}]}],"@type":"sc:Sequence","viewingHint":"paged","rendering":[{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30/nubian-message-1992-11-30.pdf","label":"Download as PDF, 5.26 MB","format":"application/pdf"},{"@id":"https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30/nubian-message-1992-11-30.txt","label":"Download OCR Text","format":"plain/text"}]}],"service":[{"@context":"http://iiif.io/api/search/0/context.json","@id":"https://ocr.lib.ncsu.edu/search/nubian-message-1992-11-30","profile":"http://iiif.io/api/search/0/search","label":"Search within this thing","service":{"@id":"https://ocr.lib.ncsu.edu/suggest/nubian-message-1992-11-30","profile":"http://iiif.io/api/search/0/autocomplete","label":"Get suggested words"}}],"seeAlso":[{"@id":"https://d.lib.ncsu.edu/collections/catalog/oai?identifier=ncsul%2Fnubian-message-1992-11-30\u0026metadataPrefix=oai_dc\u0026verb=GetRecord","format":"text/xml","label":"Dublin Core XML via OAI-PMH"},{"@id":"https://d.lib.ncsu.edu/collections/catalog/nubian-message-1992-11-30/schemaorg.json","format":"application/ld+json","profile":"https://schema.org","label":"Schema.org metadata as JSON-LD"}],"dcterms:modified":"2016-08-29T14:11:52.000Z","dcterms:created":"2016-02-19T16:47:04.000Z"} -------------------------------------------------------------------------------- /iiif_prezi_upgrader/prezi_upgrader.py: -------------------------------------------------------------------------------- 1 | 2 | ### 3 | ### IIIF Presentation API version 2 to version 3 upgrader 4 | ### 5 | 6 | import json 7 | import requests 8 | import uuid 9 | from collections import OrderedDict 10 | 11 | try: 12 | STR_TYPES = [str, unicode] #Py2 13 | except: 14 | STR_TYPES = [bytes, str] #Py3 15 | 16 | 17 | FLAGS = { 18 | "crawl": {"prop": "crawl", "default": False, 19 | "description": "NOT YET IMPLEMENTED. Crawl to linked resources, such as AnnotationLists from a Manifest"}, 20 | "desc_2_md": {"prop": "description_is_metadata", "default": True, 21 | "description": "If true, then the source's `description` properties will be put into a `metadata` pair.\ 22 | If false, they will be put into `summary`."}, 23 | "related_2_md": {"prop": "related_is_metadata", "default": False, 24 | "description": "If true, then the `related` resource will go into a `metadata` pair.\ 25 | If false, it will become the `homepage` of the resource."}, 26 | "ext_ok": {"prop": "ext_ok", "default": False, 27 | "description": "If true, then extensions are allowed and will be copied across. \ 28 | If false, then they will raise an error."}, 29 | "default_lang": {"prop": "default_lang", "default": "@none", 30 | "description": "The default language to use when adding values to language maps."}, 31 | "deref_links": {"prop": "deref_links", "default": True, 32 | "description": "If true, the conversion will dereference external content resources to look for format and type."}, 33 | "debug": {"prop": "debug", "default": False, 34 | "description": "If true, then go into a more verbose debugging mode."}, 35 | "attribution_label": {"prop": "attribution_label", "default": "Attribution", 36 | "description": "The label to use for requiredStatement mapping from attribution"}, 37 | "license_label": {"prop": "license_label", "default": "Rights/License", 38 | "description": "The label to use for non-conforming license URIs mapped into metadata"} 39 | } 40 | 41 | KEY_ORDER = ["@context", "id", "@id", "type", "@type", "motivation", "label", "profile", 42 | "format", "language", "value", "metadata", "requiredStatement", "thumbnail", 43 | "homepage", "logo", "rights", "logo", "height", "width", "start", 44 | "viewingDirection", "behavior", "navDate", "rendering", "seeAlso", 45 | "partOf", "includes", "items", "structures", "annotations"] 46 | KEY_ORDER_HASH = dict([(KEY_ORDER[x], x) for x in range(len(KEY_ORDER))]) 47 | 48 | class Upgrader(object): 49 | 50 | def __init__(self, flags={}): 51 | 52 | for flag, info in FLAGS.items(): 53 | setattr(self, info['prop'], flags.get(flag, info['default'])) 54 | 55 | self.id_type_hash = {} 56 | self.language_properties = ['label', 'summary'] 57 | self.do_not_traverse = ['metadata', 'structures', '_structures', 'requiredStatement'] 58 | 59 | self.all_properties = [ 60 | "label", "metadata", "summary", "thumbnail", "navDate", 61 | "requiredStatement", "rights", "logo", "value", 62 | "id", "type", "format", "language", "profile", "timeMode", 63 | "height", "width", "duration", "viewingDirection", "behavior", 64 | "homepage", "rendering", "service", "seeAlso", "partOf", 65 | "start", "includes", "items", "structures", "annotations"] 66 | 67 | self.annotation_properties = [ 68 | "body", "target", "motivation", "source", "selector", "state", 69 | "stylesheet", "styleClass" 70 | ] 71 | 72 | self.set_properties = [ 73 | "thumbnail", "logo", "behavior", 74 | "rendering", "service", "seeAlso", "partOf" 75 | ] 76 | 77 | self.object_property_types = { 78 | "thumbnail": "Image", 79 | "logo":"Image", 80 | "homepage": "", 81 | "rendering": "", 82 | "seeAlso": "Dataset", 83 | "partOf": "" 84 | } 85 | 86 | self.content_type_map = { 87 | "image": "Image", 88 | "audio": "Sound", 89 | "video": "Video", 90 | "application/pdf": "Text", 91 | "text/html": "Text", 92 | "text/plain": "Text", 93 | "application/xml": "Dataset", 94 | "text/xml": "Dataset" 95 | } 96 | 97 | def warn(self, msg): 98 | if self.debug: 99 | print(msg) 100 | 101 | def retrieve_resource(self, uri): 102 | resp = requests.get(uri, verify=False) 103 | try: 104 | val = resp.json() 105 | except: 106 | try: 107 | val = json.loads(r.text) 108 | except: 109 | val = {} 110 | return val 111 | 112 | def mint_uri(self): 113 | return "https://example.org/uuid/%s" % uuid.uuid4() 114 | 115 | def traverse(self, what): 116 | new = {} 117 | for (k,v) in what.items(): 118 | if k in self.language_properties or k in self.do_not_traverse: 119 | # also handled by language_map, etc 120 | new[k] = v 121 | continue 122 | elif k == 'service': 123 | # break service out as it has so many types 124 | fn = self.process_service 125 | else: 126 | fn = self.process_resource 127 | 128 | if type(v) == dict: 129 | if not set(v.keys()) == set(['type', 'id']): 130 | new[k] = fn(v) 131 | else: 132 | new[k] = v 133 | elif type(v) == list: 134 | newl = [] 135 | for i in v: 136 | if type(i) == dict: 137 | if not set(i.keys()) == set(['type', 'id']): 138 | newl.append(fn(i)) 139 | else: 140 | newl.append(i) 141 | else: 142 | newl.append(i) 143 | new[k] = newl 144 | else: 145 | new[k] = v 146 | if not k in self.all_properties and not k in self.annotation_properties: 147 | self.warn("Unknown property: %s" % k) 148 | 149 | return new 150 | 151 | def fix_service_type(self, what): 152 | # manage known service contexts 153 | if '@context' in what: 154 | ctxt = what['@context'] 155 | if ctxt == "http://iiif.io/api/image/2/context.json": 156 | what['@type'] = "ImageService2" 157 | del what['@context'] 158 | return what 159 | elif ctxt in ["http://iiif.io/api/image/1/context.json", 160 | "http://library.stanford.edu/iiif/image-api/1.1/context.json"]: 161 | what['@type'] = "ImageService1" 162 | del what['@context'] 163 | return what 164 | elif ctxt in ["http://iiif.io/api/search/1/context.json", 165 | "http://iiif.io/api/search/0/context.json", 166 | "http://iiif.io/api/auth/1/context.json", 167 | "http://iiif.io/api/auth/0/context.json"]: 168 | # handle below in profiles, but delete context here 169 | del what['@context'] 170 | elif ctxt == "http://iiif.io/api/annex/openannotation/context.json": 171 | what['@type'] = "ImageApiSelector" 172 | del what['@context'] 173 | else: 174 | what['@type'] = "Service" 175 | self.warn("Unknown context: %s" % ctxt) 176 | 177 | if 'profile' in what: 178 | # Auth: CookieService1 , TokenService1 179 | profile = what['profile'] 180 | if profile in [ 181 | "http://iiif.io/api/auth/1/kiosk", 182 | "http://iiif.io/api/auth/1/login", 183 | "http://iiif.io/api/auth/1/clickthrough", 184 | "http://iiif.io/api/auth/1/external", 185 | "http://iiif.io/api/auth/0/kiosk", 186 | "http://iiif.io/api/auth/0/login", 187 | "http://iiif.io/api/auth/0/clickthrough", 188 | "http://iiif.io/api/auth/0/external" 189 | ]: 190 | what['@type'] = 'AuthCookieService1' 191 | # leave profile alone 192 | elif profile in ["http://iiif.io/api/auth/1/token", 193 | "http://iiif.io/api/auth/0/token"]: 194 | what['@type'] = 'AuthTokenService1' 195 | elif profile in ["http://iiif.io/api/auth/1/logout", 196 | "http://iiif.io/api/auth/0/logout"]: 197 | what['@type'] = 'AuthLogoutService1' 198 | elif profile in ["http://iiif.io/api/search/1/search", 199 | "http://iiif.io/api/search/0/search"]: 200 | what['@type'] = "SearchService1" 201 | elif profile in ["http://iiif.io/api/search/1/autocomplete", 202 | "http://iiif.io/api/search/0/autocomplete"]: 203 | what['@type'] = "AutoCompleteService1" 204 | 205 | return what 206 | 207 | def fix_type(self, what): 208 | # Called from process_resource so we can switch 209 | t = what.get('@type', '') 210 | if t: 211 | if type(t) == list: 212 | if 'oa:CssStyle' in t: 213 | t = "CssStylesheet" 214 | elif 'cnt:ContentAsText' in t: 215 | t = "TextualBody" 216 | if t.startswith('sc:'): 217 | t = t.replace('sc:', '') 218 | elif t.startswith('oa:'): 219 | t = t.replace('oa:', '') 220 | elif t.startswith('dctypes:'): 221 | t = t.replace('dctypes:', '') 222 | elif t.startswith('iiif:'): 223 | # e.g iiif:ImageApiSelector 224 | t = t.replace('iiif:', '') 225 | if t == "Layer": 226 | t = "AnnotationCollection" 227 | elif t == "AnnotationList": 228 | t = "AnnotationPage" 229 | elif t == "cnt:ContentAsText": 230 | t = "TextualBody" 231 | what['type'] = t 232 | del what['@type'] 233 | return what 234 | 235 | def do_language_map(self, value): 236 | new = {} 237 | defl = self.default_lang 238 | if type(value) in STR_TYPES: 239 | new[defl] = [value] 240 | elif type(value) == dict: 241 | try: 242 | new[value['@language']].append(value['@value']) 243 | except: 244 | new[value['@language']] = [value['@value']] 245 | elif type(value) == list: 246 | for i in value: 247 | if type(i) == dict: 248 | try: 249 | new[i['@language']].append(i['@value']) 250 | except: 251 | try: 252 | new[i['@language']] = [i['@value']] 253 | except KeyError: 254 | # Just @value, no @langauge (ucd.ie) 255 | if '@none' in new: 256 | new['@none'].append(i['@value']) 257 | else: 258 | new['@none'] = [i['@value']] 259 | elif type(i) == list: 260 | pass 261 | elif type(i) == dict: 262 | # UCD has just {"@value": ""} 263 | if not '@language' in i: 264 | i['@language'] = '@none' 265 | try: 266 | new[i['@language']].append(i['@value']) 267 | except: 268 | new[i['@language']] = [i['@value']] 269 | else: # string value 270 | try: 271 | new[defl].append(i) 272 | except: 273 | new[defl] = [i] 274 | 275 | else: # string value 276 | new[defl] = [value] 277 | return new 278 | 279 | def fix_languages(self, what): 280 | for p in self.language_properties: 281 | if p in what: 282 | try: 283 | what[p] = self.do_language_map(what[p]) 284 | except: 285 | raise 286 | if 'metadata' in what: 287 | newmd = [] 288 | for pair in what['metadata']: 289 | l = self.do_language_map(pair['label']) 290 | v = self.do_language_map(pair['value']) 291 | newmd.append({'label': l, 'value': v}) 292 | what['metadata'] = newmd 293 | return what 294 | 295 | def fix_sets(self, what): 296 | for p in self.set_properties: 297 | if p in what: 298 | v = what[p] 299 | if type(v) != list: 300 | v = [v] 301 | what[p] = v 302 | return what 303 | 304 | def set_remote_type(self, what): 305 | # do a HEAD on the resource and look at Content-Type 306 | try: 307 | h = requests.head(what['id']) 308 | except: 309 | # dummy URI 310 | h = None 311 | if h and h.status_code == 200: 312 | ct = h.headers['content-type'] 313 | what['format'] = ct # as we have it... 314 | ct = ct.lower() 315 | first = ct.split('/')[0] 316 | 317 | if first in self.content_type_map: 318 | what['type'] = self.content_type_map[first] 319 | elif ct in self.content_type_map: 320 | what['type'] = self.content_type_map[ct] 321 | elif ct.startswith("application/json") or \ 322 | ct.startswith("application/ld+json"): 323 | # Try and fetch and look for a type! 324 | data = self.retrieve_resource(v['id']) 325 | if 'type' in data: 326 | what['type'] = data['type'] 327 | elif '@type' in data: 328 | data = self.fix_type(data) 329 | what['type'] = data['type'] 330 | 331 | def fix_object(self, what, typ): 332 | if type(what) != dict: 333 | what = {'id': what} 334 | 335 | if 'id' in what: 336 | myid = what['id'] 337 | elif '@id' in what: 338 | myid = what['@id'] 339 | else: 340 | myid = '' 341 | 342 | if not 'type' in what and typ: 343 | what['type'] = typ 344 | elif not 'type' in what and myid: 345 | if myid in self.id_type_hash: 346 | what['type'] = self.id_type_hash[myid] 347 | elif self.deref_links: 348 | self.set_remote_type(myid) 349 | else: 350 | # Try to guess from format 351 | if 'format' in what: 352 | if what['format'].startswith('image/'): 353 | what['type'] = "Image" 354 | elif what['format'].startswith('video/'): 355 | what['type'] = "Video" 356 | elif what['format'].startswith('audio/'): 357 | what['type'] = "Audio" 358 | elif what['format'].startswith('text/'): 359 | what['type'] = "Text" 360 | elif what['format'].startswith('application/pdf'): 361 | what['type'] = "Text" 362 | 363 | # Try to guess from URI 364 | if not 'type' in what and myid.find('.htm') > -1: 365 | what['type'] = "Text" 366 | elif not 'type' in what: 367 | # Failed to set type, but it's required 368 | # We won't validate because of this 369 | pass 370 | return what 371 | 372 | def fix_objects(self, what): 373 | for (p,typ) in self.object_property_types.items(): 374 | if p in what: 375 | new = [] 376 | # Assumes list :( 377 | if p in self.set_properties: 378 | for v in what[p]: 379 | nv = self.fix_object(v, typ) 380 | new.append(nv) 381 | else: 382 | new = self.fix_object(what[p], typ) 383 | what[p] = new 384 | return what 385 | 386 | def process_generic(self, what): 387 | """ process generic IIIF properties """ 388 | if '@id' in what: 389 | what['id'] = what['@id'] 390 | del what['@id'] 391 | else: 392 | # Add in id with a vanilla UUID 393 | what['id'] = self.mint_uri() 394 | 395 | # @type already processed 396 | # Now add to id/type hash for lookups 397 | if 'id' in what and 'type' in what: 398 | try: 399 | self.id_type_hash[what['id']] = what['type'] 400 | except Exception as e: 401 | raise ValueError(what['id']) 402 | 403 | if 'license' in what: 404 | # License went from many to single 405 | # Also requires CC or RSS, otherwise extension 406 | # Put others into metadata 407 | lic = what['license'] 408 | if type(lic) != list: 409 | lic = [lic] 410 | done = False 411 | for l in lic: 412 | if type(l) == dict: 413 | l = l['@id'] 414 | if not done and (l.find('creativecommons.org/') >-1 or l.find('rightsstatements.org/') > -1): 415 | # match 416 | what['rights'] = l 417 | done = True 418 | else: 419 | # fix_languages below will correct these to langMaps 420 | licstmt = {"label": self.license_label, "value": l} 421 | md = what.get('metadata', []) 422 | md.append(licstmt) 423 | what['metadata'] = md 424 | del what['license'] 425 | if 'attribution' in what: 426 | label = self.do_language_map(self.attribution_label) 427 | val = self.do_language_map(what['attribution']) 428 | what['requiredStatement'] = {"label": label, "value": val} 429 | del what['attribution'] 430 | 431 | if 'viewingHint' in what: 432 | if not 'behavior' in what: 433 | what['behavior'] = what['viewingHint'] 434 | else: 435 | # will already be a list 436 | if type(what['viewingHint']) == list: 437 | what['behavior'].extend(what['viewingHint']) 438 | else: 439 | what['behavior'].append(what['viewingHint']) 440 | del what['viewingHint'] 441 | if 'description' in what: 442 | if self.description_is_metadata: 443 | # Put it in metadata 444 | md = what.get('metadata', []) 445 | # NB this must happen before fix_languages 446 | md.append({"label": u"Description", "value": what['description']}) 447 | what['metadata'] = md 448 | else: 449 | # rename to summary 450 | what['summary'] = what['description'] 451 | del what['description'] 452 | if 'related' in what: 453 | rels = what['related'] 454 | if type(rels) != list: 455 | rels = [rels] 456 | for rel in rels: 457 | if not self.related_is_metadata and rel == rels[0]: 458 | # Assume first is homepage, rest to metadata 459 | if type(rel) != dict: 460 | rel = {'@id': rel} 461 | what['homepage'] = rel 462 | else: 463 | if type(rel) == dict: 464 | uri = rel['@id'] 465 | if 'label' in rel: 466 | label = rel['label'] 467 | else: 468 | uri = rel 469 | md = what.get('metadata', []) 470 | # NB this must happen before fix_languages 471 | md.append({"label": u"Related", "value": "%s" % (uri, label) }) 472 | what['metadata'] = md 473 | del what['related'] 474 | 475 | if "otherContent" in what: 476 | # otherContent is already AnnotationList, so no need to inject 477 | what['annotations'] = what['otherContent'] 478 | del what['otherContent'] 479 | 480 | if "within" in what: 481 | what['partOf'] = what['within'] 482 | del what['within'] 483 | 484 | what = self.fix_languages(what) 485 | what = self.fix_sets(what) 486 | what = self.fix_objects(what) 487 | return what 488 | 489 | def process_service(self, what): 490 | what = self.fix_service_type(what) 491 | # The only thing to traverse is further services 492 | # everything else we leave alone 493 | if 'service' in what: 494 | ss = what['service'] 495 | if type(ss) != list: 496 | what['service'] = [ss] 497 | nl = [] 498 | for s in what['service']: 499 | nl.append(self.process_service(s)) 500 | what['service'] = nl 501 | return what 502 | 503 | def process_collection(self, what): 504 | what = self.process_generic(what) 505 | 506 | if 'members' in what: 507 | what['items'] = what['members'] 508 | del what['members'] 509 | else: 510 | nl = [] 511 | colls = what.get('collections', []) 512 | for c in colls: 513 | if not type(c) == dict: 514 | c = {'id': c, 'type': 'Collection'} 515 | elif not 'type' in c: 516 | c['type'] = 'Collection' 517 | nl.append(c) 518 | mfsts = what.get('manifests', []) 519 | for m in mfsts: 520 | if not type(m) == dict: 521 | m = {'id': m, 'type': 'Manifest'} 522 | elif not 'type' in m: 523 | m['type'] = 'Manifest' 524 | nl.append(m) 525 | if nl: 526 | what['items'] = nl 527 | if 'manifests' in what: 528 | del what['manifests'] 529 | if 'collections' in what: 530 | del what['collections'] 531 | return what 532 | 533 | def process_manifest(self, what): 534 | what = self.process_generic(what) 535 | 536 | if 'startCanvas' in what: 537 | v = what['startCanvas'] 538 | if type(v) != dict: 539 | what['start'] = {'id': v, 'type': "Canvas"} 540 | else: 541 | v['type'] = "Canvas" 542 | what['start'] = v 543 | del what['startCanvas'] 544 | 545 | # Need to test as might not be top object 546 | if 'sequences' in what: 547 | # No more sequences! 548 | seqs = what['sequences'] 549 | what['items'] = seqs[0]['canvases'] 550 | del what['sequences'] 551 | if len(seqs) > 1: 552 | # Process to ranges 553 | what['_structures'] = [] 554 | for s in seqs: 555 | 556 | # XXX Test here to see if we need to crawl 557 | 558 | rng = {"id": s.get('@id', self.mint_uri()), "type": "Range"} 559 | rng['behavior'] = ['sequence'] 560 | rng['items'] = [] 561 | for c in s['canvases']: 562 | if type(c) == dict: 563 | rng['items'].append({"id": c['@id'], "type": "Canvas"}) 564 | elif type(c) in STR_TYPES: 565 | rng['items'].append({"id": c, "type": "Canvas"}) 566 | # Copy other properties and hand off to _generic 567 | del s['canvases'] 568 | for k in s.keys(): 569 | if not k in ['@id', '@type']: 570 | rng[k] = s[k] 571 | self.process_generic(rng) 572 | what['_structures'].append(rng) 573 | return what 574 | 575 | def process_range(self, what): 576 | what = self.process_generic(what) 577 | 578 | members = what.get('members', []) 579 | if 'items' in what: 580 | # preconfigured, move right along 581 | pass 582 | elif 'members' in what: 583 | its = what['members'] 584 | del what['members'] 585 | nl = [] 586 | for i in its: 587 | if not type(i) == dict: 588 | # look in id/type hash 589 | if i in self.id_type_hash: 590 | nl.append({"id": i, "type": self.id_type_hash[i]}) 591 | else: 592 | nl.append({"id": i}) 593 | else: 594 | nl.append(i) 595 | what['items'] = nl 596 | else: 597 | nl = [] 598 | rngs = what.get('ranges', []) 599 | for r in rngs: 600 | if not type(r) == dict: 601 | r = {'id': r, 'type': 'Range'} 602 | elif not 'type' in r: 603 | r['type'] = 'Range' 604 | nl.append(r) 605 | cvs = what.get('canvases', []) 606 | for c in cvs: 607 | if not type(c) == dict: 608 | c = {'id': c, 'type': 'Canvas'} 609 | elif not 'type' in c: 610 | c['type'] = 'Canvas' 611 | nl.append(c) 612 | what['items'] = nl 613 | 614 | if 'canvases' in what: 615 | del what['canvases'] 616 | if 'ranges' in what: 617 | del what['ranges'] 618 | 619 | # contentLayer 620 | if 'contentLayer' in what: 621 | v = what['contentLayer'] 622 | if type(v) == list and len(v) == 1: 623 | v = v[0] 624 | if type(v) != dict: 625 | what['supplementary'] = {'id': v, 'type': "AnnotationCollection"} 626 | else: 627 | v['type'] = "AnnotationCollection" 628 | what['supplementary'] = v 629 | del what['contentLayer'] 630 | 631 | # Remove redundant 'top' Range 632 | if 'behavior' in what and 'top' in what['behavior']: 633 | what['behavior'].remove('top') 634 | # if we're empty, remove it 635 | if not what['behavior']: 636 | del what['behavior'] 637 | 638 | if 'supplementary' in what: 639 | # single object 640 | what['supplementary'] = self.process_resource(what['supplementary']) 641 | 642 | return what 643 | 644 | 645 | def process_canvas(self, what): 646 | 647 | # XXX process otherContent here before generic grabs it 648 | 649 | what = self.process_generic(what) 650 | 651 | if 'images' in what: 652 | newl = {'type': 'AnnotationPage', 'items': []} 653 | for anno in what['images']: 654 | newl['items'].append(anno) 655 | what['items'] = [newl] 656 | del what['images'] 657 | return what 658 | 659 | def process_layer(self, what): 660 | what = self.process_generic(what) 661 | return what 662 | 663 | def process_annotationpage(self, what): 664 | what = self.process_generic(what) 665 | if 'resources' in what: 666 | what['items'] = what['resources'] 667 | del what['resources'] 668 | elif not 'items' in what: 669 | what['items'] = [] 670 | 671 | return what 672 | 673 | def process_annotationcollection(self, what): 674 | what = self.process_generic(what) 675 | return what 676 | 677 | def process_annotation(self, what): 678 | what = self.process_generic(what) 679 | 680 | if 'on' in what: 681 | what['target'] = what['on'] 682 | del what['on'] 683 | if 'resource' in what: 684 | what['body'] = what['resource'] 685 | del what['resource'] 686 | 687 | m = what.get('motivation', '') 688 | if m: 689 | if m.startswith('sc:'): 690 | m = m.replace('sc:', '') 691 | elif m.startswith('oa:'): 692 | m = m.replace('oa:', '') 693 | what['motivation'] = m 694 | 695 | if 'stylesheet' in what: 696 | ss = what['stylesheet'] 697 | if type(ss) == dict: 698 | ss['@type'] = 'oa:CssStylesheet' 699 | if 'chars' in ss: 700 | ss['value'] = ss['chars'] 701 | del ss['chars'] 702 | else: 703 | # Just a link 704 | what['stylesheet'] = {'@id': ss, '@type': 'oa:CssStylesheet'} 705 | return what 706 | 707 | def process_specificresource(self, what): 708 | what = self.process_generic(what) 709 | if 'full' in what: 710 | # And if not, it's broken... 711 | what['source'] = what['full'] 712 | del what['full'] 713 | if 'style' in what: 714 | what['styleClass'] = what['style'] 715 | del what['style'] 716 | return what 717 | 718 | def process_textualbody(self, what): 719 | if 'chars' in what: 720 | what['value'] = what['chars'] 721 | del what['chars'] 722 | return what 723 | 724 | def process_choice(self, what): 725 | what = self.process_generic(what) 726 | 727 | newl = [] 728 | if 'default' in what: 729 | newl.append(what['default']) 730 | del what['default'] 731 | if 'item' in what: 732 | v = what['item'] 733 | if type(v) != list: 734 | v = [v] 735 | newl.extend(v) 736 | del what['item'] 737 | what['items'] = newl 738 | return what 739 | 740 | def post_process_generic(self, what): 741 | 742 | # test known properties of objects for type 743 | if 'homepage' in what and not 'type' in what['homepage']: 744 | what['homepage']['type'] = "Text" 745 | 746 | # drop empty values 747 | what2 = {} 748 | for (k,v) in what.items(): 749 | if type(v) == list: 750 | new = [] 751 | for vi in v: 752 | if vi: 753 | new.append(vi) 754 | v = new 755 | if v: 756 | what2[k] = v 757 | 758 | return what2 759 | 760 | def post_process_manifest(self, what): 761 | 762 | what = self.post_process_generic(what) 763 | 764 | # do ranges at this point, after everything else is traversed 765 | tops = [] 766 | if 'structures' in what: 767 | # Need to process from here, to have access to all info 768 | # needed to unflatten them 769 | rhash = {} 770 | for r in what['structures']: 771 | new = self.fix_type(r) 772 | new = self.process_range(new) 773 | rhash[new['id']] = new 774 | tops.append(new['id']) 775 | 776 | for rng in what['structures']: 777 | # first try to include our Range items 778 | newits = [] 779 | for child in rng['items']: 780 | if "@id" in child: 781 | c = self.fix_type(child) 782 | c = self.process_generic(c) 783 | else: 784 | c = child 785 | 786 | if c['type'] == "Range" and c['id'] in rhash: 787 | newits.append(rhash[c['id']]) 788 | del rhash[c['id']] 789 | tops.remove(c['id']) 790 | else: 791 | newits.append(c) 792 | rng['items'] = newits 793 | 794 | # Harvard has a strange within based pattern 795 | # which will now be mapped to partOf 796 | if 'partOf' in rng: 797 | tops.remove(rng['id']) 798 | parid = rng['partOf'][0]['id'] 799 | del rng['partOf'] 800 | parent = rhash.get(parid, None) 801 | if not parent: 802 | # Just drop it on the floor? 803 | self.warn("Unknown parent range: %s" % parid) 804 | else: 805 | # e.g. Harvard has massive duplication of canvases 806 | # not wrong, but don't need it any more 807 | for child in rng['items']: 808 | for sibling in parent['items']: 809 | if child['id'] == sibling['id']: 810 | parent['items'].remove(sibling) 811 | break 812 | parent['items'].append(rng) 813 | 814 | if '_structures' in what: 815 | structs = what['_structures'] 816 | del what['_structures'] 817 | else: 818 | structs = [] 819 | if tops: 820 | for t in tops: 821 | if t in rhash: 822 | structs.append(rhash[t]) 823 | if structs: 824 | what['structures'] = structs 825 | return what 826 | 827 | def process_resource(self, what, top=False): 828 | if top: 829 | # process @context 830 | orig_context = what.get("@context", "") 831 | # could be a list with extensions etc 832 | del what['@context'] 833 | 834 | # First update types, so we can switch on it 835 | what = self.fix_type(what) 836 | typ = what.get('type', '') 837 | fn = getattr(self, 'process_%s' % typ.lower(), self.process_generic) 838 | what = fn(what) 839 | what = self.traverse(what) 840 | fn2 = getattr(self, 'post_process_%s' % typ.lower(), self.post_process_generic) 841 | what = fn2(what) 842 | 843 | if top: 844 | # Add back in the v3 context 845 | if type(orig_context) == list: 846 | # XXX process extensions 847 | pass 848 | else: 849 | what['@context'] = [ 850 | "http://www.w3.org/ns/anno.jsonld", 851 | "http://iiif.io/api/presentation/3/context.json"] 852 | return what 853 | 854 | def process_uri(self, uri, top=False): 855 | what = self.retrieve_resource(uri) 856 | return self.process_resource(what, top) 857 | 858 | def process_cached(self, fn, top=True): 859 | with open(fn, 'r') as fh: 860 | data = fh.read() 861 | what = json.loads(data) 862 | return self.process_resource(what, top) 863 | 864 | def reorder(self, what): 865 | new = {} 866 | for (k,v) in what.items(): 867 | if type(v) == list: 868 | nl = [] 869 | for i in v: 870 | if type(i) == dict: 871 | nl.append(self.reorder(i)) 872 | else: 873 | nl.append(i) 874 | new[k] = nl 875 | elif type(v) == dict: 876 | new[k] = self.reorder(v) 877 | else: 878 | new[k] = v 879 | return OrderedDict(sorted(new.items(), key=lambda x: KEY_ORDER_HASH.get(x[0], 1000))) 880 | 881 | 882 | if __name__ == "__main__": 883 | 884 | upgrader = Upgrader(flags={"ext_ok": False, "deref_links": False}) 885 | #results = upgrader.process_cached('tests/input_data/manifest-basic.json') 886 | 887 | #uri = "http://iiif.io/api/presentation/2.1/example/fixtures/collection.json" 888 | #uri = "http://iiif.io/api/presentation/2.1/example/fixtures/1/manifest.json" 889 | #uri = "http://iiif.io/api/presentation/2.0/example/fixtures/list/65/list1.json" 890 | #uri = "http://media.nga.gov/public/manifests/nga_highlights.json" 891 | #uri = "https://iiif.lib.harvard.edu/manifests/drs:48309543" 892 | #uri = "http://adore.ugent.be/IIIF/manifests/archive.ugent.be%3A4B39C8CA-6FF9-11E1-8C42-C8A93B7C8C91" 893 | #uri = "http://bluemountain.princeton.edu/exist/restxq/iiif/bmtnaae_1918-12_01/manifest" 894 | #uri = "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_00000004216E/manifest.json" 895 | #uri = "https://damsssl.llgc.org.uk/iiif/2.0/4389767/manifest.json" 896 | #uri = "http://iiif.bodleian.ox.ac.uk/iiif/manifest/60834383-7146-41ab-bfe1-48ee97bc04be.json" 897 | #uri = "https://lbiiif.riksarkivet.se/arkis!R0000004/manifest" 898 | #uri = "https://d.lib.ncsu.edu/collections/catalog/nubian-message-1992-11-30/manifest.json" 899 | #uri = "https://ocr.lib.ncsu.edu/ocr/nu/nubian-message-1992-11-30_0010/nubian-message-1992-11-30_0010-annotation-list-paragraph.json" 900 | #uri = "http://iiif.harvardartmuseums.org/manifests/object/299843" 901 | #uri = "https://purl.stanford.edu/qm670kv1873/iiif/manifest.json" 902 | #uri = "http://dams.llgc.org.uk/iiif/newspaper/issue/3320640/manifest.json" 903 | #uri = "http://manifests.ydc2.yale.edu/manifest/Admont43" 904 | #uri = "https://manifests.britishart.yale.edu/manifest/1474" 905 | #uri = "http://demos.biblissima-condorcet.fr/iiif/metadata/BVMM/chateauroux/manifest.json" 906 | #uri = "http://www.e-codices.unifr.ch/metadata/iiif/sl-0002/manifest.json" 907 | #uri = "https://data.ucd.ie/api/img/manifests/ucdlib:33064" 908 | #uri = "http://dzkimgs.l.u-tokyo.ac.jp/iiif/zuzoubu/12b02/manifest.json" 909 | #uri = "https://dzkimgs.l.u-tokyo.ac.jp/iiif/zuzoubu/12b02/list/p0001-0025.json" 910 | #uri = "http://www2.dhii.jp/nijl/NIJL0018/099-0014/manifest_tags.json" 911 | #uri = "https://data.getty.edu/museum/api/iiif/298147/manifest.json" 912 | #uri = "https://www.e-codices.unifr.ch/metadata/iiif/csg-0730/manifest.json" 913 | 914 | #results = upgrader.process_uri(uri, True) 915 | #results = upgrader.process_cached('tests/input_data/manifest-sequences.json') 916 | #results = upgrader.process_cached('tests/input_data/manifest-services.json') 917 | results = upgrader.process_cached('tests/input_data/manifest-basic.json') 918 | 919 | # Now reorder 920 | results = upgrader.reorder(results) 921 | print(json.dumps(results, indent=2)) 922 | 923 | 924 | 925 | ### TO DO: 926 | 927 | # Determine which annotations should be items and which annotations 928 | # -- this is non trivial, but also not common 929 | 930 | ### Cardinality Requirements 931 | # Check all presence of all MUSTs in the spec and maybe bail? 932 | # A Collection must have at least one label. 933 | # A Manifest must have at least one label. 934 | # An AnnotationCollection must have at least one label. 935 | # id on Collection, Manifest, Canvas, content, Range, 936 | # AnnotationCollection, AnnotationPage, Annotation 937 | # type on all 938 | # width+height pair for Canvas, if either 939 | # items all the way down 940 | -------------------------------------------------------------------------------- /tests/remote_cache/nlw-newspaper.manifest.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "@context": "http://iiif.io/api/presentation/2/context.json", 4 | "@id": "http://dams.llgc.org.uk/iiif/newspaper/issue/3320640/manifest.json", 5 | "@type": "sc:Manifest", 6 | "label": "Cambrian (1804-01-28)", 7 | "navDate":"1804-01-28T00:00:00Z", 8 | "metadata": [ 9 | 10 | { 11 | "label":[ 12 | { "@value":"Publisher", "@language": "en"}, 13 | { "@value":"Cyhoeddwr", "@language": "cy-GB"} 14 | ], 15 | "value": "T. Jenkins" 16 | }, 17 | 18 | { 19 | "label": "Place of Publication", 20 | "value": "Swansea" 21 | }, 22 | 23 | { 24 | "label":[ 25 | { "@value":"Frequency", "@language": "en"}, 26 | { "@value":"Amlder", "@language": "cy-GB"} 27 | ], 28 | "value": "Weekly" 29 | }, 30 | 31 | { 32 | "label": "Language", 33 | "value": "eng" 34 | }, 35 | 36 | 37 | { 38 | "label": "Subject", 39 | "value": [ 40 | 41 | "Swansea (Wales) Newspapers" 42 | ] 43 | }, 44 | { 45 | "label":[ 46 | { "@value":"Repository", "@language": "en"}, 47 | { "@value":"Ystorfa", "@language": "cy-GB"}], 48 | "value":[ 49 | { "@value":"This content has been digitised by The National Library of Wales", "@language": "en"}, 50 | { "@value":"Digidwyd y cynnwys hwn gan Lyfrgell Gendlaethol Cymru", "@language": "cy-GB"} 51 | ] 52 | } 53 | ], 54 | "within": "http://dams.llgc.org.uk/iiif/newspapers/3320639.json", 55 | "logo": { 56 | "@id": "http://dams.llgc.org.uk/iiif/2.0/image/logo/full/400,/0/default.jpg", 57 | "service": { 58 | "@context": "http://iiif.io/api/image/2/context.json", 59 | "@id": "http://dams.llgc.org.uk/iiif/2.0/image/logo", 60 | "profile": "http://iiif.io/api/image/2/level1.json" 61 | } 62 | 63 | }, 64 | "seeAlso":{ 65 | "@id":"http://hdl.handle.net/10107/3320640", 66 | "format":"application/rdf+xml", 67 | "profile":"http://www.europeana.eu/schemas/edm/" 68 | }, 69 | "sequences": [ 70 | { 71 | "@type": "sc:Sequence", 72 | "canvases": [ 73 | 74 | { 75 | "@id":"http://dams.llgc.org.uk/iiif/3320640/canvas/3320641", 76 | "@type":"sc:Canvas", 77 | 78 | "label":"[1]", 79 | "height":7905, 80 | "width":5826, 81 | "images": [ 82 | { 83 | "@id":"http://dams.llgc.org.uk/iiif/3320640/annotation/3320641.json", 84 | "@type":"oa:Annotation", 85 | "motivation":"sc:painting", 86 | "resource": { 87 | "@id":"http://dams.llgc.org.uk/iiif/2.0/3320641/image/full/512,/0/default.jpg", 88 | "@type":"dctypes:Image", 89 | "format":"image/jpeg", 90 | "service": { 91 | "@context": "http://iiif.io/api/image/2/context.json", 92 | "@id":"http://dams.llgc.org.uk/iiif/2.0/image/3320641", 93 | "profile":"http://iiif.io/api/image/2/level1.json", 94 | "height":7905, 95 | "width":5826, 96 | "tiles" : [{ 97 | "width": 256, 98 | "scaleFactors": [ 1, 2, 4, 8, 16, 32 ] 99 | }] 100 | }, 101 | "height":7905, 102 | "width":5826 103 | }, 104 | "on":"http://dams.llgc.org.uk/iiif/3320640/canvas/3320641" 105 | }], 106 | "otherContent": [ 107 | 108 | { 109 | "@id": "http://dams.llgc.org.uk/iiif/3320641/annotation/list/ART1.json", 110 | "@type": "sc:AnnotationList", 111 | "label": "TO THE PUBLIC.", 112 | "within": 113 | { 114 | "@id": "http://dams.llgc.org.uk/iiif/3320640/annotation/layer/modsarticle1.json", 115 | "@type": "sc:Layer", 116 | "label": "OCR Article Text" 117 | } 118 | }, 119 | { 120 | "@id": "http://dams.llgc.org.uk/iiif/3320641/annotation/list/ART2.json", 121 | "@type": "sc:AnnotationList", 122 | "label": "Advertising", 123 | "within": 124 | { 125 | "@id": "http://dams.llgc.org.uk/iiif/3320640/annotation/layer/modsarticle2.json", 126 | "@type": "sc:Layer", 127 | "label": "OCR Article Text" 128 | } 129 | } 130 | ] 131 | }, 132 | { 133 | "@id":"http://dams.llgc.org.uk/iiif/3320640/canvas/3320642", 134 | "@type":"sc:Canvas", 135 | 136 | "label":"[2]", 137 | "height":7905, 138 | "width":5826, 139 | "images": [ 140 | { 141 | "@id":"http://dams.llgc.org.uk/iiif/3320640/annotation/3320642.json", 142 | "@type":"oa:Annotation", 143 | "motivation":"sc:painting", 144 | "resource": { 145 | "@id":"http://dams.llgc.org.uk/iiif/2.0/3320642/image/full/512,/0/default.jpg", 146 | "@type":"dctypes:Image", 147 | "format":"image/jpeg", 148 | "service": { 149 | "@context": "http://iiif.io/api/image/2/context.json", 150 | "@id":"http://dams.llgc.org.uk/iiif/2.0/image/3320642", 151 | "profile":"http://iiif.io/api/image/2/level1.json", 152 | "height":7905, 153 | "width":5826, 154 | "tiles" : [{ 155 | "width": 256, 156 | "scaleFactors": [ 1, 2, 4, 8, 16, 32 ] 157 | }] 158 | }, 159 | "height":7905, 160 | "width":5826 161 | }, 162 | "on":"http://dams.llgc.org.uk/iiif/3320640/canvas/3320642" 163 | }], 164 | "otherContent": [ 165 | 166 | { 167 | "@id": "http://dams.llgc.org.uk/iiif/3320642/annotation/list/ART3.json", 168 | "@type": "sc:AnnotationList", 169 | "label": "LONDON.", 170 | "within": 171 | { 172 | "@id": "http://dams.llgc.org.uk/iiif/3320640/annotation/layer/modsarticle3.json", 173 | "@type": "sc:Layer", 174 | "label": "OCR Article Text" 175 | } 176 | }, 177 | { 178 | "@id": "http://dams.llgc.org.uk/iiif/3320642/annotation/list/ART4.json", 179 | "@type": "sc:AnnotationList", 180 | "label": "Advertising", 181 | "within": 182 | { 183 | "@id": "http://dams.llgc.org.uk/iiif/3320640/annotation/layer/modsarticle4.json", 184 | "@type": "sc:Layer", 185 | "label": "OCR Article Text" 186 | } 187 | } 188 | ] 189 | }, 190 | { 191 | "@id":"http://dams.llgc.org.uk/iiif/3320640/canvas/3320643", 192 | "@type":"sc:Canvas", 193 | 194 | "label":"[3]", 195 | "height":7905, 196 | "width":5826, 197 | "images": [ 198 | { 199 | "@id":"http://dams.llgc.org.uk/iiif/3320640/annotation/3320643.json", 200 | "@type":"oa:Annotation", 201 | "motivation":"sc:painting", 202 | "resource": { 203 | "@id":"http://dams.llgc.org.uk/iiif/2.0/3320643/image/full/512,/0/default.jpg", 204 | "@type":"dctypes:Image", 205 | "format":"image/jpeg", 206 | "service": { 207 | "@context": "http://iiif.io/api/image/2/context.json", 208 | "@id":"http://dams.llgc.org.uk/iiif/2.0/image/3320643", 209 | "profile":"http://iiif.io/api/image/2/level1.json", 210 | "height":7905, 211 | "width":5826, 212 | "tiles" : [{ 213 | "width": 256, 214 | "scaleFactors": [ 1, 2, 4, 8, 16, 32 ] 215 | }] 216 | }, 217 | "height":7905, 218 | "width":5826 219 | }, 220 | "on":"http://dams.llgc.org.uk/iiif/3320640/canvas/3320643" 221 | }], 222 | "otherContent": [ 223 | 224 | { 225 | "@id": "http://dams.llgc.org.uk/iiif/3320643/annotation/list/ART5.json", 226 | "@type": "sc:AnnotationList", 227 | "label": "-THE -", 228 | "within": 229 | { 230 | "@id": "http://dams.llgc.org.uk/iiif/3320640/annotation/layer/modsarticle5.json", 231 | "@type": "sc:Layer", 232 | "label": "OCR Article Text" 233 | } 234 | }, 235 | { 236 | "@id": "http://dams.llgc.org.uk/iiif/3320643/annotation/list/ART6.json", 237 | "@type": "sc:AnnotationList", 238 | "label": "Family Notices", 239 | "within": 240 | { 241 | "@id": "http://dams.llgc.org.uk/iiif/3320640/annotation/layer/modsarticle6.json", 242 | "@type": "sc:Layer", 243 | "label": "OCR Article Text" 244 | } 245 | }, 246 | { 247 | "@id": "http://dams.llgc.org.uk/iiif/3320643/annotation/list/ART7.json", 248 | "@type": "sc:AnnotationList", 249 | "label": "HIGH WATER ON SWANSEA BAR", 250 | "within": 251 | { 252 | "@id": "http://dams.llgc.org.uk/iiif/3320640/annotation/layer/modsarticle7.json", 253 | "@type": "sc:Layer", 254 | "label": "OCR Article Text" 255 | } 256 | }, 257 | { 258 | "@id": "http://dams.llgc.org.uk/iiif/3320643/annotation/list/ART8.json", 259 | "@type": "sc:AnnotationList", 260 | "label": "TO CORRESPONDENTS.", 261 | "within": 262 | { 263 | "@id": "http://dams.llgc.org.uk/iiif/3320640/annotation/layer/modsarticle8.json", 264 | "@type": "sc:Layer", 265 | "label": "OCR Article Text" 266 | } 267 | }, 268 | { 269 | "@id": "http://dams.llgc.org.uk/iiif/3320643/annotation/list/ART9.json", 270 | "@type": "sc:AnnotationList", 271 | "label": "COUNTRY MARKETS. I", 272 | "within": 273 | { 274 | "@id": "http://dams.llgc.org.uk/iiif/3320640/annotation/layer/modsarticle9.json", 275 | "@type": "sc:Layer", 276 | "label": "OCR Article Text" 277 | } 278 | }, 279 | { 280 | "@id": "http://dams.llgc.org.uk/iiif/3320643/annotation/list/ART10.json", 281 | "@type": "sc:AnnotationList", 282 | "label": "Advertising", 283 | "within": 284 | { 285 | "@id": "http://dams.llgc.org.uk/iiif/3320640/annotation/layer/modsarticle10.json", 286 | "@type": "sc:Layer", 287 | "label": "OCR Article Text" 288 | } 289 | } 290 | ] 291 | }, 292 | { 293 | "@id":"http://dams.llgc.org.uk/iiif/3320640/canvas/3320644", 294 | "@type":"sc:Canvas", 295 | 296 | "label":"[4]", 297 | "height":7905, 298 | "width":5826, 299 | "images": [ 300 | { 301 | "@id":"http://dams.llgc.org.uk/iiif/3320640/annotation/3320644.json", 302 | "@type":"oa:Annotation", 303 | "motivation":"sc:painting", 304 | "resource": { 305 | "@id":"http://dams.llgc.org.uk/iiif/2.0/3320644/image/full/512,/0/default.jpg", 306 | "@type":"dctypes:Image", 307 | "format":"image/jpeg", 308 | "service": { 309 | "@context": "http://iiif.io/api/image/2/context.json", 310 | "@id":"http://dams.llgc.org.uk/iiif/2.0/image/3320644", 311 | "profile":"http://iiif.io/api/image/2/level1.json", 312 | "height":7905, 313 | "width":5826, 314 | "tiles" : [{ 315 | "width": 256, 316 | "scaleFactors": [ 1, 2, 4, 8, 16, 32 ] 317 | }] 318 | }, 319 | "height":7905, 320 | "width":5826 321 | }, 322 | "on":"http://dams.llgc.org.uk/iiif/3320640/canvas/3320644" 323 | }], 324 | "otherContent": [ 325 | 326 | { 327 | "@id": "http://dams.llgc.org.uk/iiif/3320644/annotation/list/ART11.json", 328 | "@type": "sc:AnnotationList", 329 | "label": "ODE FOR THE NEW YEAR.", 330 | "within": 331 | { 332 | "@id": "http://dams.llgc.org.uk/iiif/3320640/annotation/layer/modsarticle11.json", 333 | "@type": "sc:Layer", 334 | "label": "OCR Article Text" 335 | } 336 | }, 337 | { 338 | "@id": "http://dams.llgc.org.uk/iiif/3320644/annotation/list/ART12.json", 339 | "@type": "sc:AnnotationList", 340 | "label": "THE CURATE.—A FRAGMENT.", 341 | "within": 342 | { 343 | "@id": "http://dams.llgc.org.uk/iiif/3320640/annotation/layer/modsarticle12.json", 344 | "@type": "sc:Layer", 345 | "label": "OCR Article Text" 346 | } 347 | }, 348 | { 349 | "@id": "http://dams.llgc.org.uk/iiif/3320644/annotation/list/ART13.json", 350 | "@type": "sc:AnnotationList", 351 | "label": "VOLUNTEERS.\"", 352 | "within": 353 | { 354 | "@id": "http://dams.llgc.org.uk/iiif/3320640/annotation/layer/modsarticle13.json", 355 | "@type": "sc:Layer", 356 | "label": "OCR Article Text" 357 | } 358 | }, 359 | { 360 | "@id": "http://dams.llgc.org.uk/iiif/3320644/annotation/list/ART14.json", 361 | "@type": "sc:AnnotationList", 362 | "label": "Advertising", 363 | "within": 364 | { 365 | "@id": "http://dams.llgc.org.uk/iiif/3320640/annotation/layer/modsarticle14.json", 366 | "@type": "sc:Layer", 367 | "label": "OCR Article Text" 368 | } 369 | } 370 | ] 371 | } 372 | ] 373 | }], 374 | "structures":[ 375 | 376 | { 377 | "@id":"http://dams.llgc.org.uk/iiif/3320640/article/modsarticle1", 378 | "@type":"sc:Range", 379 | "label":"TO THE PUBLIC.", 380 | "metadata": 381 | [ 382 | { 383 | "label": "Article Category", 384 | "value": "News" 385 | } 386 | ], 387 | 388 | "description":"ON the first appearance of a New Paper be-Cyfore that august Tribunal which is ultimately to fix its destiny, Custom exacts a disclosure of its claim to notice: obedient to. her", 389 | 390 | "canvases": [ 391 | 392 | 393 | "http://dams.llgc.org.uk/iiif/3320640/canvas/3320641#xywh=540,5040,3306,18510" 394 | 395 | ], 396 | "contentLayer": [ 397 | { 398 | "@id": "http://dams.llgc.org.uk/iiif/3320640/annotation/layer/modsarticle1.json", 399 | "@type": "sc:Layer", 400 | "label": "OCR Article Text" 401 | } 402 | ] 403 | }, 404 | { 405 | "@id":"http://dams.llgc.org.uk/iiif/3320640/article/modsarticle2", 406 | "@type":"sc:Range", 407 | "label":"Advertising", 408 | "metadata": 409 | [ 410 | { 411 | "label": "Article Category", 412 | "value": "Advertising" 413 | } 414 | ], 415 | 416 | "canvases": [ 417 | 418 | 419 | "http://dams.llgc.org.uk/iiif/3320640/canvas/3320641#xywh=3834,4983,13134,18528" 420 | 421 | ], 422 | "contentLayer": [ 423 | { 424 | "@id": "http://dams.llgc.org.uk/iiif/3320640/annotation/layer/modsarticle2.json", 425 | "@type": "sc:Layer", 426 | "label": "OCR Article Text" 427 | } 428 | ] 429 | }, 430 | { 431 | "@id":"http://dams.llgc.org.uk/iiif/3320640/article/modsarticle3", 432 | "@type":"sc:Range", 433 | "label":"LONDON.", 434 | "metadata": 435 | [ 436 | { 437 | "label": "Article Category", 438 | "value": "News" 439 | } 440 | ], 441 | 442 | "description":"___THURSDAY, Jan. 19. THE invasion of this country by the French has for some time been the general topic of .. conversation, and various rumours are con", 443 | 444 | "canvases": [ 445 | 446 | 447 | "http://dams.llgc.org.uk/iiif/3320640/canvas/3320642#xywh=582,555,16413,22638" 448 | 449 | ], 450 | "contentLayer": [ 451 | { 452 | "@id": "http://dams.llgc.org.uk/iiif/3320640/annotation/layer/modsarticle3.json", 453 | "@type": "sc:Layer", 454 | "label": "OCR Article Text" 455 | } 456 | ] 457 | }, 458 | { 459 | "@id":"http://dams.llgc.org.uk/iiif/3320640/article/modsarticle4", 460 | "@type":"sc:Range", 461 | "label":"Advertising", 462 | "metadata": 463 | [ 464 | { 465 | "label": "Article Category", 466 | "value": "Advertising" 467 | } 468 | ], 469 | 470 | "canvases": [ 471 | 472 | 473 | "http://dams.llgc.org.uk/iiif/3320640/canvas/3320642#xywh=13773,9642,3387,13467" 474 | 475 | ], 476 | "contentLayer": [ 477 | { 478 | "@id": "http://dams.llgc.org.uk/iiif/3320640/annotation/layer/modsarticle4.json", 479 | "@type": "sc:Layer", 480 | "label": "OCR Article Text" 481 | } 482 | ] 483 | }, 484 | { 485 | "@id":"http://dams.llgc.org.uk/iiif/3320640/article/modsarticle5", 486 | "@type":"sc:Range", 487 | "label":"-THE -", 488 | "metadata": 489 | [ 490 | { 491 | "label": "Article Category", 492 | "value": "News" 493 | } 494 | ], 495 | 496 | "description":"m The difficulties attending the first publications of a Newspaper being inconceivably great, we trust they will operate in extenuation of whatever errors and imperfections may be observed. Maturity will speedily", 497 | 498 | "canvases": [ 499 | 500 | 501 | "http://dams.llgc.org.uk/iiif/3320640/canvas/3320643#xywh=525,525,9993,22614" 502 | 503 | ], 504 | "contentLayer": [ 505 | { 506 | "@id": "http://dams.llgc.org.uk/iiif/3320640/annotation/layer/modsarticle5.json", 507 | "@type": "sc:Layer", 508 | "label": "OCR Article Text" 509 | } 510 | ] 511 | }, 512 | { 513 | "@id":"http://dams.llgc.org.uk/iiif/3320640/article/modsarticle6", 514 | "@type":"sc:Range", 515 | "label":"Family Notices", 516 | "metadata": 517 | [ 518 | { 519 | "label": "Article Category", 520 | "value": "Family Notices" 521 | } 522 | ], 523 | 524 | "canvases": [ 525 | 526 | 527 | "http://dams.llgc.org.uk/iiif/3320640/canvas/3320643#xywh=7173,14520,3303,6357" 528 | 529 | ], 530 | "contentLayer": [ 531 | { 532 | "@id": "http://dams.llgc.org.uk/iiif/3320640/annotation/layer/modsarticle6.json", 533 | "@type": "sc:Layer", 534 | "label": "OCR Article Text" 535 | } 536 | ] 537 | }, 538 | { 539 | "@id":"http://dams.llgc.org.uk/iiif/3320640/article/modsarticle7", 540 | "@type":"sc:Range", 541 | "label":"HIGH WATER ON SWANSEA BAR", 542 | "metadata": 543 | [ 544 | { 545 | "label": "Article Category", 546 | "value": "Detailed Lists, Results and Guides" 547 | } 548 | ], 549 | 550 | "canvases": [ 551 | 552 | 553 | "http://dams.llgc.org.uk/iiif/3320640/canvas/3320643#xywh=7209,20913,3120,1470" 554 | 555 | ], 556 | "contentLayer": [ 557 | { 558 | "@id": "http://dams.llgc.org.uk/iiif/3320640/annotation/layer/modsarticle7.json", 559 | "@type": "sc:Layer", 560 | "label": "OCR Article Text" 561 | } 562 | ] 563 | }, 564 | { 565 | "@id":"http://dams.llgc.org.uk/iiif/3320640/article/modsarticle8", 566 | "@type":"sc:Range", 567 | "label":"TO CORRESPONDENTS.", 568 | "metadata": 569 | [ 570 | { 571 | "label": "Article Category", 572 | "value": "News" 573 | } 574 | ], 575 | 576 | "description":"The Parody from Gray in our next. Communications from our Holywell Correspondent will always be acceptable. We have, for the present, omitted his last favour, on account of the extraordinary mildness of", 577 | 578 | "canvases": [ 579 | 580 | 581 | "http://dams.llgc.org.uk/iiif/3320640/canvas/3320643#xywh=7218,22359,3222,714" 582 | 583 | ], 584 | "contentLayer": [ 585 | { 586 | "@id": "http://dams.llgc.org.uk/iiif/3320640/annotation/layer/modsarticle8.json", 587 | "@type": "sc:Layer", 588 | "label": "OCR Article Text" 589 | } 590 | ] 591 | }, 592 | { 593 | "@id":"http://dams.llgc.org.uk/iiif/3320640/article/modsarticle9", 594 | "@type":"sc:Range", 595 | "label":"COUNTRY MARKETS. I", 596 | "metadata": 597 | [ 598 | { 599 | "label": "Article Category", 600 | "value": "Detailed Lists, Results and Guides" 601 | } 602 | ], 603 | 604 | "canvases": [ 605 | 606 | 607 | "http://dams.llgc.org.uk/iiif/3320640/canvas/3320643#xywh=10401,540,3360,15036" 608 | 609 | ], 610 | "contentLayer": [ 611 | { 612 | "@id": "http://dams.llgc.org.uk/iiif/3320640/annotation/layer/modsarticle9.json", 613 | "@type": "sc:Layer", 614 | "label": "OCR Article Text" 615 | } 616 | ] 617 | }, 618 | { 619 | "@id":"http://dams.llgc.org.uk/iiif/3320640/article/modsarticle10", 620 | "@type":"sc:Range", 621 | "label":"Advertising", 622 | "metadata": 623 | [ 624 | { 625 | "label": "Article Category", 626 | "value": "Advertising" 627 | } 628 | ], 629 | 630 | "canvases": [ 631 | 632 | 633 | "http://dams.llgc.org.uk/iiif/3320640/canvas/3320643#xywh=10377,546,6693,22560" 634 | 635 | ], 636 | "contentLayer": [ 637 | { 638 | "@id": "http://dams.llgc.org.uk/iiif/3320640/annotation/layer/modsarticle10.json", 639 | "@type": "sc:Layer", 640 | "label": "OCR Article Text" 641 | } 642 | ] 643 | }, 644 | { 645 | "@id":"http://dams.llgc.org.uk/iiif/3320640/article/modsarticle11", 646 | "@type":"sc:Range", 647 | "label":"ODE FOR THE NEW YEAR.", 648 | "metadata": 649 | [ 650 | { 651 | "label": "Article Category", 652 | "value": "News" 653 | } 654 | ], 655 | 656 | "description":"WHEN, at the Despot's dread command, Bridg'd Hellespont his myriads bore From servile Asia's peopled strand To Graecia's and to Freedom's share—", 657 | 658 | "canvases": [ 659 | 660 | 661 | "http://dams.llgc.org.uk/iiif/3320640/canvas/3320644#xywh=549,744,3213,9366" 662 | 663 | ], 664 | "contentLayer": [ 665 | { 666 | "@id": "http://dams.llgc.org.uk/iiif/3320640/annotation/layer/modsarticle11.json", 667 | "@type": "sc:Layer", 668 | "label": "OCR Article Text" 669 | } 670 | ] 671 | }, 672 | { 673 | "@id":"http://dams.llgc.org.uk/iiif/3320640/article/modsarticle12", 674 | "@type":"sc:Range", 675 | "label":"THE CURATE.—A FRAGMENT.", 676 | "metadata": 677 | [ 678 | { 679 | "label": "Article Category", 680 | "value": "News" 681 | } 682 | ], 683 | 684 | "description":"O'ER the pale embers of a-dying fire, His little lampe fed with but little oile, The Curate sat (for scantie was his hire) ;And ruminated sad the morrow's toil.", 685 | 686 | "canvases": [ 687 | 688 | 689 | "http://dams.llgc.org.uk/iiif/3320640/canvas/3320644#xywh=510,10398,3210,8079" 690 | 691 | ], 692 | "contentLayer": [ 693 | { 694 | "@id": "http://dams.llgc.org.uk/iiif/3320640/annotation/layer/modsarticle12.json", 695 | "@type": "sc:Layer", 696 | "label": "OCR Article Text" 697 | } 698 | ] 699 | }, 700 | { 701 | "@id":"http://dams.llgc.org.uk/iiif/3320640/article/modsarticle13", 702 | "@type":"sc:Range", 703 | "label":"VOLUNTEERS.\"", 704 | "metadata": 705 | [ 706 | { 707 | "label": "Article Category", 708 | "value": "News" 709 | } 710 | ], 711 | 712 | "description":"Copy of a circular letter _from Mr. Secretary . Yorke to the Lieutenants in the several counties in Great Britain :-My Lord, \" Whitehall, Jan. 14, 1804.", 713 | 714 | "canvases": [ 715 | 716 | 717 | "http://dams.llgc.org.uk/iiif/3320640/canvas/3320644#xywh=549,576,16377,20817" 718 | 719 | ], 720 | "contentLayer": [ 721 | { 722 | "@id": "http://dams.llgc.org.uk/iiif/3320640/annotation/layer/modsarticle13.json", 723 | "@type": "sc:Layer", 724 | "label": "OCR Article Text" 725 | } 726 | ] 727 | }, 728 | { 729 | "@id":"http://dams.llgc.org.uk/iiif/3320640/article/modsarticle14", 730 | "@type":"sc:Range", 731 | "label":"Advertising", 732 | "metadata": 733 | [ 734 | { 735 | "label": "Article Category", 736 | "value": "Advertising" 737 | } 738 | ], 739 | 740 | "canvases": [ 741 | 742 | 743 | "http://dams.llgc.org.uk/iiif/3320640/canvas/3320644#xywh=660,21486,16311,1770" 744 | 745 | ], 746 | "contentLayer": [ 747 | { 748 | "@id": "http://dams.llgc.org.uk/iiif/3320640/annotation/layer/modsarticle14.json", 749 | "@type": "sc:Layer", 750 | "label": "OCR Article Text" 751 | } 752 | ] 753 | } 754 | ] 755 | } 756 | 757 | --------------------------------------------------------------------------------