Exemplo n.º 1
0
class Player(QWidget):

    media_loaded = Signal(str)
    stopped = Signal(str)
    playlist_size_changed = Signal(int)
    handle_double_click = Slot()

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

        self.duration = 0
        self.volume = 50

        self.player = QMediaPlayer()
        self.playlist = Playlist(self)
        self.videoWidget = VideoWidget()
        self.next_url = QUrl()
        self.context_menu = QMenu(self)
        self.display_splitter = QSplitter(Qt.Horizontal)
        self.repeat_control = RepeatControl(parent=self)
        self.repeat_control.get_player_position = self.player.position
        self.repeat_control.set_position_to_player = self.player.setPosition
        self.player.positionChanged.connect(self.repeat_control.set_pos)

        self.setAcceptDrops(True)

        std_icon = self.style().standardIcon
        self.play_button = create_flat_button(std_icon(QStyle.SP_MediaPlay))
        self.stopButton = create_flat_button(std_icon(QStyle.SP_MediaStop), '')
        self.backwardButton = create_flat_button(
            std_icon(QStyle.SP_MediaSkipBackward), '')
        self.forwardButton = create_flat_button(
            std_icon(QStyle.SP_MediaSkipForward), '')

        self.order_list = self.repeat_control.menu()
        self.order_list.setFixedWidth(115)

        self.playback_rate_menu = QComboBox()
        self.playback_rate_menu.addItems(
            ('0.5x', '0.75x', '0.9x', '1.0x', '1.1x', '1.25x', '1.5x'))
        self.playback_rate_menu.setCurrentText('1.0x')

        self.muteButton = create_flat_button(
            std_icon(QStyle.SP_MediaVolume if not self.player.isMuted() else
                     QStyle.SP_MediaVolumeMuted))

        self.volumeBar = QSlider(Qt.Horizontal)
        self.volumeBar.setRange(0, 100)
        self.volumeBar.setValue(self.volume)

        self.labelVolume = QLabel(str(self.volume))
        self.labelVolume.setMinimumWidth(24)

        self.statusInfoLabel = QLabel()

        self.seekBar = QSlider(Qt.Horizontal)
        self.seekBar.setRange(0, self.player.duration() / 1000)

        self.labelTotalTime = QLabel('00:00')
        self.labelCurrentTime = QLabel('00:00')

        self.create_layout()
        self.create_connections()

        self.player.setVideoOutput(self.videoWidget)
        self.videoWidget.show()

    def create_layout(self):
        seekBarLayout = QHBoxLayout()
        seekBarLayout.addWidget(self.labelCurrentTime)
        seekBarLayout.addWidget(self.seekBar)
        seekBarLayout.addWidget(self.labelTotalTime)

        controlWithoutSeekBarLayout = QHBoxLayout()
        controlWithoutSeekBarLayout.setSpacing(1)
        controlWithoutSeekBarLayout.addWidget(self.play_button)
        controlWithoutSeekBarLayout.addWidget(self.stopButton)
        controlWithoutSeekBarLayout.addWidget(self.backwardButton)
        controlWithoutSeekBarLayout.addWidget(self.forwardButton)
        controlWithoutSeekBarLayout.addWidget(self.order_list)
        controlWithoutSeekBarLayout.addWidget(self.playback_rate_menu)
        controlWithoutSeekBarLayout.addStretch(stretch=2)
        controlWithoutSeekBarLayout.addWidget(self.muteButton)
        controlWithoutSeekBarLayout.addWidget(self.volumeBar)
        controlWithoutSeekBarLayout.addWidget(self.labelVolume,
                                              alignment=Qt.AlignRight)

        controlLayout = QVBoxLayout()
        controlLayout.addLayout(seekBarLayout)
        controlLayout.addLayout(controlWithoutSeekBarLayout)

        self.display_splitter.setOpaqueResize(False)
        self.display_splitter.addWidget(self.videoWidget)
        self.display_splitter.addWidget(self.playlist.widget)
        self.display_splitter.setSizes([300, 200])

        layout = QVBoxLayout()
        layout.setContentsMargins(11, 0, 11, 0)
        layout.addWidget(self.display_splitter, 1)
        layout.addLayout(controlLayout)
        layout.addWidget(self.repeat_control.ab_repeat_widget)
        layout.addWidget(self.statusInfoLabel)

        self.setLayout(layout)

    def create_connections(self):
        self.play_button.clicked.connect(self.optimal_play)
        self.stopButton.clicked.connect(self.stop)
        self.backwardButton.clicked.connect(self.skip_backward)
        self.forwardButton.clicked.connect(self.skip_forward)
        self.muteButton.clicked.connect(self.toggleMute)
        self.playback_rate_menu.currentTextChanged.connect(
            self.set_playback_rate)

        self.player.stateChanged.connect(self.playerStateChanged)
        self.player.mediaStatusChanged.connect(self.mediaStatusChanged)
        self.player.durationChanged.connect(self.durationChanged)
        self.player.positionChanged.connect(self.positionChanged)

        self.player.error.connect(self.handleError)

        self.volumeBar.sliderMoved.connect(self.setVolume)
        self.volumeBar.sliderReleased.connect(self.setVolume)
        self.volumeBar.valueChanged.connect(self.volumeChanged)

        self.seekBar.sliderMoved.connect(self.seek)
        self.seekBar.sliderReleased.connect(self.seekBarClicked)

        self.repeat_control.pos_exceeded.connect(self.seek)
        self.player.currentMediaChanged.connect(self.repeat_control.reset)

        self.playlist.double_clicked.connect(self.load_and_play)

        self.videoWidget.double_clicked.connect(self.no_future)

    def contextMenuEvent(self, event):
        self.context_menu.exec_(event.globalPos())

    def read_settings(self):
        settings = QSettings()
        settings.beginGroup('player')
        self.order_list.setCurrentIndex(settings.value('order_list', 0))
        self.display_splitter.restoreState(
            QByteArray(settings.value('splitter_sizes')))
        settings.endGroup()
        self.playlist.read_settings()

    def no_future(self):
        self.display_splitter.moveSplitter(0, 1)

    def autoplay(self):
        """メディアを読み込み、再生する。

        order_listに応じて、次に何を再生するかを決める。
        """
        i = self.order_list.currentIndex()
        if i == 1:
            # self.repeat_track()
            return
        elif i == 2:
            self.repeat_all()
        else:
            self.next_track()
        self.play()

    def optimal_play(self):
        if self.player.state() == QMediaPlayer.StoppedState:
            self.load_and_play()
        else:
            self.play()

    def load_and_play(self):
        self.load(self.playlist.get_new_one())
        self.play()

    def load(self, file_url: QUrl):
        if file_url is None:
            return None
        if file_url.isValid():
            c = QMediaContent(file_url)
            self.player.setMedia(c)
            self.media_loaded.emit(self.playlist.current_title())
            self.enableInterface()

    def play(self):
        if self.player.state() == QMediaPlayer.PlayingState:
            self.player.pause()
            return
        if self.player.mediaStatus() == QMediaPlayer.LoadingMedia or\
                self.player.mediaStatus() == QMediaPlayer.StalledMedia:
            QTimer.singleShot(600, self.player.play)

        self.player.play()
        self.playlist.update_listview()

    def stop(self):
        if not self.player.state() == QMediaPlayer.StoppedState:
            self.seek(0)
            self.player.stop()
            self.setStatusInfo('Stopped')
            self.stopped.emit('')

    def playerStateChanged(self, state):
        if state == QMediaPlayer.PlayingState:
            self.play_button.setIcon(self.style().standardIcon(
                QStyle.SP_MediaPause))
        else:
            self.play_button.setIcon(self.style().standardIcon(
                QStyle.SP_MediaPlay))

    def durationChanged(self, duration):
        self.repeat_control.set_duration(duration)
        duration /= 1000

        self.duration = duration

        totalTime = QTime((duration / 3600) % 60, (duration / 60) % 60,
                          (duration % 60), (duration * 1000) % 1000)

        format = 'hh:mm:ss' if duration > 3600 else 'mm:ss'
        totalTimeStr = totalTime.toString(format)

        self.labelTotalTime.setText(totalTimeStr)
        self.seekBar.setMaximum(duration)

    def positionChanged(self, progress):
        progress /= 1000

        self.updateCurrentTime(progress)
        self.seekBar.setValue(progress)

    def updateCurrentTime(self, currentInfo):
        if currentInfo:
            currentTime = QTime((currentInfo / 3600) % 60,
                                (currentInfo / 60) % 60, currentInfo % 60,
                                (currentInfo * 1000) % 1000)

            format = 'hh:mm:ss' if self.duration > 3600 else 'mm:ss'
            currentTimeStr = currentTime.toString(format)
        else:
            currentTimeStr = '00:00'

        self.labelCurrentTime.setText(currentTimeStr)

    def repeat_track(self):
        QTimer.singleShot(50, self.play)

    def repeat_all(self):
        if self.playlist.count() - 1 == self.playlist.current_row():
            url = self.playlist.first()
            self.load(url)
        else:
            self.next_track()

    def setVolume(self):
        self.player.setVolume(self.volumeBar.sliderPosition() * 2)

    def volumeChanged(self):
        self.labelVolume.setText(str(self.volumeBar.sliderPosition()))
        self.volume = self.volumeBar.sliderPosition()

    def seekBarClicked(self):
        self.seek(self.seekBar.sliderPosition())

    def seek(self, seconds):
        self.player.setPosition(seconds * 1000)

    def set_playback_rate(self, rate_text):
        self.player.setPlaybackRate(float(rate_text[:-1]))

    def toggleMute(self):
        if self.player.isMuted():
            self.player.setMuted(False)
            self.muteButton.setIcon(self.style().standardIcon(
                QStyle.SP_MediaVolume))
        else:
            self.player.setMuted(True)
            self.muteButton.setIcon(self.style().standardIcon(
                QStyle.SP_MediaVolumeMuted))

    def disableInterface(self):
        self.play_button.setEnabled(False)
        self.stopButton.setEnabled(False)
        self.backwardButton.setEnabled(False)
        self.forwardButton.setEnabled(False)
        self.labelCurrentTime.setText('00:00')
        self.labelTotalTime.setText('00:00')

    def enableInterface(self):
        self.play_button.setEnabled(True)
        self.stopButton.setEnabled(True)
        self.backwardButton.setEnabled(True)
        self.forwardButton.setEnabled(True)

    def mediaStatusChanged(self, status):
        if status == QMediaPlayer.LoadingMedia:
            self.setStatusInfo('Loading...')
        elif status == QMediaPlayer.BufferingMedia:
            self.setStatusInfo('Buffering')
        elif status == QMediaPlayer.EndOfMedia:
            # self.player.stop()
            self.autoplay()
        elif status == QMediaPlayer.InvalidMedia:
            self.handleError()
            #TODO: Step Forward を割り当てる

    def clearStatusInfo(self):
        self.statusInfoLabel.setText("")

    def handleError(self):
        self.disableInterface()
        self.setStatusInfo('Error: ' + self.player.errorString())

    def setStatusInfo(self, message, seconds=5):
        if not message == '':
            self.statusInfoLabel.setText(message)
            QTimer.singleShot(seconds * 1000, self.clearStatusInfo)

    def next_track(self):
        url = self.playlist.next()
        if url is None:
            return None
        else:
            self.load(url)

    def previous_track(self):
        url = self.playlist.previous()
        if url is None:
            return None
        else:
            self.load(url)

    def skip_forward(self):
        self.next_track()
        self.play()

    def skip_backward(self):
        if self.seekBar.sliderPosition() > 2:
            self.seek(0)
        else:
            self.previous_track()
            self.play()

    def forward(self, seconds):
        currentPosition = self.seekBar.sliderPosition()

        if currentPosition + seconds < self.duration:
            self.seek(currentPosition + seconds)
        else:
            self.seek(self.duration - 1)

    def backward(self, seconds):
        self.forward(-seconds)

    def forward_short(self):
        self.forward(SeekStep.SHORT)

    def forward_medium(self):
        self.forward(SeekStep.MEDIUM)

    def forward_long(self):
        self.forward(SeekStep.LONG)

    def forward_verylong(self):
        self.forward(SeekStep.VERYLONG)

    def backward_short(self):
        self.backward(SeekStep.SHORT)

    def backward_medium(self):
        self.backward(SeekStep.MEDIUM)

    def backward_long(self):
        self.backward(SeekStep.LONG)

    def backward_verylong(self):
        self.backward(SeekStep.VERYLONG)

    def dragEnterEvent(self, event):
        if event.mimeData().hasUrls():
            event.accept()

    def dragMoveEvent(self, event):
        if event.mimeData().hasUrls():
            event.accept()

    def dropEvent(self, event):
        if event.mimeData().hasUrls():
            urls = event.mimeData().urls()
            self.load(urls[0])
            # self.stop()
            self.play()
Exemplo n.º 2
0
class EditorWidget(QWidget):
    """Widget which contain the editor."""
    def __init__(self, plugin_manager):
        super(EditorWidget, self).__init__()
        os.environ[
            'QT_MULTIMEDIA_PREFERRED_PLUGINS'] = 'windowsmediafoundation'
        self.plugin_manager = plugin_manager

        #parent layout
        self.v_box = QVBoxLayout()
        self.h_box = QHBoxLayout()
        # parent splitter for the text and numbers
        self.text_h_box = QSplitter(Qt.Horizontal)
        self.text_h_box.splitterMoved.connect(self.on_text_changed)

        self.settings = QSettings(c.SETTINGS_PATH, QSettings.IniFormat)
        self.keyboard_settings = QSettings(c.KEYBOARD_SETTINGS_PATH,
                                           QSettings.IniFormat)
        self.theme = self.settings.value(c.THEME, defaultValue=c.THEME_D)

        # font settings
        self.font = QFont(
            self.settings.value(c.FONT, defaultValue="Arial", type=str))
        self.font.setPointSize(
            self.settings.value(c.FONT_SIZE, defaultValue=16, type=int))

        # the text widget itself
        self.text = QPlainTextEdit()
        self.text.setFont(self.font)
        self.text.textChanged.connect(self.on_text_changed)
        self.text.setFocusPolicy(Qt.StrongFocus)

        # the number text widget to show the row numbers
        self.numbers = QPlainTextEdit()
        self.numbers.setFont(self.font)
        self.numbers.setReadOnly(True)
        self.numbers.setMinimumWidth(20)
        self.numbers.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.numbers.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.numbers.setLineWrapMode(QPlainTextEdit.NoWrap)
        self.numbers.setFocusPolicy(Qt.NoFocus)
        self.numbers.setFrameStyle(QFrame.NoFrame)
        self.numbers.setStyleSheet("background-color: rgba(0,0,0,0%)")

        # sync the text widget and number widget
        self.text_bar = self.text.verticalScrollBar()
        self.number_bar = self.numbers.verticalScrollBar()
        #self.number_bar.valueChanged.connect(self.text_bar.setValue)
        self.text_bar.valueChanged.connect(self.number_bar.setValue)

        # add them into their layout
        self.text_h_box.addWidget(self.numbers)
        self.text_h_box.addWidget(self.text)
        self.text_h_box.setSizes([10, 700])

        # layout which holds the media controls in the bottom
        self.media_controls = QHBoxLayout()
        self.media_controls_settings = QVBoxLayout()
        self.media_controls_slider_h_box = QHBoxLayout()

        # direct player controls
        self.btn_size = 75
        self.play_icon = QIcon(
            os.path.join(c.ICON_PATH, self.theme, "play.png"))
        self.pause_icon = QIcon(
            os.path.join(c.ICON_PATH, self.theme, "pause.png"))
        self.play_btn = QPushButton(icon=self.play_icon)
        self.play_btn.clicked.connect(self.on_play)
        self.play_btn.setFixedSize(self.btn_size, self.btn_size)
        self.play_btn.setIconSize(QSize(self.btn_size, self.btn_size))
        self.play_btn.setFlat(True)
        self.play_btn.setShortcut(QKeySequence().fromString(
            self.keyboard_settings.value(c.PLAY_PAUSE_KEY, defaultValue="")))
        self.forward_btn = QPushButton(
            icon=QIcon(os.path.join(c.ICON_PATH, self.theme, "forward.png")))
        self.forward_btn.clicked.connect(self.on_forward)
        self.forward_btn.setFixedSize(self.btn_size, self.btn_size)
        self.forward_btn.setIconSize(QSize(self.btn_size, self.btn_size))
        self.forward_btn.setFlat(True)
        self.forward_btn.setShortcut(QKeySequence().fromString(
            self.keyboard_settings.value(c.FORWARD_KEY, defaultValue="")))
        self.backward_btn = QPushButton(
            icon=QIcon(os.path.join(c.ICON_PATH, self.theme, "backward.png")))
        self.backward_btn.clicked.connect(self.on_backward)
        self.backward_btn.setFixedSize(self.btn_size, self.btn_size)
        self.backward_btn.setIconSize(QSize(self.btn_size, self.btn_size))
        self.backward_btn.setFlat(True)
        self.backward_btn.setShortcut(QKeySequence().fromString(
            self.keyboard_settings.value(c.BACKWARDS_KEY, defaultValue="")))

        # add them to the layout
        self.media_controls.addStretch()
        self.media_controls.addWidget(self.backward_btn)
        self.media_controls.addWidget(self.play_btn)
        self.media_controls.addWidget(self.forward_btn)
        self.media_controls.addStretch(4)

        # slider which shows the current time
        self.time_slider = QSlider(Qt.Horizontal)
        self.time_slider.sliderMoved.connect(self.on_time_slider_moved)

        # label on the right of the slider, which shows the current time
        self.time_label = QLabel("00:00/00:00")
        self.media_controls_slider_h_box.addWidget(self.time_slider)
        self.media_controls_slider_h_box.addWidget(self.time_label)

        # icons for the other sliders
        self.vol_icon = QIcon(
            os.path.join(c.ICON_PATH, self.theme,
                         "volume.png")).pixmap(QSize(32, 32))
        self.rate_icon = QIcon(
            os.path.join(c.ICON_PATH, self.theme,
                         "playbackrate.png")).pixmap(QSize(32, 32))
        self.rewind_icon = QIcon(
            os.path.join(c.ICON_PATH, self.theme,
                         "time.png")).pixmap(QSize(32, 32))

        # display the icons through labels
        self.vol_icon_label = QLabel()
        self.vol_icon_label.setPixmap(self.vol_icon)
        self.rate_icon_label = QLabel()
        self.rate_icon_label.setPixmap(self.rate_icon)
        self.rewind_rewind_label = QLabel()
        self.rewind_rewind_label.setPixmap(self.rewind_icon)

        # init of the other sliders
        self.vol_slider = QSlider(Qt.Horizontal)
        self.vol_slider.sliderMoved.connect(self.on_vol_slider_moved)
        self.vol_slider.setFixedWidth(250)
        self.vol_slider.setRange(1, 100)
        self.rate_slider = QSlider(Qt.Horizontal)
        self.rate_slider.sliderMoved.connect(self.on_rate_slider_moved)
        self.rate_slider.setFixedWidth(250)
        self.rate_slider.setRange(1, 20)
        self.rewind_time = 10
        self.rewind_slider = QSlider(Qt.Horizontal)
        self.rewind_slider.sliderMoved.connect(self.on_rewind_slider_moved)
        self.rewind_slider.setFixedWidth(250)
        self.rewind_slider.setRange(1, 60)
        self.rewind_slider.setValue(self.rewind_time)

        # labels for the values
        self.vol_label = QLabel()
        self.rate_label = QLabel()
        self.rewind_label = QLabel()

        # create hbox for each of the three sliders
        self.vol_h_box = QHBoxLayout()
        self.vol_h_box.addWidget(self.vol_label)
        self.vol_h_box.addWidget(self.vol_slider)
        self.vol_h_box.addWidget(self.vol_icon_label)

        self.rate_h_box = QHBoxLayout()
        self.rate_h_box.addWidget(self.rate_label)
        self.rate_h_box.addWidget(self.rate_slider)
        self.rate_h_box.addWidget(self.rate_icon_label)

        self.rewind_h_box = QHBoxLayout()
        self.rewind_h_box.addWidget(self.rewind_label)
        self.rewind_h_box.addWidget(self.rewind_slider)
        self.rewind_h_box.addWidget(self.rewind_rewind_label)

        # group them together in a vlayout
        self.media_controls_settings.addLayout(self.vol_h_box)
        self.media_controls_settings.addLayout(self.rewind_h_box)
        self.media_controls_settings.addLayout(self.rate_h_box)

        # add this layout to the layout which already contains the buttons
        self.media_controls.addLayout(self.media_controls_settings)

        self.word_by_word_actions = QListWidget()
        self.word_by_word_actions.setMaximumWidth(150)

        self.h_box.addWidget(self.text_h_box)
        self.h_box.addWidget(self.word_by_word_actions)

        # group all ungrouped layouts and widgets to the parent layout
        self.v_box.addLayout(self.h_box, 10)
        self.v_box.addLayout(self.media_controls_slider_h_box, 1)
        self.v_box.addLayout(self.media_controls, 1)

        # set parent layout
        self.setLayout(self.v_box)

        # init media_player
        self.media_player = QMediaPlayer()
        self.video_widget = QVideoWidget()
        self.video_widget.setGeometry(200, 200, 500, 300)
        self.video_widget.setWindowTitle("Output")
        self.media_player.setVideoOutput(self.video_widget)
        self.media_player.positionChanged.connect(self.on_position_change)
        self.media_player.durationChanged.connect(self.on_duration_change)
        self.vol_slider.setValue(self.media_player.volume())
        self.rate_slider.setValue(int(self.media_player.playbackRate() * 10))

        self.on_vol_slider_moved(self.media_player.volume())
        self.on_rate_slider_moved(self.media_player.playbackRate() * 10)
        self.on_rewind_slider_moved(self.rewind_time)

        self.activate_text_modules = False
        self.get_text_modules()

        self.text_option_on = QTextOption()
        self.text_option_on.setFlags(
            QTextOption.ShowTabsAndSpaces
            | QTextOption.ShowLineAndParagraphSeparators)

        self.text_option_off = QTextOption()

        self.transcription_meta_data = None
        self.word_pos = -1
        self.word_start_time = None
        self.word_end_time = None

        self.tcf_highlight = QTextCharFormat()
        self.tcf_highlight.setBackground(Qt.red)
        self.tcf_normal = QTextCharFormat()
        self.tcf_normal.setBackground(Qt.transparent)

        self.show_empty_buttons = self.settings.value(c.SHOW_EMPTY_BUTTONS,
                                                      defaultValue=True,
                                                      type=bool)

    def on_position_change(self, position):
        """Is executed when media is played (position is changed)

        Args:
          position: Current position (ms) of the media player.

        """

        self.time_slider.setValue(position)
        self.time_label.setText(
            create_time_string(position, self.media_player.duration()))

        if self.word_end_time is None:
            return

        if position > self.word_end_time:
            self.on_play()
            self.word_start_time = None
            self.word_end_time = None

    def on_duration_change(self, duration):
        """Is executed when duration of the media changes.

        Args:
          duration: duration of the media.

        """

        self.time_slider.setRange(0, duration)
        self.time_label.setText(
            create_time_string(0, self.media_player.duration()))

    def on_time_slider_moved(self, value):
        """Is executed when the time slider was moved.

        Args:
          value: current value of the slider.

        """

        self.media_player.setPosition(value)

    def on_vol_slider_moved(self, value):
        """Is executed when the volume slider is moved.

        Args:
          value: current value of the slider.

        """

        self.media_player.setVolume(value)
        self.vol_label.setText(str(value) + "%")

    def on_rate_slider_moved(self, value):
        """Is executed when the rate slider is moved.

        Args:
          value: current value of the slider.

        """

        self.media_player.setPlaybackRate(value / 10)
        self.rate_label.setText(str(value / 10) + "x")

    def on_rewind_slider_moved(self, value):
        """Is executed when the rewind slider is moved.

        Args:
          value: current value of the slider.

        """

        self.rewind_time = value
        self.rewind_label.setText(str(value) + "s")

    def on_play(self):
        """Is executed when the play or pause button is pressed."""

        if self.media_player.state() == QMediaPlayer.PlayingState:
            self.media_player.pause()
            self.play_btn.setIcon(self.play_icon)

        else:
            self.media_player.play()
            self.play_btn.setIcon(self.pause_icon)

    def on_forward(self):
        """Is executed when the forward button is pressed."""

        self.media_player.setPosition(self.media_player.position() +
                                      self.rewind_time * 1000)

    def on_backward(self):
        """Is executed when the backward button is pressed."""

        self.media_player.setPosition(self.media_player.position() -
                                      self.rewind_time * 1000)

    def on_text_changed(self):
        """Is executed when the text changed

        Calculates the line numbers and sets the text modules if activated.

        """

        lines = int(
            self.text.document().documentLayout().documentSize().height())
        self.numbers.setPlainText("")
        text = ""
        for i in range(1, lines + 1):
            text = text + str(i) + "\n"

        self.numbers.setPlainText(text)
        self.number_bar.setSliderPosition(self.text_bar.sliderPosition())

        new_text = self.text.toPlainText()

        if self.activate_text_modules == True:
            for key in self.text_modules.keys():
                to_replace = " " + key + " "
                to_replace_with = " " + self.text_modules[key] + " "
                new_text = new_text.replace(to_replace, to_replace_with)

        if self.text.toPlainText() != new_text:
            old_pos = self.text.textCursor().position()
            self.text.setPlainText(new_text)
            cursor = self.text.textCursor()
            cursor.setPosition(old_pos, QTextCursor.MoveAnchor)
            cursor.movePosition(QTextCursor.EndOfWord)
            cursor.movePosition(QTextCursor.NextCharacter)
            self.text.setTextCursor(cursor)

    def show_video(self):
        """Shows or hides the video feed."""

        if self.video_widget.isVisible():
            self.video_widget.hide()
        else:
            self.video_widget.show()

    def open_project(self, project_folder_path):
        """Opens a project.

        Args:
          project_folder_path: folder of the project which should be opened.

        """
        self.project_folder_path = project_folder_path
        self.media_file = file_util.get_file(self.project_folder_path,
                                             c.CON_COPY_POSTFIX)
        if self.media_file is None:
            self.hide()
            return
        self.media_player.setMedia(
            QMediaContent(QUrl.fromLocalFile(self.media_file)))

        self.transcription_path = file_util.get_file(self.project_folder_path,
                                                     c.TRANSCRIPTION)
        if self.transcription_path is None:
            self.hide()
            return
        with open(self.transcription_path, 'r') as f:
            text = f.read()
        self.text.setPlainText(text)
        self.transcription_meta_data = file_util.get_value_from_shelve(
            self.project_folder_path, c.TRANSCRIPTION_META_DATA)
        print(self.transcription_meta_data)

    def change_font(self, new_font, new_size):
        """Changes the font.

        Args:
          new_font: Name of the new font.
          new_size: New font size.

        """
        self.font = QFont(new_font)
        self.font.setPointSize(int(new_size))
        self.text.setFont(self.font)
        self.numbers.setFont(self.font)
        self.settings.setValue(c.FONT_SIZE, int(new_size))
        self.settings.setValue(c.FONT, new_font)

    def get_text_modules(self):
        """Gets the saved text_modules from the settings."""

        self.text_modules = self.settings.value(c.TEXT_MODULES,
                                                defaultValue={})

    def show_special_characters(self, bol):
        """Displays the special characters.

        Args:
          bol: true or false.

        """

        if bol:
            self.text.document().setDefaultTextOption(self.text_option_on)
        else:
            self.text.document().setDefaultTextOption(self.text_option_off)

    def on_word_by_word(self):
        """Selects the next or first word in the on word by word editing mode.

        For that purpose th word_postion is increased and the next word is marked via the textcursor.
        If everything works correctly the population of the list will be started.

        """

        self.word_pos += 1
        #if self.media_player.state() == QMediaPlayer.PlayingState:
        #    return

        if self.word_pos > len(self.text.toPlainText().split()) - 1:
            self.reset_word_by_word()
            return

        cursor = self.text.textCursor()
        if self.word_pos == 0:
            self.show_empty_buttons = self.settings.value(c.SHOW_EMPTY_BUTTONS,
                                                          defaultValue=True,
                                                          type=bool)
            cursor.setPosition(QTextCursor.Start, QTextCursor.MoveAnchor)
            cursor.movePosition(QTextCursor.StartOfWord,
                                QTextCursor.MoveAnchor)
            cursor.movePosition(QTextCursor.EndOfWord, QTextCursor.KeepAnchor)
            self.text.setEnabled(False)
        else:
            cursor.movePosition(QTextCursor.NextWord, QTextCursor.MoveAnchor)
            cursor.movePosition(QTextCursor.EndOfWord, QTextCursor.KeepAnchor)

        self.text.setTextCursor(cursor)

        selected_word = cursor.selectedText()

        if not selected_word:
            self.word_pos -= 1
            self.on_word_by_word()
            return

        # change to find all meta data
        meta_data_with_word = self.find_meta_data(selected_word)

        self.populate_word_actions(selected_word, meta_data_with_word)

    def on_word_by_word_prev(self):
        """Same as word for word but selects to the previous word."""

        if self.word_pos < 1:
            return

        self.word_pos -= 2

        cursor = self.text.textCursor()
        count = 0
        cursor.setPosition(QTextCursor.Start, QTextCursor.MoveAnchor)
        cursor.movePosition(QTextCursor.StartOfWord, QTextCursor.MoveAnchor)
        cursor.movePosition(QTextCursor.EndOfWord, QTextCursor.KeepAnchor)
        while count < self.word_pos:
            cursor.movePosition(QTextCursor.NextWord, QTextCursor.MoveAnchor)
            cursor.movePosition(QTextCursor.EndOfWord, QTextCursor.KeepAnchor)
            count += 1
        self.text.setTextCursor(cursor)
        self.on_word_by_word()

    def reset_word_by_word(self):
        """Resets the word by word editing mode and goes back to the normal editing."""

        self.word_pos = -1
        self.play_to = -1
        self.text.setEnabled(True)
        self.word_by_word_actions.clear()
        cleaned = self.text.textCursor()
        cleaned.clearSelection()
        self.text.setTextCursor(cleaned)

    def populate_word_actions(self, selected, word_meta_data):
        """Calls the plugin_manager to get alle the word for word buttons and initalize the hear again buttons.

        Args:
          selected: The selected word.
          word_meta_data: The meta_data fr the word.

        """

        self.word_by_word_actions.clear()
        if self.word_pos == len(self.text.toPlainText().split()):
            return

        self.plugin_manager.get_word_by_word_actions(selected, word_meta_data,
                                                     self.word_pos)

        btns = []
        for meta_data in word_meta_data:
            media_btn = HearButton(self, meta_data)
            btns.append(media_btn)
        self.add_new_word_by_word_action(btns, "Hear again", selected,
                                         self.word_pos)

    def add_new_word_by_word_action(self, btns, name, word, word_pos):
        """Adds a new word by word action.

        Args:
          btns: The buttons to add.
          name: The (plugin-)name of the buttons.
          word: The word for which these buttons are.
          word_pos: The word position.

        """

        if not self.show_empty_buttons and len(btns) == 0:
            return

        if self.word_pos != word_pos:
            print("old item", word, word_pos, self.word_pos)
            return

        group_item = QListWidgetItem()
        group_item.setFlags(Qt.ItemIsSelectable)
        label = QLabel(name)
        label.setFixedSize(self.word_by_word_actions.width() - 15, 30)
        label.setContentsMargins(5, 0, 0, 0)
        label.setWordWrap(True)
        group_item.setSizeHint(label.size())
        self.word_by_word_actions.addItem(group_item)
        self.word_by_word_actions.setItemWidget(group_item, label)

        for btn in btns:
            btn.setFixedSize(self.word_by_word_actions.width() - 15, 30)
            item = QListWidgetItem()
            item.setSizeHint(btn.size())
            item.setFlags(Qt.ItemIsSelectable)
            self.word_by_word_actions.addItem(item)
            self.word_by_word_actions.setItemWidget(item, btn)

    def find_meta_data(self, word):
        """Gets all the meta_data for the given word.

        Args:
          word: The word for which the meta_data should be found.

        Returns:
          The meta_data
        """

        meta_data_with_word = []

        for m_d in self.transcription_meta_data:
            if m_d.get(c.WORD) == word.lower():
                meta_data_with_word.append(m_d)

        return meta_data_with_word

    def replace_selection(self, new_word):
        """Replace the selection with the given word

        Args:
          new_word: The replacement.

        """

        cursor = self.text.textCursor()
        old_cursor_pos = cursor.position()

        cursor.insertText(new_word)
        cursor.setPosition(old_cursor_pos, QTextCursor.MoveAnchor)
        cursor.movePosition(QTextCursor.EndOfWord, QTextCursor.MoveAnchor)
        self.text.setTextCursor(cursor)
        self.word_by_word_actions.clear()

    def get_selection(self):
        """Returns the current selection

        Returns:
          The current selection.

        """

        return self.text.textCursor().selectedText()

    def get_text(self):
        """Returns the current text

        Returns:
          The current text.

        """

        return self.text.toPlainText()

    def set_text(self, new_text, restore_line_breaks=False):
        """Replace the text with the new text.

        Args:
          new_text: The new text.
          restore_line_breaks: If true, tries to restore the line breaks. (Default value = False)

        """

        cursor = self.text.textCursor()
        old_cursor_pos = cursor.position()

        if restore_line_breaks:
            self.set_text_with_line_breaks(new_text)
        else:
            self.text.setPlainText(new_text)

        cursor.setPosition(old_cursor_pos, QTextCursor.MoveAnchor)
        self.text.setTextCursor(cursor)

    def get_word_at(self, pos):
        """Returns the word at the given position.

        Args:
          pos: The position of the word.

        Returns:
          The word at the given position.

        """

        text = self.text.toPlainText().strip().split()

        if pos < 0 or pos > len(text):
            return None

        return text[pos % len(text)]

    def set_word_at(self, word, pos, replace_old):
        """Sets the word at the given position.

        Args:
          word: The replacement.
          pos: The position.
          replace_old: If true, the old word at the position will be replaced, otherwise the word will be set before the old word.

        """

        old_word = self.get_word_at(pos)
        cursor = self.text.textCursor()
        cursor_pos = cursor.position()

        if pos < 0:
            self.text.setPlainText(word + " " + self.text.toPlainText())
            cursor.setPosition(cursor_pos, QTextCursor.MoveAnchor)
            self.text.setTextCursor(cursor)
            return

        text = self.text.toPlainText().strip().split()
        if replace_old and pos < len(text):
            if word:
                text[pos] = word
            else:
                text.pop(pos)
        else:
            text.insert(pos, word)

        text = " ".join(text)
        self.set_text_with_line_breaks(text)

        cursor_pos += len(word)

        if replace_old:
            cursor_pos -= len(old_word)
            if not word:
                cursor_pos -= 1
        else:
            cursor_pos += 1

        words_to_cursor_pos = self.text.toPlainText()[:cursor_pos].split()
        self.word_pos = len(words_to_cursor_pos) - 1

        cursor.setPosition(cursor_pos, QTextCursor.MoveAnchor)
        cursor.movePosition(QTextCursor.StartOfWord, QTextCursor.MoveAnchor)
        self.text.setTextCursor(cursor)

    def find_line_breaks(self):
        """Returns the lien breaks in the text.

        Returns:
          The positions of the linebreaks

        """

        found = []
        start = 0
        text = self.text.toPlainText()
        while True:
            start = text.find("\n", start)
            if start == -1:
                return found
            found.append(start)
            start += len("\n")

    def set_text_with_line_breaks(self, text):
        """Sets the text with linebreaks.

        Args:
          text: the new text.

        """

        line_breaks = self.find_line_breaks()
        for n in line_breaks:
            text = text[:n + 1] + "\n" + text[n + 1:]

        text = text.replace(" \n", "\n")
        text = text.replace("\n ", "\n")
        self.text.setPlainText(text)

    def insert_time_stamp(self):
        """Inserts the current timestamp at the current cursor position."""

        cursor = self.text.textCursor()
        time = "[" + convert_ms(self.media_player.position()) + "]"
        cursor.insertText(time)

    def start_hear_again(self, start_time, end_time):
        """Starts the audio for the specific word from the hear again button.

        Args:
          start_time: When to start the audio.
          end_time: When to end the audio.

        """

        if self.media_player.state() == QMediaPlayer.PlayingState:
            return
        self.media_player.pause()
        self.word_start_time = start_time
        self.word_end_time = end_time
        self.media_player.setPosition(self.word_start_time)
        self.on_play()
Exemplo n.º 3
0
class MainWindow(QMainWindow):
    """Display video loop and controls"""
    audio_changed = Signal(str)
    def __init__(self, parent=None):
        super().__init__(parent)

        # Default values. Updated if found in config.JSON
        self.use_qt_thread = False
        self.rhythm_algorithm = "multifeature"
        self.default_device_name = ""
        self.show_video_preview = True
        self.video_loop_bpm = 60
        self.video_update_skip_ms = 100
        self.limit_tempo_by_default = False
        self.tempo_lower_limit = 60.0
        self.tempo_upper_limit = 120.0
        self.screen = 0

        self.spotify_track_id = ""

        self.read_config()

        self.setWindowTitle("Gandalf Enjoys Music")
        self.desktop = QApplication.desktop()

        self.audio = AudioDevice(self.default_device_name)
        self.input_devices = self.audio.get_input_device_names()

        self.audio_changed.connect(self.audio.change_audio_input)

        if self.use_qt_thread:
            self.bpm_extractor = BPMQt(self.update_bpm,
                                       algorithm=self.rhythm_algorithm)
        else:
            self.bpm_extractor = BPMmp(self.update_bpm,
                                       algorithm=self.rhythm_algorithm)

        self.audio.data_ready.connect(self.bpm_extractor.start_bpm_calculation)

        self.init_ui()

    def init_ui(self):
        dir_path = os.path.dirname(os.path.realpath(__file__))
        file_location = dir_path + "/resources/gandalf_icon_256px.png"
        self.icon_pixmap = QPixmap(file_location)
        self.icon = QIcon(self.icon_pixmap)
        self.setWindowIcon(self.icon)
        self.setWindowIconText("Gandalf Enjoys Music")

        self.central = QWidget(self)
        self.setCentralWidget(self.central)
        self.layout = QVBoxLayout()

        self.lock_checkbox = QCheckBox("Manual tempo", self)
        self.lock_checkbox.clicked.connect(self.update_lock_checkbox)

        self.limit_layout = QVBoxLayout()
        self.limit_checkbox = QCheckBox("Limit tempo between:", self)
        self.limit_checkbox.setChecked(self.limit_tempo_by_default)
        self.limit_checkbox.clicked.connect(self.update_bpm_manually)

        self.init_video()

        if self.show_video_preview:
            self.setFixedSize(QSize(500, 350))
            self.layout.addWidget(self.video_widget)
        else:
            self.setFixedSize(500, 100)
            self.fullscreen_button = QPushButton(self)
            self.fullscreen_button.setText("Go Fullscreen")
            self.layout.addWidget(self.fullscreen_button)
            self.fullscreen_button.clicked.connect(self.show_fullscreen)
            self.video_widget.fullscreen_changed.connect(
                self.update_button_text)

        self.video_widget.fullscreen_changed.connect(
            self.reset_video_position
        )

        self.tempo_control_layout = QVBoxLayout()
        self.tempo_control_layout.addWidget(self.lock_checkbox)

        self.set_bpm_widget = QLineEdit("{:.1f}".format(self.old_bpm), self)
        self.set_bpm_widget.setMaxLength(5)
        self.set_bpm_widget.returnPressed.connect(self.update_bpm_manually)
        self.set_bpm_palette = QPalette()
        self.set_bpm_palette.setColor(QPalette.Text, Qt.gray)
        self.set_bpm_widget.setPalette(self.set_bpm_palette)
        self.set_bpm_widget.setFixedWidth(50)
        self.tempo_control_layout.addWidget(self.set_bpm_widget)

        self.limit_layout.addWidget(self.limit_checkbox)

        self.limits = QHBoxLayout()

        self.lower_bpm_widget = QLineEdit(str(self.tempo_lower_limit), self)
        self.lower_bpm_widget.setMaxLength(5)
        self.lower_bpm_widget.returnPressed.connect(self.update_lower_limit)
        self.lower_bpm_widget.setFixedWidth(50)
        self.limits.addWidget(self.lower_bpm_widget)

        self.upper_bpm_widget = QLineEdit(str(self.tempo_upper_limit), self)
        self.upper_bpm_widget.setMaxLength(5)
        self.upper_bpm_widget.returnPressed.connect(self.update_upper_limit)
        self.upper_bpm_widget.setFixedWidth(50)
        self.limits.addWidget(self.upper_bpm_widget)
        self.limit_layout.addLayout(self.limits)

        self.control_layout = QHBoxLayout()
        self.control_layout.addLayout(self.tempo_control_layout)
        self.control_layout.addLayout(self.limit_layout)

        self.save_settings_button = QPushButton("Save settings", self)
        self.save_settings_button.clicked.connect(self.save_config)
        self.control_layout.addWidget(self.save_settings_button)

        self.layout.addLayout(self.control_layout)

        self.device_layout = QHBoxLayout()
        self.audio_select_label = QLabel("Audio device:", self)
        self.device_layout.addWidget(self.audio_select_label)

        self.audio_selection = QComboBox(self)
        self.audio_selection.addItems(self.input_devices)
        self.audio_selection.currentIndexChanged.connect(self.audio_selection_changed)
        self.device_layout.addWidget(self.audio_selection)

        self.layout.addLayout(self.device_layout)

        self.central.setLayout(self.layout)

    def init_video(self):
        self.old_bpm = 1.0

        self.video_widget = VideoWidget(self,
                                        self.show_video_preview,
                                        self.screen)
        self.media_player = QMediaPlayer(self.central)
        self.media_player.setVideoOutput(self.video_widget)

        self.playlist = QMediaPlaylist(self.media_player)
        dir_path = os.path.dirname(os.path.realpath(__file__))
        file_location = dir_path + "/resources/video_long.mp4"
        self.video_file = QUrl.fromLocalFile(file_location)
        self.playlist.addMedia(self.video_file)
        self.playlist.setPlaybackMode(QMediaPlaylist.Loop)
        self.playlist.setCurrentIndex(0)
        self.media_player.setPlaylist(self.playlist)
        self.media_player.mediaStatusChanged.connect(self.handle_media_state_changed)

        self.media_player.play()

        self.change_playback_rate(self.video_loop_bpm)

        if not self.show_video_preview:
            self.video_widget.hide()

    def handle_media_state_changed(self, state):
        if state == QMediaPlayer.MediaStatus.BufferedMedia:
            playback_speed = self.old_bpm / self.video_loop_bpm
            self.media_player.setPlaybackRate(playback_speed)
            self.media_player.setPosition(0)

    def change_playback_rate(self, bpm):
        """Update playback speed for video loop."""
        if bpm != self.old_bpm:
            # Prevent switching between double and half tempo during the same song in spotify
            track_id = get_spotify_track()
            if not self.lock_checkbox.isChecked()\
                    and not self.limit_checkbox.isChecked()\
                    and (math.isclose(bpm*2,self.old_bpm, rel_tol=3e-2)\
                    or math.isclose(bpm, self.old_bpm*2, rel_tol=3e-2))\
                    and track_id and track_id == self.spotify_track_id:
                self.spotify_track_id = track_id
                return
            self.spotify_track_id = track_id
            
            self.old_bpm = bpm
            playback_speed = bpm / self.video_loop_bpm

            # Workaround for a bug which causes irregular video playback speed
            # after changing playback rate
            current_position = self.media_player.position()
            self.media_player.setPlaybackRate(playback_speed)
            self.media_player.setPosition(current_position
                                          + self.video_update_skip_ms
                                          * playback_speed)

    def update_bpm(self, bpm, manual=False):
        if not manual:
            if self.lock_checkbox.isChecked():
                return
            bpm = float(int(bpm+0.5))
        if self.limit_checkbox.isChecked():
            while bpm < self.tempo_lower_limit:
                bpm = bpm * 2.0
            while bpm > self.tempo_upper_limit:
                bpm = bpm / 2.0
        self.change_playback_rate(bpm)
        self.set_bpm_widget.setText("{:.1f}".format(self.old_bpm))

    def update_bpm_manually(self):
        bpm = self.set_bpm_widget.text()
        try:
            bpm = float(bpm)
            if bpm < 1.0:
                raise ValueError
        except ValueError:
            return
        self.spotify_track_id = ""
        self.update_bpm(bpm, manual=True)

    def update_lock_checkbox(self):
        if self.lock_checkbox.isChecked():
            self.set_bpm_palette = QPalette()
            self.set_bpm_palette.setColor(QPalette.Text, Qt.black)
            self.set_bpm_widget.setPalette(self.set_bpm_palette)
            self.set_bpm_widget.setReadOnly(False)
        else:
            self.set_bpm_palette = QPalette()
            self.set_bpm_palette.setColor(QPalette.Text, Qt.gray)
            self.set_bpm_widget.setPalette(self.set_bpm_palette)
            self.set_bpm_widget.setReadOnly(True)

    def update_lower_limit(self, value=None):
        if not value:
            value = self.lower_bpm_widget.text()
        try:
            value = float(value)
            if value < 1.0:
                raise ValueError
        except ValueError:
            return
        if value <= self.tempo_upper_limit / 2.0:
            self.tempo_lower_limit = value
        else:
            self.tempo_lower_limit = self.tempo_upper_limit / 2.0
        self.lower_bpm_widget.setText("{:.1f}".format(self.tempo_lower_limit))

    def update_upper_limit(self, value=None):
        if not value:
            value = self.upper_bpm_widget.text()
        try:
            value = float(value)
            if value < 1.0:
                raise ValueError
        except ValueError:
            return
        if value >= self.tempo_lower_limit * 2.0:
            self.tempo_upper_limit = value
        else:
            self.tempo_upper_limit = self.tempo_lower_limit * 2.0
        self.upper_bpm_widget.setText("{:.1f}".format(self.tempo_upper_limit))

    def audio_selection_changed(self, idx):
        self.audio_changed.emit(self.audio_selection.currentText())

    @Slot()
    def show_fullscreen(self):
        self.reset_video_position()
        if self.video_widget.isFullScreen():
            self.video_widget.hide()
            self.fullscreen_button.setText("Go Fullscreen")
        else:
            self.video_widget.setFullScreen(True)
            self.video_widget.setGeometry(self.desktop.screenGeometry(self.screen))
            self.fullscreen_button.setText("Hide Fullscreen")

    @Slot()
    def reset_video_position(self):
        self.media_player.setPosition(0)

    @Slot(bool)
    def update_button_text(self, fullscreen_status):
        if fullscreen_status:
            self.fullscreen_button.setText("Hide Fullscreen")
        else:
            self.fullscreen_button.setText("Go Fullscreen")

    def read_config(self):
        with open("config.JSON") as config_file:
            config = json.load(config_file)

            if "no_multiprocess" in config:
                self.use_qt_thread = config["no_multiprocess"]
            if config.get("rhythm_algorithm_faster"):
                self.rhythm_algorithm = "degara"
            if config.get("default_device"):
                self.default_device_name = config["default_device"]
            if "show_video_preview" in config:
                self.show_video_preview = config.get("show_video_preview")
            if config.get("video_loop_bpm"):
                self.video_loop_bpm = config["video_loop_bpm"]
            if config.get("video_update_skip_time_ms"):
                self.video_update_skip_ms = config["video_update_skip_time_ms"]
            if config.get("limit_tempo_by_default"):
                self.limit_tempo_by_default = config["limit_tempo_by_default"]
            if config.get("tempo_lower_limit"):
                self.tempo_lower_limit = config["tempo_lower_limit"]
            if config.get("tempo_upper_limit"):
                self.tempo_upper_limit = config["tempo_upper_limit"]
            if "screen" in config:
                self.screen = config["screen"]

    @Slot()
    def save_config(self):
        fast_rhythm_algo = self.rhythm_algorithm == "degara"
        data = {
            "no_multiprocess": self.use_qt_thread,
            "rhythm_algorithm_faster": fast_rhythm_algo,
            "default_device": self.audio_selection.currentText(),
            "show_video_preview": self.show_video_preview,
            "video_loop_bpm": self.video_loop_bpm,
            "video_update_skip_time_ms": self.video_update_skip_ms,
            "limit_tempo_by_default": self.limit_checkbox.isChecked(),
            "tempo_lower_limit": self.tempo_lower_limit,
            "tempo_upper_limit": self.tempo_upper_limit,
            "screen": self.screen
        }
        with open("config.JSON", "w", encoding="utf-8") as f:
            json.dump(data, f, ensure_ascii=False, indent=4)