Exemplo n.º 1
0
class Music_Player(QWidget, Ui_Form):

    with open("path.txt", 'r') as f:
        home_dir = f.read()
        home_dir = home_dir.rstrip()

    def __init__(self):
        #super(Music_Player, self).__init__()
        super().__init__()
        #set up the user interface from Designer
        self.setupUi(self)

        # this gets the inforation needed for the table
        lib = []
        directory = os.path.join(Music_Player.home_dir, "Music")

        walk = os.walk(directory)
        for root, dirs, files in walk:
            for file in files:
                path = os.path.join(root, file)
                if file.endswith(
                        '.mp3'):  # then we should add file to library\
                    song = os.path.splitext(file)[0]
                    song = song.replace('_', ' ')

                    # using eyed3 to get song info
                    audiofile = eyed3.load(path)
                    artist = audiofile.tag.artist
                    album = audiofile.tag.album
                    length_secs = audiofile.info.time_secs  # seconds
                    length_formatted = seconds_to_minutes(length_secs)

                    # list of lists with information
                    lib.append([
                        song, artist, album, length_formatted, path,
                        str(length_secs)
                    ])  # length shows seconds

        # usedfor the functions
        self.library = lib

        self.songTableWidget_tab3.setRowCount(len(lib))
        self.songTableWidget_2.setRowCount(len(lib))
        self.songTableWidget.setRowCount(len(lib))
        self.songTableWidget.setColumnHidden(4, True)  #hide path column
        self.songTableWidget.setColumnHidden(
            5, True)  #hide length column that shows seconds
        self.songTableWidget_2.setColumnHidden(4, True)  #hide path column
        self.songTableWidget_2.setColumnHidden(
            5, True)  #hide length column that shows seconds
        self.songTableWidget_tab3.setColumnHidden(4, True)  #hide path column
        self.songTableWidget_tab3.setColumnHidden(
            5, True)  #hide length column that shows seconds
        self.songTableWidget.setEditTriggers(
            QAbstractItemView.NoEditTriggers)  # no way to edit items
        self.songTableWidget_tab3.setEditTriggers(
            QAbstractItemView.NoEditTriggers)

        # setting the column widths
        self.songTableWidget.setColumnWidth(0, 220)
        self.songTableWidget.setColumnWidth(1, 160)
        self.songTableWidget.setColumnWidth(2, 165)
        self.songTableWidget.setColumnWidth(3, 60)

        # setting up touch scrolling for the songTableWidget
        scroller = QScroller.scroller(self.songTableWidget)
        QScroller.grabGesture(self.songTableWidget,
                              QScroller.LeftMouseButtonGesture)

        # setting up touch scrolling for the playlistTableWidget_2
        scroller_playlist_2 = QScroller.scroller(self.playlistTableWidget_2)
        QScroller.grabGesture(self.playlistTableWidget_2,
                              QScroller.LeftMouseButtonGesture)

        # setting up touch scrolling for the songTableWidget_2
        scroller_2 = QScroller.scroller(self.songTableWidget_2)
        QScroller.grabGesture(self.songTableWidget_2,
                              QScroller.LeftMouseButtonGesture)

        # setting up touch scrolling for the songTableWidget_tab3
        scroller_3 = QScroller.scroller(self.songTableWidget_tab3)
        QScroller.grabGesture(self.songTableWidget_tab3,
                              QScroller.LeftMouseButtonGesture)

        # populating the table
        for i in range(len(lib)):  #number of rows
            for k in range(6):  # there are 6 columns
                item = QTableWidgetItem(lib[i][k])
                item2 = QTableWidgetItem(lib[i][k])
                item3 = QTableWidgetItem(lib[i][k])
                self.songTableWidget.setItem(i, k, item)
                self.songTableWidget_tab3.setItem(i, k, item2)  # for tab3
                self.songTableWidget_2.setItem(i, k, item3)

        self.songTableWidget.sortItems(0, Qt.AscendingOrder)
        self.songTableWidget_tab3.sortItems(0, Qt.AscendingOrder)  # tab 3

        # add the check mark boxes so they don't only appear when the song is played for the first time
        for i in range(self.songTableWidget.rowCount()):
            self.songTableWidget.item(i, 0).setCheckState(Qt.Unchecked)
            self.songTableWidget_2.item(i, 0).setCheckState(Qt.Unchecked)

        # setting up media Player
        self.player = QMediaPlayer(None)

        if not os.path.exists("log.txt"):
            with open("log.txt", 'w') as f:
                pass
        # the time doesn't work but the row does
        # this will load the last song played into the media player, though time will start from 0
        with open("log.txt", 'r') as f:
            info = f.read()

        if info:
            row = int(info.split(',')[0])
            self.time = int(
                info.split(',')[1]
            )  # used in mediaStatusChanged to allow for the startup time
            song_path = self.songTableWidget.item(row, 4).text()
            self.player.setMedia(QMediaContent(QUrl.fromLocalFile(song_path)))
            #self.player.play()

            # used by mediaStatusChanged to load in the correct place the player left off from
            #self.startup = 1

        else:
            row = 0
            default_song_path = self.songTableWidget.item(0, 4).text()
            self.player.setMedia(
                QMediaContent(QUrl.fromLocalFile(default_song_path)))

        # make adjustments
        self.volumeSlider.setMaximum(100)
        self.volumeSlider.setSliderPosition(100)
        self.volumeLabel.setText(str(100))

        self.songSlider.setMinimum(0)
        self.songSlider.setMaximum(
            int(self.songTableWidget.item(row, 5).text()))

        self.endLabel.setText(self.songTableWidget.item(row, 3).text())
        self.songPlayingLabel.setText(self.songTableWidget.item(row, 0).text())

        # tab 3 signals and slots
        self.saveButton_tab3.clicked.connect(self.save_click)
        self.searchEdit_tab3.textChanged.connect(self.search_edit)

        #conncect buttons
        self.volumeSlider.valueChanged.connect(self.volume_change)
        self.playButton.clicked.connect(self.play_click)
        self.songTableWidget.cellDoubleClicked.connect(self.double_click)
        self.player.stateChanged.connect(
            self.state_changed)  # playing, paused, no media
        self.player.positionChanged.connect(self.position_changed)
        self.player.mediaStatusChanged.connect(
            self.media_status)  # #loading media, end of media, etc.
        self.player.currentMediaChanged.connect(
            self.song_changed)  # when the song changes
        self.songSlider.sliderMoved.connect(self.slider_moved)
        self.songSlider.sliderReleased.connect(self.slider_released)
        self.nextButton.clicked.connect(self.next)
        self.prevButton.clicked.connect(self.prev)

        ##################################################################
        ###########################################################
        #################### tab 2 #############################\

        self.finaldeleteButton_2.setVisible(False)

        self.player_2 = QMediaPlayer(None)
        self.player_2.setVolume(100)

        # make adjustments
        self.volumeSlider_2.setMaximum(100)
        self.volumeSlider_2.setSliderPosition(100)
        self.volumeLabel_2.setText(str(100))

        self.endLabel_2.setText(self.songTableWidget_2.item(0, 3).text())
        self.songPlayingLabel_2.setText(
            self.songTableWidget_2.item(0, 0).text())

        self.playlistTableWidget_2.setEditTriggers(
            QAbstractItemView.NoEditTriggers)
        self.songTableWidget_2.setEditTriggers(
            QAbstractItemView.NoEditTriggers)

        #conncect buttons
        self.volumeSlider_2.valueChanged.connect(self.volume_change_2)
        self.playButton_2.clicked.connect(self.play_click_2)
        self.songTableWidget_2.cellDoubleClicked.connect(self.double_click_2)
        self.player_2.stateChanged.connect(
            self.state_changed_2)  # playing, paused, no media
        self.player_2.positionChanged.connect(self.position_changed_2)
        self.player_2.mediaStatusChanged.connect(
            self.media_status_2)  # #loading media, end of media, etc.
        self.player_2.currentMediaChanged.connect(
            self.song_changed_2)  # when the song changes
        self.songSlider_2.sliderMoved.connect(self.slider_moved_2)
        self.songSlider_2.sliderReleased.connect(self.slider_released_2)
        self.nextButton_2.clicked.connect(self.next_2)
        self.prevButton_2.clicked.connect(self.prev_2)

        # if there are playlists present on the system we want to list them
        files = os.listdir(Music_Player.home_dir +
                           "/Desktop/music_player/playlists")
        if files:
            self.playlistTableWidget_2.setRowCount(len(files))
            playlist_names = [os.path.splitext(file)[0] for file in files]

            x = 0
            for file in files:
                with open(
                        Music_Player.home_dir +
                        '/Desktop/music_player/playlists/{}'.format(file),
                        'r') as f:
                    lines = f.readlines()
                    amount_of_songs = lines[-2]
                    date_added = lines[-1]
                playlist_name = os.path.splitext(file)[0]
                item_1 = QTableWidgetItem(playlist_name)
                item_2 = QTableWidgetItem(amount_of_songs)
                item_3 = QTableWidgetItem(date_added)
                self.playlistTableWidget_2.setItem(x, 0, item_1)
                self.playlistTableWidget_2.setItem(x, 1, item_2)
                self.playlistTableWidget_2.setItem(x, 2, item_3)
                x = x + 1

        self.playlistTableWidget_2.cellDoubleClicked.connect(
            self.choose_playlist)
        self.playlist_button_2.clicked.connect(self.back_to_playlists)
        self.deleteButton_2.clicked.connect(self.delete_button_2)
        self.finaldeleteButton_2.clicked.connect(self.finaldelete_clicked_2)

    def delete_button_2(self):
        if self.deleteButton_2.isChecked():
            self.playlistTableWidget_2.setSelectionMode(
                QtWidgets.QAbstractItemView.MultiSelection)
            # show delete button
            self.finaldeleteButton_2.setVisible(True)
        if self.deleteButton_2.isChecked() == False:
            self.playlistTableWidget_2.setSelectionMode(
                QtWidgets.QAbstractItemView.ExtendedSelection)
            self.finaldeleteButton_2.setVisible(False)

    def finaldelete_clicked_2(self):
        items = self.playlistTableWidget_2.selectedItems(
        )  # will have many duplicates in the list that is returned
        if items:  # if list is not empty
            pass
        else:
            self.finalDeleteLabel_2.setText("Must Select Playlist[s]")

    def back_to_playlists(self):
        self.stackedWidget.setCurrentIndex(0)

    def choose_playlist(self, row, column):  # when the playlist is clicked
        ## get back to neutral so we don't keep removing songs from table

        self.songTableWidget_2.sortItems(0, Qt.AscendingOrder)
        count = self.songTableWidget_2.rowCount()
        for k in range(count):
            self.songTableWidget_2.setRowHidden(k, False)

        playlist_name = self.playlistTableWidget_2.item(row, 0).text()
        self.stackedWidget.setCurrentIndex(1)
        self.playlist_name_2.setText(playlist_name)
        with open(
                Music_Player.home_dir +
                '/Desktop/music_player/playlists/{}.txt'.format(playlist_name),
                'r') as f:
            songs = f.readlines(
            )[:
              -2]  # the last two lines are amount of songs in the playlist and the date the playlist was created
            songs = [song.rstrip() for song in songs]

        items = []
        for song in songs:
            items.append(
                self.songTableWidget_2.findItems(song, Qt.MatchContains))
        items = [item[0] for item in items]
        rows = {i.row() for i in items}

        # hiding the songs not in playlist
        for k in range(count):
            if k not in rows:
                self.songTableWidget_2.setRowHidden(k, True)

    def prev_2(self):
        path = self.player_2.currentMedia().canonicalUrl().path()
        item = self.songTableWidget_2.findItems(path, Qt.MatchExactly)
        row = item[0].row()  # gives the index starting at 0
        #print(row, self.songTableWidget.rowCount())

        if self.player_2.position() >= 2000:
            self.player_2.setPosition(0)
            return True

        if row == 0:
            print("row==0")
            for i in range(1, self.songTableWidget_2.rowCount()):
                next_row = self.songTableWidget_2.rowCount(
                ) - i  # row count starts counting at 1 so index = rowCount -1
                if self.songTableWidget_2.isRowHidden(next_row):
                    continue
                else:
                    break
            new_song_path = self.songTableWidget_2.item(next_row, 4).text()
        else:
            for i in range(
                    1,
                    self.songTableWidget_2.rowCount()):  # 1 to the row count
                next_row = row - i  # all you need is 1 minus the row count to make the list full circle
                if next_row < 0:
                    next_row = self.songTableWidget_2.rowCount(
                    ) + next_row  # then we need be back at the last index

                if self.songTableWidget_2.isRowHidden(next_row):
                    continue
                else:
                    break
            new_song_path = self.songTableWidget_2.item(next_row, 4).text()

        if self.player_2.state() == QMediaPlayer.PlayingState:
            self.player_2.setMedia(
                QMediaContent(QUrl.fromLocalFile(new_song_path)))
            self.player_2.play()
        else:
            self.player_2.setMedia(
                QMediaContent(QUrl.fromLocalFile(new_song_path)))

    def next_2(self):
        path = self.player_2.currentMedia().canonicalUrl().path()
        item = self.songTableWidget_2.findItems(path, Qt.MatchExactly)
        row = item[0].row()  # gives the index starting at 0

        if self.songTableWidget_2.rowCount() == (
                row + 1):  # then we are at end of table
            next_row = 0
            if self.songTableWidget_2.isRowHidden(next_row):
                for i in range(1, self.songTableWidget_2.rowCount()):
                    next_row = next_row + i
                    if self.songTableWidget_2.isRowHidden(next_row):
                        continue
                    else:
                        break

            new_song_path = self.songTableWidget_2.item(next_row, 4).text()

        else:
            for i in range(1, self.songTableWidget_2.rowCount()):
                next_row = row + i
                if next_row >= self.songTableWidget_2.rowCount(
                ):  # then the index is out of range
                    next_row = next_row - self.songTableWidget_2.rowCount()
                if self.songTableWidget_2.isRowHidden(next_row):
                    continue
                else:
                    break
            new_song_path = self.songTableWidget_2.item(next_row, 4).text()

        if self.player_2.state() == QMediaPlayer.PlayingState:
            self.player_2.setMedia(
                QMediaContent(QUrl.fromLocalFile(new_song_path)))
            self.player_2.play()
        else:
            self.player_2.setMedia(
                QMediaContent(QUrl.fromLocalFile(new_song_path)))

    def slider_moved_2(self, value):  # signal when slider is pressed down
        if self.songSlider_2.isSliderDown():
            time = seconds_to_minutes(value)
            self.startLabel_2.setText(time)

    def slider_released_2(self):
        seconds = self.songSlider_2.sliderPosition()
        self.player_2.setPosition(seconds * 1000)

    def song_changed_2(self, song):  # takes QMediaContent
        path = (song.canonicalUrl().path()
                )  # path of song used to locate row in table
        item = self.songTableWidget_2.findItems(path, Qt.MatchExactly)
        row = item[0].row()  # there can only be one match in the list

        self.songPlayingLabel_2.setText(
            self.songTableWidget_2.item(row, 0).text())
        self.endLabel_2.setText(self.songTableWidget_2.item(row, 3).text())
        self.songSlider_2.setMaximum(
            int(self.songTableWidget_2.item(row, 5).text()))

        # now need to setup how the slider will work
        # when slider is pressed down song still plays at normal pace but start label adjusts to where the slider is
        # shen slider is released then move the song to that position

    def media_status_2(self, status):  # takes QMediaPlayer.MediaStatus
        if status == QMediaPlayer.BufferedMedia:
            # get rid of all the check marks on the songs that have been played
            for i in range(self.songTableWidget_2.rowCount()):
                self.songTableWidget_2.item(i, 0).setCheckState(Qt.Unchecked)
            path = self.player_2.currentMedia().canonicalUrl().path()
            item = self.songTableWidget_2.findItems(
                path, Qt.MatchExactly)  #qtbalewidgetitem
            row = item[0].row(
            )  # this finds the row that the qtablewidgetitem originates
            self.songTableWidget_2.item(row, 0).setCheckState(Qt.Checked)
        elif status == QMediaPlayer.EndOfMedia:
            path = self.player_2.currentMedia().canonicalUrl().path()
            item = self.songTableWidget_2.findItems(
                path, Qt.MatchExactly)  #qtbalewidgetitem
            row = item[0].row(
            )  # this finds the row that the qtablewidgetitem originates
            self.songTableWidget_2.item(row, 0).setCheckState(Qt.Unchecked)
            print("end of media")

    def position_changed_2(self,
                           position):  # position is expressed in milleseconds
        seconds = int(position / 1000)  # now its in seconds
        if self.songSlider_2.isSliderDown() == False:
            self.songSlider_2.setSliderPosition(seconds)
            time = seconds_to_minutes(seconds)
            self.startLabel_2.setText(time)

    def state_changed_2(self, state):
        if self.player_2.state() == QMediaPlayer.PlayingState:
            self.playButton_2.setText("Pause")
            # turns the library player off
            self.player.pause()
        if self.player_2.state(
        ) == QMediaPlayer.PausedState or self.player_2.state(
        ) == QMediaPlayer.StoppedState:
            self.playButton_2.setText("Play")

    def play_click_2(self):
        if self.player_2.state() == 1:  # playing state
            self.player_2.pause()
        elif self.player_2.state() == 2:  # paused state
            self.player_2.play()
        else:  # stopped state which = 0
            self.player_2.play()

    def volume_change_2(self, value):
        self.volumeLabel_2.setText(str(value))
        self.player_2.setVolume(value)

    def double_click_2(self, row, column):  #row and column are ints
        song = self.songTableWidget_2.item(
            row, 0).text()  # this is the cell with song name
        # returns the qtablewidgetitem need to change it to text
        path = self.songTableWidget_2.item(row, 4).text()
        self.songPlayingLabel_2.setText(song)
        self.player_2.setMedia(QMediaContent(QUrl.fromLocalFile(path)))
        self.player_2.play()


#####################################################################################
#####################################################################################
#################################### tab 3 ###########################################
#####################################################################################
#####################################################################################

    def search_edit(self, text):
        # reset at the beginnning so that everything shows up
        count = self.songTableWidget_tab3.rowCount()
        for k in range(count):
            self.songTableWidget_tab3.setRowHidden(k, False)

        items = self.songTableWidget_tab3.findItems(text, Qt.MatchContains)
        rows = {i.row() for i in items}

        count = self.songTableWidget_tab3.rowCount()
        for k in range(count):
            if k not in rows:
                self.songTableWidget_tab3.setRowHidden(k, True)

    def save_click(self):
        playlist_name = self.playlistNameEdit_tab3.text()
        if playlist_name:  # will need to check that there is no other playlist name that has the same Name
            if self.songTableWidget_tab3.selectedItems():
                rows_selected = {
                    i.row()
                    for i in self.songTableWidget_tab3.selectedItems()
                }
                songs_selected = [
                    self.songTableWidget_tab3.item(i, 4).text()
                    for i in rows_selected
                ]
                print(rows_selected)
                print(songs_selected)

                with open(
                        Music_Player.home_dir +
                        '/Desktop/music_player/playlists/{}.txt'.format(
                            playlist_name), 'w') as f:
                    for i in songs_selected:
                        f.write(i + '\n')
                    f.write(str(len(songs_selected)) + '\n')
                    f.write(str(datetime.datetime.now()))

                # update the playlist table in tab2
                files = os.listdir(Music_Player.home_dir +
                                   '/Desktop/music_player/playlists')
                if files:
                    self.playlistTableWidget_2.setRowCount(len(files))
                    playlist_names = [
                        os.path.splitext(file)[0] for file in files
                    ]

                    x = 0
                    for file in files:
                        with open(
                                Music_Player.home_dir +
                                '/Desktop/music_player/playlists/{}'.format(
                                    file), 'r') as f:
                            lines = f.readlines()
                            amount_of_songs = lines[-2]
                            date_added = lines[-1]
                        playlist_name = os.path.splitext(file)[0]
                        item_1 = QTableWidgetItem(playlist_name)
                        item_2 = QTableWidgetItem(amount_of_songs)
                        item_3 = QTableWidgetItem(date_added)
                        self.playlistTableWidget_2.setItem(x, 0, item_1)
                        self.playlistTableWidget_2.setItem(x, 1, item_2)
                        self.playlistTableWidget_2.setItem(x, 2, item_3)
                        x = x + 1

            else:
                self.playlist_verifier_tab3.setText("Need to Select Songs")

        else:
            self.playlist_verifier_tab3.setText("Not a Valid Name")

    ##############################################################
    ###########################################################
    #################### tab 1 #############################

    def prev(self):
        path = self.player.currentMedia().canonicalUrl().path()
        item = self.songTableWidget.findItems(path, Qt.MatchExactly)
        row = item[0].row()  # gives the index starting at 0
        #print(row, self.songTableWidget.rowCount())

        if self.player.position() >= 2000:
            self.player.setPosition(0)
            return True

        if row == 0:
            next_row = self.songTableWidget.rowCount(
            ) - 1  # row count starts counting at 1 so index = rowCount -1
            new_song_path = self.songTableWidget.item(next_row, 4).text()
        else:
            next_row = row - 1
            new_song_path = self.songTableWidget.item(next_row, 4).text()

        if self.player.state() == QMediaPlayer.PlayingState:
            self.player.setMedia(
                QMediaContent(QUrl.fromLocalFile(new_song_path)))
            self.player.play()
        else:
            self.player.setMedia(
                QMediaContent(QUrl.fromLocalFile(new_song_path)))

    def next(self):
        path = self.player.currentMedia().canonicalUrl().path()
        item = self.songTableWidget.findItems(path, Qt.MatchExactly)
        row = item[0].row()  # gives the index starting at 0

        if self.songTableWidget.rowCount() == (
                row + 1):  # then we are at end of table
            next_row = 0
            new_song_path = self.songTableWidget.item(next_row, 4).text()

        else:
            next_row = row + 1
            new_song_path = self.songTableWidget.item(next_row, 4).text()

        if self.player.state() == QMediaPlayer.PlayingState:
            self.player.setMedia(
                QMediaContent(QUrl.fromLocalFile(new_song_path)))
            self.player.play()
        else:
            self.player.setMedia(
                QMediaContent(QUrl.fromLocalFile(new_song_path)))

    def slider_moved(self, value):  # signal when slider is pressed down
        if self.songSlider.isSliderDown():
            time = seconds_to_minutes(value)
            self.startLabel.setText(time)

    def slider_released(self):
        seconds = self.songSlider.sliderPosition()
        self.player.setPosition(seconds * 1000)

    def song_changed(self, song):  # takes QMediaContent
        path = (song.canonicalUrl().path()
                )  # path of song used to locate row in table
        item = self.songTableWidget.findItems(path, Qt.MatchExactly)
        row = item[0].row()  # there can only be one match in the list

        self.songPlayingLabel.setText(self.songTableWidget.item(row, 0).text())
        self.endLabel.setText(self.songTableWidget.item(row, 3).text())
        self.songSlider.setMaximum(
            int(self.songTableWidget.item(row, 5).text()))

        # now need to setup how the slider will work
        # when slider is pressed down song still plays at normal pace but start label adjusts to where the slider is
        # shen slider is released then move the song to that position

    def media_status(self, status):  # takes QMediaPlayer.MediaStatus
        if status == QMediaPlayer.BufferedMedia:
            # get rid of all the check marks on the songs that have been played
            for i in range(self.songTableWidget.rowCount()):
                self.songTableWidget.item(i, 0).setCheckState(Qt.Unchecked)
            path = self.player.currentMedia().canonicalUrl().path()
            item = self.songTableWidget.findItems(
                path, Qt.MatchExactly)  #qtbalewidgetitem
            row = item[0].row(
            )  # this finds the row that the qtablewidgetitem originates
            self.songTableWidget.item(row, 0).setCheckState(Qt.Checked)
        elif status == QMediaPlayer.EndOfMedia:
            path = self.player.currentMedia().canonicalUrl().path()
            item = self.songTableWidget.findItems(
                path, Qt.MatchExactly)  #qtbalewidgetitem
            row = item[0].row(
            )  # this finds the row that the qtablewidgetitem originates
            self.songTableWidget.item(row, 0).setCheckState(Qt.Unchecked)
            print("end of media")

    def position_changed(self,
                         position):  # position is expressed in milleseconds
        seconds = int(position / 1000)  # now its in seconds
        if self.songSlider.isSliderDown() == False:
            self.songSlider.setSliderPosition(seconds)
            time = seconds_to_minutes(seconds)
            self.startLabel.setText(time)

    def state_changed(self, state):
        if self.player.state() == QMediaPlayer.PlayingState:
            self.playButton.setText("Pause")
            self.player_2.pause()

        if self.player.state(
        ) == QMediaPlayer.PausedState or self.player.state(
        ) == QMediaPlayer.StoppedState:
            self.playButton.setText("Play")

    def play_click(self):
        if self.player.state() == 1:  # playing state
            self.player.pause()
        elif self.player.state() == 2:  # paused state
            self.player.play()
        else:  # stopped state which = 0
            self.player.play()

    def volume_change(self, value):
        self.volumeLabel.setText(str(value))
        self.player.setVolume(value)

    def double_click(self, row, column):  #row and column are ints
        song = self.songTableWidget.item(
            row, 0).text()  # this is the cell with song name
        # returns the qtablewidgetitem need to change it to text
        path = self.songTableWidget.item(row, 4).text()
        self.songPlayingLabel.setText(song)
        self.player.setMedia(QMediaContent(QUrl.fromLocalFile(path)))
        self.player.play()
        #self.songTableWidget.

    def closeEvent(self, event):
        path = self.player.currentMedia().canonicalUrl().path()
        item = self.songTableWidget.findItems(
            path, Qt.MatchExactly)  #qtbalewidget item
        row = item[0].row(
        )  # this finds the row that the qtablewidgetitem originates
        time = int(
            self.player.position() /
            1000) - 1  # saves the milliseconds from the beginning of the song
        with open('log.txt', 'w') as f:
            info = "{},{}".format(str(row), str(time))
            f.write(info)
        event.accept()
Exemplo n.º 2
0
class PMusic(QWidget):
    '''central widget'''

    DEFAULT_IMG = '/usr/share/pmusic/pMusic.png'

    def __init__(self, parent):
        '''initialize instance'''

        super().__init__(parent)

        self.player = QMediaPlayer()
        self.player.mediaStatusChanged.connect(self.onmedia_status_changed)
        self.playlist = QMediaPlaylist()
        self.playlist.setPlaybackMode(QMediaPlaylist.Loop)

        self.player.setVolume(100)

        self.resize(parent.width(), parent.height())
        self.setContentsMargins(0, 0, 0, 0)

        self.current_albumart = ''
        pixmap = QPixmap(PMusic.DEFAULT_IMG)
        self.img_label = PImage(self, pixmap)
        self.img_label.resize(self.width(), self.height())
        self.img_label.clicked.connect(self.onclick_img_label)

        self.buttonbar = PButtonBar(self)
        self.buttonbar.hide()
        # position: at the bottom
        self.buttonbar.move(0, self.height() - self.buttonbar.height())
        self.buttonbar.clicked_left.connect(self.onclick_prev)
        self.buttonbar.clicked_mid.connect(self.onclick_main)
        self.buttonbar.clicked_right.connect(self.onclick_next)

        # toggle button for shuffle
        self.shufflebutton = QPushButton(self)
        self.shufflebutton.hide()
        self.shufflebutton.setCheckable(True)
        self.shufflebutton.setText('S')
        # position: top left corner
        button_size = int(self.width() * 0.2)
        self.shufflebutton.setGeometry(0, 0, button_size, button_size)
        self.shufflebutton.clicked.connect(self.onclick_shuffle)

        self.quitbutton = QPushButton(self)
        self.quitbutton.hide()
        self.quitbutton.setStyleSheet('color: rgb(240, 0, 0)')      # red
        self.quitbutton.setFont(QFont('webdings', 10))
        self.quitbutton.setText('r')                                # cross
        # position: top right corner
        self.quitbutton.setGeometry(self.width() - button_size, 0, button_size, button_size)
        self.quitbutton.clicked.connect(self.onclick_quit)

        self.show()

    def enterEvent(self, event):
        '''on mouse enter, show the buttons'''

        super().enterEvent(event)

        self.buttonbar.show()
        self.shufflebutton.show()
        self.quitbutton.show()

    def leaveEvent(self, event):
        '''on mouse leave, hide the buttons'''

        super().leaveEvent(event)

        self.buttonbar.hide()
        self.shufflebutton.hide()
        self.quitbutton.hide()

    @pyqtSlot()
    def onclick_shuffle(self):
        '''shuffle button was toggled'''

        if self.shufflebutton.isChecked():
            debug('shuffle: on')
            self.playlist.setPlaybackMode(QMediaPlaylist.Random)
        else:
            debug('shuffle: off')
            self.playlist.setPlaybackMode(QMediaPlaylist.Loop)

    @pyqtSlot()
    def onclick_quit(self):
        '''quit button was clicked'''

        debug('quit')
        self.stop()
        self.parent().close()

    @pyqtSlot()
    def onclick_img_label(self):
        '''image label was clicked'''

        debug('onclick_img_label')
        self.pause()

    @pyqtSlot()
    def onclick_prev(self):
        '''back button was pressed'''

        debug('onclick_prev')
        self.playlist.previous()

        if self.player.state() != QMediaPlayer.PlayingState:
            debug('player.state == {}'.format(self.player.state()))
            self.play()

    @pyqtSlot()
    def onclick_next(self):
        '''next button was pressed'''

        debug('onclick_next')
        self.playlist.next()

        if self.player.state() != QMediaPlayer.PlayingState:
            debug('player.state == {}'.format(self.player.state()))
            self.play()

    @pyqtSlot()
    def onclick_main(self):
        '''main button was pressed'''

        debug('onclick_main')

        # bring up directory selection dialog
        # have a preference for $HOME/Music/
        try:
            homedir = os.environ['HOME']
            if not os.path.isdir(homedir):
                homedir = os.path.curdir
        except KeyError:
            homedir = os.path.curdir
        music_dir = os.path.join(homedir, 'Music')
        if not os.path.isdir(music_dir):
            music_dir = os.path.curdir

        path = QFileDialog.getExistingDirectory(self, 'Select directory', music_dir, QFileDialog.ShowDirsOnly)
        debug('path == [{}]'.format(path))
        if not path:
            # cancel
            return

        self.stop()
        self.load_playlist(path)
        self.play()

    def onmedia_status_changed(self):
        '''media changed; player switched to next song'''

        debug('onmedia_status_changed')
        debug('player.state == {}'.format(self.player.state()))
        debug('player.mediastate == {}'.format(self.player.mediaStatus()))

        # we want to load albumart if the song is in another directory
        # and display filename on stdout or console log
        # this is only relevant if the QMediaPlayer is now loading new media

        if self.player.mediaStatus() == QMediaPlayer.LoadingMedia:
            media = self.player.currentMedia()
            if media.isNull():
                debug('media isNull')
                return

            filename = media.canonicalUrl().path()
            debug('current media == [{}]'.format(filename))

            # make a short path for informational message
            short_path = filename
            try:
                homedir = os.environ['HOME'] + os.path.sep
                if short_path.startswith(homedir):
                    short_path = filename[len(homedir):]
            except KeyError:
                pass
            if short_path.startswith('Music/'):
                short_path = short_path[len('Music/'):]
            print('now playing: {}'.format(short_path))

            folder = os.path.dirname(filename)
            self.load_albumart(folder)

        elif self.player.mediaStatus() == QMediaPlayer.NoMedia:
            debug('no media present, change albumart to default image')
            # change to default image
            pixmap = QPixmap(PMusic.DEFAULT_IMG)
            self.img_label.setPixmap(pixmap)
            self.current_albumart = ''

    def load_playlist(self, path):
        '''load new playlist'''

        debug('load playlist')
        self.playlist.clear()

        # Note: not actually sure these formats are all supported ...
        files = QDirIterator(path, ['*.mp3', '*.ogg', '*.wav', '*.flac'], flags=QDirIterator.Subdirectories)
        while files.hasNext():
            filename = files.next()
            debug('+ {}'.format(filename))

            url = QUrl.fromLocalFile(filename)
            if not self.playlist.addMedia(QMediaContent(url)):
                debug('addMedia() => False')

        self.player.setPlaylist(self.playlist)

    def load_albumart(self, path):
        '''load album art'''

        debug('load albumart, path == {}'.format(path))

        # load album art
        found = False
        for name in ('cover.jpg', 'Folder.jpg', 'folder.jpg', 'cover.png', 'AlbumArt.jpg', 'AlbumArtSmall.jpg'):
            filename = os.path.join(path, name)
            if os.path.isfile(filename):
                found = True
                if filename == self.current_albumart:
                    debug('same albumart, already loaded')
                    break

                debug('loading albumart {}'.format(filename))
                pixmap = QPixmap(filename)
                self.img_label.setPixmap(pixmap)
                self.current_albumart = filename
                break

        if not found:
            if not self.current_albumart:
                debug('no albumart found, keeping default image')
            else:
                # put default image
                debug('no albumart found, putting default image')
                pixmap = QPixmap(PMusic.DEFAULT_IMG)
                self.img_label.setPixmap(pixmap)
                self.current_albumart = ''


    def stop(self):
        '''stop playing'''

        debug('stop')
        self.player.stop()

    def play(self):
        '''start playing'''

        debug('play()')
        self.player.play()

    def pause(self):
        '''pause playing'''

        if self.player.state() == QMediaPlayer.PlayingState:
            debug('pause')
            self.player.pause()

        elif self.player.state() in (QMediaPlayer.StoppedState, QMediaPlayer.PausedState):
            self.player.play()
Exemplo n.º 3
0
class MainWindow(QMainWindow, Ui_MainWindow):
    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
        self.setupUi(self)

        self.player = QMediaPlayer()

        self.player.error.connect(self.erroralert)
        self.player.play()
        self.player.stateChanged.connect(self.Set_Icons)
        # self.player.volumeChanged.connect(self.prev_volume)
        # Connect control buttons/slides for media player.
        self.playButton.pressed.connect(self.play_pause)
        self.stopButton.pressed.connect(self.player.stop)
        self.showButton.pressed.connect(self.playlist_toggle)
        self.equalizerButton.pressed.connect(self.ShowEqualizer)
        self.volumeButton.pressed.connect(self.mute)

        self.volumeSlider.valueChanged.connect(self.volumeChanged)
        self.currentVolume = self.volumeSlider.value()
        self.timeSlider.valueChanged.connect(self.player.setPosition)

        # Setup the playlist.
        self.playlist = QMediaPlaylist()
        self.player.setPlaylist(self.playlist)

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

        self.player.durationChanged.connect(self.update_duration)
        self.player.positionChanged.connect(self.update_position)

        self.actionOpen_File.triggered.connect(self.open_file)
        self.files = []
        self.setAcceptDrops(True)

        self.graphWidget.setBackground((60, 60, 60))
        self.graphWidget.GetViewBox().setMenuEnabled(False)
        self.graphWidget.GetViewBox().setMouseEnabled(x=False, y=False)
        self.graphWidget.setPlotEQ((42, 130, 218))

        self.Visualizer = FFTAnalyser(self.player)
        self.Visualizer.calculated_visual.connect(self.draw)
        self.Visualizer.start()
        self.Equalizer = QWidget()
        self.visdata = np.array([])
        self.show()

    def draw(self, visdata):
        self.visdata = np.array(visdata)
        self.graphWidget.UpdatePlotEQ(self.visdata)

    def dragEnterEvent(self, e):
        if e.mimeData().hasUrls():
            e.acceptProposedAction()

    def play_pause(self):
        if self.player.state() == QMediaPlayer.PlayingState:
            self.player.pause()
        else:
            self.player.play()

    def Set_Icons(self, state):
        if state == QMediaPlayer.StoppedState:
            self.stopButton.setIcon(
                qta.icon('fa5s.stop-circle',
                         active='fa5s.stop-circle',
                         color='white',
                         color_active='grey'))
            self.playButton.setIcon(qta.icon('fa5s.play-circle',
                                             color='white'))
            self.playButton.setToolTip('Play')
        elif state == QMediaPlayer.PlayingState:
            self.playButton.setIcon(
                qta.icon('fa5s.pause-circle', color='white'))
            self.playButton.setToolTip('Pause')
        elif state == QMediaPlayer.PausedState:
            self.playButton.setIcon(qta.icon('fa5s.play-circle',
                                             color='white'))
            self.playButton.setToolTip('Play')

    def playlist_toggle(self):
        if self.listWidget.isHidden():
            self.listWidget.setHidden(False)
            self.listWidget.setProperty("showDropIndicator", True)
            self.showButton.setIcon(qta.icon('fa5s.eye-slash', color='white'))
            self.showButton.setToolTip('Hide Playlist')
        else:
            self.listWidget.setHidden(True)
            self.listWidget.setProperty("showDropIndicator", False)
            self.showButton.setIcon(qta.icon('fa5s.list-ul', color='white'))
            self.showButton.setToolTip('Show Playlist')

    def dropEvent(self, e):
        for url in e.mimeData().urls():
            self.playlist.addMedia(QMediaContent(url))

        self.model.layoutChanged.emit()

        # If not playing, seeking to first of newly added + play.
        if self.player.state() != QMediaPlayer.PlayingState:
            i = self.playlist.mediaCount() - len(e.mimeData().urls())
            self.playlist.setCurrentIndex(i)
            self.player.play()

    def open_file(self):
        options = QFileDialog.Options()
        options |= QFileDialog.DontUseNativeDialog
        path, _ = QFileDialog.getOpenFileName(self,
                                              "Open file",
                                              "",
                                              "Music Files (*.mp3 *.wav)",
                                              options=options)

        if path:
            self.playlist.addMedia(QMediaContent(QUrl.fromLocalFile(path)))
            self.files.append(QMediaContent(QUrl.fromLocalFile(path)))

        self.model.layoutChanged.emit()

    # def contextMenuEvent(self, event):
    # contextMenu = QMenu(self)
    # graph = contextMenu.addAction("Show Graph")

    def update_duration(self, duration):
        print("!", duration)
        print("?", self.player.duration())

        self.timeSlider.setMaximum(duration)

        if duration >= 0:
            self.totalTimeLabel.setText(hhmmss(duration))

    def update_position(self, position):
        if position >= 0:
            self.currentTimeLabel.setText(hhmmss(position))

        self.timeSlider.blockSignals(True)
        self.timeSlider.setValue(position)
        self.timeSlider.blockSignals(False)

    def playlist_selection_changed(self, ix):
        i = ix.indexes()[0].row()
        self.playlist.setCurrentIndex(i)

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

    def erroralert(self, *args):
        print(args)

    def ShowEqualizer(self):
        if self.player.currentMedia().canonicalUrl().path() != "":
            self.Equalizer = WindowingWidget(
                self.player.currentMedia().canonicalUrl().path())
            self.Equalizer.SendPath.connect(self.AddPathToPlaylist)
            self.Equalizer.show()

    def AddPathToPlaylist(self, path):
        if path:
            self.playlist.addMedia(QMediaContent(QUrl.fromLocalFile(path)))
            self.files.append(path)

        self.model.layoutChanged.emit()

    def volumeChanged(self):
        self.currentVolume = self.volumeSlider.value()
        self.player.setVolume(self.volumeSlider.value())

    def mute(self):
        if self.player.isMuted():
            self.player.setMuted(False)
            self.volumeButton.setIcon(qta.icon('fa5s.volume-up',
                                               color='white'))
            self.volumeButton.setToolTip('Mute')
            self.volumeSlider.setEnabled(True)
        else:
            self.player.setMuted(True)
            self.volumeButton.setIcon(
                qta.icon('fa5s.volume-mute', color='white'))
            self.volumeButton.setToolTip('Unmute')
            self.volumeSlider.setEnabled(False)

    def closeEvent(self, event):
        self.Visualizer.terminate()
        if self.Equalizer.isVisible():
            self.Equalizer.close()
Exemplo n.º 4
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
Exemplo n.º 5
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()
Exemplo n.º 6
0
class MainWindow(QWidget):

    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)

        self.playlistView = QListView()
        self.switch_status = 2
        self.video_widget = QVideoWidget()
        self.playlist = QMediaPlaylist()
        self.model = PlaylistModel(self.playlist)
        self.titleBar = TitleBar(self)
        self.currentTimeLabel = QLabel()
        self.timeSlider = QSlider()
        self.totalTimeLabel = QLabel()
        self.mediaPlayer = QMediaPlayer()
        self.open_btn = QPushButton('Open File')
        self.play_btn = QPushButton()
        self.prev_btn = QPushButton()
        self.stop_btn = QPushButton()
        self.next_btn = QPushButton()
        self.switch_media_widgets_btn = QPushButton()
        self.pseudo_label = QLabel()

        self.vol_label = QLabel()
        self.volume_slider = Slider(Qt.Horizontal)
        self.gui()
        self.set_children_focus_policy(Qt.NoFocus)

    def gui(self):
        self.currentTimeLabel.setMinimumSize(QSize(80, 0))
        self.currentTimeLabel.setAlignment(Qt.AlignRight | Qt.AlignTrailing | Qt.AlignVCenter)

        self.timeSlider.setOrientation(Qt.Horizontal)
        self.totalTimeLabel.setMinimumSize(QSize(80, 0))
        self.totalTimeLabel.setAlignment(Qt.AlignLeading | Qt.AlignLeft | Qt.AlignVCenter)

        self.playlistView.setAcceptDrops(True)
        self.playlistView.setProperty("showDropIndicator", True)
        self.playlistView.setDragDropMode(QAbstractItemView.DropOnly)
        self.playlistView.setAlternatingRowColors(True)
        self.playlistView.setUniformItemSizes(True)

        self.setWindowFlags(Qt.FramelessWindowHint)
        self.setWindowTitle('Media Player')
        self.titleBar.label.setText('Media Player')
        self.setWindowIcon(QIcon('icon_png/media_player.png'))

        self.setGeometry(600, 200, 850, 600)
        self.timeSlider.setRange(0, 0)
        self.play_btn.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay))
        self.prev_btn.setIcon(self.style().standardIcon(QStyle.SP_MediaSkipBackward))
        self.next_btn.setIcon(self.style().standardIcon(QStyle.SP_MediaSkipForward))
        self.stop_btn.setIcon(self.style().standardIcon(QStyle.SP_MediaStop))
        self.switch_media_widgets_btn.setIcon(self.style().standardIcon(QStyle.SP_FileDialogDetailedView))
        self.vol_label.setText("")
        self.vol_label.setPixmap(QPixmap("icon_png/speaker-volume.png"))
        self.currentTimeLabel.setText("00:00")
        self.totalTimeLabel.setText("00:00")
        self.volume_slider.setValue(self.mediaPlayer.volume())
        self.mediaPlayer.setVideoOutput(self.video_widget)
        self.mediaPlayer.setPlaylist(self.playlist)
        self.playlistView.setModel(self.model)
        self.video_widget.hide()

        sizegrip = QSizeGrip(self)

        self.setAcceptDrops(True)

        inner_h_box = QHBoxLayout()
        inner_h_box.addWidget(self.prev_btn)
        inner_h_box.addWidget(self.stop_btn)
        inner_h_box.addWidget(self.next_btn)

        vol_h_box = QHBoxLayout()
        vol_h_box.addWidget(self.vol_label, 0)
        vol_h_box.addWidget(self.volume_slider, 1)

        h_box = QHBoxLayout()
        h_box.addWidget(self.open_btn)
        h_box.addWidget(self.play_btn, 0)
        h_box.addLayout(inner_h_box, 0)
        h_box.addWidget(self.switch_media_widgets_btn, 0)
        h_box.addWidget(self.pseudo_label, 1)
        h_box.addLayout(vol_h_box, 0)
        h_box.addWidget(sizegrip, 0, Qt.AlignBottom | Qt.AlignRight)

        video_slider_h_box = QHBoxLayout()
        video_slider_h_box.addWidget(self.currentTimeLabel)
        video_slider_h_box.addWidget(self.timeSlider)
        video_slider_h_box.addWidget(self.totalTimeLabel)

        v_box = QVBoxLayout()
        v_box.addWidget(self.titleBar, 0)
        v_box.addWidget(self.video_widget, 1)
        v_box.addWidget(self.playlistView, 1)
        v_box.addLayout(video_slider_h_box, 0)
        v_box.addLayout(h_box, 0)

        inner_h_box.setContentsMargins(20, 0, 10, 0)
        vol_h_box.setContentsMargins(0, 0, 20, 0)
        h_box.setContentsMargins(20, 0, 0, 0)
        v_box.setContentsMargins(0, 0, 0, 0)
        video_slider_h_box.setSpacing(10)
        h_box.setSpacing(0)
        v_box.setSpacing(0)

        self.setLayout(v_box)
        self.enabler()

        # connections
        self.open_btn.clicked.connect(self.open_file)
        self.play_btn.clicked.connect(self.play_media)
        self.stop_btn.clicked.connect(self.stop_media)

        self.prev_btn.pressed.connect(self.play_prev)
        self.next_btn.pressed.connect(self.play_next)
        self.switch_media_widgets_btn.pressed.connect(self.switch_media)

        self.playlist.currentIndexChanged.connect(self.playlist_position_changed)
        selection_model = self.playlistView.selectionModel()
        selection_model.selectionChanged.connect(self.playlist_selection_changed)

        self.mediaPlayer.durationChanged.connect(self.update_duration)
        self.mediaPlayer.positionChanged.connect(self.update_position)
        self.timeSlider.valueChanged.connect(self.mediaPlayer.setPosition)

        self.mediaPlayer.stateChanged.connect(self.media_state)

        self.mediaPlayer.volumeChanged.connect(self.volume_changed)
        self.volume_slider.valueChanged.connect(self.set_volume)

    def set_children_focus_policy(self, policy):
        def recursive_set_child_focus_policy(parent_q_widget):
            for childQWidget in parent_q_widget.findChildren(QWidget):
                childQWidget.setFocusPolicy(policy)
                recursive_set_child_focus_policy(childQWidget)

        recursive_set_child_focus_policy(self)

    def enabler(self, state=False):
        if state is False:
            self.play_btn.setEnabled(False)
            self.prev_btn.setEnabled(False)
            self.stop_btn.setEnabled(False)
            self.next_btn.setEnabled(False)
        else:
            self.play_btn.setEnabled(True)
            self.stop_btn.setEnabled(True)
            self.prev_btn.setEnabled(True)
            self.next_btn.setEnabled(True)

    def switch_media(self):
        if self.switch_status == 0:
            self.video_widget.hide()
            self.playlistView.show()
            self.switch_status = 1
            self.switch_media_widgets_btn.setEnabled(True)
        elif self.switch_status == 1:
            self.video_widget.show()
            self.playlistView.hide()
            self.switch_status = 0
            self.switch_media_widgets_btn.setEnabled(True)
        else:
            self.video_widget.hide()
            self.playlistView.show()
            self.switch_media_widgets_btn.setEnabled(False)

    def play_media(self):
        if self.mediaPlayer.state() == QMediaPlayer.PlayingState:
            self.mediaPlayer.pause()
        else:
            self.mediaPlayer.play()
        self.ui_handler()

    def ui_handler(self):
        if not self.playlist.isEmpty():
            self.enabler(True)
        file_path = QFileInfo(self.mediaPlayer.currentMedia().canonicalUrl().toString()).fileName()
        ext = os.path.splitext(file_path)[-1].lower()
        audio_ext = ['.flac', '.mp3']
        video_ext = ['.mp4', '.m4a', '.mov', '.flv', 'avi', '3gp', '.mkv', '.wmv']

        if ext in audio_ext:
            self.switch_status = 2
            self.switch_media()
            if self.isFullScreen():
                self.fullscreen()
        elif ext in video_ext:
            self.switch_status = 1
            self.switch_media()
        self.setWindowTitle(file_path + ' - Media Player')
        self.titleBar.label.setText(file_path + ' - Media Player')

    def stop_media(self):
        if self.mediaPlayer.state() != QMediaPlayer.StoppedState:
            self.mediaPlayer.stop()
            self.setWindowTitle('Media Player')
            self.titleBar.label.setText('Media Player')

    def fullscreen(self):
        if self.switch_status == 2 or self.isFullScreen():
            self.titleBar.show()
            self.timeSlider.show()
            self.currentTimeLabel.show()
            self.totalTimeLabel.show()
            self.volume_slider.show()
            self.open_btn.show()
            self.play_btn.show()
            self.prev_btn.show()
            self.stop_btn.show()
            self.next_btn.show()
            self.switch_media_widgets_btn.show()
            self.pseudo_label.show()
            self.vol_label.show()
            self.showNormal()
        else:
            self.titleBar.hide()
            self.timeSlider.hide()
            self.currentTimeLabel.hide()
            self.totalTimeLabel.hide()
            self.volume_slider.hide()
            self.open_btn.hide()
            self.play_btn.hide()
            self.prev_btn.hide()
            self.stop_btn.hide()
            self.next_btn.hide()
            self.switch_media_widgets_btn.hide()
            self.pseudo_label.hide()
            self.vol_label.hide()
            self.showFullScreen()

    def mouseDoubleClickEvent(self, event: QMouseEvent):
        event.accept()
        if event.button() == Qt.LeftButton:
            self.fullscreen()

    def media_state(self):

        os_sleep = WindowsInhibitor()
        if self.mediaPlayer.state() == QMediaPlayer.PlayingState:
            self.play_btn.setIcon(self.style().standardIcon(QStyle.SP_MediaPause))
            if os.name == 'nt':
                os_sleep = WindowsInhibitor()
                os_sleep.inhibit()
        else:
            self.play_btn.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay))
            if os_sleep:
                os_sleep.uninhibit()

    def play_next(self):
        self.playlist.next()

    def media_seek(self, seek):
        if not self.playlist.isEmpty():
            player = self.mediaPlayer
            if (player.duration() - seek) > player.position():
                player.setPosition(player.position() + seek)

    def play_prev(self):
        self.playlist.previous()

    def dragEnterEvent(self, e):
        if e.mimeData().hasUrls():
            e.acceptProposedAction()

    def dropEvent(self, e):
        for url in e.mimeData().urls():
            ext = os.path.splitext(url.fileName())[-1].lower()
            allowed_ext = ['.flac', '.mp3', '.mp4', '.m4a', '.mov', '.flv', 'avi', '3gp', '.mkv', '.wmv']
            if ext in allowed_ext:
                self.playlist.addMedia(
                    QMediaContent(url)
                )

        self.model.layoutChanged.emit()

        if self.mediaPlayer.state() != QMediaPlayer.PlayingState:
            i = self.playlist.mediaCount() - len(e.mimeData().urls())
            self.playlist.setCurrentIndex(i)
            if not self.playlist.isEmpty():
                self.play_media()

    def open_file(self):
        filter_files = "Media (*.mp3 *.mp4 *.mkv);; Videos files (*.mp4 *.mkv);; Music Files(*.mp3)"
        paths, _ = QFileDialog.getOpenFileNames(self, "Open file", "", filter_files)

        if paths:
            self.mediaPlayer.pause()
            for path in paths:
                self.playlist.addMedia(
                    QMediaContent(
                        QUrl.fromLocalFile(path)
                    )
                )
            i = self.playlist.mediaCount() - len(paths)
            self.playlist.setCurrentIndex(i)
            self.play_media()

        self.model.layoutChanged.emit()

    def update_duration(self, duration):
        self.mediaPlayer.duration()

        self.timeSlider.setMaximum(duration)

        if duration >= 0:
            self.totalTimeLabel.setText(hhmmss(duration))

    def update_position(self, position):
        if position >= 0:
            self.currentTimeLabel.setText(hhmmss(position))

        self.timeSlider.blockSignals(True)
        self.timeSlider.setValue(position)
        self.timeSlider.blockSignals(False)

    def playlist_selection_changed(self, ix):
        i = ix.indexes()[0].row()
        self.playlist.setCurrentIndex(i)
        self.ui_handler()

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

    def set_volume(self, value):
        self.mediaPlayer.setVolume(value)

    def volume_changed(self, value):
        self.volume_slider.setValue(value)

    def keyPressEvent(self, event):
        key = event.key()
        modifiers = int(event.modifiers())
        if (modifiers and modifiers & MOD_MASK == modifiers and
                key > 0 and key != Qt.Key_Shift and key != Qt.Key_Alt and
                key != Qt.Key_Control and key != Qt.Key_Meta):
            key_name = QKeySequence(modifiers + key).toString()
            if key_name == 'Ctrl+Right':
                self.media_seek(30000)
            elif key_name == 'Ctrl+Left':
                self.media_seek(-30000)
            elif key_name == 'Ctrl+Up':
                self.mediaPlayer.setVolume(self.mediaPlayer.volume() + 5)
            elif key_name == 'Ctrl+Down':
                self.mediaPlayer.setVolume(self.mediaPlayer.volume() - 5)
            elif key_name == 'Ctrl+O':
                self.open_file()

        else:
            if event.key() == Qt.Key_Space:
                self.play_media()
            elif event.key() == Qt.Key_MediaPlay:
                self.play_media()
            elif event.key() == Qt.Key_MediaNext:
                self.play_next()
            elif event.key() == Qt.Key_MediaPrevious:
                self.play_prev()
            elif event.key() == Qt.Key_Escape:
                self.close()
            elif event.key() == Qt.Key_F:
                self.fullscreen()
            elif event.key() == Qt.Key_Right:
                self.media_seek(5000)
            elif event.key() == Qt.Key_Left:
                self.media_seek(-5000)
Exemplo n.º 7
0
class VidCutter(QWidget):
    def __init__(self, parent):
        super(VidCutter, self).__init__(parent)
        self.parent = parent
        self.mediaPlayer = QMediaPlayer(None, QMediaPlayer.VideoSurface)
        self.videoWidget = VideoWidget()
        self.videoService = VideoService(self)

        QFontDatabase.addApplicationFont(
            os.path.join(self.getAppPath(), 'fonts', 'DroidSansMono.ttf'))
        QFontDatabase.addApplicationFont(
            os.path.join(self.getAppPath(), 'fonts', 'HelveticaNeue.ttf'))
        qApp.setFont(QFont('Helvetica Neue', 10))

        self.clipTimes = []
        self.inCut = False
        self.movieFilename = ''
        self.movieLoaded = False
        self.timeformat = 'hh:mm:ss'
        self.finalFilename = ''
        self.totalRuntime = 0

        self.initIcons()
        self.initActions()

        self.toolbar = QToolBar(
            floatable=False,
            movable=False,
            iconSize=QSize(28, 28),
            toolButtonStyle=Qt.ToolButtonTextUnderIcon,
            styleSheet=
            'QToolBar QToolButton { min-width:82px; margin-left:10px; margin-right:10px; font-size:14px; }'
        )
        self.initToolbar()

        self.aboutMenu, self.cliplistMenu = QMenu(), QMenu()
        self.initMenus()

        self.seekSlider = VideoSlider(parent=self,
                                      sliderMoved=self.setPosition)
        self.seekSlider.installEventFilter(self)

        self.initNoVideo()

        self.cliplist = QListWidget(
            sizePolicy=QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding),
            contextMenuPolicy=Qt.CustomContextMenu,
            uniformItemSizes=True,
            iconSize=QSize(100, 700),
            dragDropMode=QAbstractItemView.InternalMove,
            alternatingRowColors=True,
            customContextMenuRequested=self.itemMenu,
            styleSheet='QListView::item { margin:10px 5px; }')
        self.cliplist.setFixedWidth(185)
        self.cliplist.model().rowsMoved.connect(self.syncClipList)

        listHeader = QLabel(pixmap=QPixmap(
            os.path.join(self.getAppPath(), 'images', 'clipindex.png'), 'PNG'),
                            alignment=Qt.AlignCenter)
        listHeader.setStyleSheet(
            '''padding:5px; padding-top:8px; border:1px solid #b9b9b9; border-bottom:none;
                                    background-color:qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #FFF,
                                    stop: 0.5 #EAEAEA, stop: 0.6 #EAEAEA stop:1 #FFF);'''
        )

        self.runtimeLabel = QLabel('<div align="right">00:00:00</div>',
                                   textFormat=Qt.RichText)
        self.runtimeLabel.setStyleSheet(
            '''font-family:Droid Sans Mono; font-size:10pt; color:#FFF;
                                           background:rgb(106, 69, 114) url(:images/runtime.png)
                                           no-repeat left center; padding:2px; padding-right:8px;
                                           border:1px solid #b9b9b9; border-top:none;'''
        )

        self.clipindexLayout = QVBoxLayout(spacing=0)
        self.clipindexLayout.setContentsMargins(0, 0, 0, 0)
        self.clipindexLayout.addWidget(listHeader)
        self.clipindexLayout.addWidget(self.cliplist)
        self.clipindexLayout.addWidget(self.runtimeLabel)

        self.videoLayout = QHBoxLayout()
        self.videoLayout.setContentsMargins(0, 0, 0, 0)
        self.videoLayout.addWidget(self.novideoWidget)
        self.videoLayout.addLayout(self.clipindexLayout)

        self.timeCounter = QLabel('00:00:00 / 00:00:00',
                                  autoFillBackground=True,
                                  alignment=Qt.AlignCenter,
                                  sizePolicy=QSizePolicy(
                                      QSizePolicy.Expanding,
                                      QSizePolicy.Fixed))
        self.timeCounter.setStyleSheet(
            'color:#FFF; background:#000; font-family:Droid Sans Mono; font-size:10.5pt; padding:4px;'
        )

        videoplayerLayout = QVBoxLayout(spacing=0)
        videoplayerLayout.setContentsMargins(0, 0, 0, 0)
        videoplayerLayout.addWidget(self.videoWidget)
        videoplayerLayout.addWidget(self.timeCounter)

        self.videoplayerWidget = QWidget(self, visible=False)
        self.videoplayerWidget.setLayout(videoplayerLayout)

        self.menuButton = QPushButton(icon=self.aboutIcon,
                                      flat=True,
                                      toolTip='About',
                                      statusTip='About',
                                      iconSize=QSize(24, 24),
                                      cursor=Qt.PointingHandCursor)
        self.menuButton.setMenu(self.aboutMenu)

        self.muteButton = QPushButton(icon=self.unmuteIcon,
                                      flat=True,
                                      toolTip='Mute',
                                      statusTip='Toggle audio mute',
                                      cursor=Qt.PointingHandCursor,
                                      clicked=self.muteAudio)

        self.volumeSlider = QSlider(Qt.Horizontal,
                                    toolTip='Volume',
                                    statusTip='Adjust volume level',
                                    cursor=Qt.PointingHandCursor,
                                    value=50,
                                    sizePolicy=QSizePolicy(
                                        QSizePolicy.Fixed,
                                        QSizePolicy.Minimum),
                                    minimum=0,
                                    maximum=100,
                                    sliderMoved=self.setVolume)
        self.volumeSlider.setStyleSheet(
            '''QSlider::groove:horizontal { height:40px; }
                                           QSlider::sub-page:horizontal { border:1px outset #6A4572; background:#6A4572; margin:2px; }
                                           QSlider::handle:horizontal { image: url(:images/knob.png) no-repeat top left; width:20px; }'''
        )

        self.saveAction = QPushButton(
            self.parent,
            icon=self.saveIcon,
            text='Save Video',
            flat=True,
            toolTip='Save Video',
            clicked=self.cutVideo,
            cursor=Qt.PointingHandCursor,
            iconSize=QSize(30, 30),
            statusTip='Save video clips merged as a new video file',
            enabled=False)
        self.saveAction.setStyleSheet(
            '''QPushButton { color:#FFF; padding:8px; font-size:12pt;
                                            border:1px inset #481953; border-radius:4px;
                                            background-color:rgb(106, 69, 114); }
                                         QPushButton:!enabled { background-color:rgba(0, 0, 0, 0.1);
                                            color:rgba(0, 0, 0, 0.3); border:1px inset #CDCDCD; }
                                         QPushButton:hover { background-color:rgba(255, 255, 255, 0.8); color:#444; }
                                         QPushButton:pressed { background-color:rgba(218, 218, 219, 0.8); color:#444; }'''
        )

        controlsLayout = QHBoxLayout()
        controlsLayout.addStretch(1)
        controlsLayout.addWidget(self.toolbar)
        controlsLayout.addSpacerItem(QSpacerItem(20, 1))
        controlsLayout.addWidget(self.saveAction)
        controlsLayout.addStretch(1)
        controlsLayout.addWidget(self.muteButton)
        controlsLayout.addWidget(self.volumeSlider)
        controlsLayout.addSpacing(1)
        controlsLayout.addWidget(self.menuButton)

        layout = QVBoxLayout()
        layout.setContentsMargins(10, 10, 10, 4)
        layout.addLayout(self.videoLayout)
        layout.addWidget(self.seekSlider)
        layout.addLayout(controlsLayout)

        self.setLayout(layout)

        self.mediaPlayer.setVideoOutput(self.videoWidget)
        self.mediaPlayer.stateChanged.connect(self.mediaStateChanged)
        self.mediaPlayer.positionChanged.connect(self.positionChanged)
        self.mediaPlayer.durationChanged.connect(self.durationChanged)
        self.mediaPlayer.error.connect(self.handleError)

    def initNoVideo(self) -> None:
        novideoImage = QLabel(alignment=Qt.AlignCenter,
                              autoFillBackground=False,
                              pixmap=QPixmap(
                                  os.path.join(self.getAppPath(), 'images',
                                               'novideo.png'), 'PNG'),
                              sizePolicy=QSizePolicy(
                                  QSizePolicy.Expanding,
                                  QSizePolicy.MinimumExpanding))
        novideoImage.setBackgroundRole(QPalette.Dark)
        novideoImage.setContentsMargins(0, 20, 0, 20)
        self.novideoLabel = QLabel(alignment=Qt.AlignCenter,
                                   autoFillBackground=True,
                                   sizePolicy=QSizePolicy(
                                       QSizePolicy.Expanding,
                                       QSizePolicy.Minimum))
        self.novideoLabel.setBackgroundRole(QPalette.Dark)
        self.novideoLabel.setContentsMargins(0, 20, 15, 60)
        novideoLayout = QVBoxLayout(spacing=0)
        novideoLayout.addWidget(novideoImage)
        novideoLayout.addWidget(self.novideoLabel, alignment=Qt.AlignTop)
        self.novideoMovie = QMovie(
            os.path.join(self.getAppPath(), 'images', 'novideotext.gif'))
        self.novideoMovie.frameChanged.connect(self.setNoVideoText)
        self.novideoMovie.start()
        self.novideoWidget = QWidget(self, autoFillBackground=True)
        self.novideoWidget.setBackgroundRole(QPalette.Dark)
        self.novideoWidget.setLayout(novideoLayout)

    def initIcons(self) -> None:
        self.appIcon = QIcon(
            os.path.join(self.getAppPath(), 'images', 'vidcutter.png'))
        self.openIcon = QIcon(
            os.path.join(self.getAppPath(), 'images', 'addmedia.png'))
        self.playIcon = QIcon(
            os.path.join(self.getAppPath(), 'images', 'play.png'))
        self.pauseIcon = QIcon(
            os.path.join(self.getAppPath(), 'images', 'pause.png'))
        self.cutStartIcon = QIcon(
            os.path.join(self.getAppPath(), 'images', 'cut-start.png'))
        self.cutEndIcon = QIcon(
            os.path.join(self.getAppPath(), 'images', 'cut-end.png'))
        self.saveIcon = QIcon(
            os.path.join(self.getAppPath(), 'images', 'save.png'))
        self.muteIcon = QIcon(
            os.path.join(self.getAppPath(), 'images', 'muted.png'))
        self.unmuteIcon = QIcon(
            os.path.join(self.getAppPath(), 'images', 'unmuted.png'))
        self.upIcon = QIcon(os.path.join(self.getAppPath(), 'images',
                                         'up.png'))
        self.downIcon = QIcon(
            os.path.join(self.getAppPath(), 'images', 'down.png'))
        self.removeIcon = QIcon(
            os.path.join(self.getAppPath(), 'images', 'remove.png'))
        self.removeAllIcon = QIcon(
            os.path.join(self.getAppPath(), 'images', 'remove-all.png'))
        self.successIcon = QIcon(
            os.path.join(self.getAppPath(), 'images', 'success.png'))
        self.aboutIcon = QIcon(
            os.path.join(self.getAppPath(), 'images', 'about.png'))
        self.completePlayIcon = QIcon(
            os.path.join(self.getAppPath(), 'images', 'complete-play.png'))
        self.completeOpenIcon = QIcon(
            os.path.join(self.getAppPath(), 'images', 'complete-open.png'))
        self.completeRestartIcon = QIcon(
            os.path.join(self.getAppPath(), 'images', 'complete-restart.png'))
        self.completeExitIcon = QIcon(
            os.path.join(self.getAppPath(), 'images', 'complete-exit.png'))

    def initActions(self) -> None:
        self.openAction = QAction(self.openIcon,
                                  'Add Media',
                                  self,
                                  statusTip='Select media source',
                                  triggered=self.openFile)
        self.playAction = QAction(self.playIcon,
                                  'Play Video',
                                  self,
                                  statusTip='Play selected media',
                                  triggered=self.playVideo,
                                  enabled=False)
        self.cutStartAction = QAction(self.cutStartIcon,
                                      'Set Start',
                                      self,
                                      toolTip='Set Start',
                                      statusTip='Set start marker',
                                      triggered=self.cutStart,
                                      enabled=False)
        self.cutEndAction = QAction(self.cutEndIcon,
                                    'Set End',
                                    self,
                                    statusTip='Set end marker',
                                    triggered=self.cutEnd,
                                    enabled=False)
        self.moveItemUpAction = QAction(
            self.upIcon,
            'Move Up',
            self,
            statusTip='Move clip position up in list',
            triggered=self.moveItemUp,
            enabled=False)
        self.moveItemDownAction = QAction(
            self.downIcon,
            'Move Down',
            self,
            statusTip='Move clip position down in list',
            triggered=self.moveItemDown,
            enabled=False)
        self.removeItemAction = QAction(
            self.removeIcon,
            'Remove clip',
            self,
            statusTip='Remove selected clip from list',
            triggered=self.removeItem,
            enabled=False)
        self.removeAllAction = QAction(self.removeAllIcon,
                                       'Clear list',
                                       self,
                                       statusTip='Clear all clips from list',
                                       triggered=self.clearList,
                                       enabled=False)
        self.aboutAction = QAction('About %s' % qApp.applicationName(),
                                   self,
                                   statusTip='Credits and acknowledgements',
                                   triggered=self.aboutInfo)
        self.aboutQtAction = QAction('About Qt',
                                     self,
                                     statusTip='About Qt',
                                     triggered=qApp.aboutQt)
        self.mediaInfoAction = QAction(
            'Media Information',
            self,
            statusTip='Media information from loaded video file',
            triggered=self.mediaInfo,
            enabled=False)

    def initToolbar(self) -> None:
        self.toolbar.addAction(self.openAction)
        self.toolbar.addAction(self.playAction)
        self.toolbar.addSeparator()
        self.toolbar.addAction(self.cutStartAction)
        self.toolbar.addAction(self.cutEndAction)
        self.toolbar.addSeparator()

    def initMenus(self) -> None:
        self.aboutMenu.addAction(self.mediaInfoAction)
        self.aboutMenu.addSeparator()
        self.aboutMenu.addAction(self.aboutQtAction)
        self.aboutMenu.addAction(self.aboutAction)
        self.cliplistMenu.addAction(self.moveItemUpAction)
        self.cliplistMenu.addAction(self.moveItemDownAction)
        self.cliplistMenu.addSeparator()
        self.cliplistMenu.addAction(self.removeItemAction)
        self.cliplistMenu.addAction(self.removeAllAction)

    def setRunningTime(self, runtime: str) -> None:
        self.runtimeLabel.setText('<div align="right">%s</div>' % runtime)

    @pyqtSlot(int)
    def setNoVideoText(self, frame: int) -> None:
        self.novideoLabel.setPixmap(self.novideoMovie.currentPixmap())

    def itemMenu(self, pos: QPoint) -> None:
        globalPos = self.cliplist.mapToGlobal(pos)
        self.moveItemUpAction.setEnabled(False)
        self.moveItemDownAction.setEnabled(False)
        self.removeItemAction.setEnabled(False)
        self.removeAllAction.setEnabled(False)
        index = self.cliplist.currentRow()
        if index != -1:
            if not self.inCut:
                if index > 0:
                    self.moveItemUpAction.setEnabled(True)
                if index < self.cliplist.count() - 1:
                    self.moveItemDownAction.setEnabled(True)
            if self.cliplist.count() > 0:
                self.removeItemAction.setEnabled(True)
        if self.cliplist.count() > 0:
            self.removeAllAction.setEnabled(True)
        self.cliplistMenu.exec_(globalPos)

    def moveItemUp(self) -> None:
        index = self.cliplist.currentRow()
        tmpItem = self.clipTimes[index]
        del self.clipTimes[index]
        self.clipTimes.insert(index - 1, tmpItem)
        self.renderTimes()

    def moveItemDown(self) -> None:
        index = self.cliplist.currentRow()
        tmpItem = self.clipTimes[index]
        del self.clipTimes[index]
        self.clipTimes.insert(index + 1, tmpItem)
        self.renderTimes()

    def removeItem(self) -> None:
        index = self.cliplist.currentRow()
        del self.clipTimes[index]
        if self.inCut and index == self.cliplist.count() - 1:
            self.inCut = False
            self.initMediaControls()
        self.renderTimes()

    def clearList(self) -> None:
        self.clipTimes.clear()
        self.cliplist.clear()
        self.inCut = False
        self.renderTimes()
        self.initMediaControls()

    def mediaInfo(self) -> None:
        if self.mediaPlayer.isMetaDataAvailable():
            content = '<table cellpadding="4">'
            for key in self.mediaPlayer.availableMetaData():
                val = self.mediaPlayer.metaData(key)
                if type(val) is QSize:
                    val = '%s x %s' % (val.width(), val.height())
                content += '<tr><td align="right"><b>%s:</b></td><td>%s</td></tr>\n' % (
                    key, val)
            content += '</table>'
            mbox = QMessageBox(windowTitle='Media Information',
                               windowIcon=self.parent.windowIcon(),
                               textFormat=Qt.RichText)
            mbox.setText('<b>%s</b>' % os.path.basename(
                self.mediaPlayer.currentMedia().canonicalUrl().toLocalFile()))
            mbox.setInformativeText(content)
            mbox.exec_()
        else:
            QMessageBox.critical(
                self.parent, 'Could not retrieve media information',
                '''There was a problem in tring to retrieve media information.
                                    This DOES NOT mean there is a problem with the file and you should
                                    be able to continue using it.''')

    def aboutInfo(self) -> None:
        about_html = '''<style>
    a { color:#441d4e; text-decoration:none; font-weight:bold; }
    a:hover { text-decoration:underline; }
</style>
<p style="font-size:26pt; font-weight:bold;">%s</p>
<p>
    <span style="font-size:13pt;"><b>Version: %s</b></span>
    <span style="font-size:10pt;position:relative;left:5px;">( %s )</span>
</p>
<p style="font-size:13px;">
    Copyright &copy; 2016 <a href="mailto:[email protected]">Pete Alexandrou</a>
    <br/>
    Website: <a href="%s">%s</a>
</p>
<p style="font-size:13px;">
    Thanks to the folks behind the <b>Qt</b>, <b>PyQt</b> and <b>FFmpeg</b>
    projects for all their hard and much appreciated work.
</p>
<p style="font-size:11px;">
    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    as published by the Free Software Foundation; either version 2
    of the License, or (at your option) any later version.
</p>
<p style="font-size:11px;">
    This software uses libraries from the <a href="https://www.ffmpeg.org">FFmpeg</a> project under the
    <a href="https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html">LGPLv2.1</a>
</p>''' % (qApp.applicationName(), qApp.applicationVersion(),
           platform.architecture()[0], qApp.organizationDomain(),
           qApp.organizationDomain())
        QMessageBox.about(self.parent, 'About %s' % qApp.applicationName(),
                          about_html)

    def openFile(self) -> None:
        filename, _ = QFileDialog.getOpenFileName(self.parent,
                                                  caption='Select video',
                                                  directory=QDir.homePath())
        if filename != '':
            self.loadFile(filename)

    def loadFile(self, filename: str) -> None:
        self.movieFilename = filename
        if not os.path.exists(filename):
            return
        self.mediaPlayer.setMedia(QMediaContent(QUrl.fromLocalFile(filename)))
        self.initMediaControls(True)
        self.cliplist.clear()
        self.clipTimes = []
        self.parent.setWindowTitle(
            '%s - %s' % (qApp.applicationName(), os.path.basename(filename)))
        if not self.movieLoaded:
            self.videoLayout.replaceWidget(self.novideoWidget,
                                           self.videoplayerWidget)
            self.novideoMovie.stop()
            self.novideoMovie.deleteLater()
            self.novideoWidget.deleteLater()
            self.videoplayerWidget.show()
            self.videoWidget.show()
            self.movieLoaded = True
        if self.mediaPlayer.isVideoAvailable():
            self.mediaPlayer.setPosition(1)
        self.mediaPlayer.play()
        self.mediaPlayer.pause()

    def playVideo(self) -> None:
        if self.mediaPlayer.state() == QMediaPlayer.PlayingState:
            self.mediaPlayer.pause()
            self.playAction.setText('Play Video')
        else:
            self.mediaPlayer.play()
            self.playAction.setText('Pause Video')

    def initMediaControls(self, flag: bool = True) -> None:
        self.playAction.setEnabled(flag)
        self.saveAction.setEnabled(False)
        self.cutStartAction.setEnabled(flag)
        self.cutEndAction.setEnabled(False)
        self.mediaInfoAction.setEnabled(flag)
        if flag:
            self.seekSlider.setRestrictValue(0)

    def setPosition(self, position: int) -> None:
        self.mediaPlayer.setPosition(position)

    def positionChanged(self, progress: int) -> None:
        self.seekSlider.setValue(progress)
        currentTime = self.deltaToQTime(progress)
        totalTime = self.deltaToQTime(self.mediaPlayer.duration())
        self.timeCounter.setText('%s / %s' % (currentTime.toString(
            self.timeformat), totalTime.toString(self.timeformat)))

    @pyqtSlot()
    def mediaStateChanged(self) -> None:
        if self.mediaPlayer.state() == QMediaPlayer.PlayingState:
            self.playAction.setIcon(self.pauseIcon)
        else:
            self.playAction.setIcon(self.playIcon)

    def durationChanged(self, duration: int) -> None:
        self.seekSlider.setRange(0, duration)

    def muteAudio(self, muted: bool) -> None:
        if self.mediaPlayer.isMuted():
            self.mediaPlayer.setMuted(not self.mediaPlayer.isMuted())
            self.muteButton.setIcon(self.unmuteIcon)
            self.muteButton.setToolTip('Mute')
        else:
            self.mediaPlayer.setMuted(not self.mediaPlayer.isMuted())
            self.muteButton.setIcon(self.muteIcon)
            self.muteButton.setToolTip('Unmute')

    def setVolume(self, volume: int) -> None:
        self.mediaPlayer.setVolume(volume)

    def toggleFullscreen(self) -> None:
        self.videoWidget.setFullScreen(not self.videoWidget.isFullScreen())

    def cutStart(self) -> None:
        self.clipTimes.append([
            self.deltaToQTime(self.mediaPlayer.position()), '',
            self.captureImage()
        ])
        self.cutStartAction.setDisabled(True)
        self.cutEndAction.setEnabled(True)
        self.seekSlider.setRestrictValue(self.seekSlider.value() + 1000)
        self.mediaPlayer.setPosition(self.seekSlider.restrictValue)
        self.inCut = True
        self.renderTimes()

    def cutEnd(self) -> None:
        item = self.clipTimes[len(self.clipTimes) - 1]
        selected = self.deltaToQTime(self.mediaPlayer.position())
        if selected.__lt__(item[0]):
            QMessageBox.critical(
                self.parent, 'Invalid END Time',
                'The clip end time must come AFTER it\'s start time. Please try again.'
            )
            return
        item[1] = selected
        self.cutStartAction.setEnabled(True)
        self.cutEndAction.setDisabled(True)
        self.seekSlider.setRestrictValue(0)
        self.inCut = False
        self.renderTimes()

    @pyqtSlot(QModelIndex, int, int, QModelIndex, int)
    def syncClipList(self, parent: QModelIndex, start: int, end: int,
                     destination: QModelIndex, row: int) -> None:
        if start < row:
            index = row - 1
        else:
            index = row
        clip = self.clipTimes.pop(start)
        self.clipTimes.insert(index, clip)

    def renderTimes(self) -> None:
        self.cliplist.clear()
        self.seekSlider.setCutMode(self.inCut)
        if len(self.clipTimes) > 4:
            self.cliplist.setFixedWidth(200)
        else:
            self.cliplist.setFixedWidth(185)
        self.totalRuntime = 0
        for item in self.clipTimes:
            endItem = ''
            if type(item[1]) is QTime:
                endItem = item[1].toString(self.timeformat)
                self.totalRuntime += item[0].msecsTo(item[1])
            listitem = QListWidgetItem()
            listitem.setTextAlignment(Qt.AlignVCenter)
            if type(item[2]) is QPixmap:
                listitem.setIcon(QIcon(item[2]))
            self.cliplist.addItem(listitem)
            marker = QLabel(
                '''<style>b { font-size:8pt; } p { margin:5px; }</style>
                            <p><b>START</b><br/>%s</p><p><b>END</b><br/>%s</p>'''
                % (item[0].toString(self.timeformat), endItem))
            self.cliplist.setItemWidget(listitem, marker)
            listitem.setFlags(Qt.ItemIsSelectable | Qt.ItemIsDragEnabled
                              | Qt.ItemIsEnabled)
        if len(self.clipTimes) and not self.inCut:
            self.saveAction.setEnabled(True)
        if self.inCut or len(self.clipTimes) == 0 or not type(
                self.clipTimes[0][1]) is QTime:
            self.saveAction.setEnabled(False)
        self.setRunningTime(
            self.deltaToQTime(self.totalRuntime).toString(self.timeformat))

    @staticmethod
    def deltaToQTime(millisecs: int) -> QTime:
        secs = millisecs / 1000
        return QTime((secs / 3600) % 60, (secs / 60) % 60, secs % 60,
                     (secs * 1000) % 1000)

    def captureImage(self) -> None:
        frametime = self.deltaToQTime(
            self.mediaPlayer.position()).addSecs(1).toString(self.timeformat)
        inputfile = self.mediaPlayer.currentMedia().canonicalUrl().toLocalFile(
        )
        imagecap = self.videoService.capture(inputfile, frametime)
        if type(imagecap) is QPixmap:
            return imagecap

    def cutVideo(self) -> bool:
        self.setCursor(Qt.BusyCursor)
        clips = len(self.clipTimes)
        filename, filelist = '', []
        source = self.mediaPlayer.currentMedia().canonicalUrl().toLocalFile()
        _, sourceext = os.path.splitext(source)
        if clips > 0:
            self.finalFilename, _ = QFileDialog.getSaveFileName(
                self.parent, 'Save video', source,
                'Video files (*%s)' % sourceext)
            if self.finalFilename != '':
                self.saveAction.setDisabled(True)
                self.showProgress(clips)
                file, ext = os.path.splitext(self.finalFilename)
                index = 1
                self.progress.setLabelText('Cutting video clips...')
                qApp.processEvents()
                for clip in self.clipTimes:
                    duration = self.deltaToQTime(clip[0].msecsTo(
                        clip[1])).toString(self.timeformat)
                    filename = '%s_%s%s' % (file, '{0:0>2}'.format(index), ext)
                    filelist.append(filename)
                    self.videoService.cut(source, filename,
                                          clip[0].toString(self.timeformat),
                                          duration)
                    index += 1
                if len(filelist) > 1:
                    self.joinVideos(filelist, self.finalFilename)
                else:
                    QFile.remove(self.finalFilename)
                    QFile.rename(filename, self.finalFilename)
                self.unsetCursor()
                self.progress.setLabelText('Complete...')
                qApp.processEvents()
                self.saveAction.setEnabled(True)
                self.progress.close()
                self.progress.deleteLater()
                self.complete()
                self.saveAction.setEnabled(True)
            self.unsetCursor()
            self.saveAction.setDisabled(True)
            return True
        self.unsetCursor()
        self.saveAction.setDisabled(True)
        return False

    def joinVideos(self, joinlist: list, filename: str) -> None:
        listfile = os.path.normpath(
            os.path.join(os.path.dirname(joinlist[0]), '.vidcutter.list'))
        fobj = open(listfile, 'w')
        for file in joinlist:
            fobj.write('file \'%s\'\n' % file.replace("'", "\\'"))
        fobj.close()
        self.videoService.join(listfile, filename)
        try:
            QFile.remove(listfile)
            for file in joinlist:
                if os.path.isfile(file):
                    QFile.remove(file)
        except:
            pass

    def showProgress(self,
                     steps: int,
                     label: str = 'Processing video...') -> None:
        self.progress = QProgressDialog(label,
                                        None,
                                        0,
                                        steps,
                                        self.parent,
                                        windowModality=Qt.ApplicationModal,
                                        windowIcon=self.parent.windowIcon(),
                                        minimumDuration=0,
                                        minimumWidth=500)
        self.progress.show()
        for i in range(steps):
            self.progress.setValue(i)
            qApp.processEvents()
            time.sleep(1)

    def complete(self) -> None:
        info = QFileInfo(self.finalFilename)
        mbox = QMessageBox(windowTitle='Success',
                           windowIcon=self.parent.windowIcon(),
                           minimumWidth=500,
                           iconPixmap=self.successIcon.pixmap(48, 49),
                           textFormat=Qt.RichText)
        mbox.setText(
            '''
<style>
    table.info { margin:8px; padding:4px 15px; }
    td.label { font-weight:bold; font-size:9pt; text-align:right; background-color:#444; color:#FFF; }
    td.value { background-color:#FFF !important; font-size:10pt; }
</style>
<p>Your video was successfully created.</p>
<p align="center">
    <table class="info" cellpadding="6" cellspacing="0">
        <tr>
            <td class="label"><b>Filename</b></td>
            <td class="value" nowrap>%s</td>
        </tr>
        <tr>
            <td class="label"><b>Size</b></td>
            <td class="value">%s</td>
        </tr>
        <tr>
            <td class="label"><b>Runtime</b></td>
            <td class="value">%s</td>
        </tr>
    </table>
</p>
<p>How would you like to proceed?</p>''' %
            (QDir.toNativeSeparators(
                self.finalFilename), self.sizeof_fmt(int(info.size())),
             self.deltaToQTime(self.totalRuntime).toString(self.timeformat)))
        play = mbox.addButton('Play', QMessageBox.AcceptRole)
        play.setIcon(self.completePlayIcon)
        play.clicked.connect(self.openResult)
        fileman = mbox.addButton('Open', QMessageBox.AcceptRole)
        fileman.setIcon(self.completeOpenIcon)
        fileman.clicked.connect(self.openFolder)
        end = mbox.addButton('Exit', QMessageBox.AcceptRole)
        end.setIcon(self.completeExitIcon)
        end.clicked.connect(self.close)
        new = mbox.addButton('Restart', QMessageBox.AcceptRole)
        new.setIcon(self.completeRestartIcon)
        new.clicked.connect(self.startNew)
        mbox.setDefaultButton(new)
        mbox.setEscapeButton(new)
        mbox.exec_()

    def sizeof_fmt(self, num: float, suffix: chr = 'B') -> str:
        for unit in ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']:
            if abs(num) < 1024.0:
                return "%3.1f%s%s" % (num, unit, suffix)
            num /= 1024.0
        return "%.1f%s%s" % (num, 'Y', suffix)

    @pyqtSlot()
    def openFolder(self) -> None:
        self.openResult(pathonly=True)

    @pyqtSlot(bool)
    def openResult(self, pathonly: bool = False) -> None:
        self.startNew()
        if len(self.finalFilename) and os.path.exists(self.finalFilename):
            target = self.finalFilename if not pathonly else os.path.dirname(
                self.finalFilename)
            QDesktopServices.openUrl(QUrl.fromLocalFile(target))

    @pyqtSlot()
    def startNew(self) -> None:
        self.unsetCursor()
        self.clearList()
        self.seekSlider.setValue(0)
        self.seekSlider.setRange(0, 0)
        self.mediaPlayer.setMedia(QMediaContent())
        self.initNoVideo()
        self.videoLayout.replaceWidget(self.videoplayerWidget,
                                       self.novideoWidget)
        self.initMediaControls(False)
        self.parent.setWindowTitle('%s' % qApp.applicationName())

    def wheelEvent(self, event: QWheelEvent) -> None:
        if self.mediaPlayer.isVideoAvailable(
        ) or self.mediaPlayer.isAudioAvailable():
            if event.angleDelta().y() > 0:
                newval = self.seekSlider.value() - 1000
            else:
                newval = self.seekSlider.value() + 1000
            self.seekSlider.setValue(newval)
            self.seekSlider.setSliderPosition(newval)
            self.mediaPlayer.setPosition(newval)
        event.accept()

    def keyPressEvent(self, event: QKeyEvent) -> None:
        if self.mediaPlayer.isVideoAvailable(
        ) or self.mediaPlayer.isAudioAvailable():
            addtime = 0
            if event.key() == Qt.Key_Left:
                addtime = -1000
            elif event.key() == Qt.Key_PageUp or event.key() == Qt.Key_Up:
                addtime = -10000
            elif event.key() == Qt.Key_Right:
                addtime = 1000
            elif event.key() == Qt.Key_PageDown or event.key() == Qt.Key_Down:
                addtime = 10000
            elif event.key() == Qt.Key_Enter:
                self.toggleFullscreen()
            elif event.key(
            ) == Qt.Key_Escape and self.videoWidget.isFullScreen():
                self.videoWidget.setFullScreen(False)
            if addtime != 0:
                newval = self.seekSlider.value() + addtime
                self.seekSlider.setValue(newval)
                self.seekSlider.setSliderPosition(newval)
                self.mediaPlayer.setPosition(newval)
        event.accept()

    def mousePressEvent(self, event: QMouseEvent) -> None:
        if event.button() == Qt.BackButton and self.cutStartAction.isEnabled():
            self.cutStart()
            event.accept()
        elif event.button(
        ) == Qt.ForwardButton and self.cutEndAction.isEnabled():
            self.cutEnd()
            event.accept()
        else:
            super(VidCutter, self).mousePressEvent(event)

    def eventFilter(self, obj: QObject, event: QEvent) -> bool:
        if event.type() == QEvent.MouseButtonRelease and isinstance(
                obj, VideoSlider):
            if obj.objectName() == 'VideoSlider' and (
                    self.mediaPlayer.isVideoAvailable()
                    or self.mediaPlayer.isAudioAvailable()):
                obj.setValue(
                    QStyle.sliderValueFromPosition(obj.minimum(),
                                                   obj.maximum(), event.x(),
                                                   obj.width()))
                self.mediaPlayer.setPosition(obj.sliderPosition())
        return QWidget.eventFilter(self, obj, event)

    @pyqtSlot(QMediaPlayer.Error)
    def handleError(self, error: QMediaPlayer.Error) -> None:
        self.unsetCursor()
        self.startNew()
        if error == QMediaPlayer.ResourceError:
            QMessageBox.critical(
                self.parent, 'Error',
                'Invalid media file detected at:<br/><br/><b>%s</b><br/><br/>%s'
                % (self.movieFilename, self.mediaPlayer.errorString()))
        else:
            QMessageBox.critical(self.parent, 'Error',
                                 self.mediaPlayer.errorString())

    def getAppPath(self) -> str:
        return ':'

    def closeEvent(self, event: QCloseEvent) -> None:
        self.parent.closeEvent(event)
Exemplo n.º 8
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()
Exemplo n.º 9
0
class VidCutter(QWidget):
    def __init__(self, parent):
        super(VidCutter, self).__init__(parent)
        self.novideoWidget = QWidget(self, autoFillBackground=True)
        self.parent = parent
        self.mediaPlayer = QMediaPlayer(None, QMediaPlayer.VideoSurface)
        self.videoWidget = VideoWidget(self)
        self.videoService = VideoService(self)

        QFontDatabase.addApplicationFont(
            MainWindow.get_path('fonts/DroidSansMono.ttf'))
        QFontDatabase.addApplicationFont(
            MainWindow.get_path('fonts/OpenSans.ttf'))

        fontSize = 12 if sys.platform == 'darwin' else 10
        appFont = QFont('Open Sans', fontSize, 300)
        qApp.setFont(appFont)

        self.clipTimes = []
        self.inCut = False
        self.movieFilename = ''
        self.movieLoaded = False
        self.timeformat = 'hh:mm:ss'
        self.finalFilename = ''
        self.totalRuntime = 0

        self.initIcons()
        self.initActions()

        self.toolbar = QToolBar(floatable=False,
                                movable=False,
                                iconSize=QSize(40, 36))
        self.toolbar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        self.toolbar.setStyleSheet('''QToolBar { spacing:10px; }
            QToolBar QToolButton { border:1px solid transparent; min-width:95px; font-size:11pt; font-weight:400;
                border-radius:5px; padding:1px 2px; color:#444; }
            QToolBar QToolButton:hover { border:1px inset #6A4572; color:#6A4572; background-color:rgba(255, 255, 255, 0.85); }
            QToolBar QToolButton:pressed { border:1px inset #6A4572; color:#6A4572; background-color:rgba(255, 255, 255, 0.25); }
            QToolBar QToolButton:disabled { color:#999; }''')
        self.initToolbar()

        self.appMenu, self.cliplistMenu = QMenu(), QMenu()
        self.initMenus()

        self.seekSlider = VideoSlider(parent=self,
                                      sliderMoved=self.setPosition)

        self.initNoVideo()

        self.cliplist = QListWidget(
            sizePolicy=QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding),
            contextMenuPolicy=Qt.CustomContextMenu,
            uniformItemSizes=True,
            iconSize=QSize(100, 700),
            dragDropMode=QAbstractItemView.InternalMove,
            alternatingRowColors=True,
            customContextMenuRequested=self.itemMenu,
            dragEnabled=True)
        self.cliplist.setStyleSheet(
            'QListView { border-radius:0; border:none; border-left:1px solid #B9B9B9; '
            +
            'border-right:1px solid #B9B9B9; } QListView::item { padding:10px 0; }'
        )
        self.cliplist.setFixedWidth(185)
        self.cliplist.model().rowsMoved.connect(self.syncClipList)

        listHeader = QLabel(pixmap=QPixmap(
            MainWindow.get_path('images/clipindex.png'), 'PNG'),
                            alignment=Qt.AlignCenter)
        listHeader.setStyleSheet(
            '''padding:5px; padding-top:8px; border:1px solid #b9b9b9;
                                    background-color:qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #FFF,
                                    stop: 0.5 #EAEAEA, stop: 0.6 #EAEAEA stop:1 #FFF);'''
        )

        self.runtimeLabel = QLabel('<div align="right">00:00:00</div>',
                                   textFormat=Qt.RichText)
        self.runtimeLabel.setStyleSheet(
            '''font-family:Droid Sans Mono; font-size:10pt; color:#FFF;
                                           background:rgb(106, 69, 114) url(:images/runtime.png)
                                           no-repeat left center; padding:2px; padding-right:8px;
                                           border:1px solid #B9B9B9;''')

        self.clipindexLayout = QVBoxLayout(spacing=0)
        self.clipindexLayout.setContentsMargins(0, 0, 0, 0)
        self.clipindexLayout.addWidget(listHeader)
        self.clipindexLayout.addWidget(self.cliplist)
        self.clipindexLayout.addWidget(self.runtimeLabel)

        self.videoLayout = QHBoxLayout()
        self.videoLayout.setContentsMargins(0, 0, 0, 0)
        self.videoLayout.addWidget(self.novideoWidget)
        self.videoLayout.addLayout(self.clipindexLayout)

        self.timeCounter = QLabel('00:00:00 / 00:00:00',
                                  autoFillBackground=True,
                                  alignment=Qt.AlignCenter,
                                  sizePolicy=QSizePolicy(
                                      QSizePolicy.Expanding,
                                      QSizePolicy.Fixed))
        self.timeCounter.setStyleSheet(
            'color:#FFF; background:#000; font-family:Droid Sans Mono; font-size:10.5pt; padding:4px;'
        )

        videoplayerLayout = QVBoxLayout(spacing=0)
        videoplayerLayout.setContentsMargins(0, 0, 0, 0)
        videoplayerLayout.addWidget(self.videoWidget)
        videoplayerLayout.addWidget(self.timeCounter)

        self.videoplayerWidget = QWidget(self, visible=False)
        self.videoplayerWidget.setLayout(videoplayerLayout)

        self.muteButton = QPushButton(icon=self.unmuteIcon,
                                      flat=True,
                                      toolTip='Mute',
                                      statusTip='Toggle audio mute',
                                      iconSize=QSize(16, 16),
                                      cursor=Qt.PointingHandCursor,
                                      clicked=self.muteAudio)

        self.volumeSlider = QSlider(Qt.Horizontal,
                                    toolTip='Volume',
                                    statusTip='Adjust volume level',
                                    cursor=Qt.PointingHandCursor,
                                    value=50,
                                    minimum=0,
                                    maximum=100,
                                    sliderMoved=self.setVolume)

        self.menuButton = QPushButton(
            icon=self.menuIcon,
            flat=True,
            toolTip='Menu',
            statusTip='Media + application information',
            iconSize=QSize(24, 24),
            cursor=Qt.PointingHandCursor)
        self.menuButton.setMenu(self.appMenu)

        toolbarLayout = QHBoxLayout()
        toolbarLayout.addWidget(self.toolbar)
        toolbarLayout.setContentsMargins(2, 2, 2, 2)

        toolbarGroup = QGroupBox()
        toolbarGroup.setFlat(False)
        toolbarGroup.setCursor(Qt.PointingHandCursor)
        toolbarGroup.setLayout(toolbarLayout)

        toolbarGroup.setStyleSheet(
            '''QGroupBox { background-color:rgba(0, 0, 0, 0.1);
            border:1px inset #888; border-radius:5px; }''')

        controlsLayout = QHBoxLayout(spacing=0)
        controlsLayout.addStretch(1)
        controlsLayout.addWidget(toolbarGroup)
        controlsLayout.addStretch(1)
        controlsLayout.addWidget(self.muteButton)
        controlsLayout.addWidget(self.volumeSlider)
        controlsLayout.addSpacing(1)
        controlsLayout.addWidget(self.menuButton)

        layout = QVBoxLayout()
        layout.setContentsMargins(10, 10, 10, 4)
        layout.addLayout(self.videoLayout)
        layout.addWidget(self.seekSlider)
        layout.addSpacing(5)
        layout.addLayout(controlsLayout)
        layout.addSpacing(2)

        self.setLayout(layout)

        self.mediaPlayer.setVideoOutput(self.videoWidget)
        self.mediaPlayer.stateChanged.connect(self.mediaStateChanged)
        self.mediaPlayer.positionChanged.connect(self.positionChanged)
        self.mediaPlayer.durationChanged.connect(self.durationChanged)
        self.mediaPlayer.error.connect(self.handleError)

    def initNoVideo(self) -> None:
        novideoImage = QLabel(
            alignment=Qt.AlignCenter,
            autoFillBackground=False,
            pixmap=QPixmap(MainWindow.get_path('images/novideo.png'), 'PNG'),
            sizePolicy=QSizePolicy(QSizePolicy.Expanding,
                                   QSizePolicy.MinimumExpanding))
        novideoImage.setBackgroundRole(QPalette.Dark)
        novideoImage.setContentsMargins(0, 20, 0, 20)
        self.novideoLabel = QLabel(alignment=Qt.AlignCenter,
                                   autoFillBackground=True,
                                   sizePolicy=QSizePolicy(
                                       QSizePolicy.Expanding,
                                       QSizePolicy.Minimum))
        self.novideoLabel.setBackgroundRole(QPalette.Dark)
        self.novideoLabel.setContentsMargins(0, 20, 15, 60)
        novideoLayout = QVBoxLayout(spacing=0)
        novideoLayout.addWidget(novideoImage)
        novideoLayout.addWidget(self.novideoLabel, alignment=Qt.AlignTop)
        self.novideoMovie = QMovie(
            MainWindow.get_path('images/novideotext.gif'))
        self.novideoMovie.frameChanged.connect(self.setNoVideoText)
        self.novideoMovie.start()
        self.novideoWidget.setBackgroundRole(QPalette.Dark)
        self.novideoWidget.setLayout(novideoLayout)

    def initIcons(self) -> None:
        self.appIcon = QIcon(MainWindow.get_path('images/vidcutter.png'))
        self.openIcon = icon('fa.film',
                             color='#444',
                             color_active='#6A4572',
                             scale_factor=0.9)
        self.playIcon = icon('fa.play-circle-o',
                             color='#444',
                             color_active='#6A4572',
                             scale_factor=1.1)
        self.pauseIcon = icon('fa.pause-circle-o',
                              color='#444',
                              color_active='#6A4572',
                              scale_factor=1.1)
        self.cutStartIcon = icon('fa.scissors',
                                 scale_factor=1.15,
                                 color='#444',
                                 color_active='#6A4572')
        endicon_normal = icon('fa.scissors', scale_factor=1.15,
                              color='#444').pixmap(QSize(36, 36)).toImage()
        endicon_active = icon('fa.scissors',
                              scale_factor=1.15,
                              color='#6A4572').pixmap(QSize(36, 36)).toImage()
        self.cutEndIcon = QIcon()
        self.cutEndIcon.addPixmap(
            QPixmap.fromImage(
                endicon_normal.mirrored(horizontal=True, vertical=False)),
            QIcon.Normal, QIcon.Off)
        self.cutEndIcon.addPixmap(
            QPixmap.fromImage(
                endicon_active.mirrored(horizontal=True, vertical=False)),
            QIcon.Active, QIcon.Off)
        self.saveIcon = icon('fa.video-camera',
                             color='#6A4572',
                             color_active='#6A4572')
        self.muteIcon = QIcon(MainWindow.get_path('images/muted.png'))
        self.unmuteIcon = QIcon(MainWindow.get_path('images/unmuted.png'))
        self.upIcon = icon('ei.caret-up', color='#444')
        self.downIcon = icon('ei.caret-down', color='#444')
        self.removeIcon = icon('ei.remove', color='#B41D1D')
        self.removeAllIcon = icon('ei.trash', color='#B41D1D')
        self.successIcon = QIcon(MainWindow.get_path('images/success.png'))
        self.menuIcon = icon('fa.cog', color='#444', scale_factor=1.15)
        self.completePlayIcon = icon('fa.play', color='#444')
        self.completeOpenIcon = icon('fa.folder-open', color='#444')
        self.completeRestartIcon = icon('fa.retweet', color='#444')
        self.completeExitIcon = icon('fa.sign-out', color='#444')
        self.mediaInfoIcon = icon('fa.info-circle', color='#444')
        self.updateCheckIcon = icon('fa.cloud-download', color='#444')

    def initActions(self) -> None:
        self.openAction = QAction(self.openIcon,
                                  'Open',
                                  self,
                                  statusTip='Open media file',
                                  triggered=self.openMedia)
        self.playAction = QAction(self.playIcon,
                                  'Play',
                                  self,
                                  statusTip='Play media file',
                                  triggered=self.playMedia,
                                  enabled=False)
        self.cutStartAction = QAction(self.cutStartIcon,
                                      ' Start',
                                      self,
                                      toolTip='Start',
                                      statusTip='Set clip start marker',
                                      triggered=self.setCutStart,
                                      enabled=False)
        self.cutEndAction = QAction(self.cutEndIcon,
                                    ' End',
                                    self,
                                    toolTip='End',
                                    statusTip='Set clip end marker',
                                    triggered=self.setCutEnd,
                                    enabled=False)
        self.saveAction = QAction(self.saveIcon,
                                  'Save',
                                  self,
                                  statusTip='Save clips to a new video file',
                                  triggered=self.cutVideo,
                                  enabled=False)
        self.moveItemUpAction = QAction(
            self.upIcon,
            'Move up',
            self,
            statusTip='Move clip position up in list',
            triggered=self.moveItemUp,
            enabled=False)
        self.moveItemDownAction = QAction(
            self.downIcon,
            'Move down',
            self,
            statusTip='Move clip position down in list',
            triggered=self.moveItemDown,
            enabled=False)
        self.removeItemAction = QAction(
            self.removeIcon,
            'Remove clip',
            self,
            statusTip='Remove selected clip from list',
            triggered=self.removeItem,
            enabled=False)
        self.removeAllAction = QAction(self.removeAllIcon,
                                       'Clear list',
                                       self,
                                       statusTip='Clear all clips from list',
                                       triggered=self.clearList,
                                       enabled=False)
        self.mediaInfoAction = QAction(
            self.mediaInfoIcon,
            'Media information',
            self,
            statusTip='View current media file information',
            triggered=self.mediaInfo,
            enabled=False)
        self.updateCheckAction = QAction(
            self.updateCheckIcon,
            'Check for updates...',
            self,
            statusTip='Check for application updates',
            triggered=self.updateCheck)
        self.aboutQtAction = QAction('About Qt',
                                     self,
                                     statusTip='About Qt',
                                     triggered=qApp.aboutQt)
        self.aboutAction = QAction('About %s' % qApp.applicationName(),
                                   self,
                                   statusTip='Credits and licensing',
                                   triggered=self.aboutInfo)

    def initToolbar(self) -> None:
        self.toolbar.addAction(self.openAction)
        self.toolbar.addAction(self.playAction)
        self.toolbar.addAction(self.cutStartAction)
        self.toolbar.addAction(self.cutEndAction)
        self.toolbar.addAction(self.saveAction)

    def initMenus(self) -> None:
        self.appMenu.addAction(self.mediaInfoAction)
        self.appMenu.addAction(self.updateCheckAction)
        self.appMenu.addSeparator()
        self.appMenu.addAction(self.aboutQtAction)
        self.appMenu.addAction(self.aboutAction)

        self.cliplistMenu.addAction(self.moveItemUpAction)
        self.cliplistMenu.addAction(self.moveItemDownAction)
        self.cliplistMenu.addSeparator()
        self.cliplistMenu.addAction(self.removeItemAction)
        self.cliplistMenu.addAction(self.removeAllAction)

    @staticmethod
    def getSpacer() -> QWidget:
        spacer = QWidget()
        spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
        return spacer

    def setRunningTime(self, runtime: str) -> None:
        self.runtimeLabel.setText('<div align="right">%s</div>' % runtime)

    @pyqtSlot(int)
    def setNoVideoText(self) -> None:
        self.novideoLabel.setPixmap(self.novideoMovie.currentPixmap())

    def itemMenu(self, pos: QPoint) -> None:
        globalPos = self.cliplist.mapToGlobal(pos)
        self.moveItemUpAction.setEnabled(False)
        self.moveItemDownAction.setEnabled(False)
        self.removeItemAction.setEnabled(False)
        self.removeAllAction.setEnabled(False)
        index = self.cliplist.currentRow()
        if index != -1:
            if not self.inCut:
                if index > 0:
                    self.moveItemUpAction.setEnabled(True)
                if index < self.cliplist.count() - 1:
                    self.moveItemDownAction.setEnabled(True)
            if self.cliplist.count() > 0:
                self.removeItemAction.setEnabled(True)
        if self.cliplist.count() > 0:
            self.removeAllAction.setEnabled(True)
        self.cliplistMenu.exec_(globalPos)

    def moveItemUp(self) -> None:
        index = self.cliplist.currentRow()
        tmpItem = self.clipTimes[index]
        del self.clipTimes[index]
        self.clipTimes.insert(index - 1, tmpItem)
        self.renderTimes()

    def moveItemDown(self) -> None:
        index = self.cliplist.currentRow()
        tmpItem = self.clipTimes[index]
        del self.clipTimes[index]
        self.clipTimes.insert(index + 1, tmpItem)
        self.renderTimes()

    def removeItem(self) -> None:
        index = self.cliplist.currentRow()
        del self.clipTimes[index]
        if self.inCut and index == self.cliplist.count() - 1:
            self.inCut = False
            self.initMediaControls()
        self.renderTimes()

    def clearList(self) -> None:
        self.clipTimes.clear()
        self.cliplist.clear()
        self.inCut = False
        self.renderTimes()
        self.initMediaControls()

    def mediaInfo(self) -> None:
        if self.mediaPlayer.isMetaDataAvailable():
            content = '<table cellpadding="4">'
            for key in self.mediaPlayer.availableMetaData():
                val = self.mediaPlayer.metaData(key)
                if type(val) is QSize:
                    val = '%s x %s' % (val.width(), val.height())
                content += '<tr><td align="right"><b>%s:</b></td><td>%s</td></tr>\n' % (
                    key, val)
            content += '</table>'
            mbox = QMessageBox(windowTitle='Media Information',
                               windowIcon=self.parent.windowIcon(),
                               textFormat=Qt.RichText)
            mbox.setText('<b>%s</b>' % os.path.basename(
                self.mediaPlayer.currentMedia().canonicalUrl().toLocalFile()))
            mbox.setInformativeText(content)
            mbox.exec_()
        else:
            QMessageBox.critical(
                self.parent, 'MEDIA ERROR',
                '<h3>Could not probe media file.</h3>' +
                '<p>An error occurred while analyzing the media file for its metadata details.'
                +
                '<br/><br/><b>This DOES NOT mean there is a problem with the file and you should '
                + 'be able to continue using it.</b></p>')

    def aboutInfo(self) -> None:
        about_html = '''<style>
    a { color:#441d4e; text-decoration:none; font-weight:bold; }
    a:hover { text-decoration:underline; }
</style>
<div style="min-width:650px;">
<p style="font-size:26pt; font-weight:bold; color:#6A4572;">%s</p>
<p>
    <span style="font-size:13pt;"><b>Version: %s</b></span>
    <span style="font-size:10pt;position:relative;left:5px;">( %s )</span>
</p>
<p style="font-size:13px;">
    Copyright &copy; 2016 <a href="mailto:[email protected]">Pete Alexandrou</a>
    <br/>
    Website: <a href="%s">%s</a>
</p>
<p style="font-size:13px;">
    Thanks to the folks behind the <b>Qt</b>, <b>PyQt</b> and <b>FFmpeg</b>
    projects for all their hard and much appreciated work.
</p>
<p style="font-size:11px;">
    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    as published by the Free Software Foundation; either version 2
    of the License, or (at your option) any later version.
</p>
<p style="font-size:11px;">
    This software uses libraries from the <a href="https://www.ffmpeg.org">FFmpeg</a> project under the
    <a href="https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html">LGPLv2.1</a>
</p></div>''' % (qApp.applicationName(), qApp.applicationVersion(),
                 platform.architecture()[0], qApp.organizationDomain(),
                 qApp.organizationDomain())
        QMessageBox.about(self.parent, 'About %s' % qApp.applicationName(),
                          about_html)

    def openMedia(self) -> None:
        filename, _ = QFileDialog.getOpenFileName(self.parent,
                                                  caption='Select video',
                                                  directory=QDir.homePath())
        if filename != '':
            self.loadFile(filename)

    def loadFile(self, filename: str) -> None:
        self.movieFilename = filename
        if not os.path.exists(filename):
            return
        self.mediaPlayer.setMedia(QMediaContent(QUrl.fromLocalFile(filename)))
        self.initMediaControls(True)
        self.cliplist.clear()
        self.clipTimes = []
        self.parent.setWindowTitle(
            '%s - %s' % (qApp.applicationName(), os.path.basename(filename)))
        if not self.movieLoaded:
            self.videoLayout.replaceWidget(self.novideoWidget,
                                           self.videoplayerWidget)
            self.novideoMovie.stop()
            self.novideoMovie.deleteLater()
            self.novideoWidget.deleteLater()
            self.videoplayerWidget.show()
            self.videoWidget.show()
            self.movieLoaded = True
        if self.mediaPlayer.isVideoAvailable():
            self.mediaPlayer.setPosition(1)
        self.mediaPlayer.play()
        self.mediaPlayer.pause()

    def playMedia(self) -> None:
        if self.mediaPlayer.state() == QMediaPlayer.PlayingState:
            self.mediaPlayer.pause()
            self.playAction.setText('Play')
        else:
            self.mediaPlayer.play()
            self.playAction.setText('Pause')

    def initMediaControls(self, flag: bool = True) -> None:
        self.playAction.setEnabled(flag)
        self.saveAction.setEnabled(False)
        self.cutStartAction.setEnabled(flag)
        self.cutEndAction.setEnabled(False)
        self.mediaInfoAction.setEnabled(flag)
        if flag:
            self.seekSlider.setRestrictValue(0)

    def setPosition(self, position: int) -> None:
        self.mediaPlayer.setPosition(position)

    def positionChanged(self, progress: int) -> None:
        self.seekSlider.setValue(progress)
        currentTime = self.deltaToQTime(progress)
        totalTime = self.deltaToQTime(self.mediaPlayer.duration())
        self.timeCounter.setText('%s / %s' % (currentTime.toString(
            self.timeformat), totalTime.toString(self.timeformat)))

    @pyqtSlot()
    def mediaStateChanged(self) -> None:
        if self.mediaPlayer.state() == QMediaPlayer.PlayingState:
            self.playAction.setIcon(self.pauseIcon)
        else:
            self.playAction.setIcon(self.playIcon)

    def durationChanged(self, duration: int) -> None:
        self.seekSlider.setRange(0, duration)

    def muteAudio(self) -> None:
        if self.mediaPlayer.isMuted():
            self.muteButton.setIcon(self.unmuteIcon)
            self.muteButton.setToolTip('Mute')
        else:
            self.muteButton.setIcon(self.muteIcon)
            self.muteButton.setToolTip('Unmute')
        self.mediaPlayer.setMuted(not self.mediaPlayer.isMuted())

    def setVolume(self, volume: int) -> None:
        self.mediaPlayer.setVolume(volume)

    def toggleFullscreen(self) -> None:
        self.videoWidget.setFullScreen(not self.videoWidget.isFullScreen())

    def setCutStart(self) -> None:
        self.clipTimes.append([
            self.deltaToQTime(self.mediaPlayer.position()), '',
            self.captureImage()
        ])
        self.cutStartAction.setDisabled(True)
        self.cutEndAction.setEnabled(True)
        self.seekSlider.setRestrictValue(self.seekSlider.value(), True)
        self.inCut = True
        self.renderTimes()

    def setCutEnd(self) -> None:
        item = self.clipTimes[len(self.clipTimes) - 1]
        selected = self.deltaToQTime(self.mediaPlayer.position())
        if selected.__lt__(item[0]):
            QMessageBox.critical(
                self.parent, 'Invalid END Time',
                'The clip end time must come AFTER it\'s start time. Please try again.'
            )
            return
        item[1] = selected
        self.cutStartAction.setEnabled(True)
        self.cutEndAction.setDisabled(True)
        self.seekSlider.setRestrictValue(0, False)
        self.inCut = False
        self.renderTimes()

    @pyqtSlot(QModelIndex, int, int, QModelIndex, int)
    def syncClipList(self, parent: QModelIndex, start: int, end: int,
                     destination: QModelIndex, row: int) -> None:
        if start < row:
            index = row - 1
        else:
            index = row
        clip = self.clipTimes.pop(start)
        self.clipTimes.insert(index, clip)

    def renderTimes(self) -> None:
        self.cliplist.clear()
        if len(self.clipTimes) > 4:
            self.cliplist.setFixedWidth(200)
        else:
            self.cliplist.setFixedWidth(185)
        self.totalRuntime = 0
        for item in self.clipTimes:
            endItem = ''
            if type(item[1]) is QTime:
                endItem = item[1].toString(self.timeformat)
                self.totalRuntime += item[0].msecsTo(item[1])
            listitem = QListWidgetItem()
            listitem.setTextAlignment(Qt.AlignVCenter)
            if type(item[2]) is QPixmap:
                listitem.setIcon(QIcon(item[2]))
            self.cliplist.addItem(listitem)
            marker = QLabel(
                '''<style>b { font-size:7pt; } p { margin:2px 5px; }</style>
                            <p><b>START</b><br/>%s<br/><b>END</b><br/>%s</p>'''
                % (item[0].toString(self.timeformat), endItem))
            marker.setStyleSheet('border:none;')
            self.cliplist.setItemWidget(listitem, marker)
            listitem.setFlags(Qt.ItemIsSelectable | Qt.ItemIsDragEnabled
                              | Qt.ItemIsEnabled)
        if len(self.clipTimes) and not self.inCut:
            self.saveAction.setEnabled(True)
        if self.inCut or len(self.clipTimes) == 0 or not type(
                self.clipTimes[0][1]) is QTime:
            self.saveAction.setEnabled(False)
        self.setRunningTime(
            self.deltaToQTime(self.totalRuntime).toString(self.timeformat))

    @staticmethod
    def deltaToQTime(millisecs: int) -> QTime:
        secs = millisecs / 1000
        return QTime((secs / 3600) % 60, (secs / 60) % 60, secs % 60,
                     (secs * 1000) % 1000)

    def captureImage(self) -> QPixmap:
        frametime = self.deltaToQTime(self.mediaPlayer.position()).toString(
            self.timeformat)
        inputfile = self.mediaPlayer.currentMedia().canonicalUrl().toLocalFile(
        )
        imagecap = self.videoService.capture(inputfile, frametime)
        if type(imagecap) is QPixmap:
            return imagecap

    def cutVideo(self) -> bool:
        clips = len(self.clipTimes)
        filename, filelist = '', []
        source = self.mediaPlayer.currentMedia().canonicalUrl().toLocalFile()
        _, sourceext = os.path.splitext(source)
        if clips > 0:
            self.finalFilename, _ = QFileDialog.getSaveFileName(
                self.parent, 'Save video', source,
                'Video files (*%s)' % sourceext)
            if self.finalFilename == '':
                return False
            qApp.setOverrideCursor(Qt.BusyCursor)
            self.saveAction.setDisabled(True)
            self.showProgress(clips)
            file, ext = os.path.splitext(self.finalFilename)
            index = 1
            self.progress.setLabelText('Cutting media files...')
            qApp.processEvents()
            for clip in self.clipTimes:
                duration = self.deltaToQTime(clip[0].msecsTo(
                    clip[1])).toString(self.timeformat)
                filename = '%s_%s%s' % (file, '{0:0>2}'.format(index), ext)
                filelist.append(filename)
                self.videoService.cut(source, filename,
                                      clip[0].toString(self.timeformat),
                                      duration)
                index += 1
            if len(filelist) > 1:
                self.joinVideos(filelist, self.finalFilename)
            else:
                QFile.remove(self.finalFilename)
                QFile.rename(filename, self.finalFilename)
            self.progress.setLabelText('Complete...')
            self.progress.setValue(100)
            qApp.processEvents()
            self.progress.close()
            self.progress.deleteLater()
            qApp.restoreOverrideCursor()
            self.complete()
            return True
        return False

    def joinVideos(self, joinlist: list, filename: str) -> None:
        listfile = os.path.normpath(
            os.path.join(os.path.dirname(joinlist[0]), '.vidcutter.list'))
        fobj = open(listfile, 'w')
        for file in joinlist:
            fobj.write('file \'%s\'\n' % file.replace("'", "\\'"))
        fobj.close()
        self.videoService.join(listfile, filename)
        QFile.remove(listfile)
        for file in joinlist:
            if os.path.isfile(file):
                QFile.remove(file)

    def updateCheck(self) -> None:
        self.updater = Updater()
        self.updater.updateAvailable.connect(self.updateHandler)
        self.updater.start()

    def updateHandler(self, updateExists: bool, version: str = None):
        if updateExists:
            if Updater.notify_update(self, version) == QMessageBox.AcceptRole:
                self.updater.install_update(self)
        else:
            Updater.notify_no_update(self)

    def showProgress(self,
                     steps: int,
                     label: str = 'Analyzing media...') -> None:
        self.progress = QProgressDialog(label,
                                        None,
                                        0,
                                        steps,
                                        self.parent,
                                        windowModality=Qt.ApplicationModal,
                                        windowIcon=self.parent.windowIcon(),
                                        minimumDuration=0,
                                        minimumWidth=500)
        self.progress.show()
        for i in range(steps):
            self.progress.setValue(i)
            qApp.processEvents()
            time.sleep(1)

    def complete(self) -> None:
        info = QFileInfo(self.finalFilename)
        mbox = QMessageBox(windowTitle='VIDEO PROCESSING COMPLETE',
                           minimumWidth=500,
                           textFormat=Qt.RichText)
        mbox.setText(
            '''
    <style>
        table.info { margin:6px; padding:4px 15px; }
        td.label { font-weight:bold; font-size:10.5pt; text-align:right; }
        td.value { font-size:10.5pt; }
    </style>
    <table class="info" cellpadding="4" cellspacing="0">
        <tr>
            <td class="label"><b>File:</b></td>
            <td class="value" nowrap>%s</td>
        </tr>
        <tr>
            <td class="label"><b>Size:</b></td>
            <td class="value">%s</td>
        </tr>
        <tr>
            <td class="label"><b>Length:</b></td>
            <td class="value">%s</td>
        </tr>
    </table><br/>''' %
            (QDir.toNativeSeparators(
                self.finalFilename), self.sizeof_fmt(int(info.size())),
             self.deltaToQTime(self.totalRuntime).toString(self.timeformat)))
        play = mbox.addButton('Play', QMessageBox.AcceptRole)
        play.setIcon(self.completePlayIcon)
        play.clicked.connect(self.openResult)
        fileman = mbox.addButton('Open', QMessageBox.AcceptRole)
        fileman.setIcon(self.completeOpenIcon)
        fileman.clicked.connect(self.openFolder)
        end = mbox.addButton('Exit', QMessageBox.AcceptRole)
        end.setIcon(self.completeExitIcon)
        end.clicked.connect(self.close)
        new = mbox.addButton('Restart', QMessageBox.AcceptRole)
        new.setIcon(self.completeRestartIcon)
        new.clicked.connect(self.parent.restart)
        mbox.setDefaultButton(new)
        mbox.setEscapeButton(new)
        mbox.adjustSize()
        mbox.exec_()

    def sizeof_fmt(self, num: float, suffix: chr = 'B') -> str:
        for unit in ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']:
            if abs(num) < 1024.0:
                return "%3.1f%s%s" % (num, unit, suffix)
            num /= 1024.0
        return "%.1f%s%s" % (num, 'Y', suffix)

    @pyqtSlot()
    def openFolder(self) -> None:
        self.openResult(pathonly=True)

    @pyqtSlot(bool)
    def openResult(self, pathonly: bool = False) -> None:
        self.parent.restart()
        if len(self.finalFilename) and os.path.exists(self.finalFilename):
            target = self.finalFilename if not pathonly else os.path.dirname(
                self.finalFilename)
            QDesktopServices.openUrl(QUrl.fromLocalFile(target))

    @pyqtSlot()
    def startNew(self) -> None:
        qApp.restoreOverrideCursor()
        self.clearList()
        self.seekSlider.setValue(0)
        self.seekSlider.setRange(0, 0)
        self.mediaPlayer.setMedia(QMediaContent())
        self.initNoVideo()
        self.videoLayout.replaceWidget(self.videoplayerWidget,
                                       self.novideoWidget)
        self.initMediaControls(False)
        self.parent.setWindowTitle('%s' % qApp.applicationName())

    def wheelEvent(self, event: QWheelEvent) -> None:
        if self.mediaPlayer.isVideoAvailable(
        ) or self.mediaPlayer.isAudioAvailable():
            if event.angleDelta().y() > 0:
                newval = self.seekSlider.value() - 1000
            else:
                newval = self.seekSlider.value() + 1000
            self.seekSlider.setValue(newval)
            self.seekSlider.setSliderPosition(newval)
            self.mediaPlayer.setPosition(newval)
        event.accept()

    def keyPressEvent(self, event: QKeyEvent) -> None:
        if self.mediaPlayer.isVideoAvailable(
        ) or self.mediaPlayer.isAudioAvailable():
            addtime = 0
            if event.key() == Qt.Key_Left:
                addtime = -1000
            elif event.key() == Qt.Key_PageUp or event.key() == Qt.Key_Up:
                addtime = -10000
            elif event.key() == Qt.Key_Right:
                addtime = 1000
            elif event.key() == Qt.Key_PageDown or event.key() == Qt.Key_Down:
                addtime = 10000
            elif event.key() == Qt.Key_Enter:
                self.toggleFullscreen()
            elif event.key(
            ) == Qt.Key_Escape and self.videoWidget.isFullScreen():
                self.videoWidget.setFullScreen(False)
            if addtime != 0:
                newval = self.seekSlider.value() + addtime
                self.seekSlider.setValue(newval)
                self.seekSlider.setSliderPosition(newval)
                self.mediaPlayer.setPosition(newval)
        event.accept()

    def mousePressEvent(self, event: QMouseEvent) -> None:
        if event.button() == Qt.BackButton and self.cutStartAction.isEnabled():
            self.setCutStart()
            event.accept()
        elif event.button(
        ) == Qt.ForwardButton and self.cutEndAction.isEnabled():
            self.setCutEnd()
            event.accept()
        else:
            super(VidCutter, self).mousePressEvent(event)

    @pyqtSlot(QMediaPlayer.Error)
    def handleError(self, error: QMediaPlayer.Error) -> None:
        qApp.restoreOverrideCursor()
        self.startNew()
        if error == QMediaPlayer.ResourceError:
            QMessageBox.critical(
                self.parent, 'INVALID MEDIA',
                'Invalid media file detected at:<br/><br/><b>%s</b><br/><br/>%s'
                % (self.movieFilename, self.mediaPlayer.errorString()))
        else:
            QMessageBox.critical(self.parent, 'ERROR NOTIFICATION',
                                 self.mediaPlayer.errorString())

    def closeEvent(self, event: QCloseEvent) -> None:
        self.parent.closeEvent(event)