Add types.go, small refactor and tidy
This commit is contained in:
parent
1552fc19a1
commit
b3bc63621a
|
@ -1,55 +0,0 @@
|
|||
package media
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const SizeOfInt16 = 2
|
||||
|
||||
type Audio struct {
|
||||
ContentLength int64
|
||||
Channels int
|
||||
// ApproxFrames is used during initial processing when a precise frame count
|
||||
// cannot be determined. Prefer Frames in all other cases.
|
||||
ApproxFrames int64
|
||||
Frames int64
|
||||
SampleRate int
|
||||
YoutubeItag int
|
||||
MimeType string
|
||||
}
|
||||
|
||||
type Video struct {
|
||||
ContentLength int64
|
||||
Duration time.Duration
|
||||
// not sure if this are needed any more?
|
||||
ThumbnailWidth int
|
||||
ThumbnailHeight int
|
||||
YoutubeItag int
|
||||
MimeType string
|
||||
}
|
||||
|
||||
// MediaSet represents the media and metadata associated with a single media
|
||||
// resource (for example, a YouTube video).
|
||||
type MediaSet struct {
|
||||
Audio Audio `json:"audio"`
|
||||
Video Video `json:"video"`
|
||||
ID uuid.UUID `json:"id"`
|
||||
YoutubeID string `json:"youtube_id"`
|
||||
|
||||
exists bool
|
||||
}
|
||||
|
||||
// New builds a new MediaSet with the given ID.
|
||||
func NewMediaSet(youtubeID string) *MediaSet {
|
||||
return &MediaSet{YoutubeID: youtubeID}
|
||||
}
|
||||
|
||||
// TODO: pass io.Readers/Writers instead of strings.
|
||||
func (m *MediaSet) RawAudioPath() string { return fmt.Sprintf("cache/%s.raw", m.YoutubeID) }
|
||||
func (m *MediaSet) EncodedAudioPath() string { return fmt.Sprintf("cache/%s.m4a", m.YoutubeID) }
|
||||
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) }
|
|
@ -15,7 +15,6 @@ import (
|
|||
"git.netflux.io/rob/clipper/config"
|
||||
"git.netflux.io/rob/clipper/generated/store"
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
signerv4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4"
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v4"
|
||||
|
@ -40,69 +39,6 @@ const (
|
|||
thumbnailHeight = 100 // "
|
||||
)
|
||||
|
||||
// progressReader is a reader that prints progress logs as it reads.
|
||||
type progressReader struct {
|
||||
io.Reader
|
||||
|
||||
label string
|
||||
total, exp int64
|
||||
logger *zap.SugaredLogger
|
||||
}
|
||||
|
||||
func newProgressReader(reader io.Reader, label string, exp int64, logger *zap.SugaredLogger) *progressReader {
|
||||
return &progressReader{
|
||||
Reader: reader,
|
||||
exp: exp,
|
||||
logger: logger.Named(fmt.Sprintf("ProgressReader %s", label)),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *progressReader) Read(p []byte) (int, error) {
|
||||
n, err := r.Reader.Read(p)
|
||||
r.total += int64(n)
|
||||
|
||||
r.logger.Debugf("Read %d of %d (%.02f%%) bytes from the provided reader", r.total, r.exp, (float32(r.total)/float32(r.exp))*100.0)
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Store wraps a database store.
|
||||
type Store interface {
|
||||
GetMediaSet(context.Context, uuid.UUID) (store.MediaSet, error)
|
||||
GetMediaSetByYoutubeID(context.Context, string) (store.MediaSet, error)
|
||||
CreateMediaSet(context.Context, store.CreateMediaSetParams) (store.MediaSet, error)
|
||||
SetRawAudioUploaded(context.Context, store.SetRawAudioUploadedParams) (store.MediaSet, error)
|
||||
SetEncodedAudioUploaded(context.Context, store.SetEncodedAudioUploadedParams) (store.MediaSet, error)
|
||||
SetVideoUploaded(context.Context, store.SetVideoUploadedParams) (store.MediaSet, error)
|
||||
SetVideoThumbnailUploaded(context.Context, store.SetVideoThumbnailUploadedParams) (store.MediaSet, error)
|
||||
}
|
||||
|
||||
// S3API provides an API to AWS S3.
|
||||
type S3API struct {
|
||||
S3Client
|
||||
S3PresignClient
|
||||
}
|
||||
|
||||
// S3Client wraps the AWS S3 service client.
|
||||
type S3Client interface {
|
||||
GetObject(context.Context, *s3.GetObjectInput, ...func(*s3.Options)) (*s3.GetObjectOutput, error)
|
||||
CreateMultipartUpload(context.Context, *s3.CreateMultipartUploadInput, ...func(*s3.Options)) (*s3.CreateMultipartUploadOutput, error)
|
||||
UploadPart(context.Context, *s3.UploadPartInput, ...func(*s3.Options)) (*s3.UploadPartOutput, error)
|
||||
AbortMultipartUpload(context.Context, *s3.AbortMultipartUploadInput, ...func(*s3.Options)) (*s3.AbortMultipartUploadOutput, error)
|
||||
CompleteMultipartUpload(context.Context, *s3.CompleteMultipartUploadInput, ...func(*s3.Options)) (*s3.CompleteMultipartUploadOutput, error)
|
||||
}
|
||||
|
||||
// S3PresignClient wraps the AWS S3 Presign client.
|
||||
type S3PresignClient interface {
|
||||
PresignGetObject(context.Context, *s3.GetObjectInput, ...func(*s3.PresignOptions)) (*signerv4.PresignedHTTPRequest, error)
|
||||
}
|
||||
|
||||
// YoutubeClient wraps the youtube.Client client.
|
||||
type YoutubeClient interface {
|
||||
GetVideoContext(context.Context, string) (*youtubev2.Video, error)
|
||||
GetStreamContext(context.Context, *youtubev2.Video, *youtubev2.Format) (io.ReadCloser, int64, error)
|
||||
}
|
||||
|
||||
// MediaSetService exposes logical flows handling MediaSets.
|
||||
type MediaSetService struct {
|
||||
store Store
|
||||
|
@ -123,7 +59,9 @@ func NewMediaSetService(store Store, youtubeClient YoutubeClient, s3API S3API, c
|
|||
}
|
||||
|
||||
// Get fetches the metadata for a given MediaSet source. If it does not exist
|
||||
// in the local DB, it will attempt to create it.
|
||||
// in the local DB, it will attempt to create it. After the resource has been
|
||||
// created, other endpoints (e.g. GetAudio) can be called to fetch media from
|
||||
// Youtube and store it in S3.
|
||||
func (s *MediaSetService) Get(ctx context.Context, youtubeID string) (*MediaSet, error) {
|
||||
var (
|
||||
mediaSet *MediaSet
|
||||
|
@ -132,13 +70,13 @@ func (s *MediaSetService) Get(ctx context.Context, youtubeID string) (*MediaSet,
|
|||
|
||||
mediaSet, err = s.findMediaSet(ctx, youtubeID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting existing media set: %v", err)
|
||||
return nil, fmt.Errorf("error finding existing media set: %v", err)
|
||||
}
|
||||
|
||||
if mediaSet == nil {
|
||||
mediaSet, err = s.createMediaSet(ctx, youtubeID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting new media set: %v", err)
|
||||
return nil, fmt.Errorf("error creating new media set: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -281,11 +219,11 @@ func (s *MediaSetService) GetVideo(ctx context.Context, id uuid.UUID) (GetVideoP
|
|||
return nil, fmt.Errorf("error getting media set: %v", err)
|
||||
}
|
||||
|
||||
// TODO: use mediaSet func to fetch s3Key
|
||||
s3Key := fmt.Sprintf("media_sets/%s/video.mp4", mediaSet.ID)
|
||||
|
||||
if mediaSet.VideoS3UploadedAt.Valid {
|
||||
input := s3.GetObjectInput{Bucket: aws.String(s.config.S3Bucket), Key: aws.String(s3Key)}
|
||||
input := s3.GetObjectInput{
|
||||
Bucket: aws.String(s.config.S3Bucket),
|
||||
Key: aws.String(mediaSet.VideoS3Key.String),
|
||||
}
|
||||
request, signErr := s.s3.PresignGetObject(ctx, &input, s3.WithPresignExpires(getVideoExpiresIn))
|
||||
if signErr != nil {
|
||||
return nil, fmt.Errorf("error generating presigned URL: %v", signErr)
|
||||
|
@ -309,6 +247,9 @@ func (s *MediaSetService) GetVideo(ctx context.Context, id uuid.UUID) (GetVideoP
|
|||
return nil, fmt.Errorf("error fetching stream: %v", err)
|
||||
}
|
||||
|
||||
// TODO: use mediaSet func to fetch s3Key
|
||||
s3Key := fmt.Sprintf("media_sets/%s/video.mp4", mediaSet.ID)
|
||||
|
||||
videoGetter := newVideoGetter(s.s3, s.store, s.logger)
|
||||
return videoGetter.GetVideo(
|
||||
ctx,
|
||||
|
@ -654,3 +595,29 @@ func (s *MediaSetService) getThumbnailFromYoutube(ctx context.Context, mediaSet
|
|||
|
||||
return VideoThumbnail{Width: int(thumbnail.Width), Height: int(thumbnail.Height), Data: imageData}, nil
|
||||
}
|
||||
|
||||
// progressReader is a reader that prints progress logs as it reads.
|
||||
type progressReader struct {
|
||||
io.Reader
|
||||
|
||||
label string
|
||||
total, exp int64
|
||||
logger *zap.SugaredLogger
|
||||
}
|
||||
|
||||
func newProgressReader(reader io.Reader, label string, exp int64, logger *zap.SugaredLogger) *progressReader {
|
||||
return &progressReader{
|
||||
Reader: reader,
|
||||
exp: exp,
|
||||
logger: logger.Named(fmt.Sprintf("ProgressReader %s", label)),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *progressReader) Read(p []byte) (int, error) {
|
||||
n, err := r.Reader.Read(p)
|
||||
r.total += int64(n)
|
||||
|
||||
r.logger.Debugf("Read %d of %d (%.02f%%) bytes from the provided reader", r.total, r.exp, (float32(r.total)/float32(r.exp))*100.0)
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
package media
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"git.netflux.io/rob/clipper/generated/store"
|
||||
signerv4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4"
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||
"github.com/google/uuid"
|
||||
youtubev2 "github.com/kkdai/youtube/v2"
|
||||
)
|
||||
|
||||
// An int16 has two bytes.
|
||||
const SizeOfInt16 = 2
|
||||
|
||||
// MediaSet represents the media and metadata associated with a single media
|
||||
// resource (for example, a YouTube video).
|
||||
type MediaSet struct {
|
||||
Audio Audio
|
||||
Video Video
|
||||
ID uuid.UUID
|
||||
YoutubeID string
|
||||
}
|
||||
|
||||
// Audio contains the metadata for the audio part of the media set.
|
||||
type Audio struct {
|
||||
ContentLength int64
|
||||
Channels int
|
||||
// ApproxFrames is used during initial processing when a precise frame count
|
||||
// cannot be determined. Prefer Frames in all other cases.
|
||||
ApproxFrames int64
|
||||
Frames int64
|
||||
SampleRate int
|
||||
YoutubeItag int
|
||||
MimeType string
|
||||
}
|
||||
|
||||
// Video contains the metadata for the video part of the media set.
|
||||
type Video struct {
|
||||
ContentLength int64
|
||||
Duration time.Duration
|
||||
// not sure if this are needed any more?
|
||||
ThumbnailWidth int
|
||||
ThumbnailHeight int
|
||||
YoutubeItag int
|
||||
MimeType string
|
||||
}
|
||||
|
||||
// Store wraps a database store.
|
||||
type Store interface {
|
||||
GetMediaSet(context.Context, uuid.UUID) (store.MediaSet, error)
|
||||
GetMediaSetByYoutubeID(context.Context, string) (store.MediaSet, error)
|
||||
CreateMediaSet(context.Context, store.CreateMediaSetParams) (store.MediaSet, error)
|
||||
SetRawAudioUploaded(context.Context, store.SetRawAudioUploadedParams) (store.MediaSet, error)
|
||||
SetEncodedAudioUploaded(context.Context, store.SetEncodedAudioUploadedParams) (store.MediaSet, error)
|
||||
SetVideoUploaded(context.Context, store.SetVideoUploadedParams) (store.MediaSet, error)
|
||||
SetVideoThumbnailUploaded(context.Context, store.SetVideoThumbnailUploadedParams) (store.MediaSet, error)
|
||||
}
|
||||
|
||||
// S3API provides an API to AWS S3.
|
||||
type S3API struct {
|
||||
S3Client
|
||||
S3PresignClient
|
||||
}
|
||||
|
||||
// S3Client wraps the AWS S3 service client.
|
||||
type S3Client interface {
|
||||
GetObject(context.Context, *s3.GetObjectInput, ...func(*s3.Options)) (*s3.GetObjectOutput, error)
|
||||
CreateMultipartUpload(context.Context, *s3.CreateMultipartUploadInput, ...func(*s3.Options)) (*s3.CreateMultipartUploadOutput, error)
|
||||
UploadPart(context.Context, *s3.UploadPartInput, ...func(*s3.Options)) (*s3.UploadPartOutput, error)
|
||||
AbortMultipartUpload(context.Context, *s3.AbortMultipartUploadInput, ...func(*s3.Options)) (*s3.AbortMultipartUploadOutput, error)
|
||||
CompleteMultipartUpload(context.Context, *s3.CompleteMultipartUploadInput, ...func(*s3.Options)) (*s3.CompleteMultipartUploadOutput, error)
|
||||
}
|
||||
|
||||
// S3PresignClient wraps the AWS S3 Presign client.
|
||||
type S3PresignClient interface {
|
||||
PresignGetObject(context.Context, *s3.GetObjectInput, ...func(*s3.PresignOptions)) (*signerv4.PresignedHTTPRequest, error)
|
||||
}
|
||||
|
||||
// YoutubeClient wraps the youtube.Client client.
|
||||
type YoutubeClient interface {
|
||||
GetVideoContext(context.Context, string) (*youtubev2.Video, error)
|
||||
GetStreamContext(context.Context, *youtubev2.Video, *youtubev2.Format) (io.ReadCloser, int64, error)
|
||||
}
|
Loading…
Reference in New Issue