diff options
author | Alecks Gates <agates@mail.agates.io> | 2023-05-22 09:00:05 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-05-22 16:00:05 +0200 |
commit | cb0eda5602a21d1626a7face32de6153ed07b5f9 (patch) | |
tree | d6a7a4e31c7267c130871ac8e3beb42994271c20 /server/models/video/video.ts | |
parent | 3f0ceab06e5320f62f593c49daa30d963dbc36f9 (diff) | |
download | PeerTube-cb0eda5602a21d1626a7face32de6153ed07b5f9.tar.gz PeerTube-cb0eda5602a21d1626a7face32de6153ed07b5f9.tar.zst PeerTube-cb0eda5602a21d1626a7face32de6153ed07b5f9.zip |
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>
Diffstat (limited to 'server/models/video/video.ts')
-rw-r--r-- | server/models/video/video.ts | 53 |
1 files changed, 41 insertions, 12 deletions
diff --git a/server/models/video/video.ts b/server/models/video/video.ts index baa8c120a..8e3af62a4 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts | |||
@@ -1,9 +1,11 @@ | |||
1 | import Bluebird from 'bluebird' | 1 | import Bluebird from 'bluebird' |
2 | import { remove } from 'fs-extra' | 2 | import { remove } from 'fs-extra' |
3 | import { maxBy, minBy } from 'lodash' | 3 | import { maxBy, minBy } from 'lodash' |
4 | import { join } from 'path' | ||
5 | import { FindOptions, Includeable, IncludeOptions, Op, QueryTypes, ScopeOptions, Sequelize, Transaction, WhereOptions } from 'sequelize' | 4 | import { FindOptions, Includeable, IncludeOptions, Op, QueryTypes, ScopeOptions, Sequelize, Transaction, WhereOptions } from 'sequelize' |
6 | import { | 5 | import { |
6 | AfterCreate, | ||
7 | AfterDestroy, | ||
8 | AfterUpdate, | ||
7 | AllowNull, | 9 | AllowNull, |
8 | BeforeDestroy, | 10 | BeforeDestroy, |
9 | BelongsTo, | 11 | BelongsTo, |
@@ -25,6 +27,7 @@ import { | |||
25 | UpdatedAt | 27 | UpdatedAt |
26 | } from 'sequelize-typescript' | 28 | } from 'sequelize-typescript' |
27 | import { getPrivaciesForFederation, isPrivacyForFederation, isStateForFederation } from '@server/helpers/video' | 29 | import { getPrivaciesForFederation, isPrivacyForFederation, isStateForFederation } from '@server/helpers/video' |
30 | import { InternalEventEmitter } from '@server/lib/internal-event-emitter' | ||
28 | import { LiveManager } from '@server/lib/live/live-manager' | 31 | import { LiveManager } from '@server/lib/live/live-manager' |
29 | import { removeHLSFileObjectStorageByFilename, removeHLSObjectStorage, removeWebTorrentObjectStorage } from '@server/lib/object-storage' | 32 | import { removeHLSFileObjectStorageByFilename, removeHLSObjectStorage, removeWebTorrentObjectStorage } from '@server/lib/object-storage' |
30 | import { tracer } from '@server/lib/opentelemetry/tracing' | 33 | import { tracer } from '@server/lib/opentelemetry/tracing' |
@@ -66,7 +69,7 @@ import { | |||
66 | } from '../../helpers/custom-validators/videos' | 69 | } from '../../helpers/custom-validators/videos' |
67 | import { logger } from '../../helpers/logger' | 70 | import { logger } from '../../helpers/logger' |
68 | import { CONFIG } from '../../initializers/config' | 71 | import { CONFIG } from '../../initializers/config' |
69 | import { ACTIVITY_PUB, API_VERSION, CONSTRAINTS_FIELDS, LAZY_STATIC_PATHS, STATIC_PATHS, WEBSERVER } from '../../initializers/constants' | 72 | import { ACTIVITY_PUB, API_VERSION, CONSTRAINTS_FIELDS, WEBSERVER } from '../../initializers/constants' |
70 | import { sendDeleteVideo } from '../../lib/activitypub/send' | 73 | import { sendDeleteVideo } from '../../lib/activitypub/send' |
71 | import { | 74 | import { |
72 | MChannel, | 75 | MChannel, |
@@ -740,8 +743,23 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> { | |||
740 | }) | 743 | }) |
741 | VideoJobInfo: VideoJobInfoModel | 744 | VideoJobInfo: VideoJobInfoModel |
742 | 745 | ||
746 | @AfterCreate | ||
747 | static notifyCreate (video: MVideo) { | ||
748 | InternalEventEmitter.Instance.emit('video-created', { video }) | ||
749 | } | ||
750 | |||
751 | @AfterUpdate | ||
752 | static notifyUpdate (video: MVideo) { | ||
753 | InternalEventEmitter.Instance.emit('video-updated', { video }) | ||
754 | } | ||
755 | |||
756 | @AfterDestroy | ||
757 | static notifyDestroy (video: MVideo) { | ||
758 | InternalEventEmitter.Instance.emit('video-deleted', { video }) | ||
759 | } | ||
760 | |||
743 | @BeforeDestroy | 761 | @BeforeDestroy |
744 | static async sendDelete (instance: MVideoAccountLight, options) { | 762 | static async sendDelete (instance: MVideoAccountLight, options: { transaction: Transaction }) { |
745 | if (!instance.isOwned()) return undefined | 763 | if (!instance.isOwned()) return undefined |
746 | 764 | ||
747 | // Lazy load channels | 765 | // Lazy load channels |
@@ -1686,15 +1704,14 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> { | |||
1686 | const thumbnail = this.getMiniature() | 1704 | const thumbnail = this.getMiniature() |
1687 | if (!thumbnail) return null | 1705 | if (!thumbnail) return null |
1688 | 1706 | ||
1689 | return join(STATIC_PATHS.THUMBNAILS, thumbnail.filename) | 1707 | return thumbnail.getLocalStaticPath() |
1690 | } | 1708 | } |
1691 | 1709 | ||
1692 | getPreviewStaticPath () { | 1710 | getPreviewStaticPath () { |
1693 | const preview = this.getPreview() | 1711 | const preview = this.getPreview() |
1694 | if (!preview) return null | 1712 | if (!preview) return null |
1695 | 1713 | ||
1696 | // We use a local cache, so specify our cache endpoint instead of potential remote URL | 1714 | return preview.getLocalStaticPath() |
1697 | return join(LAZY_STATIC_PATHS.PREVIEWS, preview.filename) | ||
1698 | } | 1715 | } |
1699 | 1716 | ||
1700 | toFormattedJSON (this: MVideoFormattable, options?: VideoFormattingJSONOptions): Video { | 1717 | toFormattedJSON (this: MVideoFormattable, options?: VideoFormattingJSONOptions): Video { |
@@ -1705,17 +1722,29 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> { | |||
1705 | return videoModelToFormattedDetailsJSON(this) | 1722 | return videoModelToFormattedDetailsJSON(this) |
1706 | } | 1723 | } |
1707 | 1724 | ||
1708 | getFormattedVideoFilesJSON (includeMagnet = true): VideoFile[] { | 1725 | getFormattedWebVideoFilesJSON (includeMagnet = true): VideoFile[] { |
1726 | return videoFilesModelToFormattedJSON(this, this.VideoFiles, { includeMagnet }) | ||
1727 | } | ||
1728 | |||
1729 | getFormattedHLSVideoFilesJSON (includeMagnet = true): VideoFile[] { | ||
1730 | let acc: VideoFile[] = [] | ||
1731 | |||
1732 | for (const p of this.VideoStreamingPlaylists) { | ||
1733 | acc = acc.concat(videoFilesModelToFormattedJSON(this, p.VideoFiles, { includeMagnet })) | ||
1734 | } | ||
1735 | |||
1736 | return acc | ||
1737 | } | ||
1738 | |||
1739 | getFormattedAllVideoFilesJSON (includeMagnet = true): VideoFile[] { | ||
1709 | let files: VideoFile[] = [] | 1740 | let files: VideoFile[] = [] |
1710 | 1741 | ||
1711 | if (Array.isArray(this.VideoFiles)) { | 1742 | if (Array.isArray(this.VideoFiles)) { |
1712 | const result = videoFilesModelToFormattedJSON(this, this.VideoFiles, { includeMagnet }) | 1743 | files = files.concat(this.getFormattedWebVideoFilesJSON(includeMagnet)) |
1713 | files = files.concat(result) | ||
1714 | } | 1744 | } |
1715 | 1745 | ||
1716 | for (const p of (this.VideoStreamingPlaylists || [])) { | 1746 | if (Array.isArray(this.VideoStreamingPlaylists)) { |
1717 | const result = videoFilesModelToFormattedJSON(this, p.VideoFiles, { includeMagnet }) | 1747 | files = files.concat(this.getFormattedHLSVideoFilesJSON(includeMagnet)) |
1718 | files = files.concat(result) | ||
1719 | } | 1748 | } |
1720 | 1749 | ||
1721 | return files | 1750 | return files |