Skip to content

Commit

Permalink
Add new CRF option to control quality between 0 and 100%
Browse files Browse the repository at this point in the history
  • Loading branch information
rprieto committed Jan 6, 2019
1 parent 0b881dd commit e2f722c
Show file tree
Hide file tree
Showing 7 changed files with 88 additions and 4 deletions.
23 changes: 21 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,10 +153,29 @@ The default export format is mp4. You can specify an export format by adding a `
opts = { format: 'webm' }
```

#### Variable bitrate
##### Video quality

You can specify a variable bitrate (a.k.a. average bitrate, or target bitrate) by using the `bitrate` option.
The default behaviour is to use CRF (constant rate factor) to control the output quality.
The default value is `75%`.

```js
// value between 0 (worst) and 100 (best)
opts = { quality: 75 }
```

Notes:

- the quality scale is not linear
- you will most likely want a value between 50% and 90%
- values over 90% can generate files larger than the original

![Quality size ratio](docs/quality.png)

##### Variable bitrate

Instead of CRF, you can specify a variable bitrate (a.k.a. average bitrate, or target bitrate) by using the `bitrate` option.
Check the [ffmpeg docmentation](https://trac.ffmpeg.org/wiki/Encode/H.264) for more information.
This is not compatible with the `quality` option.

```js
opts = { bitrate: '1200k' }
Expand Down
Binary file added docs/quality.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 18 additions & 2 deletions lib/video/ffargs.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@ const path = require('path')

const DEFAULT_AUDIO_BITRATE = '96k'
const DEFAULT_VIDEO_FPS = 25
// const ENCODER_CRF_MAX = { h264: 51, vpx: 63 }
const DEFAULT_VIDEO_QUALITY = 75

// See tests to understand why these values were picked
const ENCODER_CRF = {
h264: { min: 17, max: 51 },
vpx: { min: 15, max: 63 }
}

exports.prepare = function (source, target, options) {
// source file
Expand All @@ -29,7 +35,9 @@ exports.prepare = function (source, target, options) {
args.push('-b:v', options.bitrate)
} else {
// TODO: add configurable CRF value
args.push('-b:v', 0, '-crf', 20)
const quality = options.quality || DEFAULT_VIDEO_QUALITY
const encoder = (options.format === 'webm') ? 'vpx' : 'h264'
args.push('-b:v', 0, '-crf', exports.crf(quality, encoder))
}

// AVCHD/MTS videos need a full-frame export to avoid interlacing artefacts
Expand All @@ -42,3 +50,11 @@ exports.prepare = function (source, target, options) {

return args
}

// This function converts a "quality percentage" into CRF
exports.crf = function (percent, encoder) {
const range = ENCODER_CRF[encoder].max - ENCODER_CRF[encoder].min
const proportion = percent * range / 100
const inverted = ENCODER_CRF[encoder].max - proportion
return Math.floor(inverted)
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 22 additions & 0 deletions test/integration/video.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,28 @@ tape('can convert to WEBM with a target bitrate', (test) => {
})
})

tape('can convert to MP4 with a target quality', (test) => {
diff.video(test, {
input: 'videos/countdown.mp4',
expect: `videos/countdown-quality.mp4`,
options: {
format: 'mp4',
quality: 50
}
})
})

tape('can convert to WEBM with a target quality', (test) => {
diff.video(test, {
input: 'videos/countdown.mp4',
expect: 'videos/countdown-quality.webm',
options: {
format: 'webm',
quality: 50
}
})
})

tape('can report progress when processing videos', (t) => {
const report = []
const input = 'test-data/input/videos/big_buck_bunny.mp4'
Expand Down
27 changes: 27 additions & 0 deletions test/video/ffargs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const assert = require('assert')
const tape = require('tape')
const ffargs = require('../../lib/video/ffargs')

tape('crf for h264', t => {
// Full range is 0-51 (not linear)
// 17 is already near lossless so we consider it to be 100% quality
// https://trac.ffmpeg.org/wiki/Encode/H.264
assert.equal(ffargs.crf(0, 'h264'), 51)
assert.equal(ffargs.crf(20, 'h264'), 44)
assert.equal(ffargs.crf(50, 'h264'), 34)
assert.equal(ffargs.crf(70, 'h264'), 27)
assert.equal(ffargs.crf(100, 'h264'), 17)
t.end()
})

tape('crf for vpx', t => {
// Full range is 0-63 (not linear)
// 15 is already near lossless so we consider it to be 100% quality
// https://trac.ffmpeg.org/wiki/Encode/VP9
assert.equal(ffargs.crf(0, 'vpx'), 63)
assert.equal(ffargs.crf(20, 'vpx'), 53)
assert.equal(ffargs.crf(50, 'vpx'), 39)
assert.equal(ffargs.crf(80, 'vpx'), 24)
assert.equal(ffargs.crf(100, 'vpx'), 15)
t.end()
})

0 comments on commit e2f722c

Please sign in to comment.