Search code examples
pythonaudioffmpeg

Checking if a video has a sound even if it has an audio codec?


I am new to intermediate python and I am trying to find if a downloaded video has sound, every video I download has an audio codec but I want to get the decibel of sound that audio has in that particular video.

For example, this 'FFmpeg' command line script allows me to get the full info:

ffmpeg -hide_banner -i testvideo.mp4 -af volumedetect -vn -f null - 2>&1

this yields the below result in my command prompt(windows user here with win 11)

Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'testvideo.mp4':

Metadata:

major_brand : mp42

minor_version : 0

compatible_brands: mp42mp41isomavc1

creation_time : 2022-04-12T23:21:45.000000Z

Duration: 00:00:40.58, start: 0.000000, bitrate: 4104 kb/s

Stream #0:0[0x1](und): Video: h264 (High) (avc1 / 0x31637661), yuv420p(tv, bt709, progressive), 1920x1080, 3846 kb/s, 29.97 fps, 29.97 tbr, 30k tbn (default)

Metadata:

creation_time : 2022-04-12T23:21:45.000000Z

handler_name : L-SMASH Video Handler

vendor_id : [0][0][0][0]

encoder : AVC Coding

Stream #0:1[0x2](und): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 253 kb/s (default)

Metadata:

creation_time : 2022-04-12T23:21:45.000000Z

handler_name : L-SMASH Audio Handler

vendor_id : [0][0][0][0]

Stream mapping:

Stream #0:1 -> #0:0 (aac (native) -> pcm_s16le (native))

Press [q] to stop, [?] for help

Output #0, null, to 'pipe:':

Metadata:

major_brand : mp42

minor_version : 0

compatible_brands: mp42mp41isomavc1

encoder : Lavf59.35.100

Stream #0:0(und): Audio: pcm_s16le, 48000 Hz, stereo, s16, 1536 kb/s (default)

Metadata:

creation_time : 2022-04-12T23:21:45.000000Z

handler_name : L-SMASH Audio Handler

vendor_id : [0][0][0][0]

encoder : Lavc59.56.100 pcm_s16le

size=N/A time=00:00:40.55 bitrate=N/A speed=1.22e+03x

video:0kB audio:7608kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown

[Parsed_volumedetect_0 @ 0000026609be08c0] n_samples: 3895296

[Parsed_volumedetect_0 @ 0000026609be08c0] mean_volume: -91.0 dB

[Parsed_volumedetect_0 @ 0000026609be08c0] max_volume: -91.0 dB

[Parsed_volumedetect_0 @ 0000026609be08c0] histogram_91db: 3895296

As you can see there are 'parsed_volumedetect' values with dB which has a mean value of -91 dB which means the audio has no sound, i.e., the video has audio but there is no sound.

Now I am trying to do the same in python and I want to get just the mean volume value to be stored in a variable so that I can check if the video has any sound in it.

I have seen the subprocess codes so far but when I try to run my code in VS-Code - python 3.11:

import subprocess    
result = subprocess.run(["ffmpeg", "-hide_banner", "-af", "volumedetect", "-vn", "-f", "null", "testvideo1.mp4"],
    stdout=subprocess.PIPE,
    stderr=subprocess.STDOUT,
    shell=True)
    print(result.stdout)

It says that:

PS C:\Users\balaj\OneDrive\Documents\Programming language\python files> c:; cd 'c:\Users\balaj\OneDrive\Documents\Programming language\python files'; & 'C:\Python311\python.exe' 'c:\Users\balaj\.vscode\extensions\ms-python.python-2022.20.2\pythonFiles\lib\python\debugpy\adapter/../..\debugpy\launcher' '51760' '--' 'c:\Users\balaj\OneDrive\Documents\Programming language\python files\devproject\sample.py'

b"Output #0, null, to 'testvideo1.mp4':\r\nOutput file #0 does not contain any stream\r\n"

Any help is much appreciated. Sorry for the long post... TIA!!!

Just a quick update: The result is the same for video files that have sound(I tested in VLC) and don't have sound.

Another update: I have changed the subprocess.runcode to the exact same as I called in the cmd windows:

result = subprocess.run(["ffmpeg", "-hide_banner","-i","testvideo-sound.mp4", "-af", "volumedetect", "-vn", "-f", "null", "-2>&1"]

Now the result is this :

b'The handle could not be duplicated\r\nduring redirection of handle 1.\r\n'

Solution

  • This seems to work for me. I tried it with a few different video files (mp4) I had lying around.

    import subprocess
    import re
    
    files = [
        # list of file paths
    ]
    
    def find_volume(s):
        # regex to find volume in a string
        m = re.search("(?:max_volume: )(?P<volume>.+ dB)", s, flags=re.IGNORECASE)
        
        if not m:
            return None
    
        return m.groupdict()["volume"]
    
        
    
    for filepath in files:
        r = subprocess.run(f'ffmpeg -i "{filepath}" -filter:a volumedetect -f null /dev/null',
                stdout=subprocess.PIPE,
                stderr=subprocess.STDOUT,
                shell=True
        )
        # "volume" will be either None or a string;
        # if it is none, we go to do to the "else" body
        # instead, because None if 'falsy'.
        if volume := find_volume(r.stdout.decode("utf-8")):
            print(f'Video file "{filepath}" has a max volume of {volume}')
        else:
            print(f'Video file "{filepath}" has no audio')