Add Podcast RSS feeds (#5487)
* Initial test implementation of Podcast RSS
This is a pretty simple implementation to add support for The Podcast Namespace in RSS -- instead of affecting the existing RSS implementation, this adds a new UI option.
I attempted to retain compatibility with the rest of the RSS feed implementation as much as possible and have created a temporary fork of the "pfeed" library to support this effort.
* Update to pfeed-podcast 1.2.2
* Initial test implementation of Podcast RSS
This is a pretty simple implementation to add support for The Podcast Namespace in RSS -- instead of affecting the existing RSS implementation, this adds a new UI option.
I attempted to retain compatibility with the rest of the RSS feed implementation as much as possible and have created a temporary fork of the "pfeed" library to support this effort.
* Update to pfeed-podcast 1.2.2
* Initial test implementation of Podcast RSS
This is a pretty simple implementation to add support for The Podcast Namespace in RSS -- instead of affecting the existing RSS implementation, this adds a new UI option.
I attempted to retain compatibility with the rest of the RSS feed implementation as much as possible and have created a temporary fork of the "pfeed" library to support this effort.
* Update to pfeed-podcast 1.2.2
* Add correct feed image to RSS channel
* Prefer HLS videos for podcast RSS
Remove video/stream titles, add optional height attribute to podcast RSS
* Prefix podcast RSS images with root server URL
* Add optional video query support to include captions
* Add transcripts & person images to podcast RSS feed
* Prefer webseed/webtorrent files over HLS fragmented mp4s
* Experimentally adding podcast fields to basic config page
* Add validation for new basic config fields
* Don't include "content" in podcast feed, use full description for "description"
* Initial test implementation of Podcast RSS
This is a pretty simple implementation to add support for The Podcast Namespace in RSS -- instead of affecting the existing RSS implementation, this adds a new UI option.
I attempted to retain compatibility with the rest of the RSS feed implementation as much as possible and have created a temporary fork of the "pfeed" library to support this effort.
* Update to pfeed-podcast 1.2.2
* Add correct feed image to RSS channel
* Prefer HLS videos for podcast RSS
Remove video/stream titles, add optional height attribute to podcast RSS
* Prefix podcast RSS images with root server URL
* Add optional video query support to include captions
* Add transcripts & person images to podcast RSS feed
* Prefer webseed/webtorrent files over HLS fragmented mp4s
* Experimentally adding podcast fields to basic config page
* Add validation for new basic config fields
* Don't include "content" in podcast feed, use full description for "description"
* Add medium/socialInteract to podcast RSS feeds. Use HTML for description
* Change base production image to bullseye, install prosody in image
* Add liveItem and trackers to Podcast RSS feeds
Remove height from alternateEnclosure, replaced with title.
* Clear Podcast RSS feed cache when live streams start/end
* Upgrade to Node 16
* Refactor clearCacheRoute to use ApiCache
* Remove unnecessary type hint
* Update dockerfile to node 16, install python-is-python2
* Use new file paths for captions/playlists
* Fix legacy videos in RSS after migration to object storage
* Improve method of identifying non-fragmented mp4s in podcast RSS feeds
* Don't include fragmented MP4s in podcast RSS feeds
* Add experimental support for podcast:categories on the podcast RSS item
* Fix undefined category when no videos exist
Allows for empty feeds to exist (important for feeds that might only go live)
* Add support for podcast:locked -- user has to opt in to show their email
* Use comma for podcast:categories delimiter
* Make cache clearing async
* Fix merge, temporarily test with pfeed-podcast
* Syntax changes
* Add EXT_MIMETYPE constants for captions
* Update & fix tests, fix enclosure mimetypes, remove admin email
* Add test for podacst:socialInteract
* Add filters hooks for podcast customTags
* Remove showdown, updated to pfeed-podcast 6.1.2
* Add 'action:api.live-video.state.updated' hook
* Avoid assigning undefined category to podcast feeds
* Remove nvmrc
* Remove comment
* Remove unused podcast config
* Remove more unused podcast config
* Fix MChannelAccountDefault type hint missed in merge
* Remove extra line
* Re-add newline in config
* Fix lint errors for isEmailPublic
* Fix thumbnails in podcast feeds
* Requested changes based on review
* Provide podcast rss 2.0 only on video channels
* Misc cleanup for a less messy PR
* Lint fixes
* Remove pfeed-podcast
* Add peertube version to new hooks
* Don't use query include, remove TODO
* Remove film medium hack
* Clear podcast rss cache before video/channel update hooks
* Clear podcast rss cache before video uploaded/deleted hooks
* Refactor podcast feed cache clearing
* Set correct person name from video channel
* Styling
* Fix tests
---------
Co-authored-by: Chocobozzz <me@florianbigard.com>
2023-05-22 09:00:05 -05:00
|
|
|
import express from 'express'
|
|
|
|
import { extname } from 'path'
|
|
|
|
import { Feed } from '@peertube/feed'
|
|
|
|
import { CustomTag, CustomXMLNS, LiveItemStatus } from '@peertube/feed/lib/typings'
|
2023-06-21 03:44:40 -05:00
|
|
|
import { getBiggestActorImage } from '@server/lib/actor-image'
|
Add Podcast RSS feeds (#5487)
* Initial test implementation of Podcast RSS
This is a pretty simple implementation to add support for The Podcast Namespace in RSS -- instead of affecting the existing RSS implementation, this adds a new UI option.
I attempted to retain compatibility with the rest of the RSS feed implementation as much as possible and have created a temporary fork of the "pfeed" library to support this effort.
* Update to pfeed-podcast 1.2.2
* Initial test implementation of Podcast RSS
This is a pretty simple implementation to add support for The Podcast Namespace in RSS -- instead of affecting the existing RSS implementation, this adds a new UI option.
I attempted to retain compatibility with the rest of the RSS feed implementation as much as possible and have created a temporary fork of the "pfeed" library to support this effort.
* Update to pfeed-podcast 1.2.2
* Initial test implementation of Podcast RSS
This is a pretty simple implementation to add support for The Podcast Namespace in RSS -- instead of affecting the existing RSS implementation, this adds a new UI option.
I attempted to retain compatibility with the rest of the RSS feed implementation as much as possible and have created a temporary fork of the "pfeed" library to support this effort.
* Update to pfeed-podcast 1.2.2
* Add correct feed image to RSS channel
* Prefer HLS videos for podcast RSS
Remove video/stream titles, add optional height attribute to podcast RSS
* Prefix podcast RSS images with root server URL
* Add optional video query support to include captions
* Add transcripts & person images to podcast RSS feed
* Prefer webseed/webtorrent files over HLS fragmented mp4s
* Experimentally adding podcast fields to basic config page
* Add validation for new basic config fields
* Don't include "content" in podcast feed, use full description for "description"
* Initial test implementation of Podcast RSS
This is a pretty simple implementation to add support for The Podcast Namespace in RSS -- instead of affecting the existing RSS implementation, this adds a new UI option.
I attempted to retain compatibility with the rest of the RSS feed implementation as much as possible and have created a temporary fork of the "pfeed" library to support this effort.
* Update to pfeed-podcast 1.2.2
* Add correct feed image to RSS channel
* Prefer HLS videos for podcast RSS
Remove video/stream titles, add optional height attribute to podcast RSS
* Prefix podcast RSS images with root server URL
* Add optional video query support to include captions
* Add transcripts & person images to podcast RSS feed
* Prefer webseed/webtorrent files over HLS fragmented mp4s
* Experimentally adding podcast fields to basic config page
* Add validation for new basic config fields
* Don't include "content" in podcast feed, use full description for "description"
* Add medium/socialInteract to podcast RSS feeds. Use HTML for description
* Change base production image to bullseye, install prosody in image
* Add liveItem and trackers to Podcast RSS feeds
Remove height from alternateEnclosure, replaced with title.
* Clear Podcast RSS feed cache when live streams start/end
* Upgrade to Node 16
* Refactor clearCacheRoute to use ApiCache
* Remove unnecessary type hint
* Update dockerfile to node 16, install python-is-python2
* Use new file paths for captions/playlists
* Fix legacy videos in RSS after migration to object storage
* Improve method of identifying non-fragmented mp4s in podcast RSS feeds
* Don't include fragmented MP4s in podcast RSS feeds
* Add experimental support for podcast:categories on the podcast RSS item
* Fix undefined category when no videos exist
Allows for empty feeds to exist (important for feeds that might only go live)
* Add support for podcast:locked -- user has to opt in to show their email
* Use comma for podcast:categories delimiter
* Make cache clearing async
* Fix merge, temporarily test with pfeed-podcast
* Syntax changes
* Add EXT_MIMETYPE constants for captions
* Update & fix tests, fix enclosure mimetypes, remove admin email
* Add test for podacst:socialInteract
* Add filters hooks for podcast customTags
* Remove showdown, updated to pfeed-podcast 6.1.2
* Add 'action:api.live-video.state.updated' hook
* Avoid assigning undefined category to podcast feeds
* Remove nvmrc
* Remove comment
* Remove unused podcast config
* Remove more unused podcast config
* Fix MChannelAccountDefault type hint missed in merge
* Remove extra line
* Re-add newline in config
* Fix lint errors for isEmailPublic
* Fix thumbnails in podcast feeds
* Requested changes based on review
* Provide podcast rss 2.0 only on video channels
* Misc cleanup for a less messy PR
* Lint fixes
* Remove pfeed-podcast
* Add peertube version to new hooks
* Don't use query include, remove TODO
* Remove film medium hack
* Clear podcast rss cache before video/channel update hooks
* Clear podcast rss cache before video uploaded/deleted hooks
* Refactor podcast feed cache clearing
* Set correct person name from video channel
* Styling
* Fix tests
---------
Co-authored-by: Chocobozzz <me@florianbigard.com>
2023-05-22 09:00:05 -05:00
|
|
|
import { InternalEventEmitter } from '@server/lib/internal-event-emitter'
|
|
|
|
import { Hooks } from '@server/lib/plugins/hooks'
|
|
|
|
import { buildPodcastGroupsCache, cacheRouteFactory, videoFeedsPodcastSetCacheKey } from '@server/middlewares'
|
|
|
|
import { MVideo, MVideoCaptionVideo, MVideoFullLight } from '@server/types/models'
|
|
|
|
import { sortObjectComparator } from '@shared/core-utils'
|
|
|
|
import { ActorImageType, VideoFile, VideoInclude, VideoResolution, VideoState } from '@shared/models'
|
|
|
|
import { buildNSFWFilter } from '../../helpers/express-utils'
|
|
|
|
import { MIMETYPES, ROUTE_CACHE_LIFETIME, WEBSERVER } from '../../initializers/constants'
|
|
|
|
import { asyncMiddleware, setFeedPodcastContentType, videoFeedsPodcastValidator } from '../../middlewares'
|
|
|
|
import { VideoModel } from '../../models/video/video'
|
|
|
|
import { VideoCaptionModel } from '../../models/video/video-caption'
|
|
|
|
import { buildFeedMetadata, getCommonVideoFeedAttributes, getVideosForFeeds, initFeed } from './shared'
|
|
|
|
|
|
|
|
const videoPodcastFeedsRouter = express.Router()
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
const { middleware: podcastCacheRouteMiddleware, instance: podcastApiCache } = cacheRouteFactory({
|
|
|
|
headerBlacklist: [ 'Content-Type' ]
|
|
|
|
})
|
|
|
|
|
|
|
|
for (const event of ([ 'video-created', 'video-updated', 'video-deleted' ] as const)) {
|
|
|
|
InternalEventEmitter.Instance.on(event, ({ video }) => {
|
|
|
|
if (video.remote) return
|
|
|
|
|
|
|
|
podcastApiCache.clearGroupSafe(buildPodcastGroupsCache({ channelId: video.channelId }))
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const event of ([ 'channel-updated', 'channel-deleted' ] as const)) {
|
|
|
|
InternalEventEmitter.Instance.on(event, ({ channel }) => {
|
|
|
|
podcastApiCache.clearGroupSafe(buildPodcastGroupsCache({ channelId: channel.id }))
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
videoPodcastFeedsRouter.get('/feeds/podcast/videos.xml',
|
|
|
|
setFeedPodcastContentType,
|
|
|
|
videoFeedsPodcastSetCacheKey,
|
|
|
|
podcastCacheRouteMiddleware(ROUTE_CACHE_LIFETIME.FEEDS),
|
|
|
|
asyncMiddleware(videoFeedsPodcastValidator),
|
|
|
|
asyncMiddleware(generateVideoPodcastFeed)
|
|
|
|
)
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
export {
|
|
|
|
videoPodcastFeedsRouter
|
|
|
|
}
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
async function generateVideoPodcastFeed (req: express.Request, res: express.Response) {
|
|
|
|
const videoChannel = res.locals.videoChannel
|
|
|
|
|
|
|
|
const { name, userName, description, imageUrl, accountImageUrl, email, link, accountLink } = await buildFeedMetadata({ videoChannel })
|
|
|
|
|
|
|
|
const data = await getVideosForFeeds({
|
|
|
|
sort: '-publishedAt',
|
|
|
|
nsfw: buildNSFWFilter(),
|
|
|
|
// Prevent podcast feeds from listing videos in other instances
|
|
|
|
// helps prevent duplicates when they are indexed -- only the author should control them
|
|
|
|
isLocal: true,
|
|
|
|
include: VideoInclude.FILES,
|
|
|
|
videoChannelId: videoChannel?.id
|
|
|
|
})
|
|
|
|
|
|
|
|
const customTags: CustomTag[] = await Hooks.wrapObject(
|
|
|
|
[],
|
|
|
|
'filter:feed.podcast.channel.create-custom-tags.result',
|
|
|
|
{ videoChannel }
|
|
|
|
)
|
|
|
|
|
|
|
|
const customXMLNS: CustomXMLNS[] = await Hooks.wrapObject(
|
|
|
|
[],
|
|
|
|
'filter:feed.podcast.rss.create-custom-xmlns.result'
|
|
|
|
)
|
|
|
|
|
|
|
|
const feed = initFeed({
|
|
|
|
name,
|
|
|
|
description,
|
|
|
|
link,
|
|
|
|
isPodcast: true,
|
|
|
|
imageUrl,
|
|
|
|
|
|
|
|
locked: email
|
|
|
|
? { isLocked: true, email } // Default to true because we have no way of offering a redirect yet
|
|
|
|
: undefined,
|
|
|
|
|
|
|
|
person: [ { name: userName, href: accountLink, img: accountImageUrl } ],
|
|
|
|
resourceType: 'videos',
|
|
|
|
queryString: new URL(WEBSERVER.URL + req.url).search,
|
|
|
|
medium: 'video',
|
|
|
|
customXMLNS,
|
|
|
|
customTags
|
|
|
|
})
|
|
|
|
|
|
|
|
await addVideosToPodcastFeed(feed, data)
|
|
|
|
|
|
|
|
// Now the feed generation is done, let's send it!
|
|
|
|
return res.send(feed.podcast()).end()
|
|
|
|
}
|
|
|
|
|
|
|
|
type PodcastMedia =
|
|
|
|
{
|
|
|
|
type: string
|
|
|
|
length: number
|
|
|
|
bitrate: number
|
|
|
|
sources: { uri: string, contentType?: string }[]
|
|
|
|
title: string
|
|
|
|
language?: string
|
|
|
|
} |
|
|
|
|
{
|
|
|
|
sources: { uri: string }[]
|
|
|
|
type: string
|
|
|
|
title: string
|
|
|
|
}
|
|
|
|
|
|
|
|
async function generatePodcastItem (options: {
|
|
|
|
video: VideoModel
|
|
|
|
liveItem: boolean
|
|
|
|
media: PodcastMedia[]
|
|
|
|
}) {
|
|
|
|
const { video, liveItem, media } = options
|
|
|
|
|
|
|
|
const customTags: CustomTag[] = await Hooks.wrapObject(
|
|
|
|
[],
|
|
|
|
'filter:feed.podcast.video.create-custom-tags.result',
|
|
|
|
{ video, liveItem }
|
|
|
|
)
|
|
|
|
|
|
|
|
const account = video.VideoChannel.Account
|
|
|
|
|
|
|
|
const author = {
|
|
|
|
name: account.getDisplayName(),
|
|
|
|
href: account.getClientUrl()
|
|
|
|
}
|
|
|
|
|
2023-06-05 02:00:30 -05:00
|
|
|
const commonAttributes = getCommonVideoFeedAttributes(video)
|
|
|
|
const guid = liveItem
|
|
|
|
? `${video.uuid}_${video.publishedAt.toISOString()}`
|
|
|
|
: commonAttributes.link
|
|
|
|
|
|
|
|
let personImage: string
|
|
|
|
|
|
|
|
if (account.Actor.hasImage(ActorImageType.AVATAR)) {
|
2023-06-21 03:54:48 -05:00
|
|
|
const avatar = getBiggestActorImage(account.Actor.Avatars)
|
2023-06-05 02:00:30 -05:00
|
|
|
personImage = WEBSERVER.URL + avatar.getStaticPath()
|
|
|
|
}
|
|
|
|
|
Add Podcast RSS feeds (#5487)
* Initial test implementation of Podcast RSS
This is a pretty simple implementation to add support for The Podcast Namespace in RSS -- instead of affecting the existing RSS implementation, this adds a new UI option.
I attempted to retain compatibility with the rest of the RSS feed implementation as much as possible and have created a temporary fork of the "pfeed" library to support this effort.
* Update to pfeed-podcast 1.2.2
* Initial test implementation of Podcast RSS
This is a pretty simple implementation to add support for The Podcast Namespace in RSS -- instead of affecting the existing RSS implementation, this adds a new UI option.
I attempted to retain compatibility with the rest of the RSS feed implementation as much as possible and have created a temporary fork of the "pfeed" library to support this effort.
* Update to pfeed-podcast 1.2.2
* Initial test implementation of Podcast RSS
This is a pretty simple implementation to add support for The Podcast Namespace in RSS -- instead of affecting the existing RSS implementation, this adds a new UI option.
I attempted to retain compatibility with the rest of the RSS feed implementation as much as possible and have created a temporary fork of the "pfeed" library to support this effort.
* Update to pfeed-podcast 1.2.2
* Add correct feed image to RSS channel
* Prefer HLS videos for podcast RSS
Remove video/stream titles, add optional height attribute to podcast RSS
* Prefix podcast RSS images with root server URL
* Add optional video query support to include captions
* Add transcripts & person images to podcast RSS feed
* Prefer webseed/webtorrent files over HLS fragmented mp4s
* Experimentally adding podcast fields to basic config page
* Add validation for new basic config fields
* Don't include "content" in podcast feed, use full description for "description"
* Initial test implementation of Podcast RSS
This is a pretty simple implementation to add support for The Podcast Namespace in RSS -- instead of affecting the existing RSS implementation, this adds a new UI option.
I attempted to retain compatibility with the rest of the RSS feed implementation as much as possible and have created a temporary fork of the "pfeed" library to support this effort.
* Update to pfeed-podcast 1.2.2
* Add correct feed image to RSS channel
* Prefer HLS videos for podcast RSS
Remove video/stream titles, add optional height attribute to podcast RSS
* Prefix podcast RSS images with root server URL
* Add optional video query support to include captions
* Add transcripts & person images to podcast RSS feed
* Prefer webseed/webtorrent files over HLS fragmented mp4s
* Experimentally adding podcast fields to basic config page
* Add validation for new basic config fields
* Don't include "content" in podcast feed, use full description for "description"
* Add medium/socialInteract to podcast RSS feeds. Use HTML for description
* Change base production image to bullseye, install prosody in image
* Add liveItem and trackers to Podcast RSS feeds
Remove height from alternateEnclosure, replaced with title.
* Clear Podcast RSS feed cache when live streams start/end
* Upgrade to Node 16
* Refactor clearCacheRoute to use ApiCache
* Remove unnecessary type hint
* Update dockerfile to node 16, install python-is-python2
* Use new file paths for captions/playlists
* Fix legacy videos in RSS after migration to object storage
* Improve method of identifying non-fragmented mp4s in podcast RSS feeds
* Don't include fragmented MP4s in podcast RSS feeds
* Add experimental support for podcast:categories on the podcast RSS item
* Fix undefined category when no videos exist
Allows for empty feeds to exist (important for feeds that might only go live)
* Add support for podcast:locked -- user has to opt in to show their email
* Use comma for podcast:categories delimiter
* Make cache clearing async
* Fix merge, temporarily test with pfeed-podcast
* Syntax changes
* Add EXT_MIMETYPE constants for captions
* Update & fix tests, fix enclosure mimetypes, remove admin email
* Add test for podacst:socialInteract
* Add filters hooks for podcast customTags
* Remove showdown, updated to pfeed-podcast 6.1.2
* Add 'action:api.live-video.state.updated' hook
* Avoid assigning undefined category to podcast feeds
* Remove nvmrc
* Remove comment
* Remove unused podcast config
* Remove more unused podcast config
* Fix MChannelAccountDefault type hint missed in merge
* Remove extra line
* Re-add newline in config
* Fix lint errors for isEmailPublic
* Fix thumbnails in podcast feeds
* Requested changes based on review
* Provide podcast rss 2.0 only on video channels
* Misc cleanup for a less messy PR
* Lint fixes
* Remove pfeed-podcast
* Add peertube version to new hooks
* Don't use query include, remove TODO
* Remove film medium hack
* Clear podcast rss cache before video/channel update hooks
* Clear podcast rss cache before video uploaded/deleted hooks
* Refactor podcast feed cache clearing
* Set correct person name from video channel
* Styling
* Fix tests
---------
Co-authored-by: Chocobozzz <me@florianbigard.com>
2023-05-22 09:00:05 -05:00
|
|
|
return {
|
2023-06-05 02:00:30 -05:00
|
|
|
guid,
|
|
|
|
...commonAttributes,
|
Add Podcast RSS feeds (#5487)
* Initial test implementation of Podcast RSS
This is a pretty simple implementation to add support for The Podcast Namespace in RSS -- instead of affecting the existing RSS implementation, this adds a new UI option.
I attempted to retain compatibility with the rest of the RSS feed implementation as much as possible and have created a temporary fork of the "pfeed" library to support this effort.
* Update to pfeed-podcast 1.2.2
* Initial test implementation of Podcast RSS
This is a pretty simple implementation to add support for The Podcast Namespace in RSS -- instead of affecting the existing RSS implementation, this adds a new UI option.
I attempted to retain compatibility with the rest of the RSS feed implementation as much as possible and have created a temporary fork of the "pfeed" library to support this effort.
* Update to pfeed-podcast 1.2.2
* Initial test implementation of Podcast RSS
This is a pretty simple implementation to add support for The Podcast Namespace in RSS -- instead of affecting the existing RSS implementation, this adds a new UI option.
I attempted to retain compatibility with the rest of the RSS feed implementation as much as possible and have created a temporary fork of the "pfeed" library to support this effort.
* Update to pfeed-podcast 1.2.2
* Add correct feed image to RSS channel
* Prefer HLS videos for podcast RSS
Remove video/stream titles, add optional height attribute to podcast RSS
* Prefix podcast RSS images with root server URL
* Add optional video query support to include captions
* Add transcripts & person images to podcast RSS feed
* Prefer webseed/webtorrent files over HLS fragmented mp4s
* Experimentally adding podcast fields to basic config page
* Add validation for new basic config fields
* Don't include "content" in podcast feed, use full description for "description"
* Initial test implementation of Podcast RSS
This is a pretty simple implementation to add support for The Podcast Namespace in RSS -- instead of affecting the existing RSS implementation, this adds a new UI option.
I attempted to retain compatibility with the rest of the RSS feed implementation as much as possible and have created a temporary fork of the "pfeed" library to support this effort.
* Update to pfeed-podcast 1.2.2
* Add correct feed image to RSS channel
* Prefer HLS videos for podcast RSS
Remove video/stream titles, add optional height attribute to podcast RSS
* Prefix podcast RSS images with root server URL
* Add optional video query support to include captions
* Add transcripts & person images to podcast RSS feed
* Prefer webseed/webtorrent files over HLS fragmented mp4s
* Experimentally adding podcast fields to basic config page
* Add validation for new basic config fields
* Don't include "content" in podcast feed, use full description for "description"
* Add medium/socialInteract to podcast RSS feeds. Use HTML for description
* Change base production image to bullseye, install prosody in image
* Add liveItem and trackers to Podcast RSS feeds
Remove height from alternateEnclosure, replaced with title.
* Clear Podcast RSS feed cache when live streams start/end
* Upgrade to Node 16
* Refactor clearCacheRoute to use ApiCache
* Remove unnecessary type hint
* Update dockerfile to node 16, install python-is-python2
* Use new file paths for captions/playlists
* Fix legacy videos in RSS after migration to object storage
* Improve method of identifying non-fragmented mp4s in podcast RSS feeds
* Don't include fragmented MP4s in podcast RSS feeds
* Add experimental support for podcast:categories on the podcast RSS item
* Fix undefined category when no videos exist
Allows for empty feeds to exist (important for feeds that might only go live)
* Add support for podcast:locked -- user has to opt in to show their email
* Use comma for podcast:categories delimiter
* Make cache clearing async
* Fix merge, temporarily test with pfeed-podcast
* Syntax changes
* Add EXT_MIMETYPE constants for captions
* Update & fix tests, fix enclosure mimetypes, remove admin email
* Add test for podacst:socialInteract
* Add filters hooks for podcast customTags
* Remove showdown, updated to pfeed-podcast 6.1.2
* Add 'action:api.live-video.state.updated' hook
* Avoid assigning undefined category to podcast feeds
* Remove nvmrc
* Remove comment
* Remove unused podcast config
* Remove more unused podcast config
* Fix MChannelAccountDefault type hint missed in merge
* Remove extra line
* Re-add newline in config
* Fix lint errors for isEmailPublic
* Fix thumbnails in podcast feeds
* Requested changes based on review
* Provide podcast rss 2.0 only on video channels
* Misc cleanup for a less messy PR
* Lint fixes
* Remove pfeed-podcast
* Add peertube version to new hooks
* Don't use query include, remove TODO
* Remove film medium hack
* Clear podcast rss cache before video/channel update hooks
* Clear podcast rss cache before video uploaded/deleted hooks
* Refactor podcast feed cache clearing
* Set correct person name from video channel
* Styling
* Fix tests
---------
Co-authored-by: Chocobozzz <me@florianbigard.com>
2023-05-22 09:00:05 -05:00
|
|
|
|
|
|
|
trackers: video.getTrackerUrls(),
|
|
|
|
|
|
|
|
author: [ author ],
|
|
|
|
person: [
|
|
|
|
{
|
|
|
|
...author,
|
|
|
|
|
2023-06-05 02:00:30 -05:00
|
|
|
img: personImage
|
Add Podcast RSS feeds (#5487)
* Initial test implementation of Podcast RSS
This is a pretty simple implementation to add support for The Podcast Namespace in RSS -- instead of affecting the existing RSS implementation, this adds a new UI option.
I attempted to retain compatibility with the rest of the RSS feed implementation as much as possible and have created a temporary fork of the "pfeed" library to support this effort.
* Update to pfeed-podcast 1.2.2
* Initial test implementation of Podcast RSS
This is a pretty simple implementation to add support for The Podcast Namespace in RSS -- instead of affecting the existing RSS implementation, this adds a new UI option.
I attempted to retain compatibility with the rest of the RSS feed implementation as much as possible and have created a temporary fork of the "pfeed" library to support this effort.
* Update to pfeed-podcast 1.2.2
* Initial test implementation of Podcast RSS
This is a pretty simple implementation to add support for The Podcast Namespace in RSS -- instead of affecting the existing RSS implementation, this adds a new UI option.
I attempted to retain compatibility with the rest of the RSS feed implementation as much as possible and have created a temporary fork of the "pfeed" library to support this effort.
* Update to pfeed-podcast 1.2.2
* Add correct feed image to RSS channel
* Prefer HLS videos for podcast RSS
Remove video/stream titles, add optional height attribute to podcast RSS
* Prefix podcast RSS images with root server URL
* Add optional video query support to include captions
* Add transcripts & person images to podcast RSS feed
* Prefer webseed/webtorrent files over HLS fragmented mp4s
* Experimentally adding podcast fields to basic config page
* Add validation for new basic config fields
* Don't include "content" in podcast feed, use full description for "description"
* Initial test implementation of Podcast RSS
This is a pretty simple implementation to add support for The Podcast Namespace in RSS -- instead of affecting the existing RSS implementation, this adds a new UI option.
I attempted to retain compatibility with the rest of the RSS feed implementation as much as possible and have created a temporary fork of the "pfeed" library to support this effort.
* Update to pfeed-podcast 1.2.2
* Add correct feed image to RSS channel
* Prefer HLS videos for podcast RSS
Remove video/stream titles, add optional height attribute to podcast RSS
* Prefix podcast RSS images with root server URL
* Add optional video query support to include captions
* Add transcripts & person images to podcast RSS feed
* Prefer webseed/webtorrent files over HLS fragmented mp4s
* Experimentally adding podcast fields to basic config page
* Add validation for new basic config fields
* Don't include "content" in podcast feed, use full description for "description"
* Add medium/socialInteract to podcast RSS feeds. Use HTML for description
* Change base production image to bullseye, install prosody in image
* Add liveItem and trackers to Podcast RSS feeds
Remove height from alternateEnclosure, replaced with title.
* Clear Podcast RSS feed cache when live streams start/end
* Upgrade to Node 16
* Refactor clearCacheRoute to use ApiCache
* Remove unnecessary type hint
* Update dockerfile to node 16, install python-is-python2
* Use new file paths for captions/playlists
* Fix legacy videos in RSS after migration to object storage
* Improve method of identifying non-fragmented mp4s in podcast RSS feeds
* Don't include fragmented MP4s in podcast RSS feeds
* Add experimental support for podcast:categories on the podcast RSS item
* Fix undefined category when no videos exist
Allows for empty feeds to exist (important for feeds that might only go live)
* Add support for podcast:locked -- user has to opt in to show their email
* Use comma for podcast:categories delimiter
* Make cache clearing async
* Fix merge, temporarily test with pfeed-podcast
* Syntax changes
* Add EXT_MIMETYPE constants for captions
* Update & fix tests, fix enclosure mimetypes, remove admin email
* Add test for podacst:socialInteract
* Add filters hooks for podcast customTags
* Remove showdown, updated to pfeed-podcast 6.1.2
* Add 'action:api.live-video.state.updated' hook
* Avoid assigning undefined category to podcast feeds
* Remove nvmrc
* Remove comment
* Remove unused podcast config
* Remove more unused podcast config
* Fix MChannelAccountDefault type hint missed in merge
* Remove extra line
* Re-add newline in config
* Fix lint errors for isEmailPublic
* Fix thumbnails in podcast feeds
* Requested changes based on review
* Provide podcast rss 2.0 only on video channels
* Misc cleanup for a less messy PR
* Lint fixes
* Remove pfeed-podcast
* Add peertube version to new hooks
* Don't use query include, remove TODO
* Remove film medium hack
* Clear podcast rss cache before video/channel update hooks
* Clear podcast rss cache before video uploaded/deleted hooks
* Refactor podcast feed cache clearing
* Set correct person name from video channel
* Styling
* Fix tests
---------
Co-authored-by: Chocobozzz <me@florianbigard.com>
2023-05-22 09:00:05 -05:00
|
|
|
}
|
|
|
|
],
|
|
|
|
|
|
|
|
media,
|
|
|
|
|
|
|
|
socialInteract: [
|
|
|
|
{
|
|
|
|
uri: video.url,
|
|
|
|
protocol: 'activitypub',
|
|
|
|
accountUrl: account.getClientUrl()
|
|
|
|
}
|
|
|
|
],
|
|
|
|
|
|
|
|
customTags
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function addVideosToPodcastFeed (feed: Feed, videos: VideoModel[]) {
|
|
|
|
const captionsGroup = await VideoCaptionModel.listCaptionsOfMultipleVideos(videos.map(v => v.id))
|
|
|
|
|
|
|
|
for (const video of videos) {
|
|
|
|
if (!video.isLive) {
|
|
|
|
await addVODPodcastItem({ feed, video, captionsGroup })
|
|
|
|
} else if (video.isLive && video.state !== VideoState.LIVE_ENDED) {
|
|
|
|
await addLivePodcastItem({ feed, video })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function addVODPodcastItem (options: {
|
|
|
|
feed: Feed
|
|
|
|
video: VideoModel
|
|
|
|
captionsGroup: { [ id: number ]: MVideoCaptionVideo[] }
|
|
|
|
}) {
|
|
|
|
const { feed, video, captionsGroup } = options
|
|
|
|
|
|
|
|
const webVideos = video.getFormattedWebVideoFilesJSON(true)
|
|
|
|
.map(f => buildVODWebVideoFile(video, f))
|
|
|
|
.sort(sortObjectComparator('bitrate', 'desc'))
|
|
|
|
|
|
|
|
const streamingPlaylistFiles = buildVODStreamingPlaylists(video)
|
|
|
|
|
|
|
|
// Order matters here, the first media URI will be the "default"
|
|
|
|
// So web videos are default if enabled
|
|
|
|
const media = [ ...webVideos, ...streamingPlaylistFiles ]
|
|
|
|
|
|
|
|
const videoCaptions = buildVODCaptions(video, captionsGroup[video.id])
|
|
|
|
const item = await generatePodcastItem({ video, liveItem: false, media })
|
|
|
|
|
|
|
|
feed.addPodcastItem({ ...item, subTitle: videoCaptions })
|
|
|
|
}
|
|
|
|
|
|
|
|
async function addLivePodcastItem (options: {
|
|
|
|
feed: Feed
|
|
|
|
video: VideoModel
|
|
|
|
}) {
|
|
|
|
const { feed, video } = options
|
|
|
|
|
|
|
|
let status: LiveItemStatus
|
|
|
|
|
|
|
|
switch (video.state) {
|
|
|
|
case VideoState.WAITING_FOR_LIVE:
|
|
|
|
status = LiveItemStatus.pending
|
|
|
|
break
|
|
|
|
case VideoState.PUBLISHED:
|
|
|
|
status = LiveItemStatus.live
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
const item = await generatePodcastItem({ video, liveItem: true, media: buildLiveStreamingPlaylists(video) })
|
|
|
|
|
|
|
|
feed.addPodcastLiveItem({ ...item, status, start: video.updatedAt.toISOString() })
|
|
|
|
}
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
function buildVODWebVideoFile (video: MVideo, videoFile: VideoFile) {
|
|
|
|
const isAudio = videoFile.resolution.id === VideoResolution.H_NOVIDEO
|
|
|
|
const type = isAudio
|
|
|
|
? MIMETYPES.AUDIO.EXT_MIMETYPE[extname(videoFile.fileUrl)]
|
|
|
|
: MIMETYPES.VIDEO.EXT_MIMETYPE[extname(videoFile.fileUrl)]
|
|
|
|
|
|
|
|
const sources = [
|
|
|
|
{ uri: videoFile.fileUrl },
|
|
|
|
{ uri: videoFile.torrentUrl, contentType: 'application/x-bittorrent' }
|
|
|
|
]
|
|
|
|
|
|
|
|
if (videoFile.magnetUri) {
|
|
|
|
sources.push({ uri: videoFile.magnetUri })
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
type,
|
|
|
|
title: videoFile.resolution.label,
|
|
|
|
length: videoFile.size,
|
|
|
|
bitrate: videoFile.size / video.duration * 8,
|
|
|
|
language: video.language,
|
|
|
|
sources
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function buildVODStreamingPlaylists (video: MVideoFullLight) {
|
|
|
|
const hls = video.getHLSPlaylist()
|
|
|
|
if (!hls) return []
|
|
|
|
|
|
|
|
return [
|
|
|
|
{
|
|
|
|
type: 'application/x-mpegURL',
|
|
|
|
title: 'HLS',
|
|
|
|
sources: [
|
|
|
|
{ uri: hls.getMasterPlaylistUrl(video) }
|
|
|
|
],
|
|
|
|
language: video.language
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
|
|
|
|
function buildLiveStreamingPlaylists (video: MVideoFullLight) {
|
|
|
|
const hls = video.getHLSPlaylist()
|
|
|
|
|
|
|
|
return [
|
|
|
|
{
|
|
|
|
type: 'application/x-mpegURL',
|
|
|
|
title: `HLS live stream`,
|
|
|
|
sources: [
|
|
|
|
{ uri: hls.getMasterPlaylistUrl(video) }
|
|
|
|
],
|
|
|
|
language: video.language
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
|
|
|
|
function buildVODCaptions (video: MVideo, videoCaptions: MVideoCaptionVideo[]) {
|
|
|
|
return videoCaptions.map(caption => {
|
|
|
|
const type = MIMETYPES.VIDEO_CAPTIONS.EXT_MIMETYPE[extname(caption.filename)]
|
|
|
|
if (!type) return null
|
|
|
|
|
|
|
|
return {
|
|
|
|
url: caption.getFileUrl(video),
|
|
|
|
language: caption.language,
|
|
|
|
type,
|
|
|
|
rel: 'captions'
|
|
|
|
}
|
|
|
|
}).filter(c => c)
|
|
|
|
}
|