project: fix string leak
[platform/upstream/gstreamer.git] / ges / ges-project.c
1 /* GStreamer Editing Services
2  *
3  * Copyright (C) <2012> Thibault Saunier <thibault.saunier@collabora.com>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA.
19  */
20 /**
21  * SECTION: gesproject
22  * @short_description: A GESAsset that is used to manage projects
23  *
24  * The #GESProject is used to control a set of #GESAsset and is a
25  * #GESAsset with #GES_TYPE_TIMELINE as @extractable_type itself. That
26  * means that you can extract #GESTimeline from a project as followed:
27  *
28  * |[
29  *  GESProject *project;
30  *  GESTimeline *timeline;
31  *
32  *  project = ges_project_new ("file:///path/to/a/valid/project/uri");
33  *
34  *  // Here you can connect to the various signal to get more infos about
35  *  // what is happening and recover from errors if possible
36  *  ...
37  *
38  *  timeline = ges_asset_extract (GES_ASSET (project));
39  * ]|
40  *
41  * The #GESProject class offers a higher level API to handle #GESAsset-s.
42  * It lets you request new asset, and it informs you about new assets through
43  * a set of signals. Also it handles problem such as missing files/missing
44  * #GstElement and lets you try to recover from those.
45  */
46 #include "ges.h"
47 #include "ges-internal.h"
48
49 /* TODO We should rely on both extractable_type and @id to identify
50  * a Asset, not only @id
51  */
52 G_DEFINE_TYPE (GESProject, ges_project, GES_TYPE_ASSET);
53
54 struct _GESProjectPrivate
55 {
56   GHashTable *assets;
57   /* Set of asset ID being loaded */
58   GHashTable *loading_assets;
59   GHashTable *loaded_with_error;
60   GESAsset *formatter_asset;
61
62   GList *formatters;
63
64   gchar *uri;
65
66   GList *encoding_profiles;
67 };
68
69 typedef struct EmitLoadedInIdle
70 {
71   GESProject *project;
72   GESTimeline *timeline;
73 } EmitLoadedInIdle;
74
75 enum
76 {
77   LOADED_SIGNAL,
78   ERROR_LOADING_ASSET,
79   ASSET_ADDED_SIGNAL,
80   ASSET_REMOVED_SIGNAL,
81   MISSING_URI_SIGNAL,
82   LAST_SIGNAL
83 };
84
85 static guint _signals[LAST_SIGNAL] = { 0 };
86
87 static guint nb_projects = 0;
88
89 enum
90 {
91   PROP_0,
92   PROP_URI,
93   PROP_LAST,
94 };
95
96 static GParamSpec *_properties[LAST_SIGNAL] = { 0 };
97
98 static gboolean
99 _emit_loaded_in_idle (EmitLoadedInIdle * data)
100 {
101   ges_timeline_commit (data->timeline);
102   g_signal_emit (data->project, _signals[LOADED_SIGNAL], 0, data->timeline);
103
104   gst_object_unref (data->project);
105   gst_object_unref (data->timeline);
106   g_slice_free (EmitLoadedInIdle, data);
107
108   return FALSE;
109 }
110
111 static void
112 ges_project_add_formatter (GESProject * project, GESFormatter * formatter)
113 {
114   GESProjectPrivate *priv = GES_PROJECT (project)->priv;
115
116   ges_formatter_set_project (formatter, project);
117   priv->formatters = g_list_append (priv->formatters, formatter);
118
119   gst_object_ref_sink (formatter);
120 }
121
122 static void
123 ges_project_remove_formatter (GESProject * project, GESFormatter * formatter)
124 {
125   GList *tmp;
126   GESProjectPrivate *priv = GES_PROJECT (project)->priv;
127
128   for (tmp = priv->formatters; tmp; tmp = tmp->next) {
129     if (tmp->data == formatter) {
130       gst_object_unref (formatter);
131       priv->formatters = g_list_delete_link (priv->formatters, tmp);
132
133       return;
134     }
135   }
136 }
137
138 static void
139 ges_project_set_uri (GESProject * project, const gchar * uri)
140 {
141   GESProjectPrivate *priv;
142
143   g_return_if_fail (GES_IS_PROJECT (project));
144
145   priv = project->priv;
146   if (priv->uri) {
147     GST_WARNING_OBJECT (project, "Trying to rest URI, this is prohibited");
148
149     return;
150   }
151
152   if (uri == NULL || !gst_uri_is_valid (uri)) {
153     GST_LOG_OBJECT (project, "Invalid URI: %s", uri);
154     return;
155   }
156
157   priv->uri = g_strdup (uri);
158
159   /* We use that URI as ID */
160   ges_asset_set_id (GES_ASSET (project), uri);
161
162   return;
163 }
164
165 static gboolean
166 _load_project (GESProject * project, GESTimeline * timeline, GError ** error)
167 {
168   GError *lerr = NULL;
169   GESProjectPrivate *priv;
170   GESFormatter *formatter;
171
172   priv = GES_PROJECT (project)->priv;
173
174   if (priv->uri == NULL) {
175     EmitLoadedInIdle *data = g_slice_new (EmitLoadedInIdle);
176
177     GST_LOG_OBJECT (project, "%s, Loading an empty timeline %s"
178         " as no URI set yet", GST_OBJECT_NAME (timeline),
179         ges_asset_get_id (GES_ASSET (project)));
180
181     data->timeline = gst_object_ref (timeline);
182     data->project = gst_object_ref (project);
183
184     /* Make sure the signal is emitted after the functions ends */
185     g_idle_add ((GSourceFunc) _emit_loaded_in_idle, data);
186     return TRUE;
187   }
188
189   if (priv->formatter_asset == NULL)
190     priv->formatter_asset = _find_formatter_asset_for_uri (priv->uri);
191
192   if (priv->formatter_asset == NULL)
193     goto failed;
194
195   formatter = GES_FORMATTER (ges_asset_extract (priv->formatter_asset, &lerr));
196   if (lerr) {
197     GST_WARNING_OBJECT (project, "Could not create the formatter: %s",
198         (*error)->message);
199
200     goto failed;
201   }
202
203   ges_project_add_formatter (GES_PROJECT (project), formatter);
204   ges_formatter_load_from_uri (formatter, timeline, priv->uri, &lerr);
205   if (lerr) {
206     GST_WARNING_OBJECT (project, "Could not load the timeline,"
207         " returning: %s", lerr->message);
208     goto failed;
209   }
210
211   return TRUE;
212
213 failed:
214   if (lerr)
215     g_propagate_error (error, lerr);
216   return FALSE;
217 }
218
219 static gboolean
220 _uri_missing_accumulator (GSignalInvocationHint * ihint, GValue * return_accu,
221     const GValue * handler_return, gpointer data)
222 {
223   const gchar *ret = g_value_get_string (handler_return);
224
225   if (ret) {
226     if (!gst_uri_is_valid (ret)) {
227       GST_INFO ("The uri %s was not valid, can not work with it!", ret);
228       return TRUE;
229     }
230
231     g_value_set_string (return_accu, ret);
232     return FALSE;
233   }
234
235   return TRUE;
236 }
237
238 /* GESAsset vmethod implementation */
239 static GESExtractable *
240 ges_project_extract (GESAsset * project, GError ** error)
241 {
242   GESTimeline *timeline = g_object_new (GES_TYPE_TIMELINE, NULL);
243
244   ges_extractable_set_asset (GES_EXTRACTABLE (timeline), GES_ASSET (project));
245   if (_load_project (GES_PROJECT (project), timeline, error))
246     return GES_EXTRACTABLE (timeline);
247
248   gst_object_unref (timeline);
249   return NULL;
250 }
251
252 /* GObject vmethod implementation */
253 static void
254 _dispose (GObject * object)
255 {
256   GList *tmp;
257   GESProjectPrivate *priv = GES_PROJECT (object)->priv;
258
259   if (priv->assets)
260     g_hash_table_unref (priv->assets);
261   if (priv->loading_assets)
262     g_hash_table_unref (priv->loading_assets);
263   if (priv->loaded_with_error)
264     g_hash_table_unref (priv->loaded_with_error);
265   if (priv->formatter_asset)
266     gst_object_unref (priv->formatter_asset);
267
268   for (tmp = priv->formatters; tmp; tmp = tmp->next)
269     ges_project_remove_formatter (GES_PROJECT (object), tmp->data);;
270
271   G_OBJECT_CLASS (ges_project_parent_class)->dispose (object);
272 }
273
274 static void
275 _finalize (GObject * object)
276 {
277   GESProjectPrivate *priv = GES_PROJECT (object)->priv;
278
279   if (priv->uri)
280     g_free (priv->uri);
281
282   G_OBJECT_CLASS (ges_project_parent_class)->finalize (object);
283 }
284
285 static void
286 _get_property (GESProject * project, guint property_id,
287     GValue * value, GParamSpec * pspec)
288 {
289   GESProjectPrivate *priv = project->priv;
290
291   switch (property_id) {
292     case PROP_URI:
293       g_value_set_string (value, priv->uri);
294       break;
295     default:
296       G_OBJECT_WARN_INVALID_PROPERTY_ID (project, property_id, pspec);
297   }
298 }
299
300 static void
301 _set_property (GESProject * project, guint property_id,
302     const GValue * value, GParamSpec * pspec)
303 {
304   switch (property_id) {
305     case PROP_URI:
306       project->priv->uri = g_value_dup_string (value);
307       break;
308     default:
309       G_OBJECT_WARN_INVALID_PROPERTY_ID (project, property_id, pspec);
310   }
311 }
312
313 static void
314 ges_project_class_init (GESProjectClass * klass)
315 {
316   GObjectClass *object_class = G_OBJECT_CLASS (klass);
317
318   g_type_class_add_private (klass, sizeof (GESProjectPrivate));
319
320   klass->asset_added = NULL;
321   klass->missing_uri = NULL;
322   klass->loading_error = NULL;
323   klass->asset_removed = NULL;
324   object_class->get_property = (GObjectGetPropertyFunc) _get_property;
325   object_class->set_property = (GObjectSetPropertyFunc) _set_property;
326
327   /**
328    * GESProject::uri:
329    *
330    * The location of the project to use.
331    */
332   _properties[PROP_URI] = g_param_spec_string ("uri", "URI",
333       "uri of the project", NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
334
335   g_object_class_install_properties (object_class, PROP_LAST, _properties);
336
337   /**
338    * GESProject::asset-added:
339    * @formatter: the #GESProject
340    * @asset: The #GESAsset that has been added to @project
341    */
342   _signals[ASSET_ADDED_SIGNAL] =
343       g_signal_new ("asset-added", G_TYPE_FROM_CLASS (klass),
344       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GESProjectClass, asset_added),
345       NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1, GES_TYPE_ASSET);
346
347   /**
348    * GESProject::asset-removed:
349    * @formatter: the #GESProject
350    * @asset: The #GESAsset that has been removed from @project
351    */
352   _signals[ASSET_REMOVED_SIGNAL] =
353       g_signal_new ("asset-removed", G_TYPE_FROM_CLASS (klass),
354       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GESProjectClass, asset_removed),
355       NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1, GES_TYPE_ASSET);
356
357   /**
358    * GESProject::loaded:
359    * @project: the #GESProject that is done loading a project.
360    * @timeline: The #GESTimeline that complete loading
361    */
362   _signals[LOADED_SIGNAL] =
363       g_signal_new ("loaded", G_TYPE_FROM_CLASS (klass),
364       G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GESProjectClass, loaded),
365       NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE,
366       1, GES_TYPE_TIMELINE);
367
368   /**
369    * GESProject::missing-uri:
370    * @project: the #GESProject reporting that a file has moved
371    * @error: The error that happened
372    * @wrong_asset: The asset with the wrong ID, you should us it and its content
373    * only to find out what the new location is.
374    *
375    * |[
376    * static gchar
377    * source_moved_cb (GESProject *project, GError *error, GESAsset *asset_with_error)
378    * {
379    *   return g_strdup ("file:///the/new/uri.ogg");
380    * }
381    *
382    * static int
383    * main (int argc, gchar ** argv)
384    * {
385    *   GESTimeline *timeline;
386    *   GESProject *project = ges_project_new ("file:///some/uri.xges");
387    *
388    *   g_signal_connect (project, "missing-uri", source_moved_cb, NULL);
389    *   timeline = ges_asset_extract (GES_ASSET (project));
390    * }
391    * ]|
392    *
393    * Returns: (transfer full) (allow-none): The new URI of @wrong_asset
394    */
395   _signals[MISSING_URI_SIGNAL] =
396       g_signal_new ("missing-uri", G_TYPE_FROM_CLASS (klass),
397       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GESProjectClass, missing_uri),
398       _uri_missing_accumulator, NULL, g_cclosure_marshal_generic,
399       G_TYPE_STRING, 2, G_TYPE_ERROR, GES_TYPE_ASSET);
400
401   /**
402    * GESProject::error-loading-asset:
403    * @project: the #GESProject on which a problem happend when creted a #GESAsset
404    * @error: The #GError defining the error that accured, might be %NULL
405    * @id: The @id of the asset that failed loading
406    * @extractable_type: The @extractable_type of the asset that
407    * failed loading
408    *
409    * Informs you that a #GESAsset could not be created. In case of
410    * missing GStreamer plugins, the error will be set to #GST_CORE_ERROR
411    * #GST_CORE_ERROR_MISSING_PLUGIN
412    */
413   _signals[ERROR_LOADING_ASSET] =
414       g_signal_new ("error-loading-asset", G_TYPE_FROM_CLASS (klass),
415       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GESProjectClass, loading_error),
416       NULL, NULL, g_cclosure_marshal_generic,
417       G_TYPE_NONE, 3, G_TYPE_ERROR, G_TYPE_STRING, G_TYPE_GTYPE);
418
419   object_class->dispose = _dispose;
420   object_class->finalize = _finalize;
421
422   GES_ASSET_CLASS (klass)->extract = ges_project_extract;
423 }
424
425 static void
426 ges_project_init (GESProject * project)
427 {
428   GESProjectPrivate *priv = project->priv =
429       G_TYPE_INSTANCE_GET_PRIVATE (project,
430       GES_TYPE_PROJECT, GESProjectPrivate);
431
432   priv->uri = NULL;
433   priv->formatters = NULL;
434   priv->formatter_asset = NULL;
435   priv->encoding_profiles = NULL;
436   priv->assets = g_hash_table_new_full (g_str_hash, g_str_equal,
437       g_free, gst_object_unref);
438   priv->loading_assets = g_hash_table_new_full (g_str_hash, g_str_equal,
439       g_free, gst_object_unref);
440   priv->loaded_with_error = g_hash_table_new_full (g_str_hash, g_str_equal,
441       g_free, NULL);
442 }
443
444 static void
445 _send_error_loading_asset (GESProject * project, GESAsset * asset,
446     GError * error)
447 {
448   const gchar *id = ges_asset_get_id (asset);
449
450   GST_DEBUG_OBJECT (project, "Sending error loading asset for %s", id);
451   g_hash_table_remove (project->priv->loading_assets, id);
452   g_hash_table_add (project->priv->loaded_with_error, g_strdup (id));
453   g_signal_emit (project, _signals[ERROR_LOADING_ASSET], 0, error, id,
454       ges_asset_get_extractable_type (asset));
455 }
456
457 gchar *
458 ges_project_try_updating_id (GESProject * project, GESAsset * asset,
459     GError * error)
460 {
461   gchar *new_id = NULL;
462   const gchar *id;
463
464   g_return_val_if_fail (GES_IS_PROJECT (project), NULL);
465   g_return_val_if_fail (GES_IS_ASSET (asset), NULL);
466   g_return_val_if_fail (error, NULL);
467
468   id = ges_asset_get_id (asset);
469   GST_DEBUG_OBJECT (project, "Try to proxy %s", id);
470   if (ges_asset_request_id_update (asset, &new_id, error) == FALSE) {
471     GST_DEBUG_OBJECT (project, "Type: %s can not be proxied for id: %s "
472         "and error: %s", g_type_name (G_OBJECT_TYPE (asset)), id,
473         error->message);
474     _send_error_loading_asset (project, asset, error);
475
476     return NULL;
477   }
478
479   if (new_id == NULL) {
480     GST_DEBUG_OBJECT (project, "Sending 'missing-uri' signal for %s", id);
481     g_signal_emit (project, _signals[MISSING_URI_SIGNAL], 0, error, asset,
482         &new_id);
483   }
484
485   if (new_id) {
486     GST_DEBUG_OBJECT (project, "new id found: %s", new_id);
487     if (!ges_asset_set_proxy (asset, new_id)) {
488       g_free (new_id);
489       new_id = NULL;
490     }
491   } else {
492     GST_DEBUG_OBJECT (project, "No new id found for %s", id);
493   }
494
495   g_hash_table_remove (project->priv->loading_assets, id);
496
497   if (new_id == NULL)
498     _send_error_loading_asset (project, asset, error);
499
500
501   return new_id;
502 }
503
504 static void
505 new_asset_cb (GESAsset * source, GAsyncResult * res, GESProject * project)
506 {
507   GError *error = NULL;
508   gchar *possible_id = NULL;
509   GESAsset *asset = ges_asset_request_finish (res, &error);
510
511   if (error) {
512     possible_id = ges_project_try_updating_id (project, source, error);
513
514     if (possible_id == NULL)
515       return;
516
517     ges_project_create_asset (project, possible_id,
518         ges_asset_get_extractable_type (source));
519
520     g_free (possible_id);
521     g_error_free (error);
522     return;
523   }
524
525   ges_project_add_asset (project, asset);
526   if (asset)
527     gst_object_unref (asset);
528 }
529
530 /**
531  * ges_project_set_loaded:
532  * @project: The #GESProject from which to emit the "project-loaded" signal
533  *
534  * Emits the "loaded" signal. This method should be called by sublasses when
535  * the project is fully loaded.
536  *
537  * Returns: %TRUE if the signale could be emitted %FALSE otherwize
538  */
539 gboolean
540 ges_project_set_loaded (GESProject * project, GESFormatter * formatter)
541 {
542   GST_INFO_OBJECT (project, "Emit project loaded");
543   ges_timeline_commit (formatter->timeline);
544   g_signal_emit (project, _signals[LOADED_SIGNAL], 0, formatter->timeline);
545
546   /* We are now done with that formatter */
547   ges_project_remove_formatter (project, formatter);
548   return TRUE;
549 }
550
551 void
552 ges_project_add_loading_asset (GESProject * project, GType extractable_type,
553     const gchar * id)
554 {
555   GESAsset *asset;
556
557   if ((asset = ges_asset_cache_lookup (extractable_type, id)))
558     g_hash_table_insert (project->priv->loading_assets, g_strdup (id),
559         gst_object_ref (asset));
560 }
561
562 /**************************************
563  *                                    *
564  *         API Implementation         *
565  *                                    *
566  **************************************/
567
568 /**
569  * ges_project_create_asset:
570  * @project: A #GESProject
571  * @id: (allow-none): The id of the asset to create and add to @project
572  * @extractable_type: The #GType of the asset to create
573  *
574  * Create and add a #GESAsset to @project. You should connect to the
575  * "asset-added" signal to get the asset when it finally gets added to
576  * @project
577  *
578  * Returns: %TRUE if the asset started to be added %FALSE it was already
579  * in the project
580  */
581 gboolean
582 ges_project_create_asset (GESProject * project, const gchar * id,
583     GType extractable_type)
584 {
585   g_return_val_if_fail (GES_IS_PROJECT (project), FALSE);
586   g_return_val_if_fail (g_type_is_a (extractable_type, GES_TYPE_EXTRACTABLE),
587       FALSE);
588
589   if (id == NULL)
590     id = g_type_name (extractable_type);
591
592   if (g_hash_table_lookup (project->priv->assets, id) ||
593       g_hash_table_lookup (project->priv->loading_assets, id) ||
594       g_hash_table_lookup (project->priv->loaded_with_error, id))
595     return FALSE;
596
597   /* TODO Add a GCancellable somewhere in our API */
598   ges_asset_request_async (extractable_type, id, NULL,
599       (GAsyncReadyCallback) new_asset_cb, project);
600   ges_project_add_loading_asset (project, extractable_type, id);
601
602   return TRUE;
603 }
604
605 /**
606  * ges_project_add_asset:
607  * @project: A #GESProject
608  * @asset: (transfer none): A #GESAsset to add to @project
609  *
610  * Adds a #Asset to @project, the project will keep a reference on
611  * @asset.
612  *
613  * Returns: %TRUE if the asset could be added %FALSE it was already
614  * in the project
615  */
616 gboolean
617 ges_project_add_asset (GESProject * project, GESAsset * asset)
618 {
619   g_return_val_if_fail (GES_IS_PROJECT (project), FALSE);
620
621   if (g_hash_table_lookup (project->priv->assets, ges_asset_get_id (asset)))
622     return FALSE;
623
624   g_hash_table_insert (project->priv->assets,
625       g_strdup (ges_asset_get_id (asset)), gst_object_ref (asset));
626
627   g_hash_table_remove (project->priv->loading_assets, ges_asset_get_id (asset));
628   GST_DEBUG_OBJECT (project, "Asset added: %s", ges_asset_get_id (asset));
629   g_signal_emit (project, _signals[ASSET_ADDED_SIGNAL], 0, asset);
630
631   return TRUE;
632 }
633
634 /**
635  * ges_project_remove_asset:
636  * @project: A #GESProject
637  * @asset: (transfer none): A #GESAsset to remove from @project
638  *
639  * remove a @asset to from @project.
640  *
641  * Returns: %TRUE if the asset could be removed %FALSE otherwise
642  */
643 gboolean
644 ges_project_remove_asset (GESProject * project, GESAsset * asset)
645 {
646   gboolean ret;
647
648   g_return_val_if_fail (GES_IS_PROJECT (project), FALSE);
649
650   ret = g_hash_table_remove (project->priv->assets, ges_asset_get_id (asset));
651   g_signal_emit (project, _signals[ASSET_REMOVED_SIGNAL], 0, asset);
652
653   return ret;
654 }
655
656 /**
657  * ges_project_get_asset:
658  * @project: A #GESProject
659  * @id: The id of the asset to retrieve
660  * @extractable_type: The extractable_type of the asset
661  * to retrieve from @object
662  *
663  * Returns: (transfer full) (allow-none): The #GESAsset with
664  * @id or %NULL if no asset with @id as an ID
665  */
666 GESAsset *
667 ges_project_get_asset (GESProject * project, const gchar * id,
668     GType extractable_type)
669 {
670   GESAsset *asset;
671
672   g_return_val_if_fail (GES_IS_PROJECT (project), NULL);
673   g_return_val_if_fail (g_type_is_a (extractable_type, GES_TYPE_EXTRACTABLE),
674       NULL);
675
676   asset = g_hash_table_lookup (project->priv->assets, id);
677
678   if (asset)
679     return gst_object_ref (asset);
680
681   return NULL;
682 }
683
684 /**
685  * ges_project_list_assets:
686  * @project: A #GESProject
687  * @filter: Type of assets to list, #GES_TYPE_EXTRACTABLE will list
688  * all assets
689  *
690  * List all @asset contained in @project filtering per extractable_type
691  * as defined by @filter. It copies the asset and thus will not be updated
692  * in time.
693  *
694  * Returns: (transfer full) (element-type GESAsset): The list of
695  * #GESAsset the object contains
696  */
697 GList *
698 ges_project_list_assets (GESProject * project, GType filter)
699 {
700   GList *ret = NULL;
701   GHashTableIter iter;
702   gpointer key, value;
703
704   g_return_val_if_fail (GES_IS_PROJECT (project), NULL);
705
706   g_hash_table_iter_init (&iter, project->priv->assets);
707   while (g_hash_table_iter_next (&iter, &key, &value)) {
708     if (g_type_is_a (ges_asset_get_extractable_type (GES_ASSET (value)),
709             filter))
710       ret = g_list_append (ret, gst_object_ref (value));
711   }
712
713   return ret;
714 }
715
716 /**
717  * ges_project_save:
718  * @project: A #GESProject to save
719  * @timeline: The #GESTimeline to save, it must have been extracted from @project
720  * @uri: The uri where to save @project and @timeline
721  * @formatter_asset: (allow-none): The formatter asset to use or %NULL. If %NULL,
722  * will try to save in the same format as the one from which the timeline as been loaded
723  * or default to the formatter with highest rank
724  * @overwrite: %TRUE to overwrite file if it exists
725  * @error: (out) (allow-none): An error to be set in case something wrong happens or %NULL
726  *
727  * Save the timeline of @project to @uri. You should make sure that @timeline
728  * is one of the timelines that have been extracted from @project
729  * (using ges_asset_extract (@project);)
730  *
731  * Returns: %TRUE if the project could be save, %FALSE otherwize
732  */
733 gboolean
734 ges_project_save (GESProject * project, GESTimeline * timeline,
735     const gchar * uri, GESAsset * formatter_asset, gboolean overwrite,
736     GError ** error)
737 {
738   GESAsset *tl_asset;
739   gboolean ret = TRUE;
740   GESFormatter *formatter = NULL;
741
742   g_return_val_if_fail (GES_IS_PROJECT (project), FALSE);
743   g_return_val_if_fail (formatter_asset == NULL ||
744       g_type_is_a (ges_asset_get_extractable_type (formatter_asset),
745           GES_TYPE_FORMATTER), FALSE);
746   g_return_val_if_fail ((error == NULL || *error == NULL), FALSE);
747
748   tl_asset = ges_extractable_get_asset (GES_EXTRACTABLE (timeline));
749   if (tl_asset == NULL && project->priv->uri == NULL) {
750     GESAsset *asset = ges_asset_cache_lookup (GES_TYPE_PROJECT, uri);
751
752     if (asset) {
753       GST_WARNING_OBJECT (project, "Trying to save project to %s but we already"
754           "have %" GST_PTR_FORMAT " for that uri, can not save", uri, asset);
755       goto out;
756     }
757
758     GST_DEBUG_OBJECT (project, "Timeline %" GST_PTR_FORMAT " has no asset"
759         " we have no uri set, so setting ourself as asset", timeline);
760
761     ges_extractable_set_asset (GES_EXTRACTABLE (timeline), GES_ASSET (project));
762   } else if (tl_asset != GES_ASSET (project)) {
763     GST_WARNING_OBJECT (project, "Timeline %" GST_PTR_FORMAT
764         " not created by this project can not save", timeline);
765
766     ret = FALSE;
767     goto out;
768   }
769
770   if (formatter_asset == NULL)
771     formatter_asset = gst_object_ref (ges_formatter_get_default ());
772
773   formatter = GES_FORMATTER (ges_asset_extract (formatter_asset, error));
774   if (formatter == NULL) {
775     GST_WARNING_OBJECT (project, "Could not create the formatter %p %s: %s",
776         formatter_asset, ges_asset_get_id (formatter_asset),
777         (error && *error) ? (*error)->message : "Unknown Error");
778
779     ret = FALSE;
780     goto out;
781   }
782
783   ges_project_add_formatter (project, formatter);
784   ret = ges_formatter_save_to_uri (formatter, timeline, uri, overwrite, error);
785   if (ret && project->priv->uri == NULL)
786     ges_project_set_uri (project, uri);
787
788 out:
789   if (formatter_asset)
790     gst_object_unref (formatter_asset);
791   ges_project_remove_formatter (project, formatter);
792
793   return ret;
794 }
795
796 /**
797  * ges_project_new:
798  * @uri: (allow-none): The uri to be set after creating the project.
799  *
800  * Creates a new #GESProject and sets its uri to @uri if provided. Note that
801  * if @uri is not valid or %NULL, the uri of the project will then be set
802  * the first time you save the project. If you then save the project to
803  * other locations, it will never be updated again and the first valid URI is
804  * the URI it will keep refering to.
805  *
806  * Returns: A newly created #GESProject
807  */
808 GESProject *
809 ges_project_new (const gchar * uri)
810 {
811   gchar *id = (gchar *) uri;
812   GESProject *project;
813
814   if (uri == NULL)
815     id = g_strdup_printf ("project-%i", nb_projects++);
816
817   project = GES_PROJECT (ges_asset_request (GES_TYPE_TIMELINE, id, NULL));
818
819   if (project && uri)
820     ges_project_set_uri (project, uri);
821
822   if (uri == NULL)
823     g_free (id);
824
825   return project;
826 }
827
828 /**
829  * ges_project_load:
830  * @project: A #GESProject that has an @uri set already
831  * @timeline: A blank timeline to load @project into
832  * @error: (out) (allow-none): An error to be set in case something wrong happens or %NULL
833  *
834  * Loads @project into @timeline
835  *
836  * Returns: %TRUE if the project could be loaded %FALSE otherwize.
837  */
838 gboolean
839 ges_project_load (GESProject * project, GESTimeline * timeline, GError ** error)
840 {
841   g_return_val_if_fail (GES_IS_TIMELINE (timeline), FALSE);
842   g_return_val_if_fail (GES_IS_PROJECT (project), FALSE);
843   g_return_val_if_fail (ges_project_get_uri (project), FALSE);
844   g_return_val_if_fail (
845       (ges_extractable_get_asset (GES_EXTRACTABLE (timeline)) == NULL), FALSE);
846
847   if (!_load_project (project, timeline, error))
848     return FALSE;
849
850   ges_extractable_set_asset (GES_EXTRACTABLE (timeline), GES_ASSET (project));
851
852   return TRUE;
853 }
854
855 /**
856  * ges_project_get_uri:
857  * @project: A #GESProject
858  *
859  * Retrieve the uri that is currently set on @project
860  *
861  * Returns: The uri that is set on @project
862  */
863 gchar *
864 ges_project_get_uri (GESProject * project)
865 {
866   GESProjectPrivate *priv;
867
868   g_return_val_if_fail (GES_IS_PROJECT (project), FALSE);
869
870   priv = project->priv;
871   if (priv->uri)
872     return g_strdup (priv->uri);
873   return NULL;
874 }
875
876 /**
877  * ges_project_add_encoding_profile:
878  * @project: A #GESProject
879  * @profile: A #GstEncodingProfile to add to the project. If a profile with
880  * the same name already exists, it will be replaced
881  *
882  * Adds @profile to the project. It lets you save in what format
883  * the project has been renders and keep a reference to those formats.
884  * Also, those formats will be saves to the project file when possible.
885  *
886  * Returns: %TRUE if @profile could be added, %FALSE otherwize
887  */
888 gboolean
889 ges_project_add_encoding_profile (GESProject * project,
890     GstEncodingProfile * profile)
891 {
892   GList *tmp;
893   GESProjectPrivate *priv;
894
895   g_return_val_if_fail (GES_IS_PROJECT (project), FALSE);
896   g_return_val_if_fail (GST_IS_ENCODING_PROFILE (profile), FALSE);
897
898   priv = project->priv;
899   for (tmp = priv->encoding_profiles; tmp; tmp = tmp->next) {
900     GstEncodingProfile *tmpprofile = GST_ENCODING_PROFILE (tmp->data);
901
902     if (g_strcmp0 (gst_encoding_profile_get_name (tmpprofile),
903             gst_encoding_profile_get_name (profile)) == 0) {
904       GST_INFO_OBJECT (project, "Already have profile: %s, replacing it",
905           gst_encoding_profile_get_name (profile));
906
907       gst_object_unref (tmp->data);
908       tmp->data = gst_object_ref (profile);
909       return TRUE;
910     }
911   }
912
913   priv->encoding_profiles = g_list_prepend (priv->encoding_profiles,
914       gst_object_ref (profile));
915
916   return TRUE;
917 }
918
919 /**
920  * ges_project_list_encoding_profiles:
921  * @project: A #GESProject
922  *
923  * Lists the encoding profile that have been set to @project. The first one
924  * is the latest added.
925  *
926  * Returns: (transfer none) (element-type GstPbutils.EncodingProfile) (allow-none): The
927  * list of #GstEncodingProfile used in @project
928  */
929 const GList *
930 ges_project_list_encoding_profiles (GESProject * project)
931 {
932   g_return_val_if_fail (GES_IS_PROJECT (project), FALSE);
933
934   return project->priv->encoding_profiles;
935 }
936
937 /**
938  * ges_project_get_loading_assets:
939  * @project: A #GESProject
940  *
941  * Get the assets that are being loaded
942  *
943  * Returns: (transfer full) (element-type GES.Asset): A set of loading asset
944  * that will be added to @project. Note that those Asset are *not* loaded yet,
945  * and thus can not be used
946  */
947 GList *
948 ges_project_get_loading_assets (GESProject * project)
949 {
950   GHashTableIter iter;
951   gpointer key, value;
952
953   GList *ret = NULL;
954
955   g_return_val_if_fail (GES_IS_PROJECT (project), NULL);
956
957   g_hash_table_iter_init (&iter, project->priv->loading_assets);
958   while (g_hash_table_iter_next (&iter, &key, &value))
959     ret = g_list_prepend (ret, gst_object_ref (value));
960
961   return ret;
962 }