Medien aus dem eigenen System abgleichen
Einen Ordner Ihres Systems in die Bibliothek spiegeln und von einer automatischen Playlist abspielen lassen.
- Für wen
- Entwicklerinnen und Entwickler, die eine Produktdatenbank, ein DAM oder ein Marketing-Werkzeug anbinden, das Plakate und Videos ausgibt
- Voraussetzung
- ein Schlüssel
ak_…der Klasse Medien-Automation und ein Ordner in der Bibliothek - Ort
- Kommandozeile oder ein geplanter Auftrag auf Ihrem Server
Am Ende spiegelt ein Skript einen Ordner Ihres Systems in einen Ordner der Bibliothek: Neue Dateien kommen hinzu, entfernte verschwinden, unveränderte bleiben unberührt. Eine automatische Playlist auf diesem Ordner spielt das Ergebnis, ohne dass jemand sie pflegt.
So funktioniert es
Jede Datei erkennt das Skript an ihrer SHA-256-Prüfsumme. Es vergleicht die Prüfsummen Ihres Exports mit denen im Ordner der Bibliothek und handelt nur bei Unterschieden:
| Lage | Was das Skript tut |
|---|---|
| Eine Datei liegt nur in Ihrem Export | Hochladen: vorprüfen, Bytes schieben, abschließen. |
| Eine Datei liegt nur im Ordner der Bibliothek | Löschen. Gelöschte Medien lassen sich wiederherstellen. |
| Eine Datei liegt in beiden | Nichts. Eine geänderte Datei hat eine neue Prüfsumme, gilt also als neu, und die alte Fassung wird entfernt. |
Das Hochladen hat drei Schritte. POST /v1/assets/upload-precheck prüft Dateityp und Speicherkontingent und legt den Mediendatensatz an. Gibt es dieselben Bytes schon in Ihrer Organisation, trägt die Antwort keine Upload-Adresse, und die Bytes werden wiederverwendet. Sonst schieben Sie die Datei an die Adresse in presignedUrl. POST /v1/assets/upload-complete startet danach die Verarbeitung, und ein paar Sekunden später ist die Datei für Displays bereit, ein langes Video nach einigen Minuten.
Schritte
-
Legen Sie den Ordner an in der Bibliothek, etwa „Plakate aus dem PIM“, und suchen Sie seine Kennung:
GET/v1/folderscurl -sS "$SZENERA_API/v1/folders" \ -H "Authorization: Bearer $SZENERA_API_KEY" \ | jq -r '.[] | "\(.id) \(.path)"'fld_01KJ7MWKW0MAPPE00000000001 /Plakate aus dem PIM -
Speichern Sie das Skript:
TypeScript// sync-media.ts: node sync-media.ts ./export fld_…import { createHash } from 'node:crypto';import { readdir, readFile } from 'node:fs/promises';import { extname, join } from 'node:path'; const API = process.env.SZENERA_API;const KEY = process.env.SZENERA_API_KEY; // Klasse Medien-Automationconst [dir, folderId] = process.argv.slice(2); const TYPES: Record<string, string> = { '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.png': 'image/png', '.webp': 'image/webp', '.svg': 'image/svg+xml', '.mp4': 'video/mp4', '.webm': 'video/webm', '.pdf': 'application/pdf',};const SINGLE_UPLOAD_LIMIT = 50 * 1024 * 1024; // Darüber geht der Upload in Teilen async function call(method: string, path: string, body?: unknown) { const res = await fetch(`${API}${path}`, { method, headers: { Authorization: `Bearer ${KEY}`, 'Content-Type': 'application/json' }, body: body === undefined ? undefined : JSON.stringify(body), }); if (!res.ok) throw new Error(`${method} ${path}: ${res.status} ${await res.text()}`); return res.json();} // Was der Ordner der Bibliothek hält, nach Prüfsummeconst remote = new Map<string, { id: string; name: string }>();let cursor = '';do { const query = new URLSearchParams({ folderId, limit: '200' }); if (cursor) query.set('cursor', cursor); const page = await call('GET', `/v1/assets?${query}`); for (const asset of page.items) remote.set(asset.sha256, asset); cursor = page.nextCursor ?? '';} while (cursor); // Hochladen, was neu istconst local = new Set<string>();for (const file of await readdir(dir)) { const mimeType = TYPES[extname(file).toLowerCase()]; if (!mimeType) continue; const bytes = await readFile(join(dir, file)); const sha256 = createHash('sha256').update(bytes).digest('hex'); local.add(sha256); if (remote.has(sha256)) continue; if (bytes.length > SINGLE_UPLOAD_LIMIT) { console.log(`skipped, upload with szenera asset upload: ${file}`); continue; } const precheck = await call('POST', '/v1/assets/upload-precheck', { originalFileName: file, mimeType, sizeBytes: bytes.length, sha256, folderId, }); if (!precheck.exists) { const { url, headers } = precheck.presignedUrl; const put = await fetch(url, { method: 'PUT', headers, body: bytes }); if (!put.ok) throw new Error(`upload ${file}: ${put.status}`); } await call('POST', '/v1/assets/upload-complete', { assetId: precheck.assetId, sha256 }); console.log(`uploaded: ${file}`);} // Löschen, was in Ihrem System nicht mehr da istfor (const [sha256, asset] of remote) { if (local.has(sha256)) continue; const res = await fetch(`${API}/v1/assets/${asset.id}`, { method: 'DELETE', headers: { Authorization: `Bearer ${KEY}` }, }); console.log(res.ok ? `deleted: ${asset.name}` : `kept, used in a scene: ${asset.name}`);} -
Starten Sie es einmal von Hand und sehen Sie sich die Ausgabe an:
Terminalnode sync-media.ts ./export fld_01KJ7MWKW0MAPPE00000000001uploaded: herbst-angebot.pnguploaded: neue-oeffnungszeiten.mp4deleted: sommer-angebot.png -
Legen Sie eine automatische Playlist auf den Ordner im Portal an und weisen Sie sie Ihren Displays zu, siehe Eine Playlist automatisch aus einem Ordner oder Tags füllen. Neue Dateien kommen von selbst hinzu, sobald ihre Verarbeitung fertig ist.
-
Lassen Sie das Skript regelmäßig laufen, etwa jede Nacht nach Ihrem Export:
15 2 * * * cd /opt/szenera-sync && node sync-media.ts /exports/posters fld_01KJ7MWKW0MAPPE00000000001
Dateien über 50 MiB gehen in Teilen hoch. Das übernimmt die Kommandozeile: szenera asset upload --file=<pfad> --folder=<fld_…> lädt hoch, wartet, bis die Datei bereit ist, und räumt einen abgebrochenen Upload auf.
Ergebnis prüfen
- Der Ordner der Bibliothek hält genau die Dateien Ihres Exports.
- Ein zweiter Lauf direkt nach dem ersten schreibt nichts aus, weil sich nichts geändert hat.
- Die automatische Playlist listet die neuen Dateien unter Sequenz, markiert mit Aus der Quelle.
Wenn etwas nicht klappt
| Symptom | Bedeutung | Lösung |
|---|---|---|
403 Permission.Denied |
Dem Schlüssel fehlt asset.upload, asset.list oder asset.delete. |
Einen Schlüssel der Klasse Medien-Automation verwenden. |
422 Upload.MimeTypeNotAllowed |
Der Dateityp wird nicht unterstützt. | JPEG, PNG, WebP, SVG, MP4, WebM oder PDF verwenden. |
402 Upload.QuotaExceeded |
Der Speicher Ihres Pakets ist aufgebraucht. | Medien löschen, die Sie nicht mehr brauchen, oder das Paket wechseln. |
kept, used in a scene |
Das Löschen antwortete mit 409 Asset.InUse: Eine Szene nutzt die Datei direkt. |
Die Datei aus der Szene nehmen oder sie liegen lassen. |
| Eine Datei bleibt in der Verarbeitung | Lange Videos brauchen Minuten. | Auf media-library.asset.ready warten oder mit GET /v1/assets/{id} nachsehen. |
| Jeder Lauf lädt alles neu hoch | Das Skript vergleicht den falschen Ordner, oder der Export schreibt Dateien mit neuen Bytes. | Die Ordner-Kennung prüfen und gleiche Dateien Byte für Byte gleich exportieren. |