From 8982cda8afc40f3268bfc2614faa202fe6dea6d3 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Thu, 30 Apr 2026 00:49:46 +0300 Subject: fix: seed random source in thumbnail generator --- internal/thumb/thumb.go | 7 +++-- internal/thumb/thumb_test.go | 65 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 68 insertions(+), 4 deletions(-) (limited to 'internal') diff --git a/internal/thumb/thumb.go b/internal/thumb/thumb.go index 96c496b..53a139b 100644 --- a/internal/thumb/thumb.go +++ b/internal/thumb/thumb.go @@ -6,6 +6,7 @@ import ( "fmt" "math/rand" "os/exec" + "time" ) // Generator creates a thumbnail for a given media file. @@ -16,12 +17,14 @@ type Generator interface { // FFmpegGenerator uses ffmpeg to extract a random frame. type FFmpegGenerator struct { execer func(ctx context.Context, name string, arg ...string) *exec.Cmd + rnd *rand.Rand } -// NewFFmpegGenerator creates a new FFmpegGenerator. +// NewFFmpegGenerator creates a new FFmpegGenerator with a seeded random source. func NewFFmpegGenerator() *FFmpegGenerator { return &FFmpegGenerator{ execer: exec.CommandContext, + rnd: rand.New(rand.NewSource(time.Now().UnixNano())), } } @@ -30,7 +33,7 @@ func NewFFmpegGenerator() *FFmpegGenerator { func (g *FFmpegGenerator) Generate(ctx context.Context, inputPath, outputPath string, duration float64) error { offset := 0.0 if duration > 0 { - offset = rand.Float64() * duration + offset = g.rnd.Float64() * duration if offset < 1.0 { offset = 1.0 } diff --git a/internal/thumb/thumb_test.go b/internal/thumb/thumb_test.go index 2cd6124..d6272a9 100644 --- a/internal/thumb/thumb_test.go +++ b/internal/thumb/thumb_test.go @@ -3,9 +3,12 @@ package thumb import ( "context" "errors" + "math/rand" "os/exec" + "strconv" "strings" "testing" + "time" ) func TestFFmpegGenerator_Generate(t *testing.T) { @@ -35,7 +38,7 @@ func TestFFmpegGenerator_Generate(t *testing.T) { return exec.Command("true") } - g := &FFmpegGenerator{execer: fakeExecer} + g := &FFmpegGenerator{execer: fakeExecer, rnd: rand.New(rand.NewSource(1))} if err := g.Generate(ctx, "input.mp4", "output.jpg", 120.0); err != nil { t.Fatalf("unexpected error: %v", err) } @@ -58,12 +61,58 @@ func TestFFmpegGenerator_Generate_Error(t *testing.T) { fakeExecer := func(_ context.Context, name string, arg ...string) *exec.Cmd { return exec.Command("false") } - g := &FFmpegGenerator{execer: fakeExecer} + g := &FFmpegGenerator{execer: fakeExecer, rnd: rand.New(rand.NewSource(1))} if err := g.Generate(ctx, "input.mp4", "output.jpg", 10.0); err == nil { t.Fatal("expected error from failing ffmpeg command") } } +func TestFFmpegGenerator_Generate_RandomOffset(t *testing.T) { + ctx := context.Background() + var offsets []float64 + fakeExecer := func(_ context.Context, name string, arg ...string) *exec.Cmd { + for i := 0; i < len(arg); i++ { + if arg[i] == "-ss" && i+1 < len(arg) { + off, err := strconv.ParseFloat(arg[i+1], 64) + if err != nil { + t.Fatalf("failed to parse offset: %v", err) + } + offsets = append(offsets, off) + } + } + return exec.Command("true") + } + + // Use two generators with different seeds. + g1 := &FFmpegGenerator{execer: fakeExecer, rnd: rand.New(rand.NewSource(time.Now().UnixNano()))} + g2 := &FFmpegGenerator{execer: fakeExecer, rnd: rand.New(rand.NewSource(time.Now().UnixNano() + 12345))} + + for i := 0; i < 5; i++ { + if err := g1.Generate(ctx, "input.mp4", "out.jpg", 100.0); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if err := g2.Generate(ctx, "input.mp4", "out.jpg", 100.0); err != nil { + t.Fatalf("unexpected error: %v", err) + } + } + + if len(offsets) != 10 { + t.Fatalf("expected 10 offsets, got %d", len(offsets)) + } + + // Check that not all offsets are identical (should be extremely unlikely with different seeds). + allSame := true + for i := 1; i < len(offsets); i++ { + if offsets[i] != offsets[0] { + allSame = false + break + } + } + if allSame { + t.Fatal("expected different random offsets, but all were identical") + } +} + func TestMockGenerator(t *testing.T) { ctx := context.Background() m := &MockGenerator{} @@ -78,3 +127,15 @@ func TestMockGenerator(t *testing.T) { t.Fatal("expected error from mock generator") } } + +func TestNewFFmpegGenerator_Seeded(t *testing.T) { + g := NewFFmpegGenerator() + if g.rnd == nil { + t.Fatal("expected rnd to be initialized") + } + // Generate a value to ensure the source is functional. + v := g.rnd.Float64() + if v < 0 || v >= 1 { + t.Fatalf("expected float in [0,1), got %v", v) + } +} -- cgit v1.2.3