package youtube import ( "bytes" "context" "fmt" "log" "os" "path" "strings" "text/template" "time" "arimelody.space/live-vod-uploader/scanner" "golang.org/x/oauth2" "google.golang.org/api/option" "google.golang.org/api/youtube/v3" ) var DEFAULT_TAGS = []string{ "ari melody", "ari melody LIVE", "livestream", "vtuber", "twitch", "gaming", "let's play", "full VOD", "VOD", "stream", "archive", } const ( CATEGORY_GAMING = "20" CATEGORY_ENTERTAINMENT = "24" ) type TitleType int const ( TITLE_GAME TitleType = iota TITLE_OTHER ) type ( Title struct { Name string Type TitleType Url string } Video struct { Title *Title Part int Date time.Time Tags []string Filename string } ) func BuildVideo(metadata *scanner.Metadata) (*Video, error) { var titleType TitleType switch metadata.Category.Type { case "gaming": titleType = TITLE_GAME default: titleType = TITLE_OTHER } videoDate, err := time.Parse("2006-01-02", metadata.Date) if err != nil { return nil, fmt.Errorf("failed to parse date from metadata: %v", err) } return &Video{ Title: &Title{ Name: metadata.Category.Name, Type: titleType, Url: metadata.Category.Url, }, Part: metadata.Part, Date: videoDate, Tags: DEFAULT_TAGS, Filename: path.Join( metadata.FootageDir, fmt.Sprintf( "%s-fullvod.mkv", videoDate.Format("2006-01-02"), )), }, nil } type ( MetaTitle struct { Name string Type string Url string } Metadata struct { Date string Title *MetaTitle Part int } ) var titleTemplate *template.Template = template.Must( template.ParseFiles("template/title.txt"), ) func BuildTitle(video *Video) (string, error) { var titleType string switch video.Title.Type { case TITLE_GAME: titleType = "game" case TITLE_OTHER: fallthrough default: titleType = "other" } out := &bytes.Buffer{} titleTemplate.Execute(out, Metadata{ Date: strings.ToLower(video.Date.Format("02 Jan 2006")), Title: &MetaTitle{ Name: video.Title.Name, Type: titleType, Url: video.Title.Url, }, Part: video.Part, }) return strings.TrimSpace(out.String()), nil } var descriptionTemplate *template.Template = template.Must( template.ParseFiles("template/description.txt"), ) func BuildDescription(video *Video) (string, error) { var titleType string switch video.Title.Type { case TITLE_GAME: titleType = "game" case TITLE_OTHER: fallthrough default: titleType = "other" } out := &bytes.Buffer{} descriptionTemplate.Execute(out, Metadata{ Date: strings.ToLower(video.Date.Format("02 Jan 2006")), Title: &MetaTitle{ Name: video.Title.Name, Type: titleType, Url: video.Title.Url, }, Part: video.Part, }) return out.String(), nil } func UploadVideo(ctx context.Context, token *oauth2.Token, video *Video) (*youtube.Video, error) { title, err := BuildTitle(video) if err != nil { return nil, fmt.Errorf("failed to build title: %v", err) } description, err := BuildDescription(video) if err != nil { return nil, fmt.Errorf("failed to build description: %v", err) } service, err := youtube.NewService( ctx, option.WithScopes(youtube.YoutubeUploadScope), option.WithTokenSource(oauth2.StaticTokenSource(token)), ) if err != nil { log.Fatalf("Failed to create youtube service: %v\n", err) return nil, err } videoService := youtube.NewVideosService(service) var categoryId string switch video.Title.Type { case TITLE_GAME: categoryId = CATEGORY_GAMING default: categoryId = CATEGORY_ENTERTAINMENT } call := videoService.Insert([]string{ "snippet", "status", }, &youtube.Video{ Snippet: &youtube.VideoSnippet{ Title: title, Description: description, Tags: append(DEFAULT_TAGS, video.Tags...), CategoryId: categoryId, // gaming }, Status: &youtube.VideoStatus{ PrivacyStatus: "private", }, }).NotifySubscribers(false) file, err := os.Open(video.Filename) if err != nil { log.Fatalf("Failed to open file: %v\n", err) return nil, err } call.Media(file) log.Println("Uploading video...") ytVideo, err := call.Do() if err != nil { log.Fatalf("Failed to upload video: %v\n", err) return nil, err } return ytVideo, err }