package server //go:generate mockery --recursive --name MediaSetService --output ../generated/mocks import ( "context" "errors" "io" pbmediaset "git.netflux.io/rob/clipper/generated/pb/media_set" "git.netflux.io/rob/clipper/media" "github.com/google/uuid" "go.uber.org/zap" "google.golang.org/protobuf/types/known/durationpb" ) type MediaSetService interface { Get(context.Context, string) (*media.MediaSet, error) GetAudioSegment(context.Context, uuid.UUID, int64, int64, media.AudioFormat) (*media.AudioSegmentStream, error) GetPeaks(context.Context, uuid.UUID, int) (media.GetPeaksProgressReader, error) GetPeaksForSegment(context.Context, uuid.UUID, int64, int64, int) ([]int16, error) GetVideo(context.Context, uuid.UUID) (media.GetVideoProgressReader, error) GetVideoThumbnail(context.Context, uuid.UUID) (media.VideoThumbnail, error) } // mediaSetServiceController implements gRPC controller for MediaSetService type mediaSetServiceController struct { pbmediaset.UnimplementedMediaSetServiceServer mediaSetService MediaSetService logger *zap.SugaredLogger } // Get returns a pbMediaSet.MediaSet func (c *mediaSetServiceController) Get(ctx context.Context, request *pbmediaset.GetRequest) (*pbmediaset.MediaSet, error) { mediaSet, err := c.mediaSetService.Get(ctx, request.GetYoutubeId()) if err != nil { return nil, newResponseError(err) } result := pbmediaset.MediaSet{ Id: mediaSet.ID.String(), YoutubeId: mediaSet.YoutubeID, AudioChannels: int32(mediaSet.Audio.Channels), AudioFrames: mediaSet.Audio.Frames, AudioApproxFrames: mediaSet.Audio.ApproxFrames, AudioSampleRate: int32(mediaSet.Audio.SampleRate), AudioYoutubeItag: int32(mediaSet.Audio.YoutubeItag), AudioMimeType: mediaSet.Audio.MimeType, VideoDuration: durationpb.New(mediaSet.Video.Duration), VideoYoutubeItag: int32(mediaSet.Video.YoutubeItag), VideoMimeType: mediaSet.Video.MimeType, } return &result, nil } // GetPeaks returns a stream of GetPeaksProgress relating to the entire audio // part of the MediaSet. func (c *mediaSetServiceController) GetPeaks(request *pbmediaset.GetPeaksRequest, stream pbmediaset.MediaSetService_GetPeaksServer) error { // TODO: reduce timeout when fetching from S3 ctx, cancel := context.WithTimeout(context.Background(), getPeaksTimeout) defer cancel() id, err := uuid.Parse(request.GetId()) if err != nil { return newResponseError(err) } reader, err := c.mediaSetService.GetPeaks(ctx, id, int(request.GetNumBins())) if err != nil { return newResponseError(err) } for { progress, err := reader.Next() if err != nil && err != io.EOF { return newResponseError(err) } peaks := make([]int32, len(progress.Peaks)) for i, p := range progress.Peaks { peaks[i] = int32(p) } progressPb := pbmediaset.GetPeaksProgress{ PercentComplete: progress.PercentComplete, Url: progress.URL, Peaks: peaks, } stream.Send(&progressPb) if err == io.EOF { break } } return nil } // GetPeaksForSegment returns a set of peaks for a segment of an audio part of // a MediaSet. func (c *mediaSetServiceController) GetPeaksForSegment(ctx context.Context, request *pbmediaset.GetPeaksForSegmentRequest) (*pbmediaset.GetPeaksForSegmentResponse, error) { ctx, cancel := context.WithTimeout(ctx, getPeaksForSegmentTimeout) defer cancel() id, err := uuid.Parse(request.GetId()) if err != nil { return nil, newResponseError(err) } peaks, err := c.mediaSetService.GetPeaksForSegment(ctx, id, request.StartFrame, request.EndFrame, int(request.GetNumBins())) if err != nil { return nil, newResponseError(err) } peaks32 := make([]int32, len(peaks)) for i, p := range peaks { peaks32[i] = int32(p) } return &pbmediaset.GetPeaksForSegmentResponse{Peaks: peaks32}, nil } func (c *mediaSetServiceController) GetAudioSegment(request *pbmediaset.GetAudioSegmentRequest, outStream pbmediaset.MediaSetService_GetAudioSegmentServer) error { ctx, cancel := context.WithTimeout(context.Background(), getPeaksForSegmentTimeout) defer cancel() id, err := uuid.Parse(request.GetId()) if err != nil { return newResponseError(err) } var format media.AudioFormat switch request.Format { case pbmediaset.AudioFormat_MP3: format = media.AudioFormatMP3 case pbmediaset.AudioFormat_WAV: format = media.AudioFormatWAV default: return newResponseError(errors.New("unknown format")) } stream, err := c.mediaSetService.GetAudioSegment(ctx, id, request.StartFrame, request.EndFrame, format) if err != nil { return newResponseError(err) } for { progress, err := stream.Next(ctx) if err != nil && err != io.EOF { return newResponseError(err) } progressPb := pbmediaset.GetAudioSegmentProgress{ PercentComplete: progress.PercentComplete, AudioData: progress.Data, } outStream.Send(&progressPb) if err == io.EOF { break } } return nil } func (c *mediaSetServiceController) GetVideo(request *pbmediaset.GetVideoRequest, stream pbmediaset.MediaSetService_GetVideoServer) error { // TODO: reduce timeout when already fetched from Youtube ctx, cancel := context.WithTimeout(context.Background(), getVideoTimeout) defer cancel() id, err := uuid.Parse(request.GetId()) if err != nil { return newResponseError(err) } reader, err := c.mediaSetService.GetVideo(ctx, id) if err != nil { return newResponseError(err) } for { progress, err := reader.Next() if err != nil && err != io.EOF { return newResponseError(err) } progressPb := pbmediaset.GetVideoProgress{ PercentComplete: progress.PercentComplete, Url: progress.URL, } stream.Send(&progressPb) if err == io.EOF { break } } return nil } func (c *mediaSetServiceController) GetVideoThumbnail(ctx context.Context, request *pbmediaset.GetVideoThumbnailRequest) (*pbmediaset.GetVideoThumbnailResponse, error) { id, err := uuid.Parse(request.GetId()) if err != nil { return nil, newResponseError(err) } thumbnail, err := c.mediaSetService.GetVideoThumbnail(ctx, id) if err != nil { return nil, newResponseError(err) } response := pbmediaset.GetVideoThumbnailResponse{ Image: thumbnail.Data, Width: int32(thumbnail.Width), Height: int32(thumbnail.Height), } return &response, nil }