Helper tool for stitching together livestream VOD segments and uploading them to YouTube!
at main 131 lines 2.9 kB view raw
1package scanner 2 3import ( 4 "encoding/json" 5 "os" 6 "path" 7 "strconv" 8 "strings" 9 "time" 10 11 "github.com/pelletier/go-toml/v2" 12 ffmpeg_go "github.com/u2takey/ffmpeg-go" 13) 14 15type ( 16 Category struct { 17 Name string `toml:"name"` 18 Type string `toml:"type" comment:"Valid types: gaming, other (default: other)"` 19 Url string `toml:"url"` 20 } 21 22 Metadata struct { 23 Title string `toml:"title"` 24 Part int `toml:"part"` 25 Date string `toml:"date"` 26 Tags []string `toml:"tags"` 27 FootageDir string `toml:"footage_dir"` 28 Uploaded bool `toml:"uploaded"` 29 Category *Category `toml:"category" comment:"(Optional) Category details, for additional credits."` 30 } 31 32 FFprobeFormat struct { 33 Duration float64 `json:"duration"` 34 Size int64 `json:"size"` 35 } 36 37 FFprobeOutput struct { 38 Format FFprobeFormat `json:"format"` 39 } 40) 41 42const METADATA_FILENAME = "metadata.toml" 43 44func ScanSegments(directory string, extension string) ([]string, error) { 45 entries, err := os.ReadDir(directory) 46 if err != nil { 47 return nil, err 48 } 49 50 files := []string{} 51 52 for _, item := range entries { 53 if item.IsDir() { continue } 54 if strings.HasPrefix(item.Name(), ".") { continue } 55 if !strings.HasSuffix(item.Name(), "." + extension) { continue } 56 if strings.HasSuffix(item.Name(), "-fullvod." + extension) { continue } 57 files = append(files, item.Name()) 58 } 59 60 return files, nil 61} 62 63func ProbeSegment(filename string) (*FFprobeOutput, error) { 64 out, err := ffmpeg_go.Probe(filename) 65 if err != nil { return nil, err } 66 67 type ( 68 RawFFprobeFormat struct { 69 // these being strings upsets me immensely 70 Duration string `json:"duration"` 71 Size string `json:"size"` 72 } 73 RawFFprobeOutput struct { 74 Format RawFFprobeFormat `json:"format"` 75 } 76 ) 77 78 probe := RawFFprobeOutput{} 79 err = json.Unmarshal([]byte(out), &probe) 80 if err != nil { return nil, err } 81 82 duration, err := strconv.ParseFloat(probe.Format.Duration, 64) 83 if err != nil { return nil, err } 84 size, err := strconv.ParseInt(probe.Format.Size, 10, 0) 85 if err != nil { return nil, err } 86 87 return &FFprobeOutput{ 88 Format: FFprobeFormat{ 89 Duration: duration, 90 Size: size, 91 }, 92 }, nil 93} 94 95func ReadMetadata(directory string) (*Metadata, error) { 96 metadata := &Metadata{} 97 file, err := os.OpenFile( 98 path.Join(directory, METADATA_FILENAME), 99 os.O_RDONLY, os.ModePerm, 100 ) 101 if err != nil { return nil, err } 102 103 err = toml.NewDecoder(file).Decode(metadata) 104 if err != nil { return nil, err } 105 106 return metadata, nil 107} 108 109func WriteMetadata(directory string, metadata *Metadata) (error) { 110 file, err := os.OpenFile( 111 path.Join(directory, METADATA_FILENAME), 112 os.O_CREATE | os.O_RDWR, 0644, 113 ) 114 if err != nil { return err } 115 116 err = toml.NewEncoder(file).Encode(metadata) 117 return err 118} 119 120func DefaultMetadata() *Metadata { 121 return &Metadata{ 122 Title: "Untitled Stream", 123 Date: time.Now().Format("2006-01-02"), 124 Part: 0, 125 Category: &Category{ 126 Name: "Something", 127 Type: "", 128 Url: "", 129 }, 130 } 131}