Add ability to set video thumbnail/preview
authorChocobozzz <me@florianbigard.com>
Tue, 13 Feb 2018 17:17:05 +0000 (18:17 +0100)
committerChocobozzz <me@florianbigard.com>
Wed, 14 Feb 2018 15:03:09 +0000 (16:03 +0100)
22 files changed:
client/src/app/shared/video/abstract-video-list.ts
server.ts
server/controllers/api/users.ts
server/controllers/api/videos/index.ts
server/helpers/custom-validators/misc.ts
server/helpers/custom-validators/users.ts
server/helpers/custom-validators/videos.ts
server/helpers/image-utils.ts [new file with mode: 0644]
server/helpers/utils.ts
server/initializers/constants.ts
server/lib/activitypub/actor.ts
server/middlewares/validators/videos.ts
server/tests/api/check-params/users.ts
server/tests/api/check-params/videos.ts
server/tests/api/fixtures/preview.jpg [new file with mode: 0644]
server/tests/api/fixtures/thumbnail.jpg [new file with mode: 0644]
server/tests/api/videos/multiple-servers.ts
server/tests/utils/miscs/miscs.ts
server/tests/utils/requests/requests.ts
server/tests/utils/users/users.ts
server/tests/utils/videos/videos.ts
server/tools/import-youtube.ts

index 034d0d879a9e4372e2e9047b5353c2a7073169cd..16ff38558ef8415ec6e7fcc71229649ce73a3b63 100644 (file)
@@ -1,4 +1,4 @@
-import { ElementRef, OnInit, ViewChild, ViewChildren } from '@angular/core'
+import { ElementRef, OnInit, ViewChild } from '@angular/core'
 import { ActivatedRoute, Router } from '@angular/router'
 import { isInMobileView } from '@app/shared/misc/utils'
 import { InfiniteScrollerDirective } from '@app/shared/video/infinite-scroller.directive'
index dc7a71d608c2555e9edb648581234c7b8804ced7..529194a5ebd70535a9e67ca2d57d54108273274e 100644 (file)
--- a/server.ts
+++ b/server.ts
@@ -158,7 +158,7 @@ app.use(function (req, res, next) {
 })
 
 app.use(function (err, req, res, next) {
-  logger.error(err, err)
+  logger.error('Error in controller.', { error: err.stack || err.message || err })
   res.sendStatus(err.status || 500)
 })
 
index 6e5d096957d40ff488d27d624a7a2db693c13963..e3067584e7edabf7a082cf26a8fe14bce49b2047 100644 (file)
@@ -1,13 +1,13 @@
 import * as express from 'express'
+import 'multer'
 import { extname, join } from 'path'
-import * as sharp from 'sharp'
 import * as uuidv4 from 'uuid/v4'
 import { UserCreate, UserRight, UserRole, UserUpdate, UserUpdateMe, UserVideoRate as FormattedUserVideoRate } from '../../../shared'
-import { unlinkPromise } from '../../helpers/core-utils'
 import { retryTransactionWrapper } from '../../helpers/database-utils'
+import { processImage } from '../../helpers/image-utils'
 import { logger } from '../../helpers/logger'
 import { createReqFiles, getFormattedObjects } from '../../helpers/utils'
-import { AVATAR_MIMETYPE_EXT, AVATARS_SIZE, CONFIG, sequelizeTypescript } from '../../initializers'
+import { AVATARS_SIZE, CONFIG, IMAGE_MIMETYPE_EXT, sequelizeTypescript } from '../../initializers'
 import { updateActorAvatarInstance } from '../../lib/activitypub'
 import { sendUpdateUser } from '../../lib/activitypub/send'
 import { Emailer } from '../../lib/emailer'
@@ -42,7 +42,7 @@ import { UserModel } from '../../models/account/user'
 import { OAuthTokenModel } from '../../models/oauth/oauth-token'
 import { VideoModel } from '../../models/video/video'
 
-const reqAvatarFile = createReqFiles('avatarfile', CONFIG.STORAGE.AVATARS_DIR, AVATAR_MIMETYPE_EXT)
+const reqAvatarFile = createReqFiles([ 'avatarfile' ], IMAGE_MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.AVATARS_DIR })
 
 const usersRouter = express.Router()
 
@@ -288,17 +288,10 @@ async function updateMyAvatar (req: express.Request, res: express.Response, next
   const user = res.locals.oauth.token.user
   const actor = user.Account.Actor
 
-  const avatarDir = CONFIG.STORAGE.AVATARS_DIR
-  const source = join(avatarDir, avatarPhysicalFile.filename)
   const extension = extname(avatarPhysicalFile.filename)
   const avatarName = uuidv4() + extension
-  const destination = join(avatarDir, avatarName)
-
-  await sharp(source)
-    .resize(AVATARS_SIZE.width, AVATARS_SIZE.height)
-    .toFile(destination)
-
-  await unlinkPromise(source)
+  const destination = join(CONFIG.STORAGE.AVATARS_DIR, avatarName)
+  await processImage(avatarPhysicalFile, destination, AVATARS_SIZE)
 
   const avatar = await sequelizeTypescript.transaction(async t => {
     const updatedActor = await updateActorAvatarInstance(actor, avatarName, t)
index 459795141acf6fd4b6da30f30a2ae534aad7aa08..1a4de081f49fce7a117065c50f1c0fd14c191d0b 100644 (file)
@@ -4,18 +4,36 @@ import { VideoCreate, VideoPrivacy, VideoUpdate } from '../../../../shared'
 import { renamePromise } from '../../../helpers/core-utils'
 import { retryTransactionWrapper } from '../../../helpers/database-utils'
 import { getVideoFileHeight } from '../../../helpers/ffmpeg-utils'
+import { processImage } from '../../../helpers/image-utils'
 import { logger } from '../../../helpers/logger'
 import { createReqFiles, getFormattedObjects, getServerActor, resetSequelizeInstance } from '../../../helpers/utils'
 import {
-  CONFIG, sequelizeTypescript, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_MIMETYPE_EXT,
+  CONFIG,
+  IMAGE_MIMETYPE_EXT,
+  PREVIEWS_SIZE,
+  sequelizeTypescript,
+  THUMBNAILS_SIZE,
+  VIDEO_CATEGORIES,
+  VIDEO_LANGUAGES,
+  VIDEO_LICENCES,
+  VIDEO_MIMETYPE_EXT,
   VIDEO_PRIVACIES
 } from '../../../initializers'
 import { fetchRemoteVideoDescription, getVideoActivityPubUrl, shareVideoByServerAndChannel } from '../../../lib/activitypub'
 import { sendCreateVideo, sendCreateViewToOrigin, sendCreateViewToVideoFollowers, sendUpdateVideo } from '../../../lib/activitypub/send'
 import { JobQueue } from '../../../lib/job-queue'
 import {
-  asyncMiddleware, authenticate, paginationValidator, setDefaultSort, setDefaultPagination, videosAddValidator, videosGetValidator,
-  videosRemoveValidator, videosSearchValidator, videosSortValidator, videosUpdateValidator
+  asyncMiddleware,
+  authenticate,
+  paginationValidator,
+  setDefaultPagination,
+  setDefaultSort,
+  videosAddValidator,
+  videosGetValidator,
+  videosRemoveValidator,
+  videosSearchValidator,
+  videosSortValidator,
+  videosUpdateValidator
 } from '../../../middlewares'
 import { TagModel } from '../../../models/video/tag'
 import { VideoModel } from '../../../models/video/video'
@@ -28,7 +46,23 @@ import { rateVideoRouter } from './rate'
 
 const videosRouter = express.Router()
 
-const reqVideoFile = createReqFiles('videofile', CONFIG.STORAGE.VIDEOS_DIR, VIDEO_MIMETYPE_EXT)
+const reqVideoFileAdd = createReqFiles(
+  [ 'videofile', 'thumbnailfile', 'previewfile' ],
+  Object.assign({}, VIDEO_MIMETYPE_EXT, IMAGE_MIMETYPE_EXT),
+  {
+    videofile: CONFIG.STORAGE.VIDEOS_DIR,
+    thumbnailfile: CONFIG.STORAGE.THUMBNAILS_DIR,
+    previewfile: CONFIG.STORAGE.PREVIEWS_DIR
+  }
+)
+const reqVideoFileUpdate = createReqFiles(
+  [ 'thumbnailfile', 'previewfile' ],
+  IMAGE_MIMETYPE_EXT,
+  {
+    thumbnailfile: CONFIG.STORAGE.THUMBNAILS_DIR,
+    previewfile: CONFIG.STORAGE.PREVIEWS_DIR
+  }
+)
 
 videosRouter.use('/', abuseVideoRouter)
 videosRouter.use('/', blacklistRouter)
@@ -58,12 +92,13 @@ videosRouter.get('/search',
 )
 videosRouter.put('/:id',
   authenticate,
+  reqVideoFileUpdate,
   asyncMiddleware(videosUpdateValidator),
   asyncMiddleware(updateVideoRetryWrapper)
 )
 videosRouter.post('/upload',
   authenticate,
-  reqVideoFile,
+  reqVideoFileAdd,
   asyncMiddleware(videosAddValidator),
   asyncMiddleware(addVideoRetryWrapper)
 )
@@ -150,8 +185,7 @@ async function addVideo (req: express.Request, res: express.Response, videoPhysi
   const video = new VideoModel(videoData)
   video.url = getVideoActivityPubUrl(video)
 
-  const videoFilePath = join(CONFIG.STORAGE.VIDEOS_DIR, videoPhysicalFile.filename)
-  const videoFileHeight = await getVideoFileHeight(videoFilePath)
+  const videoFileHeight = await getVideoFileHeight(videoPhysicalFile.path)
 
   const videoFileData = {
     extname: extname(videoPhysicalFile.filename),
@@ -160,21 +194,28 @@ async function addVideo (req: express.Request, res: express.Response, videoPhysi
   }
   const videoFile = new VideoFileModel(videoFileData)
   const videoDir = CONFIG.STORAGE.VIDEOS_DIR
-  const source = join(videoDir, videoPhysicalFile.filename)
   const destination = join(videoDir, video.getVideoFilename(videoFile))
+  await renamePromise(videoPhysicalFile.path, destination)
 
-  await renamePromise(source, destination)
-  // This is important in case if there is another attempt in the retry process
-  videoPhysicalFile.filename = video.getVideoFilename(videoFile)
+  // Process thumbnail or create it from the video
+  const thumbnailField = req.files['thumbnailfile']
+  if (thumbnailField) {
+    const thumbnailPhysicalFile = thumbnailField[0]
+    await processImage(thumbnailPhysicalFile, join(CONFIG.STORAGE.THUMBNAILS_DIR, video.getThumbnailName()), THUMBNAILS_SIZE)
+  } else {
+    await video.createThumbnail(videoFile)
+  }
 
-  const tasks = []
+  // Process preview or create it from the video
+  const previewField = req.files['previewfile']
+  if (previewField) {
+    const previewPhysicalFile = previewField[0]
+    await processImage(previewPhysicalFile, join(CONFIG.STORAGE.PREVIEWS_DIR, video.getPreviewName()), PREVIEWS_SIZE)
+  } else {
+    await video.createPreview(videoFile)
+  }
 
-  tasks.push(
-    video.createTorrentAndSetInfoHash(videoFile),
-    video.createThumbnail(videoFile),
-    video.createPreview(videoFile)
-  )
-  await Promise.all(tasks)
+  await video.createTorrentAndSetInfoHash(videoFile)
 
   const videoCreated = await sequelizeTypescript.transaction(async t => {
     const sequelizeOptions = { transaction: t }
@@ -237,6 +278,18 @@ async function updateVideo (req: express.Request, res: express.Response) {
   const videoInfoToUpdate: VideoUpdate = req.body
   const wasPrivateVideo = videoInstance.privacy === VideoPrivacy.PRIVATE
 
+  // Process thumbnail or create it from the video
+  if (req.files && req.files['thumbnailfile']) {
+    const thumbnailPhysicalFile = req.files['thumbnailfile'][0]
+    await processImage(thumbnailPhysicalFile, join(CONFIG.STORAGE.THUMBNAILS_DIR, videoInstance.getThumbnailName()), THUMBNAILS_SIZE)
+  }
+
+  // Process preview or create it from the video
+  if (req.files && req.files['previewfile']) {
+    const previewPhysicalFile = req.files['previewfile'][0]
+    await processImage(previewPhysicalFile, join(CONFIG.STORAGE.PREVIEWS_DIR, videoInstance.getPreviewName()), PREVIEWS_SIZE)
+  }
+
   try {
     await sequelizeTypescript.transaction(async t => {
       const sequelizeOptions = {
index 3903884eadbe410de35c289626ea6752c259d1ad..8a270b77709502cd8c4b0de4bbd1446f175ba174 100644 (file)
@@ -1,3 +1,4 @@
+import 'multer'
 import * as validator from 'validator'
 
 function exists (value: any) {
@@ -28,6 +29,29 @@ function isBooleanValid (value: string) {
   return typeof value === 'boolean' || (typeof value === 'string' && validator.isBoolean(value))
 }
 
+function isFileValid (
+  files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[],
+  mimeTypeRegex: string,
+  field: string,
+  optional = false
+) {
+  // Should have files
+  if (!files) return optional
+  if (isArray(files)) return optional
+
+  // Should have a file
+  const fileArray = files[ field ]
+  if (!fileArray || fileArray.length === 0) {
+    return optional
+  }
+
+  // The file should exist
+  const file = fileArray[ 0 ]
+  if (!file || !file.originalname) return false
+
+  return new RegExp(`^${mimeTypeRegex}$`, 'i').test(file.mimetype)
+}
+
 // ---------------------------------------------------------------------------
 
 export {
@@ -37,5 +61,6 @@ export {
   isUUIDValid,
   isIdOrUUIDValid,
   isDateValid,
-  isBooleanValid
+  isBooleanValid,
+  isFileValid
 }
index 6ed60c1c4a8da6bb7c3e07918baae9e30c4e4703..e805313f831684b5ae2d94507d1f821a2df5584e 100644 (file)
@@ -1,9 +1,9 @@
-import * as validator from 'validator'
 import 'express-validator'
-
-import { exists, isArray } from './misc'
-import { CONSTRAINTS_FIELDS } from '../../initializers'
+import * as validator from 'validator'
 import { UserRole } from '../../../shared'
+import { CONSTRAINTS_FIELDS } from '../../initializers'
+
+import { exists, isFileValid } from './misc'
 
 const USERS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.USERS
 
@@ -37,20 +37,12 @@ function isUserRoleValid (value: any) {
   return exists(value) && validator.isInt('' + value) && UserRole[value] !== undefined
 }
 
+const avatarMimeTypes = CONSTRAINTS_FIELDS.ACTORS.AVATAR.EXTNAME
+  .map(v => v.replace('.', ''))
+  .join('|')
+const avatarMimeTypesRegex = `image/(${avatarMimeTypes})`
 function isAvatarFile (files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[]) {
-  // Should have files
-  if (!files) return false
-  if (isArray(files)) return false
-
-  // Should have videofile file
-  const avatarfile = files['avatarfile']
-  if (!avatarfile || avatarfile.length === 0) return false
-
-  // The file should exist
-  const file = avatarfile[0]
-  if (!file || !file.originalname) return false
-
-  return new RegExp('^image/(png|jpeg)$', 'i').test(file.mimetype)
+  return isFileValid(files, avatarMimeTypesRegex, 'avatarfile')
 }
 
 // ---------------------------------------------------------------------------
index 0e8a2aab2db83d96fbfa79643badc4825009d2bc..8ef3a3c643895b67fb1daed22de2b9faf7ea7996 100644 (file)
@@ -8,12 +8,12 @@ import {
   CONSTRAINTS_FIELDS,
   VIDEO_CATEGORIES,
   VIDEO_LANGUAGES,
-  VIDEO_LICENCES,
+  VIDEO_LICENCES, VIDEO_MIMETYPE_EXT,
   VIDEO_PRIVACIES,
   VIDEO_RATE_TYPES
 } from '../../initializers'
 import { VideoModel } from '../../models/video/video'
-import { exists, isArray } from './misc'
+import { exists, isArray, isFileValid } from './misc'
 
 const VIDEOS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEOS
 const VIDEO_ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_ABUSES
@@ -68,20 +68,18 @@ function isVideoRatingTypeValid (value: string) {
   return value === 'none' || values(VIDEO_RATE_TYPES).indexOf(value as VideoRateType) !== -1
 }
 
+const videoFileTypes = Object.keys(VIDEO_MIMETYPE_EXT).map(m => `(${m})`)
+const videoFileTypesRegex = videoFileTypes.join('|')
 function isVideoFile (files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[]) {
-  // Should have files
-  if (!files) return false
-  if (isArray(files)) return false
-
-  // Should have videofile file
-  const videofile = files['videofile']
-  if (!videofile || videofile.length === 0) return false
-
-  // The file should exist
-  const file = videofile[0]
-  if (!file || !file.originalname) return false
+  return isFileValid(files, videoFileTypesRegex, 'videofile')
+}
 
-  return new RegExp('^video/(webm|mp4|ogg)$', 'i').test(file.mimetype)
+const videoImageTypes = CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME
+  .map(v => v.replace('.', ''))
+  .join('|')
+const videoImageTypesRegex = `image/(${videoImageTypes})`
+function isVideoImage (files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[], field: string) {
+  return isFileValid(files, videoImageTypesRegex, field, true)
 }
 
 function isVideoPrivacyValid (value: string) {
@@ -141,5 +139,6 @@ export {
   isVideoPrivacyValid,
   isVideoFileResolutionValid,
   isVideoFileSizeValid,
-  isVideoExist
+  isVideoExist,
+  isVideoImage
 }
diff --git a/server/helpers/image-utils.ts b/server/helpers/image-utils.ts
new file mode 100644 (file)
index 0000000..ba57b58
--- /dev/null
@@ -0,0 +1,21 @@
+import 'multer'
+import * as sharp from 'sharp'
+import { unlinkPromise } from './core-utils'
+
+async function processImage (
+  physicalFile: Express.Multer.File,
+  destination: string,
+  newSize: { width: number, height: number }
+) {
+  await sharp(physicalFile.path)
+    .resize(newSize.width, newSize.height)
+    .toFile(destination)
+
+  await unlinkPromise(physicalFile.path)
+}
+
+// ---------------------------------------------------------------------------
+
+export {
+  processImage
+}
index 79c3b58581e246817a96574ebe8be58e77dfb1cc..3b618360bc25c52b37a86734c7a00d430e9cf958 100644 (file)
@@ -27,10 +27,14 @@ function badRequest (req: express.Request, res: express.Response, next: express.
   return res.type('json').status(400).end()
 }
 
-function createReqFiles (fieldName: string, storageDir: string, mimeTypes: { [ id: string ]: string }) {
+function createReqFiles (
+  fieldNames: string[],
+  mimeTypes: { [ id: string ]: string },
+  destinations: { [ fieldName: string ]: string }
+) {
   const storage = multer.diskStorage({
     destination: (req, file, cb) => {
-      cb(null, storageDir)
+      cb(null, destinations[file.fieldname])
     },
 
     filename: async (req, file, cb) => {
@@ -48,7 +52,15 @@ function createReqFiles (fieldName: string, storageDir: string, mimeTypes: { [ i
     }
   })
 
-  return multer({ storage }).fields([{ name: fieldName, maxCount: 1 }])
+  const fields = []
+  for (const fieldName of fieldNames) {
+    fields.push({
+      name: fieldName,
+      maxCount: 1
+    })
+  }
+
+  return multer({ storage }).fields(fields)
 }
 
 async function generateRandomString (size: number) {
index e531c4c394fd7377dcbbef9a6dde98906e72ad2f..91fbbde75e4bbc590dcb750f9b466b3f2423cef8 100644 (file)
@@ -182,6 +182,12 @@ const CONSTRAINTS_FIELDS = {
     NAME: { min: 3, max: 120 }, // Length
     TRUNCATED_DESCRIPTION: { min: 3, max: 250 }, // Length
     DESCRIPTION: { min: 3, max: 3000 }, // Length
+    IMAGE: {
+      EXTNAME: [ '.jpg', '.jpeg' ],
+      FILE_SIZE: {
+        max: 2 * 1024 * 1024 // 2MB
+      }
+    },
     EXTNAME: [ '.mp4', '.ogv', '.webm' ],
     INFO_HASH: { min: 40, max: 40 }, // Length, info hash is 20 bytes length but we represent it in hexadecimal so 20 * 2
     DURATION: { min: 1 }, // Number
@@ -285,7 +291,7 @@ const VIDEO_MIMETYPE_EXT = {
   'video/mp4': '.mp4'
 }
 
-const AVATAR_MIMETYPE_EXT = {
+const IMAGE_MIMETYPE_EXT = {
   'image/png': '.png',
   'image/jpg': '.jpg',
   'image/jpeg': '.jpg'
@@ -427,7 +433,7 @@ export {
   VIDEO_RATE_TYPES,
   VIDEO_MIMETYPE_EXT,
   USER_PASSWORD_RESET_LIFETIME,
-  AVATAR_MIMETYPE_EXT,
+  IMAGE_MIMETYPE_EXT,
   SCHEDULER_INTERVAL,
   JOB_COMPLETED_LIFETIME
 }
index 712de7d0d9b5ff09f08a3e434a6753f01e54a530..c3255d8ca1ac408e95b0b07b73626969abbda005 100644 (file)
@@ -12,7 +12,7 @@ import { logger } from '../../helpers/logger'
 import { createPrivateAndPublicKeys } from '../../helpers/peertube-crypto'
 import { doRequest, doRequestAndSaveToFile } from '../../helpers/requests'
 import { getUrlFromWebfinger } from '../../helpers/webfinger'
-import { AVATAR_MIMETYPE_EXT, CONFIG, sequelizeTypescript } from '../../initializers'
+import { IMAGE_MIMETYPE_EXT, CONFIG, sequelizeTypescript } from '../../initializers'
 import { AccountModel } from '../../models/account/account'
 import { ActorModel } from '../../models/activitypub/actor'
 import { AvatarModel } from '../../models/avatar/avatar'
@@ -147,10 +147,10 @@ async function fetchActorTotalItems (url: string) {
 
 async function fetchAvatarIfExists (actorJSON: ActivityPubActor) {
   if (
-    actorJSON.icon && actorJSON.icon.type === 'Image' && AVATAR_MIMETYPE_EXT[actorJSON.icon.mediaType] !== undefined &&
+    actorJSON.icon && actorJSON.icon.type === 'Image' && IMAGE_MIMETYPE_EXT[actorJSON.icon.mediaType] !== undefined &&
     isActivityPubUrlValid(actorJSON.icon.url)
   ) {
-    const extension = AVATAR_MIMETYPE_EXT[actorJSON.icon.mediaType]
+    const extension = IMAGE_MIMETYPE_EXT[actorJSON.icon.mediaType]
 
     const avatarName = uuidv4() + extension
     const destPath = join(CONFIG.STORAGE.AVATARS_DIR, avatarName)
index a365ed217e8e6a7c2224b117bdd389c880d15548..6d4fb907bab304226443f375805bb000018cb53a 100644 (file)
@@ -4,8 +4,18 @@ import { body, param, query } from 'express-validator/check'
 import { UserRight, VideoPrivacy } from '../../../shared'
 import { isBooleanValid, isIdOrUUIDValid, isIdValid, isUUIDValid } from '../../helpers/custom-validators/misc'
 import {
-  isVideoAbuseReasonValid, isVideoCategoryValid, isVideoDescriptionValid, isVideoExist, isVideoFile, isVideoLanguageValid,
-  isVideoLicenceValid, isVideoNameValid, isVideoPrivacyValid, isVideoRatingTypeValid, isVideoTagsValid
+  isVideoAbuseReasonValid,
+  isVideoCategoryValid,
+  isVideoDescriptionValid,
+  isVideoExist,
+  isVideoFile,
+  isVideoImage,
+  isVideoLanguageValid,
+  isVideoLicenceValid,
+  isVideoNameValid,
+  isVideoPrivacyValid,
+  isVideoRatingTypeValid,
+  isVideoTagsValid
 } from '../../helpers/custom-validators/videos'
 import { getDurationFromVideoFile } from '../../helpers/ffmpeg-utils'
 import { logger } from '../../helpers/logger'
@@ -22,6 +32,14 @@ const videosAddValidator = [
     'This file is not supported. Please, make sure it is of the following type : '
     + CONSTRAINTS_FIELDS.VIDEOS.EXTNAME.join(', ')
   ),
+  body('thumbnailfile').custom((value, { req }) => isVideoImage(req.files, 'thumbnailfile')).withMessage(
+    'This thumbnail file is not supported. Please, make sure it is of the following type : '
+    + CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ')
+  ),
+  body('previewfile').custom((value, { req }) => isVideoImage(req.files, 'previewfile')).withMessage(
+    'This preview file is not supported. Please, make sure it is of the following type : '
+    + CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ')
+  ),
   body('name').custom(isVideoNameValid).withMessage('Should have a valid name'),
   body('category').optional().custom(isVideoCategoryValid).withMessage('Should have a valid category'),
   body('licence').optional().custom(isVideoLicenceValid).withMessage('Should have a valid licence'),
@@ -37,6 +55,7 @@ const videosAddValidator = [
     logger.debug('Checking videosAdd parameters', { parameters: req.body, files: req.files })
 
     if (areValidationErrors(req, res)) return
+    if (areErrorsInVideoImageFiles(req, res)) return
 
     const videoFile: Express.Multer.File = req.files['videofile'][0]
     const user = res.locals.oauth.token.User
@@ -82,6 +101,14 @@ const videosAddValidator = [
 
 const videosUpdateValidator = [
   param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
+  body('thumbnailfile').custom((value, { req }) => isVideoImage(req.files, 'thumbnailfile')).withMessage(
+    'This thumbnail file is not supported. Please, make sure it is of the following type : '
+    + CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ')
+  ),
+  body('previewfile').custom((value, { req }) => isVideoImage(req.files, 'previewfile')).withMessage(
+    'This preview file is not supported. Please, make sure it is of the following type : '
+    + CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ')
+  ),
   body('name').optional().custom(isVideoNameValid).withMessage('Should have a valid name'),
   body('category').optional().custom(isVideoCategoryValid).withMessage('Should have a valid category'),
   body('licence').optional().custom(isVideoLicenceValid).withMessage('Should have a valid licence'),
@@ -96,6 +123,7 @@ const videosUpdateValidator = [
     logger.debug('Checking videosUpdate parameters', { parameters: req.body })
 
     if (areValidationErrors(req, res)) return
+    if (areErrorsInVideoImageFiles(req, res)) return
     if (!await isVideoExist(req.params.id, res)) return
 
     const video = res.locals.video
@@ -274,3 +302,22 @@ function checkUserCanDeleteVideo (user: UserModel, video: VideoModel, res: expre
 
   return true
 }
+
+function areErrorsInVideoImageFiles (req: express.Request, res: express.Response) {
+  // Files are optional
+  if (!req.files) return false
+
+  for (const imageField of [ 'thumbnail', 'preview' ]) {
+    if (!req.files[ imageField ]) continue
+
+    const imageFile = req.files[ imageField ][ 0 ] as Express.Multer.File
+    if (imageFile.size > CONSTRAINTS_FIELDS.VIDEOS.IMAGE.FILE_SIZE.max) {
+      res.status(400)
+        .send({ error: `The size of the ${imageField} is too big (>${CONSTRAINTS_FIELDS.VIDEOS.IMAGE.FILE_SIZE.max}).` })
+        .end()
+      return true
+    }
+  }
+
+  return false
+}
index 0fbc414c93d83aa607943df3426693890fee6371..d9dea0713e8fd42f21fc9458f7dcc8af9df93f5c 100644 (file)
@@ -7,7 +7,7 @@ import { UserRole } from '../../../../shared'
 
 import {
   createUser, flushTests, getMyUserInformation, getMyUserVideoRating, getUsersList, immutableAssign, killallServers, makeGetRequest,
-  makePostBodyRequest, makePostUploadRequest, makePutBodyRequest, registerUser, removeUser, runServer, ServerInfo, setAccessTokensToServers,
+  makePostBodyRequest, makeUploadRequest, makePutBodyRequest, registerUser, removeUser, runServer, ServerInfo, setAccessTokensToServers,
   updateUser, uploadVideo, userLogin
 } from '../../utils'
 import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params'
@@ -273,7 +273,7 @@ describe('Test users API validators', function () {
       const attaches = {
         'avatarfile': join(__dirname, '..', 'fixtures', 'video_short.mp4')
       }
-      await makePostUploadRequest({ url: server.url, path: path + '/me/avatar/pick', token: server.accessToken, fields, attaches })
+      await makeUploadRequest({ url: server.url, path: path + '/me/avatar/pick', token: server.accessToken, fields, attaches })
     })
 
     it('Should fail with a big file', async function () {
@@ -281,7 +281,7 @@ describe('Test users API validators', function () {
       const attaches = {
         'avatarfile': join(__dirname, '..', 'fixtures', 'avatar-big.png')
       }
-      await makePostUploadRequest({ url: server.url, path: path + '/me/avatar/pick', token: server.accessToken, fields, attaches })
+      await makeUploadRequest({ url: server.url, path: path + '/me/avatar/pick', token: server.accessToken, fields, attaches })
     })
 
     it('Should succeed with the correct params', async function () {
@@ -289,7 +289,7 @@ describe('Test users API validators', function () {
       const attaches = {
         'avatarfile': join(__dirname, '..', 'fixtures', 'avatar.png')
       }
-      await makePostUploadRequest({
+      await makeUploadRequest({
         url: server.url,
         path: path + '/me/avatar/pick',
         token: server.accessToken,
index f25e3f59536016a3d41d57a7cdbc98fe10376f24..aa30b721ba5d18b049085a0eaab07b022f59a3e9 100644 (file)
@@ -7,7 +7,7 @@ import { join } from 'path'
 import { VideoPrivacy } from '../../../../shared/models/videos/video-privacy.enum'
 import {
   createUser, flushTests, getMyUserInformation, getVideo, getVideosList, immutableAssign, killallServers, makeDeleteRequest,
-  makeGetRequest, makePostUploadRequest, makePutBodyRequest, removeVideo, runServer, ServerInfo, setAccessTokensToServers, userLogin
+  makeGetRequest, makeUploadRequest, makePutBodyRequest, removeVideo, runServer, ServerInfo, setAccessTokensToServers, userLogin
 } from '../../utils'
 import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params'
 
@@ -111,91 +111,91 @@ describe('Test videos API validator', function () {
     it('Should fail with nothing', async function () {
       const fields = {}
       const attaches = {}
-      await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
+      await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
     })
 
     it('Should fail without name', async function () {
       const fields = omit(baseCorrectParams, 'name')
       const attaches = baseCorrectAttaches
 
-      await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
+      await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
     })
 
     it('Should fail with a long name', async function () {
       const fields = immutableAssign(baseCorrectParams, { name: 'super'.repeat(65) })
       const attaches = baseCorrectAttaches
 
-      await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
+      await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
     })
 
     it('Should fail with a bad category', async function () {
       const fields = immutableAssign(baseCorrectParams, { category: 125 })
       const attaches = baseCorrectAttaches
 
-      await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
+      await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
     })
 
     it('Should fail with a bad licence', async function () {
       const fields = immutableAssign(baseCorrectParams, { licence: 125 })
       const attaches = baseCorrectAttaches
 
-      await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
+      await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
     })
 
     it('Should fail with a bad language', async function () {
       const fields = immutableAssign(baseCorrectParams, { language: 125 })
       const attaches = baseCorrectAttaches
 
-      await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
+      await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
     })
 
     it('Should fail without nsfw attribute', async function () {
       const fields = omit(baseCorrectParams, 'nsfw')
       const attaches = baseCorrectAttaches
 
-      await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
+      await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
     })
 
     it('Should fail with a bad nsfw attribute', async function () {
       const fields = immutableAssign(baseCorrectParams, { nsfw: 2 })
       const attaches = baseCorrectAttaches
 
-      await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
+      await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
     })
 
     it('Should fail without commentsEnabled attribute', async function () {
       const fields = omit(baseCorrectParams, 'commentsEnabled')
       const attaches = baseCorrectAttaches
 
-      await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
+      await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
     })
 
     it('Should fail with a bad commentsEnabled attribute', async function () {
       const fields = immutableAssign(baseCorrectParams, { commentsEnabled: 2 })
       const attaches = baseCorrectAttaches
 
-      await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
+      await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
     })
 
     it('Should fail with a long description', async function () {
       const fields = immutableAssign(baseCorrectParams, { description: 'super'.repeat(1500) })
       const attaches = baseCorrectAttaches
 
-      await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
+      await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
     })
 
     it('Should fail without a channel', async function () {
       const fields = omit(baseCorrectParams, 'channelId')
       const attaches = baseCorrectAttaches
 
-      await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
+      await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
     })
 
     it('Should fail with a bad channel', async function () {
       const fields = immutableAssign(baseCorrectParams, { channelId: 545454 })
       const attaches = baseCorrectAttaches
 
-      await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
+      await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
     })
 
     it('Should fail with another user channel', async function () {
@@ -212,34 +212,34 @@ describe('Test videos API validator', function () {
       const fields = immutableAssign(baseCorrectParams, { channelId: customChannelId })
       const attaches = baseCorrectAttaches
 
-      await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
+      await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
     })
 
     it('Should fail with too many tags', async function () {
       const fields = immutableAssign(baseCorrectParams, { tags: [ 'tag1', 'tag2', 'tag3', 'tag4', 'tag5', 'tag6' ] })
       const attaches = baseCorrectAttaches
 
-      await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
+      await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
     })
 
     it('Should fail with a tag length too low', async function () {
       const fields = immutableAssign(baseCorrectParams, { tags: [ 'tag1', 't' ] })
       const attaches = baseCorrectAttaches
 
-      await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
+      await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
     })
 
     it('Should fail with a tag length too big', async function () {
       const fields = immutableAssign(baseCorrectParams, { tags: [ 'tag1', 'my_super_tag_too_long_long_long_long_long_long' ] })
       const attaches = baseCorrectAttaches
 
-      await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
+      await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
     })
 
     it('Should fail without an input file', async function () {
       const fields = baseCorrectParams
       const attaches = {}
-      await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
+      await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
     })
 
     it('Should fail without an incorrect input file', async function () {
@@ -247,7 +247,47 @@ describe('Test videos API validator', function () {
       const attaches = {
         'videofile': join(__dirname, '..', 'fixtures', 'video_short_fake.webm')
       }
-      await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
+      await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
+    })
+
+    it('Should fail with an incorrect thumbnail file', async function () {
+      const fields = baseCorrectParams
+      const attaches = {
+        'thumbnailfile': join(__dirname, '..', 'fixtures', 'avatar.png'),
+        'videofile': join(__dirname, '..', 'fixtures', 'video_short_fake.webm')
+      }
+
+      await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
+    })
+
+    it('Should fail with a big thumbnail file', async function () {
+      const fields = baseCorrectParams
+      const attaches = {
+        'thumbnailfile': join(__dirname, '..', 'fixtures', 'avatar-big.png'),
+        'videofile': join(__dirname, '..', 'fixtures', 'video_short_fake.webm')
+      }
+
+      await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
+    })
+
+    it('Should fail with an incorrect preview file', async function () {
+      const fields = baseCorrectParams
+      const attaches = {
+        'previewfile': join(__dirname, '..', 'fixtures', 'avatar.png'),
+        'videofile': join(__dirname, '..', 'fixtures', 'video_short_fake.webm')
+      }
+
+      await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
+    })
+
+    it('Should fail with a big preview file', async function () {
+      const fields = baseCorrectParams
+      const attaches = {
+        'previewfile': join(__dirname, '..', 'fixtures', 'avatar-big.png'),
+        'videofile': join(__dirname, '..', 'fixtures', 'video_short_fake.webm')
+      }
+
+      await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
     })
 
     it('Should succeed with the correct parameters', async function () {
@@ -257,7 +297,7 @@ describe('Test videos API validator', function () {
 
       {
         const attaches = baseCorrectAttaches
-        await makePostUploadRequest({
+        await makeUploadRequest({
           url: server.url,
           path: path + '/upload',
           token: server.accessToken,
@@ -272,7 +312,7 @@ describe('Test videos API validator', function () {
           videofile: join(__dirname, '..', 'fixtures', 'video_short.mp4')
         })
 
-        await makePostUploadRequest({
+        await makeUploadRequest({
           url: server.url,
           path: path + '/upload',
           token: server.accessToken,
@@ -287,7 +327,7 @@ describe('Test videos API validator', function () {
           videofile: join(__dirname, '..', 'fixtures', 'video_short.ogv')
         })
 
-        await makePostUploadRequest({
+        await makeUploadRequest({
           url: server.url,
           path: path + '/upload',
           token: server.accessToken,
@@ -400,6 +440,70 @@ describe('Test videos API validator', function () {
       await makePutBodyRequest({ url: server.url, path: path + videoId, token: server.accessToken, fields })
     })
 
+    it('Should fail with an incorrect thumbnail file', async function () {
+      const fields = baseCorrectParams
+      const attaches = {
+        'thumbnailfile': join(__dirname, '..', 'fixtures', 'avatar.png')
+      }
+
+      await makeUploadRequest({
+        url: server.url,
+        method: 'PUT',
+        path: path + videoId,
+        token: server.accessToken,
+        fields,
+        attaches
+      })
+    })
+
+    it('Should fail with a big thumbnail file', async function () {
+      const fields = baseCorrectParams
+      const attaches = {
+        'thumbnailfile': join(__dirname, '..', 'fixtures', 'avatar-big.png')
+      }
+
+      await makeUploadRequest({
+        url: server.url,
+        method: 'PUT',
+        path: path + videoId,
+        token: server.accessToken,
+        fields,
+        attaches
+      })
+    })
+
+    it('Should fail with an incorrect preview file', async function () {
+      const fields = baseCorrectParams
+      const attaches = {
+        'previewfile': join(__dirname, '..', 'fixtures', 'avatar.png')
+      }
+
+      await makeUploadRequest({
+        url: server.url,
+        method: 'PUT',
+        path: path + videoId,
+        token: server.accessToken,
+        fields,
+        attaches
+      })
+    })
+
+    it('Should fail with a big preview file', async function () {
+      const fields = baseCorrectParams
+      const attaches = {
+        'previewfile': join(__dirname, '..', 'fixtures', 'avatar-big.png')
+      }
+
+      await makeUploadRequest({
+        url: server.url,
+        method: 'PUT',
+        path: path + videoId,
+        token: server.accessToken,
+        fields,
+        attaches
+      })
+    })
+
     it('Should fail with a video of another user')
 
     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 (file)
index 0000000..c40ece8
Binary files /dev/null and b/server/tests/api/fixtures/preview.jpg differ
diff --git a/server/tests/api/fixtures/thumbnail.jpg b/server/tests/api/fixtures/thumbnail.jpg
new file mode 100644 (file)
index 0000000..cc3af8a
Binary files /dev/null and b/server/tests/api/fixtures/thumbnail.jpg differ
index 0215b30118693ae06b093b2608577fd9e6c66694..3646fbb0febb417562e3c6531cc99c6721f84dd1 100644 (file)
@@ -137,7 +137,9 @@ describe('Test multiple servers', function () {
         nsfw: true,
         description: 'my super description for server 2',
         tags: [ 'tag1p2', 'tag2p2', 'tag3p2' ],
-        fixture: 'video_short2.webm'
+        fixture: 'video_short2.webm',
+        thumbnailfile: 'thumbnail.jpg',
+        previewfile: 'preview.jpg'
       }
       await uploadVideo(servers[1].url, userAccessToken, videoAttributes)
 
@@ -184,7 +186,9 @@ describe('Test multiple servers', function () {
               resolution: 720,
               size: 710000
             }
-          ]
+          ],
+          thumbnailfile: 'thumbnail',
+          previewfile: 'preview'
         }
 
         const res = await getVideosList(server.url)
@@ -521,7 +525,9 @@ describe('Test multiple servers', function () {
         language: 13,
         nsfw: true,
         description: 'my super description updated',
-        tags: [ 'tag_up_1', 'tag_up_2' ]
+        tags: [ 'tag_up_1', 'tag_up_2' ],
+        thumbnailfile: 'thumbnail.jpg',
+        previewfile: 'preview.jpg'
       }
 
       await updateVideo(servers[2].url, servers[2].accessToken, toRemove[0].id, attributes)
@@ -565,7 +571,9 @@ describe('Test multiple servers', function () {
               resolution: 720,
               size: 292677
             }
-          ]
+          ],
+          thumbnailfile: 'thumbnail',
+          previewfile: 'preview'
         }
         await completeVideoCheck(server.url, videoUpdated, checkAttributes)
       }
index 99d109bfebb13b0aa1d1c196ffd23c4b099349cd..24cbf59ca254c6f429fa15c60f6d06eff31ca72f 100644 (file)
@@ -1,6 +1,6 @@
 /* tslint:disable:no-unused-expression */
 
-import { join } from 'path'
+import { isAbsolute, join } from 'path'
 import * as request from 'supertest'
 import * as WebTorrent from 'webtorrent'
 import { readFileBufferPromise } from '../../../helpers/core-utils'
@@ -45,8 +45,8 @@ async function testImage (url: string, imageName: string, imagePath: string, ext
     const body = res.body
 
     const data = await readFileBufferPromise(join(__dirname, '..', '..', 'api', 'fixtures', imageName + extension))
-    const minLength = body.length - ((50 * body.length) / 100)
-    const maxLength = body.length + ((50 * body.length) / 100)
+    const minLength = body.length - ((20 * body.length) / 100)
+    const maxLength = body.length + ((20 * body.length) / 100)
 
     return data.length > minLength && data.length < maxLength
   } else {
@@ -55,6 +55,14 @@ async function testImage (url: string, imageName: string, imagePath: string, ext
   }
 }
 
+function buildAbsoluteFixturePath (path: string) {
+  if (isAbsolute(path)) {
+    return path
+  }
+
+  return join(__dirname, '..', '..', 'api', 'fixtures', path)
+}
+
 // ---------------------------------------------------------------------------
 
 export {
@@ -63,5 +71,6 @@ export {
   webtorrentAdd,
   immutableAssign,
   testImage,
+  buildAbsoluteFixturePath,
   root
 }
index 840072430997efed6a0641eb1e9a278d744b0aef..a9b1dff9a28e43a78f1290d4ccda236803028972 100644 (file)
@@ -1,4 +1,5 @@
 import * as request from 'supertest'
+import { buildAbsoluteFixturePath } from '../'
 
 function makeGetRequest (options: {
   url: string,
@@ -40,8 +41,9 @@ function makeDeleteRequest (options: {
     .expect(options.statusCodeExpected)
 }
 
-function makePostUploadRequest (options: {
+function makeUploadRequest (options: {
   url: string,
+  method?: 'POST' | 'PUT',
   path: string,
   token: string,
   fields: { [ fieldName: string ]: any },
@@ -50,9 +52,14 @@ function makePostUploadRequest (options: {
 }) {
   if (!options.statusCodeExpected) options.statusCodeExpected = 400
 
-  const req = request(options.url)
-                .post(options.path)
-                .set('Accept', 'application/json')
+  let req: request.Test
+  if (options.method === 'PUT') {
+    req = request(options.url).put(options.path)
+  } else {
+    req = request(options.url).post(options.path)
+  }
+
+  req.set('Accept', 'application/json')
 
   if (options.token) req.set('Authorization', 'Bearer ' + options.token)
 
@@ -70,7 +77,7 @@ function makePostUploadRequest (options: {
 
   Object.keys(options.attaches).forEach(attach => {
     const value = options.attaches[attach]
-    req.attach(attach, value)
+    req.attach(attach, buildAbsoluteFixturePath(value))
   })
 
   return req.expect(options.statusCodeExpected)
@@ -119,7 +126,7 @@ function makePutBodyRequest (options: {
 
 export {
   makeGetRequest,
-  makePostUploadRequest,
+  makeUploadRequest,
   makePostBodyRequest,
   makePutBodyRequest,
   makeDeleteRequest
index 9e33e679667e0649a29bf476f301f1e3d8dcd6d7..3c9d4624642b2154186b90183aa91d74a41285c9 100644 (file)
@@ -1,6 +1,6 @@
 import { isAbsolute, join } from 'path'
 import * as request from 'supertest'
-import { makePostBodyRequest, makePostUploadRequest, makePutBodyRequest } from '../'
+import { makePostBodyRequest, makeUploadRequest, makePutBodyRequest } from '../'
 
 import { UserRole } from '../../../../shared/index'
 
@@ -162,7 +162,7 @@ function updateMyAvatar (options: {
     filePath = join(__dirname, '..', '..', 'api', 'fixtures', options.fixture)
   }
 
-  return makePostUploadRequest({
+  return makeUploadRequest({
     url: options.url,
     path,
     token: options.accessToken,
index 9105b5f13650be2492c6989b636f2d929ffc337e..9d4267db84c00898d5f2ff8f73920c9538dc6078 100644 (file)
@@ -5,7 +5,16 @@ import { existsSync, readFile } from 'fs'
 import * as parseTorrent from 'parse-torrent'
 import { extname, isAbsolute, join } from 'path'
 import * as request from 'supertest'
-import { getMyUserInformation, makeGetRequest, root, ServerInfo, testImage } from '../'
+import {
+  buildAbsoluteFixturePath,
+  getMyUserInformation,
+  makeGetRequest,
+  makePutBodyRequest,
+  makeUploadRequest,
+  root,
+  ServerInfo,
+  testImage
+} from '../'
 import { VideoPrivacy } from '../../../../shared/models/videos'
 import { readdirPromise } from '../../../helpers/core-utils'
 import { VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../initializers'
@@ -23,6 +32,8 @@ type VideoAttributes = {
   channelId?: number
   privacy?: VideoPrivacy
   fixture?: string
+  thumbnailfile?: string
+  previewfile?: string
 }
 
 function getVideoCategories (url: string) {
@@ -228,8 +239,8 @@ async function uploadVideo (url: string, accessToken: string, videoAttributesArg
     defaultChannelId = res.body.videoChannels[0].id
   } catch (e) { /* empty */ }
 
-  // Default attributes
-  let attributes = {
+  // Override default attributes
+  const attributes = Object.assign({
     name: 'my super video',
     category: 5,
     licence: 4,
@@ -241,8 +252,7 @@ async function uploadVideo (url: string, accessToken: string, videoAttributesArg
     privacy: VideoPrivacy.PUBLIC,
     commentsEnabled: true,
     fixture: 'video_short.webm'
-  }
-  attributes = Object.assign(attributes, videoAttributesArg)
+  }, videoAttributesArg)
 
   const req = request(url)
               .post(path)
@@ -267,22 +277,22 @@ async function uploadVideo (url: string, accessToken: string, videoAttributesArg
     req.field('licence', attributes.licence.toString())
   }
 
-  for (let i = 0; i < attributes.tags.length; i++) {
-    req.field('tags[' + i + ']', attributes.tags[i])
+  if (attributes.thumbnailfile !== undefined) {
+    req.attach('thumbnailfile', buildAbsoluteFixturePath(attributes.thumbnailfile))
+  }
+  if (attributes.previewfile !== undefined) {
+    req.attach('previewfile', buildAbsoluteFixturePath(attributes.previewfile))
   }
 
-  let filePath = ''
-  if (isAbsolute(attributes.fixture)) {
-    filePath = attributes.fixture
-  } else {
-    filePath = join(__dirname, '..', '..', 'api', 'fixtures', attributes.fixture)
+  for (let i = 0; i < attributes.tags.length; i++) {
+    req.field('tags[' + i + ']', attributes.tags[i])
   }
 
-  return req.attach('videofile', filePath)
+  return req.attach('videofile', buildAbsoluteFixturePath(attributes.fixture))
             .expect(specialStatus)
 }
 
-function updateVideo (url: string, accessToken: string, id: number | string, attributes: VideoAttributes, specialStatus = 204) {
+function updateVideo (url: string, accessToken: string, id: number | string, attributes: VideoAttributes, statusCodeExpected = 204) {
   const path = '/api/v1/videos/' + id
   const body = {}
 
@@ -296,12 +306,30 @@ function updateVideo (url: string, accessToken: string, id: number | string, att
   if (attributes.tags) body['tags'] = attributes.tags
   if (attributes.privacy) body['privacy'] = attributes.privacy
 
-  return request(url)
-          .put(path)
-          .send(body)
-          .set('Accept', 'application/json')
-          .set('Authorization', 'Bearer ' + accessToken)
-          .expect(specialStatus)
+  // Upload request
+  if (attributes.thumbnailfile || attributes.previewfile) {
+    const attaches: any = {}
+    if (attributes.thumbnailfile) attaches.thumbnailfile = attributes.thumbnailfile
+    if (attributes.previewfile) attaches.previewfile = attributes.previewfile
+
+    return makeUploadRequest({
+      url,
+      method: 'PUT',
+      path,
+      token: accessToken,
+      fields: body,
+      attaches,
+      statusCodeExpected
+    })
+  }
+
+  return makePutBodyRequest({
+    url,
+    path,
+    fields: body,
+    token: accessToken,
+    statusCodeExpected
+  })
 }
 
 function rateVideo (url: string, accessToken: string, id: number, rating: string, specialStatus = 204) {
@@ -355,7 +383,9 @@ async function completeVideoCheck (
     files: {
       resolution: number
       size: number
-    }[]
+    }[],
+    thumbnailfile?: string
+    previewfile?: string
   }
 ) {
   if (!attributes.likes) attributes.likes = 0
@@ -414,8 +444,15 @@ async function completeVideoCheck (
     const maxSize = attributeFile.size + ((10 * attributeFile.size) / 100)
     expect(file.size).to.be.above(minSize).and.below(maxSize)
 
-    const test = await testImage(url, attributes.fixture, videoDetails.thumbnailPath)
-    expect(test).to.equal(true)
+    {
+      const test = await testImage(url, attributes.thumbnailfile || attributes.fixture, videoDetails.thumbnailPath)
+      expect(test).to.equal(true)
+    }
+
+    if (attributes.previewfile) {
+      const test = await testImage(url, attributes.previewfile, videoDetails.previewPath)
+      expect(test).to.equal(true)
+    }
 
     const torrent = await webtorrentAdd(magnetUri, true)
     expect(torrent.files).to.be.an('array')
index 96bce29b5d2a36f3903fdf58ad8a14c629007ae3..ccbc710299294c2f222d23e80beb6a31c8c74997 100644 (file)
@@ -1,7 +1,5 @@
 import * as program from 'commander'
-import { createWriteStream } from 'fs'
 import { join } from 'path'
-import { cursorTo } from 'readline'
 import * as youtubeDL from 'youtube-dl'
 import { VideoPrivacy } from '../../shared/models/videos'
 import { unlinkPromise } from '../helpers/core-utils'