Handle higher FPS for high resolution (test)

This commit is contained in:
Chocobozzz 2018-06-29 16:41:29 +02:00
parent 34b1919290
commit 3a6f351b25
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
12 changed files with 106 additions and 25 deletions

View File

@ -35,11 +35,16 @@ class ResolutionMenuButton extends MenuButton {
createMenu () {
const menu = new Menu(this.player_)
for (const videoFile of this.player_.peertube().videoFiles) {
let label = videoFile.resolution.label
if (videoFile.fps && videoFile.fps >= 50) {
label += videoFile.fps
}
menu.addChild(new ResolutionMenuItem(
this.player_,
{
id: videoFile.resolution.id,
label: videoFile.resolution.label,
label,
src: videoFile.magnetUri
})
)

View File

@ -2,7 +2,7 @@ import * as express from 'express'
import { extname, join } from 'path'
import { VideoCreate, VideoPrivacy, VideoState, VideoUpdate } from '../../../../shared'
import { renamePromise } from '../../../helpers/core-utils'
import { getVideoFileResolution } from '../../../helpers/ffmpeg-utils'
import { getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffmpeg-utils'
import { processImage } from '../../../helpers/image-utils'
import { logger } from '../../../helpers/logger'
import { getFormattedObjects, getServerActor, resetSequelizeInstance } from '../../../helpers/utils'
@ -184,10 +184,13 @@ async function addVideo (req: express.Request, res: express.Response) {
// Build the file object
const { videoFileResolution } = await getVideoFileResolution(videoPhysicalFile.path)
const fps = await getVideoFileFPS(videoPhysicalFile.path)
const videoFileData = {
extname: extname(videoPhysicalFile.filename),
resolution: videoFileResolution,
size: videoPhysicalFile.size
size: videoPhysicalFile.size,
fps
}
const videoFile = new VideoFileModel(videoFileData)

View File

@ -118,6 +118,10 @@ function isVideoFileResolutionValid (value: string) {
return exists(value) && validator.isInt(value + '')
}
function isVideoFPSResolutionValid (value: string) {
return value === null || validator.isInt(value + '')
}
function isVideoFileSizeValid (value: string) {
return exists(value) && validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.FILE_SIZE)
}
@ -182,6 +186,7 @@ export {
isVideoFileInfoHashValid,
isVideoNameValid,
isVideoTagsValid,
isVideoFPSResolutionValid,
isScheduleVideoUpdatePrivacyValid,
isVideoAbuseReasonValid,
isVideoFile,

View File

@ -26,7 +26,7 @@ async function getVideoFileFPS (path: string) {
if (!frames || !seconds) continue
const result = parseInt(frames, 10) / parseInt(seconds, 10)
if (result > 0) return result
if (result > 0) return Math.round(result)
}
return 0
@ -83,8 +83,6 @@ type TranscodeOptions = {
function transcode (options: TranscodeOptions) {
return new Promise<void>(async (res, rej) => {
const fps = await getVideoFileFPS(options.inputPath)
let command = ffmpeg(options.inputPath)
.output(options.outputPath)
.videoCodec('libx264')
@ -92,14 +90,27 @@ function transcode (options: TranscodeOptions) {
.outputOption('-movflags faststart')
// .outputOption('-crf 18')
// Our player has some FPS limits
if (fps > VIDEO_TRANSCODING_FPS.MAX) command = command.withFPS(VIDEO_TRANSCODING_FPS.MAX)
else if (fps < VIDEO_TRANSCODING_FPS.MIN) command = command.withFPS(VIDEO_TRANSCODING_FPS.MIN)
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) {
// 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

View File

@ -14,7 +14,7 @@ let config: IConfig = require('config')
// ---------------------------------------------------------------------------
const LAST_MIGRATION_VERSION = 220
const LAST_MIGRATION_VERSION = 225
// ---------------------------------------------------------------------------
@ -282,7 +282,9 @@ const RATES_LIMIT = {
let VIDEO_VIEW_LIFETIME = 60000 * 60 // 1 hour
const VIDEO_TRANSCODING_FPS = {
MIN: 10,
MAX: 30
AVERAGE: 30,
MAX: 60,
KEEP_ORIGIN_FPS_RESOLUTION_MIN: 720 // We keep the original FPS on high resolutions (720 minimum)
}
const VIDEO_RATE_TYPES: { [ id: string ]: VideoRateType } = {

View File

@ -0,0 +1,22 @@
import * as Sequelize from 'sequelize'
async function up (utils: {
transaction: Sequelize.Transaction
queryInterface: Sequelize.QueryInterface
sequelize: Sequelize.Sequelize
}): Promise<void> {
{
const data = {
type: Sequelize.INTEGER,
allowNull: true,
defaultValue: null
}
await utils.queryInterface.addColumn('videoFile', 'fps', data)
}
}
function down (options) {
throw new Error('Not implemented.')
}
export { up, down }

View File

@ -1,6 +1,11 @@
import { values } from 'lodash'
import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
import { isVideoFileInfoHashValid, isVideoFileResolutionValid, isVideoFileSizeValid } from '../../helpers/custom-validators/videos'
import { AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
import {
isVideoFileInfoHashValid,
isVideoFileResolutionValid,
isVideoFileSizeValid,
isVideoFPSResolutionValid
} from '../../helpers/custom-validators/videos'
import { CONSTRAINTS_FIELDS } from '../../initializers'
import { throwIfNotValid } from '../utils'
import { VideoModel } from './video'
@ -42,6 +47,12 @@ export class VideoFileModel extends Model<VideoFileModel> {
@Column
infoHash: string
@AllowNull(true)
@Default(null)
@Is('VideoFileFPS', value => throwIfNotValid(value, isVideoFPSResolutionValid, 'fps'))
@Column
fps: number
@ForeignKey(() => VideoModel)
@Column
videoId: number

View File

@ -52,7 +52,7 @@ import {
isVideoStateValid,
isVideoSupportValid
} from '../../helpers/custom-validators/videos'
import { generateImageFromVideoFile, getVideoFileResolution, transcode } from '../../helpers/ffmpeg-utils'
import { generateImageFromVideoFile, getVideoFileFPS, getVideoFileResolution, transcode } from '../../helpers/ffmpeg-utils'
import { logger } from '../../helpers/logger'
import { getServerActor } from '../../helpers/utils'
import {
@ -1168,6 +1168,7 @@ export class VideoModel extends Model<VideoModel> {
},
magnetUri: this.generateMagnetUri(videoFile, baseUrlHttp, baseUrlWs),
size: videoFile.size,
fps: videoFile.fps,
torrentUrl: this.getTorrentUrl(videoFile, baseUrlHttp),
torrentDownloadUrl: this.getTorrentDownloadUrl(videoFile, baseUrlHttp),
fileUrl: this.getVideoFileUrl(videoFile, baseUrlHttp),
@ -1303,11 +1304,11 @@ export class VideoModel extends Model<VideoModel> {
const newExtname = '.mp4'
const inputVideoFile = this.getOriginalFile()
const videoInputPath = join(videosDirectory, this.getVideoFilename(inputVideoFile))
const videoOutputPath = join(videosDirectory, this.id + '-transcoded' + newExtname)
const videoTranscodedPath = join(videosDirectory, this.id + '-transcoded' + newExtname)
const transcodeOptions = {
inputPath: videoInputPath,
outputPath: videoOutputPath
outputPath: videoTranscodedPath
}
// Could be very long!
@ -1319,10 +1320,13 @@ export class VideoModel extends Model<VideoModel> {
// Important to do this before getVideoFilename() to take in account the new file extension
inputVideoFile.set('extname', newExtname)
await renamePromise(videoOutputPath, this.getVideoFilePath(inputVideoFile))
const stats = await statPromise(this.getVideoFilePath(inputVideoFile))
const videoOutputPath = this.getVideoFilePath(inputVideoFile)
await renamePromise(videoTranscodedPath, videoOutputPath)
const stats = await statPromise(videoOutputPath)
const fps = await getVideoFileFPS(videoOutputPath)
inputVideoFile.set('size', stats.size)
inputVideoFile.set('fps', fps)
await this.createTorrentAndSetInfoHash(inputVideoFile)
await inputVideoFile.save()
@ -1360,8 +1364,10 @@ export class VideoModel extends Model<VideoModel> {
await transcode(transcodeOptions)
const stats = await statPromise(videoOutputPath)
const fps = await getVideoFileFPS(videoOutputPath)
newVideoFile.set('size', stats.size)
newVideoFile.set('fps', fps)
await this.createTorrentAndSetInfoHash(newVideoFile)
@ -1371,10 +1377,15 @@ export class VideoModel extends Model<VideoModel> {
}
async importVideoFile (inputFilePath: string) {
const { videoFileResolution } = await getVideoFileResolution(inputFilePath)
const { size } = await statPromise(inputFilePath)
const fps = await getVideoFileFPS(inputFilePath)
let updatedVideoFile = new VideoFileModel({
resolution: (await getVideoFileResolution(inputFilePath)).videoFileResolution,
resolution: videoFileResolution,
extname: extname(inputFilePath),
size: (await statPromise(inputFilePath)).size,
size,
fps,
videoId: this.id
})
@ -1390,6 +1401,7 @@ export class VideoModel extends Model<VideoModel> {
// Update the database
currentVideoFile.set('extname', updatedVideoFile.extname)
currentVideoFile.set('size', updatedVideoFile.size)
currentVideoFile.set('fps', updatedVideoFile.fps)
updatedVideoFile = currentVideoFile
}

View File

@ -91,13 +91,13 @@ describe('Test video transcoding', function () {
expect(torrent.files[0].path).match(/\.mp4$/)
})
it('Should transcode to 30 FPS', async function () {
it('Should transcode a 60 FPS video', async function () {
this.timeout(60000)
const videoAttributes = {
name: 'my super 30fps name for server 2',
description: 'my super 30fps description for server 2',
fixture: 'video_60fps_short.mp4'
fixture: '60fps_720p_small.mp4'
}
await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes)
@ -109,14 +109,23 @@ describe('Test video transcoding', function () {
const res2 = await getVideo(servers[1].url, video.id)
const videoDetails: VideoDetails = res2.body
expect(videoDetails.files).to.have.lengthOf(1)
expect(videoDetails.files).to.have.lengthOf(4)
expect(videoDetails.files[0].fps).to.be.above(58).and.below(62)
expect(videoDetails.files[1].fps).to.be.below(31)
expect(videoDetails.files[2].fps).to.be.below(31)
expect(videoDetails.files[3].fps).to.be.below(31)
for (const resolution of [ '240' ]) {
for (const resolution of [ '240', '360', '480' ]) {
const path = join(root(), 'test2', 'videos', video.uuid + '-' + resolution + '.mp4')
const fps = await getVideoFileFPS(path)
expect(fps).to.be.below(31)
}
const path = join(root(), 'test2', 'videos', video.uuid + '-720.mp4')
const fps = await getVideoFileFPS(path)
expect(fps).to.be.above(58).and.below(62)
})
it('Should wait transcoding before publishing the video', async function () {

Binary file not shown.

Binary file not shown.

View File

@ -18,6 +18,7 @@ export interface VideoFile {
torrentDownloadUrl: string
fileUrl: string
fileDownloadUrl: string
fps: number
}
export interface Video {