Exemple #1
0
    def from_range(cls, start, end, step=1):
        """
        Build a :class:`FrameSet` from given start and end frames.

        Args:
            start (int): The first frame of the :class:`FrameSet`.
            end (int): The last frame of the :class:`FrameSet`.
            step (int, optional): Range step (default 1).

        Returns:
            :class:`FrameSet`:
        """
        # match range() exception
        if not isinstance(step, int):
            raise TypeError("integer step argument expected, got {}."
                            .format(type(step)))
        elif step == 0:
            raise ValueError("step argument must not be zero")
        elif step == 1:
            start, end = normalizeFrames([start, end])
            range_str = "{0}-{1}".format(start, end)
        else:
            start, end = normalizeFrames([start, end])
            step = normalizeFrame(step)
            range_str = "{0}-{1}x{2}".format(start, end, step)

        return FrameSet(range_str)
Exemple #2
0
    def _build_frange_part_decimal(
        start, stop, count, stride, min_stride, max_stride, zfill=0
    ):
        """
        Private method: builds a proper and padded subframe range string from
        decimal values.

        Args:
            start (decimal.Decimal): first frame
            stop (decimal.Decimal): last frame
            count (int): number of frames in range (inclusive)
            stride (decimal.Decimal or None): stride to use if known else None
            min_stride (decimal.Decimal): minimum increment that will produce
                correctly rounded frames
            max_stride (decimal.Decimal): maximum increment that will produce
                correctly rounded frames
            zfill (int): width for zero padding

        Returns:
            str:
        """
        if stride is None:
            # Use an exact stride value if within allowed limits for
            # range, otherwise use midpoint of stride limits
            stride = (stop - start) / (count - 1)
            if not min_stride <= stride <= max_stride:
                stride = (min_stride + max_stride) / 2

            # Minimise number of decimal places in stride
            stride_range = max_stride - min_stride
            stride_range = stride_range.as_tuple()
            leading_zeros = abs(len(stride_range.digits) + stride_range.exponent)
            stride = abs(quantize(stride, leading_zeros + 1)).normalize()

        # Adjust end frame if required so correct number of steps is
        # calculated when recreating FrameSet from frange string
        while abs(stop - start) / stride + 1 < count:
            exponent = stop.as_tuple().exponent
            delta = decimal.Decimal(1).scaleb(exponent)
            stop += delta.copy_sign(stop)

        start, stop = normalizeFrames([start, stop])
        return FrameSet._build_frange_part(start, stop, stride, zfill=zfill)
Exemple #3
0
    def framesToFrameRanges(frames, zfill=0):
        """
        Converts a sequence of frames to a series of padded
        frame range strings.

        Args:
            frames (collections.Iterable): sequence of frames to process
            zfill (int): width for zero padding

        Yields:
            str:
        """
        _build = FrameSet._build_frange_part
        _build_decimal = FrameSet._build_frange_part_decimal

        # Ensure all frame values are of same type
        frames = normalizeFrames(frames)

        curr_start = None
        curr_stride = None
        curr_strides = None # used for decimal frame handling only
        curr_min_stride = None # used for decimal frame handling only
        curr_max_stride = None # used for decimal frame handling only
        curr_frame = None
        last_frame = None
        curr_count = 0
        for curr_frame in frames:
            if curr_start is None:
                curr_start = curr_frame
                last_frame = curr_frame
                curr_count += 1
                continue
            if curr_stride is None:
                curr_stride = abs(curr_frame-curr_start)
                curr_strides = {curr_stride}
            new_stride = abs(curr_frame-last_frame)

            # Handle decimal strides and frame rounding
            if isinstance(curr_stride, decimal.Decimal):
                # Check whether stride difference could be caused by rounding
                if len(curr_strides) == 1:
                    stride_delta = abs(curr_stride - new_stride)
                    exponent = stride_delta.as_tuple().exponent
                    max_stride_delta = decimal.Decimal(1).scaleb(exponent)
                    if stride_delta <= max_stride_delta:
                        curr_strides.add(new_stride)

                if new_stride in curr_strides:
                    # Find minimum frame value that rounds to current
                    min_frame = (curr_frame - max_stride_delta / 2)
                    while min_frame.quantize(curr_frame) != curr_frame:
                        min_frame = min_frame.next_plus()

                    # Find maximum frame value that rounds to current
                    max_frame = (curr_frame + max_stride_delta / 2)
                    while max_frame.quantize(curr_frame) != curr_frame:
                        max_frame = max_frame.next_minus()

                    # Adjust min stride limit until frame rounds to current
                    while True:
                        new_min_stride = (min_frame - curr_start) / curr_count
                        test_frame = curr_start + new_min_stride * curr_count
                        if test_frame.quantize(curr_frame) == curr_frame:
                            break
                        min_frame = min_frame.next_plus()

                    # Adjust max stride limit until frame rounds to current
                    while True:
                        new_max_stride = (max_frame - curr_start) / curr_count
                        test_frame = curr_start + new_max_stride * curr_count
                        if test_frame.quantize(curr_frame) == curr_frame:
                            break
                        max_frame = max_frame.next_minus()

                    # Update minimum and maximum stride values for overall range
                    if curr_min_stride is not None:
                        new_min_stride = max(curr_min_stride, new_min_stride)
                    if curr_max_stride is not None:
                        new_max_stride = min(curr_max_stride, new_max_stride)

                    # A stride exists that rounds all frame values correctly
                    if new_min_stride <= new_max_stride:
                        new_stride = curr_stride
                        curr_min_stride = new_min_stride
                        curr_max_stride = new_max_stride

            if curr_stride == new_stride:
                curr_count += 1
            elif curr_count == 2 and curr_stride != 1:
                yield _build(curr_start, curr_start, None, zfill)
                curr_start = last_frame
                curr_stride = new_stride
                curr_strides = {new_stride}
                curr_min_stride = None
                curr_max_stride = None
            else:
                if isinstance(curr_stride, decimal.Decimal):
                    stride = curr_strides.pop() if len(curr_strides) == 1 else None
                    yield _build_decimal(curr_start, last_frame, curr_count,
                        stride, curr_min_stride, curr_max_stride, zfill)
                else:
                    yield _build(curr_start, last_frame, curr_stride, zfill)
                curr_stride = None
                curr_strides = None
                curr_min_stride = None
                curr_max_stride = None
                curr_start = curr_frame
                curr_count = 1

            last_frame = curr_frame

        if curr_count == 2 and curr_stride != 1:
            yield _build(curr_start, curr_start, None, zfill)
            yield _build(curr_frame, curr_frame, None, zfill)
        else:
            if isinstance(curr_stride, decimal.Decimal):
                stride = curr_strides.pop() if len(curr_strides) == 1 else None
                yield _build_decimal(curr_start, curr_frame, curr_count,
                    stride, curr_min_stride, curr_max_stride, zfill)
            else:
                yield _build(curr_start, curr_frame, curr_stride, zfill)
Exemple #4
0
    def __init__(self, frange):
        """Initialize the :class:`FrameSet` object.
        """

        # if the user provides anything but a string, short-circuit the build
        if not isinstance(frange, futils.string_types):
            # if it's apparently a FrameSet already, short-circuit the build
            if set(dir(frange)).issuperset(self.__slots__):
                for attr in self.__slots__:
                    setattr(self, attr, getattr(frange, attr))
                return
            # if it's inherently disordered, sort and build
            elif isinstance(frange, Set):
                self._maxSizeCheck(frange)
                self._items = frozenset(normalizeFrames(frange))
                self._order = tuple(sorted(self._items))
                self._frange = self.framesToFrameRange(
                    self._order, sort=False, compress=False)
                return
            # if it's ordered, find unique and build
            elif isinstance(frange, Sequence):
                self._maxSizeCheck(frange)
                items = set()
                order = unique(items, normalizeFrames(frange))
                self._order = tuple(order)
                self._items = frozenset(items)
                self._frange = self.framesToFrameRange(
                    self._order, sort=False, compress=False)
                return
            # if it's an individual number build directly
            elif isinstance(frange, futils.integer_types + (float, decimal.Decimal)):
                frame = normalizeFrame(frange)
                self._order = (frame, )
                self._items = frozenset([frame])
                self._frange = self.framesToFrameRange(
                    self._order, sort=False, compress=False)
            # in all other cases, cast to a string
            else:
                try:
                    frange = asString(frange)
                except Exception as err:
                    msg = 'Could not parse "{0}": cast to string raised: {1}'
                    raise ParseException(msg.format(frange, err))

        # we're willing to trim padding characters from consideration
        # this translation is orders of magnitude faster than prior method
        if futils.PY2:
            frange = bytes(frange).translate(None, ''.join(self.PAD_MAP.keys()))
            self._frange = asString(frange)
        else:
            frange = str(frange)
            for key in self.PAD_MAP:
                frange = frange.replace(key, '')
            self._frange = asString(frange)

        # because we're acting like a set, we need to support the empty set
        if not self._frange:
            self._items = frozenset()
            self._order = tuple()
            return

        # build the mutable stores, then cast to immutable for storage
        items = set()
        order = []

        maxSize = constants.MAX_FRAME_SIZE

        frange_parts = []
        frange_types = []
        for part in self._frange.split(","):
            # this is to deal with leading / trailing commas
            if not part:
                continue
            # parse the partial range
            start, end, modifier, chunk = self._parse_frange_part(part)
            frange_parts.append((start, end, modifier, chunk))
            frange_types.extend(map(type, (start, end, chunk)))

        # Determine best type for numbers in range. Note that
        # _parse_frange_part will always return decimal.Decimal for subframes
        FrameType = int
        if decimal.Decimal in frange_types:
            FrameType = decimal.Decimal

        for start, end, modifier, chunk in frange_parts:
            # handle batched frames (1-100x5)
            if modifier == 'x':
                frames = xfrange(start, end, chunk, maxSize=maxSize)
                frames = [FrameType(f) for f in frames if f not in items]
                self._maxSizeCheck(len(frames) + len(items))
                order.extend(frames)
                items.update(frames)
            # handle staggered frames (1-100:5)
            elif modifier == ':':
                if '.' in futils.native_str(chunk):
                    raise ValueError("Unable to stagger subframes")
                for stagger in range(chunk, 0, -1):
                    frames = xfrange(start, end, stagger, maxSize=maxSize)
                    frames = [f for f in frames if f not in items]
                    self._maxSizeCheck(len(frames) + len(items))
                    order.extend(frames)
                    items.update(frames)
            # handle filled frames (1-100y5)
            elif modifier == 'y':
                if '.' in futils.native_str(chunk):
                    raise ValueError("Unable to fill subframes")
                not_good = frozenset(xfrange(start, end, chunk, maxSize=maxSize))
                frames = xfrange(start, end, 1, maxSize=maxSize)
                frames = (f for f in frames if f not in not_good)
                frames = [f for f in frames if f not in items]
                self._maxSizeCheck(len(frames) + len(items))
                order.extend(frames)
                items.update(frames)
            # handle full ranges and single frames
            else:
                frames = xfrange(start, end, 1 if start < end else -1, maxSize=maxSize)
                frames = [FrameType(f) for f in frames if f not in items]
                self._maxSizeCheck(len(frames) + len(items))
                order.extend(frames)
                items.update(frames)

        # lock the results into immutable internals
        # this allows for hashing and fast equality checking
        self._items = frozenset(items)
        self._order = tuple(order)