podcast.go 3.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
  2. // SPDX-License-Identifier: Apache-2.0
  3. package rss // import "miniflux.app/v2/internal/reader/rss"
  4. import (
  5. "errors"
  6. "math"
  7. "strconv"
  8. "strings"
  9. )
  10. var ErrInvalidDurationFormat = errors.New("rss: invalid duration format")
  11. // PodcastFeedElement represents iTunes and GooglePlay feed XML elements.
  12. // Specs:
  13. // - https://github.com/simplepie/simplepie-ng/wiki/Spec:-iTunes-Podcast-RSS
  14. // - https://developers.google.com/search/reference/podcast/rss-feed
  15. type PodcastFeedElement struct {
  16. ItunesAuthor string `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd channel>author"`
  17. Subtitle string `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd channel>subtitle"`
  18. Summary string `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd channel>summary"`
  19. PodcastOwner PodcastOwner `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd channel>owner"`
  20. GooglePlayAuthor string `xml:"http://www.google.com/schemas/play-podcasts/1.0 channel>author"`
  21. }
  22. // PodcastEntryElement represents iTunes and GooglePlay entry XML elements.
  23. type PodcastEntryElement struct {
  24. Subtitle string `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd subtitle"`
  25. Summary string `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd summary"`
  26. Duration string `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd duration"`
  27. GooglePlayDescription string `xml:"http://www.google.com/schemas/play-podcasts/1.0 description"`
  28. }
  29. // PodcastOwner represents contact information for the podcast owner.
  30. type PodcastOwner struct {
  31. Name string `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd name"`
  32. Email string `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd email"`
  33. }
  34. // Image represents podcast artwork.
  35. type Image struct {
  36. URL string `xml:"href,attr"`
  37. }
  38. // PodcastAuthor returns the author of the podcast.
  39. func (e *PodcastFeedElement) PodcastAuthor() string {
  40. author := ""
  41. switch {
  42. case e.ItunesAuthor != "":
  43. author = e.ItunesAuthor
  44. case e.GooglePlayAuthor != "":
  45. author = e.GooglePlayAuthor
  46. case e.PodcastOwner.Name != "":
  47. author = e.PodcastOwner.Name
  48. case e.PodcastOwner.Email != "":
  49. author = e.PodcastOwner.Email
  50. }
  51. return strings.TrimSpace(author)
  52. }
  53. // PodcastDescription returns the description of the podcast.
  54. func (e *PodcastEntryElement) PodcastDescription() string {
  55. description := ""
  56. switch {
  57. case e.GooglePlayDescription != "":
  58. description = e.GooglePlayDescription
  59. case e.Summary != "":
  60. description = e.Summary
  61. case e.Subtitle != "":
  62. description = e.Subtitle
  63. }
  64. return strings.TrimSpace(description)
  65. }
  66. // normalizeDuration returns the duration tag value as a number of minutes
  67. func normalizeDuration(rawDuration string) (int, error) {
  68. var sumSeconds int
  69. durationParts := strings.Split(rawDuration, ":")
  70. if len(durationParts) > 3 {
  71. return 0, ErrInvalidDurationFormat
  72. }
  73. for i, durationPart := range durationParts {
  74. durationPartValue, err := strconv.Atoi(durationPart)
  75. if err != nil {
  76. return 0, ErrInvalidDurationFormat
  77. }
  78. sumSeconds += int(math.Pow(60, float64(len(durationParts)-i-1))) * durationPartValue
  79. }
  80. return sumSeconds / 60, nil
  81. }