Skip to content

Commit

Permalink
Initial SoundCloud playlist support
Browse files Browse the repository at this point in the history
  • Loading branch information
unshuffled authored and mxpv committed Oct 10, 2021
1 parent da93686 commit c1130f3
Show file tree
Hide file tree
Showing 7 changed files with 183 additions and 3 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ require (
github.com/silentsokolov/go-vimeo v0.0.0-20190116124215-06829264260c
github.com/sirupsen/logrus v1.2.0
github.com/stretchr/testify v1.4.0
github.com/zackradisic/soundcloud-api v0.1.5
golang.org/x/net v0.0.0-20190620200207-3b0461eec859
golang.org/x/oauth2 v0.0.0-20180620175406-ef147856a6dd
golang.org/x/sync v0.0.0-20190423024810-112230192c58
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ github.com/golang/mock v1.4.3 h1:GV+pQPG/EUUbkh47niozDcADz6go/dUwhVzdUQHIVRw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/grafov/m3u8 v0.11.1 h1:igZ7EBIB2IAsPPazKwRKdbhxcoBKO3lO1UY57PZDeNA=
github.com/grafov/m3u8 v0.11.1/go.mod h1:nqzOkfBiZJENr52zTVd/Dcl03yzphIMbJqkXGu+u080=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o=
Expand Down Expand Up @@ -76,6 +78,8 @@ github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJy
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/zackradisic/soundcloud-api v0.1.5 h1:OVg8XlbNjrSpSAJhIdBDjUC/Vm1lQYPrDOhnNnImNGg=
github.com/zackradisic/soundcloud-api v0.1.5/go.mod h1:ycGIZFVZdUVC7B8pcfgze1bRBePPmjYlIGnRptKByQ0=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 h1:u+LnwYTOOW7Ukr/fppxEb1Nwz0AtPflrblfvUudpo+I=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
Expand Down
2 changes: 2 additions & 0 deletions pkg/builder/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ func New(ctx context.Context, provider model.Provider, key string) (Builder, err
return NewYouTubeBuilder(key)
case model.ProviderVimeo:
return NewVimeoBuilder(ctx, key)
case model.ProviderSoundcloud:
return NewSoundcloudBuilder()
default:
return nil, errors.Errorf("unsupported provider %q", provider)
}
Expand Down
96 changes: 96 additions & 0 deletions pkg/builder/soundcloud.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package builder

import (
"strconv"
"time"

"github.com/pkg/errors"
soundcloudapi "github.com/zackradisic/soundcloud-api"
"golang.org/x/net/context"

"github.com/mxpv/podsync/pkg/config"
"github.com/mxpv/podsync/pkg/model"
)

type SoundCloudBuilder struct {
client *soundcloudapi.API
}

func (s *SoundCloudBuilder) Build(ctx context.Context, cfg *config.Feed) (*model.Feed, error) {
info, err := ParseURL(cfg.URL)
if err != nil {
return nil, err
}

feed := &model.Feed{
ItemID: info.ItemID,
Provider: info.Provider,
LinkType: info.LinkType,
Format: cfg.Format,
Quality: cfg.Quality,
PageSize: cfg.PageSize,
UpdatedAt: time.Now().UTC(),
}

if info.LinkType == model.TypePlaylist {
if soundcloudapi.IsPlaylistURL(cfg.URL) {
scplaylist, err := s.client.GetPlaylistInfo(cfg.URL)
if err != nil {
return nil, err
}

feed.Title = scplaylist.Title
feed.Description = scplaylist.Description
feed.ItemURL = cfg.URL

date, err := time.Parse(time.RFC3339, scplaylist.CreatedAt)
if err == nil {
feed.PubDate = date
}
feed.Author = scplaylist.User.Username
feed.CoverArt = scplaylist.ArtworkURL

var added = 0
for _, track := range scplaylist.Tracks {
pubDate, _ := time.Parse(time.RFC3339, track.CreatedAt)
var (
videoID = strconv.FormatInt(track.ID, 10)
duration = track.DurationMS / 1000
mediaURL = track.PermalinkURL
trackSize = track.DurationMS * 15 // very rough estimate
)

feed.Episodes = append(feed.Episodes, &model.Episode{
ID: videoID,
Title: track.Title,
Description: track.Description,
Duration: duration,
Size: trackSize,
VideoURL: mediaURL,
PubDate: pubDate,
Thumbnail: track.ArtworkURL,
Status: model.EpisodeNew,
})

added++

if added >= feed.PageSize {
return feed, nil
}
}

return feed, nil
}
}

return nil, errors.New(("unsupported soundcloud feed type"))
}

func NewSoundcloudBuilder() (*SoundCloudBuilder, error) {
sc, err := soundcloudapi.New(soundcloudapi.APIOptions{})
if err != nil {
return nil, errors.Wrap(err, "failed to create soundcloud client")
}

return &SoundCloudBuilder{client: sc}, nil
}
42 changes: 42 additions & 0 deletions pkg/builder/soundcloud_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package builder

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/mxpv/podsync/pkg/config"
)

func TestSC_BUILDFEED(t *testing.T) {
builder, err := NewSoundcloudBuilder()
require.NoError(t, err)

urls := []string{
"https://soundcloud.com/moby/sets/remixes",
"https://soundcloud.com/npr/sets/soundscapes",
}

for _, addr := range urls {
t.Run(addr, func(t *testing.T) {
feed, err := builder.Build(testCtx, &config.Feed{URL: addr})
require.NoError(t, err)

assert.NotEmpty(t, feed.Title)
assert.NotEmpty(t, feed.Description)
assert.NotEmpty(t, feed.Author)
assert.NotEmpty(t, feed.ItemURL)

assert.NotZero(t, len(feed.Episodes))

for _, item := range feed.Episodes {
assert.NotEmpty(t, item.Title)
assert.NotEmpty(t, item.VideoURL)
assert.NotZero(t, item.Duration)
assert.NotEmpty(t, item.Title)
assert.NotEmpty(t, item.Thumbnail)
}
})
}
}
34 changes: 34 additions & 0 deletions pkg/builder/url.go
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,19 @@ func ParseURL(link string) (model.Info, error) {
return info, nil
}

if strings.HasSuffix(parsed.Host, "soundcloud.com") {
kind, id, err := parseSoundcloudURL(parsed)
if err != nil {
return model.Info{}, err
}

info.Provider = model.ProviderSoundcloud
info.LinkType = kind
info.ItemID = id

return info, nil
}

return model.Info{}, errors.New("unsupported URL host")
}

Expand Down Expand Up @@ -152,3 +165,24 @@ func parseVimeoURL(parsed *url.URL) (model.Type, string, error) {

return "", "", errors.New("unsupported link format")
}

func parseSoundcloudURL(parsed *url.URL) (model.Type, string, error) {
parts := strings.Split(parsed.EscapedPath(), "/")
if len(parts) <= 3 {
return "", "", errors.New("invald soundcloud link path")
}

var kind model.Type

// - https://soundcloud.com/user/sets/example-set
switch parts[2] {
case "sets":
kind = model.TypePlaylist
default:
return "", "", errors.New("invalid soundcloud url, missing sets")
}

id := parts[3]

return kind, id, nil
}
7 changes: 4 additions & 3 deletions pkg/model/link.go
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@ const (
type Provider string

const (
ProviderYoutube = Provider("youtube")
ProviderVimeo = Provider("vimeo")
ProviderYoutube = Provider("youtube")
ProviderVimeo = Provider("vimeo")
ProviderSoundcloud = Provider("soundcloud")
)

// Info represents data extracted from URL
type Info struct {
LinkType Type // Either group, channel or user
Provider Provider // Youtube or Vimeo
Provider Provider // Youtube, Vimeo, or SoundCloud
ItemID string
}

0 comments on commit c1130f3

Please sign in to comment.