]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/controllers/api/v1/videos.js
Update readme roadmap
[github/Chocobozzz/PeerTube.git] / server / controllers / api / v1 / videos.js
CommitLineData
9f10b292
C
1'use strict'
2
807df9e6 3const async = require('async')
f0f5567b 4const config = require('config')
f0f5567b 5const express = require('express')
cbe2f7c3
C
6const fs = require('fs')
7const path = require('path')
f0f5567b
C
8const multer = require('multer')
9
cbe2f7c3 10const constants = require('../../../initializers/constants')
f0f5567b
C
11const logger = require('../../../helpers/logger')
12const friends = require('../../../lib/friends')
b3b92647
C
13const middlewares = require('../../../middlewares')
14const oAuth2 = middlewares.oauth2
fbf1134e
C
15const pagination = middlewares.pagination
16const reqValidator = middlewares.reqValidators
17const reqValidatorPagination = reqValidator.pagination
a877d5ac 18const reqValidatorSort = reqValidator.sort
fbf1134e 19const reqValidatorVideos = reqValidator.videos
46246b5f 20const search = middlewares.search
a877d5ac 21const sort = middlewares.sort
cbe2f7c3 22const utils = require('../../../helpers/utils')
f0f5567b
C
23const Videos = require('../../../models/videos') // model
24const videos = require('../../../lib/videos')
25const webtorrent = require('../../../lib/webtorrent')
26
27const router = express.Router()
28const uploads = config.get('storage.uploads')
9f10b292
C
29
30// multer configuration
f0f5567b 31const storage = multer.diskStorage({
9f10b292
C
32 destination: function (req, file, cb) {
33 cb(null, uploads)
34 },
35
36 filename: function (req, file, cb) {
f0f5567b 37 let extension = ''
9f10b292
C
38 if (file.mimetype === 'video/webm') extension = 'webm'
39 else if (file.mimetype === 'video/mp4') extension = 'mp4'
40 else if (file.mimetype === 'video/ogg') extension = 'ogv'
bc503c2a
C
41 utils.generateRandomString(16, function (err, randomString) {
42 const fieldname = err ? undefined : randomString
9f10b292
C
43 cb(null, fieldname + '.' + extension)
44 })
45 }
46})
47
8c9c1942 48const reqFiles = multer({ storage: storage }).fields([{ name: 'videofile', maxCount: 1 }])
cbe2f7c3 49const thumbnailsDir = path.join(__dirname, '..', '..', '..', '..', config.get('storage.thumbnails'))
8c308c2b 50
fbf1134e
C
51router.get('/',
52 reqValidatorPagination.pagination,
a877d5ac
C
53 reqValidatorSort.videosSort,
54 sort.setVideosSort,
fbf1134e
C
55 pagination.setPagination,
56 listVideos
57)
58router.post('/',
59 oAuth2.authenticate,
60 reqFiles,
61 reqValidatorVideos.videosAdd,
62 addVideo
63)
64router.get('/:id',
65 reqValidatorVideos.videosGet,
68ce3ae0 66 getVideo
fbf1134e
C
67)
68router.delete('/:id',
69 oAuth2.authenticate,
70 reqValidatorVideos.videosRemove,
71 removeVideo
72)
46246b5f 73router.get('/search/:value',
fbf1134e
C
74 reqValidatorVideos.videosSearch,
75 reqValidatorPagination.pagination,
a877d5ac
C
76 reqValidatorSort.videosSort,
77 sort.setVideosSort,
fbf1134e 78 pagination.setPagination,
46246b5f 79 search.setVideosSearch,
fbf1134e
C
80 searchVideos
81)
8c308c2b 82
9f10b292 83// ---------------------------------------------------------------------------
c45f7f84 84
9f10b292 85module.exports = router
c45f7f84 86
9f10b292 87// ---------------------------------------------------------------------------
c45f7f84 88
9f10b292 89function addVideo (req, res, next) {
bc503c2a
C
90 const videoFile = req.files.videofile[0]
91 const videoInfos = req.body
9f10b292 92
807df9e6 93 async.waterfall([
67100f1f 94 function seedTheVideo (callback) {
807df9e6
C
95 videos.seed(videoFile.path, callback)
96 },
8c308c2b 97
67100f1f 98 function createThumbnail (torrent, callback) {
57a56079 99 videos.createVideoThumbnail(videoFile.path, function (err, thumbnailName) {
3a8a8b51 100 if (err) {
cbe2f7c3
C
101 // TODO: unseed the video
102 logger.error('Cannot make a thumbnail of the video file.')
807df9e6 103 return callback(err)
3a8a8b51
C
104 }
105
67100f1f 106 callback(null, torrent, thumbnailName)
807df9e6
C
107 })
108 },
109
67100f1f 110 function insertIntoDB (torrent, thumbnailName, callback) {
807df9e6
C
111 const videoData = {
112 name: videoInfos.name,
113 namePath: videoFile.filename,
114 description: videoInfos.description,
115 magnetUri: torrent.magnetURI,
116 author: res.locals.oauth.token.user.username,
67100f1f 117 duration: videoFile.duration,
be587647
C
118 thumbnail: thumbnailName,
119 tags: videoInfos.tags
807df9e6
C
120 }
121
122 Videos.add(videoData, function (err, insertedVideo) {
123 if (err) {
124 // TODO unseed the video
125 // TODO remove thumbnail
126 logger.error('Cannot insert this video in the database.')
127 return callback(err)
cbe2f7c3 128 }
3a8a8b51 129
67100f1f 130 return callback(null, torrent, thumbnailName, videoData, insertedVideo)
3a8a8b51 131 })
807df9e6
C
132 },
133
67100f1f 134 function getThumbnailBase64 (torrent, thumbnailName, videoData, insertedVideo, callback) {
807df9e6
C
135 videoData.createdDate = insertedVideo.createdDate
136
137 fs.readFile(thumbnailsDir + thumbnailName, function (err, thumbnailData) {
138 if (err) {
139 // TODO unseed the video
140 // TODO remove thumbnail
141 // TODO: remove video
142 logger.error('Cannot read the thumbnail of the video')
143 return callback(err)
144 }
145
146 return callback(null, videoData, thumbnailData)
147 })
148 },
149
150 function sendToFriends (videoData, thumbnailData, callback) {
151 // Set the image in base64
152 videoData.thumbnailBase64 = new Buffer(thumbnailData).toString('base64')
153
154 // Now we'll add the video's meta data to our friends
155 friends.addVideoToFriends(videoData)
156
157 return callback(null)
158 }
159
be587647 160 ], function andFinally (err) {
807df9e6
C
161 if (err) {
162 logger.error('Cannot insert the video.')
163 return next(err)
164 }
165
166 // TODO : include Location of the new video -> 201
167 return res.type('json').status(204).end()
9f10b292
C
168 })
169}
8c308c2b 170
68ce3ae0 171function getVideo (req, res, next) {
bc503c2a 172 Videos.get(req.params.id, function (err, videoObj) {
9f10b292 173 if (err) return next(err)
8c308c2b 174
bc503c2a 175 const state = videos.getVideoState(videoObj)
2df82d42
C
176 if (state.exist === false) {
177 return res.type('json').status(204).end()
9f10b292 178 }
8c308c2b 179
bc503c2a 180 res.json(getFormatedVideo(videoObj))
9f10b292
C
181 })
182}
8c308c2b 183
9f10b292 184function listVideos (req, res, next) {
68ce3ae0 185 Videos.list(req.query.start, req.query.count, req.query.sort, function (err, videosList, totalVideos) {
9f10b292 186 if (err) return next(err)
c45f7f84 187
68ce3ae0 188 res.json(getFormatedVideos(videosList, totalVideos))
9f10b292
C
189 })
190}
c45f7f84 191
9f10b292 192function removeVideo (req, res, next) {
bc503c2a 193 const videoId = req.params.id
8c308c2b 194
807df9e6
C
195 async.waterfall([
196 function getVideo (callback) {
197 Videos.get(videoId, callback)
198 },
199
200 function removeVideoTorrent (video, callback) {
201 removeTorrent(video.magnetUri, function () {
202 return callback(null, video)
203 })
204 },
205
206 function removeFromDB (video, callback) {
9f10b292 207 Videos.removeOwned(req.params.id, function (err) {
807df9e6 208 if (err) return callback(err)
c173e565 209
807df9e6
C
210 return callback(null, video)
211 })
212 },
cbe2f7c3 213
807df9e6
C
214 function removeVideoData (video, callback) {
215 videos.removeVideosDataFromDisk([ video ], function (err) {
216 if (err) logger.error('Cannot remove video data from disk.', { video: video })
c173e565 217
807df9e6 218 return callback(null, video)
c173e565 219 })
807df9e6
C
220 },
221
222 function sendInformationToFriends (video, callback) {
223 const params = {
224 name: video.name,
225 magnetUri: video.magnetUri
226 }
227
228 friends.removeVideoToFriends(params)
229
230 return callback(null)
231 }
be587647 232 ], function andFinally (err) {
807df9e6
C
233 if (err) {
234 logger.error('Errors when removed the video.', { error: err })
235 return next(err)
236 }
237
238 return res.type('json').status(204).end()
9f10b292
C
239 })
240}
8c308c2b 241
9f10b292 242function searchVideos (req, res, next) {
46246b5f
C
243 Videos.search(req.params.value, req.query.field, req.query.start, req.query.count, req.query.sort,
244 function (err, videosList, totalVideos) {
9f10b292 245 if (err) return next(err)
8c308c2b 246
68ce3ae0 247 res.json(getFormatedVideos(videosList, totalVideos))
9f10b292
C
248 })
249}
c173e565 250
9f10b292 251// ---------------------------------------------------------------------------
c173e565 252
bc503c2a
C
253function getFormatedVideo (videoObj) {
254 const formatedVideo = {
255 id: videoObj._id,
256 name: videoObj.name,
257 description: videoObj.description,
9e379c83 258 podUrl: videoObj.podUrl.replace(/^https?:\/\//, ''),
bc503c2a
C
259 isLocal: videos.getVideoState(videoObj).owned,
260 magnetUri: videoObj.magnetUri,
261 author: videoObj.author,
262 duration: videoObj.duration,
be587647 263 tags: videoObj.tags,
bb10240e
C
264 thumbnailPath: constants.THUMBNAILS_STATIC_PATH + '/' + videoObj.thumbnail,
265 createdDate: videoObj.createdDate
2df82d42
C
266 }
267
bc503c2a 268 return formatedVideo
2df82d42
C
269}
270
68ce3ae0 271function getFormatedVideos (videosObj, totalVideos) {
bc503c2a 272 const formatedVideos = []
2df82d42 273
bc503c2a
C
274 videosObj.forEach(function (videoObj) {
275 formatedVideos.push(getFormatedVideo(videoObj))
2df82d42
C
276 })
277
68ce3ae0
C
278 return {
279 total: totalVideos,
280 data: formatedVideos
281 }
2df82d42
C
282}
283
9f10b292
C
284// Maybe the torrent is not seeded, but we catch the error to don't stop the removing process
285function removeTorrent (magnetUri, callback) {
286 try {
287 webtorrent.remove(magnetUri, callback)
288 } catch (err) {
289 logger.warn('Cannot remove the torrent from WebTorrent', { err: err })
290 return callback(null)
c173e565 291 }
9f10b292 292}