1 /* GStreamer Editing Services
3 * Copyright (C) 2012-2015 Thibault Saunier <thibault.saunier@collabora.com>
4 * Copyright (C) 2012 Volodymyr Rudyi <vladimir.rudoy@gmail.com>
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Library General Public License for more details.
16 * You should have received a copy of the GNU Library General Public
17 * License along with this library; if not, write to the
18 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19 * Boston, MA 02111-1307, USA.
24 * @short_description: Represents usable resources inside the GStreamer
27 * A #GESAsset in the GStreamer Editing Services represents a resources
28 * that can be used. In particular, any class that implements the
29 * #GESExtractable interface may have some associated assets with a
30 * corresponding #GESAsset:extractable-type, from which its objects can be
31 * extracted using ges_asset_extract(). Some examples would be
32 * #GESClip, #GESFormatter and #GESTrackElement.
34 * All assets that are created within GES are stored in a cache; one per
35 * each #GESAsset:id and #GESAsset:extractable-type pair. These assets can
36 * be fetched, and initialized if they do not yet exist in the cache,
37 * using ges_asset_request().
40 * GESAsset *effect_asset;
43 * // You create an asset for an effect
44 * effect_asset = ges_asset_request (GES_TYPE_EFFECT, "agingtv", NULL);
46 * // And now you can extract an instance of GESEffect from that asset
47 * effect = GES_EFFECT (ges_asset_extract (effect_asset));
51 * The advantage of using assets, rather than simply creating the object
52 * directly, is that the currently loaded resources can be listed with
53 * ges_list_assets() and displayed to an end user. For example, to show
54 * which media files have been loaded, and a standard list of effects. In
55 * fact, the GES library already creates assets for #GESTransitionClip and
56 * #GESFormatter, which you can use to list all the available transition
57 * types and supported formats.
59 * The other advantage is that #GESAsset implements #GESMetaContainer, so
60 * metadata can be set on the asset, with some subclasses automatically
61 * creating this metadata on initiation.
63 * For example, to display information about the supported formats, you
64 * could do the following:
66 * GList *formatter_assets, *tmp;
68 * // List all the transitions
69 * formatter_assets = ges_list_assets (GES_TYPE_FORMATTER);
71 * // Print some infos about the formatter GESAsset
72 * for (tmp = formatter_assets; tmp; tmp = tmp->next) {
73 * gst_print ("Name of the formatter: %s, file extension it produces: %s",
74 * ges_meta_container_get_string (
75 * GES_META_CONTAINER (tmp->data), GES_META_FORMATTER_NAME),
76 * ges_meta_container_get_string (
77 * GES_META_CONTAINER (tmp->data), GES_META_FORMATTER_EXTENSION));
80 * g_list_free (transition_assets);
86 * Each asset is uniquely defined in the cache by its
87 * #GESAsset:extractable-type and #GESAsset:id. Depending on the
88 * #GESAsset:extractable-type, the #GESAsset:id can be used to parametrise
89 * the creation of the object upon extraction. By default, a class that
90 * implements #GESExtractable will only have a single associated asset,
91 * with an #GESAsset:id set to the type name of its objects. However, this
92 * is overwritten by some implementations, which allow a class to have
93 * multiple associated assets. For example, for #GESTransitionClip the
94 * #GESAsset:id will be a nickname of the #GESTransitionClip:vtype. You
95 * should check the documentation for each extractable type to see if they
96 * differ from the default.
98 * Moreover, each #GESAsset:extractable-type may also associate itself
99 * with a specific asset subclass. In such cases, when their asset is
100 * requested, an asset of this subclass will be returned instead.
104 * You can use a #GESProject to easily manage the assets of a
109 * Some assets can (temporarily) act as the #GESAsset:proxy of another
110 * asset. When the original asset is requested from the cache, the proxy
111 * will be returned in its place. This can be useful if, say, you want
112 * to substitute a #GESUriClipAsset corresponding to a high resolution
113 * media file with the asset of a lower resolution stand in.
115 * An asset may even have several proxies, the first of which will act as
116 * its default and be returned on requests, but the others will be ordered
117 * to take its place once it is removed. You can add a proxy to an asset,
118 * or set its default, using ges_asset_set_proxy(), and you can remove
119 * them with ges_asset_unproxy().
126 #include "ges-internal.h"
128 #define GLIB_DISABLE_DEPRECATION_WARNINGS
132 GST_DEBUG_CATEGORY_STATIC (ges_asset_debug);
133 #undef GST_CAT_DEFAULT
134 #define GST_CAT_DEFAULT ges_asset_debug
148 ASSET_NOT_INITIALIZED,
149 ASSET_INITIALIZING, ASSET_INITIALIZED_WITH_ERROR,
155 static GParamSpec *_properties[PROP_LAST];
157 struct _GESAssetPrivate
161 GType extractable_type;
163 /* used internally by try_proxy to pre-set a proxy whilst an asset is
164 * still loading. It can be used later to set the proxy for the asset
165 * once it has finished loading */
166 char *proxied_asset_id;
168 /* actual list of proxies */
170 /* the asset whose proxies list we belong to */
171 GESAsset *proxy_target;
173 /* The error that occurred when an asset has been initialized with error */
177 /* Internal structure to help avoid full loading
178 * of one asset several times
184 } GESAssetCacheEntry;
186 /* We are mapping entries by types and ID, such as:
189 * first_extractable_type_name1 :
191 * "some ID": GESAssetCacheEntry,
192 * "some other ID": GESAssetCacheEntry 2
194 * second_extractable_type_name :
196 * "some ID": GESAssetCacheEntry,
197 * "some other ID": GESAssetCacheEntry 2
201 * (The first extractable type is the type of the class that implemented
202 * the GESExtractable interface ie: GESClip, GESTimeline,
203 * GESFomatter, etc... but not subclasses)
205 * This is in order to be able to have 2 Asset with the same ID but
206 * different extractable types.
208 static GHashTable *type_entries_table = NULL;
209 /* Protect all the entries in the cache */
210 static GRecMutex asset_cache_lock;
211 #define LOCK_CACHE (g_rec_mutex_lock (&asset_cache_lock))
212 #define UNLOCK_CACHE (g_rec_mutex_unlock (&asset_cache_lock))
215 _check_and_update_parameters (GType * extractable_type, const gchar * id,
219 GType old_type = *extractable_type;
222 ges_extractable_get_real_extractable_type_for_id (*extractable_type, id);
224 if (*extractable_type == G_TYPE_NONE) {
225 GST_WARNING ("No way to create a Asset for ID: %s, type: %s", id,
226 g_type_name (old_type));
228 if (error && *error == NULL)
229 g_set_error (error, GES_ERROR, GES_ERROR_ASSET_WRONG_ID,
230 "Wrong ID, can not find any extractable_type");
234 real_id = ges_extractable_type_check_id (*extractable_type, id, error);
235 if (real_id == NULL) {
236 GST_WARNING ("Wrong ID %s, can not create asset", id);
239 if (error && *error == NULL)
240 g_set_error (error, GES_ERROR, GES_ERROR_ASSET_WRONG_ID, "Wrong ID");
248 /* FIXME: why are we not accepting a GError ** error argument, which we
249 * could pass to ges_asset_cache_set_loaded ()? Which would allow the
250 * error to be set for the GInitable init method below */
252 start_loading (GESAsset * asset)
254 GInitableIface *iface;
256 iface = g_type_interface_peek (GES_ASSET_GET_CLASS (asset), G_TYPE_INITABLE);
259 GST_INFO_OBJECT (asset, "Can not start loading sync, as no ->init vmethod");
263 ges_asset_cache_put (gst_object_ref (asset), NULL);
264 return ges_asset_cache_set_loaded (asset->priv->extractable_type,
265 asset->priv->id, NULL);
269 initable_init (GInitable * initable, GCancellable * cancellable,
272 /* FIXME: Is there actually a reason to be freeing the GError that
273 * error points to? */
274 g_clear_error (error);
276 return start_loading (GES_ASSET (initable));
280 async_initable_init_async (GAsyncInitable * initable, gint io_priority,
281 GCancellable * cancellable, GAsyncReadyCallback callback,
286 GError *error = NULL;
287 GESAsset *asset = GES_ASSET (initable);
289 task = g_task_new (asset, cancellable, callback, user_data);
291 ges_asset_cache_put (gst_object_ref (asset), task);
292 switch (GES_ASSET_GET_CLASS (asset)->start_loading (asset, &error)) {
293 case GES_ASSET_LOADING_ERROR:
296 g_set_error (&error, GES_ERROR, GES_ERROR_ASSET_LOADING,
297 "Could not start loading asset");
299 /* FIXME Define error code */
300 ges_asset_cache_set_loaded (asset->priv->extractable_type,
301 asset->priv->id, error);
302 g_error_free (error);
305 case GES_ASSET_LOADING_OK:
307 ges_asset_cache_set_loaded (asset->priv->extractable_type,
308 asset->priv->id, error);
311 case GES_ASSET_LOADING_ASYNC:
312 /* If Async.... let it go */
313 /* FIXME: how are user subclasses that implement ->start_loading
314 * to return GES_ASSET_LOADING_ASYNC meant to invoke the private
315 * method ges_asset_cache_set_loaded once they finish initializing?
322 async_initable_iface_init (GAsyncInitableIface * async_initable_iface)
324 async_initable_iface->init_async = async_initable_init_async;
328 initable_iface_init (GInitableIface * initable_iface)
330 initable_iface->init = initable_init;
333 G_DEFINE_TYPE_WITH_CODE (GESAsset, ges_asset, G_TYPE_OBJECT,
334 G_ADD_PRIVATE (GESAsset)
335 G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, async_initable_iface_init)
336 G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init)
337 G_IMPLEMENT_INTERFACE (GES_TYPE_META_CONTAINER, NULL));
339 /* GESAsset virtual methods default implementation */
340 static GESAssetLoadingReturn
341 ges_asset_start_loading_default (GESAsset * asset, GError ** error)
343 return GES_ASSET_LOADING_OK;
346 G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
347 static GESExtractable *
348 ges_asset_extract_default (GESAsset * asset, GError ** error)
352 GESAssetPrivate *priv = asset->priv;
353 GESExtractable *n_extractable;
358 params = ges_extractable_type_get_parameters_from_id (priv->extractable_type,
359 priv->id, &n_params);
362 values = g_malloc0 (sizeof (GValue) * n_params);
363 names = g_malloc0 (sizeof (gchar *) * n_params);
365 for (i = 0; i < n_params; i++) {
366 values[i] = params[i].value;
367 names[i] = params[i].name;
371 GES_EXTRACTABLE (g_object_new_with_properties (priv->extractable_type,
372 n_params, names, values));
377 g_value_unset (¶ms[n_params].value);
381 return n_extractable;
384 G_GNUC_END_IGNORE_DEPRECATIONS;
387 ges_asset_request_id_update_default (GESAsset * self, gchar ** proposed_new_id,
393 /* GObject virtual methods implementation */
395 ges_asset_get_property (GObject * object, guint property_id,
396 GValue * value, GParamSpec * pspec)
398 GESAsset *asset = GES_ASSET (object);
400 switch (property_id) {
402 g_value_set_gtype (value, asset->priv->extractable_type);
405 g_value_set_string (value, asset->priv->id);
408 g_value_set_object (value, ges_asset_get_proxy (asset));
410 case PROP_PROXY_TARGET:
411 g_value_set_object (value, ges_asset_get_proxy_target (asset));
414 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
419 ges_asset_set_property (GObject * object, guint property_id,
420 const GValue * value, GParamSpec * pspec)
422 GESAsset *asset = GES_ASSET (object);
424 switch (property_id) {
426 asset->priv->extractable_type = g_value_get_gtype (value);
427 /* NOTE: we calling this in the setter so metadata is set on the
428 * asset upon initiation, but before it has been loaded. */
429 ges_extractable_register_metas (asset->priv->extractable_type, asset);
432 asset->priv->id = g_value_dup_string (value);
435 ges_asset_set_proxy (asset, g_value_get_object (value));
438 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
443 ges_asset_finalize (GObject * object)
445 GESAssetPrivate *priv = GES_ASSET (object)->priv;
447 GST_LOG_OBJECT (object, "finalizing");
452 if (priv->proxied_asset_id)
453 g_free (priv->proxied_asset_id);
456 g_error_free (priv->error);
459 g_list_free (priv->proxies);
461 G_OBJECT_CLASS (ges_asset_parent_class)->finalize (object);
465 ges_asset_class_init (GESAssetClass * klass)
467 GObjectClass *object_class = G_OBJECT_CLASS (klass);
469 object_class->get_property = ges_asset_get_property;
470 object_class->set_property = ges_asset_set_property;
471 object_class->finalize = ges_asset_finalize;
474 * GESAsset:extractable-type:
476 * The #GESExtractable object type that can be extracted from the asset.
478 _properties[PROP_TYPE] =
479 g_param_spec_gtype ("extractable-type", "Extractable type",
480 "The type of the Object that can be extracted out of the asset",
481 G_TYPE_OBJECT, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE);
486 * The ID of the asset. This should be unique amongst all assets with
487 * the same #GESAsset:extractable-type. Depending on the associated
488 * #GESExtractable implementation, this id may convey some information
489 * about the #GObject that should be extracted. Note that, as such, the
490 * ID will have an expected format, and you can not choose this value
491 * arbitrarily. By default, this will be set to the type name of the
492 * #GESAsset:extractable-type, but you should check the documentation
493 * of the extractable type to see whether they differ from the
496 _properties[PROP_ID] =
497 g_param_spec_string ("id", "Identifier",
498 "The unique identifier of the asset", NULL,
499 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE);
504 * The default proxy for this asset, or %NULL if it has no proxy. A
505 * proxy will act as a substitute for the original asset when the
506 * original is requested (see ges_asset_request()).
508 * Setting this property will not usually remove the existing proxy, but
509 * will replace it as the default (see ges_asset_set_proxy()).
511 _properties[PROP_PROXY] =
512 g_param_spec_object ("proxy", "Proxy",
513 "The asset default proxy.", GES_TYPE_ASSET, G_PARAM_READWRITE);
516 * GESAsset:proxy-target:
518 * The asset that this asset is a proxy for, or %NULL if it is not a
519 * proxy for another asset.
521 * Note that even if this asset is acting as a proxy for another asset,
522 * but this asset is not the default #GESAsset:proxy, then @proxy-target
523 * will *still* point to this other asset. So you should check the
524 * #GESAsset:proxy property of @target-proxy before assuming it is the
525 * current default proxy for the target.
527 * Note that the #GObject::notify for this property is emitted after
528 * the #GESAsset:proxy #GObject::notify for the corresponding (if any)
529 * asset it is now the proxy of/no longer the proxy of.
531 _properties[PROP_PROXY_TARGET] =
532 g_param_spec_object ("proxy-target", "Proxy target",
533 "The target of a proxy asset.", GES_TYPE_ASSET, G_PARAM_READABLE);
535 g_object_class_install_properties (object_class, PROP_LAST, _properties);
537 klass->start_loading = ges_asset_start_loading_default;
538 klass->extract = ges_asset_extract_default;
539 klass->request_id_update = ges_asset_request_id_update_default;
540 klass->inform_proxy = NULL;
542 GST_DEBUG_CATEGORY_INIT (ges_asset_debug, "ges-asset",
543 GST_DEBUG_FG_BLUE | GST_DEBUG_BOLD, "GES Asset");
547 ges_asset_init (GESAsset * self)
549 self->priv = ges_asset_get_instance_private (self);
551 self->priv->state = ASSET_INITIALIZING;
552 self->priv->proxied_asset_id = NULL;
555 /* Internal methods */
557 static inline const gchar *
558 _extractable_type_name (GType type)
560 /* We can use `ges_asset_request (GES_TYPE_FORMATTER);` */
561 if (g_type_is_a (type, GES_TYPE_FORMATTER))
562 return g_type_name (GES_TYPE_FORMATTER);
564 return g_type_name (type);
568 ges_asset_cache_init_unlocked (void)
570 if (type_entries_table)
573 type_entries_table = g_hash_table_new_full (g_str_hash, g_str_equal,
574 g_free, (GDestroyNotify) g_hash_table_unref);
576 _init_formatter_assets ();
577 _init_standard_transition_assets ();
581 /* WITH LOCK_CACHE */
583 _get_type_entries (void)
585 if (type_entries_table)
586 return type_entries_table;
588 ges_asset_cache_init_unlocked ();
590 return type_entries_table;
593 /* WITH LOCK_CACHE */
594 static inline GESAssetCacheEntry *
595 _lookup_entry (GType extractable_type, const gchar * id)
597 GHashTable *entries_table;
599 entries_table = g_hash_table_lookup (_get_type_entries (),
600 _extractable_type_name (extractable_type));
602 return g_hash_table_lookup (entries_table, id);
608 _free_entries (gpointer entry)
610 GESAssetCacheEntry *data = (GESAssetCacheEntry *) entry;
612 gst_object_unref (data->asset);
613 g_slice_free (GESAssetCacheEntry, entry);
617 _gtask_return_error (GTask * task, GError * error)
619 g_task_return_error (task, g_error_copy (error));
623 _gtask_return_true (GTask * task, gpointer udata)
625 g_task_return_boolean (task, TRUE);
629 * ges_asset_cache_lookup:
631 * @id String identifier of asset
633 * Looks for asset with specified id in cache and it's completely loaded.
635 * Returns: (transfer none) (nullable): The #GESAsset found or %NULL
638 ges_asset_cache_lookup (GType extractable_type, const gchar * id)
640 GESAsset *asset = NULL;
641 GESAssetCacheEntry *entry = NULL;
643 g_return_val_if_fail (id, NULL);
646 entry = _lookup_entry (extractable_type, id);
648 asset = entry->asset;
655 ges_asset_cache_append_task (GType extractable_type,
656 const gchar * id, GTask * task)
658 GESAssetCacheEntry *entry = NULL;
661 if ((entry = _lookup_entry (extractable_type, id)))
662 entry->results = g_list_append (entry->results, task);
667 ges_asset_cache_set_loaded (GType extractable_type, const gchar * id,
671 GESAssetCacheEntry *entry = NULL;
672 GList *results = NULL;
673 GFunc user_func = NULL;
674 gpointer user_data = NULL;
677 if ((entry = _lookup_entry (extractable_type, id)) == NULL) {
679 GST_ERROR ("Calling but type %s ID: %s not in cached, "
680 "something massively screwed", g_type_name (extractable_type), id);
685 asset = entry->asset;
686 GST_DEBUG_OBJECT (entry->asset, ": (extractable type: %s) loaded, calling %i "
687 "callback (Error: %s)", g_type_name (asset->priv->extractable_type),
688 g_list_length (entry->results), error ? error->message : "");
690 results = entry->results;
691 entry->results = NULL;
694 asset->priv->state = ASSET_INITIALIZED_WITH_ERROR;
695 if (asset->priv->error)
696 g_error_free (asset->priv->error);
697 asset->priv->error = g_error_copy (error);
699 /* In case of error we do not want to emit in idle as we need to recover
701 user_func = (GFunc) _gtask_return_error;
703 GST_DEBUG_OBJECT (asset, "initialized with error");
705 asset->priv->state = ASSET_INITIALIZED;
706 user_func = (GFunc) _gtask_return_true;
707 GST_DEBUG_OBJECT (asset, "initialized");
711 g_list_foreach (results, user_func, user_data);
712 g_list_free_full (results, g_object_unref);
717 /* transfer full for both @asset and @task */
719 ges_asset_cache_put (GESAsset * asset, GTask * task)
721 GType extractable_type;
722 const gchar *asset_id;
723 GESAssetCacheEntry *entry;
725 /* Needing to work with the cache, taking the lock */
726 asset_id = ges_asset_get_id (asset);
727 extractable_type = asset->priv->extractable_type;
730 if (!(entry = _lookup_entry (extractable_type, asset_id))) {
731 GHashTable *entries_table;
733 entries_table = g_hash_table_lookup (_get_type_entries (),
734 _extractable_type_name (extractable_type));
735 if (entries_table == NULL) {
736 entries_table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
739 g_hash_table_insert (_get_type_entries (),
740 g_strdup (_extractable_type_name (extractable_type)), entries_table);
743 entry = g_slice_new0 (GESAssetCacheEntry);
745 /* transfer asset to entry */
746 entry->asset = asset;
748 entry->results = g_list_prepend (entry->results, task);
749 g_hash_table_insert (entries_table, (gpointer) g_strdup (asset_id),
752 /* give up the reference we were given */
753 gst_object_unref (asset);
755 GST_DEBUG ("%s already in cache, adding result %p", asset_id, task);
756 entry->results = g_list_prepend (entry->results, task);
763 ges_asset_cache_init (void)
766 ges_asset_cache_init_unlocked ();
771 ges_asset_cache_deinit (void)
773 _deinit_formatter_assets ();
776 g_hash_table_destroy (type_entries_table);
777 type_entries_table = NULL;
782 ges_asset_request_id_update (GESAsset * asset, gchar ** proposed_id,
785 g_return_val_if_fail (GES_IS_ASSET (asset), FALSE);
787 return GES_ASSET_GET_CLASS (asset)->request_id_update (asset, proposed_id,
791 /* pre-set a proxy id whilst the asset is still loading. Once the proxy
792 * is loaded, call ges_asset_finish_proxy (proxy) */
794 ges_asset_try_proxy (GESAsset * asset, const gchar * new_id)
796 GESAssetClass *class;
798 g_return_val_if_fail (GES_IS_ASSET (asset), FALSE);
800 if (g_strcmp0 (asset->priv->id, new_id) == 0) {
801 GST_WARNING_OBJECT (asset, "Trying to proxy to itself (%s),"
802 " NOT possible", new_id);
805 } else if (g_strcmp0 (asset->priv->proxied_asset_id, new_id) == 0) {
806 GST_WARNING_OBJECT (asset,
807 "Trying to proxy to same currently set proxy: %s -- %s",
808 asset->priv->proxied_asset_id, new_id);
813 g_free (asset->priv->proxied_asset_id);
814 asset->priv->state = ASSET_PROXIED;
815 asset->priv->proxied_asset_id = g_strdup (new_id);
817 /* FIXME: inform_proxy is not used consistently. For example, it is
818 * not called in set_proxy. However, it is still used by GESUriAsset.
819 * We should find some other method */
820 class = GES_ASSET_GET_CLASS (asset);
821 if (class->inform_proxy)
822 GES_ASSET_GET_CLASS (asset)->inform_proxy (asset, new_id);
824 GST_DEBUG_OBJECT (asset, "Trying to proxy to %s", new_id);
830 _lookup_proxied_asset (const gchar * id, GESAssetCacheEntry * entry,
831 const gchar * asset_id)
833 return !g_strcmp0 (asset_id, entry->asset->priv->proxied_asset_id);
836 /* find the assets that called try_proxy for the asset id of @proxy
837 * and set @proxy as their proxy */
839 ges_asset_finish_proxy (GESAsset * proxy)
841 GESAsset *proxied_asset;
842 GHashTable *entries_table;
843 GESAssetCacheEntry *entry;
846 entries_table = g_hash_table_lookup (_get_type_entries (),
847 _extractable_type_name (proxy->priv->extractable_type));
848 entry = g_hash_table_find (entries_table, (GHRFunc) _lookup_proxied_asset,
849 (gpointer) ges_asset_get_id (proxy));
853 GST_DEBUG_OBJECT (proxy, "Not proxying any asset %s", proxy->priv->id);
857 proxied_asset = entry->asset;
860 /* If the asset with the matching ->proxied_asset_id is already proxied
861 * by another asset, we actually want @proxy to proxy this instead */
862 while (proxied_asset->priv->proxies)
863 proxied_asset = proxied_asset->priv->proxies->data;
865 /* unless it is ourselves. I.e. it is already proxied by us */
866 if (proxied_asset == proxy)
869 GST_INFO_OBJECT (proxied_asset,
870 "%s Making sure the proxy chain is fully set.",
871 ges_asset_get_id (entry->asset));
872 if (g_strcmp0 (proxied_asset->priv->proxied_asset_id, proxy->priv->id) ||
873 g_strcmp0 (proxied_asset->priv->id, proxy->priv->proxied_asset_id))
874 ges_asset_finish_proxy (proxied_asset);
875 return ges_asset_set_proxy (proxied_asset, proxy);
879 _contained_in_proxy_tree (GESAsset * node, GESAsset * search)
884 for (tmp = node->priv->proxies; tmp; tmp = tmp->next) {
885 if (_contained_in_proxy_tree (tmp->data, search))
892 * ges_asset_set_proxy:
893 * @asset: The #GESAsset to proxy
894 * @proxy: (allow-none): A new default proxy for @asset
896 * Sets the #GESAsset:proxy for the asset.
898 * If @proxy is among the existing proxies of the asset (see
899 * ges_asset_list_proxies()) it will be moved to become the default
900 * proxy. Otherwise, if @proxy is not %NULL, it will be added to the list
901 * of proxies, as the new default. The previous default proxy will become
902 * 'next in line' for if the new one is removed, and so on. As such, this
903 * will **not** actually remove the previous default proxy (use
904 * ges_asset_unproxy() for that).
906 * Note that an asset can only act as a proxy for one other asset.
908 * As a special case, if @proxy is %NULL, then this method will actually
909 * remove **all** proxies from the asset.
911 * Returns: %TRUE if @proxy was successfully set as the default for
915 ges_asset_set_proxy (GESAsset * asset, GESAsset * proxy)
917 GESAsset *current_target;
918 g_return_val_if_fail (GES_IS_ASSET (asset), FALSE);
919 g_return_val_if_fail (proxy == NULL || GES_IS_ASSET (proxy), FALSE);
920 g_return_val_if_fail (asset != proxy, FALSE);
923 GList *tmp, *proxies;
924 if (asset->priv->error) {
925 GST_ERROR_OBJECT (asset,
926 "Asset was loaded with error (%s), it should not be 'unproxied'",
927 asset->priv->error->message);
932 GST_DEBUG_OBJECT (asset, "Removing all proxies");
933 proxies = asset->priv->proxies;
934 asset->priv->proxies = NULL;
936 for (tmp = proxies; tmp; tmp = tmp->next) {
937 GESAsset *proxy = tmp->data;
938 proxy->priv->proxy_target = NULL;
940 asset->priv->state = ASSET_INITIALIZED;
942 g_object_notify_by_pspec (G_OBJECT (asset), _properties[PROP_PROXY]);
943 for (tmp = proxies; tmp; tmp = tmp->next)
944 g_object_notify_by_pspec (G_OBJECT (tmp->data),
945 _properties[PROP_PROXY_TARGET]);
947 g_list_free (proxies);
950 current_target = proxy->priv->proxy_target;
952 if (current_target && current_target != asset) {
953 GST_ERROR_OBJECT (asset,
954 "Trying to use '%s' as a proxy, but it is already proxying '%s'",
955 proxy->priv->id, current_target->priv->id);
960 if (_contained_in_proxy_tree (proxy, asset)) {
961 GST_ERROR_OBJECT (asset, "Trying to setup a circular proxying dependency!");
966 if (g_list_find (asset->priv->proxies, proxy)) {
967 GST_INFO_OBJECT (asset,
968 "%" GST_PTR_FORMAT " already marked as proxy, moving to first", proxy);
969 asset->priv->proxies = g_list_remove (asset->priv->proxies, proxy);
972 GST_INFO ("%s is now proxied by %s", asset->priv->id, proxy->priv->id);
973 asset->priv->proxies = g_list_prepend (asset->priv->proxies, proxy);
975 proxy->priv->proxy_target = asset;
976 asset->priv->state = ASSET_PROXIED;
978 g_object_notify_by_pspec (G_OBJECT (asset), _properties[PROP_PROXY]);
979 if (current_target != asset)
980 g_object_notify_by_pspec (G_OBJECT (proxy), _properties[PROP_PROXY_TARGET]);
982 /* FIXME: ->inform_proxy is not called. We should figure out what the
983 * purpose of ->inform_proxy should be generically. Currently, it is
984 * only called in ges_asset_try_proxy! */
991 * @asset: The #GESAsset to no longer proxy with @proxy
992 * @proxy: An existing proxy of @asset
994 * Removes the proxy from the available list of proxies for the asset. If
995 * the given proxy is the default proxy of the list, then the next proxy
996 * in the available list (see ges_asset_list_proxies()) will become the
997 * default. If there are no other proxies, then the asset will no longer
998 * have a default #GESAsset:proxy.
1000 * Returns: %TRUE if @proxy was successfully removed from @asset's proxy
1004 ges_asset_unproxy (GESAsset * asset, GESAsset * proxy)
1006 gboolean removing_default;
1007 gboolean last_proxy;
1008 g_return_val_if_fail (GES_IS_ASSET (asset), FALSE);
1009 g_return_val_if_fail (GES_IS_ASSET (proxy), FALSE);
1010 g_return_val_if_fail (asset != proxy, FALSE);
1012 /* also tests if the list is NULL */
1013 if (!g_list_find (asset->priv->proxies, proxy)) {
1014 GST_INFO_OBJECT (asset, "'%s' is not a proxy.", proxy->priv->id);
1019 last_proxy = (asset->priv->proxies->next == NULL);
1020 if (last_proxy && asset->priv->error) {
1021 GST_ERROR_OBJECT (asset,
1022 "Asset was loaded with error (%s), its last proxy '%s' should "
1023 "not be removed", asset->priv->error->message, proxy->priv->id);
1027 removing_default = (asset->priv->proxies->data == proxy);
1029 asset->priv->proxies = g_list_remove (asset->priv->proxies, proxy);
1032 asset->priv->state = ASSET_INITIALIZED;
1033 proxy->priv->proxy_target = NULL;
1035 if (removing_default)
1036 g_object_notify_by_pspec (G_OBJECT (asset), _properties[PROP_PROXY]);
1037 g_object_notify_by_pspec (G_OBJECT (proxy), _properties[PROP_PROXY_TARGET]);
1043 * ges_asset_list_proxies:
1044 * @asset: A #GESAsset
1046 * Get all the proxies that the asset has. The first item of the list will
1047 * be the default #GESAsset:proxy. The second will be the proxy that is
1048 * 'next in line' to be default, and so on.
1050 * Returns: (element-type GESAsset) (transfer none): The list of proxies
1054 ges_asset_list_proxies (GESAsset * asset)
1056 g_return_val_if_fail (GES_IS_ASSET (asset), NULL);
1058 return asset->priv->proxies;
1062 * ges_asset_get_proxy:
1063 * @asset: A #GESAsset
1065 * Gets the default #GESAsset:proxy of the asset.
1067 * Returns: (transfer none) (nullable): The default proxy of @asset.
1070 ges_asset_get_proxy (GESAsset * asset)
1072 g_return_val_if_fail (GES_IS_ASSET (asset), NULL);
1074 if (asset->priv->state == ASSET_PROXIED && asset->priv->proxies) {
1075 return asset->priv->proxies->data;
1082 * ges_asset_get_proxy_target:
1083 * @proxy: A #GESAsset
1085 * Gets the #GESAsset:proxy-target of the asset.
1087 * Note that the proxy target may have loaded with an error, so you should
1088 * call ges_asset_get_error() on the returned target.
1090 * Returns: (transfer none) (nullable): The asset that @proxy is a proxy
1094 ges_asset_get_proxy_target (GESAsset * proxy)
1096 g_return_val_if_fail (GES_IS_ASSET (proxy), NULL);
1098 return proxy->priv->proxy_target;
1101 /* Caution, this method should be used in rare cases (ie: for the project
1102 * as we can change its ID from a useless one to a proper URI). In most
1103 * cases you want to update the ID creating a proxy
1106 ges_asset_set_id (GESAsset * asset, const gchar * id)
1108 GHashTable *entries;
1110 gpointer orig_id = NULL;
1111 GESAssetCacheEntry *entry = NULL;
1112 GESAssetPrivate *priv = NULL;
1114 g_return_if_fail (GES_IS_ASSET (asset));
1118 if (priv->state != ASSET_INITIALIZED) {
1119 GST_WARNING_OBJECT (asset, "Trying to rest ID on an object that is"
1120 " not properly loaded");
1124 if (g_strcmp0 (id, priv->id) == 0) {
1125 GST_DEBUG_OBJECT (asset, "ID is already %s", id);
1131 entries = g_hash_table_lookup (_get_type_entries (),
1132 _extractable_type_name (asset->priv->extractable_type));
1134 g_return_if_fail (g_hash_table_lookup_extended (entries, priv->id, &orig_id,
1135 (gpointer *) & entry));
1137 g_hash_table_steal (entries, priv->id);
1138 g_hash_table_insert (entries, g_strdup (id), entry);
1140 GST_DEBUG_OBJECT (asset, "Changing id from %s to %s", priv->id, id);
1143 priv->id = g_strdup (id);
1148 _ensure_asset_for_wrong_id (const gchar * wrong_id, GType extractable_type,
1153 if ((asset = ges_asset_cache_lookup (extractable_type, wrong_id)))
1156 /* It is a dummy GESAsset, we just bruteforce its creation */
1157 asset = g_object_new (GES_TYPE_ASSET, "id", wrong_id,
1158 "extractable-type", extractable_type, NULL);
1160 /* transfer ownership to the cache */
1161 ges_asset_cache_put (asset, NULL);
1162 ges_asset_cache_set_loaded (extractable_type, wrong_id, error);
1167 /**********************************
1169 * API implementation *
1171 **********************************/
1174 * ges_asset_get_extractable_type:
1175 * @self: The #GESAsset
1177 * Gets the #GESAsset:extractable-type of the asset.
1179 * Returns: The extractable type of @self.
1182 ges_asset_get_extractable_type (GESAsset * self)
1184 g_return_val_if_fail (GES_IS_ASSET (self), G_TYPE_INVALID);
1186 return self->priv->extractable_type;
1190 * ges_asset_request:
1191 * @extractable_type: The #GESAsset:extractable-type of the asset
1192 * @id: (allow-none): The #GESAsset:id of the asset
1193 * @error: (allow-none): An error to be set if the requested asset has
1194 * loaded with an error, or %NULL to ignore
1196 * Returns an asset with the given properties. If such an asset already
1197 * exists in the cache (it has been previously created in GES), then a
1198 * reference to the existing asset is returned. Otherwise, a newly created
1199 * asset is returned, and also added to the cache.
1201 * If the requested asset has been loaded with an error, then @error is
1202 * set, if given, and %NULL will be returned instead.
1204 * Note that the given @id may not be exactly the #GESAsset:id that is
1205 * set on the returned asset. For instance, it may be adjusted into a
1206 * standard format. Or, if a #GESExtractable type does not have its
1207 * extraction parametrised, as is the case by default, then the given @id
1208 * may be ignored entirely and the #GESAsset:id set to some standard, in
1209 * which case a %NULL @id can be given.
1211 * Similarly, the given @extractable_type may not be exactly the
1212 * #GESAsset:extractable-type that is set on the returned asset. Instead,
1213 * the actual extractable type may correspond to a subclass of the given
1214 * @extractable_type, depending on the given @id.
1216 * Moreover, depending on the given @extractable_type, the returned asset
1217 * may belong to a subclass of #GESAsset.
1219 * Finally, if the requested asset has a #GESAsset:proxy, then the proxy
1220 * that is found at the end of the chain of proxies is returned (a proxy's
1221 * proxy will take its place, and so on, unless it has no proxy).
1223 * Some asset subclasses only support asynchronous construction of its
1224 * assets, such as #GESUriClip. For such assets this method will fail, and
1225 * you should use ges_asset_request_async() instead. In the case of
1226 * #GESUriClip, you can use ges_uri_clip_asset_request_sync() if you only
1227 * want to wait for the request to finish.
1229 * Returns: (transfer full) (allow-none): A reference to the requested
1230 * asset, or %NULL if an error occurred.
1233 ges_asset_request (GType extractable_type, const gchar * id, GError ** error)
1237 GError *lerr = NULL;
1238 GESAsset *asset = NULL, *proxy;
1239 gboolean proxied = TRUE;
1241 g_return_val_if_fail (error == NULL || *error == NULL, NULL);
1242 g_return_val_if_fail (g_type_is_a (extractable_type, G_TYPE_OBJECT), NULL);
1243 g_return_val_if_fail (g_type_is_a (extractable_type, GES_TYPE_EXTRACTABLE),
1246 real_id = _check_and_update_parameters (&extractable_type, id, &lerr);
1247 if (real_id == NULL) {
1248 /* We create an asset for that wrong ID so we have a reference that the
1249 * user requested it */
1250 _ensure_asset_for_wrong_id (id, extractable_type, lerr);
1251 real_id = g_strdup (id);
1254 g_error_free (lerr);
1256 /* asset owned by cache */
1257 asset = ges_asset_cache_lookup (extractable_type, real_id);
1261 switch (asset->priv->state) {
1262 case ASSET_INITIALIZED:
1264 case ASSET_INITIALIZING:
1268 proxy = ges_asset_get_proxy (asset);
1269 if (proxy == NULL) {
1270 GST_ERROR ("Proxied against an asset we do not"
1271 " have in cache, something massively screwed");
1277 } while ((proxy = ges_asset_get_proxy (asset)));
1280 case ASSET_NEEDS_RELOAD:
1281 GST_DEBUG_OBJECT (asset, "Asset in cache and needs reload");
1282 if (!start_loading (asset)) {
1283 GST_ERROR ("Failed to reload the asset for id %s", id);
1287 case ASSET_INITIALIZED_WITH_ERROR:
1288 GST_WARNING_OBJECT (asset, "Initialized with error, not returning");
1289 if (asset->priv->error && error)
1290 *error = g_error_copy (asset->priv->error);
1294 GST_WARNING ("Case %i not handle, returning", asset->priv->state);
1300 gst_object_ref (asset);
1302 GObjectClass *klass;
1303 GInitableIface *iface;
1304 GType asset_type = ges_extractable_type_get_asset_type (extractable_type);
1306 klass = g_type_class_ref (asset_type);
1307 iface = g_type_interface_peek (klass, G_TYPE_INITABLE);
1310 /* FIXME: allow the error to be set, which GInitable is designed
1312 asset = g_initable_new (asset_type,
1313 NULL, NULL, "id", real_id, "extractable-type",
1314 extractable_type, NULL);
1316 GST_WARNING ("Tried to create an Asset for type %s but no ->init method",
1317 g_type_name (extractable_type));
1319 g_type_class_unref (klass);
1325 GST_DEBUG ("New asset created synchronously: %p", asset);
1330 * ges_asset_request_async:
1331 * @extractable_type: The #GESAsset:extractable-type of the asset
1332 * @id: (allow-none): The #GESAsset:id of the asset
1333 * @cancellable: (allow-none): An object to allow cancellation of the
1334 * asset request, or %NULL to ignore
1335 * @callback: A function to call when the initialization is finished
1336 * @user_data: Data to be passed to @callback
1338 * Requests an asset with the given properties asynchronously (see
1339 * ges_asset_request()). When the asset has been initialized or fetched
1340 * from the cache, the given callback function will be called. The
1341 * asset can then be retrieved in the callback using the
1342 * ges_asset_request_finish() method on the given #GAsyncResult.
1344 * Note that the source object passed to the callback will be the
1345 * #GESAsset corresponding to the request, but it may not have loaded
1346 * correctly and therefore can not be used as is. Instead,
1347 * ges_asset_request_finish() should be used to fetch a usable asset, or
1348 * indicate that an error occurred in the asset's creation.
1350 * Note that the callback will be called in the #GMainLoop running under
1351 * the same #GMainContext that ges_init() was called in. So, if you wish
1352 * the callback to be invoked outside the default #GMainContext, you can
1353 * call g_main_context_push_thread_default() in a new thread before
1354 * calling ges_init().
1356 * Example of an asynchronous asset request:
1358 * // The request callback
1360 * asset_loaded_cb (GESAsset * source, GAsyncResult * res, gpointer user_data)
1363 * GError *error = NULL;
1365 * asset = ges_asset_request_finish (res, &error);
1367 * gst_print ("The file: %s is usable as a GESUriClip",
1368 * ges_asset_get_id (asset));
1370 * gst_print ("The file: %s is *not* usable as a GESUriClip because: %s",
1371 * ges_asset_get_id (source), error->message);
1374 * gst_object_unref (asset);
1378 * ges_asset_request_async (GES_TYPE_URI_CLIP, some_uri, NULL,
1379 * (GAsyncReadyCallback) asset_loaded_cb, user_data);
1383 ges_asset_request_async (GType extractable_type,
1384 const gchar * id, GCancellable * cancellable, GAsyncReadyCallback callback,
1389 GError *error = NULL;
1392 g_return_if_fail (g_type_is_a (extractable_type, G_TYPE_OBJECT));
1393 g_return_if_fail (g_type_is_a (extractable_type, GES_TYPE_EXTRACTABLE));
1394 g_return_if_fail (callback);
1396 GST_DEBUG ("Creating asset with extractable type %s and ID=%s",
1397 g_type_name (extractable_type), id);
1399 real_id = _check_and_update_parameters (&extractable_type, id, &error);
1401 _ensure_asset_for_wrong_id (id, extractable_type, error);
1402 real_id = g_strdup (id);
1405 /* Check if we already have an asset for this ID */
1406 asset = ges_asset_cache_lookup (extractable_type, real_id);
1408 task = g_task_new (asset, NULL, callback, user_data);
1410 /* In the case of proxied asset, we will loop until we find the
1411 * last asset of the chain of proxied asset */
1413 switch (asset->priv->state) {
1414 case ASSET_INITIALIZED:
1415 GST_DEBUG_OBJECT (asset, "Asset in cache and initialized, "
1418 /* Takes its own references to @asset */
1419 g_task_return_boolean (task, TRUE);
1422 case ASSET_INITIALIZING:
1423 GST_DEBUG_OBJECT (asset, "Asset in cache and but not "
1424 "initialized, setting a new callback");
1425 ges_asset_cache_append_task (extractable_type, real_id, task);
1429 case ASSET_PROXIED:{
1430 GESAsset *target = ges_asset_get_proxy (asset);
1432 if (target == NULL) {
1433 GST_ERROR ("Asset %s proxied against an asset (%s) we do not"
1434 " have in cache, something massively screwed",
1435 asset->priv->id, asset->priv->proxied_asset_id);
1442 case ASSET_NEEDS_RELOAD:
1443 GST_DEBUG_OBJECT (asset, "Asset in cache and needs reload");
1444 ges_asset_cache_append_task (extractable_type, real_id, task);
1446 GES_ASSET_GET_CLASS (asset)->start_loading (asset, &error);
1449 case ASSET_INITIALIZED_WITH_ERROR:
1450 g_task_return_error (task,
1451 error ? g_error_copy (error) : g_error_copy (asset->priv->error));
1453 g_clear_error (&error);
1457 GST_WARNING ("Case %i not handle, returning", asset->priv->state);
1463 g_async_initable_new_async (ges_extractable_type_get_asset_type
1464 (extractable_type), G_PRIORITY_DEFAULT, cancellable, callback, user_data,
1465 "id", real_id, "extractable-type", extractable_type, NULL);
1468 gst_object_unref (task);
1474 * ges_asset_needs_reload
1475 * @extractable_type: The #GESAsset:extractable-type of the asset that
1477 * @id: (allow-none): The #GESAsset:id of the asset asset that needs
1480 * Indicate that an existing #GESAsset in the cache should be reloaded
1481 * upon the next request. This can be used when some condition has
1482 * changed, which may require that an existing asset should be updated.
1483 * For example, if an external resource has changed or now become
1486 * Note, the asset is not immediately changed, but will only actually
1487 * reload on the next call to ges_asset_request() or
1488 * ges_asset_request_async().
1490 * Returns: %TRUE if the specified asset exists in the cache and could be
1491 * marked for reloading.
1494 ges_asset_needs_reload (GType extractable_type, const gchar * id)
1498 GError *error = NULL;
1500 g_return_val_if_fail (g_type_is_a (extractable_type, GES_TYPE_EXTRACTABLE),
1503 real_id = _check_and_update_parameters (&extractable_type, id, &error);
1505 _ensure_asset_for_wrong_id (id, extractable_type, error);
1506 real_id = g_strdup (id);
1509 asset = ges_asset_cache_lookup (extractable_type, real_id);
1516 GST_DEBUG_OBJECT (asset,
1517 "Asset with id %s switch state to ASSET_NEEDS_RELOAD",
1518 ges_asset_get_id (asset));
1519 asset->priv->state = ASSET_NEEDS_RELOAD;
1520 g_clear_error (&asset->priv->error);
1524 GST_DEBUG ("Asset with id %s not found in cache", id);
1530 * @self: A #GESAsset
1532 * Gets the #GESAsset:id of the asset.
1534 * Returns: The ID of @self.
1537 ges_asset_get_id (GESAsset * self)
1539 g_return_val_if_fail (GES_IS_ASSET (self), NULL);
1541 return self->priv->id;
1545 * ges_asset_extract:
1546 * @self: The #GESAsset to extract an object from
1547 * @error: (allow-none): An error to be set in case something goes wrong,
1548 * or %NULL to ignore
1550 * Extracts a new #GESAsset:extractable-type object from the asset. The
1551 * #GESAsset:id of the asset may determine the properties and state of the
1552 * newly created object.
1554 * Returns: (transfer floating): A newly created object, or %NULL if an
1558 ges_asset_extract (GESAsset * self, GError ** error)
1560 GESExtractable *extractable;
1562 g_return_val_if_fail (GES_IS_ASSET (self), NULL);
1563 g_return_val_if_fail (GES_ASSET_GET_CLASS (self)->extract, NULL);
1565 GST_DEBUG_OBJECT (self, "Extracting asset of type %s",
1566 g_type_name (self->priv->extractable_type));
1567 extractable = GES_ASSET_GET_CLASS (self)->extract (self, error);
1569 if (extractable == NULL)
1572 if (ges_extractable_get_asset (extractable) == NULL)
1573 ges_extractable_set_asset (extractable, self);
1579 * ges_asset_request_finish:
1580 * @res: The task result to fetch the asset from
1581 * @error: (out) (allow-none) (transfer full): An error to be set in case
1582 * something goes wrong, or %NULL to ignore
1584 * Fetches an asset requested by ges_asset_request_async(), which
1585 * finalises the request.
1587 * Returns: (transfer full): The requested asset, or %NULL if an error
1591 ges_asset_request_finish (GAsyncResult * res, GError ** error)
1594 GObject *source_object;
1596 g_return_val_if_fail (G_IS_ASYNC_RESULT (res), NULL);
1598 source_object = g_async_result_get_source_object (res);
1599 g_assert (source_object != NULL);
1601 object = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object),
1604 gst_object_unref (source_object);
1606 return GES_ASSET (object);
1611 * @filter: The type of object that can be extracted from the asset
1613 * List all the assets in the current cache whose
1614 * #GESAsset:extractable-type are of the given type (including
1617 * Note that, since only a #GESExtractable can be extracted from an asset,
1618 * using `GES_TYPE_EXTRACTABLE` as @filter will return all the assets in
1619 * the current cache.
1621 * Returns: (transfer container) (element-type GESAsset): A list of all
1622 * #GESAsset-s currently in the cache whose #GESAsset:extractable-type is
1623 * of the @filter type.
1626 ges_list_assets (GType filter)
1630 GHashTableIter iter, types_iter;
1631 gpointer key, value, typename, assets;
1633 g_return_val_if_fail (g_type_is_a (filter, GES_TYPE_EXTRACTABLE), NULL);
1636 g_hash_table_iter_init (&types_iter, _get_type_entries ());
1637 while (g_hash_table_iter_next (&types_iter, &typename, &assets)) {
1638 if (g_type_is_a (filter, g_type_from_name ((gchar *) typename)) == FALSE)
1641 g_hash_table_iter_init (&iter, (GHashTable *) assets);
1642 while (g_hash_table_iter_next (&iter, &key, &value)) {
1643 asset = ((GESAssetCacheEntry *) value)->asset;
1645 if (g_type_is_a (asset->priv->extractable_type, filter))
1646 ret = g_list_append (ret, asset);
1655 * ges_asset_get_error:
1656 * @self: A #GESAsset
1658 * Retrieve the error that was set on the asset when it was loaded.
1660 * Returns: (transfer none) (nullable): The error set on @asset, or
1661 * %NULL if no error occurred when @asset was loaded.
1666 ges_asset_get_error (GESAsset * self)
1668 g_return_val_if_fail (GES_IS_ASSET (self), NULL);
1670 return self->priv->error;