formatter: Serialize Transition border and invert properties

Marking them as children properties and properly allow serializing
clips children properties.

This doesn't handle several TrackElement of a same type with
different property values but this require more worked already
marked as fixme to allow specifying full path of elements in the
children properties API.

See https://gitlab.gnome.org/GNOME/pitivi/issues/1687
This commit is contained in:
Thibault Saunier 2018-07-28 12:16:36 -04:00
parent 5f5cbd111c
commit bfb943be1b
6 changed files with 102 additions and 20 deletions

View file

@ -77,6 +77,7 @@ typedef struct PendingClip
GESLayer *layer; GESLayer *layer;
GstStructure *properties; GstStructure *properties;
GstStructure *children_properties;
gchar *metadatas; gchar *metadatas;
GList *effects; GList *effects;
@ -503,26 +504,26 @@ _loading_done_cb (GESFormatter * self)
static gboolean static gboolean
_set_child_property (GQuark field_id, const GValue * value, _set_child_property (GQuark field_id, const GValue * value,
GESTrackElement * effect) GESTimelineElement * tlelement)
{ {
GParamSpec *pspec; GParamSpec *pspec;
GstElement *element; GObject *object;
/* FIXME: error handling? */ /* FIXME: error handling? */
if (!ges_track_element_lookup_child (effect, if (!ges_timeline_element_lookup_child (tlelement,
g_quark_to_string (field_id), &element, &pspec)) { g_quark_to_string (field_id), &object, &pspec)) {
#ifndef GST_DISABLE_GST_DEBUG #ifndef GST_DISABLE_GST_DEBUG
gchar *tmp = gst_value_serialize (value); gchar *tmp = gst_value_serialize (value);
GST_ERROR_OBJECT (effect, "Could not set %s=%s", GST_ERROR_OBJECT (tlelement, "Could not set %s=%s",
g_quark_to_string (field_id), tmp); g_quark_to_string (field_id), tmp);
g_free (tmp); g_free (tmp);
#endif #endif
return TRUE; return TRUE;
} }
g_object_set_property (G_OBJECT (element), pspec->name, value); g_object_set_property (G_OBJECT (object), pspec->name, value);
g_param_spec_unref (pspec); g_param_spec_unref (pspec);
gst_object_unref (element); gst_object_unref (object);
return TRUE; return TRUE;
} }
@ -538,7 +539,7 @@ _add_object_to_layer (GESBaseXmlFormatterPrivate * priv, const gchar * id,
GESLayer * layer, GESAsset * asset, GstClockTime start, GESLayer * layer, GESAsset * asset, GstClockTime start,
GstClockTime inpoint, GstClockTime duration, GstClockTime inpoint, GstClockTime duration,
GESTrackType track_types, const gchar * metadatas, GESTrackType track_types, const gchar * metadatas,
GstStructure * properties) GstStructure * properties, GstStructure * children_properties)
{ {
GESClip *clip = ges_layer_add_asset (layer, GESClip *clip = ges_layer_add_asset (layer,
asset, start, inpoint, duration, track_types); asset, start, inpoint, duration, track_types);
@ -558,6 +559,10 @@ _add_object_to_layer (GESBaseXmlFormatterPrivate * priv, const gchar * id,
gst_structure_foreach (properties, gst_structure_foreach (properties,
(GstStructureForeachFunc) set_property_foreach, clip); (GstStructureForeachFunc) set_property_foreach, clip);
if (children_properties)
gst_structure_foreach (children_properties,
(GstStructureForeachFunc) _set_child_property, clip);
g_hash_table_insert (priv->containers, g_strdup (id), gst_object_ref (clip)); g_hash_table_insert (priv->containers, g_strdup (id), gst_object_ref (clip));
return clip; return clip;
} }
@ -758,7 +763,7 @@ new_asset_cb (GESAsset * source, GAsyncResult * res, PendingAsset * passet)
clip = clip =
_add_object_to_layer (priv, pend->id, pend->layer, asset, _add_object_to_layer (priv, pend->id, pend->layer, asset,
pend->start, pend->inpoint, pend->duration, pend->track_types, pend->start, pend->inpoint, pend->duration, pend->track_types,
pend->metadatas, pend->properties); pend->metadatas, pend->properties, pend->children_properties);
if (clip == NULL) if (clip == NULL)
continue; continue;
@ -944,6 +949,7 @@ ges_base_xml_formatter_add_clip (GESBaseXmlFormatter * self,
const gchar * id, const char *asset_id, GType type, GstClockTime start, const gchar * id, const char *asset_id, GType type, GstClockTime start,
GstClockTime inpoint, GstClockTime duration, GstClockTime inpoint, GstClockTime duration,
guint layer_prio, GESTrackType track_types, GstStructure * properties, guint layer_prio, GESTrackType track_types, GstStructure * properties,
GstStructure * children_properties,
const gchar * metadatas, GError ** error) const gchar * metadatas, GError ** error)
{ {
GESAsset *asset; GESAsset *asset;
@ -999,6 +1005,8 @@ ges_base_xml_formatter_add_clip (GESBaseXmlFormatter * self,
pclip->layer = gst_object_ref (entry->layer); pclip->layer = gst_object_ref (entry->layer);
pclip->properties = properties ? gst_structure_copy (properties) : NULL; pclip->properties = properties ? gst_structure_copy (properties) : NULL;
pclip->children_properties =
properties ? gst_structure_copy (children_properties) : NULL;
pclip->metadatas = g_strdup (metadatas); pclip->metadatas = g_strdup (metadatas);
/* Add the new pending object to the hashtable */ /* Add the new pending object to the hashtable */
@ -1013,7 +1021,8 @@ ges_base_xml_formatter_add_clip (GESBaseXmlFormatter * self,
} }
nclip = _add_object_to_layer (priv, id, entry->layer, nclip = _add_object_to_layer (priv, id, entry->layer,
asset, start, inpoint, duration, track_types, metadatas, properties); asset, start, inpoint, duration, track_types, metadatas, properties,
children_properties);
if (!nclip) if (!nclip)
return; return;

View file

@ -281,9 +281,8 @@ _lookup_child (GESTimelineElement * self, const gchar * prop_name,
{ {
GList *tmp; GList *tmp;
/* FIXME Implement a synthax to precisely get properties by path */ /* FIXME Implement a syntax to precisely get properties by path */
for (tmp = GES_CONTAINER_CHILDREN (self); tmp; tmp = tmp->next) { for (tmp = GES_CONTAINER_CHILDREN (self); tmp; tmp = tmp->next) {
if (ges_timeline_element_lookup_child (tmp->data, prop_name, child, pspec)) if (ges_timeline_element_lookup_child (tmp->data, prop_name, child, pspec))
return TRUE; return TRUE;
} }

View file

@ -232,6 +232,7 @@ G_GNUC_INTERNAL void ges_base_xml_formatter_add_clip (GESBaseXmlFormatter * self
guint layer_prio, guint layer_prio,
GESTrackType track_types, GESTrackType track_types,
GstStructure *properties, GstStructure *properties,
GstStructure * children_properties,
const gchar *metadatas, const gchar *metadatas,
GError **error); GError **error);
G_GNUC_INTERNAL void ges_base_xml_formatter_add_asset (GESBaseXmlFormatter * self, G_GNUC_INTERNAL void ges_base_xml_formatter_add_asset (GESBaseXmlFormatter * self,

View file

@ -227,6 +227,22 @@ ges_transition_clip_set_property (GObject * object,
} }
} }
static gboolean
_lookup_child (GESTimelineElement * self, const gchar * prop_name,
GObject ** child, GParamSpec ** pspec)
{
GESTimelineElementClass *element_klass =
g_type_class_peek (GES_TYPE_TIMELINE_ELEMENT);
/* Bypass the container implementation as we handle children properties directly */
/* FIXME Implement a syntax to precisely get properties by path */
if (element_klass->lookup_child (self, prop_name, child, pspec))
return TRUE;
return FALSE;
}
static void static void
ges_transition_clip_class_init (GESTransitionClipClass * klass) ges_transition_clip_class_init (GESTransitionClipClass * klass)
{ {
@ -251,6 +267,7 @@ ges_transition_clip_class_init (GESTransitionClipClass * klass)
GES_VIDEO_STANDARD_TRANSITION_TYPE_CROSSFADE, GES_VIDEO_STANDARD_TRANSITION_TYPE_CROSSFADE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
GES_TIMELINE_ELEMENT_CLASS (klass)->lookup_child = _lookup_child;
container_class->child_added = _child_added; container_class->child_added = _child_added;
container_class->child_removed = _child_removed; container_class->child_removed = _child_removed;
@ -287,9 +304,16 @@ _child_added (GESContainer * container, GESTimelineElement * element)
GESTransitionClipPrivate *priv = GES_TRANSITION_CLIP (container)->priv; GESTransitionClipPrivate *priv = GES_TRANSITION_CLIP (container)->priv;
if (GES_IS_VIDEO_TRANSITION (element)) { if (GES_IS_VIDEO_TRANSITION (element)) {
GObjectClass *eklass = G_OBJECT_GET_CLASS (element);
GST_DEBUG_OBJECT (container, "%" GST_PTR_FORMAT " added", element); GST_DEBUG_OBJECT (container, "%" GST_PTR_FORMAT " added", element);
priv->video_transitions = priv->video_transitions =
g_slist_prepend (priv->video_transitions, gst_object_ref (element)); g_slist_prepend (priv->video_transitions, gst_object_ref (element));
ges_timeline_element_add_child_property (GES_TIMELINE_ELEMENT (container),
g_object_class_find_property (eklass, "invert"), G_OBJECT (element));
ges_timeline_element_add_child_property (GES_TIMELINE_ELEMENT (container),
g_object_class_find_property (eklass, "border"), G_OBJECT (element));
} }
} }

View file

@ -465,13 +465,13 @@ _parse_clip (GMarkupParseContext * context,
const gchar ** attribute_values, GESXmlFormatter * self, GError ** error) const gchar ** attribute_values, GESXmlFormatter * self, GError ** error)
{ {
GType type; GType type;
GstStructure *props = NULL; GstStructure *props = NULL, *children_props = NULL;
GESTrackType track_types; GESTrackType track_types;
GstClockTime start, inpoint = 0, duration, layer_prio; GstClockTime start, inpoint = 0, duration, layer_prio;
const gchar *strid, *asset_id, *strstart, *strin, *strduration, *strrate, const gchar *strid, *asset_id, *strstart, *strin, *strduration, *strrate,
*strtrack_types, *strtype, *metadatas = NULL, *properties = *strtrack_types, *strtype, *metadatas = NULL, *properties =
NULL, *strlayer_prio; NULL, *children_properties = NULL, *strlayer_prio;
if (!g_markup_collect_attributes (element_name, attribute_names, if (!g_markup_collect_attributes (element_name, attribute_names,
attribute_values, error, attribute_values, error,
@ -483,6 +483,7 @@ _parse_clip (GMarkupParseContext * context,
G_MARKUP_COLLECT_STRING, "track-types", &strtrack_types, G_MARKUP_COLLECT_STRING, "track-types", &strtrack_types,
G_MARKUP_COLLECT_STRING, "layer-priority", &strlayer_prio, G_MARKUP_COLLECT_STRING, "layer-priority", &strlayer_prio,
COLLECT_STR_OPT, "properties", &properties, COLLECT_STR_OPT, "properties", &properties,
COLLECT_STR_OPT, "children-properties", &children_properties,
COLLECT_STR_OPT, "metadatas", &metadatas, COLLECT_STR_OPT, "metadatas", &metadatas,
COLLECT_STR_OPT, "rate", &strrate, COLLECT_STR_OPT, "rate", &strrate,
COLLECT_STR_OPT, "inpoint", &strin, G_MARKUP_COLLECT_INVALID)) { COLLECT_STR_OPT, "inpoint", &strin, G_MARKUP_COLLECT_INVALID)) {
@ -521,9 +522,15 @@ _parse_clip (GMarkupParseContext * context,
goto wrong_properties; goto wrong_properties;
} }
if (children_properties) {
children_props = gst_structure_from_string (children_properties, NULL);
if (children_props == NULL)
goto wrong_children_properties;
}
ges_base_xml_formatter_add_clip (GES_BASE_XML_FORMATTER (self), ges_base_xml_formatter_add_clip (GES_BASE_XML_FORMATTER (self),
strid, asset_id, type, start, inpoint, duration, layer_prio, strid, asset_id, type, start, inpoint, duration, layer_prio,
track_types, props, metadatas, error); track_types, props, children_props, metadatas, error);
if (props) if (props)
gst_structure_free (props); gst_structure_free (props);
@ -536,6 +543,13 @@ wrong_properties:
element_name, asset_id, properties); element_name, asset_id, properties);
return; return;
wrong_children_properties:
g_set_error (error, G_MARKUP_ERROR,
G_MARKUP_ERROR_INVALID_CONTENT,
"element '%s', Clip %s children properties '%s', could no be deserialized",
element_name, asset_id, children_properties);
return;
convertion_failed: convertion_failed:
g_set_error (error, G_MARKUP_ERROR, g_set_error (error, G_MARKUP_ERROR,
G_MARKUP_ERROR_INVALID_CONTENT, G_MARKUP_ERROR_INVALID_CONTENT,
@ -1001,14 +1015,14 @@ _save_tracks (GESXmlFormatter * self, GString * str, GESTimeline * timeline)
} }
static inline void static inline void
_save_children_properties (GString * str, GESTrackElement * trackelement) _save_children_properties (GString * str, GESTimelineElement * element)
{ {
GstStructure *structure; GstStructure *structure;
GParamSpec **pspecs, *spec; GParamSpec **pspecs, *spec;
guint i, n_props; guint i, n_props;
gchar *struct_str; gchar *struct_str;
pspecs = ges_track_element_list_children_properties (trackelement, &n_props); pspecs = ges_timeline_element_list_children_properties (element, &n_props);
structure = gst_structure_new_empty ("properties"); structure = gst_structure_new_empty ("properties");
for (i = 0; i < n_props; i++) { for (i = 0; i < n_props; i++) {
@ -1021,7 +1035,7 @@ _save_children_properties (GString * str, GESTrackElement * trackelement)
spec->name); spec->name);
_init_value_from_spec_for_serialization (&val, spec); _init_value_from_spec_for_serialization (&val, spec);
ges_track_element_get_child_property_by_pspec (trackelement, spec, &val); ges_timeline_element_get_child_property_by_pspec (element, spec, &val);
gst_structure_set_value (structure, spec_name, &val); gst_structure_set_value (structure, spec_name, &val);
g_free (spec_name); g_free (spec_name);
@ -1146,7 +1160,7 @@ _save_effect (GString * str, guint clip_id, GESTrackElement * trackelement,
g_free (properties); g_free (properties);
g_free (metas); g_free (metas);
_save_children_properties (str, trackelement); _save_children_properties (str, GES_TIMELINE_ELEMENT (trackelement));
append_escaped (str, g_markup_printf_escaped (">\n")); append_escaped (str, g_markup_printf_escaped (">\n"));
_save_keyframes (str, trackelement, -1); _save_keyframes (str, trackelement, -1);
@ -1203,17 +1217,23 @@ _save_layers (GESXmlFormatter * self, GString * str, GESTimeline * timeline)
g_markup_printf_escaped (" <clip id='%i' asset-id='%s'" g_markup_printf_escaped (" <clip id='%i' asset-id='%s'"
" type-name='%s' layer-priority='%i' track-types='%i' start='%" " type-name='%s' layer-priority='%i' track-types='%i' start='%"
G_GUINT64_FORMAT "' duration='%" G_GUINT64_FORMAT "' inpoint='%" G_GUINT64_FORMAT "' duration='%" G_GUINT64_FORMAT "' inpoint='%"
G_GUINT64_FORMAT "' rate='%d' properties='%s' >\n", G_GUINT64_FORMAT "' rate='%d' properties='%s' ",
priv->nbelements, extractable_id, priv->nbelements, extractable_id,
g_type_name (G_OBJECT_TYPE (clip)), priority, g_type_name (G_OBJECT_TYPE (clip)), priority,
ges_clip_get_supported_formats (clip), _START (clip), ges_clip_get_supported_formats (clip), _START (clip),
_DURATION (clip), _INPOINT (clip), 0, properties)); _DURATION (clip), _INPOINT (clip), 0, properties));
if (GES_IS_TRANSITION_CLIP (clip))
_save_children_properties (str, GES_TIMELINE_ELEMENT (clip));
g_string_append (str, ">\n");
g_free (extractable_id); g_free (extractable_id);
g_free (properties); g_free (properties);
g_hash_table_insert (self->priv->element_id, clip, g_hash_table_insert (self->priv->element_id, clip,
GINT_TO_POINTER (priv->nbelements)); GINT_TO_POINTER (priv->nbelements));
/* Effects must always be serialized in the right priority order. /* Effects must always be serialized in the right priority order.
* List order is guaranteed by the fact that ges_clip_get_top_effects * List order is guaranteed by the fact that ges_clip_get_top_effects
* sorts the effects. */ * sorts the effects. */
@ -1223,6 +1243,7 @@ _save_layers (GESXmlFormatter * self, GString * str, GESTimeline * timeline)
GES_TRACK_ELEMENT (tmpeffect->data), timeline); GES_TRACK_ELEMENT (tmpeffect->data), timeline);
} }
tracks = ges_timeline_get_tracks (timeline); tracks = ges_timeline_get_tracks (timeline);
for (tmptrackelement = GES_CONTAINER_CHILDREN (clip); tmptrackelement; for (tmptrackelement = GES_CONTAINER_CHILDREN (clip); tmptrackelement;

View file

@ -19,6 +19,8 @@
from . import overrides_hack from . import overrides_hack
import tempfile
import gi import gi
gi.require_version("Gst", "1.0") gi.require_version("Gst", "1.0")
gi.require_version("GES", "1.0") gi.require_version("GES", "1.0")
@ -68,6 +70,32 @@ class TestCopyPaste(unittest.TestCase):
self.assertEqual(len(self.layer.get_clips()), 2) self.assertEqual(len(self.layer.get_clips()), 2)
class TestTransitionClip(unittest.TestCase):
def test_serialize_invert(self):
timeline = GES.Timeline.new()
timeline.add_track(GES.VideoTrack.new())
layer = timeline.append_layer()
clip1 = GES.TransitionClip.new_for_nick("crossfade")
clip1.props.duration = Gst.SECOND
self.assertTrue(layer.add_clip(clip1))
vtransition, = clip1.children
vtransition.set_inverted(True)
self.assertEqual(vtransition.props.invert, True)
with tempfile.NamedTemporaryFile() as tmpxges:
uri = Gst.filename_to_uri(tmpxges.name)
timeline.save_to_uri(uri, None, True)
timeline = GES.Timeline.new_from_uri(uri)
self.assertIsNotNone(timeline)
layer, = timeline.get_layers()
clip, = layer.get_clips()
vtransition, = clip.children
self.assertEqual(vtransition.props.invert, True)
class TestTitleClip(unittest.TestCase): class TestTitleClip(unittest.TestCase):
def testSetColor(self): def testSetColor(self):