Exemplo n.º 1
0
class ExtractAudioDialog(QDialog, Ui_extractAudioDialog):
    '''
    Allows user to load a signal, processing it if necessary.
    '''
    def __init__(self,
                 parent,
                 preferences,
                 signal_model,
                 default_signal=None,
                 is_remux=False):
        super(ExtractAudioDialog, self).__init__(parent)
        self.setupUi(self)
        for f in COMPRESS_FORMAT_OPTIONS:
            self.audioFormat.addItem(f)
        self.showProbeButton.setIcon(qta.icon('fa5s.info'))
        self.showRemuxCommand.setIcon(qta.icon('fa5s.info'))
        self.inputFilePicker.setIcon(qta.icon('fa5s.folder-open'))
        self.targetDirPicker.setIcon(qta.icon('fa5s.folder-open'))
        self.calculateGainAdjustment.setIcon(qta.icon('fa5s.sliders-h'))
        self.limitRange.setIcon(qta.icon('fa5s.cut'))
        self.statusBar = QStatusBar()
        self.statusBar.setSizeGripEnabled(False)
        self.boxLayout.addWidget(self.statusBar)
        self.__preferences = preferences
        self.__signal_model = signal_model
        self.__default_signal = default_signal
        self.__executor = None
        self.__sound = None
        self.__extracted = False
        self.__stream_duration_micros = []
        self.__is_remux = is_remux
        if self.__is_remux:
            self.setWindowTitle('Remux Audio')
        self.showRemuxCommand.setVisible(self.__is_remux)
        defaultOutputDir = self.__preferences.get(EXTRACTION_OUTPUT_DIR)
        if os.path.isdir(defaultOutputDir):
            self.targetDir.setText(defaultOutputDir)
        self.__reinit_fields()
        self.filterMapping.itemDoubleClicked.connect(self.show_mapping_dialog)
        self.inputDrop.callback = self.__handle_drop

    def __handle_drop(self, file):
        if file.startswith('file:/'):
            file = url2pathname(urlparse(file).path)
        if os.path.exists(file) and os.path.isfile(file):
            self.inputFile.setText(file)
            self.__probe_file()

    def show_remux_cmd(self):
        ''' Pops the ffmpeg command into a message box '''
        if self.__executor is not None and self.__executor.filter_complex_script_content is not None:
            msg_box = QMessageBox()
            font = QFont()
            font.setFamily("Consolas")
            font.setPointSize(8)
            msg_box.setFont(font)
            msg_box.setText(
                self.__executor.filter_complex_script_content.replace(
                    ';', ';\n'))
            msg_box.setIcon(QMessageBox.Information)
            msg_box.setWindowTitle('Remux Script')
            msg_box.exec()

    def show_mapping_dialog(self, item):
        ''' Shows the edit mapping dialog '''
        if len(self.__signal_model) > 0 or self.__default_signal is not None:
            channel_idx = self.filterMapping.indexFromItem(item).row()
            mapped_filter = self.__executor.channel_to_filter.get(
                channel_idx, None)
            EditMappingDialog(self, channel_idx, self.__signal_model,
                              self.__default_signal, mapped_filter,
                              self.filterMapping.count(),
                              self.map_filter_to_channel).exec()

    def map_filter_to_channel(self, channel_idx, signal):
        ''' updates the mapping of the given signal to the specified channel idx '''
        if self.audioStreams.count() > 0 and self.__executor is not None:
            self.__executor.map_filter_to_channel(channel_idx, signal)
            self.__display_command_info()

    def selectFile(self):
        self.__reinit_fields()
        dialog = QFileDialog(parent=self)
        dialog.setFileMode(QFileDialog.ExistingFile)
        dialog.setWindowTitle('Select Audio or Video File')
        if dialog.exec():
            selected = dialog.selectedFiles()
            if len(selected) > 0:
                self.inputFile.setText(selected[0])
                self.__probe_file()

    def __reinit_fields(self):
        '''
        Resets various fields and temporary state.
        '''
        if self.__sound is not None:
            if not self.__sound.isFinished():
                self.__sound.stop()
                self.__sound = None
        self.audioStreams.clear()
        self.videoStreams.clear()
        self.statusBar.clearMessage()
        self.__executor = None
        self.__extracted = False
        self.__stream_duration_micros = []
        self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False)
        self.buttonBox.button(QDialogButtonBox.Ok).setText(
            'Remux' if self.__is_remux else 'Extract')
        if self.__is_remux:
            self.signalName.setVisible(False)
            self.signalNameLabel.setVisible(False)
            self.filterMapping.setVisible(True)
            self.filterMappingLabel.setVisible(True)
            self.includeOriginalAudio.setVisible(True)
            self.includeSubtitles.setVisible(True)
            self.gainOffset.setVisible(True)
            self.gainOffsetLabel.setVisible(True)
            self.gainOffset.setEnabled(False)
            self.gainOffsetLabel.setEnabled(False)
            self.calculateGainAdjustment.setVisible(True)
            self.calculateGainAdjustment.setEnabled(False)
            self.adjustRemuxedAudio.setVisible(True)
            self.remuxedAudioOffset.setVisible(True)
            self.adjustRemuxedAudio.setEnabled(False)
            self.remuxedAudioOffset.setEnabled(False)
        else:
            self.signalName.setText('')
            self.filterMapping.setVisible(False)
            self.filterMappingLabel.setVisible(False)
            self.includeOriginalAudio.setVisible(False)
            self.includeSubtitles.setVisible(False)
            self.gainOffset.setVisible(False)
            self.gainOffsetLabel.setVisible(False)
            self.calculateGainAdjustment.setVisible(False)
            self.adjustRemuxedAudio.setVisible(False)
            self.remuxedAudioOffset.setVisible(False)
        self.eacBitRate.setVisible(False)
        self.monoMix.setChecked(self.__preferences.get(EXTRACTION_MIX_MONO))
        self.bassManage.setChecked(False)
        self.decimateAudio.setChecked(
            self.__preferences.get(EXTRACTION_DECIMATE))
        self.includeOriginalAudio.setChecked(
            self.__preferences.get(EXTRACTION_INCLUDE_ORIGINAL))
        self.includeSubtitles.setChecked(
            self.__preferences.get(EXTRACTION_INCLUDE_SUBTITLES))
        if self.__preferences.get(EXTRACTION_COMPRESS):
            self.audioFormat.setCurrentText(COMPRESS_FORMAT_FLAC)
        else:
            self.audioFormat.setCurrentText(COMPRESS_FORMAT_NATIVE)
        self.monoMix.setEnabled(False)
        self.bassManage.setEnabled(False)
        self.decimateAudio.setEnabled(False)
        self.audioFormat.setEnabled(False)
        self.eacBitRate.setEnabled(False)
        self.includeOriginalAudio.setEnabled(False)
        self.includeSubtitles.setEnabled(False)
        self.inputFilePicker.setEnabled(True)
        self.audioStreams.setEnabled(False)
        self.videoStreams.setEnabled(False)
        self.channelCount.setEnabled(False)
        self.lfeChannelIndex.setEnabled(False)
        self.targetDirPicker.setEnabled(True)
        self.outputFilename.setEnabled(False)
        self.showProbeButton.setEnabled(False)
        self.filterMapping.setEnabled(False)
        self.filterMapping.clear()
        self.ffmpegCommandLine.clear()
        self.ffmpegCommandLine.setEnabled(False)
        self.ffmpegOutput.clear()
        self.ffmpegOutput.setEnabled(False)
        self.ffmpegProgress.setEnabled(False)
        self.ffmpegProgressLabel.setEnabled(False)
        self.ffmpegProgress.setValue(0)
        self.rangeFrom.setEnabled(False)
        self.rangeSeparatorLabel.setEnabled(False)
        self.rangeTo.setEnabled(False)
        self.limitRange.setEnabled(False)
        self.signalName.setEnabled(False)
        self.signalNameLabel.setEnabled(False)
        self.showRemuxCommand.setEnabled(False)

    def __probe_file(self):
        '''
        Probes the specified file using ffprobe in order to discover the audio streams.
        '''
        file_name = self.inputFile.text()
        self.__executor = Executor(
            file_name,
            self.targetDir.text(),
            mono_mix=self.monoMix.isChecked(),
            decimate_audio=self.decimateAudio.isChecked(),
            audio_format=self.audioFormat.currentText(),
            audio_bitrate=self.eacBitRate.value(),
            include_original=self.includeOriginalAudio.isChecked(),
            include_subtitles=self.includeSubtitles.isChecked(),
            signal_model=self.__signal_model if self.__is_remux else None,
            decimate_fs=self.__preferences.get(ANALYSIS_TARGET_FS),
            bm_fs=self.__preferences.get(BASS_MANAGEMENT_LPF_FS))
        self.__executor.progress_handler = self.__handle_ffmpeg_process
        from app import wait_cursor
        with wait_cursor(f"Probing {file_name}"):
            self.__executor.probe_file()
            self.showProbeButton.setEnabled(True)
        if self.__executor.has_audio():
            for a in self.__executor.audio_stream_data:
                text, duration_micros = parse_audio_stream(
                    self.__executor.probe, a)
                self.audioStreams.addItem(text)
                self.__stream_duration_micros.append(duration_micros)
            self.videoStreams.addItem('No Video')
            for a in self.__executor.video_stream_data:
                self.videoStreams.addItem(
                    parse_video_stream(self.__executor.probe, a))
            if self.__is_remux and self.videoStreams.count() > 1:
                if self.audioFormat.findText(COMPRESS_FORMAT_EAC3) == -1:
                    self.audioFormat.addItem(COMPRESS_FORMAT_EAC3)
                if self.__preferences.get(EXTRACTION_COMPRESS):
                    self.audioFormat.setCurrentText(COMPRESS_FORMAT_EAC3)
                    self.eacBitRate.setVisible(True)
                else:
                    self.audioFormat.setCurrentText(COMPRESS_FORMAT_NATIVE)
                    self.eacBitRate.setVisible(False)
                self.videoStreams.setCurrentIndex(1)
                self.adjustRemuxedAudio.setEnabled(True)
                self.remuxedAudioOffset.setEnabled(True)
                self.gainOffsetLabel.setEnabled(True)
                self.calculateGainAdjustment.setEnabled(True)
            self.audioStreams.setEnabled(True)
            self.videoStreams.setEnabled(True)
            self.channelCount.setEnabled(True)
            self.lfeChannelIndex.setEnabled(True)
            self.monoMix.setEnabled(True)
            self.bassManage.setEnabled(True)
            self.decimateAudio.setEnabled(True)
            self.audioFormat.setEnabled(True)
            self.eacBitRate.setEnabled(True)
            self.includeOriginalAudio.setEnabled(True)
            self.outputFilename.setEnabled(True)
            self.ffmpegCommandLine.setEnabled(True)
            self.filterMapping.setEnabled(True)
            self.limitRange.setEnabled(True)
            self.showRemuxCommand.setEnabled(True)
            self.__fit_options_to_selected()
        else:
            self.statusBar.showMessage(
                f"{file_name} contains no audio streams!")

    def onVideoStreamChange(self, idx):
        if idx == 0:
            eac_idx = self.audioFormat.findText(COMPRESS_FORMAT_EAC3)
            if eac_idx > -1:
                self.audioFormat.removeItem(eac_idx)
            if self.__preferences.get(EXTRACTION_COMPRESS):
                self.audioFormat.setCurrentText(COMPRESS_FORMAT_FLAC)
            else:
                self.audioFormat.setCurrentText(COMPRESS_FORMAT_NATIVE)
        else:
            if self.audioFormat.findText(COMPRESS_FORMAT_EAC3) == -1:
                self.audioFormat.addItem(COMPRESS_FORMAT_EAC3)
            if self.__preferences.get(EXTRACTION_COMPRESS):
                self.audioFormat.setCurrentText(COMPRESS_FORMAT_EAC3)
            else:
                self.audioFormat.setCurrentText(COMPRESS_FORMAT_NATIVE)
        self.updateFfmpegSpec()

    def updateFfmpegSpec(self):
        '''
        Creates a new ffmpeg command for the specified channel layout.
        '''
        if self.__executor is not None:
            self.__executor.update_spec(self.audioStreams.currentIndex(),
                                        self.videoStreams.currentIndex() - 1,
                                        self.monoMix.isChecked())

            self.__init_channel_count_fields(self.__executor.channel_count,
                                             lfe_index=self.__executor.lfe_idx)
            self.__fit_options_to_selected()
            self.__display_command_info()
            self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(True)

    def __fit_options_to_selected(self):
        # if we have no video then the output cannot contain multiple streams
        if self.videoStreams.currentIndex() == 0:
            self.includeOriginalAudio.setChecked(False)
            self.includeOriginalAudio.setEnabled(False)
            self.includeSubtitles.setChecked(False)
            self.includeSubtitles.setEnabled(False)
        else:
            self.includeOriginalAudio.setEnabled(True)
            self.includeSubtitles.setEnabled(True)
        # don't allow mono mix option if the stream is mono
        if self.channelCount.value() == 1:
            self.monoMix.setChecked(False)
            self.monoMix.setEnabled(False)
            self.bassManage.setChecked(False)
            self.bassManage.setEnabled(False)
        else:
            self.monoMix.setEnabled(True)
            # only allow bass management if we have an LFE channel
            if self.__executor.lfe_idx == 0:
                self.bassManage.setChecked(False)
                self.bassManage.setEnabled(False)
            else:
                self.bassManage.setEnabled(True)

    def __display_command_info(self):
        self.outputFilename.setText(self.__executor.output_file_name)
        self.ffmpegCommandLine.setPlainText(self.__executor.ffmpeg_cli)
        self.filterMapping.clear()
        for channel_idx, signal in self.__executor.channel_to_filter.items():
            self.filterMapping.addItem(
                f"Channel {channel_idx + 1} -> {signal.name if signal else 'Passthrough'}"
            )

    def updateOutputFilename(self):
        '''
        Updates the output file name.
        '''
        if self.__executor is not None:
            self.__executor.output_file_name = self.outputFilename.text()
            self.__display_command_info()

    def overrideFfmpegSpec(self, _):
        if self.__executor is not None:
            self.__executor.override('custom', self.channelCount.value(),
                                     self.lfeChannelIndex.value())
            self.__fit_options_to_selected()
            self.__display_command_info()

    def toggle_decimate_audio(self):
        '''
        Reacts to the change in decimation.
        '''
        if self.audioStreams.count() > 0 and self.__executor is not None:
            self.__executor.decimate_audio = self.decimateAudio.isChecked()
            self.__display_command_info()

    def toggle_bass_manage(self):
        '''
        Reacts to the change in bass management.
        '''
        if self.audioStreams.count() > 0 and self.__executor is not None:
            self.__executor.bass_manage = self.bassManage.isChecked()
            self.__display_command_info()

    def change_audio_format(self, audio_format):
        '''
        Reacts to the change in audio format.
        '''
        if self.audioStreams.count() > 0 and self.__executor is not None:
            self.__executor.audio_format = audio_format
            if audio_format == COMPRESS_FORMAT_EAC3:
                self.eacBitRate.setVisible(True)
                self.__executor.audio_bitrate = self.eacBitRate.value()
            else:
                self.eacBitRate.setVisible(False)
            self.__display_command_info()

    def change_audio_bitrate(self, bitrate):
        ''' Allows the bitrate to be updated '''
        if self.__executor is not None:
            self.__executor.audio_bitrate = bitrate
            self.__display_command_info()

    def update_original_audio(self):
        '''
        Reacts to the change in original audio selection.
        '''
        if self.audioStreams.count() > 0 and self.__executor is not None:
            if self.includeOriginalAudio.isChecked():
                self.__executor.include_original_audio = True
                self.__executor.original_audio_offset = self.gainOffset.value()
                self.gainOffset.setEnabled(True)
                self.gainOffsetLabel.setEnabled(True)
            else:
                self.__executor.include_original_audio = False
                self.__executor.original_audio_offset = 0.0
                self.gainOffset.setEnabled(False)
                self.gainOffsetLabel.setEnabled(False)
            self.__display_command_info()

    def toggle_include_subtitles(self):
        '''
        Reacts to the change in subtitles selection.
        '''
        if self.audioStreams.count() > 0 and self.__executor is not None:
            self.__executor.include_subtitles = self.includeSubtitles.isChecked(
            )
            self.__display_command_info()

    def toggleMonoMix(self):
        '''
        Reacts to the change in mono vs multichannel target.
        '''
        if self.audioStreams.count() > 0 and self.__executor is not None:
            self.__executor.mono_mix = self.monoMix.isChecked()
            self.__display_command_info()

    def toggle_range(self):
        ''' toggles whether the range is enabled or not '''
        if self.limitRange.isChecked():
            self.limitRange.setText('Cut')
            if self.audioStreams.count() > 0:
                duration_ms = int(self.__stream_duration_micros[
                    self.audioStreams.currentIndex()] / 1000)
                if duration_ms > 1:
                    from model.report import block_signals
                    with block_signals(self.rangeFrom):
                        self.rangeFrom.setTimeRange(
                            QTime.fromMSecsSinceStartOfDay(0),
                            QTime.fromMSecsSinceStartOfDay(duration_ms - 1))
                        self.rangeFrom.setTime(
                            QTime.fromMSecsSinceStartOfDay(0))
                        self.rangeFrom.setEnabled(True)
                    self.rangeSeparatorLabel.setEnabled(True)
                    with block_signals(self.rangeTo):
                        self.rangeTo.setEnabled(True)
                        self.rangeTo.setTimeRange(
                            QTime.fromMSecsSinceStartOfDay(1),
                            QTime.fromMSecsSinceStartOfDay(duration_ms))
                        self.rangeTo.setTime(
                            QTime.fromMSecsSinceStartOfDay(duration_ms))
        else:
            self.limitRange.setText('Enable')
            self.rangeFrom.setEnabled(False)
            self.rangeSeparatorLabel.setEnabled(False)
            self.rangeTo.setEnabled(False)
            if self.__executor is not None:
                self.__executor.start_time_ms = 0
                self.__executor.end_time_ms = 0

    def update_start_time(self, time):
        ''' Reacts to start time changes '''
        self.__executor.start_time_ms = time.msecsSinceStartOfDay()
        self.__display_command_info()

    def update_end_time(self, time):
        ''' Reacts to end time changes '''
        msecs = time.msecsSinceStartOfDay()
        duration_ms = int(
            self.__stream_duration_micros[self.audioStreams.currentIndex()] /
            1000)
        self.__executor.end_time_ms = msecs if msecs != duration_ms else 0
        self.__display_command_info()

    def __init_channel_count_fields(self, channels, lfe_index=0):
        from model.report import block_signals
        with block_signals(self.lfeChannelIndex):
            self.lfeChannelIndex.setMaximum(channels)
            self.lfeChannelIndex.setValue(lfe_index)
        with block_signals(self.channelCount):
            self.channelCount.setMaximum(channels)
            self.channelCount.setValue(channels)

    def reject(self):
        '''
        Stops any sound that is playing and exits.
        '''
        if self.__sound is not None and not self.__sound.isFinished():
            self.__sound.stop()
            self.__sound = None
        QDialog.reject(self)

    def accept(self):
        '''
        Executes the ffmpeg command.
        '''
        if self.__extracted is False:
            self.__extract()
            if not self.__is_remux:
                self.signalName.setEnabled(True)
                self.signalNameLabel.setEnabled(True)
        else:
            if self.__create_signals():
                QDialog.accept(self)

    def __create_signals(self):
        '''
        Creates signals from the output file just created.
        :return: True if we created the signals.
        '''
        loader = AutoWavLoader(self.__preferences)
        output_file = self.__executor.get_output_path()
        if os.path.exists(output_file):
            from app import wait_cursor
            with wait_cursor(f"Creating signals for {output_file}"):
                logger.info(f"Creating signals for {output_file}")
                name_provider = lambda channel, channel_count: get_channel_name(
                    self.signalName.text(),
                    channel,
                    channel_count,
                    channel_layout_name=self.__executor.channel_layout_name)
                loader.load(output_file)
                signal = loader.auto_load(name_provider,
                                          self.decimateAudio.isChecked())
                self.__signal_model.add(signal)
            return True
        else:
            msg_box = QMessageBox()
            msg_box.setText(
                f"Extracted audio file does not exist at: \n\n {output_file}")
            msg_box.setIcon(QMessageBox.Critical)
            msg_box.setWindowTitle('Unexpected Error')
            msg_box.exec()
            return False

    def __extract(self):
        '''
        Triggers the ffmpeg command.
        '''
        if self.__executor is not None:
            logger.info(
                f"Extracting {self.outputFilename.text()} from {self.inputFile.text()}"
            )
            self.__executor.execute()

    def __handle_ffmpeg_process(self, key, value):
        '''
        Handles progress reports from ffmpeg in order to communicate status via the progress bar. Used as a slot
        connected to a signal emitted by the AudioExtractor.
        :param key: the key.
        :param value: the value.
        '''
        if key == SIGNAL_CONNECTED:
            self.__extract_started()
        elif key == 'out_time_ms':
            out_time_ms = int(value)
            if self.__executor.start_time_ms > 0 and self.__executor.end_time_ms > 0:
                total_micros = (self.__executor.end_time_ms -
                                self.__executor.start_time_ms) * 1000
            elif self.__executor.end_time_ms > 0:
                total_micros = self.__executor.end_time_ms * 1000
            elif self.__executor.start_time_ms > 0:
                total_micros = self.__stream_duration_micros[
                    self.audioStreams.currentIndex()] - (
                        self.__executor.start_time_ms * 1000)
            else:
                total_micros = self.__stream_duration_micros[
                    self.audioStreams.currentIndex()]
            logger.debug(
                f"{self.inputFile.text()} -- {key}={value} vs {total_micros}")
            if total_micros > 0:
                progress = (out_time_ms / total_micros) * 100.0
                self.ffmpegProgress.setValue(math.ceil(progress))
                self.ffmpegProgress.setTextVisible(True)
                self.ffmpegProgress.setFormat(f"{round(progress, 2):.2f}%")
        elif key == SIGNAL_ERROR:
            self.__extract_complete(value, False)
        elif key == SIGNAL_COMPLETE:
            self.__extract_complete(value, True)

    def __extract_started(self):
        '''
        Changes the UI to signal that extraction has started
        '''
        self.inputFilePicker.setEnabled(False)
        self.audioStreams.setEnabled(False)
        self.videoStreams.setEnabled(False)
        self.channelCount.setEnabled(False)
        self.lfeChannelIndex.setEnabled(False)
        self.monoMix.setEnabled(False)
        self.bassManage.setEnabled(False)
        self.decimateAudio.setEnabled(False)
        self.audioFormat.setEnabled(False)
        self.eacBitRate.setEnabled(False)
        self.includeOriginalAudio.setEnabled(False)
        self.includeSubtitles.setEnabled(False)
        self.targetDirPicker.setEnabled(False)
        self.outputFilename.setEnabled(False)
        self.filterMapping.setEnabled(False)
        self.gainOffset.setEnabled(False)
        self.ffmpegOutput.setEnabled(True)
        self.ffmpegProgress.setEnabled(True)
        self.ffmpegProgressLabel.setEnabled(True)
        self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False)
        palette = QPalette(self.ffmpegProgress.palette())
        palette.setColor(QPalette.Highlight, QColor(Qt.green))
        self.ffmpegProgress.setPalette(palette)

    def __extract_complete(self, result, success):
        '''
        triggered when the extraction thread completes.
        '''
        if self.__executor is not None:
            if success:
                logger.info(
                    f"Extraction complete for {self.outputFilename.text()}")
                self.ffmpegProgress.setValue(100)
                self.__extracted = True
                if not self.__is_remux:
                    self.signalName.setEnabled(True)
                    self.signalNameLabel.setEnabled(True)
                    self.signalName.setText(
                        Path(self.outputFilename.text()).resolve().stem)
                    self.buttonBox.button(
                        QDialogButtonBox.Ok).setText('Create Signals')
            else:
                logger.error(
                    f"Extraction failed for {self.outputFilename.text()}")
                palette = QPalette(self.ffmpegProgress.palette())
                palette.setColor(QPalette.Highlight, QColor(Qt.red))
                self.ffmpegProgress.setPalette(palette)
                self.statusBar.showMessage('Extraction failed', 5000)

            self.ffmpegOutput.setPlainText(result)
            self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(True)
            audio = self.__preferences.get(EXTRACTION_NOTIFICATION_SOUND)
            if audio is not None:
                logger.debug(f"Playing {audio}")
                self.__sound = QSound(audio)
                self.__sound.play()

    def showProbeInDetail(self):
        '''
        shows a tree widget containing the contents of the probe to allow the raw probe info to be visible.
        '''
        if self.__executor is not None:
            ViewProbeDialog(self.inputFile.text(),
                            self.__executor.probe,
                            parent=self).exec()

    def setTargetDirectory(self):
        '''
        Sets the target directory based on the user selection.
        '''
        dialog = QFileDialog(parent=self)
        dialog.setFileMode(QFileDialog.DirectoryOnly)
        dialog.setWindowTitle(f"Select Output Directory")
        if dialog.exec():
            selected = dialog.selectedFiles()
            if len(selected) > 0:
                self.targetDir.setText(selected[0])
                if self.__executor is not None:
                    self.__executor.target_dir = selected[0]
                    self.__display_command_info()

    def override_filtered_gain_adjustment(self, val):
        ''' forces the gain adjustment to a specific value. '''
        if self.__executor is not None:
            self.__executor.filtered_audio_offset = val

    def calculate_gain_adjustment(self):
        '''
        Based on the filters applied, calculates the gain adjustment that is required to avoid clipping.
        '''
        filts = list(set(self.__executor.channel_to_filter.values()))
        if len(filts) > 1 or filts[0] is not None:
            from app import wait_cursor
            with wait_cursor():
                headroom = min([
                    min(
                        self.__calc_headroom(
                            x.filter_signal(filt=True, clip=False).samples),
                        0.0) for x in filts if x is not None
                ])
            self.remuxedAudioOffset.setValue(headroom)

    @staticmethod
    def __calc_headroom(samples):
        return 20 * math.log(1.0 / np.nanmax(np.abs(samples)), 10)
Exemplo n.º 2
0
def _main(args):
    # To avoid problems when testing without screen
    if args.test or args.screenshots:
        os.environ['QT_QPA_PLATFORM'] = 'offscreen'

    # Set QT_API variable before importing QtPy
    if args.qt_from in ['pyqt', 'pyqt5', 'pyside', 'pyside2']:
        os.environ['QT_API'] = args.qt_from
    elif args.qt_from == 'pyqtgraph':
        os.environ['QT_API'] = os.environ['PYQTGRAPH_QT_LIB']
    elif args.qt_from in ['qt.py', 'qt']:
        try:
            import Qt
        except ImportError:
            print('Could not import Qt (Qt.Py)')
        else:
            os.environ['QT_API'] = Qt.__binding__

    # QtPy imports
    from qtpy import API_NAME, QT_VERSION, PYQT_VERSION, PYSIDE_VERSION
    from qtpy import __version__ as QTPY_VERSION
    from qtpy.QtWidgets import (QApplication, QMainWindow, QDockWidget,
                                QStatusBar, QLabel, QMenu)
    from qtpy.QtCore import QTimer, Qt, QSettings

    # Set API_VERSION variable
    API_VERSION = ''

    if PYQT_VERSION:
        API_VERSION = PYQT_VERSION
    elif PYSIDE_VERSION:
        API_VERSION = PYSIDE_VERSION
    else:
        API_VERSION = 'Not found'

    # Import examples UI
    from mw_menus_ui import Ui_MainWindow as ui_main

    from dw_buttons_ui import Ui_DockWidget as ui_buttons
    from dw_displays_ui import Ui_DockWidget as ui_displays
    from dw_inputs_fields_ui import Ui_DockWidget as ui_inputs_fields
    from dw_inputs_no_fields_ui import Ui_DockWidget as ui_inputs_no_fields

    from dw_widgets_ui import Ui_DockWidget as ui_widgets
    from dw_views_ui import Ui_DockWidget as ui_views
    from dw_containers_tabs_ui import Ui_DockWidget as ui_containers_tabs
    from dw_containers_no_tabs_ui import Ui_DockWidget as ui_containers_no_tabs

    # qrainbowstyle.useDarwinButtons()

    QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps)
    QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)

    # create the application
    if not QApplication.instance():
        app = QApplication(sys.argv)
    else:
        app = QApplication.instance()
    app.setOrganizationName('QRainbowStyle')
    app.setApplicationName('QRainbowStyle Example')

    styles = qrainbowstyle.getAvailableStyles()

    style = args.style
    if not args.style:
        style = styles[random.randint(0, len(styles)) - 1]

    app.setStyleSheet(qrainbowstyle.load_stylesheet(style=str(style)))

    # create main window
    window = qrainbowstyle.windows.FramelessWindow()
    window.setTitlebarHeight(30)

    widget = QMainWindow(window)
    widget.setWindowFlags(Qt.Widget)
    widget.setObjectName('mainwindow')
    ui = ui_main()
    ui.setupUi(widget)

    window.addContentWidget(widget)

    title = ("QRainbowStyle Example - "
             + "(QRainbowStyle=v" + qrainbowstyle.__version__
             + ", QtPy=v" + QTPY_VERSION
             + ", " + API_NAME + "=v" + API_VERSION
             + ", Qt=v" + QT_VERSION
             + ", Python=v" + platform.python_version() + ")")

    _logger.info(title)

    window.setWindowTitle(title)

    # Create docks for buttons
    dw_buttons = QDockWidget()
    dw_buttons.setObjectName('buttons')
    ui_buttons = ui_buttons()
    ui_buttons.setupUi(dw_buttons)
    widget.addDockWidget(Qt.RightDockWidgetArea, dw_buttons)

    # Add actions on popup toolbuttons
    menu = QMenu()

    for action in ['Action A', 'Action B', 'Action C']:
        menu.addAction(action)

    ui_buttons.toolButtonDelayedPopup.setMenu(menu)
    ui_buttons.toolButtonInstantPopup.setMenu(menu)
    ui_buttons.toolButtonMenuButtonPopup.setMenu(menu)

    # Create docks for buttons
    dw_displays = QDockWidget()
    dw_displays.setObjectName('displays')
    ui_displays = ui_displays()
    ui_displays.setupUi(dw_displays)
    widget.addDockWidget(Qt.RightDockWidgetArea, dw_displays)

    # Create docks for inputs - no fields
    dw_inputs_no_fields = QDockWidget()
    dw_inputs_no_fields.setObjectName('inputs_no_fields')
    ui_inputs_no_fields = ui_inputs_no_fields()
    ui_inputs_no_fields.setupUi(dw_inputs_no_fields)
    widget.addDockWidget(Qt.RightDockWidgetArea, dw_inputs_no_fields)

    # Create docks for inputs - fields
    dw_inputs_fields = QDockWidget()
    dw_inputs_fields.setObjectName('inputs_fields')
    ui_inputs_fields = ui_inputs_fields()
    ui_inputs_fields.setupUi(dw_inputs_fields)
    widget.addDockWidget(Qt.RightDockWidgetArea, dw_inputs_fields)

    # Create docks for widgets
    dw_widgets = QDockWidget()
    dw_widgets.setObjectName('widgets')
    ui_widgets = ui_widgets()
    ui_widgets.setupUi(dw_widgets)
    widget.addDockWidget(Qt.LeftDockWidgetArea, dw_widgets)

    # Create docks for views
    dw_views = QDockWidget()
    dw_views.setObjectName('views')
    ui_views = ui_views()
    ui_views.setupUi(dw_views)
    widget.addDockWidget(Qt.LeftDockWidgetArea, dw_views)

    # Create docks for containers - no tabs
    dw_containers_no_tabs = QDockWidget()
    dw_containers_no_tabs.setObjectName('containers_no_tabs')
    ui_containers_no_tabs = ui_containers_no_tabs()
    ui_containers_no_tabs.setupUi(dw_containers_no_tabs)
    widget.addDockWidget(Qt.LeftDockWidgetArea, dw_containers_no_tabs)

    # Create docks for containters - tabs
    dw_containers_tabs = QDockWidget()
    dw_containers_tabs.setObjectName('containers_tabs')
    ui_containers_tabs = ui_containers_tabs()
    ui_containers_tabs.setupUi(dw_containers_tabs)
    widget.addDockWidget(Qt.LeftDockWidgetArea, dw_containers_tabs)

    # Tabify right docks
    widget.tabifyDockWidget(dw_buttons, dw_displays)
    widget.tabifyDockWidget(dw_displays, dw_inputs_fields)
    widget.tabifyDockWidget(dw_inputs_fields, dw_inputs_no_fields)

    # Tabify left docks
    widget.tabifyDockWidget(dw_containers_no_tabs, dw_containers_tabs)
    widget.tabifyDockWidget(dw_containers_tabs, dw_widgets)
    widget.tabifyDockWidget(dw_widgets, dw_views)

    # Issues #9120, #9121 on Spyder
    qstatusbar = QStatusBar()
    qstatusbar.addWidget(QLabel('Style'))
    qstatusbarbutton = qrainbowstyle.widgets.StylePickerHorizontal()
    qstatusbar.addWidget(qstatusbarbutton)
    qstatusbar.setSizeGripEnabled(False)

    # Add info also in status bar for screenshots get it
    qstatusbar.addWidget(QLabel('INFO: ' + title))
    widget.setStatusBar(qstatusbar)

    # Todo: add report info and other info in HELP graphical

    # Auto quit after 2s when in test mode
    if args.test:
        QTimer.singleShot(2000, app.exit)

    _read_settings(widget, args.reset, QSettings)
    window.show()
    # window.showMaximized()

    app.exec_()
    _write_settings(widget, QSettings)