From 0ba0e82742de89b0bcb35581246194b2de61d1d2 Mon Sep 17 00:00:00 2001 From: Logan G Date: Tue, 31 Dec 2024 14:33:46 -0700 Subject: [PATCH] Initial commit --- config.toml | 6 +++ ffmpeg.go | 29 +++++++++++ file.go | 23 +++++++++ go.mod | 31 ++++++++++++ go.sum | 110 +++++++++++++++++++++++++++++++++++++++++ youtube.go | 113 +++++++++++++++++++++++++++++++++++++++++++ ytva.go | 137 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 449 insertions(+) create mode 100644 config.toml create mode 100644 ffmpeg.go create mode 100644 file.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 youtube.go create mode 100644 ytva.go diff --git a/config.toml b/config.toml new file mode 100644 index 0000000..ee17a60 --- /dev/null +++ b/config.toml @@ -0,0 +1,6 @@ +OutputDir = "out/" +Channels = [ "https://www.youtube.com/@BudgetBuildsOfficial", "https://www.youtube.com/@RandomGaminginHD" ] + +[YouTube] +VideoFormat = "bestvideo[height<=720]" +AudioFormat = "bestaudio" diff --git a/ffmpeg.go b/ffmpeg.go new file mode 100644 index 0000000..b8a170b --- /dev/null +++ b/ffmpeg.go @@ -0,0 +1,29 @@ +package main + +import ( + ffmpeg "github.com/u2takey/ffmpeg-go" +) + +func mergeStreams(path1 string, path2 string, output string) (err error) { + input := []*ffmpeg.Stream{ffmpeg.Input(path1), ffmpeg.Input(path2)} + + defaultArgs := ffmpeg.KwArgs{"c:v": "copy", "c:a": "copy", "format": "matroska"} + + var ffmpegLogLevel ffmpeg.KwArgs + var silent bool + + if Flags.Verbose { + ffmpegLogLevel = ffmpeg.KwArgs{"v": "info"} + silent = false + } else if Flags.Quiet { + ffmpegLogLevel = ffmpeg.KwArgs{"v": "quiet"} + silent = true + } else { + ffmpegLogLevel = ffmpeg.KwArgs{"v": "error"} + silent = true + } + + kwArgs := ffmpeg.MergeKwArgs([]ffmpeg.KwArgs{ffmpegLogLevel, defaultArgs}) + + return ffmpeg.Output(input, output, kwArgs).OverWriteOutput().ErrorToStdOut().Silent(silent).Run() +} diff --git a/file.go b/file.go new file mode 100644 index 0000000..b366039 --- /dev/null +++ b/file.go @@ -0,0 +1,23 @@ +package main + +import ( + "fmt" + "os" + "errors" + + + //"github.com/charmbracelet/log" + "github.com/h2non/filetype" +) + + +func getExtension(filePath string) (extension string, err error) { + buf, _ := os.ReadFile(filePath) + + kind, _ := filetype.Match(buf) + if kind == filetype.Unknown { + return "", errors.New(fmt.Sprintf("Unknown file type.")) + } + + return kind.Extension, nil +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..ab695da --- /dev/null +++ b/go.mod @@ -0,0 +1,31 @@ +module ytva + +go 1.23.4 + +require ( + github.com/BurntSushi/toml v1.4.0 + github.com/alexflint/go-arg v1.5.1 + github.com/charmbracelet/log v0.4.0 + github.com/h2non/filetype v1.1.3 + github.com/u2takey/ffmpeg-go v0.5.0 + github.com/wader/goutubedl v0.0.0-20241224160441-33e26ae8181c +) + +require ( + github.com/alexflint/go-scalar v1.2.0 // indirect + github.com/aws/aws-sdk-go v1.38.20 // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/charmbracelet/lipgloss v0.10.0 // indirect + github.com/go-logfmt/logfmt v0.6.0 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/mattn/go-isatty v0.0.18 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/muesli/reflow v0.3.0 // indirect + github.com/muesli/termenv v0.15.2 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/u2takey/go-utils v0.3.1 // indirect + golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect + golang.org/x/net v0.33.0 // indirect + golang.org/x/sys v0.28.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..a6e52c9 --- /dev/null +++ b/go.sum @@ -0,0 +1,110 @@ +github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= +github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/alexflint/go-arg v1.5.1 h1:nBuWUCpuRy0snAG+uIJ6N0UvYxpxA0/ghA/AaHxlT8Y= +github.com/alexflint/go-arg v1.5.1/go.mod h1:A7vTJzvjoaSTypg4biM5uYNTkJ27SkNTArtYXnlqVO8= +github.com/alexflint/go-scalar v1.2.0 h1:WR7JPKkeNpnYIOfHRa7ivM21aWAdHD0gEWHCx+WQBRw= +github.com/alexflint/go-scalar v1.2.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o= +github.com/aws/aws-sdk-go v1.38.20 h1:QbzNx/tdfATbdKfubBpkt84OM6oBkxQZRw6+bW2GyeA= +github.com/aws/aws-sdk-go v1.38.20/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/charmbracelet/lipgloss v0.10.0 h1:KWeXFSexGcfahHX+54URiZGkBFazf70JNMtwg/AFW3s= +github.com/charmbracelet/lipgloss v0.10.0/go.mod h1:Wig9DSfvANsxqkRsqj6x87irdy123SR4dOXlKa91ciE= +github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM= +github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= +github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg= +github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= +github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= +github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= +github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= +github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= +github.com/panjf2000/ants/v2 v2.4.2/go.mod h1:f6F0NZVFsGCp5A7QW/Zj/m92atWwOkY0OIhFxRNFr4A= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/u2takey/ffmpeg-go v0.5.0 h1:r7d86XuL7uLWJ5mzSeQ03uvjfIhiJYvsRAJFCW4uklU= +github.com/u2takey/ffmpeg-go v0.5.0/go.mod h1:ruZWkvC1FEiUNjmROowOAps3ZcWxEiOpFoHCvk97kGc= +github.com/u2takey/go-utils v0.3.1 h1:TaQTgmEZZeDHQFYfd+AdUT1cT4QJgJn/XVPELhHw4ys= +github.com/u2takey/go-utils v0.3.1/go.mod h1:6e+v5vEZ/6gu12w/DC2ixZdZtCrNokVxD0JUklcqdCs= +github.com/wader/goutubedl v0.0.0-20241224160441-33e26ae8181c h1:p1o/Deq8K9VL0f9ccabJtciMc3S8opTNgJ+2JP5cLZE= +github.com/wader/goutubedl v0.0.0-20241224160441-33e26ae8181c/go.mod h1:5KXd5tImdbmz4JoVhePtbIokCwAfEhUVVx3WLHmjYuw= +github.com/wader/osleaktest v0.0.0-20191111175233-f643b0fed071 h1:QkrG4Zr5OVFuC9aaMPmFI0ibfhBZlAgtzDYWfu7tqQk= +github.com/wader/osleaktest v0.0.0-20191111175233-f643b0fed071/go.mod h1:XD6emOFPHVzb0+qQpiNOdPL2XZ0SRUM0N5JHuq6OmXo= +gocv.io/x/gocv v0.25.0/go.mod h1:Rar2PS6DV+T4FL+PM535EImD/h13hGVaHhnCu1xarBs= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= +golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/youtube.go b/youtube.go new file mode 100644 index 0000000..c296826 --- /dev/null +++ b/youtube.go @@ -0,0 +1,113 @@ +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 +} diff --git a/ytva.go b/ytva.go new file mode 100644 index 0000000..55cda00 --- /dev/null +++ b/ytva.go @@ -0,0 +1,137 @@ +package main + +import ( + //"flag" + "context" + //"fmt" + "os" + //"io" + //"reflect" + + //"google.golang.org/api/option" + //"google.golang.org/api/youtube/v3" + "github.com/wader/goutubedl" + "github.com/BurntSushi/toml" + "github.com/charmbracelet/log" + "github.com/alexflint/go-arg" +) + + + +type _flags struct { + Quiet bool `arg:"-q,--quiet" help:"Output only warnings and errors."` + Verbose bool `arg:"-v,--verbose" help:"Enables debug output."` + ConfigFile string `arg:"-c,--config,env:YTVA_CONFIG" help:"Specifies the path to a TOML formatted configuration file." default:"config.toml"` +} + +func (_flags) Description() string { + return "YouTube Video Archive - Downloads videos in bulk automatically" +} + +var Flags _flags + + + +type ConfigFile struct { + OutputDir string + Channels []string + YouTube struct { + VideoFormat string + AudioFormat string + } +} + +var MainConfig ConfigFile + + + +func main() { + _ = arg.MustParse(&Flags) + + if Flags.Verbose { + log.SetReportCaller(true) + log.SetLevel(log.DebugLevel) + if Flags.Quiet { + log.Warn("Verbose and quiet specified. Defaulting to verbose.") + } + } else if Flags.Quiet { + log.SetLevel(log.WarnLevel) + } + + confFile, err := os.ReadFile(Flags.ConfigFile) + if(err != nil) { + log.Fatal(err) + } + + confContent := string(confFile) + + _, err = toml.Decode(confContent, &MainConfig) + + log.Debug("", "config", MainConfig) + + goutubedl.Path = "yt-dlp" + + if _, err := os.Stat(MainConfig.OutputDir); err != nil { + if os.IsNotExist(err) { + if err := os.Mkdir(MainConfig.OutputDir, 0755); err != nil { + log.Fatal(err) + } + } else { + log.Fatal(err) + } + } + + for _, channel := range MainConfig.Channels { + log.Info("==========================================================================================================") + log.Infof("Channel URL: \"%s\"", channel) + + optType := goutubedl.TypeFromString["channel"] + channel, err := goutubedl.New(context.Background(), channel, + goutubedl.Options{ + Type: optType, + PlaylistEnd: 2, + }, + ) + if err != nil { + log.Fatalf("Error creating YouTube service: %v", err) + } + + log.Infof("Channel Name: %s", channel.Info.Channel) + + basePath := MainConfig.OutputDir+"/"+channel.Info.Channel+"/" + + if _, err := os.Stat(basePath); err != nil { + if os.IsNotExist(err) { + if err := os.Mkdir(basePath, 0755); err != nil { + log.Fatal(err) + } + } else { + log.Fatal(err) + } + } + + 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) + downloadVideo(v.WebpageURL, MainConfig.YouTube.VideoFormat, MainConfig.YouTube.AudioFormat, dirPath, 2^32) + } + } +}