diff --git a/source/video_transcoder/changelog.md b/source/video_transcoder/changelog.md index 3a5461095..deca8a99c 100644 --- a/source/video_transcoder/changelog.md +++ b/source/video_transcoder/changelog.md @@ -1,4 +1,7 @@ +**0.1.2** +- Fix for plugin updates from versions older than 0.1.0 + **0.1.1** - Add support for the av1_qsv encoder diff --git a/source/video_transcoder/info.json b/source/video_transcoder/info.json index 0749f6bdc..545c92558 100644 --- a/source/video_transcoder/info.json +++ b/source/video_transcoder/info.json @@ -12,5 +12,5 @@ "on_worker_process": 1 }, "tags": "video,ffmpeg", - "version": "0.1.1" + "version": "0.1.2" } diff --git a/source/video_transcoder/lib/encoders/libx.py b/source/video_transcoder/lib/encoders/libx.py index fad22f0ee..94d601e1a 100644 --- a/source/video_transcoder/lib/encoders/libx.py +++ b/source/video_transcoder/lib/encoders/libx.py @@ -24,22 +24,23 @@ class LibxEncoder: - provides = { - "libx264": { - "codec": "h264", - "label": "CPU - libx264", - }, - "libx265": { - "codec": "hevc", - "label": "CPU - libx265", - }, - } def __init__(self, settings): self.settings = settings - @staticmethod - def options(): + def provides(self): + return { + "libx264": { + "codec": "h264", + "label": "CPU - libx264", + }, + "libx265": { + "codec": "hevc", + "label": "CPU - libx265", + }, + } + + def options(self): return { "preset": "slow", "tune": "disabled", @@ -49,12 +50,10 @@ def options(): "average_bitrate": "5", } - @staticmethod - def generate_default_args(settings): + def generate_default_args(self): """ Generate a list of args for using a libx decoder - :param settings: :return: """ # No default args required @@ -62,8 +61,7 @@ def generate_default_args(settings): advanced_kwargs = {} return generic_kwargs, advanced_kwargs - @staticmethod - def generate_filtergraphs(): + def generate_filtergraphs(self): """ Generate the required filter for this encoder No filters are required for libx encoders @@ -73,7 +71,8 @@ def generate_filtergraphs(): return [] def encoder_details(self, encoder): - return self.provides.get(encoder, {}) + provides = self.provides() + return provides.get(encoder, {}) def args(self, stream_id): stream_encoding = [] diff --git a/source/video_transcoder/lib/encoders/nvenc.py b/source/video_transcoder/lib/encoders/nvenc.py index e022817b3..b817e5c0e 100644 --- a/source/video_transcoder/lib/encoders/nvenc.py +++ b/source/video_transcoder/lib/encoders/nvenc.py @@ -89,22 +89,23 @@ def get_configured_device(settings): class NvencEncoder: - provides = { - "h264_nvenc": { - "codec": "h264", - "label": "NVENC - h264_nvenc", - }, - "hevc_nvenc": { - "codec": "hevc", - "label": "NVENC - hevc_nvenc", - }, - } def __init__(self, settings): self.settings = settings - @staticmethod - def options(): + def provides(self): + return { + "h264_nvenc": { + "codec": "h264", + "label": "NVENC - h264_nvenc", + }, + "hevc_nvenc": { + "codec": "hevc", + "label": "NVENC - hevc_nvenc", + }, + } + + def options(self): return { "nvenc_device": "none", "nvenc_decoding_method": "cpu", @@ -118,34 +119,31 @@ def options(): "nvenc_aq_strength": 8, } - @staticmethod - def generate_default_args(settings): + def generate_default_args(self): """ Generate a list of args for using a NVENC decoder REF: https://trac.ffmpeg.org/wiki/HWAccelIntro#NVDECCUVID - :param settings: :return: """ - hardware_device = get_configured_device(settings) + hardware_device = get_configured_device(self.settings) generic_kwargs = {} advanced_kwargs = {} # Check if we are using a HW accelerated decoder also - if settings.get_setting('nvenc_decoding_method') in ['cuda', 'nvdec', 'cuvid']: + if self.settings.get_setting('nvenc_decoding_method') in ['cuda', 'nvdec', 'cuvid']: generic_kwargs = { "-hwaccel_device": hardware_device.get('hwaccel_device'), - "-hwaccel": settings.get_setting('nvenc_decoding_method'), + "-hwaccel": self.settings.get_setting('nvenc_decoding_method'), "-init_hw_device": "cuda=hw", "-filter_hw_device": "hw", } - if settings.get_setting('nvenc_decoding_method') in ['cuda', 'nvdec']: + if self.settings.get_setting('nvenc_decoding_method') in ['cuda', 'nvdec']: generic_kwargs["-hwaccel_output_format"] = "cuda" return generic_kwargs, advanced_kwargs - @staticmethod - def generate_filtergraphs(software_filters, hw_smart_filters): + def generate_filtergraphs(self, software_filters, hw_smart_filters): """ Generate the required filter for enabling NVENC HW acceleration @@ -166,7 +164,8 @@ def encoder_details(self, encoder): if not hardware_devices: # Return no options. No hardware device was found return {} - return self.provides.get(encoder, {}) + provides = self.provides() + return provides.get(encoder, {}) def args(self, stream_info, stream_id): generic_kwargs = {} diff --git a/source/video_transcoder/lib/encoders/qsv.py b/source/video_transcoder/lib/encoders/qsv.py index 9c00ce259..eda247f67 100644 --- a/source/video_transcoder/lib/encoders/qsv.py +++ b/source/video_transcoder/lib/encoders/qsv.py @@ -33,26 +33,27 @@ class QsvEncoder: - provides = { - "h264_qsv": { - "codec": "h264", - "label": "QSV - h264_qsv", - }, - "hevc_qsv": { - "codec": "hevc", - "label": "QSV - hevc_qsv", - }, - "av1_qsv": { - "codec": "av1", - "label": "QSV - av1_qsv", - }, - } def __init__(self, settings): self.settings = settings - @staticmethod - def options(): + def provides(self): + return { + "h264_qsv": { + "codec": "h264", + "label": "QSV - h264_qsv", + }, + "hevc_qsv": { + "codec": "hevc", + "label": "QSV - hevc_qsv", + }, + "av1_qsv": { + "codec": "av1", + "label": "QSV - av1_qsv", + }, + } + + def options(self): return { "qsv_decoding_method": "cpu", "qsv_preset": "slow", @@ -63,8 +64,7 @@ def options(): "qsv_average_bitrate": "5", } - @staticmethod - def generate_default_args(settings): + def generate_default_args(self): """ Generate a list of args for using a QSV decoder @@ -79,7 +79,7 @@ def generate_default_args(settings): } advanced_kwargs = {} # Check if we are using a HW accelerated decoder> Modify args as required - if settings.get_setting('qsv_decoding_method') in ['qsv']: + if self.settings.get_setting('qsv_decoding_method') in ['qsv']: generic_kwargs = { "-hwaccel": "qsv", "-hwaccel_output_format": "qsv", @@ -88,8 +88,7 @@ def generate_default_args(settings): } return generic_kwargs, advanced_kwargs - @staticmethod - def generate_filtergraphs(settings, software_filters, hw_smart_filters): + def generate_filtergraphs(self, settings, software_filters, hw_smart_filters): """ Generate the required filter for enabling QSV HW acceleration @@ -117,7 +116,8 @@ def generate_filtergraphs(settings, software_filters, hw_smart_filters): return generic_kwargs, advanced_kwargs, filter_args def encoder_details(self, encoder): - return self.provides.get(encoder, {}) + provides = self.provides() + return provides.get(encoder, {}) def args(self, stream_id): stream_encoding = [] diff --git a/source/video_transcoder/lib/encoders/vaapi.py b/source/video_transcoder/lib/encoders/vaapi.py index 78f721980..12618dbf8 100644 --- a/source/video_transcoder/lib/encoders/vaapi.py +++ b/source/video_transcoder/lib/encoders/vaapi.py @@ -47,22 +47,23 @@ def list_available_vaapi_devices(): class VaapiEncoder: - provides = { - "h264_vaapi": { - "codec": "h264", - "label": "VAAPI - h264_vaapi", - }, - "hevc_vaapi": { - "codec": "hevc", - "label": "VAAPI - hevc_vaapi", - }, - } def __init__(self, settings): self.settings = settings - @staticmethod - def options(): + def provides(self): + return { + "h264_vaapi": { + "codec": "h264", + "label": "VAAPI - h264_vaapi", + }, + "hevc_vaapi": { + "codec": "hevc", + "label": "VAAPI - hevc_vaapi", + }, + } + + def options(self): return { "vaapi_device": "none", "vaapi_enabled_hw_decoding": True, @@ -72,12 +73,10 @@ def options(): "vaapi_average_bitrate": "5", } - @staticmethod - def generate_default_args(settings): + def generate_default_args(self): """ Generate a list of args for using a VAAPI decoder - :param settings: :return: """ # Set the hardware device @@ -88,10 +87,10 @@ def generate_default_args(settings): hardware_device = None # If we have configured a hardware device - if settings.get_setting('vaapi_device') not in ['none']: + if self.settings.get_setting('vaapi_device') not in ['none']: # Attempt to match to that configured hardware device for hw_device in hardware_devices: - if settings.get_setting('vaapi_device') == hw_device.get('hwaccel_device'): + if self.settings.get_setting('vaapi_device') == hw_device.get('hwaccel_device'): hardware_device = hw_device break # If no matching hardware device is set, then select the first one @@ -99,7 +98,7 @@ def generate_default_args(settings): hardware_device = hardware_devices[0] # Check if we are using a VAAPI decoder also... - if settings.get_setting('vaapi_enabled_hw_decoding'): + if self.settings.get_setting('vaapi_enabled_hw_decoding'): # Set a named global device that can be used with various params dev_id = 'vaapi0' # Configure args such that when the input may or may not be able to be decoded with hardware we can do: @@ -123,8 +122,7 @@ def generate_default_args(settings): return generic_kwargs, advanced_kwargs - @staticmethod - def generate_filtergraphs(): + def generate_filtergraphs(self): """ Generate the required filter for enabling VAAPI HW acceleration @@ -133,7 +131,8 @@ def generate_filtergraphs(): return ["format=nv12|vaapi,hwupload"] def encoder_details(self, encoder): - return self.provides.get(encoder, {}) + provides = self.provides() + return provides.get(encoder, {}) def args(self, stream_id): stream_encoding = [] diff --git a/source/video_transcoder/lib/global_settings.py b/source/video_transcoder/lib/global_settings.py index f57825d59..a68b25825 100644 --- a/source/video_transcoder/lib/global_settings.py +++ b/source/video_transcoder/lib/global_settings.py @@ -97,19 +97,6 @@ def __set_default_option(self, select_options, key, default_option=None): if self.settings.get_setting(key) not in available_options: self.settings.set_setting(key, default_option) - @staticmethod - def __is_nvidia_gpu_present(): - try: - # Run the nvidia-smi command - subprocess.run("nvidia-smi", stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True) - return True - except FileNotFoundError: - # nvidia-smi executable not found - return False - except subprocess.CalledProcessError: - # nvidia-smi command failed, likely no NVIDIA GPU present - return False - def get_mode_form_settings(self): return { "label": "Config mode", diff --git a/source/video_transcoder/lib/plugin_stream_mapper.py b/source/video_transcoder/lib/plugin_stream_mapper.py index 97481fa9b..4f0feede6 100644 --- a/source/video_transcoder/lib/plugin_stream_mapper.py +++ b/source/video_transcoder/lib/plugin_stream_mapper.py @@ -92,23 +92,12 @@ def set_default_values(self, settings, abspath, probe): # Build hardware acceleration args based on encoder # Note: these are not applied to advanced mode - advanced mode was returned above - if self.settings.get_setting('video_encoder') in LibxEncoder.provides: - generic_kwargs, advanced_kwargs = LibxEncoder.generate_default_args(self.settings) - self.set_ffmpeg_generic_options(**generic_kwargs) - self.set_ffmpeg_advanced_options(**advanced_kwargs) - elif self.settings.get_setting('video_encoder') in QsvEncoder.provides: - generic_kwargs, advanced_kwargs = QsvEncoder.generate_default_args(self.settings) - self.set_ffmpeg_generic_options(**generic_kwargs) - self.set_ffmpeg_advanced_options(**advanced_kwargs) - elif self.settings.get_setting('video_encoder') in VaapiEncoder.provides: - generic_kwargs, advanced_kwargs = VaapiEncoder.generate_default_args(self.settings) - self.set_ffmpeg_generic_options(**generic_kwargs) - self.set_ffmpeg_advanced_options(**advanced_kwargs) - elif self.settings.get_setting('video_encoder') in NvencEncoder.provides: - generic_kwargs, advanced_kwargs = NvencEncoder.generate_default_args(self.settings) - self.set_ffmpeg_generic_options(**generic_kwargs) - self.set_ffmpeg_advanced_options(**advanced_kwargs) - # TODO: Disable any options not compatible with this encoder + for encoder_name in self.settings.encoders: + encoder_lib = self.settings.encoders.get(encoder_name) + if self.settings.get_setting('video_encoder') in encoder_lib.provides(): + generic_kwargs, advanced_kwargs = encoder_lib.generate_default_args() + self.set_ffmpeg_generic_options(**generic_kwargs) + self.set_ffmpeg_advanced_options(**advanced_kwargs) def scale_resolution(self, stream_info: dict): def get_test_resolution(settings): @@ -157,6 +146,12 @@ def build_filter_chain(self, stream_info, stream_id): software_filters = [] hardware_filters = [] + # Load encoder classes + libx_encoder = LibxEncoder(self.settings) + qsv_encoder = QsvEncoder(self.settings) + vaapi_encoder = VaapiEncoder(self.settings) + nvenc_encoder = NvencEncoder(self.settings) + # Apply smart filters first required_hw_smart_filters = [] if self.settings.get_setting('apply_smart_filters'): @@ -166,9 +161,9 @@ def build_filter_chain(self, stream_info, stream_id): vid_width, vid_height = self.scale_resolution(stream_info) if vid_width: # Apply scale with only width to keep aspect ratio - if self.settings.get_setting('video_encoder') in QsvEncoder.provides: + if self.settings.get_setting('video_encoder') in qsv_encoder.provides(): required_hw_smart_filters.append({'scale': [vid_width, vid_height]}) - elif self.settings.get_setting('video_encoder') in NvencEncoder.provides: + elif self.settings.get_setting('video_encoder') in nvenc_encoder.provides(): required_hw_smart_filters.append({'scale': [vid_width, vid_height]}) else: software_filters.append('scale={}:-1'.format(vid_width)) @@ -180,23 +175,23 @@ def build_filter_chain(self, stream_info, stream_id): software_filters.append(software_filter.strip()) # Check for hardware encoders that required video filters - if self.settings.get_setting('video_encoder') in QsvEncoder.provides: + if self.settings.get_setting('video_encoder') in qsv_encoder.provides(): # Add filtergraph required for using QSV encoding - generic_kwargs, advanced_kwargs, filter_args = QsvEncoder.generate_filtergraphs(self.settings, software_filters, - required_hw_smart_filters) + generic_kwargs, advanced_kwargs, filter_args = qsv_encoder.generate_filtergraphs(self.settings, software_filters, + required_hw_smart_filters) self.set_ffmpeg_generic_options(**generic_kwargs) self.set_ffmpeg_advanced_options(**advanced_kwargs) hardware_filters += filter_args - elif self.settings.get_setting('video_encoder') in VaapiEncoder.provides: + elif self.settings.get_setting('video_encoder') in vaapi_encoder.provides(): # Add filtergraph required for using VAAPI encoding - hardware_filters += VaapiEncoder.generate_filtergraphs() + hardware_filters += vaapi_encoder.generate_filtergraphs() # If we are using software filters, then disable vaapi surfaces. # Instead, output software frames if software_filters: self.set_ffmpeg_generic_options(**{'-hwaccel_output_format': 'nv12'}) - elif self.settings.get_setting('video_encoder') in NvencEncoder.provides: + elif self.settings.get_setting('video_encoder') in nvenc_encoder.provides(): # Add filtergraph required for using CUDA encoding - hardware_filters += NvencEncoder.generate_filtergraphs(software_filters, required_hw_smart_filters) + hardware_filters += nvenc_encoder.generate_filtergraphs(software_filters, required_hw_smart_filters) # If we are using software filters, then disable cuda surfaces. # Instead, output software frames if software_filters: @@ -319,18 +314,20 @@ def custom_stream_mapping(self, stream_info: dict, stream_id: int): '-c:{}'.format(stream_specifier), self.settings.get_setting('video_encoder'), ] + # Load encoder classes + libx_encoder = LibxEncoder(self.settings) + qsv_encoder = QsvEncoder(self.settings) + vaapi_encoder = VaapiEncoder(self.settings) + nvenc_encoder = NvencEncoder(self.settings) + # Add encoder args - if self.settings.get_setting('video_encoder') in LibxEncoder.provides: - qsv_encoder = LibxEncoder(self.settings) - stream_encoding += qsv_encoder.args(stream_id) - elif self.settings.get_setting('video_encoder') in QsvEncoder.provides: - qsv_encoder = QsvEncoder(self.settings) + if self.settings.get_setting('video_encoder') in libx_encoder.provides(): + stream_encoding += libx_encoder.args(stream_id) + elif self.settings.get_setting('video_encoder') in qsv_encoder.provides(): stream_encoding += qsv_encoder.args(stream_id) - elif self.settings.get_setting('video_encoder') in VaapiEncoder.provides: - vaapi_encoder = VaapiEncoder(self.settings) + elif self.settings.get_setting('video_encoder') in vaapi_encoder.provides(): stream_encoding += vaapi_encoder.args(stream_id) - elif self.settings.get_setting('video_encoder') in NvencEncoder.provides: - nvenc_encoder = NvencEncoder(self.settings) + elif self.settings.get_setting('video_encoder') in nvenc_encoder.provides(): generic_kwargs, stream_encoding_args = nvenc_encoder.args(stream_info, stream_id) self.set_ffmpeg_generic_options(**generic_kwargs) stream_encoding += stream_encoding_args diff --git a/source/video_transcoder/plugin.py b/source/video_transcoder/plugin.py index 32f1a9ec1..77b45441e 100644 --- a/source/video_transcoder/plugin.py +++ b/source/video_transcoder/plugin.py @@ -96,9 +96,10 @@ def __available_encoders(self): VaapiEncoder, NvencEncoder, ] - for encoder_lib in encoder_libs: - for encoder in encoder_lib.provides: - return_encoders[encoder] = encoder_lib(self) + for encoder_class in encoder_libs: + encoder_lib = encoder_class(self) + for encoder in encoder_lib.provides(): + return_encoders[encoder] = encoder_lib return return_encoders def __encoder_settings_object(self): @@ -110,10 +111,10 @@ def __encoder_settings_object(self): # Initial options forces the order they appear in the settings list # We need this because some encoders have settings that # Fetch all encoder settings from encoder libs - libx_options = LibxEncoder.options() - qsv_options = QsvEncoder.options() - vaapi_options = VaapiEncoder.options() - nvenc_options = NvencEncoder.options() + libx_options = LibxEncoder(self.settings).options() + qsv_options = QsvEncoder(self.settings).options() + vaapi_options = VaapiEncoder(self.settings).options() + nvenc_options = NvencEncoder(self.settings).options() return { **libx_options, **qsv_options,