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