Skip to content

Commit

Permalink
refactor: preview for very small videos
Browse files Browse the repository at this point in the history
  • Loading branch information
alxarno committed Nov 17, 2024
1 parent 2857659 commit d3a5dbe
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 23 deletions.
2 changes: 1 addition & 1 deletion internal/crawler_os_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ func TestCrawlerOS(t *testing.T) {

files, err := NewCrawlerOS("../test").Scan("../test/index.tinytune")
require.NoError(err)
require.Len(files, 15)
require.Len(files, 16)
}
4 changes: 2 additions & 2 deletions internal/excluded_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func TestIncludesFilter(t *testing.T) {

pattern := "\\.(mp4)$"
passFiles := filter(paths, filterHandler(mustCompile(t, pattern)))
require.Len(passFiles, 2)
require.Len(passFiles, 3)
_, ok := passFiles["../test/sample.mp4"]
require.True(ok)

Expand All @@ -52,7 +52,7 @@ func TestGetExcludedFiles(t *testing.T) {
excludePatterns := mustCompile(t, "\\.(mp4)$")

excludedFiles := GetExcludedFiles(files, includePatterns, excludePatterns)
require.Len(excludedFiles, 1)
require.Len(excludedFiles, 2)
_, ok := excludedFiles["../test/sample.mp4"]
require.True(ok)
}
2 changes: 1 addition & 1 deletion pkg/preview/preview_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func TestPreview(t *testing.T) {
},
{
source: mockSource{video: true, path: "../../test/sample.mp4"},
dataHash: "ab9e213afa42466148583b1ddab5bc00f8218ecac230cc7f8b6797e9e5179ba2",
dataHash: "7f2d1e244e636db9178a8cb49d375ce3268048fddd9efa1d7943e03003a2b708",
},
{
source: mockSource{path: "../../test/sample.txt"},
Expand Down
61 changes: 47 additions & 14 deletions pkg/preview/video.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ var (
ErrImageDecode = errors.New("failed to decode image")
ErrImageEncode = errors.New("failed to encode image")
ErrImageScale = errors.New("failed to scale image")
ErrBufferCopy = errors.New("failed to copy buffer")
)

type probeFormat struct {
Expand Down Expand Up @@ -81,7 +82,6 @@ func getVideoSnapshoter(wg *sync.WaitGroup, errCh chan error, params VideoParams
options := seekOptions
options = append(options, inputOptions...)
options = append(options, outputOptions...)

cmd := exec.CommandContext(ctx, "ffmpeg", options...)
stdErrBuf := bytes.NewBuffer(nil)
cmd.Stdout = w
Expand Down Expand Up @@ -113,7 +113,7 @@ func combineImagesToPreview(buffers []*bytes.Buffer) ([]byte, error) {
for i, v := range buffers[1:] {
img, _, err := image.Decode(v)
if err != nil {
return nil, fmt.Errorf("%w: %w", ErrImageDecode, err)
return nil, fmt.Errorf("%w [%d]: %w", ErrImageDecode, i, err)
}

s := image.Point{0, (i + 1) * height}
Expand Down Expand Up @@ -141,26 +141,46 @@ func combineImagesToPreview(buffers []*bytes.Buffer) ([]byte, error) {
return downScale(image, imageCollage)
}

func produceVideoPreview(path string, duration time.Duration, params VideoParams) ([]byte, error) {
minimumStart := 3
images := []*bytes.Buffer{}
func getTimeCodes(duration time.Duration) ([]time.Duration, []bool) {
parts := 5
minimumStart := 2
timestamps := []time.Duration{}
repeat := make([]bool, parts)
step := duration / time.Duration(parts)
waitGroup := new(sync.WaitGroup)
errs := make(chan error, parts)

for v := range parts {
waitGroup.Add(1)
if step < time.Second {
step = time.Second
}

timestamp := step * time.Duration(v)
if timestamp == 0 {
for part := range parts {
timestamp := step * time.Duration(part)
if timestamp == 0 && duration > time.Second*7 {
timestamp = time.Second * time.Duration(minimumStart)
}

buff := new(bytes.Buffer)
images = append(images, buff)
// if video is small (< parts*time.Second), then just copy last images to remaining buffers
if timestamp > duration && part > 1 || timestamp == duration {
repeat[part] = true
}

timestamps = append(timestamps, timestamp)
}

return timestamps, repeat
}

func produceVideoPreview(path string, duration time.Duration, params VideoParams) ([]byte, error) {
timeCodes, repeats := getTimeCodes(duration)
images := make([]*bytes.Buffer, len(repeats))
waitGroup := new(sync.WaitGroup)
errs := make(chan error, len(timeCodes))

for index, timestamp := range timeCodes {
images[index] = new(bytes.Buffer)

go getVideoSnapshoter(waitGroup, errs, params)(path, timestamp, buff)
waitGroup.Add(1)

go getVideoSnapshoter(waitGroup, errs, params)(path, timestamp, images[index])
}

waitGroup.Wait()
Expand All @@ -170,6 +190,19 @@ func produceVideoPreview(path string, duration time.Duration, params VideoParams
return nil, fmt.Errorf("%w: %w", ErrPullSnapshot, err)
}

for index, repeat := range repeats {
if !repeat {
continue
}
// just copy data from previous buffer
images[index] = new(bytes.Buffer)

_, err := images[index].Write(images[index-1].Bytes())
if err != nil {
return nil, fmt.Errorf("%w: %w", ErrBufferCopy, err)
}
}

return combineImagesToPreview(images)
}

Expand Down
21 changes: 16 additions & 5 deletions pkg/preview/video_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package preview
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"testing"
"time"

Expand All @@ -17,14 +16,14 @@ func TestPreviewVideo(t *testing.T) {

preview, err := videoPreview("../../test/sample.mp4", VideoParams{timeout: time.Minute})
require.NoError(t, err)
assert.Len(preview.Data(), 105634, fmt.Sprintf("got %d", len(preview.Data())))
assert.Len(preview.Data(), 107832)
assert.EqualValues(time.Second*5, preview.Duration())
width, height := preview.Resolution()
assert.Equal(1280, width)
assert.Equal(720, height)

hash := sha256.Sum256(preview.Data())
assert.Equal("ab9e213afa42466148583b1ddab5bc00f8218ecac230cc7f8b6797e9e5179ba2", hex.EncodeToString(hash[:]))
assert.Equal("7f2d1e244e636db9178a8cb49d375ce3268048fddd9efa1d7943e03003a2b708", hex.EncodeToString(hash[:]))
}

func TestPreviewVideoFLV(t *testing.T) {
Expand All @@ -33,8 +32,20 @@ func TestPreviewVideoFLV(t *testing.T) {

preview, err := videoPreview("../../test/video/sample_960x400_ocean_with_audio.flv", VideoParams{timeout: time.Minute})
require.NoError(t, err)
assert.Len(preview.Data(), 37618, fmt.Sprintf("got %d", len(preview.Data())))
assert.Len(preview.Data(), 37038)

hash := sha256.Sum256(preview.Data())
assert.Equal("30d4dcd9d15d17939a74bfbc7c4a19126a61369504d308bc11847f1bb45e4627", hex.EncodeToString(hash[:]))
assert.Equal("89f3a6b1f30ef25f1e4b1fc4cdfe6af7c7d9632d58c2ef15bdb88d7302ced91f", hex.EncodeToString(hash[:]))
}

func TestPreviewShortVideo(t *testing.T) {
t.Parallel()
assert := assert.New(t)

preview, err := videoPreview("../../test/short.mp4", VideoParams{timeout: time.Minute})
require.NoError(t, err)
assert.Len(preview.Data(), 107340)

hash := sha256.Sum256(preview.Data())
assert.Equal("43d96ebec876b891810f55bb8e99529ea52ebc325d4ed1d0113ab85afc115a95", hex.EncodeToString(hash[:]))
}
Binary file added test/short.mp4
Binary file not shown.

0 comments on commit d3a5dbe

Please sign in to comment.