def __init__(self, name, subtitleData, parent = None): super(FileList, self).__init__(name, parent) self._subtitleData = subtitleData self._settings = SubSettings() self.__initGui() self.__connectSignals()
def __init__(self, subFormats, parent = None): super().__init__(parent) self._settings = SubSettings() self.__createPropertyFilesDirectory() self.__initSubFormats(subFormats) self.__initGui()
def __initGui(self): self._settings = SubSettings() self.mainWidget = QWidget(self) mainLayout = QVBoxLayout() mainLayout.setContentsMargins(3, 3, 3, 3) mainLayout.setSpacing(2) self.setCentralWidget(self.mainWidget) self._videoWidget = VideoWidget(self) self._tabs = SubtitleWindow.SubTabWidget(self._subtitleData) mainLayout.addWidget(self._videoWidget, 1) mainLayout.addWidget(self._tabs, 3) self.statusBar() self.mainWidget.setLayout(mainLayout) self.setAcceptDrops(True) self.setWindowIcon(QIcon(":/img/logo.png")) self.setWindowTitle('Subconvert')
class PropertyFileEditor(QDialog): def __init__(self, subFormats, parent = None): super().__init__(parent) self._settings = SubSettings() self.__createPropertyFilesDirectory() self.__initSubFormats(subFormats) self.__initGui() def __createPropertyFilesDirectory(self): pfileDir = self._settings.getPropertyFilesPath() try: os.makedirs(pfileDir) except OSError as exc: if exc.errno == errno.EEXIST and os.path.isdir(pfileDir): pass else: raise def __initSubFormats(self, formats): self._formats = {} for f in formats: self._formats[f.NAME] = f def __initGui(self): layout = QVBoxLayout() layout.setSpacing(10) layout.addWidget(self._createFpsBox()) layout.addWidget(self._createEncodingBox()) layout.addWidget(self._createFormatBox()) layout.addWidget(self._createButtons()) self.setLayout(layout) self.setWindowTitle(_("Subtitle Properties Editor")) self.setModal(True) # Some signals self._closeButton.clicked.connect(self.close) self._openButton.clicked.connect(self.openProperties) self._saveButton.clicked.connect(self.saveProperties) self._autoEncoding.toggled.connect(self._inputEncoding.setDisabled) self._changeEncoding.toggled.connect(self._outputEncoding.setEnabled) def _createFpsBox(self): groupbox = QGroupBox(_("FPS")) layout = QHBoxLayout() self._autoFps = QCheckBox(_("Auto FPS"), self) self._fps = QComboBox(self) self._fps.addItems(["23.976", "24", "25", "29.97", "30"]) self._fps.setEditable(True) layout.addWidget(self._autoFps) layout.addWidget(self._fps) groupbox.setLayout(layout) return groupbox def _createEncodingBox(self): groupbox = QGroupBox(_("File Encoding")) layout = QGridLayout() self._autoEncoding = QCheckBox(_("Auto input encoding"), self) self._inputEncoding = QComboBox(self) self._inputEncoding.addItems(ALL_ENCODINGS) self._inputEncoding.setDisabled(self._autoEncoding.isChecked()) inputLabel = QLabel(_("Input encoding")) self._changeEncoding = QCheckBox(_("Change encoding on save"), self) self._outputEncoding = QComboBox(self) self._outputEncoding.addItems(ALL_ENCODINGS) self._outputEncoding.setEnabled(self._changeEncoding.isChecked()) outputLabel = QLabel(_("Output encoding")) layout.addWidget(self._autoEncoding, 0, 0) layout.addWidget(self._inputEncoding, 1, 0) layout.addWidget(inputLabel, 1, 1) layout.addWidget(self._changeEncoding, 2, 0) layout.addWidget(self._outputEncoding, 3, 0) layout.addWidget(outputLabel, 3, 1) groupbox.setLayout(layout) return groupbox def _createFormatBox(self): groupbox = QGroupBox(_("Subtitle format")) layout = QGridLayout() displayedFormats = list(self._formats.keys()) displayedFormats.sort() self._outputFormat = QComboBox(self) self._outputFormat.addItems(displayedFormats) formatLabel = QLabel(_("Output format")) layout.addWidget(self._outputFormat, 0, 0) layout.addWidget(formatLabel, 0, 1) groupbox.setLayout(layout) return groupbox def _createButtons(self): widget = QWidget(self) layout = QHBoxLayout() self._openButton = QPushButton(_("Open")) self._saveButton = QPushButton(_("Save")) self._closeButton = QPushButton(_("Close")) layout.addWidget(self._openButton) layout.addWidget(self._saveButton) layout.addWidget(self._closeButton) widget.setLayout(layout) return widget def _createSubtitleProperties(self): subProperties = SubtitleProperties(list(self._formats.values())) subProperties.autoFps = self._autoFps.isChecked() subProperties.fps = self._fps.currentText() subProperties.autoInputEncoding = self._autoEncoding.isChecked() subProperties.changeEncoding = self._changeEncoding.isChecked() subProperties.inputEncoding = self._inputEncoding.currentText() subProperties.outputEncoding = self._outputEncoding.currentText() subProperties.outputFormat = self._formats.get(self._outputFormat.currentText()) return subProperties def changeProperties(self, subProperties): self._autoFps.setChecked(subProperties.autoFps) self._fps.setEditText(str(subProperties.fps)) self._autoEncoding.setChecked(subProperties.autoInputEncoding) self._changeEncoding.setChecked(subProperties.changeEncoding) self._inputEncoding.setCurrentIndex( self._inputEncoding.findText(subProperties.inputEncoding)) self._outputEncoding.setCurrentIndex( self._outputEncoding.findText(subProperties.outputEncoding)) if self._formats.get(subProperties.outputFormat.NAME) is subProperties.outputFormat: self._outputFormat.setCurrentIndex( self._outputFormat.findText(subProperties.outputFormat.NAME)) else: self.close() raise RuntimeError(_("Subtitle format (%s) doesn't match any of known formats!") % subProperties.outputFormat.NAME) def saveProperties(self): subProperties = None try: subProperties = self._createSubtitleProperties() except Exception as e: dialog = QMessageBox(self) dialog.setIcon(QMessageBox.Critical) dialog.setWindowTitle(_("Incorrect value")) dialog.setText(_("Could not save SPF file because of incorrect parameters.")); dialog.setDetailedText(str(e)); dialog.exec() return fileDialog = FileDialog( parent = self, caption = _('Save Subtitle Properties'), directory = self._settings.getPropertyFilesPath() ) fileDialog.setAcceptMode(QFileDialog.AcceptSave) fileDialog.setFileMode(QFileDialog.AnyFile) if fileDialog.exec(): filename = fileDialog.selectedFiles()[0] if not filename.endswith(".spf"): filename = "%s%s" % (filename, ".spf") self._settings.setPropertyFilesPath(os.path.dirname(filename)) subProperties.save(filename) self._settings.addPropertyFile(filename) self.close() def openProperties(self): fileDialog = FileDialog( parent = self, caption = _("Open Subtitle Properties"), directory = self._settings.getPropertyFilesPath(), filter = _("Subtitle Properties (*.spf);;All files (*)") ) fileDialog.setFileMode(QFileDialog.ExistingFile) if fileDialog.exec(): filename = fileDialog.selectedFiles()[0] self._settings.setPropertyFilesPath(os.path.dirname(filename)) subProperties = SubtitleProperties(list(self._formats.values()), filename) self.changeProperties(subProperties)
class MainWindow(QMainWindow): def __init__(self, args, parser): super(MainWindow, self).__init__() log.debug(_("Theme search paths: %s") % QIcon.themeSearchPaths()) log.debug(_("Used theme name: '%s'") % QIcon.themeName()) self.setObjectName("main_window") self._subtitleData = DataController(parser, self) self.__initGui() self.__initActions() self.__initMenuBar() self.__initShortcuts() self.__updateMenuItemsState() self.__connectSignals() self.restoreWidgetState() self.handleArgs(args) def __initGui(self): self._settings = SubSettings() self.mainWidget = QWidget(self) mainLayout = QVBoxLayout() mainLayout.setContentsMargins(3, 3, 3, 3) mainLayout.setSpacing(2) self.setCentralWidget(self.mainWidget) self._videoWidget = VideoWidget(self) self._tabs = SubtitleWindow.SubTabWidget(self._subtitleData, self._videoWidget) mainLayout.addWidget(self._videoWidget, 1) mainLayout.addWidget(self._tabs, 3) self.statusBar() self.mainWidget.setLayout(mainLayout) self.setAcceptDrops(True) self.setWindowIcon(QIcon(":/img/logo.png")) self.setWindowTitle('Subconvert') def __connectSignals(self): self._tabs.tabChanged.connect(self.__updateMenuItemsState) self._tabs.tabChanged.connect(self.__updateWindowTitle) self._tabs.fileList.selectionChanged.connect(self.__updateMenuItemsState) self._subtitleData.fileAdded.connect(self.__updateMenuItemsState, Qt.QueuedConnection) self._subtitleData.fileChanged.connect(self.__updateMenuItemsState, Qt.QueuedConnection) self._subtitleData.fileRemoved.connect(self.__updateMenuItemsState, Qt.QueuedConnection) self._subtitleData.subtitlesAdded.connect(self.__updateMenuItemsState, Qt.QueuedConnection) self._subtitleData.subtitlesRemoved.connect( self.__updateMenuItemsState, Qt.QueuedConnection) self._subtitleData.subtitlesChanged.connect( self.__updateMenuItemsState, Qt.QueuedConnection) def __initActions(self): self._actions = {} af = ActionFactory(self) # open / save self._actions["openFile"] = af.create( "document-open", _("&Open"), _("Open subtitle file."), "ctrl+o", self.openFile) self._actions["saveFile"] = af.create( "document-save", _("&Save"), _("Save current file."), "ctrl+s", self.saveFile) self._actions["saveFileAs"] = af.create( "document-save-as",_("S&ave as..."), _("Save current file as..."), "ctrl++shift+s", self.saveFileAs) self._actions["saveAllFiles"] = af.create( "document-save", _("Sa&ve all"), _("Save all opened files."), None, self.saveAll) # app exit self._actions["exit"] = af.create( "application-exit", _("&Exit"), _("Exit Subconvert."), None, qApp.quit) # tab management self._actions["nextTab"] = af.create( None, None, None, "ctrl+tab", self.nextTab) self._actions["previousTab"] = af.create( None, None, None, "ctrl+shift+tab", self.previousTab) self._actions["closeTab"] = af.create( None, None, None, "ctrl+w", self.closeTab) # Subtitles self._actions["undo"] = af.create( "undo", _("&Undo"), None, "ctrl+z", self.undo) self._actions["redo"] = af.create( "redo", _("&Redo"), None, "ctrl+shift+z", self.redo) for fps in FPS_VALUES: fpsStr = str(fps) self._actions[fpsStr] = af.create( None, fpsStr, None, None, lambda _, fps=fps: self.changeFps(fps)) for encoding in ALL_ENCODINGS: self._actions["in_%s" % encoding] = af.create( None, encoding, None, None, lambda _, enc = encoding: self.changeInputEncoding(enc)) self._actions["out_%s" % encoding] = af.create( None, encoding, None, None, lambda _, enc = encoding: self.changeOutputEncoding(enc)) for fmt in self._subtitleData.supportedFormats: self._actions[fmt.NAME] = af.create( None, fmt.NAME, None, None, lambda _, fmt = fmt: self.changeSubFormat(fmt)) self._actions["linkVideo"] = af.create( None, _("&Link video"), None, "ctrl+l", self.linkVideo) self._actions["unlinkVideo"] = af.create( None, _("U&nlink video"), None, "ctrl+u", lambda: self._setVideoLink(None)) self._actions["fpsFromMovie"] = af.create( None, _("&Get FPS"), None, "ctrl+g", self.getFpsFromMovie) self._actions["insertSub"] = af.create( "list-add", _("&Insert subtitle"), None, "insert", connection = lambda: self._tabs.currentPage().insertNewSubtitle()) self._actions["addSub"] = af.create( "list-add", _("&Add subtitle"), None, "alt+insert", connection = lambda: self._tabs.currentPage().addNewSubtitle()) self._actions["offset"] = af.create( None, _("&Offset"), None, None, self.offset) self._actions["removeSub"] = af.create( "list-remove", _("&Remove subtitles"), None, "delete", connection = lambda: self._tabs.currentPage().removeSelectedSubtitles()) self._actions["findSub"] = af.create( "edit-find", _("&Find..."), None, "ctrl+f", connection = lambda: self._tabs.currentPage().highlight()) # Video self._videoRatios = [(4, 3), (14, 9), (14, 10), (16, 9), (16, 10)] self._actions["openVideo"] = af.create( "document-open", _("&Open video"), None, "ctrl+m", self.openVideo) self._actions["togglePlayback"] = af.create( "media-playback-start", _("&Play/pause"), _("Toggle video playback"), "space", self._videoWidget.togglePlayback) self._actions["forward"] = af.create( "media-skip-forward", _("&Forward"), None, "ctrl+right", self._videoWidget.forward) self._actions["rewind"] = af.create( "media-skip-backward", _("&Rewind"), None, "ctrl+left", self._videoWidget.rewind) self._actions["frameStep"] = af.create( None, _("Next &frame"), _("Go to the next frame in a video"), ".", self._videoWidget.nextFrame) for ratio in self._videoRatios: self._actions["changeRatio_%d_%d" % ratio] = af.create( None, "%d:%d" % ratio, None, None, lambda _, r=ratio: self._videoWidget.changePlayerAspectRatio(r[0], r[1])) self._actions["changeRatio_fill"] = af.create( None, _("Fill"), None, None, self._videoWidget.fillPlayer) self._actions["videoJump"] = af.create( None, _("&Jump to subtitle"), None, "ctrl+j", self.jumpToSelectedSubtitle) # SPF editor self._actions["spfEditor"] = af.create( "accessories-text-editor", _("Subtitle &Properties Editor"), None, None, self.openPropertyEditor) # View self._actions["togglePlayer"] = af.create( None, _("&Video player"), _("Show or hide video player"), "F3", self.togglePlayer) self._actions["togglePanel"] = af.create( None, _("Side &panel"), _("Show or hide left panel"), "F4", self._tabs.togglePanel) # Help self._actions["helpPage"] = af.create( "help-contents", _("&Help"), _("Open &help page"), "F1", self.openHelp) self._actions["aboutSubconvert"] = af.create( "help-about", _("&About Subconvert"), None, None, self.openAboutDialog) def __initMenuBar(self): menubar = self.menuBar() fileMenu = menubar.addMenu(_('&File')) fileMenu.addAction(self._actions["openFile"]) fileMenu.addSeparator() fileMenu.addAction(self._actions["saveFile"]) fileMenu.addAction(self._actions["saveFileAs"]) fileMenu.addAction(self._actions["saveAllFiles"]) fileMenu.addSeparator() fileMenu.addAction(self._actions["exit"]) subtitlesMenu = menubar.addMenu(_("&Subtitles")) subtitlesMenu.addAction(self._actions["undo"]) subtitlesMenu.addAction(self._actions["redo"]) subtitlesMenu.addSeparator() subtitlesMenu.addAction(self._actions["insertSub"]) subtitlesMenu.addAction(self._actions["addSub"]) subtitlesMenu.addAction(self._actions["removeSub"]) subtitlesMenu.addAction(self._actions["findSub"]) subtitlesMenu.addSeparator() self._fpsMenu = subtitlesMenu.addMenu(_("&Frames per second")) self._fpsMenu.addSeparator() for fps in FPS_VALUES: self._fpsMenu.addAction(self._actions[str(fps)]) self._subFormatMenu = subtitlesMenu.addMenu(_("Subtitles forma&t")) for fmt in self._subtitleData.supportedFormats: self._subFormatMenu.addAction(self._actions[fmt.NAME]) self._inputEncodingMenu = subtitlesMenu.addMenu(_("Input &encoding")) self._outputEncodingMenu = subtitlesMenu.addMenu(_("&Output encoding")) for encoding in ALL_ENCODINGS: self._inputEncodingMenu.addAction(self._actions["in_%s" % encoding]) self._outputEncodingMenu.addAction(self._actions["out_%s" % encoding]) subtitlesMenu.addAction(self._actions["offset"]) subtitlesMenu.addSeparator() subtitlesMenu.addAction(self._actions["linkVideo"]) subtitlesMenu.addAction(self._actions["unlinkVideo"]) subtitlesMenu.addAction(self._actions["fpsFromMovie"]) subtitlesMenu.addSeparator() videoMenu = menubar.addMenu(_("&Video")) videoMenu.addAction(self._actions["openVideo"]) videoMenu.addSeparator() playbackMenu = videoMenu.addMenu(_("&Playback")) playbackMenu.addAction(self._actions["togglePlayback"]) playbackMenu.addSeparator() playbackMenu.addAction(self._actions["forward"]) playbackMenu.addAction(self._actions["rewind"]) playbackMenu.addAction(self._actions["frameStep"]) self._ratioMenu = videoMenu.addMenu(_("&Aspect ratio")) for ratio in self._videoRatios: self._ratioMenu.addAction(self._actions["changeRatio_%d_%d" % ratio]) self._ratioMenu.addSeparator() self._ratioMenu.addAction(self._actions["changeRatio_fill"]) videoMenu.addSeparator() videoMenu.addAction(self._actions["videoJump"]) viewMenu = menubar.addMenu(_("Vie&w")) viewMenu.addAction(self._actions["togglePlayer"]) viewMenu.addAction(self._actions["togglePanel"]) toolsMenu = menubar.addMenu(_("&Tools")) toolsMenu.addAction(self._actions["spfEditor"]) helpMenu = menubar.addMenu(_("&Help")) helpMenu.addAction(self._actions["helpPage"]) helpMenu.addAction(self._actions["aboutSubconvert"]) def __initShortcuts(self): self.addAction(self._actions["nextTab"]) self.addAction(self._actions["previousTab"]) self.addAction(self._actions["closeTab"]) def cleanup(self): self._videoWidget.close() def __getAllSubExtensions(self): formats = self._subtitleData.supportedFormats exts = [_('Default')] exts.extend(set([ f.EXTENSION for f in formats ])) exts.sort() return exts def _writeFile(self, filePath, newFilePath=None): if newFilePath is None: newFilePath = filePath data = self._subtitleData.data(filePath) converter = SubConverter() content = converter.convert(data.outputFormat, data.subtitles) if File.exists(newFilePath): file_ = File(newFilePath) file_.overwrite(content, data.outputEncoding) else: File.write(newFilePath, content, data.outputEncoding) self._subtitleData.setCleanState(filePath) self.__updateMenuItemsState() def __updateWindowTitle(self): tab = self._tabs.currentPage() if tab.isStatic: self.setWindowTitle("Subconvert") else: self.setWindowTitle("%s - Subconvert" % tab.name) def _openFiles(self, paths, encoding): unsuccessfullFiles = [] for filePath in paths: # TODO: separate reading file and adding to the list. # TODO: there should be readDataFromFile(filePath, properties=None), # TODO: which should set default properties from Subtitle Properties File command = NewSubtitles(filePath, encoding) try: self._subtitleData.execute(command) except DoubleFileEntry: pass # file already opened except Exception as e: log.exception(e) unsuccessfullFiles.append("%s: %s" % (filePath, str(e))) if len(unsuccessfullFiles) > 0: dialog = CannotOpenFilesMsg(self) dialog.setFileList(unsuccessfullFiles) dialog.exec() def _setVideoLink(self, videoPath): currentTab = self._tabs.currentPage() if currentTab.isStatic: currentTab.changeSelectedFilesVideoPath(videoPath) else: currentTab.changeVideoPath(videoPath) @pyqtSlot() def __updateMenuItemsState(self): tab = self._tabs.currentPage() dataAvailable = self._subtitleData.count() != 0 anyTabOpen = tab is not None tabIsStatic = tab.isStatic if anyTabOpen else False if tabIsStatic: cleanState = False anyItemSelected = len(tab.selectedItems) > 0 else: cleanState = tab.history.isClean() anyItemSelected = False canUndo = (tabIsStatic and anyItemSelected) or (not tabIsStatic and tab.history.canUndo()) canRedo = (tabIsStatic and anyItemSelected) or (not tabIsStatic and tab.history.canRedo()) canEdit = (tabIsStatic and anyItemSelected) or (not tabIsStatic) self._actions["saveAllFiles"].setEnabled(dataAvailable) self._actions["saveFile"].setEnabled(not tabIsStatic and not cleanState) self._actions["saveFileAs"].setEnabled(not tabIsStatic) self._actions["undo"].setEnabled(canUndo) self._actions["redo"].setEnabled(canRedo) self._fpsMenu.setEnabled(canEdit) self._subFormatMenu.setEnabled(canEdit) self._inputEncodingMenu.setEnabled(canEdit) self._outputEncodingMenu.setEnabled(canEdit) self._actions["offset"].setEnabled(canEdit) self._actions["linkVideo"].setEnabled(canEdit) self._actions["unlinkVideo"].setEnabled(canEdit) self._actions["fpsFromMovie"].setEnabled(canEdit) self._actions["insertSub"].setEnabled(not tabIsStatic) self._actions["addSub"].setEnabled(not tabIsStatic) self._actions["removeSub"].setEnabled(not tabIsStatic) self._actions["findSub"].setEnabled(not tabIsStatic) self._actions["videoJump"].setEnabled(not tabIsStatic) def closeEvent(self, ev): self.saveWidgetState() def saveWidgetState(self): self._settings.setGeometry(self, self.saveGeometry()) self._settings.setState(self, self.saveState()) self._videoWidget.saveWidgetState(self._settings) self._tabs.saveWidgetState(self._settings) def restoreWidgetState(self): self.restoreGeometry(self._settings.getGeometry(self)) self.restoreState(self._settings.getState(self)) self._videoWidget.restoreWidgetState(self._settings) self._tabs.restoreWidgetState(self._settings) def handleArgs(self, args): self._openFiles(args.files, args.inputEncoding) def nextTab(self): if self._tabs.count() > 0: index = self._tabs.currentIndex() + 1 if index > self._tabs.count() - 1: index = 0 self._tabs.showTab(index) def previousTab(self): if self._tabs.count() > 0: index = self._tabs.currentIndex() - 1 if index < 0: index = self._tabs.count() - 1 self._tabs.showTab(index) def closeTab(self): if self._tabs.count() > 0: index = self._tabs.currentIndex() self._tabs.closeTab(index) def openFile(self): sub_extensions = self.__getAllSubExtensions() str_sub_exts = ' '.join(['*.%s' % ext for ext in sub_extensions[1:]]) fileDialog = FileDialog( parent = self, caption = _("Open file"), directory = self._settings.getLatestDirectory(), filter = _("Subtitles (%s);;All files (*)") % str_sub_exts ) fileDialog.addEncodings(True) fileDialog.setFileMode(QFileDialog.ExistingFiles) if fileDialog.exec(): filenames = fileDialog.selectedFiles() encoding = fileDialog.getEncoding() self._settings.setLatestDirectory(os.path.dirname(filenames[0])) self._openFiles(filenames, encoding) @pyqtSlot() def saveFile(self, newFilePath = None): currentTab = self._tabs.currentPage() try: self._writeFile(currentTab.filePath, newFilePath) except SubException as msg: dialog = QMessageBox(self) dialog.setIcon(QMessageBox.Critical) dialog.setWindowTitle(_("Couldn't save file")) dialog.setText(str(msg)) dialog.exec() @pyqtSlot() def saveFileAs(self): fileDialog = FileDialog( parent = self, caption = _('Save as...'), directory = self._settings.getLatestDirectory() ) currentTab = self._tabs.currentPage() fileDialog.addFormats(self._subtitleData.supportedFormats) fileDialog.setSubFormat(currentTab.outputFormat) fileDialog.addEncodings(False) fileDialog.setEncoding(currentTab.outputEncoding) fileDialog.setAcceptMode(QFileDialog.AcceptSave) fileDialog.setFileMode(QFileDialog.AnyFile) if fileDialog.exec(): newFilePath = fileDialog.selectedFiles()[0] data = currentTab.data outputFormat = fileDialog.getSubFormat() outputEncoding = fileDialog.getEncoding() # user can overwrite previous output encoding if data.outputFormat != outputFormat or data.outputEncoding != outputEncoding: # save user changes data.outputFormat = outputFormat data.outputEncoding = outputEncoding if self._subtitleData.fileExists(newFilePath): command = ChangeData(newFilePath, data, _("Overwritten by %s") % currentTab.name) else: command = CreateSubtitlesFromData(newFilePath, data) self._subtitleData.execute(command) self._tabs.openTab(newFilePath) self.saveFile(newFilePath) self._settings.setLatestDirectory(os.path.dirname(newFilePath)) def saveAll(self): dialog = MessageBoxWithList(self) dialog.setIcon(QMessageBox.Critical) # TODO # When asked to save file, it should be should checked if it's parsed and parse it if it's # not (in future the parsing moment might be moved to increase responsibility when opening # a lot of files - i.e. only a file list will be printed and files will be actually parsed # on their first use (e.g. on tab open, fps change etc.). I'll have to think about it). # END OF TODO for filePath in self._tabs.fileList.filePaths: try: if not self._subtitleData.isCleanState(filePath): self._writeFile(filePath) except SubException as msg: dialog.addToList(str(msg)) if dialog.listCount() > 0: dialog.setWindowTitle(P_( "Error on saving a file", "Error on saving files", dialog.listCount() )) dialog.setText(P_( "Following error occured when trying to save a file:", "Following errors occured when trying to save files:", dialog.listCount() )) dialog.exec() def undo(self): currentTab = self._tabs.currentPage() if currentTab.isStatic: currentTab.undoSelectedFiles() else: currentTab.history.undo() def redo(self): currentTab = self._tabs.currentPage() if currentTab.isStatic: currentTab.redoSelectedFiles() else: currentTab.history.redo() def changeInputEncoding(self, encoding): currentTab = self._tabs.currentPage() if currentTab.isStatic: currentTab.changeSelectedFilesInputEncoding(encoding) else: currentTab.changeInputEncoding(encoding) def changeOutputEncoding(self, encoding): currentTab = self._tabs.currentPage() if currentTab.isStatic: currentTab.changeSelectedFilesOutputEncoding(encoding) else: currentTab.changeOutputEncoding(encoding) def changeSubFormat(self, fmt): currentTab = self._tabs.currentPage() if currentTab.isStatic: currentTab.changeSelectedFilesFormat(fmt) else: currentTab.changeSubFormat(fmt) def offset(self): currentTab = self._tabs.currentPage() # fps isn't used, but we need one to init starting FrameTime dialog = OffsetDialog(self, FrameTime(25, seconds=0)) if dialog.exec(): if currentTab.isStatic: currentTab.offsetSelectedFiles(dialog.frametime.fullSeconds) else: currentTab.offset(dialog.frametime.fullSeconds) def changeFps(self, fps): currentTab = self._tabs.currentPage() if currentTab.isStatic: currentTab.changeSelectedFilesFps(fps) else: currentTab.changeFps(fps) def togglePlayer(self): if self._videoWidget.isHidden(): self._videoWidget.show() else: self._videoWidget.hide() def getFpsFromMovie(self): currentTab = self._tabs.currentPage() if currentTab.isStatic: currentTab.detectSelectedFilesFps() else: currentTab.detectFps() def openVideo(self): movieExtensions = "%s%s" % ("*.", ' *.'.join(File.MOVIE_EXTENSIONS)) fileDialog = FileDialog( parent = self, caption = _("Select a video"), directory = self._settings.getLatestDirectory(), filter = _("Video files (%s);;All files (*)") % movieExtensions) fileDialog.setFileMode(QFileDialog.ExistingFile) if fileDialog.exec(): movieFilePath = fileDialog.selectedFiles()[0] self._videoWidget.openFile(movieFilePath) def jumpToSelectedSubtitle(self): currentTab = self._tabs.currentPage() subtitleList = currentTab.selectedSubtitles() if len(subtitleList) > 0: self._videoWidget.jumpTo(subtitleList[0].start) def openPropertyEditor(self): editor = PropertyFileEditor(self._subtitleData.supportedFormats, self) editor.exec() def openAboutDialog(self): spacer = QSpacerItem(650, 0) dialog = QMessageBox(self) dialog.setIconPixmap(QPixmap(":/img/logo.png")) dialog.setWindowTitle(_("About Subconvert")) dialog.setText(aboutText) dialogLayout = dialog.layout() dialogLayout.addItem(spacer, dialogLayout.rowCount(), 0, 1, dialogLayout.columnCount()) dialog.exec() def openHelp(self): url = "https://github.com/mgoral/subconvert/wiki" if QDesktopServices.openUrl(QUrl(url)) is False: dialog = QMessageBox(self) dialog.setIcon(QMessageBox.Critical) dialog.setWindowTitle(_("Couldn't open URL")) dialog.setText(_("""Failed to open URL: <a href="%(url)s">%(url)s</a>.""") % {"url": url}) dialog.exec() def dragEnterEvent(self, event): mime = event.mimeData() # search for at least local file in dragged urls (and accept drag event only in that case) if mime.hasUrls(): urls = mime.urls() for url in urls: if url.isLocalFile() and os.path.isfile(url.toLocalFile()): event.acceptProposedAction() return def dropEvent(self, event): mime = event.mimeData() urls = mime.urls() subPaths = [] moviePaths = [] for url in urls: if url.isLocalFile(): filePath = url.toLocalFile() if not os.path.isdir(filePath): fileName, fileExtension = os.path.splitext(filePath) if fileExtension.strip('.').lower() in File.MOVIE_EXTENSIONS: moviePaths.append(filePath) else: subPaths.append(filePath) # open all subtitles and only the first movie if len(moviePaths) > 0: self._videoWidget.openFile(moviePaths[0]) if len(subPaths) > 0: self._openFiles(subPaths, None) def linkVideo(self): movieExtensions = "%s%s" % ("*.", ' *.'.join(File.MOVIE_EXTENSIONS)) fileDialog = FileDialog( parent = self, caption = _("Select a video"), directory = self._settings.getLatestDirectory(), filter = _("Video files (%s);;All files (*)") % movieExtensions) fileDialog.setFileMode(QFileDialog.ExistingFile) if fileDialog.exec(): movieFilePath = fileDialog.selectedFiles()[0] self._setVideoLink(movieFilePath)
class FileList(SubTab): requestOpen = pyqtSignal(str, bool) requestRemove = pyqtSignal(str) selectionChanged = pyqtSignal() def __init__(self, name, subtitleData, parent = None): super(FileList, self).__init__(name, parent) self._subtitleData = subtitleData self._settings = SubSettings() self.__initGui() self.__connectSignals() def __initGui(self): self._contextMenu = None mainLayout = QVBoxLayout(self) mainLayout.setContentsMargins(0, 3, 0, 0) mainLayout.setSpacing(0) self.__fileList = SubtitleList() fileListHeader = self.__fileList.header() self.__resizeHeader(fileListHeader) self.__fileList.setSelectionMode(QAbstractItemView.ExtendedSelection) self.__fileList.setColumnCount(4) self.__fileList.setHeaderLabels([ _("File name"), _("Input encoding"), _("Output encoding"), _("Subtitle format"), _("FPS")]) mainLayout.addWidget(self.__fileList) self.setContextMenuPolicy(Qt.CustomContextMenu) self.setLayout(mainLayout) def __resizeHeader(self, header): # TODO: add an option (in subconvert settings) to set the following: # header.setResizeMode(0, QHeaderView.ResizeToContents); header.setStretchLastSection(False) header.setDefaultSectionSize(130) header.resizeSection(0, 500) def __initContextMenu(self): if self._contextMenu is not None: self._contextMenu.deleteLater() self._contextMenu = None self._contextMenu = QMenu() af = ActionFactory(self) selectedItems = self.__fileList.selectedItems() anyItemSelected = len(selectedItems) > 0 # Open in tab actionOpenInTab = af.create( icon = "window-new", title = _("&Open in tab"), connection = self.requestOpeningSelectedFiles) actionOpenInTab.setEnabled(anyItemSelected) self._contextMenu.addAction(actionOpenInTab) self._contextMenu.addSeparator() # Property Files pfileMenu = self._contextMenu.addMenu(_("Use Subtitle &Properties")) pfileMenu.setEnabled(anyItemSelected) for pfile in self._settings.getLatestPropertyFiles(): # A hacky way to store pfile in lambda action = af.create( title = pfile, connection = lambda _, pfile=pfile: self._useSubProperties(pfile) ) pfileMenu.addAction(action) pfileMenu.addSeparator() pfileMenu.addAction(af.create( title = _("Open file"), connection = self._chooseSubProperties)) self._contextMenu.addSeparator() # Single properties fpsMenu = self._contextMenu.addMenu(_("&Frames per second")) fpsMenu.setEnabled(anyItemSelected) for fps in FPS_VALUES: fpsStr = str(fps) action = af.create( title = fpsStr, connection = lambda _, fps=fps: self.changeSelectedFilesFps(fps)) fpsMenu.addAction(action) formatsMenu = self._contextMenu.addMenu(_("Subtitles forma&t")) formatsMenu.setEnabled(anyItemSelected) for fmt in self._subtitleData.supportedFormats: action = af.create( title = fmt.NAME, connection = lambda _, fmt=fmt: self.changeSelectedFilesFormat(fmt) ) formatsMenu.addAction(action) inputEncodingsMenu = self._contextMenu.addMenu(_("Input &encoding")) inputEncodingsMenu.setEnabled(anyItemSelected) outputEncodingsMenu = self._contextMenu.addMenu(_("&Output encoding")) outputEncodingsMenu.setEnabled(anyItemSelected) for encoding in ALL_ENCODINGS: outAction = af.create( title = encoding, connection = lambda _, enc=encoding: self.changeSelectedFilesOutputEncoding(enc) ) outputEncodingsMenu.addAction(outAction) inAction = af.create( title = encoding, connection = lambda _, enc=encoding: self.changeSelectedFilesInputEncoding(enc) ) inputEncodingsMenu.addAction(inAction) offset = af.create(None, _("&Offset"), None, None, self._offsetDialog) offset.setEnabled(anyItemSelected) self._contextMenu.addAction(offset) self._contextMenu.addSeparator() # Link/unlink video actionLink = af.create(None, _("&Link video"), None, None, self.linkVideo) actionLink.setEnabled(anyItemSelected) self._contextMenu.addAction(actionLink) actionLink = af.create( None, _("U&nlink video"), None, None, lambda: self.changeSelectedFilesVideoPath(None)) actionLink.setEnabled(anyItemSelected) self._contextMenu.addAction(actionLink) actionLink = af.create(None, _("&Get FPS"), None, None, self.detectSelectedFilesFps) actionLink.setEnabled(anyItemSelected) self._contextMenu.addAction(actionLink) self._contextMenu.addSeparator() # Show/Remove files # Key shortcuts are actually only a hack to provide some kind of info to user that he can # use "enter/return" and "delete" to open/close subtitles. Keyboard is handled via # keyPressed -> _handleKeyPress. This is because __fileList has focus most of time anyway # (I think...) actionOpen = af.create( None, _("&Show subtitles"), None, "Enter", lambda: self._handleKeyPress(Qt.Key_Enter)) actionOpen.setEnabled(anyItemSelected) self._contextMenu.addAction(actionOpen) actionClose = af.create( None, _("&Close subtitles"), None, "Delete", lambda: self._handleKeyPress(Qt.Key_Delete)) actionClose.setEnabled(anyItemSelected) self._contextMenu.addAction(actionClose) self._contextMenu.addSeparator() # Undo/redo actionUndo = af.create("undo", _("&Undo"), None, None, self.undoSelectedFiles) actionUndo.setEnabled(anyItemSelected) self._contextMenu.addAction(actionUndo) actionRedo = af.create("redo", _("&Redo"), None, None, self.redoSelectedFiles) actionRedo.setEnabled(anyItemSelected) self._contextMenu.addAction(actionRedo) def __connectSignals(self): self.__fileList.mouseButtonDoubleClicked.connect(self._handleDoubleClick) self.__fileList.mouseButtonClicked.connect(self._handleClick) self.__fileList.keyPressed.connect(self._handleKeyPress) self.__fileList.selectionModel().selectionChanged.connect(self._selectionChangedHandle) self.customContextMenuRequested.connect(self._showContextMenu) self._subtitleData.fileAdded.connect(self._addFile) self._subtitleData.fileRemoved.connect(self._removeFile) self._subtitleData.fileChanged.connect(self._updateFile) def _addFile(self, filePath): data = self._subtitleData.data(filePath) item = QTreeWidgetItem( [filePath, data.inputEncoding, data.outputEncoding, data.outputFormat.NAME, str(data.fps)]) item.setToolTip(0, filePath) subtitleIcon = QIcon(":/img/ok.png") item.setIcon(0, subtitleIcon) videoIcon = QIcon(":/img/film.png") if data.videoPath is not None else QIcon() item.setIcon(4, videoIcon) self.__fileList.addTopLevelItem(item) self._subtitleData.history(filePath).cleanChanged.connect( lambda clean: self._cleanStateChanged(filePath, clean)) def _removeFile(self, filePath): items = self.__fileList.findItems(filePath, Qt.MatchExactly) for item in items: index = self.__fileList.indexOfTopLevelItem(item) toDelete = self.__fileList.takeTopLevelItem(index) toDelete = None def _updateFile(self, filePath): items = self.__fileList.findItems(filePath, Qt.MatchExactly) if len(items) > 0: data = self._subtitleData.data(filePath) for item in items: item.setText(1, data.inputEncoding) item.setText(2, data.outputEncoding) item.setText(3, data.outputFormat.NAME) item.setText(4, str(data.fps)) videoIcon = QIcon(":/img/film.png") if data.videoPath is not None else QIcon() item.setIcon(4, videoIcon) def _cleanStateChanged(self, filePath, clean): items = self.__fileList.findItems(filePath, Qt.MatchExactly) for item in items: if clean: icon = QIcon(":/img/ok.png") else: icon = QIcon(":/img/not_clean.png") item.setIcon(0, icon) def _selectionChangedHandle(self, selected, deselected): self.selectionChanged.emit() @property def selectedItems(self): return self.__fileList.selectedItems() def canClose(self): return False @property def isStatic(self): return True def getCurrentFile(self): return self.__fileList.currentItem() def _handleClick(self, button): item = self.__fileList.currentItem() if item is not None and button == Qt.MiddleButton: self.requestOpen.emit(item.text(0), True) def _handleDoubleClick(self, button): item = self.__fileList.currentItem() if item is not None and button == Qt.LeftButton: self.requestOpen.emit(item.text(0), False) def _handleKeyPress(self, key): items = self.__fileList.selectedItems() if key in (Qt.Key_Enter, Qt.Key_Return): for item in items: self.requestOpen.emit(item.text(0), False) elif key == Qt.Key_Delete: for item in items: self.requestRemove.emit(item.text(0)) def _showContextMenu(self): self.__initContextMenu() # redraw menu self._contextMenu.exec(QCursor.pos()) def changeSelectedSubtitleProperties(self, subProperties): # TODO: indicate the change somehow items = self.__fileList.selectedItems() applier = PropertiesFileApplier(subProperties) for item in items: filePath = item.text(0) data = self._subtitleData.data(filePath) applier.applyFor(filePath, data) command = ChangeData(filePath, data, _("Property file: %s") % filePath) self._subtitleData.execute(command) def _chooseSubProperties(self): fileDialog = FileDialog( parent = self, caption = _("Open Subtitle Properties"), directory = self._settings.getPropertyFilesPath(), filter = _("Subtitle Properties (*.spf);;All files (*)") ) fileDialog.setFileMode(QFileDialog.ExistingFile) if fileDialog.exec(): filename = fileDialog.selectedFiles()[0] self._useSubProperties(filename) def _useSubProperties(self, propertyPath): if propertyPath: try: subProperties = SubtitleProperties( self._subtitleData.supportedFormats, propertyPath) except: log.error(_("Cannot read %s as Subtitle Property file.") % propertyPath) self._settings.removePropertyFile(propertyPath) return # Don't change the call order. We don't want to change settings or redraw context menu # if something goes wrong. self.changeSelectedSubtitleProperties(subProperties) self._settings.addPropertyFile(propertyPath) @property def filePaths(self): fileList = self.__fileList # shorten notation return [fileList.topLevelItem(i).text(0) for i in range(fileList.topLevelItemCount())] def linkVideo(self): movieExtensions = "%s%s" % ("*.", ' *.'.join(File.MOVIE_EXTENSIONS)) fileDialog = FileDialog( parent = self, caption = _("Select a video"), directory = self._settings.getLatestDirectory(), filter = _("Video files (%s);;All files (*)") % movieExtensions) fileDialog.setFileMode(QFileDialog.ExistingFile) if fileDialog.exec(): movieFilePath = fileDialog.selectedFiles()[0] self.changeSelectedFilesVideoPath(movieFilePath) def requestOpeningSelectedFiles(self): # Open all files, but focus only the last one items = self.__fileList.selectedItems() for item in items: self.requestOpen.emit(item.text(0), True) self.requestOpen.emit(items[-1].text(0), False) def changeSelectedFilesFps(self, fps): items = self.__fileList.selectedItems() for item in items: filePath = item.text(0) data = self._subtitleData.data(filePath) if data.fps != fps: data.subtitles.changeFps(fps) data.fps = fps command = ChangeData(filePath, data, _("FPS: %s") % fps) self._subtitleData.execute(command) def changeSelectedFilesVideoPath(self, path): items = self.__fileList.selectedItems() for item in items: filePath = item.text(0) data = self._subtitleData.data(filePath) if data.videoPath != path: data.videoPath = path command = ChangeData(filePath, data, _("Video path: %s") % path) self._subtitleData.execute(command) def _offsetDialog(self): dialog = OffsetDialog(self, FrameTime(25, seconds=0)) if dialog.exec(): self.offsetSelectedFiles(dialog.frametime.fullSeconds) def offsetSelectedFiles(self, seconds): if seconds == 0: return items = self.__fileList.selectedItems() for item in items: filePath = item.text(0) data = self._subtitleData.data(filePath) fps = data.subtitles.fps if fps is None: log.error(_("No FPS for '%s' (empty subtitles)." % filePath)) continue ft = FrameTime(fps, seconds=seconds) data.subtitles.offset(ft) command = ChangeData(filePath, data, _("Offset by: %s") % ft.toStr()) self._subtitleData.execute(command) def detectSelectedFilesFps(self): items = self.__fileList.selectedItems() for item in items: filePath = item.text(0) data = self._subtitleData.data(filePath) if data.videoPath is not None: fpsInfo = File.detectFpsFromMovie(data.videoPath) if data.videoPath != fpsInfo.videoPath or data.fps != fpsInfo.fps: data.videoPath = fpsInfo.videoPath data.subtitles.changeFps(fpsInfo.fps) data.fps = fpsInfo.fps command = ChangeData(filePath, data, _("Detected FPS: %s") % data.fps) self._subtitleData.execute(command) def changeSelectedFilesFormat(self, fmt): items = self.__fileList.selectedItems() for item in items: filePath = item.text(0) data = self._subtitleData.data(filePath) if data.outputFormat != fmt: data.outputFormat = fmt command = ChangeData(filePath, data, _("Format: %s ") % fmt.NAME) self._subtitleData.execute(command) def changeSelectedFilesInputEncoding(self, inputEncoding): items = self.__fileList.selectedItems() for item in items: filePath = item.text(0) data = self._subtitleData.data(filePath) if data.inputEncoding != inputEncoding: try: data.encode(inputEncoding) except UnicodeDecodeError: # TODO: indicate with something more than log entry log.error(_("Cannot decode subtitles to '%s' encoding.") % inputEncoding) else: command = ChangeData(filePath, data, _("Input encoding: %s") % inputEncoding) self._subtitleData.execute(command) def changeSelectedFilesOutputEncoding(self, outputEncoding): items = self.__fileList.selectedItems() for item in items: filePath = item.text(0) data = self._subtitleData.data(filePath) if data.outputEncoding != outputEncoding: data.outputEncoding = outputEncoding command = ChangeData(filePath, data, _("Output encoding: %s") % outputEncoding) self._subtitleData.execute(command) def undoSelectedFiles(self): items = self.__fileList.selectedItems() for item in items: filePath = item.text(0) history = self._subtitleData.history(filePath) if history.canUndo(): history.undo() def redoSelectedFiles(self): items = self.__fileList.selectedItems() for item in items: filePath = item.text(0) history = self._subtitleData.history(filePath) if history.canRedo(): history.redo()
def saveWidgetState(self, settings): settings = SubSettings() settings.setHidden(self, self.isHidden())