aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--client/src/app/shared/video/abstract-video-list.ts2
-rw-r--r--server.ts2
-rw-r--r--server/controllers/api/users.ts19
-rw-r--r--server/controllers/api/videos/index.ts89
-rw-r--r--server/helpers/custom-validators/misc.ts27
-rw-r--r--server/helpers/custom-validators/users.ts26
-rw-r--r--server/helpers/custom-validators/videos.ts29
-rw-r--r--server/helpers/image-utils.ts21
-rw-r--r--server/helpers/utils.ts18
-rw-r--r--server/initializers/constants.ts10
-rw-r--r--server/lib/activitypub/actor.ts6
-rw-r--r--server/middlewares/validators/videos.ts51
-rw-r--r--server/tests/api/check-params/users.ts8
-rw-r--r--server/tests/api/check-params/videos.ts150
-rw-r--r--server/tests/api/fixtures/preview.jpgbin0 -> 4215 bytes
-rw-r--r--server/tests/api/fixtures/thumbnail.jpgbin0 -> 1457 bytes
-rw-r--r--server/tests/api/videos/multiple-servers.ts16
-rw-r--r--server/tests/utils/miscs/miscs.ts15
-rw-r--r--server/tests/utils/requests/requests.ts19
-rw-r--r--server/tests/utils/users/users.ts4
-rw-r--r--server/tests/utils/videos/videos.ts83
-rw-r--r--server/tools/import-youtube.ts2
22 files changed, 454 insertions, 143 deletions
diff --git a/client/src/app/shared/video/abstract-video-list.ts b/client/src/app/shared/video/abstract-video-list.ts
index 034d0d879..16ff38558 100644
--- a/client/src/app/shared/video/abstract-video-list.ts
+++ b/client/src/app/shared/video/abstract-video-list.ts
@@ -1,4 +1,4 @@
1import { ElementRef, OnInit, ViewChild, ViewChildren } from '@angular/core' 1import { ElementRef, OnInit, ViewChild } from '@angular/core'
2import { ActivatedRoute, Router } from '@angular/router' 2import { ActivatedRoute, Router } from '@angular/router'
3import { isInMobileView } from '@app/shared/misc/utils' 3import { isInMobileView } from '@app/shared/misc/utils'
4import { InfiniteScrollerDirective } from '@app/shared/video/infinite-scroller.directive' 4import { InfiniteScrollerDirective } from '@app/shared/video/infinite-scroller.directive'
diff --git a/server.ts b/server.ts
index dc7a71d60..529194a5e 100644
--- a/server.ts
+++ b/server.ts
@@ -158,7 +158,7 @@ app.use(function (req, res, next) {
158}) 158})
159 159
160app.use(function (err, req, res, next) { 160app.use(function (err, req, res, next) {
161 logger.error(err, err) 161 logger.error('Error in controller.', { error: err.stack || err.message || err })
162 res.sendStatus(err.status || 500) 162 res.sendStatus(err.status || 500)
163}) 163})
164 164
diff --git a/server/controllers/api/users.ts b/server/controllers/api/users.ts
index 6e5d09695..e3067584e 100644
--- a/server/controllers/api/users.ts
+++ b/server/controllers/api/users.ts
@@ -1,13 +1,13 @@
1import * as express from 'express' 1import * as express from 'express'
2import 'multer'
2import { extname, join } from 'path' 3import { extname, join } from 'path'
3import * as sharp from 'sharp'
4import * as uuidv4 from 'uuid/v4' 4import * as uuidv4 from 'uuid/v4'
5import { UserCreate, UserRight, UserRole, UserUpdate, UserUpdateMe, UserVideoRate as FormattedUserVideoRate } from '../../../shared' 5import { UserCreate, UserRight, UserRole, UserUpdate, UserUpdateMe, UserVideoRate as FormattedUserVideoRate } from '../../../shared'
6import { unlinkPromise } from '../../helpers/core-utils'
7import { retryTransactionWrapper } from '../../helpers/database-utils' 6import { retryTransactionWrapper } from '../../helpers/database-utils'
7import { processImage } from '../../helpers/image-utils'
8import { logger } from '../../helpers/logger' 8import { logger } from '../../helpers/logger'
9import { createReqFiles, getFormattedObjects } from '../../helpers/utils' 9import { createReqFiles, getFormattedObjects } from '../../helpers/utils'
10import { AVATAR_MIMETYPE_EXT, AVATARS_SIZE, CONFIG, sequelizeTypescript } from '../../initializers' 10import { AVATARS_SIZE, CONFIG, IMAGE_MIMETYPE_EXT, sequelizeTypescript } from '../../initializers'
11import { updateActorAvatarInstance } from '../../lib/activitypub' 11import { updateActorAvatarInstance } from '../../lib/activitypub'
12import { sendUpdateUser } from '../../lib/activitypub/send' 12import { sendUpdateUser } from '../../lib/activitypub/send'
13import { Emailer } from '../../lib/emailer' 13import { Emailer } from '../../lib/emailer'
@@ -42,7 +42,7 @@ import { UserModel } from '../../models/account/user'
42import { OAuthTokenModel } from '../../models/oauth/oauth-token' 42import { OAuthTokenModel } from '../../models/oauth/oauth-token'
43import { VideoModel } from '../../models/video/video' 43import { VideoModel } from '../../models/video/video'
44 44
45const reqAvatarFile = createReqFiles('avatarfile', CONFIG.STORAGE.AVATARS_DIR, AVATAR_MIMETYPE_EXT) 45const reqAvatarFile = createReqFiles([ 'avatarfile' ], IMAGE_MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.AVATARS_DIR })
46 46
47const usersRouter = express.Router() 47const usersRouter = express.Router()
48 48
@@ -288,17 +288,10 @@ async function updateMyAvatar (req: express.Request, res: express.Response, next
288 const user = res.locals.oauth.token.user 288 const user = res.locals.oauth.token.user
289 const actor = user.Account.Actor 289 const actor = user.Account.Actor
290 290
291 const avatarDir = CONFIG.STORAGE.AVATARS_DIR
292 const source = join(avatarDir, avatarPhysicalFile.filename)
293 const extension = extname(avatarPhysicalFile.filename) 291 const extension = extname(avatarPhysicalFile.filename)
294 const avatarName = uuidv4() + extension 292 const avatarName = uuidv4() + extension
295 const destination = join(avatarDir, avatarName) 293 const destination = join(CONFIG.STORAGE.AVATARS_DIR, avatarName)
296 294 await processImage(avatarPhysicalFile, destination, AVATARS_SIZE)
297 await sharp(source)
298 .resize(AVATARS_SIZE.width, AVATARS_SIZE.height)
299 .toFile(destination)
300
301 await unlinkPromise(source)
302 295
303 const avatar = await sequelizeTypescript.transaction(async t => { 296 const avatar = await sequelizeTypescript.transaction(async t => {
304 const updatedActor = await updateActorAvatarInstance(actor, avatarName, t) 297 const updatedActor = await updateActorAvatarInstance(actor, avatarName, t)
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts
index 459795141..1a4de081f 100644
--- a/server/controllers/api/videos/index.ts
+++ b/server/controllers/api/videos/index.ts
@@ -4,18 +4,36 @@ import { VideoCreate, VideoPrivacy, VideoUpdate } from '../../../../shared'
4import { renamePromise } from '../../../helpers/core-utils' 4import { renamePromise } from '../../../helpers/core-utils'
5import { retryTransactionWrapper } from '../../../helpers/database-utils' 5import { retryTransactionWrapper } from '../../../helpers/database-utils'
6import { getVideoFileHeight } from '../../../helpers/ffmpeg-utils' 6import { getVideoFileHeight } from '../../../helpers/ffmpeg-utils'
7import { processImage } from '../../../helpers/image-utils'
7import { logger } from '../../../helpers/logger' 8import { logger } from '../../../helpers/logger'
8import { createReqFiles, getFormattedObjects, getServerActor, resetSequelizeInstance } from '../../../helpers/utils' 9import { createReqFiles, getFormattedObjects, getServerActor, resetSequelizeInstance } from '../../../helpers/utils'
9import { 10import {
10 CONFIG, sequelizeTypescript, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_MIMETYPE_EXT, 11 CONFIG,
12 IMAGE_MIMETYPE_EXT,
13 PREVIEWS_SIZE,
14 sequelizeTypescript,
15 THUMBNAILS_SIZE,
16 VIDEO_CATEGORIES,
17 VIDEO_LANGUAGES,
18 VIDEO_LICENCES,
19 VIDEO_MIMETYPE_EXT,
11 VIDEO_PRIVACIES 20 VIDEO_PRIVACIES
12} from '../../../initializers' 21} from '../../../initializers'
13import { fetchRemoteVideoDescription, getVideoActivityPubUrl, shareVideoByServerAndChannel } from '../../../lib/activitypub' 22import { fetchRemoteVideoDescription, getVideoActivityPubUrl, shareVideoByServerAndChannel } from '../../../lib/activitypub'
14import { sendCreateVideo, sendCreateViewToOrigin, sendCreateViewToVideoFollowers, sendUpdateVideo } from '../../../lib/activitypub/send' 23import { sendCreateVideo, sendCreateViewToOrigin, sendCreateViewToVideoFollowers, sendUpdateVideo } from '../../../lib/activitypub/send'
15import { JobQueue } from '../../../lib/job-queue' 24import { JobQueue } from '../../../lib/job-queue'
16import { 25import {
17 asyncMiddleware, authenticate, paginationValidator, setDefaultSort, setDefaultPagination, videosAddValidator, videosGetValidator, 26 asyncMiddleware,
18 videosRemoveValidator, videosSearchValidator, videosSortValidator, videosUpdateValidator 27 authenticate,
28 paginationValidator,
29 setDefaultPagination,
30 setDefaultSort,
31 videosAddValidator,
32 videosGetValidator,
33 videosRemoveValidator,
34 videosSearchValidator,
35 videosSortValidator,
36 videosUpdateValidator
19} from '../../../middlewares' 37} from '../../../middlewares'
20import { TagModel } from '../../../models/video/tag' 38import { TagModel } from '../../../models/video/tag'
21import { VideoModel } from '../../../models/video/video' 39import { VideoModel } from '../../../models/video/video'
@@ -28,7 +46,23 @@ import { rateVideoRouter } from './rate'
28 46
29const videosRouter = express.Router() 47const videosRouter = express.Router()
30 48
31const reqVideoFile = createReqFiles('videofile', CONFIG.STORAGE.VIDEOS_DIR, VIDEO_MIMETYPE_EXT) 49const reqVideoFileAdd = createReqFiles(
50 [ 'videofile', 'thumbnailfile', 'previewfile' ],
51 Object.assign({}, VIDEO_MIMETYPE_EXT, IMAGE_MIMETYPE_EXT),
52 {
53 videofile: CONFIG.STORAGE.VIDEOS_DIR,
54 thumbnailfile: CONFIG.STORAGE.THUMBNAILS_DIR,
55 previewfile: CONFIG.STORAGE.PREVIEWS_DIR
56 }
57)
58const reqVideoFileUpdate = createReqFiles(
59 [ 'thumbnailfile', 'previewfile' ],
60 IMAGE_MIMETYPE_EXT,
61 {
62 thumbnailfile: CONFIG.STORAGE.THUMBNAILS_DIR,
63 previewfile: CONFIG.STORAGE.PREVIEWS_DIR
64 }
65)
32 66
33videosRouter.use('/', abuseVideoRouter) 67videosRouter.use('/', abuseVideoRouter)
34videosRouter.use('/', blacklistRouter) 68videosRouter.use('/', blacklistRouter)
@@ -58,12 +92,13 @@ videosRouter.get('/search',
58) 92)
59videosRouter.put('/:id', 93videosRouter.put('/:id',
60 authenticate, 94 authenticate,
95 reqVideoFileUpdate,
61 asyncMiddleware(videosUpdateValidator), 96 asyncMiddleware(videosUpdateValidator),
62 asyncMiddleware(updateVideoRetryWrapper) 97 asyncMiddleware(updateVideoRetryWrapper)
63) 98)
64videosRouter.post('/upload', 99videosRouter.post('/upload',
65 authenticate, 100 authenticate,
66 reqVideoFile, 101 reqVideoFileAdd,
67 asyncMiddleware(videosAddValidator), 102 asyncMiddleware(videosAddValidator),
68 asyncMiddleware(addVideoRetryWrapper) 103 asyncMiddleware(addVideoRetryWrapper)
69) 104)
@@ -150,8 +185,7 @@ async function addVideo (req: express.Request, res: express.Response, videoPhysi
150 const video = new VideoModel(videoData) 185 const video = new VideoModel(videoData)
151 video.url = getVideoActivityPubUrl(video) 186 video.url = getVideoActivityPubUrl(video)
152 187
153 const videoFilePath = join(CONFIG.STORAGE.VIDEOS_DIR, videoPhysicalFile.filename) 188 const videoFileHeight = await getVideoFileHeight(videoPhysicalFile.path)
154 const videoFileHeight = await getVideoFileHeight(videoFilePath)
155 189
156 const videoFileData = { 190 const videoFileData = {
157 extname: extname(videoPhysicalFile.filename), 191 extname: extname(videoPhysicalFile.filename),
@@ -160,21 +194,28 @@ async function addVideo (req: express.Request, res: express.Response, videoPhysi
160 } 194 }
161 const videoFile = new VideoFileModel(videoFileData) 195 const videoFile = new VideoFileModel(videoFileData)
162 const videoDir = CONFIG.STORAGE.VIDEOS_DIR 196 const videoDir = CONFIG.STORAGE.VIDEOS_DIR
163 const source = join(videoDir, videoPhysicalFile.filename)
164 const destination = join(videoDir, video.getVideoFilename(videoFile)) 197 const destination = join(videoDir, video.getVideoFilename(videoFile))
198 await renamePromise(videoPhysicalFile.path, destination)
165 199
166 await renamePromise(source, destination) 200 // Process thumbnail or create it from the video
167 // This is important in case if there is another attempt in the retry process 201 const thumbnailField = req.files['thumbnailfile']
168 videoPhysicalFile.filename = video.getVideoFilename(videoFile) 202 if (thumbnailField) {
203 const thumbnailPhysicalFile = thumbnailField[0]
204 await processImage(thumbnailPhysicalFile, join(CONFIG.STORAGE.THUMBNAILS_DIR, video.getThumbnailName()), THUMBNAILS_SIZE)
205 } else {
206 await video.createThumbnail(videoFile)
207 }
169 208
170 const tasks = [] 209 // Process preview or create it from the video
210 const previewField = req.files['previewfile']
211 if (previewField) {
212 const previewPhysicalFile = previewField[0]
213 await processImage(previewPhysicalFile, join(CONFIG.STORAGE.PREVIEWS_DIR, video.getPreviewName()), PREVIEWS_SIZE)
214 } else {
215 await video.createPreview(videoFile)
216 }
171 217
172 tasks.push( 218 await video.createTorrentAndSetInfoHash(videoFile)
173 video.createTorrentAndSetInfoHash(videoFile),
174 video.createThumbnail(videoFile),
175 video.createPreview(videoFile)
176 )
177 await Promise.all(tasks)
178 219
179 const videoCreated = await sequelizeTypescript.transaction(async t => { 220 const videoCreated = await sequelizeTypescript.transaction(async t => {
180 const sequelizeOptions = { transaction: t } 221 const sequelizeOptions = { transaction: t }
@@ -237,6 +278,18 @@ async function updateVideo (req: express.Request, res: express.Response) {
237 const videoInfoToUpdate: VideoUpdate = req.body 278 const videoInfoToUpdate: VideoUpdate = req.body
238 const wasPrivateVideo = videoInstance.privacy === VideoPrivacy.PRIVATE 279 const wasPrivateVideo = videoInstance.privacy === VideoPrivacy.PRIVATE
239 280
281 // Process thumbnail or create it from the video
282 if (req.files && req.files['thumbnailfile']) {
283 const thumbnailPhysicalFile = req.files['thumbnailfile'][0]
284 await processImage(thumbnailPhysicalFile, join(CONFIG.STORAGE.THUMBNAILS_DIR, videoInstance.getThumbnailName()), THUMBNAILS_SIZE)
285 }
286
287 // Process preview or create it from the video
288 if (req.files && req.files['previewfile']) {
289 const previewPhysicalFile = req.files['previewfile'][0]
290 await processImage(previewPhysicalFile, join(CONFIG.STORAGE.PREVIEWS_DIR, videoInstance.getPreviewName()), PREVIEWS_SIZE)
291 }
292
240 try { 293 try {
241 await sequelizeTypescript.transaction(async t => { 294 await sequelizeTypescript.transaction(async t => {
242 const sequelizeOptions = { 295 const sequelizeOptions = {
diff --git a/server/helpers/custom-validators/misc.ts b/server/helpers/custom-validators/misc.ts
index 3903884ea..8a270b777 100644
--- a/server/helpers/custom-validators/misc.ts
+++ b/server/helpers/custom-validators/misc.ts
@@ -1,3 +1,4 @@
1import 'multer'
1import * as validator from 'validator' 2import * as validator from 'validator'
2 3
3function exists (value: any) { 4function exists (value: any) {
@@ -28,6 +29,29 @@ function isBooleanValid (value: string) {
28 return typeof value === 'boolean' || (typeof value === 'string' && validator.isBoolean(value)) 29 return typeof value === 'boolean' || (typeof value === 'string' && validator.isBoolean(value))
29} 30}
30 31
32function isFileValid (
33 files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[],
34 mimeTypeRegex: string,
35 field: string,
36 optional = false
37) {
38 // Should have files
39 if (!files) return optional
40 if (isArray(files)) return optional
41
42 // Should have a file
43 const fileArray = files[ field ]
44 if (!fileArray || fileArray.length === 0) {
45 return optional
46 }
47
48 // The file should exist
49 const file = fileArray[ 0 ]
50 if (!file || !file.originalname) return false
51
52 return new RegExp(`^${mimeTypeRegex}$`, 'i').test(file.mimetype)
53}
54
31// --------------------------------------------------------------------------- 55// ---------------------------------------------------------------------------
32 56
33export { 57export {
@@ -37,5 +61,6 @@ export {
37 isUUIDValid, 61 isUUIDValid,
38 isIdOrUUIDValid, 62 isIdOrUUIDValid,
39 isDateValid, 63 isDateValid,
40 isBooleanValid 64 isBooleanValid,
65 isFileValid
41} 66}
diff --git a/server/helpers/custom-validators/users.ts b/server/helpers/custom-validators/users.ts
index 6ed60c1c4..e805313f8 100644
--- a/server/helpers/custom-validators/users.ts
+++ b/server/helpers/custom-validators/users.ts
@@ -1,9 +1,9 @@
1import * as validator from 'validator'
2import 'express-validator' 1import 'express-validator'
3 2import * as validator from 'validator'
4import { exists, isArray } from './misc'
5import { CONSTRAINTS_FIELDS } from '../../initializers'
6import { UserRole } from '../../../shared' 3import { UserRole } from '../../../shared'
4import { CONSTRAINTS_FIELDS } from '../../initializers'
5
6import { exists, isFileValid } from './misc'
7 7
8const USERS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.USERS 8const USERS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.USERS
9 9
@@ -37,20 +37,12 @@ function isUserRoleValid (value: any) {
37 return exists(value) && validator.isInt('' + value) && UserRole[value] !== undefined 37 return exists(value) && validator.isInt('' + value) && UserRole[value] !== undefined
38} 38}
39 39
40const avatarMimeTypes = CONSTRAINTS_FIELDS.ACTORS.AVATAR.EXTNAME
41 .map(v => v.replace('.', ''))
42 .join('|')
43const avatarMimeTypesRegex = `image/(${avatarMimeTypes})`
40function isAvatarFile (files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[]) { 44function isAvatarFile (files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[]) {
41 // Should have files 45 return isFileValid(files, avatarMimeTypesRegex, 'avatarfile')
42 if (!files) return false
43 if (isArray(files)) return false
44
45 // Should have videofile file
46 const avatarfile = files['avatarfile']
47 if (!avatarfile || avatarfile.length === 0) return false
48
49 // The file should exist
50 const file = avatarfile[0]
51 if (!file || !file.originalname) return false
52
53 return new RegExp('^image/(png|jpeg)$', 'i').test(file.mimetype)
54} 46}
55 47
56// --------------------------------------------------------------------------- 48// ---------------------------------------------------------------------------
diff --git a/server/helpers/custom-validators/videos.ts b/server/helpers/custom-validators/videos.ts
index 0e8a2aab2..8ef3a3c64 100644
--- a/server/helpers/custom-validators/videos.ts
+++ b/server/helpers/custom-validators/videos.ts
@@ -8,12 +8,12 @@ import {
8 CONSTRAINTS_FIELDS, 8 CONSTRAINTS_FIELDS,
9 VIDEO_CATEGORIES, 9 VIDEO_CATEGORIES,
10 VIDEO_LANGUAGES, 10 VIDEO_LANGUAGES,
11 VIDEO_LICENCES, 11 VIDEO_LICENCES, VIDEO_MIMETYPE_EXT,
12 VIDEO_PRIVACIES, 12 VIDEO_PRIVACIES,
13 VIDEO_RATE_TYPES 13 VIDEO_RATE_TYPES
14} from '../../initializers' 14} from '../../initializers'
15import { VideoModel } from '../../models/video/video' 15import { VideoModel } from '../../models/video/video'
16import { exists, isArray } from './misc' 16import { exists, isArray, isFileValid } from './misc'
17 17
18const VIDEOS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEOS 18const VIDEOS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEOS
19const VIDEO_ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_ABUSES 19const VIDEO_ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_ABUSES
@@ -68,20 +68,18 @@ function isVideoRatingTypeValid (value: string) {
68 return value === 'none' || values(VIDEO_RATE_TYPES).indexOf(value as VideoRateType) !== -1 68 return value === 'none' || values(VIDEO_RATE_TYPES).indexOf(value as VideoRateType) !== -1
69} 69}
70 70
71const videoFileTypes = Object.keys(VIDEO_MIMETYPE_EXT).map(m => `(${m})`)
72const videoFileTypesRegex = videoFileTypes.join('|')
71function isVideoFile (files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[]) { 73function isVideoFile (files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[]) {
72 // Should have files 74 return isFileValid(files, videoFileTypesRegex, 'videofile')
73 if (!files) return false 75}
74 if (isArray(files)) return false
75
76 // Should have videofile file
77 const videofile = files['videofile']
78 if (!videofile || videofile.length === 0) return false
79
80 // The file should exist
81 const file = videofile[0]
82 if (!file || !file.originalname) return false
83 76
84 return new RegExp('^video/(webm|mp4|ogg)$', 'i').test(file.mimetype) 77const videoImageTypes = CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME
78 .map(v => v.replace('.', ''))
79 .join('|')
80const videoImageTypesRegex = `image/(${videoImageTypes})`
81function isVideoImage (files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[], field: string) {
82 return isFileValid(files, videoImageTypesRegex, field, true)
85} 83}
86 84
87function isVideoPrivacyValid (value: string) { 85function isVideoPrivacyValid (value: string) {
@@ -141,5 +139,6 @@ export {
141 isVideoPrivacyValid, 139 isVideoPrivacyValid,
142 isVideoFileResolutionValid, 140 isVideoFileResolutionValid,
143 isVideoFileSizeValid, 141 isVideoFileSizeValid,
144 isVideoExist 142 isVideoExist,
143 isVideoImage
145} 144}
diff --git a/server/helpers/image-utils.ts b/server/helpers/image-utils.ts
new file mode 100644
index 000000000..ba57b5812
--- /dev/null
+++ b/server/helpers/image-utils.ts
@@ -0,0 +1,21 @@
1import 'multer'
2import * as sharp from 'sharp'
3import { unlinkPromise } from './core-utils'
4
5async function processImage (
6 physicalFile: Express.Multer.File,
7 destination: string,
8 newSize: { width: number, height: number }
9) {
10 await sharp(physicalFile.path)
11 .resize(newSize.width, newSize.height)
12 .toFile(destination)
13
14 await unlinkPromise(physicalFile.path)
15}
16
17// ---------------------------------------------------------------------------
18
19export {
20 processImage
21}
diff --git a/server/helpers/utils.ts b/server/helpers/utils.ts
index 79c3b5858..3b618360b 100644
--- a/server/helpers/utils.ts
+++ b/server/helpers/utils.ts
@@ -27,10 +27,14 @@ function badRequest (req: express.Request, res: express.Response, next: express.
27 return res.type('json').status(400).end() 27 return res.type('json').status(400).end()
28} 28}
29 29
30function createReqFiles (fieldName: string, storageDir: string, mimeTypes: { [ id: string ]: string }) { 30function createReqFiles (
31 fieldNames: string[],
32 mimeTypes: { [ id: string ]: string },
33 destinations: { [ fieldName: string ]: string }
34) {
31 const storage = multer.diskStorage({ 35 const storage = multer.diskStorage({
32 destination: (req, file, cb) => { 36 destination: (req, file, cb) => {
33 cb(null, storageDir) 37 cb(null, destinations[file.fieldname])
34 }, 38 },
35 39
36 filename: async (req, file, cb) => { 40 filename: async (req, file, cb) => {
@@ -48,7 +52,15 @@ function createReqFiles (fieldName: string, storageDir: string, mimeTypes: { [ i
48 } 52 }
49 }) 53 })
50 54
51 return multer({ storage }).fields([{ name: fieldName, maxCount: 1 }]) 55 const fields = []
56 for (const fieldName of fieldNames) {
57 fields.push({
58 name: fieldName,
59 maxCount: 1
60 })
61 }
62
63 return multer({ storage }).fields(fields)
52} 64}
53 65
54async function generateRandomString (size: number) { 66async function generateRandomString (size: number) {
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts
index e531c4c39..91fbbde75 100644
--- a/server/initializers/constants.ts
+++ b/server/initializers/constants.ts
@@ -182,6 +182,12 @@ const CONSTRAINTS_FIELDS = {
182 NAME: { min: 3, max: 120 }, // Length 182 NAME: { min: 3, max: 120 }, // Length
183 TRUNCATED_DESCRIPTION: { min: 3, max: 250 }, // Length 183 TRUNCATED_DESCRIPTION: { min: 3, max: 250 }, // Length
184 DESCRIPTION: { min: 3, max: 3000 }, // Length 184 DESCRIPTION: { min: 3, max: 3000 }, // Length
185 IMAGE: {
186 EXTNAME: [ '.jpg', '.jpeg' ],
187 FILE_SIZE: {
188 max: 2 * 1024 * 1024 // 2MB
189 }
190 },
185 EXTNAME: [ '.mp4', '.ogv', '.webm' ], 191 EXTNAME: [ '.mp4', '.ogv', '.webm' ],
186 INFO_HASH: { min: 40, max: 40 }, // Length, info hash is 20 bytes length but we represent it in hexadecimal so 20 * 2 192 INFO_HASH: { min: 40, max: 40 }, // Length, info hash is 20 bytes length but we represent it in hexadecimal so 20 * 2
187 DURATION: { min: 1 }, // Number 193 DURATION: { min: 1 }, // Number
@@ -285,7 +291,7 @@ const VIDEO_MIMETYPE_EXT = {
285 'video/mp4': '.mp4' 291 'video/mp4': '.mp4'
286} 292}
287 293
288const AVATAR_MIMETYPE_EXT = { 294const IMAGE_MIMETYPE_EXT = {
289 'image/png': '.png', 295 'image/png': '.png',
290 'image/jpg': '.jpg', 296 'image/jpg': '.jpg',
291 'image/jpeg': '.jpg' 297 'image/jpeg': '.jpg'
@@ -427,7 +433,7 @@ export {
427 VIDEO_RATE_TYPES, 433 VIDEO_RATE_TYPES,
428 VIDEO_MIMETYPE_EXT, 434 VIDEO_MIMETYPE_EXT,
429 USER_PASSWORD_RESET_LIFETIME, 435 USER_PASSWORD_RESET_LIFETIME,
430 AVATAR_MIMETYPE_EXT, 436 IMAGE_MIMETYPE_EXT,
431 SCHEDULER_INTERVAL, 437 SCHEDULER_INTERVAL,
432 JOB_COMPLETED_LIFETIME 438 JOB_COMPLETED_LIFETIME
433} 439}
diff --git a/server/lib/activitypub/actor.ts b/server/lib/activitypub/actor.ts
index 712de7d0d..c3255d8ca 100644
--- a/server/lib/activitypub/actor.ts
+++ b/server/lib/activitypub/actor.ts
@@ -12,7 +12,7 @@ import { logger } from '../../helpers/logger'
12import { createPrivateAndPublicKeys } from '../../helpers/peertube-crypto' 12import { createPrivateAndPublicKeys } from '../../helpers/peertube-crypto'
13import { doRequest, doRequestAndSaveToFile } from '../../helpers/requests' 13import { doRequest, doRequestAndSaveToFile } from '../../helpers/requests'
14import { getUrlFromWebfinger } from '../../helpers/webfinger' 14import { getUrlFromWebfinger } from '../../helpers/webfinger'
15import { AVATAR_MIMETYPE_EXT, CONFIG, sequelizeTypescript } from '../../initializers' 15import { IMAGE_MIMETYPE_EXT, CONFIG, sequelizeTypescript } from '../../initializers'
16import { AccountModel } from '../../models/account/account' 16import { AccountModel } from '../../models/account/account'
17import { ActorModel } from '../../models/activitypub/actor' 17import { ActorModel } from '../../models/activitypub/actor'
18import { AvatarModel } from '../../models/avatar/avatar' 18import { AvatarModel } from '../../models/avatar/avatar'
@@ -147,10 +147,10 @@ async function fetchActorTotalItems (url: string) {
147 147
148async function fetchAvatarIfExists (actorJSON: ActivityPubActor) { 148async function fetchAvatarIfExists (actorJSON: ActivityPubActor) {
149 if ( 149 if (
150 actorJSON.icon && actorJSON.icon.type === 'Image' && AVATAR_MIMETYPE_EXT[actorJSON.icon.mediaType] !== undefined && 150 actorJSON.icon && actorJSON.icon.type === 'Image' && IMAGE_MIMETYPE_EXT[actorJSON.icon.mediaType] !== undefined &&
151 isActivityPubUrlValid(actorJSON.icon.url) 151 isActivityPubUrlValid(actorJSON.icon.url)
152 ) { 152 ) {
153 const extension = AVATAR_MIMETYPE_EXT[actorJSON.icon.mediaType] 153 const extension = IMAGE_MIMETYPE_EXT[actorJSON.icon.mediaType]
154 154
155 const avatarName = uuidv4() + extension 155 const avatarName = uuidv4() + extension
156 const destPath = join(CONFIG.STORAGE.AVATARS_DIR, avatarName) 156 const destPath = join(CONFIG.STORAGE.AVATARS_DIR, avatarName)
diff --git a/server/middlewares/validators/videos.ts b/server/middlewares/validators/videos.ts
index a365ed217..6d4fb907b 100644
--- a/server/middlewares/validators/videos.ts
+++ b/server/middlewares/validators/videos.ts
@@ -4,8 +4,18 @@ import { body, param, query } from 'express-validator/check'
4import { UserRight, VideoPrivacy } from '../../../shared' 4import { UserRight, VideoPrivacy } from '../../../shared'
5import { isBooleanValid, isIdOrUUIDValid, isIdValid, isUUIDValid } from '../../helpers/custom-validators/misc' 5import { isBooleanValid, isIdOrUUIDValid, isIdValid, isUUIDValid } from '../../helpers/custom-validators/misc'
6import { 6import {
7 isVideoAbuseReasonValid, isVideoCategoryValid, isVideoDescriptionValid, isVideoExist, isVideoFile, isVideoLanguageValid, 7 isVideoAbuseReasonValid,
8 isVideoLicenceValid, isVideoNameValid, isVideoPrivacyValid, isVideoRatingTypeValid, isVideoTagsValid 8 isVideoCategoryValid,
9 isVideoDescriptionValid,
10 isVideoExist,
11 isVideoFile,
12 isVideoImage,
13 isVideoLanguageValid,
14 isVideoLicenceValid,
15 isVideoNameValid,
16 isVideoPrivacyValid,
17 isVideoRatingTypeValid,
18 isVideoTagsValid
9} from '../../helpers/custom-validators/videos' 19} from '../../helpers/custom-validators/videos'
10import { getDurationFromVideoFile } from '../../helpers/ffmpeg-utils' 20import { getDurationFromVideoFile } from '../../helpers/ffmpeg-utils'
11import { logger } from '../../helpers/logger' 21import { logger } from '../../helpers/logger'
@@ -22,6 +32,14 @@ const videosAddValidator = [
22 'This file is not supported. Please, make sure it is of the following type : ' 32 'This file is not supported. Please, make sure it is of the following type : '
23 + CONSTRAINTS_FIELDS.VIDEOS.EXTNAME.join(', ') 33 + CONSTRAINTS_FIELDS.VIDEOS.EXTNAME.join(', ')
24 ), 34 ),
35 body('thumbnailfile').custom((value, { req }) => isVideoImage(req.files, 'thumbnailfile')).withMessage(
36 'This thumbnail file is not supported. Please, make sure it is of the following type : '
37 + CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ')
38 ),
39 body('previewfile').custom((value, { req }) => isVideoImage(req.files, 'previewfile')).withMessage(
40 'This preview file is not supported. Please, make sure it is of the following type : '
41 + CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ')
42 ),
25 body('name').custom(isVideoNameValid).withMessage('Should have a valid name'), 43 body('name').custom(isVideoNameValid).withMessage('Should have a valid name'),
26 body('category').optional().custom(isVideoCategoryValid).withMessage('Should have a valid category'), 44 body('category').optional().custom(isVideoCategoryValid).withMessage('Should have a valid category'),
27 body('licence').optional().custom(isVideoLicenceValid).withMessage('Should have a valid licence'), 45 body('licence').optional().custom(isVideoLicenceValid).withMessage('Should have a valid licence'),
@@ -37,6 +55,7 @@ const videosAddValidator = [
37 logger.debug('Checking videosAdd parameters', { parameters: req.body, files: req.files }) 55 logger.debug('Checking videosAdd parameters', { parameters: req.body, files: req.files })
38 56
39 if (areValidationErrors(req, res)) return 57 if (areValidationErrors(req, res)) return
58 if (areErrorsInVideoImageFiles(req, res)) return
40 59
41 const videoFile: Express.Multer.File = req.files['videofile'][0] 60 const videoFile: Express.Multer.File = req.files['videofile'][0]
42 const user = res.locals.oauth.token.User 61 const user = res.locals.oauth.token.User
@@ -82,6 +101,14 @@ const videosAddValidator = [
82 101
83const videosUpdateValidator = [ 102const videosUpdateValidator = [
84 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), 103 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
104 body('thumbnailfile').custom((value, { req }) => isVideoImage(req.files, 'thumbnailfile')).withMessage(
105 'This thumbnail file is not supported. Please, make sure it is of the following type : '
106 + CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ')
107 ),
108 body('previewfile').custom((value, { req }) => isVideoImage(req.files, 'previewfile')).withMessage(
109 'This preview file is not supported. Please, make sure it is of the following type : '
110 + CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ')
111 ),
85 body('name').optional().custom(isVideoNameValid).withMessage('Should have a valid name'), 112 body('name').optional().custom(isVideoNameValid).withMessage('Should have a valid name'),
86 body('category').optional().custom(isVideoCategoryValid).withMessage('Should have a valid category'), 113 body('category').optional().custom(isVideoCategoryValid).withMessage('Should have a valid category'),
87 body('licence').optional().custom(isVideoLicenceValid).withMessage('Should have a valid licence'), 114 body('licence').optional().custom(isVideoLicenceValid).withMessage('Should have a valid licence'),
@@ -96,6 +123,7 @@ const videosUpdateValidator = [
96 logger.debug('Checking videosUpdate parameters', { parameters: req.body }) 123 logger.debug('Checking videosUpdate parameters', { parameters: req.body })
97 124
98 if (areValidationErrors(req, res)) return 125 if (areValidationErrors(req, res)) return
126 if (areErrorsInVideoImageFiles(req, res)) return
99 if (!await isVideoExist(req.params.id, res)) return 127 if (!await isVideoExist(req.params.id, res)) return
100 128
101 const video = res.locals.video 129 const video = res.locals.video
@@ -274,3 +302,22 @@ function checkUserCanDeleteVideo (user: UserModel, video: VideoModel, res: expre
274 302
275 return true 303 return true
276} 304}
305
306function areErrorsInVideoImageFiles (req: express.Request, res: express.Response) {
307 // Files are optional
308 if (!req.files) return false
309
310 for (const imageField of [ 'thumbnail', 'preview' ]) {
311 if (!req.files[ imageField ]) continue
312
313 const imageFile = req.files[ imageField ][ 0 ] as Express.Multer.File
314 if (imageFile.size > CONSTRAINTS_FIELDS.VIDEOS.IMAGE.FILE_SIZE.max) {
315 res.status(400)
316 .send({ error: `The size of the ${imageField} is too big (>${CONSTRAINTS_FIELDS.VIDEOS.IMAGE.FILE_SIZE.max}).` })
317 .end()
318 return true
319 }
320 }
321
322 return false
323}
diff --git a/server/tests/api/check-params/users.ts b/server/tests/api/check-params/users.ts
index 0fbc414c9..d9dea0713 100644
--- a/server/tests/api/check-params/users.ts
+++ b/server/tests/api/check-params/users.ts
@@ -7,7 +7,7 @@ import { UserRole } from '../../../../shared'
7 7
8import { 8import {
9 createUser, flushTests, getMyUserInformation, getMyUserVideoRating, getUsersList, immutableAssign, killallServers, makeGetRequest, 9 createUser, flushTests, getMyUserInformation, getMyUserVideoRating, getUsersList, immutableAssign, killallServers, makeGetRequest,
10 makePostBodyRequest, makePostUploadRequest, makePutBodyRequest, registerUser, removeUser, runServer, ServerInfo, setAccessTokensToServers, 10 makePostBodyRequest, makeUploadRequest, makePutBodyRequest, registerUser, removeUser, runServer, ServerInfo, setAccessTokensToServers,
11 updateUser, uploadVideo, userLogin 11 updateUser, uploadVideo, userLogin
12} from '../../utils' 12} from '../../utils'
13import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params' 13import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params'
@@ -273,7 +273,7 @@ describe('Test users API validators', function () {
273 const attaches = { 273 const attaches = {
274 'avatarfile': join(__dirname, '..', 'fixtures', 'video_short.mp4') 274 'avatarfile': join(__dirname, '..', 'fixtures', 'video_short.mp4')
275 } 275 }
276 await makePostUploadRequest({ url: server.url, path: path + '/me/avatar/pick', token: server.accessToken, fields, attaches }) 276 await makeUploadRequest({ url: server.url, path: path + '/me/avatar/pick', token: server.accessToken, fields, attaches })
277 }) 277 })
278 278
279 it('Should fail with a big file', async function () { 279 it('Should fail with a big file', async function () {
@@ -281,7 +281,7 @@ describe('Test users API validators', function () {
281 const attaches = { 281 const attaches = {
282 'avatarfile': join(__dirname, '..', 'fixtures', 'avatar-big.png') 282 'avatarfile': join(__dirname, '..', 'fixtures', 'avatar-big.png')
283 } 283 }
284 await makePostUploadRequest({ url: server.url, path: path + '/me/avatar/pick', token: server.accessToken, fields, attaches }) 284 await makeUploadRequest({ url: server.url, path: path + '/me/avatar/pick', token: server.accessToken, fields, attaches })
285 }) 285 })
286 286
287 it('Should succeed with the correct params', async function () { 287 it('Should succeed with the correct params', async function () {
@@ -289,7 +289,7 @@ describe('Test users API validators', function () {
289 const attaches = { 289 const attaches = {
290 'avatarfile': join(__dirname, '..', 'fixtures', 'avatar.png') 290 'avatarfile': join(__dirname, '..', 'fixtures', 'avatar.png')
291 } 291 }
292 await makePostUploadRequest({ 292 await makeUploadRequest({
293 url: server.url, 293 url: server.url,
294 path: path + '/me/avatar/pick', 294 path: path + '/me/avatar/pick',
295 token: server.accessToken, 295 token: server.accessToken,
diff --git a/server/tests/api/check-params/videos.ts b/server/tests/api/check-params/videos.ts
index f25e3f595..aa30b721b 100644
--- a/server/tests/api/check-params/videos.ts
+++ b/server/tests/api/check-params/videos.ts
@@ -7,7 +7,7 @@ import { join } from 'path'
7import { VideoPrivacy } from '../../../../shared/models/videos/video-privacy.enum' 7import { VideoPrivacy } from '../../../../shared/models/videos/video-privacy.enum'
8import { 8import {
9 createUser, flushTests, getMyUserInformation, getVideo, getVideosList, immutableAssign, killallServers, makeDeleteRequest, 9 createUser, flushTests, getMyUserInformation, getVideo, getVideosList, immutableAssign, killallServers, makeDeleteRequest,
10 makeGetRequest, makePostUploadRequest, makePutBodyRequest, removeVideo, runServer, ServerInfo, setAccessTokensToServers, userLogin 10 makeGetRequest, makeUploadRequest, makePutBodyRequest, removeVideo, runServer, ServerInfo, setAccessTokensToServers, userLogin
11} from '../../utils' 11} from '../../utils'
12import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params' 12import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params'
13 13
@@ -111,91 +111,91 @@ describe('Test videos API validator', function () {
111 it('Should fail with nothing', async function () { 111 it('Should fail with nothing', async function () {
112 const fields = {} 112 const fields = {}
113 const attaches = {} 113 const attaches = {}
114 await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) 114 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
115 }) 115 })
116 116
117 it('Should fail without name', async function () { 117 it('Should fail without name', async function () {
118 const fields = omit(baseCorrectParams, 'name') 118 const fields = omit(baseCorrectParams, 'name')
119 const attaches = baseCorrectAttaches 119 const attaches = baseCorrectAttaches
120 120
121 await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) 121 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
122 }) 122 })
123 123
124 it('Should fail with a long name', async function () { 124 it('Should fail with a long name', async function () {
125 const fields = immutableAssign(baseCorrectParams, { name: 'super'.repeat(65) }) 125 const fields = immutableAssign(baseCorrectParams, { name: 'super'.repeat(65) })
126 const attaches = baseCorrectAttaches 126 const attaches = baseCorrectAttaches
127 127
128 await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) 128 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
129 }) 129 })
130 130
131 it('Should fail with a bad category', async function () { 131 it('Should fail with a bad category', async function () {
132 const fields = immutableAssign(baseCorrectParams, { category: 125 }) 132 const fields = immutableAssign(baseCorrectParams, { category: 125 })
133 const attaches = baseCorrectAttaches 133 const attaches = baseCorrectAttaches
134 134
135 await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) 135 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
136 }) 136 })
137 137
138 it('Should fail with a bad licence', async function () { 138 it('Should fail with a bad licence', async function () {
139 const fields = immutableAssign(baseCorrectParams, { licence: 125 }) 139 const fields = immutableAssign(baseCorrectParams, { licence: 125 })
140 const attaches = baseCorrectAttaches 140 const attaches = baseCorrectAttaches
141 141
142 await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) 142 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
143 }) 143 })
144 144
145 it('Should fail with a bad language', async function () { 145 it('Should fail with a bad language', async function () {
146 const fields = immutableAssign(baseCorrectParams, { language: 125 }) 146 const fields = immutableAssign(baseCorrectParams, { language: 125 })
147 const attaches = baseCorrectAttaches 147 const attaches = baseCorrectAttaches
148 148
149 await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) 149 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
150 }) 150 })
151 151
152 it('Should fail without nsfw attribute', async function () { 152 it('Should fail without nsfw attribute', async function () {
153 const fields = omit(baseCorrectParams, 'nsfw') 153 const fields = omit(baseCorrectParams, 'nsfw')
154 const attaches = baseCorrectAttaches 154 const attaches = baseCorrectAttaches
155 155
156 await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) 156 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
157 }) 157 })
158 158
159 it('Should fail with a bad nsfw attribute', async function () { 159 it('Should fail with a bad nsfw attribute', async function () {
160 const fields = immutableAssign(baseCorrectParams, { nsfw: 2 }) 160 const fields = immutableAssign(baseCorrectParams, { nsfw: 2 })
161 const attaches = baseCorrectAttaches 161 const attaches = baseCorrectAttaches
162 162
163 await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) 163 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
164 }) 164 })
165 165
166 it('Should fail without commentsEnabled attribute', async function () { 166 it('Should fail without commentsEnabled attribute', async function () {
167 const fields = omit(baseCorrectParams, 'commentsEnabled') 167 const fields = omit(baseCorrectParams, 'commentsEnabled')
168 const attaches = baseCorrectAttaches 168 const attaches = baseCorrectAttaches
169 169
170 await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) 170 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
171 }) 171 })
172 172
173 it('Should fail with a bad commentsEnabled attribute', async function () { 173 it('Should fail with a bad commentsEnabled attribute', async function () {
174 const fields = immutableAssign(baseCorrectParams, { commentsEnabled: 2 }) 174 const fields = immutableAssign(baseCorrectParams, { commentsEnabled: 2 })
175 const attaches = baseCorrectAttaches 175 const attaches = baseCorrectAttaches
176 176
177 await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) 177 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
178 }) 178 })
179 179
180 it('Should fail with a long description', async function () { 180 it('Should fail with a long description', async function () {
181 const fields = immutableAssign(baseCorrectParams, { description: 'super'.repeat(1500) }) 181 const fields = immutableAssign(baseCorrectParams, { description: 'super'.repeat(1500) })
182 const attaches = baseCorrectAttaches 182 const attaches = baseCorrectAttaches
183 183
184 await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) 184 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
185 }) 185 })
186 186
187 it('Should fail without a channel', async function () { 187 it('Should fail without a channel', async function () {
188 const fields = omit(baseCorrectParams, 'channelId') 188 const fields = omit(baseCorrectParams, 'channelId')
189 const attaches = baseCorrectAttaches 189 const attaches = baseCorrectAttaches
190 190
191 await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) 191 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
192 }) 192 })
193 193
194 it('Should fail with a bad channel', async function () { 194 it('Should fail with a bad channel', async function () {
195 const fields = immutableAssign(baseCorrectParams, { channelId: 545454 }) 195 const fields = immutableAssign(baseCorrectParams, { channelId: 545454 })
196 const attaches = baseCorrectAttaches 196 const attaches = baseCorrectAttaches
197 197
198 await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) 198 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
199 }) 199 })
200 200
201 it('Should fail with another user channel', async function () { 201 it('Should fail with another user channel', async function () {
@@ -212,34 +212,34 @@ describe('Test videos API validator', function () {
212 const fields = immutableAssign(baseCorrectParams, { channelId: customChannelId }) 212 const fields = immutableAssign(baseCorrectParams, { channelId: customChannelId })
213 const attaches = baseCorrectAttaches 213 const attaches = baseCorrectAttaches
214 214
215 await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) 215 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
216 }) 216 })
217 217
218 it('Should fail with too many tags', async function () { 218 it('Should fail with too many tags', async function () {
219 const fields = immutableAssign(baseCorrectParams, { tags: [ 'tag1', 'tag2', 'tag3', 'tag4', 'tag5', 'tag6' ] }) 219 const fields = immutableAssign(baseCorrectParams, { tags: [ 'tag1', 'tag2', 'tag3', 'tag4', 'tag5', 'tag6' ] })
220 const attaches = baseCorrectAttaches 220 const attaches = baseCorrectAttaches
221 221
222 await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) 222 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
223 }) 223 })
224 224
225 it('Should fail with a tag length too low', async function () { 225 it('Should fail with a tag length too low', async function () {
226 const fields = immutableAssign(baseCorrectParams, { tags: [ 'tag1', 't' ] }) 226 const fields = immutableAssign(baseCorrectParams, { tags: [ 'tag1', 't' ] })
227 const attaches = baseCorrectAttaches 227 const attaches = baseCorrectAttaches
228 228
229 await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) 229 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
230 }) 230 })
231 231
232 it('Should fail with a tag length too big', async function () { 232 it('Should fail with a tag length too big', async function () {
233 const fields = immutableAssign(baseCorrectParams, { tags: [ 'tag1', 'my_super_tag_too_long_long_long_long_long_long' ] }) 233 const fields = immutableAssign(baseCorrectParams, { tags: [ 'tag1', 'my_super_tag_too_long_long_long_long_long_long' ] })
234 const attaches = baseCorrectAttaches 234 const attaches = baseCorrectAttaches
235 235
236 await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) 236 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
237 }) 237 })
238 238
239 it('Should fail without an input file', async function () { 239 it('Should fail without an input file', async function () {
240 const fields = baseCorrectParams 240 const fields = baseCorrectParams
241 const attaches = {} 241 const attaches = {}
242 await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) 242 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
243 }) 243 })
244 244
245 it('Should fail without an incorrect input file', async function () { 245 it('Should fail without an incorrect input file', async function () {
@@ -247,7 +247,47 @@ describe('Test videos API validator', function () {
247 const attaches = { 247 const attaches = {
248 'videofile': join(__dirname, '..', 'fixtures', 'video_short_fake.webm') 248 'videofile': join(__dirname, '..', 'fixtures', 'video_short_fake.webm')
249 } 249 }
250 await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) 250 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
251 })
252
253 it('Should fail with an incorrect thumbnail file', async function () {
254 const fields = baseCorrectParams
255 const attaches = {
256 'thumbnailfile': join(__dirname, '..', 'fixtures', 'avatar.png'),
257 'videofile': join(__dirname, '..', 'fixtures', 'video_short_fake.webm')
258 }
259
260 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
261 })
262
263 it('Should fail with a big thumbnail file', async function () {
264 const fields = baseCorrectParams
265 const attaches = {
266 'thumbnailfile': join(__dirname, '..', 'fixtures', 'avatar-big.png'),
267 'videofile': join(__dirname, '..', 'fixtures', 'video_short_fake.webm')
268 }
269
270 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
271 })
272
273 it('Should fail with an incorrect preview file', async function () {
274 const fields = baseCorrectParams
275 const attaches = {
276 'previewfile': join(__dirname, '..', 'fixtures', 'avatar.png'),
277 'videofile': join(__dirname, '..', 'fixtures', 'video_short_fake.webm')
278 }
279
280 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
281 })
282
283 it('Should fail with a big preview file', async function () {
284 const fields = baseCorrectParams
285 const attaches = {
286 'previewfile': join(__dirname, '..', 'fixtures', 'avatar-big.png'),
287 'videofile': join(__dirname, '..', 'fixtures', 'video_short_fake.webm')
288 }
289
290 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
251 }) 291 })
252 292
253 it('Should succeed with the correct parameters', async function () { 293 it('Should succeed with the correct parameters', async function () {
@@ -257,7 +297,7 @@ describe('Test videos API validator', function () {
257 297
258 { 298 {
259 const attaches = baseCorrectAttaches 299 const attaches = baseCorrectAttaches
260 await makePostUploadRequest({ 300 await makeUploadRequest({
261 url: server.url, 301 url: server.url,
262 path: path + '/upload', 302 path: path + '/upload',
263 token: server.accessToken, 303 token: server.accessToken,
@@ -272,7 +312,7 @@ describe('Test videos API validator', function () {
272 videofile: join(__dirname, '..', 'fixtures', 'video_short.mp4') 312 videofile: join(__dirname, '..', 'fixtures', 'video_short.mp4')
273 }) 313 })
274 314
275 await makePostUploadRequest({ 315 await makeUploadRequest({
276 url: server.url, 316 url: server.url,
277 path: path + '/upload', 317 path: path + '/upload',
278 token: server.accessToken, 318 token: server.accessToken,
@@ -287,7 +327,7 @@ describe('Test videos API validator', function () {
287 videofile: join(__dirname, '..', 'fixtures', 'video_short.ogv') 327 videofile: join(__dirname, '..', 'fixtures', 'video_short.ogv')
288 }) 328 })
289 329
290 await makePostUploadRequest({ 330 await makeUploadRequest({
291 url: server.url, 331 url: server.url,
292 path: path + '/upload', 332 path: path + '/upload',
293 token: server.accessToken, 333 token: server.accessToken,
@@ -400,6 +440,70 @@ describe('Test videos API validator', function () {
400 await makePutBodyRequest({ url: server.url, path: path + videoId, token: server.accessToken, fields }) 440 await makePutBodyRequest({ url: server.url, path: path + videoId, token: server.accessToken, fields })
401 }) 441 })
402 442
443 it('Should fail with an incorrect thumbnail file', async function () {
444 const fields = baseCorrectParams
445 const attaches = {
446 'thumbnailfile': join(__dirname, '..', 'fixtures', 'avatar.png')
447 }
448
449 await makeUploadRequest({
450 url: server.url,
451 method: 'PUT',
452 path: path + videoId,
453 token: server.accessToken,
454 fields,
455 attaches
456 })
457 })
458
459 it('Should fail with a big thumbnail file', async function () {
460 const fields = baseCorrectParams
461 const attaches = {
462 'thumbnailfile': join(__dirname, '..', 'fixtures', 'avatar-big.png')
463 }
464
465 await makeUploadRequest({
466 url: server.url,
467 method: 'PUT',
468 path: path + videoId,
469 token: server.accessToken,
470 fields,
471 attaches
472 })
473 })
474
475 it('Should fail with an incorrect preview file', async function () {
476 const fields = baseCorrectParams
477 const attaches = {
478 'previewfile': join(__dirname, '..', 'fixtures', 'avatar.png')
479 }
480
481 await makeUploadRequest({
482 url: server.url,
483 method: 'PUT',
484 path: path + videoId,
485 token: server.accessToken,
486 fields,
487 attaches
488 })
489 })
490
491 it('Should fail with a big preview file', async function () {
492 const fields = baseCorrectParams
493 const attaches = {
494 'previewfile': join(__dirname, '..', 'fixtures', 'avatar-big.png')
495 }
496
497 await makeUploadRequest({
498 url: server.url,
499 method: 'PUT',
500 path: path + videoId,
501 token: server.accessToken,
502 fields,
503 attaches
504 })
505 })
506
403 it('Should fail with a video of another user') 507 it('Should fail with a video of another user')
404 508
405 it('Should fail with a video of another server') 509 it('Should fail with a video of another server')
diff --git a/server/tests/api/fixtures/preview.jpg b/server/tests/api/fixtures/preview.jpg
new file mode 100644
index 000000000..c40ece838
--- /dev/null
+++ b/server/tests/api/fixtures/preview.jpg
Binary files differ
diff --git a/server/tests/api/fixtures/thumbnail.jpg b/server/tests/api/fixtures/thumbnail.jpg
new file mode 100644
index 000000000..cc3af8a4a
--- /dev/null
+++ b/server/tests/api/fixtures/thumbnail.jpg
Binary files differ
diff --git a/server/tests/api/videos/multiple-servers.ts b/server/tests/api/videos/multiple-servers.ts
index 0215b3011..3646fbb0f 100644
--- a/server/tests/api/videos/multiple-servers.ts
+++ b/server/tests/api/videos/multiple-servers.ts
@@ -137,7 +137,9 @@ describe('Test multiple servers', function () {
137 nsfw: true, 137 nsfw: true,
138 description: 'my super description for server 2', 138 description: 'my super description for server 2',
139 tags: [ 'tag1p2', 'tag2p2', 'tag3p2' ], 139 tags: [ 'tag1p2', 'tag2p2', 'tag3p2' ],
140 fixture: 'video_short2.webm' 140 fixture: 'video_short2.webm',
141 thumbnailfile: 'thumbnail.jpg',
142 previewfile: 'preview.jpg'
141 } 143 }
142 await uploadVideo(servers[1].url, userAccessToken, videoAttributes) 144 await uploadVideo(servers[1].url, userAccessToken, videoAttributes)
143 145
@@ -184,7 +186,9 @@ describe('Test multiple servers', function () {
184 resolution: 720, 186 resolution: 720,
185 size: 710000 187 size: 710000
186 } 188 }
187 ] 189 ],
190 thumbnailfile: 'thumbnail',
191 previewfile: 'preview'
188 } 192 }
189 193
190 const res = await getVideosList(server.url) 194 const res = await getVideosList(server.url)
@@ -521,7 +525,9 @@ describe('Test multiple servers', function () {
521 language: 13, 525 language: 13,
522 nsfw: true, 526 nsfw: true,
523 description: 'my super description updated', 527 description: 'my super description updated',
524 tags: [ 'tag_up_1', 'tag_up_2' ] 528 tags: [ 'tag_up_1', 'tag_up_2' ],
529 thumbnailfile: 'thumbnail.jpg',
530 previewfile: 'preview.jpg'
525 } 531 }
526 532
527 await updateVideo(servers[2].url, servers[2].accessToken, toRemove[0].id, attributes) 533 await updateVideo(servers[2].url, servers[2].accessToken, toRemove[0].id, attributes)
@@ -565,7 +571,9 @@ describe('Test multiple servers', function () {
565 resolution: 720, 571 resolution: 720,
566 size: 292677 572 size: 292677
567 } 573 }
568 ] 574 ],
575 thumbnailfile: 'thumbnail',
576 previewfile: 'preview'
569 } 577 }
570 await completeVideoCheck(server.url, videoUpdated, checkAttributes) 578 await completeVideoCheck(server.url, videoUpdated, checkAttributes)
571 } 579 }
diff --git a/server/tests/utils/miscs/miscs.ts b/server/tests/utils/miscs/miscs.ts
index 99d109bfe..24cbf59ca 100644
--- a/server/tests/utils/miscs/miscs.ts
+++ b/server/tests/utils/miscs/miscs.ts
@@ -1,6 +1,6 @@
1/* tslint:disable:no-unused-expression */ 1/* tslint:disable:no-unused-expression */
2 2
3import { join } from 'path' 3import { isAbsolute, join } from 'path'
4import * as request from 'supertest' 4import * as request from 'supertest'
5import * as WebTorrent from 'webtorrent' 5import * as WebTorrent from 'webtorrent'
6import { readFileBufferPromise } from '../../../helpers/core-utils' 6import { readFileBufferPromise } from '../../../helpers/core-utils'
@@ -45,8 +45,8 @@ async function testImage (url: string, imageName: string, imagePath: string, ext
45 const body = res.body 45 const body = res.body
46 46
47 const data = await readFileBufferPromise(join(__dirname, '..', '..', 'api', 'fixtures', imageName + extension)) 47 const data = await readFileBufferPromise(join(__dirname, '..', '..', 'api', 'fixtures', imageName + extension))
48 const minLength = body.length - ((50 * body.length) / 100) 48 const minLength = body.length - ((20 * body.length) / 100)
49 const maxLength = body.length + ((50 * body.length) / 100) 49 const maxLength = body.length + ((20 * body.length) / 100)
50 50
51 return data.length > minLength && data.length < maxLength 51 return data.length > minLength && data.length < maxLength
52 } else { 52 } else {
@@ -55,6 +55,14 @@ async function testImage (url: string, imageName: string, imagePath: string, ext
55 } 55 }
56} 56}
57 57
58function buildAbsoluteFixturePath (path: string) {
59 if (isAbsolute(path)) {
60 return path
61 }
62
63 return join(__dirname, '..', '..', 'api', 'fixtures', path)
64}
65
58// --------------------------------------------------------------------------- 66// ---------------------------------------------------------------------------
59 67
60export { 68export {
@@ -63,5 +71,6 @@ export {
63 webtorrentAdd, 71 webtorrentAdd,
64 immutableAssign, 72 immutableAssign,
65 testImage, 73 testImage,
74 buildAbsoluteFixturePath,
66 root 75 root
67} 76}
diff --git a/server/tests/utils/requests/requests.ts b/server/tests/utils/requests/requests.ts
index 840072430..a9b1dff9a 100644
--- a/server/tests/utils/requests/requests.ts
+++ b/server/tests/utils/requests/requests.ts
@@ -1,4 +1,5 @@
1import * as request from 'supertest' 1import * as request from 'supertest'
2import { buildAbsoluteFixturePath } from '../'
2 3
3function makeGetRequest (options: { 4function makeGetRequest (options: {
4 url: string, 5 url: string,
@@ -40,8 +41,9 @@ function makeDeleteRequest (options: {
40 .expect(options.statusCodeExpected) 41 .expect(options.statusCodeExpected)
41} 42}
42 43
43function makePostUploadRequest (options: { 44function makeUploadRequest (options: {
44 url: string, 45 url: string,
46 method?: 'POST' | 'PUT',
45 path: string, 47 path: string,
46 token: string, 48 token: string,
47 fields: { [ fieldName: string ]: any }, 49 fields: { [ fieldName: string ]: any },
@@ -50,9 +52,14 @@ function makePostUploadRequest (options: {
50}) { 52}) {
51 if (!options.statusCodeExpected) options.statusCodeExpected = 400 53 if (!options.statusCodeExpected) options.statusCodeExpected = 400
52 54
53 const req = request(options.url) 55 let req: request.Test
54 .post(options.path) 56 if (options.method === 'PUT') {
55 .set('Accept', 'application/json') 57 req = request(options.url).put(options.path)
58 } else {
59 req = request(options.url).post(options.path)
60 }
61
62 req.set('Accept', 'application/json')
56 63
57 if (options.token) req.set('Authorization', 'Bearer ' + options.token) 64 if (options.token) req.set('Authorization', 'Bearer ' + options.token)
58 65
@@ -70,7 +77,7 @@ function makePostUploadRequest (options: {
70 77
71 Object.keys(options.attaches).forEach(attach => { 78 Object.keys(options.attaches).forEach(attach => {
72 const value = options.attaches[attach] 79 const value = options.attaches[attach]
73 req.attach(attach, value) 80 req.attach(attach, buildAbsoluteFixturePath(value))
74 }) 81 })
75 82
76 return req.expect(options.statusCodeExpected) 83 return req.expect(options.statusCodeExpected)
@@ -119,7 +126,7 @@ function makePutBodyRequest (options: {
119 126
120export { 127export {
121 makeGetRequest, 128 makeGetRequest,
122 makePostUploadRequest, 129 makeUploadRequest,
123 makePostBodyRequest, 130 makePostBodyRequest,
124 makePutBodyRequest, 131 makePutBodyRequest,
125 makeDeleteRequest 132 makeDeleteRequest
diff --git a/server/tests/utils/users/users.ts b/server/tests/utils/users/users.ts
index 9e33e6796..3c9d46246 100644
--- a/server/tests/utils/users/users.ts
+++ b/server/tests/utils/users/users.ts
@@ -1,6 +1,6 @@
1import { isAbsolute, join } from 'path' 1import { isAbsolute, join } from 'path'
2import * as request from 'supertest' 2import * as request from 'supertest'
3import { makePostBodyRequest, makePostUploadRequest, makePutBodyRequest } from '../' 3import { makePostBodyRequest, makeUploadRequest, makePutBodyRequest } from '../'
4 4
5import { UserRole } from '../../../../shared/index' 5import { UserRole } from '../../../../shared/index'
6 6
@@ -162,7 +162,7 @@ function updateMyAvatar (options: {
162 filePath = join(__dirname, '..', '..', 'api', 'fixtures', options.fixture) 162 filePath = join(__dirname, '..', '..', 'api', 'fixtures', options.fixture)
163 } 163 }
164 164
165 return makePostUploadRequest({ 165 return makeUploadRequest({
166 url: options.url, 166 url: options.url,
167 path, 167 path,
168 token: options.accessToken, 168 token: options.accessToken,
diff --git a/server/tests/utils/videos/videos.ts b/server/tests/utils/videos/videos.ts
index 9105b5f13..9d4267db8 100644
--- a/server/tests/utils/videos/videos.ts
+++ b/server/tests/utils/videos/videos.ts
@@ -5,7 +5,16 @@ import { existsSync, readFile } from 'fs'
5import * as parseTorrent from 'parse-torrent' 5import * as parseTorrent from 'parse-torrent'
6import { extname, isAbsolute, join } from 'path' 6import { extname, isAbsolute, join } from 'path'
7import * as request from 'supertest' 7import * as request from 'supertest'
8import { getMyUserInformation, makeGetRequest, root, ServerInfo, testImage } from '../' 8import {
9 buildAbsoluteFixturePath,
10 getMyUserInformation,
11 makeGetRequest,
12 makePutBodyRequest,
13 makeUploadRequest,
14 root,
15 ServerInfo,
16 testImage
17} from '../'
9import { VideoPrivacy } from '../../../../shared/models/videos' 18import { VideoPrivacy } from '../../../../shared/models/videos'
10import { readdirPromise } from '../../../helpers/core-utils' 19import { readdirPromise } from '../../../helpers/core-utils'
11import { VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../initializers' 20import { VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../initializers'
@@ -23,6 +32,8 @@ type VideoAttributes = {
23 channelId?: number 32 channelId?: number
24 privacy?: VideoPrivacy 33 privacy?: VideoPrivacy
25 fixture?: string 34 fixture?: string
35 thumbnailfile?: string
36 previewfile?: string
26} 37}
27 38
28function getVideoCategories (url: string) { 39function getVideoCategories (url: string) {
@@ -228,8 +239,8 @@ async function uploadVideo (url: string, accessToken: string, videoAttributesArg
228 defaultChannelId = res.body.videoChannels[0].id 239 defaultChannelId = res.body.videoChannels[0].id
229 } catch (e) { /* empty */ } 240 } catch (e) { /* empty */ }
230 241
231 // Default attributes 242 // Override default attributes
232 let attributes = { 243 const attributes = Object.assign({
233 name: 'my super video', 244 name: 'my super video',
234 category: 5, 245 category: 5,
235 licence: 4, 246 licence: 4,
@@ -241,8 +252,7 @@ async function uploadVideo (url: string, accessToken: string, videoAttributesArg
241 privacy: VideoPrivacy.PUBLIC, 252 privacy: VideoPrivacy.PUBLIC,
242 commentsEnabled: true, 253 commentsEnabled: true,
243 fixture: 'video_short.webm' 254 fixture: 'video_short.webm'
244 } 255 }, videoAttributesArg)
245 attributes = Object.assign(attributes, videoAttributesArg)
246 256
247 const req = request(url) 257 const req = request(url)
248 .post(path) 258 .post(path)
@@ -267,22 +277,22 @@ async function uploadVideo (url: string, accessToken: string, videoAttributesArg
267 req.field('licence', attributes.licence.toString()) 277 req.field('licence', attributes.licence.toString())
268 } 278 }
269 279
270 for (let i = 0; i < attributes.tags.length; i++) { 280 if (attributes.thumbnailfile !== undefined) {
271 req.field('tags[' + i + ']', attributes.tags[i]) 281 req.attach('thumbnailfile', buildAbsoluteFixturePath(attributes.thumbnailfile))
282 }
283 if (attributes.previewfile !== undefined) {
284 req.attach('previewfile', buildAbsoluteFixturePath(attributes.previewfile))
272 } 285 }
273 286
274 let filePath = '' 287 for (let i = 0; i < attributes.tags.length; i++) {
275 if (isAbsolute(attributes.fixture)) { 288 req.field('tags[' + i + ']', attributes.tags[i])
276 filePath = attributes.fixture
277 } else {
278 filePath = join(__dirname, '..', '..', 'api', 'fixtures', attributes.fixture)
279 } 289 }
280 290
281 return req.attach('videofile', filePath) 291 return req.attach('videofile', buildAbsoluteFixturePath(attributes.fixture))
282 .expect(specialStatus) 292 .expect(specialStatus)
283} 293}
284 294
285function updateVideo (url: string, accessToken: string, id: number | string, attributes: VideoAttributes, specialStatus = 204) { 295function updateVideo (url: string, accessToken: string, id: number | string, attributes: VideoAttributes, statusCodeExpected = 204) {
286 const path = '/api/v1/videos/' + id 296 const path = '/api/v1/videos/' + id
287 const body = {} 297 const body = {}
288 298
@@ -296,12 +306,30 @@ function updateVideo (url: string, accessToken: string, id: number | string, att
296 if (attributes.tags) body['tags'] = attributes.tags 306 if (attributes.tags) body['tags'] = attributes.tags
297 if (attributes.privacy) body['privacy'] = attributes.privacy 307 if (attributes.privacy) body['privacy'] = attributes.privacy
298 308
299 return request(url) 309 // Upload request
300 .put(path) 310 if (attributes.thumbnailfile || attributes.previewfile) {
301 .send(body) 311 const attaches: any = {}
302 .set('Accept', 'application/json') 312 if (attributes.thumbnailfile) attaches.thumbnailfile = attributes.thumbnailfile
303 .set('Authorization', 'Bearer ' + accessToken) 313 if (attributes.previewfile) attaches.previewfile = attributes.previewfile
304 .expect(specialStatus) 314
315 return makeUploadRequest({
316 url,
317 method: 'PUT',
318 path,
319 token: accessToken,
320 fields: body,
321 attaches,
322 statusCodeExpected
323 })
324 }
325
326 return makePutBodyRequest({
327 url,
328 path,
329 fields: body,
330 token: accessToken,
331 statusCodeExpected
332 })
305} 333}
306 334
307function rateVideo (url: string, accessToken: string, id: number, rating: string, specialStatus = 204) { 335function rateVideo (url: string, accessToken: string, id: number, rating: string, specialStatus = 204) {
@@ -355,7 +383,9 @@ async function completeVideoCheck (
355 files: { 383 files: {
356 resolution: number 384 resolution: number
357 size: number 385 size: number
358 }[] 386 }[],
387 thumbnailfile?: string
388 previewfile?: string
359 } 389 }
360) { 390) {
361 if (!attributes.likes) attributes.likes = 0 391 if (!attributes.likes) attributes.likes = 0
@@ -414,8 +444,15 @@ async function completeVideoCheck (
414 const maxSize = attributeFile.size + ((10 * attributeFile.size) / 100) 444 const maxSize = attributeFile.size + ((10 * attributeFile.size) / 100)
415 expect(file.size).to.be.above(minSize).and.below(maxSize) 445 expect(file.size).to.be.above(minSize).and.below(maxSize)
416 446
417 const test = await testImage(url, attributes.fixture, videoDetails.thumbnailPath) 447 {
418 expect(test).to.equal(true) 448 const test = await testImage(url, attributes.thumbnailfile || attributes.fixture, videoDetails.thumbnailPath)
449 expect(test).to.equal(true)
450 }
451
452 if (attributes.previewfile) {
453 const test = await testImage(url, attributes.previewfile, videoDetails.previewPath)
454 expect(test).to.equal(true)
455 }
419 456
420 const torrent = await webtorrentAdd(magnetUri, true) 457 const torrent = await webtorrentAdd(magnetUri, true)
421 expect(torrent.files).to.be.an('array') 458 expect(torrent.files).to.be.an('array')
diff --git a/server/tools/import-youtube.ts b/server/tools/import-youtube.ts
index 96bce29b5..ccbc71029 100644
--- a/server/tools/import-youtube.ts
+++ b/server/tools/import-youtube.ts
@@ -1,7 +1,5 @@
1import * as program from 'commander' 1import * as program from 'commander'
2import { createWriteStream } from 'fs'
3import { join } from 'path' 2import { join } from 'path'
4import { cursorTo } from 'readline'
5import * as youtubeDL from 'youtube-dl' 3import * as youtubeDL from 'youtube-dl'
6import { VideoPrivacy } from '../../shared/models/videos' 4import { VideoPrivacy } from '../../shared/models/videos'
7import { unlinkPromise } from '../helpers/core-utils' 5import { unlinkPromise } from '../helpers/core-utils'