From 4635ea895bcecd4bd288147becec5b0147e6aca9 Mon Sep 17 00:00:00 2001 From: Broque Thomas <26755000+Nezreka@users.noreply.github.com> Date: Sun, 15 Mar 2026 11:19:09 -0700 Subject: [PATCH] include a '?' to include more details for each job. --- core/repair_jobs/acoustid_scanner.py | 12 + core/repair_jobs/album_completeness.py | 10 + core/repair_jobs/base.py | 1 + core/repair_jobs/cache_evictor.py | 7 + core/repair_jobs/dead_file_cleaner.py | 8 + core/repair_jobs/duplicate_detector.py | 10 + core/repair_jobs/fake_lossless_detector.py | 11 + core/repair_jobs/library_reorganize.py | 13 + core/repair_jobs/metadata_gap_filler.py | 10 + core/repair_jobs/missing_cover_art.py | 9 + core/repair_jobs/orphan_file_detector.py | 8 + core/repair_jobs/track_number_repair.py | 11 + core/repair_worker.py | 1 + webui/index.html | 2 +- webui/static/script.js | 198 ++++++++-- webui/static/style.css | 427 +++++++++++++++------ 16 files changed, 589 insertions(+), 149 deletions(-) diff --git a/core/repair_jobs/acoustid_scanner.py b/core/repair_jobs/acoustid_scanner.py index 4ac62679..4ca8b9c5 100644 --- a/core/repair_jobs/acoustid_scanner.py +++ b/core/repair_jobs/acoustid_scanner.py @@ -20,6 +20,18 @@ class AcoustIDScannerJob(RepairJob): job_id = 'acoustid_scanner' display_name = 'AcoustID Scanner' description = 'Fingerprints tracks to detect wrong downloads' + help_text = ( + 'Generates audio fingerprints using the AcoustID/Chromaprint service and compares ' + 'the identified recording against what you expected to download. This catches cases ' + 'where the wrong song was served — even if the filename looks correct.\n\n' + 'The job processes tracks in batches and saves a checkpoint so it can resume where ' + 'it left off across runs. Requires an AcoustID API key (set in Settings).\n\n' + 'Settings:\n' + '- Fingerprint Threshold: Minimum AcoustID match confidence (0.0 - 1.0)\n' + '- Title Similarity: How closely the identified title must match your expected title\n' + '- Artist Similarity: How closely the identified artist must match\n' + '- Batch Size: Number of tracks to process per scan run' + ) icon = 'repair-icon-acoustid' default_enabled = False default_interval_hours = 168 diff --git a/core/repair_jobs/album_completeness.py b/core/repair_jobs/album_completeness.py index 06886b12..cd857ddf 100644 --- a/core/repair_jobs/album_completeness.py +++ b/core/repair_jobs/album_completeness.py @@ -12,6 +12,16 @@ class AlbumCompletenessJob(RepairJob): job_id = 'album_completeness' display_name = 'Album Completeness' description = 'Checks if all tracks from albums are present' + help_text = ( + 'Compares the number of tracks you have for each album against the expected total ' + 'from the Spotify tracklist. Albums where tracks are missing get flagged as findings ' + 'with details about which tracks are absent.\n\n' + 'Useful for catching partial downloads or albums where some tracks failed to download. ' + 'You can use the Download Missing feature from the album page to fill gaps.\n\n' + 'Settings:\n' + '- Min Tracks For Check: Only check albums with at least this many expected tracks ' + '(skips singles and EPs)' + ) icon = 'repair-icon-completeness' default_enabled = False default_interval_hours = 168 diff --git a/core/repair_jobs/base.py b/core/repair_jobs/base.py index eadf475c..ce35cd09 100644 --- a/core/repair_jobs/base.py +++ b/core/repair_jobs/base.py @@ -58,6 +58,7 @@ class RepairJob(ABC): job_id: str = '' display_name: str = '' description: str = '' + help_text: str = '' # Extended explanation shown in the info modal icon: str = '' default_enabled: bool = False default_interval_hours: int = 24 diff --git a/core/repair_jobs/cache_evictor.py b/core/repair_jobs/cache_evictor.py index 68e3c613..2bf179df 100644 --- a/core/repair_jobs/cache_evictor.py +++ b/core/repair_jobs/cache_evictor.py @@ -12,6 +12,13 @@ class CacheEvictorJob(RepairJob): job_id = 'cache_evictor' display_name = 'Cache Evictor' description = 'Removes expired metadata cache entries' + help_text = ( + 'Automatically cleans up stale metadata cache entries from the database. ' + 'SoulSync caches search results, album metadata, and cover art URLs to avoid ' + 'hitting API rate limits. Over time these entries expire and take up space.\n\n' + 'This is the only fully automatic job — it runs silently and removes expired ' + 'entries without creating findings. Safe to leave enabled at all times.' + ) icon = 'repair-icon-cache' default_enabled = True default_interval_hours = 6 diff --git a/core/repair_jobs/dead_file_cleaner.py b/core/repair_jobs/dead_file_cleaner.py index 60d8c5e2..ac0e8fe4 100644 --- a/core/repair_jobs/dead_file_cleaner.py +++ b/core/repair_jobs/dead_file_cleaner.py @@ -14,6 +14,14 @@ class DeadFileCleanerJob(RepairJob): job_id = 'dead_file_cleaner' display_name = 'Dead File Cleaner' description = 'Finds database entries pointing to missing files' + help_text = ( + 'Checks every track in your database to verify the actual audio file still exists ' + 'on disk. If a file has been moved, renamed, or deleted outside of SoulSync, the ' + 'database entry becomes a "dead" reference.\n\n' + 'Each dead reference is reported as a finding. You can then resolve it by re-downloading ' + 'the track or dismiss it to clean up the database entry.\n\n' + 'This job only scans and reports — it never deletes database entries automatically.' + ) icon = 'repair-icon-deadfile' default_enabled = True default_interval_hours = 24 diff --git a/core/repair_jobs/duplicate_detector.py b/core/repair_jobs/duplicate_detector.py index d558092d..70e754fe 100644 --- a/core/repair_jobs/duplicate_detector.py +++ b/core/repair_jobs/duplicate_detector.py @@ -16,6 +16,16 @@ class DuplicateDetectorJob(RepairJob): job_id = 'duplicate_detector' display_name = 'Duplicate Detector' description = 'Finds potential duplicate tracks in your library' + help_text = ( + 'Groups tracks by similar title and artist name using fuzzy matching, then flags ' + 'groups where multiple copies exist. This helps you find accidental duplicates ' + 'from re-downloads, compilation albums, or similar-titled tracks.\n\n' + 'Each duplicate group is reported as a finding with details about every copy ' + '(file path, format, bitrate) so you can decide which to keep.\n\n' + 'Settings:\n' + '- Title Similarity: How closely titles must match to be considered duplicates (0.0 - 1.0)\n' + '- Artist Similarity: How closely artist names must match (0.0 - 1.0)' + ) icon = 'repair-icon-duplicate' default_enabled = False default_interval_hours = 168 diff --git a/core/repair_jobs/fake_lossless_detector.py b/core/repair_jobs/fake_lossless_detector.py index ec5121c3..93e81794 100644 --- a/core/repair_jobs/fake_lossless_detector.py +++ b/core/repair_jobs/fake_lossless_detector.py @@ -19,6 +19,17 @@ class FakeLosslessDetectorJob(RepairJob): job_id = 'fake_lossless_detector' display_name = 'Fake Lossless Detector' description = 'Detects FLAC/WAV files likely transcoded from lossy' + help_text = ( + 'Analyzes the spectral content of FLAC and WAV files to detect if they were ' + 'transcoded from a lossy source (like MP3 or AAC). A genuine lossless file has ' + 'audio content extending to 20kHz+, while a transcoded file shows a sharp ' + 'frequency cutoff around 16-18kHz.\n\n' + 'Files flagged as fake lossless are reported as findings. You may want to ' + 're-download them from a better source or keep the lossy version.\n\n' + 'Settings:\n' + '- Spectral Cutoff kHz: Frequency threshold below which a file is considered ' + 'suspicious (default 16.0 kHz)' + ) icon = 'repair-icon-lossless' default_enabled = False default_interval_hours = 168 diff --git a/core/repair_jobs/library_reorganize.py b/core/repair_jobs/library_reorganize.py index 52c1c56e..47870ae4 100644 --- a/core/repair_jobs/library_reorganize.py +++ b/core/repair_jobs/library_reorganize.py @@ -225,6 +225,19 @@ class LibraryReorganizeJob(RepairJob): job_id = 'library_reorganize' display_name = 'Library Reorganize' description = 'Moves files to match the current file organization template (dry run by default)' + help_text = ( + 'Scans your transfer folder and reads each audio file\'s tags (artist, album, title, ' + 'track number, disc number) to compute the expected file path based on your current ' + 'file organization template from Settings.\n\n' + 'Any file whose actual path doesn\'t match the expected template gets flagged. In dry ' + 'run mode (default), a finding is created showing the current and expected paths. ' + 'Disable dry run to have the job move files automatically.\n\n' + 'Safety features: case-insensitive path comparison on Windows/macOS, collision ' + 'detection, path escape prevention, and sidecar file handling (.lrc, .nfo, etc.).\n\n' + 'Settings:\n' + '- Dry Run: When enabled, only reports what would change without moving files\n' + '- Move Sidecars: Also move associated files (.lrc, .jpg, .nfo) alongside audio files' + ) icon = 'repair-icon-reorganize' default_enabled = False default_interval_hours = 168 # Weekly — but disabled by default so won't auto-run diff --git a/core/repair_jobs/metadata_gap_filler.py b/core/repair_jobs/metadata_gap_filler.py index 2b186a0f..606f3581 100644 --- a/core/repair_jobs/metadata_gap_filler.py +++ b/core/repair_jobs/metadata_gap_filler.py @@ -14,6 +14,16 @@ class MetadataGapFillerJob(RepairJob): job_id = 'metadata_gap_filler' display_name = 'Metadata Gap Filler' description = 'Finds tracks missing ISRC or MusicBrainz IDs and locates them' + help_text = ( + 'Searches for tracks in your library that are missing important metadata identifiers: ' + 'ISRC codes and MusicBrainz recording IDs. These identifiers are used for accurate ' + 'matching, scrobbling, and enrichment.\n\n' + 'For each track with gaps, the job queries MusicBrainz by title and artist to find ' + 'the correct IDs. Results are reported as findings for your review.\n\n' + 'Settings:\n' + '- Fill ISRC: Look up missing ISRC codes\n' + '- Fill MusicBrainz ID: Look up missing MusicBrainz recording IDs' + ) icon = 'repair-icon-metadata' default_enabled = False default_interval_hours = 72 diff --git a/core/repair_jobs/missing_cover_art.py b/core/repair_jobs/missing_cover_art.py index 6dffdbfa..bc8888db 100644 --- a/core/repair_jobs/missing_cover_art.py +++ b/core/repair_jobs/missing_cover_art.py @@ -12,6 +12,15 @@ class MissingCoverArtJob(RepairJob): job_id = 'missing_cover_art' display_name = 'Cover Art Filler' description = 'Finds albums missing artwork and locates art from Spotify/iTunes' + help_text = ( + 'Scans your library for albums that have no cover art stored in the database. ' + 'For each missing cover, it searches Spotify and iTunes APIs using the album name ' + 'and artist to find matching artwork.\n\n' + 'When artwork is found, a finding is created with the image URL so you can review ' + 'and apply it. The job does not download or embed artwork automatically.\n\n' + 'Settings:\n' + '- Prefer Source: Which API to try first for artwork (spotify or itunes)' + ) icon = 'repair-icon-coverart' default_enabled = True default_interval_hours = 48 diff --git a/core/repair_jobs/orphan_file_detector.py b/core/repair_jobs/orphan_file_detector.py index 6ec61669..9213ca77 100644 --- a/core/repair_jobs/orphan_file_detector.py +++ b/core/repair_jobs/orphan_file_detector.py @@ -17,6 +17,14 @@ class OrphanFileDetectorJob(RepairJob): job_id = 'orphan_file_detector' display_name = 'Orphan File Detector' description = 'Finds audio files not tracked in the database' + help_text = ( + 'Walks your transfer folder looking for audio files (FLAC, MP3, M4A, OGG, WAV, etc.) ' + 'that exist on disk but have no matching entry in the SoulSync database.\n\n' + 'Orphan files can appear after manual folder edits, interrupted downloads, or database ' + 'issues. Each orphan is reported as a finding so you can decide whether to import it ' + 'into your library or remove it.\n\n' + 'This job only scans and reports — it never moves or deletes files on its own.' + ) icon = 'repair-icon-orphan' default_enabled = True default_interval_hours = 24 diff --git a/core/repair_jobs/track_number_repair.py b/core/repair_jobs/track_number_repair.py index 8c152434..10d0d201 100644 --- a/core/repair_jobs/track_number_repair.py +++ b/core/repair_jobs/track_number_repair.py @@ -30,6 +30,17 @@ class TrackNumberRepairJob(RepairJob): job_id = 'track_number_repair' display_name = 'Track Number Repair' description = 'Detects mismatched track numbers using API lookups (dry run by default)' + help_text = ( + 'Scans album folders and compares each file\'s track number against the correct ' + 'tracklist from Spotify or iTunes. If a file\'s embedded track number doesn\'t match ' + 'the API data, the job creates a finding showing what needs to change.\n\n' + 'In dry run mode (default), no files are modified — you review each proposed change ' + 'in the Findings tab and decide what to approve. Disable dry run in settings to let ' + 'the job automatically rename and re-number files.\n\n' + 'Settings:\n' + '- Title Similarity: How closely a filename must match the API track title (0.0 - 1.0)\n' + '- Dry Run: When enabled, only reports issues without modifying files' + ) icon = 'repair-icon-tracknumber' default_enabled = True default_interval_hours = 24 diff --git a/core/repair_worker.py b/core/repair_worker.py index 0984301b..fb77d755 100644 --- a/core/repair_worker.py +++ b/core/repair_worker.py @@ -231,6 +231,7 @@ class RepairWorker: 'job_id': job_id, 'display_name': job.display_name, 'description': job.description, + 'help_text': job.help_text, 'icon': job.icon, 'auto_fix': job.auto_fix, 'enabled': config['enabled'], diff --git a/webui/index.html b/webui/index.html index bdb836bc..fa413569 100644 --- a/webui/index.html +++ b/webui/index.html @@ -5617,7 +5617,7 @@ diff --git a/webui/static/script.js b/webui/static/script.js index cba22dde..9eaca270 100644 --- a/webui/static/script.js +++ b/webui/static/script.js @@ -49857,6 +49857,7 @@ let _repairCurrentTab = 'jobs'; let _repairFindingsPage = 0; let _repairSelectedFindings = new Set(); const REPAIR_FINDINGS_PAGE_SIZE = 30; +let _repairJobsCache = {}; // Cache job data for help modal /** * Open the Library Maintenance modal @@ -49932,8 +49933,16 @@ async function loadRepairJobs() { const data = await response.json(); const jobs = data.jobs || []; + // Cache job data for help modal + _repairJobsCache = {}; + jobs.forEach(j => { _repairJobsCache[j.job_id] = j; }); + if (jobs.length === 0) { - container.innerHTML = '
${p.replace(/\n/g, '
')}