Add optional ReplayGain analysis to post-processing pipeline
New toggle in Settings → Library → Post-Processing: "Apply ReplayGain tags after download". When enabled, analyzes loudness via ffmpeg's ebur128 filter and writes track-level ReplayGain gain/peak tags. Runs after metadata tagging but before lossy copy so both files get the tags. Off by default — adds a few seconds per track. Applied to both album and playlist/single download paths.
This commit is contained in:
parent
461f28f084
commit
aa8f97e3d5
3 changed files with 36 additions and 1 deletions
|
|
@ -5931,7 +5931,7 @@ def handle_settings():
|
||||||
if 'active_media_server' in new_settings:
|
if 'active_media_server' in new_settings:
|
||||||
config_manager.set_active_media_server(new_settings['active_media_server'])
|
config_manager.set_active_media_server(new_settings['active_media_server'])
|
||||||
|
|
||||||
for service in ['spotify', 'plex', 'jellyfin', 'navidrome', 'soulseek', 'download_source', 'settings', 'database', 'metadata_enhancement', 'file_organization', 'playlist_sync', 'tidal', 'tidal_download', 'qobuz', 'hifi_download', 'deezer_download', 'lidarr_download', 'listenbrainz', 'acoustid', 'lastfm', 'genius', 'import', 'lossy_copy', 'listening_stats', 'ui_appearance', 'youtube', 'content_filter', 'itunes', 'm3u_export', 'musicbrainz', 'deezer', 'audiodb', 'metadata', 'hydrabase', 'security', 'discogs', 'library', 'discover', 'wishlist', 'genre_whitelist']:
|
for service in ['spotify', 'plex', 'jellyfin', 'navidrome', 'soulseek', 'download_source', 'settings', 'database', 'metadata_enhancement', 'file_organization', 'playlist_sync', 'tidal', 'tidal_download', 'qobuz', 'hifi_download', 'deezer_download', 'lidarr_download', 'listenbrainz', 'acoustid', 'lastfm', 'genius', 'import', 'lossy_copy', 'listening_stats', 'ui_appearance', 'youtube', 'content_filter', 'itunes', 'm3u_export', 'musicbrainz', 'deezer', 'audiodb', 'metadata', 'hydrabase', 'security', 'discogs', 'library', 'discover', 'wishlist', 'genre_whitelist', 'post_processing']:
|
||||||
if service in new_settings:
|
if service in new_settings:
|
||||||
for key, value in new_settings[service].items():
|
for key, value in new_settings[service].items():
|
||||||
config_manager.set(f'{service}.{key}', value)
|
config_manager.set(f'{service}.{key}', value)
|
||||||
|
|
@ -21346,6 +21346,18 @@ def _post_process_matched_download(context_key, context, file_path):
|
||||||
# Store final path for verification wrapper (before conversions may override)
|
# Store final path for verification wrapper (before conversions may override)
|
||||||
context['_final_processed_path'] = final_path
|
context['_final_processed_path'] = final_path
|
||||||
|
|
||||||
|
# ReplayGain analysis — write track-level gain tags if enabled
|
||||||
|
if config_manager.get('post_processing.replaygain_enabled', False):
|
||||||
|
try:
|
||||||
|
from core.replaygain import analyze_track as _rg_analyze, write_replaygain_tags as _rg_write, is_ffmpeg_available as _rg_ffmpeg_ok, RG_REFERENCE_LUFS as _RG_REF
|
||||||
|
if _rg_ffmpeg_ok():
|
||||||
|
lufs, peak_dbfs = _rg_analyze(final_path)
|
||||||
|
gain_db = _RG_REF - lufs
|
||||||
|
_rg_write(final_path, gain_db, peak_dbfs)
|
||||||
|
pp_logger.info(f"ReplayGain: {gain_db:+.2f} dB — {os.path.basename(final_path)}")
|
||||||
|
except Exception as rg_err:
|
||||||
|
pp_logger.debug(f"ReplayGain analysis skipped: {rg_err}")
|
||||||
|
|
||||||
# Downsample hi-res FLAC to CD quality if enabled (must run before lossy copy)
|
# Downsample hi-res FLAC to CD quality if enabled (must run before lossy copy)
|
||||||
downsampled_path = _downsample_hires_flac(final_path, context)
|
downsampled_path = _downsample_hires_flac(final_path, context)
|
||||||
if downsampled_path:
|
if downsampled_path:
|
||||||
|
|
@ -21723,6 +21735,18 @@ def _post_process_matched_download(context_key, context, file_path):
|
||||||
# 4. Generate LRC lyrics file at final location (elegant addition)
|
# 4. Generate LRC lyrics file at final location (elegant addition)
|
||||||
_generate_lrc_file(final_path, context, spotify_artist, album_info)
|
_generate_lrc_file(final_path, context, spotify_artist, album_info)
|
||||||
|
|
||||||
|
# 5. ReplayGain analysis — write track-level gain tags if enabled
|
||||||
|
if config_manager.get('post_processing.replaygain_enabled', False):
|
||||||
|
try:
|
||||||
|
from core.replaygain import analyze_track as _rg_analyze, write_replaygain_tags as _rg_write, is_ffmpeg_available as _rg_ffmpeg_ok, RG_REFERENCE_LUFS as _RG_REF
|
||||||
|
if _rg_ffmpeg_ok():
|
||||||
|
lufs, peak_dbfs = _rg_analyze(final_path)
|
||||||
|
gain_db = _RG_REF - lufs
|
||||||
|
_rg_write(final_path, gain_db, peak_dbfs)
|
||||||
|
pp_logger.info(f"ReplayGain: {gain_db:+.2f} dB, peak {peak_dbfs:.2f} dBFS — {os.path.basename(final_path)}")
|
||||||
|
except Exception as rg_err:
|
||||||
|
pp_logger.debug(f"ReplayGain analysis skipped: {rg_err}")
|
||||||
|
|
||||||
# Downsample hi-res FLAC to CD quality if enabled (must run before lossy copy)
|
# Downsample hi-res FLAC to CD quality if enabled (must run before lossy copy)
|
||||||
downsampled_path = _downsample_hires_flac(final_path, context)
|
downsampled_path = _downsample_hires_flac(final_path, context)
|
||||||
if downsampled_path:
|
if downsampled_path:
|
||||||
|
|
|
||||||
|
|
@ -5195,6 +5195,13 @@
|
||||||
Generate .lrc lyrics files (LRClib)
|
Generate .lrc lyrics files (LRClib)
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="checkbox-label">
|
||||||
|
<input type="checkbox" id="replaygain-enabled">
|
||||||
|
Apply ReplayGain tags after download
|
||||||
|
</label>
|
||||||
|
<small class="settings-hint">Analyzes loudness and writes ReplayGain track gain/peak tags. Requires ffmpeg. Adds a few seconds per track.</small>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Tag Embedding — per-tag toggles grouped by source -->
|
<!-- Tag Embedding — per-tag toggles grouped by source -->
|
||||||
|
|
|
||||||
|
|
@ -6123,6 +6123,7 @@ async function loadSettingsData() {
|
||||||
document.getElementById('cover-art-download').checked = settings.metadata_enhancement?.cover_art_download !== false;
|
document.getElementById('cover-art-download').checked = settings.metadata_enhancement?.cover_art_download !== false;
|
||||||
document.getElementById('prefer-caa-art').checked = settings.metadata_enhancement?.prefer_caa_art === true;
|
document.getElementById('prefer-caa-art').checked = settings.metadata_enhancement?.prefer_caa_art === true;
|
||||||
document.getElementById('lrclib-enabled').checked = settings.metadata_enhancement?.lrclib_enabled !== false;
|
document.getElementById('lrclib-enabled').checked = settings.metadata_enhancement?.lrclib_enabled !== false;
|
||||||
|
document.getElementById('replaygain-enabled').checked = settings.post_processing?.replaygain_enabled === true;
|
||||||
// Load service master toggles
|
// Load service master toggles
|
||||||
document.getElementById('embed-spotify').checked = settings.spotify?.embed_tags !== false;
|
document.getElementById('embed-spotify').checked = settings.spotify?.embed_tags !== false;
|
||||||
document.getElementById('embed-itunes').checked = settings.itunes?.embed_tags !== false;
|
document.getElementById('embed-itunes').checked = settings.itunes?.embed_tags !== false;
|
||||||
|
|
@ -7456,6 +7457,9 @@ async function saveSettings(quiet = false) {
|
||||||
enabled: document.getElementById('genre-whitelist-enabled').checked,
|
enabled: document.getElementById('genre-whitelist-enabled').checked,
|
||||||
genres: _collectGenreWhitelist(),
|
genres: _collectGenreWhitelist(),
|
||||||
},
|
},
|
||||||
|
post_processing: {
|
||||||
|
replaygain_enabled: document.getElementById('replaygain-enabled').checked,
|
||||||
|
},
|
||||||
library: {
|
library: {
|
||||||
music_paths: collectMusicPaths(),
|
music_paths: collectMusicPaths(),
|
||||||
music_videos_path: document.getElementById('music-videos-path').value || './MusicVideos'
|
music_videos_path: document.getElementById('music-videos-path').value || './MusicVideos'
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue