Files

122 lines
4.7 KiB
Go
Raw Permalink Normal View History

package playback
import (
"bytes"
"context"
"os/exec"
"sort"
"strings"
"time"
)
type TransformationSpecV3 struct {
Name string
RecipeVersion string
Available bool
RequiredCapability string
PromisedDynamicRange string
ValidatedClaims []string
TerminalReason string
}
type TransformationRegistryV3 struct {
entries map[string]TransformationSpecV3
}
func ProbeTransformationRegistryV3(ctx context.Context, ffmpegPath string) *TransformationRegistryV3 {
// Resolve exactly like the execution paths (remux and transcode) so every
// capability advertised here holds for the binary that later runs.
ffmpegPath = ResolveFFmpegPath(ffmpegPath)
bsfCtx, cancelBSF := context.WithTimeout(ctx, 3*time.Second)
bsfs, _ := exec.CommandContext(bsfCtx, ffmpegPath, "-hide_banner", "-bsfs").Output()
cancelBSF()
encoderCtx, cancelEncoders := context.WithTimeout(ctx, 3*time.Second)
encoders, _ := exec.CommandContext(encoderCtx, ffmpegPath, "-hide_banner", "-encoders").Output()
cancelEncoders()
_, ffmpegErr := exec.LookPath(ffmpegPath)
return NewTransformationRegistryV3([]TransformationSpecV3{
{Name: TransformationServerDV7HDR10V3, RecipeVersion: "1", Available: bytes.Contains(bsfs, []byte("dovi_rpu")), RequiredCapability: "ffmpeg_bsf:dovi_rpu", PromisedDynamicRange: DynamicRangeHDR10V3, ValidatedClaims: DV7ToHDR10ClaimsV3(), TerminalReason: TerminalDVConversionUnsupportedV3},
{Name: TransformationAudioToAACV3, RecipeVersion: "1", Available: ffmpegErr == nil && bytes.Contains(encoders, []byte(" aac ")), RequiredCapability: "ffmpeg_encoder:aac", ValidatedClaims: []string{ClaimAudioDecodeV3}, TerminalReason: TerminalAudioConversionUnsupportedV3},
{Name: TransformationVideoToH264V3, RecipeVersion: TransformationVideoToH264RecipeVersionV3, Available: ffmpegErr == nil && h264EncoderAvailableV3(encoders), RequiredCapability: "ffmpeg_encoder:h264", PromisedDynamicRange: DynamicRangeSDRV3, ValidatedClaims: []string{ClaimH264DecodeV3}, TerminalReason: TerminalVideoConversionUnsupportedV3},
})
}
// h264EncodersV3 lists every H.264 encoder the transcode pipeline can select
// (see buildTranscodeArgs' hardware ladder in transcode.go); any one of them
// satisfies the video_to_h264 transformation.
var h264EncodersV3 = []string{"libx264", "h264_qsv", "h264_vaapi", "h264_nvenc", "h264_videotoolbox"}
func h264EncoderAvailableV3(encoders []byte) bool {
for _, encoder := range h264EncodersV3 {
if bytes.Contains(encoders, []byte(encoder)) {
return true
}
}
return false
}
func NewTransformationRegistryV3(specs []TransformationSpecV3) *TransformationRegistryV3 {
r := &TransformationRegistryV3{entries: make(map[string]TransformationSpecV3, len(specs))}
for _, spec := range specs {
if spec.Name != "" {
r.entries[spec.Name] = spec
}
}
return r
}
func (r *TransformationRegistryV3) Available(name string) bool {
if r == nil {
return false
}
spec, ok := r.entries[name]
return ok && spec.Available
}
// WithAdvertised returns a registry whose known specs are additionally marked
// available when a pooled transcode node advertises the same server-executed
// transformation at the same recipe version. Advertisements never introduce
// new specs: the planner only selects transformations this server defines,
// and pinning versions to the local spec guarantees a plan built from the
// widened registry passes the per-node advertisement validation at transport
// time. Returns the receiver unchanged when nothing new becomes available.
func (r *TransformationRegistryV3) WithAdvertised(advertised []TransformationV3) *TransformationRegistryV3 {
if r == nil || len(advertised) == 0 {
return r
}
specs := make([]TransformationSpecV3, 0, len(r.entries))
changed := false
for _, spec := range r.entries {
if !spec.Available {
for _, remote := range advertised {
if strings.EqualFold(strings.TrimSpace(remote.Name), spec.Name) &&
strings.TrimSpace(remote.RecipeVersion) == spec.RecipeVersion &&
strings.EqualFold(strings.TrimSpace(remote.Executor), "server") {
spec.Available = true
changed = true
break
}
}
}
specs = append(specs, spec)
}
if !changed {
return r
}
return NewTransformationRegistryV3(specs)
}
func (r *TransformationRegistryV3) Advertised() []TransformationV3 {
if r == nil {
return nil
}
result := make([]TransformationV3, 0, len(r.entries))
for _, spec := range r.entries {
if spec.Available {
result = append(result, TransformationV3{Name: spec.Name, Executor: ExecutorServerV3, RecipeVersion: spec.RecipeVersion, ValidatedClaims: append([]string(nil), spec.ValidatedClaims...)})
}
}
sort.Slice(result, func(i, j int) bool { return result[i].Name < result[j].Name })
return result
}