prevent fps rounding

This commit is contained in:
Kilian Boute
2026-08-06 17:46:38 +02:00
parent dbff9fbc8e
commit fea6d81246
5 changed files with 18 additions and 13 deletions

View File

@@ -62,7 +62,7 @@ func recordingUploadMetadata(name, deviceKey string, timestamp int64, mp4Video *
} }
value := mp4Video.AverageFPS() value := mp4Video.AverageFPS()
if value > 0 && value <= 240 && !math.IsInf(value, 0) && !math.IsNaN(value) { if value > 0 && value <= 240 && !math.IsInf(value, 0) && !math.IsNaN(value) {
metadata.FPS = int(math.Floor(value)) metadata.FPS = value
} }
return metadata return metadata
} }

View File

@@ -29,9 +29,13 @@ func TestQueueRecordingForUploadStoresFinalizedMetadata(t *testing.T) {
if err := json.Unmarshal(got, &stored); err != nil { if err := json.Unmarshal(got, &stored); err != nil {
t.Fatalf("decode upload marker: %v", err) t.Fatalf("decode upload marker: %v", err)
} }
if stored.FileName != "recording.mp4" || stored.DeviceKey != "device-key" || stored.Timestamp != 1785934709414 || stored.Duration != 20452 || stored.FPS != 29 { expectedFPS := mp4Video.AverageFPS()
if stored.FileName != "recording.mp4" || stored.DeviceKey != "device-key" || stored.Timestamp != 1785934709414 || stored.Duration != 20452 || math.Abs(stored.FPS-expectedFPS) > 1e-9 {
t.Fatalf("upload marker = %+v", stored) t.Fatalf("upload marker = %+v", stored)
} }
if stored.FPS == math.Floor(stored.FPS) {
t.Fatalf("upload marker FPS = %v, want fractional precision", stored.FPS)
}
} }
func TestQueueRecordingForUploadKeepsUnknownFPSCompatible(t *testing.T) { func TestQueueRecordingForUploadKeepsUnknownFPSCompatible(t *testing.T) {
@@ -44,7 +48,7 @@ func TestQueueRecordingForUploadKeepsUnknownFPSCompatible(t *testing.T) {
metadata := models.RecordingUploadMetadata{FileName: "recording.mp4"} metadata := models.RecordingUploadMetadata{FileName: "recording.mp4"}
if fps >= 1 && fps <= 240 && !math.IsNaN(fps) && !math.IsInf(fps, 0) { if fps >= 1 && fps <= 240 && !math.IsNaN(fps) && !math.IsInf(fps, 0) {
metadata.FPS = int(math.Floor(fps)) metadata.FPS = fps
} }
queueRecordingForUpload(configDirectory, metadata) queueRecordingForUpload(configDirectory, metadata)

View File

@@ -28,10 +28,10 @@ func queuedRecordingFPS(fileName string) string {
marker := strings.TrimSpace(string(value)) marker := strings.TrimSpace(string(value))
if strings.HasPrefix(marker, "{") { if strings.HasPrefix(marker, "{") {
metadata, ok := decodeRecordingUploadMetadata(value) metadata, ok := decodeRecordingUploadMetadata(value)
if !ok || metadata.FPS <= 0 || metadata.FPS > 240 { if !ok || metadata.FPS <= 0 || metadata.FPS > 240 || math.IsInf(metadata.FPS, 0) || math.IsNaN(metadata.FPS) {
return "" return ""
} }
return strconv.Itoa(metadata.FPS) return strconv.FormatFloat(metadata.FPS, 'f', -1, 64)
} }
// Compatibility with markers created before upload metadata used JSON. // Compatibility with markers created before upload metadata used JSON.

View File

@@ -272,7 +272,7 @@ func TestUploadVaultResumable_HappyPath(t *testing.T) {
fileName := "1564859471_6-474162_oprit_577-283-727-375_1153_27.mp4" fileName := "1564859471_6-474162_oprit_577-283-727-375_1153_27.mp4"
payload := bytes.Repeat([]byte("x"), 4096) payload := bytes.Repeat([]byte("x"), 4096)
withRecording(t, fileName, payload) withRecording(t, fileName, payload)
withQueuedRecordingFPS(t, fileName, `{"filename":"recording.mp4","device_key":"device-key","timestamp":1785934709414,"duration":20452,"fps":29}`) withQueuedRecordingFPS(t, fileName, `{"filename":"recording.mp4","device_key":"device-key","timestamp":1785934709414,"duration":20452,"fps":29.97}`)
uploaded, responded, supported, _, err := uploadVaultResumable(testVault(ts.URL), "pk", "dev", fileName, "test", "primary") uploaded, responded, supported, _, err := uploadVaultResumable(testVault(ts.URL), "pk", "dev", fileName, "test", "primary")
if err != nil { if err != nil {
@@ -289,8 +289,8 @@ func TestUploadVaultResumable_HappyPath(t *testing.T) {
} }
posts := srv.requestsForMethod(http.MethodPost) posts := srv.requestsForMethod(http.MethodPost)
metadata := decodeTusMetadata(posts[0].header.Get("Upload-Metadata")) metadata := decodeTusMetadata(posts[0].header.Get("Upload-Metadata"))
if got := metadata["fps"]; got != "29" { if got := metadata["fps"]; got != "29.97" {
t.Fatalf("POST metadata fps = %q, want %q", got, "29") t.Fatalf("POST metadata fps = %q, want %q", got, "29.97")
} }
if got := metadata["duration"]; got != "20452" { if got := metadata["duration"]; got != "20452" {
t.Fatalf("POST metadata duration = %q, want %q", got, "20452") t.Fatalf("POST metadata duration = %q, want %q", got, "20452")
@@ -307,6 +307,7 @@ func TestQueuedRecordingFPSValidation(t *testing.T) {
want string want string
}{ }{
{name: "json", fps: `{"fps":29}`, want: "29"}, {name: "json", fps: `{"fps":29}`, want: "29"},
{name: "json fractional", fps: `{"fps":17.35}`, want: "17.35"},
{name: "json with future field", fps: `{"fps":29,"codec":"h264"}`, want: "29"}, {name: "json with future field", fps: `{"fps":29,"codec":"h264"}`, want: "29"},
{name: "json without fps", fps: `{}`}, {name: "json without fps", fps: `{}`},
{name: "json invalid fps", fps: `{"fps":241}`}, {name: "json invalid fps", fps: `{"fps":241}`},

View File

@@ -11,11 +11,11 @@ const RecordingUploadMetadataExtension = ".metadata"
// with a recording. New optional fields can be added without changing the queue // with a recording. New optional fields can be added without changing the queue
// mechanism or breaking older agents. // mechanism or breaking older agents.
type RecordingUploadMetadata struct { type RecordingUploadMetadata struct {
FileName string `json:"filename"` FileName string `json:"filename"`
DeviceKey string `json:"device_key"` DeviceKey string `json:"device_key"`
Timestamp int64 `json:"timestamp"` // Unix milliseconds. Timestamp int64 `json:"timestamp"` // Unix milliseconds.
Duration uint64 `json:"duration"` // Milliseconds. Duration uint64 `json:"duration"` // Milliseconds.
FPS int `json:"fps,omitempty"` FPS float64 `json:"fps,omitempty"`
} }
// RecordingUploadMetadataFileName returns the queue marker name associated // RecordingUploadMetadataFileName returns the queue marker name associated