feat(backend): add video scene preview generation and endpoint
All checks were successful
Build and Publish Docker Image / deploy (push) Successful in 1m52s

- Register a new route `GET /d/{boxID}/scene/{fileID}` to serve video scene previews.
- Implement the `VideoScenesPreview` handler to serve existing previews or generate them on-demand.
- Add helper functions to analyze video frames (e.g., luma calculation to filter out dark frames) and render the final scene thumbnail.
- Update the `fileView` struct to include scene URL and status fields.
This commit is contained in:
2026-06-05 10:42:30 +03:00
parent 2eba04b9da
commit f9755fa98f
9 changed files with 552 additions and 32 deletions

View File

@@ -121,18 +121,20 @@ type Box struct {
}
type File struct {
ID string `json:"id"`
Name string `json:"name"`
StoredName string `json:"storedName"`
Size int64 `json:"size"`
ContentType string `json:"contentType"`
PreviewKind string `json:"previewKind"`
Thumbnail string `json:"thumbnail,omitempty"`
ObjectKey string `json:"objectKey,omitempty"`
ThumbnailObjectKey string `json:"thumbnailObjectKey,omitempty"`
Processing bool `json:"processing,omitempty"`
ProcessingError string `json:"processingError,omitempty"`
UploadedAt time.Time `json:"uploadedAt"`
ID string `json:"id"`
Name string `json:"name"`
StoredName string `json:"storedName"`
Size int64 `json:"size"`
ContentType string `json:"contentType"`
PreviewKind string `json:"previewKind"`
Thumbnail string `json:"thumbnail,omitempty"`
SceneThumbnail string `json:"sceneThumbnail,omitempty"`
ObjectKey string `json:"objectKey,omitempty"`
ThumbnailObjectKey string `json:"thumbnailObjectKey,omitempty"`
SceneThumbnailObjectKey string `json:"sceneThumbnailObjectKey,omitempty"`
Processing bool `json:"processing,omitempty"`
ProcessingError string `json:"processingError,omitempty"`
UploadedAt time.Time `json:"uploadedAt"`
}
type UploadResult struct {
@@ -731,6 +733,9 @@ func (s *UploadService) RemoveFileFromBox(boxID, fileID string) (bool, error) {
if key := s.ThumbnailObjectKey(box, file); key != "" {
_ = backend.Delete(context.Background(), key)
}
if key := s.SceneThumbnailObjectKey(box, file); key != "" {
_ = backend.Delete(context.Background(), key)
}
}
box.Files = append(box.Files[:index], box.Files[index+1:]...)
@@ -818,6 +823,16 @@ func (s *UploadService) ThumbnailObjectKey(box Box, file File) string {
return boxObjectKey(box.ID, file.Thumbnail)
}
func (s *UploadService) SceneThumbnailObjectKey(box Box, file File) string {
if file.SceneThumbnailObjectKey != "" {
return file.SceneThumbnailObjectKey
}
if file.SceneThumbnail == "" {
return ""
}
return boxObjectKey(box.ID, file.SceneThumbnail)
}
func (s *UploadService) OpenFileObject(ctx context.Context, box Box, file File) (StorageObject, error) {
if file.Processing {
return StorageObject{}, fmt.Errorf("file is still processing")
@@ -841,6 +856,18 @@ func (s *UploadService) OpenThumbnailObject(ctx context.Context, box Box, file F
return backend.Get(ctx, key)
}
func (s *UploadService) OpenSceneThumbnailObject(ctx context.Context, box Box, file File) (StorageObject, error) {
key := s.SceneThumbnailObjectKey(box, file)
if key == "" {
return StorageObject{}, os.ErrNotExist
}
backend, err := s.storage.Backend(s.BoxStorageBackendID(box))
if err != nil {
return StorageObject{}, err
}
return backend.Get(ctx, key)
}
func (s *UploadService) PutThumbnailObject(ctx context.Context, box Box, name string, body io.Reader, size int64, contentType string) (string, error) {
backend, err := s.storage.Backend(s.BoxStorageBackendID(box))
if err != nil {