[pitivi] render: Allow using custom audio rates



commit c59a79d64e00f820b40d0faaede9b4ca05f76e0e
Author: Alexandru Băluț <alexandru balut gmail com>
Date:   Thu Mar 18 00:00:20 2021 +0100

    render: Allow using custom audio rates
    
    We don't allow the user to set new custom rates, but we allow using the
    custom audio rate set automatically when importing the first asset into
    the project.
    
    Fixes #2523

 pitivi/dialogs/projectsettings.py | 12 +++---
 pitivi/project.py                 | 17 ++++----
 pitivi/render.py                  | 33 ++++++++-------
 pitivi/utils/ui.py                | 85 +++++++++++++++++++++------------------
 tests/test_utils.py               |  3 +-
 5 files changed, 78 insertions(+), 72 deletions(-)
---
diff --git a/pitivi/dialogs/projectsettings.py b/pitivi/dialogs/projectsettings.py
index 14d18d3bc..9b913913d 100644
--- a/pitivi/dialogs/projectsettings.py
+++ b/pitivi/dialogs/projectsettings.py
@@ -27,7 +27,7 @@ from pitivi.preset import AudioPresetManager
 from pitivi.preset import VideoPresetManager
 from pitivi.utils.ripple_update_group import RippleUpdateGroup
 from pitivi.utils.ui import AUDIO_CHANNELS
-from pitivi.utils.ui import AUDIO_RATES
+from pitivi.utils.ui import create_audio_rates_model
 from pitivi.utils.ui import create_frame_rates_model
 from pitivi.utils.ui import get_combo_value
 from pitivi.utils.ui import set_combo_value
@@ -102,10 +102,6 @@ class ProjectSettingsDialog:
         frame_rate_box.pack_end(self.frame_rate_fraction_widget, True, True, 0)
         self.frame_rate_fraction_widget.show()
 
-        # Populate comboboxes.
-        self.channels_combo.set_model(AUDIO_CHANNELS)
-        self.sample_rate_combo.set_model(AUDIO_RATES)
-
         # Behavior.
         self.widgets_group = RippleUpdateGroup()
         self.widgets_group.add_vertex(self.frame_rate_combo,
@@ -203,7 +199,8 @@ class ProjectSettingsDialog:
         fr_datum = (widget_value.num, widget_value.denom)
         model = create_frame_rates_model(fr_datum)
         self.frame_rate_combo.set_model(model)
-        set_combo_value(combo_widget, widget_value)
+        res = set_combo_value(combo_widget, widget_value)
+        assert res, widget_value
 
     def __video_preset_loaded_cb(self, unused_mgr):
         self.sar = self.get_sar()
@@ -264,9 +261,12 @@ class ProjectSettingsDialog:
             self.video_presets_combo.set_active_id(matching_video_preset)
 
         # Audio
+        self.channels_combo.set_model(AUDIO_CHANNELS)
         res = set_combo_value(self.channels_combo, self.project.audiochannels)
         assert res, self.project.audiochannels
 
+        audio_rates_model = create_audio_rates_model(self.project.audiorate)
+        self.sample_rate_combo.set_model(audio_rates_model)
         res = set_combo_value(self.sample_rate_combo, self.project.audiorate)
         assert res, self.project.audiorate
 
diff --git a/pitivi/project.py b/pitivi/project.py
index 101a5375d..e8840ff47 100644
--- a/pitivi/project.py
+++ b/pitivi/project.py
@@ -24,6 +24,7 @@ import time
 import uuid
 from gettext import gettext as _
 from hashlib import md5
+from typing import Optional
 from urllib.parse import unquote
 
 from gi.repository import GdkPixbuf
@@ -1491,11 +1492,11 @@ class Project(Loggable, GES.Project):
 
         self._load_encoder_settings(profiles)
 
-    def set_container_profile(self, container_profile):
-        """Sets @container_profile as new profile if usable.
+    def set_container_profile(self, container_profile: GstPbutils.EncodingContainerProfile) -> bool:
+        """Sets the specified container profile as new profile if usable.
 
-        Args:
-            container_profile (GstPbutils.EncodingContainerProfile): The profile to use.
+        Returns:
+            True if it has been set successfully.
         """
         if container_profile == self.container_profile:
             return True
@@ -1505,10 +1506,10 @@ class Project(Loggable, GES.Project):
             muxer = Encoders().default_muxer
         container_profile.set_preset_name(muxer)
 
-        video_profile = None
-        audio_profile = None
-        vencoder = None
-        aencoder = None
+        video_profile: Optional[GstPbutils.EncodingVideoProfile] = None
+        audio_profile: Optional[GstPbutils.EncodingAudioProfile] = None
+        vencoder: Optional[str] = None
+        aencoder: Optional[str] = None
         for profile in container_profile.get_profiles():
             if isinstance(profile, GstPbutils.EncodingVideoProfile):
                 video_profile = profile
diff --git a/pitivi/render.py b/pitivi/render.py
index d34549e97..3170ca5f2 100644
--- a/pitivi/render.py
+++ b/pitivi/render.py
@@ -40,8 +40,8 @@ from pitivi.utils.misc import path_from_uri
 from pitivi.utils.misc import show_user_manual
 from pitivi.utils.ripple_update_group import RippleUpdateGroup
 from pitivi.utils.ui import AUDIO_CHANNELS
-from pitivi.utils.ui import AUDIO_RATES
 from pitivi.utils.ui import beautify_eta
+from pitivi.utils.ui import create_audio_rates_model
 from pitivi.utils.ui import create_frame_rates_model
 from pitivi.utils.ui import filter_unsupported_media_files
 from pitivi.utils.ui import get_combo_value
@@ -831,10 +831,6 @@ class RenderDialog(Loggable):
         self.preferred_aencoder = self.project.aencoder
         self.__replaced_assets = {}
 
-        self.channels_combo.set_model(AUDIO_CHANNELS)
-        self.sample_rate_combo.set_model(AUDIO_RATES)
-        self.__initialize_muxers_model()
-        self._display_settings()
         self._display_render_settings()
 
         self.window.connect("delete-event", self._delete_event_cb)
@@ -842,7 +838,7 @@ class RenderDialog(Loggable):
 
         self.presets_manager.connect("profile-updated", self._presets_manager_profile_updated_cb)
 
-        preset_item = self.presets_manager.initial_preset()
+        preset_item: PresetItem = self.presets_manager.initial_preset()
         if preset_item:
             if self.apply_preset(preset_item):
                 self.apply_vcodecsettings_quality(Quality.MEDIUM)
@@ -1018,7 +1014,7 @@ class RenderDialog(Loggable):
 
             self.preset_popover.hide()
 
-    def apply_preset(self, preset_item):
+    def apply_preset(self, preset_item: PresetItem):
         old_profile = self.project.container_profile
         profile = preset_item.profile.copy()
         if not self._set_encoding_profile(profile):
@@ -1038,11 +1034,6 @@ class RenderDialog(Loggable):
         """Handles Project metadata changes."""
         self.update_resolution()
 
-    def __initialize_muxers_model(self):
-        # By default show only supported muxers and encoders.
-        model = self.create_combobox_model(Encoders().muxers)
-        self.muxer_combo.set_model(model)
-
     def create_combobox_model(self, factories):
         """Creates a model for a combobox showing factories.
 
@@ -1118,6 +1109,8 @@ class RenderDialog(Loggable):
         self.scale_spinbutton.set_value(self.project.render_scale)
 
         # Muxer settings
+        model = self.create_combobox_model(Encoders().muxers)
+        self.muxer_combo.set_model(model)
         # This will trigger an update of the codec comboboxes.
         muxer = Encoders().factories_by_name.get(self.project.muxer)
         if muxer:
@@ -1206,8 +1199,13 @@ class RenderDialog(Loggable):
         reduced_model = Gtk.ListStore(*model_headers)
         reduced = []
         for name, value in dict(model).items():
-            ecaps = Gst.Caps(caps_template_expander(caps_template, value))
-            if not caps.intersect(ecaps).is_empty():
+            caps_raw = caps_template_expander(caps_template, value)
+            ecaps = Gst.Caps(caps_raw)
+            if caps.intersect(ecaps).is_empty():
+                self.warning(
+                    "Ignoring value because not supported by the encoder: %s",
+                    caps_raw)
+            else:
                 reduced.append((name, value))
 
         for value in sorted(reduced, key=lambda v: float(v[1])):
@@ -1224,9 +1222,10 @@ class RenderDialog(Loggable):
                     if t.direction == Gst.PadDirection.SINK][0]
 
         caps = template.static_caps.get()
+        model = create_audio_rates_model(self.project.audiorate)
         self._update_valid_restriction_values(caps, self.sample_rate_combo,
                                               "audio/x-raw,rate=(int)%d",
-                                              AUDIO_RATES,
+                                              model,
                                               self.project.audiorate)
 
         self._update_valid_restriction_values(caps, self.channels_combo,
@@ -1241,10 +1240,10 @@ class RenderDialog(Loggable):
         template = [t for t in factory.get_static_pad_templates()
                     if t.direction == Gst.PadDirection.SINK][0]
 
+        caps = template.static_caps.get()
+
         fr_datum = (self.project.videorate.num, self.project.videorate.denom)
         model = create_frame_rates_model(fr_datum)
-
-        caps = template.static_caps.get()
         self._update_valid_restriction_values(
             caps, self.frame_rate_combo,
             "video/x-raw,framerate=(GstFraction)%d/%d", model,
diff --git a/pitivi/utils/ui.py b/pitivi/utils/ui.py
index ef59cc3ce..129b31d90 100644
--- a/pitivi/utils/ui.py
+++ b/pitivi/utils/ui.py
@@ -23,6 +23,8 @@ import urllib.parse
 import urllib.request
 from gettext import gettext as _
 from gettext import ngettext
+from typing import Optional
+from typing import Tuple
 
 import cairo
 from gi.repository import Gdk
@@ -763,27 +765,26 @@ def clear_styles(widget):
         style.remove_class(css_class)
 
 
-def create_model(columns, data):
-    ret = Gtk.ListStore(*columns)
+def create_model(columns, data) -> Gtk.ListStore:
+    model = Gtk.ListStore(*columns)
     for datum in data:
-        ret.append(datum)
-    return ret
+        model.append(datum)
+    return model
 
 
-def create_frame_rates_model(*extra_frames):
-    """Create a framerate model from the list of standard frames list and extra frames(if any).
+def create_frame_rates_model(extra_rate: Optional[Tuple[int, int]] = None) -> Gtk.ListStore:
+    """Creates a framerate model based on our list of standard frame rates.
 
     Args:
-        extra_frames (tuple): extra frames to include in model.
+        extra_rate: An extra frame rate to include in model.
     """
-    final_list = list(standard_frames_list)
-    for frame in extra_frames:
-        if frame not in final_list:
-            final_list.append(frame)
-    final_list.sort(key=lambda x: x[0] / x[1])
+    rates = list(FRAME_RATES)
+    if extra_rate and extra_rate not in rates:
+        rates.append(extra_rate)
+    rates.sort(key=lambda x: x[0] / x[1])
 
     items = []
-    for fps in final_list:
+    for fps in rates:
         fraction = Gst.Fraction(*fps)
         item = (format_framerate(fraction), fraction)
         items.append(item)
@@ -791,6 +792,16 @@ def create_frame_rates_model(*extra_frames):
     return create_model((str, object), items)
 
 
+def create_audio_rates_model(extra_rate: Optional[int] = None):
+    rates = list(AUDIO_RATES)
+    if extra_rate and extra_rate not in rates:
+        rates.append(extra_rate)
+    rates.sort()
+
+    return create_model((str, int),
+                        [(format_audiorate(rate), rate) for rate in rates])
+
+
 def set_combo_value(combo, value):
     def select_specific_row(model, unused_path, iter_, found):
         model_value = model.get_value(iter_, 1)
@@ -874,32 +885,28 @@ AUDIO_CHANNELS = create_model((str, int),
                               [(format_audiochannels(ch), ch)
                                for ch in (8, 6, 4, 2, 1)])
 
-standard_frames_list = [(12, 1),
-                        (15, 1),
-                        (20, 1),
-                        (24000, 1001),
-                        (24, 1),
-                        (25, 1),
-                        (30000, 1001),
-                        (30, 1),
-                        (50, 1),
-                        (60000, 1001),
-                        (60, 1),
-                        (120, 1)
-                        ]
-
-AUDIO_RATES = create_model((str, int),
-                           [(format_audiorate(rate), rate) for rate in (
-                               8000,
-                               11025,
-                               12000,
-                               16000,
-                               22050,
-                               24000,
-                               44100,
-                               48000,
-                               96000
-                           )])
+FRAME_RATES = [(12, 1),
+               (15, 1),
+               (20, 1),
+               (24000, 1001),
+               (24, 1),
+               (25, 1),
+               (30000, 1001),
+               (30, 1),
+               (50, 1),
+               (60000, 1001),
+               (60, 1),
+               (120, 1)]
+
+AUDIO_RATES = [8000,
+               11025,
+               12000,
+               16000,
+               22050,
+               24000,
+               44100,
+               48000,
+               96000]
 
 # This whitelist is made from personal knowledge of file extensions in the wild,
 # from gst-inspect |grep demux,
diff --git a/tests/test_utils.py b/tests/test_utils.py
index fbcaf383e..9f3f1b993 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -278,7 +278,7 @@ class TestColors(common.TestCase):
 class TestCreateFramerateModel(common.TestCase):
 
     def test_create_framerate_model(self):
-        model = create_frame_rates_model((25, 2), (130, 1))
+        model = create_frame_rates_model((25, 2))
         sorted_frameslist = [(12, 1),
                              (25, 2),
                              (15, 1),
@@ -292,7 +292,6 @@ class TestCreateFramerateModel(common.TestCase):
                              (60000, 1001),
                              (60, 1),
                              (120, 1),
-                             (130, 1)
                              ]
         self.assertListEqual([(row[1].num, row[1].denom) for row in model], sorted_frameslist)
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]