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