1 /* GStreamer Editing Services
2  *
3  * Copyright (C) 2012-2015 Thibault Saunier <thibault.saunier@collabora.com>
4  * Copyright (C) 2012 Volodymyr Rudyi <vladimir.rudoy@gmail.com>
5  *
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.
10  *
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.
15  *
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.
20  */
21 /**
22  * SECTION: gesasset
23  * @title: GESAsset
24  * @short_description: Represents usable resources inside the GStreamer Editing Services
25  *
26  * The Assets in the GStreamer Editing Services represent the resources
27  * that can be used. You can create assets for any type that implements the #GESExtractable
28  * interface, for example #GESClips, #GESFormatter, and #GESTrackElement do implement it.
29  * This means that assets will represent for example a #GESUriClips, #GESBaseEffect etc,
30  * and then you can extract objects of those types with the appropriate parameters from the asset
31  * using the #ges_asset_extract method:
32  *
33  * |[
34  * GESAsset *effect_asset;
35  * GESEffect *effect;
36  *
37  * // You create an asset for an effect
38  * effect_asset = ges_asset_request (GES_TYPE_EFFECT, "agingtv", NULL);
39  *
40  * // And now you can extract an instance of GESEffect from that asset
41  * effect = GES_EFFECT (ges_asset_extract (effect_asset));
42  *
43  * ]|
44  *
45  * In that example, the advantages of having a #GESAsset are that you can know what effects
46  * you are working with and let your user know about the avalaible ones, you can add metadata
47  * to the #GESAsset through the #GESMetaContainer interface and you have a model for your
48  * custom effects. Note that #GESAsset management is making easier thanks to the #GESProject class.
49  *
50  * Each asset is represented by a pair of @extractable_type and @id (string). Actually the @extractable_type
51  * is the type that implements the #GESExtractable interface, that means that for example for a #GESUriClip,
52  * the type that implements the #GESExtractable interface is #GESClip.
53  * The identifier represents different things depending on the @extractable_type and you should check
54  * the documentation of each type to know what the ID of #GESAsset actually represents for that type. By default,
55  * we only have one #GESAsset per type, and the @id is the name of the type, but this behaviour is overriden
56  * to be more useful. For example, for GESTransitionClips, the ID is the vtype of the transition
57  * you will extract from it (ie crossfade, box-wipe-rc etc..) For #GESEffect the ID is the
58  * @bin-description property of the extracted objects (ie the gst-launch style description of the bin that
59  * will be used).
60  *
61  * Each and every #GESAsset is cached into GES, and you can query those with the #ges_list_assets function.
62  * Also the system will automatically register #GESAssets for #GESFormatters and #GESTransitionClips
63  * and standard effects (actually not implemented yet) and you can simply query those calling:
64  * |[
65  *    GList *formatter_assets, *tmp;
66  *
67  *    //  List all  the transitions
68  *    formatter_assets = ges_list_assets (GES_TYPE_FORMATTER);
69  *
70  *    // Print some infos about the formatter GESAsset
71  *    for (tmp = formatter_assets; tmp; tmp = tmp->next) {
72  *      g_print ("Name of the formatter: %s, file extension it produces: %s",
73  *        ges_meta_container_get_string (GES_META_CONTAINER (tmp->data), GES_META_FORMATTER_NAME),
74  *        ges_meta_container_get_string (GES_META_CONTAINER (tmp->data), GES_META_FORMATTER_EXTENSION));
75  *    }
76  *
77  *    g_list_free (transition_assets);
78  *
79  * ]|
80  *
81  * You can request the creation of #GESAssets using either ges_asset_request() or
82  * ges_asset_request_async(). All the #GESAssets are cached and thus any asset that has already
83  * been created can be requested again without overhead.
84  */
85 #ifdef HAVE_CONFIG_H
86 #include "config.h"
87 #endif
88 
89 #include "ges.h"
90 #include "ges-internal.h"
91 
92 #define GLIB_DISABLE_DEPRECATION_WARNINGS
93 
94 #include <gst/gst.h>
95 
96 GST_DEBUG_CATEGORY_STATIC (ges_asset_debug);
97 #undef GST_CAT_DEFAULT
98 #define GST_CAT_DEFAULT ges_asset_debug
99 
100 enum
101 {
102   PROP_0,
103   PROP_TYPE,
104   PROP_ID,
105   PROP_PROXY,
106   PROP_PROXY_TARGET,
107   PROP_LAST
108 };
109 
110 typedef enum
111 {
112   ASSET_NOT_INITIALIZED,
113   ASSET_INITIALIZING, ASSET_INITIALIZED_WITH_ERROR,
114   ASSET_PROXIED,
115   ASSET_NEEDS_RELOAD,
116   ASSET_INITIALIZED
117 } GESAssetState;
118 
119 static GParamSpec *_properties[PROP_LAST];
120 
121 struct _GESAssetPrivate
122 {
123   gchar *id;
124   GESAssetState state;
125   GType extractable_type;
126 
127   /* When an asset is proxied, instantiating it will
128    * return the asset it points to */
129   char *proxied_asset_id;
130 
131   GList *proxies;
132   GESAsset *proxy_target;
133 
134   /* The error that occurred when an asset has been initialized with error */
135   GError *error;
136 };
137 
138 /* Internal structure to help avoid full loading
139  * of one asset several times
140  */
141 typedef struct
142 {
143   GList *results;
144   GESAsset *asset;
145 } GESAssetCacheEntry;
146 
147 /* Also protect all the entries in the cache */
148 G_LOCK_DEFINE_STATIC (asset_cache_lock);
149 /* We are mapping entries by types and ID, such as:
150  *
151  * {
152  *   first_extractable_type_name1 :
153  *      {
154  *        "some ID": GESAssetCacheEntry,
155  *        "some other ID": GESAssetCacheEntry 2
156  *      },
157  *   second_extractable_type_name :
158  *      {
159  *        "some ID": GESAssetCacheEntry,
160  *        "some other ID": GESAssetCacheEntry 2
161  *      }
162  * }
163  *
164  * (The first extractable type is the type of the class that implemented
165  *  the GESExtractable interface ie: GESClip, GESTimeline,
166  *  GESFomatter, etc... but not subclasses)
167  *
168  * This is in order to be able to have 2 Asset with the same ID but
169  * different extractable types.
170  **/
171 static GHashTable *type_entries_table = NULL;
172 #define LOCK_CACHE   (G_LOCK (asset_cache_lock))
173 #define UNLOCK_CACHE (G_UNLOCK (asset_cache_lock))
174 
175 static gchar *
_check_and_update_parameters(GType * extractable_type,const gchar * id,GError ** error)176 _check_and_update_parameters (GType * extractable_type, const gchar * id,
177     GError ** error)
178 {
179   gchar *real_id;
180   GType old_type = *extractable_type;
181 
182   *extractable_type =
183       ges_extractable_get_real_extractable_type_for_id (*extractable_type, id);
184 
185   if (*extractable_type == G_TYPE_NONE) {
186     GST_WARNING ("No way to create a Asset for ID: %s, type: %s", id,
187         g_type_name (old_type));
188 
189     if (error && *error == NULL)
190       g_set_error (error, GES_ERROR, GES_ERROR_ASSET_WRONG_ID,
191           "Wrong ID, can not find any extractable_type");
192     return NULL;
193   }
194 
195   real_id = ges_extractable_type_check_id (*extractable_type, id, error);
196   if (real_id == NULL) {
197     GST_WARNING ("Wrong ID %s, can not create asset", id);
198 
199     g_free (real_id);
200     if (error && *error == NULL)
201       g_set_error (error, GES_ERROR, GES_ERROR_ASSET_WRONG_ID, "Wrong ID");
202 
203     return NULL;
204   }
205 
206   return real_id;
207 }
208 
209 static gboolean
start_loading(GESAsset * asset)210 start_loading (GESAsset * asset)
211 {
212   ges_asset_cache_put (gst_object_ref (asset), NULL);
213   return ges_asset_cache_set_loaded (asset->priv->extractable_type,
214       asset->priv->id, NULL);
215 }
216 
217 static gboolean
initable_init(GInitable * initable,GCancellable * cancellable,GError ** error)218 initable_init (GInitable * initable, GCancellable * cancellable,
219     GError ** error)
220 {
221   g_clear_error (error);
222 
223   return start_loading (GES_ASSET (initable));
224 }
225 
226 static void
async_initable_init_async(GAsyncInitable * initable,gint io_priority,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)227 async_initable_init_async (GAsyncInitable * initable, gint io_priority,
228     GCancellable * cancellable, GAsyncReadyCallback callback,
229     gpointer user_data)
230 {
231   GTask *task;
232 
233   GError *error = NULL;
234   GESAsset *asset = GES_ASSET (initable);
235 
236   task = g_task_new (asset, cancellable, callback, user_data);
237 
238   ges_asset_cache_put (g_object_ref (asset), task);
239   switch (GES_ASSET_GET_CLASS (asset)->start_loading (asset, &error)) {
240     case GES_ASSET_LOADING_ERROR:
241     {
242       if (error == NULL)
243         g_set_error (&error, GES_ERROR, GES_ERROR_ASSET_LOADING,
244             "Could not start loading asset");
245 
246       /* FIXME Define error code */
247       ges_asset_cache_set_loaded (asset->priv->extractable_type,
248           asset->priv->id, error);
249       g_error_free (error);
250       return;
251     }
252     case GES_ASSET_LOADING_OK:
253     {
254       ges_asset_cache_set_loaded (asset->priv->extractable_type,
255           asset->priv->id, error);
256       return;
257     }
258     case GES_ASSET_LOADING_ASYNC:
259       /* If Async....  let it go */
260       break;
261   }
262 }
263 
264 static void
async_initable_iface_init(GAsyncInitableIface * async_initable_iface)265 async_initable_iface_init (GAsyncInitableIface * async_initable_iface)
266 {
267   async_initable_iface->init_async = async_initable_init_async;
268 }
269 
270 static void
initable_iface_init(GInitableIface * initable_iface)271 initable_iface_init (GInitableIface * initable_iface)
272 {
273   initable_iface->init = initable_init;
274 }
275 
276 G_DEFINE_TYPE_WITH_CODE (GESAsset, ges_asset, G_TYPE_OBJECT,
277     G_ADD_PRIVATE (GESAsset)
278     G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, async_initable_iface_init)
279     G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init)
280     G_IMPLEMENT_INTERFACE (GES_TYPE_META_CONTAINER, NULL));
281 
282 /* GESAsset virtual methods default implementation */
283 static GESAssetLoadingReturn
ges_asset_start_loading_default(GESAsset * asset,GError ** error)284 ges_asset_start_loading_default (GESAsset * asset, GError ** error)
285 {
286   return GES_ASSET_LOADING_OK;
287 }
288 
289 static GESExtractable *
ges_asset_extract_default(GESAsset * asset,GError ** error)290 ges_asset_extract_default (GESAsset * asset, GError ** error)
291 {
292   guint n_params;
293   GParameter *params;
294   GESAssetPrivate *priv = asset->priv;
295   GESExtractable *n_extractable;
296 
297 
298   params = ges_extractable_type_get_parameters_from_id (priv->extractable_type,
299       priv->id, &n_params);
300 
301 #if GLIB_CHECK_VERSION(2, 53, 1)
302   {
303     gint i;
304     GValue *values;
305     const gchar **names;
306 
307     values = g_malloc0 (sizeof (GValue) * n_params);
308     names = g_malloc0 (sizeof (gchar *) * n_params);
309 
310     for (i = 0; i < n_params; i++) {
311       values[i] = params[i].value;
312       names[i] = params[i].name;
313     }
314 
315     n_extractable =
316         GES_EXTRACTABLE (g_object_new_with_properties (priv->extractable_type,
317             n_params, names, values));
318     g_free (names);
319     g_free (values);
320   }
321 #else
322   n_extractable = g_object_newv (priv->extractable_type, n_params, params);
323 #endif
324 
325   while (n_params--)
326     g_value_unset (&params[n_params].value);
327 
328   g_free (params);
329 
330   return n_extractable;
331 }
332 
333 static gboolean
ges_asset_request_id_update_default(GESAsset * self,gchar ** proposed_new_id,GError * error)334 ges_asset_request_id_update_default (GESAsset * self, gchar ** proposed_new_id,
335     GError * error)
336 {
337   return FALSE;
338 }
339 
340 /* GObject virtual methods implementation */
341 static void
ges_asset_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)342 ges_asset_get_property (GObject * object, guint property_id,
343     GValue * value, GParamSpec * pspec)
344 {
345   GESAsset *asset = GES_ASSET (object);
346 
347   switch (property_id) {
348     case PROP_TYPE:
349       g_value_set_gtype (value, asset->priv->extractable_type);
350       break;
351     case PROP_ID:
352       g_value_set_string (value, asset->priv->id);
353       break;
354     case PROP_PROXY:
355       g_value_set_object (value, ges_asset_get_proxy (asset));
356       break;
357     case PROP_PROXY_TARGET:
358       g_value_set_object (value, ges_asset_get_proxy_target (asset));
359       break;
360     default:
361       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
362   }
363 }
364 
365 static void
ges_asset_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)366 ges_asset_set_property (GObject * object, guint property_id,
367     const GValue * value, GParamSpec * pspec)
368 {
369   GESAsset *asset = GES_ASSET (object);
370 
371   switch (property_id) {
372     case PROP_TYPE:
373       asset->priv->extractable_type = g_value_get_gtype (value);
374       ges_extractable_register_metas (asset->priv->extractable_type, asset);
375       break;
376     case PROP_ID:
377       asset->priv->id = g_value_dup_string (value);
378       break;
379     case PROP_PROXY:
380       ges_asset_set_proxy (asset, g_value_get_object (value));
381       break;
382     case PROP_PROXY_TARGET:
383       ges_asset_set_proxy (g_value_get_object (value), asset);
384       break;
385     default:
386       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
387   }
388 }
389 
390 static void
ges_asset_finalize(GObject * object)391 ges_asset_finalize (GObject * object)
392 {
393   GESAssetPrivate *priv = GES_ASSET (object)->priv;
394 
395   GST_DEBUG_OBJECT (object, "finalizing");
396 
397   if (priv->id)
398     g_free (priv->id);
399 
400   if (priv->proxied_asset_id)
401     g_free (priv->proxied_asset_id);
402 
403   if (priv->error)
404     g_error_free (priv->error);
405 
406   if (priv->proxies)
407     g_list_free (priv->proxies);
408 
409   G_OBJECT_CLASS (ges_asset_parent_class)->finalize (object);
410 }
411 
412 void
ges_asset_class_init(GESAssetClass * klass)413 ges_asset_class_init (GESAssetClass * klass)
414 {
415   GObjectClass *object_class = G_OBJECT_CLASS (klass);
416 
417   object_class->get_property = ges_asset_get_property;
418   object_class->set_property = ges_asset_set_property;
419   object_class->finalize = ges_asset_finalize;
420 
421   _properties[PROP_TYPE] =
422       g_param_spec_gtype ("extractable-type", "Extractable type",
423       "The type of the Object that can be extracted out of the asset",
424       G_TYPE_OBJECT, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE);
425 
426   _properties[PROP_ID] =
427       g_param_spec_string ("id", "Identifier",
428       "The unic identifier of the asset", NULL,
429       G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE);
430 
431   _properties[PROP_PROXY] =
432       g_param_spec_object ("proxy", "Proxy",
433       "The asset default proxy.", GES_TYPE_ASSET, G_PARAM_READWRITE);
434 
435   _properties[PROP_PROXY_TARGET] =
436       g_param_spec_object ("proxy-target", "Proxy target",
437       "The target of a proxy asset.", GES_TYPE_ASSET, G_PARAM_READWRITE);
438 
439   g_object_class_install_properties (object_class, PROP_LAST, _properties);
440 
441   klass->start_loading = ges_asset_start_loading_default;
442   klass->extract = ges_asset_extract_default;
443   klass->request_id_update = ges_asset_request_id_update_default;
444   klass->inform_proxy = NULL;
445 
446   GST_DEBUG_CATEGORY_INIT (ges_asset_debug, "ges-asset",
447       GST_DEBUG_FG_BLUE | GST_DEBUG_BOLD, "GES Asset");
448 }
449 
450 void
ges_asset_init(GESAsset * self)451 ges_asset_init (GESAsset * self)
452 {
453   self->priv = ges_asset_get_instance_private (self);
454 
455   self->priv->state = ASSET_INITIALIZING;
456   self->priv->proxied_asset_id = NULL;
457 }
458 
459 /* Internal methods */
460 
461 /* Find the type that implemented the GESExtractable interface */
462 static inline const gchar *
_extractable_type_name(GType type)463 _extractable_type_name (GType type)
464 {
465   while (1) {
466     if (g_type_is_a (g_type_parent (type), GES_TYPE_EXTRACTABLE))
467       type = g_type_parent (type);
468     else
469       return g_type_name (type);
470   }
471 }
472 
473 static inline GESAssetCacheEntry *
_lookup_entry(GType extractable_type,const gchar * id)474 _lookup_entry (GType extractable_type, const gchar * id)
475 {
476   GHashTable *entries_table;
477 
478   entries_table = g_hash_table_lookup (type_entries_table,
479       _extractable_type_name (extractable_type));
480   if (entries_table)
481     return g_hash_table_lookup (entries_table, id);
482 
483   return NULL;
484 }
485 
486 static void
_free_entries(gpointer entry)487 _free_entries (gpointer entry)
488 {
489   GESAssetCacheEntry *data = (GESAssetCacheEntry *) entry;
490   if (data->asset)
491     gst_object_unref (data->asset);
492   g_slice_free (GESAssetCacheEntry, entry);
493 }
494 
495 static void
_gtask_return_error(GTask * task,GError * error)496 _gtask_return_error (GTask * task, GError * error)
497 {
498   g_task_return_error (task, g_error_copy (error));
499 }
500 
501 static void
_gtask_return_true(GTask * task,gpointer udata)502 _gtask_return_true (GTask * task, gpointer udata)
503 {
504   g_task_return_boolean (task, TRUE);
505 }
506 
507 /**
508  * ges_asset_cache_lookup:
509  *
510  * @id String identifier of asset
511  *
512  * Looks for asset with specified id in cache and it's completely loaded.
513  *
514  * Returns: (transfer none) (nullable): The #GESAsset found or %NULL
515  */
516 GESAsset *
ges_asset_cache_lookup(GType extractable_type,const gchar * id)517 ges_asset_cache_lookup (GType extractable_type, const gchar * id)
518 {
519   GESAsset *asset = NULL;
520   GESAssetCacheEntry *entry = NULL;
521 
522   g_return_val_if_fail (id, NULL);
523 
524   LOCK_CACHE;
525   entry = _lookup_entry (extractable_type, id);
526   if (entry)
527     asset = entry->asset;
528   UNLOCK_CACHE;
529 
530   return asset;
531 }
532 
533 static void
ges_asset_cache_append_task(GType extractable_type,const gchar * id,GTask * task)534 ges_asset_cache_append_task (GType extractable_type,
535     const gchar * id, GTask * task)
536 {
537   GESAssetCacheEntry *entry = NULL;
538 
539   LOCK_CACHE;
540   if ((entry = _lookup_entry (extractable_type, id)))
541     entry->results = g_list_append (entry->results, task);
542   UNLOCK_CACHE;
543 }
544 
545 gboolean
ges_asset_cache_set_loaded(GType extractable_type,const gchar * id,GError * error)546 ges_asset_cache_set_loaded (GType extractable_type, const gchar * id,
547     GError * error)
548 {
549   GESAsset *asset;
550   GESAssetCacheEntry *entry = NULL;
551   GList *results = NULL;
552   GFunc user_func = NULL;
553   gpointer user_data = NULL;
554 
555   LOCK_CACHE;
556   if ((entry = _lookup_entry (extractable_type, id)) == NULL) {
557     UNLOCK_CACHE;
558     GST_ERROR ("Calling but type %s ID: %s not in cached, "
559         "something massively screwed", g_type_name (extractable_type), id);
560 
561     return FALSE;
562   }
563 
564   asset = entry->asset;
565   GST_DEBUG_OBJECT (entry->asset, ": (extractable type: %s) loaded, calling %i "
566       "callback (Error: %s)", g_type_name (asset->priv->extractable_type),
567       g_list_length (entry->results), error ? error->message : "");
568 
569   results = entry->results;
570   entry->results = NULL;
571 
572   if (error) {
573     asset->priv->state = ASSET_INITIALIZED_WITH_ERROR;
574     if (asset->priv->error)
575       g_error_free (asset->priv->error);
576     asset->priv->error = g_error_copy (error);
577 
578     /* In case of error we do not want to emit in idle as we need to recover
579      * if possible */
580     user_func = (GFunc) _gtask_return_error;
581     user_data = error;
582     GST_DEBUG_OBJECT (asset, "initialized with error");
583   } else {
584     asset->priv->state = ASSET_INITIALIZED;
585     user_func = (GFunc) _gtask_return_true;
586     GST_DEBUG_OBJECT (asset, "initialized");
587   }
588   UNLOCK_CACHE;
589 
590   g_list_foreach (results, user_func, user_data);
591   g_list_free_full (results, g_object_unref);
592 
593   return TRUE;
594 }
595 
596 void
ges_asset_cache_put(GESAsset * asset,GTask * task)597 ges_asset_cache_put (GESAsset * asset, GTask * task)
598 {
599   GType extractable_type;
600   const gchar *asset_id;
601   GESAssetCacheEntry *entry;
602 
603   /* Needing to work with the cache, taking the lock */
604   asset_id = ges_asset_get_id (asset);
605   extractable_type = asset->priv->extractable_type;
606 
607   LOCK_CACHE;
608   if (!(entry = _lookup_entry (extractable_type, asset_id))) {
609     GHashTable *entries_table;
610 
611     entries_table = g_hash_table_lookup (type_entries_table,
612         _extractable_type_name (extractable_type));
613     if (entries_table == NULL) {
614       entries_table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
615           _free_entries);
616 
617       g_hash_table_insert (type_entries_table,
618           g_strdup (_extractable_type_name (extractable_type)), entries_table);
619     }
620 
621     entry = g_slice_new0 (GESAssetCacheEntry);
622 
623     entry->asset = asset;
624     if (task)
625       entry->results = g_list_prepend (entry->results, task);
626     g_hash_table_insert (entries_table, (gpointer) g_strdup (asset_id),
627         (gpointer) entry);
628   } else {
629     if (task) {
630       GST_DEBUG ("%s already in cache, adding result %p", asset_id, task);
631       entry->results = g_list_prepend (entry->results, task);
632     }
633   }
634   UNLOCK_CACHE;
635 }
636 
637 void
ges_asset_cache_init(void)638 ges_asset_cache_init (void)
639 {
640   type_entries_table = g_hash_table_new_full (g_str_hash, g_str_equal,
641       g_free, (GDestroyNotify) g_hash_table_unref);
642 
643   _init_formatter_assets ();
644   _init_standard_transition_assets ();
645 }
646 
647 void
ges_asset_cache_deinit(void)648 ges_asset_cache_deinit (void)
649 {
650   g_hash_table_destroy (type_entries_table);
651   type_entries_table = NULL;
652 }
653 
654 gboolean
ges_asset_request_id_update(GESAsset * asset,gchar ** proposed_id,GError * error)655 ges_asset_request_id_update (GESAsset * asset, gchar ** proposed_id,
656     GError * error)
657 {
658   g_return_val_if_fail (GES_IS_ASSET (asset), FALSE);
659 
660   return GES_ASSET_GET_CLASS (asset)->request_id_update (asset, proposed_id,
661       error);
662 }
663 
664 gboolean
ges_asset_try_proxy(GESAsset * asset,const gchar * new_id)665 ges_asset_try_proxy (GESAsset * asset, const gchar * new_id)
666 {
667   GESAssetClass *class;
668 
669   g_return_val_if_fail (GES_IS_ASSET (asset), FALSE);
670 
671   if (g_strcmp0 (asset->priv->id, new_id) == 0) {
672     GST_WARNING_OBJECT (asset, "Trying to proxy to itself (%s),"
673         " NOT possible", new_id);
674 
675     return FALSE;
676   } else if (g_strcmp0 (asset->priv->proxied_asset_id, new_id) == 0) {
677     GST_WARNING_OBJECT (asset,
678         "Trying to proxy to same currently set proxy: %s -- %s",
679         asset->priv->proxied_asset_id, new_id);
680 
681     return FALSE;
682   }
683 
684   g_free (asset->priv->proxied_asset_id);
685   asset->priv->state = ASSET_PROXIED;
686   asset->priv->proxied_asset_id = g_strdup (new_id);
687 
688   class = GES_ASSET_GET_CLASS (asset);
689   if (class->inform_proxy)
690     GES_ASSET_GET_CLASS (asset)->inform_proxy (asset, new_id);
691 
692   GST_DEBUG_OBJECT (asset, "Trying to proxy to %s", new_id);
693 
694   return TRUE;
695 }
696 
697 static gboolean
_lookup_proxied_asset(const gchar * id,GESAssetCacheEntry * entry,const gchar * asset_id)698 _lookup_proxied_asset (const gchar * id, GESAssetCacheEntry * entry,
699     const gchar * asset_id)
700 {
701   return !g_strcmp0 (asset_id, entry->asset->priv->proxied_asset_id);
702 }
703 
704 /**
705  * ges_asset_set_proxy:
706  * @asset: The #GESAsset to set proxy on
707  * @proxy: (allow-none): The #GESAsset that should be used as default proxy for @asset or
708  * %NULL if you want to use the currently set proxy. Note that an asset can proxy one and only
709  * one other asset.
710  *
711  * A proxying asset is an asset that can substitue the real @asset. For example if you
712  * have a full HD #GESUriClipAsset you might want to set a lower resolution (HD version
713  * of the same file) as proxy. Note that when an asset is proxied, calling
714  * #ges_asset_request will actually return the proxy asset.
715  *
716  * Returns: %TRUE if @proxy has been set on @asset, %FALSE otherwise.
717  */
718 gboolean
ges_asset_set_proxy(GESAsset * asset,GESAsset * proxy)719 ges_asset_set_proxy (GESAsset * asset, GESAsset * proxy)
720 {
721   g_return_val_if_fail (asset == NULL || GES_IS_ASSET (asset), FALSE);
722   g_return_val_if_fail (proxy == NULL || GES_IS_ASSET (proxy), FALSE);
723   g_return_val_if_fail (asset != proxy, FALSE);
724 
725   if (!proxy) {
726     if (asset->priv->error) {
727       GST_ERROR_OBJECT (asset,
728           "Proxy was loaded with error (%s), it should not be 'unproxied'",
729           asset->priv->error->message);
730 
731       return FALSE;
732     }
733 
734     if (asset->priv->proxies) {
735       GESAsset *old_proxy = GES_ASSET (asset->priv->proxies->data);
736 
737       old_proxy->priv->proxy_target = NULL;
738       g_object_notify_by_pspec (G_OBJECT (old_proxy),
739           _properties[PROP_PROXY_TARGET]);
740     }
741 
742     GST_DEBUG_OBJECT (asset, "%s not proxied anymore", asset->priv->id);
743     asset->priv->state = ASSET_INITIALIZED;
744     g_object_notify_by_pspec (G_OBJECT (asset), _properties[PROP_PROXY]);
745 
746     return TRUE;
747   }
748 
749   if (asset == NULL) {
750     GHashTable *entries_table;
751     GESAssetCacheEntry *entry;
752 
753     entries_table = g_hash_table_lookup (type_entries_table,
754         _extractable_type_name (proxy->priv->extractable_type));
755     entry = g_hash_table_find (entries_table, (GHRFunc) _lookup_proxied_asset,
756         (gpointer) ges_asset_get_id (proxy));
757 
758     if (!entry) {
759       GST_DEBUG_OBJECT (asset, "Not proxying any asset");
760       return FALSE;
761     }
762 
763     asset = entry->asset;
764     while (asset->priv->proxies)
765       asset = asset->priv->proxies->data;
766   }
767 
768   if (proxy->priv->proxy_target) {
769     GST_ERROR_OBJECT (asset,
770         "Trying to use %s as a proxy, but it is already proxying %s",
771         proxy->priv->id, proxy->priv->proxy_target->priv->id);
772 
773     return FALSE;
774   }
775 
776   if (g_list_find (proxy->priv->proxies, asset)) {
777     GST_ERROR_OBJECT (asset, "Trying to setup a circular proxying dependency!");
778 
779     return FALSE;
780   }
781 
782   if (g_list_find (asset->priv->proxies, proxy)) {
783     GST_INFO_OBJECT (asset,
784         "%" GST_PTR_FORMAT " already marked as proxy, moving to first", proxy);
785     GES_ASSET (asset->priv->proxies->data)->priv->proxy_target = NULL;
786     asset->priv->proxies = g_list_remove (asset->priv->proxies, proxy);
787   }
788 
789   GST_INFO ("%s is now proxied by %s", asset->priv->id, proxy->priv->id);
790   asset->priv->proxies = g_list_prepend (asset->priv->proxies, proxy);
791   proxy->priv->proxy_target = asset;
792   g_object_notify_by_pspec (G_OBJECT (proxy), _properties[PROP_PROXY_TARGET]);
793 
794   asset->priv->state = ASSET_PROXIED;
795   g_object_notify_by_pspec (G_OBJECT (asset), _properties[PROP_PROXY]);
796 
797   return TRUE;
798 }
799 
800 /**
801  * ges_asset_unproxy:
802  * @asset: The #GESAsset to stop proxying with @proxy
803  * @proxy: The #GESAsset to stop considering as a proxy for @asset
804  *
805  * Removes @proxy from the list of known proxies for @asset.
806  * If @proxy was the current proxy for @asset, stop using it.
807  *
808  * Returns: %TRUE if @proxy was a known proxy for @asset, %FALSE otherwise.
809  */
810 gboolean
ges_asset_unproxy(GESAsset * asset,GESAsset * proxy)811 ges_asset_unproxy (GESAsset * asset, GESAsset * proxy)
812 {
813   g_return_val_if_fail (GES_IS_ASSET (asset), FALSE);
814   g_return_val_if_fail (GES_IS_ASSET (proxy), FALSE);
815   g_return_val_if_fail (asset != proxy, FALSE);
816 
817   if (!g_list_find (asset->priv->proxies, proxy)) {
818     GST_INFO_OBJECT (asset, "%s is not a proxy.", proxy->priv->id);
819 
820     return FALSE;
821   }
822 
823   if (asset->priv->proxies->data == proxy)
824     ges_asset_set_proxy (asset, NULL);
825 
826   asset->priv->proxies = g_list_remove (asset->priv->proxies, proxy);
827 
828   return TRUE;
829 }
830 
831 /**
832  * ges_asset_list_proxies:
833  * @asset: The #GESAsset to get proxies from
834  *
835  * Returns: (element-type GESAsset) (transfer none): The list of proxies @asset has. Note that the default asset to be
836  * used is always the first in that list.
837  */
838 GList *
ges_asset_list_proxies(GESAsset * asset)839 ges_asset_list_proxies (GESAsset * asset)
840 {
841   g_return_val_if_fail (GES_IS_ASSET (asset), NULL);
842 
843   return asset->priv->proxies;
844 }
845 
846 /**
847  * ges_asset_get_proxy:
848  * @asset: The #GESAsset to get currenlty used proxy
849  *
850  * Returns: (transfer none) (nullable): The proxy in use for @asset
851  */
852 GESAsset *
ges_asset_get_proxy(GESAsset * asset)853 ges_asset_get_proxy (GESAsset * asset)
854 {
855   g_return_val_if_fail (GES_IS_ASSET (asset), NULL);
856 
857   if (asset->priv->state == ASSET_PROXIED && asset->priv->proxies) {
858     return asset->priv->proxies->data;
859   }
860 
861   return NULL;
862 }
863 
864 /**
865  * ges_asset_get_proxy_target:
866  * @proxy: The #GESAsset from which to get the the asset it proxies.
867  *
868  * Returns: (transfer none) (nullable): The #GESAsset that is proxied by @proxy
869  */
870 GESAsset *
ges_asset_get_proxy_target(GESAsset * proxy)871 ges_asset_get_proxy_target (GESAsset * proxy)
872 {
873   g_return_val_if_fail (GES_IS_ASSET (proxy), NULL);
874 
875   return proxy->priv->proxy_target;
876 }
877 
878 /* Caution, this method should be used in rare cases (ie: for the project
879  * as we can change its ID from a useless one to a proper URI). In most
880  * cases you want to update the ID creating a proxy
881  */
882 void
ges_asset_set_id(GESAsset * asset,const gchar * id)883 ges_asset_set_id (GESAsset * asset, const gchar * id)
884 {
885   GHashTable *entries;
886 
887   gpointer orig_id = NULL;
888   GESAssetCacheEntry *entry = NULL;
889   GESAssetPrivate *priv = NULL;
890 
891   g_return_if_fail (GES_IS_ASSET (asset));
892 
893   priv = asset->priv;
894 
895   if (priv->state != ASSET_INITIALIZED) {
896     GST_WARNING_OBJECT (asset, "Trying to rest ID on an object that is"
897         " not properly loaded");
898     return;
899   }
900 
901   if (g_strcmp0 (id, priv->id) == 0) {
902     GST_DEBUG_OBJECT (asset, "ID is already %s", id);
903 
904     return;
905   }
906 
907   LOCK_CACHE;
908   entries = g_hash_table_lookup (type_entries_table,
909       _extractable_type_name (asset->priv->extractable_type));
910 
911   g_return_if_fail (g_hash_table_lookup_extended (entries, priv->id, &orig_id,
912           (gpointer *) & entry));
913 
914   g_hash_table_steal (entries, priv->id);
915   g_hash_table_insert (entries, g_strdup (id), entry);
916 
917   GST_DEBUG_OBJECT (asset, "Changing id from %s to %s", priv->id, id);
918   g_free (priv->id);
919   g_free (orig_id);
920   priv->id = g_strdup (id);
921   UNLOCK_CACHE;
922 }
923 
924 static GESAsset *
_ensure_asset_for_wrong_id(const gchar * wrong_id,GType extractable_type,GError * error)925 _ensure_asset_for_wrong_id (const gchar * wrong_id, GType extractable_type,
926     GError * error)
927 {
928   GESAsset *asset;
929 
930   if ((asset = ges_asset_cache_lookup (extractable_type, wrong_id)))
931     return asset;
932 
933   /* It is a dummy GESAsset, we just bruteforce its creation */
934   asset = g_object_new (GES_TYPE_ASSET, "id", wrong_id,
935       "extractable-type", extractable_type, NULL);
936 
937   ges_asset_cache_put (asset, NULL);
938   ges_asset_cache_set_loaded (extractable_type, wrong_id, error);
939 
940   return asset;
941 }
942 
943 /**********************************
944  *                                *
945  *      API implementation        *
946  *                                *
947  **********************************/
948 
949 /**
950  * ges_asset_get_extractable_type:
951  * @self: The #GESAsset
952  *
953  * Gets the type of object that can be extracted from @self
954  *
955  * Returns: the type of object that can be extracted from @self
956  */
957 GType
ges_asset_get_extractable_type(GESAsset * self)958 ges_asset_get_extractable_type (GESAsset * self)
959 {
960   g_return_val_if_fail (GES_IS_ASSET (self), G_TYPE_INVALID);
961 
962   return self->priv->extractable_type;
963 }
964 
965 /**
966  * ges_asset_request:
967  * @extractable_type: The #GType of the object that can be extracted from the new asset.
968  * @id: (allow-none): The Identifier or %NULL
969  * @error: (allow-none): An error to be set in case something wrong happens or %NULL
970  *
971  * Create a #GESAsset in the most simple cases, you should look at the @extractable_type
972  * documentation to see if that constructor can be called for this particular type
973  *
974  * As it is recommanded not to instanciate assets for GESUriClip synchronously,
975  * it will not work with this method, but you can instead use the specific
976  * #ges_uri_clip_asset_request_sync method if you really want to.
977  *
978  * Returns: (transfer full) (allow-none): A reference to the wanted #GESAsset or %NULL
979  */
980 GESAsset *
ges_asset_request(GType extractable_type,const gchar * id,GError ** error)981 ges_asset_request (GType extractable_type, const gchar * id, GError ** error)
982 {
983   gchar *real_id;
984 
985   GError *lerr = NULL;
986   GESAsset *asset = NULL;
987 
988   g_return_val_if_fail (error == NULL || *error == NULL, NULL);
989   g_return_val_if_fail (g_type_is_a (extractable_type, G_TYPE_OBJECT), NULL);
990   g_return_val_if_fail (g_type_is_a (extractable_type, GES_TYPE_EXTRACTABLE),
991       NULL);
992 
993   real_id = _check_and_update_parameters (&extractable_type, id, &lerr);
994   if (real_id == NULL) {
995     /* We create an asset for that wrong ID so we have a reference that the
996      * user requested it */
997     _ensure_asset_for_wrong_id (id, extractable_type, lerr);
998     real_id = g_strdup (id);
999   }
1000   if (lerr)
1001     g_error_free (lerr);
1002 
1003   asset = ges_asset_cache_lookup (extractable_type, real_id);
1004   if (asset) {
1005     while (TRUE) {
1006       switch (asset->priv->state) {
1007         case ASSET_INITIALIZED:
1008           gst_object_ref (asset);
1009           goto done;
1010         case ASSET_INITIALIZING:
1011           asset = NULL;
1012           goto done;
1013         case ASSET_PROXIED:
1014           if (asset->priv->proxies == NULL) {
1015             GST_ERROR ("Proxied against an asset we do not"
1016                 " have in cache, something massively screwed");
1017 
1018             goto done;
1019           }
1020 
1021           asset = asset->priv->proxies->data;
1022           while (ges_asset_get_proxy (asset))
1023             asset = ges_asset_get_proxy (asset);
1024 
1025           break;
1026         case ASSET_NEEDS_RELOAD:
1027           GST_DEBUG_OBJECT (asset, "Asset in cache and needs reload");
1028           start_loading (asset);
1029 
1030           goto done;
1031         case ASSET_INITIALIZED_WITH_ERROR:
1032           GST_WARNING_OBJECT (asset, "Initialized with error, not returning");
1033           if (asset->priv->error && error)
1034             *error = g_error_copy (asset->priv->error);
1035           asset = NULL;
1036           goto done;
1037         default:
1038           GST_WARNING ("Case %i not handle, returning", asset->priv->state);
1039           goto done;
1040       }
1041     }
1042   } else {
1043     GObjectClass *klass;
1044     GInitableIface *iface;
1045     GType asset_type = ges_extractable_type_get_asset_type (extractable_type);
1046 
1047     klass = g_type_class_ref (asset_type);
1048     iface = g_type_interface_peek (klass, G_TYPE_INITABLE);
1049 
1050     if (iface->init) {
1051       asset = g_initable_new (asset_type,
1052           NULL, NULL, "id", real_id, "extractable-type",
1053           extractable_type, NULL);
1054     } else {
1055       GST_WARNING ("Tried to create an Asset for type %s but no ->init method",
1056           g_type_name (extractable_type));
1057     }
1058     g_type_class_unref (klass);
1059   }
1060 
1061 done:
1062   if (real_id)
1063     g_free (real_id);
1064 
1065   GST_DEBUG ("New asset created synchronously: %p", asset);
1066   return asset;
1067 }
1068 
1069 /**
1070  * ges_asset_request_async:
1071  * @extractable_type: The #GType of the object that can be extracted from the
1072  *    new asset. The class must implement the #GESExtractable interface.
1073  * @id: The Identifier of the asset we want to create. This identifier depends of the extractable,
1074  * type you want. By default it is the name of the class itself (or %NULL), but for example for a
1075  * GESEffect, it will be the pipeline description, for a GESUriClip it
1076  * will be the name of the file, etc... You should refer to the documentation of the #GESExtractable
1077  * type you want to create a #GESAsset for.
1078  * @cancellable: (allow-none): optional %GCancellable object, %NULL to ignore.
1079  * @callback: a #GAsyncReadyCallback to call when the initialization is finished,
1080  * Note that the @source of the callback will be the #GESAsset, but you need to
1081  * make sure that the asset is properly loaded using the #ges_asset_request_finish
1082  * method. This asset can not be used as is.
1083  * @user_data: The user data to pass when @callback is called
1084  *
1085  * The @callback will be called from a running #GMainLoop which is iterating a #GMainContext.
1086  * Note that, users should ensure the #GMainContext, since this method will notify
1087  * @callback from the thread which was associated with a thread default
1088  * #GMainContext at calling ges_init().
1089  * For example, if a user wants non-default #GMainContext to be associated
1090  * with @callback, ges_init() must be called after g_main_context_push_thread_default ()
1091  * with custom #GMainContext.
1092  *
1093  * Request a new #GESAsset asyncronously, @callback will be called when the materail is
1094  * ready to be used or if an error occured.
1095  *
1096  * Example of request of a GESAsset async:
1097  * |[
1098  * // The request callback
1099  * static void
1100  * asset_loaded_cb (GESAsset * source, GAsyncResult * res, gpointer user_data)
1101  * {
1102  *   GESAsset *asset;
1103  *   GError *error = NULL;
1104  *
1105  *   asset = ges_asset_request_finish (res, &error);
1106  *   if (asset) {
1107  *    g_print ("The file: %s is usable as a FileSource",
1108  *        ges_asset_get_id (asset));
1109  *   } else {
1110  *    g_print ("The file: %s is *not* usable as a FileSource because: %s",
1111  *        ges_asset_get_id (source), error->message);
1112  *   }
1113  *
1114  *   gst_object_unref (mfs);
1115  * }
1116  *
1117  * // The request:
1118  * ges_asset_request_async (GES_TYPE_URI_CLIP, some_uri, NULL,
1119  *    (GAsyncReadyCallback) asset_loaded_cb, user_data);
1120  * ]|
1121  */
1122 void
ges_asset_request_async(GType extractable_type,const gchar * id,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)1123 ges_asset_request_async (GType extractable_type,
1124     const gchar * id, GCancellable * cancellable, GAsyncReadyCallback callback,
1125     gpointer user_data)
1126 {
1127   gchar *real_id;
1128   GESAsset *asset;
1129   GError *error = NULL;
1130   GTask *task = NULL;
1131 
1132   g_return_if_fail (g_type_is_a (extractable_type, G_TYPE_OBJECT));
1133   g_return_if_fail (g_type_is_a (extractable_type, GES_TYPE_EXTRACTABLE));
1134   g_return_if_fail (callback);
1135 
1136   GST_DEBUG ("Creating asset with extractable type %s and ID=%s",
1137       g_type_name (extractable_type), id);
1138 
1139   real_id = _check_and_update_parameters (&extractable_type, id, &error);
1140   if (error) {
1141     _ensure_asset_for_wrong_id (id, extractable_type, error);
1142     real_id = g_strdup (id);
1143   }
1144 
1145   /* Check if we already have an asset for this ID */
1146   asset = ges_asset_cache_lookup (extractable_type, real_id);
1147   if (asset) {
1148     task = g_task_new (asset, NULL, callback, user_data);
1149 
1150     /* In the case of proxied asset, we will loop until we find the
1151      * last asset of the chain of proxied asset */
1152     while (TRUE) {
1153       switch (asset->priv->state) {
1154         case ASSET_INITIALIZED:
1155           GST_DEBUG_OBJECT (asset, "Asset in cache and initialized, "
1156               "using it");
1157 
1158           /* Takes its own references to @asset */
1159           g_task_return_boolean (task, TRUE);
1160 
1161           goto done;
1162         case ASSET_INITIALIZING:
1163           GST_DEBUG_OBJECT (asset, "Asset in cache and but not "
1164               "initialized, setting a new callback");
1165           ges_asset_cache_append_task (extractable_type, real_id, task);
1166           task = NULL;
1167 
1168           goto done;
1169         case ASSET_PROXIED:{
1170           GESAsset *target = ges_asset_get_proxy (asset);
1171 
1172           if (target == NULL) {
1173             GST_ERROR ("Asset %s proxied against an asset (%s) we do not"
1174                 " have in cache, something massively screwed",
1175                 asset->priv->id, asset->priv->proxied_asset_id);
1176 
1177             goto done;
1178           }
1179           asset = target;
1180           break;
1181         }
1182         case ASSET_NEEDS_RELOAD:
1183           GST_DEBUG_OBJECT (asset, "Asset in cache and needs reload");
1184           ges_asset_cache_append_task (extractable_type, real_id, task);
1185           task = NULL;
1186           GES_ASSET_GET_CLASS (asset)->start_loading (asset, &error);
1187 
1188           goto done;
1189         case ASSET_INITIALIZED_WITH_ERROR:
1190           g_task_return_error (task,
1191               error ? g_error_copy (error) : g_error_copy (asset->priv->error));
1192 
1193           g_clear_error (&error);
1194 
1195           goto done;
1196         default:
1197           GST_WARNING ("Case %i not handle, returning", asset->priv->state);
1198           return;
1199       }
1200     }
1201   }
1202 
1203   g_async_initable_new_async (ges_extractable_type_get_asset_type
1204       (extractable_type), G_PRIORITY_DEFAULT, cancellable, callback, user_data,
1205       "id", real_id, "extractable-type", extractable_type, NULL);
1206 done:
1207   if (task)
1208     gst_object_unref (task);
1209   if (real_id)
1210     g_free (real_id);
1211 }
1212 
1213 /**
1214  * ges_asset_needs_reload
1215  * @extractable_type: The #GType of the object that can be extracted from the
1216  *  asset to be reloaded.
1217  * @id: The identifier of the asset to mark as needing reload
1218  *
1219  * Sets an asset from the internal cache as needing reload. An asset needs reload
1220  * in the case where, for example, we were missing a GstPlugin to use it and that
1221  * plugin has been installed, or, that particular asset content as changed
1222  * meanwhile (in the case of the usage of proxies).
1223  *
1224  * Once an asset has been set as "needs reload", requesting that asset again
1225  * will lead to it being re discovered, and reloaded as if it was not in the
1226  * cache before.
1227  *
1228  * Returns: %TRUE if the asset was in the cache and could be set as needing reload,
1229  * %FALSE otherwise.
1230  */
1231 gboolean
ges_asset_needs_reload(GType extractable_type,const gchar * id)1232 ges_asset_needs_reload (GType extractable_type, const gchar * id)
1233 {
1234   gchar *real_id;
1235   GESAsset *asset;
1236   GError *error = NULL;
1237 
1238   real_id = _check_and_update_parameters (&extractable_type, id, &error);
1239   if (error) {
1240     _ensure_asset_for_wrong_id (id, extractable_type, error);
1241     real_id = g_strdup (id);
1242   }
1243 
1244   asset = ges_asset_cache_lookup (extractable_type, real_id);
1245 
1246   if (real_id) {
1247     g_free (real_id);
1248   }
1249 
1250   if (asset) {
1251     GST_DEBUG_OBJECT (asset,
1252         "Asset with id %s switch state to ASSET_NEEDS_RELOAD",
1253         ges_asset_get_id (asset));
1254     asset->priv->state = ASSET_NEEDS_RELOAD;
1255     g_clear_error (&asset->priv->error);
1256     return TRUE;
1257   }
1258 
1259   GST_DEBUG ("Asset with id %s not found in cache", id);
1260   return FALSE;
1261 }
1262 
1263 /**
1264  * ges_asset_get_id:
1265  * @self: The #GESAsset to get ID from
1266  *
1267  * Gets the ID of a #GESAsset
1268  *
1269  * Returns: The ID of @self
1270  */
1271 const gchar *
ges_asset_get_id(GESAsset * self)1272 ges_asset_get_id (GESAsset * self)
1273 {
1274   g_return_val_if_fail (GES_IS_ASSET (self), NULL);
1275 
1276   return self->priv->id;
1277 }
1278 
1279 /**
1280  * ges_asset_extract:
1281  * @self: The #GESAsset to get extract an object from
1282  * @error: (allow-none): An error to be set in case something wrong happens or %NULL
1283  *
1284  * Extracts a new #GObject from @asset. The type of the object is
1285  * defined by the extractable-type of @asset, you can check what
1286  * type will be extracted from @asset using
1287  * #ges_asset_get_extractable_type
1288  *
1289  * Returns: (transfer floating) (allow-none): A newly created #GESExtractable
1290  */
1291 GESExtractable *
ges_asset_extract(GESAsset * self,GError ** error)1292 ges_asset_extract (GESAsset * self, GError ** error)
1293 {
1294   GESExtractable *extractable;
1295 
1296   g_return_val_if_fail (GES_IS_ASSET (self), NULL);
1297   g_return_val_if_fail (GES_ASSET_GET_CLASS (self)->extract, NULL);
1298 
1299   GST_DEBUG_OBJECT (self, "Extracting asset of type %s",
1300       g_type_name (self->priv->extractable_type));
1301   extractable = GES_ASSET_GET_CLASS (self)->extract (self, error);
1302 
1303   if (extractable == NULL)
1304     return NULL;
1305 
1306   if (ges_extractable_get_asset (extractable) == NULL)
1307     ges_extractable_set_asset (extractable, self);
1308 
1309   return extractable;
1310 }
1311 
1312 /**
1313  * ges_asset_request_finish:
1314  * @res: The #GAsyncResult from which to get the newly created #GESAsset
1315  * @error: (out) (allow-none) (transfer full): An error to be set in case
1316  * something wrong happens or %NULL
1317  *
1318  * Finalize the request of an async #GESAsset
1319  *
1320  * Returns: (transfer full)(allow-none): The #GESAsset previously requested
1321  */
1322 GESAsset *
ges_asset_request_finish(GAsyncResult * res,GError ** error)1323 ges_asset_request_finish (GAsyncResult * res, GError ** error)
1324 {
1325   GObject *object;
1326   GObject *source_object;
1327 
1328   g_return_val_if_fail (G_IS_ASYNC_RESULT (res), NULL);
1329 
1330   source_object = g_async_result_get_source_object (res);
1331   g_assert (source_object != NULL);
1332 
1333   object = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object),
1334       res, error);
1335 
1336   gst_object_unref (source_object);
1337 
1338   return GES_ASSET (object);
1339 }
1340 
1341 /**
1342  * ges_list_assets:
1343  * @filter: Type of assets to list, #GES_TYPE_EXTRACTABLE  will list
1344  * all assets
1345  *
1346  * List all @asset filtering per filter as defined by @filter.
1347  * It copies the asset and thus will not be updated in time.
1348  *
1349  * Returns: (transfer container) (element-type GESAsset): The list of
1350  * #GESAsset the object contains
1351  */
1352 GList *
ges_list_assets(GType filter)1353 ges_list_assets (GType filter)
1354 {
1355   GList *ret = NULL;
1356   GESAsset *asset;
1357   GHashTableIter iter, types_iter;
1358   gpointer key, value, typename, assets;
1359 
1360   g_return_val_if_fail (g_type_is_a (filter, GES_TYPE_EXTRACTABLE), NULL);
1361 
1362   LOCK_CACHE;
1363   g_hash_table_iter_init (&types_iter, type_entries_table);
1364   while (g_hash_table_iter_next (&types_iter, &typename, &assets)) {
1365     if (g_type_is_a (filter, g_type_from_name ((gchar *) typename)) == FALSE)
1366       continue;
1367 
1368     g_hash_table_iter_init (&iter, (GHashTable *) assets);
1369     while (g_hash_table_iter_next (&iter, &key, &value)) {
1370       asset = ((GESAssetCacheEntry *) value)->asset;
1371 
1372       if (g_type_is_a (asset->priv->extractable_type, filter))
1373         ret = g_list_append (ret, asset);
1374     }
1375   }
1376   UNLOCK_CACHE;
1377 
1378   return ret;
1379 }
1380 
1381 /**
1382  * ges_asset_get_error:
1383  * @self: The asset to retrieve the error from
1384  *
1385  * Returns: (transfer none) (nullable): The #GError of the asset or %NULL if
1386  * the asset was loaded without issue
1387  *
1388  * Since: 1.8
1389  */
1390 GError *
ges_asset_get_error(GESAsset * self)1391 ges_asset_get_error (GESAsset * self)
1392 {
1393   g_return_val_if_fail (GES_IS_ASSET (self), NULL);
1394 
1395   return self->priv->error;
1396 }
1397