From 1734c3c342ebd0b047d572d997daa3f986152e8f Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 25 Sep 2024 12:36:30 +0300 Subject: [PATCH] lottie: improve interface --- ffmpeg/convert.go | 22 +++++++++++++++++----- lottie/convert.go | 46 +++++++++++++++++----------------------------- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/ffmpeg/convert.go b/ffmpeg/convert.go index 9cf0bb9..4e3889a 100644 --- a/ffmpeg/convert.go +++ b/ffmpeg/convert.go @@ -41,7 +41,7 @@ func SetPath(path string) { ffmpegPath = path } -// ConvertPath converts a media file on the disk using ffmpeg. +// ConvertPath converts a media file on the disk using ffmpeg and auto-generates the output file name. // // Args: // * inputFile: The full path to the file. @@ -53,6 +53,18 @@ func SetPath(path string) { // Returns: the path to the converted file. func ConvertPath(ctx context.Context, inputFile string, outputExtension string, inputArgs []string, outputArgs []string, removeInput bool) (string, error) { outputFilename := strings.TrimSuffix(strings.TrimSuffix(inputFile, filepath.Ext(inputFile)), "*") + outputExtension + return outputFilename, ConvertPathWithDestination(ctx, inputFile, outputFilename, inputArgs, outputArgs, removeInput) +} + +// ConvertPathWithDestination converts a media file on the disk using ffmpeg and saves the result to the provided file name. +// +// Args: +// * inputFile: The full path to the file. +// * outputFile: The full path to the output file. Must include the appropriate extension so ffmpeg knows what to convert to. +// * inputArgs: Arguments to tell ffmpeg how to parse the input file. +// * outputArgs: Arguments to tell ffmpeg how to convert the file to reach the wanted output. +// * removeInput: Whether the input file should be removed after converting. +func ConvertPathWithDestination(ctx context.Context, inputFile string, outputFile string, inputArgs []string, outputArgs []string, removeInput bool) error { if removeInput { defer func() { _ = os.Remove(inputFile) @@ -64,7 +76,7 @@ func ConvertPath(ctx context.Context, inputFile string, outputExtension string, args = append(args, inputArgs...) args = append(args, "-i", inputFile) args = append(args, outputArgs...) - args = append(args, outputFilename) + args = append(args, outputFile) cmd := exec.CommandContext(ctx, ffmpegPath, args...) ctxLog := zerolog.Ctx(ctx).With().Str("command", "ffmpeg").Logger() @@ -73,11 +85,11 @@ func ConvertPath(ctx context.Context, inputFile string, outputExtension string, cmd.Stderr = logWriter err := cmd.Run() if err != nil { - _ = os.Remove(outputFilename) - return "", fmt.Errorf("ffmpeg error: %w", err) + _ = os.Remove(outputFile) + return fmt.Errorf("ffmpeg error: %w", err) } - return outputFilename, nil + return nil } // ConvertBytes converts media data using ffmpeg. diff --git a/lottie/convert.go b/lottie/convert.go index 9f51a8f..e60b194 100644 --- a/lottie/convert.go +++ b/lottie/convert.go @@ -7,12 +7,12 @@ package lottie import ( - "bytes" "context" "fmt" "io" "os" "os/exec" + "path/filepath" "strconv" "github.com/rs/zerolog" @@ -83,42 +83,40 @@ func Convert(ctx context.Context, input io.Reader, outputFilename string, output return nil } -// FfmpegConvert converts lottie data to a video or image using ffmpeg. +// FFmpegConvert converts lottie data to a video or image using ffmpeg. // // This function should only be called if [ffmpeg.Supported] returns true. // // Args: // - input: an io.Reader containing the lottie data to convert. -// - target: the output format. Can be one of: webm, webp. +// - outputFile: the filename to write the output to. Must have .webp or .webm extension. // - width: the width of the output video or image. // - height: the height of the output video or image. // - fps: the framerate of the output video. // // Returns: the converted data as a *bytes.Buffer, the mimetype of the output, // and the thumbnail data as a PNG. -func FfmpegConvert(ctx context.Context, input io.Reader, target string, width, height, fps int) (*bytes.Buffer, string, []byte, error) { +func FFmpegConvert(ctx context.Context, input io.Reader, outputFile string, width, height, fps int) (thumbnailData []byte, err error) { if !ffmpeg.Supported() { - panic("ffmpeg is not available") + return nil, fmt.Errorf("ffmpeg is not available") } - var tmpDir string tmpDir, err := os.MkdirTemp("", "lottieconvert") if err != nil { - return nil, "", nil, err + return } defer os.RemoveAll(tmpDir) err = Convert(ctx, input, tmpDir+"/out_", nil, "pngs", width, height, strconv.Itoa(fps)) if err != nil { - return nil, "", nil, err + return } files, err := os.ReadDir(tmpDir) if err != nil { - return nil, "", nil, err + return } - var thumbnailData []byte var firstFrameName string for _, file := range files { if firstFrameName == "" || file.Name() < firstFrameName { @@ -127,36 +125,26 @@ func FfmpegConvert(ctx context.Context, input io.Reader, target string, width, h } thumbnailData, err = os.ReadFile(fmt.Sprintf("%s/%s", tmpDir, firstFrameName)) if err != nil { - return nil, "", nil, err + return } - var mimeType string var outputArgs []string - switch target { - case "webm": - mimeType = "video/webm" + switch filepath.Ext(outputFile) { + case ".webm": outputArgs = []string{"-c:v", "libvpx-vp9", "-pix_fmt", "yuva420p", "-f", "webm"} - case "webp": - mimeType = "image/webp" + case ".webp": outputArgs = []string{"-c:v", "libwebp_anim", "-pix_fmt", "yuva420p", "-f", "webp"} default: - panic(fmt.Sprintf("unsupported target %s", target)) + err = fmt.Errorf("unsupported extension %s", filepath.Ext(outputFile)) + return } - convertedFilename, err := ffmpeg.ConvertPath( + err = ffmpeg.ConvertPathWithDestination( ctx, tmpDir+"/out_*.png", - "", + outputFile, []string{"-framerate", strconv.Itoa(fps), "-pattern_type", "glob"}, outputArgs, false, ) - if err != nil { - return nil, "", nil, err - } else if file, err := os.Open(convertedFilename); err != nil { - return nil, "", nil, err - } else { - var buf bytes.Buffer - _, err = io.Copy(&buf, file) - return &buf, mimeType, thumbnailData, err - } + return }