Added gst-plugins-base-subtitles0.10-0.10.34 for Meego Harmattan 1.2
[mafwsubrenderer] / gst-plugins-base-subtitles0.10 / 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 #ifdef HAVE_CONFIG_H
22 #include "config.h"
23 #endif
24
25 #include <locale.h>
26 #include <string.h>
27 #include "encoding-target.h"
28
29 /*
30  * File format
31  *
32  * GKeyFile style.
33  *
34  * [GStreamer Encoding Target]
35  * name : <name>
36  * category : <category>
37  * description : <description> #translatable
38  *
39  * [profile-<profile1name>]
40  * name : <name>
41  * description : <description> #optional
42  * format : <format>
43  * preset : <preset>
44  *
45  * [streamprofile-<id>]
46  * parent : <encodingprofile.name>[,<encodingprofile.name>..]
47  * type : <type> # "audio", "video", "text"
48  * format : <format>
49  * preset : <preset>
50  * restriction : <restriction>
51  * presence : <presence>
52  * pass : <pass>
53  * variableframerate : <variableframerate>
54  *  */
55
56 /*
57  * Location of profile files
58  *
59  * $GST_DATADIR/gstreamer-GST_MAJORMINOR/encoding-profile
60  * $HOME/gstreamer-GST_MAJORMINOR/encoding-profile
61  *
62  * Naming convention
63  *   $(target.category)/$(target.name).gep
64  *
65  * Naming restrictions:
66  *  lowercase ASCII letter for the first character
67  *  Same for all other characters + numerics + hyphens
68  */
69
70
71 #define GST_ENCODING_TARGET_HEADER "GStreamer Encoding Target"
72 #define GST_ENCODING_TARGET_DIRECTORY "encoding-profiles"
73 #define GST_ENCODING_TARGET_SUFFIX ".gep"
74
75 struct _GstEncodingTarget
76 {
77   GstMiniObject parent;
78
79   gchar *name;
80   gchar *category;
81   gchar *description;
82   GList *profiles;
83
84   /*< private > */
85   gchar *keyfile;
86 };
87
88 G_DEFINE_TYPE (GstEncodingTarget, gst_encoding_target, GST_TYPE_MINI_OBJECT);
89
90 static void
91 gst_encoding_target_init (GstEncodingTarget * target)
92 {
93   /* Nothing to initialize */
94 }
95
96 static void
97 gst_encoding_target_finalize (GstEncodingTarget * target)
98 {
99   GST_DEBUG ("Finalizing");
100
101   if (target->name)
102     g_free (target->name);
103   if (target->category)
104     g_free (target->category);
105   if (target->description)
106     g_free (target->description);
107
108   g_list_foreach (target->profiles, (GFunc) gst_mini_object_unref, NULL);
109   g_list_free (target->profiles);
110 }
111
112 static void
113 gst_encoding_target_class_init (GstMiniObjectClass * klass)
114 {
115   klass->finalize =
116       (GstMiniObjectFinalizeFunction) gst_encoding_target_finalize;
117 }
118
119 /**
120  * gst_encoding_target_get_name:
121  * @target: a #GstEncodingTarget
122  *
123  * Since: 0.10.32
124  *
125  * Returns: (transfer none): The name of the @target.
126  */
127 const gchar *
128 gst_encoding_target_get_name (GstEncodingTarget * target)
129 {
130   return target->name;
131 }
132
133 /**
134  * gst_encoding_target_get_category:
135  * @target: a #GstEncodingTarget
136  *
137  * Since: 0.10.32
138  *
139  * Returns: (transfer none): The category of the @target. For example:
140  * #GST_ENCODING_CATEGORY_DEVICE.
141  */
142 const gchar *
143 gst_encoding_target_get_category (GstEncodingTarget * target)
144 {
145   return target->category;
146 }
147
148 /**
149  * gst_encoding_target_get_description:
150  * @target: a #GstEncodingTarget
151  *
152  * Since: 0.10.32
153  *
154  * Returns: (transfer none): The description of the @target.
155  */
156 const gchar *
157 gst_encoding_target_get_description (GstEncodingTarget * target)
158 {
159   return target->description;
160 }
161
162 /**
163  * gst_encoding_target_get_profiles:
164  * @target: a #GstEncodingTarget
165  *
166  * Since: 0.10.32
167  *
168  * Returns: (transfer none) (element-type Gst.EncodingProfile): A list of
169  * #GstEncodingProfile(s) this @target handles.
170  */
171 const GList *
172 gst_encoding_target_get_profiles (GstEncodingTarget * target)
173 {
174   return target->profiles;
175 }
176
177 /**
178  * gst_encoding_target_get_profile:
179  * @target: a #GstEncodingTarget
180  * @name: the name of the profile to retrieve
181  *
182  * Since: 0.10.32
183  *
184  * Returns: (transfer full): The matching #GstEncodingProfile, or %NULL.
185  */
186 GstEncodingProfile *
187 gst_encoding_target_get_profile (GstEncodingTarget * target, const gchar * name)
188 {
189   GList *tmp;
190
191   g_return_val_if_fail (GST_IS_ENCODING_TARGET (target), NULL);
192   g_return_val_if_fail (name != NULL, NULL);
193
194   for (tmp = target->profiles; tmp; tmp = tmp->next) {
195     GstEncodingProfile *tprof = (GstEncodingProfile *) tmp->data;
196
197     if (!g_strcmp0 (gst_encoding_profile_get_name (tprof), name)) {
198       gst_encoding_profile_ref (tprof);
199       return tprof;
200     }
201   }
202
203   return NULL;
204 }
205
206 static inline gboolean
207 validate_name (const gchar * name)
208 {
209   guint i, len;
210
211   len = strlen (name);
212   if (len == 0)
213     return FALSE;
214
215   /* First character can only be a lower case ASCII character */
216   if (!g_ascii_isalpha (name[0]) || !g_ascii_islower (name[0]))
217     return FALSE;
218
219   /* All following characters can only by:
220    * either a lower case ASCII character
221    * or an hyphen
222    * or a numeric */
223   for (i = 1; i < len; i++) {
224     /* if uppercase ASCII letter, return */
225     if (g_ascii_isupper (name[i]))
226       return FALSE;
227     /* if a digit, continue */
228     if (g_ascii_isdigit (name[i]))
229       continue;
230     /* if an hyphen, continue */
231     if (name[i] == '-')
232       continue;
233     /* remaining should only be ascii letters */
234     if (!g_ascii_isalpha (name[i]))
235       return FALSE;
236   }
237
238   return TRUE;
239 }
240
241 /**
242  * gst_encoding_target_new:
243  * @name: The name of the target.
244  * @category: (transfer none): The name of the category to which this @target
245  * belongs. For example: #GST_ENCODING_CATEGORY_DEVICE.
246  * @description: (transfer none): A description of #GstEncodingTarget in the
247  * current locale.
248  * @profiles: (transfer none) (element-type Gst.EncodingProfile): A #GList of
249  * #GstEncodingProfile.
250  *
251  * Creates a new #GstEncodingTarget.
252  *
253  * The name and category can only consist of lowercase ASCII letters for the
254  * first character, followed by either lowercase ASCII letters, digits or
255  * hyphens ('-').
256  *
257  * The @category <emphasis>should</emphasis> be one of the existing
258  * well-defined categories, like #GST_ENCODING_CATEGORY_DEVICE, but it
259  * <emphasis>can</emphasis> be a application or user specific category if
260  * needed.
261  *
262  * Since: 0.10.32
263  *
264  * Returns: (transfer full): The newly created #GstEncodingTarget or %NULL if
265  * there was an error.
266  */
267
268 GstEncodingTarget *
269 gst_encoding_target_new (const gchar * name, const gchar * category,
270     const gchar * description, const GList * profiles)
271 {
272   GstEncodingTarget *res;
273
274   g_return_val_if_fail (name != NULL, NULL);
275   g_return_val_if_fail (category != NULL, NULL);
276   g_return_val_if_fail (description != NULL, NULL);
277
278   /* Validate name */
279   if (!validate_name (name))
280     goto invalid_name;
281   if (!validate_name (category))
282     goto invalid_category;
283
284   res = (GstEncodingTarget *) gst_mini_object_new (GST_TYPE_ENCODING_TARGET);
285   res->name = g_strdup (name);
286   res->category = g_strdup (category);
287   res->description = g_strdup (description);
288
289   while (profiles) {
290     GstEncodingProfile *prof = (GstEncodingProfile *) profiles->data;
291
292     res->profiles =
293         g_list_append (res->profiles, gst_encoding_profile_ref (prof));
294     profiles = profiles->next;
295   }
296
297   return res;
298
299 invalid_name:
300   {
301     GST_ERROR ("Invalid name for encoding target : '%s'", name);
302     return NULL;
303   }
304
305 invalid_category:
306   {
307     GST_ERROR ("Invalid name for encoding category : '%s'", category);
308     return NULL;
309   }
310 }
311
312 /**
313  * gst_encoding_target_add_profile:
314  * @target: the #GstEncodingTarget to add a profile to
315  * @profile: (transfer full): the #GstEncodingProfile to add
316  *
317  * Adds the given @profile to the @target. Each added profile must have
318  * a unique name within the profile.
319  *
320  * The @target will steal a reference to the @profile. If you wish to use
321  * the profile after calling this method, you should increase its reference
322  * count.
323  *
324  * Since: 0.10.32
325  *
326  * Returns: %TRUE if the profile was added, else %FALSE.
327  **/
328
329 gboolean
330 gst_encoding_target_add_profile (GstEncodingTarget * target,
331     GstEncodingProfile * profile)
332 {
333   GList *tmp;
334
335   g_return_val_if_fail (GST_IS_ENCODING_TARGET (target), FALSE);
336   g_return_val_if_fail (GST_IS_ENCODING_PROFILE (profile), FALSE);
337
338   /* Make sure profile isn't already controlled by this target */
339   for (tmp = target->profiles; tmp; tmp = tmp->next) {
340     GstEncodingProfile *prof = (GstEncodingProfile *) tmp->data;
341
342     if (!g_strcmp0 (gst_encoding_profile_get_name (profile),
343             gst_encoding_profile_get_name (prof))) {
344       GST_WARNING ("Profile already present in target");
345       return FALSE;
346     }
347   }
348
349   target->profiles = g_list_append (target->profiles, profile);
350
351   return TRUE;
352 }
353
354 static gboolean
355 serialize_stream_profiles (GKeyFile * out, GstEncodingProfile * sprof,
356     const gchar * profilename, guint id)
357 {
358   gchar *sprofgroupname;
359   gchar *tmpc;
360   const GstCaps *format, *restriction;
361   const gchar *preset, *name, *description;
362
363   sprofgroupname = g_strdup_printf ("streamprofile-%s-%d", profilename, id);
364
365   /* Write the parent profile */
366   g_key_file_set_value (out, sprofgroupname, "parent", profilename);
367
368   g_key_file_set_value (out, sprofgroupname, "type",
369       gst_encoding_profile_get_type_nick (sprof));
370
371   format = gst_encoding_profile_get_format (sprof);
372   if (format) {
373     tmpc = gst_caps_to_string (format);
374     g_key_file_set_value (out, sprofgroupname, "format", tmpc);
375     g_free (tmpc);
376   }
377
378   name = gst_encoding_profile_get_name (sprof);
379   if (name)
380     g_key_file_set_string (out, sprofgroupname, "name", name);
381
382   description = gst_encoding_profile_get_description (sprof);
383   if (description)
384     g_key_file_set_string (out, sprofgroupname, "description", description);
385
386   preset = gst_encoding_profile_get_preset (sprof);
387   if (preset)
388     g_key_file_set_string (out, sprofgroupname, "preset", preset);
389
390   restriction = gst_encoding_profile_get_restriction (sprof);
391   if (restriction) {
392     tmpc = gst_caps_to_string (restriction);
393     g_key_file_set_value (out, sprofgroupname, "restriction", tmpc);
394     g_free (tmpc);
395   }
396   g_key_file_set_integer (out, sprofgroupname, "presence",
397       gst_encoding_profile_get_presence (sprof));
398
399   if (GST_IS_ENCODING_VIDEO_PROFILE (sprof)) {
400     GstEncodingVideoProfile *vp = (GstEncodingVideoProfile *) sprof;
401
402     g_key_file_set_integer (out, sprofgroupname, "pass",
403         gst_encoding_video_profile_get_pass (vp));
404     g_key_file_set_boolean (out, sprofgroupname, "variableframerate",
405         gst_encoding_video_profile_get_variableframerate (vp));
406   }
407
408   g_free (sprofgroupname);
409   return TRUE;
410 }
411
412 static gchar *
413 get_locale (void)
414 {
415   const char *loc = NULL;
416   gchar *ret;
417
418 #ifdef ENABLE_NLS
419 #if defined(LC_MESSAGES)
420   loc = setlocale (LC_MESSAGES, NULL);
421   GST_LOG ("LC_MESSAGES: %s", GST_STR_NULL (loc));
422 #elif defined(LC_ALL)
423   loc = setlocale (LC_ALL, NULL);
424   GST_LOG ("LC_ALL: %s", GST_STR_NULL (loc));
425 #else
426   GST_LOG ("Neither LC_ALL nor LC_MESSAGES defined");
427 #endif
428 #else /* !ENABLE_NLS */
429   GST_LOG ("i18n disabled");
430 #endif
431
432   if (loc == NULL || g_ascii_strncasecmp (loc, "en", 2) == 0)
433     return NULL;
434
435   /* en_GB.UTF-8 => en */
436   ret = g_ascii_strdown (loc, -1);
437   ret = g_strcanon (ret, "abcdefghijklmnopqrstuvwxyz", '\0');
438   GST_LOG ("using locale: %s", ret);
439   return ret;
440 }
441
442 /* Serialize the top-level profiles
443  * Note: They don't have to be containerprofiles */
444 static gboolean
445 serialize_encoding_profile (GKeyFile * out, GstEncodingProfile * prof)
446 {
447   gchar *profgroupname;
448   const GList *tmp;
449   guint i;
450   const gchar *profname, *profdesc, *profpreset, *proftype;
451   const GstCaps *profformat;
452
453   profname = gst_encoding_profile_get_name (prof);
454   profdesc = gst_encoding_profile_get_description (prof);
455   profformat = gst_encoding_profile_get_format (prof);
456   profpreset = gst_encoding_profile_get_preset (prof);
457   proftype = gst_encoding_profile_get_type_nick (prof);
458
459   profgroupname = g_strdup_printf ("profile-%s", profname);
460
461   g_key_file_set_string (out, profgroupname, "name", profname);
462
463   g_key_file_set_value (out, profgroupname, "type", proftype);
464
465   if (profdesc) {
466     gchar *locale;
467
468     locale = get_locale ();
469     if (locale != NULL) {
470       g_key_file_set_locale_string (out, profgroupname, "description",
471           locale, profdesc);
472       g_free (locale);
473     } else {
474       g_key_file_set_string (out, profgroupname, "description", profdesc);
475     }
476   }
477   if (profformat) {
478     gchar *tmpc = gst_caps_to_string (profformat);
479     g_key_file_set_string (out, profgroupname, "format", tmpc);
480     g_free (tmpc);
481   }
482   if (profpreset)
483     g_key_file_set_string (out, profgroupname, "preset", profpreset);
484
485   /* stream profiles */
486   if (GST_IS_ENCODING_CONTAINER_PROFILE (prof)) {
487     for (tmp =
488         gst_encoding_container_profile_get_profiles
489         (GST_ENCODING_CONTAINER_PROFILE (prof)), i = 0; tmp;
490         tmp = tmp->next, i++) {
491       GstEncodingProfile *sprof = (GstEncodingProfile *) tmp->data;
492
493       if (!serialize_stream_profiles (out, sprof, profname, i))
494         return FALSE;
495     }
496   }
497   g_free (profgroupname);
498   return TRUE;
499 }
500
501 static gboolean
502 serialize_target (GKeyFile * out, GstEncodingTarget * target)
503 {
504   GList *tmp;
505
506   g_key_file_set_string (out, GST_ENCODING_TARGET_HEADER, "name", target->name);
507   g_key_file_set_string (out, GST_ENCODING_TARGET_HEADER, "category",
508       target->category);
509   g_key_file_set_string (out, GST_ENCODING_TARGET_HEADER, "description",
510       target->description);
511
512   for (tmp = target->profiles; tmp; tmp = tmp->next) {
513     GstEncodingProfile *prof = (GstEncodingProfile *) tmp->data;
514     if (!serialize_encoding_profile (out, prof))
515       return FALSE;
516   }
517
518   return TRUE;
519 }
520
521 /**
522  * parse_encoding_profile:
523  * @in: a #GKeyFile
524  * @parentprofilename: the parent profile name (including 'profile-' or 'streamprofile-' header)
525  * @profilename: the profile name group to parse
526  * @nbgroups: the number of top-level groups
527  * @groups: the top-level groups
528  */
529 static GstEncodingProfile *
530 parse_encoding_profile (GKeyFile * in, gchar * parentprofilename,
531     gchar * profilename, gsize nbgroups, gchar ** groups)
532 {
533   GstEncodingProfile *sprof = NULL;
534   gchar **parent;
535   gchar *proftype, *format, *preset, *restriction, *pname, *description;
536   GstCaps *formatcaps = NULL;
537   GstCaps *restrictioncaps = NULL;
538   gboolean variableframerate;
539   gint pass, presence;
540   gsize i, nbencprofiles;
541
542   GST_DEBUG ("parentprofilename : %s , profilename : %s",
543       parentprofilename, profilename);
544
545   if (parentprofilename) {
546     gboolean found = FALSE;
547
548     parent =
549         g_key_file_get_string_list (in, profilename, "parent",
550         &nbencprofiles, NULL);
551     if (!parent || !nbencprofiles) {
552       return NULL;
553     }
554
555     /* Check if this streamprofile is used in <profilename> */
556     for (i = 0; i < nbencprofiles; i++) {
557       if (!g_strcmp0 (parent[i], parentprofilename)) {
558         found = TRUE;
559         break;
560       }
561     }
562     g_strfreev (parent);
563
564     if (!found) {
565       GST_DEBUG ("Stream profile '%s' isn't used in profile '%s'",
566           profilename, parentprofilename);
567       return NULL;
568     }
569   }
570
571   pname = g_key_file_get_value (in, profilename, "name", NULL);
572
573   /* First try to get localized description */
574   {
575     gchar *locale;
576
577     locale = get_locale ();
578     if (locale != NULL) {
579       /* will try to fall back to untranslated string if no translation found */
580       description = g_key_file_get_locale_string (in, profilename,
581           "description", locale, NULL);
582       g_free (locale);
583     } else {
584       description =
585           g_key_file_get_string (in, profilename, "description", NULL);
586     }
587   }
588
589   /* Note: a missing description is normal for non-container profiles */
590   if (description == NULL) {
591     GST_LOG ("Missing 'description' field for streamprofile %s", profilename);
592   }
593
594   /* Parse the remaining fields */
595   proftype = g_key_file_get_value (in, profilename, "type", NULL);
596   if (!proftype) {
597     GST_WARNING ("Missing 'type' field for streamprofile %s", profilename);
598     return NULL;
599   }
600
601   format = g_key_file_get_value (in, profilename, "format", NULL);
602   if (format) {
603     formatcaps = gst_caps_from_string (format);
604     g_free (format);
605   }
606
607   preset = g_key_file_get_value (in, profilename, "preset", NULL);
608
609   restriction = g_key_file_get_value (in, profilename, "restriction", NULL);
610   if (restriction) {
611     restrictioncaps = gst_caps_from_string (restriction);
612     g_free (restriction);
613   }
614
615   presence = g_key_file_get_integer (in, profilename, "presence", NULL);
616   pass = g_key_file_get_integer (in, profilename, "pass", NULL);
617   variableframerate =
618       g_key_file_get_boolean (in, profilename, "variableframerate", NULL);
619
620   /* Build the streamprofile ! */
621   if (!g_strcmp0 (proftype, "container")) {
622     GstEncodingProfile *pprof;
623
624     sprof =
625         (GstEncodingProfile *) gst_encoding_container_profile_new (pname,
626         description, formatcaps, preset);
627     /* Now look for the stream profiles */
628     for (i = 0; i < nbgroups; i++) {
629       if (!g_ascii_strncasecmp (groups[i], "streamprofile-", 13)) {
630         pprof = parse_encoding_profile (in, pname, groups[i], nbgroups, groups);
631         if (pprof) {
632           gst_encoding_container_profile_add_profile (
633               (GstEncodingContainerProfile *) sprof, pprof);
634         }
635       }
636     }
637   } else if (!g_strcmp0 (proftype, "video")) {
638     sprof =
639         (GstEncodingProfile *) gst_encoding_video_profile_new (formatcaps,
640         preset, restrictioncaps, presence);
641     gst_encoding_video_profile_set_variableframerate ((GstEncodingVideoProfile
642             *) sprof, variableframerate);
643     gst_encoding_video_profile_set_pass ((GstEncodingVideoProfile *) sprof,
644         pass);
645   } else if (!g_strcmp0 (proftype, "audio")) {
646     sprof =
647         (GstEncodingProfile *) gst_encoding_audio_profile_new (formatcaps,
648         preset, restrictioncaps, presence);
649   } else
650     GST_ERROR ("Unknown profile format '%s'", proftype);
651
652   if (restrictioncaps)
653     gst_caps_unref (restrictioncaps);
654   if (formatcaps)
655     gst_caps_unref (formatcaps);
656
657   if (pname)
658     g_free (pname);
659   if (description)
660     g_free (description);
661   if (preset)
662     g_free (preset);
663   if (proftype)
664     g_free (proftype);
665
666   return sprof;
667 }
668
669 static GstEncodingTarget *
670 parse_keyfile (GKeyFile * in, gchar * targetname, gchar * categoryname,
671     gchar * description)
672 {
673   GstEncodingTarget *res = NULL;
674   GstEncodingProfile *prof;
675   gchar **groups;
676   gsize i, nbgroups;
677
678   res = gst_encoding_target_new (targetname, categoryname, description, NULL);
679
680   /* Figure out the various profiles */
681   groups = g_key_file_get_groups (in, &nbgroups);
682   for (i = 0; i < nbgroups; i++) {
683     if (!g_ascii_strncasecmp (groups[i], "profile-", 8)) {
684       prof = parse_encoding_profile (in, NULL, groups[i], nbgroups, groups);
685       if (prof)
686         gst_encoding_target_add_profile (res, prof);
687     }
688   }
689
690   g_strfreev (groups);
691
692   if (targetname)
693     g_free (targetname);
694   if (categoryname)
695     g_free (categoryname);
696   if (description)
697     g_free (description);
698
699   return res;
700 }
701
702 static GKeyFile *
703 load_file_and_read_header (const gchar * path, gchar ** targetname,
704     gchar ** categoryname, gchar ** description, GError ** error)
705 {
706   GKeyFile *in;
707   gboolean res;
708   GError *key_error = NULL;
709
710   g_return_val_if_fail (error == NULL || *error == NULL, NULL);
711
712   in = g_key_file_new ();
713
714   GST_DEBUG ("path:%s", path);
715
716   res =
717       g_key_file_load_from_file (in, path,
718       G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS, &key_error);
719   if (!res || key_error != NULL)
720     goto load_error;
721
722   key_error = NULL;
723   *targetname =
724       g_key_file_get_value (in, GST_ENCODING_TARGET_HEADER, "name", &key_error);
725   if (!*targetname)
726     goto empty_name;
727
728   *categoryname =
729       g_key_file_get_value (in, GST_ENCODING_TARGET_HEADER, "category", NULL);
730   *description =
731       g_key_file_get_value (in, GST_ENCODING_TARGET_HEADER, "description",
732       NULL);
733
734   return in;
735
736 load_error:
737   {
738     GST_WARNING ("Unable to read GstEncodingTarget file %s: %s",
739         path, key_error->message);
740     g_propagate_error (error, key_error);
741     g_key_file_free (in);
742     return NULL;
743   }
744
745 empty_name:
746   {
747     GST_WARNING ("Wrong header in file %s: %s", path, key_error->message);
748     g_propagate_error (error, key_error);
749     g_key_file_free (in);
750     return NULL;
751   }
752 }
753
754 /**
755  * gst_encoding_target_load_from_file:
756  * @filepath: The file location to load the #GstEncodingTarget from
757  * @error: If an error occured, this field will be filled in.
758  *
759  * Opens the provided file and returns the contained #GstEncodingTarget.
760  *
761  * Since: 0.10.32
762  *
763  * Returns: (transfer full): The #GstEncodingTarget contained in the file, else
764  * %NULL
765  */
766
767 GstEncodingTarget *
768 gst_encoding_target_load_from_file (const gchar * filepath, GError ** error)
769 {
770   GKeyFile *in;
771   gchar *targetname, *categoryname, *description;
772   GstEncodingTarget *res = NULL;
773
774   in = load_file_and_read_header (filepath, &targetname, &categoryname,
775       &description, error);
776   if (!in)
777     goto beach;
778
779   res = parse_keyfile (in, targetname, categoryname, description);
780
781   g_key_file_free (in);
782
783 beach:
784   return res;
785 }
786
787 /*
788  * returned list contents must be freed
789  */
790 static GList *
791 get_matching_filenames (gchar * path, gchar * filename)
792 {
793   GList *res = NULL;
794   GDir *topdir;
795   const gchar *subdirname;
796
797   topdir = g_dir_open (path, 0, NULL);
798   if (G_UNLIKELY (topdir == NULL))
799     return NULL;
800
801   while ((subdirname = g_dir_read_name (topdir))) {
802     gchar *ltmp = g_build_filename (path, subdirname, NULL);
803
804     if (g_file_test (ltmp, G_FILE_TEST_IS_DIR)) {
805       gchar *tmp = g_build_filename (path, subdirname, filename, NULL);
806       /* Test to see if we have a file named like that in that directory */
807       if (g_file_test (tmp, G_FILE_TEST_EXISTS))
808         res = g_list_append (res, tmp);
809       else
810         g_free (tmp);
811     }
812     g_free (ltmp);
813   }
814
815   g_dir_close (topdir);
816
817   return res;
818 }
819
820 static GstEncodingTarget *
821 gst_encoding_target_subload (gchar * path, const gchar * category,
822     gchar * lfilename, GError ** error)
823 {
824   GstEncodingTarget *target = NULL;
825
826   if (category) {
827     gchar *filename;
828
829     filename = g_build_filename (path, category, lfilename, NULL);
830     target = gst_encoding_target_load_from_file (filename, error);
831     g_free (filename);
832   } else {
833     GList *tmp, *tries = get_matching_filenames (path, lfilename);
834
835     /* Try to find a file named %s.gstprofile in any subdirectories */
836     for (tmp = tries; tmp; tmp = tmp->next) {
837       target = gst_encoding_target_load_from_file ((gchar *) tmp->data, NULL);
838       if (target)
839         break;
840     }
841     g_list_foreach (tries, (GFunc) g_free, NULL);
842     if (tries)
843       g_list_free (tries);
844   }
845
846   return target;
847 }
848
849 /**
850  * gst_encoding_target_load:
851  * @name: the name of the #GstEncodingTarget to load.
852  * @category: (allow-none): the name of the target category, like
853  * #GST_ENCODING_CATEGORY_DEVICE. Can be %NULL
854  * @error: If an error occured, this field will be filled in.
855  *
856  * Searches for the #GstEncodingTarget with the given name, loads it
857  * and returns it.
858  *
859  * If the category name is specified only targets from that category will be
860  * searched for.
861  *
862  * Since: 0.10.32
863  *
864  * Returns: (transfer full): The #GstEncodingTarget if available, else %NULL.
865  */
866 GstEncodingTarget *
867 gst_encoding_target_load (const gchar * name, const gchar * category,
868     GError ** error)
869 {
870   gchar *lfilename, *tldir;
871   GstEncodingTarget *target = NULL;
872
873   g_return_val_if_fail (name != NULL, NULL);
874
875   if (!validate_name (name))
876     goto invalid_name;
877
878   if (category && !validate_name (category))
879     goto invalid_category;
880
881   lfilename = g_strdup_printf ("%s" GST_ENCODING_TARGET_SUFFIX, name);
882
883   /* Try from local profiles */
884   tldir =
885       g_build_filename (g_get_home_dir (), ".gstreamer-" GST_MAJORMINOR,
886       GST_ENCODING_TARGET_DIRECTORY, NULL);
887   target = gst_encoding_target_subload (tldir, category, lfilename, error);
888   g_free (tldir);
889
890   if (target == NULL) {
891     /* Try from system-wide profiles */
892     tldir =
893         g_build_filename (GST_DATADIR, "gstreamer-" GST_MAJORMINOR,
894         GST_ENCODING_TARGET_DIRECTORY, NULL);
895     target = gst_encoding_target_subload (tldir, category, lfilename, error);
896     g_free (tldir);
897   }
898
899   g_free (lfilename);
900
901   return target;
902
903 invalid_name:
904   {
905     GST_ERROR ("Invalid name for encoding target : '%s'", name);
906     return NULL;
907   }
908 invalid_category:
909   {
910     GST_ERROR ("Invalid name for encoding category : '%s'", category);
911     return NULL;
912   }
913 }
914
915 /**
916  * gst_encoding_target_save_to_file:
917  * @target: a #GstEncodingTarget
918  * @filepath: the location to store the @target at.
919  * @error: If an error occured, this field will be filled in.
920  *
921  * Saves the @target to the provided file location.
922  *
923  * Since: 0.10.32
924  *
925  * Returns: %TRUE if the target was correctly saved, else %FALSE.
926  **/
927
928 gboolean
929 gst_encoding_target_save_to_file (GstEncodingTarget * target,
930     const gchar * filepath, GError ** error)
931 {
932   GKeyFile *out;
933   gchar *data;
934   gsize data_size;
935
936   g_return_val_if_fail (GST_IS_ENCODING_TARGET (target), FALSE);
937   g_return_val_if_fail (filepath != NULL, FALSE);
938
939   /* FIXME : Check filepath is valid and writable
940    * FIXME : Strip out profiles already present in system target */
941
942   /* Get unique name... */
943
944   /* Create output GKeyFile */
945   out = g_key_file_new ();
946
947   if (!serialize_target (out, target))
948     goto serialize_failure;
949
950   if (!(data = g_key_file_to_data (out, &data_size, error)))
951     goto convert_failed;
952
953   if (!g_file_set_contents (filepath, data, data_size, error))
954     goto write_failed;
955
956   g_key_file_free (out);
957   g_free (data);
958
959   return TRUE;
960
961 serialize_failure:
962   {
963     GST_ERROR ("Failure serializing target");
964     g_key_file_free (out);
965     return FALSE;
966   }
967
968 convert_failed:
969   {
970     GST_ERROR ("Failure converting keyfile: %s", (*error)->message);
971     g_key_file_free (out);
972     g_free (data);
973     return FALSE;
974   }
975
976 write_failed:
977   {
978     GST_ERROR ("Unable to write file %s: %s", filepath, (*error)->message);
979     g_key_file_free (out);
980     g_free (data);
981     return FALSE;
982   }
983 }
984
985 /**
986  * gst_encoding_target_save:
987  * @target: a #GstEncodingTarget
988  * @error: If an error occured, this field will be filled in.
989  *
990  * Saves the @target to a default user-local directory.
991  *
992  * Since: 0.10.32
993  *
994  * Returns: %TRUE if the target was correctly saved, else %FALSE.
995  **/
996
997 gboolean
998 gst_encoding_target_save (GstEncodingTarget * target, GError ** error)
999 {
1000   gchar *filename;
1001   gchar *lfilename;
1002
1003   g_return_val_if_fail (GST_IS_ENCODING_TARGET (target), FALSE);
1004   g_return_val_if_fail (target->category != NULL, FALSE);
1005
1006   lfilename = g_strdup_printf ("%s" GST_ENCODING_TARGET_SUFFIX, target->name);
1007   filename =
1008       g_build_filename (g_get_home_dir (), ".gstreamer-" GST_MAJORMINOR,
1009       GST_ENCODING_TARGET_DIRECTORY, target->category, lfilename, NULL);
1010   g_free (lfilename);
1011
1012   gst_encoding_target_save_to_file (target, filename, error);
1013   g_free (filename);
1014
1015   return TRUE;
1016 }
1017
1018 static GList *
1019 get_categories (gchar * path)
1020 {
1021   GList *res = NULL;
1022   GDir *topdir;
1023   const gchar *subdirname;
1024
1025   topdir = g_dir_open (path, 0, NULL);
1026   if (G_UNLIKELY (topdir == NULL))
1027     return NULL;
1028
1029   while ((subdirname = g_dir_read_name (topdir))) {
1030     gchar *ltmp = g_build_filename (path, subdirname, NULL);
1031
1032     if (g_file_test (ltmp, G_FILE_TEST_IS_DIR)) {
1033       res = g_list_append (res, (gpointer) g_strdup (subdirname));
1034     }
1035     g_free (ltmp);
1036   }
1037
1038   g_dir_close (topdir);
1039
1040   return res;
1041 }
1042
1043 /**
1044  * gst_encoding_list_available_categories:
1045  *
1046  * Lists all #GstEncodingTarget categories present on disk.
1047  *
1048  * Returns: (transfer full) (element-type gchar*): A list
1049  * of #GstEncodingTarget categories.
1050  *
1051  * Since: 0.10.32
1052  */
1053 GList *
1054 gst_encoding_list_available_categories (void)
1055 {
1056   GList *res = NULL;
1057   GList *tmp1, *tmp2;
1058   gchar *topdir;
1059
1060   /* First try user-local categories */
1061   topdir = g_build_filename (g_get_home_dir (), ".gstreamer-" GST_MAJORMINOR,
1062       GST_ENCODING_TARGET_DIRECTORY, NULL);
1063   res = get_categories (topdir);
1064   g_free (topdir);
1065
1066   /* Extend with system-wide categories */
1067   topdir = g_build_filename (GST_DATADIR, "gstreamer-" GST_MAJORMINOR,
1068       GST_ENCODING_TARGET_DIRECTORY, NULL);
1069   tmp1 = get_categories (topdir);
1070   g_free (topdir);
1071
1072   for (tmp2 = tmp1; tmp2; tmp2 = tmp2->next) {
1073     gchar *name = (gchar *) tmp2->data;
1074     if (!g_list_find_custom (res, name, (GCompareFunc) g_strcmp0))
1075       res = g_list_append (res, (gpointer) name);
1076     else
1077       g_free (name);
1078   }
1079   g_free (tmp1);
1080
1081   return res;
1082 }
1083
1084 static inline GList *
1085 sub_get_all_targets (gchar * subdir)
1086 {
1087   GList *res = NULL;
1088   const gchar *filename;
1089   GDir *dir;
1090   GstEncodingTarget *target;
1091
1092   dir = g_dir_open (subdir, 0, NULL);
1093   if (G_UNLIKELY (dir == NULL))
1094     return NULL;
1095
1096   while ((filename = g_dir_read_name (dir))) {
1097     gchar *fullname;
1098
1099     /* Only try files ending with .gstprofile */
1100     if (!g_str_has_suffix (filename, GST_ENCODING_TARGET_SUFFIX))
1101       continue;
1102
1103     fullname = g_build_filename (subdir, filename, NULL);
1104     target = gst_encoding_target_load_from_file (fullname, NULL);
1105     if (target) {
1106       res = g_list_append (res, target);
1107     } else
1108       GST_WARNING ("Failed to get a target from %s", fullname);
1109     g_free (fullname);
1110   }
1111   g_dir_close (dir);
1112
1113   return res;
1114 }
1115
1116 static inline GList *
1117 get_all_targets (gchar * topdir, const gchar * categoryname)
1118 {
1119   GList *res = NULL;
1120
1121   if (categoryname) {
1122     gchar *subdir = g_build_filename (topdir, categoryname, NULL);
1123     /* Try to open the directory */
1124     res = sub_get_all_targets (subdir);
1125     g_free (subdir);
1126   } else {
1127     const gchar *subdirname;
1128     GDir *dir = g_dir_open (topdir, 0, NULL);
1129
1130     if (G_UNLIKELY (dir == NULL))
1131       return NULL;
1132
1133     while ((subdirname = g_dir_read_name (dir))) {
1134       gchar *ltmp = g_build_filename (topdir, subdirname, NULL);
1135
1136       if (g_file_test (ltmp, G_FILE_TEST_IS_DIR)) {
1137         res = g_list_concat (res, sub_get_all_targets (ltmp));
1138       }
1139       g_free (ltmp);
1140     }
1141     g_dir_close (dir);
1142   }
1143
1144   return res;
1145 }
1146
1147 static guint
1148 compare_targets (const GstEncodingTarget * ta, const GstEncodingTarget * tb)
1149 {
1150   if (!g_strcmp0 (ta->name, tb->name)
1151       && !g_strcmp0 (ta->category, tb->category))
1152     return -1;
1153
1154   return 0;
1155 }
1156
1157 /**
1158  * gst_encoding_list_all_targets:
1159  * @categoryname: (allow-none): The category, for ex: #GST_ENCODING_CATEGORY_DEVICE.
1160  * Can be %NULL.
1161  *
1162  * List all available #GstEncodingTarget for the specified category, or all categories
1163  * if @categoryname is %NULL.
1164  *
1165  * Returns: (transfer full) (element-type GstEncodingTarget): The list of #GstEncodingTarget
1166  *
1167  * Since: 0.10.32
1168  */
1169 GList *
1170 gst_encoding_list_all_targets (const gchar * categoryname)
1171 {
1172   GList *res;
1173   GList *tmp1, *tmp2;
1174   gchar *topdir;
1175
1176   /* Get user-locals */
1177   topdir = g_build_filename (g_get_home_dir (), ".gstreamer-" GST_MAJORMINOR,
1178       GST_ENCODING_TARGET_DIRECTORY, NULL);
1179   res = get_all_targets (topdir, categoryname);
1180   g_free (topdir);
1181
1182   /* Get system-wide */
1183   topdir = g_build_filename (GST_DATADIR, "gstreamer-" GST_MAJORMINOR,
1184       GST_ENCODING_TARGET_DIRECTORY, NULL);
1185   tmp1 = get_all_targets (topdir, categoryname);
1186   g_free (topdir);
1187
1188   /* Merge system-wide targets */
1189   /* FIXME : We should merge the system-wide profiles into the user-locals
1190    * instead of stopping at identical target names */
1191   for (tmp2 = tmp1; tmp2; tmp2 = tmp2->next) {
1192     GstEncodingTarget *target = (GstEncodingTarget *) tmp2->data;
1193     if (g_list_find_custom (res, target, (GCompareFunc) compare_targets))
1194       gst_encoding_target_unref (target);
1195     else
1196       res = g_list_append (res, target);
1197   }
1198   g_list_free (tmp1);
1199
1200   return res;
1201 }