From c0fe11c8cfb74197ddabe7e9dd366ec6eb764a17 Mon Sep 17 00:00:00 2001 From: Matt Lee Date: Fri, 25 Jan 2019 11:04:47 +0100 Subject: [PATCH 01/11] Initial Commit to add new Per Title Stream Mode --- bitmovin/resources/enums/stream_mode.py | 1 + .../resources/models/encodings/__init__.py | 2 +- .../models/encodings/pertitle/__init__.py | 2 ++ ...am_per_title_fixed_res_bitrate_settings.py | 15 ++++++++ .../pertitle/stream_per_title_settings.py | 36 +++++++++++++++++++ bitmovin/resources/models/encodings/stream.py | 25 +++++++++++-- 6 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 bitmovin/resources/models/encodings/pertitle/stream_per_title_fixed_res_bitrate_settings.py create mode 100644 bitmovin/resources/models/encodings/pertitle/stream_per_title_settings.py diff --git a/bitmovin/resources/enums/stream_mode.py b/bitmovin/resources/enums/stream_mode.py index 6020619..c0a5d14 100644 --- a/bitmovin/resources/enums/stream_mode.py +++ b/bitmovin/resources/enums/stream_mode.py @@ -6,3 +6,4 @@ class StreamMode(Enum): STANDARD = 'STANDARD' PER_TITLE_RESULT = 'PER_TITLE_RESULT' PER_TITLE_TEMPLATE_FIXED_RESOLUTION = 'PER_TITLE_TEMPLATE_FIXED_RESOLUTION' + PER_TITLE_TEMPLATE_FIXED_RESOLUTION_AND_BITRATE = 'PER_TITLE_TEMPLATE_FIXED_RESOLUTION_AND_BITRATE' diff --git a/bitmovin/resources/models/encodings/__init__.py b/bitmovin/resources/models/encodings/__init__.py index cf03058..4e96ead 100644 --- a/bitmovin/resources/models/encodings/__init__.py +++ b/bitmovin/resources/models/encodings/__init__.py @@ -27,7 +27,7 @@ from .conditions import ConditionType, Condition, AndConjunction, OrConjunction from .stream_metadata import StreamMetadata from .pertitle import PerTitle, PerTitleConfiguration, H264PerTitleConfiguration, H265PerTitleConfiguration, \ - VP9PerTitleConfiguration, AutoRepresentation + VP9PerTitleConfiguration, AutoRepresentation, StreamPerTitleSettings, StreamPerTitleFixedResolutionAndBitrateSettings from .captions import BurnInSrtSubtitle from .encoding_input import EncodingInput from .inputstreams import IngestInputStream, ConcatenationInputStreamConfiguration, ConcatenationInputStream, \ diff --git a/bitmovin/resources/models/encodings/pertitle/__init__.py b/bitmovin/resources/models/encodings/pertitle/__init__.py index facf274..408d338 100644 --- a/bitmovin/resources/models/encodings/pertitle/__init__.py +++ b/bitmovin/resources/models/encodings/pertitle/__init__.py @@ -4,3 +4,5 @@ from .h265_per_title_configuration import H265PerTitleConfiguration from .vp9_per_title_configuration import VP9PerTitleConfiguration from .per_title_configuration import PerTitleConfiguration +from .stream_per_title_fixed_res_bitrate_settings import StreamPerTitleFixedResolutionAndBitrateSettings +from .stream_per_title_settings import StreamPerTitleSettings diff --git a/bitmovin/resources/models/encodings/pertitle/stream_per_title_fixed_res_bitrate_settings.py b/bitmovin/resources/models/encodings/pertitle/stream_per_title_fixed_res_bitrate_settings.py new file mode 100644 index 0000000..3c17ce9 --- /dev/null +++ b/bitmovin/resources/models/encodings/pertitle/stream_per_title_fixed_res_bitrate_settings.py @@ -0,0 +1,15 @@ +from bitmovin.utils import Serializable + + +class StreamPerTitleFixedResolutionAndBitrateSettings(Serializable): + def __init__(self, min_bitrate=None, max_bitrate=None): + self.minBitrate = min_bitrate + self.maxBitrate = max_bitrate + + @classmethod + def parse_from_json_object(cls, json_object): + min_bitrate = json_object.get('minBitrate') + max_bitrate = json_object.get('maxBitrate') + stream_per_title_fixed_resolution_and_bitrate_settings = StreamPerTitleFixedResolutionAndBitrateSettings(min_bitrate=min_bitrate,max_bitrate=max_bitrate) + return stream_per_title_fixed_resolution_and_bitrate_settings + \ No newline at end of file diff --git a/bitmovin/resources/models/encodings/pertitle/stream_per_title_settings.py b/bitmovin/resources/models/encodings/pertitle/stream_per_title_settings.py new file mode 100644 index 0000000..400ce0a --- /dev/null +++ b/bitmovin/resources/models/encodings/pertitle/stream_per_title_settings.py @@ -0,0 +1,36 @@ +from bitmovin.errors import InvalidTypeError +from bitmovin.utils import Serializable +from .stream_per_title_fixed_res_bitrate_settings import StreamPerTitleFixedResolutionAndBitrateSettings + + +class StreamPerTitleSettings(Serializable): + def __init__(self, fixed_resolution_and_bitrate_settings=None): + super().__init__() + self._fixedResolutionAndBitrateSettings = None + self.fixedResolutionAndBitrateSettings = fixed_resolution_and_bitrate_settings + + @property + def fixedResolutionAndBitrateSettings(self): + return self._fixedResolutionAndBitrateSettings + + @fixedResolutionAndBitrateSettings.setter + def fixedResolutionAndBitrateSettings(self, new_fixed_resolution_and_bitrate_settings): + if new_fixed_resolution_and_bitrate_settings is None: + self._fixedResolutionAndBitrateSettings = None + return + + if isinstance(new_fixed_resolution_and_bitrate_settings, StreamPerTitleFixedResolutionAndBitrateSettings): + self._fixedResolutionAndBitrateSettings = new_fixed_resolution_and_bitrate_settings + else: + self._fixedResolutionAndBitrateSettings = StreamPerTitleFixedResolutionAndBitrateSettings.parse_from_json_object(new_fixed_resolution_and_bitrate_settings) + + @classmethod + def parse_from_json_object(cls, json_object): + fixed_resolution_and_bitrate_settings = json_object.get('fixedResolutionAndBitrateSettings') + stream_per_title_settings = StreamPerTitleSettings(fixed_resolution_and_bitrate_settings=fixed_resolution_and_bitrate_settings) + return stream_per_title_settings + + def serialize(self): + serialized = super().serialize() + serialized['fixedResolutionAndBitrateSettings'] = self.fixedResolutionAndBitrateSettings + return serialized diff --git a/bitmovin/resources/models/encodings/stream.py b/bitmovin/resources/models/encodings/stream.py index c87b3e9..4638db6 100644 --- a/bitmovin/resources/models/encodings/stream.py +++ b/bitmovin/resources/models/encodings/stream.py @@ -1,6 +1,7 @@ from bitmovin.errors import InvalidTypeError from bitmovin.resources import AbstractNameDescriptionResource from bitmovin.resources.models import AbstractModel +from bitmovin.resources.models.encodings.pertitle import StreamPerTitleSettings from bitmovin.utils import Serializable from bitmovin.resources.enums import StreamMode, StreamDecodingErrorMode from .encoding_output import EncodingOutput @@ -11,10 +12,11 @@ from .stream_metadata import StreamMetadata + class Stream(AbstractNameDescriptionResource, AbstractModel, Serializable): def __init__(self, codec_configuration_id, input_streams=None, outputs=None, id_=None, custom_data=None, name=None, description=None, conditions=None, ignored_by=None, metadata=None, mode=None, - decoding_error_mode=None): + decoding_error_mode=None, per_title_settings=None): super().__init__(id_=id_, custom_data=custom_data, name=name, description=description) self._inputStreams = None self._outputs = None @@ -37,6 +39,8 @@ def __init__(self, codec_configuration_id, input_streams=None, outputs=None, id_ self.mode = mode self._decodingErrorMode = None self.decodingErrorMode = decoding_error_mode + self._perTitleSettings = None + self.perTitleSettings = per_title_settings @classmethod def parse_from_json_object(cls, json_object): @@ -52,11 +56,12 @@ def parse_from_json_object(cls, json_object): metadata = json_object.get('metadata') mode = json_object.get('mode') decoding_error_mode = json_object.get('decoding_error_mode') + per_title_settings = json_object.get('perTitleSettings') stream = Stream(id_=id_, custom_data=custom_data, codec_configuration_id=codec_configuration_id, input_streams=input_streams, outputs=outputs, name=name, description=description, conditions=conditions, ignored_by=ignored_by, - metadata=metadata, mode=mode, decoding_error_mode=decoding_error_mode) + metadata=metadata, mode=mode, decoding_error_mode=decoding_error_mode, per_title_settings=per_title_settings) return stream @property @@ -188,6 +193,21 @@ def decodingErrorMode(self, new_decoding_error_mode): type(new_decoding_error_mode)) ) + @property + def perTitleSettings(self): + return self._perTitleSettings + + @perTitleSettings.setter + def perTitleSettings(self, new_per_title_settings): + if new_per_title_settings is None: + self._perTitleSettings = None + return + + if isinstance(new_per_title_settings, StreamPerTitleSettings): + self._perTitleSettings = new_per_title_settings + else: + self._perTitleSettings = StreamPerTitleSettings.parse_from_json_object(new_per_title_settings) + def serialize(self): serialized = super().serialize() serialized['inputStreams'] = self.inputStreams @@ -196,4 +216,5 @@ def serialize(self): serialized['mode'] = self.mode serialized['metadata'] = self.metadata serialized['decodingErrorMode'] = self.decodingErrorMode + serialized['perTitleSettings'] = self.perTitleSettings return serialized From 5f47084c7bbbbf703805d3aa7ffa368ac599dcca Mon Sep 17 00:00:00 2001 From: Matt Lee Date: Fri, 25 Jan 2019 16:00:17 +0100 Subject: [PATCH 02/11] Added new modes to Per-Title Start Settings --- bitmovin/resources/enums/__init__.py | 1 + ...resolution_and_bitrate_calculation_mode.py | 8 +++++ .../resources/models/encodings/__init__.py | 3 +- .../models/encodings/pertitle/__init__.py | 1 + ...ed_resolution_and_bitrate_configuration.py | 32 +++++++++++++++++++ .../pertitle/h264_per_title_configuration.py | 4 +-- .../pertitle/per_title_configuration.py | 24 +++++++++++++- 7 files changed, 69 insertions(+), 4 deletions(-) create mode 100644 bitmovin/resources/enums/per_title_fixed_resolution_and_bitrate_calculation_mode.py create mode 100644 bitmovin/resources/models/encodings/pertitle/fixed_resolution_and_bitrate_configuration.py diff --git a/bitmovin/resources/enums/__init__.py b/bitmovin/resources/enums/__init__.py index 2a83780..d9fed4b 100644 --- a/bitmovin/resources/enums/__init__.py +++ b/bitmovin/resources/enums/__init__.py @@ -73,3 +73,4 @@ from .h265_adaptive_quantization_mode import H265AdaptiveQuantizationMode from .hls_version import HlsVersion from .stream_conditions_mode import StreamConditionsMode +from .per_title_fixed_resolution_and_bitrate_calculation_mode import PerTitleFixedResolutionAndBitrateCalculationMode diff --git a/bitmovin/resources/enums/per_title_fixed_resolution_and_bitrate_calculation_mode.py b/bitmovin/resources/enums/per_title_fixed_resolution_and_bitrate_calculation_mode.py new file mode 100644 index 0000000..f4aa42d --- /dev/null +++ b/bitmovin/resources/enums/per_title_fixed_resolution_and_bitrate_calculation_mode.py @@ -0,0 +1,8 @@ +from enum import Enum + + +class PerTitleFixedResolutionAndBitrateCalculationMode(Enum): + LAST_CALCULATED_BITRATE = 'LAST_CALCULATED_BITRATE' + MINIMUM = 'MINIMUM' + MAXIMUM = 'MAXIMUM' + AVERAGE = 'AVERAGE' diff --git a/bitmovin/resources/models/encodings/__init__.py b/bitmovin/resources/models/encodings/__init__.py index 4e96ead..108c4f0 100644 --- a/bitmovin/resources/models/encodings/__init__.py +++ b/bitmovin/resources/models/encodings/__init__.py @@ -27,7 +27,8 @@ from .conditions import ConditionType, Condition, AndConjunction, OrConjunction from .stream_metadata import StreamMetadata from .pertitle import PerTitle, PerTitleConfiguration, H264PerTitleConfiguration, H265PerTitleConfiguration, \ - VP9PerTitleConfiguration, AutoRepresentation, StreamPerTitleSettings, StreamPerTitleFixedResolutionAndBitrateSettings + VP9PerTitleConfiguration, AutoRepresentation, StreamPerTitleSettings, StreamPerTitleFixedResolutionAndBitrateSettings, \ + PerTitleFixedResolutionAndBitrateConfiguration from .captions import BurnInSrtSubtitle from .encoding_input import EncodingInput from .inputstreams import IngestInputStream, ConcatenationInputStreamConfiguration, ConcatenationInputStream, \ diff --git a/bitmovin/resources/models/encodings/pertitle/__init__.py b/bitmovin/resources/models/encodings/pertitle/__init__.py index 408d338..16f18a5 100644 --- a/bitmovin/resources/models/encodings/pertitle/__init__.py +++ b/bitmovin/resources/models/encodings/pertitle/__init__.py @@ -6,3 +6,4 @@ from .per_title_configuration import PerTitleConfiguration from .stream_per_title_fixed_res_bitrate_settings import StreamPerTitleFixedResolutionAndBitrateSettings from .stream_per_title_settings import StreamPerTitleSettings +from .fixed_resolution_and_bitrate_configuration import PerTitleFixedResolutionAndBitrateConfiguration diff --git a/bitmovin/resources/models/encodings/pertitle/fixed_resolution_and_bitrate_configuration.py b/bitmovin/resources/models/encodings/pertitle/fixed_resolution_and_bitrate_configuration.py new file mode 100644 index 0000000..69d9da9 --- /dev/null +++ b/bitmovin/resources/models/encodings/pertitle/fixed_resolution_and_bitrate_configuration.py @@ -0,0 +1,32 @@ +from bitmovin.utils import Serializable +from bitmovin.resources.enums import PerTitleFixedResolutionAndBitrateCalculationMode + +class PerTitleFixedResolutionAndBitrateConfiguration(Serializable): + def __init__(self, forced_renditions, forced_rendition_factor, forced_rendition_calculation_mode): + self.forcedRenditionAboveHighestFixedRepresentation = forced_renditions + self.forcedRenditionAboveHighestFixedRepresentationFactor = forced_rendition_factor + + self._forced_rendition_calculation_mode = None + self.forcedRenditionAboveHighestFixedRepresentationCalculationMode = forced_rendition_calculation_mode + + @property + def forcedRenditionAboveHighestFixedRepresentationCalculationMode(self): + return self._forced_rendition_calculation_mode + + @forcedRenditionAboveHighestFixedRepresentationCalculationMode.setter + def forcedRenditionAboveHighestFixedRepresentationCalculationMode(self, new_forced_rendition_calculation_mode): + if new_forced_rendition_calculation_mode is None: + self._forced_rendition_calculation_mode = None + return + if isinstance(new_forced_rendition_calculation_mode, str): + self._forced_rendition_calculation_mode = new_forced_rendition_calculation_mode + elif isinstance(new_forced_rendition_calculation_mode, PerTitleFixedResolutionAndBitrateCalculationMode): + self._forced_rendition_calculation_mode = new_forced_rendition_calculation_mode.value + else: + raise InvalidTypeError( + 'Invalid type {} for forcedRenditionAboveHighestFixedRepresentationCalculationMode: must be either str or PerTitleFixedResolutionAndBitrateCalculationMode!'.format(type(new_forced_rendition_calculation_mode))) + + def serialize(self): + serialized = super().serialize() + serialized['forcedRenditionAboveHighestFixedRepresentationCalculationMode'] = self.forcedRenditionAboveHighestFixedRepresentationCalculationMode + return serialized \ No newline at end of file diff --git a/bitmovin/resources/models/encodings/pertitle/h264_per_title_configuration.py b/bitmovin/resources/models/encodings/pertitle/h264_per_title_configuration.py index 8aff1f2..bf5c29f 100644 --- a/bitmovin/resources/models/encodings/pertitle/h264_per_title_configuration.py +++ b/bitmovin/resources/models/encodings/pertitle/h264_per_title_configuration.py @@ -5,10 +5,10 @@ class H264PerTitleConfiguration(PerTitleConfiguration): def __init__(self, min_bitrate=None, max_bitrate=None, min_bitrate_step_size=None, max_bitrate_step_size=None, target_quality_crf=None, auto_representations=None, codec_min_bitrate_factor=None, codec_max_bitrate_factor=None, codec_bufsize_factor=None, - complexity_factor=None): + complexity_factor=None, fixed_resolution_and_bitrate_configuration=None): super().__init__(min_bitrate=min_bitrate, max_bitrate=max_bitrate, min_bitrate_step_size=min_bitrate_step_size, max_bitrate_step_size=max_bitrate_step_size, auto_representations=auto_representations, - complexity_factor=complexity_factor) + complexity_factor=complexity_factor, fixed_resolution_and_bitrate_configuration=fixed_resolution_and_bitrate_configuration) self.targetQualityCrf = target_quality_crf self.codecMinBitrateFactor = codec_min_bitrate_factor self.codecMaxBitrateFactor = codec_max_bitrate_factor diff --git a/bitmovin/resources/models/encodings/pertitle/per_title_configuration.py b/bitmovin/resources/models/encodings/pertitle/per_title_configuration.py index 5a32211..7b58d74 100644 --- a/bitmovin/resources/models/encodings/pertitle/per_title_configuration.py +++ b/bitmovin/resources/models/encodings/pertitle/per_title_configuration.py @@ -1,11 +1,12 @@ from bitmovin.errors import InvalidTypeError from bitmovin.utils import Serializable from .auto_representation import AutoRepresentation +from .fixed_resolution_and_bitrate_configuration import PerTitleFixedResolutionAndBitrateConfiguration class PerTitleConfiguration(Serializable): def __init__(self, min_bitrate=None, max_bitrate=None, min_bitrate_step_size=None, max_bitrate_step_size=None, - auto_representations=None, complexity_factor=None): + auto_representations=None, complexity_factor=None, fixed_resolution_and_bitrate_configuration=None): super().__init__() self.minBitrate = min_bitrate self.maxBitrate = max_bitrate @@ -15,6 +16,9 @@ def __init__(self, min_bitrate=None, max_bitrate=None, min_bitrate_step_size=Non self._auto_representations = None self.autoRepresentations = auto_representations + + self._fixedResolutionAndBitrateConfiguration = None + self.fixedResolutionAndBitrateConfiguration = fixed_resolution_and_bitrate_configuration @property def autoRepresentations(self): @@ -33,7 +37,25 @@ def autoRepresentations(self, new_auto_representations): self._auto_representations = new_auto_representations + @property + def fixedResolutionAndBitrateConfiguration(self): + return self._fixedResolutionAndBitrateConfiguration + + @fixedResolutionAndBitrateConfiguration.setter + def fixedResolutionAndBitrateConfiguration(self, new_fixed_resolution_and_bitrate_configuration): + if new_fixed_resolution_and_bitrate_configuration is None: + self._fixedResolutionAndBitrateConfiguration = None + return + + if not isinstance(new_fixed_resolution_and_bitrate_configuration, PerTitleFixedResolutionAndBitrateConfiguration): + raise InvalidTypeError( + 'Invalid type {} for fixed_resolution_and_bitrate_configuration: must be PerTitleFixedResolutionAndBitrateConfiguration!'.format( + type(new_fixed_resolution_and_bitrate_configuration))) + + self._fixedResolutionAndBitrateConfiguration = new_fixed_resolution_and_bitrate_configuration + def serialize(self): serialized = super().serialize() serialized['autoRepresentations'] = self.autoRepresentations + serialized['fixedResolutionAndBitrateConfiguration'] = self.fixedResolutionAndBitrateConfiguration return serialized From 12f33bc627b6fa467d0a7f2547ae56417a3f4c8f Mon Sep 17 00:00:00 2001 From: Matthew Lee Date: Tue, 29 Jan 2019 14:09:56 +0800 Subject: [PATCH 03/11] Add Fixed Resolution and Bitrate to H265 --- .../models/encodings/pertitle/h265_per_title_configuration.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bitmovin/resources/models/encodings/pertitle/h265_per_title_configuration.py b/bitmovin/resources/models/encodings/pertitle/h265_per_title_configuration.py index 21d09dd..1729303 100644 --- a/bitmovin/resources/models/encodings/pertitle/h265_per_title_configuration.py +++ b/bitmovin/resources/models/encodings/pertitle/h265_per_title_configuration.py @@ -5,10 +5,10 @@ class H265PerTitleConfiguration(PerTitleConfiguration): def __init__(self, min_bitrate=None, max_bitrate=None, min_bitrate_step_size=None, max_bitrate_step_size=None, target_quality_crf=None, auto_representations=None, codec_min_bitrate_factor=None, codec_max_bitrate_factor=None, codec_bufsize_factor=None, - complexity_factor=None): + complexity_factor=None, fixed_resolution_and_bitrate_configuration=None): super().__init__(min_bitrate=min_bitrate, max_bitrate=max_bitrate, min_bitrate_step_size=min_bitrate_step_size, max_bitrate_step_size=max_bitrate_step_size, auto_representations=auto_representations, - complexity_factor=complexity_factor) + complexity_factor=complexity_factor, fixed_resolution_and_bitrate_configuration=fixed_resolution_and_bitrate_configuration) self.targetQualityCrf = target_quality_crf self.codecMinBitrateFactor = codec_min_bitrate_factor self.codecMaxBitrateFactor = codec_max_bitrate_factor From 83cb4c7c5a59fbf390cab5fe85be41deacc90b9f Mon Sep 17 00:00:00 2001 From: Matthew Lee Date: Wed, 13 Feb 2019 20:29:10 +0800 Subject: [PATCH 04/11] Added Complexity Optimised Settings --- bitmovin/resources/enums/__init__.py | 1 + .../resources/enums/bitrate_selection_mode.py | 6 +++ ...am_per_title_fixed_res_bitrate_settings.py | 39 +++++++++++++++++-- 3 files changed, 43 insertions(+), 3 deletions(-) create mode 100644 bitmovin/resources/enums/bitrate_selection_mode.py diff --git a/bitmovin/resources/enums/__init__.py b/bitmovin/resources/enums/__init__.py index d9fed4b..1116fd3 100644 --- a/bitmovin/resources/enums/__init__.py +++ b/bitmovin/resources/enums/__init__.py @@ -74,3 +74,4 @@ from .hls_version import HlsVersion from .stream_conditions_mode import StreamConditionsMode from .per_title_fixed_resolution_and_bitrate_calculation_mode import PerTitleFixedResolutionAndBitrateCalculationMode +from .bitrate_selection_mode import BitrateSelectionMode diff --git a/bitmovin/resources/enums/bitrate_selection_mode.py b/bitmovin/resources/enums/bitrate_selection_mode.py new file mode 100644 index 0000000..c3c00f0 --- /dev/null +++ b/bitmovin/resources/enums/bitrate_selection_mode.py @@ -0,0 +1,6 @@ +import enum + + +class BitrateSelectionMode(enum.Enum): + OPTIMIZED = 'OPTIMIZED' + COMPLEXITY_RANGE = 'COMPLEXITY_RANGE' diff --git a/bitmovin/resources/models/encodings/pertitle/stream_per_title_fixed_res_bitrate_settings.py b/bitmovin/resources/models/encodings/pertitle/stream_per_title_fixed_res_bitrate_settings.py index 3c17ce9..4bd2f11 100644 --- a/bitmovin/resources/models/encodings/pertitle/stream_per_title_fixed_res_bitrate_settings.py +++ b/bitmovin/resources/models/encodings/pertitle/stream_per_title_fixed_res_bitrate_settings.py @@ -1,15 +1,48 @@ from bitmovin.utils import Serializable - +from bitmovin.resources.enums import BitrateSelectionMode class StreamPerTitleFixedResolutionAndBitrateSettings(Serializable): - def __init__(self, min_bitrate=None, max_bitrate=None): + def __init__(self, min_bitrate=None, max_bitrate=None, selection_mode=None, low_complexity_boundary=None, high_complexity_boundary=None): self.minBitrate = min_bitrate self.maxBitrate = max_bitrate + self._bitrateSelectionMode = None + self.bitrateSelectionMode = selection_mode + self.lowComplexityBoundaryForMaxBitrate = low_complexity_boundary + self.highComplexityBoundaryForMaxBitrate = high_complexity_boundary + + @property + def bitrateSelectionMode(self): + return self._bitrateSelectionMode + + @bitrateSelectionMode.setter + def bitrateSelectionMode(self, new_selection_mode): + if new_selection_mode is None: + self._bitrateSelectionMode = None + return + if isinstance(new_selection_mode, str): + self._bitrateSelectionMode = new_selection_mode + elif isinstance(new_selection_mode, BitrateSelectionMode): + self._bitrateSelectionMode = new_selection_mode.value + else: + raise InvalidTypeError( + 'Invalid type {} for bitrateSelectionMode: must be either str or BitrateSelectionMode!'.format(type(new_forced_rendition_calculation_mode))) @classmethod def parse_from_json_object(cls, json_object): min_bitrate = json_object.get('minBitrate') max_bitrate = json_object.get('maxBitrate') - stream_per_title_fixed_resolution_and_bitrate_settings = StreamPerTitleFixedResolutionAndBitrateSettings(min_bitrate=min_bitrate,max_bitrate=max_bitrate) + selection_mode = json_object.get('bitrateSelectionMode') + low_complexity_boundary = json_object.get('lowComplexityBoundaryForMaxBitrate') + high_complexity_boundary = json_object.get('highComplexityBoundaryForMaxBitrate') + stream_per_title_fixed_resolution_and_bitrate_settings = StreamPerTitleFixedResolutionAndBitrateSettings(min_bitrate=min_bitrate, + max_bitrate=max_bitrate, + selection_mode=selection_mode, + low_complexity_boundary=low_complexity_boundary, + high_complexity_boundary=high_complexity_boundary) return stream_per_title_fixed_resolution_and_bitrate_settings + + def serialize(self): + serialized = super().serialize() + serialized['bitrateSelectionMode'] = self.bitrateSelectionMode + return serialized \ No newline at end of file From e044a30fe519bff8d8c70dbf5a4d19456a555e85 Mon Sep 17 00:00:00 2001 From: Matt Lee Date: Fri, 15 Mar 2019 18:34:28 +0800 Subject: [PATCH 05/11] Added SAR Values --- bitmovin/resources/models/filters/scale_filter.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/bitmovin/resources/models/filters/scale_filter.py b/bitmovin/resources/models/filters/scale_filter.py index e5cdf13..13759bb 100644 --- a/bitmovin/resources/models/filters/scale_filter.py +++ b/bitmovin/resources/models/filters/scale_filter.py @@ -9,12 +9,14 @@ class ScaleFilter(AbstractFilter, Serializable): def __init__(self, name=None, width=None, height=None, scaling_algorithm=None, id_=None, custom_data=None, - description=None): + description=None, sample_aspect_ratio_numerator=None, sample_aspect_ratio_denominator=None): super().__init__(id_=id_, custom_data=custom_data, name=name, description=description) self.width = width self.height = height self._scaling_algorithm = None self.scalingAlgorithm = scaling_algorithm + self.sampleAspectRatioNumerator = sample_aspect_ratio_numerator + self.sampleAspectRatioDenominator = sample_aspect_ratio_denominator @property def scalingAlgorithm(self): @@ -47,12 +49,16 @@ def parse_from_json_object(cls, json_object): height = json_object.get('height') name = json_object.get('name') description = json_object.get('description') + sample_aspect_ratio_numerator = json_object.get('sampleAspectRatioNumerator') + sample_aspect_ratio_denominator = json_object.get('sampleAspectRatioDenominator') scale_filter = ScaleFilter( name=name, width=width, height=height, scaling_algorithm=scaling_algorithm, id_=id_, - description=description + description=description, + sample_aspect_ratio_numerator=sample_aspect_ratio_numerator, + sample_aspect_ratio_denominator=sample_aspect_ratio_denominator ) return scale_filter From 7d56213b68ac566e19d31ad8117cbf531ed0e230 Mon Sep 17 00:00:00 2001 From: Matthew Lee Date: Sun, 24 Mar 2019 01:23:51 +0800 Subject: [PATCH 06/11] Add Per Title Fixed Resolution and Bitrate Settings to VP9 --- .../models/encodings/pertitle/vp9_per_title_configuration.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bitmovin/resources/models/encodings/pertitle/vp9_per_title_configuration.py b/bitmovin/resources/models/encodings/pertitle/vp9_per_title_configuration.py index 852445d..3642ea5 100644 --- a/bitmovin/resources/models/encodings/pertitle/vp9_per_title_configuration.py +++ b/bitmovin/resources/models/encodings/pertitle/vp9_per_title_configuration.py @@ -4,8 +4,8 @@ class VP9PerTitleConfiguration(PerTitleConfiguration): def __init__(self, min_bitrate=None, max_bitrate=None, min_bitrate_step_size=None, max_bitrate_step_size=None, target_quality_crf=None, auto_representations=None, - complexity_factor=None): + complexity_factor=None, fixed_resolution_and_bitrate_configuration=None): super().__init__(min_bitrate=min_bitrate, max_bitrate=max_bitrate, min_bitrate_step_size=min_bitrate_step_size, max_bitrate_step_size=max_bitrate_step_size, auto_representations=auto_representations, - complexity_factor=complexity_factor) + complexity_factor=complexity_factor, fixed_resolution_and_bitrate_configuration=fixed_resolution_and_bitrate_configuration) self.targetQualityCrf = target_quality_crf From 4c7a749912c0e85df1a60eba6759353e6165836a Mon Sep 17 00:00:00 2001 From: Matthew Lee Date: Mon, 25 Mar 2019 12:24:44 +0800 Subject: [PATCH 07/11] Added Color Information to VP9 --- .../vp9_codec_configuration.py | 54 +++++++++++++++++-- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/bitmovin/resources/models/codecconfigurations/vp9_codec_configuration.py b/bitmovin/resources/models/codecconfigurations/vp9_codec_configuration.py index b3ea923..ecd6519 100644 --- a/bitmovin/resources/models/codecconfigurations/vp9_codec_configuration.py +++ b/bitmovin/resources/models/codecconfigurations/vp9_codec_configuration.py @@ -5,7 +5,7 @@ from bitmovin.utils import Serializable from .video_codec_configuration import VideoCodecConfiguration - +from .color_config import ColorConfig class VP9CodecConfiguration(VideoCodecConfiguration, Serializable): @@ -15,7 +15,7 @@ def __init__(self, name, bitrate, rate=None, id_=None, description=None, custom_ rate_overshoot_pct=None, cpu_used=None, noise_sensitivity=None, quality=None, lossless=None, static_thresh=None, aq_mode=None, arnr_max_frames=None, arnr_strength=None, arnr_type=None, pixel_format=None, min_gop=None, max_gop=None, - min_keyframe_interval=None, max_keyframe_interval=None): + min_keyframe_interval=None, max_keyframe_interval=None, color_config=None): super().__init__(id_=id_, custom_data=custom_data, name=name, description=description, bitrate=bitrate, rate=rate, width=width, height=height, pixel_format=pixel_format) @@ -46,6 +46,8 @@ def __init__(self, name, bitrate, rate=None, id_=None, description=None, custom_ self.maxGop = max_gop self.minKeyframeInterval = min_keyframe_interval self.maxKeyframeInterval = max_keyframe_interval + self._colorConfig = None + self.colorConfig = color_config @property def quality(self): @@ -95,6 +97,19 @@ def arnrType(self, new_arnrType): raise InvalidTypeError( 'Invalid type {} for arnrType: must be either str or VP9ARNRType!'.format(type(new_arnrType))) + @property + def colorConfig(self): + return self._colorConfig + + @colorConfig.setter + def colorConfig(self, new_color_config): + if new_color_config is None: + self._colorConfig = None + elif isinstance(new_color_config, ColorConfig): + self._colorConfig = new_color_config + else: + raise InvalidTypeError('colorConfig has to be of type ColorConfig') + @classmethod def parse_from_json_object(cls, json_object): video_codec_configuration = VideoCodecConfiguration.parse_from_json_object(json_object=json_object) @@ -132,7 +147,33 @@ def parse_from_json_object(cls, json_object): max_gop = json_object.get('maxGop') min_keyframe_interval = json_object.get('minKeyframeInterval') max_keyframe_interval = json_object.get('maxKeyframeInterval') - + + color_config = None + color_config_json = json_object.get('colorConfig') + if color_config_json is not None: + copy_chroma_location_flag = color_config_json.get('copyChromaLocationFlag') + copy_color_space_flag = color_config_json.get('copyColorSpaceFlag') + copy_color_primaries_flag = color_config_json.get('copyColorPrimariesFlag') + copy_color_range_flag = color_config_json.get('copyColorRangeFlag') + copy_color_transfer_flag = color_config_json.get('copyColorTransferFlag') + chroma_location = color_config_json.get('chromaLocation') + color_space = color_config_json.get('colorSpace') + color_primaries = color_config_json.get('colorPrimaries') + color_range = color_config_json.get('colorRange') + color_transfer = color_config_json.get('colorTransfer') + input_color_space = color_config_json.get('inputColorSpace') + input_color_range = color_config_json.get('inputColorRange') + + color_config = ColorConfig(copy_chroma_location_flag=copy_chroma_location_flag, + copy_color_space_flag=copy_color_space_flag, + copy_color_primaries_flag=copy_color_primaries_flag, + copy_color_range_flag=copy_color_range_flag, + copy_color_transfer_flag=copy_color_transfer_flag, + chroma_location=chroma_location, color_space=color_space, + color_primaries=color_primaries, color_range=color_range, + color_transfer=color_transfer, input_color_space=input_color_space, + input_color_range=input_color_range) + vp9_codec_configuration = VP9CodecConfiguration(name=name, bitrate=bitrate, rate=rate, id_=id_, description=description, custom_data=custom_data, width=width, height=height, lag_in_frames=lag_in_frames, @@ -147,7 +188,8 @@ def parse_from_json_object(cls, json_object): arnr_strength=arnr_strength, arnr_type=arnr_type, pixel_format=pixel_format, min_gop=min_gop, max_gop=max_gop, min_keyframe_interval=min_keyframe_interval, - max_keyframe_interval=max_keyframe_interval) + max_keyframe_interval=max_keyframe_interval, + color_config=color_config) return vp9_codec_configuration @@ -156,4 +198,8 @@ def serialize(self): serialized['quality'] = self.quality serialized['aqMode'] = self.aqMode serialized['arnrType'] = self.arnrType + + if isinstance(self.colorConfig, ColorConfig): + serialized['colorConfig'] = self.colorConfig.serialize() + return serialized From 651669a1ba0c0d610655532b20f2bc45d29f953c Mon Sep 17 00:00:00 2001 From: Matthew Lee Date: Fri, 29 Mar 2019 08:30:22 +0800 Subject: [PATCH 08/11] Added AQ Strength for HEVC --- .../models/codecconfigurations/h265_codec_configuration.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bitmovin/resources/models/codecconfigurations/h265_codec_configuration.py b/bitmovin/resources/models/codecconfigurations/h265_codec_configuration.py index b8bf47e..15599ee 100644 --- a/bitmovin/resources/models/codecconfigurations/h265_codec_configuration.py +++ b/bitmovin/resources/models/codecconfigurations/h265_codec_configuration.py @@ -18,7 +18,8 @@ def __init__(self, name, bitrate, rate, profile, id_=None, description=None, cus scene_cut_threshold=None, enable_hlg_signaling=None, video_format=None, hdr=None, master_display=None, max_content_light_level=None, max_picture_average_light_level=None, sample_aspect_ratio_numerator=None, sample_aspect_ratio_denominator=None, adaptive_quantization_mode=None, psy_rate_distortion_optimization=None, - psy_rate_distortion_optimization_quantization=None, cutree=None, qp_min=None, qp_max=None): + psy_rate_distortion_optimization_quantization=None, cutree=None, qp_min=None, qp_max=None, + adaptive_quantization_strength=None): super().__init__(id_=id_, custom_data=custom_data, name=name, description=description, bitrate=bitrate, rate=rate, width=width, height=height, pixel_format=pixel_format) @@ -69,6 +70,7 @@ def __init__(self, name, bitrate, rate, profile, id_=None, description=None, cus self.sampleAspectRatioDenominator = sample_aspect_ratio_denominator self._adaptive_quantization_mode = None self.adaptiveQuantizationMode = adaptive_quantization_mode + self.adaptiveQuantizationStrength = adaptive_quantization_strength self.psyRateDistortionOptimization = psy_rate_distortion_optimization self.psyRateDistortionOptimizedQuantization = psy_rate_distortion_optimization_quantization self.cutree = cutree @@ -313,6 +315,7 @@ def parse_from_json_object(cls, json_object): aspect_ratio_numerator = json_object.get('sampleAspectRatioNumerator') aspect_ratio_denominator = json_object.get('sampleAspectRatioDenominator') adaptive_quantization_mode = json_object.get('adaptiveQuantizationMode') + adaptive_quantization_strength = json_object.get('adaptiveQuantizationStrength') psy_rate_distortion_optimization = json_object.get('psyRateDistortionOptimization') psy_rate_distortion_optimization_quantization = json_object.get('psyRateDistortionOptimizedQuantization') cutree = json_object.get('cutree') @@ -370,6 +373,7 @@ def parse_from_json_object(cls, json_object): sample_aspect_ratio_numerator=aspect_ratio_numerator, sample_aspect_ratio_denominator=aspect_ratio_denominator, adaptive_quantization_mode=adaptive_quantization_mode, + adaptive_quantization_strength=adaptive_quantization_strength, psy_rate_distortion_optimization=psy_rate_distortion_optimization, psy_rate_distortion_optimization_quantization=psy_rate_distortion_optimization_quantization, cutree=cutree, From 00c0da406f0f4b4e74ae5481a6de26a5bedfb952 Mon Sep 17 00:00:00 2001 From: Matt Lee Date: Tue, 9 Apr 2019 18:26:36 +0800 Subject: [PATCH 09/11] Set default check interval to 30s while waiting for an encoding to finish. Add Exponential Retry to HTTP GET Requests --- bitmovin/rest/http_client.py | 7 ++++++- bitmovin/services/encodings/encoding_control_service.py | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/bitmovin/rest/http_client.py b/bitmovin/rest/http_client.py index 46b1e52..2e47756 100644 --- a/bitmovin/rest/http_client.py +++ b/bitmovin/rest/http_client.py @@ -8,6 +8,8 @@ from bitmovin.resources import Response from bitmovin.utils import BitmovinJSONEncoder from .utils import check_response_success, check_response_header_json +from requests.packages.urllib3.util.retry import Retry +from requests.adapters import HTTPAdapter class BitmovinHttpClient(BitmovinObject): @@ -57,7 +59,10 @@ def post(self, relative_url, payload): def get(self, relative_url): self._log_request('GET', relative_url) url = urljoin(self.base_url, relative_url) - response = requests.get(url, headers=self.http_headers) + session = requests.Session() + retries = Retry(total=10, backoff_factor=1, status_forcelist=[ 502, 503, 504 ]) + session.mount('https://', HTTPAdapter(max_retries=retries)) + response = session.get(url, headers=self.http_headers) parsed_response = self._parse_response(response) return parsed_response diff --git a/bitmovin/services/encodings/encoding_control_service.py b/bitmovin/services/encodings/encoding_control_service.py index b15da76..de2018b 100644 --- a/bitmovin/services/encodings/encoding_control_service.py +++ b/bitmovin/services/encodings/encoding_control_service.py @@ -84,7 +84,7 @@ def status(self, encoding_id): return ResourceResponse(response=response, resource=created_resource) raise InvalidStatusError('Unknown status {} received'.format(response.status)) - def wait_until_finished(self, encoding_id, check_interval=5, timeout=-1): + def wait_until_finished(self, encoding_id, check_interval=30, timeout=-1): status_response = None encoding_status = EncodingStatus(None) From 92a55f0d539bcc224659ba7c1b109aa5212e4860 Mon Sep 17 00:00:00 2001 From: Matthew Lee Date: Wed, 10 Apr 2019 16:30:37 +0800 Subject: [PATCH 10/11] When polling Encoding Status, only throw an error if the encoding has failed, not the encoding status call --- bitmovin/services/encodings/encoding_control_service.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bitmovin/services/encodings/encoding_control_service.py b/bitmovin/services/encodings/encoding_control_service.py index de2018b..f027a98 100644 --- a/bitmovin/services/encodings/encoding_control_service.py +++ b/bitmovin/services/encodings/encoding_control_service.py @@ -77,7 +77,8 @@ def status(self, encoding_id): response = self.http_client.get(url) if response.status == Status.ERROR.value: - raise BitmovinApiError('Response was not successful', response) + created_resource = EncodingStatus(None) + return ResourceResponse(response=None, resource=created_resource) if response.status == Status.SUCCESS.value: created_resource = self.parsing_utils.parse_bitmovin_resource_from_response(response=response, class_=EncodingStatus) From 1f8350c6c82e94503585ece6faadb9bec290ba75 Mon Sep 17 00:00:00 2001 From: Matthew Lee Date: Wed, 10 Apr 2019 17:03:05 +0800 Subject: [PATCH 11/11] Added logic to check a settable number of polls in case of an error fetching encoding status --- .../encodings/encoding_control_service.py | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/bitmovin/services/encodings/encoding_control_service.py b/bitmovin/services/encodings/encoding_control_service.py index f027a98..8c30127 100644 --- a/bitmovin/services/encodings/encoding_control_service.py +++ b/bitmovin/services/encodings/encoding_control_service.py @@ -85,28 +85,37 @@ def status(self, encoding_id): return ResourceResponse(response=response, resource=created_resource) raise InvalidStatusError('Unknown status {} received'.format(response.status)) - def wait_until_finished(self, encoding_id, check_interval=30, timeout=-1): + def wait_until_finished(self, encoding_id, check_interval=30, timeout=-1, max_empty_polls=10): status_response = None encoding_status = EncodingStatus(None) start_time = time.time() + empty_poll_count = 0 - while encoding_status.status != 'FINISHED' and encoding_status.status != 'ERROR': + while encoding_status.status != 'FINISHED' and encoding_status.status != 'ERROR' and empty_poll_count < max_empty_polls: TimeoutUtils.raise_error_if_timeout_reached(start_time_in_seconds=start_time, timeout_in_seconds=timeout) status_response = self.status(encoding_id=encoding_id) encoding_status = status_response.resource # type: EncodingStatus + if encoding_status.status == None: + empty_poll_count += 1 + self.logger.info("Empty polls: {}".format(empty_poll_count)) + else: + empty_poll_count = 0 self.logger.info("Encoding status: {}".format(encoding_status.status)) self.logger.info("Will check again in {} seconds...".format(check_interval)) time.sleep(check_interval) - + self.logger.info("Encoding Status: {}".format(json.dumps(obj=encoding_status, cls=BitmovinJSONEncoder))) - + if encoding_status.status == 'FINISHED': return True - + + if empty_poll_count == max_empty_polls: + raise BitmovinApiError("Unable to fetch encoding status after {} attempts".format(max_empty_polls), status_response) + raise BitmovinApiError("Encoding with ID '{}' was not successfull! Status: {}".format( - encoding_id, encoding_status.status), status_response) - + encoding_id, encoding_status.status), status_response) + def wait_until_running(self, encoding_id, check_interval=5, timeout=-1): status_response = None encoding_status = EncodingStatus(None)