├── waveform.png ├── package.json ├── LICENSE ├── normalize-volume.js └── README.md /waveform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tpkn/normalize-volume/HEAD/waveform.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "normalize-volume", 3 | "main": "normalize-volume.js", 4 | "version": "1.0.8", 5 | "author": { 6 | "url": "https://github.com/tpkn/" 7 | }, 8 | "repository": "https://github.com/tpkn/normalize-volume", 9 | "license": "MIT", 10 | "keywords": [ 11 | "video", 12 | "mp4", 13 | "normalize", 14 | "volume", 15 | "waveform", 16 | "wave", 17 | "ffmpeg", 18 | "convert" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021, tpkn.me 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /normalize-volume.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Normalize Volume, http://tpkn.me/ 3 | */ 4 | const fs = require('fs'); 5 | const path = require('path'); 6 | const spawn = require('child_process').spawn; 7 | 8 | function NormalizeVolume(input_file, output_file, options = {}){ 9 | let { 10 | volume = 0.5, 11 | normalize = true, 12 | ffmpeg_bin = 'ffmpeg', 13 | convert_bin = 'convert', 14 | waveform, 15 | silent = true 16 | } = options; 17 | 18 | let cmd = []; 19 | let errors = []; 20 | let result = { file: output_file }; 21 | 22 | // Normalize or just change the volume 23 | if(normalize){ 24 | cmd.push(`"${ffmpeg_bin}" -i "${input_file}" -y -c:v libx264 -c:a aac -ar 44100 -vcodec copy -filter:a loudnorm=print_format=json "${output_file}"`); 25 | }else{ 26 | cmd.push(`"${ffmpeg_bin}" -i "${input_file}" -y -filter:a "volume=${volume}" "${output_file}"`); 27 | } 28 | 29 | // Create two waveforms (before and after converting) merged together for comparison 30 | if(waveform){ 31 | let { 32 | image_before = output_file + '_before.png', 33 | image_after = output_file + '_after.png', 34 | image_comparison = output_file + '_comparison.png', 35 | width = 400, 36 | height = 225, 37 | before_color = 'white', 38 | after_color = '#ff00b3', 39 | } = waveform; 40 | 41 | // 'Before' waveform 42 | cmd.unshift(`"${ffmpeg_bin}" -i "${input_file}" -y -filter_complex "showwavespic=s=${width}x${height}:colors=${before_color}:split_channels=1" -frames:v 1 "${image_before}"`); 43 | // 'After' waveform 44 | cmd.push(`"${ffmpeg_bin}" -i "${output_file}" -y -filter_complex "showwavespic=s=${width}x${height}:colors=${after_color}:split_channels=1" -frames:v 1 "${image_after}"`); 45 | // Merging 46 | cmd.push(`"${convert_bin}" "${image_before}" "${image_after}" -gravity NorthEast -composite "${image_comparison}"`); 47 | // Cleanup a bit 48 | cmd.push(`del /f ${image_before}`); 49 | cmd.push(`del /f ${image_after}`); 50 | 51 | result.waveform = image_comparison; 52 | } 53 | 54 | return new Promise((resolve, reject) => { 55 | 56 | let child = spawn(cmd.join(' && '), { shell: true }); 57 | 58 | child.stdout.on('data', (data) => { 59 | if(!silent){ 60 | console.log(`${data}`); 61 | } 62 | }); 63 | 64 | child.stderr.on('data', (data) => { 65 | errors.push(data.toString()); 66 | if(!silent){ 67 | console.log(`${data}`); 68 | } 69 | }); 70 | 71 | child.on('exit', (exitCode) => { 72 | child.stdin.pause(); 73 | child.kill(); 74 | 75 | if(exitCode == 0){ 76 | resolve(result); 77 | }else{ 78 | reject({ code: exitCode, errors }); 79 | } 80 | }); 81 | 82 | }) 83 | } 84 | 85 | module.exports = NormalizeVolume; 86 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Normalize Volume [![npm Package](https://img.shields.io/npm/v/normalize-volume.svg)](https://www.npmjs.org/package/normalize-volume) 2 | Normalize audio using NodeJs and FFmpeg 3 | 4 | 5 | 6 | ## API 7 | 8 | ```javascript 9 | await NormalizeVolume(input_file, output_file[, options]) 10 | ``` 11 | 12 | ### input_file 13 | **Type**: _String_ 14 | Full path to source video file 15 | 16 | 17 | ### output_file 18 | **Type**: _String_ 19 | Full path to future normalized video file 20 | 21 | 22 | ### options 23 | **Type**: _Object_ 24 | 25 | 26 | ### options.normalize 27 | **Type**: _Boolean_ 28 | **Default**: `true` 29 | Base on [Loudness Normalization](https://trac.ffmpeg.org/wiki/AudioVolume#LoudnessNormalization) 30 | 31 | 32 | ### options.volume 33 | **Type**: _Number_ 34 | **Default**: `0.5` 35 | Number starts with 0. Volume `0 = muted`, `2 = 200%`. Ignored if `normalize = true` 36 | 37 | 38 | ### options.ffmpeg_bin 39 | **Type**: _String_ 40 | **Default**: `ffmpeg` 41 | Path to [FFmpeg](http://ffmpeg.org/download.html) binary file 42 | 43 | 44 | ### options.convert_bin 45 | **Type**: _String_ 46 | **Default**: `convert` 47 | Path to [ImageMagick Convert](https://imagemagick.org/) binary file 48 | 49 | 50 | ### options.waveform 51 | **Type**: _Object_ 52 | **Default**: `null` 53 | Optional visualization of the normalization result. In short, it is a waveforms before and after normalization, merged together. 54 | ![waveform](https://raw.githubusercontent.com/tpkn/normalize-volume/master/waveform.png) 55 | 56 | To customize waveform, use this options: 57 | 58 | | Option | Type | Default | What for? | 59 | | ------ | :------: | ------ | ------ | 60 | | `image_before` | _String_ | `output_file + '_before.png'` | | 61 | | `image_after` | _String_ | `output_file + '_after.png'` | | 62 | | `image_comparison` | _String_ | `output_file + '_comparison.png'` | | 63 | | `width` | _Number_ | 400 | | 64 | | `height` | _Number_ | 225 | | 65 | | `before_color` | _String_ | white | 'before' waveform color | 66 | | `after_color` | _String_ | #ff00b3 | 'after' waveform color | 67 | 68 | 69 | ### options.silent 70 | **Type**: _Boolean_ 71 | **Default**: `true` 72 | Enables logging `stdout` / `stderr` data 73 | 74 | 75 | ### @output 76 | **Type**: _Object_ 77 | ```javascript 78 | { 79 | "file": "z:\test_normalized.mp4", 80 | "waveform": "z:\test.mp4_comparison.png" 81 | } 82 | ``` 83 | 84 | 85 | ## Usage 86 | ```javascript 87 | const NormalizeVolume = require('normalize-volume'); 88 | 89 | let options = { 90 | normalize: true, 91 | waveform: { width: 1400, height: 225 }, 92 | ffmpeg_bin: 'ffmpeg.exe', 93 | convert_bin: 'convert.exe' 94 | } 95 | 96 | NormalizeVolume('z:\test.mp4', 'z:\test_normalized.mp4', options) 97 | .then(result => { 98 | console.log(result); 99 | }) 100 | .catch(err => { 101 | console.log(err); 102 | }) 103 | ``` 104 | 105 | 106 | 107 | ## Changelog 108 | #### 1.0.8: 109 | - Now the Promise returns an error object `{ code, []errors }` (in case an error occurred). 110 | 111 | --------------------------------------------------------------------------------