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
+21
View File
@@ -5,6 +5,7 @@ import (
"encoding/json"
"net/http"
"net/http/httptest"
"os"
"testing"
"time"
@@ -12,6 +13,7 @@ import (
"galo/internal/config"
"galo/internal/storage"
"galo/internal/video"
)
func mustSigner(t *testing.T) *storage.Signer {
@@ -58,4 +60,23 @@ func TestHealthStorageLabel(t *testing.T) {
if body := call(); body["s3"] != "ok" {
t.Fatalf("expected default s3 label, got %v", body)
}
// A missing toolchain reports video unavailable without degrading the service.
if body := call(); body["video"] != "unavailable" {
t.Fatalf("expected video unavailable, got %v", body)
}
if body := call(); body["status"] != "ok" {
t.Fatalf("video unavailability must not degrade status, got %v", body)
}
// A resolvable toolchain reports video ok. The test binary is a known
// executable on every platform.
self, err := os.Executable()
if err != nil {
t.Fatalf("executable: %v", err)
}
srv.videoTools = video.Detect(config.Video{FFmpegPath: self, FFprobePath: self})
if body := call(); body["video"] != "ok" {
t.Fatalf("expected video ok, got %v", body)
}
}