package video import ( "context" "fmt" "os" "os/exec" "path/filepath" "time" dto "media/model/dto/video" "github.com/gogf/gf/v2/frame/g" ) type transcodeService struct{} var Transcode = new(transcodeService) // TranscodeToMP4 将视频转码为 H.264 + AAC + MP4 + faststart // inputPath: 输入文件路径 // outputDir: 输出目录(可选),为空则使用 resource/temp // 返回输出文件完整路径、文件信息 func (s *transcodeService) TranscodeToMP4(ctx context.Context, inputPath string, outputDir string) (*dto.TranscodeRes, error) { ffmpegPath, err := lookupFFmpegPath() if err != nil { return nil, fmt.Errorf("ffmpeg 未找到: %v", err) } // 输出目录 if outputDir == "" { outputDir = g.Cfg().MustGet(ctx, "ffmpeg.temp_dir", "resource/temp").String() if outputDir == "" { outputDir = "resource/temp" } if !filepath.IsAbs(outputDir) { absDir, _ := filepath.Abs(outputDir) outputDir = absDir } } os.MkdirAll(outputDir, 0755) // 输出文件名:原文件名_转码_时间戳.mp4 base := filepath.Base(inputPath) ext := filepath.Ext(base) name := base[:len(base)-len(ext)] outputName := fmt.Sprintf("%s_transcoded_%d.mp4", name, time.Now().UnixMilli()) outputPath := filepath.Join(outputDir, outputName) g.Log().Infof(ctx, "[转码] 开始: %s → %s", inputPath, outputPath) // FFmpeg 转码命令 // -c:v libx264 H.264 视频编码 // -preset fast 编码速度/质量平衡 // -crf 22 画质(0-51,越小越好,18-28常用) // -c:a aac AAC 音频编码 // -b:a 128k 音频码率 128k // -movflags +faststart MOOV 前置(关键!seed-lite 必需) args := []string{ "-i", inputPath, "-c:v", "libx264", "-preset", "fast", "-crf", "22", "-c:a", "aac", "-b:a", "128k", "-movflags", "+faststart", "-y", outputPath, } cmd := exec.CommandContext(ctx, ffmpegPath, args...) output, err := cmd.CombinedOutput() if err != nil { return nil, fmt.Errorf("FFmpeg 转码失败: %v\n%s", err, string(output)) } // 检查输出文件 stat, statErr := os.Stat(outputPath) if statErr != nil { return nil, fmt.Errorf("转码输出文件不存在: %v", statErr) } // 获取时长 durationStr := getVideoDurationStr(ctx, outputPath) g.Log().Infof(ctx, "[转码] 完成: %s, 大小=%d, 时长=%s", outputPath, stat.Size(), durationStr) return &dto.TranscodeRes{ OutputPath: outputPath, FileSize: stat.Size(), FileName: outputName, DurationStr: durationStr, }, nil }