Prototype
This commit is contained in:
parent
6e027de406
commit
44994bd14e
6 changed files with 166 additions and 235 deletions
11
config.toml
11
config.toml
|
@ -1,6 +1,11 @@
|
||||||
OutputDir = "out/"
|
TempDir = "/tmp/"
|
||||||
Channels = [ "https://www.youtube.com/@BudgetBuildsOfficial", "https://www.youtube.com/@RandomGaminginHD" ]
|
|
||||||
|
|
||||||
[YouTube]
|
[Group.PCBuilders]
|
||||||
|
URL = [ "https://www.youtube.com/@BudgetBuildsOfficial", "https://www.youtube.com/@RandomGaminginHD" ]
|
||||||
|
OutputDir = "out/%Channel%/%Playlist%/"
|
||||||
|
FileName = "%Timestamp% - %Title%.mkv"
|
||||||
|
CFormat = "matroska"
|
||||||
VideoFormat = "bestvideo[height<=720]"
|
VideoFormat = "bestvideo[height<=720]"
|
||||||
AudioFormat = "bestaudio"
|
AudioFormat = "bestaudio"
|
||||||
|
NumVideos = 2
|
||||||
|
SizeLimit = 4294967296
|
||||||
|
|
113
download.go
113
download.go
|
@ -7,7 +7,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
//"strconv"
|
||||||
|
|
||||||
|
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
|
@ -33,7 +33,7 @@ func checkURL(input string) (error) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func downloadVideo(url string, videoFormat string, audioFormat string, outDir string, sizeLimit float64) (err error) {
|
func downloadVideo(url string, path string, group GroupConfig) (err error) {
|
||||||
goutubedl.Path = "yt-dlp"
|
goutubedl.Path = "yt-dlp"
|
||||||
|
|
||||||
if err := checkURL(url); err != nil {
|
if err := checkURL(url); err != nil {
|
||||||
|
@ -47,67 +47,68 @@ func downloadVideo(url string, videoFormat string, audioFormat string, outDir st
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
path := outDir+strconv.Itoa(int(video.Info.Timestamp))+" - "+video.Info.Title
|
if video.Info.IsLive {
|
||||||
|
log.Info("Video is a live stream. Skipping.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
if ! video.Info.IsLive {
|
/* VIDEO */
|
||||||
/* VIDEO */
|
{
|
||||||
{
|
log.Debugf("Downloading video \"%s\" with format \"%s\"", video.Info.ID, group.VideoFormat)
|
||||||
log.Debugf("Downloading video \"%s\" with format \"%s\"", video.Info.ID, videoFormat)
|
|
||||||
|
|
||||||
videoDLResult, err := video.Download(context.Background(), videoFormat)
|
videoDLResult, err := video.Download(context.Background(), group.VideoFormat)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer videoDLResult.Close()
|
|
||||||
|
|
||||||
file, err := os.OpenFile(path+"-vid", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
io.Copy(file, videoDLResult)
|
|
||||||
|
|
||||||
videoDLResult.Close()
|
|
||||||
file.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
/* AUDIO */
|
|
||||||
{
|
|
||||||
log.Debugf("Downloading audio \"%s\" with format \"%s\"", video.Info.ID, audioFormat)
|
|
||||||
|
|
||||||
audioDLResult, err := video.Download(context.Background(), audioFormat)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer audioDLResult.Close()
|
|
||||||
|
|
||||||
file, err := os.OpenFile(path+"-audio", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
io.Copy(file, audioDLResult)
|
|
||||||
|
|
||||||
audioDLResult.Close()
|
|
||||||
file.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
mergeStreams(path+"-vid",path+"-audio",path+".mkv")
|
|
||||||
|
|
||||||
if err := os.Remove(path+"-vid"); err != nil {
|
|
||||||
log.Warnf("Could not remove file \"%s\"", path+"-vid")
|
|
||||||
}
|
|
||||||
if err := os.Remove(path+"-audio"); err != nil {
|
|
||||||
log.Warnf("Could not remove file \"%s\"", path+"-audio")
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
extension, err := getExtension(path)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
os.Rename(path, path+"."+extension)
|
defer videoDLResult.Close()
|
||||||
*/
|
|
||||||
|
file, err := os.OpenFile(MainConfig.TempDir+"/"+video.Info.ID+"-vid", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
io.Copy(file, videoDLResult)
|
||||||
|
|
||||||
|
videoDLResult.Close()
|
||||||
|
file.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* AUDIO */
|
||||||
|
{
|
||||||
|
log.Debugf("Downloading audio \"%s\" with format \"%s\"", video.Info.ID, group.AudioFormat)
|
||||||
|
|
||||||
|
audioDLResult, err := video.Download(context.Background(), group.AudioFormat)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer audioDLResult.Close()
|
||||||
|
|
||||||
|
file, err := os.OpenFile(MainConfig.TempDir+"/"+video.Info.ID+"-audio", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
io.Copy(file, audioDLResult)
|
||||||
|
|
||||||
|
audioDLResult.Close()
|
||||||
|
file.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
mergeStreams(MainConfig.TempDir+"/"+video.Info.ID+"-vid",MainConfig.TempDir+"/"+video.Info.ID+"-audio", group.CFormat, path)
|
||||||
|
|
||||||
|
if err := os.Remove(MainConfig.TempDir+"/"+video.Info.ID+"-vid"); err != nil {
|
||||||
|
log.Warnf("Could not remove file \"%s\"", MainConfig.TempDir+"/"+video.Info.ID+"-vid")
|
||||||
|
}
|
||||||
|
if err := os.Remove(MainConfig.TempDir+"/"+video.Info.ID+"-audio"); err != nil {
|
||||||
|
log.Warnf("Could not remove file \"%s\"", MainConfig.TempDir+"/"+video.Info.ID+"-audio")
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
extension, err := getExtension(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Rename(path, path+"."+extension)
|
||||||
|
*/
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,10 +4,10 @@ import (
|
||||||
ffmpeg "github.com/u2takey/ffmpeg-go"
|
ffmpeg "github.com/u2takey/ffmpeg-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
func mergeStreams(path1 string, path2 string, output string) (err error) {
|
func mergeStreams(path1 string, path2 string, format string, output string) (err error) {
|
||||||
input := []*ffmpeg.Stream{ffmpeg.Input(path1), ffmpeg.Input(path2)}
|
input := []*ffmpeg.Stream{ffmpeg.Input(path1), ffmpeg.Input(path2)}
|
||||||
|
|
||||||
defaultArgs := ffmpeg.KwArgs{"c:v": "copy", "c:a": "copy", "format": "matroska"}
|
defaultArgs := ffmpeg.KwArgs{"c:v": "copy", "c:a": "copy", "format": format}
|
||||||
|
|
||||||
var ffmpegLogLevel ffmpeg.KwArgs
|
var ffmpegLogLevel ffmpeg.KwArgs
|
||||||
var silent bool
|
var silent bool
|
||||||
|
|
42
substring.go
Normal file
42
substring.go
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"reflect"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
//"github.com/charmbracelet/log"
|
||||||
|
"github.com/wader/goutubedl"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func fillPlaceholders(input string, videoInfo goutubedl.Info) (output string) {
|
||||||
|
|
||||||
|
re := regexp.MustCompile(`%([^%]+)%`)
|
||||||
|
|
||||||
|
return re.ReplaceAllStringFunc(input, func(match string) string {
|
||||||
|
groups := re.FindStringSubmatch(match)
|
||||||
|
if len(groups) < 1 {
|
||||||
|
// No capture group found; return original match
|
||||||
|
return match
|
||||||
|
}
|
||||||
|
key := groups[1] // the captured part
|
||||||
|
|
||||||
|
v := reflect.ValueOf(videoInfo)
|
||||||
|
if v.Kind() == reflect.Struct {
|
||||||
|
fieldVal := v.FieldByName(key)
|
||||||
|
if fieldVal.IsValid() && fieldVal.CanInterface() {
|
||||||
|
// Haha cheating :)
|
||||||
|
if fieldVal.Kind() == reflect.Float64 {
|
||||||
|
return fmt.Sprintf("%v", int(fieldVal.Float()))
|
||||||
|
} else {
|
||||||
|
return fmt.Sprintf("%v", fieldVal.Interface())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value not found; return original match
|
||||||
|
return match
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
113
youtube.go
113
youtube.go
|
@ -1,113 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"io"
|
|
||||||
"errors"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
|
|
||||||
"github.com/charmbracelet/log"
|
|
||||||
"github.com/wader/goutubedl"
|
|
||||||
)
|
|
||||||
|
|
||||||
func checkURL(input string) (error) {
|
|
||||||
request, err := http.NewRequest("GET", input, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
client := &http.Client{}
|
|
||||||
resp, err := client.Do(request)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode == http.StatusNotFound {
|
|
||||||
return errors.New(fmt.Sprintf("Video does not exist!"))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func downloadVideo(url string, videoFormat string, audioFormat string, outDir string, sizeLimit float64) (err error) {
|
|
||||||
goutubedl.Path = "yt-dlp"
|
|
||||||
|
|
||||||
if err := checkURL(url); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf("URL \"%s\" exists.", url)
|
|
||||||
|
|
||||||
video, err := goutubedl.New(context.Background(), url, goutubedl.Options{})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
path := outDir+strconv.Itoa(int(video.Info.Timestamp))+" - "+video.Info.Title
|
|
||||||
|
|
||||||
if ! video.Info.IsLive {
|
|
||||||
/* VIDEO */
|
|
||||||
{
|
|
||||||
log.Debugf("Downloading video \"%s\" with format \"%s\"", video.Info.ID, videoFormat)
|
|
||||||
|
|
||||||
videoDLResult, err := video.Download(context.Background(), videoFormat)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer videoDLResult.Close()
|
|
||||||
|
|
||||||
file, err := os.OpenFile(path+"-vid", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
io.Copy(file, videoDLResult)
|
|
||||||
|
|
||||||
videoDLResult.Close()
|
|
||||||
file.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
/* AUDIO */
|
|
||||||
{
|
|
||||||
log.Debugf("Downloading audio \"%s\" with format \"%s\"", video.Info.ID, audioFormat)
|
|
||||||
|
|
||||||
audioDLResult, err := video.Download(context.Background(), audioFormat)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer audioDLResult.Close()
|
|
||||||
|
|
||||||
file, err := os.OpenFile(path+"-audio", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
io.Copy(file, audioDLResult)
|
|
||||||
|
|
||||||
audioDLResult.Close()
|
|
||||||
file.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
mergeStreams(path+"-vid",path+"-audio",path+".mkv")
|
|
||||||
|
|
||||||
if err := os.Remove(path+"-vid"); err != nil {
|
|
||||||
log.Warnf("Could not remove file \"%s\"", path+"-vid")
|
|
||||||
}
|
|
||||||
if err := os.Remove(path+"-audio"); err != nil {
|
|
||||||
log.Warnf("Could not remove file \"%s\"", path+"-audio")
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
extension, err := getExtension(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
os.Rename(path, path+"."+extension)
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
118
ytva.go
118
ytva.go
|
@ -32,18 +32,23 @@ var Flags _flags
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
type ConfigFile struct {
|
type _config struct {
|
||||||
OutputDir string
|
TempDir string
|
||||||
Channels []string
|
Group map[string]GroupConfig
|
||||||
YouTube struct {
|
|
||||||
VideoFormat string
|
|
||||||
AudioFormat string
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var MainConfig ConfigFile
|
type GroupConfig struct {
|
||||||
|
URL []string
|
||||||
|
OutputDir string
|
||||||
|
FileName string
|
||||||
|
CFormat string
|
||||||
|
VideoFormat string
|
||||||
|
AudioFormat string
|
||||||
|
NumVideos uint
|
||||||
|
SizeLimit float64
|
||||||
|
}
|
||||||
|
|
||||||
|
var MainConfig _config
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
_ = arg.MustParse(&Flags)
|
_ = arg.MustParse(&Flags)
|
||||||
|
@ -71,67 +76,58 @@ func main() {
|
||||||
|
|
||||||
goutubedl.Path = "yt-dlp"
|
goutubedl.Path = "yt-dlp"
|
||||||
|
|
||||||
if _, err := os.Stat(MainConfig.OutputDir); err != nil {
|
for name, group := range MainConfig.Group {
|
||||||
if os.IsNotExist(err) {
|
log.Info("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++")
|
||||||
if err := os.Mkdir(MainConfig.OutputDir, 0755); err != nil {
|
log.Infof("Started group \"%s\"", name)
|
||||||
log.Fatal(err)
|
|
||||||
|
for _, channel := range group.URL {
|
||||||
|
log.Info("================================================================")
|
||||||
|
log.Infof("Channel URL: \"%s\"", channel)
|
||||||
|
|
||||||
|
optType := goutubedl.TypeFromString["channel"]
|
||||||
|
channel, err := goutubedl.New(context.Background(), channel,
|
||||||
|
goutubedl.Options{
|
||||||
|
Type: optType,
|
||||||
|
PlaylistEnd: group.NumVideos,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error creating YouTube service: %v", err)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, channel := range MainConfig.Channels {
|
log.Infof("Channel Name: %s", channel.Info.Channel)
|
||||||
log.Info("==========================================================================================================")
|
|
||||||
log.Infof("Channel URL: \"%s\"", channel)
|
|
||||||
|
|
||||||
optType := goutubedl.TypeFromString["channel"]
|
for _, v := range channel.Info.Entries {
|
||||||
channel, err := goutubedl.New(context.Background(), channel,
|
log.Debugf("-------------------------------------")
|
||||||
goutubedl.Options{
|
log.Debugf("Title: %s", v.Title)
|
||||||
Type: optType,
|
log.Debugf("URL: %s", v.WebpageURL)
|
||||||
PlaylistEnd: 2,
|
log.Debugf("Playlist: %s", v.Playlist)
|
||||||
},
|
log.Debugf("PublishedAt: %s", v.UploadDate)
|
||||||
)
|
log.Debugf("IsLive: %t", v.IsLive)
|
||||||
if err != nil {
|
//log.Debugf("Description: %s\n", item.Snippet.Description)
|
||||||
log.Fatalf("Error creating YouTube service: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Infof("Channel Name: %s", channel.Info.Channel)
|
dirPath := fillPlaceholders(group.OutputDir, v)
|
||||||
|
fileName := fillPlaceholders(group.FileName, v)
|
||||||
|
path := dirPath+"/"+fileName
|
||||||
|
|
||||||
basePath := MainConfig.OutputDir+"/"+channel.Info.Channel+"/"
|
log.Debugf("Output: %s", path)
|
||||||
|
|
||||||
if _, err := os.Stat(basePath); err != nil {
|
if _, err := os.Stat(dirPath); err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
if err := os.Mkdir(basePath, 0755); err != nil {
|
log.Debugf("Directory \"%s\" does not exist, creating.", dirPath)
|
||||||
log.Fatal(err)
|
if err := os.MkdirAll(dirPath, 0755); err != nil {
|
||||||
}
|
log.Error(err)
|
||||||
} else {
|
continue
|
||||||
log.Fatal(err)
|
}
|
||||||
}
|
} else {
|
||||||
}
|
log.Error(err)
|
||||||
|
continue
|
||||||
for _, v := range channel.Info.Entries {
|
|
||||||
log.Debugf("-----------------------------------------------------")
|
|
||||||
log.Debugf("Title: %s", v.Title)
|
|
||||||
log.Debugf("URL: %s", v.WebpageURL)
|
|
||||||
log.Debugf("Playlist: %s", v.Playlist)
|
|
||||||
log.Debugf("PublishedAt: %s", v.UploadDate)
|
|
||||||
log.Debugf("IsLive: %t", v.IsLive)
|
|
||||||
//log.Debugf("Description: %s\n", item.Snippet.Description)
|
|
||||||
|
|
||||||
dirPath := basePath+"/"+v.Playlist+"/"
|
|
||||||
if _, err := os.Stat(dirPath); err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
if err := os.Mkdir(dirPath, 0755); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
log.Infof("Downloading video: %s", v.Title)
|
log.Infof("Downloading video: %s", v.Title)
|
||||||
downloadVideo(v.WebpageURL, MainConfig.YouTube.VideoFormat, MainConfig.YouTube.AudioFormat, dirPath, 2^32)
|
downloadVideo(v.WebpageURL, path, group)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue