a.k.a muting layers.
Adding unit tests and making sure serialization works properly
void
ges_base_xml_formatter_add_layer (GESBaseXmlFormatter * self,
GType extractable_type, guint priority, GstStructure * properties,
- const gchar * metadatas, GError ** error)
+ const gchar * metadatas, gchar ** deactivated_tracks, GError ** error)
{
LayerEntry *entry;
GESAsset *asset;
G_MARKUP_ERROR_INVALID_CONTENT,
"Layer type %s could not be created'",
g_type_name (extractable_type));
- return;
}
+ return;
}
layer = GES_LAYER (ges_asset_extract (asset, error));
+ gst_object_unref (asset);
}
ges_layer_set_priority (layer, priority);
ges_meta_container_add_metas_from_string (GES_META_CONTAINER (layer),
metadatas);
+ if (deactivated_tracks) {
+ gint i;
+ GList *tracks = NULL;
+
+ for (i = 0; deactivated_tracks[i] && deactivated_tracks[i][0] != '\0'; i++) {
+ GESTrack *track =
+ g_hash_table_lookup (priv->tracks, deactivated_tracks[i]);
+
+ if (!track) {
+ GST_ERROR_OBJECT (self,
+ "Unknown deactivated track: %s", deactivated_tracks[i]);
+ continue;
+ }
+
+ tracks = g_list_append (tracks, track);
+ }
+
+ ges_layer_set_active_for_tracks (layer, FALSE, tracks);
+ g_list_free (tracks);
+ }
+
entry = g_slice_new0 (LayerEntry);
entry->layer = gst_object_ref (layer);
entry->auto_trans = auto_transition;
guint priority,
GstStructure *properties,
const gchar *metadatas,
+ gchar **deactivated_tracks,
GError **error);
G_GNUC_INTERNAL void ges_base_xml_formatter_add_track (GESBaseXmlFormatter *self,
GESTrackType track_type,
G_GNUC_INTERNAL gboolean ges_track_element_set_track (GESTrackElement * object, GESTrack * track);
G_GNUC_INTERNAL void ges_track_element_copy_properties (GESTimelineElement * element,
GESTimelineElement * elementcopy);
+G_GNUC_INTERNAL void ges_track_element_set_layer_active (GESTrackElement *element, gboolean active);
G_GNUC_INTERNAL void ges_track_element_copy_bindings (GESTrackElement *element,
GESTrackElement *new_element,
guint32 priority; /* The priority of the layer within the
* containing timeline */
gboolean auto_transition;
+
+ GHashTable *tracks_activness;
};
typedef struct
GESLayer *layer;
} NewAssetUData;
+typedef struct
+{
+ GESTrack *track;
+ GESLayer *layer;
+ gboolean active;
+ gboolean track_disposed;
+} LayerActivnessData;
+
+static void
+_track_disposed_cb (LayerActivnessData * data, GObject * disposed_track)
+{
+ data->track_disposed = TRUE;
+ g_hash_table_remove (data->layer->priv->tracks_activness, data->track);
+}
+
+static void
+layer_activness_data_free (LayerActivnessData * data)
+{
+ if (!data->track_disposed)
+ g_object_weak_unref ((GObject *) data->track,
+ (GWeakNotify) _track_disposed_cb, data);
+ g_free (data);
+}
+
+static LayerActivnessData *
+layer_activness_data_new (GESTrack * track, GESLayer * layer, gboolean active)
+{
+ LayerActivnessData *data = g_new0 (LayerActivnessData, 1);
+
+ data->layer = layer;
+ data->track = track;
+ data->active = active;
+ g_object_weak_ref (G_OBJECT (track), (GWeakNotify) _track_disposed_cb, data);
+
+ return data;
+}
+
enum
{
PROP_0,
{
OBJECT_ADDED,
OBJECT_REMOVED,
+ ACTIVE_CHANGED,
LAST_SIGNAL
};
while (priv->clips_start)
ges_layer_remove_clip (layer, (GESClip *) priv->clips_start->data);
+ g_clear_pointer (&layer->priv->tracks_activness, g_hash_table_unref);
+
G_OBJECT_CLASS (ges_layer_parent_class)->dispose (object);
}
g_signal_new ("clip-removed", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GESLayerClass,
object_removed), NULL, NULL, NULL, G_TYPE_NONE, 1, GES_TYPE_CLIP);
+
+ /**
+ * GESLayer::active-changed:
+ * @layer: The #GESLayer
+ * @active: Whether @layer has been made active or de-active in the @tracks
+ * @tracks: (element-type GESTrack) (transfer none): A list of #GESTrack
+ * which have been activated or deactivated
+ *
+ * Will be emitted whenever the layer is activated or deactivated
+ * for some #GESTrack. See ges_layer_set_active_for_tracks().
+ */
+ ges_layer_signals[ACTIVE_CHANGED] =
+ g_signal_new ("active-changed", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 2,
+ G_TYPE_BOOLEAN, G_TYPE_PTR_ARRAY);
}
static void
self->min_nle_priority = MIN_NLE_PRIO;
self->max_nle_priority = LAYER_HEIGHT + MIN_NLE_PRIO;
+ self->priv->tracks_activness =
+ g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, (GDestroyNotify) layer_activness_data_free);
+
_register_metas (self);
}
gboolean emit_removed)
{
GESLayer *current_layer;
+ GList *tmp;
GST_DEBUG ("layer:%p, clip:%p", layer, clip);
if (layer->timeline)
ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (clip), NULL);
+ for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next)
+ ges_track_element_set_layer_active (tmp->data, TRUE);
+
/* Remove our reference to the clip */
gst_object_unref (clip);
gboolean
ges_layer_add_clip (GESLayer * layer, GESClip * clip)
{
+ GList *tmp;
GESAsset *asset;
GESLayerPrivate *priv;
GESLayer *current_layer;
return FALSE;
}
+ for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) {
+ GESTrack *track = ges_track_element_get_track (tmp->data);
+
+ if (track)
+ ges_track_element_set_layer_active (tmp->data,
+ ges_layer_get_active_for_track (layer, track));
+ }
+
return TRUE;
}
}
return intersecting_clips;
}
+
+/**
+ * ges_layer_get_active_for_track:
+ * @layer: The #GESLayer
+ * @track: The #GESTrack to check if @layer is currently active for
+ *
+ * Gets whether the layer is active for the given track. See
+ * ges_layer_set_active_for_tracks().
+ *
+ * Returns: %TRUE if @layer is active for @track, or %FALSE otherwise.
+ */
+gboolean
+ges_layer_get_active_for_track (GESLayer * layer, GESTrack * track)
+{
+ LayerActivnessData *d;
+
+ g_return_val_if_fail (GES_IS_LAYER (layer), FALSE);
+ g_return_val_if_fail (GES_IS_TRACK (track), FALSE);
+ g_return_val_if_fail (layer->timeline == ges_track_get_timeline (track),
+ FALSE);
+
+ d = g_hash_table_lookup (layer->priv->tracks_activness, track);
+
+ return d ? d->active : TRUE;
+}
+
+/**
+ * ges_layer_set_active_for_tracks:
+ * @layer: The #GESLayer
+ * @active: Whether elements in @tracks should be active or not
+ * @tracks: (transfer none) (element-type GESTrack) (allow-none): The list of
+ * tracks @layer should be (de-)active in, or %NULL to include all the tracks
+ * in the @layer's timeline
+ *
+ * Activate or deactivate track elements in @tracks (or in all tracks if @tracks
+ * is %NULL).
+ *
+ * When a layer is deactivated for a track, all the #GESTrackElement-s in
+ * the track that belong to a #GESClip in the layer will no longer be
+ * active in the track, regardless of their individual
+ * #GESTrackElement:active value.
+ *
+ * Note that by default a layer will be active for all of its
+ * timeline's tracks.
+ *
+ * Returns: %TRUE if the operation worked %FALSE otherwise.
+ */
+gboolean
+ges_layer_set_active_for_tracks (GESLayer * layer, gboolean active,
+ GList * tracks)
+{
+ GList *tmp, *owned_tracks = NULL;
+ GPtrArray *changed_tracks = NULL;
+
+ g_return_val_if_fail (GES_IS_LAYER (layer), FALSE);
+
+ if (!tracks && layer->timeline)
+ owned_tracks = tracks = ges_timeline_get_tracks (layer->timeline);
+
+ for (tmp = tracks; tmp; tmp = tmp->next) {
+ GESTrack *track = tmp->data;
+
+ /* Handle setting timeline later */
+ g_return_val_if_fail (layer->timeline == ges_track_get_timeline (track),
+ FALSE);
+
+ if (ges_layer_get_active_for_track (layer, track) != active) {
+ if (changed_tracks == NULL)
+ changed_tracks = g_ptr_array_new ();
+ g_ptr_array_add (changed_tracks, track);
+ }
+ g_hash_table_insert (layer->priv->tracks_activness, track,
+ layer_activness_data_new (track, layer, active));
+ }
+
+ if (changed_tracks) {
+ g_signal_emit (layer, ges_layer_signals[ACTIVE_CHANGED], 0, active,
+ changed_tracks);
+ g_ptr_array_unref (changed_tracks);
+ }
+ g_list_free_full (owned_tracks, gst_object_unref);
+
+ return TRUE;
+}
GList* ges_layer_get_clips (GESLayer * layer);
GES_API
GstClockTime ges_layer_get_duration (GESLayer *layer);
+GES_API
+gboolean ges_layer_set_active_for_tracks(GESLayer *layer, gboolean active,
+ GList *tracks);
+
+GES_API gboolean ges_layer_get_active_for_track(GESLayer *layer,
+ GESTrack *track);
G_END_DECLS
return duration;
}
+
+static gboolean
+reset_layer_activness (GNode * node, GESLayer * layer)
+{
+ GESTrack *track;
+
+
+ if (!GES_IS_TRACK_ELEMENT (node->data))
+ return FALSE;
+
+ track = ges_track_element_get_track (node->data);
+ if (!track || (ges_timeline_element_get_layer_priority (node->data) !=
+ ges_layer_get_priority (layer)))
+ return FALSE;
+
+ ges_track_element_set_layer_active (node->data,
+ ges_layer_get_active_for_track (layer, track));
+
+ return FALSE;
+}
+
+void
+timeline_tree_reset_layer_active (GNode * root, GESLayer * layer)
+{
+ g_node_traverse (root, G_PRE_ORDER, G_TRAVERSE_LEAFS, -1,
+ (GNodeTraverseFunc) reset_layer_activness, layer);
+}
void
timeline_update_duration (GESTimeline * timeline);
+void timeline_tree_reset_layer_active (GNode *root, GESLayer *layer);
+
void timeline_tree_init_debug (void);
UNLOCK_DYN (timeline);
}
+static void
+layer_active_changed_cb (GESLayer * layer, gboolean active G_GNUC_UNUSED,
+ GPtrArray * tracks G_GNUC_UNUSED, GESTimeline * timeline)
+{
+ timeline_tree_reset_layer_active (timeline->priv->tree, layer);
+}
+
static void
layer_auto_transition_changed_cb (GESLayer * layer,
GParamSpec * arg G_GNUC_UNUSED, GESTimeline * timeline)
G_CALLBACK (layer_priority_changed_cb), timeline);
g_signal_connect (layer, "notify::auto-transition",
G_CALLBACK (layer_auto_transition_changed_cb), timeline);
+ g_signal_connect_after (layer, "active-changed",
+ G_CALLBACK (layer_active_changed_cb), timeline);
GST_DEBUG ("Done adding layer, emitting 'layer-added' signal");
g_signal_emit (timeline, ges_timeline_signals[LAYER_ADDED], 0, layer);
timeline);
g_signal_handlers_disconnect_by_func (layer,
layer_auto_transition_changed_cb, timeline);
+ g_signal_handlers_disconnect_by_func (layer, layer_active_changed_cb,
+ timeline);
timeline->layers = g_list_remove (timeline->layers, layer);
ges_layer_set_timeline (layer, NULL);
gboolean locked; /* If TRUE, then moves in sync with its controlling
* GESClip */
+ gboolean layer_active;
GHashTable *bindings_hashtable; /* We need this if we want to be able to serialize
and deserialize keyframes */
"inpoint", GES_TIMELINE_ELEMENT_INPOINT (object),
"duration", GES_TIMELINE_ELEMENT_DURATION (object),
"priority", GES_TIMELINE_ELEMENT_PRIORITY (object),
- "active", object->active, NULL);
+ "active", object->active & object->priv->layer_active, NULL);
media_duration_factor =
ges_timeline_element_get_media_duration_factor (GES_TIMELINE_ELEMENT
GES_TIMELINE_ELEMENT_DURATION (self) = GST_SECOND;
GES_TIMELINE_ELEMENT_PRIORITY (self) = 0;
self->active = TRUE;
+ self->priv->layer_active = TRUE;
priv->bindings_hashtable = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, NULL);
if (G_UNLIKELY (active == object->active))
return FALSE;
- g_object_set (object->priv->nleobject, "active", active, NULL);
+ g_object_set (object->priv->nleobject, "active",
+ active & object->priv->layer_active, NULL);
object->active = active;
if (GES_TRACK_ELEMENT_GET_CLASS (object)->active_changed)
return ret;
}
+void
+ges_track_element_set_layer_active (GESTrackElement * element, gboolean active)
+{
+ if (element->priv->layer_active == active)
+ return;
+
+ element->priv->layer_active = active;
+ g_object_set (element->priv->nleobject, "active", active & element->active,
+ NULL);
+}
+
/**
* ges_track_element_get_all_control_bindings
* @trackelement: A #GESTrackElement
if (!ges_track_element_is_active (trackelement))
continue;
+ if (priv->timeline) {
+ guint32 layer_prio = GES_TIMELINE_ELEMENT_LAYER_PRIORITY (trackelement);
+
+ if (layer_prio != GES_TIMELINE_ELEMENT_NO_LAYER_PRIORITY) {
+ GESLayer *layer = g_list_nth_data (priv->timeline->layers, layer_prio);
+
+ if (!ges_layer_get_active_for_track (layer, track))
+ continue;
+ }
+ }
+
start = _START (trackelement);
end = start + _DURATION (trackelement);
return res;
}
+static gint
+set_layer_active (GstValidateScenario * scenario, GstValidateAction * action)
+{
+ gboolean active;
+ gint i, layer_prio;
+ GESLayer *layer;
+ GList *tracks = NULL;
+ GstValidateExecuteActionReturn res = GST_VALIDATE_EXECUTE_ACTION_OK;
+ gchar **track_names =
+ gst_validate_utils_get_strv (action->structure, "tracks");
+
+ DECLARE_AND_GET_TIMELINE (scenario, action);
+
+ for (i = 0; track_names[i]; i++) {
+ GESTrack *track =
+ (GESTrack *) gst_bin_get_by_name (GST_BIN (timeline), track_names[i]);
+
+ if (!track) {
+ GST_VALIDATE_REPORT_ACTION (scenario, action,
+ SCENARIO_ACTION_EXECUTION_ERROR,
+ "Could not find track %s", track_names[i]);
+ res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
+ goto done;
+ }
+
+ tracks = g_list_prepend (tracks, track);
+ }
+
+ if (!gst_structure_get_int (action->structure, "layer-priority", &layer_prio)) {
+ GST_VALIDATE_REPORT_ACTION (scenario, action,
+ SCENARIO_ACTION_EXECUTION_ERROR,
+ "Could not find layer from %" GST_PTR_FORMAT, action->structure);
+ res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
+ goto done;
+ }
+ if (!(layer = g_list_nth_data (timeline->layers, layer_prio))) {
+ GST_VALIDATE_REPORT_ACTION (scenario, action,
+ SCENARIO_ACTION_EXECUTION_ERROR, "Could not find layer %d", layer_prio);
+ res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
+ goto done;
+ }
+
+ if (!gst_structure_get_boolean (action->structure, "active", &active)) {
+ GST_VALIDATE_REPORT_ACTION (scenario, action,
+ SCENARIO_ACTION_EXECUTION_ERROR,
+ "Could not find 'active' boolean in %" GST_PTR_FORMAT,
+ action->structure);
+ res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
+ goto done;
+ }
+
+ if (!ges_layer_set_active_for_tracks (layer, active, tracks)) {
+ GST_VALIDATE_REPORT_ACTION (scenario, action,
+ SCENARIO_ACTION_EXECUTION_ERROR,
+ "Could not set active for track defined in %" GST_PTR_FORMAT,
+ action->structure);
+ res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
+ goto done;
+ }
+
+done:
+ g_strfreev (track_names);
+ gst_object_unref (timeline);
+ g_list_free_full (tracks, gst_object_unref);
+
+ return res;
+}
+
#endif
gboolean
{NULL}
}, "Allows to change child property of an object", GST_VALIDATE_ACTION_TYPE_NONE);
+ gst_validate_register_action_type ("set-layer-active", "ges", set_layer_active,
+ (GstValidateActionParameter []) {
+ {
+ .name = "layer-priority",
+ .description = "The priority of the layer to set activness on",
+ .types = "gint",
+ .mandatory = TRUE,
+ },
+ {
+ .name = "active",
+ .description = "The activness of the layer",
+ .types = "gboolean",
+ .mandatory = TRUE,
+ },
+ {
+ .name = "tracks",
+ .description = "tracks",
+ .types = "{string, }",
+ .mandatory = FALSE,
+ },
+ {NULL}
+ }, "Set activness of a layer (on optional tracks).",
+ GST_VALIDATE_ACTION_TYPE_NONE);
+
+ gst_validate_register_action_type ("set-ges-properties", "ges", set_or_check_properties,
+ (GstValidateActionParameter []) {
+ {
+ .name = "element-name",
+ .description = "The name of the element on which to set properties",
+ .types = "string",
+ .mandatory = TRUE,
+ },
+ {NULL}
+ }, "Set `element-name` properties values defined by the"
+ " fields in the following format: `property_name=expected-value`",
+ GST_VALIDATE_ACTION_TYPE_NONE);
+
gst_validate_register_action_type ("check-ges-properties", "ges", set_or_check_properties,
(GstValidateActionParameter []) {
{
#define parent_class ges_xml_formatter_parent_class
#define API_VERSION 0
-#define MINOR_VERSION 6
-#define VERSION 0.5
+#define MINOR_VERSION 7
+#define VERSION 0.7
#define COLLECT_STR_OPT (G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL)
guint priority;
GType extractable_type = G_TYPE_NONE;
const gchar *metadatas = NULL, *properties = NULL, *strprio = NULL,
- *extractable_type_name;
+ *extractable_type_name, *deactivated_tracks_str;
+
+ gchar **deactivated_tracks = NULL;
if (!g_markup_collect_attributes (element_name, attribute_names,
attribute_values, error,
G_MARKUP_COLLECT_STRING, "priority", &strprio,
COLLECT_STR_OPT, "extractable-type-name", &extractable_type_name,
COLLECT_STR_OPT, "properties", &properties,
+ COLLECT_STR_OPT, "deactivated-tracks", &deactivated_tracks_str,
COLLECT_STR_OPT, "metadatas", &metadatas, G_MARKUP_COLLECT_INVALID))
return;
if (errno)
goto convertion_failed;
+ if (deactivated_tracks_str)
+ deactivated_tracks = g_strsplit (deactivated_tracks_str, " ", -1);
+
ges_base_xml_formatter_add_layer (GES_BASE_XML_FORMATTER (self),
- extractable_type, priority, props, metadatas, error);
+ extractable_type, priority, props, metadatas, deactivated_tracks, error);
+
+ g_strfreev (deactivated_tracks);
done:
if (props)
}
static gchar *
-_serialize_properties (GObject * object, const gchar * fieldname, ...)
+_serialize_properties (GObject * object, gint * ret_n_props,
+ const gchar * fieldname, ...)
{
gchar *ret;
guint n_props, j;
GValue val = { 0 };
spec = pspecs[j];
+ if (!_can_serialize_spec (spec))
+ continue;
+
+ _init_value_from_spec_for_serialization (&val, spec);
+ g_object_get_property (object, spec->name, &val);
+ if (gst_value_compare (g_param_spec_get_default_value (spec),
+ &val) == GST_VALUE_EQUAL) {
+ GST_INFO ("Ignoring %s as it is using the default value", spec->name);
+ goto next;
+ }
+
if (spec->value_type == GST_TYPE_CAPS) {
- GstCaps *caps;
gchar *caps_str;
+ const GstCaps *caps = gst_value_get_caps (&val);
- g_object_get (object, spec->name, &caps, NULL);
caps_str = gst_caps_to_string (caps);
gst_structure_set (structure, spec->name, G_TYPE_STRING, caps_str, NULL);
- if (caps)
- gst_caps_unref (caps);
g_free (caps_str);
- } else if (_can_serialize_spec (spec)) {
- _init_value_from_spec_for_serialization (&val, spec);
- g_object_get_property (object, spec->name, &val);
- gst_structure_set_value (structure, spec->name, &val);
- g_value_unset (&val);
+ goto next;
}
+
+ gst_structure_set_value (structure, spec->name, &val);
+
+ next:
+ g_value_unset (&val);
}
g_free (pspecs);
}
ret = gst_structure_to_string (structure);
+ if (ret_n_props)
+ *ret_n_props = gst_structure_n_fields (structure);
gst_structure_free (structure);
return ret;
subproject = ges_extractable_get_asset (GES_EXTRACTABLE (timeline));
substr = g_string_new (NULL);
- properties = _serialize_properties (G_OBJECT (subproject), NULL);
+ properties = _serialize_properties (G_OBJECT (subproject), NULL, NULL);
metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (subproject));
append_escaped (str,
g_markup_printf_escaped
ges_uri_source_asset_get_stream_info (tmp->data);
GstCaps *caps = gst_discoverer_stream_info_get_caps (sinfo);
- properties = _serialize_properties (tmp->data, NULL);
+ properties = _serialize_properties (tmp->data, NULL, NULL);
metas = ges_meta_container_metas_to_string (tmp->data);
capsstr = gst_caps_to_string (caps);
G_UNLOCK (uri_subprojects_map_lock);
}
- properties = _serialize_properties (G_OBJECT (asset), NULL);
+ properties = _serialize_properties (G_OBJECT (asset), NULL, NULL);
metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (asset));
append_escaped (str,
g_markup_printf_escaped
tracks = ges_timeline_get_tracks (timeline);
for (tmp = tracks; tmp; tmp = tmp->next) {
track = GES_TRACK (tmp->data);
- properties = _serialize_properties (G_OBJECT (track), "caps", NULL);
+ properties = _serialize_properties (G_OBJECT (track), NULL, "caps", NULL);
strtmp = gst_caps_to_string (ges_track_get_caps (track));
metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (track));
append_escaped (str,
}
g_list_free_full (tracks, gst_object_unref);
- properties = _serialize_properties (G_OBJECT (trackelement), "start",
+ properties = _serialize_properties (G_OBJECT (trackelement), NULL, "start",
"in-point", "duration", "locked", "max-duration", "name", "priority",
NULL);
metas =
depth);
}
+static inline void
+_save_layer_track_activness (GESXmlFormatter * self, GESLayer * layer,
+ GString * str, GESTimeline * timeline, guint depth)
+{
+ guint nb_tracks = 0, i;
+ GList *tmp, *tracks = ges_timeline_get_tracks (timeline);
+ GArray *deactivated_tracks = g_array_new (TRUE, FALSE, sizeof (gint32));
+
+ for (tmp = tracks; tmp; tmp = tmp->next, nb_tracks++) {
+ if (!ges_layer_get_active_for_track (layer, tmp->data))
+ g_array_append_val (deactivated_tracks, nb_tracks);
+ }
+
+ if (!deactivated_tracks->len) {
+ g_string_append (str, ">\n");
+ goto done;
+ }
+
+ self->priv->min_version = MAX (self->priv->min_version, 7);
+ g_string_append (str, " deactivated-tracks='");
+ for (i = 0; i < deactivated_tracks->len; i++)
+ g_string_append_printf (str, "%d ", g_array_index (deactivated_tracks, gint,
+ i));
+ g_string_append (str, "'>\n");
+
+done:
+ g_array_free (deactivated_tracks, TRUE);
+ g_list_free_full (tracks, gst_object_unref);
+}
+
+static void
+_save_source (GESXmlFormatter * self, GString * str,
+ GESTimelineElement * element, GESTimeline * timeline, GList * tracks,
+ guint depth)
+{
+ gint index, n_props;
+ gboolean serialize;
+ gchar *properties;
+
+ if (!GES_IS_SOURCE (element))
+ return;
+
+ g_object_get (element, "serialize", &serialize, NULL);
+ if (!serialize) {
+ GST_DEBUG_OBJECT (element, "Should not be serialized");
+ return;
+ }
+
+ index =
+ g_list_index (tracks,
+ ges_track_element_get_track (GES_TRACK_ELEMENT (element)));
+ append_escaped (str,
+ g_markup_printf_escaped
+ (" <source track-id='%i' ", index), depth);
+
+ properties = _serialize_properties (G_OBJECT (element), &n_props,
+ "in-point", "priority", "start", "duration", "track", "track-type"
+ "uri", "name", "max-duration", NULL);
+
+ /* Try as possible to allow older versions of GES to load the files */
+ if (n_props) {
+ self->priv->min_version = MAX (self->priv->min_version, 7);
+ g_string_append_printf (str, "properties='%s' ", properties);
+ }
+ g_free (properties);
+
+ _save_children_properties (str, element, depth);
+ append_escaped (str, g_markup_printf_escaped (">\n"), depth);
+ _save_keyframes (str, GES_TRACK_ELEMENT (element), index, depth);
+ append_escaped (str, g_markup_printf_escaped (" </source>\n"),
+ depth);
+}
+
static inline void
_save_layers (GESXmlFormatter * self, GString * str, GESTimeline * timeline,
guint depth)
layer = GES_LAYER (tmplayer->data);
priority = ges_layer_get_priority (layer);
- properties = _serialize_properties (G_OBJECT (layer), "priority", NULL);
+ properties =
+ _serialize_properties (G_OBJECT (layer), NULL, "priority", NULL);
metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (layer));
append_escaped (str,
g_markup_printf_escaped
- (" <layer priority='%i' properties='%s' metadatas='%s'>\n",
+ (" <layer priority='%i' properties='%s' metadatas='%s'",
priority, properties, metas), depth);
g_free (properties);
g_free (metas);
+ _save_layer_track_activness (self, layer, str, timeline, depth);
+
clips = ges_layer_get_clips (layer);
for (tmpclip = clips; tmpclip; tmpclip = tmpclip->next) {
GList *effects, *tmpeffect;
/* We escape all mandatrorry properties that are handled sparetely
* and vtype for StandarTransition as it is the asset ID */
- properties = _serialize_properties (G_OBJECT (clip),
+ properties = _serialize_properties (G_OBJECT (clip), NULL,
"supported-formats", "rate", "in-point", "start", "duration",
"max-duration", "priority", "vtype", "uri", NULL);
extractable_id = ges_extractable_get_id (GES_EXTRACTABLE (clip));
for (tmptrackelement = GES_CONTAINER_CHILDREN (clip); tmptrackelement;
tmptrackelement = tmptrackelement->next) {
- gint index;
- gboolean serialize;
-
- if (!GES_IS_SOURCE (tmptrackelement->data))
- continue;
-
- g_object_get (tmptrackelement->data, "serialize", &serialize, NULL);
- if (!serialize) {
- GST_DEBUG_OBJECT (tmptrackelement->data, "Should not be serialized");
- continue;
- }
-
- index =
- g_list_index (tracks,
- ges_track_element_get_track (tmptrackelement->data));
- append_escaped (str,
- g_markup_printf_escaped (" <source track-id='%i'", index),
- depth);
- _save_children_properties (str, tmptrackelement->data, depth);
- append_escaped (str, g_markup_printf_escaped (">\n"), depth);
- _save_keyframes (str, tmptrackelement->data, index, depth);
- append_escaped (str, g_markup_printf_escaped (" </source>\n"),
+ _save_source (self, str, tmptrackelement->data, timeline, tracks,
depth);
}
-
g_list_free_full (tracks, gst_object_unref);
string_append_with_depth (str, " </clip>\n", depth);
}
}
- properties = _serialize_properties (G_OBJECT (group), NULL);
+ properties = _serialize_properties (G_OBJECT (group), NULL, NULL);
metadatas = ges_meta_container_metas_to_string (GES_META_CONTAINER (group));
self->priv->min_version = MAX (self->priv->min_version, 5);
{
gchar *properties = NULL, *metas = NULL;
- properties = _serialize_properties (G_OBJECT (timeline), "update", "name",
+ properties =
+ _serialize_properties (G_OBJECT (timeline), NULL, "update", "name",
"async-handling", "message-forward", NULL);
ges_meta_container_set_uint64 (GES_META_CONTAINER (timeline), "duration",
if (GST_IS_PRESET (encoder) &&
gst_preset_load_preset (GST_PRESET (encoder), preset)) {
- gchar *settings = _serialize_properties (G_OBJECT (encoder), NULL);
- append_escaped (str,
- g_markup_printf_escaped ("preset-properties='%s' ", settings),
- depth);
+ gchar *settings =
+ _serialize_properties (G_OBJECT (encoder), NULL, NULL);
+ append_escaped (str, g_markup_printf_escaped ("preset-properties='%s' ",
+ settings), depth);
g_free (settings);
}
gst_object_unref (encoder);
if (element) {
if (GST_IS_PRESET (element) &&
gst_preset_load_preset (GST_PRESET (element), profpreset)) {
- gchar *settings = _serialize_properties (G_OBJECT (element), NULL);
+ gchar *settings =
+ _serialize_properties (G_OBJECT (element), NULL, NULL);
append_escaped (str,
g_markup_printf_escaped ("preset-properties='%s' ", settings),
depth);
GESXmlFormatter *self = GES_XML_FORMATTER (formatter);
GESXmlFormatterPrivate *priv = _GET_PRIV (formatter);
- properties = _serialize_properties (G_OBJECT (project), NULL);
+ properties = _serialize_properties (G_OBJECT (project), NULL, NULL);
metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (project));
append_escaped (str,
g_markup_printf_escaped (" <project properties='%s' metadatas='%s'>\n",
'check_video_track_restriction_scale_with_keyframes',
'check_edit_in_frames',
'check_edit_in_frames_with_framerate_mismatch',
+ 'check_layer_activness_gaps',
]
env = environment()
from gi.repository import Gst # noqa
from gi.repository import GES # noqa
from gi.repository import GLib # noqa
+from gi.repository import GObject # noqa
import contextlib # noqa
import os #noqa
import unittest # noqa
return clip
+ def assertElementAreEqual(self, ref, element):
+ self.assertTrue(isinstance(element, type(ref)), "%s and %s do not have the same type!" % (ref, element))
+
+ props = [p for p in ref.list_properties() if p.name not in ['name']
+ and not GObject.type_is_a(p.value_type, GObject.Object)]
+ for p in props:
+ pname = p.name
+ v0 = GObject.Value()
+ v0.init(p.value_type)
+ v0.set_value(ref.get_property(pname))
+
+ v1 = GObject.Value()
+ v1.init(p.value_type)
+ v1.set_value(element.get_property(pname))
+
+ self.assertTrue(Gst.value_compare(v0, v1) == Gst.VALUE_EQUAL,
+ "%s are not equal: %s != %s" % (pname, v0, v1))
+
+ if isinstance(ref, GES.TrackElement):
+ self.assertElementAreEqual(ref.get_nleobject(), element.get_nleobject())
+ return
+
+ if not isinstance(ref, GES.Clip):
+ return
+
+ ttypes = [track.type for track in self.timeline.get_tracks()]
+ for ttype in ttypes:
+ if ttypes.count(ttype) > 1:
+ self.warning("Can't deeply check %s and %s "
+ "(only one track per type supported %s %s found)" % (ref,
+ element, ttypes.count(ttype), ttype))
+ return
+
+ children = element.get_children(False)
+ for ref_child in ref.get_children(False):
+ ref_track = ref_child.get_track()
+ if not ref_track:
+ self.warning("Can't check %s as not in a track" % (ref_child))
+ continue
+
+ child = None
+ for tmpchild in children:
+ if not isinstance(tmpchild, type(ref_child)):
+ continue
+
+ if ref_track.type != tmpchild.get_track().type:
+ continue
+
+ if not isinstance(ref_child, GES.Effect):
+ child = tmpchild
+ break
+ elif ref_child.props.bin_description == child.props.bin_description:
+ child = tmpchild
+ break
+
+ self.assertIsNotNone(child, "Could not find equivalent child %s in %s(%s)" % (ref_child,
+ element, children))
+
+ self.assertElementAreEqual(ref_child, child)
+
+ def check_reload_timeline(self):
+ tmpf = tempfile.NamedTemporaryFile(suffix='.xges')
+ uri = Gst.filename_to_uri(tmpf.name)
+ self.assertTrue(self.timeline.save_to_uri(uri, None, True))
+ project = GES.Project.new(uri)
+ mainloop = create_main_loop()
+ def loaded_cb(unused_project, unused_timeline):
+ mainloop.quit()
+
+ project.connect("loaded", loaded_cb)
+ reloaded_timeline = project.extract()
+
+ mainloop.run()
+ self.assertIsNotNone(reloaded_timeline)
+
+ layers = self.timeline.get_layers()
+ reloaded_layers = reloaded_timeline.get_layers()
+ self.assertEqual(len(layers), len(reloaded_layers))
+ for layer, reloaded_layer in zip(layers, reloaded_layers):
+ clips = layer.get_clips()
+ reloaded_clips = reloaded_layer.get_clips()
+ self.assertEqual(len(clips), len(reloaded_clips))
+ for clip, reloaded_clip in zip(clips, reloaded_clips):
+ self.assertElementAreEqual(clip, reloaded_clip)
+
+ return reloaded_timeline
+
def assertTimelineTopology(self, topology, groups=[]):
res = []
for layer in self.timeline.get_layers():
self.assertEqual(self.timeline.get_frame_at(Gst.SECOND), 60)
self.assertEqual(clip.props.max_duration, Gst.SECOND)
+ def test_layer_active(self):
+ def check_nle_object_activeness(clip, track_type, active=None, ref_clip=None):
+ assert ref_clip is not None or active is not None
+
+ if ref_clip:
+ ref_elem, = ref_clip.find_track_elements(None, track_type, GES.Source)
+ active = ref_elem.get_nleobject().props.active
+
+ elem, = clip.find_track_elements(None, track_type, GES.Source)
+ self.assertIsNotNone(elem)
+ self.assertEqual(elem.get_nleobject().props.active, active)
+
+ def get_tracks(timeline):
+ for track in self.timeline.get_tracks():
+ if track.props.track_type == GES.TrackType.VIDEO:
+ video_track = track
+ else:
+ audio_track = track
+ return video_track, audio_track
+
+
+ def check_set_active_for_tracks(layer, active, tracks, expected_changed_tracks):
+ callback_called = []
+ def _check_active_changed_cb(layer, active, tracks, expected_tracks, expected_active):
+ self.assertEqual(set(tracks), set(expected_tracks))
+ self.assertEqual(active, expected_active)
+ callback_called.append(True)
+
+ layer.connect("active-changed", _check_active_changed_cb, expected_changed_tracks, active)
+ self.assertTrue(layer.set_active_for_tracks(active, tracks))
+ self.layer.disconnect_by_func(_check_active_changed_cb)
+ self.assertEqual(callback_called, [True])
+
+ c0 = self.append_clip()
+ check_nle_object_activeness(c0, GES.TrackType.VIDEO, True)
+ check_nle_object_activeness(c0, GES.TrackType.AUDIO, True)
+
+ elem, = c0.find_track_elements(None, GES.TrackType.AUDIO, GES.Source)
+ elem.props.active = False
+ check_nle_object_activeness(c0, GES.TrackType.VIDEO, True)
+ check_nle_object_activeness(c0, GES.TrackType.AUDIO, False)
+ self.check_reload_timeline()
+ elem.props.active = True
+
+ # Muting audio track
+ video_track, audio_track = get_tracks(self.timeline)
+
+ check_set_active_for_tracks(self.layer, False, [audio_track], [audio_track])
+
+ check_nle_object_activeness(c0, GES.TrackType.VIDEO, True)
+ check_nle_object_activeness(c0, GES.TrackType.AUDIO, False)
+ self.check_reload_timeline()
+
+ c1 = self.append_clip()
+ check_nle_object_activeness(c1, GES.TrackType.VIDEO, True)
+ check_nle_object_activeness(c1, GES.TrackType.AUDIO, False)
+
+ l1 = self.timeline.append_layer()
+ c1.move_to_layer(l1)
+ check_nle_object_activeness(c1, GES.TrackType.VIDEO, True)
+ check_nle_object_activeness(c1, GES.TrackType.AUDIO, True)
+
+ self.assertTrue(c1.edit([], self.layer.get_priority(), GES.EditMode.EDIT_NORMAL,
+ GES.Edge.EDGE_NONE, c1.props.start))
+ check_nle_object_activeness(c1, GES.TrackType.VIDEO, True)
+ check_nle_object_activeness(c1, GES.TrackType.AUDIO, False)
+ self.check_reload_timeline()
+
+ self.assertTrue(self.layer.remove_clip(c1))
+ check_nle_object_activeness(c1, GES.TrackType.VIDEO, True)
+ check_nle_object_activeness(c1, GES.TrackType.AUDIO, True)
+
+ self.assertTrue(self.layer.add_clip(c1))
+ check_nle_object_activeness(c1, GES.TrackType.VIDEO, True)
+ check_nle_object_activeness(c1, GES.TrackType.AUDIO, False)
+
+ check_set_active_for_tracks(self.layer, True, None, [audio_track])
+ check_nle_object_activeness(c1, GES.TrackType.VIDEO, True)
+ check_nle_object_activeness(c1, GES.TrackType.AUDIO, True)
+
+ elem, = c1.find_track_elements(None, GES.TrackType.AUDIO, GES.Source)
+ check_nle_object_activeness(c1, GES.TrackType.VIDEO, True)
+ check_nle_object_activeness(c1, GES.TrackType.AUDIO, True)
+
+ # Force deactivating a specific TrackElement
+ elem.props.active = False
+ check_nle_object_activeness(c1, GES.TrackType.VIDEO, True)
+ check_nle_object_activeness(c1, GES.TrackType.AUDIO, False)
+ self.check_reload_timeline()
+
+ # Try activating a specific TrackElement, that won't change the
+ # underlying nleobject activness
+ check_set_active_for_tracks(self.layer, False, None, [audio_track, video_track])
+ check_nle_object_activeness(c1, GES.TrackType.VIDEO, False)
+ check_nle_object_activeness(c1, GES.TrackType.AUDIO, False)
+
+ elem.props.active = True
+ check_nle_object_activeness(c1, GES.TrackType.VIDEO, False)
+ check_nle_object_activeness(c1, GES.TrackType.AUDIO, False)
+ self.check_reload_timeline()
+
class TestEditing(common.GESSimpleTimelineTest):
--- /dev/null
+description, handles-states=true,
+ ges-options={\
+ "--disable-mixing",
+ "--videosink=fakevideosink",
+ "--audiosink=fakesink"\
+ }
+
+add-clip, name=clip, asset-id="framerate=30/1", layer-priority=0, type=GESTestClip, pattern=blue, duration=5000.0
+set-layer-active, tracks={gesvideotrack0}, active=false, layer-priority=0
+
+pause;
+
+# Make sure the video test src is a gap test src.
+check-property, target-element-factory-name=videotestsrc, property-name=pattern, property-value="100% Black"
+check-property, target-element-factory-name=audiotestsrc, property-name=wave, property-value="Sine"
+
+set-layer-active, tracks={gesvideotrack0}, active=true, layer-priority=0
+set-layer-active, tracks={gesaudiotrack0}, active=false, layer-priority=0
+commit;
+# Make sure the video test src is the GESVideoTestSource and the audio test source is a gap
+check-property, target-element-factory-name=videotestsrc, property-name=pattern, property-value="Blue"
+check-property, target-element-factory-name=audiotestsrc, property-name=wave, property-value="Silence"
+
+stop;
\ No newline at end of file