├── .codesandbox └── ci.json ├── .github └── workflows │ ├── build-test.yml │ └── release.yml ├── .gitignore ├── .prettierrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── __tests__ ├── image-3-parser │ ├── parameters.test.ts │ └── supports.test.ts ├── presentation-2-parser │ ├── __snapshots__ │ │ └── upgrade.test.ts.snap │ ├── traverse-test.ts │ └── upgrade.test.ts └── presentation-3-parser │ ├── __snapshots__ │ ├── cookbook.tests.ts.snap │ ├── has-part.test.ts.snap │ ├── normalize.test.ts.snap │ └── smoke.tests.ts.snap │ ├── cookbook.tests.ts │ ├── has-part.test.ts │ ├── iiif-traverse.test.ts │ ├── normalize.test.ts │ ├── serializer.test.ts │ ├── smoke.tests.ts │ ├── store-errors.test.ts │ ├── strict-upgrade.test.ts │ ├── to-ref.tests.ts │ └── utilities.test.ts ├── fixtures ├── 2-to-3-converted │ ├── annotation-pages │ │ ├── dzkimgs.l.u-tokyo.ac.jp__iiif__zuzoubu__12b02__list__p0001-0025.json │ │ ├── iiif.io__api__presentation__2.0__example__fixtures__list__65__list1.json │ │ └── ocr.lib.ncsu.edu__ocr__nu__nubian-message-1992-11-30_0010__nubian-message-1992-11-30_0010-annotation-list-paragraph.json │ ├── collections │ │ └── iiif.io__api__presentation__2.1__example__fixtures__collection.json │ └── manifests │ │ ├── british-library-manifest.json │ │ ├── data.getty.edu__museum__api__iiif__298147__manifest.json │ │ ├── data.ucd.ie__api__img__manifests__ucdlib:33064.json │ │ ├── demos.biblissima-condorcet.fr__iiif__metadata__BVMM__chateauroux__manifest.json │ │ ├── dzkimgs.l.u-tokyo.ac.jp__iiif__zuzoubu__12b02__manifest.json │ │ ├── ghent-university-manifest.json │ │ ├── iiif.bodleian.ox.ac.uk__iiif__manifest__60834383-7146-41ab-bfe1-48ee97bc04be.json │ │ ├── iiif.harvardartmuseums.org__manifests__object__299843.json │ │ ├── iiif.io__api__presentation__2.1__example__fixtures__1__manifest.json │ │ ├── iiif.lib.harvard.edu__manifests__drs:48309543.json │ │ ├── lbiiif.riksarkivet.se__arkis!R0000004__manifest.json │ │ ├── manifests.britishart.yale.edu__manifest__1474.json │ │ ├── manifests.ydc2.yale.edu__manifest__Admont43.json │ │ ├── media.nga.gov__public__manifests__nga_highlights.json │ │ ├── ncsu-libraries-manifest.json │ │ ├── nlw-manuscript-manifest.json │ │ ├── nlw-newspaper-manifest.json │ │ ├── princeton-manifest.json │ │ ├── purl.stanford.edu__qm670kv1873__iiif__manifest.json │ │ ├── www.e-codices.unifr.ch__metadata__iiif__csg-0730__manifest.json │ │ ├── www.e-codices.unifr.ch__metadata__iiif__sl-0002__manifest.json │ │ └── www2.dhii.jp__nijl__NIJL0018__099-0014__manifest_tags.json ├── cookbook │ ├── 0001-mvm-image.json │ ├── 0002-mvm-audio.json │ ├── 0003-mvm-video.json │ ├── 0004-canvas-size.json │ ├── 0005-image-service.json │ ├── 0006-text-language.json │ ├── 0007-string-formats.json │ ├── 0008-rights.json │ ├── 0009-book-1.json │ ├── 0010-book-2-viewing-direction-manifest-rtl.json │ ├── 0010-book-2-viewing-direction-manifest-ttb.json │ ├── 0011-book-3-behavior-manifest-continuous.json │ ├── 0011-book-3-behavior-manifest-individuals.json │ ├── 0013-placeholderCanvas.json │ ├── 0014-accompanyingcanvas.json │ ├── 0015-start.json │ ├── 0017-transcription-av.json │ ├── 0019-html-in-annotations.json │ ├── 0021-tagging.json │ ├── 0022-linking-with-a-hotspot.json │ ├── 0024-book-4-toc.json │ ├── 0026-toc-opera.json │ ├── 0029-metadata-anywhere.json │ ├── 0030-multi-volume-collection.json │ ├── 0030-multi-volume-manifest_v1.json │ ├── 0030-multi-volume-manifest_v2.json │ ├── 0031-bound-multivolume.json │ ├── 0032-collection-collection.json │ ├── 0032-collection-manifest-01.json │ ├── 0032-collection-manifest-02.json │ ├── 0033-choice.json │ ├── 0035-foldouts.json │ ├── 0036-composition-from-multiple-images.json │ ├── 0040-image-rotation-service-manifest-css.json │ ├── 0040-image-rotation-service-manifest-service.json │ ├── 0046-rendering.json │ ├── 0047-homepage.json │ ├── 0053-seeAlso.json │ ├── 0064-opera-one-canvas.json │ ├── 0065-opera-multiple-canvases.json │ ├── 0074-multiple-language-captions.json │ ├── 0117-add-image-thumbnail.json │ ├── 0118-multivalue.json │ ├── 0135-annotating-point-in-canvas.json │ ├── 0139-geolocate-canvas-fragment.json │ ├── 0154-geo-extension.json │ ├── 0202-start-canvas.json │ ├── 0219-using-caption-file.json │ ├── 0229-behavior-ranges.json │ ├── 0230-navdate-navdate-collection.json │ ├── 0230-navdate-navdate_map_1-manifest.json │ ├── 0230-navdate-navdate_map_2-manifest.json │ ├── 0234-provider.json │ ├── 0240-navPlace-on-canvases.json │ ├── 0258-tagging-external-resource.json │ ├── 0261-non-rectangular-commenting.json │ ├── 0266-full-canvas-annotation.json │ ├── 0269-embedded-or-referenced-annotations-annotationpage.json │ ├── 0269-embedded-or-referenced-annotations.json │ ├── 0283-missing-image.json │ ├── 0299-region.json │ ├── 0306-linking-annotations-to-manifests-annotationpage.json │ ├── 0306-linking-annotations-to-manifests.json │ ├── 0309-annotation-collection.json │ ├── 0318-navPlace-navDate-collection.json │ ├── 0326-annotating-image-layer.json │ ├── 0346-multilingual-annotation-body.json │ ├── 0377-image-in-annotation.json │ ├── 0434-choice-av.json │ ├── 0485-contentstate-canvas-region-annotation.json │ ├── 0489-multimedia-canvas.json │ ├── 0540-link-for-opening-multiple-canvases-annotation.json │ └── _index.json ├── presentation-2 │ ├── anno_list_choice.json │ ├── artic-manifest.json │ ├── biblissima-manifest.json │ ├── bl-manifest.json │ ├── bodleian-manifest.json │ ├── body-choice.json │ ├── codex.json │ ├── collection-scta.json │ ├── duplicate-member-collection.json │ ├── europeana.json │ ├── folger-manifest.json │ ├── ghent-omeka.json │ ├── ghent.json │ ├── iiif-fixture-annotation-list.json │ ├── iiif-fixture-collection.json │ ├── iiif-fixture-manifest-with-dimensions.json │ ├── iiif-fixture-manifest.json │ ├── loc.json │ ├── manifest-l0.json │ ├── nested-ranges.json │ ├── nga-manifest.json │ ├── nls-collection.json │ ├── nls-manifest-2.json │ ├── nls-manifest.json │ ├── nlw-collection.json │ ├── nlw-manifest.json │ ├── paginated-collection-page.json │ ├── paginated-collection.json │ ├── quatar-manifest.json │ ├── sbb-test.json │ ├── scroll.json │ ├── stanford-manifest.json │ ├── thumbnails.json │ ├── villanova-manifest.json │ ├── wellcome-collection.json │ └── wikimedia-proxy.json ├── presentation-3 │ ├── accompanying-canvas.json │ ├── bl-ranges.json │ ├── bodleian.json │ ├── exhibition-1.json │ ├── ghent-choices.json │ ├── has-part.json │ ├── ocean-liners.json │ ├── specific-resource-infer.json │ ├── start-canvas.json │ ├── wellcome-collection.json │ ├── wellcome-p3-2.json │ └── wellcome-p3.json └── stores │ ├── delft-collection-store.json │ └── wellcome-error.json ├── package.json ├── pkg-tests ├── README.md ├── node-load.cjs ├── node-load.mjs └── node-umd.cjs ├── pnpm-lock.yaml ├── scripts └── update-cookbook.mjs ├── src ├── image-3 │ ├── index.ts │ ├── parser │ │ ├── parse-image-server-from-id.ts │ │ ├── parse-image-service-request.ts │ │ ├── parse-image-service-url.ts │ │ ├── parse-region-parameter.ts │ │ ├── parse-rotation-parameter.ts │ │ └── parse-size-parameter.ts │ ├── profiles │ │ ├── combine-profiles.ts │ │ ├── image-service-supports-format.ts │ │ ├── image-service-supports-request.ts │ │ ├── is-level-0.ts │ │ ├── level-to-profile.ts │ │ ├── profiles.ts │ │ ├── supports-custom-sizes.ts │ │ └── supports.ts │ ├── serialize │ │ ├── image-service-request-info.ts │ │ ├── image-service-request-to-string.ts │ │ ├── region-parameter-to-string.ts │ │ ├── rotation-parameter-to-string.ts │ │ └── size-parameter-to-string.ts │ ├── types.ts │ └── utilities │ │ ├── canonical-service-url.ts │ │ ├── create-image-service-request.ts │ │ ├── extract-fixed-size-scales.ts │ │ ├── fixed-sizes-from-scales.ts │ │ ├── get-id.ts │ │ ├── get-image-service-level.ts │ │ ├── get-image-services.ts │ │ ├── get-type.ts │ │ ├── identify-image-server.ts │ │ ├── is-image-service-level.ts │ │ └── is-image-service.ts ├── index.ts ├── index.umd.ts ├── presentation-2 │ ├── index.ts │ ├── traverse.ts │ └── upgrader.ts ├── presentation-3 │ ├── empty-types.ts │ ├── index.ts │ ├── normalize.ts │ ├── serialize-presentation-2.ts │ ├── serialize-presentation-3.ts │ ├── serialize.ts │ ├── strict-upgrade.ts │ ├── traverse.ts │ └── utilities.ts ├── shared │ ├── compose.ts │ ├── compress-specific-resource.ts │ ├── ensure-array.ts │ ├── expand-target.ts │ ├── image-api-profiles.ts │ ├── is-specific-resource.ts │ ├── remove-undefined-properties.ts │ └── to-ref.ts └── upgrader.ts ├── tsconfig.json ├── tsup.config.ts └── vite.config.ts /.codesandbox/ci.json: -------------------------------------------------------------------------------- 1 | { 2 | "sandboxes": [ 3 | "new", 4 | "simple-parser-2xxljf" 5 | ], 6 | "node": "18" 7 | } 8 | -------------------------------------------------------------------------------- /.github/workflows/build-test.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | node: ['18', '20'] 11 | 12 | name: Node ${{ matrix.node }} build 13 | steps: 14 | - uses: actions/checkout@v1 15 | - name: Setup node 16 | uses: actions/setup-node@v1 17 | with: 18 | node-version: ${{ matrix.node }} 19 | 20 | - uses: pnpm/action-setup@v3 21 | with: 22 | version: 9 23 | run_install: | 24 | - recursive: true 25 | args: [--frozen-lockfile, --strict-peer-dependencies] 26 | 27 | - run: pnpm run build 28 | - run: pnpm run test 29 | - run: pnpm run lint 30 | - run: pnpm run typecheck 31 | - run: node pkg-tests/node-load.cjs 32 | - run: node pkg-tests/node-load.mjs 33 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release NPM package 2 | 3 | on: 4 | push: 5 | tags: ['v*'] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | name: Node build 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Setup node 14 | uses: actions/setup-node@v2 15 | with: 16 | node-version: '18.x' 17 | 18 | - uses: pnpm/action-setup@v3 19 | with: 20 | version: 9 21 | run_install: | 22 | - recursive: true 23 | args: [--frozen-lockfile, --strict-peer-dependencies] 24 | 25 | - run: pnpm build 26 | - run: pnpm run test 27 | - run: pnpm run lint 28 | - run: pnpm run typecheck 29 | 30 | release: 31 | needs: [build] 32 | runs-on: ubuntu-latest 33 | name: Release 34 | steps: 35 | - uses: actions/checkout@v2 36 | - name: Setup node 37 | uses: actions/setup-node@v2 38 | with: 39 | node-version: '18.x' 40 | 41 | - name: Set tag 42 | id: tagName 43 | run: echo ::set-output name=tag::${GITHUB_REF#refs/*/} 44 | 45 | - uses: pnpm/action-setup@v3 46 | with: 47 | version: 9 48 | run_install: | 49 | - recursive: true 50 | args: [--frozen-lockfile, --strict-peer-dependencies] 51 | 52 | - uses: JS-DevTools/npm-publish@v1 53 | with: 54 | token: ${{ secrets.NPM_TOKEN }} 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | .build 3 | .idea 4 | node_modules 5 | coverage 6 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "printWidth": 120, 4 | "trailingComma": "es5" 5 | } 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 IIIF Commons 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /__tests__/presentation-2-parser/traverse-test.ts: -------------------------------------------------------------------------------- 1 | import iiifCollection from '../../fixtures/presentation-2/iiif-fixture-collection.json'; 2 | import iiifManifest from '../../fixtures/presentation-2/iiif-fixture-manifest.json'; 3 | import europeana from '../../fixtures/presentation-2/europeana.json'; 4 | import { Traverse } from '../../src/presentation-2'; 5 | 6 | describe('Presentation 2 Traverse', () => { 7 | test('traverse simple collection', () => { 8 | const ids: string[] = []; 9 | const manifestIds = []; 10 | const traverse = new Traverse({ 11 | collection: [ 12 | (collection) => { 13 | ids.push(collection['@id']); 14 | return collection; 15 | }, 16 | ], 17 | manifest: [ 18 | (manifest) => { 19 | manifestIds.push(manifest['@id']); 20 | return manifest; 21 | }, 22 | ], 23 | }); 24 | 25 | traverse.traverseCollection(iiifCollection); 26 | 27 | expect(ids).toEqual(['http://iiif.io/api/presentation/2.1/example/fixtures/collection.json']); 28 | expect(manifestIds.length).toEqual(55); 29 | }); 30 | 31 | test('traverse simple manifest', () => { 32 | const ids: any[] = []; 33 | function trackId(type: string) { 34 | return (item: any) => { 35 | ids.push({ id: item['@id'], type }); 36 | }; 37 | } 38 | const traverse = new Traverse({ 39 | manifest: [trackId('manifest')], 40 | sequence: [trackId('sequence')], 41 | canvas: [trackId('canvas')], 42 | contentResource: [trackId('contentResource')], 43 | annotation: [trackId('annotation')], 44 | }); 45 | 46 | traverse.traverseManifest(iiifManifest); 47 | 48 | expect(ids).toEqual([ 49 | { 50 | id: 'http://iiif.io/api/presentation/2.1/example/fixtures/resources/page1-full.png', 51 | type: 'contentResource', 52 | }, 53 | { 54 | // No id, but it traversed it. 55 | type: 'annotation', 56 | }, 57 | { 58 | id: 'http://iiif.io/api/presentation/2.1/example/fixtures/canvas/1/c1.json', 59 | type: 'canvas', 60 | }, 61 | { 62 | // No id, but it traversed it. 63 | type: 'sequence', 64 | }, 65 | { 66 | id: 'http://iiif.io/api/presentation/2.1/example/fixtures/1/manifest.json', 67 | type: 'manifest', 68 | }, 69 | ]); 70 | }); 71 | 72 | test('bug: cannot find other content', () => { 73 | const ids: any[] = []; 74 | function trackId(type: string) { 75 | return (item: any) => { 76 | ids.push({ id: item['@id'], type }); 77 | }; 78 | } 79 | const traverse = new Traverse({ 80 | annotationList: [trackId('annotationList')], 81 | }); 82 | 83 | traverse.traverseCanvas(europeana as any); 84 | 85 | expect(ids).toEqual([ 86 | { 87 | id: 'https://iiif.europeana.eu/presentation/9200396/BibliographicResource_3000118436165/annopage/21', 88 | type: 'annotationList', 89 | }, 90 | ]); 91 | }); 92 | }); 93 | -------------------------------------------------------------------------------- /__tests__/presentation-3-parser/cookbook.tests.ts: -------------------------------------------------------------------------------- 1 | import cookbookIndex from '../../fixtures/cookbook/_index.json'; 2 | import { promises } from 'node:fs'; 3 | import { cwd } from 'node:process'; 4 | import { join } from 'path'; 5 | const { readFile } = promises; 6 | import { normalize, serialize, serializeConfigPresentation3 } from '../../src'; 7 | 8 | const prWaitingForMerge: string[] = [ 9 | // These 3 have uncompressed SpecificResources. Manually tested, that's the only deviation. 10 | '0022-linking-with-a-hotspot', 11 | '0540-link-for-opening-multiple-canvases-annotation', 12 | '0485-contentstate-canvas-region-annotation', 13 | ]; 14 | 15 | describe('Cookbook', function () { 16 | const tests = Object.values(cookbookIndex) 17 | .filter((item: any) => prWaitingForMerge.indexOf(item.id) === -1) 18 | .map((item) => [item.id, item.url]); 19 | 20 | 21 | test.each(tests)('Testing normalize %p (%p)', async (id, url) => { 22 | const json = await readFile(join(cwd(), 'fixtures/cookbook', `${id}.json`)); 23 | const jsonString = json.toString(); 24 | const manifest = JSON.parse(jsonString); 25 | const original = JSON.parse(jsonString); 26 | const result = normalize(manifest); 27 | expect(result).toMatchSnapshot(); 28 | 29 | const reserialized = serialize( 30 | { 31 | mapping: result.mapping, 32 | entities: result.entities, 33 | requests: {}, 34 | }, 35 | result.resource, 36 | serializeConfigPresentation3 37 | ); 38 | expect(reserialized).toMatchSnapshot(); 39 | 40 | expect(reserialized).toEqual(original); 41 | 42 | // Immutability: 43 | // expect(manifest).toEqual(original); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /__tests__/presentation-3-parser/store-errors.test.ts: -------------------------------------------------------------------------------- 1 | import { serialize, serializeConfigPresentation3 } from '../../src'; 2 | import { expect } from 'vitest'; 3 | 4 | describe('store errors', () => { 5 | test('Wellcome store error', async () => { 6 | const store = await import('../../fixtures/stores/wellcome-error.json'); 7 | 8 | const serialized = serialize( 9 | { 10 | mapping: store.iiif.mapping, 11 | entities: store.iiif.entities, 12 | requests: {}, 13 | }, 14 | { 15 | id: 'https://iiif.wellcomecollection.org/presentation/b12024673', 16 | type: 'Manifest', 17 | }, 18 | serializeConfigPresentation3 19 | ); 20 | 21 | expect(serialized).toHaveProperty('type'); 22 | expect(serialized).toHaveProperty('id'); 23 | expect((serialized as any).id).toMatchInlineSnapshot( 24 | '"https://iiif.wellcomecollection.org/presentation/b12024673"' 25 | ); 26 | }); 27 | test('Delft collection store error', async () => { 28 | const store = await import('../../fixtures/stores/delft-collection-store.json'); 29 | 30 | const serialized = serialize( 31 | { 32 | mapping: store.iiif.mapping, 33 | entities: store.iiif.entities, 34 | requests: {}, 35 | }, 36 | { 37 | id: 'https://delft-static-site-generator.netlify.com/collections/lib-tr-universiteitsgeschiedenis', 38 | type: 'Collection', 39 | }, 40 | serializeConfigPresentation3 41 | ); 42 | 43 | expect(serialized).toHaveProperty('type'); 44 | expect(serialized).toHaveProperty('id'); 45 | expect((serialized as any)).toMatchInlineSnapshot( 46 | 47 | ` 48 | { 49 | "@context": "http://iiif.io/api/presentation/3/context.json", 50 | "id": "https://delft-static-site-generator.netlify.com/collections/lib-tr-universiteitsgeschiedenis", 51 | "label": { 52 | "en": [ 53 | "History of the university", 54 | ], 55 | "nl": [ 56 | "Universiteitsgeschiedenis", 57 | ], 58 | }, 59 | "metadata": [ 60 | { 61 | "label": { 62 | "none": [ 63 | "", 64 | ], 65 | }, 66 | "value": { 67 | "none": [ 68 | "", 69 | ], 70 | }, 71 | }, 72 | ], 73 | "summary": { 74 | "en": [ 75 | "Digitised books about the history of Delft University of Technology, from TU Delft Library's Special Collections", 76 | ], 77 | "nl": [ 78 | "Gedigitaliseerde boeken over de geschiedenis van de TU Delft, afkomstig uit de Bijzondere Collecties van de TU Delft Library", 79 | ], 80 | }, 81 | "type": "Collection", 82 | } 83 | `); 84 | }); 85 | }); 86 | -------------------------------------------------------------------------------- /__tests__/presentation-3-parser/to-ref.tests.ts: -------------------------------------------------------------------------------- 1 | import { toRef } from '../../src/shared/to-ref'; 2 | 3 | describe('toRef()', function () { 4 | const supportedRefs = [ 5 | // 6 | ['https://example.org/manifest-1', 'Manifest'], 7 | [{ id: 'https://example.org/manifest-1', type: 'unknown' }, 'Manifest'], 8 | [{ id: 'https://example.org/manifest-1', type: 'Manifest' }, undefined], 9 | [{ id: 'https://example.org/manifest-1', type: 'Manifest', label: { en: ['Not seen'] } }, undefined], 10 | [{ '@id': 'https://example.org/manifest-1', '@type': 'Manifest' }, undefined], 11 | [{ '@id': 'https://example.org/manifest-1', '@type': 'sc:Manifest' }, undefined], 12 | [{ type: 'SpecificResource', source: 'https://example.org/manifest-1' }, 'Manifest'], 13 | [{ type: 'SpecificResource', source: { id: 'https://example.org/manifest-1', type: 'Manifest' } }], 14 | ]; 15 | 16 | test.each(supportedRefs as [any, any][])('Testing toRef(%s, %s)', async (ref, type) => { 17 | expect(toRef(ref, type)).toEqual({ id: 'https://example.org/manifest-1', type: 'Manifest' }); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /__tests__/presentation-3-parser/utilities.test.ts: -------------------------------------------------------------------------------- 1 | import { compressSpecificResource } from '../../src/shared/compress-specific-resource'; 2 | import { SpecificResource } from '@iiif/presentation-3'; 3 | import { frameResource, WILDCARD } from '../../src'; 4 | 5 | describe('Misc Utilites', function () { 6 | test('compressSpecificResource', () => { 7 | const state: SpecificResource = { 8 | id: 'https://iiif.io/api/cookbook/recipe/0015-start/canvas-start/segment1', 9 | type: 'SpecificResource', 10 | source: { 11 | id: 'https://iiif.io/api/cookbook/recipe/0015-start/canvas/segment1', 12 | type: 'Canvas', 13 | }, 14 | selector: { type: 'PointSelector', t: 120.5 }, 15 | }; 16 | 17 | expect(compressSpecificResource(state, { allowSourceString: false })).toMatchInlineSnapshot(` 18 | { 19 | "id": "https://iiif.io/api/cookbook/recipe/0015-start/canvas-start/segment1", 20 | "selector": { 21 | "t": 120.5, 22 | "type": "PointSelector", 23 | }, 24 | "source": { 25 | "id": "https://iiif.io/api/cookbook/recipe/0015-start/canvas/segment1", 26 | "type": "Canvas", 27 | }, 28 | "type": "SpecificResource", 29 | } 30 | `); 31 | }); 32 | test('compressSpecificResource (allowString=true)', () => { 33 | const state: SpecificResource = { 34 | id: 'https://iiif.io/api/cookbook/recipe/0015-start/canvas-start/segment1', 35 | type: 'SpecificResource', 36 | source: { 37 | id: 'https://iiif.io/api/cookbook/recipe/0015-start/canvas/segment1', 38 | type: 'Canvas', 39 | }, 40 | selector: { type: 'PointSelector', t: 120.5 }, 41 | }; 42 | 43 | expect(compressSpecificResource(state, { allowSourceString: true })).toMatchInlineSnapshot(` 44 | { 45 | "id": "https://iiif.io/api/cookbook/recipe/0015-start/canvas-start/segment1", 46 | "selector": { 47 | "t": 120.5, 48 | "type": "PointSelector", 49 | }, 50 | "source": "https://iiif.io/api/cookbook/recipe/0015-start/canvas/segment1", 51 | "type": "SpecificResource", 52 | } 53 | `); 54 | }); 55 | 56 | test('compressSpecificResource (single content resource)', () => { 57 | const state: SpecificResource = { 58 | type: 'SpecificResource', 59 | source: { 60 | id: 'https://exameple.org/link-to-something', 61 | type: 'ContentResource', 62 | }, 63 | }; 64 | 65 | const compressed = compressSpecificResource(state, { allowSourceString: true, allowString: false }); 66 | 67 | expect(compressed).toMatchInlineSnapshot(` 68 | { 69 | "source": "https://exameple.org/link-to-something", 70 | "type": "SpecificResource", 71 | } 72 | `); 73 | }); 74 | 75 | test('frameResource', () => { 76 | const resource = { 77 | id: 'test', 78 | type: 'test-type', 79 | nested: { id: 'something', type: 'something-else' }, 80 | ignored: 'not included', 81 | }; 82 | 83 | expect(frameResource(resource, {})).toEqual(resource); 84 | expect( 85 | frameResource(resource, { 86 | '@explicit': true, 87 | id: {}, 88 | type: {}, 89 | }) 90 | ).toEqual({ 91 | id: 'test', 92 | type: 'test-type', 93 | }); 94 | expect( 95 | frameResource(resource, { 96 | '@explicit': true, 97 | id: {}, 98 | type: {}, 99 | override: 'concrete value', 100 | }) 101 | ).toEqual({ 102 | id: 'test', 103 | type: 'test-type', 104 | override: 'concrete value', 105 | }); 106 | expect( 107 | frameResource(resource, { 108 | '@explicit': true, 109 | id: {}, 110 | type: {}, 111 | nested: {}, 112 | }) 113 | ).toEqual({ 114 | id: 'test', 115 | type: 'test-type', 116 | nested: { id: 'something', type: 'something-else' }, 117 | }); 118 | }); 119 | }); 120 | -------------------------------------------------------------------------------- /fixtures/2-to-3-converted/annotation-pages/dzkimgs.l.u-tokyo.ac.jp__iiif__zuzoubu__12b02__list__p0001-0025.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "AnnotationPage", 3 | "id": "https://dzkimgs.l.u-tokyo.ac.jp/iiif/zuzoubu/12b02/list/p0001-0025.json", 4 | "items": [ 5 | { 6 | "motivation": "classifying", 7 | "type": "Annotation", 8 | "id": "https://dzkimgs.l.u-tokyo.ac.jp/iiif/zuzoubu/12b02/p0001-0025/1", 9 | "target": "https://dzkimgs.l.u-tokyo.ac.jp/iiif/zuzoubu/12b02/p0001-0025#xywh=9912,13519,2583,2663", 10 | "body": { 11 | "@id": "https://dzkimgs.l.u-tokyo.ac.jp/iiif/zuzoubu/12b02/p0001-0025/1/1", 12 | "language": "ja", 13 | "format": "text/html", 14 | "type": "TextualBody", 15 | "value": "
大日如來
(仏・如來)
目の数: 二目 : 白毫無 持物: 輪宝 , 臂数: 二臂 台座: 蓮華 , 光背: 炎光 , 顔の向き: 正面
タグ付け担当: 永崎研宣
" 16 | } 17 | }, 18 | { 19 | "motivation": "classifying", 20 | "type": "Annotation", 21 | "id": "https://dzkimgs.l.u-tokyo.ac.jp/iiif/zuzoubu/12b02/p0001-0025/2", 22 | "target": "https://dzkimgs.l.u-tokyo.ac.jp/iiif/zuzoubu/12b02/p0001-0025#xywh=8457,11791,5583,6317", 23 | "body": { 24 | "@id": "https://dzkimgs.l.u-tokyo.ac.jp/iiif/zuzoubu/12b02/p0001-0025/2/1", 25 | "language": "ja", 26 | "format": "text/html", 27 | "type": "TextualBody", 28 | "value": "
八葉院
(曼荼羅)
目の数: 二目 : 白毫無 臂数: 二臂
タグ付け担当: 永崎研宣
" 29 | } 30 | } 31 | ], 32 | "@context": [ 33 | "http://www.w3.org/ns/anno.jsonld", 34 | "http://iiif.io/api/presentation/3/context.json" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /fixtures/2-to-3-converted/annotation-pages/iiif.io__api__presentation__2.0__example__fixtures__list__65__list1.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": { 3 | "@none": ["Test 65 List 1"] 4 | }, 5 | "type": "AnnotationPage", 6 | "id": "http://iiif.io/api/presentation/2.0/example/fixtures/list/65/list1.json", 7 | "items": [ 8 | { 9 | "motivation": "painting", 10 | "type": "Annotation", 11 | "id": "https://example.org/uuid/12badf4b-9e14-4076-a63b-cb5fb4cc2e22", 12 | "target": { 13 | "selector": { 14 | "value": "xywh=225,70,750,150", 15 | "type": "FragmentSelector" 16 | }, 17 | "type": "SpecificResource", 18 | "source": { 19 | "label": { 20 | "@none": ["Test 65 Canvas: 1"] 21 | }, 22 | "type": "Canvas", 23 | "id": "http://iiif.io/api/presentation/2.0/example/fixtures/canvas/65/c1.json" 24 | } 25 | }, 26 | "body": { 27 | "type": "TextualBody", 28 | "value": "Top of First Page to Display" 29 | } 30 | } 31 | ], 32 | "@context": [ 33 | "http://www.w3.org/ns/anno.jsonld", 34 | "http://iiif.io/api/presentation/3/context.json" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /fixtures/2-to-3-converted/manifests/dzkimgs.l.u-tokyo.ac.jp__iiif__zuzoubu__12b02__manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": { 3 | "@none": ["大正新脩大藏經図像部第12b02巻"] 4 | }, 5 | "metadata": [ 6 | { 7 | "label": { 8 | "@none": ["Author"] 9 | }, 10 | "value": { 11 | "@none": ["高楠順次郎"] 12 | } 13 | }, 14 | { 15 | "label": { 16 | "@none": ["published"] 17 | }, 18 | "value": { 19 | "ja": ["大蔵出版"] 20 | } 21 | }, 22 | { 23 | "label": { 24 | "@none": ["Source"] 25 | }, 26 | "value": { 27 | "@none": ["大正新脩大藏經 図像部"] 28 | } 29 | }, 30 | { 31 | "label": { 32 | "@none": ["manifest URI"] 33 | }, 34 | "value": { 35 | "@none": [ 36 | "https://dzkimgs.l.u-tokyo.ac.jp/iiif/zuzoubu/12b02/manifest.json" 37 | ] 38 | } 39 | }, 40 | { 41 | "label": { 42 | "@none": ["Description"] 43 | }, 44 | "value": { 45 | "@none": ["大正新脩大藏經図像部"] 46 | } 47 | } 48 | ], 49 | "viewingDirection": "right-to-left", 50 | "logo": [ 51 | { 52 | "id": "https://dzkimgs.l.u-tokyo.ac.jp/iiif/zuzoubu/satlogo80.png", 53 | "type": "Image" 54 | } 55 | ], 56 | "type": "Manifest", 57 | "id": "https://dzkimgs.l.u-tokyo.ac.jp/iiif/zuzoubu/12b02/manifest.json", 58 | "rights": "http://creativecommons.org/licenses/by-sa/4.0/", 59 | "requiredStatement": { 60 | "label": { 61 | "@none": ["Attribution"] 62 | }, 63 | "value": { 64 | "@none": [ 65 | "大蔵出版(Daizo shuppan) and SAT大蔵経テキストデータベース研究会(SAT Daizōkyō Text Database Committee) " 66 | ] 67 | } 68 | }, 69 | "behavior": ["paged"], 70 | "items": [ 71 | { 72 | "label": { 73 | "@none": [""] 74 | }, 75 | "width": 22779, 76 | "height": 30000, 77 | "type": "Canvas", 78 | "id": "https://dzkimgs.l.u-tokyo.ac.jp/iiif/zuzoubu/12b02/p0001-0025", 79 | "annotations": [ 80 | { 81 | "type": "AnnotationPage", 82 | "id": "https://dzkimgs.l.u-tokyo.ac.jp/iiif/zuzoubu/12b02/list/p0001-0025.json" 83 | } 84 | ], 85 | "items": [ 86 | { 87 | "type": "AnnotationPage", 88 | "items": [ 89 | { 90 | "motivation": "painting", 91 | "type": "Annotation", 92 | "id": "https://dzkimgs.l.u-tokyo.ac.jp/iiif/zuzoubu/12b02/ano0001-0025", 93 | "target": "https://dzkimgs.l.u-tokyo.ac.jp/iiif/zuzoubu/12b02/p0001-0025", 94 | "body": { 95 | "format": "image/jpeg", 96 | "width": 22779, 97 | "height": 30000, 98 | "service": [ 99 | { 100 | "profile": "http://iiif.io/api/image/2/level1.json", 101 | "id": "https://dzkimgs.l.u-tokyo.ac.jp/iiifimgs/zuzoubu/12b02/0001-0025.tif", 102 | "type": "ImageService2" 103 | } 104 | ], 105 | "type": "Image", 106 | "id": "https://dzkimgs.l.u-tokyo.ac.jp/iiifimgs/zuzoubu/12b02/0001-0025.tif/full/full/0/default.jpg" 107 | } 108 | } 109 | ], 110 | "id": "https://example.org/uuid/3c1e463e-7ce0-46e2-b91d-e21786168d7c" 111 | } 112 | ] 113 | } 114 | ], 115 | "@context": [ 116 | "http://www.w3.org/ns/anno.jsonld", 117 | "http://iiif.io/api/presentation/3/context.json" 118 | ] 119 | } 120 | -------------------------------------------------------------------------------- /fixtures/2-to-3-converted/manifests/iiif.io__api__presentation__2.1__example__fixtures__1__manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": { 3 | "@none": ["Test 1 Manifest: Minimum Required Fields"] 4 | }, 5 | "type": "Manifest", 6 | "id": "http://iiif.io/api/presentation/2.1/example/fixtures/1/manifest.json", 7 | "partOf": [ 8 | { 9 | "id": "http://iiif.io/api/presentation/2.1/example/fixtures/collection.json", 10 | "type": "Collection" 11 | } 12 | ], 13 | "items": [ 14 | { 15 | "label": { 16 | "@none": ["Test 1 Canvas: 1"] 17 | }, 18 | "height": 1800, 19 | "width": 1200, 20 | "type": "Canvas", 21 | "id": "http://iiif.io/api/presentation/2.1/example/fixtures/canvas/1/c1.json", 22 | "items": [ 23 | { 24 | "type": "AnnotationPage", 25 | "items": [ 26 | { 27 | "motivation": "painting", 28 | "type": "Annotation", 29 | "id": "https://example.org/uuid/3644c005-bf7a-48d0-9fa9-1e1757bf8df1", 30 | "target": "http://iiif.io/api/presentation/2.1/example/fixtures/canvas/1/c1.json", 31 | "body": { 32 | "height": 1800, 33 | "width": 1200, 34 | "type": "Image", 35 | "id": "http://iiif.io/api/presentation/2.1/example/fixtures/resources/page1-full.png" 36 | } 37 | } 38 | ], 39 | "id": "https://example.org/uuid/1dad04b3-79c6-4a97-a831-634f2ee50a26" 40 | } 41 | ] 42 | } 43 | ], 44 | "@context": [ 45 | "http://www.w3.org/ns/anno.jsonld", 46 | "http://iiif.io/api/presentation/3/context.json" 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /fixtures/cookbook/0001-mvm-image.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/3/context.json", 3 | "id": "https://iiif.io/api/cookbook/recipe/0001-mvm-image/manifest.json", 4 | "type": "Manifest", 5 | "label": { 6 | "en": [ 7 | "Single Image Example" 8 | ] 9 | }, 10 | "items": [ 11 | { 12 | "id": "https://iiif.io/api/cookbook/recipe/0001-mvm-image/canvas/p1", 13 | "type": "Canvas", 14 | "height": 1800, 15 | "width": 1200, 16 | "items": [ 17 | { 18 | "id": "https://iiif.io/api/cookbook/recipe/0001-mvm-image/page/p1/1", 19 | "type": "AnnotationPage", 20 | "items": [ 21 | { 22 | "id": "https://iiif.io/api/cookbook/recipe/0001-mvm-image/annotation/p0001-image", 23 | "type": "Annotation", 24 | "motivation": "painting", 25 | "body": { 26 | "id": "http://iiif.io/api/presentation/2.1/example/fixtures/resources/page1-full.png", 27 | "type": "Image", 28 | "format": "image/png", 29 | "height": 1800, 30 | "width": 1200 31 | }, 32 | "target": "https://iiif.io/api/cookbook/recipe/0001-mvm-image/canvas/p1" 33 | } 34 | ] 35 | } 36 | ] 37 | } 38 | ] 39 | } -------------------------------------------------------------------------------- /fixtures/cookbook/0002-mvm-audio.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/3/context.json", 3 | "id": "https://iiif.io/api/cookbook/recipe/0002-mvm-audio/manifest.json", 4 | "type": "Manifest", 5 | "label": { 6 | "en": [ 7 | "Simplest Audio Example 1" 8 | ] 9 | }, 10 | "items": [ 11 | { 12 | "id": "https://iiif.io/api/cookbook/recipe/0002-mvm-audio/canvas", 13 | "type": "Canvas", 14 | "duration": 1985.024, 15 | "items": [ 16 | { 17 | "id": "https://iiif.io/api/cookbook/recipe/0002-mvm-audio/canvas/page", 18 | "type": "AnnotationPage", 19 | "items": [ 20 | { 21 | "id": "https://iiif.io/api/cookbook/recipe/0002-mvm-audio/canvas/page/annotation", 22 | "type": "Annotation", 23 | "motivation": "painting", 24 | "body": { 25 | "id": "https://fixtures.iiif.io/audio/indiana/mahler-symphony-3/CD1/medium/128Kbps.mp4", 26 | "type": "Sound", 27 | "format": "audio/mp4", 28 | "duration": 1985.024 29 | }, 30 | "target": "https://iiif.io/api/cookbook/recipe/0002-mvm-audio/canvas" 31 | } 32 | ] 33 | } 34 | ] 35 | } 36 | ] 37 | } -------------------------------------------------------------------------------- /fixtures/cookbook/0003-mvm-video.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/3/context.json", 3 | "id": "https://iiif.io/api/cookbook/recipe/0003-mvm-video/manifest.json", 4 | "type": "Manifest", 5 | "label": { 6 | "en": [ 7 | "Video Example 3" 8 | ] 9 | }, 10 | "items": [ 11 | { 12 | "id": "https://iiif.io/api/cookbook/recipe/0003-mvm-video/canvas", 13 | "type": "Canvas", 14 | "height": 360, 15 | "width": 480, 16 | "duration": 572.034, 17 | "items": [ 18 | { 19 | "id": "https://iiif.io/api/cookbook/recipe/0003-mvm-video/canvas/page", 20 | "type": "AnnotationPage", 21 | "items": [ 22 | { 23 | "id": "https://iiif.io/api/cookbook/recipe/0003-mvm-video/canvas/page/annotation", 24 | "type": "Annotation", 25 | "motivation": "painting", 26 | "body": { 27 | "id": "https://fixtures.iiif.io/video/indiana/lunchroom_manners/high/lunchroom_manners_1024kb.mp4", 28 | "type": "Video", 29 | "height": 360, 30 | "width": 480, 31 | "duration": 572.034, 32 | "format": "video/mp4" 33 | }, 34 | "target": "https://iiif.io/api/cookbook/recipe/0003-mvm-video/canvas" 35 | } 36 | ] 37 | } 38 | ] 39 | } 40 | ] 41 | } -------------------------------------------------------------------------------- /fixtures/cookbook/0004-canvas-size.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/3/context.json", 3 | "id": "https://iiif.io/api/cookbook/recipe/0004-canvas-size/manifest.json", 4 | "type": "Manifest", 5 | "label": { 6 | "en": [ 7 | "Still image from an opera performance at Indiana University" 8 | ] 9 | }, 10 | "items": [ 11 | { 12 | "id": "https://iiif.io/api/cookbook/recipe/0004-canvas-size/canvas/p1", 13 | "type": "Canvas", 14 | "height": 1080, 15 | "width": 1920, 16 | "items": [ 17 | { 18 | "id": "https://iiif.io/api/cookbook/recipe/0004-canvas-size/page/p1/1", 19 | "type": "AnnotationPage", 20 | "items": [ 21 | { 22 | "id": "https://iiif.io/api/cookbook/recipe/0004-canvas-size/annotation/p0001-image", 23 | "type": "Annotation", 24 | "motivation": "painting", 25 | "body": { 26 | "id": "https://fixtures.iiif.io/video/indiana/donizetti-elixir/act1-thumbnail.png", 27 | "type": "Image", 28 | "format": "image/png", 29 | "height": 360, 30 | "width": 640 31 | }, 32 | "target": "https://iiif.io/api/cookbook/recipe/0004-canvas-size/canvas/p1" 33 | } 34 | ] 35 | } 36 | ] 37 | } 38 | ] 39 | } -------------------------------------------------------------------------------- /fixtures/cookbook/0005-image-service.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/3/context.json", 3 | "id": "https://iiif.io/api/cookbook/recipe/0005-image-service/manifest.json", 4 | "type": "Manifest", 5 | "label": { 6 | "en": [ 7 | "Picture of Göttingen taken during the 2019 IIIF Conference" 8 | ] 9 | }, 10 | "items": [ 11 | { 12 | "id": "https://iiif.io/api/cookbook/recipe/0005-image-service/canvas/p1", 13 | "type": "Canvas", 14 | "label": { 15 | "en": [ 16 | "Canvas with a single IIIF image" 17 | ] 18 | }, 19 | "height": 3024, 20 | "width": 4032, 21 | "items": [ 22 | { 23 | "id": "https://iiif.io/api/cookbook/recipe/0005-image-service/page/p1/1", 24 | "type": "AnnotationPage", 25 | "items": [ 26 | { 27 | "id": "https://iiif.io/api/cookbook/recipe/0005-image-service/annotation/p0001-image", 28 | "type": "Annotation", 29 | "motivation": "painting", 30 | "body": { 31 | "id": "https://iiif.io/api/image/3.0/example/reference/918ecd18c2592080851777620de9bcb5-gottingen/full/max/0/default.jpg", 32 | "type": "Image", 33 | "format": "image/jpeg", 34 | "height": 3024, 35 | "width": 4032, 36 | "service": [ 37 | { 38 | "id": "https://iiif.io/api/image/3.0/example/reference/918ecd18c2592080851777620de9bcb5-gottingen", 39 | "profile": "level1", 40 | "type": "ImageService3" 41 | } 42 | ] 43 | }, 44 | "target": "https://iiif.io/api/cookbook/recipe/0005-image-service/canvas/p1" 45 | } 46 | ] 47 | } 48 | ] 49 | } 50 | ] 51 | } -------------------------------------------------------------------------------- /fixtures/cookbook/0006-text-language.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/3/context.json", 3 | "id": "https://iiif.io/api/cookbook/recipe/0006-text-language/manifest.json", 4 | "type": "Manifest", 5 | "label": { 6 | "en": [ 7 | "Whistler's Mother" 8 | ], 9 | "fr": [ 10 | "La Mère de Whistler" 11 | ] 12 | }, 13 | "metadata": [ 14 | { 15 | "label": { 16 | "en": [ 17 | "Creator" 18 | ], 19 | "fr": [ 20 | "Auteur" 21 | ] 22 | }, 23 | "value": { 24 | "none": [ 25 | "Whistler, James Abbott McNeill" 26 | ] 27 | } 28 | }, 29 | { 30 | "label": { 31 | "en": [ 32 | "Subject" 33 | ], 34 | "fr": [ 35 | "Sujet" 36 | ] 37 | }, 38 | "value": { 39 | "en": [ 40 | "McNeill Anna Matilda, mother of Whistler (1804-1881)" 41 | ], 42 | "fr": [ 43 | "McNeill Anna Matilda, mère de Whistler (1804-1881)" 44 | ] 45 | } 46 | } 47 | ], 48 | "summary": { 49 | "en": [ 50 | "Arrangement in Grey and Black No. 1, also called Portrait of the Artist's Mother." 51 | ], 52 | "fr": [ 53 | "Arrangement en gris et noir n°1, also called Portrait de la mère de l'artiste." 54 | ] 55 | }, 56 | "requiredStatement": { 57 | "label": { 58 | "en": [ 59 | "Held By" 60 | ], 61 | "fr": [ 62 | "Détenu par" 63 | ] 64 | }, 65 | "value": { 66 | "none": [ 67 | "Musée d'Orsay, Paris, France" 68 | ] 69 | } 70 | }, 71 | "items": [ 72 | { 73 | "id": "https://iiif.io/api/cookbook/recipe/0006-text-language/canvas/p1", 74 | "type": "Canvas", 75 | "width": 1114, 76 | "height": 991, 77 | "items": [ 78 | { 79 | "id": "https://iiif.io/api/cookbook/recipe/0006-text-language/page/p1/1", 80 | "type": "AnnotationPage", 81 | "items": [ 82 | { 83 | "id": "https://iiif.io/api/cookbook/recipe/0006-text-language/annotation/p0001-image", 84 | "type": "Annotation", 85 | "motivation": "painting", 86 | "body": { 87 | "id": "https://iiif.io/api/image/3.0/example/reference/329817fc8a251a01c393f517d8a17d87-Whistlers_Mother/full/max/0/default.jpg", 88 | "type": "Image", 89 | "format": "image/jpeg", 90 | "width": 1114, 91 | "height": 991, 92 | "service": [ 93 | { 94 | "id": "https://iiif.io/api/image/3.0/example/reference/329817fc8a251a01c393f517d8a17d87-Whistlers_Mother", 95 | "profile": "level1", 96 | "type": "ImageService3" 97 | } 98 | ] 99 | }, 100 | "target": "https://iiif.io/api/cookbook/recipe/0006-text-language/canvas/p1" 101 | } 102 | ] 103 | } 104 | ] 105 | } 106 | ] 107 | } -------------------------------------------------------------------------------- /fixtures/cookbook/0007-string-formats.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/3/context.json", 3 | "id": "https://iiif.io/api/cookbook/recipe/0007-string-formats/manifest.json", 4 | "type": "Manifest", 5 | "label": { 6 | "en": [ 7 | "Picture of Göttingen taken during the 2019 IIIF Conference" 8 | ] 9 | }, 10 | "summary": { 11 | "en": [ 12 | "

Picture taken by the IIIF Technical Coordinator

" 13 | ] 14 | }, 15 | "metadata": [ 16 | { 17 | "label": { 18 | "en": [ 19 | "Author" 20 | ] 21 | }, 22 | "value": { 23 | "none": [ 24 | "Glen Robson" 25 | ] 26 | } 27 | } 28 | ], 29 | "rights": "http://creativecommons.org/licenses/by-sa/3.0/", 30 | "requiredStatement": { 31 | "label": { 32 | "en": [ 33 | "Attribution" 34 | ] 35 | }, 36 | "value": { 37 | "en": [ 38 | "Glen Robson, IIIF Technical Coordinator. CC BY-SA 3.0 " 39 | ] 40 | } 41 | }, 42 | "items": [ 43 | { 44 | "id": "https://iiif.io/api/cookbook/recipe/0007-string-formats/canvas/p1", 45 | "type": "Canvas", 46 | "height": 3024, 47 | "width": 4032, 48 | "items": [ 49 | { 50 | "id": "https://iiif.io/api/cookbook/recipe/0007-string-formats/page/p1/1", 51 | "type": "AnnotationPage", 52 | "items": [ 53 | { 54 | "id": "https://iiif.io/api/cookbook/recipe/0007-string-formats/annotation/p0001-image", 55 | "type": "Annotation", 56 | "motivation": "painting", 57 | "body": { 58 | "id": "https://iiif.io/api/image/3.0/example/reference/918ecd18c2592080851777620de9bcb5-gottingen/full/max/0/default.jpg", 59 | "type": "Image", 60 | "format": "image/jpeg", 61 | "height": 3024, 62 | "width": 4032, 63 | "service": [ 64 | { 65 | "id": "https://iiif.io/api/image/3.0/example/reference/918ecd18c2592080851777620de9bcb5-gottingen", 66 | "profile": "level1", 67 | "type": "ImageService3" 68 | } 69 | ] 70 | }, 71 | "target": "https://iiif.io/api/cookbook/recipe/0007-string-formats/canvas/p1" 72 | } 73 | ] 74 | } 75 | ] 76 | } 77 | ] 78 | } -------------------------------------------------------------------------------- /fixtures/cookbook/0008-rights.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/3/context.json", 3 | "id": "https://iiif.io/api/cookbook/recipe/0008-rights/manifest.json", 4 | "type": "Manifest", 5 | "label": { 6 | "en": [ 7 | "Picture of Göttingen taken during the 2019 IIIF Conference" 8 | ] 9 | }, 10 | "summary": { 11 | "en": [ 12 | "

Picture taken by the IIIF Technical Coordinator

" 13 | ] 14 | }, 15 | "rights": "http://creativecommons.org/licenses/by-sa/3.0/", 16 | "requiredStatement": { 17 | "label": { 18 | "en": [ 19 | "Attribution" 20 | ] 21 | }, 22 | "value": { 23 | "en": [ 24 | "Glen Robson, IIIF Technical Coordinator. CC BY-SA 3.0 " 25 | ] 26 | } 27 | }, 28 | "items": [ 29 | { 30 | "id": "https://iiif.io/api/cookbook/recipe/0008-rights/canvas/p1", 31 | "type": "Canvas", 32 | "height": 3024, 33 | "width": 4032, 34 | "items": [ 35 | { 36 | "id": "https://iiif.io/api/cookbook/recipe/0008-rights/page/p1/1", 37 | "type": "AnnotationPage", 38 | "items": [ 39 | { 40 | "id": "https://iiif.io/api/cookbook/recipe/0008-rights/annotation/p0001-image", 41 | "type": "Annotation", 42 | "motivation": "painting", 43 | "body": { 44 | "id": "https://iiif.io/api/image/3.0/example/reference/918ecd18c2592080851777620de9bcb5-gottingen/full/max/0/default.jpg", 45 | "type": "Image", 46 | "format": "image/jpeg", 47 | "height": 3024, 48 | "width": 4032, 49 | "service": [ 50 | { 51 | "id": "https://iiif.io/api/image/3.0/example/reference/918ecd18c2592080851777620de9bcb5-gottingen", 52 | "profile": "level1", 53 | "type": "ImageService3" 54 | } 55 | ] 56 | }, 57 | "target": "https://iiif.io/api/cookbook/recipe/0008-rights/canvas/p1" 58 | } 59 | ] 60 | } 61 | ] 62 | } 63 | ] 64 | } -------------------------------------------------------------------------------- /fixtures/cookbook/0013-placeholderCanvas.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/3/context.json", 3 | "id": "https://iiif.io/api/cookbook/recipe/0013-placeholderCanvas/manifest.json", 4 | "type": "Manifest", 5 | "label": { 6 | "en": [ 7 | "Video recording of Donizetti's _The Elixer of Love_" 8 | ] 9 | }, 10 | "items": [ 11 | { 12 | "id": "https://iiif.io/api/cookbook/recipe/0013-placeholderCanvas/canvas/donizetti", 13 | "type": "Canvas", 14 | "duration": 7278.466, 15 | "width": 640, 16 | "height": 360, 17 | "placeholderCanvas": { 18 | "id": "https://iiif.io/api/cookbook/recipe/0013-placeholderCanvas/canvas/donizetti/placeholder", 19 | "type": "Canvas", 20 | "width": 640, 21 | "height": 360, 22 | "items": [ 23 | { 24 | "id": "https://iiif.io/api/cookbook/recipe/0013-placeholderCanvas/canvas/donizetti/placeholder/1", 25 | "type": "AnnotationPage", 26 | "items": [ 27 | { 28 | "id": "https://iiif.io/api/cookbook/recipe/0013-placeholderCanvas/canvas/donizetti/placeholder/1-image", 29 | "type": "Annotation", 30 | "motivation": "painting", 31 | "body": { 32 | "id": "https://fixtures.iiif.io/video/indiana/donizetti-elixir/act1-thumbnail.png", 33 | "type": "Image", 34 | "format": "image/png", 35 | "width": 640, 36 | "height": 360 37 | }, 38 | "target": "https://iiif.io/api/cookbook/recipe/0013-placeholderCanvas/canvas/donizetti/placeholder" 39 | } 40 | ] 41 | } 42 | ] 43 | }, 44 | "items": [ 45 | { 46 | "id": "https://iiif.io/api/cookbook/recipe/0013-placeholderCanvas/donizetti/1", 47 | "type": "AnnotationPage", 48 | "items": [ 49 | { 50 | "id": "https://iiif.io/api/cookbook/recipe/0013-placeholderCanvas/donizetti/1-video", 51 | "type": "Annotation", 52 | "motivation": "painting", 53 | "body": { 54 | "id": "https://fixtures.iiif.io/video/indiana/donizetti-elixir/vae0637_accessH264_low.mp4", 55 | "type": "Video", 56 | "duration": 7278.466, 57 | "width": 640, 58 | "height": 360, 59 | "format": "video/mp4" 60 | }, 61 | "target": "https://iiif.io/api/cookbook/recipe/0013-placeholderCanvas/canvas/donizetti" 62 | } 63 | ] 64 | } 65 | ] 66 | } 67 | ] 68 | } -------------------------------------------------------------------------------- /fixtures/cookbook/0014-accompanyingcanvas.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/3/context.json", 3 | "id": "https://iiif.io/api/cookbook/recipe/0014-accompanyingcanvas/manifest.json", 4 | "type": "Manifest", 5 | "label": { 6 | "en": [ 7 | "Partial audio recording of Gustav Mahler's _Symphony No. 3_" 8 | ] 9 | }, 10 | "items": [ 11 | { 12 | "id": "https://iiif.io/api/cookbook/recipe/0014-accompanyingcanvas/canvas/p1", 13 | "type": "Canvas", 14 | "label": { 15 | "en": [ 16 | "Gustav Mahler, Symphony No. 3, CD 1" 17 | ] 18 | }, 19 | "duration": 1985.024, 20 | "accompanyingCanvas": { 21 | "id": "https://iiif.io/api/cookbook/recipe/0014-accompanyingcanvas/canvas/accompanying", 22 | "type": "Canvas", 23 | "label": { 24 | "en": [ 25 | "First page of score for Gustav Mahler, Symphony No. 3" 26 | ] 27 | }, 28 | "height": 998, 29 | "width": 772, 30 | "items": [ 31 | { 32 | "id": "https://iiif.io/api/cookbook/recipe/0014-accompanyingcanvas/canvas/accompanying/annotation/page", 33 | "type": "AnnotationPage", 34 | "items": [ 35 | { 36 | "id": "https://iiif.io/api/cookbook/recipe/0014-accompanyingcanvas/canvas/accompanying/annotation/image", 37 | "type": "Annotation", 38 | "motivation": "painting", 39 | "body": { 40 | "id": "https://iiif.io/api/image/3.0/example/reference/4b45bba3ea612ee46f5371ce84dbcd89-mahler-0/full/,998/0/default.jpg", 41 | "type": "Image", 42 | "format": "image/jpeg", 43 | "height": 998, 44 | "width": 772, 45 | "service": [ 46 | { 47 | "id": "https://iiif.io/api/image/3.0/example/reference/4b45bba3ea612ee46f5371ce84dbcd89-mahler-0", 48 | "type": "ImageService3", 49 | "profile": "level1" 50 | } 51 | ] 52 | }, 53 | "target": "https://iiif.io/api/cookbook/recipe/0014-accompanyingcanvas/canvas/accompanying" 54 | } 55 | ] 56 | } 57 | ] 58 | }, 59 | "items": [ 60 | { 61 | "id": "https://iiif.io/api/cookbook/recipe/0014-accompanyingcanvas/canvas/page/p1", 62 | "type": "AnnotationPage", 63 | "items": [ 64 | { 65 | "id": "https://iiif.io/api/cookbook/recipe/0014-accompanyingcanvas/canvas/page/annotation/segment1-audio", 66 | "type": "Annotation", 67 | "motivation": "painting", 68 | "body": { 69 | "id": "https://fixtures.iiif.io/audio/indiana/mahler-symphony-3/CD1/medium/128Kbps.mp4", 70 | "type": "Sound", 71 | "duration": 1985.024, 72 | "format": "video/mp4" 73 | }, 74 | "target": "https://iiif.io/api/cookbook/recipe/0014-accompanyingcanvas/canvas/p1" 75 | } 76 | ] 77 | } 78 | ] 79 | } 80 | ] 81 | } -------------------------------------------------------------------------------- /fixtures/cookbook/0015-start.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/3/context.json", 3 | "id": "https://iiif.io/api/cookbook/recipe/0015-start/manifest.json", 4 | "type": "Manifest", 5 | "label": { 6 | "en": [ 7 | "Video of a 30-minute digital clock" 8 | ] 9 | }, 10 | "start": { 11 | "id": "https://iiif.io/api/cookbook/recipe/0015-start/canvas-start/segment1", 12 | "type": "SpecificResource", 13 | "source": "https://iiif.io/api/cookbook/recipe/0015-start/canvas/segment1", 14 | "selector": { 15 | "type": "PointSelector", 16 | "t": 120.5 17 | } 18 | }, 19 | "rights": "http://creativecommons.org/licenses/by/3.0/", 20 | "requiredStatement": { 21 | "label": { 22 | "en": [ 23 | "Attribution" 24 | ] 25 | }, 26 | "value": { 27 | "en": [ 28 | "The video was created by DrLex1 and was released using a Creative Commons Attribution license" 29 | ] 30 | } 31 | }, 32 | "items": [ 33 | { 34 | "id": "https://iiif.io/api/cookbook/recipe/0015-start/canvas/segment1", 35 | "type": "Canvas", 36 | "duration": 1801.055, 37 | "items": [ 38 | { 39 | "id": "https://iiif.io/api/cookbook/recipe/0015-start/annotation/segment1/page", 40 | "type": "AnnotationPage", 41 | "items": [ 42 | { 43 | "id": "https://iiif.io/api/cookbook/recipe/0015-start/annotation/segment1-video", 44 | "type": "Annotation", 45 | "motivation": "painting", 46 | "body": { 47 | "id": "https://fixtures.iiif.io/video/indiana/30-minute-clock/medium/30-minute-clock.mp4", 48 | "type": "Video", 49 | "duration": 1801.055, 50 | "format": "video/mp4" 51 | }, 52 | "target": "https://iiif.io/api/cookbook/recipe/0015-start/canvas/segment1" 53 | } 54 | ] 55 | } 56 | ] 57 | } 58 | ] 59 | } -------------------------------------------------------------------------------- /fixtures/cookbook/0017-transcription-av.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/3/context.json", 3 | "id": "https://iiif.io/api/cookbook/recipe/0017-transcription-av/manifest.json", 4 | "type": "Manifest", 5 | "label": { 6 | "en": [ 7 | "Volleyball for Boys" 8 | ] 9 | }, 10 | "items": [ 11 | { 12 | "id": "https://iiif.io/api/cookbook/recipe/0017-transcription-av/canvas", 13 | "type": "Canvas", 14 | "height": 1080, 15 | "width": 1920, 16 | "duration": 662.037, 17 | "items": [ 18 | { 19 | "id": "https://iiif.io/api/cookbook/recipe/0017-transcription-av/canvas/page", 20 | "type": "AnnotationPage", 21 | "items": [ 22 | { 23 | "id": "https://iiif.io/api/cookbook/recipe/0017-transcription-av/canvas/page/annotation", 24 | "type": "Annotation", 25 | "motivation": "painting", 26 | "target": "https://iiif.io/api/cookbook/recipe/0017-transcription-av/canvas", 27 | "body": { 28 | "id": "https://fixtures.iiif.io/video/indiana/volleyball/high/volleyball-for-boys.mp4", 29 | "type": "Video", 30 | "format": "video/mp4", 31 | "height": 1080, 32 | "width": 1920, 33 | "duration": 662.037 34 | } 35 | } 36 | ] 37 | } 38 | ], 39 | "rendering": [ 40 | { 41 | "id": "https://fixtures.iiif.io/video/indiana/volleyball/volleyball.txt", 42 | "type": "Text", 43 | "label": { 44 | "en": [ 45 | "Transcript" 46 | ] 47 | }, 48 | "format": "text/plain" 49 | } 50 | ] 51 | } 52 | ] 53 | } -------------------------------------------------------------------------------- /fixtures/cookbook/0019-html-in-annotations.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/3/context.json", 3 | "id": "https://iiif.io/api/cookbook/recipe/0019-html-in-annotations/manifest.json", 4 | "type": "Manifest", 5 | "label": { 6 | "en": [ 7 | "Picture of Göttingen taken during the 2019 IIIF Conference" 8 | ] 9 | }, 10 | "items": [ 11 | { 12 | "id": "https://iiif.io/api/cookbook/recipe/0019-html-in-annotations/canvas-1", 13 | "type": "Canvas", 14 | "height": 3024, 15 | "width": 4032, 16 | "items": [ 17 | { 18 | "id": "https://iiif.io/api/cookbook/recipe/0019-html-in-annotations/canvas-1/annopage-1", 19 | "type": "AnnotationPage", 20 | "items": [ 21 | { 22 | "id": "https://iiif.io/api/cookbook/recipe/0019-html-in-annotations/canvas-1/annopage-1/anno-1", 23 | "type": "Annotation", 24 | "motivation": "painting", 25 | "body": { 26 | "id": "https://iiif.io/api/image/3.0/example/reference/918ecd18c2592080851777620de9bcb5-gottingen/full/max/0/default.jpg", 27 | "type": "Image", 28 | "format": "image/jpeg", 29 | "height": 3024, 30 | "width": 4032, 31 | "service": [ 32 | { 33 | "id": "https://iiif.io/api/image/3.0/example/reference/918ecd18c2592080851777620de9bcb5-gottingen", 34 | "profile": "level1", 35 | "type": "ImageService3" 36 | } 37 | ] 38 | }, 39 | "target": "https://iiif.io/api/cookbook/recipe/0019-html-in-annotations/canvas-1" 40 | } 41 | ] 42 | } 43 | ], 44 | "annotations": [ 45 | { 46 | "id": "https://iiif.io/api/cookbook/recipe/0019-html-in-annotations/canvas-1/annopage-2", 47 | "type": "AnnotationPage", 48 | "items": [ 49 | { 50 | "id": "https://iiif.io/api/cookbook/recipe/0019-html-in-annotations/canvas-1/annopage-2/anno-1", 51 | "type": "Annotation", 52 | "motivation": "commenting", 53 | "body": { 54 | "type": "TextualBody", 55 | "language": "de", 56 | "format": "text/html", 57 | "value": "

Göttinger Marktplatz mit Gänseliesel Brunnen Wikipedia logo

" 58 | }, 59 | "target": "https://iiif.io/api/cookbook/recipe/0019-html-in-annotations/canvas-1" 60 | } 61 | ] 62 | } 63 | ] 64 | } 65 | ] 66 | } -------------------------------------------------------------------------------- /fixtures/cookbook/0021-tagging.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/3/context.json", 3 | "id": "https://iiif.io/api/cookbook/recipe/0021-tagging/manifest.json", 4 | "type": "Manifest", 5 | "label": { 6 | "en": [ 7 | "Picture of Göttingen taken during the 2019 IIIF Conference" 8 | ] 9 | }, 10 | "items": [ 11 | { 12 | "id": "https://iiif.io/api/cookbook/recipe/0021-tagging/canvas/p1", 13 | "type": "Canvas", 14 | "height": 3024, 15 | "width": 4032, 16 | "items": [ 17 | { 18 | "id": "https://iiif.io/api/cookbook/recipe/0021-tagging/page/p1/1", 19 | "type": "AnnotationPage", 20 | "items": [ 21 | { 22 | "id": "https://iiif.io/api/cookbook/recipe/0021-tagging/annotation/p0001-image", 23 | "type": "Annotation", 24 | "motivation": "painting", 25 | "body": { 26 | "id": "https://iiif.io/api/image/3.0/example/reference/918ecd18c2592080851777620de9bcb5-gottingen/full/max/0/default.jpg", 27 | "type": "Image", 28 | "format": "image/jpeg", 29 | "height": 3024, 30 | "width": 4032, 31 | "service": [ 32 | { 33 | "id": "https://iiif.io/api/image/3.0/example/reference/918ecd18c2592080851777620de9bcb5-gottingen", 34 | "profile": "level1", 35 | "type": "ImageService3" 36 | } 37 | ] 38 | }, 39 | "target": "https://iiif.io/api/cookbook/recipe/0021-tagging/canvas/p1" 40 | } 41 | ] 42 | } 43 | ], 44 | "annotations": [ 45 | { 46 | "id": "https://iiif.io/api/cookbook/recipe/0021-tagging/page/p2/1", 47 | "type": "AnnotationPage", 48 | "items": [ 49 | { 50 | "id": "https://iiif.io/api/cookbook/recipe/0021-tagging/annotation/p0002-tag", 51 | "type": "Annotation", 52 | "motivation": "tagging", 53 | "body": { 54 | "type": "TextualBody", 55 | "value": "Gänseliesel-Brunnen", 56 | "language": "de", 57 | "format": "text/plain" 58 | }, 59 | "target": "https://iiif.io/api/cookbook/recipe/0021-tagging/canvas/p1#xywh=265,661,1260,1239" 60 | } 61 | ] 62 | } 63 | ] 64 | } 65 | ] 66 | } -------------------------------------------------------------------------------- /fixtures/cookbook/0026-toc-opera.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/3/context.json", 3 | "id": "https://iiif.io/api/cookbook/recipe/0026-toc-opera/manifest.json", 4 | "type": "Manifest", 5 | "label": { 6 | "it": [ 7 | "L'Elisir D'Amore" 8 | ], 9 | "en": [ 10 | "The Elixir of Love" 11 | ] 12 | }, 13 | "items": [ 14 | { 15 | "id": "https://iiif.io/api/cookbook/recipe/0026-toc-opera/canvas/1", 16 | "type": "Canvas", 17 | "width": 1920, 18 | "height": 1080, 19 | "duration": 7278.422, 20 | "items": [ 21 | { 22 | "id": "https://iiif.io/api/cookbook/recipe/0026-toc-opera/canvas/1/annotation_page/1", 23 | "type": "AnnotationPage", 24 | "items": [ 25 | { 26 | "id": "https://iiif.io/api/cookbook/recipe/0026-toc-opera/canvas/1/annotation_page/1/annotation/1", 27 | "type": "Annotation", 28 | "motivation": "painting", 29 | "target": "https://iiif.io/api/cookbook/recipe/0026-toc-opera/canvas/1", 30 | "body": { 31 | "id": "https://fixtures.iiif.io/video/indiana/donizetti-elixir/vae0637_accessH264_low.mp4", 32 | "type": "Video", 33 | "format": "video/mp4", 34 | "height": 1080, 35 | "width": 1920, 36 | "duration": 7278.422 37 | } 38 | } 39 | ] 40 | } 41 | ] 42 | } 43 | ], 44 | "structures": [ 45 | { 46 | "type": "Range", 47 | "id": "https://iiif.io/api/cookbook/recipe/0026-toc-opera/range/1", 48 | "label": { 49 | "it": [ 50 | "Gaetano Donizetti, L'Elisir D'Amore" 51 | ] 52 | }, 53 | "items": [ 54 | { 55 | "type": "Range", 56 | "id": "https://iiif.io/api/cookbook/recipe/0026-toc-opera/range/2", 57 | "label": { 58 | "it": [ 59 | "Atto Primo" 60 | ] 61 | }, 62 | "items": [ 63 | { 64 | "type": "Range", 65 | "id": "https://iiif.io/api/cookbook/recipe/0026-toc-opera/range/3", 66 | "label": { 67 | "it": [ 68 | "Preludio e Coro d'introduzione – Bel conforto al mietitore" 69 | ] 70 | }, 71 | "items": [ 72 | { 73 | "type": "Canvas", 74 | "id": "https://iiif.io/api/cookbook/recipe/0026-toc-opera/canvas/1#t=0,302.05" 75 | } 76 | ] 77 | }, 78 | { 79 | "type": "Range", 80 | "id": "https://iiif.io/api/cookbook/recipe/0026-toc-opera/range/4", 81 | "label": { 82 | "en": [ 83 | "Remainder of Atto Primo" 84 | ] 85 | }, 86 | "items": [ 87 | { 88 | "type": "Canvas", 89 | "id": "https://iiif.io/api/cookbook/recipe/0026-toc-opera/canvas/1#t=302.05,3971.24" 90 | } 91 | ] 92 | } 93 | ] 94 | }, 95 | { 96 | "type": "Range", 97 | "id": "https://iiif.io/api/cookbook/recipe/0026-toc-opera/range/5", 98 | "label": { 99 | "it": [ 100 | "Atto Secondo" 101 | ] 102 | }, 103 | "items": [ 104 | { 105 | "type": "Canvas", 106 | "id": "https://iiif.io/api/cookbook/recipe/0026-toc-opera/canvas/1#t=3971.24" 107 | } 108 | ] 109 | } 110 | ] 111 | } 112 | ] 113 | } -------------------------------------------------------------------------------- /fixtures/cookbook/0030-multi-volume-collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/3/context.json", 3 | "id": "https://iiif.io/api/cookbook/recipe/0030-multi-volume/collection.json", 4 | "type": "Collection", 5 | "label": { 6 | "jp": [ 7 | "青楼絵本年中行事 [Seirō ehon nenjū gyōji]" 8 | ] 9 | }, 10 | "behavior": [ 11 | "multi-part" 12 | ], 13 | "items": [ 14 | { 15 | "id": "https://iiif.io/api/cookbook/recipe/0030-multi-volume/manifest_v1.json", 16 | "type": "Manifest", 17 | "label": { 18 | "jp": [ 19 | "巻 1 [Vol. 1]" 20 | ] 21 | } 22 | }, 23 | { 24 | "id": "https://iiif.io/api/cookbook/recipe/0030-multi-volume/manifest_v2.json", 25 | "type": "Manifest", 26 | "label": { 27 | "jp": [ 28 | "巻 2 [Vol. 2]" 29 | ] 30 | } 31 | } 32 | ] 33 | } -------------------------------------------------------------------------------- /fixtures/cookbook/0032-collection-collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/3/context.json", 3 | "id": "https://iiif.io/api/cookbook/recipe/0032-collection/collection.json", 4 | "type": "Collection", 5 | "label": { 6 | "en": [ 7 | "Simple Collection Example" 8 | ] 9 | }, 10 | "items": [ 11 | { 12 | "id": "https://iiif.io/api/cookbook/recipe/0032-collection/manifest-01.json", 13 | "type": "Manifest", 14 | "label": { 15 | "en": [ 16 | "The Gulf Stream" 17 | ] 18 | } 19 | }, 20 | { 21 | "id": "https://iiif.io/api/cookbook/recipe/0032-collection/manifest-02.json", 22 | "type": "Manifest", 23 | "label": { 24 | "en": [ 25 | "Northeaster" 26 | ] 27 | } 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /fixtures/cookbook/0032-collection-manifest-01.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/3/context.json", 3 | "id": "https://iiif.io/api/cookbook/recipe/0032-collection/manifest-01.json", 4 | "type": "Manifest", 5 | "label": { 6 | "en": [ 7 | "The Gulf Stream" 8 | ] 9 | }, 10 | "metadata": [ 11 | { 12 | "label": { 13 | "en": [ 14 | "Artist" 15 | ] 16 | }, 17 | "value": { 18 | "en": [ 19 | "Winslow Homer (1836–1910)" 20 | ] 21 | } 22 | }, 23 | { 24 | "label": { 25 | "en": [ 26 | "Date" 27 | ] 28 | }, 29 | "value": { 30 | "en": [ 31 | "1899" 32 | ] 33 | } 34 | } 35 | ], 36 | "items": [ 37 | { 38 | "id": "https://iiif.io/api/cookbook/recipe/0032-collection/manifest/1/canvas/p1", 39 | "type": "Canvas", 40 | "height": 3540, 41 | "width": 5886, 42 | "items": [ 43 | { 44 | "id": "https://iiif.io/api/cookbook/recipe/0032-collection/manifest/1/page/p1/1", 45 | "type": "AnnotationPage", 46 | "items": [ 47 | { 48 | "id": "https://iiif.io/api/cookbook/recipe/0032-collection/manifest/1/annotation/p0001-image", 49 | "type": "Annotation", 50 | "motivation": "painting", 51 | "body": { 52 | "id": "https://iiif.io/api/image/3.0/example/reference/329817fc8a251a01c393f517d8a17d87-Winslow_Homer_-_The_Gulf_Stream_-_Metropolitan_Museum_of_Art/full/max/0/default.jpg", 53 | "type": "Image", 54 | "format": "image/jpeg", 55 | "height": 3540, 56 | "width": 5886, 57 | "service": [ 58 | { 59 | "id": "https://iiif.io/api/image/3.0/example/reference/329817fc8a251a01c393f517d8a17d87-Winslow_Homer_-_The_Gulf_Stream_-_Metropolitan_Museum_of_Art", 60 | "profile": "level1", 61 | "type": "ImageService3" 62 | } 63 | ] 64 | }, 65 | "target": "https://iiif.io/api/cookbook/recipe/0032-collection/manifest/1/canvas/p1" 66 | } 67 | ] 68 | } 69 | ] 70 | } 71 | ] 72 | } -------------------------------------------------------------------------------- /fixtures/cookbook/0032-collection-manifest-02.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/3/context.json", 3 | "id": "https://iiif.io/api/cookbook/recipe/0032-collection/manifest-02.json", 4 | "type": "Manifest", 5 | "label": { 6 | "en": [ 7 | "Northeaster" 8 | ] 9 | }, 10 | "metadata": [ 11 | { 12 | "label": { 13 | "en": [ 14 | "Artist" 15 | ] 16 | }, 17 | "value": { 18 | "en": [ 19 | "Winslow Homer (1836–1910)" 20 | ] 21 | } 22 | }, 23 | { 24 | "label": { 25 | "en": [ 26 | "Date" 27 | ] 28 | }, 29 | "value": { 30 | "en": [ 31 | "1895" 32 | ] 33 | } 34 | } 35 | ], 36 | "items": [ 37 | { 38 | "id": "https://iiif.io/api/cookbook/recipe/0032-collection/manifest/2/canvas/p1", 39 | "type": "Canvas", 40 | "height": 2572, 41 | "width": 3764, 42 | "items": [ 43 | { 44 | "id": "https://iiif.io/api/cookbook/recipe/0032-collection/manifest/2/page/p1/1", 45 | "type": "AnnotationPage", 46 | "items": [ 47 | { 48 | "id": "https://iiif.io/api/cookbook/recipe/0032-collection/manifest/2/annotation/p0001-image", 49 | "type": "Annotation", 50 | "motivation": "painting", 51 | "body": { 52 | "id": "https://iiif.io/api/image/3.0/example/reference/329817fc8a251a01c393f517d8a17d87-Northeaster_by_Winslow_Homer_1895/full/max/0/default.jpg", 53 | "type": "Image", 54 | "format": "image/jpeg", 55 | "height": 2572, 56 | "width": 3764, 57 | "service": [ 58 | { 59 | "id": "https://iiif.io/api/image/3.0/example/reference/329817fc8a251a01c393f517d8a17d87-Northeaster_by_Winslow_Homer_1895", 60 | "profile": "level1", 61 | "type": "ImageService3" 62 | } 63 | ] 64 | }, 65 | "target": "https://iiif.io/api/cookbook/recipe/0032-collection/manifest/2/canvas/p1" 66 | } 67 | ] 68 | } 69 | ] 70 | } 71 | ] 72 | } -------------------------------------------------------------------------------- /fixtures/cookbook/0033-choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/3/context.json", 3 | "id": "https://iiif.io/api/cookbook/recipe/0033-choice/manifest.json", 4 | "type": "Manifest", 5 | "label": { 6 | "en": [ 7 | "John Dee performing an experiment before Queen Elizabeth I." 8 | ] 9 | }, 10 | "items": [ 11 | { 12 | "id": "https://iiif.io/api/cookbook/recipe/0033-choice/canvas/p1", 13 | "type": "Canvas", 14 | "height": 1271, 15 | "width": 2000, 16 | "items": [ 17 | { 18 | "id": "https://iiif.io/api/cookbook/recipe/0033-choice/page/p1/1", 19 | "type": "AnnotationPage", 20 | "items": [ 21 | { 22 | "id": "https://iiif.io/api/cookbook/recipe/0033-choice/annotation/p0001-image", 23 | "type": "Annotation", 24 | "motivation": "painting", 25 | "body": { 26 | "type": "Choice", 27 | "items": [ 28 | { 29 | "id": "https://iiif.io/api/image/3.0/example/reference/421e65be2ce95439b3ad6ef1f2ab87a9-dee-natural/full/max/0/default.jpg", 30 | "type": "Image", 31 | "format": "image/jpeg", 32 | "width": 2000, 33 | "height": 1271, 34 | "label": { 35 | "en": [ 36 | "Natural Light" 37 | ] 38 | }, 39 | "service": [ 40 | { 41 | "id": "https://iiif.io/api/image/3.0/example/reference/421e65be2ce95439b3ad6ef1f2ab87a9-dee-natural", 42 | "type": "ImageService3", 43 | "profile": "level1" 44 | } 45 | ] 46 | }, 47 | { 48 | "id": "https://iiif.io/api/image/3.0/example/reference/421e65be2ce95439b3ad6ef1f2ab87a9-dee-xray/full/max/0/default.jpg", 49 | "type": "Image", 50 | "format": "image/jpeg", 51 | "width": 2000, 52 | "height": 1271, 53 | "label": { 54 | "en": [ 55 | "X-Ray" 56 | ] 57 | }, 58 | "service": [ 59 | { 60 | "id": "https://iiif.io/api/image/3.0/example/reference/421e65be2ce95439b3ad6ef1f2ab87a9-dee-xray", 61 | "type": "ImageService3", 62 | "profile": "level1" 63 | } 64 | ] 65 | } 66 | ] 67 | }, 68 | "target": "https://iiif.io/api/cookbook/recipe/0033-choice/canvas/p1" 69 | } 70 | ] 71 | } 72 | ] 73 | } 74 | ] 75 | } -------------------------------------------------------------------------------- /fixtures/cookbook/0036-composition-from-multiple-images.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/3/context.json", 3 | "id": "https://iiif.io/api/cookbook/recipe/0036-composition-from-multiple-images/manifest.json", 4 | "type": "Manifest", 5 | "label": { 6 | "en": [ 7 | "Folio from Grandes Chroniques de France, ca. 1460" 8 | ] 9 | }, 10 | "items": [ 11 | { 12 | "id": "https://iiif.io/api/cookbook/recipe/0036-composition-from-multiple-images/canvas/p1", 13 | "type": "Canvas", 14 | "label": { 15 | "none": [ 16 | "f. 033v-034r [Chilpéric Ier tue Galswinthe, se remarie et est assassiné]" 17 | ] 18 | }, 19 | "height": 5412, 20 | "width": 7216, 21 | "items": [ 22 | { 23 | "id": "https://iiif.io/api/cookbook/recipe/0036-composition-from-multiple-images/page/p1/1", 24 | "type": "AnnotationPage", 25 | "items": [ 26 | { 27 | "id": "https://iiif.io/api/cookbook/recipe/0036-composition-from-multiple-images/annotation/p0001-image", 28 | "type": "Annotation", 29 | "motivation": "painting", 30 | "body": { 31 | "id": "https://iiif.io/api/image/3.0/example/reference/899da506920824588764bc12b10fc800-bnf_chateauroux/full/max/0/default.jpg", 32 | "type": "Image", 33 | "format": "image/jpeg", 34 | "height": 5412, 35 | "width": 7216, 36 | "service": [ 37 | { 38 | "id": "https://iiif.io/api/image/3.0/example/reference/899da506920824588764bc12b10fc800-bnf_chateauroux", 39 | "type": "ImageService3", 40 | "profile": "level1" 41 | } 42 | ] 43 | }, 44 | "target": "https://iiif.io/api/cookbook/recipe/0036-composition-from-multiple-images/canvas/p1" 45 | }, 46 | { 47 | "id": "https://iiif.io/api/cookbook/recipe/0036-composition-from-multiple-images/annotation/p0002-image", 48 | "type": "Annotation", 49 | "motivation": "painting", 50 | "body": { 51 | "id": "https://iiif.io/api/image/3.0/example/reference/899da506920824588764bc12b10fc800-bnf_chateauroux_miniature/full/max/0/default.jpg", 52 | "type": "Image", 53 | "format": "image/jpeg", 54 | "label": { 55 | "fr": [ 56 | "Miniature [Chilpéric Ier tue Galswinthe, se remarie et est assassiné]" 57 | ] 58 | }, 59 | "width": 2138, 60 | "height": 2414, 61 | "service": [ 62 | { 63 | "id": "https://iiif.io/api/image/3.0/example/reference/899da506920824588764bc12b10fc800-bnf_chateauroux_miniature", 64 | "type": "ImageService3", 65 | "profile": "level1" 66 | } 67 | ] 68 | }, 69 | "target": "https://iiif.io/api/cookbook/recipe/0036-composition-from-multiple-images/canvas/p1#xywh=3949,994,1091,1232" 70 | } 71 | ] 72 | } 73 | ] 74 | } 75 | ] 76 | } -------------------------------------------------------------------------------- /fixtures/cookbook/0040-image-rotation-service-manifest-css.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/3/context.json", 3 | "id": "https://iiif.io/api/cookbook/recipe/0040-image-rotation-service/manifest-css.json", 4 | "type": "Manifest", 5 | "label": { 6 | "ca": [ 7 | "[Conoximent de las orines] Ihesus, Ihesus. En nom de Deu et dela beneyeta sa mare e de tots los angels i archangels e de tots los sants e santes de paradis yo micer Johannes comense aquest libre de reseptes en l’ayn Mi 466." 8 | ] 9 | }, 10 | "items": [ 11 | { 12 | "id": "https://iiif.io/api/cookbook/recipe/0040-image-rotation-service/canvas/p1", 13 | "type": "Canvas", 14 | "label": { 15 | "en": [ 16 | "inside cover; 1r" 17 | ] 18 | }, 19 | "width": 2105, 20 | "height": 1523, 21 | "items": [ 22 | { 23 | "id": "https://iiif.io/api/cookbook/recipe/0040-image-rotation-service/p1/1", 24 | "type": "AnnotationPage", 25 | "items": [ 26 | { 27 | "id": "https://iiif.io/api/cookbook/recipe/0040-image-rotation-service/annotation/v0001-image", 28 | "type": "Annotation", 29 | "motivation": "painting", 30 | "stylesheet": { 31 | "type": "CssStylesheet", 32 | "value": ".rotated { transform-origin: center; transform: rotate(90deg); }" 33 | }, 34 | "body": { 35 | "id": "https://iiif.io/api/cookbook/recipe/0040-image-rotation-service/body/sr1", 36 | "type": "SpecificResource", 37 | "styleClass": "rotated", 38 | "source": { 39 | "id": "https://iiif.io/api/image/3.0/example/reference/85a96c630f077e6ac6cb984f1b752bbf-0-21198-zz00022840-1-page1/full/max/0/default.jpg", 40 | "type": "Image", 41 | "format": "image/jpeg", 42 | "width": 1523, 43 | "height": 2105, 44 | "service": [ 45 | { 46 | "id": "https://iiif.io/api/image/3.0/example/reference/85a96c630f077e6ac6cb984f1b752bbf-0-21198-zz00022840-1-master", 47 | "type": "ImageService3", 48 | "profile": "level1" 49 | } 50 | ] 51 | } 52 | }, 53 | "target": "https://iiif.io/api/cookbook/recipe/0040-image-rotation-service/canvas/p1" 54 | } 55 | ] 56 | } 57 | ] 58 | } 59 | ] 60 | } -------------------------------------------------------------------------------- /fixtures/cookbook/0040-image-rotation-service-manifest-service.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/3/context.json", 3 | "id": "https://iiif.io/api/cookbook/recipe/0040-image-rotation-service/manifest-service.json ", 4 | "type": "Manifest", 5 | "label": { 6 | "ca": [ 7 | "[Conoximent de las orines] Ihesus, Ihesus. En nom de Deu et dela beneyeta sa mare e de tots los angels i archangels e de tots los sants e santes de paradis yo micer Johannes comense aquest libre de reseptes en l’ayn Mi 466." 8 | ] 9 | }, 10 | "items": [ 11 | { 12 | "id": "https://iiif.io/api/cookbook/recipe/0040-image-rotation-service/canvas/p1", 13 | "type": "Canvas", 14 | "label": { 15 | "en": [ 16 | "inside cover; 1r" 17 | ] 18 | }, 19 | "width": 2105, 20 | "height": 1523, 21 | "items": [ 22 | { 23 | "id": "https://iiif.io/api/cookbook/recipe/0040-image-rotation-service/p1/1", 24 | "type": "AnnotationPage", 25 | "items": [ 26 | { 27 | "id": "https://iiif.io/api/cookbook/recipe/0040-image-rotation-service/annotation/v0001-image", 28 | "type": "Annotation", 29 | "motivation": "painting", 30 | "body": { 31 | "id": "https://iiif.io/api/cookbook/recipe/0040-image-rotation-service/body/v0001-image", 32 | "type": "SpecificResource", 33 | "source": { 34 | "id": "https://iiif.io/api/image/3.0/example/reference/85a96c630f077e6ac6cb984f1b752bbf-0-21198-zz00022840-1-page1/full/max/0/default.jpg", 35 | "type": "Image", 36 | "format": "image/jpeg", 37 | "width": 1523, 38 | "height": 2105, 39 | "service": [ 40 | { 41 | "id": "https://iiif.io/api/image/3.0/example/reference/85a96c630f077e6ac6cb984f1b752bbf-0-21198-zz00022840-1-page1", 42 | "type": "ImageService3", 43 | "profile": "level1" 44 | } 45 | ] 46 | }, 47 | "selector": { 48 | "type": "ImageApiSelector", 49 | "rotation": "90" 50 | } 51 | }, 52 | "target": "https://iiif.io/api/cookbook/recipe/0040-image-rotation-service/canvas/p1" 53 | } 54 | ] 55 | } 56 | ] 57 | } 58 | ] 59 | } -------------------------------------------------------------------------------- /fixtures/cookbook/0047-homepage.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/3/context.json", 3 | "id": "https://iiif.io/api/cookbook/recipe/0047-homepage/manifest.json", 4 | "type": "Manifest", 5 | "label": { 6 | "none": [ 7 | "Laocöon" 8 | ] 9 | }, 10 | "homepage": [ 11 | { 12 | "id": "https://www.getty.edu/art/collection/object/103RQQ", 13 | "type": "Text", 14 | "label": { 15 | "en": [ 16 | "Home page at the Getty Museum Collection" 17 | ] 18 | }, 19 | "format": "text/html", 20 | "language": [ 21 | "en" 22 | ] 23 | } 24 | ], 25 | "items": [ 26 | { 27 | "id": "https://iiif.io/api/cookbook/recipe/0047-homepage/canvas/1", 28 | "type": "Canvas", 29 | "label": { 30 | "none": [ 31 | "Front" 32 | ] 33 | }, 34 | "height": 3000, 35 | "width": 2315, 36 | "items": [ 37 | { 38 | "id": "https://iiif.io/api/cookbook/recipe/0047-homepage/canvas/1/page/1", 39 | "type": "AnnotationPage", 40 | "items": [ 41 | { 42 | "id": "https://iiif.io/api/cookbook/recipe/0047-homepage/canvas/1/page/1/annotation/1", 43 | "type": "Annotation", 44 | "motivation": "painting", 45 | "body": { 46 | "id": "https://iiif.io/api/image/3.0/example/reference/28473c77da3deebe4375c3a50572d9d3-laocoon/full/!500,500/0/default.jpg", 47 | "type": "Image", 48 | "format": "image/jpeg", 49 | "service": [ 50 | { 51 | "id": "https://iiif.io/api/image/3.0/example/reference/28473c77da3deebe4375c3a50572d9d3-laocoon", 52 | "type": "ImageService3", 53 | "profile": "level1" 54 | } 55 | ], 56 | "height": 3000, 57 | "width": 2315 58 | }, 59 | "target": "https://iiif.io/api/cookbook/recipe/0047-homepage/canvas/1" 60 | } 61 | ] 62 | } 63 | ] 64 | } 65 | ] 66 | } -------------------------------------------------------------------------------- /fixtures/cookbook/0074-multiple-language-captions.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/3/context.json", 3 | "id": "https://iiif.io/api/cookbook/recipe/0074-multiple-language-captions/manifest.json", 4 | "type": "Manifest", 5 | "label": { 6 | "it": [ 7 | "Per voi signore. Modelli francesi" 8 | ], 9 | "en": [ 10 | "For ladies. French models" 11 | ] 12 | }, 13 | "rights": "http://rightsstatements.org/vocab/InC/1.0/", 14 | "requiredStatement": { 15 | "label": { 16 | "en": [ 17 | "Rights" 18 | ] 19 | }, 20 | "value": { 21 | "en": [ 22 | "All rights reserved Cinecittà Luce spa" 23 | ] 24 | } 25 | }, 26 | "items": [ 27 | { 28 | "id": "https://iiif.io/api/cookbook/recipe/0074-multiple-language-captions/canvas", 29 | "type": "Canvas", 30 | "height": 384, 31 | "width": 288, 32 | "duration": 65.0, 33 | "items": [ 34 | { 35 | "id": "https://iiif.io/api/cookbook/recipe/0074-multiple-language-captions/canvas/page", 36 | "type": "AnnotationPage", 37 | "items": [ 38 | { 39 | "id": "https://iiif.io/api/cookbook/recipe/0074-multiple-language-captions/canvas/page/annotation", 40 | "type": "Annotation", 41 | "motivation": "painting", 42 | "body": { 43 | "id": "https://fixtures.iiif.io/video/europeana/Per_voi_signore_Modelli_francesi.mp4", 44 | "type": "Video", 45 | "height": 384, 46 | "width": 288, 47 | "duration": 65.0, 48 | "format": "video/mp4" 49 | }, 50 | "target": "https://iiif.io/api/cookbook/recipe/0074-multiple-language-captions/canvas" 51 | } 52 | ] 53 | } 54 | ], 55 | "annotations": [ 56 | { 57 | "id": "https://iiif.io/api/cookbook/recipe/0074-multiple-language-captions/manifest.json/anno/page/1", 58 | "type": "AnnotationPage", 59 | "items": [ 60 | { 61 | "id": "https://iiif.io/api/cookbook/recipe/0074-multiple-language-captions/manifest.json/subtitles_captions-files-vtt", 62 | "type": "Annotation", 63 | "motivation": "supplementing", 64 | "body": { 65 | "type": "Choice", 66 | "items": [ 67 | { 68 | "id": "https://iiif.io/api/cookbook/recipe/0074-multiple-language-captions/Per_voi_signore_Modelli_francesi_en.vtt", 69 | "type": "Text", 70 | "format": "text/vtt", 71 | "label": { 72 | "en": [ 73 | "Captions in WebVTT format" 74 | ] 75 | }, 76 | "language": "en" 77 | }, 78 | { 79 | "id": "https://iiif.io/api/cookbook/recipe/0074-multiple-language-captions/Per_voi_signore_Modelli_francesi_it.vtt", 80 | "type": "Text", 81 | "format": "text/vtt", 82 | "label": { 83 | "it": [ 84 | "Sottotitoli in formato WebVTT" 85 | ] 86 | }, 87 | "language": "it" 88 | } 89 | ] 90 | }, 91 | "target": "https://iiif.io/api/cookbook/recipe/0074-multiple-language-captions/canvas" 92 | } 93 | ] 94 | } 95 | ] 96 | } 97 | ] 98 | } -------------------------------------------------------------------------------- /fixtures/cookbook/0117-add-image-thumbnail.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/3/context.json", 3 | "id": "https://iiif.io/api/cookbook/recipe/0117-add-image-thumbnail/manifest.json", 4 | "type": "Manifest", 5 | "label": { 6 | "en": [ 7 | "Playbill Cover with Manifest Thumbnail" 8 | ] 9 | }, 10 | "summary": { 11 | "en": [ 12 | "Cover of playbill for \"Akiba gongen kaisen-banashi,\" \"Futatsu chōchō kuruwa nikki\" and \"Godairiki koi no fūjime\" performed at the Chikugo Theater in Osaka from the fifth month of Kaei 2 (May, 1849); main actors: Gadō Kataoka II, Ebizō Ichikawa VI, Kitō Sawamura II, Daigorō Mimasu IV and Karoku Nakamura I; on front cover: producer Mominosuke Ichikawa's crest." 13 | ] 14 | }, 15 | "thumbnail": [ 16 | { 17 | "id": "https://iiif.io/api/image/3.0/example/reference/4f92cceb12dd53b52433425ce44308c7-ucla_bib1987273_no001_rs_001/full/max/0/default.jpg", 18 | "type": "Image", 19 | "format": "image/jpeg", 20 | "height": 4823, 21 | "width": 3497, 22 | "service": [ 23 | { 24 | "id": "https://iiif.io/api/image/3.0/example/reference/4f92cceb12dd53b52433425ce44308c7-ucla_bib1987273_no001_rs_001", 25 | "type": "ImageService3", 26 | "profile": "level1", 27 | "height": 4823, 28 | "width": 3497, 29 | "extraFormats": [ 30 | "png" 31 | ], 32 | "extraQualities": [ 33 | "default", 34 | "color", 35 | "gray" 36 | ], 37 | "protocol": "http://iiif.io/api/image", 38 | "tiles": [ 39 | { 40 | "height": 512, 41 | "scaleFactors": [ 42 | 1, 43 | 2, 44 | 4, 45 | 8 46 | ], 47 | "width": 512 48 | } 49 | ] 50 | } 51 | ] 52 | } 53 | ], 54 | "items": [ 55 | { 56 | "id": "https://iiif.io/api/cookbook/recipe/0117-add-image-thumbnail/canvas/p0", 57 | "type": "Canvas", 58 | "label": { 59 | "en": [ 60 | "front cover with color bar" 61 | ] 62 | }, 63 | "width": 4520, 64 | "height": 5312, 65 | "items": [ 66 | { 67 | "id": "https://iiif.io/api/cookbook/recipe/0117-add-image-thumbnail/page/p0/1", 68 | "type": "AnnotationPage", 69 | "items": [ 70 | { 71 | "id": "https://iiif.io/api/cookbook/recipe/0117-add-image-thumbnail/annotation/p0000-image", 72 | "type": "Annotation", 73 | "motivation": "painting", 74 | "body": { 75 | "id": "https://iiif.io/api/image/3.0/example/reference/4f92cceb12dd53b52433425ce44308c7-ucla_bib1987273_no001_rs_001_full/full/max/0/default.jpg", 76 | "type": "Image", 77 | "format": "image/jpeg", 78 | "height": 5312, 79 | "width": 4520, 80 | "service": [ 81 | { 82 | "id": "https://iiif.io/api/image/3.0/example/reference/4f92cceb12dd53b52433425ce44308c7-ucla_bib1987273_no001_rs_001_full", 83 | "type": "ImageService3", 84 | "profile": "level1" 85 | } 86 | ] 87 | }, 88 | "target": "https://iiif.io/api/cookbook/recipe/0117-add-image-thumbnail/canvas/p0" 89 | } 90 | ] 91 | } 92 | ] 93 | } 94 | ] 95 | } -------------------------------------------------------------------------------- /fixtures/cookbook/0118-multivalue.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/3/context.json", 3 | "id": "https://iiif.io/api/cookbook/recipe/0118-multivalue/manifest.json", 4 | "type": "Manifest", 5 | "label": { 6 | "fr": [ 7 | "Arrangement en gris et noir no 1" 8 | ] 9 | }, 10 | "metadata": [ 11 | { 12 | "label": { 13 | "en": [ 14 | "Alternative titles" 15 | ] 16 | }, 17 | "value": { 18 | "en": [ 19 | "Whistler's Mother", 20 | "Arrangement in Grey and Black No. 1" 21 | ], 22 | "fr": [ 23 | "Portrait de la mère de l'artiste", 24 | "La Mère de Whistler" 25 | ] 26 | } 27 | } 28 | ], 29 | "summary": { 30 | "en": [ 31 | "A painting in oil on canvas created by the American-born painter James McNeill Whistler, in 1871." 32 | ] 33 | }, 34 | "items": [ 35 | { 36 | "id": "https://iiif.io/api/cookbook/recipe/0118-multivalue/canvas/1", 37 | "type": "Canvas", 38 | "width": 1114, 39 | "height": 991, 40 | "items": [ 41 | { 42 | "id": "https://iiif.io/api/cookbook/recipe/0118-multivalue/canvas/1/page/1", 43 | "type": "AnnotationPage", 44 | "items": [ 45 | { 46 | "id": "https://iiif.io/api/cookbook/recipe/0118-multivalue/canvas/1/page/1/annotation/1", 47 | "type": "Annotation", 48 | "motivation": "painting", 49 | "body": { 50 | "id": "https://upload.wikimedia.org/wikipedia/commons/thumb/1/1b/Whistlers_Mother_high_res.jpg/1114px-Whistlers_Mother_high_res.jpg", 51 | "type": "Image", 52 | "format": "image/jpeg" 53 | }, 54 | "target": "https://iiif.io/api/cookbook/recipe/0118-multivalue/canvas/1" 55 | } 56 | ] 57 | } 58 | ] 59 | } 60 | ] 61 | } -------------------------------------------------------------------------------- /fixtures/cookbook/0135-annotating-point-in-canvas.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/3/context.json", 3 | "id": "https://iiif.io/api/cookbook/recipe/0135-annotating-point-in-canvas/manifest.json", 4 | "type": "Manifest", 5 | "label": { 6 | "en": [ 7 | "Using a point selector for annotating a location on a map." 8 | ] 9 | }, 10 | "summary": { 11 | "en": [ 12 | "A map containing an point with an annotation of the location." 13 | ] 14 | }, 15 | "items": [ 16 | { 17 | "id": "https://iiif.io/api/cookbook/recipe/0135-annotating-point-in-canvas/canvas.json", 18 | "type": "Canvas", 19 | "label": { 20 | "en": [ 21 | "Chesapeake and Ohio Canal Pamphlet" 22 | ] 23 | }, 24 | "height": 7072, 25 | "width": 5212, 26 | "items": [ 27 | { 28 | "id": "https://iiif.io/api/cookbook/recipe/0135-annotating-point-in-canvas/contentPage.json", 29 | "type": "AnnotationPage", 30 | "items": [ 31 | { 32 | "id": "https://iiif.io/api/cookbook/recipe/0135-annotating-point-in-canvas/content.json", 33 | "type": "Annotation", 34 | "motivation": "painting", 35 | "body": { 36 | "id": "https://iiif.io/api/image/3.0/example/reference/43153e2ec7531f14dd1c9b2fc401678a-88695674/full/max/0/default.jpg", 37 | "type": "Image", 38 | "format": "image/jpeg", 39 | "height": 7072, 40 | "width": 5212, 41 | "service": [ 42 | { 43 | "id": "https://iiif.io/api/image/3.0/example/reference/43153e2ec7531f14dd1c9b2fc401678a-88695674", 44 | "type": "ImageService3", 45 | "profile": "level1" 46 | } 47 | ] 48 | }, 49 | "target": "https://iiif.io/api/cookbook/recipe/0135-annotating-point-in-canvas/canvas.json" 50 | } 51 | ] 52 | } 53 | ], 54 | "annotations": [ 55 | { 56 | "id": "https://iiif.io/api/cookbook/recipe/0135-annotating-point-in-canvas/page/p2/1", 57 | "type": "AnnotationPage", 58 | "items": [ 59 | { 60 | "id": "https://iiif.io/api/cookbook/recipe/0135-annotating-point-in-canvas/annotation/p0002-tag", 61 | "type": "Annotation", 62 | "motivation": "tagging", 63 | "body": { 64 | "type": "TextualBody", 65 | "value": "Town Creek Aqueduct", 66 | "language": "en", 67 | "format": "text/plain" 68 | }, 69 | "target": { 70 | "type": "SpecificResource", 71 | "source": "https://iiif.io/api/cookbook/recipe/0135-annotating-point-in-canvas/canvas.json", 72 | "selector": { 73 | "type": "PointSelector", 74 | "x": 3385, 75 | "y": 1464 76 | } 77 | } 78 | } 79 | ] 80 | } 81 | ] 82 | } 83 | ] 84 | } -------------------------------------------------------------------------------- /fixtures/cookbook/0139-geolocate-canvas-fragment.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": [ 3 | "http://geojson.org/geojson-ld/geojson-context.jsonld", 4 | "http://iiif.io/api/presentation/3/context.json" 5 | ], 6 | "id": "https://iiif.io/api/cookbook/recipe/0139-geolocate-canvas-fragment/manifest.json", 7 | "type": "Manifest", 8 | "label": { 9 | "en": [ 10 | "Recipe Manifest for #139" 11 | ] 12 | }, 13 | "summary": { 14 | "en": [ 15 | "A IIIF Presentation API 3.0 Manifest containing a GeoJSON-LD Web Annotation which targets a Canvas fragment." 16 | ] 17 | }, 18 | "items": [ 19 | { 20 | "id": "https://iiif.io/api/cookbook/recipe/0139-geolocate-canvas-fragment/canvas.json", 21 | "type": "Canvas", 22 | "label": { 23 | "en": [ 24 | "Chesapeake and Ohio Canal Pamphlet" 25 | ] 26 | }, 27 | "width": 5212, 28 | "height": 7072, 29 | "items": [ 30 | { 31 | "id": "https://iiif.io/api/cookbook/recipe/0139-geolocate-canvas-fragment/contentPage.json", 32 | "type": "AnnotationPage", 33 | "items": [ 34 | { 35 | "id": "https://iiif.io/api/cookbook/recipe/0139-geolocate-canvas-fragment/content.json", 36 | "type": "Annotation", 37 | "motivation": "painting", 38 | "body": { 39 | "id": "https://iiif.io/api/image/3.0/example/reference/43153e2ec7531f14dd1c9b2fc401678a-88695674/full/max/0/default.jpg", 40 | "type": "Image", 41 | "format": "image/jpeg", 42 | "service": [ 43 | { 44 | "id": "https://iiif.io/api/image/3.0/example/reference/43153e2ec7531f14dd1c9b2fc401678a-88695674", 45 | "type": "ImageService3", 46 | "profile": "level1" 47 | } 48 | ], 49 | "width": 5212, 50 | "height": 7072 51 | }, 52 | "target": "https://iiif.io/api/cookbook/recipe/0139-geolocate-canvas-fragment/canvas.json" 53 | } 54 | ] 55 | } 56 | ], 57 | "annotations": [ 58 | { 59 | "id": "https://iiif.io/api/cookbook/recipe/0139-geolocate-canvas-fragment/supplementingPage.json", 60 | "type": "AnnotationPage", 61 | "items": [ 62 | { 63 | "id": "https://iiif.io/api/cookbook/recipe/0139-geolocate-canvas-fragment/geoAnno.json", 64 | "type": "Annotation", 65 | "motivation": "tagging", 66 | "body": { 67 | "id": "https://iiif.io/api/cookbook/recipe/0139-geolocate-canvas-fragment/geo.json", 68 | "type": "Feature", 69 | "properties": { 70 | "label": { 71 | "en": [ 72 | "Targeted Map from Chesapeake and Ohio Canal Pamphlet" 73 | ] 74 | } 75 | }, 76 | "geometry": { 77 | "type": "Polygon", 78 | "coordinates": [ 79 | [ 80 | [ 81 | -77.019853, 82 | 38.913101 83 | ], 84 | [ 85 | -77.110013, 86 | 38.843254 87 | ], 88 | [ 89 | -77.284698, 90 | 38.997574 91 | ], 92 | [ 93 | -77.188911, 94 | 39.062648 95 | ] 96 | ] 97 | ] 98 | } 99 | }, 100 | "target": "https://iiif.io/api/cookbook/recipe/0139-geolocate-canvas-fragment/canvas.json#xywh=920,3600,1510,3000" 101 | } 102 | ] 103 | } 104 | ] 105 | } 106 | ] 107 | } -------------------------------------------------------------------------------- /fixtures/cookbook/0154-geo-extension.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": [ 3 | "http://iiif.io/api/extension/navplace/context.json", 4 | "http://iiif.io/api/presentation/3/context.json" 5 | ], 6 | "id": "https://iiif.io/api/cookbook/recipe/0154-geo-extension/manifest.json", 7 | "type": "Manifest", 8 | "label": { 9 | "it": [ 10 | "Bronzo Laocoonte e i suoi figli" 11 | ] 12 | }, 13 | "navPlace": { 14 | "id": "https://iiif.io/api/cookbook/recipe/0154-geo-extension/feature-collection/1", 15 | "type": "FeatureCollection", 16 | "features": [ 17 | { 18 | "id": "https://iiif.io/api/cookbook/recipe/0154-geo-extension/feature/1", 19 | "type": "Feature", 20 | "properties": { 21 | "label": { 22 | "en": [ 23 | "The Laocoön Bronze" 24 | ], 25 | "it": [ 26 | "Bronzo Laocoonte e i suoi figli" 27 | ] 28 | } 29 | }, 30 | "geometry": { 31 | "type": "Point", 32 | "coordinates": [ 33 | -118.4745559, 34 | 34.0776376 35 | ] 36 | } 37 | } 38 | ] 39 | }, 40 | "items": [ 41 | { 42 | "id": "https://iiif.io/api/cookbook/recipe/0154-geo-extension/canvas/1", 43 | "type": "Canvas", 44 | "height": 3000, 45 | "width": 2315, 46 | "label": { 47 | "en": [ 48 | "Front of Bronze" 49 | ] 50 | }, 51 | "items": [ 52 | { 53 | "id": "https://iiif.io/api/cookbook/recipe/0154-geo-extension/anno-page/1", 54 | "type": "AnnotationPage", 55 | "items": [ 56 | { 57 | "id": "https://iiif.io/api/cookbook/recipe/0154-geo-extension/anno/1", 58 | "type": "Annotation", 59 | "motivation": "painting", 60 | "body": { 61 | "id": "https://iiif.io/api/image/3.0/example/reference/28473c77da3deebe4375c3a50572d9d3-laocoon/full/max/0/default.jpg", 62 | "type": "Image", 63 | "format": "image/jpeg", 64 | "height": 3000, 65 | "width": 2315, 66 | "service": [ 67 | { 68 | "id": "https://iiif.io/api/image/3.0/example/reference/28473c77da3deebe4375c3a50572d9d3-laocoon", 69 | "profile": "level1", 70 | "type": "ImageService3" 71 | } 72 | ] 73 | }, 74 | "target": "https://iiif.io/api/cookbook/recipe/0154-geo-extension/canvas/1" 75 | } 76 | ] 77 | } 78 | ] 79 | } 80 | ] 81 | } -------------------------------------------------------------------------------- /fixtures/cookbook/0219-using-caption-file.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/3/context.json", 3 | "id": "https://iiif.io/api/cookbook/recipe/0219-using-caption-file/manifest.json", 4 | "type": "Manifest", 5 | "label": { 6 | "en": [ 7 | "Lunchroom Manners" 8 | ] 9 | }, 10 | "items": [ 11 | { 12 | "id": "https://iiif.io/api/cookbook/recipe/0219-using-caption-file/canvas", 13 | "type": "Canvas", 14 | "height": 360, 15 | "width": 480, 16 | "duration": 572.034, 17 | "items": [ 18 | { 19 | "id": "https://iiif.io/api/cookbook/recipe/0219-using-caption-file/canvas/page", 20 | "type": "AnnotationPage", 21 | "items": [ 22 | { 23 | "id": "https://iiif.io/api/cookbook/recipe/0219-using-caption-file/canvas/page/annotation1", 24 | "type": "Annotation", 25 | "motivation": "painting", 26 | "body": { 27 | "id": "https://fixtures.iiif.io/video/indiana/lunchroom_manners/high/lunchroom_manners_1024kb.mp4", 28 | "type": "Video", 29 | "height": 360, 30 | "width": 480, 31 | "duration": 572.034, 32 | "format": "video/mp4" 33 | }, 34 | "target": "https://iiif.io/api/cookbook/recipe/0219-using-caption-file/canvas" 35 | } 36 | ] 37 | } 38 | ], 39 | "annotations": [ 40 | { 41 | "id": "https://iiif.io/api/cookbook/recipe/0219-using-caption-file/canvas/page2", 42 | "type": "AnnotationPage", 43 | "items": [ 44 | { 45 | "id": "https://iiif.io/api/cookbook/recipe/0219-using-caption-file/canvas/page2/a1", 46 | "type": "Annotation", 47 | "motivation": "supplementing", 48 | "body": { 49 | "id": "https://fixtures.iiif.io/video/indiana/lunchroom_manners/lunchroom_manners.vtt", 50 | "type": "Text", 51 | "format": "text/vtt", 52 | "label": { 53 | "en": [ 54 | "Captions in WebVTT format" 55 | ] 56 | }, 57 | "language": "en" 58 | }, 59 | "target": "https://iiif.io/api/cookbook/recipe/0219-using-caption-file/canvas" 60 | } 61 | ] 62 | } 63 | ] 64 | } 65 | ] 66 | } -------------------------------------------------------------------------------- /fixtures/cookbook/0230-navdate-navdate-collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/3/context.json", 3 | "id": "https://iiif.io/api/cookbook/recipe/0230-navdate/navdate-collection.json", 4 | "type": "Collection", 5 | "label": { 6 | "en": [ 7 | "Chesapeake and Ohio Canal map and guide pamphlets" 8 | ] 9 | }, 10 | "thumbnail": [ 11 | { 12 | "id": "https://iiif.io/api/image/3.0/example/reference/43153e2ec7531f14dd1c9b2fc401678a-88695674/full/max/0/default.jpg", 13 | "type": "Image", 14 | "format": "image/jpeg", 15 | "height": 300, 16 | "width": 221, 17 | "service": [ 18 | { 19 | "id": "https://iiif.io/api/image/3.0/example/reference/43153e2ec7531f14dd1c9b2fc401678a-88695674", 20 | "profile": "level1", 21 | "type": "ImageService3" 22 | } 23 | ] 24 | } 25 | ], 26 | "items": [ 27 | { 28 | "id": "https://iiif.io/api/cookbook/recipe/0230-navdate/navdate_map_2-manifest.json", 29 | "type": "Manifest", 30 | "label": { 31 | "en": [ 32 | "1986 Chesapeake and Ohio Canal, Washington, D.C., Maryland, West Virginia, official map and guide" 33 | ] 34 | }, 35 | "navDate": "1986-01-01T00:00:00+00:00" 36 | }, 37 | { 38 | "id": "https://iiif.io/api/cookbook/recipe/0230-navdate/navdate_map_1-manifest.json", 39 | "type": "Manifest", 40 | "label": { 41 | "en": [ 42 | "1987 Chesapeake and Ohio Canal, Washington, D.C., Maryland, West Virginia, official map and guide" 43 | ] 44 | }, 45 | "navDate": "1987-01-01T00:00:00+00:00" 46 | } 47 | ] 48 | } -------------------------------------------------------------------------------- /fixtures/cookbook/0230-navdate-navdate_map_1-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/3/context.json", 3 | "id": "https://iiif.io/api/cookbook/recipe/0230-navdate/navdate_map_1-manifest.json", 4 | "type": "Manifest", 5 | "label": { 6 | "en": [ 7 | "1987 Chesapeake and Ohio Canal, Washington, D.C., Maryland, West Virginia, official map and guide" 8 | ] 9 | }, 10 | "navDate": "1987-01-01T00:00:00+00:00", 11 | "items": [ 12 | { 13 | "id": "https://iiif.io/api/cookbook/recipe/0230-navdate/canvas/p1", 14 | "type": "Canvas", 15 | "label": { 16 | "en": [ 17 | "1987 Map, recto and verso, with a date of publication" 18 | ] 19 | }, 20 | "height": 7072, 21 | "width": 5212, 22 | "items": [ 23 | { 24 | "id": "https://iiif.io/api/cookbook/recipe/0230-navdate/page/p1/1", 25 | "type": "AnnotationPage", 26 | "items": [ 27 | { 28 | "id": "https://iiif.io/api/cookbook/recipe/0230-navdate/annotation/p0001-image", 29 | "type": "Annotation", 30 | "motivation": "painting", 31 | "body": { 32 | "id": "https://iiif.io/api/image/3.0/example/reference/43153e2ec7531f14dd1c9b2fc401678a-88695674/full/max/0/default.jpg", 33 | "type": "Image", 34 | "format": "image/jpeg", 35 | "height": 7072, 36 | "width": 5212, 37 | "service": [ 38 | { 39 | "id": "https://iiif.io/api/image/3.0/example/reference/43153e2ec7531f14dd1c9b2fc401678a-88695674/", 40 | "profile": "level1", 41 | "type": "ImageService3" 42 | } 43 | ] 44 | }, 45 | "target": "https://iiif.io/api/cookbook/recipe/0230-navdate/canvas/p1" 46 | } 47 | ] 48 | } 49 | ] 50 | } 51 | ] 52 | } -------------------------------------------------------------------------------- /fixtures/cookbook/0230-navdate-navdate_map_2-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/3/context.json", 3 | "id": "https://iiif.io/api/cookbook/recipe/0230-navdate/navdate_map_2-manifest.json", 4 | "type": "Manifest", 5 | "label": { 6 | "en": [ 7 | "1986 Chesapeake and Ohio Canal, Washington, D.C., Maryland, West Virginia, official map and guide" 8 | ] 9 | }, 10 | "navDate": "1986-01-01T00:00:00+00:00", 11 | "items": [ 12 | { 13 | "id": "https://iiif.io/api/cookbook/recipe/0230-navdate/canvas/p1", 14 | "type": "Canvas", 15 | "label": { 16 | "en": [ 17 | "1986 Map, recto and verso, with a date of publication" 18 | ] 19 | }, 20 | "height": 1765, 21 | "width": 1286, 22 | "items": [ 23 | { 24 | "id": "https://iiif.io/api/cookbook/recipe/0230-navdate/page/p1/1", 25 | "type": "AnnotationPage", 26 | "items": [ 27 | { 28 | "id": "https://iiif.io/api/cookbook/recipe/0230-navdate/annotation/p0001-image", 29 | "type": "Annotation", 30 | "motivation": "painting", 31 | "body": { 32 | "id": "https://iiif.io/api/image/3.0/example/reference/43153e2ec7531f14dd1c9b2fc401678a-87691274-1986/full/max/0/default.jpg", 33 | "type": "Image", 34 | "format": "image/jpeg", 35 | "height": 1765, 36 | "width": 1286, 37 | "service": [ 38 | { 39 | "id": "https://iiif.io/api/image/3.0/example/reference/43153e2ec7531f14dd1c9b2fc401678a-87691274-1986/", 40 | "profile": "level1", 41 | "type": "ImageService3" 42 | } 43 | ] 44 | }, 45 | "target": "https://iiif.io/api/cookbook/recipe/0230-navdate/canvas/p1" 46 | } 47 | ] 48 | } 49 | ] 50 | } 51 | ] 52 | } -------------------------------------------------------------------------------- /fixtures/cookbook/0258-tagging-external-resource.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/3/context.json", 3 | "id": "https://iiif.io/api/cookbook/recipe/0258-tagging-external-resource/manifest.json", 4 | "type": "Manifest", 5 | "label": { 6 | "en": [ 7 | "Picture of Göttingen taken during the 2019 IIIF Conference" 8 | ] 9 | }, 10 | "items": [ 11 | { 12 | "id": "https://iiif.io/api/cookbook/recipe/0258-tagging-external-resource/canvas/p1", 13 | "type": "Canvas", 14 | "height": 3024, 15 | "width": 4032, 16 | "items": [ 17 | { 18 | "id": "https://iiif.io/api/cookbook/recipe/0258-tagging-external-resource/page/p1/1", 19 | "type": "AnnotationPage", 20 | "items": [ 21 | { 22 | "id": "https://iiif.io/api/cookbook/recipe/0258-tagging-external-resource/annotation/p0001-image", 23 | "type": "Annotation", 24 | "motivation": "painting", 25 | "body": { 26 | "id": "https://iiif.io/api/image/3.0/example/reference/918ecd18c2592080851777620de9bcb5-gottingen/full/max/0/default.jpg", 27 | "type": "Image", 28 | "format": "image/jpeg", 29 | "height": 3024, 30 | "width": 4032, 31 | "service": [ 32 | { 33 | "id": "https://iiif.io/api/image/3.0/example/reference/918ecd18c2592080851777620de9bcb5-gottingen", 34 | "profile": "level1", 35 | "type": "ImageService3" 36 | } 37 | ] 38 | }, 39 | "target": "https://iiif.io/api/cookbook/recipe/0258-tagging-external-resource/canvas/p1" 40 | } 41 | ] 42 | } 43 | ], 44 | "annotations": [ 45 | { 46 | "id": "https://iiif.io/api/cookbook/recipe/0258-tagging-external-resource/page/p2/1", 47 | "type": "AnnotationPage", 48 | "items": [ 49 | { 50 | "id": "https://iiif.io/api/cookbook/recipe/0258-tagging-external-resource/annotation/anno/p0002-wikidata", 51 | "type": "Annotation", 52 | "motivation": "tagging", 53 | "body": [ 54 | { 55 | "type": "SpecificResource", 56 | "source": "http://www.wikidata.org/entity/Q18624915" 57 | }, 58 | { 59 | "type": "TextualBody", 60 | "value": "Gänseliesel-Brunnen", 61 | "format": "text/plain", 62 | "language": "de" 63 | } 64 | ], 65 | "target": "https://iiif.io/api/cookbook/recipe/0258-tagging-external-resource/canvas/p1#xywh=749,1054,338,460" 66 | } 67 | ] 68 | } 69 | ] 70 | } 71 | ] 72 | } -------------------------------------------------------------------------------- /fixtures/cookbook/0261-non-rectangular-commenting.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/3/context.json", 3 | "id": "https://iiif.io/api/cookbook/recipe/0261-non-rectangular-commenting/manifest.json", 4 | "type": "Manifest", 5 | "label": { 6 | "en": [ 7 | "Picture of Göttingen taken during the 2019 IIIF Conference" 8 | ] 9 | }, 10 | "items": [ 11 | { 12 | "id": "https://iiif.io/api/cookbook/recipe/0261-non-rectangular-commenting/canvas/p1", 13 | "type": "Canvas", 14 | "height": 3024, 15 | "width": 4032, 16 | "items": [ 17 | { 18 | "id": "https://iiif.io/api/cookbook/recipe/0261-non-rectangular-commenting/page/p1/1", 19 | "type": "AnnotationPage", 20 | "items": [ 21 | { 22 | "id": "https://iiif.io/api/cookbook/recipe/0261-non-rectangular-commenting/annotation/p0001-image", 23 | "type": "Annotation", 24 | "motivation": "painting", 25 | "body": { 26 | "id": "https://iiif.io/api/image/3.0/example/reference/918ecd18c2592080851777620de9bcb5-gottingen/full/max/0/default.jpg", 27 | "type": "Image", 28 | "format": "image/jpeg", 29 | "height": 3024, 30 | "width": 4032, 31 | "service": [ 32 | { 33 | "id": "https://iiif.io/api/image/3.0/example/reference/918ecd18c2592080851777620de9bcb5-gottingen", 34 | "profile": "level1", 35 | "type": "ImageService3" 36 | } 37 | ] 38 | }, 39 | "target": "https://iiif.io/api/cookbook/recipe/0261-non-rectangular-commenting/canvas/p1" 40 | } 41 | ] 42 | } 43 | ], 44 | "annotations": [ 45 | { 46 | "id": "https://iiif.io/api/cookbook/recipe/0261-non-rectangular-commenting/page/p2/1", 47 | "type": "AnnotationPage", 48 | "items": [ 49 | { 50 | "id": "https://iiif.io/api/cookbook/recipe/0261-non-rectangular-commenting/annotation/p0002-svg", 51 | "type": "Annotation", 52 | "motivation": "tagging", 53 | "body": { 54 | "type": "TextualBody", 55 | "value": "Gänseliesel-Brunnen", 56 | "language": "de", 57 | "format": "text/plain" 58 | }, 59 | "target": { 60 | "type": "SpecificResource", 61 | "source": "https://iiif.io/api/cookbook/recipe/0261-non-rectangular-commenting/canvas/p1", 62 | "selector": { 63 | "type": "SvgSelector", 64 | "value": "" 65 | } 66 | } 67 | } 68 | ] 69 | } 70 | ] 71 | } 72 | ] 73 | } -------------------------------------------------------------------------------- /fixtures/cookbook/0266-full-canvas-annotation.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/3/context.json", 3 | "id": "https://iiif.io/api/cookbook/recipe/0266-full-canvas-annotation/manifest.json", 4 | "type": "Manifest", 5 | "label": { 6 | "en": [ 7 | "Picture of Göttingen taken during the 2019 IIIF Conference" 8 | ] 9 | }, 10 | "items": [ 11 | { 12 | "id": "https://iiif.io/api/cookbook/recipe/0266-full-canvas-annotation/canvas-1", 13 | "type": "Canvas", 14 | "height": 3024, 15 | "width": 4032, 16 | "items": [ 17 | { 18 | "id": "https://iiif.io/api/cookbook/recipe/0266-full-canvas-annotation/canvas-1/annopage-1", 19 | "type": "AnnotationPage", 20 | "items": [ 21 | { 22 | "id": "https://iiif.io/api/cookbook/recipe/0266-full-canvas-annotation/canvas-1/annopage-1/anno-1", 23 | "type": "Annotation", 24 | "motivation": "painting", 25 | "body": { 26 | "id": "https://iiif.io/api/image/3.0/example/reference/918ecd18c2592080851777620de9bcb5-gottingen/full/max/0/default.jpg", 27 | "type": "Image", 28 | "format": "image/jpeg", 29 | "height": 3024, 30 | "width": 4032, 31 | "service": [ 32 | { 33 | "id": "https://iiif.io/api/image/3.0/example/reference/918ecd18c2592080851777620de9bcb5-gottingen", 34 | "profile": "level1", 35 | "type": "ImageService3" 36 | } 37 | ] 38 | }, 39 | "target": "https://iiif.io/api/cookbook/recipe/0266-full-canvas-annotation/canvas-1" 40 | } 41 | ] 42 | } 43 | ], 44 | "annotations": [ 45 | { 46 | "id": "https://iiif.io/api/cookbook/recipe/0266-full-canvas-annotation/canvas-1/annopage-2", 47 | "type": "AnnotationPage", 48 | "items": [ 49 | { 50 | "id": "https://iiif.io/api/cookbook/recipe/0266-full-canvas-annotation/canvas-1/annopage-2/anno-1", 51 | "type": "Annotation", 52 | "motivation": "commenting", 53 | "body": { 54 | "type": "TextualBody", 55 | "language": "de", 56 | "format": "text/plain", 57 | "value": "Göttinger Marktplatz mit Gänseliesel Brunnen" 58 | }, 59 | "target": "https://iiif.io/api/cookbook/recipe/0266-full-canvas-annotation/canvas-1" 60 | } 61 | ] 62 | } 63 | ] 64 | } 65 | ] 66 | } -------------------------------------------------------------------------------- /fixtures/cookbook/0269-embedded-or-referenced-annotations-annotationpage.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/3/context.json", 3 | "id": "https://iiif.io/api/cookbook/recipe/0269-embedded-or-referenced-annotations/annotationpage.json", 4 | "type": "AnnotationPage", 5 | "items": [ 6 | { 7 | "id": "https://iiif.io/api/cookbook/recipe/0269-embedded-or-referenced-annotations/canvas-1/annopage-2/anno-1", 8 | "type": "Annotation", 9 | "motivation": "commenting", 10 | "body": { 11 | "type": "TextualBody", 12 | "language": "de", 13 | "format": "text/plain", 14 | "value": "Göttinger Marktplatz mit Gänseliesel Brunnen" 15 | }, 16 | "target": "https://iiif.io/api/cookbook/recipe/0269-embedded-or-referenced-annotations/canvas-1" 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /fixtures/cookbook/0269-embedded-or-referenced-annotations.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/3/context.json", 3 | "id": "https://iiif.io/api/cookbook/recipe/0269-embedded-or-referenced-annotations/manifest.json", 4 | "type": "Manifest", 5 | "label": { 6 | "en": [ 7 | "Picture of Göttingen taken during the 2019 IIIF Conference" 8 | ] 9 | }, 10 | "items": [ 11 | { 12 | "id": "https://iiif.io/api/cookbook/recipe/0269-embedded-or-referenced-annotations/canvas-1", 13 | "type": "Canvas", 14 | "height": 3024, 15 | "width": 4032, 16 | "items": [ 17 | { 18 | "id": "https://iiif.io/api/cookbook/recipe/0269-embedded-or-referenced-annotations/canvas-1/annopage-1", 19 | "type": "AnnotationPage", 20 | "items": [ 21 | { 22 | "id": "https://iiif.io/api/cookbook/recipe/0269-embedded-or-referenced-annotations/canvas-1/annopage-1/anno-1", 23 | "type": "Annotation", 24 | "motivation": "painting", 25 | "body": { 26 | "id": "https://iiif.io/api/image/3.0/example/reference/918ecd18c2592080851777620de9bcb5-gottingen/full/max/0/default.jpg", 27 | "type": "Image", 28 | "format": "image/jpeg", 29 | "height": 3024, 30 | "width": 4032, 31 | "service": [ 32 | { 33 | "id": "https://iiif.io/api/image/3.0/example/reference/918ecd18c2592080851777620de9bcb5-gottingen", 34 | "profile": "level1", 35 | "type": "ImageService3" 36 | } 37 | ] 38 | }, 39 | "target": "https://iiif.io/api/cookbook/recipe/0269-embedded-or-referenced-annotations/canvas-1" 40 | } 41 | ] 42 | } 43 | ], 44 | "annotations": [ 45 | { 46 | "id": "https://iiif.io/api/cookbook/recipe/0269-embedded-or-referenced-annotations/annotationpage.json", 47 | "type": "AnnotationPage" 48 | } 49 | ] 50 | } 51 | ] 52 | } -------------------------------------------------------------------------------- /fixtures/cookbook/0299-region.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/3/context.json", 3 | "id": "https://iiif.io/api/cookbook/recipe/0299-region/manifest.json", 4 | "type": "Manifest", 5 | "label": { 6 | "en": [ 7 | "Berliner Tageblatt article, 'Ein neuer Sicherungsplan?'" 8 | ] 9 | }, 10 | "items": [ 11 | { 12 | "id": "https://iiif.io/api/cookbook/recipe/0299-region/canvas/p1", 13 | "type": "Canvas", 14 | "height": 2080, 15 | "width": 1768, 16 | "items": [ 17 | { 18 | "id": "https://iiif.io/api/cookbook/recipe/0299-region/page/p1/1", 19 | "type": "AnnotationPage", 20 | "items": [ 21 | { 22 | "id": "https://iiif.io/api/cookbook/recipe/0299-region/annotation/p0001-image", 23 | "type": "Annotation", 24 | "motivation": "painting", 25 | "body": { 26 | "id": "https://iiif.io/api/cookbook/recipe/0299-region/body/b1", 27 | "type": "SpecificResource", 28 | "source": { 29 | "id": "https://iiif.io/api/image/3.0/example/reference/4ce82cef49fb16798f4c2440307c3d6f-newspaper-p2/full/max/0/default.jpg", 30 | "type": "Image", 31 | "format": "image/jpeg", 32 | "height": 4999, 33 | "width": 3536, 34 | "service": [ 35 | { 36 | "id": "https://iiif.io/api/image/3.0/example/reference/4ce82cef49fb16798f4c2440307c3d6f-newspaper-p2", 37 | "profile": "level1", 38 | "type": "ImageService3" 39 | } 40 | ] 41 | }, 42 | "selector": { 43 | "type": "ImageApiSelector", 44 | "region": "1768,2423,1768,2080" 45 | } 46 | }, 47 | "target": "https://iiif.io/api/cookbook/recipe/0299-region/canvas/p1" 48 | } 49 | ] 50 | } 51 | ] 52 | } 53 | ] 54 | } -------------------------------------------------------------------------------- /fixtures/cookbook/0306-linking-annotations-to-manifests-annotationpage.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/3/context.json", 3 | "id": "https://iiif.io/api/cookbook/recipe/0306-linking-annotations-to-manifests/annotationpage.json", 4 | "type": "AnnotationPage", 5 | "items": [ 6 | { 7 | "id": "https://iiif.io/api/cookbook/recipe/0306-linking-annotations-to-manifests/canvas-1/annopage-2/anno-1", 8 | "type": "Annotation", 9 | "motivation": "commenting", 10 | "body": { 11 | "type": "TextualBody", 12 | "language": "de", 13 | "format": "text/plain", 14 | "value": "Der Gänseliesel-Brunnen" 15 | }, 16 | "target": { 17 | "type": "SpecificResource", 18 | "source": { 19 | "id": "https://iiif.io/api/cookbook/recipe/0306-linking-annotations-to-manifests/canvas-1", 20 | "type": "Canvas", 21 | "partOf": [ 22 | { 23 | "id": "https://iiif.io/api/cookbook/recipe/0306-linking-annotations-to-manifests/manifest.json", 24 | "type": "Manifest" 25 | } 26 | ] 27 | }, 28 | "selector": { 29 | "type": "FragmentSelector", 30 | "conformsTo": "http://www.w3.org/TR/media-frags/", 31 | "value": "xywh=300,800,1200,1200" 32 | } 33 | } 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /fixtures/cookbook/0306-linking-annotations-to-manifests.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/3/context.json", 3 | "id": "https://iiif.io/api/cookbook/recipe/0306-linking-annotations-to-manifests/manifest.json", 4 | "type": "Manifest", 5 | "label": { 6 | "en": [ 7 | "Picture of Göttingen taken during the 2019 IIIF Conference" 8 | ] 9 | }, 10 | "items": [ 11 | { 12 | "id": "https://iiif.io/api/cookbook/recipe/0306-linking-annotations-to-manifests/canvas-1", 13 | "type": "Canvas", 14 | "height": 3024, 15 | "width": 4032, 16 | "items": [ 17 | { 18 | "id": "https://iiif.io/api/cookbook/recipe/0306-linking-annotations-to-manifests/canvas-1/annopage-1", 19 | "type": "AnnotationPage", 20 | "items": [ 21 | { 22 | "id": "https://iiif.io/api/cookbook/recipe/0306-linking-annotations-to-manifests/canvas-1/annopage-1/anno-1", 23 | "type": "Annotation", 24 | "motivation": "painting", 25 | "body": { 26 | "id": "https://iiif.io/api/image/3.0/example/reference/918ecd18c2592080851777620de9bcb5-gottingen/full/max/0/default.jpg", 27 | "type": "Image", 28 | "format": "image/jpeg", 29 | "height": 3024, 30 | "width": 4032, 31 | "service": [ 32 | { 33 | "id": "https://iiif.io/api/image/3.0/example/reference/918ecd18c2592080851777620de9bcb5-gottingen", 34 | "profile": "level1", 35 | "type": "ImageService3" 36 | } 37 | ] 38 | }, 39 | "target": "https://iiif.io/api/cookbook/recipe/0306-linking-annotations-to-manifests/canvas-1" 40 | } 41 | ] 42 | } 43 | ], 44 | "annotations": [ 45 | { 46 | "id": "https://iiif.io/api/cookbook/recipe/0306-linking-annotations-to-manifests/annotationpage.json", 47 | "type": "AnnotationPage" 48 | } 49 | ] 50 | } 51 | ] 52 | } -------------------------------------------------------------------------------- /fixtures/cookbook/0346-multilingual-annotation-body.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/3/context.json", 3 | "id": "https://iiif.io/api/cookbook/recipe/0346-multilingual-annotation-body/manifest.json", 4 | "type": "Manifest", 5 | "label": { 6 | "en": [ 7 | "Koto, chess, calligraphy, and painting" 8 | ], 9 | "ja": [ 10 | "琴棋書画図屏風" 11 | ] 12 | }, 13 | "items": [ 14 | { 15 | "id": "https://iiif.io/api/cookbook/recipe/0346-multilingual-annotation-body/canvas/p1", 16 | "type": "Canvas", 17 | "height": 31722, 18 | "width": 70399, 19 | "items": [ 20 | { 21 | "id": "https://iiif.io/api/cookbook/recipe/0346-multilingual-annotation-body/page/p1/1", 22 | "type": "AnnotationPage", 23 | "items": [ 24 | { 25 | "id": "https://iiif.io/api/cookbook/recipe/0346-multilingual-annotation-body/annotation/p0001-image", 26 | "type": "Annotation", 27 | "motivation": "painting", 28 | "body": { 29 | "id": "https://iiif.io/api/image/3.0/example/reference/36ca0a3370db128ec984b33d71a1543d-100320001004/full/max/0/default.jpg", 30 | "type": "Image", 31 | "format": "image/jpeg", 32 | "height": 31722, 33 | "width": 70399, 34 | "service": [ 35 | { 36 | "id": "https://iiif.io/api/image/3.0/example/reference/36ca0a3370db128ec984b33d71a1543d-100320001004", 37 | "profile": "level1", 38 | "type": "ImageService3" 39 | } 40 | ] 41 | }, 42 | "target": "https://iiif.io/api/cookbook/recipe/0346-multilingual-annotation-body/canvas/p1" 43 | } 44 | ] 45 | } 46 | ], 47 | "annotations": [ 48 | { 49 | "id": "https://iiif.io/api/cookbook/recipe/0346-multilingual-annotation-body/page/p2/1", 50 | "type": "AnnotationPage", 51 | "items": [ 52 | { 53 | "id": "https://iiif.io/api/cookbook/recipe/0346-multilingual-annotation-body/annotation/p0001-comment", 54 | "type": "Annotation", 55 | "motivation": "commenting", 56 | "body": { 57 | "type": "Choice", 58 | "items": [ 59 | { 60 | "type": "TextualBody", 61 | "value": "Koto with a cover being carried", 62 | "language": "en", 63 | "format": "text/plain" 64 | }, 65 | { 66 | "type": "TextualBody", 67 | "value": "袋に収められた琴", 68 | "language": "ja", 69 | "format": "text/plain" 70 | } 71 | ] 72 | }, 73 | "target": "https://iiif.io/api/cookbook/recipe/0346-multilingual-annotation-body/canvas/p1#xywh=1650,1200,925,1250" 74 | } 75 | ] 76 | } 77 | ] 78 | } 79 | ] 80 | } -------------------------------------------------------------------------------- /fixtures/cookbook/0377-image-in-annotation.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/3/context.json", 3 | "id": "https://iiif.io/api/cookbook/recipe/0377-image-in-annotation/manifest.json", 4 | "type": "Manifest", 5 | "label": { 6 | "en": [ 7 | "Picture of Göttingen taken during the 2019 IIIF Conference" 8 | ] 9 | }, 10 | "items": [ 11 | { 12 | "id": "https://iiif.io/api/cookbook/recipe/0377-image-in-annotation/canvas-1", 13 | "type": "Canvas", 14 | "height": 3024, 15 | "width": 4032, 16 | "items": [ 17 | { 18 | "id": "https://iiif.io/api/cookbook/recipe/0377-image-in-annotation/canvas-1/annopage-1", 19 | "type": "AnnotationPage", 20 | "items": [ 21 | { 22 | "id": "https://iiif.io/api/cookbook/recipe/0377-image-in-annotation/canvas-1/annopage-1/anno-1", 23 | "type": "Annotation", 24 | "motivation": "painting", 25 | "body": { 26 | "id": "https://iiif.io/api/image/3.0/example/reference/918ecd18c2592080851777620de9bcb5-gottingen/full/max/0/default.jpg", 27 | "type": "Image", 28 | "format": "image/jpeg", 29 | "height": 3024, 30 | "width": 4032, 31 | "service": [ 32 | { 33 | "id": "https://iiif.io/api/image/3.0/example/reference/918ecd18c2592080851777620de9bcb5-gottingen", 34 | "profile": "level1", 35 | "type": "ImageService3" 36 | } 37 | ] 38 | }, 39 | "target": "https://iiif.io/api/cookbook/recipe/0377-image-in-annotation/canvas-1" 40 | } 41 | ] 42 | } 43 | ], 44 | "annotations": [ 45 | { 46 | "id": "https://iiif.io/api/cookbook/recipe/0377-image-in-annotation/canvas-1/annopage-2", 47 | "type": "AnnotationPage", 48 | "items": [ 49 | { 50 | "id": "https://iiif.io/api/cookbook/recipe/0377-image-in-annotation/canvas-1/annopage-2/anno-1", 51 | "type": "Annotation", 52 | "motivation": "commenting", 53 | "body": [ 54 | { 55 | "id": "https://iiif.io/api/image/3.0/example/reference/918ecd18c2592080851777620de9bcb5-fountain/full/300,/0/default.jpg", 56 | "type": "Image", 57 | "format": "image/jpeg" 58 | }, 59 | { 60 | "type": "TextualBody", 61 | "language": "en", 62 | "value": "Night picture of the Gänseliesel fountain in Göttingen taken during the 2019 IIIF Conference" 63 | } 64 | ], 65 | "target": "https://iiif.io/api/cookbook/recipe/0377-image-in-annotation/canvas-1#xywh=138,550,1477,1710" 66 | } 67 | ] 68 | } 69 | ] 70 | } 71 | ] 72 | } -------------------------------------------------------------------------------- /fixtures/cookbook/0434-choice-av.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/3/context.json", 3 | "id": "https://iiif.io/api/cookbook/recipe/0434-choice-av/manifest.json", 4 | "type": "Manifest", 5 | "label": { 6 | "en": [ 7 | "Excerpt from Egbe Iyawo" 8 | ] 9 | }, 10 | "summary": { 11 | "en": [ 12 | "Excerpt from a performance of Egbe Iyawo recorded in Kabba Division, Kwara State. " 13 | ] 14 | }, 15 | "rights": "http://creativecommons.org/publicdomain/zero/1.0/", 16 | "items": [ 17 | { 18 | "id": "https://iiif.io/api/cookbook/recipe/0434-choice-av/canvas/1", 19 | "type": "Canvas", 20 | "duration": 16.0, 21 | "items": [ 22 | { 23 | "id": "https://iiif.io/api/cookbook/recipe/0434-choice-av/canvas/1/annotation_page/1", 24 | "type": "AnnotationPage", 25 | "items": [ 26 | { 27 | "id": "https://iiif.io/api/cookbook/recipe/0434-choice-av/canvas/1/annotation_page/1/annotation/1", 28 | "type": "Annotation", 29 | "motivation": "painting", 30 | "target": "https://iiif.io/api/cookbook/recipe/0434-choice-av/canvas/1", 31 | "body": { 32 | "type": "Choice", 33 | "items": [ 34 | { 35 | "id": "https://fixtures.iiif.io/audio/ucla/egbe-iyawo-ucla.m4a", 36 | "type": "Sound", 37 | "format": "audio/alac", 38 | "duration": 16.0, 39 | "label": { 40 | "en": [ 41 | "ALAC" 42 | ] 43 | } 44 | }, 45 | { 46 | "id": "https://fixtures.iiif.io/audio/ucla/egbe-iyawo-ucla.mp3", 47 | "type": "Sound", 48 | "format": "audio/mpeg", 49 | "duration": 16.0, 50 | "label": { 51 | "en": [ 52 | "MP3" 53 | ] 54 | } 55 | }, 56 | { 57 | "id": "https://fixtures.iiif.io/audio/ucla/egbe-iyawo-ucla.flac", 58 | "type": "Sound", 59 | "format": "audio/flac", 60 | "duration": 16.0, 61 | "label": { 62 | "en": [ 63 | "FLAC" 64 | ] 65 | } 66 | }, 67 | { 68 | "id": "https://fixtures.iiif.io/audio/ucla/egbe-iyawo-ucla.ogg", 69 | "type": "Sound", 70 | "format": "audio/ogg", 71 | "duration": 16.0, 72 | "label": { 73 | "en": [ 74 | "OGG Vorbis OGG" 75 | ] 76 | } 77 | }, 78 | { 79 | "id": "https://fixtures.iiif.io/audio/ucla/egbe-iyawo-ucla.mpeg", 80 | "type": "Sound", 81 | "format": "audio/mpeg", 82 | "duration": 16.0, 83 | "label": { 84 | "en": [ 85 | "MPEG2" 86 | ] 87 | } 88 | }, 89 | { 90 | "id": "https://fixtures.iiif.io/audio/ucla/egbe-iyawo-ucla.wav", 91 | "type": "Sound", 92 | "format": "audio/wav", 93 | "duration": 16.0, 94 | "label": { 95 | "en": [ 96 | "WAV" 97 | ] 98 | } 99 | } 100 | ] 101 | } 102 | } 103 | ] 104 | } 105 | ] 106 | } 107 | ] 108 | } -------------------------------------------------------------------------------- /fixtures/cookbook/0485-contentstate-canvas-region-annotation.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/3/context.json", 3 | "id": "https://example.org/import/1", 4 | "type": "Annotation", 5 | "motivation": [ 6 | "contentState" 7 | ], 8 | "target": { 9 | "id": "https://iiif.io/api/cookbook/recipe/0009-book-1/canvas/p2#xywh=1528,3024,344,408", 10 | "type": "Canvas", 11 | "partOf": [ 12 | { 13 | "id": "https://iiif.io/api/cookbook/recipe/0009-book-1/manifest.json", 14 | "type": "Manifest" 15 | } 16 | ] 17 | } 18 | } -------------------------------------------------------------------------------- /fixtures/cookbook/0540-link-for-opening-multiple-canvases-annotation.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/3/context.json", 3 | "id": "https://iiif.io/api/cookbook/recipe/0540-link-for-opening-multiple-canvases/annotation.json", 4 | "type": "Annotation", 5 | "motivation": [ 6 | "contentState" 7 | ], 8 | "target": [ 9 | { 10 | "id": "https://iiif.io/api/cookbook/recipe/0540-link-for-opening-multiple-canvases/canvas/2", 11 | "type": "Canvas", 12 | "partOf": [ 13 | { 14 | "id": "https://iiif.io/api/cookbook/recipe/0540-link-for-opening-multiple-canvases/manifest-2.json", 15 | "type": "Manifest" 16 | } 17 | ] 18 | }, 19 | { 20 | "id": "https://iiif.io/api/cookbook/recipe/0540-link-for-opening-multiple-canvases/canvas/p2", 21 | "type": "Canvas", 22 | "partOf": [ 23 | { 24 | "id": "https://iiif.io/api/cookbook/recipe/0540-link-for-opening-multiple-canvases/manifest.json", 25 | "type": "Manifest" 26 | } 27 | ] 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /fixtures/presentation-2/anno_list_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context" : "http://iiif.io/api/presentation/2/context.json", 3 | "@id" : "https://collections.recolnat.org/annotate-server/iiif/2/annotationList/30/febbe57672bb5eaf097de6c5448c43ad8a82e012/list", 4 | "@type" : "sc:AnnotationList", 5 | "resources" : [ 6 | { 7 | "@id" : "https://collections.recolnat.org/annotate-server/iiif/2/annotationList/30/febbe57672bb5eaf097de6c5448c43ad8a82e012/list/1", 8 | "@type" : "oa:Annotation", 9 | "motivation" : ["oa:commenting", "oa:tagging"], 10 | "resource" : [ 11 | { 12 | "@type": "dctypes:Text", 13 | "chars" : "

Zone of interest
Place
Cauca of Huila

", 14 | "format" : "text/html" 15 | }, 16 | { 17 | "@type" : "oa:Tag", 18 | "chars" : "Colombia" 19 | }, 20 | { 21 | "@type" : "oa:Tag", 22 | "chars" : "Cauca" 23 | } 24 | ], 25 | "on" : { 26 | "@id" : "https://collections.recolnat.org/annotate-server/iiif/2/resource/1", 27 | "@type" : "oa:SpecificResource", 28 | "full" : "https://collections.recolnat.org/annotate-server/iiif/2/canvases/canvas/1", 29 | "selector" : { 30 | "@type" : "oa:Choice", 31 | "default" : { 32 | "@type" : "oa:FragmentSelector", 33 | "value" : "xywh=2458.3294117573255,4064.0775928284547,148.5859420675797,52.27032787325061" 34 | }, 35 | "item" : { 36 | "@type" : "oa:SvgSelector", 37 | "value" : "" 38 | } 39 | }, 40 | "within" : { 41 | "@type" : "sc:Manifest", 42 | "@id" : "https://collections.recolnat.org/annotate-server/iiif/2/30/manifest" 43 | } 44 | }, 45 | "label" : "label-Place", 46 | "metadata": [ 47 | { 48 | "label": "Note", 49 | "value": "Une note complémentaire à propos de cette annotation" 50 | } 51 | ] 52 | }, 53 | { 54 | "@id" : "https://collections.recolnat.org/annotate-server/iiif/2/annotationList/30/febbe57672bb5eaf097de6c5448c43ad8a82e012/list/3", 55 | "@type" : "oa:Annotation", 56 | "motivation" : "oa:commenting", 57 | "resource" : { 58 | "@type" : "dctypes:Text", 59 | "chars" : "

Surface tool
Feuille
70.97 mm²

", 60 | "format" : "text/html" 61 | }, 62 | "on" : { 63 | "@id" : "https://collections.recolnat.org/annotate-server/iiif/2/resource/3", 64 | "@type": "oa:SpecificResource", 65 | "full": "https://collections.recolnat.org/annotate-server/iiif/2/canvases/canvas/1", 66 | "selector": { 67 | "@type": "oa:Choice", 68 | "default" : { 69 | "@type": "oa:FragmentSelector", 70 | "value": "xywh=1437.1021112372669,1013.9299384835548,129.10014843443582,234.9981533683548" 71 | }, 72 | "item": { 73 | "@type": "oa:SvgSelector", 74 | "value": "" 75 | } 76 | }, 77 | "within": { 78 | "@type": "sc:Manifest", 79 | "@id": "https://collections.recolnat.org/annotate-server/iiif/2/30/manifest" 80 | } 81 | }, 82 | "label": "label-Feuille" 83 | } 84 | ] 85 | } -------------------------------------------------------------------------------- /fixtures/presentation-2/duplicate-member-collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/2/context.json", 3 | "@id": "https://iiif.harvardartmuseums.org/collections/", 4 | "@type": "sc:Collection", 5 | "collections": [ 6 | { 7 | "@id": "https://iiif.harvardartmuseums.org/collections/object", 8 | "@type": "sc:Collection", 9 | "label": "Objects" 10 | } 11 | ], 12 | "label": "Harvard Art Museums Collections", 13 | "members": [ 14 | { 15 | "@id": "https://iiif.harvardartmuseums.org/collections/object", 16 | "@type": "sc:Collection", 17 | "label": "Objects", 18 | "viewingHint": "individuals" 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /fixtures/presentation-2/europeana.json: -------------------------------------------------------------------------------- 1 | { 2 | "@type": "sc:Canvas", 3 | "@id": "https://iiif.europeana.eu/presentation/9200396/BibliographicResource_3000118436165/canvas/p21", 4 | "label": "p. 21", 5 | "height": 2048, 6 | "width": 1256, 7 | "attribution": "Journal historique et littéraire - 1788-09-01 - https://www.europeana.eu/item/9200396/BibliographicResource_3000118436165. National Library of Luxembourg. Public Domain Mark - http://creativecommons.org/publicdomain/mark/1.0/", 8 | "images": [ 9 | { 10 | "@type": "oa:Annotation", 11 | "motivation": "sc:painting", 12 | "resource": { 13 | "@type": "dctypes:Image", 14 | "@id": "https://iiif.europeana.eu/image/QNYVL2Z2FVHRGMK2UNXIP4DUUOTOVA2ND3WIPJQF6V23SO2CJ5UA/presentation_images/ee0edfa0-0220-11e6-a696-fa163e2dd531/node-3/image/BNL/Journal_historique_et_littéraire/1788/09/01/00021/full/full/0/default.jpg", 15 | "format": "image/jpeg", 16 | "service": { 17 | "@context": "http://iiif.io/api/image/2/context.json", 18 | "@id": "https://iiif.europeana.eu/image/QNYVL2Z2FVHRGMK2UNXIP4DUUOTOVA2ND3WIPJQF6V23SO2CJ5UA/presentation_images/ee0edfa0-0220-11e6-a696-fa163e2dd531/node-3/image/BNL/Journal_historique_et_littéraire/1788/09/01/00021", 19 | "profile": "http://iiif.io/api/image/2/level1.json" 20 | } 21 | }, 22 | "on": "https://iiif.europeana.eu/presentation/9200396/BibliographicResource_3000118436165/canvas/p21" 23 | } 24 | ], 25 | "otherContent": [ 26 | "https://iiif.europeana.eu/presentation/9200396/BibliographicResource_3000118436165/annopage/21" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /fixtures/presentation-2/iiif-fixture-annotation-list.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/2/context.json", 3 | "@id": "http://iiif.io/api/presentation/2.0/example/fixtures/list/65/list1.json", 4 | "@type": "sc:AnnotationList", 5 | "label": "Test 65 List 1", 6 | "resources": [ 7 | { 8 | "@type": "oa:Annotation", 9 | "motivation": "sc:painting", 10 | "resource": { 11 | "@type": "cnt:ContentAsText", 12 | "chars": "Top of First Page to Display" 13 | }, 14 | "on": { 15 | "@type": "oa:SpecificResource", 16 | "full": { 17 | "@id": "http://iiif.io/api/presentation/2.0/example/fixtures/canvas/65/c1.json", 18 | "@type": "sc:Canvas", 19 | "label": "Test 65 Canvas: 1" 20 | }, 21 | "selector": { 22 | "@type": "oa:FragmentSelector", 23 | "value": "xywh=225,70,750,150" 24 | } 25 | } 26 | }, 27 | { 28 | "@type": "oa:Annotation", 29 | "motivation": ["oa:tagging", "oa:commenting"], 30 | "resource": [ 31 | { 32 | "@type": "oa:Tag", 33 | "chars": "character" 34 | }, 35 | { 36 | "@type": "dctypes:Text", 37 | "format": "text/html", 38 | "chars": "

\n

" 39 | } 40 | ], 41 | "on": [ 42 | { 43 | "@type": "oa:SpecificResource", 44 | "full": "https://purl.stanford.edu/ch264fq0568/iiif/canvas/ch264fq0568_1", 45 | "selector": { 46 | "@type": "oa:Choice", 47 | "default": { 48 | "@type": "oa:FragmentSelector", 49 | "value": "xywh=3112,1063,518,656" 50 | }, 51 | "item": { 52 | "@type": "oa:SvgSelector", 53 | "value": "" 54 | } 55 | } 56 | } 57 | ] 58 | } 59 | ] 60 | } 61 | -------------------------------------------------------------------------------- /fixtures/presentation-2/iiif-fixture-manifest-with-dimensions.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": "Test 1 Manifest: Minimum Required Fields", 6 | "within": "http://iiif.io/api/presentation/2.1/example/fixtures/collection.json", 7 | "related": { 8 | "@id": "http://example.org/videos/video-book1.mpg", 9 | "format": "video/mpeg" 10 | }, 11 | "rendering": { 12 | "@id": "http://example.org/iiif/book1.pdf", 13 | "label": "Download as PDF", 14 | "format": "application/pdf" 15 | }, 16 | "seeAlso": { 17 | "@id": "http://example.org/library/catalog/book1.marc", 18 | "format": "application/marc", 19 | "profile": "http://example.org/profiles/marc21" 20 | }, 21 | "service": { 22 | "@context": "http://iiif.io/api/annex/services/physdim/1/context.json", 23 | "profile": "http://iiif.io/api/annex/services/physdim", 24 | "@id": "http://example.org/iiif/manifest/1/dims", 25 | "physicalScale": 0.0025, 26 | "physicalUnits": "in" 27 | }, 28 | "sequences": [ 29 | { 30 | "@type": "sc:Sequence", 31 | "canvases": [ 32 | { 33 | "@id": "http://iiif.io/api/presentation/2.1/example/fixtures/canvas/1/c1.json", 34 | "@type": "sc:Canvas", 35 | "label": "Test 1 Canvas: 1", 36 | "height": 1800, 37 | "width": 1200, 38 | "images": [ 39 | { 40 | "@type": "oa:Annotation", 41 | "motivation": "sc:painting", 42 | "resource": { 43 | "@id": "http://iiif.io/api/presentation/2.1/example/fixtures/resources/page1-full.png", 44 | "@type": "dctypes:Image", 45 | "height": 1800, 46 | "width": 1200 47 | }, 48 | "on": "http://iiif.io/api/presentation/2.1/example/fixtures/canvas/1/c1.json" 49 | } 50 | ] 51 | } 52 | ] 53 | } 54 | ] 55 | } 56 | -------------------------------------------------------------------------------- /fixtures/presentation-2/iiif-fixture-manifest.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": "Test 1 Manifest: Minimum Required Fields", 6 | "within": "http://iiif.io/api/presentation/2.1/example/fixtures/collection.json", 7 | "sequences": [ 8 | { 9 | "@type": "sc:Sequence", 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 | "height": 1800, 25 | "width": 1200 26 | }, 27 | "on": "http://iiif.io/api/presentation/2.1/example/fixtures/canvas/1/c1.json" 28 | } 29 | ] 30 | } 31 | ] 32 | } 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /fixtures/presentation-2/nls-collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/2/context.json", 3 | "@id": "https://view.nls.uk/collections/7446/74462370.json", 4 | "@type": "sc:Collection", 5 | "label": "First World War 'Official Photographs'", 6 | "manifests": [ 7 | { 8 | "@id": "https://view.nls.uk/manifest/7517/75171407/manifest.json", 9 | "@type": "sc:Manifest", 10 | "label": "Ernest Brooks" 11 | }, 12 | { 13 | "@id": "https://view.nls.uk/manifest/7517/75171408/manifest.json", 14 | "@type": "sc:Manifest", 15 | "label": "John Warwick Brooke" 16 | }, 17 | { 18 | "@id": "https://view.nls.uk/manifest/7517/75171409/manifest.json", 19 | "@type": "sc:Manifest", 20 | "label": "Tom Aitken" 21 | }, 22 | { 23 | "@id": "https://view.nls.uk/manifest/7517/75171410/manifest.json", 24 | "@type": "sc:Manifest", 25 | "label": "David McLellan" 26 | }, 27 | { 28 | "@id": "https://view.nls.uk/manifest/7520/75201623/manifest.json", 29 | "@type": "sc:Manifest", 30 | "label": "Photographs of Haig" 31 | }, 32 | { 33 | "@id": "https://view.nls.uk/manifest/7520/75201640/manifest.json", 34 | "@type": "sc:Manifest", 35 | "label": "After the War" 36 | }, 37 | { 38 | "@id": "https://view.nls.uk/manifest/7520/75201641/manifest.json", 39 | "@type": "sc:Manifest", 40 | "label": "British Western Front" 41 | }, 42 | { 43 | "@id": "https://view.nls.uk/manifest/7520/75201649/manifest.json", 44 | "@type": "sc:Manifest", 45 | "label": "Germans" 46 | }, 47 | { 48 | "@id": "https://view.nls.uk/manifest/7520/75201717/manifest.json", 49 | "@type": "sc:Manifest", 50 | "label": "Royal visits" 51 | }, 52 | { 53 | "@id": "https://view.nls.uk/manifest/7520/75206933/manifest.json", 54 | "@type": "sc:Manifest", 55 | "label": "Cavalry" 56 | }, 57 | { 58 | "@id": "https://view.nls.uk/manifest/7517/75172459/manifest.json", 59 | "@type": "sc:Manifest", 60 | "label": "French" 61 | }, 62 | { 63 | "@id": "https://view.nls.uk/manifest/7517/75172559/manifest.json", 64 | "@type": "sc:Manifest", 65 | "label": "Canadian and Newfoundland" 66 | }, 67 | { 68 | "@id": "https://view.nls.uk/manifest/7520/75208534/manifest.json", 69 | "@type": "sc:Manifest", 70 | "label": "South African" 71 | }, 72 | { 73 | "@id": "https://view.nls.uk/manifest/7520/75209726/manifest.json", 74 | "@type": "sc:Manifest", 75 | "label": "Chinese" 76 | }, 77 | { 78 | "@id": "https://view.nls.uk/manifest/7520/75209781/manifest.json", 79 | "@type": "sc:Manifest", 80 | "label": "American" 81 | }, 82 | { 83 | "@id": "https://view.nls.uk/manifest/7521/75210000/manifest.json", 84 | "@type": "sc:Manifest", 85 | "label": "Australian and New Zealand" 86 | }, 87 | { 88 | "@id": "https://view.nls.uk/manifest/7521/75210064/manifest.json", 89 | "@type": "sc:Manifest", 90 | "label": "Portuguese" 91 | }, 92 | { 93 | "@id": "https://view.nls.uk/manifest/7521/75210218/manifest.json", 94 | "@type": "sc:Manifest", 95 | "label": "Belgian" 96 | }, 97 | { 98 | "@id": "https://view.nls.uk/manifest/7521/75210267/manifest.json", 99 | "@type": "sc:Manifest", 100 | "label": "East Indian" 101 | }, 102 | { 103 | "@id": "https://view.nls.uk/manifest/7521/75210287/manifest.json", 104 | "@type": "sc:Manifest", 105 | "label": "Other visitors" 106 | } 107 | ] 108 | } 109 | -------------------------------------------------------------------------------- /fixtures/presentation-2/paginated-collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/2/context.json", 3 | "@id": "https://api.digitale-sammlungen.de/iiif/presentation/v2/collection/top", 4 | "@type": "sc:Collection", 5 | "label": "Top Level Collection for BSB Digital Collections", 6 | "first": "https://api.digitale-sammlungen.de/iiif/presentation/v2/collection/top?cursor=initial", 7 | "total": 3074231, 8 | "attribution": "Bayerische Staatsbibliothek", 9 | "viewingHint": "top" 10 | } 11 | -------------------------------------------------------------------------------- /fixtures/presentation-2/wellcome-collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "@id": "https://iiif.wellcomecollection.org/presentation/v2/collections/genres/Abstracts", 3 | "@type": "sc:Collection", 4 | "label": "Genre: Abstracts", 5 | "members": [ 6 | { 7 | "@id": "https://iiif.wellcomecollection.org/presentation/v2/b29011577", 8 | "@type": "sc:Manifest", 9 | "label": "Titres et travaux scientifiques du Dr F. Lejars." 10 | }, 11 | { 12 | "@id": "https://iiif.wellcomecollection.org/presentation/v2/b32183859", 13 | "@type": "sc:Manifest", 14 | "label": "Annotated bibliography on vital and health statistics." 15 | }, 16 | { 17 | "@id": "https://iiif.wellcomecollection.org/presentation/v2/b21782763", 18 | "@type": "sc:Manifest", 19 | "label": "Notice sur les travaux scientifiques de M. Henri Becquerel." 20 | }, 21 | { 22 | "@id": "https://iiif.wellcomecollection.org/presentation/v2/b29011553", 23 | "@type": "sc:Manifest", 24 | "label": "Notice sur les titres et travaux scientifiques de M. Louis Lapicque." 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /fixtures/presentation-3/accompanying-canvas.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/3/context.json", 3 | "id": "https://iiif.io/api/cookbook/recipe/0014-accompanyingcanvas/manifest.json", 4 | "type": "Manifest", 5 | "label": { 6 | "en": [ 7 | "Partial audio recording of Gustav Mahler's _Symphony No. 3_" 8 | ] 9 | }, 10 | "items": [ 11 | { 12 | "id": "https://iiif.io/api/cookbook/recipe/0014-accompanyingcanvas/canvas/p1", 13 | "type": "Canvas", 14 | "label": { 15 | "en": [ 16 | "Gustav Mahler, Symphony No. 3, CD 1" 17 | ] 18 | }, 19 | "duration": 1985.024, 20 | "accompanyingCanvas": { 21 | "id": "https://iiif.io/api/cookbook/recipe/0014-accompanyingcanvas/canvas/accompanying", 22 | "type": "Canvas", 23 | "label": { 24 | "en": [ 25 | "First page of score for Gustav Mahler, Symphony No. 3" 26 | ] 27 | }, 28 | "height": 998, 29 | "width": 772, 30 | "items": [ 31 | { 32 | "id": "https://iiif.io/api/cookbook/recipe/0014-accompanyingcanvas/canvas/accompanying/annotation/page", 33 | "type": "AnnotationPage", 34 | "items": [ 35 | { 36 | "id": "https://iiif.io/api/cookbook/recipe/0014-accompanyingcanvas/canvas/accompanying/annotation/image", 37 | "type": "Annotation", 38 | "motivation": "painting", 39 | "body": { 40 | "id": "https://iiif.io/api/image/3.0/example/reference/4b45bba3ea612ee46f5371ce84dbcd89-mahler-0/full/,998/0/default.jpg", 41 | "type": "Image", 42 | "format": "image/jpeg", 43 | "height": 998, 44 | "width": 772, 45 | "service": [ 46 | { 47 | "id": "https://iiif.io/api/image/3.0/example/reference/4b45bba3ea612ee46f5371ce84dbcd89-mahler-0", 48 | "type": "ImageService3", 49 | "profile": "level1" 50 | } 51 | ] 52 | }, 53 | "target": "https://iiif.io/api/cookbook/recipe/0014-accompanyingcanvas/canvas/accompanying" 54 | } 55 | ] 56 | } 57 | ] 58 | }, 59 | "items": [ 60 | { 61 | "id": "https://iiif.io/api/cookbook/recipe/0014-accompanyingcanvas/canvas/page/p1", 62 | "type": "AnnotationPage", 63 | "items": [ 64 | { 65 | "id": "https://iiif.io/api/cookbook/recipe/0014-accompanyingcanvas/canvas/page/annotation/segment1-audio", 66 | "type": "Annotation", 67 | "motivation": "painting", 68 | "body": { 69 | "id": "https://fixtures.iiif.io/audio/indiana/mahler-symphony-3/CD1/medium/128Kbps.mp4", 70 | "type": "Sound", 71 | "duration": 1985.024, 72 | "format": "video/mp4" 73 | }, 74 | "target": "https://iiif.io/api/cookbook/recipe/0014-accompanyingcanvas/canvas/page/p1" 75 | } 76 | ] 77 | } 78 | ] 79 | } 80 | ] 81 | } 82 | -------------------------------------------------------------------------------- /fixtures/presentation-3/has-part.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://iiif.io/api/presentation/3/context.json", 3 | "id": "https://iiif.io/api/cookbook/recipe/0005-image-service/manifest.json", 4 | "type": "Manifest", 5 | "label": { 6 | "en": [ 7 | "Picture of Göttingen taken during the 2019 IIIF Conference" 8 | ] 9 | }, 10 | "items": [ 11 | { 12 | "id": "https://iiif.io/api/cookbook/recipe/0005-image-service/canvas/p1", 13 | "type": "Canvas", 14 | "label": { 15 | "en": [ 16 | "Canvas with a single IIIF image" 17 | ] 18 | }, 19 | "thumbnail": [ 20 | { 21 | "id": "https://iiif.io/api/image/3.0/example/reference/918ecd18c2592080851777620de9bcb5-gottingen/full/max/0/default.jpg", 22 | "type": "Image", 23 | "format": "image/jpeg" 24 | } 25 | ], 26 | "height": 3024, 27 | "width": 4032, 28 | "items": [ 29 | { 30 | "id": "https://iiif.io/api/cookbook/recipe/0005-image-service/page/p1/1", 31 | "type": "AnnotationPage", 32 | "items": [ 33 | { 34 | "id": "https://iiif.io/api/cookbook/recipe/0005-image-service/annotation/p0001-image", 35 | "type": "Annotation", 36 | "motivation": "painting", 37 | "body": { 38 | "id": "https://iiif.io/api/image/3.0/example/reference/918ecd18c2592080851777620de9bcb5-gottingen/full/max/0/default.jpg", 39 | "type": "Image", 40 | "format": "image/jpeg", 41 | "height": 3024, 42 | "width": 4032, 43 | "service": [ 44 | { 45 | "id": "https://iiif.io/api/image/3.0/example/reference/918ecd18c2592080851777620de9bcb5-gottingen", 46 | "profile": "level1", 47 | "type": "ImageService3" 48 | } 49 | ] 50 | }, 51 | "target": "https://iiif.io/api/cookbook/recipe/0005-image-service/canvas/p1" 52 | } 53 | ] 54 | } 55 | ] 56 | } 57 | ] 58 | } 59 | -------------------------------------------------------------------------------- /fixtures/stores/delft-collection-store.json: -------------------------------------------------------------------------------- 1 | { 2 | "iiif": { 3 | "mapping": { 4 | "https://delft-static-site-generator.netlify.com/collections/lib-tr-universiteitsgeschiedenis": "Collection" 5 | }, 6 | "entities": { 7 | "Collection": { 8 | "https://delft-static-site-generator.netlify.com/collections/lib-tr-universiteitsgeschiedenis": { 9 | "id": "https://delft-static-site-generator.netlify.com/collections/lib-tr-universiteitsgeschiedenis", 10 | "type": "Collection", 11 | "label": { 12 | "en": [ 13 | "History of the university" 14 | ], 15 | "nl": [ 16 | "Universiteitsgeschiedenis" 17 | ] 18 | }, 19 | "viewingDirection": "left-to-right", 20 | "behavior": [], 21 | "thumbnail": [], 22 | "accompanyingCanvas": null, 23 | "placeholderCanvas": null, 24 | "summary": { 25 | "en": [ 26 | "Digitised books about the history of Delft University of Technology, from TU Delft Library's Special Collections" 27 | ], 28 | "nl": [ 29 | "Gedigitaliseerde boeken over de geschiedenis van de TU Delft, afkomstig uit de Bijzondere Collecties van de TU Delft Library" 30 | ] 31 | }, 32 | "requiredStatement": null, 33 | "metadata": [ 34 | { 35 | "label": { 36 | "none": [ 37 | "" 38 | ] 39 | }, 40 | "value": { 41 | "none": [ 42 | "" 43 | ] 44 | } 45 | } 46 | ], 47 | "rights": null, 48 | "navDate": null, 49 | "provider": [], 50 | "items": [], 51 | "annotations": [], 52 | "seeAlso": [], 53 | "homepage": [], 54 | "partOf": [], 55 | "rendering": [], 56 | "service": [], 57 | "services": [], 58 | "@context": [ 59 | "http://www.w3.org/ns/anno.jsonld", 60 | "http://iiif.io/api/presentation/3/context.json" 61 | ], 62 | "iiif-parser:isExternal": true, 63 | "iiif-parser:hasPart": [ 64 | { 65 | "iiif-parser:partOf": "https://delft-static-site-generator.netlify.com/collections/lib-tr-universiteitsgeschiedenis", 66 | "id": "https://delft-static-site-generator.netlify.com/collections/lib-tr-universiteitsgeschiedenis", 67 | "type": "Collection" 68 | } 69 | ] 70 | } 71 | }, 72 | "Manifest": {}, 73 | "Canvas": {}, 74 | "AnnotationPage": {}, 75 | "AnnotationCollection": {}, 76 | "Annotation": {}, 77 | "ContentResource": {}, 78 | "Range": {}, 79 | "Service": {}, 80 | "Selector": {}, 81 | "Agent": {} 82 | }, 83 | "requests": { 84 | "https://delft-static-site-generator.netlify.com/collections/lib-tr-universiteitsgeschiedenis": { 85 | "requestUri": "https://delft-static-site-generator.netlify.com/collections/lib-tr-universiteitsgeschiedenis", 86 | "loadingState": "RESOURCE_READY", 87 | "uriMismatch": false, 88 | "resourceUri": "https://delft-static-site-generator.netlify.com/collections/lib-tr-universiteitsgeschiedenis" 89 | } 90 | }, 91 | "meta": {} 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@iiif/parser", 3 | "version": "2.2.0", 4 | "license": "MIT", 5 | "type": "module", 6 | "description": "IIIF Presentation 2 and 3 parsing utilities", 7 | "bugs": "https://github.com/iiif-commons/parser/issues", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/iiif-commons/parser" 11 | }, 12 | "main": "dist/index.cjs", 13 | "module": "dist/index.js", 14 | "types": "dist/index.d.ts", 15 | "files": [ 16 | "dist" 17 | ], 18 | "exports": { 19 | ".": { 20 | "require": { 21 | "types": "./dist/index.d.cts", 22 | "default": "./dist/index.cjs" 23 | }, 24 | "import": { 25 | "types": "./dist/index.d.ts", 26 | "default": "./dist/index.js" 27 | } 28 | }, 29 | "./presentation-2": { 30 | "require": { 31 | "types": "./dist/presentation-2.d.cts", 32 | "default": "./dist/presentation-2.cjs" 33 | }, 34 | "import": { 35 | "types": "./dist/presentation-2.d.ts", 36 | "default": "./dist/presentation-2.js" 37 | } 38 | }, 39 | "./presentation-3": { 40 | "require": { 41 | "types": "./dist/presentation-3.d.cts", 42 | "default": "./dist/presentation-3.cjs" 43 | }, 44 | "import": { 45 | "types": "./dist/presentation-3.d.ts", 46 | "default": "./dist/presentation-3.js" 47 | } 48 | }, 49 | "./strict": { 50 | "require": { 51 | "types": "./dist/strict.d.cts", 52 | "default": "./dist/strict.cjs" 53 | }, 54 | "import": { 55 | "types": "./dist/strict.d.ts", 56 | "default": "./dist/strict.js" 57 | } 58 | }, 59 | "./image-3": { 60 | "require": { 61 | "types": "./dist/image-3.d.cts", 62 | "default": "./dist/image-3.cjs" 63 | }, 64 | "import": { 65 | "types": "./dist/image-3.d.ts", 66 | "default": "./dist/image-3.js" 67 | } 68 | }, 69 | "./upgrader": { 70 | "require": { 71 | "types": "./dist/upgrader.d.cts", 72 | "default": "./dist/upgrader.cjs" 73 | }, 74 | "import": { 75 | "types": "./dist/upgrader.d.ts", 76 | "default": "./dist/upgrader.js" 77 | } 78 | } 79 | }, 80 | "scripts": { 81 | "build": "tsup", 82 | "dev": "tsup --watch", 83 | "typecheck": "tsc --noEmit", 84 | "test": "vitest", 85 | "prepack": "tsup", 86 | "lint": "publint" 87 | }, 88 | "dependencies": { 89 | "@iiif/presentation-2": "^1.0.4", 90 | "@iiif/presentation-3": "^2.2.2", 91 | "@iiif/presentation-3-normalized": "^0.9.7", 92 | "@types/geojson": "^7946.0.10" 93 | }, 94 | "devDependencies": { 95 | "@types/node": "^18", 96 | "@hyperion-framework/validator": "^1.1.0", 97 | "@happy-dom/global-registrator": "^16.3.0", 98 | "prettier": "^3.2.5", 99 | "tslib": "^2.6.2", 100 | "typescript": "^5.4.4", 101 | "vitest": "^2.1.8", 102 | "publint": "^0.2.7", 103 | "tsup": "^8.0.2" 104 | }, 105 | "sideEffects": false, 106 | "publishConfig": { 107 | "access": "public" 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /pkg-tests/README.md: -------------------------------------------------------------------------------- 1 | # Package tests 2 | 3 | Testing each of the build dist packages can be loaded correctly from Node. 4 | 5 | ``` 6 | node pkg-tests/node-load.cjs 7 | node pkg-tests/node-load.mjs 8 | node pkg-tests/node-umd.cjs 9 | ``` 10 | -------------------------------------------------------------------------------- /pkg-tests/node-load.cjs: -------------------------------------------------------------------------------- 1 | const parser3 = require('@iiif/parser'); 2 | const parser2 = require('@iiif/parser/presentation-2'); 3 | 4 | console.log(parser2.Traverse); 5 | console.log(parser3.Traverse); 6 | -------------------------------------------------------------------------------- /pkg-tests/node-load.mjs: -------------------------------------------------------------------------------- 1 | import { Traverse as Traverse3 } from '@iiif/parser/presentation-3'; 2 | import { Traverse as Traverse2 } from '@iiif/parser/presentation-2'; 3 | import * as Upgrader from '@iiif/parser/upgrader'; 4 | 5 | console.log({ Traverse3 }); 6 | console.log({ Traverse2 }); 7 | console.log(Upgrader); 8 | -------------------------------------------------------------------------------- /pkg-tests/node-umd.cjs: -------------------------------------------------------------------------------- 1 | const IIIFParser = require('../dist/index.global'); 2 | 3 | console.log(IIIFParser.Presentation2.Traverse); 4 | console.log(IIIFParser.Presentation3.Traverse); 5 | -------------------------------------------------------------------------------- /scripts/update-cookbook.mjs: -------------------------------------------------------------------------------- 1 | import { GlobalRegistrator } from '@happy-dom/global-registrator'; 2 | import { promises } from 'node:fs'; 3 | import { cwd } from 'node:process'; 4 | import { join } from 'node:path'; 5 | 6 | const { writeFile } = promises; 7 | 8 | const fetch = global.fetch; 9 | 10 | GlobalRegistrator.register(); 11 | 12 | const matcher = /https:\/\/iiif\.io\/api\/cookbook\/recipe\/([^\/.]*)\//; 13 | 14 | function getManifest(id) { 15 | return `https://iiif.io/api/cookbook/recipe/${id}/manifest.json`; 16 | } 17 | 18 | async function main() { 19 | const resp = await fetch('https://iiif.io/api/cookbook/recipe/matrix/'); 20 | 21 | const wrapper = document.createElement('div'); 22 | wrapper.innerHTML = await resp.text(); 23 | 24 | const elements = wrapper.querySelectorAll('.api-content table td a[href]'); 25 | const index = {}; 26 | 27 | for (const el of elements) { 28 | const link = el.getAttribute('href'); 29 | const matches = matcher.exec(link); 30 | if (matches && matches[1]) { 31 | let id = matches[1]; 32 | index[id] = { 33 | id: id, 34 | url: getManifest(id), 35 | }; 36 | console.log(id, getManifest(id)); 37 | } 38 | } 39 | 40 | const keys = Object.keys(index); 41 | const promises = []; 42 | for (const key of keys) { 43 | promises.push( 44 | await (async () => { 45 | const jsonHopefully = await (await fetch(index[key].url)).text(); 46 | 47 | if (false && jsonHopefully.trim().startsWith('{')) { 48 | await writeFile(join(cwd(), `fixtures/cookbook`, `${key}.json`), jsonHopefully); 49 | } else { 50 | if (index[key]) { 51 | delete index[key]; 52 | } 53 | 54 | // Otherwise.. 55 | const innerDocument = await fetch(`https://iiif.io/api/cookbook/recipe/${key}/`); 56 | const innerWrapper = document.createElement('div'); 57 | innerWrapper.innerHTML = await innerDocument.text(); 58 | const elements = innerWrapper.querySelectorAll('.content > p > a'); 59 | const headingEl = innerWrapper.querySelector('h1.title'); 60 | for (const el of elements) { 61 | if (el.innerHTML === 'JSON-LD') { 62 | const href = el.getAttribute('href'); 63 | const filenameParts = href.split('/'); 64 | const fileNameWithExtension = filenameParts[filenameParts.length - 1]; 65 | if (fileNameWithExtension.endsWith('.json')) { 66 | const fileName = fileNameWithExtension.slice(0, -5); 67 | const data = await (await fetch(`https://iiif.io/api/cookbook/recipe/${key}/${href}`)).text(); 68 | const realKey = fileName === 'manifest' ? `${key}` : `${key}-${fileName}`; 69 | 70 | await writeFile( 71 | join(cwd(), `fixtures/cookbook`, `${realKey}.json`), 72 | await (await fetch(`https://iiif.io/api/cookbook/recipe/${key}/${href}`)).text() 73 | ); 74 | 75 | index[realKey] = { 76 | id: realKey, 77 | url: `https://iiif.io/api/cookbook/recipe/${key}/${href}`, 78 | }; 79 | const heading = headingEl ? headingEl.innerHTML : 'Untitled'; 80 | console.log( 81 | JSON.stringify({ 82 | title: heading, 83 | cookbookUrl: `https://iiif.io/api/cookbook/recipe/${key}/`, 84 | url: `https://iiif.io/api/cookbook/recipe/${key}/${href}`, 85 | }) 86 | ); 87 | // console.log(`https://iiif.io/api/cookbook/recipe/${key}/${href}`); 88 | } 89 | } 90 | } 91 | } 92 | })() 93 | ); 94 | } 95 | 96 | await Promise.all(promises); 97 | 98 | await writeFile(join(cwd(), `fixtures/cookbook`, '_index.json'), JSON.stringify(index, null, 2)); 99 | } 100 | 101 | main().then(() => { 102 | console.log('done'); 103 | }); 104 | -------------------------------------------------------------------------------- /src/image-3/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types'; 2 | export * from './parser/parse-image-server-from-id'; 3 | export * from './parser/parse-image-service-request'; 4 | export * from './parser/parse-image-service-url'; 5 | export * from './parser/parse-region-parameter'; 6 | export * from './parser/parse-rotation-parameter'; 7 | export * from './parser/parse-size-parameter'; 8 | export * from './profiles/profiles'; 9 | export * from './profiles/combine-profiles'; 10 | export * from './profiles/level-to-profile'; 11 | export * from './profiles/is-level-0'; 12 | export * from './profiles/supports'; 13 | export * from './profiles/supports-custom-sizes'; 14 | export * from './profiles/image-service-supports-format'; 15 | export * from './profiles/image-service-supports-request'; 16 | export * from './serialize/image-service-request-to-string'; 17 | export * from './serialize/image-service-request-info'; 18 | export * from './serialize/region-parameter-to-string'; 19 | export * from './serialize/rotation-parameter-to-string'; 20 | export * from './serialize/size-parameter-to-string'; 21 | export * from './utilities/canonical-service-url'; 22 | export * from './utilities/create-image-service-request'; 23 | export * from './utilities/extract-fixed-size-scales'; 24 | export * from './utilities/fixed-sizes-from-scales'; 25 | export * from './utilities/get-id'; 26 | export * from './utilities/get-image-service-level'; 27 | export * from './utilities/get-image-services'; 28 | export * from './utilities/get-type'; 29 | export * from './utilities/identify-image-server'; 30 | export * from './utilities/is-image-service'; 31 | export * from './utilities/is-image-service-level'; 32 | -------------------------------------------------------------------------------- /src/image-3/parser/parse-image-server-from-id.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Get image server from ID. 3 | * 4 | * Normalises image service URLs to extract identity of the image server. 5 | * 6 | * @param url 7 | */ 8 | export function parseImageServerFromId(url: string): string { 9 | // Strip off the protocol + www 10 | const id = url.replace(/(https?:\/\/)?(www.)?/i, ''); 11 | 12 | // Strip off the path. 13 | if (id.indexOf('/') !== -1) { 14 | return id.split('/')[0]!; 15 | } 16 | 17 | // Return the id. 18 | return id; 19 | } 20 | -------------------------------------------------------------------------------- /src/image-3/parser/parse-image-service-request.ts: -------------------------------------------------------------------------------- 1 | import { parseRegionParameter } from './parse-region-parameter'; 2 | import { parseSizeParameter } from './parse-size-parameter'; 3 | import { parseRotationParameter } from './parse-rotation-parameter'; 4 | import { ImageServiceImageRequest } from '../types'; 5 | import { parseImageServiceUrl } from './parse-image-service-url'; 6 | 7 | export function parseImageServiceRequest(input: string, _prefix = ''): ImageServiceImageRequest { 8 | const { path, scheme, server, prefix } = parseImageServiceUrl(input, _prefix); 9 | 10 | const parts = path.split('/').reverse(); 11 | const [fileName, rotation, size, region, ...others] = parts; 12 | const identifier = others.reverse().filter(Boolean).join('/'); 13 | 14 | if (parts.length === 1 || fileName === '') { 15 | // likely the server will want to redirect this 16 | return { type: 'base', scheme, server, prefix, identifier }; 17 | } 18 | 19 | if (fileName === 'info.json') { 20 | const [, ...identifierParts] = parts; 21 | 22 | return { 23 | type: 'info', 24 | scheme, 25 | server, 26 | prefix, 27 | identifier: identifierParts.reverse().filter(Boolean).join('/'), 28 | }; 29 | } 30 | 31 | if ( 32 | typeof scheme === 'undefined' || 33 | typeof server === 'undefined' || 34 | typeof path === 'undefined' || 35 | typeof region === 'undefined' || 36 | typeof size === 'undefined' || 37 | typeof rotation === 'undefined' || 38 | typeof fileName === 'undefined' 39 | ) { 40 | throw new Error('Invalid image service URL'); 41 | } 42 | 43 | const [quality = '', format = ''] = fileName.split('.'); 44 | 45 | return { 46 | type: 'image', 47 | scheme, 48 | server, 49 | prefix, 50 | identifier, 51 | originalPath: path, 52 | region: parseRegionParameter(region), 53 | size: parseSizeParameter(size), 54 | rotation: parseRotationParameter(rotation), 55 | quality, 56 | format, 57 | }; 58 | } 59 | -------------------------------------------------------------------------------- /src/image-3/parser/parse-image-service-url.ts: -------------------------------------------------------------------------------- 1 | export function parseImageServiceUrl(canonicalId: string, prefix = '') { 2 | const parsedUrl = canonicalId.match(/^(([a-zA-Z]+):\/\/([^/]+))?((.*)+)/); 3 | if (!parsedUrl) { 4 | throw new Error(`Invalid or unknown input ${canonicalId}`); 5 | } 6 | const scheme = parsedUrl[2]; 7 | const server = parsedUrl[3]; 8 | let path = parsedUrl[4]!; 9 | if (path[0] === '/') { 10 | path = path.substring(1); 11 | } 12 | if (prefix.length > 0) { 13 | if (prefix[0] === '/') { 14 | prefix = prefix.substring(1); 15 | } 16 | if (prefix !== path.substring(0, prefix.length)) { 17 | throw new Error(`Path does not start with prefix (path: ${path}, prefix: ${prefix})`); 18 | } 19 | path = path.substring(prefix.length); 20 | } 21 | 22 | return { 23 | scheme, 24 | server, 25 | path, 26 | prefix, 27 | } as { 28 | scheme: string; 29 | server: string; 30 | path: string; 31 | prefix: string; 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /src/image-3/parser/parse-region-parameter.ts: -------------------------------------------------------------------------------- 1 | import { RegionParameter } from '../types'; 2 | 3 | export function parseRegionParameter(pathPart: string): RegionParameter { 4 | try { 5 | if (pathPart === 'full') { 6 | return { full: true }; 7 | } 8 | if (pathPart === 'square') { 9 | return { square: true }; 10 | } 11 | 12 | const percent = pathPart.startsWith('pct:'); 13 | const stringParts = pathPart.substr(percent ? 4 : 0).split(','); 14 | const xywh = stringParts.map((part) => parseFloat(part)); 15 | return { 16 | x: xywh[0], 17 | y: xywh[1], 18 | w: xywh[2], 19 | h: xywh[3], 20 | percent: percent, 21 | }; 22 | } catch { 23 | throw new Error("Expected 'full', 'square' or 'x,y,w,h'. Found " + pathPart); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/image-3/parser/parse-rotation-parameter.ts: -------------------------------------------------------------------------------- 1 | import { RotationParameter } from '../types'; 2 | 3 | export function parseRotationParameter(pathPart: string): RotationParameter { 4 | const rotation: RotationParameter = { angle: 0 }; 5 | if (pathPart[0] === '!') { 6 | rotation.mirror = true; 7 | pathPart = pathPart.substr(1); 8 | } 9 | 10 | rotation.angle = parseFloat(pathPart) % 360; 11 | if (Number.isNaN(rotation.angle)) { 12 | throw new Error(`Invalid rotation ${pathPart}`); 13 | } 14 | return rotation; 15 | } 16 | -------------------------------------------------------------------------------- /src/image-3/parser/parse-size-parameter.ts: -------------------------------------------------------------------------------- 1 | import { SizeParameter } from '../types'; 2 | 3 | export function parseSizeParameter(pathPart: string): SizeParameter { 4 | const size: SizeParameter = { 5 | upscaled: false, 6 | max: false, 7 | confined: false, 8 | }; 9 | 10 | if (pathPart[0] === '^') { 11 | size.upscaled = true; 12 | pathPart = pathPart.slice(1); 13 | } 14 | 15 | if (pathPart === 'max' || pathPart === 'full') { 16 | size.max = true; 17 | size.serialiseAsFull = pathPart === 'full'; 18 | return size; 19 | } 20 | 21 | if (pathPart[0] === '!') { 22 | size.confined = true; 23 | pathPart = pathPart.slice(1); 24 | } 25 | 26 | if (pathPart[0] === 'p') { 27 | size.percentScale = parseFloat(pathPart.slice(4)); 28 | return size; 29 | } 30 | 31 | const wh = pathPart.split(',').map((t) => t.trim()); 32 | if (wh.length) { 33 | if (wh[0] !== '') { 34 | size.width = parseInt(wh[0]!, 10); 35 | } 36 | 37 | if (wh[1] !== '') { 38 | size.height = parseInt(wh[1]!, 10); 39 | } 40 | } 41 | 42 | return size; 43 | } 44 | -------------------------------------------------------------------------------- /src/image-3/profiles/combine-profiles.ts: -------------------------------------------------------------------------------- 1 | import { levelToProfile } from './level-to-profile'; 2 | import { Profile } from './profiles'; 3 | import { ImageService } from '@iiif/presentation-3'; 4 | 5 | export function combineProfiles(service: ImageService): Profile { 6 | const profiles: any[] = service ? (Array.isArray(service.profile) ? service.profile : [service.profile]) : []; 7 | const final: Profile = { 8 | extraQualities: [], 9 | extraFormats: [], 10 | extraFeatures: [], 11 | }; 12 | 13 | for (let profile of profiles) { 14 | if (typeof profile === 'string') { 15 | profile = levelToProfile(profile); 16 | } 17 | 18 | if (!profile) { 19 | continue; 20 | } 21 | 22 | // Merging Image 2.1.1 23 | if (profile.formats) { 24 | for (const format of profile.formats) { 25 | if (final.extraFormats.indexOf(format) === -1) { 26 | final.extraFormats.push(format); 27 | } 28 | } 29 | } 30 | if (profile.qualities) { 31 | for (const format of profile.qualities) { 32 | if (final.extraQualities.indexOf(format) === -1) { 33 | final.extraQualities.push(format); 34 | } 35 | } 36 | } 37 | if (profile.supports) { 38 | for (const feature of profile.supports) { 39 | if (final.extraFeatures.indexOf(feature as any) === -1) { 40 | final.extraFeatures.push(feature as any); 41 | } 42 | } 43 | } 44 | 45 | if (profile.maxHeight) { 46 | final.maxHeight = profile.maxHeight; 47 | } 48 | if (profile.maxWidth) { 49 | final.maxWidth = profile.maxWidth; 50 | } 51 | if (profile.maxArea) { 52 | final.maxArea = profile.maxArea; 53 | } 54 | 55 | // Merging Image 3.0 56 | if (profile.extraFormats) { 57 | for (const format of profile.extraFormats) { 58 | if (final.extraFormats.indexOf(format) === -1) { 59 | final.extraFormats.push(format); 60 | } 61 | } 62 | } 63 | if (profile.extraQualities) { 64 | for (const format of profile.extraQualities) { 65 | if (final.extraQualities.indexOf(format) === -1) { 66 | final.extraQualities.push(format); 67 | } 68 | } 69 | } 70 | if (profile.extraFeatures) { 71 | for (const feature of profile.extraFeatures) { 72 | if (final.extraFeatures.indexOf(feature as any) === -1) { 73 | final.extraFeatures.push(feature as any); 74 | } 75 | } 76 | } 77 | 78 | if (profile.maxHeight) { 79 | final.maxHeight = profile.maxHeight; 80 | } 81 | if (profile.maxWidth) { 82 | final.maxWidth = profile.maxWidth; 83 | } 84 | if (profile.maxArea) { 85 | final.maxArea = profile.maxArea; 86 | } 87 | } 88 | 89 | if (service.extraFormats) { 90 | for (const format of service.extraFormats) { 91 | if (final.extraFormats.indexOf(format) === -1) { 92 | final.extraFormats.push(format); 93 | } 94 | } 95 | } 96 | if (service.extraFeatures) { 97 | for (const feature of service.extraFeatures) { 98 | if (final.extraFeatures.indexOf(feature as any) === -1) { 99 | final.extraFeatures.push(feature as any); 100 | } 101 | } 102 | } 103 | if (service.extraQualities) { 104 | for (const quality of service.extraQualities) { 105 | if (final.extraQualities.indexOf(quality as any) === -1) { 106 | final.extraQualities.push(quality as any); 107 | } 108 | } 109 | } 110 | 111 | return final; 112 | } 113 | -------------------------------------------------------------------------------- /src/image-3/profiles/image-service-supports-format.ts: -------------------------------------------------------------------------------- 1 | import { ImageService } from '@iiif/presentation-3'; 2 | import { supports } from './supports'; 3 | 4 | export function imageServiceSupportsFormat(imageService: ImageService, format: string) { 5 | return supports(imageService, { 6 | extraFormats: [format], 7 | }); 8 | } 9 | -------------------------------------------------------------------------------- /src/image-3/profiles/image-service-supports-request.ts: -------------------------------------------------------------------------------- 1 | import { ImageService } from '@iiif/presentation-3'; 2 | import { ImageServiceImageRequest } from '../types'; 3 | import { supports } from './supports'; 4 | import { ExtraFeature } from './profiles'; 5 | 6 | export function imageServiceSupportsRequest(imageService: ImageService, request: ImageServiceImageRequest) { 7 | if (request.type !== 'image') { 8 | return [true]; 9 | } 10 | 11 | const extraFeatures: ExtraFeature[] = []; 12 | 13 | if (request.rotation.mirror) { 14 | extraFeatures.push('mirroring'); 15 | } 16 | 17 | if (request.region.percent) { 18 | extraFeatures.push('regionByPct'); 19 | } 20 | 21 | if (request.region.square) { 22 | extraFeatures.push('regionSquare'); 23 | } else if (!request.region.full) { 24 | extraFeatures.push('regionByPx'); 25 | } 26 | 27 | if (request.rotation.angle) { 28 | const remainder = request.rotation.angle % 90; 29 | if (remainder) { 30 | extraFeatures.push('rotationArbitrary'); 31 | } else { 32 | extraFeatures.push('rotationBy90s'); 33 | } 34 | } 35 | 36 | if (request.size.confined) { 37 | extraFeatures.push('sizeByConfinedWh'); 38 | } 39 | 40 | if (!request.size.width && request.size.height) { 41 | extraFeatures.push('sizeByH'); 42 | } 43 | 44 | if (request.size.percentScale) { 45 | extraFeatures.push('sizeByPct'); 46 | } 47 | 48 | // Could we bail, and check sizes instead? 49 | const fixedSize = (imageService.sizes || []).find( 50 | (size) => 51 | (size.width === request.size.width && !request.size.height) || 52 | (size.height === request.size.height && !request.size.width) || 53 | (size.height === request.size.height && size.width === request.size.width) 54 | ); 55 | if (fixedSize) { 56 | extraFeatures.push('sizeByWhListed'); 57 | } else { 58 | if (request.size.width && !request.size.height) { 59 | extraFeatures.push('sizeByW'); 60 | } 61 | 62 | if (request.size.width && request.size.height) { 63 | extraFeatures.push('sizeByWh'); 64 | } 65 | } 66 | 67 | if (request.size.upscaled) { 68 | extraFeatures.push('sizeUpscaling'); 69 | } 70 | 71 | const [doesSupport, reason] = supports(imageService, { 72 | extraFeatures, 73 | extraQualities: [request.quality], 74 | extraFormats: [request.format], 75 | exactSize: request.size, 76 | }); 77 | 78 | if (doesSupport) { 79 | return [true] as const; 80 | } 81 | 82 | return [false, reason] as const; 83 | } 84 | -------------------------------------------------------------------------------- /src/image-3/profiles/is-level-0.ts: -------------------------------------------------------------------------------- 1 | import { ImageService } from '@iiif/presentation-3'; 2 | import { onlyLevel0 } from './profiles'; 3 | 4 | export function isLevel0(service: ImageService) { 5 | const profile = Array.isArray(service.profile) ? service.profile : [service.profile]; 6 | 7 | for (const single of profile) { 8 | if (typeof single === 'string' && onlyLevel0.indexOf(single) !== -1) { 9 | return true; 10 | } 11 | } 12 | 13 | return false; 14 | } 15 | -------------------------------------------------------------------------------- /src/image-3/profiles/level-to-profile.ts: -------------------------------------------------------------------------------- 1 | import { level0, level1, level1Support, level2, level2Support, Profile } from './profiles'; 2 | 3 | export function levelToProfile(levelProfile: string): Profile { 4 | const isLevel2 = level2Support.indexOf(levelProfile) !== -1; 5 | if (isLevel2) { 6 | return level2; 7 | } 8 | const isLevel1 = level1Support.indexOf(levelProfile) !== -1; 9 | if (isLevel1) { 10 | return level1; 11 | } 12 | 13 | // The minimum. 14 | return level0; 15 | } 16 | -------------------------------------------------------------------------------- /src/image-3/profiles/supports-custom-sizes.ts: -------------------------------------------------------------------------------- 1 | import { ImageService } from '@iiif/presentation-3'; 2 | import { isImageService } from '../utilities/is-image-service'; 3 | import { level1Support, Profile } from './profiles'; 4 | 5 | export function supportsCustomSizes(service: ImageService): boolean { 6 | if (!isImageService(service)) { 7 | return false; 8 | } 9 | 10 | const profiles = Array.isArray(service.profile) ? service.profile : [service.profile]; 11 | 12 | for (const profile of profiles) { 13 | if (typeof profile === 'string') { 14 | if (level1Support.indexOf(profile) !== -1) { 15 | return true; 16 | } 17 | } else { 18 | const supports = [...(profile.supports || []), ...((profile as Profile).extraFeatures || [])]; 19 | if ( 20 | supports.indexOf('regionByPx') !== -1 && 21 | (supports.indexOf('sizeByW') !== -1 || supports.indexOf('sizeByWh') !== -1) 22 | ) { 23 | return true; 24 | } 25 | } 26 | } 27 | 28 | return false; 29 | } 30 | -------------------------------------------------------------------------------- /src/image-3/profiles/supports.ts: -------------------------------------------------------------------------------- 1 | import { ImageService } from '@iiif/presentation-3'; 2 | import { extraFeatures, Profile } from './profiles'; 3 | import { isImageService } from '../utilities/is-image-service'; 4 | import { combineProfiles } from './combine-profiles'; 5 | 6 | export function supports( 7 | service: ImageService, 8 | req: Partial & { exactSize?: { width?: number; height?: number } } 9 | ) { 10 | if (!isImageService(service)) { 11 | return [false, 'Not a valid image service'] as const; 12 | } 13 | 14 | req.extraFeatures = req.extraFeatures ? req.extraFeatures : []; 15 | 16 | const combined = combineProfiles(service); 17 | 18 | if (req.exactSize) { 19 | let valid = false; 20 | // 1. Check sizes. 21 | if (service.sizes) { 22 | for (const size of service.sizes) { 23 | if (size.width && size.width === req.exactSize.width) { 24 | if (extraFeatures.indexOf('sizeByW') !== -1) { 25 | valid = true; 26 | } else if (size.height && size.height === req.exactSize.height) { 27 | valid = true; 28 | } 29 | } 30 | if (size.height && size.height === req.exactSize.height) { 31 | if (extraFeatures.indexOf('sizeByH') !== -1) { 32 | valid = true; 33 | } else if (size.width && size.width === req.exactSize.width) { 34 | valid = true; 35 | } 36 | } 37 | } 38 | } 39 | 40 | if (!valid) { 41 | req.maxWidth = Math.max(req.maxWidth || 0, req.exactSize.width || 0) || undefined; 42 | req.maxHeight = Math.max(req.maxHeight || 0, req.exactSize.height || 0) || undefined; 43 | req.maxArea = 44 | Math.max( 45 | req.maxArea || 0, 46 | (req.exactSize.width && req.exactSize.height ? req.exactSize.width * req.exactSize.height : req.maxArea) || 0 47 | ) || undefined; 48 | 49 | if (!req.exactSize.height && req.exactSize.width) { 50 | if (req.extraFeatures.indexOf('sizeByW') === -1) { 51 | req.extraFeatures.push('sizeByW'); 52 | } 53 | } else if (!req.exactSize.width && req.exactSize.height) { 54 | if (req.extraFeatures.indexOf('sizeByH') === -1) { 55 | req.extraFeatures.push('sizeByH'); 56 | } 57 | } 58 | } 59 | } 60 | 61 | if (req.maxArea && combined.maxArea && req.maxArea > combined.maxArea) { 62 | return [false, `Max area is ${combined.maxArea}`] as const; 63 | } 64 | 65 | if (req.maxWidth && combined.maxWidth && req.maxWidth > combined.maxWidth) { 66 | return [false, `Max width is ${combined.maxWidth}`] as const; 67 | } 68 | 69 | if (req.maxHeight && combined.maxHeight && req.maxHeight > combined.maxHeight) { 70 | return [false, `Max height is ${combined.maxHeight}`] as const; 71 | } 72 | 73 | if (req.extraFeatures) { 74 | const missingFeatures = []; 75 | for (const feature of req.extraFeatures) { 76 | if (combined.extraFeatures.indexOf(feature) === -1) { 77 | missingFeatures.push(feature); 78 | } 79 | } 80 | if (missingFeatures.length) { 81 | return [false, `Missing features: ${missingFeatures.join(', ')}`] as const; 82 | } 83 | } 84 | 85 | if (req.extraFormats) { 86 | const missingFormats = []; 87 | for (const feature of req.extraFormats) { 88 | if (combined.extraFormats.indexOf(feature) === -1) { 89 | missingFormats.push(feature); 90 | } 91 | } 92 | if (missingFormats.length) { 93 | return [false, `Missing formats: ${missingFormats.join(', ')}`] as const; 94 | } 95 | } 96 | 97 | if (req.extraQualities) { 98 | const missingQualities = []; 99 | for (const quality of req.extraQualities) { 100 | if (combined.extraQualities.indexOf(quality) === -1) { 101 | missingQualities.push(quality); 102 | } 103 | } 104 | if (missingQualities.length) { 105 | return [false, `Missing qualities: ${missingQualities.join(', ')}`] as const; 106 | } 107 | } 108 | 109 | return [true] as const; 110 | } 111 | -------------------------------------------------------------------------------- /src/image-3/serialize/image-service-request-info.ts: -------------------------------------------------------------------------------- 1 | import { ImageService } from "@iiif/presentation-3"; 2 | import { ImageServiceImageRequest } from "../types"; 3 | import { imageServiceRequestToString } from "./image-service-request-to-string"; 4 | 5 | export function imageServiceRequestInfo(req: ImageServiceImageRequest, service?: ImageService): string { 6 | return imageServiceRequestToString({ ...req, type: 'info' }, service); 7 | } -------------------------------------------------------------------------------- /src/image-3/serialize/image-service-request-to-string.ts: -------------------------------------------------------------------------------- 1 | import { ImageServiceImageRequest } from '../types'; 2 | import { regionParameterToString } from './region-parameter-to-string'; 3 | import { sizeParameterToString } from './size-parameter-to-string'; 4 | import { rotationParameterToString } from './rotation-parameter-to-string'; 5 | import { ImageService } from '@iiif/presentation-3'; 6 | 7 | export function imageServiceRequestToString(req: ImageServiceImageRequest, service?: ImageService): string { 8 | const prefix = req.prefix.startsWith('/') ? req.prefix.substring(1) : req.prefix; 9 | const baseUrl = `${req.scheme}://${req.server}/${prefix ? `${prefix}/` : ''}${req.identifier}`; 10 | 11 | if (req.type === 'base') { 12 | return baseUrl; 13 | } 14 | 15 | if (req.type === 'info') { 16 | return `${baseUrl}/info.json`; 17 | } 18 | 19 | let { size } = req; 20 | const { region, rotation, format, quality } = req; 21 | 22 | if (service) { 23 | // Service specific changes. 24 | const ctx = service['@context'] 25 | ? Array.isArray(service['@context']) 26 | ? service['@context'] 27 | : [service['@context']] 28 | : []; 29 | const is2 = ctx.indexOf('http://iiif.io/api/image/2/context.json') !== -1; 30 | const is3 = ctx.indexOf('http://iiif.io/api/image/3/context.json') !== -1; 31 | 32 | // max size, for canonical. 33 | if ( 34 | (size.width === service.width && !size.height) || 35 | (size.height === service.height && !size.width) || 36 | (size.width === service.width && size.height === service.height) 37 | ) { 38 | size = { ...size, max: true }; 39 | } 40 | 41 | if (is2) { 42 | if (size.max && !size.serialiseAsFull) { 43 | size = { ...size, serialiseAsFull: true }; 44 | } 45 | 46 | if (!size.max && size.width && size.height) { 47 | size = { ...size, height: undefined }; 48 | } 49 | 50 | size = { ...size, version: 2 }; 51 | } 52 | if (is3) { 53 | if (size.max && size.serialiseAsFull) { 54 | size = { ...size, serialiseAsFull: false }; 55 | } 56 | 57 | if (size.width && !size.height && service.width && service.height) { 58 | // canonical requires height. 59 | const ratio = service.height / service.width; 60 | size = { ...size, height: Math.ceil(size.width * ratio) }; 61 | } 62 | 63 | size = { ...size, version: 3 }; 64 | } 65 | 66 | // @todo FUTURE - possibly passing in a correct=true option 67 | // 1. Closeness/rounding to fixed size 68 | // 2. Fallback to supported format. 69 | // 3. Round to rotation 70 | } 71 | 72 | return [ 73 | baseUrl, 74 | regionParameterToString(region), 75 | sizeParameterToString(size), 76 | rotationParameterToString(rotation), 77 | `${quality}.${format}`, 78 | ] 79 | .filter(Boolean) 80 | .join('/'); 81 | } 82 | -------------------------------------------------------------------------------- /src/image-3/serialize/region-parameter-to-string.ts: -------------------------------------------------------------------------------- 1 | import { RegionParameter } from '../types'; 2 | 3 | export function regionParameterToString({ x = 0, y = 0, w, h, full, square, percent }: RegionParameter) { 4 | if (full) { 5 | return 'full'; 6 | } 7 | 8 | if (square) { 9 | return 'square'; 10 | } 11 | 12 | if (typeof w === 'undefined' || typeof h === 'undefined') { 13 | throw new Error('RegionParameter: invalid region'); 14 | } 15 | 16 | const xywh = `${x},${y},${w},${h}`; 17 | if (percent) { 18 | return `pct:${xywh}`; 19 | } 20 | 21 | return xywh; 22 | } 23 | -------------------------------------------------------------------------------- /src/image-3/serialize/rotation-parameter-to-string.ts: -------------------------------------------------------------------------------- 1 | import { RotationParameter } from '../types'; 2 | 3 | export function rotationParameterToString(rotationParameter: RotationParameter) { 4 | return `${rotationParameter.mirror ? '!' : ''}${(rotationParameter.angle || 0) % 360}`; 5 | } 6 | -------------------------------------------------------------------------------- /src/image-3/serialize/size-parameter-to-string.ts: -------------------------------------------------------------------------------- 1 | import { SizeParameter } from '../types'; 2 | 3 | export function sizeParameterToString({ 4 | max, 5 | percentScale, 6 | upscaled, 7 | confined, 8 | width, 9 | height, 10 | serialiseAsFull, 11 | version, 12 | }: SizeParameter): string { 13 | const sb: string[] = []; 14 | 15 | if (upscaled) { 16 | sb.push('^'); 17 | } 18 | 19 | if (max) { 20 | sb.push(serialiseAsFull ? 'full' : 'max'); 21 | return sb.join(''); 22 | } 23 | 24 | if (confined) { 25 | sb.push('!'); 26 | } 27 | 28 | if (percentScale) { 29 | sb.push(`pct:${percentScale}`); 30 | } 31 | 32 | if (width) { 33 | sb.push(`${width}`); 34 | } 35 | 36 | sb.push(','); 37 | 38 | if (height && version === 3) { 39 | sb.push(`${height}`); 40 | } 41 | 42 | return sb.join(''); 43 | } 44 | -------------------------------------------------------------------------------- /src/image-3/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Size parameter 3 | * 4 | * Represents the {size} parameter of a IIIF image request. 5 | * see https://iiif.io/api/image/3.0/#42-size 6 | * Port of https://github.com/digirati-co-uk/iiif-net/blob/main/src/IIIF/IIIF/ImageApi/SizeParameter.cs 7 | */ 8 | export type SizeParameter = { 9 | height?: number; 10 | width?: number; 11 | max: boolean; 12 | serialiseAsFull?: boolean; 13 | upscaled: boolean; 14 | confined: boolean; 15 | percentScale?: number; 16 | version?: 2 | 3; 17 | }; 18 | 19 | /** 20 | * Region parameter 21 | * 22 | * Represents the {region} parameter of a IIIF image request. 23 | * see https://iiif.io/api/image/3.0/#41-region 24 | * Port of https://github.com/digirati-co-uk/iiif-net/blob/main/src/IIIF/IIIF/ImageApi/RegionParameter.cs 25 | */ 26 | export type RegionParameter = { 27 | x?: number; 28 | y?: number; 29 | w?: number; 30 | h?: number; 31 | full?: boolean; 32 | square?: boolean; 33 | percent?: boolean; 34 | }; 35 | 36 | /** 37 | * Rotation parameter 38 | * 39 | * Represents the {rotation} parameter of a IIIF image request. 40 | * see https://iiif.io/api/image/3.0/#43-rotation 41 | * Port of https://github.com/digirati-co-uk/iiif-net/blob/main/src/IIIF/IIIF/ImageApi/RotationParameter.cs 42 | */ 43 | export type RotationParameter = { 44 | mirror?: boolean; 45 | angle: number; 46 | }; 47 | 48 | export type ImageServiceImageRequest = 49 | | { 50 | type: 'base'; 51 | scheme: string; 52 | server: string; 53 | prefix: string; 54 | identifier: string; 55 | } 56 | | { 57 | type: 'info'; 58 | scheme: string; 59 | server: string; 60 | prefix: string; 61 | identifier: string; 62 | } 63 | | { 64 | type: 'image'; 65 | scheme: string; 66 | server: string; 67 | prefix: string; 68 | identifier: string; 69 | region: RegionParameter; 70 | size: SizeParameter; 71 | rotation: RotationParameter; 72 | quality: string; 73 | format: string; 74 | originalPath: string; 75 | }; 76 | -------------------------------------------------------------------------------- /src/image-3/utilities/canonical-service-url.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Get canonical service url 3 | * Ensures an image service id contains the /info.json on the end of it. 4 | * 5 | * @param serviceId 6 | */ 7 | export function canonicalServiceUrl(serviceId: string) { 8 | return serviceId.endsWith('info.json') 9 | ? serviceId 10 | : serviceId.endsWith('/') 11 | ? `${serviceId}info.json` 12 | : `${serviceId}/info.json`; 13 | } 14 | -------------------------------------------------------------------------------- /src/image-3/utilities/create-image-service-request.ts: -------------------------------------------------------------------------------- 1 | import { ImageService } from '@iiif/presentation-3'; 2 | import { ImageServiceImageRequest } from '../types'; 3 | import { combineProfiles } from '../profiles/combine-profiles'; 4 | import { parseImageServiceRequest } from '../parser/parse-image-service-request'; 5 | import { canonicalServiceUrl } from './canonical-service-url'; 6 | 7 | export function createImageServiceRequest(imageService: ImageService): ImageServiceImageRequest { 8 | const parsed = parseImageServiceRequest(canonicalServiceUrl(imageService.id)); 9 | if (parsed.type !== 'info') { 10 | throw new Error('Invalid service URL'); 11 | } 12 | 13 | const features = combineProfiles(imageService); 14 | 15 | return { 16 | identifier: parsed.identifier, 17 | originalPath: '', 18 | server: parsed.server, 19 | prefix: parsed.prefix, 20 | scheme: parsed.scheme, 21 | type: 'image', 22 | quality: features.extraQualities.indexOf('default') === -1 ? features.extraQualities[0]! : 'default', 23 | region: { 24 | full: true, 25 | }, 26 | size: { 27 | max: true, 28 | upscaled: false, 29 | confined: false, 30 | }, 31 | format: 'jpg', 32 | rotation: { 33 | angle: 0, 34 | }, 35 | }; 36 | } 37 | -------------------------------------------------------------------------------- /src/image-3/utilities/extract-fixed-size-scales.ts: -------------------------------------------------------------------------------- 1 | import { ImageSize } from '@iiif/presentation-3'; 2 | 3 | /** 4 | * Extract fixed size scales 5 | * 6 | * Given a source width and height and a list of sizes of that same image, 7 | * it will return an ordered list of scales. 8 | * 9 | * @param width 10 | * @param height 11 | * @param sizes 12 | */ 13 | export function extractFixedSizeScales(width: number, height: number, sizes: ImageSize[]): number[] { 14 | const len = sizes.length; 15 | const scales = []; 16 | for (let i = 0; i < len; i++) { 17 | const size = sizes[i]; 18 | if (!size) continue; 19 | const w = size.width; 20 | scales.push(width / w); 21 | } 22 | return scales; 23 | } 24 | -------------------------------------------------------------------------------- /src/image-3/utilities/fixed-sizes-from-scales.ts: -------------------------------------------------------------------------------- 1 | import { ImageSize } from '@iiif/presentation-3'; 2 | 3 | /** 4 | * Fixed sizes from scales. 5 | * 6 | * Given a width and height of an image and a list of scales, this will return 7 | * an ordered list of widths and heights of the image at those scales. 8 | * 9 | * @param width 10 | * @param height 11 | * @param scales 12 | */ 13 | export function fixedSizesFromScales(width: number, height: number, scales: number[]): ImageSize[] { 14 | const len = scales.length; 15 | const sizes: ImageSize[] = []; 16 | for (let i = 0; i < len; i++) { 17 | const scale = scales[i]; 18 | if (!scale) continue; 19 | sizes.push({ 20 | width: Math.floor(width / scale), 21 | height: Math.floor(height / scale), 22 | }); 23 | } 24 | return sizes; 25 | } 26 | -------------------------------------------------------------------------------- /src/image-3/utilities/get-id.ts: -------------------------------------------------------------------------------- 1 | export function getId(resource: any) { 2 | if (resource['@id']) { 3 | return resource['@id']; 4 | } 5 | 6 | if (resource.id) { 7 | return resource.id; 8 | } 9 | 10 | return undefined; 11 | } 12 | -------------------------------------------------------------------------------- /src/image-3/utilities/get-image-service-level.ts: -------------------------------------------------------------------------------- 1 | import { ImageService } from '@iiif/presentation-3'; 2 | import { isImageServiceLevel } from './is-image-service-level'; 3 | import { isImageService } from './is-image-service'; 4 | 5 | export function getImageServiceLevel(service: ImageService): null | number { 6 | if (!isImageService(service)) { 7 | return null; 8 | } 9 | if (isImageServiceLevel(0, service)) { 10 | return 0; 11 | } 12 | if (isImageServiceLevel(1, service)) { 13 | return 1; 14 | } 15 | if (isImageServiceLevel(2, service)) { 16 | return 2; 17 | } 18 | return null; 19 | } 20 | -------------------------------------------------------------------------------- /src/image-3/utilities/get-image-services.ts: -------------------------------------------------------------------------------- 1 | import { ImageService, Service } from '@iiif/presentation-3'; 2 | import { isImageService } from './is-image-service'; 3 | 4 | /** 5 | * Given a resource, will return only the image services on that resource. 6 | * 7 | * @param resource 8 | */ 9 | export function getImageServices(resource: { service?: Array }): ImageService[] { 10 | const services = resource.service ? (Array.isArray(resource.service) ? resource.service : [resource.service]) : []; 11 | const totalServices = services.length; 12 | const imageServices = []; 13 | for (let i = 0; i < totalServices; i++) { 14 | if (isImageService((services as ImageService[])[i])) { 15 | imageServices.push(services[i]); 16 | } 17 | } 18 | return imageServices as any; 19 | } 20 | -------------------------------------------------------------------------------- /src/image-3/utilities/get-type.ts: -------------------------------------------------------------------------------- 1 | export function getType(resource: any) { 2 | if (resource['@type']) { 3 | return resource['@type']; 4 | } 5 | if (resource.type) { 6 | return resource.type; 7 | } 8 | 9 | return undefined; 10 | } 11 | -------------------------------------------------------------------------------- /src/image-3/utilities/identify-image-server.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Get image server from ID. 3 | * 4 | * Normalises image service URLs to extract identity of the image server. 5 | * 6 | * @param url 7 | */ 8 | export function identifyImageServer(url: string): string { 9 | // Strip off the protocol + www 10 | const id = url.replace(/(https?:\/\/)?(www.)?/i, ''); 11 | 12 | // Strip off the path. 13 | if (id.indexOf('/') !== -1) { 14 | return id.split('/')[0]!; 15 | } 16 | 17 | // Return the id. 18 | return id; 19 | } 20 | -------------------------------------------------------------------------------- /src/image-3/utilities/is-image-service-level.ts: -------------------------------------------------------------------------------- 1 | import { ImageService } from '@iiif/presentation-3'; 2 | import { level1Support, level2Support, onlyLevel0 } from '../profiles/profiles'; 3 | 4 | export function isImageServiceLevel(level: 0 | 1 | 2, imageService?: ImageService) { 5 | if (imageService && imageService.profile) { 6 | const profile = imageService.profile; 7 | if (profile) { 8 | const profileArray = Array.isArray(profile) ? profile : [profile]; 9 | 10 | if ( 11 | profileArray.includes(`level${level}`) || 12 | profileArray.includes(`http://iiif.io/api/image/2/level${level}.json`) || 13 | profileArray.includes(`http://iiif.io/api/image/1/level${level}.json`) || 14 | profileArray.includes(`http://iiif.io/api/image/1/profiles/level${level}.json`) 15 | ) { 16 | return true; 17 | } 18 | 19 | if (level === 2) { 20 | for (let singleProfile of profileArray) { 21 | if (level2Support.includes(singleProfile as string)) { 22 | return true; 23 | } 24 | } 25 | } 26 | 27 | if (level === 1) { 28 | for (let singleProfile of profileArray) { 29 | if (level1Support.includes(singleProfile as string)) { 30 | return true; 31 | } 32 | } 33 | } 34 | 35 | if (level === 0) { 36 | for (let singleProfile of profileArray) { 37 | if (onlyLevel0.includes(singleProfile as string)) { 38 | return true; 39 | } 40 | } 41 | } 42 | } 43 | } 44 | 45 | return false; 46 | } 47 | -------------------------------------------------------------------------------- /src/image-3/utilities/is-image-service.ts: -------------------------------------------------------------------------------- 1 | import { imageServiceProfiles } from '../profiles/profiles'; 2 | import { ImageService } from '@iiif/presentation-3'; 3 | import { getId } from './get-id'; 4 | 5 | export function isImageService(service: any): service is ImageService { 6 | if (!service || !service.profile) { 7 | return false; 8 | } 9 | 10 | if (!getId(service)) { 11 | return false; 12 | } 13 | 14 | const profiles = Array.isArray(service.profile) ? service.profile : [service.profile]; 15 | 16 | for (const profile of profiles) { 17 | if (typeof profile === 'string' && imageServiceProfiles.indexOf(profile) !== -1) { 18 | return true; 19 | } 20 | } 21 | 22 | return false; 23 | } 24 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // Exports the latest 2 | export * from './presentation-3'; 3 | -------------------------------------------------------------------------------- /src/index.umd.ts: -------------------------------------------------------------------------------- 1 | import * as Presentation2 from './presentation-2'; 2 | import * as Presentation3 from './presentation-3'; 3 | import * as Image3 from './image-3'; 4 | 5 | export default { 6 | Presentation2, 7 | Presentation3, 8 | Image3, 9 | }; 10 | -------------------------------------------------------------------------------- /src/presentation-2/index.ts: -------------------------------------------------------------------------------- 1 | export * from './traverse'; 2 | export * from './upgrader'; 3 | -------------------------------------------------------------------------------- /src/presentation-3/index.ts: -------------------------------------------------------------------------------- 1 | export * from './empty-types'; 2 | export * from './normalize'; 3 | export * from './traverse'; 4 | export * from './serialize'; 5 | export * from './serialize-presentation-2'; 6 | export * from './serialize-presentation-3'; 7 | export * from './utilities'; 8 | 9 | // Export some shared utilities 10 | export * from '../shared/to-ref'; 11 | export * from '../shared/compress-specific-resource'; 12 | export * from '../shared/is-specific-resource'; 13 | -------------------------------------------------------------------------------- /src/presentation-3/utilities.ts: -------------------------------------------------------------------------------- 1 | import { CompatibleStore, NormalizedEntity } from './serialize'; 2 | import { toRef } from '../shared/to-ref'; 3 | 4 | export const WILDCARD = {}; 5 | export const HAS_PART = 'iiif-parser:hasPart'; 6 | export const PART_OF = 'iiif-parser:partOf'; 7 | export const IS_EXTERNAL = 'iiif-parser:isExternal'; 8 | export const UNSET = '__$UNSET$__'; 9 | export const UNWRAP = '__$UNWRAP$__'; 10 | export const EMPTY = []; 11 | 12 | // Prevent accidental mutation 13 | Object.freeze(EMPTY); 14 | Object.freeze(WILDCARD); 15 | 16 | export function isWildcard(object: any) { 17 | if (object === WILDCARD || Object.keys(object).length === 0) { 18 | return true; 19 | } 20 | for (const i in object) { 21 | return false; 22 | } 23 | return true; 24 | } 25 | 26 | export function frameResource(resource: any, framing: any) { 27 | if (framing && framing['@explicit']) { 28 | const newEntity: any = {}; 29 | const keys = Object.keys(framing); 30 | for (const key of keys) { 31 | if (key === PART_OF || key === '@explicit') { 32 | continue; 33 | } 34 | if (isWildcard(framing[key])) { 35 | newEntity[key] = resource[key]; 36 | } else { 37 | newEntity[key] = framing[key]; 38 | } 39 | } 40 | return newEntity; 41 | } 42 | 43 | return resource; 44 | } 45 | 46 | export function resolveIfExists( 47 | state: CompatibleStore, 48 | urlOrResource: any, 49 | parent?: any 50 | ): readonly [T | undefined, T | undefined] { 51 | const ref = toRef(urlOrResource); 52 | if (!ref) { 53 | return [undefined, undefined]; 54 | } 55 | 56 | const request = state.requests[ref.id]; 57 | // Return the resource. 58 | const resourceType = ref.type || state.mapping[ref.id]; 59 | if (!resourceType || (request && request.resourceUri && (!state.entities[resourceType] || !state.entities[resourceType]![request.resourceUri]))) { 60 | // Continue refetching resource, this is an invalid state. 61 | return [undefined, undefined]; 62 | } 63 | 64 | const fullEntity: any = state.entities[resourceType]![request ? request.resourceUri : ref.id] as T; 65 | 66 | if (ref.type && !fullEntity) { 67 | return resolveIfExists(state, { id: ref.id }, parent); 68 | } 69 | 70 | if (fullEntity && fullEntity[HAS_PART]) { 71 | const framing = fullEntity[HAS_PART].find((t: any) => { 72 | return parent ? t[PART_OF] === parent.id : t[PART_OF] === fullEntity.id; 73 | }); 74 | 75 | const newEntity = frameResource(fullEntity, framing); 76 | return [newEntity, fullEntity]; 77 | } 78 | 79 | return [fullEntity, fullEntity]; 80 | } 81 | -------------------------------------------------------------------------------- /src/shared/compose.ts: -------------------------------------------------------------------------------- 1 | export function compose(...fns: any[]): (input: T) => T { 2 | return (arg: any) => fns.reduce((a, f) => f(a), arg) as any as T; 3 | } 4 | -------------------------------------------------------------------------------- /src/shared/compress-specific-resource.ts: -------------------------------------------------------------------------------- 1 | import { SpecificResource } from '@iiif/presentation-3'; 2 | 3 | export function compressSpecificResource( 4 | target: undefined | SpecificResource, 5 | { allowSourceString = true, allowString = false, allowedStringType }: { allowString?: boolean; allowSourceString?: boolean; allowedStringType?: string } = {} 6 | ): any { 7 | const fixSource = (resource: any) => { 8 | if (allowSourceString && resource && resource.source && typeof resource.source !== 'string') { 9 | const keys = Object.keys(resource.source); 10 | if (resource.source.id && resource.source.type && keys.length === 2) { 11 | return { ...resource, source: resource.source.id }; 12 | } 13 | } 14 | return resource; 15 | }; 16 | 17 | if (target) { 18 | if (target.source && target.source.partOf) { 19 | // Ignore if we have a partOf 20 | return fixSource(target); 21 | } 22 | const keys = Object.keys(target); 23 | if ( 24 | (keys.length === 2 && target.type && target.source) || 25 | (keys.length === 3 && target.type && target.source && keys.indexOf('selector') !== -1 && !target.selector) 26 | ) { 27 | if (allowString && (!allowedStringType || allowedStringType === target.source.type)) { 28 | return target.source.id; 29 | } 30 | 31 | if (target.source.type === 'ContentResource') { 32 | return { type: 'SpecificResource', source: target.source.id }; 33 | } 34 | 35 | // If all we have is the wrapped source, just return the ID. 36 | return target.source; 37 | } 38 | if (target.selector) { 39 | if ( 40 | !Array.isArray(target.selector) && 41 | typeof target.selector !== 'string' && 42 | target.selector.type === 'FragmentSelector' 43 | ) { 44 | const newId = `${target.source.id}#${target.selector.value}`; 45 | return allowString ? newId : { id: newId, type: target.source.type }; 46 | } 47 | } 48 | } 49 | return fixSource(target); 50 | } 51 | -------------------------------------------------------------------------------- /src/shared/ensure-array.ts: -------------------------------------------------------------------------------- 1 | export function ensureArray(maybeArray: T | T[]): T[] { 2 | if (Array.isArray(maybeArray)) { 3 | return maybeArray; 4 | } 5 | return maybeArray ? [maybeArray] : []; 6 | } 7 | -------------------------------------------------------------------------------- /src/shared/expand-target.ts: -------------------------------------------------------------------------------- 1 | import { ExternalWebResource, SpecificResource, W3CAnnotationTarget } from '@iiif/presentation-3'; 2 | 3 | export function expandTargetToSpecificResource( 4 | target: W3CAnnotationTarget | W3CAnnotationTarget[], 5 | options: { 6 | typeMap?: Record; 7 | typeHint?: string; 8 | } = {} 9 | ): SpecificResource { 10 | if (Array.isArray(target)) { 11 | // Don't support multiple targets for now. 12 | return expandTargetToSpecificResource(target[0]!); 13 | } 14 | 15 | if (typeof target === 'string') { 16 | const [id, fragment] = target.split('#'); 17 | 18 | if (!fragment) { 19 | // This is an unknown selector. 20 | return { 21 | type: 'SpecificResource', 22 | source: { id, type: (options.typeMap && (options.typeMap[id!] as any)) || options.typeHint || 'Unknown' }, 23 | }; 24 | } 25 | 26 | return { 27 | type: 'SpecificResource', 28 | source: { id, type: options.typeHint || 'Unknown' }, 29 | selector: { 30 | type: 'FragmentSelector', 31 | value: fragment, 32 | }, 33 | }; 34 | } 35 | 36 | // @todo, how do we want to support choices for targets. 37 | if ( 38 | target.type === 'Choice' || 39 | target.type === 'List' || 40 | target.type === 'Composite' || 41 | target.type === 'Independents' 42 | ) { 43 | // we also don't support these, just choose the first. 44 | return expandTargetToSpecificResource(target.items[0]!); 45 | } 46 | 47 | if (!target.type && 'source' in target) { 48 | (target as any).type = 'SpecificResource'; 49 | } 50 | 51 | if (target.type === 'SpecificResource') { 52 | if (target.source.type === 'Canvas' && target.source.partOf && typeof target.source.partOf === 'string') { 53 | target.source.partOf = [ 54 | { 55 | id: target.source.partOf, 56 | type: 'Manifest', 57 | }, 58 | ]; 59 | } 60 | 61 | if (target.selector) { 62 | return { 63 | type: 'SpecificResource', 64 | source: target.source, 65 | selector: target.selector, 66 | }; 67 | } 68 | return { 69 | type: 'SpecificResource', 70 | source: target.source, 71 | }; 72 | } 73 | 74 | if (target.id) { 75 | if ((target as any).type === 'Canvas' && (target as any).partOf && typeof (target as any).partOf === 'string') { 76 | (target as any).partOf = [ 77 | { 78 | id: (target as any).partOf, 79 | type: 'Manifest', 80 | }, 81 | ]; 82 | } 83 | 84 | const [id, fragment] = target.id.split('#'); 85 | if (!fragment) { 86 | // This is an unknown selector. 87 | return { 88 | type: 'SpecificResource', 89 | source: { 90 | ...(target as any), 91 | id, 92 | }, 93 | }; 94 | } 95 | 96 | return { 97 | type: 'SpecificResource', 98 | source: { 99 | ...(target as any), 100 | id, 101 | }, 102 | selector: { 103 | type: 'FragmentSelector', 104 | value: fragment, 105 | }, 106 | }; 107 | } 108 | 109 | return { 110 | type: 'SpecificResource', 111 | source: target as ExternalWebResource, 112 | }; 113 | } 114 | -------------------------------------------------------------------------------- /src/shared/is-specific-resource.ts: -------------------------------------------------------------------------------- 1 | import { SpecificResource } from '@iiif/presentation-3'; 2 | 3 | export function isSpecificResource(resource: unknown): resource is SpecificResource { 4 | if (typeof resource === 'string') { 5 | return false; 6 | } 7 | 8 | if (resource && !(resource as any).type && 'source' in (resource as any)) { 9 | (resource as any).type = 'SpecificResource'; 10 | return true; 11 | } 12 | 13 | return !!resource && (resource as any).type === 'SpecificResource'; 14 | } 15 | -------------------------------------------------------------------------------- /src/shared/remove-undefined-properties.ts: -------------------------------------------------------------------------------- 1 | export function removeUndefinedProperties(obj: any) { 2 | for (const prop in obj) { 3 | if (typeof obj[prop] === 'undefined' || obj[prop] === null) { 4 | delete obj[prop]; 5 | } 6 | } 7 | return obj; 8 | } 9 | -------------------------------------------------------------------------------- /src/shared/to-ref.ts: -------------------------------------------------------------------------------- 1 | import { Reference } from '@iiif/presentation-3'; 2 | import { isSpecificResource } from './is-specific-resource'; 3 | 4 | export function toRef(reference: any, _typeHint?: T): Reference | undefined { 5 | const type = (_typeHint || 'unknown') as T; 6 | 7 | if (!reference) { 8 | return undefined; 9 | } 10 | 11 | if (typeof reference === 'string') { 12 | return { id: reference, type }; 13 | } 14 | 15 | if (isSpecificResource(reference)) { 16 | return toRef(reference.source, _typeHint); 17 | } 18 | 19 | let _type = type && type !== 'unknown' ? type : (reference as any).type || (reference as any)['@type']; 20 | const _id = (reference as any).id || (reference as any)['@id']; 21 | 22 | if (_type && _type.indexOf(':') !== -1) { 23 | _type = _type.split(':').pop(); 24 | } 25 | 26 | if (_id && _type) { 27 | return { id: _id, type: _type }; 28 | } 29 | 30 | return undefined; 31 | } 32 | -------------------------------------------------------------------------------- /src/upgrader.ts: -------------------------------------------------------------------------------- 1 | import { convertPresentation2 } from './presentation-2/upgrader'; 2 | 3 | export const upgrade = convertPresentation2; 4 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Default", 4 | "compilerOptions": { 5 | "declaration": true, 6 | "declarationMap": true, 7 | "esModuleInterop": true, 8 | "incremental": false, 9 | "isolatedModules": true, 10 | "lib": ["es2022", "DOM", "DOM.Iterable"], 11 | "module": "ESNext", 12 | "moduleResolution": "Bundler", 13 | "moduleDetection": "force", 14 | "noUncheckedIndexedAccess": true, 15 | "resolveJsonModule": true, 16 | "skipLibCheck": true, 17 | "strict": true, 18 | "target": "ES2022", 19 | "allowJs": true, 20 | "types": ["vitest/globals"] 21 | }, 22 | "include": ["./src/**/*", "./src/*", "./__tests__/**/*", "./demos/**/*", "./fixtures/**/*"], 23 | "exclude": ["node_modules", "dist"] 24 | } 25 | -------------------------------------------------------------------------------- /tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, type Options } from 'tsup'; 2 | 3 | export default defineConfig((options: Options) => ({ 4 | dts: true, 5 | target: ['es2020'], 6 | format: ['esm', 'cjs', 'iife'], 7 | platform: 'browser', 8 | entry: { 9 | index: 'src/index.ts', 10 | 'image-3': 'src/image-3/index.ts', 11 | 'presentation-2': 'src/presentation-2/index.ts', 12 | 'presentation-3': 'src/presentation-3/index.ts', 13 | upgrader: 'src/upgrader.ts', 14 | strict: 'src/presentation-3//strict-upgrade.ts', 15 | }, 16 | minify: true, 17 | external: [], 18 | globalName: 'IIIFParser', 19 | ...options, 20 | })); 21 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config'; 2 | 3 | export default defineConfig({ 4 | plugins: [], 5 | test: { 6 | include: ['**/*.{test,tests,spec}.{js,mjs,cjs,ts,mts,cts}'], 7 | environment: 'happy-dom', 8 | globals: true, 9 | }, 10 | server: { 11 | port: 3008, 12 | }, 13 | }); 14 | --------------------------------------------------------------------------------