2021-12-29 15:38:25 +00:00
|
|
|
package media_test
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"context"
|
|
|
|
"errors"
|
|
|
|
"io"
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
"git.netflux.io/rob/clipper/config"
|
|
|
|
"git.netflux.io/rob/clipper/generated/mocks"
|
|
|
|
"git.netflux.io/rob/clipper/generated/store"
|
|
|
|
"git.netflux.io/rob/clipper/media"
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"github.com/jackc/pgx/v4"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/stretchr/testify/mock"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"go.uber.org/zap"
|
|
|
|
)
|
|
|
|
|
|
|
|
func TestGetSegment(t *testing.T) {
|
2021-12-31 18:25:22 +00:00
|
|
|
const inFixturePath = "testdata/tone-44100-stereo-int16-30000ms.raw"
|
2022-01-05 18:49:21 +00:00
|
|
|
mediaSetID := uuid.MustParse("4c440241-cca9-436f-adb0-be074588cf2b")
|
|
|
|
wp := media.NewTestWorkerPool()
|
2021-12-29 15:38:25 +00:00
|
|
|
|
|
|
|
t.Run("invalid range", func(t *testing.T) {
|
|
|
|
var mockStore mocks.Store
|
|
|
|
var fileStore mocks.FileStore
|
2022-01-05 18:49:21 +00:00
|
|
|
service := media.NewMediaSetService(&mockStore, nil, &fileStore, nil, wp, config.Config{}, zap.NewNop().Sugar())
|
2021-12-29 15:38:25 +00:00
|
|
|
|
|
|
|
stream, err := service.GetAudioSegment(context.Background(), mediaSetID, 1, 0, media.AudioFormatMP3)
|
|
|
|
require.Nil(t, stream)
|
|
|
|
require.EqualError(t, err, "invalid range")
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("error fetching media set", func(t *testing.T) {
|
|
|
|
var mockStore mocks.Store
|
|
|
|
mockStore.On("GetMediaSet", mock.Anything, mediaSetID).Return(store.MediaSet{}, pgx.ErrNoRows)
|
|
|
|
var fileStore mocks.FileStore
|
2022-01-05 18:49:21 +00:00
|
|
|
service := media.NewMediaSetService(&mockStore, nil, &fileStore, nil, wp, config.Config{}, zap.NewNop().Sugar())
|
2021-12-29 15:38:25 +00:00
|
|
|
|
|
|
|
stream, err := service.GetAudioSegment(context.Background(), mediaSetID, 0, 1, media.AudioFormatMP3)
|
|
|
|
require.Nil(t, stream)
|
|
|
|
require.EqualError(t, err, "error getting media set: no rows in result set")
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("error fetching audio data", func(t *testing.T) {
|
|
|
|
mediaSet := store.MediaSet{ID: mediaSetID, AudioChannels: 2}
|
|
|
|
|
|
|
|
var mockStore mocks.Store
|
|
|
|
mockStore.On("GetMediaSet", mock.Anything, mediaSetID).Return(mediaSet, nil)
|
|
|
|
|
|
|
|
var fileStore mocks.FileStore
|
|
|
|
fileStore.On("GetObjectWithRange", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
|
|
|
|
Return(nil, errors.New("network error"))
|
|
|
|
|
2022-01-05 18:49:21 +00:00
|
|
|
service := media.NewMediaSetService(&mockStore, nil, &fileStore, nil, wp, config.Config{}, zap.NewNop().Sugar())
|
2021-12-29 15:38:25 +00:00
|
|
|
|
|
|
|
stream, err := service.GetAudioSegment(context.Background(), mediaSetID, 0, 1, media.AudioFormatMP3)
|
|
|
|
require.Nil(t, stream)
|
|
|
|
require.EqualError(t, err, "error getting object from store: network error")
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("ffmpeg returns non-zero error code", func(t *testing.T) {
|
|
|
|
mediaSet := store.MediaSet{ID: mediaSetID, AudioChannels: 2}
|
|
|
|
|
|
|
|
var mockStore mocks.Store
|
|
|
|
mockStore.On("GetMediaSet", mock.Anything, mediaSetID).Return(mediaSet, nil)
|
|
|
|
|
|
|
|
var fileStore mocks.FileStore
|
|
|
|
fileStore.On("GetObjectWithRange", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
|
2021-12-31 18:25:22 +00:00
|
|
|
Return(fixtureReader(t, inFixturePath, 1), nil)
|
2021-12-29 15:38:25 +00:00
|
|
|
|
|
|
|
cmd := helperCommand(t, "", "", "something bad happened", 2)
|
2022-01-05 18:49:21 +00:00
|
|
|
service := media.NewMediaSetService(&mockStore, nil, &fileStore, cmd, wp, config.Config{}, zap.NewNop().Sugar())
|
2021-12-29 15:38:25 +00:00
|
|
|
|
|
|
|
stream, err := service.GetAudioSegment(context.Background(), mediaSetID, 0, 1, media.AudioFormatMP3)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
_, err = stream.Next(context.Background())
|
|
|
|
require.EqualError(t, err, "error waiting for ffmpeg: exit status 2, output: something bad happened")
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
testCases := []struct {
|
|
|
|
name string
|
|
|
|
audioFormat media.AudioFormat
|
|
|
|
audioChannels int32
|
|
|
|
inStartFrame, inEndFrame int64
|
|
|
|
wantStartByte, wantEndByte int64
|
|
|
|
outFixturePath string
|
|
|
|
wantCommand string
|
|
|
|
wantOutput string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "mono to mp3",
|
|
|
|
audioFormat: media.AudioFormatMP3,
|
|
|
|
audioChannels: 1,
|
|
|
|
inStartFrame: 500,
|
|
|
|
inEndFrame: 2_000,
|
|
|
|
wantStartByte: 1_000,
|
|
|
|
wantEndByte: 4_000,
|
|
|
|
outFixturePath: "testdata/fake.mp3",
|
|
|
|
wantCommand: "ffmpeg -hide_banner -loglevel error -f s16le -ac 1 -ar 48000 -i - -f mp3 -",
|
|
|
|
wantOutput: "this is a fake mp3",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "stereo to mp3",
|
|
|
|
audioFormat: media.AudioFormatMP3,
|
|
|
|
audioChannels: 2,
|
|
|
|
inStartFrame: 0,
|
|
|
|
inEndFrame: 1_323_000,
|
|
|
|
wantStartByte: 0,
|
|
|
|
wantEndByte: 5_292_000,
|
|
|
|
outFixturePath: "testdata/fake.mp3",
|
|
|
|
wantCommand: "ffmpeg -hide_banner -loglevel error -f s16le -ac 2 -ar 48000 -i - -f mp3 -",
|
|
|
|
wantOutput: "this is a fake mp3",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "mono to wav",
|
|
|
|
audioFormat: media.AudioFormatWAV,
|
|
|
|
audioChannels: 1,
|
|
|
|
inStartFrame: 16_384,
|
|
|
|
inEndFrame: 32_768,
|
|
|
|
wantStartByte: 32_768,
|
|
|
|
wantEndByte: 65_536,
|
|
|
|
outFixturePath: "testdata/fake.wav",
|
|
|
|
wantCommand: "ffmpeg -hide_banner -loglevel error -f s16le -ac 1 -ar 48000 -i - -f wav -",
|
|
|
|
wantOutput: "this is a fake wav",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "stereo to wav",
|
|
|
|
audioFormat: media.AudioFormatWAV,
|
|
|
|
audioChannels: 2,
|
|
|
|
inStartFrame: 2_048,
|
|
|
|
inEndFrame: 4_096,
|
|
|
|
wantStartByte: 8_192,
|
|
|
|
wantEndByte: 16_384,
|
|
|
|
outFixturePath: "testdata/fake.wav",
|
|
|
|
wantCommand: "ffmpeg -hide_banner -loglevel error -f s16le -ac 2 -ar 48000 -i - -f wav -",
|
|
|
|
wantOutput: "this is a fake wav",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range testCases {
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
mediaSet := store.MediaSet{ID: mediaSetID, AudioChannels: tc.audioChannels}
|
|
|
|
var mockStore mocks.Store
|
|
|
|
mockStore.On("GetMediaSet", mock.Anything, mediaSetID).Return(mediaSet, nil)
|
|
|
|
defer mockStore.AssertExpectations(t)
|
|
|
|
|
|
|
|
var fileStore mocks.FileStore
|
|
|
|
fileStore.
|
|
|
|
On("GetObjectWithRange", mock.Anything, "media_sets/4c440241-cca9-436f-adb0-be074588cf2b/audio.raw", tc.wantStartByte, tc.wantEndByte).
|
2021-12-31 18:25:22 +00:00
|
|
|
Return(fixtureReader(t, inFixturePath, tc.wantEndByte-tc.wantStartByte), nil)
|
2021-12-29 15:38:25 +00:00
|
|
|
defer fileStore.AssertExpectations(t)
|
|
|
|
|
|
|
|
cmd := helperCommand(t, tc.wantCommand, tc.outFixturePath, "", 0)
|
2022-01-05 18:49:21 +00:00
|
|
|
service := media.NewMediaSetService(&mockStore, nil, &fileStore, cmd, wp, config.Config{}, zap.NewNop().Sugar())
|
2021-12-29 15:38:25 +00:00
|
|
|
|
|
|
|
stream, err := service.GetAudioSegment(ctx, mediaSetID, tc.inStartFrame, tc.inEndFrame, tc.audioFormat)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
var data bytes.Buffer
|
|
|
|
var lastPercentComplete float32
|
|
|
|
var progress media.AudioSegmentProgress
|
|
|
|
for {
|
|
|
|
progress, err = stream.Next(ctx)
|
|
|
|
if err == io.EOF {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.GreaterOrEqual(t, progress.PercentComplete, lastPercentComplete)
|
|
|
|
lastPercentComplete = progress.PercentComplete
|
|
|
|
data.Write(progress.Data)
|
|
|
|
}
|
|
|
|
|
|
|
|
assert.Equal(t, tc.wantOutput, data.String())
|
|
|
|
assert.Equal(t, float32(100), lastPercentComplete)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|