# Replace AWS MediaConvert with Ittybit

Migrate from MediaConvert to Ittybit for simpler S3-based video transcoding

MediaConvert works, but the setup tax is real: IAM roles, job templates, output presets, queue configuration, CloudWatch alarms for failures, S3 event notifications to trigger jobs. Each piece has its own console, its own API, its own failure modes. Ittybit replaces all of that with a single POST. Your files stay in S3 -- Ittybit reads from your bucket, processes the media, and writes the output back.

## What you're replacing

A typical MediaConvert pipeline looks like this:

```
IAM role (MediaConvert execution role)
  -> IAM policy (S3 read/write, CloudWatch logs)
    -> Job template (codec, container, resolution, bitrate)
      -> Output preset (per rendition)
        -> Queue (on-demand or reserved)
          -> S3 event notification -> Lambda -> CreateJob API
            -> CloudWatch alarm (job failure)
              -> SNS topic -> email / Slack
```

That's at least 8 resources to create, configure, and maintain before a single frame gets transcoded.

With Ittybit, the entire pipeline is:

```
S3 bucket -> Ittybit connection (once) -> POST /jobs
```

## Set up your S3 connection

Register your bucket with Ittybit once. This lets Ittybit read inputs from and write outputs to your S3 bucket.

```bash
ittybit connections add s3 \
  --name my-media-bucket \
  --endpoint https://s3.us-east-1.amazonaws.com \
  --region us-east-1 \
  --access-key-id $AWS_ACCESS_KEY_ID \
  --secret-access-key $AWS_SECRET_ACCESS_KEY
```

Note the `connection_id` returned -- you'll pass it in your task options.

## Video transcode

MediaConvert requires a job template with codec settings, container format, resolution, and a queue. Here's the equivalent Ittybit call.

<CodeGroup labels={["TypeScript", "Python", "curl"]}>
```typescript
const task = {
  input: "s3://my-media-bucket/uploads/raw.mov",
  kind: "video",
  options: {
    connection_id: "conn_abc123",
    width: 1920,
    format: "mp4",
    quality: "high",
  },
  output: "s3://my-media-bucket/processed/raw.mp4",
};

const res = await fetch("https://api.ittybit.com/jobs", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ITTYBIT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify(task),
});
const data = await res.json();

````

```python

task = {
    "input": "s3://my-media-bucket/uploads/raw.mov",
    "kind": "video",
    "options": {
        "connection_id": "conn_abc123",
        "width": 1920,
        "format": "mp4",
        "quality": "high",
    },
    "output": "s3://my-media-bucket/processed/raw.mp4",
}

res = requests.post(
    "https://api.ittybit.com/jobs",
    headers={"Authorization": f"Bearer {api_key}"},
    json=task,
)
data = res.json()
````

```bash
TASK='{
  "input": "s3://my-media-bucket/uploads/raw.mov",
  "kind": "video",
  "options": {
    "connection_id": "conn_abc123",
    "width": 1920,
    "format": "mp4",
    "quality": "high"
  },
  "output": "s3://my-media-bucket/processed/raw.mp4"
}'

curl -X POST https://api.ittybit.com/jobs \
  -H "Authorization: Bearer $ITTYBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d "$TASK"
```

</CodeGroup>

No job template. No output preset. No queue. The `output` field writes the result straight back to S3.

## Audio extraction

MediaConvert audio-only jobs need a separate job template with an audio-only output group. With Ittybit, change `kind` to `audio`.

<CodeGroup labels={["TypeScript", "Python", "curl"]}>
```typescript
const task = {
  input: "s3://my-media-bucket/uploads/interview.mov",
  kind: "audio",
  options: {
    connection_id: "conn_abc123",
    format: "mp3",
    quality: "medium",
  },
  output: "s3://my-media-bucket/audio/interview.mp3",
};

const res = await fetch("https://api.ittybit.com/jobs", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ITTYBIT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify(task),
});
const data = await res.json();

````

```python

task = {
    "input": "s3://my-media-bucket/uploads/interview.mov",
    "kind": "audio",
    "options": {
        "connection_id": "conn_abc123",
        "format": "mp3",
        "quality": "medium",
    },
    "output": "s3://my-media-bucket/audio/interview.mp3",
}

res = requests.post(
    "https://api.ittybit.com/jobs",
    headers={"Authorization": f"Bearer {api_key}"},
    json=task,
)
data = res.json()
````

```bash
TASK='{
  "input": "s3://my-media-bucket/uploads/interview.mov",
  "kind": "audio",
  "options": {
    "connection_id": "conn_abc123",
    "format": "mp3",
    "quality": "medium"
  },
  "output": "s3://my-media-bucket/audio/interview.mp3"
}'

curl -X POST https://api.ittybit.com/jobs \
  -H "Authorization: Bearer $ITTYBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d "$TASK"
```

</CodeGroup>

## Adaptive bitrate (HLS)

MediaConvert's ABR workflows require an Apple HLS or DASH output group, multiple output presets (one per rendition), and careful bitrate ladder configuration. Ittybit handles the rendition ladder automatically.

<CodeGroup labels={["TypeScript", "Python", "curl"]}>
```typescript
const task = {
  input: "s3://my-media-bucket/uploads/keynote.mov",
  kind: "adaptive_video",
  options: {
    connection_id: "conn_abc123",
    format: "hls",
  },
  output: "s3://my-media-bucket/streaming/keynote/",
};

const res = await fetch("https://api.ittybit.com/jobs", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ITTYBIT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify(task),
});
const data = await res.json();

````

```python

task = {
    "input": "s3://my-media-bucket/uploads/keynote.mov",
    "kind": "adaptive_video",
    "options": {
        "connection_id": "conn_abc123",
        "format": "hls",
    },
    "output": "s3://my-media-bucket/streaming/keynote/",
}

res = requests.post(
    "https://api.ittybit.com/jobs",
    headers={"Authorization": f"Bearer {api_key}"},
    json=task,
)
data = res.json()
````

```bash
TASK='{
  "input": "s3://my-media-bucket/uploads/keynote.mov",
  "kind": "adaptive_video",
  "options": {
    "connection_id": "conn_abc123",
    "format": "hls"
  },
  "output": "s3://my-media-bucket/streaming/keynote/"
}'

curl -X POST https://api.ittybit.com/jobs \
  -H "Authorization: Bearer $ITTYBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d "$TASK"
```

</CodeGroup>

The manifest and segments land in your S3 prefix. Point your player at the `.m3u8` and it handles quality switching automatically.

## Replacing the Lambda trigger

If you have a Lambda that calls `CreateJob` on MediaConvert when files hit S3, swap the MediaConvert call for an Ittybit call. The Lambda, EventBridge rule, and S3 notification stay the same -- only the job creation changes.

Before (MediaConvert):

```typescript

const mc = new MediaConvertClient({ endpoint: MEDIACONVERT_ENDPOINT });

await mc.send(
  new CreateJobCommand({
    Role: MEDIACONVERT_ROLE_ARN,
    Settings: {
      Inputs: [{ FileInput: `s3://${bucket}/${key}` }],
      OutputGroups: [
        {
          OutputGroupSettings: {
            Type: 'FILE_GROUP_SETTINGS',
            FileGroupSettings: { Destination: `s3://${bucket}/processed/` },
          },
          Outputs: [
            {
              ContainerSettings: { Container: 'MP4' },
              VideoDescription: {
                Width: 1920,
                CodecSettings: {
                  Codec: 'H_264',
                  H264Settings: { RateControlMode: 'QVBR', QualityTuningLevel: 'SINGLE_PASS_HQ' },
                },
              },
              AudioDescriptions: [
                {
                  CodecSettings: {
                    Codec: 'AAC',
                    AacSettings: { Bitrate: 128000, SampleRate: 48000 },
                  },
                },
              ],
            },
          ],
        },
      ],
    },
  }),
);
```

After (Ittybit):

```typescript
await fetch('https://api.ittybit.com/jobs', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${ITTYBIT_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    input: `s3://${bucket}/${key}`,
    kind: 'video',
    options: {
      connection_id: CONNECTION_ID,
      width: 1920,
      format: 'mp4',
      quality: 'high',
    },
    output: `s3://${bucket}/processed/${key.replace(/\.[^.]+$/, '.mp4')}`,
  }),
});
```

You can also remove the MediaConvert execution IAM role, the CloudWatch alarm, and the SNS topic. Ittybit handles retries internally and can notify you via webhooks on completion or failure.

## What you can delete

Once you've confirmed the Ittybit pipeline is working:

| AWS Resource                  | Why it existed                     |
| ----------------------------- | ---------------------------------- |
| MediaConvert job template(s)  | Codec/resolution/container config  |
| MediaConvert output preset(s) | Per-rendition settings             |
| MediaConvert queue            | Job scheduling                     |
| IAM role + policy             | MediaConvert execution permissions |
| CloudWatch alarm              | Job failure detection              |
| SNS topic + subscription      | Failure notifications              |

Keep your S3 bucket, EventBridge rule, and Lambda -- those still work, just with a simpler job creation call.

## See also

- [Process files from S3](/guides/process-files-from-s3) -- setting up S3 connections
- [Write output to S3](/guides/write-output-to-s3) -- writing processed files back to your bucket
- [Create HLS streams](/guides/create-hls-streams) -- adaptive bitrate streaming
- [AWS event-driven media processing](/guides/aws-event-driven-media-processing) -- full EventBridge + Lambda + webhook pipeline