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