diff --git a/src/config_loader.py b/src/config_loader.py index dbba4d4..8db4b2c 100644 --- a/src/config_loader.py +++ b/src/config_loader.py @@ -94,40 +94,54 @@ class ConfigLoader: if not self.config.has_section('General'): self.config.add_section('General') - self.config.set('General', 'output_dir', os.path.join(os.path.expanduser("~"), "Music", "iPod")) - self.config.set('General', 'format', 'm4a') - self.config.set('General', 'quality', '256') + if not self.config.has_option('General', 'output_dir'): + self.config.set('General', 'output_dir', os.path.join(os.path.expanduser("~"), "Music", "iPod")) + if not self.config.has_option('General', 'format'): + self.config.set('General', 'format', 'm4a') + if not self.config.has_option('General', 'quality'): + self.config.set('General', 'quality', '256') # Video section if not self.config.has_section('Video'): self.config.add_section('Video') - self.config.set('Video', 'enable_video', 'false') - self.config.set('Video', 'resolution', '640x480') + if not self.config.has_option('Video', 'enable_video'): + self.config.set('Video', 'enable_video', 'false') + if not self.config.has_option('Video', 'resolution'): + self.config.set('Video', 'resolution', '640x480') # Metadata section if not self.config.has_section('Metadata'): self.config.add_section('Metadata') - self.config.set('Metadata', 'embed_artwork', 'true') - self.config.set('Metadata', 'use_musicbrainz', 'false') + if not self.config.has_option('Metadata', 'embed_artwork'): + self.config.set('Metadata', 'embed_artwork', 'true') + if not self.config.has_option('Metadata', 'use_musicbrainz'): + self.config.set('Metadata', 'use_musicbrainz', 'false') # Device section if not self.config.has_section('Device'): self.config.add_section('Device') - self.config.set('Device', 'auto_detect', 'true') - self.config.set('Device', 'mount_point', '') + if not self.config.has_option('Device', 'auto_detect'): + self.config.set('Device', 'auto_detect', 'true') + if not self.config.has_option('Device', 'mount_point'): + self.config.set('Device', 'mount_point', '') # Advanced section if not self.config.has_section('Advanced'): self.config.add_section('Advanced') - self.config.set('Advanced', 'temp_dir', 'temp') - self.config.set('Advanced', 'concurrent_downloads', '2') - self.config.set('Advanced', 'debug', 'false') - self.config.set('Advanced', 'clean_temp', 'true') - self.config.set('Advanced', 'show_download', 'false') + if not self.config.has_option('Advanced', 'temp_dir'): + self.config.set('Advanced', 'temp_dir', 'temp') + if not self.config.has_option('Advanced', 'concurrent_downloads'): + self.config.set('Advanced', 'concurrent_downloads', '2') + if not self.config.has_option('Advanced', 'debug'): + self.config.set('Advanced', 'debug', 'false') + if not self.config.has_option('Advanced', 'clean_temp'): + self.config.set('Advanced', 'clean_temp', 'true') + if not self.config.has_option('Advanced', 'show_download'): + self.config.set('Advanced', 'show_download', 'false') # Hotkeys section if not self.config.has_section('Hotkeys'): @@ -153,7 +167,18 @@ class ConfigLoader: 'delete_selected': 'Delete', } for key, value in hotkey_defaults.items(): - self.config.set('Hotkeys', key, value) + if not self.config.has_option('Hotkeys', key): + self.config.set('Hotkeys', key, value) + + # Playback section + if not self.config.has_section('Playback'): + self.config.add_section('Playback') + if not self.config.has_option('Playback', 'last_track_path'): + self.config.set('Playback', 'last_track_path', '') + if not self.config.has_option('Playback', 'last_position_ms'): + self.config.set('Playback', 'last_position_ms', '0') + if not self.config.has_option('Playback', 'last_volume'): + self.config.set('Playback', 'last_volume', '80') def get(self, section: str, option: str, fallback: Any = None) -> Any: """ diff --git a/src/main.py b/src/main.py index 36e6f63..7f5b855 100644 --- a/src/main.py +++ b/src/main.py @@ -294,12 +294,17 @@ class MainWindow(QMainWindow): self.audio_output: Optional[QAudioOutput] = None self.current_playback_index: int = -1 self._is_playing = False + self._saved_volume: int = 80 + self._saved_track_path: str = "" + self._saved_position_ms: int = 0 + self._pending_seek_ms: int = 0 self._setup_ui() - self._setup_player() self._load_settings() + self._setup_player() self._setup_hotkeys() self._scan_library() + self._resume_playback_if_saved() self._on_refresh_devices_clicked() def _cleanup_worker(self): @@ -345,6 +350,10 @@ class MainWindow(QMainWindow): self.show_download_tab = show_download self.tab_widget.setTabVisible(self.download_tab_index, show_download) + self._saved_volume = config.get_int("Playback", "last_volume", fallback=80) + self._saved_track_path = config.get("Playback", "last_track_path", fallback="") + self._saved_position_ms = config.get_int("Playback", "last_position_ms", fallback=0) + def _save_settings(self): config = self.config_loader config.set("General", "output_dir", self.output_dir_input.text()) @@ -357,6 +366,23 @@ class MainWindow(QMainWindow): config.set("Advanced", "clean_temp", str(self.clean_temp_check.isChecked()).lower()) config.set("Device", "auto_detect", str(self.auto_detect_check.isChecked()).lower()) config.set("Advanced", "show_download", str(self.show_download_check.isChecked()).lower()) + + config.set("Playback", "last_volume", str(self.volume_slider.value())) + if self.current_playback_index >= 0 and self.player.source().isValid(): + track_path = self.player.source().toLocalFile() + if track_path and os.path.exists(track_path): + config.set("Playback", "last_track_path", track_path) + position = self.player.position() + duration = self.player.duration() + if duration > 0 and position > duration - 2000: + position = 0 + config.set("Playback", "last_position_ms", str(position)) + else: + config.set("Playback", "last_track_path", "") + config.set("Playback", "last_position_ms", "0") + else: + config.set("Playback", "last_track_path", "") + config.set("Playback", "last_position_ms", "0") config.save() def _setup_hotkeys(self): @@ -799,14 +825,14 @@ class MainWindow(QMainWindow): """Initialize QMediaPlayer and QAudioOutput for playback.""" self.player = QMediaPlayer() self.audio_output = QAudioOutput() - self.audio_output.setVolume(0.8) + self.audio_output.setVolume(self._saved_volume / 100.0) self.player.setAudioOutput(self.audio_output) self.player.positionChanged.connect(self._on_media_position_changed) self.player.durationChanged.connect(self._on_media_duration_changed) self.player.mediaStatusChanged.connect(self._on_media_status_changed) - def _play_track_from_index(self, index: int): + def _play_track_from_index(self, index: int, start_position_ms: int = 0): """Start playing a track from the library table by row index.""" if index < 0 or index >= self.library_table.rowCount(): return @@ -834,7 +860,6 @@ class MainWindow(QMainWindow): self.cover_label.setPixmap(pixmap) self.cover_label.setToolTip(track_data.get("album", "")) else: - # Fallback: try looking up by artist/album in cover cache artist = track_data.get("artist", "") album = track_data.get("album", "") cached = cover_cache.get(artist, album) if artist else None @@ -846,10 +871,14 @@ class MainWindow(QMainWindow): self.cover_label.clear() self.cover_label.setToolTip("") - # Highlight the current row self.library_table.clearSelection() self.library_table.selectRow(index) + if start_position_ms > 0: + self._pending_seek_ms = start_position_ms + else: + self._pending_seek_ms = 0 + def _on_play_pause_clicked(self): if self.player is None: return @@ -946,6 +975,10 @@ class MainWindow(QMainWindow): elif status == QMediaPlayer.MediaStatus.NoMedia: self.play_btn.setText("\u25B6") self._is_playing = False + elif status == QMediaPlayer.MediaStatus.LoadedMedia: + if self._pending_seek_ms > 0: + self.player.setPosition(self._pending_seek_ms) + self._pending_seek_ms = 0 def _on_table_double_clicked(self, row: int): self._play_track_from_index(row) @@ -1037,6 +1070,18 @@ class MainWindow(QMainWindow): self.library_scanned_sources = sources self._refresh_library_ui() + def _resume_playback_if_saved(self): + if not self._saved_track_path or not os.path.exists(self._saved_track_path): + return + for row in range(self.library_table.rowCount()): + data_item = self.library_table.item(row, 0) + if data_item is None: + continue + _is_ready, track_data = data_item.data(Qt.ItemDataRole.UserRole) + if track_data and track_data.get("path", "") == self._saved_track_path: + self._play_track_from_index(row, start_position_ms=self._saved_position_ms) + return + def _extract_ready_track(self, fpath: str, fname: str, handler) -> Dict[str, Any]: size = os.path.getsize(fpath) cover_path = None