Coverage Report

Created: 2026-07-12 07:12

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