diff options
author | Chocobozzz <me@florianbigard.com> | 2018-08-02 15:34:09 +0200 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2018-08-06 11:19:16 +0200 |
commit | fbad87b0472f574409f7aa3ae7f8b54927d0cdd6 (patch) | |
tree | 197b4209e75d57dabae7cdd6f2da5f765e427023 /server/controllers/api/videos | |
parent | 5e319fb7898fd0482c399cc3ae9dcfc20d274a58 (diff) | |
download | PeerTube-fbad87b0472f574409f7aa3ae7f8b54927d0cdd6.tar.gz PeerTube-fbad87b0472f574409f7aa3ae7f8b54927d0cdd6.tar.zst PeerTube-fbad87b0472f574409f7aa3ae7f8b54927d0cdd6.zip |
Add ability to import video with youtube-dl
Diffstat (limited to 'server/controllers/api/videos')
-rw-r--r-- | server/controllers/api/videos/import.ts | 151 | ||||
-rw-r--r-- | server/controllers/api/videos/index.ts | 3 |
2 files changed, 153 insertions, 1 deletions
diff --git a/server/controllers/api/videos/import.ts b/server/controllers/api/videos/import.ts new file mode 100644 index 000000000..9761cdbcf --- /dev/null +++ b/server/controllers/api/videos/import.ts | |||
@@ -0,0 +1,151 @@ | |||
1 | import * as express from 'express' | ||
2 | import { auditLoggerFactory } from '../../../helpers/audit-logger' | ||
3 | import { | ||
4 | asyncMiddleware, | ||
5 | asyncRetryTransactionMiddleware, | ||
6 | authenticate, | ||
7 | videoImportAddValidator, | ||
8 | videoImportDeleteValidator | ||
9 | } from '../../../middlewares' | ||
10 | import { CONFIG, IMAGE_MIMETYPE_EXT, PREVIEWS_SIZE, sequelizeTypescript, THUMBNAILS_SIZE } from '../../../initializers' | ||
11 | import { getYoutubeDLInfo, YoutubeDLInfo } from '../../../helpers/youtube-dl' | ||
12 | import { createReqFiles } from '../../../helpers/express-utils' | ||
13 | import { logger } from '../../../helpers/logger' | ||
14 | import { VideoImportCreate, VideoImportState, VideoPrivacy, VideoState } from '../../../../shared' | ||
15 | import { VideoModel } from '../../../models/video/video' | ||
16 | import { getVideoActivityPubUrl } from '../../../lib/activitypub' | ||
17 | import { TagModel } from '../../../models/video/tag' | ||
18 | import { VideoImportModel } from '../../../models/video/video-import' | ||
19 | import { JobQueue } from '../../../lib/job-queue/job-queue' | ||
20 | import { processImage } from '../../../helpers/image-utils' | ||
21 | import { join } from 'path' | ||
22 | |||
23 | const auditLogger = auditLoggerFactory('video-imports') | ||
24 | const videoImportsRouter = express.Router() | ||
25 | |||
26 | const reqVideoFileImport = createReqFiles( | ||
27 | [ 'thumbnailfile', 'previewfile' ], | ||
28 | IMAGE_MIMETYPE_EXT, | ||
29 | { | ||
30 | thumbnailfile: CONFIG.STORAGE.THUMBNAILS_DIR, | ||
31 | previewfile: CONFIG.STORAGE.PREVIEWS_DIR | ||
32 | } | ||
33 | ) | ||
34 | |||
35 | videoImportsRouter.post('/imports', | ||
36 | authenticate, | ||
37 | reqVideoFileImport, | ||
38 | asyncMiddleware(videoImportAddValidator), | ||
39 | asyncRetryTransactionMiddleware(addVideoImport) | ||
40 | ) | ||
41 | |||
42 | videoImportsRouter.delete('/imports/:id', | ||
43 | authenticate, | ||
44 | videoImportDeleteValidator, | ||
45 | asyncRetryTransactionMiddleware(deleteVideoImport) | ||
46 | ) | ||
47 | |||
48 | // --------------------------------------------------------------------------- | ||
49 | |||
50 | export { | ||
51 | videoImportsRouter | ||
52 | } | ||
53 | |||
54 | // --------------------------------------------------------------------------- | ||
55 | |||
56 | async function addVideoImport (req: express.Request, res: express.Response) { | ||
57 | const body: VideoImportCreate = req.body | ||
58 | const targetUrl = body.targetUrl | ||
59 | |||
60 | let youtubeDLInfo: YoutubeDLInfo | ||
61 | try { | ||
62 | youtubeDLInfo = await getYoutubeDLInfo(targetUrl) | ||
63 | } catch (err) { | ||
64 | logger.info('Cannot fetch information from import for URL %s.', targetUrl, { err }) | ||
65 | |||
66 | return res.status(400).json({ | ||
67 | error: 'Cannot fetch remote information of this URL.' | ||
68 | }).end() | ||
69 | } | ||
70 | |||
71 | // Create video DB object | ||
72 | const videoData = { | ||
73 | name: body.name || youtubeDLInfo.name, | ||
74 | remote: false, | ||
75 | category: body.category || youtubeDLInfo.category, | ||
76 | licence: body.licence || youtubeDLInfo.licence, | ||
77 | language: undefined, | ||
78 | commentsEnabled: body.commentsEnabled || true, | ||
79 | waitTranscoding: body.waitTranscoding || false, | ||
80 | state: VideoState.TO_IMPORT, | ||
81 | nsfw: body.nsfw || youtubeDLInfo.nsfw || false, | ||
82 | description: body.description || youtubeDLInfo.description, | ||
83 | support: body.support || null, | ||
84 | privacy: body.privacy || VideoPrivacy.PRIVATE, | ||
85 | duration: 0, // duration will be set by the import job | ||
86 | channelId: res.locals.videoChannel.id | ||
87 | } | ||
88 | const video = new VideoModel(videoData) | ||
89 | video.url = getVideoActivityPubUrl(video) | ||
90 | |||
91 | // Process thumbnail file? | ||
92 | const thumbnailField = req.files['thumbnailfile'] | ||
93 | let downloadThumbnail = true | ||
94 | if (thumbnailField) { | ||
95 | const thumbnailPhysicalFile = thumbnailField[ 0 ] | ||
96 | await processImage(thumbnailPhysicalFile, join(CONFIG.STORAGE.THUMBNAILS_DIR, video.getThumbnailName()), THUMBNAILS_SIZE) | ||
97 | downloadThumbnail = false | ||
98 | } | ||
99 | |||
100 | // Process preview file? | ||
101 | const previewField = req.files['previewfile'] | ||
102 | let downloadPreview = true | ||
103 | if (previewField) { | ||
104 | const previewPhysicalFile = previewField[0] | ||
105 | await processImage(previewPhysicalFile, join(CONFIG.STORAGE.PREVIEWS_DIR, video.getPreviewName()), PREVIEWS_SIZE) | ||
106 | downloadPreview = false | ||
107 | } | ||
108 | |||
109 | const videoImport: VideoImportModel = await sequelizeTypescript.transaction(async t => { | ||
110 | const sequelizeOptions = { transaction: t } | ||
111 | |||
112 | // Save video object in database | ||
113 | const videoCreated = await video.save(sequelizeOptions) | ||
114 | videoCreated.VideoChannel = res.locals.videoChannel | ||
115 | |||
116 | // Set tags to the video | ||
117 | if (youtubeDLInfo.tags !== undefined) { | ||
118 | const tagInstances = await TagModel.findOrCreateTags(youtubeDLInfo.tags, t) | ||
119 | |||
120 | await videoCreated.$set('Tags', tagInstances, sequelizeOptions) | ||
121 | videoCreated.Tags = tagInstances | ||
122 | } | ||
123 | |||
124 | // Create video import object in database | ||
125 | const videoImport = await VideoImportModel.create({ | ||
126 | targetUrl, | ||
127 | state: VideoImportState.PENDING, | ||
128 | videoId: videoCreated.id | ||
129 | }, sequelizeOptions) | ||
130 | |||
131 | videoImport.Video = videoCreated | ||
132 | |||
133 | return videoImport | ||
134 | }) | ||
135 | |||
136 | // Create job to import the video | ||
137 | const payload = { | ||
138 | type: 'youtube-dl' as 'youtube-dl', | ||
139 | videoImportId: videoImport.id, | ||
140 | thumbnailUrl: youtubeDLInfo.thumbnailUrl, | ||
141 | downloadThumbnail, | ||
142 | downloadPreview | ||
143 | } | ||
144 | await JobQueue.Instance.createJob({ type: 'video-import', payload }) | ||
145 | |||
146 | return res.json(videoImport.toFormattedJSON()) | ||
147 | } | ||
148 | |||
149 | async function deleteVideoImport (req: express.Request, res: express.Response) { | ||
150 | // TODO: delete video import | ||
151 | } | ||
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts index e396ee6be..c9365da08 100644 --- a/server/controllers/api/videos/index.ts +++ b/server/controllers/api/videos/index.ts | |||
@@ -54,6 +54,7 @@ import { VideoFilter } from '../../../../shared/models/videos/video-query.type' | |||
54 | import { createReqFiles, buildNSFWFilter } from '../../../helpers/express-utils' | 54 | import { createReqFiles, buildNSFWFilter } from '../../../helpers/express-utils' |
55 | import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update' | 55 | import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update' |
56 | import { videoCaptionsRouter } from './captions' | 56 | import { videoCaptionsRouter } from './captions' |
57 | import { videoImportsRouter } from './import' | ||
57 | 58 | ||
58 | const auditLogger = auditLoggerFactory('videos') | 59 | const auditLogger = auditLoggerFactory('videos') |
59 | const videosRouter = express.Router() | 60 | const videosRouter = express.Router() |
@@ -81,6 +82,7 @@ videosRouter.use('/', blacklistRouter) | |||
81 | videosRouter.use('/', rateVideoRouter) | 82 | videosRouter.use('/', rateVideoRouter) |
82 | videosRouter.use('/', videoCommentRouter) | 83 | videosRouter.use('/', videoCommentRouter) |
83 | videosRouter.use('/', videoCaptionsRouter) | 84 | videosRouter.use('/', videoCaptionsRouter) |
85 | videosRouter.use('/', videoImportsRouter) | ||
84 | 86 | ||
85 | videosRouter.get('/categories', listVideoCategories) | 87 | videosRouter.get('/categories', listVideoCategories) |
86 | videosRouter.get('/licences', listVideoLicences) | 88 | videosRouter.get('/licences', listVideoLicences) |
@@ -160,7 +162,6 @@ async function addVideo (req: express.Request, res: express.Response) { | |||
160 | const videoData = { | 162 | const videoData = { |
161 | name: videoInfo.name, | 163 | name: videoInfo.name, |
162 | remote: false, | 164 | remote: false, |
163 | extname: extname(videoPhysicalFile.filename), | ||
164 | category: videoInfo.category, | 165 | category: videoInfo.category, |
165 | licence: videoInfo.licence, | 166 | licence: videoInfo.licence, |
166 | language: videoInfo.language, | 167 | language: videoInfo.language, |