Explicit content filter with configurable toggle to skip explicit tracks during downloads

pull/253/head
Broque Thomas 2 months ago
parent 16e01f6039
commit bbccd3524f

@ -248,6 +248,9 @@ class ConfigManager:
"url": "",
"api_key": "",
"auto_connect": False
},
"content_filter": {
"allow_explicit": True
}
}

@ -3894,7 +3894,7 @@ def handle_settings():
if 'active_media_server' in new_settings:
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', 'listenbrainz', 'acoustid', 'import', 'lossy_copy', 'ui_appearance', 'youtube']:
for service in ['spotify', 'plex', 'jellyfin', 'navidrome', 'soulseek', 'download_source', 'settings', 'database', 'metadata_enhancement', 'file_organization', 'playlist_sync', 'tidal', 'tidal_download', 'listenbrainz', 'acoustid', 'import', 'lossy_copy', 'ui_appearance', 'youtube', 'content_filter']:
if service in new_settings:
for key, value in new_settings[service].items():
config_manager.set(f'{service}.{key}', value)
@ -10477,6 +10477,24 @@ def search_match():
return jsonify({"error": str(e)}), 500
def _is_explicit_blocked(track_data):
"""Check if a track should be blocked by the explicit content filter.
Returns True if the track is explicit and explicit content is disabled."""
if config_manager.get('content_filter.allow_explicit', True):
return False
# Check direct explicit field
if track_data.get('explicit', False):
return True
# Check nested spotify_data (wishlist tracks)
sp_data = track_data.get('spotify_data', {})
if isinstance(sp_data, str):
try:
sp_data = json.loads(sp_data)
except Exception:
sp_data = {}
return sp_data.get('explicit', False)
def _start_enhanced_album_download(enhanced_tracks, unmatched_tracks, spotify_artist, spotify_album):
"""
Download album tracks that have been matched to Spotify with full track metadata.
@ -10496,6 +10514,10 @@ def _start_enhanced_album_download(enhanced_tracks, unmatched_tracks, spotify_ar
slskd_track = matched_item['slskd_track']
spotify_track = matched_item['spotify_track']
if _is_explicit_blocked(spotify_track):
logger.info(f"🚫 [Content Filter] Skipping explicit track: '{spotify_track.get('name')}'")
continue
username = slskd_track.get('username')
filename = slskd_track.get('filename')
size = slskd_track.get('size', 0)
@ -10622,6 +10644,10 @@ def _start_album_download_tasks(album_result, spotify_artist, spotify_album):
corrected_meta = _match_track_to_spotify_title(parsed_meta, official_spotify_tracks)
# --- END OF CRITICAL STEP ---
if _is_explicit_blocked(corrected_meta):
print(f"🚫 [Content Filter] Skipping explicit track: '{corrected_meta.get('title')}'")
continue
# Create a clean context object using the CORRECTED metadata
individual_track_context = {
'username': username,
@ -10686,6 +10712,8 @@ def start_matched_download():
# NEW: Enhanced single track with full Spotify metadata
if is_single_track and spotify_track:
if _is_explicit_blocked(spotify_track):
return jsonify({"success": False, "error": "Explicit content is disabled in settings", "explicit_blocked": True}), 403
logger.info(f"🎯 Starting enhanced single track download: '{spotify_track['name']}' by {spotify_artist['name']}")
username = download_payload.get('username')
@ -13065,7 +13093,8 @@ def _get_spotify_album_tracks(spotify_album: dict) -> list:
'name': item.get('name'),
'track_number': item.get('track_number'),
'disc_number': item.get('disc_number', 1),
'id': item.get('id')
'id': item.get('id'),
'explicit': item.get('explicit', False)
} for item in tracks_data['items']]
return []
except Exception as e:
@ -13092,7 +13121,8 @@ def _match_track_to_spotify_title(slsk_track_meta: dict, spotify_tracks: list) -
'artist': slsk_track_meta.get('artist'),
'album': slsk_track_meta.get('album'),
'track_number': sp_track['track_number'],
'disc_number': sp_track.get('disc_number', 1)
'disc_number': sp_track.get('disc_number', 1),
'explicit': sp_track.get('explicit', False)
}
# Priority 2: Match by title similarity (if track number fails)
@ -13106,7 +13136,7 @@ def _match_track_to_spotify_title(slsk_track_meta: dict, spotify_tracks: list) -
if score > best_score:
best_score = score
best_match = sp_track
if best_match:
print(f"✅ Matched track by title similarity ({best_score:.2f}): '{slsk_track_meta['title']}' -> '{best_match['name']}'")
return {
@ -13114,7 +13144,8 @@ def _match_track_to_spotify_title(slsk_track_meta: dict, spotify_tracks: list) -
'artist': slsk_track_meta.get('artist'),
'album': slsk_track_meta.get('album'),
'track_number': best_match['track_number'],
'disc_number': best_match.get('disc_number', 1)
'disc_number': best_match.get('disc_number', 1),
'explicit': best_match.get('explicit', False)
}
print(f"⚠️ Could not confidently match track '{slsk_track_meta['title']}'. Using original metadata.")
@ -18555,6 +18586,14 @@ def _run_full_missing_tracks_process(batch_id, playlist_id, tracks_json):
missing_tracks = [res for res in analysis_results if not res['found']]
# Filter explicit tracks if content filter is enabled
if not config_manager.get('content_filter.allow_explicit', True):
before_count = len(missing_tracks)
missing_tracks = [res for res in missing_tracks if not _is_explicit_blocked(res.get('track', {}))]
skipped = before_count - len(missing_tracks)
if skipped > 0:
print(f"🚫 [Content Filter] Filtered out {skipped} explicit track(s) from download queue")
with tasks_lock:
if batch_id in download_batches:
download_batches[batch_id]['analysis_results'] = analysis_results
@ -19889,6 +19928,16 @@ def start_playlist_missing_downloads(playlist_id):
if not missing_tracks:
return jsonify({"success": False, "error": "No missing tracks provided"}), 400
# Filter explicit tracks if content filter is enabled
if not config_manager.get('content_filter.allow_explicit', True):
before_count = len(missing_tracks)
missing_tracks = [t for t in missing_tracks if not _is_explicit_blocked(t.get('track', t))]
skipped = before_count - len(missing_tracks)
if skipped > 0:
print(f"🚫 [Content Filter] Filtered out {skipped} explicit track(s) from playlist download")
if not missing_tracks:
return jsonify({"success": False, "error": "All tracks were filtered by explicit content setting"}), 400
# Add activity for playlist download missing start
playlist_name = data.get('playlist_name', f'Playlist {playlist_id}')
add_activity_item("📥", "Missing Tracks Download Started", f"'{playlist_name}' - {len(missing_tracks)} tracks", "Now")

@ -3782,6 +3782,22 @@
</div>
</div>
<!-- Content Filter Settings -->
<div class="settings-group">
<h3>🔞 Content Filter</h3>
<div class="form-group">
<label class="checkbox-label">
<input type="checkbox" id="allow-explicit" checked>
Allow explicit content in downloads
</label>
</div>
<div class="help-text">
When disabled, tracks marked as explicit on Spotify will be skipped
during matched downloads. Does not affect manual Soulseek searches.
</div>
</div>
<!-- File Organization Settings -->
<div class="settings-group">
<h3>📁 File Organization</h3>

@ -3946,6 +3946,9 @@ async function loadSettingsData() {
document.getElementById('lossy-copy-options').style.display =
settings.lossy_copy?.enabled ? 'block' : 'none';
// Populate Content Filter settings
document.getElementById('allow-explicit').checked = settings.content_filter?.allow_explicit !== false;
// Populate M3U Export settings
document.getElementById('m3u-export-enabled').checked = settings.m3u_export?.enabled === true;
@ -4637,6 +4640,9 @@ async function saveSettings(quiet = false) {
playlist_sync: {
create_backup: document.getElementById('create-backup').checked
},
content_filter: {
allow_explicit: document.getElementById('allow-explicit').checked
},
lossy_copy: {
enabled: document.getElementById('lossy-copy-enabled').checked,
bitrate: document.getElementById('lossy-copy-bitrate').value,

Loading…
Cancel
Save