diff --git a/backend/cmd/progress-test/main.go b/backend/cmd/progress-test/main.go index 93ea33b..27b02e6 100644 --- a/backend/cmd/progress-test/main.go +++ b/backend/cmd/progress-test/main.go @@ -13,6 +13,7 @@ import ( "github.com/aws/aws-sdk-go-v2/service/s3" "github.com/kkdai/youtube/v2" _ "github.com/lib/pq" + "go.uber.org/zap" ) const ( @@ -42,7 +43,7 @@ func main() { var youtubeClient youtube.Client // Create a MediaSetService - mediaSetService := media.NewMediaSetService(store, &youtubeClient, s3Client) + mediaSetService := media.NewMediaSetService(store, &youtubeClient, s3Client, zap.NewNop()) mediaSet, err := mediaSetService.Get(ctx, videoID) if err != nil { diff --git a/backend/go.mod b/backend/go.mod index 3749cb9..832e49a 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -8,10 +8,12 @@ require ( github.com/aws/aws-sdk-go-v2/service/s3 v1.17.0 github.com/aws/smithy-go v1.8.1 github.com/google/uuid v1.1.2 + github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 github.com/improbable-eng/grpc-web v0.14.1 github.com/kkdai/youtube/v2 v2.7.4 github.com/lib/pq v1.10.3 github.com/stretchr/testify v1.7.0 + go.uber.org/zap v1.19.1 google.golang.org/grpc v1.41.0 google.golang.org/protobuf v1.26.0 ) @@ -33,6 +35,8 @@ require ( github.com/mattn/go-isatty v0.0.14 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rs/cors v1.7.0 // indirect + go.uber.org/atomic v1.9.0 // indirect + go.uber.org/multierr v1.7.0 // indirect golang.org/x/net v0.0.0-20210913180222-943fd674d43e // indirect golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0 // indirect golang.org/x/text v0.3.7 // indirect diff --git a/backend/go.sum b/backend/go.sum index 824c650..666cd96 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -87,6 +87,8 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.8.0 h1:7N7RsEVvUcvEg7jrWKU5AnSi4/6b6 github.com/aws/aws-sdk-go-v2/service/sts v1.8.0/go.mod h1:dOlm91B439le5y1vtPCk5yJtbx3RdT3hRGYRY8TYKvQ= github.com/aws/smithy-go v1.8.1 h1:9Y6qxtzgEODaLNGN+oN2QvcHvKUe4jsH8w4M+8LXzGk= github.com/aws/smithy-go v1.8.1/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= +github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= @@ -185,6 +187,7 @@ github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFG github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -266,6 +269,8 @@ github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= +github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= +github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= @@ -408,6 +413,7 @@ github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0 github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= @@ -523,13 +529,21 @@ go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.11-0.20210813005559-691160354723 h1:sHOAIxRGBp443oHZIPB+HsUGaksVCXVQENPxwTfQdH4= +go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.7.0 h1:zaiO/rmgFjbmCXdSYJWQcdvOCsthmdaHfr3Gm2Kx4Ec= +go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +go.uber.org/zap v1.19.1 h1:ue41HOKd1vGURxrmeKIgELGb3jPW9DMUDGtsinblHwI= +go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -776,6 +790,7 @@ golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/backend/media/media_set.go b/backend/media/media_set.go index 0c4884b..65a4636 100644 --- a/backend/media/media_set.go +++ b/backend/media/media_set.go @@ -1,13 +1,7 @@ package media import ( - "encoding/binary" - "encoding/json" - "errors" "fmt" - "io" - "log" - "os" "time" "github.com/google/uuid" @@ -45,7 +39,7 @@ type MediaSet struct { ID uuid.UUID `json:"id"` YoutubeID string `json:"youtube_id"` - exists bool `json:"exists"` + exists bool } // New builds a new MediaSet with the given ID. @@ -59,80 +53,3 @@ func (m *MediaSet) EncodedAudioPath() string { return fmt.Sprintf("cache/%s.m4a" func (m *MediaSet) VideoPath() string { return fmt.Sprintf("cache/%s.mp4", m.YoutubeID) } func (m *MediaSet) ThumbnailPath() string { return fmt.Sprintf("cache/%s.jpg", m.YoutubeID) } func (m *MediaSet) MetadataPath() string { return fmt.Sprintf("cache/%s.json", m.YoutubeID) } - -func (m *MediaSet) Exists() bool { - if m.YoutubeID == "" { - return false - } - if m.exists { - return true - } - if _, err := os.Stat(m.MetadataPath()); err == nil { - m.exists = true - return true - } - return false -} - -func (m *MediaSet) Load() error { - if m.YoutubeID == "" { - return errors.New("error opening mediaset with blank ID") - } - - metadataFile, err := os.Open(m.MetadataPath()) - if err != nil { - return fmt.Errorf("error opening metadata file: %v", err) - } - defer func() { _ = metadataFile.Close() }() - - if err := json.NewDecoder(metadataFile).Decode(m); err != nil { - return fmt.Errorf("error decoding metadata: %v", err) - } - return nil -} - -func (m *MediaSet) Peaks(start, end int64, numBins int) ([][]int16, error) { - if !m.Exists() { - return nil, errors.New("cannot compute peaks for non-existent MediaSet") - } - - var err error - fptr, err := os.Open(m.RawAudioPath()) - if err != nil { - return nil, fmt.Errorf("audio open error: %v", err) - } - defer fptr.Close() - - startByte := start * int64(m.Audio.Channels) * SizeOfInt16 - if _, err = fptr.Seek(startByte, io.SeekStart); err != nil { - return nil, fmt.Errorf("audio seek error: %v", err) - } - - numFrames := end - start - framesPerBin := numFrames / int64(numBins) - - peaks := make([][]int16, m.Audio.Channels) - for i := 0; i < m.Audio.Channels; i++ { - peaks[i] = make([]int16, numBins) - } - - samples := make([]int16, framesPerBin*int64(m.Audio.Channels)) - - for binNum := 0; binNum < numBins; binNum++ { - if err := binary.Read(fptr, binary.LittleEndian, samples); err != nil { - return nil, fmt.Errorf("error reading samples: %v", err) - } - for i, samp := range samples { - if samp < 0 { - samp = -samp - } - chanIndex := i % m.Audio.Channels - if samp > peaks[chanIndex][binNum] { - peaks[chanIndex][binNum] = samp - } - } - } - - log.Println("finished generating peaks") - return peaks, nil -} diff --git a/backend/media/service.go b/backend/media/service.go index 338d855..b847724 100644 --- a/backend/media/service.go +++ b/backend/media/service.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "database/sql" + "encoding/binary" "errors" "fmt" "io" @@ -16,6 +17,7 @@ import ( "github.com/aws/aws-sdk-go-v2/service/s3" "github.com/google/uuid" youtubev2 "github.com/kkdai/youtube/v2" + "go.uber.org/zap" ) const s3Bucket = "clipper-development" @@ -76,13 +78,15 @@ type MediaSetService struct { store Store youtube YoutubeClient s3 S3Client + logger *zap.SugaredLogger } -func NewMediaSetService(store Store, youtubeClient YoutubeClient, s3Client S3Client) *MediaSetService { +func NewMediaSetService(store Store, youtubeClient YoutubeClient, s3Client S3Client, logger *zap.Logger) *MediaSetService { return &MediaSetService{ store: store, youtube: youtubeClient, s3: s3Client, + logger: logger.Sugar(), } } @@ -418,6 +422,78 @@ func (s *getAudioFromYoutubeState) run(ctx context.Context, mediaSetID uuid.UUID } } +func (s *MediaSetService) GetAudioSegment(ctx context.Context, id uuid.UUID, startFrame, endFrame int64, numBins int) ([][]int16, error) { + mediaSet, err := s.store.GetMediaSet(ctx, id) + if err != nil { + return nil, fmt.Errorf("error getting media set: %v", err) + } + + byteRange := fmt.Sprintf( + "bytes=%d-%d", + startFrame*int64(mediaSet.AudioChannels)*SizeOfInt16, + endFrame*int64(mediaSet.AudioChannels)*SizeOfInt16, + ) + input := s3.GetObjectInput{ + Bucket: aws.String(mediaSet.AudioS3Bucket.String), + Key: aws.String(mediaSet.AudioS3Key.String), + Range: aws.String(byteRange), + } + output, err := s.s3.GetObject(ctx, &input) + if err != nil { + return nil, fmt.Errorf("error getting object from s3: %v", err) + } + defer output.Body.Close() + + modReader := NewModuloBufReader(output.Body, int(mediaSet.AudioChannels)*SizeOfInt16) + bufSizeBytes := 8_192 + buf := make([]byte, bufSizeBytes) + + peaks := make([][]int16, mediaSet.AudioChannels) + for i := range peaks { + peaks[i] = make([]int16, numBins) + } + var currPeakIndex int + var currFrame int64 + + channels := int(mediaSet.AudioChannels) + totalFrames := endFrame - startFrame + framesPerBin := totalFrames / int64(numBins) + + samples := make([]int16, bufSizeBytes/SizeOfInt16) + + for { + n, err := modReader.Read(buf) + if err != nil { + if err == io.EOF { + break + } + return nil, fmt.Errorf("read error: %v", err) + } + + if err := binary.Read(bytes.NewReader(buf[:n]), binary.LittleEndian, samples); err != nil { + return nil, fmt.Errorf("error interpreting samples: %v", err) + } + + for i := 0; i < len(samples); i += channels { + for j := 0; j < channels; j++ { + samp := samples[i+j] + if samp < 0 { + samp = -samp + } + if samp > peaks[currPeakIndex][j] { + peaks[currPeakIndex][j] = samp + } + } + currFrame++ + if currFrame == framesPerBin { + currFrame = 0 + } + } + } + + return peaks, nil +} + func sqlString(s string) sql.NullString { return sql.NullString{String: s, Valid: true} } @@ -426,6 +502,8 @@ func sqlInt64(i int64) sql.NullInt64 { return sql.NullInt64{Int64: 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 diff --git a/backend/media/uploader.go b/backend/media/uploader.go index be319bc..e2452f3 100644 --- a/backend/media/uploader.go +++ b/backend/media/uploader.go @@ -14,6 +14,9 @@ import ( "github.com/aws/aws-sdk-go-v2/service/s3/types" ) +// multipartUploader uploads a file to S3. +// +// TODO: extract to s3 package type multipartUploader struct { s3 S3Client } diff --git a/backend/server/server.go b/backend/server/server.go index 58abe89..a723049 100644 --- a/backend/server/server.go +++ b/backend/server/server.go @@ -4,26 +4,21 @@ import ( "context" "fmt" "io" - "log" "net/http" - "os" "time" pbMediaSet "git.netflux.io/rob/clipper/generated/pb/media_set" "git.netflux.io/rob/clipper/media" "github.com/google/uuid" + grpczap "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap" "github.com/improbable-eng/grpc-web/go/grpcweb" + "go.uber.org/zap" "google.golang.org/grpc" "google.golang.org/grpc/codes" - "google.golang.org/grpc/grpclog" "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/durationpb" ) -func init() { - grpclog.SetLogger(log.New(os.Stdout, "server: ", log.LstdFlags)) -} - const ( // ts-proto generates code that automatically retries for a subset of gRPC // response codes. To avoid invoking this behaviour, default to returning a @@ -34,6 +29,11 @@ const ( defaultResponseMessage = "An unexpected error occurred" ) +const ( + getAudioTimeout = time.Minute * 5 + getAudioSegmentTimeout = time.Second * 10 +) + type ResponseError struct { err error s string @@ -63,10 +63,6 @@ type Options struct { S3Client media.S3Client } -const ( - getAudioTimeout = time.Minute * 5 -) - // mediaSetServiceController implements gRPC controller for MediaSetService type mediaSetServiceController struct { pbMediaSet.UnimplementedMediaSetServiceServer @@ -98,8 +94,10 @@ func (c *mediaSetServiceController) Get(ctx context.Context, request *pbMediaSet return &result, nil } -// GetAudio streams the progress report of GetAudio. +// GetAudio returns a stream of GetAudioProgress relating to the entire audio +// part of the MediaSet. func (c *mediaSetServiceController) GetAudio(request *pbMediaSet.GetAudioRequest, stream pbMediaSet.MediaSetService_GetAudioServer) error { + // TODO: reduce timeout when fetching from S3 ctx, cancel := context.WithTimeout(context.Background(), getAudioTimeout) defer cancel() @@ -139,15 +137,39 @@ func (c *mediaSetServiceController) GetAudio(request *pbMediaSet.GetAudioRequest return nil } +// GetAudioSegment returns a set of peaks for a segment of an audio part of a +// MediaSet. +func (c *mediaSetServiceController) GetAudioSegment(ctx context.Context, request *pbMediaSet.GetAudioSegmentRequest) (*pbMediaSet.GetAudioSegmentResponse, error) { + ctx, cancel := context.WithTimeout(ctx, getAudioSegmentTimeout) + defer cancel() + + id, err := uuid.Parse(request.GetId()) + if err != nil { + return nil, newResponseError(err) + } + + _, err = c.mediaSetService.GetAudioSegment(ctx, id, request.StartFrame, request.EndFrame, int(request.GetNumBins())) + if err != nil { + return nil, newResponseError(err) + } + + return nil, nil +} + func Start(options Options) error { - grpcServer := grpc.NewServer() + logger, _ := zap.NewDevelopment() + defer logger.Sync() - fetchMediaSetService := media.NewMediaSetService(options.Store, options.YoutubeClient, options.S3Client) + grpcServer := grpc.NewServer( + grpc.UnaryInterceptor(grpczap.UnaryServerInterceptor(logger)), + ) + fetchMediaSetService := media.NewMediaSetService(options.Store, options.YoutubeClient, options.S3Client, logger) pbMediaSet.RegisterMediaSetServiceServer(grpcServer, &mediaSetServiceController{mediaSetService: fetchMediaSetService}) // TODO: configure CORS grpcWebServer := grpcweb.WrapServer(grpcServer, grpcweb.WithOriginFunc(func(string) bool { return true })) + handler := func(w http.ResponseWriter, r *http.Request) { grpcWebServer.ServeHTTP(w, r) } diff --git a/frontend/package.json b/frontend/package.json index d6a8596..eec789e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -15,7 +15,6 @@ "react": "^17.0.2", "react-dom": "^17.0.2", "react-scripts": "4.0.3", - "ts-proto": "^1.83.3", "typescript": "^4.1.2", "web-vitals": "^1.0.1" }, @@ -50,6 +49,7 @@ "eslint-config-prettier": "^8.3.0", "eslint-plugin-react": "^7.25.1", "prettier": "2.4.0", - "rxjs": "^7.4.0" + "rxjs": "^7.4.0", + "ts-proto": "^1.85.0" } } diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 68306c0..a378b30 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1881,9 +1881,9 @@ integrity sha512-vFHy/ezP5qI0rFgJ7aQnjDXwAMrG0KqqIH7tQG5PPv3BWBayOPIQNBjVc/P6hhdZfMx51REc6tfDNXHUio893g== "@types/node@>=13.7.0": - version "16.11.6" - resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.6.tgz#6bef7a2a0ad684cf6e90fcfe31cecabd9ce0a3ae" - integrity sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w== + version "16.11.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.7.tgz#36820945061326978c42a01e56b61cd223dfdc42" + integrity sha512-QB5D2sqfSjCmTuWcBWyJ+/44bcjO7VbjSbOE0ucoVbAsSNQc4Lt6QkgkVXkTDwkL4z/beecZNDvVX15D4P8Jbw== "@types/node@^12.0.0": version "12.20.23" @@ -11196,10 +11196,10 @@ ts-proto-descriptors@^1.2.1: long "^4.0.0" protobufjs "^6.8.8" -ts-proto@^1.83.3: - version "1.83.3" - resolved "https://registry.yarnpkg.com/ts-proto/-/ts-proto-1.83.3.tgz#ada7483035ddc946aa686dad1049e4fe45ae1d0f" - integrity sha512-r6MKFjoc4Og2kB4cNJ/bddLebdIwhouG5plu0Rry1jJMEqp2GKA7AE4FrR/FnTCIGbNPYP4622lBqckZd7UHcQ== +ts-proto@^1.85.0: + version "1.85.0" + resolved "https://registry.yarnpkg.com/ts-proto/-/ts-proto-1.85.0.tgz#a46925423a4df576f2fcf483d0d4b1ff2530d0ea" + integrity sha512-wYDzMl8LVqV8K65oI463KSIkfAxd3ulzMqZD+5NYWawwKfkJVNfTXjK847NJEHt4UBxSLZ0+aBjuc++cFNAUvw== dependencies: "@types/object-hash" "^1.3.0" dataloader "^1.4.0" diff --git a/proto/media_set.proto b/proto/media_set.proto index 8f9205a..4ae2b32 100644 --- a/proto/media_set.proto +++ b/proto/media_set.proto @@ -22,8 +22,8 @@ message MediaSet { }; message GetAudioProgress { - float percent_completed = 2; repeated int32 peaks = 1; + float percent_completed = 2; } message GetRequest { @@ -35,7 +35,19 @@ message GetAudioRequest { int32 num_bins = 2; } +message GetAudioSegmentRequest { + string id = 1; + int32 num_bins = 2; + int64 start_frame = 3; + int64 end_frame = 4; +} + +message GetAudioSegmentResponse { + repeated int32 peaks = 1; +} + service MediaSetService { rpc Get(GetRequest) returns (MediaSet) {} rpc GetAudio(GetAudioRequest) returns (stream GetAudioProgress) {} + rpc GetAudioSegment(GetAudioSegmentRequest) returns (GetAudioSegmentResponse) {} }