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 @@ -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' 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) { }) 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) }) 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 @@ 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) 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' 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 = { 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 @@ +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 } 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 @@ -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') } // --------------------------------------------------------------------------- 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 { 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 + return isFileValid(files, videoFileTypesRegex, 'videofile') +} - // 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 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 index 000000000..ba57b5812 --- /dev/null +++ b/server/helpers/image-utils.ts @@ -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 +} 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. 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) { 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 = { 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 } 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' 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) 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' 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 +} 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' 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, 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' 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 index 000000000..c40ece838 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 index 000000000..cc3af8a4a Binary files /dev/null and b/server/tests/api/fixtures/thumbnail.jpg 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 () { 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) } 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 @@ /* 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 } 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 @@ 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 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 @@ 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, 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' 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()) } + if (attributes.thumbnailfile !== undefined) { + req.attach('thumbnailfile', buildAbsoluteFixturePath(attributes.thumbnailfile)) + } + if (attributes.previewfile !== undefined) { + req.attach('previewfile', buildAbsoluteFixturePath(attributes.previewfile)) + } + for (let i = 0; i < attributes.tags.length; i++) { req.field('tags[' + i + ']', attributes.tags[i]) } - let filePath = '' - if (isAbsolute(attributes.fixture)) { - filePath = attributes.fixture - } else { - filePath = join(__dirname, '..', '..', 'api', 'fixtures', attributes.fixture) - } - - 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') 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 @@ 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'