Yimaru-BackEnd/internal/services/cloudconvert/service.go

205 lines
5.8 KiB
Go

package cloudconvert
import (
cc "Yimaru-Backend/internal/pkgs/cloudconvert"
"bytes"
"context"
"fmt"
"io"
"time"
"go.uber.org/zap"
)
type Service struct {
client *cc.Client
logger *zap.Logger
}
func NewService(apiKey string, logger *zap.Logger) *Service {
return &Service{
client: cc.NewClient(apiKey),
logger: logger,
}
}
type CompressResult struct {
Data io.ReadCloser
FileSize int64
Filename string
}
type OptimizeImageResult struct {
Data io.ReadCloser
FileSize int64
Filename string
}
func (s *Service) CompressVideo(ctx context.Context, filename string, fileData io.Reader, fileSize int64) (*CompressResult, error) {
s.logger.Info("Creating CloudConvert compression job", zap.String("filename", filename), zap.Int64("original_size", fileSize))
job, err := s.client.CreateVideoCompressionJob(ctx)
if err != nil {
s.logger.Error("Failed to create CloudConvert job", zap.Error(err))
return nil, fmt.Errorf("failed to create compression job: %w", err)
}
var uploadForm *cc.UploadForm
for _, task := range job.Tasks {
if task.Name == "import-video" && task.Result != nil && task.Result.Form != nil {
uploadForm = task.Result.Form
break
}
}
if uploadForm == nil {
return nil, fmt.Errorf("no upload form found in job response")
}
s.logger.Info("Uploading video to CloudConvert", zap.String("job_id", job.ID))
if err := s.client.UploadFile(ctx, uploadForm, filename, fileData); err != nil {
s.logger.Error("Failed to upload file to CloudConvert", zap.Error(err))
return nil, fmt.Errorf("failed to upload file: %w", err)
}
s.logger.Info("Waiting for CloudConvert job to complete", zap.String("job_id", job.ID))
completedJob, err := s.client.WaitForJob(ctx, job.ID, 5*time.Second, 30*time.Minute)
if err != nil {
s.logger.Error("CloudConvert job failed", zap.String("job_id", job.ID), zap.Error(err))
return nil, fmt.Errorf("compression job failed: %w", err)
}
var exportURL string
var exportFilename string
for _, task := range completedJob.Tasks {
if task.Name == "export-video" && task.Result != nil && len(task.Result.Files) > 0 {
exportURL = task.Result.Files[0].URL
exportFilename = task.Result.Files[0].Filename
break
}
}
if exportURL == "" {
return nil, fmt.Errorf("no export URL found in completed job")
}
s.logger.Info("Downloading compressed video from CloudConvert", zap.String("job_id", job.ID), zap.String("filename", exportFilename))
body, contentLength, err := s.client.DownloadFile(ctx, exportURL)
if err != nil {
return nil, fmt.Errorf("failed to download compressed file: %w", err)
}
if contentLength <= 0 {
data, err := io.ReadAll(body)
body.Close()
if err != nil {
return nil, fmt.Errorf("failed to read compressed file: %w", err)
}
contentLength = int64(len(data))
body = io.NopCloser(bytes.NewReader(data))
}
s.logger.Info("Video compression complete",
zap.Int64("original_size", fileSize),
zap.Int64("compressed_size", contentLength),
zap.String("filename", exportFilename),
)
return &CompressResult{
Data: body,
FileSize: contentLength,
Filename: exportFilename,
}, nil
}
func (s *Service) OptimizeImage(ctx context.Context, filename string, fileData io.Reader, fileSize int64, width int, quality int) (*OptimizeImageResult, error) {
s.logger.Info("Creating CloudConvert image optimization job",
zap.String("filename", filename),
zap.Int64("original_size", fileSize),
zap.Int("width", width),
zap.Int("quality", quality),
)
job, err := s.client.CreateImageOptimizationJob(ctx, width, quality)
if err != nil {
s.logger.Error("Failed to create CloudConvert image job", zap.Error(err))
return nil, fmt.Errorf("failed to create image optimization job: %w", err)
}
var uploadForm *cc.UploadForm
for _, task := range job.Tasks {
if task.Name == "import-image" && task.Result != nil && task.Result.Form != nil {
uploadForm = task.Result.Form
break
}
}
if uploadForm == nil {
return nil, fmt.Errorf("no upload form found in image job response")
}
s.logger.Info("Uploading image to CloudConvert", zap.String("job_id", job.ID))
if err := s.client.UploadFile(ctx, uploadForm, filename, fileData); err != nil {
s.logger.Error("Failed to upload image to CloudConvert", zap.Error(err))
return nil, fmt.Errorf("failed to upload image: %w", err)
}
s.logger.Info("Waiting for CloudConvert image job to complete", zap.String("job_id", job.ID))
completedJob, err := s.client.WaitForJob(ctx, job.ID, 3*time.Second, 5*time.Minute)
if err != nil {
s.logger.Error("CloudConvert image job failed", zap.String("job_id", job.ID), zap.Error(err))
return nil, fmt.Errorf("image optimization job failed: %w", err)
}
var exportURL string
var exportFilename string
for _, task := range completedJob.Tasks {
if task.Name == "export-image" && task.Result != nil && len(task.Result.Files) > 0 {
exportURL = task.Result.Files[0].URL
exportFilename = task.Result.Files[0].Filename
break
}
}
if exportURL == "" {
return nil, fmt.Errorf("no export URL found in completed image job")
}
s.logger.Info("Downloading optimized image from CloudConvert",
zap.String("job_id", job.ID),
zap.String("filename", exportFilename),
)
body, contentLength, err := s.client.DownloadFile(ctx, exportURL)
if err != nil {
return nil, fmt.Errorf("failed to download optimized image: %w", err)
}
if contentLength <= 0 {
data, err := io.ReadAll(body)
body.Close()
if err != nil {
return nil, fmt.Errorf("failed to read optimized image: %w", err)
}
contentLength = int64(len(data))
body = io.NopCloser(bytes.NewReader(data))
}
s.logger.Info("Image optimization complete",
zap.Int64("original_size", fileSize),
zap.Int64("optimized_size", contentLength),
zap.String("filename", exportFilename),
)
return &OptimizeImageResult{
Data: body,
FileSize: contentLength,
Filename: exportFilename,
}, nil
}