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 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)