From FFmpeg Shell Scripts to TypeScript: A Migration Guide
September 19, 2026 · By VideoFlowTired of string-concatenated FFmpeg commands? Learn how to migrate your video pipeline to TypeScript for a typed, cinematic, and maintainable workflow.
From FFmpeg Shell Scripts to TypeScript: A Migration Guide
If you have ever built a video automation pipeline, you know the "FFmpeg String Fatigue." It starts with a simple concatenation of two clips and ends as a 400-line shell script of complex filtergraphs, escaped semicolons, and fragile mapping flags. One missing comma and the whole render fails with an opaque error.
There is a better way. By moving from string-based shell commands to a typed, programmatic approach, you can treat video like code—complete with IDE autocompletion, unit tests, and maintainable logic. In this guide, we will walk through how to migrate your legacy FFmpeg scripts to VideoFlow, the open-source TypeScript toolkit for video composition.
The Problem with "Stringly-Typed" Video
FFmpeg is an incredible piece of engineering, but it was never designed to be a high-level composition engine for developers. When you use it for automated video production, you're essentially building a complex UI using only string concatenation.
Consider a simple task: placing a text overlay on a video with a fade-in. In FFmpeg, this requires calculating frame offsets manually and constructing a drawtext filter string. In VideoFlow, it's a single method call on the builder API.

By switching to a programmatic video workflow, you gain:
- Type Safety: Catch errors in your property names or preset values before you even hit render.
- Readability: Your video timeline is expressed as a clear, sequential flow of actions.
- Portability: VideoFlow compiles to a VideoJSON document that renders identically in the browser, on a server, or in a live preview.
Step 1: Mapping FFmpeg Filters to VideoFlow Layers
In FFmpeg, everything is a stream processed by filters. In VideoFlow, everything is a Layer. This mental shift is the key to a successful migration.
Instead of chaining -vf filters, you add layers to a VideoFlow instance. Here is how common FFmpeg operations map to VideoFlow:
| FFmpeg Filter | VideoFlow Equivalent |
|---|---|
drawtext | $.addText() |
overlay (image) | $.addImage() |
overlay (video) | $.addVideo() |
amix | $.addAudio() |
fade / xfade | .fadeIn() / transitionIn |
boxblur / gblur | gaussianBlur effect |
Step 2: Translating the Code
Let's look at a typical FFmpeg command that adds a background image, a centered title that fades in, and a background music track:
ffmpeg -i bg.jpg -i music.mp3 -filter_complex \
"[0:v]scale=1920:1080,format=yuv420p[bg]; \
[bg][1:a]drawtext=text='Hello World':fontcolor=white:fontsize=100:x=(w-text_w)/2:y=(h-text_h)/2:alpha='if(lt(t,1),t,1)'[v]" \
-map "[v]" -map 1:a -t 5 out.mp4
Now, let's see the same logic using the @videoflow/core builder. Notice how we use normalized coordinates ([0.5, 0.5]) and em-based sizing to ensure the design is resolution-agnostic.
import VideoFlow from '@videoflow/core';
const $ = new VideoFlow({ width: 1920, height: 1080, fps: 30 });
// 1. Add background image (Settings in 2nd arg)
$.addImage({ fit: 'cover' }, { source: 'https://example.com/bg.jpg' });
// 2. Add title with a built-in transition
const title = $.addText(
{
text: 'Hello World',
fontSize: 8,
color: '#fff',
position: [0.5, 0.5]
},
{
transitionIn: { transition: 'fade', duration: '1s' }
}
);
// 3. Add background audio
$.addAudio({ volume: 0.5 }, { source: 'https://example.com/music.mp3' });
// 4. Set duration and compile
$.wait('5s');
const videoJson = await $.compile();
Step 3: Leveraging Cinematic Primitives
One of the biggest pain points in FFmpeg migration is recreating complex visual effects. FFmpeg requires manual GLSL filter implementation or complex math. VideoFlow ships with 27 transition presets and 42 GLSL effects out of the box.
If you want to add a high-end "bloom" effect or a "vhsDistortion" to a layer, you don't need to write a single line of shader code. You just add it to the effects array in the layer's properties.
$.addVideo(
{
effects: [
{ effect: 'bloom', params: { strength: 0.5 } },
{ effect: 'vignette', params: { strength: 0.3 } }
]
},
{ source: 'https://example.com/clip.mp4' }
);
Step 4: Choosing Your Renderer
In the FFmpeg world, you are tied to the binary installed on your system. With VideoFlow, you have access to three official renderers that all accept the same JSON output:
@videoflow/renderer-server: For Node.js environments. It uses headless Chromium to render, meaning you can often render in Node.js without FFmpeg entirely.@videoflow/renderer-browser: For exporting MP4s directly in the user's browser using WebCodecs.@videoflow/renderer-dom: For a frame-accurate, 60fps live preview inside your web application.

Conclusion: Future-Proof Your Video Stack
Migrating from FFmpeg shell scripts to TypeScript isn't just about changing syntax; it's about adopting a more resilient architecture. By using VideoFlow, you move from a "black box" command-line tool to a transparent, open-source ecosystem that scales with your engineering team.
Ready to stop concatenating strings and start building? Head over to the Playground to try these patterns live in your browser, or dive into our Getting Started guide to install the core toolkit.