Guest User

Vapoursynth AutoChromaFix

a guest
Jun 20th, 2026
72
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 6.20 KB | None | 0 0
  1. import vapoursynth as vs
  2. from functools import partial
  3.  
  4. core = vs.core
  5.  
  6. class ChromaFixer:
  7. def __init__(self, clip, referenceframe, accuracy, maxshift, nodisplay, nofix):
  8. if clip.format.color_family != vs.YUV:
  9. raise ValueError("Input must be YUV format")
  10.  
  11. self.clip = clip
  12. self.referenceframe = referenceframe
  13. self.accuracy = accuracy
  14. self.maxshift = maxshift
  15. self.nodisplay = nodisplay
  16. self.nofix = nofix
  17. self.shifts = {} # Stores {'u': (h,v), 'v': (h,v)} for each frame
  18. self.ref_shift = None
  19.  
  20. # Get chroma subsampling factors
  21. self.ssw = clip.format.subsampling_w
  22. self.ssh = clip.format.subsampling_h
  23.  
  24. # Pre-calculate dimensions
  25. self.luma_width = clip.width
  26. self.luma_height = clip.height
  27. self.chroma_width = clip.width >> self.ssw
  28. self.chroma_height = clip.height >> self.ssh
  29.  
  30. def process_frame(self, n):
  31. frame = self.clip[n]
  32.  
  33. # If using reference frame and we've already calculated it
  34. if self.referenceframe >= 0 and self.ref_shift is not None:
  35. return self.apply_shifts(frame, self.ref_shift)
  36.  
  37. # Calculate new shifts if needed
  38. if self.referenceframe < 0 or n == self.referenceframe:
  39. # Extract planes
  40. y = frame.std.ShufflePlanes(planes=0, colorfamily=vs.GRAY)
  41. u = frame.std.ShufflePlanes(planes=1, colorfamily=vs.GRAY)
  42. v = frame.std.ShufflePlanes(planes=2, colorfamily=vs.GRAY)
  43.  
  44. # Find optimal shifts
  45. u_shift = self.find_chroma_shift(y, u)
  46. v_shift = self.find_chroma_shift(y, v)
  47.  
  48. # Store results
  49. shifts = {'u': u_shift, 'v': v_shift}
  50. if self.referenceframe >= 0:
  51. self.ref_shift = shifts
  52. else:
  53. self.shifts[n] = shifts
  54.  
  55. return self.apply_shifts(frame, shifts)
  56.  
  57. return frame
  58.  
  59. def find_chroma_shift(self, y_plane, chroma_plane):
  60. """Find optimal shift for one chroma plane relative to luma"""
  61. best_diff = float('inf')
  62. best_shift = (0, 0)
  63.  
  64. # Convert luma to chroma resolution for comparison
  65. y_down = y_plane.resize.Point(
  66. width=self.chroma_width,
  67. height=self.chroma_height,
  68. format=vs.GRAY16
  69. )
  70.  
  71. # Coarse search
  72. for h in range(-self.maxshift, self.maxshift + 1):
  73. for v in range(-self.maxshift, self.maxshift + 1):
  74. shifted = core.resize.Bicubic(
  75. chroma_plane,
  76. src_left=h,
  77. src_top=v,
  78. format=vs.GRAY16
  79. )
  80. diff = self.calculate_alignment_diff(y_down, shifted)
  81. if diff < best_diff:
  82. best_diff = diff
  83. best_shift = (h, v)
  84.  
  85. # Fine search around best match
  86. best_diff = float('inf')
  87. h_base, v_base = best_shift
  88. for h in [h_base + x*self.accuracy for x in range(-2, 3)]:
  89. for v in [v_base + x*self.accuracy for x in range(-2, 3)]:
  90. shifted = core.resize.Bicubic(
  91. chroma_plane,
  92. src_left=h,
  93. src_top=v,
  94. format=vs.GRAY16
  95. )
  96. diff = self.calculate_alignment_diff(y_down, shifted)
  97. if diff < best_diff:
  98. best_diff = diff
  99. best_shift = (h, v)
  100.  
  101. return best_shift
  102.  
  103. def calculate_alignment_diff(self, y, chroma):
  104. """Calculate alignment difference between luma and chroma edges"""
  105. # Edge detection
  106. y_edges = core.std.Prewitt(y)
  107. chroma_edges = core.std.Prewitt(chroma)
  108.  
  109. # Calculate absolute difference
  110. EXPR = core.llvmexpr.Expr if hasattr(core, 'llvmexpr') else core.akarin.Expr if hasattr(core, 'akarin') else core.cranexpr.Expr if hasattr(core, 'cranexpr') else core.std.Expr
  111. diff = EXPR([y_edges, chroma_edges], "x y - abs")
  112. return core.std.PlaneStats(diff).get_frame(0).props.PlaneStatsAverage
  113.  
  114. def apply_shifts(self, frame, shifts):
  115. """Apply the calculated shifts to chroma planes"""
  116. if self.nofix:
  117. result = frame
  118. else:
  119. # Get original planes
  120. y = frame.std.ShufflePlanes(planes=0, colorfamily=vs.GRAY)
  121.  
  122. # Shift chroma planes
  123. u_shifted = core.resize.Bicubic(
  124. frame.std.ShufflePlanes(planes=1, colorfamily=vs.GRAY),
  125. src_left=shifts['u'][0],
  126. src_top=shifts['u'][1]
  127. )
  128. v_shifted = core.resize.Bicubic(
  129. frame.std.ShufflePlanes(planes=2, colorfamily=vs.GRAY),
  130. src_left=shifts['v'][0],
  131. src_top=shifts['v'][1]
  132. )
  133.  
  134. # Recombine planes - CORRECTED PLANE COMBINATION
  135. result = core.std.ShufflePlanes(
  136. clips=[y, u_shifted, v_shifted],
  137. planes=[0, 0, 0],
  138. colorfamily=vs.YUV
  139. )
  140.  
  141. if not self.nodisplay:
  142. result = core.text.Text(
  143. result,
  144. f"U: h={shifts['u'][0]:.2f} (x), v={shifts['u'][1]:.2f} (y)\n"
  145. f"V: h={shifts['v'][0]:.2f} (x), v={shifts['v'][1]:.2f} (y)"
  146. )
  147.  
  148. return result
  149.  
  150. def AutoChromaFix(clip, referenceframe=-1, nodisplay=False, nofix=False, accuracy=0.25, maxshift=2):
  151. """
  152. Correct chroma alignment in YUV video
  153. Parameters:
  154. clip: YUV input clip
  155. referenceframe: Frame to use as reference (-1 = per-frame)
  156. nodisplay: Hide shift values
  157. nofix: Don't apply correction
  158. accuracy: Search precision (0.25 = quarter-pixel)
  159. maxshift: Maximum shift to search (pixels)
  160. """
  161. fixer = ChromaFixer(clip, referenceframe, accuracy, maxshift, nodisplay, nofix)
  162. return core.std.FrameEval(clip, lambda n: fixer.process_frame(n))
  163.  
Advertisement
Add Comment
Please, Sign In to add comment