aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/helpers
diff options
context:
space:
mode:
Diffstat (limited to 'server/helpers')
-rw-r--r--server/helpers/core-utils.ts48
-rw-r--r--server/helpers/custom-validators/activitypub/videos.ts7
-rw-r--r--server/helpers/custom-validators/users.ts5
-rw-r--r--server/helpers/custom-validators/videos.ts13
-rw-r--r--server/helpers/express-utils.ts3
-rw-r--r--server/helpers/ffmpeg-utils.ts167
-rw-r--r--server/helpers/peertube-crypto.ts5
-rw-r--r--server/helpers/utils.ts22
-rw-r--r--server/helpers/video.ts4
9 files changed, 194 insertions, 80 deletions
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 = {
21 week: 3600000 * 24 * 7, 21 week: 3600000 * 24 * 7,
22 month: 3600000 * 24 * 30 22 month: 3600000 * 24 * 30
23} 23}
24
24export function parseDuration (duration: number | string): number { 25export function parseDuration (duration: number | string): number {
25 if (typeof duration === 'number') return duration 26 if (typeof duration === 'number') return duration
26 27
@@ -41,6 +42,53 @@ export function parseDuration (duration: number | string): number {
41 throw new Error('Duration could not be properly parsed') 42 throw new Error('Duration could not be properly parsed')
42} 43}
43 44
45export function parseBytes (value: string | number): number {
46 if (typeof value === 'number') return value
47
48 const tgm = /^(\d+)\s*TB\s*(\d+)\s*GB\s*(\d+)\s*MB$/
49 const tg = /^(\d+)\s*TB\s*(\d+)\s*GB$/
50 const tm = /^(\d+)\s*TB\s*(\d+)\s*MB$/
51 const gm = /^(\d+)\s*GB\s*(\d+)\s*MB$/
52 const t = /^(\d+)\s*TB$/
53 const g = /^(\d+)\s*GB$/
54 const m = /^(\d+)\s*MB$/
55 const b = /^(\d+)\s*B$/
56 let match
57
58 if (value.match(tgm)) {
59 match = value.match(tgm)
60 return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024
61 + parseInt(match[2], 10) * 1024 * 1024 * 1024
62 + parseInt(match[3], 10) * 1024 * 1024
63 } else if (value.match(tg)) {
64 match = value.match(tg)
65 return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024
66 + parseInt(match[2], 10) * 1024 * 1024 * 1024
67 } else if (value.match(tm)) {
68 match = value.match(tm)
69 return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024
70 + parseInt(match[2], 10) * 1024 * 1024
71 } else if (value.match(gm)) {
72 match = value.match(gm)
73 return parseInt(match[1], 10) * 1024 * 1024 * 1024
74 + parseInt(match[2], 10) * 1024 * 1024
75 } else if (value.match(t)) {
76 match = value.match(t)
77 return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024
78 } else if (value.match(g)) {
79 match = value.match(g)
80 return parseInt(match[1], 10) * 1024 * 1024 * 1024
81 } else if (value.match(m)) {
82 match = value.match(m)
83 return parseInt(match[1], 10) * 1024 * 1024
84 } else if (value.match(b)) {
85 match = value.match(b)
86 return parseInt(match[1], 10) * 1024
87 } else {
88 return parseInt(value, 10)
89 }
90}
91
44function sanitizeUrl (url: string) { 92function sanitizeUrl (url: string) {
45 const urlObject = new URL(url) 93 const urlObject = new URL(url)
46 94
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) {
81 81
82 return url.type === 'Link' && 82 return url.type === 'Link' &&
83 ( 83 (
84 ACTIVITY_PUB.URL_MIME_TYPES.VIDEO.indexOf(url.mimeType) !== -1 && 84 // TODO: remove mimeType (backward compatibility, introduced in v1.1.0)
85 ACTIVITY_PUB.URL_MIME_TYPES.VIDEO.indexOf(url.mediaType || url.mimeType) !== -1 &&
85 isActivityPubUrlValid(url.href) && 86 isActivityPubUrlValid(url.href) &&
86 validator.isInt(url.height + '', { min: 0 }) && 87 validator.isInt(url.height + '', { min: 0 }) &&
87 validator.isInt(url.size + '', { min: 0 }) && 88 validator.isInt(url.size + '', { min: 0 }) &&
88 (!url.fps || validator.isInt(url.fps + '', { min: -1 })) 89 (!url.fps || validator.isInt(url.fps + '', { min: -1 }))
89 ) || 90 ) ||
90 ( 91 (
91 ACTIVITY_PUB.URL_MIME_TYPES.TORRENT.indexOf(url.mimeType) !== -1 && 92 ACTIVITY_PUB.URL_MIME_TYPES.TORRENT.indexOf(url.mediaType || url.mimeType) !== -1 &&
92 isActivityPubUrlValid(url.href) && 93 isActivityPubUrlValid(url.href) &&
93 validator.isInt(url.height + '', { min: 0 }) 94 validator.isInt(url.height + '', { min: 0 })
94 ) || 95 ) ||
95 ( 96 (
96 ACTIVITY_PUB.URL_MIME_TYPES.MAGNET.indexOf(url.mimeType) !== -1 && 97 ACTIVITY_PUB.URL_MIME_TYPES.MAGNET.indexOf(url.mediaType || url.mimeType) !== -1 &&
97 validator.isLength(url.href, { min: 5 }) && 98 validator.isLength(url.href, { min: 5 }) &&
98 validator.isInt(url.height + '', { min: 0 }) 99 validator.isInt(url.height + '', { min: 0 })
99 ) 100 )
diff --git a/server/helpers/custom-validators/users.ts b/server/helpers/custom-validators/users.ts
index 90fc74a48..1cb5e5b0f 100644
--- a/server/helpers/custom-validators/users.ts
+++ b/server/helpers/custom-validators/users.ts
@@ -42,6 +42,10 @@ function isUserNSFWPolicyValid (value: any) {
42 return exists(value) && nsfwPolicies.indexOf(value) !== -1 42 return exists(value) && nsfwPolicies.indexOf(value) !== -1
43} 43}
44 44
45function isUserWebTorrentEnabledValid (value: any) {
46 return isBooleanValid(value)
47}
48
45function isUserAutoPlayVideoValid (value: any) { 49function isUserAutoPlayVideoValid (value: any) {
46 return isBooleanValid(value) 50 return isBooleanValid(value)
47} 51}
@@ -78,6 +82,7 @@ export {
78 isUserUsernameValid, 82 isUserUsernameValid,
79 isUserEmailVerifiedValid, 83 isUserEmailVerifiedValid,
80 isUserNSFWPolicyValid, 84 isUserNSFWPolicyValid,
85 isUserWebTorrentEnabledValid,
81 isUserAutoPlayVideoValid, 86 isUserAutoPlayVideoValid,
82 isUserDisplayNameValid, 87 isUserDisplayNameValid,
83 isUserDescriptionValid, 88 isUserDescriptionValid,
diff --git a/server/helpers/custom-validators/videos.ts b/server/helpers/custom-validators/videos.ts
index 9875c68bd..a13b09ac8 100644
--- a/server/helpers/custom-validators/videos.ts
+++ b/server/helpers/custom-validators/videos.ts
@@ -3,7 +3,7 @@ import 'express-validator'
3import { values } from 'lodash' 3import { values } from 'lodash'
4import 'multer' 4import 'multer'
5import * as validator from 'validator' 5import * as validator from 'validator'
6import { UserRight, VideoPrivacy, VideoRateType } from '../../../shared' 6import { UserRight, VideoFilter, VideoPrivacy, VideoRateType } from '../../../shared'
7import { 7import {
8 CONSTRAINTS_FIELDS, 8 CONSTRAINTS_FIELDS,
9 VIDEO_CATEGORIES, 9 VIDEO_CATEGORIES,
@@ -22,6 +22,10 @@ import { fetchVideo, VideoFetchType } from '../video'
22 22
23const VIDEOS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEOS 23const VIDEOS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEOS
24 24
25function isVideoFilterValid (filter: VideoFilter) {
26 return filter === 'local' || filter === 'all-local'
27}
28
25function isVideoCategoryValid (value: any) { 29function isVideoCategoryValid (value: any) {
26 return value === null || VIDEO_CATEGORIES[ value ] !== undefined 30 return value === null || VIDEO_CATEGORIES[ value ] !== undefined
27} 31}
@@ -154,7 +158,9 @@ function checkUserCanManageVideo (user: UserModel, video: VideoModel, right: Use
154} 158}
155 159
156async function isVideoExist (id: string, res: Response, fetchType: VideoFetchType = 'all') { 160async function isVideoExist (id: string, res: Response, fetchType: VideoFetchType = 'all') {
157 const video = await fetchVideo(id, fetchType) 161 const userId = res.locals.oauth ? res.locals.oauth.token.User.id : undefined
162
163 const video = await fetchVideo(id, fetchType, userId)
158 164
159 if (video === null) { 165 if (video === null) {
160 res.status(404) 166 res.status(404)
@@ -223,5 +229,6 @@ export {
223 isVideoExist, 229 isVideoExist,
224 isVideoImage, 230 isVideoImage,
225 isVideoChannelOfAccountExist, 231 isVideoChannelOfAccountExist,
226 isVideoSupportValid 232 isVideoSupportValid,
233 isVideoFilterValid
227} 234}
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'
2import * as multer from 'multer' 2import * as multer from 'multer'
3import { CONFIG, REMOTE_SCHEME } from '../initializers' 3import { CONFIG, REMOTE_SCHEME } from '../initializers'
4import { logger } from './logger' 4import { logger } from './logger'
5import { User } from '../../shared/models/users'
6import { deleteFileAsync, generateRandomString } from './utils' 5import { deleteFileAsync, generateRandomString } from './utils'
7import { extname } from 'path' 6import { extname } from 'path'
8import { isArray } from './custom-validators/misc' 7import { isArray } from './custom-validators/misc'
@@ -101,7 +100,7 @@ function createReqFiles (
101} 100}
102 101
103function isUserAbleToSearchRemoteURI (res: express.Response) { 102function isUserAbleToSearchRemoteURI (res: express.Response) {
104 const user: User = res.locals.oauth ? res.locals.oauth.token.User : undefined 103 const user: UserModel = res.locals.oauth ? res.locals.oauth.token.User : undefined
105 104
106 return CONFIG.SEARCH.REMOTE_URI.ANONYMOUS === true || 105 return CONFIG.SEARCH.REMOTE_URI.ANONYMOUS === true ||
107 (CONFIG.SEARCH.REMOTE_URI.USERS === true && user !== undefined) 106 (CONFIG.SEARCH.REMOTE_URI.USERS === true && user !== undefined)
diff --git a/server/helpers/ffmpeg-utils.ts b/server/helpers/ffmpeg-utils.ts
index 22bc25476..8b9045038 100644
--- a/server/helpers/ffmpeg-utils.ts
+++ b/server/helpers/ffmpeg-utils.ts
@@ -1,6 +1,6 @@
1import * as ffmpeg from 'fluent-ffmpeg' 1import * as ffmpeg from 'fluent-ffmpeg'
2import { join } from 'path' 2import { join } from 'path'
3import { VideoResolution } from '../../shared/models/videos' 3import { getTargetBitrate, VideoResolution } from '../../shared/models/videos'
4import { CONFIG, FFMPEG_NICE, VIDEO_TRANSCODING_FPS } from '../initializers' 4import { CONFIG, FFMPEG_NICE, VIDEO_TRANSCODING_FPS } from '../initializers'
5import { processImage } from './image-utils' 5import { processImage } from './image-utils'
6import { logger } from './logger' 6import { logger } from './logger'
@@ -55,6 +55,16 @@ async function getVideoFileFPS (path: string) {
55 return 0 55 return 0
56} 56}
57 57
58async function getVideoFileBitrate (path: string) {
59 return new Promise<number>((res, rej) => {
60 ffmpeg.ffprobe(path, (err, metadata) => {
61 if (err) return rej(err)
62
63 return res(metadata.format.bit_rate)
64 })
65 })
66}
67
58function getDurationFromVideoFile (path: string) { 68function getDurationFromVideoFile (path: string) {
59 return new Promise<number>((res, rej) => { 69 return new Promise<number>((res, rej) => {
60 ffmpeg.ffprobe(path, (err, metadata) => { 70 ffmpeg.ffprobe(path, (err, metadata) => {
@@ -106,45 +116,50 @@ type TranscodeOptions = {
106 116
107function transcode (options: TranscodeOptions) { 117function transcode (options: TranscodeOptions) {
108 return new Promise<void>(async (res, rej) => { 118 return new Promise<void>(async (res, rej) => {
109 let command = ffmpeg(options.inputPath, { niceness: FFMPEG_NICE.TRANSCODING }) 119 try {
110 .output(options.outputPath) 120 let fps = await getVideoFileFPS(options.inputPath)
111 .preset(standard)
112
113 if (CONFIG.TRANSCODING.THREADS > 0) {
114 // if we don't set any threads ffmpeg will chose automatically
115 command = command.outputOption('-threads ' + CONFIG.TRANSCODING.THREADS)
116 }
117
118 let fps = await getVideoFileFPS(options.inputPath)
119 if (options.resolution !== undefined) {
120 // '?x720' or '720x?' for example
121 const size = options.isPortraitMode === true ? `${options.resolution}x?` : `?x${options.resolution}`
122 command = command.size(size)
123
124 // On small/medium resolutions, limit FPS 121 // On small/medium resolutions, limit FPS
125 if ( 122 if (
123 options.resolution !== undefined &&
126 options.resolution < VIDEO_TRANSCODING_FPS.KEEP_ORIGIN_FPS_RESOLUTION_MIN && 124 options.resolution < VIDEO_TRANSCODING_FPS.KEEP_ORIGIN_FPS_RESOLUTION_MIN &&
127 fps > VIDEO_TRANSCODING_FPS.AVERAGE 125 fps > VIDEO_TRANSCODING_FPS.AVERAGE
128 ) { 126 ) {
129 fps = VIDEO_TRANSCODING_FPS.AVERAGE 127 fps = VIDEO_TRANSCODING_FPS.AVERAGE
130 } 128 }
131 }
132 129
133 if (fps) { 130 let command = ffmpeg(options.inputPath, { niceness: FFMPEG_NICE.TRANSCODING })
134 // Hard FPS limits 131 .output(options.outputPath)
135 if (fps > VIDEO_TRANSCODING_FPS.MAX) fps = VIDEO_TRANSCODING_FPS.MAX 132 command = await presetH264(command, options.resolution, fps)
136 else if (fps < VIDEO_TRANSCODING_FPS.MIN) fps = VIDEO_TRANSCODING_FPS.MIN
137 133
138 command = command.withFPS(fps) 134 if (CONFIG.TRANSCODING.THREADS > 0) {
139 } 135 // if we don't set any threads ffmpeg will chose automatically
136 command = command.outputOption('-threads ' + CONFIG.TRANSCODING.THREADS)
137 }
138
139 if (options.resolution !== undefined) {
140 // '?x720' or '720x?' for example
141 const size = options.isPortraitMode === true ? `${options.resolution}x?` : `?x${options.resolution}`
142 command = command.size(size)
143 }
144
145 if (fps) {
146 // Hard FPS limits
147 if (fps > VIDEO_TRANSCODING_FPS.MAX) fps = VIDEO_TRANSCODING_FPS.MAX
148 else if (fps < VIDEO_TRANSCODING_FPS.MIN) fps = VIDEO_TRANSCODING_FPS.MIN
140 149
141 command 150 command = command.withFPS(fps)
142 .on('error', (err, stdout, stderr) => { 151 }
143 logger.error('Error in transcoding job.', { stdout, stderr }) 152
144 return rej(err) 153 command
145 }) 154 .on('error', (err, stdout, stderr) => {
146 .on('end', res) 155 logger.error('Error in transcoding job.', { stdout, stderr })
147 .run() 156 return rej(err)
157 })
158 .on('end', res)
159 .run()
160 } catch (err) {
161 return rej(err)
162 }
148 }) 163 })
149} 164}
150 165
@@ -157,7 +172,8 @@ export {
157 transcode, 172 transcode,
158 getVideoFileFPS, 173 getVideoFileFPS,
159 computeResolutionsToTranscode, 174 computeResolutionsToTranscode,
160 audio 175 audio,
176 getVideoFileBitrate
161} 177}
162 178
163// --------------------------------------------------------------------------- 179// ---------------------------------------------------------------------------
@@ -182,11 +198,10 @@ function getVideoFileStream (path: string) {
182 * and quality. Superfast and ultrafast will give you better 198 * and quality. Superfast and ultrafast will give you better
183 * performance, but then quality is noticeably worse. 199 * performance, but then quality is noticeably worse.
184 */ 200 */
185function veryfast (_ffmpeg) { 201async function presetH264VeryFast (command: ffmpeg.FfmpegCommand, resolution: VideoResolution, fps: number): Promise<ffmpeg.FfmpegCommand> {
186 _ffmpeg 202 let localCommand = await presetH264(command, resolution, fps)
187 .preset(standard) 203 localCommand = localCommand.outputOption('-preset:v veryfast')
188 .outputOption('-preset:v veryfast') 204 .outputOption([ '--aq-mode=2', '--aq-strength=1.3' ])
189 .outputOption(['--aq-mode=2', '--aq-strength=1.3'])
190 /* 205 /*
191 MAIN reference: https://slhck.info/video/2017/03/01/rate-control.html 206 MAIN reference: https://slhck.info/video/2017/03/01/rate-control.html
192 Our target situation is closer to a livestream than a stream, 207 Our target situation is closer to a livestream than a stream,
@@ -198,31 +213,39 @@ function veryfast (_ffmpeg) {
198 Make up for most of the loss of grain and macroblocking 213 Make up for most of the loss of grain and macroblocking
199 with less computing power. 214 with less computing power.
200 */ 215 */
216
217 return localCommand
201} 218}
202 219
203/** 220/**
204 * A preset optimised for a stillimage audio video 221 * A preset optimised for a stillimage audio video
205 */ 222 */
206function audio (_ffmpeg) { 223async function presetStillImageWithAudio (
207 _ffmpeg 224 command: ffmpeg.FfmpegCommand,
208 .preset(veryfast) 225 resolution: VideoResolution,
209 .outputOption('-tune stillimage') 226 fps: number
227): Promise<ffmpeg.FfmpegCommand> {
228 let localCommand = await presetH264VeryFast(command, resolution, fps)
229 localCommand = localCommand.outputOption('-tune stillimage')
230
231 return localCommand
210} 232}
211 233
212/** 234/**
213 * A toolbox to play with audio 235 * A toolbox to play with audio
214 */ 236 */
215namespace audio { 237namespace audio {
216 export const get = (_ffmpeg, pos: number | string = 0) => { 238 export const get = (option: ffmpeg.FfmpegCommand | string) => {
217 // without position, ffprobe considers the last input only 239 // without position, ffprobe considers the last input only
218 // we make it consider the first input only 240 // we make it consider the first input only
219 // if you pass a file path to pos, then ffprobe acts on that file directly 241 // if you pass a file path to pos, then ffprobe acts on that file directly
220 return new Promise<{ absolutePath: string, audioStream?: any }>((res, rej) => { 242 return new Promise<{ absolutePath: string, audioStream?: any }>((res, rej) => {
221 _ffmpeg.ffprobe(pos, (err,data) => { 243
244 function parseFfprobe (err: any, data: ffmpeg.FfprobeData) {
222 if (err) return rej(err) 245 if (err) return rej(err)
223 246
224 if ('streams' in data) { 247 if ('streams' in data) {
225 const audioStream = data['streams'].find(stream => stream['codec_type'] === 'audio') 248 const audioStream = data.streams.find(stream => stream['codec_type'] === 'audio')
226 if (audioStream) { 249 if (audioStream) {
227 return res({ 250 return res({
228 absolutePath: data.format.filename, 251 absolutePath: data.format.filename,
@@ -230,8 +253,15 @@ namespace audio {
230 }) 253 })
231 } 254 }
232 } 255 }
256
233 return res({ absolutePath: data.format.filename }) 257 return res({ absolutePath: data.format.filename })
234 }) 258 }
259
260 if (typeof option === 'string') {
261 return ffmpeg.ffprobe(option, parseFfprobe)
262 }
263
264 return option.ffprobe(parseFfprobe)
235 }) 265 })
236 } 266 }
237 267
@@ -273,39 +303,48 @@ namespace audio {
273 * As for the audio, quality '5' is the highest and ensures 96-112kbps/channel 303 * As for the audio, quality '5' is the highest and ensures 96-112kbps/channel
274 * See https://trac.ffmpeg.org/wiki/Encode/AAC#fdk_vbr 304 * See https://trac.ffmpeg.org/wiki/Encode/AAC#fdk_vbr
275 */ 305 */
276async function standard (_ffmpeg) { 306async function presetH264 (command: ffmpeg.FfmpegCommand, resolution: VideoResolution, fps: number): Promise<ffmpeg.FfmpegCommand> {
277 let localFfmpeg = _ffmpeg 307 let localCommand = command
278 .format('mp4') 308 .format('mp4')
279 .videoCodec('libx264') 309 .videoCodec('libx264')
280 .outputOption('-level 3.1') // 3.1 is the minimal ressource allocation for our highest supported resolution 310 .outputOption('-level 3.1') // 3.1 is the minimal ressource allocation for our highest supported resolution
281 .outputOption('-b_strategy 1') // NOTE: b-strategy 1 - heuristic algorythm, 16 is optimal B-frames for it 311 .outputOption('-b_strategy 1') // NOTE: b-strategy 1 - heuristic algorythm, 16 is optimal B-frames for it
282 .outputOption('-bf 16') // NOTE: Why 16: https://github.com/Chocobozzz/PeerTube/pull/774. b-strategy 2 -> B-frames<16 312 .outputOption('-bf 16') // NOTE: Why 16: https://github.com/Chocobozzz/PeerTube/pull/774. b-strategy 2 -> B-frames<16
313 .outputOption('-pix_fmt yuv420p') // allows import of source material with incompatible pixel formats (e.g. MJPEG video)
283 .outputOption('-map_metadata -1') // strip all metadata 314 .outputOption('-map_metadata -1') // strip all metadata
284 .outputOption('-movflags faststart') 315 .outputOption('-movflags faststart')
285 const _audio = await audio.get(localFfmpeg)
286 316
287 if (!_audio.audioStream) { 317 const parsedAudio = await audio.get(localCommand)
288 return localFfmpeg.noAudio()
289 }
290 318
291 // we favor VBR, if a good AAC encoder is available 319 if (!parsedAudio.audioStream) {
292 if ((await checkFFmpegEncoders()).get('libfdk_aac')) { 320 localCommand = localCommand.noAudio()
293 return localFfmpeg 321 } else if ((await checkFFmpegEncoders()).get('libfdk_aac')) { // we favor VBR, if a good AAC encoder is available
322 localCommand = localCommand
294 .audioCodec('libfdk_aac') 323 .audioCodec('libfdk_aac')
295 .audioQuality(5) 324 .audioQuality(5)
325 } else {
326 // we try to reduce the ceiling bitrate by making rough correspondances of bitrates
327 // of course this is far from perfect, but it might save some space in the end
328 const audioCodecName = parsedAudio.audioStream[ 'codec_name' ]
329 let bitrate: number
330 if (audio.bitrate[ audioCodecName ]) {
331 bitrate = audio.bitrate[ audioCodecName ](parsedAudio.audioStream[ 'bit_rate' ])
332
333 if (bitrate === -1) localCommand = localCommand.audioCodec('copy')
334 else if (bitrate !== undefined) localCommand = localCommand.audioBitrate(bitrate)
335 }
296 } 336 }
297 337
298 // we try to reduce the ceiling bitrate by making rough correspondances of bitrates 338 // Constrained Encoding (VBV)
299 // of course this is far from perfect, but it might save some space in the end 339 // https://slhck.info/video/2017/03/01/rate-control.html
300 const audioCodecName = _audio.audioStream['codec_name'] 340 // https://trac.ffmpeg.org/wiki/Limiting%20the%20output%20bitrate
301 let bitrate: number 341 const targetBitrate = getTargetBitrate(resolution, fps, VIDEO_TRANSCODING_FPS)
302 if (audio.bitrate[audioCodecName]) { 342 localCommand = localCommand.outputOptions([`-maxrate ${ targetBitrate }`, `-bufsize ${ targetBitrate * 2 }`])
303 bitrate = audio.bitrate[audioCodecName](_audio.audioStream['bit_rate'])
304
305 if (bitrate === -1) return localFfmpeg.audioCodec('copy')
306 }
307 343
308 if (bitrate !== undefined) return localFfmpeg.audioBitrate(bitrate) 344 // Keyframe interval of 2 seconds for faster seeking and resolution switching.
345 // https://streaminglearningcenter.com/blogs/whats-the-right-keyframe-interval.html
346 // https://superuser.com/a/908325
347 localCommand = localCommand.outputOption(`-g ${ fps * 2 }`)
309 348
310 return localFfmpeg 349 return localCommand
311} 350}
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)
62 62
63 return jsig.promises 63 return jsig.promises
64 .verify(signedDocument, options) 64 .verify(signedDocument, options)
65 .then((result: { verified: boolean }) => { 65 .then((result: { verified: boolean }) => result.verified)
66 logger.info('coucou', result)
67 return result.verified
68 })
69 .catch(err => { 66 .catch(err => {
70 logger.error('Cannot check signature.', { err }) 67 logger.error('Cannot check signature.', { err })
71 return false 68 return false
diff --git a/server/helpers/utils.ts b/server/helpers/utils.ts
index 6228fec04..049c3f8bc 100644
--- a/server/helpers/utils.ts
+++ b/server/helpers/utils.ts
@@ -40,7 +40,10 @@ const getServerActor = memoizee(async function () {
40 const application = await ApplicationModel.load() 40 const application = await ApplicationModel.load()
41 if (!application) throw Error('Could not load Application from database.') 41 if (!application) throw Error('Could not load Application from database.')
42 42
43 return application.Account.Actor 43 const actor = application.Account.Actor
44 actor.Account = application.Account
45
46 return actor
44}) 47})
45 48
46function generateVideoTmpPath (target: string | ParseTorrent) { 49function generateVideoTmpPath (target: string | ParseTorrent) {
@@ -77,6 +80,20 @@ async function getVersion () {
77 return require('../../../package.json').version 80 return require('../../../package.json').version
78} 81}
79 82
83/**
84 * From a filename like "ede4cba5-742b-46fa-a388-9a6eb3a3aeb3.mp4", returns
85 * only the "ede4cba5-742b-46fa-a388-9a6eb3a3aeb3" part. If the filename does
86 * not contain a UUID, returns null.
87 */
88function getUUIDFromFilename (filename: string) {
89 const regex = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/
90 const result = filename.match(regex)
91
92 if (!result || Array.isArray(result) === false) return null
93
94 return result[0]
95}
96
80// --------------------------------------------------------------------------- 97// ---------------------------------------------------------------------------
81 98
82export { 99export {
@@ -86,5 +103,6 @@ export {
86 getSecureTorrentName, 103 getSecureTorrentName,
87 getServerActor, 104 getServerActor,
88 getVersion, 105 getVersion,
89 generateVideoTmpPath 106 generateVideoTmpPath,
107 getUUIDFromFilename
90} 108}
diff --git a/server/helpers/video.ts b/server/helpers/video.ts
index b1577a6b0..1bd21467d 100644
--- a/server/helpers/video.ts
+++ b/server/helpers/video.ts
@@ -2,8 +2,8 @@ import { VideoModel } from '../models/video/video'
2 2
3type VideoFetchType = 'all' | 'only-video' | 'id' | 'none' 3type VideoFetchType = 'all' | 'only-video' | 'id' | 'none'
4 4
5function fetchVideo (id: number | string, fetchType: VideoFetchType) { 5function fetchVideo (id: number | string, fetchType: VideoFetchType, userId?: number) {
6 if (fetchType === 'all') return VideoModel.loadAndPopulateAccountAndServerAndTags(id) 6 if (fetchType === 'all') return VideoModel.loadAndPopulateAccountAndServerAndTags(id, undefined, userId)
7 7
8 if (fetchType === 'only-video') return VideoModel.load(id) 8 if (fetchType === 'only-video') return VideoModel.load(id)
9 9