// Package youtube contains an uploader to push VoDs to a Youtube // channel package youtube import ( "context" "fmt" "path" "strings" "time" "github.com/sirupsen/logrus" "google.golang.org/api/option" "google.golang.org/api/youtube/v3" "git.luzifer.io/luzifer/publish-vod/pkg/uploader" vaultoauth2 "git.luzifer.io/luzifer/publish-vod/pkg/vault-oauth2" oauth4youtube "git.luzifer.io/luzifer/publish-vod/pkg/vault-oauth2/youtube" "github.com/Luzifer/go_helpers/v2/fieldcollection" ) type ( impl struct{} ) var ptrStringEmpty = func(v string) *string { return &v }("") // New creates a new Youtube Uploader func New() uploader.Uploader { return impl{} } func (impl) Prepare(ctx context.Context, opts uploader.Opts) error { _, ts, err := oauth4youtube.GetOAuth2Client(ctx, opts.Config.MustString("vaultKey", nil)) if err != nil { return fmt.Errorf("getting oauth credentials: %w", err) } t, err := ts.Token() if err != nil { return fmt.Errorf("getting token to store: %w", err) } if err := vaultoauth2.SaveTokenToVault(opts.Config.MustString("vaultKey", nil), t); err != nil { return fmt.Errorf("storing token to vault: %w", err) } return nil } func (impl) UploadFile(ctx context.Context, opts uploader.Opts) error { client, ts, err := oauth4youtube.GetOAuth2Client(ctx, opts.Config.MustString("vaultKey", nil)) if err != nil { return fmt.Errorf("getting oauth credentials: %w", err) } defer func() { t, err := ts.Token() if err != nil { logrus.WithError(err).Error("getting token to store back") } if err := vaultoauth2.SaveTokenToVault(opts.Config.MustString("vaultKey", nil), t); err != nil { logrus.WithError(err).Error("storing token back to vault") } }() service, err := youtube.NewService(ctx, option.WithHTTPClient(client)) if err != nil { return fmt.Errorf("creating Youtube client: %w", err) } upload := &youtube.Video{ Snippet: &youtube.VideoSnippet{ CategoryId: "20", // Gaming ChannelId: opts.Config.MustString("channel", nil), DefaultAudioLanguage: "de", DefaultLanguage: "de", Description: opts.Config.MustString("description", ptrStringEmpty), LiveBroadcastContent: "none", Title: strings.ReplaceAll(path.Base(opts.Filename), path.Ext(opts.Filename), ""), }, Status: &youtube.VideoStatus{ Embeddable: true, License: "youtube", MadeForKids: false, PrivacyStatus: "private", PublicStatsViewable: true, }, RecordingDetails: &youtube.VideoRecordingDetails{ RecordingDate: time.Now().Format(time.RFC3339), }, } progressReader := opts.ProgressBar.NewProxyReader(opts.Content) resp, err := service.Videos. Insert([]string{"snippet", "status", "recordingDetails"}, upload). Media(progressReader). Do() if err != nil { return fmt.Errorf("inserting video: %w", err) } opts.FinalMessage("Video uploaded: https://youtu.be/%s", resp.Id) return nil } func (impl) ValidateConfig(config *fieldcollection.FieldCollection) error { for _, key := range []string{"channel", "vaultKey"} { if v, err := config.String(key); err != nil || v == "" { return fmt.Errorf("key %q must be non-empty string", key) } } return nil }