package main import ( "context" "fmt" "net/http" "os" "path" "strings" "time" "github.com/cheggaaa/pb/v3" "github.com/pkg/errors" "github.com/sirupsen/logrus" "golang.org/x/oauth2" "golang.org/x/oauth2/google" "google.golang.org/api/option" "google.golang.org/api/youtube/v3" "github.com/Luzifer/rconfig/v2" ) const defaultDescription = `Twitch: https://twitch.tv/luziferus Chapters: 0:00:00 Game1` var ( cfg = struct { Channel string `flag:"channel" default:"UCjsRmaAQ0IHR2CNEBqfNOSQ" description:"ID of the channel to assign the video to"` Code string `flag:"code" default:"" description:"Auth-Code to create the token from"` LogLevel string `flag:"log-level" default:"info" description:"Log level (debug, info, warn, error, fatal)"` VaultKey string `flag:"vault-key" default:"secret/vod2yt" description:"Where to find / store oauth data"` VersionAndExit bool `flag:"version" default:"false" description:"Prints current version and exits"` }{} version = "dev" ) func initApp() error { rconfig.AutoEnv(true) if err := rconfig.ParseAndValidate(&cfg); err != nil { return errors.Wrap(err, "parsing cli options") } l, err := logrus.ParseLevel(cfg.LogLevel) if err != nil { return errors.Wrap(err, "parsing log-level") } logrus.SetLevel(l) return nil } func main() { var err error if err = initApp(); err != nil { logrus.WithError(err).Fatal("initializing app") } if cfg.VersionAndExit { logrus.WithField("version", version).Info("vod2yt") os.Exit(0) } if len(rconfig.Args()) < 2 { //nolint:gomnd logrus.Fatal("Usage: vod2yt ") } var ( ctx = context.Background() fileName = rconfig.Args()[1] ) client, ts, err := oauth(ctx) if err != nil { logrus.WithError(err).Fatal("getting oauth credentials") } defer func() { t, err := ts.Token() if err != nil { logrus.WithError(err).Fatal("getting token to store back") } if err := saveTokenToVault(t); err != nil { logrus.WithError(err).Fatal("storing token back to vault") } }() service, err := youtube.NewService(ctx, option.WithHTTPClient(client)) if err != nil { logrus.WithError(err).Fatal("creating YouTube client") } upload := &youtube.Video{ Snippet: &youtube.VideoSnippet{ CategoryId: "20", // Gaming ChannelId: cfg.Channel, DefaultAudioLanguage: "de", DefaultLanguage: "de", Description: defaultDescription, LiveBroadcastContent: "none", Title: strings.ReplaceAll(path.Base(fileName), path.Ext(fileName), ""), }, Status: &youtube.VideoStatus{ Embeddable: true, License: "youtube", MadeForKids: false, PrivacyStatus: "private", PublicStatsViewable: true, }, RecordingDetails: &youtube.VideoRecordingDetails{ RecordingDate: time.Now().Format(time.RFC3339), }, } mediaFileStat, err := os.Stat(fileName) if err != nil { logrus.WithError(err).Fatal("getting media file stat") } mediaFile, err := os.Open(fileName) //#nosec:G304 // Intended to upload arbitrary file if err != nil { logrus.WithError(err).Fatal("opening media file") } defer mediaFile.Close() //nolint:errcheck // File will be closed by program exit bar := pb.Full.Start64(mediaFileStat.Size()) progressReader := bar.NewProxyReader(mediaFile) resp, err := service.Videos. Insert([]string{"snippet", "status", "recordingDetails"}, upload). Media(progressReader). Do() if err != nil { logrus.WithError(err).Fatal("inserting video") } bar.Finish() logrus.WithField("video-id", resp.Id).Info("video uploaded") } func oauth(ctx context.Context) (client *http.Client, ts oauth2.TokenSource, err error) { clientID, clientSecret, err := loadClientDetailsFromVault() if err != nil { return nil, nil, fmt.Errorf("loading client details: %w", err) } c := &oauth2.Config{ ClientID: clientID, ClientSecret: clientSecret, Endpoint: google.Endpoint, RedirectURL: "http://127.0.0.1:8000", Scopes: []string{youtube.YoutubeUploadScope}, } t, err := loadTokenFromVault() switch { case err == nil: // We've successfully loaded a token we can use below case cfg.Code != "": // We got an auth-code to exchange for a token t, err = c.Exchange(ctx, cfg.Code) if err != nil { return nil, nil, fmt.Errorf("exchanging token: %w", err) } if err = saveTokenToVault(t); err != nil { return nil, nil, fmt.Errorf("storing initial token: %w", err) } default: logrus.WithError(err).Debug("token load error") return nil, nil, fmt.Errorf("no token / code available, auth using following URL and restart with --code: %s", c.AuthCodeURL("offline", oauth2.AccessTypeOffline)) } ts = c.TokenSource(ctx, t) return oauth2.NewClient(ctx, ts), ts, nil }