Add Blasphemy Mode to delete FLAC after MP3
Introduce an optional "Blasphemy Mode" that deletes the original FLAC after a verified MP3 copy is created. - config: add lossy_copy.delete_original (default: false). - webui/index.html & static script: add checkbox and warning in settings UI and persist the setting. - web_server.py: make _create_lossy_copy return the MP3 path when it deletes the FLAC (otherwise None); validate the MP3 using mutagen before removing the FLAC; rename associated .lrc files if present; update post-processing to use the final processed path in logs and wishlist checks and to consider .mp3 variants when FLAC may have been removed. Behavior is off by default and includes safety checks and logging to avoid accidental deletion of originals.
This commit is contained in:
parent
24bfc2462d
commit
129f69fce9
4 changed files with 64 additions and 12 deletions
|
|
@ -222,7 +222,8 @@ class ConfigManager:
|
|||
},
|
||||
"lossy_copy": {
|
||||
"enabled": False,
|
||||
"bitrate": "320"
|
||||
"bitrate": "320",
|
||||
"delete_original": False
|
||||
},
|
||||
"import": {
|
||||
"staging_path": "./Staging"
|
||||
|
|
|
|||
|
|
@ -8380,13 +8380,15 @@ def _create_lossy_copy(final_path):
|
|||
|
||||
Only runs when lossy_copy is enabled and the file is a FLAC.
|
||||
Places the MP3 alongside the FLAC with the same basename.
|
||||
|
||||
Returns the MP3 path if Blasphemy Mode deleted the original, else None.
|
||||
"""
|
||||
if not config_manager.get('lossy_copy.enabled', False):
|
||||
return
|
||||
return None
|
||||
|
||||
ext = os.path.splitext(final_path)[1].lower()
|
||||
if ext != '.flac':
|
||||
return
|
||||
return None
|
||||
|
||||
bitrate = config_manager.get('lossy_copy.bitrate', '320')
|
||||
mp3_quality = f'MP3-{bitrate}'
|
||||
|
|
@ -8407,7 +8409,7 @@ def _create_lossy_copy(final_path):
|
|||
ffmpeg_bin = local
|
||||
else:
|
||||
print("⚠️ [Lossy Copy] ffmpeg not found — skipping MP3 conversion")
|
||||
return
|
||||
return None
|
||||
|
||||
try:
|
||||
print(f"🎵 [Lossy Copy] Converting to MP3-{bitrate}: {os.path.basename(final_path)}")
|
||||
|
|
@ -8430,12 +8432,39 @@ def _create_lossy_copy(final_path):
|
|||
tags.save()
|
||||
except Exception as tag_err:
|
||||
print(f"⚠️ [Lossy Copy] Could not update QUALITY tag: {tag_err}")
|
||||
|
||||
# Blasphemy Mode: delete original FLAC if enabled and MP3 is verified
|
||||
if config_manager.get('lossy_copy.delete_original', False):
|
||||
try:
|
||||
if os.path.isfile(mp3_path) and os.path.getsize(mp3_path) > 0:
|
||||
from mutagen import File as MutagenFile
|
||||
test_audio = MutagenFile(mp3_path)
|
||||
if test_audio is not None:
|
||||
os.remove(final_path)
|
||||
print(f"🔥 [Blasphemy Mode] Deleted original: {os.path.basename(final_path)}")
|
||||
# Rename LRC file to match the MP3 filename
|
||||
flac_lrc = os.path.splitext(final_path)[0] + '.lrc'
|
||||
if os.path.isfile(flac_lrc):
|
||||
mp3_lrc = os.path.splitext(mp3_path)[0] + '.lrc'
|
||||
try:
|
||||
os.rename(flac_lrc, mp3_lrc)
|
||||
print(f"🔥 [Blasphemy Mode] Renamed LRC: {os.path.basename(flac_lrc)} -> {os.path.basename(mp3_lrc)}")
|
||||
except Exception as lrc_err:
|
||||
print(f"⚠️ [Blasphemy Mode] Could not rename LRC: {lrc_err}")
|
||||
return mp3_path
|
||||
else:
|
||||
print(f"⚠️ [Blasphemy Mode] MP3 failed audio validation, keeping original: {os.path.basename(final_path)}")
|
||||
else:
|
||||
print(f"⚠️ [Blasphemy Mode] MP3 missing or empty, keeping original: {os.path.basename(final_path)}")
|
||||
except Exception as del_err:
|
||||
print(f"⚠️ [Blasphemy Mode] Error during original deletion, keeping original: {del_err}")
|
||||
else:
|
||||
print(f"⚠️ [Lossy Copy] ffmpeg failed: {result.stderr[:200]}")
|
||||
except subprocess.TimeoutExpired:
|
||||
print(f"⚠️ [Lossy Copy] Conversion timed out for: {os.path.basename(final_path)}")
|
||||
except Exception as e:
|
||||
print(f"⚠️ [Lossy Copy] Conversion error: {e}")
|
||||
return None
|
||||
|
||||
def _apply_path_template(template: str, context: dict) -> str:
|
||||
"""
|
||||
|
|
@ -9934,13 +9963,15 @@ def _post_process_matched_download(context_key, context, file_path):
|
|||
_safe_move_file(file_path, final_path)
|
||||
|
||||
# Lossy copy: create MP3 version if enabled
|
||||
_create_lossy_copy(final_path)
|
||||
blasphemy_path = _create_lossy_copy(final_path)
|
||||
if blasphemy_path:
|
||||
context['_final_processed_path'] = blasphemy_path
|
||||
|
||||
# Clean up empty directories in downloads folder
|
||||
downloads_path = docker_resolve_path(config_manager.get('soulseek.download_path', './downloads'))
|
||||
_cleanup_empty_directories(downloads_path, file_path)
|
||||
|
||||
print(f"✅ [Playlist Folder Mode] Post-processing complete: {final_path}")
|
||||
print(f"✅ [Playlist Folder Mode] Post-processing complete: {context.get('_final_processed_path', final_path)}")
|
||||
|
||||
# WISHLIST REMOVAL: Check if this track should be removed from wishlist
|
||||
try:
|
||||
|
|
@ -10194,11 +10225,17 @@ def _post_process_matched_download(context_key, context, file_path):
|
|||
expected_stem = os.path.splitext(os.path.basename(final_path))[0]
|
||||
expected_ext = os.path.splitext(final_path)[1]
|
||||
found_variant = None
|
||||
# Also check for .mp3 if Blasphemy Mode may have deleted the .flac
|
||||
check_exts = {expected_ext}
|
||||
if expected_ext == '.flac' and config_manager.get('lossy_copy.enabled', False) and config_manager.get('lossy_copy.delete_original', False):
|
||||
check_exts.add('.mp3')
|
||||
if os.path.exists(expected_dir):
|
||||
for f in os.listdir(expected_dir):
|
||||
# Match files that start with the expected stem and have the same extension
|
||||
# Match files that start with the expected stem and have a matching extension
|
||||
# This catches "01 - track [FLAC 24bit].flac" when expecting "01 - track.flac"
|
||||
if f.endswith(expected_ext) and os.path.splitext(f)[0].startswith(expected_stem):
|
||||
# and "01 - track [MP3-320].mp3" when Blasphemy Mode deleted the FLAC
|
||||
f_ext = os.path.splitext(f)[1].lower()
|
||||
if f_ext in check_exts and os.path.splitext(f)[0].startswith(expected_stem):
|
||||
found_variant = os.path.join(expected_dir, f)
|
||||
break
|
||||
if found_variant:
|
||||
|
|
@ -10219,13 +10256,15 @@ def _post_process_matched_download(context_key, context, file_path):
|
|||
_generate_lrc_file(final_path, context, spotify_artist, album_info)
|
||||
|
||||
# Lossy copy: create MP3 version if enabled
|
||||
_create_lossy_copy(final_path)
|
||||
blasphemy_path = _create_lossy_copy(final_path)
|
||||
if blasphemy_path:
|
||||
context['_final_processed_path'] = blasphemy_path
|
||||
|
||||
downloads_path = docker_resolve_path(config_manager.get('soulseek.download_path', './downloads'))
|
||||
_cleanup_empty_directories(downloads_path, file_path)
|
||||
|
||||
print(f"✅ Post-processing complete for: {final_path}")
|
||||
|
||||
print(f"✅ Post-processing complete for: {context.get('_final_processed_path', final_path)}")
|
||||
|
||||
# WISHLIST REMOVAL: Check if this track should be removed from wishlist after successful download
|
||||
try:
|
||||
_check_and_remove_from_wishlist(context)
|
||||
|
|
|
|||
|
|
@ -3309,6 +3309,16 @@
|
|||
After downloading a FLAC, an MP3 copy at the selected bitrate
|
||||
will be created in the same folder. Requires ffmpeg.
|
||||
</div>
|
||||
<div class="form-group" style="margin-top: 12px;">
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" id="lossy-copy-delete-original">
|
||||
Blasphemy Mode — Delete original FLAC after MP3 is created
|
||||
</label>
|
||||
</div>
|
||||
<div class="help-text" style="color: rgba(255, 100, 100, 0.8);">
|
||||
Warning: The original high-quality file will be permanently deleted.
|
||||
Only the MP3 copy will remain.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -1833,6 +1833,7 @@ async function loadSettingsData() {
|
|||
// Populate Lossy Copy settings
|
||||
document.getElementById('lossy-copy-enabled').checked = settings.lossy_copy?.enabled === true;
|
||||
document.getElementById('lossy-copy-bitrate').value = settings.lossy_copy?.bitrate || '320';
|
||||
document.getElementById('lossy-copy-delete-original').checked = settings.lossy_copy?.delete_original === true;
|
||||
document.getElementById('lossy-copy-options').style.display =
|
||||
settings.lossy_copy?.enabled ? 'block' : 'none';
|
||||
|
||||
|
|
@ -2432,7 +2433,8 @@ async function saveSettings(quiet = false) {
|
|||
},
|
||||
lossy_copy: {
|
||||
enabled: document.getElementById('lossy-copy-enabled').checked,
|
||||
bitrate: document.getElementById('lossy-copy-bitrate').value
|
||||
bitrate: document.getElementById('lossy-copy-bitrate').value,
|
||||
delete_original: document.getElementById('lossy-copy-delete-original').checked
|
||||
},
|
||||
import: {
|
||||
staging_path: document.getElementById('staging-path').value || './Staging'
|
||||
|
|
|
|||
Loading…
Reference in a new issue