gstreamer/ges/ges-formatter.c
Tim-Philipp Müller a7347ca8f7 WIP: ges: fix API export/import and 'inconsistent linkage' on MSVC
Export GES library API in headers when we're building the
library itself, otherwise import the API from the headers.

This fixes linker warnings on Windows when building with MSVC.

Fix up some missing config.h includes when building the lib which
is needed to get the export api define from config.h

Fixes https://gitlab.freedesktop.org/gstreamer/gst-editing-services/issues/42
2018-12-15 00:14:51 +00:00

551 lines
14 KiB
C

/* GStreamer Editing Services
* Copyright (C) 2010 Brandon Lewis <brandon.lewis@collabora.co.uk>
* 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:gesformatter
* @title: GESFormatter
* @short_description: Timeline saving and loading.
*
**/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <gst/gst.h>
#include <gio/gio.h>
#include <stdlib.h>
#include <string.h>
#include "ges-formatter.h"
#include "ges-internal.h"
#include "ges.h"
/* TODO Add a GCancellable somewhere in the API */
static void ges_extractable_interface_init (GESExtractableInterface * iface);
struct _GESFormatterPrivate
{
gpointer nothing;
};
G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GESFormatter, ges_formatter,
G_TYPE_INITIALLY_UNOWNED, G_ADD_PRIVATE (GESFormatter)
G_IMPLEMENT_INTERFACE (GES_TYPE_EXTRACTABLE,
ges_extractable_interface_init));
static void ges_formatter_dispose (GObject * object);
static gboolean default_can_load_uri (GESFormatter * dummy_instance,
const gchar * uri, GError ** error);
/* GESExtractable implementation */
static gchar *
extractable_check_id (GType type, const gchar * id)
{
GESFormatterClass *class;
if (id)
return g_strdup (id);
class = g_type_class_peek (type);
return g_strdup (class->name);
}
static gchar *
extractable_get_id (GESExtractable * self)
{
GESAsset *asset;
if (!(asset = ges_extractable_get_asset (self)))
return NULL;
return g_strdup (ges_asset_get_id (asset));
}
static gboolean
_register_metas (GESExtractableInterface * iface, GObjectClass * class,
GESAsset * asset)
{
GESFormatterClass *fclass = GES_FORMATTER_CLASS (class);
GESMetaContainer *container = GES_META_CONTAINER (asset);
ges_meta_container_register_meta_string (container, GES_META_READABLE,
GES_META_FORMATTER_NAME, fclass->name);
ges_meta_container_register_meta_string (container, GES_META_READABLE,
GES_META_DESCRIPTION, fclass->description);
ges_meta_container_register_meta_string (container, GES_META_READABLE,
GES_META_FORMATTER_MIMETYPE, fclass->mimetype);
ges_meta_container_register_meta_string (container, GES_META_READABLE,
GES_META_FORMATTER_EXTENSION, fclass->extension);
ges_meta_container_register_meta_double (container, GES_META_READABLE,
GES_META_FORMATTER_VERSION, fclass->version);
ges_meta_container_register_meta_uint (container, GES_META_READABLE,
GES_META_FORMATTER_RANK, fclass->rank);
ges_meta_container_register_meta_string (container, GES_META_READ_WRITE,
GES_META_FORMAT_VERSION, NULL);
return TRUE;
}
static void
ges_extractable_interface_init (GESExtractableInterface * iface)
{
iface->check_id = (GESExtractableCheckId) extractable_check_id;
iface->get_id = extractable_get_id;
iface->asset_type = GES_TYPE_ASSET;
iface->register_metas = _register_metas;
}
static void
ges_formatter_class_init (GESFormatterClass * klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = ges_formatter_dispose;
klass->can_load_uri = default_can_load_uri;
klass->load_from_uri = NULL;
klass->save_to_uri = NULL;
/* We set dummy metas */
klass->name = "base-formatter";
klass->extension = "noextension";
klass->description = "Formatter base class, you should give"
" a name to your formatter";
klass->mimetype = "No mimetype";
klass->version = 0.0;
klass->rank = GST_RANK_NONE;
}
static void
ges_formatter_init (GESFormatter * object)
{
object->priv = ges_formatter_get_instance_private (object);
object->project = NULL;
}
static void
ges_formatter_dispose (GObject * object)
{
ges_formatter_set_project (GES_FORMATTER (object), NULL);
G_OBJECT_CLASS (ges_formatter_parent_class)->dispose (object);
}
static gboolean
default_can_load_uri (GESFormatter * dummy_instance, const gchar * uri,
GError ** error)
{
GST_DEBUG ("%s: no 'can_load_uri' vmethod implementation",
G_OBJECT_TYPE_NAME (dummy_instance));
return FALSE;
}
static gchar *
_get_extension (const gchar * uri)
{
gchar *result;
gsize len;
gint find;
GST_DEBUG ("finding extension of %s", uri);
if (uri == NULL)
goto no_uri;
/* find the extension on the uri, this is everything after a '.' */
len = strlen (uri);
find = len - 1;
while (find >= 0) {
if (uri[find] == '.')
break;
find--;
}
if (find < 0)
goto no_extension;
result = g_strdup (&uri[find + 1]);
GST_DEBUG ("found extension %s", result);
return result;
/* ERRORS */
no_uri:
{
GST_WARNING ("could not parse the peer uri");
return NULL;
}
no_extension:
{
GST_WARNING ("could not find uri extension in %s", uri);
return NULL;
}
}
/**
* ges_formatter_can_load_uri:
* @uri: a #gchar * pointing to the URI
* @error: A #GError that will be set in case of error
*
* Checks if there is a #GESFormatter available which can load a #GESTimeline
* from the given URI.
*
* Returns: TRUE if there is a #GESFormatter that can support the given uri
* or FALSE if not.
*/
gboolean
ges_formatter_can_load_uri (const gchar * uri, GError ** error)
{
gboolean ret = FALSE;
gchar *extension;
GList *formatter_assets, *tmp;
GESFormatterClass *class = NULL;
if (!(gst_uri_is_valid (uri))) {
GST_ERROR ("Invalid uri!");
return FALSE;
}
extension = _get_extension (uri);
formatter_assets = ges_list_assets (GES_TYPE_FORMATTER);
for (tmp = formatter_assets; tmp; tmp = tmp->next) {
GESAsset *asset = GES_ASSET (tmp->data);
GESFormatter *dummy_instance;
if (extension
&& g_strcmp0 (extension,
ges_meta_container_get_string (GES_META_CONTAINER (asset),
GES_META_FORMATTER_EXTENSION)))
continue;
class = g_type_class_ref (ges_asset_get_extractable_type (asset));
dummy_instance =
g_object_new (ges_asset_get_extractable_type (asset), NULL);
if (class->can_load_uri (dummy_instance, uri, error)) {
g_type_class_unref (class);
gst_object_unref (dummy_instance);
ret = TRUE;
break;
}
g_type_class_unref (class);
gst_object_unref (dummy_instance);
}
g_list_free (formatter_assets);
return ret;
}
/**
* ges_formatter_can_save_uri:
* @uri: a #gchar * pointing to a URI
* @error: A #GError that will be set in case of error
*
* Returns TRUE if there is a #GESFormatter available which can save a
* #GESTimeline to the given URI.
*
* Returns: TRUE if the given @uri is supported, else FALSE.
*/
gboolean
ges_formatter_can_save_uri (const gchar * uri, GError ** error)
{
GFile *file = NULL;
GFile *dir = NULL;
gboolean ret = TRUE;
GFileInfo *info = NULL;
if (!(gst_uri_is_valid (uri))) {
GST_ERROR ("%s invalid uri!", uri);
return FALSE;
}
if (!(gst_uri_has_protocol (uri, "file"))) {
gchar *proto = gst_uri_get_protocol (uri);
GST_ERROR ("Unspported protocol '%s'", proto);
g_free (proto);
return FALSE;
}
/* Check if URI or parent directory is writeable */
file = g_file_new_for_uri (uri);
if (g_file_query_file_type (file, G_FILE_QUERY_INFO_NONE, NULL)
== G_FILE_TYPE_DIRECTORY) {
dir = g_object_ref (file);
} else {
dir = g_file_get_parent (file);
if (dir == NULL)
goto error;
}
info = g_file_query_info (dir, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE,
G_FILE_QUERY_INFO_NONE, NULL, error);
if (error && *error != NULL) {
GST_ERROR ("Unable to write to directory: %s", (*error)->message);
goto error;
} else {
gboolean writeable = g_file_info_get_attribute_boolean (info,
G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE);
if (!writeable) {
GST_ERROR ("Unable to write to directory");
goto error;
}
}
done:
if (file)
g_object_unref (file);
if (dir)
g_object_unref (dir);
if (info)
g_object_unref (info);
/* TODO: implement file format registry */
/* TODO: search through the registry and chose a GESFormatter class that can
* handle the URI.*/
return ret;
error:
ret = FALSE;
goto done;
}
/**
* ges_formatter_load_from_uri:
* @formatter: a #GESFormatter
* @timeline: a #GESTimeline
* @uri: a #gchar * pointing to a URI
* @error: A #GError that will be set in case of error
*
* Load data from the given URI into timeline.
*
* Returns: TRUE if the timeline data was successfully loaded from the URI,
* else FALSE.
*/
gboolean
ges_formatter_load_from_uri (GESFormatter * formatter,
GESTimeline * timeline, const gchar * uri, GError ** error)
{
gboolean ret = FALSE;
GESFormatterClass *klass = GES_FORMATTER_GET_CLASS (formatter);
g_return_val_if_fail (GES_IS_FORMATTER (formatter), FALSE);
g_return_val_if_fail (GES_IS_TIMELINE (timeline), FALSE);
if (klass->load_from_uri) {
formatter->timeline = timeline;
ret = klass->load_from_uri (formatter, timeline, uri, error);
}
return ret;
}
/**
* ges_formatter_save_to_uri:
* @formatter: a #GESFormatter
* @timeline: a #GESTimeline
* @uri: a #gchar * pointing to a URI
* @overwrite: %TRUE to overwrite file if it exists
* @error: A #GError that will be set in case of error
*
* Save data from timeline to the given URI.
*
* Returns: TRUE if the timeline data was successfully saved to the URI
* else FALSE.
*/
gboolean
ges_formatter_save_to_uri (GESFormatter * formatter, GESTimeline *
timeline, const gchar * uri, gboolean overwrite, GError ** error)
{
GError *lerr = NULL;
gboolean ret = FALSE;
GESFormatterClass *klass = GES_FORMATTER_GET_CLASS (formatter);
GST_DEBUG_OBJECT (formatter, "Saving %" GST_PTR_FORMAT " to %s",
timeline, uri);
if (klass->save_to_uri)
ret = klass->save_to_uri (formatter, timeline, uri, overwrite, &lerr);
else
GST_ERROR_OBJECT (formatter, "save_to_uri not implemented!");
if (lerr) {
GST_WARNING_OBJECT (formatter, "%" GST_PTR_FORMAT
" not saved to %s error: %s", timeline, uri, lerr->message);
g_propagate_error (error, lerr);
} else
GST_INFO_OBJECT (formatter, "%" GST_PTR_FORMAT
" saved to %s", timeline, uri);
return ret;
}
/**
* ges_formatter_get_default:
*
* Get the default #GESAsset to use as formatter. It will return
* the asset for the #GESFormatter that has the highest @rank
*
* Returns: (transfer none): The #GESAsset for the formatter with highest @rank
*/
GESAsset *
ges_formatter_get_default (void)
{
GList *assets, *tmp;
GESAsset *ret = NULL;
guint tmprank, rank = GST_RANK_NONE;
assets = ges_list_assets (GES_TYPE_FORMATTER);
for (tmp = assets; tmp; tmp = tmp->next) {
tmprank = GST_RANK_NONE;
ges_meta_container_get_uint (GES_META_CONTAINER (tmp->data),
GES_META_FORMATTER_RANK, &tmprank);
if (tmprank > rank) {
rank = tmprank;
ret = GES_ASSET (tmp->data);
}
}
g_list_free (assets);
return ret;
}
void
ges_formatter_class_register_metas (GESFormatterClass * class,
const gchar * name, const gchar * description, const gchar * extension,
const gchar * mimetype, gdouble version, GstRank rank)
{
class->name = name;
class->description = description;
class->extension = extension;
class->mimetype = mimetype;
class->version = version;
class->rank = rank;
}
/* Main Formatter methods */
/*< protected >*/
void
ges_formatter_set_project (GESFormatter * formatter, GESProject * project)
{
formatter->project = project;
}
GESProject *
ges_formatter_get_project (GESFormatter * formatter)
{
return formatter->project;
}
static void
_list_formatters (GType * formatters, guint n_formatters)
{
GType *tmptypes, type;
guint tmp_n_types, i;
for (i = 0; i < n_formatters; i++) {
type = formatters[i];
tmptypes = g_type_children (type, &tmp_n_types);
if (tmp_n_types) {
/* Recurse as g_type_children does not */
_list_formatters (tmptypes, tmp_n_types);
}
g_free (tmptypes);
if (G_TYPE_IS_ABSTRACT (type)) {
GST_DEBUG ("%s is abstract, not using", g_type_name (type));
} else {
gst_object_unref (ges_asset_request (type, NULL, NULL));
}
}
}
void
_init_formatter_assets (void)
{
GType *formatters;
guint n_formatters;
formatters = g_type_children (GES_TYPE_FORMATTER, &n_formatters);
_list_formatters (formatters, n_formatters);
g_free (formatters);
}
static gint
_sort_formatters (GESAsset * asset, GESAsset * asset1)
{
GESFormatterClass *class =
g_type_class_peek (ges_asset_get_extractable_type (asset));
GESFormatterClass *class1 =
g_type_class_peek (ges_asset_get_extractable_type (asset1));
/* We want the highest ranks to be first! */
if (class->rank > class1->rank)
return -1;
else if (class->rank < class1->rank)
return 1;
return 0;
}
GESAsset *
_find_formatter_asset_for_id (const gchar * id)
{
GESFormatterClass *class = NULL;
GList *formatter_assets, *tmp;
GESAsset *asset = NULL;
formatter_assets = g_list_sort (ges_list_assets (GES_TYPE_FORMATTER),
(GCompareFunc) _sort_formatters);
for (tmp = formatter_assets; tmp; tmp = tmp->next) {
GESFormatter *dummy_instance;
asset = GES_ASSET (tmp->data);
class = g_type_class_ref (ges_asset_get_extractable_type (asset));
dummy_instance =
g_object_new (ges_asset_get_extractable_type (asset), NULL);
if (class->can_load_uri (dummy_instance, id, NULL)) {
g_type_class_unref (class);
asset = gst_object_ref (asset);
gst_object_unref (dummy_instance);
break;
}
asset = NULL;
g_type_class_unref (class);
gst_object_unref (dummy_instance);
}
g_list_free (formatter_assets);
return asset;
}