diff --git a/api/unstable/info.go b/api/unstable/info.go index e880aec2f4d9c1c14680cb816e2de84fc2398857..5f669d862680b3bb551201827c76cf2512942473 100644 --- a/api/unstable/info.go +++ b/api/unstable/info.go @@ -18,8 +18,8 @@ import ( "github.com/turt2live/matrix-media-repo/storage" "github.com/turt2live/matrix-media-repo/thumbnailing" "github.com/turt2live/matrix-media-repo/thumbnailing/i" - "github.com/turt2live/matrix-media-repo/util" "github.com/turt2live/matrix-media-repo/util/cleanup" + "github.com/turt2live/matrix-media-repo/util/util_byte_seeker" ) type mediaInfoHashes struct { @@ -123,7 +123,7 @@ func MediaInfo(r *http.Request, rctx rcontext.RequestContext, user api.UserInfo) } if strings.HasPrefix(response.ContentType, "audio/") { - generator, err := thumbnailing.GetGenerator(util.ByteCloser(b), response.ContentType, false) + generator, err := thumbnailing.GetGenerator(util_byte_seeker.NewByteSeeker(b), response.ContentType, false) if err == nil { if audiogenerator, ok := generator.(i.AudioGenerator); ok { audioInfo, err := audiogenerator.GetAudioData(b, 768, rctx) diff --git a/thumbnailing/i/flac.go b/thumbnailing/i/flac.go index 17e743b23f5c254da54c4290c6cb408f83a9578b..1c45f6e5892a31de3c34d11333c82f7b5cf616da 100644 --- a/thumbnailing/i/flac.go +++ b/thumbnailing/i/flac.go @@ -8,7 +8,7 @@ import ( "github.com/turt2live/matrix-media-repo/common/rcontext" "github.com/turt2live/matrix-media-repo/thumbnailing/m" "github.com/turt2live/matrix-media-repo/thumbnailing/u" - "github.com/turt2live/matrix-media-repo/util" + "github.com/turt2live/matrix-media-repo/util/util_byte_seeker" ) type flacGenerator struct { @@ -27,7 +27,7 @@ func (d flacGenerator) matches(img []byte, contentType string) bool { } func (d flacGenerator) decode(b []byte) (beep.StreamSeekCloser, beep.Format, error) { - audio, format, err := flac.Decode(util.ByteCloser(b)) + audio, format, err := flac.Decode(util_byte_seeker.NewByteSeeker(b)) if err != nil { return audio, format, errors.New("flac: error decoding audio: " + err.Error()) } diff --git a/thumbnailing/i/mp3.go b/thumbnailing/i/mp3.go index 8df8924209954cc3ecc4f1bc133343d4bf7d5d32..b3c7f8dd30de34f187fee3d240e32120806bf84c 100644 --- a/thumbnailing/i/mp3.go +++ b/thumbnailing/i/mp3.go @@ -6,7 +6,6 @@ import ( "image" "image/color" "image/draw" - "io" "io/ioutil" "math" "path" @@ -20,7 +19,8 @@ import ( "github.com/turt2live/matrix-media-repo/common/rcontext" "github.com/turt2live/matrix-media-repo/thumbnailing/m" "github.com/turt2live/matrix-media-repo/thumbnailing/u" - "github.com/turt2live/matrix-media-repo/util" + "github.com/turt2live/matrix-media-repo/util/util_audio" + "github.com/turt2live/matrix-media-repo/util/util_byte_seeker" ) type mp3Generator struct { @@ -39,7 +39,7 @@ func (d mp3Generator) matches(img []byte, contentType string) bool { } func (d mp3Generator) decode(b []byte) (beep.StreamSeekCloser, beep.Format, error) { - audio, format, err := mp3.Decode(util.ByteCloser(b)) + audio, format, err := mp3.Decode(util_byte_seeker.NewByteSeeker(b)) if err != nil { return audio, format, errors.New("mp3: error decoding audio: " + err.Error()) } @@ -67,39 +67,16 @@ func (d mp3Generator) GetAudioData(b []byte, nKeys int, ctx rcontext.RequestCont } func (d mp3Generator) GetDataFromStream(audio beep.StreamSeekCloser, format beep.Format, nKeys int) (*m.AudioInfo, error) { - allSamples := make([][2]float64, 0) - - moreSamples := true - samples := make([][2]float64, 100000) // a 3 minute mp3 has roughly 7 million samples, so reduce the number of iterations we have to do - for moreSamples { - n, ok := audio.Stream(*&samples) - if n == 0 { - moreSamples = false - } - if !ok && audio.Err() != nil && audio.Err() != io.EOF { - return nil, errors.New("beep-visual: error sampling audio: " + audio.Err().Error()) - } - for i, v := range samples { - if i >= n { - break - } - allSamples = append(allSamples, v) - } - } - - downsampled := make([][2]float64, 0) - everyNth := int(math.Round(float64(len(allSamples)) / float64(nKeys))) - for i, s := range allSamples { - if i%everyNth != 0 { - continue - } - downsampled = append(downsampled, s) + totalSamples := audio.Len() + downsampled, err := util_audio.FastSampleAudio(audio, nKeys) + if err != nil { + return nil, err } return &m.AudioInfo{ - Duration: format.SampleRate.D(len(allSamples)), + Duration: format.SampleRate.D(totalSamples), Channels: format.NumChannels, - TotalSamples: len(allSamples), + TotalSamples: totalSamples, KeySamples: downsampled, }, nil } diff --git a/thumbnailing/i/ogg.go b/thumbnailing/i/ogg.go index ef2eaba2f564b084e00ca185bd77e539d9475db4..8b73cb41891dbc2613a5e2915dade50aca8e2325 100644 --- a/thumbnailing/i/ogg.go +++ b/thumbnailing/i/ogg.go @@ -8,7 +8,7 @@ import ( "github.com/turt2live/matrix-media-repo/common/rcontext" "github.com/turt2live/matrix-media-repo/thumbnailing/m" "github.com/turt2live/matrix-media-repo/thumbnailing/u" - "github.com/turt2live/matrix-media-repo/util" + "github.com/turt2live/matrix-media-repo/util/util_byte_seeker" ) type oggGenerator struct { @@ -27,7 +27,7 @@ func (d oggGenerator) matches(img []byte, contentType string) bool { } func (d oggGenerator) decode(b []byte) (beep.StreamSeekCloser, beep.Format, error) { - audio, format, err := vorbis.Decode(util.ByteCloser(b)) + audio, format, err := vorbis.Decode(util_byte_seeker.NewByteSeeker(b)) if err != nil { return audio, format, errors.New("ogg: error decoding audio: " + err.Error()) } diff --git a/thumbnailing/i/wav.go b/thumbnailing/i/wav.go index 7d63b5ab422d8cf081ed9c7e8ff108dca10de87e..265fc28fdfdf2334fd41dbb98b82e9978b4f0a3a 100644 --- a/thumbnailing/i/wav.go +++ b/thumbnailing/i/wav.go @@ -8,7 +8,7 @@ import ( "github.com/turt2live/matrix-media-repo/common/rcontext" "github.com/turt2live/matrix-media-repo/thumbnailing/m" "github.com/turt2live/matrix-media-repo/thumbnailing/u" - "github.com/turt2live/matrix-media-repo/util" + "github.com/turt2live/matrix-media-repo/util/util_byte_seeker" ) type wavGenerator struct { @@ -27,7 +27,7 @@ func (d wavGenerator) matches(img []byte, contentType string) bool { } func (d wavGenerator) decode(b []byte) (beep.StreamSeekCloser, beep.Format, error) { - audio, format, err := wav.Decode(util.ByteCloser(b)) + audio, format, err := wav.Decode(util_byte_seeker.NewByteSeeker(b)) if err != nil { return audio, format, errors.New("wav: error decoding audio: " + err.Error()) } diff --git a/util/streams.go b/util/streams.go index e01d01c92cc3434698ba464ef6d1241650151b37..a51adcdb61ed957ec7cef3c4ad10c8f2fde266ea 100644 --- a/util/streams.go +++ b/util/streams.go @@ -50,8 +50,3 @@ func GetSha256HashOfStream(r io.ReadCloser) (string, error) { return hex.EncodeToString(hasher.Sum(nil)), nil } - -func ByteCloser(b []byte) io.ReadCloser { - buf := bytes.NewBuffer(b) - return ioutil.NopCloser(buf) -} \ No newline at end of file diff --git a/util/util_audio/fastsample.go b/util/util_audio/fastsample.go new file mode 100644 index 0000000000000000000000000000000000000000..bef57ca3ad84d4470883cfe9b088041bf1e2e2e3 --- /dev/null +++ b/util/util_audio/fastsample.go @@ -0,0 +1,39 @@ +package util_audio + +import ( + "errors" + "math" + + "github.com/faiface/beep" +) + +func FastSampleAudio(stream beep.StreamSeekCloser, numSamples int) ([][2]float64, error) { + everyNth := int(math.Round(float64(stream.Len()) / float64(numSamples))) + samples := make([][2]float64, numSamples) + totalRead := 0 + for i := range samples { + pos := i * everyNth + if stream.Position() != pos { + err := stream.Seek(pos) + if err != nil { + return nil, errors.New("fast-sample: could not seek: " + err.Error()) + } + } + + sample := make([][2]float64, 1) + n, _ := stream.Stream(sample) + if stream.Err() != nil { + return nil, errors.New("fast-sample: could not stream: " + stream.Err().Error()) + } + if n > 0 { + samples[i] = sample[0] + totalRead++ + } else { + break + } + } + if totalRead != len(samples) { + return samples[:totalRead], nil + } + return samples, nil +} diff --git a/util/util_byte_seeker/seekable.go b/util/util_byte_seeker/seekable.go new file mode 100644 index 0000000000000000000000000000000000000000..1257ec72e9dd41ec4ae021f19aeac6c7547fe848 --- /dev/null +++ b/util/util_byte_seeker/seekable.go @@ -0,0 +1,25 @@ +package util_byte_seeker + +import ( + "bytes" +) + +type ByteSeeker struct { + s *bytes.Reader +} + +func NewByteSeeker(b []byte) ByteSeeker { + return ByteSeeker{s: bytes.NewReader(b)} +} + +func (s ByteSeeker) Close() error { + return nil // no-op +} + +func (s ByteSeeker) Read(p []byte) (n int, err error) { + return s.s.Read(p) +} + +func (s ByteSeeker) Seek(offset int64, whence int) (int64, error) { + return s.s.Seek(offset, whence) +}