Secure your code as it's written. Use Snyk Code to scan source code in minutes - no build needed - and fix issues immediately.
It's kinda experimental, but I wanted to try something like this.
It works by finding the edge of the subtitle (where the black border and the white fill color touch),
and it grows these areas into a regular brightness + difference mask via hysteresis.
This should (in theory) reliably find all hardsubs in the image with barely any false positives (or none at all).
Output depth and processing precision are the same as the input
It is not necessary for 'clip' and 'ref' to have the same bit depth, as 'ref' will be dithered to match 'clip'
Most of this code was written by Zastin (https://github.com/Z4ST1N)
Clean code soon(tm)
"""
clp_f = clip.format
bits = clp_f.bits_per_sample
stype = clp_f.sample_type
expand_n = fallback(expand_n, clip.width // 200)
yuv_fmt = core.register_format(clp_f.color_family, vs.INTEGER, 8, clp_f.subsampling_w, clp_f.subsampling_h)
y_range = 219 << (bits - 8) if stype == vs.INTEGER else 1
uv_range = 224 << (bits - 8) if stype == vs.INTEGER else 1
offset = 16 << (bits - 8) if stype == vs.INTEGER else 0
uv_abs = ' abs ' if stype == vs.FLOAT else ' {} - abs '.format((1 << bits) // 2)
yexpr = 'x y - abs {thr} > 255 0 ?'.format(thr=y_range * 0.7)
uvexpr = 'x {uv_abs} {thr} < y {uv_abs} {thr} < and 255 0 ?'.format(uv_abs=uv_abs, thr=uv_range * 0.1)
difexpr = 'x {upper} > x {lower} < or x y - abs {mindiff} > and 255 0 ?'.format(upper=y_range * 0.8 + offset,
lower=y_range * 0.2 + offset,
mindiff=y_range * 0.1)
# right shift by 4 pixels.
# fmtc uses at least 16 bit internally, so it's slower for 8 bit,
# but its behaviour when shifting/replicating edge pixels makes it faster otherwise
def test_should_dither(self):
# --- True ---
# Range conversion
self.assertTrue(vsutil.clips._should_dither(1, 1, in_range=vsutil.Range.LIMITED, out_range=vsutil.Range.FULL))
# Float to int
self.assertTrue(vsutil.clips._should_dither(1, 1, in_sample_type=vs.FLOAT))
# Upsampling full range 10 -> 12
self.assertTrue(vsutil.clips._should_dither(10, 12, in_range=vsutil.Range.FULL, out_range=vsutil.Range.FULL))
# Downsampling
self.assertTrue(vsutil.clips._should_dither(10, 8, in_sample_type=vs.INTEGER))
self.assertTrue(vsutil.clips._should_dither(10, 8, in_sample_type=vs.INTEGER, in_range=vsutil.Range.FULL, out_range=vsutil.Range.FULL))
self.assertTrue(vsutil.clips._should_dither(10, 8, in_sample_type=vs.INTEGER, in_range=vsutil.Range.LIMITED, out_range=vsutil.Range.LIMITED))
# --- False ---
# Int to int
self.assertFalse(vsutil.clips._should_dither(8, 8, in_sample_type=vs.INTEGER))
# Upsampling full range 8 -> 16
self.assertFalse(vsutil.clips._should_dither(8, 16, in_range=vsutil.Range.FULL, out_range=vsutil.Range.FULL))
# Upsampling
self.assertFalse(vsutil.clips._should_dither(8, 16, in_sample_type=vs.INTEGER))
self.assertFalse(vsutil.clips._should_dither(8, 16, in_sample_type=vs.INTEGER, in_range=vsutil.Range.LIMITED, out_range=vsutil.Range.LIMITED))
# Float output
self.assertFalse(vsutil.clips._should_dither(32, 32, in_sample_type=vs.INTEGER))
self.assertFalse(vsutil.clips._should_dither(32, 16, in_sample_type=vs.INTEGER, out_sample_type=vs.FLOAT))
def Depth(src, bits, dither_type='error_diffusion', range_v=None, range_in=None):
src_f = src.format
src_cf = src_f.color_family
src_bits = src_f.bits_per_sample
src_sw = src_f.subsampling_w
src_sh = src_f.subsampling_h
dst_st = vs.INTEGER if bits < 32 else vs.FLOAT
if isinstance(range_v, str):
range_v = RANGEDICT[range_v]
if isinstance(range_in, str):
range_in = RANGEDICT[range_in]
if (src_bits, range_in) == (bits, range_v):
return src
out_f = core.register_format(src_cf, dst_st, bits, src_sw, src_sh)
return core.resize.Point(src, format=out_f.id, dither_type=dither_type, range=range_v, range_in=range_in)
def __init__(self, clip, strength=0.0, down8=False):
super(AAParent, self).__init__(clip)
self.dfactor = 1 - min(strength, 0.5)
self.dw = round(self.clip_width * self.dfactor / 4) * 4
self.dh = round(self.clip_height * self.dfactor / 4) * 4
self.upw4 = round(self.dw * 0.375) * 4
self.uph4 = round(self.dh * 0.375) * 4
self.down8 = down8
self.process_depth = self.clip_bits
if down8 is True:
self.down_8()
if self.dfactor != 1:
self.clip = self.resize(self.clip, self.dw, self.dh, shift=0)
if self.clip_color_family is vs.GRAY:
if self.clip_sample_type is not vs.INTEGER:
raise TypeError(MODULE_NAME + ': clip must be integer format.')
else:
raise TypeError(MODULE_NAME + ': clip must be GRAY family.')
def __init__(self, clip, strength=0.0, down8=False):
super(AAParent, self).__init__(clip)
self.aa_clip = self.clip
self.dfactor = 1 - max(min(strength, 0.5), 0)
self.dw = round(self.clip_width * self.dfactor / 4) * 4
self.dh = round(self.clip_height * self.dfactor / 4) * 4
self.upw4 = round(self.dw * 0.375) * 4
self.uph4 = round(self.dh * 0.375) * 4
self.down8 = down8
self.process_depth = self.clip_bits
if down8 is True:
self.down_8()
if self.dfactor != 1:
self.aa_clip = self.resize(self.aa_clip, self.dw, self.dh, shift=0)
if self.clip_color_family is vs.GRAY:
if self.clip_sample_type is not vs.INTEGER:
raise TypeError(MODULE_NAME + ': clip must be integer format.')
else:
raise TypeError(MODULE_NAME + ': clip must be GRAY family.')
if output == 0:
dbitPS = sbitPS
else:
dbitPS = pbitPS
elif not isinstance(depth, int):
raise TypeError(funcName + ': \"depth\" must be an int!')
else:
dbitPS = depth
if sample is None:
if depth is None:
if output == 0:
dSType = sSType
else:
dSType = pSType
else:
dSType = vs.FLOAT if dbitPS >= 32 else vs.INTEGER
elif not isinstance(sample, int):
raise TypeError(funcName + ': \"sample\" must be an int!')
elif sample != vs.INTEGER and sample != vs.FLOAT:
raise ValueError(funcName + ': \"sample\" must be either 0(vs.INTEGER) or 1(vs.FLOAT)!')
else:
dSType = sample
if dSType == vs.INTEGER and (dbitPS < 8 or dbitPS > 16):
raise ValueError(funcName + ': {0}-bit integer output is not supported!'.format(dbitPS))
if dSType == vs.FLOAT and (dbitPS != 16 and dbitPS != 32):
raise ValueError(funcName + ': {0}-bit float output is not supported!'.format(dbitPS))
if output == 0:
fulld = fulls
else:
# Always full range output when output=1|output=2 (full range RGB or full range OPP)
fulld = True
def Depth(src, bits, dither_type='error_diffusion', range=None, range_in=None):
src_f = src.format
src_cf = src_f.color_family
src_st = src_f.sample_type
src_bits = src_f.bits_per_sample
src_sw = src_f.subsampling_w
src_sh = src_f.subsampling_h
dst_st = vs.INTEGER if bits < 32 else vs.FLOAT
if isinstance(range, str):
range = RANGEDICT[range]
if isinstance(range_in, str):
range_in = RANGEDICT[range_in]
if (src_bits, range_in) == (bits, range):
return src
out_f = core.register_format(src_cf, dst_st, bits, src_sw, src_sh)
return core.resize.Point(src, format=out_f.id, dither_type=dither_type, range=range, range_in=range_in)