Files
transcriber/internal/controller/http/transcribe.go

254 lines
6.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package http
import (
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"time"
"git.vakhrushev.me/av/transcriber/internal/entity"
"git.vakhrushev.me/av/transcriber/internal/repo"
"git.vakhrushev.me/av/transcriber/internal/repo/ffmpeg"
"git.vakhrushev.me/av/transcriber/internal/service/s3"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
type TranscribeHandler struct {
jobRepo repo.TranscriptJobRepository
fileRepo repo.FileRepository
}
func NewTranscribeHandler(jobRepo repo.TranscriptJobRepository, fileRepo repo.FileRepository) *TranscribeHandler {
return &TranscribeHandler{jobRepo: jobRepo, fileRepo: fileRepo}
}
type CreateTranscribeJobResponse struct {
JobID string `json:"job_id"`
State string `json:"status"`
}
type GetTranscribeJobResponse struct {
JobID string `json:"job_id"`
State string `json:"status"`
CreatedAt time.Time `json:"created_at"`
}
func (h *TranscribeHandler) CreateTranscribeJob(c *gin.Context) {
// Получаем файл из формы
file, header, err := c.Request.FormFile("audio")
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "No audio file provided"})
return
}
defer file.Close()
// Генерируем UUID для файла
fileId := uuid.New().String()
// Определяем расширение файла
ext := filepath.Ext(header.Filename)
if ext == "" {
ext = ".audio" // fallback если расширение не определено
}
// Создаем путь для сохранения файла
fileName := fmt.Sprintf("%s%s", fileId, ext)
filePath := filepath.Join("data", "files", fileName)
// Создаем файл на диске
dst, err := os.Create(filePath)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create file"})
return
}
defer dst.Close()
// Копируем содержимое загруженного файла
size, err := io.Copy(dst, file)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save file"})
return
}
// Создаем запись в таблице files
fileRecord := &entity.File{
Id: fileId,
Storage: entity.StorageLocal,
FileName: fileName,
Size: size,
CreatedAt: time.Now(),
}
if err := h.fileRepo.Create(fileRecord); err != nil {
// Удаляем файл если не удалось создать запись в БД
os.Remove(filePath)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save file record"})
return
}
// Создаем запись в таблице transcribe_jobs
jobId := uuid.New().String()
job := &entity.TranscribeJob{
Id: jobId,
State: entity.StateCreated,
FileID: &fileId,
IsError: false,
CreatedAt: time.Now(),
}
if err := h.jobRepo.Create(job); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create transcribe job"})
return
}
// Возвращаем успешный ответ
response := CreateTranscribeJobResponse{
JobID: job.Id,
State: job.State,
}
c.JSON(http.StatusCreated, response)
}
func (h *TranscribeHandler) GetTranscribeJobStatus(c *gin.Context) {
jobID := c.Param("id")
job, err := h.jobRepo.GetByID(jobID)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Job not found"})
return
}
c.JSON(http.StatusOK, GetTranscribeJobResponse{
JobID: job.Id,
State: job.State,
CreatedAt: job.CreatedAt,
})
}
func (h *TranscribeHandler) RunConversionJob(c *gin.Context) {
acquisitionId := uuid.NewString()
rottingTime := time.Now().Add(-1 * time.Hour)
job, err := h.jobRepo.FindAndAcquire(entity.StateCreated, acquisitionId, rottingTime)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
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()})
return
}
c.Status(http.StatusOK)
}
func (h *TranscribeHandler) RunUploadJob(c *gin.Context) {
acquisitionId := uuid.NewString()
rottingTime := time.Now().Add(-1 * time.Hour)
job, err := h.jobRepo.FindAndAcquire(entity.StateConverted, acquisitionId, rottingTime)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
fileRecord, err := h.fileRepo.GetByID(*job.FileID)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
filePath := filepath.Join("data", "files", fileRecord.FileName)
destFileId := uuid.New().String()
destFileRecord := &entity.File{
Id: destFileId,
Storage: entity.StorageS3,
FileName: fileRecord.FileName,
Size: fileRecord.Size,
CreatedAt: time.Now(),
}
// Создаем S3 сервис
s3Service, err := s3.NewS3Service()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to initialize S3 service: " + err.Error()})
return
}
// Загружаем файл на S3
err = s3Service.UploadFile(filePath, destFileRecord.FileName)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to upload file to S3: " + err.Error()})
return
}
job.FileID = &destFileId
job.MoveToState(entity.StateTranscribeReady)
// Сохраняем информацию о загрузке файла на S3
err = h.fileRepo.Create(destFileRecord)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update file record: " + err.Error()})
return
}
// Обновляем состояние задачи
err = h.jobRepo.Save(job)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update job state: " + err.Error()})
return
}
c.Status(http.StatusOK)
}