Files

293 lines
5.6 KiB
Go
Raw Permalink Normal View History

2026-06-07 00:40:27 +03:00
package gopeg
import (
"image/png"
"os"
"path/filepath"
"strings"
"testing"
"time"
)
const testVideo = "testdata/small.webm"
func TestNewDecoder(t *testing.T) {
f, err := os.Open(testVideo)
if err != nil {
t.Skipf("test video not found: %v", err)
}
defer f.Close()
d, err := NewDecoder(f)
if err != nil {
t.Fatal("NewDecoder:", err)
}
defer d.Close()
if d.fmtCtx == nil {
t.Fatal("format context is nil")
}
if d.videoIdx < 0 {
t.Fatal("no video stream found")
}
}
func TestMeta(t *testing.T) {
f, err := os.Open(testVideo)
if err != nil {
t.Skipf("test video not found: %v", err)
}
defer f.Close()
d, err := NewDecoder(f)
if err != nil {
t.Fatal("NewDecoder:", err)
}
defer d.Close()
m := d.Meta()
if m.Duration <= 0 {
t.Error("duration is zero or negative")
}
if m.Video == nil {
t.Fatal("video meta is nil")
}
if m.Video.Width == 0 || m.Video.Height == 0 {
t.Errorf("video dimensions are zero: %dx%d", m.Video.Width, m.Video.Height)
}
if m.Video.CodecName == "" {
t.Error("video codec name is empty")
}
if m.Video.FPSNum == 0 || m.Video.FPSDen == 0 {
t.Errorf("frame rate is zero: %d/%d", m.Video.FPSNum, m.Video.FPSDen)
}
t.Logf("Duration: %v", m.Duration)
t.Logf("Video: %dx%d %s @ %d/%d",
m.Video.Width, m.Video.Height,
m.Video.CodecName, m.Video.FPSNum, m.Video.FPSDen)
if m.Audio != nil {
t.Logf("Audio: %dHz %dch %s",
m.Audio.SampleRate, m.Audio.Channels, m.Audio.CodecName)
}
}
func TestDecodeFrames(t *testing.T) {
f, err := os.Open(testVideo)
if err != nil {
t.Skipf("test video not found: %v", err)
}
defer f.Close()
d, err := NewDecoder(f)
if err != nil {
t.Fatal("NewDecoder:", err)
}
defer d.Close()
var (
videoFrames int
audioFrames int
firstPTS time.Duration
lastPTS time.Duration
maxFrames = 100 // limit to avoid timeout
)
for videoFrames < maxFrames {
frame, err := d.DecodeFrame()
if err != nil {
t.Fatal("DecodeFrame:", err)
}
if frame == nil {
break
}
if frame.Video != nil {
videoFrames++
if firstPTS == 0 {
firstPTS = frame.Video.PTS
}
lastPTS = frame.Video.PTS
if frame.Video.Img == nil {
t.Error("video frame image is nil")
}
if frame.Video.Img.Bounds().Dx() == 0 {
t.Error("video frame has zero width")
}
}
if frame.Audio != nil {
audioFrames++
if len(frame.Audio.Samples) == 0 {
t.Error("audio frame has no samples")
}
}
}
t.Logf("Decoded %d video frames and %d audio frames", videoFrames, audioFrames)
t.Logf("First PTS: %v, Last PTS: %v", firstPTS, lastPTS)
if videoFrames == 0 {
t.Error("no video frames decoded")
}
if lastPTS <= firstPTS && videoFrames > 1 {
t.Error("PTS is not monotonically increasing")
}
}
func TestDecodeFrameImages(t *testing.T) {
f, err := os.Open(testVideo)
if err != nil {
t.Skipf("test video not found: %v", err)
}
defer f.Close()
d, err := NewDecoder(f)
if err != nil {
t.Fatal("NewDecoder:", err)
}
defer d.Close()
// Decode first 3 video frames and save them as PNG
dir := t.TempDir()
saved := 0
for saved < 3 {
frame, err := d.DecodeFrame()
if err != nil {
t.Fatal("DecodeFrame:", err)
}
if frame == nil || frame.Video == nil {
continue
}
outPath := filepath.Join(dir, "frame_"+pad(saved)+".png")
out, err := os.Create(outPath)
if err != nil {
t.Fatal("create png:", err)
}
if err := png.Encode(out, frame.Video.Img); err != nil {
out.Close()
t.Fatal("encode png:", err)
}
out.Close()
saved++
t.Logf("Saved frame %d (%dx%d) to %s",
saved,
frame.Video.Img.Bounds().Dx(),
frame.Video.Img.Bounds().Dy(),
outPath)
}
if saved == 0 {
t.Error("no video frames saved")
}
}
func TestClose(t *testing.T) {
f, err := os.Open(testVideo)
if err != nil {
t.Skipf("test video not found: %v", err)
}
defer f.Close()
d, err := NewDecoder(f)
if err != nil {
t.Fatal("NewDecoder:", err)
}
// Close should be safe to call multiple times
d.Close()
d.Close()
}
func TestFrameImagesAreValid(t *testing.T) {
f, err := os.Open(testVideo)
if err != nil {
t.Skipf("test video not found: %v", err)
}
defer f.Close()
d, err := NewDecoder(f)
if err != nil {
t.Fatal("NewDecoder:", err)
}
defer d.Close()
// Decode up to 50 frames and verify each is valid
checked := 0
for checked < 50 {
frame, err := d.DecodeFrame()
if err != nil {
t.Fatal("DecodeFrame:", err)
}
if frame == nil || frame.Video == nil {
continue
}
img := frame.Video.Img
if img == nil {
t.Fatal("image is nil")
}
bounds := img.Bounds()
if bounds.Dx() == 0 || bounds.Dy() == 0 {
t.Errorf("frame %d has zero dimensions", checked)
}
// Check that not all pixels are black
pix := img.Pix
allBlack := true
for _, b := range pix {
if b != 0 {
allBlack = false
break
}
}
if allBlack {
t.Errorf("frame %d is completely black", checked)
}
checked++
}
if checked == 0 {
t.Error("no frames checked")
}
t.Logf("Checked %d frames", checked)
}
func TestSeekableReader(t *testing.T) {
// Use bytes.Reader which implements io.ReadSeeker
data, err := os.ReadFile(testVideo)
if err != nil {
t.Skipf("test video not found: %v", err)
}
reader := strings.NewReader(string(data))
d, err := NewDecoder(reader)
if err != nil {
t.Fatal("NewDecoder with strings.Reader:", err)
}
defer d.Close()
m := d.Meta()
if m.Video == nil {
t.Fatal("no video stream from seekable reader")
}
t.Logf("Decoded from seekable reader: %dx%d %s",
m.Video.Width, m.Video.Height, m.Video.CodecName)
}
func pad(n int) string {
if n < 10 {
return "00" + string(rune('0'+n))
}
if n < 100 {
return "0" + string(rune('0'+n/10)) + string(rune('0'+n%10))
}
return "999"
}