go-dcp/cmd/playback/playback.go

182 lines
4.2 KiB
Go

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)
}
}