Assets API
Die Assets API ermöglicht den Zugriff auf Bilder und andere Medien-Dateien mit dynamischen Transformationsparametern. Sie bietet optimierte Bilddelivery mit Format-Konvertierung, Größenanpassung und verschiedenen Anpassungsoptionen.
Endpunkt
Asset abrufen
GET /assets/{asset_id}
Ruft ein Asset vom CMS ab und wendet die angegebenen Transformationsparameter an.
Parameter
Parameter |
Typ |
Beschreibung |
Beispiel |
asset_id |
string |
Asset-ID (UUID) aus dem CMS |
8e1cf6a9-d1d6-4090-9a36-303f1f57a016 |
width |
integer |
Breite in Pixeln |
1500 |
height |
integer |
Höhe in Pixeln |
700 |
quality |
integer |
Bildqualität (1-100) |
80 |
withoutEnlargement |
boolean |
Automatische Vergrößerung deaktivieren |
true |
format |
string |
Ausgabeformat |
webp , jpg , png , avif , tiff , auto |
fit |
string |
Anpassungsmodus |
cover , contain , inside , outside |
Beispiele
# Einfacher Asset-Abruf
curl "https://api.kulturpool.at/assets/8e1cf6a9-d1d6-4090-9a36-303f1f57a016"
# Optimiertes Thumbnail
curl "https://api.kulturpool.at/assets/8e1cf6a9-d1d6-4090-9a36-303f1f57a016?format=webp&width=300&height=200&fit=cover&quality=85"
# Hero-Image für moderne Browser
curl "https://api.kulturpool.at/assets/8e1cf6a9-d1d6-4090-9a36-303f1f57a016?format=avif&width=1920&height=800&fit=cover&withoutEnlargement=true"
Parameter im Detail
Anpassungsmodi (fit)
Modus |
Beschreibung |
Verwendung |
cover |
Bild vollständig in Dimensionen einpassen (Standard) |
Hero-Images, Thumbnails |
contain |
Bild mit Letterboxing in Dimensionen einpassen |
Logos, vollständige Bilder |
inside |
So groß wie möglich innerhalb der Dimensionen |
Responsive Bilder |
outside |
So klein wie möglich innerhalb/außerhalb der Dimensionen |
Spezielle Layouts |
Qualitätseinstellungen
Qualität |
Beschreibung |
Dateigröße |
Verwendung |
95-100 |
Höchste Qualität |
Sehr groß |
Druckvorbereitung |
85-95 |
Hohe Qualität |
Groß |
Hero-Images |
75-85 |
Gute Qualität |
Mittel |
Standard-Bilder |
60-75 |
Moderate Qualität |
Klein |
Thumbnails |
40-60 |
Niedrige Qualität |
Sehr klein |
Vorschaubilder |
Antwort
Die API gibt eine HTTP 302 Redirect zum optimierten Asset im CMS zurück:
HTTP/1.1 302 Found
Location: https://cms.kulturpool.at/assets/8e1cf6a9-d1d6-4090-9a36-303f1f57a016/?format=webp&width=1500&height=700&fit=cover
Content-Type: application/json
Beispiel-Code
Responsive Bilder in HTML
<picture>
<source
srcset="https://api.kulturpool.at/assets/8e1cf6a9-d1d6-4090-9a36-303f1f57a016?format=avif&width=1920&height=800&fit=cover"
type="image/avif"
media="(min-width: 1200px)">
<source
srcset="https://api.kulturpool.at/assets/8e1cf6a9-d1d6-4090-9a36-303f1f57a016?format=webp&width=1200&height=600&fit=cover"
type="image/webp"
media="(min-width: 768px)">
<source
srcset="https://api.kulturpool.at/assets/8e1cf6a9-d1d6-4090-9a36-303f1f57a016?format=webp&width=800&height=400&fit=cover"
type="image/webp">
<img
src="https://api.kulturpool.at/assets/8e1cf6a9-d1d6-4090-9a36-303f1f57a016?format=jpg&width=800&height=400&fit=cover"
alt="Beschreibung"
loading="lazy">
</picture>
JavaScript Asset-URL Generator
class AssetUrlBuilder {
constructor(baseUrl = 'https://api.kulturpool.at/assets') {
this.baseUrl = baseUrl;
}
build(assetId, options = {}) {
const {
width,
height,
quality = 85,
format = 'auto',
fit = 'cover',
withoutEnlargement = false
} = options;
const params = new URLSearchParams();
if (width) params.set('width', width);
if (height) params.set('height', height);
if (quality !== 85) params.set('quality', quality);
if (format !== 'auto') params.set('format', format);
if (fit !== 'cover') params.set('fit', fit);
if (withoutEnlargement) params.set('withoutEnlargement', 'true');
const queryString = params.toString();
return `${this.baseUrl}/${assetId}${queryString ? `?${queryString}` : ''}`;
}
// Vordefinierte Größen
thumbnail(assetId, size = 150) {
return this.build(assetId, {
width: size,
height: size,
format: 'webp',
quality: 80
});
}
hero(assetId, width = 1920, height = 800) {
return this.build(assetId, {
width,
height,
format: 'webp',
quality: 85,
fit: 'cover'
});
}
logo(assetId, maxWidth = 200) {
return this.build(assetId, {
width: maxWidth,
format: 'png',
fit: 'contain',
withoutEnlargement: true
});
}
}
// Verwendung
const assetBuilder = new AssetUrlBuilder();
const thumbnailUrl = assetBuilder.thumbnail('8e1cf6a9-d1d6-4090-9a36-303f1f57a016');
const heroUrl = assetBuilder.hero('8e1cf6a9-d1d6-4090-9a36-303f1f57a016', 1500, 600);
Python Asset-Manager
from urllib.parse import urlencode
class AssetManager:
def __init__(self, base_url='https://api.kulturpool.at/assets'):
self.base_url = base_url
def build_url(self, asset_id, **kwargs):
"""Asset-URL mit Parametern erstellen"""
params = {k: v for k, v in kwargs.items() if v is not None}
# Boolean-Werte in Strings konvertieren
for key, value in params.items():
if isinstance(value, bool):
params[key] = str(value).lower()
url = f"{self.base_url}/{asset_id}"
if params:
url += f"?{urlencode(params)}"
return url
def get_responsive_urls(self, asset_id, format_type='webp'):
"""Responsive Bild-URLs für verschiedene Bildschirmgrößen"""
return {
'mobile': self.build_url(asset_id, width=480, height=320, format=format_type, fit='cover'),
'tablet': self.build_url(asset_id, width=768, height=512, format=format_type, fit='cover'),
'desktop': self.build_url(asset_id, width=1200, height=800, format=format_type, fit='cover'),
'large': self.build_url(asset_id, width=1920, height=1080, format=format_type, fit='cover')
}
def get_optimized_thumbnail(self, asset_id, size=300):
"""Optimiertes Thumbnail"""
return self.build_url(
asset_id,
width=size,
height=size,
format='webp',
quality=80,
fit='cover'
)
# Verwendung
asset_manager = AssetManager()
asset_id = '8e1cf6a9-d1d6-4090-9a36-303f1f57a016'
# Responsive URLs
responsive_urls = asset_manager.get_responsive_urls(asset_id)
print(f"Mobile: {responsive_urls['mobile']}")
# Thumbnail
thumbnail_url = asset_manager.get_optimized_thumbnail(asset_id, 150)
print(f"Thumbnail: {thumbnail_url}")
Anwendungsfälle
Content Management System Integration
// Asset-URLs für Institution Hero-Images generieren
async function getInstitutionWithOptimizedImages(institutionId) {
const response = await fetch(`https://api.kulturpool.at/institutions/${institutionId}`);
const { data: institution } = await response.json();
const assetBuilder = new AssetUrlBuilder();
return {
...institution,
optimizedImages: {
favicon: institution.favicon?.id ?
assetBuilder.build(institution.favicon.id, {
width: 32,
height: 32,
format: 'png'
}) : null,
logo: institution.logo?.id ?
assetBuilder.logo(institution.logo.id, 200) : null,
hero: institution.hero_image?.id ?
assetBuilder.hero(institution.hero_image.id) : null,
heroMobile: institution.hero_image?.id ?
assetBuilder.build(institution.hero_image.id, {
width: 768,
height: 400,
format: 'webp',
fit: 'cover'
}) : null
}
};
}
Bildergalerie mit Lazy Loading
class ImageGallery {
constructor(containerId) {
this.container = document.getElementById(containerId);
this.assetBuilder = new AssetUrlBuilder();
this.observer = new IntersectionObserver(this.handleIntersection.bind(this));
}
addImage(assetId, alt = '') {
const imageContainer = document.createElement('div');
imageContainer.className = 'gallery-item';
imageContainer.dataset.assetId = assetId;
imageContainer.dataset.alt = alt;
// Placeholder
const placeholder = document.createElement('div');
placeholder.className = 'image-placeholder';
placeholder.style.backgroundColor = '#f0f0f0';
placeholder.style.aspectRatio = '16/9';
imageContainer.appendChild(placeholder);
this.container.appendChild(imageContainer);
// Für Lazy Loading beobachten
this.observer.observe(imageContainer);
}
handleIntersection(entries) {
entries.forEach(entry => {
if (entry.isIntersecting) {
this.loadImage(entry.target);
this.observer.unobserve(entry.target);
}
});
}
loadImage(container) {
const assetId = container.dataset.assetId;
const alt = container.dataset.alt;
const picture = document.createElement('picture');
// WebP für moderne Browser
const sourceWebP = document.createElement('source');
sourceWebP.srcset = this.assetBuilder.build(assetId, {
width: 800,
height: 450,
format: 'webp',
fit: 'cover'
});
sourceWebP.type = 'image/webp';
// JPEG Fallback
const img = document.createElement('img');
img.src = this.assetBuilder.build(assetId, {
width: 800,
height: 450,
format: 'jpg',
fit: 'cover'
});
img.alt = alt;
img.loading = 'lazy';
picture.appendChild(sourceWebP);
picture.appendChild(img);
container.innerHTML = '';
container.appendChild(picture);
}
}
CDN-Integration und Caching
class CachedAssetManager {
constructor() {
this.assetBuilder = new AssetUrlBuilder();
this.cache = new Map();
this.cacheTimeout = 5 * 60 * 1000; // 5 Minuten
}
async preloadAsset(assetId, options = {}) {
const url = this.assetBuilder.build(assetId, options);
const cacheKey = `${assetId}_${JSON.stringify(options)}`;
if (this.cache.has(cacheKey)) {
const cached = this.cache.get(cacheKey);
if (Date.now() - cached.timestamp < this.cacheTimeout) {
return cached.blob;
}
}
try {
const response = await fetch(url);
const blob = await response.blob();
this.cache.set(cacheKey, {
blob,
timestamp: Date.now()
});
return blob;
} catch (error) {
console.error(`Fehler beim Laden von Asset ${assetId}:`, error);
return null;
}
}
createObjectUrl(assetId, options = {}) {
const cacheKey = `${assetId}_${JSON.stringify(options)}`;
const cached = this.cache.get(cacheKey);
if (cached?.blob) {
return URL.createObjectURL(cached.blob);
}
// Fallback: Direkte URL zurückgeben
return this.assetBuilder.build(assetId, options);
}
// Cache aufräumen
clearExpiredCache() {
const now = Date.now();
for (const [key, value] of this.cache.entries()) {
if (now - value.timestamp >= this.cacheTimeout) {
this.cache.delete(key);
}
}
}
}
SEO-optimierte Asset-Integration
function generateSEOOptimizedAssetUrls(assetId, title = '') {
const assetBuilder = new AssetUrlBuilder();
return {
// Open Graph Image
ogImage: assetBuilder.build(assetId, {
width: 1200,
height: 630,
format: 'jpg',
fit: 'cover',
quality: 85
}),
// Twitter Card Image
twitterImage: assetBuilder.build(assetId, {
width: 1024,
height: 512,
format: 'jpg',
fit: 'cover',
quality: 85
}),
// JSON-LD Structured Data Image
structuredDataImage: assetBuilder.build(assetId, {
width: 1200,
height: 800,
format: 'jpg',
fit: 'cover',
quality: 90
}),
// Apple Touch Icon
appleTouchIcon: assetBuilder.build(assetId, {
width: 180,
height: 180,
format: 'png',
fit: 'cover'
})
};
}
function getBestSupportedFormat() {
// Feature Detection für moderne Bildformate
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// AVIF Support
if (canvas.toDataURL('image/avif').indexOf('data:image/avif') === 0) {
return 'avif';
}
// WebP Support
if (canvas.toDataURL('image/webp').indexOf('data:image/webp') === 0) {
return 'webp';
}
return 'jpg'; // Fallback
}
// Automatische Format-Auswahl
const optimalFormat = getBestSupportedFormat();
const optimizedUrl = assetBuilder.build(assetId, {
width: 800,
height: 600,
format: optimalFormat
});
Batch-Preloading für bessere Performance
async function preloadAssetBatch(assetIds, options = {}) {
const promises = assetIds.map(async (assetId) => {
const link = document.createElement('link');
link.rel = 'preload';
link.as = 'image';
link.href = assetBuilder.build(assetId, options);
document.head.appendChild(link);
return new Promise((resolve) => {
link.onload = resolve;
link.onerror = resolve; // Auch bei Fehlern fortfahren
});
});
await Promise.allSettled(promises);
}
// Verwendung für kritische Bilder
const criticalAssets = ['asset-1', 'asset-2', 'asset-3'];
await preloadAssetBatch(criticalAssets, {
width: 1200,
height: 600,
format: 'webp'
});
Fehlerbehandlung
404 - Asset nicht gefunden
{
"detail": "Asset mit ID 'invalid-id' wurde nicht gefunden"
}
500 - CMS-Fehler
{
"detail": "Fehler beim Abrufen des Assets vom CMS"
}
Fehlerbehandlung im Frontend
function createAssetWithFallback(assetId, options = {}, fallbackSrc = '/placeholder.jpg') {
const img = new Image();
const primaryUrl = assetBuilder.build(assetId, options);
img.onerror = () => {
img.src = fallbackSrc;
};
img.src = primaryUrl;
return img;
}