From 15bfbdbe8857ac56b165faa871535977e420e2c2 Mon Sep 17 00:00:00 2001 From: Javanaut Date: Thu, 6 Nov 2025 14:08:00 +0100 Subject: [PATCH] Adds setting quality accoeding to pattern default --- .gitignore | 4 +++- build/lib/ffx/audio_layout.py | 9 ++++++++ build/lib/ffx/ffx.py | 28 +++++++++++++++---------- build/lib/ffx/ffx_controller.py | 27 +++++++++++++++++++++--- build/lib/ffx/filter/quality_filter.py | 13 +++++++++--- build/lib/ffx/model/pattern.py | 4 +++- build/lib/ffx/pattern_controller.py | 1 + build/lib/ffx/pattern_details_screen.py | 17 ++++++++++++++- build/lib/ffx/process.py | 10 ++------- src/ffx/ffx.py | 20 ++++++++---------- src/ffx/ffx_controller.py | 26 +++++++++++++++++++---- src/ffx/filter/quality_filter.py | 13 +++++++++--- 12 files changed, 126 insertions(+), 46 deletions(-) diff --git a/.gitignore b/.gitignore index cac7cf1..8f2bcca 100644 --- a/.gitignore +++ b/.gitignore @@ -8,5 +8,7 @@ tools/ansible/inventory/cappuccino.yml tools/ansible/inventory/group_vars/all.yml ffx_test_report.log bin/conversiontest.py -*.egg-info/ +build/ +dist/ +*.egg-info/ diff --git a/build/lib/ffx/audio_layout.py b/build/lib/ffx/audio_layout.py index 99e8ab8..efb28a1 100644 --- a/build/lib/ffx/audio_layout.py +++ b/build/lib/ffx/audio_layout.py @@ -30,6 +30,15 @@ class AudioLayout(Enum): except: return AudioLayout.LAYOUT_UNDEFINED + # @staticmethod + # def fromIndex(index : int): + # try: + # target_index = int(index) + # except (TypeError, ValueError): + # return AudioLayout.LAYOUT_UNDEFINED + # return next((a for a in AudioLayout if a.value['index'] == target_index), + # AudioLayout.LAYOUT_UNDEFINED) + @staticmethod def fromIndex(index : int): try: diff --git a/build/lib/ffx/ffx.py b/build/lib/ffx/ffx.py index 8402c69..04c69f4 100644 --- a/build/lib/ffx/ffx.py +++ b/build/lib/ffx/ffx.py @@ -32,6 +32,7 @@ from ffx.filter.preset_filter import PresetFilter from ffx.filter.crop_filter import CropFilter from ffx.filter.nlmeans_filter import NlmeansFilter +from ffx.filter.deinterlace_filter import DeinterlaceFilter from ffx.constants import VERSION @@ -335,6 +336,8 @@ def checkUniqueDispositions(context, mediaDescriptor: MediaDescriptor): @click.option("--output-directory", type=str, default='') +@click.option("--deinterlace", is_flag=False, flag_value="default", default="none") + @click.option("--denoise", is_flag=False, flag_value="default", default="none") @click.option("--denoise-use-hw", is_flag=True, default=False) @click.option('--denoise-strength', type=str, default='', help='Denoising strength, more blurring vs more details.') @@ -391,6 +394,8 @@ def convert(ctx, output_directory, + deinterlace, + denoise, denoise_use_hw, denoise_strength, @@ -517,14 +522,6 @@ def convert(ctx, ctx.obj['logger'].debug(f"\nVideo encoder: {video_encoder}") - qualityTokens = quality.split(',') - q_list = [q for q in qualityTokens if q.isnumeric()] - 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']['stereo'] = str(stereo_bitrate) if str(stereo_bitrate).endswith('k') else f"{stereo_bitrate}k" @@ -548,9 +545,12 @@ def convert(ctx, tc = TmdbController() if context['use_tmdb'] else None - qualityKwargs = {QualityFilter.QUALITY_KEY: str(QualityFilter.DEFAULT_H264_QUALITY if (context['video_encoder'] == VideoEncoder.H264 and not quality) else quality)} + + qualityKwargs = {QualityFilter.QUALITY_KEY: str(quality)} qf = QualityFilter(**qualityKwargs) + + if context['video_encoder'] == VideoEncoder.AV1 and preset: presetKwargs = {PresetFilter.PRESET_KEY: preset} PresetFilter(**presetKwargs) @@ -575,6 +575,9 @@ def convert(ctx, if denoise != 'none' or denoiseKwargs: NlmeansFilter(**denoiseKwargs) + if deinterlace != 'none': + DeinterlaceFilter() + chainYield = list(qf.getChainYield()) ctx.obj['logger'].info(f"\nRunning {len(existingSourcePaths) * len(chainYield)} jobs") @@ -791,15 +794,18 @@ def convert(ctx, ctx.obj['logger'].info(f"Creating file {targetFilename}") + if rename_only: shutil.copyfile(sourcePath, targetPath) else: fc.runJob(sourcePath, targetPath, targetFormat, - context['video_encoder'], chainIteration, - cropArguments) + cropArguments, + currentPattern) + + endTime = time.perf_counter() ctx.obj['logger'].info(f"\nDONE\nTime elapsed {endTime - startTime}") diff --git a/build/lib/ffx/ffx_controller.py b/build/lib/ffx/ffx_controller.py index a6c62f9..4464ad3 100644 --- a/build/lib/ffx/ffx_controller.py +++ b/build/lib/ffx/ffx_controller.py @@ -15,6 +15,8 @@ from ffx.filter.quality_filter import QualityFilter from ffx.filter.preset_filter import PresetFilter from ffx.filter.crop_filter import CropFilter +from ffx.model.pattern import Pattern + class FfxController(): @@ -179,19 +181,35 @@ class FfxController(): sourcePath, targetPath, targetFormat: str = '', - videoEncoder: VideoEncoder = VideoEncoder.VP9, chainIteration: list = [], - cropArguments: dict = {}): + cropArguments: dict = {}, + currentPattern: Pattern = None): # quality: int = DEFAULT_QUALITY, # preset: int = DEFAULT_AV1_PRESET): + + videoEncoder: VideoEncoder = self.__context.get('video_encoder', VideoEncoder.VP9) + + qualityFilters = [fy for fy in chainIteration if fy['identifier'] == 'quality'] presetFilters = [fy for fy in chainIteration if fy['identifier'] == 'preset'] cropFilters = [fy for fy in chainIteration if fy['identifier'] == 'crop'] denoiseFilters = [fy for fy in chainIteration if fy['identifier'] == 'nlmeans'] + deinterlaceFilters = [fy for fy in chainIteration if fy['identifier'] == 'bwdif'] + + + if qualityFilters and (quality := qualityFilters[0]['parameters']['quality']): + self.__logger.info(f"Setting quality {quality} from filter") + elif (quality := currentPattern.quality): + self.__logger.info(f"Setting quality {quality} from pattern default") + else: + quality = (QualityFilter.DEFAULT_H264_QUALITY + if (videoEncoder == VideoEncoder.H264) + else QualityFilter.DEFAULT_VP9_QUALITY) + self.__logger.info(f"Setting quality {quality} from default") + - quality = (qualityFilters[0]['parameters']['quality'] if qualityFilters else QualityFilter.DEFAULT_VP9_QUALITY) preset = presetFilters[0]['parameters']['preset'] if presetFilters else PresetFilter.DEFAULT_PRESET @@ -208,6 +226,9 @@ class FfxController(): filterParamTokens.append(cropParams) filterParamTokens.extend(denoiseFilters[0]['tokens'] if denoiseFilters else []) + filterParamTokens.extend(deinterlaceFilters[0]['tokens'] if deinterlaceFilters else []) + + deinterlaceFilters filterTokens = ['-vf', ', '.join(filterParamTokens)] if filterParamTokens else [] diff --git a/build/lib/ffx/filter/quality_filter.py b/build/lib/ffx/filter/quality_filter.py index e5a8ff7..75576ac 100644 --- a/build/lib/ffx/filter/quality_filter.py +++ b/build/lib/ffx/filter/quality_filter.py @@ -1,7 +1,9 @@ -import itertools +import click from .filter import Filter +from ffx.video_encoder import VideoEncoder + class QualityFilter(Filter): @@ -14,6 +16,9 @@ class QualityFilter(Filter): def __init__(self, **kwargs): + context = click.get_current_context().obj + + self.__qualitiesList = [] qualities = kwargs.get(QualityFilter.QUALITY_KEY, '') if qualities: @@ -27,7 +32,9 @@ class QualityFilter(Filter): raise ValueError('QualityFilter: Quality value has to be between 0 and 63') self.__qualitiesList.append(qualityValue) else: - self.__qualitiesList = [QualityFilter.DEFAULT_VP9_QUALITY] + + self.__qualitiesList = [None] + super().__init__(self) @@ -52,4 +59,4 @@ class QualityFilter(Filter): def getYield(self): for q in self.__qualitiesList: - yield self.getPayload(q) \ No newline at end of file + yield self.getPayload(q) diff --git a/build/lib/ffx/model/pattern.py b/build/lib/ffx/model/pattern.py index ac1584a..c612cc5 100644 --- a/build/lib/ffx/model/pattern.py +++ b/build/lib/ffx/model/pattern.py @@ -31,9 +31,11 @@ class Pattern(Base): tracks = relationship('Track', back_populates='pattern', cascade="all, delete", lazy='joined') - media_tags = relationship('MediaTag', back_populates='pattern', cascade="all, delete", lazy='joined') + quality = Column(Integer, default=0) + + def getId(self): return int(self.id) diff --git a/build/lib/ffx/pattern_controller.py b/build/lib/ffx/pattern_controller.py index ae3c560..956cf76 100644 --- a/build/lib/ffx/pattern_controller.py +++ b/build/lib/ffx/pattern_controller.py @@ -49,6 +49,7 @@ class PatternController(): pattern.show_id = int(patternObj['show_id']) pattern.pattern = str(patternObj['pattern']) + pattern.quality = str(patternObj['quality']) s.commit() return True diff --git a/build/lib/ffx/pattern_details_screen.py b/build/lib/ffx/pattern_details_screen.py index 955d91c..5e78330 100644 --- a/build/lib/ffx/pattern_details_screen.py +++ b/build/lib/ffx/pattern_details_screen.py @@ -40,7 +40,7 @@ class PatternDetailsScreen(Screen): Grid { grid-size: 7 13; - grid-rows: 2 2 2 2 2 8 2 2 8 2 2 2 2; + grid-rows: 2 2 2 2 2 2 8 2 2 8 2 2 2 2; grid-columns: 25 25 25 25 25 25 25; height: 100%; width: 100%; @@ -255,6 +255,9 @@ class PatternDetailsScreen(Screen): self.query_one("#pattern_input", Input).value = str(self.__pattern.getPattern()) + if self.__pattern and self.__pattern.quality: + self.query_one("#quality_input", Input).value = str(self.__pattern.quality) + self.updateTags() self.updateTracks() @@ -301,6 +304,12 @@ class PatternDetailsScreen(Screen): # 3 yield Static(" ", classes="seven") + + # 4 + yield Static("Quality") + yield Input(type="integer", id="quality_input") + yield Static(' ', classes="five") + # 4 yield Static(" ", classes="seven") @@ -367,6 +376,11 @@ class PatternDetailsScreen(Screen): def getPatternFromInput(self): return str(self.query_one("#pattern_input", Input).value) + def getQualityFromInput(self): + try: + return int(self.query_one("#quality_input", Input).value) + except ValueError: + return 0 def getSelectedTrackDescriptor(self): @@ -428,6 +442,7 @@ class PatternDetailsScreen(Screen): patternDescriptor = {} patternDescriptor['show_id'] = self.__showDescriptor.getId() patternDescriptor['pattern'] = self.getPatternFromInput() + patternDescriptor['quality'] = self.getQualityFromInput() if self.__pattern is not None: diff --git a/build/lib/ffx/process.py b/build/lib/ffx/process.py index 1ec79a1..08953bd 100644 --- a/build/lib/ffx/process.py +++ b/build/lib/ffx/process.py @@ -15,14 +15,8 @@ def executeProcess(commandSequence: List[str], directory: str = None, context: d niceSequence = [] - niceness = (int(context['resource_limits']['niceness']) - if not context is None - and 'resource_limits' in context.keys() - and 'niceness' in context['resource_limits'].keys() else 99) - cpu_percent = (int(context['resource_limits']['cpu_percent']) - if not context is None - and 'resource_limits' in context.keys() - and 'cpu_percent' in context['resource_limits'].keys() else 0) + niceness = int((context or {}).get('resource_limits', {}).get('niceness', 99)) + cpu_percent = int((context or {}).get('resource_limits', {}).get('cpu_percent', 0)) if niceness >= -20 and niceness <= 19: niceSequence += ['nice', '-n', str(niceness)] diff --git a/src/ffx/ffx.py b/src/ffx/ffx.py index b372688..04c69f4 100755 --- a/src/ffx/ffx.py +++ b/src/ffx/ffx.py @@ -522,14 +522,6 @@ def convert(ctx, ctx.obj['logger'].debug(f"\nVideo encoder: {video_encoder}") - qualityTokens = quality.split(',') - q_list = [q for q in qualityTokens if q.isnumeric()] - 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']['stereo'] = str(stereo_bitrate) if str(stereo_bitrate).endswith('k') else f"{stereo_bitrate}k" @@ -553,9 +545,12 @@ def convert(ctx, tc = TmdbController() if context['use_tmdb'] else None - qualityKwargs = {QualityFilter.QUALITY_KEY: str(QualityFilter.DEFAULT_H264_QUALITY if (context['video_encoder'] == VideoEncoder.H264 and not quality) else quality)} + + qualityKwargs = {QualityFilter.QUALITY_KEY: str(quality)} qf = QualityFilter(**qualityKwargs) + + if context['video_encoder'] == VideoEncoder.AV1 and preset: presetKwargs = {PresetFilter.PRESET_KEY: preset} PresetFilter(**presetKwargs) @@ -799,15 +794,18 @@ def convert(ctx, ctx.obj['logger'].info(f"Creating file {targetFilename}") + if rename_only: shutil.copyfile(sourcePath, targetPath) else: fc.runJob(sourcePath, targetPath, targetFormat, - context['video_encoder'], chainIteration, - cropArguments) + cropArguments, + currentPattern) + + endTime = time.perf_counter() ctx.obj['logger'].info(f"\nDONE\nTime elapsed {endTime - startTime}") diff --git a/src/ffx/ffx_controller.py b/src/ffx/ffx_controller.py index c81406e..db123c8 100644 --- a/src/ffx/ffx_controller.py +++ b/src/ffx/ffx_controller.py @@ -1,4 +1,5 @@ import os, click +from logging import Logger from ffx.media_descriptor_change_set import MediaDescriptorChangeSet @@ -15,6 +16,8 @@ from ffx.filter.quality_filter import QualityFilter from ffx.filter.preset_filter import PresetFilter from ffx.filter.crop_filter import CropFilter +from ffx.model.pattern import Pattern + class FfxController(): @@ -46,7 +49,7 @@ class FfxController(): targetMediaDescriptor, sourceMediaDescriptor) - self.__logger = context['logger'] + self.__logger: Logger = context['logger'] def generateAV1Tokens(self, quality, preset, subIndex : int = 0): @@ -179,12 +182,16 @@ class FfxController(): sourcePath, targetPath, targetFormat: str = '', - videoEncoder: VideoEncoder = VideoEncoder.VP9, chainIteration: list = [], - cropArguments: dict = {}): + cropArguments: dict = {}, + currentPattern: Pattern = None): # quality: int = DEFAULT_QUALITY, # preset: int = DEFAULT_AV1_PRESET): + + videoEncoder: VideoEncoder = self.__context.get('video_encoder', VideoEncoder.VP9) + + qualityFilters = [fy for fy in chainIteration if fy['identifier'] == 'quality'] presetFilters = [fy for fy in chainIteration if fy['identifier'] == 'preset'] @@ -192,7 +199,18 @@ class FfxController(): denoiseFilters = [fy for fy in chainIteration if fy['identifier'] == 'nlmeans'] deinterlaceFilters = [fy for fy in chainIteration if fy['identifier'] == 'bwdif'] - quality = (qualityFilters[0]['parameters']['quality'] if qualityFilters else QualityFilter.DEFAULT_VP9_QUALITY) + + if qualityFilters and (quality := qualityFilters[0]['parameters']['quality']): + self.__logger.debug(f"Setting quality {quality} from filter") + elif (quality := currentPattern.quality): + self.__logger.debug(f"Setting quality {quality} from pattern default") + else: + quality = (QualityFilter.DEFAULT_H264_QUALITY + if (videoEncoder == VideoEncoder.H264) + else QualityFilter.DEFAULT_VP9_QUALITY) + self.__logger.debug(f"Setting quality {quality} from default") + + preset = presetFilters[0]['parameters']['preset'] if presetFilters else PresetFilter.DEFAULT_PRESET diff --git a/src/ffx/filter/quality_filter.py b/src/ffx/filter/quality_filter.py index e5a8ff7..75576ac 100644 --- a/src/ffx/filter/quality_filter.py +++ b/src/ffx/filter/quality_filter.py @@ -1,7 +1,9 @@ -import itertools +import click from .filter import Filter +from ffx.video_encoder import VideoEncoder + class QualityFilter(Filter): @@ -14,6 +16,9 @@ class QualityFilter(Filter): def __init__(self, **kwargs): + context = click.get_current_context().obj + + self.__qualitiesList = [] qualities = kwargs.get(QualityFilter.QUALITY_KEY, '') if qualities: @@ -27,7 +32,9 @@ class QualityFilter(Filter): raise ValueError('QualityFilter: Quality value has to be between 0 and 63') self.__qualitiesList.append(qualityValue) else: - self.__qualitiesList = [QualityFilter.DEFAULT_VP9_QUALITY] + + self.__qualitiesList = [None] + super().__init__(self) @@ -52,4 +59,4 @@ class QualityFilter(Filter): def getYield(self): for q in self.__qualitiesList: - yield self.getPayload(q) \ No newline at end of file + yield self.getPayload(q)