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