vodular/main.go

241 lines
5.5 KiB
Go
Raw Normal View History

2025-11-05 21:22:15 +00:00
package main
import (
"context"
"encoding/json"
"fmt"
2026-01-28 10:48:14 +00:00
"log"
2025-11-05 21:22:15 +00:00
"os"
2026-01-28 10:48:14 +00:00
"path"
"strings"
2025-11-05 21:22:15 +00:00
toml "github.com/pelletier/go-toml/v2"
2026-01-28 10:48:14 +00:00
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
2025-11-05 21:22:15 +00:00
"google.golang.org/api/youtube/v3"
2026-01-28 10:48:14 +00:00
"arimelody.space/live-vod-uploader/scanner"
vid "arimelody.space/live-vod-uploader/video"
yt "arimelody.space/live-vod-uploader/youtube"
2025-11-05 21:22:15 +00:00
)
type (
Config struct {
Google GoogleConfig `toml:"google"`
}
GoogleConfig struct {
ApiKey string `toml:"api_key"`
ClientID string `toml:"client_id"`
ClientSecret string `toml:"client_secret"`
}
)
2026-01-28 10:48:14 +00:00
const CONFIG_FILENAME = "config.toml"
func showHelp() {
execSplits := strings.Split(os.Args[0], "/")
execName := execSplits[len(execSplits) - 1]
fmt.Printf(
"usage: %s [options] [directory]\n\n" +
"options:\n" +
"\t-h, --help: Show this help message.\n" +
"\t-v, --verbose: Show verbose logging output.\n" +
"\t--init: Initialise `directory` as a VOD directory.\n",
execName)
}
2025-11-05 21:22:15 +00:00
func main() {
2026-01-28 10:48:14 +00:00
if len(os.Args) < 2 || os.Args[1] == "--help" || os.Args[1] == "-h" {
showHelp()
2025-11-05 21:22:15 +00:00
os.Exit(0)
}
2026-01-28 10:48:14 +00:00
var directory string
var initDirectory bool = false
var verbose bool = false
for i, arg := range os.Args {
if i == 0 { continue }
if strings.HasPrefix(arg, "-") {
switch arg {
case "-h":
fallthrough
case "--help":
showHelp()
os.Exit(0)
case "--init":
initDirectory = true
case "-v":
fallthrough
case "--verbose":
verbose = true
default:
fmt.Fprintf(os.Stderr, "Unknown option `%s`\n", arg)
os.Exit(1)
}
} else {
directory = arg
}
}
2025-11-05 21:22:15 +00:00
2026-01-28 10:48:14 +00:00
cfg := Config{}
cfgBytes, err := os.ReadFile(CONFIG_FILENAME)
2025-11-05 21:22:15 +00:00
if err != nil {
2026-01-28 10:48:14 +00:00
log.Fatalf("Failed to read config file: %v", err)
tomlBytes, err := toml.Marshal(&cfg)
if err != nil {
log.Fatalf("Failed to marshal json: %v", err)
os.Exit(1)
}
err = os.WriteFile(CONFIG_FILENAME, tomlBytes, 0o644)
if err != nil {
log.Fatalf("Failed to write config file: %v", err)
os.Exit(1)
}
log.Printf("New config file created. Please edit this before running again!")
os.Exit(0)
2025-11-05 21:22:15 +00:00
}
err = toml.Unmarshal(cfgBytes, &cfg)
if err != nil {
2026-01-28 10:48:14 +00:00
log.Fatalf("Failed to parse config: %v", err)
2025-11-05 21:22:15 +00:00
os.Exit(1)
}
2026-01-28 10:48:14 +00:00
if initDirectory {
dirInfo, err := os.Stat(directory)
if err != nil {
if err == os.ErrNotExist {
log.Fatalf("No such directory: %s", directory)
os.Exit(1)
}
log.Fatalf("Failed to open directory: %v", err)
os.Exit(1)
}
if !dirInfo.IsDir() {
log.Fatalf("Not a directory: %s", directory)
os.Exit(1)
}
dirEntry, err := os.ReadDir(directory)
if err != nil {
log.Fatalf("Failed to open directory: %v", err)
os.Exit(1)
}
for _, entry := range dirEntry {
if !entry.IsDir() && entry.Name() == "metadata.toml" {
log.Printf("Directory `%s` already initialised", directory)
os.Exit(0)
return
}
defaultMetadata := scanner.DefaultMetadata()
metadataStr, _ := toml.Marshal(defaultMetadata)
err = os.WriteFile(path.Join(directory, "metadata.toml"), metadataStr, 0o644)
if err != nil {
log.Fatalf("Failed to write to file: %v", err)
os.Exit(1)
}
log.Printf("Directory successfully initialised")
os.Exit(0)
}
}
metadata, err := scanner.FetchMetadata(directory)
2025-11-05 21:22:15 +00:00
if err != nil {
2026-01-28 10:48:14 +00:00
log.Fatalf("Failed to fetch VOD metadata: %v", err)
os.Exit(1)
}
if metadata == nil {
log.Fatal("Directory contained no metadata. Use `--init` to initialise this directory.")
os.Exit(1)
}
vodFiles, err := scanner.FetchVideos(metadata.FootageDir)
if err != nil {
log.Fatalf("Failed to fetch VOD filenames: %v", err)
os.Exit(1)
}
if len(vodFiles) == 0 {
log.Fatal("Directory contained no VOD files (expecting .mkv)")
2025-11-05 21:22:15 +00:00
os.Exit(1)
}
2026-01-28 10:48:14 +00:00
if verbose {
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", "\t")
fmt.Printf("Directory metadata: ")
enc.Encode(metadata)
fmt.Printf("\nVOD files available: ")
enc.Encode(vodFiles)
}
video, err := yt.BuildVideo(metadata)
2025-11-05 21:22:15 +00:00
if err != nil {
2026-01-28 10:48:14 +00:00
log.Fatalf("Failed to build video template: %v", err)
2025-11-05 21:22:15 +00:00
os.Exit(1)
}
2026-01-28 10:48:14 +00:00
if verbose {
enc := json.NewEncoder(os.Stdout)
fmt.Printf("\nVideo template: ")
enc.Encode(video)
title, err := yt.BuildTitle(video)
if err != nil {
log.Fatalf("Failed to build video title: %v", err)
os.Exit(1)
}
description, err := yt.BuildDescription(video)
if err != nil {
log.Fatalf("Failed to build video description: %v", err)
os.Exit(1)
}
fmt.Printf(
"\nTITLE: %s\nDESCRIPTION: %s",
title, description,
)
}
err = vid.ConcatVideo(video, vodFiles)
if err != nil {
log.Fatalf("Failed to concatenate VOD files: %v", err)
os.Exit(1)
}
// okay actual youtube stuff now
// TODO: tidy up oauth flow with localhost webserver
ctx := context.Background()
config := &oauth2.Config{
ClientID: cfg.Google.ClientID,
ClientSecret: cfg.Google.ClientSecret,
Endpoint: google.Endpoint,
Scopes: []string{ youtube.YoutubeScope },
RedirectURL: "http://localhost:8090",
}
verifier := oauth2.GenerateVerifier()
url := config.AuthCodeURL("state", oauth2.AccessTypeOffline, oauth2.S256ChallengeOption(verifier))
log.Printf("Visit URL to initiate OAuth2: %s", url)
var code string
fmt.Print("Enter OAuth2 code: ")
if _, err := fmt.Scan(&code); err != nil {
log.Fatalf("Failed to read oauth2 code: %v", err)
}
2025-11-05 21:22:15 +00:00
2026-01-28 10:48:14 +00:00
token, err := config.Exchange(ctx, code, oauth2.VerifierOption(verifier))
log.Printf("Token expires on %s\n", token.Expiry.Format("02 Jan 2006"))
2025-11-05 21:22:15 +00:00
if err != nil {
2026-01-28 10:48:14 +00:00
log.Fatalf("Could not exchange OAuth2 code: %v", err)
2025-11-05 21:22:15 +00:00
os.Exit(1)
}
2026-01-28 10:48:14 +00:00
yt.UploadVideo(ctx, token, video)
2025-11-05 21:22:15 +00:00
}