project: Run the vmethod in first stage for the "loaded" signal
[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_remove_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       return new_id;
449     else
450       g_free (new_id);
451   }
452
453   return NULL;
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     return;
477   }
478
479   ges_project_add_asset (project, asset);
480   if (asset)
481     gst_object_unref (asset);
482 }
483
484 /**
485  * ges_project_set_loaded:
486  * @project: The #GESProject from which to emit the "project-loaded" signal
487  *
488  * Emits the "loaded" signal. This method should be called by sublasses when
489  * the project is fully loaded.
490  *
491  * Returns: %TRUE if the signale could be emitted %FALSE otherwize
492  */
493 gboolean
494 ges_project_set_loaded (GESProject * project, GESFormatter * formatter)
495 {
496   GST_INFO_OBJECT (project, "Emit project loaded");
497   g_signal_emit (project, _signals[LOADED_SIGNAL], 0, formatter->timeline);
498
499   /* We are now done with that formatter */
500   ges_project_remove_formatter (project, formatter);
501   return TRUE;
502 }
503
504 /**************************************
505  *                                    *
506  *         API Implementation         *
507  *                                    *
508  **************************************/
509
510 /**
511  * ges_project_create_asset:
512  * @project: A #GESProject
513  * @id: The id of the asset to create and add to @project
514  * @extractable_type: The #GType of the asset to create
515  *
516  * Create and add a #GESAsset to @project. You should connect to the
517  * "asset-added" signal to get the asset when it finally gets added to
518  * @project
519  *
520  * Returns: %TRUE if the asset started to be added %FALSE it was already
521  * in the project
522  */
523 gboolean
524 ges_project_create_asset (GESProject * project, const gchar * id,
525     GType extractable_type)
526 {
527   g_return_val_if_fail (GES_IS_PROJECT (project), FALSE);
528   g_return_val_if_fail (g_type_is_a (extractable_type, GES_TYPE_EXTRACTABLE),
529       FALSE);
530
531   if (g_hash_table_lookup (project->priv->assets, id))
532     return FALSE;
533
534   /* TODO Add a GCancellable somewhere in our API */
535   ges_asset_request_async (extractable_type, id, NULL,
536       (GAsyncReadyCallback) new_asset_cb, project);
537
538   return TRUE;
539 }
540
541 /**
542  * ges_project_add_asset:
543  * @project: A #GESProject
544  * @asset: (transfer none): A #GESAsset to add to @project
545  *
546  * Adds a #Asset to @project, the project will keep a reference on
547  * @asset.
548  *
549  * Returns: %TRUE if the asset could be added %FALSE it was already
550  * in the project
551  */
552 gboolean
553 ges_project_add_asset (GESProject * project, GESAsset * asset)
554 {
555   g_return_val_if_fail (GES_IS_PROJECT (project), FALSE);
556
557   if (g_hash_table_lookup (project->priv->assets, ges_asset_get_id (asset)))
558     return FALSE;
559
560   g_hash_table_insert (project->priv->assets,
561       g_strdup (ges_asset_get_id (asset)), gst_object_ref (asset));
562
563   GST_DEBUG_OBJECT (project, "Asset added: %s", ges_asset_get_id (asset));
564   g_signal_emit (project, _signals[ASSET_ADDED_SIGNAL], 0, asset);
565
566   return TRUE;
567 }
568
569 /**
570  * ges_project_remove_asset:
571  * @project: A #GESProject
572  * @asset: (transfer none): A #GESAsset to remove from @project
573  *
574  * remove a @asset to from @project.
575  *
576  * Returns: %TRUE if the asset could be removed %FALSE otherwise
577  */
578 gboolean
579 ges_project_remove_asset (GESProject * project, GESAsset * asset)
580 {
581   gboolean ret;
582
583   g_return_val_if_fail (GES_IS_PROJECT (project), FALSE);
584
585   ret = g_hash_table_remove (project->priv->assets, ges_asset_get_id (asset));
586
587   g_signal_emit (project, _signals[ASSET_REMOVED_SIGNAL], 0, asset);
588
589   return ret;
590 }
591
592 /**
593  * ges_project_get_asset:
594  * @project: A #GESProject
595  * @id: The id of the asset to retrieve
596  * @extractable_type: The extractable_type of the asset
597  * to retrieve from @object
598  *
599  * Returns: (transfer full) (allow-none): The #GESAsset with
600  * @id or %NULL if no asset with @id as an ID
601  */
602 GESAsset *
603 ges_project_get_asset (GESProject * project, const gchar * id,
604     GType extractable_type)
605 {
606   GESAsset *asset;
607
608   g_return_val_if_fail (GES_IS_PROJECT (project), NULL);
609   g_return_val_if_fail (g_type_is_a (extractable_type, GES_TYPE_EXTRACTABLE),
610       NULL);
611
612   asset = g_hash_table_lookup (project->priv->assets, id);
613
614   if (asset)
615     return gst_object_ref (asset);
616
617   return NULL;
618 }
619
620 /**
621  * ges_project_list_assets:
622  * @project: A #GESProject
623  * @filter: Type of assets to list, #GES_TYPE_EXTRACTABLE will list
624  * all assets
625  *
626  * List all @asset contained in @project filtering per extractable_type
627  * as defined by @filter. It copies the asset and thus will not be updated
628  * in time.
629  *
630  * Returns: (transfer full) (element-type GESAsset): The list of
631  * #GESAsset the object contains
632  */
633 GList *
634 ges_project_list_assets (GESProject * project, GType filter)
635 {
636   GList *ret = NULL;
637   GHashTableIter iter;
638   gpointer key, value;
639
640   g_return_val_if_fail (GES_IS_PROJECT (project), NULL);
641
642   g_hash_table_iter_init (&iter, project->priv->assets);
643   while (g_hash_table_iter_next (&iter, &key, &value)) {
644     if (g_type_is_a (ges_asset_get_extractable_type (GES_ASSET (value)),
645             filter))
646       ret = g_list_append (ret, g_object_ref (value));
647   }
648
649   return ret;
650 }
651
652 /**
653  * ges_project_save:
654  * @project: A #GESProject to save
655  * @timeline: The #GESTimeline to save, it must have been extracted from @project
656  * @uri: The uri where to save @project and @timeline
657  * @formatter_asset: (allow-none): The formatter asset to use or %NULL. If %NULL,
658  * will try to save in the same format as the one from which the timeline as been loaded
659  * or default to the formatter with highest rank
660  * @overwrite: %TRUE to overwrite file if it exists
661  * @error: (out) (allow-none): An error to be set in case something wrong happens or %NULL
662  *
663  * Save the timeline of @project to @uri. You should make sure that @timeline
664  * is one of the timelines that have been extracted from @project
665  * (using ges_asset_extract (@project);)
666  *
667  * Returns: %TRUE if the project could be save, %FALSE otherwize
668  */
669 gboolean
670 ges_project_save (GESProject * project, GESTimeline * timeline,
671     const gchar * uri, GESAsset * formatter_asset, gboolean overwrite,
672     GError ** error)
673 {
674   GESAsset *tl_asset;
675   gboolean ret = TRUE;
676   GESFormatter *formatter = NULL;
677
678   g_return_val_if_fail (GES_IS_PROJECT (project), FALSE);
679   g_return_val_if_fail (formatter_asset == NULL ||
680       g_type_is_a (ges_asset_get_extractable_type (formatter_asset),
681           GES_TYPE_FORMATTER), FALSE);
682   g_return_val_if_fail ((error == NULL || *error == NULL), FALSE);
683
684   tl_asset = ges_extractable_get_asset (GES_EXTRACTABLE (timeline));
685   if (tl_asset == NULL && project->priv->uri == NULL) {
686     GESAsset *asset = ges_asset_cache_lookup (GES_TYPE_PROJECT, uri);
687
688     if (asset) {
689       GST_WARNING_OBJECT (project, "Trying to save project to %s but we already"
690           "have %" GST_PTR_FORMAT " for that uri, can not save", uri, asset);
691       goto out;
692     }
693
694     GST_DEBUG_OBJECT (project, "Timeline %" GST_PTR_FORMAT " has no asset"
695         " we have no uri set, so setting ourself as asset", timeline);
696
697     ges_extractable_set_asset (GES_EXTRACTABLE (timeline), GES_ASSET (project));
698   } else if (tl_asset != GES_ASSET (project)) {
699     GST_WARNING_OBJECT (project, "Timeline %" GST_PTR_FORMAT
700         " not created by this project can not save", timeline);
701
702     ret = FALSE;
703     goto out;
704   }
705
706   if (formatter_asset == NULL)
707     formatter_asset = gst_object_ref (ges_formatter_get_default ());
708
709   formatter = GES_FORMATTER (ges_asset_extract (formatter_asset, error));
710   if (formatter == NULL) {
711     GST_WARNING_OBJECT (project, "Could not create the formatter %s: Error: %s",
712         formatter_asset, (error && *error) ? (*error)->message : "Unknown");
713
714     ret = FALSE;
715     goto out;
716   }
717
718   ges_project_add_formatter (project, formatter);
719   ret = ges_formatter_save_to_uri (formatter, timeline, uri, overwrite, error);
720   if (ret && project->priv->uri == NULL)
721     ges_project_set_uri (project, uri);
722
723 out:
724   if (formatter_asset)
725     gst_object_unref (formatter_asset);
726   ges_project_remove_formatter (project, formatter);
727
728   return ret;
729 }
730
731 /**
732  * ges_project_new:
733  * @uri: (allow-none): The uri to be set after creating the project.
734  *
735  * Creates a new #GESProject and sets its uri to @uri if provided. Note that
736  * if @uri is not valid or %NULL, the uri of the project will then be set
737  * the first time you save the project. If you then save the project to
738  * other locations, it will never be updated again and the first valid URI is
739  * the URI it will keep refering to.
740  *
741  * Returns: A newly created #GESProject
742  */
743 GESProject *
744 ges_project_new (const gchar * uri)
745 {
746   gchar *id = (gchar *) uri;
747   GESProject *project;
748
749   if (uri == NULL)
750     id = g_strdup_printf ("project-%i", nb_projects++);
751
752   project = GES_PROJECT (ges_asset_request (GES_TYPE_TIMELINE, id, NULL));
753
754   if (project && uri)
755     ges_project_set_uri (project, uri);
756
757   return project;
758 }
759
760 /**
761  * ges_project_load:
762  * @project: A #GESProject that has an @uri set already
763  * @timeline: A blank timeline to load @project into
764  * @error: (out) (allow-none): An error to be set in case something wrong happens or %NULL
765  *
766  * Loads @project into @timeline
767  *
768  * Returns: %TRUE if the project could be loaded %FALSE otherwize.
769  */
770 gboolean
771 ges_project_load (GESProject * project, GESTimeline * timeline, GError ** error)
772 {
773   g_return_val_if_fail (GES_IS_TIMELINE (timeline), FALSE);
774   g_return_val_if_fail (GES_IS_PROJECT (project), FALSE);
775   g_return_val_if_fail (ges_project_get_uri (project), FALSE);
776   g_return_val_if_fail (
777       (ges_extractable_get_asset (GES_EXTRACTABLE (timeline)) == NULL), FALSE);
778
779   if (!_load_project (project, timeline, error))
780     return FALSE;
781
782   ges_extractable_set_asset (GES_EXTRACTABLE (timeline), GES_ASSET (project));
783
784   return TRUE;
785 }
786
787 /**
788  * ges_project_get_uri:
789  * @project: A #GESProject
790  *
791  * Retrieve the uri that is currently set on @project
792  *
793  * Returns: The uri that is set on @project
794  */
795 gchar *
796 ges_project_get_uri (GESProject * project)
797 {
798   GESProjectPrivate *priv;
799
800   g_return_val_if_fail (GES_IS_PROJECT (project), FALSE);
801
802   priv = project->priv;
803   if (priv->uri)
804     return g_strdup (priv->uri);
805   return NULL;
806 }
807
808 /**
809  * ges_project_add_encoding_profile:
810  * @project: A #GESProject
811  * @profile: A #GstEncodingProfile to add to the project. If a profile with
812  * the same name already exists, it will be replaced
813  *
814  * Adds @profile to the project. It lets you save in what format
815  * the project has been renders and keep a reference to those formats.
816  * Also, those formats will be saves to the project file when possible.
817  *
818  * Returns: %TRUE if @profile could be added, %FALSE otherwize
819  */
820 gboolean
821 ges_project_add_encoding_profile (GESProject * project,
822     GstEncodingProfile * profile)
823 {
824   GList *tmp;
825   GESProjectPrivate *priv;
826
827   g_return_val_if_fail (GES_IS_PROJECT (project), FALSE);
828   g_return_val_if_fail (GST_IS_ENCODING_PROFILE (profile), FALSE);
829
830   priv = project->priv;
831   for (tmp = priv->encoding_profiles; tmp; tmp = tmp->next) {
832     GstEncodingProfile *tmpprofile = GST_ENCODING_PROFILE (tmp->data);
833
834     if (g_strcmp0 (gst_encoding_profile_get_name (tmpprofile),
835             gst_encoding_profile_get_name (profile)) == 0) {
836       GST_INFO_OBJECT (project, "Already have profile: %s, replacing it",
837           gst_encoding_profile_get_name (profile));
838
839       gst_object_unref (tmp->data);
840       tmp->data = gst_object_ref (profile);
841       return TRUE;
842     }
843   }
844
845   priv->encoding_profiles = g_list_prepend (priv->encoding_profiles,
846       gst_object_ref (profile));
847
848   return TRUE;
849 }
850
851 /**
852  * ges_project_list_encoding_profiles:
853  * @project: A #GESProject
854  *
855  * Lists the encoding profile that have been set to @project. The first one
856  * is the latest added.
857  *
858  * Returns: (transfer none) (element-type GstPbutils.EncodingProfile) (allow-none): The
859  * list of #GstEncodingProfile used in @project
860  */
861 const GList *
862 ges_project_list_encoding_profiles (GESProject * project)
863 {
864   g_return_val_if_fail (GES_IS_PROJECT (project), FALSE);
865
866   return project->priv->encoding_profiles;
867 }