From 914bd7cd6aa14e839332a98d91c30b19865b0cf2 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Sun, 17 May 2026 15:25:52 +0300 Subject: Restructure repo: move Go server into player-server/ --- internal/probe/image_test.go | 232 ---------------------------------- internal/probe/probe.go | 242 ------------------------------------ internal/probe/probe_test.go | 126 ------------------- internal/probe/remux.go | 119 ------------------ internal/probe/remux_test.go | 85 ------------- internal/probe/testdata/golden.json | 14 --- 6 files changed, 818 deletions(-) delete mode 100644 internal/probe/image_test.go delete mode 100644 internal/probe/probe.go delete mode 100644 internal/probe/probe_test.go delete mode 100644 internal/probe/remux.go delete mode 100644 internal/probe/remux_test.go delete mode 100644 internal/probe/testdata/golden.json (limited to 'internal/probe') diff --git a/internal/probe/image_test.go b/internal/probe/image_test.go deleted file mode 100644 index 77677a3..0000000 --- a/internal/probe/image_test.go +++ /dev/null @@ -1,232 +0,0 @@ -package probe - -import ( - "context" - "os" - "os/exec" - "path/filepath" - "testing" - - "codeberg.org/snonux/player/internal/mediatype" - "codeberg.org/snonux/player/internal/model" -) - -func generateEXIFImage(t *testing.T, dst string) { - t.Helper() - if _, err := exec.LookPath("convert"); err != nil { - t.Skip("ImageMagick convert not available") - } - if _, err := exec.LookPath("exiv2"); err != nil { - t.Skip("exiv2 not available") - } - - cmd := exec.Command("convert", "-size", "2x2", "xc:red", dst) - if err := cmd.Run(); err != nil { - t.Fatalf("convert failed: %v", err) - } - - exiv := exec.Command("exiv2", - "-Mset Exif.Image.Make Canon", - "-Mset Exif.Image.Model EOS 5D", - "-Mset Exif.Photo.LensModel EF 50mm f/1.8", - "-Mset Exif.Photo.DateTimeOriginal 2024:01:01 12:00:00", - "-Mset Exif.Photo.ISOSpeedRatings 400", - "-Mset Exif.Photo.FNumber 18/10", - "-Mset Exif.Photo.ExposureTime 1/250", - "-Mset Exif.Photo.FocalLength 50/1", - dst, - ) - if err := exiv.Run(); err != nil { - t.Fatalf("exiv2 failed: %v", err) - } -} - -func generateVideo(t *testing.T, dst string) { - t.Helper() - if _, err := exec.LookPath("ffmpeg"); err != nil { - t.Skip("ffmpeg not available") - } - cmd := exec.Command("ffmpeg", "-f", "lavfi", "-i", "color=c=red:size=2x2:d=1", - "-pix_fmt", "yuv420p", "-c:v", "libx264", "-an", "-y", dst) - if err := cmd.Run(); err != nil { - t.Fatalf("ffmpeg failed: %v", err) - } -} - -func TestIsImagePath(t *testing.T) { - cases := []struct { - path string - want bool - }{ - {"photo.jpg", true}, - {"photo.jpeg", true}, - {"image.png", true}, - {"anim.gif", true}, - {"img.webp", true}, - {"legacy.bmp", true}, - {"modern.avif", true}, - {"vector.svg", true}, - {"video.mp4", false}, - {"audio.mp3", false}, - {"archive.tar.gz", false}, - {"no_ext", false}, - } - for _, c := range cases { - t.Run(c.path, func(t *testing.T) { - if got := mediatype.IsImageExt(c.path); got != c.want { - t.Errorf("IsImageExt(%q) = %v, want %v", c.path, got, c.want) - } - }) - } -} - -func TestIsImagePath_CaseInsensitive(t *testing.T) { - if !mediatype.IsImageExt("UPPER.JPG") { - t.Error("expected true for uppercase extension") - } - if !mediatype.IsImageExt("Mixed.PnG") { - t.Error("expected true for mixed-case extension") - } -} - -func TestExtractEXIF(t *testing.T) { - tmpDir := t.TempDir() - imgPath := filepath.Join(tmpDir, "test.jpg") - generateEXIFImage(t, imgPath) - - meta := &model.Metadata{} - extractEXIF(imgPath, meta) - - if meta.EXIFCamera != "Canon EOS 5D" { - t.Errorf("EXIFCamera = %q, want \"Canon EOS 5D\"", meta.EXIFCamera) - } - if meta.EXIFLens != "EF 50mm f/1.8" { - t.Errorf("EXIFLens = %q, want \"EF 50mm f/1.8\"", meta.EXIFLens) - } - if meta.EXIFDate != "2024:01:01 12:00:00" { - t.Errorf("EXIFDate = %q, want \"2024:01:01 12:00:00\"", meta.EXIFDate) - } - if meta.EXIFISO != "400" { - t.Errorf("EXIFISO = %q, want \"400\"", meta.EXIFISO) - } - if meta.EXIFFNumber != "f/1.8" { - t.Errorf("EXIFFNumber = %q, want \"f/1.8\"", meta.EXIFFNumber) - } - if meta.EXIFExposure != "1/250 s" { - t.Errorf("EXIFExposure = %q, want \"1/250 s\"", meta.EXIFExposure) - } - if meta.EXIFFocalLength != "50.0 mm" { - t.Errorf("EXIFFocalLength = %q, want \"50.0 mm\"", meta.EXIFFocalLength) - } -} - -func TestExtractEXIF_NonExistentFile(t *testing.T) { - meta := &model.Metadata{} - extractEXIF("/does/not/exist.jpg", meta) - // Should not panic and leave fields empty. - if meta.EXIFCamera != "" { - t.Errorf("expected empty EXIFCamera, got %q", meta.EXIFCamera) - } -} - -func TestExtractEXIF_FileWithoutEXIF(t *testing.T) { - t.Helper() - tmpDir := t.TempDir() - imgPath := filepath.Join(tmpDir, "noexif.jpg") - if _, err := exec.LookPath("convert"); err != nil { - t.Skip("ImageMagick convert not available") - } - cmd := exec.Command("convert", "-size", "2x2", "xc:blue", imgPath) - if err := cmd.Run(); err != nil { - t.Fatalf("convert failed: %v", err) - } - - meta := &model.Metadata{} - extractEXIF(imgPath, meta) - if meta.EXIFCamera != "" { - t.Errorf("expected empty EXIFCamera, got %q", meta.EXIFCamera) - } -} - -func TestFFProber_ProbeImage(t *testing.T) { - tmpDir := t.TempDir() - imgPath := filepath.Join(tmpDir, "photo.jpg") - generateEXIFImage(t, imgPath) - - p := NewFFProber() - ctx := context.Background() - meta, err := p.Probe(ctx, imgPath) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if meta == nil { - t.Fatal("expected non-nil metadata") - } - // Verify EXIF extraction happened. - if meta.EXIFCamera != "Canon EOS 5D" { - t.Errorf("EXIFCamera = %q, want \"Canon EOS 5D\"", meta.EXIFCamera) - } -} - -func TestFFProber_ProbeVideo(t *testing.T) { - tmpDir := t.TempDir() - vidPath := filepath.Join(tmpDir, "video.mp4") - generateVideo(t, vidPath) - - p := NewFFProber() - ctx := context.Background() - meta, err := p.Probe(ctx, vidPath) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if meta == nil { - t.Fatal("expected non-nil metadata") - } - if meta.Codec == "" { - t.Error("expected non-empty Codec for video") - } - if meta.Resolution == "" { - t.Error("expected non-empty Resolution for video") - } - if meta.Duration <= 0 { - t.Errorf("expected positive Duration, got %v", meta.Duration) - } -} - -func TestFFProber_ProbeNonExistent(t *testing.T) { - p := NewFFProber() - ctx := context.Background() - _, err := p.Probe(ctx, "/nonexistent/file.mp4") - if err == nil { - t.Fatal("expected error for nonexistent file") - } -} - -func TestFFProber_ProbeEmptyFile(t *testing.T) { - if _, err := exec.LookPath("ffprobe"); err != nil { - t.Skip("ffprobe not available") - } - tmpDir := t.TempDir() - emptyPath := filepath.Join(tmpDir, "empty.mp4") - if err := os.WriteFile(emptyPath, []byte{}, 0o644); err != nil { - t.Fatal(err) - } - - p := NewFFProber() - ctx := context.Background() - _, err := p.Probe(ctx, emptyPath) - if err == nil { - t.Fatal("expected error for empty file") - } -} - -func TestParseFFprobeOutput_BitrateParsing(t *testing.T) { - input := `{"format":{"duration":"60","bit_rate":"0"},"streams":[{"codec_name":"h264","width":640,"height":480,"codec_type":"video"}]}` - meta, err := parseFFprobeOutput([]byte(input)) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if meta.Bitrate != 0 { - t.Errorf("Bitrate = %d, want 0", meta.Bitrate) - } -} diff --git a/internal/probe/probe.go b/internal/probe/probe.go deleted file mode 100644 index 7fd6924..0000000 --- a/internal/probe/probe.go +++ /dev/null @@ -1,242 +0,0 @@ -// Package probe implements media metadata probing. -package probe - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "os" - "os/exec" - "strconv" - "time" - - "codeberg.org/snonux/player/internal/mediatype" - "codeberg.org/snonux/player/internal/model" - "github.com/rwcarlsen/goexif/exif" -) - -const ( - defaultProbeWaitDelay = 10 * time.Second - defaultProbeMaxRetries = 2 - defaultProbeRetryDelay = 500 * time.Millisecond -) - -// Prober extracts metadata from a media file. -type Prober interface { - Probe(ctx context.Context, path string) (*model.Metadata, error) -} - -// FFProber wraps the ffprobe command-line tool. -type FFProber struct { - maxRetries int - retryDelay time.Duration - waitDelay time.Duration -} - -var _ Prober = (*FFProber)(nil) - -// NewFFProber creates a new FFProber with bounded retries and a process wait delay. -func NewFFProber() *FFProber { - return &FFProber{ - maxRetries: defaultProbeMaxRetries, - retryDelay: defaultProbeRetryDelay, - waitDelay: defaultProbeWaitDelay, - } -} - -// Probe runs ffprobe against the given path with retries and parses the resulting JSON. -func (f *FFProber) Probe(ctx context.Context, path string) (*model.Metadata, error) { - var lastErr error - attempts := f.maxRetries + 1 - if attempts <= 0 { - attempts = 1 - } - - for i := 0; i < attempts; i++ { - if err := ctx.Err(); err != nil { - return nil, err - } - - meta, err := f.probeOnce(ctx, path) - if err == nil { - return meta, nil - } - lastErr = err - - // Don't retry on context cancellation. - if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { - break - } - - if i < attempts-1 { - delay := f.retryDelay * time.Duration(1< maxDelay { - delay = maxDelay - } - select { - case <-time.After(delay): - case <-ctx.Done(): - return nil, ctx.Err() - } - } - } - return nil, lastErr -} - -// probeOnce performs a single ffprobe invocation. -func (f *FFProber) probeOnce(ctx context.Context, path string) (*model.Metadata, error) { - cmd := exec.CommandContext(ctx, "ffprobe", - "-v", "error", - "-show_format", - "-show_streams", - "-of", "json", - path, - ) - cmd.WaitDelay = f.waitDelay - out, err := cmd.Output() - if err != nil { - if exitErr, ok := err.(*exec.ExitError); ok && len(exitErr.Stderr) > 0 { - return nil, fmt.Errorf("ffprobe %s: %w: %s", path, err, string(exitErr.Stderr)) - } - return nil, fmt.Errorf("ffprobe %s: %w", path, err) - } - meta, err := parseFFprobeOutput(out) - if err != nil { - return nil, err - } - // For images, also extract EXIF data. - if mediatype.IsImageExt(path) { - extractEXIF(path, meta) - } - return meta, nil -} - -type ffprobeOutput struct { - Format struct { - Duration string `json:"duration"` - BitRate string `json:"bit_rate"` - } `json:"format"` - Streams []struct { - CodecName string `json:"codec_name"` - Width int `json:"width"` - Height int `json:"height"` - CodecType string `json:"codec_type"` - } `json:"streams"` -} - -func parseFFprobeOutput(data []byte) (*model.Metadata, error) { - var out ffprobeOutput - if err := json.Unmarshal(data, &out); err != nil { - return nil, fmt.Errorf("unmarshal ffprobe output: %w", err) - } - - meta := &model.Metadata{} - if out.Format.Duration != "" { - if d, err := strconv.ParseFloat(out.Format.Duration, 64); err == nil { - meta.Duration = d - } - } - if out.Format.BitRate != "" { - if b, err := strconv.Atoi(out.Format.BitRate); err == nil { - meta.Bitrate = b - } - } - - for _, s := range out.Streams { - if s.CodecType == "video" { - if meta.Codec == "" { - meta.Codec = s.CodecName - } - if s.Width > 0 && s.Height > 0 { - meta.Resolution = fmt.Sprintf("%dx%d", s.Width, s.Height) - meta.Width = s.Width - meta.Height = s.Height - } - break - } - } - - // Fallback to first stream codec if no video stream found. - if meta.Codec == "" && len(out.Streams) > 0 { - meta.Codec = out.Streams[0].CodecName - } - - return meta, nil -} - -func extractEXIF(path string, meta *model.Metadata) { - f, err := os.Open(path) - if err != nil { - return - } - defer f.Close() - - x, err := exif.Decode(f) - if err != nil { - return - } - - if v, err := x.Get(exif.Make); err == nil { - if s, err := v.StringVal(); err == nil { - meta.EXIFCamera = s - } - } - if v, err := x.Get(exif.Model); err == nil { - if s, err := v.StringVal(); err == nil { - if meta.EXIFCamera != "" { - meta.EXIFCamera = meta.EXIFCamera + " " + s - } else { - meta.EXIFCamera = s - } - } - } - if v, err := x.Get(exif.LensModel); err == nil { - if s, err := v.StringVal(); err == nil { - meta.EXIFLens = s - } - } - if v, err := x.Get(exif.DateTimeOriginal); err == nil { - if s, err := v.StringVal(); err == nil { - meta.EXIFDate = s - } - } - if v, err := x.Get(exif.ISOSpeedRatings); err == nil { - if i, err := v.Int(0); err == nil { - meta.EXIFISO = strconv.Itoa(i) - } - } - if v, err := x.Get(exif.FNumber); err == nil { - if r, err := v.Rat(0); err == nil { - num := float64(r.Num().Int64()) - denom := float64(r.Denom().Int64()) - meta.EXIFFNumber = fmt.Sprintf("f/%.1f", num/denom) - } - } - if v, err := x.Get(exif.ExposureTime); err == nil { - if r, err := v.Rat(0); err == nil { - meta.EXIFExposure = fmt.Sprintf("%s/%s s", r.Num().String(), r.Denom().String()) - } - } - if v, err := x.Get(exif.FocalLength); err == nil { - if r, err := v.Rat(0); err == nil { - num := float64(r.Num().Int64()) - denom := float64(r.Denom().Int64()) - meta.EXIFFocalLength = fmt.Sprintf("%.1f mm", num/denom) - } - } -} - -// MockProber is a test fake for Prober. -type MockProber struct { - ProbeFunc func(ctx context.Context, path string) (*model.Metadata, error) -} - -// Probe delegates to ProbeFunc or returns zero-value metadata. -func (m *MockProber) Probe(ctx context.Context, path string) (*model.Metadata, error) { - if m.ProbeFunc != nil { - return m.ProbeFunc(ctx, path) - } - return &model.Metadata{}, nil -} diff --git a/internal/probe/probe_test.go b/internal/probe/probe_test.go deleted file mode 100644 index 54493ca..0000000 --- a/internal/probe/probe_test.go +++ /dev/null @@ -1,126 +0,0 @@ -package probe - -import ( - "context" - "testing" - "time" - - "codeberg.org/snonux/player/internal/model" -) - -func TestParseFFprobeOutput(t *testing.T) { - cases := []struct { - name string - input string - want *model.Metadata - wantErr bool - }{ - { - name: "video with all fields", - input: `{ - "format": {"duration": "123.45", "bit_rate": "500000"}, - "streams": [ - {"codec_name": "h264", "width": 1920, "height": 1080, "codec_type": "video"}, - {"codec_name": "aac", "codec_type": "audio"} - ] - }`, - want: &model.Metadata{ - Duration: 123.45, - Codec: "h264", - Resolution: "1920x1080", - Bitrate: 500000, - }, - }, - { - name: "audio only no video stream", - input: `{ - "format": {"duration": "200.1", "bit_rate": "128000"}, - "streams": [ - {"codec_name": "mp3", "codec_type": "audio"} - ] - }`, - want: &model.Metadata{ - Duration: 200.1, - Codec: "mp3", - Bitrate: 128000, - }, - }, - { - name: "invalid json", - input: `{bad json`, - wantErr: true, - }, - { - name: "empty streams uses first stream fallback", - input: `{ - "format": {}, - "streams": [ - {"codec_name": "vp9", "codec_type": "video", "width": 0, "height": 0} - ] - }`, - want: &model.Metadata{Codec: "vp9"}, - }, - { - name: "no format or streams", - input: `{"format":{},"streams":[]}`, - want: &model.Metadata{}, - }, - } - - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - got, err := parseFFprobeOutput([]byte(c.input)) - if (err != nil) != c.wantErr { - t.Fatalf("unexpected error: %v", err) - } - if c.wantErr { - return - } - if got.Duration != c.want.Duration { - t.Errorf("Duration = %v, want %v", got.Duration, c.want.Duration) - } - if got.Codec != c.want.Codec { - t.Errorf("Codec = %v, want %v", got.Codec, c.want.Codec) - } - if got.Resolution != c.want.Resolution { - t.Errorf("Resolution = %v, want %v", got.Resolution, c.want.Resolution) - } - if got.Bitrate != c.want.Bitrate { - t.Errorf("Bitrate = %v, want %v", got.Bitrate, c.want.Bitrate) - } - }) - } -} - -func TestMockProber(t *testing.T) { - ctx := context.Background() - m := &MockProber{} - meta, err := m.Probe(ctx, "any") - if err != nil { - t.Errorf("unexpected error: %v", err) - } - if meta == nil { - t.Fatal("expected non-nil metadata") - } - - m.ProbeFunc = func(context.Context, string) (*model.Metadata, error) { - return &model.Metadata{Duration: 42}, nil - } - meta, err = m.Probe(ctx, "any") - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if meta.Duration != 42 { - t.Errorf("duration = %v, want 42", meta.Duration) - } -} - -func TestFFProber_ContextCancellation(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Nanosecond) - defer cancel() - p := NewFFProber() - _, err := p.Probe(ctx, "nonexistent_path_should_fail") - if err == nil { - t.Fatal("expected error when context is cancelled or ffprobe fails") - } -} diff --git a/internal/probe/remux.go b/internal/probe/remux.go deleted file mode 100644 index f6becb8..0000000 --- a/internal/probe/remux.go +++ /dev/null @@ -1,119 +0,0 @@ -package probe - -import ( - "context" - "errors" - "fmt" - "io" - "log/slog" - "os" - "os/exec" - "time" -) - -// Remuxer remuxes media on-the-fly to a browser-friendly container. -type Remuxer interface { - Remux(ctx context.Context, inputPath string, w io.Writer) error -} - -// FFRemuxer implements Remuxer using ffmpeg. -type FFRemuxer struct{} - -var _ Remuxer = (*FFRemuxer)(nil) - -// NewFFRemuxer creates a new FFRemuxer. -func NewFFRemuxer() *FFRemuxer { - return &FFRemuxer{} -} - -const remuxWaitDelay = 10 * time.Second - -// Remux runs ffmpeg to copy video/audio streams into a fragmented MP4 -// suitable for streaming to a browser. -func (f *FFRemuxer) Remux(ctx context.Context, inputPath string, w io.Writer) error { - cmd := exec.CommandContext( - ctx, - "ffmpeg", - "-hide_banner", - "-loglevel", "error", - "-i", inputPath, - "-map", "0:v:0?", - "-map", "0:a:0?", - "-dn", - "-sn", - "-c", "copy", - "-bsf:a", "aac_adtstoasc", - "-movflags", "frag_keyframe+empty_moov+default_base_moof", - "-f", "mp4", - "pipe:1", - ) - cmd.Stderr = os.Stderr - cmd.WaitDelay = remuxWaitDelay - stdout, err := cmd.StdoutPipe() - if err != nil { - return fmt.Errorf("remux stdout pipe: %w", err) - } - if err := cmd.Start(); err != nil { - if isContextError(err) { - return nil - } - return fmt.Errorf("remux start: %w", err) - } - var firstErr error - if _, err := io.Copy(w, stdout); err != nil { - if !isContextError(err) { - slog.Error("copy remuxed media", "file", inputPath, "err", err) - firstErr = fmt.Errorf("copy remuxed media: %w", err) - } - } - if err := cmd.Wait(); err != nil { - if !isContextError(err) && firstErr == nil { - firstErr = fmt.Errorf("remux wait: %w", err) - } - } - if firstErr != nil { - return firstErr - } - return nil -} - -func isContextError(err error) bool { - return errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) -} - -// LooksLikeMPEGTS inspects the first bytes of a file for MPEG-TS sync -// markers (0x47 at 188- or 192-byte intervals). -func LooksLikeMPEGTS(path string) bool { - f, err := os.Open(path) - if err != nil { - return false - } - defer f.Close() - - buf := make([]byte, 188*5) - n, err := f.Read(buf) - if err != nil && err != io.EOF { - return false - } - buf = buf[:n] - return hasMPEGTSsync(buf, 188) || hasMPEGTSsync(buf, 192) -} - -func hasMPEGTSsync(buf []byte, packetSize int) bool { - if len(buf) < packetSize*3+1 { - return false - } - for offset := 0; offset < packetSize; offset++ { - matches := 0 - for pos := offset; pos < len(buf); pos += packetSize { - if buf[pos] != 0x47 { - break - } - matches++ - if matches >= 3 { - return true - } - } - } - return false -} diff --git a/internal/probe/remux_test.go b/internal/probe/remux_test.go deleted file mode 100644 index 0a5a611..0000000 --- a/internal/probe/remux_test.go +++ /dev/null @@ -1,85 +0,0 @@ -package probe - -import ( - "context" - "errors" - "os" - "path/filepath" - "strings" - "testing" -) - -type cancelingWriter struct { - cancel context.CancelFunc - err error -} - -func (w cancelingWriter) Write(p []byte) (int, error) { - w.cancel() - if w.err != nil { - return 0, w.err - } - return len(p), nil -} - -func TestFFRemuxerReturnsCopyErrorWhenContextCanceled(t *testing.T) { - installFakeFFmpeg(t, "printf remuxed-output\nsleep 10\n") - errRemuxWrite := errors.New("remux write failed") - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - err := NewFFRemuxer().Remux(ctx, "input.ts", cancelingWriter{ - cancel: cancel, - err: errRemuxWrite, - }) - if !errors.Is(err, errRemuxWrite) { - t.Fatalf("expected copy error, got %v", err) - } -} - -func TestFFRemuxerReturnsWaitErrorWhenContextCanceled(t *testing.T) { - installFakeFFmpeg(t, "printf remuxed-output\nexit 7\n") - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - err := NewFFRemuxer().Remux(ctx, "input.ts", cancelingWriter{cancel: cancel}) - if err == nil { - t.Fatal("expected wait error") - } - if !strings.Contains(err.Error(), "remux wait") { - t.Fatalf("expected remux wait error, got %v", err) - } -} - -func TestLooksLikeMPEGTS(t *testing.T) { - ts := make([]byte, 188*5) - for i := 0; i < len(ts); i += 188 { - ts[i] = 0x47 - } - tsPath := filepath.Join(t.TempDir(), "mislabelled.mp4") - if err := os.WriteFile(tsPath, ts, 0o644); err != nil { - t.Fatal(err) - } - if !LooksLikeMPEGTS(tsPath) { - t.Fatal("expected MPEG-TS sync bytes to be detected") - } - - mp4Path := filepath.Join(t.TempDir(), "real.mp4") - if err := os.WriteFile(mp4Path, []byte("\x00\x00\x00\x18ftypmp42"), 0o644); err != nil { - t.Fatal(err) - } - if LooksLikeMPEGTS(mp4Path) { - t.Fatal("did not expect MP4 header to be detected as MPEG-TS") - } -} - -func installFakeFFmpeg(t *testing.T, body string) { - t.Helper() - dir := t.TempDir() - path := filepath.Join(dir, "ffmpeg") - script := "#!/bin/sh\n" + body - if err := os.WriteFile(path, []byte(script), 0o755); err != nil { - t.Fatal(err) - } - t.Setenv("PATH", dir+string(os.PathListSeparator)+os.Getenv("PATH")) -} diff --git a/internal/probe/testdata/golden.json b/internal/probe/testdata/golden.json deleted file mode 100644 index 0c6a9c0..0000000 --- a/internal/probe/testdata/golden.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "format": { - "duration": "123.456", - "bit_rate": "1000000" - }, - "streams": [ - { - "codec_name": "h264", - "codec_type": "video", - "width": 1920, - "height": 1080 - } - ] -} -- cgit v1.2.3