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")
}
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")
}
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)
}
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"},
},
}
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)