API-Dokumentation

Server-Basis: http://timms4culture.hdf.de

OpenAPI 3.1 (YAML) Im Swagger-Editor öffnen


1. Allgemeines

Timms4Culture stellt zwei Familien von HTTP-Endpoints bereit:

  • Medien-URLs (/media/<id>/… und /meta/<id>/…) liefern Original-Datei, HLS-Streams, Vorschaubilder, WebVTT-Untertitel und PBCore-XML.
  • JSON-API (/api/<namespace>.<method>.<format>) gibt Daten in JSON oder XML zurück und wird sowohl von der Web-Oberfläche als auch von externen Systemen genutzt.

Jede Video-Datei wird durch eine UUID identifiziert (Spalte videos.id); diese ID erscheint in jeder URL und in jedem API-Response. Optional kann jedes Video eine externe ID tragen (z. B. LFS003055). Externe IDs sind eindeutig und können pro Pool über eine Regex erzwungen werden.

2. Authentifizierung

Öffentliche Endpoints (Lese-Zugriff auf publizierte Inhalte) brauchen keinen Login. Schreibende Endpoints und persönliche Daten erfordern Authentifizierung. Drei Varianten werden unterstützt:

2.1 Session-Cookie

Über die Web-Oberfläche oder POST /api/auth.login.json mit email+password. Der Server setzt ein Session-Cookie (SameSite=Lax), das für nachfolgende Requests verwendet wird.

2.2 Bearer-Token (API-Key)

Jeder Nutzer kann sich auf der Profil-Seite einen API-Key generieren. Der Klartext-Token wird genau einmal angezeigt; in der DB liegt nur ein SHA-256(pepper + token)-Hash. Verwendung als Header:

Authorization: Bearer <token>

2.3 Query-Parameter

Für Clients, die keine Custom-Header setzen können (z. B. einfache <img>-Tags, manche Webhooks), wird derselbe Token auch als ?apikey=<token>-Parameter akzeptiert.

Deaktivierte Accounts (users.is_enabled = 0) werden bei allen Auth-Pfaden abgewiesen, auch wenn der Token noch gültig wäre.

3. Medien-URLs

Diese Routen liefern Binärdaten oder Klartext direkt aus dem videofiles/<id>/-Verzeichnis aus. Antworten enthalten Content-Type und Accept-Ranges wo sinnvoll.

3.1 Video-Stream

HLS-Multibitrate (1080p / 720p / 480p) wird on-the-fly aus einer Zip ausgeliefert:

http://timms4culture.hdf.de/media/{id}/hls/masterplaylist.m3u8

Die Master-Playlist verweist auf v0/playlist.m3u8 (1080p) etc.

Original-Datei mit Byte-Range-Support (für <video>-Direkt-Streaming, solange HLS noch nicht konvertiert ist):

http://timms4culture.hdf.de/media/{id}/file

3.2 Vorschaubild

Aktives, vom Admin gewähltes oder hochgeladenes Poster:

http://timms4culture.hdf.de/media/{id}/poster.jpg

Der Konvertierungs-Bot legt zusätzlich drei Auto-Vorschläge ab (poster1.jpg, poster2.jpg, poster3.jpg — Screenshots bei 25 / 50 / 75 % der Spieldauer). Existiert kein aktives poster.jpg, fällt die Hilfsfunktion posterUrl() der Reihe nach auf poster2 → poster1 → poster3 → /skins/grey/images/no_thumbnail.jpg zurück.

3.3 PBCore

Strukturierte Metadaten im PBCore-XML-Format:

http://timms4culture.hdf.de/meta/{id}/pbcore.xml

Liefert 404, solange kein PBCore-Dokument hochgeladen oder im Editor gespeichert wurde. Aufbau siehe Abschnitt 8.

3.4 Untertitel & Kapitel

WebVTT, pro Sprache eine Datei:

http://timms4culture.hdf.de/media/{id}/captions.{lang}.vtt
http://timms4culture.hdf.de/media/{id}/chapters.{lang}.vtt

{lang} = 2–8 Kleinbuchstaben (z. B. de, en). Beide Dateitypen sind kompatibel zu <track kind="captions|chapters"> in video.js.

4. JSON-API: Konventionen

4.1 URL-Schema

POST /api/{namespace}.{method}.{format}
  • namespace ist immer lowercase (z. B. video, profile, usermanager).
  • method ist lowercase ohne Trenner (z. B. search, uploadstart, homesection).
  • format ist üblicherweise json. Alternativ: xml, successcode, jsonp.

4.2 Request

Parameter werden klassisch per application/x-www-form-urlencoded oder multipart/form-data (für Datei-Uploads) übertragen. Arrays als field[]=…. Auth via Session-Cookie, Bearer-Header oder ?apikey=.

4.3 Response (JSON)

{
    "error":   { "id": 0, "msg": "" },
    "result":  { ... },
    "runtime": 0.0042,
    "request": { ... }
}

Bei Fehlern ist error.id > 0 und result gleich null. request spiegelt die Eingabe-Parameter.

4.4 Beispiel-Request mit curl

curl -X POST http://timms4culture.hdf.de/api/video.search.json \
     -H "Authorization: Bearer <apikey>" \
     --data-urlencode "q=Heilbronn" \
     --data-urlencode "page=1"

5. Endpoints (öffentlich)

5.1 video.search

POST /api/video.search.json

Paginierte Volltext-Suche über alle publizierten Videos.

  • q — Suchbegriff (LIKE über Titel, externe ID, Beschreibung, Pool-Name)
  • pool — UUID des Pools (optional)
  • durationshort (< 5 min), mid (5–30 min), long (> 30 min)
  • sortnewest (Default), oldest, az, za
  • page — 1-basierte Seite
  • per_page — Default 48, Maximum 100

Antwort:

{
    "rows":        [ { "id": "...", "name": "...", "external_id": "...", "pool_id": "...",
                       "pool_name": "...", "ts_created": 1716480000, "org_duration": 312.45 }, … ],
    "total":       127,
    "page":        1,
    "per_page":    48,
    "total_pages": 3
}

5.2 video.homesection

POST /api/video.homesection.json

Liefert je 12 Items für eine Startseiten-Sektion. Parameter:

  • typeforyou (Random), new (letzte 30 Tage), city (Suche), pool (Pool-Random)
  • city — Stadtname (nur bei type=city)
  • pool_name — Name des Pools (nur bei type=pool)
  • limit — Default 12, Max 24

Jede Row enthält zusätzlich poster_url mit Cache-Buster — bereit zum direkten Anzeigen.

6. Endpoints (Login)

Zugriff über Session-Cookie oder API-Key. Schreibende Endpoints prüfen zusätzlich Pool-Mitgliedschaft des Users.

6.1 Video-CRUD

  • POST /api/video.list.json · pool_id? — Videos des Users (optional auf einen Pool eingeschränkt)
  • POST /api/video.update.json · id, name?, external_id?, description? — partielles Update; externe ID wird gegen Pool-Regex und Eindeutigkeit geprüft
  • POST /api/video.delete.json · id — Video + Dateien löschen
  • POST /api/video.assetstatus.json · id — Status der Assets (Poster-Slots, PBCore, Captions, Chapters)

6.2 Asset-Uploads

  • POST /api/video.posterupload.json · id + file (JPG, max 8 MiB)
  • POST /api/video.posterchoose.json · id, slot=1|2|3
  • POST /api/video.posterclear.json · id
  • POST /api/video.pbcoreupload.json · id + file (XML, max 4 MiB)
  • POST /api/video.pbcoredelete.json · id
  • POST /api/video.captionupload.json · id, kind=captions|chapters, lang + file (VTT, max 2 MiB)
  • POST /api/video.captiondelete.json · id, kind, lang
  • POST /api/video.captioncontent.json · id, kind, lang — liefert {content, exists}
  • POST /api/video.captionsave.json · id, kind, lang, content (WebVTT-Text)

6.3 Audit-Log

  • POST /api/video.auditlist.json · id — letzte 50 Änderungen an Titel/External-ID/Beschreibung

6.4 Profile (Self-Service)

  • POST /api/profile.updatename.json · name
  • POST /api/profile.changepassword.json · current_password, new_password (min. 8 Zeichen)
  • POST /api/profile.generateapikey.json — liefert {apikey} einmalig; alter Key wird ungültig
  • POST /api/profile.revokeapikey.json — entfernt den API-Key

7. Chunked Upload

Große Dateien (bis 16 GiB) werden in 5-MiB-Chunks hochgeladen. Drei Endpoints decken den Lifecycle ab:

  1. Start
    POST /api/video.uploadstart.json
    Felder: pool_id, filename, size, total_chunks, chunk_size. Antwort: { upload_id, received_chunks: [..] }. Resume-fähig — bei identischem filename+size wird die bestehende Session weiterverwendet.
  2. Chunk hochladen
    POST /api/video.uploadchunk.json
    upload_id, index und multipart-Feld file. Antwort: { received, total }.
  3. Status abfragen
    POST /api/video.uploadstatus.json
    upload_id{ received_chunks, total_chunks }.
  4. Abschluss
    POST /api/video.uploadfinish.json
    upload_id. Server mergt die Chunks, berechnet MD5, prüft auf Duplikate (gleicher Hash → Fehler, keine DB-Zeile) und legt im Erfolgsfall videos-Row + videofiles/<id>/org/<md5>.<ext> an.
  5. Abbrechen
    POST /api/video.uploadcancel.json
    upload_id → entfernt Session und Chunks.

Nach erfolgreichem uploadfinish erzeugt der Cron-Bot convertmiss die Poster (poster1/2/3.jpg) und das HLS-Bündel (hls.zip) im Hintergrund.

8. PBCore-Editor-Felder

Der PBCore-Editor speichert ein normalisiertes pbcoreDescriptionDocument mit Namespace http://www.pbcore.org/PBCore/PBCoreNamespace.html. Der Server-seitige Serializer schreibt nur Felder, die tatsächlich befüllt sind. Strukturierter Zugriff:

  • POST /api/video.pbcorefields.json · id — parsed in JSON-Struktur
  • POST /api/video.pbcorefieldssave.json · id+fields (JSON-Body) — serialisiert & speichert
  • POST /api/video.pbcorevalidate.json · id{exists, valid, error}

JSON-Struktur (Repeater sind Arrays von Objekten):

{
    "identifiers":    [ { "source": "FESAD", "value": "LFS001199" } ],
    "titles":         [ { "type":   "Main",  "value": "Stuttgarts Neckarhafen" } ],
    "description":    "Mehrzeiliger Text …",
    "subjects":       [ "Stuttgart", "Hafen", "Neckar" ],
    "genre":          "Imagefilm",
    "creators":       [ { "role": "Director", "name": "Müller" } ],
    "contributors":   [ { "role": "Kamera",   "name": "Meier"  } ],
    "rights_summary": "© 2024 …",
    "asset_dates":    [ { "type": "created",  "value": "1971-05-12" } ]
}

Resultierendes XML (Auszug):

<?xml version="1.0" encoding="UTF-8"?>
<pbcoreDescriptionDocument xmlns="http://www.pbcore.org/PBCore/PBCoreNamespace.html">
  <pbcoreIdentifier source="FESAD">LFS001199</pbcoreIdentifier>
  <pbcoreTitle titleType="Main">Stuttgarts Neckarhafen</pbcoreTitle>
  <pbcoreDescription>Mehrzeiliger Text …</pbcoreDescription>
  <pbcoreSubject>Stuttgart</pbcoreSubject>
  <pbcoreGenre>Imagefilm</pbcoreGenre>
  <pbcoreCreator>
    <creator>Müller</creator>
    <creatorRole>Director</creatorRole>
  </pbcoreCreator>
  <pbcoreRightsSummary>
    <rightsSummary>© 2024 …</rightsSummary>
  </pbcoreRightsSummary>
  <pbcoreAssetDate dateType="created">1971-05-12</pbcoreAssetDate>
</pbcoreDescriptionDocument>

9. Admin: UserManager

Erfordert users.is_admin = 1. Alle Endpoints prüfen das serverseitig und liefern 403 bei nicht-Admin.

  • POST /api/usermanager.list.json — alle User + Counts (Pools / Videos)
  • POST /api/usermanager.getone.json · id — User + Pools + Pool-Stats + letzte 10 Audit-Einträge
  • POST /api/usermanager.create.json · name, email, password, is_admin?, is_enabled?, pool_ids[]?
  • POST /api/usermanager.update.json · id, name?, email?, is_admin?, is_enabled? — eigener Account kann nicht deaktiviert werden
  • POST /api/usermanager.setpassword.json · id, password (min. 8 Zeichen)
  • POST /api/usermanager.setpools.json · id, pool_ids[] — ersetzt die Zuordnung komplett
  • POST /api/usermanager.poolsall.json — Liste aller Pools für Edit-UIs