]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blobdiff - server/models/video/video.ts
Fix bad RSS descriptions when filtering videos by account or channel
[github/Chocobozzz/PeerTube.git] / server / models / video / video.ts
index 2875e668560392c70fb0f2bc49a8479d5af51b97..59c378efaa2f2ace4922596f6d08ab49c734f07c 100644 (file)
@@ -2,7 +2,7 @@ import * as Bluebird from 'bluebird'
 import { map, maxBy } from 'lodash'
 import * as magnetUtil from 'magnet-uri'
 import * as parseTorrent from 'parse-torrent'
-import { join, extname } from 'path'
+import { extname, join } from 'path'
 import * as Sequelize from 'sequelize'
 import {
   AllowNull,
@@ -25,14 +25,14 @@ import {
   Table,
   UpdatedAt
 } from 'sequelize-typescript'
-import { VideoPrivacy, VideoResolution } from '../../../shared'
+import { VideoPrivacy, VideoResolution, VideoState } from '../../../shared'
 import { VideoTorrentObject } from '../../../shared/models/activitypub/objects'
 import { Video, VideoDetails, VideoFile } from '../../../shared/models/videos'
 import { VideoFilter } from '../../../shared/models/videos/video-query.type'
 import {
+  copyFilePromise,
   createTorrentPromise,
   peertubeTruncate,
-  copyFilePromise,
   renamePromise,
   statPromise,
   unlinkPromise,
@@ -47,7 +47,7 @@ import {
   isVideoLanguageValid,
   isVideoLicenceValid,
   isVideoNameValid,
-  isVideoPrivacyValid,
+  isVideoPrivacyValid, isVideoStateValid,
   isVideoSupportValid
 } from '../../helpers/custom-validators/videos'
 import { generateImageFromVideoFile, getVideoFileResolution, transcode } from '../../helpers/ffmpeg-utils'
@@ -63,9 +63,10 @@ import {
   STATIC_PATHS,
   THUMBNAILS_SIZE,
   VIDEO_CATEGORIES,
+  VIDEO_EXT_MIMETYPE,
   VIDEO_LANGUAGES,
   VIDEO_LICENCES,
-  VIDEO_PRIVACIES
+  VIDEO_PRIVACIES, VIDEO_STATES
 } from '../../initializers'
 import {
   getVideoCommentsActivityPubUrl,
@@ -92,10 +93,7 @@ enum ScopeNames {
   AVAILABLE_FOR_LIST = 'AVAILABLE_FOR_LIST',
   WITH_ACCOUNT_DETAILS = 'WITH_ACCOUNT_DETAILS',
   WITH_TAGS = 'WITH_TAGS',
-  WITH_FILES = 'WITH_FILES',
-  WITH_SHARES = 'WITH_SHARES',
-  WITH_RATES = 'WITH_RATES',
-  WITH_COMMENTS = 'WITH_COMMENTS'
+  WITH_FILES = 'WITH_FILES'
 }
 
 @Scopes({
@@ -182,7 +180,20 @@ enum ScopeNames {
             ')'
           )
         },
-        privacy: VideoPrivacy.PUBLIC
+        // Always list public videos
+        privacy: VideoPrivacy.PUBLIC,
+        // Always list published videos, or videos that are being transcoded but on which we don't want to wait for transcoding
+        [ Sequelize.Op.or ]: [
+          {
+            state: VideoState.PUBLISHED
+          },
+          {
+            [ Sequelize.Op.and ]: {
+              state: VideoState.TO_TRANSCODE,
+              waitTranscoding: false
+            }
+          }
+        ]
       },
       include: [ videoChannelInclude ]
     }
@@ -271,42 +282,6 @@ enum ScopeNames {
         required: true
       }
     ]
-  },
-  [ScopeNames.WITH_SHARES]: {
-    include: [
-      {
-        ['separate' as any]: true,
-        model: () => VideoShareModel.unscoped()
-      }
-    ]
-  },
-  [ScopeNames.WITH_RATES]: {
-    include: [
-      {
-        ['separate' as any]: true,
-        model: () => AccountVideoRateModel,
-        include: [
-          {
-            model: () => AccountModel.unscoped(),
-            required: true,
-            include: [
-              {
-                attributes: [ 'url' ],
-                model: () => ActorModel.unscoped()
-              }
-            ]
-          }
-        ]
-      }
-    ]
-  },
-  [ScopeNames.WITH_COMMENTS]: {
-    include: [
-      {
-        ['separate' as any]: true,
-        model: () => VideoCommentModel.unscoped()
-      }
-    ]
   }
 })
 @Table({
@@ -334,7 +309,7 @@ enum ScopeNames {
       fields: [ 'channelId' ]
     },
     {
-      fields: [ 'id', 'privacy' ]
+      fields: [ 'id', 'privacy', 'state', 'waitTranscoding' ]
     },
     {
       fields: [ 'url'],
@@ -434,6 +409,16 @@ export class VideoModel extends Model<VideoModel> {
   @Column
   commentsEnabled: boolean
 
+  @AllowNull(false)
+  @Column
+  waitTranscoding: boolean
+
+  @AllowNull(false)
+  @Default(null)
+  @Is('VideoState', value => throwIfNotValid(value, isVideoStateValid, 'state'))
+  @Column
+  state: VideoState
+
   @CreatedAt
   createdAt: Date
 
@@ -670,7 +655,7 @@ export class VideoModel extends Model<VideoModel> {
     })
   }
 
-  static listAccountVideosForApi (accountId: number, start: number, count: number, sort: string, hideNSFW: boolean, withFiles = false) {
+  static listUserVideosForApi (accountId: number, start: number, count: number, sort: string, hideNSFW: boolean, withFiles = false) {
     const query: IFindOptions<VideoModel> = {
       offset: start,
       limit: count,
@@ -857,12 +842,13 @@ export class VideoModel extends Model<VideoModel> {
       .findOne(options)
   }
 
-  static loadByUUIDAndPopulateAccountAndServerAndTags (uuid: string) {
+  static loadByUUIDAndPopulateAccountAndServerAndTags (uuid: string, t?: Sequelize.Transaction) {
     const options = {
       order: [ [ 'Tags', 'name', 'ASC' ] ],
       where: {
         uuid
-      }
+      },
+      transaction: t
     }
 
     return VideoModel
@@ -904,31 +890,23 @@ export class VideoModel extends Model<VideoModel> {
   }
 
   private static getCategoryLabel (id: number) {
-    let categoryLabel = VIDEO_CATEGORIES[id]
-    if (!categoryLabel) categoryLabel = 'Misc'
-
-    return categoryLabel
+    return VIDEO_CATEGORIES[id] || 'Misc'
   }
 
   private static getLicenceLabel (id: number) {
-    let licenceLabel = VIDEO_LICENCES[id]
-    if (!licenceLabel) licenceLabel = 'Unknown'
-
-    return licenceLabel
+    return VIDEO_LICENCES[id] || 'Unknown'
   }
 
   private static getLanguageLabel (id: string) {
-    let languageLabel = VIDEO_LANGUAGES[id]
-    if (!languageLabel) languageLabel = 'Unknown'
-
-    return languageLabel
+    return VIDEO_LANGUAGES[id] || 'Unknown'
   }
 
   private static getPrivacyLabel (id: number) {
-    let privacyLabel = VIDEO_PRIVACIES[id]
-    if (!privacyLabel) privacyLabel = 'Unknown'
+    return VIDEO_PRIVACIES[id] || 'Unknown'
+  }
 
-    return privacyLabel
+  private static getStateLabel (id: number) {
+    return VIDEO_STATES[id] || 'Unknown'
   }
 
   getOriginalFile () {
@@ -1025,11 +1003,16 @@ export class VideoModel extends Model<VideoModel> {
     return join(STATIC_PATHS.PREVIEWS, this.getPreviewName())
   }
 
-  toFormattedJSON (): Video {
+  toFormattedJSON (options?: {
+    additionalAttributes: {
+      state: boolean,
+      waitTranscoding: boolean
+    }
+  }): Video {
     const formattedAccount = this.VideoChannel.Account.toFormattedJSON()
     const formattedVideoChannel = this.VideoChannel.toFormattedJSON()
 
-    return {
+    const videoObject: Video = {
       id: this.id,
       uuid: this.uuid,
       name: this.name,
@@ -1081,6 +1064,19 @@ export class VideoModel extends Model<VideoModel> {
         avatar: formattedVideoChannel.avatar
       }
     }
+
+    if (options) {
+      if (options.additionalAttributes.state) {
+        videoObject.state = {
+          id: this.state,
+          label: VideoModel.getStateLabel(this.state)
+        }
+      }
+
+      if (options.additionalAttributes.waitTranscoding) videoObject.waitTranscoding = this.waitTranscoding
+    }
+
+    return videoObject
   }
 
   toFormattedDetailsJSON (): VideoDetails {
@@ -1093,6 +1089,11 @@ export class VideoModel extends Model<VideoModel> {
       account: this.VideoChannel.Account.toFormattedJSON(),
       tags: map(this.Tags, 'name'),
       commentsEnabled: this.commentsEnabled,
+      waitTranscoding: this.waitTranscoding,
+      state: {
+        id: this.state,
+        label: VideoModel.getStateLabel(this.state)
+      },
       files: []
     }
 
@@ -1166,7 +1167,7 @@ export class VideoModel extends Model<VideoModel> {
     for (const file of this.VideoFiles) {
       url.push({
         type: 'Link',
-        mimeType: 'video/' + file.extname.replace('.', ''),
+        mimeType: VIDEO_EXT_MIMETYPE[file.extname],
         href: this.getVideoFileUrl(file, baseUrlHttp),
         width: file.resolution,
         size: file.size
@@ -1206,6 +1207,8 @@ export class VideoModel extends Model<VideoModel> {
       language,
       views: this.views,
       sensitive: this.nsfw,
+      waitTranscoding: this.waitTranscoding,
+      state: this.state,
       commentsEnabled: this.commentsEnabled,
       published: this.publishedAt.toISOString(),
       updated: this.updatedAt.toISOString(),
@@ -1324,28 +1327,30 @@ export class VideoModel extends Model<VideoModel> {
       videoId: this.id
     })
 
-    const outputPath = this.getVideoFilePath(updatedVideoFile)
-    await copyFilePromise(inputFilePath, outputPath)
-
     const currentVideoFile = this.VideoFiles.find(videoFile => videoFile.resolution === updatedVideoFile.resolution)
-    const isNewVideoFile = !currentVideoFile
 
-    if (!isNewVideoFile) {
-      if (currentVideoFile.extname !== updatedVideoFile.extname) {
-        await this.removeFile(currentVideoFile)
-        currentVideoFile.set('extname', updatedVideoFile.extname)
-      }
+    if (currentVideoFile) {
+      // Remove old file and old torrent
+      await this.removeFile(currentVideoFile)
+      await this.removeTorrent(currentVideoFile)
+      // Remove the old video file from the array
+      this.VideoFiles = this.VideoFiles.filter(f => f !== currentVideoFile)
+
+      // Update the database
+      currentVideoFile.set('extname', updatedVideoFile.extname)
       currentVideoFile.set('size', updatedVideoFile.size)
+
       updatedVideoFile = currentVideoFile
     }
 
+    const outputPath = this.getVideoFilePath(updatedVideoFile)
+    await copyFilePromise(inputFilePath, outputPath)
+
     await this.createTorrentAndSetInfoHash(updatedVideoFile)
 
     await updatedVideoFile.save()
 
-    if (isNewVideoFile) {
-      this.VideoFiles.push(updatedVideoFile)
-    }
+    this.VideoFiles.push(updatedVideoFile)
   }
 
   getOriginalFileResolution () {