DEV Community

Javid Jamae
Javid Jamae

Posted on • Originally published at ffmpeg-micro.com

How to Use FFmpeg with Go (Golang)

Originally published at ffmpeg-micro.com

You need video processing in your Go app. Maybe you're building a media pipeline, transcoding user uploads, or automating thumbnail generation. You search "ffmpeg golang" and find a few options. All of them require FFmpeg installed on the machine running your code.

That works on your laptop. It breaks the moment you deploy to a Docker container without FFmpeg compiled in, a serverless function with filesystem restrictions, or a CI environment that doesn't have the binary. There are three ways to approach this in Go, each with different tradeoffs.

Using os/exec to Call FFmpeg from Go

The most direct approach. Install FFmpeg on your system, then shell out from Go:

package main

import (
    "fmt"
    "os/exec"
)

func main() {
    cmd := exec.Command("ffmpeg",
        "-i", "input.mp4",
        "-c:v", "libx264",
        "-crf", "23",
        "-preset", "medium",
        "-c:a", "aac",
        "-b:a", "128k",
        "output.mp4",
    )
    cmd.Stderr = cmd.Stdout

    output, err := cmd.CombinedOutput()
    if err != nil {
        fmt.Printf("FFmpeg failed: %v\nOutput: %s\n", err, output)
        return
    }
    fmt.Println("Transcoding complete")
}
Enter fullscreen mode Exit fullscreen mode

This is what most Go developers start with. It works, but you own everything: installing FFmpeg on every deployment target, parsing stderr for progress, handling the case where the binary isn't in PATH, and making sure the right codecs are compiled in. On a minimal Docker image like scratch or distroless, there's no FFmpeg at all. You'll need a multi-stage build or a fat base image that adds 80-200MB.

Using the ffmpeg-go Wrapper

The ffmpeg-go package (github.com/u2takey/ffmpeg-go) gives you a fluent API instead of raw command arguments:

package main

import (
    "fmt"
    ffmpeg "github.com/u2takey/ffmpeg-go"
)

func main() {
    err := ffmpeg.Input("input.mp4").
        Output("output.mp4", ffmpeg.KwArgs{
            "c:v":    "libx264",
            "crf":    "23",
            "preset": "medium",
            "c:a":    "aac",
            "b:a":    "128k",
        }).
        OverWriteOutput().
        Run()

    if err != nil {
        fmt.Printf("Error: %v\n", err)
        return
    }
    fmt.Println("Done")
}
Enter fullscreen mode Exit fullscreen mode

The builder pattern is cleaner than string slices. But ffmpeg-go still requires FFmpeg installed on the host. It's a wrapper around os/exec under the hood. You get a nicer API for constructing complex filter graphs, but the same deployment headaches apply. The library also hasn't seen frequent updates, so edge cases may go unpatched.

Processing Video with a Cloud API (No FFmpeg Install)

If you don't want to ship FFmpeg with your binary, you can offload processing to a cloud API. FFmpeg Micro gives you full FFmpeg capabilities through HTTP requests. From Go, it's just net/http:

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
)

type TranscodeRequest struct {
    Inputs       []Input `json:"inputs"`
    OutputFormat string  `json:"outputFormat"`
    Preset       *Preset `json:"preset,omitempty"`
}

type Input struct {
    URL string `json:"url"`
}

type Preset struct {
    Quality    string `json:"quality"`
    Resolution string `json:"resolution"`
}

func main() {
    reqBody := TranscodeRequest{
        Inputs:       []Input{{URL: "https://example.com/input.mp4"}},
        OutputFormat: "mp4",
        Preset:       &Preset{Quality: "high", Resolution: "1080p"},
    }

    body, _ := json.Marshal(reqBody)
    req, _ := http.NewRequest("POST",
        "https://api.ffmpeg-micro.com/v1/transcodes",
        bytes.NewReader(body))
    req.Header.Set("Authorization", "Bearer YOUR_API_KEY")
    req.Header.Set("Content-Type", "application/json")

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        fmt.Printf("Request failed: %v\n", err)
        return
    }
    defer resp.Body.Close()

    respBody, _ := io.ReadAll(resp.Body)
    fmt.Printf("Status: %d\nResponse: %s\n", resp.StatusCode, respBody)
}
Enter fullscreen mode Exit fullscreen mode

No binary dependency. No Docker image bloat. Your Go binary stays small and deploys anywhere. The API handles scaling, codec management, and infrastructure. For advanced use cases, pass raw FFmpeg options instead of presets:

type Option struct {
    Option   string `json:"option"`
    Argument string `json:"argument"`
}

type AdvancedRequest struct {
    Inputs       []Input  `json:"inputs"`
    OutputFormat string   `json:"outputFormat"`
    Options      []Option `json:"options"`
}

reqBody := AdvancedRequest{
    Inputs:       []Input{{URL: "https://example.com/input.mp4"}},
    OutputFormat: "webm",
    Options: []Option{
        {Option: "-c:v", Argument: "libvpx-vp9"},
        {Option: "-crf", Argument: "30"},
        {Option: "-b:v", Argument: "0"},
    },
}
Enter fullscreen mode Exit fullscreen mode

Same FFmpeg flags you already know, sent as structured data instead of a command-line string.

Which Approach Should You Use

os/exec if you need full control over FFmpeg and you can guarantee the binary is available in every environment your code runs in.

ffmpeg-go if you want a cleaner API for complex filter chains and don't mind the native FFmpeg dependency. Good for local tools and media pipelines where you control the host.

A cloud API like FFmpeg Micro if you're deploying to containers, serverless, or anywhere installing FFmpeg is impractical. Also the right choice when you don't want to manage scaling, codecs, or infrastructure yourself.

Common Pitfalls with FFmpeg in Go

"exec: ffmpeg: executable file not found in $PATH." The classic. Your code works locally but panics in Docker because the image doesn't include FFmpeg. Always test your Docker build with docker run --rm your-image which ffmpeg before deploying.

Stderr vs stdout confusion. FFmpeg writes progress and diagnostic info to stderr, not stdout. If you're only reading cmd.Output(), you'll miss error messages entirely. Use cmd.CombinedOutput() or attach cmd.Stderr to a buffer.

Goroutine leaks on long jobs. If you spawn a transcode in a goroutine and the HTTP request that triggered it times out, the FFmpeg process keeps running. Use exec.CommandContext with a context.WithTimeout to kill the process when the caller cancels.

Large file memory pressure. Piping FFmpeg output through Go's io.ReadAll on a 2GB file will spike memory. Stream the output to disk or to cloud storage instead of buffering it in memory.

Frequently Asked Questions

Do I need FFmpeg installed to process video in Go?

With os/exec and ffmpeg-go, yes. Both require the FFmpeg binary on the host machine. A cloud API like FFmpeg Micro processes video entirely server-side, so your Go app never touches FFmpeg directly.

Can I use FFmpeg in a Go app deployed to AWS Lambda?

You can bundle a static FFmpeg binary in your deployment package. But Lambda has memory limits (128MB-10GB), a 15-minute execution ceiling, and a 250MB package size limit. A 5-minute 1080p transcode can easily exceed all three. Offloading to an API keeps your Lambda small and fast.

What's the fastest way to add video processing to a Go application?

A cloud API gets you from zero to processing video in under 10 minutes. Define your structs, make an HTTP call, poll for completion. No installation, no Docker configuration, no codec debugging. FFmpeg Micro's free tier gives you enough processing time to build and test your integration.

Is ffmpeg-go still maintained?

The github.com/u2takey/ffmpeg-go package works for most common operations, but it hasn't had a major release recently. For simple transcoding it's fine. For bleeding-edge FFmpeg features or complex filter graphs, you may find yourself falling back to os/exec with raw arguments.

Last verified: 2026-05-26 against FFmpeg 7.1 and ffmpeg-micro API v1

Top comments (0)