encoding-target: Ensure target names and categories are valid
[platform/upstream/gstreamer.git] / gst-libs / gst / pbutils / encoding-target.c
1 /* GStreamer encoding profile registry
2  * Copyright (C) 2010 Edward Hervey <edward.hervey@collabora.co.uk>
3  *           (C) 2010 Nokia Corporation
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 #include <locale.h>
22 #include <string.h>
23 #include "encoding-target.h"
24
25 /*
26  * File format
27  *
28  * GKeyFile style.
29  *
30  * [_gstencodingtarget_]
31  * name : <name>
32  * category : <category>
33  * description : <description> #translatable
34  *
35  * [profile-<profile1name>]
36  * name : <name>
37  * description : <description> #optional
38  * format : <format>
39  * preset : <preset>
40  *
41  * [streamprofile-<id>]
42  * parent : <encodingprofile.name>[,<encodingprofile.name>..]
43  * type : <type> # "audio", "video", "text"
44  * format : <format>
45  * preset : <preset>
46  * restriction : <restriction>
47  * presence : <presence>
48  * pass : <pass>
49  * variableframerate : <variableframerate>
50  *  */
51
52 #define GST_ENCODING_TARGET_HEADER "_gstencodingtarget_"
53
54 struct _GstEncodingTarget
55 {
56   GstMiniObject parent;
57
58   gchar *name;
59   gchar *category;
60   gchar *description;
61   GList *profiles;
62
63   /*< private > */
64   gchar *keyfile;
65 };
66
67 G_DEFINE_TYPE (GstEncodingTarget, gst_encoding_target, GST_TYPE_MINI_OBJECT);
68
69 static void
70 gst_encoding_target_init (GstEncodingTarget * target)
71 {
72   /* Nothing to initialize */
73 }
74
75 static void
76 gst_encoding_target_finalize (GstEncodingTarget * target)
77 {
78   GST_DEBUG ("Finalizing");
79
80   if (target->name)
81     g_free (target->name);
82   if (target->category)
83     g_free (target->category);
84   if (target->description)
85     g_free (target->description);
86
87   g_list_foreach (target->profiles, (GFunc) gst_mini_object_unref, NULL);
88   g_list_free (target->profiles);
89 }
90
91 static void
92 gst_encoding_target_class_init (GstMiniObjectClass * klass)
93 {
94   klass->finalize =
95       (GstMiniObjectFinalizeFunction) gst_encoding_target_finalize;
96 }
97
98 /**
99  * gst_encoding_target_get_name:
100  * @target: a #GstEncodingTarget
101  *
102  * Since: 0.10.32
103  *
104  * Returns: The name of the @target.
105  */
106 const gchar *
107 gst_encoding_target_get_name (GstEncodingTarget * target)
108 {
109   return target->name;
110 }
111
112 /**
113  * gst_encoding_target_get_category:
114  * @target: a #GstEncodingTarget
115  *
116  * Since: 0.10.32
117  *
118  * Returns: The category of the @target.
119  */
120 const gchar *
121 gst_encoding_target_get_category (GstEncodingTarget * target)
122 {
123   return target->category;
124 }
125
126 /**
127  * gst_encoding_target_get_description:
128  * @target: a #GstEncodingTarget
129  *
130  * Since: 0.10.32
131  *
132  * Returns: The description of the @target.
133  */
134 const gchar *
135 gst_encoding_target_get_description (GstEncodingTarget * target)
136 {
137   return target->description;
138 }
139
140 /**
141  * gst_encoding_target_get_profiles:
142  * @target: a #GstEncodingTarget
143  *
144  * Since: 0.10.32
145  *
146  * Returns: A list of #GstEncodingProfile(s) this @target handles.
147  */
148 const GList *
149 gst_encoding_target_get_profiles (GstEncodingTarget * target)
150 {
151   return target->profiles;
152 }
153
154 static inline gboolean
155 validate_name (const gchar * name)
156 {
157   guint i, len;
158
159   len = strlen (name);
160   if (len == 0)
161     return FALSE;
162
163   /* First character can only be a lower case ASCII character */
164   if (!g_ascii_isalpha (name[0]) || !g_ascii_islower (name[0]))
165     return FALSE;
166
167   /* All following characters can only by:
168    * either a lower case ASCII character
169    * or an hyphen
170    * or a numeric */
171   for (i = 1; i < len; i++) {
172     /* if uppercase ASCII letter, return */
173     if (g_ascii_isupper (name[i]))
174       return FALSE;
175     /* if a digit, continue */
176     if (g_ascii_isdigit (name[i]))
177       continue;
178     /* if an hyphen, continue */
179     if (name[i] == '-')
180       continue;
181     /* remaining should only be ascii letters */
182     if (!g_ascii_isalpha (name[i]))
183       return FALSE;
184   }
185
186   return TRUE;
187 }
188
189 /**
190  * gst_encoding_target_new:
191  * @name: The name of the target.
192  * @category: The name of the category to which this @target belongs.
193  * @description: A description of #GstEncodingTarget in the current locale.
194  * @profiles: A #GList of #GstEncodingProfile.
195  *
196  * Creates a new #GstEncodingTarget.
197  *
198  * The name and category can only consist of lowercase ASCII letters for the
199  * first character, followed by either lowercase ASCII letters, digits or
200  * hyphens ('-').
201  *
202  * Since: 0.10.32
203  *
204  * Returns: The newly created #GstEncodingTarget or %NULL if there was an 
205  * error.
206  */
207
208 GstEncodingTarget *
209 gst_encoding_target_new (const gchar * name, const gchar * category,
210     const gchar * description, const GList * profiles)
211 {
212   GstEncodingTarget *res;
213
214   g_return_val_if_fail (name != NULL, NULL);
215   g_return_val_if_fail (category != NULL, NULL);
216   g_return_val_if_fail (description != NULL, NULL);
217
218   /* Validate name */
219   if (!validate_name (name))
220     goto invalid_name;
221   if (!validate_name (category))
222     goto invalid_category;
223
224   res = (GstEncodingTarget *) gst_mini_object_new (GST_TYPE_ENCODING_TARGET);
225   res->name = g_strdup (name);
226   res->category = g_strdup (category);
227   res->description = g_strdup (description);
228
229   while (profiles) {
230     GstEncodingProfile *prof = (GstEncodingProfile *) profiles->data;
231
232     res->profiles =
233         g_list_append (res->profiles, gst_encoding_profile_ref (prof));
234     profiles = profiles->next;
235   }
236
237   return res;
238
239 invalid_name:
240   {
241     GST_ERROR ("Invalid name for encoding category : '%s'", name);
242     return NULL;
243   }
244
245 invalid_category:
246   {
247     GST_ERROR ("Invalid category for encoding category : '%s'", category);
248     return NULL;
249   }
250 }
251
252 /**
253  * gst_encoding_target_add_profile:
254  * @target: the #GstEncodingTarget to add a profile to
255  * @profile: the #GstEncodingProfile to add
256  *
257  * Adds the given @profile to the @target.
258  *
259  * The @target will steal a reference to the @profile. If you wish to use
260  * the profile after calling this method, you should increase its reference
261  * count.
262  *
263  * Since: 0.10.32
264  *
265  * Returns: %TRUE if the profile was added, else %FALSE.
266  **/
267
268 gboolean
269 gst_encoding_target_add_profile (GstEncodingTarget * target,
270     GstEncodingProfile * profile)
271 {
272   GList *tmp;
273
274   g_return_val_if_fail (GST_IS_ENCODING_TARGET (target), FALSE);
275   g_return_val_if_fail (GST_IS_ENCODING_PROFILE (profile), FALSE);
276
277   /* Make sure profile isn't already controlled by this target */
278   for (tmp = target->profiles; tmp; tmp = tmp->next) {
279     GstEncodingProfile *prof = (GstEncodingProfile *) tmp->data;
280
281     if (!g_strcmp0 (gst_encoding_profile_get_name (profile),
282             gst_encoding_profile_get_name (prof))) {
283       GST_WARNING ("Profile already present in target");
284       return FALSE;
285     }
286   }
287
288   target->profiles = g_list_append (target->profiles, profile);
289
290   return TRUE;
291 }
292
293 static gboolean
294 serialize_stream_profiles (GKeyFile * out, GstEncodingProfile * sprof,
295     const gchar * profilename, guint id)
296 {
297   gchar *sprofgroupname;
298   gchar *tmpc;
299   const GstCaps *format, *restriction;
300   const gchar *preset, *name, *description;
301
302   sprofgroupname = g_strdup_printf ("streamprofile-%s-%d", profilename, id);
303
304   /* Write the parent profile */
305   g_key_file_set_value (out, sprofgroupname, "parent", profilename);
306
307   g_key_file_set_value (out, sprofgroupname, "type",
308       gst_encoding_profile_get_type_nick (sprof));
309
310   format = gst_encoding_profile_get_format (sprof);
311   if (format) {
312     tmpc = gst_caps_to_string (format);
313     g_key_file_set_value (out, sprofgroupname, "format", tmpc);
314     g_free (tmpc);
315   }
316
317   name = gst_encoding_profile_get_name (sprof);
318   if (name)
319     g_key_file_set_string (out, sprofgroupname, "name", name);
320
321   description = gst_encoding_profile_get_description (sprof);
322   if (description)
323     g_key_file_set_string (out, sprofgroupname, "description", description);
324
325   preset = gst_encoding_profile_get_preset (sprof);
326   if (preset)
327     g_key_file_set_string (out, sprofgroupname, "preset", preset);
328
329   restriction = gst_encoding_profile_get_restriction (sprof);
330   if (restriction) {
331     tmpc = gst_caps_to_string (restriction);
332     g_key_file_set_value (out, sprofgroupname, "restriction", tmpc);
333     g_free (tmpc);
334   }
335   g_key_file_set_integer (out, sprofgroupname, "presence",
336       gst_encoding_profile_get_presence (sprof));
337
338   if (GST_IS_ENCODING_VIDEO_PROFILE (sprof)) {
339     GstEncodingVideoProfile *vp = (GstEncodingVideoProfile *) sprof;
340
341     g_key_file_set_integer (out, sprofgroupname, "pass",
342         gst_encoding_video_profile_get_pass (vp));
343     g_key_file_set_boolean (out, sprofgroupname, "variableframerate",
344         gst_encoding_video_profile_get_variableframerate (vp));
345   }
346
347   g_free (sprofgroupname);
348   return TRUE;
349 }
350
351 /* Serialize the top-level profiles
352  * Note: They don't have to be containerprofiles */
353 static gboolean
354 serialize_encoding_profile (GKeyFile * out, GstEncodingProfile * prof)
355 {
356   gchar *profgroupname;
357   const GList *tmp;
358   guint i;
359   const gchar *profname, *profdesc, *profpreset, *proftype;
360   const GstCaps *profformat, *profrestriction;
361
362   profname = gst_encoding_profile_get_name (prof);
363   profdesc = gst_encoding_profile_get_description (prof);
364   profformat = gst_encoding_profile_get_format (prof);
365   profpreset = gst_encoding_profile_get_preset (prof);
366   proftype = gst_encoding_profile_get_type_nick (prof);
367   profrestriction = gst_encoding_profile_get_restriction (prof);
368
369   profgroupname = g_strdup_printf ("profile-%s", profname);
370
371   g_key_file_set_string (out, profgroupname, "name", profname);
372
373   g_key_file_set_value (out, profgroupname, "type",
374       gst_encoding_profile_get_type_nick (prof));
375
376   if (profdesc)
377     g_key_file_set_locale_string (out, profgroupname, "description",
378         setlocale (LC_ALL, NULL), profdesc);
379   if (profformat) {
380     gchar *tmpc = gst_caps_to_string (profformat);
381     g_key_file_set_string (out, profgroupname, "format", tmpc);
382     g_free (tmpc);
383   }
384   if (profpreset)
385     g_key_file_set_string (out, profgroupname, "preset", profpreset);
386
387   /* stream profiles */
388   if (GST_IS_ENCODING_CONTAINER_PROFILE (prof)) {
389     for (tmp =
390         gst_encoding_container_profile_get_profiles
391         (GST_ENCODING_CONTAINER_PROFILE (prof)), i = 0; tmp;
392         tmp = tmp->next, i++) {
393       GstEncodingProfile *sprof = (GstEncodingProfile *) tmp->data;
394
395       if (!serialize_stream_profiles (out, sprof, profname, i))
396         return FALSE;
397     }
398   }
399   g_free (profgroupname);
400   return TRUE;
401 }
402
403 static gboolean
404 serialize_target (GKeyFile * out, GstEncodingTarget * target)
405 {
406   GList *tmp;
407
408   g_key_file_set_string (out, GST_ENCODING_TARGET_HEADER, "name", target->name);
409   g_key_file_set_string (out, GST_ENCODING_TARGET_HEADER, "category",
410       target->category);
411   g_key_file_set_string (out, GST_ENCODING_TARGET_HEADER, "description",
412       target->description);
413
414   for (tmp = target->profiles; tmp; tmp = tmp->next) {
415     GstEncodingProfile *prof = (GstEncodingProfile *) tmp->data;
416     if (!serialize_encoding_profile (out, prof))
417       return FALSE;
418   }
419
420   return TRUE;
421 }
422
423 /**
424  * parse_encoding_profile:
425  * @in: a #GKeyFile
426  * @parentprofilename: the parent profile name (including 'profile-' or 'streamprofile-' header)
427  * @profilename: the profile name group to parse
428  * @nbgroups: the number of top-level groups
429  * @groups: the top-level groups
430  */
431 static GstEncodingProfile *
432 parse_encoding_profile (GKeyFile * in, gchar * parentprofilename,
433     gchar * profilename, gsize nbgroups, gchar ** groups)
434 {
435   GstEncodingProfile *sprof = NULL;
436   gchar **parent;
437   gchar *proftype, *format, *preset, *restriction, *pname, *description;
438   GstCaps *formatcaps = NULL;
439   GstCaps *restrictioncaps = NULL;
440   gboolean variableframerate;
441   gint pass, presence;
442   gsize i, nbencprofiles;
443
444   GST_DEBUG ("parentprofilename : %s , profilename : %s",
445       parentprofilename, profilename);
446
447   if (parentprofilename) {
448     gboolean found = FALSE;
449
450     parent =
451         g_key_file_get_string_list (in, profilename, "parent",
452         &nbencprofiles, NULL);
453     if (!parent || !nbencprofiles) {
454       return NULL;
455     }
456
457     /* Check if this streamprofile is used in <profilename> */
458     for (i = 0; i < nbencprofiles; i++) {
459       if (!g_strcmp0 (parent[i], parentprofilename)) {
460         found = TRUE;
461         break;
462       }
463     }
464     g_strfreev (parent);
465
466     if (!found) {
467       GST_DEBUG ("Stream profile '%s' isn't used in profile '%s'",
468           profilename, parentprofilename);
469       return NULL;
470     }
471   }
472
473   pname = g_key_file_get_value (in, profilename, "name", NULL);
474
475   /* First try to get localized description */
476   description =
477       g_key_file_get_locale_string (in, profilename, "description",
478       setlocale (LC_ALL, NULL), NULL);
479   if (description == NULL)
480     description = g_key_file_get_value (in, profilename, "description", NULL);
481
482   /* Parse the remaining fields */
483   proftype = g_key_file_get_value (in, profilename, "type", NULL);
484   if (!proftype) {
485     GST_WARNING ("Missing 'type' field for streamprofile %s", profilename);
486     return NULL;
487   }
488
489   format = g_key_file_get_value (in, profilename, "format", NULL);
490   if (format) {
491     formatcaps = gst_caps_from_string (format);
492     g_free (format);
493   }
494
495   preset = g_key_file_get_value (in, profilename, "preset", NULL);
496
497   restriction = g_key_file_get_value (in, profilename, "restriction", NULL);
498   if (restriction) {
499     restrictioncaps = gst_caps_from_string (restriction);
500     g_free (restriction);
501   }
502
503   presence = g_key_file_get_integer (in, profilename, "presence", NULL);
504   pass = g_key_file_get_integer (in, profilename, "pass", NULL);
505   variableframerate =
506       g_key_file_get_boolean (in, profilename, "variableframerate", NULL);
507
508   /* Build the streamprofile ! */
509   if (!g_strcmp0 (proftype, "container")) {
510     GstEncodingProfile *pprof;
511
512     sprof =
513         (GstEncodingProfile *) gst_encoding_container_profile_new (pname,
514         description, formatcaps, preset);
515     /* Now look for the stream profiles */
516     for (i = 0; i < nbgroups; i++) {
517       if (!g_ascii_strncasecmp (groups[i], "streamprofile-", 13)) {
518         pprof = parse_encoding_profile (in, pname, groups[i], nbgroups, groups);
519         if (pprof) {
520           gst_encoding_container_profile_add_profile (
521               (GstEncodingContainerProfile *) sprof, pprof);
522         }
523       }
524     }
525   } else if (!g_strcmp0 (proftype, "video")) {
526     sprof =
527         (GstEncodingProfile *) gst_encoding_video_profile_new (formatcaps,
528         preset, restrictioncaps, presence);
529     gst_encoding_video_profile_set_variableframerate ((GstEncodingVideoProfile
530             *) sprof, variableframerate);
531     gst_encoding_video_profile_set_pass ((GstEncodingVideoProfile *) sprof,
532         pass);
533   } else if (!g_strcmp0 (proftype, "audio")) {
534     sprof =
535         (GstEncodingProfile *) gst_encoding_audio_profile_new (formatcaps,
536         preset, restrictioncaps, presence);
537   } else
538     GST_ERROR ("Unknown profile format '%s'", proftype);
539
540   if (restrictioncaps)
541     gst_caps_unref (restrictioncaps);
542   if (formatcaps)
543     gst_caps_unref (formatcaps);
544
545   if (pname)
546     g_free (pname);
547   if (description)
548     g_free (description);
549   if (preset)
550     g_free (preset);
551   if (proftype)
552     g_free (proftype);
553
554   return sprof;
555 }
556
557 static GstEncodingTarget *
558 parse_keyfile (GKeyFile * in, gchar * targetname, gchar * categoryname,
559     gchar * description)
560 {
561   GstEncodingTarget *res = NULL;
562   GstEncodingProfile *prof;
563   gchar **groups;
564   gsize i, nbgroups;
565
566   res = gst_encoding_target_new (targetname, categoryname, description, NULL);
567
568   /* Figure out the various profiles */
569   groups = g_key_file_get_groups (in, &nbgroups);
570   for (i = 0; i < nbgroups; i++) {
571     if (!g_ascii_strncasecmp (groups[i], "profile-", 8)) {
572       prof = parse_encoding_profile (in, NULL, groups[i], nbgroups, groups);
573       if (prof)
574         gst_encoding_target_add_profile (res, prof);
575     }
576   }
577
578   g_strfreev (groups);
579
580   if (targetname)
581     g_free (targetname);
582   if (categoryname)
583     g_free (categoryname);
584   if (description)
585     g_free (description);
586
587   return res;
588 }
589
590 static GKeyFile *
591 load_file_and_read_header (const gchar * path, gchar ** targetname,
592     gchar ** categoryname, gchar ** description, GError ** error)
593 {
594   GKeyFile *in;
595   gboolean res;
596
597   in = g_key_file_new ();
598
599   GST_DEBUG ("path:%s", path);
600
601   res =
602       g_key_file_load_from_file (in, path,
603       G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS, error);
604   if (!res || error != NULL)
605     goto load_error;
606
607   *targetname =
608       g_key_file_get_value (in, GST_ENCODING_TARGET_HEADER, "name", error);
609   if (!*targetname)
610     goto empty_name;
611
612   *categoryname =
613       g_key_file_get_value (in, GST_ENCODING_TARGET_HEADER, "category", NULL);
614   *description =
615       g_key_file_get_value (in, GST_ENCODING_TARGET_HEADER, "description",
616       NULL);
617
618   return in;
619
620 load_error:
621   {
622     GST_WARNING ("Unable to read GstEncodingTarget file %s: %s",
623         path, (*error)->message);
624     g_key_file_free (in);
625     return NULL;
626   }
627
628 empty_name:
629   {
630     GST_WARNING ("Wrong header in file %s: %s", path, (*error)->message);
631     g_key_file_free (in);
632     return NULL;
633   }
634 }
635
636 /**
637  * gst_encoding_target_load_from:
638  * @path: The file to load the #GstEncodingTarget from
639  * @error: If an error occured, this field will be filled in.
640  *
641  * Opens the provided file and returns the contained #GstEncodingTarget.
642  *
643  * Since: 0.10.32
644  *
645  * Returns: The #GstEncodingTarget contained in the file, else %NULL
646  */
647
648 GstEncodingTarget *
649 gst_encoding_target_load_from (const gchar * path, GError ** error)
650 {
651   GKeyFile *in;
652   gchar *targetname, *categoryname, *description;
653   GstEncodingTarget *res = NULL;
654
655   in = load_file_and_read_header (path, &targetname, &categoryname,
656       &description, error);
657   if (!in)
658     goto beach;
659
660   res = parse_keyfile (in, targetname, categoryname, description);
661
662   g_key_file_free (in);
663
664 beach:
665   return res;
666 }
667
668 /**
669  * gst_encoding_target_load:
670  * @name: the name of the #GstEncodingTarget to load.
671  * @error: If an error occured, this field will be filled in.
672  *
673  * Searches for the #GstEncodingTarget with the given name, loads it
674  * and returns it.
675  *
676  * Warning: NOT IMPLEMENTED.
677  *
678  * Since: 0.10.32
679  *
680  * Returns: The #GstEncodingTarget if available, else %NULL
681  */
682
683 GstEncodingTarget *
684 gst_encoding_target_load (const gchar * name, GError ** error)
685 {
686   /* FIXME : IMPLEMENT */
687   return NULL;
688 }
689
690 /**
691  * gst_encoding_target_save:
692  * @target: a #GstEncodingTarget
693  * @error: If an error occured, this field will be filled in.
694  *
695  * Saves the @target to the default location.
696  *
697  * Warning: NOT IMPLEMENTED.
698  *
699  * Since: 0.10.32
700  *
701  * Returns: %TRUE if the target was correctly saved, else %FALSE.
702  **/
703
704 gboolean
705 gst_encoding_target_save (GstEncodingTarget * target, GError ** error)
706 {
707   g_return_val_if_fail (GST_IS_ENCODING_TARGET (target), FALSE);
708
709   /* FIXME : IMPLEMENT */
710   return FALSE;
711 }
712
713 /**
714  * gst_encoding_target_save_to:
715  * @target: a #GstEncodingTarget
716  * @path: the location to store the @target at.
717  * @error: If an error occured, this field will be filled in.
718  *
719  * Saves the @target to the provided location.
720  *
721  * Since: 0.10.32
722  *
723  * Returns: %TRUE if the target was correctly saved, else %FALSE.
724  **/
725
726 gboolean
727 gst_encoding_target_save_to (GstEncodingTarget * target, const gchar * path,
728     GError ** error)
729 {
730   GKeyFile *out;
731   gchar *data;
732   gsize data_size;
733
734   g_return_val_if_fail (GST_IS_ENCODING_TARGET (target), FALSE);
735   g_return_val_if_fail (path != NULL, FALSE);
736
737   /* FIXME : Check path is valid and writable
738    * FIXME : Strip out profiles already present in system target */
739
740   /* Get unique name... */
741
742   /* Create output GKeyFile */
743   out = g_key_file_new ();
744
745   if (!serialize_target (out, target))
746     goto serialize_failure;
747
748   if (!(data = g_key_file_to_data (out, &data_size, error)))
749     goto convert_failed;
750
751   if (!g_file_set_contents (path, data, data_size, error))
752     goto write_failed;
753
754   g_key_file_free (out);
755   g_free (data);
756
757   return TRUE;
758
759 serialize_failure:
760   {
761     GST_ERROR ("Failure serializing target");
762     g_key_file_free (out);
763     return FALSE;
764   }
765
766 convert_failed:
767   {
768     GST_ERROR ("Failure converting keyfile: %s", (*error)->message);
769     g_key_file_free (out);
770     g_free (data);
771     return FALSE;
772   }
773
774 write_failed:
775   {
776     GST_ERROR ("Unable to write file %s: %s", path, (*error)->message);
777     g_key_file_free (out);
778     g_free (data);
779     return FALSE;
780   }
781 }