Zum Inhalt springen
Anleitung

Medien aus dem eigenen System abgleichen

Einen Ordner Ihres Systems in die Bibliothek spiegeln und von einer automatischen Playlist abspielen lassen.

3 Min LesezeitStand 30. Okt. 2026Als Markdown
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
Auf dieser Seite

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

  1. Legen Sie den Ordner an in der Bibliothek, etwa „Plakate aus dem PIM“, und suchen Sie seine Kennung:

    GET/v1/folders
    curl -sS "$SZENERA_API/v1/folders" \  -H "Authorization: Bearer $SZENERA_API_KEY" \  | jq -r '.[] | "\(.id)  \(.path)"'
    fld_01KJ7MWKW0MAPPE00000000001  /Plakate aus dem PIM
  2. 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}`);}
  3. Starten Sie es einmal von Hand und sehen Sie sich die Ausgabe an:

    Terminal
    node sync-media.ts ./export fld_01KJ7MWKW0MAPPE00000000001
    uploaded: herbst-angebot.pnguploaded: neue-oeffnungszeiten.mp4deleted: sommer-angebot.png
  4. 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.

  5. 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.