Bring your own media service
The media processor calls an external media service and places the result on your timeline. This guide walks through wiring one up, whether the service you want already has a preset or not. Background on how the pieces fit together is in Media services.
1. Pick a preset, or plan to write a spec
Check whether the service you want to use already has a shipped preset (see the media processor reference for what's available). If it does, start there: a preset fills in the request shape for you, and you only need to add your key and a few variables.
If there is no preset for your service, you can still use it. media does not require a preset, it works from any inline spec you write, as long as the service is reachable over HTTP. The worked example below does exactly this, for a plain text-to-speech service that has no shipped preset.
When you write your own spec and the request body is JSON, interpolate any free text (like the prompt or script) through the json filter, not bare, so a value containing a quote, newline, or backslash still produces valid JSON: use "prompt": "{{ .Text | json }}", not "prompt": "{{ .Text }}". Shipped presets already do this for you; a hand-written spec does not unless you write it yourself.
2. Set the service's API key
Whatever service you use, the request needs to authenticate with it somehow, usually an API key. Add that key in the workspace's Variables & Service API Keys, under the name your spec (or preset) expects.
If a run needs a key that has not been set, it is refused before it starts, not partway through, so this step is worth doing before your first run.
3. Choose a source strategy
source controls how the request is built from your inputs. There are three strategies:
singlebuilds one request from the text you wire into the step'ssourceinput. Pointsourceat a step or file that contains the text you want turned into media, for example a script or voiceover line.gapsscans a timeline level (set withgap_level) for empty spans and generates one request per gap, fitted to fill that span. See Filling gaps and generating per segment for a worked example.planreads a segment plan from asegment_planinput (a list of segments, each with its own text and duration) and generates one request per matching segment.
4. Choose a place strategy
place controls where the generated asset lands on your output timeline:
at_positionplaces the asset at position 0 on the output timeline.chainplaces the asset immediately after whatever is already on that timeline, end to end.gapsfits each generated clip exactly to the span it was created to fill, trimming it if it runs long.
Use chain when you are generating several pieces in sequence and want them to play back to back; use at_position when you are generating a single asset for a fixed spot; use gaps together with source: gaps (or source: plan when the plan targets specific spans) so each clip lands in the exact hole it was generated for.
5. Run it
Add a review gate if you want to approve the result before it is finalized, the same as any other step. Steps cache their output by default, including this one: the same request against the same inputs produces the same asset, so a re-run without changes will not call the service again.
Worked example: a service with no shipped preset
Say you want to use a fictional text-to-speech service, voxcast, that has no shipped preset. It authenticates with a bearer token, accepts a simple JSON body, and returns the audio bytes directly in the response. You describe it with an inline spec:
- id: narration
processor: media
input:
- id: source
step: script # step or file input providing the text to speak
params:
spec: |
transport: http
auth:
header: Authorization
value: 'Bearer {{ secret "VOXCAST_API_KEY" }}'
request:
method: POST
url: 'https://api.voxcast.example/v1/speak'
body: '{"text":"{{ .Text | json }}","voice":"{{ .Voice }}"}'
response:
kind: bytes
vars:
Voice: "narrator-warm-1"
source: single
place: chain
output:
timeline: main
review: true
cache: true
What each part is doing:
specdescribesvoxcastcompletely: how to authenticate (a bearer token read from a workspace secret namedVOXCAST_API_KEY), what request to send (aPOSTwith the spoken text and a voice name), and how to read the response (the audio comes back as raw bytes).{{ .Text }}is filled in from thesourceinput automatically; you never type your script into the spec itself.varssuppliesVoice, a value the request body needs that is not part of your source text. Add whatever extra values your service's request needs the same way.place: chainlands the generated narration right after anything already on themaintimeline, useful when you are stitching together several generated lines in order.
Before running this, add VOXCAST_API_KEY under the workspace's Variables & Service API Keys. Nothing else about this step differs from using a shipped preset: the same source, place, vars, review, and cache options apply either way.
Worked example: generating images with a preset
You can also use a preset that already knows how to talk to a service. The imagen preset generates a still image from your text with Google's Imagen and animates it into a vertical motion clip:
- id: cover_image
processor: media
input:
- id: source
step: script
params:
preset: imagen
vars:
Model: imagen-4.0-generate-001
source: single
place: at_position
output:
timeline: broll
cache: true
Set GOOGLE_API_KEY in the workspace's Variables & Service API Keys before running this. The preset requests a still image from the service, then automatically animates it into a moving vertical clip before it lands on your timeline, so you never have to write your own post-processing step.
Generate video
The veo preset generates a short video clip from your text with Google's Veo:
- id: b_roll
processor: media
input:
- id: source
step: script
params:
preset: veo
source: single
place: chain
output:
timeline: broll
cache: true
Set GOOGLE_API_KEY in the workspace's Variables & Service API Keys before running this. Video generation does not come back right away: the request kicks off a job on the service, and the step waits, checking in periodically, until the finished clip is ready before placing it on your timeline. You do not need to do anything differently for this, the step behaves like any other from the outside, it just takes longer to finish.
Generate avatar presentations
The heygen preset generates a video of a digital avatar presenting your script with HeyGen:
- id: avatar_video
processor: media
input:
- id: source
step: script
params:
preset: heygen
vars:
AvatarID: "avatar_name_1"
VoiceID: "en_us_001"
source: single
place: at_position
output:
timeline: main
cache: true
Set HEYGEN_API_KEY in the workspace's Variables & Service API Keys before running this. Like video generation, avatar presentation does not come back right away: the step starts a background job with the service and waits, checking in periodically, until the finished video is ready before placing it on your timeline.