Skip to content
Apexify.jsDocs
Apexify.js version v5.4.5

Advanced / Video · guide

Video pipeline (videoPipeline)

Current Apexify.js 6.0.0 documentation for Video pipeline (videoPipeline).

apexify.jsRuntime: nodeCURRENTSince 6.0.0

painter.videoPipeline(source?) is the recommended API when several edits belong to one project: trim, deterministic segment replacement, captions, file audio, and procedural SFX. It uses a validated layer stack and isolated internal workspaces rather than asking application code to manage a chain of temporary output files.

Also on: painter.video.videoPipeline() (same VideoStack entry).

Basics (single ops): Video overview · Timeline · Text overlays · Video audio · createAudio


When to use what

SituationAPI
Building a video editor, server render project, or saved edit JSONvideoPipeline()
Exactly one FFmpeg job (probe, trim only, one audio mix, …)createVideo({ … })
Generated motion (gameplay, animate, scenes)animate / renderSceneToVideoFrames → then pipeline
Rich captions matching createTextaddTextOverlay or pipeline.text()
Procedural SFXpainter.createAudio + pipeline.audio() or mixAudio

Requirements and source policy are the same as createVideo: FFmpeg/ffprobe plus the centralized Buffer/local/HTTP(S) resolver described in Video overview.


Layer stack model

Each builder call adds or updates a validated layer:

Layer kindBuilder methodPurpose
source.source(path | Buffer, id?)Input video (exactly one render source).
trim.trim(start, end, id?)Keep [startTime, endTime] before later layers.
splice.splice({ targetStartTime, targetEndTime, replacementVideo/replacementFrames, … }, id?)Replace a timeline range.
text.text(overlay | overlays[], id?)Timed createText-style overlays.
audio.audio(tracks, options?, id?)File/Buffer/WAV plus procedural preset/synth/sequence tracks.

Stable layer IDs

  • Same id on source / trim / splice replaces the previous layer with that id.
  • Same id on text / audio merges overlays or tracks.
  • pushLayer(layer, { replace: true }) explicitly replaces instead of merging.
  • Invalid mutations are transactional: a rejected edit does not leave the stack partially changed.

This makes IDs suitable for editor timeline rows and deterministic project updates.


Execution order and splice determinism

Rendering is normalized to this order:

  1. source resolution;
  2. trim;
  3. splices, sorted by targetStartTime and then id;
  4. text overlays;
  5. audio mix;
  6. optional preview encode.

Overlapping splice ranges are rejected. Multiple durationPolicy: 'preserve' splices are rejected because changing total timeline length would make later source coordinates ambiguous.

The render result exposes the plan that actually ran:

SOURCEtypescript
typescript
Studio
const result = await pipeline.render({ outputPath: './out.mp4' }); console.log(result.passes);console.log(result.executionPlan);// e.g. ['trim', 'splice:broll', 'text', 'audio']

passes is the actual internal FFmpeg stage count, not an estimate.


Full example — trim, B-roll, titles, music + synth SFX

SOURCEtypescript
typescript
Studio
import { ApexPainter } from 'apexify.js'; const painter = new ApexPainter({ type: 'buffer' }); const pipeline = painter  .videoPipeline('./uploads/user-clip.mp4')  .trim(0, 60, 'trim')  .splice(    {      targetStartTime: 10,      targetEndTime: 15,      replacementVideo: './uploads/b-roll.mp4',      replacementStartTime: 0,      replacementDuration: 5,      durationPolicy: 'fit',    },    'broll'  )  .text(    [      {        text: 'Before B-roll',        x: 48,        y: 80,        startTime: 0,        endTime: 10,        font: { size: 42, family: 'Arial' },        decorations: { bold: true },        fill: { color: '#ffffff' },        effects: { shadow: { color: 'rgba(0,0,0,0.5)', offsetY: 3, blur: 8 } },        transitionIn: { type: 'fade', duration: 0.3 },      },      {        text: 'B-roll',        x: 48,        y: 80,        startTime: 10,        endTime: 15,        font: { size: 42, family: 'Arial' },        fill: { color: '#fbbf24' },        transitionIn: { type: 'slideLeft', duration: 0.4 },      },    ],    'titles'  )  .audio(    [      { type: 'file', source: './music.mp3', startTime: 0, volume: 0.35 },      { type: 'preset', preset: 'whoosh', startTime: 9.8, gain: 1.1 },    ],    { keepOriginalAudio: true, originalVolume: 0.85, durationPolicy: 'video' },    'sound'  ); const result = await pipeline.render({  outputPath: './out/final.mp4',  overwrite: true,  onProgress: ({ percent, speed }) => console.log(percent, speed),}); console.log('Done:', result.outputPath);console.log('Passes:', result.passes);console.log('Plan:', result.executionPlan);

Text layers — createText parity

.text() accepts VideoTextOverlayClip: the TextProperties styling surface plus timed video fields.

FieldMeaning
startTime, endTimeVisible window on the current timeline.
transitionIn, transitionOutFade/slide/zoom/bounce presets or validated custom expressions.
overlayOpacityMaster 0–1 overlay multiplier, separate from glyph fill opacity.

Unicode, quotes, newlines, multiple overlays, timed visibility, and transitions are handled without shell command interpolation. Later overlays in the same text layer are applied after earlier ones.

Single-op equivalent: Text overlays.


Audio layers — files + procedural audio

.audio(tracks, options?, id?) accepts:

Track typePurpose
fileLocal path, URL, or Buffer. Supports placement plus volume/pan/fades.
presetcreateAudio preset at startTime.
synthSynthSoundOptions at startTime.
sequenceProcedural event timeline placed on the video.
wavPre-built WAV Buffer.

Audio-layer options include original-audio retention/volume/speed/pitch and durationPolicy: 'video' | 'shortest' | 'longest'.

SOURCEtypescript
typescript
Studio
pipeline.audio(  [    { type: 'file', source: './bed.mp3', startTime: 0, volume: 0.3, pan: -0.2, fadeIn: 0.5 },    { type: 'preset', preset: 'coin', startTime: 2.5, gain: 0.9 },    { type: 'sequence', startTime: 0, events: [{ at: 0, preset: 'laser' }, { at: 0.2, preset: 'explosionSmall' }] },  ],  { keepOriginalAudio: false, durationPolicy: 'video' },  'sfx');

Undo / redo

The builder keeps bounded project history for editor-style mutations:

SOURCEtypescript
typescript
Studio
pipeline.text(caption, 'caption'); if (pipeline.canUndo()) pipeline.undo();if (pipeline.canRedo()) pipeline.redo();

undo() and redo() return false when no corresponding history entry exists. A new successful mutation clears redo history.


Versioned saved projects

toJSON() emits a versioned snapshot:

SOURCEtypescript
typescript
Studio
const snapshot = pipeline.toJSON();console.log(snapshot.version); // 1 await db.projects.save(JSON.stringify(snapshot));

For path/URL-backed projects, restore the persisted layer array through the public painter entry:

SOURCEtypescript
typescript
Studio
const snapshot = JSON.parse(await db.projects.load()); if (snapshot.version !== 1) {  throw new Error(`Unsupported video pipeline snapshot version: ${snapshot.version}`);} const restored = painter.videoPipeline(undefined, snapshot.layers);await restored.render({ outputPath: './export.mp4' });

For durable editor projects, prefer stable path/URL/object-storage references rather than embedding large media Buffers in database JSON.


render options

SOURCEtypescript
typescript
Studio
await pipeline.render({  outputPath: './out/final.mp4',  preset: 'export', // 'preview' performs a real final proxy encode  signal: controller.signal,  timeoutMs: 120_000,  overwrite: false,  onProgress: (p) => console.log(p.percent, p.speed),});
  • preset: 'export' keeps the full export path.
  • preset: 'preview' adds a real low-quality proxy encode, constrained to a maximum 960-pixel dimension while preserving aspect ratio.
  • signal, timeoutMs, overwrite, and onProgress use the same shared execution controls as createVideo.

Pipeline vs chained createVideo

Chained createVideovideoPipeline
Application manages intermediate pathsPipeline owns and cleans isolated workspaces
Easy to accidentally re-encode every stepDeterministic normalized pass plan
Exactly one operation per callDeclarative multi-layer project
Fine for scriptsBetter for editor/server project state

Operations that are not pipeline layer kinds (for example LUT, stabilization, or batch orchestration) remain single createVideo operations and can run before or after a pipeline export.


Combine with Scene export

Typical product flow:

  1. renderSceneToVideoFrames or animate for branded/generated segments.
  2. videoPipeline to trim uploads, splice generated segments, burn captions, and mix file/procedural audio.

See Render scene to video.


Next steps