[pitivi] Make setting encoding profiles more robust
- From: Thibault Saunier <tsaunier src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [pitivi] Make setting encoding profiles more robust
- Date: Sun, 30 Jul 2017 00:23:44 +0000 (UTC)
commit 272f5a6045483ca45be9922c3a6fca0edfd55c08
Author: Thibault Saunier <tsaunier gnome org>
Date: Wed Jul 26 11:40:34 2017 -0400
Make setting encoding profiles more robust
When setting an encoding profile from a file we need to make sure
several restrictions are handled:
- We need to make sure the resulting restriction caps are
compatible with the encoder that is going to be used by encodebin
- We should ensure that the profile restriction caps are fully taken
into account (if those restriction are not compatible with the encoder
we can't do much)
- We need to try as much as possible to use user previously set formats
- We need to ensure fields that are mandatory for us are set in a way
that is compatible with the encoder
This introduces a utility function (for better testability) that allows
this kind of caps fixation and some unit tests for this function.
Reviewed-by: Alex Băluț <<alexandru balut gmail com>>
Differential Revision: https://phabricator.freedesktop.org/D1807
pitivi/project.py | 116 +++++++++++++++++++++++++++++---------------------
pitivi/render.py | 18 ++++----
pitivi/utils/misc.py | 87 +++++++++++++++++++++++++++++++++++++
tests/test_render.py | 6 ++-
tests/test_utils.py | 35 +++++++++++++++
5 files changed, 203 insertions(+), 59 deletions(-)
---
diff --git a/pitivi/project.py b/pitivi/project.py
index ce5cccc..8f81aea 100644
--- a/pitivi/project.py
+++ b/pitivi/project.py
@@ -39,6 +39,7 @@ from pitivi.render import Encoders
from pitivi.undo.project import AssetAddedIntention
from pitivi.undo.project import AssetProxiedIntention
from pitivi.utils.loggable import Loggable
+from pitivi.utils.misc import fixate_caps_with_default_values
from pitivi.utils.misc import isWritable
from pitivi.utils.misc import path_from_uri
from pitivi.utils.misc import quote_uri
@@ -1171,14 +1172,6 @@ class Project(Loggable, GES.Project):
if container_profile == self.container_profile:
return False
- previous_audio_rest = None
- previous_video_rest = None
- if not reset_all and self.container_profile:
- if self.audio_profile:
- previous_audio_rest = self.audio_profile.get_restriction()
- if self.video_profile:
- previous_video_rest = self.video_profile.get_restriction()
-
muxer = self._getElementFactoryName(
Encoders().muxers, container_profile)
if muxer is None:
@@ -1192,7 +1185,7 @@ class Project(Loggable, GES.Project):
if profile.get_restriction() is None:
profile.set_restriction(Gst.Caps("video/x-raw"))
- self._ensureVideoRestrictions(profile, previous_video_rest)
+ self._ensureVideoRestrictions(profile)
vencoder = self._getElementFactoryName(Encoders().vencoders, profile)
if vencoder:
profile.set_preset_name(vencoder)
@@ -1201,7 +1194,7 @@ class Project(Loggable, GES.Project):
if profile.get_restriction() is None:
profile.set_restriction(Gst.Caps("audio/x-raw"))
- self._ensureAudioRestrictions(profile, previous_audio_rest)
+ self._ensureAudioRestrictions(profile)
aencoder = self._getElementFactoryName(Encoders().aencoders, profile)
if aencoder:
profile.set_preset_name(aencoder)
@@ -1491,57 +1484,82 @@ class Project(Loggable, GES.Project):
if not self.ges_timeline.get_layers():
self.ges_timeline.append_layer()
- def _ensureRestrictions(self, profile, values, ref_restrictions=None):
- """Make sure restriction values defined in @values are set on @profile.
+ def _ensureRestrictions(self, profile, defaults, ref_restrictions=None,
+ prev_vals=None):
+ """Make sure restriction values defined in @defaults are set on @profile.
Attributes:
profile (Gst.EncodingProfile): The Gst.EncodingProfile to use
- values (dict): A key value dict to use to set restriction values
+ defaults (dict): A key value dict to use to set restriction defaults
ref_restrictions (Gst.Caps): Reuse values from those caps instead
of @values if available.
"""
- self.debug("Ensuring %s", profile.get_restriction().to_string())
- for fieldname, value in values:
- # Only consider the first GstStructure
- # FIXME Figure out everywhere how to be smarter.
- cvalue = profile.get_restriction()[0][fieldname]
- if cvalue is None:
- if ref_restrictions and ref_restrictions[0][fieldname]:
- value = ref_restrictions[0][fieldname]
- res = Project._set_restriction(profile, fieldname, value)
-
- encoder = profile.get_preset_name()
- if encoder:
- self._enforce_video_encoder_restrictions(encoder, profile)
-
- def _ensureVideoRestrictions(self, profile=None, ref_restrictions=None):
- values = [
- ("width", 720),
- ("height", 576),
- ("framerate", Gst.Fraction(25, 1)),
- ("pixel-aspect-ratio", Gst.Fraction(1, 1))
- ]
+ encoder = None
+ if isinstance(profile, GstPbutils.EncodingAudioProfile):
+ facttype = Gst.ELEMENT_FACTORY_TYPE_AUDIO_ENCODER
+ else:
+ facttype = Gst.ELEMENT_FACTORY_TYPE_VIDEO_ENCODER
+
+ ebin = Gst.ElementFactory.make('encodebin', None)
+ ebin.props.profile = profile
+ for element in ebin.iterate_recurse():
+ if element.get_factory().list_is_type(facttype):
+ encoder = element
+ break
+
+ encoder_sinkcaps = encoder.sinkpads[0].get_pad_template().get_caps().copy()
+ self.debug("%s - Ensuring %s\n defaults: %s\n ref_restrictions: %s\n prev_vals: %s)",
+ encoder, encoder_sinkcaps, defaults, ref_restrictions,
+ prev_vals)
+ restriction = fixate_caps_with_default_values(encoder_sinkcaps,
+ ref_restrictions,
+ defaults,
+ prev_vals)
+ assert(restriction)
+ preset_name = encoder.get_factory().get_name()
+ profile.set_restriction(restriction)
+ profile.set_preset_name(preset_name)
+
+ self._enforce_video_encoder_restrictions(preset_name, profile)
+ self.info("Fully set restriction: %s", profile.get_restriction().to_string())
+
+ def _ensureVideoRestrictions(self, profile=None):
+ defaults = {
+ "width": 720,
+ "height": 576,
+ "framerate": Gst.Fraction(25, 1),
+ "pixel-aspect-ratio": Gst.Fraction(1, 1)
+ }
+
+ prev_vals = None
+ if self.video_profile:
+ prev_vals = self.video_profile.get_restriction().copy()
+
+ ref_restrictions = None
if not profile:
profile = self.video_profile
- self._ensureRestrictions(profile, values, ref_restrictions)
+ else:
+ ref_restrictions = profile.get_restriction()
- def _ensureAudioRestrictions(self, profile=None, ref_restrictions=None):
+ self._ensureRestrictions(profile, defaults, ref_restrictions,
+ prev_vals)
+
+ def _ensureAudioRestrictions(self, profile=None):
+ ref_restrictions = None
if not profile:
profile = self.audio_profile
- defaults = [["channels", 2], ["rate", 44100]]
- for fv in defaults:
- field, value = fv
- fvalue = profile.get_format()[0][field]
- if isinstance(fvalue, Gst.ValueList) and value not in fvalue.array:
- fv[1] = fvalue.array[0]
- elif isinstance(fvalue, range) and value not in fvalue:
- fv[1] = fvalue[0]
- else:
- self.warning("How should we handle ensuring restriction caps"
- " compatibility for field %s with format value: %s",
- field, fvalue)
- return self._ensureRestrictions(profile, defaults, ref_restrictions)
+ else:
+ ref_restrictions = profile.get_restriction()
+
+ defaults = {"channels": Gst.IntRange(range(1, 2147483647)),
+ "rate": Gst.IntRange(range(8000, GLib.MAXINT))}
+ prev_vals = None
+ if self.audio_profile:
+ prev_vals = self.audio_profile.get_restriction().copy()
+
+ return self._ensureRestrictions(profile, defaults, ref_restrictions,
+ prev_vals)
def _maybeInitSettingsFromAsset(self, asset):
"""Updates the project settings to match the specified asset.
diff --git a/pitivi/render.py b/pitivi/render.py
index 65d99e3..72af178 100644
--- a/pitivi/render.py
+++ b/pitivi/render.py
@@ -479,7 +479,7 @@ class RenderDialog(Loggable):
def factory(x):
return Encoders().factories_by_name.get(getattr(self.project, x))
- self.project.set_container_profile(encoding_profile, reset_all=True)
+ self.project.set_container_profile(encoding_profile)
self._setting_encoding_profile = True
if not set_combo_value(self.muxer_combo, factory('muxer')):
@@ -487,15 +487,15 @@ class RenderDialog(Loggable):
return
self.updateAvailableEncoders()
- for i, (combo, value) in enumerate([
- (self.audio_encoder_combo, factory('aencoder')),
- (self.video_encoder_combo, factory('vencoder')),
- (self.sample_rate_combo, self.project.audiorate),
- (self.channels_combo, self.project.audiochannels),
- (self.frame_rate_combo, self.project.videorate)]):
+ for i, (combo, name, value) in enumerate([
+ (self.audio_encoder_combo, "aencoder", factory("aencoder")),
+ (self.video_encoder_combo, "vencoder", factory("vencoder")),
+ (self.sample_rate_combo, "audiorate", self.project.audiorate),
+ (self.channels_combo, "audiochannels", self.project.audiochannels),
+ (self.frame_rate_combo, "videorate", self.project.videorate)]):
if value is None:
- self.error("%d - Got no value for combo %s... rolling back",
- i, combo)
+ self.error("%d - Got no value for %s (%s)... rolling back",
+ i, name, combo)
rollback()
return
diff --git a/pitivi/utils/misc.py b/pitivi/utils/misc.py
index b6612e3..3b229e3 100644
--- a/pitivi/utils/misc.py
+++ b/pitivi/utils/misc.py
@@ -306,3 +306,90 @@ def unicode_error_dialog():
dialog.set_title(_("Error while decoding a string"))
dialog.run()
dialog.destroy()
+
+
+def intersect(v1, v2):
+ s = Gst.Structure('t', t=v1).intersect(Gst.Structure('t', t=v2))
+ if s:
+ return s['t']
+
+ return None
+
+
+def fixate_caps_with_default_values(template, restrictions, default_values,
+ prev_vals=None):
+ """Fixates @template taking into account other restriction values.
+
+ The resulting caps will only contain the fields from @default_values,
+ @restrictions and @prev_vals
+
+ Args:
+ template (Gst.Caps) : The pad template to fixate.
+ restrictions (Gst.Caps): Restriction caps to be used to fixate
+ @template. This is the minimum requested
+ restriction. Can be None
+ default_values (dict) : Dictionary containing the minimal fields
+ to be fixated and some default values (can be ranges).
+ prev_vals (Optional[Gst.Caps]) : Some values that were previously
+ used, and should be kept instead of the default values if possible.
+
+ Returns:
+ Gst.Caps: The caps resulting from the previously defined operations.
+ """
+ res = Gst.Caps.new_empty()
+ fields = set(default_values.keys())
+ if restrictions:
+ for struct in restrictions:
+ fields.update(struct.keys())
+
+ log.debug("utils", "Intersect template %s with the restriction %s",
+ template, restrictions)
+ tmp = template.intersect(restrictions)
+
+ if not tmp:
+ log.warning("utils",
+ "No common format between template %s and restrictions %s",
+ template, restrictions)
+ else:
+ template = tmp
+
+ for struct in template:
+ struct = struct.copy()
+ for field in fields:
+ prev_val = None
+ default_val = default_values.get(field)
+ if prev_vals and prev_vals[0].has_field(field):
+ prev_val = prev_vals[0][field]
+
+ if not struct.has_field(field):
+ if prev_val:
+ struct[field] = prev_val
+ elif default_val:
+ struct[field] = default_val
+ else:
+ v = None
+ struct_val = struct[field]
+ if prev_val:
+ v = intersect(struct_val, prev_val)
+ if v is not None:
+ struct[field] = v
+ if v is None and default_val:
+ v = intersect(struct_val, default_val)
+ if v is not None:
+ struct[field] = v
+ else:
+ log.info("utils", "Field %s from %s is plainly fixated",
+ field, struct)
+
+ struct = struct.copy()
+ for key in struct.keys():
+ if key not in fields:
+ struct.remove_field(key)
+
+ log.debug("utils", "Adding %s to resulting caps", struct)
+ res.append_structure(struct)
+
+ res.mini_object.refcount += 1
+ res = res.fixate()
+ log.debug("utils", "Fixated %s", res)
+ return res
diff --git a/tests/test_render.py b/tests/test_render.py
index c3bcf48..875a036 100644
--- a/tests/test_render.py
+++ b/tests/test_render.py
@@ -272,8 +272,12 @@ class TestRender(common.TestCase):
i = find_preset_row_index(preset_combo, profile_name)
self.assertIsNotNone(i)
preset_combo.set_active(i)
- from pitivi.render import RenderingProgressDialog
+ self.render(dialog)
+
+ def render(self, dialog):
+ """Renders pipeline from @dialog."""
+ from pitivi.render import RenderingProgressDialog
with tempfile.TemporaryDirectory() as temp_dir:
# Start rendering
with mock.patch.object(dialog.filebutton, "get_uri",
diff --git a/tests/test_utils.py b/tests/test_utils.py
index bf966ab..86d91b8 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -19,12 +19,14 @@
# Boston, MA 02110-1301, USA.
from unittest import TestCase
+from gi.repository import GLib
from gi.repository import Gst
from pitivi.check import CairoDependency
from pitivi.check import ClassicDependency
from pitivi.check import GstDependency
from pitivi.check import GtkDependency
+from pitivi.utils.misc import fixate_caps_with_default_values
from pitivi.utils.ui import beautify_length
second = Gst.SECOND
@@ -92,3 +94,36 @@ class TestDependencyChecks(TestCase):
classic_dep = ClassicDependency("numpy", None)
classic_dep.check()
self.assertTrue(classic_dep.satisfied)
+
+
+class TestMiscUtils(TestCase):
+
+ def test_fixate_caps_with_defalt_values(self):
+ voaacenc_caps = Gst.Caps.from_string(
+ "audio/x-raw, format=(string)S16LE, layout=(string)interleaved, rate=(int){ 8000, 11025, 12000,
16000, 22050, 24000, 32000, 44100, 48000, 64000, 88200, 96000 }, channels=(int)1;"
+ "audio/x-raw, format=(string)S16LE, layout=(string)interleaved, rate=(int){ 8000, 11025, 12000,
16000, 22050, 24000, 32000, 44100, 48000, 64000, 88200, 96000 }, channels=(int)2,
channel-mask=(bitmask)0x0000000000000003")
+ yt_audiorest = Gst.Caps("audio/x-raw,channels=6,channel-mask=0x3f,rate={48000,96000};"
+ "audio/x-raw,channels=2,rate={48000,96000}")
+
+ vorbis_caps = Gst.Caps("audio/x-raw, format=(string)F32LE, layout=(string)interleaved, rate=(int)[
1, 200000 ], channels=(int)1;"
+ "audio/x-raw, format=(string)F32LE, layout=(string)interleaved, rate=(int)[
1, 200000 ], channels=(int)2, channel-mask=(bitmask)0x0000000000000003;"
+ "audio/x-raw, format=(string)F32LE, layout=(string)interleaved, rate=(int)[
1, 200000 ], channels=(int)3, channel-mask=(bitmask)0x0000000000000007;"
+ "audio/x-raw, format=(string)F32LE, layout=(string)interleaved, rate=(int)[
1, 200000 ], channels=(int)4, channel-mask=(bitmask)0x0000000000000033;"
+ "audio/x-raw, format=(string)F32LE, layout=(string)interleaved, rate=(int)[
1, 200000 ], channels=(int)5, channel-mask=(bitmask)0x0000000000000037;"
+ "audio/x-raw, format=(string)F32LE, layout=(string)interleaved, rate=(int)[
1, 200000 ], channels=(int)6, channel-mask=(bitmask)0x000000000000003f;"
+ "audio/x-raw, format=(string)F32LE, layout=(string)interleaved, rate=(int)[
1, 200000 ], channels=(int)7, channel-mask=(bitmask)0x0000000000000d0f;"
+ "audio/x-raw, format=(string)F32LE, layout=(string)interleaved, rate=(int)[
1, 200000 ], channels=(int)8, channel-mask=(bitmask)0x0000000000000c3f;"
+ "audio/x-raw, format=(string)F32LE, layout=(string)interleaved, rate=(int)[
1, 200000 ], channels=(int)[ 9, 255 ], channel-mask=(bitmask)0x0000000000000000")
+
+ audio_defaults = {'channels': Gst.IntRange(range(1, 2147483647)),
+ "rate": Gst.IntRange(range(8000, GLib.MAXINT))}
+
+ dataset = [
+ (voaacenc_caps, yt_audiorest, audio_defaults, None, Gst.Caps("audio/x-raw,
channels=2,rate=48000,channel-mask=(bitmask)0x03")),
+ (vorbis_caps, None, audio_defaults, Gst.Caps('audio/x-raw,channels=1,rate=8000'))
+ ]
+
+ for data in dataset:
+ res = fixate_caps_with_default_values(*data[:-1])
+ print(res)
+ self.assertTrue(res.is_equal_fixed(data[-1]), "%s != %s" % (res, data[-1]))
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]