]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/lib/activitypub/videos/shared/abstract-builder.ts
Refactor AP playlists
[github/Chocobozzz/PeerTube.git] / server / lib / activitypub / videos / shared / abstract-builder.ts
CommitLineData
08a47c75 1import { Transaction } from 'sequelize/types'
c56faf0d 2import { checkUrlsSameHost } from '@server/helpers/activitypub'
08a47c75 3import { deleteNonExistingModels } from '@server/helpers/database-utils'
46320694 4import { logger, LoggerTagsFn } from '@server/helpers/logger'
08a47c75
C
5import { createPlaceholderThumbnail, createVideoMiniatureFromUrl } from '@server/lib/thumbnail'
6import { setVideoTags } from '@server/lib/video'
7import { VideoCaptionModel } from '@server/models/video/video-caption'
8import { VideoFileModel } from '@server/models/video/video-file'
9import { VideoLiveModel } from '@server/models/video/video-live'
10import { VideoStreamingPlaylistModel } from '@server/models/video/video-streaming-playlist'
11import { MStreamingPlaylistFilesVideo, MThumbnail, MVideoCaption, MVideoFile, MVideoFullLight, MVideoThumbnail } from '@server/types/models'
12import { ActivityTagObject, ThumbnailType, VideoObject, VideoStreamingPlaylistType } from '@shared/models'
c56faf0d 13import { getOrCreateActorAndServerAndModel } from '../../actor'
08a47c75
C
14import {
15 getCaptionAttributesFromObject,
16 getFileAttributesFromUrl,
17 getLiveAttributesFromObject,
18 getPreviewFromIcons,
19 getStreamingPlaylistAttributesFromObject,
20 getTagsFromObject,
21 getThumbnailFromIcons
22} from './object-to-model-attributes'
23import { getTrackerUrls, setVideoTrackers } from './trackers'
24
25export abstract class APVideoAbstractBuilder {
26 protected abstract videoObject: VideoObject
46320694 27 protected abstract lTags: LoggerTagsFn
08a47c75 28
c56faf0d
C
29 protected async getOrCreateVideoChannelFromVideoObject () {
30 const channel = this.videoObject.attributedTo.find(a => a.type === 'Group')
31 if (!channel) throw new Error('Cannot find associated video channel to video ' + this.videoObject.url)
32
33 if (checkUrlsSameHost(channel.id, this.videoObject.id) !== true) {
34 throw new Error(`Video channel url ${channel.id} does not have the same host than video object id ${this.videoObject.id}`)
35 }
36
37 return getOrCreateActorAndServerAndModel(channel.id, 'all')
38 }
39
08a47c75
C
40 protected tryToGenerateThumbnail (video: MVideoThumbnail): Promise<MThumbnail> {
41 return createVideoMiniatureFromUrl({
42 downloadUrl: getThumbnailFromIcons(this.videoObject).url,
43 video,
44 type: ThumbnailType.MINIATURE
45 }).catch(err => {
46320694 46 logger.warn('Cannot generate thumbnail of %s.', this.videoObject.id, { err, ...this.lTags(video.uuid) })
08a47c75
C
47
48 return undefined
49 })
50 }
51
52 protected async setPreview (video: MVideoFullLight, t: Transaction) {
53 // Don't fetch the preview that could be big, create a placeholder instead
54 const previewIcon = getPreviewFromIcons(this.videoObject)
55 if (!previewIcon) return
56
57 const previewModel = createPlaceholderThumbnail({
58 fileUrl: previewIcon.url,
59 video,
60 type: ThumbnailType.PREVIEW,
61 size: previewIcon
62 })
63
64 await video.addAndSaveThumbnail(previewModel, t)
65 }
66
67 protected async setTags (video: MVideoFullLight, t: Transaction) {
68 const tags = getTagsFromObject(this.videoObject)
69 await setVideoTags({ video, tags, transaction: t })
70 }
71
72 protected async setTrackers (video: MVideoFullLight, t: Transaction) {
73 const trackers = getTrackerUrls(this.videoObject, video)
74 await setVideoTrackers({ video, trackers, transaction: t })
75 }
76
77 protected async insertOrReplaceCaptions (video: MVideoFullLight, t: Transaction) {
78 const videoCaptionsPromises = getCaptionAttributesFromObject(video, this.videoObject)
79 .map(a => new VideoCaptionModel(a) as MVideoCaption)
80 .map(c => VideoCaptionModel.insertOrReplaceLanguage(c, t))
81
82 await Promise.all(videoCaptionsPromises)
83 }
84
85 protected async insertOrReplaceLive (video: MVideoFullLight, transaction: Transaction) {
86 const attributes = getLiveAttributesFromObject(video, this.videoObject)
87 const [ videoLive ] = await VideoLiveModel.upsert(attributes, { transaction, returning: true })
88
89 video.VideoLive = videoLive
90 }
91
92 protected async setWebTorrentFiles (video: MVideoFullLight, t: Transaction) {
93 const videoFileAttributes = getFileAttributesFromUrl(video, this.videoObject.url)
94 const newVideoFiles = videoFileAttributes.map(a => new VideoFileModel(a))
95
96 // Remove video files that do not exist anymore
97 const destroyTasks = deleteNonExistingModels(video.VideoFiles || [], newVideoFiles, t)
98 await Promise.all(destroyTasks)
99
100 // Update or add other one
101 const upsertTasks = newVideoFiles.map(f => VideoFileModel.customUpsert(f, 'video', t))
102 video.VideoFiles = await Promise.all(upsertTasks)
103 }
104
105 protected async setStreamingPlaylists (video: MVideoFullLight, t: Transaction) {
106 const streamingPlaylistAttributes = getStreamingPlaylistAttributesFromObject(video, this.videoObject, video.VideoFiles || [])
107 const newStreamingPlaylists = streamingPlaylistAttributes.map(a => new VideoStreamingPlaylistModel(a))
108
109 // Remove video playlists that do not exist anymore
110 const destroyTasks = deleteNonExistingModels(video.VideoStreamingPlaylists || [], newStreamingPlaylists, t)
111 await Promise.all(destroyTasks)
112
113 video.VideoStreamingPlaylists = []
114
115 for (const playlistAttributes of streamingPlaylistAttributes) {
116
117 const streamingPlaylistModel = await this.insertOrReplaceStreamingPlaylist(playlistAttributes, t)
118 streamingPlaylistModel.Video = video
119
120 await this.setStreamingPlaylistFiles(video, streamingPlaylistModel, playlistAttributes.tagAPObject, t)
121
122 video.VideoStreamingPlaylists.push(streamingPlaylistModel)
123 }
124 }
125
126 private async insertOrReplaceStreamingPlaylist (attributes: VideoStreamingPlaylistModel['_creationAttributes'], t: Transaction) {
127 const [ streamingPlaylist ] = await VideoStreamingPlaylistModel.upsert(attributes, { returning: true, transaction: t })
128
129 return streamingPlaylist as MStreamingPlaylistFilesVideo
130 }
131
132 private getStreamingPlaylistFiles (video: MVideoFullLight, type: VideoStreamingPlaylistType) {
133 const playlist = video.VideoStreamingPlaylists.find(s => s.type === type)
134 if (!playlist) return []
135
136 return playlist.VideoFiles
137 }
138
139 private async setStreamingPlaylistFiles (
140 video: MVideoFullLight,
141 playlistModel: MStreamingPlaylistFilesVideo,
142 tagObjects: ActivityTagObject[],
143 t: Transaction
144 ) {
145 const oldStreamingPlaylistFiles = this.getStreamingPlaylistFiles(video, playlistModel.type)
146
147 const newVideoFiles: MVideoFile[] = getFileAttributesFromUrl(playlistModel, tagObjects).map(a => new VideoFileModel(a))
148
149 const destroyTasks = deleteNonExistingModels(oldStreamingPlaylistFiles, newVideoFiles, t)
150 await Promise.all(destroyTasks)
151
152 // Update or add other one
153 const upsertTasks = newVideoFiles.map(f => VideoFileModel.customUpsert(f, 'streaming-playlist', t))
154 playlistModel.VideoFiles = await Promise.all(upsertTasks)
155 }
156}