Merge remote-tracking branch 'origin/pr/1785' into develop
This commit is contained in:
commit
1600235a2f
|
@ -1,6 +1,6 @@
|
||||||
import * as ffmpeg from 'fluent-ffmpeg'
|
import * as ffmpeg from 'fluent-ffmpeg'
|
||||||
import { dirname, join } from 'path'
|
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 { FFMPEG_NICE, VIDEO_TRANSCODING_FPS } from '../initializers/constants'
|
||||||
import { processImage } from './image-utils'
|
import { processImage } from './image-utils'
|
||||||
import { logger } from './logger'
|
import { logger } from './logger'
|
||||||
|
@ -31,7 +31,7 @@ function computeResolutionsToTranscode (videoFileHeight: number) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getVideoFileSize (path: string) {
|
async function getVideoFileSize (path: string) {
|
||||||
const videoStream = await getVideoFileStream(path)
|
const videoStream = await getVideoStreamFromFile(path)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
width: videoStream.width,
|
width: videoStream.width,
|
||||||
|
@ -49,7 +49,7 @@ async function getVideoFileResolution (path: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getVideoFileFPS (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' ]) {
|
for (const key of [ 'avg_frame_rate', 'r_frame_rate' ]) {
|
||||||
const valuesText: string = videoStream[key]
|
const valuesText: string = videoStream[key]
|
||||||
|
@ -122,6 +122,7 @@ type TranscodeOptions = {
|
||||||
outputPath: string
|
outputPath: string
|
||||||
resolution: VideoResolution
|
resolution: VideoResolution
|
||||||
isPortraitMode?: boolean
|
isPortraitMode?: boolean
|
||||||
|
doQuickTranscode?: Boolean
|
||||||
|
|
||||||
hlsPlaylist?: {
|
hlsPlaylist?: {
|
||||||
videoFilename: string
|
videoFilename: string
|
||||||
|
@ -134,7 +135,18 @@ function transcode (options: TranscodeOptions) {
|
||||||
let command = ffmpeg(options.inputPath, { niceness: FFMPEG_NICE.TRANSCODING })
|
let command = ffmpeg(options.inputPath, { niceness: FFMPEG_NICE.TRANSCODING })
|
||||||
.output(options.outputPath)
|
.output(options.outputPath)
|
||||||
|
|
||||||
|
if (options.doQuickTranscode) {
|
||||||
if (options.hlsPlaylist) {
|
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)
|
command = await buildHLSCommand(command, options)
|
||||||
} else {
|
} else {
|
||||||
command = await buildx264Command(command, options)
|
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 {
|
export {
|
||||||
|
@ -173,7 +209,8 @@ export {
|
||||||
getVideoFileFPS,
|
getVideoFileFPS,
|
||||||
computeResolutionsToTranscode,
|
computeResolutionsToTranscode,
|
||||||
audio,
|
audio,
|
||||||
getVideoFileBitrate
|
getVideoFileBitrate,
|
||||||
|
canDoQuickTranscode
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
@ -243,7 +280,7 @@ async function onTranscodingSuccess (options: TranscodeOptions) {
|
||||||
await writeFile(options.outputPath, newContent)
|
await writeFile(options.outputPath, newContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getVideoFileStream (path: string) {
|
function getVideoStreamFromFile (path: string) {
|
||||||
return new Promise<any>((res, rej) => {
|
return new Promise<any>((res, rej) => {
|
||||||
ffmpeg.ffprobe(path, (err, metadata) => {
|
ffmpeg.ffprobe(path, (err, metadata) => {
|
||||||
if (err) return rej(err)
|
if (err) return rej(err)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { HLS_STREAMING_PLAYLIST_DIRECTORY, P2P_MEDIA_LOADER_PEER_VERSION, WEBSERVER } from '../initializers/constants'
|
import { HLS_STREAMING_PLAYLIST_DIRECTORY, P2P_MEDIA_LOADER_PEER_VERSION, WEBSERVER } from '../initializers/constants'
|
||||||
import { join } from 'path'
|
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 { ensureDir, move, remove, stat } from 'fs-extra'
|
||||||
import { logger } from '../helpers/logger'
|
import { logger } from '../helpers/logger'
|
||||||
import { VideoResolution } from '../../shared/models/videos'
|
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 { VideoStreamingPlaylistType } from '../../shared/models/videos/video-streaming-playlist.type'
|
||||||
import { CONFIG } from '../initializers/config'
|
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) {
|
async function optimizeVideofile (video: VideoModel, inputVideoFileArg?: VideoFileModel) {
|
||||||
const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
|
const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
|
||||||
const newExtname = '.mp4'
|
const newExtname = '.mp4'
|
||||||
|
@ -19,10 +22,13 @@ async function optimizeVideofile (video: VideoModel, inputVideoFileArg?: VideoFi
|
||||||
const videoInputPath = join(videosDirectory, video.getVideoFilename(inputVideoFile))
|
const videoInputPath = join(videosDirectory, video.getVideoFilename(inputVideoFile))
|
||||||
const videoTranscodedPath = join(videosDirectory, video.id + '-transcoded' + newExtname)
|
const videoTranscodedPath = join(videosDirectory, video.id + '-transcoded' + newExtname)
|
||||||
|
|
||||||
|
const doQuickTranscode = await(canDoQuickTranscode(videoInputPath))
|
||||||
|
|
||||||
const transcodeOptions = {
|
const transcodeOptions = {
|
||||||
inputPath: videoInputPath,
|
inputPath: videoInputPath,
|
||||||
outputPath: videoTranscodedPath,
|
outputPath: videoTranscodedPath,
|
||||||
resolution: inputVideoFile.resolution
|
resolution: inputVideoFile.resolution,
|
||||||
|
doQuickTranscode
|
||||||
}
|
}
|
||||||
|
|
||||||
// Could be very long!
|
// 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) {
|
async function transcodeOriginalVideofile (video: VideoModel, resolution: VideoResolution, isPortrait: boolean) {
|
||||||
const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
|
const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
|
||||||
const extname = '.mp4'
|
const extname = '.mp4'
|
||||||
|
|
|
@ -4,7 +4,7 @@ import * as chai from 'chai'
|
||||||
import 'mocha'
|
import 'mocha'
|
||||||
import { omit } from 'lodash'
|
import { omit } from 'lodash'
|
||||||
import { getMaxBitrate, VideoDetails, VideoResolution, VideoState } from '../../../../shared/models/videos'
|
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 {
|
import {
|
||||||
buildAbsoluteFixturePath,
|
buildAbsoluteFixturePath,
|
||||||
cleanupTests,
|
cleanupTests,
|
||||||
|
@ -18,10 +18,10 @@ import {
|
||||||
ServerInfo,
|
ServerInfo,
|
||||||
setAccessTokensToServers,
|
setAccessTokensToServers,
|
||||||
uploadVideo,
|
uploadVideo,
|
||||||
|
waitJobs,
|
||||||
webtorrentAdd
|
webtorrentAdd
|
||||||
} from '../../../../shared/extra-utils'
|
} from '../../../../shared/extra-utils'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { waitJobs } from '../../../../shared/extra-utils/server/jobs'
|
|
||||||
import { VIDEO_TRANSCODING_FPS } from '../../../../server/initializers/constants'
|
import { VIDEO_TRANSCODING_FPS } from '../../../../server/initializers/constants'
|
||||||
|
|
||||||
const expect = chai.expect
|
const expect = chai.expect
|
||||||
|
@ -324,6 +324,15 @@ describe('Test video transcoding', function () {
|
||||||
it('Should accept and transcode additional extensions', async function () {
|
it('Should accept and transcode additional extensions', async function () {
|
||||||
this.timeout(300000)
|
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' ]) {
|
for (const fixture of [ 'video_short.mkv', 'video_short.avi' ]) {
|
||||||
const videoAttributes = {
|
const videoAttributes = {
|
||||||
name: fixture,
|
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 () {
|
after(async function () {
|
||||||
await cleanupTests(servers)
|
await cleanupTests(servers)
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue