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