aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/lib/activitypub/videos.ts
diff options
context:
space:
mode:
Diffstat (limited to 'server/lib/activitypub/videos.ts')
-rw-r--r--server/lib/activitypub/videos.ts97
1 files changed, 91 insertions, 6 deletions
diff --git a/server/lib/activitypub/videos.ts b/server/lib/activitypub/videos.ts
index e1e523499..edd01234f 100644
--- a/server/lib/activitypub/videos.ts
+++ b/server/lib/activitypub/videos.ts
@@ -2,7 +2,14 @@ import * as Bluebird from 'bluebird'
2import * as sequelize from 'sequelize' 2import * as sequelize from 'sequelize'
3import * as magnetUtil from 'magnet-uri' 3import * as magnetUtil from 'magnet-uri'
4import * as request from 'request' 4import * as request from 'request'
5import { ActivityIconObject, ActivityUrlObject, ActivityVideoUrlObject, VideoState } from '../../../shared/index' 5import {
6 ActivityIconObject,
7 ActivityPlaylistSegmentHashesObject,
8 ActivityPlaylistUrlObject,
9 ActivityUrlObject,
10 ActivityVideoUrlObject,
11 VideoState
12} from '../../../shared/index'
6import { VideoTorrentObject } from '../../../shared/models/activitypub/objects' 13import { VideoTorrentObject } from '../../../shared/models/activitypub/objects'
7import { VideoPrivacy } from '../../../shared/models/videos' 14import { VideoPrivacy } from '../../../shared/models/videos'
8import { sanitizeAndCheckVideoTorrentObject } from '../../helpers/custom-validators/activitypub/videos' 15import { sanitizeAndCheckVideoTorrentObject } from '../../helpers/custom-validators/activitypub/videos'
@@ -30,6 +37,9 @@ import { AccountModel } from '../../models/account/account'
30import { fetchVideoByUrl, VideoFetchByUrlType } from '../../helpers/video' 37import { fetchVideoByUrl, VideoFetchByUrlType } from '../../helpers/video'
31import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub' 38import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub'
32import { Notifier } from '../notifier' 39import { Notifier } from '../notifier'
40import { VideoStreamingPlaylistModel } from '../../models/video/video-streaming-playlist'
41import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type'
42import { FilteredModelAttributes } from 'sequelize-typescript/lib/models/Model'
33 43
34async function federateVideoIfNeeded (video: VideoModel, isNewVideo: boolean, transaction?: sequelize.Transaction) { 44async function federateVideoIfNeeded (video: VideoModel, isNewVideo: boolean, transaction?: sequelize.Transaction) {
35 // If the video is not private and published, we federate it 45 // If the video is not private and published, we federate it
@@ -264,6 +274,25 @@ async function updateVideoFromAP (options: {
264 } 274 }
265 275
266 { 276 {
277 const streamingPlaylistAttributes = streamingPlaylistActivityUrlToDBAttributes(options.video, options.videoObject)
278 const newStreamingPlaylists = streamingPlaylistAttributes.map(a => new VideoStreamingPlaylistModel(a))
279
280 // Remove video files that do not exist anymore
281 const destroyTasks = options.video.VideoStreamingPlaylists
282 .filter(f => !newStreamingPlaylists.find(newPlaylist => newPlaylist.hasSameUniqueKeysThan(f)))
283 .map(f => f.destroy(sequelizeOptions))
284 await Promise.all(destroyTasks)
285
286 // Update or add other one
287 const upsertTasks = streamingPlaylistAttributes.map(a => {
288 return VideoStreamingPlaylistModel.upsert<VideoStreamingPlaylistModel>(a, { returning: true, transaction: t })
289 .then(([ streamingPlaylist ]) => streamingPlaylist)
290 })
291
292 options.video.VideoStreamingPlaylists = await Promise.all(upsertTasks)
293 }
294
295 {
267 // Update Tags 296 // Update Tags
268 const tags = options.videoObject.tag.map(tag => tag.name) 297 const tags = options.videoObject.tag.map(tag => tag.name)
269 const tagInstances = await TagModel.findOrCreateTags(tags, t) 298 const tagInstances = await TagModel.findOrCreateTags(tags, t)
@@ -367,13 +396,25 @@ export {
367 396
368// --------------------------------------------------------------------------- 397// ---------------------------------------------------------------------------
369 398
370function isActivityVideoUrlObject (url: ActivityUrlObject): url is ActivityVideoUrlObject { 399function isAPVideoUrlObject (url: ActivityUrlObject): url is ActivityVideoUrlObject {
371 const mimeTypes = Object.keys(MIMETYPES.VIDEO.MIMETYPE_EXT) 400 const mimeTypes = Object.keys(MIMETYPES.VIDEO.MIMETYPE_EXT)
372 401
373 const urlMediaType = url.mediaType || url.mimeType 402 const urlMediaType = url.mediaType || url.mimeType
374 return mimeTypes.indexOf(urlMediaType) !== -1 && urlMediaType.startsWith('video/') 403 return mimeTypes.indexOf(urlMediaType) !== -1 && urlMediaType.startsWith('video/')
375} 404}
376 405
406function isAPStreamingPlaylistUrlObject (url: ActivityUrlObject): url is ActivityPlaylistUrlObject {
407 const urlMediaType = url.mediaType || url.mimeType
408
409 return urlMediaType === 'application/x-mpegURL'
410}
411
412function isAPPlaylistSegmentHashesUrlObject (tag: any): tag is ActivityPlaylistSegmentHashesObject {
413 const urlMediaType = tag.mediaType || tag.mimeType
414
415 return tag.name === 'sha256' && tag.type === 'Link' && urlMediaType === 'application/json'
416}
417
377async function createVideo (videoObject: VideoTorrentObject, channelActor: ActorModel, waitThumbnail = false) { 418async function createVideo (videoObject: VideoTorrentObject, channelActor: ActorModel, waitThumbnail = false) {
378 logger.debug('Adding remote video %s.', videoObject.id) 419 logger.debug('Adding remote video %s.', videoObject.id)
379 420
@@ -394,8 +435,14 @@ async function createVideo (videoObject: VideoTorrentObject, channelActor: Actor
394 const videoFilePromises = videoFileAttributes.map(f => VideoFileModel.create(f, { transaction: t })) 435 const videoFilePromises = videoFileAttributes.map(f => VideoFileModel.create(f, { transaction: t }))
395 await Promise.all(videoFilePromises) 436 await Promise.all(videoFilePromises)
396 437
438 const videoStreamingPlaylists = streamingPlaylistActivityUrlToDBAttributes(videoCreated, videoObject)
439 const playlistPromises = videoStreamingPlaylists.map(p => VideoStreamingPlaylistModel.create(p, { transaction: t }))
440 await Promise.all(playlistPromises)
441
397 // Process tags 442 // Process tags
398 const tags = videoObject.tag.map(t => t.name) 443 const tags = videoObject.tag
444 .filter(t => t.type === 'Hashtag')
445 .map(t => t.name)
399 const tagInstances = await TagModel.findOrCreateTags(tags, t) 446 const tagInstances = await TagModel.findOrCreateTags(tags, t)
400 await videoCreated.$set('Tags', tagInstances, sequelizeOptions) 447 await videoCreated.$set('Tags', tagInstances, sequelizeOptions)
401 448
@@ -473,13 +520,13 @@ async function videoActivityObjectToDBAttributes (
473} 520}
474 521
475function videoFileActivityUrlToDBAttributes (video: VideoModel, videoObject: VideoTorrentObject) { 522function videoFileActivityUrlToDBAttributes (video: VideoModel, videoObject: VideoTorrentObject) {
476 const fileUrls = videoObject.url.filter(u => isActivityVideoUrlObject(u)) as ActivityVideoUrlObject[] 523 const fileUrls = videoObject.url.filter(u => isAPVideoUrlObject(u)) as ActivityVideoUrlObject[]
477 524
478 if (fileUrls.length === 0) { 525 if (fileUrls.length === 0) {
479 throw new Error('Cannot find video files for ' + video.url) 526 throw new Error('Cannot find video files for ' + video.url)
480 } 527 }
481 528
482 const attributes: VideoFileModel[] = [] 529 const attributes: FilteredModelAttributes<VideoFileModel>[] = []
483 for (const fileUrl of fileUrls) { 530 for (const fileUrl of fileUrls) {
484 // Fetch associated magnet uri 531 // Fetch associated magnet uri
485 const magnet = videoObject.url.find(u => { 532 const magnet = videoObject.url.find(u => {
@@ -502,7 +549,45 @@ function videoFileActivityUrlToDBAttributes (video: VideoModel, videoObject: Vid
502 size: fileUrl.size, 549 size: fileUrl.size,
503 videoId: video.id, 550 videoId: video.id,
504 fps: fileUrl.fps || -1 551 fps: fileUrl.fps || -1
505 } as VideoFileModel 552 }
553
554 attributes.push(attribute)
555 }
556
557 return attributes
558}
559
560function streamingPlaylistActivityUrlToDBAttributes (video: VideoModel, videoObject: VideoTorrentObject) {
561 const playlistUrls = videoObject.url.filter(u => isAPStreamingPlaylistUrlObject(u)) as ActivityPlaylistUrlObject[]
562 if (playlistUrls.length === 0) return []
563
564 const attributes: FilteredModelAttributes<VideoStreamingPlaylistModel>[] = []
565 for (const playlistUrlObject of playlistUrls) {
566 const p2pMediaLoaderInfohashes = playlistUrlObject.tag
567 .filter(t => t.type === 'Infohash')
568 .map(t => t.name)
569 if (p2pMediaLoaderInfohashes.length === 0) {
570 logger.warn('No infohashes found in AP playlist object.', { playlistUrl: playlistUrlObject })
571 continue
572 }
573
574 const segmentsSha256UrlObject = playlistUrlObject.tag
575 .find(t => {
576 return isAPPlaylistSegmentHashesUrlObject(t)
577 }) as ActivityPlaylistSegmentHashesObject
578 if (!segmentsSha256UrlObject) {
579 logger.warn('No segment sha256 URL found in AP playlist object.', { playlistUrl: playlistUrlObject })
580 continue
581 }
582
583 const attribute = {
584 type: VideoStreamingPlaylistType.HLS,
585 playlistUrl: playlistUrlObject.href,
586 segmentsSha256Url: segmentsSha256UrlObject.href,
587 p2pMediaLoaderInfohashes,
588 videoId: video.id
589 }
590
506 attributes.push(attribute) 591 attributes.push(attribute)
507 } 592 }
508 593