more customisation, more QoL improvements

an all-around good time!
This commit is contained in:
ari melody 2026-01-30 14:14:54 +00:00
parent 2954689784
commit 45db651388
Signed by: ari
GPG key ID: CF99829C92678188
9 changed files with 315 additions and 147 deletions

124
main.go
View file

@ -6,9 +6,12 @@ import (
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"path"
"strings"
"sync"
"time"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
@ -20,11 +23,11 @@ import (
yt "arimelody.space/live-vod-uploader/youtube"
)
const segmentExtension = "mkv"
//go:embed res/help.txt
var helpText string
const segmentExtension = "mkv"
func showHelp() {
execSplits := strings.Split(os.Args[0], "/")
execName := execSplits[len(execSplits) - 1]
@ -64,7 +67,7 @@ func main() {
case "-d":
fallthrough
case "-deleteAfter":
case "--deleteAfter":
deleteFullVod = true
case "-f":
@ -89,6 +92,11 @@ func main() {
os.Exit(1)
}
if cfg == nil {
err = config.GenerateConfig(config.CONFIG_FILENAME)
if err != nil {
log.Fatalf("Failed to generate config: %v", err)
os.Exit(1)
}
log.Printf(
"New config file created (%s). " +
"Please edit this file before running again!",
@ -97,6 +105,13 @@ func main() {
os.Exit(0)
}
// fetch default tags
yt.DefaultTags, err = yt.GetDefaultTags(path.Join("template", "tags.txt"))
if err != nil {
log.Fatalf("Failed to fetch default tags: %v", err)
os.Exit(1)
}
// initialising directory (--init)
if initDirectory {
err = initialiseDirectory(directory)
@ -104,7 +119,12 @@ func main() {
log.Fatalf("Failed to initialise directory: %v", err)
os.Exit(1)
}
log.Printf("Directory successfully initialised")
log.Printf(
"Directory successfully initialised. " +
"Be sure to update %s before uploading!",
scanner.METADATA_FILENAME,
)
os.Exit(0)
}
// read directory metadata
@ -131,6 +151,11 @@ func main() {
os.Exit(0)
}
// default footage directory
if len(metadata.FootageDir) == 0 {
metadata.FootageDir = directory
}
// scan for VOD segments
vodFiles, err := scanner.ScanSegments(metadata.FootageDir, segmentExtension)
if err != nil {
@ -175,13 +200,13 @@ func main() {
os.Exit(1)
}
fmt.Printf(
"\nTITLE: %s\nDESCRIPTION: %s",
"\nTITLE: %s\nDESCRIPTION: %s\n",
title, description,
)
}
// concatenate VOD segments into full VOD
err = vid.ConcatVideo(video, vodFiles, verbose)
video.SizeBytes, err = vid.ConcatVideo(video, vodFiles, verbose)
if err != nil {
log.Fatalf("Failed to concatenate VOD segments: %v", err)
os.Exit(1)
@ -189,10 +214,20 @@ func main() {
// youtube oauth flow
ctx := context.Background()
token, err := completeOAuth(&ctx, cfg)
if err != nil {
log.Fatalf("OAuth flow failed: %v", err)
os.Exit(1)
var token *oauth2.Token
if cfg.Token != nil {
token = cfg.Token
} else {
token, err = generateOAuthToken(&ctx, cfg)
if err != nil {
log.Fatalf("OAuth flow failed: %v", err)
os.Exit(1)
}
cfg.Token = token
err = config.WriteConfig(cfg, config.CONFIG_FILENAME)
if err != nil {
log.Fatalf("Failed to save OAuth token: %v", err)
}
}
// okay actually upload now!
@ -219,7 +254,7 @@ func main() {
// delete full VOD after upload, if requested
if deleteFullVod {
err = os.Remove(path.Join(directory, scanner.METADATA_FILENAME))
err = os.Remove(video.Filename)
if err != nil {
log.Fatalf("Failed to delete full VOD: %v", err)
}
@ -240,7 +275,7 @@ func initialiseDirectory(directory string) error {
_, err = os.Stat(path.Join(directory, scanner.METADATA_FILENAME))
if err == nil {
return fmt.Errorf("directory already initialised: %v", err)
return fmt.Errorf("directory already initialised: %s", directory)
}
err = scanner.WriteMetadata(directory, scanner.DefaultMetadata())
@ -248,34 +283,69 @@ func initialiseDirectory(directory string) error {
return err
}
func completeOAuth(ctx *context.Context, cfg *config.Config) (*oauth2.Token, error) {
func generateOAuthToken(ctx *context.Context, cfg *config.Config) (*oauth2.Token, error) {
oauth2Config := &oauth2.Config{
ClientID: cfg.Google.ClientID,
ClientSecret: cfg.Google.ClientSecret,
Endpoint: google.Endpoint,
Scopes: []string{ youtube.YoutubeScope },
RedirectURL: "http://localhost:8090",
RedirectURL: cfg.RedirectUri,
}
verifier := oauth2.GenerateVerifier()
url := oauth2Config.AuthCodeURL("state", oauth2.AccessTypeOffline, oauth2.S256ChallengeOption(verifier))
fmt.Printf("Sign in to YouTube: %s\n", url)
var token *oauth2.Token
wg := sync.WaitGroup{}
var server http.Server
server.Addr = cfg.Host
server.Handler = http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
if !r.URL.Query().Has("code") {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
// TODO: tidy up oauth flow with localhost webserver
var code string
fmt.Print("Enter OAuth2 code: ")
if _, err := fmt.Scan(&code); err != nil {
return nil, fmt.Errorf("failed to read code: %v", err)
}
code := r.URL.Query().Get("code")
token, err := oauth2Config.Exchange(*ctx, code, oauth2.VerifierOption(verifier))
if err != nil {
log.Fatalf("Could not exchange OAuth2 code: %v", err)
os.Exit(1)
t, err := oauth2Config.Exchange(*ctx, code, oauth2.VerifierOption(verifier))
if err != nil {
log.Fatalf("Could not exchange OAuth2 code: %v", err)
http.Error( w,
fmt.Sprintf("Could not exchange OAuth2 code: %v", err),
http.StatusBadRequest,
)
return
}
token = t
http.Error(
w,
"Authentication successful! You may now close this tab.",
http.StatusOK,
)
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
wg.Done()
server.Close()
},
)
url := oauth2Config.AuthCodeURL(
"state",
oauth2.AccessTypeOffline,
oauth2.S256ChallengeOption(verifier),
)
fmt.Printf("\nSign in to YouTube: %s\n\n", url)
wg.Add(1)
if err := server.ListenAndServe(); err != http.ErrServerClosed {
return nil, fmt.Errorf("http: %v", err)
}
wg.Wait()
// TODO: save this token; look into token refresh
log.Printf("Token expires on %s\n", token.Expiry.Format("02 Jan 2006"))
log.Printf("Token expires at: %s\n", token.Expiry.Format(time.DateTime))
return token, nil
}