encoding-target: Do not unconditionally break when searching for a target
[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
894       if (target)
895         break;
896     }
897     g_strfreev (encoding_target_dirs);
898     if (target)
899       goto done;
900   }
901
902   /* Try from local profiles */
903
904   tldir =
905       g_build_filename (g_get_user_data_dir (), "gstreamer-" GST_API_VERSION,
906       GST_ENCODING_TARGET_DIRECTORY, NULL);
907   target = gst_encoding_target_subload (tldir, category, lfilename, error);
908   g_free (tldir);
909
910   if (target == NULL) {
911     /* Try from system-wide profiles */
912     tldir =
913         g_build_filename (GST_DATADIR, "gstreamer-" GST_API_VERSION,
914         GST_ENCODING_TARGET_DIRECTORY, NULL);
915     target = gst_encoding_target_subload (tldir, category, lfilename, error);
916     g_free (tldir);
917   }
918
919 done:
920   g_free (lfilename);
921
922   return target;
923
924 invalid_name:
925   {
926     GST_ERROR ("Invalid name for encoding target : '%s'", name);
927     return NULL;
928   }
929 invalid_category:
930   {
931     GST_ERROR ("Invalid name for encoding category : '%s'", category);
932     return NULL;
933   }
934 }
935
936 /**
937  * gst_encoding_target_save_to_file:
938  * @target: a #GstEncodingTarget
939  * @filepath: the location to store the @target at.
940  * @error: If an error occured, this field will be filled in.
941  *
942  * Saves the @target to the provided file location.
943  *
944  * Returns: %TRUE if the target was correctly saved, else %FALSE.
945  **/
946
947 gboolean
948 gst_encoding_target_save_to_file (GstEncodingTarget * target,
949     const gchar * filepath, GError ** error)
950 {
951   GKeyFile *out;
952   gchar *data;
953   gsize data_size;
954
955   g_return_val_if_fail (GST_IS_ENCODING_TARGET (target), FALSE);
956   g_return_val_if_fail (filepath != NULL, FALSE);
957
958   /* FIXME : Check filepath is valid and writable
959    * FIXME : Strip out profiles already present in system target */
960
961   /* Get unique name... */
962
963   /* Create output GKeyFile */
964   out = g_key_file_new ();
965
966   if (!serialize_target (out, target))
967     goto serialize_failure;
968
969   if (!(data = g_key_file_to_data (out, &data_size, error)))
970     goto convert_failed;
971
972   if (!g_file_set_contents (filepath, data, data_size, error))
973     goto write_failed;
974
975   g_key_file_free (out);
976   g_free (data);
977
978   return TRUE;
979
980 serialize_failure:
981   {
982     GST_ERROR ("Failure serializing target");
983     g_key_file_free (out);
984     return FALSE;
985   }
986
987 convert_failed:
988   {
989     GST_ERROR ("Failure converting keyfile: %s", (*error)->message);
990     g_key_file_free (out);
991     g_free (data);
992     return FALSE;
993   }
994
995 write_failed:
996   {
997     GST_ERROR ("Unable to write file %s: %s", filepath, (*error)->message);
998     g_key_file_free (out);
999     g_free (data);
1000     return FALSE;
1001   }
1002 }
1003
1004 /**
1005  * gst_encoding_target_save:
1006  * @target: a #GstEncodingTarget
1007  * @error: If an error occured, this field will be filled in.
1008  *
1009  * Saves the @target to a default user-local directory.
1010  *
1011  * Returns: %TRUE if the target was correctly saved, else %FALSE.
1012  **/
1013
1014 gboolean
1015 gst_encoding_target_save (GstEncodingTarget * target, GError ** error)
1016 {
1017   gchar *filename;
1018   gchar *lfilename;
1019   gchar *dirname;
1020
1021   g_return_val_if_fail (GST_IS_ENCODING_TARGET (target), FALSE);
1022   g_return_val_if_fail (target->category != NULL, FALSE);
1023
1024   lfilename = g_strdup_printf ("%s" GST_ENCODING_TARGET_SUFFIX, target->name);
1025   dirname =
1026       g_build_filename (g_get_user_data_dir (), "gstreamer-" GST_API_VERSION,
1027       GST_ENCODING_TARGET_DIRECTORY, target->category, NULL);
1028   errno = 0;
1029   if (g_mkdir_with_parents (dirname, 0755)) {
1030     GST_ERROR_OBJECT (target, "Could not create directory to save %s into: %s",
1031         target->name, g_strerror (errno));
1032
1033     return FALSE;
1034   }
1035   filename = g_build_filename (dirname, lfilename, NULL);
1036   g_free (dirname);
1037   g_free (lfilename);
1038
1039   gst_encoding_target_save_to_file (target, filename, error);
1040   g_free (filename);
1041
1042   return TRUE;
1043 }
1044
1045 static GList *
1046 get_categories (gchar * path)
1047 {
1048   GList *res = NULL;
1049   GDir *topdir;
1050   const gchar *subdirname;
1051
1052   topdir = g_dir_open (path, 0, NULL);
1053   if (G_UNLIKELY (topdir == NULL))
1054     return NULL;
1055
1056   while ((subdirname = g_dir_read_name (topdir))) {
1057     gchar *ltmp = g_build_filename (path, subdirname, NULL);
1058
1059     if (g_file_test (ltmp, G_FILE_TEST_IS_DIR)) {
1060       res = g_list_append (res, (gpointer) g_strdup (subdirname));
1061     }
1062     g_free (ltmp);
1063   }
1064
1065   g_dir_close (topdir);
1066
1067   return res;
1068 }
1069
1070 /**
1071  * gst_encoding_list_available_categories:
1072  *
1073  * Lists all #GstEncodingTarget categories present on disk.
1074  *
1075  * Returns: (transfer full) (element-type gchar*): A list
1076  * of #GstEncodingTarget categories.
1077  */
1078 GList *
1079 gst_encoding_list_available_categories (void)
1080 {
1081   GList *res = NULL;
1082   GList *tmp1, *tmp2;
1083   gchar *topdir;
1084
1085   /* First try user-local categories */
1086   topdir =
1087       g_build_filename (g_get_user_data_dir (), "gstreamer-" GST_API_VERSION,
1088       GST_ENCODING_TARGET_DIRECTORY, NULL);
1089   res = get_categories (topdir);
1090   g_free (topdir);
1091
1092   /* Extend with system-wide categories */
1093   topdir = g_build_filename (GST_DATADIR, "gstreamer-" GST_API_VERSION,
1094       GST_ENCODING_TARGET_DIRECTORY, NULL);
1095   tmp1 = get_categories (topdir);
1096   g_free (topdir);
1097
1098   for (tmp2 = tmp1; tmp2; tmp2 = tmp2->next) {
1099     gchar *name = (gchar *) tmp2->data;
1100     if (!g_list_find_custom (res, name, (GCompareFunc) g_strcmp0))
1101       res = g_list_append (res, (gpointer) name);
1102     else
1103       g_free (name);
1104   }
1105   g_free (tmp1);
1106
1107   return res;
1108 }
1109
1110 static inline GList *
1111 sub_get_all_targets (gchar * subdir)
1112 {
1113   GList *res = NULL;
1114   const gchar *filename;
1115   GDir *dir;
1116   GstEncodingTarget *target;
1117
1118   dir = g_dir_open (subdir, 0, NULL);
1119   if (G_UNLIKELY (dir == NULL))
1120     return NULL;
1121
1122   while ((filename = g_dir_read_name (dir))) {
1123     gchar *fullname;
1124
1125     /* Only try files ending with .gstprofile */
1126     if (!g_str_has_suffix (filename, GST_ENCODING_TARGET_SUFFIX))
1127       continue;
1128
1129     fullname = g_build_filename (subdir, filename, NULL);
1130     target = gst_encoding_target_load_from_file (fullname, NULL);
1131     if (target) {
1132       res = g_list_append (res, target);
1133     } else
1134       GST_WARNING ("Failed to get a target from %s", fullname);
1135     g_free (fullname);
1136   }
1137   g_dir_close (dir);
1138
1139   return res;
1140 }
1141
1142 static inline GList *
1143 get_all_targets (gchar * topdir, const gchar * categoryname)
1144 {
1145   GList *res = NULL;
1146
1147   if (categoryname) {
1148     gchar *subdir = g_build_filename (topdir, categoryname, NULL);
1149     /* Try to open the directory */
1150     res = sub_get_all_targets (subdir);
1151     g_free (subdir);
1152   } else {
1153     const gchar *subdirname;
1154     GDir *dir = g_dir_open (topdir, 0, NULL);
1155
1156     if (G_UNLIKELY (dir == NULL))
1157       return NULL;
1158
1159     while ((subdirname = g_dir_read_name (dir))) {
1160       gchar *ltmp = g_build_filename (topdir, subdirname, NULL);
1161
1162       if (g_file_test (ltmp, G_FILE_TEST_IS_DIR)) {
1163         res = g_list_concat (res, sub_get_all_targets (ltmp));
1164       }
1165       g_free (ltmp);
1166     }
1167     g_dir_close (dir);
1168   }
1169
1170   return res;
1171 }
1172
1173 static guint
1174 compare_targets (const GstEncodingTarget * ta, const GstEncodingTarget * tb)
1175 {
1176   if (!g_strcmp0 (ta->name, tb->name)
1177       && !g_strcmp0 (ta->category, tb->category))
1178     return -1;
1179
1180   return 0;
1181 }
1182
1183 /**
1184  * gst_encoding_list_all_targets:
1185  * @categoryname: (allow-none): The category, for ex: #GST_ENCODING_CATEGORY_DEVICE.
1186  * Can be %NULL.
1187  *
1188  * List all available #GstEncodingTarget for the specified category, or all categories
1189  * if @categoryname is %NULL.
1190  *
1191  * Returns: (transfer full) (element-type GstEncodingTarget): The list of #GstEncodingTarget
1192  */
1193 GList *
1194 gst_encoding_list_all_targets (const gchar * categoryname)
1195 {
1196   GList *res;
1197   GList *tmp1, *tmp2;
1198   gchar *topdir;
1199
1200   /* Get user-locals */
1201   topdir =
1202       g_build_filename (g_get_user_data_dir (), "gstreamer-" GST_API_VERSION,
1203       GST_ENCODING_TARGET_DIRECTORY, NULL);
1204   res = get_all_targets (topdir, categoryname);
1205   g_free (topdir);
1206
1207   /* Get system-wide */
1208   topdir = g_build_filename (GST_DATADIR, "gstreamer-" GST_API_VERSION,
1209       GST_ENCODING_TARGET_DIRECTORY, NULL);
1210   tmp1 = get_all_targets (topdir, categoryname);
1211   g_free (topdir);
1212
1213   /* Merge system-wide targets */
1214   /* FIXME : We should merge the system-wide profiles into the user-locals
1215    * instead of stopping at identical target names */
1216   for (tmp2 = tmp1; tmp2; tmp2 = tmp2->next) {
1217     GstEncodingTarget *target = (GstEncodingTarget *) tmp2->data;
1218     if (g_list_find_custom (res, target, (GCompareFunc) compare_targets))
1219       gst_encoding_target_unref (target);
1220     else
1221       res = g_list_append (res, target);
1222   }
1223   g_list_free (tmp1);
1224
1225   return res;
1226 }