🔄 The video metadata pipeline#
When an editor pastes a video URL, sc-videos fetches structured metadata from the external provider and populates the content's fields automatically. This page explains the end-to-end flow.
🔗 The pipeline at a glance#
flowchart LR
A["Editor pastes URL"] --> B["VideoURLWidget"]
B -->|POST| C["@video-metadata"]
C --> D["resolve_url()"]
D --> E["IVideoMetadataProvider"]
E -->|HTTP| F["External API"]
F --> E
E --> C
C -->|JSON| B
B --> G["applyVideoMetadataToForm()"]
G --> H["Content fields updated"]
H --> I["Event subscriber"]
I --> J["Preview image downloaded"]
1️⃣ URL entry (frontend)#
The editor pastes a URL into the VideoURLWidget. either in the content type's sidebar form or in the Video Player block's in-block edit form.
The widget does not validate the URL against providers on the frontend. It sends the URL as-is to the backend when the editor clicks the arrow (→) button.
2️⃣ URL resolution (backend)#
The @video-metadata service calls resolve_url(url):
Iterates over all registered
IVideoMetadataProviderutilities (loaded viaget_video_services()).Tests the URL against each provider's
url_patternregex.On first match, extracts the
video_idcapture group.Returns a
VideoReference(service, video_id)tuple.
If no provider matches, the service returns a 400 Bad Request error.
3️⃣ Metadata fetching (backend)#
With the VideoReference in hand, the service looks up the named utility for the matched service and calls fetch_metadata(video_id).
Each provider uses an HTTP client to call the external API:
YouTube API client: calls the YouTube Data API v3 (requires an API key).
YouTube public client: calls the oEmbed endpoint (no key, limited data).
Vimeo public client: calls the Vimeo oEmbed endpoint.
The client returns a VideoMetadata dataclass:
@dataclass(frozen=True)
class VideoMetadata:
video_id: str
title: str
description: str
duration: int # seconds (0 if unavailable)
thumbnail_url: str
channel: str
tags: list[str]
4️⃣ Response (backend → frontend)#
The service serializes VideoMetadata to JSON and returns it.
The response is documented in 🌐 REST API. @video-metadata.
5️⃣ Form population (frontend)#
The VideoURLWidget receives the JSON response and calls applyVideoMetadataToForm():
Writes the full metadata object to
_metadata(always).For each mapped field (
title,description,duration,channel,subjects,video_id,service), writes the value only if the field is currently empty on the form, so editor customizations are never overwritten.
6️⃣ Content save → event subscribers (backend)#
When the editor saves the content, Plone's IObjectAddedEvent or IObjectModifiedEvent fires.
The event subscriber:
Reads the
_metadataJSON field from the content.Copies
duration,channel,video_id, andserviceinto their respective schema fields (if changed).Downloads the thumbnail image from
thumbnail_urland stores it aspreview_image(only if the content doesn't already have a preview image).
This ensures the content object's fields are always in sync with the stored metadata, even if the content was created via the REST API without using the frontend widget.
⚠️ Error handling#
Scenario |
Backend response |
Frontend behavior |
|---|---|---|
URL doesn't match any provider |
|
Error message below the input |
Provider API unreachable |
|
Error message below the input |
Network failure (fetch to backend) |
. |
"Failed to connect" message |
Thumbnail download fails (on save) |
Logged as warning |
Content saved without preview image |
See also
🔌 The provider system. How providers are registered and how to add new ones.
🌐 REST API. @video-metadata. Full API reference for the
@video-metadataendpoint.📋 IRemoteVideo behavior. The
IRemoteVideobehavior fields that receive the metadata.