Skip to content

Commit c44f03d

Browse files
committed
MiradorCanvas inspects annotation type and service profile to determine iiif images
- jest specs for image service identification - #3789
1 parent 04c5b67 commit c44f03d

6 files changed

Lines changed: 234 additions & 13 deletions

File tree

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
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+
"An actively authorized video"
8+
]
9+
},
10+
"items": [
11+
{
12+
"id": "https://auth.example.org/my-video1",
13+
"type": "Canvas",
14+
"label": {
15+
"en": [
16+
"Canvas with a single IIIF video"
17+
]
18+
},
19+
"height": 3024,
20+
"width": 4032,
21+
"items": [
22+
{
23+
"id": "https://auth.example.org/my-video/page",
24+
"type": "AnnotationPage",
25+
"items": [
26+
{
27+
"id": "https://auth.example.org/my-video/annotation",
28+
"type": "Annotation",
29+
"motivation": "painting",
30+
"body": {
31+
"id": "https://auth.example.org/my-video.mp4",
32+
"type": "Video",
33+
"service": [
34+
{
35+
"id": "https://auth.example.org/probe/my-video",
36+
"type": "AuthProbeService2",
37+
"service" : [
38+
{
39+
"id": "https://auth.example.org/login",
40+
"type": "AuthAccessService2",
41+
"profile": "active",
42+
"label": { "en": [ "Login to Example Institution" ] },
43+
"service" : [
44+
{
45+
"id": "https://auth.example.org/token",
46+
"type": "AuthAccessTokenService2"
47+
},
48+
{
49+
"id": "https://auth.example.org/logout",
50+
"type": "AuthLogoutService2",
51+
"label": { "en": [ "Logout from Example Institution" ] }
52+
}
53+
]
54+
}
55+
]
56+
}
57+
]
58+
}
59+
}
60+
]
61+
}
62+
]
63+
}
64+
]
65+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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+
"A kiosk-authorized image"
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://auth.example.org/probe/918ecd18c2592080851777620de9bcb5-gottingen",
39+
"type": "AuthProbeService2",
40+
"service" : [
41+
{
42+
"id": "https://auth.example.org/kiosk",
43+
"type": "AuthAccessService2",
44+
"profile": "kiosk",
45+
"label": { "en": [ "Kiosk Login" ] },
46+
"service" : [
47+
{
48+
"id": "https://auth.example.org/token/918ecd18c2592080851777620de9bcb5-gottingen",
49+
"type": "AuthAccessTokenService2"
50+
}
51+
]
52+
}
53+
]
54+
},
55+
{
56+
"id": "https://iiif.io/api/image/3.0/example/reference/918ecd18c2592080851777620de9bcb5-gottingen",
57+
"profile": "level1",
58+
"type": "ImageService3"
59+
}
60+
]
61+
},
62+
"target": "https://iiif.io/api/cookbook/recipe/0005-image-service/canvas/p1"
63+
}
64+
]
65+
}
66+
]
67+
}
68+
]
69+
}

__tests__/src/lib/MiradorCanvas.test.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import fragmentFixtureV3 from '../../fixtures/version-3/hamilton.json';
99
import audioFixture from '../../fixtures/version-3/0002-mvm-audio.json';
1010
import videoFixture from '../../fixtures/version-3/0015-start.json';
1111
import videoWithAnnoCaptions from '../../fixtures/version-3/video_with_annotation_captions.json';
12+
import auth2WithImage from '../../fixtures/version-3/auth2-kiosk.json';
13+
import auth2WithVideo from '../../fixtures/version-3/auth2-active.json';
1214

1315
describe('MiradorCanvas', () => {
1416
let instance;
@@ -133,4 +135,26 @@ describe('MiradorCanvas', () => {
133135
expect(instance.v3VttContent.length).toEqual(1);
134136
});
135137
});
138+
describe('iiifImageResources', () => {
139+
it('returns image resources', () => {
140+
instance = new MiradorCanvas(
141+
Utils.parseManifest(auth2WithImage).getSequences()[0].getCanvases()[0],
142+
);
143+
expect(instance.iiifImageResources.length).toEqual(1);
144+
});
145+
it('returns only image resources', () => {
146+
instance = new MiradorCanvas(
147+
Utils.parseManifest(auth2WithVideo).getSequences()[0].getCanvases()[0],
148+
);
149+
expect(instance.iiifImageResources.length).toEqual(0);
150+
});
151+
});
152+
describe('imageServiceIds', () => {
153+
it('returns image service IDs', () => {
154+
instance = new MiradorCanvas(
155+
Utils.parseManifest(auth2WithImage).getSequences()[0].getCanvases()[0],
156+
);
157+
expect(instance.imageServiceIds[0]).toEqual('https://iiif.io/api/image/3.0/example/reference/918ecd18c2592080851777620de9bcb5-gottingen');
158+
});
159+
});
136160
});

src/lib/CanvasAttributes.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/** container class for Canvas-adjacent attribute value constants */
2+
export default class CanvasAttributes {}
3+
/** values for type/@type that indicate an image content resource */
4+
CanvasAttributes.IMAGE_TYPES = ['Image', 'StillImage', 'dctypes:Image', 'dctypes:StillImage'];
5+
Object.freeze(CanvasAttributes.IMAGE_TYPES);
6+
7+
/** values for type/@type that indicate a sound content resource */
8+
CanvasAttributes.SOUND_TYPES = ['Audio', 'Sound', 'dctypes:Audio', 'dctypes:Sound'];
9+
Object.freeze(CanvasAttributes.SOUND_TYPES);
10+
11+
/** values for type/@type that indicate a text content resource */
12+
CanvasAttributes.TEXT_TYPES = ['Document', 'Text', 'dctypes:Document', 'dctypes:Text'];
13+
Object.freeze(CanvasAttributes.DOCUMENT_TYPES);
14+
15+
/** values for type/@type that indicate a video content resource */
16+
CanvasAttributes.VIDEO_TYPES = ['Video', 'MovingImage', 'dctypes:Video', 'dctypes:MovingImage'];
17+
Object.freeze(CanvasAttributes.VIDEO_TYPES);
18+
19+
/** values for profile that indicate an image service */
20+
CanvasAttributes.IMAGE_SERVICE_PROFILES = [
21+
'level2',
22+
'level1',
23+
'level0',
24+
'http://iiif.io/api/image/2/level2.json',
25+
'http://iiif.io/api/image/2/level1.json',
26+
'http://iiif.io/api/image/2/level0.json',
27+
];
28+
29+
Object.freeze(CanvasAttributes.IMAGE_SERVICE_PROFILES);

src/lib/MiradorCanvas.js

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import flatten from 'lodash/flatten';
22
import flattenDeep from 'lodash/flattenDeep';
33
import { Canvas, AnnotationPage, Annotation } from 'manifesto.js';
4+
import { audioResourcesFrom, iiifImageResourcesFrom, videoResourcesFrom } from './typeFilters';
5+
import CanvasAttributes from './CanvasAttributes';
6+
47
/**
58
* MiradorCanvas - adds additional, testable logic around Manifesto's Canvas
69
* https://iiif-commons.github.io/manifesto/classes/_canvas_.manifesto.canvas.html
@@ -64,7 +67,7 @@ export default class MiradorCanvas {
6467
return this.imageResources[0];
6568
}
6669

67-
/** */
70+
/** Despite name, this method returns paintable resources, not just images */
6871
get imageResources() {
6972
const resources = flattenDeep([
7073
this.canvas.getImages().map(i => i.getResource()),
@@ -83,19 +86,12 @@ export default class MiradorCanvas {
8386

8487
/** */
8588
get videoResources() {
86-
const resources = flattenDeep([
87-
this.canvas.getContent().map(i => i.getBody()),
88-
]);
89-
return flatten(resources.filter((resource) => resource.getProperty('type') === 'Video'));
89+
return videoResourcesFrom(this.imageResources);
9090
}
9191

9292
/** */
9393
get audioResources() {
94-
const resources = flattenDeep([
95-
this.canvas.getContent().map(i => i.getBody()),
96-
]);
97-
98-
return flatten(resources.filter((resource) => resource.getProperty('type') === 'Sound'));
94+
return audioResourcesFrom(this.imageResources);
9995
}
10096

10197
/** */
@@ -158,13 +154,14 @@ export default class MiradorCanvas {
158154

159155
/** */
160156
get iiifImageResources() {
161-
return this.imageResources
162-
.filter(r => r && r.getServices()[0] && r.getServices()[0].id);
157+
return iiifImageResourcesFrom(this.imageResources);
163158
}
164159

165160
/** */
166161
get imageServiceIds() {
167-
return this.iiifImageResources.map(r => r.getServices()[0].id);
162+
/** filter services by profile for IIIF images services */
163+
const imageServiceFilter = s => CanvasAttributes.IMAGE_SERVICE_PROFILES.includes(s.getProfile());
164+
return this.iiifImageResources.map(r => r.getServices().filter(imageServiceFilter)[0].id);
168165
}
169166

170167
/**

src/lib/typeFilters.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import CanvasAttributes from './CanvasAttributes';
2+
/**
3+
*/
4+
export function filterByTypes(resources, types) {
5+
if (types === undefined || resources === undefined) return [];
6+
7+
if (!Array.isArray(types)) {
8+
const useTypes = [types];
9+
return resources.filter((resource) => useTypes.includes(resource.getProperty('type')));
10+
}
11+
12+
return resources.filter((resource) => types.includes(resource.getProperty('type')));
13+
}
14+
15+
/** */
16+
export function audioResourcesFrom(resources) {
17+
return filterByTypes(resources, CanvasAttributes.SOUND_TYPES);
18+
}
19+
20+
/** */
21+
export function iiifImageResourcesFrom(resources) {
22+
return filterByTypes(resources, CanvasAttributes.IMAGE_TYPES).filter((r) => {
23+
const services = r ? r.getServices() : [];
24+
const imageServices = services.filter(s => CanvasAttributes.IMAGE_SERVICE_PROFILES.includes(s.getProfile()));
25+
return imageServices[0] && imageServices[0].id;
26+
});
27+
}
28+
29+
/** */
30+
export function textResourcesFrom(resources) {
31+
return filterByTypes(resources, CanvasAttributes.TEXT_TYPES);
32+
}
33+
34+
/** */
35+
export function videoResourcesFrom(resources) {
36+
return filterByTypes(resources, CanvasAttributes.VIDEO_TYPES);
37+
}

0 commit comments

Comments
 (0)