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
+5 -12
View File
@@ -2,7 +2,6 @@ package photos
import (
"errors"
"io"
"net/http"
"strconv"
"time"
@@ -13,8 +12,6 @@ import (
"galo/internal/httpx"
)
const maxUploadSize = 100 << 20
type Handler struct {
svc *Service
}
@@ -41,12 +38,12 @@ func (h *Handler) List(c *gin.Context) {
}
func (h *Handler) Upload(c *gin.Context) {
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, maxUploadSize)
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, h.svc.MaxUploadSize())
form, err := c.MultipartForm()
if err != nil {
var maxErr *http.MaxBytesError
if errors.As(err, &maxErr) {
httpx.RespondError(c, httpx.NewError(http.StatusRequestEntityTooLarge, "file_too_large", "files exceed the maximum upload size (100 MB)"))
httpx.RespondError(c, httpx.NewError(http.StatusRequestEntityTooLarge, "file_too_large", "files exceed the maximum upload size"))
return
}
httpx.RespondError(c, httpx.BadRequest("invalid_request", "invalid multipart form"))
@@ -68,19 +65,15 @@ func (h *Handler) Upload(c *gin.Context) {
httpx.RespondError(c, err)
return
}
data, err := io.ReadAll(io.LimitReader(f, maxUploadSize))
f.Close()
if err != nil {
httpx.RespondError(c, err)
return
}
p, err := h.svc.Upload(c.Request.Context(), UploadInput{
AlbumID: c.Param("id"),
UserID: auth.UserID(c),
Filename: fh.Filename,
MimeType: fh.Header.Get("Content-Type"),
Data: data,
Reader: f,
Size: fh.Size,
})
f.Close()
if err != nil {
httpx.RespondError(c, err)
return