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 | |
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')
-rw-r--r-- | server/models/account/account.ts | 12 | ||||
-rw-r--r-- | server/models/actor/actor.ts | 8 | ||||
-rw-r--r-- | server/models/user/user.ts | 6 | ||||
-rw-r--r-- | server/models/video/formatter/video-format-utils.ts | 2 | ||||
-rw-r--r-- | server/models/video/thumbnail.ts | 6 | ||||
-rw-r--r-- | server/models/video/video-caption.ts | 27 | ||||
-rw-r--r-- | server/models/video/video-channel.ts | 27 | ||||
-rw-r--r-- | server/models/video/video.ts | 53 |
8 files changed, 112 insertions, 29 deletions
diff --git a/server/models/account/account.ts b/server/models/account/account.ts index ec4e8d946..396959352 100644 --- a/server/models/account/account.ts +++ b/server/models/account/account.ts | |||
@@ -28,8 +28,9 @@ import { | |||
28 | MAccountAP, | 28 | MAccountAP, |
29 | MAccountDefault, | 29 | MAccountDefault, |
30 | MAccountFormattable, | 30 | MAccountFormattable, |
31 | MAccountHost, | ||
31 | MAccountSummaryFormattable, | 32 | MAccountSummaryFormattable, |
32 | MChannelActor | 33 | MChannelHost |
33 | } from '../../types/models' | 34 | } from '../../types/models' |
34 | import { ActorModel } from '../actor/actor' | 35 | import { ActorModel } from '../actor/actor' |
35 | import { ActorFollowModel } from '../actor/actor-follow' | 36 | import { ActorFollowModel } from '../actor/actor-follow' |
@@ -410,10 +411,6 @@ export class AccountModel extends Model<Partial<AttributesOnly<AccountModel>>> { | |||
410 | .findAll(query) | 411 | .findAll(query) |
411 | } | 412 | } |
412 | 413 | ||
413 | getClientUrl () { | ||
414 | return WEBSERVER.URL + '/accounts/' + this.Actor.getIdentifier() | ||
415 | } | ||
416 | |||
417 | toFormattedJSON (this: MAccountFormattable): Account { | 414 | toFormattedJSON (this: MAccountFormattable): Account { |
418 | return { | 415 | return { |
419 | ...this.Actor.toFormattedJSON(), | 416 | ...this.Actor.toFormattedJSON(), |
@@ -463,8 +460,9 @@ export class AccountModel extends Model<Partial<AttributesOnly<AccountModel>>> { | |||
463 | return this.name | 460 | return this.name |
464 | } | 461 | } |
465 | 462 | ||
466 | getLocalUrl (this: MAccountActor | MChannelActor) { | 463 | // Avoid error when running this method on MAccount... | MChannel... |
467 | return WEBSERVER.URL + `/accounts/` + this.Actor.preferredUsername | 464 | getClientUrl (this: MAccountHost | MChannelHost) { |
465 | return WEBSERVER.URL + '/a/' + this.Actor.getIdentifier() | ||
468 | } | 466 | } |
469 | 467 | ||
470 | isBlocked () { | 468 | isBlocked () { |
diff --git a/server/models/actor/actor.ts b/server/models/actor/actor.ts index 80a646c77..dccb47a10 100644 --- a/server/models/actor/actor.ts +++ b/server/models/actor/actor.ts | |||
@@ -46,8 +46,8 @@ import { | |||
46 | MActorFormattable, | 46 | MActorFormattable, |
47 | MActorFull, | 47 | MActorFull, |
48 | MActorHost, | 48 | MActorHost, |
49 | MActorHostOnly, | ||
49 | MActorId, | 50 | MActorId, |
50 | MActorServer, | ||
51 | MActorSummaryFormattable, | 51 | MActorSummaryFormattable, |
52 | MActorUrl, | 52 | MActorUrl, |
53 | MActorWithInboxes | 53 | MActorWithInboxes |
@@ -663,15 +663,15 @@ export class ActorModel extends Model<Partial<AttributesOnly<ActorModel>>> { | |||
663 | return this.serverId === null | 663 | return this.serverId === null |
664 | } | 664 | } |
665 | 665 | ||
666 | getWebfingerUrl (this: MActorServer) { | 666 | getWebfingerUrl (this: MActorHost) { |
667 | return 'acct:' + this.preferredUsername + '@' + this.getHost() | 667 | return 'acct:' + this.preferredUsername + '@' + this.getHost() |
668 | } | 668 | } |
669 | 669 | ||
670 | getIdentifier () { | 670 | getIdentifier (this: MActorHost) { |
671 | return this.Server ? `${this.preferredUsername}@${this.Server.host}` : this.preferredUsername | 671 | return this.Server ? `${this.preferredUsername}@${this.Server.host}` : this.preferredUsername |
672 | } | 672 | } |
673 | 673 | ||
674 | getHost (this: MActorHost) { | 674 | getHost (this: MActorHostOnly) { |
675 | return this.Server ? this.Server.host : WEBSERVER.HOST | 675 | return this.Server ? this.Server.host : WEBSERVER.HOST |
676 | } | 676 | } |
677 | 677 | ||
diff --git a/server/models/user/user.ts b/server/models/user/user.ts index 735b5c171..4f6a8fce4 100644 --- a/server/models/user/user.ts +++ b/server/models/user/user.ts | |||
@@ -404,6 +404,11 @@ export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> { | |||
404 | @Column | 404 | @Column |
405 | lastLoginDate: Date | 405 | lastLoginDate: Date |
406 | 406 | ||
407 | @AllowNull(false) | ||
408 | @Default(false) | ||
409 | @Column | ||
410 | emailPublic: boolean | ||
411 | |||
407 | @AllowNull(true) | 412 | @AllowNull(true) |
408 | @Default(null) | 413 | @Default(null) |
409 | @Column | 414 | @Column |
@@ -880,6 +885,7 @@ export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> { | |||
880 | theme: getThemeOrDefault(this.theme, DEFAULT_USER_THEME_NAME), | 885 | theme: getThemeOrDefault(this.theme, DEFAULT_USER_THEME_NAME), |
881 | 886 | ||
882 | pendingEmail: this.pendingEmail, | 887 | pendingEmail: this.pendingEmail, |
888 | emailPublic: this.emailPublic, | ||
883 | emailVerified: this.emailVerified, | 889 | emailVerified: this.emailVerified, |
884 | 890 | ||
885 | nsfwPolicy: this.nsfwPolicy, | 891 | nsfwPolicy: this.nsfwPolicy, |
diff --git a/server/models/video/formatter/video-format-utils.ts b/server/models/video/formatter/video-format-utils.ts index 6f05dbdc8..f2001e432 100644 --- a/server/models/video/formatter/video-format-utils.ts +++ b/server/models/video/formatter/video-format-utils.ts | |||
@@ -459,7 +459,7 @@ function videoModelToActivityPubObject (video: MVideoAP): VideoObject { | |||
459 | 459 | ||
460 | icon: icons.map(i => ({ | 460 | icon: icons.map(i => ({ |
461 | type: 'Image', | 461 | type: 'Image', |
462 | url: i.getFileUrl(video), | 462 | url: i.getOriginFileUrl(video), |
463 | mediaType: 'image/jpeg', | 463 | mediaType: 'image/jpeg', |
464 | width: i.width, | 464 | width: i.width, |
465 | height: i.height | 465 | height: i.height |
diff --git a/server/models/video/thumbnail.ts b/server/models/video/thumbnail.ts index f33bd3179..a4ac581e5 100644 --- a/server/models/video/thumbnail.ts +++ b/server/models/video/thumbnail.ts | |||
@@ -164,7 +164,7 @@ export class ThumbnailModel extends Model<Partial<AttributesOnly<ThumbnailModel> | |||
164 | return join(directory, filename) | 164 | return join(directory, filename) |
165 | } | 165 | } |
166 | 166 | ||
167 | getFileUrl (video: MVideo) { | 167 | getOriginFileUrl (video: MVideo) { |
168 | const staticPath = ThumbnailModel.types[this.type].staticPath + this.filename | 168 | const staticPath = ThumbnailModel.types[this.type].staticPath + this.filename |
169 | 169 | ||
170 | if (video.isOwned()) return WEBSERVER.URL + staticPath | 170 | if (video.isOwned()) return WEBSERVER.URL + staticPath |
@@ -172,6 +172,10 @@ export class ThumbnailModel extends Model<Partial<AttributesOnly<ThumbnailModel> | |||
172 | return this.fileUrl | 172 | return this.fileUrl |
173 | } | 173 | } |
174 | 174 | ||
175 | getLocalStaticPath () { | ||
176 | return ThumbnailModel.types[this.type].staticPath + this.filename | ||
177 | } | ||
178 | |||
175 | getPath () { | 179 | getPath () { |
176 | return ThumbnailModel.buildPath(this.type, this.filename) | 180 | return ThumbnailModel.buildPath(this.type, this.filename) |
177 | } | 181 | } |
diff --git a/server/models/video/video-caption.ts b/server/models/video/video-caption.ts index 2eaa77407..1fb1cae82 100644 --- a/server/models/video/video-caption.ts +++ b/server/models/video/video-caption.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { remove } from 'fs-extra' | 1 | import { remove } from 'fs-extra' |
2 | import { join } from 'path' | 2 | import { join } from 'path' |
3 | import { OrderItem, Transaction } from 'sequelize' | 3 | import { Op, OrderItem, Transaction } from 'sequelize' |
4 | import { | 4 | import { |
5 | AllowNull, | 5 | AllowNull, |
6 | BeforeDestroy, | 6 | BeforeDestroy, |
@@ -166,6 +166,31 @@ export class VideoCaptionModel extends Model<Partial<AttributesOnly<VideoCaption | |||
166 | return VideoCaptionModel.scope(ScopeNames.WITH_VIDEO_UUID_AND_REMOTE).findAll(query) | 166 | return VideoCaptionModel.scope(ScopeNames.WITH_VIDEO_UUID_AND_REMOTE).findAll(query) |
167 | } | 167 | } |
168 | 168 | ||
169 | static async listCaptionsOfMultipleVideos (videoIds: number[], transaction?: Transaction) { | ||
170 | const query = { | ||
171 | order: [ [ 'language', 'ASC' ] ] as OrderItem[], | ||
172 | where: { | ||
173 | videoId: { | ||
174 | [Op.in]: videoIds | ||
175 | } | ||
176 | }, | ||
177 | transaction | ||
178 | } | ||
179 | |||
180 | const captions = await VideoCaptionModel.scope(ScopeNames.WITH_VIDEO_UUID_AND_REMOTE).findAll<MVideoCaptionVideo>(query) | ||
181 | const result: { [ id: number ]: MVideoCaptionVideo[] } = {} | ||
182 | |||
183 | for (const id of videoIds) { | ||
184 | result[id] = [] | ||
185 | } | ||
186 | |||
187 | for (const caption of captions) { | ||
188 | result[caption.videoId].push(caption) | ||
189 | } | ||
190 | |||
191 | return result | ||
192 | } | ||
193 | |||
169 | static getLanguageLabel (language: string) { | 194 | static getLanguageLabel (language: string) { |
170 | return VIDEO_LANGUAGES[language] || 'Unknown' | 195 | return VIDEO_LANGUAGES[language] || 'Unknown' |
171 | } | 196 | } |
diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts index 0fb52827e..19dd681a7 100644 --- a/server/models/video/video-channel.ts +++ b/server/models/video/video-channel.ts | |||
@@ -1,5 +1,8 @@ | |||
1 | import { FindOptions, Includeable, literal, Op, QueryTypes, ScopeOptions, Transaction, WhereOptions } from 'sequelize' | 1 | import { FindOptions, Includeable, literal, Op, QueryTypes, ScopeOptions, Transaction, WhereOptions } from 'sequelize' |
2 | import { | 2 | import { |
3 | AfterCreate, | ||
4 | AfterDestroy, | ||
5 | AfterUpdate, | ||
3 | AllowNull, | 6 | AllowNull, |
4 | BeforeDestroy, | 7 | BeforeDestroy, |
5 | BelongsTo, | 8 | BelongsTo, |
@@ -18,7 +21,8 @@ import { | |||
18 | UpdatedAt | 21 | UpdatedAt |
19 | } from 'sequelize-typescript' | 22 | } from 'sequelize-typescript' |
20 | import { CONFIG } from '@server/initializers/config' | 23 | import { CONFIG } from '@server/initializers/config' |
21 | import { MAccountActor } from '@server/types/models' | 24 | import { InternalEventEmitter } from '@server/lib/internal-event-emitter' |
25 | import { MAccountHost } from '@server/types/models' | ||
22 | import { forceNumber, pick } from '@shared/core-utils' | 26 | import { forceNumber, pick } from '@shared/core-utils' |
23 | import { AttributesOnly } from '@shared/typescript-utils' | 27 | import { AttributesOnly } from '@shared/typescript-utils' |
24 | import { ActivityPubActor } from '../../../shared/models/activitypub' | 28 | import { ActivityPubActor } from '../../../shared/models/activitypub' |
@@ -36,6 +40,7 @@ import { | |||
36 | MChannelAP, | 40 | MChannelAP, |
37 | MChannelBannerAccountDefault, | 41 | MChannelBannerAccountDefault, |
38 | MChannelFormattable, | 42 | MChannelFormattable, |
43 | MChannelHost, | ||
39 | MChannelSummaryFormattable | 44 | MChannelSummaryFormattable |
40 | } from '../../types/models/video' | 45 | } from '../../types/models/video' |
41 | import { AccountModel, ScopeNames as AccountModelScopeNames, SummaryOptions as AccountSummaryOptions } from '../account/account' | 46 | import { AccountModel, ScopeNames as AccountModelScopeNames, SummaryOptions as AccountSummaryOptions } from '../account/account' |
@@ -416,6 +421,21 @@ export class VideoChannelModel extends Model<Partial<AttributesOnly<VideoChannel | |||
416 | }) | 421 | }) |
417 | VideoPlaylists: VideoPlaylistModel[] | 422 | VideoPlaylists: VideoPlaylistModel[] |
418 | 423 | ||
424 | @AfterCreate | ||
425 | static notifyCreate (channel: MChannel) { | ||
426 | InternalEventEmitter.Instance.emit('channel-created', { channel }) | ||
427 | } | ||
428 | |||
429 | @AfterUpdate | ||
430 | static notifyUpdate (channel: MChannel) { | ||
431 | InternalEventEmitter.Instance.emit('channel-updated', { channel }) | ||
432 | } | ||
433 | |||
434 | @AfterDestroy | ||
435 | static notifyDestroy (channel: MChannel) { | ||
436 | InternalEventEmitter.Instance.emit('channel-deleted', { channel }) | ||
437 | } | ||
438 | |||
419 | @BeforeDestroy | 439 | @BeforeDestroy |
420 | static async sendDeleteIfOwned (instance: VideoChannelModel, options) { | 440 | static async sendDeleteIfOwned (instance: VideoChannelModel, options) { |
421 | if (!instance.Actor) { | 441 | if (!instance.Actor) { |
@@ -827,8 +847,9 @@ export class VideoChannelModel extends Model<Partial<AttributesOnly<VideoChannel | |||
827 | }) | 847 | }) |
828 | } | 848 | } |
829 | 849 | ||
830 | getLocalUrl (this: MAccountActor | MChannelActor) { | 850 | // Avoid error when running this method on MAccount... | MChannel... |
831 | return WEBSERVER.URL + `/video-channels/` + this.Actor.preferredUsername | 851 | getClientUrl (this: MAccountHost | MChannelHost) { |
852 | return WEBSERVER.URL + '/c/' + this.Actor.getIdentifier() | ||
832 | } | 853 | } |
833 | 854 | ||
834 | getDisplayName () { | 855 | getDisplayName () { |
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 |