Lesson 4 of 10

Convert Between Formats

Convert between MP4, WebM, and MOV with outputFormat, plus the codec gotcha that makes WebM jobs fail if you skip it.

Follow along with a live API key. 100 free minutes, no credit card.

Get free API key

One field changes the container

Converting a video from one format to another is a matter of setting outputFormat on the job body. FFmpeg Micro supports the common video containers — mp4, webm, and mov — and swaps the container for you. Most of the time that's all you touch, but one of these formats has a catch that fails the job if you ignore it. We'll cover all three and call out the gotcha.

This lesson builds on Lesson 3. You already have the createTranscode, waitForJob, and getDownloadUrl helpers, so here we'll just call createTranscode(...) with different bodies and let those helpers do the rest.

MP4 and MOV: the defaults just work

MP4 and MOV containers happily hold the default codecs (h264 video, aac audio), so you don't need to specify any codecs. Set outputFormat, optionally tune the preset, and submit.

// convert.mjs
// createTranscode, waitForJob, and getDownloadUrl come from Lesson 3.

async function convertToMp4(url) {
  return createTranscode({
    inputs: [{ url }],
    outputFormat: 'mp4',
    preset: { quality: 'medium', resolution: '720p' },
  })
}

async function convertToMov(url) {
  return createTranscode({
    inputs: [{ url }],
    outputFormat: 'mov',
    preset: { quality: 'medium' },
  })
}

WebM: you must pick the codecs

WebM is the exception. A WebM container cannot hold h264/aac, so if you set outputFormat: 'webm' and leave the default preset in place, the job fails during processing. You have to tell the API to use WebM-compatible codecs: VP9 for video and Opus for audio. You do that with the options array.

async function convertToWebM(url) {
  return createTranscode({
    inputs: [{ url }],
    outputFormat: 'webm',
    options: [
      { option: '-c:v', argument: 'libvpx-vp9' },
      { option: '-c:a', argument: 'libopus' },
    ],
  })
}

The argument values are the codec library names, not the informal codec names. Use libvpx-vp9 for VP9 video (or libvpx for VP8) and libopus for Opus audio (or libvorbis for Vorbis). Writing vp9 or opus as bare strings will not work. We cover the options array in depth in Lesson 7 — here you only need these two codec options to make WebM valid.

Putting it together

Each helper returns the job, so you drive them through the same lifecycle from Lesson 3: submit, wait, then fetch the download URL.

const source = 'https://example.com/clip.mp4'

const job = await convertToWebM(source)
await waitForJob(job.id)
const downloadUrl = await getDownloadUrl(job.id)
console.log('Converted file:', downloadUrl)

What could go wrong: Setting outputFormat: 'webm' without the VP9/Opus options lets the job submit, then fails it during processing with the FFmpeg error Only VP8 or VP9 or AV1 video and Vorbis or Opus audio and WebVTT subtitles are supported for WebM. MP4 and MOV don't hit this because the default h264/aac codecs are valid in those containers — only WebM needs the explicit codec options.

Key takeaways

  • outputFormat switches the output container: mp4, webm, or mov.
  • MP4 and MOV convert with the defaults — the h264/aac codecs are valid in those containers.
  • WebM requires -c:v libvpx-vp9 and -c:a libopus in the options array, or the job fails during processing.
  • Codec argument values must be the allowed library names (e.g. libvpx-vp9, libopus), not bare names like vp9 or opus.

Next up: strip the audio out of a video to produce an MP3 or WAV for podcast and transcription workflows.

Build this into your app. No FFmpeg install

One call kicks off a job. No local FFmpeg, no servers, no worker queue to run.

const res = await fetch('https://api.ffmpeg-micro.com/v1/transcodes', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.FFMPEG_MICRO_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    inputs: [{ url: 'gs://your-bucket/input.mp4' }],
    outputFormat: 'mp4',
    preset: { quality: 'medium', resolution: '1080p' },
  }),
})
const job = await res.json() // { id, status: 'pending' }