Skip to content

Commit da0ed3b

Browse files
committed
Use ytdlp to get the correct playlist thumbnail
1 parent 8befae9 commit da0ed3b

File tree

4 files changed

+74
-8
lines changed

4 files changed

+74
-8
lines changed

pkg/builder/builder.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ type Builder interface {
1313
Build(ctx context.Context, cfg *feed.Config) (*model.Feed, error)
1414
}
1515

16-
func New(ctx context.Context, provider model.Provider, key string) (Builder, error) {
16+
func New(ctx context.Context, provider model.Provider, key string, downloader Downloader) (Builder, error) {
1717
switch provider {
1818
case model.ProviderYoutube:
19-
return NewYouTubeBuilder(key)
19+
return NewYouTubeBuilder(key, downloader)
2020
case model.ProviderVimeo:
2121
return NewVimeoBuilder(ctx, key)
2222
case model.ProviderSoundcloud:

pkg/builder/youtube.go

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@ import (
1616
"google.golang.org/api/youtube/v3"
1717

1818
"github.com/mxpv/podsync/pkg/model"
19+
"github.com/mxpv/podsync/pkg/ytdl"
1920
)
21+
type Downloader interface {
22+
PlaylistMetadata(ctx context.Context, url string) (metadata ytdl.PlaylistMetadata, err error)
23+
}
2024

2125
const (
2226
maxYoutubeResults = 50
@@ -35,6 +39,7 @@ func (key apiKey) Get() (string, string) {
3539
type YouTubeBuilder struct {
3640
client *youtube.Service
3741
key apiKey
42+
downloader Downloader
3843
}
3944

4045
// Cost: 5 units (call method: 1, snippet: 2, contentDetails: 2)
@@ -225,8 +230,17 @@ func (yt *YouTubeBuilder) queryFeed(ctx context.Context, feed *model.Feed, info
225230
} else { // nolint:golint
226231
feed.PubDate = date
227232
}
228-
229-
thumbnails = playlist.Snippet.Thumbnails
233+
metadata, err := yt.downloader.PlaylistMetadata(ctx, feed.ItemURL)
234+
if err != nil {
235+
return errors.Wrapf(err, "failed to get playlist metadata for %s", feed.ItemURL)
236+
}
237+
log.Infof("Playlist metadata: %v", metadata)
238+
if(len(metadata.Thumbnails) > 0) {
239+
// best qualtiy thumbnail is the last one
240+
feed.CoverArt = metadata.Thumbnails[len(metadata.Thumbnails)-1].Url
241+
} else {
242+
thumbnails = playlist.Snippet.Thumbnails
243+
}
230244

231245
default:
232246
return errors.New("unsupported link format")
@@ -235,9 +249,9 @@ func (yt *YouTubeBuilder) queryFeed(ctx context.Context, feed *model.Feed, info
235249
if feed.Description == "" {
236250
feed.Description = fmt.Sprintf("%s (%s)", feed.Title, feed.PubDate)
237251
}
238-
252+
if feed.CoverArt == "" {
239253
feed.CoverArt = yt.selectThumbnail(thumbnails, feed.CoverArtQuality, "")
240-
254+
}
241255
return nil
242256
}
243257

@@ -457,7 +471,7 @@ func (yt *YouTubeBuilder) Build(ctx context.Context, cfg *feed.Config) (*model.F
457471
return _feed, nil
458472
}
459473

460-
func NewYouTubeBuilder(key string) (*YouTubeBuilder, error) {
474+
func NewYouTubeBuilder(key string, ytdlp Downloader) (*YouTubeBuilder, error) {
461475
if key == "" {
462476
return nil, errors.New("empty YouTube API key")
463477
}
@@ -467,5 +481,5 @@ func NewYouTubeBuilder(key string) (*YouTubeBuilder, error) {
467481
return nil, errors.Wrap(err, "failed to create youtube client")
468482
}
469483

470-
return &YouTubeBuilder{client: yt, key: apiKey(key)}, nil
484+
return &YouTubeBuilder{client: yt, key: apiKey(key), downloader:ytdlp}, nil
471485
}

pkg/ytdl/ytdl.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package ytdl
22

33
import (
4+
"encoding/json"
45
"context"
56
"fmt"
67
"io"
@@ -24,6 +25,26 @@ const (
2425
UpdatePeriod = 24 * time.Hour
2526
)
2627

28+
type PlaylistMetadataThumbnail struct {
29+
Id string `json:"id"`
30+
Url string `json:"url"`
31+
Resolution string `json:"resolution"`
32+
Width int `json:"width"`
33+
Height int `json:"height"`
34+
}
35+
36+
type PlaylistMetadata struct {
37+
Id string `json:"id"`
38+
Title string `json:"title"`
39+
Description string `json:"description"`
40+
Thumbnails []PlaylistMetadataThumbnail `json:"thumbnails"`
41+
Channel string `json:"channel"`
42+
ChannelId string `json:"channel_id"`
43+
ChannelUrl string `json:"channel_url"`
44+
WebpageUrl string `json:"webpage_url"`
45+
46+
}
47+
2748
var (
2849
ErrTooManyRequests = errors.New(http.StatusText(http.StatusTooManyRequests))
2950
)
@@ -156,6 +177,36 @@ func (dl *YoutubeDl) Update(ctx context.Context) error {
156177
return nil
157178
}
158179

180+
181+
func (dl *YoutubeDl) PlaylistMetadata(ctx context.Context, url string) (metadata PlaylistMetadata, err error) {
182+
log.Info("getting playlist metadata for: ", url)
183+
args := []string{
184+
"--playlist-items", "0",
185+
"-J", // JSON output
186+
"-q", // quiet mode
187+
"--no-warnings", // suppress warnings
188+
url,
189+
}
190+
dl.updateLock.Lock()
191+
defer dl.updateLock.Unlock()
192+
output, err := dl.exec(ctx, args...)
193+
if err != nil {
194+
log.WithError(err).Errorf("youtube-dl error: %s", url)
195+
196+
// YouTube might block host with HTTP Error 429: Too Many Requests
197+
if strings.Contains(output, "HTTP Error 429") {
198+
return PlaylistMetadata{}, ErrTooManyRequests
199+
}
200+
201+
log.Error(output)
202+
return PlaylistMetadata{}, errors.New(output)
203+
}
204+
205+
var playlistMetadata PlaylistMetadata
206+
json.Unmarshal([]byte(output), &playlistMetadata)
207+
return playlistMetadata, nil
208+
}
209+
159210
func (dl *YoutubeDl) Download(ctx context.Context, feedConfig *feed.Config, episode *model.Episode) (r io.ReadCloser, err error) {
160211
tmpDir, err := os.MkdirTemp("", "podsync-")
161212
if err != nil {

services/update/updater.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323

2424
type Downloader interface {
2525
Download(ctx context.Context, feedConfig *feed.Config, episode *model.Episode) (io.ReadCloser, error)
26+
PlaylistMetadata(ctx context.Context, url string) (metadata ytdl.PlaylistMetadata, err error)
2627
}
2728

2829
type TokenList []string

0 commit comments

Comments
 (0)