add conversion.go
This commit is contained in:
parent
40fc4774ff
commit
9bab1e8413
1 changed files with 115 additions and 0 deletions
115
conversion.go
Normal file
115
conversion.go
Normal file
|
@ -0,0 +1,115 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Create a file describing frames and their durations for FFmpeg's concat demuxer
|
||||
// https://ffmpeg.org/ffmpeg-formats.html#concat
|
||||
func makeConcatDemuxFile(manifest UgoiraManifest, frameDescFile *os.File) {
|
||||
frames := manifest.Body.Frames
|
||||
for _, frame := range frames {
|
||||
frameDur := float32(frame.Delay) / 1000 // convert to seconds
|
||||
frameDescFile.WriteString(fmt.Sprintf("file '%s'\n", frame.File))
|
||||
frameDescFile.WriteString(fmt.Sprintf("duration %f\n", frameDur))
|
||||
}
|
||||
// repeat last frame (see: https://trac.ffmpeg.org/wiki/Slideshow#Concatdemuxer
|
||||
lastFrame := frames[len(frames)-1]
|
||||
frameDescFile.WriteString(fmt.Sprintf("file '%s'\n", lastFrame.File))
|
||||
}
|
||||
|
||||
func callFFmpeg(frameDescPath, outputFilePath, workDir string) error {
|
||||
// TODO: allow custom ffmpeg flags
|
||||
const ffmpegArgs = `-hide_banner
|
||||
-loglevel warning
|
||||
-f concat -i %s
|
||||
-an
|
||||
-fps_mode vfr
|
||||
-q:v 10
|
||||
-c:v libvpx-vp9
|
||||
-b:v 1M
|
||||
-lossless 1 %s`
|
||||
|
||||
cmdArgs := fmt.Sprintf(ffmpegArgs, frameDescPath, outputFilePath)
|
||||
cmdArgsSplit := strings.Fields(cmdArgs)
|
||||
ffmpegProc := exec.Command("ffmpeg", cmdArgsSplit...)
|
||||
ffmpegProc.Dir = workDir
|
||||
|
||||
if err := ffmpegProc.Run(); err != nil {
|
||||
if exitErr, ok := err.(*exec.ExitError); ok {
|
||||
return fmt.Errorf("ffmpeg exited with non zero code: %d", exitErr.ExitCode())
|
||||
}
|
||||
return fmt.Errorf("Could not run ffmpeg: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reads and uncompress a frame from the archive and saves it
|
||||
func uncompressFrame(file *zip.File, framePath string) error {
|
||||
fileFp, err := file.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fileFp.Close()
|
||||
|
||||
outFp, err := os.Create(framePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer outFp.Close()
|
||||
|
||||
if _, err := io.Copy(outFp, fileFp); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func unpackUgoira(ugoiraPath, outDir string) error {
|
||||
reader, err := zip.OpenReader(ugoiraPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer reader.Close()
|
||||
|
||||
for _, file := range reader.File {
|
||||
path := filepath.Join(outDir, file.Name)
|
||||
if err := uncompressFrame(file, path); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ugoira2video(manifest UgoiraManifest, ugoiraFileName, vidOut string) error {
|
||||
tdir, err := os.MkdirTemp("", "u2vwd-")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(tdir)
|
||||
|
||||
fraFp, err := os.CreateTemp(tdir, "frames*.txt")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(fraFp.Name())
|
||||
defer fraFp.Close()
|
||||
|
||||
vidOutAbs, err := filepath.Abs(vidOut)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := unpackUgoira(ugoiraFileName, tdir); err != nil {
|
||||
return fmt.Errorf("Could not unpack Ugoira: %w", err)
|
||||
}
|
||||
makeConcatDemuxFile(manifest, fraFp)
|
||||
if err := callFFmpeg(fraFp.Name(), vidOutAbs, tdir); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
Loading…
Add table
Reference in a new issue