aboutsummaryrefslogblamecommitdiffhomepage
path: root/server/lib/activitypub/videos/shared/abstract-builder.ts
blob: f8e4d6aa3aeecbbaffce5448e6f0895344488881 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
                                             
                                                               
                                                                        
                                                             







                                                                                                                                            
                                                 












                                                             
                                        
 







                                                                                                                                
                                                

   





                                                                                  
                                                                                                              













































































































                                                                                                                                   
import { Transaction } from 'sequelize/types'
import { checkUrlsSameHost } from '@server/helpers/activitypub'
import { deleteNonExistingModels } from '@server/helpers/database-utils'
import { logger, LoggerTagsFn } from '@server/helpers/logger'
import { createPlaceholderThumbnail, createVideoMiniatureFromUrl } from '@server/lib/thumbnail'
import { setVideoTags } from '@server/lib/video'
import { VideoCaptionModel } from '@server/models/video/video-caption'
import { VideoFileModel } from '@server/models/video/video-file'
import { VideoLiveModel } from '@server/models/video/video-live'
import { VideoStreamingPlaylistModel } from '@server/models/video/video-streaming-playlist'
import { MStreamingPlaylistFilesVideo, MThumbnail, MVideoCaption, MVideoFile, MVideoFullLight, MVideoThumbnail } from '@server/types/models'
import { ActivityTagObject, ThumbnailType, VideoObject, VideoStreamingPlaylistType } from '@shared/models'
import { getOrCreateAPActor } from '../../actors'
import {
  getCaptionAttributesFromObject,
  getFileAttributesFromUrl,
  getLiveAttributesFromObject,
  getPreviewFromIcons,
  getStreamingPlaylistAttributesFromObject,
  getTagsFromObject,
  getThumbnailFromIcons
} from './object-to-model-attributes'
import { getTrackerUrls, setVideoTrackers } from './trackers'

export abstract class APVideoAbstractBuilder {
  protected abstract videoObject: VideoObject
  protected abstract lTags: LoggerTagsFn

  protected async getOrCreateVideoChannelFromVideoObject () {
    const channel = this.videoObject.attributedTo.find(a => a.type === 'Group')
    if (!channel) throw new Error('Cannot find associated video channel to video ' + this.videoObject.url)

    if (checkUrlsSameHost(channel.id, this.videoObject.id) !== true) {
      throw new Error(`Video channel url ${channel.id} does not have the same host than video object id ${this.videoObject.id}`)
    }

    return getOrCreateAPActor(channel.id, 'all')
  }

  protected tryToGenerateThumbnail (video: MVideoThumbnail): Promise<MThumbnail> {
    return createVideoMiniatureFromUrl({
      downloadUrl: getThumbnailFromIcons(this.videoObject).url,
      video,
      type: ThumbnailType.MINIATURE
    }).catch(err => {
      logger.warn('Cannot generate thumbnail of %s.', this.videoObject.id, { err, ...this.lTags(video.uuid) })

      return undefined
    })
  }

  protected async setPreview (video: MVideoFullLight, t: Transaction) {
    // Don't fetch the preview that could be big, create a placeholder instead
    const previewIcon = getPreviewFromIcons(this.videoObject)
    if (!previewIcon) return

    const previewModel = createPlaceholderThumbnail({
      fileUrl: previewIcon.url,
      video,
      type: ThumbnailType.PREVIEW,
      size: previewIcon
    })

    await video.addAndSaveThumbnail(previewModel, t)
  }

  protected async setTags (video: MVideoFullLight, t: Transaction) {
    const tags = getTagsFromObject(this.videoObject)
    await setVideoTags({ video, tags, transaction: t })
  }

  protected async setTrackers (video: MVideoFullLight, t: Transaction) {
    const trackers = getTrackerUrls(this.videoObject, video)
    await setVideoTrackers({ video, trackers, transaction: t })
  }

  protected async insertOrReplaceCaptions (video: MVideoFullLight, t: Transaction) {
    const videoCaptionsPromises = getCaptionAttributesFromObject(video, this.videoObject)
      .map(a => new VideoCaptionModel(a) as MVideoCaption)
      .map(c => VideoCaptionModel.insertOrReplaceLanguage(c, t))

    await Promise.all(videoCaptionsPromises)
  }

  protected async insertOrReplaceLive (video: MVideoFullLight, transaction: Transaction) {
    const attributes = getLiveAttributesFromObject(video, this.videoObject)
    const [ videoLive ] = await VideoLiveModel.upsert(attributes, { transaction, returning: true })

    video.VideoLive = videoLive
  }

  protected async setWebTorrentFiles (video: MVideoFullLight, t: Transaction) {
    const videoFileAttributes = getFileAttributesFromUrl(video, this.videoObject.url)
    const newVideoFiles = videoFileAttributes.map(a => new VideoFileModel(a))

    // Remove video files that do not exist anymore
    const destroyTasks = deleteNonExistingModels(video.VideoFiles || [], newVideoFiles, t)
    await Promise.all(destroyTasks)

    // Update or add other one
    const upsertTasks = newVideoFiles.map(f => VideoFileModel.customUpsert(f, 'video', t))
    video.VideoFiles = await Promise.all(upsertTasks)
  }

  protected async setStreamingPlaylists (video: MVideoFullLight, t: Transaction) {
    const streamingPlaylistAttributes = getStreamingPlaylistAttributesFromObject(video, this.videoObject, video.VideoFiles || [])
    const newStreamingPlaylists = streamingPlaylistAttributes.map(a => new VideoStreamingPlaylistModel(a))

    // Remove video playlists that do not exist anymore
    const destroyTasks = deleteNonExistingModels(video.VideoStreamingPlaylists || [], newStreamingPlaylists, t)
    await Promise.all(destroyTasks)

    video.VideoStreamingPlaylists = []

    for (const playlistAttributes of streamingPlaylistAttributes) {

      const streamingPlaylistModel = await this.insertOrReplaceStreamingPlaylist(playlistAttributes, t)
      streamingPlaylistModel.Video = video

      await this.setStreamingPlaylistFiles(video, streamingPlaylistModel, playlistAttributes.tagAPObject, t)

      video.VideoStreamingPlaylists.push(streamingPlaylistModel)
    }
  }

  private async insertOrReplaceStreamingPlaylist (attributes: VideoStreamingPlaylistModel['_creationAttributes'], t: Transaction) {
    const [ streamingPlaylist ] = await VideoStreamingPlaylistModel.upsert(attributes, { returning: true, transaction: t })

    return streamingPlaylist as MStreamingPlaylistFilesVideo
  }

  private getStreamingPlaylistFiles (video: MVideoFullLight, type: VideoStreamingPlaylistType) {
    const playlist = video.VideoStreamingPlaylists.find(s => s.type === type)
    if (!playlist) return []

    return playlist.VideoFiles
  }

  private async setStreamingPlaylistFiles (
    video: MVideoFullLight,
    playlistModel: MStreamingPlaylistFilesVideo,
    tagObjects: ActivityTagObject[],
    t: Transaction
  ) {
    const oldStreamingPlaylistFiles = this.getStreamingPlaylistFiles(video, playlistModel.type)

    const newVideoFiles: MVideoFile[] = getFileAttributesFromUrl(playlistModel, tagObjects).map(a => new VideoFileModel(a))

    const destroyTasks = deleteNonExistingModels(oldStreamingPlaylistFiles, newVideoFiles, t)
    await Promise.all(destroyTasks)

    // Update or add other one
    const upsertTasks = newVideoFiles.map(f => VideoFileModel.customUpsert(f, 'streaming-playlist', t))
    playlistModel.VideoFiles = await Promise.all(upsertTasks)
  }
}