2024-05-13 16:56:20 +00:00
|
|
|
package ytdlp
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
2024-06-14 22:14:44 +00:00
|
|
|
"io"
|
2024-05-13 16:56:20 +00:00
|
|
|
"os/exec"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/royalcat/ctxprogress"
|
|
|
|
"golang.org/x/sync/errgroup"
|
|
|
|
)
|
|
|
|
|
|
|
|
type DownloadStatus string
|
|
|
|
|
|
|
|
const (
|
|
|
|
StatusDownloading DownloadStatus = "downloading"
|
|
|
|
StatusFinished DownloadStatus = "finished"
|
|
|
|
StatusErrored DownloadStatus = "error"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Progress for the Running call
|
|
|
|
type DownloadProgress struct {
|
|
|
|
Status DownloadStatus `json:"status"`
|
|
|
|
Filename string `json:"filename"`
|
|
|
|
TmpFilename string `json:"tmpfilename"`
|
|
|
|
DownloadedBytes int64 `json:"downloaded_bytes"`
|
|
|
|
TotalBytes int64 `json:"total_bytes"`
|
|
|
|
TotalBytesEstimate float64 `json:"total_bytes_estimate"`
|
|
|
|
Elapsed float64 `json:"elapsed"`
|
|
|
|
ETA float64 `json:"eta"`
|
|
|
|
Speed float64 `json:"speed"`
|
|
|
|
FragmentIndex int64 `json:"fragment_index"`
|
|
|
|
FragmentCount int64 `json:"fragment_count"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// Current implements ctxprogress.Progress.
|
|
|
|
func (d DownloadProgress) Progress() (int, int) {
|
|
|
|
if d.TotalBytes != -1 && d.TotalBytes != 0 && d.DownloadedBytes != -1 {
|
|
|
|
return int(d.DownloadedBytes), int(d.TotalBytes)
|
|
|
|
}
|
|
|
|
|
|
|
|
if d.TotalBytesEstimate != -1 && d.TotalBytesEstimate != 0 && d.DownloadedBytes != -1 {
|
|
|
|
return int(d.DownloadedBytes), int(d.TotalBytesEstimate)
|
|
|
|
}
|
|
|
|
|
|
|
|
return int(d.FragmentIndex), int(d.FragmentCount)
|
|
|
|
}
|
|
|
|
|
|
|
|
const rawProgressTemplate = `download:
|
|
|
|
%{
|
|
|
|
"status":"%(progress.status)s",
|
|
|
|
"eta":%(progress.eta|-1)s,
|
|
|
|
"speed":%(progress.speed|0)s,
|
|
|
|
"downloaded_bytes":%(progress.downloaded_bytes|-1)s,
|
|
|
|
"total_bytes": %(progress.total_bytes|-1)s,
|
|
|
|
"total_bytes_estimate": %(progress.total_bytes_estimate|-1)s,
|
|
|
|
"fragment_index":%(progress.fragment_index|-1)s,
|
|
|
|
"fragment_count":%(progress.fragment_count|-1)s
|
|
|
|
}`
|
|
|
|
|
|
|
|
var progressTemplate = strings.NewReplacer("\n", "", "\t", "", " ", "").Replace(rawProgressTemplate)
|
|
|
|
|
2024-06-14 22:14:44 +00:00
|
|
|
func (c *Client) Download(ctx context.Context, url string, w io.Writer) error {
|
2024-05-13 16:56:20 +00:00
|
|
|
args := []string{
|
|
|
|
"--progress", "--newline", "--progress-template", progressTemplate,
|
2024-06-14 22:14:44 +00:00
|
|
|
"-o", "-",
|
2024-05-13 16:56:20 +00:00
|
|
|
url,
|
|
|
|
}
|
|
|
|
|
|
|
|
group, ctx := errgroup.WithContext(ctx)
|
|
|
|
|
2024-06-14 22:14:44 +00:00
|
|
|
stderr, lines, err := lineReader(group)
|
2024-05-13 16:56:20 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
cmd := exec.CommandContext(ctx, c.binary, args...)
|
|
|
|
|
|
|
|
cmd.Stdout = w
|
2024-06-14 22:14:44 +00:00
|
|
|
cmd.Stderr = stderr
|
2024-05-13 16:56:20 +00:00
|
|
|
|
|
|
|
group.Go(func() error {
|
|
|
|
err := cmd.Run()
|
2024-06-14 22:14:44 +00:00
|
|
|
stderr.Close()
|
2024-05-13 16:56:20 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
for line := range lines {
|
|
|
|
if line, ok := strings.CutPrefix(line, "%"); ok {
|
|
|
|
p := DownloadProgress{}
|
|
|
|
err = json.Unmarshal([]byte(line), &p)
|
|
|
|
if err != nil {
|
|
|
|
//TODO: handle error
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
ctxprogress.Set(ctx, p)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-14 22:14:44 +00:00
|
|
|
return group.Wait()
|
2024-05-13 16:56:20 +00:00
|
|
|
}
|