package main import ( "encoding/xml" "flag" "fmt" "git.gammaspectra.live/WeebDataHoarder/go-dcp/dcp" "git.gammaspectra.live/WeebDataHoarder/go-dcp/utils" "os" "os/exec" "strings" "sync" ) func main() { mpvCmd := flag.String("mpv", "mpv", "mpv command path") xyz2yuvCmd := flag.String("xyz2yuv", "xyz2yuv", "xyz2yuv command path") dcpPath := flag.String("dcp", "", "Path to DCP folder or ASSETMAP") pklUUID := flag.String("pkl-id", "", "PKL ID to use. Empty for first PKL found") customCPL := flag.String("custom-cpl", "", "Custom CPL path to use.") reelIDs := flag.String("drop-reel-ids", "", "IDs of reels to drop. Use to skip ads or logos") audioMapping := flag.String("audio-channel-mapping", "auto", "Audio channel mapping, usually 5.1/2.0/1.0 etc. Leave empty to disable") doASS := flag.Bool("ass", true, "Encode subtitles if present to ASS") width := flag.Int("width", 1998, "Video stream width for subtitles. Default is 2K Flat") height := flag.Int("height", 1080, "Video stream height for subtitles. Default is 2K Flat") flag.Parse() dcPackage, err := dcp.Open(*dcpPath) if err != nil { panic(err) } var cpl dcp.CompositionPlaylist var dropReelUUIDs []string for _, id := range strings.Split(*reelIDs, ",") { dropReelUUIDs = append(dropReelUUIDs, utils.CleanUUID(id)) } if *customCPL == "" { cpl, err = utils.SelectCPL(dcPackage, *pklUUID) if err != nil { panic(err) } } else { cplContents, err := os.ReadFile(*customCPL) if err != nil { panic(err) } err = xml.Unmarshal(cplContents, &cpl) if err != nil { panic(err) } if !(cpl.IsSMPTE() && dcPackage.IsSMPTE()) && !(cpl.IsIOP() && dcPackage.IsIOP()) { panic(fmt.Errorf("unexpected namespace %s", cpl.XMLName.Space)) } } err = utils.Concat(dcPackage, cpl, utils.ConcatSettings{ DropReelUUIDs: dropReelUUIDs, Subtitles: *doASS, Width: *width, Height: *height, }, func(desc, videoFile, audioFile, subtitleFile string) { af := "" switch strings.ToLower(strings.ReplaceAll(strings.ReplaceAll(*audioMapping, ".", ""), "-", "")) { case "71": af = "pan=7.1|c0=c0|c1=c1|c2=c2|c3=c3|c4=c4|c5=c5|c6=c6|c7=c7" case "51": af = "pan=5.1|c0=c0|c1=c1|c2=c2|c3=c3|c4=c4|c5=c5" case "50": af = "pan=5.0|c0=c0|c1=c1|c2=c2|c3=c4|c4=c5" case "20": af = "pan=2.0|c0=c0|c1=c1" case "10", "10center": af = "pan=1c|c0=c2" case "10side", "10fl": af = "pan=1c|c0=c0" case "10fr": af = "pan=1c|c0=c1" } xyz2yuv := []string{ "-lowres", "0", "-limited", "-depth", "10", "-in", videoFile, //TODO: allow configuring "-colorspace", "rec2020_pure24", "-out", "-", } mpv := []string{ fmt.Sprintf("--force-media-title=%s", desc), // allow concat demuxer input files "--demuxer-lavf-o=safe=0", //"--msg-level=vo=v", "--demuxer-max-bytes=4096MiB", "--demuxer-max-back-bytes=512M", "--cache-secs=30", "--demuxer-seekable-cache=yes", "--hr-seek=yes", "--demuxer-cache-wait=yes", "--cache=yes", "--force-seekable=yes", "-", fmt.Sprintf("--vf=format=colormatrix=%s:colorlevels=limited:primaries=%s:gamma=%s", "bt.2020-ncl", "bt.2020", "bt.1886"), fmt.Sprintf("--external-file=%s", audioFile), } if subtitleFile != "" { mpv = append(mpv, fmt.Sprintf("--external-file=%s", subtitleFile)) } mpv = append(mpv, "--vid=1", "--aid=1", "--sid=1") if af != "" { mpv = append(mpv, fmt.Sprintf("--af=\"lavfi=%s\"", af), ) } xyz2yuvProcess := exec.Command(*xyz2yuvCmd, xyz2yuv...) xyz2yuvProcess.Stdin = os.Stdin xyz2yuvProcess.Stderr = os.Stderr mpvProcess := exec.Command(*mpvCmd, mpv...) mpvProcess.Stdin, err = xyz2yuvProcess.StdoutPipe() if err != nil { panic(err) } mpvProcess.Stdout = os.Stdout mpvProcess.Stderr = os.Stderr err = xyz2yuvProcess.Start() if err != nil { defer xyz2yuvProcess.Process.Release() panic(err) } err = mpvProcess.Start() if err != nil { defer mpvProcess.Process.Release() panic(err) } var wg sync.WaitGroup wg.Add(2) go func() { defer wg.Done() _ = xyz2yuvProcess.Wait() }() go func() { defer wg.Done() defer xyz2yuvProcess.Process.Kill() _ = mpvProcess.Wait() }() wg.Wait() }) if err != nil { panic(err) } }