diff --git a/README.md b/README.md index de47c68..1c2b8c0 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,8 @@ Legend: | **VP9** | ❌ | ❌ | | | **AV1** | ✅ | ✅ | Supports 8-bit, 10-bit and 12-bit; 4:0:0, 4:2:0, 4:2:2, 4:4:4 chroma subsampling.
Decoding via [dav1d](https://code.videolan.org/videolan/dav1d) from .ivf bitstream
Encoding via [libaom-av1](https://aomedia.googlesource.com/aom) into .ivf bitstream. | +Additionally, a safe command-line call to ffmpeg can be made to decode into compatible YUV4MPEG2 via the integrated ffmpeg decoder. + # TODO * No SAR/PAR handling. * No color primary / transfer / matrix coefficients handling. diff --git a/build/debian-shared/Dockerfile b/build/debian-shared/Dockerfile index 2380713..4c3d367 100644 --- a/build/debian-shared/Dockerfile +++ b/build/debian-shared/Dockerfile @@ -7,7 +7,7 @@ ENV CGO_CFLAGS="-march=native -Ofast" RUN DEBIAN_FRONTEND=noninteractive apt update && \ DEBIAN_FRONTEND=noninteractive apt install -y --no-install-recommends \ - git gcc g++ musl-dev bash nasm autoconf automake cmake make libtool gettext pkg-config meson ccache perl xxd && \ + git gcc g++ musl-dev bash nasm autoconf automake cmake make libtool gettext pkg-config meson ccache perl xxd xz-utils && \ rm -rf /var/lib/apt/lists/* ENV PKG_CONFIG_PATH=/usr/lib/pkgconfig:/usr/lib64/pkgconfig @@ -64,4 +64,10 @@ RUN go build -v \ RUN CGO_ENABLED=0 go build -v \ -o /usr/bin/encode-pool git.gammaspectra.live/S.O.N.G/Ignite/cli/encode-pool +ADD https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz /tmp/ffmpeg-release.tar.xz +RUN tar -xJf /tmp/ffmpeg-release.tar.xz -C /tmp && mv /tmp/ffmpeg-*-amd64-static/ffmpeg /usr/bin/ffmpeg && rm -rf /tmp/ffmpeg-* + +ENV VMAF_MODEL_PATH="/usr/share/model/vmaf_v0.6.1.json" +ENV FFMPEG_PATH="/usr/bin/ffmpeg" + WORKDIR / \ No newline at end of file diff --git a/cli/encode-server/decoder_ffmpeg.go b/cli/encode-server/decoder_ffmpeg.go new file mode 100644 index 0000000..691c0c8 --- /dev/null +++ b/cli/encode-server/decoder_ffmpeg.go @@ -0,0 +1,20 @@ +package main + +import ( + "git.gammaspectra.live/S.O.N.G/Ignite/decoder" + "git.gammaspectra.live/S.O.N.G/Ignite/decoder/ffmpeg" + "io" +) + +func init() { + Decoders = append(Decoders, DecoderEntry{ + Name: DecoderFFMPEG, + Version: func() string { + return "1.0" + }, + MimeType: "*", + New: func(w io.Reader, settings map[string]any) (decoder.Decoder, error) { + return ffmpeg.NewDecoder(w, settings) + }, + }) +} diff --git a/cli/encode-server/decoders.go b/cli/encode-server/decoders.go index b49ad7b..0702cf2 100644 --- a/cli/encode-server/decoders.go +++ b/cli/encode-server/decoders.go @@ -7,8 +7,9 @@ import ( ) const ( - DecoderY4M = "y4m" - DecoderDav1d = "libdav1d" + DecoderFFMPEG = "ffmpeg" + DecoderY4M = "y4m" + DecoderDav1d = "libdav1d" ) type DecoderEntry struct { @@ -28,6 +29,7 @@ func GetDecoderByName(name string) *DecoderEntry { } return nil } + func GetDecoderByMimeType(mimeType string) *DecoderEntry { if i := slices.IndexFunc(Decoders, func(entry DecoderEntry) bool { return entry.MimeType == mimeType diff --git a/cli/encode-server/encode.go b/cli/encode-server/encode.go index eb90471..c2d9218 100644 --- a/cli/encode-server/encode.go +++ b/cli/encode-server/encode.go @@ -10,6 +10,7 @@ import ( "github.com/ulikunitz/xz" "io" "log" + "maps" "net/http" "os" "time" @@ -46,8 +47,12 @@ func encodeFromReader(reader io.ReadCloser, job *Job, inputMimeType string, w ht d := GetDecoderByMimeType(inputMimeType) if d == nil { - w.WriteHeader(http.StatusBadRequest) - return + //try for generic reader + d = GetDecoderByMimeType("*") + if d == nil { + w.WriteHeader(http.StatusBadRequest) + return + } } defer func() { @@ -59,6 +64,7 @@ func encodeFromReader(reader io.ReadCloser, job *Job, inputMimeType string, w ht job.Status.Processed.Store(0) settings := make(map[string]any) + maps.Copy(settings, job.Config.Decoder.Settings) if len(job.Config.Timecodes) > 0 { settings["timecodes"] = job.Config.Timecodes diff --git a/cli/encode-utils/config.go b/cli/encode-utils/config.go index decf237..5fe5080 100644 --- a/cli/encode-utils/config.go +++ b/cli/encode-utils/config.go @@ -13,6 +13,10 @@ type JobConfig struct { Properties frame.StreamProperties `json:"properties" yaml:"properties"` + Decoder struct { + Settings map[string]any `json:"settings" yaml:"settings"` + } + TimecodesV1 string `json:"timecodes_v1" yaml:"timecodes_v1"` Timecodes utilities.Timecodes `json:"timecodes" yaml:"timecodes"` } diff --git a/decoder/ffmpeg/ffmpeg.go b/decoder/ffmpeg/ffmpeg.go new file mode 100644 index 0000000..7f7e12a --- /dev/null +++ b/decoder/ffmpeg/ffmpeg.go @@ -0,0 +1,91 @@ +package ffmpeg + +import ( + "fmt" + "git.gammaspectra.live/S.O.N.G/Ignite/decoder/y4m" + "git.gammaspectra.live/S.O.N.G/Ignite/frame" + "git.gammaspectra.live/S.O.N.G/Ignite/utilities" + "io" + "os" + "os/exec" + "strconv" +) + +type Decoder struct { + cmd *exec.Cmd + y4m *y4m.Decoder +} + +func NewDecoder(r io.Reader, settings map[string]any) (d *Decoder, err error) { + videoIndex, err := strconv.ParseUint(utilities.GetSettingString(settings, "input-video-index", "0"), 10, 0) + if err != nil { + return nil, err + } + + ffmpegBinary := "ffmpeg" + if p := os.Getenv("FFMPEG_PATH"); p != "" { + ffmpegBinary = p + } + + cmd := exec.Command(ffmpegBinary, + //"-v", "quiet", + "-strict", "experimental", + "-i", "-", + "-map_metadata", "-1", + "-map", fmt.Sprintf("0:v:%d", videoIndex), + "-f", "yuv4mpegpipe", + "-strict", "experimental", + "-", + ) + cmd.Stdin = r + + pipeR, pipeW := io.Pipe() + cmd.Stdout = pipeW + + cmd.Stderr = os.Stderr + + err = cmd.Start() + if err != nil { + return nil, err + } + + d = &Decoder{ + cmd: cmd, + } + + d.y4m, err = y4m.NewDecoder(pipeR, settings) + if err != nil { + d.Close() + return nil, err + } + + return d, nil +} + +func (d *Decoder) Decode() (frame.Frame, error) { + return d.y4m.Decode() +} + +func (d *Decoder) DecodeStream() *frame.Stream { + return d.y4m.DecodeStream() +} + +func (d *Decoder) Properties() frame.StreamProperties { + return d.y4m.Properties() +} + +func (d *Decoder) Version() string { + return d.y4m.Version() +} + +func (d *Decoder) Close() { + if d.y4m != nil { + d.y4m.Close() + d.y4m = nil + } + if d.cmd != nil { + d.cmd.Process.Kill() + d.cmd.Process.Release() + d.cmd = nil + } +} diff --git a/encoder/libaom/libaom.go b/encoder/libaom/libaom.go index 7ba1a78..2f9cbcd 100644 --- a/encoder/libaom/libaom.go +++ b/encoder/libaom/libaom.go @@ -60,6 +60,11 @@ func NewEncoder(w io.Writer, properties frame.StreamProperties, settings map[str } clonedSettings := make(map[string]any) + + if modelPath := os.Getenv("VMAF_MODEL_PATH"); modelPath != "" { + clonedSettings["vmaf-model-path"] = modelPath + } + maps.Copy(clonedSettings, settings) photonNoiseIso := getSettingUnsigned[uint](clonedSettings, "photon-noise-iso", 0)