コード例 #1
0
def gencomp(num: int = 10,
            path: str = "comp",
            matrix: str = "709",
            firstnum: int = 1,
            **clips: vs.VideoNode) -> None:
    lens = set(c.num_frames for c in clips.values())
    if len(lens) != 1:
        raise ValueError("gencomp: 'Clips must be equal length!'")

    frames = sorted(random.sample(range(lens.pop()), num))
    print("Sample frames: " + str(frames))

    if os.path.exists(path):
        shutil.rmtree(path)

    os.makedirs(path)

    for name, clip in clips.items():
        log.status(f"Rendering clip {name}")
        splice = clip[frames[0]]
        for f in frames[1:]:
            splice += clip[f]
        splice = splice.resize.Bicubic(format=vs.RGB24, matrix_in_s=matrix) \
            .imwri.Write("PNG", os.path.join(path, f"{name}%0{len(str(num))}d.png"), firstnum=firstnum)
        [splice.get_frame(f) for f in range(splice.num_frames)]
コード例 #2
0
def diff(
    *clips: vs.VideoNode,
    thr: float = 72,
    height: int = 288,
    return_array: bool = False,
    return_frames: bool = False,
    **namedclips: vs.VideoNode
) -> Union[vs.VideoNode, Tuple[vs.VideoNode, List[int]]]:
    """
    Creates a standard :py:class:`lvsfunc.comparison.Stack` between frames from two clips that have differences.
    Useful for making comparisons between TV and BD encodes, as well as clean and hardsubbed sources.

    There are two methods used here to find differences.
    If `thr` is below 1, PlaneStatsDiff is used to figure out the differences.
    Else, if `thr` is equal than or higher than 1, PlaneStatsMin/Max are used.

    Recommended is PlaneStatsMin/Max, as those seem to catch
    more outrageous differences more easily and not return
    too many starved frames.

    Note that this might catch artifacting as differences!
    Make sure you verify every frame with your own eyes!

    Alias for this function is `lvsfunc.diff`.

    :param clips:         Clips for comparison (order is kept)
    :param namedclips:    Keyword arguments of `name=clip` for all clips in the comparison.
                          Clips will be labeled at the top left with their `name`.
    :param thr:           Threshold, <= 1 uses PlaneStatsDiff, >1 uses Max/Min.
                          Value must be below 128 (Default: 72)
    :param height:        Height in px to downscale clips to if `return_array` is ``False``
                          (MakeDiff clip will be twice this resolution) (Default: 288)
    :param return_array:  Return frames as an interleaved comparison (using :py:class:`lvsfunc.comparison.Interleave`)
                          (Default: ``False``)
    :param return_frames: Adds `frames list` to the return. (Default: ``False``)

    :return: Either an interleaved clip of the differences between the two clips
             or a stack of both input clips on top of MakeDiff clip.
             Optionally, you can allow the function to also return a list of frames as well.
    """
    if clips and namedclips:
        raise ValueError(
            "diff: positional clips and named keyword clips cannot both be given"
        )

    if (clips and len(clips) != 2) or (namedclips and len(namedclips) != 2):
        raise ValueError("diff: must pass exactly 2 `clips` or `namedclips`")

    if thr >= 128:
        raise ValueError("diff: `thr` must be below 128")

    if clips:
        if any(c.format is None for c in clips):
            raise ValueError("diff: variable-format clips not supported")
    elif namedclips:
        if any(nc.format is None for nc in namedclips.values()):
            raise ValueError("diff: variable-format namedclips not supported")

    if clips:
        a, b = vsutil.depth(clips[0], 8), vsutil.depth(clips[1], 8)
    elif namedclips:
        nc = list(namedclips.values())
        a, b = vsutil.depth(nc[0], 8), vsutil.depth(nc[1], 8)

    frames = []
    if thr <= 1:
        for i, f in enumerate(core.std.PlaneStats(a, b).frames()):
            print(f"Progress: {i}/{a.num_frames} frames", end="\r")
            if get_prop(f, 'PlaneStatsDiff', float) > thr:
                frames.append(i)
        diff = core.std.MakeDiff(a, b)
    else:
        diff = core.std.MakeDiff(a, b).std.PlaneStats()
        if diff.format is None:
            raise ValueError("diff: variable-format clips not supported"
                             )  # this is for mypy
        t = float if diff.format.sample_type == vs.SampleType.FLOAT else int
        for i, f in enumerate(diff.frames()):
            print(f"Progress: {i}/{diff.num_frames} frames", end='\r')
            if get_prop(f, 'PlaneStatsMin', t) <= thr or get_prop(
                    f, 'PlaneStatsMax', t) >= 255 - thr > thr:
                frames.append(i)

    if not frames:
        raise ValueError("diff: no differences found")

    if clips:
        name_a, name_b = "Clip A", "Clip B"
    else:
        name_a, name_b = namedclips.keys()

    if return_array:
        a, b = a.text.FrameNum(9), b.text.FrameNum(9)
        comparison = Interleave({
            f'{name_a}':
            core.std.Splice([a[f] for f in frames]),
            f'{name_b}':
            core.std.Splice([b[f] for f in frames])
        }).clip
        return comparison if not return_frames else (comparison, frames)

    else:
        scaled_width = vsutil.get_w(height, only_even=False)
        diff = diff.resize.Spline36(width=scaled_width * 2,
                                    height=height * 2).text.FrameNum(9)
        a, b = (c.resize.Spline36(width=scaled_width,
                                  height=height).text.FrameNum(9)
                for c in (a, b))

        diff_stack = Stack({
            f'{name_a}': core.std.Splice([a[f] for f in frames]),
            f'{name_b}': core.std.Splice([b[f] for f in frames])
        }).clip
        diff = diff.text.Text(text='diff', alignment=8)
        diff = core.std.Splice([diff[f] for f in frames])

        comparison = Stack((diff_stack, diff),
                           direction=Direction.VERTICAL).clip
        return comparison if not return_frames else (comparison, frames)
コード例 #3
0
def comp(*frames: int,
         rand: int = 0,
         slicing: bool = False,
         slices: List[str] = None,
         full: bool = False,
         label: bool = True,
         label_size: int = 30,
         label_alignment: int = 7,
         stack_type: str = 'clip',
         **in_clips: vs.VideoNode) -> vs.VideoNode:
    """
    All-encompassing comparison tool for VapourSynth preview.

    Allows an infinite number of clips to be compared.
    Can compare entire clips, frames, or slices.
    Visually arranges clips in five ways:
        continuous clip (A0 B0 A1 B1)
        vertical stacking
        horizontal stacking
        mosaic
        split (A | B [| C])

    :param frames: frame number(s) to be compared
        Can be left blank.

    :param rand: number of random frames to compare from all clips (Default value = 0)
        Can be left blank.

    :param slicing: changes output to slicing mode (Default value = False)
        Overrides 'frames' and 'rand'.

    :param slices: Python slices of all clips to be compared (Default value = None)
        Does not accept advanced / combined slicing.
        Example: '[":16","200:400","570:"]' for frames 0-15,200-399,570+
        Can be left blank is slicing is False.

    :param full: whether or not to compare full length of clips (Default value = False)
        Overrides 'frames', 'rand', and 'slicing'/'slices'

    :param label: labels clips with their name (Default value = True)

    :param label_size: <int> fontsize for 'label' (Default value = 30)

    :param label_alignment: numpad alignment of 'label' (Default value = 7)

    :param stack_type: type of comparison to output (Default value = 'clip')
        Accepts 'clip', 'vertical', 'horizontal', 'mosaic', 'split'.
        'split' allows only 2 or 3 clips and overrides 'label_alignment'

    :param in_clips: comma separated pairs of name=clip
        :bit depth: ANY
        :color family: ANY
        :float precision: ANY
        :sample type: ANY
        :subsampling: ANY

    :returns: processed clip
    """
    def _markclips(clips, names, label_size,
                   label_alignment) -> List[vs.VideoNode]:
        style = f'sans-serif,{label_size},&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,3,1,' \
                f'{label_alignment},10,10,10,1'
        margins = [10, 0, 10, 0]
        markedclips = []

        if type(clips) == vs.VideoNode:
            return core.sub.Subtitle(clips,
                                     str(names),
                                     style=style,
                                     margins=margins)
        else:
            for name, clip in zip(names, clips):
                markedclip = core.sub.Subtitle(clip,
                                               str(name),
                                               style=style,
                                               margins=margins)
                markedclips.append(markedclip)

        return markedclips

    def _cutclips(clips, frames, rand) -> List[vs.VideoNode]:
        if slicing:
            cut_clips = []
            for i, clip in enumerate(clips):
                cut_clips.append(core.std.BlankClip(clip, length=1))
                for s in slices:
                    a, b = s.split(':')
                    if a == '': cut_clips[i] += clip[:int(b)]
                    elif b == '': cut_clips[i] += clip[int(a):]
                    else: cut_clips[i] += clip[int(a):int(b)]

        else:
            if len(frames) == 0 and rand < 1: rand = 1
            if rand > 0:
                max_frame = min(clip.num_frames for clip in clips) - 1
                if rand == 1: frames.append(randint(0, max_frame))
                else: frames = frames + sample(range(max_frame), rand)

            cut_clips = []
            for i, clip in enumerate(clips):
                cut_clips.append(core.std.BlankClip(clip, length=1))
                for f in frames:
                    cut_clips[i] += clip[f]

        for i in range(len(cut_clips)):
            cut_clips[i] = cut_clips[i][1:]

        return cut_clips

    def _assemble(markedclips: List[vs.VideoNode],
                  stack_type: str) -> vs.VideoNode:
        def _stack2d(clips, size):
            rows = []
            for i in range(0, size):
                min_s = (i * size)
                max_s = ((i + 1) * size)
                if i == 0:
                    row_clips = clips[:max_s]
                    rows.append(core.std.StackHorizontal(row_clips))
                else:
                    row_clips = clips[min_s:max_s]
                    rows.append(core.std.StackHorizontal(row_clips))

            return core.std.StackVertical(rows)

        def _root_check(clips):
            def _blank_create(clips, size):
                blank_clips = []
                blank_number = (size**2) - len(clips)
                for _ in range(0, blank_number):
                    blank_clips.append(core.std.BlankClip(clips[0], length=1))
                added_clips = clips + blank_clips

                return _stack2d(added_clips, size)

            root = sqrt(len(clips))
            size = floor(root) + 1

            if int(root + 0.5)**2 != len(clips):
                return _blank_create(clips, size)
            else:
                return _stack2d(clips, int(root))

        def _split(clips):
            width = clips[0].width
            if len(clips) == 2:
                clip_left = _markclips(clips[0], names[0], label_size, 7)
                clip_right = _markclips(clips[1], names[1], label_size, 9)

                clip_left = core.std.Crop(clip_left, 0, width / 2, 0, 0)
                clip_right = core.std.Crop(clip_right, width / 2, 0, 0, 0)

                clips_list = clip_left, clip_right

                return core.std.StackHorizontal(clips_list)

            if len(clips) == 3:
                width = floor(width / 3)
                dwidth = 2 * width

                clip_left = _markclips(clips[0], names[0], label_size, 7)
                clip_middle = _markclips(clips[1], names[1], label_size, 8)
                clip_right = _markclips(clips[2], names[2], label_size, 9)

                clip_left = core.std.Crop(clip_left, 0, dwidth, 0, 0)
                clip_middle = core.std.Crop(clip_middle, width, width, 0, 0)
                clip_right = core.std.Crop(clip_right, dwidth, 0, 0, 0)

                clips_list = clip_left, clip_middle, clip_right

                return core.std.StackHorizontal(clips_list)

        if stack_type == 'vertical':
            return core.std.StackVertical(markedclips)
        elif stack_type == 'horizontal' or (stack_type == 'mosaic'
                                            and len(markedclips) < 3):
            return core.std.StackHorizontal(markedclips)
        elif stack_type == 'mosaic':
            return _root_check(markedclips)
        elif stack_type == 'split' and (len(clips) < 2 or len(clips) > 3):
            raise ValueError(
                'comp: \'split\' stack_type only allows 2 or 3 clips')
        elif stack_type == 'split':
            return _split(clips)
        else:
            return core.std.Interleave(markedclips)

    names = list(in_clips.keys())
    clips = list(in_clips.values())

    for i in range(1, len(clips)):
        if clips[i - 1].width != clips[i].width:
            raise ValueError("comp: the width of all clips must be the same")
        if clips[i - 1].height != clips[i].height:
            raise ValueError("comp: the height of all clips must be the same")
        if clips[i - 1].format != clips[i].format:
            raise ValueError("comp: the format of all clips must be the same")

    if not full:
        frames = list(frames)
        clips = _cutclips(clips, frames, rand)

    if label:
        markedclips = _markclips(clips, names, label_size, label_alignment)
    else:
        markedclips = clips

    return _assemble(markedclips, stack_type)
コード例 #4
0
def diff(
    *clips: vs.VideoNode,
    thr: float = 72,
    height: int = 288,
    interleave: bool = False,
    return_ranges: bool = False,
    exclusion_ranges: Sequence[int | Tuple[int, int]] | None = None,
    diff_func: Callable[[vs.VideoNode, vs.VideoNode],
                        vs.VideoNode] = lambda a, b: core.std.MakeDiff(a, b),
    **namedclips: vs.VideoNode
) -> vs.VideoNode | Tuple[vs.VideoNode, List[Tuple[int, int]]]:
    """
    Creates a standard :py:class:`lvsfunc.comparison.Stack` between frames from two clips that have differences.
    Useful for making comparisons between TV and BD encodes, as well as clean and hardsubbed sources.

    There are two methods used here to find differences:
    If `thr` is below 1, PlaneStatsDiff is used to figure out the differences.
    Else, if `thr` is equal than or higher than 1, PlaneStatsMin/Max are used.

    Recommended is PlaneStatsMin/Max, as those seem to catch more outrageous differences
    without returning too many starved frames.

    Note that this might catch artifacting as differences!
    Make sure you verify every frame with your own eyes!

    Alias for this function is `lvsfunc.diff`.

    :param clips:               Clips for comparison (order is kept)
    :param namedclips:          Keyword arguments of `name=clip` for all clips in the comparison.
                                Clips will be labeled at the top left with their `name`.
    :param thr:                 Threshold, <= 1 uses PlaneStatsDiff, > 1 uses Max/Min.
                                Higher values will catch more differences.
                                Value must be lower than 128
    :param height:              Height in px to downscale clips to if `interleave` is ``False``
                                (MakeDiff clip will be twice this resolution)
    :param interleave:          Return clip as an interleaved comparison
                                (using :py:class:`lvsfunc.comparison.Interleave`).
                                This will not return a diff clip
    :param return_ranges:       Return a list of ranges in addition to the comparison clip
    :param exclusion_ranges:    Excludes a list of frame ranges from difference checking output (but not processing)
    :param diff_func:           Function for calculating diff in PlaneStatsMin/Max mode

    :return:                    Either an interleaved clip of the differences between the two clips
                                or a stack of both input clips on top of MakeDiff clip.
                                Furthermore, the function will print the ranges of all the diffs found.
    """
    if clips and namedclips:
        raise ValueError(
            "diff: positional clips and named keyword clips cannot both be given"
        )

    if (clips and len(clips) != 2) or (namedclips and len(namedclips) != 2):
        raise ValueError("diff: must pass exactly 2 `clips` or `namedclips`")

    if thr >= 128:
        raise ValueError("diff: `thr` must be below 128")

    if clips and any(c.format is None for c in clips):
        raise ValueError("diff: variable-format clips not supported")
    elif namedclips and any(nc.format is None for nc in namedclips.values()):
        raise ValueError("diff: variable-format namedclips not supported")

    def _to_ranges(iterable: List[int]) -> Iterable[Tuple[int, int]]:
        iterable = sorted(set(iterable))
        for key, group in groupby(enumerate(iterable), lambda t: t[1] - t[0]):
            groupl = list(group)
            yield groupl[0][1], groupl[-1][1]

    if clips:
        a, b = depth(clips[0], 8), depth(clips[1], 8)
    elif namedclips:
        nc = list(namedclips.values())
        a, b = depth(nc[0], 8), depth(nc[1], 8)

    frames = []
    if thr <= 1:
        ps = core.std.PlaneStats(a, b)

        def _cb(n: int, f: vs.VideoFrame) -> None:
            if get_prop(f, 'PlaneStatsDiff', float) > thr:
                frames.append(n)

        clip_async_render(ps, progress="Diffing clips...", callback=_cb)
        diff = core.std.MakeDiff(a, b)
    else:
        diff = diff_func(a, b).std.PlaneStats()
        assert diff.format is not None
        t = float if diff.format.sample_type == vs.FLOAT else int

        def _cb(n: int, f: vs.VideoFrame) -> None:
            if get_prop(f, 'PlaneStatsMin', t) <= thr or get_prop(
                    f, 'PlaneStatsMax', t) >= 255 - thr > thr:
                frames.append(n)

        clip_async_render(diff, progress="Diffing clips...", callback=_cb)

    if not frames:
        raise ValueError("diff: no differences found")

    frames.sort()

    if exclusion_ranges:
        r: List[int] = []
        for e in exclusion_ranges:
            if isinstance(e, int):
                e = (e, e)
            start, end = e[0], e[1] + 1
            r += list(range(start, end))
        frames = [f for f in frames if f not in r]

    if clips:
        name_a, name_b = "Clip A", "Clip B"
    else:
        name_a, name_b = namedclips.keys()

    if interleave:
        a, b = a.text.FrameNum(9), b.text.FrameNum(9)
        comparison = Interleave({
            f'{name_a}':
            core.std.Splice([a[f] for f in frames]),
            f'{name_b}':
            core.std.Splice([b[f] for f in frames])
        }).clip
    else:
        scaled_width = get_w(height, only_even=False)
        diff = diff.resize.Spline36(width=scaled_width * 2,
                                    height=height * 2).text.FrameNum(9)
        a, b = (c.resize.Spline36(width=scaled_width,
                                  height=height).text.FrameNum(9)
                for c in (a, b))

        diff_stack = Stack({
            f'{name_a}': core.std.Splice([a[f] for f in frames]),
            f'{name_b}': core.std.Splice([b[f] for f in frames])
        }).clip
        diff = diff.text.Text(text='diff', alignment=8)
        diff = core.std.Splice([diff[f] for f in frames])

        comparison = Stack((diff_stack, diff),
                           direction=Direction.VERTICAL).clip

    if return_ranges:
        return comparison, list(_to_ranges(frames))
    else:
        return comparison