package media import ( "bytes" "context" "errors" "fmt" "io" "net/http" "git.netflux.io/rob/clipper/generated/store" "github.com/google/uuid" ) type VideoThumbnail struct { Data []byte Width, Height int } func (s *MediaSetService) GetVideoThumbnail(ctx context.Context, id uuid.UUID) (VideoThumbnail, error) { mediaSet, err := s.store.GetMediaSet(ctx, id) if err != nil { return VideoThumbnail{}, fmt.Errorf("error getting media set: %v", err) } if mediaSet.VideoThumbnailS3UploadedAt.Valid { return s.getThumbnailFromFileStore(ctx, mediaSet) } return s.getThumbnailFromYoutube(ctx, mediaSet) } func (s *MediaSetService) getThumbnailFromFileStore(ctx context.Context, mediaSet store.MediaSet) (VideoThumbnail, error) { object, err := s.fileStore.GetObject(ctx, mediaSet.VideoThumbnailS3Key.String) if err != nil { return VideoThumbnail{}, fmt.Errorf("error fetching thumbnail from file store: %v", err) } defer object.Close() imageData, err := io.ReadAll(object) if err != nil { return VideoThumbnail{}, fmt.Errorf("error reading thumbnail from file store: %v", err) } return VideoThumbnail{ Width: int(mediaSet.VideoThumbnailWidth.Int32), Height: int(mediaSet.VideoThumbnailHeight.Int32), Data: imageData, }, nil } func (s *MediaSetService) getThumbnailFromYoutube(ctx context.Context, mediaSet store.MediaSet) (VideoThumbnail, error) { video, err := s.youtube.GetVideoContext(ctx, mediaSet.YoutubeID) if err != nil { return VideoThumbnail{}, fmt.Errorf("error fetching video: %v", err) } if len(video.Formats) == 0 { return VideoThumbnail{}, errors.New("no format available") } thumbnails := video.Thumbnails SortYoutubeThumbnails(thumbnails) thumbnail := thumbnails[0] resp, err := http.Get(thumbnail.URL) if err != nil { return VideoThumbnail{}, fmt.Errorf("error fetching thumbnail: %v", err) } defer resp.Body.Close() imageData, err := io.ReadAll(resp.Body) if err != nil { return VideoThumbnail{}, fmt.Errorf("error reading thumbnail: %v", err) } // TODO: use mediaSet func to fetch key thumbnailKey := fmt.Sprintf("media_sets/%s/thumbnail.jpg", mediaSet.ID) const mimeType = "application/jpeg" _, err = s.fileStore.PutObject(ctx, thumbnailKey, bytes.NewReader(imageData), mimeType) if err != nil { return VideoThumbnail{}, fmt.Errorf("error uploading thumbnail: %v", err) } storeParams := store.SetVideoThumbnailUploadedParams{ ID: mediaSet.ID, VideoThumbnailMimeType: sqlString(mimeType), VideoThumbnailS3Key: sqlString(thumbnailKey), VideoThumbnailWidth: sqlInt32(int32(thumbnail.Width)), VideoThumbnailHeight: sqlInt32(int32(thumbnail.Height)), } if _, err := s.store.SetVideoThumbnailUploaded(ctx, storeParams); err != nil { return VideoThumbnail{}, fmt.Errorf("error updating media set: %v", err) } return VideoThumbnail{Width: int(thumbnail.Width), Height: int(thumbnail.Height), Data: imageData}, nil }