/* GStreamer Editing Services * Copyright (C) <2019> Mathieu Duponchelle * * 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., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION: gesmarkerlist * @title: GESMarkerList * @short_description: implements a list of markers with metadata asociated to time positions * @see_also: #GESMarker * * A #GESMarker can be colored by setting the #GES_META_MARKER_COLOR meta. * * Since: 1.18 */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "ges-marker-list.h" #include "ges.h" #include "ges-internal.h" #include "ges-meta-container.h" static void ges_meta_container_interface_init (GESMetaContainerInterface * iface); struct _GESMarker { GObject parent; GstClockTime position; }; G_DEFINE_TYPE_WITH_CODE (GESMarker, ges_marker, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (GES_TYPE_META_CONTAINER, ges_meta_container_interface_init)); enum { PROP_0, PROP_POSITION, PROP_LAST }; static GParamSpec *properties[PROP_LAST]; /* GObject Standard vmethods*/ static void ges_marker_get_property (GObject * object, guint property_id, GValue * value, GParamSpec * pspec) { GESMarker *marker = GES_MARKER (object); switch (property_id) { case PROP_POSITION: g_value_set_uint64 (value, marker->position); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void ges_marker_init (GESMarker * self) { ges_meta_container_register_static_meta (GES_META_CONTAINER (self), GES_META_READ_WRITE, GES_META_MARKER_COLOR, G_TYPE_UINT); } static void ges_marker_class_init (GESMarkerClass * klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->get_property = ges_marker_get_property; /** * GESMarker:position: * * Current position (in nanoseconds) of the #GESMarker * * Since: 1.18 */ properties[PROP_POSITION] = g_param_spec_uint64 ("position", "Position", "The position of the marker", 0, G_MAXUINT64, GST_CLOCK_TIME_NONE, G_PARAM_READABLE); g_object_class_install_property (object_class, PROP_POSITION, properties[PROP_POSITION]); } static void ges_meta_container_interface_init (GESMetaContainerInterface * iface) { } /* GESMarkerList */ struct _GESMarkerList { GObject parent; GSequence *markers; GHashTable *markers_iters; }; enum { MARKER_ADDED, MARKER_REMOVED, MARKER_MOVED, LAST_SIGNAL }; static guint ges_marker_list_signals[LAST_SIGNAL] = { 0 }; G_DEFINE_TYPE (GESMarkerList, ges_marker_list, G_TYPE_OBJECT); static void remove_marker (gpointer data) { GESMarker *marker = (GESMarker *) data; g_object_unref (marker); } static void ges_marker_list_init (GESMarkerList * self) { self->markers = g_sequence_new (remove_marker); self->markers_iters = g_hash_table_new (g_direct_hash, g_direct_equal); } static void ges_marker_list_finalize (GObject * object) { GESMarkerList *self = GES_MARKER_LIST (object); g_sequence_free (self->markers); g_hash_table_unref (self->markers_iters); G_OBJECT_CLASS (ges_marker_list_parent_class)->finalize (object); } static void ges_marker_list_class_init (GESMarkerListClass * klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = ges_marker_list_finalize; /** * GESMarkerList::marker-added: * @marker-list: the #GESMarkerList * @position: the position of the added marker * @marker: the #GESMarker that was added. * * Will be emitted after the marker was added to the marker-list. * Since: 1.18 */ ges_marker_list_signals[MARKER_ADDED] = g_signal_new ("marker-added", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 2, G_TYPE_UINT64, GES_TYPE_MARKER); /** * GESMarkerList::marker-removed: * @marker_list: the #GESMarkerList * @marker: the #GESMarker that was removed. * * Will be emitted after the marker was removed the marker-list. * Since: 1.18 */ ges_marker_list_signals[MARKER_REMOVED] = g_signal_new ("marker-removed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, GES_TYPE_MARKER); /** * GESMarkerList::marker-moved: * @marker_list: the #GESMarkerList * @previous_position: the previous position of the marker * @new_position: the new position of the marker * @marker: the #GESMarker that was moved. * * Will be emitted after the marker was moved to. * Since: 1.18 */ ges_marker_list_signals[MARKER_MOVED] = g_signal_new ("marker-moved", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 3, G_TYPE_UINT64, G_TYPE_UINT64, GES_TYPE_MARKER); } static gint cmp_marker (gconstpointer a, gconstpointer b, G_GNUC_UNUSED gpointer data) { GESMarker *marker_a = (GESMarker *) a; GESMarker *marker_b = (GESMarker *) b; if (marker_a->position < marker_b->position) return -1; else if (marker_a->position == marker_b->position) return 0; else return 1; } /** * ges_marker_list_new: * * Creates a new #GESMarkerList. * Returns: A new #GESMarkerList * Since: 1.18 */ GESMarkerList * ges_marker_list_new (void) { GESMarkerList *ret; ret = (GESMarkerList *) g_object_new (GES_TYPE_MARKER_LIST, NULL); return ret; } /** * ges_marker_list_add: * @position: The position of the new marker * * Returns: (transfer none): The newly-added marker, the list keeps ownership * of the marker * Since: 1.18 */ GESMarker * ges_marker_list_add (GESMarkerList * self, GstClockTime position) { GESMarker *ret; GSequenceIter *iter; g_return_val_if_fail (GES_IS_MARKER_LIST (self), NULL); ret = (GESMarker *) g_object_new (GES_TYPE_MARKER, NULL); ret->position = position; iter = g_sequence_insert_sorted (self->markers, ret, cmp_marker, NULL); g_hash_table_insert (self->markers_iters, ret, iter); g_signal_emit (self, ges_marker_list_signals[MARKER_ADDED], 0, position, ret); return ret; } /** * ges_marker_list_size: * * Returns: The number of markers in @list * Since: 1.18 */ guint ges_marker_list_size (GESMarkerList * self) { g_return_val_if_fail (GES_IS_MARKER_LIST (self), 0); return g_sequence_get_length (self->markers); } /** * ges_marker_list_remove: * * Removes @marker from @list, this decreases the refcount of the * marker by 1. * * Returns: %TRUE if the marker could be removed, %FALSE otherwise * (if the marker was not present in the list for example) * Since: 1.18 */ gboolean ges_marker_list_remove (GESMarkerList * self, GESMarker * marker) { GSequenceIter *iter; gboolean ret = FALSE; g_return_val_if_fail (GES_IS_MARKER_LIST (self), FALSE); if (!g_hash_table_lookup_extended (self->markers_iters, marker, NULL, (gpointer *) & iter)) goto done; g_assert (iter != NULL); g_hash_table_remove (self->markers_iters, marker); g_signal_emit (self, ges_marker_list_signals[MARKER_REMOVED], 0, marker); g_sequence_remove (iter); ret = TRUE; done: return ret; } /** * ges_marker_list_get_markers: * * Returns: (element-type GESMarker) (transfer full): a #GList * of the #GESMarker within the GESMarkerList. The user will have * to unref each #GESMarker and free the #GList. * * Since: 1.18 */ GList * ges_marker_list_get_markers (GESMarkerList * self) { GESMarker *marker; GSequenceIter *iter; GList *ret; g_return_val_if_fail (GES_IS_MARKER_LIST (self), NULL); ret = NULL; for (iter = g_sequence_get_begin_iter (self->markers); !g_sequence_iter_is_end (iter); iter = g_sequence_iter_next (iter)) { marker = GES_MARKER (g_sequence_get (iter)); ret = g_list_append (ret, g_object_ref (marker)); } return ret; } /** * ges_marker_list_move: * * Moves a @marker in a @list to a new @position * * Returns: %TRUE if the marker could be moved, %FALSE otherwise * (if the marker was not present in the list for example) * * Since: 1.18 */ gboolean ges_marker_list_move (GESMarkerList * self, GESMarker * marker, GstClockTime position) { GSequenceIter *iter; gboolean ret = FALSE; GstClockTime previous_position; g_return_val_if_fail (GES_IS_MARKER_LIST (self), FALSE); if (!g_hash_table_lookup_extended (self->markers_iters, marker, NULL, (gpointer *) & iter)) { GST_WARNING ("GESMarkerList doesn't contain GESMarker"); goto done; } previous_position = marker->position; marker->position = position; g_signal_emit (self, ges_marker_list_signals[MARKER_MOVED], 0, previous_position, position, marker); g_sequence_sort_changed (iter, cmp_marker, NULL); ret = TRUE; done: return ret; } gboolean ges_marker_list_deserialize (GValue * dest, const gchar * s) { gboolean ret = FALSE; GstCaps *caps = NULL; GESMarkerList *list = ges_marker_list_new (); guint i, l; caps = gst_caps_from_string (s); l = gst_caps_get_size (caps); if (l == 0) { GST_DEBUG ("Got empty caps: %s", s); goto done; } if (l % 2) { GST_ERROR ("Failed deserializing marker list: expected evenly-sized caps"); goto done; } for (i = 0; i < l - 1; i += 2) { const GstStructure *pos_s = gst_caps_get_structure (caps, i); const GstStructure *meta_s = gst_caps_get_structure (caps, i + 1); GstClockTime position; GESMarker *marker; gchar *metas; if (!gst_structure_has_name (pos_s, "marker-times")) { GST_ERROR_OBJECT (dest, "Failed deserializing marker list: unexpected structure %" GST_PTR_FORMAT, pos_s); goto done; } if (!gst_structure_get_uint64 (pos_s, "position", &position)) { GST_ERROR_OBJECT (dest, "Failed deserializing marker list: unexpected structure %" GST_PTR_FORMAT, pos_s); goto done; } marker = ges_marker_list_add (list, position); metas = gst_structure_to_string (meta_s); ges_meta_container_add_metas_from_string (GES_META_CONTAINER (marker), metas); g_free (metas); } ret = TRUE; done: if (caps) gst_caps_unref (caps); if (!ret) g_object_unref (list); else g_value_take_object (dest, list); return ret; } gchar * ges_marker_list_serialize (const GValue * v) { GESMarkerList *list = GES_MARKER_LIST (g_value_get_object (v)); GSequenceIter *iter; GstCaps *caps = gst_caps_new_empty (); gchar *caps_str, *escaped, *res; iter = g_sequence_get_begin_iter (list->markers); while (!g_sequence_iter_is_end (iter)) { GESMarker *marker = (GESMarker *) g_sequence_get (iter); GstStructure *s; gchar *metas; metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (marker)); s = gst_structure_new ("marker-times", "position", G_TYPE_UINT64, marker->position, NULL); gst_caps_append_structure (caps, s); s = gst_structure_from_string (metas, NULL); gst_caps_append_structure (caps, s); g_free (metas); iter = g_sequence_iter_next (iter); } caps_str = gst_caps_to_string (caps); escaped = g_strescape (caps_str, NULL); g_free (caps_str); res = g_strdup_printf ("\"%s\"", escaped); g_free (escaped); gst_caps_unref (caps); return res; }