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