Пример #1
0
class Window(QtWidgets.QMainWindow, Ui_MainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)

        # load config
        self.data = yaml_loader()

        # load ui
        self.setupUi(self)

        # load icons
        self.setWindowTitle("Sputofy")
        self.setWindowIcon(QIcon(os.path.join(RES_PATH, "logo.svg")))

        loopIcon = QIcon()
        loopIcon.addPixmap(QPixmap(os.path.join(RES_PATH, "loopIconOFF.svg")))
        self.loopBtn.setIcon(loopIcon)
        prevIcon = QIcon()
        prevIcon.addPixmap(QPixmap(os.path.join(RES_PATH, "backwardIcon.svg")))
        self.prevBtn.setIcon(prevIcon)
        playIcon = QIcon()
        playIcon.addPixmap(QPixmap(os.path.join(RES_PATH, "playIcon.svg")))
        self.playBtn.setIcon(playIcon)
        nextIcon = QIcon()
        nextIcon.addPixmap(QPixmap(os.path.join(RES_PATH, "forwardIcon.svg")))
        self.nextBtn.setIcon(nextIcon)
        randomIcon = QIcon()
        randomIcon.addPixmap(
            QPixmap(os.path.join(RES_PATH, "randomIconOFF.svg")))
        self.randomBtn.setIcon(randomIcon)
        volumeIcon = QIcon()
        volumeIcon.addPixmap(QPixmap(os.path.join(RES_PATH, "volumeIcon.svg")))
        self.volumeBtn.setIcon(volumeIcon)

        # window's settings
        self.xCor = self.data['last_position']['xPos']
        self.yCor = self.data['last_position']['yPos']
        self.widthSize = self.data['last_window_size']['width']
        self.heightSize = self.data['last_window_size']['height']

        self.setGeometry(self.xCor, self.yCor, self.widthSize, self.heightSize)

        # load YouTubeToMP3
        self.YouTubeToMP3 = YouTubeToMP3Window()

        # open YouTubeToMP3 using button
        self.actionYT_MP3.triggered.connect(self.YouTubeToMP3.show_window)

        # info action
        self.actionInfo.triggered.connect(self.info_handle)

        #===========================  mediaplayer  ==============================

        # create media player object
        self.mediaPlayer = QMediaPlayer(None)

        # open button
        self.actionOpen_Song.triggered.connect(self.open_song)
        self.actionOpen_Folder.triggered.connect(self.open_folder)

        # play button
        self.playBtn.setEnabled(False)
        self.playBtn.clicked.connect(
            self.play_video
        )  # when btn is pressed: if it is playing it pause, if it is paused it plays
        # QShortcut(QKeySequence("Space"), self).activated.connect(self.play_video)metodo da ricordare in caso di problemi #TODO

        # duration slider
        self.durationSlider.setEnabled(False)
        self.durationSliderMaxValue = 0
        self.durationSlider.valueChanged.connect(
            self.mediaPlayer.setPosition
        )  # set mediaPlayer position using the value took from the slider
        QShortcut('Right', self, lambda: self.durationSlider.setValue(
            self.durationSlider.value() + 10000))  # 1s = 1000ms
        QShortcut('Left', self, lambda: self.durationSlider.setValue(
            self.durationSlider.value() - 10000))  # 1s = 1000ms
        QShortcut('Shift+Right', self, lambda: self.durationSlider.setValue(
            self.durationSliderMaxValue - 1000))  # jump to the end-1s of song
        QShortcut('Shift+Left', self,
                  lambda: self.durationSlider.setValue(0))  # restart song

        # volumeSlider
        self.volumeSlider.setProperty("value", 100)
        self.volumeSlider.setRange(0, 100)
        self.volumeSlider.setValue(
            self.data['volume']
            if self.data['volume'] != 0 else self.data['volume'] + 1
        )  # set slider value | if saved volume is equal to 0 load with volume = 1 else load the saved volume
        self.mediaPlayer.setVolume(
            self.data['volume']
            if self.data['volume'] != 0 else self.data['volume'] + 1
        )  # set mediaPlayer volume | if saved volume is equal to 0 load with volume = 1 else load the saved volume
        self.volumeLabel.setText(
            f"{self.data['volume']}%"
            if self.data['volume'] != 0 else f"{self.data['volume']+1}%"
        )  # set volume label text | if saved volume is equal to 0 load with volume = 1 else load the saved volume
        self.volumeSlider.valueChanged.connect(
            self.mediaPlayer.setVolume
        )  # set mediaPlayer volume using the value took from the slider

        QShortcut('Up', self, lambda: self.volumeSlider.setValue(
            self.volumeSlider.value() + 1))  # volume + 1
        QShortcut('Down', self, lambda: self.volumeSlider.setValue(
            self.volumeSlider.value() - 1))  # volume - 1

        QShortcut(
            'Shift+Up', self,
            lambda: self.volumeSlider.setValue(100))  # set maximum volume
        QShortcut(
            'Shift+Down', self,
            lambda: self.volumeSlider.setValue(0))  # set minimun volume(mute)

        # volumeBtn
        self.volumeBtn.clicked.connect(
            self.volume_toggle)  # mute/unmute volume pressing btn
        self.isMuted = False  # starting with a non-muted volume
        self.previousVolume = self.data[
            'volume']  # loading last registered volume

        # media player signals
        self.mediaPlayer.durationChanged.connect(
            self.duration_changed)  # set range of duration slider
        self.mediaPlayer.positionChanged.connect(
            self.position_changed)  # duration slider progress
        self.mediaPlayer.stateChanged.connect(
            self.player_state)  # see when it's playing or in pause
        self.mediaPlayer.volumeChanged.connect(
            self.volume_icon)  # change volumebtn icon

        #===========================  playlist  ==============================

        # create the playlist
        self.playlist = QMediaPlaylist()
        self.playlist.setPlaybackMode(2)
        self.mediaPlayer.setPlaylist(self.playlist)

        # clear the playlist
        self.playlistIsEmpty = True

        # playlistList model
        self.model = PlaylistModel(self.playlist)
        self.playlistView.setModel(self.model)
        self.playlist.currentIndexChanged.connect(
            self.playlist_position_changed)
        selection_model = self.playlistView.selectionModel()
        selection_model.selectionChanged.connect(
            self.playlist_selection_changed)

        #===========================  playlist function  ==============================

        self.mediaList = []  # array of loaded songs
        self.currentPlaylist = ""  # current loaded playlist name
        self.isCustomPlaylist = False

        # add song name on title
        self.playlist.currentMediaChanged.connect(self.set_title)

        # playlist buttons
        self.nextBtn.clicked.connect(self.next_song)  # seek track forward

        self.prevBtn.clicked.connect(self.prev_song)  # seek track backward

        self.mediaPlayer.mediaStatusChanged.connect(
            self.auto_next_track
        )  # once song is ended seek track forward and play it

        self.actionLoopIt.triggered.connect(
            self.loop_song)  # (1) loop the same song

        self.actionShuffle.triggered.connect(
            self.shuffle_playlist)  # change song's order

        self.loopBtn.clicked.connect(self.loop)  # (3) loop the playlist

        self.randomBtn.clicked.connect(
            self.random)  # (4) play random song without end

        # create new playlist
        self.actionCreatePlaylist.triggered.connect(self.custom_playlist)

        # delete current playlist
        self.actionDeletePlaylist.triggered.connect(self.delete_playlist)

        # remove all songs
        self.actionClearQueue.triggered.connect(self.clear_queue)

        # load playlist
        self.actionDict = {}  # dictionary of action Objects

        for action in self.data['playlistList']:
            self.actionDict[action] = self.menuPlaylist.addAction(
                action, partial(self.load_playlist, action))

        if len(self.data['playlistList']) == 0:
            self.menuPlaylist.menuAction().setVisible(False)

#================== Songs opening ==================#

    def open_folder(self):
        foldername = QFileDialog.getExistingDirectory(self, "Open folder",
                                                      "c:\\")

        if foldername:
            self.playlist.clear()
            self.mediaList.clear()

            for song in os.listdir(foldername):
                media = f"{foldername}/{song}"
                self.playlist.addMedia(QMediaContent(QUrl(media)))
                self.mediaList.append(media)

            self.playlist.setCurrentIndex(0)

            self.playBtn.setEnabled(True)
            self.durationSlider.setEnabled(True)

            self.playlistIsEmpty = False
            self.isCustomPlaylist = False

            self.model.layoutChanged.emit()  # load songs in list view
            self.set_title()

            self.mediaPlayer.pause()  # adjust play/pause icon

    def open_song(self):
        filename, _ = QFileDialog.getOpenFileName(self, "Open Song", "c:\\")

        if filename:
            if self.playlistIsEmpty == False:
                self.playlist.clear()
                self.mediaList.clear()
                self.playlistIsEmpty = True

            self.playlist.addMedia(QMediaContent(QUrl.fromLocalFile(filename)))
            self.mediaList.append(filename)

            self.playBtn.setEnabled(True)
            self.durationSlider.setEnabled(True)

            self.isCustomPlaylist = False

            self.model.layoutChanged.emit()  # load song in list view
            self.set_title()

            # adjust play/pause icon
            if self.playlist.mediaCount(
            ) == 1:  # if there is 1 song and you add another
                self.playlist.setCurrentIndex(0)
                self.mediaPlayer.pause()

    def load_playlist(self, playlistName):
        self.playlist.clear()
        self.mediaList.clear()

        # reload config
        self.data = yaml_loader()

        for song in self.data['playlistList'][playlistName]:
            self.playlist.addMedia(QMediaContent(QUrl.fromLocalFile(song)))
            self.mediaList.append(song)

        self.playlist.setCurrentIndex(0)

        self.playBtn.setEnabled(True)
        self.durationSlider.setEnabled(True)

        self.playlistIsEmpty = False
        self.isCustomPlaylist = True

        self.model.layoutChanged.emit()  # load songs in list view

        self.currentPlaylist = playlistName  # name of current loaded playlist
        self.set_title()

        self.statusbar.showMessage(f'Playlist "{playlistName}" loaded', 4000)
        self.menuPlaylist.menuAction().setVisible(True)

        # adjust play/pause icon
        self.mediaPlayer.pause()

    def set_title(self):
        if self.playlist.mediaCount() == 0:
            self.setWindowTitle("Sputofy")

        else:
            if self.isCustomPlaylist == False:
                self.setWindowTitle(
                    f"Sputofy - {os.path.splitext(self.playlist.currentMedia().canonicalUrl().fileName())[0]} - {self.playlist.currentIndex()+1}/{self.playlist.mediaCount()}"
                )
            else:
                self.setWindowTitle(
                    f"Sputofy - {self.currentPlaylist} - {os.path.splitext(self.playlist.currentMedia().canonicalUrl().fileName())[0]} - {self.playlist.currentIndex()+1}/{self.playlist.mediaCount()}"
                )

#=======================================================#

#================== Player Functions ==================#

    def play_video(self):
        if self.durationSlider.isEnabled():  # if slider was enabled
            if self.mediaPlayer.state() == QMediaPlayer.PlayingState:
                self.mediaPlayer.pause()
            else:
                self.mediaPlayer.play()

    def duration_changed(self, duration):
        self.durationSlider.setRange(0, duration)

        if duration > 0:
            self.totalTime_Label.setText(time_format(round(
                duration / 1000)))  # duration is in ms
        self.durationSliderMaxValue = duration

    def position_changed(self, position):
        if position >= 0:
            self.elapsedTime_Label.setText(time_format(
                (position / 1000)))  # position is in ms

        # Disable the events to prevent updating triggering a setPosition event (can cause stuttering).
        self.durationSlider.blockSignals(True)
        self.durationSlider.setValue(position)
        self.durationSlider.blockSignals(False)

#=======================================================#

#================== Playlist Settings ==================#
#TODO Work in progress

    def playlist_array(self):
        index = self.playlist.mediaCount()
        mediaList = []
        for i in range(index):
            # songPath = (self.playlist.media(path).canonicalUrl().path())#.split("/",1)[1]
            # mediaList.append(songPath)
            # print(self.playlist.media(i).canonicalUrl().path())
            mediaList.append(self.playlist.media(i).canonicalUrl().fileName())
        return mediaList

    def custom_playlist(self):
        if not self.playlist.mediaCount() == 0:
            name, is_notEmpty = QInputDialog.getText(self, "playlist",
                                                     "save playlist as:")

            if name:

                if name in self.data['playlistList']:
                    self.statusbar.showMessage(
                        "playlist not created (name is already used)", 4000)
                else:
                    self.data['playlistList'][name] = self.mediaList
                    yaml_dump(self.data)

                    # add new action Object to dictionary
                    self.actionDict[name] = self.menuPlaylist.addAction(
                        name, partial(self.load_playlist, name))

                    self.load_playlist(
                        name)  # instantly loading the new playlist
            else:
                self.statusbar.showMessage(
                    "playlist not created (you should give a name to your baby :/)",
                    4000)
        else:
            self.statusbar.showMessage("there are no songs to playlist", 4000)

    def delete_playlist(self):
        if self.isCustomPlaylist:

            if len(self.data['playlistList']) == 1:
                self.menuPlaylist.menuAction().setVisible(False)

            self.data['playlistList'].pop(
                self.currentPlaylist)  # remove playlist from dictionary

            self.menuPlaylist.removeAction(self.actionDict[
                self.currentPlaylist])  # remove relative action
            self.actionDict.pop(
                self.currentPlaylist)  # remove relative action Object

            self.playlist.clear()
            self.model.layoutChanged.emit()
            self.setWindowTitle("Sputofy")

            yaml_dump(self.data)

            self.statusbar.showMessage(
                'succesfully deleted "' + self.currentPlaylist + '" playlist',
                4000)
        else:
            self.statusbar.showMessage("cannot delete a non custom playlist",
                                       4000)

    def clear_queue(self):
        self.playlist.clear()
        self.mediaList.clear()
        self.playBtn.setEnabled(False)
        self.model.layoutChanged.emit()

    def playlist_position_changed(self, i):
        if i > -1:
            ix = self.model.index(i)
            self.playlistView.setCurrentIndex(ix)

    def playlist_selection_changed(self, ix):
        # We receive a QItemSelection from selectionChanged.
        i = ix.indexes()[0].row()
        self.posizione = i
        self.playlist.setCurrentIndex(i)
        self.mediaPlayer.play()

#=======================================================#

#================== Playback Settings ==================#

    def next_song(self):
        if self.playlist.currentIndex() == self.playlist.mediaCount() - 1:
            self.playlist.setCurrentIndex(0)
        else:
            self.playlist.next()

    def prev_song(self):
        if self.playlist.currentIndex() == 0:
            self.playlist.setCurrentIndex(self.playlist.mediaCount() - 1)
        else:
            self.playlist.previous()

    def loop_song(self):
        if self.playlist.playbackMode() != 1:

            self.playlist.setPlaybackMode(1)

            self.actionLoopIt.setText("Loop it: ON")
            self.loopBtn.setIcon(
                QIcon(os.path.join(RES_PATH, "loopIconOFF.svg")))
            self.randomBtn.setIcon(
                QIcon(os.path.join(RES_PATH, "randomIconOFF.svg")))
        else:
            self.playlist.setPlaybackMode(2)
            self.actionLoopIt.setText("Loop it: OFF")

    def shuffle_playlist(self):
        if self.playlist.mediaCount():
            self.playlist.shuffle()
            self.model.layoutChanged.emit()
        else:
            self.statusbar.showMessage("there are no songs to shuffle", 4000)

    def loop(self):
        if self.playlist.playbackMode() != 3:
            self.playlist.setPlaybackMode(3)

            self.loopBtn.setIcon(
                QIcon(os.path.join(RES_PATH, "loopIconON.svg")))
            self.randomBtn.setIcon(
                QIcon(os.path.join(RES_PATH, "randomIconOFF.svg")))
            self.actionLoopIt.setText("Loop it: OFF")

        else:
            self.playlist.setPlaybackMode(2)
            self.loopBtn.setIcon(
                QIcon(os.path.join(RES_PATH, "loopIconOFF.svg")))

    def random(self):
        if self.playlist.playbackMode() != 4:
            self.playlist.setPlaybackMode(4)

            self.randomBtn.setIcon(
                QIcon(os.path.join(RES_PATH, "randomIconON.svg")))
            self.loopBtn.setIcon(
                QIcon(os.path.join(RES_PATH, "loopIconOFF.svg")))
            self.actionLoopIt.setText("Loop it: OFF")

        else:
            self.playlist.setPlaybackMode(2)
            self.randomBtn.setIcon(
                QIcon(os.path.join(RES_PATH, "randomIconOFF.svg")))

    def auto_next_track(self):

        if self.mediaPlayer.mediaStatus() == QMediaPlayer.EndOfMedia:
            if self.playlist.playbackMode() == 2:
                # index starts from 0       mediacount starts from 1
                if self.playlist.currentIndex(
                ) != self.playlist.mediaCount() - 1:
                    self.playlist.next()
                    self.mediaPlayer.play()
                else:  # if ended song was the last one set the index to the first one and pause
                    self.playlist.setCurrentIndex(0)
                    self.mediaPlayer.pause()

            # loop playlist
            elif self.playlist.playbackMode() == 3:
                self.playlist.next()
                self.mediaPlayer.play()

            # random song
            elif self.playlist.playbackMode() == 4:
                while self.playlist.previousIndex(
                ) == self.playlist.currentIndex(
                ):  # preventing repeating the same song
                    self.playlist.setCurrentIndex(
                        random.randint(0,
                                       self.playlist.mediaCount() - 1))

#=======================================================#

#================== Volume Settings ==================#

    def volume_icon(self, volume):

        self.volumeLabel.setText(f"{volume}%")

        if volume:
            volumeIcon = QIcon()
            volumeIcon.addPixmap(
                QPixmap(os.path.join(RES_PATH, "volumeIcon.svg")),
                QIcon.Normal, QIcon.Off)
            self.volumeBtn.setIcon(volumeIcon)
            self.previousVolume = self.volumeSlider.value()
            self.isMuted = False
        else:
            volumeMutedIcon = QIcon()
            volumeMutedIcon.addPixmap(
                QPixmap(os.path.join(RES_PATH, "volumeMutedIcon.svg")),
                QIcon.Normal, QIcon.Off)
            self.volumeBtn.setIcon(volumeMutedIcon)
            self.isMuted = True

    def volume_toggle(self):
        if self.isMuted == False:
            self.volumeSlider.setValue(0)
            self.isMuted = True

        elif self.isMuted == True:
            if self.previousVolume == 0:
                self.volumeSlider.setValue(10)
            else:
                self.volumeSlider.setValue(self.previousVolume)
            self.isMuted = False


#=======================================================#

    def mousePressEvent(self, event):
        ''' remove the border around the buttons created by using tab key '''

        focused_widget = QtWidgets.QApplication.focusWidget()
        try:
            focused_widget.clearFocus()
        except:
            pass
        QMainWindow.mousePressEvent(self, event)

    def player_state(self, event):
        ''' event handler that adjust the play/pause icon '''

        if event == QMediaPlayer.PlayingState:
            pauseIcon = QIcon()
            pauseIcon.addPixmap(
                QPixmap(os.path.join(RES_PATH, "pauseIcon.svg")), QIcon.Normal,
                QIcon.Off)
            self.playBtn.setIcon(pauseIcon)
        elif event == QMediaPlayer.PausedState:
            playIcon = QIcon()
            playIcon.addPixmap(QPixmap(os.path.join(RES_PATH, "playIcon.svg")),
                               QIcon.Normal, QIcon.Off)
            self.playBtn.setIcon(playIcon)

    def closeEvent(self, event):
        ''' event handler that take window information and save it in config before the window close '''

        # retrieve position
        xAxis = self.geometry().x()
        yAxis = self.geometry().y()

        self.data['last_position']['xPos'] = xAxis
        self.data['last_position']['yPos'] = yAxis

        # retrieve size
        width = self.width()
        height = self.height()

        self.data['last_window_size']['width'] = width
        self.data['last_window_size']['height'] = height

        # retrieve volume
        self.data['volume'] = self.mediaPlayer.volume()

        # retrieve user
        user = os.getlogin()
        self.data[
            'default_folder'] = f"C:\\Users\\{user}\\Desktop\\sputofy_songs"

        yaml_dump(self.data)

    def info_handle(self):

        info = "Sputofy\n1.0.0\n©2020 "+\
        "Sputofy is a free audio player based on the converted youtube songs made by a_str0\n\n"+\
        "Sputofy is written using python 3.x and PyQt5 modules"

        msg = QMessageBox.about(self, "About", info)
Пример #2
0
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
Пример #3
0
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:])
Пример #4
0
class MusicPlayer(QMainWindow):
    """MusicPlayer houses all of elements that directly interact with the main window."""

    def __init__(self, parent=None):
        """Initialize the QMainWindow widget.

        The window title, window icon, and window size are initialized here as well
        as the following widgets: QMediaPlayer, QMediaPlaylist, QMediaContent, QMenuBar,
        QToolBar, QLabel, QPixmap, QSlider, QDockWidget, QListWidget, QWidget, and
        QVBoxLayout. The connect signals for relavant widgets are also initialized.
        """
        super(MusicPlayer, self).__init__(parent)
        self.setWindowTitle('Mosaic')

        window_icon = utilities.resource_filename('mosaic.images', 'icon.png')
        self.setWindowIcon(QIcon(window_icon))
        self.resize(defaults.Settings().window_size, defaults.Settings().window_size + 63)

        # Initiates Qt objects to be used by MusicPlayer
        self.player = QMediaPlayer()
        self.playlist = QMediaPlaylist()
        self.playlist_location = defaults.Settings().playlist_path
        self.content = QMediaContent()
        self.menu = self.menuBar()
        self.toolbar = QToolBar()
        self.art = QLabel()
        self.pixmap = QPixmap()
        self.slider = QSlider(Qt.Horizontal)
        self.duration_label = QLabel()
        self.playlist_dock = QDockWidget('Playlist', self)
        self.library_dock = QDockWidget('Media Library', self)
        self.playlist_view = QListWidget()
        self.library_view = library.MediaLibraryView()
        self.library_model = library.MediaLibraryModel()
        self.preferences = configuration.PreferencesDialog()
        self.widget = QWidget()
        self.layout = QVBoxLayout(self.widget)
        self.duration = 0
        self.playlist_dock_state = None
        self.library_dock_state = None

        # Sets QWidget() as the central widget of the main window
        self.setCentralWidget(self.widget)
        self.layout.setContentsMargins(0, 0, 0, 0)
        self.art.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored)

        # Initiates the playlist dock widget and the library dock widget
        self.addDockWidget(defaults.Settings().dock_position, self.playlist_dock)
        self.playlist_dock.setWidget(self.playlist_view)
        self.playlist_dock.setVisible(defaults.Settings().playlist_on_start)
        self.playlist_dock.setFeatures(QDockWidget.DockWidgetClosable)

        self.addDockWidget(defaults.Settings().dock_position, self.library_dock)
        self.library_dock.setWidget(self.library_view)
        self.library_dock.setVisible(defaults.Settings().media_library_on_start)
        self.library_dock.setFeatures(QDockWidget.DockWidgetClosable)
        self.tabifyDockWidget(self.playlist_dock, self.library_dock)

        # Sets the range of the playback slider and sets the playback mode as looping
        self.slider.setRange(0, self.player.duration() / 1000)
        self.playlist.setPlaybackMode(QMediaPlaylist.Sequential)

        # OSX system menu bar causes conflicts with PyQt5 menu bar
        if sys.platform == 'darwin':
            self.menu.setNativeMenuBar(False)

        # Initiates Settings in the defaults module to give access to settings.toml
        defaults.Settings()

        # Signals that connect to other methods when they're called
        self.player.metaDataChanged.connect(self.display_meta_data)
        self.slider.sliderMoved.connect(self.seek)
        self.player.durationChanged.connect(self.song_duration)
        self.player.positionChanged.connect(self.song_position)
        self.player.stateChanged.connect(self.set_state)
        self.playlist_view.itemActivated.connect(self.activate_playlist_item)
        self.library_view.activated.connect(self.open_media_library)
        self.playlist.currentIndexChanged.connect(self.change_index)
        self.playlist.mediaInserted.connect(self.initialize_playlist)
        self.playlist_dock.visibilityChanged.connect(self.dock_visiblity_change)
        self.library_dock.visibilityChanged.connect(self.dock_visiblity_change)
        self.preferences.dialog_media_library.media_library_line.textChanged.connect(self.change_media_library_path)
        self.preferences.dialog_view_options.dropdown_box.currentIndexChanged.connect(self.change_window_size)
        self.art.mousePressEvent = self.press_playback

        # Creating the menu controls, media controls, and window size of the music player
        self.menu_controls()
        self.media_controls()
        self.load_saved_playlist()

    def menu_controls(self):
        """Initiate the menu bar and add it to the QMainWindow widget."""
        self.file = self.menu.addMenu('File')
        self.edit = self.menu.addMenu('Edit')
        self.playback = self.menu.addMenu('Playback')
        self.view = self.menu.addMenu('View')
        self.help_ = self.menu.addMenu('Help')

        self.file_menu()
        self.edit_menu()
        self.playback_menu()
        self.view_menu()
        self.help_menu()

    def media_controls(self):
        """Create the bottom toolbar and controls used for media playback."""
        self.addToolBar(Qt.BottomToolBarArea, self.toolbar)
        self.toolbar.setMovable(False)

        play_icon = utilities.resource_filename('mosaic.images', 'md_play.png')
        self.play_action = QAction(QIcon(play_icon), 'Play', self)
        self.play_action.triggered.connect(self.player.play)

        stop_icon = utilities.resource_filename('mosaic.images', 'md_stop.png')
        self.stop_action = QAction(QIcon(stop_icon), 'Stop', self)
        self.stop_action.triggered.connect(self.player.stop)

        previous_icon = utilities.resource_filename('mosaic.images', 'md_previous.png')
        self.previous_action = QAction(QIcon(previous_icon), 'Previous', self)
        self.previous_action.triggered.connect(self.previous)

        next_icon = utilities.resource_filename('mosaic.images', 'md_next.png')
        self.next_action = QAction(QIcon(next_icon), 'Next', self)
        self.next_action.triggered.connect(self.playlist.next)

        repeat_icon = utilities.resource_filename('mosaic.images', 'md_repeat_none.png')
        self.repeat_action = QAction(QIcon(repeat_icon), 'Repeat', self)
        self.repeat_action.setShortcut('R')
        self.repeat_action.triggered.connect(self.repeat_song)

        self.toolbar.addAction(self.play_action)
        self.toolbar.addAction(self.stop_action)
        self.toolbar.addAction(self.previous_action)
        self.toolbar.addAction(self.next_action)
        self.toolbar.addAction(self.repeat_action)
        self.toolbar.addWidget(self.slider)
        self.toolbar.addWidget(self.duration_label)

    def file_menu(self):
        """Add a file menu to the menu bar.

        The file menu houses the Open File, Open Multiple Files, Open Playlist,
        Open Directory, and Exit Application menu items.
        """
        self.open_action = QAction('Open File', self)
        self.open_action.setShortcut('O')
        self.open_action.triggered.connect(self.open_file)

        self.open_multiple_files_action = QAction('Open Multiple Files', self)
        self.open_multiple_files_action.setShortcut('M')
        self.open_multiple_files_action.triggered.connect(self.open_multiple_files)

        self.open_playlist_action = QAction('Open Playlist', self)
        self.open_playlist_action.setShortcut('CTRL+P')
        self.open_playlist_action.triggered.connect(self.open_playlist)

        self.open_directory_action = QAction('Open Directory', self)
        self.open_directory_action.setShortcut('D')
        self.open_directory_action.triggered.connect(self.open_directory)

        self.save_playlist_action = QAction('Save Playlist', self)
        self.save_playlist_action.setShortcut('CTRL+S')
        self.save_playlist_action.triggered.connect(self.save_playlist)

        self.exit_action = QAction('Quit', self)
        self.exit_action.setShortcut('CTRL+Q')
        self.exit_action.triggered.connect(self.closeEvent)

        self.file.addAction(self.open_action)
        self.file.addAction(self.open_multiple_files_action)
        self.file.addAction(self.open_playlist_action)
        self.file.addAction(self.open_directory_action)
        self.file.addSeparator()
        self.file.addAction(self.save_playlist_action)
        self.file.addSeparator()
        self.file.addAction(self.exit_action)

    def edit_menu(self):
        """Add an edit menu to the menu bar.

        The edit menu houses the preferences item that opens a preferences dialog
        that allows the user to customize features of the music player.
        """
        self.preferences_action = QAction('Preferences', self)
        self.preferences_action.setShortcut('CTRL+SHIFT+P')
        self.preferences_action.triggered.connect(lambda: self.preferences.exec_())

        self.edit.addAction(self.preferences_action)

    def playback_menu(self):
        """Add a playback menu to the menu bar.

        The playback menu houses
        """
        self.play_playback_action = QAction('Play', self)
        self.play_playback_action.setShortcut('P')
        self.play_playback_action.triggered.connect(self.player.play)

        self.stop_playback_action = QAction('Stop', self)
        self.stop_playback_action.setShortcut('S')
        self.stop_playback_action.triggered.connect(self.player.stop)

        self.previous_playback_action = QAction('Previous', self)
        self.previous_playback_action.setShortcut('B')
        self.previous_playback_action.triggered.connect(self.previous)

        self.next_playback_action = QAction('Next', self)
        self.next_playback_action.setShortcut('N')
        self.next_playback_action.triggered.connect(self.playlist.next)

        self.playback.addAction(self.play_playback_action)
        self.playback.addAction(self.stop_playback_action)
        self.playback.addAction(self.previous_playback_action)
        self.playback.addAction(self.next_playback_action)

    def view_menu(self):
        """Add a view menu to the menu bar.

        The view menu houses the Playlist, Media Library, Minimalist View, and Media
        Information menu items. The Playlist item toggles the playlist dock into and
        out of view. The Media Library items toggles the media library dock into and
        out of view. The Minimalist View item resizes the window and shows only the
        menu bar and player controls. The Media Information item opens a dialog that
        shows information relevant to the currently playing song.
        """
        self.dock_action = self.playlist_dock.toggleViewAction()
        self.dock_action.setShortcut('CTRL+ALT+P')

        self.library_dock_action = self.library_dock.toggleViewAction()
        self.library_dock_action.setShortcut('CTRL+ALT+L')

        self.minimalist_view_action = QAction('Minimalist View', self)
        self.minimalist_view_action.setShortcut('CTRL+ALT+M')
        self.minimalist_view_action.setCheckable(True)
        self.minimalist_view_action.triggered.connect(self.minimalist_view)

        self.view_media_info_action = QAction('Media Information', self)
        self.view_media_info_action.setShortcut('CTRL+SHIFT+M')
        self.view_media_info_action.triggered.connect(self.media_information_dialog)

        self.view.addAction(self.dock_action)
        self.view.addAction(self.library_dock_action)
        self.view.addSeparator()
        self.view.addAction(self.minimalist_view_action)
        self.view.addSeparator()
        self.view.addAction(self.view_media_info_action)

    def help_menu(self):
        """Add a help menu to the menu bar.

        The help menu houses the about dialog that shows the user information
        related to the application.
        """
        self.about_action = QAction('About', self)
        self.about_action.setShortcut('H')
        self.about_action.triggered.connect(lambda: about.AboutDialog().exec_())

        self.help_.addAction(self.about_action)

    def open_file(self):
        """Open the selected file and add it to a new playlist."""
        filename, success = QFileDialog.getOpenFileName(self, 'Open File', '', 'Audio (*.mp3 *.flac)', '', QFileDialog.ReadOnly)

        if success:
            file_info = QFileInfo(filename).fileName()
            playlist_item = QListWidgetItem(file_info)
            self.playlist.clear()
            self.playlist_view.clear()
            self.playlist.addMedia(QMediaContent(QUrl().fromLocalFile(filename)))
            self.player.setPlaylist(self.playlist)
            playlist_item.setToolTip(file_info)
            self.playlist_view.addItem(playlist_item)
            self.playlist_view.setCurrentRow(0)
            self.player.play()

    def open_multiple_files(self):
        """Open the selected files and add them to a new playlist."""
        filenames, success = QFileDialog.getOpenFileNames(self, 'Open Multiple Files', '', 'Audio (*.mp3 *.flac)', '', QFileDialog.ReadOnly)

        if success:
            self.playlist.clear()
            self.playlist_view.clear()
            for file in natsort.natsorted(filenames, alg=natsort.ns.PATH):
                file_info = QFileInfo(file).fileName()
                playlist_item = QListWidgetItem(file_info)
                self.playlist.addMedia(QMediaContent(QUrl().fromLocalFile(file)))
                self.player.setPlaylist(self.playlist)
                playlist_item.setToolTip(file_info)
                self.playlist_view.addItem(playlist_item)
                self.playlist_view.setCurrentRow(0)
                self.player.play()

    def open_playlist(self):
        """Load an M3U or PLS file into a new playlist."""
        playlist, success = QFileDialog.getOpenFileName(self, 'Open Playlist', '', 'Playlist (*.m3u *.pls)', '', QFileDialog.ReadOnly)

        if success:
            playlist = QUrl.fromLocalFile(playlist)
            self.playlist.clear()
            self.playlist_view.clear()
            self.playlist.load(playlist)
            self.player.setPlaylist(self.playlist)

            for song_index in range(self.playlist.mediaCount()):
                file_info = self.playlist.media(song_index).canonicalUrl().fileName()
                playlist_item = QListWidgetItem(file_info)
                playlist_item.setToolTip(file_info)
                self.playlist_view.addItem(playlist_item)

            self.playlist_view.setCurrentRow(0)
            self.player.play()

    def save_playlist(self):
        """Save the media in the playlist dock as a new M3U playlist."""
        playlist, success = QFileDialog.getSaveFileName(self, 'Save Playlist', '', 'Playlist (*.m3u)', '')
        if success:
            saved_playlist = "{}.m3u" .format(playlist)
            self.playlist.save(QUrl().fromLocalFile(saved_playlist), "m3u")

    def load_saved_playlist(self):
        """Load the saved playlist if user setting permits."""
        saved_playlist = "{}/.m3u" .format(self.playlist_location)
        if os.path.exists(saved_playlist):
            playlist = QUrl().fromLocalFile(saved_playlist)
            self.playlist.load(playlist)
            self.player.setPlaylist(self.playlist)

            for song_index in range(self.playlist.mediaCount()):
                file_info = self.playlist.media(song_index).canonicalUrl().fileName()
                playlist_item = QListWidgetItem(file_info)
                playlist_item.setToolTip(file_info)
                self.playlist_view.addItem(playlist_item)

            self.playlist_view.setCurrentRow(0)

    def open_directory(self):
        """Open the selected directory and add the files within to an empty playlist."""
        directory = QFileDialog.getExistingDirectory(self, 'Open Directory', '', QFileDialog.ReadOnly)

        if directory:
            self.playlist.clear()
            self.playlist_view.clear()
            for dirpath, __, files in os.walk(directory):
                for filename in natsort.natsorted(files, alg=natsort.ns.PATH):
                    file = os.path.join(dirpath, filename)
                    if filename.endswith(('mp3', 'flac')):
                        self.playlist.addMedia(QMediaContent(QUrl().fromLocalFile(file)))
                        playlist_item = QListWidgetItem(filename)
                        playlist_item.setToolTip(filename)
                        self.playlist_view.addItem(playlist_item)

            self.player.setPlaylist(self.playlist)
            self.playlist_view.setCurrentRow(0)
            self.player.play()

    def open_media_library(self, index):
        """Open a directory or file from the media library into an empty playlist."""
        self.playlist.clear()
        self.playlist_view.clear()

        if self.library_model.fileName(index).endswith(('mp3', 'flac')):
            self.playlist.addMedia(QMediaContent(QUrl().fromLocalFile(self.library_model.filePath(index))))
            self.playlist_view.addItem(self.library_model.fileName(index))

        elif self.library_model.isDir(index):
            directory = self.library_model.filePath(index)
            for dirpath, __, files in os.walk(directory):
                for filename in natsort.natsorted(files, alg=natsort.ns.PATH):
                    file = os.path.join(dirpath, filename)
                    if filename.endswith(('mp3', 'flac')):
                        self.playlist.addMedia(QMediaContent(QUrl().fromLocalFile(file)))
                        playlist_item = QListWidgetItem(filename)
                        playlist_item.setToolTip(filename)
                        self.playlist_view.addItem(playlist_item)

        self.player.setPlaylist(self.playlist)
        self.player.play()

    def display_meta_data(self):
        """Display the current song's metadata in the main window.

        If the current song contains metadata, its cover art is extracted and shown in
        the main window while the track number, artist, album, and track title are shown
        in the window title.
        """
        if self.player.isMetaDataAvailable():
            file_path = self.player.currentMedia().canonicalUrl().toLocalFile()
            (album, artist, title, track_number, *__, artwork) = metadata.metadata(file_path)

            try:
                self.pixmap.loadFromData(artwork)
            except TypeError:
                self.pixmap = QPixmap(artwork)

            meta_data = '{} - {} - {} - {}' .format(track_number, artist, album, title)

            self.setWindowTitle(meta_data)
            self.art.setScaledContents(True)
            self.art.setPixmap(self.pixmap)
            self.layout.addWidget(self.art)

    def initialize_playlist(self, start):
        """Display playlist and reset playback mode when media inserted into playlist."""
        if start == 0:
            if self.library_dock.isVisible():
                self.playlist_dock.setVisible(True)
                self.playlist_dock.show()
                self.playlist_dock.raise_()

            if self.playlist.playbackMode() != QMediaPlaylist.Sequential:
                self.playlist.setPlaybackMode(QMediaPlaylist.Sequential)
                repeat_icon = utilities.resource_filename('mosaic.images', 'md_repeat_none.png')
                self.repeat_action.setIcon(QIcon(repeat_icon))

    def press_playback(self, event):
        """Change the playback of the player on cover art mouse event.

        When the cover art is clicked, the player will play the media if the player is
        either paused or stopped. If the media is playing, the media is set
        to pause.
        """
        if event.button() == 1 and configuration.Playback().cover_art_playback.isChecked():
            if (self.player.state() == QMediaPlayer.StoppedState or
                    self.player.state() == QMediaPlayer.PausedState):
                self.player.play()
            elif self.player.state() == QMediaPlayer.PlayingState:
                self.player.pause()

    def seek(self, seconds):
        """Set the position of the song to the position dragged to by the user."""
        self.player.setPosition(seconds * 1000)

    def song_duration(self, duration):
        """Set the slider to the duration of the currently played media."""
        duration /= 1000
        self.duration = duration
        self.slider.setMaximum(duration)

    def song_position(self, progress):
        """Move the horizontal slider in sync with the duration of the song.

        The progress is relayed to update_duration() in order
        to display the time label next to the slider.
        """
        progress /= 1000

        if not self.slider.isSliderDown():
            self.slider.setValue(progress)

        self.update_duration(progress)

    def update_duration(self, current_duration):
        """Calculate the time played and the length of the song.

        Both of these times are sent to duration_label() in order to display the
        times on the toolbar.
        """
        duration = self.duration

        if current_duration or duration:
            time_played = QTime((current_duration / 3600) % 60, (current_duration / 60) % 60,
                                (current_duration % 60), (current_duration * 1000) % 1000)
            song_length = QTime((duration / 3600) % 60, (duration / 60) % 60, (duration % 60),
                                (duration * 1000) % 1000)

            if duration > 3600:
                time_format = "hh:mm:ss"
            else:
                time_format = "mm:ss"

            time_display = "{} / {}" .format(time_played.toString(time_format), song_length.toString(time_format))

        else:
            time_display = ""

        self.duration_label.setText(time_display)

    def set_state(self, state):
        """Change the icon in the toolbar in relation to the state of the player.

        The play icon changes to the pause icon when a song is playing and
        the pause icon changes back to the play icon when either paused or
        stopped.
        """
        if self.player.state() == QMediaPlayer.PlayingState:
            pause_icon = utilities.resource_filename('mosaic.images', 'md_pause.png')
            self.play_action.setIcon(QIcon(pause_icon))
            self.play_action.triggered.connect(self.player.pause)

        elif (self.player.state() == QMediaPlayer.PausedState or self.player.state() == QMediaPlayer.StoppedState):
            self.play_action.triggered.connect(self.player.play)
            play_icon = utilities.resource_filename('mosaic.images', 'md_play.png')
            self.play_action.setIcon(QIcon(play_icon))

    def previous(self):
        """Move to the previous song in the playlist.

        Moves to the previous song in the playlist if the current song is less
        than five seconds in. Otherwise, restarts the current song.
        """
        if self.player.position() <= 5000:
            self.playlist.previous()
        else:
            self.player.setPosition(0)

    def repeat_song(self):
        """Set the current media to repeat and change the repeat icon accordingly.

        There are four playback modes: repeat none, repeat all, repeat once, and shuffle.
        Clicking the repeat button cycles through each playback mode.
        """
        if self.playlist.playbackMode() == QMediaPlaylist.Sequential:
            self.playlist.setPlaybackMode(QMediaPlaylist.Loop)
            repeat_on_icon = utilities.resource_filename('mosaic.images', 'md_repeat_all.png')
            self.repeat_action.setIcon(QIcon(repeat_on_icon))

        elif self.playlist.playbackMode() == QMediaPlaylist.Loop:
            self.playlist.setPlaybackMode(QMediaPlaylist.CurrentItemInLoop)
            repeat_on_icon = utilities.resource_filename('mosaic.images', 'md_repeat_once.png')
            self.repeat_action.setIcon(QIcon(repeat_on_icon))

        elif self.playlist.playbackMode() == QMediaPlaylist.CurrentItemInLoop:
            self.playlist.setPlaybackMode(QMediaPlaylist.Random)
            repeat_icon = utilities.resource_filename('mosaic.images', 'md_shuffle.png')
            self.repeat_action.setIcon(QIcon(repeat_icon))

        elif self.playlist.playbackMode() == QMediaPlaylist.Random:
            self.playlist.setPlaybackMode(QMediaPlaylist.Sequential)
            repeat_icon = utilities.resource_filename('mosaic.images', 'md_repeat_none.png')
            self.repeat_action.setIcon(QIcon(repeat_icon))

    def activate_playlist_item(self, item):
        """Set the active media to the playlist item dobule-clicked on by the user."""
        current_index = self.playlist_view.row(item)
        if self.playlist.currentIndex() != current_index:
            self.playlist.setCurrentIndex(current_index)

        if self.player.state() != QMediaPlayer.PlayingState:
            self.player.play()

    def change_index(self, row):
        """Highlight the row in the playlist of the active media."""
        self.playlist_view.setCurrentRow(row)

    def minimalist_view(self):
        """Resize the window to only show the menu bar and audio controls."""
        if self.minimalist_view_action.isChecked():

            if self.playlist_dock.isVisible():
                self.playlist_dock_state = True
            if self.library_dock.isVisible():
                self.library_dock_state = True

            self.library_dock.close()
            self.playlist_dock.close()

            QTimer.singleShot(10, lambda: self.resize(500, 0))

        else:
            self.resize(defaults.Settings().window_size, defaults.Settings().window_size + 63)

            if self.library_dock_state:
                self.library_dock.setVisible(True)

            if self.playlist_dock_state:
                self.playlist_dock.setVisible(True)

    def dock_visiblity_change(self, visible):
        """Change the size of the main window when the docks are toggled."""
        if visible and self.playlist_dock.isVisible() and not self.library_dock.isVisible():
            self.resize(defaults.Settings().window_size + self.playlist_dock.width() + 6,
                        self.height())

        elif visible and not self.playlist_dock.isVisible() and self.library_dock.isVisible():
            self.resize(defaults.Settings().window_size + self.library_dock.width() + 6,
                        self.height())

        elif visible and self.playlist_dock.isVisible() and self.library_dock.isVisible():
            self.resize(defaults.Settings().window_size + self.library_dock.width() + 6,
                        self.height())

        elif (not visible and not self.playlist_dock.isVisible() and not
                self.library_dock.isVisible()):
            self.resize(defaults.Settings().window_size, defaults.Settings().window_size + 63)

    def media_information_dialog(self):
        """Show a dialog of the current song's metadata."""
        if self.player.isMetaDataAvailable():
            file_path = self.player.currentMedia().canonicalUrl().toLocalFile()
        else:
            file_path = None
        dialog = information.InformationDialog(file_path)
        dialog.exec_()

    def change_window_size(self):
        """Change the window size of the music player."""
        self.playlist_dock.close()
        self.library_dock.close()
        self.resize(defaults.Settings().window_size, defaults.Settings().window_size + 63)

    def change_media_library_path(self, path):
        """Change the media library path to the new path selected in the preferences dialog."""
        self.library_model.setRootPath(path)
        self.library_view.setModel(self.library_model)
        self.library_view.setRootIndex(self.library_model.index(path))

    def closeEvent(self, event):
        """Override the PyQt close event in order to handle save playlist on close."""
        playlist = "{}/.m3u" .format(self.playlist_location)
        if defaults.Settings().save_playlist_on_close:
            self.playlist.save(QUrl().fromLocalFile(playlist), "m3u")
        else:
            if os.path.exists(playlist):
                os.remove(playlist)
        QApplication.quit()
Пример #5
0
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)
Пример #6
0
class MusicPlayer(QMainWindow):
    """MusicPlayer houses all of elements that directly interact with the main window."""
    def __init__(self, parent=None):
        """Initialize the QMainWindow widget.

        The window title, window icon, and window size are initialized here as well
        as the following widgets: QMediaPlayer, QMediaPlaylist, QMediaContent, QMenuBar,
        QToolBar, QLabel, QPixmap, QSlider, QDockWidget, QListWidget, QWidget, and
        QVBoxLayout. The connect signals for relavant widgets are also initialized.
        """
        super(MusicPlayer, self).__init__(parent)
        self.setWindowTitle('Mosaic')

        window_icon = utilities.resource_filename('mosaic.images', 'icon.png')
        self.setWindowIcon(QIcon(window_icon))
        self.resize(defaults.Settings().window_size,
                    defaults.Settings().window_size + 63)

        # Initiates Qt objects to be used by MusicPlayer
        self.player = QMediaPlayer()
        self.playlist = QMediaPlaylist()
        self.playlist_location = defaults.Settings().playlist_path
        self.content = QMediaContent()
        self.menu = self.menuBar()
        self.toolbar = QToolBar()
        self.art = QLabel()
        self.pixmap = QPixmap()
        self.slider = QSlider(Qt.Horizontal)
        self.duration_label = QLabel()
        self.playlist_dock = QDockWidget('Playlist', self)
        self.library_dock = QDockWidget('Media Library', self)
        self.playlist_view = QListWidget()
        self.library_view = library.MediaLibraryView()
        self.library_model = library.MediaLibraryModel()
        self.preferences = configuration.PreferencesDialog()
        self.widget = QWidget()
        self.layout = QVBoxLayout(self.widget)
        self.duration = 0
        self.playlist_dock_state = None
        self.library_dock_state = None

        # Sets QWidget() as the central widget of the main window
        self.setCentralWidget(self.widget)
        self.layout.setContentsMargins(0, 0, 0, 0)
        self.art.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored)

        # Initiates the playlist dock widget and the library dock widget
        self.addDockWidget(defaults.Settings().dock_position,
                           self.playlist_dock)
        self.playlist_dock.setWidget(self.playlist_view)
        self.playlist_dock.setVisible(defaults.Settings().playlist_on_start)
        self.playlist_dock.setFeatures(QDockWidget.DockWidgetClosable)

        self.addDockWidget(defaults.Settings().dock_position,
                           self.library_dock)
        self.library_dock.setWidget(self.library_view)
        self.library_dock.setVisible(
            defaults.Settings().media_library_on_start)
        self.library_dock.setFeatures(QDockWidget.DockWidgetClosable)
        self.tabifyDockWidget(self.playlist_dock, self.library_dock)

        # Sets the range of the playback slider and sets the playback mode as looping
        self.slider.setRange(0, self.player.duration() / 1000)
        self.playlist.setPlaybackMode(QMediaPlaylist.Sequential)

        # OSX system menu bar causes conflicts with PyQt5 menu bar
        if sys.platform == 'darwin':
            self.menu.setNativeMenuBar(False)

        # Initiates Settings in the defaults module to give access to settings.toml
        defaults.Settings()

        # Signals that connect to other methods when they're called
        self.player.metaDataChanged.connect(self.display_meta_data)
        self.slider.sliderMoved.connect(self.seek)
        self.player.durationChanged.connect(self.song_duration)
        self.player.positionChanged.connect(self.song_position)
        self.player.stateChanged.connect(self.set_state)
        self.playlist_view.itemActivated.connect(self.activate_playlist_item)
        self.library_view.activated.connect(self.open_media_library)
        self.playlist.currentIndexChanged.connect(self.change_index)
        self.playlist.mediaInserted.connect(self.initialize_playlist)
        self.playlist_dock.visibilityChanged.connect(
            self.dock_visiblity_change)
        self.library_dock.visibilityChanged.connect(self.dock_visiblity_change)
        self.preferences.dialog_media_library.media_library_line.textChanged.connect(
            self.change_media_library_path)
        self.preferences.dialog_view_options.dropdown_box.currentIndexChanged.connect(
            self.change_window_size)
        self.art.mousePressEvent = self.press_playback

        # Creating the menu controls, media controls, and window size of the music player
        self.menu_controls()
        self.media_controls()
        self.load_saved_playlist()

    def menu_controls(self):
        """Initiate the menu bar and add it to the QMainWindow widget."""
        self.file = self.menu.addMenu('File')
        self.edit = self.menu.addMenu('Edit')
        self.playback = self.menu.addMenu('Playback')
        self.view = self.menu.addMenu('View')
        self.help_ = self.menu.addMenu('Help')

        self.file_menu()
        self.edit_menu()
        self.playback_menu()
        self.view_menu()
        self.help_menu()

    def media_controls(self):
        """Create the bottom toolbar and controls used for media playback."""
        self.addToolBar(Qt.BottomToolBarArea, self.toolbar)
        self.toolbar.setMovable(False)

        play_icon = utilities.resource_filename('mosaic.images', 'md_play.png')
        self.play_action = QAction(QIcon(play_icon), 'Play', self)
        self.play_action.triggered.connect(self.player.play)

        stop_icon = utilities.resource_filename('mosaic.images', 'md_stop.png')
        self.stop_action = QAction(QIcon(stop_icon), 'Stop', self)
        self.stop_action.triggered.connect(self.player.stop)

        previous_icon = utilities.resource_filename('mosaic.images',
                                                    'md_previous.png')
        self.previous_action = QAction(QIcon(previous_icon), 'Previous', self)
        self.previous_action.triggered.connect(self.previous)

        next_icon = utilities.resource_filename('mosaic.images', 'md_next.png')
        self.next_action = QAction(QIcon(next_icon), 'Next', self)
        self.next_action.triggered.connect(self.playlist.next)

        repeat_icon = utilities.resource_filename('mosaic.images',
                                                  'md_repeat_none.png')
        self.repeat_action = QAction(QIcon(repeat_icon), 'Repeat', self)
        self.repeat_action.triggered.connect(self.repeat_song)

        self.toolbar.addAction(self.play_action)
        self.toolbar.addAction(self.stop_action)
        self.toolbar.addAction(self.previous_action)
        self.toolbar.addAction(self.next_action)
        self.toolbar.addAction(self.repeat_action)
        self.toolbar.addWidget(self.slider)
        self.toolbar.addWidget(self.duration_label)

    def file_menu(self):
        """Add a file menu to the menu bar.

        The file menu houses the Open File, Open Multiple Files, Open Playlist,
        Open Directory, and Exit Application menu items.
        """
        self.open_action = QAction('Open File', self)
        self.open_action.setShortcut('O')
        self.open_action.triggered.connect(self.open_file)

        self.open_multiple_files_action = QAction('Open Multiple Files', self)
        self.open_multiple_files_action.setShortcut('M')
        self.open_multiple_files_action.triggered.connect(
            self.open_multiple_files)

        self.open_playlist_action = QAction('Open Playlist', self)
        self.open_playlist_action.setShortcut('CTRL+P')
        self.open_playlist_action.triggered.connect(self.open_playlist)

        self.open_directory_action = QAction('Open Directory', self)
        self.open_directory_action.setShortcut('D')
        self.open_directory_action.triggered.connect(self.open_directory)

        self.save_playlist_action = QAction('Save Playlist', self)
        self.save_playlist_action.setShortcut('CTRL+S')
        self.save_playlist_action.triggered.connect(self.save_playlist)

        self.exit_action = QAction('Quit', self)
        self.exit_action.setShortcut('CTRL+Q')
        self.exit_action.triggered.connect(self.closeEvent)

        self.file.addAction(self.open_action)
        self.file.addAction(self.open_multiple_files_action)
        self.file.addAction(self.open_playlist_action)
        self.file.addAction(self.open_directory_action)
        self.file.addSeparator()
        self.file.addAction(self.save_playlist_action)
        self.file.addSeparator()
        self.file.addAction(self.exit_action)

    def edit_menu(self):
        """Add an edit menu to the menu bar.

        The edit menu houses the preferences item that opens a preferences dialog
        that allows the user to customize features of the music player.
        """
        self.preferences_action = QAction('Preferences', self)
        self.preferences_action.setShortcut('CTRL+SHIFT+P')
        self.preferences_action.triggered.connect(
            lambda: self.preferences.exec_())

        self.edit.addAction(self.preferences_action)

    def playback_menu(self):
        """Add a playback menu to the menu bar.

        The playback menu houses
        """
        self.play_playback_action = QAction('Play', self)
        self.play_playback_action.setShortcut('P')
        self.play_playback_action.triggered.connect(self.player.play)

        self.stop_playback_action = QAction('Stop', self)
        self.stop_playback_action.setShortcut('S')
        self.stop_playback_action.triggered.connect(self.player.stop)

        self.previous_playback_action = QAction('Previous', self)
        self.previous_playback_action.setShortcut('B')
        self.previous_playback_action.triggered.connect(self.previous)

        self.next_playback_action = QAction('Next', self)
        self.next_playback_action.setShortcut('N')
        self.next_playback_action.triggered.connect(self.playlist.next)

        self.playback.addAction(self.play_playback_action)
        self.playback.addAction(self.stop_playback_action)
        self.playback.addAction(self.previous_playback_action)
        self.playback.addAction(self.next_playback_action)

    def view_menu(self):
        """Add a view menu to the menu bar.

        The view menu houses the Playlist, Media Library, Minimalist View, and Media
        Information menu items. The Playlist item toggles the playlist dock into and
        out of view. The Media Library items toggles the media library dock into and
        out of view. The Minimalist View item resizes the window and shows only the
        menu bar and player controls. The Media Information item opens a dialog that
        shows information relevant to the currently playing song.
        """
        self.dock_action = self.playlist_dock.toggleViewAction()
        self.dock_action.setShortcut('CTRL+ALT+P')

        self.library_dock_action = self.library_dock.toggleViewAction()
        self.library_dock_action.setShortcut('CTRL+ALT+L')

        self.minimalist_view_action = QAction('Minimalist View', self)
        self.minimalist_view_action.setShortcut('CTRL+ALT+M')
        self.minimalist_view_action.setCheckable(True)
        self.minimalist_view_action.triggered.connect(self.minimalist_view)

        self.view_media_info_action = QAction('Media Information', self)
        self.view_media_info_action.setShortcut('CTRL+SHIFT+M')
        self.view_media_info_action.triggered.connect(
            self.media_information_dialog)

        self.view.addAction(self.dock_action)
        self.view.addAction(self.library_dock_action)
        self.view.addSeparator()
        self.view.addAction(self.minimalist_view_action)
        self.view.addSeparator()
        self.view.addAction(self.view_media_info_action)

    def help_menu(self):
        """Add a help menu to the menu bar.

        The help menu houses the about dialog that shows the user information
        related to the application.
        """
        self.about_action = QAction('About', self)
        self.about_action.setShortcut('H')
        self.about_action.triggered.connect(
            lambda: about.AboutDialog().exec_())

        self.help_.addAction(self.about_action)

    def open_file(self):
        """Open the selected file and add it to a new playlist."""
        filename, success = QFileDialog.getOpenFileName(
            self, 'Open File', '', 'Audio (*.mp3 *.flac)', '',
            QFileDialog.ReadOnly)

        if success:
            file_info = QFileInfo(filename).fileName()
            playlist_item = QListWidgetItem(file_info)
            self.playlist.clear()
            self.playlist_view.clear()
            self.playlist.addMedia(
                QMediaContent(QUrl().fromLocalFile(filename)))
            self.player.setPlaylist(self.playlist)
            playlist_item.setToolTip(file_info)
            self.playlist_view.addItem(playlist_item)
            self.playlist_view.setCurrentRow(0)
            self.player.play()

    def open_multiple_files(self):
        """Open the selected files and add them to a new playlist."""
        filenames, success = QFileDialog.getOpenFileNames(
            self, 'Open Multiple Files', '', 'Audio (*.mp3 *.flac)', '',
            QFileDialog.ReadOnly)

        if success:
            self.playlist.clear()
            self.playlist_view.clear()
            for file in natsort.natsorted(filenames, alg=natsort.ns.PATH):
                file_info = QFileInfo(file).fileName()
                playlist_item = QListWidgetItem(file_info)
                self.playlist.addMedia(
                    QMediaContent(QUrl().fromLocalFile(file)))
                self.player.setPlaylist(self.playlist)
                playlist_item.setToolTip(file_info)
                self.playlist_view.addItem(playlist_item)
                self.playlist_view.setCurrentRow(0)
                self.player.play()

    def open_playlist(self):
        """Load an M3U or PLS file into a new playlist."""
        playlist, success = QFileDialog.getOpenFileName(
            self, 'Open Playlist', '', 'Playlist (*.m3u *.pls)', '',
            QFileDialog.ReadOnly)

        if success:
            playlist = QUrl.fromLocalFile(playlist)
            self.playlist.clear()
            self.playlist_view.clear()
            self.playlist.load(playlist)
            self.player.setPlaylist(self.playlist)

            for song_index in range(self.playlist.mediaCount()):
                file_info = self.playlist.media(
                    song_index).canonicalUrl().fileName()
                playlist_item = QListWidgetItem(file_info)
                playlist_item.setToolTip(file_info)
                self.playlist_view.addItem(playlist_item)

            self.playlist_view.setCurrentRow(0)
            self.player.play()

    def save_playlist(self):
        """Save the media in the playlist dock as a new M3U playlist."""
        playlist, success = QFileDialog.getSaveFileName(
            self, 'Save Playlist', '', 'Playlist (*.m3u)', '')
        if success:
            saved_playlist = "{}.m3u".format(playlist)
            self.playlist.save(QUrl().fromLocalFile(saved_playlist), "m3u")

    def load_saved_playlist(self):
        """Load the saved playlist if user setting permits."""
        saved_playlist = "{}/.m3u".format(self.playlist_location)
        if os.path.exists(saved_playlist):
            playlist = QUrl().fromLocalFile(saved_playlist)
            self.playlist.load(playlist)
            self.player.setPlaylist(self.playlist)

            for song_index in range(self.playlist.mediaCount()):
                file_info = self.playlist.media(
                    song_index).canonicalUrl().fileName()
                playlist_item = QListWidgetItem(file_info)
                playlist_item.setToolTip(file_info)
                self.playlist_view.addItem(playlist_item)

            self.playlist_view.setCurrentRow(0)

    def open_directory(self):
        """Open the selected directory and add the files within to an empty playlist."""
        directory = QFileDialog.getExistingDirectory(self, 'Open Directory',
                                                     '', QFileDialog.ReadOnly)

        if directory:
            self.playlist.clear()
            self.playlist_view.clear()
            for dirpath, __, files in os.walk(directory):
                for filename in natsort.natsorted(files, alg=natsort.ns.PATH):
                    file = os.path.join(dirpath, filename)
                    if filename.endswith(('mp3', 'flac')):
                        self.playlist.addMedia(
                            QMediaContent(QUrl().fromLocalFile(file)))
                        playlist_item = QListWidgetItem(filename)
                        playlist_item.setToolTip(filename)
                        self.playlist_view.addItem(playlist_item)

            self.player.setPlaylist(self.playlist)
            self.playlist_view.setCurrentRow(0)
            self.player.play()

    def open_media_library(self, index):
        """Open a directory or file from the media library into an empty playlist."""
        self.playlist.clear()
        self.playlist_view.clear()

        if self.library_model.fileName(index).endswith(('mp3', 'flac')):
            self.playlist.addMedia(
                QMediaContent(QUrl().fromLocalFile(
                    self.library_model.filePath(index))))
            self.playlist_view.addItem(self.library_model.fileName(index))

        elif self.library_model.isDir(index):
            directory = self.library_model.filePath(index)
            for dirpath, __, files in os.walk(directory):
                for filename in natsort.natsorted(files, alg=natsort.ns.PATH):
                    file = os.path.join(dirpath, filename)
                    if filename.endswith(('mp3', 'flac')):
                        self.playlist.addMedia(
                            QMediaContent(QUrl().fromLocalFile(file)))
                        playlist_item = QListWidgetItem(filename)
                        playlist_item.setToolTip(filename)
                        self.playlist_view.addItem(playlist_item)

        self.player.setPlaylist(self.playlist)
        self.player.play()

    def display_meta_data(self):
        """Display the current song's metadata in the main window.

        If the current song contains metadata, its cover art is extracted and shown in
        the main window while the track number, artist, album, and track title are shown
        in the window title.
        """
        if self.player.isMetaDataAvailable():
            file_path = self.player.currentMedia().canonicalUrl().toLocalFile()
            (album, artist, title, track_number, *__,
             artwork) = metadata.metadata(file_path)

            try:
                self.pixmap.loadFromData(artwork)
            except TypeError:
                self.pixmap = QPixmap(artwork)

            meta_data = '{} - {} - {} - {}'.format(track_number, artist, album,
                                                   title)

            self.setWindowTitle(meta_data)
            self.art.setScaledContents(True)
            self.art.setPixmap(self.pixmap)
            self.layout.addWidget(self.art)

    def initialize_playlist(self, start):
        """Display playlist and reset playback mode when media inserted into playlist."""
        if start == 0:
            if self.library_dock.isVisible():
                self.playlist_dock.setVisible(True)
                self.playlist_dock.show()
                self.playlist_dock.raise_()

            if self.playlist.playbackMode() != QMediaPlaylist.Sequential:
                self.playlist.setPlaybackMode(QMediaPlaylist.Sequential)
                repeat_icon = utilities.resource_filename(
                    'mosaic.images', 'md_repeat_none.png')
                self.repeat_action.setIcon(QIcon(repeat_icon))

    def press_playback(self, event):
        """Change the playback of the player on cover art mouse event.

        When the cover art is clicked, the player will play the media if the player is
        either paused or stopped. If the media is playing, the media is set
        to pause.
        """
        if event.button() == 1 and configuration.Playback(
        ).cover_art_playback.isChecked():
            if (self.player.state() == QMediaPlayer.StoppedState
                    or self.player.state() == QMediaPlayer.PausedState):
                self.player.play()
            elif self.player.state() == QMediaPlayer.PlayingState:
                self.player.pause()

    def seek(self, seconds):
        """Set the position of the song to the position dragged to by the user."""
        self.player.setPosition(seconds * 1000)

    def song_duration(self, duration):
        """Set the slider to the duration of the currently played media."""
        duration /= 1000
        self.duration = duration
        self.slider.setMaximum(duration)

    def song_position(self, progress):
        """Move the horizontal slider in sync with the duration of the song.

        The progress is relayed to update_duration() in order
        to display the time label next to the slider.
        """
        progress /= 1000

        if not self.slider.isSliderDown():
            self.slider.setValue(progress)

        self.update_duration(progress)

    def update_duration(self, current_duration):
        """Calculate the time played and the length of the song.

        Both of these times are sent to duration_label() in order to display the
        times on the toolbar.
        """
        duration = self.duration

        if current_duration or duration:
            time_played = QTime(
                (current_duration / 3600) % 60, (current_duration / 60) % 60,
                (current_duration % 60), (current_duration * 1000) % 1000)
            song_length = QTime((duration / 3600) % 60, (duration / 60) % 60,
                                (duration % 60), (duration * 1000) % 1000)

            if duration > 3600:
                time_format = "hh:mm:ss"
            else:
                time_format = "mm:ss"

            time_display = "{} / {}".format(time_played.toString(time_format),
                                            song_length.toString(time_format))

        else:
            time_display = ""

        self.duration_label.setText(time_display)

    def set_state(self, state):
        """Change the icon in the toolbar in relation to the state of the player.

        The play icon changes to the pause icon when a song is playing and
        the pause icon changes back to the play icon when either paused or
        stopped.
        """
        if self.player.state() == QMediaPlayer.PlayingState:
            pause_icon = utilities.resource_filename('mosaic.images',
                                                     'md_pause.png')
            self.play_action.setIcon(QIcon(pause_icon))
            self.play_action.triggered.connect(self.player.pause)

        elif (self.player.state() == QMediaPlayer.PausedState
              or self.player.state() == QMediaPlayer.StoppedState):
            self.play_action.triggered.connect(self.player.play)
            play_icon = utilities.resource_filename('mosaic.images',
                                                    'md_play.png')
            self.play_action.setIcon(QIcon(play_icon))

    def previous(self):
        """Move to the previous song in the playlist.

        Moves to the previous song in the playlist if the current song is less
        than five seconds in. Otherwise, restarts the current song.
        """
        if self.player.position() <= 5000:
            self.playlist.previous()
        else:
            self.player.setPosition(0)

    def repeat_song(self):
        """Set the current media to repeat and change the repeat icon accordingly.

        There are four playback modes: repeat none, repeat all, repeat once, and shuffle.
        Clicking the repeat button cycles through each playback mode.
        """
        if self.playlist.playbackMode() == QMediaPlaylist.Sequential:
            self.playlist.setPlaybackMode(QMediaPlaylist.Loop)
            repeat_on_icon = utilities.resource_filename(
                'mosaic.images', 'md_repeat_all.png')
            self.repeat_action.setIcon(QIcon(repeat_on_icon))

        elif self.playlist.playbackMode() == QMediaPlaylist.Loop:
            self.playlist.setPlaybackMode(QMediaPlaylist.CurrentItemInLoop)
            repeat_on_icon = utilities.resource_filename(
                'mosaic.images', 'md_repeat_once.png')
            self.repeat_action.setIcon(QIcon(repeat_on_icon))

        elif self.playlist.playbackMode() == QMediaPlaylist.CurrentItemInLoop:
            self.playlist.setPlaybackMode(QMediaPlaylist.Random)
            repeat_icon = utilities.resource_filename('mosaic.images',
                                                      'md_shuffle.png')
            self.repeat_action.setIcon(QIcon(repeat_icon))

        elif self.playlist.playbackMode() == QMediaPlaylist.Random:
            self.playlist.setPlaybackMode(QMediaPlaylist.Sequential)
            repeat_icon = utilities.resource_filename('mosaic.images',
                                                      'md_repeat_none.png')
            self.repeat_action.setIcon(QIcon(repeat_icon))

    def activate_playlist_item(self, item):
        """Set the active media to the playlist item dobule-clicked on by the user."""
        current_index = self.playlist_view.row(item)
        if self.playlist.currentIndex() != current_index:
            self.playlist.setCurrentIndex(current_index)

        if self.player.state() != QMediaPlayer.PlayingState:
            self.player.play()

    def change_index(self, row):
        """Highlight the row in the playlist of the active media."""
        self.playlist_view.setCurrentRow(row)

    def minimalist_view(self):
        """Resize the window to only show the menu bar and audio controls."""
        if self.minimalist_view_action.isChecked():

            if self.playlist_dock.isVisible():
                self.playlist_dock_state = True
            if self.library_dock.isVisible():
                self.library_dock_state = True

            self.library_dock.close()
            self.playlist_dock.close()

            QTimer.singleShot(10, lambda: self.resize(500, 0))

        else:
            self.resize(defaults.Settings().window_size,
                        defaults.Settings().window_size + 63)

            if self.library_dock_state:
                self.library_dock.setVisible(True)

            if self.playlist_dock_state:
                self.playlist_dock.setVisible(True)

    def dock_visiblity_change(self, visible):
        """Change the size of the main window when the docks are toggled."""
        if visible and self.playlist_dock.isVisible(
        ) and not self.library_dock.isVisible():
            self.resize(
                defaults.Settings().window_size + self.playlist_dock.width() +
                6, self.height())

        elif visible and not self.playlist_dock.isVisible(
        ) and self.library_dock.isVisible():
            self.resize(
                defaults.Settings().window_size + self.library_dock.width() +
                6, self.height())

        elif visible and self.playlist_dock.isVisible(
        ) and self.library_dock.isVisible():
            self.resize(
                defaults.Settings().window_size + self.library_dock.width() +
                6, self.height())

        elif (not visible and not self.playlist_dock.isVisible()
              and not self.library_dock.isVisible()):
            self.resize(defaults.Settings().window_size,
                        defaults.Settings().window_size + 63)

    def media_information_dialog(self):
        """Show a dialog of the current song's metadata."""
        if self.player.isMetaDataAvailable():
            file_path = self.player.currentMedia().canonicalUrl().toLocalFile()
        else:
            file_path = None
        dialog = information.InformationDialog(file_path)
        dialog.exec_()

    def change_window_size(self):
        """Change the window size of the music player."""
        self.playlist_dock.close()
        self.library_dock.close()
        self.resize(defaults.Settings().window_size,
                    defaults.Settings().window_size + 63)

    def change_media_library_path(self, path):
        """Change the media library path to the new path selected in the preferences dialog."""
        self.library_model.setRootPath(path)
        self.library_view.setModel(self.library_model)
        self.library_view.setRootIndex(self.library_model.index(path))

    def closeEvent(self, event):
        """Override the PyQt close event in order to handle save playlist on close."""
        playlist = "{}/.m3u".format(self.playlist_location)
        if defaults.Settings().save_playlist_on_close:
            self.playlist.save(QUrl().fromLocalFile(playlist), "m3u")
        else:
            if os.path.exists(playlist):
                os.remove(playlist)
        QApplication.quit()
Пример #7
0
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']))
Пример #8
0
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)
Пример #9
0
class MainUIClass(QMainWindow, mp3playerGUILayout3.Ui_MainWindow):
    def __init__(self, parent=None):
        super(MainUIClass, self).__init__(parent)
        self.setupUi(self)
        self.fixLayout()
        self.mediaPlayer = QMediaPlayer()
        self.mediaPlayer.error.connect(self.erroralert)

        self.playlist = QMediaPlaylist()
        self.mediaPlayer.setPlaylist(self.playlist)

        #Connecting button clicks and functions
        self.OpenButton.clicked.connect(self.addtoqueue)
        self.PlayButton.clicked.connect(self.play)
        self.StopButton.clicked.connect(self.mediaPlayer.stop)
        self.muteButton.clicked.connect(self.mute)
        self.ForwardButton.clicked.connect(self.playlist.next)
        self.BackwardButton.clicked.connect(self.playlist.previous)

        self.addToQueue.clicked.connect(self.addtoqueue)
        self.repeatButton.clicked.connect(self.repeat)
        self.shuffleButton.clicked.connect(self.shuffle)

        #Adding valuechange functionality
        self.Timeline.setRange(0, 100)
        self.Timeline.sliderReleased.connect(self.set_position)

        self.mediaPlayer.setVolume(50)
        self.volume_on = True
        self.Volumedial.setValue(50)
        self.Volumedial.valueChanged.connect(self.set_volume)

        self.displayfilename_thread = DisplayThread()
        self.displayfilename_thread.start()
        self.displayfilename_thread.labeltext.connect(
            self.displayfilename_func)

        self.mediaPlayer.stateChanged.connect(self.mediaStateChanged)
        self.mediaPlayer.durationChanged.connect(self.durationChanged)
        self.mediaPlayer.positionChanged.connect(self.positionChanged)
        self.playlist.currentMediaChanged.connect(self.filenameChange)
        self.tableWidget.cellDoubleClicked.connect(self.getcurrentrow)
        #self.mediaPlayer.mutedChanged.connect(self.muteChange)

        self.items = 0

        self.repeated = True
        self.shuffled = True

        #p = self.palette()
        #p.setColor(self.backgroundRole(), Qt.lightGray)
        #self.setPalette(p)
        #self.dumpObjectInfo()

    @Slot()
    def addtoqueue(self):
        filename, _ = QFileDialog.getOpenFileName(
            self, "Select song")  # QDir.homePath())
        if filename != '':
            self.playlist.addMedia(QMediaContent(QUrl.fromLocalFile(filename)))
            self.addtoview(filename)

    def addtoview(self, filename):
        self.tableWidget.insertRow(self.items)
        table_filename = QTableWidgetItem(filename.split("/")[-1])
        inputdialog = InputDialog()
        inputdialog.exec_()
        if len(inputdialog.accept_func()[0]) > 0 or len(
                inputdialog.accept_func()[1]) > 0:
            table_artist = QTableWidgetItem(inputdialog.accept_func()[0])
            table_artist.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
            table_song = QTableWidgetItem(inputdialog.accept_func()[1])
            table_song.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
        else:
            table_artist = QTableWidgetItem("..artist..")
            table_artist.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
            table_song = QTableWidgetItem("..song..")
            table_song.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
        self.tableWidget.setItem(self.items, 0, table_artist)
        self.tableWidget.setItem(self.items, 1, table_song)
        for i in range(self.playlist.mediaCount()):
            print(self.playlist.media(i).canonicalUrl().fileName())
        self.PlayButton.setEnabled(True)
        self.items += 1

    @Slot()
    def play(self):
        if self.playlist.mediaCount() == 0:
            alert = selectfileAlert()
            alert.exec_()
        elif self.mediaPlayer.state() == QMediaPlayer.PlayingState:
            self.mediaPlayer.pause()
        else:
            self.mediaPlayer.play()

    @Slot()
    def set_volume(self):
        volume = self.Volumedial.value()
        self.mediaPlayer.setVolume(volume)

    @Slot(QMediaPlayer.State)
    def mediaStateChanged(self, state):
        if self.mediaPlayer.state() == QMediaPlayer.PlayingState:
            self.PlayButton.setIcon(self.style().standardIcon(
                QStyle.SP_MediaPause))
            icon = self.style().standardIcon(QStyle.SP_DialogYesButton)
            self.PlayingIcon.setPixmap(icon.pixmap(QSize(15, 15)))
        elif self.mediaPlayer.state() == QMediaPlayer.StoppedState:
            filename = "........Select song to enjoy the moment........"
            self.displayfilename_thread.filename = filename
            self.PlayButton.setIcon(self.style().standardIcon(
                QStyle.SP_MediaPlay))
            icon = self.style().standardIcon(QStyle.SP_DialogNoButton)
            self.PlayingIcon.setPixmap(icon.pixmap(QSize(15, 15)))
        else:
            self.PlayButton.setIcon(self.style().standardIcon(
                QStyle.SP_MediaPlay))
            icon = self.style().standardIcon(QStyle.SP_DialogYesButton)
            self.PlayingIcon.setPixmap(icon.pixmap(QSize(15, 15)))

    @Slot()
    def repeat(self):
        if self.repeated:
            self.playlist.setPlaybackMode(QMediaPlaylist.CurrentItemInLoop)
            self.repeatButton.setFlat(True)
            self.repeated = False
            self.statusbar.showMessage("Repeat function is on")
        else:
            self.playlist.setPlaybackMode(QMediaPlaylist.CurrentItemOnce)
            self.repeatButton.setFlat(False)
            self.repeated = True
            self.statusbar.showMessage("Repeat function is off")

    @Slot()
    def shuffle(self):
        if self.shuffled:
            self.playlist.setPlaybackMode(QMediaPlaylist.Random)
            self.shuffleButton.setFlat(True)
            self.shuffled = False
            self.displayfilename_thread.exit()
            self.statusbar.clearMessage()
            self.statusbar.showMessage("Shuffle function is on", 100000)
            self.statusbar.clearMessage()
            self.displayfilename_thread.start()
        else:
            self.playlist.setPlaybackMode(QMediaPlaylist.Sequential)
            self.shuffleButton.setFlat(False)
            self.shuffled = True
            self.displayfilename_thread.exit()
            self.statusbar.clearMessage()
            self.statusbar.showMessage("Shuffle function is off", 100000)
            self.statusbar.clearMessage()
            self.displayfilename_thread.start()

    @Slot('qint64', name="positionChanged")
    def positionChanged(self, position):
        self.Timeline.setValue(position)
        self.displaytime_func(position)

    @Slot()
    def set_position(self):
        self.mediaPlayer.setPosition(
            self.Timeline.value())  #self.mediaPlayer.setPosition(position)

    @Slot('qint64', name='durationChanged')
    def durationChanged(self, duration):
        self.Timeline.setRange(0, duration)

    def displaytime_func(self, position):
        sec = round(position / 1000.)
        sec_dt = datetime.timedelta(seconds=sec)
        sec_leftdt = datetime.timedelta(
            seconds=round(self.mediaPlayer.duration() / 1000.) - sec + 1)
        sec_format = datetime.datetime(2000, 1, 1) + sec_dt
        sec_formatdt = datetime.datetime(2000, 1, 1) + sec_leftdt
        self.Timepassed.setText(sec_format.strftime("%M min %S s"))
        self.Timeleft.setText(sec_formatdt.strftime("-%M min %S s"))

    @Slot('QString')
    def displayfilename_func(self, text):
        filetext = text.split(",")[0].replace("text: ", "")
        if text.split(",")[1] == " loop: 1":
            self.statusbar.showMessage(filetext)
            #self.displayfilename.setText(filetext)
        if text.split(",")[1] == " loop: 2":
            self.statusbar.showMessage(filetext)

    @Slot(QMediaContent)
    def filenameChange(self, media):
        if media:
            self.displayfilename_thread.quit()
            filename = media.canonicalUrl().fileName()
            filename = "Now playing ..... " + filename
            self.displayfilename_thread.filename = filename
            self.displayfilename_thread.start()

    @Slot()
    def mute(self):
        if self.volume_on:
            self.muteButton.setIcon(self.style().standardIcon(
                QStyle.SP_MediaVolumeMuted))
            self.mediaPlayer.setMuted(True)
            self.volume_on = False
        else:
            self.muteButton.setIcon(self.style().standardIcon(
                QStyle.SP_MediaVolume))
            self.mediaPlayer.setMuted(False)
            self.volume_on = True

    def fixLayout(self):
        _translate = QCoreApplication.translate
        self.PlayButton.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay))
        self.BackwardButton.setIcon(self.style().standardIcon(
            QStyle.SP_MediaSkipBackward))
        self.ForwardButton.setIcon(self.style().standardIcon(
            QStyle.SP_MediaSkipForward))
        self.StopButton.setIcon(self.style().standardIcon(QStyle.SP_MediaStop))
        self.muteButton.setIcon(self.style().standardIcon(
            QStyle.SP_MediaVolume))
        icon = self.style().standardIcon(QStyle.SP_DialogNoButton)
        self.PlayingIcon.setPixmap(icon.pixmap(QSize(15, 15)))
        self.addToQueue.setIcon(self.style().standardIcon(
            QStyle.SP_FileDialogNewFolder))
        self.repeatButton.setIcon(self.style().standardIcon(
            QStyle.SP_BrowserReload))
        self.shuffleButton.setIcon(self.style().standardIcon(
            QStyle.SP_DialogOkButton))

        self.shuffleButton.setText(_translate("MainWindow", ""))
        self.repeatButton.setText(_translate("MainWindow", ""))
        self.PlayingIcon.setText(_translate("MainWindow", ""))
        self.PlayButton.setText(_translate("MainWindow", ""))
        self.BackwardButton.setText(_translate("MainWindow", ""))
        self.ForwardButton.setText(_translate("MainWindow", ""))
        self.StopButton.setText(_translate("MainWindow", ""))
        self.Timepassed.setText(_translate("MainWindow", "00 min 00 s"))
        self.muteButton.setText(_translate("MainWindow", ""))
        self.addToQueue.setText(_translate("MainWindow", ""))
        #self.displayfilename.setText(_translate("MainWindow", ""))
        #self.displayfilename2.setText(_translate("MainWindow", ""))
        self.Timeleft.setText(_translate("MainWindow", "-00 min 00 s"))
        self.tableWidget.setHorizontalHeaderLabels(["Artist", "Song"])

    def erroralert(self):
        print(self.mediaPlayer.error())
        #print("type of args: " + type(args), "  args: ",args)
    def getcurrentrow(self, row, col):
        self.playlist.setCurrentIndex(row + 1)
        self.mediaPlayer.play()
Пример #10
0
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()))
Пример #11
0
class MusicApp(QMainWindow):
    def __init__(self):
        super().__init__()
        self.player = QMediaPlayer()
        self.playlist = QMediaPlaylist()
        self.title = 'GestureTunes - player'
        self.left = 300
        self.top = 300
        self.width = 600
        self.height = 340
        self.volume_initial_value = 60
        self.model = QStandardItemModel(0, 1)
        self.song_list = QtWidgets.QTableView()
        self.play_btn = QPushButton('播放')
        self.media_controls = QHBoxLayout()
        # 共享变量
        self.image_to_show = None
        self.rec_res = {
            "set": False,
            "used": True,
            "direction": None,
            "fingers": 0
        }
        # 摄像头窗口
        self.widget = QWidget()
        self.widget.move(1000, 200)
        self.widget.resize(300, 200)
        self.widget.setWindowTitle("GestureTunes - gesture panel")  # 窗口标题
        self.videoFrame = QLabel('正在打开摄像头,请稍等...')
        video_area = QVBoxLayout()
        self.widget.setLayout(video_area)
        video_area.addWidget(self.videoFrame)
        self.widget.show()
        # self.widget.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
        # 定时器
        self.timer = QTimer()
        self.timer.setInterval(20)
        self.timer.start()
        self.timer.timeout.connect(self.show_image)
        self.have_song = False
        self.volume_slider = QSlider(Qt.Horizontal, self)
        self.volume_slider.setTracking(True)
        # 播放模式
        self.play_style = '列表循环'
        self.single_img = QIcon('pics/single.png')
        self.loop_img = QIcon('pics/loop.png')
        self.play_style_btn = QPushButton()
        self.play_style_btn.setIcon(self.loop_img)
        self.play_tip = ''

        self.playlist.setPlaybackMode(QMediaPlaylist.Loop)

        # 初始化界面
        self.init_ui()

    def init_ui(self):
        # Add file menu
        menubar = self.menuBar()
        file_menu = menubar.addMenu('文件')

        fileAct = QAction('打开文件', self)
        folderAct = QAction('打开文件夹', self)

        file_menu.addAction(fileAct)
        file_menu.addAction(folderAct)

        fileAct.triggered.connect(self.open_file)
        folderAct.triggered.connect(self.add_files)

        self.add_listener()

        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)
        self.show()

    def add_listener(self):
        wid = QWidget(self)
        self.setCentralWidget(wid)

        # 播放控件
        self.volume_slider = QSlider(Qt.Horizontal, self)
        self.volume_slider.setFocusPolicy(Qt.NoFocus)
        self.volume_slider.valueChanged[int].connect(self.change_volume)
        self.volume_slider.setValue(self.volume_initial_value)
        open_btn = QPushButton('打开...')
        prev_btn = QPushButton('上一首')
        next_btn = QPushButton('下一首')
        # 显示播放列表
        self.set_play_list()
        self.song_list.setSelectionBehavior(
            QtWidgets.QAbstractItemView.SelectRows)
        self.song_list.setShowGrid(False)
        self.song_list.setTextElideMode(QtCore.Qt.ElideLeft)

        # 按钮的layout
        control_area = QVBoxLayout()  # centralWidget
        self.media_controls = QHBoxLayout()

        # 将播放控件添加到layout
        self.media_controls.addWidget(open_btn)
        self.media_controls.addWidget(prev_btn)
        self.media_controls.addWidget(self.play_btn)
        self.media_controls.addWidget(next_btn)
        self.media_controls.addWidget(self.play_style_btn)
        self.media_controls.addWidget(self.volume_slider)

        # 将layout添加到界面中
        control_area.addWidget(self.song_list)
        control_area.addLayout(self.media_controls)
        wid.setLayout(control_area)

        # 设置监听
        self.play_btn.clicked.connect(self.start_or_stop)
        open_btn.clicked.connect(self.add_files)
        prev_btn.clicked.connect(self.prev)
        next_btn.clicked.connect(self.next)
        self.song_list.doubleClicked.connect(self.change_song)
        self.play_style_btn.clicked.connect(self.change_play_style)

        self.statusBar()
        self.playlist.currentMediaChanged.connect(self.song_changed)

    def set_play_list(self):
        self.model.clear()
        self.song_list.horizontalHeader().hide()
        # self.song_list.verticalHeader().hide()
        # QHeaderView()
        self.song_list.setModel(self.model)  # 把数据添加至QtableView中
        self.song_list.horizontalHeader().setSectionResizeMode(
            QHeaderView.Stretch)
        self.song_list.setEditTriggers(
            QAbstractItemView.NoEditTriggers)  # 设置不可编辑单元格
        if self.playlist.mediaCount():  # 添加数据
            for index in range(self.playlist.mediaCount()):
                self.model.appendRow([  # 添加一行数据
                    QStandardItem(
                        self.playlist.media(index).canonicalUrl().fileName())
                ])
            self.song_list.selectRow(0)

    def open_file(self):
        song = QFileDialog.getOpenFileName(self, "打开文件", "~",
                                           "音频文件 (*.mp3 *.ogg *.wav *.m4a)")

        if song[0] != '':
            url = QUrl.fromLocalFile(song[0])
            if self.playlist.mediaCount() == 0:
                self.playlist.addMedia(QMediaContent(url))
                self.player.setPlaylist(self.playlist)
                self.player.play()
            else:
                self.playlist.addMedia(QMediaContent(url))
        self.set_play_list()
        self.have_song = True

    def add_files(self):
        if self.playlist.mediaCount() != 0:
            self.playlist.clear()
        self.folder_iterator()
        self.player.setPlaylist(self.playlist)
        self.player.playlist().setCurrentIndex(0)
        self.song_list.selectRow(0)
        self.set_play_list()
        self.player.pause()
        self.play_tip = "暂停:" + self.playlist.currentMedia().canonicalUrl(
        ).fileName()
        self.statusBar().showMessage(self.play_tip + " - " + self.play_style)
        self.have_song = True

    def folder_iterator(self):
        folder_chosen = QFileDialog.getExistingDirectory(self, '打开文件夹', '~')
        if folder_chosen is not None:
            it = QDirIterator(folder_chosen)
            it.next()
            while it.hasNext():
                if (not it.fileInfo().isDir()) and it.filePath() != '.':
                    f_info = it.fileInfo()
                    if f_info.suffix() in ('mp3', 'ogg', 'wav', 'm4a'):
                        self.playlist.addMedia(
                            QMediaContent(QUrl.fromLocalFile(it.filePath())))
                it.next()
            if (not it.fileInfo().isDir()) and it.filePath() != '.':
                f_info = it.fileInfo()
                if f_info.suffix() in ('mp3', 'ogg', 'wav', 'm4a'):
                    self.playlist.addMedia(
                        QMediaContent(QUrl.fromLocalFile(it.filePath())))

    def start_or_stop(self):
        print(self.player.state())
        if self.playlist.mediaCount() == 0:
            self.open_file()
        else:
            if self.player.state() == QMediaPlayer.PlayingState or \
                    (self.player.state() == QMediaPlayer.PlayingState and self.play_btn.text() == '暂停'):
                self.player.pause()
                self.play_btn.setText('播放')
                self.play_tip = "暂停:" + self.playlist.currentMedia(
                ).canonicalUrl().fileName()
            elif self.player.state() == QMediaPlayer.PausedState or \
                    (self.player.state() == QMediaPlayer.PlayingState and self.play_btn.text() == '播放'):
                self.player.play()
                self.play_btn.setText('暂停')
                self.play_tip = "正在播放:" + self.playlist.currentMedia(
                ).canonicalUrl().fileName()
            self.statusBar().showMessage(self.play_tip + " - " +
                                         self.play_style)

    def change_volume(self, value):
        self.player.setVolume(value)

    def volume_up(self):
        volume = self.player.volume(
        ) + 10 if self.player.volume() + 10 <= 100 else 100
        self.player.setVolume(volume)
        self.volume_slider.setTracking(True)
        self.volume_slider.setValue(volume)

    def volume_down(self):
        volume = self.player.volume(
        ) - 10 if self.player.volume() - 10 >= 0 else 0
        self.player.setVolume(volume)
        self.volume_slider.setTracking(True)
        self.volume_slider.setValue(volume)

    def prev(self):
        if self.playlist.mediaCount() == 0:
            self.open_file()
        elif self.playlist.mediaCount() != 0:
            self.player.playlist().previous()

    def shuffle(self):
        self.playlist.shuffle()

    def next(self):
        if self.playlist.mediaCount() == 0:
            self.open_file()
        elif self.playlist.mediaCount() != 0:
            self.player.playlist().next()

    def song_changed(self, media):
        if not media.isNull():
            url = media.canonicalUrl()
            self.play_tip = "正在播放:" + url.fileName()
            self.statusBar().showMessage(self.play_tip + " - " +
                                         self.play_style)
            self.song_list.selectRow(self.playlist.currentIndex())

    def change_song(self):
        index = self.song_list.currentIndex().row()  # 获取双击所在行
        self.playlist.setCurrentIndex(index)

    def change_play_style(self):
        if self.playlist.playbackMode() == QMediaPlaylist.Loop:
            self.playlist.setPlaybackMode(QMediaPlaylist.CurrentItemInLoop)
            self.play_style = "单曲循环"
            self.play_style_btn.setIcon(self.single_img)
        elif self.playlist.playbackMode() == QMediaPlaylist.CurrentItemInLoop:
            self.playlist.setPlaybackMode(QMediaPlaylist.Loop)
            self.play_style = "列表循环"
            self.play_style_btn.setIcon(self.loop_img)
        self.statusBar().showMessage(self.play_tip + " - " + self.play_style)

    def convert_image(self, frame):
        if len(frame.shape) == 2:  # 若是灰度图则转为三通道
            frame = cv2.cvtColor(frame, cv2.COLOR_GRAY2BGR)

        rgb_image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)  # 将BGR转为RGB
        rgb_image = np.asanyarray(rgb_image)
        label_image = QImage(rgb_image.data, rgb_image.shape[1],
                             rgb_image.shape[0],
                             QImage.Format_RGB888)  # 转化为QImage
        self.image_to_show = QPixmap(label_image)

    def show_image(self):
        if self.image_to_show is not None:
            self.videoFrame.setPixmap(self.image_to_show)
        if self.rec_res['set'] and not self.rec_res['used']:
            final_direction = self.rec_res['direction']
            final_fingers = self.rec_res['fingers']
            if final_fingers == 3:
                self.start_or_stop()
                self.rec_res['used'] = True
                return
            if final_fingers == 5:
                self.change_play_style()
                self.rec_res['used'] = True
                return
            if final_direction is not None and final_direction != 'NOT_FOUND':
                if final_direction == "RIGHT":
                    self.next()
                    self.rec_res['used'] = True
                if final_direction == "LEFT":
                    self.prev()
                    self.rec_res['used'] = True
                if final_direction == "UP":
                    self.volume_up()
                    self.rec_res['used'] = True
                if final_direction == "DOWN":
                    self.volume_down()
                    self.rec_res['used'] = True

    def set_rec_res(self, res):
        self.rec_res = res
Пример #12
0
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)