Dino Angelov Software Architect

Generate animations and video with Go, gg and ffmpeg

• 3 minute read

I’ve been making some generative art pieces lately and publishing them on my other Instagram account and I wanted to make some animations. Result first:

So far I had only been using the excellent gg (Go Graphics) package for some static art. But how do you go from some static images to a video?

Pretty easy apparently and you can follow along with the simple project I setup as a playground.

Of course, make sure ffmpeg is installed on your OS and also install the gg package with:

go get -u github.com/fogleman/gg

Once it’s done, drop the following code in a file, say main.go. By the way, this code and my other generative art code is made available on GitHub.

package main

import (
	"fmt"
	"image/color"
	"math"
	"math/rand"

	"github.com/fogleman/gg"
)

type ego struct {
	X, Y, Radius, Angle, Speed float64
}

func (v *ego) init(maxRadius float64, speed float64) {
	v.Radius = float64(rand.Int31n(int32(maxRadius)))
	v.Angle = float64(rand.Int31n(180))
	v.Speed = speed
}

func (v *ego) travel() {
    // Move the angle forward
    v.Angle += v.Speed
    // Adjust the X, Y coordinates
	sin, cos := math.Sincos(gg.Radians(v.Angle))
	v.X = v.Radius * cos
	v.Y = v.Radius * sin
}

func main() {
	// config
	const (
		s         = 2000.0               // Size of final image
		maxRadius = (s - (s * 0.15)) / 2 // Max radius of the circle around which egos travel
	)

	// How many egos will be travelling?
	egos := [30]ego{}

	// Initialize all of them
	for i := range egos {
		egos[i] = ego{}
		egos[i].init(maxRadius, 0.5+rand.Float64()*6.0)
	}

    // Rotate all the egos until we complete a circle,
    // and then, for each rotation...
	for i := 0; i < 360; i++ {
		// Start a drawing context
		dc := gg.NewContext(int(s), int(s))

		// Set a background color
		dc.SetColor(color.RGBA{0, 0, 0, 255})
		dc.Clear()

        // Draw all the egos
		for n := range egos {
			ego := &egos[n]
			ego.travel()

			// Draw the orbit
			dc.SetColor(color.NRGBA{255, 255, 255, 60})
			dc.DrawCircle(s/2, s/2, ego.Radius)
			dc.SetLineWidth(3)
			dc.Stroke()

			// Draw the position
			dc.SetColor(color.RGBA{255, 0, 0, 255})
			dc.DrawCircle(ego.X+s/2, ego.Y+s/2, 12)
			dc.Fill()
        }

        // Save the output
		dc.SavePNG(fmt.Sprintf("i-%d.png", i))
	}
}

Run the code with

go run main.go

After a short while and letting the command run it’s course, you likely hate me for creating 360 images in the directory where you ran the code. But worry not, we’ll clean that up soon.

Next, we want to instruct ffmpeg to take all our images and combine them into a video.

ffmpeg -start_number 0 -i i-%d.png -pix_fmt yuv420p -movflags +faststart -vcodec libx264 video.mp4

Note that if you don’t include the -pix_fmt and -movflags options above, Firefox (and maybe other browsers) might complain with a message saying something like “Video can’t be played because the file is corrupt”. But the above has been tested and works well in Firefox and other browsers.

With -start_number we’re saying our images start with the number 0, and with i-%d.png, we instruct ffmpeg to look for files named in that pattern.

You should now have a slick video of your animation. Once you’ve opened it up and verified it works, you can clean up your directory with:

rm *.png

but be careful to run it in your project directory.

If you followed along this far, you must be interested in some generative art. Have you checked out my Instagram account yet? :)

Thanks for reading and feel free to reach out if you want to talk programming, Go, generative art or anything else I might cover on this blog.