aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/controllers/api/videos/import.ts
diff options
context:
space:
mode:
Diffstat (limited to 'server/controllers/api/videos/import.ts')
-rw-r--r--server/controllers/api/videos/import.ts262
1 files changed, 0 insertions, 262 deletions
diff --git a/server/controllers/api/videos/import.ts b/server/controllers/api/videos/import.ts
deleted file mode 100644
index defe9efd4..000000000
--- a/server/controllers/api/videos/import.ts
+++ /dev/null
@@ -1,262 +0,0 @@
1import express from 'express'
2import { move, readFile } from 'fs-extra'
3import { decode } from 'magnet-uri'
4import parseTorrent, { Instance } from 'parse-torrent'
5import { join } from 'path'
6import { buildYoutubeDLImport, buildVideoFromImport, insertFromImportIntoDB, YoutubeDlImportError } from '@server/lib/video-pre-import'
7import { MThumbnail, MVideoThumbnail } from '@server/types/models'
8import { HttpStatusCode, ServerErrorCode, ThumbnailType, VideoImportCreate, VideoImportPayload, VideoImportState } from '@shared/models'
9import { auditLoggerFactory, getAuditIdFromRes, VideoImportAuditView } from '../../../helpers/audit-logger'
10import { isArray } from '../../../helpers/custom-validators/misc'
11import { cleanUpReqFiles, createReqFiles } from '../../../helpers/express-utils'
12import { logger } from '../../../helpers/logger'
13import { getSecureTorrentName } from '../../../helpers/utils'
14import { CONFIG } from '../../../initializers/config'
15import { MIMETYPES } from '../../../initializers/constants'
16import { JobQueue } from '../../../lib/job-queue/job-queue'
17import { updateLocalVideoMiniatureFromExisting } from '../../../lib/thumbnail'
18import {
19 asyncMiddleware,
20 asyncRetryTransactionMiddleware,
21 authenticate,
22 videoImportAddValidator,
23 videoImportCancelValidator,
24 videoImportDeleteValidator
25} from '../../../middlewares'
26
27const auditLogger = auditLoggerFactory('video-imports')
28const videoImportsRouter = express.Router()
29
30const reqVideoFileImport = createReqFiles(
31 [ 'thumbnailfile', 'previewfile', 'torrentfile' ],
32 { ...MIMETYPES.TORRENT.MIMETYPE_EXT, ...MIMETYPES.IMAGE.MIMETYPE_EXT }
33)
34
35videoImportsRouter.post('/imports',
36 authenticate,
37 reqVideoFileImport,
38 asyncMiddleware(videoImportAddValidator),
39 asyncRetryTransactionMiddleware(handleVideoImport)
40)
41
42videoImportsRouter.post('/imports/:id/cancel',
43 authenticate,
44 asyncMiddleware(videoImportCancelValidator),
45 asyncRetryTransactionMiddleware(cancelVideoImport)
46)
47
48videoImportsRouter.delete('/imports/:id',
49 authenticate,
50 asyncMiddleware(videoImportDeleteValidator),
51 asyncRetryTransactionMiddleware(deleteVideoImport)
52)
53
54// ---------------------------------------------------------------------------
55
56export {
57 videoImportsRouter
58}
59
60// ---------------------------------------------------------------------------
61
62async function deleteVideoImport (req: express.Request, res: express.Response) {
63 const videoImport = res.locals.videoImport
64
65 await videoImport.destroy()
66
67 return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
68}
69
70async function cancelVideoImport (req: express.Request, res: express.Response) {
71 const videoImport = res.locals.videoImport
72
73 videoImport.state = VideoImportState.CANCELLED
74 await videoImport.save()
75
76 return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
77}
78
79function handleVideoImport (req: express.Request, res: express.Response) {
80 if (req.body.targetUrl) return handleYoutubeDlImport(req, res)
81
82 const file = req.files?.['torrentfile']?.[0]
83 if (req.body.magnetUri || file) return handleTorrentImport(req, res, file)
84}
85
86async function handleTorrentImport (req: express.Request, res: express.Response, torrentfile: Express.Multer.File) {
87 const body: VideoImportCreate = req.body
88 const user = res.locals.oauth.token.User
89
90 let videoName: string
91 let torrentName: string
92 let magnetUri: string
93
94 if (torrentfile) {
95 const result = await processTorrentOrAbortRequest(req, res, torrentfile)
96 if (!result) return
97
98 videoName = result.name
99 torrentName = result.torrentName
100 } else {
101 const result = processMagnetURI(body)
102 magnetUri = result.magnetUri
103 videoName = result.name
104 }
105
106 const video = await buildVideoFromImport({
107 channelId: res.locals.videoChannel.id,
108 importData: { name: videoName },
109 importDataOverride: body,
110 importType: 'torrent'
111 })
112
113 const thumbnailModel = await processThumbnail(req, video)
114 const previewModel = await processPreview(req, video)
115
116 const videoImport = await insertFromImportIntoDB({
117 video,
118 thumbnailModel,
119 previewModel,
120 videoChannel: res.locals.videoChannel,
121 tags: body.tags || undefined,
122 user,
123 videoPasswords: body.videoPasswords,
124 videoImportAttributes: {
125 magnetUri,
126 torrentName,
127 state: VideoImportState.PENDING,
128 userId: user.id
129 }
130 })
131
132 const payload: VideoImportPayload = {
133 type: torrentfile
134 ? 'torrent-file'
135 : 'magnet-uri',
136 videoImportId: videoImport.id,
137 preventException: false
138 }
139 await JobQueue.Instance.createJob({ type: 'video-import', payload })
140
141 auditLogger.create(getAuditIdFromRes(res), new VideoImportAuditView(videoImport.toFormattedJSON()))
142
143 return res.json(videoImport.toFormattedJSON()).end()
144}
145
146function statusFromYtDlImportError (err: YoutubeDlImportError): number {
147 switch (err.code) {
148 case YoutubeDlImportError.CODE.NOT_ONLY_UNICAST_URL:
149 return HttpStatusCode.FORBIDDEN_403
150
151 case YoutubeDlImportError.CODE.FETCH_ERROR:
152 return HttpStatusCode.BAD_REQUEST_400
153
154 default:
155 return HttpStatusCode.INTERNAL_SERVER_ERROR_500
156 }
157}
158
159async function handleYoutubeDlImport (req: express.Request, res: express.Response) {
160 const body: VideoImportCreate = req.body
161 const targetUrl = body.targetUrl
162 const user = res.locals.oauth.token.User
163
164 try {
165 const { job, videoImport } = await buildYoutubeDLImport({
166 targetUrl,
167 channel: res.locals.videoChannel,
168 importDataOverride: body,
169 thumbnailFilePath: req.files?.['thumbnailfile']?.[0].path,
170 previewFilePath: req.files?.['previewfile']?.[0].path,
171 user
172 })
173 await JobQueue.Instance.createJob(job)
174
175 auditLogger.create(getAuditIdFromRes(res), new VideoImportAuditView(videoImport.toFormattedJSON()))
176
177 return res.json(videoImport.toFormattedJSON()).end()
178 } catch (err) {
179 logger.error('An error occurred while importing the video %s. ', targetUrl, { err })
180
181 return res.fail({
182 message: err.message,
183 status: statusFromYtDlImportError(err),
184 data: {
185 targetUrl
186 }
187 })
188 }
189}
190
191async function processThumbnail (req: express.Request, video: MVideoThumbnail) {
192 const thumbnailField = req.files ? req.files['thumbnailfile'] : undefined
193 if (thumbnailField) {
194 const thumbnailPhysicalFile = thumbnailField[0]
195
196 return updateLocalVideoMiniatureFromExisting({
197 inputPath: thumbnailPhysicalFile.path,
198 video,
199 type: ThumbnailType.MINIATURE,
200 automaticallyGenerated: false
201 })
202 }
203
204 return undefined
205}
206
207async function processPreview (req: express.Request, video: MVideoThumbnail): Promise<MThumbnail> {
208 const previewField = req.files ? req.files['previewfile'] : undefined
209 if (previewField) {
210 const previewPhysicalFile = previewField[0]
211
212 return updateLocalVideoMiniatureFromExisting({
213 inputPath: previewPhysicalFile.path,
214 video,
215 type: ThumbnailType.PREVIEW,
216 automaticallyGenerated: false
217 })
218 }
219
220 return undefined
221}
222
223async function processTorrentOrAbortRequest (req: express.Request, res: express.Response, torrentfile: Express.Multer.File) {
224 const torrentName = torrentfile.originalname
225
226 // Rename the torrent to a secured name
227 const newTorrentPath = join(CONFIG.STORAGE.TORRENTS_DIR, getSecureTorrentName(torrentName))
228 await move(torrentfile.path, newTorrentPath, { overwrite: true })
229 torrentfile.path = newTorrentPath
230
231 const buf = await readFile(torrentfile.path)
232 const parsedTorrent = parseTorrent(buf) as Instance
233
234 if (parsedTorrent.files.length !== 1) {
235 cleanUpReqFiles(req)
236
237 res.fail({
238 type: ServerErrorCode.INCORRECT_FILES_IN_TORRENT,
239 message: 'Torrents with only 1 file are supported.'
240 })
241 return undefined
242 }
243
244 return {
245 name: extractNameFromArray(parsedTorrent.name),
246 torrentName
247 }
248}
249
250function processMagnetURI (body: VideoImportCreate) {
251 const magnetUri = body.magnetUri
252 const parsed = decode(magnetUri)
253
254 return {
255 name: extractNameFromArray(parsed.name),
256 magnetUri
257 }
258}
259
260function extractNameFromArray (name: string | string[]) {
261 return isArray(name) ? name[0] : name
262}