29 KiB
SoulSync - Comprehensive Code Documentation
Table of Contents
- Application Overview
- Application Entry Point
- Core Services
- UI Pages
- UI Components
- Worker Threads & Async Operations
- Data Models & Structures
Application Overview
SoulSync is a sophisticated music management desktop application built with PyQt6 that integrates three major music services:
- Spotify: For playlist access and music metadata
- Plex: For personal music library management
- Soulseek: For music downloading via slskd (headless Soulseek client)
The application enables users to:
- Synchronize Spotify playlists with their Plex library
- Download missing tracks from Soulseek
- Manage and update music metadata
- Monitor download queues and transfer operations
- Search and explore music across all platforms
Application Entry Point
main.py
Classes
ServiceStatusThread(QThread) - main.py:27
- Purpose: Background thread for checking service connection status
- Inputs: service_clients (dict), parent (QObject)
- Outputs: status_updated signal (str service, bool connected, str error)
- Key Methods:
run(): Main thread execution, tests all service connectionstest_spotify(): Tests Spotify API authenticationtest_plex(): Tests Plex server connectivitytest_soulseek(): Tests slskd API connection
MainWindow(QMainWindow) - main.py:63
- Purpose: Primary application window, manages UI pages and service clients
- Inputs: QApplication parent
- Outputs: Main application interface
- Key Methods:
setup_ui(): Initialize main UI layout and componentssetup_service_clients(): Initialize Spotify, Plex, and Soulseek clientsswitch_page(page_name): Navigate between different UI pageson_settings_changed(key, value): Handle configuration updatesshow_about_dialog(): Display application information modal
Functions
main() - main.py:340
- Purpose: Application entry point
- Inputs: Command line arguments
- Outputs: Exit code (int)
- Functionality: Sets up QApplication, initializes MainWindow, starts event loop
Core Services
spotify_client.py
Decorators
@rate_limited - spotify_client.py:23
- Purpose: Rate limiting decorator for Spotify API calls
- Inputs: func (callable)
- Outputs: Rate-limited function wrapper
- Functionality: Implements exponential backoff for API requests
Data Classes
Track - spotify_client.py:51
- Purpose: Spotify track data model
- Attributes:
id(str): Spotify track IDname(str): Track titleartists(List[str]): Artist namesalbum(str): Album nameduration_ms(int): Track duration in millisecondspreview_url(str): 30-second preview URLexternal_urls(dict): External URLs including Spotify URL
Artist - spotify_client.py:75
- Purpose: Spotify artist data model
- Attributes:
id(str): Spotify artist IDname(str): Artist namegenres(List[str]): Associated genrespopularity(int): Popularity score 0-100followers(int): Number of followersimage_url(str): Artist image URLexternal_urls(dict): External URLs
Album - spotify_client.py:102
- Purpose: Spotify album data model
- Attributes:
id(str): Spotify album IDname(str): Album nameartists(List[str]): Artist namesrelease_date(str): Release datetotal_tracks(int): Number of tracksimage_url(str): Album artwork URLexternal_urls(dict): External URLs
Playlist - spotify_client.py:131
- Purpose: Spotify playlist data model
- Attributes:
id(str): Spotify playlist IDname(str): Playlist namedescription(str): Playlist descriptionowner(str): Owner usernametrack_count(int): Number of trackspublic(bool): Public visibility statusimage_url(str): Playlist image URL
Main Client Class
SpotifyClient - spotify_client.py:154
- Purpose: Spotify Web API integration with OAuth2 authentication
- Inputs: None (uses config_manager for credentials)
- Key Methods:
is_authenticated() -> bool: Check authentication statusget_user_info() -> dict: Get current user informationget_user_playlists() -> List[Playlist]: Fetch user's playlistsget_playlist_tracks(playlist_id) -> List[Track]: Get tracks from playlistsearch_tracks(query, limit=50) -> List[Track]: Search for trackssearch_artists(query, limit=50) -> List[Artist]: Search for artistssearch_albums(query, limit=50) -> List[Album]: Search for albumsget_artist_albums(artist_id) -> List[Album]: Get artist's albumsget_album_tracks(album_id) -> List[Track]: Get album tracks
plex_client.py
Data Classes
PlexTrackInfo - plex_client.py:17
- Purpose: Plex track information container
- Attributes:
title(str): Track titleartist(str): Artist namealbum(str): Album nametrack_number(int): Track number in albumduration(int): Duration in millisecondsfile_path(str): File system pathrating_key(str): Plex unique identifieryear(int): Release yeargenre(str): Music genrebitrate(int): Audio bitratefile_size(int): File size in bytes
PlexPlaylistInfo - plex_client.py:52
- Purpose: Plex playlist information container
- Attributes:
title(str): Playlist titletrack_count(int): Number of tracksduration(int): Total durationcreated(datetime): Creation timestampupdated(datetime): Last update timestamprating_key(str): Plex unique identifiertracks(List[PlexTrackInfo]): Playlist tracks
Main Client Class
PlexClient - plex_client.py:76
- Purpose: Plex Media Server API integration for library management
- Inputs: None (uses config_manager for server details)
- Key Methods:
is_connected() -> bool: Test server connectionget_music_library(): Get primary music library sectionget_all_artists() -> List: Get all artists from libraryget_all_albums() -> List: Get all albums from libraryget_all_tracks() -> List[PlexTrackInfo]: Get all tracks from librarysearch_tracks(query) -> List[PlexTrackInfo]: Search library tracksget_playlists() -> List[PlexPlaylistInfo]: Get all playlistscreate_playlist(name, tracks) -> bool: Create new playlistupdate_artist_poster(artist, image_data) -> bool: Update artist artworkupdate_album_poster(album, image_data) -> bool: Update album artworkupdate_artist_genres(artist, genres) -> bool: Update artist genresneeds_update_by_age(artist, days) -> bool: Check if artist needs metadata refreshis_artist_ignored(artist) -> bool: Check if artist is marked to ignore updates
soulseek_client.py
Data Classes
SearchResult - soulseek_client.py:14
- Purpose: Base class for Soulseek search results
- Attributes:
username(str): File owner usernamefilename(str): Original filenamesize(int): File size in bytesbitrate(int): Audio bitratelength(int): Duration in secondsquality_score(float): Calculated quality ratingspeed(int): User connection speedqueue_length(int): User's queue lengthfree_slot(bool): Has free upload slotcountry(str): User's country
TrackResult(SearchResult) - soulseek_client.py:59
- Purpose: Individual track search result from Soulseek
- Additional Attributes:
title(str): Parsed track titleartist(str): Parsed artist namealbum(str): Parsed album nametrack_number(int): Track number if availableyear(int): Release year if availableformat(str): Audio format (MP3, FLAC, etc.)
AlbumResult - soulseek_client.py:130
- Purpose: Album collection search result
- Attributes:
artist(str): Album artistalbum(str): Album titleyear(int): Release yeartracks(List[TrackResult]): Album trackstotal_size(int): Total album sizeavg_bitrate(float): Average bitrateformats(Set[str]): Available audio formatscompleteness_score(float): How complete the album is
DownloadStatus - soulseek_client.py:190
- Purpose: Download progress tracking
- Attributes:
state(str): Current download statebytes_downloaded(int): Downloaded bytesbytes_total(int): Total file sizespeed(float): Current download speedtime_remaining(int): Estimated time remainingerror(str): Error message if failed
Main Client Class
SoulseekClient - soulseek_client.py:201
- Purpose: slskd HTTP API integration for Soulseek operations
- Inputs: None (uses config_manager for slskd connection)
- Key Methods:
check_connection() -> bool: Test slskd API connectionsearch_tracks(query, timeout=30) -> List[TrackResult]: Search for individual trackssearch_albums(artist, album, timeout=30) -> List[AlbumResult]: Search for complete albumsdownload_track(track_result) -> str: Initiate track downloaddownload_album(album_result) -> List[str]: Download entire albumget_download_status(download_id) -> DownloadStatus: Check download progressget_active_downloads() -> List[dict]: Get all active downloadsget_completed_downloads() -> List[dict]: Get completed downloadscancel_download(download_id) -> bool: Cancel active downloadbrowse_user(username) -> dict: Browse user's shared files
matching_engine.py
Data Classes
MatchResult - matching_engine.py:15
- Purpose: Music matching result with confidence score
- Attributes:
score(float): Matching confidence (0.0-1.0)matched_item(Any): The matched objectmatch_type(str): Type of match founddetails(dict): Additional matching information
Main Engine Class
MusicMatchingEngine - matching_engine.py:25
- Purpose: Fuzzy matching algorithms for cross-platform music identification
- Key Methods:
normalize_string(text) -> str: Clean and normalize text for matchingsimilarity_score(str1, str2) -> float: Calculate similarity between stringsmatch_track(spotify_track, soulseek_results) -> MatchResult: Find best track matchmatch_album(spotify_album, soulseek_results) -> MatchResult: Find best album matchmatch_artist(spotify_artist, plex_artists) -> MatchResult: Find best artist matchextract_year(text) -> int: Extract year from textextract_bitrate(filename) -> int: Extract bitrate from filenamecalculate_quality_score(track_result) -> float: Calculate overall quality rating
UI Pages
Dashboard Page (ui/pages/dashboard.py)
Worker Classes
MetadataUpdateWorker(QThread) - dashboard.py:24
- Purpose: Background thread for updating Plex artist metadata from Spotify
- Inputs: artists (List), plex_client, spotify_client, refresh_interval_days (int)
- Signals: progress_updated, artist_updated, finished, error, artists_loaded
- Key Methods:
run(): Main processing loop for all artistsupdate_artist_metadata(artist) -> (bool, str): Update single artistupdate_artist_photo(artist, spotify_artist) -> bool: Update artist photoupdate_artist_genres(artist, spotify_artist) -> bool: Update genresupdate_album_artwork(artist, spotify_artist) -> int: Update album art
Data Provider Classes
DashboardDataProvider(QObject) - dashboard.py:521
- Purpose: Central data management for dashboard real-time updates
- Signals: service_status_updated, download_stats_updated, metadata_progress_updated
- Key Methods:
set_service_clients(spotify, plex, soulseek): Connect to service clientsupdate_service_status(service, connected, response_time, error): Update service statusupdate_download_stats(): Refresh download statisticstest_service_connection(service): Test individual serviceget_uptime_string() -> str: Format application uptimeget_memory_usage() -> str: Get current memory usage
UI Component Classes
StatCard(QFrame) - dashboard.py:768
- Purpose: Display system statistics with click interaction
- Inputs: title (str), value (str), subtitle (str), clickable (bool)
- Key Methods:
update_values(value, subtitle): Update displayed valuesmousePressEvent(event): Handle click events
ServiceStatusCard(QFrame) - dashboard.py:826
- Purpose: Display individual service connection status
- Inputs: service_name (str)
- Key Methods:
update_status(connected, response_time, error): Update status display
MetadataUpdaterWidget(QFrame) - dashboard.py:920
- Purpose: Control panel for Plex metadata update operations
- Key Methods:
update_progress(is_running, current_artist, processed, total, percentage): Update progress displayget_refresh_interval_days() -> int: Get selected refresh interval
ActivityItem(QWidget) - dashboard.py:1131
- Purpose: Individual activity feed item display
- Inputs: icon (str), title (str), subtitle (str), time (str)
Main Page Class
DashboardPage(QWidget) - dashboard.py:1182
- Purpose: Main dashboard interface with system monitoring
- Key Methods:
set_service_clients(spotify, plex, soulseek): Connect service clientsset_page_references(downloads_page, sync_page): Link to other pagestest_service_connection(service): Initiate service teststart_metadata_update(): Begin metadata update processadd_activity_item(icon, title, subtitle, time): Add activity feed item
Sync Page (ui/pages/sync.py)
Utility Functions
load_sync_status() -> dict - sync.py:58
- Purpose: Load playlist sync status from persistent storage
- Returns: Dictionary of playlist sync states
save_sync_status(data: dict) - sync.py:74
- Purpose: Save playlist sync status to persistent storage
- Inputs: data (dict) - sync status data
clean_track_name_for_search(track_name: str) -> str - sync.py:83
- Purpose: Clean track names for better search results
- Inputs: track_name (str)
- Returns: Cleaned track name string
Worker Classes
PlaylistTrackAnalysisWorker(QRunnable) - sync.py:123
- Purpose: Analyze playlist tracks against Plex library
- Inputs: playlist_tracks, plex_client, matching_engine
- Signals: analysis_complete, progress_update
- Key Methods:
run(): Analyze all tracks in playlistanalyze_track(track) -> TrackAnalysisResult: Analyze individual track
TrackDownloadWorker(QRunnable) - sync.py:274
- Purpose: Download missing tracks from Soulseek
- Inputs: missing_tracks, soulseek_client, download_path
- Signals: download_complete, progress_update, error_occurred
SyncWorker(QRunnable) - sync.py:528
- Purpose: Complete playlist synchronization workflow
- Inputs: playlist, clients, options
- Signals: sync_complete, progress_update, track_found, track_downloaded
UI Component Classes
PlaylistDetailsModal(QDialog) - sync.py:608
- Purpose: Detailed view of playlist sync status and options
- Inputs: playlist_data, parent
- Key Methods:
setup_ui(): Initialize modal interfaceload_track_analysis(): Display track analysis resultshandle_download_missing(): Initiate missing track downloads
PlaylistItem(QFrame) - sync.py:1496
- Purpose: Individual playlist display widget
- Inputs: playlist_data, parent
- Key Methods:
update_sync_status(status): Update visual sync statusset_progress(percentage): Update sync progress barhandle_sync_click(): Handle sync button interaction
SyncOptionsPanel(QFrame) - sync.py:1863
- Purpose: Global sync options and controls
- Key Methods:
get_sync_options() -> dict: Get current sync configurationset_bulk_sync_enabled(enabled): Enable/disable bulk operations
Main Page Class
SyncPage(QWidget) - sync.py:1941
- Purpose: Playlist synchronization management interface
- Key Methods:
load_playlists(): Load Spotify playlistssync_playlist(playlist_id): Sync individual playlistsync_all_playlists(): Bulk sync all playlistsupdate_playlist_status(playlist_id, status): Update sync statusshow_playlist_details(playlist): Open playlist detail modal
Downloads Page (ui/pages/downloads.py)
Data Classes
ArtistMatch - downloads.py:23
- Purpose: Artist search result matching data
- Attributes: name (str), id (str), image_url (str), popularity (int)
AlbumMatch - downloads.py:30
- Purpose: Album search result matching data
- Attributes: name (str), artist (str), year (int), image_url (str), track_count (int)
Worker Classes
DownloadCompletionWorker(QRunnable) - downloads.py:78
- Purpose: Handle post-download file processing and organization
- Inputs: download_data, transfer_path, completion_callback
- Signals: completion_finished, progress_update, error_occurred
StatusProcessingWorker(QRunnable) - downloads.py:117
- Purpose: Process download status updates from slskd API
- Inputs: download_items, soulseek_client
- Signals: status_updated, downloads_completed
SearchThread(QThread) - downloads.py:1713
- Purpose: Perform Soulseek searches in background
- Inputs: query, search_type, soulseek_client
- Signals: search_complete, search_error, progress_update
- Key Methods:
run(): Execute search operationsearch_tracks(query) -> List[TrackResult]: Search for trackssearch_albums(query) -> List[AlbumResult]: Search for albums
UI Component Classes
TrackItem(QFrame) - downloads.py:2200
- Purpose: Individual track display in search results
- Inputs: track_data, parent
- Key Methods:
update_download_status(status): Update download progresshandle_download_click(): Initiate track downloadshow_track_details(): Display detailed track information
AlbumResultItem(QFrame) - downloads.py:2451
- Purpose: Album search result display widget
- Inputs: album_data, parent
- Key Methods:
update_track_list(): Refresh album track listinghandle_album_download(): Download entire albumshow_missing_tracks(): Highlight missing tracks
SearchResultItem(QFrame) - downloads.py:2747
- Purpose: Generic search result display
- Inputs: result_data, result_type, parent
- Key Methods:
update_result_info(): Update displayed informationhandle_action_click(): Process user interaction
DownloadItem(QFrame) - downloads.py:3446
- Purpose: Active download progress display
- Inputs: download_data, parent
- Key Methods:
update_progress(bytes_downloaded, bytes_total): Update progress barupdate_speed(speed): Update download speed displayhandle_cancel(): Cancel download operation
DownloadQueue(QFrame) - downloads.py:4334
- Purpose: Download queue management widget
- Key Methods:
add_download(download_item): Add new download to queueremove_download(download_id): Remove completed/cancelled downloadupdate_queue_status(): Refresh queue displayclear_completed(): Remove completed downloads
Main Page Class
DownloadsPage(QWidget) - downloads.py:4817
- Purpose: Music search and download management interface
- Key Methods:
perform_search(query, search_type): Execute music searchdownload_track(track_result): Initiate track downloaddownload_album(album_result): Download complete albumupdate_download_progress(): Refresh all download progressmanage_download_queue(): Handle queue operations
Artists Page (ui/pages/artists.py)
Worker Classes
ArtistSearchWorker(QThread) - artists.py:106
- Purpose: Search for artists across Spotify and Soulseek
- Inputs: query, spotify_client, soulseek_client
- Signals: search_complete, artist_found, search_error
AlbumFetchWorker(QThread) - artists.py:142
- Purpose: Fetch artist's albums from Spotify
- Inputs: artist_id, spotify_client
- Signals: albums_loaded, fetch_error
AlbumSearchWorker(QThread) - artists.py:187
- Purpose: Search for specific albums on Soulseek
- Inputs: album_query, soulseek_client
- Signals: album_results, search_complete
PlexLibraryWorker(QThread) - artists.py:424
- Purpose: Load and analyze Plex music library
- Inputs: plex_client
- Signals: library_loaded, artist_processed, loading_progress
UI Component Classes
ArtistResultCard(QFrame) - artists.py:839
- Purpose: Artist search result display card
- Inputs: artist_data, parent
- Key Methods:
load_artist_image(): Load artist artworkshow_artist_albums(): Display artist's discographyhandle_follow_artist(): Add artist to favorites
AlbumCard(QFrame) - artists.py:978
- Purpose: Album display card with download options
- Inputs: album_data, parent
- Key Methods:
update_download_status(): Show download progresshandle_album_download(): Initiate album downloadshow_track_list(): Display album tracks
Main Page Class
ArtistsPage(QWidget) - artists.py:2345
- Purpose: Artist and album exploration interface
- Key Methods:
search_artists(query): Search for artistsload_artist_albums(artist_id): Load artist's discographybrowse_plex_library(): Explore local Plex librarydownload_artist_discography(artist_id): Download all artist albums
Settings Page (ui/pages/settings.py)
Worker Classes
SlskdDetectionThread(QThread) - settings.py:8
- Purpose: Auto-detect slskd instances on local network
- Signals: progress_updated, detection_completed
- Key Methods:
run(): Scan local machine and network for slskdtest_url_enhanced(url) -> (str, str): Test URL for slskd APIparallel_scan(targets) -> str: Scan multiple targets in parallel
ServiceTestThread(QThread) - settings.py:255
- Purpose: Test service connections in background
- Inputs: service_type, test_config
- Signals: test_completed
- Key Methods:
_test_spotify() -> (bool, str): Test Spotify API connection_test_plex() -> (bool, str): Test Plex server connection_test_soulseek() -> (bool, str): Test slskd API connection
UI Component Classes
SettingsGroup(QGroupBox) - settings.py:417
- Purpose: Styled settings section container
- Inputs: title (str), parent
Main Page Class
SettingsPage(QWidget) - settings.py:438
- Purpose: Application configuration interface
- Key Methods:
load_config_values(): Load current settings from configsave_settings(): Save form values to configurationtest_spotify_connection(): Test Spotify API setuptest_plex_connection(): Test Plex server setuptest_soulseek_connection(): Test slskd connectionauto_detect_slskd(): Auto-discover slskd instancesbrowse_download_path(): Select download directorybrowse_transfer_path(): Select transfer directory
UI Components
Sidebar (ui/sidebar.py)
Component Classes
ScrollingLabel(QLabel) - sidebar.py:6
- Purpose: Auto-scrolling text label for long track names
- Inputs: text (str), parent
- Key Methods:
start_scrolling(): Begin text animationstop_scrolling(): Stop text animationupdate_text(text): Change displayed text
SidebarButton(QPushButton) - sidebar.py:132
- Purpose: Custom styled navigation button
- Inputs: text (str), icon (str), parent
- Key Methods:
set_active(active): Set active/inactive stateupdate_notification_count(count): Show notification badge
StatusIndicator(QWidget) - sidebar.py:358
- Purpose: Service connection status display
- Inputs: service_name (str), parent
- Key Methods:
set_status(connected, response_time): Update connection statusstart_pulse_animation(): Animate connection testing
MediaPlayer(QWidget) - sidebar.py:601
- Purpose: Mini media player for track previews
- Key Methods:
play_track(track_url): Start track playbackpause_playback(): Pause current trackstop_playback(): Stop and reset playerset_volume(volume): Adjust playback volumeupdate_progress(): Update playback progress bar
Main Sidebar Class
ModernSidebar(QWidget) - sidebar.py:976
- Purpose: Main navigation sidebar with media player and status
- Key Methods:
setup_navigation(): Initialize navigation buttonssetup_status_section(): Create service status indicatorssetup_media_player(): Initialize media player componentupdate_service_status(service, status): Update service indicatorsset_active_page(page_name): Highlight active navigation button
Toast Manager (ui/components/toast_manager.py)
Enums
ToastType(Enum) - toast_manager.py:8
- Values: SUCCESS, ERROR, WARNING, INFO
- Purpose: Define toast notification types
Component Classes
Toast(QWidget) - toast_manager.py:14
- Purpose: Individual toast notification widget
- Inputs: message (str), toast_type (ToastType), duration (int), parent
- Key Methods:
show_toast(): Display toast with animationhide_toast(): Hide toast with fade animationauto_close(): Automatically close after duration
Main Manager Class
ToastManager(QWidget) - toast_manager.py:166
- Purpose: Manages toast notification queue and display
- Key Methods:
success(message, duration=3000): Show success notificationerror(message, duration=5000): Show error notificationwarning(message, duration=4000): Show warning notificationinfo(message, duration=3000): Show info notificationclear_all(): Remove all active toasts
Worker Threads & Async Operations
Threading Architecture
The application extensively uses PyQt6's threading system for non-blocking operations:
QThread Classes
- Purpose: Long-running background operations
- Examples: MetadataUpdateWorker, PlaylistLoaderThread, SearchThread
- Lifecycle: start() → run() → finished signal → deleteLater()
QRunnable Classes
- Purpose: Short-lived parallel tasks
- Examples: TrackAnalysisWorker, DownloadCompletionWorker, StatusProcessingWorker
- Managed by: QThreadPool for automatic thread management
Signal-Slot Communication
- Worker Signals: progress_updated, task_complete, error_occurred
- UI Slots: update_progress_bar, show_results, handle_error
- Thread Safety: All UI updates via queued signal connections
Common Threading Patterns
Progress Reporting:
# Worker emits progress
self.progress_updated.emit(current, total, percentage)
# UI receives and updates
def on_progress_updated(self, current, total, percentage):
self.progress_bar.setValue(percentage)
Error Handling:
# Worker catches exceptions
try:
result = self.perform_operation()
self.task_complete.emit(result)
except Exception as e:
self.error_occurred.emit(str(e))
Resource Cleanup:
# Proper thread cleanup
def cleanup_thread(self):
if self.worker_thread.isRunning():
self.worker_thread.quit()
self.worker_thread.wait(3000)
self.worker_thread.deleteLater()
Data Models & Structures
Configuration Management
- File:
config/settings.py(config_manager) - Purpose: Centralized application configuration
- Sections: Spotify API, Plex server, Soulseek/slskd, logging, paths
Data Persistence
- Sync Status: JSON files for playlist sync state
- Download History: Track completed downloads and transfers
- User Preferences: UI settings and user choices
Cross-Platform Data Flow
Spotify → Plex Sync:
- Fetch Spotify playlist tracks
- Search Plex library for matches
- Identify missing tracks
- Queue missing tracks for download
Soulseek → Plex Transfer:
- Download tracks to temporary directory
- Process and organize files
- Move to Plex library structure
- Trigger Plex library scan
Metadata Enhancement:
- Load Plex artists needing updates
- Search Spotify for matching artists
- Download high-quality artwork
- Update genres and biography
- Refresh Plex metadata
Quality Scoring Algorithm
- File Format: FLAC > 320kbps MP3 > lower quality
- Source Reliability: User reputation and connection speed
- Completeness: Album completeness for multi-track downloads
- Metadata Accuracy: Filename parsing and tag completeness
This documentation provides a comprehensive overview of the SoulSync codebase, covering all major components, their interactions, and data flows. Each section details the purpose, inputs, outputs, and key functionality of the classes and functions that make up this sophisticated music management application.