Documentation
¶
Overview ¶
Package podcast generates a fully compliant iTunes and RSS 2.0 podcast feed for GoLang using a simple API.
Full documentation with detailed examples located at https://godoc.org/github.com/eduncan911/podcast
Usage ¶
To use, `go get` and `import` the package like your typical GoLang library.
$ go get -u github.com/eduncan911/podcast import "github.com/eduncan911/podcast"
The API exposes a number of method receivers on structs that implements the logic required to comply with the specifications and ensure a compliant feed. A number of overrides occur to help with iTunes visibility of your episodes.
Notably, the [Podcast.AddItem(i Item)](#Podcast.AddItem) function performs most of the heavy lifting by taking the Item(#Item) input and performing validation, overrides and duplicate setters through the feed.
See the detailed Examples of the API at https://godoc.org/github.com/eduncan911/podcast.
Extensibility ¶
In no way are you restricted in having full control over your feeds. You may choose to skip the API methods and instead use the structs directly. The fields have been grouped by RSS 2.0 and iTunes fields.
iTunes specific fields are all prefixed with the letter `I`.
References ¶
RSS 2.0: https://cyber.harvard.edu/rss/rss.html
Podcasts: https://help.apple.com/itc/podcasts_connect/#/itca5b22233
Release Notes ¶
1.1.0 * Enabling CDATA in ISummary fields for Podcast and Channel.
1.0.0 * Initial release. * Full documentation, full examples and complete code coverage.
Example (HttpHandlers) ¶
// ResponseWriter example using Podcast.Encode(w io.Writer).
//
httpHandler := func(w http.ResponseWriter, r *http.Request) {
// instantiate a new Podcast
p := podcast.New(
"eduncan911 Podcasts",
"http://eduncan911.com/",
"An example Podcast",
&pubDate, &updatedDate,
)
// add some channel properties
p.AddAuthor("Jane Doe", "me@janedoe.com")
p.AddImage("http://janedoe.com/i.jpg")
p.AddSummary(`link <a href="http://example.com">example.com</a>`)
for i := int64(1); i < 3; i++ {
n := strconv.FormatInt(i, 10)
d := pubDate.AddDate(0, 0, int(i))
// create an Item
item := podcast.Item{
Title: "Episode " + n,
Link: "http://example.com/" + n + ".mp3",
Description: "Description for Episode " + n,
PubDate: &d,
}
item.AddSummary(`item <a href="http://example.com">example.com</a>`)
// add a Download to the Item
item.AddEnclosure("http://e.com/"+n+".mp3", podcast.MP3, 55*(i+1))
// add the Item and check for validation errors
if _, err := p.AddItem(item); err != nil {
fmt.Println(item.Title, ": error", err.Error())
return
}
}
// set the Content Type to that of XML
w.Header().Set("Content-Type", "application/xml")
// finally, Encode and write the Podcast to the ResponseWriter.
//
// a simple pattern is to handle any errors within this check.
// alternatively if using middleware, you can just return
// the Podcast entity as it also implements the io.Writer interface
// that complies with several middleware packages.
if err := p.Encode(w); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
rr := httptest.NewRecorder()
httpHandler(rr, nil)
os.Stdout.Write(rr.Body.Bytes())
Output: <?xml version="1.0" encoding="UTF-8"?> <rss version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"> <channel> <title>eduncan911 Podcasts</title> <link>http://eduncan911.com/</link> <description>An example Podcast</description> <generator>go podcast v1.1.0 (github.com/eduncan911/podcast)</generator> <language>en-us</language> <lastBuildDate>Mon, 06 Feb 2017 08:21:52 +0000</lastBuildDate> <managingEditor>me@janedoe.com (Jane Doe)</managingEditor> <pubDate>Sat, 04 Feb 2017 08:21:52 +0000</pubDate> <image> <url>http://janedoe.com/i.jpg</url> </image> <itunes:author>me@janedoe.com (Jane Doe)</itunes:author> <itunes:summary><![CDATA[link <a href="http://example.com">example.com</a>]]></itunes:summary> <itunes:image href="http://janedoe.com/i.jpg"></itunes:image> <item> <guid>http://e.com/1.mp3</guid> <title>Episode 1</title> <link>http://example.com/1.mp3</link> <description>Description for Episode 1</description> <pubDate>Sun, 05 Feb 2017 08:21:52 +0000</pubDate> <enclosure url="http://e.com/1.mp3" length="110" type="audio/mpeg"></enclosure> <itunes:author>me@janedoe.com (Jane Doe)</itunes:author> <itunes:summary><![CDATA[item <a href="http://example.com">example.com</a>]]></itunes:summary> <itunes:image href="http://janedoe.com/i.jpg"></itunes:image> <itunes:duration>110</itunes:duration> </item> <item> <guid>http://e.com/2.mp3</guid> <title>Episode 2</title> <link>http://example.com/2.mp3</link> <description>Description for Episode 2</description> <pubDate>Mon, 06 Feb 2017 08:21:52 +0000</pubDate> <enclosure url="http://e.com/2.mp3" length="165" type="audio/mpeg"></enclosure> <itunes:author>me@janedoe.com (Jane Doe)</itunes:author> <itunes:summary><![CDATA[item <a href="http://example.com">example.com</a>]]></itunes:summary> <itunes:image href="http://janedoe.com/i.jpg"></itunes:image> <itunes:duration>165</itunes:duration> </item> </channel> </rss>
Example (IoWriter) ¶
// instantiate a new Podcast
p := podcast.New(
"Sample Podcasts",
"http://example.com/",
"An example Podcast",
&createdDate, &updatedDate,
)
// add some channel properties
p.ISubtitle = "A simple Podcast"
p.AddSummary(`link <a href="http://example.com">example.com</a>`)
p.AddImage("http://example.com/podcast.jpg")
p.AddAuthor("Jane Doe", "jane.doe@example.com")
for i := int64(9); i < 11; i++ {
n := strconv.FormatInt(i, 10)
d := pubDate.AddDate(0, 0, int(i))
// create an Item
item := podcast.Item{
Title: "Episode " + n,
Description: "Description for Episode " + n,
ISubtitle: "A simple episode " + n,
PubDate: &d,
}
item.AddSummary(`item k <a href="http://example.com">example.com</a>`)
// add a Download to the Item
item.AddEnclosure("http://example.com/"+n+".mp3", podcast.MP3, 55*(i+1))
// add the Item and check for validation errors
if _, err := p.AddItem(item); err != nil {
os.Stderr.WriteString("item validation error: " + err.Error())
}
}
// Podcast.Encode writes to an io.Writer
if err := p.Encode(os.Stdout); err != nil {
fmt.Println("error writing to stdout:", err.Error())
}
Output: <?xml version="1.0" encoding="UTF-8"?> <rss version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"> <channel> <title>Sample Podcasts</title> <link>http://example.com/</link> <description>An example Podcast</description> <generator>go podcast v1.1.0 (github.com/eduncan911/podcast)</generator> <language>en-us</language> <lastBuildDate>Mon, 06 Feb 2017 08:21:52 +0000</lastBuildDate> <managingEditor>jane.doe@example.com (Jane Doe)</managingEditor> <pubDate>Wed, 01 Feb 2017 08:21:52 +0000</pubDate> <image> <url>http://example.com/podcast.jpg</url> </image> <itunes:author>jane.doe@example.com (Jane Doe)</itunes:author> <itunes:subtitle>A simple Podcast</itunes:subtitle> <itunes:summary><![CDATA[link <a href="http://example.com">example.com</a>]]></itunes:summary> <itunes:image href="http://example.com/podcast.jpg"></itunes:image> <item> <guid>http://example.com/9.mp3</guid> <title>Episode 9</title> <link>http://example.com/9.mp3</link> <description>Description for Episode 9</description> <pubDate>Mon, 13 Feb 2017 08:21:52 +0000</pubDate> <enclosure url="http://example.com/9.mp3" length="550" type="audio/mpeg"></enclosure> <itunes:author>jane.doe@example.com (Jane Doe)</itunes:author> <itunes:subtitle>A simple episode 9</itunes:subtitle> <itunes:summary><![CDATA[item k <a href="http://example.com">example.com</a>]]></itunes:summary> <itunes:image href="http://example.com/podcast.jpg"></itunes:image> <itunes:duration>550</itunes:duration> </item> <item> <guid>http://example.com/10.mp3</guid> <title>Episode 10</title> <link>http://example.com/10.mp3</link> <description>Description for Episode 10</description> <pubDate>Tue, 14 Feb 2017 08:21:52 +0000</pubDate> <enclosure url="http://example.com/10.mp3" length="605" type="audio/mpeg"></enclosure> <itunes:author>jane.doe@example.com (Jane Doe)</itunes:author> <itunes:subtitle>A simple episode 10</itunes:subtitle> <itunes:summary><![CDATA[item k <a href="http://example.com">example.com</a>]]></itunes:summary> <itunes:image href="http://example.com/podcast.jpg"></itunes:image> <itunes:duration>605</itunes:duration> </item> </channel> </rss>
Index ¶
- type Author
- type Enclosure
- type EnclosureType
- type ICategory
- type IImage
- type ISummary
- type Image
- type Item
- type Podcast
- func (p *Podcast) AddAuthor(name, email string)
- func (p *Podcast) AddCategory(category string, subCategories []string)
- func (p *Podcast) AddImage(url string)
- func (p *Podcast) AddItem(i Item) (int, error)
- func (p *Podcast) AddSummary(summary string)
- func (p *Podcast) Bytes() []byte
- func (p *Podcast) Encode(w io.Writer) error
- func (p *Podcast) String() string
- type TextInput
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Author ¶
type Author struct {
XMLName xml.Name `xml:"itunes:owner"`
Name string `xml:"itunes:name"`
Email string `xml:"itunes:email"`
}
Author represents a named author and email.
For iTunes compliance, both Name and Email are required.
type Enclosure ¶
type Enclosure struct {
XMLName xml.Name `xml:"enclosure"`
URL string `xml:"url,attr"`
Length int64 `xml:"-"`
LengthFormatted string `xml:"length,attr"`
Type EnclosureType `xml:"-"`
TypeFormatted string `xml:"type,attr"`
}
Enclosure represents a download enclosure.
type EnclosureType ¶
type EnclosureType int
EnclosureType specifies the type of the enclosure.
const ( M4A EnclosureType = iota M4V MP4 MP3 MOV PDF EPUB )
EnclosureType specifies the type of the enclosure.
func (EnclosureType) String ¶
func (et EnclosureType) String() string
String returns the MIME type encoding of the specified EnclosureType.
type ICategory ¶
type ICategory struct {
XMLName xml.Name `xml:"itunes:category"`
Text string `xml:"text,attr"`
ICategories []*ICategory
}
ICategory is a 2-tier classification system for iTunes.
type IImage ¶
IImage represents an iTunes image.
Podcast feeds contain artwork that is a minimum size of 1400 x 1400 pixels and a maximum size of 3000 x 3000 pixels, 72 dpi, in JPEG or PNG format with appropriate file extensions (.jpg, .png), and in the RGB colorspace. To optimize images for mobile devices, Apple recommends compressing your image files.
type ISummary ¶ added in v1.1.0
ISummary is a 4000 character rich-text field for the itunes:summary tag.
This is rendered as CDATA which allows for HTML tags such as <a href="">.
type Image ¶
type Image struct {
XMLName xml.Name `xml:"image"`
// TODO: is it URL or Link? which is it?
URL string `xml:"url"`
Title string `xml:"title,omitempty"`
Link string `xml:"link,omitempty"`
Width int `xml:"width,omitempty"`
Height int `xml:"height,omitempty"`
}
Image represents an image.
Podcast feeds contain artwork that is a minimum size of 1400 x 1400 pixels and a maximum size of 3000 x 3000 pixels, 72 dpi, in JPEG or PNG format with appropriate file extensions (.jpg, .png), and in the RGB colorspace. To optimize images for mobile devices, Apple recommends compressing your image files.
type Item ¶
type Item struct {
XMLName xml.Name `xml:"item"`
GUID string `xml:"guid"`
Title string `xml:"title"`
Link string `xml:"link"`
Description string `xml:"description"`
Author *Author `xml:"-"`
AuthorFormatted string `xml:"author,omitempty"`
Category string `xml:"category,omitempty"`
Comments string `xml:"comments,omitempty"`
Source string `xml:"source,omitempty"`
PubDate *time.Time `xml:"-"`
PubDateFormatted string `xml:"pubDate,omitempty"`
Enclosure *Enclosure
// https://help.apple.com/itc/podcasts_connect/#/itcb54353390
IAuthor string `xml:"itunes:author,omitempty"`
ISubtitle string `xml:"itunes:subtitle,omitempty"`
ISummary *ISummary
IImage *IImage
IDuration string `xml:"itunes:duration,omitempty"`
IExplicit string `xml:"itunes:explicit,omitempty"`
IIsClosedCaptioned string `xml:"itunes:isClosedCaptioned,omitempty"`
IOrder string `xml:"itunes:order,omitempty"`
}
Item represents a single entry in a podcast.
Article minimal requirements are: - Title - Description - Link
Audio minimal requirements are: - Title - Description - Enclosure (HREF, Type and Length all required)
Recommendations: - Setting the minimal fields sets most of other fields, including iTunes. - Use the Published time.Time setting instead of PubDate. - Always set an Enclosure.Length, to be nice to your downloaders. - Use Enclosure.Type instead of setting TypeFormatted for valid extensions.
func (*Item) AddEnclosure ¶
func (i *Item) AddEnclosure( url string, enclosureType EnclosureType, lengthInSeconds int64)
AddEnclosure adds the downloadable asset to the podcast Item.
func (*Item) AddSummary ¶ added in v1.1.0
AddSummary adds the iTunes summary.
Limit: 4000 characters
Note that this field is a CDATA encoded field which allows for rich text such as html links: <a href="http://www.apple.com">Apple</a>.
type Podcast ¶
type Podcast struct {
XMLName xml.Name `xml:"channel"`
Title string `xml:"title"`
Link string `xml:"link"`
Description string `xml:"description"`
Category string `xml:"category,omitempty"`
Cloud string `xml:"cloud,omitempty"`
Copyright string `xml:"copyright,omitempty"`
Docs string `xml:"docs,omitempty"`
Generator string `xml:"generator,omitempty"`
Language string `xml:"language,omitempty"`
LastBuildDate string `xml:"lastBuildDate,omitempty"`
ManagingEditor string `xml:"managingEditor,omitempty"`
PubDate string `xml:"pubDate,omitempty"`
Rating string `xml:"rating,omitempty"`
SkipHours string `xml:"skipHours,omitempty"`
SkipDays string `xml:"skipDays,omitempty"`
TTL int `xml:"ttl,omitempty"`
WebMaster string `xml:"webMaster,omitempty"`
Image *Image
TextInput *TextInput
// https://help.apple.com/itc/podcasts_connect/#/itcb54353390
IAuthor string `xml:"itunes:author,omitempty"`
ISubtitle string `xml:"itunes:subtitle,omitempty"`
ISummary *ISummary
IBlock string `xml:"itunes:block,omitempty"`
IImage *IImage
IDuration string `xml:"itunes:duration,omitempty"`
IExplicit string `xml:"itunes:explicit,omitempty"`
IComplete string `xml:"itunes:complete,omitempty"`
INewFeedURL string `xml:"itunes:new-feed-url,omitempty"`
IOwner *Author // Author is formatted for itunes as-is
ICategories []*ICategory
Items []*Item
// contains filtered or unexported fields
}
Podcast represents a podcast.
func New ¶
New instantiates a Podcast with required parameters.
Nil-able fields are optional but recommended as they are formatted to the expected proper formats.
Example ¶
ti, l, d := "title", "link", "description" // instantiate a new Podcast p := podcast.New(ti, l, d, &pubDate, &updatedDate) fmt.Println(p.Title, p.Link, p.Description, p.Language) fmt.Println(p.PubDate, p.LastBuildDate)
Output: title link description en-us Sat, 04 Feb 2017 08:21:52 +0000 Mon, 06 Feb 2017 08:21:52 +0000
func (*Podcast) AddAuthor ¶
AddAuthor adds the specified Author to the podcast.
Example ¶
package main
import (
"fmt"
"github.com/eduncan911/podcast"
)
func main() {
p := podcast.New("title", "link", "description", nil, nil)
// add the Author
p.AddAuthor("the name", "me@test.com")
fmt.Println(p.ManagingEditor)
fmt.Println(p.IAuthor)
}
Output: me@test.com (the name) me@test.com (the name)
func (*Podcast) AddCategory ¶
AddCategory adds the categories to the Podcast in comma delimited format.
subCategories are optional.
Example ¶
package main
import (
"fmt"
"github.com/eduncan911/podcast"
)
func main() {
p := podcast.New("title", "link", "description", nil, nil)
// add the Category
p.AddCategory("Bombay", nil)
p.AddCategory("American", []string{"Longhair", "Shorthair"})
p.AddCategory("Siamese", nil)
fmt.Println(len(p.ICategories), len(p.ICategories[1].ICategories))
fmt.Println(p.Category)
}
Output: 3 2 Bombay,American,Siamese
func (*Podcast) AddImage ¶
AddImage adds the specified Image to the Podcast.
Podcast feeds contain artwork that is a minimum size of 1400 x 1400 pixels and a maximum size of 3000 x 3000 pixels, 72 dpi, in JPEG or PNG format with appropriate file extensions (.jpg, .png), and in the RGB colorspace. To optimize images for mobile devices, Apple recommends compressing your image files.
Example ¶
package main
import (
"fmt"
"github.com/eduncan911/podcast"
)
func main() {
p := podcast.New("title", "link", "description", nil, nil)
// add the Image
p.AddImage("http://example.com/image.jpg")
if p.Image != nil && p.IImage != nil {
fmt.Println(p.Image.URL)
fmt.Println(p.IImage.HREF)
}
}
Output: http://example.com/image.jpg http://example.com/image.jpg
func (*Podcast) AddItem ¶
AddItem adds the podcast episode. It returns a count of Items added or any errors in validation that may have occurred.
This method takes the "itunes overrides" approach to populating itunes tags according to the overrides rules in the specification. This not only complies completely with iTunes parsing rules; but, it also displays what is possible to be set on an individual episode level - if you wish to have more fine grain control over your content.
This method imposes strict validation of the Item being added to confirm to Podcast and iTunes specifications.
Article minimal requirements are:
* Title * Description * Link
Audio, Video and Downloads minimal requirements are:
* Title * Description * Enclosure (HREF, Type and Length all required)
The following fields are always overwritten (don't set them):
* GUID * PubDateFormatted * AuthorFormatted * Enclosure.TypeFormatted * Enclosure.LengthFormatted
Recommendations:
- Just set the minimal fields: the rest get set for you.
- Always set an Enclosure.Length, to be nice to your downloaders.
- Follow Apple's best practices to enrich your podcasts: https://help.apple.com/itc/podcasts_connect/#/itc2b3780e76
- For specifications of itunes tags, see: https://help.apple.com/itc/podcasts_connect/#/itcb54353390
Example ¶
p := podcast.New("title", "link", "description", &pubDate, &updatedDate)
p.AddAuthor("the name", "me@test.com")
p.AddImage("http://example.com/image.jpg")
// create an Item
date := pubDate.AddDate(0, 0, 77)
item := podcast.Item{
Title: "Episode 1",
Description: "Description for Episode 1",
ISubtitle: "A simple episode 1",
PubDate: &date,
}
item.AddEnclosure(
"http://example.com/1.mp3",
podcast.MP3,
183,
)
item.AddSummary("See more at <a href=\"http://example.com\">Here</a>")
// add the Item
if _, err := p.AddItem(item); err != nil {
fmt.Println("item validation error: " + err.Error())
}
if len(p.Items) != 1 {
fmt.Println("expected 1 item in the collection")
}
pp := p.Items[0]
fmt.Println(
pp.GUID, pp.Title, pp.Link, pp.Description, pp.Author,
pp.AuthorFormatted, pp.Category, pp.Comments, pp.Source,
pp.PubDate, pp.PubDateFormatted, *pp.Enclosure,
pp.IAuthor, pp.IDuration, pp.IExplicit, pp.IIsClosedCaptioned,
pp.IOrder, pp.ISubtitle, pp.ISummary)
Output: http://example.com/1.mp3 Episode 1 http://example.com/1.mp3 Description for Episode 1 &{{ } me@test.com (the name)} 2017-04-22 08:21:52 +0000 UTC Sat, 22 Apr 2017 08:21:52 +0000 {{ } http://example.com/1.mp3 183 183 audio/mpeg audio/mpeg} me@test.com (the name) 183 A simple episode 1 &{{ } See more at <a href="http://example.com">Here</a>}
func (*Podcast) AddSummary ¶ added in v1.1.0
AddSummary adds the iTunes summary.
Limit: 4000 characters
Note that this field is a CDATA encoded field which allows for rich text such as html links: <a href="http://www.apple.com">Apple</a>.
Example ¶
package main
import (
"fmt"
"github.com/eduncan911/podcast"
)
func main() {
p := podcast.New("title", "link", "description", nil, nil)
// add the Image
p.AddSummary(`A very cool podcast with a long summary!
See more at our website: <a href="http://example.com">example.com</a>
`)
if p.ISummary != nil {
fmt.Println(p.ISummary.Text)
}
}
Output: A very cool podcast with a long summary! See more at our website: <a href="http://example.com">example.com</a>
func (*Podcast) Bytes ¶
Bytes returns an encoded []byte slice.
Example ¶
p := podcast.New(
"eduncan911 Podcasts",
"http://eduncan911.com/",
"An example Podcast",
&pubDate, &updatedDate,
)
p.AddAuthor("Jane Doe", "me@janedoe.com")
p.AddImage("http://janedoe.com/i.jpg")
p.AddSummary(`A very cool podcast with a long summary using Bytes()!
See more at our website: <a href="http://example.com">example.com</a>
`)
for i := int64(5); i < 7; i++ {
n := strconv.FormatInt(i, 10)
d := pubDate.AddDate(0, 0, int(i+3))
item := podcast.Item{
Title: "Episode " + n,
Link: "http://example.com/" + n + ".mp3",
Description: "Description for Episode " + n,
PubDate: &d,
}
if _, err := p.AddItem(item); err != nil {
fmt.Println(item.Title, ": error", err.Error())
break
}
}
// call Podcast.Bytes() to return a byte array
os.Stdout.Write(p.Bytes())
Output: <?xml version="1.0" encoding="UTF-8"?> <rss version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"> <channel> <title>eduncan911 Podcasts</title> <link>http://eduncan911.com/</link> <description>An example Podcast</description> <generator>go podcast v1.1.0 (github.com/eduncan911/podcast)</generator> <language>en-us</language> <lastBuildDate>Mon, 06 Feb 2017 08:21:52 +0000</lastBuildDate> <managingEditor>me@janedoe.com (Jane Doe)</managingEditor> <pubDate>Sat, 04 Feb 2017 08:21:52 +0000</pubDate> <image> <url>http://janedoe.com/i.jpg</url> </image> <itunes:author>me@janedoe.com (Jane Doe)</itunes:author> <itunes:summary><![CDATA[A very cool podcast with a long summary using Bytes()! See more at our website: <a href="http://example.com">example.com</a> ]]></itunes:summary> <itunes:image href="http://janedoe.com/i.jpg"></itunes:image> <item> <guid>http://example.com/5.mp3</guid> <title>Episode 5</title> <link>http://example.com/5.mp3</link> <description>Description for Episode 5</description> <pubDate>Sun, 12 Feb 2017 08:21:52 +0000</pubDate> <itunes:author>me@janedoe.com (Jane Doe)</itunes:author> <itunes:image href="http://janedoe.com/i.jpg"></itunes:image> </item> <item> <guid>http://example.com/6.mp3</guid> <title>Episode 6</title> <link>http://example.com/6.mp3</link> <description>Description for Episode 6</description> <pubDate>Mon, 13 Feb 2017 08:21:52 +0000</pubDate> <itunes:author>me@janedoe.com (Jane Doe)</itunes:author> <itunes:image href="http://janedoe.com/i.jpg"></itunes:image> </item> </channel> </rss>