From c0d55c20884bd3adfe0b0b7919cfaa4a0f32c071 Mon Sep 17 00:00:00 2001 From: Anton Vakhrushev Date: Mon, 11 Aug 2025 10:11:13 +0300 Subject: [PATCH] Add ffmpeg converter to ogg format --- internal/controller/http/transcribe.go | 44 +++++++++++++++++ internal/entity/file.go | 1 + internal/repo/contracts.go | 4 ++ internal/repo/ffmpeg/conv.go | 47 +++++++++++++++++++ .../sqlite/{file_sqlite.go => file_repo.go} | 5 +- ...t_job_sqlite.go => transcript_job_repo.go} | 0 migrations/001_create_files_table.sql | 1 + 7 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 internal/repo/ffmpeg/conv.go rename internal/repo/sqlite/{file_sqlite.go => file_repo.go} (85%) rename internal/repo/sqlite/{transcript_job_sqlite.go => transcript_job_repo.go} (100%) diff --git a/internal/controller/http/transcribe.go b/internal/controller/http/transcribe.go index 93e4cde..65ef2d3 100644 --- a/internal/controller/http/transcribe.go +++ b/internal/controller/http/transcribe.go @@ -10,6 +10,7 @@ import ( "git.vakhrushev.me/av/transcriber/internal/entity" "git.vakhrushev.me/av/transcriber/internal/repo" + "git.vakhrushev.me/av/transcriber/internal/repo/ffmpeg" "github.com/gin-gonic/gin" "github.com/google/uuid" ) @@ -75,6 +76,7 @@ func (h *TranscribeHandler) CreateTranscribeJob(c *gin.Context) { fileRecord := &entity.File{ Id: fileId, Storage: entity.StorageLocal, + FileName: fileName, Size: size, CreatedAt: time.Now(), } @@ -136,8 +138,50 @@ func (h *TranscribeHandler) RunConversionJob(c *gin.Context) { return } + srcFile, err := h.fileRepo.GetByID(*job.FileID) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + srcFilePath := filepath.Join("data", "files", srcFile.FileName) + + destFileId := uuid.New().String() + + destFileName := fmt.Sprintf("%s%s", destFileId, ".ogg") + destFilePath := filepath.Join("data", "files", destFileName) + + conv := ffmpeg.NewFileConverter() + err = conv.Convert(srcFilePath, destFilePath) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + stat, err := os.Stat(destFilePath) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + // Создаем запись в таблице files + destFileRecord := &entity.File{ + Id: destFileId, + Storage: entity.StorageLocal, + FileName: destFileName, + Size: stat.Size(), + CreatedAt: time.Now(), + } + + job.FileID = &destFileId job.MoveToState(entity.StateConverted) + err = h.fileRepo.Create(destFileRecord) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + err = h.jobRepo.Save(job) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) diff --git a/internal/entity/file.go b/internal/entity/file.go index 41a3d1c..a329e93 100644 --- a/internal/entity/file.go +++ b/internal/entity/file.go @@ -12,6 +12,7 @@ const ( type File struct { Id string Storage string + FileName string Size int64 CreatedAt time.Time } diff --git a/internal/repo/contracts.go b/internal/repo/contracts.go index 5666afc..e0c6839 100644 --- a/internal/repo/contracts.go +++ b/internal/repo/contracts.go @@ -20,3 +20,7 @@ type TranscriptJobRepository interface { type ObjectStorage interface { } + +type FileConverter interface { + Convert(src, dest string) error +} diff --git a/internal/repo/ffmpeg/conv.go b/internal/repo/ffmpeg/conv.go new file mode 100644 index 0000000..b24d40b --- /dev/null +++ b/internal/repo/ffmpeg/conv.go @@ -0,0 +1,47 @@ +package ffmpeg + +import ( + "fmt" + "os" + "os/exec" +) + +type FileConverter struct { +} + +func NewFileConverter() *FileConverter { + return &FileConverter{} +} + +func (c *FileConverter) Convert(src, dest string) error { + // Проверяем существование исходного файла + if _, err := os.Stat(src); os.IsNotExist(err) { + return fmt.Errorf("input file does not exist: %s", src) + } + + // Проверяем, что ffmpeg доступен в системе + if _, err := exec.LookPath("ffmpeg"); err != nil { + return fmt.Errorf("ffmpeg not found in PATH: %w", err) + } + + // Создаем команду ffmpeg для конвертации в OGG + cmd := exec.Command("ffmpeg", + "-i", src, // входной файл + "-c:a", "libvorbis", // кодек Vorbis для OGG + "-q:a", "4", // качество аудио (0-10, где 4 - хорошее качество) + "-y", // перезаписать выходной файл если существует + dest, // выходной файл + ) + + // Выполняем команду + if err := cmd.Run(); err != nil { + return fmt.Errorf("ffmpeg conversion failed: %w", err) + } + + // Проверяем, что выходной файл был создан + if _, err := os.Stat(dest); os.IsNotExist(err) { + return fmt.Errorf("output file was not created: %s", dest) + } + + return nil +} diff --git a/internal/repo/sqlite/file_sqlite.go b/internal/repo/sqlite/file_repo.go similarity index 85% rename from internal/repo/sqlite/file_sqlite.go rename to internal/repo/sqlite/file_repo.go index dd792d7..0727cdb 100644 --- a/internal/repo/sqlite/file_sqlite.go +++ b/internal/repo/sqlite/file_repo.go @@ -21,6 +21,7 @@ func (repo *FileRepository) Create(file *entity.File) error { record := goqu.Record{ "id": file.Id, "storage": file.Storage, + "file_name": file.FileName, "size": file.Size, "created_at": file.CreatedAt, } @@ -39,14 +40,14 @@ func (repo *FileRepository) Create(file *entity.File) error { } func (repo *FileRepository) GetByID(id string) (*entity.File, error) { - query := repo.gq.From("files").Select("id", "storage", "size", "created_at").Where(goqu.C("id").Eq(id)) + query := repo.gq.From("files").Select("id", "storage", "file_name", "size", "created_at").Where(goqu.C("id").Eq(id)) sql, args, err := query.ToSQL() if err != nil { return nil, fmt.Errorf("failed to build query: %w", err) } var file entity.File - err = repo.db.QueryRow(sql, args...).Scan(&file.Id, &file.Storage, &file.Size, &file.CreatedAt) + err = repo.db.QueryRow(sql, args...).Scan(&file.Id, &file.Storage, &file.FileName, &file.Size, &file.CreatedAt) if err != nil { return nil, fmt.Errorf("failed to get file: %w", err) } diff --git a/internal/repo/sqlite/transcript_job_sqlite.go b/internal/repo/sqlite/transcript_job_repo.go similarity index 100% rename from internal/repo/sqlite/transcript_job_sqlite.go rename to internal/repo/sqlite/transcript_job_repo.go diff --git a/migrations/001_create_files_table.sql b/migrations/001_create_files_table.sql index 335818b..d6efc2b 100644 --- a/migrations/001_create_files_table.sql +++ b/migrations/001_create_files_table.sql @@ -2,6 +2,7 @@ CREATE TABLE files ( id TEXT PRIMARY KEY, storage TEXT NOT NULL, + file_name TEXT NOT NULL, size INTEGER NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP );