diff --git a/backend/media/modulo_reader.go b/backend/media/modulo_reader.go new file mode 100644 index 0000000..26c057d --- /dev/null +++ b/backend/media/modulo_reader.go @@ -0,0 +1,43 @@ +package media + +import ( + "bytes" + "io" +) + +// ModuloReader reads from a reader in block sizes that are exactly modulo +// modSize, with any remainder buffered until the next read. +type ModuloReader struct { + io.ReadCloser + + buf bytes.Buffer + modSize int +} + +func NewModuloReader(r io.ReadCloser, modSize int) *ModuloReader { + return &ModuloReader{ReadCloser: r, modSize: modSize} +} + +func (r *ModuloReader) Read(p []byte) (int, error) { + // err is always io.EOF or nil + nr1, _ := r.buf.Read(p) + nr2, err := r.ReadCloser.Read(p[nr1:]) + + nr := nr1 + nr2 + rem := nr % r.modSize + + // if there was an error, return immediately. + if err == io.EOF { + return nr, err + } else if err != nil { + return nr - rem, err + } + + // write any remainder to the buffer + if rem != 0 { + // err is always nil + _, _ = r.buf.Write(p[nr-rem : nr]) + } + + return nr - rem, err +} diff --git a/backend/media/modulo_reader_test.go b/backend/media/modulo_reader_test.go new file mode 100644 index 0000000..4434fac --- /dev/null +++ b/backend/media/modulo_reader_test.go @@ -0,0 +1,80 @@ +package media_test + +import ( + "io" + "testing" + + "git.netflux.io/rob/clipper/media" + "github.com/stretchr/testify/assert" +) + +type testReader struct { + count int + data [][]byte +} + +func (r *testReader) Read(p []byte) (int, error) { + if r.count == len(r.data) { + return 0, io.EOF + } + n := copy(p, r.data[r.count]) + r.count++ + return n, nil +} + +func TestModuloBufReader(t *testing.T) { + reader := testReader{ + data: [][]byte{ + {'a', 'b', 'c', 'd'}, + {'e', 'f', 'g', 'h', 'i'}, + {}, + {'j', 'k', 'l', 'm'}, + {'n', 'o', 'p'}, + {'q', 'r', 's', 't', 'u'}, + }, + } + + modReader := media.NewModuloReader(io.NopCloser(&reader), 4) + + out := make([]byte, 5) + + n, err := modReader.Read(out) + assert.NoError(t, err) + assert.Equal(t, 4, n) + assert.Equal(t, []byte{'a', 'b', 'c', 'd'}, out[:n]) + + n, err = modReader.Read(out) + assert.NoError(t, err) + assert.Equal(t, 4, n) + assert.Equal(t, []byte{'e', 'f', 'g', 'h'}, out[:n]) + + n, err = modReader.Read(out) + assert.NoError(t, err) + assert.Zero(t, n) + assert.Empty(t, out[:n]) + + n, err = modReader.Read(out) + assert.NoError(t, err) + assert.NoError(t, err) + assert.Equal(t, 4, n) + assert.Equal(t, []byte{'i', 'j', 'k', 'l'}, out[:n]) + + n, err = modReader.Read(out) + assert.NoError(t, err) + assert.Equal(t, 4, n) + assert.Equal(t, []byte{'m', 'n', 'o', 'p'}, out[:n]) + + n, err = modReader.Read(out) + assert.NoError(t, err) + assert.Equal(t, 4, n) + assert.Equal(t, []byte{'q', 'r', 's', 't'}, out[:n]) + + n, err = modReader.Read(out) + assert.Equal(t, io.EOF, err) + assert.Equal(t, 1, n) + assert.Equal(t, []byte{'u'}, out[:n]) + + n, err = modReader.Read(out) + assert.Zero(t, n) + assert.Equal(t, io.EOF, err) +} diff --git a/backend/media/service.go b/backend/media/service.go index d60b500..cf70cc2 100644 --- a/backend/media/service.go +++ b/backend/media/service.go @@ -291,7 +291,7 @@ func (s *MediaSetService) getPeaksFromFileStore(ctx context.Context, mediaSet st state := getPeaksFromFileStoreState{ getPeaksProgressReader: getPeaksProgressReader, - reader: NewModuloBufReader(object, int(mediaSet.AudioChannels)*SizeOfInt16), + reader: NewModuloReader(object, int(mediaSet.AudioChannels)*SizeOfInt16), fileStore: s.fileStore, config: s.config, logger: s.logger, @@ -376,7 +376,7 @@ func (s *MediaSetService) GetPeaksForSegment(ctx context.Context, id uuid.UUID, const readBufSizeBytes = 8_192 channels := int(mediaSet.AudioChannels) - modReader := NewModuloBufReader(object, channels*SizeOfInt16) + modReader := NewModuloReader(object, channels*SizeOfInt16) readBuf := make([]byte, readBufSizeBytes) peaks := make([]int16, channels*numBins) totalFrames := endFrame - startFrame @@ -449,43 +449,6 @@ func sqlInt32(i int32) sql.NullInt32 { return sql.NullInt32{Int32: i, Valid: true} } -// ModuloBufReader reads from a reader in block sizes that are exactly modulo -// modSize, with any remainder buffered until the next read. -type ModuloBufReader struct { - io.ReadCloser - - buf bytes.Buffer - modSize int -} - -func NewModuloBufReader(r io.ReadCloser, modSize int) *ModuloBufReader { - return &ModuloBufReader{ReadCloser: r, modSize: modSize} -} - -func (r *ModuloBufReader) Read(p []byte) (int, error) { - // err is always io.EOF or nil - nr1, _ := r.buf.Read(p) - nr2, err := r.ReadCloser.Read(p[nr1:]) - - nr := nr1 + nr2 - rem := nr % r.modSize - - // if there was an error, return immediately. - if err == io.EOF { - return nr, err - } else if err != nil { - return nr - rem, err - } - - // write any remainder to the buffer - if rem != 0 { - // err is always nil - _, _ = r.buf.Write(p[nr-rem : nr]) - } - - return nr - rem, err -} - type VideoThumbnail struct { Data []byte Width, Height int diff --git a/backend/media/service_test.go b/backend/media/service_test.go index 1b3e15b..99a5944 100644 --- a/backend/media/service_test.go +++ b/backend/media/service_test.go @@ -160,74 +160,3 @@ func BenchmarkGetPeaksForSegment(b *testing.B) { require.NoError(b, err) } } - -type testReader struct { - count int - data [][]byte -} - -func (r *testReader) Read(p []byte) (int, error) { - if r.count == len(r.data) { - return 0, io.EOF - } - n := copy(p, r.data[r.count]) - r.count++ - return n, nil -} - -func TestModuloBufReader(t *testing.T) { - reader := testReader{ - data: [][]byte{ - {'a', 'b', 'c', 'd'}, - {'e', 'f', 'g', 'h', 'i'}, - {}, - {'j', 'k', 'l', 'm'}, - {'n', 'o', 'p'}, - {'q', 'r', 's', 't', 'u'}, - }, - } - - modReader := media.NewModuloBufReader(io.NopCloser(&reader), 4) - - out := make([]byte, 5) - - n, err := modReader.Read(out) - assert.NoError(t, err) - assert.Equal(t, 4, n) - assert.Equal(t, []byte{'a', 'b', 'c', 'd'}, out[:n]) - - n, err = modReader.Read(out) - assert.NoError(t, err) - assert.Equal(t, 4, n) - assert.Equal(t, []byte{'e', 'f', 'g', 'h'}, out[:n]) - - n, err = modReader.Read(out) - assert.NoError(t, err) - assert.Zero(t, n) - assert.Empty(t, out[:n]) - - n, err = modReader.Read(out) - assert.NoError(t, err) - assert.NoError(t, err) - assert.Equal(t, 4, n) - assert.Equal(t, []byte{'i', 'j', 'k', 'l'}, out[:n]) - - n, err = modReader.Read(out) - assert.NoError(t, err) - assert.Equal(t, 4, n) - assert.Equal(t, []byte{'m', 'n', 'o', 'p'}, out[:n]) - - n, err = modReader.Read(out) - assert.NoError(t, err) - assert.Equal(t, 4, n) - assert.Equal(t, []byte{'q', 'r', 's', 't'}, out[:n]) - - n, err = modReader.Read(out) - assert.Equal(t, io.EOF, err) - assert.Equal(t, 1, n) - assert.Equal(t, []byte{'u'}, out[:n]) - - n, err = modReader.Read(out) - assert.Zero(t, n) - assert.Equal(t, io.EOF, err) -}