Adding a Thumbnail to a IIIF Manifest¶
When building exhibits with Spotlight, it may be useful to add a thumbnail to a IIIF manifest if one is missing.
The thumbnail is its own section in the manifest, on the top level. (For example, it should be aligned with
metadata and sequences.)
The thumbnail will have this format:
"thumbnail": {
"@id": "https://api-pre.library.tamu.edu/iiif/2/5e190c52-c05f-38a1-b358-dbce6429b4a0/full/,200/0/default.jpg",
"@type": "dctypes:Image",
"label": "thumbnail image",
"service": {
"@id": "https://api-pre.library.tamu.edu/iiif/2/5e190c52-c05f-38a1-b358-dbce6429b4a0",
"@context": "http://iiif.io/api/image/2/context.json",
"profile": "http://iiif.io/api/image/2/level2.json",
"label": "IIIF Image Service"
}
}
Note that @id under service should be nearly identical to @id one level under
thumbnail, except without the sizing, rotation, scale, and cropping features and the .jpg extension.
You can acquire this @id by going to the first canvas under sequences.
,200 is a good size for the Spotlight search results.
Adding Thumbnails to a directory of manifests¶
Here is a script that adds a thumbnail section to each json in a directory of IIIF manifests.
import json
from pathlib import Path
MANIFEST_DIR = Path("manifest")
THUMBNAIL_SUFFIX = "/full/,200/0/default.jpg"
def is_manifest(data):
return (
data.get("@type") == "sc:Manifest"
or "sequences" in data
)
def find_first_canvas(manifest):
try:
return manifest["sequences"][0]["canvases"][0]
except (KeyError, IndexError, TypeError):
return None
def extract_image_service(canvas):
"""
Look for an Image API service on the first canvas,
regardless of where it lives.
"""
candidates = []
# Typical v2 location
for img in canvas.get("images", []):
res = img.get("resource", {})
service = res.get("service")
if isinstance(service, dict) and "@id" in service:
candidates.append(service["@id"])
elif isinstance(service, list):
for s in service:
if "@id" in s:
candidates.append(s["@id"])
return candidates[0] if candidates else None
def add_thumbnail(manifest):
canvas = find_first_canvas(manifest)
if not canvas:
print(" ⚠️ No canvas found")
return False
service_id = extract_image_service(canvas)
if not service_id:
print(" ⚠️ No image service found on canvas")
return False
manifest["thumbnail"] = {
"@id": f"{service_id}{THUMBNAIL_SUFFIX}",
"@type": "dctypes:Image",
"label": "thumbnail image",
"service": {
"@id": service_id,
"@context": "http://iiif.io/api/image/2/context.json",
"profile": "http://iiif.io/api/image/2/level2.json",
"label": "IIIF Image Service"
}
}
return True
def main():
json_files = sorted(MANIFEST_DIR.glob("*.json"))
print(f"Found {len(json_files)} JSON files")
for path in json_files:
print(f"\nProcessing {path.name}")
with path.open(encoding="utf-8") as f:
data = json.load(f)
if not is_manifest(data):
print(" ❌ Not recognized as a manifest — skipped")
continue
if add_thumbnail(data):
with path.open("w", encoding="utf-8") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
print(" ✅ Thumbnail added")
else:
print(" ❌ Thumbnail not added")
print("\nDone.")
if __name__ == "__main__":
main()