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  * @title: GESProject
23  * @short_description: A GESAsset that is used to manage projects
24  *
25  * The #GESProject is used to control a set of #GESAsset and is a
26  * #GESAsset with #GES_TYPE_TIMELINE as @extractable_type itself. That
27  * means that you can extract #GESTimeline from a project as followed:
28  *
29  * |[
30  *  GESProject *project;
31  *  GESTimeline *timeline;
32  *
33  *  project = ges_project_new ("file:///path/to/a/valid/project/uri");
34  *
35  *  // Here you can connect to the various signal to get more infos about
36  *  // what is happening and recover from errors if possible
37  *  ...
38  *
39  *  timeline = ges_asset_extract (GES_ASSET (project));
40  * ]|
41  *
42  * The #GESProject class offers a higher level API to handle #GESAsset-s.
43  * It lets you request new asset, and it informs you about new assets through
44  * a set of signals. Also it handles problem such as missing files/missing
45  * #GstElement and lets you try to recover from those.
46  */
47 #ifdef HAVE_CONFIG_H
48 #include "config.h"
49 #endif
50 
51 #include "ges.h"
52 #include "ges-internal.h"
53 
54 static GPtrArray *new_paths = NULL;
55 static GHashTable *tried_uris = NULL;
56 
57 /* TODO We should rely on both extractable_type and @id to identify
58  * a Asset, not only @id
59  */
60 struct _GESProjectPrivate
61 {
62   GHashTable *assets;
63   /* Set of asset ID being loaded */
64   GHashTable *loading_assets;
65   GHashTable *loaded_with_error;
66   GESAsset *formatter_asset;
67 
68   GList *formatters;
69 
70   gchar *uri;
71 
72   GList *encoding_profiles;
73 };
74 
75 typedef struct EmitLoadedInIdle
76 {
77   GESProject *project;
78   GESTimeline *timeline;
79 } EmitLoadedInIdle;
80 
81 enum
82 {
83   LOADED_SIGNAL,
84   ERROR_LOADING_ASSET,
85   ASSET_ADDED_SIGNAL,
86   ASSET_REMOVED_SIGNAL,
87   MISSING_URI_SIGNAL,
88   ASSET_LOADING_SIGNAL,
89   LAST_SIGNAL
90 };
91 
92 G_DEFINE_TYPE_WITH_PRIVATE (GESProject, ges_project, GES_TYPE_ASSET);
93 
94 static guint _signals[LAST_SIGNAL] = { 0 };
95 
96 static guint nb_projects = 0;
97 
98 enum
99 {
100   PROP_0,
101   PROP_URI,
102   PROP_LAST,
103 };
104 
105 static GParamSpec *_properties[LAST_SIGNAL] = { 0 };
106 
107 static gboolean
_emit_loaded_in_idle(EmitLoadedInIdle * data)108 _emit_loaded_in_idle (EmitLoadedInIdle * data)
109 {
110   g_signal_emit (data->project, _signals[LOADED_SIGNAL], 0, data->timeline);
111 
112   gst_object_unref (data->project);
113   gst_object_unref (data->timeline);
114   g_slice_free (EmitLoadedInIdle, data);
115 
116   return FALSE;
117 }
118 
119 static void
ges_project_add_formatter(GESProject * project,GESFormatter * formatter)120 ges_project_add_formatter (GESProject * project, GESFormatter * formatter)
121 {
122   GESProjectPrivate *priv = GES_PROJECT (project)->priv;
123 
124   ges_formatter_set_project (formatter, project);
125   priv->formatters = g_list_append (priv->formatters, formatter);
126 
127   gst_object_ref_sink (formatter);
128 }
129 
130 static void
ges_project_remove_formatter(GESProject * project,GESFormatter * formatter)131 ges_project_remove_formatter (GESProject * project, GESFormatter * formatter)
132 {
133   GList *tmp;
134   GESProjectPrivate *priv = GES_PROJECT (project)->priv;
135 
136   for (tmp = priv->formatters; tmp; tmp = tmp->next) {
137     if (tmp->data == formatter) {
138       gst_object_unref (formatter);
139       priv->formatters = g_list_delete_link (priv->formatters, tmp);
140 
141       return;
142     }
143   }
144 }
145 
146 static void
ges_project_set_uri(GESProject * project,const gchar * uri)147 ges_project_set_uri (GESProject * project, const gchar * uri)
148 {
149   GESProjectPrivate *priv;
150 
151   g_return_if_fail (GES_IS_PROJECT (project));
152 
153   priv = project->priv;
154   if (priv->uri) {
155     GST_WARNING_OBJECT (project, "Trying to rest URI, this is prohibited");
156 
157     return;
158   }
159 
160   if (uri == NULL) {
161     GST_LOG_OBJECT (project, "Uri should not be NULL");
162     return;
163   }
164 
165   priv->uri = g_strdup (uri);
166 
167   /* We use that URI as ID */
168   ges_asset_set_id (GES_ASSET (project), uri);
169 
170   return;
171 }
172 
173 static gboolean
_load_project(GESProject * project,GESTimeline * timeline,GError ** error)174 _load_project (GESProject * project, GESTimeline * timeline, GError ** error)
175 {
176   GError *lerr = NULL;
177   GESProjectPrivate *priv;
178   GESFormatter *formatter;
179 
180   priv = GES_PROJECT (project)->priv;
181 
182   if (priv->uri == NULL) {
183     EmitLoadedInIdle *data = g_slice_new (EmitLoadedInIdle);
184 
185     GST_LOG_OBJECT (project, "%s, Loading an empty timeline %s"
186         " as no URI set yet", GST_OBJECT_NAME (timeline),
187         ges_asset_get_id (GES_ASSET (project)));
188 
189     data->timeline = gst_object_ref (timeline);
190     data->project = gst_object_ref (project);
191 
192     /* Make sure the signal is emitted after the functions ends */
193     g_idle_add ((GSourceFunc) _emit_loaded_in_idle, data);
194     return TRUE;
195   }
196 
197   if (priv->formatter_asset == NULL)
198     priv->formatter_asset = _find_formatter_asset_for_id (priv->uri);
199 
200   if (priv->formatter_asset == NULL) {
201     lerr = g_error_new (GES_ERROR, 0, "Could not find a suitable formatter");
202     goto failed;
203   }
204 
205   formatter = GES_FORMATTER (ges_asset_extract (priv->formatter_asset, &lerr));
206   if (lerr) {
207     GST_WARNING_OBJECT (project, "Could not create the formatter: %s",
208         lerr->message);
209 
210     goto failed;
211   }
212 
213   ges_project_add_formatter (GES_PROJECT (project), formatter);
214   ges_formatter_load_from_uri (formatter, timeline, priv->uri, &lerr);
215   if (lerr) {
216     GST_WARNING_OBJECT (project, "Could not load the timeline,"
217         " returning: %s", lerr->message);
218     goto failed;
219   }
220 
221   return TRUE;
222 
223 failed:
224   if (lerr)
225     g_propagate_error (error, lerr);
226   return FALSE;
227 }
228 
229 static gboolean
_uri_missing_accumulator(GSignalInvocationHint * ihint,GValue * return_accu,const GValue * handler_return,gpointer data)230 _uri_missing_accumulator (GSignalInvocationHint * ihint, GValue * return_accu,
231     const GValue * handler_return, gpointer data)
232 {
233   const gchar *ret = g_value_get_string (handler_return);
234 
235   if (ret) {
236     if (!gst_uri_is_valid (ret)) {
237       GST_INFO ("The uri %s was not valid, can not work with it!", ret);
238       return TRUE;
239     }
240 
241     g_value_set_string (return_accu, ret);
242     return FALSE;
243   }
244 
245   return TRUE;
246 }
247 
248 /* GESAsset vmethod implementation */
249 static GESExtractable *
ges_project_extract(GESAsset * project,GError ** error)250 ges_project_extract (GESAsset * project, GError ** error)
251 {
252   GESTimeline *timeline = g_object_new (GES_TYPE_TIMELINE, NULL);
253 
254   ges_extractable_set_asset (GES_EXTRACTABLE (timeline), GES_ASSET (project));
255   if (_load_project (GES_PROJECT (project), timeline, error))
256     return GES_EXTRACTABLE (timeline);
257 
258   gst_object_unref (timeline);
259   return NULL;
260 }
261 
262 static void
_add_media_new_paths_recursing(const gchar * value)263 _add_media_new_paths_recursing (const gchar * value)
264 {
265   GFileInfo *info;
266   GFileEnumerator *fenum;
267   GFile *file = g_file_new_for_uri (value);
268 
269   if (!(fenum = g_file_enumerate_children (file,
270               "standard::*", G_FILE_QUERY_INFO_NONE, NULL, NULL))) {
271     GST_INFO ("%s is not a folder", value);
272 
273     goto done;
274   }
275 
276   GST_INFO ("Adding folder: %s", value);
277   g_ptr_array_add (new_paths, g_strdup (value));
278   info = g_file_enumerator_next_file (fenum, NULL, NULL);
279   while (info != NULL) {
280     if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) {
281       GFile *f = g_file_enumerator_get_child (fenum, info);
282       gchar *uri = g_file_get_uri (f);
283 
284       _add_media_new_paths_recursing (uri);
285       gst_object_unref (f);
286       g_free (uri);
287     }
288     g_object_unref (info);
289     info = g_file_enumerator_next_file (fenum, NULL, NULL);
290   }
291 
292 done:
293   gst_object_unref (file);
294   if (fenum)
295     gst_object_unref (fenum);
296 }
297 
298 gboolean
ges_add_missing_uri_relocation_uri(const gchar * uri,gboolean recurse)299 ges_add_missing_uri_relocation_uri (const gchar * uri, gboolean recurse)
300 {
301   g_return_val_if_fail (gst_uri_is_valid (uri), FALSE);
302 
303   if (new_paths == NULL)
304     new_paths = g_ptr_array_new_with_free_func (g_free);
305 
306   if (recurse)
307     _add_media_new_paths_recursing (uri);
308   else
309     g_ptr_array_add (new_paths, g_strdup (uri));
310 
311   return TRUE;
312 }
313 
314 static gchar *
ges_missing_uri_default(GESProject * self,GError * error,GESAsset * wrong_asset)315 ges_missing_uri_default (GESProject * self, GError * error,
316     GESAsset * wrong_asset)
317 {
318   guint i;
319   const gchar *old_uri = ges_asset_get_id (wrong_asset);
320   gchar *new_id = NULL;
321 
322   if (ges_asset_request_id_update (wrong_asset, &new_id, error) && new_id) {
323     GST_INFO_OBJECT (self, "Returned guessed new ID: %s", new_id);
324 
325     return new_id;
326   }
327 
328   if (new_paths == NULL)
329     return NULL;
330 
331   if (tried_uris == NULL)
332     tried_uris = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
333 
334   for (i = 0; i < new_paths->len; i++) {
335     gchar *basename, *res;
336 
337     basename = g_path_get_basename (old_uri);
338     res = g_build_filename (new_paths->pdata[i], basename, NULL);
339     g_free (basename);
340 
341     if (g_strcmp0 (old_uri, res) == 0) {
342       g_hash_table_add (tried_uris, res);
343     } else if (g_hash_table_lookup (tried_uris, res)) {
344       GST_DEBUG_OBJECT (self, "File already tried: %s", res);
345       g_free (res);
346     } else {
347       g_hash_table_add (tried_uris, g_strdup (res));
348       GST_DEBUG_OBJECT (self, "Trying: %s\n", res);
349       return res;
350     }
351   }
352 
353   return NULL;
354 }
355 
356 gchar *
ges_uri_asset_try_update_id(GError * error,GESAsset * wrong_asset)357 ges_uri_asset_try_update_id (GError * error, GESAsset * wrong_asset)
358 {
359   return ges_missing_uri_default (NULL, error, wrong_asset);
360 }
361 
362 static void
ges_uri_assets_validate_uri(const gchar * nid)363 ges_uri_assets_validate_uri (const gchar * nid)
364 {
365   if (tried_uris)
366     g_hash_table_remove (tried_uris, nid);
367 }
368 
369 /* GObject vmethod implementation */
370 static void
_dispose(GObject * object)371 _dispose (GObject * object)
372 {
373   GESProjectPrivate *priv = GES_PROJECT (object)->priv;
374 
375   if (priv->assets)
376     g_hash_table_unref (priv->assets);
377   if (priv->loading_assets)
378     g_hash_table_unref (priv->loading_assets);
379   if (priv->loaded_with_error)
380     g_hash_table_unref (priv->loaded_with_error);
381   if (priv->formatter_asset)
382     gst_object_unref (priv->formatter_asset);
383 
384   while (priv->formatters)
385     ges_project_remove_formatter (GES_PROJECT (object), priv->formatters->data);
386 
387   G_OBJECT_CLASS (ges_project_parent_class)->dispose (object);
388 }
389 
390 static void
_finalize(GObject * object)391 _finalize (GObject * object)
392 {
393   GESProjectPrivate *priv = GES_PROJECT (object)->priv;
394 
395   if (priv->uri)
396     g_free (priv->uri);
397   g_list_free_full (priv->encoding_profiles, g_object_unref);
398 
399   G_OBJECT_CLASS (ges_project_parent_class)->finalize (object);
400 }
401 
402 static void
_get_property(GESProject * project,guint property_id,GValue * value,GParamSpec * pspec)403 _get_property (GESProject * project, guint property_id,
404     GValue * value, GParamSpec * pspec)
405 {
406   GESProjectPrivate *priv = project->priv;
407 
408   switch (property_id) {
409     case PROP_URI:
410       g_value_set_string (value, priv->uri);
411       break;
412     default:
413       G_OBJECT_WARN_INVALID_PROPERTY_ID (project, property_id, pspec);
414   }
415 }
416 
417 static void
_set_property(GESProject * project,guint property_id,const GValue * value,GParamSpec * pspec)418 _set_property (GESProject * project, guint property_id,
419     const GValue * value, GParamSpec * pspec)
420 {
421   switch (property_id) {
422     case PROP_URI:
423       project->priv->uri = g_value_dup_string (value);
424       break;
425     default:
426       G_OBJECT_WARN_INVALID_PROPERTY_ID (project, property_id, pspec);
427   }
428 }
429 
430 static void
ges_project_class_init(GESProjectClass * klass)431 ges_project_class_init (GESProjectClass * klass)
432 {
433   GObjectClass *object_class = G_OBJECT_CLASS (klass);
434 
435   klass->asset_added = NULL;
436   klass->missing_uri = ges_missing_uri_default;
437   klass->loading_error = NULL;
438   klass->asset_removed = NULL;
439   object_class->get_property = (GObjectGetPropertyFunc) _get_property;
440   object_class->set_property = (GObjectSetPropertyFunc) _set_property;
441 
442   /**
443    * GESProject::uri:
444    *
445    * The location of the project to use.
446    */
447   _properties[PROP_URI] = g_param_spec_string ("uri", "URI",
448       "uri of the project", NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
449 
450   g_object_class_install_properties (object_class, PROP_LAST, _properties);
451 
452   /**
453    * GESProject::asset-added:
454    * @project: the #GESProject
455    * @asset: The #GESAsset that has been added to @project
456    */
457   _signals[ASSET_ADDED_SIGNAL] =
458       g_signal_new ("asset-added", G_TYPE_FROM_CLASS (klass),
459       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GESProjectClass, asset_added),
460       NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1, GES_TYPE_ASSET);
461 
462   /**
463    * GESProject::asset-loading:
464    * @project: the #GESProject
465    * @asset: The #GESAsset that started loading
466    *
467    * Since: 1.8
468    */
469   _signals[ASSET_LOADING_SIGNAL] =
470       g_signal_new ("asset-loading", G_TYPE_FROM_CLASS (klass),
471       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GESProjectClass, asset_loading),
472       NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1, GES_TYPE_ASSET);
473 
474   /**
475    * GESProject::asset-removed:
476    * @project: the #GESProject
477    * @asset: The #GESAsset that has been removed from @project
478    */
479   _signals[ASSET_REMOVED_SIGNAL] =
480       g_signal_new ("asset-removed", G_TYPE_FROM_CLASS (klass),
481       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GESProjectClass, asset_removed),
482       NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1, GES_TYPE_ASSET);
483 
484   /**
485    * GESProject::loaded:
486    * @project: the #GESProject that is done loading a project.
487    * @timeline: The #GESTimeline that complete loading
488    */
489   _signals[LOADED_SIGNAL] =
490       g_signal_new ("loaded", G_TYPE_FROM_CLASS (klass),
491       G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GESProjectClass, loaded),
492       NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE,
493       1, GES_TYPE_TIMELINE);
494 
495   /**
496    * GESProject::missing-uri:
497    * @project: the #GESProject reporting that a file has moved
498    * @error: The error that happened
499    * @wrong_asset: The asset with the wrong ID, you should us it and its content
500    * only to find out what the new location is.
501    *
502    * |[
503    * static gchar
504    * source_moved_cb (GESProject *project, GError *error, GESAsset *asset_with_error)
505    * {
506    *   return g_strdup ("file:///the/new/uri.ogg");
507    * }
508    *
509    * static int
510    * main (int argc, gchar ** argv)
511    * {
512    *   GESTimeline *timeline;
513    *   GESProject *project = ges_project_new ("file:///some/uri.xges");
514    *
515    *   g_signal_connect (project, "missing-uri", source_moved_cb, NULL);
516    *   timeline = ges_asset_extract (GES_ASSET (project));
517    * }
518    * ]|
519    *
520    * Returns: (transfer full) (allow-none): The new URI of @wrong_asset
521    */
522   _signals[MISSING_URI_SIGNAL] =
523       g_signal_new ("missing-uri", G_TYPE_FROM_CLASS (klass),
524       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GESProjectClass, missing_uri),
525       _uri_missing_accumulator, NULL, g_cclosure_marshal_generic,
526       G_TYPE_STRING, 2, G_TYPE_ERROR, GES_TYPE_ASSET);
527 
528   /**
529    * GESProject::error-loading-asset:
530    * @project: the #GESProject on which a problem happend when creted a #GESAsset
531    * @error: The #GError defining the error that occured, might be %NULL
532    * @id: The @id of the asset that failed loading
533    * @extractable_type: The @extractable_type of the asset that
534    * failed loading
535    *
536    * Informs you that a #GESAsset could not be created. In case of
537    * missing GStreamer plugins, the error will be set to #GST_CORE_ERROR
538    * #GST_CORE_ERROR_MISSING_PLUGIN
539    */
540   _signals[ERROR_LOADING_ASSET] =
541       g_signal_new ("error-loading-asset", G_TYPE_FROM_CLASS (klass),
542       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GESProjectClass, loading_error),
543       NULL, NULL, g_cclosure_marshal_generic,
544       G_TYPE_NONE, 3, G_TYPE_ERROR, G_TYPE_STRING, G_TYPE_GTYPE);
545 
546   object_class->dispose = _dispose;
547   object_class->finalize = _finalize;
548 
549   GES_ASSET_CLASS (klass)->extract = ges_project_extract;
550 }
551 
552 static void
ges_project_init(GESProject * project)553 ges_project_init (GESProject * project)
554 {
555   GESProjectPrivate *priv = project->priv =
556       ges_project_get_instance_private (project);
557 
558   priv->uri = NULL;
559   priv->formatters = NULL;
560   priv->formatter_asset = NULL;
561   priv->encoding_profiles = NULL;
562   priv->assets = g_hash_table_new_full (g_str_hash, g_str_equal,
563       g_free, gst_object_unref);
564   priv->loading_assets = g_hash_table_new_full (g_str_hash, g_str_equal,
565       g_free, gst_object_unref);
566   priv->loaded_with_error = g_hash_table_new_full (g_str_hash, g_str_equal,
567       g_free, NULL);
568 }
569 
570 static void
_send_error_loading_asset(GESProject * project,GESAsset * asset,GError * error)571 _send_error_loading_asset (GESProject * project, GESAsset * asset,
572     GError * error)
573 {
574   const gchar *id = ges_asset_get_id (asset);
575 
576   GST_DEBUG_OBJECT (project, "Sending error loading asset for %s", id);
577   g_hash_table_remove (project->priv->loading_assets, id);
578   g_hash_table_add (project->priv->loaded_with_error, g_strdup (id));
579   g_signal_emit (project, _signals[ERROR_LOADING_ASSET], 0, error, id,
580       ges_asset_get_extractable_type (asset));
581 }
582 
583 gchar *
ges_project_try_updating_id(GESProject * project,GESAsset * asset,GError * error)584 ges_project_try_updating_id (GESProject * project, GESAsset * asset,
585     GError * error)
586 {
587   gchar *new_id = NULL;
588   const gchar *id;
589 
590   g_return_val_if_fail (GES_IS_PROJECT (project), NULL);
591   g_return_val_if_fail (GES_IS_ASSET (asset), NULL);
592   g_return_val_if_fail (error, NULL);
593 
594   id = ges_asset_get_id (asset);
595   GST_DEBUG_OBJECT (project, "Try to proxy %s", id);
596   if (ges_asset_request_id_update (asset, &new_id, error) == FALSE) {
597     GST_DEBUG_OBJECT (project, "Type: %s can not be proxied for id: %s "
598         "and error: %s", g_type_name (G_OBJECT_TYPE (asset)), id,
599         error->message);
600     _send_error_loading_asset (project, asset, error);
601 
602     return NULL;
603   }
604 
605   /* Always send the MISSING_URI signal if requesting new ID is possible
606    * so that subclasses of GESProject are aware of the missing-uri */
607   g_signal_emit (project, _signals[MISSING_URI_SIGNAL], 0, error, asset,
608       &new_id);
609 
610   if (new_id) {
611     GST_DEBUG_OBJECT (project, "new id found: %s", new_id);
612     if (!ges_asset_try_proxy (asset, new_id)) {
613       g_free (new_id);
614       new_id = NULL;
615     }
616   } else {
617     GST_DEBUG_OBJECT (project, "No new id found for %s", id);
618   }
619 
620   g_hash_table_remove (project->priv->loading_assets, id);
621 
622   if (new_id == NULL)
623     _send_error_loading_asset (project, asset, error);
624 
625 
626   return new_id;
627 }
628 
629 static void
new_asset_cb(GESAsset * source,GAsyncResult * res,GESProject * project)630 new_asset_cb (GESAsset * source, GAsyncResult * res, GESProject * project)
631 {
632   GError *error = NULL;
633   gchar *possible_id = NULL;
634   GESAsset *asset = ges_asset_request_finish (res, &error);
635 
636   if (error) {
637     possible_id = ges_project_try_updating_id (project, source, error);
638     g_clear_error (&error);
639 
640     if (possible_id == NULL)
641       return;
642 
643     ges_project_create_asset (project, possible_id,
644         ges_asset_get_extractable_type (source));
645 
646     g_free (possible_id);
647     return;
648   }
649 
650   ges_asset_set_proxy (NULL, asset);
651   ges_project_add_asset (project, asset);
652   if (asset)
653     gst_object_unref (asset);
654 }
655 
656 /**
657  * ges_project_set_loaded:
658  * @project: The #GESProject from which to emit the "project-loaded" signal
659  *
660  * Emits the "loaded" signal. This method should be called by sublasses when
661  * the project is fully loaded.
662  *
663  * Returns: %TRUE if the signale could be emitted %FALSE otherwize
664  */
665 gboolean
ges_project_set_loaded(GESProject * project,GESFormatter * formatter)666 ges_project_set_loaded (GESProject * project, GESFormatter * formatter)
667 {
668   GST_INFO_OBJECT (project, "Emit project loaded");
669   if (GST_STATE (formatter->timeline) < GST_STATE_PAUSED) {
670     timeline_fill_gaps (formatter->timeline);
671   } else {
672     ges_timeline_commit (formatter->timeline);
673   }
674 
675   g_signal_emit (project, _signals[LOADED_SIGNAL], 0, formatter->timeline);
676 
677   /* We are now done with that formatter */
678   ges_project_remove_formatter (project, formatter);
679   return TRUE;
680 }
681 
682 void
ges_project_add_loading_asset(GESProject * project,GType extractable_type,const gchar * id)683 ges_project_add_loading_asset (GESProject * project, GType extractable_type,
684     const gchar * id)
685 {
686   GESAsset *asset;
687 
688   if ((asset = ges_asset_cache_lookup (extractable_type, id))) {
689     if (g_hash_table_insert (project->priv->loading_assets, g_strdup (id),
690             gst_object_ref (asset)))
691       g_signal_emit (project, _signals[ASSET_LOADING_SIGNAL], 0, asset);
692   }
693 }
694 
695 /**************************************
696  *                                    *
697  *         API Implementation         *
698  *                                    *
699  **************************************/
700 
701 /**
702  * ges_project_create_asset:
703  * @project: A #GESProject
704  * @id: (allow-none): The id of the asset to create and add to @project
705  * @extractable_type: The #GType of the asset to create
706  *
707  * Create and add a #GESAsset to @project. You should connect to the
708  * "asset-added" signal to get the asset when it finally gets added to
709  * @project
710  *
711  * Returns: %TRUE if the asset started to be added %FALSE it was already
712  * in the project
713  */
714 gboolean
ges_project_create_asset(GESProject * project,const gchar * id,GType extractable_type)715 ges_project_create_asset (GESProject * project, const gchar * id,
716     GType extractable_type)
717 {
718   g_return_val_if_fail (GES_IS_PROJECT (project), FALSE);
719   g_return_val_if_fail (g_type_is_a (extractable_type, GES_TYPE_EXTRACTABLE),
720       FALSE);
721 
722   if (id == NULL)
723     id = g_type_name (extractable_type);
724 
725   if (g_hash_table_lookup (project->priv->assets, id) ||
726       g_hash_table_lookup (project->priv->loading_assets, id) ||
727       g_hash_table_lookup (project->priv->loaded_with_error, id))
728     return FALSE;
729 
730   /* TODO Add a GCancellable somewhere in our API */
731   ges_asset_request_async (extractable_type, id, NULL,
732       (GAsyncReadyCallback) new_asset_cb, project);
733   ges_project_add_loading_asset (project, extractable_type, id);
734 
735   return TRUE;
736 }
737 
738 /**
739  * ges_project_create_asset_sync:
740  * @project: A #GESProject
741  * @id: (allow-none): The id of the asset to create and add to @project
742  * @extractable_type: The #GType of the asset to create
743  * @error: A #GError to be set in case of error
744  *
745  * Create and add a #GESAsset to @project. You should connect to the
746  * "asset-added" signal to get the asset when it finally gets added to
747  * @project
748  *
749  * Returns: (transfer full) (nullable): The newly created #GESAsset or %NULL.
750  */
751 GESAsset *
ges_project_create_asset_sync(GESProject * project,const gchar * id,GType extractable_type,GError ** error)752 ges_project_create_asset_sync (GESProject * project, const gchar * id,
753     GType extractable_type, GError ** error)
754 {
755   GESAsset *asset;
756   gchar *possible_id = NULL;
757   gboolean retry = TRUE;
758 
759   g_return_val_if_fail (GES_IS_PROJECT (project), FALSE);
760   g_return_val_if_fail (g_type_is_a (extractable_type, GES_TYPE_EXTRACTABLE),
761       FALSE);
762 
763   if (id == NULL)
764     id = g_type_name (extractable_type);
765 
766   if ((asset = g_hash_table_lookup (project->priv->assets, id)))
767     return asset;
768   else if (g_hash_table_lookup (project->priv->loading_assets, id) ||
769       g_hash_table_lookup (project->priv->loaded_with_error, id))
770     return NULL;
771 
772   /* TODO Add a GCancellable somewhere in our API */
773   while (retry) {
774 
775     if (g_type_is_a (extractable_type, GES_TYPE_URI_CLIP)) {
776       asset = GES_ASSET (ges_uri_clip_asset_request_sync (id, error));
777     } else {
778       asset = ges_asset_request (extractable_type, id, error);
779     }
780 
781     if (asset) {
782       retry = FALSE;
783 
784       if ((!g_hash_table_lookup (project->priv->assets,
785                   ges_asset_get_id (asset))))
786         g_signal_emit (project, _signals[ASSET_LOADING_SIGNAL], 0, asset);
787 
788       if (possible_id) {
789         g_free (possible_id);
790         ges_uri_assets_validate_uri (id);
791       }
792 
793       break;
794     } else {
795       GESAsset *tmpasset;
796 
797       tmpasset = ges_asset_cache_lookup (extractable_type, id);
798       possible_id = ges_project_try_updating_id (project, tmpasset, *error);
799 
800       if (possible_id == NULL) {
801         g_signal_emit (project, _signals[ASSET_LOADING_SIGNAL], 0, tmpasset);
802         g_signal_emit (project, _signals[ERROR_LOADING_ASSET], 0, *error, id,
803             extractable_type);
804         return NULL;
805       }
806 
807 
808       g_clear_error (error);
809 
810       id = possible_id;
811     }
812   }
813 
814   if (!ges_asset_get_proxy_target (asset))
815     ges_asset_set_proxy (NULL, asset);
816 
817   ges_project_add_asset (project, asset);
818 
819   return asset;
820 }
821 
822 /**
823  * ges_project_add_asset:
824  * @project: A #GESProject
825  * @asset: (transfer none): A #GESAsset to add to @project
826  *
827  * Adds a #Asset to @project, the project will keep a reference on
828  * @asset.
829  *
830  * Returns: %TRUE if the asset could be added %FALSE it was already
831  * in the project
832  */
833 gboolean
ges_project_add_asset(GESProject * project,GESAsset * asset)834 ges_project_add_asset (GESProject * project, GESAsset * asset)
835 {
836   g_return_val_if_fail (GES_IS_PROJECT (project), FALSE);
837 
838   if (g_hash_table_lookup (project->priv->assets, ges_asset_get_id (asset)))
839     return TRUE;
840 
841   g_hash_table_insert (project->priv->assets,
842       g_strdup (ges_asset_get_id (asset)), gst_object_ref (asset));
843 
844   g_hash_table_remove (project->priv->loading_assets, ges_asset_get_id (asset));
845   GST_DEBUG_OBJECT (project, "Asset added: %s", ges_asset_get_id (asset));
846   g_signal_emit (project, _signals[ASSET_ADDED_SIGNAL], 0, asset);
847 
848   return TRUE;
849 }
850 
851 /**
852  * ges_project_remove_asset:
853  * @project: A #GESProject
854  * @asset: (transfer none): A #GESAsset to remove from @project
855  *
856  * remove a @asset to from @project.
857  *
858  * Returns: %TRUE if the asset could be removed %FALSE otherwise
859  */
860 gboolean
ges_project_remove_asset(GESProject * project,GESAsset * asset)861 ges_project_remove_asset (GESProject * project, GESAsset * asset)
862 {
863   gboolean ret;
864 
865   g_return_val_if_fail (GES_IS_PROJECT (project), FALSE);
866 
867   ret = g_hash_table_remove (project->priv->assets, ges_asset_get_id (asset));
868   g_signal_emit (project, _signals[ASSET_REMOVED_SIGNAL], 0, asset);
869 
870   return ret;
871 }
872 
873 /**
874  * ges_project_get_asset:
875  * @project: A #GESProject
876  * @id: The id of the asset to retrieve
877  * @extractable_type: The extractable_type of the asset
878  * to retrieve from @object
879  *
880  * Returns: (transfer full) (allow-none): The #GESAsset with
881  * @id or %NULL if no asset with @id as an ID
882  */
883 GESAsset *
ges_project_get_asset(GESProject * project,const gchar * id,GType extractable_type)884 ges_project_get_asset (GESProject * project, const gchar * id,
885     GType extractable_type)
886 {
887   GESAsset *asset;
888 
889   g_return_val_if_fail (GES_IS_PROJECT (project), NULL);
890   g_return_val_if_fail (g_type_is_a (extractable_type, GES_TYPE_EXTRACTABLE),
891       NULL);
892 
893   asset = g_hash_table_lookup (project->priv->assets, id);
894 
895   if (asset)
896     return gst_object_ref (asset);
897 
898   return NULL;
899 }
900 
901 /**
902  * ges_project_list_assets:
903  * @project: A #GESProject
904  * @filter: Type of assets to list, #GES_TYPE_EXTRACTABLE will list
905  * all assets
906  *
907  * List all @asset contained in @project filtering per extractable_type
908  * as defined by @filter. It copies the asset and thus will not be updated
909  * in time.
910  *
911  * Returns: (transfer full) (element-type GESAsset): The list of
912  * #GESAsset the object contains
913  */
914 GList *
ges_project_list_assets(GESProject * project,GType filter)915 ges_project_list_assets (GESProject * project, GType filter)
916 {
917   GList *ret = NULL;
918   GHashTableIter iter;
919   gpointer key, value;
920 
921   g_return_val_if_fail (GES_IS_PROJECT (project), NULL);
922 
923   g_hash_table_iter_init (&iter, project->priv->assets);
924   while (g_hash_table_iter_next (&iter, &key, &value)) {
925     if (g_type_is_a (ges_asset_get_extractable_type (GES_ASSET (value)),
926             filter))
927       ret = g_list_append (ret, gst_object_ref (value));
928   }
929 
930   return ret;
931 }
932 
933 /**
934  * ges_project_save:
935  * @project: A #GESProject to save
936  * @timeline: The #GESTimeline to save, it must have been extracted from @project
937  * @uri: The uri where to save @project and @timeline
938  * @formatter_asset: (allow-none): The formatter asset to use or %NULL. If %NULL,
939  * will try to save in the same format as the one from which the timeline as been loaded
940  * or default to the formatter with highest rank
941  * @overwrite: %TRUE to overwrite file if it exists
942  * @error: (out) (allow-none): An error to be set in case something wrong happens or %NULL
943  *
944  * Save the timeline of @project to @uri. You should make sure that @timeline
945  * is one of the timelines that have been extracted from @project
946  * (using ges_asset_extract (@project);)
947  *
948  * Returns: %TRUE if the project could be save, %FALSE otherwize
949  */
950 gboolean
ges_project_save(GESProject * project,GESTimeline * timeline,const gchar * uri,GESAsset * formatter_asset,gboolean overwrite,GError ** error)951 ges_project_save (GESProject * project, GESTimeline * timeline,
952     const gchar * uri, GESAsset * formatter_asset, gboolean overwrite,
953     GError ** error)
954 {
955   GESAsset *tl_asset;
956   gboolean ret = TRUE;
957   GESFormatter *formatter = NULL;
958 
959   g_return_val_if_fail (GES_IS_PROJECT (project), FALSE);
960   g_return_val_if_fail (formatter_asset == NULL ||
961       g_type_is_a (ges_asset_get_extractable_type (formatter_asset),
962           GES_TYPE_FORMATTER), FALSE);
963   g_return_val_if_fail ((error == NULL || *error == NULL), FALSE);
964 
965   tl_asset = ges_extractable_get_asset (GES_EXTRACTABLE (timeline));
966   if (tl_asset == NULL && project->priv->uri == NULL) {
967     GESAsset *asset = ges_asset_cache_lookup (GES_TYPE_PROJECT, uri);
968 
969     if (asset) {
970       GST_WARNING_OBJECT (project, "Trying to save project to %s but we already"
971           "have %" GST_PTR_FORMAT " for that uri, can not save", uri, asset);
972       goto out;
973     }
974 
975     GST_DEBUG_OBJECT (project, "Timeline %" GST_PTR_FORMAT " has no asset"
976         " we have no uri set, so setting ourself as asset", timeline);
977 
978     ges_extractable_set_asset (GES_EXTRACTABLE (timeline), GES_ASSET (project));
979   } else if (tl_asset != GES_ASSET (project)) {
980     GST_WARNING_OBJECT (project, "Timeline %" GST_PTR_FORMAT
981         " not created by this project can not save", timeline);
982 
983     ret = FALSE;
984     goto out;
985   }
986 
987   if (formatter_asset == NULL)
988     formatter_asset = gst_object_ref (ges_formatter_get_default ());
989 
990   formatter = GES_FORMATTER (ges_asset_extract (formatter_asset, error));
991   if (formatter == NULL) {
992     GST_WARNING_OBJECT (project, "Could not create the formatter %p %s: %s",
993         formatter_asset, ges_asset_get_id (formatter_asset),
994         (error && *error) ? (*error)->message : "Unknown Error");
995 
996     ret = FALSE;
997     goto out;
998   }
999 
1000   ges_project_add_formatter (project, formatter);
1001   ret = ges_formatter_save_to_uri (formatter, timeline, uri, overwrite, error);
1002   if (ret && project->priv->uri == NULL)
1003     ges_project_set_uri (project, uri);
1004 
1005 out:
1006   if (formatter_asset)
1007     gst_object_unref (formatter_asset);
1008   ges_project_remove_formatter (project, formatter);
1009 
1010   return ret;
1011 }
1012 
1013 /**
1014  * ges_project_new:
1015  * @uri: (allow-none): The uri to be set after creating the project.
1016  *
1017  * Creates a new #GESProject and sets its uri to @uri if provided. Note that
1018  * if @uri is not valid or %NULL, the uri of the project will then be set
1019  * the first time you save the project. If you then save the project to
1020  * other locations, it will never be updated again and the first valid URI is
1021  * the URI it will keep refering to.
1022  *
1023  * Returns: A newly created #GESProject
1024  */
1025 GESProject *
ges_project_new(const gchar * uri)1026 ges_project_new (const gchar * uri)
1027 {
1028   gchar *id = (gchar *) uri;
1029   GESProject *project;
1030 
1031   if (uri == NULL)
1032     id = g_strdup_printf ("project-%i", nb_projects++);
1033 
1034   project = GES_PROJECT (ges_asset_request (GES_TYPE_TIMELINE, id, NULL));
1035 
1036   if (project && uri)
1037     ges_project_set_uri (project, uri);
1038 
1039   if (uri == NULL)
1040     g_free (id);
1041 
1042   return project;
1043 }
1044 
1045 /**
1046  * ges_project_load:
1047  * @project: A #GESProject that has an @uri set already
1048  * @timeline: A blank timeline to load @project into
1049  * @error: (out) (allow-none): An error to be set in case something wrong happens or %NULL
1050  *
1051  * Loads @project into @timeline
1052  *
1053  * Returns: %TRUE if the project could be loaded %FALSE otherwize.
1054  */
1055 gboolean
ges_project_load(GESProject * project,GESTimeline * timeline,GError ** error)1056 ges_project_load (GESProject * project, GESTimeline * timeline, GError ** error)
1057 {
1058   g_return_val_if_fail (GES_IS_TIMELINE (timeline), FALSE);
1059   g_return_val_if_fail (GES_IS_PROJECT (project), FALSE);
1060   g_return_val_if_fail (ges_project_get_uri (project), FALSE);
1061   g_return_val_if_fail (timeline->tracks == NULL, FALSE);
1062 
1063   if (!_load_project (project, timeline, error))
1064     return FALSE;
1065 
1066   ges_extractable_set_asset (GES_EXTRACTABLE (timeline), GES_ASSET (project));
1067 
1068   return TRUE;
1069 }
1070 
1071 /**
1072  * ges_project_get_uri:
1073  * @project: A #GESProject
1074  *
1075  * Retrieve the uri that is currently set on @project
1076  *
1077  * Returns: (transfer full) (nullable): a newly allocated string representing uri.
1078  */
1079 gchar *
ges_project_get_uri(GESProject * project)1080 ges_project_get_uri (GESProject * project)
1081 {
1082   GESProjectPrivate *priv;
1083 
1084   g_return_val_if_fail (GES_IS_PROJECT (project), FALSE);
1085 
1086   priv = project->priv;
1087   if (priv->uri)
1088     return g_strdup (priv->uri);
1089   return NULL;
1090 }
1091 
1092 /**
1093  * ges_project_add_encoding_profile:
1094  * @project: A #GESProject
1095  * @profile: A #GstEncodingProfile to add to the project. If a profile with
1096  * the same name already exists, it will be replaced
1097  *
1098  * Adds @profile to the project. It lets you save in what format
1099  * the project has been renders and keep a reference to those formats.
1100  * Also, those formats will be saves to the project file when possible.
1101  *
1102  * Returns: %TRUE if @profile could be added, %FALSE otherwize
1103  */
1104 gboolean
ges_project_add_encoding_profile(GESProject * project,GstEncodingProfile * profile)1105 ges_project_add_encoding_profile (GESProject * project,
1106     GstEncodingProfile * profile)
1107 {
1108   GList *tmp;
1109   GESProjectPrivate *priv;
1110 
1111   g_return_val_if_fail (GES_IS_PROJECT (project), FALSE);
1112   g_return_val_if_fail (GST_IS_ENCODING_PROFILE (profile), FALSE);
1113 
1114   priv = project->priv;
1115   for (tmp = priv->encoding_profiles; tmp; tmp = tmp->next) {
1116     GstEncodingProfile *tmpprofile = GST_ENCODING_PROFILE (tmp->data);
1117 
1118     if (g_strcmp0 (gst_encoding_profile_get_name (tmpprofile),
1119             gst_encoding_profile_get_name (profile)) == 0) {
1120       GST_INFO_OBJECT (project, "Already have profile: %s, replacing it",
1121           gst_encoding_profile_get_name (profile));
1122 
1123       gst_object_unref (tmp->data);
1124       tmp->data = gst_object_ref (profile);
1125       return TRUE;
1126     }
1127   }
1128 
1129   priv->encoding_profiles = g_list_prepend (priv->encoding_profiles,
1130       gst_object_ref (profile));
1131 
1132   return TRUE;
1133 }
1134 
1135 /**
1136  * ges_project_list_encoding_profiles:
1137  * @project: A #GESProject
1138  *
1139  * Lists the encoding profile that have been set to @project. The first one
1140  * is the latest added.
1141  *
1142  * Returns: (transfer none) (element-type GstPbutils.EncodingProfile) (allow-none): The
1143  * list of #GstEncodingProfile used in @project
1144  */
1145 const GList *
ges_project_list_encoding_profiles(GESProject * project)1146 ges_project_list_encoding_profiles (GESProject * project)
1147 {
1148   g_return_val_if_fail (GES_IS_PROJECT (project), FALSE);
1149 
1150   return project->priv->encoding_profiles;
1151 }
1152 
1153 /**
1154  * ges_project_get_loading_assets:
1155  * @project: A #GESProject
1156  *
1157  * Get the assets that are being loaded
1158  *
1159  * Returns: (transfer full) (element-type GES.Asset): A set of loading asset
1160  * that will be added to @project. Note that those Asset are *not* loaded yet,
1161  * and thus can not be used
1162  */
1163 GList *
ges_project_get_loading_assets(GESProject * project)1164 ges_project_get_loading_assets (GESProject * project)
1165 {
1166   GHashTableIter iter;
1167   gpointer key, value;
1168 
1169   GList *ret = NULL;
1170 
1171   g_return_val_if_fail (GES_IS_PROJECT (project), NULL);
1172 
1173   g_hash_table_iter_init (&iter, project->priv->loading_assets);
1174   while (g_hash_table_iter_next (&iter, &key, &value))
1175     ret = g_list_prepend (ret, gst_object_ref (value));
1176 
1177   return ret;
1178 }
1179