Add ffmpeg cli decoder, default env variables for VMAF_MODEL_PATH / FFMPEG_PATH
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
94f9eab69f
commit
883dad8b84
|
@ -38,6 +38,8 @@ Legend:
|
||||||
| **VP9** | ❌ | ❌ | |
|
| **VP9** | ❌ | ❌ | |
|
||||||
| **AV1** | ✅ | ✅ | Supports 8-bit, 10-bit and 12-bit; 4:0:0, 4:2:0, 4:2:2, 4:4:4 chroma subsampling.</br>Decoding via [dav1d](https://code.videolan.org/videolan/dav1d) from .ivf bitstream</br>Encoding via [libaom-av1](https://aomedia.googlesource.com/aom) into .ivf bitstream. |
|
| **AV1** | ✅ | ✅ | Supports 8-bit, 10-bit and 12-bit; 4:0:0, 4:2:0, 4:2:2, 4:4:4 chroma subsampling.</br>Decoding via [dav1d](https://code.videolan.org/videolan/dav1d) from .ivf bitstream</br>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
|
# TODO
|
||||||
* No SAR/PAR handling.
|
* No SAR/PAR handling.
|
||||||
* No color primary / transfer / matrix coefficients handling.
|
* No color primary / transfer / matrix coefficients handling.
|
||||||
|
|
|
@ -7,7 +7,7 @@ ENV CGO_CFLAGS="-march=native -Ofast"
|
||||||
|
|
||||||
RUN DEBIAN_FRONTEND=noninteractive apt update && \
|
RUN DEBIAN_FRONTEND=noninteractive apt update && \
|
||||||
DEBIAN_FRONTEND=noninteractive apt install -y --no-install-recommends \
|
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/*
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
ENV PKG_CONFIG_PATH=/usr/lib/pkgconfig:/usr/lib64/pkgconfig
|
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 \
|
RUN CGO_ENABLED=0 go build -v \
|
||||||
-o /usr/bin/encode-pool git.gammaspectra.live/S.O.N.G/Ignite/cli/encode-pool
|
-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 /
|
WORKDIR /
|
20
cli/encode-server/decoder_ffmpeg.go
Normal file
20
cli/encode-server/decoder_ffmpeg.go
Normal file
|
@ -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)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
|
@ -7,8 +7,9 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
DecoderY4M = "y4m"
|
DecoderFFMPEG = "ffmpeg"
|
||||||
DecoderDav1d = "libdav1d"
|
DecoderY4M = "y4m"
|
||||||
|
DecoderDav1d = "libdav1d"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DecoderEntry struct {
|
type DecoderEntry struct {
|
||||||
|
@ -28,6 +29,7 @@ func GetDecoderByName(name string) *DecoderEntry {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDecoderByMimeType(mimeType string) *DecoderEntry {
|
func GetDecoderByMimeType(mimeType string) *DecoderEntry {
|
||||||
if i := slices.IndexFunc(Decoders, func(entry DecoderEntry) bool {
|
if i := slices.IndexFunc(Decoders, func(entry DecoderEntry) bool {
|
||||||
return entry.MimeType == mimeType
|
return entry.MimeType == mimeType
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"github.com/ulikunitz/xz"
|
"github.com/ulikunitz/xz"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
|
"maps"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
@ -46,8 +47,12 @@ func encodeFromReader(reader io.ReadCloser, job *Job, inputMimeType string, w ht
|
||||||
|
|
||||||
d := GetDecoderByMimeType(inputMimeType)
|
d := GetDecoderByMimeType(inputMimeType)
|
||||||
if d == nil {
|
if d == nil {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
//try for generic reader
|
||||||
return
|
d = GetDecoderByMimeType("*")
|
||||||
|
if d == nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
|
@ -59,6 +64,7 @@ func encodeFromReader(reader io.ReadCloser, job *Job, inputMimeType string, w ht
|
||||||
job.Status.Processed.Store(0)
|
job.Status.Processed.Store(0)
|
||||||
|
|
||||||
settings := make(map[string]any)
|
settings := make(map[string]any)
|
||||||
|
maps.Copy(settings, job.Config.Decoder.Settings)
|
||||||
|
|
||||||
if len(job.Config.Timecodes) > 0 {
|
if len(job.Config.Timecodes) > 0 {
|
||||||
settings["timecodes"] = job.Config.Timecodes
|
settings["timecodes"] = job.Config.Timecodes
|
||||||
|
|
|
@ -13,6 +13,10 @@ type JobConfig struct {
|
||||||
|
|
||||||
Properties frame.StreamProperties `json:"properties" yaml:"properties"`
|
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"`
|
TimecodesV1 string `json:"timecodes_v1" yaml:"timecodes_v1"`
|
||||||
Timecodes utilities.Timecodes `json:"timecodes" yaml:"timecodes"`
|
Timecodes utilities.Timecodes `json:"timecodes" yaml:"timecodes"`
|
||||||
}
|
}
|
||||||
|
|
91
decoder/ffmpeg/ffmpeg.go
Normal file
91
decoder/ffmpeg/ffmpeg.go
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -60,6 +60,11 @@ func NewEncoder(w io.Writer, properties frame.StreamProperties, settings map[str
|
||||||
}
|
}
|
||||||
|
|
||||||
clonedSettings := make(map[string]any)
|
clonedSettings := make(map[string]any)
|
||||||
|
|
||||||
|
if modelPath := os.Getenv("VMAF_MODEL_PATH"); modelPath != "" {
|
||||||
|
clonedSettings["vmaf-model-path"] = modelPath
|
||||||
|
}
|
||||||
|
|
||||||
maps.Copy(clonedSettings, settings)
|
maps.Copy(clonedSettings, settings)
|
||||||
|
|
||||||
photonNoiseIso := getSettingUnsigned[uint](clonedSettings, "photon-noise-iso", 0)
|
photonNoiseIso := getSettingUnsigned[uint](clonedSettings, "photon-noise-iso", 0)
|
||||||
|
|
Loading…
Reference in a new issue