clipper/backend/server/gprc_handler.go

224 lines
6.2 KiB
Go

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
}