示例#1
0
    def change_conversion(self, conversion):
        conversion = conversion.strip()
        encoder = self.app.fastflix.encoders[conversion]
        self.current_settings.close()
        self.current_settings = encoder.settings_panel(self, self.main,
                                                       self.app)
        self.current_settings.show()
        self.removeTab(0)
        self.insertTab(0, self.current_settings, t("Quality"))
        self.setTabIcon(
            0,
            QtGui.QIcon(
                get_icon("onyx-quality", self.app.fastflix.config.theme)))

        self.setCurrentIndex(0)
        self.change_tab(0)
        self.setTabEnabled(1, getattr(encoder, "enable_audio", True))
        self.setTabEnabled(2, getattr(encoder, "enable_subtitles", True))
        self.setTabEnabled(3, getattr(encoder, "enable_attachments", True))
        self.selected = conversion
        self.current_settings.new_source()
        self.main.page_update(build_thumbnail=False)
        if (self.app.fastflix.current_video and
                not getattr(self.main.current_encoder, "enable_concat", False)
                and self.app.fastflix.current_video.concat):
            error_message(
                f"This encoder, {self.main.current_encoder.name} does not support concatenating files together"
            )
        # Page update does a reload which bases itself off the current encoder so we have to do audio formats after
        self.audio.allowed_formats(self._get_audio_formats(encoder))
示例#2
0
    def update_setting(self, name, value, delete=False):
        # TODO change work dir in main and create new temp folder
        mappings = {
            "work_dir":
            "work_dir",
            "ffmpeg":
            "ffmpeg",
            "ffprobe":
            "ffprobe",
            "use_sane_audio":
            "use_sane_audio",
            "disable_version_check":
            "disable_version_check",
            "disable_automatic_subtitle_burn_in":
            "disable_automatic_subtitle_burn_in",
        }

        settings = Box(box_dots=True).from_json(filename=self.config_file)
        old_settings = settings.copy()
        if isinstance(value, Path):
            value = str(value)
        if value == "" and delete:
            del settings[mappings[name]]
        else:
            settings[mappings[name]] = value

        try:
            settings.to_json(filename=self.config_file, indent=2)
        except Exception:
            old_settings.to_json(filename=self.config_file, indent=2)
            error_message("Could not update settings", traceback=True)
示例#3
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:
            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.update_setting("work_dir", new_work_dir)
            self.main_app.path.work = new_work_dir

        self.update_setting("use_sane_audio", self.use_sane_audio.isChecked())
        self.update_setting("disable_version_check",
                            self.disable_version_check.isChecked())
        self.update_setting("disable_automatic_subtitle_burn_in",
                            self.disable_burn_in.isChecked())

        self.main_app.config = Box.from_json(filename=self.config_file)
        self.main_app.config_update(new_ffmpeg, new_ffprobe)
        self.close()
示例#4
0
 def clean_old_logs(self):
     try:
         ProgressBar(self.app, [Task(t("Clean Old Logs"), clean_logs)],
                     signal_task=True,
                     can_cancel=False)
     except Exception:
         error_message(t("Could not compress old logs"), traceback=True)
示例#5
0
    def select_folder(self):
        if self.concat_area.table.model.rowCount() > 0:
            if not yes_no_message(
                f"{t('There are already items in this list')},\n"
                f"{t('if you open a new directory, they will all be removed.')}\n\n"
                f"{t('Continue')}?",
                "Confirm Change Folder",
            ):
                return
        folder_name = QtWidgets.QFileDialog.getExistingDirectory(self, dir=self.folder_name)
        if not folder_name:
            return
        self.folder_name = folder_name
        self.set_folder_name(folder_name)

        def check_to_add(file, list_of_items, bad_items, **_):
            try:
                data = None
                details = probe(self.app, file)
                for stream in details.streams:
                    if stream.codec_type == "video":
                        data = (file.name, f"{stream.width}x{stream.height}", stream.codec_name)
                if not data:
                    raise Exception()
            except Exception:
                logger.warning(f"Skipping {file.name} as it is not a video/image file")
                bad_items.append(file.name)
            else:
                list_of_items.append(data)

        items = []
        skipped = []
        tasks = []
        for file in Path(folder_name).glob("*"):
            if file.is_file():
                tasks.append(
                    Task(
                        f"Evaluating {file.name}",
                        command=check_to_add,
                        kwargs={"file": file, "list_of_items": items, "bad_items": skipped},
                    )
                )

        ProgressBar(self.app, tasks, can_cancel=True, auto_run=True)

        self.concat_area.table.update_items(items)
        if skipped:
            error_message(
                "".join(
                    [
                        f"{t('The following items were excluded as they could not be identified as image or video files')}:\n",
                        "\n".join(skipped[:20]),
                        f"\n\n+ {len(skipped[20:])} {t('more')}..." if len(skipped) > 20 else "",
                    ]
                )
            )
示例#6
0
 def update_burn_in(self):
     if self.updating_burn:
         return
     self.updating_burn = True
     enable = self.widgets.burn_in.isChecked()
     if enable and [1 for track in self.parent.tracks if track.enabled and track.burn_in and track is not self]:
         self.widgets.burn_in.setChecked(False)
         error_message(t("There is an existing burn-in track, only one can be enabled at a time"))
     if enable and self.parent.main.fast_time:
         self.parent.main.widgets.fast_time.setCurrentText("exact")
     self.updating_burn = False
     self.page_update()
示例#7
0
 def path_check(name, new_path):
     if not new_path.exists():
         which = shutil.which(str(new_path))
         if not which:
             error_message(
                 f"No {name} instance found at {new_path}, not updated")
             return
         return Path(which)
     if not new_path.is_file():
         error_message(f"{new_path} is not a file")
         return
     return new_path
示例#8
0
    def conversion_complete(self, return_code):
        self.widgets.convert_button.setStyleSheet("background-color:green;")
        self.converting = False
        self.widgets.convert_button.setText("Convert 🎥")
        output = Path(self.output_video)

        if return_code or not output.exists() or output.stat().st_size <= 500:
            error_message("Could not encode video due to an error, please view the logs for more details!")
        else:
            sm = QtWidgets.QMessageBox()
            sm.setText("Encoded successfully, view now?")
            sm.addButton("View", QtWidgets.QMessageBox.YesRole)
            sm.setStandardButtons(QtWidgets.QMessageBox.Close)
            sm.exec_()
            if sm.clickedButton().text() == "View":
                QtGui.QDesktopServices.openUrl(QtCore.QUrl.fromLocalFile(self.output_video))
示例#9
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())
        errors = bool(self.update_ffmpeg(new_ffmpeg))
        errors |= bool(self.update_ffprobe(new_ffprobe))

        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.update_setting("work_dir", new_work_dir)
            self.main_app.path.work = new_work_dir
        if not errors:
            self.close()
示例#10
0
    def create_video(self):
        if self.converting:
            self.worker_queue.put(["cancel"])
            return

        if not self.input_video:
            return error_message("Have to select a video first")

        if self.encoding_worker and self.encoding_worker.is_alive():
            return error_message("Still encoding something else")

        if not self.input_video:
            return error_message("Please provide a source video")
        if not self.output_video:
            error_message("Please specify output video")
            return
        if not self.output_video.lower().endswith(self.current_plugin.video_extension):
            sm = QtWidgets.QMessageBox()
            sm.setText(
                f"Output video file does not have expected extension ({self.current_plugin.video_extension}), which can case issues."
            )
            sm.addButton("Continue anyways", QtWidgets.QMessageBox.DestructiveRole)
            sm.addButton(f"Append ({self.current_plugin.video_extension}) for me", QtWidgets.QMessageBox.YesRole)
            sm.setStandardButtons(QtWidgets.QMessageBox.Close)
            for button in sm.buttons():
                if button.text().startswith("Append"):
                    button.setStyleSheet("background-color:green;")
                elif button.text().startswith("Continue"):
                    button.setStyleSheet("background-color:red;")
            sm.exec_()
            if sm.clickedButton().text().startswith("Append"):
                self.output_video_path_widget.setText(f"{self.output_video}.{self.current_plugin.video_extension}")
                self.output_video_path_widget.setDisabled(False)
                self.output_path_button.setDisabled(False)
            elif not sm.clickedButton().text().startswith("Continue"):
                return

        _, commands = self.build_commands()

        self.widgets.convert_button.setText("â›” Cancel")
        self.widgets.convert_button.setStyleSheet("background-color:red;")
        self.converting = True
        for command in commands:
            self.worker_queue.put(("command", command.command, self.path.temp_dir))
        self.video_options.setCurrentWidget(self.video_options.status)
示例#11
0
    def update_setting(self, name, value):
        # TODO change work dir in main and create new temp folder
        mappings = {
            "work_dir": "work_dir",
            "ffmpeg": "ffmpeg",
            "ffprobe": "ffprobe",
        }

        settings = Box(box_dots=True).from_json(filename=self.config_file)
        old_settings = settings.copy()
        if value:
            settings[mappings[name]] = str(value)
        else:
            del settings[mappings[name]]
        try:
            settings.to_json(filename=self.config_file, indent=2)
        except Exception:
            old_settings.to_json(filename=self.config_file, indent=2)
            error_message("Could not update settings", traceback=True)
示例#12
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())
示例#13
0
 def build_crop(self):
     top = int(self.widgets.crop.top.text())
     left = int(self.widgets.crop.left.text())
     right = int(self.widgets.crop.right.text())
     bottom = int(self.widgets.crop.bottom.text())
     width = self.video_width - right - left
     height = self.video_height - bottom - top
     if (top + left + right + bottom) == 0:
         return None
     try:
         assert top >= 0, "Top must be positive number"
         assert left >= 0, "Left must be positive number"
         assert width > 0, "Total video width must be greater than 0"
         assert height > 0, "Total video height must be greater than 0"
         assert width <= self.video_width, "Width must be smaller than video width"
         assert height <= self.video_height, "Height must be smaller than video height"
     except AssertionError as err:
         error_message(f"Invalid Crop: {err}")
         return
     return f"{width}:{height}:{left}:{top}"
示例#14
0
 def manually_load_queue(self):
     filename = QtWidgets.QFileDialog.getOpenFileName(
         self,
         caption=t("Load Queue"),
         dir=os.path.expanduser("~"),
         filter=f"FastFlix Queue File (*.yaml)")
     if filename and filename[0]:
         is_yes = True
         if self.app.fastflix.conversion_list:
             is_yes = yes_no_message(
                 (t("This will remove all items in the queue currently") +
                  "\n" + t(f"It will update it with the contents of") +
                  f":\n\n {filename[0]}\n\n" +
                  t("Are you sure you want to proceed?")),
                 title="Overwrite existing queue?",
             )
         filename = Path(filename[0])
         if not filename.exists():
             error_message(t("That file doesn't exist"))
         if is_yes:
             self.queue_startup_check(filename)
示例#15
0
 def save(self):
     concat_file = self.app.fastflix.config.work_path / "concat.txt"
     with open(concat_file, "w") as f:
         f.write(
             "\n".join([f"file '{self.folder_name}{os.sep}{item}'" for item in self.concat_area.table.get_items()])
         )
     self.main.input_video = concat_file
     self.main.source_video_path_widget.setText(str(self.main.input_video))
     self.main.update_video_info()
     self.concat_area.table.model.clear()
     self.concat_area.table.buttons = []
     self.main.widgets.end_time.setText("0")
     self.main.widgets.start_time.setText("0")
     self.main.app.fastflix.current_video.interlaced = False
     self.main.widgets.deinterlace.setChecked(False)
     self.hide()
     self.main.page_update(build_thumbnail=True)
     error_message(
         "Make sure to manually supply the frame rate in the Advanced tab "
         "(usually want to set both input and output to the same thing.)",
         title="Set FPS in Advanced Tab",
     )
示例#16
0
 def delete_current_profile(self):
     if self.app.fastflix.config.selected_profile in get_preset_defaults():
         return error_message(
             f"{self.app.fastflix.config.selected_profile} " f"{t('is a default profile and will not be removed')}"
         )
     self.main.loading_video = True
     del self.app.fastflix.config.profiles[self.app.fastflix.config.selected_profile]
     self.app.fastflix.config.selected_profile = "Standard Profile"
     self.app.fastflix.config.save()
     self.main.widgets.profile_box.clear()
     self.main.widgets.profile_box.addItems(self.app.fastflix.config.profiles.keys())
     self.main.loading_video = False
     self.main.widgets.profile_box.setCurrentText("Standard Profile")
示例#17
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.flat_ui.isChecked() != self.app.fastflix.config.flat_ui:
            restart_needed = True
        self.app.fastflix.config.flat_ui = self.flat_ui.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"{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_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()
示例#18
0
 def select_folder(self):
     if self.concat_area.table.model.rowCount() > 0:
         if not yes_no_message(
                 f"{t('There are already items in this list')},\n"
                 f"{t('if you open a new directory, they will all be removed.')}\n\n"
                 f"{t('Continue')}?",
                 "Confirm Change Folder",
         ):
             return
     folder_name = QtWidgets.QFileDialog.getExistingDirectory(
         self, dir=self.folder_name)
     if not folder_name:
         return
     self.folder_name = folder_name
     self.set_folder_name(folder_name)
     items = []
     skipped = []
     for file in Path(folder_name).glob("*"):
         if file.is_file():
             try:
                 details = self.get_video_details(file)
                 if not details:
                     raise Exception()
             except Exception:
                 logger.warning(
                     f"Skipping {file.name} as it is not a video/image file"
                 )
                 skipped.append(file.name)
             else:
                 items.append(details)
     self.concat_area.table.update_items(items)
     if skipped:
         error_message("".join([
             f"{t('The following items were excluded as they could not be identified as image or video files')}:\n",
             "\n".join(skipped[:20]),
             f"\n\n+ {len(skipped[20:])} {t('more')}..."
             if len(skipped) > 20 else "",
         ]))
示例#19
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()
示例#20
0
    def save(self):
        profile_name = self.profile_name.text().strip()
        if not profile_name:
            return error_message(t("Please provide a profile name"))
        if profile_name in self.app.fastflix.config.profiles:
            return error_message(f'{t("Profile")} {self.profile_name.text().strip()} {t("already exists")}')

        audio_lang = "en"
        audio_select = True
        audio_select_preferred_language = False
        if self.audio_language.currentIndex() == 2:  # None
            audio_select_preferred_language = False
            audio_select = False
        elif self.audio_language.currentIndex() != 0:
            audio_select_preferred_language = True
            audio_lang = Lang(self.audio_language.currentText()).pt2b

        sub_lang = "en"
        subtitle_select = True
        subtitle_select_preferred_language = False
        if self.sub_language.currentIndex() == 2:  # None
            subtitle_select_preferred_language = False
            subtitle_select = False
        elif self.sub_language.currentIndex() != 0:
            subtitle_select_preferred_language = True
            sub_lang = Lang(self.sub_language.currentText()).pt2b

        v_flip, h_flip = self.main.get_flips()

        new_profile = Profile(
            auto_crop=self.auto_crop.isChecked(),
            keep_aspect_ratio=self.main.widgets.scale.keep_aspect.isChecked(),
            fast_seek=self.main.fast_time,
            rotate=self.main.widgets.rotate.currentIndex(),
            vertical_flip=v_flip,
            horizontal_flip=h_flip,
            copy_chapters=self.main.copy_chapters,
            remove_metadata=self.main.remove_metadata,
            remove_hdr=self.main.remove_hdr,
            audio_language=audio_lang,
            audio_select=audio_select,
            audio_select_preferred_language=audio_select_preferred_language,
            audio_select_first_matching=self.audio_first_only.isChecked(),
            subtitle_language=sub_lang,
            subtitle_select=subtitle_select,
            subtitle_automatic_burn_in=self.sub_burn_in.isChecked(),
            subtitle_select_preferred_language=subtitle_select_preferred_language,
            subtitle_select_first_matching=self.sub_first_only.isChecked(),
            encoder=self.encoder.name,
        )

        if isinstance(self.encoder, x265Settings):
            new_profile.x265 = self.encoder
        elif isinstance(self.encoder, x264Settings):
            new_profile.x264 = self.encoder
        elif isinstance(self.encoder, rav1eSettings):
            new_profile.rav1e = self.encoder
        elif isinstance(self.encoder, SVTAV1Settings):
            new_profile.svt_av1 = self.encoder
        elif isinstance(self.encoder, VP9Settings):
            new_profile.vp9 = self.encoder
        elif isinstance(self.encoder, AOMAV1Settings):
            new_profile.aom_av1 = self.encoder
        elif isinstance(self.encoder, GIFSettings):
            new_profile.gif = self.encoder
        elif isinstance(self.encoder, WebPSettings):
            new_profile.webp = self.encoder
        elif isinstance(self.encoder, CopySettings):
            new_profile.copy_settings = self.encoder
        elif isinstance(self.encoder, NVEncCSettings):
            new_profile.nvencc_hevc = self.encoder
        elif isinstance(self.encoder, NVEncCAVCSettings):
            new_profile.nvencc_avc = self.encoder
        elif isinstance(self.encoder, FFmpegNVENCSettings):
            new_profile.ffmpeg_hevc_nvenc = self.encoder
        elif isinstance(self.encoder, VCEEncCSettings):
            new_profile.vceencc_hevc = self.encoder
        elif isinstance(self.encoder, VCEEncCAVCSettings):
            new_profile.vceencc_avc = self.encoder
        else:
            logger.error("Profile cannot be saved! Unknown encoder type.")
            return

        self.app.fastflix.config.profiles[profile_name] = new_profile
        self.app.fastflix.config.selected_profile = profile_name
        self.app.fastflix.config.save()
        self.main.widgets.profile_box.addItem(profile_name)
        self.main.widgets.profile_box.setCurrentText(profile_name)
        self.hide()
示例#21
0
 def new_profile(self):
     if not self.app.fastflix.current_video:
         error_message(t("Please load in a video to configure a new profile"))
     else:
         self.profile.show()
示例#22
0
def required_info(logger, data_path, log_dir):
    if reusables.win_based:
        # This fixes the taskbar icon not always appearing
        try:
            import ctypes

            app_id = f"cdgriffith.fastflix.{__version__}".encode("utf-8")
            ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(
                app_id)
        except Exception:
            logger.exception(
                "Could not set application ID for Windows, please raise issue in github with above error"
            )

    ffmpeg_folder = Path(user_data_dir("FFmpeg", appauthor=False,
                                       roaming=True))
    ffmpeg = shutil.which("ffmpeg")
    if ffmpeg:
        ffmpeg = Path(ffmpeg).resolve()
    ffprobe = shutil.which("ffprobe")
    if ffprobe:
        ffprobe = Path(ffprobe).resolve()

    if ffmpeg_folder.exists():
        for file in ffmpeg_folder.iterdir():
            if file.is_file() and file.name.lower() in ("ffmpeg",
                                                        "ffmpeg.exe"):
                ffmpeg = file
            if file.is_file() and file.name.lower() in ("ffprobe",
                                                        "ffprobe.exe"):
                ffprobe = file
        if (not ffmpeg or not ffprobe) and (ffmpeg_folder / "bin").exists():
            for file in (ffmpeg_folder / "bin").iterdir():
                if file.is_file() and file.name.lower() in ("ffmpeg",
                                                            "ffmpeg.exe"):
                    ffmpeg = file
                if file.is_file() and file.name.lower() in ("ffprobe",
                                                            "ffprobe.exe"):
                    ffprobe = file

    logger.addHandler(
        logging.FileHandler(log_dir / f"flix_gui_{file_date()}.log",
                            encoding="utf-8"))

    config_file = Path(data_path, "fastflix.json")
    logger.debug(f'Using config file "{config_file}"')
    if not config_file.exists():
        config = Box({
            "version": __version__,
            "work_dir": str(data_path),
            "disable_update_check": False
        })
        config.to_json(filename=config_file, indent=2)
    else:
        try:
            config = Box.from_json(filename=config_file)
        except JSONDecodeError as err:
            logger.exception(f'Error with config file: "{config_file}"')
            error_message(
                msg=f"Bad config file: {config_file}"
                "<br> If you are unsure what to do, just delete the file"
                f"<br><br>Error: {err}",
                traceback=True,
            )
            sys.exit(1)
        if "version" not in config or "work_dir" not in config:
            message(
                "Config file does not have all required fields, adding defaults"
            )
            config.version = __version__
            config.work_dir = str(data_path)
            config.disable_update_check = False
            config.to_json(filename=config_file, indent=2)
        if StrictVersion(config.version) < StrictVersion(__version__):
            message(
                f"<h2 style='text-align: center;'>Welcome to FastFlix {__version__}!</h2><br>"
                f"<p style='text-align: center; font-size: 15px;'>Please check out the changes made since your last "
                f"update ({config.version})<br>View the change log in the Help menu (Alt+H then C)<br></p>"
            )
            config.version = __version__
            config.to_json(filename=config_file, indent=2)
        if "ffmpeg" in config:
            ffmpeg = Path(config.ffmpeg)
        if "ffprobe" in config:
            ffprobe = Path(config.ffprobe)
    work_dir = Path(config.get("work_dir", data_path))
    if not work_dir.exists():
        try:
            work_dir.mkdir(parents=True, exist_ok=True)
        except OSError as err:
            logger.error(f"Cannot use specified working directory {work_dir}"
                         f" - Falling back to {data_path} due to error: {err}")
            work_dir = data_path
            work_dir.mkdir(parents=True, exist_ok=True)

    if not ffmpeg or not ffprobe:
        qm = QtWidgets.QMessageBox
        if reusables.win_based:
            ret = qm.question(
                None,
                "FFmpeg not found!",
                f"<h2>FFmpeg not found!</h2> <br> Automatically download FFmpeg?",
                qm.Yes | qm.No,
            )
            if ret == qm.Yes:
                try:
                    windows_download_ffmpeg(ffmpeg_folder)
                except Exception as err:
                    logger.exception("Could not download FFmpeg")
                    sys.exit(2)
                else:
                    ffmpeg = ffmpeg_folder / "bin" / "ffmpeg.exe"
                    ffprobe = ffmpeg_folder / "bin" / "ffprobe.exe"
            else:
                sys.exit(1)
        else:
            qm.question(
                None,
                "FFmpeg not found!",
                "<h2>FFmpeg not found!</h2> "
                "Please <a href='https://ffmpeg.org/download.html'>download a static FFmpeg</a> and add it to PATH",
                qm.Close,
            )
            sys.exit(1)
    else:
        logger.info(f"Using FFmpeg {ffmpeg}")
        logger.info(f"Using FFprobe {ffprobe}")

    try:
        flix = Flix(ffmpeg=ffmpeg, ffprobe=ffprobe)
    except FlixError:
        error_message("FFmpeg or FFmpeg could not be executed properly!<br>",
                      traceback=True)
        sys.exit(1)

    if not config.get("disable_update_check"):
        latest_fastflix()

    return flix, work_dir, config_file
示例#23
0
    def update_video_info(self):
        self.loading_video = True
        try:
            self.streams, self.format_info = self.flix.parse(
                self.input_video, work_dir=self.path.work, extract_covers=True
            )
        except FlixError:
            error_message(f"Not a video file<br>{self.input_video}")
            self.input_video = None
            self.video_path_widget.setText("No Source Selected")
            self.output_video_path_widget.setText("")
            self.output_path_button.setDisabled(True)
            self.output_video_path_widget.setDisabled(True)
            self.streams = None
            self.format_info = None
            for i in range(self.widgets.video_track.count()):
                self.widgets.video_track.removeItem(0)
            self.widgets.convert_button.setDisabled(True)
            self.widgets.convert_button.setStyleSheet("background-color:gray;")
            self.widgets.preview.setText("No Video File")
            self.page_update()
            return

        self.side_data = self.flix.parse_hdr_details(self.input_video)
        logger.debug(self.streams)
        logger.debug(self.format_info)

        text_video_tracks = [
            f"{x.index}: codec {x.codec_name} " f'- pix_fmt {x.get("pix_fmt")} ' f'- profile {x.get("profile")}'
            for x in self.streams.video
        ]

        for i in range(self.widgets.video_track.count()):
            self.widgets.video_track.removeItem(0)

        if len(self.streams.video) == 0:
            error_message(f"No video tracks detected in file<br>{self.input_video}")
            self.input_video = None
            self.video_path_widget.setText("No Source Selected")
            self.output_video_path_widget.setText("")
            self.output_path_button.setDisabled(True)
            self.output_video_path_widget.setDisabled(True)
            self.streams = None
            self.format_info = None
            self.widgets.convert_button.setDisabled(True)
            self.widgets.convert_button.setStyleSheet("background-color:gray;")
            self.widgets.preview.setText("No Video File")
            self.page_update()
            return

        # TODO set width and height by video track
        rotation = 0
        if "rotate" in self.streams.video[0].get("tags", {}):
            rotation = abs(int(self.streams.video[0].tags.rotate))
        # elif 'side_data_list' in self.streams.video[0]:
        #     rots = [abs(int(x.rotation)) for x in self.streams.video[0].side_data_list if 'rotation' in x]
        #     rotation = rots[0] if rots else 0

        if rotation in (90, 270):
            self.video_width = self.streams.video[0].height
            self.video_height = self.streams.video[0].width
        else:
            self.video_width = self.streams.video[0].width
            self.video_height = self.streams.video[0].height

        self.initial_video_width = self.video_width
        self.initial_video_height = self.video_height

        self.widgets.scale.width.setText(
            str(self.video_width + (self.video_width % self.plugins[self.convert_to].video_dimension_divisor))
        )
        self.widgets.scale.height.setText(
            str(self.video_height + (self.video_height % self.plugins[self.convert_to].video_dimension_divisor))
        )
        self.widgets.video_track.addItems(text_video_tracks)

        self.widgets.video_track.setDisabled(bool(len(self.streams.video) == 1))

        video_duration = float(self.format_info.get("duration", 0))
        self.initial_duration = video_duration

        logger.debug(f"{len(self.streams['video'])} video tracks found")
        logger.debug(f"{len(self.streams['audio'])} audio tracks found")
        if self.streams["subtitle"]:
            logger.debug(f"{len(self.streams['subtitle'])} subtitle tracks found")
        if self.streams["attachment"]:
            logger.debug(f"{len(self.streams['attachment'])} attachment tracks found")
        if self.streams["data"]:
            logger.debug(f"{len(self.streams['data'])} data tracks found")

        self.widgets.end_time.setText(self.number_to_time(video_duration))

        self.video_options.new_source()
        self.widgets.convert_button.setDisabled(False)
        self.widgets.convert_button.setStyleSheet("background-color:green;")
        self.loading_video = False
示例#24
0
    def save(self):
        profile_name = self.profile_name.text().strip()
        if not profile_name:
            return error_message(t("Please provide a profile name"))
        if profile_name in self.app.fastflix.config.profiles:
            return error_message(
                f'{t("Profile")} {self.profile_name.text().strip()} {t("already exists")}'
            )

        sub_lang = "en"
        subtitle_enabled = True
        subtitle_select_preferred_language = False
        if self.subtitle_select.sub_language.currentIndex() == 2:  # None
            subtitle_select_preferred_language = False
            subtitle_enabled = False
        elif self.subtitle_select.sub_language.currentIndex() != 0:
            subtitle_select_preferred_language = True
            sub_lang = Lang(
                self.subtitle_select.sub_language.currentText()).pt2b

        self.advanced_options.color_space = (
            None if self.advanced_tab.color_space_widget.currentIndex() == 0
            else self.advanced_tab.color_space_widget.currentText())
        self.advanced_options.color_transfer = (
            None if self.advanced_tab.color_transfer_widget.currentIndex() == 0
            else self.advanced_tab.color_transfer_widget.currentText())
        self.advanced_options.color_primaries = (
            None if self.advanced_tab.color_primaries_widget.currentIndex()
            == 0 else self.advanced_tab.color_primaries_widget.currentText())

        new_profile = Profile(
            profile_version=2,
            auto_crop=self.primary_tab.auto_crop.isChecked(),
            keep_aspect_ratio=self.main_settings.keep_aspect_ratio,
            fast_seek=self.main_settings.fast_seek,
            rotate=self.main_settings.rotate,
            vertical_flip=self.main_settings.vertical_flip,
            horizontal_flip=self.main_settings.horizontal_flip,
            copy_chapters=self.main_settings.copy_chapters,
            remove_metadata=self.main_settings.remove_metadata,
            remove_hdr=self.main_settings.remove_hdr,
            audio_filters=self.audio_select.get_settings(),
            # subtitle_filters=self.subtitle_select.get_settings(),
            subtitle_language=sub_lang,
            subtitle_select=subtitle_enabled,
            subtitle_automatic_burn_in=self.subtitle_select.sub_burn_in.
            isChecked(),
            subtitle_select_preferred_language=
            subtitle_select_preferred_language,
            subtitle_select_first_matching=self.subtitle_select.sub_first_only.
            isChecked(),
            encoder=self.encoder.name,
            advanced_options=self.advanced_options,
        )

        if isinstance(self.encoder, x265Settings):
            new_profile.x265 = self.encoder
        elif isinstance(self.encoder, x264Settings):
            new_profile.x264 = self.encoder
        elif isinstance(self.encoder, rav1eSettings):
            new_profile.rav1e = self.encoder
        elif isinstance(self.encoder, SVTAV1Settings):
            new_profile.svt_av1 = self.encoder
        elif isinstance(self.encoder, VP9Settings):
            new_profile.vp9 = self.encoder
        elif isinstance(self.encoder, AOMAV1Settings):
            new_profile.aom_av1 = self.encoder
        elif isinstance(self.encoder, GIFSettings):
            new_profile.gif = self.encoder
        elif isinstance(self.encoder, WebPSettings):
            new_profile.webp = self.encoder
        elif isinstance(self.encoder, CopySettings):
            new_profile.copy_settings = self.encoder
        elif isinstance(self.encoder, NVEncCSettings):
            new_profile.nvencc_hevc = self.encoder
        elif isinstance(self.encoder, QSVEncCSettings):
            new_profile.qsvencc_hevc = self.encoder
        elif isinstance(self.encoder, QSVEncCH264Settings):
            new_profile.qsvencc_avc = self.encoder
        elif isinstance(self.encoder, H264VideoToolboxSettings):
            new_profile.h264_videotoolbox = self.encoder
        elif isinstance(self.encoder, HEVCVideoToolboxSettings):
            new_profile.hevc_videotoolbox = self.encoder
        elif isinstance(self.encoder, NVEncCAVCSettings):
            new_profile.nvencc_avc = self.encoder
        elif isinstance(self.encoder, FFmpegNVENCSettings):
            new_profile.ffmpeg_hevc_nvenc = self.encoder
        elif isinstance(self.encoder, VCEEncCSettings):
            new_profile.vceencc_hevc = self.encoder
        elif isinstance(self.encoder, VCEEncCAVCSettings):
            new_profile.vceencc_avc = self.encoder
        else:
            logger.error("Profile cannot be saved! Unknown encoder type.")
            return

        self.app.fastflix.config.profiles[profile_name] = new_profile
        self.app.fastflix.config.selected_profile = profile_name
        self.app.fastflix.config.save()
        self.main.widgets.profile_box.addItem(profile_name)
        self.main.widgets.profile_box.setCurrentText(profile_name)
        self.hide()