A command-line tool for traversing IIIF collections and extracting manifest URLs and image information. This tool helps you explore and collect IIIF manifest URLs from collections, with support for nested collections and image extraction.
- Recursively Traverses IIIF Collections: Finds all manifest URLs within a collection, including those in nested collections
- Supports Multiple IIIF Presentation API Versions: Compatible with both IIIF Presentation API 2.0 and 3.0, including full support for paginated collections in both IIIF 2.1.1 and 3.0 formats
- Automatic Pagination Handling: Seamlessly processes paginated IIIF collections using both
first
/next
links (2.1.1) and "Next page" items (3.0) - Multiple Output Formats: Choose between
json
,jsonl
(JSON Lines), and formatted tables - Image URL Extraction: Get IIIF image URLs from manifests with customizable dimensions
- Download Full Manifest JSONs: Save the complete JSON content of each manifest, named by their IDs
- Save Results to File or Display in Terminal: Flexible output options to suit your workflow
- Debug Mode for Detailed Logging: Provides comprehensive logs for troubleshooting and monitoring
- Robust Error Handling with Automatic Retries: Ensures reliable data fetching even in the face of transient network issues
- Support for Paginated Collections: Handles collections that span multiple pages seamlessly
- Efficient Caching: Built-in caching system with configurable options
Requires Python 3.10 or higher.
pip install loam-iiif
The basic command structure is:
loamiiif collect [OPTIONS] URL
-f, --format [json|jsonl|table]
: Output format (default: json)-o, --output PATH
: Save output to a file. For manifests, specifies directory-i, --images
: Include IIIF image URLs from each manifest-w, --width INTEGER
: Desired width of images (default: 768)-h, --height INTEGER
: Desired height of images (default: 2000)--image-format [jpg|png]
: Image format (default: jpg)--exact
: Use exact dimensions without preserving aspect ratio (default: false)--max
: Use maximum size instead of specific dimensions. Uses 'full' for IIIF v2 manifests and 'max' for v3 manifests. When enabled, overrides --width, --height, and --exact options-d, --download-manifests
: Download full JSON contents of each manifest-c, --cache-dir PATH
: Directory to cache manifest JSON files--skip-cache
: Skip reading from cache but still write to it--no-cache
: Disable manifest caching completely-m, --max-manifests INTEGER
: Maximum number of manifests to retrieve--debug
: Enable debug mode with detailed logs
loamiiif collect "https://api.dc.library.northwestern.edu/api/v2/collections/ba35820a-525a-4cfa-8f23-4891c9f798c4?as=iiif" --format json
Output:
{
"manifests": [
"https://api.dc.library.northwestern.edu/api/v2/works/faafca34-ecf4-4848-838a-da220d864042?as=iiif",
"https://api.dc.library.northwestern.edu/api/v2/works/e40479c4-06cb-48be-9d6b-adf47f238852?as=iiif",
"https://api.dc.library.northwestern.edu/api/v2/works/f4720687-61b6-4dcd-aed0-b70eff985583?as=iiif"
],
"collections": [
"https://api.dc.library.northwestern.edu/api/v2/collections/ba35820a-525a-4cfa-8f23-4891c9f798c4?as=iiif"
]
}
loamiiif collect -i "https://api.dc.library.northwestern.edu/api/v2/collections?as=iiif" --max-manifests 3 --format table
This displays a formatted table showing manifest URLs and their image counts:
Manifests
┏━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┓
┃ Index ┃ Manifest URL ┃ Image Count ┃
┡━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━┩
│ 1 │ https://api.dc.library.northwestern.edu/api/v2/works/faafca34-ecf4-4848-838a-… │ 92 │
│ 2 │ https://api.dc.library.northwestern.edu/api/v2/works/e40479c4-06cb-48be-9d6b-… │ 135 │
│ 3 │ https://api.dc.library.northwestern.edu/api/v2/works/f4720687-61b6-4dcd-aed0-… │ 168 │
└───────┴────────────────────────────────────────────────────────────────────────────────┴─────────────┘
loamiiif collect -i --width 1024 --height 1024 "https://api.dc.library.northwestern.edu/api/v2/collections?as=iiif" --format json
Output includes manifests with their image URLs, formatted to the specified dimensions (preserving aspect ratio by default):
{
"manifests": [
{
"id": "https://api.dc.library.northwestern.edu/api/v2/works/faafca34-ecf4-4848-838a-da220d864042?as=iiif",
"images": [
"https://iiif.dc.library.northwestern.edu/iiif/3/1e13bf6c-3be6-4971-8439-cbb5b75f9106/full/!1024,1024/0/default.jpg",
...
]
}
],
"collections": [...]
}
For exact dimensions without aspect ratio preservation:
loamiiif collect -i --width 1024 --height 1024 --exact "https://api.dc.library.northwestern.edu/api/v2/collections?as=iiif"
This will output image URLs using the exact dimensions specified:
{
"manifests": [
{
"id": "https://api.dc.library.northwestern.edu/api/v2/works/faafca34-ecf4-4848-838a-da220d864042?as=iiif",
"images": [
"https://iiif.dc.library.northwestern.edu/iiif/3/1e13bf6c-3be6-4971-8439-cbb5b75f9106/full/1024,1024/0/default.jpg",
...
]
}
]
}
To get maximum size images regardless of any dimension settings:
loamiiif collect -i --max "https://api.dc.library.northwestern.edu/api/v2/collections?as=iiif"
This will output image URLs using 'max' for IIIF v3 manifests and 'full' for v2 manifests:
{
"manifests": [
{
"id": "https://api.dc.library.northwestern.edu/api/v2/works/faafca34-ecf4-4848-838a-da220d864042?as=iiif",
"images": [
"https://iiif.dc.library.northwestern.edu/iiif/3/1e13bf6c-3be6-4971-8439-cbb5b75f9106/full/max/0/default.jpg", // v3 manifest
"https://view.nls.uk/iiif/1161/2928/116129282.5/full/full/0/default.jpg" // v2 manifest
]
}
]
}
Save manifest list to JSON file:
loamiiif collect "https://api.dc.library.northwestern.edu/api/v2/collections?as=iiif" -o manifests.json
Download manifest contents to a directory:
loamiiif collect "https://api.dc.library.northwestern.edu/api/v2/collections?as=iiif" --download-manifests -o ./manifest_downloads
Save as JSON Lines format:
loamiiif collect "https://api.dc.library.northwestern.edu/api/v2/collections?as=iiif" -f jsonl -o manifests.jsonl
Use a custom cache directory:
loamiiif collect "https://api.dc.library.northwestern.edu/api/v2/collections?as=iiif" --cache-dir ./my-cache-dir
Skip reading from cache but still write to it:
loamiiif collect "https://api.dc.library.northwestern.edu/api/v2/collections?as=iiif" --skip-cache
Disable caching completely:
loamiiif collect "https://api.dc.library.northwestern.edu/api/v2/collections?as=iiif" --no-cache
loam-iiif provides comprehensive support for paginated IIIF collections across multiple API versions. Many large institutions use pagination to break down extensive collections into manageable segments.
loam-iiif automatically handles multiple pagination patterns:
Uses first
and next
properties to link between pages:
{
"@context": "http://iiif.io/api/presentation/2/context.json",
"@id": "https://example.org/collection/main",
"@type": "sc:Collection",
"label": "Large Collection",
"first": {
"@id": "https://example.org/collection/main/page/1",
"@type": "sc:Collection"
}
}
Uses collection items with "Next page" labels and pagination URLs:
{
"@context": "http://iiif.io/api/presentation/3/context.json",
"id": "https://api.dc.library.northwestern.edu/api/v2/collections?as=iiif",
"type": "Collection",
"items": [
{
"id": "https://api.dc.library.northwestern.edu/api/v2/collections?as=iiif&page=2",
"type": "Collection",
"label": {
"none": ["Next page"]
}
}
]
}
When loam-iiif encounters a paginated collection, it:
- Automatically detects pagination patterns in both IIIF 2.1.1 and 3.0 formats
- Follows pagination links by traversing
first
/next
properties or "Next page" items - Collects manifests and collections from all pages seamlessly
- Respects limits set by
--max-manifests
ormax_manifests
parameter - Handles errors gracefully by stopping pagination on fetch failures
# Process IIIF 2.1.1 paginated collection (Bavarian State Library)
loamiiif collect "https://api.digitale-sammlungen.de/iiif/presentation/v2/collection/top" --max-manifests 100
# Process IIIF 3.0 paginated collection (Northwestern University)
loamiiif collect "https://api.dc.library.northwestern.edu/api/v2/collections?as=iiif" --max-manifests 50
# Pagination is handled automatically - no special flags needed
Common institutions using paginated collections:
- Northwestern University (
api.dc.library.northwestern.edu
) - IIIF 3.0 page-based pagination - Bavarian State Library (
digitale-sammlungen.de
) - IIIF 2.1.1 first/next pagination - Internet Archive - Extensive digital library collections
- HathiTrust - Academic and research library materials
Non-paginated collections contain all manifests and sub-collections directly in the main JSON response.
Paginated collections split content across multiple pages using different patterns depending on the IIIF version:
{
"@context": "http://iiif.io/api/presentation/2/context.json",
"@id": "https://example.org/collection/main",
"@type": "sc:Collection",
"label": "Large Collection",
"first": {
"@id": "https://example.org/collection/main/page/1",
"@type": "sc:Collection"
}
}
loam-iiif seamlessly handles both types without requiring different commands or options.
In addition to the command-line interface, loam-iiif can be used directly in Python code.
loam-iiif automatically detects and processes paginated collections in both IIIF 2.1.1 and 3.0 formats. Whether a collection uses first
/next
links (2.1.1) or "Next page" items (3.0), the library will traverse all pages to collect manifests and nested collections.
from loam_iiif.iiif import IIIFClient
# Initialize client
client = IIIFClient()
# Process IIIF 2.1.1 paginated collection (first/next links)
manifests, collections = client.get_manifests_and_collections_ids(
"https://api.digitale-sammlungen.de/iiif/presentation/v2/collection/top",
max_manifests=50 # Optional limit to control processing
)
# Process IIIF 3.0 paginated collection (page items)
manifests, collections = client.get_manifests_and_collections_ids(
"https://api.dc.library.northwestern.edu/api/v2/collections?as=iiif",
max_manifests=50 # Optional limit to control processing
)
print(f"Found {len(manifests)} manifests across all pages")
print(f"Found {len(collections)} collections")
Here's a comprehensive example showing how to process multiple IIIF collections and save parsed output:
from loam_iiif import IIIFClient
import json
import logging
# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
# Collection configurations
collections = [
{
"url": "https://digital.library.villanova.edu/Collection/vudl:7028/IIIF",
"output_file": "loam_villanova_collection_data.json"
},
{
"url": "https://api.dc.library.northwestern.edu/api/v2/collections/ecacd539-fe38-40ec-bbc0-590acee3d4f2?as=iiif",
"output_file": "loam_northwestern_collection_data.json"
},
{
"url": "https://iiif.durham.ac.uk/manifests/trifle/collection/32150/t2c7s75dc36q",
"output_file": "loam_durham_manifest_data.json"
},
{
"url": "https://api.digitale-sammlungen.de/iiif/presentation/v2/collection/top",
"output_file": "loam_digitale_sammlungen_manifest_data.json"
}
]
# Initialize client
client = IIIFClient()
# Process each collection
for i, collection in enumerate(collections):
print(f"\n{'='*60}")
print(f"Processing collection {i+1}/{len(collections)}: {collection['url']}")
print(f"{'='*60}")
logging.info(f"Processing collection: {collection['url']}")
try:
# Parse and save manifest data (pagination handled automatically)
parsed_data = client.create_and_save_manifest_data(
collection["url"],
collection["output_file"],
max_manifests=10 # Limit for example purposes
)
if parsed_data:
logging.info(f"Manifest data created and saved to {collection['output_file']}.")
print(f"\nSample data from {collection['output_file']}:")
print(json.dumps(parsed_data[0], indent=2))
else:
logging.error(f"Failed to create manifest data for {collection['url']}.")
print(f"No data was created for {collection['url']}.")
except Exception as e:
logging.error(f"Error processing {collection['url']}: {e}")
print(f"Error processing {collection['url']}: {e}")
print("\nProcessing complete!")
This example demonstrates:
- Processing multiple IIIF collections including paginated ones
- Automatic handling of IIIF 2.1.1 pagination (like digitale-sammlungen.de)
- Creating and saving manifest data to JSON files
- Displaying sample data output
- Error handling and progress logging
- Setting limits on manifest processing
from loam_iiif.iiif import IIIFClient
# Initialize client with options
client = IIIFClient(
skip_cache=True # Skip reading from cache but still write to it
)
# Get manifest and collection URLs from a collection
manifests, collections = client.get_manifests_and_collections_ids(
"https://api.dc.library.northwestern.edu/api/v2/collections/ba35820a-525a-4cfa-8f23-4891c9f798c4?as=iiif"
)
The above code returns a tuple of two lists: manifest URLs and collection URLs that were found:
# manifests will contain:
[
'https://api.dc.library.northwestern.edu/api/v2/works/f4720687-61b6-4dcd-aed0-b70eff985583?as=iiif',
'https://api.dc.library.northwestern.edu/api/v2/works/faafca34-ecf4-4848-838a-da220d864042?as=iiif',
'https://api.dc.library.northwestern.edu/api/v2/works/e40479c4-06cb-48be-9d6b-adf47f238852?as=iiif'
]
# collections will contain:
[
'https://api.dc.library.northwestern.edu/api/v2/collections/ba35820a-525a-4cfa-8f23-4891c9f798c4?as=iiif'
]
The library provides a convenient method for parsing individual IIIF manifests into structured data suitable for analysis or further processing.
from loam_iiif.iiif import IIIFClient
client = IIIFClient()
# Parse a single manifest into structured data
manifest_data = client.parse_manifest(
"https://api.dc.library.northwestern.edu/api/v2/works/1b607dac-481a-43e8-a11f-3818a0b10e16?as=iiif",
strip_tags=True # Remove HTML tags from text fields
)
if manifest_data:
print(f"Title: {manifest_data['metadata']['title']}")
print(f"Type: {manifest_data['metadata']['type']}")
print(f"Homepage: {manifest_data['metadata']['homepage']}")
print(f"Rights: {manifest_data['metadata']['rights']}")
# Full structured text content
print(f"\nFull Text:\n{manifest_data['text']}")
# Parent collections (if any)
if manifest_data['metadata']['parent_collections']:
print(f"\nParent Collections:")
for collection in manifest_data['metadata']['parent_collections']:
print(f" - {collection['label']} ({collection['id']})")
else:
print("Failed to parse manifest")
The parse_manifest()
method returns data in this format:
{
"text": "Manifest ID: https://example.org/manifest\nTitle: Document Title\nSummary: Document description...",
"metadata": {
"id": "https://example.org/manifest",
"title": "Document Title",
"type": "Manifest",
"parent_collections": [
{
"id": "https://example.org/collection",
"label": "Collection Name"
}
],
"homepage": "https://example.org/item/123",
"thumbnail": "https://example.org/thumb.jpg",
"rights": "https://creativecommons.org/licenses/by/4.0/",
"attribution": {
"label": "Attribution",
"value": "Northwestern University Libraries"
}
}
}
The structured data includes:
- text: Combined text content suitable for search or analysis
- metadata: Structured fields including title, rights, attribution, parent collections, and homepage URLs
- parent_collections: Information about collections this manifest belongs to
- homepage: Direct link to the item's webpage (extracted from metadata or related fields)
from loam_iiif.iiif import IIIFClient
client = IIIFClient()
# Get image URLs from a manifest with custom dimensions
image_urls = client.get_manifest_images(
"https://api.dc.library.northwestern.edu/api/v2/works/f4720687-61b6-4dcd-aed0-b70eff985583?as=iiif",
width=1024,
height=1024,
format='jpg', # 'jpg' or 'png'
exact=False, # Whether to preserve aspect ratio
use_max=False # Whether to use maximum size
)
The IIIFClient
constructor accepts several options:
client = IIIFClient(
retry_total=2, # Total number of retries for failed requests
backoff_factor=1, # Backoff factor between retries
timeout=10.0, # Request timeout in seconds
cache_dir="manifests", # Directory for caching manifests
skip_cache=False, # Skip reading from cache but still write
no_cache=False # Disable caching completely
)
When running with --debug
, you'll see detailed progress information:
[2025-01-17 14:14:48] DEBUG Starting traversal of IIIF collection: https://api.dc.library.northwestern.edu/api/v2/collections?as=iiif
INFO Processing collection: https://api.dc.library.northwestern.edu/api/v2/collections?as=iiif
DEBUG Fetching URL: https://api.dc.library.northwestern.edu/api/v2/collections?as=iiif
DEBUG Successfully fetched data from https://api.dc.library.northwestern.edu/api/v2/collections?as=iiif
DEBUG Found nested collection: https://api.dc.library.northwestern.edu/api/v2/collections/ba35820a-525a-4cfa-8f23-4891c9f798c4?as=iiif
DEBUG Added manifest: https://api.dc.library.northwestern.edu/api/v2/works/e40479c4-06cb-48be-9d6b-adf47f238852?as=iiif
- Python 3.10+
- click>=8.1.8
- requests>=2.32.3
- rich>=13.9.4
- Clone the repository:
git clone https://github.com/nulib-labs/loam-iiif.git
cd loam-iiif
- Create and activate a virtual environment with
uv
:
uv venv
source .venv/bin/activate # On Windows use: .venv\Scripts\activate
- Install development dependencies:
uv pip install -e ".[dev]"
Run the test suite with pytest:
pytest
Generate an HTML coverage report:
pytest --cov=loam_iiif test/ --cov-report=html
This will create a coverage report in the htmlcov
directory that you can view in your browser.
This project is licensed under the MIT License - see the LICENSE file for details.
Contributions are welcome! Please feel free to submit a Pull Request.