def _parse_frange_part(cls, frange): """ Internal method: parse a discrete frame range part. Args: frange (str): single part of a frame range as a string (ie "1-100x5") Returns: tuple: (start, end, modifier, chunk) Raises: :class:`.ParseException`: if the frame range can not be parsed """ match = cls.FRANGE_RE.match(frange) if not match: msg = 'Could not parse "{0}": did not match {1}' raise ParseException(msg.format(frange, cls.FRANGE_RE.pattern)) start, end, modifier, chunk = match.groups() start = normalizeFrame(start) end = normalizeFrame(end) if end is not None else start chunk = normalizeFrame(chunk) if chunk is not None else 1 if end > start and chunk is not None and chunk < 0: msg = 'Could not parse "{0}: chunk can not be negative' raise ParseException(msg.format(frange)) # a zero chunk is just plain illogical if chunk == 0: msg = 'Could not parse "{0}": chunk cannot be 0' raise ParseException(msg.format(frange)) return start, end, modifier, abs(chunk)
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)
def _build_frange_part(start, stop, stride, zfill=0): """ Private method: builds a proper and padded frame range string. Args: start (int): first frame stop (int or None): last frame stride (int or None): increment zfill (int): width for zero padding Returns: str: """ if stop is None: return '' pad_start = pad(start, zfill) pad_stop = pad(stop, zfill) if stride is None or start == stop: return '{0}'.format(pad_start) elif abs(stride) == 1: return '{0}-{1}'.format(pad_start, pad_stop) else: stride = normalizeFrame(stride) return '{0}-{1}x{2}'.format(pad_start, pad_stop, stride)
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)