]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/commitdiff
Merge remote-tracking branch 'origin/pr/1785' into develop
authorChocobozzz <me@florianbigard.com>
Thu, 16 May 2019 06:58:39 +0000 (08:58 +0200)
committerChocobozzz <me@florianbigard.com>
Thu, 16 May 2019 06:58:39 +0000 (08:58 +0200)
server/helpers/ffmpeg-utils.ts
server/lib/video-transcoding.ts
server/tests/api/videos/video-transcoder.ts

index 76b744de8543c7bd6e35a0d0a941e933143dea3d..2fdf34cb7d6918245a95dc9752dea4c5244ce3a8 100644 (file)
@@ -1,6 +1,6 @@
 import * as ffmpeg from 'fluent-ffmpeg'
 import { dirname, join } from 'path'
-import { getTargetBitrate, VideoResolution } from '../../shared/models/videos'
+import { getTargetBitrate, getMaxBitrate, VideoResolution } from '../../shared/models/videos'
 import { FFMPEG_NICE, VIDEO_TRANSCODING_FPS } from '../initializers/constants'
 import { processImage } from './image-utils'
 import { logger } from './logger'
@@ -31,7 +31,7 @@ function computeResolutionsToTranscode (videoFileHeight: number) {
 }
 
 async function getVideoFileSize (path: string) {
-  const videoStream = await getVideoFileStream(path)
+  const videoStream = await getVideoStreamFromFile(path)
 
   return {
     width: videoStream.width,
@@ -49,7 +49,7 @@ async function getVideoFileResolution (path: string) {
 }
 
 async function getVideoFileFPS (path: string) {
-  const videoStream = await getVideoFileStream(path)
+  const videoStream = await getVideoStreamFromFile(path)
 
   for (const key of [ 'avg_frame_rate', 'r_frame_rate' ]) {
     const valuesText: string = videoStream[key]
@@ -122,6 +122,7 @@ type TranscodeOptions = {
   outputPath: string
   resolution: VideoResolution
   isPortraitMode?: boolean
+  doQuickTranscode?: Boolean
 
   hlsPlaylist?: {
     videoFilename: string
@@ -134,7 +135,18 @@ function transcode (options: TranscodeOptions) {
       let command = ffmpeg(options.inputPath, { niceness: FFMPEG_NICE.TRANSCODING })
         .output(options.outputPath)
 
-      if (options.hlsPlaylist) {
+      if (options.doQuickTranscode) {
+        if (options.hlsPlaylist) {
+          throw(Error("Quick transcode and HLS can't be used at the same time"))
+        }
+
+        command
+          .format('mp4')
+          .addOption('-c:v copy')
+          .addOption('-c:a copy')
+          .outputOption('-map_metadata -1') // strip all metadata
+          .outputOption('-movflags faststart')
+      } else if (options.hlsPlaylist) {
         command = await buildHLSCommand(command, options)
       } else {
         command = await buildx264Command(command, options)
@@ -162,6 +174,30 @@ function transcode (options: TranscodeOptions) {
   })
 }
 
+async function canDoQuickTranscode (path: string): Promise<boolean> {
+  // NOTE: This could be optimized by running ffprobe only once (but it runs fast anyway)
+  const videoStream = await getVideoStreamFromFile(path)
+  const parsedAudio = await audio.get(path)
+  const fps = await getVideoFileFPS(path)
+  const bitRate = await getVideoFileBitrate(path)
+  const resolution = await getVideoFileResolution(path)
+
+  // check video params
+  if (videoStream[ 'codec_name' ] !== 'h264') return false
+  if (fps < VIDEO_TRANSCODING_FPS.MIN || fps > VIDEO_TRANSCODING_FPS.MAX) return false
+  if (bitRate > getMaxBitrate(resolution.videoFileResolution, fps, VIDEO_TRANSCODING_FPS)) return false
+
+    // check audio params (if audio stream exists)
+  if (parsedAudio.audioStream) {
+    if (parsedAudio.audioStream[ 'codec_name' ] !== 'aac') return false
+
+    const maxAudioBitrate = audio.bitrate[ 'aac' ](parsedAudio.audioStream[ 'bit_rate' ])
+    if (maxAudioBitrate !== -1 && parsedAudio.audioStream[ 'bit_rate' ] > maxAudioBitrate) return false
+  }
+
+  return true
+}
+
 // ---------------------------------------------------------------------------
 
 export {
@@ -173,7 +209,8 @@ export {
   getVideoFileFPS,
   computeResolutionsToTranscode,
   audio,
-  getVideoFileBitrate
+  getVideoFileBitrate,
+  canDoQuickTranscode
 }
 
 // ---------------------------------------------------------------------------
@@ -243,7 +280,7 @@ async function onTranscodingSuccess (options: TranscodeOptions) {
   await writeFile(options.outputPath, newContent)
 }
 
-function getVideoFileStream (path: string) {
+function getVideoStreamFromFile (path: string) {
   return new Promise<any>((res, rej) => {
     ffmpeg.ffprobe(path, (err, metadata) => {
       if (err) return rej(err)
index 0fe0ff12a4c0aa2fb4f15caba7a547bcaaf33471..8e906a1eba60df6f755f047bd165f51c6de8c010 100644 (file)
@@ -1,6 +1,6 @@
 import { HLS_STREAMING_PLAYLIST_DIRECTORY, P2P_MEDIA_LOADER_PEER_VERSION, WEBSERVER } from '../initializers/constants'
 import { join } from 'path'
-import { getVideoFileFPS, transcode } from '../helpers/ffmpeg-utils'
+import { getVideoFileFPS, transcode, canDoQuickTranscode } from '../helpers/ffmpeg-utils'
 import { ensureDir, move, remove, stat } from 'fs-extra'
 import { logger } from '../helpers/logger'
 import { VideoResolution } from '../../shared/models/videos'
@@ -11,6 +11,9 @@ import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-pla
 import { VideoStreamingPlaylistType } from '../../shared/models/videos/video-streaming-playlist.type'
 import { CONFIG } from '../initializers/config'
 
+/**
+ * Optimize the original video file and replace it. The resolution is not changed.
+ */
 async function optimizeVideofile (video: VideoModel, inputVideoFileArg?: VideoFileModel) {
   const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
   const newExtname = '.mp4'
@@ -19,10 +22,13 @@ async function optimizeVideofile (video: VideoModel, inputVideoFileArg?: VideoFi
   const videoInputPath = join(videosDirectory, video.getVideoFilename(inputVideoFile))
   const videoTranscodedPath = join(videosDirectory, video.id + '-transcoded' + newExtname)
 
+  const doQuickTranscode = await(canDoQuickTranscode(videoInputPath))
+
   const transcodeOptions = {
     inputPath: videoInputPath,
     outputPath: videoTranscodedPath,
-    resolution: inputVideoFile.resolution
+    resolution: inputVideoFile.resolution,
+    doQuickTranscode
   }
 
   // Could be very long!
@@ -52,6 +58,9 @@ async function optimizeVideofile (video: VideoModel, inputVideoFileArg?: VideoFi
   }
 }
 
+/**
+ * Transcode the original video file to a lower resolution.
+ */
 async function transcodeOriginalVideofile (video: VideoModel, resolution: VideoResolution, isPortrait: boolean) {
   const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
   const extname = '.mp4'
index 45a8c09f0d4148794f8cf248fc164de6df867525..cfd0c8430abd55c63375e26c10243c5b515dedee 100644 (file)
@@ -4,7 +4,7 @@ import * as chai from 'chai'
 import 'mocha'
 import { omit } from 'lodash'
 import { getMaxBitrate, VideoDetails, VideoResolution, VideoState } from '../../../../shared/models/videos'
-import { audio, getVideoFileBitrate, getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffmpeg-utils'
+import { audio, canDoQuickTranscode, getVideoFileBitrate, getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffmpeg-utils'
 import {
   buildAbsoluteFixturePath,
   cleanupTests,
@@ -18,10 +18,10 @@ import {
   ServerInfo,
   setAccessTokensToServers,
   uploadVideo,
+  waitJobs,
   webtorrentAdd
 } from '../../../../shared/extra-utils'
 import { join } from 'path'
-import { waitJobs } from '../../../../shared/extra-utils/server/jobs'
 import { VIDEO_TRANSCODING_FPS } from '../../../../server/initializers/constants'
 
 const expect = chai.expect
@@ -324,6 +324,15 @@ describe('Test video transcoding', function () {
   it('Should accept and transcode additional extensions', async function () {
     this.timeout(300000)
 
+    let tempFixturePath: string
+
+    {
+      tempFixturePath = await generateHighBitrateVideo()
+
+      const bitrate = await getVideoFileBitrate(tempFixturePath)
+      expect(bitrate).to.be.above(getMaxBitrate(VideoResolution.H_1080P, 60, VIDEO_TRANSCODING_FPS))
+    }
+
     for (const fixture of [ 'video_short.mkv', 'video_short.avi' ]) {
       const videoAttributes = {
         name: fixture,
@@ -349,6 +358,13 @@ describe('Test video transcoding', function () {
     }
   })
 
+  it('Should correctly detect if quick transcode is possible', async function () {
+    this.timeout(10000)
+
+    expect(await canDoQuickTranscode(buildAbsoluteFixturePath('video_short.mp4'))).to.be.true
+    expect(await canDoQuickTranscode(buildAbsoluteFixturePath('video_short.webm'))).to.be.false
+  })
+
   after(async function () {
     await cleanupTests(servers)
   })