]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blobdiff - server/models/video/video.ts
Fix getting avatars in videos list
[github/Chocobozzz/PeerTube.git] / server / models / video / video.ts
index 3eed1b58d109c2eb8cf9b86b7ac78fef51da58a1..a4093ce3ba9aba83f2cf0f0d3b0c23be6644b99e 100644 (file)
@@ -25,7 +25,6 @@ import {
   UpdatedAt
 } from 'sequelize-typescript'
 import { buildNSFWFilter } from '@server/helpers/express-utils'
-import { uuidToShort } from '@server/helpers/uuid'
 import { getPrivaciesForFederation, isPrivacyForFederation, isStateForFederation } from '@server/helpers/video'
 import { LiveManager } from '@server/lib/live/live-manager'
 import { removeHLSObjectStorage, removeWebTorrentObjectStorage } from '@server/lib/object-storage'
@@ -33,14 +32,24 @@ import { getHLSDirectory, getHLSRedundancyDirectory } from '@server/lib/paths'
 import { VideoPathManager } from '@server/lib/video-path-manager'
 import { getServerActor } from '@server/models/application/application'
 import { ModelCache } from '@server/models/model-cache'
-import { AttributesOnly, buildVideoEmbedPath, buildVideoWatchPath, pick } from '@shared/core-utils'
-import { VideoInclude } from '@shared/models'
-import { VideoFile } from '@shared/models/videos/video-file.model'
-import { ResultList, UserRight, VideoPrivacy, VideoState } from '../../../shared'
-import { VideoObject } from '../../../shared/models/activitypub/objects'
-import { Video, VideoDetails, VideoRateType, VideoStorage } from '../../../shared/models/videos'
-import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type'
-import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type'
+import { buildVideoEmbedPath, buildVideoWatchPath, pick } from '@shared/core-utils'
+import { ffprobePromise, getAudioStream, uuidToShort } from '@shared/extra-utils'
+import {
+  ResultList,
+  ThumbnailType,
+  UserRight,
+  Video,
+  VideoDetails,
+  VideoFile,
+  VideoInclude,
+  VideoObject,
+  VideoPrivacy,
+  VideoRateType,
+  VideoState,
+  VideoStorage,
+  VideoStreamingPlaylistType
+} from '@shared/models'
+import { AttributesOnly } from '@shared/typescript-utils'
 import { peertubeTruncate } from '../../helpers/core-utils'
 import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
 import { exists, isBooleanValid } from '../../helpers/custom-validators/misc'
@@ -52,7 +61,7 @@ import {
   isVideoStateValid,
   isVideoSupportValid
 } from '../../helpers/custom-validators/videos'
-import { getVideoFileResolution } from '../../helpers/ffprobe-utils'
+import { getVideoStreamDimensionsInfo } from '../../helpers/ffmpeg'
 import { logger } from '../../helpers/logger'
 import { CONFIG } from '../../initializers/config'
 import { ACTIVITY_PUB, API_VERSION, CONSTRAINTS_FIELDS, LAZY_STATIC_PATHS, STATIC_PATHS, WEBSERVER } from '../../initializers/constants'
@@ -105,9 +114,13 @@ import {
   videoModelToFormattedJSON
 } from './formatter/video-format-utils'
 import { ScheduleVideoUpdateModel } from './schedule-video-update'
-import { VideoModelGetQueryBuilder } from './sql/video-model-get-query-builder'
-import { BuildVideosListQueryOptions, DisplayOnlyForFollowerOptions, VideosIdListQueryBuilder } from './sql/videos-id-list-query-builder'
-import { VideosModelListQueryBuilder } from './sql/videos-model-list-query-builder'
+import {
+  BuildVideosListQueryOptions,
+  DisplayOnlyForFollowerOptions,
+  VideoModelGetQueryBuilder,
+  VideosIdListQueryBuilder,
+  VideosModelListQueryBuilder
+} from './sql/video'
 import { TagModel } from './tag'
 import { ThumbnailModel } from './thumbnail'
 import { VideoBlacklistModel } from './video-blacklist'
@@ -220,8 +233,8 @@ export type ForAPIOptions = {
                 required: false
               },
               {
-                model: ActorImageModel.unscoped(),
-                as: 'Avatar',
+                model: ActorImageModel,
+                as: 'Avatars',
                 required: false
               }
             ]
@@ -243,8 +256,8 @@ export type ForAPIOptions = {
                     required: false
                   },
                   {
-                    model: ActorImageModel.unscoped(),
-                    as: 'Avatar',
+                    model: ActorImageModel,
+                    as: 'Avatars',
                     required: false
                   }
                 ]
@@ -746,7 +759,7 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
 
       // Remove physical files and torrents
       instance.VideoFiles.forEach(file => {
-        tasks.push(instance.removeFileAndTorrent(file))
+        tasks.push(instance.removeWebTorrentFileAndTorrent(file))
       })
 
       // Remove playlists file
@@ -1041,6 +1054,7 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
     languageOneOf?: string[]
     tagsOneOf?: string[]
     tagsAllOf?: string[]
+    privacyOneOf?: VideoPrivacy[]
 
     accountId?: number
     videoChannelId?: number
@@ -1059,6 +1073,7 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
     search?: string
   }) {
     VideoModel.throwIfPrivateIncludeWithoutUser(options.include, options.user)
+    VideoModel.throwIfPrivacyOneOfWithoutUser(options.privacyOneOf, options.user)
 
     const trendingDays = options.sort.endsWith('trending')
       ? CONFIG.TRENDING.VIDEOS.INTERVAL_DAYS
@@ -1082,6 +1097,7 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
         'languageOneOf',
         'tagsOneOf',
         'tagsAllOf',
+        'privacyOneOf',
         'isLocal',
         'include',
         'displayOnlyForFollower',
@@ -1119,6 +1135,7 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
     languageOneOf?: string[]
     tagsOneOf?: string[]
     tagsAllOf?: string[]
+    privacyOneOf?: VideoPrivacy[]
 
     displayOnlyForFollower: DisplayOnlyForFollowerOptions | null
 
@@ -1140,6 +1157,7 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
     uuids?: string[]
   }) {
     VideoModel.throwIfPrivateIncludeWithoutUser(options.include, options.user)
+    VideoModel.throwIfPrivacyOneOfWithoutUser(options.privacyOneOf, options.user)
 
     const serverActor = await getServerActor()
 
@@ -1153,6 +1171,7 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
         'languageOneOf',
         'tagsOneOf',
         'tagsAllOf',
+        'privacyOneOf',
         'user',
         'isLocal',
         'host',
@@ -1473,7 +1492,8 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
       required: false,
       where: {
         startDate: {
-          [Op.gte]: new Date(new Date().getTime() - (24 * 3600 * 1000) * trendingDays)
+          // FIXME: ts error
+          [Op.gte as any]: new Date(new Date().getTime() - (24 * 3600 * 1000) * trendingDays)
         }
       }
     }
@@ -1510,14 +1530,19 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
 
   private static throwIfPrivateIncludeWithoutUser (include: VideoInclude, user: MUserAccountId) {
     if (VideoModel.isPrivateInclude(include) && !user?.hasRight(UserRight.SEE_ALL_VIDEOS)) {
-      throw new Error('Try to filter all-local but no user has not the see all videos right')
+      throw new Error('Try to filter all-local but user cannot see all videos')
+    }
+  }
+
+  private static throwIfPrivacyOneOfWithoutUser (privacyOneOf: VideoPrivacy[], user: MUserAccountId) {
+    if (privacyOneOf && !user?.hasRight(UserRight.SEE_ALL_VIDEOS)) {
+      throw new Error('Try to choose video privacies but user cannot see all videos')
     }
   }
 
   private static isPrivateInclude (include: VideoInclude) {
     return include & VideoInclude.BLACKLISTED ||
            include & VideoInclude.BLOCKED_OWNER ||
-           include & VideoInclude.HIDDEN_PRIVACY ||
            include & VideoInclude.NOT_PUBLISHED_STATE
   }
 
@@ -1576,9 +1601,7 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
 
     if (Array.isArray(this.Thumbnails) === false) this.Thumbnails = []
 
-    // Already have this thumbnail, skip
-    if (this.Thumbnails.find(t => t.id === savedThumbnail.id)) return
-
+    this.Thumbnails = this.Thumbnails.filter(t => t.id !== savedThumbnail.id)
     this.Thumbnails.push(savedThumbnail)
   }
 
@@ -1660,12 +1683,20 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
     return peertubeTruncate(this.description, { length: maxLength })
   }
 
-  getMaxQualityResolution () {
+  probeMaxQualityFile () {
     const file = this.getMaxQualityFile()
     const videoOrPlaylist = file.getVideoOrStreamingPlaylist()
 
-    return VideoPathManager.Instance.makeAvailableVideoFile(videoOrPlaylist, file, originalFilePath => {
-      return getVideoFileResolution(originalFilePath)
+    return VideoPathManager.Instance.makeAvailableVideoFile(file.withVideoOrPlaylist(videoOrPlaylist), async originalFilePath => {
+      const probe = await ffprobePromise(originalFilePath)
+
+      const { audioStream } = await getAudioStream(originalFilePath, probe)
+
+      return {
+        audioStream,
+
+        ...await getVideoStreamDimensionsInfo(originalFilePath, probe)
+      }
     })
   }
 
@@ -1697,7 +1728,7 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
                                        .concat(toAdd)
   }
 
-  removeFileAndTorrent (videoFile: MVideoFile, isRedundancy = false) {
+  removeWebTorrentFileAndTorrent (videoFile: MVideoFile, isRedundancy = false) {
     const filePath = isRedundancy
       ? VideoPathManager.Instance.getFSRedundancyVideoFilePath(this, videoFile)
       : VideoPathManager.Instance.getFSVideoFileOutputPath(this, videoFile)
@@ -1733,7 +1764,7 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
       )
 
       if (streamingPlaylist.storage === VideoStorage.OBJECT_STORAGE) {
-        await removeHLSObjectStorage(streamingPlaylist, this)
+        await removeHLSObjectStorage(streamingPlaylist.withVideo(this))
       }
     }
   }
@@ -1756,8 +1787,8 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
     return this.hasPrivacyForFederation() === false && isPrivacyForFederation(newPrivacy) === true
   }
 
-  setAsRefreshed () {
-    return setAsUpdated('video', this.id)
+  setAsRefreshed (transaction?: Transaction) {
+    return setAsUpdated('video', this.id, transaction)
   }
 
   requiresAuth () {