diff --git a/docs/libs/ges-sections.txt b/docs/libs/ges-sections.txt index 68d0b0ae50..b54d1dae6e 100644 --- a/docs/libs/ges-sections.txt +++ b/docs/libs/ges-sections.txt @@ -454,6 +454,7 @@ ges_timeline_element_roll_end ges_timeline_element_trim ges_timeline_element_get_toplevel_parent ges_timeline_element_copy +ges_timeline_element_paste ges_timeline_element_get_name ges_timeline_element_set_name ges_timeline_element_list_children_properties diff --git a/ges/ges-clip.c b/ges/ges-clip.c index 9dd935a1fb..66a74b2469 100644 --- a/ges/ges-clip.c +++ b/ges/ges-clip.c @@ -625,6 +625,44 @@ _edit (GESContainer * container, GList * layers, return ret; } +static gboolean +_paste (GESTimelineElement * element, GESTimelineElement * ref, + GstClockTime paste_position) +{ + GList *tmp; + GESClip *self = GES_CLIP (element); + GESClip *refclip = GES_CLIP (ref); + + ges_clip_set_moving_from_layer (self, TRUE); + ges_layer_add_clip (refclip->priv->layer, self); + ges_clip_set_moving_from_layer (self, FALSE); + + ges_timeline_element_set_start (GES_TIMELINE_ELEMENT (self), paste_position); + + for (tmp = GES_CONTAINER_CHILDREN (refclip); tmp; tmp = tmp->next) { + GESTrackElement *new_trackelement, *trackelement = + GES_TRACK_ELEMENT (tmp->data); + + new_trackelement = + GES_TRACK_ELEMENT (ges_timeline_element_copy (GES_TIMELINE_ELEMENT + (trackelement), FALSE)); + if (new_trackelement == NULL) { + GST_WARNING_OBJECT (trackelement, "Could not create a copy"); + continue; + } + + ges_container_add (GES_CONTAINER (self), + GES_TIMELINE_ELEMENT (new_trackelement)); + + ges_track_element_copy_properties (GES_TIMELINE_ELEMENT (trackelement), + GES_TIMELINE_ELEMENT (new_trackelement)); + + ges_track_element_copy_bindings (trackelement, new_trackelement, + GST_CLOCK_TIME_NONE); + } + + return TRUE; +} /**************************************************** @@ -715,7 +753,7 @@ ges_clip_class_init (GESClipClass * klass) element_class->set_inpoint = _set_inpoint; element_class->set_priority = _set_priority; element_class->set_max_duration = _set_max_duration; - /* TODO implement the deep_copy Virtual method */ + element_class->paste = _paste; container_class->add_child = _add_child; container_class->remove_child = _remove_child; @@ -1245,7 +1283,7 @@ ges_clip_split (GESClip * clip, guint64 position) ges_track_element_copy_properties (GES_TIMELINE_ELEMENT (trackelement), GES_TIMELINE_ELEMENT (new_trackelement)); - ges_track_element_split_bindings (trackelement, new_trackelement, + ges_track_element_copy_bindings (trackelement, new_trackelement, position - start + inpoint); } diff --git a/ges/ges-container.c b/ges/ges-container.c index 03a8276a34..016b6ddab8 100644 --- a/ges/ges-container.c +++ b/ges/ges-container.c @@ -283,6 +283,31 @@ _get_track_types (GESTimelineElement * object) return types ^ GES_TRACK_TYPE_UNKNOWN; } +static gboolean +_paste (GESTimelineElement * element, GESTimelineElement * ref, + GstClockTime paste_position) +{ + GList *tmp; + GESContainer *self = GES_CONTAINER (element); + GESContainer *refcontainer = GES_CONTAINER (ref); + + for (tmp = GES_CONTAINER_CHILDREN (refcontainer); tmp; tmp = tmp->next) { + ChildMapping *map; + GESTimelineElement *child, *refchild = GES_TIMELINE_ELEMENT (tmp->data); + + map = g_hash_table_lookup (refcontainer->priv->mappings, refchild); + child = ges_timeline_element_copy (GES_TIMELINE_ELEMENT (refchild), TRUE); + + ges_timeline_element_paste (child, paste_position + map->start_offset); + ges_timeline_element_set_timeline (element, + GES_TIMELINE_ELEMENT_TIMELINE (ref)); + ges_container_add (self, child); + } + + return TRUE; +} + + /****************************************** * * * GObject virtual methods implementation * @@ -382,6 +407,7 @@ ges_container_class_init (GESContainerClass * klass) element_class->list_children_properties = _list_children_properties; element_class->lookup_child = _lookup_child; element_class->get_track_types = _get_track_types; + element_class->paste = _paste; /* No default implementations */ klass->remove_child = NULL; diff --git a/ges/ges-group.c b/ges/ges-group.c index d0f70a4e32..abd0102bd1 100644 --- a/ges/ges-group.c +++ b/ges/ges-group.c @@ -598,6 +598,23 @@ _group (GList * containers) return ret; } +static gboolean +_paste (GESTimelineElement * element, GESTimelineElement * ref, + GstClockTime paste_position) +{ + if (GES_TIMELINE_ELEMENT_CLASS (parent_class)->paste (element, + ref, paste_position)) { + + if (GES_CONTAINER_CHILDREN (element)) + timeline_add_group (GES_TIMELINE_ELEMENT_TIMELINE (GES_CONTAINER_CHILDREN + (element)->data), GES_GROUP (element)); + + return TRUE; + } + + return FALSE; +} + /**************************************************** * * @@ -675,6 +692,7 @@ ges_group_class_init (GESGroupClass * klass) element_class->set_inpoint = _set_inpoint; element_class->set_start = _set_start; element_class->set_priority = _set_priority; + element_class->paste = _paste; /* We override start, inpoint, duration and max-duration from GESTimelineElement * in order to makes sure those fields are not serialized. diff --git a/ges/ges-internal.h b/ges/ges-internal.h index 7582078991..d083d4f9df 100644 --- a/ges/ges-internal.h +++ b/ges/ges-internal.h @@ -339,7 +339,7 @@ G_GNUC_INTERNAL guint32 _ges_track_element_get_layer_priority (GESTrackElement G_GNUC_INTERNAL void ges_track_element_copy_properties (GESTimelineElement * element, GESTimelineElement * elementcopy); -G_GNUC_INTERNAL void ges_track_element_split_bindings (GESTrackElement *element, +G_GNUC_INTERNAL void ges_track_element_copy_bindings (GESTrackElement *element, GESTrackElement *new_element, guint64 position); diff --git a/ges/ges-timeline-element.c b/ges/ges-timeline-element.c index 059dbfe3da..2530561704 100644 --- a/ges/ges-timeline-element.c +++ b/ges/ges-timeline-element.c @@ -94,6 +94,8 @@ struct _GESTimelineElementPrivate * The hashtable should look like * {GParamaSpec ---> child}*/ GHashTable *children_props; + + GESTimelineElement *copied_from; }; static gboolean @@ -240,6 +242,14 @@ _set_property (GObject * object, guint property_id, } } +static void +ges_timeline_element_dispose (GObject * object) +{ + GESTimelineElement *self = GES_TIMELINE_ELEMENT (object); + + g_clear_object (&self->priv->copied_from); +} + static void ges_timeline_element_finalize (GObject * self) { @@ -387,6 +397,7 @@ ges_timeline_element_class_init (GESTimelineElementClass * klass) G_SIGNAL_NO_HOOKS, 0, NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 2, G_TYPE_OBJECT, G_TYPE_PARAM); + object_class->dispose = ges_timeline_element_dispose; object_class->finalize = ges_timeline_element_finalize; klass->set_parent = NULL; @@ -1113,6 +1124,10 @@ ges_timeline_element_copy (GESTimelineElement * self, gboolean deep) " on class %s. Can not finish the copy", G_OBJECT_CLASS_NAME (klass)); } + if (deep) { + ret->priv->copied_from = gst_object_ref (self); + } + return ret; } @@ -1661,8 +1676,48 @@ ges_timeline_element_remove_child_property (GESTimelineElement * self, GESTrackType ges_timeline_element_get_track_types (GESTimelineElement * self) { - g_return_if_fail (GES_IS_TIMELINE_ELEMENT (self)); - g_return_if_fail (GES_TIMELINE_ELEMENT_GET_CLASS (self)->get_track_types); + g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), 0); + g_return_val_if_fail (GES_TIMELINE_ELEMENT_GET_CLASS (self)->get_track_types, + 0); return GES_TIMELINE_ELEMENT_GET_CLASS (self)->get_track_types (self); } + +/** + * ges_timeline_element_paste: + * @self: The #GESTimelineElement to paste + * @paste_position: The position in the timeline the element should + * be copied to, meaning it will become the start of @self + * + * Paste @self inside the timeline. @self must have been created + * using ges_timeline_element_copy with recurse=TRUE set, + * otherwise it will fail. + * + * Since: 1.6.0 + */ +gboolean +ges_timeline_element_paste (GESTimelineElement * self, + GstClockTime paste_position) +{ + gboolean res; + g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE); + + if (!self->priv->copied_from) { + GST_ERROR_OBJECT (self, "Is not being 'deeply' copied!"); + + return FALSE; + } + + if (!GES_TIMELINE_ELEMENT_GET_CLASS (self)->paste) { + GST_ERROR_OBJECT (self, "No paste vmethod implemented"); + + return FALSE; + } + + res = GES_TIMELINE_ELEMENT_GET_CLASS (self)->paste (self, + self->priv->copied_from, paste_position); + + g_clear_object (&self->priv->copied_from); + + return res; +} diff --git a/ges/ges-timeline-element.h b/ges/ges-timeline-element.h index 0e521f5f87..906b882645 100644 --- a/ges/ges-timeline-element.h +++ b/ges/ges-timeline-element.h @@ -186,6 +186,8 @@ struct _GESTimelineElementClass gboolean (*roll_end) (GESTimelineElement *self, guint64 end); gboolean (*trim) (GESTimelineElement *self, guint64 start); void (*deep_copy) (GESTimelineElement *self, GESTimelineElement *copy); + gboolean (*paste) (GESTimelineElement *self, GESTimelineElement *ref_element, + GstClockTime paste_position); GParamSpec** (*list_children_properties) (GESTimelineElement * self, guint *n_properties); gboolean (*lookup_child) (GESTimelineElement *self, const gchar *prop_name, @@ -277,6 +279,9 @@ gboolean ges_timeline_element_add_child_property (GESTimelineElement * self, gboolean ges_timeline_element_remove_child_property(GESTimelineElement * self, GParamSpec *pspec); +gboolean ges_timeline_element_paste (GESTimelineElement * self, + GstClockTime paste_position); + GESTrackType ges_timeline_element_get_track_types (GESTimelineElement * self); G_END_DECLS diff --git a/ges/ges-track-element.c b/ges/ges-track-element.c index 3ae4184d31..648131b1ba 100644 --- a/ges/ges-track-element.c +++ b/ges/ges-track-element.c @@ -1282,12 +1282,77 @@ ges_track_element_copy_properties (GESTimelineElement * element, g_free (specs); } +static void +_split_binding (GESTrackElement * element, GESTrackElement * new_element, + guint64 position, GstTimedValueControlSource * source, + GstTimedValueControlSource * new_source, gboolean absolute) +{ + GstTimedValue *last_value = NULL; + gboolean past_position = FALSE; + GList *values, *tmp; + + values = + gst_timed_value_control_source_get_all (GST_TIMED_VALUE_CONTROL_SOURCE + (source)); + + for (tmp = values; tmp; tmp = tmp->next) { + GstTimedValue *value = tmp->data; + if (value->timestamp > position) { + gfloat value_at_pos; + + /* FIXME We should be able to use gst_control_source_get_value so + * all modes are handled. Right now that method only works if the value + * we are looking for is between two actual keyframes which is not enough + * in our case. bug #706621 */ + value_at_pos = + interpolate_values_for_position (last_value, value, position, + absolute); + + past_position = TRUE; + + gst_timed_value_control_source_set (new_source, position, value_at_pos); + gst_timed_value_control_source_set (new_source, value->timestamp, + value->value); + gst_timed_value_control_source_unset (source, value->timestamp); + gst_timed_value_control_source_set (source, position, value_at_pos); + } else if (past_position) { + gst_timed_value_control_source_unset (source, value->timestamp); + gst_timed_value_control_source_set (new_source, value->timestamp, + value->value); + } + last_value = value; + } +} + +static void +_copy_binding (GESTrackElement * element, GESTrackElement * new_element, + guint64 position, GstTimedValueControlSource * source, + GstTimedValueControlSource * new_source, gboolean absolute) +{ + GList *values, *tmp; + + values = + gst_timed_value_control_source_get_all (GST_TIMED_VALUE_CONTROL_SOURCE + (source)); + for (tmp = values; tmp; tmp = tmp->next) { + GstTimedValue *value = tmp->data; + + gst_timed_value_control_source_set (new_source, value->timestamp, + value->value); + } +} + +/* position == GST_CLOCK_TIME_NONE means that we do a simple copy + * other position means that the function will do a splitting + * and thus interpollate the values in the element and new_element + */ void -ges_track_element_split_bindings (GESTrackElement * element, +ges_track_element_copy_bindings (GESTrackElement * element, GESTrackElement * new_element, guint64 position) { GParamSpec **specs; guint n, n_specs; + gboolean absolute; GstControlBinding *binding; GstTimedValueControlSource *source, *new_source; @@ -1295,60 +1360,31 @@ ges_track_element_split_bindings (GESTrackElement * element, ges_track_element_list_children_properties (GES_TRACK_ELEMENT (element), &n_specs); for (n = 0; n < n_specs; ++n) { - GList *values, *tmp; - GstTimedValue *last_value = NULL; - gboolean past_position = FALSE, absolute; GstInterpolationMode mode; binding = ges_track_element_get_control_binding (element, specs[n]->name); if (!binding) continue; - g_object_get (binding, "control_source", &source, NULL); - /* FIXME : this should work as well with other types of control sources */ + g_object_get (binding, "control_source", &source, NULL); if (!GST_IS_TIMED_VALUE_CONTROL_SOURCE (source)) continue; g_object_get (binding, "absolute", &absolute, NULL); + g_object_get (source, "mode", &mode, NULL); new_source = GST_TIMED_VALUE_CONTROL_SOURCE (gst_interpolation_control_source_new ()); - - g_object_get (source, "mode", &mode, NULL); g_object_set (new_source, "mode", mode, NULL); - values = - gst_timed_value_control_source_get_all (GST_TIMED_VALUE_CONTROL_SOURCE - (source)); - for (tmp = values; tmp; tmp = tmp->next) { - GstTimedValue *value = tmp->data; - if (value->timestamp > position) { - gfloat value_at_pos; - - /* FIXME We should be able to use gst_control_source_get_value so - * all modes are handled. Right now that method only works if the value - * we are looking for is between two actual keyframes which is not enough - * in our case. bug #706621 */ - value_at_pos = - interpolate_values_for_position (last_value, value, position, - absolute); - - past_position = TRUE; - - gst_timed_value_control_source_set (new_source, position, value_at_pos); - gst_timed_value_control_source_set (new_source, value->timestamp, - value->value); - gst_timed_value_control_source_unset (source, value->timestamp); - gst_timed_value_control_source_set (source, position, value_at_pos); - } else if (past_position) { - gst_timed_value_control_source_unset (source, value->timestamp); - gst_timed_value_control_source_set (new_source, value->timestamp, - value->value); - } - last_value = value; - } + if (GST_CLOCK_TIME_IS_VALID (position)) + _split_binding (element, new_element, position, source, new_source, + absolute); + else + _copy_binding (element, new_element, position, source, new_source, + absolute); /* We only manage direct (absolute) bindings, see TODO in set_control_source */ if (absolute)