| 582 | return video.frame_number - start_frame_num |
| 583 | |
| 584 | def _decode_thread( |
| 585 | self, |
| 586 | video: VideoStream, |
| 587 | frame_skip: int, |
| 588 | downscale_factor: float, |
| 589 | end_time: FrameTimecode, |
| 590 | out_queue: queue.Queue, |
| 591 | ): |
| 592 | try: |
| 593 | while not self._stop.is_set(): |
| 594 | frame_im = None |
| 595 | # We don't do any kind of locking here since the worst-case of this being wrong |
| 596 | # is that we do some extra work, and this function should never mutate any data |
| 597 | # (all of which should be modified under the GIL). |
| 598 | frame_im = video.read() |
| 599 | if frame_im is False: |
| 600 | break |
| 601 | assert isinstance(frame_im, np.ndarray) |
| 602 | # Verify the decoded frame size against the video container's reported |
| 603 | # resolution, and also verify that consecutive frames have the correct size. |
| 604 | decoded_size = (frame_im.shape[1], frame_im.shape[0]) |
| 605 | if self._frame_size is None: |
| 606 | self._frame_size = decoded_size |
| 607 | if video.frame_size != decoded_size: |
| 608 | logger.warn( |
| 609 | f"WARNING: Decoded frame size ({decoded_size}) does not match " |
| 610 | f" video resolution {video.frame_size}, possible corrupt input." |
| 611 | ) |
| 612 | elif self._frame_size != decoded_size: |
| 613 | self._frame_size_errors += 1 |
| 614 | if self._frame_size_errors <= MAX_FRAME_SIZE_ERRORS: |
| 615 | logger.error( |
| 616 | f"ERROR: Frame at {video.position!s} has incorrect size and " |
| 617 | f"cannot be processed: decoded size = {decoded_size}, " |
| 618 | f"expected = {self._frame_size}. Video may be corrupt." |
| 619 | ) |
| 620 | if self._frame_size_errors == MAX_FRAME_SIZE_ERRORS: |
| 621 | logger.warn("WARNING: Too many errors emitted, skipping future messages.") |
| 622 | # Skip processing frames that have an incorrect size. |
| 623 | continue |
| 624 | |
| 625 | if self._crop: |
| 626 | (x0, y0, x1, y1) = self._crop |
| 627 | frame_im = frame_im[y0:y1, x0:x1] |
| 628 | |
| 629 | if downscale_factor > 1.0: |
| 630 | frame_im = cv2.resize( |
| 631 | frame_im, |
| 632 | ( |
| 633 | max(1, round(frame_im.shape[1] / downscale_factor)), |
| 634 | max(1, round(frame_im.shape[0] / downscale_factor)), |
| 635 | ), |
| 636 | interpolation=self._interpolation.value, |
| 637 | ) |
| 638 | |
| 639 | # Set the start position now that we decoded at least the first frame. |
| 640 | if self._start_pos is None: |
| 641 | self._start_pos = video.position |