Liking cljdoc? Tell your friends :D

dk.simongray.podcast.format.chapters

Chapters in the two forms podcasts carry them: the JSON Chapters file that podcast:chapters links to, and Podlove Simple Chapters embedded in the feed as psc:chapters. Both read into one shape and write back out.

A chapters document is a map:

{:version "1.2.0"
 :title "Episode 7" :author "…" :podcast-name "…"  ; optional
 :chapters [{:start-time 94 :title "Donation Segment"
             :img "https://…" :url "https://…" :toc false
             :end-time 120 :location {:name "…" :geo "geo:…"}}]}

Times are seconds. Chapters keep the order they're given in, and the keys that the format doesn't define are kept too. Reading never throws on content, and what it had to forgive is in the report, in the metadata.

Chapters in the two forms podcasts carry them: the JSON Chapters file
that podcast:chapters links to, and Podlove Simple Chapters embedded in
the feed as psc:chapters. Both read into one shape and write back out.

A chapters document is a map:

    {:version "1.2.0"
     :title "Episode 7" :author "…" :podcast-name "…"  ; optional
     :chapters [{:start-time 94 :title "Donation Segment"
                 :img "https://…" :url "https://…" :toc false
                 :end-time 120 :location {:name "…" :geo "geo:…"}}]}

Times are seconds. Chapters keep the order they're given in, and the keys
that the format doesn't define are kept too. Reading never throws on
content, and what it had to forgive is in the report, in the metadata.
raw docstring

dk.simongray.podcast.format.hls

HLS playlists, the streams that feeds offer as alternate enclosures: what a multivariant playlist says of its variants and renditions, and what a media playlist says of its duration.

A multivariant playlist reads as a map like this, with the URLs made absolute against the playlist's own:

{:kind       :multivariant
 :version    6
 :variants   [{:url "https://…/720p.m3u8" :bandwidth 1352370
               :average-bandwidth 831345
               :codecs "hvc1.1.6.L93.90,mp4a.40.2"
               :width 1280 :height 720 :frame-rate 29.97
               :subtitles "subs"}]
 :renditions [{:type :subtitles :group-id "subs" :name "English"
               :language "en" :default? true :autoselect? true
               :url "https://…"}]}

A media playlist reads as one like this:

{:kind :media :target-duration 6 :segments 816 :duration 4895.9
 :playlist-type "VOD" :ended? true}

The reading follows RFC 8216 and never throws on content.

HLS playlists, the streams that feeds offer as alternate enclosures:
what a multivariant playlist says of its variants and renditions, and
what a media playlist says of its duration.

A multivariant playlist reads as a map like this, with the URLs made
absolute against the playlist's own:

    {:kind       :multivariant
     :version    6
     :variants   [{:url "https://…/720p.m3u8" :bandwidth 1352370
                   :average-bandwidth 831345
                   :codecs "hvc1.1.6.L93.90,mp4a.40.2"
                   :width 1280 :height 720 :frame-rate 29.97
                   :subtitles "subs"}]
     :renditions [{:type :subtitles :group-id "subs" :name "English"
                   :language "en" :default? true :autoselect? true
                   :url "https://…"}]}

A media playlist reads as one like this:

    {:kind :media :target-duration 6 :segments 816 :duration 4895.9
     :playlist-type "VOD" :ended? true}

The reading follows RFC 8216 and never throws on content.
raw docstring

dk.simongray.podcast.format.image

The header of an image file, which is what a check of artwork needs.

The header gives the type of the image, its size in pixels, its color model and whether it can be transparent. The reader takes them from the first bytes and doesn't decode the image. It reads these formats:

  • PNG, by the W3C PNG specification
  • JPEG, by ITU-T T.81
  • GIF87a and GIF89a
  • WebP, by RFC 9649, with the VP8 frame header of RFC 6386, section 9.1

A reading takes a view of the parts of the file at hand, as in dk.simongray.podcast.util.bytes.view, and gives the header, or the ranges that it still needs. An image embedded in text, e.g. the artwork of a chapter, is a data URL, which data-url and from-data-url write and read. Only colors, palette and dominant-color decode an image, through the platform, for the colours that an app can colour its UI by.

The header of an image file, which is what a check of artwork needs.

The header gives the type of the image, its size in pixels, its color
model and whether it can be transparent. The reader takes them from the
first bytes and doesn't decode the image. It reads these formats:

- PNG, by the W3C PNG specification
- JPEG, by ITU-T T.81
- GIF87a and GIF89a
- WebP, by RFC 9649, with the VP8 frame header of RFC 6386, section 9.1

A reading takes a view of the parts of the file at hand, as in
dk.simongray.podcast.util.bytes.view, and gives the header, or the ranges
that it still needs. An image embedded in text, e.g. the artwork of a
chapter, is a data URL, which data-url and from-data-url write and read.
Only colors, palette and dominant-color decode an image, through the
platform, for the colours that an app can colour its UI by.
raw docstring

dk.simongray.podcast.format.media

What a media file says about itself, apart from the feed.

A media file gives its duration, its chapters and its artwork. It's an MP3 file, an MP4 or M4A file, an Ogg file of Opus or Vorbis, or an AAC file of ADTS frames.

A reading takes a view of the parts of the file at hand, as in dk.simongray.podcast.util.bytes.view, and gives what they say, or the ranges that it still needs. A fetch of those ranges most often takes one or two requests and a few hundred kilobytes.

A host that inserts ads serves another file to each request, so the duration is that of the file that came.

The readers are strict, and their namespaces name their quirks. The :except option leaves out the quirks that it names.

What a media file says about itself, apart from the feed.

A media file gives its duration, its chapters and its artwork. It's an
MP3 file, an MP4 or M4A file, an Ogg file of Opus or Vorbis, or an AAC
file of ADTS frames.

A reading takes a view of the parts of the file at hand, as in
dk.simongray.podcast.util.bytes.view, and gives what they say, or the
ranges that it still needs. A fetch of those ranges most often takes one
or two requests and a few hundred kilobytes.

A host that inserts ads serves another file to each request, so the
duration is that of the file that came.

The readers are strict, and their namespaces name their quirks. The
:except option leaves out the quirks that it names.
raw docstring

dk.simongray.podcast.format.media.aac

AAC files: the ADTS frames of ISO/IEC 14496-3, Annex 1.A, with the ID3v2 tags that often come before them.

The duration comes from the first of these that the file has:

  • the samples of every frame, when the view holds the whole file
  • the average length of the first frames and the size of the file
  • the TLEN frame of a tag

The reading is strict: the audio starts right after the tags. The :except option leaves out the quirks of the tags that it names.

AAC files: the ADTS frames of ISO/IEC 14496-3, Annex 1.A, with the ID3v2
tags that often come before them.

The duration comes from the first of these that the file has:

- the samples of every frame, when the view holds the whole file
- the average length of the first frames and the size of the file
- the TLEN frame of a tag

The reading is strict: the audio starts right after the tags. The
:except option leaves out the quirks of the tags that it names.
raw docstring

dk.simongray.podcast.format.media.id3

The ID3v2 tags at the start of an MP3 or AAC file, and what they say about the file: its tags, its duration, its artwork and its chapters.

The tags come through a view of the file, and id3-clj reads them from their bytes, as dk.simongray.id3 describes. Its frames are true to the ID3 specs, and this namespace gives what they say in the shapes of podcast-clj, e.g. the chapters in seconds. The quirks of the reading and its diagnostics are those of id3-clj.

The ID3v2 tags at the start of an MP3 or AAC file, and what they say
about the file: its tags, its duration, its artwork and its chapters.

The tags come through a view of the file, and id3-clj reads them from
their bytes, as dk.simongray.id3 describes. Its frames are true to the
ID3 specs, and this namespace gives what they say in the shapes of
podcast-clj, e.g. the chapters in seconds. The quirks of the reading
and its diagnostics are those of id3-clj.
raw docstring

dk.simongray.podcast.format.media.mp3

MP3 files: the ID3v2 tags at the start, and the MPEG audio frames after them.

ISO/IEC 11172-3 and 13818-3 define the header of a frame, and the reader takes MPEG 2.5 too, which is in neither.

The duration comes from the first of these that the file has:

  • the Xing or Info header, which LAME and most encoders write in the first frame
  • the VBRI header of the Fraunhofer encoder
  • the average bitrate of the first frames and the size of the file, which is exact for a constant bitrate

The reading is strict: the audio starts right after the tags. The quirk :mp3/junk-before-audio skips bytes that are no frame, such as padding that the size of a tag leaves out. The :except option leaves out the quirks that it names, those of the tags too.

MP3 files: the ID3v2 tags at the start, and the MPEG audio frames after
them.

ISO/IEC 11172-3 and 13818-3 define the header of a frame, and the reader
takes MPEG 2.5 too, which is in neither.

The duration comes from the first of these that the file has:

- the Xing or Info header, which LAME and most encoders write in the
  first frame
- the VBRI header of the Fraunhofer encoder
- the average bitrate of the first frames and the size of the file,
  which is exact for a constant bitrate

The reading is strict: the audio starts right after the tags. The quirk
:mp3/junk-before-audio skips bytes that are no frame, such as padding
that the size of a tag leaves out. The :except option leaves out the
quirks that it names, those of the tags too.
raw docstring

dk.simongray.podcast.format.media.mp4

MP4 and M4A files, whose boxes ISO/IEC 14496-12 defines.

A reading gives the duration from the movie header, the chapters, the artwork, the codecs and size of the tracks that play, and whether the movie comes first. The chapters are those of a QuickTime chapter track, or else those of a Nero chpl box. The titles of a chapter track can have a link each, and a track of images can name an image for each chapter, as in the enhanced podcasts of GarageBand. The artwork is the covr item of the iTunes metadata.

The movie box can come before the media data or after it, so the reading walks the boxes at the top level. A reader that has only the start of the file learns where to look next.

A file written in fragments gives the duration of all its fragments in its mehd box. Without that box, the reading takes the end of the last fragment that the mfra index at the end of the file lists.

The reading is strict. The quirk :mp4/zero-duration takes the duration of the longest track when the movie header gives none. The :except option leaves it out.

MP4 and M4A files, whose boxes ISO/IEC 14496-12 defines.

A reading gives the duration from the movie header, the chapters, the
artwork, the codecs and size of the tracks that play, and whether the
movie comes first. The chapters are those of a QuickTime chapter track,
or else those of a Nero chpl box. The titles of a chapter track can have
a link each, and a track of images can name an image for each chapter,
as in the enhanced podcasts of GarageBand. The artwork is the covr item
of the iTunes metadata.

The movie box can come before the media data or after it, so the
reading walks the boxes at the top level. A reader that has only the
start of the file learns where to look next.

A file written in fragments gives the duration of all its fragments in
its mehd box. Without that box, the reading takes the end of the last
fragment that the mfra index at the end of the file lists.

The reading is strict. The quirk :mp4/zero-duration takes the duration
of the longest track when the movie header gives none. The :except
option leaves it out.
raw docstring

dk.simongray.podcast.format.media.ogg

Ogg files of Opus or Vorbis audio: the pages of RFC 3533, the headers of RFC 7845 and of the Vorbis I specification, and the comments after them.

The duration is the granule position of the last page of the audio, which lies in the last 64 KB of the file, less the granule position where the audio starts. For Opus, the start is exact. For Vorbis, whose packets give their lengths only through the setup header, it's exact for audio that starts at zero, and right to within one block otherwise, e.g. for a recording of a live stream.

The chapters come from the CHAPTERxxx comments of Xiph's chapter extension, and the artwork from a METADATA_BLOCK_PICTURE comment.

The reading is strict: a page whose CRC doesn't match is no page. The quirk :ogg/image-type takes the type of a picture from its bytes when the type that it declares is loose. The :except option leaves out the quirks that it names.

Ogg files of Opus or Vorbis audio: the pages of RFC 3533, the headers of
RFC 7845 and of the Vorbis I specification, and the comments after them.

The duration is the granule position of the last page of the audio,
which lies in the last 64 KB of the file, less the granule position
where the audio starts. For Opus, the start is exact. For Vorbis, whose
packets give their lengths only through the setup header, it's exact for
audio that starts at zero, and right to within one block otherwise, e.g.
for a recording of a live stream.

The chapters come from the CHAPTERxxx comments of Xiph's chapter
extension, and the artwork from a METADATA_BLOCK_PICTURE comment.

The reading is strict: a page whose CRC doesn't match is no page. The
quirk :ogg/image-type takes the type of a picture from its bytes when
the type that it declares is loose. The :except option leaves out the
quirks that it names.
raw docstring

dk.simongray.podcast.format.opml

Subscription lists as OPML 2.0, the format that every podcast app imports and exports.

A subscription list is a map:

{:title         "My podcasts"
 :date-created  #inst "…" ; and the other head elements
 :subscriptions [{:text     "A show"
                  :xml-url  "https://example.org/feed.xml"
                  :html-url "https://example.org"
                  :path     ["Tech"]
                  :category ["Daily" "/News/Denmark"]
                  :created  #inst "…"}]
 :includes      [{:text "A list"
                  :url  "https://example.org/list.opml"
                  :path ["News"]}]
 :comments      [{:text "An audiobook"
                  :guid "…"}]}

The folders of the OPML become the :path of the subscriptions inside them, and back again. In an outline:

  • the categories become a vector, which OPML writes as one comma-separated attribute of tags and slash-delimited paths
  • the time it was created becomes an instant, as do the dates of the head
  • any other attribute stays a string
  • the episodes inside it, as Overcast's extended export writes them, become its :episodes, maps of their attributes, e.g. {:enclosure-url "…" :played "1" :progress "754"}

An outline that includes another OPML list becomes one of the :includes, with the :path of its folders. It has the type include, or the type link to a URL that ends in .opml.

An outline with isComment="true" is commented out, and so is all that's inside it. It becomes one of the :comments, with its attributes and the :path of its folders, and never a subscription, even with an xmlUrl. Other apps skip a comment, so it can name a show that they can't fetch, e.g. one of local files.

Subscription lists as OPML 2.0, the format that every podcast app
imports and exports.

A subscription list is a map:

    {:title         "My podcasts"
     :date-created  #inst "…" ; and the other head elements
     :subscriptions [{:text     "A show"
                      :xml-url  "https://example.org/feed.xml"
                      :html-url "https://example.org"
                      :path     ["Tech"]
                      :category ["Daily" "/News/Denmark"]
                      :created  #inst "…"}]
     :includes      [{:text "A list"
                      :url  "https://example.org/list.opml"
                      :path ["News"]}]
     :comments      [{:text "An audiobook"
                      :guid "…"}]}

The folders of the OPML become the :path of the subscriptions inside
them, and back again. In an outline:

- the categories become a vector, which OPML writes as one
  comma-separated attribute of tags and slash-delimited paths
- the time it was created becomes an instant, as do the dates of the
  head
- any other attribute stays a string
- the episodes inside it, as Overcast's extended export writes them,
  become its :episodes, maps of their attributes, e.g.
  {:enclosure-url "…" :played "1" :progress "754"}

An outline that includes another OPML list becomes one of the :includes,
with the :path of its folders. It has the type include, or the type link
to a URL that ends in .opml.

An outline with isComment="true" is commented out, and so is all that's
inside it. It becomes one of the :comments, with its attributes and the
:path of its folders, and never a subscription, even with an xmlUrl.
Other apps skip a comment, so it can name a show that they can't fetch,
e.g. one of local files.
raw docstring

dk.simongray.podcast.format.transcript

Transcripts in the four formats that the podcast namespace allows, read into one shape and written back: SRT, WebVTT, the JSON segments format and the constrained HTML form, plus plain text.

Each format has its reader, parse-srt, parse-vtt, parse-json, parse-html and parse-text, and parse picks one by the type or by the file. The first three have writers too, emit-srt, emit-vtt and emit-json.

A transcript is a map:

{:format   :vtt                     ; :srt :json :html :text
 :segments [{:start-time 0.0 :end-time 2.76 :speaker "Sarah"
             :body "…"}]}

Times are seconds, and :end-time and :speaker are there when the source had them. A WebVTT cue with inline timestamps has the :runs of its text too, each with its :start-time and :text, e.g. a word each in a word-level transcript. Speakers are read from these:

  • the voice spans of WebVTT, which name the speaker of their cue, or of the cues that follow too in a file that marks only the changes of speaker
  • the cite elements of HTML
  • the fields of JSON
  • the "Name:" or "[Name]:" prefixes of caption files, which mark a change of speaker, so the name carries over the cues that follow

Reading is lenient and never throws on content. The report says what it had to forgive, e.g. times written as strings, an SRT wrapped in HTML or a declared type that doesn't match the file. The report is in the metadata.

Transcripts in the four formats that the podcast namespace allows, read
into one shape and written back: SRT, WebVTT, the JSON segments format
and the constrained HTML form, plus plain text.

Each format has its reader, parse-srt, parse-vtt, parse-json, parse-html
and parse-text, and parse picks one by the type or by the file. The
first three have writers too, emit-srt, emit-vtt and emit-json.

A transcript is a map:

    {:format   :vtt                     ; :srt :json :html :text
     :segments [{:start-time 0.0 :end-time 2.76 :speaker "Sarah"
                 :body "…"}]}

Times are seconds, and :end-time and :speaker are there when the source
had them. A WebVTT cue with inline timestamps has the :runs of its text
too, each with its :start-time and :text, e.g. a word each in a
word-level transcript. Speakers are read from these:

- the voice spans of WebVTT, which name the speaker of their cue, or of
  the cues that follow too in a file that marks only the changes of
  speaker
- the cite elements of HTML
- the fields of JSON
- the "Name:" or "[Name]:" prefixes of caption files, which mark a
  change of speaker, so the name carries over the cues that follow

Reading is lenient and never throws on content. The report says what it
had to forgive, e.g. times written as strings, an SRT wrapped in HTML or
a declared type that doesn't match the file. The report is in the
metadata.
raw docstring

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
←Move to previous article
→Move to next article
Ctrl+/Jump to the search field
× close