├── .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 
"
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 |
--------------------------------------------------------------------------------