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