]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/lib/activitypub/videos/shared/abstract-builder.ts
Generate random uuid for video files
[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'
91f8f8db 5import { updatePlaceholderThumbnail, updateVideoMiniatureFromUrl } from '@server/lib/thumbnail'
08a47c75
C
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'
136d7efd 13import { getOrCreateAPActor } from '../../actors'
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
136d7efd 37 return getOrCreateAPActor(channel.id, 'all')
c56faf0d
C
38 }
39
08a47c75 40 protected tryToGenerateThumbnail (video: MVideoThumbnail): Promise<MThumbnail> {
91f8f8db 41 return updateVideoMiniatureFromUrl({
08a47c75
C
42 downloadUrl: getThumbnailFromIcons(this.videoObject).url,
43 video,
44 type: ThumbnailType.MINIATURE
45 }).catch(err => {
908e6ead 46 logger.warn('Cannot generate thumbnail of %s.', this.videoObject.id, { err, ...this.lTags() })
08a47c75
C
47
48 return undefined
49 })
50 }
51
28dfb44b 52 protected async setPreview (video: MVideoFullLight, t?: Transaction) {
08a47c75
C
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
91f8f8db 57 const previewModel = updatePlaceholderThumbnail({
08a47c75
C
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) {
57a0a9cd 78 const existingCaptions = await VideoCaptionModel.listVideoCaptions(video.id, t)
08a47c75 79
57a0a9cd
C
80 let captionsToCreate = getCaptionAttributesFromObject(video, this.videoObject)
81 .map(a => new VideoCaptionModel(a) as MVideoCaption)
82
83 for (const existingCaption of existingCaptions) {
84 // Only keep captions that do not already exist
85 const filtered = captionsToCreate.filter(c => !c.isEqual(existingCaption))
86
87 // This caption already exists, we don't need to destroy and create it
88 if (filtered.length !== captionsToCreate.length) {
89 captionsToCreate = filtered
90 continue
91 }
92
93 // Destroy this caption that does not exist anymore
94 await existingCaption.destroy({ transaction: t })
95 }
96
97 for (const captionToCreate of captionsToCreate) {
98 await captionToCreate.save({ transaction: t })
99 }
08a47c75
C
100 }
101
102 protected async insertOrReplaceLive (video: MVideoFullLight, transaction: Transaction) {
103 const attributes = getLiveAttributesFromObject(video, this.videoObject)
104 const [ videoLive ] = await VideoLiveModel.upsert(attributes, { transaction, returning: true })
105
106 video.VideoLive = videoLive
107 }
108
109 protected async setWebTorrentFiles (video: MVideoFullLight, t: Transaction) {
110 const videoFileAttributes = getFileAttributesFromUrl(video, this.videoObject.url)
111 const newVideoFiles = videoFileAttributes.map(a => new VideoFileModel(a))
112
113 // Remove video files that do not exist anymore
114 const destroyTasks = deleteNonExistingModels(video.VideoFiles || [], newVideoFiles, t)
115 await Promise.all(destroyTasks)
116
117 // Update or add other one
118 const upsertTasks = newVideoFiles.map(f => VideoFileModel.customUpsert(f, 'video', t))
119 video.VideoFiles = await Promise.all(upsertTasks)
120 }
121
122 protected async setStreamingPlaylists (video: MVideoFullLight, t: Transaction) {
123 const streamingPlaylistAttributes = getStreamingPlaylistAttributesFromObject(video, this.videoObject, video.VideoFiles || [])
124 const newStreamingPlaylists = streamingPlaylistAttributes.map(a => new VideoStreamingPlaylistModel(a))
125
126 // Remove video playlists that do not exist anymore
127 const destroyTasks = deleteNonExistingModels(video.VideoStreamingPlaylists || [], newStreamingPlaylists, t)
128 await Promise.all(destroyTasks)
129
130 video.VideoStreamingPlaylists = []
131
132 for (const playlistAttributes of streamingPlaylistAttributes) {
133
134 const streamingPlaylistModel = await this.insertOrReplaceStreamingPlaylist(playlistAttributes, t)
135 streamingPlaylistModel.Video = video
136
137 await this.setStreamingPlaylistFiles(video, streamingPlaylistModel, playlistAttributes.tagAPObject, t)
138
139 video.VideoStreamingPlaylists.push(streamingPlaylistModel)
140 }
141 }
142
143 private async insertOrReplaceStreamingPlaylist (attributes: VideoStreamingPlaylistModel['_creationAttributes'], t: Transaction) {
144 const [ streamingPlaylist ] = await VideoStreamingPlaylistModel.upsert(attributes, { returning: true, transaction: t })
145
146 return streamingPlaylist as MStreamingPlaylistFilesVideo
147 }
148
149 private getStreamingPlaylistFiles (video: MVideoFullLight, type: VideoStreamingPlaylistType) {
150 const playlist = video.VideoStreamingPlaylists.find(s => s.type === type)
151 if (!playlist) return []
152
153 return playlist.VideoFiles
154 }
155
156 private async setStreamingPlaylistFiles (
157 video: MVideoFullLight,
158 playlistModel: MStreamingPlaylistFilesVideo,
159 tagObjects: ActivityTagObject[],
160 t: Transaction
161 ) {
162 const oldStreamingPlaylistFiles = this.getStreamingPlaylistFiles(video, playlistModel.type)
163
164 const newVideoFiles: MVideoFile[] = getFileAttributesFromUrl(playlistModel, tagObjects).map(a => new VideoFileModel(a))
165
166 const destroyTasks = deleteNonExistingModels(oldStreamingPlaylistFiles, newVideoFiles, t)
167 await Promise.all(destroyTasks)
168
169 // Update or add other one
170 const upsertTasks = newVideoFiles.map(f => VideoFileModel.customUpsert(f, 'streaming-playlist', t))
171 playlistModel.VideoFiles = await Promise.all(upsertTasks)
172 }
173}