I wrote a small program in go to export slides to video using ffmpeg. It creates a video for each slide then concatenates them and finally adds the audio.
For the first step, I didn’t need to create the (single image) videos one after the other, so I used goroutines to run them in separate threads.
wg.Add(1)
go img2video(imgName, imgDuration, outputName)
Running external commands is reasonably straight-forward, and so is accessing error messages
func img2video(imgName string, imgDuration string, outputName string) {
defer wg.Done()
var stderr bytes.Buffer
cmd := exec.Command("ffmpeg", "-loop", "1", "-i", imgName, "-t", imgDuration, "-pix_fmt", "yuv420p", outputName)
cmd.Stderr = &stderr
err := cmd.Start()
if err != nil {
log.Fatal(err)
} else {
log.Printf("Creating "+outputName)
}
err = cmd.Wait()
if err != nil {
log.Printf(fmt.Sprint(err) + ": " + stderr.String())
log.Fatal(err)
} else {
log.Printf("Created "+outputName)
}
}
Note that defer
postpones the wg.Done()
call to when the function finishes, and wg
is a WaitGroup.
wg.Wait()
done := make(chan bool, 1)
go concatVideos(len(lines), videoListFilename, silentFilename, done)
This way we can wait for all the (single image) videos to be created before we concatenate them using ffmpeg.
func concatVideos(numberOfVideos int, listFilename string, outputName string, done chan bool) {
var stderr bytes.Buffer
cmd := exec.Command("ffmpeg", "-f", "concat", "-i", listFilename, "-c", "copy", outputName)
cmd.Stderr = &stderr
err := cmd.Start()
if err != nil {
log.Fatal(err)
} else {
log.Printf("Creating " + outputName + " using " + listFilename)
}
err = cmd.Wait()
if err != nil {
log.Printf(fmt.Sprint(err) + ": " + stderr.String())
log.Fatal(err)
} else {
log.Printf("Created " + outputName)
}
done <- true
}
The done
channel is to know when ffmpeg has finished concatenating the files, so that we can add audio (again using ffmpeg).
<-done
go addAudio(silentFilename, *audioFilenamePtr, *outputFilenamePtr, done)
Note that we can delete the single image videos before waiting for the addAudio goroutine to pass a true
value on the done
channel.
for i, line := range lines {
if line != "" {
outputFilename := "out" + strconv.Itoa(i+1) + ".mp4"
err = os.Remove(outputFilename)
if err != nil {
log.Fatal(err)
} else {
log.Printf("Deleted " + outputFilename)
}
}
}
<-done
Finally, converting an int to string requires strconv.Itoa()
.