aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2019-05-16 08:58:39 +0200
committerChocobozzz <me@florianbigard.com>2019-05-16 08:58:39 +0200
commit1600235a2f4e30c5d4e7d4342d1c299845decc60 (patch)
tree8d170d3133a978526474532fc33ac4ba4878f632
parentbec4ea343987c69252b84d02f444c0f033d4a3f9 (diff)
parent658a47ab6812586537c3db8a5a003e287c47beb7 (diff)
downloadPeerTube-1600235a2f4e30c5d4e7d4342d1c299845decc60.tar.gz
PeerTube-1600235a2f4e30c5d4e7d4342d1c299845decc60.tar.zst
PeerTube-1600235a2f4e30c5d4e7d4342d1c299845decc60.zip
Merge remote-tracking branch 'origin/pr/1785' into develop
-rw-r--r--server/helpers/ffmpeg-utils.ts49
-rw-r--r--server/lib/video-transcoding.ts13
-rw-r--r--server/tests/api/videos/video-transcoder.ts20
3 files changed, 72 insertions, 10 deletions
diff --git a/server/helpers/ffmpeg-utils.ts b/server/helpers/ffmpeg-utils.ts
index 76b744de8..2fdf34cb7 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 { dirname, join } from 'path' 2import { dirname, join } from 'path'
3import { getTargetBitrate, VideoResolution } from '../../shared/models/videos' 3import { getTargetBitrate, getMaxBitrate, VideoResolution } from '../../shared/models/videos'
4import { FFMPEG_NICE, VIDEO_TRANSCODING_FPS } from '../initializers/constants' 4import { FFMPEG_NICE, VIDEO_TRANSCODING_FPS } from '../initializers/constants'
5import { processImage } from './image-utils' 5import { processImage } from './image-utils'
6import { logger } from './logger' 6import { logger } from './logger'
@@ -31,7 +31,7 @@ function computeResolutionsToTranscode (videoFileHeight: number) {
31} 31}
32 32
33async function getVideoFileSize (path: string) { 33async function getVideoFileSize (path: string) {
34 const videoStream = await getVideoFileStream(path) 34 const videoStream = await getVideoStreamFromFile(path)
35 35
36 return { 36 return {
37 width: videoStream.width, 37 width: videoStream.width,
@@ -49,7 +49,7 @@ async function getVideoFileResolution (path: string) {
49} 49}
50 50
51async function getVideoFileFPS (path: string) { 51async function getVideoFileFPS (path: string) {
52 const videoStream = await getVideoFileStream(path) 52 const videoStream = await getVideoStreamFromFile(path)
53 53
54 for (const key of [ 'avg_frame_rate', 'r_frame_rate' ]) { 54 for (const key of [ 'avg_frame_rate', 'r_frame_rate' ]) {
55 const valuesText: string = videoStream[key] 55 const valuesText: string = videoStream[key]
@@ -122,6 +122,7 @@ type TranscodeOptions = {
122 outputPath: string 122 outputPath: string
123 resolution: VideoResolution 123 resolution: VideoResolution
124 isPortraitMode?: boolean 124 isPortraitMode?: boolean
125 doQuickTranscode?: Boolean
125 126
126 hlsPlaylist?: { 127 hlsPlaylist?: {
127 videoFilename: string 128 videoFilename: string
@@ -134,7 +135,18 @@ function transcode (options: TranscodeOptions) {
134 let command = ffmpeg(options.inputPath, { niceness: FFMPEG_NICE.TRANSCODING }) 135 let command = ffmpeg(options.inputPath, { niceness: FFMPEG_NICE.TRANSCODING })
135 .output(options.outputPath) 136 .output(options.outputPath)
136 137
137 if (options.hlsPlaylist) { 138 if (options.doQuickTranscode) {
139 if (options.hlsPlaylist) {
140 throw(Error("Quick transcode and HLS can't be used at the same time"))
141 }
142
143 command
144 .format('mp4')
145 .addOption('-c:v copy')
146 .addOption('-c:a copy')
147 .outputOption('-map_metadata -1') // strip all metadata
148 .outputOption('-movflags faststart')
149 } else if (options.hlsPlaylist) {
138 command = await buildHLSCommand(command, options) 150 command = await buildHLSCommand(command, options)
139 } else { 151 } else {
140 command = await buildx264Command(command, options) 152 command = await buildx264Command(command, options)
@@ -162,6 +174,30 @@ function transcode (options: TranscodeOptions) {
162 }) 174 })
163} 175}
164 176
177async function canDoQuickTranscode (path: string): Promise<boolean> {
178 // NOTE: This could be optimized by running ffprobe only once (but it runs fast anyway)
179 const videoStream = await getVideoStreamFromFile(path)
180 const parsedAudio = await audio.get(path)
181 const fps = await getVideoFileFPS(path)
182 const bitRate = await getVideoFileBitrate(path)
183 const resolution = await getVideoFileResolution(path)
184
185 // check video params
186 if (videoStream[ 'codec_name' ] !== 'h264') return false
187 if (fps < VIDEO_TRANSCODING_FPS.MIN || fps > VIDEO_TRANSCODING_FPS.MAX) return false
188 if (bitRate > getMaxBitrate(resolution.videoFileResolution, fps, VIDEO_TRANSCODING_FPS)) return false
189
190 // check audio params (if audio stream exists)
191 if (parsedAudio.audioStream) {
192 if (parsedAudio.audioStream[ 'codec_name' ] !== 'aac') return false
193
194 const maxAudioBitrate = audio.bitrate[ 'aac' ](parsedAudio.audioStream[ 'bit_rate' ])
195 if (maxAudioBitrate !== -1 && parsedAudio.audioStream[ 'bit_rate' ] > maxAudioBitrate) return false
196 }
197
198 return true
199}
200
165// --------------------------------------------------------------------------- 201// ---------------------------------------------------------------------------
166 202
167export { 203export {
@@ -173,7 +209,8 @@ export {
173 getVideoFileFPS, 209 getVideoFileFPS,
174 computeResolutionsToTranscode, 210 computeResolutionsToTranscode,
175 audio, 211 audio,
176 getVideoFileBitrate 212 getVideoFileBitrate,
213 canDoQuickTranscode
177} 214}
178 215
179// --------------------------------------------------------------------------- 216// ---------------------------------------------------------------------------
@@ -243,7 +280,7 @@ async function onTranscodingSuccess (options: TranscodeOptions) {
243 await writeFile(options.outputPath, newContent) 280 await writeFile(options.outputPath, newContent)
244} 281}
245 282
246function getVideoFileStream (path: string) { 283function getVideoStreamFromFile (path: string) {
247 return new Promise<any>((res, rej) => { 284 return new Promise<any>((res, rej) => {
248 ffmpeg.ffprobe(path, (err, metadata) => { 285 ffmpeg.ffprobe(path, (err, metadata) => {
249 if (err) return rej(err) 286 if (err) return rej(err)
diff --git a/server/lib/video-transcoding.ts b/server/lib/video-transcoding.ts
index 0fe0ff12a..8e906a1eb 100644
--- a/server/lib/video-transcoding.ts
+++ b/server/lib/video-transcoding.ts
@@ -1,6 +1,6 @@
1import { HLS_STREAMING_PLAYLIST_DIRECTORY, P2P_MEDIA_LOADER_PEER_VERSION, WEBSERVER } from '../initializers/constants' 1import { HLS_STREAMING_PLAYLIST_DIRECTORY, P2P_MEDIA_LOADER_PEER_VERSION, WEBSERVER } from '../initializers/constants'
2import { join } from 'path' 2import { join } from 'path'
3import { getVideoFileFPS, transcode } from '../helpers/ffmpeg-utils' 3import { getVideoFileFPS, transcode, canDoQuickTranscode } from '../helpers/ffmpeg-utils'
4import { ensureDir, move, remove, stat } from 'fs-extra' 4import { ensureDir, move, remove, stat } from 'fs-extra'
5import { logger } from '../helpers/logger' 5import { logger } from '../helpers/logger'
6import { VideoResolution } from '../../shared/models/videos' 6import { VideoResolution } from '../../shared/models/videos'
@@ -11,6 +11,9 @@ import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-pla
11import { VideoStreamingPlaylistType } from '../../shared/models/videos/video-streaming-playlist.type' 11import { VideoStreamingPlaylistType } from '../../shared/models/videos/video-streaming-playlist.type'
12import { CONFIG } from '../initializers/config' 12import { CONFIG } from '../initializers/config'
13 13
14/**
15 * Optimize the original video file and replace it. The resolution is not changed.
16 */
14async function optimizeVideofile (video: VideoModel, inputVideoFileArg?: VideoFileModel) { 17async function optimizeVideofile (video: VideoModel, inputVideoFileArg?: VideoFileModel) {
15 const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR 18 const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
16 const newExtname = '.mp4' 19 const newExtname = '.mp4'
@@ -19,10 +22,13 @@ async function optimizeVideofile (video: VideoModel, inputVideoFileArg?: VideoFi
19 const videoInputPath = join(videosDirectory, video.getVideoFilename(inputVideoFile)) 22 const videoInputPath = join(videosDirectory, video.getVideoFilename(inputVideoFile))
20 const videoTranscodedPath = join(videosDirectory, video.id + '-transcoded' + newExtname) 23 const videoTranscodedPath = join(videosDirectory, video.id + '-transcoded' + newExtname)
21 24
25 const doQuickTranscode = await(canDoQuickTranscode(videoInputPath))
26
22 const transcodeOptions = { 27 const transcodeOptions = {
23 inputPath: videoInputPath, 28 inputPath: videoInputPath,
24 outputPath: videoTranscodedPath, 29 outputPath: videoTranscodedPath,
25 resolution: inputVideoFile.resolution 30 resolution: inputVideoFile.resolution,
31 doQuickTranscode
26 } 32 }
27 33
28 // Could be very long! 34 // Could be very long!
@@ -52,6 +58,9 @@ async function optimizeVideofile (video: VideoModel, inputVideoFileArg?: VideoFi
52 } 58 }
53} 59}
54 60
61/**
62 * Transcode the original video file to a lower resolution.
63 */
55async function transcodeOriginalVideofile (video: VideoModel, resolution: VideoResolution, isPortrait: boolean) { 64async function transcodeOriginalVideofile (video: VideoModel, resolution: VideoResolution, isPortrait: boolean) {
56 const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR 65 const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
57 const extname = '.mp4' 66 const extname = '.mp4'
diff --git a/server/tests/api/videos/video-transcoder.ts b/server/tests/api/videos/video-transcoder.ts
index 45a8c09f0..cfd0c8430 100644
--- a/server/tests/api/videos/video-transcoder.ts
+++ b/server/tests/api/videos/video-transcoder.ts
@@ -4,7 +4,7 @@ import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
5import { omit } from 'lodash' 5import { omit } from 'lodash'
6import { getMaxBitrate, VideoDetails, VideoResolution, VideoState } from '../../../../shared/models/videos' 6import { getMaxBitrate, VideoDetails, VideoResolution, VideoState } from '../../../../shared/models/videos'
7import { audio, getVideoFileBitrate, getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffmpeg-utils' 7import { audio, canDoQuickTranscode, getVideoFileBitrate, getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffmpeg-utils'
8import { 8import {
9 buildAbsoluteFixturePath, 9 buildAbsoluteFixturePath,
10 cleanupTests, 10 cleanupTests,
@@ -18,10 +18,10 @@ import {
18 ServerInfo, 18 ServerInfo,
19 setAccessTokensToServers, 19 setAccessTokensToServers,
20 uploadVideo, 20 uploadVideo,
21 waitJobs,
21 webtorrentAdd 22 webtorrentAdd
22} from '../../../../shared/extra-utils' 23} from '../../../../shared/extra-utils'
23import { join } from 'path' 24import { join } from 'path'
24import { waitJobs } from '../../../../shared/extra-utils/server/jobs'
25import { VIDEO_TRANSCODING_FPS } from '../../../../server/initializers/constants' 25import { VIDEO_TRANSCODING_FPS } from '../../../../server/initializers/constants'
26 26
27const expect = chai.expect 27const expect = chai.expect
@@ -324,6 +324,15 @@ describe('Test video transcoding', function () {
324 it('Should accept and transcode additional extensions', async function () { 324 it('Should accept and transcode additional extensions', async function () {
325 this.timeout(300000) 325 this.timeout(300000)
326 326
327 let tempFixturePath: string
328
329 {
330 tempFixturePath = await generateHighBitrateVideo()
331
332 const bitrate = await getVideoFileBitrate(tempFixturePath)
333 expect(bitrate).to.be.above(getMaxBitrate(VideoResolution.H_1080P, 60, VIDEO_TRANSCODING_FPS))
334 }
335
327 for (const fixture of [ 'video_short.mkv', 'video_short.avi' ]) { 336 for (const fixture of [ 'video_short.mkv', 'video_short.avi' ]) {
328 const videoAttributes = { 337 const videoAttributes = {
329 name: fixture, 338 name: fixture,
@@ -349,6 +358,13 @@ describe('Test video transcoding', function () {
349 } 358 }
350 }) 359 })
351 360
361 it('Should correctly detect if quick transcode is possible', async function () {
362 this.timeout(10000)
363
364 expect(await canDoQuickTranscode(buildAbsoluteFixturePath('video_short.mp4'))).to.be.true
365 expect(await canDoQuickTranscode(buildAbsoluteFixturePath('video_short.webm'))).to.be.false
366 })
367
352 after(async function () { 368 after(async function () {
353 await cleanupTests(servers) 369 await cleanupTests(servers)
354 }) 370 })