kopia lustrzana https://github.com/Podcastindex-org/podcast-namespace
Merge pull request #707 from theDanielJLewis/main
Renamed tag files for consistency and URI conventionspull/712/head
commit
ce72810b74
|
@ -1,210 +0,0 @@
|
|||
## JSON Chapters Format
|
||||
<small>Version 1.2 - Updated on 2021.04.15</small>
|
||||
|
||||
<br><br>
|
||||
|
||||
This is the initial spec for a json chapters format that can be referenced in an RSS feed using the `<podcast:chapters>` tag of
|
||||
the "podcast" namespace. This file can reside on any publicly accessible url. See the podcast namespace documentation for
|
||||
details on the format of the tag.
|
||||
|
||||
This type of file should be served with a Content-type of 'application/json+chapters'. Chapter order is assumed to be
|
||||
in ascending order based on the `startTime`.
|
||||
|
||||
<br>
|
||||
|
||||
## "Chapters" Object
|
||||
|
||||
The chapters object is a simple JSON object with only 2 required properties:
|
||||
|
||||
- `version` (required - string) The version number of the format being used
|
||||
- `chapters` (required - array) An array of chapter objects defined below
|
||||
|
||||
#### Optional Attributes:
|
||||
|
||||
- `author` (optional - string) The name of the author of this podcast episode.
|
||||
- `title` (optional - string) The title of this podcast episode.
|
||||
- `podcastName` (optional - string) The name of the podcast this episode belongs to.
|
||||
- `description` (optional - string) A description of this episode.
|
||||
- `fileName` (optional - string) The name of the audio file these chapters apply to.
|
||||
- `waypoints` (optional - boolean) If this property is present, the locations in a chapter object should be displayed with a route between locations.
|
||||
|
||||
<br>
|
||||
|
||||
## The "Chapter" Object
|
||||
|
||||
The "chapter" object takes this basic form:
|
||||
|
||||
```json
|
||||
{
|
||||
"startTime": 94,
|
||||
"title": "Donation Segment"
|
||||
}
|
||||
```
|
||||
|
||||
There is only one required attribute:
|
||||
|
||||
- `startTime` (required - float) The starting time of the chapter, expressed in seconds with float precision for fractions of a second.
|
||||
|
||||
<br>
|
||||
|
||||
#### Optional Attributes:
|
||||
|
||||
- `title` (optional - string) The title of this chapter.
|
||||
- `img` (optional - string) The url of an image to use as chapter art.
|
||||
- `url` (optional - string) The url of a web page or supporting document that's related to the topic of this chapter.
|
||||
- `toc` (optional - boolean) If this property is present and set to false, this chapter should not display visibly to the user in either the table of contents or as a jump-to point in the user interface. It should be considered a "silent" chapter marker for the purpose of meta-data only. If this property is set to `true` or not present at all, this should be considered a normal chapter for display to the user. The name "toc" is short for "table of contents".
|
||||
- `endTime` (optional - float) The end time of the chapter, expressed in seconds with float precision for fractions of a second.
|
||||
- `location` (optional - object) This object defines an optional location that is tied to this chapter. It follows the structure of the [location](https://github.com/Podcastindex-org/podcast-namespace/blob/main/location/location.md) tag in the XML namespace.
|
||||
|
||||
<br>
|
||||
|
||||
## The Location Object:
|
||||
|
||||
The "location" object takes this basic form:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Eiffel Tower, Paris",
|
||||
"geo": "geo:48.858093,2.294694"
|
||||
}
|
||||
```
|
||||
|
||||
It is intended to provide for rich location-based experiences tied to a point of time within a podcast episode, or other feed based media. For example, a "walking tour" may include latitude and longitude waypoints along side the image within chapters markers as someone listens to the tour podcast. This
|
||||
would allow apps to show a map with markers within the UI as the tour progresses. Or, perhaps a "History of the Middle East" podcast might expose a map to highlight where certain landmarks are when being discussed.
|
||||
|
||||
There are two required attributes:
|
||||
|
||||
- `name` (required - string) A human readable place name.
|
||||
- `geo` (required - string) A simple latitude,longitude given in geoURI format, conformant to [RFC 5870](https://tools.ietf.org/html/rfc5870).
|
||||
|
||||
#### Optional Attributes:
|
||||
|
||||
- `osm` (optional - string) An OpenStreetMaps query string. Please see the [location](https://github.com/Podcastindex-org/podcast-namespace/blob/main/location/location.md) XML tag specification for full details.
|
||||
|
||||
<br>
|
||||
|
||||
## Basic example
|
||||
|
||||
Here is what a very basic chapters file may look like:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "1.2.0",
|
||||
"chapters":
|
||||
[
|
||||
{
|
||||
"startTime": 0,
|
||||
"title": "Intro"
|
||||
},
|
||||
{
|
||||
"startTime": 168,
|
||||
"title": "Hearing Aids",
|
||||
"img": "https://example.com/images/hearing_aids.jpg"
|
||||
},
|
||||
{
|
||||
"startTime": 260,
|
||||
"title": "Progress Report"
|
||||
},
|
||||
{
|
||||
"startTime": 410,
|
||||
"title": "Namespace",
|
||||
"img": "https://example.com/images/namepsace_example.jpg",
|
||||
"url": "https://github.com/Podcastindex-org/podcast-namespace"
|
||||
},
|
||||
{
|
||||
"startTime": 3990,
|
||||
"title": "Just Break Up",
|
||||
"img": "https://example.com/images/justbreakuppod.png"
|
||||
},
|
||||
{
|
||||
"startTime": 5510,
|
||||
"title": "The Big Players"
|
||||
},
|
||||
{
|
||||
"startTime": 5854,
|
||||
"title": "Spread the Word"
|
||||
},
|
||||
{
|
||||
"startTime": 6089,
|
||||
"title": "Outro"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
In this simple form, the chapter objects are treated as sequential just based on their index as a function of being
|
||||
ordered in ascending fashion based on their `startTime`. In a very simple example such as this nothing is present except the `startTime`,
|
||||
`title`, and a few bits of optional meta-data.
|
||||
|
||||
<br><br>
|
||||
|
||||
## More complex example
|
||||
|
||||
In this more robust example, we can bring in more meta-data about the podcast episode, and provide a way for this file to exist more easily in a web
|
||||
context for something like an embedded HTML5 player on a website. Also there is an example of a "silent" chapter that has no presence in the visible
|
||||
chapter list, but allows for different artwork to be shown:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "1.2.0",
|
||||
"author": "John Doe",
|
||||
"title": "Episode 7 - Making Progress",
|
||||
"podcastName": "John's Awesome Podcast",
|
||||
"chapters":
|
||||
[
|
||||
{
|
||||
"startTime": 0,
|
||||
"title": "Intro"
|
||||
},
|
||||
{
|
||||
"startTime": 168,
|
||||
"title": "Hearing Aids"
|
||||
},
|
||||
{
|
||||
"startTime": 260,
|
||||
"title": "Progress Report"
|
||||
},
|
||||
{
|
||||
"startTime": 410,
|
||||
"title": "Namespace",
|
||||
"img": "https://example.com/images/namepsace_example.jpg",
|
||||
"url": "https://github.com/Podcastindex-org/podcast-namespace"
|
||||
},
|
||||
{
|
||||
"startTime": 3990,
|
||||
"title": "Just Break Up",
|
||||
"img": "https://example.com/images/justbreakuppod.png",
|
||||
"url": "https://twitter.com/justbreakuppod"
|
||||
},
|
||||
{
|
||||
"startTime": 4826,
|
||||
"img": "https://example.com/images/parisfrance.jpg",
|
||||
"toc": false,
|
||||
"location": {
|
||||
"name": "Eiffel Tower, Paris",
|
||||
"geo": "geo:42.3417649,-70.9661596"
|
||||
}
|
||||
},
|
||||
{
|
||||
"startTime": 5510,
|
||||
"title": "The Big Players"
|
||||
},
|
||||
{
|
||||
"startTime": 5854,
|
||||
"title": "Spread the Word"
|
||||
},
|
||||
{
|
||||
"startTime": 6089,
|
||||
"title": "Outro"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
<br><br>
|
||||
|
||||
## Note about privacy
|
||||
|
||||
Clearly, when pulling in web based data there is the chance that this functionality could be used for tracking. As a safeguard against that, apps
|
||||
using chapters are encouraged to prompt the user to acknowledge whether they trust the podcast in question before displaying the content. Trusted
|
||||
podcasts can then be remembered for the future.
|
19
docs/1.0.md
19
docs/1.0.md
|
@ -21,7 +21,6 @@ Each tag below exists in the podcast namespace within the specified parent. All
|
|||
explicitly specified as optional. Anywhere the url of a hyper-text based resource is specified, it must be given as
|
||||
`https:` and not `http:`.
|
||||
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Transcript](tags/transcript.md)
|
||||
|
@ -35,23 +34,23 @@ explicitly specified as optional. Anywhere the url of a hyper-text based resourc
|
|||
- [Episode](tags/episode.md)
|
||||
- [Trailer](tags/trailer.md)
|
||||
- [License](tags/license.md)
|
||||
- [Alternate Enclosure](tags/alternateEnclosure.md)
|
||||
- [Alternate Enclosure](tags/alternate-enclosure.md)
|
||||
- [Source](tags/source.md)
|
||||
- [Integrity](tags/integrity.md)
|
||||
- [Guid](tags/guid.md)
|
||||
- [Value](tags/value.md)
|
||||
- [Value Recipient](tags/valueRecipient.md)
|
||||
- [Value Recipient](tags/value-recipient.md)
|
||||
- [Medium](tags/medium.md)
|
||||
- [Images](tags/images.md)
|
||||
- [Live Item](tags/liveItem.md)
|
||||
- [Content Link](tags/contentLink.md)
|
||||
- [Social Interact](tags/socialInteract.md)
|
||||
- [Live Item](tags/live-item.md)
|
||||
- [Content Link](tags/content-link.md)
|
||||
- [Social Interact](tags/social-interact.md)
|
||||
- [Block](tags/block.md)
|
||||
- [Txt](tags/txt.md)
|
||||
- [Remote Item](tags/remoteItem.md)
|
||||
- [Remote Item](tags/remote-item.md)
|
||||
- [Podroll](tags/podroll.md)
|
||||
- [Update Frequency](tags/updateFrequency.md)
|
||||
- [Update Frequency](tags/update-frequency.md)
|
||||
- [Podping](tags/podping.md)
|
||||
- [Value Time Split](tags/valueTimeSplit.md)
|
||||
- [Value Time Split](tags/value-time-split.md)
|
||||
- [Chat](tags/chat.md)
|
||||
- [Publisher](tags/publisher.md)
|
||||
- [Publisher](tags/publisher.md)
|
||||
|
|
|
@ -0,0 +1,193 @@
|
|||
## JSON Chapters Format
|
||||
|
||||
<small>Version 1.2 - Updated on 2021.04.15</small>
|
||||
|
||||
This is the initial spec for a json chapters format that can be referenced in an RSS feed using the `<podcast:chapters>` tag of
|
||||
the "podcast" namespace. This file can reside on any publicly accessible url. See the podcast namespace documentation for
|
||||
details on the format of the tag.
|
||||
|
||||
This type of file should be served with a Content-type of 'application/json+chapters'. Chapter order is assumed to be
|
||||
in ascending order based on the `startTime`.
|
||||
|
||||
## "Chapters" Object
|
||||
|
||||
The chapters object is a simple JSON object with only 2 required properties:
|
||||
|
||||
- `version` (required - string) The version number of the format being used
|
||||
- `chapters` (required - array) An array of chapter objects defined below
|
||||
|
||||
#### Optional Attributes:
|
||||
|
||||
- `author` (optional - string) The name of the author of this podcast episode.
|
||||
- `title` (optional - string) The title of this podcast episode.
|
||||
- `podcastName` (optional - string) The name of the podcast this episode belongs to.
|
||||
- `description` (optional - string) A description of this episode.
|
||||
- `fileName` (optional - string) The name of the audio file these chapters apply to.
|
||||
- `waypoints` (optional - boolean) If this property is present, the locations in a chapter object should be displayed with a route between locations.
|
||||
|
||||
## The "Chapter" Object
|
||||
|
||||
The "chapter" object takes this basic form:
|
||||
|
||||
```json
|
||||
{
|
||||
"startTime": 94,
|
||||
"title": "Donation Segment"
|
||||
}
|
||||
```
|
||||
|
||||
There is only one required attribute:
|
||||
|
||||
- `startTime` (required - float) The starting time of the chapter, expressed in seconds with float precision for fractions of a second.
|
||||
|
||||
#### Optional Attributes:
|
||||
|
||||
- `title` (optional - string) The title of this chapter.
|
||||
- `img` (optional - string) The url of an image to use as chapter art.
|
||||
- `url` (optional - string) The url of a web page or supporting document that's related to the topic of this chapter.
|
||||
- `toc` (optional - boolean) If this property is present and set to false, this chapter should not display visibly to the user in either the table of contents or as a jump-to point in the user interface. It should be considered a "silent" chapter marker for the purpose of meta-data only. If this property is set to `true` or not present at all, this should be considered a normal chapter for display to the user. The name "toc" is short for "table of contents".
|
||||
- `endTime` (optional - float) The end time of the chapter, expressed in seconds with float precision for fractions of a second.
|
||||
- `location` (optional - object) This object defines an optional location that is tied to this chapter. It follows the structure of the [location](https://github.com/Podcastindex-org/podcast-namespace/blob/main/location/location.md) tag in the XML namespace.
|
||||
|
||||
## The Location Object:
|
||||
|
||||
The "location" object takes this basic form:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Eiffel Tower, Paris",
|
||||
"geo": "geo:48.858093,2.294694"
|
||||
}
|
||||
```
|
||||
|
||||
It is intended to provide for rich location-based experiences tied to a point of time within a podcast episode, or other feed based media. For example, a "walking tour" may include latitude and longitude waypoints along side the image within chapters markers as someone listens to the tour podcast. This
|
||||
would allow apps to show a map with markers within the UI as the tour progresses. Or, perhaps a "History of the Middle East" podcast might expose a map to highlight where certain landmarks are when being discussed.
|
||||
|
||||
There are two required attributes:
|
||||
|
||||
- `name` (required - string) A human readable place name.
|
||||
- `geo` (required - string) A simple latitude,longitude given in geoURI format, conformant to [RFC 5870](https://tools.ietf.org/html/rfc5870).
|
||||
|
||||
#### Optional Attributes:
|
||||
|
||||
- `osm` (optional - string) An OpenStreetMaps query string. Please see the [location](../../tags/location.md) XML tag specification for full details.
|
||||
|
||||
## Basic example
|
||||
|
||||
Here is what a very basic chapters file may look like:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "1.2.0",
|
||||
"chapters": [
|
||||
{
|
||||
"startTime": 0,
|
||||
"title": "Intro"
|
||||
},
|
||||
{
|
||||
"startTime": 168,
|
||||
"title": "Hearing Aids",
|
||||
"img": "https://example.com/images/hearing_aids.jpg"
|
||||
},
|
||||
{
|
||||
"startTime": 260,
|
||||
"title": "Progress Report"
|
||||
},
|
||||
{
|
||||
"startTime": 410,
|
||||
"title": "Namespace",
|
||||
"img": "https://example.com/images/namepsace_example.jpg",
|
||||
"url": "https://github.com/Podcastindex-org/podcast-namespace"
|
||||
},
|
||||
{
|
||||
"startTime": 3990,
|
||||
"title": "Just Break Up",
|
||||
"img": "https://example.com/images/justbreakuppod.png"
|
||||
},
|
||||
{
|
||||
"startTime": 5510,
|
||||
"title": "The Big Players"
|
||||
},
|
||||
{
|
||||
"startTime": 5854,
|
||||
"title": "Spread the Word"
|
||||
},
|
||||
{
|
||||
"startTime": 6089,
|
||||
"title": "Outro"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
In this simple form, the chapter objects are treated as sequential just based on their index as a function of being
|
||||
ordered in ascending fashion based on their `startTime`. In a very simple example such as this nothing is present except the `startTime`,
|
||||
`title`, and a few bits of optional meta-data.
|
||||
|
||||
## More complex example
|
||||
|
||||
In this more robust example, we can bring in more meta-data about the podcast episode, and provide a way for this file to exist more easily in a web
|
||||
context for something like an embedded HTML5 player on a website. Also there is an example of a "silent" chapter that has no presence in the visible
|
||||
chapter list, but allows for different artwork to be shown:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "1.2.0",
|
||||
"author": "John Doe",
|
||||
"title": "Episode 7 - Making Progress",
|
||||
"podcastName": "John's Awesome Podcast",
|
||||
"chapters": [
|
||||
{
|
||||
"startTime": 0,
|
||||
"title": "Intro"
|
||||
},
|
||||
{
|
||||
"startTime": 168,
|
||||
"title": "Hearing Aids"
|
||||
},
|
||||
{
|
||||
"startTime": 260,
|
||||
"title": "Progress Report"
|
||||
},
|
||||
{
|
||||
"startTime": 410,
|
||||
"title": "Namespace",
|
||||
"img": "https://example.com/images/namepsace_example.jpg",
|
||||
"url": "https://github.com/Podcastindex-org/podcast-namespace"
|
||||
},
|
||||
{
|
||||
"startTime": 3990,
|
||||
"title": "Just Break Up",
|
||||
"img": "https://example.com/images/justbreakuppod.png",
|
||||
"url": "https://twitter.com/justbreakuppod"
|
||||
},
|
||||
{
|
||||
"startTime": 4826,
|
||||
"img": "https://example.com/images/parisfrance.jpg",
|
||||
"toc": false,
|
||||
"location": {
|
||||
"name": "Eiffel Tower, Paris",
|
||||
"geo": "geo:42.3417649,-70.9661596"
|
||||
}
|
||||
},
|
||||
{
|
||||
"startTime": 5510,
|
||||
"title": "The Big Players"
|
||||
},
|
||||
{
|
||||
"startTime": 5854,
|
||||
"title": "Spread the Word"
|
||||
},
|
||||
{
|
||||
"startTime": 6089,
|
||||
"title": "Outro"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Note about privacy
|
||||
|
||||
Clearly, when pulling in web based data there is the chance that this functionality could be used for tracking. As a safeguard against that, apps
|
||||
using chapters are encouraged to prompt the user to acknowledge whether they trust the podcast in question before displaying the content. Trusted
|
||||
podcasts can then be remembered for the future.
|
|
@ -31,22 +31,23 @@ This matter is very complex so this specification only intends to scratch its su
|
|||
|
||||
## Specification
|
||||
|
||||
- **\<podcast:license url="[url to license]" />[License Slug from [License List](/license/licenseslugs.txt) or Custom Liense name]</podcast:license>**
|
||||
- **\<podcast:license url="[url to license]" />[License Slug from [License List](licenseslugs.txt) or Custom Liense name]</podcast:license>**
|
||||
|
||||
Channel (optional | single)
|
||||
Channel (optional | single)
|
||||
|
||||
Item (optional | single)
|
||||
Item (optional | single)
|
||||
|
||||
This element allows a podcaster to specify a license for a podcast or an episode.
|
||||
This element allows a podcaster to specify a license for a podcast or an episode.
|
||||
|
||||
- `url` (required): This is the url to the license file.
|
||||
- `url` (required): This is the url to the license file.
|
||||
|
||||
Examples:
|
||||
- `<podcast:license url="http://creativecommons.org/licenses/by-nd/4.0/">(CC BY-ND 4.0)</podcast:license>`
|
||||
- `<podcast:license url="http://creativecommons.org/licenses/by-nc-nd/4.0/">(CC BY-NC-ND 4.0)</podcast:license>`
|
||||
- `<podcast:license url="http://domain.tld/license.txt">© My Company 2021 - All Rights Reserved</podcast:license>`
|
||||
|
||||
|
||||
Discussion here:
|
||||
- (https://github.com/Podcastindex-org/podcast-namespace/issues/177)
|
||||
- (https://podcastindex.social/web/statuses/105839486748529374)
|
||||
Examples:
|
||||
|
||||
- `<podcast:license url="http://creativecommons.org/licenses/by-nd/4.0/">(CC BY-ND 4.0)</podcast:license>`
|
||||
- `<podcast:license url="http://creativecommons.org/licenses/by-nc-nd/4.0/">(CC BY-NC-ND 4.0)</podcast:license>`
|
||||
- `<podcast:license url="http://domain.tld/license.txt">© My Company 2021 - All Rights Reserved</podcast:license>`
|
||||
|
||||
Discussion here:
|
||||
|
||||
- (https://github.com/Podcastindex-org/podcast-namespace/issues/177)
|
||||
- (https://podcastindex.social/web/statuses/105839486748529374)
|
|
@ -1,30 +1,31 @@
|
|||
# The Publisher Medium
|
||||
|
||||
v1.0 - April 5, 2024
|
||||
|
||||
<br>
|
||||
|
||||
Below, you will find implementation details about using the `publisher` value in the [`<podcast:medium>`](https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md#medium) tag to
|
||||
Below, you will find implementation details about using the `publisher` value in the [`<podcast:medium>`](../../tags/medium.md) tag to
|
||||
create "publisher feeds".
|
||||
|
||||
<br>
|
||||
|
||||
## Overview
|
||||
|
||||
The idea of a "publisher" is that a single entity (person, organization, record label, etc) might be the responsible
|
||||
party which produces multiple podcast feeds. In such a case it would be useful to be able to see all of a
|
||||
publisher's podcasts collected in a single place. For instance, a news organization might produce 12 different
|
||||
podcast feeds. Or, a music artist might produce 3 albums of music using the `<podcast:medium>` tag of `music`. In
|
||||
those cases, having a high level feed that references these other feeds would make it easier for podcast apps to
|
||||
The idea of a "publisher" is that a single entity (person, organization, record label, etc) might be the responsible
|
||||
party which produces multiple podcast feeds. In such a case it would be useful to be able to see all of a
|
||||
publisher's podcasts collected in a single place. For instance, a news organization might produce 12 different
|
||||
podcast feeds. Or, a music artist might produce 3 albums of music using the `<podcast:medium>` tag of `music`. In
|
||||
those cases, having a high level feed that references these other feeds would make it easier for podcast apps to
|
||||
associate those feeds with a particular publishing entity.
|
||||
|
||||
Likewise, it is helpful if the produced feeds link back to the "publisher feed" so that podcast apps can walk back
|
||||
up the chain from a podcast feed to it's publisher in order to find other relevant content from that publishing
|
||||
entity. For instance, a listener may subscribe to a music album by an artist and want to find their other
|
||||
Likewise, it is helpful if the produced feeds link back to the "publisher feed" so that podcast apps can walk back
|
||||
up the chain from a podcast feed to it's publisher in order to find other relevant content from that publishing
|
||||
entity. For instance, a listener may subscribe to a music album by an artist and want to find their other
|
||||
albums and singles.
|
||||
|
||||
When a publisher feed links to it's "child" feeds, and those "child" feeds link back to their "parent" publisher
|
||||
feeds, this provides a two-way validation that a feed is indeed a valid part of a publishing entities portfolio of
|
||||
content. If a feed links to a publisher feed without the publisher feed referencing it, that association should be
|
||||
When a publisher feed links to it's "child" feeds, and those "child" feeds link back to their "parent" publisher
|
||||
feeds, this provides a two-way validation that a feed is indeed a valid part of a publishing entities portfolio of
|
||||
content. If a feed links to a publisher feed without the publisher feed referencing it, that association should be
|
||||
discarded.
|
||||
|
||||
<br>
|
||||
|
@ -33,14 +34,14 @@ discarded.
|
|||
|
||||
A publisher feed must have the following parts in it's `<channel>`:
|
||||
|
||||
1. A [`<podcast:medium>`](https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md#medium) tag with a value of `publisher`.
|
||||
2. A valid [`<podcast:guid>`](https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md#guid).
|
||||
3. One or more [`<podcast:remoteItem>`](https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md#remoteItem) tags that link to podcast feeds.
|
||||
1. A [`<podcast:medium>`](../../tags/medium.md) tag with a value of `publisher`.
|
||||
2. A valid [`<podcast:guid>`](../../tags/guid.md).
|
||||
3. One or more [`<podcast:remoteItem>`](../../tags/remote-item.md) tags that link to podcast feeds.
|
||||
|
||||
### Example
|
||||
|
||||
The following example shows a publisher feed that links to all of the feeds published by the "AgileSet Media" entity.
|
||||
This feed also makes use of the [`<podcast:person>`](https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md#person) tag to define a responsible person at the
|
||||
This feed also makes use of the [`<podcast:person>`](../../tags/person.md) tag to define a responsible person at the
|
||||
publishing entity.
|
||||
|
||||
```xml
|
||||
|
@ -70,16 +71,16 @@ publishing entity.
|
|||
|
||||
## Linking to Publisher Feeds
|
||||
|
||||
While not strictly required, adding a reference to the publisher feed from the "child" feeds is a good idea, as it
|
||||
makes discovery of your other content much easier. Podcast apps can see this linkage and "walk back up the chain"
|
||||
to your publisher feed and then recommend your other podcast content to a listener. To do this, you will need to
|
||||
add a `<podcast:publisher>` tag in the `<channel>` of the "child" feed that will contain a `<podcast:remoteItem>` link to
|
||||
the
|
||||
While not strictly required, adding a reference to the publisher feed from the "child" feeds is a good idea, as it
|
||||
makes discovery of your other content much easier. Podcast apps can see this linkage and "walk back up the chain"
|
||||
to your publisher feed and then recommend your other podcast content to a listener. To do this, you will need to
|
||||
add a `<podcast:publisher>` tag in the `<channel>` of the "child" feed that will contain a `<podcast:remoteItem>` link to
|
||||
the
|
||||
publisher feed.
|
||||
|
||||
### Example
|
||||
|
||||
The following example snippet shows a podcast feed produced by "AgileSet Media" that links to the publisher feed
|
||||
The following example snippet shows a podcast feed produced by "AgileSet Media" that links to the publisher feed
|
||||
example above.
|
||||
|
||||
```xml
|
||||
|
@ -93,7 +94,7 @@ example above.
|
|||
<podcast:guid>469b403f-db2d-574c-9db9-96dbb3f6561c</podcast:guid>
|
||||
<podcast:medium>podcast</podcast:medium>
|
||||
<podcast:publisher>
|
||||
<podcast:remoteItem medium="publisher" feedGuid="003af0a0-6a45-55bf-b765-68e3d349551a" feedUrl="https://agilesetmedia.com/assets/static/feeds/publisher.xml"/>
|
||||
<podcast:remoteItem medium="publisher" feedGuid="003af0a0-6a45-55bf-b765-68e3d349551a" feedUrl="https://agilesetmedia.com/assets/static/feeds/publisher.xml"/>
|
||||
</podcast:publisher>
|
||||
<item>
|
||||
<title><![CDATA[Runnin']]></title>
|
||||
|
@ -102,4 +103,4 @@ example above.
|
|||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
```
|
||||
```
|
|
@ -2,13 +2,13 @@
|
|||
|
||||
This is the initial spec for the podcast transcript format. There are four possible formats detailed below.
|
||||
|
||||
Some transcript implementations are done in-browser. [CORS headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS)
|
||||
are required to make these files available from other websites. [A CORS tester is available here](https://cors-test.codehappy.dev/),
|
||||
Some transcript implementations are done in-browser. [CORS headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS)
|
||||
are required to make these files available from other websites. [A CORS tester is available here](https://cors-test.codehappy.dev/),
|
||||
to ensure that transcripts are available within browser-based players.
|
||||
|
||||
* **Want to support only one format?** WebVTT is used by Apple Podcasts for ingest, and also natively supported by web browsers. Because the WebVTT format is the most flexible, it's an ideal choice if you can only support one format.
|
||||
- **Want to support only one format?** WebVTT is used by Apple Podcasts for ingest, and also natively supported by web browsers. Because the WebVTT format is the most flexible, it's an ideal choice if you can only support one format.
|
||||
|
||||
The examples given below are just for convenience. In production you should ensure you are conforming to the actual spec for each format as defined in its own documentation.
|
||||
The examples given below are just for convenience. In production you should ensure you are conforming to the actual spec for each format as defined in its own documentation.
|
||||
|
||||
## WebVTT
|
||||
|
||||
|
@ -21,7 +21,8 @@ While there is no defined maximum line-length, to ensure that displaying WebVTT
|
|||
The full specification includes formatting features; these are typically not used in podcasting applications.
|
||||
|
||||
#### Snippet:
|
||||
```
|
||||
|
||||
```vtt
|
||||
WEBVTT
|
||||
|
||||
00:00:00.000 --> 00:00:05.000
|
||||
|
@ -46,13 +47,13 @@ And the enhanced monetization options are a game-changer.
|
|||
<v John>Couldn't agree more, Tom. The future looks bright.
|
||||
```
|
||||
|
||||
Example file: [example.vtt](example.vtt)
|
||||
Example file: [example.vtt](example.vtt)
|
||||
|
||||
#### Web browser support example
|
||||
|
||||
This example code will add an audio player on a web page, and display the accompanying WebVTT file as the audio plays. (Note that this basic code will not show speaker names).
|
||||
|
||||
```
|
||||
```html
|
||||
<div style="height:111px;text-align:center;">
|
||||
<audio id="vttplayer" controls preroll="none" src="https://podnews.net/audio/podnews240125.mp3?_from=P20spec">
|
||||
<track default src="https://podnews.net/audio/podnews240125.mp3.vtt">
|
||||
|
@ -70,8 +71,6 @@ document.getElementById('vttplayer').textTracks[0].addEventListener('cuechange',
|
|||
</script>
|
||||
```
|
||||
|
||||
<br><br>
|
||||
|
||||
## SRT
|
||||
|
||||
The SRT format was designed for video captions but provides a suitable solution for podcast transcripts. The SRT format contains medium-fidelity timestamps and are a
|
||||
|
@ -80,12 +79,14 @@ popular export option from transcription services. An SRT file can be generated
|
|||
SRT transcripts used for podcasts should adhere to the following specifications:
|
||||
|
||||
#### Properties:
|
||||
|
||||
- Max number of lines: 2
|
||||
- Max characters per line: 32
|
||||
- Speaker names (optional): Start a new card when the speaker changes. Include the speaker's name, followed by a colon.
|
||||
|
||||
#### Snippet:
|
||||
```
|
||||
|
||||
```srt
|
||||
1
|
||||
00:00:00,000 --> 00:00:02,760
|
||||
Sarah: In today's episode,
|
||||
|
@ -122,14 +123,14 @@ careers podcast. My question is,
|
|||
do we need a podcast trailer?
|
||||
```
|
||||
|
||||
Example file: [example.srt](example.srt)
|
||||
|
||||
Example file: [example.srt](example.srt)
|
||||
|
||||
## JSON
|
||||
|
||||
The JSON representation is a flexible format that accomodates various degrees of fidelity in a concise way. At the most precise, it enables word-by-word highlighting. This format for podcast transcripts should adhere to the following specifications.
|
||||
|
||||
#### Elements included in this representation:
|
||||
|
||||
- `<version>`: The version of JSON transcript specification
|
||||
- `<segments>`: An array of dialogue elements (segments)
|
||||
- `<speaker>`: Speaker
|
||||
|
@ -138,6 +139,7 @@ The JSON representation is a flexible format that accomodates various degrees of
|
|||
- `<body>`: Dialogue content
|
||||
|
||||
#### Snippet:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "1.0.0",
|
||||
|
@ -163,7 +165,7 @@ The JSON representation is a flexible format that accomodates various degrees of
|
|||
{
|
||||
"speaker": "Darth Vader",
|
||||
"startTime": 2.25,
|
||||
"endTime": 2.50,
|
||||
"endTime": 2.5,
|
||||
"body": "father.\n"
|
||||
},
|
||||
{
|
||||
|
@ -176,29 +178,30 @@ The JSON representation is a flexible format that accomodates various degrees of
|
|||
}
|
||||
```
|
||||
|
||||
Example file: [example.json](example.json)
|
||||
|
||||
<br><br>
|
||||
Example file: [example.json](example.json)
|
||||
|
||||
## HTML
|
||||
|
||||
The HTML transcript format provides a solution when a transcript is available but no or limited timecode data is available. HTML transcript files are considered low-fidelity and are designed to serve as an accessibility aid and provide searchable episode content. The HTML format used for podcast transcripts should adhere to the following specifications.
|
||||
|
||||
#### HTML tags used:
|
||||
|
||||
- `<cite>`: Name of the speaker (if available)
|
||||
- `<time>`: Start time of monologue (if available)
|
||||
- `<p>`: Content of monologue
|
||||
|
||||
#### Snippet:
|
||||
|
||||
```html
|
||||
<cite>Kevin:</cite>
|
||||
<time>0:00</time>
|
||||
<p>We have an update planned where we would like to give the ability to upload an artwork file for these videos</p>
|
||||
<p>
|
||||
We have an update planned where we would like to give the ability to upload an
|
||||
artwork file for these videos
|
||||
</p>
|
||||
<cite>Alban :</cite>
|
||||
<time>0:09</time>
|
||||
<p>You're triggering Tom right now with a hey, here's a cool feature.</p>
|
||||
```
|
||||
|
||||
Example file: [example.html](example.html)
|
||||
|
||||
<br><br>
|
||||
Example file: [example.html](example.html)
|
|
@ -6,14 +6,12 @@ Created: 2022-01-24
|
|||
License: CC0
|
||||
```
|
||||
|
||||
<br>
|
||||
|
||||
## Purpose
|
||||
|
||||
Using Value 4 Value apps, listeners can send sats to the podcasts they listen to. A per-minute payment is
|
||||
called a `stream`, a manually triggered payment is called a `boost` and an automatically recurring (i.e. scheduled)
|
||||
payment is called an `auto` payment. The `auto` and `boost` action types may contain a text message in the `message`
|
||||
property. The `stream` type should not.
|
||||
payment is called an `auto` payment. The `auto` and `boost` action types may contain a text message in the `message`
|
||||
property. The `stream` type should not.
|
||||
|
||||
The payload is a simple JSON structure that is encoded as a TLV in the Lightning payment when it is sent. This
|
||||
structure is explained in this document.
|
||||
|
@ -30,15 +28,11 @@ Simple Example:
|
|||
}
|
||||
```
|
||||
|
||||
<br>
|
||||
|
||||
## Specification
|
||||
|
||||
The sender of a bLIP-10 payment (i.e. - the podcast app) needs to include some metadata so the podcast host can
|
||||
identify for whom the payment is for. Most fields are optional.
|
||||
|
||||
<br>
|
||||
|
||||
### Formatting and encoding
|
||||
|
||||
A flat, key-value json structure is used where the keys listed below can be set. The json string is then encoded
|
||||
|
@ -76,51 +70,41 @@ Treated as `utf-8`, the hex value of the above json record would be:
|
|||
3a203537342c20226e616d65223a2022506f64636173746572222c202273656e6465725f6e616d65223a20225065746572227d0a
|
||||
```
|
||||
|
||||
<br>
|
||||
|
||||
### Types
|
||||
|
||||
If a field is indicated to be a `str` in the fields-list, that means it is a JSON string (within quotes). An `int` is
|
||||
a plain number.
|
||||
|
||||
<br>
|
||||
|
||||
### Fields
|
||||
|
||||
Identifying the podcast **required**: use one or more of `podcast`, `guid`, `feedID` and/or `url`. **guid preferred**
|
||||
|
||||
* `guid` (
|
||||
str) [The `<podcast:guid>` tag](https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md#guid).
|
||||
* `podcast` (str) Title of the podcast
|
||||
* `feedID` (int) ID of podcast in PodcastIndex.org directory
|
||||
* `url` (str) RSS feed URL of podcast
|
||||
|
||||
<br>
|
||||
- `guid` (
|
||||
str) [The `<podcast:guid>` tag](../../tags/guid.md).
|
||||
- `podcast` (str) Title of the podcast
|
||||
- `feedID` (int) ID of podcast in PodcastIndex.org directory
|
||||
- `url` (str) RSS feed URL of podcast
|
||||
|
||||
Identifying the episode **recommended**: use any of `episode`, `itemID` and/or `episode_guid`. **episode_guid
|
||||
preferred**
|
||||
|
||||
* `episode` (str) Title of the podcast episode
|
||||
* `itemID` (int) ID of episode in PodcastIndex.org directory
|
||||
* `episode_guid` (str) The GUID of the episode
|
||||
|
||||
<br>
|
||||
- `episode` (str) Title of the podcast episode
|
||||
- `itemID` (int) ID of episode in PodcastIndex.org directory
|
||||
- `episode_guid` (str) The GUID of the episode
|
||||
|
||||
Information about time within the episode **recommended**: use one of `ts` or `time`. **ts preferred**
|
||||
|
||||
* `ts` (int) Timestamp of when the payment was sent, in seconds, as an offset from zero (i.e. - playback position)
|
||||
* `time` (str) Timestamp of when the payment was sent, in HH:MM:SS notation, as an offset from 00:00:00
|
||||
- `ts` (int) Timestamp of when the payment was sent, in seconds, as an offset from zero (i.e. - playback position)
|
||||
- `time` (str) Timestamp of when the payment was sent, in HH:MM:SS notation, as an offset from 00:00:00
|
||||
(i.e. - playback position)
|
||||
|
||||
<br>
|
||||
|
||||
Other fields that are strongly recommended:
|
||||
|
||||
* `action` **recommended**: (str) "boost", "stream" or "auto". See Appending B of the
|
||||
- `action` **recommended**: (str) "boost", "stream" or "auto". See Appending B of the
|
||||
[value](/value.md#appendix-b---payment-actions) spec for details.
|
||||
* `app_name`: **recommended** (str) Name of sending app
|
||||
* `sender_name` **recommended** (str) Name of the sender (free text, not validated in any way)
|
||||
* `value_msat_total`: **recommended** (int) TOTAL Number of millisats for the payment before any fees are subtracted.
|
||||
- `app_name`: **recommended** (str) Name of sending app
|
||||
- `sender_name` **recommended** (str) Name of the sender (free text, not validated in any way)
|
||||
- `value_msat_total`: **recommended** (int) TOTAL Number of millisats for the payment before any fees are subtracted.
|
||||
This should be the number the listener entered into the app. Preserving
|
||||
this value is important for numerology reasons. Certain numeric values can
|
||||
have significance to the sender and/or receiver, so giving a way to show
|
||||
|
@ -130,39 +114,39 @@ Other fields that are strongly recommended:
|
|||
|
||||
Other optional fields:
|
||||
|
||||
* `message` (str) Text message to add to the payment. When this field is present, the payment is known as a
|
||||
- `message` (str) Text message to add to the payment. When this field is present, the payment is known as a
|
||||
"boostagram".
|
||||
* `app_version`: (str) Version of sending app
|
||||
* `boost_link`: (str) App specific URL containing route to podcast, episode, and/or timestamp at time of the action.
|
||||
- `app_version`: (str) Version of sending app
|
||||
- `boost_link`: (str) App specific URL containing route to podcast, episode, and/or timestamp at time of the action.
|
||||
The use case for this is sending a link along with the payment that will take the recipient to
|
||||
the exact playback position within the episode where the payment was sent.
|
||||
* `name` (str) Name for this split in value tag
|
||||
* `sender_id` (str) Static random identifier for users, not displayed by apps to prevent abuse. Apps can set this
|
||||
- `name` (str) Name for this split in value tag
|
||||
- `sender_id` (str) Static random identifier for users, not displayed by apps to prevent abuse. Apps can set this
|
||||
per-feed or app-wide. A GUID-like random identifier or a hash works well. Max 32 bytes (64 ascii
|
||||
characters). This can be a Nostr hex encoded pubkey (not NIP-19) for purposes of sender attribution.
|
||||
* `signature` (str) Optionally, this field can contain a signature for the payment, to be able to verify that the
|
||||
- `signature` (str) Optionally, this field can contain a signature for the payment, to be able to verify that the
|
||||
user who sent it is actually who they claim in the `sender_id` field. If the `sender_id` contains
|
||||
a Nostr public key, this field should contain a Nostr `sig` value as a 64-byte encoded hex string.
|
||||
For the purpose of generating the Nostr signature, the following data should be serialized:
|
||||
[0,`sender_id`,`ts`,1,[],`message`] to conform to the
|
||||
[NIP-01](https://github.com/nostr-protocol/nips/blob/master/01.md) specification. The resulting
|
||||
serialized string should be hashed with `sha256` to obtain the value.
|
||||
* `speed` (str) Speed in which the podcast was playing, in decimal notation at the time the payment was sent. So 0.5
|
||||
- `speed` (str) Speed in which the podcast was playing, in decimal notation at the time the payment was sent. So 0.5
|
||||
is half speed and 2 is double speed.
|
||||
* `boost_uuid` (str) UUID for the boost/stream/auto payment. If there are several recipients, the same identifier should be sent to all of them.
|
||||
* `uuid` (str) UUID of a payment sent out to a single recipient.
|
||||
* `reply_address` (str) The pubkey of the lightning node that can receive payments for the sender. The node given
|
||||
- `boost_uuid` (str) UUID for the boost/stream/auto payment. If there are several recipients, the same identifier should be sent to all of them.
|
||||
- `uuid` (str) UUID of a payment sent out to a single recipient.
|
||||
- `reply_address` (str) The pubkey of the lightning node that can receive payments for the sender. The node given
|
||||
must be capable of receiving keysend payments. If this field contains an "@" symbol, it should
|
||||
be interpreted as a
|
||||
"[lightning address](https://github.com/andrerfneves/lightning-address/blob/master/README.md#tldr)".
|
||||
* `reply_custom_key` (int) The custom key for routing a reply payment to the sender. This field should not be present
|
||||
- `reply_custom_key` (int) The custom key for routing a reply payment to the sender. This field should not be present
|
||||
if it is not required for payment routing.
|
||||
* `reply_custom_value` (str) The custom value for routing a reply payment to the sender. This field should not be
|
||||
- `reply_custom_value` (str) The custom value for routing a reply payment to the sender. This field should not be
|
||||
present if it is not required for payment routing.
|
||||
* `remote_feed_guid` (str) Sometimes a payment will be sent to a feed's value block by way of a `<podcast:valueTimeSplit>`
|
||||
- `remote_feed_guid` (str) Sometimes a payment will be sent to a feed's value block by way of a `<podcast:valueTimeSplit>`
|
||||
tag containing a single `<podcast:remoteItem>` tag. When that happens, this field will contain the `feedGuid` attribute from
|
||||
the `<podcast:remoteItem>` tag.
|
||||
* `remote_feed_guid` (str) Sometimes a payment will be sent to a feed's value block by way of a `<podcast:valueTimeSplit>`
|
||||
- `remote_feed_guid` (str) Sometimes a payment will be sent to a feed's value block by way of a `<podcast:valueTimeSplit>`
|
||||
tag containing a single `<podcast:remoteItem>` tag having an `itemGuid` attribute. When that happens, this field will contain
|
||||
the `itemGuid` attribute from the `<podcast:remoteItem>` tag.
|
||||
|
||||
|
@ -170,17 +154,17 @@ Other optional fields:
|
|||
|
||||
## Reference Implementations
|
||||
|
||||
*
|
||||
-
|
||||
|
||||
Breez: <https://github.com/breez/breezmobile/blob/4cf057c066d03c155964f0c4db43476cd70a57ab/lib/bloc/podcast_payments/podcast_payments_bloc.dart>
|
||||
|
||||
*
|
||||
-
|
||||
|
||||
Podverse: <https://github.com/podverse/podverse-shared/blob/fff84c0143dea4fa01241109b8666d4c0b9a6ffc/src/satoshiStream.ts>
|
||||
|
||||
* PodStation: <https://github.com/podStation/podStation/pull/249>
|
||||
* Helipad: <https://github.com/Podcastindex-org/helipad/blob/203e72dafb65e4f9e89540fbe4fc07a456a9907a/src/main.rs>
|
||||
* Rust-V4V: <https://github.com/Conshax/Rust-V4V/blob/master/src/boostagram.rs>
|
||||
- PodStation: <https://github.com/podStation/podStation/pull/249>
|
||||
- Helipad: <https://github.com/Podcastindex-org/helipad/blob/203e72dafb65e4f9e89540fbe4fc07a456a9907a/src/main.rs>
|
||||
- Rust-V4V: <https://github.com/Conshax/Rust-V4V/blob/master/src/boostagram.rs>
|
||||
|
||||
## Examples & App Specifics
|
||||
|
||||
|
@ -340,7 +324,6 @@ received value of a split being less than it should calculated from the total.
|
|||
"sender_name": "Jade Monkey",
|
||||
"message": "test"
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### Stream
|
|
@ -4,8 +4,6 @@
|
|||
, [Evan Feenstra](https://github.com/evanfeenstra) & [Paul Itoi](https://github.com/pitoi)</small><br>
|
||||
<small>September 1st, 2021</small>
|
||||
|
||||
<br>
|
||||
|
||||
## Purpose
|
||||
|
||||
Here we describe an additional "block" of XML that gives podcasters (and other media creators) a way to receive direct
|
||||
|
@ -20,8 +18,6 @@ describes (within the feed) where and how they would like payments to be sent ba
|
|||
of that media. The format is designed to be flexible enough to handle many scenarios of direct payment streaming. Even
|
||||
the use of fiat currency, if there is an API that is capable of interfacing as a receiver within this format.
|
||||
|
||||
<br>
|
||||
|
||||
## Play to Pay
|
||||
|
||||
This system can be thought of as Play-to-pay, rather than the traditional Pay-to-play paywall approach. When a
|
||||
|
@ -30,8 +26,6 @@ block, a compatible app will be expected to begin streaming micro-payments to th
|
|||
interval that makes sense for the app. The amount of each payment can be any amount the listener chooses, including
|
||||
zero. If the listener chooses not to pay to listen to this media, then the app can ignore the value block of that feed.
|
||||
|
||||
<br>
|
||||
|
||||
## Payment Intervals
|
||||
|
||||
The "time interval" for calculating payments is **always 1 minute**. However, the actual interval between when payments
|
||||
|
@ -48,18 +42,12 @@ like this also helps to minimize the impact of transaction fees on the underlyin
|
|||
Note that playback speed is not a factor in this calculation. The "one minute payment interval" refers to the minutes
|
||||
that make up the total runtime of the content, thus all payment calculations are independent of playback speed.
|
||||
|
||||
<div class="page"/>
|
||||
|
||||
<br><br>
|
||||
|
||||
## Elements
|
||||
|
||||
There are two elements that make up what we refer to as the "value block". They are a parent element that specifies the
|
||||
currency to use, and one or more child elements that specify who to pay using the currency and protocol described by the
|
||||
parent.
|
||||
|
||||
<br>
|
||||
|
||||
### Value Element
|
||||
|
||||
The `<podcast:value>` tag designates the cryptocurrency or payment layer that will be used, the transport method for
|
||||
|
@ -68,8 +56,6 @@ the payments, and a suggested amount denominated in the given cryptocurrency.
|
|||
|
||||
This element can exist at either the `<channel>` or `<item>` level.
|
||||
|
||||
<br>
|
||||
|
||||
#### Structure:
|
||||
|
||||
```xml
|
||||
|
@ -83,16 +69,12 @@ This element can exist at either the `<channel>` or `<item>` level.
|
|||
</podcast:value>
|
||||
```
|
||||
|
||||
<br>
|
||||
|
||||
#### Attributes:
|
||||
|
||||
- `type` (required) This is the service slug of the cryptocurrency or protocol layer.
|
||||
- `method` (required) This is the transport mechanism that will be used.
|
||||
- `suggested` (optional) This is an optional suggestion on how much cryptocurrency to send with each payment.
|
||||
|
||||
<br>
|
||||
|
||||
#### Explanation:
|
||||
|
||||
Using Lightning as an example, the `type` would be "lightning". Various possible `type` values will be kept
|
||||
|
@ -131,10 +113,6 @@ the Lightning Network is a millisat, which is a thousandth of a sat.
|
|||
></podcast:value>
|
||||
```
|
||||
|
||||
<br><br>
|
||||
|
||||
<div class="page"/>
|
||||
|
||||
### Value Recipient Element
|
||||
|
||||
The `valueRecipient` tag designates various destinations for payments to be sent to during consumption of the enclosed
|
||||
|
@ -145,8 +123,6 @@ This element may only exist within a parent `<podcast:value>` element.
|
|||
|
||||
There is no limit on how many `valueRecipient` elements can be present in a given `<podcast:value>` element.
|
||||
|
||||
<br>
|
||||
|
||||
#### Structure:
|
||||
|
||||
```xml
|
||||
|
@ -162,8 +138,6 @@ There is no limit on how many `valueRecipient` elements can be present in a give
|
|||
/>
|
||||
```
|
||||
|
||||
<br>
|
||||
|
||||
#### Attributes:
|
||||
|
||||
- `name` (recommended) A free-form string that designates who or what this recipient is.
|
||||
|
@ -175,8 +149,6 @@ There is no limit on how many `valueRecipient` elements can be present in a give
|
|||
- `split` (required) The number of shares of the payment this recipient will receive.
|
||||
- `fee` (optional) If this attribute is not specified, it is assumed to be false.
|
||||
|
||||
<br>
|
||||
|
||||
#### Explanation:
|
||||
|
||||
The `name` is just a human readable description of who or what this payment destination is. This could be something
|
||||
|
@ -225,10 +197,6 @@ documented [in the list maintained by Satoshis Stream](https://github.com/satosh
|
|||
If your specific application would benefit from your own `customKey:customValue` pair which will be passed along from
|
||||
the player to your app, and for which nothing already exists, add your own.
|
||||
|
||||
<br><br>
|
||||
|
||||
<div class="page"/>
|
||||
|
||||
### Payment calculation
|
||||
|
||||
The interval payment calculation is:
|
||||
|
@ -279,21 +247,21 @@ On a 190/152/38 split, each minute the payment calculation would be:
|
|||
|
||||
- Share total: 380
|
||||
|
||||
- Recipient #1 gets a payment of: 50 sats (190 / 380 * 100)
|
||||
- Recipient #2 gets a payment of: 40 sats (152 / 380 * 100)
|
||||
- Recipient #3 gets a payment of: 10 sats (38 / 380 * 100)
|
||||
- Recipient #1 gets a payment of: 50 sats (190 / 380 \* 100)
|
||||
- Recipient #2 gets a payment of: 40 sats (152 / 380 \* 100)
|
||||
- Recipient #3 gets a payment of: 10 sats (38 / 380 \* 100)
|
||||
|
||||
If an app chooses to only make a payout once every 30 minutes of listening/watching, the calculation would be the same
|
||||
after multiplying
|
||||
the per-minute payment by 30:
|
||||
|
||||
- Interval payout: 3000 sats (100 * 30)
|
||||
- Interval payout: 3000 sats (100 \* 30)
|
||||
|
||||
- Shares total: 380
|
||||
|
||||
- Recipient #1 gets a payment of: 1500 sats (190 / 380 * 3000)
|
||||
- Recipient #2 gets a payment of: 1200 sats (152 / 380 * 3000)
|
||||
- Recipient #3 gets a payment of: 300 sats (38 / 380 * 3000)
|
||||
- Recipient #1 gets a payment of: 1500 sats (190 / 380 \* 3000)
|
||||
- Recipient #2 gets a payment of: 1200 sats (152 / 380 \* 3000)
|
||||
- Recipient #3 gets a payment of: 300 sats (38 / 380 \* 3000)
|
||||
|
||||
As shown above, the once per minute calculation does not have to actually be sent every minute. A longer payout period
|
||||
can be chosen. But,
|
||||
|
@ -301,10 +269,6 @@ the once-per-minute nature of the payout still remains in order for listeners an
|
|||
measure and calculate how much
|
||||
they will spend and charge.
|
||||
|
||||
<br><br>
|
||||
|
||||
<div class="page"/>
|
||||
|
||||
### Supported Currencies and Protocols
|
||||
|
||||
The value block is designed to be flexible enough to handle most any cryptocurrency, and even fiat currencies with a
|
||||
|
@ -321,8 +285,6 @@ widely implemented as of now.
|
|||
|
||||
[AMP]: https://bitcoinops.org/en/topics/multipath-payments/
|
||||
|
||||
<br>
|
||||
|
||||
#### Lightning
|
||||
|
||||
For the `<podcast:value>` tag, the following attributes MUST be used:
|
||||
|
@ -359,8 +321,6 @@ When sending a payment containing application specific data, the client must use
|
|||
this specification, the inclusion of the TLV record with type `7629169`, as
|
||||
defined [here](blip-0010.md), in order to correctly route the payment to the corresponding receiver
|
||||
|
||||
<br>
|
||||
|
||||
##### Example
|
||||
|
||||
This is a live, working example of a Lightning keysend value block in production. It designates four recipients for
|
||||
|
@ -485,8 +445,6 @@ and guest.
|
|||
</channel>
|
||||
```
|
||||
|
||||
<br>
|
||||
|
||||
### TLV Records and Extensions
|
||||
|
||||
Lightning payments are performed using lightning messages as specified
|
||||
|
@ -500,32 +458,28 @@ found at the document [TLV record registry](https://github.com/satoshisstream/sa
|
|||
. In special, the section _Fields used in customKey / customValue Pairs_ documents the known use cases for
|
||||
the `customKey` and `customValue` attributes.
|
||||
|
||||
<br>
|
||||
|
||||
### Payment Actions
|
||||
|
||||
There are currently 3 payment "actions" that are defined in the BLIP-10 TLV extension that is embedded in the payment
|
||||
payload: "stream", "boost" and "auto".
|
||||
payload: "stream", "boost" and "auto".
|
||||
|
||||
* `stream` - This means the payment is a timed interval payment (i.e. - every minute) that is sent or queued while the
|
||||
user is engaged in active listening/viewing of the content.
|
||||
* `boost` - This means the payment is a user generated one-time payment that happens in response to a user initiated
|
||||
action like a "Boost" button push, or some other clearly labeled payment initiation function. These
|
||||
types of payments can contain textual messages (i.e. - a boostagram).
|
||||
* `auto` - This means the payment was an app initiated payment that recurs at a specific long-form interval such as
|
||||
weekly, monthly, yearly, etc. The user configures an interval and payment amount in the app and the app
|
||||
then sends that amount at the specified time when each interval passes.
|
||||
|
||||
<br>
|
||||
- `stream` - This means the payment is a timed interval payment (i.e. - every minute) that is sent or queued while the
|
||||
user is engaged in active listening/viewing of the content.
|
||||
- `boost` - This means the payment is a user generated one-time payment that happens in response to a user initiated
|
||||
action like a "Boost" button push, or some other clearly labeled payment initiation function. These
|
||||
types of payments can contain textual messages (i.e. - a boostagram).
|
||||
- `auto` - This means the payment was an app initiated payment that recurs at a specific long-form interval such as
|
||||
weekly, monthly, yearly, etc. The user configures an interval and payment amount in the app and the app
|
||||
then sends that amount at the specified time when each interval passes.
|
||||
|
||||
### Value Recipient Address Types
|
||||
|
||||
There are a few different available recipient address types:
|
||||
|
||||
* `node` - The public address of a node. For instance, in the `lightning` value type this would represent a node's
|
||||
public address.
|
||||
* `lnaddress` - A so-called "lightning address", which takes the form of an email address that gets resolved into an
|
||||
options file which holds the underlying destinations for payment. See the full document
|
||||
[here](lnaddress.md) for explanation.
|
||||
- `node` - The public address of a node. For instance, in the `lightning` value type this would represent a node's
|
||||
public address.
|
||||
- `lnaddress` - A so-called "lightning address", which takes the form of an email address that gets resolved into an
|
||||
options file which holds the underlying destinations for payment. See the full document
|
||||
[here](lnaddress.md) for explanation.
|
||||
|
||||
More recipient address types will be added in the future.
|
||||
More recipient address types will be added in the future.
|
|
@ -6,54 +6,45 @@ While the wholistic RSS namespace for podcasting is meant to synthesize the frag
|
|||
|
||||
For all new episodes, we recommended to use
|
||||
|
||||
* either a permanent URI, e.g. `https://example.com/ep0003`
|
||||
* or a [Universally unique identifier (UUID)](https://en.wikipedia.org/wiki/Universally_unique_identifier) e.g. `7c029615-a810-5214-9342-eee73f58435d`
|
||||
|
||||
The GUID of an episode should never change, even if a new version of the episode is being published, otherwise this episode will be duplicated downstream.
|
||||
- either a permanent URI, e.g. `https://example.com/ep0003`
|
||||
- or a [Universally unique identifier (UUID)](https://en.wikipedia.org/wiki/Universally_unique_identifier) e.g. `7c029615-a810-5214-9342-eee73f58435d`
|
||||
|
||||
The GUID of an episode should never change, even if a new version of the episode is being published, otherwise this episode will be duplicated downstream.
|
||||
|
||||
Consumers of podcast feeds should fall back to the enclosure URL or the namespaced UUIDv5 (SHA1 Hash with ns:URL) of the enclosure URL, if the value is not set – see https://github.com/Podcastindex-org/podcast-namespace/issues/186#issuecomment-932742468 for more details.
|
||||
|
||||
|
||||
|
||||
#### Examples
|
||||
|
||||
Only one entry per `<item>` is valid:
|
||||
|
||||
```xml
|
||||
<guid>https://example.com/ep0003</guid>
|
||||
<guid isPermaLink="false">7c029615-a810-5214-9342-eee73f58435d</guid>
|
||||
```
|
||||
|
||||
(`isPermaLink` is optional, its default value is true –)
|
||||
|
||||
<br><br>
|
||||
|
||||
|
||||
## Episode Description and Summary
|
||||
|
||||
If you use `<itunes:summary>`, it should be an actual summary of the episode, in one or two sentences, and not a copy of the description. Be aware that Apple dropped `<itunes:summary>` from [their spec](https://help.apple.com/itc/podcasts_connect/#/itcb54353390) but many other clients still support it.
|
||||
|
||||
The [original RSS specification](https://cyber.harvard.edu/rss/rss.html#hrelementsOfLtitemgt) defined `description` as follows:
|
||||
|
||||
> A channel may contain any number of `<item>`s. An item may represent a "story" -- much like a story in a newspaper or magazine; if so its description is a synopsis of the story, and the link points to the full story. An item may also be complete in itself, if so, the description contains the text (entity-encoded HTML is allowed; see examples) …
|
||||
|
||||
There also exists `<content:encoded>` for dedicated HTML episode notes [[rssboard.org]](https://www.rssboard.org/rss-profile#namespace-elements-content-encoded), but more and more publishers switch to only having `<description>`.
|
||||
|
||||
When your description contains HTML, we recommend to wrap it into `<![CDATA[` and `]]>`. See https://podnews.net/article/html-episode-notes-in-podcast-rss and https://podnews.net/article/how-podcast-show-notes-display for more information about supported HTML tags in different clients. Typical are `<p>`, `<br/>`, `<a href=""> </a>`, `<ul>` and `<li>`.
|
||||
|
||||
|
||||
|
||||
<br><br>
|
||||
|
||||
## Feed Paging and Archiving (RFC5005)
|
||||
|
||||
To be able to put more metadata into RSS feeds, while keeping the full archive of all old episodes becomes a challange for some podcasters and podcast clients. A typical workaround in the industry is to only render the full metadata of the newest episodes, where there is already a proper solution since 2007: [RFC5005](https://tools.ietf.org/html/rfc5005)
|
||||
|
||||
There are already a few players implementing RFC5005 for a while, but . Adoption from clients is sporadic. A new/different standard wouldn't help though because I'd say RFC5005 does all that's required. We need to be louder about the existence of the standard and ask for it's implementation from all sides.
|
||||
|
||||
|
||||
|
||||
#### Examples
|
||||
|
||||
Excerpt from https://feeds.metaebene.me/freakshow/mp3 feed page 2,
|
||||
Excerpt from https://feeds.metaebene.me/freakshow/mp3 feed page 2,
|
||||
parent `<channel>`:
|
||||
|
||||
```xml
|
||||
|
@ -63,9 +54,6 @@ parent `<channel>`:
|
|||
<atom:link rel="last" href="https://freakshow.fm/feed/mp3?paged=9" />
|
||||
```
|
||||
|
||||
|
||||
|
||||
## WebSub and Podping.cloud
|
||||
|
||||
TBD
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
`<podcast:chapters>`
|
||||
|
||||
Links to an external file (see example file) containing chapter data for the episode. See the [jsonChapters.md](https://github.com/Podcastindex-org/podcast-namespace/blob/main/chapters/jsonChapters.md) file for a description of the chapter file syntax. And, see the [example.json](https://github.com/Podcastindex-org/podcast-namespace/blob/main/chapters/example.json) example file for a real world example.
|
||||
Links to an external file (see example file) containing chapter data for the episode. See the [jsonChapters.md](../examples/chapters/jsonChapters.md) file for a description of the file syntax for chapters syntax. And, see the [example.json](../examples/chapters/example.json) example file for a real world example.
|
||||
|
||||
Benefits with this approach are that chapters do not require altering audio files, and the chapters can be edited after publishing, since they are a separate file that can be requested on playback (or cached with download). JSON chapter information also allows chapters to be displayed by a wider range of playback tools, including web browsers (which typically have no access to ID3 tags), thus greatly simplifying chapter support; and images can be retrieved on playback, rather than bloating the filesize of the audio. The data held is compatible with normal ID3 tags, thus requiring no additional work for the publisher.
|
||||
|
||||
|
|
|
@ -2,15 +2,15 @@
|
|||
|
||||
`<podcast:chat>`
|
||||
|
||||
This element allows a podcaster to attach information to either the `<channel>`, `<item>` or [`<podcast:liveItem>`](liveItem.md) about where the "official" chat for either the podcast or a specific episode or live event is to be found. Just like [`<podcast:socialInteract>`](socialInteract.md) functions for social media, the `<podcast:chat>` tag will function for ephemeral chat. There are many protocols in use across the internet for chat based communication. This tag is meant to be flexible enough to adapt to whichever protocol the podcaster wants to use.
|
||||
This element allows a podcaster to attach information to either the `<channel>`, `<item>` or [`<podcast:liveItem>`](live-item.md) about where the "official" chat for either the podcast or a specific episode or live event is to be found. Just like [`<podcast:socialInteract>`](social-interact.md) functions for social media, the `<podcast:chat>` tag will function for ephemeral chat. There are many protocols in use across the internet for chat based communication. This tag is meant to be flexible enough to adapt to whichever protocol the podcaster wants to use.
|
||||
|
||||
This element's presence at a particular level governs how it should be treated. If found at the `<item>` or [`<podcast:liveItem>`](liveItem.md) level, this should be treated as the information for that specific episode, overriding what is at the `<channel>` level. If this tag is found at the `<channel>` level, it would be considered the chat for the entire podcast and is recommended to be an "always on" chat room experience.
|
||||
This element's presence at a particular level governs how it should be treated. If found at the `<item>` or [`<podcast:liveItem>`](live-item.md) level, this should be treated as the information for that specific episode, overriding what is at the `<channel>` level. If this tag is found at the `<channel>` level, it would be considered the chat for the entire podcast and is recommended to be an "always on" chat room experience.
|
||||
|
||||
If a podcast has an "always on" style chat service it is recommended to link that at the `<channel>` level and do not use the `<podcast:chat>` tag at the `<item>` or [`<podcast:liveItem>`](liveItem.md) level.
|
||||
If a podcast has an "always on" style chat service it is recommended to link that at the `<channel>` level and do not use the `<podcast:chat>` tag at the `<item>` or [`<podcast:liveItem>`](live-item.md) level.
|
||||
|
||||
### Parent
|
||||
|
||||
`<channel>` or `<item>` or [`<podcast:liveItem>`](liveItem.md)
|
||||
`<channel>` or `<item>` or [`<podcast:liveItem>`](live-item.md)
|
||||
|
||||
### Count
|
||||
|
||||
|
@ -19,7 +19,7 @@ Single
|
|||
### Attributes
|
||||
|
||||
- `server` (required) The fqdn of a chat server that serves as the "bootstrap" server to connect to.
|
||||
- `protocol` (required) The [protocol](../../chatprotocols.txt) in use on the server.
|
||||
- `protocol` (required) The [protocol](../chatprotocols.txt) in use on the server.
|
||||
- `accountId` (recommended) The account id of the podcaster on the server or platform being connected to.
|
||||
- `space` (optional) Some chat systems have a notion of a chat "space" or "room" or "topic". This attribute will serve that purpose.
|
||||
|
||||
|
|
|
@ -4,11 +4,11 @@
|
|||
|
||||
The `contentLink` tag is used to indicate that the content being delivered by the parent element can be found at an external location instead of, or in addition to, the tag itself within an app. In most instances it is used as a fallback link for the user to use when the app itself can't handle a certain content delivery directly.
|
||||
|
||||
For instance, perhaps a podcast feed specifies a [`<podcast:liveItem>`](liveItem.md) to deliver a live stream to apps. The feed may also give a `<podcast:contentLink>` pointing to YouTube and Twitch versions of the live stream as well, just in case the listener uses an app that doesn't fully support live streaming content.
|
||||
For instance, perhaps a podcast feed specifies a [`<podcast:liveItem>`](live-item.md) to deliver a live stream to apps. The feed may also give a `<podcast:contentLink>` pointing to YouTube and Twitch versions of the live stream as well, just in case the listener uses an app that doesn't fully support live streaming content.
|
||||
|
||||
### Parent
|
||||
|
||||
`<item>` or [`<podcast:liveItem>`](liveItem.md)
|
||||
`<item>` or [`<podcast:liveItem>`](live-item.md)
|
||||
|
||||
### Count
|
||||
|
||||
|
@ -30,7 +30,7 @@ The node value is a free-form string meant to explain to the user where this con
|
|||
<podcast:contentLink href="https://www.youtube.com/watch?v=8c7HWZROxD8">Watch this episode on YouTube!</podcast:contentLink>
|
||||
```
|
||||
|
||||
(under [`<podcast:liveItem>`](liveItem.md))
|
||||
(under [`<podcast:liveItem>`](live-item.md))
|
||||
|
||||
```xml
|
||||
<podcast:contentLink href="https://youtube.com/blahblah/livestream">Live on YouTube!</podcast:contentLink>
|
|
@ -6,7 +6,7 @@ This tag lists possible donation/funding links for the podcast. The content of t
|
|||
|
||||
### Parent
|
||||
|
||||
`<channel>`, `<item>` or [`<podcast:liveItem>`](liveItem.md)
|
||||
`<channel>`, `<item>` or [`<podcast:liveItem>`](live-item.md)
|
||||
|
||||
### Count
|
||||
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
`<podcast:integrity>`
|
||||
|
||||
This element defines a method of verifying integrity of the media given either an [SRI-compliant integrity string](https://www.w3.org/TR/SRI/) (preferred) or a base64 encoded PGP signature. This element is optional within a [`<podcast:alternateEnclosure>`](alternateEnclosure.md) element. It allows to ensure that the file has not been tampered with.
|
||||
This element defines a method of verifying integrity of the media given either an [SRI-compliant integrity string](https://www.w3.org/TR/SRI/) (preferred) or a base64 encoded PGP signature. This element is optional within a [`<podcast:alternateEnclosure>`](alternate-enclosure.md) element. It allows to ensure that the file has not been tampered with.
|
||||
|
||||
### Parent
|
||||
|
||||
[`<podcast:alternateEnclosure>`](alternateEnclosure.md)
|
||||
[`<podcast:alternateEnclosure>`](alternate-enclosure.md)
|
||||
|
||||
### Count
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
`<podcast:license>`
|
||||
|
||||
This element defines a license that is applied to the audio/video content of a single episode, or the audio/video of the podcast as a whole. Custom licenses must always include a url attribute. Implementors are encouraged to read the license tag companion [document](../../license/license.md) for a more complete picture of what this tag is intended to accomplish.
|
||||
This element defines a license that is applied to the audio/video content of a single episode, or the audio/video of the podcast as a whole. Custom licenses must always include a url attribute. Implementors are encouraged to read the license tag companion [document](../examples/license/license.md) for a more complete picture of what this tag is intended to accomplish.
|
||||
|
||||
### Parent
|
||||
|
||||
|
@ -14,7 +14,7 @@ Single
|
|||
|
||||
### Node Value
|
||||
|
||||
The node value must be a lower-cased reference to a license "identifier" defined in the companion [license list](../../license/licenses.json) file if the license being used is a well-known, public license. Or, if it is a custom license, it must be a free form abbreviation of the name of the license as you reference it publicly. Please do not exceed `128 characters` for the node value or it may be truncated by aggregators.
|
||||
The node value must be a lower-cased reference to a license "identifier" defined in the companion [license list](../examples/license/licenses.json) file if the license being used is a well-known, public license. Or, if it is a custom license, it must be a free form abbreviation of the name of the license as you reference it publicly. Please do not exceed `128 characters` for the node value or it may be truncated by aggregators.
|
||||
|
||||
### Attributes
|
||||
|
||||
|
|
|
@ -2,14 +2,14 @@
|
|||
|
||||
`<podcast:location>`
|
||||
|
||||
_Revised in 2025:_ This tag is intended to describe the location of editorial focus, or the source of production
|
||||
_Revised in 2025:_ This tag is intended to describe the location of editorial focus, or the source of production
|
||||
for a podcast's content; answering the question "where is this podcast about?" or "where was the podcast made?".
|
||||
The tag has many use cases and is one of the more complex ones.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> You are **highly encouraged** to read the
|
||||
full [implementation document](https://github.com/Podcastindex-org/podcast-namespace/blob/main/location/location.md)
|
||||
before starting to code for it. This document includes rationale and example code.
|
||||
> You are **highly encouraged** to read the
|
||||
> full [implementation document](../examples/location/location.md)
|
||||
> before starting to code for it. This document includes rationale and example code.
|
||||
|
||||
### Parent
|
||||
|
||||
|
@ -21,9 +21,9 @@ Multiple
|
|||
|
||||
### Node Value
|
||||
|
||||
This is a free-form string meant to be a human readable location. It may conform to conventional location
|
||||
verbiage (i.e. "Austin, TX"), but it shouldn't be depended on to be parseable in any specific way. This value
|
||||
cannot be blank. Please do not exceed `128 characters` for the node value or it may be truncated by aggregators. This
|
||||
This is a free-form string meant to be a human readable location. It may conform to conventional location
|
||||
verbiage (i.e. "Austin, TX"), but it shouldn't be depended on to be parseable in any specific way. This value
|
||||
cannot be blank. Please do not exceed `128 characters` for the node value or it may be truncated by aggregators. This
|
||||
value is mostly intended as a "display" value and it's **highly encouraged** to use the other recommended attributes to
|
||||
define the actual location parameters.
|
||||
|
||||
|
@ -37,11 +37,12 @@ define the actual location parameters.
|
|||
- **country:** (recommended) A two-letter code for the country, following [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2)
|
||||
|
||||
> [!NOTE]
|
||||
> While all elements are "recommended", **the location tag works best when all elements are populated.** The [implementation document](https://github.com/Podcastindex-org/podcast-namespace/blob/main/location/location.md) goes into more detail. An example location generator (entirely in JavaScript) is [over here](https://jamescridland.github.io/podcast-location-generator/).
|
||||
> While all elements are "recommended", **the location tag works best when all elements are populated.** The [implementation document](../examples/location/location.md) goes into more detail. An example location generator (entirely in JavaScript) is [over here](https://jamescridland.github.io/podcast-location-generator/).
|
||||
|
||||
### Examples
|
||||
|
||||
- A podcast *made in* [Austin TX](https://www.openstreetmap.org/relation/113314) in the US:
|
||||
- A podcast _made in_ [Austin TX](https://www.openstreetmap.org/relation/113314) in the US:
|
||||
|
||||
```xml
|
||||
<podcast:location
|
||||
rel="subject"
|
||||
|
@ -52,6 +53,7 @@ define the actual location parameters.
|
|||
```
|
||||
|
||||
- A podcast about the [Birmingham Civil Rights Museum](https://www.openstreetmap.org/relation/6930627) in Birmingham, AL:
|
||||
|
||||
```xml
|
||||
<podcast:location
|
||||
rel="subject"
|
||||
|
@ -61,7 +63,8 @@ define the actual location parameters.
|
|||
>Birmingham Civil Rights Museum</podcast:location>
|
||||
```
|
||||
|
||||
- A podcast *made in* [Marlow, England](https://www.openstreetmap.org/relation/3727240) - *about* [Dreamworld](https://www.openstreetmap.org/relation/16317988), a themepark in Australia:
|
||||
- A podcast _made in_ [Marlow, England](https://www.openstreetmap.org/relation/3727240) - _about_ [Dreamworld](https://www.openstreetmap.org/relation/16317988), a themepark in Australia:
|
||||
|
||||
```xml
|
||||
<podcast:location
|
||||
rel="creator"
|
||||
|
|
|
@ -25,7 +25,7 @@ The node value is a string denoting one of the following possible values:
|
|||
- `audiobook` - Specific types of audio with one item per feed, or where items represent chapters within the book.
|
||||
- `newsletter` - Describes a feed of curated written articles. Newsletter articles now sometimes have an spoken version audio enclosure attached.
|
||||
- `blog` - Describes a feed of informally written articles. Similar to `newsletter` but more informal as in a traditional blog platform style.
|
||||
- `publisher` - Describes a feed that links to other feeds a publisher owns using the [`<podcast:remoteItem>`](remoteItem.md) element. To understand the structure of how "publisher" feeds work, please see the dedicated document [here](../../publishers/publishers.md) and the [`<podcast:publisher>`](publisher.md) tag [here](./publisher.md).
|
||||
- `publisher` - Describes a feed that links to other feeds a publisher owns using the [`<podcast:remoteItem>`](remote-item.md) element. To understand the structure of how "publisher" feeds work, please [see the dedicated document](../examples/publishers/publishers.md) and the [`<podcast:publisher>` tag](publisher.md).
|
||||
- `course` - A feed of training material (audio or video courses) with each item being a session or chapter of the course or conference track.
|
||||
|
||||
### List Mediums
|
||||
|
@ -34,9 +34,9 @@ In addition to the above mediums, each medium also has a counterpart "list" vari
|
|||
|
||||
There is also a dedicated list medium for mixed content:
|
||||
|
||||
- `mixed` - This list medium type describes a feed of [`<podcast:remoteItem>`](remoteItem.md)'s that point to different remote medium types. For instance, a single list feed might point to music, podcast and audiobook items in other feeds. An example would be a personal consumption history feed.
|
||||
- `mixed` - This list medium type describes a feed of [`<podcast:remoteItem>`](remote-item.md)'s that point to different remote medium types. For instance, a single list feed might point to music, podcast and audiobook items in other feeds. An example would be a personal consumption history feed.
|
||||
|
||||
A "list" medium feed should not be expected to have regular `<item>`'s,[`<podcast:liveItem>`](liveItem.md)'s, or any future similar new item type. Rather, a "List" feed is intended to exclusively contain one or more [`<podcast:remoteItem>`](remoteItem.md)'s, allowing one to reference a feed by its [`<podcast:guid>`](guid.md) and the guid of an item.
|
||||
A "list" medium feed should not be expected to have regular `<item>`'s,[`<podcast:liveItem>`](live-item.md)'s, or any future similar new item type. Rather, a "List" feed is intended to exclusively contain one or more [`<podcast:remoteItem>`](remote-item.md)'s, allowing one to reference a feed by its [`<podcast:guid>`](guid.md) and the guid of an item.
|
||||
|
||||
### Examples
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ Single
|
|||
|
||||
### Node value
|
||||
|
||||
The node value must be one or more [`<podcast:remoteItem>`](remoteItem.md) elements.
|
||||
The node value must be one or more [`<podcast:remoteItem>`](remote-item.md) elements.
|
||||
|
||||
### Examples
|
||||
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
`<podcast:publisher>`
|
||||
|
||||
This element allows a podcast feed to link to it's "publisher feed" parent. This is useful when a parent publishing entity wants to attest ownership over all of the podcast feeds it owns/publishes. This element must contain exactly one `<podcast:remoteItem medium="publisher">` element pointing to the publisher feed. For widest compatibility, it is highly recommended to use the `feedUrl` attribute of the [`<podcast:remoteItem>`](remoteItem.md) element in this capacity.
|
||||
This element allows a podcast feed to link to it's "publisher feed" parent. This is useful when a parent publishing entity wants to attest ownership over all of the podcast feeds it owns/publishes. This element must contain exactly one `<podcast:remoteItem medium="publisher">` element pointing to the publisher feed. For widest compatibility, it is highly recommended to use the `feedUrl` attribute of the [`<podcast:remoteItem>`](remote-item.md) element in this capacity.
|
||||
|
||||
For complete implementation details regarding publisher feeds and how to create them, please see the full publisher feed [documentation](../../publishers/publishers.md) and the `publisher` medium [here](./medium.md#medium).
|
||||
For complete implementation details regarding publisher feeds and how to create them, please see the full publisher feed [documentation](../examples/publishers/publishers.md) and the `publisher` medium [here](./medium.md).
|
||||
|
||||
### Parent
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ Using the `feedGuid` attribute is the preferred way to address a remote feed sin
|
|||
|
||||
### Parent
|
||||
|
||||
`<channel>` or [`<podcast:podroll>`](podroll.md) or [`<podcast:valueTimeSplit>`](valueTimeSplit.md) or [`<podcast:publisher>`](publisher.md)
|
||||
`<channel>` or [`<podcast:podroll>`](podroll.md) or [`<podcast:valueTimeSplit>`](value-time-split.md) or [`<podcast:publisher>`](publisher.md)
|
||||
|
||||
### Count
|
||||
|
|
@ -18,7 +18,7 @@ Multiple
|
|||
|
||||
### Attributes
|
||||
|
||||
- **protocol** (required) The [protocol](../../socialprotocols.txt) in use for interacting with the comment root post.
|
||||
- **protocol** (required) The [protocol](https://github.com/Podcastindex-org/podcast-namespace/blob/main/socialprotocols.txt) in use for interacting with the comment root post.
|
||||
- **uri** (required) The uri/url of root post comment.
|
||||
- **accountId** (recommended) The account id (on the commenting platform) of the account that created this root post.
|
||||
- **accountUrl** (optional) The public url (on the commenting platform) of the account that created this root post.
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
`<podcast:source>`
|
||||
|
||||
This element defines a uri location for a [`<podcast:alternateEnclosure>`](alternateEnclosure.md) media file. It is meant to be used as a child of the [`<podcast:alternateEnclosure>`](alternateEnclosure.md) element. At least one `<podcast:source>` element must be present within every [`<podcast:alternateEnclosure>`](alternateEnclosure.md) element.
|
||||
This element defines a uri location for a [`<podcast:alternateEnclosure>`](alternate-enclosure.md) media file. It is meant to be used as a child of the [`<podcast:alternateEnclosure>`](alternate-enclosure.md) element. At least one `<podcast:source>` element must be present within every [`<podcast:alternateEnclosure>`](alternate-enclosure.md) element.
|
||||
|
||||
### Parent
|
||||
|
||||
[`<podcast:alternateEnclosure>`](alternateEnclosure.md)
|
||||
[`<podcast:alternateEnclosure>`](alternate-enclosure.md)
|
||||
|
||||
### Count
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
This tag is used to link to a transcript or closed captions file. Multiple tags can be present for multiple transcript formats.
|
||||
|
||||
Detailed file format information and example files are [here](../../transcripts/transcripts.md).
|
||||
Detailed file format information and example files are [here](../examples/transcripts/transcripts.md).
|
||||
|
||||
### Parent
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ This element may only exist within a parent [`<podcast:value>`](value.md) elemen
|
|||
|
||||
There is no limit on how many `valueRecipient` elements can be present in a given [`<podcast:value>`](value.md) element.
|
||||
|
||||
This is a complex tag, so implementors are HIGHLY encouraged to read the companion [document](https://github.com/Podcastindex-org/podcast-namespace/blob/main/value/value.md) for a complete understanding of how this tag works and what it is capable of.
|
||||
This is a complex tag, so implementors are HIGHLY encouraged to read the companion [document](../examples/value/value.md) for a complete understanding of how this tag works and what it is capable of.
|
||||
|
||||
### Parent
|
||||
|
|
@ -2,15 +2,15 @@
|
|||
|
||||
`<podcast:valueTimeSplit>`
|
||||
|
||||
This element allows different value splits for a certain period of time. It is a combination of the concept of [`<podcast:soundbite>`](soundbite.md) and [`<podcast:remoteItem>`](remoteItem.md) where a start time and a duration is supplied with alternative value recipients. The alternative value recipients are not required to be remote, as the recipients may not have an RSS feed/item of their own to reference.
|
||||
This element allows different value splits for a certain period of time. It is a combination of the concept of [`<podcast:soundbite>`](soundbite.md) and [`<podcast:remoteItem>`](remote-item.md) where a start time and a duration is supplied with alternative value recipients. The alternative value recipients are not required to be remote, as the recipients may not have an RSS feed/item of their own to reference.
|
||||
|
||||
The `<podcast:valueTimeSplit>` element allows time-based changes of value recipient information during playback of a feed's enclosure content.
|
||||
|
||||
This can either contain one or more [`<podcast:valueRecipient>`](valueRecipient.md) tags _or_ exactly one [`<podcast:remoteItem>`](remoteItem.md) tag. If a [`<podcast:remoteItem>`](remoteItem.md) tag is present, the `remotePercentage` attribute can be defined to control how much the remote value block's [`<podcast:valueRecipient>`](valueRecipient.md) tags will receive as a whole on top of the default, non-fee [`<podcast:valueRecipient>`](valueRecipient.md) tags.
|
||||
This can either contain one or more [`<podcast:valueRecipient>`](value-recipient.md) tags _or_ exactly one [`<podcast:remoteItem>`](remote-item.md) tag. If a [`<podcast:remoteItem>`](remote-item.md) tag is present, the `remotePercentage` attribute can be defined to control how much the remote value block's [`<podcast:valueRecipient>`](value-recipient.md) tags will receive as a whole on top of the default, non-fee [`<podcast:valueRecipient>`](value-recipient.md) tags.
|
||||
|
||||
If the remote value block contains any `<podcast:valueTimeSplit>` tags, they should be ignored even if the `remoteStartTime` indicates it's in a position that would invoke them. When referencing a remote value block, only the root level block splits should be used and any `<podcast:valueTimeSplit>` children are to be ignored.
|
||||
|
||||
Fees from the default [`<podcast:valueRecipient>`](valueRecipient.md) tags should remain to be calculated before anything is taken out from `<podcast:valueTimeSplit>`.
|
||||
Fees from the default [`<podcast:valueRecipient>`](value-recipient.md) tags should remain to be calculated before anything is taken out from `<podcast:valueTimeSplit>`.
|
||||
|
||||
### Parent
|
||||
|
||||
|
@ -22,14 +22,14 @@ Multiple
|
|||
|
||||
### Node Value
|
||||
|
||||
A single [`<podcast:remoteItem>`](remoteItem.md) element OR one or more [`<podcast:valueRecipient>`](valueRecipient.md) elements.
|
||||
A single [`<podcast:remoteItem>`](remote-item.md) element OR one or more [`<podcast:valueRecipient>`](value-recipient.md) elements.
|
||||
|
||||
### Attributes
|
||||
|
||||
- `startTime` (required) - The time, in seconds, to stop using the currently active value recipient information and start using the value recipient information contained in this element.
|
||||
- `duration` (required) - How many seconds the playback app should use this element's value recipient information before switching back to the value recipient information of the parent feed.
|
||||
- `remoteStartTime` (optional) - The time in the remote item where the value split begins. Allows the timestamp to be set correctly in value metadata. If not defined, defaults to 0.
|
||||
- `remotePercentage` (optional) - The percentage of the payment the remote recipients will receive if a [`<podcast:remoteItem>`](remoteItem.md) is present. If not defined, defaults to 100. If the value is less than 0, 0 is assumed. If the value is greater than 100, 100 is assumed.
|
||||
- `remotePercentage` (optional) - The percentage of the payment the remote recipients will receive if a [`<podcast:remoteItem>`](remote-item.md) is present. If not defined, defaults to 100. If the value is less than 0, 0 is assumed. If the value is greater than 100, 100 is assumed.
|
||||
|
||||
### Example (Remote Item)
|
||||
|
|
@ -6,7 +6,7 @@ This element designates the cryptocurrency or payment layer that will be used, t
|
|||
|
||||
This element can exist at either the `<channel>` or `<item>` level. When it exists at the `<item>` level, it should be treated as an "override" of whatever is defined at the `<channel>` level.
|
||||
|
||||
This is a complex tag, so implementors are HIGHLY encouraged to read the companion [document](https://github.com/Podcastindex-org/podcast-namespace/blob/main/value/value.md) for a complete understanding of how this tag works and what it is capable of.
|
||||
This is a complex tag, so implementors are HIGHLY encouraged to read the companion [document](../examples/value/value.md) for a complete understanding of how this tag works and what it is capable of.
|
||||
|
||||
### Parent
|
||||
|
||||
|
@ -18,7 +18,7 @@ Multiple
|
|||
|
||||
### Node Value
|
||||
|
||||
The node value must be one or more [`<podcast:valueRecipient>`](valueRecipient.md) elements.
|
||||
The node value must be one or more [`<podcast:valueRecipient>`](value-recipient.md) elements.
|
||||
|
||||
### Attributes
|
||||
|
||||
|
|
|
@ -1,23 +1,19 @@
|
|||
# Podcasting 2.0
|
||||
|
||||
|
||||
Podcasting 2.0 is a set of forward looking ideas combined with the technology to realize them. It's a vision for what the podcast listener experience can and should
|
||||
be. That experience has stagnated for over a decade, with almost all of the improvements coming in isolated sections of the ecosystem. There hasn't been a single,
|
||||
unified vision from the podcasting community acting together with one voice. So, we've ended up with fragments of innovation across the podcasting landscape with no
|
||||
central driving goal in mind. Podcasting 2.0 is the expression of what that goal could be.
|
||||
Podcasting 2.0 is a set of forward looking ideas combined with the technology to realize them. It's a vision for what the podcast listener experience can and should
|
||||
be. That experience has stagnated for over a decade, with almost all of the improvements coming in isolated sections of the ecosystem. There hasn't been a single,
|
||||
unified vision from the podcasting community acting together with one voice. So, we've ended up with fragments of innovation across the podcasting landscape with no
|
||||
central driving goal in mind. Podcasting 2.0 is the expression of what that goal could be.
|
||||
|
||||
Stated eloquently, the aim of Podcasting 2.0 is this:
|
||||
|
||||
> "I think our focus should be 100% on improving the podcasting experience in an open-standard way that allows every player to innovate faster
|
||||
> and better than any one company could do on their own. This is our best bet at avoiding one company emerging as the monopoly of podcasting."
|
||||
>
|
||||
> --Tom Rossi [Tom Rossi](https://podcastindex.social/@tomrossi7/105839063781381384)
|
||||
> "I think our focus should be 100% on improving the podcasting experience in an open-standard way that allows every player to innovate faster
|
||||
> and better than any one company could do on their own. This is our best bet at avoiding one company emerging as the monopoly of podcasting."
|
||||
>
|
||||
> --Tom Rossi [Tom Rossi](https://podcastindex.social/@tomrossi7/105839063781381384)
|
||||
|
||||
|
||||
<br>
|
||||
|
||||
Closed ecosystems can not innovate any better or faster than open systems. We should know this by now. The open world of RSS based podcasting can not only
|
||||
keep pace with closed systems, it can exceed them easily. Podcasting 2.0 is simply the technological expression of this idea. We can make a better podcasting
|
||||
Closed ecosystems can not innovate any better or faster than open systems. We should know this by now. The open world of RSS based podcasting can not only
|
||||
keep pace with closed systems, it can exceed them easily. Podcasting 2.0 is simply the technological expression of this idea. We can make a better podcasting
|
||||
experience for listeners than they can get behind any walled garden - no matter how high or expensive those walls are.
|
||||
|
||||
There are three parts to Podcasting 2.0:
|
||||
|
@ -26,68 +22,61 @@ There are three parts to Podcasting 2.0:
|
|||
2. Web app friendliness
|
||||
3. Value for Value
|
||||
|
||||
<br><br>
|
||||
|
||||
## Step 1. Adopt the "podcast" Namespace
|
||||
|
||||
To be a Podcasting 2.0 compliant podcast you need to first declare the "podcast"
|
||||
[namespace](https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md) in your feed if you self-host your podcast. If you
|
||||
use a hosting company for your podcast, check [here](https://podcastindex.org/apps) for a list of hosts that now support the new namespace.
|
||||
[namespace](/docs/1.0.md) in your feed if you self-host your podcast. If you
|
||||
use a hosting company for your podcast, check [PodcastIndex.org/apps](https://podcastindex.org/apps) or [Podcasting2.org/apps](https://podcasting2.org/apps) for a list of hosts that now support the new namespace.
|
||||
|
||||
The namespace gives you (and your listeners) access to many new features:
|
||||
|
||||
- Transcripts: You can deliver a text transcript along with your episode to make your content more accessible to those with hearing challenges, or for those
|
||||
learning your language.
|
||||
- Funding: This points listeners back to a donation or membership page that they can click on to join or donate money to your show.
|
||||
- Chapters: MP3 files have had the ability to embed chapters for many years. But, now you can create a "chapters file" that gets delivered along with your
|
||||
episode to allow rich content like images, embedded web pages, titles and silent markers. This chapters file lives on the web, so it can be
|
||||
changed later after publishing without uploading a new audio file or changing your episode.
|
||||
- Soundbites: Specify short bits of your episode to serve as an intro or a teaser for your show.
|
||||
- Persons: You can give multiple bio's in each episode that have short "about" descriptions of the people on that episode (like hosts, guests, etc.). Did you
|
||||
interview someone cool? Point to their head shot image and link to their Wikipedia page or their blog. It makes searching for people within podcasts
|
||||
easy and enjoyable.
|
||||
- Location: Is your podcast about a specific place? Tag it's location right in the episode or podcast feed to let people know. It makes your show more
|
||||
discoverable on the web.
|
||||
- Named Seasons: Seasons have been around for a while, but now you can name them. This way you can avoid the hassle of trying to cram everything in your show title.
|
||||
- Trailers: Trailers can exist as a separate audio/video file, outside of an actual episode. They can be tied to specific seasons for promotional consistency.
|
||||
- License: Want to protect your work from derivative use, or the other way around? You can assign a specific license to the content of the entire podcast, or different
|
||||
licenses for each episode. You can use preexisting open source licenses or write one yourself.
|
||||
- Alternate Enclosures: It's now possible to deliver alternative versions of content in the same podcast feed. You can assign video versions of an audio podcast. Or,
|
||||
maybe a low (or high) bandwidth version of the main audio/video file. Or, perhaps you want to deliver a version of your content over another
|
||||
method like IPFS or WebTorrent.
|
||||
|
||||
|
||||
<br><br>
|
||||
- Transcripts: You can deliver a text transcript along with your episode to make your content more accessible to those with hearing challenges, or for those
|
||||
learning your language.
|
||||
- Funding: This points listeners back to a donation or membership page that they can click on to join or donate money to your show.
|
||||
- Chapters: MP3 files have had the ability to embed chapters for many years. But, now you can create a "chapters file" that gets delivered along with your
|
||||
episode to allow rich content like images, embedded web pages, titles and silent markers. This chapters file lives on the web, so it can be
|
||||
changed later after publishing without uploading a new audio file or changing your episode.
|
||||
- Soundbites: Specify short bits of your episode to serve as an intro or a teaser for your show.
|
||||
- Persons: You can give multiple bio's in each episode that have short "about" descriptions of the people on that episode (like hosts, guests, etc.). Did you
|
||||
interview someone cool? Point to their head shot image and link to their Wikipedia page or their blog. It makes searching for people within podcasts
|
||||
easy and enjoyable.
|
||||
- Location: Is your podcast about a specific place? Tag it's location right in the episode or podcast feed to let people know. It makes your show more
|
||||
discoverable on the web.
|
||||
- Named Seasons: Seasons have been around for a while, but now you can name them. This way you can avoid the hassle of trying to cram everything in your show title.
|
||||
- Trailers: Trailers can exist as a separate audio/video file, outside of an actual episode. They can be tied to specific seasons for promotional consistency.
|
||||
- License: Want to protect your work from derivative use, or the other way around? You can assign a specific license to the content of the entire podcast, or different
|
||||
licenses for each episode. You can use preexisting open source licenses or write one yourself.
|
||||
- Alternate Enclosures: It's now possible to deliver alternative versions of content in the same podcast feed. You can assign video versions of an audio podcast. Or,
|
||||
maybe a low (or high) bandwidth version of the main audio/video file. Or, perhaps you want to deliver a version of your content over another
|
||||
method like IPFS or WebTorrent.
|
||||
|
||||
## Step 2. Be Web App Friendly
|
||||
|
||||
Next, you need to confirm that your feed does not have "mixed content", and that it supports CORS where necessary. Again, if you use a hosting company for your podcast
|
||||
just [make sure](https://podcastindex.org/apps) they are supporting Podcasting 2.0 features. If your host isn't on that list, send them a message and let them know you
|
||||
Next, you need to confirm that your feed does not have "mixed content", and that it supports CORS where necessary. Again, if you use a hosting company for your podcast
|
||||
just [make sure](https://podcastindex.org/apps) they are supporting Podcasting 2.0 features. If your host isn't on that list, send them a message and let them know you
|
||||
want these new features.
|
||||
|
||||
If you self-host your podcast, take note of these two things...
|
||||
|
||||
#### Mixed Content
|
||||
|
||||
"Mixed content" is what happens when part of your podcast (like your feed) is served securely and other parts of it (like the images or the audio) are not. When this
|
||||
happens, web-based podcast players cannot play your content or see your images. Modern web browsers are very strict about security and "mixed content" is blocked by
|
||||
most of them. Make sure your entire podcast is served over HTTPS with a valid certificate.
|
||||
"Mixed content" is what happens when part of your podcast (like your feed) is served securely and other parts of it (like the images or the audio) are not. When this
|
||||
happens, web-based podcast players cannot play your content or see your images. Modern web browsers are very strict about security and "mixed content" is blocked by
|
||||
most of them. Make sure your entire podcast is served over HTTPS with a valid certificate.
|
||||
|
||||
#### CORS
|
||||
|
||||
Another problem that can hamper your content under certain circumstances (like embedded pages in chapters) is CORS. If you serve content along with your podcast that
|
||||
requires cross-origin access, please be sure to enable the correct CORS policies on the domain the content is served from. You can find details
|
||||
Another problem that can hamper your content under certain circumstances (like embedded pages in chapters) is CORS. If you serve content along with your podcast that
|
||||
requires cross-origin access, please be sure to enable the correct CORS policies on the domain the content is served from. You can find details
|
||||
[here](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS).
|
||||
|
||||
Web-based podcast apps, PWA's (Progressive Web Apps) and Browser Extension based apps are critical to Podcasting 2.0, so the above changes are very important.
|
||||
|
||||
<br><br>
|
||||
|
||||
## Step 3. Value for Value
|
||||
|
||||
The final step is monetizing your content with cryptocurrency so your listeners can support you directly with no middle-men. This allows your content to be truly free from the pressures of advertising. Advertising serves a necessary role in any free market, but it does come with a cost. That cost is censorship - whether it's direct censorship by the advertisers themselves or self-censorship as you restrict your speech as to not offend the advertisers.
|
||||
The final step is monetizing your content with cryptocurrency so your listeners can support you directly with no middle-men. This allows your content to be truly free from the pressures of advertising. Advertising serves a necessary role in any free market, but it does come with a cost. That cost is censorship - whether it's direct censorship by the advertisers themselves or self-censorship as you restrict your speech as to not offend the advertisers.
|
||||
|
||||
Because of these issues, we've created a way to receive cryptocurrency payments directly from your listeners to you using the experimental `<podcast:value>` tag in your podcast feed. Because it is experimental, it is not currently supported by any major podcast hosting companies. But, if you are so inclined, you can start using it in your feed today so that your podcast will show up on apps that support it like:
|
||||
Because of these issues, we've created a way to receive cryptocurrency payments directly from your listeners to you using the experimental `<podcast:value>` tag in your podcast feed. Because it is experimental, it is not currently supported by any major podcast hosting companies. But, if you are so inclined, you can start using it in your feed today so that your podcast will show up on apps that support it like:
|
||||
|
||||
- [Breez](https://Breez.technology)
|
||||
- [Podfriend](https://podfriend.com)
|
||||
|
@ -98,6 +87,6 @@ Because of these issues, we've created a way to receive cryptocurrency payments
|
|||
- [Castamatic](https://castamatic.com)
|
||||
- [Fountain.fm](https://fountain.fm)
|
||||
|
||||
If you can't add the `<podcast:value>` tag to your feed manually, we also have created [a site](https://podcasterwallet.com) that can help you put a value tag directly into the Podcast Index database for your feed. Any apps that use the Podcast Index will see your value tag and be able to stream micropayments to you.
|
||||
If you can't add the `<podcast:value>` tag to your feed manually, we also have created [a site](https://podcasterwallet.com) that can help you put a value tag directly into the Podcast Index database for your feed. Any apps that use the Podcast Index will see your value tag and be able to stream micropayments to you.
|
||||
|
||||
The `<podcast:value>` tag is still early and experimental. But, it does work today. There are more details about it in this [blog post](https://blog.podcastindex.org/html/AnotherWay-lJmNWj9T490hdmPmz5M4GV1Tlw6rDF.html), and in the official [whitepaper](value/value.md). The Podcasting 2.0 community is also always willing to lend you a hand and some advice on the [podcastindex.social](https://podcastindex.social) discussion server.
|
||||
The `<podcast:value>` tag is still early and experimental. But, it does work today. There are more details about it in this [blog post](https://blog.podcastindex.org/html/AnotherWay-lJmNWj9T490hdmPmz5M4GV1Tlw6rDF.html), and in the official [whitepaper](docs/examples/value/value.md). The Podcasting 2.0 community is also always willing to lend you a hand and some advice on the [podcastindex.social](https://podcastindex.social) discussion server.
|
||||
|
|
|
@ -6,17 +6,17 @@
|
|||
|
||||
## Purpose
|
||||
|
||||
Podcasting is a tremendous ecosystem brimming with tons of stories and ideas that go freely from any platform to any application. That comes
|
||||
with a huge drawback: finding and being found can be harsh. Podcast creators struggle to be found while podcast listeners struggle to find content.
|
||||
Podcasting is a tremendous ecosystem brimming with tons of stories and ideas that go freely from any platform to any application. That comes
|
||||
with a huge drawback: finding and being found can be harsh. Podcast creators struggle to be found while podcast listeners struggle to find content.
|
||||
|
||||
Several platforms are now implementing recommendation engines, but these features are expensive and unattainable for small players. Moreover they
|
||||
are slowly creating closed silos and removing power from content creators. This specification is about giving control to content creators on the
|
||||
are slowly creating closed silos and removing power from content creators. This specification is about giving control to content creators on the
|
||||
content they want to recommend, and at the same time providing a free recommendation system to all players.
|
||||
|
||||
|
||||
|
||||
<br><br>
|
||||
|
||||
## Specification
|
||||
|
||||
```xml
|
||||
<podcast:recommendations
|
||||
url="[url to json file(string)]"
|
||||
|
@ -40,36 +40,38 @@ There may be several occurences of this tag for the same element (one per langua
|
|||
|
||||
#### Attributes
|
||||
|
||||
- `url` (required): This is the url to the json file.
|
||||
- `type` (required): Mime type, must be json.
|
||||
- `language` (optional): The language of the recommended episodes (two-letter language codes, with some possible modifiers, such as "en-us"). If there
|
||||
is no language attribute given, the linked file is assumed to be the same language that is specified by the RSS `<language>` element.
|
||||
- `url` (required): This is the url to the json file.
|
||||
- `type` (required): Mime type, must be json.
|
||||
- `language` (optional): The language of the recommended episodes (two-letter language codes, with some possible modifiers, such as "en-us"). If there
|
||||
is no language attribute given, the linked file is assumed to be the same language that is specified by the RSS `<language>` element.
|
||||
|
||||
#### Examples
|
||||
- `<podcast:recommendations url="https://domain.tld/recommendation?guid=1234" type="application/json" />`
|
||||
- `<podcast:recommendations url="https://domain.tld/recommendation?guid=1234" type="application/json" language="en" />`
|
||||
|
||||
|
||||
- `<podcast:recommendations url="https://domain.tld/recommendation?guid=1234" type="application/json" />`
|
||||
- `<podcast:recommendations url="https://domain.tld/recommendation?guid=1234" type="application/json" language="en" />`
|
||||
|
||||
<br><br>
|
||||
|
||||
## JSON File Specification
|
||||
|
||||
The recommendations file contains a simple JSON object with 2 required properties and a few optional properties. This top-level object
|
||||
The recommendations file contains a simple JSON object with 2 required properties and a few optional properties. This top-level object
|
||||
is simply a container for the main payload, which is an array of `recommendation` objects contained in the `recommendations` property.
|
||||
|
||||
#### Required Attributes
|
||||
|
||||
- `version` (required - string) The version number of the format being used.
|
||||
- `recommendations` (required - array) An array of `recommendations` objects defined below.
|
||||
- `version` (required - string) The version number of the format being used.
|
||||
- `recommendations` (required - array) An array of `recommendations` objects defined below.
|
||||
|
||||
#### Optional Attributes
|
||||
- `comment` (optional - string) A comment on this file.
|
||||
- `title` (optional - string) The name of the source podcast or **source** podcast episode. Applies to both Channel and Item.
|
||||
- `feed` (optional - string) The RSS URL of the **source** podcast. Applies to both podcast Channel and podcast Item.
|
||||
- `guid` (optional - string) The GUID of the **source** element. Applies to both podcast Channel and podcast Item.
|
||||
- `url` (optionnal - string) The enclosure URL of the **source** medium.
|
||||
|
||||
- `comment` (optional - string) A comment on this file.
|
||||
- `title` (optional - string) The name of the source podcast or **source** podcast episode. Applies to both Channel and Item.
|
||||
- `feed` (optional - string) The RSS URL of the **source** podcast. Applies to both podcast Channel and podcast Item.
|
||||
- `guid` (optional - string) The GUID of the **source** element. Applies to both podcast Channel and podcast Item.
|
||||
- `url` (optionnal - string) The enclosure URL of the **source** medium.
|
||||
|
||||
#### Example
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "1.0",
|
||||
|
@ -82,63 +84,63 @@ is simply a container for the main payload, which is an array of `recommendation
|
|||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
<br><br>
|
||||
|
||||
## The "recommendations" Array
|
||||
|
||||
Each recommendation is defined in a `recommendation` object that resides within the `recommendations` array. It is meant to structure data
|
||||
Each recommendation is defined in a `recommendation` object that resides within the `recommendations` array. It is meant to structure data
|
||||
that might otherwise be present - buried within shownotes HTML.
|
||||
|
||||
#### Required attributes:
|
||||
|
||||
- `linkType` (required - string) The link type of this recommended content, it can be:
|
||||
- 'generic',
|
||||
- 'feed',
|
||||
- 'item',
|
||||
- 'none'
|
||||
- `medium` (required - string) The [medium](https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md#medium) type. It can be:
|
||||
- `podcast`,
|
||||
- `music`,
|
||||
- `video`,
|
||||
- `film`,
|
||||
- `audiobook`,
|
||||
- `newsletter`,
|
||||
- `blog`
|
||||
- `title` (required - string) The title for this recommended content.
|
||||
- `linkType` (required - string) The link type of this recommended content, it can be:
|
||||
- 'generic',
|
||||
- 'feed',
|
||||
- 'item',
|
||||
- 'none'
|
||||
- `medium` (required - string) The [medium](/docs/tags/medium.md) type. It can be:
|
||||
- `podcast`,
|
||||
- `music`,
|
||||
- `video`,
|
||||
- `film`,
|
||||
- `audiobook`,
|
||||
- `newsletter`,
|
||||
- `blog`
|
||||
- `title` (required - string) The title for this recommended content.
|
||||
|
||||
#### Optional Attributes:
|
||||
|
||||
- `url` (optional - string) The URL for this recommended content. If recommended content type is *"feed"* this is the home page of the podcast/medium.
|
||||
If recommended content type is *"feed-item"* this is the enclosure URL.
|
||||
- `image` (optional - string) The image URL for this recommended content. Image must have a 1:1 ratio (square).
|
||||
- `displayStartTime` (optional - float) The start time (in seconds) that tells when this recommended content should start being displayed. If `displayStartTime`
|
||||
is omitted, recommendation will be displayed from the beginning. Applies only when called from an *Item* (not from the *Channel*).
|
||||
- `displayDuration` (optional - float) The duration (in seconds) that tells when this recommended content should stop being displayed. If `displayDuration` is
|
||||
omitted, recommendation will be displayed until the end. Applies only when called from an *Item* (not from the *Channel*).
|
||||
- `feed` (optional - string) The RSS URL of this recommended content. Applies to *"feed"*, *"feed-item"* types.
|
||||
- `guid` (optional - string) The GUID of this recommended content. Applies to *"feed"* and *"feed-item"* types.
|
||||
- `startTime` (optional - float) The start time (in seconds) of this recommended content. Applies to *"feed-item"* type only.
|
||||
- `duration` (optional - float) The duration (in seconds) of this recommended content. Applies to *"feed-item"* type only.
|
||||
- `motive` (optional - string) The reason why this content is recommended. It can be:
|
||||
- `references` (content that was used when creating this podcast, similar to the Wikipedia References paragraph),
|
||||
- `additional content` (content that provides extra information),
|
||||
- `acknowledgment` (thanking people),
|
||||
- `advertising` (sponsored content),
|
||||
- `audience exchange` (exchanging audiences between podcasts),
|
||||
- `content-based recommendation` (content related thanks to semantic indexation),
|
||||
- `audience-based recommendation` (people who liked this also liked…),
|
||||
- `made by the same people` (the creators of this podcast also made that…)
|
||||
- `relevance` (optional - float) The relevance of this recommended content regarding this Channel or Item. Number must be in [0…1]. 0 is for irrelevant content, 1 is
|
||||
for contents that match perfectly.
|
||||
- `url` (optional - string) The URL for this recommended content. If recommended content type is _"feed"_ this is the home page of the podcast/medium.
|
||||
If recommended content type is _"feed-item"_ this is the enclosure URL.
|
||||
- `image` (optional - string) The image URL for this recommended content. Image must have a 1:1 ratio (square).
|
||||
- `displayStartTime` (optional - float) The start time (in seconds) that tells when this recommended content should start being displayed. If `displayStartTime`
|
||||
is omitted, recommendation will be displayed from the beginning. Applies only when called from an _Item_ (not from the _Channel_).
|
||||
- `displayDuration` (optional - float) The duration (in seconds) that tells when this recommended content should stop being displayed. If `displayDuration` is
|
||||
omitted, recommendation will be displayed until the end. Applies only when called from an _Item_ (not from the _Channel_).
|
||||
- `feed` (optional - string) The RSS URL of this recommended content. Applies to _"feed"_, _"feed-item"_ types.
|
||||
- `guid` (optional - string) The GUID of this recommended content. Applies to _"feed"_ and _"feed-item"_ types.
|
||||
- `startTime` (optional - float) The start time (in seconds) of this recommended content. Applies to _"feed-item"_ type only.
|
||||
- `duration` (optional - float) The duration (in seconds) of this recommended content. Applies to _"feed-item"_ type only.
|
||||
- `motive` (optional - string) The reason why this content is recommended. It can be:
|
||||
- `references` (content that was used when creating this podcast, similar to the Wikipedia References paragraph),
|
||||
- `additional content` (content that provides extra information),
|
||||
- `acknowledgment` (thanking people),
|
||||
- `advertising` (sponsored content),
|
||||
- `audience exchange` (exchanging audiences between podcasts),
|
||||
- `content-based recommendation` (content related thanks to semantic indexation),
|
||||
- `audience-based recommendation` (people who liked this also liked…),
|
||||
- `made by the same people` (the creators of this podcast also made that…)
|
||||
- `relevance` (optional - float) The relevance of this recommended content regarding this Channel or Item. Number must be in [0…1]. 0 is for irrelevant content, 1 is
|
||||
for contents that match perfectly.
|
||||
|
||||
#### Example of Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"linkType": "generic",
|
||||
"title": "History of podcasting",
|
||||
"image": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e7/Podcasts_%28iOS%29.svg/440px-Podcasts_%28iOS%29.svg.png",
|
||||
"url": "https://en.wikipedia.org/wiki/History_of_podcasting"
|
||||
"linkType": "generic",
|
||||
"title": "History of podcasting",
|
||||
"image": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e7/Podcasts_%28iOS%29.svg/440px-Podcasts_%28iOS%29.svg.png",
|
||||
"url": "https://en.wikipedia.org/wiki/History_of_podcasting"
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -148,53 +150,52 @@ Here is what a very basic recommendations file may look like:
|
|||
|
||||
```json
|
||||
{
|
||||
"version": "1.0",
|
||||
"recommendations":
|
||||
[
|
||||
{
|
||||
"linkType": "none",
|
||||
"medium": "text",
|
||||
"title": "Eat five vegetables every day.",
|
||||
"motive": "advertising"
|
||||
},
|
||||
{
|
||||
"linkType": "generic",
|
||||
"medium": "html",
|
||||
"title": "History of podcasting",
|
||||
"image": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e7/Podcasts_%28iOS%29.svg/440px-Podcasts_%28iOS%29.svg.png",
|
||||
"url": "https://en.wikipedia.org/wiki/History_of_podcasting",
|
||||
"motive": "additional content"
|
||||
},
|
||||
{
|
||||
"linkType": "feed",
|
||||
"medium": "podcast",
|
||||
"title": "Podcasting 2.0",
|
||||
"image": "https://noagendaassets.com/enc/1601061118.678_pciavatar.jpg",
|
||||
"url": "https://podcastindex.org/podcast/920666",
|
||||
"feed": "http://mp3s.nashownotes.com/pc20rss.xml",
|
||||
"motive": "audience exchange"
|
||||
},
|
||||
{
|
||||
"linkType": "item",
|
||||
"medium": "podcast",
|
||||
"title": "Episode 26: Manning Battlestations",
|
||||
"image": "https://noagendaassets.com/enc/1601061118.678_pciavatar.jpg",
|
||||
"url": "https://mp3s.nashownotes.com/PC20-26-2021-02-26-Final.mp3",
|
||||
"feed": "http://mp3s.nashownotes.com/pc20rss.xml",
|
||||
"guid": "PC2026"
|
||||
},
|
||||
{
|
||||
"linkType": "item",
|
||||
"medium": "podcast",
|
||||
"title": "GO PODCASTING!!!",
|
||||
"image": "https://noagendaassets.com/enc/1601061118.678_pciavatar.jpg",
|
||||
"url": "https://mp3s.nashownotes.com/PC20-26-2021-02-26-Final.mp3",
|
||||
"feed": "http://mp3s.nashownotes.com/pc20rss.xml",
|
||||
"guid": "PC2026",
|
||||
"startTime": 4737.0,
|
||||
"duration": 5.0
|
||||
}
|
||||
]
|
||||
"version": "1.0",
|
||||
"recommendations": [
|
||||
{
|
||||
"linkType": "none",
|
||||
"medium": "text",
|
||||
"title": "Eat five vegetables every day.",
|
||||
"motive": "advertising"
|
||||
},
|
||||
{
|
||||
"linkType": "generic",
|
||||
"medium": "html",
|
||||
"title": "History of podcasting",
|
||||
"image": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e7/Podcasts_%28iOS%29.svg/440px-Podcasts_%28iOS%29.svg.png",
|
||||
"url": "https://en.wikipedia.org/wiki/History_of_podcasting",
|
||||
"motive": "additional content"
|
||||
},
|
||||
{
|
||||
"linkType": "feed",
|
||||
"medium": "podcast",
|
||||
"title": "Podcasting 2.0",
|
||||
"image": "https://noagendaassets.com/enc/1601061118.678_pciavatar.jpg",
|
||||
"url": "https://podcastindex.org/podcast/920666",
|
||||
"feed": "http://mp3s.nashownotes.com/pc20rss.xml",
|
||||
"motive": "audience exchange"
|
||||
},
|
||||
{
|
||||
"linkType": "item",
|
||||
"medium": "podcast",
|
||||
"title": "Episode 26: Manning Battlestations",
|
||||
"image": "https://noagendaassets.com/enc/1601061118.678_pciavatar.jpg",
|
||||
"url": "https://mp3s.nashownotes.com/PC20-26-2021-02-26-Final.mp3",
|
||||
"feed": "http://mp3s.nashownotes.com/pc20rss.xml",
|
||||
"guid": "PC2026"
|
||||
},
|
||||
{
|
||||
"linkType": "item",
|
||||
"medium": "podcast",
|
||||
"title": "GO PODCASTING!!!",
|
||||
"image": "https://noagendaassets.com/enc/1601061118.678_pciavatar.jpg",
|
||||
"url": "https://mp3s.nashownotes.com/PC20-26-2021-02-26-Final.mp3",
|
||||
"feed": "http://mp3s.nashownotes.com/pc20rss.xml",
|
||||
"guid": "PC2026",
|
||||
"startTime": 4737.0,
|
||||
"duration": 5.0
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -202,69 +203,68 @@ Here is what a very basic recommendations file may look like:
|
|||
|
||||
```json
|
||||
{
|
||||
"version": "1.0",
|
||||
"title": "Podnews podcasting news",
|
||||
"feed": "https://podnews.net/rss",
|
||||
"guid": "9b024349-ccf0-5f69-a609-6b82873eab3c",
|
||||
"recommendations":
|
||||
[
|
||||
{
|
||||
"linkType": "none",
|
||||
"medium": "text",
|
||||
"title": "Eat five vegetables every day.",
|
||||
"motive": "advertising"
|
||||
},
|
||||
{
|
||||
"displayStartTime": 0.0,
|
||||
"displayDuration": 120.0,
|
||||
"linkType": "generic",
|
||||
"medium": "html",
|
||||
"title": "History of podcasting",
|
||||
"image": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e7/Podcasts_%28iOS%29.svg/440px-Podcasts_%28iOS%29.svg.png",
|
||||
"url": "https://en.wikipedia.org/wiki/History_of_podcasting",
|
||||
"motive": "additional content",
|
||||
"relevance": 0.8
|
||||
},
|
||||
{
|
||||
"displayStartTime": 120.50,
|
||||
"displayDuration": 60.0,
|
||||
"linkType": "feed",
|
||||
"medium": "podcast",
|
||||
"title": "Podcasting 2.0",
|
||||
"image": "https://noagendaassets.com/enc/1601061118.678_pciavatar.jpg",
|
||||
"url": "https://podcastindex.org/podcast/920666",
|
||||
"feed": "http://mp3s.nashownotes.com/pc20rss.xml",
|
||||
"motive": "audience exchange",
|
||||
"relevance": 0.7
|
||||
},
|
||||
{
|
||||
"displayStartTime": 240.60,
|
||||
"displayDuration": 180.0,
|
||||
"linkType": "feed-item",
|
||||
"medium": "podcast",
|
||||
"title": "Episode 26: Manning Battlestations",
|
||||
"image": "https://noagendaassets.com/enc/1601061118.678_pciavatar.jpg",
|
||||
"url": "https://mp3s.nashownotes.com/PC20-26-2021-02-26-Final.mp3",
|
||||
"feed": "http://mp3s.nashownotes.com/pc20rss.xml",
|
||||
"guid": "PC2026",
|
||||
"relevance": 0.5
|
||||
},
|
||||
{
|
||||
"displayStartTime": 3600.10,
|
||||
"displayDuration": 60.0,
|
||||
"linkType": "item",
|
||||
"medium": "podcast",
|
||||
"title": "GO PODCASTING!!!",
|
||||
"image": "https://noagendaassets.com/enc/1601061118.678_pciavatar.jpg",
|
||||
"url": "https://mp3s.nashownotes.com/PC20-26-2021-02-26-Final.mp3",
|
||||
"feed": "http://mp3s.nashownotes.com/pc20rss.xml",
|
||||
"guid": "PC2026",
|
||||
"startTime": 4737.0,
|
||||
"duration": 5.0,
|
||||
"motive": "additional content",
|
||||
"relevance": 0.9
|
||||
}
|
||||
]
|
||||
"version": "1.0",
|
||||
"title": "Podnews podcasting news",
|
||||
"feed": "https://podnews.net/rss",
|
||||
"guid": "9b024349-ccf0-5f69-a609-6b82873eab3c",
|
||||
"recommendations": [
|
||||
{
|
||||
"linkType": "none",
|
||||
"medium": "text",
|
||||
"title": "Eat five vegetables every day.",
|
||||
"motive": "advertising"
|
||||
},
|
||||
{
|
||||
"displayStartTime": 0.0,
|
||||
"displayDuration": 120.0,
|
||||
"linkType": "generic",
|
||||
"medium": "html",
|
||||
"title": "History of podcasting",
|
||||
"image": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e7/Podcasts_%28iOS%29.svg/440px-Podcasts_%28iOS%29.svg.png",
|
||||
"url": "https://en.wikipedia.org/wiki/History_of_podcasting",
|
||||
"motive": "additional content",
|
||||
"relevance": 0.8
|
||||
},
|
||||
{
|
||||
"displayStartTime": 120.5,
|
||||
"displayDuration": 60.0,
|
||||
"linkType": "feed",
|
||||
"medium": "podcast",
|
||||
"title": "Podcasting 2.0",
|
||||
"image": "https://noagendaassets.com/enc/1601061118.678_pciavatar.jpg",
|
||||
"url": "https://podcastindex.org/podcast/920666",
|
||||
"feed": "http://mp3s.nashownotes.com/pc20rss.xml",
|
||||
"motive": "audience exchange",
|
||||
"relevance": 0.7
|
||||
},
|
||||
{
|
||||
"displayStartTime": 240.6,
|
||||
"displayDuration": 180.0,
|
||||
"linkType": "feed-item",
|
||||
"medium": "podcast",
|
||||
"title": "Episode 26: Manning Battlestations",
|
||||
"image": "https://noagendaassets.com/enc/1601061118.678_pciavatar.jpg",
|
||||
"url": "https://mp3s.nashownotes.com/PC20-26-2021-02-26-Final.mp3",
|
||||
"feed": "http://mp3s.nashownotes.com/pc20rss.xml",
|
||||
"guid": "PC2026",
|
||||
"relevance": 0.5
|
||||
},
|
||||
{
|
||||
"displayStartTime": 3600.1,
|
||||
"displayDuration": 60.0,
|
||||
"linkType": "item",
|
||||
"medium": "podcast",
|
||||
"title": "GO PODCASTING!!!",
|
||||
"image": "https://noagendaassets.com/enc/1601061118.678_pciavatar.jpg",
|
||||
"url": "https://mp3s.nashownotes.com/PC20-26-2021-02-26-Final.mp3",
|
||||
"feed": "http://mp3s.nashownotes.com/pc20rss.xml",
|
||||
"guid": "PC2026",
|
||||
"startTime": 4737.0,
|
||||
"duration": 5.0,
|
||||
"motive": "additional content",
|
||||
"relevance": 0.9
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -278,5 +278,6 @@ As a safeguard against that, apps should:
|
|||
- Fetch all recommendations at the same time disregarding `displayStartTime` so that HTTP requests cannot be used as a way of measuring who listens to what.
|
||||
|
||||
Discussion here:
|
||||
|
||||
- https://github.com/Podcastindex-org/podcast-namespace/issues/205
|
||||
- https://podcastindex.social/web/statuses/105833620038854052
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
# THIS IS NOT THE FINAL SPECIFICATION
|
||||
|
||||
You [want to look here for that](https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md#social-interact)
|
||||
You [want to look here for that](/docs/tags/social-interact.md)
|
||||
|
||||
<hr>
|
||||
|
||||
|
@ -18,7 +18,7 @@ You [want to look here for that](https://github.com/Podcastindex-org/podcast-nam
|
|||
|
||||
<small>Version 1.0 by Benjamin Bellamy - 2021.04.13</small>
|
||||
|
||||
**This is not the final specification. You [want to look here for that](https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md#social-interact)**
|
||||
**This is not the final specification. You [want to look here for that](/docs/tags/social-interact.md)**
|
||||
|
||||
<br />
|
||||
|
||||
|
@ -32,6 +32,7 @@ necessary to make them possible - as seamless as possible.
|
|||
Of course not all podcast apps would implement all platforms. Each one would implement the one(s) they want to provide their users a better interaction with.
|
||||
|
||||
There are three elements:
|
||||
|
||||
- **"podcast:social"** for the \<channel> element: tells the user **which platform(s)** is/are used for this podcast.
|
||||
- **"podcast:socialSignUp"** for the \<podcast:social> element: tells the user **where to sign up** on this platform.
|
||||
- **"podcast:socialInteract"** for the \<item> element: tells the user where to **interact with this specific episode**.
|
||||
|
@ -42,43 +43,44 @@ There are three elements:
|
|||
|
||||
- **\<podcast:social platform="[platform_id]" protocol="[protocol_name]" accountId="[podcast_account_id]" accountUrl="[podcast_account_url]" priority="[platform_priority]">**[one or more "podcast:socialSignUp" elements]**\</podcast:social>**
|
||||
|
||||
Channel (optional | multiple)
|
||||
Channel (optional | multiple)
|
||||
|
||||
This element allows a podcaster to specify one or more platforms where listeners can interact.
|
||||
There may be several occurences of this tag for the same element (on several platforms, the podcast may have several accounts on the same plaforms…)
|
||||
This element allows a podcaster to specify one or more platforms where listeners can interact.
|
||||
There may be several occurences of this tag for the same element (on several platforms, the podcast may have several accounts on the same plaforms…)
|
||||
|
||||
- `platform` (required): This is the platform id. It can be one of the following:
|
||||
- castopod
|
||||
- mastodon
|
||||
- peertube
|
||||
- facebook
|
||||
- twitter
|
||||
- instagram
|
||||
- slack
|
||||
- discord
|
||||
- cast.garden
|
||||
- 3speak
|
||||
- peakd.com
|
||||
- …
|
||||
- castopod
|
||||
- mastodon
|
||||
- peertube
|
||||
- facebook
|
||||
- twitter
|
||||
- instagram
|
||||
- slack
|
||||
- discord
|
||||
- cast.garden
|
||||
- 3speak
|
||||
- peakd.com
|
||||
- …
|
||||
- `protocol` (required): This is the protocol name. It can be one of the following:
|
||||
- activitypub
|
||||
- xmpp
|
||||
- irc
|
||||
- matrix
|
||||
- facebook
|
||||
- twitter
|
||||
- instagram
|
||||
- slack
|
||||
- discord
|
||||
- hive
|
||||
- …
|
||||
- `accountId` (required): The podcast ID on this platform.
|
||||
- `accountUrl` (required): The podcast URL on this platform.
|
||||
- `priority` (optional): This platform priority (useful if the podcaster wants to tell which platform is preferred, lower is better)
|
||||
- activitypub
|
||||
- xmpp
|
||||
- irc
|
||||
- matrix
|
||||
- facebook
|
||||
- twitter
|
||||
- instagram
|
||||
- slack
|
||||
- discord
|
||||
- hive
|
||||
- …
|
||||
- `accountId` (required): The podcast ID on this platform.
|
||||
- `accountUrl` (required): The podcast URL on this platform.
|
||||
- `priority` (optional): This platform priority (useful if the podcaster wants to tell which platform is preferred, lower is better)
|
||||
|
||||
Examples:
|
||||
- `<podcast:social platform="twitter" protocol="twitter" accountId="@PodcastindexOrg" accountUrl="https://twitter.com/PodcastindexOrg"></podcast:social>`
|
||||
- `<podcast:social platform="mastodon" protocol="activitypub" accountId="@podcastindex@noagendasocial.com" accountUrl="https://noagendasocial.com/@podcastindex"></podcast:social>`
|
||||
Examples:
|
||||
|
||||
- `<podcast:social platform="twitter" protocol="twitter" accountId="@PodcastindexOrg" accountUrl="https://twitter.com/PodcastindexOrg"></podcast:social>`
|
||||
- `<podcast:social platform="mastodon" protocol="activitypub" accountId="@podcastindex@noagendasocial.com" accountUrl="https://noagendasocial.com/@podcastindex"></podcast:social>`
|
||||
|
||||
### SocialSignUp Element
|
||||
|
||||
|
@ -88,11 +90,12 @@ There are three elements:
|
|||
|
||||
This element allows easy onboarding for listeners on social/discussion platforms, especially for decentralized ones (such as Matrix or ActivityPub) where podcasters and listeners can register on different servers.
|
||||
|
||||
- `homeUrl` (required): The platform home URL.
|
||||
- `signUpUrl` (required): The platform sign up URL.
|
||||
- `priority` (optional): This platform priority (useful if the podcaster wants to tell which platform is preferred, lower is better)
|
||||
- `homeUrl` (required): The platform home URL.
|
||||
- `signUpUrl` (required): The platform sign up URL.
|
||||
- `priority` (optional): This platform priority (useful if the podcaster wants to tell which platform is preferred, lower is better)
|
||||
|
||||
Examples:
|
||||
|
||||
- `<podcast:socialSignUp homeUrl="https://twitter.com/" signUpUrl="https://twitter.com/login" priority="1" />`
|
||||
- `<podcast:socialSignUp homeUrl="https://podcastindex.social/public" signUpUrl="https://podcastindex.social/auth/sign_up" priority="2" />`
|
||||
|
||||
|
@ -105,44 +108,46 @@ There are three elements:
|
|||
This element allows listeners to interact with (comment, share, like, review…) an episode, or a podcast.
|
||||
|
||||
- `platform` (required): This is the platform id. It can be one of the following:
|
||||
- castopod
|
||||
- mastodon
|
||||
- peertube
|
||||
- facebook
|
||||
- twitter
|
||||
- instagram
|
||||
- slack
|
||||
- discord
|
||||
- cast.garden
|
||||
- 3speak
|
||||
- peakd
|
||||
- fountain
|
||||
- none *(to indicate a strong opt-out preference)*
|
||||
- …
|
||||
- castopod
|
||||
- mastodon
|
||||
- peertube
|
||||
- facebook
|
||||
- twitter
|
||||
- instagram
|
||||
- slack
|
||||
- discord
|
||||
- cast.garden
|
||||
- 3speak
|
||||
- peakd
|
||||
- fountain
|
||||
- none _(to indicate a strong opt-out preference)_
|
||||
- …
|
||||
- `protocol` (required): This is the protocol name. It can be one of the following:
|
||||
- activitypub
|
||||
- facebook
|
||||
- twitter
|
||||
- instagram
|
||||
- slack
|
||||
- discord
|
||||
- xmpp
|
||||
- irc
|
||||
- matrix
|
||||
- hive
|
||||
- lightningcomments (see #347 for protocol description)
|
||||
- …
|
||||
- `accountId` (required): The podcast ID on this platform.
|
||||
- `pubDate` (optional): publication date on this platform. This can be useful when there are several interactions for the same platform for the same episode (for instance, two Tweets about the same episode). Format must be [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601).
|
||||
- `priority` (optional): This platform priority (useful if the podcaster wants to tell which platform is preferred, lower is better)
|
||||
- element's content: URL to the social media post on this platform corresponding to this episode (if at the `<item>` level) or for this podcast (if at the `channel` level), or a short reason for apps to display when comments are disabled (if `platform="none"`)
|
||||
- activitypub
|
||||
- facebook
|
||||
- twitter
|
||||
- instagram
|
||||
- slack
|
||||
- discord
|
||||
- xmpp
|
||||
- irc
|
||||
- matrix
|
||||
- hive
|
||||
- lightningcomments (see #347 for protocol description)
|
||||
- …
|
||||
- `accountId` (required): The podcast ID on this platform.
|
||||
- `pubDate` (optional): publication date on this platform. This can be useful when there are several interactions for the same platform for the same episode (for instance, two Tweets about the same episode). Format must be [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601).
|
||||
- `priority` (optional): This platform priority (useful if the podcaster wants to tell which platform is preferred, lower is better)
|
||||
- element's content: URL to the social media post on this platform corresponding to this episode (if at the `<item>` level) or for this podcast (if at the `channel` level), or a short reason for apps to display when comments are disabled (if `platform="none"`)
|
||||
|
||||
Examples:
|
||||
|
||||
- `<podcast:socialInteract platform="twitter" protocol="twitter" accountId="@Podverse" priority="2" pubDate="2021-04-14T10:25:42Z">https://twitter.com/Podverse/status/1375624446296395781</podcast:socialInteract>`
|
||||
- `<podcast:socialInteract priority="1" platform="castopod" protocol="activitypub" accountId="@heloise@lespoesiesdheloise.fr" pubDate="2021-04-08T20:07:13+0000">https://lespoesiesdheloise.fr/@heloise/notes/e4b3d7f3-e84b-40c6-b828-f5537f0c3659</podcast:socialInteract>`
|
||||
- `<podcast:socialInteract priority="1" platform="fountain" protocol="lightningcomments" accountId="123868c219bdb51a33560d854d500fe7d3123a1ad9e05dd89d0007e11313588123">https://api.fountain.fm/v1/comments?feed=221233&episode=1230123071</podcast:socialInteract>`
|
||||
|
||||
Or to opt out:
|
||||
|
||||
- `<podcast:socialInteract platform="none">Comments disabled for this episode</podcast:socialInteract>`
|
||||
|
||||
## Full RSS feed example
|
||||
|
@ -187,5 +192,6 @@ There are three elements:
|
|||
```
|
||||
|
||||
Discussion here:
|
||||
|
||||
- https://github.com/Podcastindex-org/podcast-namespace/issues/153
|
||||
- https://podcastindex.social/web/statuses/106065482252134072
|
||||
|
|
|
@ -4,12 +4,11 @@
|
|||
|
||||
<br>
|
||||
|
||||
|
||||
# Purpose
|
||||
|
||||
"Claiming" a podcast in a podcast directory, platform, app or service means that your customer is proving that
|
||||
"Claiming" a podcast in a podcast directory, platform, app or service means that your customer is proving that
|
||||
they are the owner of the podcast feed and have current control of it. You might claim a podcast in Apple to add to your
|
||||
iTunes Connect account, Podchaser to add host information, in GoodPods to be notified of comments, in
|
||||
iTunes Connect account, Podchaser to add host information, in GoodPods to be notified of comments, in
|
||||
Podcasterwallet.com to add value4value info, or in Spotify to add it to the Spotify dashboard for analytics.
|
||||
|
||||
The "quick-claim" process described in this document, is a programmatic way to claim a podcast, by taking a podcast
|
||||
|
@ -20,28 +19,30 @@ material.
|
|||
|
||||
<br>
|
||||
|
||||
|
||||
# The Claiming Service
|
||||
|
||||
Run a podcast directory, app, platform or service and want to check that someone is the owner of this podcast?
|
||||
|
||||
Benefits of using "quick-claim" are that it's one-click within a browser or webview, rather than sending a potential
|
||||
user an email. Assuming they are logged-in to their podcast host (or can log in), they can approve your request
|
||||
Benefits of using "quick-claim" are that it's one-click within a browser or webview, rather than sending a potential
|
||||
user an email. Assuming they are logged-in to their podcast host (or can log in), they can approve your request
|
||||
instantly and come back to your service within seconds.
|
||||
|
||||
Additional benefits are:
|
||||
* Anyone authorized to access the podcast host dashboard can claim the show (subject to the user having the right
|
||||
|
||||
- Anyone authorized to access the podcast host dashboard can claim the show (subject to the user having the right
|
||||
permissions).
|
||||
* Email confirmations are often subject to delay, fall into spam, or are blocked altogether.
|
||||
* Email confirmation emails can be faked and lead to security concerns.
|
||||
* The listing of an email address in an RSS feed leads to privacy issues and spam.
|
||||
* Email confirmations often lead to around [25%](https://www.quora.com/What-is-a-typical-abandonment-rate-for-email-verifications) abandomnent
|
||||
- Email confirmations are often subject to delay, fall into spam, or are blocked altogether.
|
||||
- Email confirmation emails can be faked and lead to security concerns.
|
||||
- The listing of an email address in an RSS feed leads to privacy issues and spam.
|
||||
- Email confirmations often lead to around [25%](https://www.quora.com/What-is-a-typical-abandonment-rate-for-email-verifications) abandomnent
|
||||
|
||||
<br>
|
||||
|
||||
## 1. Check that quick-claiming is enabled in the RSS feed
|
||||
|
||||
```xml
|
||||
<podcast:verify
|
||||
auth="https://hostingprovider.com/claiming/"
|
||||
auth="https://hostingprovider.com/claiming/"
|
||||
pub="MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9q9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg=="
|
||||
/>
|
||||
```
|
||||
|
@ -49,8 +50,8 @@ Additional benefits are:
|
|||
The presence of the `<podcast:verify>` tag means that this feed supports quick claiming. The tag will have two required
|
||||
attributes that must be present:
|
||||
|
||||
* `auth` (required): The https URL of a "quick claim" page on the hosting provider that is protected by customer login.
|
||||
* `pub` (required): The public key corresponding to the private key the hosting provider users to sign claiming requests.
|
||||
- `auth` (required): The https URL of a "quick claim" page on the hosting provider that is protected by customer login.
|
||||
- `pub` (required): The public key corresponding to the private key the hosting provider users to sign claiming requests.
|
||||
|
||||
<br>
|
||||
|
||||
|
@ -59,40 +60,41 @@ attributes that must be present:
|
|||
You will redirect your user to the `auth` url present in the podcaster's RSS feed, with 1 to 3 URL parameters, as
|
||||
defined below.
|
||||
|
||||
|
||||
|
||||
### Param: `consumer`
|
||||
|
||||
Your directory/service base URL of the return_path.
|
||||
|
||||
It's recommanded to support open graph allowing the hosting provider to present a clean name and icon for your
|
||||
service while asking permission. If open graph is not supported, hosting provider will most likely fallback to
|
||||
It's recommanded to support open graph allowing the hosting provider to present a clean name and icon for your
|
||||
service while asking permission. If open graph is not supported, hosting provider will most likely fallback to
|
||||
hostname, path, favicon.
|
||||
|
||||
Splitting the return url into a `consumer` and a `return_path` gives us control of which part of the url is used
|
||||
Splitting the return url into a `consumer` and a `return_path` gives us control of which part of the url is used
|
||||
for open graph data while ensuring the return path is tied to the hostname and identity presented to the user.
|
||||
|
||||
#### Examples:
|
||||
|
||||
`consumer=https://podcastindex.org` ⤵
|
||||
```html
|
||||
PodcastIndex.org (podcastindex.org)
|
||||
The Podcast Index is here to preserve, protect and extend the open, independent podcasting ecosystem.
|
||||
|
||||
This service would like to verify you are the owner of this podcast. Do you want us to confirm ?
|
||||
```html
|
||||
PodcastIndex.org (podcastindex.org) The Podcast Index is here to preserve,
|
||||
protect and extend the open, independent podcasting ecosystem. This service
|
||||
would like to verify you are the owner of this podcast. Do you want us to
|
||||
confirm ?
|
||||
```
|
||||
|
||||
`consumer=https://podcastindex.org/quick_claim` ⤵
|
||||
```html
|
||||
🗼PodcastIndex Quick Claiming (podcastindex.org)
|
||||
Claiming your podcast into The Podcast Index directory gives you cool stuff !
|
||||
|
||||
This service would like to verify you are the owner of this podcast. Do you want us to confirm ?
|
||||
```html
|
||||
🗼PodcastIndex Quick Claiming (podcastindex.org) Claiming your podcast into The
|
||||
Podcast Index directory gives you cool stuff ! This service would like to verify
|
||||
you are the owner of this podcast. Do you want us to confirm ?
|
||||
```
|
||||
|
||||
<br>
|
||||
|
||||
### Param: `return_path`
|
||||
A path relative to the `consumer` parameter, to redirect with the result of the authentication. If the consumer is enough,
|
||||
|
||||
A path relative to the `consumer` parameter, to redirect with the result of the authentication. If the consumer is enough,
|
||||
you can omit it.
|
||||
|
||||
#### Examples:
|
||||
|
@ -100,16 +102,19 @@ you can omit it.
|
|||
These are example calculations of what the return url will be with different inputs:
|
||||
|
||||
`consumer=https://podcastindex.org/quick_claim` (no return path) ⤵ <br>
|
||||
|
||||
```http
|
||||
https://podcastindex.org/quick_claim?token=[token]
|
||||
```
|
||||
|
||||
`consumer=https://podcastindex.org/quick_claim&return_path=/claimed.php` ⤵ <br>
|
||||
|
||||
```http
|
||||
https://podcastindex.org/quick_claim/claimed.php?token=[token]
|
||||
```
|
||||
|
||||
`consumer=https://podcastindex.org/quick_claim&return_path=../claim.php` ⤵ <br>
|
||||
|
||||
```http
|
||||
https://podcastindex.org/claim.php?token=[token]
|
||||
```
|
||||
|
@ -117,14 +122,15 @@ https://podcastindex.org/claim.php?token=[token]
|
|||
<br>
|
||||
|
||||
### Param: `guid`
|
||||
If a [`<podcast:guid>`](https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md#guid) is present in the RSS feed it SHOULD be included. If it's not we omit it or send an empty string.
|
||||
|
||||
We keep it simple, we should trust the users of our spec (in this case the hosting providers) : if they don't include
|
||||
a [`<podcast:guid>`](https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md#guid), I don't see the point to calculate it and give it to them, as they most likely don't have it on their
|
||||
If a [`<podcast:guid>`](/docs/tags/guid.md) is present in the RSS feed it SHOULD be included. If it's not we omit it or send an empty string.
|
||||
|
||||
We keep it simple, we should trust the users of our spec (in this case the hosting providers) : if they don't include
|
||||
a [`<podcast:guid>`](/docs/tags/guid.md), I don't see the point to calculate it and give it to them, as they most likely don't have it on their
|
||||
hand and probably don't use it. If they would, it would be included in the feed.
|
||||
|
||||
If we don't have any [`<podcast:guid>`](https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md#guid), maybe the hosting provider has only partial support of the podcasting 2.0 spec. Maybe
|
||||
they only support claiming and include the guid in the auth url. Perhaps it's a Wordpress extension and there is only
|
||||
If we don't have any [`<podcast:guid>`](/docs/tags/guid.md), maybe the hosting provider has only partial support of the podcasting 2.0 spec. Maybe
|
||||
they only support claiming and include the guid in the auth url. Perhaps it's a Wordpress extension and there is only
|
||||
one podcast and no need to "select a podcast" when you're logged in. We can't know and guess all usages.
|
||||
|
||||
<br>
|
||||
|
@ -133,20 +139,20 @@ one podcast and no need to "select a podcast" when you're logged in. We can't kn
|
|||
|
||||
Here's a simple example, in PHP, of a quick claim button:
|
||||
|
||||
```php
|
||||
<a
|
||||
class="btn btn-default"
|
||||
href="<?php echo $feed_auth_url."?consumer=https://myservice.com/quick_claim&guid=".$feed_guid; ?>"
|
||||
```php
|
||||
<a
|
||||
class="btn btn-default"
|
||||
href="<?php echo $feed_auth_url."?consumer=https://myservice.com/quick_claim&guid=".$feed_guid; ?>"
|
||||
>
|
||||
Claim this now
|
||||
</a>
|
||||
```
|
||||
```
|
||||
|
||||
<br>
|
||||
|
||||
## 3. Handle the response from the hosting provider
|
||||
|
||||
If the podcaster wants to claim this podcast, they will confirm it inside their hosting service. You will receive a GET
|
||||
If the podcaster wants to claim this podcast, they will confirm it inside their hosting service. You will receive a GET
|
||||
request to the `return URL` computed from the `consumer` and `return_path`.
|
||||
|
||||
The GET request will contain only one parameter: `token`.
|
||||
|
@ -154,44 +160,47 @@ The GET request will contain only one parameter: `token`.
|
|||
This parameter will be a JSON Web Token ([JWT](https://jwt.io/)), and will include the following data :
|
||||
|
||||
```typescript
|
||||
type QuickClaimResponse = {
|
||||
guid?: GUID;
|
||||
accepted: true;
|
||||
} | {
|
||||
guid?: GUID;
|
||||
accepted: false;
|
||||
failureReason: "back" | string;
|
||||
};
|
||||
type QuickClaimResponse =
|
||||
| {
|
||||
guid?: GUID
|
||||
accepted: true
|
||||
}
|
||||
| {
|
||||
guid?: GUID
|
||||
accepted: false
|
||||
failureReason: "back" | string
|
||||
}
|
||||
```
|
||||
|
||||
Using JWT lets us ensure the hosting provider wrote the response. It also allow us to define, or not, an expiration
|
||||
Using JWT lets us ensure the hosting provider wrote the response. It also allow us to define, or not, an expiration
|
||||
date and such. ([More info on JWT](https://jwt.io/))
|
||||
|
||||
The JWT must use an asymmetric signing algorithm (ES256 for example). The response is signed by the hosting provider
|
||||
The JWT must use an asymmetric signing algorithm (ES256 for example). The response is signed by the hosting provider
|
||||
private key, and it can be verified using the `pub` attribute of the tag (put there by the same hosting provider).
|
||||
|
||||
You can verify the signature in PHP with a JWT library. If it fails to decode, it means the signature is wrong or the
|
||||
You can verify the signature in PHP with a JWT library. If it fails to decode, it means the signature is wrong or the
|
||||
token has expired:
|
||||
|
||||
```php
|
||||
$result = JWT::decode($quickClaimResponse, $feed["podcast:verify"]["pub"]);
|
||||
```
|
||||
|
||||
As a podcast directory, app, platform or service you now have the confirmation the user was indeed authenticated,
|
||||
As a podcast directory, app, platform or service you now have the confirmation the user was indeed authenticated,
|
||||
confirmed the operation, and that it's the same entity giving you this information and producing the RSS feed.
|
||||
|
||||
<br><br>
|
||||
|
||||
# The Hosting Service
|
||||
|
||||
Quick-claim is designed to allow your customers to demonstrate that they own their podcast on third-party services.
|
||||
|
||||
The benefits of using quick claiming are that it's one-click for your customers from the service they wish to claim,
|
||||
direct to you. You can monitor the services your users are using, and can give multiple people access to claiming a
|
||||
The benefits of using quick claiming are that it's one-click for your customers from the service they wish to claim,
|
||||
direct to you. You can monitor the services your users are using, and can give multiple people access to claiming a
|
||||
podcast on a separate service based on your service's access levels.
|
||||
|
||||
The email in your customer's RSS feed need not be the registration email of the user on this third-party service, thus
|
||||
lowering your customer support calls and streamlining access for your customers. Ease of use on third-party services
|
||||
retains the customer with your company, thus lowering churn, and possibly giving them more access and interaction,
|
||||
The email in your customer's RSS feed need not be the registration email of the user on this third-party service, thus
|
||||
lowering your customer support calls and streamlining access for your customers. Ease of use on third-party services
|
||||
retains the customer with your company, thus lowering churn, and possibly giving them more access and interaction,
|
||||
prolonging their activity with you.
|
||||
|
||||
If you're a podcast host wanting to add quick claiming for your customers, then here's how you can do it painlessly:
|
||||
|
@ -201,16 +210,16 @@ If you're a podcast host wanting to add quick claiming for your customers, then
|
|||
## 1. Add the `<podcast:verify>` tag to your RSS feeds
|
||||
|
||||
```xml
|
||||
<podcast:verify
|
||||
auth="https://amazingpodcasthost.example.com/claiming"
|
||||
pub="MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9q9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg=="
|
||||
<podcast:verify
|
||||
auth="https://amazingpodcasthost.example.com/claiming"
|
||||
pub="MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9q9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg=="
|
||||
/>
|
||||
```
|
||||
|
||||
The tag needs two attributes :
|
||||
|
||||
* `auth` (required): The URL of a secure page that is protected on your server by customer login.
|
||||
* `pub` (required): The public key corresponding to the private key you use to sign claiming requests.
|
||||
- `auth` (required): The URL of a secure page that is protected on your server by customer login.
|
||||
- `pub` (required): The public key corresponding to the private key you use to sign claiming requests.
|
||||
|
||||
<br>
|
||||
|
||||
|
@ -219,18 +228,20 @@ The tag needs two attributes :
|
|||
Your claiming `auth` url will be called with 1 to 3 parameters, as defined below:
|
||||
|
||||
### Param: `consumer`
|
||||
|
||||
The directory/service base URL.
|
||||
|
||||
This url must be used to present the permission asker to the user. It's recommended for services to support
|
||||
This url must be used to present the permission asker to the user. It's recommended for services to support
|
||||
open graph on this URL allowing the hosting provider to present a clean name and icon. If open graph is not supported,
|
||||
you will most likely fallback to title and favicon, hostname only, or full url.
|
||||
|
||||
Splitting the return url into a `consumer` and a `return_path` gives us control of which part of the url is used for
|
||||
Splitting the return url into a `consumer` and a `return_path` gives us control of which part of the url is used for
|
||||
open graph data while ensuring the return path is tied to the hostname and identity presented to the user.
|
||||
|
||||
<br>
|
||||
|
||||
### Param: `return_path`
|
||||
|
||||
A path relative to the `consumer` parameter, to use as a callback to the `consumer` with the result of the authentication.
|
||||
If the `consumer` is enough, you can omit this parameter.
|
||||
|
||||
|
@ -239,16 +250,19 @@ These are example calculations of what the return url will be with different inp
|
|||
#### Examples:
|
||||
|
||||
`consumer=https://podcastindex.org/quick_claim` (no return path) ⤵ <br>
|
||||
|
||||
```http
|
||||
https://podcastindex.org/quick_claim?token=[token]
|
||||
```
|
||||
|
||||
`consumer=https://podcastindex.org/quick_claim&return_path=/claimed.php` ⤵ <br>
|
||||
|
||||
```http
|
||||
https://podcastindex.org/quick_claim/claimed.php?token=[token]
|
||||
```
|
||||
|
||||
`consumer=https://podcastindex.org/quick_claim&return_path=../claim.php` ⤵ <br>
|
||||
|
||||
```http
|
||||
https://podcastindex.org/claim.php?token=[token]
|
||||
```
|
||||
|
@ -256,19 +270,20 @@ https://podcastindex.org/claim.php?token=[token]
|
|||
<br>
|
||||
|
||||
### Param: `guid`
|
||||
|
||||
If a `<podcast:guid>` tag was present in the RSS feed, expect it to also be included as a parameter here.
|
||||
|
||||
### PHP Example:
|
||||
|
||||
```php
|
||||
```php
|
||||
<?php
|
||||
$HOSTING_PRIVATE_KEY = "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgevZzL1gdAFr88hb2
|
||||
OF/2NxApJCzGCEDdfSp6VQO30hyhRANCAAQRWz+jn65BtOMvdyHKcvjBeBSDZH2r
|
||||
1RTwjmYSi9R/zpBnuQ4EiMnCqfMPWiZqB4QdbAd0E7oH50VpuZ1P087G";
|
||||
|
||||
if (!$user['loggedin']) {
|
||||
// this user is not logged in. Take them to log in
|
||||
// Retain the consumer, return_path, guid, and bring them back here
|
||||
// this user is not logged in. Take them to log in
|
||||
// Retain the consumer, return_path, guid, and bring them back here
|
||||
//
|
||||
exit;
|
||||
}
|
||||
|
@ -294,7 +309,7 @@ if(!$podcast) {
|
|||
[
|
||||
"accepted" => false,
|
||||
"error" => "Podcast could not be found for this user"
|
||||
],
|
||||
],
|
||||
$HOSTING_PRIVATE_KEY
|
||||
)
|
||||
);
|
||||
|
@ -304,8 +319,8 @@ if(!$podcast) {
|
|||
$guid = $podcast["guid"];
|
||||
|
||||
|
||||
// If we already shown the page and got user response
|
||||
//
|
||||
// If we already shown the page and got user response
|
||||
//
|
||||
// checking csrf depends on your context, but is a strong recommendation for security
|
||||
if(check_csrf()) exit("Bad request");
|
||||
|
||||
|
@ -319,7 +334,7 @@ if($action == "accept") {
|
|||
build_jwt_token(
|
||||
[
|
||||
"accepted" => true
|
||||
],
|
||||
],
|
||||
$HOSTING_PRIVATE_KEY
|
||||
)
|
||||
);
|
||||
|
@ -334,14 +349,14 @@ if($action == "accept") {
|
|||
[
|
||||
"accepted" => false,
|
||||
"error" => "back"
|
||||
],
|
||||
],
|
||||
$HOSTING_PRIVATE_KEY
|
||||
)
|
||||
);
|
||||
exit();
|
||||
}
|
||||
|
||||
$service = opengraph($consumer);
|
||||
$service = opengraph($consumer);
|
||||
|
||||
$claim_fields = <<<TAGS
|
||||
<input type="hidden" name="guid" value="$guid" />
|
||||
|
@ -369,21 +384,21 @@ TAGS;
|
|||
<input type="hidden" name="action" value="back" />
|
||||
<input type="submit" value="cancel this request">
|
||||
</form>
|
||||
```
|
||||
```
|
||||
|
||||
The above will successfully check that your user is authenticated and send back the JWT token to the directory service
|
||||
The above will successfully check that your user is authenticated and send back the JWT token to the directory service
|
||||
if the user agreed.
|
||||
|
||||
<br><br>
|
||||
|
||||
## Full Workflow Example
|
||||
|
||||
This section will summarize everything into one big example. Here are the defined terms we will use in this example:
|
||||
This section will summarize everything into one big example. Here are the defined terms we will use in this example:
|
||||
|
||||
- **DIRECTORY**: a podcast directory platform
|
||||
- **HOST**: a podcast hosting service
|
||||
- **PODCAST** is a show with an RSS feed
|
||||
- **CREATOR** is an owner of the **PODCAST** who is allowed to claim it
|
||||
- **PODCAST** is a show with an RSS feed
|
||||
- **CREATOR** is an owner of the **PODCAST** who is allowed to claim it
|
||||
|
||||
<br>
|
||||
|
||||
|
@ -391,32 +406,33 @@ This section will summarize everything into one big example. Here are the defin
|
|||
|
||||
```xml
|
||||
<podcast:guid>ead4c236-bf58-58c6-a2c6-a6b28d128cb6</podcast:guid>
|
||||
<podcast:verify
|
||||
auth="https://host.com/studio/quick_claim/"
|
||||
pub="MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9q9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg=="
|
||||
<podcast:verify
|
||||
auth="https://host.com/studio/quick_claim/"
|
||||
pub="MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9q9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg=="
|
||||
/>
|
||||
```
|
||||
|
||||
**DIRECTORY** presents a button labeled "Quick Claim" on the page of every **PODCAST**.
|
||||
|
||||
When **CREATOR** clicks on the "Quick Claim" button on the page belonging to their **PODCAST**, they are redirected
|
||||
When **CREATOR** clicks on the "Quick Claim" button on the page belonging to their **PODCAST**, they are redirected
|
||||
to their **HOST** with this URL:
|
||||
|
||||
```http
|
||||
https://host.com/studio/quick_claim/?guid=ead4c236-bf58-58c6-a2c6-a6b28d128cb6&consumer=https://directory.com/quick_claiming/ead4c236-bf58-58c6-a2c6-a6b28d128cb6&return_path=/return
|
||||
```
|
||||
|
||||
**HOST** can use the `consumer` parameter to fetch some data (name, icon, description) through open graph. In this
|
||||
example **DIRECTORY** uses the `consumer` URL, including podcast guid, and could use it to add some podcast info into the
|
||||
**HOST** can use the `consumer` parameter to fetch some data (name, icon, description) through open graph. In this
|
||||
example **DIRECTORY** uses the `consumer` URL, including podcast guid, and could use it to add some podcast info into the
|
||||
open graph description.
|
||||
|
||||
**HOST** asks for **CREATOR** confirmation in a logged-in only area after verifying, on their own responsibility, if
|
||||
**HOST** asks for **CREATOR** confirmation in a logged-in only area after verifying, on their own responsibility, if
|
||||
this user account indeed has access to this podcast.
|
||||
|
||||
When **CREATOR** agrees to the claim request, the **HOST** will generate a JWT token, signed by their private key,
|
||||
When **CREATOR** agrees to the claim request, the **HOST** will generate a JWT token, signed by their private key,
|
||||
authenticating the **CREATOR** decision.
|
||||
|
||||
#### Unsigned JWT:
|
||||
|
||||
```json
|
||||
{
|
||||
"guid": "ead4c236-bf58-58c6-a2c6-a6b28d128cb6",
|
||||
|
@ -424,7 +440,8 @@ authenticating the **CREATOR** decision.
|
|||
}
|
||||
```
|
||||
|
||||
#### Signed JWT:
|
||||
#### Signed JWT:
|
||||
|
||||
```base64
|
||||
eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJndWlkIjoiZWFkNGMyMzYtYmY1OC01OGM2LWEyYzYtYTZiMjhkMTI4Y2I2IiwiYWNjZXB0ZWQiOnRydWV9.eOXYFi9uUSUAKWcI8GdJ15RIhjoCvR0l9TUCPsqhsTYqaGFTwbH6zXzYqIqhxmtSotvL8ZLumP64LRFBjHX5Mw
|
||||
```
|
||||
|
@ -434,6 +451,7 @@ Decode/Encode online with : https://jwt.io/
|
|||
#### Key Details
|
||||
|
||||
- Private Key
|
||||
|
||||
```
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgevZzL1gdAFr88hb2
|
||||
|
@ -443,6 +461,7 @@ OF/2NxApJCzGCEDdfSp6VQO30hyhRANCAAQRWz+jn65BtOMvdyHKcvjBeBSDZH2r
|
|||
```
|
||||
|
||||
- Public Key
|
||||
|
||||
```
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9
|
||||
|
@ -458,7 +477,7 @@ q9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg==
|
|||
https://directory.com/quick_claiming/ead4c236-bf58-58c6-a2c6-a6b28d128cb6/return?token=eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJndWlkIjoiZWFkNGMyMzYtYmY1OC01OGM2LWEyYzYtYTZiMjhkMTI4Y2I2IiwiYWNjZXB0ZWQiOnRydWV9.eOXYFi9uUSUAKWcI8GdJ15RIhjoCvR0l9TUCPsqhsTYqaGFTwbH6zXzYqIqhxmtSotvL8ZLumP64LRFBjHX5Mw
|
||||
```
|
||||
|
||||
**DIRECTORY** can now check the token parameter to ensure it has been correctly signed with the private key
|
||||
**DIRECTORY** can now check the token parameter to ensure it has been correctly signed with the private key
|
||||
corresponding to the public key seen in the RSS feed, and in this case, the claiming request has been accepted.
|
||||
|
||||
Other responses could be, for example:
|
||||
|
@ -492,18 +511,19 @@ https://directory.com/quick_claiming/ead4c236-bf58-58c6-a2c6-a6b28d128cb6/return
|
|||
<br><br>
|
||||
|
||||
## Final Thoughts
|
||||
|
||||
Here are my thoughts on this idea and how to implement it, feel free to make any remarks about it.
|
||||
|
||||
JWT seems to me to be the middle ground between complexity and simplicity for a decentralized authorization system.
|
||||
It only requires the hosting provider to add a private key somewhere and print the pub key on the feed.
|
||||
Signing/Verifying JWT is quick and easy and there are libraries in almost any languages. It basically falls down
|
||||
It only requires the hosting provider to add a private key somewhere and print the pub key on the feed.
|
||||
Signing/Verifying JWT is quick and easy and there are libraries in almost any languages. It basically falls down
|
||||
to something like `[verify|sign]JWT(content, priv/pub_key)` in most languages.
|
||||
|
||||
Open Graph is a nice bonus "out of the box" for permission asker identity.
|
||||
|
||||
We could also make this a kind of API spec, and add more data to the token.
|
||||
Maybe mimick oauth and add a scope param asking specifically for some data, permissions about the
|
||||
Maybe mimick oauth and add a scope param asking specifically for some data, permissions about the
|
||||
feed. (`scope="stats,edit,delete,admin"` etc)
|
||||
|
||||
That's a big subject and a spec of its own. That could be a nice addition, but it has to be done in a way we can trust
|
||||
That's a big subject and a spec of its own. That could be a nice addition, but it has to be done in a way we can trust
|
||||
the system and all information it conveys.
|
||||
|
|
Ładowanie…
Reference in New Issue