Add metrics for audio duration

This commit is contained in:
2025-08-13 12:18:51 +03:00
parent 900c7ecb51
commit d957800e18
6 changed files with 109 additions and 6 deletions

View File

@@ -0,0 +1,72 @@
package ffmpeg
import (
"encoding/json"
"fmt"
"os"
"os/exec"
"strconv"
"git.vakhrushev.me/av/transcriber/internal/contract"
)
const ffprobeExecutable = "ffprobe"
type FfmpegMetaViewer struct {
}
// ffprobeOutput представляет структуру JSON-ответа от ffprobe
type ffprobeOutput struct {
Format struct {
Duration string `json:"duration"`
} `json:"format"`
}
func NewFfmpegMetaViewer() *FfmpegMetaViewer {
return &FfmpegMetaViewer{}
}
func (m *FfmpegMetaViewer) GetInfo(src string) (*contract.AudioInfo, error) {
// Проверяем существование исходного файла
if _, err := os.Stat(src); os.IsNotExist(err) {
return nil, fmt.Errorf("input file does not exist: %s", src)
}
// Проверяем, что ffprobe доступен в системе
if _, err := exec.LookPath(ffprobeExecutable); err != nil {
return nil, fmt.Errorf("ffprobe not found in PATH: %w", err)
}
// Создаем команду ffprobe для получения метаданных
cmd := exec.Command(ffprobeExecutable,
"-v", "quiet", // тихий режим (без лишнего вывода)
"-print_format", "json", // вывод в формате JSON
"-show_format", // показать информацию о формате
src, // входной файл
)
// Выполняем команду и получаем вывод
output, err := cmd.Output()
if err != nil {
return nil, fmt.Errorf("ffprobe execution failed: %w", err)
}
// Парсим JSON-ответ
var probeResult ffprobeOutput
if err := json.Unmarshal(output, &probeResult); err != nil {
return nil, fmt.Errorf("failed to parse ffprobe output: %w", err)
}
// Конвертируем длительность из строки в секунды
durationFloat, err := strconv.ParseFloat(probeResult.Format.Duration, 64)
if err != nil {
return nil, fmt.Errorf("failed to parse duration: %w", err)
}
// Округляем до целых секунд
durationSeconds := int(durationFloat + 0.5) // +0.5 для правильного округления
return &contract.AudioInfo{
Seconds: durationSeconds,
}, nil
}

View File

@@ -6,6 +6,14 @@ import (
"git.vakhrushev.me/av/transcriber/internal/entity"
)
type AudioInfo struct {
Seconds int // Длина аудиофайла в секундах
}
type AudioMetaViewer interface {
GetInfo(src string) (*AudioInfo, error)
}
type AudioFileConverter interface {
Convert(src, dest string) error
}

View File

@@ -15,7 +15,8 @@ import (
"testing"
"time"
"git.vakhrushev.me/av/transcriber/internal/adapter/converter/ffmpeg"
ffmpegconv "git.vakhrushev.me/av/transcriber/internal/adapter/converter/ffmpeg"
ffmpegmv "git.vakhrushev.me/av/transcriber/internal/adapter/metaviewer/ffmpeg"
"git.vakhrushev.me/av/transcriber/internal/adapter/recognizer"
"git.vakhrushev.me/av/transcriber/internal/adapter/repo/sqlite"
"git.vakhrushev.me/av/transcriber/internal/entity"
@@ -58,10 +59,11 @@ func setupTestRouter(t *testing.T) (*gin.Engine, *TranscribeHandler) {
fileRepo := sqlite.NewFileRepository(db, gq)
jobRepo := sqlite.NewTranscriptJobRepository(db, gq)
converter := ffmpeg.NewFfmpegConverter()
metaviewer := ffmpegmv.NewFfmpegMetaViewer()
converter := ffmpegconv.NewFfmpegConverter()
recognizer := &recognizer.MemoryAudioRecognizer{}
trsService := service.NewTranscribeService(jobRepo, fileRepo, converter, recognizer)
trsService := service.NewTranscribeService(jobRepo, fileRepo, metaviewer, converter, recognizer)
handler := NewTranscribeHandler(jobRepo, trsService)

View File

@@ -24,6 +24,16 @@ var (
[]string{"file_extension"},
)
// Время конвертации файлов (в секундах)
InputFileDurationHistogram = promauto.NewHistogramVec(
prometheus.HistogramOpts{
Name: "transcriber_input_file_duration_seconds",
Help: "Duration of input audio file",
Buckets: []float64{15, 30, 60, 120, 300, 600, 1200, 1800, 2400, 3000, 3600, 7200, 10800, 14400},
},
[]string{},
)
// Время конвертации файлов (в секундах)
ConversionDurationHistogram = promauto.NewHistogramVec(
prometheus.HistogramOpts{

View File

@@ -25,6 +25,7 @@ const (
type TranscribeService struct {
jobRepo contract.TranscriptJobRepository
fileRepo contract.FileRepository
metaviewer contract.AudioMetaViewer
converter contract.AudioFileConverter
recognizer contract.AudioRecognizer
}
@@ -32,12 +33,14 @@ type TranscribeService struct {
func NewTranscribeService(
jobRepo contract.TranscriptJobRepository,
fileRepo contract.FileRepository,
metaviewer contract.AudioMetaViewer,
converter contract.AudioFileConverter,
recognizer contract.AudioRecognizer,
) *TranscribeService {
return &TranscribeService{
jobRepo: jobRepo,
fileRepo: fileRepo,
metaviewer: metaviewer,
converter: converter,
recognizer: recognizer,
}
@@ -74,6 +77,12 @@ func (s *TranscribeService) CreateTranscribeJob(file io.Reader, fileName string)
return nil, err
}
info, err := s.metaviewer.GetInfo(storageFilePath)
if err != nil {
return nil, err
}
metrics.InputFileDurationHistogram.WithLabelValues().Observe(float64(info.Seconds))
metrics.InputFileSizeHistogram.WithLabelValues(ext).Observe(float64(size))
// Создаем запись в таблице files

View File

@@ -12,7 +12,8 @@ import (
"syscall"
"time"
"git.vakhrushev.me/av/transcriber/internal/adapter/converter/ffmpeg"
ffmpegconv "git.vakhrushev.me/av/transcriber/internal/adapter/converter/ffmpeg"
ffmpegmv "git.vakhrushev.me/av/transcriber/internal/adapter/metaviewer/ffmpeg"
"git.vakhrushev.me/av/transcriber/internal/adapter/recognizer/yandex"
"git.vakhrushev.me/av/transcriber/internal/adapter/repo/sqlite"
httpcontroller "git.vakhrushev.me/av/transcriber/internal/controller/http"
@@ -62,7 +63,8 @@ func main() {
// Создаем адаптеры
converter := ffmpeg.NewFfmpegConverter()
metaviewer := ffmpegmv.NewFfmpegMetaViewer()
converter := ffmpegconv.NewFfmpegConverter()
recognizer, err := yandex.NewYandexAudioRecognizerService(yandex.YandexAudioRecognizerConfig{
Region: os.Getenv("AWS_REGION"),
@@ -80,7 +82,7 @@ func main() {
// Создаем сервисы
transcribeService := service.NewTranscribeService(jobRepo, fileRepo, converter, recognizer)
transcribeService := service.NewTranscribeService(jobRepo, fileRepo, metaviewer, converter, recognizer)
// Создаем воркеры