feat: support S3 media processing pipeline

This commit is contained in:
2026-02-04 19:15:44 +08:00
parent 8f7000dc8d
commit 57b7269215
4 changed files with 366 additions and 17 deletions

View File

@@ -114,7 +114,49 @@ func (j *MediaProcessWorker) Work(ctx context.Context, job *river.Job[args.Media
}
}
} else {
log.Warn("non-local provider, skipping ffmpeg processing")
tempDir, err := os.MkdirTemp("", "media-process-")
if err != nil {
log.Errorf("create temp dir failed: %v", err)
finalStatus = consts.MediaAssetStatusFailed
} else {
defer os.RemoveAll(tempDir)
ext := path.Ext(asset.ObjectKey)
inputFile := filepath.Join(tempDir, "source"+ext)
if err := j.storage.Download(ctx, asset.ObjectKey, inputFile); err != nil {
log.Errorf("download media file failed: %s, err=%v", asset.ObjectKey, err)
finalStatus = consts.MediaAssetStatusFailed
} else if _, err := exec.LookPath("ffmpeg"); err != nil {
log.Warn("ffmpeg not found, skipping real transcoding")
} else {
coverFile := filepath.Join(tempDir, "cover.jpg")
cmd := exec.CommandContext(
ctx,
"ffmpeg",
"-y",
"-i",
inputFile,
"-ss",
"00:00:00.000",
"-vframes",
"1",
"-vf",
"format=yuv420p",
"-update",
"1",
coverFile,
)
if out, err := cmd.CombinedOutput(); err != nil {
log.Errorf("ffmpeg failed: %s, output: %s", err, string(out))
finalStatus = consts.MediaAssetStatusFailed
} else {
log.Infof("Generated cover: %s", coverFile)
if err := j.registerCoverAsset(ctx, asset, coverFile); err != nil {
log.Errorf("register cover failed: %s", err)
finalStatus = consts.MediaAssetStatusFailed
}
}
}
}
}
}
@@ -175,21 +217,27 @@ func (j *MediaProcessWorker) registerCoverAsset(ctx context.Context, asset *mode
coverName := coverFilename(filename)
objectKey := buildObjectKey(tenant, hash, coverName)
// 本地存储将文件移动到目标 objectKey 位置,保持路径规范。
localPath := j.storage.Config.LocalPath
if localPath == "" {
localPath = "./storage"
}
dstPath := filepath.Join(localPath, filepath.FromSlash(objectKey))
if coverFile != dstPath {
if err := os.MkdirAll(filepath.Dir(dstPath), 0o755); err != nil {
return err
}
if _, err := os.Stat(dstPath); err == nil {
_ = os.Remove(coverFile)
} else if err := os.Rename(coverFile, dstPath); err != nil {
if strings.ToLower(asset.Provider) == "local" {
localPath := j.storage.Config.LocalPath
if localPath == "" {
localPath = "./storage"
}
dstPath := filepath.Join(localPath, filepath.FromSlash(objectKey))
if coverFile != dstPath {
if err := os.MkdirAll(filepath.Dir(dstPath), 0o755); err != nil {
return err
}
if _, err := os.Stat(dstPath); err == nil {
_ = os.Remove(coverFile)
} else if err := os.Rename(coverFile, dstPath); err != nil {
return err
}
}
} else {
if err := j.storage.PutObject(ctx, objectKey, coverFile, "image/jpeg"); err != nil {
return err
}
_ = os.Remove(coverFile)
}
coverAsset := &models.MediaAsset{