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

check if fullvod exists; store config in UserConfigDir

+58 -13
+1 -1
config/config.go
··· 33 33 }, 34 34 } 35 35 36 - const CONFIG_FILENAME = "config.toml" 36 + var CONFIG_FILENAME string = "config.toml" 37 37 38 38 func ReadConfig(filename string) (*Config, error) { 39 39 cfgBytes, err := os.ReadFile(filename)
+35 -12
main.go
··· 28 28 const segmentExtension = "mkv" 29 29 30 30 func showHelp() { 31 - execSplits := strings.Split(os.Args[0], "/") 32 - execName := execSplits[len(execSplits) - 1] 33 - fmt.Printf(helpText, execName) 31 + fmt.Println(helpText) 32 + os.Exit(0) 34 33 } 35 34 36 35 func main() { 37 36 // config 37 + userConfigDir, err := os.UserConfigDir() 38 + if err != nil { 39 + log.Fatalf("Could not determine user configuration directory: %v", err) 40 + os.Exit(1) 41 + } 42 + config.CONFIG_FILENAME = path.Join(userConfigDir, "vodular", "config.toml") 38 43 cfg, err := config.ReadConfig(config.CONFIG_FILENAME) 39 44 if err != nil { 40 45 log.Fatalf("Failed to read config: %v", err) 41 46 os.Exit(1) 42 47 } 43 48 if cfg == nil { 49 + err = os.MkdirAll(path.Dir(config.CONFIG_FILENAME), 0750) 50 + if err != nil { 51 + log.Fatalf("Failed to create config directory: %v", err) 52 + os.Exit(1) 53 + } 44 54 err = config.GenerateConfig(config.CONFIG_FILENAME) 45 55 if err != nil { 46 56 log.Fatalf("Failed to generate config: %v", err) ··· 57 67 // arguments 58 68 if len(os.Args) < 2 || os.Args[1] == "--help" || os.Args[1] == "-h" { 59 69 showHelp() 60 - os.Exit(0) 61 70 } 62 71 63 72 var verbose bool = false ··· 76 85 fallthrough 77 86 case "--help": 78 87 showHelp() 79 - os.Exit(0) 80 88 81 89 case "-v": 82 90 fallthrough ··· 228 236 os.Exit(1) 229 237 } 230 238 fmt.Printf( 231 - "\n================================\n" + 232 - "TITLE:\n%s\n\n" + 233 - "DESCRIPTION:\n%s\n" + 239 + "\n================================\n\n" + 240 + "< TITLE >\n%s\n\n" + 241 + "< DESCRIPTION >\n%s\n" + 234 242 "\n================================\n", 235 243 title, description, 236 244 ) 237 245 } 238 246 239 247 // concatenate VOD segments into full VOD 240 - video.SizeBytes, err = vid.ConcatVideo(video, vodFiles, verbose) 241 - if err != nil { 242 - log.Fatalf("Failed to concatenate VOD segments: %v", err) 243 - os.Exit(1) 248 + fullVodExists := func () bool { 249 + // check if full VOD already exists with expected duration 250 + if fullVodProbe, err := scanner.ProbeSegment(video.Filename); err != nil { 251 + var totalLength float64 = 0 252 + for _, filename := range vodFiles { 253 + probe, err := scanner.ProbeSegment(filename) 254 + if err != nil { continue } 255 + totalLength += probe.Format.Duration 256 + } 257 + return fullVodProbe.Format.Duration == totalLength 258 + } 259 + return false 260 + }() 261 + if !fullVodExists { 262 + video.SizeBytes, err = vid.ConcatVideo(video, vodFiles, verbose) 263 + if err != nil { 264 + log.Fatalf("Failed to concatenate VOD segments: %v", err) 265 + os.Exit(1) 266 + } 244 267 } 245 268 246 269 // youtube oauth flow
+22
scanner/scanner.go
··· 1 1 package scanner 2 2 3 3 import ( 4 + "encoding/json" 4 5 "os" 5 6 "path" 6 7 "strings" 7 8 "time" 8 9 9 10 "github.com/pelletier/go-toml/v2" 11 + ffmpeg_go "github.com/u2takey/ffmpeg-go" 10 12 ) 11 13 12 14 type ( ··· 24 26 FootageDir string `toml:"footage_dir"` 25 27 Uploaded bool `toml:"uploaded"` 26 28 Category *Category `toml:"category" comment:"(Optional) Category details, for additional credits."` 29 + } 30 + 31 + ffprobeFormat struct { 32 + Duration float64 `json:"duration"` 33 + Size int64 `json:"size"` 34 + } 35 + 36 + ffprobeOutput struct { 37 + Format ffprobeFormat `json:"format"` 27 38 } 28 39 ) 29 40 ··· 45 56 } 46 57 47 58 return files, nil 59 + } 60 + 61 + func ProbeSegment(filename string) (*ffprobeOutput, error) { 62 + out, err := ffmpeg_go.Probe(filename) 63 + if err != nil { return nil, err } 64 + 65 + probe := ffprobeOutput{} 66 + err = json.Unmarshal([]byte(out), probe) 67 + if err != nil { return nil, err } 68 + 69 + return &probe, nil 48 70 } 49 71 50 72 func ReadMetadata(directory string) (*Metadata, error) {