automatically upload nearby thumbnail.png
This commit is contained in:
parent
fc1d6b20c1
commit
2fcbc31f77
4 changed files with 75 additions and 19 deletions
17
main.go
17
main.go
|
|
@ -216,6 +216,17 @@ func main() {
|
||||||
enc.Encode(vodFiles)
|
enc.Encode(vodFiles)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// scan for thumbnail
|
||||||
|
thumbnailPath, thumbnailSizeBytes, err := scanner.ScanThumbnail(directory)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to fetch thumbnail: %v", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
thumbnail := &yt.Thumbnail{
|
||||||
|
Filepath: thumbnailPath,
|
||||||
|
SizeBytes: thumbnailSizeBytes,
|
||||||
|
}
|
||||||
|
|
||||||
// build video template for upload
|
// build video template for upload
|
||||||
video, err := yt.BuildVideo(metadata)
|
video, err := yt.BuildVideo(metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -263,7 +274,7 @@ func main() {
|
||||||
// concatenate VOD segments into full VOD
|
// concatenate VOD segments into full VOD
|
||||||
fullVodExists := func () bool {
|
fullVodExists := func () bool {
|
||||||
// check if full VOD already exists with expected duration
|
// check if full VOD already exists with expected duration
|
||||||
fullVodProbe, err := scanner.ProbeSegment(video.Filename)
|
fullVodProbe, err := scanner.ProbeSegment(video.Filepath)
|
||||||
if err != nil { return false }
|
if err != nil { return false }
|
||||||
video.SizeBytes = fullVodProbe.Format.Size
|
video.SizeBytes = fullVodProbe.Format.Size
|
||||||
var totalLength float64 = 0
|
var totalLength float64 = 0
|
||||||
|
|
@ -317,7 +328,7 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// okay actually upload now!
|
// okay actually upload now!
|
||||||
ytVideo, err := yt.UploadVideo(ctx, tokenSource, video, templates)
|
ytVideo, err := yt.UploadVideo(ctx, tokenSource, video, thumbnail, templates)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to upload video: %v", err)
|
log.Fatalf("Failed to upload video: %v", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|
@ -341,7 +352,7 @@ func main() {
|
||||||
|
|
||||||
// delete full VOD after upload, if requested
|
// delete full VOD after upload, if requested
|
||||||
if deleteFullVod {
|
if deleteFullVod {
|
||||||
err = os.Remove(video.Filename)
|
err = os.Remove(video.Filepath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to delete full VOD: %v", err)
|
log.Fatalf("Failed to delete full VOD: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package scanner
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
@ -60,6 +61,18 @@ func ScanSegments(directory string, extension string) ([]string, error) {
|
||||||
return files, nil
|
return files, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ScanThumbnail(directory string) (string, int64, error) {
|
||||||
|
thumbnailPath := path.Join(directory, "thumbnail.png")
|
||||||
|
stat, err := os.Stat(thumbnailPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", 0, err
|
||||||
|
}
|
||||||
|
if stat.IsDir() {
|
||||||
|
return "", 0, fmt.Errorf("thumbnail.png is a directory")
|
||||||
|
}
|
||||||
|
return thumbnailPath, stat.Size(), nil
|
||||||
|
}
|
||||||
|
|
||||||
func ProbeSegment(filename string) (*FFprobeOutput, error) {
|
func ProbeSegment(filename string) (*FFprobeOutput, error) {
|
||||||
out, err := ffmpeg_go.Probe(filename)
|
out, err := ffmpeg_go.Probe(filename)
|
||||||
if err != nil { return nil, err }
|
if err != nil { return nil, err }
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"arimelody.space/vodular/youtube"
|
"arimelody.space/vodular/youtube"
|
||||||
ffmpeg "github.com/u2takey/ffmpeg-go"
|
ffmpeg "github.com/u2takey/ffmpeg-go"
|
||||||
|
|
@ -23,15 +24,15 @@ type (
|
||||||
|
|
||||||
func ConcatVideo(video *youtube.Video, vodFiles []string, verbose bool) (int64, error) {
|
func ConcatVideo(video *youtube.Video, vodFiles []string, verbose bool) (int64, error) {
|
||||||
fileListPath := path.Join(
|
fileListPath := path.Join(
|
||||||
path.Dir(video.Filename),
|
path.Dir(video.Filepath),
|
||||||
"files.txt",
|
"files.txt",
|
||||||
)
|
)
|
||||||
|
|
||||||
totalDuration := float64(0.0)
|
totalDuration := float64(0.0)
|
||||||
fileListString := ""
|
fileListString := strings.Builder{}
|
||||||
for _, file := range vodFiles {
|
for _, file := range vodFiles {
|
||||||
fileListString += fmt.Sprintf("file '%s'\n", file)
|
fmt.Fprintf(&fileListString, "file '%s'\n", file)
|
||||||
jsonProbe, err := ffmpeg.Probe(path.Join(path.Dir(video.Filename), file))
|
jsonProbe, err := ffmpeg.Probe(path.Join(path.Dir(video.Filepath), file))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("failed to probe file `%s`: %v", file, err)
|
return 0, fmt.Errorf("failed to probe file `%s`: %v", file, err)
|
||||||
}
|
}
|
||||||
|
|
@ -45,7 +46,7 @@ func ConcatVideo(video *youtube.Video, vodFiles []string, verbose bool) (int64,
|
||||||
}
|
}
|
||||||
err := os.WriteFile(
|
err := os.WriteFile(
|
||||||
fileListPath,
|
fileListPath,
|
||||||
[]byte(fileListString),
|
[]byte(fileListString.String()),
|
||||||
0644,
|
0644,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -55,7 +56,7 @@ func ConcatVideo(video *youtube.Video, vodFiles []string, verbose bool) (int64,
|
||||||
stream := ffmpeg.Input(fileListPath, ffmpeg.KwArgs{
|
stream := ffmpeg.Input(fileListPath, ffmpeg.KwArgs{
|
||||||
"f": "concat",
|
"f": "concat",
|
||||||
"safe": "0",
|
"safe": "0",
|
||||||
}).Output(video.Filename, ffmpeg.KwArgs{
|
}).Output(video.Filepath, ffmpeg.KwArgs{
|
||||||
"c": "copy",
|
"c": "copy",
|
||||||
}).OverWriteOutput()
|
}).OverWriteOutput()
|
||||||
if verbose { stream = stream.ErrorToStdOut() }
|
if verbose { stream = stream.ErrorToStdOut() }
|
||||||
|
|
@ -70,7 +71,7 @@ func ConcatVideo(video *youtube.Video, vodFiles []string, verbose bool) (int64,
|
||||||
// not the end of the world; move along
|
// not the end of the world; move along
|
||||||
}
|
}
|
||||||
|
|
||||||
fileInfo, err := os.Stat(video.Filename)
|
fileInfo, err := os.Stat(video.Filepath)
|
||||||
if err != nil { return 0, fmt.Errorf("failed to read output file: %v", err) }
|
if err != nil { return 0, fmt.Errorf("failed to read output file: %v", err) }
|
||||||
|
|
||||||
return fileInfo.Size(), nil
|
return fileInfo.Size(), nil
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,12 @@ type (
|
||||||
Part int
|
Part int
|
||||||
Date time.Time
|
Date time.Time
|
||||||
Tags []string
|
Tags []string
|
||||||
Filename string
|
Filepath string
|
||||||
|
SizeBytes int64
|
||||||
|
}
|
||||||
|
|
||||||
|
Thumbnail struct {
|
||||||
|
Filepath string
|
||||||
SizeBytes int64
|
SizeBytes int64
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
@ -65,7 +70,7 @@ func BuildVideo(metadata *scanner.Metadata) (*Video, error) {
|
||||||
Part: metadata.Part,
|
Part: metadata.Part,
|
||||||
Date: metadata.Date.AsTime(time.UTC),
|
Date: metadata.Date.AsTime(time.UTC),
|
||||||
Tags: metadata.Tags,
|
Tags: metadata.Tags,
|
||||||
Filename: path.Join(
|
Filepath: path.Join(
|
||||||
metadata.FootageDir,
|
metadata.FootageDir,
|
||||||
fmt.Sprintf(
|
fmt.Sprintf(
|
||||||
"%s-fullvod.mkv",
|
"%s-fullvod.mkv",
|
||||||
|
|
@ -216,6 +221,7 @@ func UploadVideo(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
tokenSource oauth2.TokenSource,
|
tokenSource oauth2.TokenSource,
|
||||||
video *Video,
|
video *Video,
|
||||||
|
thumbnail *Thumbnail,
|
||||||
templates *Template,
|
templates *Template,
|
||||||
) (*youtube.Video, error) {
|
) (*youtube.Video, error) {
|
||||||
title, err := BuildTemplate(video, templates.Title)
|
title, err := BuildTemplate(video, templates.Title)
|
||||||
|
|
@ -233,6 +239,8 @@ func UploadVideo(
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// upload video
|
||||||
|
|
||||||
videoService := youtube.NewVideosService(service)
|
videoService := youtube.NewVideosService(service)
|
||||||
|
|
||||||
categoryId := YT_CATEGORY_ENTERTAINMENT
|
categoryId := YT_CATEGORY_ENTERTAINMENT
|
||||||
|
|
@ -242,7 +250,7 @@ func UploadVideo(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
call := videoService.Insert([]string{
|
videoInsertCall := videoService.Insert([]string{
|
||||||
"snippet", "status",
|
"snippet", "status",
|
||||||
}, &youtube.Video{
|
}, &youtube.Video{
|
||||||
Snippet: &youtube.VideoSnippet{
|
Snippet: &youtube.VideoSnippet{
|
||||||
|
|
@ -256,26 +264,49 @@ func UploadVideo(
|
||||||
},
|
},
|
||||||
}).NotifySubscribers(false)
|
}).NotifySubscribers(false)
|
||||||
|
|
||||||
file, err := os.Open(video.Filename)
|
videoFile, err := os.Open(video.Filepath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to open file: %v\n", err)
|
log.Fatalf("Failed to open video: %v\n", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
call.Media(file)
|
videoInsertCall.Media(videoFile)
|
||||||
|
|
||||||
log.Println("Uploading video...")
|
log.Println("Uploading video...")
|
||||||
|
|
||||||
call.ProgressUpdater(func(current, total int64) {
|
videoInsertCall.ProgressUpdater(func(current, total int64) {
|
||||||
// for some reason, this only returns 0.
|
// for some reason, this only returns 0.
|
||||||
// instead, we pull the file size from the ffmpeg output directly.
|
// instead, we pull the file size from the ffmpeg output directly.
|
||||||
if total == 0 { total = video.SizeBytes }
|
if total == 0 { total = video.SizeBytes }
|
||||||
fmt.Printf("Uploading... (%.2f%%)\n", float64(current) / float64(total) * 100)
|
fmt.Printf("\t(%.2f%%)\n", float64(current) / float64(total) * 100)
|
||||||
})
|
})
|
||||||
ytVideo, err := call.Do()
|
ytVideo, err := videoInsertCall.Do()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to upload video: %v\n", err)
|
log.Fatalf("Failed to upload video: %v\n", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// upload thumbnail
|
||||||
|
|
||||||
|
thumbnailService := youtube.NewThumbnailsService(service)
|
||||||
|
thumbnailSetCall := thumbnailService.Set(ytVideo.Id)
|
||||||
|
thumbnailFile, err := os.Open(thumbnail.Filepath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to open thumbnail: %v\n", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
thumbnailSetCall.Media(thumbnailFile)
|
||||||
|
|
||||||
|
log.Println("Uploading thumbnail...")
|
||||||
|
|
||||||
|
thumbnailSetCall.ProgressUpdater(func(current, total int64) {
|
||||||
|
if total == 0 { total = thumbnail.SizeBytes }
|
||||||
|
fmt.Printf("\t(%.2f%%)\n", float64(current) / float64(total) * 100)
|
||||||
|
})
|
||||||
|
// kinda don't care about the response here- so long as it works!
|
||||||
|
_, err = thumbnailSetCall.Do()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to upload thumbnail: %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
return ytVideo, err
|
return ytVideo, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue