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