@@ -53,6 +53,69 @@ def yuv_heuristic(width: int, height: int) -> t.Mapping[str, str]:
5353 return result
5454
5555
56+ # Move this function out of the closure to avoid capturing clip.
57+ def _convert_yuv (
58+ c : vs .VideoNode ,
59+ * ,
60+ core : vs .Core ,
61+ real_rgb24 : vs .VideoFormat ,
62+ default_args : t .Dict [str , t .Any ],
63+ scaler : t .Union [str , t .Callable [..., vs .VideoNode ]]
64+ ):
65+ # We make yuv_heuristic not configurable so the heuristic
66+ # will be shared across projects.
67+ #
68+ # In my opinion, this is a quirk that should be shared.
69+
70+ args = {
71+ ** yuv_heuristic (c .width , c .height ),
72+ ** default_args
73+ }
74+
75+ if c .format .subsampling_w != 0 or c .format .subsampling_h != 0 :
76+ # To be clear, scaler should always be a string.
77+ # Being able to provide a callable just makes testing args easier.
78+ resizer = getattr (core .resize , scaler ) if isinstance (scaler , str ) else scaler
79+ else :
80+ # In this case we only do cs transforms, point resize is more then enough.
81+ resizer = core .resize .Point
82+
83+ # Keep bitdepth so we can dither futher down in the RGB part.
84+ return resizer (
85+ c ,
86+ format = real_rgb24 .replace (bits_per_sample = c .format .bits_per_sample ),
87+ ** args
88+ )
89+
90+
91+ # Move this function out of the closure to avoid capturing clip.
92+ def _actually_resize (
93+ c : vs .VideoNode ,
94+ * ,
95+ core : vs .Core ,
96+ convert_yuv : t .Callable [[vs .VideoNode ], vs .VideoNode ],
97+ target_rgb : vs .VideoFormat
98+ ) -> vs .VideoNode :
99+ # Converting to YUV is a little bit more complicated,
100+ # so I extracted it to its own function.
101+ if c .format .color_family == vs .YUV :
102+ c = convert_yuv (c )
103+
104+ # Defaulting prefer_props to True makes resizing choke
105+ # on GRAY clips.
106+ if c .format == vs .GRAY :
107+ c = c .std .RemoveFrameProps ("_Matrix" )
108+
109+ # Actually perform the format conversion on a non-subsampled clip.
110+ if c .format .color_family != vs .RGB or c .format .bits_per_sample != target_rgb .bits_per_sample :
111+ c = core .resize .Point (
112+ c ,
113+ format = target_rgb
114+ )
115+
116+ return c
117+
118+
56119def to_rgb (
57120 clip : vs .VideoNode ,
58121 env : t .Optional [EnvironmentTypes ] = None ,
@@ -61,7 +124,7 @@ def to_rgb(
61124 bits_per_sample : int = 8 ,
62125
63126 # Input: YUV
64- scaler : t .Union [str , t .Callable [..., vs .VideoNode ]] = "Spline36 " ,
127+ scaler : t .Union [str , t .Callable [..., vs .VideoNode ]] = "Bicubic " ,
65128 default_matrix : t .Optional [str ] = None ,
66129 default_transfer : t .Optional [str ] = None ,
67130 default_primaries : t .Optional [str ] = None ,
@@ -78,59 +141,46 @@ def to_rgb(
78141 :param default_*: Manually override the defaults predicted by the heuristics.
79142 :param yuv_heuristic: The heuristic function that takes the frame size and returns a set of yuv-metadata. (For test purposes)
80143 """
144+
145+ # This function does a lot.
146+ # This is why there are so many comments.
147+
148+ default_args = {}
149+ if default_matrix is not None :
150+ default_args ["matrix_in_s" ] = default_matrix
151+ if default_transfer is not None :
152+ default_args ["transfer_in_s" ] = default_transfer
153+ if default_primaries is not None :
154+ default_args ["primaries_in_s" ] = default_primaries
155+ if default_range is not None :
156+ default_args ["range_in_s" ] = default_range
157+ if default_chromaloc is not None :
158+ default_args ["chromaloc_in_s" ] = default_chromaloc
159+
81160 with use_inline ("to_rgb" , env ):
82161 core = vs .core .core
83162 real_rgb24 = core .get_video_format (vs .RGB24 )
84163 target_rgb = real_rgb24 .replace (bits_per_sample = bits_per_sample )
85164
86- def _convert_yuv (c : vs .VideoNode ):
87- # We make yuv_heuristic not configurable so the heuristic
88- #
89- args = {
90- ** yuv_heuristic (c .width , c .height )
91- }
92-
93- # Override heuristics with default values if specified.
94- if default_matrix is not None :
95- args ["matrix_in_s" ] = default_matrix
96- if default_transfer is not None :
97- args ["transfer_in_s" ] = default_transfer
98- if default_primaries is not None :
99- args ["primaries_in_s" ] = default_primaries
100- if default_range is not None :
101- args ["range_in_s" ] = default_range
102- if default_chromaloc is not None :
103- args ["chromaloc_in_s" ] = default_chromaloc
104-
105- # Detect subsampling
106- if clip .format .subsampling_w != 0 or clip .format .subsampling_h != 0 :
107- # Allowing to manually specify a scaler function allows for testing of the args.
108- resizer = getattr (core .resize , scaler ) if isinstance (scaler , str ) else scaler
109- else :
110- resizer = core .resize .Point
111-
112- return resizer (
113- c ,
114- format = target_rgb ,
115- ** args
165+ # This avoids capturing `clip` in a closure creating a self-reference.
166+ convert_yuv = functools .partial (
167+ _convert_yuv ,
168+ core = core ,
169+ real_rgb24 = real_rgb24 ,
170+ default_args = default_args ,
171+ scaler = scaler
116172 )
117173
118- def _actually_resize (c : vs .VideoNode ) -> vs .VideoNode :
119- if c .format .color_family == vs .YUV :
120- c = _convert_yuv (c )
121-
122- if c .format == vs .GRAY :
123- c = c .std .RemoveFrameProps ("_Matrix" )
124-
125- if c .format .color_family != vs .RGB or c .format .bits_per_sample != bits_per_sample :
126- c = core .resize .Point (c , format = target_rgb )
127-
128- return c
174+ actually_resize = functools .partial (
175+ _actually_resize ,
176+ core = core ,
177+ target_rgb = target_rgb ,
178+ convert_yuv = convert_yuv
179+ )
129180
130- with use_inline ("to_rgb" , env ):
131181 return wrap_variable_size (
132182 clip ,
133183 force_assumed_format = target_rgb ,
134- func = _actually_resize
184+ func = actually_resize
135185 )
136186
0 commit comments