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