ges: Do not recreate auto-transitions when changing clip assets

Otherwise we loose the configuration of the auto transition, and
it is not required at all in any case.

Fixes https://gitlab.gnome.org/GNOME/pitivi/-/issues/2380

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/208>
This commit is contained in:
Thibault Saunier 2020-09-03 23:32:23 -04:00 committed by GStreamer Merge Bot
parent 2ba222c46b
commit 0030b0833b
9 changed files with 178 additions and 74 deletions

View file

@ -104,6 +104,11 @@ static void
_track_changed_cb (GESTrackElement * track_element, _track_changed_cb (GESTrackElement * track_element,
GParamSpec * arg G_GNUC_UNUSED, GESAutoTransition * self) GParamSpec * arg G_GNUC_UNUSED, GESAutoTransition * self)
{ {
if (self->frozen) {
GST_LOG_OBJECT (self, "Not updating because frozen");
return;
}
if (ges_track_element_get_track (track_element) == NULL) { if (ges_track_element_get_track (track_element) == NULL) {
GST_DEBUG_OBJECT (self, "Neighboor %" GST_PTR_FORMAT GST_DEBUG_OBJECT (self, "Neighboor %" GST_PTR_FORMAT
" removed from track ... auto destructing", track_element); " removed from track ... auto destructing", track_element);
@ -134,12 +139,16 @@ _disconnect_from_source (GESAutoTransition * self, GESTrackElement * source)
} }
void void
ges_auto_transition_set_previous_source (GESAutoTransition * self, ges_auto_transition_set_source (GESAutoTransition * self,
GESTrackElement * source) GESTrackElement * source, GESEdge edge)
{ {
_disconnect_from_source (self, self->previous_source); _disconnect_from_source (self, self->previous_source);
_connect_to_source (self, source); _connect_to_source (self, source);
self->previous_source = source;
if (edge == GES_EDGE_END)
self->next_source = source;
else
self->previous_source = source;
} }
static void static void

View file

@ -3491,11 +3491,12 @@ ges_clip_split_full (GESClip * clip, guint64 position, GError ** error)
gst_object_ref (track)); gst_object_ref (track));
trans = timeline ? trans = timeline ?
ges_timeline_get_auto_transition_at_end (timeline, orig) : NULL; ges_timeline_get_auto_transition_at_edge (timeline, orig,
GES_EDGE_END) : NULL;
if (trans) { if (trans) {
trans->frozen = TRUE; trans->frozen = TRUE;
ges_auto_transition_set_previous_source (trans, copy); ges_auto_transition_set_source (trans, copy, GES_EDGE_START);
transitions = g_list_append (transitions, trans); transitions = g_list_append (transitions, trans);
} }
} }

View file

@ -112,7 +112,8 @@ G_GNUC_INTERNAL void
ges_timeline_freeze_auto_transitions (GESTimeline * timeline, gboolean freeze); ges_timeline_freeze_auto_transitions (GESTimeline * timeline, gboolean freeze);
G_GNUC_INTERNAL GESAutoTransition * G_GNUC_INTERNAL GESAutoTransition *
ges_timeline_get_auto_transition_at_end (GESTimeline * timeline, GESTrackElement * source); ges_timeline_get_auto_transition_at_edge (GESTimeline * timeline, GESTrackElement * source,
GESEdge edge);
G_GNUC_INTERNAL gboolean ges_timeline_is_disposed (GESTimeline* timeline); G_GNUC_INTERNAL gboolean ges_timeline_is_disposed (GESTimeline* timeline);
@ -183,7 +184,7 @@ G_GNUC_INTERNAL gboolean
ges_timeline_get_smart_rendering (GESTimeline *timeline); ges_timeline_get_smart_rendering (GESTimeline *timeline);
G_GNUC_INTERNAL void G_GNUC_INTERNAL void
ges_auto_transition_set_previous_source (GESAutoTransition * self, GESTrackElement * source); ges_auto_transition_set_source (GESAutoTransition * self, GESTrackElement * source, GESEdge edge);

View file

@ -2368,9 +2368,8 @@ create_transition_if_needed (GESTimeline * timeline, GESTrackElement * prev,
ges_timeline_create_transition (timeline, prev, next, NULL, layer, ges_timeline_create_transition (timeline, prev, next, NULL, layer,
_START (next), duration); _START (next), duration);
} else { } else {
GST_INFO ("Already have transition %" GES_FORMAT " between %" GES_FORMAT GST_INFO ("Already have transition %" GST_PTR_FORMAT " between %" GES_FORMAT
" and %" GES_FORMAT, GES_ARGS (trans), GES_ARGS (prev), " and %" GES_FORMAT, trans, GES_ARGS (prev), GES_ARGS (next));
GES_ARGS (next));
} }
} }

View file

@ -1074,8 +1074,8 @@ ges_timeline_find_auto_transition (GESTimeline * timeline,
} }
GESAutoTransition * GESAutoTransition *
ges_timeline_get_auto_transition_at_end (GESTimeline * timeline, ges_timeline_get_auto_transition_at_edge (GESTimeline * timeline,
GESTrackElement * source) GESTrackElement * source, GESEdge edge)
{ {
GList *tmp, *auto_transitions; GList *tmp, *auto_transitions;
GESAutoTransition *ret = NULL; GESAutoTransition *ret = NULL;
@ -1090,7 +1090,10 @@ ges_timeline_get_auto_transition_at_end (GESTimeline * timeline,
/* We already have a transition linked to one of the elements we want to /* We already have a transition linked to one of the elements we want to
* find a transition for */ * find a transition for */
if (auto_trans->previous_source == source) { if (edge == GES_EDGE_END && auto_trans->previous_source == source) {
ret = gst_object_ref (auto_trans);
break;
} else if (edge == GES_EDGE_START && auto_trans->next_source == source) {
ret = gst_object_ref (auto_trans); ret = gst_object_ref (auto_trans);
break; break;
} }

View file

@ -224,6 +224,27 @@ extractable_get_id (GESExtractable * self)
return g_strdup (GES_URI_CLIP (self)->priv->uri); return g_strdup (GES_URI_CLIP (self)->priv->uri);
} }
static GList *
get_auto_transitions_around_source (GESTrackElement * child)
{
GList *transitions = NULL;
GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (child);
gint i;
GESEdge edges[] = { GES_EDGE_START, GES_EDGE_END };
if (!timeline)
return NULL;
for (i = 0; i < G_N_ELEMENTS (edges); i++) {
GESAutoTransition *transition =
ges_timeline_get_auto_transition_at_edge (timeline, child, edges[i]);
if (transition)
transitions = g_list_prepend (transitions, transition);
}
return transitions;
}
static gboolean static gboolean
extractable_set_asset (GESExtractable * self, GESAsset * asset) extractable_set_asset (GESExtractable * self, GESAsset * asset)
{ {
@ -235,9 +256,11 @@ extractable_set_asset (GESExtractable * self, GESAsset * asset)
GESTimelineElement *element = GES_TIMELINE_ELEMENT (self); GESTimelineElement *element = GES_TIMELINE_ELEMENT (self);
GESLayer *layer = ges_clip_get_layer (clip); GESLayer *layer = ges_clip_get_layer (clip);
GList *tmp, *children; GList *tmp, *children;
GHashTable *source_by_track; GHashTable *source_by_track, *auto_transitions_on_sources;
GstClockTime max_duration; GstClockTime max_duration;
GESAsset *prev_asset; GESAsset *prev_asset;
GList *transitions = NULL;
GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (self);
g_return_val_if_fail (GES_IS_URI_CLIP_ASSET (asset), FALSE); g_return_val_if_fail (GES_IS_URI_CLIP_ASSET (asset), FALSE);
@ -300,23 +323,36 @@ extractable_set_asset (GESExtractable * self, GESAsset * asset)
source_by_track = g_hash_table_new_full (NULL, NULL, source_by_track = g_hash_table_new_full (NULL, NULL,
gst_object_unref, gst_object_unref); gst_object_unref, gst_object_unref);
children = ges_container_get_children (container, FALSE); auto_transitions_on_sources = g_hash_table_new_full (NULL, NULL,
gst_object_unref, (GDestroyNotify) g_list_free);
if (timeline)
ges_timeline_freeze_auto_transitions (timeline, TRUE);
children = ges_container_get_children (container, FALSE);
for (tmp = children; tmp; tmp = tmp->next) { for (tmp = children; tmp; tmp = tmp->next) {
GESTrackElement *child = tmp->data; GESTrackElement *child = tmp->data;
GESTrack *track = ges_track_element_get_track (child); GESTrack *track;
/* remove our core children */
if (ges_track_element_is_core (child)) {
if (track)
g_hash_table_insert (source_by_track, gst_object_ref (track),
gst_object_ref (child));
/* removing the track element from its clip whilst it is in a /* remove our core children */
* timeline will remove it from its track */ if (!ges_track_element_is_core (child))
/* removing the core element will also empty its non-core siblings continue;
* from the same track */
ges_container_remove (container, GES_TIMELINE_ELEMENT (child)); track = ges_track_element_get_track (child);
} if (track)
g_hash_table_insert (source_by_track, gst_object_ref (track),
gst_object_ref (child));
transitions = get_auto_transitions_around_source (child);
if (transitions)
g_hash_table_insert (auto_transitions_on_sources, gst_object_ref (child),
transitions);
/* removing the track element from its clip whilst it is in a
* timeline will remove it from its track */
/* removing the core element will also empty its non-core siblings
* from the same track */
ges_container_remove (container, GES_TIMELINE_ELEMENT (child));
} }
g_list_free_full (children, g_object_unref); g_list_free_full (children, g_object_unref);
@ -343,17 +379,32 @@ extractable_set_asset (GESExtractable * self, GESAsset * asset)
* the same source! */ * the same source! */
for (tmp = container->children; tmp; tmp = tmp->next) { for (tmp = container->children; tmp; tmp = tmp->next) {
GESTrackElement *child = tmp->data; GESTrackElement *child = tmp->data;
if (ges_track_element_is_core (child)) { GESTrackElement *orig_source;
GESTrackElement *orig_source = g_hash_table_lookup (source_by_track,
ges_track_element_get_track (child));
contains_core = TRUE;
if (orig_source) { if (!ges_track_element_is_core (child))
ges_track_element_copy_properties (GES_TIMELINE_ELEMENT continue;
(orig_source), GES_TIMELINE_ELEMENT (child));
ges_track_element_copy_bindings (orig_source, child, contains_core = TRUE;
GST_CLOCK_TIME_NONE); orig_source = g_hash_table_lookup (source_by_track,
} ges_track_element_get_track (child));
if (!orig_source)
continue;
ges_track_element_copy_properties (GES_TIMELINE_ELEMENT
(orig_source), GES_TIMELINE_ELEMENT (child));
ges_track_element_copy_bindings (orig_source, child,
GST_CLOCK_TIME_NONE);
transitions =
g_hash_table_lookup (auto_transitions_on_sources, orig_source);
for (; transitions; transitions = transitions->next) {
GESAutoTransition *transition = transitions->data;
if (transition->previous_source == orig_source)
ges_auto_transition_set_source (transition, child, GES_EDGE_START);
else if (transition->next_source == orig_source)
ges_auto_transition_set_source (transition, child, GES_EDGE_END);
} }
} }
} else { } else {
@ -363,6 +414,10 @@ extractable_set_asset (GESExtractable * self, GESAsset * asset)
gst_object_unref (layer); gst_object_unref (layer);
} }
g_hash_table_unref (source_by_track); g_hash_table_unref (source_by_track);
g_hash_table_unref (auto_transitions_on_sources);
if (timeline)
ges_timeline_freeze_auto_transitions (timeline, FALSE);
if (res) { if (res) {
g_free (uriclip->priv->uri); g_free (uriclip->priv->uri);

View file

@ -32,6 +32,12 @@ import os #noqa
import unittest # noqa import unittest # noqa
import tempfile # noqa import tempfile # noqa
try:
gi.require_version("GstTranscoder", "1.0")
from gi.repository import GstTranscoder
except ValueError:
GstTranscoder = None
Gst.init(None) Gst.init(None)
GES.init() GES.init()
@ -99,6 +105,28 @@ def created_project_file(xges):
os.remove(xges_path) os.remove(xges_path)
def can_generate_assets():
if GstTranscoder is None:
return False, "GstTranscoder is not available"
if not Gst.ElementFactory.make("testsrcbin"):
return False, "testbinsrc is not available"
return True, None
@contextlib.contextmanager
def created_video_asset(uri=None, num_bufs=30):
with tempfile.NamedTemporaryFile(suffix=".ogg") as f:
if not uri:
uri = Gst.filename_to_uri(f.name)
transcoder = GstTranscoder.Transcoder.new("testbin://video,num-buffers=%s" % num_bufs,
uri, "application/ogg:video/x-theora:audio/x-vorbis")
transcoder.run()
yield uri
def get_asset_uri(name): def get_asset_uri(name):
python_tests_dir = os.path.dirname(os.path.abspath(__file__)) python_tests_dir = os.path.dirname(os.path.abspath(__file__))
assets_dir = os.path.join(python_tests_dir, "..", "assets") assets_dir = os.path.join(python_tests_dir, "..", "assets")
@ -217,7 +245,11 @@ class GESSimpleTimelineTest(GESTest):
while len(self.timeline.get_layers()) < layer + 1: while len(self.timeline.get_layers()) < layer + 1:
self.timeline.append_layer() self.timeline.append_layer()
layer = self.timeline.get_layers()[layer] layer = self.timeline.get_layers()[layer]
clip = GES.Asset.request(asset_type, asset_id).extract() if asset_type == GES.UriClip:
asset = GES.UriClipAsset.request_sync(asset_id)
else:
asset = GES.Asset.request(asset_type, asset_id)
clip = asset.extract()
clip.props.start = layer.get_duration() clip.props.start = layer.get_duration()
clip.props.duration = 10 clip.props.duration = 10
self.assertTrue(layer.add_clip(clip)) self.assertTrue(layer.add_clip(clip))

View file

@ -32,12 +32,6 @@ from gi.repository import GES # noqa
import unittest # noqa import unittest # noqa
from unittest import mock from unittest import mock
try:
gi.require_version("GstTranscoder", "1.0")
from gi.repository import GstTranscoder
except ValueError:
GstTranscoder = None
from . import common from . import common
from .common import GESSimpleTimelineTest # noqa from .common import GESSimpleTimelineTest # noqa
@ -66,40 +60,29 @@ class TestTimeline(GESSimpleTimelineTest):
asset = proj.create_asset_sync("file:///png.png", GES.UriClip) asset = proj.create_asset_sync("file:///png.png", GES.UriClip)
self.assertIsNotNone(asset) self.assertIsNotNone(asset)
@unittest.skipIf(GstTranscoder is None, "GstTranscoder is not available") @unittest.skipUnless(*common.can_generate_assets())
@unittest.skipIf(Gst.ElementFactory.make("testsrcbin") is None, "testbinsrc is not available")
def test_reload_asset(self): def test_reload_asset(self):
with tempfile.NamedTemporaryFile(suffix=".ogg") as f: with common.created_video_asset() as uri:
uri = Gst.filename_to_uri(f.name)
transcoder = GstTranscoder.Transcoder.new("testbin://video,num-buffers=30",
uri, "application/ogg:video/x-theora:audio/x-vorbis")
transcoder.run()
asset0 = GES.UriClipAsset.request_sync(uri) asset0 = GES.UriClipAsset.request_sync(uri)
self.assertEqual(asset0.props.duration, Gst.SECOND) self.assertEqual(asset0.props.duration, Gst.SECOND)
transcoder = GstTranscoder.Transcoder.new("testbin://video,num-buffers=60", with common.created_video_asset(uri, 60) as uri:
uri, "application/ogg:video/x-theora:audio/x-vorbis") GES.Asset.needs_reload(GES.UriClip, uri)
transcoder.run() asset1 = GES.UriClipAsset.request_sync(uri)
self.assertEqual(asset1.props.duration, 2 * Gst.SECOND)
self.assertEqual(asset1, asset0)
GES.Asset.needs_reload(GES.UriClip, uri) with common.created_video_asset(uri, 90) as uri:
asset1 = GES.UriClipAsset.request_sync(uri) mainloop = common.create_main_loop()
self.assertEqual(asset1.props.duration, 2 * Gst.SECOND) def asset_loaded_cb(_, res, mainloop):
self.assertEqual(asset1, asset0) asset2 = GES.Asset.request_finish(res)
self.assertEqual(asset2.props.duration, 3 * Gst.SECOND)
self.assertEqual(asset2, asset0)
mainloop.quit()
transcoder = GstTranscoder.Transcoder.new("testbin://video,num-buffers=90", GES.Asset.needs_reload(GES.UriClip, uri)
uri, "application/ogg:video/x-theora:audio/x-vorbis") GES.Asset.request_async(GES.UriClip, uri, None, asset_loaded_cb, mainloop)
transcoder.run() mainloop.run()
mainloop = common.create_main_loop()
def asset_loaded_cb(_, res, mainloop):
asset2 = GES.Asset.request_finish(res)
self.assertEqual(asset2.props.duration, 3 * Gst.SECOND)
self.assertEqual(asset2, asset0)
mainloop.quit()
GES.Asset.needs_reload(GES.UriClip, uri)
GES.Asset.request_async(GES.UriClip, uri, None, asset_loaded_cb, mainloop)
mainloop.run()
def test_asset_metadata_on_reload(self): def test_asset_metadata_on_reload(self):
mainloop = GLib.MainLoop() mainloop = GLib.MainLoop()

View file

@ -201,6 +201,29 @@ class TestTimeline(common.GESSimpleTimelineTest):
] ]
]) ])
@unittest.skipUnless(*common.can_generate_assets())
def test_auto_transition_type_after_setting_proxy_asset(self):
self.track_types = [GES.TrackType.VIDEO]
super().setUp()
self.timeline.props.auto_transition = True
with common.created_video_asset() as uri:
self.append_clip(asset_type=GES.UriClip, asset_id=uri)
self.append_clip(asset_type=GES.UriClip, asset_id=uri).props.start = 5
clip1, transition, clip2 = self.layer.get_clips()
video_transition, = transition.get_children(True)
video_transition.set_transition_type(GES.VideoStandardTransitionType.BAR_WIPE_LR)
self.assertEqual(video_transition.get_transition_type(), GES.VideoStandardTransitionType.BAR_WIPE_LR)
with common.created_video_asset() as uri2:
proxy_asset = GES.UriClipAsset.request_sync(uri2)
clip1.set_asset(proxy_asset)
clip1, transition1, clip2 = self.layer.get_clips()
video_transition1, = transition1.get_children(True)
self.assertEqual(video_transition, video_transition1)
self.assertEqual(video_transition.get_transition_type(), GES.VideoStandardTransitionType.BAR_WIPE_LR)
def test_frame_info(self): def test_frame_info(self):
self.track_types = [GES.TrackType.VIDEO] self.track_types = [GES.TrackType.VIDEO]
super().setUp() super().setUp()
@ -345,12 +368,10 @@ class TestEditing(common.GESSimpleTimelineTest):
def _snapped_cb(timeline, elem1, elem2, position): def _snapped_cb(timeline, elem1, elem2, position):
self.snapped_at.append(position) self.snapped_at.append(position)
Gst.error('%s' % position)
def _snapped_end_cb(timeline, elem1, elem2, position): def _snapped_end_cb(timeline, elem1, elem2, position):
if self.snapped_at: # Ignoring first snap end. if self.snapped_at: # Ignoring first snap end.
self.snapped_at.append(Gst.CLOCK_TIME_NONE) self.snapped_at.append(Gst.CLOCK_TIME_NONE)
Gst.error('%s' % position)
self.timeline.connect("snapping-started", _snapped_cb) self.timeline.connect("snapping-started", _snapped_cb)
self.timeline.connect("snapping-ended", _snapped_end_cb) self.timeline.connect("snapping-ended", _snapped_end_cb)