def settings_changed(self, setting, _): valid_keys = {SettingsKeys.encoder_path} if setting in valid_keys: self.encoder.setText(TransCodaSettings.get_encoder_name()) self.tool_bar.set_encode_state(file_count=self.main_panel.row_count(), encoder_name=TransCodaSettings.get_encoder_name(), output_dir=TransCodaSettings.get_output_dir())
def __init__(self, file, url=None): self.file = file self.file_name = os.path.split(file)[1] self.url = url mime_type = _mime_database.mimeTypeForFile(file) os_info = os.stat(file) self.file_size = os_info.st_size self.access_time = os_info.st_atime self.modify_time = os_info.st_mtime self.create_time = os_info.st_ctime self.mime_type_name = mime_type.name() self.mime_type_icon_name = mime_type.iconName() self.status = EncoderStatus.READING_METADATA self.output_file_dir = TransCodaSettings.get_output_dir() self.encoder = TransCodaSettings.get_encoder_name() self.encoder_props = TransCodaSettings.get_encoder( ) if self.encoder is not None else None self.output_file = "" self.update_output_file() self.history_result = None self.encode_start_time = None self.encode_end_time = None self.encode_output_size = None self.encode_command = None self.encode_percent = None self.encode_messages = None self.encode_cpu_time = None self.encode_compression_ratio = None self.meta_data = {}
def add_column(self, column): self.beginInsertColumns(QModelIndex(), len(self.columnHeaders), len(self.columnHeaders)) self.columnHeaders.append(column) self.endInsertColumns() TransCodaSettings.save_columns(self.columnHeaders) return len(self.columnHeaders) - 1
def _check_file(self): if not os.path.exists(self.file.file): raise TranscodaError( "Input File could not be found on the file system") # Overwrite if exists only if os.path.exists( self.file.output_file ) and not TransCodaSettings.get_overwrite_if_exists(): raise TranscodaError( f"Output file {self.file.output_file} already exists!") # Ensure path exists if self.file.encoder_props["extension"] == "": file_path = self.file.output_file else: file_path, _ = os.path.split(self.file.output_file) os.makedirs(file_path, exist_ok=True) # Check History if self.file.history_result is not None and TransCodaSettings.is_history_enforced( ): raise TranscodaError( f"{self.file.file} has been previously processed. Skipping this file.\n" f"Execution Details are: {self.file.history_result}")
def jobs_complete_event(self, all_results, time_taken): self.progressbar.setValue(self.progressbar.maximum()) self.executor = None self.statusBar().showMessage(f"Processed {len(all_results)} files in {time_taken} seconds", msecs=400) self.set_window_title() self.tool_bar.encoding_finished(file_count=self.main_panel.row_count(), encoder_name=TransCodaSettings.get_encoder_name(), output_dir=TransCodaSettings.get_output_dir()) self.reset_timer()
def do_work(self): self.file.encode_start_time = datetime.datetime.now() encoder = self.file.encoder_props try: self._check_file() except Exception as exception: self.emit_exception(exception) return # Start encoding try: if not self.file.is_supported(): copy_extensions = TransCodaSettings.get_copy_extensions() _, extension = os.path.splitext(self.file.output_file) if extension.startswith("."): extension = extension[1:] if extension.lower() in copy_extensions: executable = "copy" else: self.file.status = EncoderStatus.SKIPPED self.signals.result.emit([self.file]) return else: executable = encoder["executable"] self.file.encode_command = encoder["command"] if executable not in ProcessRunners.runners_registry: raise TranscodaError( f"Unable to find a process handler for executable {executable}" ) process_runner = ProcessRunners.runners_registry[executable] runner = process_runner( input_file=self.file.file, input_url=self.file.url, output_file=self.file.output_file, base_command=self.file.encode_command, delete_metadata=TransCodaSettings.get_delete_metadata()) runner.status_event.connect(self.status_event) runner.message_event.connect(self.log_message) runner.run() output_stat = os.stat(self.file.output_file) if TransCodaSettings.get_preserve_timestamps(): os.utime(self.file.output_file, (self.file.access_time, self.file.modify_time)) self.file.encode_end_time = datetime.datetime.now() self.file.encode_cpu_time = ( self.file.encode_end_time - self.file.encode_start_time).total_seconds() self.file.encode_compression_ratio = 100 - ( output_stat.st_size / (self.file.file_size + 1)) * 100 self.file.status = EncoderStatus.SUCCESS self.file.encode_output_size = output_stat.st_size self.file.encode_percent = 100 self.signals.result.emit([self.file]) except Exception as exception: self.emit_exception(exception)
def remove_column(self, column): del_index = -1 for index, col in enumerate(self.columnHeaders): if col == column: del_index = index break if del_index >= 0: self.beginRemoveColumns(QModelIndex(), del_index, del_index) del (self.columnHeaders[del_index]) self.endRemoveColumns() TransCodaSettings.save_columns(self.columnHeaders)
def files_changed_event(self, is_added, files): if is_added: self.statusBar().showMessage("Scanning files please wait...") scanner = CommonUtils.FileScanner(files, recurse=True, is_qfiles=True) self.statusBar().showMessage(f"Loading files into {TransCoda.__APP_NAME__}") # Add files first total_added = self.main_panel.add_files(scanner.files) # Fetch and enrich with metadata batches = [] for batch in CommonUtils.batch(list(scanner.files), batch_size=20): retriever = FileMetaDataExtractor(batch, batch_size=len(batch)) retriever.signals.result.connect(self.result_received_event) batches.append(retriever) # UX self.begin_tasks(batches, f"Fetching meta-data for {total_added} files", total_added) self.tool_bar.set_encode_state(file_count=self.main_panel.row_count(), encoder_name=TransCodaSettings.get_encoder_name(), output_dir=TransCodaSettings.get_output_dir())
def begin_tasks(self, tasks, status_message, total_size, threads=TransCodaSettings.get_max_threads()): TransCoda.logger.info(status_message) self.progressbar.setVisible(True) self.progressbar.setValue(0) self.progressbar.setMaximum(total_size) self.executor = CommonUtils.CommandExecutionFactory(tasks, logger=TransCoda.logger, max_threads=threads) self.executor.finish_event.connect(self.jobs_complete_event) self.statusBar().showMessage(status_message) self.executor.start()
def _get_icon(self, ico_name, system_fallback=True): file_name = f"{ico_name}.svg" theme_location = os.path.join(self.resource_dir, file_name) default_location = os.path.join(self.__DEFAULT_PATH__, file_name) use_system_theme = TransCodaSettings.use_system_theme() and system_fallback if not use_system_theme: if os.path.exists(theme_location): return QIcon(theme_location) elif os.path.exists(default_location): return QIcon(default_location) # Always fallback to system theme return QIcon.fromTheme(ico_name)
def init_ui(self): encoder_name = TransCodaSettings.get_encoder_name() self.main_panel.set_items(TransCodaSettings.get_encode_list()) self.main_panel.files_changed_event.connect(self.files_changed_event) self.main_panel.menu_item_event.connect(self.action_event) self.tool_bar.set_encode_state(file_count=self.main_panel.row_count(), encoder_name=encoder_name, output_dir=TransCodaSettings.get_output_dir()) self.tool_bar.button_pressed.connect(self.action_event) self.addToolBar(QtCore.Qt.LeftToolBarArea, self.tool_bar) self.setCentralWidget(self.main_panel) self.statusBar().addPermanentWidget(QVLine()) if encoder_name: self.encoder.setText(TransCodaSettings.get_encoder_name()) else: self.encoder.setText("No encoder selected.") self.terminal_btn.setIcon(TransCoda.theme.ico_terminal) self.terminal_btn.setFlat(True) self.terminal_btn.setToolTip("Show Encoder Logs") self.terminal_btn.clicked.connect(self.show_encode_logs) self.statusBar().addPermanentWidget(self.encoder, 0) self.statusBar().addPermanentWidget(QVLine()) self.statusBar().addPermanentWidget(self.progressbar, 0) self.statusBar().addPermanentWidget(self.terminal_btn, 0) self.setMinimumSize(800, 600) self.setWindowTitle(TransCoda.__APP_NAME__) self.setWindowIcon(TransCoda.theme.ico_app_icon) self.timer.timeout.connect(self.timer_timeout_event) self.timer.setInterval(6000) self.timer.setSingleShot(True) TransCodaSettings.settings.settings_change_event.connect(self.settings_changed) TransCodaSettings.settings.load_ui(self, TransCoda.logger) self.show() # Executed after the show, where all dimensions are calculated self.progressbar.setFixedWidth(self.progressbar.width()) self.progressbar.setVisible(False)
def __init__(self): super().__init__() self.file_items = [] self.sort_column = 0 self.sort_order = Qt.AscendingOrder self.columnHeaders = TransCodaSettings.get_columns() if not self.columnHeaders: self.columnHeaders = [ Header.input_file_name, Header.input_file_size, Header.input_bitrate, Header.input_duration, Header.percent_compete, Header.input_encoder, Header.output_file_dir, Header.output_file_size, Header.compression_ratio, Header.cpu_time, Header.input_file_type ]
def validate_and_start_encoding(self, run_indices=None): def create_runnable(_item): runnable = EncoderCommand(_item) runnable.signals.result.connect(self.result_received_event) runnable.signals.status.connect(self.status_received_event) runnable.signals.log_message.connect(self.terminal_view.log_message) return runnable runnables = [] is_video = False if run_indices is not None: self.main_panel.update_item_status(run_indices, EncoderStatus.WAITING) for index in run_indices: item = self.main_panel.get_items(index) is_video = is_video or item.is_video() runnables.append(create_runnable(item)) else: for index in range(0, self.main_panel.row_count()): item = self.main_panel.get_items(index) self.main_panel.update_item_status([index], EncoderStatus.WAITING) is_video = is_video or item.is_video() runnables.append(create_runnable(item)) if len(runnables) <= 0: self.statusBar().showMessage("Nothing to encode!") return if TransCodaSettings.sort_by_size(): runnables.sort(key=lambda x: x.file.file_size) self.tool_bar.encoding_started() if is_video and TransCodaSettings.is_single_thread_video(): self.begin_tasks(runnables, f"Dispatching {len(runnables)} jobs for serial encoding", len(runnables), threads=1) else: self.begin_tasks(runnables, f"Dispatching {len(runnables)} jobs for encoding", len(runnables), threads=TransCodaSettings.get_max_threads())
def update_output_file(self): if self.encoder is None or self.output_file_dir is None: return _, file_path = os.path.splitdrive(self.file) file_path, file = os.path.split(file_path) name, _ = os.path.splitext(file) output_dir = self.output_file_dir + file_path if TransCodaSettings.get_preserve_dir( ) else self.output_file_dir if self.encoder_props["extension"] == "*" or not self.is_supported(): self.output_file = os.path.join(output_dir, file) elif self.encoder_props["extension"] == "": self.output_file = os.path.join(output_dir, name) TransCoda.logger.warn( f"Encoder props do no specify file extension. " f"Encoder is expected to create the output file in {self.output_file}" ) else: self.output_file = os.path.join( output_dir, name + self.encoder_props["extension"])
def action_event(self, event, item_indices=None): if event == Action.ADD_FILE: file, _ = QFileDialog.getOpenFileUrl(caption="Select a File") if not file.isEmpty(): self.files_changed_event(True, [file]) elif event == Action.ADD_DIR: _dir = QFileDialog.getExistingDirectoryUrl(caption="Select a directory") if not _dir.isEmpty(): self.files_changed_event(True, [_dir]) elif event == Action.SETTINGS: TransCodaSettings.TransCodaSettings().exec() elif event == Action.DEL_ALL: self.main_panel.clear_table() elif event == Action.DEL_FILE: self.main_panel.remove_files(item_indices) elif event == Action.ENCODE: if self.executor is not None and self.executor.is_running(): self.executor.stop_scan() self.statusBar().showMessage("Stop command issued. Waiting for threads to finish") return self.validate_and_start_encoding(run_indices=item_indices) elif event == Action.CHANGE_STATUS_SUCCESS: self.main_panel.update_item_status(item_indices, EncoderStatus.SUCCESS) elif event == Action.CHANGE_STATUS_READY: self.main_panel.update_item_status(item_indices, EncoderStatus.READY) elif event == Action.OPEN_FILE: self.open_paths_in_system_default(item_indices, "file") elif event == Action.OPEN_SOURCE: self.open_paths_in_system_default(item_indices, "file", func=os.path.dirname) elif event == Action.OPEN_DEST: self.open_paths_in_system_default(item_indices, "output_file", func=os.path.dirname) elif event == Action.ABOUT: with open(os.path.join(os.path.dirname(__file__), "resource/about.html"), 'r') as file: about_html = file.read() QMessageBox.about(self, TransCoda.__APP_NAME__, about_html.format(APP_NAME=TransCoda.__APP_NAME__, VERSION=TransCoda.__VERSION__, YEAR=datetime.now().year))
def encoder_setting_changed(self): self.encoder = TransCodaSettings.get_encoder_name() self.encoder_props = TransCodaSettings.get_encoder() self.update_output_file()
def output_dir_changed(self): self.output_file_dir = TransCodaSettings.get_output_dir() self.update_output_file()
def closeEvent(self, close_event) -> None: TransCoda.logger.info("Saving encode list...") TransCodaSettings.save_encode_list(self.main_panel.get_items()) TransCoda.logger.info("Saving UI...") TransCodaSettings.settings.save_ui(self, TransCoda.logger)