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))
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)
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()
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)
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 "", ] ) )
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()
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
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))
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()
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)
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)
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 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}"
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)
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", )
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")
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()
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 "", ]))
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 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()
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()
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
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
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()