From edb4ffc7e0b13659d7c73b120f2c87b27e4c26a1 Mon Sep 17 00:00:00 2001 From: Felix Ableitner Date: Mon, 8 Oct 2018 09:26:04 -0500 Subject: Set bitrate limits for transcoding (fixes #638) (#1135) * Set bitrate limits for transcoding (fixes #638) * added optimization script and test, changed stuff * fix test, improve docs * re-add optimize-old-videos script * added documentation * Don't optimize videos without valid UUID, or redundancy videos * move getUUIDFromFilename * fix tests? * update torrent and file size, some more fixes/improvements * use higher bitrate for high fps video, adjust bitrates * add test video * don't throw error if resolution is undefined * generate test fixture on the fly * use random noise video for bitrate test, add promise * shorten test video to avoid timeout * use existing function to optimize video * various fixes * increase test timeout * limit test fixture size, add link * test fixes * add await * more test fixes, add -b:v parameter * replace ffmpeg wiki link * fix ffmpeg params * fix unit test * add test fixture to .gitgnore * add video transcoding fps model * add missing file --- server/helpers/ffmpeg-utils.ts | 21 +++++++++++++++++++-- server/helpers/utils.ts | 17 ++++++++++++++++- 2 files changed, 35 insertions(+), 3 deletions(-) (limited to 'server/helpers') diff --git a/server/helpers/ffmpeg-utils.ts b/server/helpers/ffmpeg-utils.ts index 22bc25476..8e4471173 100644 --- a/server/helpers/ffmpeg-utils.ts +++ b/server/helpers/ffmpeg-utils.ts @@ -1,6 +1,6 @@ import * as ffmpeg from 'fluent-ffmpeg' import { join } from 'path' -import { VideoResolution } from '../../shared/models/videos' +import { VideoResolution, getTargetBitrate } from '../../shared/models/videos' import { CONFIG, FFMPEG_NICE, VIDEO_TRANSCODING_FPS } from '../initializers' import { processImage } from './image-utils' import { logger } from './logger' @@ -55,6 +55,16 @@ async function getVideoFileFPS (path: string) { return 0 } +async function getVideoFileBitrate (path: string) { + return new Promise((res, rej) => { + ffmpeg.ffprobe(path, (err, metadata) => { + if (err) return rej(err) + + return res(metadata.format.bit_rate) + }) + }) +} + function getDurationFromVideoFile (path: string) { return new Promise((res, rej) => { ffmpeg.ffprobe(path, (err, metadata) => { @@ -138,6 +148,12 @@ function transcode (options: TranscodeOptions) { command = command.withFPS(fps) } + // Constrained Encoding (VBV) + // https://slhck.info/video/2017/03/01/rate-control.html + // https://trac.ffmpeg.org/wiki/Limiting%20the%20output%20bitrate + const targetBitrate = getTargetBitrate(options.resolution, fps, VIDEO_TRANSCODING_FPS) + command.outputOptions([`-b:v ${ targetBitrate }`, `-maxrate ${ targetBitrate }`, `-bufsize ${ targetBitrate * 2 }`]) + command .on('error', (err, stdout, stderr) => { logger.error('Error in transcoding job.', { stdout, stderr }) @@ -157,7 +173,8 @@ export { transcode, getVideoFileFPS, computeResolutionsToTranscode, - audio + audio, + getVideoFileBitrate } // --------------------------------------------------------------------------- diff --git a/server/helpers/utils.ts b/server/helpers/utils.ts index 6228fec04..39afb4e7b 100644 --- a/server/helpers/utils.ts +++ b/server/helpers/utils.ts @@ -77,6 +77,20 @@ async function getVersion () { return require('../../../package.json').version } +/** + * From a filename like "ede4cba5-742b-46fa-a388-9a6eb3a3aeb3.mp4", returns + * only the "ede4cba5-742b-46fa-a388-9a6eb3a3aeb3" part. If the filename does + * not contain a UUID, returns null. + */ +function getUUIDFromFilename (filename: string) { + const regex = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/ + const result = filename.match(regex) + + if (!result || Array.isArray(result) === false) return null + + return result[0] +} + // --------------------------------------------------------------------------- export { @@ -86,5 +100,6 @@ export { getSecureTorrentName, getServerActor, getVersion, - generateVideoTmpPath + generateVideoTmpPath, + getUUIDFromFilename } -- cgit v1.2.3 From e1d7b98bc762bf3a5e6590f827f67187713fb27e Mon Sep 17 00:00:00 2001 From: Rigel Kent Date: Mon, 8 Oct 2018 21:13:13 +0200 Subject: fix automatic bitrate adjustment The regression was introduced in edb4ffc7e0b13659d7c73b120f2c87b27e4c26a1 and forced a fixed bitrate per resolution --- server/helpers/ffmpeg-utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'server/helpers') diff --git a/server/helpers/ffmpeg-utils.ts b/server/helpers/ffmpeg-utils.ts index 8e4471173..a964abdd4 100644 --- a/server/helpers/ffmpeg-utils.ts +++ b/server/helpers/ffmpeg-utils.ts @@ -152,7 +152,7 @@ function transcode (options: TranscodeOptions) { // https://slhck.info/video/2017/03/01/rate-control.html // https://trac.ffmpeg.org/wiki/Limiting%20the%20output%20bitrate const targetBitrate = getTargetBitrate(options.resolution, fps, VIDEO_TRANSCODING_FPS) - command.outputOptions([`-b:v ${ targetBitrate }`, `-maxrate ${ targetBitrate }`, `-bufsize ${ targetBitrate * 2 }`]) + command.outputOptions([`-maxrate ${ targetBitrate }`, `-bufsize ${ targetBitrate * 2 }`]) command .on('error', (err, stdout, stderr) => { -- cgit v1.2.3 From 729bb184819ddda1d7313da0c30b3397e5689721 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Wed, 10 Oct 2018 08:51:58 +0200 Subject: Add more headers to broadcast/unicast --- server/helpers/core-utils.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'server/helpers') diff --git a/server/helpers/core-utils.ts b/server/helpers/core-utils.ts index 00bc0bdda..224e4fe92 100644 --- a/server/helpers/core-utils.ts +++ b/server/helpers/core-utils.ts @@ -5,7 +5,7 @@ import * as bcrypt from 'bcrypt' import * as createTorrent from 'create-torrent' -import { createHash, pseudoRandomBytes } from 'crypto' +import { createHash, HexBase64Latin1Encoding, pseudoRandomBytes } from 'crypto' import { isAbsolute, join } from 'path' import * as pem from 'pem' import { URL } from 'url' @@ -126,8 +126,8 @@ function peertubeTruncate (str: string, maxLength: number) { return truncate(str, options) } -function sha256 (str: string) { - return createHash('sha256').update(str).digest('hex') +function sha256 (str: string, encoding: HexBase64Latin1Encoding = 'hex') { + return createHash('sha256').update(str).digest(encoding) } function promisify0 (func: (cb: (err: any, result: A) => void) => void): () => Promise { -- cgit v1.2.3 From 1cd3facc3de899ac864e979cd6d6a704b712cce3 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Wed, 10 Oct 2018 11:46:50 +0200 Subject: Add ability to list all local videos Including private/unlisted for moderators/admins --- server/helpers/custom-validators/videos.ts | 9 +++++++-- server/helpers/express-utils.ts | 3 +-- 2 files changed, 8 insertions(+), 4 deletions(-) (limited to 'server/helpers') diff --git a/server/helpers/custom-validators/videos.ts b/server/helpers/custom-validators/videos.ts index 714f7ac95..a13b09ac8 100644 --- a/server/helpers/custom-validators/videos.ts +++ b/server/helpers/custom-validators/videos.ts @@ -3,7 +3,7 @@ import 'express-validator' import { values } from 'lodash' import 'multer' import * as validator from 'validator' -import { UserRight, VideoPrivacy, VideoRateType } from '../../../shared' +import { UserRight, VideoFilter, VideoPrivacy, VideoRateType } from '../../../shared' import { CONSTRAINTS_FIELDS, VIDEO_CATEGORIES, @@ -22,6 +22,10 @@ import { fetchVideo, VideoFetchType } from '../video' const VIDEOS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEOS +function isVideoFilterValid (filter: VideoFilter) { + return filter === 'local' || filter === 'all-local' +} + function isVideoCategoryValid (value: any) { return value === null || VIDEO_CATEGORIES[ value ] !== undefined } @@ -225,5 +229,6 @@ export { isVideoExist, isVideoImage, isVideoChannelOfAccountExist, - isVideoSupportValid + isVideoSupportValid, + isVideoFilterValid } diff --git a/server/helpers/express-utils.ts b/server/helpers/express-utils.ts index 8a9cee8c5..162fe2244 100644 --- a/server/helpers/express-utils.ts +++ b/server/helpers/express-utils.ts @@ -2,7 +2,6 @@ import * as express from 'express' import * as multer from 'multer' import { CONFIG, REMOTE_SCHEME } from '../initializers' import { logger } from './logger' -import { User } from '../../shared/models/users' import { deleteFileAsync, generateRandomString } from './utils' import { extname } from 'path' import { isArray } from './custom-validators/misc' @@ -101,7 +100,7 @@ function createReqFiles ( } function isUserAbleToSearchRemoteURI (res: express.Response) { - const user: User = res.locals.oauth ? res.locals.oauth.token.User : undefined + const user: UserModel = res.locals.oauth ? res.locals.oauth.token.User : undefined return CONFIG.SEARCH.REMOTE_URI.ANONYMOUS === true || (CONFIG.SEARCH.REMOTE_URI.USERS === true && user !== undefined) -- cgit v1.2.3 From 0e5ff97f6fdf9a4cebe5a15f5a390380465803ad Mon Sep 17 00:00:00 2001 From: BRAINS YUM <43896676+McFlat@users.noreply.github.com> Date: Sat, 13 Oct 2018 01:43:55 -0500 Subject: add parseBytes utility function and tests (#1239) * add parseBytes utility function and tests make it parse TB MB fix parseBytes; * 1024 test bytes too, and make parseByte to parse quotas add test in travis.sh in misc * fix parseBytes and test to pass linting --- server/helpers/core-utils.ts | 48 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) (limited to 'server/helpers') diff --git a/server/helpers/core-utils.ts b/server/helpers/core-utils.ts index 224e4fe92..84e33c0e9 100644 --- a/server/helpers/core-utils.ts +++ b/server/helpers/core-utils.ts @@ -21,6 +21,7 @@ const timeTable = { week: 3600000 * 24 * 7, month: 3600000 * 24 * 30 } + export function parseDuration (duration: number | string): number { if (typeof duration === 'number') return duration @@ -41,6 +42,53 @@ export function parseDuration (duration: number | string): number { throw new Error('Duration could not be properly parsed') } +export function parseBytes (value: string | number): number { + if (typeof value === 'number') return value + + const tgm = /^(\d+)\s*TB\s*(\d+)\s*GB\s*(\d+)\s*MB$/ + const tg = /^(\d+)\s*TB\s*(\d+)\s*GB$/ + const tm = /^(\d+)\s*TB\s*(\d+)\s*MB$/ + const gm = /^(\d+)\s*GB\s*(\d+)\s*MB$/ + const t = /^(\d+)\s*TB$/ + const g = /^(\d+)\s*GB$/ + const m = /^(\d+)\s*MB$/ + const b = /^(\d+)\s*B$/ + let match + + if (value.match(tgm)) { + match = value.match(tgm) + return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024 + + parseInt(match[2], 10) * 1024 * 1024 * 1024 + + parseInt(match[3], 10) * 1024 * 1024 + } else if (value.match(tg)) { + match = value.match(tg) + return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024 + + parseInt(match[2], 10) * 1024 * 1024 * 1024 + } else if (value.match(tm)) { + match = value.match(tm) + return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024 + + parseInt(match[2], 10) * 1024 * 1024 + } else if (value.match(gm)) { + match = value.match(gm) + return parseInt(match[1], 10) * 1024 * 1024 * 1024 + + parseInt(match[2], 10) * 1024 * 1024 + } else if (value.match(t)) { + match = value.match(t) + return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024 + } else if (value.match(g)) { + match = value.match(g) + return parseInt(match[1], 10) * 1024 * 1024 * 1024 + } else if (value.match(m)) { + match = value.match(m) + return parseInt(match[1], 10) * 1024 * 1024 + } else if (value.match(b)) { + match = value.match(b) + return parseInt(match[1], 10) * 1024 + } else { + return parseInt(value, 10) + } +} + function sanitizeUrl (url: string) { const urlObject = new URL(url) -- cgit v1.2.3 From 64cc5e8575fda47b281ae20abf0020e27fc8ce7c Mon Sep 17 00:00:00 2001 From: Rigel Kent Date: Fri, 5 Oct 2018 15:17:34 +0200 Subject: add webtorrent opt-out settings - add a key in localstorage to remember the opt-out - add a user setting --- server/helpers/custom-validators/users.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'server/helpers') diff --git a/server/helpers/custom-validators/users.ts b/server/helpers/custom-validators/users.ts index 90fc74a48..2024d4a22 100644 --- a/server/helpers/custom-validators/users.ts +++ b/server/helpers/custom-validators/users.ts @@ -1,7 +1,7 @@ import 'express-validator' import * as validator from 'validator' import { UserRole } from '../../../shared' -import { CONSTRAINTS_FIELDS, NSFW_POLICY_TYPES } from '../../initializers' +import { CONSTRAINTS_FIELDS, NSFW_POLICY_TYPES, WEBTORRENT_POLICY_TYPES } from '../../initializers' import { exists, isFileValid, isBooleanValid } from './misc' import { values } from 'lodash' @@ -42,6 +42,11 @@ function isUserNSFWPolicyValid (value: any) { return exists(value) && nsfwPolicies.indexOf(value) !== -1 } +const webTorrentPolicies = values(WEBTORRENT_POLICY_TYPES) +function isUserWebTorrentPolicyValid (value: any) { + return exists(value) && webTorrentPolicies.indexOf(value) !== -1 +} + function isUserAutoPlayVideoValid (value: any) { return isBooleanValid(value) } @@ -78,6 +83,7 @@ export { isUserUsernameValid, isUserEmailVerifiedValid, isUserNSFWPolicyValid, + isUserWebTorrentPolicyValid, isUserAutoPlayVideoValid, isUserDisplayNameValid, isUserDescriptionValid, -- cgit v1.2.3 From ed638e5325096ef580da20f370ac61c59cd48cf7 Mon Sep 17 00:00:00 2001 From: Rigel Kent Date: Fri, 12 Oct 2018 18:12:39 +0200 Subject: move to boolean switch --- server/helpers/custom-validators/users.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'server/helpers') diff --git a/server/helpers/custom-validators/users.ts b/server/helpers/custom-validators/users.ts index 2024d4a22..1cb5e5b0f 100644 --- a/server/helpers/custom-validators/users.ts +++ b/server/helpers/custom-validators/users.ts @@ -1,7 +1,7 @@ import 'express-validator' import * as validator from 'validator' import { UserRole } from '../../../shared' -import { CONSTRAINTS_FIELDS, NSFW_POLICY_TYPES, WEBTORRENT_POLICY_TYPES } from '../../initializers' +import { CONSTRAINTS_FIELDS, NSFW_POLICY_TYPES } from '../../initializers' import { exists, isFileValid, isBooleanValid } from './misc' import { values } from 'lodash' @@ -42,9 +42,8 @@ function isUserNSFWPolicyValid (value: any) { return exists(value) && nsfwPolicies.indexOf(value) !== -1 } -const webTorrentPolicies = values(WEBTORRENT_POLICY_TYPES) -function isUserWebTorrentPolicyValid (value: any) { - return exists(value) && webTorrentPolicies.indexOf(value) !== -1 +function isUserWebTorrentEnabledValid (value: any) { + return isBooleanValid(value) } function isUserAutoPlayVideoValid (value: any) { @@ -83,7 +82,7 @@ export { isUserUsernameValid, isUserEmailVerifiedValid, isUserNSFWPolicyValid, - isUserWebTorrentPolicyValid, + isUserWebTorrentEnabledValid, isUserAutoPlayVideoValid, isUserDisplayNameValid, isUserDescriptionValid, -- cgit v1.2.3 From 7ad9b9846c44d198a736183fb186c2039f5236b5 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 12 Oct 2018 15:26:04 +0200 Subject: Add ability for users to block an account/instance on server side --- server/helpers/utils.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'server/helpers') diff --git a/server/helpers/utils.ts b/server/helpers/utils.ts index 39afb4e7b..049c3f8bc 100644 --- a/server/helpers/utils.ts +++ b/server/helpers/utils.ts @@ -40,7 +40,10 @@ const getServerActor = memoizee(async function () { const application = await ApplicationModel.load() if (!application) throw Error('Could not load Application from database.') - return application.Account.Actor + const actor = application.Account.Actor + actor.Account = application.Account + + return actor }) function generateVideoTmpPath (target: string | ParseTorrent) { -- cgit v1.2.3 From bcf21a376f1e26cb3e74236e4cc41909310d4c32 Mon Sep 17 00:00:00 2001 From: Felix Ableitner Date: Wed, 17 Oct 2018 03:15:38 -0500 Subject: Set keyframe interval for transcoding (fixes #1147) (#1231) * Set keyframe interval for transcoding (fixes #1147) * remove -maxrate and old bitrate setter * pass fps as parameter * set type for ffmpeg param * assign ffmpeg object --- server/helpers/ffmpeg-utils.ts | 52 +++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 24 deletions(-) (limited to 'server/helpers') diff --git a/server/helpers/ffmpeg-utils.ts b/server/helpers/ffmpeg-utils.ts index a964abdd4..17f35fe8d 100644 --- a/server/helpers/ffmpeg-utils.ts +++ b/server/helpers/ffmpeg-utils.ts @@ -116,28 +116,27 @@ type TranscodeOptions = { function transcode (options: TranscodeOptions) { return new Promise(async (res, rej) => { + let fps = await getVideoFileFPS(options.inputPath) + // On small/medium resolutions, limit FPS + if (options.resolution !== undefined && + options.resolution < VIDEO_TRANSCODING_FPS.KEEP_ORIGIN_FPS_RESOLUTION_MIN && + fps > VIDEO_TRANSCODING_FPS.AVERAGE) { + fps = VIDEO_TRANSCODING_FPS.AVERAGE + } + let command = ffmpeg(options.inputPath, { niceness: FFMPEG_NICE.TRANSCODING }) .output(options.outputPath) - .preset(standard) + command = await presetH264(command, options.resolution, fps) if (CONFIG.TRANSCODING.THREADS > 0) { // if we don't set any threads ffmpeg will chose automatically command = command.outputOption('-threads ' + CONFIG.TRANSCODING.THREADS) } - let fps = await getVideoFileFPS(options.inputPath) if (options.resolution !== undefined) { // '?x720' or '720x?' for example const size = options.isPortraitMode === true ? `${options.resolution}x?` : `?x${options.resolution}` command = command.size(size) - - // On small/medium resolutions, limit FPS - if ( - options.resolution < VIDEO_TRANSCODING_FPS.KEEP_ORIGIN_FPS_RESOLUTION_MIN && - fps > VIDEO_TRANSCODING_FPS.AVERAGE - ) { - fps = VIDEO_TRANSCODING_FPS.AVERAGE - } } if (fps) { @@ -148,12 +147,6 @@ function transcode (options: TranscodeOptions) { command = command.withFPS(fps) } - // Constrained Encoding (VBV) - // https://slhck.info/video/2017/03/01/rate-control.html - // https://trac.ffmpeg.org/wiki/Limiting%20the%20output%20bitrate - const targetBitrate = getTargetBitrate(options.resolution, fps, VIDEO_TRANSCODING_FPS) - command.outputOptions([`-maxrate ${ targetBitrate }`, `-bufsize ${ targetBitrate * 2 }`]) - command .on('error', (err, stdout, stderr) => { logger.error('Error in transcoding job.', { stdout, stderr }) @@ -199,9 +192,9 @@ function getVideoFileStream (path: string) { * and quality. Superfast and ultrafast will give you better * performance, but then quality is noticeably worse. */ -function veryfast (_ffmpeg) { - _ffmpeg - .preset(standard) +async function presetH264VeryFast (ffmpeg: ffmpeg, resolution: VideoResolution, fps: number): ffmpeg { + const localFfmpeg = await presetH264(ffmpeg, resolution, fps) + localFfmpeg .outputOption('-preset:v veryfast') .outputOption(['--aq-mode=2', '--aq-strength=1.3']) /* @@ -220,9 +213,9 @@ function veryfast (_ffmpeg) { /** * A preset optimised for a stillimage audio video */ -function audio (_ffmpeg) { - _ffmpeg - .preset(veryfast) +async function presetStillImageWithAudio (ffmpeg: ffmpeg, resolution: VideoResolution, fps: number): ffmpeg { + const localFfmpeg = await presetH264VeryFast(ffmpeg, resolution, fps) + localFfmpeg .outputOption('-tune stillimage') } @@ -290,8 +283,8 @@ namespace audio { * As for the audio, quality '5' is the highest and ensures 96-112kbps/channel * See https://trac.ffmpeg.org/wiki/Encode/AAC#fdk_vbr */ -async function standard (_ffmpeg) { - let localFfmpeg = _ffmpeg +async function presetH264 (ffmpeg: ffmpeg, resolution: VideoResolution, fps: number): ffmpeg { + let localFfmpeg = ffmpeg .format('mp4') .videoCodec('libx264') .outputOption('-level 3.1') // 3.1 is the minimal ressource allocation for our highest supported resolution @@ -324,5 +317,16 @@ async function standard (_ffmpeg) { if (bitrate !== undefined) return localFfmpeg.audioBitrate(bitrate) + // Constrained Encoding (VBV) + // https://slhck.info/video/2017/03/01/rate-control.html + // https://trac.ffmpeg.org/wiki/Limiting%20the%20output%20bitrate + const targetBitrate = getTargetBitrate(resolution, fps, VIDEO_TRANSCODING_FPS) + localFfmpeg.outputOptions([`-maxrate ${ targetBitrate }`, `-bufsize ${ targetBitrate * 2 }`]) + + // Keyframe interval of 2 seconds for faster seeking and resolution switching. + // https://streaminglearningcenter.com/blogs/whats-the-right-keyframe-interval.html + // https://superuser.com/a/908325 + localFfmpeg.outputOption(`-g ${ fps * 2 }`) + return localFfmpeg } -- cgit v1.2.3 From 0229b014e0101844df028342b8d4dd9ae4e887a4 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Wed, 17 Oct 2018 13:10:58 +0200 Subject: Fix tests --- server/helpers/ffmpeg-utils.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) (limited to 'server/helpers') diff --git a/server/helpers/ffmpeg-utils.ts b/server/helpers/ffmpeg-utils.ts index 17f35fe8d..a53a7bae4 100644 --- a/server/helpers/ffmpeg-utils.ts +++ b/server/helpers/ffmpeg-utils.ts @@ -118,11 +118,13 @@ function transcode (options: TranscodeOptions) { return new Promise(async (res, rej) => { let fps = await getVideoFileFPS(options.inputPath) // On small/medium resolutions, limit FPS - if (options.resolution !== undefined && - options.resolution < VIDEO_TRANSCODING_FPS.KEEP_ORIGIN_FPS_RESOLUTION_MIN && - fps > VIDEO_TRANSCODING_FPS.AVERAGE) { - fps = VIDEO_TRANSCODING_FPS.AVERAGE - } + // if ( + // options.resolution !== undefined && + // options.resolution < VIDEO_TRANSCODING_FPS.KEEP_ORIGIN_FPS_RESOLUTION_MIN && + // fps > VIDEO_TRANSCODING_FPS.AVERAGE + // ) { + // fps = VIDEO_TRANSCODING_FPS.AVERAGE + // } let command = ffmpeg(options.inputPath, { niceness: FFMPEG_NICE.TRANSCODING }) .output(options.outputPath) @@ -321,12 +323,12 @@ async function presetH264 (ffmpeg: ffmpeg, resolution: VideoResolution, fps: num // https://slhck.info/video/2017/03/01/rate-control.html // https://trac.ffmpeg.org/wiki/Limiting%20the%20output%20bitrate const targetBitrate = getTargetBitrate(resolution, fps, VIDEO_TRANSCODING_FPS) - localFfmpeg.outputOptions([`-maxrate ${ targetBitrate }`, `-bufsize ${ targetBitrate * 2 }`]) + localFfmpeg = localFfmpeg.outputOptions([`-maxrate ${ targetBitrate }`, `-bufsize ${ targetBitrate * 2 }`]) // Keyframe interval of 2 seconds for faster seeking and resolution switching. // https://streaminglearningcenter.com/blogs/whats-the-right-keyframe-interval.html // https://superuser.com/a/908325 - localFfmpeg.outputOption(`-g ${ fps * 2 }`) + localFfmpeg = localFfmpeg.outputOption(`-g ${ fps * 2 }`) return localFfmpeg } -- cgit v1.2.3 From 28e51e831bd121f063600a597d7b02f8fd846de9 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Wed, 17 Oct 2018 17:58:21 +0200 Subject: Oup's --- server/helpers/ffmpeg-utils.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'server/helpers') diff --git a/server/helpers/ffmpeg-utils.ts b/server/helpers/ffmpeg-utils.ts index a53a7bae4..037bf703a 100644 --- a/server/helpers/ffmpeg-utils.ts +++ b/server/helpers/ffmpeg-utils.ts @@ -118,13 +118,13 @@ function transcode (options: TranscodeOptions) { return new Promise(async (res, rej) => { let fps = await getVideoFileFPS(options.inputPath) // On small/medium resolutions, limit FPS - // if ( - // options.resolution !== undefined && - // options.resolution < VIDEO_TRANSCODING_FPS.KEEP_ORIGIN_FPS_RESOLUTION_MIN && - // fps > VIDEO_TRANSCODING_FPS.AVERAGE - // ) { - // fps = VIDEO_TRANSCODING_FPS.AVERAGE - // } + if ( + options.resolution !== undefined && + options.resolution < VIDEO_TRANSCODING_FPS.KEEP_ORIGIN_FPS_RESOLUTION_MIN && + fps > VIDEO_TRANSCODING_FPS.AVERAGE + ) { + fps = VIDEO_TRANSCODING_FPS.AVERAGE + } let command = ffmpeg(options.inputPath, { niceness: FFMPEG_NICE.TRANSCODING }) .output(options.outputPath) -- cgit v1.2.3 From e27ff5da6ed7bc1f56f50f862b80fb0c7d8a6d98 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Thu, 18 Oct 2018 08:48:24 +0200 Subject: AP mimeType -> mediaType --- server/helpers/custom-validators/activitypub/videos.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'server/helpers') diff --git a/server/helpers/custom-validators/activitypub/videos.ts b/server/helpers/custom-validators/activitypub/videos.ts index f88d26561..95fe824b9 100644 --- a/server/helpers/custom-validators/activitypub/videos.ts +++ b/server/helpers/custom-validators/activitypub/videos.ts @@ -81,19 +81,20 @@ function isRemoteVideoUrlValid (url: any) { return url.type === 'Link' && ( - ACTIVITY_PUB.URL_MIME_TYPES.VIDEO.indexOf(url.mimeType) !== -1 && + // TODO: remove mimeType (backward compatibility, introduced in v1.1.0) + ACTIVITY_PUB.URL_MIME_TYPES.VIDEO.indexOf(url.mediaType || url.mimeType) !== -1 && isActivityPubUrlValid(url.href) && validator.isInt(url.height + '', { min: 0 }) && validator.isInt(url.size + '', { min: 0 }) && (!url.fps || validator.isInt(url.fps + '', { min: -1 })) ) || ( - ACTIVITY_PUB.URL_MIME_TYPES.TORRENT.indexOf(url.mimeType) !== -1 && + ACTIVITY_PUB.URL_MIME_TYPES.TORRENT.indexOf(url.mediaType || url.mimeType) !== -1 && isActivityPubUrlValid(url.href) && validator.isInt(url.height + '', { min: 0 }) ) || ( - ACTIVITY_PUB.URL_MIME_TYPES.MAGNET.indexOf(url.mimeType) !== -1 && + ACTIVITY_PUB.URL_MIME_TYPES.MAGNET.indexOf(url.mediaType || url.mimeType) !== -1 && validator.isLength(url.href, { min: 5 }) && validator.isInt(url.height + '', { min: 0 }) ) -- cgit v1.2.3 From cdf4cb9eaf5f6bc71f7c1e1963c07575f1d2593d Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Thu, 18 Oct 2018 09:44:43 +0200 Subject: Fix transcoding --- server/helpers/ffmpeg-utils.ts | 159 ++++++++++++++++++++++------------------- 1 file changed, 87 insertions(+), 72 deletions(-) (limited to 'server/helpers') diff --git a/server/helpers/ffmpeg-utils.ts b/server/helpers/ffmpeg-utils.ts index 037bf703a..a108d46a0 100644 --- a/server/helpers/ffmpeg-utils.ts +++ b/server/helpers/ffmpeg-utils.ts @@ -1,6 +1,6 @@ import * as ffmpeg from 'fluent-ffmpeg' import { join } from 'path' -import { VideoResolution, getTargetBitrate } from '../../shared/models/videos' +import { getTargetBitrate, VideoResolution } from '../../shared/models/videos' import { CONFIG, FFMPEG_NICE, VIDEO_TRANSCODING_FPS } from '../initializers' import { processImage } from './image-utils' import { logger } from './logger' @@ -116,46 +116,50 @@ type TranscodeOptions = { function transcode (options: TranscodeOptions) { return new Promise(async (res, rej) => { - let fps = await getVideoFileFPS(options.inputPath) - // On small/medium resolutions, limit FPS - if ( - options.resolution !== undefined && - options.resolution < VIDEO_TRANSCODING_FPS.KEEP_ORIGIN_FPS_RESOLUTION_MIN && - fps > VIDEO_TRANSCODING_FPS.AVERAGE - ) { - fps = VIDEO_TRANSCODING_FPS.AVERAGE - } + try { + let fps = await getVideoFileFPS(options.inputPath) + // On small/medium resolutions, limit FPS + if ( + options.resolution !== undefined && + options.resolution < VIDEO_TRANSCODING_FPS.KEEP_ORIGIN_FPS_RESOLUTION_MIN && + fps > VIDEO_TRANSCODING_FPS.AVERAGE + ) { + fps = VIDEO_TRANSCODING_FPS.AVERAGE + } - let command = ffmpeg(options.inputPath, { niceness: FFMPEG_NICE.TRANSCODING }) - .output(options.outputPath) - command = await presetH264(command, options.resolution, fps) + let command = ffmpeg(options.inputPath, { niceness: FFMPEG_NICE.TRANSCODING }) + .output(options.outputPath) + command = await presetH264(command, options.resolution, fps) - if (CONFIG.TRANSCODING.THREADS > 0) { - // if we don't set any threads ffmpeg will chose automatically - command = command.outputOption('-threads ' + CONFIG.TRANSCODING.THREADS) - } + if (CONFIG.TRANSCODING.THREADS > 0) { + // if we don't set any threads ffmpeg will chose automatically + command = command.outputOption('-threads ' + CONFIG.TRANSCODING.THREADS) + } - if (options.resolution !== undefined) { - // '?x720' or '720x?' for example - const size = options.isPortraitMode === true ? `${options.resolution}x?` : `?x${options.resolution}` - command = command.size(size) - } + if (options.resolution !== undefined) { + // '?x720' or '720x?' for example + const size = options.isPortraitMode === true ? `${options.resolution}x?` : `?x${options.resolution}` + command = command.size(size) + } - if (fps) { - // Hard FPS limits - if (fps > VIDEO_TRANSCODING_FPS.MAX) fps = VIDEO_TRANSCODING_FPS.MAX - else if (fps < VIDEO_TRANSCODING_FPS.MIN) fps = VIDEO_TRANSCODING_FPS.MIN + if (fps) { + // Hard FPS limits + if (fps > VIDEO_TRANSCODING_FPS.MAX) fps = VIDEO_TRANSCODING_FPS.MAX + else if (fps < VIDEO_TRANSCODING_FPS.MIN) fps = VIDEO_TRANSCODING_FPS.MIN - command = command.withFPS(fps) - } + command = command.withFPS(fps) + } - command - .on('error', (err, stdout, stderr) => { - logger.error('Error in transcoding job.', { stdout, stderr }) - return rej(err) - }) - .on('end', res) - .run() + command + .on('error', (err, stdout, stderr) => { + logger.error('Error in transcoding job.', { stdout, stderr }) + return rej(err) + }) + .on('end', res) + .run() + } catch (err) { + return rej(err) + } }) } @@ -194,11 +198,10 @@ function getVideoFileStream (path: string) { * and quality. Superfast and ultrafast will give you better * performance, but then quality is noticeably worse. */ -async function presetH264VeryFast (ffmpeg: ffmpeg, resolution: VideoResolution, fps: number): ffmpeg { - const localFfmpeg = await presetH264(ffmpeg, resolution, fps) - localFfmpeg - .outputOption('-preset:v veryfast') - .outputOption(['--aq-mode=2', '--aq-strength=1.3']) +async function presetH264VeryFast (command: ffmpeg.FfmpegCommand, resolution: VideoResolution, fps: number): Promise { + let localCommand = await presetH264(command, resolution, fps) + localCommand = localCommand.outputOption('-preset:v veryfast') + .outputOption([ '--aq-mode=2', '--aq-strength=1.3' ]) /* MAIN reference: https://slhck.info/video/2017/03/01/rate-control.html Our target situation is closer to a livestream than a stream, @@ -210,31 +213,39 @@ async function presetH264VeryFast (ffmpeg: ffmpeg, resolution: VideoResolution, Make up for most of the loss of grain and macroblocking with less computing power. */ + + return localCommand } /** * A preset optimised for a stillimage audio video */ -async function presetStillImageWithAudio (ffmpeg: ffmpeg, resolution: VideoResolution, fps: number): ffmpeg { - const localFfmpeg = await presetH264VeryFast(ffmpeg, resolution, fps) - localFfmpeg - .outputOption('-tune stillimage') +async function presetStillImageWithAudio ( + command: ffmpeg.FfmpegCommand, + resolution: VideoResolution, + fps: number +): Promise { + let localCommand = await presetH264VeryFast(command, resolution, fps) + localCommand = localCommand.outputOption('-tune stillimage') + + return localCommand } /** * A toolbox to play with audio */ namespace audio { - export const get = (_ffmpeg, pos: number | string = 0) => { + export const get = (option: ffmpeg.FfmpegCommand | string) => { // without position, ffprobe considers the last input only // we make it consider the first input only // if you pass a file path to pos, then ffprobe acts on that file directly return new Promise<{ absolutePath: string, audioStream?: any }>((res, rej) => { - _ffmpeg.ffprobe(pos, (err,data) => { + + function parseFfprobe (err: any, data: ffmpeg.FfprobeData) { if (err) return rej(err) if ('streams' in data) { - const audioStream = data['streams'].find(stream => stream['codec_type'] === 'audio') + const audioStream = data.streams.find(stream => stream['codec_type'] === 'audio') if (audioStream) { return res({ absolutePath: data.format.filename, @@ -242,8 +253,15 @@ namespace audio { }) } } + return res({ absolutePath: data.format.filename }) - }) + } + + if (typeof option === 'string') { + return ffmpeg.ffprobe(option, parseFfprobe) + } + + return option.ffprobe(parseFfprobe) }) } @@ -285,8 +303,8 @@ namespace audio { * As for the audio, quality '5' is the highest and ensures 96-112kbps/channel * See https://trac.ffmpeg.org/wiki/Encode/AAC#fdk_vbr */ -async function presetH264 (ffmpeg: ffmpeg, resolution: VideoResolution, fps: number): ffmpeg { - let localFfmpeg = ffmpeg +async function presetH264 (command: ffmpeg.FfmpegCommand, resolution: VideoResolution, fps: number): Promise { + let localCommand = command .format('mp4') .videoCodec('libx264') .outputOption('-level 3.1') // 3.1 is the minimal ressource allocation for our highest supported resolution @@ -294,41 +312,38 @@ async function presetH264 (ffmpeg: ffmpeg, resolution: VideoResolution, fps: num .outputOption('-bf 16') // NOTE: Why 16: https://github.com/Chocobozzz/PeerTube/pull/774. b-strategy 2 -> B-frames<16 .outputOption('-map_metadata -1') // strip all metadata .outputOption('-movflags faststart') - const _audio = await audio.get(localFfmpeg) - if (!_audio.audioStream) { - return localFfmpeg.noAudio() - } + const parsedAudio = await audio.get(localCommand) - // we favor VBR, if a good AAC encoder is available - if ((await checkFFmpegEncoders()).get('libfdk_aac')) { - return localFfmpeg + if (!parsedAudio.audioStream) { + localCommand = localCommand.noAudio() + } else if ((await checkFFmpegEncoders()).get('libfdk_aac')) { // we favor VBR, if a good AAC encoder is available + localCommand = localCommand .audioCodec('libfdk_aac') .audioQuality(5) + } else { + // we try to reduce the ceiling bitrate by making rough correspondances of bitrates + // of course this is far from perfect, but it might save some space in the end + const audioCodecName = parsedAudio.audioStream[ 'codec_name' ] + let bitrate: number + if (audio.bitrate[ audioCodecName ]) { + bitrate = audio.bitrate[ audioCodecName ](parsedAudio.audioStream[ 'bit_rate' ]) + + if (bitrate === -1) localCommand = localCommand.audioCodec('copy') + else if (bitrate !== undefined) localCommand = localCommand.audioBitrate(bitrate) + } } - // we try to reduce the ceiling bitrate by making rough correspondances of bitrates - // of course this is far from perfect, but it might save some space in the end - const audioCodecName = _audio.audioStream['codec_name'] - let bitrate: number - if (audio.bitrate[audioCodecName]) { - bitrate = audio.bitrate[audioCodecName](_audio.audioStream['bit_rate']) - - if (bitrate === -1) return localFfmpeg.audioCodec('copy') - } - - if (bitrate !== undefined) return localFfmpeg.audioBitrate(bitrate) - // Constrained Encoding (VBV) // https://slhck.info/video/2017/03/01/rate-control.html // https://trac.ffmpeg.org/wiki/Limiting%20the%20output%20bitrate const targetBitrate = getTargetBitrate(resolution, fps, VIDEO_TRANSCODING_FPS) - localFfmpeg = localFfmpeg.outputOptions([`-maxrate ${ targetBitrate }`, `-bufsize ${ targetBitrate * 2 }`]) + localCommand = localCommand.outputOptions([`-maxrate ${ targetBitrate }`, `-bufsize ${ targetBitrate * 2 }`]) // Keyframe interval of 2 seconds for faster seeking and resolution switching. // https://streaminglearningcenter.com/blogs/whats-the-right-keyframe-interval.html // https://superuser.com/a/908325 - localFfmpeg = localFfmpeg.outputOption(`-g ${ fps * 2 }`) + localCommand = localCommand.outputOption(`-g ${ fps * 2 }`) - return localFfmpeg + return localCommand } -- cgit v1.2.3 From 41f2ebae4f970932fb62d2d8923b1f776f0b1494 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 19 Oct 2018 11:41:19 +0200 Subject: Add HTTP signature check before linked signature It's faster, and will allow us to use RSA signature 2018 (with upstream jsonld-signature module) without too much incompatibilities in the peertube federation --- server/helpers/activitypub.ts | 32 +++++++++--------- server/helpers/peertube-crypto.ts | 70 ++++++++++++++++++++++++++------------- 2 files changed, 63 insertions(+), 39 deletions(-) (limited to 'server/helpers') diff --git a/server/helpers/activitypub.ts b/server/helpers/activitypub.ts index 1304c7559..278010e78 100644 --- a/server/helpers/activitypub.ts +++ b/server/helpers/activitypub.ts @@ -4,7 +4,7 @@ import { ResultList } from '../../shared/models' import { Activity, ActivityPubActor } from '../../shared/models/activitypub' import { ACTIVITY_PUB } from '../initializers' import { ActorModel } from '../models/activitypub/actor' -import { signObject } from './peertube-crypto' +import { signJsonLDObject } from './peertube-crypto' import { pageToStartAndCount } from './core-utils' function activityPubContextify (data: T) { @@ -15,22 +15,22 @@ function activityPubContextify (data: T) { { RsaSignature2017: 'https://w3id.org/security#RsaSignature2017', pt: 'https://joinpeertube.org/ns', - schema: 'http://schema.org#', + sc: 'http://schema.org#', Hashtag: 'as:Hashtag', - uuid: 'schema:identifier', - category: 'schema:category', - licence: 'schema:license', - subtitleLanguage: 'schema:subtitleLanguage', + uuid: 'sc:identifier', + category: 'sc:category', + licence: 'sc:license', + subtitleLanguage: 'sc:subtitleLanguage', sensitive: 'as:sensitive', - language: 'schema:inLanguage', - views: 'schema:Number', - stats: 'schema:Number', - size: 'schema:Number', - fps: 'schema:Number', - commentsEnabled: 'schema:Boolean', - waitTranscoding: 'schema:Boolean', - expires: 'schema:expires', - support: 'schema:Text', + language: 'sc:inLanguage', + views: 'sc:Number', + stats: 'sc:Number', + size: 'sc:Number', + fps: 'sc:Number', + commentsEnabled: 'sc:Boolean', + waitTranscoding: 'sc:Boolean', + expires: 'sc:expires', + support: 'sc:Text', CacheFile: 'pt:CacheFile' }, { @@ -102,7 +102,7 @@ async function activityPubCollectionPagination (url: string, handler: ActivityPu function buildSignedActivity (byActor: ActorModel, data: Object) { const activity = activityPubContextify(data) - return signObject(byActor, activity) as Promise + return signJsonLDObject(byActor, activity) as Promise } function getActorUrl (activityActor: string | ActivityPubActor) { diff --git a/server/helpers/peertube-crypto.ts b/server/helpers/peertube-crypto.ts index 5c182961d..cb5f27240 100644 --- a/server/helpers/peertube-crypto.ts +++ b/server/helpers/peertube-crypto.ts @@ -1,9 +1,12 @@ -import { BCRYPT_SALT_SIZE, PRIVATE_RSA_KEY_SIZE } from '../initializers' +import { Request } from 'express' +import { BCRYPT_SALT_SIZE, HTTP_SIGNATURE, PRIVATE_RSA_KEY_SIZE } from '../initializers' import { ActorModel } from '../models/activitypub/actor' import { bcryptComparePromise, bcryptGenSaltPromise, bcryptHashPromise, createPrivateKey, getPublicKey } from './core-utils' import { jsig } from './custom-jsonld-signature' import { logger } from './logger' +const httpSignature = require('http-signature') + async function createPrivateAndPublicKeys () { logger.info('Generating a RSA key...') @@ -13,18 +16,42 @@ async function createPrivateAndPublicKeys () { return { privateKey: key, publicKey } } -function isSignatureVerified (fromActor: ActorModel, signedDocument: object) { +// User password checks + +function comparePassword (plainPassword: string, hashPassword: string) { + return bcryptComparePromise(plainPassword, hashPassword) +} + +async function cryptPassword (password: string) { + const salt = await bcryptGenSaltPromise(BCRYPT_SALT_SIZE) + + return bcryptHashPromise(password, salt) +} + +// HTTP Signature + +function isHTTPSignatureVerified (httpSignatureParsed: any, actor: ActorModel) { + return httpSignature.verifySignature(httpSignatureParsed, actor.publicKey) === true +} + +function parseHTTPSignature (req: Request) { + return httpSignature.parse(req, { authorizationHeaderName: HTTP_SIGNATURE.HEADER_NAME }) +} + +// JSONLD + +function isJsonLDSignatureVerified (fromActor: ActorModel, signedDocument: any) { const publicKeyObject = { '@context': jsig.SECURITY_CONTEXT_URL, - '@id': fromActor.url, - '@type': 'CryptographicKey', + id: fromActor.url, + type: 'CryptographicKey', owner: fromActor.url, publicKeyPem: fromActor.publicKey } const publicKeyOwnerObject = { '@context': jsig.SECURITY_CONTEXT_URL, - '@id': fromActor.url, + id: fromActor.url, publicKey: [ publicKeyObject ] } @@ -33,14 +60,19 @@ function isSignatureVerified (fromActor: ActorModel, signedDocument: object) { publicKeyOwner: publicKeyOwnerObject } - return jsig.promises.verify(signedDocument, options) - .catch(err => { - logger.error('Cannot check signature.', { err }) - return false - }) + return jsig.promises + .verify(signedDocument, options) + .then((result: { verified: boolean }) => { + logger.info('coucou', result) + return result.verified + }) + .catch(err => { + logger.error('Cannot check signature.', { err }) + return false + }) } -function signObject (byActor: ActorModel, data: any) { +function signJsonLDObject (byActor: ActorModel, data: any) { const options = { privateKeyPem: byActor.privateKey, creator: byActor.url, @@ -50,22 +82,14 @@ function signObject (byActor: ActorModel, data: any) { return jsig.promises.sign(data, options) } -function comparePassword (plainPassword: string, hashPassword: string) { - return bcryptComparePromise(plainPassword, hashPassword) -} - -async function cryptPassword (password: string) { - const salt = await bcryptGenSaltPromise(BCRYPT_SALT_SIZE) - - return bcryptHashPromise(password, salt) -} - // --------------------------------------------------------------------------- export { - isSignatureVerified, + parseHTTPSignature, + isHTTPSignatureVerified, + isJsonLDSignatureVerified, comparePassword, createPrivateAndPublicKeys, cryptPassword, - signObject + signJsonLDObject } -- cgit v1.2.3 From 40ed9f6aaeb9e78fc8d9a5f82bd7dbad16639051 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 19 Oct 2018 12:42:13 +0200 Subject: Update translations --- server/helpers/peertube-crypto.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'server/helpers') diff --git a/server/helpers/peertube-crypto.ts b/server/helpers/peertube-crypto.ts index cb5f27240..8ef7b1359 100644 --- a/server/helpers/peertube-crypto.ts +++ b/server/helpers/peertube-crypto.ts @@ -62,10 +62,7 @@ function isJsonLDSignatureVerified (fromActor: ActorModel, signedDocument: any) return jsig.promises .verify(signedDocument, options) - .then((result: { verified: boolean }) => { - logger.info('coucou', result) - return result.verified - }) + .then((result: { verified: boolean }) => result.verified) .catch(err => { logger.error('Cannot check signature.', { err }) return false -- cgit v1.2.3 From f7509cbec875ec4ee3201cce08839f2a02676c1c Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 19 Oct 2018 11:41:19 +0200 Subject: Add HTTP signature check before linked signature It's faster, and will allow us to use RSA signature 2018 (with upstream jsonld-signature module) without too much incompatibilities in the peertube federation --- server/helpers/activitypub.ts | 32 +++++++++--------- server/helpers/peertube-crypto.ts | 70 ++++++++++++++++++++++++++------------- 2 files changed, 63 insertions(+), 39 deletions(-) (limited to 'server/helpers') diff --git a/server/helpers/activitypub.ts b/server/helpers/activitypub.ts index 1304c7559..278010e78 100644 --- a/server/helpers/activitypub.ts +++ b/server/helpers/activitypub.ts @@ -4,7 +4,7 @@ import { ResultList } from '../../shared/models' import { Activity, ActivityPubActor } from '../../shared/models/activitypub' import { ACTIVITY_PUB } from '../initializers' import { ActorModel } from '../models/activitypub/actor' -import { signObject } from './peertube-crypto' +import { signJsonLDObject } from './peertube-crypto' import { pageToStartAndCount } from './core-utils' function activityPubContextify (data: T) { @@ -15,22 +15,22 @@ function activityPubContextify (data: T) { { RsaSignature2017: 'https://w3id.org/security#RsaSignature2017', pt: 'https://joinpeertube.org/ns', - schema: 'http://schema.org#', + sc: 'http://schema.org#', Hashtag: 'as:Hashtag', - uuid: 'schema:identifier', - category: 'schema:category', - licence: 'schema:license', - subtitleLanguage: 'schema:subtitleLanguage', + uuid: 'sc:identifier', + category: 'sc:category', + licence: 'sc:license', + subtitleLanguage: 'sc:subtitleLanguage', sensitive: 'as:sensitive', - language: 'schema:inLanguage', - views: 'schema:Number', - stats: 'schema:Number', - size: 'schema:Number', - fps: 'schema:Number', - commentsEnabled: 'schema:Boolean', - waitTranscoding: 'schema:Boolean', - expires: 'schema:expires', - support: 'schema:Text', + language: 'sc:inLanguage', + views: 'sc:Number', + stats: 'sc:Number', + size: 'sc:Number', + fps: 'sc:Number', + commentsEnabled: 'sc:Boolean', + waitTranscoding: 'sc:Boolean', + expires: 'sc:expires', + support: 'sc:Text', CacheFile: 'pt:CacheFile' }, { @@ -102,7 +102,7 @@ async function activityPubCollectionPagination (url: string, handler: ActivityPu function buildSignedActivity (byActor: ActorModel, data: Object) { const activity = activityPubContextify(data) - return signObject(byActor, activity) as Promise + return signJsonLDObject(byActor, activity) as Promise } function getActorUrl (activityActor: string | ActivityPubActor) { diff --git a/server/helpers/peertube-crypto.ts b/server/helpers/peertube-crypto.ts index 5c182961d..cb5f27240 100644 --- a/server/helpers/peertube-crypto.ts +++ b/server/helpers/peertube-crypto.ts @@ -1,9 +1,12 @@ -import { BCRYPT_SALT_SIZE, PRIVATE_RSA_KEY_SIZE } from '../initializers' +import { Request } from 'express' +import { BCRYPT_SALT_SIZE, HTTP_SIGNATURE, PRIVATE_RSA_KEY_SIZE } from '../initializers' import { ActorModel } from '../models/activitypub/actor' import { bcryptComparePromise, bcryptGenSaltPromise, bcryptHashPromise, createPrivateKey, getPublicKey } from './core-utils' import { jsig } from './custom-jsonld-signature' import { logger } from './logger' +const httpSignature = require('http-signature') + async function createPrivateAndPublicKeys () { logger.info('Generating a RSA key...') @@ -13,18 +16,42 @@ async function createPrivateAndPublicKeys () { return { privateKey: key, publicKey } } -function isSignatureVerified (fromActor: ActorModel, signedDocument: object) { +// User password checks + +function comparePassword (plainPassword: string, hashPassword: string) { + return bcryptComparePromise(plainPassword, hashPassword) +} + +async function cryptPassword (password: string) { + const salt = await bcryptGenSaltPromise(BCRYPT_SALT_SIZE) + + return bcryptHashPromise(password, salt) +} + +// HTTP Signature + +function isHTTPSignatureVerified (httpSignatureParsed: any, actor: ActorModel) { + return httpSignature.verifySignature(httpSignatureParsed, actor.publicKey) === true +} + +function parseHTTPSignature (req: Request) { + return httpSignature.parse(req, { authorizationHeaderName: HTTP_SIGNATURE.HEADER_NAME }) +} + +// JSONLD + +function isJsonLDSignatureVerified (fromActor: ActorModel, signedDocument: any) { const publicKeyObject = { '@context': jsig.SECURITY_CONTEXT_URL, - '@id': fromActor.url, - '@type': 'CryptographicKey', + id: fromActor.url, + type: 'CryptographicKey', owner: fromActor.url, publicKeyPem: fromActor.publicKey } const publicKeyOwnerObject = { '@context': jsig.SECURITY_CONTEXT_URL, - '@id': fromActor.url, + id: fromActor.url, publicKey: [ publicKeyObject ] } @@ -33,14 +60,19 @@ function isSignatureVerified (fromActor: ActorModel, signedDocument: object) { publicKeyOwner: publicKeyOwnerObject } - return jsig.promises.verify(signedDocument, options) - .catch(err => { - logger.error('Cannot check signature.', { err }) - return false - }) + return jsig.promises + .verify(signedDocument, options) + .then((result: { verified: boolean }) => { + logger.info('coucou', result) + return result.verified + }) + .catch(err => { + logger.error('Cannot check signature.', { err }) + return false + }) } -function signObject (byActor: ActorModel, data: any) { +function signJsonLDObject (byActor: ActorModel, data: any) { const options = { privateKeyPem: byActor.privateKey, creator: byActor.url, @@ -50,22 +82,14 @@ function signObject (byActor: ActorModel, data: any) { return jsig.promises.sign(data, options) } -function comparePassword (plainPassword: string, hashPassword: string) { - return bcryptComparePromise(plainPassword, hashPassword) -} - -async function cryptPassword (password: string) { - const salt = await bcryptGenSaltPromise(BCRYPT_SALT_SIZE) - - return bcryptHashPromise(password, salt) -} - // --------------------------------------------------------------------------- export { - isSignatureVerified, + parseHTTPSignature, + isHTTPSignatureVerified, + isJsonLDSignatureVerified, comparePassword, createPrivateAndPublicKeys, cryptPassword, - signObject + signJsonLDObject } -- cgit v1.2.3 From 408f50ebc7cc3c0887d1e77a5c04508517dc151e Mon Sep 17 00:00:00 2001 From: Rigel Kent Date: Mon, 12 Nov 2018 15:43:51 +0100 Subject: (ffmpeg) force pixel format yuv420p (#1394) --- server/helpers/ffmpeg-utils.ts | 1 + 1 file changed, 1 insertion(+) (limited to 'server/helpers') diff --git a/server/helpers/ffmpeg-utils.ts b/server/helpers/ffmpeg-utils.ts index a108d46a0..8b9045038 100644 --- a/server/helpers/ffmpeg-utils.ts +++ b/server/helpers/ffmpeg-utils.ts @@ -310,6 +310,7 @@ async function presetH264 (command: ffmpeg.FfmpegCommand, resolution: VideoResol .outputOption('-level 3.1') // 3.1 is the minimal ressource allocation for our highest supported resolution .outputOption('-b_strategy 1') // NOTE: b-strategy 1 - heuristic algorythm, 16 is optimal B-frames for it .outputOption('-bf 16') // NOTE: Why 16: https://github.com/Chocobozzz/PeerTube/pull/774. b-strategy 2 -> B-frames<16 + .outputOption('-pix_fmt yuv420p') // allows import of source material with incompatible pixel formats (e.g. MJPEG video) .outputOption('-map_metadata -1') // strip all metadata .outputOption('-movflags faststart') -- cgit v1.2.3 From 1cf8aca11dcfec76e72cdb0edfb49bfc2bf4fdd8 Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Fri, 9 Nov 2018 19:32:10 +0100 Subject: Rename context stats to state I guess it refers to the VideoState enum used here as `state` instead. --- server/helpers/activitypub.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'server/helpers') diff --git a/server/helpers/activitypub.ts b/server/helpers/activitypub.ts index 278010e78..b0bcfe824 100644 --- a/server/helpers/activitypub.ts +++ b/server/helpers/activitypub.ts @@ -24,7 +24,7 @@ function activityPubContextify (data: T) { sensitive: 'as:sensitive', language: 'sc:inLanguage', views: 'sc:Number', - stats: 'sc:Number', + state: 'sc:Number', size: 'sc:Number', fps: 'sc:Number', commentsEnabled: 'sc:Boolean', -- cgit v1.2.3 From df66d81583e07ce049daeeef1edc6a87b57b3684 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 23 Oct 2018 11:38:48 +0200 Subject: Add compatibility with other Linked Signature algorithms --- server/helpers/custom-jsonld-signature.ts | 4 +- server/helpers/peertube-crypto.ts | 71 ++++++++++++++++++++++++++++--- 2 files changed, 66 insertions(+), 9 deletions(-) (limited to 'server/helpers') diff --git a/server/helpers/custom-jsonld-signature.ts b/server/helpers/custom-jsonld-signature.ts index e4f28018e..27a187db1 100644 --- a/server/helpers/custom-jsonld-signature.ts +++ b/server/helpers/custom-jsonld-signature.ts @@ -1,5 +1,5 @@ import * as AsyncLRU from 'async-lru' -import * as jsonld from 'jsonld/' +import * as jsonld from 'jsonld' import * as jsig from 'jsonld-signatures' const nodeDocumentLoader = jsonld.documentLoaders.node() @@ -17,4 +17,4 @@ jsonld.documentLoader = (url, cb) => { jsig.use('jsonld', jsonld) -export { jsig } +export { jsig, jsonld } diff --git a/server/helpers/peertube-crypto.ts b/server/helpers/peertube-crypto.ts index 8ef7b1359..ab9ec077e 100644 --- a/server/helpers/peertube-crypto.ts +++ b/server/helpers/peertube-crypto.ts @@ -1,9 +1,12 @@ import { Request } from 'express' import { BCRYPT_SALT_SIZE, HTTP_SIGNATURE, PRIVATE_RSA_KEY_SIZE } from '../initializers' import { ActorModel } from '../models/activitypub/actor' -import { bcryptComparePromise, bcryptGenSaltPromise, bcryptHashPromise, createPrivateKey, getPublicKey } from './core-utils' -import { jsig } from './custom-jsonld-signature' +import { bcryptComparePromise, bcryptGenSaltPromise, bcryptHashPromise, createPrivateKey, getPublicKey, sha256 } from './core-utils' +import { jsig, jsonld } from './custom-jsonld-signature' import { logger } from './logger' +import { cloneDeep } from 'lodash' +import { createVerify } from 'crypto' +import { buildDigest } from '../lib/job-queue/handlers/utils/activitypub-http-utils' const httpSignature = require('http-signature') @@ -30,21 +33,36 @@ async function cryptPassword (password: string) { // HTTP Signature -function isHTTPSignatureVerified (httpSignatureParsed: any, actor: ActorModel) { +function isHTTPSignatureDigestValid (rawBody: Buffer, req: Request): boolean { + if (req.headers[HTTP_SIGNATURE.HEADER_NAME] && req.headers['digest']) { + return buildDigest(rawBody.toString()) === req.headers['digest'] + } + + return true +} + +function isHTTPSignatureVerified (httpSignatureParsed: any, actor: ActorModel): boolean { return httpSignature.verifySignature(httpSignatureParsed, actor.publicKey) === true } -function parseHTTPSignature (req: Request) { - return httpSignature.parse(req, { authorizationHeaderName: HTTP_SIGNATURE.HEADER_NAME }) +function parseHTTPSignature (req: Request, clockSkew?: number) { + return httpSignature.parse(req, { authorizationHeaderName: HTTP_SIGNATURE.HEADER_NAME, clockSkew }) } // JSONLD -function isJsonLDSignatureVerified (fromActor: ActorModel, signedDocument: any) { +async function isJsonLDSignatureVerified (fromActor: ActorModel, signedDocument: any): Promise { + if (signedDocument.signature.type === 'RsaSignature2017') { + // Mastodon algorithm + const res = await isJsonLDRSA2017Verified(fromActor, signedDocument) + // Success? If no, try with our library + if (res === true) return true + } + const publicKeyObject = { '@context': jsig.SECURITY_CONTEXT_URL, id: fromActor.url, - type: 'CryptographicKey', + type: 'CryptographicKey', owner: fromActor.url, publicKeyPem: fromActor.publicKey } @@ -69,6 +87,44 @@ function isJsonLDSignatureVerified (fromActor: ActorModel, signedDocument: any) }) } +// Backward compatibility with "other" implementations +async function isJsonLDRSA2017Verified (fromActor: ActorModel, signedDocument: any) { + function hash (obj: any): Promise { + return jsonld.promises + .normalize(obj, { + algorithm: 'URDNA2015', + format: 'application/n-quads' + }) + .then(res => sha256(res)) + } + + const signatureCopy = cloneDeep(signedDocument.signature) + Object.assign(signatureCopy, { + '@context': [ + 'https://w3id.org/security/v1', + { RsaSignature2017: 'https://w3id.org/security#RsaSignature2017' } + ] + }) + delete signatureCopy.type + delete signatureCopy.id + delete signatureCopy.signatureValue + + const docWithoutSignature = cloneDeep(signedDocument) + delete docWithoutSignature.signature + + const [ documentHash, optionsHash ] = await Promise.all([ + hash(docWithoutSignature), + hash(signatureCopy) + ]) + + const toVerify = optionsHash + documentHash + + const verify = createVerify('RSA-SHA256') + verify.update(toVerify, 'utf8') + + return verify.verify(fromActor.publicKey, signedDocument.signature.signatureValue, 'base64') +} + function signJsonLDObject (byActor: ActorModel, data: any) { const options = { privateKeyPem: byActor.privateKey, @@ -82,6 +138,7 @@ function signJsonLDObject (byActor: ActorModel, data: any) { // --------------------------------------------------------------------------- export { + isHTTPSignatureDigestValid, parseHTTPSignature, isHTTPSignatureVerified, isJsonLDSignatureVerified, -- cgit v1.2.3 From 5c6d985faeef1d6793d3f44ca6374f1a9b722806 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Wed, 14 Nov 2018 15:01:28 +0100 Subject: Check activities host --- server/helpers/activitypub.ts | 9 +++++++++ server/helpers/requests.ts | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) (limited to 'server/helpers') diff --git a/server/helpers/activitypub.ts b/server/helpers/activitypub.ts index b0bcfe824..4bf6e387d 100644 --- a/server/helpers/activitypub.ts +++ b/server/helpers/activitypub.ts @@ -6,6 +6,7 @@ import { ACTIVITY_PUB } from '../initializers' import { ActorModel } from '../models/activitypub/actor' import { signJsonLDObject } from './peertube-crypto' import { pageToStartAndCount } from './core-utils' +import { parse } from 'url' function activityPubContextify (data: T) { return Object.assign(data, { @@ -111,9 +112,17 @@ function getActorUrl (activityActor: string | ActivityPubActor) { return activityActor.id } +function checkUrlsSameHost (url1: string, url2: string) { + const idHost = parse(url1).host + const actorHost = parse(url2).host + + return idHost && actorHost && idHost.toLowerCase() === actorHost.toLowerCase() +} + // --------------------------------------------------------------------------- export { + checkUrlsSameHost, getActorUrl, activityPubContextify, activityPubCollectionPagination, diff --git a/server/helpers/requests.ts b/server/helpers/requests.ts index ee9e80404..51facc9e0 100644 --- a/server/helpers/requests.ts +++ b/server/helpers/requests.ts @@ -3,7 +3,7 @@ import { createWriteStream } from 'fs-extra' import * as request from 'request' import { ACTIVITY_PUB } from '../initializers' -function doRequest ( +function doRequest ( requestOptions: request.CoreOptions & request.UriOptions & { activityPub?: boolean } ): Bluebird<{ response: request.RequestResponse, body: any }> { if (requestOptions.activityPub === true) { @@ -11,7 +11,7 @@ function doRequest ( requestOptions.headers['accept'] = ACTIVITY_PUB.ACCEPT_HEADER } - return new Bluebird<{ response: request.RequestResponse, body: any }>((res, rej) => { + return new Bluebird<{ response: request.RequestResponse, body: T }>((res, rej) => { request(requestOptions, (err, response, body) => err ? rej(err) : res({ response, body })) }) } -- cgit v1.2.3 From babecc3c09cd4ed06fe643a97fff4bcc31c5a9be Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 16 Nov 2018 15:38:09 +0100 Subject: Fix AP collections pagination --- server/helpers/activitypub.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'server/helpers') diff --git a/server/helpers/activitypub.ts b/server/helpers/activitypub.ts index 4bf6e387d..bcbd9be59 100644 --- a/server/helpers/activitypub.ts +++ b/server/helpers/activitypub.ts @@ -57,16 +57,16 @@ function activityPubContextify (data: T) { } type ActivityPubCollectionPaginationHandler = (start: number, count: number) => Bluebird> | Promise> -async function activityPubCollectionPagination (url: string, handler: ActivityPubCollectionPaginationHandler, page?: any) { +async function activityPubCollectionPagination (baseUrl: string, handler: ActivityPubCollectionPaginationHandler, page?: any) { if (!page || !validator.isInt(page)) { // We just display the first page URL, we only need the total items const result = await handler(0, 1) return { - id: url, + id: baseUrl, type: 'OrderedCollection', totalItems: result.total, - first: url + '?page=1' + first: baseUrl + '?page=1' } } @@ -81,19 +81,19 @@ async function activityPubCollectionPagination (url: string, handler: ActivityPu // There are more results if (result.total > page * ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE) { - next = url + '?page=' + (page + 1) + next = baseUrl + '?page=' + (page + 1) } if (page > 1) { - prev = url + '?page=' + (page - 1) + prev = baseUrl + '?page=' + (page - 1) } return { - id: url + '?page=' + page, + id: baseUrl + '?page=' + page, type: 'OrderedCollectionPage', prev, next, - partOf: url, + partOf: baseUrl, orderedItems: result.data, totalItems: result.total } -- cgit v1.2.3 From 58d515e32fe1d0133435b3a5e550c6ff24906fff Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 16 Nov 2018 16:48:17 +0100 Subject: Fix images size when downloading them --- server/helpers/requests.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) (limited to 'server/helpers') diff --git a/server/helpers/requests.ts b/server/helpers/requests.ts index 51facc9e0..805930a9f 100644 --- a/server/helpers/requests.ts +++ b/server/helpers/requests.ts @@ -2,6 +2,7 @@ import * as Bluebird from 'bluebird' import { createWriteStream } from 'fs-extra' import * as request from 'request' import { ACTIVITY_PUB } from '../initializers' +import { processImage } from './image-utils' function doRequest ( requestOptions: request.CoreOptions & request.UriOptions & { activityPub?: boolean } @@ -27,9 +28,18 @@ function doRequestAndSaveToFile (requestOptions: request.CoreOptions & request.U }) } +async function downloadImage (url: string, destPath: string, size: { width: number, height: number }) { + const tmpPath = destPath + '.tmp' + + await doRequestAndSaveToFile({ method: 'GET', uri: url }, tmpPath) + + await processImage({ path: tmpPath }, destPath, size) +} + // --------------------------------------------------------------------------- export { doRequest, - doRequestAndSaveToFile + doRequestAndSaveToFile, + downloadImage } -- cgit v1.2.3 From a8a63227781c6815532cb7a68699b08fdb0368be Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Mon, 19 Nov 2018 11:24:31 +0100 Subject: Optimize image resizing --- server/helpers/image-utils.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) (limited to 'server/helpers') diff --git a/server/helpers/image-utils.ts b/server/helpers/image-utils.ts index 3eaa674ed..da3285b13 100644 --- a/server/helpers/image-utils.ts +++ b/server/helpers/image-utils.ts @@ -1,13 +1,28 @@ import 'multer' import * as sharp from 'sharp' -import { remove } from 'fs-extra' +import { move, remove } from 'fs-extra' async function processImage ( physicalFile: { path: string }, destination: string, newSize: { width: number, height: number } ) { - await sharp(physicalFile.path) + if (physicalFile.path === destination) { + throw new Error('Sharp needs an input path different that the output path.') + } + + const sharpInstance = sharp(physicalFile.path) + const metadata = await sharpInstance.metadata() + + // No need to resize + if (metadata.width === newSize.width && metadata.height === newSize.height) { + await move(physicalFile.path, destination, { overwrite: true }) + return + } + + await remove(destination) + + await sharpInstance .resize(newSize.width, newSize.height) .toFile(destination) -- cgit v1.2.3 From 361805c48b14c5402c9984485c67c45a1a3113cc Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Mon, 19 Nov 2018 14:34:01 +0100 Subject: Fix checkbox margins --- server/helpers/activitypub.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'server/helpers') diff --git a/server/helpers/activitypub.ts b/server/helpers/activitypub.ts index bcbd9be59..79b76fa0b 100644 --- a/server/helpers/activitypub.ts +++ b/server/helpers/activitypub.ts @@ -1,7 +1,7 @@ import * as Bluebird from 'bluebird' import * as validator from 'validator' import { ResultList } from '../../shared/models' -import { Activity, ActivityPubActor } from '../../shared/models/activitypub' +import { Activity } from '../../shared/models/activitypub' import { ACTIVITY_PUB } from '../initializers' import { ActorModel } from '../models/activitypub/actor' import { signJsonLDObject } from './peertube-crypto' @@ -106,10 +106,10 @@ function buildSignedActivity (byActor: ActorModel, data: Object) { return signJsonLDObject(byActor, activity) as Promise } -function getActorUrl (activityActor: string | ActivityPubActor) { - if (typeof activityActor === 'string') return activityActor +function getAPUrl (activity: string | { id: string }) { + if (typeof activity === 'string') return activity - return activityActor.id + return activity.id } function checkUrlsSameHost (url1: string, url2: string) { @@ -123,7 +123,7 @@ function checkUrlsSameHost (url1: string, url2: string) { export { checkUrlsSameHost, - getActorUrl, + getAPUrl, activityPubContextify, activityPubCollectionPagination, buildSignedActivity -- cgit v1.2.3 From d175a6f7ab9dd53e36f9f52769ac02dbfdc57e3e Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Mon, 19 Nov 2018 17:08:18 +0100 Subject: Cleanup tests imports --- server/helpers/ffmpeg-utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'server/helpers') diff --git a/server/helpers/ffmpeg-utils.ts b/server/helpers/ffmpeg-utils.ts index 8b9045038..b59e7e40e 100644 --- a/server/helpers/ffmpeg-utils.ts +++ b/server/helpers/ffmpeg-utils.ts @@ -1,7 +1,7 @@ import * as ffmpeg from 'fluent-ffmpeg' import { join } from 'path' import { getTargetBitrate, VideoResolution } from '../../shared/models/videos' -import { CONFIG, FFMPEG_NICE, VIDEO_TRANSCODING_FPS } from '../initializers' +import { CONFIG, FFMPEG_NICE, VIDEO_TRANSCODING_FPS } from '../initializers/constants' import { processImage } from './image-utils' import { logger } from './logger' import { checkFFmpegEncoders } from '../initializers/checker-before-init' -- cgit v1.2.3 From 9fa0ea41aaa511bed3aa179dacc312fad6170c21 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Wed, 21 Nov 2018 16:29:09 +0100 Subject: Fix youtube video import --- server/helpers/youtube-dl.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'server/helpers') diff --git a/server/helpers/youtube-dl.ts b/server/helpers/youtube-dl.ts index 70b4e1b78..2a5663042 100644 --- a/server/helpers/youtube-dl.ts +++ b/server/helpers/youtube-dl.ts @@ -24,10 +24,10 @@ const processOptions = { function getYoutubeDLInfo (url: string, opts?: string[]): Promise { return new Promise(async (res, rej) => { - const options = opts || [ '-j', '--flat-playlist' ] + const args = opts || [ '-j', '--flat-playlist' ] const youtubeDL = await safeGetYoutubeDL() - youtubeDL.getInfo(url, options, (err, info) => { + youtubeDL.getInfo(url, args, processOptions, (err, info) => { if (err) return rej(err) if (info.is_live === true) return rej(new Error('Cannot download a live streaming.')) -- cgit v1.2.3 From 1b5e2d72900c8ceaf76940b72839d3c424ac96e8 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 23 Nov 2018 11:06:10 +0100 Subject: Optimize config endpoint --- server/helpers/utils.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'server/helpers') diff --git a/server/helpers/utils.ts b/server/helpers/utils.ts index 049c3f8bc..5c9d6fe2f 100644 --- a/server/helpers/utils.ts +++ b/server/helpers/utils.ts @@ -57,7 +57,7 @@ function getSecureTorrentName (originalName: string) { return sha256(originalName) + '.torrent' } -async function getVersion () { +async function getServerCommit () { try { const tag = await execPromise2( '[ ! -d .git ] || git name-rev --name-only --tags --no-undefined HEAD 2>/dev/null || true', @@ -77,7 +77,7 @@ async function getVersion () { logger.debug('Cannot get version from git HEAD.', { err }) } - return require('../../../package.json').version + return '' } /** @@ -102,7 +102,7 @@ export { getFormattedObjects, getSecureTorrentName, getServerActor, - getVersion, + getServerCommit, generateVideoTmpPath, getUUIDFromFilename } -- cgit v1.2.3 From 745778256ced65415b04a9817fc49db70d4b6681 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 4 Dec 2018 15:12:54 +0100 Subject: Fix thumbnail processing --- server/helpers/image-utils.ts | 14 ++++++-------- server/helpers/requests.ts | 4 ++-- 2 files changed, 8 insertions(+), 10 deletions(-) (limited to 'server/helpers') diff --git a/server/helpers/image-utils.ts b/server/helpers/image-utils.ts index da3285b13..e43ea3f1d 100644 --- a/server/helpers/image-utils.ts +++ b/server/helpers/image-utils.ts @@ -1,6 +1,7 @@ import 'multer' import * as sharp from 'sharp' -import { move, remove } from 'fs-extra' +import { readFile, remove } from 'fs-extra' +import { logger } from './logger' async function processImage ( physicalFile: { path: string }, @@ -11,14 +12,11 @@ async function processImage ( throw new Error('Sharp needs an input path different that the output path.') } - const sharpInstance = sharp(physicalFile.path) - const metadata = await sharpInstance.metadata() + logger.debug('Processing image %s to %s.', physicalFile.path, destination) - // No need to resize - if (metadata.width === newSize.width && metadata.height === newSize.height) { - await move(physicalFile.path, destination, { overwrite: true }) - return - } + // Avoid sharp cache + const buf = await readFile(physicalFile.path) + const sharpInstance = sharp(buf) await remove(destination) diff --git a/server/helpers/requests.ts b/server/helpers/requests.ts index 805930a9f..5760ad1c1 100644 --- a/server/helpers/requests.ts +++ b/server/helpers/requests.ts @@ -3,6 +3,7 @@ import { createWriteStream } from 'fs-extra' import * as request from 'request' import { ACTIVITY_PUB } from '../initializers' import { processImage } from './image-utils' +import { extname } from 'path' function doRequest ( requestOptions: request.CoreOptions & request.UriOptions & { activityPub?: boolean } @@ -29,8 +30,7 @@ function doRequestAndSaveToFile (requestOptions: request.CoreOptions & request.U } async function downloadImage (url: string, destPath: string, size: { width: number, height: number }) { - const tmpPath = destPath + '.tmp' - + const tmpPath = destPath + '.tmp' + extname(destPath) await doRequestAndSaveToFile({ method: 'GET', uri: url }, tmpPath) await processImage({ path: tmpPath }, destPath, size) -- cgit v1.2.3 From 6040f87d143a5fa01db79867ece8197c3ce7be47 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 4 Dec 2018 16:02:49 +0100 Subject: Add tmp and redundancy directories --- server/helpers/requests.ts | 9 +++++---- server/helpers/utils.ts | 6 +++--- server/helpers/webtorrent.ts | 6 +++--- server/helpers/youtube-dl.ts | 4 ++-- 4 files changed, 13 insertions(+), 12 deletions(-) (limited to 'server/helpers') diff --git a/server/helpers/requests.ts b/server/helpers/requests.ts index 5760ad1c1..3fc776f1a 100644 --- a/server/helpers/requests.ts +++ b/server/helpers/requests.ts @@ -1,9 +1,9 @@ import * as Bluebird from 'bluebird' import { createWriteStream } from 'fs-extra' import * as request from 'request' -import { ACTIVITY_PUB } from '../initializers' +import { ACTIVITY_PUB, CONFIG } from '../initializers' import { processImage } from './image-utils' -import { extname } from 'path' +import { join } from 'path' function doRequest ( requestOptions: request.CoreOptions & request.UriOptions & { activityPub?: boolean } @@ -29,10 +29,11 @@ function doRequestAndSaveToFile (requestOptions: request.CoreOptions & request.U }) } -async function downloadImage (url: string, destPath: string, size: { width: number, height: number }) { - const tmpPath = destPath + '.tmp' + extname(destPath) +async function downloadImage (url: string, destDir: string, destName: string, size: { width: number, height: number }) { + const tmpPath = join(CONFIG.STORAGE.TMP_DIR, 'pending-' + destName) await doRequestAndSaveToFile({ method: 'GET', uri: url }, tmpPath) + const destPath = join(destDir, destName) await processImage({ path: tmpPath }, destPath, size) } diff --git a/server/helpers/utils.ts b/server/helpers/utils.ts index 5c9d6fe2f..9b89e3e61 100644 --- a/server/helpers/utils.ts +++ b/server/helpers/utils.ts @@ -46,11 +46,11 @@ const getServerActor = memoizee(async function () { return actor }) -function generateVideoTmpPath (target: string | ParseTorrent) { +function generateVideoImportTmpPath (target: string | ParseTorrent) { const id = typeof target === 'string' ? target : target.infoHash const hash = sha256(id) - return join(CONFIG.STORAGE.VIDEOS_DIR, hash + '-import.mp4') + return join(CONFIG.STORAGE.TMP_DIR, hash + '-import.mp4') } function getSecureTorrentName (originalName: string) { @@ -103,6 +103,6 @@ export { getSecureTorrentName, getServerActor, getServerCommit, - generateVideoTmpPath, + generateVideoImportTmpPath, getUUIDFromFilename } diff --git a/server/helpers/webtorrent.ts b/server/helpers/webtorrent.ts index ce35b87da..3c9a0b96a 100644 --- a/server/helpers/webtorrent.ts +++ b/server/helpers/webtorrent.ts @@ -1,5 +1,5 @@ import { logger } from './logger' -import { generateVideoTmpPath } from './utils' +import { generateVideoImportTmpPath } from './utils' import * as WebTorrent from 'webtorrent' import { createWriteStream, ensureDir, remove } from 'fs-extra' import { CONFIG } from '../initializers' @@ -9,10 +9,10 @@ async function downloadWebTorrentVideo (target: { magnetUri: string, torrentName const id = target.magnetUri || target.torrentName let timer - const path = generateVideoTmpPath(id) + const path = generateVideoImportTmpPath(id) logger.info('Importing torrent video %s', id) - const directoryPath = join(CONFIG.STORAGE.VIDEOS_DIR, 'import') + const directoryPath = join(CONFIG.STORAGE.TMP_DIR, 'webtorrent') await ensureDir(directoryPath) return new Promise((res, rej) => { diff --git a/server/helpers/youtube-dl.ts b/server/helpers/youtube-dl.ts index 2a5663042..b74351b42 100644 --- a/server/helpers/youtube-dl.ts +++ b/server/helpers/youtube-dl.ts @@ -1,7 +1,7 @@ import { truncate } from 'lodash' import { CONSTRAINTS_FIELDS, VIDEO_CATEGORIES } from '../initializers' import { logger } from './logger' -import { generateVideoTmpPath } from './utils' +import { generateVideoImportTmpPath } from './utils' import { join } from 'path' import { root } from './core-utils' import { ensureDir, writeFile, remove } from 'fs-extra' @@ -40,7 +40,7 @@ function getYoutubeDLInfo (url: string, opts?: string[]): Promise } function downloadYoutubeDLVideo (url: string, timeout: number) { - const path = generateVideoTmpPath(url) + const path = generateVideoImportTmpPath(url) let timer logger.info('Importing youtubeDL video %s', url) -- cgit v1.2.3 From 2feebf3e6afaad9ab80976d1557d3a7bcf94de03 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Wed, 5 Dec 2018 17:27:24 +0100 Subject: Add sitemap --- server/helpers/express-utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'server/helpers') diff --git a/server/helpers/express-utils.ts b/server/helpers/express-utils.ts index 162fe2244..9a72ee96d 100644 --- a/server/helpers/express-utils.ts +++ b/server/helpers/express-utils.ts @@ -7,12 +7,12 @@ import { extname } from 'path' import { isArray } from './custom-validators/misc' import { UserModel } from '../models/account/user' -function buildNSFWFilter (res: express.Response, paramNSFW?: string) { +function buildNSFWFilter (res?: express.Response, paramNSFW?: string) { if (paramNSFW === 'true') return true if (paramNSFW === 'false') return false if (paramNSFW === 'both') return undefined - if (res.locals.oauth) { + if (res && res.locals.oauth) { const user: UserModel = res.locals.oauth.token.User // User does not want NSFW videos -- cgit v1.2.3 From 14e2014acc1362cfbb770c051a7254b156cd8efb Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 11 Dec 2018 14:52:50 +0100 Subject: Support additional video extensions --- server/helpers/custom-validators/video-captions.ts | 4 ++-- server/helpers/custom-validators/video-imports.ts | 4 ++-- server/helpers/custom-validators/videos.ts | 13 +++++++++---- 3 files changed, 13 insertions(+), 8 deletions(-) (limited to 'server/helpers') diff --git a/server/helpers/custom-validators/video-captions.ts b/server/helpers/custom-validators/video-captions.ts index 177e9e86e..b33d90e18 100644 --- a/server/helpers/custom-validators/video-captions.ts +++ b/server/helpers/custom-validators/video-captions.ts @@ -1,4 +1,4 @@ -import { CONSTRAINTS_FIELDS, VIDEO_CAPTIONS_MIMETYPE_EXT, VIDEO_LANGUAGES } from '../../initializers' +import { CONSTRAINTS_FIELDS, MIMETYPES, VIDEO_LANGUAGES } from '../../initializers' import { exists, isFileValid } from './misc' import { Response } from 'express' import { VideoModel } from '../../models/video/video' @@ -8,7 +8,7 @@ function isVideoCaptionLanguageValid (value: any) { return exists(value) && VIDEO_LANGUAGES[ value ] !== undefined } -const videoCaptionTypes = Object.keys(VIDEO_CAPTIONS_MIMETYPE_EXT) +const videoCaptionTypes = Object.keys(MIMETYPES.VIDEO_CAPTIONS.MIMETYPE_EXT) .concat([ 'application/octet-stream' ]) // MacOS sends application/octet-stream >< .map(m => `(${m})`) const videoCaptionTypesRegex = videoCaptionTypes.join('|') diff --git a/server/helpers/custom-validators/video-imports.ts b/server/helpers/custom-validators/video-imports.ts index 4d6ab1fa4..ce9e9193c 100644 --- a/server/helpers/custom-validators/video-imports.ts +++ b/server/helpers/custom-validators/video-imports.ts @@ -1,7 +1,7 @@ import 'express-validator' import 'multer' import * as validator from 'validator' -import { CONSTRAINTS_FIELDS, TORRENT_MIMETYPE_EXT, VIDEO_IMPORT_STATES } from '../../initializers' +import { CONSTRAINTS_FIELDS, MIMETYPES, VIDEO_IMPORT_STATES } from '../../initializers' import { exists, isFileValid } from './misc' import * as express from 'express' import { VideoImportModel } from '../../models/video/video-import' @@ -24,7 +24,7 @@ function isVideoImportStateValid (value: any) { return exists(value) && VIDEO_IMPORT_STATES[ value ] !== undefined } -const videoTorrentImportTypes = Object.keys(TORRENT_MIMETYPE_EXT).map(m => `(${m})`) +const videoTorrentImportTypes = Object.keys(MIMETYPES.TORRENT.MIMETYPE_EXT).map(m => `(${m})`) const videoTorrentImportRegex = videoTorrentImportTypes.join('|') function isVideoImportTorrentFile (files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[]) { return isFileValid(files, videoTorrentImportRegex, 'torrentfile', CONSTRAINTS_FIELDS.VIDEO_IMPORTS.TORRENT_FILE.FILE_SIZE.max, true) diff --git a/server/helpers/custom-validators/videos.ts b/server/helpers/custom-validators/videos.ts index a13b09ac8..e6f22e6c5 100644 --- a/server/helpers/custom-validators/videos.ts +++ b/server/helpers/custom-validators/videos.ts @@ -5,10 +5,9 @@ import 'multer' import * as validator from 'validator' import { UserRight, VideoFilter, VideoPrivacy, VideoRateType } from '../../../shared' import { - CONSTRAINTS_FIELDS, + CONSTRAINTS_FIELDS, MIMETYPES, VIDEO_CATEGORIES, VIDEO_LICENCES, - VIDEO_MIMETYPE_EXT, VIDEO_PRIVACIES, VIDEO_RATE_TYPES, VIDEO_STATES @@ -83,10 +82,15 @@ 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 isVideoFileExtnameValid (value: string) { + return exists(value) && MIMETYPES.VIDEO.EXT_MIMETYPE[value] !== undefined +} function isVideoFile (files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[]) { + const videoFileTypesRegex = Object.keys(MIMETYPES.VIDEO.MIMETYPE_EXT) + .map(m => `(${m})`) + .join('|') + return isFileValid(files, videoFileTypesRegex, 'videofile', null) } @@ -221,6 +225,7 @@ export { isVideoStateValid, isVideoViewsValid, isVideoRatingTypeValid, + isVideoFileExtnameValid, isVideoDurationValid, isVideoTagValid, isVideoPrivacyValid, -- cgit v1.2.3 From f481c4f9f31e897a08e818f388fecdee07f57142 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 11 Dec 2018 15:12:38 +0100 Subject: Use move instead rename To avoid EXDEV errors --- server/helpers/captions-utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'server/helpers') diff --git a/server/helpers/captions-utils.ts b/server/helpers/captions-utils.ts index 660dce65c..1aafbf805 100644 --- a/server/helpers/captions-utils.ts +++ b/server/helpers/captions-utils.ts @@ -2,7 +2,7 @@ import { join } from 'path' import { CONFIG } from '../initializers' import { VideoCaptionModel } from '../models/video/video-caption' import * as srt2vtt from 'srt-to-vtt' -import { createReadStream, createWriteStream, remove, rename } from 'fs-extra' +import { createReadStream, createWriteStream, remove, move } from 'fs-extra' async function moveAndProcessCaptionFile (physicalFile: { filename: string, path: string }, videoCaption: VideoCaptionModel) { const videoCaptionsDir = CONFIG.STORAGE.CAPTIONS_DIR @@ -13,7 +13,7 @@ async function moveAndProcessCaptionFile (physicalFile: { filename: string, path await convertSrtToVtt(physicalFile.path, destination) await remove(physicalFile.path) } else { // Just move the vtt file - await rename(physicalFile.path, destination) + await move(physicalFile.path, destination) } // This is important in case if there is another attempt in the retry process -- cgit v1.2.3 From 44848a51dc239ccb55046bb5f422f03bba26d57b Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 11 Dec 2018 15:56:35 +0100 Subject: Overwrite video caption --- server/helpers/captions-utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'server/helpers') diff --git a/server/helpers/captions-utils.ts b/server/helpers/captions-utils.ts index 1aafbf805..0fb11a125 100644 --- a/server/helpers/captions-utils.ts +++ b/server/helpers/captions-utils.ts @@ -13,7 +13,7 @@ async function moveAndProcessCaptionFile (physicalFile: { filename: string, path await convertSrtToVtt(physicalFile.path, destination) await remove(physicalFile.path) } else { // Just move the vtt file - await move(physicalFile.path, destination) + await move(physicalFile.path, destination, { overwrite: true }) } // This is important in case if there is another attempt in the retry process -- cgit v1.2.3 From 64e3e27053a5881fa9efca32f5a0debace617ced Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 11 Dec 2018 17:48:14 +0100 Subject: Fix broken audio with transcoding --- server/helpers/ffmpeg-utils.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'server/helpers') diff --git a/server/helpers/ffmpeg-utils.ts b/server/helpers/ffmpeg-utils.ts index b59e7e40e..0caa86efc 100644 --- a/server/helpers/ffmpeg-utils.ts +++ b/server/helpers/ffmpeg-utils.ts @@ -328,10 +328,10 @@ async function presetH264 (command: ffmpeg.FfmpegCommand, resolution: VideoResol const audioCodecName = parsedAudio.audioStream[ 'codec_name' ] let bitrate: number if (audio.bitrate[ audioCodecName ]) { - bitrate = audio.bitrate[ audioCodecName ](parsedAudio.audioStream[ 'bit_rate' ]) + localCommand = localCommand.audioCodec('aac') - if (bitrate === -1) localCommand = localCommand.audioCodec('copy') - else if (bitrate !== undefined) localCommand = localCommand.audioBitrate(bitrate) + bitrate = audio.bitrate[ audioCodecName ](parsedAudio.audioStream[ 'bit_rate' ]) + if (bitrate !== undefined && bitrate !== -1) localCommand = localCommand.audioBitrate(bitrate) } } -- cgit v1.2.3 From 9ecac97be024cf2277872986950d7eec85cbc76e Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Wed, 12 Dec 2018 08:55:56 +0100 Subject: Fix crash regarding video stream issue --- server/helpers/ffmpeg-utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'server/helpers') diff --git a/server/helpers/ffmpeg-utils.ts b/server/helpers/ffmpeg-utils.ts index 0caa86efc..c7296054d 100644 --- a/server/helpers/ffmpeg-utils.ts +++ b/server/helpers/ffmpeg-utils.ts @@ -184,7 +184,7 @@ function getVideoFileStream (path: string) { if (err) return rej(err) const videoStream = metadata.streams.find(s => s.codec_type === 'video') - if (!videoStream) throw new Error('Cannot find video stream of ' + path) + if (!videoStream) return rej(new Error('Cannot find video stream of ' + path)) return res(videoStream) }) -- cgit v1.2.3 From 8b9a525a180cc9f3a98c334cc052dcfc8f36dcd4 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Mon, 17 Dec 2018 15:52:38 +0100 Subject: Add history on server side Add ability to disable, clear and list user videos history --- server/helpers/custom-validators/users.ts | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'server/helpers') diff --git a/server/helpers/custom-validators/users.ts b/server/helpers/custom-validators/users.ts index 1cb5e5b0f..80652b479 100644 --- a/server/helpers/custom-validators/users.ts +++ b/server/helpers/custom-validators/users.ts @@ -46,6 +46,10 @@ function isUserWebTorrentEnabledValid (value: any) { return isBooleanValid(value) } +function isUserVideosHistoryEnabledValid (value: any) { + return isBooleanValid(value) +} + function isUserAutoPlayVideoValid (value: any) { return isBooleanValid(value) } @@ -73,6 +77,7 @@ function isAvatarFile (files: { [ fieldname: string ]: Express.Multer.File[] } | // --------------------------------------------------------------------------- export { + isUserVideosHistoryEnabledValid, isUserBlockedValid, isUserPasswordValid, isUserBlockedReasonValid, -- cgit v1.2.3 From cef534ed53e4518fe0acf581bfe880788d42fc36 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Wed, 26 Dec 2018 10:36:24 +0100 Subject: Add user notification base code --- server/helpers/custom-validators/misc.ts | 5 +++++ .../helpers/custom-validators/user-notifications.ts | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 server/helpers/custom-validators/user-notifications.ts (limited to 'server/helpers') diff --git a/server/helpers/custom-validators/misc.ts b/server/helpers/custom-validators/misc.ts index 6d10a65a8..a093e3e1b 100644 --- a/server/helpers/custom-validators/misc.ts +++ b/server/helpers/custom-validators/misc.ts @@ -9,6 +9,10 @@ function isArray (value: any) { return Array.isArray(value) } +function isIntArray (value: any) { + return Array.isArray(value) && value.every(v => validator.isInt('' + v)) +} + function isDateValid (value: string) { return exists(value) && validator.isISO8601(value) } @@ -78,6 +82,7 @@ function isFileValid ( export { exists, + isIntArray, isArray, isIdValid, isUUIDValid, diff --git a/server/helpers/custom-validators/user-notifications.ts b/server/helpers/custom-validators/user-notifications.ts new file mode 100644 index 000000000..4fb5d922d --- /dev/null +++ b/server/helpers/custom-validators/user-notifications.ts @@ -0,0 +1,19 @@ +import { exists } from './misc' +import * as validator from 'validator' +import { UserNotificationType } from '../../../shared/models/users' +import { UserNotificationSettingValue } from '../../../shared/models/users/user-notification-setting.model' + +function isUserNotificationTypeValid (value: any) { + return exists(value) && validator.isInt('' + value) && UserNotificationType[value] !== undefined +} + +function isUserNotificationSettingValid (value: any) { + return exists(value) && + validator.isInt('' + value) && + UserNotificationSettingValue[ value ] !== undefined +} + +export { + isUserNotificationSettingValid, + isUserNotificationTypeValid +} -- cgit v1.2.3 From f7cc67b455a12ccae9b0ea16876d166720364357 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 4 Jan 2019 08:56:20 +0100 Subject: Add new follow, mention and user registered notifs --- .../helpers/custom-validators/activitypub/actor.ts | 4 +++- server/helpers/regexp.ts | 23 ++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 server/helpers/regexp.ts (limited to 'server/helpers') diff --git a/server/helpers/custom-validators/activitypub/actor.ts b/server/helpers/custom-validators/activitypub/actor.ts index 77c003cdf..070632a20 100644 --- a/server/helpers/custom-validators/activitypub/actor.ts +++ b/server/helpers/custom-validators/activitypub/actor.ts @@ -27,7 +27,8 @@ function isActorPublicKeyValid (publicKey: string) { validator.isLength(publicKey, CONSTRAINTS_FIELDS.ACTORS.PUBLIC_KEY) } -const actorNameRegExp = new RegExp('^[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\\-_\.]+$') +const actorNameAlphabet = '[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\\-_.]' +const actorNameRegExp = new RegExp(`^${actorNameAlphabet}+$`) function isActorPreferredUsernameValid (preferredUsername: string) { return exists(preferredUsername) && validator.matches(preferredUsername, actorNameRegExp) } @@ -127,6 +128,7 @@ function areValidActorHandles (handles: string[]) { export { normalizeActor, + actorNameAlphabet, areValidActorHandles, isActorEndpointsObjectValid, isActorPublicKeyObjectValid, diff --git a/server/helpers/regexp.ts b/server/helpers/regexp.ts new file mode 100644 index 000000000..2336654b0 --- /dev/null +++ b/server/helpers/regexp.ts @@ -0,0 +1,23 @@ +// Thanks to https://regex101.com +function regexpCapture (str: string, regex: RegExp, maxIterations = 100) { + let m: RegExpExecArray + let i = 0 + let result: RegExpExecArray[] = [] + + // tslint:disable:no-conditional-assignment + while ((m = regex.exec(str)) !== null && i < maxIterations) { + // This is necessary to avoid infinite loops with zero-width matches + if (m.index === regex.lastIndex) { + regex.lastIndex++ + } + + result.push(m) + i++ + } + + return result +} + +export { + regexpCapture +} -- cgit v1.2.3 From 2f1548fda32c3ba9e53913270394eedfacd55986 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 8 Jan 2019 11:26:41 +0100 Subject: Add notifications in the client --- server/helpers/custom-validators/misc.ts | 6 +++--- server/helpers/custom-validators/user-notifications.ts | 8 ++++++-- 2 files changed, 9 insertions(+), 5 deletions(-) (limited to 'server/helpers') diff --git a/server/helpers/custom-validators/misc.ts b/server/helpers/custom-validators/misc.ts index a093e3e1b..b6f0ebe6f 100644 --- a/server/helpers/custom-validators/misc.ts +++ b/server/helpers/custom-validators/misc.ts @@ -9,8 +9,8 @@ function isArray (value: any) { return Array.isArray(value) } -function isIntArray (value: any) { - return Array.isArray(value) && value.every(v => validator.isInt('' + v)) +function isNotEmptyIntArray (value: any) { + return Array.isArray(value) && value.every(v => validator.isInt('' + v)) && value.length !== 0 } function isDateValid (value: string) { @@ -82,7 +82,7 @@ function isFileValid ( export { exists, - isIntArray, + isNotEmptyIntArray, isArray, isIdValid, isUUIDValid, diff --git a/server/helpers/custom-validators/user-notifications.ts b/server/helpers/custom-validators/user-notifications.ts index 4fb5d922d..02ea3bbc2 100644 --- a/server/helpers/custom-validators/user-notifications.ts +++ b/server/helpers/custom-validators/user-notifications.ts @@ -9,8 +9,12 @@ function isUserNotificationTypeValid (value: any) { function isUserNotificationSettingValid (value: any) { return exists(value) && - validator.isInt('' + value) && - UserNotificationSettingValue[ value ] !== undefined + validator.isInt('' + value) && ( + value === UserNotificationSettingValue.NONE || + value === UserNotificationSettingValue.WEB || + value === UserNotificationSettingValue.EMAIL || + value === (UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL) + ) } export { -- cgit v1.2.3 From a4101923e699e49ceb9ff36e971c75417fafc9f0 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Wed, 9 Jan 2019 15:14:29 +0100 Subject: Implement contact form on server side --- server/helpers/core-utils.ts | 20 ++++++++++++++++++++ server/helpers/custom-validators/servers.ts | 11 +++++++++++ server/helpers/utils.ts | 6 ++---- 3 files changed, 33 insertions(+), 4 deletions(-) (limited to 'server/helpers') diff --git a/server/helpers/core-utils.ts b/server/helpers/core-utils.ts index 84e33c0e9..3fb824e36 100644 --- a/server/helpers/core-utils.ts +++ b/server/helpers/core-utils.ts @@ -11,6 +11,25 @@ import * as pem from 'pem' import { URL } from 'url' import { truncate } from 'lodash' import { exec } from 'child_process' +import { isArray } from './custom-validators/misc' + +const objectConverter = (oldObject: any, keyConverter: (e: string) => string, valueConverter: (e: any) => any) => { + if (!oldObject || typeof oldObject !== 'object') { + return valueConverter(oldObject) + } + + if (isArray(oldObject)) { + return oldObject.map(e => objectConverter(e, keyConverter, valueConverter)) + } + + const newObject = {} + Object.keys(oldObject).forEach(oldKey => { + const newKey = keyConverter(oldKey) + newObject[ newKey ] = objectConverter(oldObject[ oldKey ], keyConverter, valueConverter) + }) + + return newObject +} const timeTable = { ms: 1, @@ -235,6 +254,7 @@ export { isTestInstance, isProdInstance, + objectConverter, root, escapeHTML, pageToStartAndCount, diff --git a/server/helpers/custom-validators/servers.ts b/server/helpers/custom-validators/servers.ts index d5021bf38..18c80ec8f 100644 --- a/server/helpers/custom-validators/servers.ts +++ b/server/helpers/custom-validators/servers.ts @@ -3,6 +3,7 @@ import 'express-validator' import { isArray, exists } from './misc' import { isTestInstance } from '../core-utils' +import { CONSTRAINTS_FIELDS } from '../../initializers' function isHostValid (host: string) { const isURLOptions = { @@ -26,9 +27,19 @@ function isEachUniqueHostValid (hosts: string[]) { }) } +function isValidContactBody (value: any) { + return exists(value) && validator.isLength(value, CONSTRAINTS_FIELDS.CONTACT_FORM.BODY) +} + +function isValidContactFromName (value: any) { + return exists(value) && validator.isLength(value, CONSTRAINTS_FIELDS.CONTACT_FORM.FROM_NAME) +} + // --------------------------------------------------------------------------- export { + isValidContactBody, + isValidContactFromName, isEachUniqueHostValid, isHostValid } diff --git a/server/helpers/utils.ts b/server/helpers/utils.ts index 9b89e3e61..3c3406e38 100644 --- a/server/helpers/utils.ts +++ b/server/helpers/utils.ts @@ -7,6 +7,7 @@ import { join } from 'path' import { Instance as ParseTorrent } from 'parse-torrent' import { remove } from 'fs-extra' import * as memoizee from 'memoizee' +import { isArray } from './custom-validators/misc' function deleteFileAsync (path: string) { remove(path) @@ -19,10 +20,7 @@ async function generateRandomString (size: number) { return raw.toString('hex') } -interface FormattableToJSON { - toFormattedJSON (args?: any) -} - +interface FormattableToJSON { toFormattedJSON (args?: any) } function getFormattedObjects (objects: T[], objectsTotal: number, formattedArg?: any) { const formattedObjects: U[] = [] -- cgit v1.2.3 From 848f499def54db2dd36437ef0dfb74dd5041c23b Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 15 Jan 2019 11:14:12 +0100 Subject: Prepare Dislike/Flag/View fixes For now we Create these activities, but we should just send them directly. This fix handles correctly direct Dislikes/Flags/Views, we'll implement the sending correctly these activities in the next peertube version --- server/helpers/activitypub.ts | 4 +- .../custom-validators/activitypub/activity.ts | 99 +++++++++++++--------- .../helpers/custom-validators/activitypub/actor.ts | 25 +----- .../custom-validators/activitypub/announce.ts | 13 --- .../custom-validators/activitypub/cache-file.ts | 16 +--- .../helpers/custom-validators/activitypub/flag.ts | 14 +++ .../helpers/custom-validators/activitypub/misc.ts | 24 ++++-- .../helpers/custom-validators/activitypub/rate.ts | 15 +--- .../helpers/custom-validators/activitypub/undo.ts | 20 ----- .../activitypub/video-comments.ts | 11 --- .../custom-validators/activitypub/videos.ts | 19 ----- .../helpers/custom-validators/activitypub/view.ts | 10 +-- 12 files changed, 107 insertions(+), 163 deletions(-) delete mode 100644 server/helpers/custom-validators/activitypub/announce.ts create mode 100644 server/helpers/custom-validators/activitypub/flag.ts delete mode 100644 server/helpers/custom-validators/activitypub/undo.ts (limited to 'server/helpers') diff --git a/server/helpers/activitypub.ts b/server/helpers/activitypub.ts index 79b76fa0b..f1430055f 100644 --- a/server/helpers/activitypub.ts +++ b/server/helpers/activitypub.ts @@ -106,7 +106,7 @@ function buildSignedActivity (byActor: ActorModel, data: Object) { return signJsonLDObject(byActor, activity) as Promise } -function getAPUrl (activity: string | { id: string }) { +function getAPId (activity: string | { id: string }) { if (typeof activity === 'string') return activity return activity.id @@ -123,7 +123,7 @@ function checkUrlsSameHost (url1: string, url2: string) { export { checkUrlsSameHost, - getAPUrl, + getAPId, activityPubContextify, activityPubCollectionPagination, buildSignedActivity diff --git a/server/helpers/custom-validators/activitypub/activity.ts b/server/helpers/custom-validators/activitypub/activity.ts index 2562ead9b..b24590d9d 100644 --- a/server/helpers/custom-validators/activitypub/activity.ts +++ b/server/helpers/custom-validators/activitypub/activity.ts @@ -1,26 +1,14 @@ import * as validator from 'validator' import { Activity, ActivityType } from '../../../../shared/models/activitypub' -import { - isActorAcceptActivityValid, - isActorDeleteActivityValid, - isActorFollowActivityValid, - isActorRejectActivityValid, - isActorUpdateActivityValid -} from './actor' -import { isAnnounceActivityValid } from './announce' -import { isActivityPubUrlValid } from './misc' -import { isDislikeActivityValid, isLikeActivityValid } from './rate' -import { isUndoActivityValid } from './undo' -import { isVideoCommentCreateActivityValid, isVideoCommentDeleteActivityValid } from './video-comments' -import { - isVideoFlagValid, - isVideoTorrentDeleteActivityValid, - sanitizeAndCheckVideoTorrentCreateActivity, - sanitizeAndCheckVideoTorrentUpdateActivity -} from './videos' +import { sanitizeAndCheckActorObject } from './actor' +import { isActivityPubUrlValid, isBaseActivityValid, isObjectValid } from './misc' +import { isDislikeActivityValid } from './rate' +import { sanitizeAndCheckVideoCommentObject } from './video-comments' +import { sanitizeAndCheckVideoTorrentObject } from './videos' import { isViewActivityValid } from './view' import { exists } from '../misc' -import { isCacheFileCreateActivityValid, isCacheFileUpdateActivityValid } from './cache-file' +import { isCacheFileObjectValid } from './cache-file' +import { isFlagActivityValid } from './flag' function isRootActivityValid (activity: any) { return Array.isArray(activity['@context']) && ( @@ -46,7 +34,10 @@ const activityCheckers: { [ P in ActivityType ]: (activity: Activity) => boolean Reject: checkRejectActivity, Announce: checkAnnounceActivity, Undo: checkUndoActivity, - Like: checkLikeActivity + Like: checkLikeActivity, + View: checkViewActivity, + Flag: checkFlagActivity, + Dislike: checkDislikeActivity } function isActivityValid (activity: any) { @@ -66,47 +57,79 @@ export { // --------------------------------------------------------------------------- +function checkViewActivity (activity: any) { + return isBaseActivityValid(activity, 'View') && + isViewActivityValid(activity) +} + +function checkFlagActivity (activity: any) { + return isBaseActivityValid(activity, 'Flag') && + isFlagActivityValid(activity) +} + +function checkDislikeActivity (activity: any) { + return isBaseActivityValid(activity, 'Dislike') && + isDislikeActivityValid(activity) +} + function checkCreateActivity (activity: any) { - return isViewActivityValid(activity) || - isDislikeActivityValid(activity) || - sanitizeAndCheckVideoTorrentCreateActivity(activity) || - isVideoFlagValid(activity) || - isVideoCommentCreateActivityValid(activity) || - isCacheFileCreateActivityValid(activity) + return isBaseActivityValid(activity, 'Create') && + ( + isViewActivityValid(activity.object) || + isDislikeActivityValid(activity.object) || + isFlagActivityValid(activity.object) || + + isCacheFileObjectValid(activity.object) || + sanitizeAndCheckVideoCommentObject(activity.object) || + sanitizeAndCheckVideoTorrentObject(activity.object) + ) } function checkUpdateActivity (activity: any) { - return isCacheFileUpdateActivityValid(activity) || - sanitizeAndCheckVideoTorrentUpdateActivity(activity) || - isActorUpdateActivityValid(activity) + return isBaseActivityValid(activity, 'Update') && + ( + isCacheFileObjectValid(activity.object) || + sanitizeAndCheckVideoTorrentObject(activity.object) || + sanitizeAndCheckActorObject(activity.object) + ) } function checkDeleteActivity (activity: any) { - return isVideoTorrentDeleteActivityValid(activity) || - isActorDeleteActivityValid(activity) || - isVideoCommentDeleteActivityValid(activity) + // We don't really check objects + return isBaseActivityValid(activity, 'Delete') && + isObjectValid(activity.object) } function checkFollowActivity (activity: any) { - return isActorFollowActivityValid(activity) + return isBaseActivityValid(activity, 'Follow') && + isObjectValid(activity.object) } function checkAcceptActivity (activity: any) { - return isActorAcceptActivityValid(activity) + return isBaseActivityValid(activity, 'Accept') } function checkRejectActivity (activity: any) { - return isActorRejectActivityValid(activity) + return isBaseActivityValid(activity, 'Reject') } function checkAnnounceActivity (activity: any) { - return isAnnounceActivityValid(activity) + return isBaseActivityValid(activity, 'Announce') && + isObjectValid(activity.object) } function checkUndoActivity (activity: any) { - return isUndoActivityValid(activity) + return isBaseActivityValid(activity, 'Undo') && + ( + checkFollowActivity(activity.object) || + checkLikeActivity(activity.object) || + checkDislikeActivity(activity.object) || + checkAnnounceActivity(activity.object) || + checkCreateActivity(activity.object) + ) } function checkLikeActivity (activity: any) { - return isLikeActivityValid(activity) + return isBaseActivityValid(activity, 'Like') && + isObjectValid(activity.object) } diff --git a/server/helpers/custom-validators/activitypub/actor.ts b/server/helpers/custom-validators/activitypub/actor.ts index 070632a20..c05f60f14 100644 --- a/server/helpers/custom-validators/activitypub/actor.ts +++ b/server/helpers/custom-validators/activitypub/actor.ts @@ -73,24 +73,10 @@ function isActorDeleteActivityValid (activity: any) { return isBaseActivityValid(activity, 'Delete') } -function isActorFollowActivityValid (activity: any) { - return isBaseActivityValid(activity, 'Follow') && - isActivityPubUrlValid(activity.object) -} - -function isActorAcceptActivityValid (activity: any) { - return isBaseActivityValid(activity, 'Accept') -} - -function isActorRejectActivityValid (activity: any) { - return isBaseActivityValid(activity, 'Reject') -} - -function isActorUpdateActivityValid (activity: any) { - normalizeActor(activity.object) +function sanitizeAndCheckActorObject (object: any) { + normalizeActor(object) - return isBaseActivityValid(activity, 'Update') && - isActorObjectValid(activity.object) + return isActorObjectValid(object) } function normalizeActor (actor: any) { @@ -139,10 +125,7 @@ export { isActorObjectValid, isActorFollowingCountValid, isActorFollowersCountValid, - isActorFollowActivityValid, - isActorAcceptActivityValid, - isActorRejectActivityValid, isActorDeleteActivityValid, - isActorUpdateActivityValid, + sanitizeAndCheckActorObject, isValidActorHandle } diff --git a/server/helpers/custom-validators/activitypub/announce.ts b/server/helpers/custom-validators/activitypub/announce.ts deleted file mode 100644 index 0519c6026..000000000 --- a/server/helpers/custom-validators/activitypub/announce.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { isActivityPubUrlValid, isBaseActivityValid } from './misc' - -function isAnnounceActivityValid (activity: any) { - return isBaseActivityValid(activity, 'Announce') && - ( - isActivityPubUrlValid(activity.object) || - (activity.object && isActivityPubUrlValid(activity.object.id)) - ) -} - -export { - isAnnounceActivityValid -} diff --git a/server/helpers/custom-validators/activitypub/cache-file.ts b/server/helpers/custom-validators/activitypub/cache-file.ts index bd70934c8..e2bd0c55e 100644 --- a/server/helpers/custom-validators/activitypub/cache-file.ts +++ b/server/helpers/custom-validators/activitypub/cache-file.ts @@ -1,18 +1,8 @@ -import { isActivityPubUrlValid, isBaseActivityValid } from './misc' +import { isActivityPubUrlValid } from './misc' import { isRemoteVideoUrlValid } from './videos' -import { isDateValid, exists } from '../misc' +import { exists, isDateValid } from '../misc' import { CacheFileObject } from '../../../../shared/models/activitypub/objects' -function isCacheFileCreateActivityValid (activity: any) { - return isBaseActivityValid(activity, 'Create') && - isCacheFileObjectValid(activity.object) -} - -function isCacheFileUpdateActivityValid (activity: any) { - return isBaseActivityValid(activity, 'Update') && - isCacheFileObjectValid(activity.object) -} - function isCacheFileObjectValid (object: CacheFileObject) { return exists(object) && object.type === 'CacheFile' && @@ -22,7 +12,5 @@ function isCacheFileObjectValid (object: CacheFileObject) { } export { - isCacheFileUpdateActivityValid, - isCacheFileCreateActivityValid, isCacheFileObjectValid } diff --git a/server/helpers/custom-validators/activitypub/flag.ts b/server/helpers/custom-validators/activitypub/flag.ts new file mode 100644 index 000000000..6452e297c --- /dev/null +++ b/server/helpers/custom-validators/activitypub/flag.ts @@ -0,0 +1,14 @@ +import { isActivityPubUrlValid } from './misc' +import { isVideoAbuseReasonValid } from '../video-abuses' + +function isFlagActivityValid (activity: any) { + return activity.type === 'Flag' && + isVideoAbuseReasonValid(activity.content) && + isActivityPubUrlValid(activity.object) +} + +// --------------------------------------------------------------------------- + +export { + isFlagActivityValid +} diff --git a/server/helpers/custom-validators/activitypub/misc.ts b/server/helpers/custom-validators/activitypub/misc.ts index 4e2c57f04..f1762d11c 100644 --- a/server/helpers/custom-validators/activitypub/misc.ts +++ b/server/helpers/custom-validators/activitypub/misc.ts @@ -28,15 +28,20 @@ function isBaseActivityValid (activity: any, type: string) { return (activity['@context'] === undefined || Array.isArray(activity['@context'])) && activity.type === type && isActivityPubUrlValid(activity.id) && - exists(activity.actor) && - (isActivityPubUrlValid(activity.actor) || isActivityPubUrlValid(activity.actor.id)) && - ( - activity.to === undefined || - (Array.isArray(activity.to) && activity.to.every(t => isActivityPubUrlValid(t))) - ) && + isObjectValid(activity.actor) && + isUrlCollectionValid(activity.to) && + isUrlCollectionValid(activity.cc) +} + +function isUrlCollectionValid (collection: any) { + return collection === undefined || + (Array.isArray(collection) && collection.every(t => isActivityPubUrlValid(t))) +} + +function isObjectValid (object: any) { + return exists(object) && ( - activity.cc === undefined || - (Array.isArray(activity.cc) && activity.cc.every(t => isActivityPubUrlValid(t))) + isActivityPubUrlValid(object) || isActivityPubUrlValid(object.id) ) } @@ -57,5 +62,6 @@ export { isUrlValid, isActivityPubUrlValid, isBaseActivityValid, - setValidAttributedTo + setValidAttributedTo, + isObjectValid } diff --git a/server/helpers/custom-validators/activitypub/rate.ts b/server/helpers/custom-validators/activitypub/rate.ts index e70bd94b8..ba68e8074 100644 --- a/server/helpers/custom-validators/activitypub/rate.ts +++ b/server/helpers/custom-validators/activitypub/rate.ts @@ -1,20 +1,13 @@ -import { isActivityPubUrlValid, isBaseActivityValid } from './misc' - -function isLikeActivityValid (activity: any) { - return isBaseActivityValid(activity, 'Like') && - isActivityPubUrlValid(activity.object) -} +import { isActivityPubUrlValid, isObjectValid } from './misc' function isDislikeActivityValid (activity: any) { - return isBaseActivityValid(activity, 'Create') && - activity.object.type === 'Dislike' && - isActivityPubUrlValid(activity.object.actor) && - isActivityPubUrlValid(activity.object.object) + return activity.type === 'Dislike' && + isActivityPubUrlValid(activity.actor) && + isObjectValid(activity.object) } // --------------------------------------------------------------------------- export { - isLikeActivityValid, isDislikeActivityValid } diff --git a/server/helpers/custom-validators/activitypub/undo.ts b/server/helpers/custom-validators/activitypub/undo.ts deleted file mode 100644 index 578035893..000000000 --- a/server/helpers/custom-validators/activitypub/undo.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { isActorFollowActivityValid } from './actor' -import { isBaseActivityValid } from './misc' -import { isDislikeActivityValid, isLikeActivityValid } from './rate' -import { isAnnounceActivityValid } from './announce' -import { isCacheFileCreateActivityValid } from './cache-file' - -function isUndoActivityValid (activity: any) { - return isBaseActivityValid(activity, 'Undo') && - ( - isActorFollowActivityValid(activity.object) || - isLikeActivityValid(activity.object) || - isDislikeActivityValid(activity.object) || - isAnnounceActivityValid(activity.object) || - isCacheFileCreateActivityValid(activity.object) - ) -} - -export { - isUndoActivityValid -} diff --git a/server/helpers/custom-validators/activitypub/video-comments.ts b/server/helpers/custom-validators/activitypub/video-comments.ts index 051c4565a..0415db21c 100644 --- a/server/helpers/custom-validators/activitypub/video-comments.ts +++ b/server/helpers/custom-validators/activitypub/video-comments.ts @@ -3,11 +3,6 @@ import { ACTIVITY_PUB, CONSTRAINTS_FIELDS } from '../../../initializers' import { exists, isArray, isDateValid } from '../misc' import { isActivityPubUrlValid, isBaseActivityValid } from './misc' -function isVideoCommentCreateActivityValid (activity: any) { - return isBaseActivityValid(activity, 'Create') && - sanitizeAndCheckVideoCommentObject(activity.object) -} - function sanitizeAndCheckVideoCommentObject (comment: any) { if (!comment || comment.type !== 'Note') return false @@ -25,15 +20,9 @@ function sanitizeAndCheckVideoCommentObject (comment: any) { ) // Only accept public comments } -function isVideoCommentDeleteActivityValid (activity: any) { - return isBaseActivityValid(activity, 'Delete') -} - // --------------------------------------------------------------------------- export { - isVideoCommentCreateActivityValid, - isVideoCommentDeleteActivityValid, sanitizeAndCheckVideoCommentObject } diff --git a/server/helpers/custom-validators/activitypub/videos.ts b/server/helpers/custom-validators/activitypub/videos.ts index 95fe824b9..0f34aab21 100644 --- a/server/helpers/custom-validators/activitypub/videos.ts +++ b/server/helpers/custom-validators/activitypub/videos.ts @@ -14,27 +14,11 @@ import { isActivityPubUrlValid, isBaseActivityValid, setValidAttributedTo } from import { VideoState } from '../../../../shared/models/videos' import { isVideoAbuseReasonValid } from '../video-abuses' -function sanitizeAndCheckVideoTorrentCreateActivity (activity: any) { - return isBaseActivityValid(activity, 'Create') && - sanitizeAndCheckVideoTorrentObject(activity.object) -} - function sanitizeAndCheckVideoTorrentUpdateActivity (activity: any) { return isBaseActivityValid(activity, 'Update') && sanitizeAndCheckVideoTorrentObject(activity.object) } -function isVideoTorrentDeleteActivityValid (activity: any) { - return isBaseActivityValid(activity, 'Delete') -} - -function isVideoFlagValid (activity: any) { - return isBaseActivityValid(activity, 'Create') && - activity.object.type === 'Flag' && - isVideoAbuseReasonValid(activity.object.content) && - isActivityPubUrlValid(activity.object.object) -} - function isActivityPubVideoDurationValid (value: string) { // https://www.w3.org/TR/activitystreams-vocabulary/#dfn-duration return exists(value) && @@ -103,11 +87,8 @@ function isRemoteVideoUrlValid (url: any) { // --------------------------------------------------------------------------- export { - sanitizeAndCheckVideoTorrentCreateActivity, sanitizeAndCheckVideoTorrentUpdateActivity, - isVideoTorrentDeleteActivityValid, isRemoteStringIdentifierValid, - isVideoFlagValid, sanitizeAndCheckVideoTorrentObject, isRemoteVideoUrlValid } diff --git a/server/helpers/custom-validators/activitypub/view.ts b/server/helpers/custom-validators/activitypub/view.ts index 7a3aca6f5..41d16469f 100644 --- a/server/helpers/custom-validators/activitypub/view.ts +++ b/server/helpers/custom-validators/activitypub/view.ts @@ -1,11 +1,11 @@ -import { isActivityPubUrlValid, isBaseActivityValid } from './misc' +import { isActivityPubUrlValid } from './misc' function isViewActivityValid (activity: any) { - return isBaseActivityValid(activity, 'Create') && - activity.object.type === 'View' && - isActivityPubUrlValid(activity.object.actor) && - isActivityPubUrlValid(activity.object.object) + return activity.type === 'View' && + isActivityPubUrlValid(activity.actor) && + isActivityPubUrlValid(activity.object) } + // --------------------------------------------------------------------------- export { -- cgit v1.2.3 From ef04ae20fe4155f516ab41959e312de093f98d0e Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Thu, 17 Jan 2019 14:03:32 +0100 Subject: Prefer avg_frame_rate to fetch video FPS --- server/helpers/ffmpeg-utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'server/helpers') diff --git a/server/helpers/ffmpeg-utils.ts b/server/helpers/ffmpeg-utils.ts index c7296054d..132f4690e 100644 --- a/server/helpers/ffmpeg-utils.ts +++ b/server/helpers/ffmpeg-utils.ts @@ -41,7 +41,7 @@ async function getVideoFileResolution (path: string) { async function getVideoFileFPS (path: string) { const videoStream = await getVideoFileStream(path) - for (const key of [ 'r_frame_rate' , 'avg_frame_rate' ]) { + for (const key of [ 'avg_frame_rate', 'r_frame_rate' ]) { const valuesText: string = videoStream[key] if (!valuesText) continue -- cgit v1.2.3 From 307902e2b3248073aeb677e420aafd8b5e041117 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Thu, 24 Jan 2019 15:23:06 +0100 Subject: Try to fix Mac video upload --- server/helpers/custom-validators/videos.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'server/helpers') diff --git a/server/helpers/custom-validators/videos.ts b/server/helpers/custom-validators/videos.ts index e6f22e6c5..95e256b8f 100644 --- a/server/helpers/custom-validators/videos.ts +++ b/server/helpers/custom-validators/videos.ts @@ -88,8 +88,8 @@ function isVideoFileExtnameValid (value: string) { function isVideoFile (files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[]) { const videoFileTypesRegex = Object.keys(MIMETYPES.VIDEO.MIMETYPE_EXT) - .map(m => `(${m})`) - .join('|') + .map(m => `(${m})`) + .join('|') return isFileValid(files, videoFileTypesRegex, 'videofile', null) } -- cgit v1.2.3 From 092092969633bbcf6d4891a083ea497a7d5c3154 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 29 Jan 2019 08:37:25 +0100 Subject: Add hls support on server --- server/helpers/activitypub.ts | 5 ++-- server/helpers/core-utils.ts | 8 +++++- .../custom-validators/activitypub/cache-file.ts | 12 +++++++- .../custom-validators/activitypub/videos.ts | 8 ++++-- server/helpers/custom-validators/misc.ts | 5 ++++ server/helpers/ffmpeg-utils.ts | 32 ++++++++++++++++++---- server/helpers/video.ts | 4 ++- 7 files changed, 62 insertions(+), 12 deletions(-) (limited to 'server/helpers') diff --git a/server/helpers/activitypub.ts b/server/helpers/activitypub.ts index f1430055f..eba552524 100644 --- a/server/helpers/activitypub.ts +++ b/server/helpers/activitypub.ts @@ -15,7 +15,7 @@ function activityPubContextify (data: T) { 'https://w3id.org/security/v1', { RsaSignature2017: 'https://w3id.org/security#RsaSignature2017', - pt: 'https://joinpeertube.org/ns', + pt: 'https://joinpeertube.org/ns#', sc: 'http://schema.org#', Hashtag: 'as:Hashtag', uuid: 'sc:identifier', @@ -32,7 +32,8 @@ function activityPubContextify (data: T) { waitTranscoding: 'sc:Boolean', expires: 'sc:expires', support: 'sc:Text', - CacheFile: 'pt:CacheFile' + CacheFile: 'pt:CacheFile', + Infohash: 'pt:Infohash' }, { likes: { diff --git a/server/helpers/core-utils.ts b/server/helpers/core-utils.ts index 3fb824e36..f38b82d97 100644 --- a/server/helpers/core-utils.ts +++ b/server/helpers/core-utils.ts @@ -193,10 +193,14 @@ function peertubeTruncate (str: string, maxLength: number) { return truncate(str, options) } -function sha256 (str: string, encoding: HexBase64Latin1Encoding = 'hex') { +function sha256 (str: string | Buffer, encoding: HexBase64Latin1Encoding = 'hex') { return createHash('sha256').update(str).digest(encoding) } +function sha1 (str: string | Buffer, encoding: HexBase64Latin1Encoding = 'hex') { + return createHash('sha1').update(str).digest(encoding) +} + function promisify0 (func: (cb: (err: any, result: A) => void) => void): () => Promise { return function promisified (): Promise { return new Promise((resolve: (arg: A) => void, reject: (err: any) => void) => { @@ -262,7 +266,9 @@ export { sanitizeHost, buildPath, peertubeTruncate, + sha256, + sha1, promisify0, promisify1, diff --git a/server/helpers/custom-validators/activitypub/cache-file.ts b/server/helpers/custom-validators/activitypub/cache-file.ts index e2bd0c55e..21d5c53ca 100644 --- a/server/helpers/custom-validators/activitypub/cache-file.ts +++ b/server/helpers/custom-validators/activitypub/cache-file.ts @@ -8,9 +8,19 @@ function isCacheFileObjectValid (object: CacheFileObject) { object.type === 'CacheFile' && isDateValid(object.expires) && isActivityPubUrlValid(object.object) && - isRemoteVideoUrlValid(object.url) + (isRemoteVideoUrlValid(object.url) || isPlaylistRedundancyUrlValid(object.url)) } +// --------------------------------------------------------------------------- + export { isCacheFileObjectValid } + +// --------------------------------------------------------------------------- + +function isPlaylistRedundancyUrlValid (url: any) { + return url.type === 'Link' && + (url.mediaType || url.mimeType) === 'application/x-mpegURL' && + isActivityPubUrlValid(url.href) +} diff --git a/server/helpers/custom-validators/activitypub/videos.ts b/server/helpers/custom-validators/activitypub/videos.ts index 0f34aab21..ad99c2724 100644 --- a/server/helpers/custom-validators/activitypub/videos.ts +++ b/server/helpers/custom-validators/activitypub/videos.ts @@ -1,7 +1,7 @@ import * as validator from 'validator' import { ACTIVITY_PUB, CONSTRAINTS_FIELDS } from '../../../initializers' import { peertubeTruncate } from '../../core-utils' -import { exists, isBooleanValid, isDateValid, isUUIDValid } from '../misc' +import { exists, isArray, isBooleanValid, isDateValid, isUUIDValid } from '../misc' import { isVideoDurationValid, isVideoNameValid, @@ -12,7 +12,6 @@ import { } from '../videos' import { isActivityPubUrlValid, isBaseActivityValid, setValidAttributedTo } from './misc' import { VideoState } from '../../../../shared/models/videos' -import { isVideoAbuseReasonValid } from '../video-abuses' function sanitizeAndCheckVideoTorrentUpdateActivity (activity: any) { return isBaseActivityValid(activity, 'Update') && @@ -81,6 +80,11 @@ function isRemoteVideoUrlValid (url: any) { ACTIVITY_PUB.URL_MIME_TYPES.MAGNET.indexOf(url.mediaType || url.mimeType) !== -1 && validator.isLength(url.href, { min: 5 }) && validator.isInt(url.height + '', { min: 0 }) + ) || + ( + (url.mediaType || url.mimeType) === 'application/x-mpegURL' && + isActivityPubUrlValid(url.href) && + isArray(url.tag) ) } diff --git a/server/helpers/custom-validators/misc.ts b/server/helpers/custom-validators/misc.ts index b6f0ebe6f..76647fea2 100644 --- a/server/helpers/custom-validators/misc.ts +++ b/server/helpers/custom-validators/misc.ts @@ -13,6 +13,10 @@ function isNotEmptyIntArray (value: any) { return Array.isArray(value) && value.every(v => validator.isInt('' + v)) && value.length !== 0 } +function isArrayOf (value: any, validator: (value: any) => boolean) { + return isArray(value) && value.every(v => validator(v)) +} + function isDateValid (value: string) { return exists(value) && validator.isISO8601(value) } @@ -82,6 +86,7 @@ function isFileValid ( export { exists, + isArrayOf, isNotEmptyIntArray, isArray, isIdValid, diff --git a/server/helpers/ffmpeg-utils.ts b/server/helpers/ffmpeg-utils.ts index 132f4690e..5ad8ed48e 100644 --- a/server/helpers/ffmpeg-utils.ts +++ b/server/helpers/ffmpeg-utils.ts @@ -1,5 +1,5 @@ import * as ffmpeg from 'fluent-ffmpeg' -import { join } from 'path' +import { dirname, join } from 'path' import { getTargetBitrate, VideoResolution } from '../../shared/models/videos' import { CONFIG, FFMPEG_NICE, VIDEO_TRANSCODING_FPS } from '../initializers/constants' import { processImage } from './image-utils' @@ -29,12 +29,21 @@ function computeResolutionsToTranscode (videoFileHeight: number) { return resolutionsEnabled } -async function getVideoFileResolution (path: string) { +async function getVideoFileSize (path: string) { const videoStream = await getVideoFileStream(path) return { - videoFileResolution: Math.min(videoStream.height, videoStream.width), - isPortraitMode: videoStream.height > videoStream.width + width: videoStream.width, + height: videoStream.height + } +} + +async function getVideoFileResolution (path: string) { + const size = await getVideoFileSize(path) + + return { + videoFileResolution: Math.min(size.height, size.width), + isPortraitMode: size.height > size.width } } @@ -110,8 +119,10 @@ async function generateImageFromVideoFile (fromPath: string, folder: string, ima type TranscodeOptions = { inputPath: string outputPath: string - resolution?: VideoResolution + resolution: VideoResolution isPortraitMode?: boolean + + generateHlsPlaylist?: boolean } function transcode (options: TranscodeOptions) { @@ -150,6 +161,16 @@ function transcode (options: TranscodeOptions) { command = command.withFPS(fps) } + if (options.generateHlsPlaylist) { + const segmentFilename = `${dirname(options.outputPath)}/${options.resolution}_%03d.ts` + + command = command.outputOption('-hls_time 4') + .outputOption('-hls_list_size 0') + .outputOption('-hls_playlist_type vod') + .outputOption('-hls_segment_filename ' + segmentFilename) + .outputOption('-f hls') + } + command .on('error', (err, stdout, stderr) => { logger.error('Error in transcoding job.', { stdout, stderr }) @@ -166,6 +187,7 @@ function transcode (options: TranscodeOptions) { // --------------------------------------------------------------------------- export { + getVideoFileSize, getVideoFileResolution, getDurationFromVideoFile, generateImageFromVideoFile, diff --git a/server/helpers/video.ts b/server/helpers/video.ts index 1bd21467d..c90fe06c7 100644 --- a/server/helpers/video.ts +++ b/server/helpers/video.ts @@ -1,10 +1,12 @@ import { VideoModel } from '../models/video/video' -type VideoFetchType = 'all' | 'only-video' | 'id' | 'none' +type VideoFetchType = 'all' | 'only-video' | 'only-video-with-rights' | 'id' | 'none' function fetchVideo (id: number | string, fetchType: VideoFetchType, userId?: number) { if (fetchType === 'all') return VideoModel.loadAndPopulateAccountAndServerAndTags(id, undefined, userId) + if (fetchType === 'only-video-with-rights') return VideoModel.loadWithRights(id) + if (fetchType === 'only-video') return VideoModel.load(id) if (fetchType === 'id' || fetchType === 'none') return VideoModel.loadOnlyId(id) -- cgit v1.2.3 From 4c280004ce62bf11ddb091854c28f1e1d54a54d6 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Thu, 7 Feb 2019 15:08:19 +0100 Subject: Use a single file instead of segments for HLS --- server/helpers/ffmpeg-utils.ts | 12 ++++++++---- server/helpers/requests.ts | 2 +- server/helpers/utils.ts | 1 - 3 files changed, 9 insertions(+), 6 deletions(-) (limited to 'server/helpers') diff --git a/server/helpers/ffmpeg-utils.ts b/server/helpers/ffmpeg-utils.ts index 5ad8ed48e..133b1b03b 100644 --- a/server/helpers/ffmpeg-utils.ts +++ b/server/helpers/ffmpeg-utils.ts @@ -122,7 +122,9 @@ type TranscodeOptions = { resolution: VideoResolution isPortraitMode?: boolean - generateHlsPlaylist?: boolean + hlsPlaylist?: { + videoFilename: string + } } function transcode (options: TranscodeOptions) { @@ -161,14 +163,16 @@ function transcode (options: TranscodeOptions) { command = command.withFPS(fps) } - if (options.generateHlsPlaylist) { - const segmentFilename = `${dirname(options.outputPath)}/${options.resolution}_%03d.ts` + if (options.hlsPlaylist) { + const videoPath = `${dirname(options.outputPath)}/${options.hlsPlaylist.videoFilename}` command = command.outputOption('-hls_time 4') .outputOption('-hls_list_size 0') .outputOption('-hls_playlist_type vod') - .outputOption('-hls_segment_filename ' + segmentFilename) + .outputOption('-hls_segment_filename ' + videoPath) + .outputOption('-hls_segment_type fmp4') .outputOption('-f hls') + .outputOption('-hls_flags single_file') } command diff --git a/server/helpers/requests.ts b/server/helpers/requests.ts index 3fc776f1a..5c6dc5e19 100644 --- a/server/helpers/requests.ts +++ b/server/helpers/requests.ts @@ -7,7 +7,7 @@ import { join } from 'path' function doRequest ( requestOptions: request.CoreOptions & request.UriOptions & { activityPub?: boolean } -): Bluebird<{ response: request.RequestResponse, body: any }> { +): Bluebird<{ response: request.RequestResponse, body: T }> { if (requestOptions.activityPub === true) { if (!Array.isArray(requestOptions.headers)) requestOptions.headers = {} requestOptions.headers['accept'] = ACTIVITY_PUB.ACCEPT_HEADER diff --git a/server/helpers/utils.ts b/server/helpers/utils.ts index 3c3406e38..cb0e823c5 100644 --- a/server/helpers/utils.ts +++ b/server/helpers/utils.ts @@ -7,7 +7,6 @@ import { join } from 'path' import { Instance as ParseTorrent } from 'parse-torrent' import { remove } from 'fs-extra' import * as memoizee from 'memoizee' -import { isArray } from './custom-validators/misc' function deleteFileAsync (path: string) { remove(path) -- cgit v1.2.3