From 120df4a73484873ba212518d8f5094e7dd54365d Mon Sep 17 00:00:00 2001 From: WeebDataHoarder <57538841+WeebDataHoarder@users.noreply.github.com> Date: Thu, 10 Aug 2023 12:44:17 +0200 Subject: [PATCH] Cleanup and add samples for tests, decode .y4m.xz streams --- .dockerignore | 1 + README.md | 29 +++++++++- color/space.go | 8 +++ decoder/libdav1d/libdav1d_test.go | 78 ++++++++++++++++++++++----- decoder/y4m/y4m.go | 9 ++++ decoder/y4m/y4m_test.go | 90 +++++++++++++++++++++++++++---- encoder/libaom/libaom_test.go | 63 +++++++++++++++++----- encoder/libx264/libx264_test.go | 89 +++++++++++++++++++++++++----- frame/frame.go | 6 +++ go.mod | 1 + go.sum | 2 + testdata/.gitignore | 1 + testdata/prepare.sh | 14 ++++- testdata/testdata.go | 84 +++++++++++++++++++++++++++-- utilities/libvmaf/libvmaf_test.go | 4 +- utilities/settings.go | 39 ++++++++++++++ 16 files changed, 458 insertions(+), 60 deletions(-) create mode 100644 utilities/settings.go diff --git a/.dockerignore b/.dockerignore index d165b43..712058a 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,4 @@ testdata/*.ivf testdata/*.y4m +testdata/*.y4m.xz testdata/testoutput \ No newline at end of file diff --git a/README.md b/README.md index 6869b3f..d21c9e1 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,35 @@ # Supported * y4m pipes -* 4:4:4, 4:2:0, and probably 4:2:2 and 4:0:0. -* 8, 10, 12 bit depth. Probably 14 and 16 as well. +* Frame tested support for 4:4:4, 4:2:2, 4:2:0. Probably 4:0:0 as well, untested. +* Frame tested support for 8, 10, 12 bit depth. Probably 14 and 16 as well, untested. +* VMAF tools +* IVF reader +* Frameserver * TODO: make list per encoder and decoder. +## Bitdepth / subsampling Frame support matrix + +| Depth/Sampling | 4:0:0 | 4:2:0 | 4:2:2 | 4:4:4 | +|:--------------:|:-----:|:-----:|:-----:|:-----:| +| 8-bit | ⚠️ | ✅ | ✅ | ✅ | +| 10-bit | ⚠️ | ✅ | ⚠️ | ⚠️ | +| 12-bit | ⚠️ | ⚠️ | ⚠️ | ✅ | +| 14-bit | ⚠️ | ⚠️ | ⚠️ | ⚠️ | +| 16-bit | ⚠️ | ⚠️ | ⚠️ | ⚠️ | + +Legend: ✅ = supported, tested; ️⚠️ = supported, untested; ❌ = unsupported + +## Formats supported + +| Format | Decoder | Encoder | Notes | +|:-------------:|:-------:|:-------:|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **YUV4MPEG2** | ✅ | ❌ | Supports most bitdepth and chroma subsampling. Library limited, not format limited.
Decoding via [S.O.N.G/Ignite](https://git.gammaspectra.live/S.O.N.G/Ignite) | +| **H.264** | ❌ | ✅ | Supports 8-bit and 10-bit; 4:0:0, 4:2:0, 4:2:2, 4:4:4 chroma subsampling.
Encoding via [x264](https://code.videolan.org/videolan/x264) into .h264 bitstream. | +| **H.265** | ❌ | ❌ | | +| **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. | + # TODO * No SAR/PAR handling. * No color primary / transfer / matrix coefficients handling. diff --git a/color/space.go b/color/space.go index 28f10ae..2217b71 100644 --- a/color/space.go +++ b/color/space.go @@ -81,6 +81,14 @@ func (c Space) check() error { return nil } +func MustColorFormatFromString(colorFormat string) Space { + s, err := NewColorFormatFromString(colorFormat) + if err != nil { + panic(err) + } + return s +} + func NewColorFormatFromString(colorFormat string) (Space, error) { colorFormat = strings.ToLower(colorFormat) if colorFormat == "420paldv" { diff --git a/decoder/libdav1d/libdav1d_test.go b/decoder/libdav1d/libdav1d_test.go index 0c1b89c..553afd4 100644 --- a/decoder/libdav1d/libdav1d_test.go +++ b/decoder/libdav1d/libdav1d_test.go @@ -3,7 +3,10 @@ package libdav1d import ( + "git.gammaspectra.live/S.O.N.G/Ignite/frame" "git.gammaspectra.live/S.O.N.G/Ignite/testdata" + "io" + "net/http" "os" "testing" ) @@ -12,28 +15,77 @@ func TestVersion(t *testing.T) { t.Logf("dav1d version: %s", Version()) } -func TestDecodeYUV420_8bit(t *testing.T) { - if testing.Short() { - t.Skip("skipping test in short mode") - } - f, err := os.Open(testdata.AV1_Sintel_Trailer_720p24_YUV420_8bit_Low) - if err != nil { - t.Fatal(err) - } - defer f.Close() +func testDecode(sample testdata.TestSample, t *testing.T) { + var reader io.Reader + var err error - if decoder, err := NewDecoder(f, nil); err != nil { + if _, err = os.Stat(sample.Path); err != nil { + if sample.SkipNotFound || sample.Url == "" { + t.Skip("skipping without sample") + } + response, err := http.DefaultClient.Get(sample.Url) + if err != nil { + t.Fatal(err) + } + defer response.Body.Close() + reader = response.Body + } else { + f, err := os.Open(sample.Path) + if err != nil { + t.Fatal(err) + } + defer f.Close() + reader = f + } + + if decoder, err := NewDecoder(reader, nil); err != nil { t.Fatal(err) } else { defer decoder.Close() decoded := 0 - for range decoder.DecodeStream().Channel() { + + var frameProperties frame.Properties + for decodedFrame := range decoder.DecodeStream().Channel() { + if decoded == 0 { + frameProperties = decodedFrame.Properties() + } //ingest decoded++ + if decoded%50 == 0 { + t.Logf("%d/%d", decoded, sample.Frames) + } } - if decoded != 1253 { - t.Fatalf("expected %d frames, got %d", 1253, decoded) + if decoded != sample.Frames { + t.Fatalf("expected %d frames, got %d", sample.Frames, decoded) + } + + if frameProperties.Width != sample.Width { + t.Fatalf("expected %d width, got %d", sample.Width, frameProperties.Width) + } + + if frameProperties.Height != sample.Height { + t.Fatalf("expected %d height, got %d", sample.Height, frameProperties.Height) + } + + if frameProperties.ColorSpace.String() != sample.ColorSpace.String() { + t.Fatalf("expected %s color space, got %s", sample.ColorSpace.String(), frameProperties.ColorSpace.String()) } } } + +func TestDecode_YUV420_8bit(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode") + } + + testDecode(testdata.AV1_Sintel_Trailer_720p24_YUV420_8bit_Low, t) +} + +func TestDecode_YUV444_12bit(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode") + } + + testDecode(testdata.AV1_Netflix_Sol_Levante_2160p24_YUV444_12bit_Lossy, t) +} diff --git a/decoder/y4m/y4m.go b/decoder/y4m/y4m.go index 7aaafd8..8319c75 100644 --- a/decoder/y4m/y4m.go +++ b/decoder/y4m/y4m.go @@ -5,6 +5,7 @@ import ( "fmt" "git.gammaspectra.live/S.O.N.G/Ignite/color" "git.gammaspectra.live/S.O.N.G/Ignite/frame" + "github.com/ulikunitz/xz" "io" "runtime" "strconv" @@ -43,6 +44,14 @@ const ( const fileMagic = "YUV4MPEG2 " const frameMagic = "FRAME" +func NewXZCompressedDecoder(reader io.Reader, settings map[string]any) (*Decoder, error) { + r, err := xz.NewReader(reader) + if err != nil { + return nil, err + } + return NewDecoder(r, settings) +} + func NewDecoder(reader io.Reader, settings map[string]any) (*Decoder, error) { s := &Decoder{ r: reader, diff --git a/decoder/y4m/y4m_test.go b/decoder/y4m/y4m_test.go index 638897d..d078c19 100644 --- a/decoder/y4m/y4m_test.go +++ b/decoder/y4m/y4m_test.go @@ -1,30 +1,100 @@ package y4m import ( + "git.gammaspectra.live/S.O.N.G/Ignite/frame" "git.gammaspectra.live/S.O.N.G/Ignite/testdata" + "io" + "net/http" "os" "testing" ) -func TestDecodeYUV420_8bit(t *testing.T) { - f, err := os.Open(testdata.Y4M_Sintel_Trailer_720p24_YUV420_8bit) - if err != nil { - t.Fatal(err) - } - defer f.Close() +func testDecode(sample testdata.TestSample, t *testing.T) { + var reader io.Reader + var err error - if y4m, err := NewDecoder(f, nil); err != nil { + if _, err = os.Stat(sample.Path); err != nil { + if sample.SkipNotFound || sample.Url == "" { + t.Skip("skipping without sample") + } + response, err := http.DefaultClient.Get(sample.Url) + if err != nil { + t.Fatal(err) + } + defer response.Body.Close() + reader = response.Body + } else { + f, err := os.Open(sample.Path) + if err != nil { + t.Fatal(err) + } + defer f.Close() + reader = f + } + + var y4m *Decoder + + switch sample.Type { + case "y4m": + y4m, err = NewDecoder(reader, nil) + case "y4m.xz": + y4m, err = NewXZCompressedDecoder(reader, nil) + default: + t.Fatal("unsupported sample type") + } + + if err != nil { t.Fatal(err) } else { defer y4m.Close() decoded := 0 - for range y4m.DecodeStream().Channel() { + + var frameProperties frame.Properties + for decodedFrame := range y4m.DecodeStream().Channel() { + if decoded == 0 { + frameProperties = decodedFrame.Properties() + } //ingest decoded++ + if decoded%50 == 0 { + t.Logf("%d/%d", decoded, sample.Frames) + } } - if decoded != 1253 { - t.Fatalf("expected %d frames, got %d", 1253, decoded) + if decoded != sample.Frames { + t.Fatalf("expected %d frames, got %d", sample.Frames, decoded) + } + + if frameProperties.Width != sample.Width { + t.Fatalf("expected %d width, got %d", sample.Width, frameProperties.Width) + } + + if frameProperties.Height != sample.Height { + t.Fatalf("expected %d height, got %d", sample.Height, frameProperties.Height) + } + + if frameProperties.ColorSpace.String() != sample.ColorSpace.String() { + t.Fatalf("expected %s color space, got %s", sample.ColorSpace.String(), frameProperties.ColorSpace.String()) } } } + +func TestDecode_YUV420_720p24_8bit(t *testing.T) { + testDecode(testdata.Y4M_Sintel_Trailer_720p24_YUV420_8bit, t) +} + +func TestDecode_YUV444_720p50_8bit(t *testing.T) { + testDecode(testdata.Y4M_Ducks_Take_Off_720p50_YUV444_8bit, t) +} + +func TestDecode_YUV422_720p50_8bit(t *testing.T) { + testDecode(testdata.Y4M_Ducks_Take_Off_720p50_YUV422_8bit, t) +} + +func TestDecode_YUV420_2160p60_10bit(t *testing.T) { + testDecode(testdata.Y4M_Netflix_FoodMarket_2160p60_YUV420_10bit, t) +} + +func TestDecode_YUV420_360p24_8bit_xz(t *testing.T) { + testDecode(testdata.Y4M_Big_Buck_Bunny_360p24_YUV420_8bit, t) +} diff --git a/encoder/libaom/libaom_test.go b/encoder/libaom/libaom_test.go index 8ded919..45a5186 100644 --- a/encoder/libaom/libaom_test.go +++ b/encoder/libaom/libaom_test.go @@ -5,6 +5,8 @@ package libaom import ( "git.gammaspectra.live/S.O.N.G/Ignite/decoder/y4m" "git.gammaspectra.live/S.O.N.G/Ignite/testdata" + "io" + "net/http" "os" "runtime" "sync" @@ -15,20 +17,45 @@ func TestVersion(t *testing.T) { t.Logf("libaom version: %s", Version()) } -func TestEncode(t *testing.T) { - if testing.Short() { - t.Skip("skipping test in short mode") - } - f, err := os.Open(testdata.Y4M_Sintel_Trailer_720p24_YUV420_8bit) - if err != nil { - t.Fatal(err) - } - defer f.Close() +func testEncode(sample testdata.TestSample, t *testing.T) { + var reader io.Reader + var err error - if pipe, err := y4m.NewDecoder(f, nil); err != nil { + if _, err = os.Stat(sample.Path); err != nil { + if sample.SkipNotFound || sample.Url == "" { + t.Skip("skipping without sample") + } + response, err := http.DefaultClient.Get(sample.Url) + if err != nil { + t.Fatal(err) + } + defer response.Body.Close() + reader = response.Body + } else { + f, err := os.Open(sample.Path) + if err != nil { + t.Fatal(err) + } + defer f.Close() + reader = f + } + + var pipe *y4m.Decoder + + switch sample.Type { + case "y4m": + pipe, err = y4m.NewDecoder(reader, nil) + case "y4m.xz": + pipe, err = y4m.NewXZCompressedDecoder(reader, nil) + default: + t.Fatal("unsupported sample type") + } + + if err != nil { t.Fatal(err) } else { defer pipe.Close() + target, err := os.CreateTemp("", "encode_test_*.ivf") if err != nil { t.Fatal(err) @@ -51,7 +78,7 @@ func TestEncode(t *testing.T) { settings["frame-parallel"] = 1 settings["lag-in-frames"] = 2 settings["tile-columns"] = 1 - settings["tile-rows"] = 1 + settings["tile-rows"] = 4 if encoder, err := NewEncoder(target, stream.Properties(), settings); err != nil { t.Fatal(err) @@ -65,7 +92,7 @@ func TestEncode(t *testing.T) { defer wg.Done() for range s[0].Channel() { if encoded%10 == 0 { - t.Logf("frame %d/1253", encoded) + t.Logf("frame %d/%d", encoded, sample.Frames) } encoded++ } @@ -77,9 +104,17 @@ func TestEncode(t *testing.T) { wg.Wait() - if encoded != 1253 { - t.Fatalf("expected %d frames, got %d", 1253, encoded) + if encoded != sample.Frames { + t.Fatalf("expected %d frames, got %d", sample.Frames, encoded) } } } } + +func TestEncode_YUV420_8bit(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode") + } + + testEncode(testdata.Y4M_Sintel_Trailer_720p24_YUV420_8bit, t) +} diff --git a/encoder/libx264/libx264_test.go b/encoder/libx264/libx264_test.go index 612dc5e..6b63fce 100644 --- a/encoder/libx264/libx264_test.go +++ b/encoder/libx264/libx264_test.go @@ -5,6 +5,8 @@ package libx264 import ( "git.gammaspectra.live/S.O.N.G/Ignite/decoder/y4m" "git.gammaspectra.live/S.O.N.G/Ignite/testdata" + "io" + "net/http" "os" "runtime" "sync" @@ -15,20 +17,45 @@ func TestVersion(t *testing.T) { t.Logf("libx264 version: %s", Version()) } -func TestEncode(t *testing.T) { - if testing.Short() { - t.Skip("skipping encode test in short mode") - } - f, err := os.Open(testdata.Y4M_Sintel_Trailer_720p24_YUV420_8bit) - if err != nil { - t.Fatal(err) - } - defer f.Close() +func testEncode(sample testdata.TestSample, t *testing.T) { + var reader io.Reader + var err error - if pipe, err := y4m.NewDecoder(f, nil); err != nil { + if _, err = os.Stat(sample.Path); err != nil { + if sample.SkipNotFound || sample.Url == "" { + t.Skip("skipping without sample") + } + response, err := http.DefaultClient.Get(sample.Url) + if err != nil { + t.Fatal(err) + } + defer response.Body.Close() + reader = response.Body + } else { + f, err := os.Open(sample.Path) + if err != nil { + t.Fatal(err) + } + defer f.Close() + reader = f + } + + var pipe *y4m.Decoder + + switch sample.Type { + case "y4m": + pipe, err = y4m.NewDecoder(reader, nil) + case "y4m.xz": + pipe, err = y4m.NewXZCompressedDecoder(reader, nil) + default: + t.Fatal("unsupported sample type") + } + + if err != nil { t.Fatal(err) } else { defer pipe.Close() + target, err := os.CreateTemp("", "encode_test_*.h264") if err != nil { t.Fatal(err) @@ -46,7 +73,17 @@ func TestEncode(t *testing.T) { settings := make(map[string]any) settings["threads"] = runtime.NumCPU() settings["preset"] = "fast" - settings["profile"] = "high" + settings["log"] = LogLevelInfo + + if sample.ColorSpace.ChromaSampling.J == 4 && sample.ColorSpace.ChromaSampling.A == 4 && sample.ColorSpace.ChromaSampling.B == 4 { + settings["profile"] = "high444" + } else if sample.ColorSpace.ChromaSampling.J == 4 && sample.ColorSpace.ChromaSampling.A == 2 && sample.ColorSpace.ChromaSampling.B == 2 { + settings["profile"] = "high422" + } else if sample.ColorSpace.BitDepth > 8 { + settings["profile"] = "high10" + } else { + settings["profile"] = "high" + } if encoder, err := NewEncoder(target, stream.Properties(), settings); err != nil { t.Fatal(err) @@ -60,7 +97,7 @@ func TestEncode(t *testing.T) { defer wg.Done() for range s[0].Channel() { if encoded%10 == 0 { - t.Logf("frame %d/1253", encoded) + t.Logf("frame %d/%d", encoded, sample.Frames) } encoded++ } @@ -72,9 +109,33 @@ func TestEncode(t *testing.T) { wg.Wait() - if encoded != 1253 { - t.Fatalf("expected %d frames, got %d", 1253, encoded) + if encoded != sample.Frames { + t.Fatalf("expected %d frames, got %d", sample.Frames, encoded) } } } } + +func TestEncode_YUV420_8bit(t *testing.T) { + if testing.Short() { + t.Skip("skipping encode test in short mode") + } + + testEncode(testdata.Y4M_Sintel_Trailer_720p24_YUV420_8bit, t) +} + +func TestEncode_YUV422_8bit(t *testing.T) { + if testing.Short() { + t.Skip("skipping encode test in short mode") + } + + testEncode(testdata.Y4M_Ducks_Take_Off_720p50_YUV422_8bit, t) +} + +func TestEncode_YUV444_8bit(t *testing.T) { + if testing.Short() { + t.Skip("skipping encode test in short mode") + } + + testEncode(testdata.Y4M_Ducks_Take_Off_720p50_YUV444_8bit, t) +} diff --git a/frame/frame.go b/frame/frame.go index f3c9022..92a19a5 100644 --- a/frame/frame.go +++ b/frame/frame.go @@ -13,12 +13,18 @@ type Frame interface { Properties() Properties // PTS usually frame number PTS() int64 + + // Get16 get a pixel sample in 16-bit depth Get16(x, y int) (Y uint16, Cb uint16, Cr uint16) + + // Get8 get a pixel sample in 8-bit depth Get8(x, y int) (Y uint8, Cb uint8, Cr uint8) } type TypedFrame[T AllowedFrameTypes] interface { Frame + + // GetNative get a pixel sample in native bit depth GetNative(x, y int) (Y T, Cb T, Cr T) // GetNativeLuma also known as Y. Do not keep references to this slice, copy instead. diff --git a/go.mod b/go.mod index bd1b018..f900ba3 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.21 require ( github.com/stretchr/testify v1.8.1 + github.com/ulikunitz/xz v0.5.11 golang.org/x/exp v0.0.0-20230807204917-050eac23e9de ) diff --git a/go.sum b/go.sum index 37c155c..40b3d68 100644 --- a/go.sum +++ b/go.sum @@ -10,6 +10,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= +github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= golang.org/x/exp v0.0.0-20230807204917-050eac23e9de h1:l5Za6utMv/HsBWWqzt4S8X17j+kt1uVETUX5UFhn2rE= golang.org/x/exp v0.0.0-20230807204917-050eac23e9de/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= diff --git a/testdata/.gitignore b/testdata/.gitignore index 36463d0..b947550 100644 --- a/testdata/.gitignore +++ b/testdata/.gitignore @@ -1,2 +1,3 @@ *.y4m +*.y4m.xz *.ivf \ No newline at end of file diff --git a/testdata/prepare.sh b/testdata/prepare.sh index 8fc13d9..f95b514 100755 --- a/testdata/prepare.sh +++ b/testdata/prepare.sh @@ -2,12 +2,22 @@ pushd "${0%/*}" || exit -# Samples taken from https://media.xiph.org/ - if [[ ! -f "sintel_trailer_2k_720p24.y4m" ]]; then wget --show-progress -O "sintel_trailer_2k_720p24.y4m" https://git.gammaspectra.live/S.O.N.G/Video-Samples/media/branch/master/sintel_trailer_2k_720p24.y4m fi +if [[ ! -f "ducks_take_off_422_720p50.y4m" ]]; then + wget --show-progress -O "ducks_take_off_422_720p50.y4m" https://git.gammaspectra.live/S.O.N.G/Video-Samples/media/branch/master/ducks_take_off_422_720p50.y4m +fi + +if [[ ! -f "ducks_take_off_444_720p50.y4m" ]]; then + wget --show-progress -O "ducks_take_off_444_720p50.y4m" https://git.gammaspectra.live/S.O.N.G/Video-Samples/media/branch/master/ducks_take_off_444_720p50.y4m +fi + +if [[ ! -f "netflix_sol_levante_2160p24_12bit_av1_lossy.ivf" ]]; then + wget --show-progress -O "sintel_trailer_2k_720p24_av1_low.ivf" https://git.gammaspectra.live/S.O.N.G/Video-Samples/media/branch/master/netflix_sol_levante_2160p24_12bit_av1_lossy.ivf +fi + if [[ ! -f "sintel_trailer_2k_720p24_av1_low.ivf" ]]; then wget --show-progress -O "sintel_trailer_2k_720p24_av1_low.ivf" https://git.gammaspectra.live/S.O.N.G/Video-Samples/media/branch/master/sintel_trailer_2k_720p24_av1_low.ivf fi \ No newline at end of file diff --git a/testdata/testdata.go b/testdata/testdata.go index 6ca3ac9..e5f5fa8 100644 --- a/testdata/testdata.go +++ b/testdata/testdata.go @@ -1,6 +1,7 @@ package testdata import ( + "git.gammaspectra.live/S.O.N.G/Ignite/color" "os" "path" "runtime" @@ -16,7 +17,84 @@ func init() { } } -const ( - Y4M_Sintel_Trailer_720p24_YUV420_8bit = "testdata/sintel_trailer_2k_720p24.y4m" - AV1_Sintel_Trailer_720p24_YUV420_8bit_Low = "testdata/sintel_trailer_2k_720p24_av1_low.ivf" +type TestSample struct { + Path string + Url string + Type string + Width, Height int + Frames int + ColorSpace color.Space + SkipNotFound bool +} + +var ( + Y4M_Sintel_Trailer_720p24_YUV420_8bit = TestSample{ + Path: "testdata/sintel_trailer_2k_720p24.y4m", + // https://media.xiph.org/video/derf/y4m/sintel_trailer_2k_720p24.y4m + Url: "https://git.gammaspectra.live/S.O.N.G/Video-Samples/media/branch/master/sintel_trailer_2k_720p24.y4m", + Type: "y4m", + Width: 1280, + Height: 720, + Frames: 1253, + ColorSpace: color.MustColorFormatFromString("420jpeg"), + } + Y4M_Big_Buck_Bunny_360p24_YUV420_8bit = TestSample{ + Path: "testdata/big_buck_bunny_360p24.y4m.xz", + Url: "https://media.xiph.org/video/derf/y4m/big_buck_bunny_360p24.y4m.xz", + Type: "y4m.xz", + Width: 640, + Height: 360, + Frames: 14315, + ColorSpace: color.MustColorFormatFromString("420jpeg"), + SkipNotFound: true, + } + Y4M_Ducks_Take_Off_720p50_YUV444_8bit = TestSample{ + Path: "testdata/ducks_take_off_444_720p50.y4m", + // https://media.xiph.org/video/derf/y4m/ducks_take_off_444_720p50.y4m + Url: "https://git.gammaspectra.live/S.O.N.G/Video-Samples/media/branch/master/ducks_take_off_444_720p50.y4m", + Type: "y4m", + Width: 1280, + Height: 720, + Frames: 500, + ColorSpace: color.MustColorFormatFromString("444p8"), + } + Y4M_Ducks_Take_Off_720p50_YUV422_8bit = TestSample{ + Path: "testdata/ducks_take_off_422_720p50.y4m", + // https://media.xiph.org/video/derf/y4m/ducks_take_off_422_720p50.y4m + Url: "https://git.gammaspectra.live/S.O.N.G/Video-Samples/media/branch/master/ducks_take_off_422_720p50.y4m", + Type: "y4m", + Width: 1280, + Height: 720, + Frames: 500, + ColorSpace: color.MustColorFormatFromString("422p8"), + } + Y4M_Netflix_FoodMarket_2160p60_YUV420_10bit = TestSample{ + Path: "testdata/Netflix_FoodMarket_4096x2160_60fps_10bit_420.y4m", + Url: "https://media.xiph.org/video/derf/ElFuente/Netflix_FoodMarket_4096x2160_60fps_10bit_420.y4m", + Type: "y4m", + Width: 4096, + Height: 2160, + Frames: 600, + ColorSpace: color.MustColorFormatFromString("420p10"), + SkipNotFound: true, + } + + AV1_Sintel_Trailer_720p24_YUV420_8bit_Low = TestSample{ + Path: "testdata/sintel_trailer_2k_720p24_av1_low.ivf", + Url: "https://git.gammaspectra.live/S.O.N.G/Video-Samples/media/branch/master/sintel_trailer_2k_720p24_av1_low.ivf", + Type: "ivf", + Width: 1280, + Height: 720, + Frames: 1253, + ColorSpace: color.MustColorFormatFromString("420jpeg"), + } + AV1_Netflix_Sol_Levante_2160p24_YUV444_12bit_Lossy = TestSample{ + Path: "testdata/netflix_sol_levante_2160p24_12bit_av1_lossy.ivf", + Url: "https://git.gammaspectra.live/S.O.N.G/Video-Samples/media/branch/master/netflix_sol_levante_2160p24_12bit_av1_lossy.ivf", + Type: "ivf", + Width: 3840, + Height: 2160, + Frames: 6313, + ColorSpace: color.MustColorFormatFromString("444p12"), + } ) diff --git a/utilities/libvmaf/libvmaf_test.go b/utilities/libvmaf/libvmaf_test.go index fe3c227..d70e8ba 100644 --- a/utilities/libvmaf/libvmaf_test.go +++ b/utilities/libvmaf/libvmaf_test.go @@ -19,12 +19,12 @@ func TestVMAFYUV420_8bit(t *testing.T) { if testing.Short() { t.Skip("skipping test in short mode") } - referenceFile, err := os.Open(testdata.Y4M_Sintel_Trailer_720p24_YUV420_8bit) + referenceFile, err := os.Open(testdata.Y4M_Sintel_Trailer_720p24_YUV420_8bit.Path) if err != nil { t.Fatal(err) } defer referenceFile.Close() - distortedFile, err := os.Open(testdata.AV1_Sintel_Trailer_720p24_YUV420_8bit_Low) + distortedFile, err := os.Open(testdata.AV1_Sintel_Trailer_720p24_YUV420_8bit_Low.Path) if err != nil { t.Fatal(err) } diff --git a/utilities/settings.go b/utilities/settings.go new file mode 100644 index 0000000..1206d05 --- /dev/null +++ b/utilities/settings.go @@ -0,0 +1,39 @@ +package utilities + +import ( + "strconv" +) + +func GetSettingString(m map[string]any, name string, fallback string) string { + + v, ok := m[name] + + if !ok { + return fallback + } + + switch val := v.(type) { + case string: + return val + case bool: + if val { + return "1" + } else { + return "0" + } + case int: + return strconv.FormatInt(int64(val), 10) + case int64: + return strconv.FormatInt(val, 10) + case uint: + return strconv.FormatUint(uint64(val), 10) + case uint64: + return strconv.FormatUint(val, 10) + case float32: + return strconv.FormatFloat(float64(val), 'f', -1, 64) + case float64: + return strconv.FormatFloat(val, 'f', -1, 64) + default: + panic("unknown type") + } +}