]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blobdiff - server/models/video/video.ts
Add admin view to manage comments
[github/Chocobozzz/PeerTube.git] / server / models / video / video.ts
index 1eded0d56191f775fd15fa73f6b8afa8cf4745ae..f3055a494a4fab81de5c5e73555ff9723631785c 100644 (file)
@@ -25,13 +25,14 @@ import {
   UpdatedAt
 } from 'sequelize-typescript'
 import { buildNSFWFilter } from '@server/helpers/express-utils'
-import { getPrivaciesForFederation, isPrivacyForFederation } from '@server/helpers/video'
+import { getPrivaciesForFederation, isPrivacyForFederation, isStateForFederation } from '@server/helpers/video'
+import { LiveManager } from '@server/lib/live-manager'
 import { getHLSDirectory, getTorrentFileName, getTorrentFilePath, getVideoFilename, getVideoFilePath } from '@server/lib/video-paths'
 import { getServerActor } from '@server/models/application/application'
 import { ModelCache } from '@server/models/model-cache'
 import { VideoFile } from '@shared/models/videos/video-file.model'
 import { ResultList, UserRight, VideoPrivacy, VideoState } from '../../../shared'
-import { VideoTorrentObject } from '../../../shared/models/activitypub/objects'
+import { VideoObject } from '../../../shared/models/activitypub/objects'
 import { Video, VideoDetails } from '../../../shared/models/videos'
 import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type'
 import { VideoFilter } from '../../../shared/models/videos/video-query.type'
@@ -95,7 +96,7 @@ import {
   MVideoWithRights
 } from '../../types/models'
 import { MThumbnail } from '../../types/models/video/thumbnail'
-import { MVideoFile, MVideoFileStreamingPlaylistVideo, MVideoFileRedundanciesOpt } from '../../types/models/video/video-file'
+import { MVideoFile, MVideoFileRedundanciesOpt, MVideoFileStreamingPlaylistVideo } from '../../types/models/video/video-file'
 import { VideoAbuseModel } from '../abuse/video-abuse'
 import { AccountModel } from '../account/account'
 import { AccountVideoRateModel } from '../account/account-video-rate'
@@ -121,13 +122,13 @@ import {
   videoModelToFormattedJSON
 } from './video-format-utils'
 import { VideoImportModel } from './video-import'
+import { VideoLiveModel } from './video-live'
 import { VideoPlaylistElementModel } from './video-playlist-element'
 import { buildListQuery, BuildVideosQueryOptions, wrapForAPIResults } from './video-query-builder'
 import { VideoShareModel } from './video-share'
 import { VideoStreamingPlaylistModel } from './video-streaming-playlist'
 import { VideoTagModel } from './video-tag'
 import { VideoViewModel } from './video-view'
-import { stream } from 'winston'
 
 export enum ScopeNames {
   AVAILABLE_FOR_LIST_IDS = 'AVAILABLE_FOR_LIST_IDS',
@@ -141,7 +142,8 @@ export enum ScopeNames {
   WITH_STREAMING_PLAYLISTS = 'WITH_STREAMING_PLAYLISTS',
   WITH_USER_ID = 'WITH_USER_ID',
   WITH_IMMUTABLE_ATTRIBUTES = 'WITH_IMMUTABLE_ATTRIBUTES',
-  WITH_THUMBNAILS = 'WITH_THUMBNAILS'
+  WITH_THUMBNAILS = 'WITH_THUMBNAILS',
+  WITH_LIVE = 'WITH_LIVE'
 }
 
 export type ForAPIOptions = {
@@ -244,6 +246,14 @@ export type AvailableForListIDsOptions = {
       }
     ]
   },
+  [ScopeNames.WITH_LIVE]: {
+    include: [
+      {
+        model: VideoLiveModel.unscoped(),
+        required: false
+      }
+    ]
+  },
   [ScopeNames.WITH_USER_ID]: {
     include: [
       {
@@ -550,6 +560,11 @@ export class VideoModel extends Model<VideoModel> {
   @Column
   remote: boolean
 
+  @AllowNull(false)
+  @Default(false)
+  @Column
+  isLive: boolean
+
   @AllowNull(false)
   @Is('VideoUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'url'))
   @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEOS.URL.max))
@@ -720,6 +735,15 @@ export class VideoModel extends Model<VideoModel> {
   })
   VideoBlacklist: VideoBlacklistModel
 
+  @HasOne(() => VideoLiveModel, {
+    foreignKey: {
+      name: 'videoId',
+      allowNull: false
+    },
+    onDelete: 'cascade'
+  })
+  VideoLive: VideoLiveModel
+
   @HasOne(() => VideoImportModel, {
     foreignKey: {
       name: 'videoId',
@@ -795,6 +819,15 @@ export class VideoModel extends Model<VideoModel> {
     return undefined
   }
 
+  @BeforeDestroy
+  static stopLiveIfNeeded (instance: VideoModel) {
+    if (!instance.isLive) return
+
+    logger.info('Stopping live of video %s after video deletion.', instance.uuid)
+
+    return LiveManager.Instance.stopSessionOf(instance.id)
+  }
+
   @BeforeDestroy
   static invalidateCache (instance: VideoModel) {
     ModelCache.Instance.invalidateCache('video', instance.id)
@@ -921,6 +954,17 @@ export class VideoModel extends Model<VideoModel> {
             }
           ]
         },
+        {
+          model: VideoStreamingPlaylistModel.unscoped(),
+          required: false,
+          include: [
+            {
+              model: VideoFileModel,
+              required: false
+            }
+          ]
+        },
+        VideoLiveModel.unscoped(),
         VideoFileModel,
         TagModel
       ]
@@ -944,6 +988,19 @@ export class VideoModel extends Model<VideoModel> {
     })
   }
 
+  static listPublishedLiveIds () {
+    const options = {
+      attributes: [ 'id' ],
+      where: {
+        isLive: true,
+        state: VideoState.PUBLISHED
+      }
+    }
+
+    return VideoModel.findAll(options)
+      .map(v => v.id)
+  }
+
   static listUserVideosForApi (
     accountId: number,
     start: number,
@@ -1120,6 +1177,37 @@ export class VideoModel extends Model<VideoModel> {
     return VideoModel.getAvailableForApi(queryOptions)
   }
 
+  static countLocalLives () {
+    const options = {
+      where: {
+        remote: false,
+        isLive: true
+      }
+    }
+
+    return VideoModel.count(options)
+  }
+
+  static countLivesOfAccount (accountId: number) {
+    const options = {
+      where: {
+        remote: false,
+        isLive: true
+      },
+      include: [
+        {
+          required: true,
+          model: VideoChannelModel.unscoped(),
+          where: {
+            accountId
+          }
+        }
+      ]
+    }
+
+    return VideoModel.count(options)
+  }
+
   static load (id: number | string, t?: Transaction): Bluebird<MVideoThumbnail> {
     const where = buildWhereIdOrUUID(id)
     const options = {
@@ -1277,7 +1365,8 @@ export class VideoModel extends Model<VideoModel> {
       ScopeNames.WITH_SCHEDULED_UPDATE,
       ScopeNames.WITH_WEBTORRENT_FILES,
       ScopeNames.WITH_STREAMING_PLAYLISTS,
-      ScopeNames.WITH_THUMBNAILS
+      ScopeNames.WITH_THUMBNAILS,
+      ScopeNames.WITH_LIVE
     ]
 
     if (userId) {
@@ -1309,6 +1398,7 @@ export class VideoModel extends Model<VideoModel> {
       ScopeNames.WITH_ACCOUNT_DETAILS,
       ScopeNames.WITH_SCHEDULED_UPDATE,
       ScopeNames.WITH_THUMBNAILS,
+      ScopeNames.WITH_LIVE,
       { method: [ ScopeNames.WITH_WEBTORRENT_FILES, true ] },
       { method: [ ScopeNames.WITH_STREAMING_PLAYLISTS, true ] }
     ]
@@ -1479,7 +1569,6 @@ export class VideoModel extends Model<VideoModel> {
     const thumbnailsDone = new Set<number>()
     const historyDone = new Set<number>()
     const videoFilesDone = new Set<number>()
-    const videoStreamingPlaylistsDone = new Set<number>()
 
     const videos: VideoModel[] = []
 
@@ -1581,13 +1670,6 @@ export class VideoModel extends Model<VideoModel> {
         videoFilesDone.add(row.VideoFiles.id)
       }
 
-      if (row.VideoFiles?.id && !videoFilesDone.has(row.VideoFiles.id)) {
-        const videoFileModel = new VideoFileModel(pick(row.VideoFiles, videoFileKeys))
-        videoModel.VideoFiles.push(videoFileModel)
-
-        videoFilesDone.add(row.VideoFiles.id)
-      }
-
       if (row.VideoStreamingPlaylists?.id && !videoStreamingPlaylistMemo[row.VideoStreamingPlaylists.id]) {
         const streamingPlaylist = new VideoStreamingPlaylistModel(pick(row.VideoStreamingPlaylists, videoStreamingPlaylistKeys))
         streamingPlaylist.VideoFiles = []
@@ -1749,8 +1831,6 @@ export class VideoModel extends Model<VideoModel> {
     const { baseUrlHttp, baseUrlWs } = this.getBaseUrls()
     let files: MVideoFileRedundanciesOpt[] = []
 
-    logger.info('coucou', { files })
-
     if (Array.isArray(this.VideoFiles)) {
       files = files.concat(this.VideoFiles)
     }
@@ -1759,12 +1839,10 @@ export class VideoModel extends Model<VideoModel> {
       files = files.concat(p.VideoFiles || [])
     }
 
-    logger.info('coucou', { files, video: this.VideoStreamingPlaylists })
-
     return videoFilesModelToFormattedJSON(this, baseUrlHttp, baseUrlWs, files)
   }
 
-  toActivityPubObject (this: MVideoAP): VideoTorrentObject {
+  toActivityPubObject (this: MVideoAP): VideoObject {
     return videoModelToActivityPubObject(this)
   }
 
@@ -1851,6 +1929,10 @@ export class VideoModel extends Model<VideoModel> {
     return isPrivacyForFederation(this.privacy)
   }
 
+  hasStateForFederation () {
+    return isStateForFederation(this.state)
+  }
+
   isNewVideo (newPrivacy: VideoPrivacy) {
     return this.hasPrivacyForFederation() === false && isPrivacyForFederation(newPrivacy) === true
   }