From 39f68d3fa69e5dde2f1049f77b44654949afa513 Mon Sep 17 00:00:00 2001 From: JJ Style Date: Sun, 21 Apr 2024 14:51:06 +0100 Subject: [PATCH] feat(server): Itunes tags (#24) * feat(rss): add itunes tags * add todo to remove feeds replace directive --- .../internal/biz/globalplayer/usecase_test.go | 32 +++++++++++++++++-- go.mod | 4 +++ go.sum | 6 ++-- pkg/globalplayer/feeds/feeds.go | 16 ++++++++++ 4 files changed, 53 insertions(+), 5 deletions(-) diff --git a/cmd/gobal-player-server/internal/biz/globalplayer/usecase_test.go b/cmd/gobal-player-server/internal/biz/globalplayer/usecase_test.go index a185e26..356a0eb 100644 --- a/cmd/gobal-player-server/internal/biz/globalplayer/usecase_test.go +++ b/cmd/gobal-player-server/internal/biz/globalplayer/usecase_test.go @@ -360,7 +360,7 @@ func Test_useCase_GetEpisodesFeed(t *testing.T) { args: args{stationSlug: "station", showId: "show"}, setup: func(f *fields) { f.gp.EXPECT().GetEpisodes("station", "show"). - Return([]*models.Episode{{Id: "id", Name: "episode 1", Description: "episode", StreamUrl: "episode.mp3"}}, nil) + Return([]*models.Episode{{Id: "id", Name: "episode 1", Description: "episode", StreamUrl: "episode.mp3", Duration: "00:30:00"}}, nil) f.gp.EXPECT(). GetShows("station"). @@ -372,7 +372,14 @@ func Test_useCase_GetEpisodesFeed(t *testing.T) { want: &feeds.Feed{ Title: "show", Description: "episode", + Subtitle: "episode", Image: &feeds.Image{Url: "show.jpg"}, + ITunes: &feeds.ITunesFeed{ + Explicit: false, + Type: feeds.ITunesFeedTypeEpisodic, + Title: "show", + Image: &feeds.ITunesImage{Href: "show.jpg"}, + }, Items: []*feeds.Item{ { Id: "id", @@ -380,6 +387,10 @@ func Test_useCase_GetEpisodesFeed(t *testing.T) { Description: "episode

Available until Monday 01 January 0001 00:00:00.", Enclosure: &feeds.Enclosure{Url: "episode.mp3", Type: "audio/mpeg", Length: "100"}, Link: &feeds.Link{Href: "episode.mp3"}, + ITunes: &feeds.ITunesItem{ + Duration: "30m0s", + EpisodeType: feeds.ITunesEpisodeTypeFull, + }, }, }, }, @@ -457,10 +468,10 @@ func Test_useCase_GetAllShowsFeed(t *testing.T) { }, nil) f.gp.EXPECT().GetEpisodes("station", "show1"). - Return([]*models.Episode{{Id: "show1id1", Name: "show 1 episode 1", Description: "show 1 episode 1", StreamUrl: "s1ep1.mp3"}}, nil) + Return([]*models.Episode{{Id: "show1id1", Name: "show 1 episode 1", Description: "show 1 episode 1", StreamUrl: "s1ep1.mp3", Duration: "00:30:00"}}, nil) f.gp.EXPECT().GetEpisodes("station", "show2"). - Return([]*models.Episode{{Id: "show2id1", Name: "show 2 episode 1", Description: "show 2 episode 1", StreamUrl: "s2ep1.mp3"}}, nil) + Return([]*models.Episode{{Id: "show2id1", Name: "show 2 episode 1", Description: "show 2 episode 1", StreamUrl: "s2ep1.mp3", Duration: "00:30:00"}}, nil) expectReq1, _ := http.NewRequest(http.MethodHead, "s1ep1.mp3", nil) f.hc.EXPECT().Do(expectReq1).Return(&http.Response{ContentLength: 100}, nil) @@ -471,7 +482,14 @@ func Test_useCase_GetAllShowsFeed(t *testing.T) { want: &feeds.Feed{ Title: "station", Description: "a cool station", + Subtitle: "a cool station", Image: &feeds.Image{Url: "station.jpg"}, + ITunes: &feeds.ITunesFeed{ + Image: &feeds.ITunesImage{Href: "station.jpg"}, + Explicit: false, + Type: feeds.ITunesFeedTypeEpisodic, + Title: "station", + }, Items: []*feeds.Item{ { Id: "show1id1", @@ -479,6 +497,10 @@ func Test_useCase_GetAllShowsFeed(t *testing.T) { Description: "show 1 episode 1

Available until Monday 01 January 0001 00:00:00.", Enclosure: &feeds.Enclosure{Url: "s1ep1.mp3", Type: "audio/mpeg", Length: "100"}, Link: &feeds.Link{Href: "s1ep1.mp3"}, + ITunes: &feeds.ITunesItem{ + Duration: "30m0s", + EpisodeType: feeds.ITunesEpisodeTypeFull, + }, }, { Id: "show2id1", @@ -486,6 +508,10 @@ func Test_useCase_GetAllShowsFeed(t *testing.T) { Description: "show 2 episode 1

Available until Monday 01 January 0001 00:00:00.", Enclosure: &feeds.Enclosure{Url: "s2ep1.mp3", Type: "audio/mpeg", Length: "200"}, Link: &feeds.Link{Href: "s2ep1.mp3"}, + ITunes: &feeds.ITunesItem{ + Duration: "30m0s", + EpisodeType: feeds.ITunesEpisodeTypeFull, + }, }, }, }, diff --git a/go.mod b/go.mod index 5028147..9842184 100644 --- a/go.mod +++ b/go.mod @@ -2,8 +2,12 @@ module github.com/jj-style/gobal-player go 1.22.0 +// TODO: remove when https://github.com/gorilla/feeds/pull/110 complete +replace github.com/gorilla/feeds => github.com/jj-style/feeds v0.0.0-20240404232609-6cdbd9ef1ed0 + require ( github.com/adrg/libvlc-go/v3 v3.1.5 + github.com/dannav/hhmmss v1.0.0 github.com/eko/gocache/lib/v4 v4.1.5 github.com/eko/gocache/store/go_cache/v4 v4.2.1 github.com/gdamore/tcell/v2 v2.7.4 diff --git a/go.sum b/go.sum index 201fd59..d55fc04 100644 --- a/go.sum +++ b/go.sum @@ -27,6 +27,8 @@ github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpV github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0= github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= +github.com/dannav/hhmmss v1.0.0 h1:/FjTOHXSEOuQIWwPs4abUS6s42ndAGhnVo17VbGnCMA= +github.com/dannav/hhmmss v1.0.0/go.mod h1:LXyJMlU/lUpkUB4Mj5xQr3Ad1YQb7jBLajgzuKqpaV0= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -76,14 +78,14 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/wire v0.6.0 h1:HBkoIh4BdSxoyo9PveV8giw7ZsaBOvzWKfcg/6MrVwI= github.com/google/wire v0.6.0/go.mod h1:F4QhpQ9EDIdJ1Mbop/NZBRB+5yrR6qg3BnctaoUk6NA= -github.com/gorilla/feeds v1.1.2 h1:pxzZ5PD3RJdhFH2FsJJ4x6PqMqbgFk1+Vez4XWBW8Iw= -github.com/gorilla/feeds v1.1.2/go.mod h1:WMib8uJP3BbY+X8Szd1rA5Pzhdfh+HCCAYT2z7Fza6Y= github.com/hashicorp/hcl v1.0.1-vault-5 h1:kI3hhbbyzr4dldA8UdTb7ZlVVlI2DACdCfz31RPDgJM= github.com/hashicorp/hcl v1.0.1-vault-5/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= github.com/influxdata/influxdb-client-go/v2 v2.13.0 h1:ioBbLmR5NMbAjP4UVA5r9b5xGjpABD7j65pI8kFphDM= github.com/influxdata/influxdb-client-go/v2 v2.13.0/go.mod h1:k+spCbt9hcvqvUiz0sr5D8LolXHqAAOfPw9v/RIRHl4= github.com/influxdata/line-protocol v0.0.0-20210922203350-b1ad95c89adf h1:7JTmneyiNEwVBOHSjoMxiWAqB992atOeepeFYegn5RU= github.com/influxdata/line-protocol v0.0.0-20210922203350-b1ad95c89adf/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= +github.com/jj-style/feeds v0.0.0-20240404232609-6cdbd9ef1ed0 h1:5wD5FEULWtGopHjzpDRr3TCfKrQn6CSNZhXotYJR08I= +github.com/jj-style/feeds v0.0.0-20240404232609-6cdbd9ef1ed0/go.mod h1:WMib8uJP3BbY+X8Szd1rA5Pzhdfh+HCCAYT2z7Fza6Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= diff --git a/pkg/globalplayer/feeds/feeds.go b/pkg/globalplayer/feeds/feeds.go index 8e59b3c..51965cc 100644 --- a/pkg/globalplayer/feeds/feeds.go +++ b/pkg/globalplayer/feeds/feeds.go @@ -5,6 +5,7 @@ import ( "net/http" "time" + "github.com/dannav/hhmmss" "github.com/gorilla/feeds" "github.com/jj-style/gobal-player/pkg/globalplayer/models" "github.com/jj-style/gobal-player/pkg/resty" @@ -19,6 +20,13 @@ func ToFeed(hc resty.HttpClient, show *models.Show, episodes []*models.Episode, Image: &feeds.Image{Url: show.ImageUrl}, Updated: lo.MaxBy(episodes, func(a, b *models.Episode) bool { return b.Aired.After(a.Aired) }).Aired, Description: description, + Subtitle: description, + ITunes: &feeds.ITunesFeed{ + Title: show.Name, + Image: &feeds.ITunesImage{Href: show.ImageUrl}, + Type: feeds.ITunesFeedTypeEpisodic, + Explicit: false, + }, } feedItems := make([]*feeds.Item, len(episodes)) @@ -27,6 +35,10 @@ func ToFeed(hc resty.HttpClient, show *models.Show, episodes []*models.Episode, idx := idx item := item g.Go(func() error { + duration, err := hhmmss.Parse(item.Duration) + if err != nil { + return fmt.Errorf("parsing episode duration '%s': %v", item.Duration, err) + } lengthChan := lo.Async(func() int64 { headReq, _ := http.NewRequest(http.MethodHead, item.StreamUrl, nil) @@ -50,6 +62,10 @@ func ToFeed(hc resty.HttpClient, show *models.Show, episodes []*models.Episode, Created: item.Aired, Description: fmt.Sprintf("%s

Available until %s.", item.Description, item.Until.Format("Monday 02 January 2006 15:04:05")), Enclosure: &feeds.Enclosure{Url: item.StreamUrl, Type: "audio/mpeg", Length: fmt.Sprint(contentLength)}, + ITunes: &feeds.ITunesItem{ + Duration: fmt.Sprint(duration), + EpisodeType: feeds.ITunesEpisodeTypeFull, + }, } return nil })