2026-08-06 16:43:21 +02:00
|
|
|
package playback
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"testing"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func TestExecutableRecipeV3RoundTripPreservesOperationalFields(t *testing.T) {
|
|
|
|
|
plan := &PlanV3{PlanID: "plan:frozen", Delivery: DeliveryRemuxHLSV3}
|
|
|
|
|
want := PlannerResultV3{
|
|
|
|
|
Plan: plan, PlayMethod: PlayRemux, TranscodeAudio: true,
|
2026-08-10 18:14:49 -04:00
|
|
|
TargetVideoCodec: "copy", TargetAudioCodec: "aac", TargetAudioChannels: 6, TargetAudioBitrateKbps: 320,
|
2026-08-06 16:43:21 +02:00
|
|
|
TargetResolution: "1080p", TargetBitrateKbps: 18_000,
|
2026-08-10 18:14:49 -04:00
|
|
|
FrozenSourceMetadata: &SourceExecutionMetadataV3{VideoCodec: "h264", SoftwareVideoDecode: true, DurationSeconds: 7_201},
|
2026-08-06 16:43:21 +02:00
|
|
|
SubtitleTrackIndex: 4, SubtitleTransportTrackIndex: 2,
|
|
|
|
|
SubtitleBurnIn: true, SubtitleCodec: "hdmv_pgs_subtitle", DownloadedSubtitleID: 71,
|
|
|
|
|
}
|
|
|
|
|
recipe := FreezeExecutableRecipeV3(want)
|
|
|
|
|
if !recipe.Valid() {
|
|
|
|
|
t.Fatalf("frozen recipe is invalid: %#v", recipe)
|
|
|
|
|
}
|
|
|
|
|
if !recipe.ValidFor(*plan) {
|
|
|
|
|
t.Fatalf("frozen recipe does not match its plan: %#v", recipe)
|
|
|
|
|
}
|
|
|
|
|
changedPlan := *plan
|
|
|
|
|
changedPlan.PlanID = "plan:newer"
|
|
|
|
|
if recipe.ValidFor(changedPlan) {
|
|
|
|
|
t.Fatal("stale frozen recipe matched a newer plan")
|
|
|
|
|
}
|
|
|
|
|
got := recipe.PlannerResult(plan)
|
|
|
|
|
if got.Plan != plan || got.PlayMethod != want.PlayMethod || got.TranscodeAudio != want.TranscodeAudio ||
|
|
|
|
|
got.TargetVideoCodec != want.TargetVideoCodec || got.TargetAudioCodec != want.TargetAudioCodec ||
|
2026-08-10 18:14:49 -04:00
|
|
|
got.TargetAudioChannels != want.TargetAudioChannels || got.TargetAudioBitrateKbps != want.TargetAudioBitrateKbps || got.TargetResolution != want.TargetResolution ||
|
2026-08-06 16:43:21 +02:00
|
|
|
got.TargetBitrateKbps != want.TargetBitrateKbps || got.SubtitleTrackIndex != want.SubtitleTrackIndex ||
|
|
|
|
|
got.SubtitleTransportTrackIndex != want.SubtitleTransportTrackIndex || got.SubtitleBurnIn != want.SubtitleBurnIn ||
|
|
|
|
|
got.SubtitleCodec != want.SubtitleCodec || got.DownloadedSubtitleID != want.DownloadedSubtitleID || got.FrozenSourceMetadata == nil ||
|
2026-08-10 18:14:49 -04:00
|
|
|
got.FrozenSourceMetadata.VideoCodec != want.FrozenSourceMetadata.VideoCodec || got.FrozenSourceMetadata.SoftwareVideoDecode != want.FrozenSourceMetadata.SoftwareVideoDecode || got.FrozenSourceMetadata.DurationSeconds != want.FrozenSourceMetadata.DurationSeconds {
|
2026-08-06 16:43:21 +02:00
|
|
|
t.Fatalf("thawed result = %#v, want %#v", got, want)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestExecutableRecipeV3SurvivesJSONRoundTrip(t *testing.T) {
|
|
|
|
|
plan := &PlanV3{PlanID: "plan:frozen"}
|
|
|
|
|
recipe := FreezeExecutableRecipeV3(PlannerResultV3{
|
|
|
|
|
Plan: plan, PlayMethod: PlayRemux,
|
2026-08-10 18:14:49 -04:00
|
|
|
FrozenSourceMetadata: &SourceExecutionMetadataV3{VideoCodec: "h264", SoftwareVideoDecode: true, DurationSeconds: 7_201},
|
2026-08-06 16:43:21 +02:00
|
|
|
SubtitleTrackIndex: -1, SubtitleTransportTrackIndex: 0,
|
|
|
|
|
})
|
|
|
|
|
recipe.SubtitleSource = SubtitleSourceDownloadedV3
|
|
|
|
|
recipe.DownloadedSubtitleID = 71
|
|
|
|
|
|
|
|
|
|
encoded, err := json.Marshal(recipe)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("marshal recipe: %v", err)
|
|
|
|
|
}
|
|
|
|
|
var fields map[string]json.RawMessage
|
|
|
|
|
if err := json.Unmarshal(encoded, &fields); err != nil {
|
|
|
|
|
t.Fatalf("unmarshal recipe fields: %v", err)
|
|
|
|
|
}
|
|
|
|
|
for _, field := range []string{"subtitle_track_index", "subtitle_transport_track_index"} {
|
|
|
|
|
if _, ok := fields[field]; !ok {
|
|
|
|
|
t.Fatalf("encoded recipe omitted meaningful zero-value field %q: %s", field, encoded)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
var decoded ExecutableRecipeV3
|
|
|
|
|
if err := json.Unmarshal(encoded, &decoded); err != nil {
|
|
|
|
|
t.Fatalf("unmarshal recipe: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if decoded != recipe {
|
|
|
|
|
t.Fatalf("decoded recipe = %#v, want %#v", decoded, recipe)
|
|
|
|
|
}
|
|
|
|
|
if !decoded.ValidFor(*plan) {
|
|
|
|
|
t.Fatalf("decoded recipe no longer matches its plan: %#v", decoded)
|
|
|
|
|
}
|
|
|
|
|
}
|