diff --git a/README.md b/README.md index ff53e8f..b36d3c7 100644 --- a/README.md +++ b/README.md @@ -14,13 +14,8 @@ I built this to greatly simplify the process of getting my full-quality livestre $ vodular New config file created (config.toml). Please edit this file before running again! ``` -The directory which holds your configuration file and templates varies, -depending on platform: -- **Linux:** `~/.config/vodular/templates` -- **macOS:** `~/Library/Application Support/vodular/templates` -- **Windows:** `%AppData%/vodular/templates` -2. Edit your configuration file as necessary (You will need to create a [YouTube Data API v3](https://developers.google.com/youtube/v3) service and provide its credentials here). +2. Edit configuration file as necessary (You will need to create a [YouTube Data API v3](https://developers.google.com/youtube/v3) service and provide its credentials here). **IMPORTANT:** `config.toml` contains very sensitive credentials. Do not share this file with anyone. 3. Initialise a VOD directory: @@ -68,11 +63,10 @@ url = 'https://example.org' ``` ## Templates -There are three template files, `title.txt`, `description.txt`, and `tags.txt`, -which can be created in `/path/to/vodular/templates`. These templates can be -created and tweaked to customise your VOD metadata on upload. They are enhanced -with Go's [template format](https://pkg.go.dev/text/template) to inject -information provided in `metadata.toml`, and other neat functionality! +Template files can be created at `templates/title.txt`, +`template/description.txt`, and `templates/tags.txt` respectively. These +files can use Go's [text template format](https://pkg.go.dev/text/template) to +customise VOD metadata on upload. You can use the following data in templates: - **`.Title`:** The title of the stream. diff --git a/main.go b/main.go index 341711a..45845f3 100644 --- a/main.go +++ b/main.go @@ -6,7 +6,6 @@ import ( "encoding/json" "fmt" "log" - "math" "net/http" "os" "path" @@ -26,9 +25,7 @@ import ( //go:embed res/help.txt var helpText string -const SEGMENT_EXTENSION = "mkv" -const MAX_TITLE_LEN = 100 -const MAX_DESCRIPTION_LEN = 5000 +const segmentExtension = "mkv" func showHelp() { fmt.Println(helpText) @@ -154,7 +151,7 @@ func main() { } // good to have early on - templates, err := yt.FetchTemplates(path.Join(userConfigDir, "vodular", "templates")) + templates, err := yt.FetchTemplates() if err != nil { log.Fatalf("Failed to fetch templates: %v", err) os.Exit(1) @@ -196,7 +193,7 @@ func main() { } // scan for VOD segments - vodFiles, err := scanner.ScanSegments(metadata.FootageDir, SEGMENT_EXTENSION) + vodFiles, err := scanner.ScanSegments(metadata.FootageDir, segmentExtension) if err != nil { log.Fatalf("Failed to fetch VOD filenames: %v", err) os.Exit(1) @@ -204,7 +201,7 @@ func main() { if len(vodFiles) == 0 { log.Fatalf( "Directory contained no VOD files (expecting .%s)", - SEGMENT_EXTENSION, + segmentExtension, ) os.Exit(1) } @@ -223,35 +220,21 @@ func main() { log.Fatalf("Failed to build video template: %v", err) os.Exit(1) } - title, err := yt.BuildTemplate(video, templates.Title) - if err != nil { - log.Fatalf("Failed to build video title: %v", err) - os.Exit(1) - } - description, err := yt.BuildTemplate(video, templates.Description) - if err != nil { - log.Fatalf("Failed to build video description: %v", err) - os.Exit(1) - } - if len(title) > 100 { - log.Fatalf( - "Video title length exceeds %d characters (%d). YouTube may reject this!", - MAX_TITLE_LEN, - len(video.Title), - ) - } - if len(description) > 5000 { - log.Fatalf( - "Video description length exceeds %d characters (%d). YouTube may reject this!", - MAX_DESCRIPTION_LEN, - len(description), - ) - } if verbose { enc := json.NewEncoder(os.Stdout) fmt.Printf("\nVideo template: ") enc.Encode(video) + title, err := yt.BuildTemplate(video, templates.Title) + if err != nil { + log.Fatalf("Failed to build video title: %v", err) + os.Exit(1) + } + description, err := yt.BuildTemplate(video, templates.Description) + if err != nil { + log.Fatalf("Failed to build video description: %v", err) + os.Exit(1) + } fmt.Printf( "\n================================\n\n" + "< TITLE >\n%s\n\n" + @@ -264,21 +247,18 @@ func main() { // concatenate VOD segments into full VOD fullVodExists := func () bool { // check if full VOD already exists with expected duration - fullVodProbe, err := scanner.ProbeSegment(video.Filename) - if err != nil { return false } - video.SizeBytes = fullVodProbe.Format.Size - var totalLength float64 = 0 - - for _, filename := range vodFiles { - probe, err := scanner.ProbeSegment(path.Join(metadata.FootageDir, filename)) - if err != nil { continue } - totalLength += probe.Format.Duration + if fullVodProbe, err := scanner.ProbeSegment(video.Filename); err != nil { + var totalLength float64 = 0 + for _, filename := range vodFiles { + probe, err := scanner.ProbeSegment(filename) + if err != nil { continue } + totalLength += probe.Format.Duration + } + return fullVodProbe.Format.Duration == totalLength } - return math.Abs(fullVodProbe.Format.Duration - totalLength) < float64(0.1) + return false }() - if fullVodExists { - log.Print("Full VOD appears to already exist- uploading this file...") - } else { + if !fullVodExists { video.SizeBytes, err = vid.ConcatVideo(video, vodFiles, verbose) if err != nil { log.Fatalf("Failed to concatenate VOD segments: %v", err) @@ -333,7 +313,6 @@ func main() { log.Print("Video uploaded successfully!") // update metadata to reflect VOD is uploaded - // TODO: rather than a boolean flag, link to actual video metadata.Uploaded = true err = scanner.WriteMetadata(directory, metadata) if err != nil { diff --git a/scanner/scanner.go b/scanner/scanner.go index 1ff5bd9..5cb3f14 100644 --- a/scanner/scanner.go +++ b/scanner/scanner.go @@ -4,7 +4,6 @@ import ( "encoding/json" "os" "path" - "strconv" "strings" "time" @@ -29,13 +28,13 @@ type ( Category *Category `toml:"category" comment:"(Optional) Category details, for additional credits."` } - FFprobeFormat struct { + ffprobeFormat struct { Duration float64 `json:"duration"` Size int64 `json:"size"` } - FFprobeOutput struct { - Format FFprobeFormat `json:"format"` + ffprobeOutput struct { + Format ffprobeFormat `json:"format"` } ) @@ -51,7 +50,6 @@ func ScanSegments(directory string, extension string) ([]string, error) { for _, item := range entries { if item.IsDir() { continue } - if strings.HasPrefix(item.Name(), ".") { continue } if !strings.HasSuffix(item.Name(), "." + extension) { continue } if strings.HasSuffix(item.Name(), "-fullvod." + extension) { continue } files = append(files, item.Name()) @@ -60,36 +58,15 @@ func ScanSegments(directory string, extension string) ([]string, error) { return files, nil } -func ProbeSegment(filename string) (*FFprobeOutput, error) { +func ProbeSegment(filename string) (*ffprobeOutput, error) { out, err := ffmpeg_go.Probe(filename) if err != nil { return nil, err } - type ( - RawFFprobeFormat struct { - // these being strings upsets me immensely - Duration string `json:"duration"` - Size string `json:"size"` - } - RawFFprobeOutput struct { - Format RawFFprobeFormat `json:"format"` - } - ) - - probe := RawFFprobeOutput{} - err = json.Unmarshal([]byte(out), &probe) + probe := ffprobeOutput{} + err = json.Unmarshal([]byte(out), probe) if err != nil { return nil, err } - duration, err := strconv.ParseFloat(probe.Format.Duration, 64) - if err != nil { return nil, err } - size, err := strconv.ParseInt(probe.Format.Size, 10, 0) - if err != nil { return nil, err } - - return &FFprobeOutput{ - Format: FFprobeFormat{ - Duration: duration, - Size: size, - }, - }, nil + return &probe, nil } func ReadMetadata(directory string) (*Metadata, error) { diff --git a/youtube/youtube.go b/youtube/youtube.go index c6ace15..79641fa 100644 --- a/youtube/youtube.go +++ b/youtube/youtube.go @@ -113,6 +113,11 @@ var videoYtCategory = map[CategoryType]string { CATEGORY_ENTERTAINMENT: YT_CATEGORY_ENTERTAINMENT, } +var templateDir = "templates" +var tagsPath = path.Join(templateDir, "tags.txt") +var titlePath = path.Join(templateDir, "title.txt") +var descriptionPath = path.Join(templateDir, "description.txt") + const defaultTitleTemplate = "{{.Title}} - {{FormatTime .Date \"02 Jan 2006\"}}" const defaultDescriptionTemplate = @@ -130,13 +135,9 @@ var templateFuncs = template.FuncMap{ }, } -func FetchTemplates(templateDir string) (*Template, error) { +func FetchTemplates() (*Template, error) { tmpl := Template{} - var tagsPath = path.Join(templateDir, "tags.txt") - var titlePath = path.Join(templateDir, "title.txt") - var descriptionPath = path.Join(templateDir, "description.txt") - // tags if tagsFile, err := os.ReadFile(tagsPath); err == nil { tmpl.Tags = strings.Split(string(tagsFile), "\n")