mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-24 09:10:36 +00:00
422 lines
12 KiB
C
422 lines
12 KiB
C
/* GStreamer Editing Services
|
|
*
|
|
* Copyright (C) <2015> Thibault Saunier <tsaunier@gnome.org>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public
|
|
* License along with this library; if not, write to the
|
|
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
|
* Boston, MA 02111-1307, USA.
|
|
*/
|
|
|
|
#include "ges-command-line-formatter.h"
|
|
|
|
#include "ges/ges-structured-interface.h"
|
|
#include "ges-structure-parser.h"
|
|
#include "ges-internal.h"
|
|
#include "ges-parse-lex.h"
|
|
|
|
struct _GESCommandLineFormatterPrivate
|
|
{
|
|
gpointer dummy;
|
|
};
|
|
|
|
|
|
G_DEFINE_TYPE (GESCommandLineFormatter, ges_command_line_formatter,
|
|
GES_TYPE_FORMATTER);
|
|
|
|
typedef struct
|
|
{
|
|
const gchar *long_name;
|
|
const gchar *short_name;
|
|
GType type;
|
|
const gchar *new_name;
|
|
} Properties;
|
|
|
|
static gint /* -1: not present, 0: failure, 1: OK */
|
|
_convert_to_clocktime (GstStructure * structure, const gchar * name,
|
|
GstClockTime default_value)
|
|
{
|
|
gint res = 1;
|
|
gdouble val;
|
|
GValue d_val = { 0 };
|
|
GstClockTime timestamp;
|
|
const GValue *gvalue = gst_structure_get_value (structure, name);
|
|
|
|
if (gvalue == NULL) {
|
|
timestamp = default_value;
|
|
|
|
res = -1;
|
|
|
|
goto done;
|
|
}
|
|
|
|
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 gboolean
|
|
_cleanup_fields (const Properties * field_names, GstStructure * structure,
|
|
GError ** error)
|
|
{
|
|
guint i;
|
|
|
|
for (i = 0; field_names[i].long_name; i++) {
|
|
gboolean exists = FALSE;
|
|
|
|
/* Move shortly named fields to longname variante */
|
|
if (gst_structure_has_field (structure, field_names[i].short_name)) {
|
|
exists = TRUE;
|
|
|
|
if (gst_structure_has_field (structure, field_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?!",
|
|
field_names[i].long_name);
|
|
|
|
return FALSE;
|
|
} else {
|
|
const GValue *val =
|
|
gst_structure_get_value (structure, field_names[i].short_name);
|
|
|
|
gst_structure_set_value (structure, field_names[i].long_name, val);
|
|
gst_structure_remove_field (structure, field_names[i].short_name);
|
|
}
|
|
} else if (gst_structure_has_field (structure, field_names[i].long_name)) {
|
|
exists = TRUE;
|
|
}
|
|
|
|
if (exists) {
|
|
if (field_names[i].type == GST_TYPE_CLOCK_TIME) {
|
|
if (_convert_to_clocktime (structure, field_names[i].long_name, 0) == 0) {
|
|
*error = g_error_new (GES_ERROR, 0, "Could not convert"
|
|
" %s to GstClockTime", field_names[i].long_name);
|
|
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (field_names[i].new_name
|
|
&& gst_structure_has_field (structure, field_names[i].long_name)) {
|
|
const GValue *val =
|
|
gst_structure_get_value (structure, field_names[i].long_name);
|
|
|
|
gst_structure_set_value (structure, field_names[i].new_name, val);
|
|
gst_structure_remove_field (structure, field_names[i].long_name);
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
_ges_command_line_formatter_add_clip (GESTimeline * timeline,
|
|
GstStructure * structure, GError ** error)
|
|
{
|
|
const Properties field_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},
|
|
{"layer", "l", 0, NULL},
|
|
{NULL, 0, 0, NULL},
|
|
};
|
|
|
|
if (!_cleanup_fields (field_names, structure, error))
|
|
return FALSE;
|
|
|
|
gst_structure_set (structure, "type", G_TYPE_STRING, "GESUriClip", NULL);
|
|
|
|
return _ges_add_clip_from_struct (timeline, structure, error);
|
|
}
|
|
|
|
static gboolean
|
|
_ges_command_line_formatter_add_test_clip (GESTimeline * timeline,
|
|
GstStructure * structure, GError ** error)
|
|
{
|
|
const Properties field_names[] = {
|
|
{"pattern", "p", G_TYPE_STRING, NULL},
|
|
{"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},
|
|
{"layer", "l", 0, NULL},
|
|
{NULL, 0, 0, NULL},
|
|
};
|
|
|
|
if (!_cleanup_fields (field_names, structure, error))
|
|
return FALSE;
|
|
|
|
gst_structure_set (structure, "type", G_TYPE_STRING, "GESTestClip", NULL);
|
|
gst_structure_set (structure, "asset-id", G_TYPE_STRING,
|
|
gst_structure_get_string (structure, "pattern"), NULL);
|
|
|
|
return _ges_add_clip_from_struct (timeline, structure, error);
|
|
}
|
|
|
|
static gboolean
|
|
_ges_command_line_formatter_add_effect (GESTimeline * timeline,
|
|
GstStructure * structure, GError ** error)
|
|
{
|
|
const Properties field_names[] = {
|
|
{"element-name", "e", 0, NULL},
|
|
{"bin-description", "d", 0, "asset-id"},
|
|
{"name", "n", 0, "child-name"},
|
|
{NULL, NULL, 0, NULL},
|
|
};
|
|
|
|
if (!_cleanup_fields (field_names, structure, error))
|
|
return FALSE;
|
|
|
|
gst_structure_set (structure, "child-type", G_TYPE_STRING, "GESEffect", NULL);
|
|
|
|
return _ges_container_add_child_from_struct (timeline, structure, error);
|
|
}
|
|
|
|
static GOptionEntry timeline_parsing_options[] = {
|
|
{"clip", 'c', 0.0, G_OPTION_ARG_CALLBACK,
|
|
&_ges_command_line_formatter_add_clip,
|
|
"<clip uri> - Adds a clip in the timeline.",
|
|
" * s=, start The start position of the element inside the layer.\n"
|
|
" * d=, duration The duration of the clip.\n"
|
|
" * i=, inpoint The inpoint of the clip.\n"
|
|
" * tt=, track-types 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)\n"},
|
|
{"effect", 'e', 0.0, G_OPTION_ARG_CALLBACK,
|
|
&_ges_command_line_formatter_add_effect,
|
|
"<effect bin description> - Adds an effect as specified by 'bin-description'.",
|
|
" * d=, bin-description The description of the effect bin with a gst-launch-style pipeline description.\n"
|
|
" * e=, element-name The name of the element to apply the effect on.\n"},
|
|
{"test-clip", 0, 0.0, G_OPTION_ARG_CALLBACK,
|
|
&_ges_command_line_formatter_add_test_clip,
|
|
"<test clip pattern> - Add a test clip in the timeline.",
|
|
" * s=, start The start position of the element inside the layer.\n"
|
|
" * d=, duration The duration of the clip.\n"
|
|
" * i=, inpoint The inpoint of the clip.\n"},
|
|
{"set-", 0, 0.0, G_OPTION_ARG_CALLBACK,
|
|
NULL,
|
|
"<property name> <value> - Set a property on the last added element."
|
|
" Any child property that exists on the previously added element"
|
|
" can be used as <property name>", NULL},
|
|
};
|
|
|
|
gchar *
|
|
ges_command_line_formatter_get_help (gint nargs, gchar ** commands)
|
|
{
|
|
gint i;
|
|
gchar *help = NULL;
|
|
|
|
for (i = 0; i < G_N_ELEMENTS (timeline_parsing_options); i++) {
|
|
gboolean print = nargs == 0;
|
|
|
|
if (!print) {
|
|
gint j;
|
|
|
|
for (j = 0; j < nargs; j++) {
|
|
gchar *cname = commands[j][0] == '+' ? &commands[j][1] : commands[j];
|
|
|
|
if (!g_strcmp0 (cname, timeline_parsing_options[i].long_name)) {
|
|
print = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (print) {
|
|
gchar *tmp = g_strdup_printf ("%s %s%s %s\n", help ? help : "",
|
|
timeline_parsing_options[i].arg_description ? "+" : "",
|
|
timeline_parsing_options[i].long_name,
|
|
timeline_parsing_options[i].description);
|
|
|
|
g_free (help);
|
|
help = tmp;
|
|
|
|
if (timeline_parsing_options[i].arg_description) {
|
|
tmp = g_strdup_printf ("%s Properties:\n%s\n", help,
|
|
timeline_parsing_options[i].arg_description);
|
|
g_free (help);
|
|
help = tmp;
|
|
}
|
|
}
|
|
}
|
|
|
|
return help;
|
|
}
|
|
|
|
|
|
static gboolean
|
|
_set_child_property (GESTimeline * timeline, GstStructure * structure,
|
|
GError ** error)
|
|
{
|
|
return _ges_set_child_property_from_struct (timeline, structure, error);
|
|
}
|
|
|
|
#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 fail; \
|
|
} \
|
|
} G_STMT_END
|
|
|
|
|
|
static GESStructureParser *
|
|
_parse_structures (const gchar * string)
|
|
{
|
|
yyscan_t scanner;
|
|
GESStructureParser *parser = ges_structure_parser_new ();
|
|
|
|
priv_ges_parse_yylex_init_extra (parser, &scanner);
|
|
priv_ges_parse_yy_scan_string (string, scanner);
|
|
priv_ges_parse_yylex (scanner);
|
|
priv_ges_parse_yylex_destroy (scanner);
|
|
|
|
ges_structure_parser_end_of_file (parser);
|
|
return parser;
|
|
}
|
|
|
|
static gboolean
|
|
_can_load (GESFormatter * dummy_formatter, const gchar * string,
|
|
GError ** error)
|
|
{
|
|
gboolean res = FALSE;
|
|
GESStructureParser *parser;
|
|
|
|
if (string == NULL)
|
|
return FALSE;
|
|
|
|
parser = _parse_structures (string);
|
|
|
|
if (parser->structures)
|
|
res = TRUE;
|
|
|
|
gst_object_unref (parser);
|
|
|
|
return res;
|
|
}
|
|
|
|
static gboolean
|
|
_load (GESFormatter * self, GESTimeline * timeline, const gchar * string,
|
|
GError ** error)
|
|
{
|
|
guint i;
|
|
GList *tmp;
|
|
GError *err;
|
|
GESStructureParser *parser = _parse_structures (string);
|
|
|
|
err = ges_structure_parser_get_error (parser);
|
|
|
|
if (err) {
|
|
if (error)
|
|
*error = err;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
g_object_set (timeline, "auto-transition", TRUE, NULL);
|
|
if (!(ges_timeline_add_track (timeline, GES_TRACK (ges_video_track_new ()))))
|
|
goto fail;
|
|
|
|
if (!(ges_timeline_add_track (timeline, GES_TRACK (ges_audio_track_new ()))))
|
|
goto fail;
|
|
|
|
/* Here we've finished initializing our timeline, we're
|
|
* ready to start using it... by solely working with the layer !*/
|
|
for (tmp = parser->structures; tmp; tmp = tmp->next) {
|
|
const gchar *name = gst_structure_get_name (tmp->data);
|
|
if (g_str_has_prefix (name, "set-")) {
|
|
EXEC (_set_child_property, tmp->data, &err);
|
|
continue;
|
|
}
|
|
|
|
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, &err);
|
|
}
|
|
}
|
|
}
|
|
|
|
gst_object_unref (parser);
|
|
|
|
return TRUE;
|
|
|
|
fail:
|
|
gst_object_unref (parser);
|
|
if (err) {
|
|
if (error)
|
|
*error = err;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
ges_command_line_formatter_init (GESCommandLineFormatter *
|
|
ges_command_line_formatter)
|
|
{
|
|
ges_command_line_formatter->priv =
|
|
G_TYPE_INSTANCE_GET_PRIVATE (ges_command_line_formatter,
|
|
GES_TYPE_COMMAND_LINE_FORMATTER, GESCommandLineFormatterPrivate);
|
|
|
|
/* TODO: Add initialization code here */
|
|
}
|
|
|
|
static void
|
|
ges_command_line_formatter_finalize (GObject * object)
|
|
{
|
|
/* TODO: Add deinitalization code here */
|
|
|
|
G_OBJECT_CLASS (ges_command_line_formatter_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
ges_command_line_formatter_class_init (GESCommandLineFormatterClass * klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
GESFormatterClass *formatter_klass = GES_FORMATTER_CLASS (klass);
|
|
|
|
g_type_class_add_private (klass, sizeof (GESCommandLineFormatterPrivate));
|
|
|
|
object_class->finalize = ges_command_line_formatter_finalize;
|
|
|
|
formatter_klass->can_load_uri = _can_load;
|
|
formatter_klass->load_from_uri = _load;
|
|
formatter_klass->rank = GST_RANK_MARGINAL;
|
|
}
|