Exemple #1
0
 def allowed_formats(self, allowed_formats=None):
     disable_dups = "nvencc" in self.main.convert_to.lower()
     tracks_need_removed = False
     for track in self.tracks:
         track.widgets.dup_button.setDisabled(disable_dups)
         if not track.original:
             if disable_dups:
                 tracks_need_removed = True
         else:
             if disable_dups:
                 track.widgets.dup_button.hide()
             else:
                 track.widgets.dup_button.show()
     if tracks_need_removed:
         error_message(
             t("This encoder does not support duplicating audio tracks, please remove copied tracks!"
               ))
     if not allowed_formats:
         return
     for track in self.tracks:
         track.update_codecs(allowed_formats or set())
Exemple #2
0
def time_to_number(string_time: str) -> float:
    try:
        return float(string_time)
    except ValueError:
        pass
    base, *extra = string_time.split(".")
    micro = 0
    if extra and len(extra) == 1:
        try:
            micro = int(extra[0])
        except ValueError:
            logger.info(t("bad micro value"))
            return
    total = float(f".{micro}")
    for i, v in enumerate(reversed(base.split(":"))):
        try:
            v = int(v)
        except ValueError:
            logger.info(f"{t('Not a valid int for time conversion')}: {v}")
        else:
            total += v * (60**i)
    return total
 def init_level(self):
     layout = self._add_combo_box(
         label="Level",
         tooltip="Set the encoding level restriction",
         widget_name="level",
         options=[
             t("Auto"),
             "1.0",
             "2.0",
             "2.1",
             "3.0",
             "3.1",
             "4.0",
             "4.1",
             "5.0",
             "5.1",
             "5.2",
         ],
         opt="level",
     )
     self.widgets.level.setMinimumWidth(60)
     return layout
Exemple #4
0
 def download_ffmpeg(self):
     ffmpeg_folder = Path(
         user_data_dir("FFmpeg", appauthor=False, roaming=True)) / "bin"
     ffmpeg = ffmpeg_folder / "ffmpeg.exe"
     ffprobe = ffmpeg_folder / "ffprobe.exe"
     try:
         ProgressBar(self.app,
                     [Task(t("Downloading FFmpeg"), latest_ffmpeg)],
                     signal_task=True,
                     can_cancel=True)
     except FastFlixInternalException:
         pass
     except Exception as err:
         message(f"{t('Could not download the newest FFmpeg')}: {err}")
     else:
         if not ffmpeg.exists() or not ffprobe.exists():
             message(
                 f"{t('Could not locate the downloaded files at')} {ffmpeg_folder}!"
             )
         else:
             self.app.fastflix.config.ffmpeg = ffmpeg
             self.app.fastflix.config.ffprobe = ffprobe
    def init_dhdr10_info(self):
        layout = self._add_file_select(
            label="HDR10+ Metadata",
            widget_name="hdr10plus_metadata",
            button_action=lambda: self.dhdr10_update(),
            tooltip="dhdr10_info: Path to HDR10+ JSON metadata file",
        )
        self.labels["hdr10plus_metadata"].setFixedWidth(200)
        self.extract_button = QtWidgets.QPushButton(t("Extract HDR10+"))
        self.extract_button.hide()
        self.extract_button.clicked.connect(self.extract_hdr10plus)

        self.extract_label = QtWidgets.QLabel(self)
        self.extract_label.hide()
        self.movie = QtGui.QMovie(loading_movie)
        self.movie.setScaledSize(QtCore.QSize(25, 25))
        self.extract_label.setMovie(self.movie)

        layout.addWidget(self.extract_button)
        layout.addWidget(self.extract_label)

        return layout
Exemple #6
0
    def __init__(
        self,
        app: QtWidgets.QApplication,
        tasks: List[Task],
        signal_task: bool = False,
        auto_run: bool = True,
        can_cancel: bool = False,
    ):
        super().__init__(None)
        self.app = app

        self.tasks = tasks
        self.signal_task = signal_task
        self.cancelled = False

        self.setObjectName("ProgressBar")
        self.setStyleSheet("#ProgressBar{border: 1px solid #aaa}")

        self.setMinimumWidth(400)
        self.setWindowFlags(QtCore.Qt.SplashScreen
                            | QtCore.Qt.FramelessWindowHint)

        self.status = QtWidgets.QLabel()
        self.progress_bar = QtWidgets.QProgressBar(self)
        self.progress_bar.setGeometry(30, 40, 500, 75)

        self.layout = QtWidgets.QVBoxLayout()
        self.layout.addWidget(self.status)
        self.layout.addWidget(self.progress_bar)
        if can_cancel:
            cancel_button = QtWidgets.QPushButton(t("Cancel"))
            cancel_button.clicked.connect(self.cancel)
            self.layout.addWidget(cancel_button)
        self.setLayout(self.layout)

        self.show()
        if auto_run:
            self.run()
 def update_cover(self, cover_path=None):
     if cover_path:
         cover = str(cover_path)
     else:
         cover = self.cover_path.text().strip()
     if not cover:
         self.poster.setPixmap(QtGui.QPixmap())
         self.update_cover_settings()
         self.main.page_update(build_thumbnail=False)
         return
     if (not Path(cover).exists() or not Path(cover).is_file()
             or not cover.lower().endswith((".jpg", ".png", ".jpeg"))):
         return
     try:
         pixmap = QtGui.QPixmap(cover)
         pixmap = pixmap.scaled(230, 230, QtCore.Qt.KeepAspectRatio)
         self.poster.setPixmap(pixmap)
     except Exception:
         logger.exception(t("Bad image"))
         self.cover_path.setText("")
     else:
         self.update_cover_settings()
         self.main.page_update(build_thumbnail=False)
    def _add_check_box(self, label, widget_name, opt, connect="default", enabled=True, checked=True, tooltip=""):
        layout = QtWidgets.QHBoxLayout()
        # self.labels[widget_name] = QtWidgets.QLabel()
        # self.labels[widget_name].setToolTip()

        self.widgets[widget_name] = QtWidgets.QCheckBox(t(label))
        self.opts[widget_name] = opt
        self.widgets[widget_name].setChecked(self.app.fastflix.config.encoder_opt(self.profile_name, opt))
        self.widgets[widget_name].setDisabled(not enabled)
        if tooltip:
            self.widgets[widget_name].setToolTip(self.translate_tip(tooltip))
        if connect:
            if connect == "default":
                self.widgets[widget_name].toggled.connect(lambda: self.main.page_update(build_thumbnail=False))
            elif connect == "self":
                self.widgets[widget_name].toggled.connect(lambda: self.page_update())
            else:
                self.widgets[widget_name].toggled.connect(connect)

        # layout.addWidget(self.labels[widget_name])
        layout.addWidget(self.widgets[widget_name])

        return layout
    def __init__(self, parent, main, app: FastFlixApp):
        super().__init__(parent, main, app)
        self.main = main
        self.app = app

        grid = QtWidgets.QGridLayout()

        self.widgets = Box(fps=None, mode=None, segment_size=None)

        self.mode = "QP"

        grid.addLayout(self.init_preset(), 0, 0, 1, 2)
        grid.addLayout(self.init_pix_fmt(), 1, 0, 1, 2)
        grid.addLayout(self.init_tile_rows(), 2, 0, 1, 2)
        grid.addLayout(self.init_tile_columns(), 3, 0, 1, 2)
        grid.addLayout(self.init_tier(), 4, 0, 1, 2)
        grid.addLayout(self.init_qp_or_crf(), 5, 0, 1, 2)
        grid.addLayout(self.init_sc_detection(), 6, 0, 1, 2)
        grid.addLayout(self.init_max_mux(), 7, 0, 1, 2)
        grid.addLayout(self.init_modes(), 0, 2, 5, 4)
        grid.addLayout(self.init_single_pass(), 6, 2, 1, 1)
        grid.addLayout(self.init_svtav1_params(), 5, 2, 1, 4)

        grid.setRowStretch(8, 1)
        guide_label = QtWidgets.QLabel(
            link(
                "https://github.com/AOMediaCodec/SVT-AV1/blob/master/Docs/svt-av1_encoder_user_guide.md",
                t("SVT-AV1 Encoding Guide"),
                app.fastflix.config.theme,
            ))
        guide_label.setAlignment(QtCore.Qt.AlignBottom)
        guide_label.setOpenExternalLinks(True)
        grid.addLayout(self._add_custom(), 10, 0, 1, 6)
        grid.addWidget(guide_label, 11, 0, -1, 1)
        self.setLayout(grid)
        self.hide()
Exemple #10
0
    def save(self):
        new_ffmpeg = Path(self.ffmpeg_path.text())
        new_ffprobe = Path(self.ffprobe_path.text())
        new_work_dir = Path(self.work_dir.text())
        try:
            updated_ffmpeg = self.update_ffmpeg(new_ffmpeg)
            self.update_ffprobe(new_ffprobe)
        except FastFlixInternalException:
            return

        try:
            new_work_dir.mkdir(exist_ok=True, parents=True)
        except OSError:
            error_message(
                f'Could not create / access work directory "{new_work_dir}"')
        else:
            self.app.fastflix.config.work_path = new_work_dir
        self.app.fastflix.config.use_sane_audio = self.use_sane_audio.isChecked(
        )

        old_lang = self.app.fastflix.config.language
        try:
            self.app.fastflix.config.language = Lang(
                self.language_combo.currentText()).pt3
        except InvalidLanguageValue:
            error_message(
                f"Could not set language to {self.language_combo.currentText()}\n Please report this issue"
            )
        self.app.fastflix.config.disable_version_check = self.disable_version_check.isChecked(
        )

        self.main.config_update()
        self.app.fastflix.config.save()
        if updated_ffmpeg or old_lang != self.app.fastflix.config.language:
            error_message(t("Please restart FastFlix to apply settings"))
        self.close()
Exemple #11
0
    def __init__(self, advanced_settings):
        super().__init__()

        layout = QtWidgets.QVBoxLayout()
        self.label = QtWidgets.QLabel()

        self.color_primaries_widget = QtWidgets.QComboBox()
        self.color_primaries_widget.addItem(t("Unspecified"))
        self.color_primaries_widget.addItems(ffmpeg_valid_color_primaries)

        self.color_transfer_widget = QtWidgets.QComboBox()
        self.color_transfer_widget.addItem(t("Unspecified"))
        self.color_transfer_widget.addItems(ffmpeg_valid_color_transfers)

        self.color_space_widget = QtWidgets.QComboBox()
        self.color_space_widget.addItem(t("Unspecified"))
        self.color_space_widget.addItems(ffmpeg_valid_color_space)

        primaries_layout = QtWidgets.QHBoxLayout()
        primaries_layout.addWidget(QtWidgets.QLabel(t("Color Primaries")))
        primaries_layout.addWidget(self.color_primaries_widget)

        transfer_layout = QtWidgets.QHBoxLayout()
        transfer_layout.addWidget(QtWidgets.QLabel(t("Color Transfer")))
        transfer_layout.addWidget(self.color_transfer_widget)

        space_layout = QtWidgets.QHBoxLayout()
        space_layout.addWidget(QtWidgets.QLabel(t("Color Space")))
        space_layout.addWidget(self.color_space_widget)

        layout.addLayout(primaries_layout)
        layout.addLayout(transfer_layout)
        layout.addLayout(space_layout)
        layout.addStretch(1)
        layout.addWidget(self.label)
        layout.addStretch(1)
        self.text_update(advanced_settings)
        self.setLayout(layout)
Exemple #12
0
    def __init__(self, parent, main, app: FastFlixApp):
        super().__init__(parent, main, app)
        self.main = main
        self.app = app

        grid = QtWidgets.QGridLayout()

        self.widgets = Box(mode=None)

        self.mode = "Bitrate"
        self.updating_settings = False

        grid.addLayout(self.init_modes(), 0, 2, 4, 4)
        grid.addLayout(
            self._add_custom(title="Custom VCEEncC options",
                             disable_both_passes=True), 10, 0, 1, 6)
        grid.addLayout(self.init_preset(), 0, 0, 1, 2)
        grid.addLayout(self.init_profile(), 1, 0, 1, 2)
        grid.addLayout(self.init_mv_precision(), 2, 0, 1, 2)
        grid.addLayout(self.init_pre(), 3, 0, 1, 2)

        breaker = QtWidgets.QHBoxLayout()
        breaker_label = QtWidgets.QLabel(t("Advanced"))
        breaker_label.setFont(QtGui.QFont("helvetica", 8, weight=55))

        breaker.addWidget(get_breaker(), stretch=1)
        breaker.addWidget(breaker_label, alignment=QtCore.Qt.AlignHCenter)
        breaker.addWidget(get_breaker(), stretch=1)

        grid.addLayout(breaker, 4, 0, 1, 6)

        qp_line = QtWidgets.QHBoxLayout()
        qp_line.addLayout(self.init_min_q())
        qp_line.addStretch(1)
        qp_line.addLayout(self.init_max_q())
        qp_line.addStretch(1)
        qp_line.addLayout(self.init_ref())
        qp_line.addStretch(1)
        qp_line.addLayout(self.init_b_frames())
        qp_line.addStretch(1)
        qp_line.addLayout(self.init_level())
        qp_line.addStretch(1)
        qp_line.addLayout(self.init_decoder())
        qp_line.addStretch(1)
        qp_line.addLayout(self.init_metrics())
        grid.addLayout(qp_line, 5, 0, 1, 6)

        self.ffmpeg_level = QtWidgets.QLabel()
        grid.addWidget(self.ffmpeg_level, 8, 2, 1, 4)

        grid.setRowStretch(9, 1)

        guide_label = QtWidgets.QLabel(
            link(
                "https://github.com/rigaya/VCEEnc/blob/master/VCEEncC_Options.en.md",
                t("VCEEncC Options"),
                app.fastflix.config.theme,
            ))

        warning_label = QtWidgets.QLabel()
        warning_label.setPixmap(
            QtGui.QIcon(
                get_icon("onyx-warning",
                         self.app.fastflix.config.theme)).pixmap(22))

        guide_label.setAlignment(QtCore.Qt.AlignBottom)
        guide_label.setOpenExternalLinks(True)
        grid.addWidget(guide_label, 11, 0, 1, 4)
        grid.addWidget(warning_label,
                       11,
                       4,
                       1,
                       1,
                       alignment=QtCore.Qt.AlignRight)
        grid.addWidget(
            QtWidgets.QLabel(
                t("VCEEncC Encoder support is still experimental!")), 11, 5, 1,
            1)

        self.setLayout(grid)
        self.hide()
        self.hdr10plus_signal.connect(self.done_hdr10plus_extract)
        self.hdr10plus_ffmpeg_signal.connect(
            lambda x: self.ffmpeg_level.setText(x))
Exemple #13
0
    def __init__(self, app: FastFlixApp, main, *args, **kwargs):
        super().__init__(None, *args, **kwargs)
        self.app = app
        self.main = main
        self.config_file = self.app.fastflix.config.config_path
        self.setWindowTitle(t("Settings"))
        self.setMinimumSize(600, 200)
        layout = QtWidgets.QGridLayout()

        ffmpeg_label = QtWidgets.QLabel("FFmpeg")
        self.ffmpeg_path = QtWidgets.QLineEdit()
        self.ffmpeg_path.setText(str(self.app.fastflix.config.ffmpeg))
        ffmpeg_path_button = QtWidgets.QPushButton(
            icon=self.style().standardIcon(QtWidgets.QStyle.SP_DirIcon))
        ffmpeg_path_button.clicked.connect(lambda: self.select_ffmpeg())
        layout.addWidget(ffmpeg_label, 0, 0)
        layout.addWidget(self.ffmpeg_path, 0, 1)
        layout.addWidget(ffmpeg_path_button, 0, 2)

        ffprobe_label = QtWidgets.QLabel("FFprobe")
        self.ffprobe_path = QtWidgets.QLineEdit()
        self.ffprobe_path.setText(str(self.app.fastflix.config.ffprobe))
        ffprobe_path_button = QtWidgets.QPushButton(
            icon=self.style().standardIcon(QtWidgets.QStyle.SP_DirIcon))
        ffprobe_path_button.clicked.connect(lambda: self.select_ffprobe())
        layout.addWidget(ffprobe_label, 1, 0)
        layout.addWidget(self.ffprobe_path, 1, 1)
        layout.addWidget(ffprobe_path_button, 1, 2)

        work_dir_label = QtWidgets.QLabel(t("Work Directory"))
        self.work_dir = QtWidgets.QLineEdit()
        self.work_dir.setText(str(self.app.fastflix.config.work_path))
        work_path_button = QtWidgets.QPushButton(
            icon=self.style().standardIcon(QtWidgets.QStyle.SP_DirIcon))
        work_path_button.clicked.connect(lambda: self.select_work_path())
        layout.addWidget(work_dir_label, 2, 0)
        layout.addWidget(self.work_dir, 2, 1)
        layout.addWidget(work_path_button, 2, 2)

        layout.addWidget(QtWidgets.QLabel(t("Config File")), 4, 0)
        layout.addWidget(QtWidgets.QLabel(str(self.config_file)), 4, 1)

        self.language_combo = QtWidgets.QComboBox(self)
        self.language_combo.addItems(known_language_list)
        try:
            index = known_language_list.index(
                Lang(self.app.fastflix.config.language).name)
        except (IndexError, InvalidLanguageValue):
            logger.exception(
                f"{t('Could not find language for')} {self.app.fastflix.config.language}"
            )
            index = known_language_list.index("English")
        self.language_combo.setCurrentIndex(index)

        layout.addWidget(QtWidgets.QLabel(t("Language")), 5, 0)
        layout.addWidget(self.language_combo, 5, 1)

        config_button = QtWidgets.QPushButton(
            icon=self.style().standardIcon(QtWidgets.QStyle.SP_FileIcon))
        config_button.clicked.connect(lambda: QtGui.QDesktopServices.openUrl(
            QtCore.QUrl.fromLocalFile(str(self.config_file))))
        layout.addWidget(config_button, 4, 2)

        save = QtWidgets.QPushButton(icon=self.style().standardIcon(
            QtWidgets.QStyle.SP_DialogApplyButton),
                                     text=t("Save"))
        save.clicked.connect(lambda: self.save())

        cancel = QtWidgets.QPushButton(icon=self.style().standardIcon(
            QtWidgets.QStyle.SP_DialogCancelButton),
                                       text=t("Cancel"))
        cancel.clicked.connect(lambda: self.close())

        self.use_sane_audio = QtWidgets.QCheckBox(
            t("Use Sane Audio Selection (updatable in config file)"))
        if self.app.fastflix.config.use_sane_audio:
            self.use_sane_audio.setChecked(True)
        self.disable_version_check = QtWidgets.QCheckBox(
            t("Disable update check on startup"))
        if not self.app.fastflix.config.disable_version_check:
            self.disable_version_check.setChecked(False)
        elif self.app.fastflix.config.disable_version_check:
            self.disable_version_check.setChecked(True)

        self.logger_level_widget = QtWidgets.QComboBox()
        self.logger_level_widget.addItems(
            ["Debug", "Info", "Warning", "Error"])
        self.logger_level_widget.setCurrentIndex(
            int(self.app.fastflix.config.logging_level // 10) - 1)

        self.theme = QtWidgets.QComboBox()
        self.theme.addItems(["onyx", "light", "dark", "system"])
        self.theme.setCurrentText(self.app.fastflix.config.theme)

        self.crop_detect_points_widget = QtWidgets.QComboBox()
        self.crop_detect_points_widget.addItems(possible_detect_points)

        try:
            self.crop_detect_points_widget.setCurrentIndex(
                possible_detect_points.index(
                    str(self.app.fastflix.config.crop_detect_points)))
        except ValueError:
            self.crop_detect_points_widget.setCurrentIndex(5)

        nvencc_label = QtWidgets.QLabel("NVEncC")
        self.nvencc_path = QtWidgets.QLineEdit()
        if self.app.fastflix.config.nvencc:
            self.nvencc_path.setText(str(self.app.fastflix.config.nvencc))
        nvenc_path_button = QtWidgets.QPushButton(
            icon=self.style().standardIcon(QtWidgets.QStyle.SP_DirIcon))
        nvenc_path_button.clicked.connect(lambda: self.select_nvencc())
        layout.addWidget(nvencc_label, 12, 0)
        layout.addWidget(self.nvencc_path, 12, 1)
        layout.addWidget(nvenc_path_button, 12, 2)

        vceenc_label = QtWidgets.QLabel("VCEEncC")
        self.vceenc_path = QtWidgets.QLineEdit()
        if self.app.fastflix.config.vceencc:
            self.vceenc_path.setText(str(self.app.fastflix.config.vceencc))
        vceenc_path_button = QtWidgets.QPushButton(
            icon=self.style().standardIcon(QtWidgets.QStyle.SP_DirIcon))
        vceenc_path_button.clicked.connect(lambda: self.select_vceenc())
        layout.addWidget(vceenc_label, 13, 0)
        layout.addWidget(self.vceenc_path, 13, 1)
        layout.addWidget(vceenc_path_button, 13, 2)

        qsvencc_label = QtWidgets.QLabel("QSVEncC")
        self.qsvenc_path = QtWidgets.QLineEdit()
        if self.app.fastflix.config.qsvencc:
            self.qsvenc_path.setText(str(self.app.fastflix.config.qsvencc))
        qsvencc_path_button = QtWidgets.QPushButton(
            icon=self.style().standardIcon(QtWidgets.QStyle.SP_DirIcon))
        qsvencc_path_button.clicked.connect(lambda: self.select_qsvencc())
        layout.addWidget(qsvencc_label, 14, 0)
        layout.addWidget(self.qsvenc_path, 14, 1)
        layout.addWidget(qsvencc_path_button, 14, 2)

        hdr10_parser_label = QtWidgets.QLabel(t("HDR10+ Parser"))
        self.hdr10_parser_path = QtWidgets.QLineEdit()
        if self.app.fastflix.config.hdr10plus_parser:
            self.hdr10_parser_path.setText(
                str(self.app.fastflix.config.hdr10plus_parser))
        hdr10_parser_path_button = QtWidgets.QPushButton(
            icon=self.style().standardIcon(QtWidgets.QStyle.SP_DirIcon))
        hdr10_parser_path_button.clicked.connect(
            lambda: self.select_hdr10_parser())
        layout.addWidget(hdr10_parser_label, 15, 0)
        layout.addWidget(self.hdr10_parser_path, 15, 1)
        layout.addWidget(hdr10_parser_path_button, 15, 2)

        layout.addWidget(self.use_sane_audio, 7, 0, 1, 2)
        layout.addWidget(self.disable_version_check, 8, 0, 1, 2)
        layout.addWidget(QtWidgets.QLabel(t("GUI Logging Level")), 9, 0)
        layout.addWidget(self.logger_level_widget, 9, 1)
        layout.addWidget(QtWidgets.QLabel(t("Theme")), 10, 0)
        layout.addWidget(self.theme, 10, 1)
        layout.addWidget(QtWidgets.QLabel(t("Crop Detect Points")), 11, 0, 1,
                         1)
        layout.addWidget(self.crop_detect_points_widget, 11, 1, 1, 1)

        button_layout = QtWidgets.QHBoxLayout()
        button_layout.addStretch()
        button_layout.addWidget(cancel)
        button_layout.addWidget(save)

        layout.addLayout(button_layout, 17, 0, 1, 3)

        self.setLayout(layout)
Exemple #14
0
    def save(self):
        new_ffmpeg = Path(self.ffmpeg_path.text())
        new_ffprobe = Path(self.ffprobe_path.text())
        new_work_dir = Path(self.work_dir.text())
        restart_needed = False
        try:
            updated_ffmpeg = self.update_ffmpeg(new_ffmpeg)
            self.update_ffprobe(new_ffprobe)
        except FastFlixInternalException:
            return

        try:
            new_work_dir.mkdir(exist_ok=True, parents=True)
        except OSError:
            error_message(
                f'{t("Could not create / access work directory")} "{new_work_dir}"'
            )
        else:
            self.app.fastflix.config.work_path = new_work_dir
        self.app.fastflix.config.use_sane_audio = self.use_sane_audio.isChecked(
        )
        if self.theme.currentText() != self.app.fastflix.config.theme:
            restart_needed = True
        self.app.fastflix.config.theme = self.theme.currentText()

        old_lang = self.app.fastflix.config.language
        try:
            self.app.fastflix.config.language = Lang(
                self.language_combo.currentText()).pt3
        except InvalidLanguageValue:
            error_message(
                f"{t('Could not set language to')} {self.language_combo.currentText()}\n {t('Please report this issue')}"
            )
        self.app.fastflix.config.disable_version_check = self.disable_version_check.isChecked(
        )
        log_level = (self.logger_level_widget.currentIndex() + 1) * 10
        self.app.fastflix.config.logging_level = log_level
        logger.setLevel(log_level)
        self.app.fastflix.config.crop_detect_points = int(
            self.crop_detect_points_widget.currentText())

        new_nvencc = Path(
            self.nvencc_path.text()) if self.nvencc_path.text() else None
        if self.app.fastflix.config.nvencc != new_nvencc:
            restart_needed = True
        self.app.fastflix.config.nvencc = new_nvencc

        new_qsvencc = Path(
            self.qsvenc_path.text()) if self.qsvenc_path.text() else None
        if self.app.fastflix.config.qsvencc != new_qsvencc:
            restart_needed = True
        self.app.fastflix.config.qsvencc = new_qsvencc

        new_vce = Path(
            self.vceenc_path.text()) if self.vceenc_path.text() else None
        if self.app.fastflix.config.vceencc != new_vce:
            restart_needed = True
        self.app.fastflix.config.vceencc = new_vce

        new_hdr10_parser = Path(self.hdr10_parser_path.text()
                                ) if self.hdr10_parser_path.text() else None
        if self.app.fastflix.config.hdr10plus_parser != new_hdr10_parser:
            restart_needed = True
        self.app.fastflix.config.hdr10plus_parser = new_hdr10_parser

        self.main.config_update()
        self.app.fastflix.config.save()
        if updated_ffmpeg or old_lang != self.app.fastflix.config.language or restart_needed:
            error_message(t("Please restart FastFlix to apply settings"))
        self.close()
    def __init__(self, parent, video: Video, index, first=False):
        self.loading = True
        super().__init__(parent)
        self.parent = parent
        self.index = index
        self.first = first
        self.last = False
        self.video = video.copy()
        self.setFixedHeight(60)

        self.widgets = Box(
            up_button=QtWidgets.QPushButton(
                QtGui.QIcon(
                    get_icon("up-arrow",
                             self.parent.app.fastflix.config.theme)), ""),
            down_button=QtWidgets.QPushButton(
                QtGui.QIcon(
                    get_icon("down-arrow",
                             self.parent.app.fastflix.config.theme)), ""),
            cancel_button=QtWidgets.QPushButton(
                QtGui.QIcon(
                    get_icon("black-x",
                             self.parent.app.fastflix.config.theme)), ""),
            reload_button=QtWidgets.QPushButton(
                QtGui.QIcon(
                    get_icon("edit-box",
                             self.parent.app.fastflix.config.theme)), ""),
            retry_button=QtWidgets.QPushButton(
                QtGui.QIcon(
                    get_icon("undo", self.parent.app.fastflix.config.theme)),
                ""),
        )

        for widget in self.widgets.values():
            widget.setStyleSheet(no_border)

        title = QtWidgets.QLabel(
            video.video_settings.video_title if video.video_settings.
            video_title else video.video_settings.output_path.name)
        title.setFixedWidth(300)

        settings = Box(copy.deepcopy(video.video_settings.dict()))
        settings.output_path = str(settings.output_path)
        for i, o in enumerate(settings.attachment_tracks):
            if o.get("file_path"):
                o["file_path"] = str(o["file_path"])
        del settings.conversion_commands

        title.setToolTip(settings.to_yaml())

        open_button = QtWidgets.QPushButton(
            QtGui.QIcon(get_icon("play",
                                 self.parent.app.fastflix.config.theme)),
            t("Open Directory"))
        open_button.setLayoutDirection(QtCore.Qt.RightToLeft)
        open_button.setIconSize(QtCore.QSize(14, 14))
        open_button.clicked.connect(
            lambda: open_folder(video.video_settings.output_path.parent))

        view_button = QtWidgets.QPushButton(
            QtGui.QIcon(get_icon("play",
                                 self.parent.app.fastflix.config.theme)),
            t("Watch"))
        view_button.setLayoutDirection(QtCore.Qt.RightToLeft)
        view_button.setIconSize(QtCore.QSize(14, 14))
        view_button.clicked.connect(lambda: QtGui.QDesktopServices.openUrl(
            QtCore.QUrl.fromLocalFile(str(video.video_settings.output_path))))

        open_button.setStyleSheet(no_border)
        view_button.setStyleSheet(no_border)

        add_retry = False
        status = t("Ready to encode")
        if video.status.error:
            status = t("Encoding errored")
        elif video.status.complete:
            status = f"{t('Encoding complete')}"
        elif video.status.running:
            status = (
                f"{t('Encoding command')} {video.status.current_command + 1} {t('of')} "
                f"{len(video.video_settings.conversion_commands)}")
        elif video.status.cancelled:
            status = t("Cancelled")
            add_retry = True

        if not self.video.status.running:
            self.widgets.cancel_button.clicked.connect(
                lambda: self.parent.remove_item(self.video))
            self.widgets.reload_button.clicked.connect(
                lambda: self.parent.reload_from_queue(self.video))
            self.widgets.cancel_button.setFixedWidth(25)
            self.widgets.reload_button.setFixedWidth(25)
        else:
            self.widgets.cancel_button.hide()
            self.widgets.reload_button.hide()

        grid = QtWidgets.QGridLayout()
        grid.addLayout(self.init_move_buttons(), 0, 0)
        # grid.addWidget(self.widgets.track_number, 0, 1)
        grid.addWidget(title, 0, 1, 1, 3)
        grid.addWidget(
            QtWidgets.QLabel(
                f"{video.video_settings.video_encoder_settings.name}"), 0, 4)
        grid.addWidget(
            QtWidgets.QLabel(
                f"{t('Audio Tracks')}: {len(video.video_settings.audio_tracks)}"
            ), 0, 5)
        grid.addWidget(
            QtWidgets.QLabel(
                f"{t('Subtitles')}: {len(video.video_settings.subtitle_tracks)}"
            ), 0, 6)
        grid.addWidget(QtWidgets.QLabel(status), 0, 7)
        if video.status.complete and not get_bool_env("FF_DOCKERMODE"):
            grid.addWidget(view_button, 0, 8)
            grid.addWidget(open_button, 0, 9)
        elif add_retry:
            grid.addWidget(self.widgets.retry_button, 0, 8)
            self.widgets.retry_button.setFixedWidth(25)
            self.widgets.retry_button.clicked.connect(
                lambda: self.parent.retry_video(self.video))

        right_buttons = QtWidgets.QHBoxLayout()
        right_buttons.addWidget(self.widgets.reload_button)
        right_buttons.addWidget(self.widgets.cancel_button)

        grid.addLayout(right_buttons, 0, 10, alignment=QtCore.Qt.AlignRight)

        self.setLayout(grid)
        self.loading = False
        self.updating_burn = False
    def __init__(self, parent, app: FastFlixApp):
        self.main = parent.main
        self.app = app
        self.paused = False
        self.encode_paused = False
        self.encoding = False
        top_layout = QtWidgets.QHBoxLayout()

        top_layout.addWidget(QtWidgets.QLabel(t("Queue")))
        top_layout.addStretch(1)

        self.clear_queue = QtWidgets.QPushButton(
            QtGui.QIcon(
                get_icon("onyx-clear-queue", self.app.fastflix.config.theme)),
            t("Clear Completed"))
        self.clear_queue.clicked.connect(self.clear_complete)
        self.clear_queue.setFixedWidth(120)
        self.clear_queue.setToolTip(t("Remove completed tasks"))

        self.pause_queue = QtWidgets.QPushButton(
            QtGui.QIcon(get_icon("onyx-pause",
                                 self.app.fastflix.config.theme)),
            t("Pause Queue"))
        self.pause_queue.clicked.connect(self.pause_resume_queue)
        # pause_queue.setFixedHeight(40)
        self.pause_queue.setFixedWidth(120)
        self.pause_queue.setToolTip(
            t("Wait for the current command to finish,"
              " and stop the next command from processing"))

        self.pause_encode = QtWidgets.QPushButton(
            QtGui.QIcon(get_icon("onyx-pause",
                                 self.app.fastflix.config.theme)),
            t("Pause Encode"))
        self.pause_encode.clicked.connect(self.pause_resume_encode)
        # pause_queue.setFixedHeight(40)
        self.pause_encode.setFixedWidth(120)
        self.pause_encode.setToolTip(t("Pause / Resume the current command"))

        self.after_done_combo = QtWidgets.QComboBox()
        self.after_done_combo.addItem("None")
        actions = set()
        if reusables.win_based:
            actions.update(done_actions["windows"].keys())

        elif sys.platform == "darwin":
            actions.update(["shutdown", "restart"])
        else:
            actions.update(done_actions["linux"].keys())
        if self.app.fastflix.config.custom_after_run_scripts:
            actions.update(self.app.fastflix.config.custom_after_run_scripts)

        self.after_done_combo.addItems(sorted(actions))
        self.after_done_combo.setToolTip(
            "Run a command after conversion completes")
        self.after_done_combo.currentIndexChanged.connect(
            lambda: self.set_after_done())
        self.after_done_combo.setMaximumWidth(150)
        top_layout.addWidget(QtWidgets.QLabel(t("After Conversion")))
        top_layout.addWidget(self.after_done_combo, QtCore.Qt.AlignRight)
        top_layout.addWidget(self.pause_encode, QtCore.Qt.AlignRight)
        top_layout.addWidget(self.pause_queue, QtCore.Qt.AlignRight)
        top_layout.addWidget(self.clear_queue, QtCore.Qt.AlignRight)

        super().__init__(app,
                         parent,
                         t("Queue"),
                         "queue",
                         top_row_layout=top_layout)
        try:
            self.queue_startup_check()
        except Exception:
            logger.exception(
                "Could not load queue as it is outdated or malformed. Deleting for safety."
            )
            save_queue([],
                       queue_file=self.app.fastflix.queue_path,
                       config=self.app.fastflix.config)
 def __init__(self, parent, app: FastFlixApp):
     super(AudioList, self).__init__(app, parent, t("Audio Tracks"),
                                     "audio")
     self.available_audio_encoders = app.fastflix.audio_encoders
     self.app = app
     self._first_selected = False
Exemple #18
0
def queue_worker(gui_proc, worker_queue, status_queue, log_queue):
    runner = BackgroundRunner(log_queue=log_queue)

    # Command looks like (video_uuid, command_uuid, command, work_dir)
    after_done_command = ""
    commands_to_run = []
    gui_died = False
    currently_encoding = False
    paused = False
    log_path = Path(user_data_dir("FastFlix", appauthor=False,
                                  roaming=True)) / "logs"
    after_done_path = Path(
        user_data_dir("FastFlix", appauthor=False,
                      roaming=True)) / "after_done_logs"

    def start_command():
        nonlocal currently_encoding
        log_queue.put(
            f"CLEAR_WINDOW:{commands_to_run[0][0]}:{commands_to_run[0][1]}")
        reusables.remove_file_handlers(logger)
        new_file_handler = reusables.get_file_handler(
            log_path /
            f"flix_conversion_{commands_to_run[0][4]}_{file_date()}.log",
            level=logging.DEBUG,
            log_format="%(asctime)s - %(message)s",
            encoding="utf-8",
        )
        logger.addHandler(new_file_handler)
        prevent_sleep_mode()
        currently_encoding = True
        status_queue.put(
            ("running", commands_to_run[0][0], commands_to_run[0][1]))
        runner.start_exec(
            commands_to_run[0][2],
            work_dir=commands_to_run[0][3],
        )

    while True:
        if currently_encoding and not runner.is_alive():
            reusables.remove_file_handlers(logger)
            if runner.error_detected:
                logger.info(t("Error detected while converting"))

                # Stop working!
                currently_encoding = False
                status_queue.put(
                    ("error", commands_to_run[0][0], commands_to_run[0][1]))
                commands_to_run = []
                allow_sleep_mode()
                if gui_died:
                    return
                continue

            # Successfully encoded, do next one if it exists
            # First check if the current video has more commands
            logger.info(t("Command has completed"))
            status_queue.put(
                ("converted", commands_to_run[0][0], commands_to_run[0][1]))
            commands_to_run.pop(0)
            if commands_to_run:
                if not paused:
                    logger.info(t("starting next command"))
                    start_command()
                else:
                    currently_encoding = False
                    allow_sleep_mode()
                    logger.debug(t("Queue has been paused"))
                continue
            else:
                logger.info(t("all conversions complete"))
                # Finished the queue
                # fastflix.current_encoding = None
                currently_encoding = False
                status_queue.put(("complete", ))
                allow_sleep_mode()
                if after_done_command:
                    logger.info(
                        f"{t('Running after done command:')} {after_done_command}"
                    )
                    try:
                        runner.start_exec(after_done_command,
                                          str(after_done_path))
                    except Exception:
                        logger.exception(
                            "Error occurred while running after done command")
                        continue
            if gui_died:
                return

        if not gui_died and not gui_proc.is_alive():
            gui_proc.join()
            gui_died = True
            if runner.is_alive() or currently_encoding:
                logger.info(
                    t("The GUI might have died, but I'm going to keep converting!"
                      ))
            else:
                logger.debug(t("Conversion worker shutting down"))
                return

        try:
            request = worker_queue.get(block=True, timeout=0.05)
        except Empty:
            continue
        except KeyboardInterrupt:
            status_queue.put(("exit", ))
            allow_sleep_mode()
            return
        else:
            if request[0] == "add_items":

                # Request looks like (queue command, log_dir, (commands))
                log_path = Path(request[1])
                for command in request[2]:
                    if command not in commands_to_run:
                        logger.debug(
                            t(f"Adding command to the queue for {command[4]} - {command[2]}"
                              ))
                        commands_to_run.append(command)
                    # else:
                    #     logger.debug(t(f"Command already in queue: {command[1]}"))
                if not runner.is_alive() and not paused:
                    logger.debug(
                        t("No encoding is currently in process, starting encode"
                          ))
                    start_command()
            if request[0] == "cancel":
                logger.debug(t("Cancel has been requested, killing encoding"))
                runner.kill()
                currently_encoding = False
                allow_sleep_mode()
                status_queue.put(("cancelled", commands_to_run[0][0],
                                  commands_to_run[0][1]))
                commands_to_run = []
            if request[0] == "pause queue":
                logger.debug(
                    t("Command worker received request to pause encoding after the current item completes"
                      ))
                paused = True
            if request[0] == "resume queue":
                paused = False
                logger.debug(
                    t("Command worker received request to resume encoding"))
                if commands_to_run and not runner.is_alive():
                    start_command()
            if request[0] == "set after done":
                after_done_command = request[1]
                if after_done_command:
                    logger.debug(
                        f'{t("Setting after done command to:")} {after_done_command}'
                    )
                else:
                    logger.debug(t("Removing after done command"))
            if request[0] == "pause encode":
                logger.debug(
                    t("Command worker received request to pause current encode"
                      ))
                try:
                    runner.pause()
                except Exception:
                    logger.exception("Could not pause command")
                else:
                    status_queue.put(("paused encode", commands_to_run[0][0],
                                      commands_to_run[0][1]))
            if request[0] == "resume encode":
                logger.debug(
                    t("Command worker received request to resume paused encode"
                      ))
                try:
                    runner.resume()
                except Exception:
                    logger.exception("Could not resume command")
                else:
                    status_queue.put(("resumed encode", commands_to_run[0][0],
                                      commands_to_run[0][1]))
Exemple #19
0
 def reset_pause_encode(self):
     self.pause_encode.setText(t("Pause Encode"))
     self.pause_encode.setIcon(self.app.style().standardIcon(
         QtWidgets.QStyle.SP_MediaPause))
     self.encode_paused = False
Exemple #20
0
    def __init__(self, parent, main, app: FastFlixApp):
        super().__init__(parent, main, app)
        self.main = main
        self.app = app

        grid = QtWidgets.QGridLayout()

        self.mode = "CRF"
        self.updating_settings = False
        self.extract_thread = None

        grid.addLayout(self.init_preset(), 0, 0, 1, 2)
        grid.addLayout(self.init_tune(), 1, 0, 1, 2)
        grid.addLayout(self.init_profile(), 2, 0, 1, 2)
        grid.addLayout(self.init_pix_fmt(), 3, 0, 1, 2)
        grid.addLayout(self.init_modes(), 0, 2, 5, 4)

        breaker = QtWidgets.QHBoxLayout()
        breaker_label = QtWidgets.QLabel(t("Advanced"))
        breaker_label.setFont(QtGui.QFont("helvetica", 8, weight=55))

        breaker.addWidget(get_breaker(), stretch=1)
        breaker.addWidget(breaker_label, alignment=QtCore.Qt.AlignHCenter)
        breaker.addWidget(get_breaker(), stretch=1)

        grid.addLayout(breaker, 5, 0, 1, 6)

        grid.addLayout(self.init_aq_mode(), 6, 0, 1, 2)
        grid.addLayout(self.init_frame_threads(), 7, 0, 1, 2)
        grid.addLayout(self.init_max_mux(), 8, 0, 1, 2)
        grid.addLayout(self.init_x265_row(), 6, 2, 1, 4)
        grid.addLayout(self.init_x265_row_two(), 7, 2, 1, 4)
        # grid.addLayout(self.init_hdr10_opt(), 5, 2, 1, 1)
        # grid.addLayout(self.init_repeat_headers(), 5, 3, 1, 1)
        # grid.addLayout(self.init_aq_mode(), 5, 4, 1, 2)

        grid.addLayout(self.init_x265_params(), 8, 2, 1, 4)

        grid.addLayout(self.init_dhdr10_info(), 9, 2, 1, 3)
        grid.addLayout(self.init_dhdr10_warning_and_opt(), 9, 5, 1, 1)
        self.ffmpeg_level = QtWidgets.QLabel()
        grid.addWidget(self.ffmpeg_level, 10, 2, 1, 4)

        grid.setRowStretch(11, True)

        grid.addLayout(self._add_custom(), 12, 0, 1, 6)

        link_1 = link(
            "https://trac.ffmpeg.org/wiki/Encode/H.265",
            t("FFMPEG HEVC / H.265 Encoding Guide"),
        )
        link_2 = link(
            "https://codecalamity.com/encoding-uhd-4k-hdr10-videos-with-ffmpeg",
            t("CodeCalamity UHD HDR Encoding Guide"),
        )
        link_3 = link(
            "https://github.com/cdgriffith/FastFlix/wiki/HDR10-Plus-Metadata-Extraction",
            t("HDR10+ Metadata Extraction"),
        )

        guide_label = QtWidgets.QLabel(f"{link_1} | {link_2} | {link_3}")
        guide_label.setAlignment(QtCore.Qt.AlignBottom)
        guide_label.setOpenExternalLinks(True)

        grid.addWidget(guide_label, 13, 0, 1, 6)

        self.hdr10plus_signal.connect(self.done_hdr10plus_extract)
        self.hdr10plus_ffmpeg_signal.connect(
            lambda x: self.ffmpeg_level.setText(x))
        self.setLayout(grid)
        self.hide()
def latest_ffmpeg(signal, stop_signal, **_):
    stop = False

    def stop_me():
        nonlocal stop
        stop = True

    stop_signal.connect(stop_me)
    ffmpeg_folder = Path(user_data_dir("FFmpeg", appauthor=False, roaming=True))
    ffmpeg_folder.mkdir(exist_ok=True)
    url = "https://api.github.com/repos/BtbN/FFmpeg-Builds/releases/latest"

    try:
        data = requests.get(url, timeout=15).json()
    except Exception:
        message(t("Could not connect to github to check for newer versions."))
        raise

    if stop:
        message(t("Download Cancelled"))
        return

    gpl_ffmpeg = [asset for asset in data["assets"] if asset["name"].endswith("win64-gpl.zip")]
    if not gpl_ffmpeg:
        message(
            t("Could not find any matching FFmpeg ending with 'win64-gpl.zip' with")
            + f" {t('latest release from')} <a href='https://github.com/BtbN/FFmpeg-Builds/releases/'>"
            "https://github.com/BtbN/FFmpeg-Builds/releases/</a> "
        )
        raise

    req = requests.get(gpl_ffmpeg[0]["browser_download_url"], stream=True)

    filename = ffmpeg_folder / "ffmpeg-full.zip"
    with open(filename, "wb") as f:
        for i, block in enumerate(req.iter_content(chunk_size=1024)):
            if i % 1000 == 0.0:
                # logger.debug(f"Downloaded {i // 1000}MB")
                signal.emit(int(((i * 1024) / gpl_ffmpeg[0]["size"]) * 90))
            f.write(block)
            if stop:
                f.close()
                Path(filename).unlink()
                message(t("Download Cancelled"))
                return

    if filename.stat().st_size < 1000:
        message(t("FFmpeg was not properly downloaded as the file size is too small"))
        try:
            Path(filename).unlink()
        except OSError:
            pass
        raise

    try:
        reusables.extract(filename, path=ffmpeg_folder)
    except Exception:
        message(f"{t('Could not extract FFmpeg files from')} {filename}!")
        raise

    if stop:
        Path(filename).unlink()
        message(t("Download Cancelled"))
        return

    signal.emit(95)

    try:
        shutil.rmtree(str(ffmpeg_folder / "bin"), ignore_errors=True)
        shutil.rmtree(str(ffmpeg_folder / "doc"), ignore_errors=True)
        Path(filename).unlink()
    except OSError:
        pass

    signal.emit(96)
    sub_dir = next(Path(ffmpeg_folder).glob("ffmpeg-*"))

    for item in os.listdir(sub_dir):
        try:
            shutil.move(str(sub_dir / item), str(ffmpeg_folder))
        except Exception as err:
            message(f"{t('Error while moving files in')} {ffmpeg_folder}: {err}")
            raise
    signal.emit(98)
    shutil.rmtree(sub_dir, ignore_errors=True)
    signal.emit(100)
Exemple #22
0
def start_app(worker_queue, status_queue, log_queue, queue_list, queue_lock):
    app = create_app()
    app.fastflix = FastFlix(queue=queue_list, queue_lock=queue_lock)
    app.fastflix.log_queue = log_queue
    app.fastflix.status_queue = status_queue
    app.fastflix.worker_queue = worker_queue

    app.fastflix.config = Config()
    init_fastflix_directories(app)
    init_logging(app)
    register_app()
    upgraded = app.fastflix.config.upgrade_check()
    if upgraded:
        # No translation will be possible in this case
        message(
            f"Your config file has been upgraded to FastFlix's new YAML config format\n"
            f"{app.fastflix.config.config_path}",
            title="Upgraded",
        )
    try:
        app.fastflix.config.load()
    except MissingFF as err:
        if reusables.win_based and ask_for_ffmpeg():
            try:
                ProgressBar(app, [Task(t("Downloading FFmpeg"), latest_ffmpeg)], signal_task=True)
                app.fastflix.config.load()
            except Exception as err:
                logger.exception(str(err))
                sys.exit(1)
        else:
            logger.error(f"Could not find {err} location, please manually set in {app.fastflix.config.config_path}")
            sys.exit(1)
    except Exception:
        # TODO give edit / delete options
        logger.exception(t("Could not load config file!"))
        sys.exit(1)

    if app.fastflix.config.theme != "system":
        QtCore.QDir.addSearchPath(app.fastflix.config.theme, str(breeze_styles_path / app.fastflix.config.theme))
        file = QtCore.QFile(f"{app.fastflix.config.theme}:stylesheet.qss")
        file.open(QtCore.QFile.OpenModeFlag.ReadOnly | QtCore.QFile.OpenModeFlag.Text)
        stream = QtCore.QTextStream(file)
        data = stream.readAll()
        if not reusables.win_based:
            data = data.replace("url(dark:", f"url({str(breeze_styles_path / 'dark')}/")
            data = data.replace("url(light:", f"url({str(breeze_styles_path / 'light')}/")
            data = data.replace("url(onyx:", f"url({str(breeze_styles_path / 'onyx')}/")

        app.setStyleSheet(data)

    logger.setLevel(app.fastflix.config.logging_level)

    startup_tasks = [
        Task(t("Gather FFmpeg version"), ffmpeg_configuration),
        Task(t("Gather FFprobe version"), ffprobe_configuration),
        Task(t("Gather FFmpeg audio encoders"), ffmpeg_audio_encoders),
        Task(t("Determine OpenCL Support"), ffmpeg_opencl_support),
        Task(t("Initialize Encoders"), init_encoders),
    ]

    try:
        ProgressBar(app, startup_tasks)
    except Exception:
        logger.exception(f'{t("Could not start FastFlix")}!')
        sys.exit(1)

    container = Container(app)
    container.show()

    try:
        app.exec_()
    except Exception:
        logger.exception("Error while running FastFlix")
        raise
Exemple #23
0
    def __init__(self, parent, app: FastFlixApp):
        super().__init__(parent)
        self.app = app
        self.main = parent.main
        self.attachments = Box()

        layout = QtWidgets.QGridLayout()

        sp = QtWidgets.QSizePolicy()
        sp.setVerticalPolicy(QtWidgets.QSizePolicy.Policy.Maximum)
        sp.setHorizontalPolicy(QtWidgets.QSizePolicy.Policy.Maximum)

        # row, column, row span, column span
        layout.addWidget(QtWidgets.QLabel(t("Poster Cover")), 0, 0, 1, 5)
        layout.addWidget(QtWidgets.QLabel(t("Landscape Cover")), 0, 6, 1, 4)
        info_label = QtWidgets.QLabel(
            link(
                "https://codecalamity.com/guides/video-thumbnails/",
                t("Enabling cover thumbnails on your system"),
                app.fastflix.config.theme,
            )
        )
        info_label.setOpenExternalLinks(True)
        layout.addWidget(info_label, 10, 0, 1, 9, QtCore.Qt.AlignLeft)

        poster_options_layout = QtWidgets.QHBoxLayout()
        self.cover_passthrough_checkbox = QtWidgets.QCheckBox(t("Copy Cover"))
        self.small_cover_passthrough_checkbox = QtWidgets.QCheckBox(t("Copy Small Cover (no preview)"))

        poster_options_layout.addWidget(self.cover_passthrough_checkbox)
        poster_options_layout.addWidget(self.small_cover_passthrough_checkbox)

        land_options_layout = QtWidgets.QHBoxLayout()
        self.cover_land_passthrough_checkbox = QtWidgets.QCheckBox(t("Copy Landscape Cover"))
        self.small_cover_land_passthrough_checkbox = QtWidgets.QCheckBox(t("Copy Small Landscape Cover  (no preview)"))

        land_options_layout.addWidget(self.cover_land_passthrough_checkbox)
        land_options_layout.addWidget(self.small_cover_land_passthrough_checkbox)

        self.cover_passthrough_checkbox.toggled.connect(lambda: self.cover_passthrough_check())
        self.small_cover_passthrough_checkbox.toggled.connect(lambda: self.small_cover_passthrough_check())
        self.cover_land_passthrough_checkbox.toggled.connect(lambda: self.cover_land_passthrough_check())
        self.small_cover_land_passthrough_checkbox.toggled.connect(lambda: self.small_cover_land_passthrough_check())

        self.poster = QtWidgets.QLabel()
        self.poster.setSizePolicy(sp)

        self.landscape = QtWidgets.QLabel()
        self.landscape.setSizePolicy(sp)

        layout.addLayout(poster_options_layout, 1, 0, 1, 4)
        layout.addLayout(land_options_layout, 1, 6, 1, 4)

        layout.addWidget(self.poster, 2, 0, 8, 4)
        layout.addWidget(self.landscape, 2, 6, 8, 4)

        layout.addLayout(self.init_cover(), 9, 0, 1, 4)
        layout.addLayout(self.init_landscape_cover(), 9, 6, 1, 4)
        layout.columnStretch(5)

        self.setLayout(layout)
Exemple #24
0
 def translate_tip(tooltip):
     return "\n".join([t(x) for x in tooltip.split("\n") if x.strip()])
Exemple #25
0
    def __init__(self, app):
        super(About, self).__init__()
        layout = QtWidgets.QGridLayout()
        self.app = app
        self.setMinimumSize(QtCore.QSize(400, 400))

        build_file = Path(base_path, "build_version")

        build = t("Build")
        label = QtWidgets.QLabel(
            f"<b>FastFlix</b> v{__version__}<br>"
            f"{f'{build}: {build_file.read_text().strip()}<br>' if build_file.exists() else ''}"
            f"<br>{t('Author')}: {link('https://github.com/cdgriffith', 'Chris Griffith', app.fastflix.config.theme)}"
            f"<br>{t('License')}: MIT")
        label.setFont(QtGui.QFont("Arial", 14))
        label.setAlignment(QtCore.Qt.AlignCenter)
        label.setOpenExternalLinks(True)
        label.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
                            QtWidgets.QSizePolicy.Expanding)
        layout.addWidget(label)

        support_label = QtWidgets.QLabel(
            f'{link("https://github.com/cdgriffith/FastFlix/wiki/Support-FastFlix", t("Support FastFlix"), app.fastflix.config.theme)}<br><br>'
        )
        support_label.setOpenExternalLinks(True)
        support_label.setFont(QtGui.QFont("Arial", 12))
        support_label.setAlignment(
            (QtCore.Qt.AlignCenter | QtCore.Qt.AlignTop))
        layout.addWidget(support_label)

        bundle_label = QtWidgets.QLabel(
            f"Conversion suites: {link('https://www.ffmpeg.org/download.html', 'FFmpeg', app.fastflix.config.theme)} ({t('Various')}), "
            f"{link('https://github.com/rigaya/NVEnc', 'NVEncC', app.fastflix.config.theme)} (MIT) "
            f"{link('https://github.com/rigaya/VCEEnc', 'VCEEnc', app.fastflix.config.theme)} (MIT)<br><br>"
            f"Encoders: <br> {link('https://github.com/rigaya/NVEnc', 'NVEncC', app.fastflix.config.theme)} (MIT), "
            f"{link('https://github.com/rigaya/VCEEnc', 'VCEEnc', app.fastflix.config.theme)} (MIT), "
            f"SVT AV1 (MIT), rav1e (MIT), aom (MIT), x265 (GPL), x264 (GPL), libvpx (BSD)"
        )
        bundle_label.setAlignment(QtCore.Qt.AlignCenter)
        bundle_label.setOpenExternalLinks(True)
        layout.addWidget(bundle_label)

        supporting_libraries_label = QtWidgets.QLabel(
            "Supporting libraries<br>"
            f"{link('https://www.python.org/', t('Python'), app.fastflix.config.theme)}{reusables.version_string} (PSF LICENSE), "
            f"{link('https://github.com/cdgriffith/Box', t('python-box'), app.fastflix.config.theme)} {box_version} (MIT), "
            f"{link('https://github.com/cdgriffith/Reusables', t('Reusables'), app.fastflix.config.theme)} {reusables.__version__} (MIT)<br>"
            "mistune (BSD), colorama (BSD), coloredlogs (MIT), Requests (Apache 2.0)<br>"
            "appdirs (MIT), iso639-lang (MIT), psutil (BSD), pathvalidate (MIT) <br>"
            "BreezeStyleSheets (MIT), PySide2 (LGPL)")
        supporting_libraries_label.setAlignment(QtCore.Qt.AlignCenter)
        supporting_libraries_label.setOpenExternalLinks(True)
        layout.addWidget(supporting_libraries_label)

        if pyinstaller:
            pyinstaller_label = QtWidgets.QLabel(
                f"Packaged with: {link('https://www.pyinstaller.org/index.html', 'PyInstaller', app.fastflix.config.theme)}"
            )
            pyinstaller_label.setAlignment(QtCore.Qt.AlignCenter)
            pyinstaller_label.setOpenExternalLinks(True)
            layout.addWidget(QtWidgets.QLabel())
            layout.addWidget(pyinstaller_label)

        license_label = QtWidgets.QLabel(
            link(
                "https://github.com/cdgriffith/FastFlix/blob/master/docs/build-licenses.txt",
                t("LICENSES"),
                app.fastflix.config.theme,
            ))
        license_label.setAlignment(QtCore.Qt.AlignCenter)
        license_label.setOpenExternalLinks(True)
        layout.addWidget(QtWidgets.QLabel())
        layout.addWidget(license_label)

        self.setLayout(layout)
    def __init__(
            self,
            parent,
            audio,
            index,
            codec,
            available_audio_encoders,
            title="",
            language="",
            profile="",
            outdex=None,
            enabled=True,
            original=False,
            first=False,
            last=False,
            codecs=(),
            channels=2,
            all_info=None,
            disable_dup=False,
    ):
        self.loading = True
        super(Audio, self).__init__(parent)
        self.parent = parent
        self.audio = audio
        self.setFixedHeight(60)
        self.original = original
        self.outdex = index if self.original else outdex
        self.first = first
        self.track_name = title
        self.profile = profile
        self.last = last
        self.index = index
        self.codec = codec
        self.codecs = codecs
        self.channels = channels
        self.available_audio_encoders = available_audio_encoders
        self.all_info = all_info

        self.widgets = Box(
            track_number=QtWidgets.QLabel(
                f"{index}:{self.outdex}" if enabled else "❌"),
            title=QtWidgets.QLineEdit(title),
            audio_info=QtWidgets.QLabel(audio),
            up_button=QtWidgets.QPushButton(
                QtGui.QIcon(
                    get_icon("up-arrow",
                             self.parent.app.fastflix.config.theme)), ""),
            down_button=QtWidgets.QPushButton(
                QtGui.QIcon(
                    get_icon("down-arrow",
                             self.parent.app.fastflix.config.theme)), ""),
            enable_check=QtWidgets.QCheckBox(t("Enabled")),
            dup_button=QtWidgets.QPushButton(
                QtGui.QIcon(
                    get_icon("onyx-copy",
                             self.parent.app.fastflix.config.theme)), ""),
            delete_button=QtWidgets.QPushButton(
                QtGui.QIcon(
                    get_icon("black-x",
                             self.parent.app.fastflix.config.theme)), ""),
            language=QtWidgets.QComboBox(),
            downmix=QtWidgets.QComboBox(),
            convert_to=None,
            convert_bitrate=None,
        )

        self.widgets.up_button.setStyleSheet(no_border)
        self.widgets.down_button.setStyleSheet(no_border)
        self.widgets.dup_button.setStyleSheet(no_border)
        self.widgets.delete_button.setStyleSheet(no_border)

        if all_info:
            self.widgets.audio_info.setToolTip(all_info.to_yaml())

        self.widgets.language.addItems(["No Language Set"] + language_list)
        self.widgets.language.setMaximumWidth(110)
        if language:
            try:
                lang = Lang(language).name
            except InvalidLanguageValue:
                pass
            else:
                if lang in language_list:
                    self.widgets.language.setCurrentText(lang)

        self.widgets.language.currentIndexChanged.connect(self.page_update)
        self.widgets.title.setFixedWidth(150)
        self.widgets.title.textChanged.connect(self.page_update)
        self.widgets.audio_info.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
                                              QtWidgets.QSizePolicy.Expanding)

        self.widgets.downmix.addItems(
            [t("No Downmix")] +
            [k for k, v in channel_list.items() if v <= channels])
        self.widgets.downmix.currentIndexChanged.connect(self.update_downmix)
        self.widgets.downmix.setCurrentIndex(0)
        self.widgets.downmix.setDisabled(True)

        self.widgets.enable_check.setChecked(enabled)
        self.widgets.enable_check.toggled.connect(self.update_enable)

        self.widgets.dup_button.clicked.connect(lambda: self.dup_me())
        self.widgets.dup_button.setFixedWidth(20)
        if disable_dup:
            self.widgets.dup_button.hide()
            self.widgets.dup_button.setDisabled(True)

        self.widgets.delete_button.clicked.connect(lambda: self.del_me())
        self.widgets.delete_button.setFixedWidth(20)

        self.widgets.track_number.setFixedWidth(20)

        label = QtWidgets.QLabel(f"{t('Title')}: ")
        self.widgets.title.setFixedWidth(150)
        title_layout = QtWidgets.QHBoxLayout()
        title_layout.addWidget(label)
        title_layout.addWidget(self.widgets.title)

        grid = QtWidgets.QGridLayout()
        grid.addLayout(self.init_move_buttons(), 0, 0)
        grid.addWidget(self.widgets.track_number, 0, 1)
        grid.addWidget(self.widgets.audio_info, 0, 2)
        grid.addLayout(title_layout, 0, 3)
        # grid.addWidget(self.widgets.title, 0, 4)
        grid.addLayout(self.init_conversion(), 0, 5)
        grid.addWidget(self.widgets.downmix, 0, 6)
        grid.addWidget(self.widgets.language, 0, 7)

        right_button_start_index = 8

        if not original:
            spacer = QtWidgets.QLabel()
            spacer.setFixedWidth(63)
            grid.addWidget(spacer, 0, right_button_start_index)
            grid.addWidget(self.widgets.delete_button, 0,
                           right_button_start_index + 1)
        else:
            grid.addWidget(self.widgets.enable_check, 0,
                           right_button_start_index)
            grid.addWidget(self.widgets.dup_button, 0,
                           right_button_start_index + 1)
        self.setLayout(grid)
        self.loading = False
Exemple #27
0
    def __init__(self, parent, app: FastFlixApp):
        self.main = parent.main
        self.app = app
        self.paused = False
        self.encode_paused = False

        top_layout = QtWidgets.QHBoxLayout()

        top_layout.addWidget(QtWidgets.QLabel(t("Queue")))
        top_layout.addStretch(1)

        self.clear_queue = QtWidgets.QPushButton(
            self.app.style().standardIcon(
                QtWidgets.QStyle.SP_LineEditClearButton), t("Clear Completed"))
        self.clear_queue.clicked.connect(self.clear_complete)
        self.clear_queue.setFixedWidth(120)
        self.clear_queue.setToolTip(t("Remove completed tasks"))

        self.pause_queue = QtWidgets.QPushButton(
            self.app.style().standardIcon(QtWidgets.QStyle.SP_MediaPause),
            t("Pause Queue"))
        self.pause_queue.clicked.connect(self.pause_resume_queue)
        # pause_queue.setFixedHeight(40)
        self.pause_queue.setFixedWidth(120)
        self.pause_queue.setToolTip(
            t("Wait for the current command to finish,"
              " and stop the next command from processing"))

        self.pause_encode = QtWidgets.QPushButton(
            self.app.style().standardIcon(QtWidgets.QStyle.SP_MediaPause),
            t("Pause Encode"))
        self.pause_encode.clicked.connect(self.pause_resume_encode)
        # pause_queue.setFixedHeight(40)
        self.pause_encode.setFixedWidth(120)
        self.pause_encode.setToolTip(t("Pause / Resume the current command"))

        self.after_done_combo = QtWidgets.QComboBox()
        self.after_done_combo.addItem("None")
        actions = set()
        if reusables.win_based:
            actions.update(done_actions["windows"].keys())

        elif sys.platform == "darwin":
            actions.update(["shutdown", "restart"])
        else:
            actions.update(done_actions["linux"].keys())
        if self.app.fastflix.config.custom_after_run_scripts:
            actions.update(self.app.fastflix.config.custom_after_run_scripts)

        self.after_done_combo.addItems(sorted(actions))
        self.after_done_combo.setToolTip(
            "Run a command after conversion completes")
        self.after_done_combo.currentIndexChanged.connect(
            lambda: self.set_after_done())
        self.after_done_combo.setMaximumWidth(150)
        top_layout.addWidget(QtWidgets.QLabel(t("After Conversion")))
        top_layout.addWidget(self.after_done_combo, QtCore.Qt.AlignRight)
        top_layout.addWidget(self.pause_encode, QtCore.Qt.AlignRight)
        top_layout.addWidget(self.pause_queue, QtCore.Qt.AlignRight)
        top_layout.addWidget(self.clear_queue, QtCore.Qt.AlignRight)
        # pause_encode = QtWidgets.QPushButton(
        #     self.app.style().standardIcon(QtWidgets.QStyle.SP_MediaPause), "Pause Encode"
        # )
        # # pause_encode.setFixedHeight(40)
        # pause_encode.setFixedWidth(120)
        # top_layout.addWidget(pause_encode, QtCore.Qt.AlignRight)

        super().__init__(app,
                         parent,
                         t("Queue"),
                         "queue",
                         top_row_layout=top_layout)
# -*- coding: utf-8 -*-

import logging

from box import Box
from qtpy import QtCore, QtGui, QtWidgets

from fastflix.language import t
from fastflix.models.fastflix_app import FastFlixApp
from fastflix.models.video import VideoSettings
from fastflix.resources import warning_icon

logger = logging.getLogger("fastflix")

video_speeds = {
    t("Same as Source"): 1,
    "1/100": 100,
    "1/10": 10,
    # "1/5": 5,
    "1/4": 4,
    # "1/3": 3,
    "1/2": 2,
    # "2/3": 1.67,
    # "3/4": 1.5,
    # "1.5x": 0.75,
    "2x": 0.5,
    # "3x": 0.34,
    "4x": 0.25,
    # "5x": 0.2,
    "10x": 0.1,
    "100x": 0.01,
Exemple #29
0
    def __init__(self,
                 parent,
                 video: Video,
                 index,
                 first=False,
                 currently_encoding=False):
        self.loading = True
        super().__init__(parent)
        self.parent = parent
        self.index = index
        self.first = first
        self.last = False
        self.video = video
        self.currently_encoding = currently_encoding
        self.setFixedHeight(60)

        self.widgets = Box(
            up_button=QtWidgets.QPushButton(QtGui.QIcon(up_arrow_icon), ""),
            down_button=QtWidgets.QPushButton(QtGui.QIcon(down_arrow_icon),
                                              ""),
            cancel_button=QtWidgets.QPushButton(QtGui.QIcon(black_x_icon), ""),
            reload_buttom=QtWidgets.QPushButton(QtGui.QIcon(edit_box_icon),
                                                ""),
        )

        for widget in self.widgets.values():
            widget.setStyleSheet(no_border)
            if self.currently_encoding:
                widget.setDisabled(True)

        title = QtWidgets.QLabel(
            video.video_settings.video_title if video.video_settings.
            video_title else video.video_settings.output_path.name)
        title.setFixedWidth(300)

        settings = Box(copy.deepcopy(asdict(video.video_settings)))
        settings.output_path = str(settings.output_path)
        del settings.conversion_commands

        title.setToolTip(settings.to_yaml())

        open_button = QtWidgets.QPushButton(QtGui.QIcon(folder_icon),
                                            t("Open Directory"))
        open_button.setLayoutDirection(QtCore.Qt.RightToLeft)
        open_button.setIconSize(QtCore.QSize(14, 14))
        open_button.clicked.connect(
            lambda: open_folder(video.video_settings.output_path.parent))

        view_button = QtWidgets.QPushButton(QtGui.QIcon(play_icon), t("Watch"))
        view_button.setLayoutDirection(QtCore.Qt.RightToLeft)
        view_button.setIconSize(QtCore.QSize(14, 14))
        view_button.clicked.connect(lambda: QtGui.QDesktopServices.openUrl(
            QtCore.QUrl.fromLocalFile(str(video.video_settings.output_path))))

        open_button.setStyleSheet(no_border)
        view_button.setStyleSheet(no_border)

        status = t("Ready to encode")
        if video.status.error:
            status = t("Encoding errored")
        elif video.status.complete:
            status = f"{t('Encoding complete')}"
        elif video.status.running:
            status = (
                f"{t('Encoding command')} {video.status.current_command} {t('of')} "
                f"{len(video.video_settings.conversion_commands)}")
        elif video.status.cancelled:
            status = t("Cancelled - Ready to try again")

        if not self.currently_encoding:
            self.widgets.cancel_button.clicked.connect(
                lambda: self.parent.remove_item(self.video))
            self.widgets.reload_buttom.clicked.connect(
                lambda: self.parent.reload_from_queue(self.video))
        self.widgets.cancel_button.setFixedWidth(25)
        self.widgets.reload_buttom.setFixedWidth(25)

        grid = QtWidgets.QGridLayout()
        grid.addLayout(self.init_move_buttons(), 0, 0)
        # grid.addWidget(self.widgets.track_number, 0, 1)
        grid.addWidget(title, 0, 1, 1, 3)
        grid.addWidget(
            QtWidgets.QLabel(
                f"{video.video_settings.video_encoder_settings.name}"), 0, 4)
        grid.addWidget(
            QtWidgets.QLabel(
                f"{t('Audio Tracks')}: {len(video.video_settings.audio_tracks)}"
            ), 0, 5)
        grid.addWidget(
            QtWidgets.QLabel(
                f"{t('Subtitles')}: {len(video.video_settings.subtitle_tracks)}"
            ), 0, 6)
        grid.addWidget(QtWidgets.QLabel(status), 0, 7)
        if video.status.complete:
            grid.addWidget(view_button, 0, 8)
            grid.addWidget(open_button, 0, 9)

        right_buttons = QtWidgets.QHBoxLayout()
        right_buttons.addWidget(self.widgets.reload_buttom)
        right_buttons.addWidget(self.widgets.cancel_button)

        grid.addLayout(right_buttons, 0, 10, alignment=QtCore.Qt.AlignRight)
        # grid.addLayout(disposition_layout, 0, 4)
        # grid.addWidget(self.widgets.burn_in, 0, 5)
        # grid.addLayout(self.init_language(), 0, 6)
        # # grid.addWidget(self.init_extract_button(), 0, 6)
        # grid.addWidget(self.widgets.enable_check, 0, 8)

        self.setLayout(grid)
        self.loading = False
        self.updating_burn = False
    def reset(self, settings: VideoSettings = None):
        if settings:
            self.video_speed_widget.setCurrentText(
                get_key(video_speeds, settings.video_speed))
            if settings.deblock:
                self.deblock_widget.setCurrentText(settings.deblock)
            self.deblock_size_widget.setCurrentText(str(settings.deblock_size))
            self.tone_map_widget.setCurrentText(settings.tone_map)

            if not settings.source_fps:
                self.incoming_same_as_source.setChecked(True)
                self.incoming_fps_widget.setText("")
            else:
                self.incoming_same_as_source.setChecked(False)
                self.incoming_fps_widget.setText(settings.source_fps)

            if not settings.output_fps:
                self.outgoing_same_as_source.setChecked(True)
                self.outgoing_fps_widget.setText("")
            else:
                self.outgoing_same_as_source.setChecked(False)
                self.outgoing_fps_widget.setText(settings.output_fps)

            if settings.denoise:
                for denoise_type, preset in denoise_presets.items():
                    for preset_name, value in preset.items():
                        if settings.denoise == value:
                            self.denoise_type_widget.setCurrentText(
                                denoise_type)
                            self.denoise_strength_widget.setCurrentText(
                                preset_name)
            if settings.vsync:
                self.vsync_widget.setCurrentText(settings.vsync)
            else:
                self.vsync_widget.setCurrentIndex(0)

            if settings.maxrate:
                self.vbv_checkbox.setChecked(True)
                self.maxrate_widget.setText(str(settings.maxrate))
                self.bufsize_widget.setText(str(settings.bufsize))
                self.maxrate_widget.setEnabled(True)
                self.bufsize_widget.setEnabled(True)
            else:
                self.vbv_checkbox.setChecked(False)
                self.maxrate_widget.setText("")
                self.bufsize_widget.setText("")
                self.maxrate_widget.setDisabled(True)
                self.bufsize_widget.setDisabled(True)

        else:
            self.video_speed_widget.setCurrentIndex(0)
            self.deblock_widget.setCurrentIndex(0)
            self.deblock_size_widget.setCurrentIndex(0)
            self.tone_map_widget.setCurrentIndex(5)
            self.incoming_same_as_source.setChecked(True)
            self.outgoing_same_as_source.setChecked(True)
            self.incoming_fps_widget.setDisabled(True)
            self.outgoing_fps_widget.setDisabled(True)
            self.incoming_fps_widget.setText("")
            self.outgoing_fps_widget.setText("")
            self.denoise_type_widget.setCurrentIndex(0)
            self.denoise_strength_widget.setCurrentIndex(0)
            self.vsync_widget.setCurrentIndex(0)
            self.vbv_checkbox.setChecked(False)
            self.maxrate_widget.setText("")
            self.bufsize_widget.setText("")
            self.maxrate_widget.setDisabled(True)
            self.bufsize_widget.setDisabled(True)

        self.hdr_settings()

        # Set the frame rate
        if self.app.fastflix.current_video:
            dont_set = False
            if "/" in self.app.fastflix.current_video.frame_rate:
                try:
                    over, under = self.app.fastflix.current_video.frame_rate.split(
                        "/")
                    if under == "1":
                        self.source_frame_rate.setText(over)
                        dont_set = True
                    readable_rate = int(over) / int(under)
                except Exception:
                    self.source_frame_rate.setText(
                        self.app.fastflix.current_video.frame_rate)
                else:
                    if not dont_set:
                        self.source_frame_rate.setText(
                            f"{self.app.fastflix.current_video.frame_rate}   [ ~{readable_rate:.3f} ]"
                        )
            else:
                self.source_frame_rate.setText(
                    self.app.fastflix.current_video.frame_rate)
            self.source_frame_rate_type.setText(
                t("Constant") if self.app.fastflix.current_video.frame_rate ==
                self.app.fastflix.current_video.
                average_frame_rate else t("Variable"))
        else:
            self.source_frame_rate.setText("")
            self.source_frame_rate_type.setText("")