mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-26 11:41:09 +00:00
ges: Add a way to set layer activeness by track
a.k.a muting layers. Adding unit tests and making sure serialization works properly
This commit is contained in:
parent
7ccc7aa7ca
commit
f9f30c4ced
15 changed files with 707 additions and 59 deletions
|
@ -943,7 +943,7 @@ ges_base_xml_formatter_set_timeline_properties (GESBaseXmlFormatter * self,
|
|||
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;
|
||||
|
@ -967,10 +967,11 @@ ges_base_xml_formatter_add_layer (GESBaseXmlFormatter * self,
|
|||
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);
|
||||
|
@ -988,6 +989,27 @@ ges_base_xml_formatter_add_layer (GESBaseXmlFormatter * self,
|
|||
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;
|
||||
|
|
|
@ -291,6 +291,7 @@ G_GNUC_INTERNAL void ges_base_xml_formatter_add_layer (GESBaseXmlForma
|
|||
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,
|
||||
|
@ -418,6 +419,7 @@ G_GNUC_INTERNAL void layer_set_priority (GESLayer * layer, guint p
|
|||
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,
|
||||
|
|
158
ges/ges-layer.c
158
ges/ges-layer.c
|
@ -65,6 +65,8 @@ struct _GESLayerPrivate
|
|||
guint32 priority; /* The priority of the layer within the
|
||||
* containing timeline */
|
||||
gboolean auto_transition;
|
||||
|
||||
GHashTable *tracks_activness;
|
||||
};
|
||||
|
||||
typedef struct
|
||||
|
@ -73,6 +75,43 @@ 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,
|
||||
|
@ -85,6 +124,7 @@ enum
|
|||
{
|
||||
OBJECT_ADDED,
|
||||
OBJECT_REMOVED,
|
||||
ACTIVE_CHANGED,
|
||||
LAST_SIGNAL
|
||||
};
|
||||
|
||||
|
@ -145,6 +185,8 @@ ges_layer_dispose (GObject * object)
|
|||
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);
|
||||
}
|
||||
|
||||
|
@ -245,6 +287,21 @@ ges_layer_class_init (GESLayerClass * klass)
|
|||
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
|
||||
|
@ -257,6 +314,10 @@ ges_layer_init (GESLayer * self)
|
|||
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);
|
||||
}
|
||||
|
||||
|
@ -420,6 +481,7 @@ ges_layer_remove_clip_internal (GESLayer * layer, GESClip * clip,
|
|||
gboolean emit_removed)
|
||||
{
|
||||
GESLayer *current_layer;
|
||||
GList *tmp;
|
||||
|
||||
GST_DEBUG ("layer:%p, clip:%p", layer, clip);
|
||||
|
||||
|
@ -448,6 +510,9 @@ ges_layer_remove_clip_internal (GESLayer * layer, GESClip * 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);
|
||||
|
||||
|
@ -618,6 +683,7 @@ ges_layer_is_empty (GESLayer * layer)
|
|||
gboolean
|
||||
ges_layer_add_clip (GESLayer * layer, GESClip * clip)
|
||||
{
|
||||
GList *tmp;
|
||||
GESAsset *asset;
|
||||
GESLayerPrivate *priv;
|
||||
GESLayer *current_layer;
|
||||
|
@ -723,6 +789,14 @@ ges_layer_add_clip (GESLayer * layer, GESClip * clip)
|
|||
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;
|
||||
}
|
||||
|
||||
|
@ -879,3 +953,87 @@ ges_layer_get_clips_in_interval (GESLayer * layer, GstClockTime start,
|
|||
}
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -121,5 +121,11 @@ GES_API
|
|||
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
|
||||
|
|
|
@ -1333,3 +1333,30 @@ timeline_tree_get_duration (GNode * root)
|
|||
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -73,4 +73,6 @@ ges_timeline_find_auto_transition (GESTimeline * timeline, GESTrackEleme
|
|||
void
|
||||
timeline_update_duration (GESTimeline * timeline);
|
||||
|
||||
void timeline_tree_reset_layer_active (GNode *root, GESLayer *layer);
|
||||
|
||||
void timeline_tree_init_debug (void);
|
||||
|
|
|
@ -1350,6 +1350,13 @@ add_object_to_tracks (GESTimeline * timeline, GESClip * clip, GESTrack * track)
|
|||
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)
|
||||
|
@ -2050,6 +2057,8 @@ ges_timeline_add_layer (GESTimeline * timeline, GESLayer * layer)
|
|||
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);
|
||||
|
@ -2111,6 +2120,8 @@ ges_timeline_remove_layer (GESTimeline * timeline, GESLayer * 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);
|
||||
|
|
|
@ -63,6 +63,7 @@ struct _GESTrackElementPrivate
|
|||
|
||||
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 */
|
||||
|
@ -302,7 +303,7 @@ ges_track_element_constructed (GObject * gobject)
|
|||
"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
|
||||
|
@ -467,6 +468,7 @@ ges_track_element_init (GESTrackElement * self)
|
|||
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);
|
||||
|
@ -740,7 +742,8 @@ ges_track_element_set_active (GESTrackElement * object, gboolean active)
|
|||
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)
|
||||
|
@ -1050,6 +1053,17 @@ ges_track_element_set_track (GESTrackElement * object, GESTrack * track)
|
|||
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
|
||||
|
|
|
@ -232,6 +232,17 @@ update_gaps (GESTrack * track)
|
|||
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);
|
||||
|
||||
|
|
|
@ -1066,6 +1066,74 @@ done:
|
|||
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
|
||||
|
@ -1404,6 +1472,43 @@ ges_validate_register_action_types (void)
|
|||
{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 []) {
|
||||
{
|
||||
|
|
|
@ -35,8 +35,8 @@
|
|||
|
||||
#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)
|
||||
|
||||
|
@ -471,13 +471,16 @@ _parse_layer (GMarkupParseContext * context, const gchar * element_name,
|
|||
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;
|
||||
|
||||
|
@ -511,8 +514,13 @@ _parse_layer (GMarkupParseContext * context, const gchar * element_name,
|
|||
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)
|
||||
|
@ -1084,7 +1092,8 @@ _init_value_from_spec_for_serialization (GValue * value, GParamSpec * spec)
|
|||
}
|
||||
|
||||
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;
|
||||
|
@ -1097,22 +1106,31 @@ _serialize_properties (GObject * object, const gchar * fieldname, ...)
|
|||
GValue val = { 0 };
|
||||
|
||||
spec = pspecs[j];
|
||||
if (spec->value_type == GST_TYPE_CAPS) {
|
||||
GstCaps *caps;
|
||||
gchar *caps_str;
|
||||
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) {
|
||||
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);
|
||||
|
||||
|
@ -1124,6 +1142,8 @@ _serialize_properties (GObject * object, const gchar * fieldname, ...)
|
|||
}
|
||||
|
||||
ret = gst_structure_to_string (structure);
|
||||
if (ret_n_props)
|
||||
*ret_n_props = gst_structure_n_fields (structure);
|
||||
gst_structure_free (structure);
|
||||
|
||||
return ret;
|
||||
|
@ -1190,7 +1210,7 @@ _save_subproject (GESXmlFormatter * self, GString * str, GESProject * project,
|
|||
|
||||
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
|
||||
|
@ -1248,7 +1268,7 @@ _serialize_streams (GESXmlFormatter * self, GString * str,
|
|||
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);
|
||||
|
||||
|
@ -1299,7 +1319,7 @@ _save_assets (GESXmlFormatter * self, GString * str, GESProject * project,
|
|||
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
|
||||
|
@ -1363,7 +1383,7 @@ _save_tracks (GESXmlFormatter * self, GString * str, GESTimeline * timeline,
|
|||
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,
|
||||
|
@ -1512,7 +1532,7 @@ _save_effect (GString * str, guint clip_id, GESTrackElement * trackelement,
|
|||
}
|
||||
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 =
|
||||
|
@ -1537,6 +1557,79 @@ _save_effect (GString * str, guint clip_id, GESTrackElement * trackelement,
|
|||
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)
|
||||
|
@ -1552,15 +1645,18 @@ _save_layers (GESXmlFormatter * self, GString * str, GESTimeline * timeline,
|
|||
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;
|
||||
|
@ -1579,7 +1675,7 @@ _save_layers (GESXmlFormatter * self, GString * str, GESTimeline * timeline,
|
|||
|
||||
/* 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));
|
||||
|
@ -1629,31 +1725,9 @@ _save_layers (GESXmlFormatter * self, GString * str, GESTimeline * timeline,
|
|||
|
||||
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);
|
||||
|
@ -1695,7 +1769,7 @@ _save_group (GESXmlFormatter * self, GString * str, GList ** seen_groups,
|
|||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
@ -1742,7 +1816,8 @@ _save_timeline (GESXmlFormatter * self, GString * str, GESTimeline * timeline,
|
|||
{
|
||||
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",
|
||||
|
@ -1815,10 +1890,10 @@ _save_stream_profiles (GESXmlFormatter * self, GString * str,
|
|||
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);
|
||||
|
@ -1893,7 +1968,8 @@ _save_encoding_profiles (GESXmlFormatter * self, GString * str,
|
|||
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);
|
||||
|
@ -1960,7 +2036,7 @@ _save_project (GESFormatter * formatter, GString * str, GESProject * project,
|
|||
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",
|
||||
|
|
|
@ -77,6 +77,7 @@ if gstvalidate_dep.found()
|
|||
'check_video_track_restriction_scale_with_keyframes',
|
||||
'check_edit_in_frames',
|
||||
'check_edit_in_frames_with_framerate_mismatch',
|
||||
'check_layer_activness_gaps',
|
||||
]
|
||||
|
||||
env = environment()
|
||||
|
|
|
@ -26,6 +26,7 @@ gi.require_version("GES", "1.0")
|
|||
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
|
||||
|
@ -208,6 +209,93 @@ class GESSimpleTimelineTest(GESTest):
|
|||
|
||||
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():
|
||||
|
|
|
@ -222,6 +222,107 @@ class TestTimeline(common.GESSimpleTimelineTest):
|
|||
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):
|
||||
|
||||
|
|
24
tests/check/scenarios/check_layer_activness_gaps.scenario
Normal file
24
tests/check/scenarios/check_layer_activness_gaps.scenario
Normal file
|
@ -0,0 +1,24 @@
|
|||
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;
|
Loading…
Reference in a new issue