diff options
author | Chocobozzz <me@florianbigard.com> | 2018-08-06 17:13:39 +0200 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2018-08-08 09:30:31 +0200 |
commit | ce33919c24e7402d92d81f3cd8e545df52d98240 (patch) | |
tree | 7e131a2f8df649899d0a71294665cf386ffb50d4 /server/controllers/api/videos | |
parent | 788487140c500abeb69ca44daf3a9e26efa8d36f (diff) | |
download | PeerTube-ce33919c24e7402d92d81f3cd8e545df52d98240.tar.gz PeerTube-ce33919c24e7402d92d81f3cd8e545df52d98240.tar.zst PeerTube-ce33919c24e7402d92d81f3cd8e545df52d98240.zip |
Import magnets with webtorrent
Diffstat (limited to 'server/controllers/api/videos')
-rw-r--r-- | server/controllers/api/videos/import.ts | 142 |
1 files changed, 105 insertions, 37 deletions
diff --git a/server/controllers/api/videos/import.ts b/server/controllers/api/videos/import.ts index 30a7d816c..c16a254d2 100644 --- a/server/controllers/api/videos/import.ts +++ b/server/controllers/api/videos/import.ts | |||
@@ -1,3 +1,4 @@ | |||
1 | import * as magnetUtil from 'magnet-uri' | ||
1 | import * as express from 'express' | 2 | import * as express from 'express' |
2 | import { auditLoggerFactory, VideoImportAuditView } from '../../../helpers/audit-logger' | 3 | import { auditLoggerFactory, VideoImportAuditView } from '../../../helpers/audit-logger' |
3 | import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoImportAddValidator } from '../../../middlewares' | 4 | import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoImportAddValidator } from '../../../middlewares' |
@@ -13,6 +14,10 @@ import { VideoImportModel } from '../../../models/video/video-import' | |||
13 | import { JobQueue } from '../../../lib/job-queue/job-queue' | 14 | import { JobQueue } from '../../../lib/job-queue/job-queue' |
14 | import { processImage } from '../../../helpers/image-utils' | 15 | import { processImage } from '../../../helpers/image-utils' |
15 | import { join } from 'path' | 16 | import { join } from 'path' |
17 | import { isArray } from '../../../helpers/custom-validators/misc' | ||
18 | import { FilteredModelAttributes } from 'sequelize-typescript/lib/models/Model' | ||
19 | import { VideoChannelModel } from '../../../models/video/video-channel' | ||
20 | import * as Bluebird from 'bluebird' | ||
16 | 21 | ||
17 | const auditLogger = auditLoggerFactory('video-imports') | 22 | const auditLogger = auditLoggerFactory('video-imports') |
18 | const videoImportsRouter = express.Router() | 23 | const videoImportsRouter = express.Router() |
@@ -41,7 +46,45 @@ export { | |||
41 | 46 | ||
42 | // --------------------------------------------------------------------------- | 47 | // --------------------------------------------------------------------------- |
43 | 48 | ||
44 | async function addVideoImport (req: express.Request, res: express.Response) { | 49 | function addVideoImport (req: express.Request, res: express.Response) { |
50 | if (req.body.targetUrl) return addYoutubeDLImport(req, res) | ||
51 | |||
52 | if (req.body.magnetUri) return addTorrentImport(req, res) | ||
53 | } | ||
54 | |||
55 | async function addTorrentImport (req: express.Request, res: express.Response) { | ||
56 | const body: VideoImportCreate = req.body | ||
57 | const magnetUri = body.magnetUri | ||
58 | |||
59 | const parsed = magnetUtil.decode(magnetUri) | ||
60 | const magnetName = isArray(parsed.name) ? parsed.name[0] : parsed.name as string | ||
61 | |||
62 | const video = buildVideo(res.locals.videoChannel.id, body, { name: magnetName }) | ||
63 | |||
64 | await processThumbnail(req, video) | ||
65 | await processPreview(req, video) | ||
66 | |||
67 | const tags = null | ||
68 | const videoImportAttributes = { | ||
69 | magnetUri, | ||
70 | state: VideoImportState.PENDING | ||
71 | } | ||
72 | const videoImport: VideoImportModel = await insertIntoDB(video, res.locals.videoChannel, tags, videoImportAttributes) | ||
73 | |||
74 | // Create job to import the video | ||
75 | const payload = { | ||
76 | type: 'magnet-uri' as 'magnet-uri', | ||
77 | videoImportId: videoImport.id, | ||
78 | magnetUri | ||
79 | } | ||
80 | await JobQueue.Instance.createJob({ type: 'video-import', payload }) | ||
81 | |||
82 | auditLogger.create(res.locals.oauth.token.User.Account.Actor.getIdentifier(), new VideoImportAuditView(videoImport.toFormattedJSON())) | ||
83 | |||
84 | return res.json(videoImport.toFormattedJSON()).end() | ||
85 | } | ||
86 | |||
87 | async function addYoutubeDLImport (req: express.Request, res: express.Response) { | ||
45 | const body: VideoImportCreate = req.body | 88 | const body: VideoImportCreate = req.body |
46 | const targetUrl = body.targetUrl | 89 | const targetUrl = body.targetUrl |
47 | 90 | ||
@@ -56,53 +99,94 @@ async function addVideoImport (req: express.Request, res: express.Response) { | |||
56 | }).end() | 99 | }).end() |
57 | } | 100 | } |
58 | 101 | ||
59 | // Create video DB object | 102 | const video = buildVideo(res.locals.videoChannel.id, body, youtubeDLInfo) |
103 | |||
104 | const downloadThumbnail = !await processThumbnail(req, video) | ||
105 | const downloadPreview = !await processPreview(req, video) | ||
106 | |||
107 | const tags = body.tags || youtubeDLInfo.tags | ||
108 | const videoImportAttributes = { | ||
109 | targetUrl, | ||
110 | state: VideoImportState.PENDING | ||
111 | } | ||
112 | const videoImport: VideoImportModel = await insertIntoDB(video, res.locals.videoChannel, tags, videoImportAttributes) | ||
113 | |||
114 | // Create job to import the video | ||
115 | const payload = { | ||
116 | type: 'youtube-dl' as 'youtube-dl', | ||
117 | videoImportId: videoImport.id, | ||
118 | thumbnailUrl: youtubeDLInfo.thumbnailUrl, | ||
119 | downloadThumbnail, | ||
120 | downloadPreview | ||
121 | } | ||
122 | await JobQueue.Instance.createJob({ type: 'video-import', payload }) | ||
123 | |||
124 | auditLogger.create(res.locals.oauth.token.User.Account.Actor.getIdentifier(), new VideoImportAuditView(videoImport.toFormattedJSON())) | ||
125 | |||
126 | return res.json(videoImport.toFormattedJSON()).end() | ||
127 | } | ||
128 | |||
129 | function buildVideo (channelId: number, body: VideoImportCreate, importData: YoutubeDLInfo) { | ||
60 | const videoData = { | 130 | const videoData = { |
61 | name: body.name || youtubeDLInfo.name, | 131 | name: body.name || importData.name || 'Unknown name', |
62 | remote: false, | 132 | remote: false, |
63 | category: body.category || youtubeDLInfo.category, | 133 | category: body.category || importData.category, |
64 | licence: body.licence || youtubeDLInfo.licence, | 134 | licence: body.licence || importData.licence, |
65 | language: body.language || undefined, | 135 | language: body.language || undefined, |
66 | commentsEnabled: body.commentsEnabled || true, | 136 | commentsEnabled: body.commentsEnabled || true, |
67 | waitTranscoding: body.waitTranscoding || false, | 137 | waitTranscoding: body.waitTranscoding || false, |
68 | state: VideoState.TO_IMPORT, | 138 | state: VideoState.TO_IMPORT, |
69 | nsfw: body.nsfw || youtubeDLInfo.nsfw || false, | 139 | nsfw: body.nsfw || importData.nsfw || false, |
70 | description: body.description || youtubeDLInfo.description, | 140 | description: body.description || importData.description, |
71 | support: body.support || null, | 141 | support: body.support || null, |
72 | privacy: body.privacy || VideoPrivacy.PRIVATE, | 142 | privacy: body.privacy || VideoPrivacy.PRIVATE, |
73 | duration: 0, // duration will be set by the import job | 143 | duration: 0, // duration will be set by the import job |
74 | channelId: res.locals.videoChannel.id | 144 | channelId: channelId |
75 | } | 145 | } |
76 | const video = new VideoModel(videoData) | 146 | const video = new VideoModel(videoData) |
77 | video.url = getVideoActivityPubUrl(video) | 147 | video.url = getVideoActivityPubUrl(video) |
78 | 148 | ||
79 | // Process thumbnail file? | 149 | return video |
150 | } | ||
151 | |||
152 | async function processThumbnail (req: express.Request, video: VideoModel) { | ||
80 | const thumbnailField = req.files ? req.files['thumbnailfile'] : undefined | 153 | const thumbnailField = req.files ? req.files['thumbnailfile'] : undefined |
81 | let downloadThumbnail = true | ||
82 | if (thumbnailField) { | 154 | if (thumbnailField) { |
83 | const thumbnailPhysicalFile = thumbnailField[ 0 ] | 155 | const thumbnailPhysicalFile = thumbnailField[ 0 ] |
84 | await processImage(thumbnailPhysicalFile, join(CONFIG.STORAGE.THUMBNAILS_DIR, video.getThumbnailName()), THUMBNAILS_SIZE) | 156 | await processImage(thumbnailPhysicalFile, join(CONFIG.STORAGE.THUMBNAILS_DIR, video.getThumbnailName()), THUMBNAILS_SIZE) |
85 | downloadThumbnail = false | 157 | |
158 | return true | ||
86 | } | 159 | } |
87 | 160 | ||
88 | // Process preview file? | 161 | return false |
162 | } | ||
163 | |||
164 | async function processPreview (req: express.Request, video: VideoModel) { | ||
89 | const previewField = req.files ? req.files['previewfile'] : undefined | 165 | const previewField = req.files ? req.files['previewfile'] : undefined |
90 | let downloadPreview = true | ||
91 | if (previewField) { | 166 | if (previewField) { |
92 | const previewPhysicalFile = previewField[0] | 167 | const previewPhysicalFile = previewField[0] |
93 | await processImage(previewPhysicalFile, join(CONFIG.STORAGE.PREVIEWS_DIR, video.getPreviewName()), PREVIEWS_SIZE) | 168 | await processImage(previewPhysicalFile, join(CONFIG.STORAGE.PREVIEWS_DIR, video.getPreviewName()), PREVIEWS_SIZE) |
94 | downloadPreview = false | 169 | |
170 | return true | ||
95 | } | 171 | } |
96 | 172 | ||
97 | const videoImport: VideoImportModel = await sequelizeTypescript.transaction(async t => { | 173 | return false |
174 | } | ||
175 | |||
176 | function insertIntoDB ( | ||
177 | video: VideoModel, | ||
178 | videoChannel: VideoChannelModel, | ||
179 | tags: string[], | ||
180 | videoImportAttributes: FilteredModelAttributes<VideoImportModel> | ||
181 | ): Bluebird<VideoImportModel> { | ||
182 | return sequelizeTypescript.transaction(async t => { | ||
98 | const sequelizeOptions = { transaction: t } | 183 | const sequelizeOptions = { transaction: t } |
99 | 184 | ||
100 | // Save video object in database | 185 | // Save video object in database |
101 | const videoCreated = await video.save(sequelizeOptions) | 186 | const videoCreated = await video.save(sequelizeOptions) |
102 | videoCreated.VideoChannel = res.locals.videoChannel | 187 | videoCreated.VideoChannel = videoChannel |
103 | 188 | ||
104 | // Set tags to the video | 189 | // Set tags to the video |
105 | const tags = body.tags ? body.tags : youtubeDLInfo.tags | ||
106 | if (tags !== undefined) { | 190 | if (tags !== undefined) { |
107 | const tagInstances = await TagModel.findOrCreateTags(tags, t) | 191 | const tagInstances = await TagModel.findOrCreateTags(tags, t) |
108 | 192 | ||
@@ -111,28 +195,12 @@ async function addVideoImport (req: express.Request, res: express.Response) { | |||
111 | } | 195 | } |
112 | 196 | ||
113 | // Create video import object in database | 197 | // Create video import object in database |
114 | const videoImport = await VideoImportModel.create({ | 198 | const videoImport = await VideoImportModel.create( |
115 | targetUrl, | 199 | Object.assign({ videoId: videoCreated.id }, videoImportAttributes), |
116 | state: VideoImportState.PENDING, | 200 | sequelizeOptions |
117 | videoId: videoCreated.id | 201 | ) |
118 | }, sequelizeOptions) | ||
119 | |||
120 | videoImport.Video = videoCreated | 202 | videoImport.Video = videoCreated |
121 | 203 | ||
122 | return videoImport | 204 | return videoImport |
123 | }) | 205 | }) |
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 | } | 206 | } |