]>
Commit | Line | Data |
---|---|---|
1 | import * as express from 'express' | |
2 | import { auditLoggerFactory, VideoImportAuditView } from '../../../helpers/audit-logger' | |
3 | import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoImportAddValidator } from '../../../middlewares' | |
4 | import { CONFIG, IMAGE_MIMETYPE_EXT, PREVIEWS_SIZE, sequelizeTypescript, THUMBNAILS_SIZE } from '../../../initializers' | |
5 | import { getYoutubeDLInfo, YoutubeDLInfo } from '../../../helpers/youtube-dl' | |
6 | import { createReqFiles } from '../../../helpers/express-utils' | |
7 | import { logger } from '../../../helpers/logger' | |
8 | import { VideoImportCreate, VideoImportState, VideoPrivacy, VideoState } from '../../../../shared' | |
9 | import { VideoModel } from '../../../models/video/video' | |
10 | import { getVideoActivityPubUrl } from '../../../lib/activitypub' | |
11 | import { TagModel } from '../../../models/video/tag' | |
12 | import { VideoImportModel } from '../../../models/video/video-import' | |
13 | import { JobQueue } from '../../../lib/job-queue/job-queue' | |
14 | import { processImage } from '../../../helpers/image-utils' | |
15 | import { join } from 'path' | |
16 | ||
17 | const auditLogger = auditLoggerFactory('video-imports') | |
18 | const videoImportsRouter = express.Router() | |
19 | ||
20 | const reqVideoFileImport = createReqFiles( | |
21 | [ 'thumbnailfile', 'previewfile' ], | |
22 | IMAGE_MIMETYPE_EXT, | |
23 | { | |
24 | thumbnailfile: CONFIG.STORAGE.THUMBNAILS_DIR, | |
25 | previewfile: CONFIG.STORAGE.PREVIEWS_DIR | |
26 | } | |
27 | ) | |
28 | ||
29 | videoImportsRouter.post('/imports', | |
30 | authenticate, | |
31 | reqVideoFileImport, | |
32 | asyncMiddleware(videoImportAddValidator), | |
33 | asyncRetryTransactionMiddleware(addVideoImport) | |
34 | ) | |
35 | ||
36 | // --------------------------------------------------------------------------- | |
37 | ||
38 | export { | |
39 | videoImportsRouter | |
40 | } | |
41 | ||
42 | // --------------------------------------------------------------------------- | |
43 | ||
44 | async function addVideoImport (req: express.Request, res: express.Response) { | |
45 | const body: VideoImportCreate = req.body | |
46 | const targetUrl = body.targetUrl | |
47 | ||
48 | let youtubeDLInfo: YoutubeDLInfo | |
49 | try { | |
50 | youtubeDLInfo = await getYoutubeDLInfo(targetUrl) | |
51 | } catch (err) { | |
52 | logger.info('Cannot fetch information from import for URL %s.', targetUrl, { err }) | |
53 | ||
54 | return res.status(400).json({ | |
55 | error: 'Cannot fetch remote information of this URL.' | |
56 | }).end() | |
57 | } | |
58 | ||
59 | // Create video DB object | |
60 | const videoData = { | |
61 | name: body.name || youtubeDLInfo.name, | |
62 | remote: false, | |
63 | category: body.category || youtubeDLInfo.category, | |
64 | licence: body.licence || youtubeDLInfo.licence, | |
65 | language: body.language || undefined, | |
66 | commentsEnabled: body.commentsEnabled || true, | |
67 | waitTranscoding: body.waitTranscoding || false, | |
68 | state: VideoState.TO_IMPORT, | |
69 | nsfw: body.nsfw || youtubeDLInfo.nsfw || false, | |
70 | description: body.description || youtubeDLInfo.description, | |
71 | support: body.support || null, | |
72 | privacy: body.privacy || VideoPrivacy.PRIVATE, | |
73 | duration: 0, // duration will be set by the import job | |
74 | channelId: res.locals.videoChannel.id | |
75 | } | |
76 | const video = new VideoModel(videoData) | |
77 | video.url = getVideoActivityPubUrl(video) | |
78 | ||
79 | // Process thumbnail file? | |
80 | const thumbnailField = req.files ? req.files['thumbnailfile'] : undefined | |
81 | let downloadThumbnail = true | |
82 | if (thumbnailField) { | |
83 | const thumbnailPhysicalFile = thumbnailField[ 0 ] | |
84 | await processImage(thumbnailPhysicalFile, join(CONFIG.STORAGE.THUMBNAILS_DIR, video.getThumbnailName()), THUMBNAILS_SIZE) | |
85 | downloadThumbnail = false | |
86 | } | |
87 | ||
88 | // Process preview file? | |
89 | const previewField = req.files ? req.files['previewfile'] : undefined | |
90 | let downloadPreview = true | |
91 | if (previewField) { | |
92 | const previewPhysicalFile = previewField[0] | |
93 | await processImage(previewPhysicalFile, join(CONFIG.STORAGE.PREVIEWS_DIR, video.getPreviewName()), PREVIEWS_SIZE) | |
94 | downloadPreview = false | |
95 | } | |
96 | ||
97 | const videoImport: VideoImportModel = await sequelizeTypescript.transaction(async t => { | |
98 | const sequelizeOptions = { transaction: t } | |
99 | ||
100 | // Save video object in database | |
101 | const videoCreated = await video.save(sequelizeOptions) | |
102 | videoCreated.VideoChannel = res.locals.videoChannel | |
103 | ||
104 | // Set tags to the video | |
105 | const tags = body.tags ? body.tags : youtubeDLInfo.tags | |
106 | if (tags !== undefined) { | |
107 | const tagInstances = await TagModel.findOrCreateTags(tags, t) | |
108 | ||
109 | await videoCreated.$set('Tags', tagInstances, sequelizeOptions) | |
110 | videoCreated.Tags = tagInstances | |
111 | } | |
112 | ||
113 | // Create video import object in database | |
114 | const videoImport = await VideoImportModel.create({ | |
115 | targetUrl, | |
116 | state: VideoImportState.PENDING, | |
117 | videoId: videoCreated.id | |
118 | }, sequelizeOptions) | |
119 | ||
120 | videoImport.Video = videoCreated | |
121 | ||
122 | return videoImport | |
123 | }) | |
124 | ||
125 | // Create job to import the video | |
126 | const payload = { | |
127 | type: 'youtube-dl' as 'youtube-dl', | |
128 | videoImportId: videoImport.id, | |
129 | thumbnailUrl: youtubeDLInfo.thumbnailUrl, | |
130 | downloadThumbnail, | |
131 | downloadPreview | |
132 | } | |
133 | await JobQueue.Instance.createJob({ type: 'video-import', payload }) | |
134 | ||
135 | auditLogger.create(res.locals.oauth.token.User.Account.Actor.getIdentifier(), new VideoImportAuditView(videoImport.toFormattedJSON())) | |
136 | ||
137 | return res.json(videoImport.toFormattedJSON()).end() | |
138 | } |