diff options
Diffstat (limited to 'server/lib')
-rw-r--r-- | server/lib/activitypub/actor.ts | 33 | ||||
-rw-r--r-- | server/lib/activitypub/crawl.ts | 19 | ||||
-rw-r--r-- | server/lib/activitypub/process/process-announce.ts | 15 | ||||
-rw-r--r-- | server/lib/activitypub/process/process-create.ts | 29 | ||||
-rw-r--r-- | server/lib/activitypub/process/process-delete.ts | 8 | ||||
-rw-r--r-- | server/lib/activitypub/process/process-update.ts | 4 | ||||
-rw-r--r-- | server/lib/activitypub/video-comments.ts | 4 | ||||
-rw-r--r-- | server/lib/emailer.ts | 66 | ||||
-rw-r--r-- | server/lib/files-cache/videos-preview-cache.ts | 2 | ||||
-rw-r--r-- | server/lib/job-queue/handlers/video-file-import.ts | 4 | ||||
-rw-r--r-- | server/lib/job-queue/handlers/video-import.ts | 1 | ||||
-rw-r--r-- | server/lib/job-queue/handlers/video-transcoding.ts | 56 | ||||
-rw-r--r-- | server/lib/thumbnail.ts | 8 | ||||
-rw-r--r-- | server/lib/user.ts | 34 | ||||
-rw-r--r-- | server/lib/video-channel.ts | 16 | ||||
-rw-r--r-- | server/lib/video-transcoding.ts | 97 |
16 files changed, 257 insertions, 139 deletions
diff --git a/server/lib/activitypub/actor.ts b/server/lib/activitypub/actor.ts index 25cd40905..38eb87d1e 100644 --- a/server/lib/activitypub/actor.ts +++ b/server/lib/activitypub/actor.ts | |||
@@ -5,7 +5,7 @@ import * as uuidv4 from 'uuid/v4' | |||
5 | import { ActivityPubActor, ActivityPubActorType } from '../../../shared/models/activitypub' | 5 | import { ActivityPubActor, ActivityPubActorType } from '../../../shared/models/activitypub' |
6 | import { ActivityPubAttributedTo } from '../../../shared/models/activitypub/objects' | 6 | import { ActivityPubAttributedTo } from '../../../shared/models/activitypub/objects' |
7 | import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub' | 7 | import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub' |
8 | import { isActorObjectValid, normalizeActor } from '../../helpers/custom-validators/activitypub/actor' | 8 | import { sanitizeAndCheckActorObject } from '../../helpers/custom-validators/activitypub/actor' |
9 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' | 9 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' |
10 | import { retryTransactionWrapper, updateInstanceWithAnother } from '../../helpers/database-utils' | 10 | import { retryTransactionWrapper, updateInstanceWithAnother } from '../../helpers/database-utils' |
11 | import { logger } from '../../helpers/logger' | 11 | import { logger } from '../../helpers/logger' |
@@ -33,7 +33,7 @@ function setAsyncActorKeys (actor: ActorModel) { | |||
33 | return actor.save() | 33 | return actor.save() |
34 | }) | 34 | }) |
35 | .catch(err => { | 35 | .catch(err => { |
36 | logger.error('Cannot set public/private keys of actor %d.', actor.uuid, { err }) | 36 | logger.error('Cannot set public/private keys of actor %d.', actor.url, { err }) |
37 | return actor | 37 | return actor |
38 | }) | 38 | }) |
39 | } | 39 | } |
@@ -128,18 +128,17 @@ async function updateActorInstance (actorInstance: ActorModel, attributes: Activ | |||
128 | const followersCount = await fetchActorTotalItems(attributes.followers) | 128 | const followersCount = await fetchActorTotalItems(attributes.followers) |
129 | const followingCount = await fetchActorTotalItems(attributes.following) | 129 | const followingCount = await fetchActorTotalItems(attributes.following) |
130 | 130 | ||
131 | actorInstance.set('type', attributes.type) | 131 | actorInstance.type = attributes.type |
132 | actorInstance.set('uuid', attributes.uuid) | 132 | actorInstance.preferredUsername = attributes.preferredUsername |
133 | actorInstance.set('preferredUsername', attributes.preferredUsername) | 133 | actorInstance.url = attributes.id |
134 | actorInstance.set('url', attributes.id) | 134 | actorInstance.publicKey = attributes.publicKey.publicKeyPem |
135 | actorInstance.set('publicKey', attributes.publicKey.publicKeyPem) | 135 | actorInstance.followersCount = followersCount |
136 | actorInstance.set('followersCount', followersCount) | 136 | actorInstance.followingCount = followingCount |
137 | actorInstance.set('followingCount', followingCount) | 137 | actorInstance.inboxUrl = attributes.inbox |
138 | actorInstance.set('inboxUrl', attributes.inbox) | 138 | actorInstance.outboxUrl = attributes.outbox |
139 | actorInstance.set('outboxUrl', attributes.outbox) | 139 | actorInstance.sharedInboxUrl = attributes.endpoints.sharedInbox |
140 | actorInstance.set('sharedInboxUrl', attributes.endpoints.sharedInbox) | 140 | actorInstance.followersUrl = attributes.followers |
141 | actorInstance.set('followersUrl', attributes.followers) | 141 | actorInstance.followingUrl = attributes.following |
142 | actorInstance.set('followingUrl', attributes.following) | ||
143 | } | 142 | } |
144 | 143 | ||
145 | async function updateActorAvatarInstance (actorInstance: ActorModel, avatarName: string, t: Transaction) { | 144 | async function updateActorAvatarInstance (actorInstance: ActorModel, avatarName: string, t: Transaction) { |
@@ -370,10 +369,9 @@ async function fetchRemoteActor (actorUrl: string): Promise<{ statusCode?: numbe | |||
370 | logger.info('Fetching remote actor %s.', actorUrl) | 369 | logger.info('Fetching remote actor %s.', actorUrl) |
371 | 370 | ||
372 | const requestResult = await doRequest<ActivityPubActor>(options) | 371 | const requestResult = await doRequest<ActivityPubActor>(options) |
373 | normalizeActor(requestResult.body) | ||
374 | |||
375 | const actorJSON = requestResult.body | 372 | const actorJSON = requestResult.body |
376 | if (isActorObjectValid(actorJSON) === false) { | 373 | |
374 | if (sanitizeAndCheckActorObject(actorJSON) === false) { | ||
377 | logger.debug('Remote actor JSON is not valid.', { actorJSON }) | 375 | logger.debug('Remote actor JSON is not valid.', { actorJSON }) |
378 | return { result: undefined, statusCode: requestResult.response.statusCode } | 376 | return { result: undefined, statusCode: requestResult.response.statusCode } |
379 | } | 377 | } |
@@ -388,7 +386,6 @@ async function fetchRemoteActor (actorUrl: string): Promise<{ statusCode?: numbe | |||
388 | 386 | ||
389 | const actor = new ActorModel({ | 387 | const actor = new ActorModel({ |
390 | type: actorJSON.type, | 388 | type: actorJSON.type, |
391 | uuid: actorJSON.uuid, | ||
392 | preferredUsername: actorJSON.preferredUsername, | 389 | preferredUsername: actorJSON.preferredUsername, |
393 | url: actorJSON.id, | 390 | url: actorJSON.id, |
394 | publicKey: actorJSON.publicKey.publicKeyPem, | 391 | publicKey: actorJSON.publicKey.publicKeyPem, |
diff --git a/server/lib/activitypub/crawl.ts b/server/lib/activitypub/crawl.ts index 686eef04d..9e469e3e6 100644 --- a/server/lib/activitypub/crawl.ts +++ b/server/lib/activitypub/crawl.ts | |||
@@ -28,13 +28,22 @@ async function crawlCollectionPage <T> (uri: string, handler: HandlerFunction<T> | |||
28 | let i = 0 | 28 | let i = 0 |
29 | let nextLink = firstBody.first | 29 | let nextLink = firstBody.first |
30 | while (nextLink && i < limit) { | 30 | while (nextLink && i < limit) { |
31 | // Don't crawl ourselves | 31 | let body: any |
32 | const remoteHost = parse(nextLink).host | ||
33 | if (remoteHost === WEBSERVER.HOST) continue | ||
34 | 32 | ||
35 | options.uri = nextLink | 33 | if (typeof nextLink === 'string') { |
34 | // Don't crawl ourselves | ||
35 | const remoteHost = parse(nextLink).host | ||
36 | if (remoteHost === WEBSERVER.HOST) continue | ||
37 | |||
38 | options.uri = nextLink | ||
39 | |||
40 | const res = await doRequest<ActivityPubOrderedCollection<T>>(options) | ||
41 | body = res.body | ||
42 | } else { | ||
43 | // nextLink is already the object we want | ||
44 | body = nextLink | ||
45 | } | ||
36 | 46 | ||
37 | const { body } = await doRequest<ActivityPubOrderedCollection<T>>(options) | ||
38 | nextLink = body.next | 47 | nextLink = body.next |
39 | i++ | 48 | i++ |
40 | 49 | ||
diff --git a/server/lib/activitypub/process/process-announce.ts b/server/lib/activitypub/process/process-announce.ts index 23310b41e..bbf1bd3a8 100644 --- a/server/lib/activitypub/process/process-announce.ts +++ b/server/lib/activitypub/process/process-announce.ts | |||
@@ -5,8 +5,9 @@ import { ActorModel } from '../../../models/activitypub/actor' | |||
5 | import { VideoShareModel } from '../../../models/video/video-share' | 5 | import { VideoShareModel } from '../../../models/video/video-share' |
6 | import { forwardVideoRelatedActivity } from '../send/utils' | 6 | import { forwardVideoRelatedActivity } from '../send/utils' |
7 | import { getOrCreateVideoAndAccountAndChannel } from '../videos' | 7 | import { getOrCreateVideoAndAccountAndChannel } from '../videos' |
8 | import { VideoPrivacy } from '../../../../shared/models/videos' | ||
9 | import { Notifier } from '../../notifier' | 8 | import { Notifier } from '../../notifier' |
9 | import { VideoModel } from '../../../models/video/video' | ||
10 | import { logger } from '../../../helpers/logger' | ||
10 | 11 | ||
11 | async function processAnnounceActivity (activity: ActivityAnnounce, actorAnnouncer: ActorModel) { | 12 | async function processAnnounceActivity (activity: ActivityAnnounce, actorAnnouncer: ActorModel) { |
12 | return retryTransactionWrapper(processVideoShare, actorAnnouncer, activity) | 13 | return retryTransactionWrapper(processVideoShare, actorAnnouncer, activity) |
@@ -23,7 +24,17 @@ export { | |||
23 | async function processVideoShare (actorAnnouncer: ActorModel, activity: ActivityAnnounce) { | 24 | async function processVideoShare (actorAnnouncer: ActorModel, activity: ActivityAnnounce) { |
24 | const objectUri = typeof activity.object === 'string' ? activity.object : activity.object.id | 25 | const objectUri = typeof activity.object === 'string' ? activity.object : activity.object.id |
25 | 26 | ||
26 | const { video, created: videoCreated } = await getOrCreateVideoAndAccountAndChannel({ videoObject: objectUri }) | 27 | let video: VideoModel |
28 | let videoCreated: boolean | ||
29 | |||
30 | try { | ||
31 | const result = await getOrCreateVideoAndAccountAndChannel({ videoObject: objectUri }) | ||
32 | video = result.video | ||
33 | videoCreated = result.created | ||
34 | } catch (err) { | ||
35 | logger.debug('Cannot process share of %s. Maybe this is not a video object, so just skipping.', objectUri, { err }) | ||
36 | return | ||
37 | } | ||
27 | 38 | ||
28 | await sequelizeTypescript.transaction(async t => { | 39 | await sequelizeTypescript.transaction(async t => { |
29 | // Add share entry | 40 | // Add share entry |
diff --git a/server/lib/activitypub/process/process-create.ts b/server/lib/activitypub/process/process-create.ts index e882669ce..daf846513 100644 --- a/server/lib/activitypub/process/process-create.ts +++ b/server/lib/activitypub/process/process-create.ts | |||
@@ -9,28 +9,14 @@ import { getOrCreateVideoAndAccountAndChannel } from '../videos' | |||
9 | import { forwardVideoRelatedActivity } from '../send/utils' | 9 | import { forwardVideoRelatedActivity } from '../send/utils' |
10 | import { createOrUpdateCacheFile } from '../cache-file' | 10 | import { createOrUpdateCacheFile } from '../cache-file' |
11 | import { Notifier } from '../../notifier' | 11 | import { Notifier } from '../../notifier' |
12 | import { processViewActivity } from './process-view' | ||
13 | import { processDislikeActivity } from './process-dislike' | ||
14 | import { processFlagActivity } from './process-flag' | ||
15 | import { PlaylistObject } from '../../../../shared/models/activitypub/objects/playlist-object' | 12 | import { PlaylistObject } from '../../../../shared/models/activitypub/objects/playlist-object' |
16 | import { createOrUpdateVideoPlaylist } from '../playlist' | 13 | import { createOrUpdateVideoPlaylist } from '../playlist' |
14 | import { VideoModel } from '../../../models/video/video' | ||
17 | 15 | ||
18 | async function processCreateActivity (activity: ActivityCreate, byActor: ActorModel) { | 16 | async function processCreateActivity (activity: ActivityCreate, byActor: ActorModel) { |
19 | const activityObject = activity.object | 17 | const activityObject = activity.object |
20 | const activityType = activityObject.type | 18 | const activityType = activityObject.type |
21 | 19 | ||
22 | if (activityType === 'View') { | ||
23 | return processViewActivity(activity, byActor) | ||
24 | } | ||
25 | |||
26 | if (activityType === 'Dislike') { | ||
27 | return retryTransactionWrapper(processDislikeActivity, activity, byActor) | ||
28 | } | ||
29 | |||
30 | if (activityType === 'Flag') { | ||
31 | return retryTransactionWrapper(processFlagActivity, activity, byActor) | ||
32 | } | ||
33 | |||
34 | if (activityType === 'Video') { | 20 | if (activityType === 'Video') { |
35 | return processCreateVideo(activity) | 21 | return processCreateVideo(activity) |
36 | } | 22 | } |
@@ -91,7 +77,18 @@ async function processCreateVideoComment (activity: ActivityCreate, byActor: Act | |||
91 | 77 | ||
92 | if (!byAccount) throw new Error('Cannot create video comment with the non account actor ' + byActor.url) | 78 | if (!byAccount) throw new Error('Cannot create video comment with the non account actor ' + byActor.url) |
93 | 79 | ||
94 | const { video } = await resolveThread(commentObject.inReplyTo) | 80 | let video: VideoModel |
81 | try { | ||
82 | const resolveThreadResult = await resolveThread(commentObject.inReplyTo) | ||
83 | video = resolveThreadResult.video | ||
84 | } catch (err) { | ||
85 | logger.debug( | ||
86 | 'Cannot process video comment because we could not resolve thread %s. Maybe it was not a video thread, so skip it.', | ||
87 | commentObject.inReplyTo, | ||
88 | { err } | ||
89 | ) | ||
90 | return | ||
91 | } | ||
95 | 92 | ||
96 | const { comment, created } = await addVideoComment(video, commentObject.id) | 93 | const { comment, created } = await addVideoComment(video, commentObject.id) |
97 | 94 | ||
diff --git a/server/lib/activitypub/process/process-delete.ts b/server/lib/activitypub/process/process-delete.ts index 76f07fd8a..6f10a50bd 100644 --- a/server/lib/activitypub/process/process-delete.ts +++ b/server/lib/activitypub/process/process-delete.ts | |||
@@ -95,23 +95,23 @@ async function processDeleteVideoPlaylist (actor: ActorModel, playlistToDelete: | |||
95 | } | 95 | } |
96 | 96 | ||
97 | async function processDeleteAccount (accountToRemove: AccountModel) { | 97 | async function processDeleteAccount (accountToRemove: AccountModel) { |
98 | logger.debug('Removing remote account "%s".', accountToRemove.Actor.uuid) | 98 | logger.debug('Removing remote account "%s".', accountToRemove.Actor.url) |
99 | 99 | ||
100 | await sequelizeTypescript.transaction(async t => { | 100 | await sequelizeTypescript.transaction(async t => { |
101 | await accountToRemove.destroy({ transaction: t }) | 101 | await accountToRemove.destroy({ transaction: t }) |
102 | }) | 102 | }) |
103 | 103 | ||
104 | logger.info('Remote account with uuid %s removed.', accountToRemove.Actor.uuid) | 104 | logger.info('Remote account %s removed.', accountToRemove.Actor.url) |
105 | } | 105 | } |
106 | 106 | ||
107 | async function processDeleteVideoChannel (videoChannelToRemove: VideoChannelModel) { | 107 | async function processDeleteVideoChannel (videoChannelToRemove: VideoChannelModel) { |
108 | logger.debug('Removing remote video channel "%s".', videoChannelToRemove.Actor.uuid) | 108 | logger.debug('Removing remote video channel "%s".', videoChannelToRemove.Actor.url) |
109 | 109 | ||
110 | await sequelizeTypescript.transaction(async t => { | 110 | await sequelizeTypescript.transaction(async t => { |
111 | await videoChannelToRemove.destroy({ transaction: t }) | 111 | await videoChannelToRemove.destroy({ transaction: t }) |
112 | }) | 112 | }) |
113 | 113 | ||
114 | logger.info('Remote video channel with uuid %s removed.', videoChannelToRemove.Actor.uuid) | 114 | logger.info('Remote video channel %s removed.', videoChannelToRemove.Actor.url) |
115 | } | 115 | } |
116 | 116 | ||
117 | function processDeleteVideoComment (byActor: ActorModel, videoComment: VideoCommentModel, activity: ActivityDelete) { | 117 | function processDeleteVideoComment (byActor: ActorModel, videoComment: VideoCommentModel, activity: ActivityDelete) { |
diff --git a/server/lib/activitypub/process/process-update.ts b/server/lib/activitypub/process/process-update.ts index 54a9234bb..71a16dacc 100644 --- a/server/lib/activitypub/process/process-update.ts +++ b/server/lib/activitypub/process/process-update.ts | |||
@@ -95,7 +95,7 @@ async function processUpdateCacheFile (byActor: ActorModel, activity: ActivityUp | |||
95 | async function processUpdateActor (actor: ActorModel, activity: ActivityUpdate) { | 95 | async function processUpdateActor (actor: ActorModel, activity: ActivityUpdate) { |
96 | const actorAttributesToUpdate = activity.object as ActivityPubActor | 96 | const actorAttributesToUpdate = activity.object as ActivityPubActor |
97 | 97 | ||
98 | logger.debug('Updating remote account "%s".', actorAttributesToUpdate.uuid) | 98 | logger.debug('Updating remote account "%s".', actorAttributesToUpdate.url) |
99 | let accountOrChannelInstance: AccountModel | VideoChannelModel | 99 | let accountOrChannelInstance: AccountModel | VideoChannelModel |
100 | let actorFieldsSave: object | 100 | let actorFieldsSave: object |
101 | let accountOrChannelFieldsSave: object | 101 | let accountOrChannelFieldsSave: object |
@@ -128,7 +128,7 @@ async function processUpdateActor (actor: ActorModel, activity: ActivityUpdate) | |||
128 | await accountOrChannelInstance.save({ transaction: t }) | 128 | await accountOrChannelInstance.save({ transaction: t }) |
129 | }) | 129 | }) |
130 | 130 | ||
131 | logger.info('Remote account with uuid %s updated', actorAttributesToUpdate.uuid) | 131 | logger.info('Remote account %s updated', actorAttributesToUpdate.url) |
132 | } catch (err) { | 132 | } catch (err) { |
133 | if (actor !== undefined && actorFieldsSave !== undefined) { | 133 | if (actor !== undefined && actorFieldsSave !== undefined) { |
134 | resetSequelizeInstance(actor, actorFieldsSave) | 134 | resetSequelizeInstance(actor, actorFieldsSave) |
diff --git a/server/lib/activitypub/video-comments.ts b/server/lib/activitypub/video-comments.ts index 18f44d50e..c3fc6b462 100644 --- a/server/lib/activitypub/video-comments.ts +++ b/server/lib/activitypub/video-comments.ts | |||
@@ -80,7 +80,8 @@ async function addVideoComment (videoInstance: VideoModel, commentUrl: string) { | |||
80 | return { comment, created } | 80 | return { comment, created } |
81 | } | 81 | } |
82 | 82 | ||
83 | async function resolveThread (url: string, comments: VideoCommentModel[] = []) { | 83 | type ResolveThreadResult = Promise<{ video: VideoModel, parents: VideoCommentModel[] }> |
84 | async function resolveThread (url: string, comments: VideoCommentModel[] = []): ResolveThreadResult { | ||
84 | // Already have this comment? | 85 | // Already have this comment? |
85 | const commentFromDatabase = await VideoCommentModel.loadByUrlAndPopulateReplyAndVideo(url) | 86 | const commentFromDatabase = await VideoCommentModel.loadByUrlAndPopulateReplyAndVideo(url) |
86 | if (commentFromDatabase) { | 87 | if (commentFromDatabase) { |
@@ -161,7 +162,6 @@ async function resolveThread (url: string, comments: VideoCommentModel[] = []) { | |||
161 | 162 | ||
162 | return resolveThread(body.inReplyTo, comments.concat([ comment ])) | 163 | return resolveThread(body.inReplyTo, comments.concat([ comment ])) |
163 | } | 164 | } |
164 | |||
165 | } | 165 | } |
166 | 166 | ||
167 | export { | 167 | export { |
diff --git a/server/lib/emailer.ts b/server/lib/emailer.ts index 8c06e9751..c4a5a5853 100644 --- a/server/lib/emailer.ts +++ b/server/lib/emailer.ts | |||
@@ -100,11 +100,11 @@ class Emailer { | |||
100 | `You can view it on ${videoUrl} ` + | 100 | `You can view it on ${videoUrl} ` + |
101 | `\n\n` + | 101 | `\n\n` + |
102 | `Cheers,\n` + | 102 | `Cheers,\n` + |
103 | `PeerTube.` | 103 | `${CONFIG.EMAIL.BODY.SIGNATURE}` |
104 | 104 | ||
105 | const emailPayload: EmailPayload = { | 105 | const emailPayload: EmailPayload = { |
106 | to, | 106 | to, |
107 | subject: channelName + ' just published a new video', | 107 | subject: CONFIG.EMAIL.OBJECT.PREFIX + channelName + ' just published a new video', |
108 | text | 108 | text |
109 | } | 109 | } |
110 | 110 | ||
@@ -119,11 +119,11 @@ class Emailer { | |||
119 | `Your ${followType} ${followingName} has a new subscriber: ${followerName}` + | 119 | `Your ${followType} ${followingName} has a new subscriber: ${followerName}` + |
120 | `\n\n` + | 120 | `\n\n` + |
121 | `Cheers,\n` + | 121 | `Cheers,\n` + |
122 | `PeerTube.` | 122 | `${CONFIG.EMAIL.BODY.SIGNATURE}` |
123 | 123 | ||
124 | const emailPayload: EmailPayload = { | 124 | const emailPayload: EmailPayload = { |
125 | to, | 125 | to, |
126 | subject: 'New follower on your channel ' + followingName, | 126 | subject: CONFIG.EMAIL.OBJECT.PREFIX + 'New follower on your channel ' + followingName, |
127 | text | 127 | text |
128 | } | 128 | } |
129 | 129 | ||
@@ -137,11 +137,11 @@ class Emailer { | |||
137 | `Your instance has a new follower: ${actorFollow.ActorFollower.url}${awaitingApproval}` + | 137 | `Your instance has a new follower: ${actorFollow.ActorFollower.url}${awaitingApproval}` + |
138 | `\n\n` + | 138 | `\n\n` + |
139 | `Cheers,\n` + | 139 | `Cheers,\n` + |
140 | `PeerTube.` | 140 | `${CONFIG.EMAIL.BODY.SIGNATURE}` |
141 | 141 | ||
142 | const emailPayload: EmailPayload = { | 142 | const emailPayload: EmailPayload = { |
143 | to, | 143 | to, |
144 | subject: 'New instance follower', | 144 | subject: CONFIG.EMAIL.OBJECT.PREFIX + 'New instance follower', |
145 | text | 145 | text |
146 | } | 146 | } |
147 | 147 | ||
@@ -157,11 +157,11 @@ class Emailer { | |||
157 | `You can view it on ${videoUrl} ` + | 157 | `You can view it on ${videoUrl} ` + |
158 | `\n\n` + | 158 | `\n\n` + |
159 | `Cheers,\n` + | 159 | `Cheers,\n` + |
160 | `PeerTube.` | 160 | `${CONFIG.EMAIL.BODY.SIGNATURE}` |
161 | 161 | ||
162 | const emailPayload: EmailPayload = { | 162 | const emailPayload: EmailPayload = { |
163 | to, | 163 | to, |
164 | subject: `Your video ${video.name} is published`, | 164 | subject: CONFIG.EMAIL.OBJECT.PREFIX + `Your video ${video.name} is published`, |
165 | text | 165 | text |
166 | } | 166 | } |
167 | 167 | ||
@@ -177,11 +177,11 @@ class Emailer { | |||
177 | `You can view the imported video on ${videoUrl} ` + | 177 | `You can view the imported video on ${videoUrl} ` + |
178 | `\n\n` + | 178 | `\n\n` + |
179 | `Cheers,\n` + | 179 | `Cheers,\n` + |
180 | `PeerTube.` | 180 | `${CONFIG.EMAIL.BODY.SIGNATURE}` |
181 | 181 | ||
182 | const emailPayload: EmailPayload = { | 182 | const emailPayload: EmailPayload = { |
183 | to, | 183 | to, |
184 | subject: `Your video import ${videoImport.getTargetIdentifier()} is finished`, | 184 | subject: CONFIG.EMAIL.OBJECT.PREFIX + `Your video import ${videoImport.getTargetIdentifier()} is finished`, |
185 | text | 185 | text |
186 | } | 186 | } |
187 | 187 | ||
@@ -197,11 +197,11 @@ class Emailer { | |||
197 | `See your videos import dashboard for more information: ${importUrl}` + | 197 | `See your videos import dashboard for more information: ${importUrl}` + |
198 | `\n\n` + | 198 | `\n\n` + |
199 | `Cheers,\n` + | 199 | `Cheers,\n` + |
200 | `PeerTube.` | 200 | `${CONFIG.EMAIL.BODY.SIGNATURE}` |
201 | 201 | ||
202 | const emailPayload: EmailPayload = { | 202 | const emailPayload: EmailPayload = { |
203 | to, | 203 | to, |
204 | subject: `Your video import ${videoImport.getTargetIdentifier()} encountered an error`, | 204 | subject: CONFIG.EMAIL.OBJECT.PREFIX + `Your video import ${videoImport.getTargetIdentifier()} encountered an error`, |
205 | text | 205 | text |
206 | } | 206 | } |
207 | 207 | ||
@@ -219,11 +219,11 @@ class Emailer { | |||
219 | `You can view it on ${commentUrl} ` + | 219 | `You can view it on ${commentUrl} ` + |
220 | `\n\n` + | 220 | `\n\n` + |
221 | `Cheers,\n` + | 221 | `Cheers,\n` + |
222 | `PeerTube.` | 222 | `${CONFIG.EMAIL.BODY.SIGNATURE}` |
223 | 223 | ||
224 | const emailPayload: EmailPayload = { | 224 | const emailPayload: EmailPayload = { |
225 | to, | 225 | to, |
226 | subject: 'New comment on your video ' + video.name, | 226 | subject: CONFIG.EMAIL.OBJECT.PREFIX + 'New comment on your video ' + video.name, |
227 | text | 227 | text |
228 | } | 228 | } |
229 | 229 | ||
@@ -241,11 +241,11 @@ class Emailer { | |||
241 | `You can view the comment on ${commentUrl} ` + | 241 | `You can view the comment on ${commentUrl} ` + |
242 | `\n\n` + | 242 | `\n\n` + |
243 | `Cheers,\n` + | 243 | `Cheers,\n` + |
244 | `PeerTube.` | 244 | `${CONFIG.EMAIL.BODY.SIGNATURE}` |
245 | 245 | ||
246 | const emailPayload: EmailPayload = { | 246 | const emailPayload: EmailPayload = { |
247 | to, | 247 | to, |
248 | subject: 'Mention on video ' + video.name, | 248 | subject: CONFIG.EMAIL.OBJECT.PREFIX + 'Mention on video ' + video.name, |
249 | text | 249 | text |
250 | } | 250 | } |
251 | 251 | ||
@@ -258,11 +258,11 @@ class Emailer { | |||
258 | const text = `Hi,\n\n` + | 258 | const text = `Hi,\n\n` + |
259 | `${WEBSERVER.HOST} received an abuse for the following video ${videoUrl}\n\n` + | 259 | `${WEBSERVER.HOST} received an abuse for the following video ${videoUrl}\n\n` + |
260 | `Cheers,\n` + | 260 | `Cheers,\n` + |
261 | `PeerTube.` | 261 | `${CONFIG.EMAIL.BODY.SIGNATURE}` |
262 | 262 | ||
263 | const emailPayload: EmailPayload = { | 263 | const emailPayload: EmailPayload = { |
264 | to, | 264 | to, |
265 | subject: '[PeerTube] Received a video abuse', | 265 | subject: CONFIG.EMAIL.OBJECT.PREFIX + 'Received a video abuse', |
266 | text | 266 | text |
267 | } | 267 | } |
268 | 268 | ||
@@ -281,11 +281,11 @@ class Emailer { | |||
281 | `A full list of auto-blacklisted videos can be reviewed here: ${VIDEO_AUTO_BLACKLIST_URL}` + | 281 | `A full list of auto-blacklisted videos can be reviewed here: ${VIDEO_AUTO_BLACKLIST_URL}` + |
282 | `\n\n` + | 282 | `\n\n` + |
283 | `Cheers,\n` + | 283 | `Cheers,\n` + |
284 | `PeerTube.` | 284 | `${CONFIG.EMAIL.BODY.SIGNATURE}` |
285 | 285 | ||
286 | const emailPayload: EmailPayload = { | 286 | const emailPayload: EmailPayload = { |
287 | to, | 287 | to, |
288 | subject: '[PeerTube] An auto-blacklisted video is awaiting review', | 288 | subject: CONFIG.EMAIL.OBJECT.PREFIX + 'An auto-blacklisted video is awaiting review', |
289 | text | 289 | text |
290 | } | 290 | } |
291 | 291 | ||
@@ -296,11 +296,11 @@ class Emailer { | |||
296 | const text = `Hi,\n\n` + | 296 | const text = `Hi,\n\n` + |
297 | `User ${user.username} just registered on ${WEBSERVER.HOST} PeerTube instance.\n\n` + | 297 | `User ${user.username} just registered on ${WEBSERVER.HOST} PeerTube instance.\n\n` + |
298 | `Cheers,\n` + | 298 | `Cheers,\n` + |
299 | `PeerTube.` | 299 | `${CONFIG.EMAIL.BODY.SIGNATURE}` |
300 | 300 | ||
301 | const emailPayload: EmailPayload = { | 301 | const emailPayload: EmailPayload = { |
302 | to, | 302 | to, |
303 | subject: '[PeerTube] New user registration on ' + WEBSERVER.HOST, | 303 | subject: CONFIG.EMAIL.OBJECT.PREFIX + 'New user registration on ' + WEBSERVER.HOST, |
304 | text | 304 | text |
305 | } | 305 | } |
306 | 306 | ||
@@ -318,11 +318,11 @@ class Emailer { | |||
318 | blockedString + | 318 | blockedString + |
319 | '\n\n' + | 319 | '\n\n' + |
320 | 'Cheers,\n' + | 320 | 'Cheers,\n' + |
321 | `PeerTube.` | 321 | `${CONFIG.EMAIL.BODY.SIGNATURE}` |
322 | 322 | ||
323 | const emailPayload: EmailPayload = { | 323 | const emailPayload: EmailPayload = { |
324 | to, | 324 | to, |
325 | subject: `[PeerTube] Video ${videoName} blacklisted`, | 325 | subject: CONFIG.EMAIL.OBJECT.PREFIX + `Video ${videoName} blacklisted`, |
326 | text | 326 | text |
327 | } | 327 | } |
328 | 328 | ||
@@ -336,11 +336,11 @@ class Emailer { | |||
336 | `Your video ${video.name} (${videoUrl}) on ${WEBSERVER.HOST} has been unblacklisted.` + | 336 | `Your video ${video.name} (${videoUrl}) on ${WEBSERVER.HOST} has been unblacklisted.` + |
337 | '\n\n' + | 337 | '\n\n' + |
338 | 'Cheers,\n' + | 338 | 'Cheers,\n' + |
339 | `PeerTube.` | 339 | `${CONFIG.EMAIL.BODY.SIGNATURE}` |
340 | 340 | ||
341 | const emailPayload: EmailPayload = { | 341 | const emailPayload: EmailPayload = { |
342 | to, | 342 | to, |
343 | subject: `[PeerTube] Video ${video.name} unblacklisted`, | 343 | subject: CONFIG.EMAIL.OBJECT.PREFIX + `Video ${video.name} unblacklisted`, |
344 | text | 344 | text |
345 | } | 345 | } |
346 | 346 | ||
@@ -353,11 +353,11 @@ class Emailer { | |||
353 | `Please follow this link to reset it: ${resetPasswordUrl}\n\n` + | 353 | `Please follow this link to reset it: ${resetPasswordUrl}\n\n` + |
354 | `If you are not the person who initiated this request, please ignore this email.\n\n` + | 354 | `If you are not the person who initiated this request, please ignore this email.\n\n` + |
355 | `Cheers,\n` + | 355 | `Cheers,\n` + |
356 | `PeerTube.` | 356 | `${CONFIG.EMAIL.BODY.SIGNATURE}` |
357 | 357 | ||
358 | const emailPayload: EmailPayload = { | 358 | const emailPayload: EmailPayload = { |
359 | to: [ to ], | 359 | to: [ to ], |
360 | subject: 'Reset your PeerTube password', | 360 | subject: CONFIG.EMAIL.OBJECT.PREFIX + 'Reset your password', |
361 | text | 361 | text |
362 | } | 362 | } |
363 | 363 | ||
@@ -370,11 +370,11 @@ class Emailer { | |||
370 | `Please follow this link to verify this email belongs to you: ${verifyEmailUrl}\n\n` + | 370 | `Please follow this link to verify this email belongs to you: ${verifyEmailUrl}\n\n` + |
371 | `If you are not the person who initiated this request, please ignore this email.\n\n` + | 371 | `If you are not the person who initiated this request, please ignore this email.\n\n` + |
372 | `Cheers,\n` + | 372 | `Cheers,\n` + |
373 | `PeerTube.` | 373 | `${CONFIG.EMAIL.BODY.SIGNATURE}` |
374 | 374 | ||
375 | const emailPayload: EmailPayload = { | 375 | const emailPayload: EmailPayload = { |
376 | to: [ to ], | 376 | to: [ to ], |
377 | subject: 'Verify your PeerTube email', | 377 | subject: CONFIG.EMAIL.OBJECT.PREFIX + 'Verify your email', |
378 | text | 378 | text |
379 | } | 379 | } |
380 | 380 | ||
@@ -390,12 +390,12 @@ class Emailer { | |||
390 | blockedString + | 390 | blockedString + |
391 | '\n\n' + | 391 | '\n\n' + |
392 | 'Cheers,\n' + | 392 | 'Cheers,\n' + |
393 | `PeerTube.` | 393 | `${CONFIG.EMAIL.BODY.SIGNATURE}` |
394 | 394 | ||
395 | const to = user.email | 395 | const to = user.email |
396 | const emailPayload: EmailPayload = { | 396 | const emailPayload: EmailPayload = { |
397 | to: [ to ], | 397 | to: [ to ], |
398 | subject: '[PeerTube] Account ' + blockedWord, | 398 | subject: CONFIG.EMAIL.OBJECT.PREFIX + 'Account ' + blockedWord, |
399 | text | 399 | text |
400 | } | 400 | } |
401 | 401 | ||
@@ -415,7 +415,7 @@ class Emailer { | |||
415 | fromDisplayName: fromEmail, | 415 | fromDisplayName: fromEmail, |
416 | replyTo: fromEmail, | 416 | replyTo: fromEmail, |
417 | to: [ CONFIG.ADMIN.EMAIL ], | 417 | to: [ CONFIG.ADMIN.EMAIL ], |
418 | subject: '[PeerTube] Contact form submitted', | 418 | subject: CONFIG.EMAIL.OBJECT.PREFIX + 'Contact form submitted', |
419 | text | 419 | text |
420 | } | 420 | } |
421 | 421 | ||
diff --git a/server/lib/files-cache/videos-preview-cache.ts b/server/lib/files-cache/videos-preview-cache.ts index 14be7f24a..a68619d07 100644 --- a/server/lib/files-cache/videos-preview-cache.ts +++ b/server/lib/files-cache/videos-preview-cache.ts | |||
@@ -21,7 +21,7 @@ class VideosPreviewCache extends AbstractVideoStaticFileCache <string> { | |||
21 | const video = await VideoModel.loadByUUIDWithFile(videoUUID) | 21 | const video = await VideoModel.loadByUUIDWithFile(videoUUID) |
22 | if (!video) return undefined | 22 | if (!video) return undefined |
23 | 23 | ||
24 | if (video.isOwned()) return { isOwned: true, path: join(CONFIG.STORAGE.PREVIEWS_DIR, video.getPreview().filename) } | 24 | if (video.isOwned()) return { isOwned: true, path: video.getPreview().getPath() } |
25 | 25 | ||
26 | return this.loadRemoteFile(videoUUID) | 26 | return this.loadRemoteFile(videoUUID) |
27 | } | 27 | } |
diff --git a/server/lib/job-queue/handlers/video-file-import.ts b/server/lib/job-queue/handlers/video-file-import.ts index 921d9a083..8cacb0ef3 100644 --- a/server/lib/job-queue/handlers/video-file-import.ts +++ b/server/lib/job-queue/handlers/video-file-import.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import * as Bull from 'bull' | 1 | import * as Bull from 'bull' |
2 | import { logger } from '../../../helpers/logger' | 2 | import { logger } from '../../../helpers/logger' |
3 | import { VideoModel } from '../../../models/video/video' | 3 | import { VideoModel } from '../../../models/video/video' |
4 | import { publishVideoIfNeeded } from './video-transcoding' | 4 | import { publishNewResolutionIfNeeded } from './video-transcoding' |
5 | import { getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffmpeg-utils' | 5 | import { getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffmpeg-utils' |
6 | import { copy, stat } from 'fs-extra' | 6 | import { copy, stat } from 'fs-extra' |
7 | import { VideoFileModel } from '../../../models/video/video-file' | 7 | import { VideoFileModel } from '../../../models/video/video-file' |
@@ -25,7 +25,7 @@ async function processVideoFileImport (job: Bull.Job) { | |||
25 | 25 | ||
26 | await updateVideoFile(video, payload.filePath) | 26 | await updateVideoFile(video, payload.filePath) |
27 | 27 | ||
28 | await publishVideoIfNeeded(video) | 28 | await publishNewResolutionIfNeeded(video) |
29 | return video | 29 | return video |
30 | } | 30 | } |
31 | 31 | ||
diff --git a/server/lib/job-queue/handlers/video-import.ts b/server/lib/job-queue/handlers/video-import.ts index 1650916a6..50e159245 100644 --- a/server/lib/job-queue/handlers/video-import.ts +++ b/server/lib/job-queue/handlers/video-import.ts | |||
@@ -209,6 +209,7 @@ async function processFile (downloader: () => Promise<string>, videoImport: Vide | |||
209 | if (videoImportUpdated.Video.state === VideoState.TO_TRANSCODE) { | 209 | if (videoImportUpdated.Video.state === VideoState.TO_TRANSCODE) { |
210 | // Put uuid because we don't have id auto incremented for now | 210 | // Put uuid because we don't have id auto incremented for now |
211 | const dataInput = { | 211 | const dataInput = { |
212 | type: 'optimize' as 'optimize', | ||
212 | videoUUID: videoImportUpdated.Video.uuid, | 213 | videoUUID: videoImportUpdated.Video.uuid, |
213 | isNewVideo: true | 214 | isNewVideo: true |
214 | } | 215 | } |
diff --git a/server/lib/job-queue/handlers/video-transcoding.ts b/server/lib/job-queue/handlers/video-transcoding.ts index 48cac517e..e9b84ecd6 100644 --- a/server/lib/job-queue/handlers/video-transcoding.ts +++ b/server/lib/job-queue/handlers/video-transcoding.ts | |||
@@ -8,18 +8,39 @@ import { retryTransactionWrapper } from '../../../helpers/database-utils' | |||
8 | import { sequelizeTypescript } from '../../../initializers' | 8 | import { sequelizeTypescript } from '../../../initializers' |
9 | import * as Bluebird from 'bluebird' | 9 | import * as Bluebird from 'bluebird' |
10 | import { computeResolutionsToTranscode } from '../../../helpers/ffmpeg-utils' | 10 | import { computeResolutionsToTranscode } from '../../../helpers/ffmpeg-utils' |
11 | import { generateHlsPlaylist, optimizeVideofile, transcodeOriginalVideofile } from '../../video-transcoding' | 11 | import { generateHlsPlaylist, optimizeVideofile, transcodeOriginalVideofile, mergeAudioVideofile } from '../../video-transcoding' |
12 | import { Notifier } from '../../notifier' | 12 | import { Notifier } from '../../notifier' |
13 | import { CONFIG } from '../../../initializers/config' | 13 | import { CONFIG } from '../../../initializers/config' |
14 | 14 | ||
15 | export type VideoTranscodingPayload = { | 15 | interface BaseTranscodingPayload { |
16 | videoUUID: string | 16 | videoUUID: string |
17 | resolution?: VideoResolution | ||
18 | isNewVideo?: boolean | 17 | isNewVideo?: boolean |
18 | } | ||
19 | |||
20 | interface HLSTranscodingPayload extends BaseTranscodingPayload { | ||
21 | type: 'hls' | ||
22 | isPortraitMode?: boolean | ||
23 | resolution: VideoResolution | ||
24 | } | ||
25 | |||
26 | interface NewResolutionTranscodingPayload extends BaseTranscodingPayload { | ||
27 | type: 'new-resolution' | ||
19 | isPortraitMode?: boolean | 28 | isPortraitMode?: boolean |
20 | generateHlsPlaylist?: boolean | 29 | resolution: VideoResolution |
30 | } | ||
31 | |||
32 | interface MergeAudioTranscodingPayload extends BaseTranscodingPayload { | ||
33 | type: 'merge-audio' | ||
34 | resolution: VideoResolution | ||
35 | } | ||
36 | |||
37 | interface OptimizeTranscodingPayload extends BaseTranscodingPayload { | ||
38 | type: 'optimize' | ||
21 | } | 39 | } |
22 | 40 | ||
41 | export type VideoTranscodingPayload = HLSTranscodingPayload | NewResolutionTranscodingPayload | ||
42 | | OptimizeTranscodingPayload | MergeAudioTranscodingPayload | ||
43 | |||
23 | async function processVideoTranscoding (job: Bull.Job) { | 44 | async function processVideoTranscoding (job: Bull.Job) { |
24 | const payload = job.data as VideoTranscodingPayload | 45 | const payload = job.data as VideoTranscodingPayload |
25 | logger.info('Processing video file in job %d.', job.id) | 46 | logger.info('Processing video file in job %d.', job.id) |
@@ -31,14 +52,18 @@ async function processVideoTranscoding (job: Bull.Job) { | |||
31 | return undefined | 52 | return undefined |
32 | } | 53 | } |
33 | 54 | ||
34 | if (payload.generateHlsPlaylist) { | 55 | if (payload.type === 'hls') { |
35 | await generateHlsPlaylist(video, payload.resolution, payload.isPortraitMode || false) | 56 | await generateHlsPlaylist(video, payload.resolution, payload.isPortraitMode || false) |
36 | 57 | ||
37 | await retryTransactionWrapper(onHlsPlaylistGenerationSuccess, video) | 58 | await retryTransactionWrapper(onHlsPlaylistGenerationSuccess, video) |
38 | } else if (payload.resolution) { // Transcoding in other resolution | 59 | } else if (payload.type === 'new-resolution') { |
39 | await transcodeOriginalVideofile(video, payload.resolution, payload.isPortraitMode || false) | 60 | await transcodeOriginalVideofile(video, payload.resolution, payload.isPortraitMode || false) |
40 | 61 | ||
41 | await retryTransactionWrapper(publishVideoIfNeeded, video, payload) | 62 | await retryTransactionWrapper(publishNewResolutionIfNeeded, video, payload) |
63 | } else if (payload.type === 'merge-audio') { | ||
64 | await mergeAudioVideofile(video, payload.resolution) | ||
65 | |||
66 | await retryTransactionWrapper(publishNewResolutionIfNeeded, video, payload) | ||
42 | } else { | 67 | } else { |
43 | await optimizeVideofile(video) | 68 | await optimizeVideofile(video) |
44 | 69 | ||
@@ -62,7 +87,7 @@ async function onHlsPlaylistGenerationSuccess (video: VideoModel) { | |||
62 | }) | 87 | }) |
63 | } | 88 | } |
64 | 89 | ||
65 | async function publishVideoIfNeeded (video: VideoModel, payload?: VideoTranscodingPayload) { | 90 | async function publishNewResolutionIfNeeded (video: VideoModel, payload?: NewResolutionTranscodingPayload | MergeAudioTranscodingPayload) { |
66 | const { videoDatabase, videoPublished } = await sequelizeTypescript.transaction(async t => { | 91 | const { videoDatabase, videoPublished } = await sequelizeTypescript.transaction(async t => { |
67 | // Maybe the video changed in database, refresh it | 92 | // Maybe the video changed in database, refresh it |
68 | let videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.uuid, t) | 93 | let videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.uuid, t) |
@@ -94,7 +119,7 @@ async function publishVideoIfNeeded (video: VideoModel, payload?: VideoTranscodi | |||
94 | await createHlsJobIfEnabled(payload) | 119 | await createHlsJobIfEnabled(payload) |
95 | } | 120 | } |
96 | 121 | ||
97 | async function onVideoFileOptimizerSuccess (videoArg: VideoModel, payload: VideoTranscodingPayload) { | 122 | async function onVideoFileOptimizerSuccess (videoArg: VideoModel, payload: OptimizeTranscodingPayload) { |
98 | if (videoArg === undefined) return undefined | 123 | if (videoArg === undefined) return undefined |
99 | 124 | ||
100 | // Outside the transaction (IO on disk) | 125 | // Outside the transaction (IO on disk) |
@@ -120,6 +145,7 @@ async function onVideoFileOptimizerSuccess (videoArg: VideoModel, payload: Video | |||
120 | 145 | ||
121 | for (const resolution of resolutionsEnabled) { | 146 | for (const resolution of resolutionsEnabled) { |
122 | const dataInput = { | 147 | const dataInput = { |
148 | type: 'new-resolution' as 'new-resolution', | ||
123 | videoUUID: videoDatabase.uuid, | 149 | videoUUID: videoDatabase.uuid, |
124 | resolution | 150 | resolution |
125 | } | 151 | } |
@@ -149,27 +175,27 @@ async function onVideoFileOptimizerSuccess (videoArg: VideoModel, payload: Video | |||
149 | if (payload.isNewVideo) Notifier.Instance.notifyOnNewVideo(videoDatabase) | 175 | if (payload.isNewVideo) Notifier.Instance.notifyOnNewVideo(videoDatabase) |
150 | if (videoPublished) Notifier.Instance.notifyOnVideoPublishedAfterTranscoding(videoDatabase) | 176 | if (videoPublished) Notifier.Instance.notifyOnVideoPublishedAfterTranscoding(videoDatabase) |
151 | 177 | ||
152 | await createHlsJobIfEnabled(Object.assign({}, payload, { resolution: videoDatabase.getOriginalFile().resolution })) | 178 | const hlsPayload = Object.assign({}, payload, { resolution: videoDatabase.getOriginalFile().resolution }) |
179 | await createHlsJobIfEnabled(hlsPayload) | ||
153 | } | 180 | } |
154 | 181 | ||
155 | // --------------------------------------------------------------------------- | 182 | // --------------------------------------------------------------------------- |
156 | 183 | ||
157 | export { | 184 | export { |
158 | processVideoTranscoding, | 185 | processVideoTranscoding, |
159 | publishVideoIfNeeded | 186 | publishNewResolutionIfNeeded |
160 | } | 187 | } |
161 | 188 | ||
162 | // --------------------------------------------------------------------------- | 189 | // --------------------------------------------------------------------------- |
163 | 190 | ||
164 | function createHlsJobIfEnabled (payload?: VideoTranscodingPayload) { | 191 | function createHlsJobIfEnabled (payload?: { videoUUID: string, resolution: number, isPortraitMode?: boolean }) { |
165 | // Generate HLS playlist? | 192 | // Generate HLS playlist? |
166 | if (payload && CONFIG.TRANSCODING.HLS.ENABLED) { | 193 | if (payload && CONFIG.TRANSCODING.HLS.ENABLED) { |
167 | const hlsTranscodingPayload = { | 194 | const hlsTranscodingPayload = { |
195 | type: 'hls' as 'hls', | ||
168 | videoUUID: payload.videoUUID, | 196 | videoUUID: payload.videoUUID, |
169 | resolution: payload.resolution, | 197 | resolution: payload.resolution, |
170 | isPortraitMode: payload.isPortraitMode, | 198 | isPortraitMode: payload.isPortraitMode |
171 | |||
172 | generateHlsPlaylist: true | ||
173 | } | 199 | } |
174 | 200 | ||
175 | return JobQueue.Instance.createJob({ type: 'video-transcoding', payload: hlsTranscodingPayload }) | 201 | return JobQueue.Instance.createJob({ type: 'video-transcoding', payload: hlsTranscodingPayload }) |
diff --git a/server/lib/thumbnail.ts b/server/lib/thumbnail.ts index 950b14c3b..18bdcded4 100644 --- a/server/lib/thumbnail.ts +++ b/server/lib/thumbnail.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { VideoFileModel } from '../models/video/video-file' | 1 | import { VideoFileModel } from '../models/video/video-file' |
2 | import { generateImageFromVideoFile } from '../helpers/ffmpeg-utils' | 2 | import { generateImageFromVideoFile } from '../helpers/ffmpeg-utils' |
3 | import { CONFIG } from '../initializers/config' | 3 | import { CONFIG } from '../initializers/config' |
4 | import { PREVIEWS_SIZE, THUMBNAILS_SIZE } from '../initializers/constants' | 4 | import { PREVIEWS_SIZE, THUMBNAILS_SIZE, ASSETS_PATH } from '../initializers/constants' |
5 | import { VideoModel } from '../models/video/video' | 5 | import { VideoModel } from '../models/video/video' |
6 | import { ThumbnailModel } from '../models/video/thumbnail' | 6 | import { ThumbnailModel } from '../models/video/thumbnail' |
7 | import { ThumbnailType } from '../../shared/models/videos/thumbnail.type' | 7 | import { ThumbnailType } from '../../shared/models/videos/thumbnail.type' |
@@ -45,8 +45,10 @@ function createVideoMiniatureFromExisting (inputPath: string, video: VideoModel, | |||
45 | function generateVideoMiniature (video: VideoModel, videoFile: VideoFileModel, type: ThumbnailType) { | 45 | function generateVideoMiniature (video: VideoModel, videoFile: VideoFileModel, type: ThumbnailType) { |
46 | const input = video.getVideoFilePath(videoFile) | 46 | const input = video.getVideoFilePath(videoFile) |
47 | 47 | ||
48 | const { filename, basePath, height, width, existingThumbnail } = buildMetadataFromVideo(video, type) | 48 | const { filename, basePath, height, width, existingThumbnail, outputPath } = buildMetadataFromVideo(video, type) |
49 | const thumbnailCreator = () => generateImageFromVideoFile(input, basePath, filename, { height, width }) | 49 | const thumbnailCreator = videoFile.isAudio() |
50 | ? () => processImage(ASSETS_PATH.DEFAULT_AUDIO_BACKGROUND, outputPath, { width, height }, true) | ||
51 | : () => generateImageFromVideoFile(input, basePath, filename, { height, width }) | ||
50 | 52 | ||
51 | return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail }) | 53 | return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail }) |
52 | } | 54 | } |
diff --git a/server/lib/user.ts b/server/lib/user.ts index 7badb3e72..d9fd89e15 100644 --- a/server/lib/user.ts +++ b/server/lib/user.ts | |||
@@ -13,7 +13,8 @@ import { UserNotificationSetting, UserNotificationSettingValue } from '../../sha | |||
13 | import { createWatchLaterPlaylist } from './video-playlist' | 13 | import { createWatchLaterPlaylist } from './video-playlist' |
14 | import { sequelizeTypescript } from '../initializers/database' | 14 | import { sequelizeTypescript } from '../initializers/database' |
15 | 15 | ||
16 | async function createUserAccountAndChannelAndPlaylist (userToCreate: UserModel, validateUser = true) { | 16 | type ChannelNames = { name: string, displayName: string } |
17 | async function createUserAccountAndChannelAndPlaylist (userToCreate: UserModel, channelNames?: ChannelNames, validateUser = true) { | ||
17 | const { user, account, videoChannel } = await sequelizeTypescript.transaction(async t => { | 18 | const { user, account, videoChannel } = await sequelizeTypescript.transaction(async t => { |
18 | const userOptions = { | 19 | const userOptions = { |
19 | transaction: t, | 20 | transaction: t, |
@@ -26,18 +27,8 @@ async function createUserAccountAndChannelAndPlaylist (userToCreate: UserModel, | |||
26 | const accountCreated = await createLocalAccountWithoutKeys(userCreated.username, userCreated.id, null, t) | 27 | const accountCreated = await createLocalAccountWithoutKeys(userCreated.username, userCreated.id, null, t) |
27 | userCreated.Account = accountCreated | 28 | userCreated.Account = accountCreated |
28 | 29 | ||
29 | let channelName = userCreated.username + '_channel' | 30 | const channelAttributes = await buildChannelAttributes(userCreated, channelNames) |
30 | 31 | const videoChannel = await createVideoChannel(channelAttributes, accountCreated, t) | |
31 | // Conflict, generate uuid instead | ||
32 | const actor = await ActorModel.loadLocalByName(channelName) | ||
33 | if (actor) channelName = uuidv4() | ||
34 | |||
35 | const videoChannelDisplayName = `Main ${userCreated.username} channel` | ||
36 | const videoChannelInfo = { | ||
37 | name: channelName, | ||
38 | displayName: videoChannelDisplayName | ||
39 | } | ||
40 | const videoChannel = await createVideoChannel(videoChannelInfo, accountCreated, t) | ||
41 | 32 | ||
42 | const videoPlaylist = await createWatchLaterPlaylist(accountCreated, t) | 33 | const videoPlaylist = await createWatchLaterPlaylist(accountCreated, t) |
43 | 34 | ||
@@ -116,3 +107,20 @@ function createDefaultUserNotificationSettings (user: UserModel, t: Sequelize.Tr | |||
116 | 107 | ||
117 | return UserNotificationSettingModel.create(values, { transaction: t }) | 108 | return UserNotificationSettingModel.create(values, { transaction: t }) |
118 | } | 109 | } |
110 | |||
111 | async function buildChannelAttributes (user: UserModel, channelNames?: ChannelNames) { | ||
112 | if (channelNames) return channelNames | ||
113 | |||
114 | let channelName = user.username + '_channel' | ||
115 | |||
116 | // Conflict, generate uuid instead | ||
117 | const actor = await ActorModel.loadLocalByName(channelName) | ||
118 | if (actor) channelName = uuidv4() | ||
119 | |||
120 | const videoChannelDisplayName = `Main ${user.username} channel` | ||
121 | |||
122 | return { | ||
123 | name: channelName, | ||
124 | displayName: videoChannelDisplayName | ||
125 | } | ||
126 | } | ||
diff --git a/server/lib/video-channel.ts b/server/lib/video-channel.ts index 0fe95ca09..ee0482c36 100644 --- a/server/lib/video-channel.ts +++ b/server/lib/video-channel.ts | |||
@@ -3,7 +3,8 @@ import * as uuidv4 from 'uuid/v4' | |||
3 | import { VideoChannelCreate } from '../../shared/models' | 3 | import { VideoChannelCreate } from '../../shared/models' |
4 | import { AccountModel } from '../models/account/account' | 4 | import { AccountModel } from '../models/account/account' |
5 | import { VideoChannelModel } from '../models/video/video-channel' | 5 | import { VideoChannelModel } from '../models/video/video-channel' |
6 | import { buildActorInstance, getVideoChannelActivityPubUrl } from './activitypub' | 6 | import { buildActorInstance, federateVideoIfNeeded, getVideoChannelActivityPubUrl } from './activitypub' |
7 | import { VideoModel } from '../models/video/video' | ||
7 | 8 | ||
8 | async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account: AccountModel, t: Sequelize.Transaction) { | 9 | async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account: AccountModel, t: Sequelize.Transaction) { |
9 | const uuid = uuidv4() | 10 | const uuid = uuidv4() |
@@ -33,8 +34,19 @@ async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account | |||
33 | return videoChannelCreated | 34 | return videoChannelCreated |
34 | } | 35 | } |
35 | 36 | ||
37 | async function federateAllVideosOfChannel (videoChannel: VideoChannelModel) { | ||
38 | const videoIds = await VideoModel.getAllIdsFromChannel(videoChannel) | ||
39 | |||
40 | for (const videoId of videoIds) { | ||
41 | const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoId) | ||
42 | |||
43 | await federateVideoIfNeeded(video, false) | ||
44 | } | ||
45 | } | ||
46 | |||
36 | // --------------------------------------------------------------------------- | 47 | // --------------------------------------------------------------------------- |
37 | 48 | ||
38 | export { | 49 | export { |
39 | createVideoChannel | 50 | createVideoChannel, |
51 | federateAllVideosOfChannel | ||
40 | } | 52 | } |
diff --git a/server/lib/video-transcoding.ts b/server/lib/video-transcoding.ts index 0fe0ff12a..8d786e0ef 100644 --- a/server/lib/video-transcoding.ts +++ b/server/lib/video-transcoding.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { HLS_STREAMING_PLAYLIST_DIRECTORY, P2P_MEDIA_LOADER_PEER_VERSION, WEBSERVER } from '../initializers/constants' | 1 | import { HLS_STREAMING_PLAYLIST_DIRECTORY, P2P_MEDIA_LOADER_PEER_VERSION, WEBSERVER } from '../initializers/constants' |
2 | import { join } from 'path' | 2 | import { join } from 'path' |
3 | import { getVideoFileFPS, transcode } from '../helpers/ffmpeg-utils' | 3 | import { canDoQuickTranscode, getVideoFileFPS, transcode, TranscodeOptions, TranscodeOptionsType } from '../helpers/ffmpeg-utils' |
4 | import { ensureDir, move, remove, stat } from 'fs-extra' | 4 | import { ensureDir, move, remove, stat } from 'fs-extra' |
5 | import { logger } from '../helpers/logger' | 5 | import { logger } from '../helpers/logger' |
6 | import { VideoResolution } from '../../shared/models/videos' | 6 | import { VideoResolution } from '../../shared/models/videos' |
@@ -11,15 +11,24 @@ import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-pla | |||
11 | import { VideoStreamingPlaylistType } from '../../shared/models/videos/video-streaming-playlist.type' | 11 | import { VideoStreamingPlaylistType } from '../../shared/models/videos/video-streaming-playlist.type' |
12 | import { CONFIG } from '../initializers/config' | 12 | import { CONFIG } from '../initializers/config' |
13 | 13 | ||
14 | /** | ||
15 | * Optimize the original video file and replace it. The resolution is not changed. | ||
16 | */ | ||
14 | async function optimizeVideofile (video: VideoModel, inputVideoFileArg?: VideoFileModel) { | 17 | async function optimizeVideofile (video: VideoModel, inputVideoFileArg?: VideoFileModel) { |
15 | const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR | 18 | const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR |
19 | const transcodeDirectory = CONFIG.STORAGE.TMP_DIR | ||
16 | const newExtname = '.mp4' | 20 | const newExtname = '.mp4' |
17 | 21 | ||
18 | const inputVideoFile = inputVideoFileArg ? inputVideoFileArg : video.getOriginalFile() | 22 | const inputVideoFile = inputVideoFileArg ? inputVideoFileArg : video.getOriginalFile() |
19 | const videoInputPath = join(videosDirectory, video.getVideoFilename(inputVideoFile)) | 23 | const videoInputPath = join(videosDirectory, video.getVideoFilename(inputVideoFile)) |
20 | const videoTranscodedPath = join(videosDirectory, video.id + '-transcoded' + newExtname) | 24 | const videoTranscodedPath = join(transcodeDirectory, video.id + '-transcoded' + newExtname) |
21 | 25 | ||
22 | const transcodeOptions = { | 26 | const transcodeType: TranscodeOptionsType = await canDoQuickTranscode(videoInputPath) |
27 | ? 'quick-transcode' | ||
28 | : 'video' | ||
29 | |||
30 | const transcodeOptions: TranscodeOptions = { | ||
31 | type: transcodeType as any, // FIXME: typing issue | ||
23 | inputPath: videoInputPath, | 32 | inputPath: videoInputPath, |
24 | outputPath: videoTranscodedPath, | 33 | outputPath: videoTranscodedPath, |
25 | resolution: inputVideoFile.resolution | 34 | resolution: inputVideoFile.resolution |
@@ -32,18 +41,11 @@ async function optimizeVideofile (video: VideoModel, inputVideoFileArg?: VideoFi | |||
32 | await remove(videoInputPath) | 41 | await remove(videoInputPath) |
33 | 42 | ||
34 | // Important to do this before getVideoFilename() to take in account the new file extension | 43 | // Important to do this before getVideoFilename() to take in account the new file extension |
35 | inputVideoFile.set('extname', newExtname) | 44 | inputVideoFile.extname = newExtname |
36 | 45 | ||
37 | const videoOutputPath = video.getVideoFilePath(inputVideoFile) | 46 | const videoOutputPath = video.getVideoFilePath(inputVideoFile) |
38 | await move(videoTranscodedPath, videoOutputPath) | ||
39 | const stats = await stat(videoOutputPath) | ||
40 | const fps = await getVideoFileFPS(videoOutputPath) | ||
41 | 47 | ||
42 | inputVideoFile.set('size', stats.size) | 48 | await onVideoFileTranscoding(video, inputVideoFile, videoTranscodedPath, videoOutputPath) |
43 | inputVideoFile.set('fps', fps) | ||
44 | |||
45 | await video.createTorrentAndSetInfoHash(inputVideoFile) | ||
46 | await inputVideoFile.save() | ||
47 | } catch (err) { | 49 | } catch (err) { |
48 | // Auto destruction... | 50 | // Auto destruction... |
49 | video.destroy().catch(err => logger.error('Cannot destruct video after transcoding failure.', { err })) | 51 | video.destroy().catch(err => logger.error('Cannot destruct video after transcoding failure.', { err })) |
@@ -52,8 +54,12 @@ async function optimizeVideofile (video: VideoModel, inputVideoFileArg?: VideoFi | |||
52 | } | 54 | } |
53 | } | 55 | } |
54 | 56 | ||
57 | /** | ||
58 | * Transcode the original video file to a lower resolution. | ||
59 | */ | ||
55 | async function transcodeOriginalVideofile (video: VideoModel, resolution: VideoResolution, isPortrait: boolean) { | 60 | async function transcodeOriginalVideofile (video: VideoModel, resolution: VideoResolution, isPortrait: boolean) { |
56 | const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR | 61 | const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR |
62 | const transcodeDirectory = CONFIG.STORAGE.TMP_DIR | ||
57 | const extname = '.mp4' | 63 | const extname = '.mp4' |
58 | 64 | ||
59 | // We are sure it's x264 in mp4 because optimizeOriginalVideofile was already executed | 65 | // We are sure it's x264 in mp4 because optimizeOriginalVideofile was already executed |
@@ -66,27 +72,49 @@ async function transcodeOriginalVideofile (video: VideoModel, resolution: VideoR | |||
66 | videoId: video.id | 72 | videoId: video.id |
67 | }) | 73 | }) |
68 | const videoOutputPath = join(CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename(newVideoFile)) | 74 | const videoOutputPath = join(CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename(newVideoFile)) |
75 | const videoTranscodedPath = join(transcodeDirectory, video.getVideoFilename(newVideoFile)) | ||
69 | 76 | ||
70 | const transcodeOptions = { | 77 | const transcodeOptions = { |
78 | type: 'video' as 'video', | ||
71 | inputPath: videoInputPath, | 79 | inputPath: videoInputPath, |
72 | outputPath: videoOutputPath, | 80 | outputPath: videoTranscodedPath, |
73 | resolution, | 81 | resolution, |
74 | isPortraitMode: isPortrait | 82 | isPortraitMode: isPortrait |
75 | } | 83 | } |
76 | 84 | ||
77 | await transcode(transcodeOptions) | 85 | await transcode(transcodeOptions) |
78 | 86 | ||
79 | const stats = await stat(videoOutputPath) | 87 | return onVideoFileTranscoding(video, newVideoFile, videoTranscodedPath, videoOutputPath) |
80 | const fps = await getVideoFileFPS(videoOutputPath) | 88 | } |
89 | |||
90 | async function mergeAudioVideofile (video: VideoModel, resolution: VideoResolution) { | ||
91 | const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR | ||
92 | const transcodeDirectory = CONFIG.STORAGE.TMP_DIR | ||
93 | const newExtname = '.mp4' | ||
94 | |||
95 | const inputVideoFile = video.getOriginalFile() | ||
81 | 96 | ||
82 | newVideoFile.set('size', stats.size) | 97 | const audioInputPath = join(videosDirectory, video.getVideoFilename(video.getOriginalFile())) |
83 | newVideoFile.set('fps', fps) | 98 | const videoTranscodedPath = join(transcodeDirectory, video.id + '-transcoded' + newExtname) |
84 | 99 | ||
85 | await video.createTorrentAndSetInfoHash(newVideoFile) | 100 | const transcodeOptions = { |
101 | type: 'merge-audio' as 'merge-audio', | ||
102 | inputPath: video.getPreview().getPath(), | ||
103 | outputPath: videoTranscodedPath, | ||
104 | audioPath: audioInputPath, | ||
105 | resolution | ||
106 | } | ||
107 | |||
108 | await transcode(transcodeOptions) | ||
86 | 109 | ||
87 | await newVideoFile.save() | 110 | await remove(audioInputPath) |
88 | 111 | ||
89 | video.VideoFiles.push(newVideoFile) | 112 | // Important to do this before getVideoFilename() to take in account the new file extension |
113 | inputVideoFile.extname = newExtname | ||
114 | |||
115 | const videoOutputPath = video.getVideoFilePath(inputVideoFile) | ||
116 | |||
117 | return onVideoFileTranscoding(video, inputVideoFile, videoTranscodedPath, videoOutputPath) | ||
90 | } | 118 | } |
91 | 119 | ||
92 | async function generateHlsPlaylist (video: VideoModel, resolution: VideoResolution, isPortraitMode: boolean) { | 120 | async function generateHlsPlaylist (video: VideoModel, resolution: VideoResolution, isPortraitMode: boolean) { |
@@ -97,6 +125,7 @@ async function generateHlsPlaylist (video: VideoModel, resolution: VideoResoluti | |||
97 | const outputPath = join(baseHlsDirectory, VideoStreamingPlaylistModel.getHlsPlaylistFilename(resolution)) | 125 | const outputPath = join(baseHlsDirectory, VideoStreamingPlaylistModel.getHlsPlaylistFilename(resolution)) |
98 | 126 | ||
99 | const transcodeOptions = { | 127 | const transcodeOptions = { |
128 | type: 'hls' as 'hls', | ||
100 | inputPath: videoInputPath, | 129 | inputPath: videoInputPath, |
101 | outputPath, | 130 | outputPath, |
102 | resolution, | 131 | resolution, |
@@ -125,8 +154,34 @@ async function generateHlsPlaylist (video: VideoModel, resolution: VideoResoluti | |||
125 | }) | 154 | }) |
126 | } | 155 | } |
127 | 156 | ||
157 | // --------------------------------------------------------------------------- | ||
158 | |||
128 | export { | 159 | export { |
129 | generateHlsPlaylist, | 160 | generateHlsPlaylist, |
130 | optimizeVideofile, | 161 | optimizeVideofile, |
131 | transcodeOriginalVideofile | 162 | transcodeOriginalVideofile, |
163 | mergeAudioVideofile | ||
164 | } | ||
165 | |||
166 | // --------------------------------------------------------------------------- | ||
167 | |||
168 | async function onVideoFileTranscoding (video: VideoModel, videoFile: VideoFileModel, transcodingPath: string, outputPath: string) { | ||
169 | const stats = await stat(transcodingPath) | ||
170 | const fps = await getVideoFileFPS(transcodingPath) | ||
171 | |||
172 | await move(transcodingPath, outputPath) | ||
173 | |||
174 | videoFile.set('size', stats.size) | ||
175 | videoFile.set('fps', fps) | ||
176 | |||
177 | await video.createTorrentAndSetInfoHash(videoFile) | ||
178 | |||
179 | const updatedVideoFile = await videoFile.save() | ||
180 | |||
181 | // Add it if this is a new created file | ||
182 | if (video.VideoFiles.some(f => f.id === videoFile.id) === false) { | ||
183 | video.VideoFiles.push(updatedVideoFile) | ||
184 | } | ||
185 | |||
186 | return video | ||
132 | } | 187 | } |