diff --git a/docs/libs/ges-docs.sgml b/docs/libs/ges-docs.sgml index eef4b3f635..a5a54479e5 100644 --- a/docs/libs/ges-docs.sgml +++ b/docs/libs/ges-docs.sgml @@ -47,6 +47,7 @@ platform as well as Windows. It is released under the GNU Library General Public + diff --git a/docs/libs/ges-sections.txt b/docs/libs/ges-sections.txt index c5a4bae7ad..0e5380ed4c 100644 --- a/docs/libs/ges-sections.txt +++ b/docs/libs/ges-sections.txt @@ -297,6 +297,7 @@ GES_TYPE_TIMELINE GESTimelineLayer GESTimelineLayerClass ges_timeline_layer_add_object +ges_timeline_layer_add_asset ges_timeline_layer_new ges_timeline_layer_remove_object ges_timeline_layer_set_priority @@ -796,19 +797,10 @@ GESFormatterLoadedMethod ges_default_formatter_new ges_formatter_load_from_uri ges_formatter_save_to_uri -ges_formatter_new_for_uri ges_formatter_can_load_uri ges_formatter_can_save_uri -ges_formatter_update_source_uri -ges_formatter_load -ges_formatter_save -ges_formatter_set_data -ges_formatter_clear_data -ges_formatter_get_data GESFormatterCanLoadURIMethod GESFormatterCanSaveURIMethod -GESFormatterLoadMethod -GESFormatterSaveMethod ges_formatter_emit_loaded @@ -823,22 +815,6 @@ ges_formatter_get_type GESFormatterPrivate -
-ges-keyfile-formatter -GESKeyFileFormatter -GESKeyfileFormatter -ges_keyfile_formatter_new - -GESKeyfileFormatterClass -GES_IS_KEYFILE_FORMATTER -GES_IS_KEYFILE_FORMATTER_CLASS -GES_KEYFILE_FORMATTER -GES_KEYFILE_FORMATTER_CLASS -GES_KEYFILE_FORMATTER_GET_CLASS -GES_TYPE_KEYFILE_FORMATTER -ges_keyfile_formatter_get_type -
-
ges-pitivi-formatter GESPitiviFormatter @@ -951,3 +927,25 @@ GES_META_CONTAINER_GET_INTERFACE GES_TYPE_META_CONTAINER ges_meta_container_get_ype
+ +
+ges-asset +GESAsset +GESAsset +ges_asset_get_extractable_type +ges_asset_get_id +ges_asset_request +ges_asset_request_async +ges_asset_request_finish +ges_asset_extract +ges_list_assets + +GESAssetPrivate +GES_ASSET +GES_TYPE_ASSET +GES_ASSET_CLASS +GES_IS_ASSET +GES_IS_ASSET_CLASS +GES_ASSET_GET_CLASS +ges_asset_get_type +
diff --git a/ges/Makefile.am b/ges/Makefile.am index 0afd3226c2..99117eb631 100644 --- a/ges/Makefile.am +++ b/ges/Makefile.am @@ -94,7 +94,8 @@ libges_@GST_API_VERSION@include_HEADERS = \ ges-utils.h noinst_HEADERS = \ - ges-internal.h + ges-internal.h \ + ges-internal-enums.h libges_@GST_API_VERSION@_la_CFLAGS = -I$(top_srcdir) $(GST_PBUTILS_CFLAGS) \ $(GST_VIDEO_CFLAGS) $(GST_CONTROLLER_CFLAGS) $(GST_PLUGINS_BASE_CFLAGS) \ diff --git a/ges/ges-asset.c b/ges/ges-asset.c new file mode 100644 index 0000000000..8b7d0703df --- /dev/null +++ b/ges/ges-asset.c @@ -0,0 +1,1008 @@ +/* GStreamer Editing Services + * + * Copyright (C) 2012 Thibault Saunier + * Copyright (C) 2012 Volodymyr Rudyi + * + * 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. + */ +/** + * SECTION: ges-asset + * @short_description: #GESAssets represent usable ressources inside the GStreamer Editing Services + * + * The Assets in the GStreamer Editing Services represent the ressources + * that can be used. You can create assets for any type that implements the #GESExtractable + * interface, for example #GESTimelineObjects, #GESFormatter, and #GESTrackObject do implement it. + * This means that asssets will represent for example a #GESTimelineFileSources, #GESTrackEffect etc, + * and then you can extract objects of those types with the appropriate parameters from the asset + * using the #ges_asset_extract method: + * + * |[ + * GESAsset *effect_asset; + * GESTrackParseLaunchEffect *effect; + * + * // You create an asset for an effect + * effect_asset = ges_asset_request (GES_TYPE_TRACK_PARSE_LAUNCH_EFFECT, "agingtv", NULL); + * + * // And now you can extract an instance of GESTrackParseLaunchEffect from that asset + * effect = GES_TRACK_PARSE_LAUNCH_EFFECT (ges_asset_extract (effect_asset)); + * + * ]| + * + * In that example, the advantages of having a #GESAsset are that you can know what effects + * you are working with and let your user know about the avalaible ones, you can add metadata + * to the #GESAsset through the #GESMetaContainer interface and you have a model for your + * custom effects. Note that #GESAsset management is making easier thanks to the #GESProject class. + * + * Each asset are represented by a pair of @extractable_type and @id (string). Actually the @extractable_type + * is the type that implements the #GESExtractable interface, that means that for example for a #GESTimelineFileSource, + * the type that implements the #GESExtractable interface is #GESTimelineObject. + * The identifier represents different things depending on the @extractable_type and you should check + * the documentation of each type to know what the ID of #GESAsset actually represents for that type. By default, + * we only have one #GESAsset per type, and the @id is the name of the type, but this behaviour is overriden + * to be more usefull. For example, for GESTimelineStandardTransitions, the ID is the vtype of the transition + * you will extract from it (ie crossfade, box-wipe-rc etc..) For #GESTrackParseLaunchEffect the id is the + * @bin-description property of the extracted objects (ie the gst-launch style description of the bin that + * will be used). + * + * Each and every #GESAsset are cached into GES, and you can query those with the #ges_list_assets function. + * Also the system will automatically register #GESAssets for #GESFormatters and #GESTimelineStandardTransitions + * and standard effects (actually not implemented yet) and you can simply query those calling: + * |[ + * GList *formatter_assets, *tmp; + * + * // List all the transitions + * formatter_assets = ges_list_assets (GES_TYPE_FORMATTER); + * + * // Print some infos about the formatter GESAsset + * for (tmp = formatter_assets; tmp; tmp = tmp->next) { + * g_print ("Name of the formatter: %s, file extension it produces: %s", + * ges_meta_container_get_string (GES_META_CONTAINER (tmp->data), GES_META_FORMATTER_NAME), + * ges_meta_container_get_string (GES_META_CONTAINER (tmp->data), GES_META_FORMATTER_EXTENSION)); + * } + * + * g_list_free (transition_assets); + * + * ]| + * + * You can request the creation of #GESAssets using either #ges_asset_request_async or + * #ges_asset_request_async. All the #GESAssets are cached and thus any asset that has already + * been created can be requested again without overhead. + */ + +#include "ges.h" +#include "ges-internal.h" + +#include + +enum +{ + PROP_0, + PROP_TYPE, + PROP_ID, + PROP_LAST +}; + +typedef enum +{ + ASSET_NOT_INITIALIZED, + ASSET_INITIALIZING, ASSET_INITIALIZED_WITH_ERROR, + ASSET_PROXIED, + ASSET_INITIALIZED +} GESAssetState; + +static GParamSpec *_properties[PROP_LAST]; + +struct _GESAssetPrivate +{ + gchar *id; + GESAssetState state; + GType extractable_type; + + /* When a asset is proxied, instanciating it will + * return the asset it points to */ + char *proxied_asset_id; + + /* The error that accured when a asset has been initialized with error */ + GError *error; +}; + +/* Internal structure to help avoid full loading + * of one asset several times + */ +typedef struct +{ + GList *results; + GESAsset *asset; +} GESAssetCacheEntry; + +/* Also protect all the entries in the cache */ +static GMutex asset_cache_lock; +/* We are mapping entries by types and ID, such as: + * + * { + * first_extractable_type_name1 : + * { + * "some ID": GESAssetCacheEntry, + * "some other ID": GESAssetCacheEntry 2 + * }, + * second_extractable_type_name : + * { + * "some ID": GESAssetCacheEntry, + * "some other ID": GESAssetCacheEntry 2 + * } + * } + * + * (The first extractable type is the type of the class that implemented + * the GESExtractable interface ie: GESTimelineObject, GESTimeline, + * GESFomatter, etc... but not subclasses) + * + * This is in order to be able to have 2 Asset with the same ID but + * different extractable types. + **/ +static GHashTable *type_entries_table = NULL; +#define LOCK_CACHE (g_mutex_lock (&asset_cache_lock)) +#define UNLOCK_CACHE (g_mutex_unlock (&asset_cache_lock)) + +static gchar * +_check_and_update_parameters (GType * extractable_type, const gchar * id, + GError ** error) +{ + gchar *real_id; + GType old_type = *extractable_type; + + *extractable_type = + ges_extractable_get_real_extractable_type_for_id (*extractable_type, id); + + if (*extractable_type == G_TYPE_NONE) { + GST_WARNING ("No way to create a Asset for ID: %s, type: %s", id, + g_type_name (old_type)); + + if (error && *error == NULL) + g_set_error (error, GES_ERROR_DOMAIN, 0, + "Wrong ID, can not find any extractable_type"); + return NULL; + } + + real_id = ges_extractable_type_check_id (*extractable_type, id, error); + if (real_id == NULL) { + GST_WARNING ("Wrong ID %s, can not create asset", id); + + g_free (real_id); + if (error && *error == NULL) + g_set_error (error, GES_ERROR_DOMAIN, 0, "Wrong ID"); + + return NULL; + } + + return real_id; +} + +static gboolean +initable_init (GInitable * initable, GCancellable * cancellable, + GError ** error) +{ + gboolean ret; + GESAsset *asset = GES_ASSET (initable); + + ges_asset_cache_put (gst_object_ref (asset), NULL); + ret = ges_asset_cache_set_loaded (asset->priv->extractable_type, + asset->priv->id, NULL); + + g_clear_error (error); + + return ret; +} + +static void +async_initable_init_async (GAsyncInitable * initable, gint io_priority, + GCancellable * cancellable, GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *simple; + + GError *error = NULL; + GESAsset *asset = GES_ASSET (initable); + + simple = g_simple_async_result_new (G_OBJECT (asset), + callback, user_data, ges_asset_request_async); + + ges_asset_cache_put (asset, simple); + switch (GES_ASSET_GET_CLASS (asset)->start_loading (asset, &error)) { + case GES_ASSET_LOADING_ERROR: + { + if (error == NULL) + g_set_error (&error, GES_ERROR_DOMAIN, 1, + "Could not start loading asset"); + + /* FIXME Define error code */ + ges_asset_cache_set_loaded (asset->priv->extractable_type, + asset->priv->id, error); + g_error_free (error); + return; + } + case GES_ASSET_LOADING_OK: + { + ges_asset_cache_set_loaded (asset->priv->extractable_type, + asset->priv->id, error); + return; + } + case GES_ASSET_LOADING_ASYNC: + /* If Async.... let it go */ + break; + } +} + +static void +async_initable_iface_init (GAsyncInitableIface * async_initable_iface) +{ + async_initable_iface->init_async = async_initable_init_async; +} + +static void +initable_iface_init (GInitableIface * initable_iface) +{ + initable_iface->init = initable_init; +} + +G_DEFINE_TYPE_WITH_CODE (GESAsset, ges_asset, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, async_initable_iface_init) + G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init) + G_IMPLEMENT_INTERFACE (GES_TYPE_META_CONTAINER, NULL)); + +/* GESAsset virtual methods default implementation */ +static GESAssetLoadingReturn +ges_asset_start_loading_default (GESAsset * asset, GError ** error) +{ + return GES_ASSET_LOADING_OK; +} + +static GESExtractable * +ges_asset_extract_default (GESAsset * asset, GError ** error) +{ + guint n_params; + GParameter *params; + GESAssetPrivate *priv = asset->priv; + + params = ges_extractable_type_get_parameters_from_id (priv->extractable_type, + priv->id, &n_params); + + return g_object_newv (priv->extractable_type, n_params, params); +} + +static gboolean +ges_asset_request_id_update_default (GESAsset * self, gchar ** proposed_new_id, + GError * error) +{ + return FALSE; +} + +/* GObject virtual methods implementation */ +static void +ges_asset_get_property (GObject * object, guint property_id, + GValue * value, GParamSpec * pspec) +{ + GESAsset *asset = GES_ASSET (object); + + switch (property_id) { + case PROP_TYPE: + g_value_set_gtype (value, asset->priv->extractable_type); + break; + case PROP_ID: + g_value_set_string (value, asset->priv->id); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ges_asset_set_property (GObject * object, guint property_id, + const GValue * value, GParamSpec * pspec) +{ + GESAsset *asset = GES_ASSET (object); + + switch (property_id) { + case PROP_TYPE: + asset->priv->extractable_type = g_value_get_gtype (value); + ges_extractable_register_metas (asset->priv->extractable_type, asset); + break; + case PROP_ID: + asset->priv->id = g_value_dup_string (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ges_asset_finalize (GObject * object) +{ + GESAssetPrivate *priv = GES_ASSET (object)->priv; + + if (priv->id) + g_free (priv->id); + + if (priv->proxied_asset_id) + g_free (priv->proxied_asset_id); + + G_OBJECT_CLASS (ges_asset_parent_class)->finalize (object); +} + +void +ges_asset_class_init (GESAssetClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + g_type_class_add_private (klass, sizeof (GESAssetPrivate)); + + object_class->get_property = ges_asset_get_property; + object_class->set_property = ges_asset_set_property; + object_class->finalize = ges_asset_finalize; + + _properties[PROP_TYPE] = + g_param_spec_gtype ("extractable-type", "Extractable type", + "The type of the Object that can be extracted out of the asset", + G_TYPE_OBJECT, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE); + + _properties[PROP_ID] = + g_param_spec_string ("id", "Identifier", + "The unic identifier of the asset", NULL, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE); + + g_object_class_install_properties (object_class, PROP_LAST, _properties); + + klass->start_loading = ges_asset_start_loading_default; + klass->extract = ges_asset_extract_default; + klass->request_id_update = ges_asset_request_id_update_default; + klass->inform_proxy = NULL; +} + +void +ges_asset_init (GESAsset * self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + GES_TYPE_ASSET, GESAssetPrivate); + + self->priv->state = ASSET_INITIALIZING; + self->priv->proxied_asset_id = NULL; +} + +/* Internal methods */ + +/* Find the type that implemented the GESExtractable interface */ +static inline const gchar * +_extractable_type_name (GType type) +{ + while (1) { + if (g_type_is_a (g_type_parent (type), GES_TYPE_EXTRACTABLE)) + type = g_type_parent (type); + else + return g_type_name (type); + } +} + +static inline GESAssetCacheEntry * +_lookup_entry (GType extractable_type, const gchar * id) +{ + GHashTable *entries_table; + + entries_table = g_hash_table_lookup (type_entries_table, + _extractable_type_name (extractable_type)); + if (entries_table) + return g_hash_table_lookup (entries_table, id); + + return NULL; +} + +static void +_free_entries (gpointer entry) +{ + g_slice_free (GESAssetCacheEntry, entry); +} + +/** + * ges_asset_cache_lookup: + * + * @id String identifier of asset + * + * Looks for asset with specified id in cache and it's completely loaded. + * + * Returns: (transfer none): The #GESAsset found or %NULL + */ +GESAsset * +ges_asset_cache_lookup (GType extractable_type, const gchar * id) +{ + GESAsset *asset = NULL; + GESAssetCacheEntry *entry = NULL; + + g_return_val_if_fail (id, NULL); + + LOCK_CACHE; + entry = _lookup_entry (extractable_type, id); + if (entry) + asset = entry->asset; + UNLOCK_CACHE; + + return asset; +} + +static void +ges_asset_cache_append_result (GType extractable_type, + const gchar * id, GSimpleAsyncResult * res) +{ + GESAssetCacheEntry *entry = NULL; + + LOCK_CACHE; + if ((entry = _lookup_entry (extractable_type, id))) + entry->results = g_list_append (entry->results, res); + UNLOCK_CACHE; +} + +gboolean +ges_asset_cache_set_loaded (GType extractable_type, const gchar * id, + GError * error) +{ + GList *tmp; + GESAsset *asset; + GESAssetCacheEntry *entry = NULL; + + LOCK_CACHE; + if ((entry = _lookup_entry (extractable_type, id)) == NULL) { + UNLOCK_CACHE; + GST_ERROR ("Calling but type %s ID: %s not in cached, " + "something massively screwed", g_type_name (extractable_type), id); + + return FALSE; + } + + asset = entry->asset; + GST_DEBUG_OBJECT (entry->asset, ": (extractable type: %s) loaded, calling %i " + "callback (Error: %s)", g_type_name (asset->priv->extractable_type), + g_list_length (entry->results), error ? error->message : ""); + + if (error) { + GList *results = entry->results; + + asset->priv->state = ASSET_INITIALIZED_WITH_ERROR; + asset->priv->error = g_error_copy (error); + entry->results = NULL; + UNLOCK_CACHE; + + /* In case of error we do not want to emit in idle as we need to recover + * if possible */ + for (tmp = results; tmp; tmp = tmp->next) { + g_simple_async_result_set_from_error (G_SIMPLE_ASYNC_RESULT (tmp->data), + error); + g_simple_async_result_complete (G_SIMPLE_ASYNC_RESULT (tmp->data)); + } + + g_list_free (results); + return TRUE; + } else { + asset->priv->state = ASSET_INITIALIZED; + + g_list_free_full (entry->results, + (GDestroyNotify) g_simple_async_result_complete_in_idle); + entry->results = NULL; + UNLOCK_CACHE; + } + + return TRUE; +} + +void +ges_asset_cache_put (GESAsset * asset, GSimpleAsyncResult * res) +{ + GType extractable_type; + const gchar *asset_id; + GESAssetCacheEntry *entry; + + /* Needing to work with the cache, taking the lock */ + asset_id = ges_asset_get_id (asset); + extractable_type = asset->priv->extractable_type; + + LOCK_CACHE; + if (!(entry = _lookup_entry (extractable_type, asset_id))) { + GHashTable *entries_table; + + entries_table = g_hash_table_lookup (type_entries_table, + _extractable_type_name (extractable_type)); + if (entries_table == NULL) { + entries_table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, + _free_entries); + + g_hash_table_insert (type_entries_table, + g_strdup (_extractable_type_name (extractable_type)), entries_table); + } + + entry = g_slice_new0 (GESAssetCacheEntry); + + entry->asset = asset; + if (res) + entry->results = g_list_prepend (entry->results, res); + g_hash_table_insert (entries_table, (gpointer) g_strdup (asset_id), + (gpointer) entry); + } else { + if (res) { + GST_DEBUG ("%s already in cache, adding result %p", asset_id, res); + entry->results = g_list_prepend (entry->results, res); + } + } + UNLOCK_CACHE; +} + +void +ges_asset_cache_init (void) +{ + g_mutex_init (&asset_cache_lock); + type_entries_table = g_hash_table_new_full (g_str_hash, g_str_equal, + NULL, (GDestroyNotify) g_hash_table_unref); + + _init_standard_transition_assets (); + _init_formatter_assets (); +} + +gboolean +ges_asset_request_id_update (GESAsset * asset, gchar ** proposed_id, + GError * error) +{ + return GES_ASSET_GET_CLASS (asset)->request_id_update (asset, proposed_id, + error); +} + +gboolean +ges_asset_set_proxy (GESAsset * asset, const gchar * new_id) +{ + GESAssetClass *class; + if (g_strcmp0 (asset->priv->id, new_id) == 0) { + GST_WARNING_OBJECT (asset, "Trying to proxy to itself (%s)," + " NOT possible", new_id); + + return FALSE; + } + + if (asset->priv->proxied_asset_id) + g_free (asset->priv->proxied_asset_id); + + asset->priv->state = ASSET_PROXIED; + asset->priv->proxied_asset_id = g_strdup (new_id); + + class = GES_ASSET_GET_CLASS (asset); + if (class->inform_proxy) + GES_ASSET_GET_CLASS (asset)->inform_proxy (asset, new_id); + + GST_DEBUG_OBJECT (asset, "Now proxied to %s", new_id); + return TRUE; +} + +/* Caution, this method should be used in rare cases (ie: for the project + * as we can change its ID from a useless one to a proper URI). In most + * cases you want to update the ID creating a proxy + */ +void +ges_asset_set_id (GESAsset * asset, const gchar * id) +{ + GHashTable *entries; + + gpointer orig_id = NULL; + GESAssetCacheEntry *entry = NULL; + GESAssetPrivate *priv = asset->priv; + + if (priv->state != ASSET_INITIALIZED) { + GST_WARNING_OBJECT (asset, "Trying to rest ID on an object that is" + " not properly loaded"); + return; + } + + if (g_strcmp0 (id, priv->id) == 0) { + GST_DEBUG_OBJECT (asset, "ID is already %s", id); + + return; + } + + LOCK_CACHE; + entries = g_hash_table_lookup (type_entries_table, + _extractable_type_name (asset->priv->extractable_type)); + + g_hash_table_lookup_extended (entries, priv->id, &orig_id, + (gpointer *) & entry); + + g_hash_table_steal (entries, priv->id); + g_hash_table_insert (entries, g_strdup (id), entry); + + GST_DEBUG_OBJECT (asset, "Changing id from %s to %s", priv->id, id); + g_free (priv->id); + g_free (orig_id); + priv->id = g_strdup (id); + UNLOCK_CACHE; +} + +static GESAsset * +_unsure_material_for_wrong_id (const gchar * wrong_id, GType extractable_type, + GError * error) +{ + GESAsset *asset; + + if ((asset = ges_asset_cache_lookup (extractable_type, wrong_id))) + return asset; + + /* It is a dummy GESAsset, we just bruteforce its creation */ + asset = g_object_new (GES_TYPE_ASSET, "id", wrong_id, + "extractable-type", extractable_type, NULL); + + ges_asset_cache_put (asset, NULL); + ges_asset_cache_set_loaded (extractable_type, wrong_id, error); + + return asset; +} + +/********************************** + * * + * API implementation * + * * + **********************************/ + +/** + * ges_asset_get_extractable_type: + * @self: The #GESAsset + * + * Gets the type of object that can be extracted from @self + * + * Returns: the type of object that can be extracted from @self + */ +GType +ges_asset_get_extractable_type (GESAsset * self) +{ + return self->priv->extractable_type; +} + +/** + * ges_asset_request: + * @extractable_type: The #GType of the object that can be extracted from the new asset. + * @id: (allow-none): The Identifier or %NULL + * @error: (allow-none): An error to be set in case something wrong happens or %NULL + * + * Create a #GESAsset in the most simple cases, you should look at the @extractable_type + * documentation to see if that constructor can be called for this particular type + * + * Note that it won't be possible to instantiate the first %GESAsset with + * @id depending on the @extractable_type. For example instantiate a + * #GESAsset that extract #GESTimelineFileSource needs to be done async + * the first time for a specific ID. + * + * Returns: (transfer full) (allow-none): A reference to the wanted #GESAsset or %NULL + */ +GESAsset * +ges_asset_request (GType extractable_type, const gchar * id, GError ** error) +{ + gchar *real_id; + + GError *lerr = NULL; + GESAsset *asset = NULL; + + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + g_return_val_if_fail (g_type_is_a (extractable_type, G_TYPE_OBJECT), NULL); + g_return_val_if_fail (g_type_is_a (extractable_type, GES_TYPE_EXTRACTABLE), + NULL); + + real_id = _check_and_update_parameters (&extractable_type, id, &lerr); + if (real_id == NULL) { + /* We create an asset for that wrong ID so we have a reference that the + * user requested it */ + _unsure_material_for_wrong_id (id, extractable_type, lerr); + real_id = g_strdup (id); + } + + asset = ges_asset_cache_lookup (extractable_type, real_id); + if (asset) { + while (TRUE) { + switch (asset->priv->state) { + case ASSET_INITIALIZED: + gst_object_ref (asset); + goto done; + case ASSET_INITIALIZING: + asset = NULL; + goto done; + case ASSET_PROXIED: + asset = + ges_asset_cache_lookup (asset->priv->extractable_type, + asset->priv->proxied_asset_id); + if (asset == NULL) { + GST_ERROR ("Asset against a asset we do not" + " have in cache, something massively screwed"); + + goto done; + } + break; + case ASSET_INITIALIZED_WITH_ERROR: + GST_WARNING_OBJECT (asset, "Initialized with error, not returning"); + if (error) + *error = g_error_copy (asset->priv->error); + asset = NULL; + goto done; + default: + GST_WARNING ("Case %i not handle, returning", asset->priv->state); + goto done; + } + } + } else { + GObjectClass *klass; + GInitableIface *iface; + GType asset_type = ges_extractable_type_get_asset_type (extractable_type); + + klass = g_type_class_ref (asset_type); + iface = g_type_interface_peek (klass, G_TYPE_INITABLE); + + if (iface->init) { + asset = g_initable_new (asset_type, + NULL, NULL, "id", real_id, "extractable-type", + extractable_type, NULL); + } else { + GST_WARNING ("Tried to create an Asset for type %s but no ->init method", + g_type_name (extractable_type)); + } + g_type_class_unref (klass); + } + +done: + if (real_id) + g_free (real_id); + + GST_DEBUG ("New asset created synchronously: %p", asset); + return asset; +} + +/** + * ges_asset_request_async: + * @extractable_type: The #GType of the object that can be extracted from the + * new asset. The class must implement the #GESExtractable interface. + * @id: The Identifier of the asset we want to create. This identifier depends of the extractable, + * type you want. By default it is the name of the class itself (or %NULL), but for example for a + * GESTrackParseLaunchEffect, it will be the pipeline description, for a GESTimelineFileSource it + * will be the name of the file, etc... You should refer to the documentation of the #GESExtractable + * type you want to create a #GESAsset for. + * @cancellable: (allow-none): optional %GCancellable object, %NULL to ignore. + * @callback: a #GAsyncReadyCallback to call when the initialization is finished, + * Note that the @source of the callback will be the #GESAsset, but you need to + * make sure that the asset is properly loaded using the #ges_asset_request_finish + * method. This asset can not be used as is. + * @user_data: The user data to pass when @callback is called + * + * Request a new #GESAsset asyncronously, @callback will be called when the materail is + * ready to be used or if an error occured. + * + * Example of request of a GESAsset async: + * |[ + * // The request callback + * static void + * asset_loaded_cb (GESAsset * source, GAsyncResult * res, gpointer user_data) + * { + * GESAsset *asset; + * GError *error = NULL; + * + * asset = ges_asset_request_finish (res, &error); + * if (asset) { + * g_print ("The file: %s is usable as a FileSource", + * ges_asset_get_id (asset)); + * } else { + * g_print ("The file: %s is *not* usable as a FileSource because: %s", + * ges_asset_get_id (source), error->message); + * } + * + * g_object_unref (mfs); + * } + * + * // The request: + * ges_asset_request_async (GES_TYPE_TIMELINE_FILE_SOURCE, some_uri, NULL, + * (GAsyncReadyCallback) asset_loaded_cb, user_data); + * ]| + */ +void +ges_asset_request_async (GType extractable_type, + const gchar * id, GCancellable * cancellable, GAsyncReadyCallback callback, + gpointer user_data) +{ + gchar *real_id; + GESAsset *asset; + GError *error = NULL; + + g_return_if_fail (g_type_is_a (extractable_type, G_TYPE_OBJECT)); + g_return_if_fail (g_type_is_a (extractable_type, GES_TYPE_EXTRACTABLE)); + g_return_if_fail (callback); + + GST_DEBUG ("Creating asset with extractable type %s and ID=%s", + g_type_name (extractable_type), id); + + real_id = _check_and_update_parameters (&extractable_type, id, &error); + if (error) { + _unsure_material_for_wrong_id (id, extractable_type, error); + real_id = g_strdup (id); + } + + /* Check if we already have a asset for this ID */ + asset = ges_asset_cache_lookup (extractable_type, real_id); + if (asset) { + GSimpleAsyncResult *simple = g_simple_async_result_new (G_OBJECT (asset), + callback, user_data, ges_asset_request_async); + + /* In the case of proxied asset, we will loop until we find the + * last asset of the chain of proxied asset */ + while (TRUE) { + switch (asset->priv->state) { + case ASSET_INITIALIZED: + gst_object_ref (asset); + + GST_DEBUG_OBJECT (asset, "Asset in cache and initialized, " + "using it"); + + /* Takes its own references to @asset */ + g_simple_async_result_complete_in_idle (simple); + + goto done; + case ASSET_INITIALIZING: + GST_DEBUG_OBJECT (asset, "Asset in cache and but not " + "initialized, setting a new callback"); + ges_asset_cache_append_result (extractable_type, real_id, simple); + + goto done; + case ASSET_PROXIED: + asset = + ges_asset_cache_lookup (asset->priv->extractable_type, + asset->priv->proxied_asset_id); + if (asset == NULL) { + GST_ERROR ("Asset proxied against a asset we do not" + " have in cache, something massively screwed"); + + goto done; + } + break; + case ASSET_INITIALIZED_WITH_ERROR: + g_simple_async_report_gerror_in_idle (G_OBJECT (asset), callback, + user_data, error ? error : asset->priv->error); + + if (error) + g_error_free (error); + goto done; + default: + GST_WARNING ("Case %i not handle, returning", asset->priv->state); + return; + } + } + } + + g_async_initable_new_async (ges_extractable_type_get_asset_type + (extractable_type), G_PRIORITY_DEFAULT, cancellable, callback, user_data, + "id", real_id, "extractable-type", extractable_type, NULL); +done: + if (real_id) + g_free (real_id); +} + +/** + * ges_asset_get_id: + * @self: The #GESAsset to get ID from + * + * Gets the ID of a #GESAsset + * + * Returns: The ID of @self + */ +const gchar * +ges_asset_get_id (GESAsset * self) +{ + g_return_val_if_fail (GES_IS_ASSET (self), NULL); + + return self->priv->id; +} + +/** + * ges_asset_extract: + * @self: The #GESAsset to get extract an object from + * @error: (allow-none): An error to be set in case something wrong happens or %NULL + * + * Extracts a new #GObject from @asset. The type of the object is + * defined by the extractable-type of @asset, you can check what + * type will be extracted from @asset using + * #ges_asset_get_extractable_type + * + * Returns: (transfer floating) (allow-none): A newly created #GESExtractable + */ +GESExtractable * +ges_asset_extract (GESAsset * self, GError ** error) +{ + GESExtractable *extractable; + + g_return_val_if_fail (GES_IS_ASSET (self), NULL); + g_return_val_if_fail (GES_ASSET_GET_CLASS (self)->extract, NULL); + + GST_DEBUG_OBJECT (self, "Extracting asset of type %s", + g_type_name (self->priv->extractable_type)); + extractable = GES_ASSET_GET_CLASS (self)->extract (self, error); + + if (extractable == NULL) + return NULL; + + ges_extractable_set_asset (extractable, self); + + return extractable; +} + +/** + * ges_asset_request_finish: + * @res: The #GAsyncResult from which to get the newly created #GESAsset + * @error: (out) (allow-none) (transfer full): An error to be set in case + * something wrong happens or %NULL + * + * Finalize the request of an async #GESAsset + * + * Returns: (transfer full)(allow-none): The #GESAsset previously requested + */ +GESAsset * +ges_asset_request_finish (GAsyncResult * res, GError ** error) +{ + GObject *object; + GObject *source_object; + + g_return_val_if_fail (G_IS_ASYNC_RESULT (res), NULL); + + source_object = g_async_result_get_source_object (res); + g_assert (source_object != NULL); + + object = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object), + res, error); + + g_object_unref (source_object); + + return GES_ASSET (object); +} + +/** + * ges_list_assets: + * @filter: Type of assets to list, #GES_TYPE_EXTRACTABLE will list + * all assets + * + * List all @asset filtering per filter as defined by @filter. + * It copies the asset and thus will not be updated in time. + * + * Returns: (transfer container) (element-type GESAsset): The list of + * #GESAsset the object contains + */ +GList * +ges_list_assets (GType filter) +{ + GList *ret = NULL; + GESAsset *asset; + GHashTableIter iter, types_iter; + gpointer key, value, typename, assets; + + g_return_val_if_fail (g_type_is_a (filter, GES_TYPE_EXTRACTABLE), NULL); + + LOCK_CACHE; + g_hash_table_iter_init (&types_iter, type_entries_table); + while (g_hash_table_iter_next (&types_iter, &typename, &assets)) { + if (g_type_is_a (filter, g_type_from_name ((gchar *) typename)) == FALSE) + continue; + + g_hash_table_iter_init (&iter, (GHashTable *) assets); + while (g_hash_table_iter_next (&iter, &key, &value)) { + asset = ((GESAssetCacheEntry *) value)->asset; + + if (g_type_is_a (asset->priv->extractable_type, filter)) + ret = g_list_append (ret, asset); + } + } + UNLOCK_CACHE; + + return ret; +} diff --git a/ges/ges-asset.h b/ges/ges-asset.h new file mode 100644 index 0000000000..2232e0647a --- /dev/null +++ b/ges/ges-asset.h @@ -0,0 +1,98 @@ +/* GStreamer Editing Services + * + * Copyright (C) 2012 Thibault Saunier + * Copyright (C) 2012 Volodymyr Rudyi + * + * 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. + */ + +#ifndef _GES_ASSET_ +#define _GES_ASSET_ + +#include "ges-internal-enums.h" + +#include +#include +#include +#include +#include + +G_BEGIN_DECLS +#define GES_TYPE_ASSET ges_asset_get_type() +#define GES_ASSET(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_ASSET, GESAsset)) +#define GES_ASSET_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_ASSET, GESAssetClass)) +#define GES_IS_ASSET(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_ASSET)) +#define GES_IS_ASSET_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_ASSET)) +#define GES_ASSET_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_ASSET, GESAssetClass)) + +typedef struct _GESAssetPrivate GESAssetPrivate; + +GType ges_asset_get_type (void); + +struct _GESAsset +{ + GObject parent; + + /* */ + GESAssetPrivate *priv; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +struct _GESAssetClass +{ + GObjectClass parent; + + GESAssetLoadingReturn (*start_loading) (GESAsset *self, + GError **error); + GESExtractable* (*extract) (GESAsset *self, + GError **error); + /* Let subclasses know that we proxied an asset */ + void (*inform_proxy) (GESAsset *self, + const gchar *proxy_id); + /* Ask subclasses for a new ID for @self when the asset failed loading + * This function returns %FALSE when the ID could be updated or %TRUE + * otherwize */ + gboolean (*request_id_update) (GESAsset *self, + gchar **proposed_new_id, + GError *error) ; + gpointer _ges_reserved[GES_PADDING]; +}; + +GType ges_asset_get_extractable_type (GESAsset * self); +void ges_asset_request_async (GType extractable_type, + const gchar * id, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +GESAsset * ges_asset_request (GType extractable_type, + const gchar * id, + GError **error); +const gchar * ges_asset_get_id (GESAsset* self); +GESAsset * ges_asset_request_finish (GAsyncResult *res, + GError **error); +GESExtractable * ges_asset_extract (GESAsset * self, + GError **error); +GList * ges_list_assets (GType filter); + +G_END_DECLS +#endif /* _GES_ASSET */ diff --git a/ges/ges-internal-enums.h b/ges/ges-internal-enums.h new file mode 100644 index 0000000000..fde0e6d28b --- /dev/null +++ b/ges/ges-internal-enums.h @@ -0,0 +1,31 @@ +/* GStreamer Editing Services + * + * Copyright (C) 2012 Thibault Saunier + * + * 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. + */ + +#ifndef __GES_INTERNAL_ENUMS_H__ +#define __GES_INTERNAL_ENUMS_H__ + +typedef enum +{ + GES_ASSET_LOADING_ERROR, + GES_ASSET_LOADING_ASYNC, + GES_ASSET_LOADING_OK +} GESAssetLoadingReturn; + +#endif /* __GES_INTERNAL_ENUMS_H__ */ diff --git a/ges/ges-internal.h b/ges/ges-internal.h index 15ca0587b1..7fc59146a8 100644 --- a/ges/ges-internal.h +++ b/ges/ges-internal.h @@ -22,9 +22,15 @@ #define __GES_INTERNAL_H__ #include +#include + #include "ges-timeline.h" #include "ges-track-object.h" +#if 0 +#include "ges-asset.h" +#endif + GST_DEBUG_CATEGORY_EXTERN (_ges_debug); #define GST_CAT_DEFAULT _ges_debug @@ -59,4 +65,27 @@ timeline_move_object (GESTimeline *timeline, GESTrackObject * object, gboolean timeline_context_to_layer (GESTimeline *timeline, gint offset); +#if 0 +G_GNUC_INTERNAL void +ges_asset_cache_init (void); + +G_GNUC_INTERNAL void +ges_asset_set_id (GESAsset *asset, const gchar *id); + +G_GNUC_INTERNAL void +ges_asset_cache_put (GESAsset * asset, GSimpleAsyncResult *res); + +G_GNUC_INTERNAL gboolean +ges_asset_cache_set_loaded(GType extractable_type, const gchar * id, GError *error); + +GESAsset* +ges_asset_cache_lookup(GType extractable_type, const gchar * id); + +gboolean +ges_asset_set_proxy (GESAsset *asset, const gchar *new_id); + +G_GNUC_INTERNAL gboolean +ges_asset_request_id_update (GESAsset *asset, gchar **proposed_id, + GError *error); +#endif #endif /* __GES_INTERNAL_H__ */ diff --git a/ges/ges-types.h b/ges/ges-types.h index f677d8a8d8..a7bc701eed 100644 --- a/ges/ges-types.h +++ b/ges/ges-types.h @@ -140,4 +140,9 @@ typedef struct _GESKeyfileFormatterClass GESKeyfileFormatterClass; typedef struct _GESPitiviFormatter GESPitiviFormatter; typedef struct _GESPitiviFormatterClass GESPitiviFormatterClass; +#if 0 +typedef struct _GESAsset GESAsset; +typedef struct _GESAssetClass GESAssetClass; +#endif + #endif /* __GES_TYPES_H__ */ diff --git a/ges/ges.h b/ges/ges.h index 67e8ee1895..86457885d8 100644 --- a/ges/ges.h +++ b/ges/ges.h @@ -45,6 +45,9 @@ #include #include #include +#if 0 +#include +#endif #include #include @@ -79,6 +82,8 @@ gboolean ges_init (void); void ges_version (guint * major, guint * minor, guint * micro, guint * nano); +#define GES_ERROR_DOMAIN g_quark_from_static_string("GES") + G_END_DECLS #endif /* __GES_H__ */