DEV Community

Cover image for The Missing Piece in Go Media Processing
Danila Fominykh
Danila Fominykh

Posted on

The Missing Piece in Go Media Processing

Most Go media libraries eventually hit the same wall.

They don't have their own decoder.

Instead, they rely on a system-installed FFmpeg package, shared libraries, platform-specific DLLs, or external binaries that must already exist on the target machine.

Everything works fine during development. Then deployment starts.

And suddenly you're debugging missing DLLs on Windows, incompatible FFmpeg versions on Linux, Homebrew version mismatches on macOS, container images missing required libs, or a routine package update silently breaking media processing in production.

The Goal

I needed a decoder that:

  • Has pure Go API
  • Compiles into a single binary
  • Requires no FFmpeg or other runtime dependencies
  • Supports modern codecs — AV1, H.264, H.265, VP8, VP9, Opus, AAC, MP3, and more
  • Can read from files, memory buffers, HTTP streams, pipes, SSH streams, and any io.Reader

The result is gopeg.

Instead of depending on whatever media stack happens to be installed on the machine, the required FFmpeg components and the dav1d decoder are statically linked into the application at build time via cgo.

By the time the binary is ready, everything needed to decode video is already inside it.

Why This Matters

An enormous amount of time gets spent on deployment problems rather than on actual application logic. A typical media processing pipeline often looks like this:

  1. Install FFmpeg
  2. Install compatible shared libraries
  3. Package them correctly
  4. Make sure production machines have compatible versions
  5. Hope that future OS updates don't break anything

The application becomes dependent on external state. With static linking, the deployment story collapses to a single step: go build, and you're done.

No external codec packages. No DLLs. No runtime library search paths or hacky cmd.Exec wrappers. No "works on my machine."

io.Reader Support

Flexibility was another core design goal. The decoder doesn't need direct file access — it should work with arbitrary streams:

resp, _ := http.Get(url)
dec, err := gopeg.NewDecoder(resp.Body)
Enter fullscreen mode Exit fullscreen mode

Or:

dec, err := gopeg.NewDecoder(os.Stdin)
Enter fullscreen mode Exit fullscreen mode

Or any data arriving over an SSH connection or a custom transport. When the source implements Seek(), the library can extract metadata as well — though note that for true streams, accurate duration cannot be determined.

Simplicity Over Abstraction

One of my main goals was to avoid building a heavyweight framework. The API is intentionally as minimal as possible.

Open a source:

dec, err := gopeg.NewDecoder(file)
Enter fullscreen mode Exit fullscreen mode

Read metadata:

meta := dec.Meta()
Enter fullscreen mode Exit fullscreen mode

Decode frames:

for {
    frame, err := dec.DecodeFrame()
    if err != nil {
        panic(err)
    }
    if frame == nil {
        break
    }
    // process frame
}
Enter fullscreen mode Exit fullscreen mode

No background workers. No hidden processes. No external dependencies. Just a clean Go interface with idiomatic types.

Wrapping Up

FFmpeg is an incredible piece of software, but depending on a system-installed FFmpeg introduces operational complexity that many applications simply don't need. For my use cases, a statically linked approach turned out to be the more sensible choice — and as a bonus, the final binary size came out smaller than expected.

The result is a media decoder that's easy to embed, easy to deploy, and behaves predictably in any environment.

One self-contained binary. Everything needed to decode video, already inside.

That's why gopeg is the first complete Go solution that gives access to all modern decoders without requiring platform DLLs or external packages — making deployment dramatically simpler.

Link: gopeg repository on github.

Top comments (0)