mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-01-11 09:55:36 +00:00
tools: Implement a new CLI interface for the timeline creation
This commit is contained in:
parent
2ffa9477e9
commit
ecf167e95e
2 changed files with 201 additions and 144 deletions
|
@ -27,6 +27,7 @@
|
|||
#include <glib.h>
|
||||
#include <glib/gprintf.h>
|
||||
#include <ges/ges.h>
|
||||
#include "../ges/ges-structured-interface.h"
|
||||
#include <gst/pbutils/encoding-profile.h>
|
||||
|
||||
#include <locale.h> /* for LC_ALL */
|
||||
|
@ -177,50 +178,197 @@ project_loaded_cb (GESProject * project, GESTimeline * timeline)
|
|||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
check_time (char *time)
|
||||
static gint /* -1: not present, 0: failure, 1: OK */
|
||||
_convert_to_clocktime (GstStructure * structure, const gchar * name,
|
||||
GstClockTime default_value)
|
||||
{
|
||||
static GRegex *re = NULL;
|
||||
gint res = 1;
|
||||
gdouble val;
|
||||
GValue d_val = { 0 };
|
||||
GstClockTime timestamp;
|
||||
const GValue *gvalue = gst_structure_get_value (structure, name);
|
||||
|
||||
if (!re) {
|
||||
if (NULL == (re = g_regex_new ("^[0-9]+(.[0-9]+)?$", G_REGEX_EXTENDED, 0,
|
||||
NULL)))
|
||||
return FALSE;
|
||||
if (gvalue == NULL) {
|
||||
timestamp = default_value;
|
||||
|
||||
res = -1;
|
||||
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (g_regex_match (re, time, 0, NULL))
|
||||
return TRUE;
|
||||
return FALSE;
|
||||
if (G_VALUE_TYPE (gvalue) == GST_TYPE_CLOCK_TIME)
|
||||
return 1;
|
||||
|
||||
g_value_init (&d_val, G_TYPE_DOUBLE);
|
||||
if (!g_value_transform (gvalue, &d_val)) {
|
||||
GST_ERROR ("Could not get timestamp for %s", name);
|
||||
|
||||
return 0;
|
||||
}
|
||||
val = g_value_get_double ((const GValue *) &d_val);
|
||||
|
||||
if (val == -1.0)
|
||||
timestamp = GST_CLOCK_TIME_NONE;
|
||||
else
|
||||
timestamp = val * GST_SECOND;
|
||||
|
||||
done:
|
||||
gst_structure_set (structure, name, G_TYPE_UINT64, timestamp, NULL);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static guint64
|
||||
str_to_time (char *time)
|
||||
typedef struct
|
||||
{
|
||||
gdouble nsecs;
|
||||
const gchar *long_name;
|
||||
const gchar *short_name;
|
||||
GType type;
|
||||
const gchar *new_name;
|
||||
} Properties;
|
||||
|
||||
g_return_val_if_fail (check_time (time), 0);
|
||||
|
||||
nsecs = g_ascii_strtod (time, NULL);
|
||||
|
||||
return nsecs * GST_SECOND;
|
||||
}
|
||||
|
||||
static void
|
||||
_clip_added_cb (GESLayer * layer, GESClip * clip, GESAsset * asset)
|
||||
static gboolean
|
||||
_cleanup_fields (const Properties * filed_names, GstStructure * structure,
|
||||
GError ** error)
|
||||
{
|
||||
if (GES_IS_TRANSITION_CLIP (clip))
|
||||
ges_extractable_set_asset (GES_EXTRACTABLE (clip), asset);
|
||||
guint i;
|
||||
|
||||
for (i = 0; filed_names[i].long_name; i++) {
|
||||
gboolean exists = FALSE;
|
||||
|
||||
/* Move shortly named fields to longname variante */
|
||||
if (gst_structure_has_field (structure, filed_names[i].short_name)) {
|
||||
exists = TRUE;
|
||||
|
||||
if (gst_structure_has_field (structure, filed_names[i].long_name)) {
|
||||
*error = g_error_new (GES_ERROR, 0, "Using short and long name"
|
||||
" at the same time for property: %s, which one should I use?!",
|
||||
filed_names[i].long_name);
|
||||
|
||||
return FALSE;
|
||||
} else {
|
||||
const GValue *val =
|
||||
gst_structure_get_value (structure, filed_names[i].short_name);
|
||||
|
||||
gst_structure_set_value (structure, filed_names[i].long_name, val);
|
||||
gst_structure_remove_field (structure, filed_names[i].short_name);
|
||||
}
|
||||
} else if (gst_structure_has_field (structure, filed_names[i].long_name)) {
|
||||
exists = TRUE;
|
||||
}
|
||||
|
||||
if (exists) {
|
||||
if (filed_names[i].type == GST_TYPE_CLOCK_TIME) {
|
||||
if (_convert_to_clocktime (structure, filed_names[i].long_name, 0) == 0) {
|
||||
*error = g_error_new (GES_ERROR, 0, "Could not convert"
|
||||
" %s to GstClockTime", filed_names[i].long_name);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (filed_names[i].new_name
|
||||
&& gst_structure_has_field (structure, filed_names[i].long_name)) {
|
||||
const GValue *val =
|
||||
gst_structure_get_value (structure, filed_names[i].long_name);
|
||||
|
||||
gst_structure_set_value (structure, filed_names[i].new_name, val);
|
||||
gst_structure_remove_field (structure, filed_names[i].long_name);
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
_add_clip (GESTimeline * timeline, GstStructure * structure, GError ** error)
|
||||
{
|
||||
const Properties filed_names[] = {
|
||||
{"uri", "n", 0, "asset-id"},
|
||||
{"name", "n", 0, NULL},
|
||||
{"start", "s", GST_TYPE_CLOCK_TIME, NULL},
|
||||
{"duration", "d", GST_TYPE_CLOCK_TIME, NULL},
|
||||
{"inpoint", "i", GST_TYPE_CLOCK_TIME, NULL},
|
||||
{"track-types", "tt", 0, NULL},
|
||||
{NULL},
|
||||
};
|
||||
|
||||
if (!_cleanup_fields (filed_names, structure, error))
|
||||
return FALSE;
|
||||
|
||||
gst_structure_set (structure, "type", G_TYPE_STRING, "GESUriClip", NULL);
|
||||
|
||||
GST_ERROR ("Adding a clip %" GST_PTR_FORMAT, structure);
|
||||
|
||||
return _ges_add_add_clip_from_struct (timeline, structure, error);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
_add_effect (GESTimeline * timeline, GstStructure * structure, GError ** error)
|
||||
{
|
||||
const Properties filed_names[] = {
|
||||
{"element-name", "e", 0, NULL},
|
||||
{"bin-description", "d", 0, "asset-id"},
|
||||
{"name", "n", 0, "child-name"},
|
||||
{NULL, NULL, 0, NULL},
|
||||
};
|
||||
|
||||
if (!_cleanup_fields (filed_names, structure, error))
|
||||
return FALSE;
|
||||
|
||||
gst_structure_set (structure, "child-type", G_TYPE_STRING, "GESEffect", NULL);
|
||||
|
||||
GST_ERROR ("Adding a clip %" GST_PTR_FORMAT, structure);
|
||||
|
||||
return _ges_container_add_child_from_struct (timeline, structure, error);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
_set_child_property (GESTimeline * timeline, GstStructure * structure,
|
||||
GError ** error)
|
||||
{
|
||||
return _ges_set_child_property_from_struct (timeline, structure, error);
|
||||
}
|
||||
|
||||
static GOptionEntry timeline_parsing_options[] = {
|
||||
{"clip", 'c', 0.0, G_OPTION_ARG_CALLBACK, &_add_clip,
|
||||
"Adds a clip in the timeline",
|
||||
" start - s: The start position of the element inside the layer.\n"
|
||||
" duration - d: The duration of the clip.\n"
|
||||
" inpoint - i: The inpoint of the clip\n."
|
||||
" track-types - tt: The type of the tracks where the clip should be used:\n"
|
||||
" Examples:\n"
|
||||
" * audio / a\n"
|
||||
" * video / v\n"
|
||||
" * audio+video / a+v\n"
|
||||
" Will default to all the media types in the clip that match the global track-types"},
|
||||
{"effect", 'e', 0.0, G_OPTION_ARG_CALLBACK, &_add_effect,
|
||||
"Adds an effect as decribed by 'bin-description'",
|
||||
" bin-description - d: The description of the effect bin with a gst-launch-style pipeline description."
|
||||
" element-name - d: The name of the element to apply the effect on."
|
||||
/* TODO: Implement that:
|
||||
* " start - s: The start position of the element inside the layer -- implies creation of effect *Clip*.\n"
|
||||
* " duration - d: The duration of the clip -- implies creation of effect *Clip*.\n"
|
||||
* " inpoint - i: The inpoint of the clip-- implies creation of effect *Clip*.\n" */
|
||||
},
|
||||
};
|
||||
|
||||
#define EXEC(func,structure,error) G_STMT_START { \
|
||||
gboolean res = ((ActionFromStructureFunc)func)(timeline, structure, error); \
|
||||
if (!res) {\
|
||||
GST_ERROR ("Could not execute: %" GST_PTR_FORMAT ", error: %s", structure, (*error)->message); \
|
||||
goto build_failure; \
|
||||
} \
|
||||
} G_STMT_END
|
||||
|
||||
static GESTimeline *
|
||||
create_timeline (int nbargs, gchar ** argv, const gchar * proj_uri,
|
||||
create_timeline (GList * structures, const gchar * proj_uri,
|
||||
const gchar * scenario)
|
||||
{
|
||||
GESLayer *layer = NULL;
|
||||
GESTrack *tracka = NULL, *trackv = NULL;
|
||||
guint i;
|
||||
GList *tmp;
|
||||
GESTimeline *timeline;
|
||||
guint i, clip_added_sigid = 0;
|
||||
GstClockTime next_trans_dur = 0;
|
||||
GESTrack *tracka = NULL, *trackv = NULL;
|
||||
GESProject *project = ges_project_new (proj_uri);
|
||||
|
||||
g_signal_connect (project, "error-loading-asset",
|
||||
|
@ -258,114 +406,24 @@ create_timeline (int nbargs, gchar ** argv, const gchar * proj_uri,
|
|||
|
||||
/* Here we've finished initializing our timeline, we're
|
||||
* ready to start using it... by solely working with the layer !*/
|
||||
for (tmp = structures; tmp; tmp = tmp->next) {
|
||||
const gchar *name = gst_structure_get_name (tmp->data);
|
||||
GError *error = NULL;
|
||||
|
||||
for (i = 0; i < nbargs / 3; i++) {
|
||||
GESClip *clip;
|
||||
|
||||
char *source = argv[i * 3];
|
||||
char *arg0 = argv[(i * 3) + 1];
|
||||
guint64 duration = str_to_time (argv[(i * 3) + 2]);
|
||||
|
||||
if (i == 0) {
|
||||
/* We are only going to be doing one layer of clips */
|
||||
layer = (GESLayer *) ges_layer_new ();
|
||||
|
||||
/* Add the tracks and the layer to the timeline */
|
||||
if (!ges_timeline_add_layer (timeline, layer))
|
||||
goto build_failure;
|
||||
}
|
||||
|
||||
if (duration == 0)
|
||||
duration = GST_CLOCK_TIME_NONE;
|
||||
|
||||
if (!g_strcmp0 ("+pattern", source)) {
|
||||
clip = GES_CLIP (ges_test_clip_new_for_nick (arg0));
|
||||
if (!clip) {
|
||||
g_error ("%s is an invalid pattern name!\n", arg0);
|
||||
goto build_failure;
|
||||
}
|
||||
|
||||
g_object_set (G_OBJECT (clip), "duration", duration, NULL);
|
||||
|
||||
g_printf ("Adding <pattern:%s> duration %" GST_TIME_FORMAT "\n", arg0,
|
||||
GST_TIME_ARGS (duration));
|
||||
}
|
||||
|
||||
else if (!g_strcmp0 ("+transition", source)) {
|
||||
GESAsset *asset =
|
||||
ges_asset_request (GES_TYPE_TRANSITION_CLIP, arg0, NULL);
|
||||
|
||||
if (asset == NULL) {
|
||||
g_warning ("Can not create transition %s", arg0);
|
||||
}
|
||||
|
||||
next_trans_dur = duration;
|
||||
clip_added_sigid = g_signal_connect (layer, "clip-added",
|
||||
(GCallback) _clip_added_cb, asset);
|
||||
|
||||
if (g_str_has_prefix (name, "set-")) {
|
||||
EXEC (_set_child_property, tmp->data, &error);
|
||||
continue;
|
||||
} else if (!g_strcmp0 ("+title", source)) {
|
||||
clip = GES_CLIP (ges_title_clip_new ());
|
||||
|
||||
g_object_set (clip, "duration", duration, "text", arg0, NULL);
|
||||
|
||||
g_printf ("Adding <title:%s> duration %" GST_TIME_FORMAT "\n", arg0,
|
||||
GST_TIME_ARGS (duration));
|
||||
}
|
||||
|
||||
else {
|
||||
gchar *uri;
|
||||
GESAsset *asset;
|
||||
guint64 inpoint;
|
||||
|
||||
GError *error = NULL;
|
||||
|
||||
if (!(uri = ensure_uri (source))) {
|
||||
GST_ERROR ("couldn't create uri for '%s'", source);
|
||||
goto build_failure;
|
||||
for (i = 0; i < G_N_ELEMENTS (timeline_parsing_options); i++) {
|
||||
if (gst_structure_has_name (tmp->data,
|
||||
timeline_parsing_options[i].long_name)
|
||||
|| (strlen (name) == 1 &&
|
||||
*name == timeline_parsing_options[i].short_name)) {
|
||||
EXEC (((ActionFromStructureFunc) timeline_parsing_options[i].arg_data),
|
||||
tmp->data, &error);
|
||||
}
|
||||
|
||||
inpoint = str_to_time (argv[i * 3 + 1]);
|
||||
asset = GES_ASSET (ges_uri_clip_asset_request_sync (uri, &error));
|
||||
if (error) {
|
||||
g_printerr ("Can not create asset for %s", uri);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ges_project_add_asset (project, asset);
|
||||
clip = GES_CLIP (ges_asset_extract (asset, &error));
|
||||
if (error) {
|
||||
g_printerr ("Can not extract asset for %s", uri);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!GST_CLOCK_TIME_IS_VALID (duration))
|
||||
duration =
|
||||
GES_TIMELINE_ELEMENT_DURATION (clip) - (GstClockTime) inpoint;
|
||||
|
||||
g_object_set (clip,
|
||||
"in-point", (guint64) inpoint, "duration", (guint64) duration, NULL);
|
||||
|
||||
g_printf ("Adding clip %s inpoint:%" GST_TIME_FORMAT " duration:%"
|
||||
GST_TIME_FORMAT "\n", uri, GST_TIME_ARGS (inpoint),
|
||||
GST_TIME_ARGS (duration));
|
||||
|
||||
g_free (uri);
|
||||
}
|
||||
|
||||
g_object_set (G_OBJECT (clip), "start",
|
||||
ges_layer_get_duration (layer) - next_trans_dur, NULL);
|
||||
|
||||
ges_layer_add_clip (layer, clip);
|
||||
|
||||
if (clip_added_sigid) {
|
||||
g_signal_handler_disconnect (layer, clip_added_sigid);
|
||||
clip_added_sigid = 0;
|
||||
next_trans_dur = 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
done:
|
||||
|
@ -396,7 +454,7 @@ _save_timeline (GESTimeline * timeline, const gchar * load_path)
|
|||
|
||||
static GESPipeline *
|
||||
create_pipeline (GESTimeline ** ret_timeline, gchar * load_path,
|
||||
int argc, char **argv, const gchar * scenario)
|
||||
GList * structures, const gchar * scenario)
|
||||
{
|
||||
gchar *uri = NULL;
|
||||
GESTimeline *timeline = NULL;
|
||||
|
@ -413,7 +471,7 @@ create_pipeline (GESTimeline ** ret_timeline, gchar * load_path,
|
|||
|
||||
pipeline = ges_pipeline_new ();
|
||||
|
||||
if (!(timeline = create_timeline (argc, argv, uri, scenario)))
|
||||
if (!(timeline = create_timeline (structures, uri, scenario)))
|
||||
goto failure;
|
||||
|
||||
if (!load_path)
|
||||
|
@ -897,15 +955,6 @@ main (int argc, gchar ** argv)
|
|||
exit (1);
|
||||
}
|
||||
|
||||
parser = _parse_timeline (argc, argv);
|
||||
|
||||
{
|
||||
GList *tmp;
|
||||
for (tmp = parser->structures; tmp; tmp = tmp->next) {
|
||||
/* Do stuff here */
|
||||
}
|
||||
}
|
||||
|
||||
if (list_transitions) {
|
||||
print_transition_list ();
|
||||
exit (0);
|
||||
|
@ -920,7 +969,7 @@ main (int argc, gchar ** argv)
|
|||
return ges_validate_print_action_types ((const gchar **) argv + 1,
|
||||
argc - 1);
|
||||
|
||||
if (((!load_path && !scenario && (argc < 4)))) {
|
||||
if (((!load_path && !scenario && (argc < 1)))) {
|
||||
g_printf ("%s", g_option_context_get_help (ctx, TRUE, NULL));
|
||||
g_option_context_free (ctx);
|
||||
exit (1);
|
||||
|
@ -929,7 +978,8 @@ main (int argc, gchar ** argv)
|
|||
g_option_context_free (ctx);
|
||||
|
||||
/* Create the pipeline */
|
||||
create_pipeline (&timeline, load_path, argc - 1, argv + 1, scenario);
|
||||
parser = _parse_timeline (argc, argv);
|
||||
create_pipeline (&timeline, load_path, parser->structures, scenario);
|
||||
if (!pipeline)
|
||||
exit (1);
|
||||
|
||||
|
|
|
@ -76,6 +76,13 @@ _finish_structure (GESStructureParser * self)
|
|||
if (self->current_string) {
|
||||
GstStructure *structure =
|
||||
gst_structure_new_from_string (self->current_string);
|
||||
|
||||
if (structure == NULL) {
|
||||
GST_ERROR ("Error creating structure from %s", self->current_string);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
self->structures = g_list_append (self->structures, structure);
|
||||
g_free (self->current_string);
|
||||
self->current_string = NULL;
|
||||
|
|
Loading…
Reference in a new issue