563c91e7d5
Resolves issues with gcp cloud logging quirks such as field names
105 lines
2.4 KiB
Go
105 lines
2.4 KiB
Go
package media
|
||
|
||
import (
|
||
"bytes"
|
||
"context"
|
||
"errors"
|
||
"os"
|
||
"os/exec"
|
||
"strings"
|
||
|
||
"github.com/flowy-live/llink/internal/utils/flog"
|
||
)
|
||
|
||
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
|
||
OutputExt string // e.g. ".m4a" or ".mp4"
|
||
}
|
||
|
||
var (
|
||
ErrInvalidInput error = errors.New("invalid input")
|
||
)
|
||
|
||
// TranscodeToMp4 writes the result to a temp file; caller is 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 {
|
||
flog.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 {
|
||
// 1440p–4K screen recordings + libx264's lookahead buffers can OOM the
|
||
// worker. Bound parallelism/lookahead and downscale to 1080p; 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
|
||
}
|