* fix(playback): stop a broken Dolby Vision RPU from hanging playback
A Profile 7 source whose RPU ffmpeg cannot parse took the whole session
down. The dovi_rpu bitstream filter does not fail cleanly — it rejects
every packet while ffmpeg runs on, so one observed session emitted
376,316 stderr lines before the process was killed, no manifest was ever
built, and the client got a 503 after ~10 seconds that it showed as an
endless spinner:
[dovi_rpu] Failed to read unit 1 (type 39).
[vost#0:0/copy] Error applying bitstream filters to a packet:
Invalid data found ... Invalid SEI message: payload_size too large
Whether the strip works is a property of the file, not of ffmpeg, so
SupportsDoviRPUFilter cannot answer it — but asking ffmpeg to strip two
seconds to the null muxer can, in about a second. Profile 7 sources are
probed once each on the start path and the result is cached; a source
that fails is copied without the filter, which leaves the base layer and
plays. Refusing to play at all does not.
The probe reads stderr rather than trusting the exit code: ffmpeg treats
a per-packet bitstream-filter error as non-fatal and exits 0, which is
exactly how a stream that could never start reached a live session.
Cache is keyed on path plus size so a replaced file is re-probed, and
bounded like the letterbox cache. A nil probe keeps stripping, since most
Profile 7 sources need it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Db4dSxN9tH8yN7uUP549tK
* chore(playback): use InfoContext in the rpu probe
* fix(playback): decide the DV RPU strip in the plan, not at the transport
The probe answered the right question in the wrong place. Suppressing the
bitstream filter as the transport was being built left the plan still
promising DynamicRange "hdr10", Claims.Video.HDR10 and the
dolby_vision_metadata_removed / hdr10_base_layer_preserved validated
claims, and left session.RemuxDVMode persisted as strip_to_hdr10 — so the
client was told it was getting clean HDR10 while receiving Profile 7 with
dangling RPUs, and every path that re-derives the filter from the durable
session put the hanging filter straight back:
- HandleStartTranscode (quality change, seek, burn-in restart), which
also re-derives it from file.PrimaryDVProfile() == 7 with no plan at all
- the remote audio-switch restart, from updatedSession.RemuxDVMode
- the progressive-remux transport, which plan_v3 evaluates *before* the
HLS branches, so for many clients the broken file never reached the
probe in the first place
The verdict is now a planner input alongside the transformation
registries: the registries answer whether the executor carries the
transformation, this answers whether the file does. A source that fails it
is never planned onto a strip route, so the plan's claims, RemuxDVMode and
every restart derived from them agree with what the pipeline can produce.
With no tone-map recipe in this tree an HDR10-only client has no route
left, so it gets a dv_conversion_unsupported terminal naming the real
cause rather than a generic HDR message; a client that can run its own DV
transformation still gets that route, with a degradation warning
explaining why the server route was dropped.
The two paths that bypass the plan entirely are gated at the executor:
legacy/auto remux neutralizes the profile exactly as it already does for a
missing dovi_rpu filter, and the explicit v3 strip recipe fails loudly
rather than emit dangling RPUs under an HDR10 claim.
The probe itself:
- Tri-state verdict. Only a stderr-confirmed rejection is cached. A
timeout, a cancelled request or an ffmpeg that will not start is
inconclusive: the strip is kept and nothing is written, so one client
disconnecting can no longer disable the strip for a file permanently.
- Singleflight, so concurrent or retried starts of a title share one run.
- Timeout cut to 6s, inside the budget a client waits on the manifest,
and off the session lifecycle lock now that it runs at planning time.
- Keyed on size and mtime as well as path and binary, so a file replaced
in place with the same length is re-probed.
- stderr capture bounded at 64 KiB; the markers are in the first lines
and a rejecting filter emits a pair per frame.
- The head-only coverage is stated rather than asserted: this catches a
source that rejects from the first access unit, not one that breaks an
hour in.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(playback): keep the RPU probe alive when its caller leaves
The probe rode the caller's context, so a leader whose client disconnected
returned dvRPUUnknown — and CanStrip then handed that fail-open to every
follower already blocked on the shared call, even though their own requests
were still alive. One client walking away was enough to give a live session
the strip the source cannot survive, which is the hang this whole change
exists to prevent. The work was also thrown away, so the next start paid for
the probe again.
Run it under context.WithoutCancel instead. dvRPUProbeTimeout still bounds
it, so nothing is left running; a verdict reached after the leader has gone
is still correct and still worth caching. Follower behaviour is unchanged:
a follower whose own request is cancelled still leaves immediately rather
than waiting on someone else's probe.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: rxwatcher <rxwatcher@users.noreply.github.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Quick <31828688+Quick104@users.noreply.github.com>
1110 lines
58 KiB
Go
1110 lines
58 KiB
Go
package playback
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/Silo-Server/silo-server/internal/models"
|
|
)
|
|
|
|
func hasDegradationWarningV3(warnings []DegradationWarningV3, code string) bool {
|
|
for _, warning := range warnings {
|
|
if warning.Code == code {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func TestServerFeaturesV3ReturnsCompleteIndependentSlices(t *testing.T) {
|
|
first := ServerFeaturesV3()
|
|
second := ServerFeaturesV3()
|
|
expected := map[string]struct{}{
|
|
FeaturePlaybackPlanV3: {},
|
|
FeatureMedia3Only: {},
|
|
FeatureDetailedDecodeV3: {},
|
|
FeatureLayoutPassthrough: {},
|
|
FeatureRouteDiagnostics: {},
|
|
FeatureDeviceQuirksV3: {},
|
|
FeatureSeekReanchorV3: {},
|
|
FeatureDirectStreamResumeV3: {},
|
|
FeaturePlanSourceDurationV3: {},
|
|
}
|
|
if len(first) != len(expected) {
|
|
t.Fatalf("server features = %v, want %d entries", first, len(expected))
|
|
}
|
|
seen := make(map[string]struct{}, len(first))
|
|
for _, feature := range first {
|
|
if _, ok := expected[feature]; !ok {
|
|
t.Fatalf("server features contain unexpected %q: %v", feature, first)
|
|
}
|
|
if _, duplicate := seen[feature]; duplicate {
|
|
t.Fatalf("server features contain duplicate %q: %v", feature, first)
|
|
}
|
|
seen[feature] = struct{}{}
|
|
}
|
|
for feature := range expected {
|
|
if _, ok := seen[feature]; !ok {
|
|
t.Fatalf("server features omitted %q: %v", feature, first)
|
|
}
|
|
}
|
|
|
|
first[0] = "mutated"
|
|
if second[0] != FeaturePlaybackPlanV3 {
|
|
t.Fatalf("feature slices share backing storage: %v", second)
|
|
}
|
|
}
|
|
|
|
func TestStartRequestV3Validation(t *testing.T) {
|
|
index := 1
|
|
req := validStartRequestV3()
|
|
req.AudioTrackIndex = &index
|
|
req.AudioTrackID = TrackIDV3(req.FileID, "audio", index)
|
|
warnings, err := req.NormalizeAndValidate()
|
|
if err != nil {
|
|
t.Fatalf("NormalizeAndValidate: %v", err)
|
|
}
|
|
if len(warnings) != 0 {
|
|
t.Fatalf("warnings = %#v, want none", warnings)
|
|
}
|
|
|
|
req.AudioTrackID = TrackIDV3(req.FileID, "audio", 2)
|
|
if _, err := req.NormalizeAndValidate(); err == nil {
|
|
t.Fatal("mismatched track id/index accepted")
|
|
}
|
|
}
|
|
|
|
func TestStartRequestV3UnknownQualityFallsBackToAuto(t *testing.T) {
|
|
req := validStartRequestV3()
|
|
req.QualityPreference = "future-super-quality"
|
|
warnings, err := req.NormalizeAndValidate()
|
|
if err != nil {
|
|
t.Fatalf("NormalizeAndValidate: %v", err)
|
|
}
|
|
if req.QualityPreference != "auto" || len(warnings) != 1 || warnings[0].Code != "quality_preference_normalized" {
|
|
t.Fatalf("quality=%q warnings=%#v", req.QualityPreference, warnings)
|
|
}
|
|
}
|
|
|
|
func TestReplanRequestV3OperationDefaultsAndValidates(t *testing.T) {
|
|
start := validStartRequestV3()
|
|
request := ReplanRequestV3{
|
|
ProtocolVersion: ProtocolV3,
|
|
PlaybackAttemptID: start.PlaybackAttemptID,
|
|
ReplanRequestID: "replan-operation-0001",
|
|
FailedPlanID: "plan:operation-0001",
|
|
PlanAttemptID: "plan-attempt-operation-0001",
|
|
PlanAttemptKey: "v3:0000000000000001",
|
|
AttemptCount: 1,
|
|
QualityPreference: start.QualityPreference,
|
|
OutputRouteGeneration: start.OutputRouteGeneration,
|
|
Failure: FailureV3{Classification: "parser_failure"},
|
|
Capabilities: start.Capabilities,
|
|
ClientPlaybackContext: start.ClientPlaybackContext,
|
|
}
|
|
if request.EffectiveOperation() != ReplanOperationFailureRecoveryV3 {
|
|
t.Fatalf("missing operation = %q", request.EffectiveOperation())
|
|
}
|
|
if err := request.Validate(); err != nil {
|
|
t.Fatalf("backward-compatible default operation: %v", err)
|
|
}
|
|
|
|
request.Operation = ReplanOperationSeekReanchorV3
|
|
request.Failure.Classification = ""
|
|
if err := request.Validate(); err != nil {
|
|
t.Fatalf("seek reanchor operation without fake failure: %v", err)
|
|
}
|
|
body, err := json.Marshal(request)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var wire map[string]any
|
|
if err := json.Unmarshal(body, &wire); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if wire["operation"] != string(ReplanOperationSeekReanchorV3) {
|
|
t.Fatalf("serialized operation = %#v", wire["operation"])
|
|
}
|
|
|
|
request.Operation = ReplanOperationSeekFailureRecoveryV3
|
|
if err := request.Validate(); err == nil {
|
|
t.Fatal("seek failure recovery without a classification was accepted")
|
|
}
|
|
request.Failure.Classification = "decoder_failure"
|
|
if err := request.Validate(); err != nil {
|
|
t.Fatalf("seek failure recovery operation: %v", err)
|
|
}
|
|
|
|
request.Operation = "future_operation"
|
|
if err := request.Validate(); err == nil {
|
|
t.Fatal("unknown replan operation was accepted")
|
|
}
|
|
}
|
|
|
|
func TestReplanRequestV3RejectsInvalidNetworkAndTrackEvidence(t *testing.T) {
|
|
start := validStartRequestV3()
|
|
request := ReplanRequestV3{
|
|
ProtocolVersion: ProtocolV3,
|
|
PlaybackAttemptID: start.PlaybackAttemptID,
|
|
ReplanRequestID: "replan-validation-0001",
|
|
FailedPlanID: "plan:validation-0001",
|
|
PlanAttemptID: "plan-attempt-validation-0001",
|
|
PlanAttemptKey: "v3:0000000000000001",
|
|
AttemptCount: 1,
|
|
QualityPreference: start.QualityPreference,
|
|
OutputRouteGeneration: start.OutputRouteGeneration,
|
|
Failure: FailureV3{Classification: "parser_failure"},
|
|
Capabilities: start.Capabilities,
|
|
ClientPlaybackContext: start.ClientPlaybackContext,
|
|
}
|
|
|
|
negative := -1
|
|
request.SelectedTracks.Subtitle = &TrackIdentityV3{ID: "file:42:subtitle:-1", Index: &negative}
|
|
if err := request.Validate(); err == nil {
|
|
t.Fatal("negative subtitle index was accepted")
|
|
}
|
|
|
|
request.SelectedTracks.Subtitle = nil
|
|
tooLow := 99
|
|
request.BandwidthEstimateKbps = &tooLow
|
|
if err := request.Validate(); err == nil {
|
|
t.Fatal("out-of-range bandwidth estimate was accepted")
|
|
}
|
|
}
|
|
|
|
func TestPlanAttemptKeyV3KotlinFixture(t *testing.T) {
|
|
type fixture struct {
|
|
Name string `json:"name"`
|
|
PlanID string `json:"plan_id"`
|
|
Delivery DeliveryV3 `json:"delivery"`
|
|
StreamProtocol StreamProtocolV3 `json:"stream_protocol"`
|
|
Container string `json:"container"`
|
|
VideoCodec string `json:"video_codec"`
|
|
AudioCodec string `json:"audio_codec"`
|
|
Width int `json:"width"`
|
|
Height int `json:"height"`
|
|
BitrateKbps int `json:"bitrate_kbps"`
|
|
DynamicRange string `json:"dynamic_range"`
|
|
SubtitleMode SubtitleModeV3 `json:"subtitle_mode"`
|
|
Transformations []TransformationV3 `json:"transformations"`
|
|
AppliedQuirks []AppliedQuirkV3 `json:"applied_quirks"`
|
|
RuntimeCorrections []string `json:"runtime_corrections"`
|
|
OutputRouteGeneration int64 `json:"output_route_generation"`
|
|
LocalMutations []string `json:"local_mutations"`
|
|
Expected string `json:"expected"`
|
|
}
|
|
body, err := os.ReadFile("testdata/protocol_v3/attempt_keys.json")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var fixtures []fixture
|
|
if err := json.Unmarshal(body, &fixtures); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, value := range fixtures {
|
|
t.Run(value.Name, func(t *testing.T) {
|
|
plan := PlanV3{
|
|
PlanID: value.PlanID,
|
|
Delivery: value.Delivery,
|
|
Stream: StreamV3{Protocol: value.StreamProtocol, Container: value.Container},
|
|
EffectiveRecipe: EffectiveRecipeV3{VideoCodec: value.VideoCodec, AudioCodec: value.AudioCodec, Width: &value.Width, Height: &value.Height, BitrateKbps: &value.BitrateKbps, DynamicRange: value.DynamicRange},
|
|
Subtitle: SubtitleDecisionV3{Mode: value.SubtitleMode},
|
|
}
|
|
plan.Transformations = append(plan.Transformations, value.Transformations...)
|
|
// The quirk identity is conditionally omitted from the preimage
|
|
// when no quirks or runtime corrections apply; fixtures pin both
|
|
// arities so the Kotlin client reproduces the omission exactly.
|
|
plan.AppliedQuirks = append(plan.AppliedQuirks, value.AppliedQuirks...)
|
|
plan.RuntimeCorrections = append(plan.RuntimeCorrections, value.RuntimeCorrections...)
|
|
if got := PlanAttemptKeyV3(plan, value.OutputRouteGeneration, value.LocalMutations); got != value.Expected {
|
|
t.Fatalf("key = %q, want %q", got, value.Expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestProtocolV3GoldenWireFixtures(t *testing.T) {
|
|
startBody, err := os.ReadFile("testdata/protocol_v3/start_request.json")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var start StartRequestV3
|
|
if err := json.Unmarshal(startBody, &start); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := start.NormalizeAndValidate(); err != nil {
|
|
t.Fatalf("golden start request: %v", err)
|
|
}
|
|
replanBody, err := os.ReadFile("testdata/protocol_v3/replan_request.json")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var replan ReplanRequestV3
|
|
if err := json.Unmarshal(replanBody, &replan); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := replan.Validate(); err != nil {
|
|
t.Fatalf("golden replan request: %v", err)
|
|
}
|
|
if replan.BandwidthEstimateKbps == nil || *replan.BandwidthEstimateKbps != 3_500 ||
|
|
replan.BandwidthCapKbps == nil || *replan.BandwidthCapKbps != 4_000 || !replan.Metered {
|
|
t.Fatalf("golden replan network evidence = %#v", replan)
|
|
}
|
|
responseBody, err := os.ReadFile("testdata/protocol_v3/decision_response.json")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var response DecisionResponseV3
|
|
if err := json.Unmarshal(responseBody, &response); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if response.ProtocolVersion != ProtocolV3 || response.Outcome != OutcomePlayableV3 || response.PlaybackPlan == nil || response.PlaybackPlan.Stream.Protocol != StreamHTTPProgressiveV3 {
|
|
t.Fatalf("golden response = %#v", response)
|
|
}
|
|
}
|
|
|
|
func TestPlanPlaybackV3DirectRequiresDetailedEvidence(t *testing.T) {
|
|
file := detailedFixtureFileV3()
|
|
req := validStartRequestV3()
|
|
req.ClientFeatures = append(req.ClientFeatures, FeatureDetailedDecodeV3, FeatureLayoutPassthrough)
|
|
req.ClientPlaybackContext.Features = append(req.ClientPlaybackContext.Features, FeatureDetailedDecodeV3, FeatureLayoutPassthrough)
|
|
req.Capabilities.VideoDecode = []VideoDecodeCapabilityV3{{Codec: "hevc", Profiles: []string{"main 10"}, Levels: []int{153}, BitDepths: []int{10}, MaxWidth: 3840, MaxHeight: 2160, MaxFrameRate: 60, MaxBitrateKbps: 80_000, Hardware: true}}
|
|
req.Capabilities.HDRDetails = &HDRCapabilitiesV3{HDR10: true}
|
|
req.ClientPlaybackContext.Output.HDRDetails = &HDRCapabilitiesV3{HDR10: true}
|
|
result := PlanPlaybackV3(PlannerInputV3{Request: req, RequestedFile: file, EffectiveFile: file, AudioTrackIndex: 0, Settings: PlannerSettingsV3{TranscodeEnabled: true, Allow4KTranscode: true}, Registry: testTransformationRegistryV3()})
|
|
if result.Plan == nil || result.Plan.Delivery != DeliveryOriginalHTTPV3 {
|
|
t.Fatalf("result = %s", ExplainPlannerResultV3(result))
|
|
}
|
|
|
|
req.ClientFeatures = []string{FeaturePlaybackPlanV3, FeatureMedia3Only}
|
|
req.ClientPlaybackContext.Features = req.ClientFeatures
|
|
result = PlanPlaybackV3(PlannerInputV3{Request: req, RequestedFile: file, EffectiveFile: file, AudioTrackIndex: 0, Settings: PlannerSettingsV3{TranscodeEnabled: false, Allow4KTranscode: true}})
|
|
if result.Terminal == nil || result.Terminal.Reason != "transcoding_disabled" {
|
|
t.Fatalf("result = %s", ExplainPlannerResultV3(result))
|
|
}
|
|
}
|
|
|
|
func TestSourceDescriptorV3NormalizesLegacyHEVCMetadata(t *testing.T) {
|
|
file := detailedFixtureFileV3()
|
|
file.VideoTracks[0].BitDepth = 0
|
|
file.VideoTracks[0].PixelFormat = "yuv420p10le"
|
|
file.VideoTracks[0].DVProfile = 7
|
|
file.VideoTracks[0].DVELPresent = false
|
|
file.VideoTracks[0].DVEnhancementLayer = ""
|
|
file.VideoTracks[0].VideoRangeType = "DOVIWithEL"
|
|
|
|
source := SourceDescriptorFromFileV3(file, 0)
|
|
if source.BitDepth != 10 {
|
|
t.Fatalf("bit depth = %d, want inferred 10", source.BitDepth)
|
|
}
|
|
if source.DVEnhancementLayer != EnhancementUnknownV3 {
|
|
t.Fatalf("enhancement layer = %q, want unknown", source.DVEnhancementLayer)
|
|
}
|
|
}
|
|
|
|
func TestSourceDescriptorV3PreservesCanonicalColorRange(t *testing.T) {
|
|
for _, test := range []struct {
|
|
name string
|
|
input string
|
|
want string
|
|
}{
|
|
{name: "limited", input: "tv", want: "tv"},
|
|
{name: "full", input: "pc", want: "pc"},
|
|
{name: "unspecified", input: "unknown", want: "unknown"},
|
|
{name: "normalizes case and whitespace", input: " PC ", want: "pc"},
|
|
{name: "rejects non-ffmpeg value", input: "limited", want: ""},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
file := detailedFixtureFileV3()
|
|
file.VideoTracks[0].ColorRange = test.input
|
|
if got := SourceDescriptorFromFileV3(file, 0).ColorRange; got != test.want {
|
|
t.Fatalf("color range = %q, want %q", got, test.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPlanPlaybackV3DirectPlaysLegacyHDR10WithInferredBitDepth(t *testing.T) {
|
|
file := detailedFixtureFileV3()
|
|
file.VideoTracks[0].BitDepth = 0
|
|
file.VideoTracks[0].PixelFormat = "yuv420p10le"
|
|
req := validStartRequestV3()
|
|
req.ClientFeatures = append(req.ClientFeatures, FeatureDetailedDecodeV3)
|
|
req.ClientPlaybackContext.Features = append(req.ClientPlaybackContext.Features, FeatureDetailedDecodeV3)
|
|
req.Capabilities.VideoDecode = []VideoDecodeCapabilityV3{{Codec: "hevc", Profiles: []string{"main 10"}, Levels: []int{153}, BitDepths: []int{10}, MaxWidth: 3840, MaxHeight: 2160, MaxFrameRate: 60, MaxBitrateKbps: 80_000, Hardware: true}}
|
|
req.Capabilities.HDRDetails = &HDRCapabilitiesV3{HDR10: true}
|
|
req.ClientPlaybackContext.Output.HDRDetails = &HDRCapabilitiesV3{HDR10: true}
|
|
|
|
result := PlanPlaybackV3(PlannerInputV3{Request: req, RequestedFile: file, EffectiveFile: file, AudioTrackIndex: 0, Settings: PlannerSettingsV3{TranscodeEnabled: true}, Registry: testTransformationRegistryV3()})
|
|
if result.Plan == nil || result.Plan.Delivery != DeliveryOriginalHTTPV3 || !result.Plan.Claims.Video.HDR10 {
|
|
t.Fatalf("result = %s", ExplainPlannerResultV3(result))
|
|
}
|
|
}
|
|
|
|
func TestPlanPlaybackV3DirectPlaysLegacyDolbyVisionProfile8(t *testing.T) {
|
|
file := detailedFixtureFileV3()
|
|
file.VideoTracks[0].BitDepth = 0
|
|
file.VideoTracks[0].PixelFormat = "yuv420p10le"
|
|
file.VideoTracks[0].DVProfile = 8
|
|
file.VideoTracks[0].DVBLCompatID = 1
|
|
file.VideoTracks[0].VideoRange = "DolbyVision"
|
|
file.VideoTracks[0].VideoRangeType = "DOVIWithHDR10"
|
|
req := validStartRequestV3()
|
|
req.ClientFeatures = append(req.ClientFeatures, FeatureDetailedDecodeV3)
|
|
req.ClientPlaybackContext.Features = append(req.ClientPlaybackContext.Features, FeatureDetailedDecodeV3)
|
|
req.Capabilities.VideoDecode = []VideoDecodeCapabilityV3{{Codec: "hevc", Profiles: []string{"main 10"}, Levels: []int{153}, BitDepths: []int{10}, MaxWidth: 3840, MaxHeight: 2160, MaxFrameRate: 60, MaxBitrateKbps: 80_000, Hardware: true}}
|
|
req.Capabilities.HDRDetails = &HDRCapabilitiesV3{HDR10: true, DolbyVisionProfiles: []int{8}}
|
|
req.ClientPlaybackContext.Output.HDRDetails = req.Capabilities.HDRDetails
|
|
|
|
result := PlanPlaybackV3(PlannerInputV3{Request: req, RequestedFile: file, EffectiveFile: file, AudioTrackIndex: 0, Settings: PlannerSettingsV3{TranscodeEnabled: true}, Registry: testTransformationRegistryV3()})
|
|
if result.Plan == nil || result.Plan.Delivery != DeliveryOriginalHTTPV3 || !result.Plan.Claims.Video.DolbyVision {
|
|
t.Fatalf("result = %s", ExplainPlannerResultV3(result))
|
|
}
|
|
if result.Plan.RequestedMediaFileID != file.ID || result.Plan.EffectiveMediaFileID != file.ID {
|
|
t.Fatalf("source ids = requested %d effective %d", result.Plan.RequestedMediaFileID, result.Plan.EffectiveMediaFileID)
|
|
}
|
|
}
|
|
|
|
func TestPlanPlaybackV3RejectsTrulyIncompleteVideoMetadata(t *testing.T) {
|
|
file := detailedFixtureFileV3()
|
|
file.VideoTracks[0].BitDepth = 0
|
|
file.VideoTracks[0].PixelFormat = ""
|
|
file.VideoTracks[0].Profile = ""
|
|
req := validStartRequestV3()
|
|
req.ClientFeatures = append(req.ClientFeatures, FeatureDetailedDecodeV3)
|
|
req.ClientPlaybackContext.Features = append(req.ClientPlaybackContext.Features, FeatureDetailedDecodeV3)
|
|
|
|
result := PlanPlaybackV3(PlannerInputV3{Request: req, RequestedFile: file, EffectiveFile: file, AudioTrackIndex: 0, Settings: PlannerSettingsV3{TranscodeEnabled: true, Allow4KTranscode: true}, Registry: testTransformationRegistryV3()})
|
|
if result.Terminal == nil || result.Terminal.Reason != "source_metadata_incomplete" {
|
|
t.Fatalf("result = %s", ExplainPlannerResultV3(result))
|
|
}
|
|
}
|
|
|
|
func TestPlanPlaybackV3BlocksUltrawide4KTranscode(t *testing.T) {
|
|
file := detailedFixtureFileV3()
|
|
file.Resolution = "2160p"
|
|
file.VideoTracks[0].Width = 3840
|
|
file.VideoTracks[0].Height = 1626
|
|
file.VideoTracks[0].VideoRange = "SDR"
|
|
file.VideoTracks[0].VideoRangeType = "SDR"
|
|
file.VideoTracks[0].ColorTransfer = "bt709"
|
|
req := validStartRequestV3()
|
|
req.QualityPreference = "1080p"
|
|
req.ClientFeatures = append(req.ClientFeatures, FeatureDetailedDecodeV3)
|
|
req.ClientPlaybackContext.Features = append(req.ClientPlaybackContext.Features, FeatureDetailedDecodeV3)
|
|
req.Capabilities.VideoDecode = []VideoDecodeCapabilityV3{{Codec: "hevc", Profiles: []string{"main 10"}, Levels: []int{153}, BitDepths: []int{10}, MaxWidth: 3840, MaxHeight: 2160, MaxFrameRate: 60, MaxBitrateKbps: 80_000, Hardware: true}}
|
|
|
|
result := PlanPlaybackV3(PlannerInputV3{Request: req, RequestedFile: file, EffectiveFile: file, AudioTrackIndex: 0, Settings: PlannerSettingsV3{TranscodeEnabled: true, Allow4KTranscode: false}, Registry: testTransformationRegistryV3()})
|
|
if result.Terminal == nil || result.Terminal.Reason != "no_alternate_version" {
|
|
t.Fatalf("result = %s", ExplainPlannerResultV3(result))
|
|
}
|
|
}
|
|
|
|
func TestPlanPlaybackV3Profile7FallsBackToHDR10WithoutNativeP7(t *testing.T) {
|
|
file := detailedFixtureFileV3()
|
|
file.VideoTracks[0].DVProfile = 7
|
|
file.VideoTracks[0].DVBLCompatID = 6
|
|
file.VideoTracks[0].DVELPresent = false
|
|
file.VideoTracks[0].DVEnhancementLayer = ""
|
|
file.VideoTracks[0].VideoRange = "DolbyVision"
|
|
file.VideoTracks[0].VideoRangeType = "DOVIWithEL"
|
|
req := validStartRequestV3()
|
|
req.ClientFeatures = append(req.ClientFeatures, FeatureDetailedDecodeV3)
|
|
req.ClientPlaybackContext.Features = append(req.ClientPlaybackContext.Features, FeatureDetailedDecodeV3)
|
|
req.Capabilities.VideoDecode = []VideoDecodeCapabilityV3{{Codec: "hevc", Profiles: []string{"main 10"}, Levels: []int{153}, BitDepths: []int{10}, MaxWidth: 3840, MaxHeight: 2160, MaxFrameRate: 60, MaxBitrateKbps: 80_000, Hardware: true}}
|
|
req.Capabilities.HDRDetails = &HDRCapabilitiesV3{HDR10: true, DolbyVisionProfiles: []int{5, 8}}
|
|
req.ClientPlaybackContext.Output.HDRDetails = req.Capabilities.HDRDetails
|
|
registry := NewTransformationRegistryV3([]TransformationSpecV3{{Name: "server_dv7_to_hdr10", Available: true}})
|
|
|
|
result := PlanPlaybackV3(PlannerInputV3{Request: req, RequestedFile: file, EffectiveFile: file, AudioTrackIndex: 0, Settings: PlannerSettingsV3{TranscodeEnabled: true}, Registry: registry})
|
|
if result.Plan == nil || result.Plan.Delivery != DeliveryRemuxProgressiveV3 || !result.Plan.Claims.Video.HDR10 || result.Plan.Claims.Video.DolbyVision {
|
|
t.Fatalf("result = %#v", result)
|
|
}
|
|
if len(result.Plan.Transformations) != 1 || result.Plan.Transformations[0].Name != "server_dv7_to_hdr10" {
|
|
t.Fatalf("transformations = %#v", result.Plan.Transformations)
|
|
}
|
|
}
|
|
|
|
func TestPlanPlaybackV3Profile7UsesVersionedClientTransformationsOnSameFile(t *testing.T) {
|
|
file := detailedFixtureFileV3()
|
|
file.VideoTracks[0].DVProfile = 7
|
|
file.VideoTracks[0].DVBLCompatID = 1
|
|
file.VideoTracks[0].DVELPresent = true
|
|
file.VideoTracks[0].DVEnhancementLayer = "unknown"
|
|
file.VideoTracks[0].VideoRange = "DolbyVision"
|
|
file.VideoTracks[0].VideoRangeType = "DOVIWithEL"
|
|
req := validStartRequestV3()
|
|
req.ClientFeatures = append(req.ClientFeatures, FeatureDetailedDecodeV3, FeatureClientVideoTransforms)
|
|
req.ClientPlaybackContext.Features = append(req.ClientPlaybackContext.Features, FeatureDetailedDecodeV3, FeatureClientVideoTransforms)
|
|
req.Capabilities.VideoDecode = []VideoDecodeCapabilityV3{{Codec: "hevc", Profiles: []string{"main 10"}, Levels: []int{153}, BitDepths: []int{10}, MaxWidth: 3840, MaxHeight: 2160, MaxFrameRate: 60, MaxBitrateKbps: 80_000, Hardware: true}}
|
|
req.Capabilities.HDRDetails = &HDRCapabilitiesV3{HDR10: true, DolbyVisionProfiles: []int{8}}
|
|
req.ClientPlaybackContext.Output.HDRDetails = req.Capabilities.HDRDetails
|
|
direct := req.ClientPlaybackContext.Engines[string(EngineMedia3DirectV3)]
|
|
direct.Transformations = []TransformationV3{
|
|
{Name: ClientDV7ToDV81V3, Executor: "client", RecipeVersion: ClientDVTransformVersionV3},
|
|
{Name: ClientDV7ToHDR10V3, Executor: "client", RecipeVersion: ClientDVTransformVersionV3},
|
|
}
|
|
req.ClientPlaybackContext.Engines[string(EngineMedia3DirectV3)] = direct
|
|
|
|
first := PlanPlaybackV3(PlannerInputV3{Request: req, RequestedFile: file, EffectiveFile: file, AudioTrackIndex: 0, Settings: PlannerSettingsV3{TranscodeEnabled: true}, Registry: testTransformationRegistryV3()})
|
|
if first.Plan == nil || first.Plan.Delivery != DeliveryOriginalHTTPV3 || first.Plan.EffectiveMediaFileID != file.ID || first.Plan.DecisionReason != "client_dv7_to_dv81" {
|
|
t.Fatalf("first = %#v", first)
|
|
}
|
|
if got := first.Plan.Transformations; len(got) != 1 || got[0].Name != ClientDV7ToDV81V3 || got[0].Executor != "client" || got[0].RecipeVersion != "1" {
|
|
t.Fatalf("first transformations = %#v", got)
|
|
}
|
|
|
|
failedKey := PlanAttemptKeyV3(*first.Plan, req.OutputRouteGeneration, nil)
|
|
second := PlanPlaybackV3(PlannerInputV3{Request: req, RequestedFile: file, EffectiveFile: file, AudioTrackIndex: 0, Settings: PlannerSettingsV3{TranscodeEnabled: true}, Registry: testTransformationRegistryV3(), AttemptedKeys: []string{failedKey}})
|
|
if second.Plan == nil || second.Plan.Delivery != DeliveryOriginalHTTPV3 || second.Plan.EffectiveMediaFileID != file.ID || second.Plan.DecisionReason != "client_dv7_to_hdr10" || !second.Plan.Claims.Video.HDR10 {
|
|
t.Fatalf("second = %#v", second)
|
|
}
|
|
}
|
|
|
|
func TestPlanPlaybackV3Profile7DoesNotInferClientTransformFromHDRProfile(t *testing.T) {
|
|
file := detailedFixtureFileV3()
|
|
file.VideoTracks[0].DVProfile = 7
|
|
file.VideoTracks[0].VideoRange = "DolbyVision"
|
|
file.VideoTracks[0].VideoRangeType = "DOVIWithEL"
|
|
req := validStartRequestV3()
|
|
req.ClientFeatures = append(req.ClientFeatures, FeatureDetailedDecodeV3)
|
|
req.ClientPlaybackContext.Features = append(req.ClientPlaybackContext.Features, FeatureDetailedDecodeV3)
|
|
req.Capabilities.VideoDecode = []VideoDecodeCapabilityV3{{Codec: "hevc", Profiles: []string{"main 10"}, Levels: []int{153}, BitDepths: []int{10}, MaxWidth: 3840, MaxHeight: 2160, MaxFrameRate: 60, MaxBitrateKbps: 80_000, Hardware: true}}
|
|
req.Capabilities.HDRDetails = &HDRCapabilitiesV3{HDR10: true, DolbyVisionProfiles: []int{7, 8}}
|
|
req.ClientPlaybackContext.Output.HDRDetails = req.Capabilities.HDRDetails
|
|
|
|
result := PlanPlaybackV3(PlannerInputV3{Request: req, RequestedFile: file, EffectiveFile: file, AudioTrackIndex: 0, Settings: PlannerSettingsV3{TranscodeEnabled: false}})
|
|
if result.Plan != nil && result.Plan.Delivery == DeliveryOriginalHTTPV3 {
|
|
t.Fatalf("Profile 7 must not direct-play from codec claims alone: %#v", result.Plan)
|
|
}
|
|
}
|
|
|
|
func TestPlanPlaybackV3AudioAdaptationCopiesVideo(t *testing.T) {
|
|
file := detailedFixtureFileV3()
|
|
file.AudioTracks[0] = models.AudioTrack{Codec: "truehd", Channels: 8, Layout: "7.1"}
|
|
file.CodecAudio = "truehd"
|
|
req := validStartRequestV3()
|
|
req.ClientFeatures = append(req.ClientFeatures, FeatureDetailedDecodeV3)
|
|
req.ClientPlaybackContext.Features = append(req.ClientPlaybackContext.Features, FeatureDetailedDecodeV3)
|
|
req.Capabilities.VideoDecode = []VideoDecodeCapabilityV3{{Codec: "hevc", Profiles: []string{"main 10"}, Levels: []int{153}, BitDepths: []int{10}, MaxWidth: 3840, MaxHeight: 2160, MaxFrameRate: 60, MaxBitrateKbps: 80_000, Hardware: true}}
|
|
req.Capabilities.HDRDetails = &HDRCapabilitiesV3{HDR10: true}
|
|
result := PlanPlaybackV3(PlannerInputV3{Request: req, RequestedFile: file, EffectiveFile: file, AudioTrackIndex: 0, Settings: PlannerSettingsV3{TranscodeEnabled: true, Allow4KTranscode: true}, Registry: testTransformationRegistryV3()})
|
|
if result.Plan == nil || result.Plan.Delivery != DeliveryRemuxProgressiveV3 || !result.TranscodeAudio || result.TargetVideoCodec != "" {
|
|
t.Fatalf("result = %#v", result)
|
|
}
|
|
}
|
|
|
|
func TestPlanPlaybackV3FallsBackFromProgressiveToHLSWithoutRepeatingKey(t *testing.T) {
|
|
file := detailedFixtureFileV3()
|
|
file.VideoTracks[0].VideoRange = "SDR"
|
|
file.VideoTracks[0].VideoRangeType = "SDR"
|
|
file.VideoTracks[0].ColorTransfer = "bt709"
|
|
req := validStartRequestV3()
|
|
req.ClientFeatures = append(req.ClientFeatures, FeatureDetailedDecodeV3)
|
|
req.ClientPlaybackContext.Features = append(req.ClientPlaybackContext.Features, FeatureDetailedDecodeV3)
|
|
req.Capabilities.Containers = []string{"mp4"}
|
|
req.Capabilities.VideoDecode = []VideoDecodeCapabilityV3{{Codec: "hevc", Profiles: []string{"main 10"}, Levels: []int{153}, BitDepths: []int{10}, MaxWidth: 3840, MaxHeight: 2160, MaxFrameRate: 60, MaxBitrateKbps: 80_000, Hardware: true}}
|
|
req.Capabilities.HDRDetails = &HDRCapabilitiesV3{HDR10: true}
|
|
first := PlanPlaybackV3(PlannerInputV3{Request: req, RequestedFile: file, EffectiveFile: file, AudioTrackIndex: 0, Settings: PlannerSettingsV3{TranscodeEnabled: true, Allow4KTranscode: true}})
|
|
if first.Plan == nil || first.Plan.Delivery != DeliveryRemuxProgressiveV3 {
|
|
t.Fatalf("first = %s", ExplainPlannerResultV3(first))
|
|
}
|
|
failedKey := PlanAttemptKeyV3(*first.Plan, req.OutputRouteGeneration, nil)
|
|
second := PlanPlaybackV3(PlannerInputV3{Request: req, RequestedFile: file, EffectiveFile: file, AudioTrackIndex: 0, Settings: PlannerSettingsV3{TranscodeEnabled: true, Allow4KTranscode: true}, AttemptedKeys: []string{failedKey}})
|
|
if second.Plan == nil || second.Plan.Delivery != DeliveryRemuxHLSV3 || second.TargetVideoCodec != "copy" {
|
|
t.Fatalf("second = %#v", second)
|
|
}
|
|
secondKey := PlanAttemptKeyV3(*second.Plan, req.OutputRouteGeneration, nil)
|
|
third := PlanPlaybackV3(PlannerInputV3{Request: req, RequestedFile: file, EffectiveFile: file, AudioTrackIndex: 0, Settings: PlannerSettingsV3{TranscodeEnabled: true, Allow4KTranscode: true}, Registry: testTransformationRegistryV3(), AttemptedKeys: []string{failedKey, secondKey}})
|
|
if third.Plan == nil || third.Plan.Delivery != DeliveryTranscodeHLSV3 || third.Plan.DecisionReason != "copy_routes_exhausted" {
|
|
t.Fatalf("third = %#v", third)
|
|
}
|
|
}
|
|
|
|
func TestPlanPlaybackV3NeverClaimsUnimplementedHDRTranscode(t *testing.T) {
|
|
file := detailedFixtureFileV3()
|
|
req := validStartRequestV3()
|
|
req.QualityPreference = "1080p"
|
|
req.ClientFeatures = append(req.ClientFeatures, FeatureDetailedDecodeV3)
|
|
req.ClientPlaybackContext.Features = append(req.ClientPlaybackContext.Features, FeatureDetailedDecodeV3)
|
|
req.Capabilities.VideoDecode = []VideoDecodeCapabilityV3{{Codec: "hevc", Profiles: []string{"main 10"}, Levels: []int{153}, BitDepths: []int{10}, MaxWidth: 3840, MaxHeight: 2160, MaxFrameRate: 60, MaxBitrateKbps: 80_000, Hardware: true}}
|
|
req.Capabilities.HDRDetails = &HDRCapabilitiesV3{HDR10: true}
|
|
result := PlanPlaybackV3(PlannerInputV3{Request: req, RequestedFile: file, EffectiveFile: file, AudioTrackIndex: 0, Settings: PlannerSettingsV3{TranscodeEnabled: true, Allow4KTranscode: true}})
|
|
if result.Terminal == nil || result.Terminal.Reason != "hdr_transcode_unsupported" {
|
|
t.Fatalf("result = %s", ExplainPlannerResultV3(result))
|
|
}
|
|
}
|
|
|
|
func TestPlanPlaybackV3Profile7StripFallsBackToValidatedHLSCopy(t *testing.T) {
|
|
file := detailedFixtureFileV3()
|
|
file.VideoTracks[0].DVProfile = 7
|
|
file.VideoTracks[0].DVBLCompatID = 1
|
|
file.VideoTracks[0].DVELPresent = true
|
|
file.VideoTracks[0].DVEnhancementLayer = "mel"
|
|
file.VideoTracks[0].VideoRange = "DolbyVision"
|
|
file.VideoTracks[0].VideoRangeType = "DOVIWithEL"
|
|
req := validStartRequestV3()
|
|
req.ClientFeatures = append(req.ClientFeatures, FeatureDetailedDecodeV3)
|
|
req.ClientPlaybackContext.Features = append(req.ClientPlaybackContext.Features, FeatureDetailedDecodeV3)
|
|
req.Capabilities.Containers = []string{"mp4"}
|
|
req.Capabilities.VideoDecode = []VideoDecodeCapabilityV3{{Codec: "hevc", Profiles: []string{"main 10"}, Levels: []int{153}, BitDepths: []int{10}, MaxWidth: 3840, MaxHeight: 2160, MaxFrameRate: 60, MaxBitrateKbps: 80_000, Hardware: true}}
|
|
req.Capabilities.HDRDetails = &HDRCapabilitiesV3{HDR10: true, DolbyVisionProfiles: []int{7}}
|
|
registry := NewTransformationRegistryV3([]TransformationSpecV3{{Name: "server_dv7_to_hdr10", Available: true}})
|
|
first := PlanPlaybackV3(PlannerInputV3{Request: req, RequestedFile: file, EffectiveFile: file, AudioTrackIndex: 0, Settings: PlannerSettingsV3{TranscodeEnabled: true, Allow4KTranscode: true}, Registry: registry})
|
|
if first.Plan == nil || first.Plan.Delivery != DeliveryRemuxProgressiveV3 || len(first.Plan.Transformations) != 1 {
|
|
t.Fatalf("first = %#v", first)
|
|
}
|
|
failedKey := PlanAttemptKeyV3(*first.Plan, req.OutputRouteGeneration, nil)
|
|
second := PlanPlaybackV3(PlannerInputV3{Request: req, RequestedFile: file, EffectiveFile: file, AudioTrackIndex: 0, Settings: PlannerSettingsV3{TranscodeEnabled: true, Allow4KTranscode: true}, Registry: registry, AttemptedKeys: []string{failedKey}})
|
|
if second.Plan == nil || second.Plan.Delivery != DeliveryRemuxHLSV3 || second.TargetVideoCodec != "copy" || len(second.Plan.Transformations) != 1 || second.Plan.Transformations[0].Name != "server_dv7_to_hdr10" {
|
|
t.Fatalf("second = %#v", second)
|
|
}
|
|
}
|
|
|
|
func TestPlanPlaybackV3Profile8CompatibleBaseLayerStripsToHDR10(t *testing.T) {
|
|
file := detailedFixtureFileV3()
|
|
file.VideoTracks[0].DVProfile = 8
|
|
file.VideoTracks[0].DVBLCompatID = 1
|
|
file.VideoTracks[0].VideoRange = "DolbyVision"
|
|
file.VideoTracks[0].VideoRangeType = "DOVI"
|
|
req := validStartRequestV3()
|
|
req.ClientFeatures = append(req.ClientFeatures, FeatureDetailedDecodeV3)
|
|
req.ClientPlaybackContext.Features = append(req.ClientPlaybackContext.Features, FeatureDetailedDecodeV3)
|
|
req.Capabilities.VideoDecode = []VideoDecodeCapabilityV3{{Codec: "hevc", Profiles: []string{"main 10"}, Levels: []int{153}, BitDepths: []int{10}, MaxWidth: 3840, MaxHeight: 2160, MaxFrameRate: 60, MaxBitrateKbps: 80_000, Hardware: true}}
|
|
req.Capabilities.HDRDetails = &HDRCapabilitiesV3{HDR10: true}
|
|
registry := NewTransformationRegistryV3([]TransformationSpecV3{{Name: "server_dv7_to_hdr10", Available: true}})
|
|
result := PlanPlaybackV3(PlannerInputV3{Request: req, RequestedFile: file, EffectiveFile: file, AudioTrackIndex: 0, Settings: PlannerSettingsV3{TranscodeEnabled: true, Allow4KTranscode: true}, Registry: registry})
|
|
if result.Plan == nil || result.Plan.Delivery != DeliveryRemuxProgressiveV3 || result.Plan.EffectiveRecipe.DynamicRange != "hdr10" || len(result.Plan.Transformations) != 1 || result.Plan.Transformations[0].Name != "server_dv7_to_hdr10" {
|
|
t.Fatalf("result = %#v", result)
|
|
}
|
|
}
|
|
|
|
func TestPlanPlaybackV3PassthroughRequiresExactLayoutEntry(t *testing.T) {
|
|
file := detailedFixtureFileV3()
|
|
file.CodecAudio = "truehd"
|
|
file.AudioTracks[0] = models.AudioTrack{Codec: "truehd", Channels: 8, Layout: "7.1"}
|
|
req := validStartRequestV3()
|
|
req.ClientFeatures = append(req.ClientFeatures, FeatureDetailedDecodeV3, FeatureLayoutPassthrough)
|
|
req.ClientPlaybackContext.Features = append(req.ClientPlaybackContext.Features, FeatureDetailedDecodeV3, FeatureLayoutPassthrough)
|
|
req.Capabilities.VideoDecode = []VideoDecodeCapabilityV3{{Codec: "hevc", Profiles: []string{"main 10"}, Levels: []int{153}, BitDepths: []int{10}, MaxWidth: 3840, MaxHeight: 2160, MaxFrameRate: 60, MaxBitrateKbps: 80_000, Hardware: true}}
|
|
req.Capabilities.HDRDetails = &HDRCapabilitiesV3{HDR10: true}
|
|
req.Capabilities.AudioPassthrough = &AudioPassthroughV3{PassthroughCodecs: []string{"truehd"}, MaxChannels: 8, Entries: []AudioPassthroughEntryV3{{Codec: "truehd"}}}
|
|
withoutLayout := PlanPlaybackV3(PlannerInputV3{Request: req, RequestedFile: file, EffectiveFile: file, AudioTrackIndex: 0, Settings: PlannerSettingsV3{TranscodeEnabled: true, Allow4KTranscode: true}, Registry: testTransformationRegistryV3()})
|
|
if withoutLayout.Plan == nil || !withoutLayout.TranscodeAudio {
|
|
t.Fatalf("without exact layout = %#v", withoutLayout)
|
|
}
|
|
req.Capabilities.AudioPassthrough.Entries[0].ChannelCounts = []int{8}
|
|
req.Capabilities.AudioPassthrough.Entries[0].Layouts = []string{"7.1"}
|
|
withLayout := PlanPlaybackV3(PlannerInputV3{Request: req, RequestedFile: file, EffectiveFile: file, AudioTrackIndex: 0, Settings: PlannerSettingsV3{TranscodeEnabled: true, Allow4KTranscode: true}})
|
|
if withLayout.Plan == nil || withLayout.Plan.Delivery != DeliveryOriginalHTTPV3 || !withLayout.Plan.Claims.Audio.Passthrough {
|
|
t.Fatalf("with exact layout = %#v", withLayout)
|
|
}
|
|
}
|
|
|
|
func TestPlanPlaybackV3DownloadedSubtitleUsesFrozenCombinedOrdinal(t *testing.T) {
|
|
file := detailedFixtureFileV3()
|
|
req := validStartRequestV3()
|
|
req.ClientFeatures = append(req.ClientFeatures, FeatureDetailedDecodeV3)
|
|
req.ClientPlaybackContext.Features = append(req.ClientPlaybackContext.Features, FeatureDetailedDecodeV3)
|
|
req.Capabilities.VideoDecode = []VideoDecodeCapabilityV3{{Codec: "hevc", Profiles: []string{"main 10"}, Levels: []int{153}, BitDepths: []int{10}, MaxWidth: 3840, MaxHeight: 2160, MaxFrameRate: 60, MaxBitrateKbps: 80_000, Hardware: true}}
|
|
req.Capabilities.HDRDetails = &HDRCapabilitiesV3{HDR10: true}
|
|
index := 0
|
|
req.SubtitleTrackIndex = &index
|
|
req.SubtitleTrackID = TrackIDV3(file.ID, "subtitle", index)
|
|
result := PlanPlaybackV3(PlannerInputV3{Request: req, RequestedFile: file, EffectiveFile: file, AudioTrackIndex: 0, Settings: PlannerSettingsV3{TranscodeEnabled: true, Allow4KTranscode: true}, AdditionalSubtitles: []SubtitleInventoryEntryV3{{CombinedIndex: 0, Codec: "srt", Source: "downloaded"}}})
|
|
if result.Plan == nil || result.Plan.Subtitle.Mode != SubtitleRenderV3 || result.Plan.SelectedTracks.Subtitle == nil || result.Plan.SelectedTracks.Subtitle.ID != req.SubtitleTrackID {
|
|
t.Fatalf("result = %#v", result)
|
|
}
|
|
}
|
|
|
|
func TestSubtitleBurnInUsesEmbeddedOrdinalAndRejectsUnsupportedSources(t *testing.T) {
|
|
file := detailedFixtureFileV3()
|
|
file.ExternalSubtitles = []models.ExternalSubtitle{{Format: "ass"}}
|
|
file.SubtitleTracks = []models.SubtitleTrack{{Codec: "hdmv_pgs_subtitle"}}
|
|
req := validStartRequestV3()
|
|
embeddedCombinedIndex := 1
|
|
req.SubtitleTrackIndex = &embeddedCombinedIndex
|
|
req.SubtitleTrackID = TrackIDV3(file.ID, "subtitle", embeddedCombinedIndex)
|
|
result := ResolveSubtitlePolicyV3(file, req, true, EngineMedia3DirectV3, nil)
|
|
if !result.RequiresBurn || result.SelectedIndex != 1 || result.TransportIndex != 0 {
|
|
t.Fatalf("embedded burn-in result = %#v", result)
|
|
}
|
|
|
|
externalIndex := 0
|
|
req.SubtitleTrackIndex = &externalIndex
|
|
req.SubtitleTrackID = TrackIDV3(file.ID, "subtitle", externalIndex)
|
|
req.SubtitleFidelityPreference = SubtitleFidelityPreserveV3
|
|
result = ResolveSubtitlePolicyV3(file, req, true, EngineMedia3DirectV3, nil)
|
|
if result.Terminal == nil || result.Terminal.Reason != "subtitle_burn_in_source_unsupported" {
|
|
t.Fatalf("external burn-in result = %#v", result)
|
|
}
|
|
}
|
|
|
|
func TestResolveQualityPolicyV3HonorsBandwidthCapInAllModes(t *testing.T) {
|
|
source := SourceDescriptorV3{Width: 3840, Height: 2160, BitrateKbps: 20_000}
|
|
cap := 5_000
|
|
|
|
req := validStartRequestV3()
|
|
req.QualityPreference = "original"
|
|
req.BandwidthCapKbps = &cap
|
|
result := ResolveQualityPolicyV3(req, source)
|
|
if !result.RequiresTranscode || result.Height != 720 || result.Reason != "quality_bandwidth_cap" || result.ExplicitRung {
|
|
t.Fatalf("original over cap = %#v", result)
|
|
}
|
|
if !hasDegradationWarningV3(result.Warnings, "bandwidth_cap_applied") {
|
|
t.Fatalf("missing cap warning: %#v", result.Warnings)
|
|
}
|
|
|
|
lowBitrateSource := SourceDescriptorV3{Width: 1920, Height: 1080, BitrateKbps: 4_000}
|
|
result = ResolveQualityPolicyV3(req, lowBitrateSource)
|
|
if !result.PreservesSource || result.RequiresTranscode || len(result.Warnings) != 0 {
|
|
t.Fatalf("original under cap = %#v", result)
|
|
}
|
|
|
|
req.QualityPreference = "1080p"
|
|
result = ResolveQualityPolicyV3(req, source)
|
|
if result.Height != 720 || result.Reason != "quality_bandwidth_cap" || !result.ExplicitRung || !result.RequiresTranscode {
|
|
t.Fatalf("fixed rung over cap = %#v", result)
|
|
}
|
|
if !hasDegradationWarningV3(result.Warnings, "bandwidth_cap_applied") {
|
|
t.Fatalf("missing cap warning: %#v", result.Warnings)
|
|
}
|
|
|
|
req.QualityPreference = "480p"
|
|
result = ResolveQualityPolicyV3(req, source)
|
|
if result.Height != 480 || result.Reason != "quality_fixed_rung" || hasDegradationWarningV3(result.Warnings, "bandwidth_cap_applied") {
|
|
t.Fatalf("fixed rung under cap = %#v", result)
|
|
}
|
|
|
|
req.QualityPreference = "auto"
|
|
result = ResolveQualityPolicyV3(req, source)
|
|
if !result.RequiresTranscode || result.Height != 720 || result.PreservesSource {
|
|
t.Fatalf("auto with cap = %#v", result)
|
|
}
|
|
}
|
|
|
|
func TestResolveQualityPolicyV3MeteredWithoutEvidencePrefersConservativeRung(t *testing.T) {
|
|
source := SourceDescriptorV3{Width: 3840, Height: 2160, BitrateKbps: 20_000}
|
|
req := validStartRequestV3()
|
|
req.QualityPreference = "auto"
|
|
req.Metered = true
|
|
result := ResolveQualityPolicyV3(req, source)
|
|
if result.Height != 720 || !result.RequiresTranscode || result.Reason != "quality_metered_limit" || result.ExplicitRung {
|
|
t.Fatalf("metered auto = %#v", result)
|
|
}
|
|
|
|
estimate := 30_000
|
|
req.BandwidthEstimateKbps = &estimate
|
|
result = ResolveQualityPolicyV3(req, source)
|
|
if !result.PreservesSource || result.Reason != "quality_bandwidth_limit" {
|
|
t.Fatalf("metered with estimate = %#v", result)
|
|
}
|
|
|
|
req.BandwidthEstimateKbps = nil
|
|
req.Metered = false
|
|
result = ResolveQualityPolicyV3(req, source)
|
|
if !result.PreservesSource || result.RequiresTranscode {
|
|
t.Fatalf("unmetered auto = %#v", result)
|
|
}
|
|
}
|
|
|
|
func TestPlanPlaybackV3HDRDeviceCapFallsBackToOriginalQuality(t *testing.T) {
|
|
file := detailedFixtureFileV3()
|
|
req := validStartRequestV3()
|
|
req.QualityPreference = "auto"
|
|
req.Capabilities.MaxResolution = "1080p"
|
|
req.ClientFeatures = append(req.ClientFeatures, FeatureDetailedDecodeV3)
|
|
req.ClientPlaybackContext.Features = append(req.ClientPlaybackContext.Features, FeatureDetailedDecodeV3)
|
|
req.Capabilities.VideoDecode = []VideoDecodeCapabilityV3{{Codec: "hevc", Profiles: []string{"main 10"}, Levels: []int{153}, BitDepths: []int{10}, MaxWidth: 3840, MaxHeight: 2160, MaxFrameRate: 60, MaxBitrateKbps: 80_000, Hardware: true}}
|
|
req.Capabilities.HDRDetails = &HDRCapabilitiesV3{HDR10: true}
|
|
req.ClientPlaybackContext.Output.HDRDetails = req.Capabilities.HDRDetails
|
|
|
|
result := PlanPlaybackV3(PlannerInputV3{Request: req, RequestedFile: file, EffectiveFile: file, AudioTrackIndex: 0, Settings: PlannerSettingsV3{TranscodeEnabled: true, Allow4KTranscode: true}, Registry: testTransformationRegistryV3()})
|
|
if result.Plan == nil || result.Plan.Delivery != DeliveryOriginalHTTPV3 || !result.Plan.Claims.Video.HDR10 {
|
|
t.Fatalf("result = %s", ExplainPlannerResultV3(result))
|
|
}
|
|
if result.Plan.EffectiveRecipe.Height == nil || *result.Plan.EffectiveRecipe.Height != 2160 {
|
|
t.Fatalf("recipe = %#v", result.Plan.EffectiveRecipe)
|
|
}
|
|
if !hasDegradationWarningV3(result.Plan.DegradationWarnings, "quality_reduction_unavailable") {
|
|
t.Fatalf("warnings = %#v", result.Plan.DegradationWarnings)
|
|
}
|
|
}
|
|
|
|
func TestPlanPlaybackV3HDRBandwidthCapFallsBackToOriginalQuality(t *testing.T) {
|
|
file := detailedFixtureFileV3()
|
|
req := validStartRequestV3()
|
|
req.QualityPreference = "auto"
|
|
cap := 5_000
|
|
req.BandwidthCapKbps = &cap
|
|
req.ClientFeatures = append(req.ClientFeatures, FeatureDetailedDecodeV3)
|
|
req.ClientPlaybackContext.Features = append(req.ClientPlaybackContext.Features, FeatureDetailedDecodeV3)
|
|
req.Capabilities.VideoDecode = []VideoDecodeCapabilityV3{{Codec: "hevc", Profiles: []string{"main 10"}, Levels: []int{153}, BitDepths: []int{10}, MaxWidth: 3840, MaxHeight: 2160, MaxFrameRate: 60, MaxBitrateKbps: 80_000, Hardware: true}}
|
|
req.Capabilities.HDRDetails = &HDRCapabilitiesV3{HDR10: true}
|
|
req.ClientPlaybackContext.Output.HDRDetails = req.Capabilities.HDRDetails
|
|
|
|
result := PlanPlaybackV3(PlannerInputV3{Request: req, RequestedFile: file, EffectiveFile: file, AudioTrackIndex: 0, Settings: PlannerSettingsV3{TranscodeEnabled: true, Allow4KTranscode: true}, Registry: testTransformationRegistryV3()})
|
|
if result.Plan == nil || result.Plan.Delivery != DeliveryOriginalHTTPV3 || !result.Plan.Claims.Video.HDR10 {
|
|
t.Fatalf("result = %s", ExplainPlannerResultV3(result))
|
|
}
|
|
if !hasDegradationWarningV3(result.Plan.DegradationWarnings, "quality_reduction_unavailable") {
|
|
t.Fatalf("warnings = %#v", result.Plan.DegradationWarnings)
|
|
}
|
|
}
|
|
|
|
func TestPlanPlaybackV3CapWithoutTranscodeRouteFallsBackToOriginal(t *testing.T) {
|
|
file := detailedFixtureFileV3()
|
|
file.Resolution = "1080p"
|
|
file.VideoTracks[0].Width = 1920
|
|
file.VideoTracks[0].Height = 1080
|
|
file.VideoTracks[0].Bitrate = 8_000
|
|
file.VideoTracks[0].VideoRange = "SDR"
|
|
file.VideoTracks[0].VideoRangeType = "SDR"
|
|
file.VideoTracks[0].ColorTransfer = "bt709"
|
|
req := validStartRequestV3()
|
|
req.QualityPreference = "original"
|
|
cap := 4_000
|
|
req.BandwidthCapKbps = &cap
|
|
req.ClientFeatures = append(req.ClientFeatures, FeatureDetailedDecodeV3)
|
|
req.ClientPlaybackContext.Features = append(req.ClientPlaybackContext.Features, FeatureDetailedDecodeV3)
|
|
req.Capabilities.VideoDecode = []VideoDecodeCapabilityV3{{Codec: "hevc", Profiles: []string{"main 10"}, Levels: []int{153}, BitDepths: []int{10}, MaxWidth: 3840, MaxHeight: 2160, MaxFrameRate: 60, MaxBitrateKbps: 80_000, Hardware: true}}
|
|
// The client has no HLS engine, so the cap-induced transcode cannot run.
|
|
delete(req.ClientPlaybackContext.Engines, string(EngineMedia3HLSV3))
|
|
|
|
result := PlanPlaybackV3(PlannerInputV3{Request: req, RequestedFile: file, EffectiveFile: file, AudioTrackIndex: 0, Settings: PlannerSettingsV3{TranscodeEnabled: true, Allow4KTranscode: true}, Registry: testTransformationRegistryV3()})
|
|
if result.Plan == nil || result.Plan.Delivery != DeliveryOriginalHTTPV3 {
|
|
t.Fatalf("result = %s", ExplainPlannerResultV3(result))
|
|
}
|
|
if !hasDegradationWarningV3(result.Plan.DegradationWarnings, "quality_reduction_unavailable") {
|
|
t.Fatalf("warnings = %#v", result.Plan.DegradationWarnings)
|
|
}
|
|
}
|
|
|
|
func TestPlanPlaybackV3LegacyHDRUnknownAssumesHDR10ForCapableClients(t *testing.T) {
|
|
file := detailedFixtureFileV3()
|
|
file.HDR = true
|
|
file.VideoTracks[0].VideoRange = ""
|
|
file.VideoTracks[0].VideoRangeType = ""
|
|
file.VideoTracks[0].ColorTransfer = ""
|
|
req := validStartRequestV3()
|
|
req.ClientFeatures = append(req.ClientFeatures, FeatureDetailedDecodeV3)
|
|
req.ClientPlaybackContext.Features = append(req.ClientPlaybackContext.Features, FeatureDetailedDecodeV3)
|
|
req.Capabilities.VideoDecode = []VideoDecodeCapabilityV3{{Codec: "hevc", Profiles: []string{"main 10"}, Levels: []int{153}, BitDepths: []int{10}, MaxWidth: 3840, MaxHeight: 2160, MaxFrameRate: 60, MaxBitrateKbps: 80_000, Hardware: true}}
|
|
req.Capabilities.HDRDetails = &HDRCapabilitiesV3{HDR10: true}
|
|
req.ClientPlaybackContext.Output.HDRDetails = req.Capabilities.HDRDetails
|
|
|
|
result := PlanPlaybackV3(PlannerInputV3{Request: req, RequestedFile: file, EffectiveFile: file, AudioTrackIndex: 0, Settings: PlannerSettingsV3{TranscodeEnabled: true, Allow4KTranscode: true}, Registry: testTransformationRegistryV3()})
|
|
if result.Plan == nil || result.Plan.Delivery != DeliveryOriginalHTTPV3 || !result.Plan.Claims.Video.HDR10 {
|
|
t.Fatalf("result = %s", ExplainPlannerResultV3(result))
|
|
}
|
|
if !hasDegradationWarningV3(result.Plan.DegradationWarnings, "hdr_range_assumed_hdr10") {
|
|
t.Fatalf("warnings = %#v", result.Plan.DegradationWarnings)
|
|
}
|
|
|
|
// Without HDR10 output support the legacy row keeps the previous
|
|
// (ineligible) behavior instead of guessing at the client's range.
|
|
req.Capabilities.HDRDetails = nil
|
|
req.ClientPlaybackContext.Output.HDRDetails = nil
|
|
result = PlanPlaybackV3(PlannerInputV3{Request: req, RequestedFile: file, EffectiveFile: file, AudioTrackIndex: 0, Settings: PlannerSettingsV3{TranscodeEnabled: true, Allow4KTranscode: true}, Registry: testTransformationRegistryV3()})
|
|
if result.Terminal == nil || result.Terminal.Reason != "hdr_transcode_unsupported" {
|
|
t.Fatalf("result = %s", ExplainPlannerResultV3(result))
|
|
}
|
|
}
|
|
|
|
func TestSourceDescriptorV3NormalizesLegacyFileBitrateFallback(t *testing.T) {
|
|
file := detailedFixtureFileV3()
|
|
file.VideoTracks[0].Bitrate = 0
|
|
file.Bitrate = 60_000_000 // legacy rows stored bps
|
|
|
|
source := SourceDescriptorFromFileV3(file, 0)
|
|
if source.BitrateKbps != 60_000 {
|
|
t.Fatalf("file-level bitrate = %d, want normalized 60000", source.BitrateKbps)
|
|
}
|
|
|
|
file.VideoTracks[0].Bitrate = 45_000_000
|
|
source = SourceDescriptorFromFileV3(file, 0)
|
|
if source.BitrateKbps != 45_000 {
|
|
t.Fatalf("track-level bitrate = %d, want normalized 45000", source.BitrateKbps)
|
|
}
|
|
}
|
|
|
|
func TestPlanAttemptedV3RequiresExactKeyMatch(t *testing.T) {
|
|
plan := PlanV3{PlanID: "plan:exact", Delivery: DeliveryOriginalHTTPV3, Stream: StreamV3{Protocol: StreamHTTPProgressiveV3, Container: "mkv"}, Subtitle: SubtitleDecisionV3{Mode: SubtitleOffV3}}
|
|
key := PlanAttemptKeyV3(plan, 1, nil)
|
|
if !planAttemptedV3(plan, 1, []string{" " + key + " "}) {
|
|
t.Fatal("whitespace-trimmed exact key must match")
|
|
}
|
|
if planAttemptedV3(plan, 1, []string{strings.ToUpper(key)}) {
|
|
t.Fatal("case-folded attempt key must not match an exact hash")
|
|
}
|
|
}
|
|
|
|
func TestStartRequestV3ValidationBoundsInnerLists(t *testing.T) {
|
|
longValue := strings.Repeat("x", 65)
|
|
cases := []struct {
|
|
name string
|
|
mutate func(*StartRequestV3)
|
|
}{
|
|
{"video_decode_profile_count", func(r *StartRequestV3) {
|
|
r.Capabilities.VideoDecode = []VideoDecodeCapabilityV3{{Codec: "h264", Hardware: true, Profiles: make([]string, 65)}}
|
|
}},
|
|
{"video_decode_profile_length", func(r *StartRequestV3) {
|
|
r.Capabilities.VideoDecode = []VideoDecodeCapabilityV3{{Codec: "h264", Hardware: true, Profiles: []string{longValue}}}
|
|
}},
|
|
{"video_decode_level_count", func(r *StartRequestV3) {
|
|
r.Capabilities.VideoDecode = []VideoDecodeCapabilityV3{{Codec: "h264", Hardware: true, Levels: make([]int, 65)}}
|
|
}},
|
|
{"video_decode_bit_depth_count", func(r *StartRequestV3) {
|
|
r.Capabilities.VideoDecode = []VideoDecodeCapabilityV3{{Codec: "h264", Hardware: true, BitDepths: make([]int, 65)}}
|
|
}},
|
|
{"capability_dolby_vision_profiles", func(r *StartRequestV3) {
|
|
r.Capabilities.HDRDetails = &HDRCapabilitiesV3{DolbyVisionProfiles: make([]int, 17)}
|
|
}},
|
|
{"output_dolby_vision_profiles", func(r *StartRequestV3) {
|
|
r.ClientPlaybackContext.Output.HDRDetails = &HDRCapabilitiesV3{DolbyVisionProfiles: make([]int, 17)}
|
|
}},
|
|
{"engine_dolby_vision_profiles", func(r *StartRequestV3) {
|
|
engine := r.ClientPlaybackContext.Engines[string(EngineMedia3DirectV3)]
|
|
engine.HDRDetails = &HDRCapabilitiesV3{DolbyVisionProfiles: make([]int, 17)}
|
|
r.ClientPlaybackContext.Engines[string(EngineMedia3DirectV3)] = engine
|
|
}},
|
|
{"engine_container_length", func(r *StartRequestV3) {
|
|
engine := r.ClientPlaybackContext.Engines[string(EngineMedia3DirectV3)]
|
|
engine.Containers = []string{longValue}
|
|
r.ClientPlaybackContext.Engines[string(EngineMedia3DirectV3)] = engine
|
|
}},
|
|
{"engine_validated_claim_length", func(r *StartRequestV3) {
|
|
engine := r.ClientPlaybackContext.Engines[string(EngineMedia3DirectV3)]
|
|
engine.ValidatedClaims = []string{longValue}
|
|
r.ClientPlaybackContext.Engines[string(EngineMedia3DirectV3)] = engine
|
|
}},
|
|
{"engine_feature_length", func(r *StartRequestV3) {
|
|
engine := r.ClientPlaybackContext.Engines[string(EngineMedia3DirectV3)]
|
|
engine.Features = []string{longValue}
|
|
r.ClientPlaybackContext.Engines[string(EngineMedia3DirectV3)] = engine
|
|
}},
|
|
{"audio_track_id_length", func(r *StartRequestV3) {
|
|
r.AudioTrackID = strings.Repeat("a", 129)
|
|
}},
|
|
{"subtitle_track_id_length", func(r *StartRequestV3) {
|
|
r.SubtitleTrackID = strings.Repeat("s", 129)
|
|
}},
|
|
}
|
|
for _, value := range cases {
|
|
t.Run(value.name, func(t *testing.T) {
|
|
req := validStartRequestV3()
|
|
value.mutate(&req)
|
|
if _, err := req.NormalizeAndValidate(); err == nil {
|
|
t.Fatal("oversized capability accepted")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestResolveQualityPolicyV3PreservesNonStandardSourceHeight(t *testing.T) {
|
|
req := validStartRequestV3()
|
|
req.QualityPreference = "auto"
|
|
result := ResolveQualityPolicyV3(req, SourceDescriptorV3{Width: 2560, Height: 1440, BitrateKbps: 12_000})
|
|
if !result.PreservesSource || result.RequiresTranscode || result.Width != 2560 || result.Height != 1440 || result.Label != "1440p" {
|
|
t.Fatalf("quality result = %#v", result)
|
|
}
|
|
}
|
|
|
|
func TestPlanPlaybackV3SeekedHLSCopyPreservesVideo(t *testing.T) {
|
|
file := detailedFixtureFileV3()
|
|
file.Resolution = "1080p"
|
|
file.VideoTracks[0].Width = 1920
|
|
file.VideoTracks[0].Height = 1080
|
|
file.VideoTracks[0].BitDepth = 8
|
|
file.VideoTracks[0].VideoRange = "SDR"
|
|
file.VideoTracks[0].VideoRangeType = "SDR"
|
|
file.VideoTracks[0].ColorTransfer = "bt709"
|
|
req := validStartRequestV3()
|
|
start := 20.0
|
|
req.StartPosition = &start
|
|
req.ClientFeatures = append(req.ClientFeatures, FeatureDetailedDecodeV3)
|
|
req.ClientPlaybackContext.Features = append(req.ClientPlaybackContext.Features, FeatureDetailedDecodeV3)
|
|
req.Capabilities.Containers = []string{"mp4"}
|
|
req.Capabilities.VideoDecode = []VideoDecodeCapabilityV3{{Codec: "hevc", Profiles: []string{"main 10"}, Levels: []int{153}, BitDepths: []int{8}, MaxWidth: 1920, MaxHeight: 1080, MaxFrameRate: 60, MaxBitrateKbps: 80_000, Hardware: true}}
|
|
req.ClientPlaybackContext.Engines[string(EngineMedia3DirectV3)] = EngineCapabilityV3{}
|
|
req.ClientPlaybackContext.Engines[string(EngineMedia3ProgressiveRemuxV3)] = EngineCapabilityV3{}
|
|
result := PlanPlaybackV3(PlannerInputV3{Request: req, RequestedFile: file, EffectiveFile: file, AudioTrackIndex: 0, Settings: PlannerSettingsV3{TranscodeEnabled: true, Allow4KTranscode: true}, Registry: testTransformationRegistryV3()})
|
|
if result.Plan == nil || result.Plan.Delivery != DeliveryRemuxHLSV3 || result.TargetVideoCodec != "copy" || result.Plan.Timeline.SourceStartSeconds != start {
|
|
t.Fatalf("result = %#v", result)
|
|
}
|
|
}
|
|
|
|
func TestPlanPlaybackV3TimelineChangePreservesRouteIdentity(t *testing.T) {
|
|
file := detailedFixtureFileV3()
|
|
request := validStartRequestV3()
|
|
request.ClientFeatures = append(request.ClientFeatures, FeatureDetailedDecodeV3)
|
|
request.ClientPlaybackContext.Features = append(request.ClientPlaybackContext.Features, FeatureDetailedDecodeV3)
|
|
request.Capabilities.VideoDecode = []VideoDecodeCapabilityV3{{Codec: "hevc", Profiles: []string{"main 10"}, Levels: []int{153}, BitDepths: []int{10}, MaxWidth: 3840, MaxHeight: 2160, MaxFrameRate: 60, MaxBitrateKbps: 80_000, Hardware: true}}
|
|
hdr := &HDRCapabilitiesV3{HDR10: true}
|
|
request.Capabilities.HDR = true
|
|
request.Capabilities.HDRDetails = hdr
|
|
request.ClientPlaybackContext.Output.HDRDetails = hdr
|
|
startAtZero := 0.0
|
|
request.StartPosition = &startAtZero
|
|
first := PlanPlaybackV3(PlannerInputV3{
|
|
Request: request, RequestedFile: file, EffectiveFile: file, AudioTrackIndex: 0,
|
|
Settings: PlannerSettingsV3{TranscodeEnabled: true, Allow4KTranscode: true}, Registry: testTransformationRegistryV3(),
|
|
})
|
|
if first.Plan == nil {
|
|
t.Fatalf("initial plan = %#v", first)
|
|
}
|
|
|
|
startAtSeek := 321.25
|
|
request.StartPosition = &startAtSeek
|
|
second := PlanPlaybackV3(PlannerInputV3{
|
|
Request: request, RequestedFile: file, EffectiveFile: file, AudioTrackIndex: 0,
|
|
Settings: PlannerSettingsV3{TranscodeEnabled: true, Allow4KTranscode: true}, Registry: testTransformationRegistryV3(),
|
|
})
|
|
if second.Plan == nil {
|
|
t.Fatalf("seeked plan = %#v", second)
|
|
}
|
|
if first.Plan.PlanID != second.Plan.PlanID ||
|
|
PlanAttemptKeyV3(*first.Plan, request.OutputRouteGeneration, nil) != PlanAttemptKeyV3(*second.Plan, request.OutputRouteGeneration, nil) {
|
|
t.Fatalf("timeline changed route identity: first=%#v second=%#v", first.Plan, second.Plan)
|
|
}
|
|
if first.Plan.Timeline.SourceStartSeconds != 0 || second.Plan.Timeline.SourceStartSeconds != startAtSeek {
|
|
t.Fatalf("timeline positions: first=%#v second=%#v", first.Plan.Timeline, second.Plan.Timeline)
|
|
}
|
|
}
|
|
|
|
func validStartRequestV3() StartRequestV3 {
|
|
return StartRequestV3{
|
|
ProtocolVersion: ProtocolV3,
|
|
ClientFeatures: []string{FeaturePlaybackPlanV3, FeatureMedia3Only},
|
|
FileID: 42,
|
|
ProfileID: "profile-1",
|
|
PlaybackAttemptID: "attempt-0001",
|
|
QualityPreference: "original",
|
|
SubtitleFidelityPreference: SubtitleFidelityCompatibleV3,
|
|
OutputRouteGeneration: 1,
|
|
Capabilities: ClientCodecCapabilitiesV3{CodecsVideo: []string{"hevc"}, CodecsAudio: []string{"aac"}, Containers: []string{"mkv"}, MaxResolution: "2160p"},
|
|
ClientPlaybackContext: ClientPlaybackContextV3{ProtocolVersion: ProtocolV3, Features: []string{FeaturePlaybackPlanV3, FeatureMedia3Only}, Platform: "android", FormFactor: "tv", AppVersion: "test", Output: OutputContextV3{OutputRouteGeneration: 1}, Engines: map[string]EngineCapabilityV3{
|
|
string(EngineMedia3DirectV3): {Enabled: true, SupportedOnDevice: true, Subtitles: EngineSubtitleCapabilitiesV3{EmbeddedText: true, SidecarText: true}},
|
|
string(EngineMedia3ProgressiveRemuxV3): {Enabled: true, SupportedOnDevice: true},
|
|
string(EngineMedia3HLSV3): {Enabled: true, SupportedOnDevice: true},
|
|
}},
|
|
}
|
|
}
|
|
|
|
func detailedFixtureFileV3() *models.MediaFile {
|
|
return &models.MediaFile{ID: 42, FilePath: "/media/movie.mkv", Container: "mkv", CodecVideo: "hevc", CodecAudio: "aac", Resolution: "2160p", Bitrate: 60_000, AudioChannels: 2, VideoTracks: []models.VideoTrack{{Codec: "hevc", Profile: "Main 10", Level: 153, Width: 3840, Height: 2160, FrameRate: "24000/1001", Bitrate: 60_000, BitDepth: 10, VideoRange: "HDR", VideoRangeType: "HDR10", ColorRange: "tv"}}, AudioTracks: []models.AudioTrack{{Codec: "aac", Channels: 2, Layout: "stereo"}}}
|
|
}
|
|
|
|
func testTransformationRegistryV3() *TransformationRegistryV3 {
|
|
return NewTransformationRegistryV3([]TransformationSpecV3{
|
|
{Name: "audio_to_aac", Available: true},
|
|
{Name: "video_to_h264", Available: true},
|
|
{Name: "server_dv7_to_hdr10", Available: true},
|
|
})
|
|
}
|
|
|
|
// A source whose RPU ffmpeg cannot parse must lose the strip in the plan, not
|
|
// at the transport. The plan is what promises HDR10, and the durable session's
|
|
// RemuxDVMode — re-read by every later restart, seek and audio switch — is
|
|
// derived from it, so a plan that still names server_dv7_to_hdr10 puts the
|
|
// hanging filter back no matter what the start path did with it. With no
|
|
// tone-map recipe in the tree there is no route left for an HDR10-only client,
|
|
// and the terminal has to name the real cause.
|
|
func TestPlanPlaybackV3AbandonsStripForAnUnstrippableSource(t *testing.T) {
|
|
file := unstrippableProfile7FixtureV3()
|
|
req := hdr10OnlyProfile7RequestV3()
|
|
registry := NewTransformationRegistryV3([]TransformationSpecV3{{Name: "server_dv7_to_hdr10", Available: true}})
|
|
input := PlannerInputV3{Request: req, RequestedFile: file, EffectiveFile: file, AudioTrackIndex: 0, Settings: PlannerSettingsV3{TranscodeEnabled: true, Allow4KTranscode: true}, Registry: registry}
|
|
|
|
// Baseline: with a parseable RPU this is the validated HDR10 remux.
|
|
if healthy := PlanPlaybackV3(input); healthy.Plan == nil || len(healthy.Plan.Transformations) != 1 || healthy.Plan.Transformations[0].Name != "server_dv7_to_hdr10" {
|
|
t.Fatalf("the strip route regressed for a healthy source: %#v", healthy)
|
|
}
|
|
|
|
input.DVRPUStrippable = func() bool { return false }
|
|
result := PlanPlaybackV3(input)
|
|
if result.Plan != nil {
|
|
t.Fatalf("a source that cannot be stripped was still planned onto a route: %#v", result.Plan)
|
|
}
|
|
if result.Terminal == nil || result.Terminal.Reason != "dv_conversion_unsupported" {
|
|
t.Fatalf("terminal = %#v, want the Dolby Vision cause rather than a generic HDR message", result.Terminal)
|
|
}
|
|
}
|
|
|
|
// The strip is a server capability, not the only one: a client that can do the
|
|
// conversion itself must still get its route, with the reason the server route
|
|
// was dropped attached.
|
|
func TestPlanPlaybackV3KeepsTheClientTransformWhenTheSourceCannotBeStripped(t *testing.T) {
|
|
file := unstrippableProfile7FixtureV3()
|
|
req := hdr10OnlyProfile7RequestV3()
|
|
req.ClientFeatures = append(req.ClientFeatures, FeatureClientVideoTransforms)
|
|
req.ClientPlaybackContext.Features = append(req.ClientPlaybackContext.Features, FeatureClientVideoTransforms)
|
|
direct := req.ClientPlaybackContext.Engines[string(EngineMedia3DirectV3)]
|
|
direct.Transformations = []TransformationV3{{Name: ClientDV7ToHDR10V3, Executor: "client", RecipeVersion: ClientDVTransformVersionV3}}
|
|
req.ClientPlaybackContext.Engines[string(EngineMedia3DirectV3)] = direct
|
|
registry := NewTransformationRegistryV3([]TransformationSpecV3{{Name: "server_dv7_to_hdr10", Available: true}})
|
|
|
|
result := PlanPlaybackV3(PlannerInputV3{
|
|
Request: req, RequestedFile: file, EffectiveFile: file, AudioTrackIndex: 0,
|
|
Settings: PlannerSettingsV3{TranscodeEnabled: true, Allow4KTranscode: true}, Registry: registry,
|
|
DVRPUStrippable: func() bool { return false },
|
|
})
|
|
if result.Plan == nil {
|
|
t.Fatalf("terminal = %#v, want the client-side transformation route", result.Terminal)
|
|
}
|
|
for _, transformation := range result.Plan.Transformations {
|
|
if transformation.Name == "server_dv7_to_hdr10" {
|
|
t.Fatalf("the unusable server strip survived: %#v", result.Plan.Transformations)
|
|
}
|
|
}
|
|
if !hasDegradationWarningV3(result.Plan.DegradationWarnings, "dolby_vision_strip_unsupported_by_source") {
|
|
t.Fatalf("the client was not told why the server route was dropped: %#v", result.Plan.DegradationWarnings)
|
|
}
|
|
}
|
|
|
|
func unstrippableProfile7FixtureV3() *models.MediaFile {
|
|
file := detailedFixtureFileV3()
|
|
file.VideoTracks[0].DVProfile = 7
|
|
file.VideoTracks[0].DVBLCompatID = 6
|
|
file.VideoTracks[0].DVELPresent = false
|
|
file.VideoTracks[0].DVEnhancementLayer = ""
|
|
file.VideoTracks[0].VideoRange = "DolbyVision"
|
|
file.VideoTracks[0].VideoRangeType = "DOVIWithEL"
|
|
return file
|
|
}
|
|
|
|
func hdr10OnlyProfile7RequestV3() StartRequestV3 {
|
|
req := validStartRequestV3()
|
|
req.ClientFeatures = append(req.ClientFeatures, FeatureDetailedDecodeV3)
|
|
req.ClientPlaybackContext.Features = append(req.ClientPlaybackContext.Features, FeatureDetailedDecodeV3)
|
|
req.Capabilities.VideoDecode = []VideoDecodeCapabilityV3{{Codec: "hevc", Profiles: []string{"main 10"}, Levels: []int{153}, BitDepths: []int{10}, MaxWidth: 3840, MaxHeight: 2160, MaxFrameRate: 60, MaxBitrateKbps: 80_000, Hardware: true}}
|
|
req.Capabilities.HDRDetails = &HDRCapabilitiesV3{HDR10: true, DolbyVisionProfiles: []int{5, 8}}
|
|
req.ClientPlaybackContext.Output.HDRDetails = req.Capabilities.HDRDetails
|
|
return req
|
|
}
|
|
|
|
// The probe is expensive, so it must sit behind every cheap gate: a source
|
|
// nobody would strip anyway must never spawn one.
|
|
func TestPlanPlaybackV3DoesNotProbeWhenNoStripIsOnTheTable(t *testing.T) {
|
|
file := detailedFixtureFileV3()
|
|
file.VideoTracks[0].VideoRange = "SDR"
|
|
file.VideoTracks[0].VideoRangeType = "SDR"
|
|
file.VideoTracks[0].BitDepth = 8
|
|
req := validStartRequestV3()
|
|
probed := false
|
|
result := PlanPlaybackV3(PlannerInputV3{
|
|
Request: req, RequestedFile: file, EffectiveFile: file, AudioTrackIndex: 0,
|
|
Settings: PlannerSettingsV3{TranscodeEnabled: true, Allow4KTranscode: true},
|
|
Registry: testTransformationRegistryV3(),
|
|
DVRPUStrippable: func() bool { probed = true; return true },
|
|
})
|
|
if result.Plan == nil {
|
|
t.Fatalf("terminal = %#v", result.Terminal)
|
|
}
|
|
if probed {
|
|
t.Fatal("an ordinary SDR source paid for a Dolby Vision RPU probe")
|
|
}
|
|
}
|