]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blobdiff - server/lib/activitypub/videos.ts
First implem global search
[github/Chocobozzz/PeerTube.git] / server / lib / activitypub / videos.ts
index d182ca5a28e41df77dedf6bdf89c4676122d931a..6d20e0e654437c5ffa7f186978ea332b0fdcee89 100644 (file)
@@ -6,7 +6,7 @@ import {
   ActivityHashTagObject,
   ActivityMagnetUrlObject,
   ActivityPlaylistSegmentHashesObject,
-  ActivityPlaylistUrlObject,
+  ActivityPlaylistUrlObject, ActivitypubHttpFetcherPayload,
   ActivityTagObject,
   ActivityUrlObject,
   ActivityVideoUrlObject,
@@ -14,7 +14,7 @@ import {
 } from '../../../shared/index'
 import { VideoTorrentObject } from '../../../shared/models/activitypub/objects'
 import { VideoPrivacy } from '../../../shared/models/videos'
-import { sanitizeAndCheckVideoTorrentObject } from '../../helpers/custom-validators/activitypub/videos'
+import { isAPVideoFileMetadataObject, sanitizeAndCheckVideoTorrentObject } from '../../helpers/custom-validators/activitypub/videos'
 import { isVideoFileInfoHashValid } from '../../helpers/custom-validators/videos'
 import { deleteNonExistingModels, resetSequelizeInstance, retryTransactionWrapper } from '../../helpers/database-utils'
 import { logger } from '../../helpers/logger'
@@ -25,7 +25,8 @@ import {
   P2P_MEDIA_LOADER_PEER_VERSION,
   PREVIEWS_SIZE,
   REMOTE_SCHEME,
-  STATIC_PATHS, THUMBNAILS_SIZE
+  STATIC_PATHS,
+  THUMBNAILS_SIZE
 } from '../../initializers/constants'
 import { TagModel } from '../../models/video/tag'
 import { VideoModel } from '../../models/video/video'
@@ -37,7 +38,6 @@ import { sendCreateVideo, sendUpdateVideo } from './send'
 import { isArray } from '../../helpers/custom-validators/misc'
 import { VideoCaptionModel } from '../../models/video/video-caption'
 import { JobQueue } from '../job-queue'
-import { ActivitypubHttpFetcherPayload } from '../job-queue/handlers/activitypub-http-fetcher'
 import { createRates } from './video-rates'
 import { addVideoShares, shareVideoByServerAndChannel } from './share'
 import { fetchVideoByUrl, VideoFetchByUrlType } from '../../helpers/video'
@@ -68,7 +68,8 @@ import {
   MVideoAPWithoutCaption,
   MVideoFile,
   MVideoFullLight,
-  MVideoId, MVideoImmutable,
+  MVideoId,
+  MVideoImmutable,
   MVideoThumbnail
 } from '../../typings/models'
 import { MThumbnail } from '../../typings/models/video/thumbnail'
@@ -111,7 +112,7 @@ async function fetchRemoteVideo (videoUrl: string): Promise<{ response: request.
 
   logger.info('Fetching remote video %s.', videoUrl)
 
-  const { response, body } = await doRequest(options)
+  const { response, body } = await doRequest<any>(options)
 
   if (sanitizeAndCheckVideoTorrentObject(body) === false || checkUrlsSameHost(body.id, videoUrl) !== true) {
     logger.debug('Remote video JSON is not valid.', { body })
@@ -129,7 +130,7 @@ async function fetchRemoteVideoDescription (video: MVideoAccountLight) {
     json: true
   }
 
-  const { body } = await doRequest(options)
+  const { body } = await doRequest<any>(options)
   return body.description ? body.description : ''
 }
 
@@ -271,11 +272,22 @@ async function getOrCreateVideoAndAccountAndChannel (
 
   const actor = await getOrCreateVideoChannelFromVideoObject(fetchedVideo)
   const videoChannel = actor.VideoChannel
-  const { autoBlacklisted, videoCreated } = await retryTransactionWrapper(createVideo, fetchedVideo, videoChannel, syncParam.thumbnail)
 
-  await syncVideoExternalAttributes(videoCreated, fetchedVideo, syncParam)
+  try {
+    const { autoBlacklisted, videoCreated } = await retryTransactionWrapper(createVideo, fetchedVideo, videoChannel, syncParam.thumbnail)
+
+    await syncVideoExternalAttributes(videoCreated, fetchedVideo, syncParam)
+
+    return { video: videoCreated, created: true, autoBlacklisted }
+  } catch (err) {
+    // Maybe a concurrent getOrCreateVideoAndAccountAndChannel call created this video
+    if (err.name === 'SequelizeUniqueConstraintError') {
+      const fallbackVideo = await fetchVideoByUrl(videoUrl, fetchType)
+      if (fallbackVideo) return { video: fallbackVideo, created: false }
+    }
 
-  return { video: videoCreated, created: true, autoBlacklisted }
+    throw err
+  }
 }
 
 async function updateVideoFromAP (options: {
@@ -507,7 +519,7 @@ function isAPVideoUrlObject (url: any): url is ActivityVideoUrlObject {
   const mimeTypes = Object.keys(MIMETYPES.VIDEO.MIMETYPE_EXT)
 
   const urlMediaType = url.mediaType
-  return mimeTypes.indexOf(urlMediaType) !== -1 && urlMediaType.startsWith('video/')
+  return mimeTypes.includes(urlMediaType) && urlMediaType.startsWith('video/')
 }
 
 function isAPStreamingPlaylistUrlObject (url: ActivityUrlObject): url is ActivityPlaylistUrlObject {
@@ -623,9 +635,11 @@ async function createVideo (videoObject: VideoTorrentObject, channel: MChannelAc
 }
 
 function videoActivityObjectToDBAttributes (videoChannel: MChannelId, videoObject: VideoTorrentObject, to: string[] = []) {
-  const privacy = to.indexOf(ACTIVITY_PUB.PUBLIC) !== -1 ? VideoPrivacy.PUBLIC : VideoPrivacy.UNLISTED
-  const duration = videoObject.duration.replace(/[^\d]+/, '')
+  const privacy = to.includes(ACTIVITY_PUB.PUBLIC)
+    ? VideoPrivacy.PUBLIC
+    : VideoPrivacy.UNLISTED
 
+  const duration = videoObject.duration.replace(/[^\d]+/, '')
   const language = videoObject.language?.identifier
 
   const category = videoObject.category
@@ -692,6 +706,14 @@ function videoFileActivityUrlToDBAttributes (
       throw new Error('Cannot parse magnet URI ' + magnet.href)
     }
 
+    // Fetch associated metadata url, if any
+    const metadata = urls.filter(isAPVideoFileMetadataObject)
+                         .find(u => {
+                           return u.height === fileUrl.height &&
+                             u.fps === fileUrl.fps &&
+                             u.rel.includes(fileUrl.mediaType)
+                         })
+
     const mediaType = fileUrl.mediaType
     const attribute = {
       extname: MIMETYPES.VIDEO.MIMETYPE_EXT[mediaType],
@@ -699,6 +721,7 @@ function videoFileActivityUrlToDBAttributes (
       resolution: fileUrl.height,
       size: fileUrl.size,
       fps: fileUrl.fps || -1,
+      metadataUrl: metadata?.href,
 
       // This is a video file owned by a video or by a streaming playlist
       videoId: (videoOrPlaylist as MStreamingPlaylist).playlistUrl ? null : videoOrPlaylist.id,