command-line-formatter: Add a way to format timelines using the format

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/227>
This commit is contained in:
Thibault Saunier 2021-01-15 15:21:06 -03:00
parent 70e3b8ae2a
commit d45e594a31
6 changed files with 366 additions and 13 deletions

View file

@ -509,7 +509,6 @@ _ges_command_line_formatter_add_title_clip (GESTimeline * timeline,
gst_structure_set (structure, "type", G_TYPE_STRING, "GESTitleClip", NULL); gst_structure_set (structure, "type", G_TYPE_STRING, "GESTitleClip", NULL);
gst_structure_set (structure, "asset-id", G_TYPE_STRING, "GESTitleClip", gst_structure_set (structure, "asset-id", G_TYPE_STRING, "GESTitleClip",
NULL); NULL);
GST_ERROR ("Structure: %" GST_PTR_FORMAT, structure);
return _ges_add_clip_from_struct (timeline, structure, error); return _ges_add_clip_from_struct (timeline, structure, error);
} }
@ -809,3 +808,350 @@ ges_command_line_formatter_class_init (GESCommandLineFormatterClass * klass)
formatter_klass->load_from_uri = _load; formatter_klass->load_from_uri = _load;
formatter_klass->rank = GST_RANK_MARGINAL; formatter_klass->rank = GST_RANK_MARGINAL;
} }
/* Copy of GST_ASCII_IS_STRING */
#define ASCII_IS_STRING(c) (g_ascii_isalnum((c)) || ((c) == '_') || \
((c) == '-') || ((c) == '+') || ((c) == '/') || ((c) == ':') || \
((c) == '.'))
static void
_sanitize_argument (const gchar * arg, GString * res)
{
gboolean need_wrap = FALSE;
const gchar *tmp_string;
for (tmp_string = arg; *tmp_string != '\0'; tmp_string++) {
if (!ASCII_IS_STRING (*tmp_string) || (*tmp_string == '\n')) {
need_wrap = TRUE;
break;
}
}
if (!need_wrap) {
g_string_append (res, arg);
return;
}
g_string_append_c (res, '"');
while (*arg != '\0') {
if (*arg == '"' || *arg == '\\') {
g_string_append_c (res, '\\');
} else if (*arg == '\n') {
g_string_append (res, "\\n");
arg++;
continue;
}
g_string_append_c (res, *(arg++));
}
g_string_append_c (res, '"');
}
static gboolean
_serialize_control_binding (GESTrackElement * e, const gchar * prop,
GString * res)
{
GstInterpolationMode mode;
GstControlSource *source = NULL;
GList *timed_values, *tmp;
gboolean absolute = FALSE;
GstControlBinding *binding = ges_track_element_get_control_binding (e, prop);
if (!binding)
return FALSE;
if (!GST_IS_DIRECT_CONTROL_BINDING (binding)) {
g_warning ("Unsupported control binding type: %s",
G_OBJECT_TYPE_NAME (binding));
goto done;
}
g_object_get (binding, "control-source", &source,
"absolute", &absolute, NULL);
if (!GST_IS_INTERPOLATION_CONTROL_SOURCE (source)) {
g_warning ("Unsupported control source type: %s",
G_OBJECT_TYPE_NAME (source));
goto done;
}
g_object_get (source, "mode", &mode, NULL);
g_string_append_printf (res, " +keyframes %s t=%s",
prop, absolute ? "direct-absolute" : "direct");
if (mode != GST_INTERPOLATION_MODE_LINEAR)
g_string_append_printf (res, " mode=%s",
g_enum_get_value (g_type_class_peek (GST_TYPE_INTERPOLATION_MODE),
mode)->value_nick);
timed_values =
gst_timed_value_control_source_get_all
(GST_TIMED_VALUE_CONTROL_SOURCE (source));
for (tmp = timed_values; tmp; tmp = tmp->next) {
gchar strbuf[G_ASCII_DTOSTR_BUF_SIZE];
GstTimedValue *value;
value = (GstTimedValue *) tmp->data;
g_string_append_printf (res, " %f=%s",
(gdouble) value->timestamp / (gdouble) GST_SECOND,
g_ascii_dtostr (strbuf, G_ASCII_DTOSTR_BUF_SIZE, value->value));
}
g_list_free (timed_values);
done:
g_clear_object (&source);
return TRUE;
}
static void
_serialize_object_properties (GObject * object, GESCommandLineOption * option,
gboolean children_props, GString * res)
{
guint n_props, j;
GParamSpec *spec, **pspecs;
GObjectClass *class = G_OBJECT_GET_CLASS (object);
const gchar *ignored_props[] = {
"max-duration", "supported-formats", "priority", "video-direction",
"is-image", NULL,
};
if (!children_props)
pspecs = g_object_class_list_properties (class, &n_props);
else {
pspecs =
ges_timeline_element_list_children_properties (GES_TIMELINE_ELEMENT
(object), &n_props);
g_assert (GES_IS_TRACK_ELEMENT (object));
}
for (j = 0; j < n_props; j++) {
const gchar *name;
gchar *value_str = NULL;
GValue val = { 0 };
gint i;
spec = pspecs[j];
if (!ges_util_can_serialize_spec (spec))
continue;
g_value_init (&val, spec->value_type);
if (!children_props)
g_object_get_property (object, spec->name, &val);
else
ges_timeline_element_get_child_property_by_pspec (GES_TIMELINE_ELEMENT
(object), spec, &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;
}
name = spec->name;
if (!children_props && !g_strcmp0 (name, "in-point"))
name = "inpoint";
for (i = 0; option->properties[i].long_name; i++) {
if (!g_strcmp0 (spec->name, option->properties[i].long_name)) {
if (children_props) {
name = NULL;
} else {
name = option->properties[i].short_name;
if (option->properties[i].type == GST_TYPE_CLOCK_TIME)
value_str =
g_strdup_printf ("%f",
(gdouble) (g_value_get_uint64 (&val) / GST_SECOND));
}
break;
} else if (!g_strcmp0 (spec->name, option->properties[0].long_name)) {
name = NULL;
break;
}
}
for (i = 0; i < G_N_ELEMENTS (ignored_props); i++) {
if (!g_strcmp0 (spec->name, ignored_props[i])) {
name = NULL;
break;
}
}
if (!name) {
g_free (value_str);
continue;
}
if (GES_IS_TRACK_ELEMENT (object) &&
_serialize_control_binding (GES_TRACK_ELEMENT (object), name, res)) {
g_free (value_str);
continue;
}
if (!value_str)
value_str = gst_value_serialize (&val);
g_string_append_printf (res, " %s%s%s",
children_props ? "set-" : "", name, children_props ? " " : "=");
_sanitize_argument (value_str, res);
g_free (value_str);
next:
g_value_unset (&val);
}
g_free (pspecs);
}
static void
_serialize_clip_track_types (GESClip * clip, GESTrackType tt, GString * res)
{
GValue v = G_VALUE_INIT;
gchar *ttype_str;
if (ges_clip_get_supported_formats (clip) == tt)
return;
g_value_init (&v, GES_TYPE_TRACK_TYPE);
g_value_set_flags (&v, ges_clip_get_supported_formats (clip));
ttype_str = gst_value_serialize (&v);
g_string_append_printf (res, " tt=%s", ttype_str);
g_value_reset (&v);
g_free (ttype_str);
}
static void
_serialize_clip_effects (GESClip * clip, GString * res)
{
GList *tmpeffect, *effects;
effects = ges_clip_get_top_effects (clip);
for (tmpeffect = effects; tmpeffect; tmpeffect = tmpeffect->next) {
gchar *bin_desc;
g_object_get (tmpeffect->data, "bin-description", &bin_desc, NULL);
g_string_append_printf (res, " +effect %s", bin_desc);
g_free (bin_desc);
}
g_list_free_full (effects, gst_object_unref);
}
/**
* ges_command_line_formatter_get_timeline_uri:
* @timeline: A GESTimeline to serialize
*
* Since: 1.20
*/
gchar *
ges_command_line_formatter_get_timeline_uri (GESTimeline * timeline)
{
gchar *tmpstr;
GList *tmp;
gint i;
GString *res = g_string_new ("ges:");
GESTrackType tt = 0;
if (!timeline)
goto done;
for (tmp = timeline->tracks; tmp; tmp = tmp->next) {
GstCaps *caps, *default_caps;
GESTrack *tmptrack, *track = tmp->data;
if (GES_IS_VIDEO_TRACK (track))
tmptrack = GES_TRACK (ges_video_track_new ());
else if (GES_IS_AUDIO_TRACK (track))
tmptrack = GES_TRACK (ges_audio_track_new ());
else {
g_warning ("Unhandled track type: %s", G_OBJECT_TYPE_NAME (track));
continue;
}
tt |= track->type;
g_string_append_printf (res, " +track %s",
(track->type == GES_TRACK_TYPE_VIDEO) ? "video" : "audio");
default_caps = ges_track_get_restriction_caps (tmptrack);
caps = ges_track_get_restriction_caps (track);
if (!gst_caps_is_equal (caps, default_caps)) {
tmpstr = gst_caps_serialize (caps, 0);
g_string_append (res, " restrictions=");
_sanitize_argument (tmpstr, res);
g_free (tmpstr);
}
gst_caps_unref (default_caps);
gst_caps_unref (caps);
gst_object_unref (tmptrack);
}
for (tmp = timeline->layers, i = 0; tmp; tmp = tmp->next, i++) {
GList *tmpclip, *clips = ges_layer_get_clips (tmp->data);
GList *tmptrackelem;
for (tmpclip = clips; tmpclip; tmpclip = tmpclip->next) {
GESClip *clip = tmpclip->data;
GESCommandLineOption *option = NULL;
if (GES_IS_TEST_CLIP (clip)) {
GESAsset *asset = ges_extractable_get_asset (GES_EXTRACTABLE (clip));
const gchar *id = ges_asset_get_id (asset);
g_string_append (res, " +test-clip ");
_sanitize_argument (g_enum_get_value (g_type_class_peek
(GES_VIDEO_TEST_PATTERN_TYPE),
ges_test_clip_get_vpattern (GES_TEST_CLIP (clip)))->value_nick,
res);
if (g_strcmp0 (id, "GESTestClip")) {
g_string_append (res, " asset-id=");
_sanitize_argument (id, res);
}
option = &options[TEST_CLIP];
} else if (GES_IS_TITLE_CLIP (clip)) {
g_string_append (res, " +title ");
_sanitize_argument (ges_title_clip_get_text (GES_TITLE_CLIP (clip)),
res);
option = &options[TITLE];
} else if (GES_IS_URI_CLIP (clip)) {
g_string_append (res, " +clip ");
_sanitize_argument (ges_uri_clip_get_uri (GES_URI_CLIP (clip)), res);
option = &options[CLIP];
} else {
g_warning ("Unhandled clip type: %s", G_OBJECT_TYPE_NAME (clip));
continue;
}
_serialize_clip_track_types (clip, tt, res);
if (i)
g_string_append_printf (res, " layer=%d", i);
_serialize_object_properties (G_OBJECT (clip), option, FALSE, res);
_serialize_clip_effects (clip, res);
for (tmptrackelem = GES_CONTAINER_CHILDREN (clip); tmptrackelem;
tmptrackelem = tmptrackelem->next)
_serialize_object_properties (G_OBJECT (tmptrackelem->data), option,
TRUE, res);
}
g_list_free_full (clips, gst_object_unref);
}
done:
return g_string_free (res, FALSE);
{
GstUri *uri = gst_uri_from_string (res->str);
gchar *uri_str = gst_uri_to_string (uri);
g_string_free (res, TRUE);
return uri_str;
}
}

View file

@ -46,4 +46,7 @@ struct _GESCommandLineFormatter
GES_API GES_API
gchar * ges_command_line_formatter_get_help (gint nargs, gchar ** commands); gchar * ges_command_line_formatter_get_help (gint nargs, gchar ** commands);
GES_API
gchar * ges_command_line_formatter_get_timeline_uri (GESTimeline *timeline);
G_END_DECLS G_END_DECLS

View file

@ -106,6 +106,11 @@ GstDebugCategory * _ges_debug (void);
ges_timeline_element_peak_toplevel (GES_TIMELINE_ELEMENT (element)), \ ges_timeline_element_peak_toplevel (GES_TIMELINE_ELEMENT (element)), \
GES_TIMELINE_ELEMENT_SET_SIMPLE) GES_TIMELINE_ELEMENT_SET_SIMPLE)
/************************
* Our property masks *
************************/
#define GES_PARAM_NO_SERIALIZATION (1 << (G_PARAM_USER_SHIFT + 1))
#define SUPRESS_UNUSED_WARNING(a) (void)a #define SUPRESS_UNUSED_WARNING(a) (void)a
G_GNUC_INTERNAL void G_GNUC_INTERNAL void
@ -416,6 +421,8 @@ G_GNUC_INTERNAL gboolean
ges_util_structure_get_clocktime (GstStructure *structure, const gchar *name, ges_util_structure_get_clocktime (GstStructure *structure, const gchar *name,
GstClockTime *val, GESFrameNumber *frames); GstClockTime *val, GESFrameNumber *frames);
G_GNUC_INTERNAL gboolean /* From ges-xml-formatter.c */
ges_util_can_serialize_spec (GParamSpec * spec);
/**************************************************** /****************************************************
* GESContainer * * GESContainer *
@ -578,11 +585,6 @@ G_GNUC_INTERNAL gboolean ges_test_clip_asset_get_natural_size (GESAsset *self,
G_GNUC_INTERNAL gchar *ges_test_source_asset_check_id (GType type, const gchar *id, G_GNUC_INTERNAL gchar *ges_test_source_asset_check_id (GType type, const gchar *id,
GError **error); GError **error);
/************************
* Our property masks *
************************/
#define GES_PARAM_NO_SERIALIZATION (1 << (G_PARAM_USER_SHIFT + 1))
/******************************* /*******************************
* GESMarkerList serialization * * GESMarkerList serialization *
*******************************/ *******************************/

View file

@ -1063,8 +1063,8 @@ append_escaped (GString * str, gchar * tmpstr, guint depth)
g_free (tmpstr); g_free (tmpstr);
} }
static inline gboolean gboolean
_can_serialize_spec (GParamSpec * spec) ges_util_can_serialize_spec (GParamSpec * spec)
{ {
if (!(spec->flags & G_PARAM_WRITABLE)) { if (!(spec->flags & G_PARAM_WRITABLE)) {
GST_LOG ("%s from %s is not writable", GST_LOG ("%s from %s is not writable",
@ -1127,7 +1127,7 @@ _serialize_properties (GObject * object, gint * ret_n_props,
GValue val = { 0 }; GValue val = { 0 };
spec = pspecs[j]; spec = pspecs[j];
if (!_can_serialize_spec (spec)) if (!ges_util_can_serialize_spec (spec))
continue; continue;
_init_value_from_spec_for_serialization (&val, spec); _init_value_from_spec_for_serialization (&val, spec);
@ -1434,7 +1434,7 @@ _save_children_properties (GString * str, GESTimelineElement * element,
GValue val = { 0 }; GValue val = { 0 };
spec = pspecs[i]; spec = pspecs[i];
if (_can_serialize_spec (spec)) { if (ges_util_can_serialize_spec (spec)) {
gchar *spec_name = gchar *spec_name =
g_strdup_printf ("%s::%s", g_type_name (spec->owner_type), g_strdup_printf ("%s::%s", g_type_name (spec->owner_type),
spec->name); spec->name);

View file

@ -82,8 +82,7 @@ ges_src_uri_get_uri (GstURIHandler * handler)
GESSrc *self = GES_SRC (handler); GESSrc *self = GES_SRC (handler);
GESTimeline *timeline = ges_base_bin_get_timeline (GES_BASE_BIN (self)); GESTimeline *timeline = ges_base_bin_get_timeline (GES_BASE_BIN (self));
return timeline ? g_strdup_printf ("ges://%s", return ges_command_line_formatter_get_timeline_uri (timeline);
GST_OBJECT_NAME (timeline)) : NULL;
} }
static gboolean static gboolean

View file

@ -436,12 +436,15 @@ describe_discoverer (GstDiscovererInfo * info)
void void
print_timeline (GESTimeline * timeline) print_timeline (GESTimeline * timeline)
{ {
gchar *uri;
GList *layer, *clip, *clips; GList *layer, *clip, *clips;
if (!timeline->layers) if (!timeline->layers)
return; return;
g_print ("\nTimeline description:\n"); uri = ges_command_line_formatter_get_timeline_uri (timeline);
g_print ("\nTimeline description: `%s`\n", &uri[5]);
g_free (uri);
g_print ("====================\n\n"); g_print ("====================\n\n");
for (layer = timeline->layers; layer; layer = layer->next) { for (layer = timeline->layers; layer; layer = layer->next) {
clips = ges_layer_get_clips (layer->data); clips = ges_layer_get_clips (layer->data);