class MyWidget(QMainWindow): def __init__(self): super().__init__() # !Костыль # Запрещаю менять размер окна, т.к. не хочу работать с layout`ами self.setFixedSize(656, 532) self.setWindowIcon(QIcon('icons/icon.ico')) # Добавляем эквалайзер громкости self.equalizer = QVolumeEq((255, 255, 102), (255, 26, 10), bg_color=(0, 0, 0, 20)) self.equalizer.setParent(self) self.equalizer.move(20, 391) self.equalizer.resize(341, 31) uic.loadUi('icons/djap.ui', self) self.lightThemeAction.triggered.connect(self.change_theme) self.darkThemeAction.triggered.connect(self.change_theme) # Создаём организатор плейлистов self.playlist_handler = QPlaylistHandler() self.playlist_handler.setParent(self) self.playlist_handler.move(380, 40) # Настраиваем меню self.saveOption.triggered.connect(self.playlist_handler.save_playlist) self.saveAsOption.triggered.connect(self.playlist_handler.save_playlist_as) self.deleteOption.triggered.connect(self.playlist_handler.delete_playlist) self.aboutOption.triggered.connect(self.about) self.helpOption.triggered.connect(self.help) self.repeatModeOption.triggered.connect(self.playlist_handler.change_playmode) self.oneModeOption.triggered.connect(self.playlist_handler.change_playmode) self.randomModeOption.triggered.connect(self.playlist_handler.change_playmode) # Реализация проигрывателя и основного плейлиста self.player = QMediaPlayer() self.player.durationChanged.connect(self.update_song) self.player.setVolume(25) self.player.positionChanged.connect(self.update_timeline_position) self.queue = QMediaPlaylist(self.player) self.player.setPlaylist(self.queue) self.queue.setPlaybackMode(QMediaPlaylist.Loop) self.is_looped_queue = True # Загрузка музыки из плейлиста self.load_songs_from_playlist() # Подключаем кнопки к их функциям self.is_playing = False self.playBtn.clicked.connect(self.play_or_pause) self.queue.currentMediaChanged.connect(self.check_to_stop) self.nextBtn.clicked.connect(self.next_audio) self.prevBtn.clicked.connect(self.prev_audio) self.is_looped_current_track = False self.loopBtn.clicked.connect(self.loop_or_unloop_current_track) # Настройка слайдера звука self.volumeSlider.hide() self.volumeSlider.setValue(25) self.equalizer.setValue(25) self.volumeSlider.sliderReleased.connect(self.volumeSlider.hide) self.volumeSlider.valueChanged.connect(self.change_volume) self.volumeBtn.clicked.connect(self.change_volume) # Настройка таймлайна self.is_timeline_dragged = False self.timeline.sliderReleased.connect(self.timeline_changed) self.timeline.sliderPressed.connect(self.timeline_is_dragged) # !Костыль # Имитируем запуск аудиодорожки, чтобы подгрузить данные self.play_or_pause() self.play_or_pause() self.icon_changed() def palette(self): """Функция для создания темы""" con = sqlite3.connect('user_data.sqlite') cur = con.cursor() if int(cur.execute('SELECT * FROM theme').fetchone()[0]): palette = QPalette() palette.setColor(QPalette.Window, QColor(240, 240, 240)) palette.setColor(QPalette.WindowText, Qt.black) palette.setColor(QPalette.Base, Qt.white) palette.setColor(QPalette.AlternateBase, QColor(246, 246, 246)) palette.setColor(QPalette.ToolTipBase, QColor(255, 255, 240)) palette.setColor(QPalette.ToolTipText, Qt.black) palette.setColor(QPalette.Text, Qt.black) palette.setColor(QPalette.Button, QColor(240, 240, 240)) palette.setColor(QPalette.ButtonText, Qt.black) palette.setColor(QPalette.BrightText, Qt.white) palette.setColor(QPalette.Highlight, QColor(0, 120, 215).lighter()) palette.setColor(QPalette.HighlightedText, Qt.white) else: palette = QPalette() palette.setColor(QPalette.Window, QColor(53, 53, 53)) palette.setColor(QPalette.WindowText, Qt.white) palette.setColor(QPalette.Base, QColor(15, 15, 15)) palette.setColor(QPalette.AlternateBase, QColor(53, 53, 53)) palette.setColor(QPalette.ToolTipBase, Qt.white) palette.setColor(QPalette.ToolTipText, Qt.white) palette.setColor(QPalette.Text, Qt.white) palette.setColor(QPalette.Button, QColor(53, 53, 53)) palette.setColor(QPalette.ButtonText, Qt.white) palette.setColor(QPalette.BrightText, Qt.red) palette.setColor(QPalette.Highlight, QColor(142, 45, 197).lighter()) palette.setColor(QPalette.HighlightedText, Qt.black) return palette def change_theme(self): """Функция для смены темы""" con = sqlite3.connect('user_data.sqlite') cur = con.cursor() if self.sender() == self.lightThemeAction: cur.execute('UPDATE theme SET isit = 1') else: cur.execute('UPDATE theme SET isit = 0') con.commit() QMessageBox.warning(self, 'DJust Audio Player', 'Чтобы изменения вступили в силу, ' 'перезапустите приложение.', QMessageBox.Ok) def help(self): """Функция для показа формы справки""" self.help = QHelp() def about(self): """Функция для показа формы О программе""" self.about = QAbout() def delete_song_from_mediaplayer(self, index): """Удалить трек из медиаплеера""" self.queue.removeMedia(index) def add_song_to_mediaplayer(self, url): """Добавить трек в медиаплеер""" self.queue.addMedia(QMediaContent(QUrl(url))) def change_track_by_click(self, row): """Функция для смены трека по нажатию в списке""" self.queue.setCurrentIndex(row) def check_to_stop(self): """Проверяем, чтобы плеер не играл с пустым плейлистом""" if self.queue.isEmpty() and self.is_playing: self.play_or_pause() def load_songs_from_playlist(self): """Загрузка музыки из БД в плейлист""" self.queue.clear() for url in self.playlist_handler.urls(): self.queue.addMedia(QMediaContent(QUrl(url))) def icon_changed(self): """Функция для вывода обложки трека""" def noicon(): """В случае отсутствия обложки эта функция ставить свою заглушку""" try: pixmap = QPixmap() if pixmap.load('icons/noicon.png'): self.audioPic.setPixmap(pixmap) except Exception as e: print(e.__class__.__name__, ': ', e, sep='') try: # Читаем метаданные трека n = self.queue.currentIndex() url = self.playlist_handler.urls()[n if n > -1 else 0] file = File(url) for i in file.tags.keys(): if 'APIC:' in i: artwork = file.tags[i].data break else: raise KeyError # Переводим их в байтовый массив ba = QByteArray(artwork) # И загружаем на форму, если это возможно pixmap = QPixmap() if pixmap.loadFromData(ba): self.audioPic.setPixmap(pixmap.scaled(341, 341, Qt.IgnoreAspectRatio)) else: raise KeyError except Exception as e: # print(e.__class__.__name__, ': ', e, sep='') noicon() def timeline_is_dragged(self): """Мини-функция для изменения переменной""" # При вызове этой функции мы понимаем, что пользователь # взаимодействует с таймлайном трека self.is_timeline_dragged = True def timeline_changed(self): """Функция для пользовательской перемотки данного трека""" # Перематываем позицию плеера self.player.setPosition(self.timeline.value() * 1000) # Пользователь отпустил язычок слайдера и больше не взаимодействует с таймлайном self.is_timeline_dragged = False # Вызываем обновление временной шкалы, т.к. произошла перемотка self.update_timeline_position() def update_song(self): """Изменение данных о треке""" # !Костыль # Проматываем несколько миллисекунд, чтобы избежать повреждённых треков self.player.setPosition(10) # Меняем обложку self.icon_changed() # Выделяем в списке новый трек self.playlist_handler.set_current_select(self.queue.currentIndex()) # Меняем название окна в соответствии с играющим треком title = self.queue.currentMedia().canonicalUrl().path().split('/')[-1] if title: self.setWindowTitle('DJust Audio Player | %s' % title) else: self.setWindowTitle('DJust Audio Player') # Изменяем длину таймлайна на длину трека self.timeline.setMaximum(self.player.duration() // 1000) # Выводим длину трека в минутах/секундах на экран minutes, seconds = self.player.duration() // 1000 // 60, self.player.duration() // 1000 % 60 self.endTimeLabel.setText(str(minutes) + ':{:02d}'.format(seconds)) def update_timeline_position(self): """Функция для обновления временной шкалы""" # Выводим текущее положение плеера в минутах/секундах minutes, seconds = self.player.position() // 1000 // 60, self.player.position() // 1000 % 60 self.currentTimeLabel.setText(str(minutes) + ':{:02d}'.format(seconds)) # Чтобы не "вырывать" язычок слайдера из рук пользователя, # проверяем, что пользователь НЕ взаимодейтсвует с таймлайном. if not self.is_timeline_dragged: self.timeline.setValue(minutes * 60 + seconds) def loop_or_unloop_current_track(self): """Мини-функция для зацикливания данного трека""" if not self.is_looped_current_track: # Зацикливаем self.is_looped_current_track = True self.queue.setPlaybackMode(QMediaPlaylist.CurrentItemInLoop) # Меняем картинку на перечёркнутый круг self.loopBtn.setStyleSheet('background-image: url(icons/unloop.png)') else: # убираем цикл self.is_looped_current_track = False self.queue.setPlaybackMode(self.playlist_handler.mode) self.loopBtn.setStyleSheet('background-image: url(icons/loop.png)') def next_audio(self): """Функция для перемотки на следующий в списке трек""" if self.queue.mediaCount() - 1 == self.queue.currentIndex(): # Если трек ппоследний в списке, возвращаемся к первому self.queue.setCurrentIndex(0) else: # Иначе просто перемещаемся к следующему self.queue.next() def prev_audio(self): """Функция для перемотки на предыдущий в списке трек""" if 0 == self.queue.currentIndex(): # Если трек первый в списке, возвращаемся к последнему self.queue.setCurrentIndex(self.queue.mediaCount() - 1) else: # Иначе просто возвращаемся к предыдущему self.queue.previous() def play_or_pause(self): """Функция для запуска или остановки проигрывателя""" if self.queue.isEmpty(): # Меняем картинку на стрелочку (проигрывание) self.is_playing = False self.playBtn.setStyleSheet('background-image: url(icons/play.png)') # Ставим на паузу self.player.pause() return if self.is_playing: # Меняем картинку на стрелочку (проигрывание) self.is_playing = False self.playBtn.setStyleSheet('background-image: url(icons/play.png)') # Ставим на паузу self.player.pause() else: # Меняем картинку на 2 палочки (пауза) self.is_playing = True self.playBtn.setStyleSheet('background-image: url(icons/pause.png)') # Запускаем звук self.player.play() def change_volume(self): """Мини-Функция для передачи уровня громкости в плеер""" # Функцию использует несколько объектов сразу, проверяем вызывателя if self.sender() == self.volumeBtn: # Если функцию вызвала кнопка, то включаем/выключаем отображение слайдера if not self.volumeSlider.isHidden(): self.volumeSlider.hide() else: self.volumeSlider.show() else: # Иначе функцию вызвал сам слайдер. Значит, меняем значение self.player.setVolume(self.volumeSlider.value()) self.equalizer.setValue(self.volumeSlider.value())
class Player(QMediaPlayer): def __init__(self, parent=None): super(Player, self).__init__(parent) self.parent = parent self.player = QMediaPlayer() self.queueList = QMediaPlaylist() self.player.setPlaylist(self.queueList) self.queueData = [] self.position = 0 self.volume = 100 self.player.mediaStatusChanged.connect(self.qmp_mediaStatusChanged) self.player.positionChanged.connect(self.qmp_positionChanged) self.player.durationChanged.connect(self.durationChanged) self.queueList.currentIndexChanged.connect(self.playlistPosChanged) def add(self, data): """Add track to the queue""" queueData = { 'pc_title': data['pc_title'], 'title': data['title'], 'url': data['url'], 'date': data['date_format'], 'description': data['description'] } self.queueData.append(queueData) self.queueList.addMedia(QMediaContent(QUrl(data['url']))) def playPause(self): icon = QIcon.fromTheme("media-playback-pause") if self.player.state() == QMediaPlayer.StoppedState: if self.player.mediaStatus() == QMediaPlayer.NoMedia: if self.queueList.mediaCount() != 0: self.player.play() elif self.player.mediaStatus() == QMediaPlayer.LoadedMedia: self.queueList.setCurrentIndex(self.position) self.player.play() elif self.player.mediaStatus() == QMediaPlayer.BufferedMedia: self.player.play() elif self.player.state() == QMediaPlayer.PlayingState: icon = QIcon.fromTheme("media-playback-start") self.player.pause() elif self.player.state() == QMediaPlayer.PausedState: self.player.play() self.parent.playBtn.setIcon(icon) def startPlay(self): data = self.queueData[0] self.queueList.setCurrentIndex(0) self.parent.curPCLabel.setText(data['pc_title']) self.parent.curTrackName.setText(data['title']) self.player.play() icon = QIcon.fromTheme("media-playback-pause") self.parent.playBtn.setIcon(icon) def stop(self): self.player.stop() icon = QIcon.fromTheme("media-playback-start") self.parent.playBtn.setIcon(icon) def setPosition(self, pos): self.player.setPosition(pos) def durationChanged(self, duration): total_time = '0:00:00' duration = self.player.duration() total_time = ms_to_time(duration) self.parent.timeSlider.setMaximum(duration) self.currentTrackDuration = duration self.parent.totalTimeLabel.setText(total_time) def qmp_mediaStatusChanged(self, status): icon = QIcon.fromTheme("media-playback-pause") if self.player.state() == QMediaPlayer.StoppedState: icon = QIcon.fromTheme("media-playback-start") elif self.player.state() == QMediaPlayer.PausedState: icon = QIcon.fromTheme("media-playback-start") self.parent.playBtn.setIcon(icon) def qmp_positionChanged(self, position, senderType=False): self.currentTime = position current_time = '0:00:00' if position != -1: current_time = ms_to_time(position) self.parent.timeLabel.setText(current_time) self.parent.timeSlider.blockSignals(True) self.parent.timeSlider.setValue(position) self.parent.timeSlider.blockSignals(False) def playlistPosChanged(self): pos = self.queueList.currentIndex() data = self.queueData[pos] self.parent.curPCLabel.setText(data['pc_title']) self.parent.curTrackName.setText(data['title']) windowTitle = '{0} - {1}'.format(data['pc_title'], data['title']) self.parent.setWindowTitle(windowTitle) if self.queueList.mediaCount() > 1: if pos < self.queueList.mediaCount() - 1: self.parent.queueNextBtn.setEnabled(True) else: self.parent.queueNextBtn.setEnabled(False) if pos > 0: self.parent.queuePrevBtn.setEnabled(True) else: self.parent.queuePrevBtn.setEnabled(False) if pos < self.queueList.mediaCount(): prevPos = 0 if self.position < pos: prevPos = pos - 1 else: prevPos = pos + 1 prevItem = self.parent.queueList.item(prevPos) prevWidget = self.parent.queueList.itemWidget(prevItem) if prevItem: prevWidget.statusIcon.setPixmap(QPixmap()) self.position = pos item = self.parent.queueList.item(pos) widget = self.parent.queueList.itemWidget(item) if widget: icon = QIcon.fromTheme("media-playback-start") widget.statusIcon.setPixmap(icon.pixmap(16, 16)) def setVolume(self, volume): self.player.setVolume(volume) def rev10Secs(self): position = self.player.position() new_pos = position - 10000 self.player.setPosition(new_pos) def for10Secs(self): position = self.player.position() new_pos = position + 10000 self.player.setPosition(new_pos) def delete(self, position): """ Delete the track and her data from position""" self.queueData.pop(position) self.queueList.removeMedia(position) if (position == self.position): self.playlistPosChanged()
class Control: """A class that handles the logic behind the program by manipulating the GUI classes and calling their methods in response to received signals.""" MAGIC = b"\x01\xff" def __init__(self, screens: list) -> None: self.player = QMediaPlayer() self.player.setAudioRole(QAudio.MusicRole) self.playlist = QMediaPlaylist() self.player.setPlaylist(self.playlist) self.mainWindow = MainWindow(self, screens) self.mainArea = self.mainWindow.centralWidget().upperBox.mainArea self.songList = self.mainWindow.centralWidget().upperBox.songList self.mediaControlArea = self.mainWindow.centralWidget( ).mediaControlArea self.mainWindow.show() self.library = None self.currentSong = None self.playing = False self.random = False self.repeat = 0 self.volume = 50 self.volumeChange(self.volume) self.mediaControlArea.volumeControl.setValue(self.volume) self.mainTimer = QTimer() self.mainTimer.setInterval(100) self.mainTimer.timeout.connect(self.updateSongProgress) self.mainTimer.start() self.libraryUpdateTimer = QTimer() self.libraryUpdateTimer.setInterval(15_000) self.libraryUpdateTimer.timeout.connect(self.updateLibrary) self.libraryUpdateTimer.start() self._connection = None self.connections() self.displayedType = None self.displayedName = None self.types = None self.songListWidth = None values = self.load() if values: self.mainWindow.setGeometry(*values) self.volumeChange(self.volume) self.mediaControlArea.volumeControl.setValue(self.volume) self.setUpTimer = QTimer() self.setUpTimer.setInterval(20) self.setUpTimer.setSingleShot(True) self.setUpTimer.timeout.connect(self.setAreas) self.setUpTimer.start() def connections(self): self.player.currentMediaChanged.connect(self.updateCurrentSong) self.player.durationChanged.connect(self.updateSongProgressRange) self.player.stateChanged.connect(self.playerStatusChanged) self.mediaControlArea.previousButton.click.connect( self.playlist.previous) self.mediaControlArea.repeatButton.click.connect( self.repeatButtonClick) self.mediaControlArea.stopButton.click.connect(self.stopButtonClick) self.mediaControlArea.playButton.click.connect(self.playButtonClick) self.mediaControlArea.randomButton.click.connect( self.randomButtonClick) self.mediaControlArea.nextButton.click.connect(self.playlist.next) self.mediaControlArea.muteButton.click.connect(self.mute) self.mediaControlArea.songProgress.sliderMoved.connect( self.songProgressMove) self.mediaControlArea.volumeControl.sliderMoved.connect( self.volumeChange) def setAreas(self) -> None: """Called after the GUI is created to provide user with a feedback that the program is running in case a larger amount of songs will be added when the Library class is initialized.""" # TODO add a tooltip that will notify the user larger amount of songs is being loaded # (freezes the program as the execution moves to the Library class.) self.library = library.Library() self.types = { "artist": self.library.getSongsForArtist, "album": self.library.getSongsForAlbum, "playlist": self.library.getSongsForPlaylist } self.mainArea.setAreas(self.library) self.setUpTimer.deleteLater() self.setUpTimer = None self.getSongs(self.displayedType, self.displayedName) if self.songListWidth is not None: songListGeometry = self.songList.geometry() self.songList.preferredWidth = songListGeometry.width( ) - self.songListWidth self.mainWindow.centralWidget().upperBox.line.resizeWidgets( songListGeometry.width() - self.songListWidth) def updateLibrary(self) -> None: self.library.update() self.mainArea.updateView(self.library) def updateCurrentSong(self) -> None: """Update all areas that may display information about the currently playing song - SongList, Now Playing tab, BottomBox""" media = self.player.currentMedia() self.currentSong = media.request().url().toLocalFile().replace( "/", "\\") if self.currentSong in self.library.library: self.songList.updateActiveSong(self.currentSong) self.mainArea.updateActiveSong(self.playlist.currentIndex()) songEntry = self.library.library[self.currentSong] self.mediaControlArea.updateSongInfo( f"{songEntry[ARTIST]} - {songEntry[NAME]}") def updateSongProgressRange(self) -> None: """Updates the range of the slider that represents the song position.""" self.mediaControlArea.updateSongProgressRange(self.player.duration()) def playerStatusChanged(self) -> None: """Used to properly update the player look after the current playlist has finished.""" index = self.playlist.currentIndex() if index == -1: self.stopButtonClick() def getSongs(self, isType: str, name: str) -> None: """Retrieves the songs for a given artist, album or playlist based on type and passes the resulting list to the SongList class.""" if isType is None: isType = self.displayedType if name is None: name = self.displayedName orderBy = self.songList.buttonOrderBy.text() reverse = True if self.songList.buttonOrderReverse.text() == chr( 0x25bc) else False listForType = self.types[isType](name, orderBy, reverse) playlist = None if isType == "playlist": playlist = name if len(listForType) == 0: artists = self.library.artists if len(artists): listForType = self.library.getSongsForArtist(artists[0]) self.songList.updateSongList(listForType, self.library.library, self.currentSong, playlist, isType) self.displayedType = isType self.displayedName = name def playSongList(self, song: str = None) -> None: """Called when user double-clicks on an artist/album/playlist widget or a song in right-hand side panel.""" self.playlist.clear() index = 0 loopIndex = 0 for songPath in self.songList.garbageProtector: if song == songPath: index = loopIndex self.playlist.addMedia(QMediaContent(QUrl.fromLocalFile(songPath))) loopIndex += 1 if self.playlist.isEmpty(): return self.player.play() if index > 0: self.playlist.setCurrentIndex(index) self.playing = True self.mediaControlArea.playButton.updatePictures( bottom.pausePixmap, bottom.pauseHoverPixmap, False) self.mainArea.setNowPlayingArea(self.library) self.mainArea.updateActiveSong(self.playlist.currentIndex()) def playSongWidget(self, songPath: str, afterCurrent: bool = False) -> None: if afterCurrent: index = self.playlist.currentIndex() + 1 self.playlist.insertMedia( index, QMediaContent(QUrl.fromLocalFile(songPath))) else: self.playlist.addMedia(QMediaContent(QUrl.fromLocalFile(songPath))) self.mainArea.setNowPlayingArea(self.library) self.mainArea.updateActiveSong(self.playlist.currentIndex()) self.playing = True def removeFromNowPlaying(self, widget) -> None: if self.playlist.mediaCount() > 1: for row in range(self.mainArea.nowPlayingLayout.rowCount()): for column in range( self.mainArea.nowPlayingLayout.columnCount()): if self.mainArea.nowPlayingLayout.itemAtPosition( row, column).widget() is widget: self.playlist.removeMedia(row - 1) break else: continue break else: self.stopButtonClick() self.playlist.clear() self.mainArea.setNowPlayingArea(self.library) if self.playing: self.mainArea.updateActiveSong(self.playlist.currentIndex()) def playMediaWidget(self, isType: str, target: str, startOver: bool, afterCurrent: bool) -> None: """Called from MediaWidget - plays all songs for MediaWidget's type and name.""" if startOver: self.playlist.clear() if afterCurrent: index = self.playlist.currentIndex() + 1 for songPath in self.types[isType](target): self.playlist.insertMedia( index, QMediaContent(QUrl.fromLocalFile(songPath))) index += 1 else: for songPath in self.types[isType](target): self.playlist.addMedia( QMediaContent(QUrl.fromLocalFile(songPath))) if startOver: self.player.play() self.playing = True self.mediaControlArea.playButton.updatePictures( bottom.pausePixmap, bottom.pauseHoverPixmap, False) self.mainArea.setNowPlayingArea(self.library) self.mainArea.updateActiveSong(self.playlist.currentIndex()) def playFromNowPlaying(self, song: str) -> None: """Called when user double-clicks on a song in the Now Playing tab.""" for n in range(self.playlist.mediaCount()): media = self.playlist.media(n) if song == media.request().url().toLocalFile().replace("/", "\\"): self.playlist.setCurrentIndex(n) if not self.playing: self.player.play() self.playing = True return def createPlaylist(self, playlistName: str) -> None: self.library.createPlaylist(playlistName) self.mainArea.setMainAreaPlaylists(self.library) def addToExistingPlaylist(self, playlist: str, songOrWidget: str, isType: str) -> None: if isType in self.types: for song in self.types[isType](songOrWidget): self.library.addToPlaylist(playlist, song) else: self.library.addToPlaylist(playlist, songOrWidget) self.library.update() def removeFromPlaylist(self, playlist: str, song: str) -> None: self.library.deleteFromPlaylist(playlist, song) self.mainArea.setMainAreaPlaylists(self.library) self.library.update() self.getSongs("playlist", playlist) def renamePlaylist(self, playlistName: str, newPlaylistName: str) -> None: self.library.renamePlaylist(playlistName, newPlaylistName) self.mainArea.setMainAreaPlaylists(self.library) self.library.update() def deletePlaylist(self, playlistName: str) -> None: self.library.deletePlaylist(playlistName) self.mainArea.setMainAreaPlaylists(self.library) self.library.update() def addWatchedFolder(self, folder: str) -> None: """Adds a folder to the Library class. all mp3 files within the folder and its sub-folders will be added to the library and accessible to the player.""" self.library.addFolder(folder.replace("/", "\\")) self.mainArea.updateView(self.library) def removeWatchedFolder(self, folder: str) -> None: """Removes folder from the library, updates view and stops playback if the current song was in the now-removed folder.""" self.library.deleteFolder(folder) self.mainArea.updateView(self.library) if self.currentSong not in self.library.library: self.songList.updateSongList([], [], "", "") self.player.stop() self.playlist.clear() self.mediaControlArea.updateSongInfo("") self.songList.nowPlayingSong = None self.mainArea.nowPlayingSong = None self.playing = False self.mediaControlArea.updatePlayButton(self.playing, False) def playButtonClick(self, passMove: bool = True) -> None: if not self.playing: if self.playlist.isEmpty(): self.playSongList() return self.playing = True self.player.play() self.mainArea.updateActiveSong(self.playlist.currentIndex()) self.songList.updateActiveSong(self.currentSong) else: self.playing = False self.player.pause() self.mediaControlArea.updatePlayButton(self.playing, passMove) def repeatButtonClick(self) -> None: if self.repeat == 0: self.repeat = 1 self.playlist.setPlaybackMode(QMediaPlaylist.Loop) elif self.repeat == 1: self.repeat = 2 self.playlist.setPlaybackMode(QMediaPlaylist.CurrentItemInLoop) elif self.repeat == 2: self.repeat = 0 self.playlist.setPlaybackMode(QMediaPlaylist.Sequential) self.mediaControlArea.updateRepeatButton(self.repeat) def randomButtonClick(self) -> None: if not self.random: self.random = True self.playlist.setPlaybackMode(QMediaPlaylist.Random) else: self.random = False self.playlist.setPlaybackMode(QMediaPlaylist.Sequential) self.mediaControlArea.updateRandomButton(self.random) def stopButtonClick(self) -> None: self.playing = False self.player.stop() if self.songList.nowPlayingSong is not None: self.songList.nowPlayingSong.clear() if self.mainArea.nowPlayingSong is not None: self.mainArea.nowPlayingSong.clear() self.mediaControlArea.updatePlayButton(self.playing, False) def mute(self) -> None: if not self.player.isMuted(): self.player.setMuted(True) self.mediaControlArea.showMute() else: self.player.setMuted(False) self.volumeChange(self.volume) def volumeChange(self, volume: int) -> None: logVolume = QAudio.convertVolume(volume / 100, QAudio.LogarithmicVolumeScale, QAudio.LinearVolumeScale) * 100 self.player.setVolume(logVolume) self.volume = volume self.mediaControlArea.updateVolumeBar(volume) def songProgressMove(self, position: int) -> None: self.player.setPosition(position) def updateSongProgress(self) -> None: position = self.player.position() if 0 <= position < 2_000_000_000: if self.player.state() > 0: self.mediaControlArea.updateSongProgress(position) if self.playing: self.songList.activeSongPixmap() self.mainArea.activeSongPixmap() else: self.mediaControlArea.updateSongProgress(0) def disconnect(self): self.player.currentMediaChanged.disconnect() self.player.durationChanged.disconnect() self.player.stateChanged.disconnect() self.mediaControlArea.previousButton.click.disconnect() self.mediaControlArea.repeatButton.click.disconnect() self.mediaControlArea.stopButton.click.disconnect() self.mediaControlArea.playButton.click.disconnect() self.mediaControlArea.randomButton.click.disconnect() self.mediaControlArea.nextButton.click.disconnect() self.mediaControlArea.muteButton.click.disconnect() self.mediaControlArea.songProgress.sliderMoved.disconnect() self.mediaControlArea.volumeControl.sliderMoved.disconnect() def close(self) -> None: self.disconnect() self.player.stop() self.mainTimer.stop() self.save() def save(self) -> None: """Called on exit, saves current view, geometry and volume.""" with gzip.open(r"musicplayer\mpdata", "wb") as fh: fh.write(self.MAGIC) toBeWritten = struct.pack(f"<h{len(self.displayedType.encode())}s", len(self.displayedType.encode()), self.displayedType.encode()) fh.write(toBeWritten) fh.write(self.MAGIC) toBeWritten = struct.pack(f"<h{len(self.displayedName.encode())}s", len(self.displayedName.encode()), self.displayedName.encode()) fh.write(toBeWritten) fh.write(self.MAGIC) geo = self.mainWindow.geometry() toBeWritten = struct.pack("<4h", geo.x(), geo.y(), geo.width(), geo.height()) fh.write(toBeWritten) toBeWritten = struct.pack("<h", self.volume) fh.write(toBeWritten) toBeWritten = struct.pack("<h", self.songList.width()) fh.write(toBeWritten) def load(self) -> [bool, tuple]: """Called on startup, loads view, geometry and volume saved on previous run.""" try: with gzip.open(r"musicplayer\mpdata", "rb") as fh: if not fh.read(2) == self.MAGIC: return False length = fh.read(2) length = struct.unpack("<h", length)[0] displayedType = fh.read(length) displayedType = struct.unpack(f"<{length}s", displayedType)[0].decode("utf8") if displayedType in ["artist", "album", "playlist"]: self.displayedType = displayedType if not fh.read(2) == self.MAGIC: return False length = fh.read(2) length = struct.unpack("<h", length)[0] displayedName = fh.read(length) displayedName = struct.unpack(f"<{length}s", displayedName)[0].decode("utf8") if not fh.read(2) == self.MAGIC: return False self.displayedName = displayedName variables = [] for n in range(6): var = fh.read(2) var = struct.unpack("<h", var)[0] variables.append(var) x, y, width, height, volume, songListWidth = variables self.volume = volume self.songListWidth = songListWidth return x, y, width, height except Exception: return False
class FmvManager(QDockWidget, Ui_ManagerWindow): ''' Video Manager ''' def __init__(self, iface, parent=None): super().__init__(parent) self.setupUi(self) self.parent = parent self.iface = iface self._PlayerDlg = None self.meta_reader = [] self.initialPt = [] self.pass_time = 250 self.buf_interval = 2000 self.update_interval = 2000 self.loading = False self.playlist = QMediaPlaylist() self.VManager.viewport().installEventFilter(self) # Context Menu self.VManager.customContextMenuRequested.connect(self.__context_menu) self.removeAct = QAction( QIcon(":/imgFMV/images/mActionDeleteSelected.svg"), QCoreApplication.translate("ManagerDock", "Remove from list"), self, triggered=self.remove) self.VManager.setColumnWidth(1, 250) self.VManager.setColumnWidth(2, 140) self.VManager.setColumnWidth(3, 600) self.VManager.setColumnWidth(4, 600) self.VManager.setColumnWidth(5, 130) self.VManager.verticalHeader().setDefaultAlignment(Qt.AlignHCenter) self.VManager.hideColumn(0) self.videoPlayable = [] self.videoIsStreaming = [] self.dtm_path = parser['GENERAL']['DTM_file'] draw.setValues() self.setAcceptDrops(True) def loadVideosFromSettings(self): # Get Video Manager List VideoList = getVideoManagerList() for load_id in VideoList: filename = s.value(getNameSpace() + "/Manager_List/" + load_id) _, name = os.path.split(filename) folder = getVideoFolder(filename) klv_folder = os.path.join(folder, "klv") exist = os.path.exists(klv_folder) if exist: self.AddFileRowToManager(name, filename, load_id, exist, klv_folder) else: if os.path.isfile(filename): self.AddFileRowToManager(name, filename, load_id) def eventFilter(self, source, event): ''' Event Filter ''' if (event.type() == QEvent.MouseButtonPress and source is self.VManager.viewport() and self.VManager.itemAt(event.pos()) is None): self.VManager.clearSelection() return QDockWidget.eventFilter(self, source, event) @pyqtSlot(QPoint) def __context_menu(self, position): ''' Context Menu Manager Rows ''' if self.VManager.itemAt(position) is None: return menu = QMenu() menu.addAction(self.removeAct) menu.exec_(self.VManager.mapToGlobal(position)) def remove(self): ''' Remove current row ''' if self.loading: return # close video player (safer because it changes playlist internals) if self._PlayerDlg is not None: self._PlayerDlg.close() for cr in self.VManager.selectedItems(): idx = 0 # we browse cells but we need lines, so ignore already deleted rows try: idx = cr.row() except Exception: continue row_id = self.VManager.item(idx, 0).text() row_text = self.VManager.item(idx, 1).text() self.VManager.removeRow(idx) self.videoPlayable.pop(idx) self.videoIsStreaming.pop(idx) self.initialPt.pop(idx) # Remove video to Settings List RemoveVideoToSettings(row_id) # Remove folder if is local RemoveVideoFolder(row_text) if self.meta_reader[idx] is not None: self.meta_reader[idx].dispose() self.meta_reader.pop(idx) # remove from playlist self.playlist.removeMedia(idx) def closePlayer(self): ''' Close FMV ''' try: self._PlayerDlg.close() except Exception: None def closeFMV(self): ''' Close FMV ''' try: self._PlayerDlg.close() except Exception: None self.close() return def openStreamDialog(self): ''' Open Stream Dialog ''' self.OpenStream = OpenStream(self.iface, parent=self) self.OpenStream.setWindowFlags(Qt.Window | Qt.WindowCloseButtonHint) self.OpenStream.exec_() return def openMuiltiplexorDialog(self): ''' Open Multiplexor Dialog ''' self.Muiltiplexor = Multiplexor(self.iface, parent=self, Exts=ast.literal_eval( parser.get("FILES", "Exts"))) self.Muiltiplexor.setWindowFlags(Qt.Window | Qt.WindowCloseButtonHint) self.Muiltiplexor.exec_() return def AddFileRowToManager(self, name, filename, load_id=None, islocal=False, klv_folder=None): ''' Add file Video to new Row ''' # We limit the number of videos due to the buffer self.loading = True if self.VManager.rowCount() > 5: qgsu.showUserAndLogMessage(QCoreApplication.translate( "ManagerDock", "You must delete some video from the list before adding a new one" ), level=QGis.Warning) self.loading = False return self.islocal = islocal self.klv_folder = klv_folder w = QWidget() layout = QVBoxLayout() pbar = QProgressBar() layout.addWidget(pbar) w.setLayout(layout) rowPosition = self.VManager.rowCount() self.videoPlayable.append(False) pbar.setGeometry(0, 0, 300, 30) pbar.setValue(0) pbar.setMaximumHeight(30) if load_id is None: row_id = 0 if rowPosition != 0: row_id = int(self.VManager.item(rowPosition - 1, 0).text()) + 1 else: row_id = load_id self.VManager.insertRow(rowPosition) self.VManager.setItem(rowPosition, 0, QTableWidgetItem(str(row_id))) self.VManager.setItem(rowPosition, 1, QTableWidgetItem(name)) self.VManager.setItem( rowPosition, 2, QTableWidgetItem( QCoreApplication.translate("ManagerDock", "Loading"))) self.VManager.setItem(rowPosition, 3, QTableWidgetItem(filename)) self.VManager.setItem(rowPosition, 4, QTableWidgetItem("-")) self.VManager.setCellWidget(rowPosition, 5, w) self.VManager.setVisible(False) self.VManager.horizontalHeader().setStretchLastSection(True) self.VManager.setVisible(True) # resolve if it is a stream if "://" in filename: self.videoIsStreaming.append(True) else: self.videoIsStreaming.append(False) if not self.videoIsStreaming[-1]: # Disable row if not exist video file if not os.path.exists(filename): self.ToggleActiveRow(rowPosition, value="Missing source file") for j in range(self.VManager.columnCount()): try: self.VManager.item(rowPosition, j).setFlags(Qt.NoItemFlags | Qt.ItemIsEnabled) self.VManager.item(rowPosition, j).setBackground( QColor(211, 211, 211)) except Exception: self.VManager.cellWidget(rowPosition, j).setStyleSheet( "background-color:rgb(211,211,211);") pass self.loading = False return pbar.setValue(30) info = FFMpeg().probe(filename) if info is None: qgsu.showUserAndLogMessage( QCoreApplication.translate("ManagerDock", "Failed loading FFMPEG ! ")) klvIdx = getKlvStreamIndex(filename, islocal) # init non-blocking metadata buffered reader self.meta_reader.append( BufferedMetaReader(filename, klv_index=klvIdx, pass_time=self.pass_time, interval=self.buf_interval)) pbar.setValue(60) try: # init point we can center the video on self.initialPt.append( getVideoLocationInfo(filename, islocal, klv_folder)) if not self.initialPt[rowPosition]: self.VManager.setItem( rowPosition, 4, QTableWidgetItem( QCoreApplication.translate( "ManagerDock", "Start location not available."))) self.ToggleActiveRow(rowPosition, value="Video not applicable") pbar.setValue(100) else: self.VManager.setItem( rowPosition, 4, QTableWidgetItem(self.initialPt[rowPosition][2])) pbar.setValue(90) self.videoPlayable[rowPosition] = True except Exception: qgsu.showUserAndLogMessage( QCoreApplication.translate( "ManagerDock", "This video doesn't have Metadata ! ")) pbar.setValue(100) self.ToggleActiveRow(rowPosition, value="Video not applicable") else: self.meta_reader.append(StreamMetaReader(filename)) qgsu.showUserAndLogMessage("", "StreamMetaReader initialized.", onlyLog=True) self.initialPt.append(None) self.videoPlayable[rowPosition] = True url = "" if self.videoIsStreaming[-1]: # show video from splitter (port +1) oldPort = filename.split(":")[2] newPort = str(int(oldPort) + 10) proto = filename.split(":")[0] url = QUrl(proto + "://127.0.0.1:" + newPort) else: url = QUrl.fromLocalFile(filename) self.playlist.addMedia(QMediaContent(url)) if self.videoPlayable[rowPosition]: pbar.setValue(100) if islocal: self.ToggleActiveRow(rowPosition, value="Ready Local") else: self.ToggleActiveRow(rowPosition, value="Ready") # Add video to settings list AddVideoToSettings(str(row_id), filename) self.loading = False def openVideoFileDialog(self): ''' Open video file dialog ''' if self.loading: return Exts = ast.literal_eval(parser.get("FILES", "Exts")) filename, _ = askForFiles(self, QCoreApplication.translate( "ManagerDock", "Open video"), exts=Exts) if filename: if not self.isFileInPlaylist(filename): _, name = os.path.split(filename) self.AddFileRowToManager(name, filename) else: qgsu.showUserAndLogMessage( QCoreApplication.translate( "ManagerDock", "File is already loaded in playlist: " + filename)) return def isFileInPlaylist(self, filename): mcount = self.playlist.mediaCount() for x in range(mcount): if filename in self.playlist.media(x).canonicalUrl().toString(): return True return False def PlayVideoFromManager(self, model): ''' Play video from manager dock. Manager row double clicked ''' # Don't enable Play if video doesn't have metadata if not self.videoPlayable[model.row()]: return try: if self._PlayerDlg.isVisible(): self._PlayerDlg.close() except Exception: None path = self.VManager.item(model.row(), 3).text() text = self.VManager.item(model.row(), 1).text() folder = getVideoFolder(text) klv_folder = os.path.join(folder, "klv") exist = os.path.exists(klv_folder) # First time we open the player if self._PlayerDlg is None: if exist: self.CreatePlayer(path, self.update_interval, model.row(), islocal=True, klv_folder=klv_folder) else: self.CreatePlayer(path, self.update_interval, model.row()) self.SetupPlayer(model.row()) if exist: self._PlayerDlg.playFile(path, islocal=True, klv_folder=klv_folder) else: self._PlayerDlg.playFile(path) def SetupPlayer(self, row): ''' Play video from manager dock. Manager row double clicked ''' self.ToggleActiveRow(row) self.playlist.setCurrentIndex(row) # qgsu.CustomMessage("QGIS FMV", path, self._PlayerDlg.fileName, icon="Information") # if path != self._PlayerDlg.fileName: self._PlayerDlg.setMetaReader(self.meta_reader[row]) self.ToggleActiveFromTitle() self._PlayerDlg.show() self._PlayerDlg.activateWindow() # zoom to map zone curAuthId = self.iface.mapCanvas().mapSettings().destinationCrs( ).authid() if self.initialPt[row][1] is not None and self.initialPt[row][ 0] is not None: map_pos = QgsPointXY(self.initialPt[row][1], self.initialPt[row][0]) if curAuthId != "EPSG:4326": trgCode = int(curAuthId.split(":")[1]) xform = QgsCoordinateTransform( QgsCoordinateReferenceSystem(4326), QgsCoordinateReferenceSystem(trgCode), QgsProject().instance()) map_pos = xform.transform(map_pos) self.iface.mapCanvas().setCenter(map_pos) self.iface.mapCanvas().zoomScale(50000) def CreatePlayer(self, path, interval, row, islocal=False, klv_folder=None): ''' Create Player ''' self._PlayerDlg = QgsFmvPlayer(self.iface, path, interval, parent=self, meta_reader=self.meta_reader[row], pass_time=self.pass_time, islocal=islocal, klv_folder=klv_folder) self._PlayerDlg.player.setPlaylist(self.playlist) self._PlayerDlg.setWindowFlags(Qt.Dialog | Qt.WindowCloseButtonHint) self._PlayerDlg.show() self._PlayerDlg.activateWindow() def ToggleActiveFromTitle(self): ''' Toggle Active video status ''' column = 2 for row in range(self.VManager.rowCount()): if self.VManager.item(row, column) is not None: v = self.VManager.item(row, column).text() text = self.VManager.item(row, 1).text() if v == "Playing": folder = getVideoFolder(text) klv_folder = os.path.join(folder, "klv") exist = os.path.exists(klv_folder) if exist: self.ToggleActiveRow(row, value="Ready Local") else: self.ToggleActiveRow(row, value="Ready") return def ToggleActiveRow(self, row, value="Playing"): ''' Toggle Active row manager video status ''' self.VManager.setItem( row, 2, QTableWidgetItem(QCoreApplication.translate("ManagerDock", value))) return def closeEvent(self, _): ''' Close Manager Event ''' FmvDock = qgis.utils.plugins[getNameSpace()] FmvDock._FMVManager = None try: if self._PlayerDlg.isVisible(): self._PlayerDlg.close() except Exception: None return def dragEnterEvent(self, e): check = True Exts = ast.literal_eval(parser.get("FILES", "Exts")) if e.mimeData().hasUrls(): for url in e.mimeData().urls(): fileIsOk = False for ext in Exts: if url.fileName().lower().endswith(ext): fileIsOk = True break if not fileIsOk: check = False break # Only accept if all files match a required extension if check: e.acceptProposedAction() # Ignore and stop propagation else: e.setDropAction(Qt.IgnoreAction) e.accept() def dropEvent(self, e): for url in e.mimeData().urls(): # local files if "file:///" in url.toString(): if not self.isFileInPlaylist(url.toString()[8:]): self.AddFileRowToManager(url.fileName(), url.toString()[8:]) # network drives else: if not self.isFileInPlaylist(url.toString()[5:]): self.AddFileRowToManager(url.fileName(), url.toString()[5:])
class MusicPlayer(QWidget): def __init__(self, parent=None): QWidget.__init__(self, parent) self.options = self.parent().options self.ffmpeg = self.options['paths']['ffmpeg_bin'] self.thumbnails = self.options['paths']['thumbnails'] self.thumb_width = self.options['thumbnail']['width'] self.thumb_height = self.options['thumbnail']['height'] self.jumping = False self.blocked = False self.durations = {} self.playback_value = 0 self.text = ["None", "Repeat", "Random"] self.playlist_list = [] self.values = [ QMediaPlaylist.Loop, QMediaPlaylist.CurrentItemInLoop, QMediaPlaylist.Random ] # Thumbnail widget self.image_label = QLabel() # Control widgets self.playButton = QToolButton(clicked=self.play) self.playButton.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay)) self.stopButton = QToolButton(clicked=self.stop) self.stopButton.setIcon(self.style().standardIcon(QStyle.SP_MediaStop)) self.stopButton.setEnabled(False) self.playbackButton = QToolButton(clicked=self.playback_mode) self.playbackButton.setText(self.text[0]) self.nextButton = QToolButton(clicked=self.next_song) self.nextButton.setIcon(self.style().standardIcon( QStyle.SP_MediaSkipForward)) self.previousButton = QToolButton(clicked=self.previous_song) self.previousButton.setIcon(self.style().standardIcon( QStyle.SP_MediaSkipBackward)) self.muteButton = QToolButton(clicked=self.mute_clicked) self.muteButton.setIcon(self.style().standardIcon( QStyle.SP_MediaVolume)) self.volumeSlider = QSlider(Qt.Horizontal, sliderMoved=self.change_volume) self.volumeSlider.setRange(0, 100) self.volumeSlider.setPageStep(1) self.volumeSlider.setValue(50) # Player and playlist setup self.player = QMediaPlayer() self.player.setVolume(50) self.player.stateChanged.connect(self.waveform) self.playlist = QMediaPlaylist() self.playlist.setPlaybackMode(self.values[0]) self.playlist.setCurrentIndex(1) self.player.setPlaylist(self.playlist) self.playlistModel = PlaylistModel() self.playlistModel.setPlaylist(self.playlist) self.playlistView = QListView() self.playlistView.setModel(self.playlistModel) self.playlistView.setCurrentIndex( self.playlistModel.index(self.playlist.currentIndex(), 0)) self.playlistView.activated.connect(self.jump) self.playlistView.setContextMenuPolicy(Qt.CustomContextMenu) self.playlistView.customContextMenuRequested.connect( self.list_view_menu) self.playlist.currentIndexChanged.connect( lambda position: self.change_thumbnail(position)) song_search = QLineEdit() song_search.textChanged.connect(self.search) song_search.setClearButtonEnabled(True) # Playlist self.playlist_name = QComboBox() self.playlist_name.currentTextChanged.connect(self.switch_playlist) self.refresh_lists() self.up_button = QToolButton(clicked=self.move_up) self.up_button.setIcon(self.style().standardIcon(QStyle.SP_ArrowUp)) self.down_button = QToolButton(clicked=self.move_down) self.down_button.setIcon(self.style().standardIcon( QStyle.SP_ArrowDown)) # Sound wave widget self.wave_graphic = WaveGraphic(self) #self.wave_graphic.hide() # Testing slider again self.slider = QSlider(Qt.Horizontal) self.slider.setRange(0, self.player.duration() / 1000) self.slider.sliderMoved.connect(self.seek) self.player.durationChanged.connect(self.durationChanged) self.player.positionChanged.connect(self.positionChanged) # Shortcuts QShortcut(QKeySequence(Qt.CTRL + Qt.Key_F), self, song_search.setFocus) # Layouts setup playlist_layout = QHBoxLayout() playlist_layout.addWidget(self.playlist_name) playlist_layout.addWidget(self.up_button) playlist_layout.addWidget(self.down_button) control_layout = QHBoxLayout() control_layout.setContentsMargins(0, 0, 0, 0) control_layout.addWidget(self.stopButton) control_layout.addWidget(self.previousButton) control_layout.addWidget(self.playButton) control_layout.addWidget(self.nextButton) control_layout.addWidget(self.muteButton) control_layout.addWidget(self.volumeSlider) control_layout.addWidget(self.playbackButton) display_layout = QVBoxLayout() display_layout.addWidget(song_search) display_layout.addWidget(self.playlistView) display_layout.addLayout(playlist_layout) music_layout = QVBoxLayout() music_layout.addWidget(self.image_label) music_layout.addWidget(self.slider) music_layout.addLayout(control_layout) main_layout = QHBoxLayout() main_layout.addLayout(music_layout) main_layout.addLayout(display_layout) main_2_layout = QVBoxLayout() main_2_layout.addLayout(main_layout) main_2_layout.addWidget(self.wave_graphic) self.setLayout(main_2_layout) def waveform(self, status): if status == QMediaPlayer.PlayingState: self.wave_graphic.start() elif status == QMediaPlayer.PausedState: self.wave_graphic.pause() else: self.wave_graphic.stop() def list_view_menu(self, point): menu = QMenu("Menu", self) recommend_action = QAction('&Recommend Songs', self) menu.addAction(recommend_action) # TODO: [FEATURE] add rename song recommend_action.triggered.connect( lambda: self.parent().call_download_manager(self.get_links())) rename_action = QAction('&Rename', self) menu.addAction(rename_action) # TODO: [FEATURE] add rename song rename_action.triggered.connect(lambda: print("rename")) # Show the context menu. menu.exec_(self.playlistView.mapToGlobal(point)) def get_links(self): title = self.playlistView.selectedIndexes()[0].data() link = getYoutubeURLFromSearch(title) if len(link) > 1: return youtube_recommendations(link) def move_up(self): index = self.playlistView.currentIndex().row() if index - 1 >= 0: item, above = self.playlist.media(index), self.playlist.media( index - 1) self.playlist.removeMedia(index) self.playlist.removeMedia(index - 1) self.playlist.insertMedia(index - 1, item) self.playlist.insertMedia(index, above) self.blocked = True self.playlistView.setCurrentIndex( self.playlistModel.index(index - 1, 0)) self.blocked = False self.stop() def move_down(self): index = self.playlistView.currentIndex().row() if index + 1 <= self.playlistModel.rowCount(): item, below = self.playlist.media(index), self.playlist.media( index + 1) self.playlist.removeMedia(index + 1) self.playlist.removeMedia(index) self.playlist.insertMedia(index, below) self.playlist.insertMedia(index + 1, item) self.blocked = True self.playlistView.setCurrentIndex( self.playlistModel.index(index + 1, 0)) self.blocked = False self.stop() def search(self, part_of_song): for index in range(self.playlistModel.rowCount()): item = self.playlistModel.data(self.playlistModel.index( index, 0)).lower() self.playlistView.setRowHidden(index, part_of_song.lower() not in item) def change_thumbnail(self, position=0): self.playlistView.setCurrentIndex(self.playlistModel.index( position, 0)) song = self.playlistView.selectedIndexes()[0].data() if self.wave_graphic.is_song_cached(song): self.wave_graphic.load_waves(song) else: wc_ = WaveConverter(song, self.wave_graphic.set_wav) wc_.convert() if self.playlistView.currentIndex().data() is None or self.blocked: return max_ratio = 0 img = None for item in listdir(self.thumbnails): if item.endswith('.jpg'): ratio = similar( item, self.playlistView.currentIndex().data().replace( '.mp3', '.jpg')) if ratio > max_ratio: max_ratio = ratio img = item if img: p = QPixmap(self.thumbnails + img) self.image_label.setPixmap( p.scaled(self.thumb_width, self.thumb_height, Qt.KeepAspectRatio)) def switch_playlist(self, current_text): self.playlist.clear() if current_text == "No Playlist": self.refresh() else: if read_playlist(current_text): songs = read_playlist(current_text).split('\n') for song in songs: self.playlist.addMedia( QMediaContent(QUrl.fromLocalFile(song))) def refresh(self): # Change it so it will go to same song. if self.playlist_name.currentText() != "No Playlist": return paths = fetch_options()['paths']['music_path'].split(';') current_songs = [ self.playlistModel.data(self.playlistModel.index(row, 0)) for row in range(self.playlistModel.rowCount()) ] for path in paths: if not path: continue for item in listdir(path): if isfile(join(path, item)) and item.endswith(".mp3") and ( item not in current_songs): self.playlist.addMedia( QMediaContent(QUrl.fromLocalFile(join(path, item)))) def refresh_lists(self): path = fetch_options()['paths']['playlist'] self.playlist_name.clear() self.playlist_list = ["No Playlist"] self.playlist_name.addItem("No Playlist") for item in listdir(path): if isfile(join(path, item)) and item.endswith(".lst"): self.playlist_list.append(item.split('.')[0]) self.playlist_name.addItem(item.split('.')[0]) def playback_mode(self): # Normal -> Loop -> Random def up_value(value, max_value=3): if value + 1 == max_value: return 0 return value + 1 self.playback_value = up_value(self.playback_value) self.playlist.setPlaybackMode(self.values[self.playback_value]) self.playbackButton.setText(self.text[self.playback_value]) def jump(self, index): if index.isValid() and not self.blocked: self.playlist.setCurrentIndex(index.row()) self.jumping = True self.play() def play(self): if self.blocked: return if self.player.state() != QMediaPlayer.PlayingState or self.jumping: self.player.play() self.playButton.setIcon(self.style().standardIcon( QStyle.SP_MediaPause)) self.jumping = False else: self.player.pause() self.playButton.setIcon(self.style().standardIcon( QStyle.SP_MediaPlay)) self.stopButton.setEnabled(True) def change_volume(self, value): self.player.setVolume(value) def stop(self): if self.player.state() != QMediaPlayer.StoppedState: self.player.stop() self.playButton.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay)) self.stopButton.setEnabled(False) def next_song(self): self.playlist.next() self.playlistView.setCurrentIndex( self.playlistModel.index(self.playlist.currentIndex(), 0)) def previous_song(self): self.playlist.previous() self.playlistView.setCurrentIndex( self.playlistModel.index(self.playlist.currentIndex(), 0)) def mute_clicked(self): self.player.setMuted(not self.player.isMuted()) self.muteButton.setIcon(self.style().standardIcon( QStyle.SP_MediaVolume if not self.player.isMuted() else QStyle. SP_MediaVolumeMuted)) def durationChanged(self, duration): duration /= 1000 self.slider.setMaximum(duration) def positionChanged(self, progress): progress /= 1000 if not self.slider.isSliderDown(): self.slider.setValue(progress) def seek(self, seconds): self.player.setPosition(seconds * 1000)
class QmyMainWindow(QMainWindow): def __init__(self, parent=None): super().__init__(parent) #调用父类构造函数,创建窗体 self.ui = Ui_MainWindow() #创建UI对象 self.ui.setupUi(self) #构造UI界面 ## self.setWindowTitle("Demo10_1,音乐播放器") self.player = QMediaPlayer(self) self.playlist = QMediaPlaylist(self) self.player.setPlaylist(self.playlist) self.playlist.setPlaybackMode(QMediaPlaylist.Loop) #循环模式 self.__duration = "" #文件总时间长度 self.__curPos = "" #当前播放到位置 self.player.stateChanged.connect(self.do_stateChanged) self.player.positionChanged.connect(self.do_positionChanged) self.player.durationChanged.connect(self.do_durationChanged) self.playlist.currentIndexChanged.connect(self.do_currentChanged) ## ==============自定义功能函数============ ## ===============event 处理函数========== def closeEvent(self, event): ##窗体关闭时 ## 窗口关闭时不能自动停止播放,需手动停止 if (self.player.state() == QMediaPlayer.PlayingState): self.player.stop() ## ==========由connectSlotsByName() 自动连接的槽函数================== # 播放列表管理 @pyqtSlot() ##添加文件 def on_btnAdd_clicked(self): ## curPath=os.getcwd() #获取系统当前目录 ## curPath=QDir.homePath() curPath = QDir.currentPath() dlgTitle = "选择音频文件" filt = "音频文件(*.mp3 *.wav *.wma);;所有文件(*.*)" fileList, flt = QFileDialog.getOpenFileNames(self, dlgTitle, curPath, filt) count = len(fileList) if count < 1: return filename = fileList[0] fileInfo = QFileInfo(filename) #文件信息 QDir.setCurrent(fileInfo.absolutePath()) #重设当前路径 for i in range(count): filename = fileList[i] fileInfo.setFile(filename) song = QMediaContent(QUrl.fromLocalFile(filename)) self.playlist.addMedia(song) #添加播放媒体 ## basename=os.path.basename(filename) #文件名和后缀 basename = fileInfo.baseName() self.ui.listWidget.addItem(basename) #添加到界面文件列表 if (self.player.state() != QMediaPlayer.PlayingState): self.playlist.setCurrentIndex(0) self.player.play() @pyqtSlot() ##移除一个文件 def on_btnRemove_clicked(self): pos = self.ui.listWidget.currentRow() item = self.ui.listWidget.takeItem(pos) #python会自动删除 if (self.playlist.currentIndex() == pos): #是当前播放的曲目 nextPos = 0 if pos >= 1: nextPos = pos - 1 self.playlist.removeMedia(pos) #从播放列表里移除 if self.ui.listWidget.count() > 0: #剩余个数 self.playlist.setCurrentIndex(nextPos) self.do_currentChanged(nextPos) #当前曲目变化 else: self.player.stop() self.ui.LabCurMedia.setText("无曲目") else: self.playlist.removeMedia(pos) @pyqtSlot() ##清空播放列表 def on_btnClear_clicked(self): self.playlist.clear() #清空播放列表 self.ui.listWidget.clear() self.player.stop() #停止播放 ## @pyqtSlot() ##双击时切换播放文件 def on_listWidget_doubleClicked(self, index): rowNo = index.row() #行号 self.playlist.setCurrentIndex(rowNo) self.player.play() # 播放控制 @pyqtSlot() ##播放 def on_btnPlay_clicked(self): if (self.playlist.currentIndex() < 0): self.playlist.setCurrentIndex(0) self.player.play() @pyqtSlot() ##暂停 def on_btnPause_clicked(self): self.player.pause() @pyqtSlot() ##停止 def on_btnStop_clicked(self): self.player.stop() @pyqtSlot() ##上一曲目 def on_btnPrevious_clicked(self): self.playlist.previous() @pyqtSlot() ##下一曲目 def on_btnNext_clicked(self): self.playlist.next() @pyqtSlot() ##静音控制 def on_btnSound_clicked(self): mute = self.player.isMuted() self.player.setMuted(not mute) if mute: self.ui.btnSound.setIcon(QIcon(":/icons/images/volumn.bmp")) else: self.ui.btnSound.setIcon(QIcon(":/icons/images/mute.bmp")) @pyqtSlot(int) ##调节音量 def on_sliderVolumn_valueChanged(self, value): self.player.setVolume(value) @pyqtSlot(int) ##文件进度调控 def on_sliderPosition_valueChanged(self, value): self.player.setPosition(value) ## =============自定义槽函数=============================== def do_stateChanged(self, state): ##播放器状态变化 self.ui.btnPlay.setEnabled(state != QMediaPlayer.PlayingState) self.ui.btnPause.setEnabled(state == QMediaPlayer.PlayingState) self.ui.btnStop.setEnabled(state == QMediaPlayer.PlayingState) def do_positionChanged(self, position): ##当前文件播放位置变化,更新进度显示 if (self.ui.sliderPosition.isSliderDown()): #在拖动滑块调整进度 return self.ui.sliderPosition.setSliderPosition(position) secs = position / 1000 #秒 mins = secs / 60 #分钟 secs = secs % 60 #余数秒 self.__curPos = "%d:%d" % (mins, secs) self.ui.LabRatio.setText(self.__curPos + "/" + self.__duration) def do_durationChanged(self, duration): ##文件时长变化 self.ui.sliderPosition.setMaximum(duration) secs = duration / 1000 #秒 mins = secs / 60 #分钟 secs = secs % 60 #余数秒 self.__duration = "%d:%d" % (mins, secs) self.ui.LabRatio.setText(self.__curPos + "/" + self.__duration) def do_currentChanged(self, position): ##playlist当前曲目变化 self.ui.listWidget.setCurrentRow(position) item = self.ui.listWidget.currentItem() #QListWidgetItem if (item != None): self.ui.LabCurMedia.setText(item.text())
class DPlayerCore(QWidget): def __init__(self): """Initialize player and load playlist if any.""" super().__init__() self.player = QMediaPlayer() self.playlist = QMediaPlaylist() self.player.setPlaylist(self.playlist) self.shuffling = False self.repeatingPlaylist = False self.repeatingSong = False self.musicOrder = [] self.loadPlaylist(QUrl( 'file://{}/lastListened.m3u'.format(os.getcwd()))) self.lyricsApi = 'http://api.musixmatch.com/ws/1.1/' self.lyricsApiKey = '4b364f0652e471aa50813a22cdf830ea' self.lastFMapi = 'http://ws.audioscrobbler.com/2.0/' self.lastFMapikey = '052c43a00a4fc294bb3c9e0c38bdf710' self.lastFMsecret = '14c66392fa9c6c142a41ccc2b0674e19' self.username = None self.password = None self.network = None self.error = 'Something went wrong! Try again later.' def play(self): """Start the player.""" self.player.play() def pause(self): """Pause the player.""" self.player.pause() def stop(self): """Stop the player.""" self.player.stop() def previous(self): """Play previous song.""" self.playlist.previous() def next(self): """Play next song.""" self.playlist.next() def mute(self): """Mute the player.""" self.player.setMuted(True) def unmute(self): """Unmute the player.""" self.player.setMuted(False) def setVolume(self, value): """Set player's volume to value.""" self.player.setVolume(value) def add(self, fileNames): """Add fileNames to the playlist.""" for name in fileNames: url = QUrl.fromLocalFile(QFileInfo(name).absoluteFilePath()) self.playlist.addMedia(QMediaContent(url)) self.musicOrder.append([name]) self.added(len(fileNames)) def added(self, added): """Saves music info in musicOrder.""" for name, index in zip( self.musicOrder[self.playlist.mediaCount() - added:], range(self.playlist.mediaCount() - added, len(self.musicOrder))): name = name[0] artist = self.getArtist(name)[0] title = self.getTitle(name)[0] album = self.getAlbum(name)[0] seconds = self.getDuration(name) duration = QTime(0, seconds // 60, seconds % 60) duration = duration.toString('mm:ss') self.musicOrder[index].extend( [artist, title, album, duration]) def remove(self, songIndexes): """Remove songIndexes from the playlist.""" for index in songIndexes: self.songChanged = True del self.musicOrder[index] self.playlist.removeMedia(index) self.songChanged = False def savePlaylist(self, path): """Save playlist to path.""" if path.toString()[len(path.toString()) - 4:] != '.m3u': path = QUrl('{}.m3u'.format(path.toString())) self.playlist.save(path, 'm3u') def loadPlaylist(self, path): """Load playlist form path.""" count = self.playlist.mediaCount() self.playlist.load(path) for index in range(count, self.playlist.mediaCount()): self.musicOrder.append( [self.playlist.media(index).canonicalUrl().path()]) self.added(self.playlist.mediaCount() - count) def clearPlaylist(self): """Delete all songs in the playlist.""" self.playlist.clear() self.musicOrder = [] def shuffle(self, value): """Shuffle playlist if value = True.""" self.shuffling = value if self.repeatingSong: return if self.shuffling: self.playlist.setPlaybackMode(QMediaPlaylist.Random) elif self.repeatingPlaylist: self.playlist.setPlaybackMode(QMediaPlaylist.Loop) else: self.playlist.setPlaybackMode(QMediaPlaylist.Sequential) def repeatPlaylist(self, value): """Repeat playlist after the last song is finished if value = True.""" self.repeatingPlaylist = value if self.repeatingSong or self.shuffling: return if self.repeatingPlaylist: self.playlist.setPlaybackMode(QMediaPlaylist.Loop) else: self.playlist.setPlaybackMode(QMediaPlaylist.Sequential) def repeatSong(self, value): """Repeat current song if value = True.""" self.repeatingSong = value if self.repeatingSong: self.playlist.setPlaybackMode(QMediaPlaylist.CurrentItemInLoop) elif self.shuffling: self.playlist.setPlaybackMode(QMediaPlaylist.Random) elif self.repeatingPlaylist: self.playlist.setPlaybackMode(QMediaPlaylist.Loop) else: self.playlist.setPlaybackMode(QMediaPlaylist.Sequential) def sort(self, column, order): """Sort playlist by column in order.""" ordered = sorted(self.musicOrder, key=itemgetter(column + 1), reverse=order) self.clearPlaylist() for song in ordered: url = QUrl.fromLocalFile(QFileInfo(song[0]).absoluteFilePath()) self.playlist.addMedia(QMediaContent(url)) self.musicOrder = ordered def findLyrics(self, index): """Returns lyrics for song at index.""" if self.musicOrder[index][2] == 'Unknown': return 'Unknown song.' searchSong = '{}track.search?q_track={}'.format( self.lyricsApi, self.musicOrder[index][2].replace(' ', '%20')) if self.musicOrder[index][1] != 'Unknown': searchSong = '{}&q_artist={}'.format( searchSong, self.musicOrder[index][1].replace(' ', '%20')) searchSong = '{}&f_has_lyrics=1&apikey={}'.format( searchSong, self.lyricsApiKey) try: requestSong = requests.get(searchSong) except requests.ConnectionError: return self.error songJson = requestSong.json() if requestSong.status_code != 200 or \ songJson['message']['header']['available'] == 0: return self.error songId = songJson[ 'message']['body']['track_list'][0]["track"]["track_id"] searchLyrics = '{}track.lyrics.get?track_id={}&apikey={}'.format( self.lyricsApi, songId, self.lyricsApiKey) try: requestLyrics = requests.get(searchLyrics) except requests.ConnectionError: return self.error if requestLyrics.status_code != 200 or \ songJson['message']['header']['available'] == 0: return self.error return requestLyrics.json()[ 'message']['body']['lyrics']['lyrics_body'][:-58] # spam and bacon def findInfo(self, index): """Returns info about artist and album for index if any.""" info = [] if self.musicOrder[index][1] != 'Unknown': artist = self.artistInfo(self.musicOrder[index][1]) if artist != self.error: info += artist if self.musicOrder[index][1] != 'Unknown' and \ self.musicOrder[index][3] != 'Unknown': album = self.albumInfo(self.musicOrder[index][1], self.musicOrder[index][3]) if album != self.error: info += album if info: return info else: return ['Unknown artist and song!'] def artistInfo(self, artist): """Returns info about artist if any.""" try: response = requests.get( ('{}/?method=artist.getinfo&artist={}&api_key={}&' 'format=json&autocorrect=1').format( self.lastFMapi, artist, self.lastFMapikey)) except Exception: return self.error if response.status_code != 200: return self.error artist = 'Artist: {}'.format(response.json()['artist']['name']) bio = 'Bio: {}'.format( response.json()['artist']['bio']['summary'].replace('.', '.\n')) spam = bio.find('<a') bio = bio[:spam] return [artist, bio] def albumInfo(self, artist, album): """Returns info about album if any.""" try: response = requests.get( ('{}/?method=album.getinfo&artist={}&album={}&api_key={}&' 'format=json&autocorrect=1').format( self.lastFMapi, artist, album, self.lastFMapikey)) except Exception: return self.error if response.status_code != 200 or \ 'album' not in response.json().keys(): return self.error album = 'Album: {}'.format(response.json()['album']['name']) tracks = ['Tracks: '] t = response.json()['album']['tracks']['track'] for track, index in zip(t, range(len(t))): tracks.append('{}. {}'.format(index + 1, track['name'])) info = [album, '\n'.join(tracks)] if 'wiki' in response.json()['album'].keys(): wiki = response.json()['album']['wiki'] if 'published' in wiki.keys(): info.append('Published: {}'.format(wiki['published'])) if 'summary' in wiki.keys(): summary = wiki['summary'].replace('.', '.\n') spam = summary.find('<a') info.append('Summary: {}'.format(summary[:spam])) if 'Musical style' in wiki.keys(): info.append('Musical style: {}'.format(wiki['Musical style'])) return info def login(self, username, password): """Creates lastFM network.""" self.username = username self.password = pylast.md5(password) try: self.network = pylast.LastFMNetwork(api_key=self.lastFMapikey, api_secret=self.lastFMsecret, username=self.username, password_hash=self.password) except Exception: self.username = None self.password = None self.network = None return False return True def logout(self): """Destoys lastFM network and current user info.""" self.username = None self.password = None self.network = None def loveTrack(self, index): """Love track at index in lastFM.""" if self.network is None: return False track = self.network.get_track(self.musicOrder[index][1], self.musicOrder[index][2]) try: track.love() except Exception: return False return True def unloveTrack(self, index): """Unlove track at index in lastFM.""" if self.network is None: return False track = self.network.get_track(self.musicOrder[index][1], self.musicOrder[index][2]) try: track.unlove() except Exception: return False return True def isMuted(self): """Returns True if player is muted.""" return self.player.isMuted() def getArtist(self, song): """Returns the artist of song.""" if song[-4:] == '.mp3': obj = EasyID3(song) if 'artist' in obj.keys(): return obj['artist'] elif 'TAG' in mediainfo(song).keys(): obj = mediainfo(song)['TAG'] if 'artist' in obj.keys(): return [obj['artist']] elif 'ARTIST' in obj.keys(): return [obj['ARTIST']] else: return ['Unknown'] else: return ['Unknown'] def getTitle(self, song): """Returns the title of song.""" if song[-4:] == '.mp3': obj = EasyID3(song) if 'title' in obj.keys(): return obj['title'] elif 'TAG' in mediainfo(song).keys(): obj = mediainfo(song)['TAG'] if 'title' in obj.keys(): return [obj['title']] elif 'TITLE' in obj.keys(): return [obj['TITLE']] else: return ['Unknown'] else: return ['Unknown'] def getAlbum(self, song): """Returns the album of song.""" if song[-4:] == '.mp3': obj = EasyID3(song) if 'album' in obj.keys(): return obj['album'] elif 'TAG' in mediainfo(song).keys(): obj = mediainfo(song)['TAG'] if 'album' in obj.keys(): return [obj['album']] elif 'ALBUM' in obj.keys(): return [obj['ALBUM']] else: return ['Unknown'] else: return ['Unknown'] def getDuration(self, song): """Returns the duration of song.""" if song[-4:] == '.mp3': return MP3(song).info.length return int(float(mediainfo(song)['duration']))
class YtdlMusic(QMainWindow, Ui_MainWindow): def __init__(self, *args, **kwargs): QMainWindow.__init__(self, *args, **kwargs) self.setupUi(self) self.setWindowIcon(QIcon(LOCAL_DIR + '/ytdl_music.svg')) self.player = QMediaPlayer() self.player.isSeekable() self.playList = QMediaPlaylist() self.playListData = [] self.player.setPlaylist(self.playList) self.currentPos = 0 self.currentTrackDuration = '0:00:00' self.player.positionChanged.connect(self.positionChanged) self.player.durationChanged.connect(self.durationChanged) self.playList.currentIndexChanged.connect(self.playlistPosChanged) self.playlistTable.itemDoubleClicked.connect(self.changeTrack) self.playlistTable.itemSelectionChanged.connect(self.selectedTracks) # self.timeSlider.valueChanged.connect(self.setPosition) self.addBtn.clicked.connect(self.addDialog) self.removeBtn.clicked.connect(self.delTracks) self.playBtn.clicked.connect(self.playPause) self.stopBtn.clicked.connect(self.stop) self.prevBtn.clicked.connect(self.playList.previous) self.nextBtn.clicked.connect(self.playList.next) self.playlistTable.setHorizontalHeaderLabels([ '', _translate('MainWindow', 'Channel'), _translate('MainWindow', 'Title') ]) header = self.playlistTable.horizontalHeader() header.setSectionResizeMode(1, QHeaderView.Stretch) header.setSectionResizeMode(2, QHeaderView.Stretch) def setPosition(self, pos): self.player.setPosition(pos) def durationChanged(self, duration): total_time = '0:00:00' duration = self.player.duration() total_time = ms_to_time(duration) # self.timeSlider.setMaximum(duration) self.currentTrackDuration = duration # self.totalTimeLabel.setText(total_time) self.currentTrackDuration = total_time def mediaStatusChanged(self, status): icon = QIcon.fromTheme("media-playback-pause") if self.player.state() == QMediaPlayer.StoppedState: icon = QIcon.fromTheme("media-playback-start") elif self.player.state() == QMediaPlayer.PausedState: icon = QIcon.fromTheme("media-playback-start") self.playBtn.setIcon(icon) def positionChanged(self, position, senderType=False): self.currentTime = position current_time = '0:00:00' if position != -1: current_time = ms_to_time(position) self.timeLabel.setText('{0} / {1}'.format( current_time, self.currentTrackDuration)) '''self.timeSlider.blockSignals(True) self.timeSlider.setValue(position) self.timeSlider.blockSignals(False)''' def playlistPosChanged(self): pos = self.playList.currentIndex() data = self.playListData[pos] self.setWindowTitle('YouTube-dl Music: ' + data['title']) duration = ms_to_time(data['duration'] * 1000) self.timeLabel.setText('0:00:00 / {0}'.format(duration)) # self.totalTimeLabel.setText(ms_to_time(data['duration'] * 1000)) if self.playList.mediaCount() > 1: if pos < self.playList.mediaCount() - 1: self.nextBtn.setEnabled(True) else: self.nextBtn.setEnabled(False) if pos > 0: self.prevBtn.setEnabled(True) else: self.prevBtn.setEnabled(False) prevPos = 0 if pos < self.playList.mediaCount(): if self.currentPos < pos: prevPos = pos - 1 else: prevPos = pos + 1 statusItem = QLabel() icon = QIcon.fromTheme("media-playback-start") statusItem.setPixmap(icon.pixmap(16, 16)) statusItem.setAlignment(Qt.AlignCenter) self.playlistTable.setCellWidget(pos, 0, statusItem) if prevPos > -1: self.playlistTable.setCellWidget(prevPos, 0, QLabel()) else: self.playlistTable.setItem(pos, 0, QTableWidgetItem('')) self.currentPos = pos else: statusItem = QLabel() icon = QIcon.fromTheme("media-playback-start") statusItem.setPixmap(icon.pixmap(16, 16)) statusItem.setAlignment(Qt.AlignCenter) self.playlistTable.setCellWidget(pos, 0, statusItem) def playPause(self): icon = QIcon.fromTheme("media-playback-pause") if self.player.state() == QMediaPlayer.StoppedState: if self.player.mediaStatus() == QMediaPlayer.NoMedia: if self.playList.mediaCount() != 0: self.player.play() elif self.player.mediaStatus() == QMediaPlayer.LoadedMedia: self.playList.setCurrentIndex(self.currentPos) self.player.play() elif self.player.mediaStatus() == QMediaPlayer.BufferedMedia: self.player.play() elif self.player.state() == QMediaPlayer.PlayingState: icon = QIcon.fromTheme("media-playback-start") self.player.pause() elif self.player.state() == QMediaPlayer.PausedState: self.player.play() self.playBtn.setIcon(icon) def stop(self): self.player.stop() icon = QIcon.fromTheme("media-playback-start") self.playBtn.setIcon(icon) def insertTrack(self, data): if data: self.playListData.append(data) totalTracks = self.playList.mediaCount() pos = totalTracks self.playlistTable.insertRow(totalTracks) self.playlistTable.setRowCount(totalTracks + 1) self.playlistTable.setItem(pos, 0, QTableWidgetItem('')) self.playlistTable.setItem(pos, 1, QTableWidgetItem(data['channel'])) self.playlistTable.setItem(pos, 2, QTableWidgetItem(data['title'])) media = QMediaContent(QUrl(data['url'])) self.playList.addMedia(media) if totalTracks > 1: self.nextBtn.setEnabled(True) else: self.statusBar().showMessage('Total pistas: {0}'.format( self.playList.mediaCount())) def addDialog(self): url, ok = QInputDialog.getText( self, _translate('MainWindow', 'Add video/playlist'), 'URL:') if ok and url != '': self.addPCThread = addVideos(self, url) self.addPCThread.video.connect(self.insertTrack) self.addPCThread.start() def changeTrack(self, item): pos = item.row() self.playlistTable.setCellWidget(self.currentPos, 0, QLabel()) self.playList.setCurrentIndex(pos) if self.player.state() == QMediaPlayer.StoppedState: icon = QIcon.fromTheme("media-playback-pause") self.playBtn.setIcon(icon) self.player.play() def delTracks(self): indexes = self.playlistTable.selectionModel().selectedRows() del_first = True for index in sorted(indexes): pos = index.row() if not del_first: pos -= 1 else: del_first = False self.playlistTable.removeRow(pos) self.playListData.pop(pos) self.playList.removeMedia(pos) self.playlistPosChanged() def selectedTracks(self): totalSelected = len(self.playlistTable.selectedItems()) if totalSelected > 0: self.removeBtn.setEnabled(True) else: self.removeBtn.setEnabled(False)
class MediaPlayerTab(GalacteekTab): statePlaying = QMediaPlayer.PlayingState statePaused = QMediaPlayer.PausedState stateStopped = QMediaPlayer.StoppedState def __init__(self, *args, **kw): super(MediaPlayerTab, self).__init__(*args, **kw) self.playlistIpfsPath = None self.playlist = QMediaPlaylist() self.model = ListModel(self.playlist) self.playlistsMenu = QMenu(self) self.playlistsMenu.triggered.connect(self.onPlaylistsMenu) self.pListWidget = QWidget(self) self.uipList = ui_mediaplaylist.Ui_MediaPlaylist() self.uipList.setupUi(self.pListWidget) self.uipList.savePlaylistButton.clicked.connect(self.onSavePlaylist) self.uipList.savePlaylistButton.setEnabled(False) self.uipList.loadPlaylistButton.setPopupMode(QToolButton.InstantPopup) self.uipList.loadPlaylistButton.setMenu(self.playlistsMenu) self.clipMenu = QMenu(self) self.copyPathAction = QAction(getIconIpfsIce(), iCopyPlaylistPath(), self, triggered=self.onCopyPlaylistPath) self.loadPathAction = QAction(getIconIpfsIce(), iLoadPlaylistFromPath(), self, triggered=self.onLoadPlaylistPath) self.copyPathAction.setEnabled(False) self.clipMenu.addAction(self.copyPathAction) self.clipMenu.addAction(self.loadPathAction) self.uipList.clipPlaylistButton.setPopupMode(QToolButton.InstantPopup) self.uipList.clipPlaylistButton.setMenu(self.clipMenu) self.uipList.clearButton.clicked.connect(self.onClearPlaylist) self.uipList.nextButton.clicked.connect(self.onPlaylistNext) self.uipList.previousButton.clicked.connect(self.onPlaylistPrevious) self.uipList.nextButton.setIcon(self.style().standardIcon( QStyle.SP_MediaSkipForward)) self.uipList.previousButton.setIcon(self.style().standardIcon( QStyle.SP_MediaSkipBackward)) self.pListView = self.uipList.listView self.pListView.mousePressEvent = self.playlistMousePressEvent self.pListView.setModel(self.model) self.pListView.setResizeMode(QListView.Adjust) self.pListView.setMinimumWidth(self.width() / 2) self.duration = None self.playerState = None self.player = QMediaPlayer(self) self.player.setPlaylist(self.playlist) self.videoWidget = MPlayerVideoWidget(self.player, self) self.useUpdates(True) self.videoWidget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.player.setVideoOutput(self.videoWidget) self.player.error.connect(self.onError) self.player.stateChanged.connect(self.onStateChanged) self.player.metaDataChanged.connect(self.onMetaData) self.player.durationChanged.connect(self.mediaDurationChanged) self.player.positionChanged.connect(self.mediaPositionChanged) self.player.videoAvailableChanged.connect(self.onVideoAvailable) self.pListView.activated.connect(self.onListActivated) self.playlist.currentIndexChanged.connect(self.playlistPositionChanged) self.playlist.currentMediaChanged.connect(self.playlistMediaChanged) self.playlist.mediaInserted.connect(self.playlistMediaInserted) self.playlist.mediaRemoved.connect(self.playlistMediaRemoved) self.togglePList = QToolButton(self) self.togglePList.setIcon(self.style().standardIcon( QStyle.SP_ArrowRight)) self.togglePList.setFixedSize(32, 128) self.togglePList.clicked.connect(self.onTogglePlaylist) self.clipboardMediaItem = None self.clipboardButton = QToolButton(clicked=self.onClipboardClicked) self.clipboardButton.setIcon(getIconClipboard()) self.clipboardButton.setEnabled(False) self.clipboardButton.setToolTip('Load media from clipboard') self.pinButton = QToolButton(clicked=self.onPinMediaClicked) self.pinButton.setIcon(getIcon('pin.png')) self.processClipboardItem(self.app.clipTracker.current, force=True) self.app.clipTracker.currentItemChanged.connect(self.onClipItemChange) self.playButton = QToolButton(clicked=self.onPlayClicked) self.playButton.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay)) self.pauseButton = QToolButton(clicked=self.onPauseClicked) self.pauseButton.setIcon(self.style().standardIcon( QStyle.SP_MediaPause)) self.pauseButton.setEnabled(False) self.stopButton = QToolButton(clicked=self.onStopClicked) self.stopButton.setIcon(self.style().standardIcon(QStyle.SP_MediaStop)) self.stopButton.setEnabled(False) self.fullscreenButton = QToolButton(clicked=self.onFullScreen) self.fullscreenButton.setIcon(getIcon('fullscreen.png')) self.fullscreenButton.setToolTip(iFullScreen()) self.seekSlider = QSlider(Qt.Horizontal, sliderMoved=self.onSeek) self.seekSlider.sliderReleased.connect(self.onSliderReleased) self.seekSlider.setObjectName('mediaPlayerSlider') self.durationLabel = QLabel() vLayout = QVBoxLayout() hLayoutControls = QHBoxLayout() hLayoutControls.setContentsMargins(0, 0, 0, 0) hLayoutControls.addWidget(self.clipboardButton) hLayoutControls.addWidget(self.pinButton) hLayoutControls.addWidget(self.playButton) hLayoutControls.addWidget(self.pauseButton) hLayoutControls.addWidget(self.stopButton) hLayoutControls.addWidget(self.seekSlider) hLayoutControls.addWidget(self.durationLabel) hLayoutControls.addWidget(self.fullscreenButton) vLayout.addWidget(self.videoWidget) vLayout.addLayout(hLayoutControls) hLayout = QHBoxLayout() hLayout.addLayout(vLayout) hLayout.addWidget(self.pListWidget) hLayout.addWidget(self.togglePList) self.pListWidget.hide() self.vLayout.addLayout(hLayout) self.update() self.videoWidget.changeFocus() @property def isPlaying(self): return self.playerState == self.statePlaying @property def isPaused(self): return self.playerState == self.statePaused @property def isStopped(self): return self.playerState == self.stateStopped def useUpdates(self, updates=True): # Enable widget updates or not on the video widget self.videoWidget.setUpdatesEnabled(updates) def update(self): self.app.task(self.updatePlaylistsMenu) def onFullScreen(self): self.videoWidget.viewFullScreen(True) def onClearPlaylist(self): self.copyPathAction.setEnabled(False) self.player.stop() self.clearPlaylist() def onLoadPlaylistPath(self): current = self.app.clipTracker.getCurrent() if current: self.app.task(self.loadPlaylistFromPath, current.path) def onCopyPlaylistPath(self): if self.playlistIpfsPath: self.app.setClipboardText(self.playlistIpfsPath) def onPinMediaClicked(self): currentMedia = self.playlist.currentMedia() if currentMedia.isNull(): return messageBox(iNoMediaInPlaylist()) ensure(self.pinMedia(currentMedia)) @ipfsOp async def pinMedia(self, ipfsop, media): mediaUrl = qurlPercentDecode(media.canonicalUrl()) path = IPFSPath(mediaUrl, autoCidConv=True) if path.valid: await ipfsop.ctx.pin(str(path), qname='mediaplayer') @ipfsOp async def updatePlaylistsMenu(self, ipfsop): currentList = [ action.text() for action in self.playlistsMenu.actions() ] listing = await ipfsop.filesList(self.profile.pathPlaylists) for entry in listing: if entry['Name'] in currentList: continue action = QAction(entry['Name'], self) action.setData(entry) self.playlistsMenu.addAction(action) def playlistShowContextMenu(self, event): selModel = self.pListView.selectionModel() idx = self.pListView.indexAt(event.pos()) if not idx.isValid(): return path = self.model.data(idx) if path: selModel.reset() selModel.select(idx, QItemSelectionModel.Select) menu = QMenu(self) menu.addAction(getIcon('clear-all.png'), iPlaylistRemoveMedia(), functools.partial(self.onRemoveMediaFromIndex, idx)) menu.exec_(event.globalPos()) def onRemoveMediaFromIndex(self, idx): self.playlist.removeMedia(idx.row()) def playlistMousePressEvent(self, event): if event.button() == Qt.RightButton: self.pListView.selectionModel().reset() self.playlistShowContextMenu(event) else: if not self.pListView.indexAt(event.pos()).isValid(): self.deselectPlaylistItems() QListView.mousePressEvent(self.pListView, event) def onPlaylistsMenu(self, action): entry = action.data() self.app.task(self.loadPlaylistFromPath, joinIpfs(entry['Hash'])) def onSavePlaylist(self): paths = self.playlistGetPaths() listName = inputText(title=iPlaylistName(), label=iPlaylistName()) if not listName: return obj = JSONPlaylistV1(listName=listName, itemPaths=paths) self.app.task(self.savePlaylist, obj, listName) @ipfsOp async def savePlaylist(self, ipfsop, obj, name): objPath = os.path.join(self.profile.pathPlaylists, name) exists = await ipfsop.filesStat(objPath) if exists: await ipfsop.filesRm(objPath) ent = await ipfsop.client.core.add_json(obj.root) if ent: await ipfsop.filesLinkFp(ent, objPath) self.playlistIpfsPath = joinIpfs(ent['Hash']) self.copyPathAction.setEnabled(True) self.update() @ipfsOp async def loadPlaylistFromPath(self, ipfsop, path): try: obj = await ipfsop.jsonLoad(path) except Exception: return messageBox(iCannotLoadPlaylist()) if obj is None: return messageBox(iCannotLoadPlaylist()) try: # Assume v1 format for now, when the format evolves we'll just # use json validation pList = JSONPlaylistV1(data=obj) self.clearPlaylist() for item in pList.items(): self.queueFromPath(item['path']) self.playlistIpfsPath = path self.copyPathAction.setEnabled(True) except Exception: return messageBox(iCannotLoadPlaylist()) def playlistMediaInserted(self, start, end): self.uipList.savePlaylistButton.setEnabled( self.playlist.mediaCount() > 0) def playlistMediaRemoved(self, start, end): self.uipList.savePlaylistButton.setEnabled( self.playlist.mediaCount() > 0) self.model.modelReset.emit() def playlistGetPaths(self): return [u.path() for u in self.playlistGetUrls()] def playlistGetUrls(self): urls = [] for idx in range(0, self.playlist.mediaCount()): media = self.playlist.media(idx) urls.append(media.canonicalUrl()) return urls def onClipItemChange(self, item): self.processClipboardItem(item) def processClipboardItem(self, item, force=False): if not item: return def analyzeMimeType(cItem): if cItem.mimeCategory in ['audio', 'video', 'image']: self.clipboardMediaItem = cItem self.clipboardButton.setEnabled(True) self.clipboardButton.setToolTip(cItem.path) else: self.clipboardButton.setEnabled(False) self.clipboardButton.setToolTip(iClipboardEmpty()) if force: analyzeMimeType(item) else: item.mimeTypeAvailable.connect(lambda mType: analyzeMimeType(item)) def onClipboardClicked(self): if self.clipboardMediaItem: self.playFromPath(self.clipboardMediaItem.path) else: messageBox('Not a multimedia resource') def onSliderReleased(self): pass def onPlaylistNext(self): self.playlist.next() def onPlaylistPrevious(self): self.playlist.previous() def onPlayClicked(self): self.player.play() def onPauseClicked(self): if self.isPlaying: self.player.pause() elif self.isPaused: self.player.play() def onStopClicked(self): self.player.stop() self.player.setPosition(0) self.seekSlider.setValue(0) self.seekSlider.setRange(0, 0) def onSeek(self, seconds): if self.player.isSeekable(): self.player.setPosition(seconds * 1000) def onTogglePlaylist(self): self.pListWidget.setVisible(self.pListWidget.isHidden()) def onError(self, error): messageBox(iPlayerError(error)) def onStateChanged(self, state): self.playerState = state self.updateControls(state) def updateControls(self, state): if self.isStopped: self.stopButton.setEnabled(False) self.pauseButton.setEnabled(False) self.playButton.setEnabled(True) self.seekSlider.setEnabled(False) self.duration = None elif self.isPlaying: self.seekSlider.setRange(0, self.player.duration() / 1000) self.seekSlider.setEnabled(True) self.pauseButton.setEnabled(True) self.playButton.setEnabled(False) self.stopButton.setEnabled(True) def onListActivated(self, index): if index.isValid(): self.playlist.setCurrentIndex(index.row()) self.player.play() def onMetaData(self): # Unfinished if self.player.isMetaDataAvailable(): availableKeys = self.player.availableMetaData() for key in availableKeys: self.player.metaData(key) def playFromUrl(self, url, mediaName=None): if self.isPlaying: self.player.stop() cUrls = self.playlistGetUrls() for u in cUrls: if u.toString() == url.toString(): return messageBox(iAlreadyInPlaylist()) media = QMediaContent(url) if self.playlist.addMedia(media): count = self.model.rowCount() if count > 0: self.playlist.setCurrentIndex(count - 1) self.player.play() def playFromPath(self, path, mediaName=None): mediaUrl = self.app.subUrl(path) self.playFromUrl(mediaUrl) def queueFromPath(self, path, playLast=False, mediaName=None): mediaUrl = self.app.subUrl(path) self.playlist.addMedia(QMediaContent(mediaUrl)) if playLast: count = self.playlist.mediaCount() if count > 0: self.player.stop() self.playlist.setCurrentIndex(count - 1) self.player.play() def clearPlaylist(self): self.playlist.clear() self.pListView.reset() def playlistPositionChanged(self, position): self.pListView.setCurrentIndex(self.model.index(position, 0)) def deselectPlaylistItems(self): self.pListView.selectionModel().reset() def playlistMediaChanged(self, media): selModel = self.pListView.selectionModel() self.deselectPlaylistItems() self.model.modelReset.emit() idx = self.model.index(self.playlist.currentIndex(), 0) if idx.isValid(): selModel.select(idx, QItemSelectionModel.Select) def onVideoAvailable(self, available): if available: if self.isPlaying: self.useUpdates(False) elif self.isStopped or self.isPaused: self.useUpdates(True) else: self.useUpdates(True) def mediaDurationChanged(self, duration): self.duration = duration / 1000 self.seekSlider.setMaximum(self.duration) def mediaPositionChanged(self, progress): progress /= 1000 if self.duration: cTime = durationConvert(progress) tTime = durationConvert(self.duration) self.durationLabel.setText('{0} ({1})'.format( cTime.toString(), tTime.toString())) if not self.seekSlider.isSliderDown(): self.seekSlider.setValue(progress) async def onClose(self): self.player.stop() self.player.setMedia(QMediaContent(None)) return True def playerAvailable(self): return mediaPlayerAvailable(player=self.player)
class BuddingVoice(QtWidgets.QPushButton): def __init__(self, parent, controller): super(BuddingVoice, self).__init__(parent) # music button config. self.controller = controller self.controller.register_observer("music_button", self) self.clicked.connect(self.voice_clicked) self.musicon = 1 #playlist, set according to level self.playlist = QMediaPlaylist() self.url = None self.level = self.controller.get_level() print("user level", self.level) #below is for test #self.level = 3 self.url = self.choose_music(self.level) self.playlist.addMedia(QMediaContent(self.url)) self.playlist.setPlaybackMode(QMediaPlaylist.Loop) #player self.player = QMediaPlayer() self.player.setPlaylist(self.playlist) self.player.play() def voice_clicked(self): if self.musicon: self.player.pause() else: #below is for test on_level_update #self.on_level_update() self.player.play() self.musicon = 1 - self.musicon def on_level_update(self, newlevel): #below is for test #newlevel = self.level + 1 print("newlevel", newlevel, "oldlevel", self.level) self.player.pause() self.playlist.removeMedia(0) self.url = self.choose_music(newlevel) self.playlist.addMedia(QMediaContent(self.url)) self.player.setPlaylist(self.playlist) self.player.play() self.level = newlevel def music_path(self, music): fp = "./music/" + music return os.path.abspath(fp) def choose_music(self, level): mystr = "" if level == 1: mystr = self.music_path("sad.mp3") elif level == 2: mystr = self.music_path("neutral.mp3") else: mystr = self.music_path("happy.mp3") return QUrl.fromLocalFile(mystr) def on_logout(self): self.musicon = 0 self.player.stop()
class DBPlayer(QWidget): # signal signaltxt = pyqtSignal(str) signalnum = pyqtSignal(int) def __init__(self): super(DBPlayer, self).__init__() self.setMaximumSize(16777215, 35) # Init Player self.messtitle = TITL_PROG self.namemedia = '' self.albumname = '' self.currentPlaylist = QMediaPlaylist() self.player = QMediaPlayer() self.player.stateChanged.connect(self.qmp_stateChanged) self.player.positionChanged.connect(self.qmp_positionChanged) self.player.volumeChanged.connect(self.qmp_volumeChanged) self.player.durationChanged.connect(self.qmp_durationChanged) self.player.setVolume(60) # Init GUI self.setLayout(self.addControls()) self.infoBox = None def addControls(self): # buttons self.playBtn = QPushButton() self.playBtn.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay)) self.playBtn.setStyleSheet('border: 0px;') stopBtn = QPushButton() stopBtn.setIcon(self.style().standardIcon(QStyle.SP_MediaStop)) stopBtn.setStyleSheet('border: 0px;') prevBtn = QPushButton() prevBtn.setIcon(self.style().standardIcon(QStyle.SP_MediaSkipBackward)) prevBtn.setStyleSheet('border: 0px;') nextBtn = QPushButton() nextBtn.setIcon(self.style().standardIcon(QStyle.SP_MediaSkipForward)) nextBtn.setStyleSheet('border: 0px;') volumeDescBtn = QPushButton('▼') volumeDescBtn.setIcon(self.style().standardIcon(QStyle.SP_MediaVolume)) volumeDescBtn.setMaximumWidth(30) volumeDescBtn.setStyleSheet('border: 0px;') volumeIncBtn = QPushButton('▲') volumeIncBtn.setIcon(self.style().standardIcon(QStyle.SP_MediaVolume)) volumeIncBtn.setMaximumWidth(40) volumeIncBtn.setStyleSheet('border: 0px;') infoBtn = QPushButton() infoBtn.setIcon(self.style().standardIcon( QStyle.SP_FileDialogContentsView)) infoBtn.setStyleSheet('border: 0px;') # seek slider self.seekSlider = QSlider(Qt.Horizontal, self) self.seekSlider.setMinimum(0) self.seekSlider.setMaximum(100) self.seekSlider.setTracking(False) # labels position start/end self.seekSliderLabel1 = QLabel('0:00') self.seekSliderLabel2 = QLabel('0:00') # layout controlArea = QHBoxLayout() controlArea.addWidget(prevBtn) controlArea.addWidget(self.playBtn) controlArea.addWidget(stopBtn) controlArea.addWidget(nextBtn) controlArea.addWidget(self.seekSliderLabel1) controlArea.addWidget(self.seekSlider) controlArea.addWidget(self.seekSliderLabel2) controlArea.addWidget(infoBtn) controlArea.addWidget(volumeDescBtn) controlArea.addWidget(volumeIncBtn) # link buttons to media self.seekSlider.sliderMoved.connect(self.seekPosition) self.playBtn.clicked.connect(self.playHandler) stopBtn.clicked.connect(self.stopHandler) volumeDescBtn.clicked.connect(self.decreaseVolume) volumeIncBtn.clicked.connect(self.increaseVolume) prevBtn.clicked.connect(self.prevItemPlaylist) nextBtn.clicked.connect(self.nextItemPlaylist) infoBtn.clicked.connect(self.displaySongInfo) return controlArea def playHandler(self): if self.player.state() == QMediaPlayer.PlayingState: self.player.pause() message = (' [Paused at position %s]' % self.seekSliderLabel1.text()) self.messtitle = self.namemedia + message self.signaltxt.emit(self.messtitle) else: if self.player.state() == QMediaPlayer.StoppedState: if self.player.mediaStatus() == QMediaPlayer.NoMedia: if self.currentPlaylist.mediaCount() != 0: self.player.setPlaylist(self.currentPlaylist) elif self.player.mediaStatus() == QMediaPlayer.LoadedMedia: self.player.play() elif self.player.mediaStatus() == QMediaPlayer.BufferedMedia: self.player.play() elif self.player.state() == QMediaPlayer.PlayingState: pass elif self.player.state() == QMediaPlayer.PausedState: self.player.play() if self.player.volume() is not None and self.player.state( ) == QMediaPlayer.PlayingState: message = ' [Volume %d]' % self.player.volume() self.messtitle = self.namemedia + message self.signaltxt.emit(self.messtitle) def stopHandler(self): if self.player.state() == QMediaPlayer.PlayingState: self.stopState = True self.player.stop() elif self.player.state() == QMediaPlayer.PausedState: self.player.stop() elif self.player.state() == QMediaPlayer.StoppedState: pass if self.player.volume() is not None and self.player.state( ) == QMediaPlayer.PlayingState: self.messtitle = self.namemedia + (' [Stopped]') self.signaltxt.emit(self.messtitle) def qmp_stateChanged(self): if self.player.state() == QMediaPlayer.StoppedState: self.player.stop() # buttons icon play/pause change if self.player.state() == QMediaPlayer.PlayingState: self.playBtn.setIcon(self.style().standardIcon( QStyle.SP_MediaPause)) else: self.playBtn.setIcon(self.style().standardIcon( QStyle.SP_MediaPlay)) def qmp_positionChanged(self, position): # update position slider self.seekSlider.setValue(position) # update the text label self.seekSliderLabel1.setText( '%d:%02d' % (int(position / 60000), int((position / 1000) % 60))) def seekPosition(self, position): sender = self.sender() if isinstance(sender, QSlider): if self.player.isSeekable(): self.player.setPosition(position) def qmp_volumeChanged(self): if self.player.volume() is not None: message = (' [Playing at Volume %d]' % (self.player.volume())) if self.namemedia != '': self.messtitle = self.namemedia + message else: self.messtitle = "Initialisation player " + message self.signaltxt.emit(self.messtitle) def qmp_durationChanged(self, duration): self.seekSlider.setRange(0, duration) self.seekSliderLabel2.setText( '%d:%02d' % (int(duration / 60000), int((duration / 1000) % 60))) nummedia = self.currentPlaylist.mediaCount() curmedia = self.currentPlaylist.currentIndex() #artist = self.player.metaData(QMediaMetaData.Author) #tittle = self.player.metaData(QMediaMetaData.Title) self.namemedia = path.basename(self.homMed[curmedia]) self.namemedia = '[%02d/%02d' % ( curmedia + 1, nummedia) + '] "' + self.namemedia + '"' self.buildPlaylist() message = (' [Playing at Volume %d]' % (self.player.volume())) if self.player.volume() is not None and self.player.state( ) == QMediaPlayer.PlayingState: self.messtitle = self.namemedia + message self.signaltxt.emit(self.messtitle) def buildPlaylist(self): """Build play list.""" nummedia = self.currentPlaylist.mediaCount() curmedia = self.currentPlaylist.currentIndex() + 1 compteur = 1 self.textplaylist = '<b>' + self.albumname + '</b>' self.textplaylist += '<table class="tftable" border="0">' for namemedia in self.homMed: media = path.basename(namemedia) media = '[%02d/%02d' % (compteur, nummedia) + '] "' + media + '"' if curmedia == compteur: self.textplaylist += '<tr><td><b>' + media + '</b></td></tr>' else: self.textplaylist += '<tr><td>' + media + '</td></tr>' compteur += 1 self.textplaylist = self.textplaylist + '</table>' self.playBtn.setToolTip(self.textplaylist) self.signalnum.emit(curmedia - 1) def increaseVolume(self): """Volume +.""" vol = self.player.volume() vol = min(vol + 5, 100) self.player.setVolume(vol) def decreaseVolume(self): """Volume -.""" vol = self.player.volume() vol = max(vol - 5, 0) self.player.setVolume(vol) def prevItemPlaylist(self): self.player.playlist().previous() if self.currentPlaylist.currentIndex() == -1: self.player.playlist().previous() def nextItemPlaylist(self): self.player.playlist().next() if self.currentPlaylist.currentIndex() == -1: self.player.playlist().next() def addMediaslist(self, listmedias, position, albumname): if self.currentPlaylist.mediaCount() > 0: self.currentPlaylist.removeMedia(0, self.currentPlaylist.mediaCount()) self.player.stop() self.stopHandler() self.currentPlaylist.removeMedia(0, self.currentPlaylist.mediaCount()) self.albumname = albumname if listmedias: self.homMed = listmedias for media in self.homMed: self.currentPlaylist.addMedia( QMediaContent(QUrl.fromLocalFile(media))) self.currentPlaylist.setCurrentIndex(position) self.playHandler() def displaySongInfo(self): # extract datas metaDataKeyList = self.player.availableMetaData() fullText = '<table class="tftable" border="0">' for key in metaDataKeyList: value = str(self.player.metaData(key)).replace("'", "").replace( "[", "").replace("]", "") if key == 'Duration': value = '%d:%02d' % (int( int(value) / 60000), int((int(value) / 1000) % 60)) fullText = fullText + '<tr><td>' + key + '</td><td>' + value + '</td></tr>' fullText = fullText + '</table>' # re-init if self.infoBox is not None: self.infoBox.destroy() # infos box self.infoBox = QMessageBox(self) self.infoBox.setWindowTitle('Detailed Song Information') self.infoBox.setTextFormat(Qt.RichText) self.infoBox.addButton('OK', QMessageBox.AcceptRole) self.infoBox.setText(fullText) self.infoBox.show()
class DirectoryPlaylist(Playlist): def __init__(self, parent=None): super(DirectoryPlaylist, self).__init__(parent) self._directories_urls = set() self._added_song_urls = set() self._tracks = [] self._qPlaylist = QMediaPlaylist(parent) self._qPlaylist.mediaAboutToBeInserted.connect( lambda s, e: self.mediaAboutToBeInserted.emit(s, e)) self._qPlaylist.mediaInserted.connect( lambda s, e: self.mediaInserted.emit(s, e)) self._qPlaylist.mediaAboutToBeRemoved.connect( lambda s, e: self.mediaAboutToBeRemoved.emit(s, e)) self._qPlaylist.mediaRemoved.connect( lambda s, e: self.mediaRemoved.emit(s, e)) self._qPlaylist.mediaChanged.connect( lambda s, e: self.mediaChanged.emit(s, e)) def songs(self): return self._tracks def albums_data(self): return self._albumsData def artists_data(self): return self._artistsData def __traverse_directory(self, url, func): songs = [] for root, dirs, files in os.walk(url): for file in files: abs_path = os.path.join(root, file) song = func(abs_path) if song: songs.append(song) return songs def is_empty(self): return not self._tracks def add_song(self, abs_path): if abs_path not in self._added_song_urls: url = QtCore.QUrl.fromLocalFile(abs_path) song = MediaFactory.create_media(abs_path) if not song: return None added = self._qPlaylist.addMedia(QMediaContent(url)) if not added: return None self._tracks.append(song) self._added_song_urls.add(abs_path) return song def remove_song(self, row): if row < 0 or row > self.song_count() - 1: return None if self._qPlaylist.currentIndex() == row: if row < self.song_count(): self._qPlaylist.setCurrentIndex(row + 1) else: self._qPlaylist.setCurrentIndex(row - 1) removed = self._qPlaylist.removeMedia(row) if not removed: return del self._tracks[row] url = self.get_song_abs_path(row) self._added_song_urls.discard(url) def add_directory(self, directory): if directory not in self._directories_urls: self._directories_urls.add(directory) songs = self.__traverse_directory(directory, self.add_song) return songs return None def add_directories(self, directories): songs = [] for directory in directories: if directory not in self._directories_urls: current_songs = self.add_directory(directory) if current_songs: songs.extend(current_songs) return songs def remove_directory(self, directory): if directory not in self._directories_urls: return False # raise Error self.__traverse_directory(directory, self.remove_song) def setCurrentIndex(self, index): self._qPlaylist.setCurrentIndex(index) def getCurrentIndex(self): return self._qPlaylist.currentIndex() def getCurrentSong(self): return self._qPlaylist.currentMedia() def clear(self): self._tracks = [] self._directories_urls = set() self._added_song_urls = set() self._qPlaylist.clear() @property def internalPlaylist(self): return self._qPlaylist def song_count(self): return len(self._tracks) def get_song_metadata(self, row, tags): if row < 0 or row > self.song_count() - 1: return None if not isinstance(tags, list): tags = [tags] return self._tracks[row].get_values(tags) def get_song(self, row): if row < 0 or row > self.song_count() - 1: return None return self._tracks[row] def get_song_title(self, row): if row < 0 or row > self.song_count() - 1: return None k, v = self.get_song_metadata(row, 'title').popitem() try: return v[0] except IndexError: return None def get_song_album(self, row): if row < 0 or row > self.song_count() - 1: return None k, v = self.get_song_metadata(row, 'album').popitem() try: return v[0] except IndexError: return None def get_song_artist(self, row): if row < 0 or row > self.song_count() - 1: return None k, v = self.get_song_metadata(row, 'artist').popitem() try: return v[0] except IndexError: return None def get_song_genre(self, row): if row < 0 or row > self.song_count() - 1: return None k, v = self.get_song_metadata(row, 'genre').popitem() try: return v[0] except IndexError: return None def get_song_abs_path(self, row): if row < 0 or row > self.song_count() - 1: return None return self._tracks[row].get_abs_path() def getDirectories(self): return self._directories_urls def getAddedSongUrls(self): return self._added_song_urls def __str__(self): return str(self._tracks) def __eq__(self, other): return (isinstance(other, self.__class__) and self._directories_urls == other._directories_urls and self._added_song_urls == other._added_song_urls)
class MainWindow(QMainWindow): def __init__(self): super(MainWindow, self).__init__() #Load mainwindow.ui from Qt Designer uic.loadUi('../ui/mainwindow.ui', self) #Load all settings self.initMainWindow() #Show the main window self.show() def initMainWindow(self): """ This function initialize all the buttons and all the setting for displaying and control the video. """ self.setWindowIcon( QtGui.QIcon( "../resources/icons/GUI_Icons/1519132568_movavi-video-editor.png" )) #delete all temp files self.deleteAllTemp() self.setWindowTitle("VideoEditor") """<Objects>""" #Object for controling all edit functions self.edit = Panel() """</Objects>""" """--------------------<Global variables>----------------------------""" #Thread manager variable self.threadmanager = True #Audio file holder self.audioFile = '' #frame time variable self.frameTime = '' #frame file path self.frameFilePath = '' #Total number of curent media opened self.totalIndex = -1 #Dictionary for index and path for the media self.curentFiles = {} #Index that indicates the curent media selected self.curentIndex = self.totalIndex #Curent index of Concatenate ListView self.curentIndexOfConcatenateList = -1 #Dictionary with all video path for concatenate self.concatenateVideos = {} #Total number of videos added to the concatenate list view self.totalNrOfVideosConcat = -1 #Media play speed self.speed = 1 # self.subtitlesFileVG = '' """-------------------</Global variables>----------------------------""" """----------------<Media player settings>---------------------------""" #Create a mediaplayer object to control the video self.mediaPlayer = QMediaPlayer(None, QMediaPlayer.VideoSurface) self.mediaPlayer.setVolume(50) #Create a playlist object for multiple videos self.playlist = QMediaPlaylist() self.mediaPlayer.setPlaylist(self.playlist) self.model = PlaylistModel(self.playlist) self.videoFiles.setModel(self.model) self.playlist.currentIndexChanged.connect( self.playlist_position_changed) selection_model = self.videoFiles.selectionModel() selection_model.selectionChanged.connect( self.playlist_selection_changed) #Create videoWidget object for displaying the video videoWidget = QVideoWidget() #videoWidget set self.videoPreviewLayout = QVBoxLayout() self.videoPreviewLayout.addWidget(videoWidget) self.vpfVideoPreview.setLayout(self.videoPreviewLayout) """----------------</Media player settings>--------------------------""" """-----------------<Buttons&Labels settings>-------------------------""" #Create Open Video button in taskbar self.open = QAction('&Open Video', self) self.open.setStatusTip('Open Video') self.menuFiles.addAction(self.open) self.setAcceptDrops(True) #PlayButton self.playButton.setEnabled(False) #Speed label self.speedLabel.setText("1.0x") #Slider settings timeline self.videoTimeSlider.setRange(0, 0) #Slider settings volume self.volume.setRange(0, 100) self.volume.setValue(50) self.volumeTextDisplay.setText("50%") #Cut lock buttons self.lockButtonStart.setCheckable(True) self.lockButtonFinish.setCheckable(True) #Cut text editor settings self.cutStart.setReadOnly(True) self.cutFinish.setReadOnly(True) self.cutStart.setText("0:00:00") self.cutFinish.setText("0:00:00") #Resolution Image settings self.resolutionIcon.setPixmap( QPixmap("../resources/icons/GUI_Icons/720.png")) """-----------------</Buttons&Labels settings>-------------------------""" """-----------------<Buttons connections>------------------------------""" """ -----------<Player buttons>--------- """ #Play button self.playButton.clicked.connect(self.playVideo) #back 15 seconds Button self.skipbackButton.clicked.connect(self.skipbackFunction) #skip 15 seconds forward Button self.skipforwardButton.clicked.connect(self.skipforwadFunction) #fastorForward button self.fastForward.clicked.connect(self.fastForwardFunction) #rewind button self.rewind.clicked.connect(self.rewindFunction) #Add video button self.addButton.clicked.connect(self.openFile) #Remove video button self.deleteButton.clicked.connect(self.RemoveVideo) #save video self.saveVideo.clicked.connect(self.saveVideoFunction) #Time slider self.videoTimeSlider.sliderMoved.connect(self.setPosition) #Volume slider self.volume.sliderMoved.connect(self.volumeControl) #media player change state self.mediaPlayer.stateChanged.connect(self.mediaStateChange) self.mediaPlayer.positionChanged.connect(self.positionChange) self.mediaPlayer.durationChanged.connect(self.durationChange) """ -----------</Player buttons>--------- """ #Open file self.open.triggered.connect(self.openFile) """ -------------<Edit Buttons>----------------- """ """<Concatenate>""" #Create a model for Concatenate Lisview self.concatenateModel = QtGui.QStandardItemModel() #Set the model to the Concatenate List view self.concatenateList.setModel(self.concatenateModel) #Concatenate list of videos self.concatenateList.clicked[QtCore.QModelIndex].connect( self.setConcatenateIndex) #Add button to concatenate list self.addConcatenate.clicked.connect(self.addVideoToConcatenate) # When you receive the signal, you call QtGui.QStandardItemModel.itemFromIndex() # on the given model index to get a pointer to the item self.removeConcatenate.clicked.connect(self.removeVideoToConcatenate) #Concatenate Button self.concatenateButton.clicked.connect(self.concatenateThreadFunction) """</Concatenate>""" """<Cut>""" #Lock cut filed1 self.lockButtonStart.clicked.connect(self.lockButtonChangeIconStart) #Lock cut filed2 self.lockButtonFinish.clicked.connect(self.lockButtonChangeIconFinish) #Cut button self.cutButton.clicked.connect(self.cutThreadFunction) """</Cut>""" """<Resolution>""" #Resoluiton ComboBox for selecting the desired resolution self.ResolutionsList.currentIndexChanged.connect( self.changeResolutionDisplay) #Change resolution button self.changeResolution.clicked.connect(self.changeResolutionThread) """</Resoluton>""" """<Mirror>""" #Mirror button self.mirroringButton.clicked.connect(self.mirrorThread) """</Mirror>""" """<Audio replace>""" self.openAudioFile.clicked.connect(self.openAudio) self.removeAudioFile.clicked.connect(self.removeAudioFileFunction) self.audioModeSelect.currentIndexChanged.connect( self.changeAudioBackground) self.AddAudio.clicked.connect(self.SoundReplaceThread) """</Audio replace>""" """<GetFrame>""" self.getFrameButton.clicked.connect(self.GetFrameFunction) #self.saveFrameButton.setShortcut("Ctrl+S") self.saveFrameButton.setStatusTip('Save File') self.saveFrameButton.clicked.connect(self.saveFrame) """"</GetFrame>""" """<AddSubtitles>""" self.loadSubtitles.clicked.connect(self.loadSubtitlesFunction) self.cleanButton.clicked.connect(self.removeSubtitlesFunction) self.addSubtitle.clicked.connect(self.addSubtitlesThread) """</AddSubtitles>""" """<VideoGrep>""" self.loadSubtitlesVG.clicked.connect(self.loadSubtitlesVideoGrep) self.cleanButtonVideoGrep.clicked.connect( self.removeSubtitlesVideoGrep) self.videoGrep.clicked.connect(self.videoGrepThread) """</VideoGrep>""" """ -------------</Edit Buttons>----------------- """ """ -------------<Shortcut Buttons>--------------- """ self.soundShortcut.clicked.connect(self.soundShortcutKey) self.getFrameShortcut.clicked.connect(self.getFrameShortcutKey) self.cutShortcut.clicked.connect(self.cutShortcutKey) self.concatShortcut.clicked.connect(self.concatShortcutKey) self.mirrorShortcut.clicked.connect(self.mirrorShortcutKey) """ -------------</Shortcut Buttons>--------------- """ """-----------------</Buttons connections>----------------------------""" """-------------------<Threads for editing>--------------------------""" self.pool = QThreadPool() """-------------------</Threads for editing>--------------------------""" """<Experimental>""" qtimeline = QTimeLine(360, 1) self.test = QVBoxLayout() qtimeline2 = QTimeLine(360, 1) self.test.addWidget(qtimeline) self.test.addWidget(qtimeline2) self.sfTimeLineFrame.setLayout(self.test) #self.editMenu.setCurrentIndex(5) """</Experimental>""" #Set output to the video self.mediaPlayer.setVideoOutput(videoWidget) """ ------------------<Concatenate functions>---------------------------""" def setConcatenateIndex(self, index): """ Set the curent index of the selected item from concatenate list view (self.concatenateList) """ #Get the item from the model of index "index" item = self.concatenateModel.itemFromIndex(index) #Get the "item " row index self.curentIndexOfConcatenateList = item.index().row() print(self.concatenateVideos) def addVideoToConcatenate(self): """ Add from 'concatenateVideoList' a video to 'concatenateList' """ try: #Add the current video selected from the ComboBox to the concatenate list view item = QtGui.QStandardItem(self.curentFiles[ self.concatenateVideoList.currentIndex()].split('/')[-1]) self.concatenateModel.appendRow(item) self.totalNrOfVideosConcat += 1 self.concatenateVideos[ self.totalNrOfVideosConcat] = self.curentFiles[ self.concatenateVideoList.currentIndex()] except: print("No video") QMessageBox.about(self, "No video", "Please add a video! ") print(self.concatenateVideoList.currentIndex()) def removeVideoToConcatenate(self): """ Remove video from concatenation list. """ try: self.concatenateModel.removeRow(self.curentIndexOfConcatenateList) del self.concatenateVideos[self.curentIndexOfConcatenateList] self.totalNrOfVideosConcat -= 1 self.SortFilesIndexConcat() except: QMessageBox.about(self, "No video", "No video to remove or not selected! ") print("Error when removing video from list") def concatenate(self): """ This function is used for concatenate multiple videos from the list. """ if (self.curentIndex == -1 and self.totalIndex != -1): self.curentIndex = 0 try: videosToConcatenate = [] #I'm adding the main video videosToConcatenate.append(self.curentFiles[self.curentIndex]) #Add the path to all videos to be concatenated for key in self.concatenateVideos: videosToConcatenate.append(self.concatenateVideos[key]) #Save the current index befor concatenation because #during the concatenation the user could change the curent video #so it would affect curentFiles indexOfRootVideo = self.curentIndex print(videosToConcatenate) #Call concatenate function and save the url of the modified video self.curentFiles[indexOfRootVideo] = self.edit.concat( videosToConcatenate) #Update the media with the edited video self.mediaPlayer.setMedia( QMediaContent( QUrl.fromLocalFile("../resources/videos/blackvideo.mp4"))) self.mediaPlayer.setMedia( QMediaContent( QUrl.fromLocalFile(self.curentFiles[indexOfRootVideo]))) #Clear concatenate list view self.concatenateModel.removeRows(0, self.concatenateModel.rowCount()) #Clear the dict that holds all the data for concatenation self.concatenateVideos.clear() #Reste the number of videos to be concatenated self.totalNrOfVideosConcat = -1 except: print( "A problem occured during concatenation process.Check 'concatenate' function" ) def SortFilesIndexConcat(self): """ This function sort the curentFiles dictionary. When an element is deleted from curentFiles the function sort the index of the curentFiles ascending. """ newIndex = 0 newCurentFiles = {} #loop through the curentFiles and update the index for key in self.concatenateVideos: newCurentFiles[newIndex] = self.concatenateVideos[key] newIndex += 1 #curentFiles files is updated to the new dictionary of files self.concatenateVideos = newCurentFiles.copy() """-------------------</Concatenate functions>--------------------------""" """-----------------------<Cut functions>--------------------------------""" def lockButtonChangeIconStart(self): """ Function for changing the icon when the button is pressed. """ if (self.lockButtonStart.isChecked() == True): self.lockButtonStart.setIcon( QIcon("../resources/icons/GUI_Icons/icons8-lock-80.png")) else: self.lockButtonStart.setIcon( QIcon("../resources/icons/GUI_Icons/icons8-padlock-80.png")) def lockButtonChangeIconFinish(self): """ Function for changing the icon when the button is pressed. """ if (self.lockButtonFinish.isChecked() == True): self.lockButtonFinish.setIcon( QIcon("../resources/icons/GUI_Icons/icons8-lock-80.png")) else: self.lockButtonFinish.setIcon( QIcon("../resources/icons/GUI_Icons/icons8-padlock-80.png")) def restCutButtons(self): """ This function is used to reset all the value for cut section. """ self.cutStart.setText("0:00:00") self.cutFinish.setText("0:00:00") if (self.lockButtonStart.isChecked() == True): self.lockButtonStart.toggle() if (self.lockButtonFinish.isChecked() == True): self.lockButtonFinish.toggle() self.lockButtonChangeIconStart() self.lockButtonChangeIconFinish() def cutFunction(self): """ Function used for cut a video from 'start' to 'finish' and replace in the 'curentFiles'(dictionary that holds all opend video) the curent video path with the path provided by the 'edit.cut' """ try: #if both buttons are pressed the the function can be called if (self.lockButtonStart.isChecked() == True and self.lockButtonFinish.isChecked() == True): try: #save the current index before cut because user can change the 'curentIndex' during execution indexOfRootVideo = self.curentIndex #call the cut function and the result path is saved in currentFiles self.curentFiles[indexOfRootVideo] = self.edit.cut( [self.curentFiles[indexOfRootVideo]], [ self.cutStart.toPlainText(), self.cutFinish.toPlainText() ]) #Set the new video self.mediaPlayer.setMedia( QMediaContent( QUrl.fromLocalFile( "../resources/videos/blackvideo.mp4"))) self.mediaPlayer.setMedia( QMediaContent( QUrl.fromLocalFile( self.curentFiles[indexOfRootVideo]))) #Reset all values for cut function except: print("Problem in at self.edit.cut or mediaPlayer") except: print("Problem in cutFunction function") """-----------------------</Cut functions>-------------------------------""" """-----------------------<Mirror functions>-----------------------------""" def mirrorVideo(self): if (self.curentIndex == -1 and self.totalIndex != -1): self.curentIndex = 0 try: indexOfRootVideo = self.curentIndex result = '' result = self.edit.video_mirroring( [self.curentFiles[indexOfRootVideo]]) if (result != ''): self.curentFiles[indexOfRootVideo] = result self.mediaPlayer.setMedia( QMediaContent( QUrl.fromLocalFile( "../resources/videos/blackvideo.mp4"))) self.mediaPlayer.setMedia( QMediaContent( QUrl.fromLocalFile( self.curentFiles[indexOfRootVideo]))) except: print("Problem in change mirror function") """-----------------------</Mirror functions>-----------------------------""" """-----------------------<Resoluton functions>-----------------------------""" def changeResolutionF(self): """ Function for changing the video resolution. """ if (self.curentIndex == -1 and self.totalIndex != -1): self.curentIndex = 0 currentResolution = self.ResolutionsList.currentText( )[0:len(self.ResolutionsList.currentText()) - 1] try: indexOfRootVideo = self.curentIndex self.curentFiles[indexOfRootVideo] = self.edit.video_resize( [self.curentFiles[indexOfRootVideo]], currentResolution) self.mediaPlayer.setMedia( QMediaContent( QUrl.fromLocalFile("../resources/videos/blackvideo.mp4"))) self.mediaPlayer.setMedia( QMediaContent( QUrl.fromLocalFile(self.curentFiles[indexOfRootVideo]))) except: print("Problem in change resolution") def changeResolutionDisplay(self): """ Usef for changing image in resolution section when the user select a resolution. """ resolutionsIconList = [ "../resources/icons/GUI_Icons/720.png", "../resources/icons/GUI_Icons/540.png", "../resources/icons/GUI_Icons/360.png", "../resources/icons/GUI_Icons/240.png", "../resources/icons/GUI_Icons/144.png" ] if (self.ResolutionsList.currentIndex() == 0): self.resolutionIcon.setPixmap(QPixmap(resolutionsIconList[0])) elif (self.ResolutionsList.currentIndex() == 1): self.resolutionIcon.setPixmap(QPixmap(resolutionsIconList[1])) elif (self.ResolutionsList.currentIndex() == 2): self.resolutionIcon.setPixmap(QPixmap(resolutionsIconList[2])) elif (self.ResolutionsList.currentIndex() == 3): self.resolutionIcon.setPixmap(QPixmap(resolutionsIconList[3])) elif (self.ResolutionsList.currentIndex() == 4): self.resolutionIcon.setPixmap(QPixmap(resolutionsIconList[4])) """-----------------------</Resoluton functions>--------------------------""" """-----------------------<Sound Repalce functions>--------------------------""" def openAudio(self): try: fileName = QFileDialog.getOpenFileName(self, "Open Audio") mimetypes.init() mimestart = mimetypes.guess_type(fileName[0])[0] if mimestart != None: mimestart = mimestart.split('/')[0] if mimestart == 'audio': print("Audio file detected") self.audioFile = fileName[0] self.audioFileCheck.setIcon( QIcon("../resources/icons/GUI_Icons/check.png")) else: QMessageBox.about( self, "Audio", "This is not an audio file.Please load an audio file.." ) print("Non audio file detected") else: QMessageBox.about(self, "Audio", "This file format is not accepted.") print("Not accepted file") except: QMessageBox.about(self, "Audio", "Changing sound track failed.") print("Problem in open audio function") def removeAudioFileFunction(self): self.audioFile = '' self.audioFileCheck.setIcon( QIcon("../resources/icons/GUI_Icons/ezgif-7-e04c11fb7018.png")) def changeAudioBackground(self): if (self.audioModeSelect.currentIndex() == 0): self.aduioModeImage.setPixmap( QPixmap("../resources/img/soundAdd.png")) elif (self.audioModeSelect.currentIndex() == 1): self.aduioModeImage.setPixmap( QPixmap("../resources/img/soundReplace.png")) def SoundReplaceFunction(self): if (self.curentIndex == -1 and self.totalIndex != -1): self.curentIndex = 0 audioMode = self.audioModeSelect.currentText() print(audioMode) try: if (self.audioFile != ''): if (self.totalIndex != -1): indexOfRootVideo = self.curentIndex self.curentFiles[ indexOfRootVideo] = self.edit.soundReplace( [self.curentFiles[indexOfRootVideo]], self.audioFile, audioMode) self.mediaPlayer.setMedia( QMediaContent( QUrl.fromLocalFile( "../resources/videos/blackvideo.mp4"))) self.mediaPlayer.setMedia( QMediaContent( QUrl.fromLocalFile( self.curentFiles[indexOfRootVideo]))) else: print("No video uploaded") else: print("No aduio file uploaded") except: print("Problem in SoundReplaceFunction") """-----------------------</Sound Repalce functions>-------------------------""" """-----------------------<GetFrame functions>-------------------------""" def GetFrameFunction(self): if (self.curentIndex == -1 and self.totalIndex != -1): self.curentIndex = 0 try: if (self.totalIndex != -1 and self.frameTime != ''): indexOfRootVideo = self.curentIndex print(self.frameTime) self.frameFilePath = '' self.frameFilePath = self.edit.getFrame( [self.curentFiles[indexOfRootVideo]], self.frameTime) self.extractedFrame.setPixmap(QPixmap(self.frameFilePath)) else: print("No video uploaded or frameTime is empty") except: print("Problem in GetFrameFunction") def saveFrame(self): try: fileName = QFileDialog.getSaveFileName(self, 'Save File', "img.jpg", '*.jpg') img = cv2.imread(self.frameFilePath) cv2.imwrite(fileName[0], img) except: QMessageBox.about(self, "Save image", "Problem during saving image.") print("Problem during saving image") """-----------------------</GetFrame functions>-------------------------""" """-----------------------<Add Subtitles functions>-----------------------""" def loadSubtitlesFunction(self): try: fileName = QFileDialog.getOpenFileName(self, "Open Subtitles") if (fileName[0].split('.')[-1] == "srt"): self.subtitlesFile = fileName[0] self.subtitlesCheck.setIcon( QIcon("../resources/icons/GUI_Icons/check.png")) else: QMessageBox.about( self, "Subtitles", "Couldn't load subtitles.Please use file with .srt extension." ) except: print("Problem in load Subtitles function") def removeSubtitlesFunction(self): self.subtitlesFile = '' self.subtitlesCheck.setIcon( QIcon("../resources/icons/GUI_Icons/ezgif-7-e04c11fb7018.png")) def addSubtitlesFunction(self): if (self.curentIndex == -1 and self.totalIndex != -1): self.curentIndex = 0 try: if (self.subtitlesFile != ''): if (self.totalIndex != -1): indexOfRootVideo = self.curentIndex self.curentFiles[ indexOfRootVideo] = self.edit.addSubtitles( [self.curentFiles[indexOfRootVideo]], self.subtitlesFile) self.mediaPlayer.setMedia( QMediaContent( QUrl.fromLocalFile( "../resources/videos/blackvideo.mp4"))) self.mediaPlayer.setMedia( QMediaContent( QUrl.fromLocalFile( self.curentFiles[indexOfRootVideo]))) else: print("No video uploaded") else: print("No subtitles file uploaded") except: print("Problem in addSubtitlesFunction") """-----------------------</Add Subtitles functions>----------------------""" """-----------------------</VideoGrep functions>--------------------------""" def loadSubtitlesVideoGrep(self): try: fileName = QFileDialog.getOpenFileName(self, "Open Subtitles") if (fileName[0].split('.')[-1] == "srt"): self.subtitlesFileVG = fileName[0] self.subtitlesCheckVideoGrep.setIcon( QIcon("../resources/icons/GUI_Icons/check.png")) else: QMessageBox.about( self, "Subtitles", "Couldn't load subtitles.Please use file with .srt extension." ) except: print("Problem in load Subtitles function") def removeSubtitlesVideoGrep(self): self.subtitlesFileVG = '' self.subtitlesCheckVideoGrep.setIcon( QIcon("../resources/icons/GUI_Icons/ezgif-7-e04c11fb7018.png")) def videoGrepFunction(self): try: if (self.subtitlesFileVG != ''): if (self.totalIndex != -1): indexOfRootVideo = self.curentIndex vgMode = self.VGMODE.currentText() if (vgMode == "Clasic"): words = self.vglistSpeciale.toPlainText().split(',') else: words = [ self.firstWord.toPlainText(), self.secondWord.toPlainText() ] self.curentFiles[ indexOfRootVideo] = self.edit.find_sequence( [self.curentFiles[indexOfRootVideo]], words, self.subtitlesFileVG, vgMode) self.mediaPlayer.setMedia( QMediaContent( QUrl.fromLocalFile( "../resources/videos/blackvideo.mp4"))) self.mediaPlayer.setMedia( QMediaContent( QUrl.fromLocalFile( self.curentFiles[indexOfRootVideo]))) else: print("No video uploaded") else: print("No subtitles file uploaded") except: print("Problem in video Grep Function") """-----------------------</VideoGrep functions>--------------------------""" """--------------------------<File functions>----------------------------""" def openFile(self): """ Function for opening a video file from taskbar. """ fileName = QFileDialog.getOpenFileName(self, "OpenVideo") #Checks if fileName is a valid file if fileName[0] != '': #Set to mediaPlayer object the file(video) self.mediaPlayer.setMedia( QMediaContent(QUrl.fromLocalFile(fileName[0]))) #Update total number of curent media opened self.totalIndex = self.totalIndex + 1 self.curentIndex = self.totalIndex #Update the curentFiles dict which holds the path for the opened videos self.curentFiles[self.totalIndex] = fileName[0] if (self.totalIndex == 0): self.curentIndex = 0 #Enable the play button after the video was set self.playButton.setEnabled(True) #Add media to the playlist self.playlist.addMedia( QMediaContent(QUrl.fromLocalFile(fileName[0]))) #Add item to the list for avalabile videos for concatenate self.concatenateVideoList.addItem(fileName[0].split('/')[-1]) #A new media was added so we sent a signal to updated List view self.model.layoutChanged.emit() """------------------------</File functions>----------------------------""" """------------------<Media player functions>---------------------------""" def playVideo(self): """" Function for controling the video Play/Pause """ #Checks the state of the video.If the video is playing it will be paused. if self.mediaPlayer.state() == QMediaPlayer.PlayingState: #Pause the video self.mediaPlayer.pause() else: #If the video is paused it will be playing self.mediaPlayer.play() def mediaStateChange(self, ): """ This function is changing the icon of the playButton. If the video is going from playing to "pause",the icon will change to pause icon.Otherwise the playingButton will change to "play" icon """ if self.mediaPlayer.state() == QMediaPlayer.PlayingState: #Change playButton icon into "Pause icon" self.playButton.setIcon( QIcon("../resources/icons/GUI_Icons/002-pause.png")) else: #Change playButton icon into "Play icon" self.playButton.setIcon( QIcon("../resources/icons/GUI_Icons/play.png")) def convert(self, seconds): """ Function for converting duration of the video(seconds) into Hour/minute/seconds and returning that format. """ seconds = seconds % (24 * 3600) hour = seconds // 3600 seconds %= 3600 minutes = seconds // 60 seconds %= 60 return "%d:%02d:%02d" % (hour, minutes, seconds) def positionChange(self, position): """ This function is activated when the video is changing the time.When that happens is updating the Time slider the new position and the time label. """ self.videoTimeSlider.setValue(position) #Convert position into seconds duration = position / 1000 self.frameTime = int(duration) self.videoTimeDisplay.setText(self.convert(duration)) if (self.lockButtonStart.isChecked() == False): self.cutStart.setText(self.convert(duration)) if (self.lockButtonFinish.isChecked() == False): self.cutFinish.setText(self.convert(duration)) def durationChange(self, duration): """ This function update the range of the time splider when the video position is changing. """ self.videoTimeSlider.setRange(0, duration) def setPosition(self, position): """ Sets the video time based on time slider. When the user is changing the time slider to a specific time,the video position is updated. """ self.mediaPlayer.setPosition(position) def skipforwadFunction(self): self.mediaPlayer.setPosition(self.mediaPlayer.position() + 15000) def skipbackFunction(self): self.mediaPlayer.setPosition(self.mediaPlayer.position() - 15000) def rewindFunction(self): if (self.speed - 0.1 >= 0.1): self.speed -= 0.1 self.mediaPlayer.setPlaybackRate(self.speed) self.speedLabel.setText(str(round(self.speed, 2)) + "x") def fastForwardFunction(self): if (self.speed + 0.1 <= 2.1): self.speed += 0.1 self.mediaPlayer.setPlaybackRate(self.speed) self.speedLabel.setText(str(round(self.speed, 2)) + "x") def volumeControl(self, volume): """ This function is used for changing the volume of the video and update the volume label.Also,is used to change the color of the slider based on the volume.If is < 50 is green, <50 && < 75 yellow and red for 74 > """ self.volume.setValue(volume) self.mediaPlayer.setVolume(volume) self.volumeTextDisplay.setText(str(volume) + "%") if (volume <= 50): #green self.volume.setStyleSheet(styleSheet.volumeStageOne) elif (volume > 50 and volume <= 75): #yellow self.volume.setStyleSheet(styleSheet.volumeStageTwo) else: #red self.volume.setStyleSheet(styleSheet.volumeStageThree) #If the volume is zero the icon of the volume is changed if (volume == 0): self.volumeIcon.setIcon( QIcon("../resources/icons/GUI_Icons/mute.png")) else: self.volumeIcon.setIcon( QIcon("../resources/icons/GUI_Icons/speaker.png")) def playlist_selection_changed(self, ix): # We receive a QItemSelection from selectionChanged. i = ix.indexes()[0].row() self.playlist.setCurrentIndex(i) self.curentIndex = i self.speed = 1 self.mediaPlayer.setPlaybackRate(self.speed) self.speedLabel.setText(str(self.speed) + "x") try: self.mediaPlayer.setMedia( QMediaContent( QUrl.fromLocalFile(self.curentFiles[self.curentIndex]))) #Reset cut text and icon self.restCutButtons() except: print( "Error in playlist_selection_changed function.Media player couldn't be updated" ) def playlist_position_changed(self, i): if i > -1: ix = self.model.index(i) print("playlist_position_changed") self.videoFiles.setCurrentIndex(ix) def RemoveVideo(self): """ This function is connected to the remove button on Project files. It removes the file from List view and curentFiles dictionary.It also changethe mediaPlayer output based on what media was left.If the is media left the video output will be the firs mediaFile from the list.If the List view have no media left,the video output will be an video of 1 seconds with black background to clear the screen. """ if (self.totalIndex != -1): if (self.curentIndex != -1): try: #Delete media from index "curentIndex" self.playlist.removeMedia(self.curentIndex) #Delete video from concatenate ComboBox self.concatenateVideoList.removeItem(self.curentIndex) try: #Delete the file name from index "curentIndex" from curentFiles del self.curentFiles[self.curentIndex] #Decrement the total number of videos opened self.totalIndex = self.totalIndex - 1 #Update index of every video from curentFiles self.SortFilesIndex() if (self.totalIndex == -1): try: self.mediaPlayer.setMedia( QMediaContent( QUrl.fromLocalFile( "../resources/videos/blackvideo.mp4" ))) #Block the play button self.playButton.setEnabled(False) #Reset the time slider self.videoTimeSlider.setValue(0) #Reset the time slider self.videoTimeSlider.setRange(0, 0) except: print("The black video couldn't be loaded") else: try: self.mediaPlayer.setMedia( QMediaContent( QUrl.fromLocalFile( self.curentFiles[0]))) except: print("The mediaPlayer couldn't be updated") except: print("The value from index " + str(self.curentIndex) + " could not be deleted form curentFiles") except: print("Media cannot be deleted from playlist") else: #Block the play button self.playButton.setEnabled(False) #Reset the time slider self.videoTimeSlider.setValue(0) #Reset the time slider self.videoTimeSlider.setRange(0, 0) def saveVideoFunction(self): try: import shutil extension = self.curentFiles[self.curentIndex].split('.')[-1] extension = "*." + extension fileName = QFileDialog.getSaveFileName(self, 'Save video', "video_name", extension) shutil.move(self.curentFiles[self.curentIndex], fileName[0]) print("Save video Done") except: print("Error during the video saving") def SortFilesIndex(self): """ This function sort the curentFiles dictionary. When an element is deleted from curentFiles the function sort the index of the curentFiles ascending. """ newIndex = 0 newCurentFiles = {} #loop through the curentFiles and update the index for key in self.curentFiles: newCurentFiles[newIndex] = self.curentFiles[key] newIndex += 1 #curentFiles files is updated to the new dictionary of files self.curentFiles = newCurentFiles.copy() def soundShortcutKey(self): self.editMenu.setCurrentIndex(4) def getFrameShortcutKey(self): self.editMenu.setCurrentIndex(5) def cutShortcutKey(self): self.editMenu.setCurrentIndex(1) def concatShortcutKey(self): self.editMenu.setCurrentIndex(0) def mirrorShortcutKey(self): self.editMenu.setCurrentIndex(3) """------------------</Media player functions>-----------------------""" """----------------------<Thread functions>--------------------------""" """---<Concatenate>---""" def concatenateThreadFunction(self): #If is true that means that no thread is running and can start a thread if (self.threadmanager == True): try: #If is false that means that a thread is already executing self.threadmanager = False worker = Worker(self.concatenate) #When the thread is done threadmanager will be True worker.signals.finished.connect(self.ReleaseThread) self.pool.start(worker) except: print("Problem with concatenate thread") else: print("A thread is already running") """---</Concatenate>---""" """---<Cut>---""" def cutThreadFunction(self): if (self.threadmanager == True): try: self.threadmanager = False worker = Worker(self.cutFunction) worker.signals.finished.connect(self.restCutButtons) worker.signals.finished.connect(self.ReleaseThread) self.pool.start(worker) except: print("Problem with cut thread") else: print("A thread is already running") """---</Cut>---""" """---<Resolution>---""" def changeResolutionThread(self): if (self.threadmanager == True): try: self.threadmanager = False worker = Worker(self.changeResolutionF) worker.signals.finished.connect(self.ReleaseThread) self.pool.start(worker) except: print("Problem with resolution thread") else: print("A thread is already running") """---</Resolution>---""" """---<Mirror>---""" def mirrorThread(self): if (self.threadmanager == True): try: self.threadmanager = False worker = Worker(self.mirrorVideo) worker.signals.finished.connect(self.ReleaseThread) self.pool.start(worker) except: print("Problem with mirror thread") else: print("A thread is already running") """---</Mirror>---""" """---<SoundReplace>---""" def SoundReplaceThread(self): if (self.threadmanager == True): try: self.threadmanager = False worker = Worker(self.SoundReplaceFunction) worker.signals.finished.connect(self.ReleaseThread) worker.signals.finished.connect(self.removeAudioFileFunction) self.pool.start(worker) except: print("Problem with SoundReplace thread") else: print("A thread is already running") """---</SoundReplace>---""" def ReleaseThread(self): self.threadmanager = True print(self.curentFiles) """---<GetFrame>---""" def GetFrameThread(self): if (self.threadmanager == True): try: self.threadmanager = False worker = Worker(self.GetFrameFunction) worker.signals.finished.connect(self.ReleaseThread) self.pool.start(worker) except: print("Problem with getFrame thread") else: print("A thread is already running") """---</GetFrame>---""" """---<AddSubtitles>---""" def addSubtitlesThread(self): if (self.threadmanager == True): try: self.threadmanager = False worker = Worker(self.addSubtitlesFunction) worker.signals.finished.connect(self.ReleaseThread) worker.signals.finished.connect(self.removeSubtitlesFunction) self.pool.start(worker) except: print("Problem with add subtitles thread") else: print("A thread is already running") """---</AddSubtitles>--""" """---</VideoGrep>--""" def videoGrepThread(self): if (self.threadmanager == True): try: self.threadmanager = False worker = Worker(self.videoGrepFunction) worker.signals.finished.connect(self.ReleaseThread) worker.signals.finished.connect(self.removeSubtitlesVideoGrep) self.pool.start(worker) except: print("Problem with VideoGrep thread") else: print("A thread is already running") """---</VideoGrep>--""" """---------------------</Thread functions>--------------------------""" def deleteAllTemp(self): import os, shutil folder = ProjectFolders.tmpDir for filename in os.listdir(folder): file_path = os.path.join(folder, filename) try: if os.path.isfile(file_path) or os.path.islink(file_path): os.unlink(file_path) elif os.path.isdir(file_path): shutil.rmtree(file_path) except Exception as e: print('Failed to delete %s. Reason: %s' % (file_path, e)) def closeEvent(self, event): """ Popup a dialog when the user is trying to close the main app. """ reply = QMessageBox.question( self, 'Window Close', 'Are you sure you want to close the window?', QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: if (self.threadmanager == True): try: self.threadmanager = False worker = Worker(self.deleteAllTemp) worker.signals.finished.connect(self.ReleaseThread) self.pool.start(worker) except: print("Problem with getFrame thread") else: print("A thread is already running") event.accept() print('Window closed') else: event.ignore()
class Ui_MainWindow(object): # for stylizing progress bar progress_styleSheet = """ QSlider::groove:horizontal { background: dark; height: 40px; } QSlider::sub-page:horizontal { background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #07406a, stop: 1 #696969); background: qlineargradient(x1: 0, y1: 0.2, x2: 1, y2: 1, stop: 0 #696969, stop: 1 #07406a); height: 40px; } QSlider::add-page:horizontal { background: #696969; height: 40px; } QSlider::handle:horizontal { background: #aaa; border: 0px; width: 5px; margin-top: 0px; margin-bottom: 0px; border-radius: 0px; } """ def setupUi(self, MainWindow): MainWindow.setObjectName("Music Player") MainWindow.resize(640, 480) MainWindow.setAcceptDrops(True) self.playlist = QMediaPlaylist() self.player = QMediaPlayer() self.player.setPlaylist(self.playlist) self.player.setVolume(20) self.player.setPlaybackRate(jdata['playback']) self.playlist.setPlaybackMode(jdata['curr_playstyle']) self.centralwidget = QtWidgets.QWidget(MainWindow) self.centralwidget.setObjectName("centralwidget") self.gridLayout = QtWidgets.QGridLayout(self.centralwidget) self.gridLayout.setObjectName("gridLayout") self.gridLayout_2 = QtWidgets.QGridLayout() self.gridLayout_2.setObjectName("gridLayout_2") self.progress = QtWidgets.QSlider(self.centralwidget) self.progress.setOrientation(QtCore.Qt.Horizontal) self.progress.setStyleSheet(self.progress_styleSheet) self.progress.setObjectName("progress") self.progress.setSingleStep(1) self.progress.setPageStep(1) self.gridLayout_2.addWidget(self.progress, 1, 0, 1, 3) self.stop_but = QtWidgets.QPushButton(self.centralwidget) self.stop_but.setObjectName("stop_but") self.gridLayout_2.addWidget(self.stop_but, 0, 2, 1, 1) self.backward_b = QtWidgets.QPushButton(self.centralwidget) self.backward_b.setObjectName("backward") self.gridLayout_2.addWidget(self.backward_b, 0, 0, 1, 1) self.cont_pau = QtWidgets.QPushButton(self.centralwidget) self.cont_pau.setObjectName("cont_pau") self.gridLayout_2.addWidget(self.cont_pau, 0, 1, 1, 1) self.playstyle = QtWidgets.QPushButton(self.centralwidget) self.playstyle.setObjectName("playstyle") self.playstyle.setText("Loop") self.gridLayout_2.addWidget(self.playstyle, 0, 4, 1, 1) self.forward_b = QtWidgets.QPushButton(self.centralwidget) self.forward_b.setObjectName("forward") self.gridLayout_2.addWidget(self.forward_b, 0, 3, 1, 1) self.time_remain = QtWidgets.QLabel(self.centralwidget) self.time_remain.setObjectName("time_remain") self.gridLayout_2.addWidget(self.time_remain, 1, 3, 1, 1) self.verticalLayout_2 = QtWidgets.QVBoxLayout() self.verticalLayout_2.setObjectName("verticalLayout_2") self.volume = QtWidgets.QLabel(self.centralwidget) self.volume.setAlignment(QtCore.Qt.AlignCenter) self.volume.setObjectName("volume") self.verticalLayout_2.addWidget(self.volume) self.volume_ctrl = QtWidgets.QSlider(self.centralwidget) self.volume_ctrl.setOrientation(QtCore.Qt.Horizontal) self.volume_ctrl.setObjectName("volume_ctrl") self.volume_ctrl.setValue(jdata["volume"]) self.volume_ctrl.setRange(-1, 101) self.volume_ctrl.setSingleStep(1) self.volume_ctrl.setPageStep(1) self.verticalLayout_2.addWidget(self.volume_ctrl) self.gridLayout_2.addLayout(self.verticalLayout_2, 1, 4, 1, 1) self.gridLayout.addLayout(self.gridLayout_2, 1, 0, 1, 1) self.verticalLayout = QtWidgets.QVBoxLayout() self.verticalLayout.setObjectName("verticalLayout") self.playing = QtWidgets.QLabel(self.centralwidget) font = QtGui.QFont() font.setFamily("Arial") font.setPointSize(12) self.playing.setFont(font) self.playing.setAlignment(QtCore.Qt.AlignCenter) self.playing.setObjectName("playing") self.playing.setScaledContents(True) self.playing.setWordWrap(True) self.verticalLayout.addWidget(self.playing) self.gridLayout.addLayout(self.verticalLayout, 0, 0, 1, 1) self.verticalLayout_3 = QtWidgets.QVBoxLayout() self.verticalLayout_3.setObjectName("verticalLayout_3") self.song_list = ListWidget(self.centralwidget) self.song_list.setMinimumSize(QtCore.QSize(183, 0)) self.song_list.setAcceptDrops(True) self.song_list.setDragDropMode( QtWidgets.QAbstractItemView.InternalMove) self.song_list.setObjectName("song_list") self.song_list.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.song_list.setWordWrap(True) self.verticalLayout_3.addWidget(self.song_list) self.gridLayout.addLayout(self.verticalLayout_3, 0, 1, 2, 1) MainWindow.setCentralWidget(self.centralwidget) self.menubar = QtWidgets.QMenuBar(MainWindow) self.menubar.setGeometry(QtCore.QRect(0, 0, 632, 25)) self.menubar.setObjectName("menubar") self.menuFile = QtWidgets.QMenu(self.menubar) self.menuFile.setObjectName("menuFile") self.menuAbout = QtWidgets.QMenu(self.menubar) self.menuAbout.setObjectName("menuAbout") MainWindow.setMenuBar(self.menubar) self.statusbar = QtWidgets.QStatusBar(MainWindow) self.statusbar.setObjectName("statusbar") MainWindow.setStatusBar(self.statusbar) self.actionOpen = QtWidgets.QAction(MainWindow) self.actionOpen.setObjectName("actionOpen") self.actionLisence_Information = QtWidgets.QAction(MainWindow) self.actionLisence_Information.setObjectName( "actionLisence_Information") self.menuFile.addAction(self.actionOpen) self.menuAbout.addAction(self.actionLisence_Information) self.menubar.addAction(self.menuFile.menuAction()) self.menubar.addAction(self.menuAbout.menuAction()) spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.gridLayout_2.addItem(spacerItem, 0, 5, 1, 1) self.gridLayout.addLayout(self.gridLayout_2, 1, 0, 1, 1) self.info = QMessageBox() self.info.setWindowTitle("License Information") self.info.setText("MIT License\n\nCopyright (c) 2021 Marganotvke") self.err = QMessageBox() self.err.setWindowTitle("Error Loading File") self.err.setText("Error Loading File!\n\nPlease check file type!") self.retranslateUi(MainWindow) QtCore.QMetaObject.connectSlotsByName(MainWindow) self.actionOpen.triggered.connect(lambda: self.open_trig()) self.actionLisence_Information.triggered.connect( lambda: self.info.exec_()) self.cont_pau.clicked.connect(lambda: self.cur_playing()) self.volume_ctrl.sliderMoved.connect(lambda: self.change_vol()) self.stop_but.clicked.connect(lambda: self.stop_playing()) self.forward_b.clicked.connect(lambda: self.forward()) self.backward_b.clicked.connect(lambda: self.backward()) self.playstyle.clicked.connect(lambda: self.playlist_style()) self.song_list.dropped.connect(lambda e: self.start_play(e)) self.song_list.itemDoubleClicked.connect( lambda: self.jump_start(self.song_list.currentRow())) self.song_list.model().rowsAboutToBeMoved.connect( lambda e, f, g, h, i: self.re_arr(f, i)) self.song_list.dele.connect(lambda e: self.delete_q(e)) self.player.metaDataAvailableChanged.connect(lambda: self.set_meta()) self.player.stateChanged.connect(lambda e: self.set_state(e)) self.player.positionChanged.connect(lambda e: self.set_pos(e)) self.progress.sliderMoved.connect(lambda e: self.skip_to(e)) def retranslateUi(self, MainWindow): _translate = QtCore.QCoreApplication.translate MainWindow.setWindowTitle(_translate("MainWindow", "Music Player")) self.stop_but.setText(_translate("MainWindow", "Stop")) self.backward_b.setText(_translate("MainWindow", "Backward")) self.cont_pau.setText(_translate("MainWindow", "Load Song")) self.playstyle.setText( _translate("MainWindow", f"{playstyles[jdata['curr_playstyle']]}")) self.forward_b.setText(_translate("MainWindow", "Forward")) self.time_remain.setText(_translate("MainWindow", "--/--")) self.volume.setText( _translate("MainWindow", f"Volume: {jdata['volume']}%")) self.playing.setText( _translate("MainWindow", "Currently playing: None")) self.menuFile.setTitle(_translate("MainWindow", "File")) self.menuAbout.setTitle(_translate("MainWindow", "About")) self.actionOpen.setText(_translate("MainWindow", "Open")) self.actionLisence_Information.setText( _translate("MainWindow", "License Information")) self.actionOpen.setShortcut(_translate("MainWindow", "Ctrl+O")) def re_arr(self, e=None, f=None): f -= 1 if self.song_list.count() == f else 0 media = self.playlist.media(e) self.playlist.removeMedia(e) self.playlist.insertMedia(f, media) def jump_start(self, index=None): if self.playlist.currentIndex() != index: self.playlist.setCurrentIndex(index) self.player.stop() self.start_play() def start_play(self, url=None): if url: print("Emitted from drop event:", url) threading.Thread(target=self.open_file(url)).start() self.player.play() def stop_playing(self): self.player.stop() def cur_playing(self): if self.playlist.isEmpty(): self.open_trig() else: if self.player.state() == 1: self.player.pause() self.cont_pau.setText("Play") else: self.player.play() self.cont_pau.setText("Pause") def forward(self): self.playlist.next() def backward(self): if round(self.player.position() / 1000) >= 5: self.player.setPosition(0) if not self.player.state(): self.start_play() else: self.playlist.previous() def open_trig(self): filename = QFileDialog.getOpenFileName( None, 'Open File', filter= "MPEG-2 (*.mp3);;WAVE Audio (*.wav,*.aif,*.aiff);;MPEG-1/DD/ACC (*.aac);;MIDI (*.mid,*.midi);;Windows Media Audio (*.wma);;Xiph.Org OGG Vorbis (*.ogg);;NeXT SouND (*.snd);;FLAC (*.flac);;All files(*)" ) threading.Thread(target=self.open_file(filename[0])).start() def open_file(self, file_url): url = QUrl.fromLocalFile(file_url) avai = ('.mp3', '.wav', 'ogg', '.aac', '.aif', '.aiff', '.mid', '.midi', '.wma', '.snd', '.flac') if url.url() != '' and not url.fileName().lower().endswith(avai): self.err.exec_() elif url.fileName().lower().endswith(avai): song_name = url.fileName().split(".")[0] self.song_list.addItem(song_name) if self.playlist.isEmpty(): self.playlist.addMedia(QMediaContent(url)) self.cont_pau.setText("Pause") self.start_play() else: self.playlist.addMedia(QMediaContent(url)) def delete_q(self, i): self.playlist.removeMedia(i) self.song_list.takeItem(i) def playlist_style(self): i = self.playlist.playbackMode() i += 1 i *= (i < 5) self.playlist.setPlaybackMode(i) self.playstyle.setText(f"{playstyles[self.playlist.playbackMode()]}") jdata["curr_playstyle"] = self.playlist.playbackMode() print(playstyles[self.playlist.playbackMode()]) def change_vol(self): vol = self.volume_ctrl.value() self.player.setVolume(vol) self.volume.setText(f"Volume: {vol}%") jdata["volume"] = vol def set_pos(self, e): if self.player.isMetaDataAvailable(): t = self.player.duration() dt = datetime.datetime.fromtimestamp( round(self.player.duration() / 1000)).strftime('%M:%S') dt_ct = datetime.datetime.fromtimestamp(round( e / 1000)).strftime('%M:%S') self.thread = threading.Thread( target=self.progress.setValue(round((e / t) * 100))) self.thread.start() self.time_remain.setText(f"{dt_ct}/{dt}") def set_meta(self): if self.player.isMetaDataAvailable(): self.duraiton = self.player.duration() self.progress.setRange(0, 100) self.playing.setText( f"Currently playing: {self.player.currentMedia().canonicalUrl().fileName().split('.')[0]}" ) else: if self.playlist.isEmpty(): self.cont_pau.setText("Load Song") self.time_remain.setText("--/--") def set_state(self, e): if not e: self.playing.setText("Currently playing: None") if self.playlist.isEmpty(): self.cont_pau.setText("Load Song") else: self.cont_pau.setText("Play") else: if self.player.isMetaDataAvailable(): self.playing.setText( f"Currently playing: {self.player.currentMedia().canonicalUrl().fileName().split('.')[0]}" ) def skip_to(self, e): self.player.setPosition(round((e / 100) * self.player.duration()))
class Win(QWidget): """ 重写 QWidget 类,自定义创建一个播放器窗体 """ def __init__(self): """ 构造方法:类的初始化 """ # 初始化父类 super().__init__() # 定义类属性 self.moveMode = False # 是否开启窗口移动模式 self.windowPoint = QPoint(0, 0) # 记录窗口移动时位置 self.startPoint = QPoint(0, 0) # 记录窗口移动前位置 # 设定窗体基本属性(标题、尺寸、图标等) self.setWindowTitle("简易MP3音乐播放器") # 窗口标题 self.setObjectName("app") # 对象名称 self.resize(260, 348) # 窗口尺寸 self.setFixedSize(self.width(), self.height()) # 禁止拉伸窗口大小 self.setWindowFlags(Qt.FramelessWindowHint) # 窗口无边框 self.setAutoFillBackground(True) # 允许背景填充 palette = QPalette() palette.setBrush(QPalette.Background, QBrush(QPixmap('./img/bg.jpg'))) self.setPalette(palette) # 填充背景图片 # 定义窗体组件(定义按钮、标签、列表框、滑块条等)及其基本属性 self.create_widgets() # 创建播放器和播放列表 self.media = QMediaPlayer() self.list = QMediaPlaylist() self.media.setPlaylist(self.list) # 设置窗体组件的样式和布局(颜色、图片、背景、位置、字体等) self.set_style() # 绑定事件(信号槽) self.bind_slots() # 其他初始化操作 self.list.setPlaybackMode(QMediaPlaylist.Loop) # 循环播放 self.media.setVolume(100) # 设置默认音量 self.slider_volume.setRange(0, 100) self.slider_volume.setValue(100) self.slider_pos.setRange(0, 0) # 设置默认进度 self.slider_pos.setValue(0) self.label_title.setText("简易MP3音乐播放器") # 设置默认标题 # 加载已保存的播放列表 self.load_playlist() def create_widgets(self): """ 定义窗体组件(定义按钮、标签、列表框、滑块条等)及其基本属性 :return: """ self.frame_player = QFrame(self) # 播放器布局框 self.frame_player.setObjectName("frame_player") self.frame_player.setGeometry(0, 0, 260, 120) self.frame_list = QFrame(self) # 播放列表布局框 self.frame_list.setObjectName("frame_list") self.frame_list.setGeometry(0, 120, 260, 228) self.btn_play = QPushButton(self.frame_player) # 播放按钮 self.btn_play.setObjectName("btn_play") self.btn_play.setGeometry(8, 78, 32, 32) self.btn_pause = QPushButton(self.frame_player) # 暂停按钮 self.btn_pause.setObjectName("btn_pause") self.btn_pause.setGeometry(8, 78, 32, 32) self.btn_pause.setVisible(False) self.btn_stop = QPushButton(self.frame_player) # 停止按钮 self.btn_stop.setObjectName("btn_stop") self.btn_stop.setGeometry(40, 78, 32, 32) self.btn_previous = QPushButton(self.frame_player) # 上一曲按钮 self.btn_previous.setObjectName("btn_previous") self.btn_previous.setGeometry(72, 78, 32, 32) self.btn_next = QPushButton(self.frame_player) # 下一曲按钮 self.btn_next.setObjectName("btn_next") self.btn_next.setGeometry(104, 78, 32, 32) self.btn_mute = QPushButton(self.frame_player) # 静音按钮 self.btn_mute.setObjectName("btn_mute") self.btn_mute.setGeometry(144, 90, 20, 20) self.label_title = QLabel(self.frame_player) # 歌曲标题标签 self.label_title.setObjectName("label_title") self.label_title.setGeometry(8, 38, 244, 32) self.label_time = QLabel("00:00", self.frame_player) # 时间标签 self.label_time.setObjectName("label_time") self.label_time.setGeometry(220, 74, 30, 20) self.slider_volume = QSlider(self.frame_player) # 音量滑块条 self.slider_volume.setObjectName("slider_volume") self.slider_volume.setGeometry(172, 90, 80, 20) self.slider_volume.setOrientation(Qt.Horizontal) self.slider_pos = QSlider(self.frame_player) # 播放进度条 self.slider_pos.setObjectName("slider_pos") self.slider_pos.setGeometry(0, 114, 260, 6) self.slider_pos.setOrientation(Qt.Horizontal) self.btn_add = QPushButton(self.frame_list) # 添加文件按钮 self.btn_add.setObjectName("btn_add") self.btn_add.setGeometry(8, 196, 24, 24) self.btn_remove = QPushButton(self.frame_list) # 移除文件按钮 self.btn_remove.setObjectName("btn_remove") self.btn_remove.setGeometry(40, 196, 24, 24) self.play_list = QListWidget(self.frame_list) # 播放列表 self.play_list.setObjectName("play_list") self.play_list.setGeometry(8, 8, 244, 180) self.label_version = QLabel(self.frame_list) # 软件版本说明标签 self.label_version.setObjectName("label_version") self.label_version.setGeometry(72, 196, 172, 24) self.label_version.setText("Version: 1.0 | By: yowfung") self.btn_close = QPushButton(self) # 关闭窗口按钮 self.btn_close.setObjectName("btn_close") self.btn_close.setGeometry(236, 0, 24, 24) def set_style(self): """ 设置窗体组件的样式和布局(颜色、图片、背景、位置、字体等) :return: """ # 通过各种选择器的方式,并结合qss样式表对组件进行样式美化和布局 self.setStyleSheet( # 窗体全局样式 "QWidget{font-family:'微软雅黑'}" # 播放器框和播放列表框的样式 "QFrame#frame_player{background-color: rgba(0,0,0,0)}" "QFrame#frame_list{background-color:rgba(200, 240, 255, 0.3)}" # 歌曲标题标签、时间标签、版本说明标签的样式 "QLabel#label_title{font-size:18px; color: white}" "QLabel#label_time{color: #bbb; font-size:11px; }" "QLabel#label_version{color:#444;font-size:12px}" # 音量滑块条、进度条样式 "QSlider#slider_volume::groove:horizontal{height:4px;background:rgb(167,176,255)}" "QSlider#slider_volume::handle:horizontal{width:4px;background:rgb(180, 220, 255);margin:-2px 0px;}" "QSlider#slider_pos::groove:horizontal{height:2px;background:rgb(50,80,120)}" "QSlider#slider_pos::handle:horizontal{width:6px;background:rgb(240, 50, 60);margin:-2px 0;border-radius:1px}" # 按钮样式 "QPushButton{border: none;}" "QPushButton:pressed{margin: 1px}" "QPushButton#btn_play:pressed, QPushButton#btn_pause:pressed, QPushButton#btn_stop:pressed{margin:2px}" "QPushButton#btn_play{image: url('./img/icons/play.png')}" "QPushButton#btn_pause{image: url('./img/icons/pause.png')}" "QPushButton#btn_stop{image: url('./img/icons/stop.png')}" "QPushButton#btn_previous{image: url('./img/icons/previous.png')}" "QPushButton#btn_next{image: url('./img/icons/next.png')}" "QPushButton#btn_mute{image: url('./img/icons/volume-normal.png')}" "QPushButton#btn_add{image: url('./img/icons/add.png')}" "QPushButton#btn_remove{image: url('./img/icons/remove.png')}" "QPushButton#btn_close{image: url('./img/icons/close.png')}" # 播放列表框样式 "QListWidget{border:1px solid #ccc; font-size:12px;color:#444;background-color:rgba(255,255,255,0.6)}" "QListWidget::Item:hover{background-color:rgba(120,120,120,0.15);color:black}" "QListWidget::Item:selected{background-color:rgba(120,120,150,0.3);color:#208}" # 垂直滚动条样式 "QScrollBar:vertical{background-color:#eee;width:6px}" "QScrollBar::handle:vertical{background-color:#ccc;min-height:16px}" "QScrollBar::handle::vertical:hover {background-color:#bbb;}" "QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical {height: 0}" # 水平滚动条样式 "QScrollBar:horizontal{background-color: #eee;height: 6px;}" "QScrollBar::handle:horizontal {background-color:#ccc;min-width:16px;}" "QScrollBar::handle::horizontal:hover {background-color:#bbb;}" "QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal {width:0;}" ) def bind_slots(self): """ 绑定信号槽 :return: """ self.btn_close.clicked.connect(self.close) # 关闭按钮:被单击 self.btn_play.clicked.connect(self.con_btn_clicked_event) # 播放按钮:被单击 self.btn_pause.clicked.connect(self.con_btn_clicked_event) # 暂停按钮:被单击 self.btn_stop.clicked.connect(self.con_btn_clicked_event) # 停止按钮:被单击 self.btn_next.clicked.connect(self.con_btn_clicked_event) # 下一曲按钮:被单击 self.btn_previous.clicked.connect( self.con_btn_clicked_event) # 上一曲按钮:被单击 self.btn_mute.clicked.connect(self.con_btn_clicked_event) # 静音按钮:被单击 self.btn_add.clicked.connect(self.con_btn_clicked_event) # 添加文件按钮:被单击 self.btn_remove.clicked.connect( self.con_btn_clicked_event) # 移除项目按钮:被单击 self.slider_volume.valueChanged.connect( self.vol_changed_event) # 音量滑块条:值被改变 self.slider_pos.sliderReleased.connect( self.progress_changed_event) # 进度滑块条:值被改变 self.play_list.doubleClicked.connect( self.list_double_clicked_event) # 播放列表:被双击 self.media.positionChanged.connect( self.pos_changed_event) # 媒体播放器:播放位置被改变 self.media.durationChanged.connect( self.duration_changed_event) # 媒体播放器:时间区间被改变 self.media.stateChanged.connect( self.state_changed_event) # 媒体播放器:播放状态被改变 self.list.currentIndexChanged.connect( self.cur_changed_event) # 媒体播放列表:当前播放项被改变 def closeEvent(self, e): """ 重写 closeEvent:关闭程序前停止播放并保存记录 :param e: :return: """ self.save_playlist() self.media.stop() def mousePressEvent(self, e): """ 重写 mousePressEvent:鼠标在顶部按下时进入“移动窗口模式” :param e: :return: """ if e.button() == Qt.LeftButton \ and 2 <= e.pos().x() <= self.width() - 2 \ and 2 <= e.pos().y() <= 28: self.moveMode = True self.startPoint = e.globalPos() self.windowPoint = self.geometry().topLeft() def mouseMoveEvent(self, e): """ 重写 mouseMoveEvent:在“移动窗口模式”中进行窗口位置移动 :param e: :return: """ if e.buttons() & Qt.LeftButton and self.moveMode: re_point = e.globalPos() - self.startPoint self.move(self.windowPoint + re_point) def mouseReleaseEvent(self, e): """ 重写 mouseReleaseEvent:鼠标释放时离开“移动窗口模式” :param e: :return: """ self.moveMode = False def con_btn_clicked_event(self): """ 播放/暂停/上一曲/下一曲/停止/静音/添加文件/移除文件等控制按钮鼠标被单击事件 :return: """ # 取出事件对象(信号发送者) sender = self.sender() # 播放 if sender.objectName() == "btn_play": self.media.play() # 暂停 if sender.objectName() == "btn_pause": self.media.pause() # 停止 if sender.objectName() == "btn_stop": self.media.stop() # 上一曲 if sender.objectName() == "btn_previous": self.previous() # 下一曲 if sender.objectName() == "btn_next": self.next() # 静音 if sender.objectName() == "btn_mute": # 如果音量为0,或者原先未静音,则设置成静音 status = self.slider_volume.value() is 0 or not self.media.isMuted( ) self.media.setMuted(status) img = "./img/icons/volume-mute.png" if status else "./img/icons/volume-normal.png" self.btn_mute.setStyleSheet("QPushButton#btn_mute{image:url('" + img + "')}") # 静音按钮的图标样式 # 添加文件到列表 if sender.objectName() == "btn_add": self.add_to_list() # 从列表移除 if sender.objectName() == "btn_remove": self.remove_from_list() def list_double_clicked_event(self): """ 播放列表框中的项目被双击事件 :return: """ # 播放选中的歌曲 index = self.play_list.currentRow() if 0 <= index < self.play_list.count(): self.list.setCurrentIndex(index) self.media.play() def vol_changed_event(self, val): """ 音量滑块条值被改变事件:调节音量 :return: """ self.media.setVolume(val) self.media.setMuted(val is 0) # 音量为0时设置为静音 img = "./img/icons/volume-mute.png" if val is 0 else "./img/icons/volume-normal.png" self.btn_mute.setStyleSheet("QPushButton#btn_mute{image:url('" + img + "')}") # 静音按钮图标样式 def state_changed_event(self, state): """ 播放状态被改变事件:变化播放按钮和暂停按钮的可视状态 :param state: :return: """ self.btn_play.setVisible( True if state != 1 else False) # 只要不是“正在播放”,就显示播放按钮 self.btn_pause.setVisible( True if state == 1 else False) # 只要“正在播放”,就显示暂停按钮 def progress_changed_event(self): """ 播放进度条值被改变事件:定位播放 :return: """ # 将播放位置设置为进度条调节的位置 self.media.setPosition(self.slider_pos.value()) def pos_changed_event(self, position): """ 播放位置被改变事件:更新播放时间和进度条位置 :return: """ time = self.calc_time(position) # 计算播放时间 self.label_time.setText(time) # 显示播放时间 self.slider_pos.setValue(position) # 设置播放进度 def duration_changed_event(self, duration): """ 时间区间被改变事件:更新进度条区间 :param duration: :return: """ self.slider_pos.setRange(0, duration) def cur_changed_event(self, index): """ 当前播放索引被改变事件:更新播放列表索引 :param index: :return: """ self.play_list.setCurrentRow(index) # 在列表中选中已显示正在播放的项 self.label_title.setText(self.play_list.currentItem().text()) # 显示歌曲标题 self.label_time.setText("00:00") # 设置为默认时间 self.save_playlist() # 保存播放列表记录 def next(self): """ 下一曲 :return: """ # 判断播放列表是否为空 if self.list.mediaCount() < 0: return index = self.list.currentIndex() index = 0 if index + 1 >= self.list.mediaCount( ) else index + 1 # 如果当前是最后一首,则下一首就为第一首 self.list.setCurrentIndex(index) self.media.play() def previous(self): """ 上一曲 :return: """ # 判断播放列表是否为空 if self.list.mediaCount() < 0: return index = self.list.currentIndex() index = self.list.mediaCount( ) - 1 if index - 1 < 0 else index - 1 # 如果当前为第一首,则上一首就为最后一首 self.list.setCurrentIndex(index) self.media.play() def add_to_list(self): """ 添加项目到播放列表中 """ # 弹出多文件选择对话框,默认目录为程序当前目录,仅支持MP3格式 files = QFileDialog.getOpenFileNames(self, "打开MP3文件", QDir.currentPath(), "MP3文件(*.mp3)") for filename in files[0]: # 判断是否为MP3文件 if os.path.exists(filename) and str.lower(filename[-4:]) == ".mp3": tmp = filename.replace('\\', '/') # 路径转义 name = tmp.split('/')[-1][:-4] # 提取出简单的歌曲文件名 index = self.list.mediaCount() - 1 song = QMediaContent(QUrl.fromLocalFile(filename)) if self.list.insertMedia(index, song): self.play_list.insertItem(index, name) # 保存当前播放列表 self.save_playlist() def remove_from_list(self): """ 从播放列表中移除选中的项目 :return: bool 是否移除成功 """ index = self.play_list.currentRow() # 记录将要删除的索引 tmp_index = -1 # 记录删除后继续播放的索引 tmp_position = 0 # 记录删除后继续播放的位置 exec_play = self.media.state() == 1 # 删除后是否要执行播放操作 if index < 0: # 索引无效,不作处理 return elif index == self.list.currentIndex(): # 如果要删除的歌曲为正在播放的那首歌曲,则先停止播放,删除后播放下一曲 tmp_index = index if index + 1 < self.list.mediaCount() else 0 self.media.stop() elif index <= self.list.currentIndex(): # 如果要删除的歌曲是当前播放歌曲之前的歌曲,则改变索引继续播放 tmp_index = self.list.currentIndex() - 1 tmp_position = self.media.position() self.media.stop() # 删除操作 if self.list.removeMedia(index): self.play_list.takeItem(index) # 设置新的播放 self.label_title.setText("简易MP3音乐播放器") if 0 <= tmp_index <= self.list.mediaCount() - 1: self.list.setCurrentIndex(tmp_index) if exec_play: self.media.setPosition(tmp_position) self.media.play() self.label_title.setText(self.play_list.item(tmp_index).text()) # 保存当前播放列表 self.save_playlist() def load_playlist(self): """ 加载播放列表 :return: """ if not os.path.exists("./default.playlist"): return try: with open("./default.playlist", 'rb') as f: data = eval(f.read()) self.list.clear() self.play_list.clear() for item in data["list"]: # 判断是否为MP3文件 if os.path.exists(item["path"]) and str.lower( item["path"][-4:]) == ".mp3": index = self.list.mediaCount() song = QMediaContent(QUrl.fromLocalFile(item["path"])) if self.list.insertMedia(index, song): self.play_list.insertItem(index, item["name"]) if -1 <= data["currentItem"] < self.list.mediaCount(): self.list.setCurrentIndex(data["currentItem"]) self.play_list.setCurrentRow(data["currentItem"]) self.label_title.setText( self.play_list.currentItem().text()) self.slider_pos.setRange(0, data["duration"]) self.slider_pos.setValue(data["currentPosition"]) self.media.setPosition(data["currentPosition"]) self.media.setVolume(data["volume"]) self.slider_volume.setValue(data["volume"]) self.media.setMuted(data["muted"]) img = "./img/icons/volume-mute.png" if data[ "muted"] else "./img/icons/volume-normal.png" self.btn_mute.setStyleSheet( "QPushButton#btn_mute{image:url('" + img + "')}") # 静音按钮图标样式 except: pass def save_playlist(self): """ 保存播放列表 :return: """ data = {"list": [], "current": -1} for i in range(self.list.mediaCount()): path = self.list.media(i).canonicalUrl().path() name = self.list.media(i).canonicalUrl().fileName()[:-4] data["list"].append({"path": path[1:], "name": name}) data["currentItem"] = self.list.currentIndex() data["currentPosition"] = self.media.position() data["duration"] = self.media.duration() data["volume"] = self.media.volume() data["muted"] = self.media.isMuted() with open("./default.playlist", 'wb') as f: f.write(str(data).encode('utf-8')) @staticmethod def calc_time(millisecond): """ 计算时间(毫秒数格式转“分:秒”格式) :param millisecond: int 毫秒数 :return: string min:sec """ second = millisecond / 1000 minute = int(second / 60) second = int(second % 60) # 如果是个位数的话,就在前面加个“0” minute = "0" + str(minute) if minute < 10 else str(minute) second = "0" + str(second) if second < 10 else str(second) return str(minute) + ":" + str(second)