gstreamer/gst-libs/gst/pbutils/encoding-target.c

1304 lines
35 KiB
C

/* GStreamer encoding profile registry
* Copyright (C) 2010 Edward Hervey <edward.hervey@collabora.co.uk>
* (C) 2010 Nokia Corporation
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
/**
* SECTION:encoding-target
*
* On top of the notion of profiles, we implement the notion of EncodingTarget.
* Encoding Targets are basically a higher level of abstraction to define formats
* for specific target types. Those can define several GstEncodingProfiles with
* different names, for example one for transcoding in full HD, another one for
* low res, etc.. which are defined in the same encoding target.
*
* Basically if you want to encode a stream to send it to, say, youtube you should
* have a Youtube encoding target defined in the "online-service" category.
*
* ## Encoding target serialization format
*
* Encoding targets are serialized in a KeyFile like files.
*
* |[
* [GStreamer Encoding Target]
* name : <name>
* category : <category>
* \description : <description> #translatable
*
* [profile-<profile1name>]
* name : <name>
* \description : <description> #optional
* format : <format>
* preset : <preset>
*
* [streamprofile-<id>]
* parent : <encodingprofile.name>[,<encodingprofile.name>..]
* \type : <type> # "audio", "video", "text"
* format : <format>
* preset : <preset>
* restriction : <restriction>
* presence : <presence>
* pass : <pass>
* variableframerate : <variableframerate>
* ]|
*
* ## Location of encoding target files
*
* $GST_DATADIR/gstreamer-GST_API_VERSION/encoding-profile
* $HOME/gstreamer-GST_API_VERSION/encoding-profile
*
* There also is a GST_ENCODING_TARGET_PATH environment variable
* defining a list of folder containing encoding target files.
*
* ## Naming convention
*
* |[
* $(target.category)/$(target.name).gep
* ]|
*
* ## Naming restrictions:
*
* * lowercase ASCII letter for the first character
* * Same for all other characters + numerics + hyphens
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <locale.h>
#include <errno.h>
#include <string.h>
#include "encoding-target.h"
#include "pbutils-private.h"
/* Documented in encoding-profile.c */
#define GST_ENCODING_TARGET_HEADER "GStreamer Encoding Target"
#define GST_ENCODING_TARGET_DIRECTORY "encoding-profiles"
#define GST_ENCODING_TARGET_SUFFIX ".gep"
struct _GstEncodingTarget
{
GObject parent;
gchar *name;
gchar *category;
gchar *description;
gchar *path;
GList *profiles;
/*< private > */
gchar *keyfile;
};
G_DEFINE_TYPE (GstEncodingTarget, gst_encoding_target, G_TYPE_OBJECT);
static void
gst_encoding_target_init (GstEncodingTarget * target)
{
/* Nothing to initialize */
}
static void
gst_encoding_target_finalize (GObject * object)
{
GstEncodingTarget *target = (GstEncodingTarget *) object;
GST_DEBUG ("Finalizing");
g_free (target->name);
g_free (target->category);
g_free (target->description);
g_free (target->path);
g_list_foreach (target->profiles, (GFunc) g_object_unref, NULL);
g_list_free (target->profiles);
}
static void
gst_encoding_target_class_init (GObjectClass * klass)
{
klass->finalize = gst_encoding_target_finalize;
}
/**
* gst_encoding_target_get_name:
* @target: a #GstEncodingTarget
*
* Returns: (transfer none): The name of the @target.
*/
const gchar *
gst_encoding_target_get_name (GstEncodingTarget * target)
{
return target->name;
}
/**
* gst_encoding_target_get_category:
* @target: a #GstEncodingTarget
*
* Returns: (transfer none): The category of the @target. For example:
* #GST_ENCODING_CATEGORY_DEVICE.
*/
const gchar *
gst_encoding_target_get_category (GstEncodingTarget * target)
{
return target->category;
}
/**
* gst_encoding_target_get_description:
* @target: a #GstEncodingTarget
*
* Returns: (transfer none): The description of the @target.
*/
const gchar *
gst_encoding_target_get_description (GstEncodingTarget * target)
{
return target->description;
}
/**
* gst_encoding_target_get_path:
* @target: a #GstEncodingTarget
*
* Returns: (transfer none): The path to the @target file.
*
* Since: 1.18
*/
const gchar *
gst_encoding_target_get_path (GstEncodingTarget * target)
{
g_return_val_if_fail (GST_IS_ENCODING_TARGET (target), NULL);
return target->path;
}
/**
* gst_encoding_target_get_profiles:
* @target: a #GstEncodingTarget
*
* Returns: (transfer none) (element-type GstPbutils.EncodingProfile): A list of
* #GstEncodingProfile(s) this @target handles.
*/
const GList *
gst_encoding_target_get_profiles (GstEncodingTarget * target)
{
return target->profiles;
}
/**
* gst_encoding_target_get_profile:
* @target: a #GstEncodingTarget
* @name: the name of the profile to retrieve
*
* Returns: (transfer full): The matching #GstEncodingProfile, or %NULL.
*/
GstEncodingProfile *
gst_encoding_target_get_profile (GstEncodingTarget * target, const gchar * name)
{
GList *tmp;
g_return_val_if_fail (GST_IS_ENCODING_TARGET (target), NULL);
g_return_val_if_fail (name != NULL, NULL);
for (tmp = target->profiles; tmp; tmp = tmp->next) {
GstEncodingProfile *tprof = (GstEncodingProfile *) tmp->data;
if (!g_strcmp0 (gst_encoding_profile_get_name (tprof), name)) {
gst_encoding_profile_ref (tprof);
return tprof;
}
}
return NULL;
}
static inline gboolean
validate_name (const gchar * name)
{
guint i, len;
len = strlen (name);
if (len == 0)
return FALSE;
/* First character can only be a lower case ASCII character */
if (!g_ascii_isalpha (name[0]) || !g_ascii_islower (name[0]))
return FALSE;
/* All following characters can only by:
* either a lower case ASCII character
* or an hyphen
* or a numeric */
for (i = 1; i < len; i++) {
/* if uppercase ASCII letter, return */
if (g_ascii_isupper (name[i]))
return FALSE;
/* if a digit, continue */
if (g_ascii_isdigit (name[i]))
continue;
/* if an hyphen, continue */
if (name[i] == '-')
continue;
/* if an ';', continue (list delimiter) */
if (name[i] == ';') {
continue;
}
/* remaining should only be ascii letters */
if (!g_ascii_isalpha (name[i]))
return FALSE;
}
return TRUE;
}
/**
* gst_encoding_target_new:
* @name: The name of the target.
* @category: (transfer none): The name of the category to which this @target
* belongs. For example: #GST_ENCODING_CATEGORY_DEVICE.
* @description: (transfer none): A description of #GstEncodingTarget in the
* current locale.
* @profiles: (transfer none) (element-type GstPbutils.EncodingProfile): A #GList of
* #GstEncodingProfile.
*
* Creates a new #GstEncodingTarget.
*
* The name and category can only consist of lowercase ASCII letters for the
* first character, followed by either lowercase ASCII letters, digits or
* hyphens ('-').
*
* The @category *should* be one of the existing
* well-defined categories, like #GST_ENCODING_CATEGORY_DEVICE, but it
* *can* be a application or user specific category if
* needed.
*
* Returns: (transfer full): The newly created #GstEncodingTarget or %NULL if
* there was an error.
*/
GstEncodingTarget *
gst_encoding_target_new (const gchar * name, const gchar * category,
const gchar * description, const GList * profiles)
{
GstEncodingTarget *res;
g_return_val_if_fail (name != NULL, NULL);
g_return_val_if_fail (category != NULL, NULL);
g_return_val_if_fail (description != NULL, NULL);
/* Validate name */
if (!validate_name (name))
goto invalid_name;
if (category && !validate_name (category))
goto invalid_category;
res = (GstEncodingTarget *) g_object_new (GST_TYPE_ENCODING_TARGET, NULL);
res->name = g_strdup (name);
res->category = g_strdup (category);
res->description = g_strdup (description);
while (profiles) {
GstEncodingProfile *prof = (GstEncodingProfile *) profiles->data;
res->profiles =
g_list_append (res->profiles, gst_encoding_profile_ref (prof));
profiles = profiles->next;
}
return res;
invalid_name:
{
GST_ERROR ("Invalid name for encoding target : '%s'", name);
return NULL;
}
invalid_category:
{
GST_ERROR ("Invalid name for encoding category : '%s'", category);
return NULL;
}
}
/**
* gst_encoding_target_add_profile:
* @target: the #GstEncodingTarget to add a profile to
* @profile: (transfer full): the #GstEncodingProfile to add
*
* Adds the given @profile to the @target. Each added profile must have
* a unique name within the profile.
*
* The @target will steal a reference to the @profile. If you wish to use
* the profile after calling this method, you should increase its reference
* count.
*
* Returns: %TRUE if the profile was added, else %FALSE.
**/
gboolean
gst_encoding_target_add_profile (GstEncodingTarget * target,
GstEncodingProfile * profile)
{
GList *tmp;
g_return_val_if_fail (GST_IS_ENCODING_TARGET (target), FALSE);
g_return_val_if_fail (GST_IS_ENCODING_PROFILE (profile), FALSE);
/* Make sure profile isn't already controlled by this target */
for (tmp = target->profiles; tmp; tmp = tmp->next) {
GstEncodingProfile *prof = (GstEncodingProfile *) tmp->data;
if (!g_strcmp0 (gst_encoding_profile_get_name (profile),
gst_encoding_profile_get_name (prof))) {
GST_WARNING ("Profile already present in target");
return FALSE;
}
}
target->profiles = g_list_append (target->profiles, profile);
return TRUE;
}
static gboolean
serialize_stream_profiles (GKeyFile * out, GstEncodingProfile * sprof,
const gchar * profilename, guint id)
{
gchar *sprofgroupname;
gchar *tmpc;
GstCaps *format, *restriction;
const gchar *preset, *name, *description;
sprofgroupname = g_strdup_printf ("streamprofile-%s-%d", profilename, id);
/* Write the parent profile */
g_key_file_set_value (out, sprofgroupname, "parent", profilename);
g_key_file_set_value (out, sprofgroupname, "type",
gst_encoding_profile_get_type_nick (sprof));
format = gst_encoding_profile_get_format (sprof);
if (format) {
tmpc = gst_caps_to_string (format);
g_key_file_set_value (out, sprofgroupname, "format", tmpc);
g_free (tmpc);
}
name = gst_encoding_profile_get_name (sprof);
if (name)
g_key_file_set_string (out, sprofgroupname, "name", name);
description = gst_encoding_profile_get_description (sprof);
if (description)
g_key_file_set_string (out, sprofgroupname, "description", description);
preset = gst_encoding_profile_get_preset (sprof);
if (preset)
g_key_file_set_string (out, sprofgroupname, "preset", preset);
restriction = gst_encoding_profile_get_restriction (sprof);
if (restriction) {
tmpc = gst_caps_to_string (restriction);
g_key_file_set_value (out, sprofgroupname, "restriction", tmpc);
g_free (tmpc);
}
g_key_file_set_integer (out, sprofgroupname, "presence",
gst_encoding_profile_get_presence (sprof));
if (GST_IS_ENCODING_VIDEO_PROFILE (sprof)) {
GstEncodingVideoProfile *vp = (GstEncodingVideoProfile *) sprof;
g_key_file_set_integer (out, sprofgroupname, "pass",
gst_encoding_video_profile_get_pass (vp));
g_key_file_set_boolean (out, sprofgroupname, "variableframerate",
gst_encoding_video_profile_get_variableframerate (vp));
}
g_free (sprofgroupname);
if (format)
gst_caps_unref (format);
if (restriction)
gst_caps_unref (restriction);
return TRUE;
}
static gchar *
get_locale (void)
{
const char *loc = NULL;
gchar *ret;
gst_pb_utils_init_locale_text_domain ();
#ifdef ENABLE_NLS
#if defined(LC_MESSAGES)
loc = setlocale (LC_MESSAGES, NULL);
GST_LOG ("LC_MESSAGES: %s", GST_STR_NULL (loc));
#elif defined(LC_ALL)
loc = setlocale (LC_ALL, NULL);
GST_LOG ("LC_ALL: %s", GST_STR_NULL (loc));
#else
GST_LOG ("Neither LC_ALL nor LC_MESSAGES defined");
#endif
#else /* !ENABLE_NLS */
GST_LOG ("i18n disabled");
#endif
if (loc == NULL || g_ascii_strncasecmp (loc, "en", 2) == 0)
return NULL;
/* en_GB.UTF-8 => en */
ret = g_ascii_strdown (loc, -1);
ret = g_strcanon (ret, "abcdefghijklmnopqrstuvwxyz", '\0');
GST_LOG ("using locale: %s", ret);
return ret;
}
/* Serialize the top-level profiles
* Note: They don't have to be containerprofiles */
static gboolean
serialize_encoding_profile (GKeyFile * out, GstEncodingProfile * prof)
{
gchar *profgroupname;
const GList *tmp;
guint i;
const gchar *profname, *profdesc, *profpreset, *proftype;
GstCaps *profformat;
profname = gst_encoding_profile_get_name (prof);
profdesc = gst_encoding_profile_get_description (prof);
profformat = gst_encoding_profile_get_format (prof);
profpreset = gst_encoding_profile_get_preset (prof);
proftype = gst_encoding_profile_get_type_nick (prof);
profgroupname = g_strdup_printf ("profile-%s", profname);
g_key_file_set_string (out, profgroupname, "name", profname);
g_key_file_set_value (out, profgroupname, "type", proftype);
if (profdesc) {
gchar *locale;
locale = get_locale ();
if (locale != NULL) {
g_key_file_set_locale_string (out, profgroupname, "description",
locale, profdesc);
g_free (locale);
} else {
g_key_file_set_string (out, profgroupname, "description", profdesc);
}
}
if (profformat) {
gchar *tmpc = gst_caps_to_string (profformat);
g_key_file_set_string (out, profgroupname, "format", tmpc);
g_free (tmpc);
}
if (profpreset)
g_key_file_set_string (out, profgroupname, "preset", profpreset);
/* stream profiles */
if (GST_IS_ENCODING_CONTAINER_PROFILE (prof)) {
for (tmp =
gst_encoding_container_profile_get_profiles
(GST_ENCODING_CONTAINER_PROFILE (prof)), i = 0; tmp;
tmp = tmp->next, i++) {
GstEncodingProfile *sprof = (GstEncodingProfile *) tmp->data;
if (!serialize_stream_profiles (out, sprof, profname, i))
return FALSE;
}
}
if (profformat)
gst_caps_unref (profformat);
g_free (profgroupname);
return TRUE;
}
static gboolean
serialize_target (GKeyFile * out, GstEncodingTarget * target)
{
GList *tmp;
g_key_file_set_string (out, GST_ENCODING_TARGET_HEADER, "name", target->name);
g_key_file_set_string (out, GST_ENCODING_TARGET_HEADER, "category",
target->category);
g_key_file_set_string (out, GST_ENCODING_TARGET_HEADER, "description",
target->description);
for (tmp = target->profiles; tmp; tmp = tmp->next) {
GstEncodingProfile *prof = (GstEncodingProfile *) tmp->data;
if (!serialize_encoding_profile (out, prof))
return FALSE;
}
return TRUE;
}
/**
* parse_encoding_profile:
* @in: a #GKeyFile
* @parentprofilename: the parent profile name (including 'profile-' or 'streamprofile-' header)
* @profilename: the profile name group to parse
* @nbgroups: the number of top-level groups
* @groups: the top-level groups
*/
static GstEncodingProfile *
parse_encoding_profile (GKeyFile * in, gchar * parentprofilename,
gchar * profilename, gsize nbgroups, gchar ** groups)
{
GstEncodingProfile *sprof = NULL;
gchar **parent;
gchar *proftype, *format, *preset, *restriction, *pname, *description,
*locale;
GstCaps *formatcaps = NULL;
GstCaps *restrictioncaps = NULL;
gboolean variableframerate;
gint pass, presence;
gsize i, nbencprofiles;
GST_DEBUG ("parentprofilename : %s , profilename : %s",
parentprofilename, profilename);
if (parentprofilename) {
gboolean found = FALSE;
parent =
g_key_file_get_string_list (in, profilename, "parent",
&nbencprofiles, NULL);
if (!parent || !nbencprofiles) {
return NULL;
}
/* Check if this streamprofile is used in <profilename> */
for (i = 0; i < nbencprofiles; i++) {
if (!g_strcmp0 (parent[i], parentprofilename)) {
found = TRUE;
break;
}
}
g_strfreev (parent);
if (!found) {
GST_DEBUG ("Stream profile '%s' isn't used in profile '%s'",
profilename, parentprofilename);
return NULL;
}
}
pname = g_key_file_get_value (in, profilename, "name", NULL);
locale = get_locale ();
/* will try to fall back to untranslated string if no translation found */
description = g_key_file_get_locale_string (in, profilename,
"description", locale, NULL);
g_free (locale);
/* Note: a missing description is normal for non-container profiles */
if (description == NULL) {
GST_LOG ("Missing 'description' field for streamprofile %s", profilename);
}
/* Parse the remaining fields */
proftype = g_key_file_get_value (in, profilename, "type", NULL);
if (!proftype) {
GST_WARNING ("Missing 'type' field for streamprofile %s", profilename);
return NULL;
}
format = g_key_file_get_value (in, profilename, "format", NULL);
if (format) {
formatcaps = gst_caps_from_string (format);
g_free (format);
}
preset = g_key_file_get_value (in, profilename, "preset", NULL);
restriction = g_key_file_get_value (in, profilename, "restriction", NULL);
if (restriction) {
restrictioncaps = gst_caps_from_string (restriction);
g_free (restriction);
}
presence = g_key_file_get_integer (in, profilename, "presence", NULL);
pass = g_key_file_get_integer (in, profilename, "pass", NULL);
variableframerate =
g_key_file_get_boolean (in, profilename, "variableframerate", NULL);
/* Build the streamprofile ! */
if (!g_strcmp0 (proftype, "container")) {
GstEncodingProfile *pprof;
sprof =
(GstEncodingProfile *) gst_encoding_container_profile_new (pname,
description, formatcaps, preset);
/* Now look for the stream profiles */
for (i = 0; i < nbgroups; i++) {
if (!g_ascii_strncasecmp (groups[i], "streamprofile-", 13)) {
pprof = parse_encoding_profile (in, pname, groups[i], nbgroups, groups);
if (pprof) {
gst_encoding_container_profile_add_profile (
(GstEncodingContainerProfile *) sprof, pprof);
}
}
}
} else if (!g_strcmp0 (proftype, "video")) {
sprof =
(GstEncodingProfile *) gst_encoding_video_profile_new (formatcaps,
preset, restrictioncaps, presence);
gst_encoding_video_profile_set_variableframerate ((GstEncodingVideoProfile
*) sprof, variableframerate);
gst_encoding_video_profile_set_pass ((GstEncodingVideoProfile *) sprof,
pass);
gst_encoding_profile_set_name (sprof, pname);
gst_encoding_profile_set_description (sprof, description);
} else if (!g_strcmp0 (proftype, "audio")) {
sprof =
(GstEncodingProfile *) gst_encoding_audio_profile_new (formatcaps,
preset, restrictioncaps, presence);
gst_encoding_profile_set_name (sprof, pname);
gst_encoding_profile_set_description (sprof, description);
} else
GST_ERROR ("Unknown profile format '%s'", proftype);
if (restrictioncaps)
gst_caps_unref (restrictioncaps);
if (formatcaps)
gst_caps_unref (formatcaps);
g_free (pname);
g_free (description);
g_free (preset);
g_free (proftype);
return sprof;
}
static GstEncodingTarget *
parse_keyfile (GKeyFile * in, gchar * targetname, gchar * categoryname,
gchar * description)
{
GstEncodingTarget *res = NULL;
GstEncodingProfile *prof;
gchar **groups;
gsize i, nbgroups;
res = gst_encoding_target_new (targetname, categoryname, description, NULL);
/* Figure out the various profiles */
groups = g_key_file_get_groups (in, &nbgroups);
for (i = 0; i < nbgroups; i++) {
if (!g_ascii_strncasecmp (groups[i], "profile-", 8)) {
prof = parse_encoding_profile (in, NULL, groups[i], nbgroups, groups);
if (prof)
gst_encoding_target_add_profile (res, prof);
}
}
g_strfreev (groups);
g_free (targetname);
g_free (categoryname);
g_free (description);
return res;
}
static GKeyFile *
load_file_and_read_header (const gchar * path, gchar ** targetname,
gchar ** categoryname, gchar ** description, GError ** error)
{
GKeyFile *in;
gboolean res;
GError *key_error = NULL;
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
in = g_key_file_new ();
GST_DEBUG ("path:%s", path);
res =
g_key_file_load_from_file (in, path,
G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS, &key_error);
if (!res || key_error != NULL)
goto load_error;
key_error = NULL;
*targetname =
g_key_file_get_value (in, GST_ENCODING_TARGET_HEADER, "name", &key_error);
if (!*targetname)
goto empty_name;
*categoryname =
g_key_file_get_value (in, GST_ENCODING_TARGET_HEADER, "category", NULL);
*description =
g_key_file_get_value (in, GST_ENCODING_TARGET_HEADER, "description",
NULL);
return in;
load_error:
{
GST_WARNING ("Unable to read GstEncodingTarget file %s: %s",
path, key_error->message);
g_propagate_error (error, key_error);
g_key_file_free (in);
return NULL;
}
empty_name:
{
GST_WARNING ("Wrong header in file %s: %s", path, key_error->message);
g_propagate_error (error, key_error);
g_key_file_free (in);
return NULL;
}
}
/**
* gst_encoding_target_load_from_file:
* @filepath: (type filename): The file location to load the #GstEncodingTarget from
* @error: If an error occurred, this field will be filled in.
*
* Opens the provided file and returns the contained #GstEncodingTarget.
*
* Returns: (transfer full): The #GstEncodingTarget contained in the file, else
* %NULL
*/
GstEncodingTarget *
gst_encoding_target_load_from_file (const gchar * filepath, GError ** error)
{
GKeyFile *in;
gchar *targetname, *categoryname, *description;
GstEncodingTarget *res = NULL;
in = load_file_and_read_header (filepath, &targetname, &categoryname,
&description, error);
if (!in)
goto beach;
res = parse_keyfile (in, targetname, categoryname, description);
g_key_file_free (in);
beach:
return res;
}
/*
* returned list contents must be freed
*/
static GList *
get_matching_filenames (gchar * path, gchar * filename)
{
GList *res = NULL;
GDir *topdir;
const gchar *subdirname;
gchar *tmp;
topdir = g_dir_open (path, 0, NULL);
if (G_UNLIKELY (topdir == NULL))
return NULL;
tmp = g_build_filename (path, filename, NULL);
if (g_file_test (tmp, G_FILE_TEST_EXISTS))
res = g_list_append (res, tmp);
else
g_free (tmp);
while ((subdirname = g_dir_read_name (topdir))) {
gchar *ltmp = g_build_filename (path, subdirname, NULL);
if (g_file_test (ltmp, G_FILE_TEST_IS_DIR)) {
gchar *tmp = g_build_filename (path, subdirname, filename, NULL);
/* Test to see if we have a file named like that in that directory */
if (g_file_test (tmp, G_FILE_TEST_EXISTS))
res = g_list_append (res, tmp);
else
g_free (tmp);
}
g_free (ltmp);
}
g_dir_close (topdir);
return res;
}
static GstEncodingTarget *
gst_encoding_target_subload (gchar * path, const gchar * category,
gchar * lfilename, GError ** error)
{
GstEncodingTarget *target = NULL;
if (category) {
gchar *filename;
filename = g_build_filename (path, category, lfilename, NULL);
target = gst_encoding_target_load_from_file (filename, error);
g_free (filename);
} else {
GList *tmp, *tries = get_matching_filenames (path, lfilename);
/* Try to find a file named %s.gstprofile in any subdirectories */
for (tmp = tries; tmp; tmp = tmp->next) {
target = gst_encoding_target_load_from_file ((gchar *) tmp->data, NULL);
if (target)
break;
}
g_list_foreach (tries, (GFunc) g_free, NULL);
if (tries)
g_list_free (tries);
}
return target;
}
/**
* gst_encoding_target_load:
* @name: the name of the #GstEncodingTarget to load (automatically
* converted to lower case internally as capital letters are not
* valid for target names).
* @category: (allow-none): the name of the target category, like
* #GST_ENCODING_CATEGORY_DEVICE. Can be %NULL
* @error: If an error occurred, this field will be filled in.
*
* Searches for the #GstEncodingTarget with the given name, loads it
* and returns it.
*
* If the category name is specified only targets from that category will be
* searched for.
*
* Returns: (transfer full): The #GstEncodingTarget if available, else %NULL.
*/
GstEncodingTarget *
gst_encoding_target_load (const gchar * name, const gchar * category,
GError ** error)
{
gint i;
gchar *p, *lname, *lfilename = NULL, *tldir, **encoding_target_dirs;
const gchar *envvar;
GstEncodingTarget *target = NULL;
g_return_val_if_fail (name != NULL, NULL);
p = lname = g_str_to_ascii (name, NULL);
for (; *p; ++p)
*p = g_ascii_tolower (*p);
if (!validate_name (lname))
goto invalid_name;
if (category && !validate_name (category))
goto invalid_category;
lfilename = g_strdup_printf ("%s" GST_ENCODING_TARGET_SUFFIX, lname);
envvar = g_getenv ("GST_ENCODING_TARGET_PATH");
if (envvar) {
encoding_target_dirs = g_strsplit (envvar, G_SEARCHPATH_SEPARATOR_S, -1);
for (i = 0; encoding_target_dirs[i]; i++) {
target = gst_encoding_target_subload (encoding_target_dirs[i],
category, lfilename, error);
if (target)
break;
}
g_strfreev (encoding_target_dirs);
if (target)
goto done;
}
/* Try from local profiles */
tldir =
g_build_filename (g_get_user_data_dir (), "gstreamer-" GST_API_VERSION,
GST_ENCODING_TARGET_DIRECTORY, NULL);
target = gst_encoding_target_subload (tldir, category, lfilename, error);
g_free (tldir);
if (target == NULL) {
/* Try from system-wide profiles */
tldir =
g_build_filename (GST_DATADIR, "gstreamer-" GST_API_VERSION,
GST_ENCODING_TARGET_DIRECTORY, NULL);
target = gst_encoding_target_subload (tldir, category, lfilename, error);
g_free (tldir);
}
if (!target) {
GList *tmp, *targets = gst_encoding_list_all_targets (NULL);
for (tmp = targets; tmp; tmp = tmp->next) {
gint i;
GstEncodingTarget *tmptarget = tmp->data;
gchar **names = g_strsplit (tmptarget->name, ";", -1);
for (i = 0; names[i]; i++) {
if (!g_strcmp0 (names[i], lname) && (!category ||
!g_strcmp0 (tmptarget->category, category))) {
target = gst_object_ref (tmptarget);
break;
}
}
g_strfreev (names);
if (target)
break;
}
g_list_free_full (targets, gst_object_unref);
}
done:
g_free (lfilename);
g_free (lname);
return target;
invalid_name:
{
GST_INFO ("Invalid name for encoding target : '%s'", name);
goto done;
}
invalid_category:
{
GST_INFO ("Invalid name for encoding category : '%s'", category);
goto done;
}
}
/**
* gst_encoding_target_save_to_file:
* @target: a #GstEncodingTarget
* @filepath: (type filename): the location to store the @target at.
* @error: If an error occurred, this field will be filled in.
*
* Saves the @target to the provided file location.
*
* Returns: %TRUE if the target was correctly saved, else %FALSE.
**/
gboolean
gst_encoding_target_save_to_file (GstEncodingTarget * target,
const gchar * filepath, GError ** error)
{
GKeyFile *out;
gchar *data;
gsize data_size;
g_return_val_if_fail (GST_IS_ENCODING_TARGET (target), FALSE);
g_return_val_if_fail (filepath != NULL, FALSE);
/* FIXME : Check filepath is valid and writable
* FIXME : Strip out profiles already present in system target */
/* Get unique name... */
/* Create output GKeyFile */
out = g_key_file_new ();
if (!serialize_target (out, target))
goto serialize_failure;
if (!(data = g_key_file_to_data (out, &data_size, error)))
goto convert_failed;
if (!g_file_set_contents (filepath, data, data_size, error))
goto write_failed;
target->path = g_strdup (filepath);
g_key_file_free (out);
g_free (data);
return TRUE;
serialize_failure:
{
GST_ERROR ("Failure serializing target");
g_key_file_free (out);
return FALSE;
}
convert_failed:
{
GST_ERROR ("Failure converting keyfile: %s", (*error)->message);
g_key_file_free (out);
g_free (data);
return FALSE;
}
write_failed:
{
GST_ERROR ("Unable to write file %s: %s", filepath, (*error)->message);
g_key_file_free (out);
g_free (data);
return FALSE;
}
}
/**
* gst_encoding_target_save:
* @target: a #GstEncodingTarget
* @error: If an error occurred, this field will be filled in.
*
* Saves the @target to a default user-local directory.
*
* Returns: %TRUE if the target was correctly saved, else %FALSE.
**/
gboolean
gst_encoding_target_save (GstEncodingTarget * target, GError ** error)
{
gchar *filename;
gchar *lfilename;
gchar *dirname;
g_return_val_if_fail (GST_IS_ENCODING_TARGET (target), FALSE);
g_return_val_if_fail (target->category != NULL, FALSE);
lfilename = g_strdup_printf ("%s" GST_ENCODING_TARGET_SUFFIX, target->name);
dirname =
g_build_filename (g_get_user_data_dir (), "gstreamer-" GST_API_VERSION,
GST_ENCODING_TARGET_DIRECTORY, target->category, NULL);
errno = 0;
if (g_mkdir_with_parents (dirname, 0755)) {
GST_ERROR_OBJECT (target, "Could not create directory to save %s into: %s",
target->name, g_strerror (errno));
return FALSE;
}
filename = g_build_filename (dirname, lfilename, NULL);
g_free (dirname);
g_free (lfilename);
gst_encoding_target_save_to_file (target, filename, error);
g_free (filename);
return TRUE;
}
static GList *
get_categories (gchar * path)
{
GList *res = NULL;
GDir *topdir;
const gchar *subdirname;
topdir = g_dir_open (path, 0, NULL);
if (G_UNLIKELY (topdir == NULL))
return NULL;
while ((subdirname = g_dir_read_name (topdir))) {
gchar *ltmp = g_build_filename (path, subdirname, NULL);
if (g_file_test (ltmp, G_FILE_TEST_IS_DIR)) {
res = g_list_append (res, (gpointer) g_strdup (subdirname));
}
g_free (ltmp);
}
g_dir_close (topdir);
return res;
}
/**
* gst_encoding_list_available_categories:
*
* Lists all #GstEncodingTarget categories present on disk.
*
* Returns: (transfer full) (element-type gchar*): A list
* of #GstEncodingTarget categories.
*/
GList *
gst_encoding_list_available_categories (void)
{
GList *res = NULL;
GList *tmp1, *tmp2;
gchar *topdir;
/* First try user-local categories */
topdir =
g_build_filename (g_get_user_data_dir (), "gstreamer-" GST_API_VERSION,
GST_ENCODING_TARGET_DIRECTORY, NULL);
res = get_categories (topdir);
g_free (topdir);
/* Extend with system-wide categories */
topdir = g_build_filename (GST_DATADIR, "gstreamer-" GST_API_VERSION,
GST_ENCODING_TARGET_DIRECTORY, NULL);
tmp1 = get_categories (topdir);
g_free (topdir);
for (tmp2 = tmp1; tmp2; tmp2 = tmp2->next) {
gchar *name = (gchar *) tmp2->data;
if (!g_list_find_custom (res, name, (GCompareFunc) g_strcmp0))
res = g_list_append (res, (gpointer) name);
else
g_free (name);
}
g_list_free (tmp1);
return res;
}
static inline GList *
sub_get_all_targets (gchar * subdir)
{
GList *res = NULL;
const gchar *filename;
GDir *dir;
GstEncodingTarget *target;
dir = g_dir_open (subdir, 0, NULL);
if (G_UNLIKELY (dir == NULL))
return NULL;
while ((filename = g_dir_read_name (dir))) {
gchar *fullname;
/* Only try files ending with .gstprofile */
if (!g_str_has_suffix (filename, GST_ENCODING_TARGET_SUFFIX))
continue;
fullname = g_build_filename (subdir, filename, NULL);
target = gst_encoding_target_load_from_file (fullname, NULL);
if (target) {
target->path = fullname;
res = g_list_append (res, target);
} else
GST_WARNING ("Failed to get a target from %s", fullname);
}
g_dir_close (dir);
return res;
}
static inline GList *
get_all_targets (gchar * topdir, const gchar * categoryname)
{
GList *res = NULL;
if (categoryname) {
gchar *subdir = g_build_filename (topdir, categoryname, NULL);
/* Try to open the directory */
res = sub_get_all_targets (subdir);
g_free (subdir);
} else {
const gchar *subdirname;
GDir *dir = g_dir_open (topdir, 0, NULL);
if (G_UNLIKELY (dir == NULL))
return NULL;
while ((subdirname = g_dir_read_name (dir))) {
gchar *ltmp = g_build_filename (topdir, subdirname, NULL);
if (g_file_test (ltmp, G_FILE_TEST_IS_DIR)) {
res = g_list_concat (res, sub_get_all_targets (ltmp));
}
g_free (ltmp);
}
g_dir_close (dir);
}
return res;
}
static guint
compare_targets (const GstEncodingTarget * ta, const GstEncodingTarget * tb)
{
if (g_strcmp0 (ta->name, tb->name)
|| g_strcmp0 (ta->category, tb->category))
return -1;
return 0;
}
static GList *
merge_targets (GList * res, GList * extra)
{
GList *tmp;
/* FIXME : We should merge the system-wide profiles into the user-locals
* instead of stopping at identical target names */
for (tmp = extra; tmp; tmp = tmp->next) {
GstEncodingTarget *target = (GstEncodingTarget *) tmp->data;
if (g_list_find_custom (res, target, (GCompareFunc) compare_targets))
gst_encoding_target_unref (target);
else
res = g_list_append (res, target);
}
g_list_free (extra);
return res;
}
/**
* gst_encoding_list_all_targets:
* @categoryname: (allow-none): The category, for ex: #GST_ENCODING_CATEGORY_DEVICE.
* Can be %NULL.
*
* List all available #GstEncodingTarget for the specified category, or all categories
* if @categoryname is %NULL.
*
* Returns: (transfer full) (element-type GstEncodingTarget): The list of #GstEncodingTarget
*/
GList *
gst_encoding_list_all_targets (const gchar * categoryname)
{
GList *res = NULL;
gchar *topdir;
gchar **encoding_target_dirs;
const gchar *envvar = g_getenv ("GST_ENCODING_TARGET_PATH");
if (envvar) {
gint i;
encoding_target_dirs = g_strsplit (envvar, G_SEARCHPATH_SEPARATOR_S, -1);
for (i = 0; encoding_target_dirs[i]; i++)
res =
merge_targets (res, get_all_targets (encoding_target_dirs[i],
categoryname));
g_strfreev (encoding_target_dirs);
}
/* Get user-locals */
topdir =
g_build_filename (g_get_user_data_dir (), "gstreamer-" GST_API_VERSION,
GST_ENCODING_TARGET_DIRECTORY, NULL);
res = merge_targets (res, get_all_targets (topdir, categoryname));
g_free (topdir);
/* Get system-wide */
topdir = g_build_filename (GST_DATADIR, "gstreamer-" GST_API_VERSION,
GST_ENCODING_TARGET_DIRECTORY, NULL);
res = merge_targets (res, get_all_targets (topdir, categoryname));
g_free (topdir);
return res;
}