Replaced "sox" with "ffmpeg" for audio normalization
This commit is contained in:
@@ -32,7 +32,7 @@ class Tee:
|
|||||||
|
|
||||||
def check_tools():
|
def check_tools():
|
||||||
"""Checks if all required command-line tools are in the system's PATH."""
|
"""Checks if all required command-line tools are in the system's PATH."""
|
||||||
required_tools = ["ffmpeg", "ffprobe", "mkvmerge", "sox_ng", "opusenc", "mediainfo"]
|
required_tools = ["ffmpeg", "ffprobe", "mkvmerge", "opusenc", "mediainfo"]
|
||||||
print("--- Prerequisite Check ---")
|
print("--- Prerequisite Check ---")
|
||||||
all_found = True
|
all_found = True
|
||||||
for tool in required_tools:
|
for tool in required_tools:
|
||||||
@@ -50,7 +50,7 @@ def run_cmd(args, capture_output=False, check=True):
|
|||||||
|
|
||||||
def convert_audio_track(stream_index, channels, temp_dir, source_file, should_downmix, bitrate_info):
|
def convert_audio_track(stream_index, channels, temp_dir, source_file, should_downmix, bitrate_info):
|
||||||
"""Extracts, normalizes, and encodes a single audio track to Opus."""
|
"""Extracts, normalizes, and encodes a single audio track to Opus."""
|
||||||
temp_extracted = temp_dir / f"track_{stream_index}_extracted.flac"
|
temp_extracted = temp_dir / f"track_{stream_index}_extracted.flac" # This will be the input for loudnorm pass 1
|
||||||
temp_normalized = temp_dir / f"track_{stream_index}_normalized.flac"
|
temp_normalized = temp_dir / f"track_{stream_index}_normalized.flac"
|
||||||
final_opus = temp_dir / f"track_{stream_index}_final.opus"
|
final_opus = temp_dir / f"track_{stream_index}_final.opus"
|
||||||
|
|
||||||
@@ -78,9 +78,43 @@ def convert_audio_track(stream_index, channels, temp_dir, source_file, should_do
|
|||||||
ffmpeg_args.extend(["-c:a", "flac", str(temp_extracted)])
|
ffmpeg_args.extend(["-c:a", "flac", str(temp_extracted)])
|
||||||
run_cmd(ffmpeg_args)
|
run_cmd(ffmpeg_args)
|
||||||
|
|
||||||
# Step 2: Normalize the track with SoX NG
|
# Step 2: Normalize the track with ffmpeg (loudnorm 2-pass)
|
||||||
print(" - Normalizing with SoX NG...")
|
print(" - Normalizing Audio Track with ffmpeg (loudnorm 2-pass)...")
|
||||||
run_cmd(["sox_ng", str(temp_extracted), str(temp_normalized), "-S", "--temp", str(temp_dir), "--guard", "gain", "-n"])
|
# First pass: Analyze the audio to get loudnorm stats
|
||||||
|
# The stats are printed to stderr, so we must use subprocess.run directly to capture it.
|
||||||
|
print(" - Pass 1: Analyzing...")
|
||||||
|
result = subprocess.run(
|
||||||
|
["ffmpeg", "-v", "info", "-i", str(temp_extracted), "-af", "loudnorm=I=-18:LRA=7:tp=-1:print_format=json", "-f", "null", "-"],
|
||||||
|
capture_output=True, text=True, check=True)
|
||||||
|
|
||||||
|
# Find the start of the JSON block in stderr and parse it.
|
||||||
|
# This is more robust than slicing the last N lines.
|
||||||
|
# We find the start and end of the JSON block to avoid parsing extra data.
|
||||||
|
stderr_output = result.stderr
|
||||||
|
json_start_index = stderr_output.find('{')
|
||||||
|
if json_start_index == -1:
|
||||||
|
raise ValueError("Could not find start of JSON block in ffmpeg output for loudnorm analysis.")
|
||||||
|
|
||||||
|
brace_level = 0
|
||||||
|
json_end_index = -1
|
||||||
|
for i, char in enumerate(stderr_output[json_start_index:]):
|
||||||
|
if char == '{':
|
||||||
|
brace_level += 1
|
||||||
|
elif char == '}':
|
||||||
|
brace_level -= 1
|
||||||
|
if brace_level == 0:
|
||||||
|
json_end_index = json_start_index + i + 1
|
||||||
|
break
|
||||||
|
|
||||||
|
stats = json.loads(stderr_output[json_start_index:json_end_index])
|
||||||
|
|
||||||
|
# Second pass: Apply the normalization using the stats from the first pass
|
||||||
|
print(" - Pass 2: Applying normalization...")
|
||||||
|
run_cmd([
|
||||||
|
"ffmpeg", "-v", "quiet", "-stats", "-y", "-i", str(temp_extracted), "-af",
|
||||||
|
f"loudnorm=I=-18:LRA=7:tp=-1:measured_i={stats['input_i']}:measured_lra={stats['input_lra']}:measured_tp={stats['input_tp']}:measured_thresh={stats['input_thresh']}:offset={stats['target_offset']}",
|
||||||
|
"-c:a", "flac", str(temp_normalized)
|
||||||
|
])
|
||||||
|
|
||||||
# Step 3: Encode to Opus with the correct bitrate
|
# Step 3: Encode to Opus with the correct bitrate
|
||||||
bitrate = "192k" # Fallback
|
bitrate = "192k" # Fallback
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
* Remuxes existing `AAC` and `Opus` tracks without re-encoding to preserve quality.
|
* Remuxes existing `AAC` and `Opus` tracks without re-encoding to preserve quality.
|
||||||
* Re-encodes all other audio formats (DTS, AC3, TrueHD, FLAC, etc.) to Opus.
|
* Re-encodes all other audio formats (DTS, AC3, TrueHD, FLAC, etc.) to Opus.
|
||||||
* **Advanced Downmixing:** Includes an optional `--downmix` flag that converts multi-channel audio (5.1, 7.1) to stereo using a dialogue-boosting formula.
|
* **Advanced Downmixing:** Includes an optional `--downmix` flag that converts multi-channel audio (5.1, 7.1) to stereo using a dialogue-boosting formula.
|
||||||
* **Audio Normalization:** Uses `SoX` to normalize audio levels for a consistent listening experience.
|
* **Audio Normalization:** Uses a 2-pass `ffmpeg` loudnorm process (EBU R128) to normalize audio levels for a consistent and standard-compliant listening experience.
|
||||||
* **Metadata Preservation:** Carefully preserves audio track metadata such as titles, language tags, and delay/sync information.
|
* **Metadata Preservation:** Carefully preserves audio track metadata such as titles, language tags, and delay/sync information.
|
||||||
* **Detailed Logging:** Creates a separate, detailed log file for each processed MKV in the `conv_logs/` directory, capturing the full terminal output and conversion details for easy review.
|
* **Detailed Logging:** Creates a separate, detailed log file for each processed MKV in the `conv_logs/` directory, capturing the full terminal output and conversion details for easy review.
|
||||||
* **File Organization:** Automatically moves the original source files to an `original/` directory and the newly processed files to a `completed/` directory, keeping your workspace clean.
|
* **File Organization:** Automatically moves the original source files to an `original/` directory and the newly processed files to a `completed/` directory, keeping your workspace clean.
|
||||||
@@ -24,7 +24,6 @@ The following command-line tools must be installed and available in your system'
|
|||||||
* `ffmpeg`
|
* `ffmpeg`
|
||||||
* `ffprobe`
|
* `ffprobe`
|
||||||
* `mkvmerge`
|
* `mkvmerge`
|
||||||
* `sox`
|
|
||||||
* `opusenc`
|
* `opusenc`
|
||||||
* `mediainfo`
|
* `mediainfo`
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user