diff options
author | Chocobozzz <me@florianbigard.com> | 2018-06-12 20:04:58 +0200 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2018-06-12 20:37:51 +0200 |
commit | 2186386cca113506791583cb07d6ccacba7af4e0 (patch) | |
tree | 3c214c0b5fbd64332624267fa6e51fd4a9cf6474 /server | |
parent | 6ccdf3a23ecec5ba2eeaf487fd1fafdc7606b4bf (diff) | |
download | PeerTube-2186386cca113506791583cb07d6ccacba7af4e0.tar.gz PeerTube-2186386cca113506791583cb07d6ccacba7af4e0.tar.zst PeerTube-2186386cca113506791583cb07d6ccacba7af4e0.zip |
Add concept of video state, and add ability to wait transcoding before
publishing a video
Diffstat (limited to 'server')
32 files changed, 523 insertions, 359 deletions
diff --git a/server/controllers/activitypub/client.ts b/server/controllers/activitypub/client.ts index 1c780783c..ea8e25f68 100644 --- a/server/controllers/activitypub/client.ts +++ b/server/controllers/activitypub/client.ts | |||
@@ -123,11 +123,11 @@ async function accountFollowingController (req: express.Request, res: express.Re | |||
123 | async function videoController (req: express.Request, res: express.Response, next: express.NextFunction) { | 123 | async function videoController (req: express.Request, res: express.Response, next: express.NextFunction) { |
124 | const video: VideoModel = res.locals.video | 124 | const video: VideoModel = res.locals.video |
125 | 125 | ||
126 | const audience = await getAudience(video.VideoChannel.Account.Actor, undefined, video.privacy === VideoPrivacy.PUBLIC) | 126 | const audience = getAudience(video.VideoChannel.Account.Actor, video.privacy === VideoPrivacy.PUBLIC) |
127 | const videoObject = audiencify(video.toActivityPubObject(), audience) | 127 | const videoObject = audiencify(video.toActivityPubObject(), audience) |
128 | 128 | ||
129 | if (req.path.endsWith('/activity')) { | 129 | if (req.path.endsWith('/activity')) { |
130 | const data = await createActivityData(video.url, video.VideoChannel.Account.Actor, videoObject, undefined, audience) | 130 | const data = createActivityData(video.url, video.VideoChannel.Account.Actor, videoObject, audience) |
131 | return activityPubResponse(activityPubContextify(data), res) | 131 | return activityPubResponse(activityPubContextify(data), res) |
132 | } | 132 | } |
133 | 133 | ||
@@ -210,12 +210,12 @@ async function videoCommentController (req: express.Request, res: express.Respon | |||
210 | 210 | ||
211 | const threadParentComments = await VideoCommentModel.listThreadParentComments(videoComment, undefined) | 211 | const threadParentComments = await VideoCommentModel.listThreadParentComments(videoComment, undefined) |
212 | const isPublic = true // Comments are always public | 212 | const isPublic = true // Comments are always public |
213 | const audience = await getAudience(videoComment.Account.Actor, undefined, isPublic) | 213 | const audience = getAudience(videoComment.Account.Actor, isPublic) |
214 | 214 | ||
215 | const videoCommentObject = audiencify(videoComment.toActivityPubObject(threadParentComments), audience) | 215 | const videoCommentObject = audiencify(videoComment.toActivityPubObject(threadParentComments), audience) |
216 | 216 | ||
217 | if (req.path.endsWith('/activity')) { | 217 | if (req.path.endsWith('/activity')) { |
218 | const data = await createActivityData(videoComment.url, videoComment.Account.Actor, videoCommentObject, undefined, audience) | 218 | const data = createActivityData(videoComment.url, videoComment.Account.Actor, videoCommentObject, audience) |
219 | return activityPubResponse(activityPubContextify(data), res) | 219 | return activityPubResponse(activityPubContextify(data), res) |
220 | } | 220 | } |
221 | 221 | ||
diff --git a/server/controllers/activitypub/outbox.ts b/server/controllers/activitypub/outbox.ts index 2793ae267..ae7adcd4c 100644 --- a/server/controllers/activitypub/outbox.ts +++ b/server/controllers/activitypub/outbox.ts | |||
@@ -54,12 +54,12 @@ async function buildActivities (actor: ActorModel, start: number, count: number) | |||
54 | // This is a shared video | 54 | // This is a shared video |
55 | if (video.VideoShares !== undefined && video.VideoShares.length !== 0) { | 55 | if (video.VideoShares !== undefined && video.VideoShares.length !== 0) { |
56 | const videoShare = video.VideoShares[0] | 56 | const videoShare = video.VideoShares[0] |
57 | const announceActivity = await announceActivityData(videoShare.url, actor, video.url, undefined, createActivityAudience) | 57 | const announceActivity = announceActivityData(videoShare.url, actor, video.url, createActivityAudience) |
58 | 58 | ||
59 | activities.push(announceActivity) | 59 | activities.push(announceActivity) |
60 | } else { | 60 | } else { |
61 | const videoObject = video.toActivityPubObject() | 61 | const videoObject = video.toActivityPubObject() |
62 | const createActivity = await createActivityData(video.url, byActor, videoObject, undefined, createActivityAudience) | 62 | const createActivity = createActivityData(video.url, byActor, videoObject, createActivityAudience) |
63 | 63 | ||
64 | activities.push(createActivity) | 64 | activities.push(createActivity) |
65 | } | 65 | } |
diff --git a/server/controllers/api/users.ts b/server/controllers/api/users.ts index 8dff4b87c..2b40c44d9 100644 --- a/server/controllers/api/users.ts +++ b/server/controllers/api/users.ts | |||
@@ -166,7 +166,7 @@ export { | |||
166 | 166 | ||
167 | async function getUserVideos (req: express.Request, res: express.Response, next: express.NextFunction) { | 167 | async function getUserVideos (req: express.Request, res: express.Response, next: express.NextFunction) { |
168 | const user = res.locals.oauth.token.User as UserModel | 168 | const user = res.locals.oauth.token.User as UserModel |
169 | const resultList = await VideoModel.listAccountVideosForApi( | 169 | const resultList = await VideoModel.listUserVideosForApi( |
170 | user.Account.id, | 170 | user.Account.id, |
171 | req.query.start as number, | 171 | req.query.start as number, |
172 | req.query.count as number, | 172 | req.query.count as number, |
@@ -174,7 +174,8 @@ async function getUserVideos (req: express.Request, res: express.Response, next: | |||
174 | false // Display my NSFW videos | 174 | false // Display my NSFW videos |
175 | ) | 175 | ) |
176 | 176 | ||
177 | return res.json(getFormattedObjects(resultList.data, resultList.total)) | 177 | const additionalAttributes = { waitTranscoding: true, state: true } |
178 | return res.json(getFormattedObjects(resultList.data, resultList.total, { additionalAttributes })) | ||
178 | } | 179 | } |
179 | 180 | ||
180 | async function createUserRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) { | 181 | async function createUserRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) { |
@@ -318,7 +319,7 @@ async function updateMe (req: express.Request, res: express.Response, next: expr | |||
318 | } | 319 | } |
319 | 320 | ||
320 | async function updateMyAvatar (req: express.Request, res: express.Response, next: express.NextFunction) { | 321 | async function updateMyAvatar (req: express.Request, res: express.Response, next: express.NextFunction) { |
321 | const avatarPhysicalFile = req.files['avatarfile'][0] | 322 | const avatarPhysicalFile = req.files[ 'avatarfile' ][ 0 ] |
322 | const user = res.locals.oauth.token.user | 323 | const user = res.locals.oauth.token.user |
323 | const actor = user.Account.Actor | 324 | const actor = user.Account.Actor |
324 | 325 | ||
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts index 7f5e74626..9d9b2b0e1 100644 --- a/server/controllers/api/videos/index.ts +++ b/server/controllers/api/videos/index.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { extname, join } from 'path' | 2 | import { extname, join } from 'path' |
3 | import { VideoCreate, VideoPrivacy, VideoUpdate } from '../../../../shared' | 3 | import { VideoCreate, VideoPrivacy, VideoState, VideoUpdate } from '../../../../shared' |
4 | import { renamePromise } from '../../../helpers/core-utils' | 4 | import { renamePromise } from '../../../helpers/core-utils' |
5 | import { retryTransactionWrapper } from '../../../helpers/database-utils' | 5 | import { retryTransactionWrapper } from '../../../helpers/database-utils' |
6 | import { getVideoFileResolution } from '../../../helpers/ffmpeg-utils' | 6 | import { getVideoFileResolution } from '../../../helpers/ffmpeg-utils' |
@@ -21,11 +21,11 @@ import { | |||
21 | } from '../../../initializers' | 21 | } from '../../../initializers' |
22 | import { | 22 | import { |
23 | changeVideoChannelShare, | 23 | changeVideoChannelShare, |
24 | federateVideoIfNeeded, | ||
24 | fetchRemoteVideoDescription, | 25 | fetchRemoteVideoDescription, |
25 | getVideoActivityPubUrl, | 26 | getVideoActivityPubUrl |
26 | shareVideoByServerAndChannel | ||
27 | } from '../../../lib/activitypub' | 27 | } from '../../../lib/activitypub' |
28 | import { sendCreateVideo, sendCreateView, sendUpdateVideo } from '../../../lib/activitypub/send' | 28 | import { sendCreateView } from '../../../lib/activitypub/send' |
29 | import { JobQueue } from '../../../lib/job-queue' | 29 | import { JobQueue } from '../../../lib/job-queue' |
30 | import { Redis } from '../../../lib/redis' | 30 | import { Redis } from '../../../lib/redis' |
31 | import { | 31 | import { |
@@ -51,7 +51,7 @@ import { videoCommentRouter } from './comment' | |||
51 | import { rateVideoRouter } from './rate' | 51 | import { rateVideoRouter } from './rate' |
52 | import { VideoFilter } from '../../../../shared/models/videos/video-query.type' | 52 | import { VideoFilter } from '../../../../shared/models/videos/video-query.type' |
53 | import { VideoSortField } from '../../../../client/src/app/shared/video/sort-field.type' | 53 | import { VideoSortField } from '../../../../client/src/app/shared/video/sort-field.type' |
54 | import { isNSFWHidden, createReqFiles } from '../../../helpers/express-utils' | 54 | import { createReqFiles, isNSFWHidden } from '../../../helpers/express-utils' |
55 | 55 | ||
56 | const videosRouter = express.Router() | 56 | const videosRouter = express.Router() |
57 | 57 | ||
@@ -185,8 +185,10 @@ async function addVideo (req: express.Request, res: express.Response, videoPhysi | |||
185 | category: videoInfo.category, | 185 | category: videoInfo.category, |
186 | licence: videoInfo.licence, | 186 | licence: videoInfo.licence, |
187 | language: videoInfo.language, | 187 | language: videoInfo.language, |
188 | commentsEnabled: videoInfo.commentsEnabled, | 188 | commentsEnabled: videoInfo.commentsEnabled || false, |
189 | nsfw: videoInfo.nsfw, | 189 | waitTranscoding: videoInfo.waitTranscoding || false, |
190 | state: CONFIG.TRANSCODING.ENABLED ? VideoState.TO_TRANSCODE : VideoState.PUBLISHED, | ||
191 | nsfw: videoInfo.nsfw || false, | ||
190 | description: videoInfo.description, | 192 | description: videoInfo.description, |
191 | support: videoInfo.support, | 193 | support: videoInfo.support, |
192 | privacy: videoInfo.privacy, | 194 | privacy: videoInfo.privacy, |
@@ -194,19 +196,20 @@ async function addVideo (req: express.Request, res: express.Response, videoPhysi | |||
194 | channelId: res.locals.videoChannel.id | 196 | channelId: res.locals.videoChannel.id |
195 | } | 197 | } |
196 | const video = new VideoModel(videoData) | 198 | const video = new VideoModel(videoData) |
197 | video.url = getVideoActivityPubUrl(video) | 199 | video.url = getVideoActivityPubUrl(video) // We use the UUID, so set the URL after building the object |
198 | 200 | ||
201 | // Build the file object | ||
199 | const { videoFileResolution } = await getVideoFileResolution(videoPhysicalFile.path) | 202 | const { videoFileResolution } = await getVideoFileResolution(videoPhysicalFile.path) |
200 | |||
201 | const videoFileData = { | 203 | const videoFileData = { |
202 | extname: extname(videoPhysicalFile.filename), | 204 | extname: extname(videoPhysicalFile.filename), |
203 | resolution: videoFileResolution, | 205 | resolution: videoFileResolution, |
204 | size: videoPhysicalFile.size | 206 | size: videoPhysicalFile.size |
205 | } | 207 | } |
206 | const videoFile = new VideoFileModel(videoFileData) | 208 | const videoFile = new VideoFileModel(videoFileData) |
209 | |||
210 | // Move physical file | ||
207 | const videoDir = CONFIG.STORAGE.VIDEOS_DIR | 211 | const videoDir = CONFIG.STORAGE.VIDEOS_DIR |
208 | const destination = join(videoDir, video.getVideoFilename(videoFile)) | 212 | const destination = join(videoDir, video.getVideoFilename(videoFile)) |
209 | |||
210 | await renamePromise(videoPhysicalFile.path, destination) | 213 | await renamePromise(videoPhysicalFile.path, destination) |
211 | // This is important in case if there is another attempt in the retry process | 214 | // This is important in case if there is another attempt in the retry process |
212 | videoPhysicalFile.filename = video.getVideoFilename(videoFile) | 215 | videoPhysicalFile.filename = video.getVideoFilename(videoFile) |
@@ -230,6 +233,7 @@ async function addVideo (req: express.Request, res: express.Response, videoPhysi | |||
230 | await video.createPreview(videoFile) | 233 | await video.createPreview(videoFile) |
231 | } | 234 | } |
232 | 235 | ||
236 | // Create the torrent file | ||
233 | await video.createTorrentAndSetInfoHash(videoFile) | 237 | await video.createTorrentAndSetInfoHash(videoFile) |
234 | 238 | ||
235 | const videoCreated = await sequelizeTypescript.transaction(async t => { | 239 | const videoCreated = await sequelizeTypescript.transaction(async t => { |
@@ -251,20 +255,14 @@ async function addVideo (req: express.Request, res: express.Response, videoPhysi | |||
251 | video.Tags = tagInstances | 255 | video.Tags = tagInstances |
252 | } | 256 | } |
253 | 257 | ||
254 | // Let transcoding job send the video to friends because the video file extension might change | 258 | await federateVideoIfNeeded(video, true, t) |
255 | if (CONFIG.TRANSCODING.ENABLED === true) return videoCreated | ||
256 | // Don't send video to remote servers, it is private | ||
257 | if (video.privacy === VideoPrivacy.PRIVATE) return videoCreated | ||
258 | |||
259 | await sendCreateVideo(video, t) | ||
260 | await shareVideoByServerAndChannel(video, t) | ||
261 | 259 | ||
262 | logger.info('Video with name %s and uuid %s created.', videoInfo.name, videoCreated.uuid) | 260 | logger.info('Video with name %s and uuid %s created.', videoInfo.name, videoCreated.uuid) |
263 | 261 | ||
264 | return videoCreated | 262 | return videoCreated |
265 | }) | 263 | }) |
266 | 264 | ||
267 | if (CONFIG.TRANSCODING.ENABLED === true) { | 265 | if (video.state === VideoState.TO_TRANSCODE) { |
268 | // Put uuid because we don't have id auto incremented for now | 266 | // Put uuid because we don't have id auto incremented for now |
269 | const dataInput = { | 267 | const dataInput = { |
270 | videoUUID: videoCreated.uuid, | 268 | videoUUID: videoCreated.uuid, |
@@ -318,6 +316,7 @@ async function updateVideo (req: express.Request, res: express.Response) { | |||
318 | if (videoInfoToUpdate.licence !== undefined) videoInstance.set('licence', videoInfoToUpdate.licence) | 316 | if (videoInfoToUpdate.licence !== undefined) videoInstance.set('licence', videoInfoToUpdate.licence) |
319 | if (videoInfoToUpdate.language !== undefined) videoInstance.set('language', videoInfoToUpdate.language) | 317 | if (videoInfoToUpdate.language !== undefined) videoInstance.set('language', videoInfoToUpdate.language) |
320 | if (videoInfoToUpdate.nsfw !== undefined) videoInstance.set('nsfw', videoInfoToUpdate.nsfw) | 318 | if (videoInfoToUpdate.nsfw !== undefined) videoInstance.set('nsfw', videoInfoToUpdate.nsfw) |
319 | if (videoInfoToUpdate.waitTranscoding !== undefined) videoInstance.set('waitTranscoding', videoInfoToUpdate.waitTranscoding) | ||
321 | if (videoInfoToUpdate.support !== undefined) videoInstance.set('support', videoInfoToUpdate.support) | 320 | if (videoInfoToUpdate.support !== undefined) videoInstance.set('support', videoInfoToUpdate.support) |
322 | if (videoInfoToUpdate.description !== undefined) videoInstance.set('description', videoInfoToUpdate.description) | 321 | if (videoInfoToUpdate.description !== undefined) videoInstance.set('description', videoInfoToUpdate.description) |
323 | if (videoInfoToUpdate.commentsEnabled !== undefined) videoInstance.set('commentsEnabled', videoInfoToUpdate.commentsEnabled) | 322 | if (videoInfoToUpdate.commentsEnabled !== undefined) videoInstance.set('commentsEnabled', videoInfoToUpdate.commentsEnabled) |
@@ -343,19 +342,13 @@ async function updateVideo (req: express.Request, res: express.Response) { | |||
343 | // Video channel update? | 342 | // Video channel update? |
344 | if (res.locals.videoChannel && videoInstanceUpdated.channelId !== res.locals.videoChannel.id) { | 343 | if (res.locals.videoChannel && videoInstanceUpdated.channelId !== res.locals.videoChannel.id) { |
345 | await videoInstanceUpdated.$set('VideoChannel', res.locals.videoChannel, { transaction: t }) | 344 | await videoInstanceUpdated.$set('VideoChannel', res.locals.videoChannel, { transaction: t }) |
346 | videoInstance.VideoChannel = res.locals.videoChannel | 345 | videoInstanceUpdated.VideoChannel = res.locals.videoChannel |
347 | 346 | ||
348 | if (wasPrivateVideo === false) await changeVideoChannelShare(videoInstanceUpdated, oldVideoChannel, t) | 347 | if (wasPrivateVideo === false) await changeVideoChannelShare(videoInstanceUpdated, oldVideoChannel, t) |
349 | } | 348 | } |
350 | 349 | ||
351 | // Now we'll update the video's meta data to our friends | 350 | const isNewVideo = wasPrivateVideo && videoInstanceUpdated.privacy !== VideoPrivacy.PRIVATE |
352 | if (wasPrivateVideo === false) await sendUpdateVideo(videoInstanceUpdated, t) | 351 | await federateVideoIfNeeded(videoInstanceUpdated, isNewVideo) |
353 | |||
354 | // Video is not private anymore, send a create action to remote servers | ||
355 | if (wasPrivateVideo === true && videoInstanceUpdated.privacy !== VideoPrivacy.PRIVATE) { | ||
356 | await sendCreateVideo(videoInstanceUpdated, t) | ||
357 | await shareVideoByServerAndChannel(videoInstanceUpdated, t) | ||
358 | } | ||
359 | }) | 352 | }) |
360 | 353 | ||
361 | logger.info('Video with name %s and uuid %s updated.', videoInstance.name, videoInstance.uuid) | 354 | logger.info('Video with name %s and uuid %s updated.', videoInstance.name, videoInstance.uuid) |
diff --git a/server/helpers/activitypub.ts b/server/helpers/activitypub.ts index d1f3ec02d..37a251697 100644 --- a/server/helpers/activitypub.ts +++ b/server/helpers/activitypub.ts | |||
@@ -8,22 +8,24 @@ import { signObject } from './peertube-crypto' | |||
8 | import { pageToStartAndCount } from './core-utils' | 8 | import { pageToStartAndCount } from './core-utils' |
9 | 9 | ||
10 | function activityPubContextify <T> (data: T) { | 10 | function activityPubContextify <T> (data: T) { |
11 | return Object.assign(data,{ | 11 | return Object.assign(data, { |
12 | '@context': [ | 12 | '@context': [ |
13 | 'https://www.w3.org/ns/activitystreams', | 13 | 'https://www.w3.org/ns/activitystreams', |
14 | 'https://w3id.org/security/v1', | 14 | 'https://w3id.org/security/v1', |
15 | { | 15 | { |
16 | 'RsaSignature2017': 'https://w3id.org/security#RsaSignature2017', | 16 | RsaSignature2017: 'https://w3id.org/security#RsaSignature2017', |
17 | 'Hashtag': 'as:Hashtag', | 17 | Hashtag: 'as:Hashtag', |
18 | 'uuid': 'http://schema.org/identifier', | 18 | uuid: 'http://schema.org/identifier', |
19 | 'category': 'http://schema.org/category', | 19 | category: 'http://schema.org/category', |
20 | 'licence': 'http://schema.org/license', | 20 | licence: 'http://schema.org/license', |
21 | 'sensitive': 'as:sensitive', | 21 | sensitive: 'as:sensitive', |
22 | 'language': 'http://schema.org/inLanguage', | 22 | language: 'http://schema.org/inLanguage', |
23 | 'views': 'http://schema.org/Number', | 23 | views: 'http://schema.org/Number', |
24 | 'size': 'http://schema.org/Number', | 24 | stats: 'http://schema.org/Number', |
25 | 'commentsEnabled': 'http://schema.org/Boolean', | 25 | size: 'http://schema.org/Number', |
26 | 'support': 'http://schema.org/Text' | 26 | commentsEnabled: 'http://schema.org/Boolean', |
27 | waitTranscoding: 'http://schema.org/Boolean', | ||
28 | support: 'http://schema.org/Text' | ||
27 | }, | 29 | }, |
28 | { | 30 | { |
29 | likes: { | 31 | likes: { |
diff --git a/server/helpers/custom-validators/activitypub/videos.ts b/server/helpers/custom-validators/activitypub/videos.ts index 7e1d57c34..37c90a0c8 100644 --- a/server/helpers/custom-validators/activitypub/videos.ts +++ b/server/helpers/custom-validators/activitypub/videos.ts | |||
@@ -6,11 +6,13 @@ import { | |||
6 | isVideoAbuseReasonValid, | 6 | isVideoAbuseReasonValid, |
7 | isVideoDurationValid, | 7 | isVideoDurationValid, |
8 | isVideoNameValid, | 8 | isVideoNameValid, |
9 | isVideoStateValid, | ||
9 | isVideoTagValid, | 10 | isVideoTagValid, |
10 | isVideoTruncatedDescriptionValid, | 11 | isVideoTruncatedDescriptionValid, |
11 | isVideoViewsValid | 12 | isVideoViewsValid |
12 | } from '../videos' | 13 | } from '../videos' |
13 | import { isActivityPubUrlValid, isBaseActivityValid, setValidAttributedTo } from './misc' | 14 | import { isActivityPubUrlValid, isBaseActivityValid, setValidAttributedTo } from './misc' |
15 | import { VideoState } from '../../../../shared/models/videos' | ||
14 | 16 | ||
15 | function sanitizeAndCheckVideoTorrentCreateActivity (activity: any) { | 17 | function sanitizeAndCheckVideoTorrentCreateActivity (activity: any) { |
16 | return isBaseActivityValid(activity, 'Create') && | 18 | return isBaseActivityValid(activity, 'Create') && |
@@ -50,6 +52,10 @@ function sanitizeAndCheckVideoTorrentObject (video: any) { | |||
50 | if (!setRemoteVideoTruncatedContent(video)) return false | 52 | if (!setRemoteVideoTruncatedContent(video)) return false |
51 | if (!setValidAttributedTo(video)) return false | 53 | if (!setValidAttributedTo(video)) return false |
52 | 54 | ||
55 | // Default attributes | ||
56 | if (!isVideoStateValid(video.state)) video.state = VideoState.PUBLISHED | ||
57 | if (!isBooleanValid(video.waitTranscoding)) video.waitTranscoding = false | ||
58 | |||
53 | return isActivityPubUrlValid(video.id) && | 59 | return isActivityPubUrlValid(video.id) && |
54 | isVideoNameValid(video.name) && | 60 | isVideoNameValid(video.name) && |
55 | isActivityPubVideoDurationValid(video.duration) && | 61 | isActivityPubVideoDurationValid(video.duration) && |
diff --git a/server/helpers/custom-validators/videos.ts b/server/helpers/custom-validators/videos.ts index f365df985..8496e679a 100644 --- a/server/helpers/custom-validators/videos.ts +++ b/server/helpers/custom-validators/videos.ts | |||
@@ -10,7 +10,8 @@ import { | |||
10 | VIDEO_LICENCES, | 10 | VIDEO_LICENCES, |
11 | VIDEO_MIMETYPE_EXT, | 11 | VIDEO_MIMETYPE_EXT, |
12 | VIDEO_PRIVACIES, | 12 | VIDEO_PRIVACIES, |
13 | VIDEO_RATE_TYPES | 13 | VIDEO_RATE_TYPES, |
14 | VIDEO_STATES | ||
14 | } from '../../initializers' | 15 | } from '../../initializers' |
15 | import { VideoModel } from '../../models/video/video' | 16 | import { VideoModel } from '../../models/video/video' |
16 | import { exists, isArray, isFileValid } from './misc' | 17 | import { exists, isArray, isFileValid } from './misc' |
@@ -21,11 +22,15 @@ const VIDEOS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEOS | |||
21 | const VIDEO_ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_ABUSES | 22 | const VIDEO_ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_ABUSES |
22 | 23 | ||
23 | function isVideoCategoryValid (value: any) { | 24 | function isVideoCategoryValid (value: any) { |
24 | return value === null || VIDEO_CATEGORIES[value] !== undefined | 25 | return value === null || VIDEO_CATEGORIES[ value ] !== undefined |
26 | } | ||
27 | |||
28 | function isVideoStateValid (value: any) { | ||
29 | return exists(value) && VIDEO_STATES[ value ] !== undefined | ||
25 | } | 30 | } |
26 | 31 | ||
27 | function isVideoLicenceValid (value: any) { | 32 | function isVideoLicenceValid (value: any) { |
28 | return value === null || VIDEO_LICENCES[value] !== undefined | 33 | return value === null || VIDEO_LICENCES[ value ] !== undefined |
29 | } | 34 | } |
30 | 35 | ||
31 | function isVideoLanguageValid (value: any) { | 36 | function isVideoLanguageValid (value: any) { |
@@ -79,20 +84,22 @@ function isVideoRatingTypeValid (value: string) { | |||
79 | 84 | ||
80 | const videoFileTypes = Object.keys(VIDEO_MIMETYPE_EXT).map(m => `(${m})`) | 85 | const videoFileTypes = Object.keys(VIDEO_MIMETYPE_EXT).map(m => `(${m})`) |
81 | const videoFileTypesRegex = videoFileTypes.join('|') | 86 | const videoFileTypesRegex = videoFileTypes.join('|') |
87 | |||
82 | function isVideoFile (files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[]) { | 88 | function isVideoFile (files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[]) { |
83 | return isFileValid(files, videoFileTypesRegex, 'videofile') | 89 | return isFileValid(files, videoFileTypesRegex, 'videofile') |
84 | } | 90 | } |
85 | 91 | ||
86 | const videoImageTypes = CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME | 92 | const videoImageTypes = CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME |
87 | .map(v => v.replace('.', '')) | 93 | .map(v => v.replace('.', '')) |
88 | .join('|') | 94 | .join('|') |
89 | const videoImageTypesRegex = `image/(${videoImageTypes})` | 95 | const videoImageTypesRegex = `image/(${videoImageTypes})` |
96 | |||
90 | function isVideoImage (files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[], field: string) { | 97 | function isVideoImage (files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[], field: string) { |
91 | return isFileValid(files, videoImageTypesRegex, field, true) | 98 | return isFileValid(files, videoImageTypesRegex, field, true) |
92 | } | 99 | } |
93 | 100 | ||
94 | function isVideoPrivacyValid (value: string) { | 101 | function isVideoPrivacyValid (value: string) { |
95 | return validator.isInt(value + '') && VIDEO_PRIVACIES[value] !== undefined | 102 | return validator.isInt(value + '') && VIDEO_PRIVACIES[ value ] !== undefined |
96 | } | 103 | } |
97 | 104 | ||
98 | function isVideoFileInfoHashValid (value: string) { | 105 | function isVideoFileInfoHashValid (value: string) { |
@@ -118,8 +125,8 @@ async function isVideoExist (id: string, res: Response) { | |||
118 | 125 | ||
119 | if (!video) { | 126 | if (!video) { |
120 | res.status(404) | 127 | res.status(404) |
121 | .json({ error: 'Video not found' }) | 128 | .json({ error: 'Video not found' }) |
122 | .end() | 129 | .end() |
123 | 130 | ||
124 | return false | 131 | return false |
125 | } | 132 | } |
@@ -169,6 +176,7 @@ export { | |||
169 | isVideoTagsValid, | 176 | isVideoTagsValid, |
170 | isVideoAbuseReasonValid, | 177 | isVideoAbuseReasonValid, |
171 | isVideoFile, | 178 | isVideoFile, |
179 | isVideoStateValid, | ||
172 | isVideoViewsValid, | 180 | isVideoViewsValid, |
173 | isVideoRatingTypeValid, | 181 | isVideoRatingTypeValid, |
174 | isVideoDurationValid, | 182 | isVideoDurationValid, |
diff --git a/server/helpers/utils.ts b/server/helpers/utils.ts index e4556fa12..8fa861281 100644 --- a/server/helpers/utils.ts +++ b/server/helpers/utils.ts | |||
@@ -1,6 +1,5 @@ | |||
1 | import { Model } from 'sequelize-typescript' | 1 | import { Model } from 'sequelize-typescript' |
2 | import * as ipaddr from 'ipaddr.js' | 2 | import * as ipaddr from 'ipaddr.js' |
3 | const isCidr = require('is-cidr') | ||
4 | import { ResultList } from '../../shared' | 3 | import { ResultList } from '../../shared' |
5 | import { VideoResolution } from '../../shared/models/videos' | 4 | import { VideoResolution } from '../../shared/models/videos' |
6 | import { CONFIG } from '../initializers' | 5 | import { CONFIG } from '../initializers' |
@@ -10,6 +9,8 @@ import { ApplicationModel } from '../models/application/application' | |||
10 | import { pseudoRandomBytesPromise } from './core-utils' | 9 | import { pseudoRandomBytesPromise } from './core-utils' |
11 | import { logger } from './logger' | 10 | import { logger } from './logger' |
12 | 11 | ||
12 | const isCidr = require('is-cidr') | ||
13 | |||
13 | async function generateRandomString (size: number) { | 14 | async function generateRandomString (size: number) { |
14 | const raw = await pseudoRandomBytesPromise(size) | 15 | const raw = await pseudoRandomBytesPromise(size) |
15 | 16 | ||
@@ -17,22 +18,20 @@ async function generateRandomString (size: number) { | |||
17 | } | 18 | } |
18 | 19 | ||
19 | interface FormattableToJSON { | 20 | interface FormattableToJSON { |
20 | toFormattedJSON () | 21 | toFormattedJSON (args?: any) |
21 | } | 22 | } |
22 | 23 | ||
23 | function getFormattedObjects<U, T extends FormattableToJSON> (objects: T[], objectsTotal: number) { | 24 | function getFormattedObjects<U, T extends FormattableToJSON> (objects: T[], objectsTotal: number, formattedArg?: any) { |
24 | const formattedObjects: U[] = [] | 25 | const formattedObjects: U[] = [] |
25 | 26 | ||
26 | objects.forEach(object => { | 27 | objects.forEach(object => { |
27 | formattedObjects.push(object.toFormattedJSON()) | 28 | formattedObjects.push(object.toFormattedJSON(formattedArg)) |
28 | }) | 29 | }) |
29 | 30 | ||
30 | const res: ResultList<U> = { | 31 | return { |
31 | total: objectsTotal, | 32 | total: objectsTotal, |
32 | data: formattedObjects | 33 | data: formattedObjects |
33 | } | 34 | } as ResultList<U> |
34 | |||
35 | return res | ||
36 | } | 35 | } |
37 | 36 | ||
38 | async function isSignupAllowed () { | 37 | async function isSignupAllowed () { |
@@ -87,16 +86,17 @@ function computeResolutionsToTranscode (videoFileHeight: number) { | |||
87 | const resolutionsEnabled: number[] = [] | 86 | const resolutionsEnabled: number[] = [] |
88 | const configResolutions = CONFIG.TRANSCODING.RESOLUTIONS | 87 | const configResolutions = CONFIG.TRANSCODING.RESOLUTIONS |
89 | 88 | ||
89 | // Put in the order we want to proceed jobs | ||
90 | const resolutions = [ | 90 | const resolutions = [ |
91 | VideoResolution.H_240P, | ||
92 | VideoResolution.H_360P, | ||
93 | VideoResolution.H_480P, | 91 | VideoResolution.H_480P, |
92 | VideoResolution.H_360P, | ||
94 | VideoResolution.H_720P, | 93 | VideoResolution.H_720P, |
94 | VideoResolution.H_240P, | ||
95 | VideoResolution.H_1080P | 95 | VideoResolution.H_1080P |
96 | ] | 96 | ] |
97 | 97 | ||
98 | for (const resolution of resolutions) { | 98 | for (const resolution of resolutions) { |
99 | if (configResolutions[resolution + 'p'] === true && videoFileHeight > resolution) { | 99 | if (configResolutions[ resolution + 'p' ] === true && videoFileHeight > resolution) { |
100 | resolutionsEnabled.push(resolution) | 100 | resolutionsEnabled.push(resolution) |
101 | } | 101 | } |
102 | } | 102 | } |
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index 79e4bb7f0..8dbc1b060 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { IConfig } from 'config' | 1 | import { IConfig } from 'config' |
2 | import { dirname, join } from 'path' | 2 | import { dirname, join } from 'path' |
3 | import { JobType, VideoRateType } from '../../shared/models' | 3 | import { JobType, VideoRateType, VideoState } from '../../shared/models' |
4 | import { ActivityPubActorType } from '../../shared/models/activitypub' | 4 | import { ActivityPubActorType } from '../../shared/models/activitypub' |
5 | import { FollowState } from '../../shared/models/actors' | 5 | import { FollowState } from '../../shared/models/actors' |
6 | import { VideoPrivacy } from '../../shared/models/videos' | 6 | import { VideoPrivacy } from '../../shared/models/videos' |
@@ -14,7 +14,7 @@ let config: IConfig = require('config') | |||
14 | 14 | ||
15 | // --------------------------------------------------------------------------- | 15 | // --------------------------------------------------------------------------- |
16 | 16 | ||
17 | const LAST_MIGRATION_VERSION = 215 | 17 | const LAST_MIGRATION_VERSION = 220 |
18 | 18 | ||
19 | // --------------------------------------------------------------------------- | 19 | // --------------------------------------------------------------------------- |
20 | 20 | ||
@@ -326,6 +326,11 @@ const VIDEO_PRIVACIES = { | |||
326 | [VideoPrivacy.PRIVATE]: 'Private' | 326 | [VideoPrivacy.PRIVATE]: 'Private' |
327 | } | 327 | } |
328 | 328 | ||
329 | const VIDEO_STATES = { | ||
330 | [VideoState.PUBLISHED]: 'Published', | ||
331 | [VideoState.TO_TRANSCODE]: 'To transcode' | ||
332 | } | ||
333 | |||
329 | const VIDEO_MIMETYPE_EXT = { | 334 | const VIDEO_MIMETYPE_EXT = { |
330 | 'video/webm': '.webm', | 335 | 'video/webm': '.webm', |
331 | 'video/ogg': '.ogv', | 336 | 'video/ogg': '.ogv', |
@@ -493,6 +498,7 @@ export { | |||
493 | VIDEO_LANGUAGES, | 498 | VIDEO_LANGUAGES, |
494 | VIDEO_PRIVACIES, | 499 | VIDEO_PRIVACIES, |
495 | VIDEO_LICENCES, | 500 | VIDEO_LICENCES, |
501 | VIDEO_STATES, | ||
496 | VIDEO_RATE_TYPES, | 502 | VIDEO_RATE_TYPES, |
497 | VIDEO_MIMETYPE_EXT, | 503 | VIDEO_MIMETYPE_EXT, |
498 | VIDEO_TRANSCODING_FPS, | 504 | VIDEO_TRANSCODING_FPS, |
diff --git a/server/initializers/migrations/0220-video-state.ts b/server/initializers/migrations/0220-video-state.ts new file mode 100644 index 000000000..491702157 --- /dev/null +++ b/server/initializers/migrations/0220-video-state.ts | |||
@@ -0,0 +1,62 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | |||
3 | async function up (utils: { | ||
4 | transaction: Sequelize.Transaction | ||
5 | queryInterface: Sequelize.QueryInterface | ||
6 | sequelize: Sequelize.Sequelize | ||
7 | }): Promise<void> { | ||
8 | // waitingTranscoding column | ||
9 | { | ||
10 | const data = { | ||
11 | type: Sequelize.BOOLEAN, | ||
12 | allowNull: true, | ||
13 | defaultValue: null | ||
14 | } | ||
15 | await utils.queryInterface.addColumn('video', 'waitTranscoding', data) | ||
16 | } | ||
17 | |||
18 | { | ||
19 | const query = 'UPDATE video SET "waitTranscoding" = false' | ||
20 | await utils.sequelize.query(query) | ||
21 | } | ||
22 | |||
23 | { | ||
24 | const data = { | ||
25 | type: Sequelize.BOOLEAN, | ||
26 | allowNull: false, | ||
27 | defaultValue: null | ||
28 | } | ||
29 | await utils.queryInterface.changeColumn('video', 'waitTranscoding', data) | ||
30 | } | ||
31 | |||
32 | // state | ||
33 | { | ||
34 | const data = { | ||
35 | type: Sequelize.INTEGER, | ||
36 | allowNull: true, | ||
37 | defaultValue: null | ||
38 | } | ||
39 | await utils.queryInterface.addColumn('video', 'state', data) | ||
40 | } | ||
41 | |||
42 | { | ||
43 | // Published | ||
44 | const query = 'UPDATE video SET "state" = 1' | ||
45 | await utils.sequelize.query(query) | ||
46 | } | ||
47 | |||
48 | { | ||
49 | const data = { | ||
50 | type: Sequelize.INTEGER, | ||
51 | allowNull: false, | ||
52 | defaultValue: null | ||
53 | } | ||
54 | await utils.queryInterface.changeColumn('video', 'state', data) | ||
55 | } | ||
56 | } | ||
57 | |||
58 | function down (options) { | ||
59 | throw new Error('Not implemented.') | ||
60 | } | ||
61 | |||
62 | export { up, down } | ||
diff --git a/server/lib/activitypub/audience.ts b/server/lib/activitypub/audience.ts index c1265dbcd..7164135b6 100644 --- a/server/lib/activitypub/audience.ts +++ b/server/lib/activitypub/audience.ts | |||
@@ -20,7 +20,7 @@ function getVideoCommentAudience ( | |||
20 | isOrigin = false | 20 | isOrigin = false |
21 | ) { | 21 | ) { |
22 | const to = [ ACTIVITY_PUB.PUBLIC ] | 22 | const to = [ ACTIVITY_PUB.PUBLIC ] |
23 | const cc = [ ] | 23 | const cc = [] |
24 | 24 | ||
25 | // Owner of the video we comment | 25 | // Owner of the video we comment |
26 | if (isOrigin === false) { | 26 | if (isOrigin === false) { |
@@ -55,7 +55,7 @@ async function getActorsInvolvedInVideo (video: VideoModel, t: Transaction) { | |||
55 | return actors | 55 | return actors |
56 | } | 56 | } |
57 | 57 | ||
58 | async function getAudience (actorSender: ActorModel, t: Transaction, isPublic = true) { | 58 | function getAudience (actorSender: ActorModel, isPublic = true) { |
59 | return buildAudience([ actorSender.followersUrl ], isPublic) | 59 | return buildAudience([ actorSender.followersUrl ], isPublic) |
60 | } | 60 | } |
61 | 61 | ||
@@ -67,14 +67,14 @@ function buildAudience (followerUrls: string[], isPublic = true) { | |||
67 | to = [ ACTIVITY_PUB.PUBLIC ] | 67 | to = [ ACTIVITY_PUB.PUBLIC ] |
68 | cc = followerUrls | 68 | cc = followerUrls |
69 | } else { // Unlisted | 69 | } else { // Unlisted |
70 | to = [ ] | 70 | to = [] |
71 | cc = [ ] | 71 | cc = [] |
72 | } | 72 | } |
73 | 73 | ||
74 | return { to, cc } | 74 | return { to, cc } |
75 | } | 75 | } |
76 | 76 | ||
77 | function audiencify <T> (object: T, audience: ActivityAudience) { | 77 | function audiencify<T> (object: T, audience: ActivityAudience) { |
78 | return Object.assign(object, audience) | 78 | return Object.assign(object, audience) |
79 | } | 79 | } |
80 | 80 | ||
diff --git a/server/lib/activitypub/crawl.ts b/server/lib/activitypub/crawl.ts index 7305b3969..d4fc786f7 100644 --- a/server/lib/activitypub/crawl.ts +++ b/server/lib/activitypub/crawl.ts | |||
@@ -28,7 +28,7 @@ async function crawlCollectionPage <T> (uri: string, handler: (items: T[]) => Pr | |||
28 | 28 | ||
29 | if (Array.isArray(body.orderedItems)) { | 29 | if (Array.isArray(body.orderedItems)) { |
30 | const items = body.orderedItems | 30 | const items = body.orderedItems |
31 | logger.info('Processing %i ActivityPub items for %s.', items.length, nextLink) | 31 | logger.info('Processing %i ActivityPub items for %s.', items.length, options.uri) |
32 | 32 | ||
33 | await handler(items) | 33 | await handler(items) |
34 | } | 34 | } |
diff --git a/server/lib/activitypub/process/process-update.ts b/server/lib/activitypub/process/process-update.ts index 2750f48c3..77de8c155 100644 --- a/server/lib/activitypub/process/process-update.ts +++ b/server/lib/activitypub/process/process-update.ts | |||
@@ -1,7 +1,6 @@ | |||
1 | import * as Bluebird from 'bluebird' | 1 | import * as Bluebird from 'bluebird' |
2 | import { ActivityUpdate } from '../../../../shared/models/activitypub' | 2 | import { ActivityUpdate } from '../../../../shared/models/activitypub' |
3 | import { ActivityPubActor } from '../../../../shared/models/activitypub/activitypub-actor' | 3 | import { ActivityPubActor } from '../../../../shared/models/activitypub/activitypub-actor' |
4 | import { VideoTorrentObject } from '../../../../shared/models/activitypub/objects' | ||
5 | import { retryTransactionWrapper } from '../../../helpers/database-utils' | 4 | import { retryTransactionWrapper } from '../../../helpers/database-utils' |
6 | import { logger } from '../../../helpers/logger' | 5 | import { logger } from '../../../helpers/logger' |
7 | import { resetSequelizeInstance } from '../../../helpers/utils' | 6 | import { resetSequelizeInstance } from '../../../helpers/utils' |
@@ -13,6 +12,7 @@ import { VideoChannelModel } from '../../../models/video/video-channel' | |||
13 | import { VideoFileModel } from '../../../models/video/video-file' | 12 | import { VideoFileModel } from '../../../models/video/video-file' |
14 | import { fetchAvatarIfExists, getOrCreateActorAndServerAndModel, updateActorAvatarInstance, updateActorInstance } from '../actor' | 13 | import { fetchAvatarIfExists, getOrCreateActorAndServerAndModel, updateActorAvatarInstance, updateActorInstance } from '../actor' |
15 | import { | 14 | import { |
15 | fetchRemoteVideo, | ||
16 | generateThumbnailFromUrl, | 16 | generateThumbnailFromUrl, |
17 | getOrCreateAccountAndVideoAndChannel, | 17 | getOrCreateAccountAndVideoAndChannel, |
18 | getOrCreateVideoChannel, | 18 | getOrCreateVideoChannel, |
@@ -51,15 +51,18 @@ function processUpdateVideo (actor: ActorModel, activity: ActivityUpdate) { | |||
51 | } | 51 | } |
52 | 52 | ||
53 | async function updateRemoteVideo (actor: ActorModel, activity: ActivityUpdate) { | 53 | async function updateRemoteVideo (actor: ActorModel, activity: ActivityUpdate) { |
54 | const videoAttributesToUpdate = activity.object as VideoTorrentObject | 54 | const videoUrl = activity.object.id |
55 | 55 | ||
56 | const res = await getOrCreateAccountAndVideoAndChannel(videoAttributesToUpdate.id) | 56 | const videoObject = await fetchRemoteVideo(videoUrl) |
57 | if (!videoObject) throw new Error('Cannot fetch remote video with url: ' + videoUrl) | ||
58 | |||
59 | const res = await getOrCreateAccountAndVideoAndChannel(videoObject.id) | ||
57 | 60 | ||
58 | // Fetch video channel outside the transaction | 61 | // Fetch video channel outside the transaction |
59 | const newVideoChannelActor = await getOrCreateVideoChannel(videoAttributesToUpdate) | 62 | const newVideoChannelActor = await getOrCreateVideoChannel(videoObject) |
60 | const newVideoChannel = newVideoChannelActor.VideoChannel | 63 | const newVideoChannel = newVideoChannelActor.VideoChannel |
61 | 64 | ||
62 | logger.debug('Updating remote video "%s".', videoAttributesToUpdate.uuid) | 65 | logger.debug('Updating remote video "%s".', videoObject.uuid) |
63 | let videoInstance = res.video | 66 | let videoInstance = res.video |
64 | let videoFieldsSave: any | 67 | let videoFieldsSave: any |
65 | 68 | ||
@@ -77,7 +80,7 @@ async function updateRemoteVideo (actor: ActorModel, activity: ActivityUpdate) { | |||
77 | throw new Error('Account ' + actor.url + ' does not own video channel ' + videoChannel.Actor.url) | 80 | throw new Error('Account ' + actor.url + ' does not own video channel ' + videoChannel.Actor.url) |
78 | } | 81 | } |
79 | 82 | ||
80 | const videoData = await videoActivityObjectToDBAttributes(newVideoChannel, videoAttributesToUpdate, activity.to) | 83 | const videoData = await videoActivityObjectToDBAttributes(newVideoChannel, videoObject, activity.to) |
81 | videoInstance.set('name', videoData.name) | 84 | videoInstance.set('name', videoData.name) |
82 | videoInstance.set('uuid', videoData.uuid) | 85 | videoInstance.set('uuid', videoData.uuid) |
83 | videoInstance.set('url', videoData.url) | 86 | videoInstance.set('url', videoData.url) |
@@ -88,6 +91,8 @@ async function updateRemoteVideo (actor: ActorModel, activity: ActivityUpdate) { | |||
88 | videoInstance.set('support', videoData.support) | 91 | videoInstance.set('support', videoData.support) |
89 | videoInstance.set('nsfw', videoData.nsfw) | 92 | videoInstance.set('nsfw', videoData.nsfw) |
90 | videoInstance.set('commentsEnabled', videoData.commentsEnabled) | 93 | videoInstance.set('commentsEnabled', videoData.commentsEnabled) |
94 | videoInstance.set('waitTranscoding', videoData.waitTranscoding) | ||
95 | videoInstance.set('state', videoData.state) | ||
91 | videoInstance.set('duration', videoData.duration) | 96 | videoInstance.set('duration', videoData.duration) |
92 | videoInstance.set('createdAt', videoData.createdAt) | 97 | videoInstance.set('createdAt', videoData.createdAt) |
93 | videoInstance.set('updatedAt', videoData.updatedAt) | 98 | videoInstance.set('updatedAt', videoData.updatedAt) |
@@ -98,8 +103,8 @@ async function updateRemoteVideo (actor: ActorModel, activity: ActivityUpdate) { | |||
98 | await videoInstance.save(sequelizeOptions) | 103 | await videoInstance.save(sequelizeOptions) |
99 | 104 | ||
100 | // Don't block on request | 105 | // Don't block on request |
101 | generateThumbnailFromUrl(videoInstance, videoAttributesToUpdate.icon) | 106 | generateThumbnailFromUrl(videoInstance, videoObject.icon) |
102 | .catch(err => logger.warn('Cannot generate thumbnail of %s.', videoAttributesToUpdate.id, { err })) | 107 | .catch(err => logger.warn('Cannot generate thumbnail of %s.', videoObject.id, { err })) |
103 | 108 | ||
104 | // Remove old video files | 109 | // Remove old video files |
105 | const videoFileDestroyTasks: Bluebird<void>[] = [] | 110 | const videoFileDestroyTasks: Bluebird<void>[] = [] |
@@ -108,16 +113,16 @@ async function updateRemoteVideo (actor: ActorModel, activity: ActivityUpdate) { | |||
108 | } | 113 | } |
109 | await Promise.all(videoFileDestroyTasks) | 114 | await Promise.all(videoFileDestroyTasks) |
110 | 115 | ||
111 | const videoFileAttributes = videoFileActivityUrlToDBAttributes(videoInstance, videoAttributesToUpdate) | 116 | const videoFileAttributes = videoFileActivityUrlToDBAttributes(videoInstance, videoObject) |
112 | const tasks = videoFileAttributes.map(f => VideoFileModel.create(f)) | 117 | const tasks = videoFileAttributes.map(f => VideoFileModel.create(f)) |
113 | await Promise.all(tasks) | 118 | await Promise.all(tasks) |
114 | 119 | ||
115 | const tags = videoAttributesToUpdate.tag.map(t => t.name) | 120 | const tags = videoObject.tag.map(t => t.name) |
116 | const tagInstances = await TagModel.findOrCreateTags(tags, t) | 121 | const tagInstances = await TagModel.findOrCreateTags(tags, t) |
117 | await videoInstance.$set('Tags', tagInstances, sequelizeOptions) | 122 | await videoInstance.$set('Tags', tagInstances, sequelizeOptions) |
118 | }) | 123 | }) |
119 | 124 | ||
120 | logger.info('Remote video with uuid %s updated', videoAttributesToUpdate.uuid) | 125 | logger.info('Remote video with uuid %s updated', videoObject.uuid) |
121 | } catch (err) { | 126 | } catch (err) { |
122 | if (videoInstance !== undefined && videoFieldsSave !== undefined) { | 127 | if (videoInstance !== undefined && videoFieldsSave !== undefined) { |
123 | resetSequelizeInstance(videoInstance, videoFieldsSave) | 128 | resetSequelizeInstance(videoInstance, videoFieldsSave) |
diff --git a/server/lib/activitypub/send/send-announce.ts b/server/lib/activitypub/send/send-announce.ts index fa1d47259..dfc099ff2 100644 --- a/server/lib/activitypub/send/send-announce.ts +++ b/server/lib/activitypub/send/send-announce.ts | |||
@@ -11,7 +11,7 @@ async function buildVideoAnnounce (byActor: ActorModel, videoShare: VideoShareMo | |||
11 | 11 | ||
12 | const accountsToForwardView = await getActorsInvolvedInVideo(video, t) | 12 | const accountsToForwardView = await getActorsInvolvedInVideo(video, t) |
13 | const audience = getObjectFollowersAudience(accountsToForwardView) | 13 | const audience = getObjectFollowersAudience(accountsToForwardView) |
14 | return announceActivityData(videoShare.url, byActor, announcedObject, t, audience) | 14 | return announceActivityData(videoShare.url, byActor, announcedObject, audience) |
15 | } | 15 | } |
16 | 16 | ||
17 | async function sendVideoAnnounce (byActor: ActorModel, videoShare: VideoShareModel, video: VideoModel, t: Transaction) { | 17 | async function sendVideoAnnounce (byActor: ActorModel, videoShare: VideoShareModel, video: VideoModel, t: Transaction) { |
@@ -20,16 +20,8 @@ async function sendVideoAnnounce (byActor: ActorModel, videoShare: VideoShareMod | |||
20 | return broadcastToFollowers(data, byActor, [ byActor ], t) | 20 | return broadcastToFollowers(data, byActor, [ byActor ], t) |
21 | } | 21 | } |
22 | 22 | ||
23 | async function announceActivityData ( | 23 | function announceActivityData (url: string, byActor: ActorModel, object: string, audience?: ActivityAudience): ActivityAnnounce { |
24 | url: string, | 24 | if (!audience) audience = getAudience(byActor) |
25 | byActor: ActorModel, | ||
26 | object: string, | ||
27 | t: Transaction, | ||
28 | audience?: ActivityAudience | ||
29 | ): Promise<ActivityAnnounce> { | ||
30 | if (!audience) { | ||
31 | audience = await getAudience(byActor, t) | ||
32 | } | ||
33 | 25 | ||
34 | return { | 26 | return { |
35 | type: 'Announce', | 27 | type: 'Announce', |
diff --git a/server/lib/activitypub/send/send-create.ts b/server/lib/activitypub/send/send-create.ts index 3ef4fcd3b..293947b05 100644 --- a/server/lib/activitypub/send/send-create.ts +++ b/server/lib/activitypub/send/send-create.ts | |||
@@ -23,8 +23,8 @@ async function sendCreateVideo (video: VideoModel, t: Transaction) { | |||
23 | const byActor = video.VideoChannel.Account.Actor | 23 | const byActor = video.VideoChannel.Account.Actor |
24 | const videoObject = video.toActivityPubObject() | 24 | const videoObject = video.toActivityPubObject() |
25 | 25 | ||
26 | const audience = await getAudience(byActor, t, video.privacy === VideoPrivacy.PUBLIC) | 26 | const audience = getAudience(byActor, video.privacy === VideoPrivacy.PUBLIC) |
27 | const data = await createActivityData(video.url, byActor, videoObject, t, audience) | 27 | const data = createActivityData(video.url, byActor, videoObject, audience) |
28 | 28 | ||
29 | return broadcastToFollowers(data, byActor, [ byActor ], t) | 29 | return broadcastToFollowers(data, byActor, [ byActor ], t) |
30 | } | 30 | } |
@@ -33,7 +33,7 @@ async function sendVideoAbuse (byActor: ActorModel, videoAbuse: VideoAbuseModel, | |||
33 | const url = getVideoAbuseActivityPubUrl(videoAbuse) | 33 | const url = getVideoAbuseActivityPubUrl(videoAbuse) |
34 | 34 | ||
35 | const audience = { to: [ video.VideoChannel.Account.Actor.url ], cc: [] } | 35 | const audience = { to: [ video.VideoChannel.Account.Actor.url ], cc: [] } |
36 | const data = await createActivityData(url, byActor, videoAbuse.toActivityPubObject(), t, audience) | 36 | const data = createActivityData(url, byActor, videoAbuse.toActivityPubObject(), audience) |
37 | 37 | ||
38 | return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl) | 38 | return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl) |
39 | } | 39 | } |
@@ -57,7 +57,7 @@ async function sendCreateVideoComment (comment: VideoCommentModel, t: Transactio | |||
57 | audience = getObjectFollowersAudience(actorsInvolvedInComment.concat(parentsCommentActors)) | 57 | audience = getObjectFollowersAudience(actorsInvolvedInComment.concat(parentsCommentActors)) |
58 | } | 58 | } |
59 | 59 | ||
60 | const data = await createActivityData(comment.url, byActor, commentObject, t, audience) | 60 | const data = createActivityData(comment.url, byActor, commentObject, audience) |
61 | 61 | ||
62 | // This was a reply, send it to the parent actors | 62 | // This was a reply, send it to the parent actors |
63 | const actorsException = [ byActor ] | 63 | const actorsException = [ byActor ] |
@@ -82,14 +82,14 @@ async function sendCreateView (byActor: ActorModel, video: VideoModel, t: Transa | |||
82 | // Send to origin | 82 | // Send to origin |
83 | if (video.isOwned() === false) { | 83 | if (video.isOwned() === false) { |
84 | const audience = getVideoAudience(video, actorsInvolvedInVideo) | 84 | const audience = getVideoAudience(video, actorsInvolvedInVideo) |
85 | const data = await createActivityData(url, byActor, viewActivityData, t, audience) | 85 | const data = createActivityData(url, byActor, viewActivityData, audience) |
86 | 86 | ||
87 | return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl) | 87 | return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl) |
88 | } | 88 | } |
89 | 89 | ||
90 | // Send to followers | 90 | // Send to followers |
91 | const audience = getObjectFollowersAudience(actorsInvolvedInVideo) | 91 | const audience = getObjectFollowersAudience(actorsInvolvedInVideo) |
92 | const data = await createActivityData(url, byActor, viewActivityData, t, audience) | 92 | const data = createActivityData(url, byActor, viewActivityData, audience) |
93 | 93 | ||
94 | // Use the server actor to send the view | 94 | // Use the server actor to send the view |
95 | const serverActor = await getServerActor() | 95 | const serverActor = await getServerActor() |
@@ -106,34 +106,31 @@ async function sendCreateDislike (byActor: ActorModel, video: VideoModel, t: Tra | |||
106 | // Send to origin | 106 | // Send to origin |
107 | if (video.isOwned() === false) { | 107 | if (video.isOwned() === false) { |
108 | const audience = getVideoAudience(video, actorsInvolvedInVideo) | 108 | const audience = getVideoAudience(video, actorsInvolvedInVideo) |
109 | const data = await createActivityData(url, byActor, dislikeActivityData, t, audience) | 109 | const data = createActivityData(url, byActor, dislikeActivityData, audience) |
110 | 110 | ||
111 | return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl) | 111 | return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl) |
112 | } | 112 | } |
113 | 113 | ||
114 | // Send to followers | 114 | // Send to followers |
115 | const audience = getObjectFollowersAudience(actorsInvolvedInVideo) | 115 | const audience = getObjectFollowersAudience(actorsInvolvedInVideo) |
116 | const data = await createActivityData(url, byActor, dislikeActivityData, t, audience) | 116 | const data = createActivityData(url, byActor, dislikeActivityData, audience) |
117 | 117 | ||
118 | const actorsException = [ byActor ] | 118 | const actorsException = [ byActor ] |
119 | return broadcastToFollowers(data, byActor, actorsInvolvedInVideo, t, actorsException) | 119 | return broadcastToFollowers(data, byActor, actorsInvolvedInVideo, t, actorsException) |
120 | } | 120 | } |
121 | 121 | ||
122 | async function createActivityData (url: string, | 122 | function createActivityData (url: string, byActor: ActorModel, object: any, audience?: ActivityAudience): ActivityCreate { |
123 | byActor: ActorModel, | 123 | if (!audience) audience = getAudience(byActor) |
124 | object: any, | 124 | |
125 | t: Transaction, | 125 | return audiencify( |
126 | audience?: ActivityAudience): Promise<ActivityCreate> { | 126 | { |
127 | if (!audience) { | 127 | type: 'Create' as 'Create', |
128 | audience = await getAudience(byActor, t) | 128 | id: url + '/activity', |
129 | } | 129 | actor: byActor.url, |
130 | 130 | object: audiencify(object, audience) | |
131 | return audiencify({ | 131 | }, |
132 | type: 'Create' as 'Create', | 132 | audience |
133 | id: url + '/activity', | 133 | ) |
134 | actor: byActor.url, | ||
135 | object: audiencify(object, audience) | ||
136 | }, audience) | ||
137 | } | 134 | } |
138 | 135 | ||
139 | function createDislikeActivityData (byActor: ActorModel, video: VideoModel) { | 136 | function createDislikeActivityData (byActor: ActorModel, video: VideoModel) { |
diff --git a/server/lib/activitypub/send/send-like.ts b/server/lib/activitypub/send/send-like.ts index ddeb1fcd2..37ee7c096 100644 --- a/server/lib/activitypub/send/send-like.ts +++ b/server/lib/activitypub/send/send-like.ts | |||
@@ -14,36 +14,31 @@ async function sendLike (byActor: ActorModel, video: VideoModel, t: Transaction) | |||
14 | // Send to origin | 14 | // Send to origin |
15 | if (video.isOwned() === false) { | 15 | if (video.isOwned() === false) { |
16 | const audience = getVideoAudience(video, accountsInvolvedInVideo) | 16 | const audience = getVideoAudience(video, accountsInvolvedInVideo) |
17 | const data = await likeActivityData(url, byActor, video, t, audience) | 17 | const data = likeActivityData(url, byActor, video, audience) |
18 | 18 | ||
19 | return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl) | 19 | return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl) |
20 | } | 20 | } |
21 | 21 | ||
22 | // Send to followers | 22 | // Send to followers |
23 | const audience = getObjectFollowersAudience(accountsInvolvedInVideo) | 23 | const audience = getObjectFollowersAudience(accountsInvolvedInVideo) |
24 | const data = await likeActivityData(url, byActor, video, t, audience) | 24 | const data = likeActivityData(url, byActor, video, audience) |
25 | 25 | ||
26 | const followersException = [ byActor ] | 26 | const followersException = [ byActor ] |
27 | return broadcastToFollowers(data, byActor, accountsInvolvedInVideo, t, followersException) | 27 | return broadcastToFollowers(data, byActor, accountsInvolvedInVideo, t, followersException) |
28 | } | 28 | } |
29 | 29 | ||
30 | async function likeActivityData ( | 30 | function likeActivityData (url: string, byActor: ActorModel, video: VideoModel, audience?: ActivityAudience): ActivityLike { |
31 | url: string, | 31 | if (!audience) audience = getAudience(byActor) |
32 | byActor: ActorModel, | 32 | |
33 | video: VideoModel, | 33 | return audiencify( |
34 | t: Transaction, | 34 | { |
35 | audience?: ActivityAudience | 35 | type: 'Like' as 'Like', |
36 | ): Promise<ActivityLike> { | 36 | id: url, |
37 | if (!audience) { | 37 | actor: byActor.url, |
38 | audience = await getAudience(byActor, t) | 38 | object: video.url |
39 | } | 39 | }, |
40 | 40 | audience | |
41 | return audiencify({ | 41 | ) |
42 | type: 'Like' as 'Like', | ||
43 | id: url, | ||
44 | actor: byActor.url, | ||
45 | object: video.url | ||
46 | }, audience) | ||
47 | } | 42 | } |
48 | 43 | ||
49 | // --------------------------------------------------------------------------- | 44 | // --------------------------------------------------------------------------- |
diff --git a/server/lib/activitypub/send/send-undo.ts b/server/lib/activitypub/send/send-undo.ts index 9733e66dc..33c3d2429 100644 --- a/server/lib/activitypub/send/send-undo.ts +++ b/server/lib/activitypub/send/send-undo.ts | |||
@@ -27,7 +27,7 @@ async function sendUndoFollow (actorFollow: ActorFollowModel, t: Transaction) { | |||
27 | const undoUrl = getUndoActivityPubUrl(followUrl) | 27 | const undoUrl = getUndoActivityPubUrl(followUrl) |
28 | 28 | ||
29 | const object = followActivityData(followUrl, me, following) | 29 | const object = followActivityData(followUrl, me, following) |
30 | const data = await undoActivityData(undoUrl, me, object, t) | 30 | const data = undoActivityData(undoUrl, me, object) |
31 | 31 | ||
32 | return unicastTo(data, me, following.inboxUrl) | 32 | return unicastTo(data, me, following.inboxUrl) |
33 | } | 33 | } |
@@ -37,18 +37,18 @@ async function sendUndoLike (byActor: ActorModel, video: VideoModel, t: Transact | |||
37 | const undoUrl = getUndoActivityPubUrl(likeUrl) | 37 | const undoUrl = getUndoActivityPubUrl(likeUrl) |
38 | 38 | ||
39 | const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, t) | 39 | const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, t) |
40 | const object = await likeActivityData(likeUrl, byActor, video, t) | 40 | const object = likeActivityData(likeUrl, byActor, video) |
41 | 41 | ||
42 | // Send to origin | 42 | // Send to origin |
43 | if (video.isOwned() === false) { | 43 | if (video.isOwned() === false) { |
44 | const audience = getVideoAudience(video, actorsInvolvedInVideo) | 44 | const audience = getVideoAudience(video, actorsInvolvedInVideo) |
45 | const data = await undoActivityData(undoUrl, byActor, object, t, audience) | 45 | const data = undoActivityData(undoUrl, byActor, object, audience) |
46 | 46 | ||
47 | return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl) | 47 | return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl) |
48 | } | 48 | } |
49 | 49 | ||
50 | const audience = getObjectFollowersAudience(actorsInvolvedInVideo) | 50 | const audience = getObjectFollowersAudience(actorsInvolvedInVideo) |
51 | const data = await undoActivityData(undoUrl, byActor, object, t, audience) | 51 | const data = undoActivityData(undoUrl, byActor, object, audience) |
52 | 52 | ||
53 | const followersException = [ byActor ] | 53 | const followersException = [ byActor ] |
54 | return broadcastToFollowers(data, byActor, actorsInvolvedInVideo, t, followersException) | 54 | return broadcastToFollowers(data, byActor, actorsInvolvedInVideo, t, followersException) |
@@ -60,16 +60,16 @@ async function sendUndoDislike (byActor: ActorModel, video: VideoModel, t: Trans | |||
60 | 60 | ||
61 | const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, t) | 61 | const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, t) |
62 | const dislikeActivity = createDislikeActivityData(byActor, video) | 62 | const dislikeActivity = createDislikeActivityData(byActor, video) |
63 | const object = await createActivityData(dislikeUrl, byActor, dislikeActivity, t) | 63 | const object = createActivityData(dislikeUrl, byActor, dislikeActivity) |
64 | 64 | ||
65 | if (video.isOwned() === false) { | 65 | if (video.isOwned() === false) { |
66 | const audience = getVideoAudience(video, actorsInvolvedInVideo) | 66 | const audience = getVideoAudience(video, actorsInvolvedInVideo) |
67 | const data = await undoActivityData(undoUrl, byActor, object, t, audience) | 67 | const data = undoActivityData(undoUrl, byActor, object, audience) |
68 | 68 | ||
69 | return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl) | 69 | return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl) |
70 | } | 70 | } |
71 | 71 | ||
72 | const data = await undoActivityData(undoUrl, byActor, object, t) | 72 | const data = undoActivityData(undoUrl, byActor, object) |
73 | 73 | ||
74 | const followersException = [ byActor ] | 74 | const followersException = [ byActor ] |
75 | return broadcastToFollowers(data, byActor, actorsInvolvedInVideo, t, followersException) | 75 | return broadcastToFollowers(data, byActor, actorsInvolvedInVideo, t, followersException) |
@@ -80,7 +80,7 @@ async function sendUndoAnnounce (byActor: ActorModel, videoShare: VideoShareMode | |||
80 | 80 | ||
81 | const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, t) | 81 | const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, t) |
82 | const object = await buildVideoAnnounce(byActor, videoShare, video, t) | 82 | const object = await buildVideoAnnounce(byActor, videoShare, video, t) |
83 | const data = await undoActivityData(undoUrl, byActor, object, t) | 83 | const data = undoActivityData(undoUrl, byActor, object) |
84 | 84 | ||
85 | const followersException = [ byActor ] | 85 | const followersException = [ byActor ] |
86 | return broadcastToFollowers(data, byActor, actorsInvolvedInVideo, t, followersException) | 86 | return broadcastToFollowers(data, byActor, actorsInvolvedInVideo, t, followersException) |
@@ -97,21 +97,21 @@ export { | |||
97 | 97 | ||
98 | // --------------------------------------------------------------------------- | 98 | // --------------------------------------------------------------------------- |
99 | 99 | ||
100 | async function undoActivityData ( | 100 | function undoActivityData ( |
101 | url: string, | 101 | url: string, |
102 | byActor: ActorModel, | 102 | byActor: ActorModel, |
103 | object: ActivityFollow | ActivityLike | ActivityCreate | ActivityAnnounce, | 103 | object: ActivityFollow | ActivityLike | ActivityCreate | ActivityAnnounce, |
104 | t: Transaction, | ||
105 | audience?: ActivityAudience | 104 | audience?: ActivityAudience |
106 | ): Promise<ActivityUndo> { | 105 | ): ActivityUndo { |
107 | if (!audience) { | 106 | if (!audience) audience = getAudience(byActor) |
108 | audience = await getAudience(byActor, t) | 107 | |
109 | } | 108 | return audiencify( |
110 | 109 | { | |
111 | return audiencify({ | 110 | type: 'Undo' as 'Undo', |
112 | type: 'Undo' as 'Undo', | 111 | id: url, |
113 | id: url, | 112 | actor: byActor.url, |
114 | actor: byActor.url, | 113 | object |
115 | object | 114 | }, |
116 | }, audience) | 115 | audience |
116 | ) | ||
117 | } | 117 | } |
diff --git a/server/lib/activitypub/send/send-update.ts b/server/lib/activitypub/send/send-update.ts index d64b88343..2fd374ec6 100644 --- a/server/lib/activitypub/send/send-update.ts +++ b/server/lib/activitypub/send/send-update.ts | |||
@@ -15,9 +15,9 @@ async function sendUpdateVideo (video: VideoModel, t: Transaction) { | |||
15 | 15 | ||
16 | const url = getUpdateActivityPubUrl(video.url, video.updatedAt.toISOString()) | 16 | const url = getUpdateActivityPubUrl(video.url, video.updatedAt.toISOString()) |
17 | const videoObject = video.toActivityPubObject() | 17 | const videoObject = video.toActivityPubObject() |
18 | const audience = await getAudience(byActor, t, video.privacy === VideoPrivacy.PUBLIC) | 18 | const audience = getAudience(byActor, video.privacy === VideoPrivacy.PUBLIC) |
19 | 19 | ||
20 | const data = await updateActivityData(url, byActor, videoObject, t, audience) | 20 | const data = updateActivityData(url, byActor, videoObject, audience) |
21 | 21 | ||
22 | const actorsInvolved = await VideoShareModel.loadActorsByShare(video.id, t) | 22 | const actorsInvolved = await VideoShareModel.loadActorsByShare(video.id, t) |
23 | actorsInvolved.push(byActor) | 23 | actorsInvolved.push(byActor) |
@@ -30,8 +30,8 @@ async function sendUpdateActor (accountOrChannel: AccountModel | VideoChannelMod | |||
30 | 30 | ||
31 | const url = getUpdateActivityPubUrl(byActor.url, byActor.updatedAt.toISOString()) | 31 | const url = getUpdateActivityPubUrl(byActor.url, byActor.updatedAt.toISOString()) |
32 | const accountOrChannelObject = accountOrChannel.toActivityPubObject() | 32 | const accountOrChannelObject = accountOrChannel.toActivityPubObject() |
33 | const audience = await getAudience(byActor, t) | 33 | const audience = getAudience(byActor) |
34 | const data = await updateActivityData(url, byActor, accountOrChannelObject, t, audience) | 34 | const data = updateActivityData(url, byActor, accountOrChannelObject, audience) |
35 | 35 | ||
36 | let actorsInvolved: ActorModel[] | 36 | let actorsInvolved: ActorModel[] |
37 | if (accountOrChannel instanceof AccountModel) { | 37 | if (accountOrChannel instanceof AccountModel) { |
@@ -56,21 +56,17 @@ export { | |||
56 | 56 | ||
57 | // --------------------------------------------------------------------------- | 57 | // --------------------------------------------------------------------------- |
58 | 58 | ||
59 | async function updateActivityData ( | 59 | function updateActivityData (url: string, byActor: ActorModel, object: any, audience?: ActivityAudience): ActivityUpdate { |
60 | url: string, | 60 | if (!audience) audience = getAudience(byActor) |
61 | byActor: ActorModel, | ||
62 | object: any, | ||
63 | t: Transaction, | ||
64 | audience?: ActivityAudience | ||
65 | ): Promise<ActivityUpdate> { | ||
66 | if (!audience) { | ||
67 | audience = await getAudience(byActor, t) | ||
68 | } | ||
69 | 61 | ||
70 | return audiencify({ | 62 | return audiencify( |
71 | type: 'Update' as 'Update', | 63 | { |
72 | id: url, | 64 | type: 'Update' as 'Update', |
73 | actor: byActor.url, | 65 | id: url, |
74 | object: audiencify(object, audience) | 66 | actor: byActor.url, |
75 | }, audience) | 67 | object: audiencify(object, audience |
68 | ) | ||
69 | }, | ||
70 | audience | ||
71 | ) | ||
76 | } | 72 | } |
diff --git a/server/lib/activitypub/videos.ts b/server/lib/activitypub/videos.ts index 907f7e11e..7ec8ca193 100644 --- a/server/lib/activitypub/videos.ts +++ b/server/lib/activitypub/videos.ts | |||
@@ -1,8 +1,9 @@ | |||
1 | import * as Bluebird from 'bluebird' | 1 | import * as Bluebird from 'bluebird' |
2 | import * as sequelize from 'sequelize' | ||
2 | import * as magnetUtil from 'magnet-uri' | 3 | import * as magnetUtil from 'magnet-uri' |
3 | import { join } from 'path' | 4 | import { join } from 'path' |
4 | import * as request from 'request' | 5 | import * as request from 'request' |
5 | import { ActivityIconObject } from '../../../shared/index' | 6 | import { ActivityIconObject, VideoState } from '../../../shared/index' |
6 | import { VideoTorrentObject } from '../../../shared/models/activitypub/objects' | 7 | import { VideoTorrentObject } from '../../../shared/models/activitypub/objects' |
7 | import { VideoPrivacy, VideoRateType } from '../../../shared/models/videos' | 8 | import { VideoPrivacy, VideoRateType } from '../../../shared/models/videos' |
8 | import { sanitizeAndCheckVideoTorrentObject } from '../../helpers/custom-validators/activitypub/videos' | 9 | import { sanitizeAndCheckVideoTorrentObject } from '../../helpers/custom-validators/activitypub/videos' |
@@ -21,6 +22,21 @@ import { VideoShareModel } from '../../models/video/video-share' | |||
21 | import { getOrCreateActorAndServerAndModel } from './actor' | 22 | import { getOrCreateActorAndServerAndModel } from './actor' |
22 | import { addVideoComments } from './video-comments' | 23 | import { addVideoComments } from './video-comments' |
23 | import { crawlCollectionPage } from './crawl' | 24 | import { crawlCollectionPage } from './crawl' |
25 | import { sendCreateVideo, sendUpdateVideo } from './send' | ||
26 | import { shareVideoByServerAndChannel } from './index' | ||
27 | |||
28 | async function federateVideoIfNeeded (video: VideoModel, isNewVideo: boolean, transaction?: sequelize.Transaction) { | ||
29 | // If the video is not private and published, we federate it | ||
30 | if (video.privacy !== VideoPrivacy.PRIVATE && video.state === VideoState.PUBLISHED) { | ||
31 | if (isNewVideo === true) { | ||
32 | // Now we'll add the video's meta data to our followers | ||
33 | await sendCreateVideo(video, transaction) | ||
34 | await shareVideoByServerAndChannel(video, transaction) | ||
35 | } else { | ||
36 | await sendUpdateVideo(video, transaction) | ||
37 | } | ||
38 | } | ||
39 | } | ||
24 | 40 | ||
25 | function fetchRemoteVideoPreview (video: VideoModel, reject: Function) { | 41 | function fetchRemoteVideoPreview (video: VideoModel, reject: Function) { |
26 | const host = video.VideoChannel.Account.Actor.Server.host | 42 | const host = video.VideoChannel.Account.Actor.Server.host |
@@ -55,9 +71,11 @@ function generateThumbnailFromUrl (video: VideoModel, icon: ActivityIconObject) | |||
55 | return doRequestAndSaveToFile(options, thumbnailPath) | 71 | return doRequestAndSaveToFile(options, thumbnailPath) |
56 | } | 72 | } |
57 | 73 | ||
58 | async function videoActivityObjectToDBAttributes (videoChannel: VideoChannelModel, | 74 | async function videoActivityObjectToDBAttributes ( |
59 | videoObject: VideoTorrentObject, | 75 | videoChannel: VideoChannelModel, |
60 | to: string[] = []) { | 76 | videoObject: VideoTorrentObject, |
77 | to: string[] = [] | ||
78 | ) { | ||
61 | const privacy = to.indexOf(ACTIVITY_PUB.PUBLIC) !== -1 ? VideoPrivacy.PUBLIC : VideoPrivacy.UNLISTED | 79 | const privacy = to.indexOf(ACTIVITY_PUB.PUBLIC) !== -1 ? VideoPrivacy.PUBLIC : VideoPrivacy.UNLISTED |
62 | const duration = videoObject.duration.replace(/[^\d]+/, '') | 80 | const duration = videoObject.duration.replace(/[^\d]+/, '') |
63 | 81 | ||
@@ -90,6 +108,8 @@ async function videoActivityObjectToDBAttributes (videoChannel: VideoChannelMode | |||
90 | support, | 108 | support, |
91 | nsfw: videoObject.sensitive, | 109 | nsfw: videoObject.sensitive, |
92 | commentsEnabled: videoObject.commentsEnabled, | 110 | commentsEnabled: videoObject.commentsEnabled, |
111 | waitTranscoding: videoObject.waitTranscoding, | ||
112 | state: videoObject.state, | ||
93 | channelId: videoChannel.id, | 113 | channelId: videoChannel.id, |
94 | duration: parseInt(duration, 10), | 114 | duration: parseInt(duration, 10), |
95 | createdAt: new Date(videoObject.published), | 115 | createdAt: new Date(videoObject.published), |
@@ -185,22 +205,20 @@ async function getOrCreateVideo (videoObject: VideoTorrentObject, channelActor: | |||
185 | } | 205 | } |
186 | 206 | ||
187 | async function getOrCreateAccountAndVideoAndChannel (videoObject: VideoTorrentObject | string, actor?: ActorModel) { | 207 | async function getOrCreateAccountAndVideoAndChannel (videoObject: VideoTorrentObject | string, actor?: ActorModel) { |
188 | if (typeof videoObject === 'string') { | 208 | const videoUrl = typeof videoObject === 'string' ? videoObject : videoObject.id |
189 | const videoUrl = videoObject | 209 | |
190 | 210 | const videoFromDatabase = await VideoModel.loadByUrlAndPopulateAccount(videoUrl) | |
191 | const videoFromDatabase = await VideoModel.loadByUrlAndPopulateAccount(videoUrl) | 211 | if (videoFromDatabase) { |
192 | if (videoFromDatabase) { | 212 | return { |
193 | return { | 213 | video: videoFromDatabase, |
194 | video: videoFromDatabase, | 214 | actor: videoFromDatabase.VideoChannel.Account.Actor, |
195 | actor: videoFromDatabase.VideoChannel.Account.Actor, | 215 | channelActor: videoFromDatabase.VideoChannel.Actor |
196 | channelActor: videoFromDatabase.VideoChannel.Actor | ||
197 | } | ||
198 | } | 216 | } |
199 | |||
200 | videoObject = await fetchRemoteVideo(videoUrl) | ||
201 | if (!videoObject) throw new Error('Cannot fetch remote video with url: ' + videoUrl) | ||
202 | } | 217 | } |
203 | 218 | ||
219 | videoObject = await fetchRemoteVideo(videoUrl) | ||
220 | if (!videoObject) throw new Error('Cannot fetch remote video with url: ' + videoUrl) | ||
221 | |||
204 | if (!actor) { | 222 | if (!actor) { |
205 | const actorObj = videoObject.attributedTo.find(a => a.type === 'Person') | 223 | const actorObj = videoObject.attributedTo.find(a => a.type === 'Person') |
206 | if (!actorObj) throw new Error('Cannot find associated actor to video ' + videoObject.url) | 224 | if (!actorObj) throw new Error('Cannot find associated actor to video ' + videoObject.url) |
@@ -291,20 +309,6 @@ async function addVideoShares (shareUrls: string[], instance: VideoModel) { | |||
291 | } | 309 | } |
292 | } | 310 | } |
293 | 311 | ||
294 | export { | ||
295 | getOrCreateAccountAndVideoAndChannel, | ||
296 | fetchRemoteVideoPreview, | ||
297 | fetchRemoteVideoDescription, | ||
298 | generateThumbnailFromUrl, | ||
299 | videoActivityObjectToDBAttributes, | ||
300 | videoFileActivityUrlToDBAttributes, | ||
301 | getOrCreateVideo, | ||
302 | getOrCreateVideoChannel, | ||
303 | addVideoShares | ||
304 | } | ||
305 | |||
306 | // --------------------------------------------------------------------------- | ||
307 | |||
308 | async function fetchRemoteVideo (videoUrl: string): Promise<VideoTorrentObject> { | 312 | async function fetchRemoteVideo (videoUrl: string): Promise<VideoTorrentObject> { |
309 | const options = { | 313 | const options = { |
310 | uri: videoUrl, | 314 | uri: videoUrl, |
@@ -324,3 +328,17 @@ async function fetchRemoteVideo (videoUrl: string): Promise<VideoTorrentObject> | |||
324 | 328 | ||
325 | return body | 329 | return body |
326 | } | 330 | } |
331 | |||
332 | export { | ||
333 | federateVideoIfNeeded, | ||
334 | fetchRemoteVideo, | ||
335 | getOrCreateAccountAndVideoAndChannel, | ||
336 | fetchRemoteVideoPreview, | ||
337 | fetchRemoteVideoDescription, | ||
338 | generateThumbnailFromUrl, | ||
339 | videoActivityObjectToDBAttributes, | ||
340 | videoFileActivityUrlToDBAttributes, | ||
341 | getOrCreateVideo, | ||
342 | getOrCreateVideoChannel, | ||
343 | addVideoShares | ||
344 | } | ||
diff --git a/server/lib/job-queue/handlers/video-file.ts b/server/lib/job-queue/handlers/video-file.ts index 85f7dbfc2..f5ad076a6 100644 --- a/server/lib/job-queue/handlers/video-file.ts +++ b/server/lib/job-queue/handlers/video-file.ts | |||
@@ -1,17 +1,16 @@ | |||
1 | import * as kue from 'kue' | 1 | import * as kue from 'kue' |
2 | import { VideoResolution } from '../../../../shared' | 2 | import { VideoResolution, VideoState } from '../../../../shared' |
3 | import { VideoPrivacy } from '../../../../shared/models/videos' | ||
4 | import { logger } from '../../../helpers/logger' | 3 | import { logger } from '../../../helpers/logger' |
5 | import { computeResolutionsToTranscode } from '../../../helpers/utils' | 4 | import { computeResolutionsToTranscode } from '../../../helpers/utils' |
6 | import { sequelizeTypescript } from '../../../initializers' | ||
7 | import { VideoModel } from '../../../models/video/video' | 5 | import { VideoModel } from '../../../models/video/video' |
8 | import { shareVideoByServerAndChannel } from '../../activitypub' | ||
9 | import { sendCreateVideo, sendUpdateVideo } from '../../activitypub/send' | ||
10 | import { JobQueue } from '../job-queue' | 6 | import { JobQueue } from '../job-queue' |
7 | import { federateVideoIfNeeded } from '../../activitypub' | ||
8 | import { retryTransactionWrapper } from '../../../helpers/database-utils' | ||
9 | import { sequelizeTypescript } from '../../../initializers' | ||
11 | 10 | ||
12 | export type VideoFilePayload = { | 11 | export type VideoFilePayload = { |
13 | videoUUID: string | 12 | videoUUID: string |
14 | isNewVideo: boolean | 13 | isNewVideo?: boolean |
15 | resolution?: VideoResolution | 14 | resolution?: VideoResolution |
16 | isPortraitMode?: boolean | 15 | isPortraitMode?: boolean |
17 | } | 16 | } |
@@ -52,10 +51,20 @@ async function processVideoFile (job: kue.Job) { | |||
52 | // Transcoding in other resolution | 51 | // Transcoding in other resolution |
53 | if (payload.resolution) { | 52 | if (payload.resolution) { |
54 | await video.transcodeOriginalVideofile(payload.resolution, payload.isPortraitMode) | 53 | await video.transcodeOriginalVideofile(payload.resolution, payload.isPortraitMode) |
55 | await onVideoFileTranscoderOrImportSuccess(video) | 54 | |
55 | const options = { | ||
56 | arguments: [ video ], | ||
57 | errorMessage: 'Cannot execute onVideoFileTranscoderOrImportSuccess with many retries.' | ||
58 | } | ||
59 | await retryTransactionWrapper(onVideoFileTranscoderOrImportSuccess, options) | ||
56 | } else { | 60 | } else { |
57 | await video.optimizeOriginalVideofile() | 61 | await video.optimizeOriginalVideofile() |
58 | await onVideoFileOptimizerSuccess(video, payload.isNewVideo) | 62 | |
63 | const options = { | ||
64 | arguments: [ video, payload.isNewVideo ], | ||
65 | errorMessage: 'Cannot execute onVideoFileOptimizerSuccess with many retries.' | ||
66 | } | ||
67 | await retryTransactionWrapper(onVideoFileOptimizerSuccess, options) | ||
59 | } | 68 | } |
60 | 69 | ||
61 | return video | 70 | return video |
@@ -64,68 +73,70 @@ async function processVideoFile (job: kue.Job) { | |||
64 | async function onVideoFileTranscoderOrImportSuccess (video: VideoModel) { | 73 | async function onVideoFileTranscoderOrImportSuccess (video: VideoModel) { |
65 | if (video === undefined) return undefined | 74 | if (video === undefined) return undefined |
66 | 75 | ||
67 | // Maybe the video changed in database, refresh it | 76 | return sequelizeTypescript.transaction(async t => { |
68 | const videoDatabase = await VideoModel.loadByUUIDAndPopulateAccountAndServerAndTags(video.uuid) | 77 | // Maybe the video changed in database, refresh it |
69 | // Video does not exist anymore | 78 | let videoDatabase = await VideoModel.loadByUUIDAndPopulateAccountAndServerAndTags(video.uuid, t) |
70 | if (!videoDatabase) return undefined | 79 | // Video does not exist anymore |
80 | if (!videoDatabase) return undefined | ||
71 | 81 | ||
72 | if (video.privacy !== VideoPrivacy.PRIVATE) { | 82 | // We transcoded the video file in another format, now we can publish it |
73 | await sendUpdateVideo(video, undefined) | 83 | const oldState = videoDatabase.state |
74 | } | 84 | videoDatabase.state = VideoState.PUBLISHED |
85 | videoDatabase = await videoDatabase.save({ transaction: t }) | ||
86 | |||
87 | // If the video was not published, we consider it is a new one for other instances | ||
88 | const isNewVideo = oldState !== VideoState.PUBLISHED | ||
89 | await federateVideoIfNeeded(videoDatabase, isNewVideo, t) | ||
75 | 90 | ||
76 | return undefined | 91 | return undefined |
92 | }) | ||
77 | } | 93 | } |
78 | 94 | ||
79 | async function onVideoFileOptimizerSuccess (video: VideoModel, isNewVideo: boolean) { | 95 | async function onVideoFileOptimizerSuccess (video: VideoModel, isNewVideo: boolean) { |
80 | if (video === undefined) return undefined | 96 | if (video === undefined) return undefined |
81 | 97 | ||
82 | // Maybe the video changed in database, refresh it | 98 | // Outside the transaction (IO on disk) |
83 | const videoDatabase = await VideoModel.loadByUUIDAndPopulateAccountAndServerAndTags(video.uuid) | 99 | const { videoFileResolution } = await video.getOriginalFileResolution() |
84 | // Video does not exist anymore | 100 | |
85 | if (!videoDatabase) return undefined | 101 | return sequelizeTypescript.transaction(async t => { |
86 | 102 | // Maybe the video changed in database, refresh it | |
87 | if (video.privacy !== VideoPrivacy.PRIVATE) { | 103 | const videoDatabase = await VideoModel.loadByUUIDAndPopulateAccountAndServerAndTags(video.uuid, t) |
88 | if (isNewVideo !== false) { | 104 | // Video does not exist anymore |
89 | // Now we'll add the video's meta data to our followers | 105 | if (!videoDatabase) return undefined |
90 | await sequelizeTypescript.transaction(async t => { | 106 | |
91 | await sendCreateVideo(video, t) | 107 | // Create transcoding jobs if there are enabled resolutions |
92 | await shareVideoByServerAndChannel(video, t) | 108 | const resolutionsEnabled = computeResolutionsToTranscode(videoFileResolution) |
93 | }) | 109 | logger.info( |
94 | } else { | 110 | 'Resolutions computed for video %s and origin file height of %d.', videoDatabase.uuid, videoFileResolution, |
95 | await sendUpdateVideo(video, undefined) | 111 | { resolutions: resolutionsEnabled } |
96 | } | 112 | ) |
97 | } | 113 | |
98 | 114 | if (resolutionsEnabled.length !== 0) { | |
99 | const { videoFileResolution } = await videoDatabase.getOriginalFileResolution() | 115 | const tasks: Promise<any>[] = [] |
100 | 116 | ||
101 | // Create transcoding jobs if there are enabled resolutions | 117 | for (const resolution of resolutionsEnabled) { |
102 | const resolutionsEnabled = computeResolutionsToTranscode(videoFileResolution) | 118 | const dataInput = { |
103 | logger.info( | 119 | videoUUID: videoDatabase.uuid, |
104 | 'Resolutions computed for video %s and origin file height of %d.', videoDatabase.uuid, videoFileResolution, | 120 | resolution |
105 | { resolutions: resolutionsEnabled } | 121 | } |
106 | ) | 122 | |
123 | const p = JobQueue.Instance.createJob({ type: 'video-file', payload: dataInput }) | ||
124 | tasks.push(p) | ||
125 | } | ||
107 | 126 | ||
108 | if (resolutionsEnabled.length !== 0) { | 127 | await Promise.all(tasks) |
109 | const tasks: Promise<any>[] = [] | ||
110 | 128 | ||
111 | for (const resolution of resolutionsEnabled) { | 129 | logger.info('Transcoding jobs created for uuid %s.', videoDatabase.uuid, { resolutionsEnabled }) |
112 | const dataInput = { | 130 | } else { |
113 | videoUUID: videoDatabase.uuid, | 131 | // No transcoding to do, it's now published |
114 | resolution, | 132 | video.state = VideoState.PUBLISHED |
115 | isNewVideo | 133 | video = await video.save({ transaction: t }) |
116 | } | ||
117 | 134 | ||
118 | const p = JobQueue.Instance.createJob({ type: 'video-file', payload: dataInput }) | 135 | logger.info('No transcoding jobs created for video %s (no resolutions).', video.uuid) |
119 | tasks.push(p) | ||
120 | } | 136 | } |
121 | 137 | ||
122 | await Promise.all(tasks) | 138 | return federateVideoIfNeeded(video, isNewVideo, t) |
123 | 139 | }) | |
124 | logger.info('Transcoding jobs created for uuid %s.', videoDatabase.uuid, { resolutionsEnabled }) | ||
125 | } else { | ||
126 | logger.info('No transcoding jobs created for video %s (no resolutions enabled).') | ||
127 | return undefined | ||
128 | } | ||
129 | } | 140 | } |
130 | 141 | ||
131 | // --------------------------------------------------------------------------- | 142 | // --------------------------------------------------------------------------- |
diff --git a/server/lib/job-queue/job-queue.ts b/server/lib/job-queue/job-queue.ts index bdfa19b61..695fe0eea 100644 --- a/server/lib/job-queue/job-queue.ts +++ b/server/lib/job-queue/job-queue.ts | |||
@@ -79,6 +79,7 @@ class JobQueue { | |||
79 | const res = await handlers[ handlerName ](job) | 79 | const res = await handlers[ handlerName ](job) |
80 | return done(null, res) | 80 | return done(null, res) |
81 | } catch (err) { | 81 | } catch (err) { |
82 | logger.error('Cannot execute job %d.', job.id, { err }) | ||
82 | return done(err) | 83 | return done(err) |
83 | } | 84 | } |
84 | }) | 85 | }) |
diff --git a/server/middlewares/cache.ts b/server/middlewares/cache.ts index bf6659687..1de44db70 100644 --- a/server/middlewares/cache.ts +++ b/server/middlewares/cache.ts | |||
@@ -14,7 +14,7 @@ function cacheRoute (lifetime: number) { | |||
14 | 14 | ||
15 | // Not cached | 15 | // Not cached |
16 | if (!cached) { | 16 | if (!cached) { |
17 | logger.debug('Not cached result for route %s.', req.originalUrl) | 17 | logger.debug('No cached results for route %s.', req.originalUrl) |
18 | 18 | ||
19 | const sendSave = res.send.bind(res) | 19 | const sendSave = res.send.bind(res) |
20 | 20 | ||
diff --git a/server/middlewares/validators/videos.ts b/server/middlewares/validators/videos.ts index c5c45fe58..e181aebdb 100644 --- a/server/middlewares/validators/videos.ts +++ b/server/middlewares/validators/videos.ts | |||
@@ -55,8 +55,13 @@ const videosAddValidator = [ | |||
55 | .customSanitizer(toValueOrNull) | 55 | .customSanitizer(toValueOrNull) |
56 | .custom(isVideoLanguageValid).withMessage('Should have a valid language'), | 56 | .custom(isVideoLanguageValid).withMessage('Should have a valid language'), |
57 | body('nsfw') | 57 | body('nsfw') |
58 | .optional() | ||
58 | .toBoolean() | 59 | .toBoolean() |
59 | .custom(isBooleanValid).withMessage('Should have a valid NSFW attribute'), | 60 | .custom(isBooleanValid).withMessage('Should have a valid NSFW attribute'), |
61 | body('waitTranscoding') | ||
62 | .optional() | ||
63 | .toBoolean() | ||
64 | .custom(isBooleanValid).withMessage('Should have a valid wait transcoding attribute'), | ||
60 | body('description') | 65 | body('description') |
61 | .optional() | 66 | .optional() |
62 | .customSanitizer(toValueOrNull) | 67 | .customSanitizer(toValueOrNull) |
@@ -70,6 +75,7 @@ const videosAddValidator = [ | |||
70 | .customSanitizer(toValueOrNull) | 75 | .customSanitizer(toValueOrNull) |
71 | .custom(isVideoTagsValid).withMessage('Should have correct tags'), | 76 | .custom(isVideoTagsValid).withMessage('Should have correct tags'), |
72 | body('commentsEnabled') | 77 | body('commentsEnabled') |
78 | .optional() | ||
73 | .toBoolean() | 79 | .toBoolean() |
74 | .custom(isBooleanValid).withMessage('Should have comments enabled boolean'), | 80 | .custom(isBooleanValid).withMessage('Should have comments enabled boolean'), |
75 | body('privacy') | 81 | body('privacy') |
@@ -149,6 +155,10 @@ const videosUpdateValidator = [ | |||
149 | .optional() | 155 | .optional() |
150 | .toBoolean() | 156 | .toBoolean() |
151 | .custom(isBooleanValid).withMessage('Should have a valid NSFW attribute'), | 157 | .custom(isBooleanValid).withMessage('Should have a valid NSFW attribute'), |
158 | body('waitTranscoding') | ||
159 | .optional() | ||
160 | .toBoolean() | ||
161 | .custom(isBooleanValid).withMessage('Should have a valid wait transcoding attribute'), | ||
152 | body('privacy') | 162 | body('privacy') |
153 | .optional() | 163 | .optional() |
154 | .toInt() | 164 | .toInt() |
diff --git a/server/models/video/video.ts b/server/models/video/video.ts index 1cb1e6798..59c378efa 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts | |||
@@ -25,7 +25,7 @@ import { | |||
25 | Table, | 25 | Table, |
26 | UpdatedAt | 26 | UpdatedAt |
27 | } from 'sequelize-typescript' | 27 | } from 'sequelize-typescript' |
28 | import { VideoPrivacy, VideoResolution } from '../../../shared' | 28 | import { VideoPrivacy, VideoResolution, VideoState } from '../../../shared' |
29 | import { VideoTorrentObject } from '../../../shared/models/activitypub/objects' | 29 | import { VideoTorrentObject } from '../../../shared/models/activitypub/objects' |
30 | import { Video, VideoDetails, VideoFile } from '../../../shared/models/videos' | 30 | import { Video, VideoDetails, VideoFile } from '../../../shared/models/videos' |
31 | import { VideoFilter } from '../../../shared/models/videos/video-query.type' | 31 | import { VideoFilter } from '../../../shared/models/videos/video-query.type' |
@@ -47,7 +47,7 @@ import { | |||
47 | isVideoLanguageValid, | 47 | isVideoLanguageValid, |
48 | isVideoLicenceValid, | 48 | isVideoLicenceValid, |
49 | isVideoNameValid, | 49 | isVideoNameValid, |
50 | isVideoPrivacyValid, | 50 | isVideoPrivacyValid, isVideoStateValid, |
51 | isVideoSupportValid | 51 | isVideoSupportValid |
52 | } from '../../helpers/custom-validators/videos' | 52 | } from '../../helpers/custom-validators/videos' |
53 | import { generateImageFromVideoFile, getVideoFileResolution, transcode } from '../../helpers/ffmpeg-utils' | 53 | import { generateImageFromVideoFile, getVideoFileResolution, transcode } from '../../helpers/ffmpeg-utils' |
@@ -66,7 +66,7 @@ import { | |||
66 | VIDEO_EXT_MIMETYPE, | 66 | VIDEO_EXT_MIMETYPE, |
67 | VIDEO_LANGUAGES, | 67 | VIDEO_LANGUAGES, |
68 | VIDEO_LICENCES, | 68 | VIDEO_LICENCES, |
69 | VIDEO_PRIVACIES | 69 | VIDEO_PRIVACIES, VIDEO_STATES |
70 | } from '../../initializers' | 70 | } from '../../initializers' |
71 | import { | 71 | import { |
72 | getVideoCommentsActivityPubUrl, | 72 | getVideoCommentsActivityPubUrl, |
@@ -93,10 +93,7 @@ enum ScopeNames { | |||
93 | AVAILABLE_FOR_LIST = 'AVAILABLE_FOR_LIST', | 93 | AVAILABLE_FOR_LIST = 'AVAILABLE_FOR_LIST', |
94 | WITH_ACCOUNT_DETAILS = 'WITH_ACCOUNT_DETAILS', | 94 | WITH_ACCOUNT_DETAILS = 'WITH_ACCOUNT_DETAILS', |
95 | WITH_TAGS = 'WITH_TAGS', | 95 | WITH_TAGS = 'WITH_TAGS', |
96 | WITH_FILES = 'WITH_FILES', | 96 | WITH_FILES = 'WITH_FILES' |
97 | WITH_SHARES = 'WITH_SHARES', | ||
98 | WITH_RATES = 'WITH_RATES', | ||
99 | WITH_COMMENTS = 'WITH_COMMENTS' | ||
100 | } | 97 | } |
101 | 98 | ||
102 | @Scopes({ | 99 | @Scopes({ |
@@ -183,7 +180,20 @@ enum ScopeNames { | |||
183 | ')' | 180 | ')' |
184 | ) | 181 | ) |
185 | }, | 182 | }, |
186 | privacy: VideoPrivacy.PUBLIC | 183 | // Always list public videos |
184 | privacy: VideoPrivacy.PUBLIC, | ||
185 | // Always list published videos, or videos that are being transcoded but on which we don't want to wait for transcoding | ||
186 | [ Sequelize.Op.or ]: [ | ||
187 | { | ||
188 | state: VideoState.PUBLISHED | ||
189 | }, | ||
190 | { | ||
191 | [ Sequelize.Op.and ]: { | ||
192 | state: VideoState.TO_TRANSCODE, | ||
193 | waitTranscoding: false | ||
194 | } | ||
195 | } | ||
196 | ] | ||
187 | }, | 197 | }, |
188 | include: [ videoChannelInclude ] | 198 | include: [ videoChannelInclude ] |
189 | } | 199 | } |
@@ -272,42 +282,6 @@ enum ScopeNames { | |||
272 | required: true | 282 | required: true |
273 | } | 283 | } |
274 | ] | 284 | ] |
275 | }, | ||
276 | [ScopeNames.WITH_SHARES]: { | ||
277 | include: [ | ||
278 | { | ||
279 | ['separate' as any]: true, | ||
280 | model: () => VideoShareModel.unscoped() | ||
281 | } | ||
282 | ] | ||
283 | }, | ||
284 | [ScopeNames.WITH_RATES]: { | ||
285 | include: [ | ||
286 | { | ||
287 | ['separate' as any]: true, | ||
288 | model: () => AccountVideoRateModel, | ||
289 | include: [ | ||
290 | { | ||
291 | model: () => AccountModel.unscoped(), | ||
292 | required: true, | ||
293 | include: [ | ||
294 | { | ||
295 | attributes: [ 'url' ], | ||
296 | model: () => ActorModel.unscoped() | ||
297 | } | ||
298 | ] | ||
299 | } | ||
300 | ] | ||
301 | } | ||
302 | ] | ||
303 | }, | ||
304 | [ScopeNames.WITH_COMMENTS]: { | ||
305 | include: [ | ||
306 | { | ||
307 | ['separate' as any]: true, | ||
308 | model: () => VideoCommentModel.unscoped() | ||
309 | } | ||
310 | ] | ||
311 | } | 285 | } |
312 | }) | 286 | }) |
313 | @Table({ | 287 | @Table({ |
@@ -335,7 +309,7 @@ enum ScopeNames { | |||
335 | fields: [ 'channelId' ] | 309 | fields: [ 'channelId' ] |
336 | }, | 310 | }, |
337 | { | 311 | { |
338 | fields: [ 'id', 'privacy' ] | 312 | fields: [ 'id', 'privacy', 'state', 'waitTranscoding' ] |
339 | }, | 313 | }, |
340 | { | 314 | { |
341 | fields: [ 'url'], | 315 | fields: [ 'url'], |
@@ -435,6 +409,16 @@ export class VideoModel extends Model<VideoModel> { | |||
435 | @Column | 409 | @Column |
436 | commentsEnabled: boolean | 410 | commentsEnabled: boolean |
437 | 411 | ||
412 | @AllowNull(false) | ||
413 | @Column | ||
414 | waitTranscoding: boolean | ||
415 | |||
416 | @AllowNull(false) | ||
417 | @Default(null) | ||
418 | @Is('VideoState', value => throwIfNotValid(value, isVideoStateValid, 'state')) | ||
419 | @Column | ||
420 | state: VideoState | ||
421 | |||
438 | @CreatedAt | 422 | @CreatedAt |
439 | createdAt: Date | 423 | createdAt: Date |
440 | 424 | ||
@@ -671,7 +655,7 @@ export class VideoModel extends Model<VideoModel> { | |||
671 | }) | 655 | }) |
672 | } | 656 | } |
673 | 657 | ||
674 | static listAccountVideosForApi (accountId: number, start: number, count: number, sort: string, hideNSFW: boolean, withFiles = false) { | 658 | static listUserVideosForApi (accountId: number, start: number, count: number, sort: string, hideNSFW: boolean, withFiles = false) { |
675 | const query: IFindOptions<VideoModel> = { | 659 | const query: IFindOptions<VideoModel> = { |
676 | offset: start, | 660 | offset: start, |
677 | limit: count, | 661 | limit: count, |
@@ -858,12 +842,13 @@ export class VideoModel extends Model<VideoModel> { | |||
858 | .findOne(options) | 842 | .findOne(options) |
859 | } | 843 | } |
860 | 844 | ||
861 | static loadByUUIDAndPopulateAccountAndServerAndTags (uuid: string) { | 845 | static loadByUUIDAndPopulateAccountAndServerAndTags (uuid: string, t?: Sequelize.Transaction) { |
862 | const options = { | 846 | const options = { |
863 | order: [ [ 'Tags', 'name', 'ASC' ] ], | 847 | order: [ [ 'Tags', 'name', 'ASC' ] ], |
864 | where: { | 848 | where: { |
865 | uuid | 849 | uuid |
866 | } | 850 | }, |
851 | transaction: t | ||
867 | } | 852 | } |
868 | 853 | ||
869 | return VideoModel | 854 | return VideoModel |
@@ -905,31 +890,23 @@ export class VideoModel extends Model<VideoModel> { | |||
905 | } | 890 | } |
906 | 891 | ||
907 | private static getCategoryLabel (id: number) { | 892 | private static getCategoryLabel (id: number) { |
908 | let categoryLabel = VIDEO_CATEGORIES[id] | 893 | return VIDEO_CATEGORIES[id] || 'Misc' |
909 | if (!categoryLabel) categoryLabel = 'Misc' | ||
910 | |||
911 | return categoryLabel | ||
912 | } | 894 | } |
913 | 895 | ||
914 | private static getLicenceLabel (id: number) { | 896 | private static getLicenceLabel (id: number) { |
915 | let licenceLabel = VIDEO_LICENCES[id] | 897 | return VIDEO_LICENCES[id] || 'Unknown' |
916 | if (!licenceLabel) licenceLabel = 'Unknown' | ||
917 | |||
918 | return licenceLabel | ||
919 | } | 898 | } |
920 | 899 | ||
921 | private static getLanguageLabel (id: string) { | 900 | private static getLanguageLabel (id: string) { |
922 | let languageLabel = VIDEO_LANGUAGES[id] | 901 | return VIDEO_LANGUAGES[id] || 'Unknown' |
923 | if (!languageLabel) languageLabel = 'Unknown' | ||
924 | |||
925 | return languageLabel | ||
926 | } | 902 | } |
927 | 903 | ||
928 | private static getPrivacyLabel (id: number) { | 904 | private static getPrivacyLabel (id: number) { |
929 | let privacyLabel = VIDEO_PRIVACIES[id] | 905 | return VIDEO_PRIVACIES[id] || 'Unknown' |
930 | if (!privacyLabel) privacyLabel = 'Unknown' | 906 | } |
931 | 907 | ||
932 | return privacyLabel | 908 | private static getStateLabel (id: number) { |
909 | return VIDEO_STATES[id] || 'Unknown' | ||
933 | } | 910 | } |
934 | 911 | ||
935 | getOriginalFile () { | 912 | getOriginalFile () { |
@@ -1026,11 +1003,16 @@ export class VideoModel extends Model<VideoModel> { | |||
1026 | return join(STATIC_PATHS.PREVIEWS, this.getPreviewName()) | 1003 | return join(STATIC_PATHS.PREVIEWS, this.getPreviewName()) |
1027 | } | 1004 | } |
1028 | 1005 | ||
1029 | toFormattedJSON (): Video { | 1006 | toFormattedJSON (options?: { |
1007 | additionalAttributes: { | ||
1008 | state: boolean, | ||
1009 | waitTranscoding: boolean | ||
1010 | } | ||
1011 | }): Video { | ||
1030 | const formattedAccount = this.VideoChannel.Account.toFormattedJSON() | 1012 | const formattedAccount = this.VideoChannel.Account.toFormattedJSON() |
1031 | const formattedVideoChannel = this.VideoChannel.toFormattedJSON() | 1013 | const formattedVideoChannel = this.VideoChannel.toFormattedJSON() |
1032 | 1014 | ||
1033 | return { | 1015 | const videoObject: Video = { |
1034 | id: this.id, | 1016 | id: this.id, |
1035 | uuid: this.uuid, | 1017 | uuid: this.uuid, |
1036 | name: this.name, | 1018 | name: this.name, |
@@ -1082,6 +1064,19 @@ export class VideoModel extends Model<VideoModel> { | |||
1082 | avatar: formattedVideoChannel.avatar | 1064 | avatar: formattedVideoChannel.avatar |
1083 | } | 1065 | } |
1084 | } | 1066 | } |
1067 | |||
1068 | if (options) { | ||
1069 | if (options.additionalAttributes.state) { | ||
1070 | videoObject.state = { | ||
1071 | id: this.state, | ||
1072 | label: VideoModel.getStateLabel(this.state) | ||
1073 | } | ||
1074 | } | ||
1075 | |||
1076 | if (options.additionalAttributes.waitTranscoding) videoObject.waitTranscoding = this.waitTranscoding | ||
1077 | } | ||
1078 | |||
1079 | return videoObject | ||
1085 | } | 1080 | } |
1086 | 1081 | ||
1087 | toFormattedDetailsJSON (): VideoDetails { | 1082 | toFormattedDetailsJSON (): VideoDetails { |
@@ -1094,6 +1089,11 @@ export class VideoModel extends Model<VideoModel> { | |||
1094 | account: this.VideoChannel.Account.toFormattedJSON(), | 1089 | account: this.VideoChannel.Account.toFormattedJSON(), |
1095 | tags: map(this.Tags, 'name'), | 1090 | tags: map(this.Tags, 'name'), |
1096 | commentsEnabled: this.commentsEnabled, | 1091 | commentsEnabled: this.commentsEnabled, |
1092 | waitTranscoding: this.waitTranscoding, | ||
1093 | state: { | ||
1094 | id: this.state, | ||
1095 | label: VideoModel.getStateLabel(this.state) | ||
1096 | }, | ||
1097 | files: [] | 1097 | files: [] |
1098 | } | 1098 | } |
1099 | 1099 | ||
@@ -1207,6 +1207,8 @@ export class VideoModel extends Model<VideoModel> { | |||
1207 | language, | 1207 | language, |
1208 | views: this.views, | 1208 | views: this.views, |
1209 | sensitive: this.nsfw, | 1209 | sensitive: this.nsfw, |
1210 | waitTranscoding: this.waitTranscoding, | ||
1211 | state: this.state, | ||
1210 | commentsEnabled: this.commentsEnabled, | 1212 | commentsEnabled: this.commentsEnabled, |
1211 | published: this.publishedAt.toISOString(), | 1213 | published: this.publishedAt.toISOString(), |
1212 | updated: this.updatedAt.toISOString(), | 1214 | updated: this.updatedAt.toISOString(), |
diff --git a/server/tests/api/check-params/videos.ts b/server/tests/api/check-params/videos.ts index bc6c7fc46..04bed3b44 100644 --- a/server/tests/api/check-params/videos.ts +++ b/server/tests/api/check-params/videos.ts | |||
@@ -175,6 +175,7 @@ describe('Test videos API validator', function () { | |||
175 | language: 'pt', | 175 | language: 'pt', |
176 | nsfw: false, | 176 | nsfw: false, |
177 | commentsEnabled: true, | 177 | commentsEnabled: true, |
178 | waitTranscoding: true, | ||
178 | description: 'my super description', | 179 | description: 'my super description', |
179 | support: 'my super support text', | 180 | support: 'my super support text', |
180 | tags: [ 'tag1', 'tag2' ], | 181 | tags: [ 'tag1', 'tag2' ], |
@@ -224,20 +225,6 @@ describe('Test videos API validator', function () { | |||
224 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) | 225 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) |
225 | }) | 226 | }) |
226 | 227 | ||
227 | it('Should fail without nsfw attribute', async function () { | ||
228 | const fields = omit(baseCorrectParams, 'nsfw') | ||
229 | const attaches = baseCorrectAttaches | ||
230 | |||
231 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) | ||
232 | }) | ||
233 | |||
234 | it('Should fail without commentsEnabled attribute', async function () { | ||
235 | const fields = omit(baseCorrectParams, 'commentsEnabled') | ||
236 | const attaches = baseCorrectAttaches | ||
237 | |||
238 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) | ||
239 | }) | ||
240 | |||
241 | it('Should fail with a long description', async function () { | 228 | it('Should fail with a long description', async function () { |
242 | const fields = immutableAssign(baseCorrectParams, { description: 'super'.repeat(2500) }) | 229 | const fields = immutableAssign(baseCorrectParams, { description: 'super'.repeat(2500) }) |
243 | const attaches = baseCorrectAttaches | 230 | const attaches = baseCorrectAttaches |
diff --git a/server/tests/api/videos/multiple-servers.ts b/server/tests/api/videos/multiple-servers.ts index 5f9a76621..edc46a644 100644 --- a/server/tests/api/videos/multiple-servers.ts +++ b/server/tests/api/videos/multiple-servers.ts | |||
@@ -924,7 +924,7 @@ describe('Test multiple servers', function () { | |||
924 | 924 | ||
925 | describe('With minimum parameters', function () { | 925 | describe('With minimum parameters', function () { |
926 | it('Should upload and propagate the video', async function () { | 926 | it('Should upload and propagate the video', async function () { |
927 | this.timeout(50000) | 927 | this.timeout(60000) |
928 | 928 | ||
929 | const path = '/api/v1/videos/upload' | 929 | const path = '/api/v1/videos/upload' |
930 | 930 | ||
@@ -934,16 +934,14 @@ describe('Test multiple servers', function () { | |||
934 | .set('Authorization', 'Bearer ' + servers[1].accessToken) | 934 | .set('Authorization', 'Bearer ' + servers[1].accessToken) |
935 | .field('name', 'minimum parameters') | 935 | .field('name', 'minimum parameters') |
936 | .field('privacy', '1') | 936 | .field('privacy', '1') |
937 | .field('nsfw', 'false') | ||
938 | .field('channelId', '1') | 937 | .field('channelId', '1') |
939 | .field('commentsEnabled', 'true') | ||
940 | 938 | ||
941 | const filePath = join(__dirname, '..', '..', 'fixtures', 'video_short.webm') | 939 | const filePath = join(__dirname, '..', '..', 'fixtures', 'video_short.webm') |
942 | 940 | ||
943 | await req.attach('videofile', filePath) | 941 | await req.attach('videofile', filePath) |
944 | .expect(200) | 942 | .expect(200) |
945 | 943 | ||
946 | await wait(25000) | 944 | await wait(40000) |
947 | 945 | ||
948 | for (const server of servers) { | 946 | for (const server of servers) { |
949 | const res = await getVideosList(server.url) | 947 | const res = await getVideosList(server.url) |
@@ -964,7 +962,7 @@ describe('Test multiple servers', function () { | |||
964 | }, | 962 | }, |
965 | isLocal, | 963 | isLocal, |
966 | duration: 5, | 964 | duration: 5, |
967 | commentsEnabled: true, | 965 | commentsEnabled: false, |
968 | tags: [ ], | 966 | tags: [ ], |
969 | privacy: VideoPrivacy.PUBLIC, | 967 | privacy: VideoPrivacy.PUBLIC, |
970 | channel: { | 968 | channel: { |
diff --git a/server/tests/api/videos/services.ts b/server/tests/api/videos/services.ts index 45b4a1a81..51db000a2 100644 --- a/server/tests/api/videos/services.ts +++ b/server/tests/api/videos/services.ts | |||
@@ -32,7 +32,8 @@ describe('Test services', function () { | |||
32 | const oembedUrl = 'http://localhost:9001/videos/watch/' + server.video.uuid | 32 | const oembedUrl = 'http://localhost:9001/videos/watch/' + server.video.uuid |
33 | 33 | ||
34 | const res = await getOEmbed(server.url, oembedUrl) | 34 | const res = await getOEmbed(server.url, oembedUrl) |
35 | const expectedHtml = `<iframe width="560" height="315" src="http://localhost:9001/videos/embed/${server.video.uuid}" ` + | 35 | const expectedHtml = '<iframe width="560" height="315" sandbox="allow-same-origin allow-scripts" ' + |
36 | `src="http://localhost:9001/videos/embed/${server.video.uuid}" ` + | ||
36 | 'frameborder="0" allowfullscreen></iframe>' | 37 | 'frameborder="0" allowfullscreen></iframe>' |
37 | const expectedThumbnailUrl = 'http://localhost:9001/static/previews/' + server.video.uuid + '.jpg' | 38 | const expectedThumbnailUrl = 'http://localhost:9001/static/previews/' + server.video.uuid + '.jpg' |
38 | 39 | ||
diff --git a/server/tests/api/videos/video-transcoder.ts b/server/tests/api/videos/video-transcoder.ts index ef929960d..1eace6491 100644 --- a/server/tests/api/videos/video-transcoder.ts +++ b/server/tests/api/videos/video-transcoder.ts | |||
@@ -2,11 +2,22 @@ | |||
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { VideoDetails } from '../../../../shared/models/videos' | 5 | import { VideoDetails, VideoState } from '../../../../shared/models/videos' |
6 | import { getVideoFileFPS } from '../../../helpers/ffmpeg-utils' | 6 | import { getVideoFileFPS } from '../../../helpers/ffmpeg-utils' |
7 | import { | 7 | import { |
8 | flushAndRunMultipleServers, flushTests, getVideo, getVideosList, killallServers, root, ServerInfo, setAccessTokensToServers, uploadVideo, | 8 | doubleFollow, |
9 | wait, webtorrentAdd | 9 | flushAndRunMultipleServers, |
10 | flushTests, | ||
11 | getMyVideos, | ||
12 | getVideo, | ||
13 | getVideosList, | ||
14 | killallServers, | ||
15 | root, | ||
16 | ServerInfo, | ||
17 | setAccessTokensToServers, | ||
18 | uploadVideo, | ||
19 | wait, | ||
20 | webtorrentAdd | ||
10 | } from '../../utils' | 21 | } from '../../utils' |
11 | import { join } from 'path' | 22 | import { join } from 'path' |
12 | 23 | ||
@@ -109,6 +120,63 @@ describe('Test video transcoding', function () { | |||
109 | } | 120 | } |
110 | }) | 121 | }) |
111 | 122 | ||
123 | it('Should wait transcoding before publishing the video', async function () { | ||
124 | this.timeout(80000) | ||
125 | |||
126 | await doubleFollow(servers[0], servers[1]) | ||
127 | |||
128 | await wait(15000) | ||
129 | |||
130 | { | ||
131 | // Upload the video, but wait transcoding | ||
132 | const videoAttributes = { | ||
133 | name: 'waiting video', | ||
134 | fixture: 'video_short1.webm', | ||
135 | waitTranscoding: true | ||
136 | } | ||
137 | const resVideo = await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, videoAttributes) | ||
138 | const videoId = resVideo.body.video.uuid | ||
139 | |||
140 | // Should be in transcode state | ||
141 | const { body } = await getVideo(servers[ 1 ].url, videoId) | ||
142 | expect(body.name).to.equal('waiting video') | ||
143 | expect(body.state.id).to.equal(VideoState.TO_TRANSCODE) | ||
144 | expect(body.state.label).to.equal('To transcode') | ||
145 | expect(body.waitTranscoding).to.be.true | ||
146 | |||
147 | // Should have my video | ||
148 | const resMyVideos = await getMyVideos(servers[1].url, servers[1].accessToken, 0, 10) | ||
149 | const videoToFindInMine = resMyVideos.body.data.find(v => v.name === 'waiting video') | ||
150 | expect(videoToFindInMine).not.to.be.undefined | ||
151 | expect(videoToFindInMine.state.id).to.equal(VideoState.TO_TRANSCODE) | ||
152 | expect(videoToFindInMine.state.label).to.equal('To transcode') | ||
153 | expect(videoToFindInMine.waitTranscoding).to.be.true | ||
154 | |||
155 | // Should not list this video | ||
156 | const resVideos = await getVideosList(servers[1].url) | ||
157 | const videoToFindInList = resVideos.body.data.find(v => v.name === 'waiting video') | ||
158 | expect(videoToFindInList).to.be.undefined | ||
159 | |||
160 | // Server 1 should not have the video yet | ||
161 | await getVideo(servers[0].url, videoId, 404) | ||
162 | } | ||
163 | |||
164 | await wait(30000) | ||
165 | |||
166 | for (const server of servers) { | ||
167 | const res = await getVideosList(server.url) | ||
168 | const videoToFind = res.body.data.find(v => v.name === 'waiting video') | ||
169 | expect(videoToFind).not.to.be.undefined | ||
170 | |||
171 | const res2 = await getVideo(server.url, videoToFind.id) | ||
172 | const videoDetails: VideoDetails = res2.body | ||
173 | |||
174 | expect(videoDetails.state.id).to.equal(VideoState.PUBLISHED) | ||
175 | expect(videoDetails.state.label).to.equal('Published') | ||
176 | expect(videoDetails.waitTranscoding).to.be.true | ||
177 | } | ||
178 | }) | ||
179 | |||
112 | after(async function () { | 180 | after(async function () { |
113 | killallServers(servers) | 181 | killallServers(servers) |
114 | 182 | ||
diff --git a/server/tests/cli/create-transcoding-job.ts b/server/tests/cli/create-transcoding-job.ts index 557dd8af9..fe1c0c03d 100644 --- a/server/tests/cli/create-transcoding-job.ts +++ b/server/tests/cli/create-transcoding-job.ts | |||
@@ -65,7 +65,7 @@ describe('Test create transcoding jobs', function () { | |||
65 | const env = getEnvCli(servers[0]) | 65 | const env = getEnvCli(servers[0]) |
66 | await execCLI(`${env} npm run create-transcoding-job -- -v ${video2UUID}`) | 66 | await execCLI(`${env} npm run create-transcoding-job -- -v ${video2UUID}`) |
67 | 67 | ||
68 | await wait(30000) | 68 | await wait(40000) |
69 | 69 | ||
70 | for (const server of servers) { | 70 | for (const server of servers) { |
71 | const res = await getVideosList(server.url) | 71 | const res = await getVideosList(server.url) |
diff --git a/server/tests/utils/videos/videos.ts b/server/tests/utils/videos/videos.ts index ab0ce12ec..2c1d20ef1 100644 --- a/server/tests/utils/videos/videos.ts +++ b/server/tests/utils/videos/videos.ts | |||
@@ -27,6 +27,7 @@ type VideoAttributes = { | |||
27 | language?: string | 27 | language?: string |
28 | nsfw?: boolean | 28 | nsfw?: boolean |
29 | commentsEnabled?: boolean | 29 | commentsEnabled?: boolean |
30 | waitTranscoding?: boolean | ||
30 | description?: string | 31 | description?: string |
31 | tags?: string[] | 32 | tags?: string[] |
32 | channelId?: number | 33 | channelId?: number |
@@ -326,6 +327,7 @@ async function uploadVideo (url: string, accessToken: string, videoAttributesArg | |||
326 | language: 'zh', | 327 | language: 'zh', |
327 | channelId: defaultChannelId, | 328 | channelId: defaultChannelId, |
328 | nsfw: true, | 329 | nsfw: true, |
330 | waitTranscoding: false, | ||
329 | description: 'my super description', | 331 | description: 'my super description', |
330 | support: 'my super support text', | 332 | support: 'my super support text', |
331 | tags: [ 'tag' ], | 333 | tags: [ 'tag' ], |
@@ -341,6 +343,7 @@ async function uploadVideo (url: string, accessToken: string, videoAttributesArg | |||
341 | .field('name', attributes.name) | 343 | .field('name', attributes.name) |
342 | .field('nsfw', JSON.stringify(attributes.nsfw)) | 344 | .field('nsfw', JSON.stringify(attributes.nsfw)) |
343 | .field('commentsEnabled', JSON.stringify(attributes.commentsEnabled)) | 345 | .field('commentsEnabled', JSON.stringify(attributes.commentsEnabled)) |
346 | .field('waitTranscoding', JSON.stringify(attributes.waitTranscoding)) | ||
344 | .field('privacy', attributes.privacy.toString()) | 347 | .field('privacy', attributes.privacy.toString()) |
345 | .field('channelId', attributes.channelId) | 348 | .field('channelId', attributes.channelId) |
346 | 349 | ||
diff --git a/server/tools/import-videos.ts b/server/tools/import-videos.ts index fd351ae7e..e49fbb2f5 100644 --- a/server/tools/import-videos.ts +++ b/server/tools/import-videos.ts | |||
@@ -176,6 +176,7 @@ async function uploadVideoOnPeerTube (videoInfo: any, videoPath: string, languag | |||
176 | licence, | 176 | licence, |
177 | language, | 177 | language, |
178 | nsfw: isNSFW(videoInfo), | 178 | nsfw: isNSFW(videoInfo), |
179 | waitTranscoding: true, | ||
179 | commentsEnabled: true, | 180 | commentsEnabled: true, |
180 | description: videoInfo.description || undefined, | 181 | description: videoInfo.description || undefined, |
181 | support: undefined, | 182 | support: undefined, |
diff --git a/server/tools/upload.ts b/server/tools/upload.ts index 177d849f3..4d40c8c1a 100644 --- a/server/tools/upload.ts +++ b/server/tools/upload.ts | |||
@@ -84,6 +84,7 @@ async function run () { | |||
84 | fixture: program['file'], | 84 | fixture: program['file'], |
85 | thumbnailfile: program['thumbnailPath'], | 85 | thumbnailfile: program['thumbnailPath'], |
86 | previewfile: program['previewPath'], | 86 | previewfile: program['previewPath'], |
87 | waitTranscoding: true, | ||
87 | privacy: program['privacy'], | 88 | privacy: program['privacy'], |
88 | support: undefined | 89 | support: undefined |
89 | } | 90 | } |