Lossy codecs achieve high compression ratios by removing audio information
that's considered less important to human perception. Modern lossy codecs
provide excellent quality at reasonable bitrates.
import subprocess
import os
import json
from pathlib import Path
class LossyCodecConverter:
def __init__(self):
self.ffmpeg_path = "ffmpeg" # Assumes ffmpeg is in PATH
# Codec configurations for different use cases
self.presets = {
'voice_low': {
'mp3': ['-c:a', 'libmp3lame', '-b:a', '64k', '-ar', '16000', '-ac', '1'],
'aac': ['-c:a', 'aac', '-b:a', '64k', '-ar', '16000', '-ac', '1'],
'opus': ['-c:a', 'libopus', '-b:a', '32k', '-ar', '16000', '-ac', '1']
},
'voice_high': {
'mp3': ['-c:a', 'libmp3lame', '-b:a', '128k', '-ar', '22050', '-ac', '1'],
'aac': ['-c:a', 'aac', '-b:a', '96k', '-ar', '22050', '-ac', '1'],
'opus': ['-c:a', 'libopus', '-b:a', '64k', '-ar', '24000', '-ac', '1']
},
'music_standard': {
'mp3': ['-c:a', 'libmp3lame', '-b:a', '192k', '-ar', '44100'],
'aac': ['-c:a', 'aac', '-b:a', '128k', '-ar', '44100'],
'opus': ['-c:a', 'libopus', '-b:a', '128k', '-ar', '48000']
},
'music_high': {
'mp3': ['-c:a', 'libmp3lame', '-b:a', '320k', '-ar', '44100'],
'aac': ['-c:a', 'aac', '-b:a', '256k', '-ar', '44100'],
'opus': ['-c:a', 'libopus', '-b:a', '192k', '-ar', '48000']
}
}
def convert(self, input_file, output_file, codec, preset='voice_high', custom_args=None):
"""Convert audio using lossy codec"""
try:
if custom_args:
codec_args = custom_args
else:
codec_args = self.presets.get(preset, {}).get(codec, [])
if not codec_args:
return {'error': f'No preset found for {codec} with {preset}'}
# Build FFmpeg command
cmd = [
self.ffmpeg_path,
'-i', input_file,
'-y', # Overwrite output file
*codec_args,
output_file
]
# Execute conversion
result = subprocess.run(
cmd,
capture_output=True,
text=True,
check=True
)
return {
'success': True,
'output_file': output_file,
'command': ' '.join(cmd)
}
except subprocess.CalledProcessError as e:
return {
'error': f'FFmpeg error: {e.stderr}',
'command': ' '.join(cmd)
}
except Exception as e:
return {'error': str(e)}
def batch_convert(self, input_file, output_dir, codecs=['mp3', 'aac', 'opus'], preset='voice_high'):
"""Convert to multiple formats for comparison"""
results = {}
output_path = Path(output_dir)
output_path.mkdir(exist_ok=True)
input_stem = Path(input_file).stem
for codec in codecs:
if codec == 'mp3':
output_file = output_path / f"{input_stem}.mp3"
elif codec == 'aac':
output_file = output_path / f"{input_stem}.m4a"
elif codec == 'opus':
output_file = output_path / f"{input_stem}.opus"
else:
output_file = output_path / f"{input_stem}.{codec}"
result = self.convert(input_file, str(output_file), codec, preset)
results[codec] = result
if result.get('success'):
# Add file size info
file_size = os.path.getsize(output_file)
results[codec]['file_size_mb'] = file_size / (1024 * 1024)
return results
def quality_test(self, input_file, output_dir, bitrates=[64, 128, 192, 256, 320]):
"""Test codec quality at different bitrates"""
results = {}
output_path = Path(output_dir)
output_path.mkdir(exist_ok=True)
input_stem = Path(input_file).stem
for bitrate in bitrates:
for codec in ['mp3', 'aac', 'opus']:
# Custom arguments for specific bitrate
if codec == 'mp3':
args = ['-c:a', 'libmp3lame', '-b:a', f'{bitrate}k']
ext = '.mp3'
elif codec == 'aac':
args = ['-c:a', 'aac', '-b:a', f'{bitrate}k']
ext = '.m4a'
elif codec == 'opus':
# Opus has different bitrate ranges
opus_bitrate = min(bitrate, 256) # Opus max is ~256k
args = ['-c:a', 'libopus', '-b:a', f'{opus_bitrate}k']
ext = '.opus'
output_file = output_path / f"{input_stem}_{codec}_{bitrate}k{ext}"
result = self.convert(
input_file,
str(output_file),
codec,
custom_args=args
)
if result.get('success'):
file_size = os.path.getsize(output_file)
results[f"{codec}_{bitrate}k"] = {
'codec': codec,
'bitrate': bitrate,
'file_size_mb': file_size / (1024 * 1024),
'output_file': str(output_file)
}
return results
def analyze_compression(self, original_file, compressed_files):
"""Analyze compression efficiency and quality"""
try:
# Get original file info
original_size = os.path.getsize(original_file)
original_info = self._get_audio_info(original_file)
analysis = {
'original': {
'file_size_mb': original_size / (1024 * 1024),
'duration': original_info.get('duration', 0),
'bitrate_kbps': original_info.get('bit_rate', 0) / 1000
},
'compressed': {}
}
for name, file_path in compressed_files.items():
if os.path.exists(file_path):
compressed_size = os.path.getsize(file_path)
compressed_info = self._get_audio_info(file_path)
compression_ratio = original_size / compressed_size
space_saving = ((original_size - compressed_size) / original_size) * 100
analysis['compressed'][name] = {
'file_size_mb': compressed_size / (1024 * 1024),
'compression_ratio': round(compression_ratio, 2),
'space_saving_percent': round(space_saving, 1),
'bitrate_kbps': compressed_info.get('bit_rate', 0) / 1000
}
return analysis
except Exception as e:
return {'error': str(e)}
def _get_audio_info(self, file_path):
"""Get audio file information using ffprobe"""
try:
cmd = [
'ffprobe',
'-v', 'quiet',
'-print_format', 'json',
'-show_format',
'-show_streams',
file_path
]
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
data = json.loads(result.stdout)
# Extract audio stream info
audio_stream = None
for stream in data.get('streams', []):
if stream.get('codec_type') == 'audio':
audio_stream = stream
break
if audio_stream:
return {
'codec': audio_stream.get('codec_name'),
'sample_rate': int(audio_stream.get('sample_rate', 0)),
'channels': audio_stream.get('channels', 1),
'bit_rate': int(audio_stream.get('bit_rate', 0)),
'duration': float(data.get('format', {}).get('duration', 0))
}
return {}
except Exception as e:
return {'error': str(e)}
# Usage example
if __name__ == "__main__":
converter = LossyCodecConverter()
# Convert to multiple formats
results = converter.batch_convert(
'input.wav',
'output/',
codecs=['mp3', 'aac', 'opus'],
preset='voice_high'
)
print("Batch conversion results:")
for codec, result in results.items():
if result.get('success'):
print(f" {codec}: {result['file_size_mb']:.2f} MB")
else:
print(f" {codec}: Failed - {result.get('error')}")
# Quality test at different bitrates
quality_results = converter.quality_test('input.wav', 'quality_test/')
print(f"\nQuality test completed: {len(quality_results)} files generated")
# Analyze compression
compressed_files = {
'mp3_128k': 'output/test_128k.mp3',
'aac_128k': 'output/test_128k.m4a',
'opus_128k': 'output/test_128k.opus'
}
analysis = converter.analyze_compression('input.wav', compressed_files)
print(f"\nCompression Analysis: {analysis}")