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())
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
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
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()
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()
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)
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))
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)
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
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]))
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
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)
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
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)
def translate_tip(tooltip): return "\n".join([t(x) for x in tooltip.split("\n") if x.strip()])
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
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,
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("")