package ytdlp import ( "bufio" "context" "encoding/json" "errors" "io" "os" "os/exec" "strings" "github.com/royalcat/ctxprogress" "golang.org/x/sync/errgroup" ) // Progress implements ctxprogress.Progress. func (p Entry) Progress() (current int, total int) { return int(p.PlaylistIndex), int(p.PlaylistCount) } // func (p PlaylistEntry) Url() string { // if p.URL != "" { // return p.URL // } // if p.WebpageURL != "" { // return p.WebpageURL // } // if p.OriginalURL != "" { // return p.OriginalURL // } // return "" // } func (yt *Client) Playlist(ctx context.Context, url string) ([]Entry, error) { group, ctx := errgroup.WithContext(ctx) w, lines, err := lineReader(group) if err != nil { return nil, err } cmd := exec.CommandContext(ctx, yt.binary, "-j", url) cmd.Stdout = w group.Go(func() error { err := cmd.Run() if err != nil { return err } return w.Close() }) playlists := []Entry{} for line := range lines { entry := Entry{} err = json.Unmarshal([]byte(line), &entry) if err != nil { return nil, err } playlists = append(playlists, entry) } return playlists, nil } func lineReader(group *errgroup.Group) (io.WriteCloser, <-chan string, error) { lines := make(chan string) var r io.Reader r, w := io.Pipe() r = io.TeeReader(r, os.Stdout) group.Go(func() error { defer close(lines) reader := bufio.NewReader(r) for { line, err := reader.ReadString('\n') if err != nil { if err == io.EOF { return w.Close() } return errors.Join(err, w.Close()) } line = strings.Trim(line, " \r\t") if line == "" { continue } lines <- line } }) return w, lines, nil } var _ ctxprogress.Progress = (*Entry)(nil) var _ ctxprogress.Progress = (*DownloadProgress)(nil) func parseProgress(line string) (ctxprogress.Progress, error) { line = strings.Trim(line, " \r\t") p := &DownloadProgress{} err := json.Unmarshal([]byte(line), p) if err != nil { return nil, err } return p, nil }