Fix config loading and fix/simplify yt-dlp command handling
This commit is contained in:
		
							parent
							
								
									47caf17973
								
							
						
					
					
						commit
						681225c2ae
					
				| 
						 | 
				
			
			@ -5,7 +5,6 @@ import (
 | 
			
		|||
	"io"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"sync"
 | 
			
		||||
 | 
			
		||||
	"github.com/go-chi/chi/v5"
 | 
			
		||||
	"github.com/spf13/viper"
 | 
			
		||||
| 
						 | 
				
			
			@ -109,6 +108,10 @@ func (c *DownloadController) ListDownloadLinks(w http.ResponseWriter, r *http.Re
 | 
			
		|||
	}, layout...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	BUF_LEN = 1024
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (c *DownloadController) ProxyDownload(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	videoUrl, ok := c.getUrlParam(r)
 | 
			
		||||
	if !ok {
 | 
			
		||||
| 
						 | 
				
			
			@ -159,33 +162,19 @@ func (c *DownloadController) ProxyDownload(w http.ResponseWriter, r *http.Reques
 | 
			
		|||
		index = -1
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pread, pwrite := io.Pipe()
 | 
			
		||||
	var wg sync.WaitGroup
 | 
			
		||||
	wg.Add(2)
 | 
			
		||||
	read, write := io.Pipe()
 | 
			
		||||
 | 
			
		||||
	go func() {
 | 
			
		||||
		defer wg.Done()
 | 
			
		||||
 | 
			
		||||
		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()
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	go func() {
 | 
			
		||||
		defer wg.Done()
 | 
			
		||||
 | 
			
		||||
		_, err = io.Copy(w, pread)
 | 
			
		||||
		_, err := io.Copy(w, read)
 | 
			
		||||
		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()
 | 
			
		||||
	if err := c.ytdl.Download(write, 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)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	write.Close()
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,6 +2,7 @@ package config
 | 
			
		|||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"path"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -10,11 +11,11 @@ import (
 | 
			
		|||
)
 | 
			
		||||
 | 
			
		||||
type Config struct {
 | 
			
		||||
	Env        string        `mapstructure:"env"`
 | 
			
		||||
	BinaryPath string        `mapstructure:"binaryPath"`
 | 
			
		||||
	HTTP       ConfigHTTP    `mapstructure:"http"`
 | 
			
		||||
	Cache      ConfigCache   `mapstructure:"cache"`
 | 
			
		||||
	Cookies    ConfigCookies `mapstructure:"cookies"`
 | 
			
		||||
	Env     string        `mapstructure:"env"`
 | 
			
		||||
	Ytdlp   ConfigYtdlp   `mapstructure:"ytdlp"`
 | 
			
		||||
	HTTP    ConfigHTTP    `mapstructure:"http"`
 | 
			
		||||
	Cache   ConfigCache   `mapstructure:"cache"`
 | 
			
		||||
	Cookies ConfigCookies `mapstructure:"cookies"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *Config) IsProduction() bool {
 | 
			
		||||
| 
						 | 
				
			
			@ -29,6 +30,10 @@ func (c *Config) IsStaging() bool {
 | 
			
		|||
	return c.Env == "Staging"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ConfigYtdlp struct {
 | 
			
		||||
	BinaryPath string `mapstructure:"binaryPath"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ConfigHTTP struct {
 | 
			
		||||
	Port           int      `mapstructure:"port"`
 | 
			
		||||
	Listen         string   `mapstructure:"listen"`
 | 
			
		||||
| 
						 | 
				
			
			@ -56,8 +61,8 @@ type ConfigCookiesFromBrowser struct {
 | 
			
		|||
 | 
			
		||||
func DefaultConfig() *Config {
 | 
			
		||||
	return &Config{
 | 
			
		||||
		Env:        "Production",
 | 
			
		||||
		BinaryPath: "yt-dlp",
 | 
			
		||||
		Env:   "Production",
 | 
			
		||||
		Ytdlp: ConfigYtdlp{BinaryPath: "yt-dlp"},
 | 
			
		||||
		HTTP: ConfigHTTP{
 | 
			
		||||
			Port:     8080,
 | 
			
		||||
			Listen:   "127.0.0.1",
 | 
			
		||||
| 
						 | 
				
			
			@ -76,46 +81,46 @@ func DefaultConfig() *Config {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
func LoadConfig(paths ...string) (*Config, error) {
 | 
			
		||||
	v := viper.New()
 | 
			
		||||
	viper.SetEnvPrefix("YTDL")
 | 
			
		||||
	viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
 | 
			
		||||
	viper.AutomaticEnv()
 | 
			
		||||
 | 
			
		||||
	v.SetEnvPrefix("YTDL")
 | 
			
		||||
	v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
 | 
			
		||||
	v.AutomaticEnv()
 | 
			
		||||
 | 
			
		||||
	v.SetConfigName("config")
 | 
			
		||||
	v.SetConfigType("yaml")
 | 
			
		||||
	viper.SetConfigName("config")
 | 
			
		||||
	viper.SetConfigType("yaml")
 | 
			
		||||
 | 
			
		||||
	if len(paths) > 0 {
 | 
			
		||||
		for _, path := range paths {
 | 
			
		||||
			v.AddConfigPath(path)
 | 
			
		||||
		for _, p := range paths {
 | 
			
		||||
			viper.AddConfigPath(path.Dir(p))
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		envDir := os.Getenv("YTDL_CONFIGDIR")
 | 
			
		||||
		if envDir != "" {
 | 
			
		||||
			v.AddConfigPath(envDir)
 | 
			
		||||
			viper.AddConfigPath(envDir)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		v.AddConfigPath(".")
 | 
			
		||||
		viper.AddConfigPath(".")
 | 
			
		||||
 | 
			
		||||
		homeDir, err := os.UserHomeDir()
 | 
			
		||||
		if err == nil {
 | 
			
		||||
			v.AddConfigPath(homeDir + "/.config/ytdl-web")
 | 
			
		||||
			viper.AddConfigPath(homeDir + "/.config/ytdl-web")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		v.AddConfigPath(xdg.ConfigHome + "/ytdl-web")
 | 
			
		||||
		viper.AddConfigPath(xdg.ConfigHome + "/ytdl-web")
 | 
			
		||||
		for _, dir := range xdg.ConfigDirs {
 | 
			
		||||
			v.AddConfigPath(dir + "/ytdl-web")
 | 
			
		||||
			viper.AddConfigPath(dir + "/ytdl-web")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := v.ReadInConfig(); err != nil {
 | 
			
		||||
	if err := viper.ReadInConfig(); err != nil {
 | 
			
		||||
		if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	viper.Debug()
 | 
			
		||||
 | 
			
		||||
	config := DefaultConfig()
 | 
			
		||||
	if err := v.Unmarshal(config); err != nil {
 | 
			
		||||
	if err := viper.Unmarshal(config); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,56 +0,0 @@
 | 
			
		|||
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
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,99 +0,0 @@
 | 
			
		|||
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,14 +0,0 @@
 | 
			
		|||
package ytdl
 | 
			
		||||
 | 
			
		||||
type Error struct {
 | 
			
		||||
	stderr string
 | 
			
		||||
	child  error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (e *Error) Error() string {
 | 
			
		||||
	return e.child.Error()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (e *Error) Stderr() string {
 | 
			
		||||
	return e.stderr
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										102
									
								
								pkg/ytdl/ytdl.go
								
								
								
								
							
							
						
						
									
										102
									
								
								pkg/ytdl/ytdl.go
								
								
								
								
							| 
						 | 
				
			
			@ -2,7 +2,8 @@ package ytdl
 | 
			
		|||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"strings"
 | 
			
		||||
| 
						 | 
				
			
			@ -28,7 +29,7 @@ type ytdlImpl struct {
 | 
			
		|||
 | 
			
		||||
func NewYtdl(cfg *config.Config, logger *slog.Logger, cache cache.MetadataCache) Ytdl {
 | 
			
		||||
	cmd := exec.Command(
 | 
			
		||||
		cfg.BinaryPath,
 | 
			
		||||
		cfg.Ytdlp.BinaryPath,
 | 
			
		||||
		"--version",
 | 
			
		||||
	)
 | 
			
		||||
	var out bytes.Buffer
 | 
			
		||||
| 
						 | 
				
			
			@ -43,28 +44,44 @@ func NewYtdl(cfg *config.Config, logger *slog.Logger, cache cache.MetadataCache)
 | 
			
		|||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (y *ytdlImpl) baseOptions(url string) []Option {
 | 
			
		||||
	options := []Option{}
 | 
			
		||||
func buildBrowserCookieString(browser, keyring, profile, container string) string {
 | 
			
		||||
	var sb strings.Builder
 | 
			
		||||
	sb.WriteString(browser)
 | 
			
		||||
 | 
			
		||||
	metadata, err := y.cache.Get(url)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		options = append(options, WithLoadJson(metadata))
 | 
			
		||||
	if keyring != "" {
 | 
			
		||||
		sb.WriteByte('+')
 | 
			
		||||
		sb.WriteString(keyring)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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.FromBrowser.Browser != "" {
 | 
			
		||||
			options = append(options, WithBrowserCookies(
 | 
			
		||||
			args = append(args, "--cookies-from-browser", buildBrowserCookieString(
 | 
			
		||||
				y.cfg.Cookies.FromBrowser.Browser,
 | 
			
		||||
				y.cfg.Cookies.FromBrowser.Keyring,
 | 
			
		||||
				y.cfg.Cookies.FromBrowser.Profile,
 | 
			
		||||
				y.cfg.Cookies.FromBrowser.Container,
 | 
			
		||||
			))
 | 
			
		||||
		} else {
 | 
			
		||||
			options = append(options, WithCookieFile(y.cfg.Cookies.FilePath))
 | 
			
		||||
			args = append(args, "--cookies", y.cfg.Cookies.FilePath)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return options
 | 
			
		||||
	return args
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (y *ytdlImpl) Version() string {
 | 
			
		||||
| 
						 | 
				
			
			@ -78,27 +95,32 @@ func (y *ytdlImpl) GetMetadata(url string) (*metadata.Metadata, error) {
 | 
			
		|||
		return meta, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	meta = &metadata.Metadata{}
 | 
			
		||||
	options := append(
 | 
			
		||||
		y.baseOptions(url),
 | 
			
		||||
		WithDumpJson(meta),
 | 
			
		||||
	)
 | 
			
		||||
	args := []string{
 | 
			
		||||
		url,
 | 
			
		||||
		"--dump-single-json",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := Exec(y.cfg.BinaryPath, url, options...); err != nil {
 | 
			
		||||
	args = y.appendCookieArgs(args)
 | 
			
		||||
 | 
			
		||||
	cmd := exec.Command(y.cfg.Ytdlp.BinaryPath, args...)
 | 
			
		||||
 | 
			
		||||
	out, err := cmd.Output()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		attrs := []any{
 | 
			
		||||
			slog.String("url", url),
 | 
			
		||||
			slog.String("error", err.Error()),
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var ytdlErr *Error
 | 
			
		||||
		if ok := errors.As(err, &ytdlErr); ok {
 | 
			
		||||
			attrs = append(attrs, slog.String("stderr", ytdlErr.Stderr()))
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		y.logger.Error("failed to get metadata", attrs...)
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	meta = &metadata.Metadata{}
 | 
			
		||||
	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
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := y.cache.Set(url, meta, y.cfg.Cache.TTL); err != nil {
 | 
			
		||||
		y.logger.Warn("failed to cache metadata", slog.String("url", url), slog.String("error", err.Error()))
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -108,17 +130,37 @@ func (y *ytdlImpl) GetMetadata(url string) (*metadata.Metadata, error) {
 | 
			
		|||
 | 
			
		||||
// Download implements Ytdl
 | 
			
		||||
func (y *ytdlImpl) Download(w io.Writer, url, format string, index int) error {
 | 
			
		||||
	options := append(
 | 
			
		||||
		y.baseOptions(url),
 | 
			
		||||
		WithFormat(format),
 | 
			
		||||
		WithStreamOutput(w),
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	if index >= 0 {
 | 
			
		||||
		options = append(options, WithPlaylistIndex(index))
 | 
			
		||||
	args := []string{
 | 
			
		||||
		url,
 | 
			
		||||
		"--format", format,
 | 
			
		||||
		"--output", "-",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := Exec(y.cfg.BinaryPath, url, options...); err != nil {
 | 
			
		||||
	if index >= 0 {
 | 
			
		||||
		args = append(args, "--playlist-ites", fmt.Sprint(index+1))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	args = y.appendCookieArgs(args)
 | 
			
		||||
 | 
			
		||||
	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
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue