CheckMyFeed

Checks a podcast feed (a raw XML string) against RSS 2.0, the Apple Podcasts requirements and the Podcasting 2.0 namespace, and reports what falls short. Every rule has an id, a severity (:error, :warning or :info), an explanation, a link to the specification it comes from and the rule set (standard) it belongs to.

Podcasting 2.0 is optional as a whole: only the podcast: tags a feed uses are checked, and a feed that doesn't use the namespace gets no findings from it.

The library never fetches anything and never fixes up the feed before looking at it: what it sees is exactly what a podcast app would get. It only checks what the feed text itself shows, so it does not download artwork or media to verify pixel sizes, file types or loudness.

Installation

def deps do
  [
    {:check_my_feed, "~> 0.5.0"}
  ]
end

Usage

{:ok, report} = CheckMyFeed.check(xml)

for finding <- report.findings do
  standard = CheckMyFeed.standard_name(finding.standard)
  IO.puts("[#{finding.severity}] #{standard} #{finding.rule_id}: #{finding.detail}")
end

# rules that were checked and passed
Enum.map(report.passed, & &1.id)

# rules that had nothing to check, e.g. the enclosure rules on a feed
# without episodes, or the rule for an optional tag the feed doesn't use
Enum.map(report.not_applicable, & &1.id)

CheckMyFeed.check/1 returns {:error, {:invalid_xml, reason}} if the document cannot be parsed at all.

Probing what the feed points to

Some requirements can't be seen in the feed text: artwork size, file type, colorspace or transparency, whether the media files are reachable with the length and type the feed states, and the audio itself (format, bit rate, sample rate, loudness, true peak). The library still downloads nothing: you take the measurements and CheckMyFeed.check_probes/1 judges them, with the same kind of report (Apple artwork and audio rules, RSS 2.0 enclosure rules). Only the rules for the keys you pass (:artwork, :enclosures, :audio) run.

{:ok, report} =
  CheckMyFeed.check_probes(%{
    artwork: [
      %{label: "Artwork", url: url,
        result: {:ok, %{format: "JPEG", width: 3000, height: 3000,
                        colorspace: "sRGB", alpha: false, dpi: 72.0}}}
    ],
    enclosures: [
      %{label: "Episode 1", url: media_url, feed_length: "123", feed_type: "audio/mpeg",
        result: {:ok, %{status: 200, length: 123, type: "audio/mpeg"}}}
    ],
    audio: [
      %{label: "Episode 1", url: media_url,
        result: {:ok, %{codec: "aac", container: "mp4", sample_rate: 44_100, channels: 2,
                        bit_rate: 128_000, loudness: -16.2, true_peak: -1.4, seconds: 62.0}}}
    ]
  })

License

AGPL-3.0-or-later, see LICENSE.