diff options
author | Chocobozzz <me@florianbigard.com> | 2020-09-17 10:00:46 +0200 |
---|---|---|
committer | Chocobozzz <chocobozzz@cpy.re> | 2020-11-09 15:33:04 +0100 |
commit | 1ef65f4c034cc53ab5d55417e52d60e1f7fc1ddb (patch) | |
tree | 8bb02f8dc2590e5071306fb311bdc53289e20336 | |
parent | c6c0fa6cd8fe8f752463d8982c3dbcd448739c4e (diff) | |
download | PeerTube-1ef65f4c034cc53ab5d55417e52d60e1f7fc1ddb.tar.gz PeerTube-1ef65f4c034cc53ab5d55417e52d60e1f7fc1ddb.tar.zst PeerTube-1ef65f4c034cc53ab5d55417e52d60e1f7fc1ddb.zip |
Refactor video creation
-rw-r--r-- | server/controllers/api/videos/import.ts | 72 | ||||
-rw-r--r-- | server/controllers/api/videos/index.ts | 78 | ||||
-rw-r--r-- | server/controllers/api/videos/live.ts | 40 | ||||
-rw-r--r-- | server/lib/activitypub/videos.ts | 8 | ||||
-rw-r--r-- | server/lib/thumbnail.ts | 17 | ||||
-rw-r--r-- | server/lib/video.ts | 68 |
6 files changed, 151 insertions, 132 deletions
diff --git a/server/controllers/api/videos/import.ts b/server/controllers/api/videos/import.ts index 24a237304..5840cd063 100644 --- a/server/controllers/api/videos/import.ts +++ b/server/controllers/api/videos/import.ts | |||
@@ -1,30 +1,10 @@ | |||
1 | import * as Bluebird from 'bluebird' | ||
1 | import * as express from 'express' | 2 | import * as express from 'express' |
3 | import { move, readFile } from 'fs-extra' | ||
2 | import * as magnetUtil from 'magnet-uri' | 4 | import * as magnetUtil from 'magnet-uri' |
3 | import { auditLoggerFactory, getAuditIdFromRes, VideoImportAuditView } from '../../../helpers/audit-logger' | ||
4 | import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoImportAddValidator } from '../../../middlewares' | ||
5 | import { MIMETYPES } from '../../../initializers/constants' | ||
6 | import { getYoutubeDLInfo, YoutubeDLInfo, getYoutubeDLSubs } from '../../../helpers/youtube-dl' | ||
7 | import { createReqFiles } from '../../../helpers/express-utils' | ||
8 | import { logger } from '../../../helpers/logger' | ||
9 | import { VideoImportCreate, VideoImportState, VideoPrivacy, VideoState } from '../../../../shared' | ||
10 | import { VideoModel } from '../../../models/video/video' | ||
11 | import { VideoCaptionModel } from '../../../models/video/video-caption' | ||
12 | import { moveAndProcessCaptionFile } from '../../../helpers/captions-utils' | ||
13 | import { getVideoActivityPubUrl } from '../../../lib/activitypub/url' | ||
14 | import { TagModel } from '../../../models/video/tag' | ||
15 | import { VideoImportModel } from '../../../models/video/video-import' | ||
16 | import { JobQueue } from '../../../lib/job-queue/job-queue' | ||
17 | import { join } from 'path' | ||
18 | import { isArray } from '../../../helpers/custom-validators/misc' | ||
19 | import * as Bluebird from 'bluebird' | ||
20 | import * as parseTorrent from 'parse-torrent' | 5 | import * as parseTorrent from 'parse-torrent' |
21 | import { getSecureTorrentName } from '../../../helpers/utils' | 6 | import { join } from 'path' |
22 | import { move, readFile } from 'fs-extra' | 7 | import { setVideoTags } from '@server/lib/video' |
23 | import { autoBlacklistVideoIfNeeded } from '../../../lib/video-blacklist' | ||
24 | import { CONFIG } from '../../../initializers/config' | ||
25 | import { sequelizeTypescript } from '../../../initializers/database' | ||
26 | import { createVideoMiniatureFromExisting, createVideoMiniatureFromUrl } from '../../../lib/thumbnail' | ||
27 | import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type' | ||
28 | import { | 8 | import { |
29 | MChannelAccountDefault, | 9 | MChannelAccountDefault, |
30 | MThumbnail, | 10 | MThumbnail, |
@@ -36,6 +16,26 @@ import { | |||
36 | MVideoWithBlacklistLight | 16 | MVideoWithBlacklistLight |
37 | } from '@server/types/models' | 17 | } from '@server/types/models' |
38 | import { MVideoImport, MVideoImportFormattable } from '@server/types/models/video/video-import' | 18 | import { MVideoImport, MVideoImportFormattable } from '@server/types/models/video/video-import' |
19 | import { VideoImportCreate, VideoImportState, VideoPrivacy, VideoState } from '../../../../shared' | ||
20 | import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type' | ||
21 | import { auditLoggerFactory, getAuditIdFromRes, VideoImportAuditView } from '../../../helpers/audit-logger' | ||
22 | import { moveAndProcessCaptionFile } from '../../../helpers/captions-utils' | ||
23 | import { isArray } from '../../../helpers/custom-validators/misc' | ||
24 | import { createReqFiles } from '../../../helpers/express-utils' | ||
25 | import { logger } from '../../../helpers/logger' | ||
26 | import { getSecureTorrentName } from '../../../helpers/utils' | ||
27 | import { getYoutubeDLInfo, getYoutubeDLSubs, YoutubeDLInfo } from '../../../helpers/youtube-dl' | ||
28 | import { CONFIG } from '../../../initializers/config' | ||
29 | import { MIMETYPES } from '../../../initializers/constants' | ||
30 | import { sequelizeTypescript } from '../../../initializers/database' | ||
31 | import { getVideoActivityPubUrl } from '../../../lib/activitypub/url' | ||
32 | import { JobQueue } from '../../../lib/job-queue/job-queue' | ||
33 | import { createVideoMiniatureFromExisting, createVideoMiniatureFromUrl } from '../../../lib/thumbnail' | ||
34 | import { autoBlacklistVideoIfNeeded } from '../../../lib/video-blacklist' | ||
35 | import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoImportAddValidator } from '../../../middlewares' | ||
36 | import { VideoModel } from '../../../models/video/video' | ||
37 | import { VideoCaptionModel } from '../../../models/video/video-caption' | ||
38 | import { VideoImportModel } from '../../../models/video/video-import' | ||
39 | 39 | ||
40 | const auditLogger = auditLoggerFactory('video-imports') | 40 | const auditLogger = auditLoggerFactory('video-imports') |
41 | const videoImportsRouter = express.Router() | 41 | const videoImportsRouter = express.Router() |
@@ -260,7 +260,12 @@ async function processThumbnail (req: express.Request, video: VideoModel) { | |||
260 | if (thumbnailField) { | 260 | if (thumbnailField) { |
261 | const thumbnailPhysicalFile = thumbnailField[0] | 261 | const thumbnailPhysicalFile = thumbnailField[0] |
262 | 262 | ||
263 | return createVideoMiniatureFromExisting(thumbnailPhysicalFile.path, video, ThumbnailType.MINIATURE, false) | 263 | return createVideoMiniatureFromExisting({ |
264 | inputPath: thumbnailPhysicalFile.path, | ||
265 | video, | ||
266 | type: ThumbnailType.MINIATURE, | ||
267 | automaticallyGenerated: false | ||
268 | }) | ||
264 | } | 269 | } |
265 | 270 | ||
266 | return undefined | 271 | return undefined |
@@ -271,7 +276,12 @@ async function processPreview (req: express.Request, video: VideoModel) { | |||
271 | if (previewField) { | 276 | if (previewField) { |
272 | const previewPhysicalFile = previewField[0] | 277 | const previewPhysicalFile = previewField[0] |
273 | 278 | ||
274 | return createVideoMiniatureFromExisting(previewPhysicalFile.path, video, ThumbnailType.PREVIEW, false) | 279 | return createVideoMiniatureFromExisting({ |
280 | inputPath: previewPhysicalFile.path, | ||
281 | video, | ||
282 | type: ThumbnailType.PREVIEW, | ||
283 | automaticallyGenerated: false | ||
284 | }) | ||
275 | } | 285 | } |
276 | 286 | ||
277 | return undefined | 287 | return undefined |
@@ -325,15 +335,7 @@ function insertIntoDB (parameters: { | |||
325 | transaction: t | 335 | transaction: t |
326 | }) | 336 | }) |
327 | 337 | ||
328 | // Set tags to the video | 338 | await setVideoTags({ video: videoCreated, tags, transaction: t }) |
329 | if (tags) { | ||
330 | const tagInstances = await TagModel.findOrCreateTags(tags, t) | ||
331 | |||
332 | await videoCreated.$set('Tags', tagInstances, sequelizeOptions) | ||
333 | videoCreated.Tags = tagInstances | ||
334 | } else { | ||
335 | videoCreated.Tags = [] | ||
336 | } | ||
337 | 339 | ||
338 | // Create video import object in database | 340 | // Create video import object in database |
339 | const videoImport = await VideoImportModel.create( | 341 | const videoImport = await VideoImportModel.create( |
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts index 94f0361ee..1539afc35 100644 --- a/server/controllers/api/videos/index.ts +++ b/server/controllers/api/videos/index.ts | |||
@@ -6,11 +6,11 @@ import { addOptimizeOrMergeAudioJob } from '@server/helpers/video' | |||
6 | import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent' | 6 | import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent' |
7 | import { changeVideoChannelShare } from '@server/lib/activitypub/share' | 7 | import { changeVideoChannelShare } from '@server/lib/activitypub/share' |
8 | import { getVideoActivityPubUrl } from '@server/lib/activitypub/url' | 8 | import { getVideoActivityPubUrl } from '@server/lib/activitypub/url' |
9 | import { buildLocalVideoFromReq, buildVideoThumbnailsFromReq, setVideoTags } from '@server/lib/video' | ||
9 | import { getVideoFilePath } from '@server/lib/video-paths' | 10 | import { getVideoFilePath } from '@server/lib/video-paths' |
10 | import { getServerActor } from '@server/models/application/application' | 11 | import { getServerActor } from '@server/models/application/application' |
11 | import { MVideoDetails, MVideoFullLight } from '@server/types/models' | 12 | import { MVideoDetails, MVideoFullLight } from '@server/types/models' |
12 | import { VideoCreate, VideoPrivacy, VideoState, VideoUpdate } from '../../../../shared' | 13 | import { VideoCreate, VideoState, VideoUpdate } from '../../../../shared' |
13 | import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type' | ||
14 | import { VideoFilter } from '../../../../shared/models/videos/video-query.type' | 14 | import { VideoFilter } from '../../../../shared/models/videos/video-query.type' |
15 | import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger' | 15 | import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger' |
16 | import { resetSequelizeInstance } from '../../../helpers/database-utils' | 16 | import { resetSequelizeInstance } from '../../../helpers/database-utils' |
@@ -34,7 +34,7 @@ import { JobQueue } from '../../../lib/job-queue' | |||
34 | import { Notifier } from '../../../lib/notifier' | 34 | import { Notifier } from '../../../lib/notifier' |
35 | import { Hooks } from '../../../lib/plugins/hooks' | 35 | import { Hooks } from '../../../lib/plugins/hooks' |
36 | import { Redis } from '../../../lib/redis' | 36 | import { Redis } from '../../../lib/redis' |
37 | import { createVideoMiniatureFromExisting, generateVideoMiniature } from '../../../lib/thumbnail' | 37 | import { generateVideoMiniature } from '../../../lib/thumbnail' |
38 | import { autoBlacklistVideoIfNeeded } from '../../../lib/video-blacklist' | 38 | import { autoBlacklistVideoIfNeeded } from '../../../lib/video-blacklist' |
39 | import { | 39 | import { |
40 | asyncMiddleware, | 40 | asyncMiddleware, |
@@ -186,25 +186,9 @@ async function addVideo (req: express.Request, res: express.Response) { | |||
186 | const videoPhysicalFile = req.files['videofile'][0] | 186 | const videoPhysicalFile = req.files['videofile'][0] |
187 | const videoInfo: VideoCreate = req.body | 187 | const videoInfo: VideoCreate = req.body |
188 | 188 | ||
189 | // Prepare data so we don't block the transaction | 189 | const videoData = buildLocalVideoFromReq(videoInfo, res.locals.videoChannel.id) |
190 | const videoData = { | 190 | videoData.state = CONFIG.TRANSCODING.ENABLED ? VideoState.TO_TRANSCODE : VideoState.PUBLISHED |
191 | name: videoInfo.name, | 191 | videoData.duration = videoPhysicalFile['duration'] // duration was added by a previous middleware |
192 | remote: false, | ||
193 | category: videoInfo.category, | ||
194 | licence: videoInfo.licence, | ||
195 | language: videoInfo.language, | ||
196 | commentsEnabled: videoInfo.commentsEnabled !== false, // If the value is not "false", the default is "true" | ||
197 | downloadEnabled: videoInfo.downloadEnabled !== false, | ||
198 | waitTranscoding: videoInfo.waitTranscoding || false, | ||
199 | state: CONFIG.TRANSCODING.ENABLED ? VideoState.TO_TRANSCODE : VideoState.PUBLISHED, | ||
200 | nsfw: videoInfo.nsfw || false, | ||
201 | description: videoInfo.description, | ||
202 | support: videoInfo.support, | ||
203 | privacy: videoInfo.privacy || VideoPrivacy.PRIVATE, | ||
204 | duration: videoPhysicalFile['duration'], // duration was added by a previous middleware | ||
205 | channelId: res.locals.videoChannel.id, | ||
206 | originallyPublishedAt: videoInfo.originallyPublishedAt | ||
207 | } | ||
208 | 192 | ||
209 | const video = new VideoModel(videoData) as MVideoDetails | 193 | const video = new VideoModel(videoData) as MVideoDetails |
210 | video.url = getVideoActivityPubUrl(video) // We use the UUID, so set the URL after building the object | 194 | video.url = getVideoActivityPubUrl(video) // We use the UUID, so set the URL after building the object |
@@ -230,17 +214,11 @@ async function addVideo (req: express.Request, res: express.Response) { | |||
230 | videoPhysicalFile.filename = getVideoFilePath(video, videoFile) | 214 | videoPhysicalFile.filename = getVideoFilePath(video, videoFile) |
231 | videoPhysicalFile.path = destination | 215 | videoPhysicalFile.path = destination |
232 | 216 | ||
233 | // Process thumbnail or create it from the video | 217 | const [ thumbnailModel, previewModel ] = await buildVideoThumbnailsFromReq({ |
234 | const thumbnailField = req.files['thumbnailfile'] | 218 | video, |
235 | const thumbnailModel = thumbnailField | 219 | files: req.files, |
236 | ? await createVideoMiniatureFromExisting(thumbnailField[0].path, video, ThumbnailType.MINIATURE, false) | 220 | fallback: type => generateVideoMiniature(video, videoFile, type) |
237 | : await generateVideoMiniature(video, videoFile, ThumbnailType.MINIATURE) | 221 | }) |
238 | |||
239 | // Process preview or create it from the video | ||
240 | const previewField = req.files['previewfile'] | ||
241 | const previewModel = previewField | ||
242 | ? await createVideoMiniatureFromExisting(previewField[0].path, video, ThumbnailType.PREVIEW, false) | ||
243 | : await generateVideoMiniature(video, videoFile, ThumbnailType.PREVIEW) | ||
244 | 222 | ||
245 | // Create the torrent file | 223 | // Create the torrent file |
246 | await createTorrentAndSetInfoHash(video, videoFile) | 224 | await createTorrentAndSetInfoHash(video, videoFile) |
@@ -261,13 +239,7 @@ async function addVideo (req: express.Request, res: express.Response) { | |||
261 | 239 | ||
262 | video.VideoFiles = [ videoFile ] | 240 | video.VideoFiles = [ videoFile ] |
263 | 241 | ||
264 | // Create tags | 242 | await setVideoTags({ video, tags: videoInfo.tags, transaction: t }) |
265 | if (videoInfo.tags !== undefined) { | ||
266 | const tagInstances = await TagModel.findOrCreateTags(videoInfo.tags, t) | ||
267 | |||
268 | await video.$set('Tags', tagInstances, sequelizeOptions) | ||
269 | video.Tags = tagInstances | ||
270 | } | ||
271 | 243 | ||
272 | // Schedule an update in the future? | 244 | // Schedule an update in the future? |
273 | if (videoInfo.scheduleUpdate) { | 245 | if (videoInfo.scheduleUpdate) { |
@@ -318,14 +290,12 @@ async function updateVideo (req: express.Request, res: express.Response) { | |||
318 | const wasConfidentialVideo = videoInstance.isConfidential() | 290 | const wasConfidentialVideo = videoInstance.isConfidential() |
319 | const hadPrivacyForFederation = videoInstance.hasPrivacyForFederation() | 291 | const hadPrivacyForFederation = videoInstance.hasPrivacyForFederation() |
320 | 292 | ||
321 | // Process thumbnail or create it from the video | 293 | const [ thumbnailModel, previewModel ] = await buildVideoThumbnailsFromReq({ |
322 | const thumbnailModel = req.files?.['thumbnailfile'] | 294 | video: videoInstance, |
323 | ? await createVideoMiniatureFromExisting(req.files['thumbnailfile'][0].path, videoInstance, ThumbnailType.MINIATURE, false) | 295 | files: req.files, |
324 | : undefined | 296 | fallback: () => Promise.resolve(undefined), |
325 | 297 | automaticallyGenerated: false | |
326 | const previewModel = req.files?.['previewfile'] | 298 | }) |
327 | ? await createVideoMiniatureFromExisting(req.files['previewfile'][0].path, videoInstance, ThumbnailType.PREVIEW, false) | ||
328 | : undefined | ||
329 | 299 | ||
330 | try { | 300 | try { |
331 | const videoInstanceUpdated = await sequelizeTypescript.transaction(async t => { | 301 | const videoInstanceUpdated = await sequelizeTypescript.transaction(async t => { |
@@ -366,12 +336,12 @@ async function updateVideo (req: express.Request, res: express.Response) { | |||
366 | if (previewModel) await videoInstanceUpdated.addAndSaveThumbnail(previewModel, t) | 336 | if (previewModel) await videoInstanceUpdated.addAndSaveThumbnail(previewModel, t) |
367 | 337 | ||
368 | // Video tags update? | 338 | // Video tags update? |
369 | if (videoInfoToUpdate.tags !== undefined) { | 339 | await setVideoTags({ |
370 | const tagInstances = await TagModel.findOrCreateTags(videoInfoToUpdate.tags, t) | 340 | video: videoInstanceUpdated, |
371 | 341 | tags: videoInfoToUpdate.tags, | |
372 | await videoInstanceUpdated.$set('Tags', tagInstances, sequelizeOptions) | 342 | transaction: t, |
373 | videoInstanceUpdated.Tags = tagInstances | 343 | defaultValue: videoInstanceUpdated.Tags |
374 | } | 344 | }) |
375 | 345 | ||
376 | // Video channel update? | 346 | // Video channel update? |
377 | if (res.locals.videoChannel && videoInstanceUpdated.channelId !== res.locals.videoChannel.id) { | 347 | if (res.locals.videoChannel && videoInstanceUpdated.channelId !== res.locals.videoChannel.id) { |
diff --git a/server/controllers/api/videos/live.ts b/server/controllers/api/videos/live.ts index d08ef9869..97b135f96 100644 --- a/server/controllers/api/videos/live.ts +++ b/server/controllers/api/videos/live.ts | |||
@@ -4,18 +4,16 @@ import { createReqFiles } from '@server/helpers/express-utils' | |||
4 | import { CONFIG } from '@server/initializers/config' | 4 | import { CONFIG } from '@server/initializers/config' |
5 | import { ASSETS_PATH, MIMETYPES } from '@server/initializers/constants' | 5 | import { ASSETS_PATH, MIMETYPES } from '@server/initializers/constants' |
6 | import { getVideoActivityPubUrl } from '@server/lib/activitypub/url' | 6 | import { getVideoActivityPubUrl } from '@server/lib/activitypub/url' |
7 | import { buildLocalVideoFromReq, buildVideoThumbnailsFromReq, setVideoTags } from '@server/lib/video' | ||
7 | import { videoLiveAddValidator, videoLiveGetValidator } from '@server/middlewares/validators/videos/video-live' | 8 | import { videoLiveAddValidator, videoLiveGetValidator } from '@server/middlewares/validators/videos/video-live' |
8 | import { VideoLiveModel } from '@server/models/video/video-live' | 9 | import { VideoLiveModel } from '@server/models/video/video-live' |
9 | import { MVideoDetails, MVideoFullLight } from '@server/types/models' | 10 | import { MVideoDetails, MVideoFullLight } from '@server/types/models' |
10 | import { VideoCreate, VideoPrivacy, VideoState } from '../../../../shared' | 11 | import { VideoCreate, VideoState } from '../../../../shared' |
11 | import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type' | ||
12 | import { logger } from '../../../helpers/logger' | 12 | import { logger } from '../../../helpers/logger' |
13 | import { sequelizeTypescript } from '../../../initializers/database' | 13 | import { sequelizeTypescript } from '../../../initializers/database' |
14 | import { createVideoMiniatureFromExisting } from '../../../lib/thumbnail' | 14 | import { createVideoMiniatureFromExisting } from '../../../lib/thumbnail' |
15 | import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate } from '../../../middlewares' | 15 | import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate } from '../../../middlewares' |
16 | import { TagModel } from '../../../models/video/tag' | ||
17 | import { VideoModel } from '../../../models/video/video' | 16 | import { VideoModel } from '../../../models/video/video' |
18 | import { buildLocalVideoFromCreate } from '@server/lib/video' | ||
19 | 17 | ||
20 | const liveRouter = express.Router() | 18 | const liveRouter = express.Router() |
21 | 19 | ||
@@ -59,26 +57,24 @@ async function addLiveVideo (req: express.Request, res: express.Response) { | |||
59 | const videoInfo: VideoCreate = req.body | 57 | const videoInfo: VideoCreate = req.body |
60 | 58 | ||
61 | // Prepare data so we don't block the transaction | 59 | // Prepare data so we don't block the transaction |
62 | const videoData = buildLocalVideoFromCreate(videoInfo, res.locals.videoChannel.id) | 60 | const videoData = buildLocalVideoFromReq(videoInfo, res.locals.videoChannel.id) |
63 | videoData.isLive = true | 61 | videoData.isLive = true |
64 | 62 | videoData.state = VideoState.WAITING_FOR_LIVE | |
65 | const videoLive = new VideoLiveModel() | 63 | videoData.duration = 0 |
66 | videoLive.streamKey = uuidv4() | ||
67 | 64 | ||
68 | const video = new VideoModel(videoData) as MVideoDetails | 65 | const video = new VideoModel(videoData) as MVideoDetails |
69 | video.url = getVideoActivityPubUrl(video) // We use the UUID, so set the URL after building the object | 66 | video.url = getVideoActivityPubUrl(video) // We use the UUID, so set the URL after building the object |
70 | 67 | ||
71 | // Process thumbnail or create it from the video | 68 | const videoLive = new VideoLiveModel() |
72 | const thumbnailField = req.files ? req.files['thumbnailfile'] : null | 69 | videoLive.streamKey = uuidv4() |
73 | const thumbnailModel = thumbnailField | ||
74 | ? await createVideoMiniatureFromExisting(thumbnailField[0].path, video, ThumbnailType.MINIATURE, false) | ||
75 | : await createVideoMiniatureFromExisting(ASSETS_PATH.DEFAULT_LIVE_BACKGROUND, video, ThumbnailType.MINIATURE, true) | ||
76 | 70 | ||
77 | // Process preview or create it from the video | 71 | const [ thumbnailModel, previewModel ] = await buildVideoThumbnailsFromReq({ |
78 | const previewField = req.files ? req.files['previewfile'] : null | 72 | video, |
79 | const previewModel = previewField | 73 | files: req.files, |
80 | ? await createVideoMiniatureFromExisting(previewField[0].path, video, ThumbnailType.PREVIEW, false) | 74 | fallback: type => { |
81 | : await createVideoMiniatureFromExisting(ASSETS_PATH.DEFAULT_LIVE_BACKGROUND, video, ThumbnailType.PREVIEW, true) | 75 | return createVideoMiniatureFromExisting({ inputPath: ASSETS_PATH.DEFAULT_LIVE_BACKGROUND, video, type, automaticallyGenerated: true }) |
76 | } | ||
77 | }) | ||
82 | 78 | ||
83 | const { videoCreated } = await sequelizeTypescript.transaction(async t => { | 79 | const { videoCreated } = await sequelizeTypescript.transaction(async t => { |
84 | const sequelizeOptions = { transaction: t } | 80 | const sequelizeOptions = { transaction: t } |
@@ -94,13 +90,7 @@ async function addLiveVideo (req: express.Request, res: express.Response) { | |||
94 | videoLive.videoId = videoCreated.id | 90 | videoLive.videoId = videoCreated.id |
95 | await videoLive.save(sequelizeOptions) | 91 | await videoLive.save(sequelizeOptions) |
96 | 92 | ||
97 | // Create tags | 93 | await setVideoTags({ video, tags: videoInfo.tags, transaction: t }) |
98 | if (videoInfo.tags !== undefined) { | ||
99 | const tagInstances = await TagModel.findOrCreateTags(videoInfo.tags, t) | ||
100 | |||
101 | await video.$set('Tags', tagInstances, sequelizeOptions) | ||
102 | video.Tags = tagInstances | ||
103 | } | ||
104 | 94 | ||
105 | logger.info('Video live %s with uuid %s created.', videoInfo.name, videoCreated.uuid) | 95 | logger.info('Video live %s with uuid %s created.', videoInfo.name, videoCreated.uuid) |
106 | 96 | ||
diff --git a/server/lib/activitypub/videos.ts b/server/lib/activitypub/videos.ts index cbbf23be1..096884776 100644 --- a/server/lib/activitypub/videos.ts +++ b/server/lib/activitypub/videos.ts | |||
@@ -68,6 +68,7 @@ import { ActorFollowScoreCache } from '../files-cache' | |||
68 | import { JobQueue } from '../job-queue' | 68 | import { JobQueue } from '../job-queue' |
69 | import { Notifier } from '../notifier' | 69 | import { Notifier } from '../notifier' |
70 | import { createPlaceholderThumbnail, createVideoMiniatureFromUrl } from '../thumbnail' | 70 | import { createPlaceholderThumbnail, createVideoMiniatureFromUrl } from '../thumbnail' |
71 | import { setVideoTags } from '../video' | ||
71 | import { autoBlacklistVideoIfNeeded } from '../video-blacklist' | 72 | import { autoBlacklistVideoIfNeeded } from '../video-blacklist' |
72 | import { getOrCreateActorAndServerAndModel } from './actor' | 73 | import { getOrCreateActorAndServerAndModel } from './actor' |
73 | import { crawlCollectionPage } from './crawl' | 74 | import { crawlCollectionPage } from './crawl' |
@@ -409,8 +410,7 @@ async function updateVideoFromAP (options: { | |||
409 | const tags = videoObject.tag | 410 | const tags = videoObject.tag |
410 | .filter(isAPHashTagObject) | 411 | .filter(isAPHashTagObject) |
411 | .map(tag => tag.name) | 412 | .map(tag => tag.name) |
412 | const tagInstances = await TagModel.findOrCreateTags(tags, t) | 413 | await setVideoTags({ video: videoUpdated, tags, transaction: t, defaultValue: videoUpdated.Tags }) |
413 | await videoUpdated.$set('Tags', tagInstances, sequelizeOptions) | ||
414 | } | 414 | } |
415 | 415 | ||
416 | { | 416 | { |
@@ -594,8 +594,7 @@ async function createVideo (videoObject: VideoTorrentObject, channel: MChannelAc | |||
594 | const tags = videoObject.tag | 594 | const tags = videoObject.tag |
595 | .filter(isAPHashTagObject) | 595 | .filter(isAPHashTagObject) |
596 | .map(t => t.name) | 596 | .map(t => t.name) |
597 | const tagInstances = await TagModel.findOrCreateTags(tags, t) | 597 | await setVideoTags({ video: videoCreated, tags, transaction: t }) |
598 | await videoCreated.$set('Tags', tagInstances, sequelizeOptions) | ||
599 | 598 | ||
600 | // Process captions | 599 | // Process captions |
601 | const videoCaptionsPromises = videoObject.subtitleLanguage.map(c => { | 600 | const videoCaptionsPromises = videoObject.subtitleLanguage.map(c => { |
@@ -604,7 +603,6 @@ async function createVideo (videoObject: VideoTorrentObject, channel: MChannelAc | |||
604 | await Promise.all(videoCaptionsPromises) | 603 | await Promise.all(videoCaptionsPromises) |
605 | 604 | ||
606 | videoCreated.VideoFiles = videoFiles | 605 | videoCreated.VideoFiles = videoFiles |
607 | videoCreated.Tags = tagInstances | ||
608 | 606 | ||
609 | const autoBlacklisted = await autoBlacklistVideoIfNeeded({ | 607 | const autoBlacklisted = await autoBlacklistVideoIfNeeded({ |
610 | video: videoCreated, | 608 | video: videoCreated, |
diff --git a/server/lib/thumbnail.ts b/server/lib/thumbnail.ts index 78d2f69e3..dc86423f8 100644 --- a/server/lib/thumbnail.ts +++ b/server/lib/thumbnail.ts | |||
@@ -42,15 +42,18 @@ function createVideoMiniatureFromUrl (fileUrl: string, video: MVideoThumbnail, t | |||
42 | return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail, fileUrl }) | 42 | return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail, fileUrl }) |
43 | } | 43 | } |
44 | 44 | ||
45 | function createVideoMiniatureFromExisting ( | 45 | function createVideoMiniatureFromExisting (options: { |
46 | inputPath: string, | 46 | inputPath: string |
47 | video: MVideoThumbnail, | 47 | video: MVideoThumbnail |
48 | type: ThumbnailType, | 48 | type: ThumbnailType |
49 | automaticallyGenerated: boolean, | 49 | automaticallyGenerated: boolean |
50 | size?: ImageSize | 50 | size?: ImageSize |
51 | ) { | 51 | keepOriginal?: boolean |
52 | }) { | ||
53 | const { inputPath, video, type, automaticallyGenerated, size, keepOriginal } = options | ||
54 | |||
52 | const { filename, outputPath, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size) | 55 | const { filename, outputPath, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size) |
53 | const thumbnailCreator = () => processImage(inputPath, outputPath, { width, height }) | 56 | const thumbnailCreator = () => processImage(inputPath, outputPath, { width, height }, keepOriginal) |
54 | 57 | ||
55 | return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, automaticallyGenerated, existingThumbnail }) | 58 | return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, automaticallyGenerated, existingThumbnail }) |
56 | } | 59 | } |
diff --git a/server/lib/video.ts b/server/lib/video.ts index a28f31529..6df41e6cd 100644 --- a/server/lib/video.ts +++ b/server/lib/video.ts | |||
@@ -1,9 +1,12 @@ | |||
1 | 1 | import { Transaction } from 'sequelize/types' | |
2 | import { TagModel } from '@server/models/video/tag' | ||
2 | import { VideoModel } from '@server/models/video/video' | 3 | import { VideoModel } from '@server/models/video/video' |
3 | import { FilteredModelAttributes } from '@server/types' | 4 | import { FilteredModelAttributes } from '@server/types' |
4 | import { VideoCreate, VideoPrivacy, VideoState } from '@shared/models' | 5 | import { MTag, MThumbnail, MVideoTag, MVideoThumbnail } from '@server/types/models' |
6 | import { ThumbnailType, VideoCreate, VideoPrivacy } from '@shared/models' | ||
7 | import { createVideoMiniatureFromExisting } from './thumbnail' | ||
5 | 8 | ||
6 | function buildLocalVideoFromCreate (videoInfo: VideoCreate, channelId: number): FilteredModelAttributes<VideoModel> { | 9 | function buildLocalVideoFromReq (videoInfo: VideoCreate, channelId: number): FilteredModelAttributes<VideoModel> { |
7 | return { | 10 | return { |
8 | name: videoInfo.name, | 11 | name: videoInfo.name, |
9 | remote: false, | 12 | remote: false, |
@@ -13,19 +16,72 @@ function buildLocalVideoFromCreate (videoInfo: VideoCreate, channelId: number): | |||
13 | commentsEnabled: videoInfo.commentsEnabled !== false, // If the value is not "false", the default is "true" | 16 | commentsEnabled: videoInfo.commentsEnabled !== false, // If the value is not "false", the default is "true" |
14 | downloadEnabled: videoInfo.downloadEnabled !== false, | 17 | downloadEnabled: videoInfo.downloadEnabled !== false, |
15 | waitTranscoding: videoInfo.waitTranscoding || false, | 18 | waitTranscoding: videoInfo.waitTranscoding || false, |
16 | state: VideoState.WAITING_FOR_LIVE, | ||
17 | nsfw: videoInfo.nsfw || false, | 19 | nsfw: videoInfo.nsfw || false, |
18 | description: videoInfo.description, | 20 | description: videoInfo.description, |
19 | support: videoInfo.support, | 21 | support: videoInfo.support, |
20 | privacy: videoInfo.privacy || VideoPrivacy.PRIVATE, | 22 | privacy: videoInfo.privacy || VideoPrivacy.PRIVATE, |
21 | duration: 0, | ||
22 | channelId: channelId, | 23 | channelId: channelId, |
23 | originallyPublishedAt: videoInfo.originallyPublishedAt | 24 | originallyPublishedAt: videoInfo.originallyPublishedAt |
24 | } | 25 | } |
25 | } | 26 | } |
26 | 27 | ||
28 | async function buildVideoThumbnailsFromReq (options: { | ||
29 | video: MVideoThumbnail | ||
30 | files: { [fieldname: string]: Express.Multer.File[] } | Express.Multer.File[] | ||
31 | fallback: (type: ThumbnailType) => Promise<MThumbnail> | ||
32 | automaticallyGenerated?: boolean | ||
33 | }) { | ||
34 | const { video, files, fallback, automaticallyGenerated } = options | ||
35 | |||
36 | const promises = [ | ||
37 | { | ||
38 | type: ThumbnailType.MINIATURE, | ||
39 | fieldName: 'thumbnailfile' | ||
40 | }, | ||
41 | { | ||
42 | type: ThumbnailType.PREVIEW, | ||
43 | fieldName: 'previewfile' | ||
44 | } | ||
45 | ].map(p => { | ||
46 | const fields = files?.[p.fieldName] | ||
47 | |||
48 | if (fields) { | ||
49 | return createVideoMiniatureFromExisting({ | ||
50 | inputPath: fields[0].path, | ||
51 | video, | ||
52 | type: p.type, | ||
53 | automaticallyGenerated: automaticallyGenerated || false | ||
54 | }) | ||
55 | } | ||
56 | |||
57 | return fallback(p.type) | ||
58 | }) | ||
59 | |||
60 | return Promise.all(promises) | ||
61 | } | ||
62 | |||
63 | async function setVideoTags (options: { | ||
64 | video: MVideoTag | ||
65 | tags: string[] | ||
66 | transaction?: Transaction | ||
67 | defaultValue?: MTag[] | ||
68 | }) { | ||
69 | const { video, tags, transaction, defaultValue } = options | ||
70 | // Set tags to the video | ||
71 | if (tags) { | ||
72 | const tagInstances = await TagModel.findOrCreateTags(tags, transaction) | ||
73 | |||
74 | await video.$set('Tags', tagInstances, { transaction }) | ||
75 | video.Tags = tagInstances | ||
76 | } else { | ||
77 | video.Tags = defaultValue || [] | ||
78 | } | ||
79 | } | ||
80 | |||
27 | // --------------------------------------------------------------------------- | 81 | // --------------------------------------------------------------------------- |
28 | 82 | ||
29 | export { | 83 | export { |
30 | buildLocalVideoFromCreate | 84 | buildLocalVideoFromReq, |
85 | buildVideoThumbnailsFromReq, | ||
86 | setVideoTags | ||
31 | } | 87 | } |