Created
May 30, 2024 20:42
-
-
Save astiob/9e687c04d2e712fed930318b1a9b00b5 to your computer and use it in GitHub Desktop.
VapourSynth script for converting interlaced fades over telecined content to pure-progressive fades
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import vapoursynth as vs | |
from vsdeinterlace.combing import vinverse | |
__all__ = ( | |
'smooth_stable_fade', | |
'unfade_unique_into_repeating', | |
'unfade_repeating_into_unique', | |
'fade_unique_into_repeating', | |
'fade_repeating_into_unique', | |
'fade_repeating_into_repeating', | |
) | |
c = vs.core | |
def smooth_stable_fade_fields(clip, first, last, triple_field_index, extra_clip=None, merge_expr=None, unique_expr=None): | |
end = last + 1 | |
clip = clip.std.SeparateFields() | |
first *= 2 | |
end *= 2 | |
offset = 0 | |
if first: | |
match triple_field_index: | |
case 3: | |
clip = c.std.Splice([clip[:first], clip[first - 2], clip[first + 1:]]) | |
offset = -1 | |
case 4: | |
offset = 1 | |
first -= offset | |
triple_field_index = (triple_field_index + offset) % 5 | |
n = end - first | |
match (n - triple_field_index) % 5: | |
case 1: | |
end -= 1 | |
case 2: | |
if end < clip.num_frames: | |
clip = c.std.Splice([clip[:end - 2], clip[end], clip[end - 1:]]) | |
end -= 2 | |
fade = clip[first:end].akarin.PropExpr(lambda: {'FadeFieldIndex': f'N {offset} -'}) | |
if extra_clip: | |
extra_clip = extra_clip[first:end] | |
a = triple_field_index | |
b = a + 2 | |
if merge_expr is None: | |
mean = c.std.Merge(fade[a::5], fade[b::5]) | |
elif extra_clip: | |
mean = c.akarin.Expr([fade[a::5], fade[b::5], extra_clip[a::5], extra_clip[b::5]], merge_expr) | |
else: | |
mean = c.akarin.Expr([fade[a::5], fade[b::5]], merge_expr) | |
mean = mean.akarin.PropExpr(lambda: {'FadeFieldIndex': f'N 5 * {a + 1 - offset} +'}) | |
if unique_expr is None: | |
cycle = [mean if i in (a, b) else fade[i::5] for i in range(5)] | |
elif extra_clip: | |
cycle = [mean if i in (a, b) else c.akarin.Expr([fade[i::5], extra_clip[i::5]], unique_expr) for i in range(5)] | |
else: | |
cycle = [mean if i in (a, b) else fade[i::5].akarin.Expr(unique_expr) for i in range(5)] | |
return c.std.Splice([ | |
clip[:first], | |
c.std.Interleave(cycle), | |
clip[end:], | |
]) | |
def smooth_stable_fade(clip, first, last, triple_field_index): | |
return smooth_stable_fade_fields(clip, first, last, triple_field_index).std.DoubleWeave()[::2] | |
def unfade_unique_into_repeating(clip, first, last, repeating_clip, triple_field_index=None): | |
end = last + 1 | |
n = (end - first) * 2 | |
right = repeating_clip | |
right = (right * (clip.num_frames // right.num_frames + 2))[-end % right.num_frames:][:clip.num_frames] | |
if triple_field_index is None: | |
return c.akarin.Expr([clip, right], f'N {first} - 2 * 1 + Y 2 % + I! x {n} * y I@ * - {n} I@ - /') | |
else: | |
right = c.akarin.PropExpr([right, clip], lambda: {'_FieldBased': 'y._FieldBased'}) | |
right = right.std.SeparateFields() | |
# NB: may alter one additional field preceding {first} | |
clip = smooth_stable_fade_fields(clip, first, last, triple_field_index, | |
extra_clip=right, | |
merge_expr=f''' | |
x.FadeFieldIndex 1 + I! | |
y.FadeFieldIndex 1 + J! | |
{n} x * I@ z * - {n} I@ - * | |
{n} y * J@ a * - {n} J@ - * + | |
{n} I@ - dup * | |
{n} J@ - dup * + | |
/ | |
''', | |
unique_expr=f'x.FadeFieldIndex 1 + I! x {n} * y I@ * - {n} I@ - /', | |
) | |
return clip.std.DoubleWeave()[::2] | |
def unfade_repeating_into_unique(clip, first, last, repeating_clip, triple_field_index=None): | |
end = last + 1 | |
n = (end - first) * 2 | |
left = repeating_clip | |
left = (left * (clip.num_frames // left.num_frames + 2))[-first % left.num_frames:][:clip.num_frames] | |
if triple_field_index is None: | |
return c.akarin.Expr([left, clip], f'N {first} - 2 * 1 + Y 2 % + I! y {n} * x {n} I@ - * - I@ /') | |
else: | |
left = c.akarin.PropExpr([left, clip], lambda: {'_FieldBased': 'y._FieldBased'}) | |
left = left.std.SeparateFields() | |
# NB: may alter one additional field preceding {first} | |
clip = smooth_stable_fade_fields(clip, first, last, triple_field_index, | |
extra_clip=left, | |
merge_expr=f''' | |
x.FadeFieldIndex 1 + I! | |
y.FadeFieldIndex 1 + J! | |
{n} x * {n} I@ - z * - I@ * | |
{n} y * {n} J@ - a * - J@ * + | |
I@ dup * | |
J@ dup * + | |
/ | |
''', | |
unique_expr=f'x.FadeFieldIndex 1 + I! x {n} * y {n} I@ - * - I@ /', | |
) | |
return clip.std.DoubleWeave()[::2] | |
def fade_unique_into_repeating(clip, first, last, repeating_clip, delay: float, protect_top=0, protect_bottom=0): | |
end = last + 1 | |
right = repeating_clip | |
right = (right * (clip.num_frames // right.num_frames + 2))[-end % right.num_frames:][:clip.num_frames] | |
# Some top & bottom pixels of this flashback are 2px-per-band gradients. | |
# Vinverse happily smooths them out to 1px per band, | |
# but that contrasts with the non-crossfade frames, so avoid that. | |
stack = [] | |
if protect_top: | |
stack.append(clip.std.Crop(bottom = clip.height - protect_top)) | |
stack.append(vinverse(clip).std.Crop(top=protect_top, bottom=protect_bottom)) | |
if protect_bottom: | |
stack.append(clip.std.Crop(top = clip.height - protect_bottom)) | |
clip = c.std.StackVertical(stack) | |
return c.akarin.Expr([clip, right], f'N {first - 1 + delay} - {end - first} / A! x 1 A@ - * y A@ * +') | |
def fade_repeating_into_unique(clip, first, last, repeating_clip, delay: float, protect_top=0, protect_bottom=0): | |
end = last + 1 | |
left = repeating_clip | |
left = (left * (clip.num_frames // left.num_frames + 2))[-first % left.num_frames:][:clip.num_frames] | |
# Some top & bottom pixels of this flashback are 2px-per-band gradients. | |
# Vinverse happily smooths them out to 1px per band, | |
# but that contrasts with the non-crossfade frames, so avoid that. | |
stack = [] | |
if protect_top: | |
stack.append(clip.std.Crop(bottom = clip.height - protect_top)) | |
stack.append(vinverse(clip).std.Crop(top=protect_top, bottom=protect_bottom)) | |
if protect_bottom: | |
stack.append(clip.std.Crop(top = clip.height - protect_bottom)) | |
clip = c.std.StackVertical(stack) | |
return c.akarin.Expr([left, clip], f'N {first - 1 + delay} - {end - first} / A! x 1 A@ - * y A@ * +') | |
def fade_repeating_into_repeating(clip, first, last, left, right, delay: float): | |
end = last + 1 | |
left = (left * (clip.num_frames // left.num_frames + 2))[-first % left.num_frames:][:clip.num_frames] | |
right = (right * (clip.num_frames // right.num_frames + 2))[-end % right.num_frames:][:clip.num_frames] | |
return c.akarin.Expr([left, right], f'N {first - 1 + delay} - {end - first} / A! x 1 A@ - * y A@ * +') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment