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

# Writing files

> Learn how to create media files with multiple tracks, encode video and audio, and write to different outputs

Mediabunny enables you to create media files with fine-grained control. You can add multiple video, audio, and subtitle tracks to a media file and precisely control the timing of media data.

Using output targets, you can decide whether to build the entire file in memory or stream it out in chunks as it's being created, allowing you to create very large files efficiently.

## Creating an output

Media file creation in Mediabunny revolves around the `Output` class. One instance of `Output` represents one media file you want to create.

<Steps>
  <Step title="Import the required classes">
    ```typescript theme={null}
    import { Output, Mp4OutputFormat, BufferTarget } from 'mediabunny';
    ```
  </Step>

  <Step title="Create a new output">
    ```typescript theme={null}
    const output = new Output({
      format: new Mp4OutputFormat(),
      target: new BufferTarget(),
    });
    ```

    The `format` determines the container format of the output file (MP4, WebM, etc.).

    The `target` determines where the data will be written (memory, disk, stream, etc.). See the [Streaming guide](/guides/streaming) for available targets.
  </Step>
</Steps>

## Adding tracks

Before starting an output, you need to add tracks to it. Each track requires a **media source** that provides the media data.

### Adding a video track

```typescript theme={null}
import { CanvasSource } from 'mediabunny';

// Create a video source from a canvas element
const videoSource = new CanvasSource(canvasElement, {
  codec: 'avc',
  bitrate: 1e6, // 1 Mbps
});

output.addVideoTrack(videoSource, {
  frameRate: 30,
  rotation: 0,
  language: 'eng',
  name: 'Main video',
});
```

### Adding an audio track

```typescript theme={null}
import { MediaStreamAudioTrackSource } from 'mediabunny';

// Create an audio source from microphone
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
const audioStreamTrack = stream.getAudioTracks()[0];
const audioSource = new MediaStreamAudioTrackSource(audioStreamTrack, {
  codec: 'aac',
  bitrate: 128e3, // 128 kbps
});

output.addAudioTrack(audioSource, {
  language: 'eng',
  name: 'Microphone',
});
```

### Track metadata options

<CodeGroup>
  ```typescript Video metadata theme={null}
  output.addVideoTrack(videoSource, {
    // Clockwise rotation in degrees
    rotation: 90,
    // Expected frame rate in hertz
    frameRate: 30,
    // ISO 639-2/T language code
    language: 'eng',
    // User-defined track name
    name: 'Main camera',
    // Track disposition flags
    disposition: { default: true },
  });
  ```

  ```typescript Audio metadata theme={null}
  output.addAudioTrack(audioSource, {
    language: 'eng',
    name: 'Developer Commentary',
    disposition: { commentary: true },
  });
  ```

  ```typescript Subtitle metadata theme={null}
  output.addSubtitleTrack(subtitleSource, {
    language: 'spa',
    disposition: { forced: true },
  });
  ```
</CodeGroup>

<Info>
  The `frameRate` option snaps all timestamps and durations to the specified frame rate. To achieve fractional frame rates precisely, use their exact fractional forms:

  * 23.976 → `24000/1001`
  * 29.97 → `30000/1001`
  * 59.94 → `60000/1001`
</Info>

## Setting metadata tags

You can write descriptive metadata tags to the output file:

```typescript theme={null}
output.setMetadataTags({
  title: 'Big Buck Bunny',
  artist: 'Blender Foundation',
  date: new Date('2008-05-20'),
  images: [{
    data: coverImageBytes,
    mimeType: 'image/jpeg',
    kind: 'coverFront',
  }],
});
```

<Warning>
  Metadata tags must be set before calling `output.start()`.
</Warning>

See the [Metadata guide](/guides/metadata) for all available metadata fields.

## Starting an output

After adding all tracks, you need to start the output:

```typescript theme={null}
await output.start();
```

This spins up the writing process and prevents adding new tracks. After this, you can start sending media data to the output file.

## Adding media data

After starting an output, use the media sources to pipe data to the output file:

```typescript theme={null}
// For a CanvasSource, capture frames at regular intervals
let framesAdded = 0;
const intervalId = setInterval(() => {
  const timestamp = framesAdded / 30;
  const duration = 1 / 30;
  
  // Captures the canvas state at the time of calling add
  videoSource.add(timestamp, duration);
  framesAdded++;
}, 1000 / 30);

// Audio from MediaStreamAudioTrackSource is automatically piped
// after calling start()
```

The API differs for each media source type - check the media sources documentation for details.

## Finalizing an output

Once all media data has been added, finalize the output:

```typescript theme={null}
clearInterval(intervalId);  // Stop capturing
audioStreamTrack.stop();    // Stop microphone

await output.finalize();

const file = output.target.buffer; // => ArrayBuffer
```

<Warning>
  After calling `finalize()`, adding more media data will result in an error.
</Warning>

## Output state

You can check the current state of an output:

```typescript theme={null}
output.state; // => 'pending' | 'started' | 'canceled' | 'finalizing' | 'finalized'
```

* `'pending'` - Not started yet; tracks can be added
* `'started'` - Ready to receive media data; no more tracks can be added
* `'finalizing'` - `finalize()` has been called but hasn't completed
* `'finalized'` - Output is complete
* `'canceled'` - Output was canceled

## Canceling an output

To cancel an ongoing output:

```typescript theme={null}
await output.cancel();
```

This frees up resources like encoders and prevents adding more data.

## Example: Record canvas and microphone

<CodeGroup>
  ```typescript Basic recording theme={null}
  import {
    Output,
    Mp4OutputFormat,
    BufferTarget,
    CanvasSource,
    MediaStreamAudioTrackSource,
  } from 'mediabunny';

  const output = new Output({
    format: new Mp4OutputFormat(),
    target: new BufferTarget(),
  });

  // Set up video from canvas
  const videoSource = new CanvasSource(canvas, {
    codec: 'avc',
    bitrate: 2e6,
  });
  output.addVideoTrack(videoSource, { frameRate: 30 });

  // Set up audio from microphone
  const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
  const audioTrack = stream.getAudioTracks()[0];
  const audioSource = new MediaStreamAudioTrackSource(audioTrack, {
    codec: 'aac',
    bitrate: 128e3,
  });
  output.addAudioTrack(audioSource);

  await output.start();

  // Capture video frames
  const intervalId = setInterval(() => {
    videoSource.add(framesAdded / 30, 1 / 30);
    framesAdded++;
  }, 1000 / 30);

  // Stop after 10 seconds
  setTimeout(async () => {
    clearInterval(intervalId);
    audioTrack.stop();
    
    await output.finalize();
    
    // Download the file
    const blob = new Blob([output.target.buffer], { 
      type: output.format.mimeType 
    });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = 'recording.mp4';
    a.click();
  }, 10000);
  ```

  ```typescript Write to disk (Node.js) theme={null}
  import {
    Output,
    Mp4OutputFormat,
    FilePathTarget,
    VideoSampleSource,
  } from 'mediabunny';

  const output = new Output({
    format: new Mp4OutputFormat(),
    target: new FilePathTarget('/path/to/output.mp4'),
  });

  const videoSource = new VideoSampleSource({
    codec: 'avc',
    bitrate: 5e6,
  });
  output.addVideoTrack(videoSource, { frameRate: 24 });

  await output.start();

  // Add video frames...
  for (let i = 0; i < 240; i++) {
    const frame = generateFrame(i); // Your frame generation logic
    await videoSource.add(frame);
  }

  await output.finalize();
  // File is written to /path/to/output.mp4
  ```
</CodeGroup>

## Getting the MIME type

To retrieve the full MIME type of the output file (including codec strings):

```typescript theme={null}
const mimeType = await output.getMimeType();
// => 'video/mp4; codecs="avc1.42c032, mp4a.40.2"'
```

<Warning>
  This promise only resolves once codec strings for all tracks are known, which requires encoders to be initialized. Don't await this before adding media data or you'll create a deadlock.
</Warning>

## Packet buffering

Some output formats require **packet buffering** for multi-track outputs. The output must wait for data from all tracks for a given timestamp before writing.

<Tip>
  To minimize memory usage, add media data in an interleaved way. For example, add 10 seconds of video, then 10 seconds of audio, then repeat - instead of adding all video first, then all audio.
</Tip>
