#417 Filter-Chains
This commit is contained in:
77
bin/ffx.py
77
bin/ffx.py
@@ -19,7 +19,6 @@ from ffx.show_descriptor import ShowDescriptor
|
|||||||
from ffx.track_type import TrackType
|
from ffx.track_type import TrackType
|
||||||
from ffx.video_encoder import VideoEncoder
|
from ffx.video_encoder import VideoEncoder
|
||||||
from ffx.track_disposition import TrackDisposition
|
from ffx.track_disposition import TrackDisposition
|
||||||
from ffx.nlmeans_controller import NlmeansController
|
|
||||||
|
|
||||||
from ffx.process import executeProcess
|
from ffx.process import executeProcess
|
||||||
from ffx.helper import filterFilename
|
from ffx.helper import filterFilename
|
||||||
@@ -27,6 +26,12 @@ from ffx.helper import filterFilename
|
|||||||
from ffx.constants import DEFAULT_QUALITY, DEFAULT_AV1_PRESET
|
from ffx.constants import DEFAULT_QUALITY, DEFAULT_AV1_PRESET
|
||||||
from ffx.constants import DEFAULT_STEREO_BANDWIDTH, DEFAULT_AC3_BANDWIDTH, DEFAULT_DTS_BANDWIDTH, DEFAULT_7_1_BANDWIDTH
|
from ffx.constants import DEFAULT_STEREO_BANDWIDTH, DEFAULT_AC3_BANDWIDTH, DEFAULT_DTS_BANDWIDTH, DEFAULT_7_1_BANDWIDTH
|
||||||
|
|
||||||
|
from ffx.filter.quality_filter import QualityFilter
|
||||||
|
from ffx.filter.preset_filter import PresetFilter
|
||||||
|
|
||||||
|
from ffx.filter.nlmeans_filter import NlmeansFilter
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
VERSION='0.2.2'
|
VERSION='0.2.2'
|
||||||
|
|
||||||
@@ -277,8 +282,8 @@ def checkUniqueDispositions(context, mediaDescriptor: MediaDescriptor):
|
|||||||
|
|
||||||
@click.option('-v', '--video-encoder', type=str, default=FfxController.DEFAULT_VIDEO_ENCODER, help=f"Target video encoder (vp9 or av1)", show_default=True)
|
@click.option('-v', '--video-encoder', type=str, default=FfxController.DEFAULT_VIDEO_ENCODER, help=f"Target video encoder (vp9 or av1)", show_default=True)
|
||||||
|
|
||||||
@click.option('-q', '--quality', type=str, default=DEFAULT_QUALITY, help=f"Quality settings to be used with VP9 encoder", show_default=True)
|
@click.option('-q', '--quality', type=str, default="", help=f"Quality settings to be used with VP9 encoder")
|
||||||
@click.option('-p', '--preset', type=str, default=DEFAULT_AV1_PRESET, help=f"Quality preset to be used with AV1 encoder", show_default=True)
|
@click.option('-p', '--preset', type=str, default="", help=f"Quality preset to be used with AV1 encoder")
|
||||||
|
|
||||||
@click.option('-a', '--stereo-bitrate', type=int, default=DEFAULT_STEREO_BANDWIDTH, help=f"Bitrate in kbit/s to be used to encode stereo audio streams", show_default=True)
|
@click.option('-a', '--stereo-bitrate', type=int, default=DEFAULT_STEREO_BANDWIDTH, help=f"Bitrate in kbit/s to be used to encode stereo audio streams", show_default=True)
|
||||||
@click.option('--ac3', type=int, default=DEFAULT_AC3_BANDWIDTH, help=f"Bitrate in kbit/s to be used to encode 5.1 audio streams", show_default=True)
|
@click.option('--ac3', type=int, default=DEFAULT_AC3_BANDWIDTH, help=f"Bitrate in kbit/s to be used to encode 5.1 audio streams", show_default=True)
|
||||||
@@ -409,14 +414,6 @@ def convert(ctx,
|
|||||||
context['resource_limits']['cpu_percent'] = cpu
|
context['resource_limits']['cpu_percent'] = cpu
|
||||||
|
|
||||||
|
|
||||||
context['denoiser'] = NlmeansController(parameters = denoise,
|
|
||||||
strength = denoise_strength,
|
|
||||||
patchSize = denoise_patch_size,
|
|
||||||
chromaPatchSize = denoise_chroma_patch_size,
|
|
||||||
researchWindow = denoise_research_window,
|
|
||||||
chromaResearchWindow = denoise_chroma_research_window,
|
|
||||||
useHardware = denoise_use_hw)
|
|
||||||
|
|
||||||
context['import_subtitles'] = (subtitle_directory and subtitle_prefix)
|
context['import_subtitles'] = (subtitle_directory and subtitle_prefix)
|
||||||
if context['import_subtitles']:
|
if context['import_subtitles']:
|
||||||
context['subtitle_directory'] = subtitle_directory
|
context['subtitle_directory'] = subtitle_directory
|
||||||
@@ -494,9 +491,13 @@ def convert(ctx,
|
|||||||
|
|
||||||
qualityTokens = quality.split(',')
|
qualityTokens = quality.split(',')
|
||||||
q_list = [q for q in qualityTokens if q.isnumeric()]
|
q_list = [q for q in qualityTokens if q.isnumeric()]
|
||||||
|
|
||||||
ctx.obj['logger'].debug(f"Qualities: {q_list}")
|
ctx.obj['logger'].debug(f"Qualities: {q_list}")
|
||||||
|
|
||||||
|
presetTokens = preset.split(',')
|
||||||
|
p_list = [p for p in presetTokens if p.isnumeric()]
|
||||||
|
ctx.obj['logger'].debug(f"Presets: {p_list}")
|
||||||
|
|
||||||
|
|
||||||
context['bitrates'] = {}
|
context['bitrates'] = {}
|
||||||
context['bitrates']['stereo'] = str(stereo_bitrate) if str(stereo_bitrate).endswith('k') else f"{stereo_bitrate}k"
|
context['bitrates']['stereo'] = str(stereo_bitrate) if str(stereo_bitrate).endswith('k') else f"{stereo_bitrate}k"
|
||||||
context['bitrates']['ac3'] = str(ac3) if str(ac3).endswith('k') else f"{ac3}k"
|
context['bitrates']['ac3'] = str(ac3) if str(ac3).endswith('k') else f"{ac3}k"
|
||||||
@@ -519,8 +520,29 @@ def convert(ctx,
|
|||||||
|
|
||||||
tc = TmdbController() if context['use_tmdb'] else None
|
tc = TmdbController() if context['use_tmdb'] else None
|
||||||
|
|
||||||
|
qualityKwargs = {QualityFilter.QUALITY_KEY: quality}
|
||||||
|
qf = QualityFilter(**qualityKwargs)
|
||||||
|
|
||||||
ctx.obj['logger'].info(f"\nRunning {len(existingSourcePaths) * len(q_list)} jobs")
|
presetKwargs = {PresetFilter.PRESET_KEY: preset}
|
||||||
|
PresetFilter(**presetKwargs)
|
||||||
|
|
||||||
|
denoiseKwargs = {}
|
||||||
|
if denoise_strength:
|
||||||
|
denoiseKwargs[NlmeansFilter.STRENGTH_KEY] = denoise_strength
|
||||||
|
if denoise_patch_size:
|
||||||
|
denoiseKwargs[NlmeansFilter.PATCH_SIZE_KEY] = denoise_patch_size
|
||||||
|
if denoise_chroma_patch_size:
|
||||||
|
denoiseKwargs[NlmeansFilter.CHROMA_PATCH_SIZE_KEY] = denoise_chroma_patch_size
|
||||||
|
if denoise_research_window:
|
||||||
|
denoiseKwargs[NlmeansFilter.RESEARCH_WINDOW_KEY] = denoise_research_window
|
||||||
|
if denoise_chroma_research_window:
|
||||||
|
denoiseKwargs[NlmeansFilter.CHROMA_RESEARCH_WINDOW_KEY] = denoise_chroma_research_window
|
||||||
|
if denoise != 'none' or denoiseKwargs:
|
||||||
|
NlmeansFilter(**denoiseKwargs)
|
||||||
|
|
||||||
|
chainYield = list(qf.getChainYield())
|
||||||
|
|
||||||
|
ctx.obj['logger'].info(f"\nRunning {len(existingSourcePaths) * len(chainYield)} jobs")
|
||||||
|
|
||||||
jobIndex = 0
|
jobIndex = 0
|
||||||
|
|
||||||
@@ -650,18 +672,24 @@ def convert(ctx,
|
|||||||
if 'se' in targetSuffices.keys():
|
if 'se' in targetSuffices.keys():
|
||||||
del targetSuffices['se']
|
del targetSuffices['se']
|
||||||
|
|
||||||
|
|
||||||
ctx.obj['logger'].debug(f"fileBasename={sourceFileBasename}")
|
ctx.obj['logger'].debug(f"fileBasename={sourceFileBasename}")
|
||||||
|
|
||||||
|
|
||||||
for q in q_list:
|
for chainIteration in chainYield:
|
||||||
|
|
||||||
if len(q_list) > 1:
|
|
||||||
targetSuffices['q'] = f"q{q}"
|
|
||||||
|
|
||||||
ctx.obj['logger'].debug(f"\nRunning job {jobIndex} file={sourcePath} q={q}")
|
ctx.obj['logger'].debug(f"\nchain iteration: {chainIteration}\n")
|
||||||
|
|
||||||
|
# if len(q_list) > 1:
|
||||||
|
# targetSuffices['q'] = f"q{q}"
|
||||||
|
|
||||||
|
chainVariant = '-'.join([fy['variant'] for fy in chainIteration])
|
||||||
|
|
||||||
|
ctx.obj['logger'].debug(f"\nRunning job {jobIndex} file={sourcePath} variant={chainVariant}")
|
||||||
jobIndex += 1
|
jobIndex += 1
|
||||||
|
|
||||||
|
continue
|
||||||
|
|
||||||
ctx.obj['logger'].debug(f"label={label if label else 'Falsy'}")
|
ctx.obj['logger'].debug(f"label={label if label else 'Falsy'}")
|
||||||
ctx.obj['logger'].debug(f"sourceFileBasename={sourceFileBasename}")
|
ctx.obj['logger'].debug(f"sourceFileBasename={sourceFileBasename}")
|
||||||
|
|
||||||
@@ -676,9 +704,15 @@ def convert(ctx,
|
|||||||
if 'se' in targetSuffices.keys():
|
if 'se' in targetSuffices.keys():
|
||||||
targetFilenameTokens += [targetSuffices['se']]
|
targetFilenameTokens += [targetSuffices['se']]
|
||||||
|
|
||||||
if 'q' in targetSuffices.keys():
|
# if 'q' in targetSuffices.keys():
|
||||||
targetFilenameTokens += [targetSuffices['q']]
|
# targetFilenameTokens += [targetSuffices['q']]
|
||||||
|
for filterYield in chainIteration:
|
||||||
|
|
||||||
|
# filterIdentifier = filterYield['identifier']
|
||||||
|
# filterParameters = filterYield['parameters']
|
||||||
|
# filterSuffices = filterYield['suffices']
|
||||||
|
|
||||||
|
targetFilenameTokens += filterYield['suffices']
|
||||||
|
|
||||||
#TODO #387
|
#TODO #387
|
||||||
# targetFilename = ((f"{sourceFileBasename}_q{q}" if len(q_list) > 1 else sourceFileBasename)
|
# targetFilename = ((f"{sourceFileBasename}_q{q}" if len(q_list) > 1 else sourceFileBasename)
|
||||||
@@ -695,8 +729,7 @@ def convert(ctx,
|
|||||||
targetPath,
|
targetPath,
|
||||||
targetFormat,
|
targetFormat,
|
||||||
context['video_encoder'],
|
context['video_encoder'],
|
||||||
q,
|
chainIteration)
|
||||||
preset)
|
|
||||||
|
|
||||||
#TODO: click.confirm('Warning! This file is not compliant to the defined source schema! Do you want to continue?', abort=True)
|
#TODO: click.confirm('Warning! This file is not compliant to the defined source schema! Do you want to continue?', abort=True)
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,10 @@ from ffx.track_disposition import TrackDisposition
|
|||||||
from ffx.constants import DEFAULT_QUALITY, DEFAULT_AV1_PRESET
|
from ffx.constants import DEFAULT_QUALITY, DEFAULT_AV1_PRESET
|
||||||
from ffx.constants import DEFAULT_CROP_START, DEFAULT_CROP_LENGTH
|
from ffx.constants import DEFAULT_CROP_START, DEFAULT_CROP_LENGTH
|
||||||
|
|
||||||
|
from ffx.filter.quality_filter import QualityFilter
|
||||||
|
from ffx.filter.preset_filter import PresetFilter
|
||||||
|
from ffx.filter.nlmeans_filter import NlmeansFilter
|
||||||
|
|
||||||
|
|
||||||
class FfxController():
|
class FfxController():
|
||||||
|
|
||||||
@@ -44,8 +48,8 @@ class FfxController():
|
|||||||
self.__configurationData = self.__context['config'].getData()
|
self.__configurationData = self.__context['config'].getData()
|
||||||
|
|
||||||
# Convenience
|
# Convenience
|
||||||
self.__niceness = self.__context['resource_limits']['niceness'] if 'resource_limits' in self.__context.keys() and 'niceness' in self.__context['resource_limits'].keys() else 99
|
# self.__niceness = self.__context['resource_limits']['niceness'] if 'resource_limits' in self.__context.keys() and 'niceness' in self.__context['resource_limits'].keys() else 99
|
||||||
self.__cpuPercent = self.__context['resource_limits']['cpu_percent'] if 'resource_limits' in self.__context.keys() and 'cpu_percent' in self.__context['resource_limits'].keys() else 0
|
# self.__cpuPercent = self.__context['resource_limits']['cpu_percent'] if 'resource_limits' in self.__context.keys() and 'cpu_percent' in self.__context['resource_limits'].keys() else 0
|
||||||
|
|
||||||
self.__logger = context['logger']
|
self.__logger = context['logger']
|
||||||
|
|
||||||
@@ -242,8 +246,19 @@ class FfxController():
|
|||||||
targetPath,
|
targetPath,
|
||||||
targetFormat: str = '',
|
targetFormat: str = '',
|
||||||
videoEncoder: VideoEncoder = VideoEncoder.VP9,
|
videoEncoder: VideoEncoder = VideoEncoder.VP9,
|
||||||
quality: int = DEFAULT_QUALITY,
|
chainIteration: list = []):
|
||||||
preset: int = DEFAULT_AV1_PRESET):
|
# quality: int = DEFAULT_QUALITY,
|
||||||
|
# preset: int = DEFAULT_AV1_PRESET):
|
||||||
|
|
||||||
|
qualityFilters = [fy for fy in chainIteration if fy['identifier'] == 'quality']
|
||||||
|
presetFilters = [fy for fy in chainIteration if fy['identifier'] == 'preset']
|
||||||
|
denoiseFilters = [fy for fy in chainIteration if fy['identifier'] == 'nlmeans']
|
||||||
|
|
||||||
|
quality = qualityFilters[0]['parameters']['quality'] if qualityFilters else QualityFilter.DEFAULT_QUALITY
|
||||||
|
preset = presetFilters[0]['parameters']['preset'] if presetFilters else PresetFilter.DEFAULT_PRESET
|
||||||
|
|
||||||
|
|
||||||
|
denoiseTokens = denoiseFilters[0]['tokens'] if denoiseFilters else []
|
||||||
|
|
||||||
|
|
||||||
commandTokens = FfxController.COMMAND_TOKENS + ['-i', sourcePath]
|
commandTokens = FfxController.COMMAND_TOKENS + ['-i', sourcePath]
|
||||||
@@ -257,7 +272,7 @@ class FfxController():
|
|||||||
|
|
||||||
# Optional tokens
|
# Optional tokens
|
||||||
commandSequence += self.generateMetadataTokens()
|
commandSequence += self.generateMetadataTokens()
|
||||||
commandSequence += self.__context['denoiser'].generateDenoiseTokens()
|
commandSequence += denoiseTokens
|
||||||
|
|
||||||
commandSequence += (self.generateAudioEncodingTokens()
|
commandSequence += (self.generateAudioEncodingTokens()
|
||||||
+ self.generateAV1Tokens(int(quality), int(preset))
|
+ self.generateAV1Tokens(int(quality), int(preset))
|
||||||
@@ -309,7 +324,7 @@ class FfxController():
|
|||||||
|
|
||||||
# Optional tokens
|
# Optional tokens
|
||||||
commandSequence2 += self.generateMetadataTokens()
|
commandSequence2 += self.generateMetadataTokens()
|
||||||
commandSequence2 += self.__context['denoiser'].generateDenoiseTokens()
|
commandSequence2 += denoiseTokens
|
||||||
|
|
||||||
commandSequence2 += self.generateVP9Pass2Tokens(int(quality)) + self.generateAudioEncodingTokens()
|
commandSequence2 += self.generateVP9Pass2Tokens(int(quality)) + self.generateAudioEncodingTokens()
|
||||||
|
|
||||||
|
|||||||
0
bin/ffx/filter/__init__.py
Normal file
0
bin/ffx/filter/__init__.py
Normal file
17
bin/ffx/filter/filter.py
Normal file
17
bin/ffx/filter/filter.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import itertools
|
||||||
|
|
||||||
|
|
||||||
|
class Filter():
|
||||||
|
|
||||||
|
filterChain: list = []
|
||||||
|
|
||||||
|
def __init__(self, filter):
|
||||||
|
|
||||||
|
self.filterChain.append(filter)
|
||||||
|
|
||||||
|
def getFilterChain(self):
|
||||||
|
return self.filterChain
|
||||||
|
|
||||||
|
def getChainYield(self):
|
||||||
|
for fy in itertools.product(*[f.getYield() for f in self.filterChain]):
|
||||||
|
yield fy
|
||||||
162
bin/ffx/filter/nlmeans_filter.py
Normal file
162
bin/ffx/filter/nlmeans_filter.py
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
import itertools
|
||||||
|
|
||||||
|
from .filter import Filter
|
||||||
|
|
||||||
|
|
||||||
|
class NlmeansFilter(Filter):
|
||||||
|
|
||||||
|
IDENTIFIER = 'nlmeans'
|
||||||
|
|
||||||
|
DEFAULT_STRENGTH: float = 2.8
|
||||||
|
DEFAULT_PATCH_SIZE: int = 13
|
||||||
|
DEFAULT_CHROMA_PATCH_SIZE: int = 9
|
||||||
|
DEFAULT_RESEARCH_WINDOW: int = 23
|
||||||
|
DEFAULT_CHROMA_RESEARCH_WINDOW: int= 17
|
||||||
|
|
||||||
|
STRENGTH_KEY = 'strength'
|
||||||
|
PATCH_SIZE_KEY = 'patch_size'
|
||||||
|
CHROMA_PATCH_SIZE_KEY = 'chroma_patch_size'
|
||||||
|
RESEARCH_WINDOW_KEY = 'research_window'
|
||||||
|
CHROMA_RESEARCH_WINDOW_KEY = 'chroma_research_window'
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
|
||||||
|
self.__useHardware = kwargs.get('use_hardware', False)
|
||||||
|
|
||||||
|
self.__strengthList = []
|
||||||
|
strength = kwargs.get(NlmeansFilter.STRENGTH_KEY, '')
|
||||||
|
if strength:
|
||||||
|
strengthTokens = strength.split(',')
|
||||||
|
for st in strengthTokens:
|
||||||
|
try:
|
||||||
|
strengthValue = float(st)
|
||||||
|
except:
|
||||||
|
raise ValueError('NlmeansFilter: Strength value has to be of type float')
|
||||||
|
if strengthValue < 1.0 or strengthValue > 30.0:
|
||||||
|
raise ValueError('NlmeansFilter: Strength value has to be between 1.0 and 30.0')
|
||||||
|
self.__strengthList.append(strengthValue)
|
||||||
|
else:
|
||||||
|
self.__strengthList = [NlmeansFilter.DEFAULT_STRENGTH]
|
||||||
|
|
||||||
|
self.__patchSizeList = []
|
||||||
|
patchSize = kwargs.get(NlmeansFilter.PATCH_SIZE_KEY, '')
|
||||||
|
if patchSize:
|
||||||
|
patchSizeTokens = patchSize.split(',')
|
||||||
|
for pst in patchSizeTokens:
|
||||||
|
try:
|
||||||
|
patchSizeValue = int(pst)
|
||||||
|
except:
|
||||||
|
raise ValueError('NlmeansFilter: Patch size value has to be of type int')
|
||||||
|
if patchSizeValue < 0 or patchSizeValue > 99:
|
||||||
|
raise ValueError('NlmeansFilter: Patch size value has to be between 0 and 99')
|
||||||
|
if patchSizeValue % 2 == 0:
|
||||||
|
raise ValueError('NlmeansFilter: Patch size value has to an odd number')
|
||||||
|
self.__patchSizeList.append(patchSizeValue)
|
||||||
|
else:
|
||||||
|
self.__patchSizeList = [NlmeansFilter.DEFAULT_PATCH_SIZE]
|
||||||
|
|
||||||
|
self.__chromaPatchSizeList = []
|
||||||
|
chromaPatchSize = kwargs.get(NlmeansFilter.CHROMA_PATCH_SIZE_KEY, '')
|
||||||
|
if chromaPatchSize:
|
||||||
|
chromaPatchSizeTokens = chromaPatchSize.split(',')
|
||||||
|
for cpst in chromaPatchSizeTokens:
|
||||||
|
try:
|
||||||
|
chromaPatchSizeValue = int(pst)
|
||||||
|
except:
|
||||||
|
raise ValueError('NlmeansFilter: Chroma patch size value has to be of type int')
|
||||||
|
if chromaPatchSizeValue < 0 or chromaPatchSizeValue > 99:
|
||||||
|
raise ValueError('NlmeansFilter: Chroma patch value has to be between 0 and 99')
|
||||||
|
if chromaPatchSizeValue % 2 == 0:
|
||||||
|
raise ValueError('NlmeansFilter: Chroma patch value has to an odd number')
|
||||||
|
self.__chromaPatchSizeList.append(chromaPatchSizeValue)
|
||||||
|
else:
|
||||||
|
self.__chromaPatchSizeList = [NlmeansFilter.DEFAULT_CHROMA_PATCH_SIZE]
|
||||||
|
|
||||||
|
self.__researchWindowList = []
|
||||||
|
researchWindow = kwargs.get(NlmeansFilter.RESEARCH_WINDOW_KEY, '')
|
||||||
|
if researchWindow:
|
||||||
|
researchWindowTokens = researchWindow.split(',')
|
||||||
|
for rwt in researchWindowTokens:
|
||||||
|
try:
|
||||||
|
researchWindowValue = int(rwt)
|
||||||
|
except:
|
||||||
|
raise ValueError('NlmeansFilter: Research window value has to be of type int')
|
||||||
|
if researchWindowValue < 0 or researchWindowValue > 99:
|
||||||
|
raise ValueError('NlmeansFilter: Research window value has to be between 0 and 99')
|
||||||
|
if researchWindowValue % 2 == 0:
|
||||||
|
raise ValueError('NlmeansFilter: Research window value has to an odd number')
|
||||||
|
self.__researchWindowList.append(researchWindowValue)
|
||||||
|
else:
|
||||||
|
self.__researchWindowList = [NlmeansFilter.DEFAULT_RESEARCH_WINDOW]
|
||||||
|
|
||||||
|
self.__chromaResearchWindowList = []
|
||||||
|
chromaResearchWindow = kwargs.get(NlmeansFilter.CHROMA_RESEARCH_WINDOW_KEY, '')
|
||||||
|
if chromaResearchWindow:
|
||||||
|
chromaResearchWindowTokens = chromaResearchWindow.split(',')
|
||||||
|
for crwt in chromaResearchWindowTokens:
|
||||||
|
try:
|
||||||
|
chromaResearchWindowValue = int(crwt)
|
||||||
|
except:
|
||||||
|
raise ValueError('NlmeansFilter: Chroma research window value has to be of type int')
|
||||||
|
if chromaResearchWindowValue < 0 or chromaResearchWindowValue > 99:
|
||||||
|
raise ValueError('NlmeansFilter: Chroma research window value has to be between 0 and 99')
|
||||||
|
if chromaResearchWindowValue % 2 == 0:
|
||||||
|
raise ValueError('NlmeansFilter: Chroma research window value has to an odd number')
|
||||||
|
self.__chromaResearchWindowList.append(chromaResearchWindowValue)
|
||||||
|
else:
|
||||||
|
self.__chromaResearchWindowList = [NlmeansFilter.DEFAULT_CHROMA_RESEARCH_WINDOW]
|
||||||
|
|
||||||
|
super().__init__(self)
|
||||||
|
|
||||||
|
|
||||||
|
def getPayload(self, iteration):
|
||||||
|
|
||||||
|
strength = iteration[0]
|
||||||
|
patchSize = iteration[1]
|
||||||
|
chromaPatchSize = iteration[2]
|
||||||
|
researchWindow = iteration[3]
|
||||||
|
chromaResearchWindow = iteration[4]
|
||||||
|
|
||||||
|
suffices = []
|
||||||
|
|
||||||
|
if len(self.__strengthList) > 1:
|
||||||
|
suffices += [f"ds{strength}"]
|
||||||
|
if len(self.__patchSizeList) > 1:
|
||||||
|
suffices += [f"dp{patchSize}"]
|
||||||
|
if len(self.__chromaPatchSizeList) > 1:
|
||||||
|
suffices += [f"dpc{chromaPatchSize}"]
|
||||||
|
if len(self.__researchWindowList) > 1:
|
||||||
|
suffices += [f"dr{researchWindow}"]
|
||||||
|
if len(self.__chromaResearchWindowList) > 1:
|
||||||
|
suffices += [f"drc{chromaResearchWindow}"]
|
||||||
|
|
||||||
|
filterName = 'nlmeans_opencl' if self.__useHardware else 'nlmeans'
|
||||||
|
|
||||||
|
payload = {'identifier': NlmeansFilter.IDENTIFIER,
|
||||||
|
'parameters': {
|
||||||
|
'strength': strength,
|
||||||
|
'patch_size': patchSize,
|
||||||
|
'chroma_patch_size': chromaPatchSize,
|
||||||
|
'research_window': researchWindow,
|
||||||
|
'chroma_research_window': chromaResearchWindow
|
||||||
|
},
|
||||||
|
'suffices': suffices,
|
||||||
|
'variant': f"DS{strength}-DP{patchSize}-DPC{chromaPatchSize}"
|
||||||
|
+ f"-DR{researchWindow}-DRC{chromaResearchWindow}",
|
||||||
|
'tokens': ['-vf', f"{filterName}=s={strength}"
|
||||||
|
+ f":p={patchSize}"
|
||||||
|
+ f":pc={chromaPatchSize}"
|
||||||
|
+ f":r={researchWindow}"
|
||||||
|
+ f":rc={chromaResearchWindow}"]}
|
||||||
|
|
||||||
|
return payload
|
||||||
|
|
||||||
|
|
||||||
|
def getYield(self):
|
||||||
|
for it in itertools.product(self.__strengthList,
|
||||||
|
self.__patchSizeList,
|
||||||
|
self.__chromaPatchSizeList,
|
||||||
|
self.__researchWindowList,
|
||||||
|
self.__chromaResearchWindowList):
|
||||||
|
yield self.getPayload(it)
|
||||||
54
bin/ffx/filter/preset_filter.py
Normal file
54
bin/ffx/filter/preset_filter.py
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import itertools
|
||||||
|
|
||||||
|
from .filter import Filter
|
||||||
|
|
||||||
|
|
||||||
|
class PresetFilter(Filter):
|
||||||
|
|
||||||
|
IDENTIFIER = 'preset'
|
||||||
|
|
||||||
|
DEFAULT_PRESET = 5
|
||||||
|
|
||||||
|
PRESET_KEY = 'preset'
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
|
||||||
|
self.__presetsList = []
|
||||||
|
presets = str(kwargs.get(PresetFilter.PRESET_KEY, ''))
|
||||||
|
if presets:
|
||||||
|
presetTokens = presets.split(',')
|
||||||
|
for q in presetTokens:
|
||||||
|
try:
|
||||||
|
presetValue = int(q)
|
||||||
|
except:
|
||||||
|
raise ValueError('PresetFilter: Preset value has to be of type int')
|
||||||
|
if presetValue < 0 or presetValue > 13:
|
||||||
|
raise ValueError('PresetFilter: Preset value has to be between 0 and 13')
|
||||||
|
self.__presetsList.append(presetValue)
|
||||||
|
else:
|
||||||
|
self.__presetsList = [PresetFilter.DEFAULT_PRESET]
|
||||||
|
|
||||||
|
super().__init__(self)
|
||||||
|
|
||||||
|
|
||||||
|
def getPayload(self, preset):
|
||||||
|
|
||||||
|
suffices = []
|
||||||
|
|
||||||
|
if len(self.__presetsList) > 1:
|
||||||
|
suffices += [f"p{preset}"]
|
||||||
|
|
||||||
|
payload = {'identifier': PresetFilter.IDENTIFIER,
|
||||||
|
'parameters': {
|
||||||
|
'preset': preset
|
||||||
|
},
|
||||||
|
'suffices': suffices,
|
||||||
|
'variant': f"P{preset}",
|
||||||
|
'tokens': []}
|
||||||
|
|
||||||
|
return payload
|
||||||
|
|
||||||
|
|
||||||
|
def getYield(self):
|
||||||
|
for q in self.__presetsList:
|
||||||
|
yield self.getPayload(q)
|
||||||
54
bin/ffx/filter/quality_filter.py
Normal file
54
bin/ffx/filter/quality_filter.py
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import itertools
|
||||||
|
|
||||||
|
from .filter import Filter
|
||||||
|
|
||||||
|
|
||||||
|
class QualityFilter(Filter):
|
||||||
|
|
||||||
|
IDENTIFIER = 'quality'
|
||||||
|
|
||||||
|
DEFAULT_QUALITY = 32
|
||||||
|
|
||||||
|
QUALITY_KEY = 'quality'
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
|
||||||
|
self.__qualitiesList = []
|
||||||
|
qualities = kwargs.get(QualityFilter.QUALITY_KEY, '')
|
||||||
|
if qualities:
|
||||||
|
qualityTokens = qualities.split(',')
|
||||||
|
for q in qualityTokens:
|
||||||
|
try:
|
||||||
|
qualityValue = int(q)
|
||||||
|
except:
|
||||||
|
raise ValueError('QualityFilter: Quality value has to be of type int')
|
||||||
|
if qualityValue < 0 or qualityValue > 63:
|
||||||
|
raise ValueError('QualityFilter: Quality value has to be between 0 and 63')
|
||||||
|
self.__qualitiesList.append(qualityValue)
|
||||||
|
else:
|
||||||
|
self.__qualitiesList = [QualityFilter.DEFAULT_QUALITY]
|
||||||
|
|
||||||
|
super().__init__(self)
|
||||||
|
|
||||||
|
|
||||||
|
def getPayload(self, quality):
|
||||||
|
|
||||||
|
suffices = []
|
||||||
|
|
||||||
|
if len(self.__qualitiesList) > 1:
|
||||||
|
suffices += [f"q{quality}"]
|
||||||
|
|
||||||
|
payload = {'identifier': QualityFilter.IDENTIFIER,
|
||||||
|
'parameters': {
|
||||||
|
'quality': quality
|
||||||
|
},
|
||||||
|
'suffices': suffices,
|
||||||
|
'variant': f"Q{quality}",
|
||||||
|
'tokens': []}
|
||||||
|
|
||||||
|
return payload
|
||||||
|
|
||||||
|
|
||||||
|
def getYield(self):
|
||||||
|
for q in self.__qualitiesList:
|
||||||
|
yield self.getPayload(q)
|
||||||
6
bin/ffx/filter/scale_filter.py
Normal file
6
bin/ffx/filter/scale_filter.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from .filter import Filter
|
||||||
|
|
||||||
|
class ScaleFilter(Filter):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(self)
|
||||||
@@ -74,5 +74,6 @@ def filterFilename(fileName: str) -> str:
|
|||||||
fileName = str(fileName).replace(':', ';')
|
fileName = str(fileName).replace(':', ';')
|
||||||
fileName = str(fileName).replace('*', '')
|
fileName = str(fileName).replace('*', '')
|
||||||
fileName = str(fileName).replace("'", '')
|
fileName = str(fileName).replace("'", '')
|
||||||
|
fileName = str(fileName).replace("?", '#')
|
||||||
|
|
||||||
return fileName.strip()
|
return fileName.strip()
|
||||||
|
|||||||
@@ -1,142 +0,0 @@
|
|||||||
class NlmeansController():
|
|
||||||
"""
|
|
||||||
s: double
|
|
||||||
|
|
||||||
Denoising strength (from 1 to 30) (default 1)
|
|
||||||
Trade-off between noise removal and detail retention. Comparable to gaussian sigma.
|
|
||||||
|
|
||||||
p: int patch size (from 0 to 99) (default 7)
|
|
||||||
|
|
||||||
Catches larger areas reducing broader noise patterns, but costly
|
|
||||||
|
|
||||||
pc: int patch size for chroma planes (from 0 to 99) (default 0)
|
|
||||||
|
|
||||||
r: int research window (from 0 to 99) (default 15)
|
|
||||||
|
|
||||||
Range to search for comparable patches.
|
|
||||||
Better filtering but costly
|
|
||||||
|
|
||||||
rc: int research window for chroma planes (from 0 to 99) (default 0)
|
|
||||||
|
|
||||||
Good values to denoise film grain that was subobtimally encoded:
|
|
||||||
strength: float = 2.8
|
|
||||||
patchSize: int = 12
|
|
||||||
chromaPatchSize: int = 8
|
|
||||||
researchWindow: int = 22
|
|
||||||
chromaResearchWindow: int= 16
|
|
||||||
"""
|
|
||||||
|
|
||||||
DEFAULT_STRENGTH: float = 2.8
|
|
||||||
DEFAULT_PATCH_SIZE: int = 13
|
|
||||||
DEFAULT_CHROMA_PATCH_SIZE: int = 9
|
|
||||||
DEFAULT_RESEARCH_WINDOW: int = 23
|
|
||||||
DEFAULT_CHROMA_RESEARCH_WINDOW: int= 17
|
|
||||||
|
|
||||||
def __init__(self,
|
|
||||||
parameters: str = "none",
|
|
||||||
strength: str = "",
|
|
||||||
patchSize: str = "",
|
|
||||||
chromaPatchSize: str = "",
|
|
||||||
researchWindow: str = "",
|
|
||||||
chromaResearchWindow: str = "",
|
|
||||||
useHardware: bool = False):
|
|
||||||
|
|
||||||
self.__isActive = (parameters != "none"
|
|
||||||
or strength
|
|
||||||
or patchSize
|
|
||||||
or chromaPatchSize
|
|
||||||
or researchWindow
|
|
||||||
or chromaResearchWindow)
|
|
||||||
self.__useHardware = useHardware
|
|
||||||
|
|
||||||
parameterTokens = parameters.split(',')
|
|
||||||
|
|
||||||
self.__strengthList = []
|
|
||||||
if strength:
|
|
||||||
strengthTokens = strength.split(',')
|
|
||||||
for st in strengthTokens:
|
|
||||||
try:
|
|
||||||
strengthValue = float(st)
|
|
||||||
except:
|
|
||||||
raise ValueError('NlmeansController: Strength value has to be of type float')
|
|
||||||
if strengthValue < 1.0 or strengthValue > 30.0:
|
|
||||||
raise ValueError('NlmeansController: Strength value has to be between 1.0 and 30.0')
|
|
||||||
self.__strengthList.append(strengthValue)
|
|
||||||
else:
|
|
||||||
self.__strengthList = [NlmeansController.DEFAULT_STRENGTH]
|
|
||||||
|
|
||||||
self.__patchSizeList = []
|
|
||||||
if patchSize:
|
|
||||||
patchSizeTokens = patchSize.split(',')
|
|
||||||
for pst in patchSizeTokens:
|
|
||||||
try:
|
|
||||||
patchSizeValue = int(pst)
|
|
||||||
except:
|
|
||||||
raise ValueError('NlmeansController: Patch size value has to be of type int')
|
|
||||||
if patchSizeValue < 0 or patchSizeValue > 99:
|
|
||||||
raise ValueError('NlmeansController: Patch size value has to be between 0 and 99')
|
|
||||||
if patchSizeValue % 2 == 0:
|
|
||||||
raise ValueError('NlmeansController: Patch size value has to an odd number')
|
|
||||||
self.__patchSizeList.append(patchSizeValue)
|
|
||||||
else:
|
|
||||||
self.__patchSizeList = [NlmeansController.DEFAULT_PATCH_SIZE]
|
|
||||||
|
|
||||||
self.__chromaPatchSizeList = []
|
|
||||||
if chromaPatchSize:
|
|
||||||
chromaPatchSizeTokens = chromaPatchSize.split(',')
|
|
||||||
for cpst in chromaPatchSizeTokens:
|
|
||||||
try:
|
|
||||||
chromaPatchSizeValue = int(pst)
|
|
||||||
except:
|
|
||||||
raise ValueError('NlmeansController: Chroma patch size value has to be of type int')
|
|
||||||
if chromaPatchSizeValue < 0 or chromaPatchSizeValue > 99:
|
|
||||||
raise ValueError('NlmeansController: Chroma patch value has to be between 0 and 99')
|
|
||||||
if chromaPatchSizeValue % 2 == 0:
|
|
||||||
raise ValueError('NlmeansController: Chroma patch value has to an odd number')
|
|
||||||
self.__chromaPatchSizeList.append(chromaPatchSizeValue)
|
|
||||||
else:
|
|
||||||
self.__chromaPatchSizeList = [NlmeansController.DEFAULT_CHROMA_PATCH_SIZE]
|
|
||||||
|
|
||||||
self.__researchWindowList = []
|
|
||||||
if researchWindow:
|
|
||||||
researchWindowTokens = researchWindow.split(',')
|
|
||||||
for rwt in researchWindowTokens:
|
|
||||||
try:
|
|
||||||
researchWindowValue = int(rwt)
|
|
||||||
except:
|
|
||||||
raise ValueError('NlmeansController: Research window value has to be of type int')
|
|
||||||
if researchWindowValue < 0 or researchWindowValue > 99:
|
|
||||||
raise ValueError('NlmeansController: Research window value has to be between 0 and 99')
|
|
||||||
if researchWindowValue % 2 == 0:
|
|
||||||
raise ValueError('NlmeansController: Research window value has to an odd number')
|
|
||||||
self.__researchWindowList.append(researchWindowValue)
|
|
||||||
else:
|
|
||||||
self.__researchWindowList = [NlmeansController.DEFAULT_RESEARCH_WINDOW]
|
|
||||||
|
|
||||||
self.__chromaResearchWindowList = []
|
|
||||||
if chromaResearchWindow:
|
|
||||||
chromaResearchWindowTokens = chromaResearchWindow.split(',')
|
|
||||||
for crwt in chromaResearchWindowTokens:
|
|
||||||
try:
|
|
||||||
chromaResearchWindowValue = int(crwt)
|
|
||||||
except:
|
|
||||||
raise ValueError('NlmeansController: Chroma research window value has to be of type int')
|
|
||||||
if chromaResearchWindowValue < 0 or chromaResearchWindowValue > 99:
|
|
||||||
raise ValueError('NlmeansController: Chroma research window value has to be between 0 and 99')
|
|
||||||
if chromaResearchWindowValue % 2 == 0:
|
|
||||||
raise ValueError('NlmeansController: Chroma research window value has to an odd number')
|
|
||||||
self.__chromaResearchWindowList.append(chromaResearchWindowValue)
|
|
||||||
else:
|
|
||||||
self.__chromaResearchWindowList = [NlmeansController.DEFAULT_CHROMA_RESEARCH_WINDOW]
|
|
||||||
|
|
||||||
def isActive(self):
|
|
||||||
return self.__isActive
|
|
||||||
|
|
||||||
def generateDenoiseTokens(self):
|
|
||||||
filterName = 'nlmeans_opencl' if self.__useHardware else 'nlmeans'
|
|
||||||
return ['-vf', f"{filterName}=s={self.__strengthList[0]}"
|
|
||||||
+ f":p={self.__patchSizeList[0]}"
|
|
||||||
+ f":pc={self.__chromaPatchSizeList[0]}"
|
|
||||||
+ f":r={self.__researchWindowList[0]}"
|
|
||||||
+ f":rc={self.__chromaResearchWindowList[0]}"] if self.__isActive else []
|
|
||||||
|
|
||||||
9
bin/filtertest.py
Normal file
9
bin/filtertest.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
from ffx.filter.nlmeans_filter import NlmeansFilter
|
||||||
|
from ffx.filter.quality_filter import QualityFilter
|
||||||
|
|
||||||
|
q = QualityFilter()
|
||||||
|
#q = QualityFilter('32,34')
|
||||||
|
# NlmeansFilter(researchWindow='5,7,9', strength='2.0,3.0,4.0')
|
||||||
|
|
||||||
|
for cy in q.getChainYield():
|
||||||
|
print(cy)
|
||||||
Reference in New Issue
Block a user