diff options
Diffstat (limited to 'server/lib')
-rw-r--r-- | server/lib/activitypub/videos.ts | 59 | ||||
-rw-r--r-- | server/lib/schedulers/videos-redundancy-scheduler.ts | 5 | ||||
-rw-r--r-- | server/lib/thumbnail.ts | 16 |
3 files changed, 68 insertions, 12 deletions
diff --git a/server/lib/activitypub/videos.ts b/server/lib/activitypub/videos.ts index a5f6537eb..66330a964 100644 --- a/server/lib/activitypub/videos.ts +++ b/server/lib/activitypub/videos.ts | |||
@@ -3,7 +3,8 @@ import { maxBy, minBy } from 'lodash' | |||
3 | import * as magnetUtil from 'magnet-uri' | 3 | import * as magnetUtil from 'magnet-uri' |
4 | import { basename, join } from 'path' | 4 | import { basename, join } from 'path' |
5 | import * as request from 'request' | 5 | import * as request from 'request' |
6 | import * as sequelize from 'sequelize' | 6 | import { Transaction } from 'sequelize/types' |
7 | import { TrackerModel } from '@server/models/server/tracker' | ||
7 | import { VideoLiveModel } from '@server/models/video/video-live' | 8 | import { VideoLiveModel } from '@server/models/video/video-live' |
8 | import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' | 9 | import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' |
9 | import { | 10 | import { |
@@ -16,12 +17,16 @@ import { | |||
16 | ActivityUrlObject, | 17 | ActivityUrlObject, |
17 | ActivityVideoUrlObject | 18 | ActivityVideoUrlObject |
18 | } from '../../../shared/index' | 19 | } from '../../../shared/index' |
19 | import { ActivityIconObject, VideoObject } from '../../../shared/models/activitypub/objects' | 20 | import { ActivityIconObject, ActivityTrackerUrlObject, VideoObject } from '../../../shared/models/activitypub/objects' |
20 | import { VideoPrivacy } from '../../../shared/models/videos' | 21 | import { VideoPrivacy } from '../../../shared/models/videos' |
21 | import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type' | 22 | import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type' |
22 | import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type' | 23 | import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type' |
23 | import { buildRemoteVideoBaseUrl, checkUrlsSameHost, getAPId } from '../../helpers/activitypub' | 24 | import { buildRemoteVideoBaseUrl, checkUrlsSameHost, getAPId } from '../../helpers/activitypub' |
24 | import { isAPVideoFileMetadataObject, sanitizeAndCheckVideoTorrentObject } from '../../helpers/custom-validators/activitypub/videos' | 25 | import { |
26 | isAPVideoFileUrlMetadataObject, | ||
27 | isAPVideoTrackerUrlObject, | ||
28 | sanitizeAndCheckVideoTorrentObject | ||
29 | } from '../../helpers/custom-validators/activitypub/videos' | ||
25 | import { isArray } from '../../helpers/custom-validators/misc' | 30 | import { isArray } from '../../helpers/custom-validators/misc' |
26 | import { isVideoFileInfoHashValid } from '../../helpers/custom-validators/videos' | 31 | import { isVideoFileInfoHashValid } from '../../helpers/custom-validators/videos' |
27 | import { deleteNonExistingModels, resetSequelizeInstance, retryTransactionWrapper } from '../../helpers/database-utils' | 32 | import { deleteNonExistingModels, resetSequelizeInstance, retryTransactionWrapper } from '../../helpers/database-utils' |
@@ -83,7 +88,7 @@ import { addVideoShares, shareVideoByServerAndChannel } from './share' | |||
83 | import { addVideoComments } from './video-comments' | 88 | import { addVideoComments } from './video-comments' |
84 | import { createRates } from './video-rates' | 89 | import { createRates } from './video-rates' |
85 | 90 | ||
86 | async function federateVideoIfNeeded (videoArg: MVideoAPWithoutCaption, isNewVideo: boolean, transaction?: sequelize.Transaction) { | 91 | async function federateVideoIfNeeded (videoArg: MVideoAPWithoutCaption, isNewVideo: boolean, transaction?: Transaction) { |
87 | const video = videoArg as MVideoAP | 92 | const video = videoArg as MVideoAP |
88 | 93 | ||
89 | if ( | 94 | if ( |
@@ -433,6 +438,12 @@ async function updateVideoFromAP (options: { | |||
433 | await setVideoTags({ video: videoUpdated, tags, transaction: t, defaultValue: videoUpdated.Tags }) | 438 | await setVideoTags({ video: videoUpdated, tags, transaction: t, defaultValue: videoUpdated.Tags }) |
434 | } | 439 | } |
435 | 440 | ||
441 | // Update trackers | ||
442 | { | ||
443 | const trackers = getTrackerUrls(videoObject, videoUpdated) | ||
444 | await setVideoTrackers({ video: videoUpdated, trackers, transaction: t }) | ||
445 | } | ||
446 | |||
436 | { | 447 | { |
437 | // Update captions | 448 | // Update captions |
438 | await VideoCaptionModel.deleteAllCaptionsOfRemoteVideo(videoUpdated.id, t) | 449 | await VideoCaptionModel.deleteAllCaptionsOfRemoteVideo(videoUpdated.id, t) |
@@ -577,7 +588,7 @@ function isAPVideoUrlObject (url: any): url is ActivityVideoUrlObject { | |||
577 | return MIMETYPES.VIDEO.MIMETYPE_EXT[urlMediaType] && urlMediaType.startsWith('video/') | 588 | return MIMETYPES.VIDEO.MIMETYPE_EXT[urlMediaType] && urlMediaType.startsWith('video/') |
578 | } | 589 | } |
579 | 590 | ||
580 | function isAPStreamingPlaylistUrlObject (url: ActivityUrlObject): url is ActivityPlaylistUrlObject { | 591 | function isAPStreamingPlaylistUrlObject (url: any): url is ActivityPlaylistUrlObject { |
581 | return url && url.mediaType === 'application/x-mpegURL' | 592 | return url && url.mediaType === 'application/x-mpegURL' |
582 | } | 593 | } |
583 | 594 | ||
@@ -671,6 +682,12 @@ async function createVideo (videoObject: VideoObject, channel: MChannelAccountLi | |||
671 | }) | 682 | }) |
672 | await Promise.all(videoCaptionsPromises) | 683 | await Promise.all(videoCaptionsPromises) |
673 | 684 | ||
685 | // Process trackers | ||
686 | { | ||
687 | const trackers = getTrackerUrls(videoObject, videoCreated) | ||
688 | await setVideoTrackers({ video: videoCreated, trackers, transaction: t }) | ||
689 | } | ||
690 | |||
674 | videoCreated.VideoFiles = videoFiles | 691 | videoCreated.VideoFiles = videoFiles |
675 | 692 | ||
676 | if (videoCreated.isLive) { | 693 | if (videoCreated.isLive) { |
@@ -797,7 +814,7 @@ function videoFileActivityUrlToDBAttributes ( | |||
797 | : parsed.xs | 814 | : parsed.xs |
798 | 815 | ||
799 | // Fetch associated metadata url, if any | 816 | // Fetch associated metadata url, if any |
800 | const metadata = urls.filter(isAPVideoFileMetadataObject) | 817 | const metadata = urls.filter(isAPVideoFileUrlMetadataObject) |
801 | .find(u => { | 818 | .find(u => { |
802 | return u.height === fileUrl.height && | 819 | return u.height === fileUrl.height && |
803 | u.fps === fileUrl.fps && | 820 | u.fps === fileUrl.fps && |
@@ -889,3 +906,33 @@ function getPreviewUrl (previewIcon: ActivityIconObject, video: MVideoWithHost) | |||
889 | ? previewIcon.url | 906 | ? previewIcon.url |
890 | : buildRemoteVideoBaseUrl(video, join(LAZY_STATIC_PATHS.PREVIEWS, video.generatePreviewName())) | 907 | : buildRemoteVideoBaseUrl(video, join(LAZY_STATIC_PATHS.PREVIEWS, video.generatePreviewName())) |
891 | } | 908 | } |
909 | |||
910 | function getTrackerUrls (object: VideoObject, video: MVideoWithHost) { | ||
911 | let wsFound = false | ||
912 | |||
913 | const trackers = object.url.filter(u => isAPVideoTrackerUrlObject(u)) | ||
914 | .map((u: ActivityTrackerUrlObject) => { | ||
915 | if (u.rel.includes('websocket')) wsFound = true | ||
916 | |||
917 | return u.href | ||
918 | }) | ||
919 | |||
920 | if (wsFound) return trackers | ||
921 | |||
922 | return [ | ||
923 | buildRemoteVideoBaseUrl(video, '/tracker/socket', REMOTE_SCHEME.WS), | ||
924 | buildRemoteVideoBaseUrl(video, '/tracker/announce') | ||
925 | ] | ||
926 | } | ||
927 | |||
928 | async function setVideoTrackers (options: { | ||
929 | video: MVideo | ||
930 | trackers: string[] | ||
931 | transaction?: Transaction | ||
932 | }) { | ||
933 | const { video, trackers, transaction } = options | ||
934 | |||
935 | const trackerInstances = await TrackerModel.findOrCreateTrackers(trackers, transaction) | ||
936 | |||
937 | await video.$set('Trackers', trackerInstances, { transaction }) | ||
938 | } | ||
diff --git a/server/lib/schedulers/videos-redundancy-scheduler.ts b/server/lib/schedulers/videos-redundancy-scheduler.ts index 60008e695..9e2667416 100644 --- a/server/lib/schedulers/videos-redundancy-scheduler.ts +++ b/server/lib/schedulers/videos-redundancy-scheduler.ts | |||
@@ -1,6 +1,7 @@ | |||
1 | import { move } from 'fs-extra' | 1 | import { move } from 'fs-extra' |
2 | import { join } from 'path' | 2 | import { join } from 'path' |
3 | import { getServerActor } from '@server/models/application/application' | 3 | import { getServerActor } from '@server/models/application/application' |
4 | import { TrackerModel } from '@server/models/server/tracker' | ||
4 | import { VideoModel } from '@server/models/video/video' | 5 | import { VideoModel } from '@server/models/video/video' |
5 | import { | 6 | import { |
6 | MStreamingPlaylist, | 7 | MStreamingPlaylist, |
@@ -221,8 +222,8 @@ export class VideosRedundancyScheduler extends AbstractScheduler { | |||
221 | 222 | ||
222 | logger.info('Duplicating %s - %d in videos redundancy with "%s" strategy.', video.url, file.resolution, strategy) | 223 | logger.info('Duplicating %s - %d in videos redundancy with "%s" strategy.', video.url, file.resolution, strategy) |
223 | 224 | ||
224 | const { baseUrlHttp, baseUrlWs } = video.getBaseUrls() | 225 | const trackerUrls = await TrackerModel.listUrlsByVideoId(video.id) |
225 | const magnetUri = generateMagnetUri(video, video, file, baseUrlHttp, baseUrlWs) | 226 | const magnetUri = generateMagnetUri(video, file, trackerUrls) |
226 | 227 | ||
227 | const tmpPath = await downloadWebTorrentVideo({ magnetUri }, VIDEO_IMPORT_TIMEOUT) | 228 | const tmpPath = await downloadWebTorrentVideo({ magnetUri }, VIDEO_IMPORT_TIMEOUT) |
228 | 229 | ||
diff --git a/server/lib/thumbnail.ts b/server/lib/thumbnail.ts index 4bad8d6ca..49317df28 100644 --- a/server/lib/thumbnail.ts +++ b/server/lib/thumbnail.ts | |||
@@ -1,5 +1,6 @@ | |||
1 | import { copy } from 'fs-extra' | 1 | import { copy } from 'fs-extra' |
2 | import { join } from 'path' | 2 | import { join } from 'path' |
3 | import { logger } from '@server/helpers/logger' | ||
3 | import { ThumbnailType } from '../../shared/models/videos/thumbnail.type' | 4 | import { ThumbnailType } from '../../shared/models/videos/thumbnail.type' |
4 | import { generateImageFromVideoFile } from '../helpers/ffmpeg-utils' | 5 | import { generateImageFromVideoFile } from '../helpers/ffmpeg-utils' |
5 | import { processImage } from '../helpers/image-utils' | 6 | import { processImage } from '../helpers/image-utils' |
@@ -62,7 +63,7 @@ function createVideoMiniatureFromUrl (options: { | |||
62 | size?: ImageSize | 63 | size?: ImageSize |
63 | }) { | 64 | }) { |
64 | const { downloadUrl, video, type, size } = options | 65 | const { downloadUrl, video, type, size } = options |
65 | const { filename, basePath, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size) | 66 | const { filename: updatedFilename, basePath, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size) |
66 | 67 | ||
67 | // Only save the file URL if it is a remote video | 68 | // Only save the file URL if it is a remote video |
68 | const fileUrl = video.isOwned() | 69 | const fileUrl = video.isOwned() |
@@ -76,10 +77,16 @@ function createVideoMiniatureFromUrl (options: { | |||
76 | 77 | ||
77 | // If the thumbnail URL did not change and has a unique filename (introduced in 3.2), avoid thumbnail processing | 78 | // If the thumbnail URL did not change and has a unique filename (introduced in 3.2), avoid thumbnail processing |
78 | const thumbnailUrlChanged = !existingUrl || existingUrl !== downloadUrl || downloadUrl.endsWith(`${video.uuid}.jpg`) | 79 | const thumbnailUrlChanged = !existingUrl || existingUrl !== downloadUrl || downloadUrl.endsWith(`${video.uuid}.jpg`) |
80 | |||
81 | // Do not change the thumbnail filename if the file did not change | ||
82 | const filename = thumbnailUrlChanged | ||
83 | ? updatedFilename | ||
84 | : existingThumbnail.filename | ||
85 | |||
79 | const thumbnailCreator = () => { | 86 | const thumbnailCreator = () => { |
80 | if (thumbnailUrlChanged) return downloadImage(downloadUrl, basePath, filename, { width, height }) | 87 | if (thumbnailUrlChanged) return downloadImage(downloadUrl, basePath, filename, { width, height }) |
81 | 88 | ||
82 | return copy(existingThumbnail.getPath(), ThumbnailModel.buildPath(type, filename)) | 89 | return Promise.resolve() |
83 | } | 90 | } |
84 | 91 | ||
85 | return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail, fileUrl }) | 92 | return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail, fileUrl }) |
@@ -236,7 +243,7 @@ async function createThumbnailFromFunction (parameters: { | |||
236 | fileUrl = null | 243 | fileUrl = null |
237 | } = parameters | 244 | } = parameters |
238 | 245 | ||
239 | const oldFilename = existingThumbnail | 246 | const oldFilename = existingThumbnail && existingThumbnail.filename !== filename |
240 | ? existingThumbnail.filename | 247 | ? existingThumbnail.filename |
241 | : undefined | 248 | : undefined |
242 | 249 | ||
@@ -248,7 +255,8 @@ async function createThumbnailFromFunction (parameters: { | |||
248 | thumbnail.type = type | 255 | thumbnail.type = type |
249 | thumbnail.fileUrl = fileUrl | 256 | thumbnail.fileUrl = fileUrl |
250 | thumbnail.automaticallyGenerated = automaticallyGenerated | 257 | thumbnail.automaticallyGenerated = automaticallyGenerated |
251 | thumbnail.previousThumbnailFilename = oldFilename | 258 | |
259 | if (oldFilename) thumbnail.previousThumbnailFilename = oldFilename | ||
252 | 260 | ||
253 | await thumbnailCreator() | 261 | await thumbnailCreator() |
254 | 262 | ||