vodular/youtube/youtube.go

194 lines
4.1 KiB
Go

package youtube
import (
"bytes"
"context"
"fmt"
"log"
"os"
"path"
"strings"
"text/template"
"time"
"arimelody.space/melody-vod-manager/scanner"
"arimelody.space/melody-vod-manager/templates"
"golang.org/x/oauth2"
"google.golang.org/api/option"
"google.golang.org/api/youtube/v3"
)
const (
YT_CATEGORY_GAMING = "20"
YT_CATEGORY_ENTERTAINMENT = "24"
)
type CategoryType int
const (
CATEGORY_GAME CategoryType = iota
CATEGORY_ENTERTAINMENT
)
type (
Category struct {
Name string
Type CategoryType
Url string
}
Video struct {
Title string
Category *Category
Part int
Date time.Time
Tags []string
Filename string
SizeBytes int64
}
)
func BuildVideo(metadata *scanner.Metadata) (*Video, error) {
var category *Category = nil
if metadata.Category != nil {
category = &Category{
Name: metadata.Category.Name,
Type: CATEGORY_ENTERTAINMENT,
Url: metadata.Category.Url,
}
var ok bool
category.Type, ok = videoCategoryStringTypes[metadata.Category.Type]
if !ok { category.Type = CATEGORY_ENTERTAINMENT }
}
return &Video{
Title: metadata.Title,
Category: category,
Part: metadata.Part,
Date: metadata.Date.AsTime(time.UTC),
Tags: metadata.Tags,
Filename: path.Join(
metadata.FootageDir,
fmt.Sprintf(
"%s-fullvod.mkv",
metadata.Date.String(),
)),
}, nil
}
type (
MetaCategory struct {
Name string
Type string
Url string
}
Metadata struct {
Title string
Date time.Time
Category *MetaCategory
Part int
}
)
var videoCategoryTypeStrings = map[CategoryType]string{
CATEGORY_GAME: "gaming",
CATEGORY_ENTERTAINMENT: "entertainment",
}
var videoCategoryStringTypes = map[string]CategoryType{
"gaming": CATEGORY_GAME,
"entertainment": CATEGORY_ENTERTAINMENT,
}
var videoYtCategory = map[CategoryType]string {
CATEGORY_GAME: YT_CATEGORY_GAMING,
CATEGORY_ENTERTAINMENT: YT_CATEGORY_ENTERTAINMENT,
}
func BuildTemplate(video *Video, tmpl *template.Template) (string, error) {
out := &bytes.Buffer{}
var category *MetaCategory
if video.Category != nil {
category = &MetaCategory{
Name: video.Category.Name,
Type: videoCategoryTypeStrings[video.Category.Type],
Url: video.Category.Url,
}
}
err := tmpl.Execute(out, Metadata{
Title: video.Title,
Date: video.Date,
Category: category,
Part: video.Part,
})
return strings.TrimSpace(out.String()), err
}
func UploadVideo(
ctx context.Context,
tokenSource oauth2.TokenSource,
video *Video,
templates *templates.TextTemplates,
) (*youtube.Video, error) {
title, err := BuildTemplate(video, templates.Title)
if err != nil { return nil, fmt.Errorf("failed to build title: %v", err) }
description, err := BuildTemplate(video, templates.Description)
if err != nil { return nil, fmt.Errorf("failed to build description: %v", err) }
service, err := youtube.NewService(
ctx,
option.WithScopes(youtube.YoutubeUploadScope),
option.WithTokenSource(tokenSource),
)
if err != nil {
log.Fatalf("Failed to create youtube service: %v\n", err)
return nil, err
}
videoService := youtube.NewVideosService(service)
categoryId := YT_CATEGORY_ENTERTAINMENT
if video.Category != nil {
if cid, ok := videoYtCategory[video.Category.Type]; ok {
categoryId = cid
}
}
call := videoService.Insert([]string{
"snippet", "status",
}, &youtube.Video{
Snippet: &youtube.VideoSnippet{
Title: title,
Description: description,
Tags: append(templates.DefaultTags, 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...")
call.ProgressUpdater(func(current, total int64) {
// for some reason, this only returns 0.
// instead, we pull the file size from the ffmpeg output directly.
if total == 0 { total = video.SizeBytes }
fmt.Printf("Uploading... (%.2f%%)\n", float64(current) / float64(total) * 100)
})
ytVideo, err := call.Do()
if err != nil {
log.Fatalf("Failed to upload video: %v\n", err)
return nil, err
}
return ytVideo, err
}