Compare commits
No commits in common. "main" and "v1.2.2" have entirely different histories.
|
@ -25,7 +25,7 @@ You can configure the application using environment variables
|
||||||
| ---------------------------------- | ----------------------------------------------------------------------------------------------- | --------------- | ------------------------------------------------------------------------------ |
|
| ---------------------------------- | ----------------------------------------------------------------------------------------------- | --------------- | ------------------------------------------------------------------------------ |
|
||||||
| YTDL_CONFIGDIR | Add a custom config directory to search for the config file | ` ` | |
|
| YTDL_CONFIGDIR | Add a custom config directory to search for the config file | ` ` | |
|
||||||
| YTDL_ENV | The application environment | `Production` | `Development`, `Staging`, `Production` |
|
| YTDL_ENV | The application environment | `Production` | `Development`, `Staging`, `Production` |
|
||||||
| YTDL_YTDLP_BINARYPATH | The path to the yt-dlp binary | `yt-dlp` | |
|
| YTDL_BINARYPATH | The path to the yt-dlp binary | `yt-dlp` | |
|
||||||
| YTDL_HTTP_PORT | The tcp port for the web server to listen on | `8080` | |
|
| YTDL_HTTP_PORT | The tcp port for the web server to listen on | `8080` | |
|
||||||
| YTDL_HTTP_LISTEN | The address for the web server to listen on | `127.0.0.1` | `0.0.0.0`, `127.0.0.1`, etc. |
|
| YTDL_HTTP_LISTEN | The address for the web server to listen on | `127.0.0.1` | `0.0.0.0`, `127.0.0.1`, etc. |
|
||||||
| YTDL_HTTP_BASEPATH | The base path of the application, useful for reverse proxies | `/` | |
|
| YTDL_HTTP_BASEPATH | The base path of the application, useful for reverse proxies | `/` | |
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
@ -108,10 +109,6 @@ func (c *DownloadController) ListDownloadLinks(w http.ResponseWriter, r *http.Re
|
||||||
}, layout...)
|
}, layout...)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
BUF_LEN = 1024
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *DownloadController) ProxyDownload(w http.ResponseWriter, r *http.Request) {
|
func (c *DownloadController) ProxyDownload(w http.ResponseWriter, r *http.Request) {
|
||||||
videoUrl, ok := c.getUrlParam(r)
|
videoUrl, ok := c.getUrlParam(r)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -162,19 +159,33 @@ func (c *DownloadController) ProxyDownload(w http.ResponseWriter, r *http.Reques
|
||||||
index = -1
|
index = -1
|
||||||
}
|
}
|
||||||
|
|
||||||
read, write := io.Pipe()
|
pread, pwrite := io.Pipe()
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(2)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
_, err := io.Copy(w, read)
|
defer wg.Done()
|
||||||
if err != nil {
|
|
||||||
slog.Error("Failed to copy", slog.String("error", err.Error()))
|
if err := c.ytdl.Download(pwrite, videoUrl, format.FormatID, index); err != nil {
|
||||||
|
slog.Error("Failed to download", slog.String("error", err.Error()))
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
pwrite.CloseWithError(err)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
pwrite.Close()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if err := c.ytdl.Download(write, videoUrl, format.FormatID, index); err != nil {
|
go func() {
|
||||||
slog.Error("Failed to download", slog.String("error", err.Error()))
|
defer wg.Done()
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
write.Close()
|
_, err = io.Copy(w, pread)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Failed to copy", slog.String("error", err.Error()))
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
<div class="d-flex flex-column flex-lg-row justify-content-center gap-5 mt-5">
|
<div class="d-flex flex-column flex-lg-row justify-content-center gap-5 mt-5">
|
||||||
<div class="d-flex justify-content-center">
|
<div class="d-flex justify-content-center">
|
||||||
<img src="{{$video.Meta.Thumbnail}}" alt="{{$video.Meta.Title}}" style="max-height: 25rem; max-width: 100%; margin: 0 auto" />
|
<img src="{{.Meta.Thumbnail}}" alt="{{.Meta.Title}}" style="max-height: 25rem; max-width: 100%; margin: 0 auto" />
|
||||||
</div>
|
</div>
|
||||||
<div class="downloads flex-lg-grow-1">
|
<div class="downloads flex-lg-grow-1">
|
||||||
{{range $index, $format := $video.Formats}}
|
{{range $index, $format := $video.Formats}}
|
||||||
|
@ -25,18 +25,12 @@
|
||||||
</div>
|
</div>
|
||||||
{{if gt (len $video.OtherFormats) 0}}
|
{{if gt (len $video.OtherFormats) 0}}
|
||||||
<div class="d-grid my-3">
|
<div class="d-grid my-3">
|
||||||
<button
|
<button class="btn btn-secondary" data-bs-toggle="collapse"
|
||||||
type="button"
|
data-bs-target="#{{$root.Meta.ID}}-{{$vidIndex}}-collapse">
|
||||||
class="btn btn-secondary"
|
|
||||||
data-bs-toggle="collapse"
|
|
||||||
data-bs-target="#collapse-{{$root.Meta.ID}}-{{$vidIndex}}"
|
|
||||||
aria-expanded="false"
|
|
||||||
aria-controls="collapse-{{$root.Meta.ID}}-{{$vidIndex}}"
|
|
||||||
>
|
|
||||||
See More Formats
|
See More Formats
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="collapse-{{$root.Meta.ID}}-{{$vidIndex}}" class="collapse">
|
<div id="{{$root.Meta.ID}}-{{$vidIndex}}-collapse" class="collapse">
|
||||||
<div class="downloads d-flex d-md-grid flex-column">
|
<div class="downloads d-flex d-md-grid flex-column">
|
||||||
{{range $index, $format := $video.OtherFormats}}
|
{{range $index, $format := $video.OtherFormats}}
|
||||||
{{$label := sprintf "ext: %s, resolution: %s, filesize: %s, note: %s" $format.Ext $format.Resolution
|
{{$label := sprintf "ext: %s, resolution: %s, filesize: %s, note: %s" $format.Ext $format.Resolution
|
||||||
|
|
|
@ -2,7 +2,6 @@ package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"path"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -12,7 +11,7 @@ import (
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Env string `mapstructure:"env"`
|
Env string `mapstructure:"env"`
|
||||||
Ytdlp ConfigYtdlp `mapstructure:"ytdlp"`
|
BinaryPath string `mapstructure:"binaryPath"`
|
||||||
HTTP ConfigHTTP `mapstructure:"http"`
|
HTTP ConfigHTTP `mapstructure:"http"`
|
||||||
Cache ConfigCache `mapstructure:"cache"`
|
Cache ConfigCache `mapstructure:"cache"`
|
||||||
Cookies ConfigCookies `mapstructure:"cookies"`
|
Cookies ConfigCookies `mapstructure:"cookies"`
|
||||||
|
@ -30,10 +29,6 @@ func (c *Config) IsStaging() bool {
|
||||||
return c.Env == "Staging"
|
return c.Env == "Staging"
|
||||||
}
|
}
|
||||||
|
|
||||||
type ConfigYtdlp struct {
|
|
||||||
BinaryPath string `mapstructure:"binaryPath"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConfigHTTP struct {
|
type ConfigHTTP struct {
|
||||||
Port int `mapstructure:"port"`
|
Port int `mapstructure:"port"`
|
||||||
Listen string `mapstructure:"listen"`
|
Listen string `mapstructure:"listen"`
|
||||||
|
@ -62,7 +57,7 @@ type ConfigCookiesFromBrowser struct {
|
||||||
func DefaultConfig() *Config {
|
func DefaultConfig() *Config {
|
||||||
return &Config{
|
return &Config{
|
||||||
Env: "Production",
|
Env: "Production",
|
||||||
Ytdlp: ConfigYtdlp{BinaryPath: "yt-dlp"},
|
BinaryPath: "yt-dlp",
|
||||||
HTTP: ConfigHTTP{
|
HTTP: ConfigHTTP{
|
||||||
Port: 8080,
|
Port: 8080,
|
||||||
Listen: "127.0.0.1",
|
Listen: "127.0.0.1",
|
||||||
|
@ -81,44 +76,46 @@ func DefaultConfig() *Config {
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadConfig(paths ...string) (*Config, error) {
|
func LoadConfig(paths ...string) (*Config, error) {
|
||||||
viper.SetEnvPrefix("YTDL")
|
v := viper.New()
|
||||||
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
|
||||||
viper.AutomaticEnv()
|
|
||||||
|
|
||||||
viper.SetConfigName("config")
|
v.SetEnvPrefix("YTDL")
|
||||||
viper.SetConfigType("yaml")
|
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||||
|
v.AutomaticEnv()
|
||||||
|
|
||||||
|
v.SetConfigName("config")
|
||||||
|
v.SetConfigType("yaml")
|
||||||
|
|
||||||
if len(paths) > 0 {
|
if len(paths) > 0 {
|
||||||
for _, p := range paths {
|
for _, path := range paths {
|
||||||
viper.AddConfigPath(path.Dir(p))
|
v.AddConfigPath(path)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
envDir := os.Getenv("YTDL_CONFIGDIR")
|
envDir := os.Getenv("YTDL_CONFIGDIR")
|
||||||
if envDir != "" {
|
if envDir != "" {
|
||||||
viper.AddConfigPath(envDir)
|
v.AddConfigPath(envDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
viper.AddConfigPath(".")
|
v.AddConfigPath(".")
|
||||||
|
|
||||||
homeDir, err := os.UserHomeDir()
|
homeDir, err := os.UserHomeDir()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
viper.AddConfigPath(homeDir + "/.config/ytdl-web")
|
v.AddConfigPath(homeDir + "/.config/ytdl-web")
|
||||||
}
|
}
|
||||||
|
|
||||||
viper.AddConfigPath(xdg.ConfigHome + "/ytdl-web")
|
v.AddConfigPath(xdg.ConfigHome + "/ytdl-web")
|
||||||
for _, dir := range xdg.ConfigDirs {
|
for _, dir := range xdg.ConfigDirs {
|
||||||
viper.AddConfigPath(dir + "/ytdl-web")
|
v.AddConfigPath(dir + "/ytdl-web")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := viper.ReadInConfig(); err != nil {
|
if err := v.ReadInConfig(); err != nil {
|
||||||
if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
|
if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
config := DefaultConfig()
|
config := DefaultConfig()
|
||||||
if err := viper.Unmarshal(config); err != nil {
|
if err := v.Unmarshal(config); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -1,6 +1,6 @@
|
||||||
module go.fifitido.net/ytdl-web
|
module go.fifitido.net/ytdl-web
|
||||||
|
|
||||||
go 1.22
|
go 1.21
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/adrg/xdg v0.4.0
|
github.com/adrg/xdg v0.4.0
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
# https://devenv.sh/basics/
|
# https://devenv.sh/basics/
|
||||||
env.NAME = "ytdl-web";
|
env.NAME = "ytdl-web";
|
||||||
env.BINARY_OUT = "./out/ytdl-web";
|
env.BINARY_OUT = "./out/ytdl-web";
|
||||||
env.VERSION = "v1.2.3";
|
env.VERSION = "v1.2.2";
|
||||||
env.VERSION_PKG = "go.fifitido.net/ytdl-web/version";
|
env.VERSION_PKG = "go.fifitido.net/ytdl-web/version";
|
||||||
env.DOCKER_REGISTRY = "git.fifitido.net";
|
env.DOCKER_REGISTRY = "git.fifitido.net";
|
||||||
env.DOCKER_ORG = "apps";
|
env.DOCKER_ORG = "apps";
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
package ytdl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"os/exec"
|
||||||
|
|
||||||
|
"go.fifitido.net/ytdl-web/pkg/ytdl/metadata"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Exec(binary, url string, options ...Option) error {
|
||||||
|
opts := Options{
|
||||||
|
args: []string{url},
|
||||||
|
stdin: nil,
|
||||||
|
stdout: nil,
|
||||||
|
stderr: nil,
|
||||||
|
output: nil,
|
||||||
|
}
|
||||||
|
for _, opt := range options {
|
||||||
|
if err := opt(&opts); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command(binary, opts.args...)
|
||||||
|
|
||||||
|
if opts.stdin != nil {
|
||||||
|
cmd.Stdin = opts.stdin
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.stdout != nil {
|
||||||
|
cmd.Stdout = opts.stdout
|
||||||
|
}
|
||||||
|
|
||||||
|
var stderr = new(bytes.Buffer)
|
||||||
|
if opts.stderr != nil {
|
||||||
|
cmd.Stderr = stderr
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return &Error{
|
||||||
|
stderr: stderr.String(),
|
||||||
|
child: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, bufOk := opts.stdout.(*bytes.Buffer)
|
||||||
|
meta, metaOk := opts.output.(*metadata.Metadata)
|
||||||
|
if bufOk && metaOk {
|
||||||
|
if err := json.Unmarshal(buf.Bytes(), meta); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
package ytdl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"go.fifitido.net/ytdl-web/pkg/ytdl/metadata"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
args []string
|
||||||
|
stdin io.Reader
|
||||||
|
stdout io.Writer
|
||||||
|
stderr io.Writer
|
||||||
|
output interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Option func(*Options) error
|
||||||
|
|
||||||
|
func WithFormat(format string) Option {
|
||||||
|
return func(opts *Options) error {
|
||||||
|
opts.args = append(opts.args, "--format", format)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithCookieFile(cookieFile string) Option {
|
||||||
|
return func(opts *Options) error {
|
||||||
|
opts.args = append(opts.args, "--cookies", cookieFile)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithDumpJson(meta *metadata.Metadata) Option {
|
||||||
|
return func(opts *Options) error {
|
||||||
|
opts.args = append(opts.args, "--dump-single-json")
|
||||||
|
opts.stdout = new(bytes.Buffer)
|
||||||
|
opts.output = meta
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithLoadJson(metadata *metadata.Metadata) Option {
|
||||||
|
return func(opts *Options) error {
|
||||||
|
opts.args = append(opts.args, "--load-info-json", "-")
|
||||||
|
|
||||||
|
json, err := json.Marshal(metadata)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
opts.stdin = bytes.NewBuffer(json)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithBrowserCookies(browser, keyring, profile, container string) Option {
|
||||||
|
return func(opts *Options) error {
|
||||||
|
var sb strings.Builder
|
||||||
|
sb.WriteString(browser)
|
||||||
|
|
||||||
|
if keyring != "" {
|
||||||
|
sb.WriteByte('+')
|
||||||
|
sb.WriteString(keyring)
|
||||||
|
}
|
||||||
|
|
||||||
|
if profile != "" {
|
||||||
|
sb.WriteByte(':')
|
||||||
|
sb.WriteString(profile)
|
||||||
|
}
|
||||||
|
|
||||||
|
if container != "" {
|
||||||
|
sb.WriteByte(':')
|
||||||
|
sb.WriteByte(':')
|
||||||
|
sb.WriteString(container)
|
||||||
|
}
|
||||||
|
|
||||||
|
opts.args = append(opts.args, "--cookies-from-browser", sb.String())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithStreamOutput(output io.Writer) Option {
|
||||||
|
return func(opts *Options) error {
|
||||||
|
opts.args = append(opts.args, "--output", "-")
|
||||||
|
opts.stdout = output
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithPlaylistIndex(index int) Option {
|
||||||
|
return func(opts *Options) error {
|
||||||
|
opts.args = append(opts.args, "--playlist-items", fmt.Sprint(index+1))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
package ytdl
|
package ytdl
|
||||||
|
|
||||||
type Error struct {
|
type Error struct {
|
||||||
stdout string
|
|
||||||
stderr string
|
stderr string
|
||||||
child error
|
child error
|
||||||
}
|
}
|
||||||
|
@ -10,10 +9,6 @@ func (e *Error) Error() string {
|
||||||
return e.child.Error()
|
return e.child.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Error) Stdout() string {
|
|
||||||
return e.stdout
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Error) Stderr() string {
|
func (e *Error) Stderr() string {
|
||||||
return e.stderr
|
return e.stderr
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,7 @@ package ytdl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -29,7 +28,7 @@ type ytdlImpl struct {
|
||||||
|
|
||||||
func NewYtdl(cfg *config.Config, logger *slog.Logger, cache cache.MetadataCache) Ytdl {
|
func NewYtdl(cfg *config.Config, logger *slog.Logger, cache cache.MetadataCache) Ytdl {
|
||||||
cmd := exec.Command(
|
cmd := exec.Command(
|
||||||
cfg.Ytdlp.BinaryPath,
|
cfg.BinaryPath,
|
||||||
"--version",
|
"--version",
|
||||||
)
|
)
|
||||||
var out bytes.Buffer
|
var out bytes.Buffer
|
||||||
|
@ -44,44 +43,28 @@ func NewYtdl(cfg *config.Config, logger *slog.Logger, cache cache.MetadataCache)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildBrowserCookieString(browser, keyring, profile, container string) string {
|
func (y *ytdlImpl) baseOptions(url string) []Option {
|
||||||
var sb strings.Builder
|
options := []Option{}
|
||||||
sb.WriteString(browser)
|
|
||||||
|
|
||||||
if keyring != "" {
|
metadata, err := y.cache.Get(url)
|
||||||
sb.WriteByte('+')
|
if err == nil {
|
||||||
sb.WriteString(keyring)
|
options = append(options, WithLoadJson(metadata))
|
||||||
}
|
}
|
||||||
|
|
||||||
if profile != "" {
|
|
||||||
sb.WriteByte(':')
|
|
||||||
sb.WriteString(profile)
|
|
||||||
}
|
|
||||||
|
|
||||||
if container != "" {
|
|
||||||
sb.WriteByte(':')
|
|
||||||
sb.WriteByte(':')
|
|
||||||
sb.WriteString(container)
|
|
||||||
}
|
|
||||||
|
|
||||||
return sb.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (y *ytdlImpl) appendCookieArgs(args []string) []string {
|
|
||||||
if y.cfg.Cookies.Enabled {
|
if y.cfg.Cookies.Enabled {
|
||||||
if y.cfg.Cookies.FromBrowser.Browser != "" {
|
if y.cfg.Cookies.FromBrowser.Browser != "" {
|
||||||
args = append(args, "--cookies-from-browser", buildBrowserCookieString(
|
options = append(options, WithBrowserCookies(
|
||||||
y.cfg.Cookies.FromBrowser.Browser,
|
y.cfg.Cookies.FromBrowser.Browser,
|
||||||
y.cfg.Cookies.FromBrowser.Keyring,
|
y.cfg.Cookies.FromBrowser.Keyring,
|
||||||
y.cfg.Cookies.FromBrowser.Profile,
|
y.cfg.Cookies.FromBrowser.Profile,
|
||||||
y.cfg.Cookies.FromBrowser.Container,
|
y.cfg.Cookies.FromBrowser.Container,
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
args = append(args, "--cookies", y.cfg.Cookies.FilePath)
|
options = append(options, WithCookieFile(y.cfg.Cookies.FilePath))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return args
|
return options
|
||||||
}
|
}
|
||||||
|
|
||||||
func (y *ytdlImpl) Version() string {
|
func (y *ytdlImpl) Version() string {
|
||||||
|
@ -95,29 +78,24 @@ func (y *ytdlImpl) GetMetadata(url string) (*metadata.Metadata, error) {
|
||||||
return meta, nil
|
return meta, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
args := []string{
|
meta = &metadata.Metadata{}
|
||||||
url,
|
options := append(
|
||||||
"--dump-single-json",
|
y.baseOptions(url),
|
||||||
}
|
WithDumpJson(meta),
|
||||||
|
)
|
||||||
|
|
||||||
args = y.appendCookieArgs(args)
|
if err := Exec(y.cfg.BinaryPath, url, options...); err != nil {
|
||||||
|
|
||||||
cmd := exec.Command(y.cfg.Ytdlp.BinaryPath, args...)
|
|
||||||
|
|
||||||
out, err := cmd.Output()
|
|
||||||
if err != nil {
|
|
||||||
attrs := []any{
|
attrs := []any{
|
||||||
slog.String("url", url),
|
slog.String("url", url),
|
||||||
slog.String("error", err.Error()),
|
slog.String("error", err.Error()),
|
||||||
}
|
}
|
||||||
|
|
||||||
y.logger.Error("failed to get metadata", attrs...)
|
var ytdlErr *Error
|
||||||
return nil, err
|
if ok := errors.As(err, &ytdlErr); ok {
|
||||||
|
attrs = append(attrs, slog.String("stderr", ytdlErr.Stderr()))
|
||||||
}
|
}
|
||||||
|
|
||||||
meta = &metadata.Metadata{}
|
y.logger.Error("failed to get metadata", attrs...)
|
||||||
if err := json.Unmarshal(out, meta); err != nil {
|
|
||||||
y.logger.Error("failed to unmarshal metadata", slog.String("url", url), slog.String("error", err.Error()))
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,37 +108,17 @@ func (y *ytdlImpl) GetMetadata(url string) (*metadata.Metadata, error) {
|
||||||
|
|
||||||
// Download implements Ytdl
|
// Download implements Ytdl
|
||||||
func (y *ytdlImpl) Download(w io.Writer, url, format string, index int) error {
|
func (y *ytdlImpl) Download(w io.Writer, url, format string, index int) error {
|
||||||
args := []string{
|
options := append(
|
||||||
url,
|
y.baseOptions(url),
|
||||||
"--format", format,
|
WithFormat(format),
|
||||||
"--output", "-",
|
WithStreamOutput(w),
|
||||||
}
|
)
|
||||||
|
|
||||||
if index >= 0 {
|
if index >= 0 {
|
||||||
args = append(args, "--playlist-ites", fmt.Sprint(index+1))
|
options = append(options, WithPlaylistIndex(index))
|
||||||
}
|
}
|
||||||
|
|
||||||
args = y.appendCookieArgs(args)
|
if err := Exec(y.cfg.BinaryPath, url, options...); err != nil {
|
||||||
|
|
||||||
metadata, err := y.cache.Get(url)
|
|
||||||
if err == nil {
|
|
||||||
args = append(args, "--load-info-json", "-")
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := exec.Command(y.cfg.Ytdlp.BinaryPath, args...)
|
|
||||||
cmd.Stdout = w
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
json, err := json.Marshal(metadata)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.Stdin = bytes.NewReader(json)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
y.logger.Error("failed to download", slog.String("url", url), slog.String("error", err.Error()))
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue