Files
llink/go/internal/media/helpers.go
T

108 lines
2.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package media
import (
"bytes"
"context"
"errors"
"log/slog"
"os"
"os/exec"
"strings"
)
func IsIOSPlayableMime(mime string) bool {
switch mime {
case "video/mp4", "video/quicktime", "audio/mp4", "audio/aac", "audio/x-m4a", "audio/mpeg":
return true
}
return false
}
func IsAudio(mime string) bool {
return strings.HasPrefix(mime, "audio/")
}
type TranscodeInput struct {
SourceURL string
MimeType string
}
type TranscodeOutput struct {
TempLocalFilePath string
OutputMimeType string
// Extension such as ".m4a" or ".mp4"
OutputExt string
}
var (
ErrInvalidInput error = errors.New("invalid input")
)
// TranscodeToMp4 takes in any audio or video source URL and
// returns the filepath of the transcoded media
// WARNING: caller responsible for deleting TempLocalFilePath
func TranscodeToMp4(ctx context.Context, input TranscodeInput) (*TranscodeOutput, error) {
if input.SourceURL == "" || input.MimeType == "" {
return nil, ErrInvalidInput
}
isAudio := IsAudio(input.MimeType)
var outputExt, outputMime string
if isAudio {
outputExt = ".m4a"
outputMime = "audio/mp4"
} else {
outputExt = ".mp4"
outputMime = "video/mp4"
}
tmp, err := os.CreateTemp("", "transcode-*"+outputExt)
if err != nil {
slog.Error("transcode: failed to create temp file", "error", err)
return nil, err
}
tmpPath := tmp.Name()
tmp.Close()
var args []string
if isAudio {
args = []string{
"-y", "-i", input.SourceURL,
"-vn",
"-c:a", "aac", "-b:a", "128k",
"-movflags", "+faststart",
tmpPath,
}
} else {
// Cap encoder parallelism and lookahead to keep memory bounded — screen
// recordings come in at native display resolution (often 1440p4K) and
// libx264's per-thread lookahead/reference buffers blow past the worker's
// memory limit otherwise. Output is also downscaled to 1080p max, which
// mobile playback won't notice; the original WebM stays in GCS untouched.
args = []string{
"-y", "-i", input.SourceURL,
"-vf", "scale='min(1920,iw)':-2:flags=lanczos",
"-c:v", "libx264", "-preset", "veryfast", "-crf", "23",
"-pix_fmt", "yuv420p", "-profile:v", "baseline", "-level", "3.1",
"-x264-params", "rc-lookahead=20:ref=2",
"-threads", "2", "-filter_threads", "2",
"-c:a", "aac", "-b:a", "128k",
"-movflags", "+faststart",
tmpPath,
}
}
cmd := exec.CommandContext(ctx, "ffmpeg", args...)
var stderr bytes.Buffer
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return nil, errors.Join(err, errors.New(stderr.String()))
}
return &TranscodeOutput{
TempLocalFilePath: tmpPath,
OutputMimeType: outputMime,
OutputExt: outputExt,
}, nil
}