zijiren 11 месяцев назад
Родитель
Сommit
468fcb8a8a
5 измененных файлов с 100 добавлено и 9 удалено
  1. 1 1
      Dockerfile
  2. 1 1
      README.md
  3. 1 1
      README.zh.md
  4. 96 5
      core/common/audio/audio.go
  5. 1 1
      core/common/config/config.go

+ 1 - 1
Dockerfile

@@ -29,7 +29,7 @@ COPY --from=builder /aiproxy/core/aiproxy /usr/local/bin/aiproxy
 
 ENV PUID=0 PGID=0 UMASK=022
 
-ENV FFPROBE_ENABLED=true
+ENV FFMPEG_ENABLED=true
 
 EXPOSE 3000
 

+ 1 - 1
README.md

@@ -68,7 +68,7 @@ docker-compose up -d
 - `LISTEN`: The listen address, default is `:3000`
 - `ADMIN_KEY`: The admin key for the AI Proxy Service, admin key is used to admin api and relay api, default is empty
 - `INTERNAL_TOKEN`: Internal token for service authentication, default is empty
-- `FFPROBE_ENABLED`: Whether to enable ffprobe, default is `false`
+- `FFMPEG_ENABLED`: Whether to enable ffmpeg, default is `false`
 
 ### Debug Options
 

+ 1 - 1
README.zh.md

@@ -69,7 +69,7 @@ docker-compose up -d
 - `LISTEN`: 监听地址,默认 `:3000`
 - `ADMIN_KEY`: 管理员密钥,用于管理 API 和转发 API,默认空
 - `INTERNAL_TOKEN`: 内部服务认证 token,默认空
-- `FFPROBE_ENABLED`: 是否启用 ffprobe,默认 `false`
+- `FFMPEG_ENABLED`: 是否启用 ffmpeg,默认 `false`
 
 ### Debug 选项
 

+ 96 - 5
core/common/audio/audio.go

@@ -1,19 +1,24 @@
 package audio
 
 import (
+	"bytes"
 	"errors"
 	"io"
 	"os/exec"
+	"regexp"
 	"strconv"
 	"strings"
 
 	"github.com/labring/aiproxy/core/common/config"
 )
 
-var ErrAudioDurationNAN = errors.New("audio duration is N/A")
+var (
+	ErrAudioDurationNAN = errors.New("audio duration is N/A")
+	re                  = regexp.MustCompile(`time=(\d+:\d+:\d+\.\d+)`)
+)
 
 func GetAudioDuration(audio io.Reader) (float64, error) {
-	if !config.FfprobeEnabled {
+	if !config.FfmpegEnabled {
 		return 0, nil
 	}
 
@@ -34,7 +39,15 @@ func GetAudioDuration(audio io.Reader) (float64, error) {
 	str := strings.TrimSpace(string(output))
 
 	if str == "" || str == "N/A" {
-		return 0, ErrAudioDurationNAN
+		seeker, ok := audio.(io.Seeker)
+		if !ok {
+			return 0, ErrAudioDurationNAN
+		}
+		_, err := seeker.Seek(0, io.SeekStart)
+		if err != nil {
+			return 0, ErrAudioDurationNAN
+		}
+		return getAudioDurationFallback(audio)
 	}
 
 	duration, err := strconv.ParseFloat(str, 64)
@@ -44,8 +57,31 @@ func GetAudioDuration(audio io.Reader) (float64, error) {
 	return duration, nil
 }
 
+func getAudioDurationFallback(audio io.Reader) (float64, error) {
+	if !config.FfmpegEnabled {
+		return 0, nil
+	}
+
+	ffmpegCmd := exec.Command(
+		"ffmpeg",
+		"-i", "-",
+		"-f", "null", "-",
+	)
+	ffmpegCmd.Stdin = audio
+	var stderr bytes.Buffer
+	ffmpegCmd.Stderr = &stderr
+	err := ffmpegCmd.Run()
+	if err != nil {
+		return 0, err
+	}
+
+	// Parse the time from ffmpeg output
+	// Example: size=N/A time=00:00:05.52 bitrate=N/A speed= 785x
+	return parseTimeFromFfmpegOutput(stderr.String())
+}
+
 func GetAudioDurationFromFilePath(filePath string) (float64, error) {
-	if !config.FfprobeEnabled {
+	if !config.FfmpegEnabled {
 		return 0, nil
 	}
 
@@ -65,7 +101,7 @@ func GetAudioDurationFromFilePath(filePath string) (float64, error) {
 	str := strings.TrimSpace(string(output))
 
 	if str == "" || str == "N/A" {
-		return 0, ErrAudioDurationNAN
+		return getAudioDurationFromFilePathFallback(filePath)
 	}
 
 	duration, err := strconv.ParseFloat(str, 64)
@@ -74,3 +110,58 @@ func GetAudioDurationFromFilePath(filePath string) (float64, error) {
 	}
 	return duration, nil
 }
+
+func getAudioDurationFromFilePathFallback(filePath string) (float64, error) {
+	if !config.FfmpegEnabled {
+		return 0, nil
+	}
+
+	ffmpegCmd := exec.Command(
+		"ffmpeg",
+		"-i", filePath,
+		"-f", "null", "-",
+	)
+
+	var stderr bytes.Buffer
+	ffmpegCmd.Stderr = &stderr
+	err := ffmpegCmd.Run()
+	if err != nil {
+		return 0, err
+	}
+
+	// Parse the time from ffmpeg output
+	return parseTimeFromFfmpegOutput(stderr.String())
+}
+
+// parseTimeFromFfmpegOutput extracts and converts time from ffmpeg output to seconds
+func parseTimeFromFfmpegOutput(output string) (float64, error) {
+	match := re.FindStringSubmatch(output)
+	if len(match) < 2 {
+		return 0, ErrAudioDurationNAN
+	}
+
+	// Convert time format HH:MM:SS.MS to seconds
+	timeStr := match[1]
+	parts := strings.Split(timeStr, ":")
+	if len(parts) != 3 {
+		return 0, errors.New("invalid time format")
+	}
+
+	hours, err := strconv.ParseFloat(parts[0], 64)
+	if err != nil {
+		return 0, err
+	}
+
+	minutes, err := strconv.ParseFloat(parts[1], 64)
+	if err != nil {
+		return 0, err
+	}
+
+	seconds, err := strconv.ParseFloat(parts[2], 64)
+	if err != nil {
+		return 0, err
+	}
+
+	duration := hours*3600 + minutes*60 + seconds
+	return duration, nil
+}

+ 1 - 1
core/common/config/config.go

@@ -18,7 +18,7 @@ var (
 var (
 	DisableAutoMigrateDB = env.Bool("DISABLE_AUTO_MIGRATE_DB", false)
 	AdminKey             = os.Getenv("ADMIN_KEY")
-	FfprobeEnabled       = env.Bool("FFPROBE_ENABLED", false)
+	FfmpegEnabled        = env.Bool("FFMPEG_ENABLED", false)
 )
 
 var (