class MainWindow(QMainWindow): def __init__(self): super(MainWindow, self).__init__() self.edl, self.video = '', '' self.parse_cmdline() self.init_logger() self.init_cutter() self.setWindowTitle('%s' % qApp.applicationName()) self.setContentsMargins(0, 0, 0, 0) self.statusBar().showMessage('Ready') statuslogo = QLabel(pixmap=QPixmap(':/images/vidcutter-emboss.png'), objectName='logowidget') self.statusBar().addPermanentWidget(statuslogo) self.statusBar().setStyleSheet('border:none;') self.setAcceptDrops(True) self.setMinimumSize(900, 640) self.show() try: if len(self.video): self.cutter.loadMedia(self.video) if len(self.edl): self.cutter.openEDL(edlfile=self.edl) except (FileNotFoundError, PermissionError) as e: QMessageBox.critical(self, 'Error loading file', sys.exc_info()[0]) logging.exception('Error loading file') qApp.restoreOverrideCursor() self.cutter.startNew() if not self.cutter.ffmpeg_check(): self.close() sys.exit(1) def init_logger(self) -> None: try: log_path = QStandardPaths.writableLocation( QStandardPaths.AppConfigLocation).lower() except AttributeError: if sys.platform == 'win32': log_path = os.path.join(QDir.homePath(), 'AppData', 'Local', qApp.applicationName().lower()) elif sys.platform == 'darwin': log_path = os.path.join(QDir.homePath(), 'Library', 'Preferences', qApp.applicationName()).lower() else: log_path = os.path.join(QDir.homePath(), '.config', qApp.applicationName()).lower() os.makedirs(log_path, exist_ok=True) handlers = [ logging.handlers.RotatingFileHandler(os.path.join( log_path, '%s.log' % qApp.applicationName().lower()), maxBytes=1000000, backupCount=1) ] if os.getenv('DEBUG', False): handlers.append(logging.StreamHandler()) logging.basicConfig( handlers=handlers, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M', level=logging.INFO) logging.captureWarnings(capture=True) sys.excepthook = self.log_uncaught_exceptions @staticmethod def log_uncaught_exceptions(cls, exc, tb) -> None: logging.critical(''.join(traceback.format_tb(tb))) logging.critical('{0}: {1}'.format(cls, exc)) def parse_cmdline(self) -> None: self.parser = QCommandLineParser() self.parser.setApplicationDescription( 'The simply FAST & ACCURATE video cutter & joiner') self.parser.addPositionalArgument('video', 'Preloads the video file in app.', '[video]') self.edl_option = QCommandLineOption( 'edl', 'Preloads clip index from a previously saved EDL file.\n' + 'NOTE: You must also set the video argument for this to work.', 'edl file') self.debug_option = QCommandLineOption( ['d', 'debug'], 'Output all info, warnings and errors to the console. ' + 'This will basically output what is being logged to file to the ' + 'console stdout. Mainly useful for debugging problems with your ' + 'system video and/or audio stack and codec configuration.') self.parser.addOption(self.edl_option) self.parser.addOption(self.debug_option) self.parser.addVersionOption() self.parser.addHelpOption() self.parser.process(qApp) self.args = self.parser.positionalArguments() if self.parser.value('edl').strip() and not os.path.exists( self.parser.value('edl')): print('\n ERROR: EDL file not found.\n', file=sys.stderr) self.close() sys.exit(1) if self.parser.value('edl').strip() and len(self.args) == 0: print('\n ERROR: Video file argument is missing.\n', file=sys.stderr) self.close() sys.exit(1) if self.parser.value('edl').strip(): self.edl = self.parser.value('edl') if self.parser.isSet(self.debug_option): os.environ['DEBUG'] = '1' if len(self.args) > 0 and not os.path.exists(self.args[0]): print('\n ERROR: Video file not found.\n', file=sys.stderr) self.close() sys.exit(1) if len(self.args) > 0: self.video = self.args[0] def init_cutter(self) -> None: self.cutter = VideoCutter(self) qApp.setWindowIcon(self.cutter.appIcon) self.setCentralWidget(self.cutter) @staticmethod def get_bitness() -> int: from struct import calcsize return calcsize('P') * 8 def restart(self) -> None: self.cutter.deleteLater() self.init_cutter() @staticmethod def get_path(path: str = None, override: bool = False) -> str: if override: if getattr(sys, 'frozen', False): return os.path.join(sys._MEIPASS, path) return os.path.join(QFileInfo(__file__).absolutePath(), path) return ':%s' % path @staticmethod def load_stylesheet(qssfile: str) -> None: if QFileInfo(qssfile).exists(): qss = QFile(qssfile) qss.open(QFile.ReadOnly | QFile.Text) qApp.setStyleSheet(QTextStream(qss).readAll()) @staticmethod def get_version(filename: str = '__init__.py') -> str: with open(MainWindow.get_path(filename, override=True), 'r', encoding='utf-8') as initfile: for line in initfile.readlines(): m = re.match('__version__ *= *[\'](.*)[\']', line) if m: return m.group(1) def contextMenuEvent(self, event: QContextMenuEvent) -> None: if event.reason() == QContextMenuEvent.Mouse: self.cutter.appMenu.exec_(event.globalPos()) event.accept() super(MainWindow, self).contextMenuEvent(event) def mousePressEvent(self, event: QMouseEvent): if event.button() == Qt.LeftButton: self.cutter.cliplist.clearSelection() def dragEnterEvent(self, event: QDragEnterEvent) -> None: if event.mimeData().hasUrls(): event.accept() def dropEvent(self, event: QDropEvent) -> None: filename = event.mimeData().urls()[0].toLocalFile() self.cutter.loadMedia(filename) event.accept() def closeEvent(self, event: QCloseEvent) -> None: if hasattr(self, 'cutter'): if hasattr(self.cutter, 'mediaPlayer'): self.cutter.mediaPlayer.terminate() qApp.quit()
class MainWindow(QMainWindow): EXIT_CODE_REBOOT = 666 def __init__(self): super(MainWindow, self).__init__() self.video, self.devmode = '', False self.parse_cmdline() self.init_logger() self.init_settings() self.init_scale() self.init_cutter() self.setWindowTitle('%s' % qApp.applicationName()) self.setContentsMargins(0, 0, 0, 0) self.statusBar().showMessage('Ready') self.statusBar().setStyleSheet('border: none; padding: 0; margin: 0;') self.setAcceptDrops(True) self.show() self.console.setGeometry(int(self.x() - (self.width() / 2)), self.y() + int(self.height() / 3), 750, 300) try: if len(self.video): if QFileInfo(self.video).suffix() == 'vcp': self.cutter.openProject(project_file=self.video) else: self.cutter.loadMedia(self.video) except (FileNotFoundError, PermissionError) as e: QMessageBox.critical(self, 'Error loading file', sys.exc_info()[0]) logging.exception('Error loading file') qApp.restoreOverrideCursor() self.restart() if not self.cutter.ffmpeg_check(): qApp.exit(1) def init_scale(self) -> None: screen_size = qApp.desktop().availableGeometry(-1) self.scale = 'LOW' if screen_size.width() <= 1024 else 'NORMAL' self.setMinimumSize(self.get_size(self.scale)) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) @staticmethod def get_size(mode: str = 'NORMAL') -> QSize: modes = { 'LOW': QSize(800, 425), 'NORMAL': QSize(915, 680), 'HIGH': QSize(1850, 1300) } return modes[mode] def init_logger(self) -> None: try: log_path = QStandardPaths.writableLocation(QStandardPaths.AppConfigLocation).lower() except AttributeError: if sys.platform == 'win32': log_path = os.path.join(QDir.homePath(), 'AppData', 'Local', qApp.applicationName().lower()) elif sys.platform == 'darwin': log_path = os.path.join(QDir.homePath(), 'Library', 'Preferences', qApp.applicationName()).lower() else: log_path = os.path.join(QDir.homePath(), '.config', qApp.applicationName()).lower() os.makedirs(log_path, exist_ok=True) self.console = ConsoleWidget(self) self.consoleLogger = ConsoleHandler(self.console) handlers = [logging.handlers.RotatingFileHandler(os.path.join(log_path, '%s.log' % qApp.applicationName().lower()), maxBytes=1000000, backupCount=1), self.consoleLogger] if self.parser.isSet(self.debug_option): handlers.append(logging.StreamHandler()) logging.basicConfig(handlers=handlers, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M', level=logging.INFO) logging.captureWarnings(capture=True) sys.excepthook = self.log_uncaught_exceptions def init_settings(self) -> None: if sys.platform == 'darwin': QSettings.setDefaultFormat(QSettings.IniFormat) self.settings = QSettings(self) else: try: settings_path = QStandardPaths.writableLocation(QStandardPaths.AppConfigLocation).lower() except AttributeError: if sys.platform == 'win32': settings_path = os.path.join(QDir.homePath(), 'AppData', 'Local', qApp.applicationName().lower()) elif sys.platform == 'darwin': settings_path = os.path.join(QDir.homePath(), 'Library', 'Preferences', qApp.applicationName()).lower() else: settings_path = os.path.join(QDir.homePath(), '.config', qApp.applicationName()).lower() os.makedirs(settings_path, exist_ok=True) settings_file = '%s.ini' % qApp.applicationName().lower() self.settings = QSettings(os.path.join(settings_path, settings_file), QSettings.IniFormat) if self.settings.value('geometry') is not None: self.restoreGeometry(self.settings.value('geometry')) if self.settings.value('windowState') is not None: self.restoreState(self.settings.value('windowState')) self.theme = self.settings.value('theme', 'light', type=str) self.startupvol = self.settings.value('volume', 100, type=int) @staticmethod def log_uncaught_exceptions(cls, exc, tb) -> None: logging.critical(''.join(traceback.format_tb(tb))) logging.critical('{0}: {1}'.format(cls, exc)) def parse_cmdline(self) -> None: self.parser = QCommandLineParser() self.parser.setApplicationDescription('\nVidCutter - the simplest + fastest video cutter & joiner') self.parser.addPositionalArgument('video', 'Preload video file', '[video]') self.parser.addPositionalArgument('project', 'Open VidCutter project file (.vcp)', '[project]') self.debug_option = QCommandLineOption(['debug'], 'debug mode; verbose console output & logging. ' + 'This will basically output what is being logged to file to the ' + 'console stdout. Mainly useful for debugging problems with your ' + 'system video and/or audio stack and codec configuration.') self.dev_option = QCommandLineOption(['dev'], 'developer mode; disables the use of compiled resource files ' + 'so that all app resources & assets are accessed directly from the file ' + 'system allowing you to see UI changes immediately. this typically ' + 'relates to changes made to Qt stylesheets (.qss), layout/templates, ' + 'content includes and images. basically all assets defined in .qrc ' + 'files throughout the codebase.') self.parser.addOption(self.debug_option) self.parser.addOption(self.dev_option) self.parser.addVersionOption() self.parser.addHelpOption() self.parser.process(qApp) self.args = self.parser.positionalArguments() if self.parser.isSet(self.debug_option): os.environ['DEBUG'] = '1' if self.parser.isSet(self.dev_option): self.devmode = True if len(self.args) > 0: file_path = QFileInfo(self.args[0]).absoluteFilePath() if not os.path.exists(file_path): sys.stderr.write('\nERROR: File not found: %s\n' % file_path) self.close() sys.exit(1) self.video = file_path def init_cutter(self) -> None: self.cutter = VideoCutter(self) self.cutter.errorOccurred.connect(self.errorHandler) # qApp.setWindowIcon(QIcon(':/images/vidcutter.png')) qApp.setWindowIcon(QIcon.fromTheme(qApp.applicationName().lower(), QIcon(':/images/vidcutter.png'))) self.setCentralWidget(self.cutter) @staticmethod def get_bitness() -> int: from struct import calcsize return calcsize('P') * 8 @pyqtSlot() def reboot(self) -> None: self.save_settings() qApp.exit(MainWindow.EXIT_CODE_REBOOT) def save_settings(self) -> None: self.settings.setValue('lastFolder', self.cutter.lastFolder) self.settings.setValue('geometry', self.saveGeometry()) self.settings.setValue('windowState', self.saveState()) self.settings.sync() @staticmethod def get_path(path: str = None, override: bool = False) -> str: if override: if getattr(sys, 'frozen', False): return os.path.join(sys._MEIPASS, path) return os.path.join(QFileInfo(__file__).absolutePath(), path) return ':%s' % path @staticmethod def get_version(filename: str = '__init__.py') -> str: with open(MainWindow.get_path(filename, override=True), 'r', encoding='utf-8') as initfile: for line in initfile.readlines(): m = re.match('__version__ *= *[\'](.*)[\']', line) if m: return m.group(1) @pyqtSlot(str) def errorHandler(self, msg: str) -> None: QMessageBox.critical(self, 'An error occurred', msg, QMessageBox.Ok) logging.error(msg) def contextMenuEvent(self, event: QContextMenuEvent) -> None: if event.reason() == QContextMenuEvent.Mouse: self.cutter.appMenu.exec_(event.globalPos()) event.accept() super(MainWindow, self).contextMenuEvent(event) def mousePressEvent(self, event: QMouseEvent) -> None: if event.button() == Qt.LeftButton and self.cutter.mediaAvailable: self.cutter.cliplist.clearSelection() self.cutter.timeCounter.clearFocus() self.cutter.frameCounter.clearFocus() def dragEnterEvent(self, event: QDragEnterEvent) -> None: if event.mimeData().hasUrls(): event.accept() def dropEvent(self, event: QDropEvent) -> None: filename = event.mimeData().urls()[0].toLocalFile() self.cutter.loadMedia(filename) event.accept() def resizeEvent(self, event: QResizeEvent) -> None: try: if self.cutter.mediaAvailable and self.cutter.thumbnailsButton.isChecked(): self.cutter.seekSlider.reloadThumbs() except AttributeError: pass def closeEvent(self, event: QCloseEvent) -> None: event.accept() self.console.deleteLater() if hasattr(self, 'cutter'): self.save_settings() if hasattr(self.cutter, 'mpvWidget'): self.cutter.mpvWidget.shutdown() qApp.quit()