Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ This directory contains different examples of how to use the Workflow DevKit.
- [Birthday Card Generator](https://github.com/vercel/workflow-examples/tree/main/birthday-card-generator) - Example of how to use the Workflow DevKit to build a birthday card generator.
- [Custom Adapter](https://github.com/vercel/workflow-examples/tree/main/custom-adapter) - Example of building a custom adapter to run workflows on other frameworks and runtimes, like Bun.
- [Flight Booking App](https://github.com/vercel/workflow-examples/tree/main/flight-booking-app) - Example of how to use the Workflow DevKit to build a flight booking app.

- [FFmpeg Processing](https://github.com/vercel/workflow-examples/tree/main/ffmpeg-processing) - Express + Workflow DevKit example for building FFmpeg-based media processing workflows.
- [Kitchen Sink](https://github.com/vercel/workflow-examples/tree/main/kitchen-sink) - Comprehensive reference implementation of all core workflow patterns including steps, control flow, streaming, AI integration, hooks, and batching.
- [RAG Agent Example](https://github.com/vercel/workflow-examples/tree/main/rag-agent) - Example of how to use the AI SDK with the Workflow DevKit.

Expand Down
52 changes: 52 additions & 0 deletions ffmpeg-processing/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# local env files
.env*.local
.env

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts

# workflow
.well-known
.swc
dist
.workflow-data

# nitro
.output
.nitro
112 changes: 112 additions & 0 deletions ffmpeg-processing/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# FFmpeg Processing Example

[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fvercel%2Fworkflow-examples%2Ftree%2Fmain%2Fffmpeg-processing)

This example demonstrates how to use [Workflow DevKit](https://useworkflow.dev) with Express and Vercel Sandbox to build an **audio compression service**. Audio files are uploaded via multipart/form-data, processed by FFmpeg within a durable workflow running in an isolated sandbox, and streamed directly back in the HTTP response.

## Features

- **Sandbox isolation**: FFmpeg runs in a Vercel Sandbox for secure, isolated processing
- **Workflow orchestration**: Audio compression is broken into multiple steps (`createSandbox` -> `setupFfmpeg` -> `transcode` -> `streamOutput` -> `stopSandbox`)
- **Streaming support**: Results are streamed back to the client as they're produced
- **FFmpeg compression**: Converts audio to AAC codec in M4A container at 128kbps

## Getting Started

### Prerequisites

- **`VERCEL_OIDC_TOKEN`**: The workflow runtime expects the `VERCEL_OIDC_TOKEN` environment variable to be present for `@vercel/sandbox`. When running inside a Vercel Sandbox this is injected automatically; if you run the server outside of Vercel, you must provide a valid OIDC token yourself.

### Local Development

1. Clone this example and install dependencies:

```bash
git clone https://github.com/vercel/workflow-examples
cd workflow-examples/ffmpeg-processing
pnpm install
```

2. Link your Vercel project:

```bash
npx vercel link
```
3. Fetch the `VERCEL_OIDC_TOKEN`:

```bash
npx vercel env pull
```

4. Start the development server:

```bash
pnpm dev
```

5. **Test the audio compression workflow**:

```bash
# Convert a WAV file to compressed M4A
curl -X POST -F "file=@input.wav;type=audio/wav" -H "Expect:" http://localhost:3000/convert --output output.m4a
```

The endpoint accepts any audio file and returns a compressed M4A file. Be sure to update the audio file extension if not a .wav file.

## API Endpoints

### `POST /convert`

Compresses an uploaded audio file using FFmpeg within a workflow.

**Request:**
- Content-Type: `multipart/form-data`
- Field name: `file`
- Accepted formats: WAV, MP3, OGG, FLAC, AAC, M4A

**Response:**
- Content-Type: `audio/mp4`
- Body: Compressed audio file (M4A/AAC at 128kbps)

**Example:**
```bash
curl -X POST -F "file=@podcast.wav;type=audio/wav" -H "Expect:" http://localhost:3000/convert --output podcast.m4a
```

## How It Works

1. **Express route** receives the upload via `multer.memoryStorage()`
2. **Workflow orchestrates** five steps:
- `createSandbox`: Provisions a Vercel Sandbox instance
- `setupFfmpeg`: Downloads and installs FFmpeg in the sandbox
- `transcode`: Writes input to sandbox, runs FFmpeg to compress audio
- `streamOutput`: Streams the compressed file back to the workflow
- `stopSandbox`: Cleans up the sandbox instance
3. **Response** streams the compressed bytes directly to the client

## Project Structure

```
ffmpeg-processing/
├── src/
│ ├── index.ts # Express app with /convert route
│ └── workflows/
│ └── audio-convert/
│ ├── index.ts # Main workflow orchestration
│ └── steps/ # Individual workflow steps
│ ├── create-sandbox.ts
│ ├── setup-ffmpeg.ts
│ ├── transcode.ts
│ ├── stream-output.ts
│ └── stop-sandbox.ts
├── nitro.config.ts # Nitro configuration with workflow module
├── package.json
├── tsconfig.json
└── README.md
```

## Learn More

- [Workflow DevKit Documentation](https://useworkflow.dev)
- [Express Getting Started Guide](https://useworkflow.dev/docs/getting-started/express)
- [FFmpeg Documentation](https://ffmpeg.org/documentation.html)
8 changes: 8 additions & 0 deletions ffmpeg-processing/nitro.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { defineNitroConfig } from "nitro/config";

export default defineNitroConfig({
modules: ["workflow/nitro"],
routes: {
"/**": { handler: "./src/index.ts", format: "node" },
},
});
27 changes: 27 additions & 0 deletions ffmpeg-processing/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "ffmpeg-processing",
"type": "module",
"private": true,
"scripts": {
"dev": "nitro dev",
"build": "nitro build"
},
"dependencies": {
"@vercel/sandbox": "^1.0.4",
"cors": "^2.8.5",
"express": "^5.2.1",
"ms": "^2.1.3",
"multer": "^2.0.2",
"nitro": "3.0.1-alpha.1",
"rollup": "^4.53.3",
"workflow": "4.0.1-beta.28"
},
"devDependencies": {
"@types/cors": "^2.8.19",
"@types/express": "^5.0.6",
"@types/ms": "^2.1.0",
"@types/multer": "^2.0.0",
"@types/node": "^20.19.25",
"typescript": "^5.9.3"
}
}
Loading