Created
August 14, 2025 10:15
-
-
Save mako34/c1a350d3770bf456b30e07f8efe3949c to your computer and use it in GitHub Desktop.
Python normalize video file audio level
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
""" | |
Simple Audio Level Normalizer | |
Just normalizes video audio to proper voice levels | |
""" | |
import subprocess | |
import sys | |
from pathlib import Path | |
def check_ffmpeg(): | |
"""Check if FFmpeg is available""" | |
try: | |
subprocess.run(['ffmpeg', '-version'], capture_output=True, check=True) | |
return True | |
except (subprocess.CalledProcessError, FileNotFoundError): | |
print("β FFmpeg not found! Please install FFmpeg first.") | |
return False | |
def get_current_audio_levels(video_path): | |
"""Get current peak and mean audio levels""" | |
print(f"π Analyzing audio levels in: {Path(video_path).name}") | |
cmd = [ | |
'ffmpeg', '-i', video_path, | |
'-af', 'volumedetect', | |
'-vn', '-sn', '-dn', | |
'-f', 'null', '-' | |
] | |
try: | |
result = subprocess.run(cmd, capture_output=True, text=True) | |
output = result.stderr | |
max_volume = None | |
mean_volume = None | |
for line in output.split('\n'): | |
if 'max_volume:' in line: | |
max_volume = float(line.split('max_volume:')[1].split('dB')[0].strip()) | |
elif 'mean_volume:' in line: | |
mean_volume = float(line.split('mean_volume:')[1].split('dB')[0].strip()) | |
if max_volume is not None: | |
print(f" Current peak: {max_volume:.1f} dB") | |
if mean_volume is not None: | |
print(f" Current mean: {mean_volume:.1f} dB") | |
return max_volume, mean_volume | |
else: | |
print(" β οΈ Could not detect audio levels") | |
return None, None | |
except Exception as e: | |
print(f"β Error analyzing audio: {e}") | |
return None, None | |
def normalize_video_audio(input_path, output_path=None, target_peak=-12): | |
""" | |
Normalize video audio to target peak level | |
Args: | |
input_path: Path to input video | |
output_path: Path for output video (optional) | |
target_peak: Target peak level in dB (default -12 dB for voice) | |
""" | |
input_path = Path(input_path) | |
if not input_path.exists(): | |
print(f"β File not found: {input_path}") | |
return False | |
# Generate output path if not provided | |
if output_path is None: | |
output_path = input_path.parent / f"{input_path.stem}_normalized{input_path.suffix}" | |
else: | |
output_path = Path(output_path) | |
# Get current levels | |
current_peak, current_mean = get_current_audio_levels(str(input_path)) | |
if current_peak is None: | |
print("β Could not analyze audio levels") | |
return False | |
# Calculate needed gain | |
gain_needed = target_peak - current_peak | |
print(f"\nποΈ Audio Normalization:") | |
print(f" Target peak: {target_peak} dB") | |
print(f" Gain needed: {gain_needed:+.1f} dB") | |
if abs(gain_needed) < 1: | |
print(f" β Audio is already at good levels (within 1 dB)") | |
print(f" Copying file without changes...") | |
# Just copy the file | |
cmd = [ | |
'ffmpeg', '-i', str(input_path), | |
'-c', 'copy', # Copy streams without re-encoding | |
'-y', | |
str(output_path) | |
] | |
else: | |
print(f" π§ Applying {gain_needed:+.1f} dB gain...") | |
# Apply volume adjustment | |
cmd = [ | |
'ffmpeg', '-i', str(input_path), | |
'-af', f'volume={gain_needed}dB', | |
'-c:v', 'copy', # Don't re-encode video | |
'-c:a', 'aac', # Re-encode audio with normalization | |
'-b:a', '128k', # Good audio quality | |
'-y', | |
str(output_path) | |
] | |
try: | |
print("βοΈ Processing...") | |
result = subprocess.run(cmd, capture_output=True, text=True) | |
if result.returncode == 0: | |
# Check file sizes | |
input_size = input_path.stat().st_size / (1024 * 1024) | |
output_size = output_path.stat().st_size / (1024 * 1024) | |
print(f"β Success!") | |
print(f" Input: {input_size:.1f} MB") | |
print(f" Output: {output_size:.1f} MB") | |
print(f" Saved as: {output_path.name}") | |
# Verify new levels | |
print(f"\nπ Verifying new audio levels...") | |
new_peak, new_mean = get_current_audio_levels(str(output_path)) | |
if new_peak is not None: | |
print(f" New peak: {new_peak:.1f} dB β ") | |
return True | |
else: | |
print(f"β FFmpeg error: {result.stderr}") | |
return False | |
except Exception as e: | |
print(f"β Error processing: {e}") | |
return False | |
def main(): | |
if len(sys.argv) < 2: | |
print("Usage: python audio_normalizer.py input_video.mp4 [output_video.mp4] [target_db]") | |
print("\nExamples:") | |
print(" python audio_normalizer.py video.mp4") | |
print(" python audio_normalizer.py video.mp4 normalized.mp4") | |
print(" python audio_normalizer.py video.mp4 normalized.mp4 -14") | |
print("\nTarget levels:") | |
print(" -12 dB: Standard for voice content (default)") | |
print(" -14 dB: Good for YouTube/streaming") | |
print(" -16 dB: Conservative, leaves headroom") | |
sys.exit(1) | |
if not check_ffmpeg(): | |
sys.exit(1) | |
input_video = sys.argv[1] | |
output_video = sys.argv[2] if len(sys.argv) > 2 else None | |
target_db = float(sys.argv[3]) if len(sys.argv) > 3 else -12 | |
print("π¬ Simple Audio Normalizer") | |
print("=" * 30) | |
success = normalize_video_audio(input_video, output_video, target_db) | |
if success: | |
print(f"\nπ Done! Your video now has properly normalized audio levels.") | |
else: | |
print(f"\nβ Failed to normalize audio.") | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment