class QTimeInputDialog(QDialog): def __init__(self, title='', description='', initial=QtCore.QTime(), minValue=QtCore.QTime(), maxValue=QtCore.QTime(), dformat='mm.ss', parent=None): super().__init__(parent) self.setWindowTitle(title) self.setWindowModality(QtCore.Qt.ApplicationModal) self.setMaximumSize(300, 110) self.setMinimumSize(300, 110) self.resize(300, 130) self.label = QLabel(description, self) self.label.setGeometry(10, 0, 280, 20) self.label.setAlignment(QtCore.Qt.AlignCenter) self.time = QTimeEdit(self) self.time.setDisplayFormat(dformat) self.time.setMaximumTime(maxValue) self.time.setMinimumTime(minValue) self.time.setTime(initial) self.time.setGeometry(10, 30, 280, 25) self.acceptButton = QPushButton(self) self.acceptButton.setGeometry(QtCore.QRect(180, 80, 100, 25)) self.acceptButton.setText("Ok") self.rejectButton = QPushButton(self) self.rejectButton.setGeometry(QtCore.QRect(60, 80, 100, 25)) self.rejectButton.setText("Cancel") self.rejectButton.clicked.connect(self.reject) self.acceptButton.clicked.connect(self.accept)
def createEditor(self, parent, option, index): editor = QTimeEdit(parent=parent) editor.setMinimumTime(datetime.time(hour=8, minute=30, second=30)) editor.setMaximumTime(datetime.time(hour=23, minute=30, second=30)) editor.setDisplayFormat("HH:mm:ss") # setFrame(): tell whether the line edit draws itself with a frame. # If enabled (the default) the line edit draws itself inside a frame, otherwise the line edit draws itself without any frame. editor.setFrame(False) return editor
class Window(QWidget): def __init__(self): super().__init__() # Make widgets ################# self.edit1 = QTimeEdit() self.edit2 = QTimeEdit() self.edit3 = QTimeEdit() self.edit1.setMinimumTime(datetime.time(hour=8, minute=30, second=30)) self.edit2.setMinimumTime(datetime.time(hour=8, minute=30, second=30)) self.edit3.setMinimumTime(datetime.time(hour=8, minute=30, second=30)) self.edit1.setMaximumTime(datetime.time(hour=18, minute=30, second=30)) self.edit2.setMaximumTime(datetime.time(hour=18, minute=30, second=30)) self.edit3.setMaximumTime(datetime.time(hour=18, minute=30, second=30)) self.edit1.setTime(datetime.datetime.now().time()) self.edit2.setTime(datetime.datetime.now().time()) self.edit3.setTime(datetime.datetime.now().time()) # Format: see http://doc.qt.io/qt-5/qdatetime.html#toString-2 self.edit1.setDisplayFormat("HH:mm") self.edit2.setDisplayFormat("HH:mm:ss t") self.edit3.setDisplayFormat("h m AP") self.btn = QPushButton("Print") # Set button slot ############## self.btn.clicked.connect(self.printText) # Set the layout ############### vbox = QVBoxLayout() vbox.addWidget(self.edit1) vbox.addWidget(self.edit2) vbox.addWidget(self.edit3) vbox.addWidget(self.btn) self.setLayout(vbox) def printText(self): print(self.edit1.text()) print(self.edit2.text()) print(self.edit3.text())
class VCTimeCounter(QWidget): timeChanged = pyqtSignal(QTime) def __init__(self, parent=None): super(VCTimeCounter, self).__init__(parent) self.parent = parent self.timeedit = QTimeEdit(QTime(0, 0)) self.timeedit.setObjectName('timeCounter') self.timeedit.setStyle(QStyleFactory.create('Fusion')) self.timeedit.setFrame(False) self.timeedit.setDisplayFormat('hh:mm:ss.zzz') self.timeedit.timeChanged.connect(self.timeChangeHandler) separator = QLabel('/') separator.setObjectName('timeSeparator') self.duration = QLabel('00:00:00.000') self.duration.setObjectName('timeDuration') layout = QHBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(5) layout.addWidget(self.timeedit) layout.addWidget(separator) layout.addWidget(self.duration) self.setLayout(layout) def setRange(self, minval: str, maxval: str) -> None: self.timeedit.setTimeRange(QTime.fromString(minval, 'hh:mm:ss.zzz'), QTime.fromString(maxval, 'hh:mm:ss.zzz')) def setMinimum(self, val: str = None) -> None: if val is None: self.timeedit.setMinimumTime(QTime(0, 0)) else: self.timeedit.setMinimumTime(QTime.fromString(val, 'hh:mm:ss.zzz')) def setMaximum(self, val: str) -> None: self.timeedit.setMaximumTime(QTime.fromString(val, 'hh:mm:ss.zzz')) def setTime(self, time: str) -> None: self.timeedit.setTime(QTime.fromString(time, 'hh:mm:ss.zzz')) def setDuration(self, time: str) -> None: self.duration.setText(time) self.setMaximum(time) def clearFocus(self) -> None: self.timeedit.clearFocus() def hasFocus(self) -> bool: if self.timeedit.hasFocus(): return True return super(VCTimeCounter, self).hasFocus() def reset(self) -> None: self.timeedit.setTime(QTime(0, 0)) self.setDuration('00:00:00.000') def setReadOnly(self, readonly: bool) -> None: self.timeedit.setReadOnly(readonly) if readonly: self.timeedit.setButtonSymbols(QAbstractSpinBox.NoButtons) else: self.timeedit.setButtonSymbols(QAbstractSpinBox.UpDownArrows) @pyqtSlot(QTime) def timeChangeHandler(self, newtime: QTime) -> None: if self.timeedit.hasFocus(): self.timeChanged.emit(newtime)
class MediaCueSettings(SettingsPage): Name = 'Media-Cue' def __init__(self, **kwargs): super().__init__(**kwargs) self.setLayout(QVBoxLayout(self)) # Start time self.startGroup = QGroupBox(self) self.startGroup.setLayout(QHBoxLayout()) self.layout().addWidget(self.startGroup) self.startEdit = QTimeEdit(self.startGroup) self.startEdit.setDisplayFormat('HH.mm.ss.zzz') self.startGroup.layout().addWidget(self.startEdit) self.startLabel = QLabel(self.startGroup) self.startLabel.setAlignment(Qt.AlignCenter) self.startGroup.layout().addWidget(self.startLabel) # Stop time self.stopGroup = QGroupBox(self) self.stopGroup.setLayout(QHBoxLayout()) self.layout().addWidget(self.stopGroup) self.stopEdit = QTimeEdit(self.stopGroup) self.stopEdit.setDisplayFormat('HH.mm.ss.zzz') self.stopGroup.layout().addWidget(self.stopEdit) self.stopLabel = QLabel(self.stopGroup) self.stopLabel.setAlignment(Qt.AlignCenter) self.stopGroup.layout().addWidget(self.stopLabel) # Loop self.loopGroup = QGroupBox(self) self.loopGroup.setLayout(QHBoxLayout()) self.layout().addWidget(self.loopGroup) self.spinLoop = QSpinBox(self.loopGroup) self.spinLoop.setRange(-1, 1000000) self.loopGroup.layout().addWidget(self.spinLoop) self.loopLabel = QLabel(self.loopGroup) self.loopLabel.setAlignment(Qt.AlignCenter) self.loopGroup.layout().addWidget(self.loopLabel) self.retranslateUi() def retranslateUi(self): self.startGroup.setTitle('Start time') self.stopLabel.setText('Stop position of the media') self.stopGroup.setTitle('Stop time') self.startLabel.setText('Start position of the media') self.loopGroup.setTitle('Loop') self.loopLabel.setText('Repetition after first play (-1 = infinite)') def get_settings(self): conf = {'_media_': {}} checkable = self.startGroup.isCheckable() if not (checkable and not self.startGroup.isChecked()): time = self.startEdit.time().msecsSinceStartOfDay() conf['_media_']['start_time'] = time if not (checkable and not self.stopGroup.isChecked()): time = self.stopEdit.time().msecsSinceStartOfDay() conf['_media_']['stop_time'] = time if not (checkable and not self.loopGroup.isChecked()): conf['_media_']['loop'] = self.spinLoop.value() return conf def enable_check(self, enable): self.startGroup.setCheckable(enable) self.startGroup.setChecked(False) self.stopGroup.setCheckable(enable) self.stopGroup.setChecked(False) self.loopGroup.setCheckable(enable) self.loopGroup.setChecked(False) def load_settings(self, settings): if '_media_' in settings: if 'loop' in settings['_media_']: self.spinLoop.setValue(settings['_media_']['loop']) if 'start_time' in settings['_media_']: t = self._to_qtime(settings['_media_']['start_time']) self.startEdit.setTime(t) if 'stop_time' in settings['_media_']: t = self._to_qtime(settings['_media_']['stop_time']) self.stopEdit.setTime(t) t = self._to_qtime(settings['_media_'].get('duration', 0)) self.startEdit.setMaximumTime(t) self.stopEdit.setMaximumTime(t) def _to_qtime(self, m_seconds): return QTime.fromMSecsSinceStartOfDay(m_seconds)
class SeekCueSettings(SettingsPage): Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Seek Settings') def __init__(self, **kwargs): super().__init__(**kwargs) self.setLayout(QVBoxLayout()) self.layout().setAlignment(QtCore.Qt.AlignTop) self.cue_id = -1 self.cueDialog = CueSelectDialog( cues=Application().cue_model.filter(MediaCue), parent=self) self.cueGroup = QGroupBox(self) self.cueGroup.setLayout(QVBoxLayout()) self.layout().addWidget(self.cueGroup) self.cueButton = QPushButton(self.cueGroup) self.cueButton.clicked.connect(self.select_cue) self.cueGroup.layout().addWidget(self.cueButton) self.cueLabel = QLabel(self.cueGroup) self.cueLabel.setAlignment(QtCore.Qt.AlignCenter) self.cueGroup.layout().addWidget(self.cueLabel) self.seekGroup = QGroupBox(self) self.seekGroup.setLayout(QHBoxLayout()) self.layout().addWidget(self.seekGroup) self.seekEdit = QTimeEdit(self.seekGroup) self.seekEdit.setDisplayFormat('HH.mm.ss.zzz') self.seekGroup.layout().addWidget(self.seekEdit) self.seekLabel = QLabel(self.seekGroup) self.seekLabel.setAlignment(QtCore.Qt.AlignCenter) self.seekGroup.layout().addWidget(self.seekLabel) self.retranslateUi() def retranslateUi(self): self.cueGroup.setTitle(translate('SeekCue', 'Cue')) self.cueButton.setText(translate('SeekCue', 'Click to select')) self.cueLabel.setText(translate('SeekCue', 'Not selected')) self.seekGroup.setTitle(translate('SeekCue', 'Seek')) self.seekLabel.setText(translate('SeekCue', 'Time to reach')) def select_cue(self): if self.cueDialog.exec_() == self.cueDialog.Accepted: cue = self.cueDialog.selected_cue() if cue is not None: self.cue_id = cue.id self.seekEdit.setMaximumTime( QTime.fromMSecsSinceStartOfDay(cue.media.duration)) self.cueLabel.setText(cue.name) def enable_check(self, enabled): self.cueGroup.setCheckable(enabled) self.cueGroup.setChecked(False) self.seekGroup.setCheckable(enabled) self.seekGroup.setChecked(False) def get_settings(self): return { 'target_id': self.cue_id, 'time': self.seekEdit.time().msecsSinceStartOfDay() } def load_settings(self, settings): if settings is not None: cue = Application().cue_model.get(settings.get('target_id')) if cue is not None: self.cue_id = settings['target_id'] self.seekEdit.setMaximumTime( QTime.fromMSecsSinceStartOfDay(cue.media.duration)) self.cueLabel.setText(cue.name) self.seekEdit.setTime( QTime.fromMSecsSinceStartOfDay(settings.get('time', 0)))
class FrameSelectWidget(QWidget): frameSelectionChanged = pyqtSignal(int, QTime) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.setMaximumWidth(960) layout = QVBoxLayout() layout.setContentsMargins(4, 4, 4, 4) layout.setSpacing(4) self.setLayout(layout) self.imageView = QImageView(self) layout.addWidget(self.imageView) self.slider = Slider(self) self.slider.setOrientation(Qt.Horizontal) self.slider.valueChanged.connect(self.handleSliderChange) self.slider.setTickInterval(1) layout.addWidget(self.slider) hlayout = QHBoxLayout() layout.addLayout(hlayout) self.prevLabel = QLabel(self) self.currentIndex = QSpinBox(self) self.currentIndex.valueChanged.connect(self.handleIndexChange) self.currentTime = QTimeEdit(self) self.currentTime.setDisplayFormat("H:mm:ss.zzz") self.currentTime.timeChanged.connect(self.handleTimeChange) self.nextLabel = QLabel(self) hlayout.addWidget(self.prevLabel) hlayout.addStretch() hlayout.addWidget(QLabel("Frame index:", self)) hlayout.addWidget(self.currentIndex) hlayout.addWidget(QLabel("Timestamp:", self)) hlayout.addWidget(self.currentTime) hlayout.addStretch() hlayout.addWidget(self.nextLabel) hlayout = QHBoxLayout() hlayout.setContentsMargins(0, 0, 0, 0) hlayout.setSpacing(4) layout.addLayout(hlayout) self.okayBtn = QPushButton("&OK", self) self.cancelBtn = QPushButton("&Cancel", self) hlayout.addStretch() hlayout.addWidget(self.okayBtn) hlayout.addWidget(self.cancelBtn) self.setFrameSource(None, None) def sizeHint(self): widgetHeights = ( self.slider.height() + max([self.okayBtn.height(), self.cancelBtn.height()]) + max([self.currentIndex.height(), self.currentTime.height()]) ) if isinstance(self.filters, BaseFilter): w, h = self.filters.width, self.filters.height sar = self.filters.sar elif isinstance(self.source, (Track, BaseFilter)): w, h = self.source.width, self.source.height sar = self.source.sar else: return super().sizeHint() dar = w*sar/h W, H = min([ max([(w, h/sar), (w*sar, h)]), (960 - 8, (960 - 8)/dar), ((720 - 20 - widgetHeights)*dar, 720 - 20 - widgetHeights) ]) return QSize(int(W + 8), int(H + 20 + widgetHeights)) def setFrameSource(self, source, filters=None): self.source = source if source is not None: self.slider.setMaximum(self.source.framecount - 1) self.currentIndex.setMaximum(self.source.framecount - 1) self.filters = filters if self.filters is not None: lastpts = self.filters.pts_time[-1] self.slider.setSnapValues(self.filters.keyframes) else: lastpts = self.source.pts_time[-1] if isinstance(self.source, BaseFilter): self.slider.setSnapValues(self.source.keyframes) else: self.slider.setSnapValues(None) ms = int(lastpts*1000 + 0.5) s, ms = divmod(ms, 1000) m, s = divmod(s, 60) h, m = divmod(m, 60) self.currentTime.setMaximumTime(QTime(h, m, s, ms)) self.slider.setValue(0) self._frameChange(0) else: self.slider.setSnapValues(None) self.update() def handleIndexChange(self, n): self._frameChange(n) self.slider.blockSignals(True) self.slider.setValue(n) self.slider.blockSignals(False) def handleSliderChange(self, n): self._frameChange(n) self.currentIndex.blockSignals(True) self.currentIndex.setValue(n) self.currentIndex.blockSignals(False) def _frameChange(self, n): if self.source is not None: if self.filters is not None: nn = n m = -1 while m < 0 and nn < len(self.filters.indexMap): m = self.filters.indexMap[nn] nn += 1 try: pts = self.filters.pts_time[m] except Exception: pts = None try: frame = next(self.filters.iterFrames( m, whence="framenumber")) except StopIteration: frame = None sar = self.filters.sar else: try: pts = self.source.pts_time[n] except IndexError: pts = None try: frame = next(self.source.iterFrames( n, whence="framenumber")) except StopIteration: frame = None sar = self.source.sar if frame is not None: im = frame.to_image() self.imageView.setFrame(im.toqpixmap()) self.imageView.setSar(sar) if pts is not None: ms = int(pts*1000+0.5) s, ms = divmod(ms, 1000) m, s = divmod(s, 60) h, m = divmod(m, 60) self.currentTime.blockSignals(True) self.currentTime.setTime(QTime(h, m, s, ms)) self.currentTime.blockSignals(False) self.frameSelectionChanged.emit(n, self.currentTime.time()) def handleTimeChange(self, t): if self.source is not None: if self.filters is not None: pts = t.msecsSinceStartOfDay()/1000 n = self.filters.frameIndexFromPtsTime(pts, dir="-") else: pts = t.msecsSinceStartOfDay()/1000 n = self.source.frameIndexFromPtsTime(pts, dir="-") self.slider.blockSignals(True) self.slider.setValue(n) self.slider.blockSignals(False) self.currentIndex.blockSignals(True) self.currentIndex.setValue(n) self.currentIndex.blockSignals(False) self._frameChange(n)
class MediaCueSettings(SettingsPage): Name = 'Media-Cue' def __init__(self, **kwargs): super().__init__(**kwargs) self.setLayout(QVBoxLayout(self)) # Start time self.startGroup = QGroupBox(self) self.startGroup.setLayout(QHBoxLayout()) self.layout().addWidget(self.startGroup) self.startEdit = QTimeEdit(self.startGroup) self.startEdit.setDisplayFormat('HH.mm.ss.zzz') self.startGroup.layout().addWidget(self.startEdit) self.startLabel = QLabel(self.startGroup) self.startLabel.setAlignment(Qt.AlignCenter) self.startGroup.layout().addWidget(self.startLabel) # Stop time self.stopGroup = QGroupBox(self) self.stopGroup.setLayout(QHBoxLayout()) self.layout().addWidget(self.stopGroup) self.stopEdit = QTimeEdit(self.stopGroup) self.stopEdit.setDisplayFormat('HH.mm.ss.zzz') self.stopGroup.layout().addWidget(self.stopEdit) self.stopLabel = QLabel(self.stopGroup) self.stopLabel.setAlignment(Qt.AlignCenter) self.stopGroup.layout().addWidget(self.stopLabel) # Loop self.loopGroup = QGroupBox(self) self.loopGroup.setLayout(QHBoxLayout()) self.layout().addWidget(self.loopGroup) self.spinLoop = QSpinBox(self.loopGroup) self.spinLoop.setRange(-1, 1000000) self.loopGroup.layout().addWidget(self.spinLoop) self.loopLabel = QLabel(self.loopGroup) self.loopLabel.setAlignment(Qt.AlignCenter) self.loopGroup.layout().addWidget(self.loopLabel) self.retranslateUi() def retranslateUi(self): self.startGroup.setTitle(translate('MediaCueSettings', 'Start time')) self.stopLabel.setText( translate('MediaCueSettings', 'Stop position of the media')) self.stopGroup.setTitle(translate('MediaCueSettings', 'Stop time')) self.startLabel.setText( translate('MediaCueSettings', 'Start position of the media')) self.loopGroup.setTitle(translate('MediaCueSettings', 'Loop')) self.loopLabel.setText( translate('MediaCueSettings', 'Repetition after first play ' '(-1 = infinite)')) def get_settings(self): conf = {'_media_': {}} checkable = self.startGroup.isCheckable() if not (checkable and not self.startGroup.isChecked()): time = self.startEdit.time().msecsSinceStartOfDay() conf['_media_']['start_time'] = time if not (checkable and not self.stopGroup.isChecked()): time = self.stopEdit.time().msecsSinceStartOfDay() conf['_media_']['stop_time'] = time if not (checkable and not self.loopGroup.isChecked()): conf['_media_']['loop'] = self.spinLoop.value() return conf def enable_check(self, enable): self.startGroup.setCheckable(enable) self.startGroup.setChecked(False) self.stopGroup.setCheckable(enable) self.stopGroup.setChecked(False) self.loopGroup.setCheckable(enable) self.loopGroup.setChecked(False) def load_settings(self, settings): if '_media_' in settings: if 'loop' in settings['_media_']: self.spinLoop.setValue(settings['_media_']['loop']) if 'start_time' in settings['_media_']: t = self._to_qtime(settings['_media_']['start_time']) self.startEdit.setTime(t) if 'stop_time' in settings['_media_']: t = self._to_qtime(settings['_media_']['stop_time']) self.stopEdit.setTime(t) t = self._to_qtime(settings['_media_'].get('duration', 0)) self.startEdit.setMaximumTime(t) self.stopEdit.setMaximumTime(t) def _to_qtime(self, m_seconds): return QTime.fromMSecsSinceStartOfDay(m_seconds)
class MediaCueGeneral(CueGeneral): Name = 'Cue Settings' def __init__(self, size, cue=None, parent=None): super().__init__(size, cue=cue, parent=parent) # Start at self.startGroup = QGroupBox(self) self.startGroup.setLayout(QHBoxLayout()) self.startEdit = QTimeEdit(self.startGroup) self.startEdit.setDisplayFormat('HH.mm.ss.zzz') self.startGroup.layout().addWidget(self.startEdit) self.startLabel = QLabel(self.startGroup) self.startLabel.setAlignment(QtCore.Qt.AlignCenter) self.startGroup.layout().addWidget(self.startLabel) self.layout().addWidget(self.startGroup) # Loop self.loopGroup = QGroupBox(self) self.loopGroup.setLayout(QHBoxLayout()) self.spinLoop = QSpinBox(self.loopGroup) self.spinLoop.setRange(-1, 1000000) self.loopGroup.layout().addWidget(self.spinLoop) self.loopLabel = QLabel(self.loopGroup) self.loopLabel.setAlignment(QtCore.Qt.AlignCenter) self.loopGroup.layout().addWidget(self.loopLabel) self.layout().addWidget(self.loopGroup) # Checks self.checkPause = QCheckBox(self) self.layout().addWidget(self.checkPause) self.retranslateUi() def retranslateUi(self): self.startGroup.setTitle('Start time') self.startLabel.setText('Amount of skip time') self.loopGroup.setTitle("Loop") self.loopLabel.setText("Repetition after first play (-1 = infinite)") self.checkPause.setText("Enable Pause") def get_configuration(self): conf = super().get_configuration() conf['media'] = {} checkable = self.startGroup.isCheckable() if not (checkable and not self.startGroup.isChecked()): time = self.startEdit.time().msecsSinceStartOfDay() conf['media']['start_at'] = time if not (checkable and not self.loopGroup.isChecked()): conf['media']['loop'] = self.spinLoop.value() if self.checkPause.checkState() != QtCore.Qt.PartiallyChecked: conf['pause'] = self.checkPause.isChecked() return conf def enable_check(self, enable): super().enable_check(enable) self.startGroup.setCheckable(enable) self.startGroup.setChecked(False) self.loopGroup.setCheckable(enable) self.loopGroup.setChecked(False) self.checkPause.setTristate(enable) if enable: self.checkPause.setCheckState(QtCore.Qt.PartiallyChecked) def set_configuration(self, conf): super().set_configuration(conf) if 'pause' in conf: self.checkPause.setChecked(conf['pause']) if 'media' in conf: if 'loop' in conf['media']: self.spinLoop.setValue(conf['media']['loop']) if 'duration' in conf['media'] and conf['media']['duration'] > 0: t = QTime().fromMSecsSinceStartOfDay(conf['media']['duration']) self.startEdit.setMaximumTime(t) if 'start_at' in conf['media']: t = QTime().fromMSecsSinceStartOfDay(conf['media']['start_at']) self.startEdit.setTime(t)
class VideoWindow(QWidget): def __init__(self, vidPath): super().__init__() self.fullPath = vidPath self.startTime = 0 self.endTime = 0 self.init_ui() def init_ui(self): layout = QVBoxLayout() self.setLayout(layout) self.setWindowTitle(self.fullPath) self.mediaPlayer = QMediaPlayer(None, QMediaPlayer.VideoSurface) self.videoWidget = QVideoWidget() self.playButton = QPushButton() self.playButton.setEnabled(True) self.playButton.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay)) self.playButton.setFixedWidth(100) self.playButton.setFixedHeight(50) self.playButton.clicked.connect(self.play) self.trimButton = QPushButton("Trim") self.trimButton.setFixedWidth(150) self.trimButton.setFixedHeight(50) self.trimButton.clicked.connect(self.trimVid) self.positionSlider = QSlider(QtCore.Qt.Horizontal) self.positionSlider.setRange(0, 0) self.positionSlider.sliderMoved.connect(self.setPosition) self.rangeSlider = qrangeslider.QRangeSlider() self.rangeSlider.setRange(0, 0) self.rangeSlider.endValueChanged.connect(self.adjustForEnd) self.rangeSlider.startValueChanged.connect(self.adjustForStart) self.rangeSlider.setFixedHeight(15) self.startTimeInput = QTimeEdit() self.endTimeInput = QTimeEdit() self.startTimeInput.setDisplayFormat('hh:mm:ss.zzz') self.endTimeInput.setDisplayFormat('hh:mm:ss.zzz') self.startTimeInput.timeChanged.connect(self.startInputChanged) self.endTimeInput.timeChanged.connect(self.endInputChanged) self.mediaPlayer.setMedia( QMediaContent(QtCore.QUrl.fromLocalFile(self.fullPath))) layout.addWidget(self.videoWidget) self.mediaPlayer.setVideoOutput(self.videoWidget) self.mediaPlayer.setNotifyInterval(10) self.mediaPlayer.stateChanged.connect(self.mediaStateChanged) self.mediaPlayer.positionChanged.connect(self.positionChanged) self.mediaPlayer.durationChanged.connect(self.durationChanged) controlLayout = QVBoxLayout() controlLayout.setContentsMargins(0, 0, 0, 0) controlLayout.addWidget(self.rangeSlider) controlLayout.addWidget(self.positionSlider) timeInputLayout = QHBoxLayout() timeInputLayout.addWidget(self.playButton) timeInputLayout.addWidget(self.startTimeInput) timeInputLayout.addWidget(self.endTimeInput) timeInputLayout.addWidget(self.trimButton) controlLayout.addLayout(timeInputLayout) layout.addLayout(controlLayout) self.mediaPlayer.play() self.resize(1024, 700) self.show() def closeEvent(self, event): self.mediaPlayer.stop() self.videoWidget.setParent(None) self.mediaPlayer.setParent(None) self.mediaPlayer.deleteLater() self.videoWidget.deleteLater() def trimVid(self): self.trimButton.setEnabled(False) outName = mytools.getAvailableName(self.fullPath, 'Trim') print(outName) trimStartTime = self.startTimeInput.time().toString('hh:mm:ss.zzz') trimEndTime = self.endTimeInput.time().toString('hh:mm:ss.zzz') try: ff = FFmpeg(inputs={self.fullPath: None}, outputs={ outName: [ '-ss', trimStartTime, '-to', trimEndTime, '-c:v', 'copy', '-c:a', 'copy', ] }) ff.run() except Exception as e: msg = QMessageBox() msg.setWindowTitle("Trim Failed") msg.setText(str(e)) msg.setIcon(QMessageBox.Critical) showMsg = msg.exec_() self.trimButton.setEnabled(True) def play(self): if self.mediaPlayer.state() == QMediaPlayer.PlayingState: self.mediaPlayer.pause() else: self.mediaPlayer.play() def mediaStateChanged(self, state): if self.mediaPlayer.state() == QMediaPlayer.PlayingState: self.playButton.setIcon(self.style().standardIcon( QStyle.SP_MediaPause)) else: self.playButton.setIcon(self.style().standardIcon( QStyle.SP_MediaPlay)) def positionChanged(self, position): self.positionSlider.setValue(position) if position > self.endTime: self.mediaPlayer.setPosition(self.startTime) def adjustForStart(self, startPos): self.startTime = startPos self.mediaPlayer.setPosition(startPos) self.startTimeInput.setTime(QtCore.QTime(0, 0).addMSecs(startPos)) self.endTimeInput.setMinimumTime(QtCore.QTime(0, 0).addMSecs(startPos)) def adjustForEnd(self, endPos): self.endTime = endPos if self.positionSlider.value() > endPos: self.mediaPlayer.setPosition(endPos) self.endTimeInput.setTime(QtCore.QTime(0, 0).addMSecs(endPos)) self.startTimeInput.setMaximumTime(QtCore.QTime(0, 0).addMSecs(endPos)) def startInputChanged(self, inputTime): self.rangeSlider.setStart(QtCore.QTime(0, 0, 0, 0).msecsTo(inputTime)) def endInputChanged(self, inputTime): self.rangeSlider.setEnd(QtCore.QTime(0, 0, 0, 0).msecsTo(inputTime)) def durationChanged(self, duration): self.positionSlider.setRange(0, duration) self.rangeSlider.setMax(duration) self.rangeSlider.setEnd(duration) self.startTimeInput.setMinimumTime(QtCore.QTime(0, 0)) self.endTimeInput.setMinimumTime(QtCore.QTime(0, 0)) self.endTimeInput.setTime(QtCore.QTime(0, 0).addMSecs(duration)) self.startTimeInput.setMaximumTime( QtCore.QTime(0, 0).addMSecs(duration)) self.endTimeInput.setMaximumTime(QtCore.QTime(0, 0).addMSecs(duration)) def setPosition(self, position): self.mediaPlayer.setPosition(position)
class SeekSettings(SettingsPage): Name = 'Seek Settings' def __init__(self, **kwargs): super().__init__(**kwargs) self.cue_id = -1 self.setLayout(QVBoxLayout(self)) self.cueDialog = CueListDialog( cues=Application().cue_model.filter(MediaCue), parent=self) self.cueGroup = QGroupBox(self) self.cueGroup.setLayout(QVBoxLayout()) self.layout().addWidget(self.cueGroup) self.cueButton = QPushButton(self.cueGroup) self.cueButton.clicked.connect(self.select_cue) self.cueGroup.layout().addWidget(self.cueButton) self.cueLabel = QLabel(self.cueGroup) self.cueLabel.setAlignment(QtCore.Qt.AlignCenter) self.cueGroup.layout().addWidget(self.cueLabel) self.seekGroup = QGroupBox(self) self.seekGroup.setLayout(QHBoxLayout()) self.layout().addWidget(self.seekGroup) self.seekEdit = QTimeEdit(self.seekGroup) self.seekEdit.setDisplayFormat('HH.mm.ss.zzz') self.seekGroup.layout().addWidget(self.seekEdit) self.seekLabel = QLabel(self.seekGroup) self.seekLabel.setAlignment(QtCore.Qt.AlignCenter) self.seekGroup.layout().addWidget(self.seekLabel) self.layout().addSpacing(200) self.retranslateUi() def retranslateUi(self): self.cueGroup.setTitle('Cue') self.cueButton.setText('Click to select') self.cueLabel.setText('Not selected') self.seekGroup.setTitle('Seek') self.seekLabel.setText('Time to reach') def select_cue(self): if self.cueDialog.exec_() == self.cueDialog.Accepted: cue = self.cueDialog.selected_cues()[0] self.cue_id = cue.id self.seekEdit.setMaximumTime( QTime.fromMSecsSinceStartOfDay(cue.media.duration)) self.cueLabel.setText(cue.name) def enable_check(self, enabled): self.cueGroup.setCheckable(enabled) self.cueGroup.setChecked(False) self.seekGroup.setCheckable(enabled) self.seekGroup.setChecked(False) def get_settings(self): return {'target_id': self.cue_id, 'time': self.seekEdit.time().msecsSinceStartOfDay()} def load_settings(self, settings): if settings is not None: cue = Application().cue_model.get(settings['target_id']) if cue is not None: self.cue_id = settings['target_id'] self.seekEdit.setTime( QTime.fromMSecsSinceStartOfDay(settings['time'])) self.seekEdit.setMaximumTime( QTime.fromMSecsSinceStartOfDay(cue.media.duration)) self.cueLabel.setText(cue.name)
class QFrameSelect(QWidget): frameSelectionChanged = pyqtSignal(int, QTime) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) layout = QVBoxLayout() self.setLayout(layout) self.slider = Slider(self) self.slider.setOrientation(Qt.Horizontal) self.slider.valueChanged.connect(self._handleSliderChange) self.slider.setTickInterval(1) layout.addWidget(self.slider) hlayout = QHBoxLayout() layout.addLayout(hlayout) self.leftLabel = QLabel(self) self.spinBox = QSpinBox(self) self.spinBox.valueChanged.connect(self._handleSpinboxChange) self.currentTime = QTimeEdit(self) self.currentTime.setDisplayFormat("H:mm:ss.zzz") self.currentTime.timeChanged.connect(self._handleTimeEditChange) self.currentTime.editingFinished.connect(self._handleTimeEditFinished) self.rightLabel = QLabel(self) hlayout.addWidget(self.leftLabel) hlayout.addStretch() hlayout.addWidget(QLabel("Frame index:", self)) hlayout.addWidget(self.spinBox) hlayout.addWidget(QLabel("Timestamp:", self)) hlayout.addWidget(self.currentTime) hlayout.addStretch() hlayout.addWidget(self.rightLabel) self.setPtsTimeArray(None) def setValue(self, n): self.slider.setValue(n) def setMinimum(self, n=0): self.slider.setMinimum(n) self.spinBox.setMinimum(n) ms = int(self.pts_time[n] * 1000 + 0.5) s, ms = divmod(ms, 1000) m, s = divmod(s, 60) h, m = divmod(m, 60) self.currentTime.setMinimumTime(QTime(h, m, s, ms)) self.leftLabel.setText(f"{n} ({h}:{m:02d}:{s:02d}.{ms:03d})") def setMaximum(self, n=None): if n is None: n = len(self.pts_time) - 1 self.slider.setMaximum(n) self.spinBox.setMaximum(n) ms = int(self.pts_time[n] * 1000 + 0.5) s, ms = divmod(ms, 1000) m, s = divmod(s, 60) h, m = divmod(m, 60) self.currentTime.setMaximumTime(QTime(h, m, s, ms)) self.rightLabel.setText(f"{n} ({h}:{m:02d}:{s:02d}.{ms:03d})") def setStartEndVisible(self, value): self.leftLabel.setHidden(not bool(value)) self.rightLabel.setHidden(not bool(value)) def setPtsTimeArray(self, pts_time=None): self.pts_time = pts_time if pts_time is not None: N = len(pts_time) self.setMinimum(0) self.setMaximum(N - 1) self.slider.setValue(0) self.slider.setSnapValues(None) self.slider.setDisabled(False) self.spinBox.setDisabled(False) self.currentTime.setDisabled(False) else: self.slider.setMinimum(0) self.slider.setMaximum(0) self.slider.setSnapValues(None) self.slider.setDisabled(True) self.spinBox.setDisabled(True) self.currentTime.setDisabled(True) def _handleSliderChange(self, n): self.spinBox.blockSignals(True) self.spinBox.setValue(n) self.spinBox.blockSignals(False) pts_time = self.pts_time[n] ms = int(pts_time * 1000 + 0.5) s, ms = divmod(ms, 1000) m, s = divmod(s, 60) h, m = divmod(m, 60) self.currentTime.blockSignals(True) self.currentTime.setTime(QTime(h, m, s, ms)) self.currentTime.blockSignals(False) self.frameSelectionChanged.emit(n, QTime(h, m, s, ms)) def _handleSpinboxChange(self, n): self.slider.blockSignals(True) self.slider.setValue(n) self.slider.blockSignals(False) pts_time = self.pts_time[n] ms = int(pts_time * 1000 + 0.5) s, ms = divmod(ms, 1000) m, s = divmod(s, 60) h, m = divmod(m, 60) self.currentTime.blockSignals(True) self.currentTime.setTime(QTime(h, m, s, ms)) self.currentTime.blockSignals(False) self.frameSelectionChanged.emit(n, QTime(h, m, s, ms)) def _handleTimeEditChange(self, t): pts = t.msecsSinceStartOfDay() / 1000 try: n = search(self.pts_time, pts + 0.0005, dir="-") except IndexError: n = 0 if n != self.slider.value(): self.slider.blockSignals(True) self.slider.setValue(n) self.slider.blockSignals(False) self.spinBox.blockSignals(True) self.spinBox.setValue(n) self.spinBox.blockSignals(False) pts_time = self.pts_time[n] ms = int(pts_time * 1000 + 0.5) s, ms = divmod(ms, 1000) m, s = divmod(s, 60) h, m = divmod(m, 60) self.frameSelectionChanged.emit(n, QTime(h, m, s, ms)) def _handleTimeEditFinished(self): t = self.currentTime.time() pts = t.msecsSinceStartOfDay() / 1000 n = search(self.pts_time, pts + 0.0005, dir="-") pts_time = self.pts_time[n] ms = int(pts_time * 1000 + 0.5) s, ms = divmod(ms, 1000) m, s = divmod(s, 60) h, m = divmod(m, 60) T = QTime(h, m, s, ms) if t != T: self.currentTime.setTime(T)