def on_tableview_selection_changed(self, selected: Qt.QItemSelection, deselected: Qt.QItemSelection) -> None:
     if len(selected.indexes()) == 0:
         self.delete_button.setEnabled(False)
         return
     index = selected.indexes()[0]
     scene = self.scening_list[index.row()]
     qt_silent_call(self.start_frame_control.setValue,     scene.start)
     qt_silent_call(self.  end_frame_control.setValue,     scene.end)
     qt_silent_call(self. start_time_control.setValue, Time(scene.start))
     qt_silent_call(self.   end_time_control.setValue, Time(scene.end))
     qt_silent_call(self.     label_lineedit.setText,      scene.label)
     self.delete_button.setEnabled(True)
    def import_matroska_xml_chapters(self, path: Path, scening_list: SceningList, out_of_range_count: int) -> None:
        '''
        Imports chapters as scenes.
        Preserve end time and text if they're present.
        '''
        from xml.etree import ElementTree

        timestamp_pattern = re.compile(r'(\d{2}):(\d{2}):(\d{2}(?:\.\d{3})?)')

        try:
            root = ElementTree.parse(str(path)).getroot()
        except ElementTree.ParseError as exc:
            logging.warning(
                f'Scening import: error occured'
                ' while parsing \'{path.name}\':')
            logging.warning(exc.msg)
            return
        for chapter in root.iter('ChapterAtom'):
            start_element = chapter.find('ChapterTimeStart')
            if start_element is None or start_element.text is None:
                continue
            match = timestamp_pattern.match(start_element.text)
            if match is None:
                continue
            start =  Frame(Time(
                hours   =   int(match[1]),
                minutes =   int(match[2]),
                seconds = float(match[3])
            ))

            end = None
            end_element = chapter.find('ChapterTimeEnd')
            if end_element is not None and end_element.text is not None:
                match = timestamp_pattern.match(end_element.text)
                if match is not None:
                    end = Frame(Time(
                        hours   =   int(match[1]),
                        minutes =   int(match[2]),
                        seconds = float(match[3])
                    ))

            label = ''
            label_element = chapter.find('ChapterDisplay/ChapterString')
            if label_element is not None and label_element.text is not None:
                label = label_element.text

            try:
                scening_list.add(start, end, label)
            except ValueError:
                out_of_range_count += 1
    def import_ass(self, path: Path, scening_list: SceningList, out_of_range_count: int) -> None:
        '''
        Imports lines as scenes.
        Text is ignored.
        '''
        import pysubs2

        subs = pysubs2.load(str(path))
        for line in subs:
            t_start = Time(milliseconds=line.start)
            t_end   = Time(milliseconds=line.end)
            try:
                scening_list.add(Frame(t_start), Frame(t_end))
            except ValueError:
                out_of_range_count += 1
Exemple #4
0
    def switch_frame(self,
                     pos: Union[Frame, Time],
                     *,
                     render_frame: bool = True) -> None:
        if isinstance(pos, Frame):
            frame = pos
            time = Time(frame)
        elif isinstance(pos, Time):
            frame = Frame(pos)
            time = pos
        else:
            logging.debug('switch_frame(): position is neither Frame nor Time')
            return
        if frame > self.current_output.end_frame:
            return

        self.current_output.last_showed_frame = frame

        self.timeline.set_position(frame)
        self.toolbars.main.on_current_frame_changed(frame, time)
        for toolbar in self.toolbars:
            toolbar.on_current_frame_changed(frame, time)

        if render_frame:
            self.current_output.graphics_scene_item.setImage(
                self.render_frame(frame))
Exemple #5
0
    def switch_frame(self,
                     frame: Optional[Frame] = None,
                     time: Optional[Time] = None,
                     *,
                     render_frame: bool = True) -> None:
        if frame is not None:
            time = Time(frame)
        elif time is not None:
            frame = Frame(time)
        else:
            logging.debug('switch_frame(): both frame and time is None')
            return
        if frame > self.current_output.end_frame:
            return

        self.current_output.last_showed_frame = frame

        self.timeline.set_position(frame)
        self.toolbars.main.on_current_frame_changed(frame, time)
        for toolbar in self.toolbars:
            toolbar.on_current_frame_changed(frame, time)

        if render_frame:
            self.current_output.graphics_scene_item.setImage(
                self.render_frame(frame))
Exemple #6
0
 def offset_to_time(offset: str) -> Optional[Time]:
     pattern = re.compile(r'(\d{1,2}):(\d{1,2}):(\d{1,2})')
     match = pattern.match(offset)
     if match is None:
         return None
     return Time(minutes=int(match[1]),
                 seconds=int(match[2]),
                 milliseconds=int(match[3]) / 75 * 1000)
Exemple #7
0
    def data(self, index: Qt.QModelIndex, role: int = Qt.Qt.UserRole) -> Any:
        if not index.isValid():
            return None
        row = index.row()
        if row >= len(self.items):
            return None
        column = index.column()
        if column >= self.COLUMN_COUNT:
            return None

        if role in (Qt.Qt.DisplayRole, Qt.Qt.EditRole):
            if column == self.START_FRAME_COLUMN:
                return str(self.items[row].start)
            if column == self.END_FRAME_COLUMN:
                if self.items[row].end != self.items[row].start:
                    return str(self.items[row].end)
                else:
                    return ''
            if column == self.START_TIME_COLUMN:
                return str(Time(self.items[row].start))
            if column == self.END_TIME_COLUMN:
                if self.items[row].end != self.items[row].start:
                    return str(Time(self.items[row].end))
                else:
                    return ''
            if column == self.LABEL_COLUMN:
                return str(self.items[row].label)

        if role == Qt.Qt.UserRole:
            if column == self.START_FRAME_COLUMN:
                return self.items[row].start
            if column == self.END_FRAME_COLUMN:
                return self.items[row].end
            if column == self.START_TIME_COLUMN:
                return Time(self.items[row].start)
            if column == self.END_TIME_COLUMN:
                return Time(self.items[row].end)
            if column == self.LABEL_COLUMN:
                return self.items[row].label

        return None
Exemple #8
0
    def update_controls(self,
                        start: Optional[Frame] = None,
                        end: Optional[Frame] = None,
                        total: Optional[FrameInterval] = None) -> None:
        if start is not None:
            end = self.end_frame_control.value()
            total = self.total_frames_control.value()

            if start > end:
                end = start
            total = end - start + FrameInterval(1)

        elif end is not None:
            start = self.start_frame_control.value()
            total = self.total_frames_control.value()

            if end < start:
                start = end
            total = end - start + FrameInterval(1)

        elif total is not None:
            start = self.start_frame_control.value()
            end = self.end_frame_control.value()
            old_total = end - start + FrameInterval(1)
            delta = total - old_total

            end += delta
            if end > self.main.current_output.end_frame:
                start -= end - self.main.current_output.end_frame
                end = self.main.current_output.end_frame
        else:
            return

        qt_silent_call(self.start_frame_control.setValue, start)
        qt_silent_call(self.start_time_control.setValue, Time(start))
        qt_silent_call(self.end_frame_control.setValue, end)
        qt_silent_call(self.end_time_control.setValue, Time(end))
        qt_silent_call(self.total_frames_control.setValue, total)
        qt_silent_call(self.total_time_control.setValue, TimeInterval(total))
    def import_matroska_timestamps_v2(self, path: Path, scening_list: SceningList, out_of_range_count: int) -> None:
        '''
        Imports intervals of constant FPS as scenes.
        Uses FPS for scene label.
        '''
        timestamps: List[Time] = []
        for line in path.read_text().splitlines():
            try:
                timestamps.append(Time(milliseconds=float(line)))
            except ValueError:
                continue

        if len(timestamps) < 2:
            logging.warning(
                'Scening import: timestamps file contains less than'
                ' 2 timestamps, so there\'s nothing to import.')
            return

        deltas = [
            timestamps[i] - timestamps[i - 1]
            for i in range(1, len(timestamps))
        ]
        scene_delta = deltas[0]
        scene_start = Frame(0)
        scene_end: Optional[Frame] = None
        for i in range(1, len(deltas)):
            if abs(round(float(deltas[i] - scene_delta), 6)) <= 0.000_001:
                continue
            # TODO: investigate, why offset by -1 is necessary here
            scene_end = Frame(i - 1)
            try:
                scening_list.add(
                    scene_start, scene_end,
                    '{:.3f} fps'.format(1 / float(scene_delta)))
            except ValueError:
                out_of_range_count += 1
            scene_start = Frame(i)
            scene_end = None
            scene_delta = deltas[i]

        if scene_end is None:
            try:
                scening_list.add(
                    scene_start, Frame(len(timestamps) - 1),
                    '{:.3f} fps'.format(1 / float(scene_delta)))
            except ValueError:
                out_of_range_count += 1
 def import_ogm_chapters(self, path: Path, scening_list: SceningList, out_of_range_count: int) -> None:
     '''
     Imports chapters as signle-frame scenes.
     Uses NAME for scene label.
     '''
     pattern = re.compile(
         r'(CHAPTER\d+)=(\d{2}):(\d{2}):(\d{2}(?:\.\d{3})?)\n\1NAME=(.*)',
         re.RegexFlag.MULTILINE)
     for match in pattern.finditer(path.read_text()):
         time = Time(
             hours   =   int(match[2]),
             minutes =   int(match[3]),
             seconds = float(match[4]))
         try:
             scening_list.add(Frame(time), label=match[5])
         except ValueError:
             out_of_range_count += 1
Exemple #11
0
    def __init__(self, parent: Qt.QWidget) -> None:
        from vspreview.utils import main_window

        super().__init__(parent)
        self.app = Qt.QApplication.instance()
        self.main = main_window()

        self._mode = self.Mode.TIME

        self.rect_f = Qt.QRectF()

        self.end_t = Time(seconds=1)
        self.end_f = Frame(1)

        self.notch_interval_target_x = round(75 * self.main.display_scale)
        self.notch_height = round(6 * self.main.display_scale)
        self.font_height = round(10 * self.main.display_scale)
        self.notch_label_interval = round(-1 * self.main.display_scale)
        self.notch_scroll_interval = round(2 * self.main.display_scale)
        self.scroll_height = round(10 * self.main.display_scale)

        self.setMinimumSize(self.notch_interval_target_x,
                            round(33 * self.main.display_scale))

        font = self.font()
        font.setPixelSize(self.font_height)
        self.setFont(font)

        self.cursor_x = 0
        # used as a fallback when self.rectF.width() is 0,
        # so cursorX is incorrect
        self.cursor_ftx: Optional[Union[Frame, Time, int]] = None
        # False means that only cursor position'll be recalculated
        self.need_full_repaint = True

        self.toolbars_notches: Dict[AbstractToolbar, Notches] = {}

        self.setAttribute(Qt.Qt.WA_OpaquePaintEvent)
        self.setMouseTracking(True)
Exemple #12
0
 def set_end_frame(self, end_f: Frame) -> None:
     self.end_f = end_f
     self.end_t = Time(end_f)
     self.full_repaint()
Exemple #13
0
    def drawWidget(self, painter: Qt.QPainter) -> None:
        from copy import deepcopy

        from vspreview.utils import strfdelta

        # calculations

        if self.need_full_repaint:
            labels_notches = Notches()
            label_notch_bottom = (self.rect_f.top() + self.font_height +
                                  self.notch_label_interval +
                                  self.notch_height + 5)
            label_notch_top = label_notch_bottom - self.notch_height
            label_notch_x = self.rect_f.left()

            if self.mode == self.Mode.TIME:
                notch_interval_t = self.calculate_notch_interval_t(
                    self.notch_interval_target_x)
                label_format = self.generate_label_format(
                    notch_interval_t, self.end_t)
                label_notch_t = Time()

                while (label_notch_x < self.rect_f.right()
                       and label_notch_t <= self.end_t):
                    line = Qt.QLineF(label_notch_x, label_notch_bottom,
                                     label_notch_x, label_notch_top)
                    labels_notches.add(
                        Notch(deepcopy(label_notch_t), line=line))
                    label_notch_t += notch_interval_t
                    label_notch_x = self.t_to_x(label_notch_t)

            elif self.mode == self.Mode.FRAME:
                notch_interval_f = self.calculate_notch_interval_f(
                    self.notch_interval_target_x)
                label_notch_f = Frame(0)

                while (label_notch_x < self.rect_f.right()
                       and label_notch_f <= self.end_f):
                    line = Qt.QLineF(label_notch_x, label_notch_bottom,
                                     label_notch_x, label_notch_top)
                    labels_notches.add(
                        Notch(deepcopy(label_notch_f), line=line))
                    label_notch_f += notch_interval_f
                    label_notch_x = self.f_to_x(label_notch_f)

            self.scroll_rect = Qt.QRectF(
                self.rect_f.left(),
                label_notch_bottom + self.notch_scroll_interval,
                self.rect_f.width(), self.scroll_height)

            for toolbar, notches in self.toolbars_notches.items():
                if not toolbar.is_notches_visible():
                    continue

                for notch in notches:
                    if isinstance(notch.data, Frame):
                        x = self.f_to_x(notch.data)
                    elif isinstance(notch.data, Time):
                        x = self.t_to_x(notch.data)
                    y = self.scroll_rect.top()
                    notch.line = Qt.QLineF(x, y, x,
                                           y + self.scroll_rect.height() - 1)

        cursor_line = Qt.QLineF(
            self.cursor_x, self.scroll_rect.top(), self.cursor_x,
            self.scroll_rect.top() + self.scroll_rect.height() - 1)

        # drawing

        if self.need_full_repaint:
            painter.fillRect(self.rect_f,
                             self.palette().color(Qt.QPalette.Window))

            painter.setPen(
                Qt.QPen(self.palette().color(Qt.QPalette.WindowText)))
            painter.setRenderHint(Qt.QPainter.Antialiasing, False)
            painter.drawLines([notch.line for notch in labels_notches])

            painter.setRenderHint(Qt.QPainter.Antialiasing)
            for i, notch in enumerate(labels_notches):
                line = notch.line
                anchor_rect = Qt.QRectF(line.x2(),
                                        line.y2() - self.notch_label_interval,
                                        0, 0)

                if self.mode == self.Mode.TIME:
                    time = cast(Time, notch.data)
                    label = strfdelta(time, label_format)
                if self.mode == self.Mode.FRAME:
                    label = str(notch.data)

                if i == 0:
                    rect = painter.boundingRect(
                        anchor_rect, Qt.Qt.AlignBottom + Qt.Qt.AlignLeft,
                        label)
                    if self.mode == self.Mode.TIME:
                        rect.moveLeft(-2.5)
                elif i == (len(labels_notches) - 1):
                    rect = painter.boundingRect(
                        anchor_rect, Qt.Qt.AlignBottom + Qt.Qt.AlignHCenter,
                        label)
                    if rect.right() > self.rect_f.right():
                        rect = painter.boundingRect(
                            anchor_rect, Qt.Qt.AlignBottom + Qt.Qt.AlignRight,
                            label)
                else:
                    rect = painter.boundingRect(
                        anchor_rect, Qt.Qt.AlignBottom + Qt.Qt.AlignHCenter,
                        label)
                painter.drawText(rect, label)

        painter.setRenderHint(Qt.QPainter.Antialiasing, False)
        painter.fillRect(self.scroll_rect, Qt.Qt.gray)

        for toolbar, notches in self.toolbars_notches.items():
            if not toolbar.is_notches_visible():
                continue

            for notch in notches:
                painter.setPen(notch.color)
                painter.drawLine(notch.line)

        painter.setPen(Qt.Qt.black)
        painter.drawLine(cursor_line)

        self.need_full_repaint = False
Exemple #14
0
 def current_time(self) -> Time:  # type: ignore
     return Time(self.current_output.last_showed_frame)
        '''
        Imports listed scenes, ignoring gaps.
        Uses FPS for scene label.
        '''
        pattern = re.compile(
            r'^((?:\d+(?:\.\d+)?)|gap)(?:,\s?(\d+(?:\.\d+)?))?',
            re.RegexFlag.MULTILINE)

        assume_pattern = re.compile(r'assume (\d+(?:\.\d+))')
        if len(match := assume_pattern.findall(path.read_text())) > 0:
            default_fps = float(match[0])
        else:
            logging.warning('Scening import: "assume" entry not found.')
            return

        pos = Time()
        for match in pattern.finditer(path.read_text()):
            if match[1] == 'gap':
                pos += TimeInterval(seconds=float(match[2]))
                continue

            interval = TimeInterval(seconds=float(match[1]))
            fps = float(match[2]) if match.lastindex >= 2 else default_fps

            scening_list.add(
                self.main.current_output.to_frame(pos),
                self.main.current_output.to_frame(pos + interval),
                '{:.3f} fps'.format(fps))

            pos += interval