> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/BintzGavin/helios/llms.txt
> Use this file to discover all available pages before exploring further.

# Render orchestrator

> Distributed rendering engine for parallel video generation

The `RenderOrchestrator` class enables distributed rendering by splitting a composition into multiple chunks that can be rendered in parallel. This dramatically reduces rendering time for long videos on multi-core systems.

## How it works

1. **Plan**: Divides the composition into frame ranges based on concurrency
2. **Render**: Executes chunks in parallel using worker renderers
3. **Concatenate**: Stitches chunk videos into a single file
4. **Mix**: Applies final audio mixing and encoding

## Static methods

### plan

```typescript theme={null}
static plan(
  compositionUrl: string,
  outputPath: string,
  options: DistributedRenderOptions
): RenderPlan
```

Creates a render plan without executing it. Useful for previewing the distribution strategy.

<ParamField path="compositionUrl" type="string" required>
  URL of the HTML composition to render.
</ParamField>

<ParamField path="outputPath" type="string" required>
  Final output file path for the rendered video.
</ParamField>

<ParamField path="options" type="DistributedRenderOptions" required>
  Configuration object extending RendererOptions with distributed rendering settings.
</ParamField>

#### Returns

A `RenderPlan` object containing:

<ParamField path="totalFrames" type="number">
  Total number of frames in the composition.
</ParamField>

<ParamField path="chunks" type="RenderChunk[]">
  Array of chunk specifications, each containing:

  * `id`: Chunk identifier
  * `startFrame`: First frame in this chunk
  * `frameCount`: Number of frames to render
  * `outputFile`: Temporary file path for chunk output
  * `options`: RendererOptions for this specific chunk
</ParamField>

<ParamField path="concatManifest" type="string[]">
  List of chunk file paths to concatenate.
</ParamField>

<ParamField path="concatOutputFile" type="string">
  Temporary file path for the concatenated video (before final audio mix).
</ParamField>

<ParamField path="finalOutputFile" type="string">
  Final output path (same as the outputPath parameter).
</ParamField>

<ParamField path="mixOptions" type="RendererOptions">
  Options for the final audio mixing pass.
</ParamField>

<ParamField path="cleanupFiles" type="string[]">
  List of temporary files to delete after successful rendering.
</ParamField>

#### Example

```typescript theme={null}
import { RenderOrchestrator } from '@helios/renderer';

const plan = RenderOrchestrator.plan(
  'file:///composition.html',
  './output.mp4',
  {
    width: 1920,
    height: 1080,
    fps: 30,
    durationInSeconds: 60,
    concurrency: 4
  }
);

console.log(`Rendering ${plan.totalFrames} frames across ${plan.chunks.length} chunks`);
plan.chunks.forEach(chunk => {
  console.log(`Chunk ${chunk.id}: frames ${chunk.startFrame}-${chunk.startFrame + chunk.frameCount}`);
});
```

### render

```typescript theme={null}
static async render(
  compositionUrl: string,
  outputPath: string,
  options: DistributedRenderOptions,
  jobOptions?: RenderJobOptions
): Promise<void>
```

Executes a distributed render job.

<ParamField path="compositionUrl" type="string" required>
  URL of the HTML composition to render.
</ParamField>

<ParamField path="outputPath" type="string" required>
  Final output file path for the rendered video.
</ParamField>

<ParamField path="options" type="DistributedRenderOptions" required>
  Configuration object extending RendererOptions with distributed rendering settings.
</ParamField>

<ParamField path="jobOptions" type="RenderJobOptions">
  Optional job configuration including progress callbacks and abort signals.
</ParamField>

#### Example

```typescript theme={null}
await RenderOrchestrator.render(
  'file:///composition.html',
  './output.mp4',
  {
    width: 1920,
    height: 1080,
    fps: 30,
    durationInSeconds: 120,
    concurrency: 8,
    audioFilePath: './music.mp3'
  },
  {
    onProgress: (progress) => {
      console.log(`Overall progress: ${(progress * 100).toFixed(1)}%`);
    }
  }
);
```

## Distributed render options

```typescript theme={null}
interface DistributedRenderOptions extends RendererOptions {
  concurrency?: number;
  executor?: RenderExecutor;
}
```

<ParamField path="concurrency" type="number">
  Number of parallel render workers. Defaults to `os.cpus().length - 1`.
</ParamField>

<ParamField path="executor" type="RenderExecutor">
  Custom executor for running render chunks. Defaults to `LocalExecutor` which runs chunks as parallel processes on the local machine. Implement custom executors for cloud-based or distributed execution.
</ParamField>

## Concurrency behavior

### Automatic concurrency

```typescript theme={null}
// Uses CPU count - 1 workers
await RenderOrchestrator.render(
  'file:///composition.html',
  './output.mp4',
  {
    width: 1920,
    height: 1080,
    fps: 30,
    durationInSeconds: 60
    // concurrency not specified
  }
);
```

### Single worker fallback

When `concurrency: 1`, the orchestrator skips chunking and uses a standard `Renderer` directly:

```typescript theme={null}
await RenderOrchestrator.render(
  'file:///composition.html',
  './output.mp4',
  {
    width: 1920,
    height: 1080,
    fps: 30,
    durationInSeconds: 60,
    concurrency: 1 // No parallelization
  }
);
```

### Custom concurrency

```typescript theme={null}
await RenderOrchestrator.render(
  'file:///composition.html',
  './output.mp4',
  {
    width: 1920,
    height: 1080,
    fps: 30,
    durationInSeconds: 60,
    concurrency: 16 // Use 16 workers
  }
);
```

## Audio handling in distributed mode

The orchestrator uses a multi-stage audio pipeline:

### Chunk rendering

1. Each chunk renders with **PCM audio** (`pcm_s16le` codec)
2. Audio tracks are **not** mixed during chunk rendering
3. Chunks output to `.mov` container format to preserve PCM audio

### Concatenation

1. Chunk videos are concatenated without re-encoding
2. PCM audio streams are preserved in the concatenated file

### Final mix

1. Concatenated video is processed with final audio options
2. Audio tracks from `audioFilePath` and `audioTracks` are mixed
3. Output is encoded with specified `audioCodec` (default: `aac`)
4. Final video uses the container format from `outputPath` extension

#### Example with audio

```typescript theme={null}
await RenderOrchestrator.render(
  'file:///composition.html',
  './output.mp4',
  {
    width: 1920,
    height: 1080,
    fps: 30,
    durationInSeconds: 60,
    concurrency: 4,
    audioTracks: [
      {
        path: './music.mp3',
        volume: 0.7,
        fadeInDuration: 2,
        fadeOutDuration: 3
      },
      {
        path: './sfx.wav',
        offset: 10,
        volume: 1.0
      }
    ],
    audioCodec: 'aac',
    audioBitrate: '192k'
  }
);
```

## Progress tracking

The orchestrator aggregates progress from all workers:

```typescript theme={null}
const progressBar = new Map<number, number>();

await RenderOrchestrator.render(
  'file:///composition.html',
  './output.mp4',
  {
    width: 1920,
    height: 1080,
    fps: 30,
    durationInSeconds: 60,
    concurrency: 4
  },
  {
    onProgress: (globalProgress) => {
      // globalProgress is weighted by chunk size
      console.log(`Overall: ${(globalProgress * 100).toFixed(1)}%`);
    }
  }
);
```

Each worker's progress is weighted by its frame count, ensuring accurate overall progress reporting.

## Error handling and cancellation

### Abort signal

```typescript theme={null}
const controller = new AbortController();

const renderPromise = RenderOrchestrator.render(
  'file:///composition.html',
  './output.mp4',
  {
    width: 1920,
    height: 1080,
    fps: 30,
    durationInSeconds: 60,
    concurrency: 4
  },
  {
    signal: controller.signal
  }
);

// Cancel after 10 seconds
setTimeout(() => controller.abort(), 10000);

try {
  await renderPromise;
} catch (error) {
  if (error.message === 'Aborted') {
    console.log('Render was cancelled');
  }
}
```

### Worker failure propagation

If any worker fails, all other workers are immediately aborted:

```typescript theme={null}
try {
  await RenderOrchestrator.render(
    'file:///composition.html',
    './output.mp4',
    {
      width: 1920,
      height: 1080,
      fps: 30,
      durationInSeconds: 60,
      concurrency: 4
    }
  );
} catch (error) {
  // If worker 2 fails, workers 0, 1, and 3 are aborted
  console.error('Distributed render failed:', error);
}
```

## Temporary file cleanup

The orchestrator automatically cleans up temporary files:

* Chunk video files (e.g., `temp_123_part_0.mov`)
* Concatenated intermediate file (e.g., `temp_concat_123.mov`)

Cleanup occurs in the `finally` block, ensuring files are removed even if rendering fails.

## Custom executors

Implement the `RenderExecutor` interface to run chunks on cloud infrastructure:

```typescript theme={null}
import { RenderExecutor } from '@helios/renderer';

class CloudExecutor implements RenderExecutor {
  async render(
    compositionUrl: string,
    outputPath: string,
    options: RendererOptions,
    jobOptions?: RenderJobOptions
  ): Promise<void> {
    // Upload composition to cloud storage
    // Trigger cloud render job
    // Download rendered chunk
  }
}

await RenderOrchestrator.render(
  'file:///composition.html',
  './output.mp4',
  {
    width: 1920,
    height: 1080,
    fps: 30,
    durationInSeconds: 60,
    concurrency: 100, // Scale to cloud capacity
    executor: new CloudExecutor()
  }
);
```

## Performance considerations

### Optimal concurrency

* **CPU-bound**: Set concurrency to CPU core count minus 1
* **Memory-bound**: Reduce concurrency if renders are memory-intensive
* **Cloud**: Scale concurrency based on available cloud workers

### Chunk size

Chunk size is automatically calculated as `totalFrames / concurrency`. For very short videos, some workers may receive no frames:

```typescript theme={null}
// 90 frames, 4 workers = 22-23 frames per chunk
// 90 frames, 100 workers = 0-1 frames per chunk (inefficient)
```

### When to use distributed rendering

**Use distributed rendering when:**

* Videos are longer than 30 seconds
* You have multiple CPU cores available
* Rendering time is critical

**Use standard Renderer when:**

* Videos are very short (\< 10 seconds)
* Single-core environment
* Simplicity is preferred over speed

## Console output

Distributed rendering produces detailed logs:

```
Starting distributed render with concurrency: 4
[Worker 0] Rendering frames 0 to 450 (450 frames) to /tmp/temp_123_part_0.mov
[Worker 1] Rendering frames 450 to 900 (450 frames) to /tmp/temp_123_part_1.mov
[Worker 2] Rendering frames 900 to 1350 (450 frames) to /tmp/temp_123_part_2.mov
[Worker 3] Rendering frames 1350 to 1800 (450 frames) to /tmp/temp_123_part_3.mov
All chunks rendered. Concatenating...
Mixing audio into concatenated video...
Spawning FFmpeg for audio mixing: /usr/local/bin/ffmpeg -i /tmp/concat.mov ...
Cleaning up temporary files...
```
