]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blobdiff - server/models/video/video.ts
add import-youtube guide inside documentation (#298)
[github/Chocobozzz/PeerTube.git] / server / models / video / video.ts
index d22d5731015ed32ab5ad2956cbba47c816e453f5..aa1878caac311566ae88dedbc5cf7bba4ae02349 100644 (file)
@@ -5,8 +5,26 @@ import * as parseTorrent from 'parse-torrent'
 import { join } from 'path'
 import * as Sequelize from 'sequelize'
 import {
-  AfterDestroy, AllowNull, BeforeDestroy, BelongsTo, BelongsToMany, Column, CreatedAt, DataType, Default, ForeignKey, HasMany,
-  IFindOptions, Is, IsInt, IsUUID, Min, Model, Scopes, Table, UpdatedAt
+  AfterDestroy,
+  AllowNull,
+  BeforeDestroy,
+  BelongsTo,
+  BelongsToMany,
+  Column,
+  CreatedAt,
+  DataType,
+  Default,
+  ForeignKey,
+  HasMany,
+  IFindOptions,
+  Is,
+  IsInt,
+  IsUUID,
+  Min,
+  Model,
+  Scopes,
+  Table,
+  UpdatedAt
 } from 'sequelize-typescript'
 import { VideoPrivacy, VideoResolution } from '../../../shared'
 import { VideoTorrentObject } from '../../../shared/models/activitypub/objects'
@@ -16,21 +34,41 @@ import { createTorrentPromise, renamePromise, statPromise, unlinkPromise, writeF
 import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
 import { isBooleanValid } from '../../helpers/custom-validators/misc'
 import {
-  isVideoCategoryValid, isVideoDescriptionValid, isVideoDurationValid, isVideoLanguageValid, isVideoLicenceValid, isVideoNameValid,
-  isVideoPrivacyValid
+  isVideoCategoryValid,
+  isVideoDescriptionValid,
+  isVideoDurationValid,
+  isVideoLanguageValid,
+  isVideoLicenceValid,
+  isVideoNameValid,
+  isVideoPrivacyValid, isVideoSupportValid
 } from '../../helpers/custom-validators/videos'
 import { generateImageFromVideoFile, getVideoFileHeight, transcode } from '../../helpers/ffmpeg-utils'
 import { logger } from '../../helpers/logger'
 import { getServerActor } from '../../helpers/utils'
 import {
-  API_VERSION, CONFIG, CONSTRAINTS_FIELDS, PREVIEWS_SIZE, REMOTE_SCHEME, STATIC_PATHS, THUMBNAILS_SIZE, VIDEO_CATEGORIES,
-  VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES
+  API_VERSION,
+  CONFIG,
+  CONSTRAINTS_FIELDS,
+  PREVIEWS_SIZE,
+  REMOTE_SCHEME,
+  STATIC_PATHS,
+  THUMBNAILS_SIZE,
+  VIDEO_CATEGORIES,
+  VIDEO_LANGUAGES,
+  VIDEO_LICENCES,
+  VIDEO_PRIVACIES
 } from '../../initializers'
-import { getAnnounceActivityPubUrl } from '../../lib/activitypub'
+import {
+  getVideoCommentsActivityPubUrl,
+  getVideoDislikesActivityPubUrl,
+  getVideoLikesActivityPubUrl,
+  getVideoSharesActivityPubUrl
+} from '../../lib/activitypub'
 import { sendDeleteVideo } from '../../lib/activitypub/send'
 import { AccountModel } from '../account/account'
 import { AccountVideoRateModel } from '../account/account-video-rate'
 import { ActorModel } from '../activitypub/actor'
+import { AvatarModel } from '../avatar/avatar'
 import { ServerModel } from '../server/server'
 import { getSort, throwIfNotValid } from '../utils'
 import { TagModel } from './tag'
@@ -138,6 +176,10 @@ enum ScopeNames {
                     attributes: [ 'host' ],
                     model: () => ServerModel.unscoped(),
                     required: false
+                  },
+                  {
+                    model: () => AvatarModel.unscoped(),
+                    required: false
                   }
                 ]
               }
@@ -262,6 +304,12 @@ export class VideoModel extends Model<VideoModel> {
   @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEOS.DESCRIPTION.max))
   description: string
 
+  @AllowNull(true)
+  @Default(null)
+  @Is('VideoSupport', value => throwIfNotValid(value, isVideoSupportValid, 'support'))
+  @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEOS.SUPPORT.max))
+  support: string
+
   @AllowNull(false)
   @Is('VideoDuration', value => throwIfNotValid(value, isVideoDurationValid, 'duration'))
   @Column
@@ -449,11 +497,15 @@ export class VideoModel extends Model<VideoModel> {
       where: {
         id: {
           [Sequelize.Op.in]: Sequelize.literal('(' + rawQuery + ')')
-        }
+        },
+        [Sequelize.Op.or]: [
+          { privacy: VideoPrivacy.PUBLIC },
+          { privacy: VideoPrivacy.UNLISTED }
+        ]
       },
       include: [
         {
-          attributes: [ 'id' ],
+          attributes: [ 'id', 'url' ],
           model: VideoShareModel.unscoped(),
           required: false,
           where: {
@@ -769,7 +821,8 @@ export class VideoModel extends Model<VideoModel> {
   createTorrentAndSetInfoHash = async function (videoFile: VideoFileModel) {
     const options = {
       announceList: [
-        [ CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT + '/tracker/socket' ]
+        [ CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT + '/tracker/socket' ],
+        [ CONFIG.WEBSERVER.URL + '/tracker/announce' ]
       ],
       urlList: [
         CONFIG.WEBSERVER.URL + STATIC_PATHS.WEBSEED + this.getVideoFilename(videoFile)
@@ -799,7 +852,7 @@ export class VideoModel extends Model<VideoModel> {
     return join(STATIC_PATHS.PREVIEWS, this.getPreviewName())
   }
 
-  toFormattedJSON () {
+  toFormattedJSON (): Video {
     let serverHost
 
     if (this.VideoChannel.Account.Actor.Server) {
@@ -833,10 +886,10 @@ export class VideoModel extends Model<VideoModel> {
       embedPath: this.getEmbedPath(),
       createdAt: this.createdAt,
       updatedAt: this.updatedAt
-    } as Video
+    }
   }
 
-  toFormattedDetailsJSON () {
+  toFormattedDetailsJSON (): VideoDetails {
     const formattedJson = this.toFormattedJSON()
 
     // Maybe our server is not up to date and there are new privacy settings since our version
@@ -846,6 +899,7 @@ export class VideoModel extends Model<VideoModel> {
     const detailsJson = {
       privacyLabel,
       privacy: this.privacy,
+      support: this.support,
       descriptionPath: this.getDescriptionPath(),
       channel: this.VideoChannel.toFormattedJSON(),
       account: this.VideoChannel.Account.toFormattedJSON(),
@@ -875,7 +929,7 @@ export class VideoModel extends Model<VideoModel> {
         return -1
       })
 
-    return Object.assign(formattedJson, detailsJson) as VideoDetails
+    return Object.assign(formattedJson, detailsJson)
   }
 
   toActivityPubObject (): VideoTorrentObject {
@@ -915,42 +969,19 @@ export class VideoModel extends Model<VideoModel> {
     let dislikesObject
 
     if (Array.isArray(this.AccountVideoRates)) {
-      const likes: string[] = []
-      const dislikes: string[] = []
-
-      for (const rate of this.AccountVideoRates) {
-        if (rate.type === 'like') {
-          likes.push(rate.Account.Actor.url)
-        } else if (rate.type === 'dislike') {
-          dislikes.push(rate.Account.Actor.url)
-        }
-      }
-
-      likesObject = activityPubCollection(likes)
-      dislikesObject = activityPubCollection(dislikes)
+      const res = this.toRatesActivityPubObjects()
+      likesObject = res.likesObject
+      dislikesObject = res.dislikesObject
     }
 
     let sharesObject
     if (Array.isArray(this.VideoShares)) {
-      const shares: string[] = []
-
-      for (const videoShare of this.VideoShares) {
-        const shareUrl = getAnnounceActivityPubUrl(this.url, videoShare.Actor)
-        shares.push(shareUrl)
-      }
-
-      sharesObject = activityPubCollection(shares)
+      sharesObject = this.toAnnouncesActivityPubObject()
     }
 
     let commentsObject
     if (Array.isArray(this.VideoComments)) {
-      const comments: string[] = []
-
-      for (const videoComment of this.VideoComments) {
-        comments.push(videoComment.url)
-      }
-
-      commentsObject = activityPubCollection(comments)
+      commentsObject = this.toCommentsActivityPubObject()
     }
 
     const url = []
@@ -989,20 +1020,20 @@ export class VideoModel extends Model<VideoModel> {
       type: 'Video' as 'Video',
       id: this.url,
       name: this.name,
-      // https://www.w3.org/TR/activitystreams-vocabulary/#dfn-duration
-      duration: 'PT' + this.duration + 'S',
+      duration: this.getActivityStreamDuration(),
       uuid: this.uuid,
       tag,
       category,
       licence,
       language,
       views: this.views,
-      nsfw: this.nsfw,
+      sensitive: this.nsfw,
       commentsEnabled: this.commentsEnabled,
       published: this.createdAt.toISOString(),
       updated: this.updatedAt.toISOString(),
       mediaType: 'text/markdown',
       content: this.getTruncatedDescription(),
+      support: this.support,
       icon: {
         type: 'Image',
         url: this.getThumbnailUrl(baseUrlHttp),
@@ -1028,6 +1059,44 @@ export class VideoModel extends Model<VideoModel> {
     }
   }
 
+  toAnnouncesActivityPubObject () {
+    const shares: string[] = []
+
+    for (const videoShare of this.VideoShares) {
+      shares.push(videoShare.url)
+    }
+
+    return activityPubCollection(getVideoSharesActivityPubUrl(this), shares)
+  }
+
+  toCommentsActivityPubObject () {
+    const comments: string[] = []
+
+    for (const videoComment of this.VideoComments) {
+      comments.push(videoComment.url)
+    }
+
+    return activityPubCollection(getVideoCommentsActivityPubUrl(this), comments)
+  }
+
+  toRatesActivityPubObjects () {
+    const likes: string[] = []
+    const dislikes: string[] = []
+
+    for (const rate of this.AccountVideoRates) {
+      if (rate.type === 'like') {
+        likes.push(rate.Account.Actor.url)
+      } else if (rate.type === 'dislike') {
+        dislikes.push(rate.Account.Actor.url)
+      }
+    }
+
+    const likesObject = activityPubCollection(getVideoLikesActivityPubUrl(this), likes)
+    const dislikesObject = activityPubCollection(getVideoDislikesActivityPubUrl(this), dislikes)
+
+    return { likesObject, dislikesObject }
+  }
+
   getTruncatedDescription () {
     if (!this.description) return null
 
@@ -1160,6 +1229,11 @@ export class VideoModel extends Model<VideoModel> {
     return unlinkPromise(torrentPath)
   }
 
+  getActivityStreamDuration () {
+    // https://www.w3.org/TR/activitystreams-vocabulary/#dfn-duration
+    return 'PT' + this.duration + 'S'
+  }
+
   private getBaseUrls () {
     let baseUrlHttp
     let baseUrlWs