ahgIiifPlugin - Technical Documentation
Overview
The ahgIiifPlugin provides comprehensive IIIF (International Image Interoperability Framework) capabilities for AtoM, including manifest generation, deep zoom viewing, media streaming with on-the-fly transcoding, annotation support, collection management, and authentication (IIIF Auth API 1.0). The plugin supports images, PDFs, multi-page TIFFs, 3D models, and audio/video content.
Version: 1.2.0
Category: Media/Viewing
Dependencies: atom >= 2.8.0, PHP >= 8.1, atom-framework
Optional Dependencies:
- Cantaloupe (IIIF Image Server for deep zoom tiling)
- FFmpeg (media transcoding and metadata extraction)
- Whisper (audio/video transcription)
- ImageMagick (PSD/RAW conversion)
- LibreOffice (Office document → PDF conversion)
Architecture
Component Diagram
┌─────────────────────────────────────────────────────────────────────────────────┐
│ ahgIiifPlugin │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────────────────────────────────────────────────────────────────┐ │
│ │ PRESENTATION LAYER │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │ │
│ │ │ IiifViewer │ │ Collection │ │ Auth Admin │ │ Viewer Settings │ │ │
│ │ │ Manager (JS) │ │ Templates │ │ Templates │ │ Templates │ │ │
│ │ │ (ES6 module) │ │ │ │ │ │ │ │ │
│ │ └──────────────┘ └──────────────┘ └──────────────┘ └──────────────────┘ │ │
│ └────────────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────────────────────┐ │
│ │ CONTROLLER LAYER │ │
│ │ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │ │
│ │ │ iiif/actions │ │ iiifCollection│ │ iiifAuth │ │ media/actions │ │ │
│ │ │ │ │ /actions │ │ /actions │ │ │ │ │
│ │ │ manifest() │ │ index() │ │ login() │ │ stream() │ │ │
│ │ │ manifestById()│ │ create() │ │ token() │ │ download() │ │ │
│ │ │ annotations() │ │ edit() │ │ logout() │ │ transcribe() │ │ │
│ │ │ settings() │ │ manifest() │ │ confirm() │ │ convert() │ │ │
│ │ │ │ │ addItems() │ │ check() │ │ extract() │ │ │
│ │ │ │ │ reorder() │ │ protect() │ │ snippets() │ │ │
│ │ └───────────────┘ └───────────────┘ └───────────────┘ └───────────────┘ │ │
│ └────────────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────────────────────┐ │
│ │ SERVICE LAYER │ │
│ │ ┌──────────────────────┐ ┌──────────────────────┐ ┌────────────────────┐ │ │
│ │ │ IiifAnnotationService│ │ IiifCollectionService │ │ IiifAuthService │ │ │
│ │ │ (CRUD + W3C format) │ │ (CRUD + hierarchy) │ │ (tokens + access) │ │ │
│ │ └──────────────────────┘ └──────────────────────┘ └────────────────────┘ │ │
│ │ ┌──────────────────────┐ ┌──────────────────────┐ │ │
│ │ │ TranscriptionService │ │ MediaConversionService│ │ │
│ │ │ (Whisper integration)│ │ (FFmpeg/IM/LO) │ │ │
│ │ └──────────────────────┘ └──────────────────────┘ │ │
│ └────────────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────────────────────┐ │
│ │ HELPER LAYER │ │
│ │ IiifViewerHelper.php │ │
│ │ ┌───────────────────────────────────────────────────────────────────────┐ │ │
│ │ │ render_iiif_viewer() │ get_iiif_base_url() │ get_preferred_viewer() │ │ │
│ │ │ render_3d_viewer() │ get_cantaloupe_url() │ has_3d_models() │ │ │
│ │ │ render_av_viewer() │ get_digital_obj_url() │ render_controls() │ │ │
│ │ │ render_pdf_viewer() │ render_thumbnail_strip() │ render_toggle() │ │ │
│ │ └───────────────────────────────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────────────────────┐ │
│ │ DATA LAYER │ │
│ │ Illuminate\Database\Capsule\Manager (Laravel Query Builder) │ │
│ └────────────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘
Database Schema
Tables
Table
Purpose
Key Columns
iiif_collection
Collection hierarchy
name, slug, parent_id, attribution, viewing_hint
iiif_collection_i18n
Collection translations
collection_id, culture, name, description
iiif_collection_item
Items in collections
collection_id, object_id, manifest_uri, item_type, sort_order
iiif_annotation
W3C annotations
object_id, canvas_id, target_selector (JSON), motivation
iiif_annotation_body
Annotation content
annotation_id, body_type, body_value, body_format
iiif_ocr_text
OCR full text
digital_object_id, full_text (FULLTEXT), format, confidence
iiif_ocr_block
OCR block coordinates
ocr_id, page_number, text, x, y, width, height
iiif_viewer_settings
Viewer config (key-value)
setting_key, setting_value
iiif_auth_service
Auth service profiles
name, profile, label, token_ttl, is_active
iiif_auth_token
Access tokens
token_hash (SHA-256), user_id, expires_at, is_revoked
iiif_auth_resource
Per-object access control
object_id, service_id, apply_to_children, degraded_access
iiif_auth_access_log
Audit trail
object_id, user_id, action, details (JSON)
Entity Relationships
information_object (AtoM core)
│
├──► iiif_collection_item.object_id
├──► iiif_annotation.object_id
├──► iiif_auth_resource.object_id
└──► iiif_auth_access_log.object_id
iiif_collection ──► iiif_collection_i18n (1:N)
──► iiif_collection_item (1:N, CASCADE)
──► iiif_collection (self-ref parent_id)
iiif_annotation ──► iiif_annotation_body (1:N, CASCADE)
iiif_auth_service ──► iiif_auth_token (1:N)
──► iiif_auth_resource (1:N)
iiif_ocr_text ──► iiif_ocr_block (1:N)
Routing
Routes are registered in ahgIiifPluginConfiguration.class.php via the routing.load_configuration event.
Manifest Routes
Method
Route
Action
Description
GET
/iiif/manifest/:slug
manifest
IIIF 2.1 manifest by slug
GET
/iiif/manifest/id/:id
manifestById
IIIF 2.1 manifest by ID
Annotation Routes
Method
Route
Action
Description
GET
/iiif/annotations/object/:id
listAnnotations
Annotations for object
POST
/iiif/annotations
createAnnotation
Create annotation
PUT
/iiif/annotations/:id
updateAnnotation
Update annotation
DELETE
/iiif/annotations/:id
deleteAnnotation
Delete annotation
Collection Routes
Method
Route
Action
Description
GET
/manifest-collections
index
List all collections
GET
/manifest-collection/new
new
Create form
POST
/manifest-collection/create
create
Create collection
GET
/manifest-collection/:id/view
view
View collection
GET
/manifest-collection/:id/edit
edit
Edit form
PUT
/manifest-collection/:id/update
update
Update collection
DELETE
/manifest-collection/:id/delete
delete
Delete collection
POST
/manifest-collection/:id/items/add
addItems
Add items
GET
/manifest-collection/:slug/manifest.json
manifest
IIIF Collection manifest
Method
Route
Action
Description
GET
/media/stream/:id
stream
Stream with transcoding
GET
/media/download/:id
download
Download original
GET
/media/snippets/:id
snippets
Get/manage snippets
GET
/media/extract/:id
extract
Extract metadata (FFprobe)
POST
/media/transcribe/:id
transcribe
Start transcription
GET
/media/transcription/:id/:format
transcription
Get transcription (json/vtt/srt/txt)
GET
/media/convert/:id
convert
On-demand format conversion
GET
/media/metadata/:id
metadata
Get extracted metadata
Auth Routes (IIIF Auth API 1.0)
Method
Route
Action
Description
GET
/iiif/auth/login/:service
login
Interactive login
GET
/iiif/auth/token/:service
token
Request access token
GET
/iiif/auth/logout/:service
logout
Revoke token
POST
/iiif/auth/confirm/:service
confirm
Clickthrough confirmation
GET
/iiif/auth/check/:id
check
Check access for object
Manifest Generation
Process
Query information_object by slug or ID (Laravel Query Builder)
Get all digital_object records for the object
Build IIIF identifier: str_replace('/', '_SL_', $path . $name)
Check for multi-page TIFF (probe Cantaloupe for pages 2-100)
Generate canvases (one per digital object, or one per page for multi-page TIFFs)
Return IIIF Presentation API 2.1 manifest with CORS headers
Multi-Page TIFF Detection
// Probe Cantaloupe for page 2
$page2InfoUrl = "{$cantaloupeBaseUrl}/iiif/2/{$cantaloupeId};2/info.json";
$page2Info = @file_get_contents($page2InfoUrl);
if ($page2Info !== false) {
$isMultiPageTiff = true;
// Continue probing up to page 100
}
Manifest Structure (IIIF 2.1)
{
"@context" : "http://iiif.io/api/presentation/2/context.json" ,
"@type" : "sc:Manifest" ,
"@id" : "https://host/iiif/manifest/slug" ,
"label" : "Object Title" ,
"sequences" : [{
"@type" : "sc:Sequence" ,
"canvases" : [{
"@type" : "sc:Canvas" ,
"label" : "Page 1" ,
"width" : 4000 ,
"height" : 3000 ,
"images" : [{
"@type" : "oa:Annotation" ,
"resource" : {
"@type" : "dctypes:Image" ,
"service" : {
"@context" : "http://iiif.io/api/image/2/context.json" ,
"@id" : "https://host/iiif/2/identifier" ,
"profile" : "http://iiif.io/api/image/2/level2.json"
}
}
}]
}]
}]
}
Viewer Manager (JavaScript)
File: web/js/iiif-viewer-manager.js
ES6 module that manages all viewer types with lazy-loading and preference persistence.
Supported Viewers
Viewer
Content
Source
Library
OpenSeadragon
Images (deep zoom)
CDN + bundled
openseadragon@3.1.0
Mirador 3
Rich IIIF workspace
Bundled
mirador.min.js
PDF.js
PDF documents
CDN
pdf.js@3.11.174
model-viewer
3D models
CDN
model-viewer@3.3.0
Annotorious
Annotations on OSD
Bundled
annotorious-openseadragon
Viewer Selection Logic
Content type detected
│
├─ PDF → PDF.js (override user preference)
├─ Audio/Video → HTML5 player (override user preference)
├─ 3D model → model-viewer (override user preference)
└─ Image → User preference (OpenSeadragon or Mirador)
Saved in localStorage
Key Methods
class IiifViewerManager {
constructor ( containerId , options )
initOpenSeadragon ( manifestUrl )
initMirador ( manifestUrl )
initPdfViewer ( pdfUrl )
init3DViewer ( modelUrl )
switchViewer ( viewerType )
toggleFullscreen ()
toggleAnnotations ()
showError ( message )
destroy ()
}
Transcoding (FFmpeg)
When a file's MIME type is not browser-native, FFmpeg transcodes in real-time:
Video → MP4:
ffmpeg -y -i INPUT \
-c:v libx264 -preset ultrafast -tune zerolatency -crf 23 \
-c:a aac -b:a 128k \
-movflags frag_keyframe+empty_moov+faststart \
-f mp4 pipe:1
Audio → MP3:
ffmpeg -y -i INPUT \
-c:a libmp3lame -b:a 192k \
-f mp3 pipe:1
Type
Formats
Video
ASF, AVI, MOV, WMV, FLV, MKV, TS, WTV, HEVC, 3GP, VOB, MXF, MPEG
Audio
AIFF, AU, AC3, 8SVX, WMA, RA, FLAC
Range Request Support
For native formats, HTTP Range requests enable seeking:
if (isset($_SERVER['HTTP_RANGE'])) {
preg_match('/bytes=(\d*)-(\d*)/', $_SERVER['HTTP_RANGE'], $matches);
http_response_code(206);
header("Content-Range: bytes {$start}-{$end}/{$fileSize}");
}
Source
Output
Tool
PSD, CR2 (RAW)
JPEG
ImageMagick
DOCX, XLSX, PPTX
PDF
LibreOffice headless
ZIP, RAR, TGZ
JSON file listing
Built-in
TXT, CSV, XML
Plain text (500KB limit)
Built-in
SVG
Served with correct MIME
Pass-through
Conversions cached in /uploads/conversions/.
Transcription (Whisper)
Background Job Pattern
Create lock file to prevent duplicate jobs
Generate temporary PHP script in /tmp
Launch as background process: php script.php >> log 2>&1 &
Client polls /media/transcription/:id every 5 seconds
Status file written on completion
TranscriptionService
Python/Whisper integration
Models: tiny, base, small, medium, large
Language auto-detection or manually specified
Output: segments (timestamped), full_text, confidence
Format
Route
Use
JSON
/media/transcription/:id/json
Full data with timestamps
WebVTT
/media/transcription/:id/vtt
Browser captions
SRT
/media/transcription/:id/srt
Subtitle editors
TXT
/media/transcription/:id/txt
Plain text
Annotation Service
IiifAnnotationService Methods
getAnnotationsForObject(int $objectId): array
getAnnotationsForCanvas(string $canvasId): array
createAnnotation(array $data): int
updateAnnotation(int $annotationId, array $data): bool
deleteAnnotation(int $annotationId): bool
formatAsAnnotationPage(array $annotations, int $objectId): array
formatAnnotationAsIiif(object $annotation): array
parseAnnotoriousAnnotation(array $annoData, int $objectId): array
Motivations
Motivation
Purpose
commenting
General comments on a region
tagging
Tag with a keyword
describing
Detailed description
linking
Link to external resource
transcribing
Transcribe text in image
identifying
Identify a person/object
Selectors (JSON stored in target_selector)
FragmentSelector: xywh=x,y,w,h (rectangle)
SvgSelector: <svg>...</svg> (polygon, freehand)
IIIF Auth API 1.0
Auth Profiles
Profile
Description
Use Case
login
Requires AtoM authentication
Registered users only
clickthrough
User agrees to terms
Public with acknowledgment
kiosk
Location-based cookie
On-premises terminals
external
External auth provider
SSO integration
Authentication Flow
Client Server
│ │
│ 1. Request manifest │
│───────────────────────────────────────────────────►│
│ │
│ 2. Manifest with service block │
│◄───────────────────────────────────────────────────│
│ │
│ 3. Open popup: /iiif/auth/login/:service │
│───────────────────────────────────────────────────►│
│ │
│ 4. Login page / Clickthrough terms │
│◄───────────────────────────────────────────────────│
│ │
│ 5. User authenticates/agrees │
│───────────────────────────────────────────────────►│
│ │
│ 6. Set cookie, close popup │
│◄───────────────────────────────────────────────────│
│ │
│ 7. Token request: /iiif/auth/token/:service │
│───────────────────────────────────────────────────►│
│ │
│ 8. Access token response │
│◄───────────────────────────────────────────────────│
│ │
│ 9. Request image with token │
│───────────────────────────────────────────────────►│
│ │
│ 10. Full resolution image │
│◄───────────────────────────────────────────────────│
IiifAuthService Methods
checkAccess(int $objectId, ?int $userId = null): array
// Returns: ['allowed' => bool, 'degraded' => bool, 'service' => ?array]
requestToken(string $serviceName, ?int $userId, ?string $messageId): array
validateCurrentToken(?int $serviceId = null): ?object
logout(): bool
cleanupExpiredTokens(): int
setObjectAuth(int $objectId, string $serviceName, array $options = []): bool
// Options: apply_to_children, degraded_access, degraded_width, notes
removeObjectAuth(int $objectId, ?string $serviceName = null): bool
Token Security
Tokens hashed with SHA-256 before storage
Cookies: HttpOnly, Secure, SameSite=None
Configurable TTL per service (default: 1 hour)
Automatic cleanup of expired tokens
Access Inheritance
Repository Level
└── Fonds (apply_to_children=true)
└── Series (inherits)
└── File (inherits)
└── Item (inherits)
Nginx Integration
extensions.conf
# Media streaming - buffering off for real-time transcoding
location ~ ^/media/stream/ {
fastcgi_buffering off ;
fastcgi_read_timeout 600 ;
# → PHP-FPM
}
# Format conversion - 120s timeout for LibreOffice/ImageMagick
location ~ ^/media/convert/ {
fastcgi_read_timeout 120 ;
# → PHP-FPM
}
# Transcription - 600s timeout for Whisper
location ~ ^/media/transcribe/ {
fastcgi_read_timeout 600 ;
# → PHP-FPM
}
# Static viewer assets - 7 day cache
location /atom-framework/src/Extensions/IiifViewer/public/ {
expires 7d ;
}
# Optional: Cantaloupe IIIF Image API proxy
# location /iiif/ {
# proxy_pass http://127.0.0.1:8182/iiif/;
# }
Helper Functions (IiifViewerHelper.php)
Main Entry Point
render_iiif_viewer($resource, $options = [])
Detects content type, selects viewer, builds HTML with container + controls + JavaScript initialization. Returns complete HTML string.
Content-Specific Renderers
Function
Purpose
ahg_iiif_render_pdf_viewer_html()
iframe with browser PDF viewer
ahg_iiif_render_3d_viewer_html()
<model-viewer> custom element
ahg_iiif_render_av_viewer_html()
<video> or <audio> element with streaming URL
UI Components
Function
Purpose
ahg_iiif_render_viewer_toggle()
Viewer type switch buttons
ahg_iiif_render_viewer_controls()
IIIF badge, fullscreen, download, annotations
ahg_iiif_render_thumbnail_strip()
Multi-image thumbnail navigation
ahg_iiif_render_viewer_javascript()
ES6 module initialization script
URL Helpers
Function
Purpose
get_iiif_base_url()
Auto-detects from request or sfConfig
get_iiif_cantaloupe_url()
Handles relative URLs to Cantaloupe
is_iiif_available()
Checks if IIIF is enabled
get_digital_object_url()
Checks for PDF redactions (ahgPrivacyPlugin)
has_3d_models()
Uses ahg3DModelPlugin or extension check
Plugin Integration Points
Plugin
Integration
Pattern
ahgCorePlugin
Database bootstrap
DatabaseBootstrap::initializeFromAtom()
ahgUiOverridesPlugin
Viewer dispatch
Determines which component to render
ahgThemeB5Plugin
Bootstrap 5 UI
Digital object display templates
ahg3DModelPlugin
3D model detection
has_3d_model(), try/catch if absent
ahgPrivacyPlugin
PDF redactions
get_digital_object_url(), try/catch if absent
All integration is guarded with try/catch or class_exists() — the plugin degrades gracefully when optional plugins are missing.
CSP Compliance
All inline <script> and <style> tags use the CSP nonce:
$n = sfConfig::get('csp_nonce', '');
$nonceAttr = $n ? preg_replace('/^nonce=/', ' nonce="', $n) . '"' : '';
echo '<script type="module"' . $nonceAttr . '>';
CDN domains must be whitelisted in config/app.yml:
- script-src: https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://ajax.googleapis.com
- style-src: Same + 'unsafe-hashes' for inline style attributes
Configuration (sfConfig)
Setting
Default
Description
app_iiif_enabled
true
Master on/off
app_iiif_base_url
(auto)
Public base URL
app_iiif_cantaloupe_url
/iiif/2
Public Cantaloupe URL
app_iiif_cantaloupe_internal_url
http://127.0.0.1:8182
Internal Cantaloupe URL
app_iiif_plugin_path
/plugins/ahgIiifPlugin/web
Plugin web path
app_iiif_default_viewer
openseadragon
Default viewer
app_iiif_viewer_height
600px
Viewer CSS height
app_iiif_enable_annotations
true
Enable annotation UI
Viewer Settings (iiif_viewer_settings table)
Key
Default
Description
viewer_type
mirador
openseadragon, mirador, carousel, single
carousel_autoplay
1
Auto-rotate carousel
carousel_interval
5000
Rotation interval (ms)
background_color
#b1aaaa
Viewer background
homepage_collection_enabled
1
Show collection on homepage
homepage_collection_id
null
Featured collection ID
Static Assets
Bundled Libraries
Path
Library
web/public/mirador/
Mirador 3 (CSS + JS)
web/public/openseadragon/
OpenSeadragon
web/public/viewers/annotorious/
Annotorious for OSD
web/images/
OSD button icons (PNG)
web/js/iiif-viewer-manager.js
Main viewer manager (ES6)
web/js/atom-media-player.js
Enhanced media player
CDN Dependencies (lazy-loaded)
Library
CDN URL
OpenSeadragon 3.1.0
cdn.jsdelivr.net (fallback if bundled fails)
PDF.js 3.11.174
cdnjs.cloudflare.com
model-viewer 3.3.0
ajax.googleapis.com
Error Handling
Layer
Strategy
Manifest generation
Returns 404 if object not found or no digital objects
Viewer JS
Catches errors, falls back to OpenSeadragon, shows error in container
Media streaming
Falls back to transcoding if direct playback fails; 500 on FFmpeg failure
Transcription
Background job with status file; polls for completion
Auth
Logs all access attempts; degraded access on failure
All services
Wrapped in try/catch; failures logged to PHP error log
Troubleshooting
Issue
Cause
Solution
Token not accepted
Cookie blocked by browser
Check SameSite settings
Auth popup blocked
Browser popup blocker
Whitelist domain
403 on images
Missing Cantaloupe config
Check nginx proxy
Manifest returns 404
No digital objects
Verify object has media
Video won't play
FFmpeg not installed
apt install ffmpeg
Transcription stuck
Whisper process failed
Check lock file in /tmp
3D model not rendering
WebGL not available
Check browser support
Page frozen
CSP blocking scripts
Add nonce to all script tags
Version History
Version
Date
Changes
1.2.0
2026-02
Added media streaming, transcription, format conversion, snippets, annotations REST API, 3D viewer, viewer manager JS
1.1.0
2025-01-24
Added IIIF Auth API 1.0 support
1.0.0
2025-01-15
Initial release with manifests and collections
Part of the AtoM AHG Framework
February 8, 2026
January 24, 2026