]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/lib/activitypub/videos/shared/abstract-builder.ts
Use random names for VOD HLS 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'
764b1a14 3import { deleteAllModels, filterNonExistingModels } 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
764b1a14 114 await deleteAllModels(filterNonExistingModels(video.VideoFiles || [], newVideoFiles), t)
08a47c75
C
115
116 // Update or add other one
117 const upsertTasks = newVideoFiles.map(f => VideoFileModel.customUpsert(f, 'video', t))
118 video.VideoFiles = await Promise.all(upsertTasks)
119 }
120
121 protected async setStreamingPlaylists (video: MVideoFullLight, t: Transaction) {
122 const streamingPlaylistAttributes = getStreamingPlaylistAttributesFromObject(video, this.videoObject, video.VideoFiles || [])
123 const newStreamingPlaylists = streamingPlaylistAttributes.map(a => new VideoStreamingPlaylistModel(a))
124
125 // Remove video playlists that do not exist anymore
764b1a14 126 await deleteAllModels(filterNonExistingModels(video.VideoStreamingPlaylists || [], newStreamingPlaylists), t)
08a47c75
C
127
128 video.VideoStreamingPlaylists = []
129
130 for (const playlistAttributes of streamingPlaylistAttributes) {
08a47c75
C
131 const streamingPlaylistModel = await this.insertOrReplaceStreamingPlaylist(playlistAttributes, t)
132 streamingPlaylistModel.Video = video
133
134 await this.setStreamingPlaylistFiles(video, streamingPlaylistModel, playlistAttributes.tagAPObject, t)
135
136 video.VideoStreamingPlaylists.push(streamingPlaylistModel)
137 }
138 }
139
140 private async insertOrReplaceStreamingPlaylist (attributes: VideoStreamingPlaylistModel['_creationAttributes'], t: Transaction) {
141 const [ streamingPlaylist ] = await VideoStreamingPlaylistModel.upsert(attributes, { returning: true, transaction: t })
142
143 return streamingPlaylist as MStreamingPlaylistFilesVideo
144 }
145
146 private getStreamingPlaylistFiles (video: MVideoFullLight, type: VideoStreamingPlaylistType) {
147 const playlist = video.VideoStreamingPlaylists.find(s => s.type === type)
148 if (!playlist) return []
149
150 return playlist.VideoFiles
151 }
152
153 private async setStreamingPlaylistFiles (
154 video: MVideoFullLight,
155 playlistModel: MStreamingPlaylistFilesVideo,
156 tagObjects: ActivityTagObject[],
157 t: Transaction
158 ) {
159 const oldStreamingPlaylistFiles = this.getStreamingPlaylistFiles(video, playlistModel.type)
160
161 const newVideoFiles: MVideoFile[] = getFileAttributesFromUrl(playlistModel, tagObjects).map(a => new VideoFileModel(a))
162
764b1a14 163 await deleteAllModels(filterNonExistingModels(oldStreamingPlaylistFiles, newVideoFiles), t)
08a47c75
C
164
165 // Update or add other one
166 const upsertTasks = newVideoFiles.map(f => VideoFileModel.customUpsert(f, 'streaming-playlist', t))
167 playlistModel.VideoFiles = await Promise.all(upsertTasks)
168 }
169}