1 | import os |
||
2 | import sys |
||
3 | |||
4 | import natsort |
||
5 | |||
6 | from PyQt5.QtCore import Qt, QFileInfo, QTime, QTimer, QUrl |
||
7 | from PyQt5.QtGui import QIcon, QPixmap |
||
8 | from PyQt5.QtMultimedia import QMediaContent, QMediaPlayer, QMediaPlaylist |
||
9 | from PyQt5.QtWidgets import (QAction, QApplication, QDesktopWidget, QDockWidget, QFileDialog, |
||
10 | QLabel, QListWidget, QListWidgetItem, QMainWindow, QSizePolicy, |
||
11 | QSlider, QToolBar, QVBoxLayout, QWidget) |
||
12 | |||
13 | from mosaic import about, configuration, defaults, information, library, metadata, utilities |
||
14 | |||
15 | |||
16 | class MusicPlayer(QMainWindow): |
||
17 | """MusicPlayer houses all of elements that directly interact with the main window.""" |
||
18 | |||
19 | def __init__(self, parent=None): |
||
20 | """Initialize the QMainWindow widget. |
||
21 | |||
22 | The window title, window icon, and window size are initialized here as well |
||
23 | as the following widgets: QMediaPlayer, QMediaPlaylist, QMediaContent, QMenuBar, |
||
24 | QToolBar, QLabel, QPixmap, QSlider, QDockWidget, QListWidget, QWidget, and |
||
25 | QVBoxLayout. The connect signals for relavant widgets are also initialized. |
||
26 | """ |
||
27 | super(MusicPlayer, self).__init__(parent) |
||
28 | self.setWindowTitle('Mosaic') |
||
29 | |||
30 | window_icon = utilities.resource_filename('mosaic.images', 'icon.png') |
||
31 | self.setWindowIcon(QIcon(window_icon)) |
||
32 | self.resize(defaults.Settings().window_size, defaults.Settings().window_size + 63) |
||
33 | |||
34 | # Initiates Qt objects to be used by MusicPlayer |
||
35 | self.player = QMediaPlayer() |
||
36 | self.playlist = QMediaPlaylist() |
||
37 | self.playlist_location = defaults.Settings().playlist_path |
||
38 | self.content = QMediaContent() |
||
39 | self.menu = self.menuBar() |
||
40 | self.toolbar = QToolBar() |
||
41 | self.art = QLabel() |
||
42 | self.pixmap = QPixmap() |
||
43 | self.slider = QSlider(Qt.Horizontal) |
||
44 | self.duration_label = QLabel() |
||
45 | self.playlist_dock = QDockWidget('Playlist', self) |
||
46 | self.library_dock = QDockWidget('Media Library', self) |
||
47 | self.playlist_view = QListWidget() |
||
48 | self.library_view = library.MediaLibraryView() |
||
49 | self.library_model = library.MediaLibraryModel() |
||
50 | self.preferences = configuration.PreferencesDialog() |
||
51 | self.widget = QWidget() |
||
52 | self.layout = QVBoxLayout(self.widget) |
||
53 | self.duration = 0 |
||
54 | self.playlist_dock_state = None |
||
55 | self.library_dock_state = None |
||
56 | |||
57 | # Sets QWidget() as the central widget of the main window |
||
58 | self.setCentralWidget(self.widget) |
||
59 | self.layout.setContentsMargins(0, 0, 0, 0) |
||
60 | self.art.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) |
||
61 | |||
62 | # Initiates the playlist dock widget and the library dock widget |
||
63 | self.addDockWidget(defaults.Settings().dock_position, self.playlist_dock) |
||
64 | self.playlist_dock.setWidget(self.playlist_view) |
||
65 | self.playlist_dock.setVisible(defaults.Settings().playlist_on_start) |
||
66 | self.playlist_dock.setFeatures(QDockWidget.DockWidgetClosable) |
||
67 | |||
68 | self.addDockWidget(defaults.Settings().dock_position, self.library_dock) |
||
69 | self.library_dock.setWidget(self.library_view) |
||
70 | self.library_dock.setVisible(defaults.Settings().media_library_on_start) |
||
71 | self.library_dock.setFeatures(QDockWidget.DockWidgetClosable) |
||
72 | self.tabifyDockWidget(self.playlist_dock, self.library_dock) |
||
73 | |||
74 | # Sets the range of the playback slider and sets the playback mode as looping |
||
75 | self.slider.setRange(0, self.player.duration() / 1000) |
||
76 | self.playlist.setPlaybackMode(QMediaPlaylist.Sequential) |
||
77 | |||
78 | # OSX system menu bar causes conflicts with PyQt5 menu bar |
||
79 | if sys.platform == 'darwin': |
||
80 | self.menu.setNativeMenuBar(False) |
||
81 | |||
82 | # Initiates Settings in the defaults module to give access to settings.toml |
||
83 | defaults.Settings() |
||
84 | |||
85 | # Signals that connect to other methods when they're called |
||
86 | self.player.metaDataChanged.connect(self.display_meta_data) |
||
87 | self.slider.sliderMoved.connect(self.seek) |
||
88 | self.player.durationChanged.connect(self.song_duration) |
||
89 | self.player.positionChanged.connect(self.song_position) |
||
90 | self.player.stateChanged.connect(self.set_state) |
||
91 | self.playlist_view.itemActivated.connect(self.activate_playlist_item) |
||
92 | self.library_view.activated.connect(self.open_media_library) |
||
93 | self.playlist.currentIndexChanged.connect(self.change_index) |
||
94 | self.playlist.mediaInserted.connect(self.initialize_playlist) |
||
95 | self.playlist_dock.visibilityChanged.connect(self.dock_visiblity_change) |
||
96 | self.library_dock.visibilityChanged.connect(self.dock_visiblity_change) |
||
97 | self.preferences.dialog_media_library.media_library_line.textChanged.connect(self.change_media_library_path) |
||
98 | self.preferences.dialog_view_options.dropdown_box.currentIndexChanged.connect(self.change_window_size) |
||
99 | self.art.mousePressEvent = self.press_playback |
||
100 | |||
101 | # Creating the menu controls, media controls, and window size of the music player |
||
102 | self.menu_controls() |
||
103 | self.media_controls() |
||
104 | self.load_saved_playlist() |
||
105 | |||
106 | def menu_controls(self): |
||
107 | """Initiate the menu bar and add it to the QMainWindow widget.""" |
||
108 | self.file = self.menu.addMenu('File') |
||
109 | self.edit = self.menu.addMenu('Edit') |
||
110 | self.playback = self.menu.addMenu('Playback') |
||
111 | self.view = self.menu.addMenu('View') |
||
112 | self.help_ = self.menu.addMenu('Help') |
||
113 | |||
114 | self.file_menu() |
||
115 | self.edit_menu() |
||
116 | self.playback_menu() |
||
117 | self.view_menu() |
||
118 | self.help_menu() |
||
119 | |||
120 | def media_controls(self): |
||
121 | """Create the bottom toolbar and controls used for media playback.""" |
||
122 | self.addToolBar(Qt.BottomToolBarArea, self.toolbar) |
||
123 | self.toolbar.setMovable(False) |
||
124 | |||
125 | play_icon = utilities.resource_filename('mosaic.images', 'md_play.png') |
||
126 | self.play_action = QAction(QIcon(play_icon), 'Play', self) |
||
127 | self.play_action.triggered.connect(self.player.play) |
||
128 | |||
129 | stop_icon = utilities.resource_filename('mosaic.images', 'md_stop.png') |
||
130 | self.stop_action = QAction(QIcon(stop_icon), 'Stop', self) |
||
131 | self.stop_action.triggered.connect(self.player.stop) |
||
132 | |||
133 | previous_icon = utilities.resource_filename('mosaic.images', 'md_previous.png') |
||
134 | self.previous_action = QAction(QIcon(previous_icon), 'Previous', self) |
||
135 | self.previous_action.triggered.connect(self.previous) |
||
136 | |||
137 | next_icon = utilities.resource_filename('mosaic.images', 'md_next.png') |
||
138 | self.next_action = QAction(QIcon(next_icon), 'Next', self) |
||
139 | self.next_action.triggered.connect(self.playlist.next) |
||
140 | |||
141 | repeat_icon = utilities.resource_filename('mosaic.images', 'md_repeat_none.png') |
||
142 | self.repeat_action = QAction(QIcon(repeat_icon), 'Repeat', self) |
||
143 | self.repeat_action.triggered.connect(self.repeat_song) |
||
144 | |||
145 | self.toolbar.addAction(self.play_action) |
||
146 | self.toolbar.addAction(self.stop_action) |
||
147 | self.toolbar.addAction(self.previous_action) |
||
148 | self.toolbar.addAction(self.next_action) |
||
149 | self.toolbar.addAction(self.repeat_action) |
||
150 | self.toolbar.addWidget(self.slider) |
||
151 | self.toolbar.addWidget(self.duration_label) |
||
152 | |||
153 | def file_menu(self): |
||
154 | """Add a file menu to the menu bar. |
||
155 | |||
156 | The file menu houses the Open File, Open Multiple Files, Open Playlist, |
||
157 | Open Directory, and Exit Application menu items. |
||
158 | """ |
||
159 | self.open_action = QAction('Open File', self) |
||
160 | self.open_action.setShortcut('O') |
||
161 | self.open_action.triggered.connect(self.open_file) |
||
162 | |||
163 | self.open_multiple_files_action = QAction('Open Multiple Files', self) |
||
164 | self.open_multiple_files_action.setShortcut('M') |
||
165 | self.open_multiple_files_action.triggered.connect(self.open_multiple_files) |
||
166 | |||
167 | self.open_playlist_action = QAction('Open Playlist', self) |
||
168 | self.open_playlist_action.setShortcut('CTRL+P') |
||
169 | self.open_playlist_action.triggered.connect(self.open_playlist) |
||
170 | |||
171 | self.open_directory_action = QAction('Open Directory', self) |
||
172 | self.open_directory_action.setShortcut('D') |
||
173 | self.open_directory_action.triggered.connect(self.open_directory) |
||
174 | |||
175 | self.save_playlist_action = QAction('Save Playlist', self) |
||
176 | self.save_playlist_action.setShortcut('CTRL+S') |
||
177 | self.save_playlist_action.triggered.connect(self.save_playlist) |
||
178 | |||
179 | self.exit_action = QAction('Quit', self) |
||
180 | self.exit_action.setShortcut('CTRL+Q') |
||
181 | self.exit_action.triggered.connect(self.closeEvent) |
||
182 | |||
183 | self.file.addAction(self.open_action) |
||
184 | self.file.addAction(self.open_multiple_files_action) |
||
185 | self.file.addAction(self.open_playlist_action) |
||
186 | self.file.addAction(self.open_directory_action) |
||
187 | self.file.addSeparator() |
||
188 | self.file.addAction(self.save_playlist_action) |
||
189 | self.file.addSeparator() |
||
190 | self.file.addAction(self.exit_action) |
||
191 | |||
192 | def edit_menu(self): |
||
193 | """Add an edit menu to the menu bar. |
||
194 | |||
195 | The edit menu houses the preferences item that opens a preferences dialog |
||
196 | that allows the user to customize features of the music player. |
||
197 | """ |
||
198 | self.preferences_action = QAction('Preferences', self) |
||
199 | self.preferences_action.setShortcut('CTRL+SHIFT+P') |
||
200 | self.preferences_action.triggered.connect(lambda: self.preferences.exec_()) |
||
201 | |||
202 | self.edit.addAction(self.preferences_action) |
||
203 | |||
204 | def playback_menu(self): |
||
205 | """Add a playback menu to the menu bar. |
||
206 | |||
207 | The playback menu houses |
||
208 | """ |
||
209 | self.play_playback_action = QAction('Play', self) |
||
210 | self.play_playback_action.setShortcut('P') |
||
211 | self.play_playback_action.triggered.connect(self.player.play) |
||
212 | |||
213 | self.stop_playback_action = QAction('Stop', self) |
||
214 | self.stop_playback_action.setShortcut('S') |
||
215 | self.stop_playback_action.triggered.connect(self.player.stop) |
||
216 | |||
217 | self.previous_playback_action = QAction('Previous', self) |
||
218 | self.previous_playback_action.setShortcut('B') |
||
219 | self.previous_playback_action.triggered.connect(self.previous) |
||
220 | |||
221 | self.next_playback_action = QAction('Next', self) |
||
222 | self.next_playback_action.setShortcut('N') |
||
223 | self.next_playback_action.triggered.connect(self.playlist.next) |
||
224 | |||
225 | self.playback.addAction(self.play_playback_action) |
||
226 | self.playback.addAction(self.stop_playback_action) |
||
227 | self.playback.addAction(self.previous_playback_action) |
||
228 | self.playback.addAction(self.next_playback_action) |
||
229 | |||
230 | def view_menu(self): |
||
231 | """Add a view menu to the menu bar. |
||
232 | |||
233 | The view menu houses the Playlist, Media Library, Minimalist View, and Media |
||
234 | Information menu items. The Playlist item toggles the playlist dock into and |
||
235 | out of view. The Media Library items toggles the media library dock into and |
||
236 | out of view. The Minimalist View item resizes the window and shows only the |
||
237 | menu bar and player controls. The Media Information item opens a dialog that |
||
238 | shows information relevant to the currently playing song. |
||
239 | """ |
||
240 | self.dock_action = self.playlist_dock.toggleViewAction() |
||
241 | self.dock_action.setShortcut('CTRL+ALT+P') |
||
242 | |||
243 | self.library_dock_action = self.library_dock.toggleViewAction() |
||
244 | self.library_dock_action.setShortcut('CTRL+ALT+L') |
||
245 | |||
246 | self.minimalist_view_action = QAction('Minimalist View', self) |
||
247 | self.minimalist_view_action.setShortcut('CTRL+ALT+M') |
||
248 | self.minimalist_view_action.setCheckable(True) |
||
249 | self.minimalist_view_action.triggered.connect(self.minimalist_view) |
||
250 | |||
251 | self.view_media_info_action = QAction('Media Information', self) |
||
252 | self.view_media_info_action.setShortcut('CTRL+SHIFT+M') |
||
253 | self.view_media_info_action.triggered.connect(self.media_information_dialog) |
||
254 | |||
255 | self.view.addAction(self.dock_action) |
||
256 | self.view.addAction(self.library_dock_action) |
||
257 | self.view.addSeparator() |
||
258 | self.view.addAction(self.minimalist_view_action) |
||
259 | self.view.addSeparator() |
||
260 | self.view.addAction(self.view_media_info_action) |
||
261 | |||
262 | def help_menu(self): |
||
263 | """Add a help menu to the menu bar. |
||
264 | |||
265 | The help menu houses the about dialog that shows the user information |
||
266 | related to the application. |
||
267 | """ |
||
268 | self.about_action = QAction('About', self) |
||
269 | self.about_action.setShortcut('H') |
||
270 | self.about_action.triggered.connect(lambda: about.AboutDialog().exec_()) |
||
271 | |||
272 | self.help_.addAction(self.about_action) |
||
273 | |||
274 | View Code Duplication | def open_file(self): |
|
0 ignored issues
–
show
Duplication
introduced
by
![]() |
|||
275 | """Open the selected file and add it to a new playlist.""" |
||
276 | filename, success = QFileDialog.getOpenFileName(self, 'Open File', '', 'Audio (*.mp3 *.flac)', '', QFileDialog.ReadOnly) |
||
277 | |||
278 | if success: |
||
279 | file_info = QFileInfo(filename).fileName() |
||
280 | playlist_item = QListWidgetItem(file_info) |
||
281 | self.playlist.clear() |
||
282 | self.playlist_view.clear() |
||
283 | self.playlist.addMedia(QMediaContent(QUrl().fromLocalFile(filename))) |
||
284 | self.player.setPlaylist(self.playlist) |
||
285 | playlist_item.setToolTip(file_info) |
||
286 | self.playlist_view.addItem(playlist_item) |
||
287 | self.playlist_view.setCurrentRow(0) |
||
288 | self.player.play() |
||
289 | |||
290 | View Code Duplication | def open_multiple_files(self): |
|
0 ignored issues
–
show
|
|||
291 | """Open the selected files and add them to a new playlist.""" |
||
292 | filenames, success = QFileDialog.getOpenFileNames(self, 'Open Multiple Files', '', 'Audio (*.mp3 *.flac)', '', QFileDialog.ReadOnly) |
||
293 | |||
294 | if success: |
||
295 | self.playlist.clear() |
||
296 | self.playlist_view.clear() |
||
297 | for file in natsort.natsorted(filenames, alg=natsort.ns.PATH): |
||
298 | file_info = QFileInfo(file).fileName() |
||
299 | playlist_item = QListWidgetItem(file_info) |
||
300 | self.playlist.addMedia(QMediaContent(QUrl().fromLocalFile(file))) |
||
301 | self.player.setPlaylist(self.playlist) |
||
302 | playlist_item.setToolTip(file_info) |
||
303 | self.playlist_view.addItem(playlist_item) |
||
304 | self.playlist_view.setCurrentRow(0) |
||
305 | self.player.play() |
||
306 | |||
307 | View Code Duplication | def open_playlist(self): |
|
0 ignored issues
–
show
|
|||
308 | """Load an M3U or PLS file into a new playlist.""" |
||
309 | playlist, success = QFileDialog.getOpenFileName(self, 'Open Playlist', '', 'Playlist (*.m3u *.pls)', '', QFileDialog.ReadOnly) |
||
310 | |||
311 | if success: |
||
312 | playlist = QUrl.fromLocalFile(playlist) |
||
313 | self.playlist.clear() |
||
314 | self.playlist_view.clear() |
||
315 | self.playlist.load(playlist) |
||
316 | self.player.setPlaylist(self.playlist) |
||
317 | |||
318 | for song_index in range(self.playlist.mediaCount()): |
||
319 | file_info = self.playlist.media(song_index).canonicalUrl().fileName() |
||
320 | playlist_item = QListWidgetItem(file_info) |
||
321 | playlist_item.setToolTip(file_info) |
||
322 | self.playlist_view.addItem(playlist_item) |
||
323 | |||
324 | self.playlist_view.setCurrentRow(0) |
||
325 | self.player.play() |
||
326 | |||
327 | def save_playlist(self): |
||
328 | """Save the media in the playlist dock as a new M3U playlist.""" |
||
329 | playlist, success = QFileDialog.getSaveFileName(self, 'Save Playlist', '', 'Playlist (*.m3u)', '') |
||
330 | if success: |
||
331 | saved_playlist = "{}.m3u" .format(playlist) |
||
332 | self.playlist.save(QUrl().fromLocalFile(saved_playlist), "m3u") |
||
333 | |||
334 | def load_saved_playlist(self): |
||
335 | """Load the saved playlist if user setting permits.""" |
||
336 | saved_playlist = "{}/.m3u" .format(self.playlist_location) |
||
337 | if os.path.exists(saved_playlist): |
||
338 | playlist = QUrl().fromLocalFile(saved_playlist) |
||
339 | self.playlist.load(playlist) |
||
340 | self.player.setPlaylist(self.playlist) |
||
341 | |||
342 | for song_index in range(self.playlist.mediaCount()): |
||
343 | file_info = self.playlist.media(song_index).canonicalUrl().fileName() |
||
344 | playlist_item = QListWidgetItem(file_info) |
||
345 | playlist_item.setToolTip(file_info) |
||
346 | self.playlist_view.addItem(playlist_item) |
||
347 | |||
348 | self.playlist_view.setCurrentRow(0) |
||
349 | |||
350 | def open_directory(self): |
||
351 | """Open the selected directory and add the files within to an empty playlist.""" |
||
352 | directory = QFileDialog.getExistingDirectory(self, 'Open Directory', '', QFileDialog.ReadOnly) |
||
353 | |||
354 | if directory: |
||
355 | self.playlist.clear() |
||
356 | self.playlist_view.clear() |
||
357 | for dirpath, __, files in os.walk(directory): |
||
358 | for filename in natsort.natsorted(files, alg=natsort.ns.PATH): |
||
359 | file = os.path.join(dirpath, filename) |
||
360 | if filename.endswith(('mp3', 'flac')): |
||
361 | self.playlist.addMedia(QMediaContent(QUrl().fromLocalFile(file))) |
||
362 | playlist_item = QListWidgetItem(filename) |
||
363 | playlist_item.setToolTip(filename) |
||
364 | self.playlist_view.addItem(playlist_item) |
||
365 | |||
366 | self.player.setPlaylist(self.playlist) |
||
367 | self.playlist_view.setCurrentRow(0) |
||
368 | self.player.play() |
||
369 | |||
370 | def open_media_library(self, index): |
||
371 | """Open a directory or file from the media library into an empty playlist.""" |
||
372 | self.playlist.clear() |
||
373 | self.playlist_view.clear() |
||
374 | |||
375 | if self.library_model.fileName(index).endswith(('mp3', 'flac')): |
||
376 | self.playlist.addMedia(QMediaContent(QUrl().fromLocalFile(self.library_model.filePath(index)))) |
||
377 | self.playlist_view.addItem(self.library_model.fileName(index)) |
||
378 | |||
379 | elif self.library_model.isDir(index): |
||
380 | directory = self.library_model.filePath(index) |
||
381 | for dirpath, __, files in os.walk(directory): |
||
382 | for filename in natsort.natsorted(files, alg=natsort.ns.PATH): |
||
383 | file = os.path.join(dirpath, filename) |
||
384 | if filename.endswith(('mp3', 'flac')): |
||
385 | self.playlist.addMedia(QMediaContent(QUrl().fromLocalFile(file))) |
||
386 | playlist_item = QListWidgetItem(filename) |
||
387 | playlist_item.setToolTip(filename) |
||
388 | self.playlist_view.addItem(playlist_item) |
||
389 | |||
390 | self.player.setPlaylist(self.playlist) |
||
391 | self.player.play() |
||
392 | |||
393 | def display_meta_data(self): |
||
394 | """Display the current song's metadata in the main window. |
||
395 | |||
396 | If the current song contains metadata, its cover art is extracted and shown in |
||
397 | the main window while the track number, artist, album, and track title are shown |
||
398 | in the window title. |
||
399 | """ |
||
400 | if self.player.isMetaDataAvailable(): |
||
401 | file_path = self.player.currentMedia().canonicalUrl().toLocalFile() |
||
402 | (album, artist, title, track_number, *__, artwork) = metadata.metadata(file_path) |
||
403 | |||
404 | try: |
||
405 | self.pixmap.loadFromData(artwork) |
||
406 | except TypeError: |
||
407 | self.pixmap = QPixmap(artwork) |
||
408 | |||
409 | meta_data = '{} - {} - {} - {}' .format(track_number, artist, album, title) |
||
410 | |||
411 | self.setWindowTitle(meta_data) |
||
412 | self.art.setScaledContents(True) |
||
413 | self.art.setPixmap(self.pixmap) |
||
414 | self.layout.addWidget(self.art) |
||
415 | |||
416 | def initialize_playlist(self, start): |
||
417 | """Display playlist and reset playback mode when media inserted into playlist.""" |
||
418 | if start == 0: |
||
419 | if self.library_dock.isVisible(): |
||
420 | self.playlist_dock.setVisible(True) |
||
421 | self.playlist_dock.show() |
||
422 | self.playlist_dock.raise_() |
||
423 | |||
424 | if self.playlist.playbackMode() != QMediaPlaylist.Sequential: |
||
425 | self.playlist.setPlaybackMode(QMediaPlaylist.Sequential) |
||
426 | repeat_icon = utilities.resource_filename('mosaic.images', 'md_repeat_none.png') |
||
427 | self.repeat_action.setIcon(QIcon(repeat_icon)) |
||
428 | |||
429 | def press_playback(self, event): |
||
430 | """Change the playback of the player on cover art mouse event. |
||
431 | |||
432 | When the cover art is clicked, the player will play the media if the player is |
||
433 | either paused or stopped. If the media is playing, the media is set |
||
434 | to pause. |
||
435 | """ |
||
436 | if event.button() == 1 and configuration.Playback().cover_art_playback.isChecked(): |
||
437 | if (self.player.state() == QMediaPlayer.StoppedState or |
||
438 | self.player.state() == QMediaPlayer.PausedState): |
||
439 | self.player.play() |
||
440 | elif self.player.state() == QMediaPlayer.PlayingState: |
||
441 | self.player.pause() |
||
442 | |||
443 | def seek(self, seconds): |
||
444 | """Set the position of the song to the position dragged to by the user.""" |
||
445 | self.player.setPosition(seconds * 1000) |
||
446 | |||
447 | def song_duration(self, duration): |
||
448 | """Set the slider to the duration of the currently played media.""" |
||
449 | duration /= 1000 |
||
450 | self.duration = duration |
||
451 | self.slider.setMaximum(duration) |
||
452 | |||
453 | def song_position(self, progress): |
||
454 | """Move the horizontal slider in sync with the duration of the song. |
||
455 | |||
456 | The progress is relayed to update_duration() in order |
||
457 | to display the time label next to the slider. |
||
458 | """ |
||
459 | progress /= 1000 |
||
460 | |||
461 | if not self.slider.isSliderDown(): |
||
462 | self.slider.setValue(progress) |
||
463 | |||
464 | self.update_duration(progress) |
||
465 | |||
466 | def update_duration(self, current_duration): |
||
467 | """Calculate the time played and the length of the song. |
||
468 | |||
469 | Both of these times are sent to duration_label() in order to display the |
||
470 | times on the toolbar. |
||
471 | """ |
||
472 | duration = self.duration |
||
473 | |||
474 | if current_duration or duration: |
||
475 | time_played = QTime((current_duration / 3600) % 60, (current_duration / 60) % 60, |
||
476 | (current_duration % 60), (current_duration * 1000) % 1000) |
||
477 | song_length = QTime((duration / 3600) % 60, (duration / 60) % 60, (duration % 60), |
||
478 | (duration * 1000) % 1000) |
||
479 | |||
480 | if duration > 3600: |
||
481 | time_format = "hh:mm:ss" |
||
482 | else: |
||
483 | time_format = "mm:ss" |
||
484 | |||
485 | time_display = "{} / {}" .format(time_played.toString(time_format), song_length.toString(time_format)) |
||
486 | |||
487 | else: |
||
488 | time_display = "" |
||
489 | |||
490 | self.duration_label.setText(time_display) |
||
491 | |||
492 | def set_state(self, state): |
||
493 | """Change the icon in the toolbar in relation to the state of the player. |
||
494 | |||
495 | The play icon changes to the pause icon when a song is playing and |
||
496 | the pause icon changes back to the play icon when either paused or |
||
497 | stopped. |
||
498 | """ |
||
499 | if self.player.state() == QMediaPlayer.PlayingState: |
||
500 | pause_icon = utilities.resource_filename('mosaic.images', 'md_pause.png') |
||
501 | self.play_action.setIcon(QIcon(pause_icon)) |
||
502 | self.play_action.triggered.connect(self.player.pause) |
||
503 | |||
504 | elif (self.player.state() == QMediaPlayer.PausedState or self.player.state() == QMediaPlayer.StoppedState): |
||
505 | self.play_action.triggered.connect(self.player.play) |
||
506 | play_icon = utilities.resource_filename('mosaic.images', 'md_play.png') |
||
507 | self.play_action.setIcon(QIcon(play_icon)) |
||
508 | |||
509 | def previous(self): |
||
510 | """Move to the previous song in the playlist. |
||
511 | |||
512 | Moves to the previous song in the playlist if the current song is less |
||
513 | than five seconds in. Otherwise, restarts the current song. |
||
514 | """ |
||
515 | if self.player.position() <= 5000: |
||
516 | self.playlist.previous() |
||
517 | else: |
||
518 | self.player.setPosition(0) |
||
519 | |||
520 | def repeat_song(self): |
||
521 | """Set the current media to repeat and change the repeat icon accordingly. |
||
522 | |||
523 | There are four playback modes: repeat none, repeat all, repeat once, and shuffle. |
||
524 | Clicking the repeat button cycles through each playback mode. |
||
525 | """ |
||
526 | if self.playlist.playbackMode() == QMediaPlaylist.Sequential: |
||
527 | self.playlist.setPlaybackMode(QMediaPlaylist.Loop) |
||
528 | repeat_on_icon = utilities.resource_filename('mosaic.images', 'md_repeat_all.png') |
||
529 | self.repeat_action.setIcon(QIcon(repeat_on_icon)) |
||
530 | |||
531 | elif self.playlist.playbackMode() == QMediaPlaylist.Loop: |
||
532 | self.playlist.setPlaybackMode(QMediaPlaylist.CurrentItemInLoop) |
||
533 | repeat_on_icon = utilities.resource_filename('mosaic.images', 'md_repeat_once.png') |
||
534 | self.repeat_action.setIcon(QIcon(repeat_on_icon)) |
||
535 | |||
536 | elif self.playlist.playbackMode() == QMediaPlaylist.CurrentItemInLoop: |
||
537 | self.playlist.setPlaybackMode(QMediaPlaylist.Random) |
||
538 | repeat_icon = utilities.resource_filename('mosaic.images', 'md_shuffle.png') |
||
539 | self.repeat_action.setIcon(QIcon(repeat_icon)) |
||
540 | |||
541 | elif self.playlist.playbackMode() == QMediaPlaylist.Random: |
||
542 | self.playlist.setPlaybackMode(QMediaPlaylist.Sequential) |
||
543 | repeat_icon = utilities.resource_filename('mosaic.images', 'md_repeat_none.png') |
||
544 | self.repeat_action.setIcon(QIcon(repeat_icon)) |
||
545 | |||
546 | def activate_playlist_item(self, item): |
||
547 | """Set the active media to the playlist item dobule-clicked on by the user.""" |
||
548 | current_index = self.playlist_view.row(item) |
||
549 | if self.playlist.currentIndex() != current_index: |
||
550 | self.playlist.setCurrentIndex(current_index) |
||
551 | |||
552 | if self.player.state() != QMediaPlayer.PlayingState: |
||
553 | self.player.play() |
||
554 | |||
555 | def change_index(self, row): |
||
556 | """Highlight the row in the playlist of the active media.""" |
||
557 | self.playlist_view.setCurrentRow(row) |
||
558 | |||
559 | def minimalist_view(self): |
||
560 | """Resize the window to only show the menu bar and audio controls.""" |
||
561 | if self.minimalist_view_action.isChecked(): |
||
562 | |||
563 | if self.playlist_dock.isVisible(): |
||
564 | self.playlist_dock_state = True |
||
565 | if self.library_dock.isVisible(): |
||
566 | self.library_dock_state = True |
||
567 | |||
568 | self.library_dock.close() |
||
569 | self.playlist_dock.close() |
||
570 | |||
571 | QTimer.singleShot(10, lambda: self.resize(500, 0)) |
||
572 | |||
573 | else: |
||
574 | self.resize(defaults.Settings().window_size, defaults.Settings().window_size + 63) |
||
575 | |||
576 | if self.library_dock_state: |
||
577 | self.library_dock.setVisible(True) |
||
578 | |||
579 | if self.playlist_dock_state: |
||
580 | self.playlist_dock.setVisible(True) |
||
581 | |||
582 | def dock_visiblity_change(self, visible): |
||
583 | """Change the size of the main window when the docks are toggled.""" |
||
584 | if visible and self.playlist_dock.isVisible() and not self.library_dock.isVisible(): |
||
585 | self.resize(defaults.Settings().window_size + self.playlist_dock.width() + 6, |
||
586 | self.height()) |
||
587 | |||
588 | elif visible and not self.playlist_dock.isVisible() and self.library_dock.isVisible(): |
||
589 | self.resize(defaults.Settings().window_size + self.library_dock.width() + 6, |
||
590 | self.height()) |
||
591 | |||
592 | elif visible and self.playlist_dock.isVisible() and self.library_dock.isVisible(): |
||
593 | self.resize(defaults.Settings().window_size + self.library_dock.width() + 6, |
||
594 | self.height()) |
||
595 | |||
596 | elif (not visible and not self.playlist_dock.isVisible() and not |
||
597 | self.library_dock.isVisible()): |
||
598 | self.resize(defaults.Settings().window_size, defaults.Settings().window_size + 63) |
||
599 | |||
600 | def media_information_dialog(self): |
||
601 | """Show a dialog of the current song's metadata.""" |
||
602 | if self.player.isMetaDataAvailable(): |
||
603 | file_path = self.player.currentMedia().canonicalUrl().toLocalFile() |
||
604 | else: |
||
605 | file_path = None |
||
606 | dialog = information.InformationDialog(file_path) |
||
607 | dialog.exec_() |
||
608 | |||
609 | def change_window_size(self): |
||
610 | """Change the window size of the music player.""" |
||
611 | self.playlist_dock.close() |
||
612 | self.library_dock.close() |
||
613 | self.resize(defaults.Settings().window_size, defaults.Settings().window_size + 63) |
||
614 | |||
615 | def change_media_library_path(self, path): |
||
616 | """Change the media library path to the new path selected in the preferences dialog.""" |
||
617 | self.library_model.setRootPath(path) |
||
618 | self.library_view.setModel(self.library_model) |
||
619 | self.library_view.setRootIndex(self.library_model.index(path)) |
||
620 | |||
621 | def closeEvent(self, event): |
||
622 | """Override the PyQt close event in order to handle save playlist on close.""" |
||
623 | playlist = "{}/.m3u" .format(self.playlist_location) |
||
624 | if defaults.Settings().save_playlist_on_close: |
||
625 | self.playlist.save(QUrl().fromLocalFile(playlist), "m3u") |
||
626 | else: |
||
627 | if os.path.exists(playlist): |
||
628 | os.remove(playlist) |
||
629 | QApplication.quit() |
||
630 | |||
631 | |||
632 | def main(): |
||
633 | """Create an instance of the music player and use QApplication to show the GUI. |
||
634 | |||
635 | QDesktopWidget() is used to move the application to the center of the user's screen. |
||
636 | """ |
||
637 | application = QApplication(sys.argv) |
||
638 | window = MusicPlayer() |
||
639 | desktop = QDesktopWidget().availableGeometry() |
||
640 | width = (desktop.width() - window.width()) / 2 |
||
641 | height = (desktop.height() - window.height()) / 2 |
||
642 | window.show() |
||
643 | window.move(width, height) |
||
644 | sys.exit(application.exec_()) |
||
645 |