Log in

View Full Version : Specifying color (for borders or a blank clip)


hello_hello
11th August 2023, 05:31
For anyone who might want it, here's the latest version of the function discussed in this thread (for specifying color in an Avisynth-like manner).
I've removed all the old links from the thread (as some pointed to an incomplete version) and changed the function name to RGBColor.

Even though the default color is "black" for VapourSynth's AddBorders & BlankClip functions, the added borders are always full range (for 8 bit video the black level is 0 rather than 16 as it should be for limited range video). The RGBColor function will ensure the black level is correct. YUV and RGB are both supported at any bitdepth (except for half float).

Changes for RGBColor version 2024-10-02:

- I hadn't realised the VapourSynth _ColorRange frame property uses 0 for full range and 1 for limited, while the resizers use 0 for limited and 1 for full, so the color range is now specified correctly when RGBColor creates the blank clip it uses for determining the color.
- The previous versions required the user to specify a color. This version defaults to "black".
- "lightgray" wasn't included as a color, only "lightgrey". Both are now included (as the same color).

RGBColor 2025-03-25.zip (https://files.videohelp.com/u/210984/RGBColor%202025-03-25.zip)

There's a help file included.

import vapoursynth as vs
core = vs.core
import RGBColor as rgb

Specifying a border color for the AddBorders function

Adding black borders

clip = core.ffms2.Source("Video.mkv")
clip = core.std.AddBorders(clip, 20,20,0,0, color=rgb.RGBColor(clip))

Adding dark blue borders

clip = core.ffms2.Source("Video.mkv")
clip = core.std.AddBorders(clip, 20,20,0,0, color=rgb.RGBColor(clip, color="darkblue"))

Specifying the color of a blank clip with the same properties as a previous clip

A blank clip with black frames

clip = core.ffms2.Source("Video.mkv")
BClip = core.std.BlankClip(clip, color=rgb.RGBColor(clip))

A blank clip with red frames

clip = core.ffms2.Source("Video.mkv")
BClip = core.std.BlankClip(clip, color=rgb.RGBColor(clip, color="red"))

Specifying the color of a blank clip when no previous clip exists

A blank clip with black frames

BClip = core.std.BlankClip(format=vs.YUV420P8)
BClip = core.std.BlankClip(BClip, color=rgb.RGBColor(BClip))

A blank clip with red frames

BClip = core.std.BlankClip(format=vs.YUV420P8)
BClip = core.std.BlankClip(BClip, color=rgb.RGBColor(BClip, color="red"))

_Al_
11th August 2023, 07:32
something similar to colors_rgb.avsi not sure, but for data like that python can use dictionaries, enums or dataclasses to give values to named objects, so you just create one of those.

To change these data from that colors_rgb.avsi to python's dictionary is in a code below.
Then getting a color from a string:
yellow = colors["yellow"]
So yellow is color for RGB24 (8bit).

To automatize that and get color based on your used clip's format, bitdepth and matrix you can use custom made function that changes color based on clips properties:

import vapoursynth as vs
from vapoursynth import core

clip = core.ffms2.Source(r'video.mp4')
colors = {
"color_aliceblue" : (240, 248, 255),
"color_antiquewhite" : (250, 235, 215),
"color_aqua" : (0, 255, 255),
#etc.
"yellow" : (255, 255, 0),
}

def get_clip_color(clip, rgb_color: tuple=None, string_color: str=None):
"""
pass rgb_color (8bit tuple) or string_color
"""
if rgb_color is None:
color = colors[string_color]
else:
color = rgb_color
blank_rgb = core.std.BlankClip(color=color)

matrix_prop = clip.get_frame(0).props.get("_Matrix", None)
if matrix_prop in [0,2,3]: matrix_prop = None
matrix_size = None
if not clip.format.color_family==vs.RGB:
if clip.height <= 576: matrix_size = 5 #470bg or 170m
elif clip.height < 1090: matrix_size = 1 #709
else: matrix_size = 9 #2020ncl
my_color_clip = blank_rgb.resize.Point(format=clip.format.id, matrix=matrix_prop or matrix_size)
#getting color values from 0,0 pixel
p0, p1, p2 = (None, None, None)
f = my_color_clip.get_frame(0)
planes =[f[i] for i in range(f.format.num_planes)]
p0 = planes[0][0,0]
try: p1 = planes[1][0,0]
except IndexError: pass
try: p2 = planes[2][0,0]
except IndexError: pass
return (p0,p1,p2) if p1 is not None else p0

color = get_clip_color(clip, string_color="yellow")
#color = get_clip_color(clip, rgb_color=(255, 255, 0))
print(color)

_Al_
11th August 2023, 08:03
or those dictionaries could be left with those hex strings and let code to change it to rgb tuple:

def hex_to_rgb(hex):
return tuple(int(hex[i:i+2], 16) for i in (0, 2, 4))

hexstring = "FFFF00"
rgb_color = hex_to_rgb(hexstring)) # (255, 255, 0)

hello_hello
11th August 2023, 08:47
I couldn't get RGB values to work correctly for YUV, which is why my example contains YUV values too, but maybe I was doing something silly.

Thanks for the info. I've been on the computer all day and my brain has just about shut down, so I'll return tomorrow when I can take it in.

Cheers.

hello_hello
24th August 2023, 16:26
I'm on my way to a colors_rgb type function for Vapoursynth. It's working perfectly if the video is RGB, but not so much for YUV.

Any suggestions welcome. I'm still very new to Python-speak.
<link removed>

To specify a border color for AddBorders (RGB clip) (should be okay for any bitdepth).

import vapoursynth as vs
vc = vs.core
import colors

clip = <SomethingRGB>
BorderColor = colors.RGBColor(clip, color='aqua')
clip = clip.std.AddBorders(32,32,32,32, color=BorderColor)

To specify the color of a new BlankClip (RGB).

import vapoursynth as vs
vc = vs.core
import colors

clip = vc.std.BlankClip(format=vs.RGBS)
ClipColor = colors.RGBColor(clip, color='aqua')
clip = vc.std.BlankClip(clip, color=ClipColor)

So now I have to work out how to convert the RGB values to YUV, using the correct matrix I suppose, and I'll need someone to explain how to scale YUV values for float. When I used YUV values for a YUV clip they scaled as I expected for integer bit depths, but not for float.

hello_hello
24th August 2023, 16:29
or those dictionaries could be left with those hex strings and let code to change it to rgb tuple:

def hex_to_rgb(hex):
return tuple(int(hex[i:i+2], 16) for i in (0, 2, 4))

hexstring = "FFFF00"
rgb_color = hex_to_rgb(hexstring)) # (255, 255, 0)

If I'd seen your post I would have done it that way, but unfortunately I didn't see it until just now, so I guess I converted the hex values to RGB the hard way. I'm still very new to dealing with tuples (although is the way I did it considered a tuple?).

PS. Is the matrix a factor when adding borders to an RGB clip or just YUV? I'm still trying to get my head around your original function.

Thanks.

hello_hello
24th August 2023, 18:32
_Al_ ,

I used your method above instead of the way I was originally doing it, so now the color can be specified either way, similar to Avisynth.

Still only works for RGB.
<link removed>

BColor = colors.RGBColor(clip, 'FFFF00')
or
BColor = colors.RGBColor(clip, 'yellow')

clip = clip.std.AddBorders(32,32,32,32, color=BColor)

_Al_
24th August 2023, 21:51
Is the matrix a factor when adding borders to an RGB clip or just YUV?
no, only for yuv clips, in that example I posted in that get_clip_color() , matrix would end up being None if clip was rgb, which resize function would accept, that's a default value, if not specified anyway.

That get_clip_color function can accept rgb or yuv clip and returns rgb or yuv tuple, or just a single value if t is a gray clip
the principle of that function is making blankclip of desired rgb color, then using resize function to change it into clips format and color, in rgb or yuv format,
then simply getting color for 0,0 pixel values, (could be any pixel, but with 0,0 pixel we do not need to deal with subsampling shifted positions for chroma), gives you desired color in particular bitdepth either for rgb or yuv, that code could be improved a bit so it is more readable to something like this:
#getting color tuple from 0,0 pixel from yuv, rgb or gray clip:
f = clip.get_frame(0) #getting a first frame for a clip that gives us an access to clips planes - one plane (gray clip) or three planes (rgb or yuv clip)
#f[0] is first plane, first slice of frame f, it is a two dimensional array of values, same as numpy array would be, values are accesed like this: value=f[0][y,x] for x,y pixel coordinates
p0 = f[0][0,0] #this is R value for rgb clip or Y value for yuv clip, for 0,0 pixel of the first plane
if f.format.name.startswith('Gray'):
return p0 #only Y value if clip is gray (there is one plane only for gray clip)
return p0, f[1][0,0], f[2][0,0] #R, G, B values for rgb clip or Y, U, V values for yuv clip

that function returns any bitdepth even float tuple if a clip is 32bit (float values), no need to scale it to a bitdepth as you did in colors.py,
no calculations are needed or to get yuv values from rgb etc., let vapoursynth resize function to come up with proper values

_Al_
24th August 2023, 22:49
I include it here as well, just if someone wants to get a pixel value of yuv clip, where subsampling is important, if wanting any pixel values, not just 0,0 pixel, chroma planes could be smaller than first luma plane (YUV420, YUV422, YUV411), so in that case getting a pixel value for x, y coordinates for frame 0, would be:
f = clip.get_frame(0)
p0 = f[0][y,x]
if f.format.name.startswith('Gray'):
return p0
ys = y >> f.format.subsampling_h
xs = x >> f.format.subsampling_w
return p0, f[1][ys,xs], f[2][ys,xs]
which would work even in above function, even for any rgb besides any yuv, because subsampling_h and subsampling_w are 0 for rgb as well, same as for YUV444, so there would not be any binary shift (1 or 2 which equals decimal division by 2 or 4)

_Al_
25th August 2023, 00:05
Also, as using python now, you can use thousands of its modules that come with python as well, like color picker, so you can implement to select color from a GUI interface:

import tkinter
from tkinter import colorchooser
root = tkinter.Tk()
root.withdraw()
color = colorchooser.askcolor(title ="Choose color")
if not color:
root.mainloop()
else:
rgb_color, hex_color = color
root.destroy()
print(rgb_color)

hello_hello
25th August 2023, 00:50
_Al_,
I've only skimmed over your last posts as I've got to spend some time in the real world, but I've been playing around with the colors function and it now converts the RGB values to YUV.
I'm not sure if the formula is always the same, and obviously the primaries differ for rec.601, 709, and 2020. The difference between the first two probably isn't enough to matter, but I assume the sRGB primaries should be converted to rec.2020 primaries when adding YUV borders, but if that's correct I'm not sure how to do it yet. The function scales YUV correctly for integer bit depths, but it's obviously wrong for float.

Anyway, I'll be back at some stage to re-read your posts and try to take them in, but in the mean time I've replaced the version of colors.py I uploaded earlier with the new version, which works for YUV of integer bitdepths now.

Cheers.

_Al_
25th August 2023, 01:34
I got correct results for float formats as well.
Using "yellow" color for YUV444PS clip I got: (0.9277999997138977, -0.5, 0.045847088098526)
Using "yellow" color for RGBS clip I got : (1.0, 1.0, 0)
previewer shows the same yellow rgb 255,255,0 for borders if I use those colors for particular clip in AddBorders(), as for any integer "yelow" argument

As for get_clip_color() from above, if matrix is in clip's props, than correct matrix is used for resize conversion and we get correct colors, if not it defaults depending on size(that can be changed).

You can include the same code for primaries and transfer, because if you have a in value and out value is not specified, you might get resize error. I do the same thing when automatizing these things.
Checking props if _Matrix, _Primaries, _Transfer is set and then get out arguments for resize, even updating props afterwords, like in that example (https://forum.videohelp.com/threads/408230-ffmpeg-avc-from-jpgs-of-arbitrary-dimensions-maintaining-aspect-ratio/page4#post2694300) but that could get complicated, I use even mediainfo in that example, where I should use rather vapoursynth props only. For your purpose, you might just copy your clip and delete those particular props and just be dealing with matrix maybe. It is just a color picker, it might not be that important.

hello_hello
25th August 2023, 12:04
I haven't looked back over your earlier posts yet (short on time), but after reading your last post and looking at your numbers I thought maybe after scaling I needed to subtract 0.5 from the UV values for float, so zero becomes the equivalent of 128 in 8 bit. It's still not quite right though, and my calculation for 8 bit YUV yellow isn't quite RGB yellow. I'm wondering if it's the matrix being used to convert the video to RGB for display. How exactly did you calculate your numbers?

What I'm currently getting using the formula in the script (the output of a blank clip).

For yellow:

https://imgur.com/xQaIZiF.png

https://imgur.com/KWsn3lC.png

https://imgur.com/tV4XX4l.png

https://imgur.com/HyssZxC.png

Your numbers:

https://imgur.com/cQf2WCw.png

PS I found an online RGB to YUV converter and the numbers it calculates for 8 bit yellow are not quite, but very close to the numbers I'm getting.
https://www.calculatormix.com/conversions/color/rgb-to-yuv/#google_vignette
I don't know why there's a small difference.

It turns out that using the same numbers for 8 bit I get pure yellow after a conversion from 601 to 709 (excluding the primaries), so the difference is probably there somewhere, at least for integer.

https://imgur.com/JaDjlfv.png

_Al_
25th August 2023, 14:46
I do not calculate anything, I use zimg resizer (default in vapoursynth) to do the math. Making sure matrix is correct (and possibly primaries or transfer if they are set in clip as well). Having simple one color yellow (255,255,0) RGB blank clip and resizing that clip to clips format. Then reading its color values from its planes. That is what get_clip_color() does. All possible format values loading into previewer then give 255,255,0 yellow rgb.

hello_hello
25th August 2023, 18:48
Well I think I have the formulas worked out now. I assume YUV float is always supposed to be full-scale, because I'm getting values very similar to yours now, with matrix="709".
I added a matrix argument for specifying "601", "709" or "2020". If no matrix is specified it uses the matrix from frame properties if it exists, otherwise the default is "601".

I'll try your method sometime soon anyway, to check I've got the three matrices correct, but I think it's okay. I wish that someone who knows for sure would come along.
Obviously the matrix argument only applies to YUV.

<link removed>

"None" in the subtitle was the check for a matrix in frame properties I forgot to remove.

Color = colors.RGBColor(clip, color='yellow', matrix='601')

https://imgur.com/muQJham.png

https://imgur.com/URRaBtO.png

Color = colors.RGBColor(clip, color='yellow', matrix='709')

https://imgur.com/12zuMj0.png

https://imgur.com/J9SWNH0.png

_Al_
25th August 2023, 22:34
Those values do not look correct. Values like 0.99 float looks wrong (too close to 1) but more visibly -0.49 (no blue in yellow, should be U=-0.5?), similar like would be in red, 255,0,0 there would be positive 0.5 for V (full red). And matrix should not matter because color is not there (blue in yellow) or full amount (red in red only), would that be correct?

For real video footage, not our color case, for float limited range, all illegal Y values should be above 1.0. Previewer should show those values.
But for our task, getting color, it should never be above 1.0, no illegal colors, because we start with full rgb 255 value, it cannot be illegal from that point., unless using faulty equations.

I checked that colors.py. You you are not using values from a zimg conversion but values are calculated using equations, :-)

but how about ye different matrixes alone? Not mentioning color ranges, etc. You just need more and more equations. Simply using vapoursynth conversions and then pulling out pixel values seams like way to go. Someone already worked his a** off :-) , to get proper conversions for all scenarios, why not to use it.
I might post that function again considering it all, range , primaries, transfer....

hello_hello
26th August 2023, 13:03
I checked that colors.py. You you are not using values from a zimg conversion but values are calculated using equations, :-)

Yeah, I wanted to see if I could get the equations correct, once I start doing it that way. I'll use your method to get the values from a zimg conversion too and compare the results.

I think I read that according to the specs the results are supposed to be rounded for integer (I didn't bother as Vapoursynth accepts float) and the float result could simply be due to floating point math inaccuracies. Your luma value for yellow was 0.9277999997138977 rather than 1.0 too, but that's probably correct because I assume adding blue must increase the luma so only pure white would have a value of 1.0. If I use a color picker on those images though, I get (255, 255, 7) for the 8 bit yellow image and (255, 254, 6) for the 32 bit one, so maybe they're not 100% correct. Then again, they were converted back to 8 bit RGB for display by VS edit and I've no idea what it's doing.

I might start another thread, or ask over at video help, to see if I can find someone who knows what the equations should be, given I've come this far.

Thinking about it, I'm not sure any difference in primaries would be a factor, given the hex values used are for specifying RGB primaries, so RGB's green would be the same green for each colorspace (well maybe a tad off for SD). If 2020's green was specified instead, it'd have to be converted to RGB's green for rec.709 as the RGB color space doesn't include rec.2020's green, but the rec.2020 color space includes RGB green. Does that make sense or is it faulty logic?
https://en.wikipedia.org/wiki/File:CIExy1931_Rec_601.svg
https://en.wikipedia.org/wiki/File:CIExy1931_Rec_709.svg
https://en.wikipedia.org/wiki/File:CIExy1931_Rec_2020.svg

hello_hello
26th August 2023, 18:29
Well it looks like you talked me into using your method.
If a matrix is specified it's used (YUV only).
If it's not specified any matrix in frame properties is used.
If there's no frame property it's based on resolution, similar to your example.
(Edit: Re-uploaded to fix an error with choosing the matrix).
<link removed>

The new script works the same way as the original one.

import vapoursynth as vs
vc = vs.core
import colors

clip = <SomeVideo>
BorderColor = colors.RGBColor(clip, color='aqua', matrix='709') # or (color='00FFFF', matrix=1)
clip = clip.std.AddBorders(32,32,32,32, color=BorderColor)

To specify the color of a new BlankClip.

import vapoursynth as vs
vc = vs.core
import colors

clip = vc.std.BlankClip(format=vs.RGBS) # or format=vs.GRAY8 or format=vs.YUV420P10 etc.
ClipColor = colors.RGBColor(clip, color='aqua')
clip = vc.std.BlankClip(clip, color=ClipColor)

_Al_
27th August 2023, 07:02
ad colors.py,
I would not pass matrix, because matrix could be different than clip's matrix, and all weirdness breaks. But if you prefer to pass matrix, like you did, and to have two types for one argument, then you could deal with kwargs instead of values (because key argument changes depending on type value). Also vapoursynth would just raise error if matrix would be wrong. Exactly the same what you do, so not trying to troubleshoot matrix validity. I included range check as well. It would be like this:

import vapoursynth as vs
from vapoursynth import core
from typing import Union

colors = {'aliceblue' : 'F0F8FF',
'antiquewhite' : 'FAEBD7',
#etc. dictionary is not complete}

def input_rgb24_color(color:str):
''' returns rgb color as a tuple'''
if color in colors.keys():
color = colors[color]
#color is hex string only now
try:
rgb_color = tuple(int(color[i:i+2], 16) for i in (0, 2, 4))
core.std.BlankClip(color=rgb_color)
except (ValueError, vs.Error):
print('RGBColor: color must be a proper hex string that represents 8bit rgb color, example: "00008B"')
raise
return rgb_color

def RGBColor(clip, color:str, matrix:Union[int,str]=None):
if not isinstance(clip, vs.VideoNode):
raise ValueError('RGBColor: clip must be a video')

if not isinstance(color, str):
raise ValueError('RGBColor: color must be a string, for example "darkblue" or "00008B"')
else:
rgb_color = input_rgb24_color(color)

if clip.format.color_family==vs.RGB:
matrix_kwarg_out = {}

else:
#yuv, gray
if matrix is None: MatrixPassed_kwarg = {}
elif isinstance(matrix, str): MatrixPassed_kwarg = {'matrix_s':matrix}
elif isinstance(matrix, int): MatrixPassed_kwarg = {'matrix':matrix}
else:
raise ValueError('RGBColor: matrix must be an integer or string')

MatrixProp = clip.get_frame(0).props.get("_Matrix", None)
MatrixProp_kwarg = {} if MatrixProp in [None,0,2,3] else {'matrix':MatrixProp}

if clip.width <= 1024 and clip.height <= 576: MatrixSize_kwarg = {'matrix':5}
elif clip.width < 3840 and clip.height < 2160: MatrixSize_kwarg = {'matrix':1}
else: MatrixSize_kwarg = {'matrix':9}

matrix_kwarg_out = MatrixPassed_kwarg or MatrixProp_kwarg or MatrixSize_kwarg

RangeProp = clip.get_frame(0).props.get('_ColorRange', None)
range = None if RangeProp is None else {0:1,1:0}[RangeProp] #values needs to be swapped for zimg

BlankRGBClip = core.std.BlankClip(color=rgb_color)
ColorClip = BlankRGBClip.resize.Point(format=clip.format.id, **matrix_kwarg_out, range=range)
f = ColorClip.get_frame(0)
p0 = f[0][0,0]
if clip.format.color_family==vs.GRAY:
return p0
return p0, f[1][0,0], f[2][0,0]
all colors are ok in previewer as well, for yellow (255,255,0) , or one of those values could +-1 with rounding error (like for some 8bit integer source) , more than +-1 would be too much

hello_hello
27th August 2023, 20:02
I was experiencing this problem yesterday and in the end I put it down to user error when it went away, but as it was happening today I managed to track down the cause.... eventually.

clip = YUV420_Video # here clip.get_frame(0).props.get("_Matrix", None) returns 1
clip = vc.std.BlankClip(clip) # here it returns None
clip = clip.resize.Spline36(1280,720) # here it returns 2
RGBClip = vc.std.BlankClip(color=(255, 255, 0))
MatrixProp = clip.get_frame(0).props.get("_Matrix", None)
clip = RGBClip.resize.Point(format=clip.format.id, matrix=MatrixProp) # here the error is "no path between colorspaces"

clip = YUV420_Video # here clip.get_frame(0).props.get("_Matrix", None) returns 1
clip = vc.std.BlankClip(clip) # here it returns None
RGBClip = vc.std.BlankClip(color=(255, 255, 0))
MatrixProp = clip.get_frame(0).props.get("_Matrix", None)
clip = RGBClip.resize.Point(format=clip.format.id, matrix=MatrixProp) # here the error is "a matrix must be specified"

Two different error message depending on whether the _Matrix property doesn't exist, or exists but has an "unspecified" value.
Why would the Vapoursynth resizers add the frame property and give it an unspecified value instead of just not adding it?

My bad, I guess, for not considering the possibility that _Matrix might have a value of 2, but because a value of 2 and no value at all produce different error messages, when they're effectively the same thing, I banged my head on the desk way too many times before I tracked that one down.

_Al_
27th August 2023, 21:06
yes, "no path between colorspaces" thats what needs a treatment as well, have a look at my first post, you might miss it :-)
after pulling a prop, like _Matrix, you have to check if it is usable, values: 2,3 are not usable (or zero, but that is rgb value) , and you still need to come up with out value if resizing rgb -> YUV

unspecified value is a value that is in specs, so vapoursynth goes with it, and we have to make sure to treat it as such, as if it is None

also be careful, not in our case (we convert rgb full to anything only) but if you convert something that have primaries or transfer also and you not specify outgoing values, you get that error "no path between colorspaces" as well

to make it more clear and readable how to treat different props, better to smash the code into a little pieces so everything is more readable and can be treated as sort of utility if some other function need to pull props in other modules:

import vapoursynth as vs
from vapoursynth import core
from typing import Union

colors = {'aliceblue' : 'F0F8FF',
'antiquewhite' : 'FAEBD7',
#etc. dictionary is not complete}

def clip_pixel_values(f:vs.VideoFrame, coordinates:Tuple[int,int]):
x, y = coordinates
p0 = f[0][y,x]
if f.format.name.startswith('Gray'):
return p0
ys = y >> f.format.subsampling_h
xs = x >> f.format.subsampling_w
return p0, f[1][ys,xs], f[2][ys,xs]

def get_rgb24_color(color:str):
'''rgb24 tuple from hex string or color word string'''
if color is None:
return 0,0,0
if color in colors.keys():
color = colors[color]
#color is hex string only now
try:
rgb_color = tuple(int(color[i:i+2], 16) for i in (0, 2, 4))
core.std.BlankClip(color=rgb_color)
except (ValueError, vs.Error):
print('get_rgb24_color: color must be a proper hex string that represents 8bit rgb color, example: "00008B"')
raise
return rgb_color

def matrixPassed_kwarg(matrix):
if matrix is None: return {}
elif isinstance(matrix, str): return {'matrix_s':matrix}
elif isinstance(matrix, int): return {'matrix':matrix}
else:
raise ValueError('matrix must be an integer or string')

def matrixProp_kwarg(f:vs.VideoFrame):
prop = f.props.get("_Matrix", None)
return {} if prop in [None,0,2,3] else {'matrix':prop}

def matrixSize_kwarg(clip:vs.VideoNode):
if clip.width <= 1024 and clip.height <= 576: return {'matrix_s':'470bg'}
elif clip.width < 3840 and clip.height < 2160: return {'matrix_s':'709'}
else: return {'matrix_s':'2020ncl'}

def rangeProp_kwarg(f:vs.VideoFrame):
prop = f.props.get('_ColorRange', None)
return {} if prop is None else dict(range={0:1,1:0}[prop]) #values need to be swapped for zimg

def RGBColor(clip, color:str, matrix:Union[int,str]=None):
if not isinstance(clip, vs.VideoNode):
raise ValueError('RGBColor: clip must be a video')

if not isinstance(color, str):
raise ValueError('RGBColor: color must be a string, for example "darkblue" or "00008B"')
rgb24_color = get_rgb24_color(color)

if clip.format.color_family==vs.RGB:
matrix_kwarg_out = {}
else:
#yuv, gray
matrix_kwarg_out = matrixPassed_kwarg(matrix) or matrixProp_kwarg(clip.get_frame(0)) or matrixSize_kwarg(clip)

BlankRGBClip = core.std.BlankClip(color=rgb24_color)
ColorClip = BlankRGBClip.resize.Point(format=clip.format.id, **matrix_kwarg_out, **rangeProp_kwarg(clip.get_frame(0)))
return clip_pixel_values(ColorClip.get_frame(0), (0,0))

hello_hello
27th August 2023, 23:34
I think it's done now (hopefully).
<link removed>

The matrix argument now produces an error message if there's a _Matrix value in frame properties and the two don't match (unless _Matrix is 2).
There's also a range argument which produces an error message if there's a _ColorRange value in frame properties and the two don't match.

hello_hello
27th August 2023, 23:39
also be careful, not in our case (we convert rgb full to anything only) but if you convert something that have primaries or transfer also and you not specify outgoing values, you get that error "no path between colorspaces" as well

It never ends. :)
Just having the correct formulas for converting RGB colors to YUV would avoid that type of problem, as it'd never be necessary to convert a clip as such. I might look at going back to doing it that way, but not right now.... the real world keeps annoying me.

_Al_
28th August 2023, 00:03
If you use equations, there will be more problems. There is perhaps only a couple of people that can handle all of this -> anything in to anything out conversion, it is not you or me. You can check out this on github and check the code (https://github.com/sekrit-twc/zimg/tree/master/src/zimg), how can you ever catch up with it? :-) Use my examples. Primaries and Transfer could be skipped, because it is about rgb full 8bit to any format conversion, only matrix needs to be introduced or color range(to be more accurate if clip's range is weird, like limited rgb or full yuv).

In your functions you do lots of if else, where instead you can use simple logical or to come up with a value.
matrix1 = None #passed matrix
matrix2 = 5 #gotten from prop
matrix3 = 1 #selected by size
matrix = matrix1 or matrix2 or matrix3
print(matrix)
>>> 5
Will select first available value, which is 5, so you can set priorities like that. If any value is None, 0, [], {}, False, it looks for following value etc., so it gets first available logical True value. So basically if values are difficult to get, it does not even calculate them if a value was already selected. Logical OR works like that, as soon as it finds a logical True, it ignores the rest. In python it selects actual value, not 1 or True. This can clean code tremendously with quick priority change.

Anyway passing matrix and now even range does not make a sense, if needing those prop values, you check them within RGBcolor(), you might call helper function, like example above. Just getting a color means to check clip props not imported props.

hello_hello
28th August 2023, 13:56
Thanks for the info.

I'm pretty new to Python so I'm mostly doing stuff as I'd do it using Avisynth, but with Python's syntax, annoying case sensitivity and infuriating compulsory indentation. :)

I wasn't even aware you could use logic like that. There's a lot to learn.

_Al_
29th August 2023, 02:32
The way it is, if you are lost in indentation, it means the code is wrong. Usually like having tons of if , else, line across multiple lines, no functions, just main line.

Example clearing things up using dictionary:
#checking matrix value
MATRIX = {
0: None,
1:'709',
2: None,
3: None,
4:'fcc',
5:'470bg',
6:'170m',
7:'240m',
8:'ycgco',
9:'2020ncl',
10:'2020cl' ,
12:'chromancl',
13:'chromacl',
14:'ictcp'
}
if matrix in MATRIX.keys() and not MATRIX[matrix] is None:
print("valid integer matrix")
elif matrix in MATRIX.values() and matrix is not None:
print("valid string matrix")
else:
print("matrix not valid or not useful")
But as I mentioned, validate matrix might not be necessary because that is what vapoursynth does, you might just have try, except vs.Error block around that resize function, print explanations if your error occurs and just raise, that would print vapoursynth error as well.

hello_hello
27th March 2025, 13:39
There's a link for a new version dated 2025-03-25 in the opening post.
The functionality hasn't changed at all but I think I changed the syntax a little at some stage, however it was a while ago and I actually can't remember. :)