2 import { AccountModel } from '@server/models/account/account'
3 import { AccountBlocklistModel } from '@server/models/account/account-blocklist'
4 import { ActorModel } from '@server/models/actor/actor'
5 import { ActorImageModel } from '@server/models/actor/actor-image'
6 import { VideoRedundancyModel } from '@server/models/redundancy/video-redundancy'
7 import { ServerModel } from '@server/models/server/server'
8 import { ServerBlocklistModel } from '@server/models/server/server-blocklist'
9 import { TrackerModel } from '@server/models/server/tracker'
10 import { UserVideoHistoryModel } from '@server/models/user/user-video-history'
11 import { VideoInclude } from '@shared/models'
12 import { ScheduleVideoUpdateModel } from '../../../schedule-video-update'
13 import { TagModel } from '../../../tag'
14 import { ThumbnailModel } from '../../../thumbnail'
15 import { VideoModel } from '../../../video'
16 import { VideoBlacklistModel } from '../../../video-blacklist'
17 import { VideoChannelModel } from '../../../video-channel'
18 import { VideoFileModel } from '../../../video-file'
19 import { VideoLiveModel } from '../../../video-live'
20 import { VideoStreamingPlaylistModel } from '../../../video-streaming-playlist'
21 import { VideoTableAttributes } from './video-table-attributes'
23 type SQLRow = { [id: string]: string | number }
27 * Build video models from SQL rows
31 export class VideoModelBuilder {
32 private videosMemo: { [ id: number ]: VideoModel }
33 private videoStreamingPlaylistMemo: { [ id: number ]: VideoStreamingPlaylistModel }
34 private videoFileMemo: { [ id: number ]: VideoFileModel }
36 private thumbnailsDone: Set<any>
37 private actorImagesDone: Set<any>
38 private historyDone: Set<any>
39 private blacklistDone: Set<any>
40 private accountBlocklistDone: Set<any>
41 private serverBlocklistDone: Set<any>
42 private liveDone: Set<any>
43 private redundancyDone: Set<any>
44 private scheduleVideoUpdateDone: Set<any>
46 private trackersDone: Set<string>
47 private tagsDone: Set<string>
49 private videos: VideoModel[]
51 private readonly buildOpts = { raw: true, isNewRecord: false }
54 readonly mode: 'get' | 'list',
55 readonly tables: VideoTableAttributes
60 buildVideosFromRows (options: {
62 include?: VideoInclude
63 rowsWebTorrentFiles?: SQLRow[]
64 rowsStreamingPlaylist?: SQLRow[]
66 const { rows, rowsWebTorrentFiles, rowsStreamingPlaylist, include } = options
70 for (const row of rows) {
71 this.buildVideoAndAccount(row)
73 const videoModel = this.videosMemo[row.id as number]
75 this.setUserHistory(row, videoModel)
76 this.addThumbnail(row, videoModel)
78 const channelActor = videoModel.VideoChannel?.Actor
80 this.addActorAvatar(row, 'VideoChannel.Actor', channelActor)
83 const accountActor = videoModel.VideoChannel?.Account?.Actor
85 this.addActorAvatar(row, 'VideoChannel.Account.Actor', accountActor)
88 if (!rowsWebTorrentFiles) {
89 this.addWebTorrentFile(row, videoModel)
92 if (!rowsStreamingPlaylist) {
93 this.addStreamingPlaylist(row, videoModel)
94 this.addStreamingPlaylistFile(row)
97 if (this.mode === 'get') {
98 this.addTag(row, videoModel)
99 this.addTracker(row, videoModel)
100 this.setBlacklisted(row, videoModel)
101 this.setScheduleVideoUpdate(row, videoModel)
102 this.setLive(row, videoModel)
104 if (include & VideoInclude.BLACKLISTED) {
105 this.setBlacklisted(row, videoModel)
108 if (include & VideoInclude.BLOCKED_OWNER) {
109 this.setBlockedOwner(row, videoModel)
110 this.setBlockedServer(row, videoModel)
115 this.grabSeparateWebTorrentFiles(rowsWebTorrentFiles)
116 this.grabSeparateStreamingPlaylistFiles(rowsStreamingPlaylist)
123 this.videoStreamingPlaylistMemo = {}
124 this.videoFileMemo = {}
126 this.thumbnailsDone = new Set()
127 this.actorImagesDone = new Set()
128 this.historyDone = new Set()
129 this.blacklistDone = new Set()
130 this.liveDone = new Set()
131 this.redundancyDone = new Set()
132 this.scheduleVideoUpdateDone = new Set()
134 this.accountBlocklistDone = new Set()
135 this.serverBlocklistDone = new Set()
137 this.trackersDone = new Set()
138 this.tagsDone = new Set()
143 private grabSeparateWebTorrentFiles (rowsWebTorrentFiles?: SQLRow[]) {
144 if (!rowsWebTorrentFiles) return
146 for (const row of rowsWebTorrentFiles) {
147 const id = row['VideoFiles.id']
150 const videoModel = this.videosMemo[row.id]
151 this.addWebTorrentFile(row, videoModel)
152 this.addRedundancy(row, 'VideoFiles', this.videoFileMemo[id])
156 private grabSeparateStreamingPlaylistFiles (rowsStreamingPlaylist?: SQLRow[]) {
157 if (!rowsStreamingPlaylist) return
159 for (const row of rowsStreamingPlaylist) {
160 const id = row['VideoStreamingPlaylists.id']
163 const videoModel = this.videosMemo[row.id]
165 this.addStreamingPlaylist(row, videoModel)
166 this.addStreamingPlaylistFile(row)
167 this.addRedundancy(row, 'VideoStreamingPlaylists', this.videoStreamingPlaylistMemo[id])
171 private buildVideoAndAccount (row: SQLRow) {
172 if (this.videosMemo[row.id]) return
174 const videoModel = new VideoModel(this.grab(row, this.tables.getVideoAttributes(), ''), this.buildOpts)
176 videoModel.UserVideoHistories = []
177 videoModel.Thumbnails = []
178 videoModel.VideoFiles = []
179 videoModel.VideoStreamingPlaylists = []
181 videoModel.Trackers = []
183 this.buildAccount(row, videoModel)
185 this.videosMemo[row.id] = videoModel
188 this.videos.push(videoModel)
191 private buildAccount (row: SQLRow, videoModel: VideoModel) {
192 const id = row['VideoChannel.Account.id']
195 const channelModel = new VideoChannelModel(this.grab(row, this.tables.getChannelAttributes(), 'VideoChannel'), this.buildOpts)
196 channelModel.Actor = this.buildActor(row, 'VideoChannel')
198 const accountModel = new AccountModel(this.grab(row, this.tables.getAccountAttributes(), 'VideoChannel.Account'), this.buildOpts)
199 accountModel.Actor = this.buildActor(row, 'VideoChannel.Account')
201 accountModel.BlockedBy = []
203 channelModel.Account = accountModel
205 videoModel.VideoChannel = channelModel
208 private buildActor (row: SQLRow, prefix: string) {
209 const actorPrefix = `${prefix}.Actor`
210 const serverPrefix = `${actorPrefix}.Server`
212 const serverModel = row[`${serverPrefix}.id`] !== null
213 ? new ServerModel(this.grab(row, this.tables.getServerAttributes(), serverPrefix), this.buildOpts)
216 if (serverModel) serverModel.BlockedBy = []
218 const actorModel = new ActorModel(this.grab(row, this.tables.getActorAttributes(), actorPrefix), this.buildOpts)
219 actorModel.Server = serverModel
220 actorModel.Avatars = []
225 private setUserHistory (row: SQLRow, videoModel: VideoModel) {
226 const id = row['userVideoHistory.id']
227 if (!id || this.historyDone.has(id)) return
229 const attributes = this.grab(row, this.tables.getUserHistoryAttributes(), 'userVideoHistory')
230 const historyModel = new UserVideoHistoryModel(attributes, this.buildOpts)
231 videoModel.UserVideoHistories.push(historyModel)
233 this.historyDone.add(id)
236 private addActorAvatar (row: SQLRow, actorPrefix: string, actor: ActorModel) {
237 const avatarPrefix = `${actorPrefix}.Avatar`
238 const id = row[`${avatarPrefix}.id`]
239 if (!id || this.actorImagesDone.has(id)) return
241 const attributes = this.grab(row, this.tables.getAvatarAttributes(), avatarPrefix)
242 const avatarModel = new ActorImageModel(attributes, this.buildOpts)
243 actor.Avatars.push(avatarModel)
245 this.actorImagesDone.add(id)
248 private addThumbnail (row: SQLRow, videoModel: VideoModel) {
249 const id = row['Thumbnails.id']
250 if (!id || this.thumbnailsDone.has(id)) return
252 const attributes = this.grab(row, this.tables.getThumbnailAttributes(), 'Thumbnails')
253 const thumbnailModel = new ThumbnailModel(attributes, this.buildOpts)
254 videoModel.Thumbnails.push(thumbnailModel)
256 this.thumbnailsDone.add(id)
259 private addWebTorrentFile (row: SQLRow, videoModel: VideoModel) {
260 const id = row['VideoFiles.id']
261 if (!id || this.videoFileMemo[id]) return
263 const attributes = this.grab(row, this.tables.getFileAttributes(), 'VideoFiles')
264 const videoFileModel = new VideoFileModel(attributes, this.buildOpts)
265 videoModel.VideoFiles.push(videoFileModel)
267 this.videoFileMemo[id] = videoFileModel
270 private addStreamingPlaylist (row: SQLRow, videoModel: VideoModel) {
271 const id = row['VideoStreamingPlaylists.id']
272 if (!id || this.videoStreamingPlaylistMemo[id]) return
274 const attributes = this.grab(row, this.tables.getStreamingPlaylistAttributes(), 'VideoStreamingPlaylists')
275 const streamingPlaylist = new VideoStreamingPlaylistModel(attributes, this.buildOpts)
276 streamingPlaylist.VideoFiles = []
278 videoModel.VideoStreamingPlaylists.push(streamingPlaylist)
280 this.videoStreamingPlaylistMemo[id] = streamingPlaylist
283 private addStreamingPlaylistFile (row: SQLRow) {
284 const id = row['VideoStreamingPlaylists.VideoFiles.id']
285 if (!id || this.videoFileMemo[id]) return
287 const streamingPlaylist = this.videoStreamingPlaylistMemo[row['VideoStreamingPlaylists.id']]
289 const attributes = this.grab(row, this.tables.getFileAttributes(), 'VideoStreamingPlaylists.VideoFiles')
290 const videoFileModel = new VideoFileModel(attributes, this.buildOpts)
291 streamingPlaylist.VideoFiles.push(videoFileModel)
293 this.videoFileMemo[id] = videoFileModel
296 private addRedundancy (row: SQLRow, prefix: string, to: VideoFileModel | VideoStreamingPlaylistModel) {
297 if (!to.RedundancyVideos) to.RedundancyVideos = []
299 const redundancyPrefix = `${prefix}.RedundancyVideos`
300 const id = row[`${redundancyPrefix}.id`]
302 if (!id || this.redundancyDone.has(id)) return
304 const attributes = this.grab(row, this.tables.getRedundancyAttributes(), redundancyPrefix)
305 const redundancyModel = new VideoRedundancyModel(attributes, this.buildOpts)
306 to.RedundancyVideos.push(redundancyModel)
308 this.redundancyDone.add(id)
311 private addTag (row: SQLRow, videoModel: VideoModel) {
312 if (!row['Tags.name']) return
314 const key = `${row['Tags.VideoTagModel.videoId']}-${row['Tags.VideoTagModel.tagId']}`
315 if (this.tagsDone.has(key)) return
317 const attributes = this.grab(row, this.tables.getTagAttributes(), 'Tags')
318 const tagModel = new TagModel(attributes, this.buildOpts)
319 videoModel.Tags.push(tagModel)
321 this.tagsDone.add(key)
324 private addTracker (row: SQLRow, videoModel: VideoModel) {
325 if (!row['Trackers.id']) return
327 const key = `${row['Trackers.VideoTrackerModel.videoId']}-${row['Trackers.VideoTrackerModel.trackerId']}`
328 if (this.trackersDone.has(key)) return
330 const attributes = this.grab(row, this.tables.getTrackerAttributes(), 'Trackers')
331 const trackerModel = new TrackerModel(attributes, this.buildOpts)
332 videoModel.Trackers.push(trackerModel)
334 this.trackersDone.add(key)
337 private setBlacklisted (row: SQLRow, videoModel: VideoModel) {
338 const id = row['VideoBlacklist.id']
339 if (!id || this.blacklistDone.has(id)) return
341 const attributes = this.grab(row, this.tables.getBlacklistedAttributes(), 'VideoBlacklist')
342 videoModel.VideoBlacklist = new VideoBlacklistModel(attributes, this.buildOpts)
344 this.blacklistDone.add(id)
347 private setBlockedOwner (row: SQLRow, videoModel: VideoModel) {
348 const id = row['VideoChannel.Account.AccountBlocklist.id']
351 const key = `${videoModel.id}-${id}`
352 if (this.accountBlocklistDone.has(key)) return
354 const attributes = this.grab(row, this.tables.getBlocklistAttributes(), 'VideoChannel.Account.AccountBlocklist')
355 videoModel.VideoChannel.Account.BlockedBy.push(new AccountBlocklistModel(attributes, this.buildOpts))
357 this.accountBlocklistDone.add(key)
360 private setBlockedServer (row: SQLRow, videoModel: VideoModel) {
361 const id = row['VideoChannel.Account.Actor.Server.ServerBlocklist.id']
362 if (!id || this.serverBlocklistDone.has(id)) return
364 const key = `${videoModel.id}-${id}`
365 if (this.serverBlocklistDone.has(key)) return
367 const attributes = this.grab(row, this.tables.getBlocklistAttributes(), 'VideoChannel.Account.Actor.Server.ServerBlocklist')
368 videoModel.VideoChannel.Account.Actor.Server.BlockedBy.push(new ServerBlocklistModel(attributes, this.buildOpts))
370 this.serverBlocklistDone.add(key)
373 private setScheduleVideoUpdate (row: SQLRow, videoModel: VideoModel) {
374 const id = row['ScheduleVideoUpdate.id']
375 if (!id || this.scheduleVideoUpdateDone.has(id)) return
377 const attributes = this.grab(row, this.tables.getScheduleUpdateAttributes(), 'ScheduleVideoUpdate')
378 videoModel.ScheduleVideoUpdate = new ScheduleVideoUpdateModel(attributes, this.buildOpts)
380 this.scheduleVideoUpdateDone.add(id)
383 private setLive (row: SQLRow, videoModel: VideoModel) {
384 const id = row['VideoLive.id']
385 if (!id || this.liveDone.has(id)) return
387 const attributes = this.grab(row, this.tables.getLiveAttributes(), 'VideoLive')
388 videoModel.VideoLive = new VideoLiveModel(attributes, this.buildOpts)
390 this.liveDone.add(id)
393 private grab (row: SQLRow, attributes: string[], prefix: string) {
394 const result: { [ id: string ]: string | number } = {}
396 for (const a of attributes) {