To get volume level of the media file in command line I use:
ffmpeg -i audo.mp3 -af "volumedetect" -f null /dev/null
in the output I see something like this:
[Parsed_volumedetect_0 @ 0000000004087260] n_samples: 41316480
[Parsed_volumedetect_0 @ 0000000004087260] mean_volume: -22.5 dB
[Parsed_volumedetect_0 @ 0000000004087260] max_volume: -5.8 dB
[Parsed_volumedetect_0 @ 0000000004087260] histogram_5db: 3
[Parsed_volumedetect_0 @ 0000000004087260] histogram_6db: 892
[Parsed_volumedetect_0 @ 0000000004087260] histogram_7db: 8686
[Parsed_volumedetect_0 @ 0000000004087260] histogram_8db: 36098
How can I get volume level using fluent-ffmpeg?
I tried
new Ffmpeg({ source: file})
.withAudioFilter('volumedetect')
.addOption('-f', 'null /dev/null')
.on('error', function(err) {
console.log('An error occurred: ' + err.message);
})
.on('end', function(){
console.log('finished')
})
But it doesn't work for me (end event doesn't occur)
So could you advice what should I do to execute the command get the output to parse?
Maybe volume detection could be included in future releases.
.addOption('-f', 'null /dev/null')
is your culprit there. This will fail because "null /dev/null" is not a valid format. You should just use:
new Ffmpeg({ source: file})
.withAudioFilter('volumedetect')
.on('error', function(err) {
console.log('An error occurred: ' + err.message);
})
.on('end', function(stdout, stderr){
console.log('finished, ffmpeg output is:\n' + stdout);
})
.saveToFile('/dev/null');
Ah, thank you very much.
Though you code didn't work for me (I'm on windows 7 x64, An error occurred: ffmpeg exited with code 1), ended up with:
new Ffmpeg({ source: file})
.withAudioFilter('volumedetect')
.addOption('-f', 'null')
.on('error', function(err) {
console.log('An error occurred: ' + err.message);
})
.on('end', function(stdout, stderr){
console.log('finished', stderr)
})
.saveToFile('/dev/null');
Btw why I see the output in stderr not stdout ?
Oh, I guess ffmpeg reports messages on stderr instead of stdout.
Can you try this:
.on('error', function(err, stdout, stderr) {
console.log('An error occurred: ' + err.message);
})
and report the contents of stdout and stderr ?
with your code stderr ends with:
[NULL @ 000000000031c640] Unable to find a suitable output format for '\dev\null'
\dev\null: Invalid argument
Oh, nevermind, I forgot you were on Windows.
You'd have to use 'NUL' as an output file instead of '/dev/null'.
Alternatively, you can use any file name as the "null" ffmpeg format does not create any file.
Would you consider adding volume detection via fluent-ffmpeg API?
Hmm, I personnally don't think it fits fluent-ffmpeg purpose. What are you trying to achieve, and did you succeed ?
I think something along these lines would be a good feature. For example if you've got a bucket of videos you want to normalise the audio level of.
There are a lot of things ffmpeg can do. I don't think fluent-ffmpeg should have specific API for them all, only for the most frequently-used ones (and the ones that are difficult to set up).
Plus such a thing would break the usual fluent-ffmpeg workflow, as we'd have to make a first detection pass and then a volume pass.
And last point, volumedetect + volume is really not a great way of normalizing the volume. The peak and mean volume on a video or audio track do not indicate perceived volume well at all...
A compand filter would be more appropriate. But you cannot determine the right settings automatically.
However, I'd love to have a better way of getting ffmpeg output, in the case where this is what the user is interested in (and not in an transcoded file).
Yes I want to normalize volume of bunch of related audio files (usually have the same volume). I'm not an expert on audio/ffmpeg so I haven't decided on a strategy how to implement this correctly with less (manual) efforts. For now I decided to detect mean and max volume levels using volumedetect filter. And then modify audio using volume filter.
Some information I've found affirms that if max-level is 0 there is no need to normalize audio. Perhaps this refers to peak normalization. But I'm interested in loudness normalization, so probably target volume level for a particular bunch of audios will be determined manually. If you have any ideas how loudness normalization can be automated I would appreciate the advice.
You cannot determine overall loudness from mean energy, peak energy or both.
Imagine two samples of 100 seconds, with people talking with the same _perceived_ loudness, only on the first sample they talk for 10 seconds and then mute, and on the second they talk all the way long.
Mean energy over the whole sample will be 10x less (-3dB IIRC) for the first sample than for the other one, even with the same perceived loudness.
Now imagine that, in the second sample, the mike was poorly setup and the 'P's and 'T's people speak cause huge peaks. Again, this won't alter perceived loudness, but peak energy can be up to 12dB louder.
There are ways to compute perceived loudness for an audio sample, but they are very complicated (audio software can do this, not ffmpeg filters for now).
An other important point is a vocabulary one.
There's a thing called "replay gain" (google for that if you like). Some tracks come with metadata indicating the overall perceived loudness (in dB) for the track, in which case you can use it to compute necessary volume changes. There's even "track replay gain" and "album replay gain" in some cases. When your tracks don't have this information, you can compute it using dedicated software. mp3gain is a notable open source program that is able to compute replay gain and tag files with it. From its website:
MP3Gain does not just do peak normalization, as many normalizers do. Instead, it does some statistical analysis to determine how loud the file actually sounds to the human ear.
I think this is your best option: passing your tracks to mp3gain (possibly transcoding them first, the advantage with this approach being that you don't need good quality audio to compute replay gain, so you can transcode very fast with a low bitrate), and then applying a volume filter with ffmpeg.
Hope this helps, don't hesitate if you have more questions. I'm closing this issue as it is not fluent-ffmpegs scope, I think, but don't hesitate either to reopen it if you think the opposite.
Thank you, I will consider your suggestions.
Hi,
Volumedetect actually gives you the main volume which is the RMS value of the loudness. This is the best value you can use to normalize a track so all sound in similar loudness.
The process of leveling all the songs is called Mastering and involves several processes but the main are multiband compression and limiting.
I'm trying to find the way to dettect clipping in an audio file using ffmpeg but I found no solution to this yet.
I'm assuming the .withAudioFilter() option isn't available in this implementation of ffprobe? I know it should be in the actual ffprobe binary as the documentation mentions it.
You're right, no options can be passed to ffprobe. I'm not sure what the effect of filters would be on ffprobe, though, even if the documentation mentions it.
Okay, it's no biggie :smiley: Well, the current value metadata that ffprobe returns could have volume data added to the object. At the moment I'm doing it in two goes and running the ffmpeg.withAudiofilter and ffprobe separately, but it'd be nice to have an optional kind of... "souped up" version of ffprobe data returned that gives loads of extra info about the audio.
This is my current method of parsing the audiofilter stdoutput and merging the two together, I'm sure it could be done better but you get the idea (apologies for coffeescript!)
probe: (cb) =>
ffmpeg.ffprobe e.file.pathName, (err, stat) =>
if err
cb 'Error converting file'
else if (lo.where stat.streams, codec_type: 'audio').length < 1
cb 'File has no audio'
else
ffmpeg(e.file.pathName).withAudioFilter 'volumedetect'
.on 'end', (stdout, stderr) ->
volumeDetect = {}
y = stderr.split("\n").filter (x) ->
return ~x.indexOf 'Parsed_volumedetect'
.forEach (x) ->
x = x.substr 2 + x.indexOf '] '
x = x.split ': '
volumeDetect[x[0]] = x[1]
stat.volumeDetect = volumeDetect
cb null, stat
.on 'error', (err) ->
cb err, stat
.saveToFile '/dev/null/null.mp3'
Ps, neither .saveToFile '/dev/null/null.mp3' nor .saveToFile '/dev/null' work for me. The former says not a directory, the latter says invalid argument. Only seems to work if I write to an actual real file! (on mac)
First of all, sorry for the necrobump.
For those who find this message I had to add .format(null) to the code provided by njoyard in order to make it work on my mac with ffmpeg 4.2 (this response https://github.com/fluent-ffmpeg/node-fluent-ffmpeg/issues/250#issuecomment-42934633). It ended up looking like this:
new Ffmpeg({ source: file})
.withAudioFilter('volumedetect')
.addOption('-f', 'null')
.on('error', function(err) {
console.log('An error occurred: ' + err.message);
})
.on('end', function(stdout, stderr){
console.log('finished', stderr)
})
.format(null)
.saveToFile('/dev/null');
Most helpful comment
Okay, it's no biggie :smiley: Well, the current value
metadatathat ffprobe returns could have volume data added to the object. At the moment I'm doing it in two goes and running theffmpeg.withAudiofilterandffprobeseparately, but it'd be nice to have an optional kind of... "souped up" version of ffprobe data returned that gives loads of extra info about the audio.This is my current method of parsing the audiofilter stdoutput and merging the two together, I'm sure it could be done better but you get the idea (apologies for coffeescript!)