2026-08-06 16:43:21 +02:00
|
|
|
package playback
|
|
|
|
|
|
|
|
|
|
// ExecutableRecipeV3 is the frozen operational half of a protocol-v3 plan.
|
|
|
|
|
// PlanV3 describes the client-visible route identity; these fields are the
|
|
|
|
|
// additional inputs needed to open another transport for that same route.
|
|
|
|
|
// Keeping them with the durable attempt prevents seek reanchoring from
|
|
|
|
|
// reverse-engineering execution details from presentation fields or mutable
|
|
|
|
|
// planner inputs.
|
|
|
|
|
type ExecutableRecipeV3 struct {
|
|
|
|
|
Version int `json:"version"`
|
|
|
|
|
PlanID string `json:"plan_id"`
|
|
|
|
|
PlayMethod PlayMethod `json:"play_method"`
|
|
|
|
|
TranscodeAudio bool `json:"transcode_audio"`
|
|
|
|
|
TargetVideoCodec string `json:"target_video_codec,omitempty"`
|
|
|
|
|
TargetAudioCodec string `json:"target_audio_codec,omitempty"`
|
|
|
|
|
TargetAudioChannels int `json:"target_audio_channels,omitempty"`
|
2026-08-10 18:14:49 -04:00
|
|
|
TargetAudioBitrateKbps int `json:"target_audio_bitrate_kbps,omitempty"`
|
2026-08-06 16:43:21 +02:00
|
|
|
TargetResolution string `json:"target_resolution,omitempty"`
|
|
|
|
|
TargetBitrateKbps int `json:"target_bitrate_kbps,omitempty"`
|
|
|
|
|
SourceVideoCodec string `json:"source_video_codec,omitempty"`
|
2026-08-10 18:14:49 -04:00
|
|
|
SoftwareVideoDecode bool `json:"software_video_decode,omitempty"`
|
2026-08-06 16:43:21 +02:00
|
|
|
SourceDurationSeconds float64 `json:"source_duration_seconds,omitempty"`
|
|
|
|
|
SubtitleTrackIndex int `json:"subtitle_track_index"`
|
|
|
|
|
SubtitleTransportTrackIndex int `json:"subtitle_transport_track_index"`
|
|
|
|
|
SubtitleBurnIn bool `json:"subtitle_burn_in"`
|
|
|
|
|
SubtitleCodec string `json:"subtitle_codec,omitempty"`
|
|
|
|
|
// SubtitleSource pins which sidecar inventory segment SubtitleTrackIndex
|
|
|
|
|
// pointed into when the plan was accepted, and the identity fields below
|
|
|
|
|
// pin the exact entry. The combined index space (externals, then embedded,
|
|
|
|
|
// then downloaded) shifts when any segment grows or shrinks; a seek
|
|
|
|
|
// reanchor must detect that drift and fail rather than silently resolve
|
|
|
|
|
// the frozen index to a different artifact.
|
|
|
|
|
SubtitleSource string `json:"subtitle_source,omitempty"`
|
|
|
|
|
ExternalSubtitlePath string `json:"external_subtitle_path,omitempty"`
|
|
|
|
|
EmbeddedStreamIndex int `json:"embedded_stream_index,omitempty"`
|
|
|
|
|
DownloadedSubtitleID int `json:"downloaded_subtitle_id,omitempty"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const executableRecipeVersionV3 = 1
|
|
|
|
|
|
|
|
|
|
func FreezeExecutableRecipeV3(result PlannerResultV3) ExecutableRecipeV3 {
|
|
|
|
|
planID := ""
|
|
|
|
|
if result.Plan != nil {
|
|
|
|
|
planID = result.Plan.PlanID
|
|
|
|
|
}
|
|
|
|
|
sourceMetadata := SourceExecutionMetadataV3{}
|
|
|
|
|
if result.FrozenSourceMetadata != nil {
|
|
|
|
|
sourceMetadata = *result.FrozenSourceMetadata
|
|
|
|
|
}
|
|
|
|
|
return ExecutableRecipeV3{
|
|
|
|
|
Version: executableRecipeVersionV3,
|
|
|
|
|
PlanID: planID,
|
|
|
|
|
PlayMethod: result.PlayMethod,
|
|
|
|
|
TranscodeAudio: result.TranscodeAudio,
|
|
|
|
|
TargetVideoCodec: result.TargetVideoCodec,
|
|
|
|
|
TargetAudioCodec: result.TargetAudioCodec,
|
|
|
|
|
TargetAudioChannels: result.TargetAudioChannels,
|
2026-08-10 18:14:49 -04:00
|
|
|
TargetAudioBitrateKbps: result.TargetAudioBitrateKbps,
|
2026-08-06 16:43:21 +02:00
|
|
|
TargetResolution: result.TargetResolution,
|
|
|
|
|
TargetBitrateKbps: result.TargetBitrateKbps,
|
|
|
|
|
SourceVideoCodec: sourceMetadata.VideoCodec,
|
2026-08-10 18:14:49 -04:00
|
|
|
SoftwareVideoDecode: sourceMetadata.SoftwareVideoDecode,
|
2026-08-06 16:43:21 +02:00
|
|
|
SourceDurationSeconds: sourceMetadata.DurationSeconds,
|
|
|
|
|
SubtitleTrackIndex: result.SubtitleTrackIndex,
|
|
|
|
|
SubtitleTransportTrackIndex: result.SubtitleTransportTrackIndex,
|
|
|
|
|
SubtitleBurnIn: result.SubtitleBurnIn,
|
|
|
|
|
SubtitleCodec: result.SubtitleCodec,
|
|
|
|
|
DownloadedSubtitleID: result.DownloadedSubtitleID,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r ExecutableRecipeV3) Valid() bool {
|
|
|
|
|
if r.Version != executableRecipeVersionV3 || r.PlanID == "" {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
switch r.PlayMethod {
|
|
|
|
|
case PlayDirect, PlayRemux, PlayTranscode:
|
|
|
|
|
return true
|
|
|
|
|
default:
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r ExecutableRecipeV3) ValidFor(plan PlanV3) bool {
|
|
|
|
|
return r.Valid() && r.PlanID == plan.PlanID
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r ExecutableRecipeV3) PlannerResult(plan *PlanV3) PlannerResultV3 {
|
|
|
|
|
return PlannerResultV3{
|
2026-08-10 18:14:49 -04:00
|
|
|
Plan: plan,
|
|
|
|
|
PlayMethod: r.PlayMethod,
|
|
|
|
|
TranscodeAudio: r.TranscodeAudio,
|
|
|
|
|
TargetVideoCodec: r.TargetVideoCodec,
|
|
|
|
|
TargetAudioCodec: r.TargetAudioCodec,
|
|
|
|
|
TargetAudioChannels: r.TargetAudioChannels,
|
|
|
|
|
TargetAudioBitrateKbps: r.TargetAudioBitrateKbps,
|
|
|
|
|
TargetResolution: r.TargetResolution,
|
|
|
|
|
TargetBitrateKbps: r.TargetBitrateKbps,
|
2026-08-06 16:43:21 +02:00
|
|
|
FrozenSourceMetadata: &SourceExecutionMetadataV3{
|
2026-08-10 18:14:49 -04:00
|
|
|
VideoCodec: r.SourceVideoCodec,
|
|
|
|
|
SoftwareVideoDecode: r.SoftwareVideoDecode,
|
|
|
|
|
DurationSeconds: r.SourceDurationSeconds,
|
2026-08-06 16:43:21 +02:00
|
|
|
},
|
|
|
|
|
SubtitleTrackIndex: r.SubtitleTrackIndex,
|
|
|
|
|
SubtitleTransportTrackIndex: r.SubtitleTransportTrackIndex,
|
|
|
|
|
SubtitleBurnIn: r.SubtitleBurnIn,
|
|
|
|
|
SubtitleCodec: r.SubtitleCodec,
|
|
|
|
|
DownloadedSubtitleID: r.DownloadedSubtitleID,
|
|
|
|
|
}
|
|
|
|
|
}
|