2020-03-07 17:25:55 -08:00
|
|
|
package feed
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
2020-06-07 22:10:03 -07:00
|
|
|
"sort"
|
2020-03-07 17:25:55 -08:00
|
|
|
"strconv"
|
2022-01-02 13:17:28 +02:00
|
|
|
"strings"
|
2020-03-07 17:25:55 -08:00
|
|
|
"time"
|
|
|
|
|
|
|
|
itunes "github.com/eduncan911/podcast"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
|
|
|
|
"github.com/mxpv/podsync/pkg/model"
|
|
|
|
)
|
|
|
|
|
2020-06-07 22:10:03 -07:00
|
|
|
// sort.Interface implementation
|
|
|
|
type timeSlice []*model.Episode
|
|
|
|
|
|
|
|
func (p timeSlice) Len() int {
|
|
|
|
return len(p)
|
|
|
|
}
|
|
|
|
|
|
|
|
// In descending order
|
|
|
|
func (p timeSlice) Less(i, j int) bool {
|
|
|
|
return p[i].PubDate.After(p[j].PubDate)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p timeSlice) Swap(i, j int) {
|
|
|
|
p[i], p[j] = p[j], p[i]
|
|
|
|
}
|
|
|
|
|
2022-01-02 14:57:10 +02:00
|
|
|
func Build(_ctx context.Context, feed *model.Feed, cfg *Config, hostname string) (*itunes.Podcast, error) {
|
2020-03-07 17:25:55 -08:00
|
|
|
const (
|
|
|
|
podsyncGenerator = "Podsync generator (support us at https://github.com/mxpv/podsync)"
|
|
|
|
defaultCategory = "TV & Film"
|
|
|
|
)
|
|
|
|
|
2020-03-07 22:32:51 -08:00
|
|
|
var (
|
2020-10-01 22:51:47 +02:00
|
|
|
now = time.Now().UTC()
|
|
|
|
author = feed.Title
|
|
|
|
title = feed.Title
|
|
|
|
description = feed.Description
|
2020-03-07 22:32:51 -08:00
|
|
|
)
|
2020-03-07 17:25:55 -08:00
|
|
|
|
2020-10-01 22:51:47 +02:00
|
|
|
if cfg.Custom.Author != "" {
|
|
|
|
author = cfg.Custom.Author
|
|
|
|
}
|
|
|
|
|
|
|
|
if cfg.Custom.Title != "" {
|
|
|
|
title = cfg.Custom.Title
|
|
|
|
}
|
|
|
|
|
|
|
|
if cfg.Custom.Description != "" {
|
|
|
|
description = cfg.Custom.Description
|
|
|
|
}
|
|
|
|
|
|
|
|
p := itunes.New(title, feed.ItemURL, description, &feed.PubDate, &now)
|
2020-03-07 17:25:55 -08:00
|
|
|
p.Generator = podsyncGenerator
|
2020-10-01 22:51:47 +02:00
|
|
|
p.AddSubTitle(title)
|
|
|
|
p.IAuthor = author
|
|
|
|
p.AddSummary(description)
|
2020-03-07 17:25:55 -08:00
|
|
|
|
2022-04-02 10:34:33 +02:00
|
|
|
if feed.PrivateFeed {
|
2022-04-02 10:28:22 +02:00
|
|
|
p.IBlock = "yes"
|
|
|
|
}
|
|
|
|
|
2020-10-28 18:24:23 -04:00
|
|
|
if cfg.Custom.OwnerName != "" && cfg.Custom.OwnerEmail != "" {
|
|
|
|
p.IOwner = &itunes.Author{
|
|
|
|
Name: cfg.Custom.OwnerName,
|
|
|
|
Email: cfg.Custom.OwnerEmail,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-07 22:32:51 -08:00
|
|
|
if cfg.Custom.CoverArt != "" {
|
|
|
|
p.AddImage(cfg.Custom.CoverArt)
|
|
|
|
} else {
|
|
|
|
p.AddImage(feed.CoverArt)
|
|
|
|
}
|
|
|
|
|
|
|
|
if cfg.Custom.Category != "" {
|
2020-10-04 14:00:51 -07:00
|
|
|
p.AddCategory(cfg.Custom.Category, cfg.Custom.Subcategories)
|
2020-03-07 22:32:51 -08:00
|
|
|
} else {
|
2020-10-04 14:00:51 -07:00
|
|
|
p.AddCategory(defaultCategory, cfg.Custom.Subcategories)
|
2020-03-07 22:32:51 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
if cfg.Custom.Explicit {
|
2020-03-07 17:25:55 -08:00
|
|
|
p.IExplicit = "yes"
|
|
|
|
} else {
|
|
|
|
p.IExplicit = "no"
|
|
|
|
}
|
|
|
|
|
2020-03-07 22:32:51 -08:00
|
|
|
if cfg.Custom.Language != "" {
|
|
|
|
p.Language = cfg.Custom.Language
|
2020-03-07 17:25:55 -08:00
|
|
|
}
|
|
|
|
|
2020-06-07 22:10:03 -07:00
|
|
|
for _, episode := range feed.Episodes {
|
|
|
|
if episode.PubDate.IsZero() {
|
|
|
|
episode.PubDate = now
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sort all episodes in descending order
|
|
|
|
sort.Sort(timeSlice(feed.Episodes))
|
|
|
|
|
2020-03-07 17:25:55 -08:00
|
|
|
for i, episode := range feed.Episodes {
|
|
|
|
if episode.Status != model.EpisodeDownloaded {
|
|
|
|
// Skip episodes that are not yet downloaded
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
item := itunes.Item{
|
|
|
|
GUID: episode.ID,
|
|
|
|
Link: episode.VideoURL,
|
|
|
|
Title: episode.Title,
|
|
|
|
Description: episode.Description,
|
|
|
|
ISubtitle: episode.Title,
|
2020-06-07 22:10:03 -07:00
|
|
|
// Some app prefer 1-based order
|
|
|
|
IOrder: strconv.Itoa(i + 1),
|
2020-03-07 17:25:55 -08:00
|
|
|
}
|
|
|
|
|
2020-06-07 22:10:03 -07:00
|
|
|
item.AddPubDate(&episode.PubDate)
|
2020-03-07 17:25:55 -08:00
|
|
|
item.AddSummary(episode.Description)
|
|
|
|
item.AddImage(episode.Thumbnail)
|
|
|
|
item.AddDuration(episode.Duration)
|
|
|
|
|
|
|
|
enclosureType := itunes.MP4
|
|
|
|
if feed.Format == model.FormatAudio {
|
2020-04-05 13:09:40 +02:00
|
|
|
enclosureType = itunes.MP3
|
2020-03-07 17:25:55 -08:00
|
|
|
}
|
|
|
|
|
2022-01-02 13:17:28 +02:00
|
|
|
var (
|
|
|
|
episodeName = EpisodeName(cfg, episode)
|
|
|
|
downloadURL = fmt.Sprintf("%s/%s/%s", strings.TrimRight(hostname, "/"), cfg.ID, episodeName)
|
|
|
|
)
|
2020-03-07 17:25:55 -08:00
|
|
|
|
|
|
|
item.AddEnclosure(downloadURL, enclosureType, episode.Size)
|
|
|
|
|
|
|
|
// p.AddItem requires description to be not empty, use workaround
|
|
|
|
if item.Description == "" {
|
|
|
|
item.Description = " "
|
|
|
|
}
|
|
|
|
|
2020-03-07 22:32:51 -08:00
|
|
|
if cfg.Custom.Explicit {
|
2020-03-07 17:25:55 -08:00
|
|
|
item.IExplicit = "yes"
|
|
|
|
} else {
|
|
|
|
item.IExplicit = "no"
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, err := p.AddItem(item); err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "failed to add item to podcast (id %q)", episode.ID)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return &p, nil
|
|
|
|
}
|
|
|
|
|
2022-01-02 14:57:10 +02:00
|
|
|
func EpisodeName(feedConfig *Config, episode *model.Episode) string {
|
2020-03-07 17:25:55 -08:00
|
|
|
ext := "mp4"
|
|
|
|
if feedConfig.Format == model.FormatAudio {
|
|
|
|
ext = "mp3"
|
|
|
|
}
|
|
|
|
|
|
|
|
return fmt.Sprintf("%s.%s", episode.ID, ext)
|
|
|
|
}
|