Stream post videos to Spaces.
CI / test (pull_request) Successful in 6m31s

Pass the spooled video file through instead of buffering it in RAM, and sign Spaces uploads as UNSIGNED-PAYLOAD so the client does not hash the body first.
This commit is contained in:
2026-08-30 23:31:32 -07:00
parent a6e414853d
commit 1b002a6409
4 changed files with 79 additions and 27 deletions
+38 -7
View File
@@ -60,20 +60,34 @@ func TestPreparePostImage(t *testing.T) {
func TestPreparePostVideo(t *testing.T) {
t.Parallel()
prepared, err := preparePostVideoHeader(t, "clip.mp4", tinyMP4())
mp4 := tinyMP4()
prepared, err := preparePostVideoHeader(t, "clip.mp4", mp4)
if err != nil {
t.Fatal(err)
}
if prepared.extension != ".mp4" || prepared.contentType != "video/mp4" || len(prepared.body) == 0 {
t.Fatalf("prepared MP4 = %+v", prepared)
}
prepared, err = preparePostVideoHeader(t, "clip.webm", tinyWebM())
defer prepared.body.Close()
got, err := io.ReadAll(prepared.body)
if err != nil {
t.Fatal(err)
}
if prepared.extension != ".webm" || prepared.contentType != "video/webm" {
if prepared.extension != ".mp4" || prepared.contentType != "video/mp4" ||
prepared.size != int64(len(mp4)) || !bytes.Equal(got, mp4) {
t.Fatalf("prepared MP4 = %+v len(body)=%d", prepared, len(got))
}
webm := tinyWebM()
prepared, err = preparePostVideoHeader(t, "clip.webm", webm)
if err != nil {
t.Fatal(err)
}
defer prepared.body.Close()
if prepared.extension != ".webm" || prepared.contentType != "video/webm" ||
prepared.size != int64(len(webm)) {
t.Fatalf("prepared WebM = %+v", prepared)
}
if _, err := preparePostVideoHeader(t, "empty.mp4", nil); err == nil {
t.Fatal("empty video unexpectedly succeeded")
}
if _, err := preparePostVideoHeader(t, "notes.txt", []byte("not a video")); err == nil {
t.Fatal("text video upload unexpectedly succeeded")
}
@@ -269,6 +283,17 @@ func TestPostVideoMultipartLifecycle(t *testing.T) {
!strings.HasPrefix(root.Images[1].ObjectKey, "post-videos/") {
t.Fatalf("root media = %+v", root.Images)
}
clip := tinyMP4()
var streamed recordedImageUpload
for _, upload := range blobs.recordedUploads() {
if strings.HasPrefix(upload.key, "post-videos/") {
streamed = upload
break
}
}
if streamed.size != int64(len(clip)) || !bytes.Equal(streamed.body, clip) {
t.Fatalf("streamed video upload = %+v", streamed)
}
rec = multipartPost(t, handler, "/posts/"+root.ID+"/edit", map[string][]string{
"_csrf": {csrf},
@@ -513,6 +538,7 @@ func solidJPEG(t *testing.T, width, height int) []byte {
type recordedImageUpload struct {
key string
contentType string
size int64
body []byte
}
@@ -540,6 +566,7 @@ func (b *recordingImageBlob) Upload(_ context.Context, object blob.FileUpload) (
b.uploads = append(b.uploads, recordedImageUpload{
key: object.Key,
contentType: object.ContentType,
size: object.Size,
body: body,
})
return "https://cdn.example/" + object.Key, nil
@@ -553,9 +580,13 @@ func (b *recordingImageBlob) Delete(_ context.Context, key string) error {
}
func (b *recordingImageBlob) uploadCount() int {
return len(b.recordedUploads())
}
func (b *recordingImageBlob) recordedUploads() []recordedImageUpload {
b.mu.Lock()
defer b.mu.Unlock()
return len(b.uploads)
return append([]recordedImageUpload(nil), b.uploads...)
}
func (b *recordingImageBlob) deletedKeys() []string {