Skip to content

@wity/scene-compose

Server-side full compositing pass. Blends ws-video clips, ws-audio tracks, and an optional graphics overlay MP4 into a single output using FFmpeg filter_complex. Node.js / Lambda only.

This is step 2 of the two-step pipeline — it takes the graphics MP4 produced by @wity/scene-to-video and composites it on top of the video/audio content derived from the same scene document.

Install

bash
npm install @wity/scene-compose
# peer dep:
npm install @wity/scene-core

FFmpeg must be available in PATH (or set FFMPEG_PATH env var).


compose(sceneXml, graphicsMp4Url, options)

ts
compose(
  sceneXml:       string,
  graphicsMp4Url: string | null,
  options:        ComposeOptions
): Promise<ComposeResult>
ArgumentTypeDescription
sceneXmlstringRaw <wity-scene> XML — same document passed to scene-to-video
graphicsMp4Urlstring | nullURL of the graphics overlay MP4 from scene-to-video. Pass null if the scene has no graphic elements.
optionsComposeOptionsSee below

ComposeOptions

OptionTypeDefaultDescription
outputBucketstringOUTPUT_BUCKET envS3 bucket for the output file
outputPrefixstring"scene-composed/"S3 key prefix
fpsnumber30Output frame rate
sceneWidthnumber1280Canvas width px
sceneHeightnumber720Canvas height px

ComposeResult

ts
{ url: string, fileSize: number }

url is the public S3 URL of the finished MP4.


What is composited

Element typeHandled?
ws-videoYes — each clip is scaled, positioned (x/y/width/height/fit), trimmed (trimIn), and overlaid at the correct scene time (begin/dur)
ws-audioYes — each track is delayed to begin time, volume-scaled, and mixed
ws-video embedded audioYes — extracted and mixed unless muted: true or volume: 0
ws-rect / ws-text / ws-imageVia graphicsMp4Url overlay (from scene-to-video)

Example

js
import { compose } from '@wity/scene-compose';

const result = await compose(sceneXml, graphicsMp4Url, {
  outputBucket: 'my-s3-bucket',
  fps:          30,
  sceneWidth:   1920,
  sceneHeight:  1080,
});

console.log(result.url);       // https://my-s3-bucket.s3.ap-south-1.amazonaws.com/scene-composed/...
console.log(result.fileSize);  // bytes

FFmpeg pipeline internals

The compositing pass builds a filter_complex with the following input layout:

[0]   — lavfi color=black base canvas (scene resolution, scene duration)
[1…V] — ws-video clip files (one per element, pre-seeked to trimIn)
[V+1] — graphics overlay MP4 (if provided)
[V+2…] — ws-audio track files (one per element)

Video path: base → scale+fit each clip → setpts to shift to begin time → overlay=x:y:enable='between(t,begin,end)' → overlay graphics on top.

Audio path: each track/clip audio is resampled to 48 kHz, delayed via adelay to begin time, volume-scaled, then merged with amix=normalize=0:dropout_transition=0.

Output: single libx264 / aac MP4, uploaded to S3 via streaming (no full-file buffer in memory).


Lambda deployment

The witySceneCompose Lambda wraps this package. It accepts:

json
{
  "sceneXml":       "<wity-scene>...</wity-scene>",
  "graphicsMp4Url": "https://...",
  "fps":            30,
  "sceneWidth":     1920,
  "sceneHeight":    1080
}

And returns:

json
{ "url": "https://...", "fileSize": 1234567 }

graphicsMp4Url is optional — omit it when the scene contains no graphic elements.

See services/scene-render/scene-compose/ for the handler and config.

ConfigValue
Function namewitySceneCompose
Memory2048 MB
Timeout300 s
Ephemeral storage4096 MB
RuntimeNode.js 20

Two-step pipeline (managed by the gateway)

sceneXml

  ├──▶  witySceneToVideo  ──▶  graphicsMp4Url

  └──▶  witySceneCompose  ◀──  graphicsMp4Url


        final composited MP4

Both Lambda invocations receive the same sceneXml. The witySceneRender gateway orchestrates this automatically — callers send a single request to the gateway and receive the final URL. Neither witySceneToVideo nor witySceneCompose should be called directly.

See the witySceneRender gateway reference and the Deployment guide for the full architecture.