feat(video): add video upload with remux/transcode playback

Add video as a media kind in photos: streaming upload with per-kind
limits, ffprobe metadata, poster frame reusing the image variant
pipeline, and a playback object (remux when web-compatible, else
h264/yuv420p/aac transcode). Video jobs run on a separate pool with a
long lease. Frontend gains a video viewer, grid badge/duration and
video upload.
This commit is contained in:
iu
2026-10-11 20:37:53 +03:00
parent d059d042f0
commit d38ede0201
52 changed files with 2717 additions and 82 deletions
+17 -1
View File
@@ -24,6 +24,7 @@ import (
"galo/internal/sse"
"galo/internal/storage"
"galo/internal/store"
"galo/internal/video"
)
type Server struct {
@@ -42,6 +43,8 @@ type Server struct {
photos *photos.Service
worker *photos.Worker
videoTools *video.Toolchain
social *social.Service
notifications *notifications.Service
@@ -62,6 +65,12 @@ func New(cfg *config.Config, repos store.Repos, ping func(ctx context.Context) e
photosSvc := photos.NewService(repos.Photos, albumsSvc, st, hub)
photosWorker := photos.NewWorker(photosSvc, cfg.Worker.Interval, cfg.Worker.Batch, cfg.Worker.PendingTTL, cfg.Worker.Concurrency)
videoTools := video.Detect(cfg.Video)
photosSvc.SetUploadLimits(cfg.Upload.ImageMaxBytes, cfg.Upload.VideoMaxBytes)
photosSvc.SetVideoEnabled(videoTools.Available())
photosSvc.SetTempDir(cfg.Video.TempDir)
photosWorker.SetVideo(videoTools, cfg.Video.TranscodeConcurrency)
socialSvc := social.NewService(repos.Social, albumsSvc, repos.Photos, hub, notifSvc)
shareSvc := sharelinks.NewService(repos.ShareLinks, albumsSvc)
@@ -77,6 +86,7 @@ func New(cfg *config.Config, repos store.Repos, ping func(ctx context.Context) e
membersSvc: membersSvc,
photos: photosSvc,
worker: photosWorker,
videoTools: videoTools,
social: socialSvc,
notifications: notifSvc,
share: shareSvc,
@@ -188,13 +198,19 @@ func (s *Server) health(c *gin.Context) {
if err := s.storage.Ping(ctx); err != nil {
storeStatus = "error"
}
// Video tooling is optional: its absence does not degrade the service, but
// it is reported so operators can see why video upload is disabled.
videoStatus := "ok"
if !s.videoTools.Available() {
videoStatus = "unavailable"
}
status := http.StatusOK
overall := "ok"
if dbStatus != "ok" || storeStatus != "ok" {
status = http.StatusServiceUnavailable
overall = "degraded"
}
c.JSON(status, gin.H{"status": overall, "db": dbStatus, storageLabel: storeStatus})
c.JSON(status, gin.H{"status": overall, "db": dbStatus, storageLabel: storeStatus, "video": videoStatus})
}
func (s *Server) serveObject(c *gin.Context) {