Helper tool for stitching together livestream VOD segments and uploading them to YouTube!
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

first google API experiments

ari melody 1bba7ef0

+204
+1
.gitignore
··· 1 + config.toml
+31
go.mod
··· 1 + module arimelody.space/live-vod-uploader 2 + 3 + go 1.25.3 4 + 5 + require ( 6 + cloud.google.com/go/auth v0.17.0 // indirect 7 + cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect 8 + cloud.google.com/go/compute/metadata v0.9.0 // indirect 9 + github.com/felixge/httpsnoop v1.0.4 // indirect 10 + github.com/go-logr/logr v1.4.3 // indirect 11 + github.com/go-logr/stdr v1.2.2 // indirect 12 + github.com/google/s2a-go v0.1.9 // indirect 13 + github.com/google/uuid v1.6.0 // indirect 14 + github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect 15 + github.com/googleapis/gax-go/v2 v2.15.0 // indirect 16 + github.com/pelletier/go-toml/v2 v2.2.4 // indirect 17 + go.opentelemetry.io/auto/sdk v1.1.0 // indirect 18 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect 19 + go.opentelemetry.io/otel v1.37.0 // indirect 20 + go.opentelemetry.io/otel/metric v1.37.0 // indirect 21 + go.opentelemetry.io/otel/trace v1.37.0 // indirect 22 + golang.org/x/crypto v0.43.0 // indirect 23 + golang.org/x/net v0.46.0 // indirect 24 + golang.org/x/oauth2 v0.32.0 // indirect 25 + golang.org/x/sys v0.37.0 // indirect 26 + golang.org/x/text v0.30.0 // indirect 27 + google.golang.org/api v0.254.0 // indirect 28 + google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 // indirect 29 + google.golang.org/grpc v1.76.0 // indirect 30 + google.golang.org/protobuf v1.36.10 // indirect 31 + )
+51
go.sum
··· 1 + cloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4= 2 + cloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ= 3 + cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= 4 + cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= 5 + cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= 6 + cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= 7 + github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= 8 + github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 9 + github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 10 + github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= 11 + github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 12 + github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 13 + github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 14 + github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= 15 + github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= 16 + github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 17 + github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 18 + github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4= 19 + github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= 20 + github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo= 21 + github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc= 22 + github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= 23 + github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= 24 + go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= 25 + go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= 26 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= 27 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= 28 + go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= 29 + go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= 30 + go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= 31 + go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= 32 + go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= 33 + go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= 34 + golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= 35 + golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= 36 + golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= 37 + golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= 38 + golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY= 39 + golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= 40 + golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= 41 + golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 42 + golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= 43 + golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= 44 + google.golang.org/api v0.254.0 h1:jl3XrGj7lRjnlUvZAbAdhINTLbsg5dbjmR90+pTQvt4= 45 + google.golang.org/api v0.254.0/go.mod h1:5BkSURm3D9kAqjGvBNgf0EcbX6Rnrf6UArKkwBzAyqQ= 46 + google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 h1:M1rk8KBnUsBDg1oPGHNCxG4vc1f49epmTO7xscSajMk= 47 + google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= 48 + google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= 49 + google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= 50 + google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= 51 + google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
+121
main.go
··· 1 + package main 2 + 3 + import ( 4 + "context" 5 + "encoding/json" 6 + "fmt" 7 + "os" 8 + 9 + toml "github.com/pelletier/go-toml/v2" 10 + "google.golang.org/api/option" 11 + "google.golang.org/api/youtube/v3" 12 + ) 13 + 14 + type ( 15 + Config struct { 16 + Google GoogleConfig `toml:"google"` 17 + } 18 + 19 + GoogleConfig struct { 20 + ApiKey string `toml:"api_key"` 21 + ClientID string `toml:"client_id"` 22 + ClientSecret string `toml:"client_secret"` 23 + } 24 + ) 25 + 26 + var DEFAULT_TAGS = []string{ 27 + "ari melody", 28 + "ari melody LIVE", 29 + "livestream", 30 + "vtuber", 31 + "twitch", 32 + "gaming", 33 + "let's play", 34 + "full VOD", 35 + "VOD", 36 + "stream", 37 + "archive", 38 + } 39 + 40 + const ( 41 + CATEGORY_GAMING = "20" 42 + ) 43 + 44 + func main() { 45 + if len(os.Args) < 2 { 46 + fmt.Printf("usage: %s <video ID>\n", os.Args[0]) 47 + os.Exit(0) 48 + } 49 + 50 + // videoID := os.Args[1] 51 + 52 + cfgBytes, err := os.ReadFile("config.toml") 53 + if err != nil { 54 + fmt.Fprintf(os.Stderr, "fatal: failed to read config file: %s\n", err.Error()) 55 + os.Exit(1) 56 + } 57 + cfg := Config{} 58 + err = toml.Unmarshal(cfgBytes, &cfg) 59 + if err != nil { 60 + fmt.Fprintf(os.Stderr, "fatal: failed to parse config: %s\n", err.Error()) 61 + os.Exit(1) 62 + } 63 + 64 + ctx := context.Background() 65 + service, err := youtube.NewService( 66 + ctx, 67 + option.WithScopes(youtube.YoutubeUploadScope), 68 + option.WithAPIKey(cfg.Google.ApiKey), 69 + ) 70 + if err != nil { 71 + fmt.Fprintf(os.Stderr, "fatal: failed to create youtube service: %s\n", err.Error()) 72 + os.Exit(1) 73 + } 74 + 75 + videoService := youtube.NewVideosService(service) 76 + 77 + // get video by ID 78 + { 79 + // call := service.Videos.List([]string{ 80 + // "snippet", "contentDetails", "statistics", "status", 81 + // }).Id(videoID) 82 + // res, err := call.Do() 83 + // if err != nil { 84 + // fmt.Fprintf(os.Stderr, "fatal: failed to request videos list: %s\n", err.Error()) 85 + // os.Exit(1) 86 + // } 87 + 88 + // data, err := json.MarshalIndent(res, "", " ") 89 + // if err != nil { 90 + // fmt.Fprintf(os.Stderr, "fatal: failed to marshal json: %s\n", err.Error()) 91 + // os.Exit(1) 92 + // } 93 + 94 + // fmt.Println(string(data)) 95 + } 96 + 97 + call := videoService.Insert([]string{ 98 + "snippet", "status", 99 + }, &youtube.Video{ 100 + Snippet: &youtube.VideoSnippet{ 101 + Title: "Untitled Video", 102 + Description: "No description", 103 + Tags: DEFAULT_TAGS, 104 + CategoryId: CATEGORY_GAMING, // gaming 105 + }, 106 + }).NotifySubscribers(false) 107 + // TODO: call.Media() 108 + video, err := call.Do() 109 + if err != nil { 110 + fmt.Fprintf(os.Stderr, "fatal: failed to upload video: %s\n", err.Error()) 111 + os.Exit(1) 112 + } 113 + 114 + data, err := json.MarshalIndent(video, "", " ") 115 + if err != nil { 116 + fmt.Fprintf(os.Stderr, "fatal: failed to marshal video data json: %s\n", err.Error()) 117 + os.Exit(1) 118 + } 119 + 120 + fmt.Println(string(data)) 121 + }