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