<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Volume-Normalization on Mozabre As Idle Moments</title><link>https://mitz17.com/en/tags/volume-normalization/</link><description>Recent content in Volume-Normalization on Mozabre As Idle Moments</description><generator>Hugo -- gohugo.io</generator><language>en-US</language><lastBuildDate>Wed, 18 Mar 2026 00:00:00 +0900</lastBuildDate><atom:link href="https://mitz17.com/en/tags/volume-normalization/index.xml" rel="self" type="application/rss+xml"/><item><title>Python MP3 Volume Normalizer with ffmpeg: How It Works and How to Use It</title><link>https://mitz17.com/en/blog/mp3-normalizer-devlog/</link><pubDate>Mon, 02 Mar 2026 00:00:00 +0900</pubDate><guid>https://mitz17.com/en/blog/mp3-normalizer-devlog/</guid><description>&lt;p&gt;GitHub: &lt;a class="link" href="https://github.com/mitz17/mp3-normalizer" target="_blank" rel="noopener"
 &gt;mitz17/mp3-normalizer&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="are-your-mp3-files-all-different-volumes"&gt;Are Your MP3 Files All Different Volumes?
&lt;/h2&gt;&lt;p&gt;Have you ever played an old MP3 file and thought, &amp;ldquo;Why is this so quiet?&amp;rdquo; The file itself is not broken, but each track has a different loudness, so you end up adjusting the volume manually every time. This tool solves that problem with &lt;strong&gt;ffmpeg&amp;rsquo;s &lt;code&gt;loudnorm&lt;/code&gt; filter&lt;/strong&gt; and a &lt;strong&gt;Python tool called &lt;code&gt;mp3-normalizer&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Here is what the tool does:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Batch-normalizes MP3 files in a folder to &lt;strong&gt;-14 LUFS&lt;/strong&gt; as a practical default target&lt;/li&gt;
&lt;li&gt;Uses the same processing engine from both &lt;strong&gt;GUI and CLI&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Re-encodes while preserving ID3 tags, lyrics, and artwork as much as possible&lt;/li&gt;
&lt;li&gt;Tracks processed history in JSON to &lt;strong&gt;avoid duplicate processing and accidental overwrite&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="what-is-ffmpeg-loudnorm"&gt;What Is ffmpeg &lt;code&gt;loudnorm&lt;/code&gt;?
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;loudnorm&lt;/code&gt; is a loudness normalization filter built into ffmpeg. Instead of relying only on waveform peak values, it uses &lt;strong&gt;LUFS (Loudness Units Full Scale)&lt;/strong&gt;, which is closer to perceived loudness. That makes it much better for fixing tracks that feel too loud or too quiet compared with each other.&lt;/p&gt;
&lt;p&gt;If you want a deeper explanation of the parameters and the difference between 1-pass and 2-pass normalization, see &lt;a class="link" href="../../../en/blog/ffmpeg-loudnorm-guide/" &gt;ffmpeg loudnorm Guide: LUFS Normalization, True Peak, and 2-Pass Settings&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="intended-readers-and-requirements"&gt;Intended Readers and Requirements
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;People who want to keep old MP3 archives but only need to &lt;strong&gt;fix loudness quickly&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;People who do not want to memorize &lt;code&gt;ffmpeg&lt;/code&gt; commands but still want batch processing&lt;/li&gt;
&lt;li&gt;Python users who also want to understand the implementation details&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You need &lt;strong&gt;Python 3.11 or later&lt;/strong&gt; and &lt;strong&gt;ffmpeg 6.x&lt;/strong&gt;. For ffmpeg installation, see &lt;a class="link" href="https://qiita.com/Tadataka_Takahashi/items/9dcb0cf308db6f5dc31b" target="_blank" rel="noopener"
 &gt;this Qiita article&lt;/a&gt;. Put the &lt;code&gt;.mp3&lt;/code&gt; files you want to normalize into one folder first. If you want to process WAV or other formats, convert them in advance or use the extension/output-format options described later.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="setup"&gt;Setup
&lt;/h2&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 1. Clone the repository&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;git clone https://github.com/mitz17/mp3-normalizer
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 2. Create and activate a virtual environment&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;python -m venv .venv
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;source .venv/bin/activate &lt;span style="color:#75715e"&gt;# Windows: .venv\Scripts\Activate.ps1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 3. Install dependencies&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;pip install -r requirements.txt
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 4. Launch&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;python main.py &lt;span style="color:#75715e"&gt;# GUI mode&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;python main.py --cli ... &lt;span style="color:#75715e"&gt;# CLI batch mode&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="usage-gui"&gt;Usage: GUI
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;Set the &lt;strong&gt;input and output directories&lt;/strong&gt;. The target MP3 list appears automatically.&lt;/li&gt;
&lt;li&gt;Enter the &lt;strong&gt;target LUFS and True Peak&lt;/strong&gt;. If you are unsure, the defaults (&lt;code&gt;-14 LUFS / -1 dBFS&lt;/code&gt;) are a safe starting point.&lt;/li&gt;
&lt;li&gt;Adjust behavior with toggles such as &lt;strong&gt;recursive scan&lt;/strong&gt; and &lt;strong&gt;force re-encode&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Run&lt;/strong&gt; and watch the progress log update in real time, then review the results after completion.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="../../../images/mp3-normalizer-gui.png" alt="mp3-normalizer GUI" loading="lazy" style="border-radius:16px; box-shadow:0 8px 24px rgba(15,23,42,0.18);"&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;You can see input/output paths, target LUFS, target files, and logs on a single screen.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="usage-cli"&gt;Usage: CLI
&lt;/h2&gt;&lt;p&gt;If you want batch processing without the GUI, use the CLI mode. It is easier to combine with scripts or task schedulers.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Basic usage&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;python main.py --cli --input ./music_in --output ./music_out
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Change LUFS and True Peak&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;python main.py --cli --input ./music_in --output ./music_out --lufs -16 --tp -1.5
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Set worker count (default: CPU core count)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;python main.py --cli --input ./music_in --output ./music_out --workers &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Change input extension and output format&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;python main.py --cli --input ./music_in --output ./music_out --ext flac --format aac
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The processing log is saved to &lt;code&gt;mp3_normalizer.log&lt;/code&gt;, including the LUFS-related output for each ffmpeg command.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="implementation-highlights"&gt;Implementation Highlights
&lt;/h2&gt;&lt;h3 id="architecture-overview"&gt;Architecture Overview
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;main.py
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── gui.py ─ Tkinter + ttk. Runs ffmpeg in a worker thread to avoid UI freezes
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── processor.py ─ Shared engine for GUI / CLI. Handles file scanning, deduplication, and normalization
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;└── utils.py ─ General utilities such as path generation and log formatting
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Processed files are tracked in &lt;code&gt;processed_history.json&lt;/code&gt; using file size and modification time, and are skipped automatically on later runs. With the default setting (&lt;code&gt;force=False&lt;/code&gt;), existing output files are not overwritten.&lt;/p&gt;
&lt;h3 id="file-scanning-and-duplicate-avoidance"&gt;File Scanning and Duplicate Avoidance
&lt;/h3&gt;&lt;p&gt;In &lt;code&gt;AudioProcessor.process_directory&lt;/code&gt; inside &lt;code&gt;processor.py&lt;/code&gt;, the tool scans the input directory, builds a processing plan, and adds suffixes such as &lt;code&gt;_1&lt;/code&gt; and &lt;code&gt;_2&lt;/code&gt; when output names would collide (&lt;a class="link" href="https://github.com/mitz17/mp3-normalizer/blob/main/processor.py#L180-L232" target="_blank" rel="noopener"
 &gt;GitHub code&lt;/a&gt;).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; index, entry &lt;span style="color:#f92672"&gt;in&lt;/span&gt; enumerate(plan&lt;span style="color:#f92672"&gt;.&lt;/span&gt;entries, start&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; destination &lt;span style="color:#f92672"&gt;=&lt;/span&gt; output_dir &lt;span style="color:#f92672"&gt;/&lt;/span&gt; entry&lt;span style="color:#f92672"&gt;.&lt;/span&gt;relative
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ensure_directory(destination&lt;span style="color:#f92672"&gt;.&lt;/span&gt;parent)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; destination &lt;span style="color:#f92672"&gt;=&lt;/span&gt; destination&lt;span style="color:#f92672"&gt;.&lt;/span&gt;with_suffix(&lt;span style="color:#e6db74"&gt;&amp;#34;.mp3&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; destination &lt;span style="color:#f92672"&gt;=&lt;/span&gt; generate_unique_output_path(destination)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; result &lt;span style="color:#f92672"&gt;=&lt;/span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;executor&lt;span style="color:#f92672"&gt;.&lt;/span&gt;normalize(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; input_file&lt;span style="color:#f92672"&gt;=&lt;/span&gt;entry&lt;span style="color:#f92672"&gt;.&lt;/span&gt;source,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; destination&lt;span style="color:#f92672"&gt;=&lt;/span&gt;destination,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; target_lufs&lt;span style="color:#f92672"&gt;=&lt;/span&gt;target_lufs,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; true_peak&lt;span style="color:#f92672"&gt;=&lt;/span&gt;true_peak,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; results&lt;span style="color:#f92672"&gt;.&lt;/span&gt;append(result)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; result&lt;span style="color:#f92672"&gt;.&lt;/span&gt;success:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;history_service&lt;span style="color:#f92672"&gt;.&lt;/span&gt;mark_processed(entry&lt;span style="color:#f92672"&gt;.&lt;/span&gt;relative, entry&lt;span style="color:#f92672"&gt;.&lt;/span&gt;size, entry&lt;span style="color:#f92672"&gt;.&lt;/span&gt;mtime)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;history_service&lt;span style="color:#f92672"&gt;.&lt;/span&gt;save()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="building-the-ffmpeg-command"&gt;Building the ffmpeg Command
&lt;/h3&gt;&lt;p&gt;Actual ffmpeg execution happens in &lt;code&gt;FfmpegExecutor.normalize&lt;/code&gt; (&lt;a class="link" href="https://github.com/mitz17/mp3-normalizer/blob/main/processor.py#L63-L111" target="_blank" rel="noopener"
 &gt;code here&lt;/a&gt;). The full command is written to the log so you can always see what the GUI is doing underneath.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;command &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;ffmpeg_cmd, &lt;span style="color:#e6db74"&gt;&amp;#34;-hide_banner&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;-y&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;-i&amp;#34;&lt;/span&gt;, str(input_file),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;-af&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;loudnorm=I=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;target_lufs&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;:TP=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;true_peak&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;:LRA=11&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;-c:a&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;libmp3lame&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;-q:a&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;2&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;-map_metadata&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;0&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; str(destination),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;command_str &lt;span style="color:#f92672"&gt;=&lt;/span&gt; format_command(command)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;logger&lt;span style="color:#f92672"&gt;.&lt;/span&gt;info(&lt;span style="color:#e6db74"&gt;&amp;#34;ffmpeg command: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;%s&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, command_str)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;completed &lt;span style="color:#f92672"&gt;=&lt;/span&gt; subprocess&lt;span style="color:#f92672"&gt;.&lt;/span&gt;run(command, check&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;False&lt;/span&gt;, capture_output&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;True&lt;/span&gt;, text&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;True&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;-map_metadata 0&lt;/code&gt; preserves ID3 metadata during re-encoding, and additional post-processing with the &lt;code&gt;mutagen&lt;/code&gt; library is used to copy lyric tags that ffmpeg alone may drop.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="update-history"&gt;Update History
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Date&lt;/th&gt;
 &lt;th&gt;Details&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;2026-03-04&lt;/td&gt;
 &lt;td&gt;Changed from serial processing to parallel processing. Processing 145 files improved from 670 seconds to 207 seconds, about 3.2x faster. Also hardened text encoding handling on Windows.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;2026-03-05&lt;/td&gt;
 &lt;td&gt;Added lyric tag copy support using &lt;code&gt;mutagen&lt;/code&gt;, so lyrics can be preserved after normalization.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;2026-03-08&lt;/td&gt;
 &lt;td&gt;Reduced excessive loudness boost on quiet intros. Added bitrate detection to keep output at a comparable bitrate. Added selectable input extension and output formats (&lt;code&gt;mp3&lt;/code&gt;, &lt;code&gt;aac&lt;/code&gt;, &lt;code&gt;flac&lt;/code&gt;, &lt;code&gt;wav&lt;/code&gt;, &lt;code&gt;ogg&lt;/code&gt;). Strengthened artwork retention with a two-step approach.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;2026-03-18&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;Known issue&lt;/strong&gt;: about 1 in 100 files may lose only the artist tag. The reproduction pattern is still unclear even within the same album. I plan to open an Issue. &lt;a class="link" href="https://github.com/mitz17/mp3-normalizer" target="_blank" rel="noopener"
 &gt;If you want to help investigate, start here&lt;/a&gt;.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="summary"&gt;Summary
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Even with a simple Tkinter plus ffmpeg setup, MP3 loudness normalization can be made very practical&lt;/li&gt;
&lt;li&gt;The tool keeps GUI interaction and CLI batch automation aligned around the same processing quality&lt;/li&gt;
&lt;li&gt;If you still have old MP3 files you want to keep using, &lt;code&gt;mp3-normalizer&lt;/code&gt; is meant to make that cleanup easier&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="related-posts"&gt;Related Posts
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="../../../en/blog/ffmpeg-loudnorm-guide/" &gt;How to Use ffmpeg loudnorm for LUFS Normalization&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="../../../en/blog/ansys-version-selector/" &gt;Why I Built an Ansys Version Selector Tool | Reducing the Risk of Opening Old Simulation Files in the Wrong Version&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>