Yimaru-BackEnd/internal/services/course_management/sub_course_videos.go

300 lines
8.0 KiB
Go

package course_management
import (
"Yimaru-Backend/internal/domain"
"Yimaru-Backend/internal/pkgs/vimeo"
vimeoservice "Yimaru-Backend/internal/services/vimeo"
"context"
"fmt"
"io"
"net/http"
"path"
"time"
)
func (s *Service) CreateSubCourseVideo(
ctx context.Context,
subCourseID int64,
title string,
description *string,
videoURL string,
duration int32,
resolution *string,
instructorID *string,
thumbnail *string,
visibility *string,
displayOrder *int32,
status *string,
) (domain.SubCourseVideo, error) {
// Default to DIRECT provider when no Vimeo info provided
provider := string(domain.VideoHostProviderDirect)
return s.courseStore.CreateSubCourseVideo(
ctx, subCourseID, title, description, videoURL, duration,
resolution, instructorID, thumbnail, visibility, displayOrder, status,
nil, nil, nil, nil, &provider,
)
}
// CreateSubCourseVideoWithVimeo creates a video and uploads it to Vimeo
func (s *Service) CreateSubCourseVideoWithVimeo(
ctx context.Context,
subCourseID int64,
title string,
description *string,
sourceURL string,
fileSize int64,
duration int32,
resolution *string,
instructorID *string,
thumbnail *string,
visibility *string,
displayOrder *int32,
) (domain.SubCourseVideo, error) {
if s.vimeoSvc == nil {
return domain.SubCourseVideo{}, fmt.Errorf("vimeo service is not configured")
}
descStr := ""
if description != nil {
descStr = *description
}
var uploadResult *vimeoservice.UploadResult
var err error
if s.cloudConvertSvc != nil {
httpClient := &http.Client{Timeout: 30 * time.Minute}
req, reqErr := http.NewRequestWithContext(ctx, http.MethodGet, sourceURL, nil)
if reqErr != nil {
return domain.SubCourseVideo{}, fmt.Errorf("failed to create download request: %w", reqErr)
}
resp, dlErr := httpClient.Do(req)
if dlErr != nil {
return domain.SubCourseVideo{}, fmt.Errorf("failed to download source video: %w", dlErr)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return domain.SubCourseVideo{}, fmt.Errorf("failed to download source video: status %d", resp.StatusCode)
}
dlSize := resp.ContentLength
if dlSize <= 0 {
dlSize = fileSize
}
filename := path.Base(sourceURL)
if filename == "" || filename == "." || filename == "/" {
filename = "video.mp4"
}
result, compErr := s.cloudConvertSvc.CompressVideo(ctx, filename, resp.Body, dlSize)
if compErr != nil {
return domain.SubCourseVideo{}, fmt.Errorf("failed to compress video: %w", compErr)
}
defer result.Data.Close()
uploadResult, err = s.vimeoSvc.UploadVideoFile(ctx, title, descStr, result.Data, result.FileSize)
} else {
uploadResult, err = s.vimeoSvc.CreatePullUpload(ctx, title, descStr, sourceURL, fileSize)
}
if err != nil {
return domain.SubCourseVideo{}, fmt.Errorf("failed to upload to Vimeo: %w", err)
}
embedURL := vimeo.GenerateEmbedURL(uploadResult.VimeoID, &vimeo.EmbedOptions{
Title: true,
Byline: true,
Portrait: true,
})
embedHTML := vimeo.GenerateIframeEmbed(uploadResult.VimeoID, 640, 360, nil)
provider := string(domain.VideoHostProviderVimeo)
vimeoStatus := "uploading"
status := "DRAFT"
return s.courseStore.CreateSubCourseVideo(
ctx, subCourseID, title, description,
uploadResult.Link,
duration, resolution, instructorID, thumbnail, visibility, displayOrder, &status,
&uploadResult.VimeoID, &embedURL, &embedHTML, &vimeoStatus, &provider,
)
}
func (s *Service) CreateSubCourseVideoWithFileUpload(
ctx context.Context,
subCourseID int64,
title string,
description *string,
filename string,
fileData io.Reader,
fileSize int64,
duration int32,
resolution *string,
instructorID *string,
thumbnail *string,
visibility *string,
displayOrder *int32,
) (domain.SubCourseVideo, error) {
if s.vimeoSvc == nil {
return domain.SubCourseVideo{}, fmt.Errorf("vimeo service is not configured")
}
descStr := ""
if description != nil {
descStr = *description
}
videoReader := fileData
videoSize := fileSize
if s.cloudConvertSvc != nil {
result, err := s.cloudConvertSvc.CompressVideo(ctx, filename, fileData, fileSize)
if err != nil {
return domain.SubCourseVideo{}, fmt.Errorf("failed to compress video: %w", err)
}
defer result.Data.Close()
videoReader = result.Data
videoSize = result.FileSize
}
uploadResult, err := s.vimeoSvc.UploadVideoFile(ctx, title, descStr, videoReader, videoSize)
if err != nil {
return domain.SubCourseVideo{}, fmt.Errorf("failed to upload video file to Vimeo: %w", err)
}
embedURL := vimeo.GenerateEmbedURL(uploadResult.VimeoID, &vimeo.EmbedOptions{
Title: true,
Byline: true,
Portrait: true,
})
embedHTML := vimeo.GenerateIframeEmbed(uploadResult.VimeoID, 640, 360, nil)
provider := string(domain.VideoHostProviderVimeo)
vimeoStatus := "uploading"
status := "DRAFT"
return s.courseStore.CreateSubCourseVideo(
ctx, subCourseID, title, description,
uploadResult.Link,
duration, resolution, instructorID, thumbnail, visibility, displayOrder, &status,
&uploadResult.VimeoID, &embedURL, &embedHTML, &vimeoStatus, &provider,
)
}
// CreateSubCourseVideoFromVimeoID creates a video record from an existing Vimeo video
func (s *Service) CreateSubCourseVideoFromVimeoID(
ctx context.Context,
subCourseID int64,
vimeoVideoID string,
title string,
description *string,
displayOrder *int32,
instructorID *string,
) (domain.SubCourseVideo, error) {
if s.vimeoSvc == nil {
return domain.SubCourseVideo{}, fmt.Errorf("vimeo service is not configured")
}
// Fetch video info from Vimeo
info, err := s.vimeoSvc.GetVideoInfo(ctx, vimeoVideoID)
if err != nil {
return domain.SubCourseVideo{}, fmt.Errorf("failed to get Vimeo video info: %w", err)
}
// Use Vimeo data
embedHTML := vimeo.GenerateIframeEmbed(vimeoVideoID, 640, 360, nil)
provider := string(domain.VideoHostProviderVimeo)
vimeoStatus := info.TranscodeStatus
if vimeoStatus == "" {
vimeoStatus = "available"
}
status := "DRAFT"
duration := int32(info.Duration)
resolution := fmt.Sprintf("%dx%d", info.Width, info.Height)
thumbnail := info.ThumbnailURL
return s.courseStore.CreateSubCourseVideo(
ctx, subCourseID, title, description,
info.Link, duration, &resolution, instructorID, &thumbnail, nil, displayOrder, &status,
&vimeoVideoID, &info.EmbedURL, &embedHTML, &vimeoStatus, &provider,
)
}
func (s *Service) GetSubCourseVideoByID(
ctx context.Context,
id int64,
) (domain.SubCourseVideo, error) {
return s.courseStore.GetSubCourseVideoByID(ctx, id)
}
func (s *Service) GetVideosBySubCourse(
ctx context.Context,
subCourseID int64,
) ([]domain.SubCourseVideo, int64, error) {
return s.courseStore.GetVideosBySubCourse(ctx, subCourseID)
}
func (s *Service) GetPublishedVideosBySubCourse(
ctx context.Context,
subCourseID int64,
) ([]domain.SubCourseVideo, error) {
return s.courseStore.GetPublishedVideosBySubCourse(ctx, subCourseID)
}
func (s *Service) GetFirstIncompletePreviousVideo(
ctx context.Context,
userID int64,
videoID int64,
) (*domain.VideoAccessBlock, error) {
return s.courseStore.GetFirstIncompletePreviousVideo(ctx, userID, videoID)
}
func (s *Service) MarkVideoCompleted(
ctx context.Context,
userID int64,
videoID int64,
) error {
return s.courseStore.MarkVideoCompleted(ctx, userID, videoID)
}
func (s *Service) PublishSubCourseVideo(
ctx context.Context,
videoID int64,
) error {
return s.courseStore.PublishSubCourseVideo(ctx, videoID)
}
func (s *Service) UpdateSubCourseVideo(
ctx context.Context,
id int64,
title *string,
description *string,
videoURL *string,
duration *int32,
resolution *string,
visibility *string,
thumbnail *string,
displayOrder *int32,
status *string,
) error {
return s.courseStore.UpdateSubCourseVideo(ctx, id, title, description, videoURL, duration, resolution, visibility, thumbnail, displayOrder, status)
}
func (s *Service) ArchiveSubCourseVideo(
ctx context.Context,
id int64,
) error {
return s.courseStore.ArchiveSubCourseVideo(ctx, id)
}
func (s *Service) DeleteSubCourseVideo(
ctx context.Context,
id int64,
) error {
return s.courseStore.DeleteSubCourseVideo(ctx, id)
}