Skip to content

Commit

Permalink
refactor filters and encoders
Browse files Browse the repository at this point in the history
  • Loading branch information
porzione committed Jul 23, 2024
1 parent e04d685 commit ba70c23
Show file tree
Hide file tree
Showing 10 changed files with 127 additions and 64 deletions.
30 changes: 12 additions & 18 deletions cpmyvideos.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,6 @@
RESOLUTIONS = (1080, 1440, 2160)

FORMATS = ('dnxhr', 'hevc')
FORMAT_EXTENSIONS = {
'hevc': 'MOV',
'dnxhr': 'MOV',
}
# downscale: Lanczos/Spline, upscale: Bicubic/Lanczos
# error diffusion dithering to minimize banding +dither=error_diffusion
DSCALE_FLAGS = 'flags=lanczos+accurate_rnd+full_chroma_int'
Expand Down Expand Up @@ -95,14 +91,11 @@ def transcode(src, dst, info, enc_mod):
else:
cmd = ['ffmpeg', '-hide_banner', '-nostdin', '-ignore_editlist', '1']

filter_v = []
if hasattr(encoder, 'get_filter'):
filter_v.append(encoder.get_filter())
if args.res and args.res < info.height:
if hasattr(encoder, 'scale'):
filter_v.append(encoder.scale())
else:
filter_v.append(f'scale=w=-1:h={args.res}:{DSCALE_FLAGS}')
need_scale = args.res and args.res < info.height
filter_v = encoder.get_filter(scale=need_scale)
if need_scale and not encoder.can_scale:
filter_v.append({'scale': f'w=-1:h={args.res}:{DSCALE_FLAGS}'})
#print(f'filter_v: {filter_v}')

params = encoder.get_params()

Expand Down Expand Up @@ -147,9 +140,12 @@ def transcode(src, dst, info, enc_mod):
# filters
if filter_v:
if hasattr(encoder, 'CMD'):
cmd.extend(*filter_v)
cmd.extend(filter_v)
else:
cmd.extend(['-filter:v', ','.join(filter_v)])
items = lib.join_filters(filter_v)
#print(f'items: {items} from filter_v: {filter_v}')
cmd.extend(['-filter:v', items])
#print(cmd) ; return 0

# output
if args.duration:
Expand All @@ -176,7 +172,6 @@ def copy(src, dst):
ENC_MOD = enc_dnxhr
elif args.fmt == 'hevc':
ENC_MOD = importlib.import_module(f'enc_{args.fmt}_{args.enc}')

else:
raise ValueError(f"Unsupported encoder format/type: {args.fmt}{args.enc}")

Expand All @@ -197,7 +192,6 @@ def copy(src, dst):
if args.res:
debug.append(f'res: {args.res}')

ext = FORMAT_EXTENSIONS.get(args.fmt)
base_name = os.path.splitext(filename)[0]
if args.fnparams:
if args.fmt == 'dnxhr':
Expand All @@ -223,7 +217,7 @@ def copy(src, dst):
base_name += f'_tun={args.tune}'
if args.gop:
base_name += f'_gop{lib.gop(mi.frame_rate, args.gop)}'
dst_file = os.path.join(args.dstdir, f'{base_name}.{ext}')
dst_file = os.path.join(args.dstdir, f'{base_name}.MOV')
if os.path.exists(dst_file):
print(f'EXISTS {dst_file}')
if not args.dry:
Expand All @@ -233,7 +227,7 @@ def copy(src, dst):
debug.append(f'crf: {crf}')
if args.preset:
debug.append(f'preset: {args.preset}')
if args.fmt == 'dnxhr' and args.dnx:
if args.fmt == 'dnxhr' and dnxp:
debug.append(f'DNxHR profile: {dnxp}')
print('\n'.join(map(lambda s: f'> {s}', debug)))
mi.print()
Expand Down
10 changes: 7 additions & 3 deletions enc_dnxhr.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
Supported pixel formats: yuv422p yuv422p10le yuv444p10le gbrp10le
"""
from lib import BaseEncoder

PROFILES = {
'lb': 'yuv422p', # Offline Quality. 22:1
'sq': 'yuv422p', # Suitable for delivery. 7:1
Expand All @@ -21,7 +23,9 @@
'yuv422:10': 'hqx',
}

class Encoder:
class Encoder(BaseEncoder):

can_scale = False

def __init__(self, vid):
self.profile = vid.dnx if vid.dnx else PROFILES_AUTO[vid.idx()]
Expand All @@ -36,5 +40,5 @@ def get_params(self):
def get_profile(self):
return self.profile

def get_filter(self):
return f'format={PROFILES[self.profile]}'
def get_filter(self, *args, scale=None, **kwargs):
return [{'format': PROFILES[self.profile]}]
11 changes: 7 additions & 4 deletions enc_hevc_amf.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@
Supported pixel formats: nv12 yuv420p
"""
from lib import BaseEncoder

class Encoder:
class Encoder(BaseEncoder):

can_scale = False

def __init__(self, vid):
self.params = {
Expand All @@ -18,7 +21,7 @@ def __init__(self, vid):
'qp_p': vid.crf,
'qp_i': vid.crf,
'profile_tier': 'high',
'level': '5.2',
'level': '5.1',
}
self.bits = vid.bits
self.fmt = f'{vid.color_format}p'
Expand All @@ -28,5 +31,5 @@ def __init__(self, vid):
def get_params(self):
return self.params

def get_filter(self):
return f'format={self.fmt}'
def get_filter(self, *args, scale=None, **kwargs):
return [{'format': self.fmt}]
29 changes: 19 additions & 10 deletions enc_hevc_nv.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@
https://developer.nvidia.com/blog/calculating-video-quality-using-nvidia-gpus-and-vmaf-cuda/
https://developer.nvidia.com/blog/nvidia-ffmpeg-transcoding-guide/
hwupload_cuda filter: uploading the data from system to GPU memory using
Supported pixel formats: yuv420p nv12 p010le yuv444p p016le yuv444p16le
bgr0 bgra rgb0 rgba x2rgb10le x2bgr10le gbrp gbrp16le cuda
"""
from lib import BaseEncoder

PRESETS = {
1080: 'p6',
Expand All @@ -19,7 +22,7 @@
DEFAULT_TUNE='hq'
PARAMS_IN = {
# vdpau cuda vaapi qsv drm opencl vulkan
'hwaccel': 'nvdec',
'hwaccel': 'cuda',
# keeps the decoded frames in GPU memory
'hwaccel_output_format': 'cuda'
}
Expand All @@ -30,10 +33,11 @@
'yuv422:10': 'yuv444p16le',
}

class Encoder:
class Encoder(BaseEncoder):
can_scale = True

def __init__(self, vid):
#print(f'hevc_nvenc idx:{vid.idx()}')
print(f'hevc_nvenc idx:{vid.idx()}')

self.params = {
'c:v': 'hevc_nvenc',
Expand All @@ -47,7 +51,7 @@ def __init__(self, vid):
'tier': 'high',
'profile:v': 'main10' if vid.bits == 10 else 'main',
}
self.bits = vid.bits
self.bits_in = vid.bits_in
self.res = vid.res
self.idx = vid.idx()

Expand All @@ -57,9 +61,14 @@ def get_params_in(self):
def get_params(self):
return self.params

def scale(self):
flt = ['hwupload_cuda']
scale = f'scale_cuda=w=-1:h={self.res}:interp_algo=lanczos'
scale += f':format={FORMATS[self.idx]}'
flt.append(scale)
return ','.join(flt)
def get_filter(self, *args, scale=None, **kwargs):
flt = []
# NVDec H.264/AVC: nv12, yv12 ONLY
if self.bits_in == 10:
flt.append('hwupload_cuda')
sparams = []
if scale:
sparams.append(f'w=-1:h={self.res}:interp_algo=lanczos')
sparams.append(f'format={FORMATS[self.idx]}')
flt.append({'scale_cuda': ':'.join(sparams)})
return flt
12 changes: 10 additions & 2 deletions enc_hevc_nvenc.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@
Max Level 186 (6.2)
4:4:4 yes
10bit depth yes
NVDec features
H.264/AVC: nv12, yv12
H.265/HEVC: nv12, yv12, yv12(10bit), yv12(12bit), yuv444, yuv444(10bit), yuv444(12bit)
"""
from lib import BaseEncoder

OUTPUT_BUFFER = 64

Expand All @@ -32,9 +37,10 @@
# smpte2084 bt2020nc bt2020c
}

class Encoder:
class Encoder(BaseEncoder):

CMD = ['nvencc']
can_scale = True

def __init__(self, vid):
#print(f'nvenc idx: {vid.idx()}')
Expand Down Expand Up @@ -64,5 +70,7 @@ def __init__(self, vid):
def get_params(self):
return self.params

def scale(self):
def get_filter(self, *args, scale=None, **kwargs):
if not scale:
return {}
return ['--output-res', f'-2x{self.res}', '--vpp-resize', 'lanczos3']
31 changes: 19 additions & 12 deletions enc_hevc_vaapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
Supported pixel formats: vaapi, scale_vaapi format
nv12 yuv420p p010(420/10) yuy2(422/8)
"""
from lib import BaseEncoder

COMPLVL = 29 # 1 AMD
# VBAQ=16 (not with CQP), pre-encode=8, quality=4, preset=2, speed=0
Expand All @@ -20,7 +21,8 @@
#'vaapi_device': '/dev/dri/renderD128',
}

class Encoder:
class Encoder(BaseEncoder):
can_scale = True

def __init__(self, vid):
self.params = {
Expand All @@ -40,15 +42,20 @@ def get_params_in(self):
def get_params(self):
return self.params

def get_filter(self):
flt = []
def get_filter(self, *args, scale=None, **kwargs):
flt = ['hwupload']
scale_vaapi = {}

if scale:
scale_vaapi = {
'w': -1,
'h': self.res,
'mode': 'hq',
'force_original_aspect_ratio': 1
}

if self.bits == 10:
flt.append('format=p010')
flt.append('hwupload')
return ','.join(flt)

def scale(self):
flt = []
if self.res:
flt.append(f'scale_vaapi=w=-1:h={self.res}:mode=hq:force_original_aspect_ratio=1')
return ','.join(flt)
scale_vaapi['format'] = 'p010'

flt.append({'scale_vaapi': scale_vaapi})
return flt
8 changes: 6 additions & 2 deletions enc_hevc_vceenc.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
timeout support: yes
smart access: no
"""
from lib import BaseEncoder

OUTPUT_BUFFER = 32
PROFILES = {
Expand All @@ -27,9 +28,10 @@
}


class Encoder:
class Encoder(BaseEncoder):

CMD = ['vceencc', '--avsw']
can_scale = True

def __init__(self, vid):
#print(f'hevc_vceenc idx:{vid.idx()}')
Expand All @@ -56,5 +58,7 @@ def __init__(self, vid):
def get_params(self):
return self.params

def scale(self):
def get_filter(self, *args, scale=None, **kwargs):
if not scale:
return {}
return ['--output-res', f'-2x{self.res}', '--vpp-resize', 'lanczos3']
11 changes: 7 additions & 4 deletions enc_hevc_x265.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
gbrp yuv420p10le yuv422p10le yuv444p10le gbrp10le yuv420p12le yuv422p12le yuv444p12le
gbrp12le gray gray10le gray12le
"""
from lib import BaseEncoder

PRESETS = {
1080: 'medium',
Expand All @@ -29,7 +30,9 @@
'yuv422:10': 'main422-10',
}

class Encoder:
class Encoder(BaseEncoder):

can_scale = False

def __init__(self, vid):
print(f'x265 idx: {vid.idx()}')
Expand All @@ -49,10 +52,10 @@ def __init__(self, vid):
x265params.append(f'{vid.params}')
self.params['x265-params'] = ':'.join(x265params)

self.flt = [f'format={FORMATS[vid.idx()]}']
self.idx = vid.idx()

def get_params(self):
return self.params

def get_filter(self):
return ','.join(self.flt)
def get_filter(self, *args, scale=None, **kwargs):
return [{'format': FORMATS[self.idx]}]
24 changes: 24 additions & 0 deletions lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@
}
# FRAME_RATES = (23.98 24 25 29.97 30 50 59.94 60 120 150 180)

class BaseEncoder:

ERROR = "Method should be overridden in subclasses"

def get_filter(self, *args, scale=None, **kwargs):
raise NotImplementedError(self.ERROR)

def get_params(self):
raise NotImplementedError(self.ERROR)

@dataclass
class Video:
# pylint: disable=too-many-instance-attributes
Expand All @@ -42,6 +52,20 @@ def gop(frame_rate, gop_mul):
return int(float(frame_rate) * gop_mul)
return None

def join_filters(filters):
result = []
for filter_item in filters:
if isinstance(filter_item, str):
result.append(filter_item)
elif isinstance(filter_item, dict):
for key, value in filter_item.items():
if isinstance(value, dict):
sub_items = [f"{k}={v}" for k, v in value.items()]
result.append(f"{key}=" + ":".join(sub_items))
else:
result.append(f"{key}={value}")
return ",".join(result)

def format_time(seconds):
if seconds < 60:
return f"{seconds:.3f}s"
Expand Down
Loading

0 comments on commit ba70c23

Please sign in to comment.