tstor/pkg/ytdlp/playlist.go

181 lines
5.2 KiB
Go
Raw Normal View History

2024-05-13 16:56:20 +00:00
package ytdlp
import (
"bufio"
"context"
"encoding/json"
"errors"
"io"
"os"
"os/exec"
"strings"
"github.com/royalcat/ctxprogress"
"golang.org/x/sync/errgroup"
)
type PlaylistEntry struct {
ID string `json:"id"`
Uploader string `json:"uploader"`
UploaderID string `json:"uploader_id"`
UploadDate string `json:"upload_date"`
Title string `json:"title"`
Thumbnail string `json:"thumbnail"`
Duration int64 `json:"duration"`
LikeCount int64 `json:"like_count"`
DislikeCount int64 `json:"dislike_count"`
CommentCount int64 `json:"comment_count"`
Formats []Format `json:"formats"`
AgeLimit int64 `json:"age_limit"`
Tags []string `json:"tags"`
Categories []string `json:"categories"`
Cast []any `json:"cast"`
Subtitles Subtitles `json:"subtitles"`
Thumbnails []Thumbnail `json:"thumbnails"`
Timestamp int64 `json:"timestamp"`
ViewCount int64 `json:"view_count"`
WebpageURL string `json:"webpage_url"`
OriginalURL string `json:"original_url"`
WebpageURLBasename string `json:"webpage_url_basename"`
WebpageURLDomain string `json:"webpage_url_domain"`
Extractor string `json:"extractor"`
ExtractorKey string `json:"extractor_key"`
PlaylistCount int64 `json:"playlist_count"`
Playlist string `json:"playlist"`
PlaylistID string `json:"playlist_id"`
PlaylistTitle string `json:"playlist_title"`
PlaylistUploader string `json:"playlist_uploader"`
PlaylistUploaderID string `json:"playlist_uploader_id"`
NEntries int64 `json:"n_entries"`
PlaylistIndex int64 `json:"playlist_index"`
PlaylistAutonumber int64 `json:"playlist_autonumber"`
DisplayID string `json:"display_id"`
Fulltitle string `json:"fulltitle"`
DurationString string `json:"duration_string"`
ReleaseYear int `json:"release_year"`
Epoch int64 `json:"epoch"`
FormatID string `json:"format_id"`
URL string `json:"url"`
ManifestURL string `json:"manifest_url"`
Tbr float64 `json:"tbr"`
EXT EXT `json:"ext"`
FPS float64 `json:"fps"`
Protocol Protocol `json:"protocol"`
VideoHasDRM bool `json:"has_drm"`
Width int64 `json:"width"`
Height int64 `json:"height"`
Vcodec string `json:"vcodec"`
Acodec string `json:"acodec"`
DynamicRange DynamicRange `json:"dynamic_range"`
Resolution string `json:"resolution"`
AspectRatio float64 `json:"aspect_ratio"`
HTTPHeaders HTTPHeaders `json:"http_headers"`
VideoEXT EXT `json:"video_ext"`
AudioEXT AudioEXT `json:"audio_ext"`
Format string `json:"format"`
Filename string `json:"_filename"`
VideoFilename string `json:"filename"`
Type string `json:"_type"`
Version Version `json:"_version"`
2024-05-13 16:56:20 +00:00
}
// Progress implements ctxprogress.Progress.
func (p PlaylistEntry) 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 ""
2024-05-13 16:56:20 +00:00
}
func (yt *Client) Playlist(ctx context.Context, url string) ([]PlaylistEntry, 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 := []PlaylistEntry{}
for line := range lines {
entry := PlaylistEntry{}
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 = (*PlaylistEntry)(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
}