[pitivi] render: Show the matching profile
- From: Alexandru Băluț <alexbalut src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [pitivi] render: Show the matching profile
- Date: Wed, 30 Sep 2020 17:55:48 +0000 (UTC)
commit 891fe24d840c118ae66fe9c6571eaee47f25e18f
Author: Alexandru Băluț <alexandru balut gmail com>
Date: Mon Sep 14 03:27:24 2020 +0200
render: Show the matching profile
pitivi/project.py | 67 +++++++++++++++++++++-
pitivi/render.py | 156 +++++++++++++++++++++++++++++++++++----------------
tests/test_render.py | 99 ++++++++++++++++++++++++++++++--
3 files changed, 268 insertions(+), 54 deletions(-)
---
diff --git a/pitivi/project.py b/pitivi/project.py
index afbab4cde..18cd97cba 100644
--- a/pitivi/project.py
+++ b/pitivi/project.py
@@ -1494,6 +1494,7 @@ class Project(Loggable, GES.Project):
if profiles:
# The project just loaded, check the new
# encoding profile and make use of it now.
+ self.info("Using first encoding profile: %s", [p.get_preset_name() for p in profiles])
self.set_container_profile(profiles[0])
self._load_encoder_settings(profiles)
@@ -1540,6 +1541,7 @@ class Project(Loggable, GES.Project):
if not aencoder:
self.error("Can't use profile, no audio encoder found.")
return False
+
if not vencoder:
self.error("Can't use profile, no video encoder found.")
return False
@@ -1550,6 +1552,55 @@ class Project(Loggable, GES.Project):
return True
+ def is_profile_subset(self, profile, superset):
+ return self._get_element_factory_name(profile) == self._get_element_factory_name(superset)
+
+ def matches_container_profile(self, container_profile):
+ if not self.is_profile_subset(container_profile, self.container_profile):
+ return False
+
+ video_matches = False
+ has_video = False
+ audio_matches = False
+ has_audio = False
+ for profile in container_profile.get_profiles():
+ if isinstance(profile, GstPbutils.EncodingVideoProfile):
+ has_video = True
+ video_matches |= self.is_profile_subset(profile, self.video_profile)
+
+ # For example: "Profile Youtube"
+ preset_name = profile.get_preset()
+ if preset_name:
+ # We assume container_profile has the same preset
+ # as the included video profile.
+
+ current_preset_name = self.video_profile.get_preset()
+ if not current_preset_name:
+ return False
+
+ # For example: "x264enc"
+ preset_factory_name = self.video_profile.get_preset_name()
+ tmp_preset = Gst.ElementFactory.make(preset_factory_name, None)
+ tmp_preset.load_preset(current_preset_name)
+
+ res, last_applied_preset_name = tmp_preset.get_meta(current_preset_name,
"pitivi::OriginalPreset")
+ if res and preset_name != last_applied_preset_name:
+ return False
+
+ elif isinstance(profile, GstPbutils.EncodingAudioProfile):
+ has_audio = True
+ audio_matches |= self.is_profile_subset(profile, self.audio_profile)
+
+ if has_audio:
+ if not audio_matches:
+ return False
+
+ if has_video:
+ if not video_matches:
+ return False
+
+ return True
+
def _load_encoder_settings(self, profiles):
for container_profile in profiles:
if not isinstance(container_profile, GstPbutils.EncodingContainerProfile):
@@ -1626,6 +1677,10 @@ class Project(Loggable, GES.Project):
if cache_key not in cache:
continue
+ current_preset = profile.get_preset()
+ if current_preset and current_preset.startswith("encoder_settings_"):
+ current_preset = None
+
# The settings for the current GstPbutils.EncodingProfile.
settings = cache[cache_key]
# The name of the Gst.Preset storing the settings.
@@ -1638,13 +1693,18 @@ class Project(Loggable, GES.Project):
# automatically.
profile.set_preset(preset_name)
+ # The original preset name is also important.
+ preset.set_meta(preset_name, "pitivi::OriginalPreset", current_preset)
+
# Store the current GstPbutils.EncodingProfile's settings
# in the Gst.Preset.
for prop, value in settings.items():
preset.set_property(prop, value)
# Serialize the GstPbutils.EncodingProfile's settings
- # from the cache into a Gst.Preset.
+ # from the cache into e.g.
+ # $XDG_DATA_HOME/gstreamer-1.0/presets/GstX264Enc.prs
+ # for x264enc presets.
res = preset.save_preset(preset_name)
assert res
@@ -2091,7 +2151,7 @@ class Project(Loggable, GES.Project):
if profile.get_preset_name():
return profile.get_preset_name()
- factories = self.__factories_compatible_with_profile(profile)
+ factories = Project.__factories_compatible_with_profile(profile)
if not factories:
return None
@@ -2112,7 +2172,8 @@ class Project(Loggable, GES.Project):
self.error("Could not find any element with preset %s", preset)
return None
- def __factories_compatible_with_profile(self, profile):
+ @staticmethod
+ def __factories_compatible_with_profile(profile):
"""Finds factories of the same type as the specified profile.
Args:
diff --git a/pitivi/render.py b/pitivi/render.py
index e7b01e606..1a8e1e87d 100644
--- a/pitivi/render.py
+++ b/pitivi/render.py
@@ -96,6 +96,14 @@ def set_icon_and_title(icon, title, preset_item, icon_size=Gtk.IconSize.DND):
class PresetItem(GObject.Object):
+ """Info about a render preset.
+
+ Attributes:
+ name (string): Name of the target containing the profile.
+ target (GstPbutils.EncodingTarget): The encoding target containing
+ the profile.
+ profile (GstPbutils.EncodingContainerProfile): The represented preset.
+ """
def __init__(self, name, target, profile):
GObject.Object.__init__(self)
@@ -168,7 +176,7 @@ class PresetsManager(GObject.Object, Loggable):
"""
__gsignals__ = {
- "profile-selected": (GObject.SignalFlags.RUN_LAST, None, (PresetItem,))
+ "profile-updated": (GObject.SignalFlags.RUN_LAST, None, (PresetItem,))
}
def __init__(self, project):
@@ -242,7 +250,7 @@ class PresetsManager(GObject.Object, Loggable):
self.model.remove(pos)
self.cur_preset_item = None
- self.emit("profile-selected", None)
+ self.emit("profile-updated", None)
def _save_preset_cb(self, unused_action, unused_param):
name = self.cur_preset_item.target.get_name()
@@ -254,7 +262,7 @@ class PresetsManager(GObject.Object, Loggable):
# Recreate the preset with the current values.
self.cur_preset_item = self.create_preset(name)
- self.emit("profile-selected", self.cur_preset_item)
+ self.emit("profile-updated", self.cur_preset_item)
def _add_target(self, encoding_target):
"""Adds the profiles of the specified encoding_target as render presets.
@@ -311,11 +319,12 @@ class PresetsManager(GObject.Object, Loggable):
preset_item (PresetItem): The row representing the preset to be applied.
"""
self.cur_preset_item = preset_item
- writable = len(preset_item.target.get_profiles()) == 1 and os.access(preset_item.target.get_path(),
os.W_OK)
+ writable = bool(preset_item) and \
+ len(preset_item.target.get_profiles()) == 1 and \
+ os.access(preset_item.target.get_path(), os.W_OK)
self.action_remove.set_enabled(writable)
self.action_save.set_enabled(writable)
- self.emit("profile-selected", preset_item)
def select_default_preset(self):
"""Selects the default hardcoded preset."""
@@ -324,6 +333,15 @@ class PresetsManager(GObject.Object, Loggable):
self.select_preset(item)
break
+ def select_matching_preset(self):
+ """Selects the first preset matching the project's encoders settings."""
+ for item in self.model:
+ if self.project.matches_container_profile(item.profile):
+ self.select_preset(item)
+ return
+
+ self.select_preset(None)
+
class Encoders(Loggable):
"""Registry of available Muxers, Audio encoders and Video encoders.
@@ -587,7 +605,7 @@ class QualityAdapter(Loggable):
prop_name = list(props_values.keys())[0]
self.prop_name = prop_name
- def update_adjustment(self, adjustment, vcodecsettings, callback_handler_id):
+ def calculate_quality(self, vcodecsettings):
if self.prop_name in vcodecsettings:
encoder_property_value = vcodecsettings[self.prop_name]
values = self.props_values[self.prop_name]
@@ -601,11 +619,7 @@ class QualityAdapter(Loggable):
quality = Quality.LOW
self.debug("Cannot calculate quality from missing prop %s", self.prop_name)
- adjustment.handler_block(callback_handler_id)
- try:
- adjustment.props.value = quality
- finally:
- adjustment.handler_unblock(callback_handler_id)
+ return quality
def update_project_vcodecsettings(self, project, quality):
for prop_name, values in self.props_values.items():
@@ -779,7 +793,6 @@ class RenderDialog(Loggable):
self._gst_signal_handlers_ids = {}
self.presets_manager = PresetsManager(project)
- self.presets_manager.connect("profile-selected", self._presets_manager_profile_selected_cb)
# Whether encoders changing are a result of changing the muxer.
self.muxer_combo_changing = False
@@ -819,41 +832,44 @@ class RenderDialog(Loggable):
self.project.connect("rendering-settings-changed",
self._rendering_settings_changed_cb)
- # Monitor changes
+ self.presets_manager.connect("profile-updated", self._presets_manager_profile_updated_cb)
+ has_vcodecsettings = bool(self.project.vcodecsettings)
+ if has_vcodecsettings:
+ self.presets_manager.select_matching_preset()
+ else:
+ self.presets_manager.select_default_preset()
+ cur_preset_item = self.presets_manager.cur_preset_item
+ if cur_preset_item and self.apply_preset(cur_preset_item):
+ self.apply_vcodecsettings_quality(Quality.MEDIUM)
+ else:
+ self.presets_manager.select_preset(None)
+
+ set_icon_and_title(self.preset_icon, self.preset_label, self.presets_manager.cur_preset_item)
+ self._update_quality_scale()
+
+ # Monitor changes to keep the preset_selection_menubutton updated.
self.widgets_group = RippleUpdateGroup()
+ self.widgets_group.add_vertex(self.preset_selection_menubutton,
+ update_func=self._update_preset_selection_menubutton_func)
+
+ self.widgets_group.add_vertex(self.muxer_combo, signal="changed")
+ self.widgets_group.add_vertex(self.video_encoder_combo, signal="changed")
self.widgets_group.add_vertex(self.frame_rate_combo, signal="changed")
+ self.widgets_group.add_vertex(self.audio_encoder_combo, signal="changed")
self.widgets_group.add_vertex(self.channels_combo, signal="changed")
self.widgets_group.add_vertex(self.sample_rate_combo, signal="changed")
- self.widgets_group.add_vertex(self.muxer_combo, signal="changed")
- self.widgets_group.add_vertex(self.audio_encoder_combo, signal="changed")
- self.widgets_group.add_vertex(self.video_encoder_combo, signal="changed")
- self.widgets_group.add_vertex(self.preset_menubutton, signal="clicked")
- self.widgets_group.add_vertex(self.preset_selection_menubutton, signal="clicked")
- self.widgets_group.add_vertex(self.quality_adjustment, signal="value-changed",
update_func=self._update_quality_adjustment_func)
-
- self.widgets_group.add_edge(self.frame_rate_combo, self.preset_menubutton)
- self.widgets_group.add_edge(self.audio_encoder_combo, self.preset_menubutton)
- self.widgets_group.add_edge(self.video_encoder_combo, self.preset_menubutton)
- self.widgets_group.add_edge(self.muxer_combo, self.preset_menubutton)
- self.widgets_group.add_edge(self.channels_combo, self.preset_menubutton)
- self.widgets_group.add_edge(self.sample_rate_combo, self.preset_menubutton)
- self.widgets_group.add_edge(self.preset_selection_menubutton, self.audio_encoder_combo)
- self.widgets_group.add_edge(self.preset_selection_menubutton, self.video_encoder_combo)
- self.widgets_group.add_edge(self.video_encoder_combo, self.quality_adjustment)
-
- if not self.project.vcodecsettings:
- self.presets_manager.select_default_preset()
- self.quality_adjustment.props.value = Quality.MEDIUM
- def _presets_manager_profile_selected_cb(self, unused_target, preset_item):
- """Handles the selection of a render preset."""
- set_icon_and_title(self.preset_icon, self.preset_label, preset_item)
+ self.widgets_group.add_edge(self.muxer_combo, self.preset_selection_menubutton)
+ self.widgets_group.add_edge(self.video_encoder_combo, self.preset_selection_menubutton)
+ self.widgets_group.add_edge(self.frame_rate_combo, self.preset_selection_menubutton)
+ self.widgets_group.add_edge(self.audio_encoder_combo, self.preset_selection_menubutton)
+ self.widgets_group.add_edge(self.channels_combo, self.preset_selection_menubutton)
+ self.widgets_group.add_edge(self.sample_rate_combo, self.preset_selection_menubutton)
- if preset_item:
- old_profile = self.project.container_profile
- if not self._set_encoding_profile(preset_item.profile):
- self._set_encoding_profile(old_profile)
+ def _presets_manager_profile_updated_cb(self, presets_manager, preset_item):
+ """Handles the saving or removing of a render preset."""
+ set_icon_and_title(self.preset_icon, self.preset_label, preset_item)
def _set_encoding_profile(self, encoding_profile):
"""Sets the encoding profile of the project.
@@ -869,6 +885,7 @@ class RenderDialog(Loggable):
try:
muxer = Encoders().factories_by_name.get(self.project.muxer)
if not set_combo_value(self.muxer_combo, muxer):
+ self.error("Failed to set muxer_combo to %s", muxer)
return False
self.update_available_encoders()
@@ -988,8 +1005,28 @@ class RenderDialog(Loggable):
return PresetBoxRow(preset_item)
def _preset_listbox_row_activated_cb(self, listbox, row):
- self.presets_manager.select_preset(row.preset_item)
- self.preset_popover.hide()
+ if self.apply_preset(row.preset_item):
+ quality = Quality.MEDIUM
+ if self.quality_scale.get_sensitive():
+ quality = self.quality_adjustment.props.value
+
+ self.apply_vcodecsettings_quality(quality)
+ self._update_quality_scale()
+
+ self.preset_popover.hide()
+
+ def apply_preset(self, preset_item):
+ old_profile = self.project.container_profile
+ profile = preset_item.profile.copy()
+ if not self._set_encoding_profile(profile):
+ self.error("failed to apply the encoding profile, reverting to previous one")
+ self._set_encoding_profile(old_profile)
+ return False
+
+ self.presets_manager.select_preset(preset_item)
+ set_icon_and_title(self.preset_icon, self.preset_label, preset_item)
+
+ return True
def _preset_selection_menubutton_clicked_cb(self, button):
self.preset_popover.show_all()
@@ -1638,12 +1675,14 @@ class RenderDialog(Loggable):
def _frame_rate_combo_changed_cb(self, combo):
if self._setting_encoding_profile:
return
+
framerate = get_combo_value(combo)
self.project.videorate = framerate
def _video_encoder_combo_changed_cb(self, combo):
if self._setting_encoding_profile:
return
+
factory = get_combo_value(combo)
name = factory.get_name()
self.project.vencoder = name
@@ -1652,26 +1691,31 @@ class RenderDialog(Loggable):
self.debug("User chose a video encoder: %s", name)
self.preferred_vencoder = name
self._update_valid_video_restrictions(factory)
+ self._update_quality_scale()
def _video_settings_button_clicked_cb(self, unused_button):
if self._setting_encoding_profile:
return
+
factory = get_combo_value(self.video_encoder_combo)
self._element_settings_dialog(factory, "video")
def _channels_combo_changed_cb(self, combo):
if self._setting_encoding_profile:
return
+
self.project.audiochannels = get_combo_value(combo)
def _sample_rate_combo_changed_cb(self, combo):
if self._setting_encoding_profile:
return
+
self.project.audiorate = get_combo_value(combo)
def _audio_encoder_changed_combo_cb(self, combo):
if self._setting_encoding_profile:
return
+
factory = get_combo_value(combo)
name = factory.get_name()
self.project.aencoder = name
@@ -1703,18 +1747,36 @@ class RenderDialog(Loggable):
# Update muxer-dependent widgets.
self.update_available_encoders()
- def _update_quality_adjustment_func(self, unused_source, adjustment):
+ def _update_preset_selection_menubutton_func(self, source_widget, target_widget):
+ if self._setting_encoding_profile:
+ return
+
+ self.presets_manager.select_matching_preset()
+
+ def _update_quality_scale(self):
encoder = get_combo_value(self.video_encoder_combo)
adapter = quality_adapters.get(encoder.get_name())
+
self.quality_scale.set_sensitive(bool(adapter))
if adapter:
- adapter.update_adjustment(self.quality_adjustment, self.project.vcodecsettings,
self.quality_adjustment_handler_id)
+ quality = adapter.calculate_quality(self.project.vcodecsettings)
else:
- self.quality_adjustment.props.value = self.quality_adjustment.props.lower
+ quality = self.quality_adjustment.props.lower
+ self.quality_adjustment.handler_block(self.quality_adjustment_handler_id)
+ try:
+ self.quality_adjustment.props.value = quality
+ finally:
+ self.quality_adjustment.handler_unblock(self.quality_adjustment_handler_id)
def _quality_adjustment_value_changed_cb(self, adjustment):
+ self.apply_vcodecsettings_quality(self.quality_adjustment.props.value)
+
+ def apply_vcodecsettings_quality(self, quality):
encoder = get_combo_value(self.video_encoder_combo)
adapter = quality_adapters.get(encoder.get_name())
- quality = round(self.quality_adjustment.props.value)
- adapter.update_project_vcodecsettings(self.project, quality)
+ if not adapter:
+ # The current video encoder is not yet supported.
+ return
+
+ adapter.update_project_vcodecsettings(self.project, round(quality))
diff --git a/tests/test_render.py b/tests/test_render.py
index b518f13da..2905d8b6c 100644
--- a/tests/test_render.py
+++ b/tests/test_render.py
@@ -95,13 +95,12 @@ class TestQualityAdapter(common.TestCase):
def check_adapter(self, adapter, expected_qualities):
qualities = []
for prop_value in range(len(expected_qualities)):
- adjustment = mock.Mock()
vcodecsettings = {adapter.prop_name: prop_value}
- adapter.update_adjustment(adjustment, vcodecsettings, 0)
- qualities.append(adjustment.props.value)
+ quality = adapter.calculate_quality(vcodecsettings)
+ qualities.append(quality)
self.assertListEqual(qualities, expected_qualities)
- def test_update_adjustment(self):
+ def test_calculate_quality(self):
self.check_adapter(QualityAdapter({"prop1": (0, 3, 5)}),
[Quality.LOW, Quality.LOW, Quality.LOW, Quality.MEDIUM, Quality.MEDIUM,
Quality.HIGH])
self.check_adapter(QualityAdapter({"prop1": (100, 3, 2)}),
@@ -529,3 +528,95 @@ class TestRender(BaseTestMediaLibrary):
caps = dialog.dialog.get_caps()
self.assert_caps_equal(caps, "video/x-h264,profile=baseline")
+
+ def check_quality_widget(self, dialog, vencoder, vcodecsettings, preset, sensitive, value):
+ if vencoder:
+ self.assertEqual(dialog.project.vencoder, vencoder)
+ if vcodecsettings is not None:
+ self.assertDictEqual(dialog.project.vcodecsettings, vcodecsettings)
+
+ if preset:
+ self.assertEqual(dialog.presets_manager.cur_preset_item.name, preset)
+ else:
+ self.assertIsNone(dialog.presets_manager.cur_preset_item)
+
+ self.assertEqual(dialog.quality_scale.props.sensitive, sensitive)
+ self.assertEqual(dialog.quality_adjustment.props.value, value)
+
+ @skipUnless(*encoding_target_exists("dvd"))
+ @skipUnless(*encoding_target_exists("youtube"))
+ @skipUnless(*factory_exists("pngenc"))
+ def test_quality_widget(self):
+ project = self.create_simple_project()
+ dialog = self.create_rendering_dialog(project)
+ self.check_quality_widget(dialog,
+ vencoder="x264enc", vcodecsettings={"quantizer": 21, "pass": 5},
+ preset="youtube",
+ sensitive=True, value=Quality.MEDIUM)
+
+ self.assertEqual(project.video_profile.get_preset_name(), "x264enc")
+ dialog.quality_adjustment.props.value = Quality.HIGH
+ self.check_quality_widget(dialog,
+ vencoder="x264enc", vcodecsettings={"quantizer": 18, "pass": 5},
+ preset="youtube",
+ sensitive=True, value=Quality.HIGH)
+
+ self.select_render_preset(dialog, "dvd")
+ self.check_quality_widget(dialog,
+ vencoder=None, vcodecsettings={},
+ preset="dvd",
+ sensitive=False, value=Quality.LOW)
+
+ self.select_render_preset(dialog, "youtube")
+ self.assertEqual(project.video_profile.get_preset_name(), "x264enc")
+ self.check_quality_widget(dialog,
+ vencoder="x264enc", vcodecsettings={"quantizer": 21, "pass": 5},
+ preset="youtube",
+ sensitive=True, value=Quality.MEDIUM)
+
+ self.assertTrue(set_combo_value(dialog.video_encoder_combo,
+ Gst.ElementFactory.find("pngenc")))
+ self.check_quality_widget(dialog,
+ vencoder="pngenc", vcodecsettings={},
+ preset=None,
+ sensitive=False, value=Quality.LOW)
+
+ self.select_render_preset(dialog, "youtube")
+ self.check_quality_widget(dialog,
+ vencoder="x264enc", vcodecsettings={"quantizer": 21, "pass": 5},
+ preset="youtube",
+ sensitive=True, value=Quality.MEDIUM)
+
+ def test_preset_persistent(self):
+ """Checks the render preset is remembered when loading a project."""
+ project = self.create_simple_project()
+ self.assertEqual(project.muxer, "webmmux")
+ self.assertEqual(project.vencoder, "vp8enc")
+ self.assertDictEqual(project.vcodecsettings, {})
+
+ dialog = self.create_rendering_dialog(project)
+ self.check_quality_widget(dialog,
+ vencoder="x264enc", vcodecsettings={"quantizer": 21, "pass": 5},
+ preset="youtube",
+ sensitive=True, value=Quality.MEDIUM)
+
+ project_manager = project.app.project_manager
+ with tempfile.NamedTemporaryFile() as temp_file:
+ uri = Gst.filename_to_uri(temp_file.name)
+ project_manager.save_project(uri=uri, backup=False)
+
+ app2 = common.create_pitivi()
+ project2 = app2.project_manager.load_project(uri)
+ timeline_container = TimelineContainer(app2, editor_state=app2.gui.editor.editor_state)
+ timeline_container.set_project(project2)
+ common.create_main_loop().run(until_empty=True)
+ self.assertEqual(project2.muxer, "qtmux")
+ self.assertEqual(project2.vencoder, "x264enc")
+ self.assertTrue(set({"quantizer": 21, "pass":
5}.items()).issubset(set(project2.vcodecsettings.items())))
+
+ dialog2 = self.create_rendering_dialog(project2)
+ self.assertTrue(set({"quantizer": 21, "pass":
5}.items()).issubset(set(project2.vcodecsettings.items())))
+ self.check_quality_widget(dialog2,
+ vencoder="x264enc", vcodecsettings=None,
+ preset="youtube",
+ sensitive=True, value=Quality.MEDIUM)
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]