Compare commits

..

2 Commits

Author SHA1 Message Date
1936c1a73a feat(video): 支持HEVC编码视频转换为TS格式
- 添加GetVideoCodec函数用于检测视频编码格式
- 实现HEVC编码视频的特殊转换逻辑
- 引入convertHevcToTs函数处理HEVC编码视频
- 保持原有MP4格式转换功能的兼容性
- 添加错误处理和日志记录机制
- 集成链路追踪支持视频编解码操作监控
2026-02-27 17:52:11 +08:00
72b8d277ea fix(video): 修复视频剪辑功能中的参数错误和音频处理问题
- 修正 SlowVideoCut 函数中传入的参数,将第二个 task.Offset 替换为 task.Length
- 添加静音音频源输入以解决无音频轨道的视频处理问题
- 配置音频编解码器为 aac 格式并启用最短时长截取
- 在多个视频处理函数中统一音频处理逻辑
- 修复 concat 方式视频剪辑的音频映射问题
- 确保视频剪辑操作保留正确的音频流处理
2026-02-27 17:50:50 +08:00

View File

@@ -79,7 +79,20 @@ func runFfmpegForMultipleFile1(ctx context.Context, task *dto.FfmpegTask) bool {
go func(file *dto.File) { go func(file *dto.File) {
defer wg.Done() defer wg.Done()
tmpFile := path.Join(os.TempDir(), file.Name+strconv.Itoa(rand.Int())+".ts") tmpFile := path.Join(os.TempDir(), file.Name+strconv.Itoa(rand.Int())+".ts")
result, err := convertMp4ToTs(subCtx, *file, tmpFile) codec, err := GetVideoCodec(subCtx, file.Url)
if err != nil {
logger.Error("获取视频编码失败", zap.Error(err))
mu.Lock()
notOk = true
mu.Unlock()
return
}
var result bool
if codec == "hevc" {
result, err = convertHevcToTs(subCtx, *file, tmpFile)
} else {
result, err = convertMp4ToTs(subCtx, *file, tmpFile)
}
if err != nil { if err != nil {
logger.Error("转码出错", zap.Error(err)) logger.Error("转码出错", zap.Error(err))
mu.Lock() mu.Lock()
@@ -147,7 +160,7 @@ func runFfmpegForMultipleFile2(ctx context.Context, task *dto.FfmpegTask) bool {
subCtx, span := tracer.Start(ctx, "runFfmpegForMultipleFile2") subCtx, span := tracer.Start(ctx, "runFfmpegForMultipleFile2")
defer span.End() defer span.End()
// 多文件,方法二:使用计算资源编码 // 多文件,方法二:使用计算资源编码
result, err := SlowVideoCut(subCtx, task.Files, task.Offset, task.Offset, task.OutputFile) result, err := SlowVideoCut(subCtx, task.Files, task.Offset, task.Length, task.OutputFile)
if err != nil { if err != nil {
return false return false
} }
@@ -317,8 +330,10 @@ func QuickVideoCut(ctx context.Context, inputFile string, offset, length float64
"-hide_banner", "-hide_banner",
"-y", "-y",
"-i", inputFile, "-i", inputFile,
"-f", "lavfi", "-i", "anullsrc=channel_layout=mono:sample_rate=44100",
"-c:v", "copy", "-c:v", "copy",
"-an", "-c:a", "aac",
"-shortest",
"-reset_timestamps", "1", "-reset_timestamps", "1",
"-ss", strconv.FormatFloat(offset, 'f', 2, 64), "-ss", strconv.FormatFloat(offset, 'f', 2, 64),
"-t", strconv.FormatFloat(length, 'f', 2, 64), "-t", strconv.FormatFloat(length, 'f', 2, 64),
@@ -360,8 +375,10 @@ func QuickConcatVideoCut(ctx context.Context, inputFiles []dto.File, offset, len
"-f", "concat", "-f", "concat",
"-safe", "0", "-safe", "0",
"-i", tmpFile, "-i", tmpFile,
"-f", "lavfi", "-i", "anullsrc=channel_layout=mono:sample_rate=44100",
"-c:v", "copy", "-c:v", "copy",
"-an", "-c:a", "aac",
"-shortest",
"-ss", strconv.FormatFloat(offset, 'f', 2, 64), "-ss", strconv.FormatFloat(offset, 'f', 2, 64),
"-t", strconv.FormatFloat(length, 'f', 2, 64), "-t", strconv.FormatFloat(length, 'f', 2, 64),
"-f", "mp4", "-f", "mp4",
@@ -383,6 +400,9 @@ func SlowVideoCut(ctx context.Context, inputFiles []dto.File, offset, length flo
ffmpegCmd = append(ffmpegCmd, "-i", file.Url) ffmpegCmd = append(ffmpegCmd, "-i", file.Url)
} }
// 添加静音音频源作为额外输入
ffmpegCmd = append(ffmpegCmd, "-f", "lavfi", "-i", "anullsrc=channel_layout=mono:sample_rate=44100")
inputCount := len(inputFiles) inputCount := len(inputFiles)
filterComplex := strings.Builder{} filterComplex := strings.Builder{}
for i := 0; i < inputCount; i++ { for i := 0; i < inputCount; i++ {
@@ -393,8 +413,10 @@ func SlowVideoCut(ctx context.Context, inputFiles []dto.File, offset, length flo
ffmpegCmd = append(ffmpegCmd, ffmpegCmd = append(ffmpegCmd,
"-filter_complex", filterComplex.String(), "-filter_complex", filterComplex.String(),
"-map", "[v]", "-map", "[v]",
"-map", fmt.Sprintf("%d:a", inputCount),
"-c:a", "aac",
"-shortest",
"-preset:v", "fast", "-preset:v", "fast",
"-an",
"-ss", strconv.FormatFloat(offset, 'f', 2, 64), "-ss", strconv.FormatFloat(offset, 'f', 2, 64),
"-t", strconv.FormatFloat(length, 'f', 2, 64), "-t", strconv.FormatFloat(length, 'f', 2, 64),
"-f", "mp4", "-f", "mp4",
@@ -457,6 +479,33 @@ func handleFfmpegProcess(ctx context.Context, ffmpegCmd []string) (bool, error)
} }
} }
func GetVideoCodec(ctx context.Context, filePath string) (string, error) {
_, span := tracer.Start(ctx, "GetVideoCodec")
defer span.End()
ffprobeCmd := []string{
"ffprobe",
"-v", "error",
"-select_streams", "v:0",
"-show_entries", "stream=codec_name",
"-of", "default=noprint_wrappers=1:nokey=1",
filePath,
}
span.SetAttributes(attribute.String("ffprobe.cmd", ToJson(ffprobeCmd)))
cmd := exec.Command(ffprobeCmd[0], ffprobeCmd[1:]...)
var out bytes.Buffer
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
span.SetAttributes(attribute.String("error", err.Error()))
span.SetStatus(codes.Error, "获取视频编码失败")
return "", fmt.Errorf("failed to get video codec: %w", err)
}
codec := strings.TrimSpace(out.String())
span.SetAttributes(attribute.String("video.codec", codec))
return codec, nil
}
func GetVideoDuration(ctx context.Context, filePath string) (float64, error) { func GetVideoDuration(ctx context.Context, filePath string) (float64, error) {
_, span := tracer.Start(ctx, "GetVideoDuration") _, span := tracer.Start(ctx, "GetVideoDuration")
defer span.End() defer span.End()