mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-23 18:21:04 +00:00
47a07a4f5a
And just skip them as we can't do anything else with them right now Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/3976>
1005 lines
28 KiB
C
1005 lines
28 KiB
C
/* GStreamer Editing Services
|
|
*
|
|
* Copyright (C) 2012 Thibault Saunier <thibault.saunier@collabora.com>
|
|
* Copyright (C) 2012 Volodymyr Rudyi <vladimir.rudoy@gmail.com>
|
|
*
|
|
* 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., 59 Temple Place - Suite 330,
|
|
* Boston, MA 02111-1307, USA.
|
|
*/
|
|
/**
|
|
* SECTION: gesuriasset
|
|
* @title: GESUriClipAsset
|
|
* @short_description: A GESAsset subclass specialized in GESUriClip extraction
|
|
*
|
|
* The #GESUriClipAsset is a special #GESAsset that lets you handle
|
|
* the media file to use inside the GStreamer Editing Services. It has APIs that
|
|
* let you get information about the medias. Also, the tags found in the media file are
|
|
* set as Metadata of the Asset.
|
|
*/
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <errno.h>
|
|
#include <gst/pbutils/pbutils.h>
|
|
#include "ges.h"
|
|
#include "ges-internal.h"
|
|
#include "ges-track-element-asset.h"
|
|
|
|
#define DEFAULT_DISCOVERY_TIMEOUT (60 * GST_SECOND)
|
|
|
|
static GHashTable *parent_newparent_table = NULL;
|
|
|
|
G_LOCK_DEFINE_STATIC (discoverers_lock);
|
|
static GstClockTime discovering_timeout = DEFAULT_DISCOVERY_TIMEOUT;
|
|
static GHashTable *discoverers = NULL; /* Thread ID -> GstDiscoverer */
|
|
static void discoverer_discovered_cb (GstDiscoverer * discoverer,
|
|
GstDiscovererInfo * info, GError * err, gpointer user_data);
|
|
|
|
/* WITH discoverers_lock */
|
|
static GstDiscoverer *
|
|
create_discoverer (void)
|
|
{
|
|
GstDiscoverer *disco = gst_discoverer_new (discovering_timeout, NULL);
|
|
|
|
g_signal_connect (disco, "discovered", G_CALLBACK (discoverer_discovered_cb),
|
|
NULL);
|
|
GST_INFO_OBJECT (disco, "Creating new discoverer");
|
|
g_hash_table_insert (discoverers, g_thread_self (), disco);
|
|
gst_discoverer_start (disco);
|
|
|
|
return disco;
|
|
}
|
|
|
|
static GstDiscoverer *
|
|
get_discoverer (void)
|
|
{
|
|
GstDiscoverer *disco;
|
|
|
|
G_LOCK (discoverers_lock);
|
|
g_assert (discoverers);
|
|
disco = g_hash_table_lookup (discoverers, g_thread_self ());
|
|
if (!disco) {
|
|
disco = create_discoverer ();
|
|
}
|
|
disco = gst_object_ref (disco);
|
|
G_UNLOCK (discoverers_lock);
|
|
|
|
return disco;
|
|
}
|
|
|
|
static void
|
|
initable_iface_init (GInitableIface * initable_iface)
|
|
{
|
|
/* We can not iniate synchronously */
|
|
initable_iface->init = NULL;
|
|
}
|
|
|
|
/* TODO: We should monitor files here, and add some way of reporting changes
|
|
* to user
|
|
*/
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_DURATION,
|
|
PROP_IS_NESTED_TIMELINE,
|
|
PROP_LAST
|
|
};
|
|
static GParamSpec *properties[PROP_LAST];
|
|
|
|
struct _GESUriClipAssetPrivate
|
|
{
|
|
GstDiscovererInfo *info;
|
|
GstClockTime duration;
|
|
GstClockTime max_duration;
|
|
gboolean is_image;
|
|
gboolean is_nested_timeline;
|
|
|
|
GList *asset_trackfilesources;
|
|
};
|
|
|
|
typedef struct
|
|
{
|
|
GMainLoop *ml;
|
|
GESAsset *asset;
|
|
GError *error;
|
|
} RequestSyncData;
|
|
|
|
struct _GESUriSourceAssetPrivate
|
|
{
|
|
GstDiscovererStreamInfo *sinfo;
|
|
GESUriClipAsset *creator_asset;
|
|
|
|
const gchar *uri;
|
|
};
|
|
|
|
G_DEFINE_TYPE_WITH_CODE (GESUriClipAsset, ges_uri_clip_asset,
|
|
GES_TYPE_SOURCE_CLIP_ASSET, G_ADD_PRIVATE (GESUriClipAsset)
|
|
G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init));
|
|
|
|
static void
|
|
ges_uri_clip_asset_get_property (GObject * object, guint property_id,
|
|
GValue * value, GParamSpec * pspec)
|
|
{
|
|
GESUriClipAssetPrivate *priv = GES_URI_CLIP_ASSET (object)->priv;
|
|
|
|
switch (property_id) {
|
|
case PROP_DURATION:
|
|
g_value_set_uint64 (value, priv->duration);
|
|
break;
|
|
case PROP_IS_NESTED_TIMELINE:
|
|
g_value_set_boolean (value, priv->is_nested_timeline);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
}
|
|
}
|
|
|
|
static void
|
|
ges_uri_clip_asset_set_property (GObject * object, guint property_id,
|
|
const GValue * value, GParamSpec * pspec)
|
|
{
|
|
GESUriClipAssetPrivate *priv = GES_URI_CLIP_ASSET (object)->priv;
|
|
|
|
switch (property_id) {
|
|
case PROP_DURATION:
|
|
priv->duration = g_value_get_uint64 (value);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
}
|
|
}
|
|
|
|
static GESAssetLoadingReturn
|
|
_start_loading (GESAsset * asset, GError ** error)
|
|
{
|
|
gboolean ret;
|
|
const gchar *uri;
|
|
GstDiscoverer *discoverer = get_discoverer ();
|
|
|
|
uri = ges_asset_get_id (asset);
|
|
GST_DEBUG_OBJECT (discoverer, "Started loading %s", uri);
|
|
|
|
ret = gst_discoverer_discover_uri_async (discoverer, uri);
|
|
gst_object_unref (discoverer);
|
|
|
|
if (ret)
|
|
return GES_ASSET_LOADING_ASYNC;
|
|
|
|
return GES_ASSET_LOADING_ERROR;
|
|
}
|
|
|
|
static gboolean
|
|
_request_id_update (GESAsset * self, gchar ** proposed_new_id, GError * error)
|
|
{
|
|
if (error->domain == GST_RESOURCE_ERROR &&
|
|
(error->code == GST_RESOURCE_ERROR_NOT_FOUND ||
|
|
error->code == GST_RESOURCE_ERROR_OPEN_READ)) {
|
|
const gchar *uri = ges_asset_get_id (self);
|
|
GFile *parent, *file = g_file_new_for_uri (uri);
|
|
|
|
/* Check if we have the new parent in cache */
|
|
parent = g_file_get_parent (file);
|
|
if (parent) {
|
|
GFile *new_parent = g_hash_table_lookup (parent_newparent_table, parent);
|
|
|
|
if (new_parent) {
|
|
gchar *basename = g_file_get_basename (file);
|
|
GFile *new_file = g_file_get_child (new_parent, basename);
|
|
|
|
/* FIXME Handle the GCancellable */
|
|
if (g_file_query_exists (new_file, NULL)) {
|
|
*proposed_new_id = g_file_get_uri (new_file);
|
|
GST_DEBUG_OBJECT (self, "Proposing path: %s as proxy",
|
|
*proposed_new_id);
|
|
}
|
|
|
|
gst_object_unref (new_file);
|
|
g_free (basename);
|
|
}
|
|
gst_object_unref (parent);
|
|
}
|
|
|
|
gst_object_unref (file);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
ges_uri_source_asset_get_natural_framerate (GESTrackElementAsset * asset,
|
|
gint * framerate_n, gint * framerate_d)
|
|
{
|
|
GESUriSourceAssetPrivate *priv = GES_URI_SOURCE_ASSET (asset)->priv;
|
|
|
|
if (!GST_IS_DISCOVERER_VIDEO_INFO (priv->sinfo))
|
|
return FALSE;
|
|
|
|
*framerate_d =
|
|
gst_discoverer_video_info_get_framerate_denom (GST_DISCOVERER_VIDEO_INFO
|
|
(priv->sinfo));
|
|
*framerate_n =
|
|
gst_discoverer_video_info_get_framerate_num (GST_DISCOVERER_VIDEO_INFO
|
|
(priv->sinfo));
|
|
|
|
if ((*framerate_n == 0 && *framerate_d == 1) || *framerate_d == 0
|
|
|| *framerate_d == G_MAXINT) {
|
|
GST_INFO_OBJECT (asset, "No framerate information about the file.");
|
|
|
|
*framerate_n = 0;
|
|
*framerate_d = -1;
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
_get_natural_framerate (GESClipAsset * self, gint * framerate_n,
|
|
gint * framerate_d)
|
|
{
|
|
GList *tmp;
|
|
|
|
for (tmp = (GList *)
|
|
ges_uri_clip_asset_get_stream_assets (GES_URI_CLIP_ASSET (self)); tmp;
|
|
tmp = tmp->next) {
|
|
|
|
if (ges_track_element_asset_get_natural_framerate (tmp->data, framerate_n,
|
|
framerate_d))
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
_asset_proxied (GESAsset * self, const gchar * new_uri)
|
|
{
|
|
const gchar *uri = ges_asset_get_id (self);
|
|
GFile *parent, *new_parent, *new_file = g_file_new_for_uri (new_uri),
|
|
*file = g_file_new_for_uri (uri);
|
|
|
|
parent = g_file_get_parent (file);
|
|
new_parent = g_file_get_parent (new_file);
|
|
g_hash_table_insert (parent_newparent_table, parent, new_parent);
|
|
gst_object_unref (file);
|
|
gst_object_unref (new_file);
|
|
}
|
|
|
|
static void
|
|
ges_uri_clip_asset_dispose (GObject * object)
|
|
{
|
|
GESUriClipAsset *self = GES_URI_CLIP_ASSET (object);
|
|
GESUriClipAssetPrivate *prif = self->priv;
|
|
|
|
if (prif->asset_trackfilesources) {
|
|
g_list_free_full (prif->asset_trackfilesources,
|
|
(GDestroyNotify) gst_object_unref);
|
|
prif->asset_trackfilesources = NULL;
|
|
}
|
|
|
|
gst_clear_object (&prif->info);
|
|
|
|
G_OBJECT_CLASS (ges_uri_clip_asset_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
ges_uri_clip_asset_class_init (GESUriClipAssetClass * klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
|
|
object_class->get_property = ges_uri_clip_asset_get_property;
|
|
object_class->set_property = ges_uri_clip_asset_set_property;
|
|
object_class->dispose = ges_uri_clip_asset_dispose;
|
|
|
|
GES_ASSET_CLASS (klass)->start_loading = _start_loading;
|
|
GES_ASSET_CLASS (klass)->request_id_update = _request_id_update;
|
|
GES_ASSET_CLASS (klass)->inform_proxy = _asset_proxied;
|
|
|
|
GES_CLIP_ASSET_CLASS (klass)->get_natural_framerate = _get_natural_framerate;
|
|
|
|
klass->discovered = discoverer_discovered_cb;
|
|
|
|
|
|
/**
|
|
* GESUriClipAsset:duration:
|
|
*
|
|
* The duration (in nanoseconds) of the media file
|
|
*/
|
|
properties[PROP_DURATION] =
|
|
g_param_spec_uint64 ("duration", "Duration", "The duration to use", 0,
|
|
G_MAXUINT64, GST_CLOCK_TIME_NONE, G_PARAM_READWRITE);
|
|
g_object_class_install_property (object_class, PROP_DURATION,
|
|
properties[PROP_DURATION]);
|
|
|
|
/**
|
|
* GESUriClipAsset:is-nested-timeline:
|
|
*
|
|
* The duration (in nanoseconds) of the media file
|
|
*
|
|
* Since: 1.18
|
|
*/
|
|
properties[PROP_IS_NESTED_TIMELINE] =
|
|
g_param_spec_boolean ("is-nested-timeline", "Is nested timeline",
|
|
"Whether this is a nested timeline", FALSE, G_PARAM_READABLE);
|
|
g_object_class_install_property (object_class, PROP_IS_NESTED_TIMELINE,
|
|
properties[PROP_IS_NESTED_TIMELINE]);
|
|
|
|
_ges_uri_asset_ensure_setup (klass);
|
|
}
|
|
|
|
static void
|
|
ges_uri_clip_asset_init (GESUriClipAsset * self)
|
|
{
|
|
GESUriClipAssetPrivate *priv;
|
|
|
|
priv = self->priv = ges_uri_clip_asset_get_instance_private (self);
|
|
|
|
priv->info = NULL;
|
|
priv->max_duration = priv->duration = GST_CLOCK_TIME_NONE;
|
|
priv->is_image = FALSE;
|
|
}
|
|
|
|
static void
|
|
_create_uri_source_asset (GESUriClipAsset * asset,
|
|
GstDiscovererStreamInfo * sinfo, GESTrackType type)
|
|
{
|
|
GESAsset *src_asset;
|
|
GESUriSourceAssetPrivate *src_priv;
|
|
GESUriClipAssetPrivate *priv = asset->priv;
|
|
gchar *stream_id =
|
|
g_strdup (gst_discoverer_stream_info_get_stream_id (sinfo));
|
|
|
|
if (stream_id == NULL) {
|
|
GST_WARNING_OBJECT (asset,
|
|
"No stream ID, ignoring stream info: %p off type: %s", sinfo,
|
|
ges_track_type_name (type));
|
|
return;
|
|
}
|
|
|
|
if (type == GES_TRACK_TYPE_VIDEO) {
|
|
src_asset = ges_asset_request (GES_TYPE_VIDEO_URI_SOURCE, stream_id, NULL);
|
|
} else if (type == GES_TRACK_TYPE_AUDIO) {
|
|
src_asset = ges_asset_request (GES_TYPE_AUDIO_URI_SOURCE, stream_id, NULL);
|
|
} else {
|
|
GST_INFO_OBJECT (asset,
|
|
"Unknown track type %d, not creating any asset backing it", type);
|
|
return;
|
|
}
|
|
g_free (stream_id);
|
|
|
|
src_priv = GES_URI_SOURCE_ASSET (src_asset)->priv;
|
|
src_priv->uri = ges_asset_get_id (GES_ASSET (asset));
|
|
src_priv->sinfo = gst_object_ref (sinfo);
|
|
src_priv->creator_asset = asset;
|
|
ges_track_element_asset_set_track_type (GES_TRACK_ELEMENT_ASSET
|
|
(src_asset), type);
|
|
|
|
priv->is_image |=
|
|
ges_uri_source_asset_is_image (GES_URI_SOURCE_ASSET (src_asset));
|
|
priv->asset_trackfilesources =
|
|
g_list_append (priv->asset_trackfilesources, src_asset);
|
|
}
|
|
|
|
static void
|
|
ges_uri_clip_asset_set_info (GESUriClipAsset * self, GstDiscovererInfo * info)
|
|
{
|
|
GList *tmp, *stream_list;
|
|
|
|
GESTrackType supportedformats = GES_TRACK_TYPE_UNKNOWN;
|
|
GESUriClipAssetPrivate *priv = GES_URI_CLIP_ASSET (self)->priv;
|
|
const GstTagList *tlist = gst_discoverer_info_get_tags (info);
|
|
|
|
/* Extract infos from the GstDiscovererInfo */
|
|
stream_list = gst_discoverer_info_get_stream_list (info);
|
|
for (tmp = stream_list; tmp; tmp = tmp->next) {
|
|
GESTrackType type = GES_TRACK_TYPE_UNKNOWN;
|
|
GstDiscovererStreamInfo *sinf = (GstDiscovererStreamInfo *) tmp->data;
|
|
|
|
if (GST_IS_DISCOVERER_AUDIO_INFO (sinf)) {
|
|
if (supportedformats == GES_TRACK_TYPE_UNKNOWN)
|
|
supportedformats = GES_TRACK_TYPE_AUDIO;
|
|
else
|
|
supportedformats |= GES_TRACK_TYPE_AUDIO;
|
|
|
|
type = GES_TRACK_TYPE_AUDIO;
|
|
} else if (GST_IS_DISCOVERER_VIDEO_INFO (sinf)) {
|
|
if (supportedformats == GES_TRACK_TYPE_UNKNOWN)
|
|
supportedformats = GES_TRACK_TYPE_VIDEO;
|
|
else
|
|
supportedformats |= GES_TRACK_TYPE_VIDEO;
|
|
type = GES_TRACK_TYPE_VIDEO;
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (self, "Creating GESUriSourceAsset for stream: %s",
|
|
gst_discoverer_stream_info_get_stream_id (sinf));
|
|
_create_uri_source_asset (self, sinf, type);
|
|
}
|
|
ges_clip_asset_set_supported_formats (GES_CLIP_ASSET
|
|
(self), supportedformats);
|
|
|
|
if (stream_list)
|
|
gst_discoverer_stream_info_list_free (stream_list);
|
|
|
|
if (tlist)
|
|
gst_tag_list_get_boolean (tlist, "is-ges-timeline",
|
|
&priv->is_nested_timeline);
|
|
|
|
if (priv->is_image == FALSE) {
|
|
priv->max_duration = priv->duration =
|
|
gst_discoverer_info_get_duration (info);
|
|
if (priv->is_nested_timeline)
|
|
priv->max_duration = GST_CLOCK_TIME_NONE;
|
|
}
|
|
/* else we keep #GST_CLOCK_TIME_NONE */
|
|
|
|
priv->info = gst_object_ref (info);
|
|
}
|
|
|
|
static void
|
|
_set_meta_file_size (const gchar * uri, GESUriClipAsset * asset)
|
|
{
|
|
GError *error = NULL;
|
|
GFileInfo *file_info = NULL;
|
|
guint64 file_size;
|
|
GFile *gfile = NULL;
|
|
|
|
GESMetaContainer *container = GES_META_CONTAINER (asset);
|
|
|
|
gfile = g_file_new_for_uri (uri);
|
|
file_info = g_file_query_info (gfile, "standard::size",
|
|
G_FILE_QUERY_INFO_NONE, NULL, &error);
|
|
if (!error) {
|
|
file_size = g_file_info_get_attribute_uint64 (file_info, "standard::size");
|
|
ges_meta_container_register_meta_uint64 (container, GES_META_READ_WRITE,
|
|
"file-size", file_size);
|
|
} else {
|
|
g_error_free (error);
|
|
}
|
|
if (gfile)
|
|
g_object_unref (gfile);
|
|
if (file_info)
|
|
g_object_unref (file_info);
|
|
}
|
|
|
|
static void
|
|
_set_meta_foreach (const GstTagList * tags, const gchar * tag,
|
|
GESMetaContainer * container)
|
|
{
|
|
GValue value = { 0 };
|
|
|
|
if (gst_tag_list_copy_value (&value, tags, tag)) {
|
|
ges_meta_container_set_meta (container, tag, &value);
|
|
g_value_unset (&value);
|
|
} else {
|
|
GST_INFO ("Could not set metadata: %s", tag);
|
|
}
|
|
}
|
|
|
|
static void
|
|
discoverer_discovered_cb (GstDiscoverer * discoverer,
|
|
GstDiscovererInfo * info, GError * err, gpointer user_data)
|
|
{
|
|
GError *error = NULL;
|
|
const GstTagList *tags;
|
|
|
|
const gchar *uri = gst_discoverer_info_get_uri (info);
|
|
GESUriClipAsset *mfs =
|
|
GES_URI_CLIP_ASSET (ges_asset_cache_lookup (GES_TYPE_URI_CLIP, uri));
|
|
|
|
tags = gst_discoverer_info_get_tags (info);
|
|
if (tags)
|
|
gst_tag_list_foreach (tags, (GstTagForeachFunc) _set_meta_foreach, mfs);
|
|
|
|
_set_meta_file_size (uri, mfs);
|
|
|
|
if (gst_discoverer_info_get_result (info) == GST_DISCOVERER_OK
|
|
|| gst_discoverer_info_get_result (info) ==
|
|
GST_DISCOVERER_MISSING_PLUGINS) {
|
|
ges_uri_clip_asset_set_info (mfs, info);
|
|
} else {
|
|
if (err) {
|
|
error = g_error_copy (err);
|
|
} else {
|
|
error = g_error_new (GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_FAILED,
|
|
"Stream %s discovering failed (error code: %d)",
|
|
uri, gst_discoverer_info_get_result (info));
|
|
}
|
|
}
|
|
|
|
ges_asset_cache_set_loaded (GES_TYPE_URI_CLIP, uri, error);
|
|
|
|
if (error)
|
|
g_error_free (error);
|
|
}
|
|
|
|
static void
|
|
asset_ready_cb (GESAsset * source, GAsyncResult * res, RequestSyncData * data)
|
|
{
|
|
data->asset = ges_asset_request_finish (res, &data->error);
|
|
|
|
if (data->error) {
|
|
gchar *possible_uri = ges_uri_asset_try_update_id (data->error, source);
|
|
|
|
if (possible_uri) {
|
|
ges_asset_try_proxy (source, possible_uri);
|
|
g_clear_error (&data->error);
|
|
ges_asset_request_async (GES_TYPE_URI_CLIP, possible_uri, NULL,
|
|
(GAsyncReadyCallback) asset_ready_cb, data);
|
|
g_free (possible_uri);
|
|
|
|
return;
|
|
}
|
|
}
|
|
g_main_loop_quit (data->ml);
|
|
}
|
|
|
|
/* API implementation */
|
|
/**
|
|
* ges_uri_clip_asset_get_info:
|
|
* @self: Target asset
|
|
*
|
|
* Gets #GstDiscovererInfo about the file
|
|
*
|
|
* Returns: (transfer none): #GstDiscovererInfo of specified asset
|
|
*/
|
|
GstDiscovererInfo *
|
|
ges_uri_clip_asset_get_info (const GESUriClipAsset * self)
|
|
{
|
|
g_return_val_if_fail (GES_IS_URI_CLIP_ASSET ((GESUriClipAsset *) self), NULL);
|
|
|
|
return self->priv->info;
|
|
}
|
|
|
|
/**
|
|
* ges_uri_clip_asset_get_duration:
|
|
* @self: a #GESUriClipAsset
|
|
*
|
|
* Gets duration of the file represented by @self
|
|
*
|
|
* Returns: The duration of @self
|
|
*/
|
|
GstClockTime
|
|
ges_uri_clip_asset_get_duration (GESUriClipAsset * self)
|
|
{
|
|
g_return_val_if_fail (GES_IS_URI_CLIP_ASSET (self), GST_CLOCK_TIME_NONE);
|
|
|
|
return self->priv->duration;
|
|
}
|
|
|
|
|
|
/**
|
|
* ges_uri_clip_asset_get_max_duration:
|
|
* @self: a #GESUriClipAsset
|
|
*
|
|
* Gets maximum duration of the file represented by @self,
|
|
* it is usually the same as GESUriClipAsset::duration,
|
|
* but in the case of nested timelines, for example, they
|
|
* are different as those can be extended 'infinitely'.
|
|
*
|
|
* Returns: The maximum duration of @self
|
|
*
|
|
* Since: 1.18
|
|
*/
|
|
GstClockTime
|
|
ges_uri_clip_asset_get_max_duration (GESUriClipAsset * self)
|
|
{
|
|
g_return_val_if_fail (GES_IS_URI_CLIP_ASSET (self), GST_CLOCK_TIME_NONE);
|
|
|
|
return self->priv->max_duration;
|
|
}
|
|
|
|
/**
|
|
* ges_uri_clip_asset_is_image:
|
|
* @self: a #GESUriClipAsset
|
|
*
|
|
* Gets Whether the file represented by @self is an image or not
|
|
*
|
|
* Returns: Whether the file represented by @self is an image or not
|
|
*
|
|
* Since: 1.18
|
|
*/
|
|
gboolean
|
|
ges_uri_clip_asset_is_image (GESUriClipAsset * self)
|
|
{
|
|
g_return_val_if_fail (GES_IS_URI_CLIP_ASSET (self), FALSE);
|
|
|
|
return self->priv->is_image;
|
|
}
|
|
|
|
/**
|
|
* ges_uri_clip_asset_new:
|
|
* @uri: The URI of the file for which to create a #GESUriClipAsset
|
|
* @cancellable: optional %GCancellable object, %NULL to ignore.
|
|
* @callback: (scope async): a #GAsyncReadyCallback to call when the initialization is finished
|
|
* @user_data: The user data to pass when @callback is called
|
|
*
|
|
* Creates a #GESUriClipAsset for @uri
|
|
*
|
|
* Example of request of a GESUriClipAsset:
|
|
* |[
|
|
* // The request callback
|
|
* static void
|
|
* filesource_asset_loaded_cb (GESAsset * source, GAsyncResult * res, gpointer user_data)
|
|
* {
|
|
* GError *error = NULL;
|
|
* GESUriClipAsset *filesource_asset;
|
|
*
|
|
* filesource_asset = ges_uri_clip_asset_finish (res, &error);
|
|
* if (filesource_asset) {
|
|
* gst_print ("The file: %s is usable as a FileSource, it is%s an image and lasts %" GST_TIME_FORMAT,
|
|
* ges_asset_get_id (GES_ASSET (filesource_asset))
|
|
* ges_uri_clip_asset_is_image (filesource_asset) ? "" : " not",
|
|
* GST_TIME_ARGS (ges_uri_clip_asset_get_duration (filesource_asset));
|
|
* } else {
|
|
* gst_print ("The file: %s is *not* usable as a FileSource because: %s",
|
|
* ges_asset_get_id (source), error->message);
|
|
* }
|
|
*
|
|
* gst_object_unref (mfs);
|
|
* }
|
|
*
|
|
* // The request:
|
|
* ges_uri_clip_asset_new (uri, (GAsyncReadyCallback) filesource_asset_loaded_cb, user_data);
|
|
* ]|
|
|
*/
|
|
void
|
|
ges_uri_clip_asset_new (const gchar * uri, GCancellable * cancellable,
|
|
GAsyncReadyCallback callback, gpointer user_data)
|
|
{
|
|
ges_asset_request_async (GES_TYPE_URI_CLIP, uri, cancellable,
|
|
callback, user_data);
|
|
}
|
|
|
|
/**
|
|
* ges_uri_clip_asset_finish:
|
|
* @res: The #GAsyncResult from which to get the newly created #GESUriClipAsset
|
|
* @error: An error to be set in case something wrong happens or %NULL
|
|
*
|
|
* Finalize the request of an async #GESUriClipAsset
|
|
*
|
|
* Returns: (transfer full): The #GESUriClipAsset previously requested
|
|
*
|
|
* Since: 1.16
|
|
*/
|
|
GESUriClipAsset *
|
|
ges_uri_clip_asset_finish (GAsyncResult * res, GError ** error)
|
|
{
|
|
GESAsset *asset;
|
|
|
|
g_return_val_if_fail (G_IS_ASYNC_RESULT (res), NULL);
|
|
|
|
asset = ges_asset_request_finish (res, error);
|
|
if (asset != NULL) {
|
|
return GES_URI_CLIP_ASSET (asset);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* ges_uri_clip_asset_request_sync:
|
|
* @uri: The URI of the file for which to create a #GESUriClipAsset.
|
|
* You can also use multi file uris for #GESMultiFileSource.
|
|
* @error: An error to be set in case something wrong happens or %NULL
|
|
*
|
|
* Creates a #GESUriClipAsset for @uri syncronously. You should avoid
|
|
* to use it in application, and rather create #GESUriClipAsset asynchronously
|
|
*
|
|
* Returns: (transfer full): A reference to the requested asset or %NULL if
|
|
* an error happened
|
|
*/
|
|
GESUriClipAsset *
|
|
ges_uri_clip_asset_request_sync (const gchar * uri, GError ** error)
|
|
{
|
|
GError *lerror = NULL;
|
|
GESUriClipAsset *asset;
|
|
RequestSyncData data = { 0, };
|
|
GstDiscoverer *previous_discoverer;
|
|
|
|
asset = GES_URI_CLIP_ASSET (ges_asset_request (GES_TYPE_URI_CLIP, uri,
|
|
&lerror));
|
|
|
|
if (asset)
|
|
return asset;
|
|
|
|
data.ml = g_main_loop_new (NULL, TRUE);
|
|
previous_discoverer = get_discoverer ();
|
|
create_discoverer ();
|
|
|
|
ges_asset_request_async (GES_TYPE_URI_CLIP, uri, NULL,
|
|
(GAsyncReadyCallback) asset_ready_cb, &data);
|
|
g_main_loop_run (data.ml);
|
|
g_main_loop_unref (data.ml);
|
|
|
|
G_LOCK (discoverers_lock);
|
|
g_hash_table_insert (discoverers, g_thread_self (), previous_discoverer);
|
|
G_UNLOCK (discoverers_lock);
|
|
|
|
if (data.error) {
|
|
GST_ERROR ("Got an error requesting asset: %s", data.error->message);
|
|
if (error != NULL)
|
|
g_propagate_error (error, data.error);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
return GES_URI_CLIP_ASSET (data.asset);
|
|
}
|
|
|
|
/**
|
|
* ges_uri_clip_asset_class_set_timeout:
|
|
* @klass: The #GESUriClipAssetClass on which to set the discoverer timeout
|
|
* @timeout: The timeout to set
|
|
*
|
|
* Sets the timeout of #GESUriClipAsset loading
|
|
*/
|
|
void
|
|
ges_uri_clip_asset_class_set_timeout (GESUriClipAssetClass * klass,
|
|
GstClockTime timeout)
|
|
{
|
|
GHashTableIter iter;
|
|
gpointer value;
|
|
|
|
g_return_if_fail (GES_IS_URI_CLIP_ASSET_CLASS (klass));
|
|
|
|
discovering_timeout = timeout;
|
|
|
|
G_LOCK (discoverers_lock);
|
|
g_hash_table_iter_init (&iter, discoverers);
|
|
while (g_hash_table_iter_next (&iter, NULL, &value))
|
|
g_object_set (value, "timeout", timeout, NULL);
|
|
G_UNLOCK (discoverers_lock);
|
|
}
|
|
|
|
/**
|
|
* ges_uri_clip_asset_get_stream_assets:
|
|
* @self: A #GESUriClipAsset
|
|
*
|
|
* Get the GESUriSourceAsset @self containes
|
|
*
|
|
* Returns: (transfer none) (element-type GESUriSourceAsset): a
|
|
* #GList of #GESUriSourceAsset
|
|
*/
|
|
const GList *
|
|
ges_uri_clip_asset_get_stream_assets (GESUriClipAsset * self)
|
|
{
|
|
g_return_val_if_fail (GES_IS_URI_CLIP_ASSET (self), FALSE);
|
|
|
|
return self->priv->asset_trackfilesources;
|
|
}
|
|
|
|
/*****************************************************************
|
|
* GESUriSourceAsset implementation *
|
|
*****************************************************************/
|
|
G_DEFINE_TYPE_WITH_PRIVATE (GESUriSourceAsset, ges_uri_source_asset,
|
|
GES_TYPE_TRACK_ELEMENT_ASSET);
|
|
|
|
static GESExtractable *
|
|
_extract (GESAsset * asset, GError ** error)
|
|
{
|
|
gchar *uri = NULL;
|
|
GESTrackElement *trackelement;
|
|
GESUriSourceAssetPrivate *priv = GES_URI_SOURCE_ASSET (asset)->priv;
|
|
|
|
if (GST_IS_DISCOVERER_STREAM_INFO (priv->sinfo) == FALSE) {
|
|
GST_WARNING_OBJECT (asset, "Can not extract as no strean info set");
|
|
|
|
return NULL;
|
|
}
|
|
|
|
if (priv->uri == NULL) {
|
|
GST_WARNING_OBJECT (asset, "Can not extract as no uri set");
|
|
|
|
return NULL;
|
|
}
|
|
|
|
uri = g_strdup (priv->uri);
|
|
|
|
if (g_str_has_prefix (priv->uri, GES_MULTI_FILE_URI_PREFIX))
|
|
trackelement = GES_TRACK_ELEMENT (ges_multi_file_source_new (uri));
|
|
else if (GST_IS_DISCOVERER_VIDEO_INFO (priv->sinfo))
|
|
trackelement = GES_TRACK_ELEMENT (ges_video_uri_source_new (uri));
|
|
else
|
|
trackelement = GES_TRACK_ELEMENT (ges_audio_uri_source_new (uri));
|
|
|
|
ges_track_element_set_track_type (trackelement,
|
|
ges_track_element_asset_get_track_type (GES_TRACK_ELEMENT_ASSET (asset)));
|
|
|
|
g_free (uri);
|
|
|
|
return GES_EXTRACTABLE (trackelement);
|
|
}
|
|
|
|
static void
|
|
ges_uri_source_asset_dispose (GObject * object)
|
|
{
|
|
GESUriSourceAsset *self = GES_URI_SOURCE_ASSET (object);
|
|
GESUriSourceAssetPrivate *priv = self->priv;
|
|
|
|
gst_clear_object (&priv->sinfo);
|
|
|
|
G_OBJECT_CLASS (ges_uri_source_asset_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
ges_uri_source_asset_class_init (GESUriSourceAssetClass * klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
|
|
object_class->dispose = ges_uri_source_asset_dispose;
|
|
|
|
GES_ASSET_CLASS (klass)->extract = _extract;
|
|
GES_TRACK_ELEMENT_ASSET_CLASS (klass)->get_natural_framerate =
|
|
ges_uri_source_asset_get_natural_framerate;
|
|
}
|
|
|
|
static void
|
|
ges_uri_source_asset_init (GESUriSourceAsset * self)
|
|
{
|
|
GESUriSourceAssetPrivate *priv;
|
|
|
|
priv = self->priv = ges_uri_source_asset_get_instance_private (self);
|
|
|
|
priv->sinfo = NULL;
|
|
priv->creator_asset = NULL;
|
|
priv->uri = NULL;
|
|
}
|
|
|
|
/**
|
|
* ges_uri_source_asset_get_stream_info:
|
|
* @asset: A #GESUriClipAsset
|
|
*
|
|
* Get the #GstDiscovererStreamInfo user by @asset
|
|
*
|
|
* Returns: (transfer none): a #GESUriClipAsset
|
|
*/
|
|
GstDiscovererStreamInfo *
|
|
ges_uri_source_asset_get_stream_info (GESUriSourceAsset * asset)
|
|
{
|
|
g_return_val_if_fail (GES_IS_URI_SOURCE_ASSET (asset), NULL);
|
|
|
|
return asset->priv->sinfo;
|
|
}
|
|
|
|
const gchar *
|
|
ges_uri_source_asset_get_stream_uri (GESUriSourceAsset * asset)
|
|
{
|
|
g_return_val_if_fail (GES_IS_URI_SOURCE_ASSET (asset), NULL);
|
|
|
|
return asset->priv->uri;
|
|
}
|
|
|
|
/**
|
|
* ges_uri_source_asset_get_filesource_asset:
|
|
* @asset: A #GESUriClipAsset
|
|
*
|
|
* Get the #GESUriClipAsset @self is contained in
|
|
*
|
|
* Returns: a #GESUriClipAsset
|
|
*/
|
|
const GESUriClipAsset *
|
|
ges_uri_source_asset_get_filesource_asset (GESUriSourceAsset * asset)
|
|
{
|
|
g_return_val_if_fail (GES_IS_URI_SOURCE_ASSET (asset), NULL);
|
|
|
|
return asset->priv->creator_asset;
|
|
}
|
|
|
|
/**
|
|
* ges_uri_source_asset_is_image:
|
|
* @asset: A #GESUriClipAsset
|
|
*
|
|
* Check if @asset contains a single image
|
|
*
|
|
* Returns: %TRUE if the video stream corresponds to an image (i.e. only
|
|
* contains one frame)
|
|
*
|
|
* Since: 1.18
|
|
*/
|
|
gboolean
|
|
ges_uri_source_asset_is_image (GESUriSourceAsset * asset)
|
|
{
|
|
g_return_val_if_fail (GES_IS_URI_SOURCE_ASSET (asset), FALSE);
|
|
|
|
if (!GST_IS_DISCOVERER_VIDEO_INFO (asset->priv->sinfo))
|
|
return FALSE;
|
|
|
|
return gst_discoverer_video_info_is_image ((GstDiscovererVideoInfo *)
|
|
asset->priv->sinfo);
|
|
}
|
|
|
|
void
|
|
_ges_uri_asset_cleanup (void)
|
|
{
|
|
if (parent_newparent_table) {
|
|
g_hash_table_destroy (parent_newparent_table);
|
|
parent_newparent_table = NULL;
|
|
}
|
|
|
|
G_LOCK (discoverers_lock);
|
|
if (discoverers) {
|
|
g_hash_table_destroy (discoverers);
|
|
discoverers = NULL;
|
|
}
|
|
gst_clear_object (&GES_URI_CLIP_ASSET_CLASS (g_type_class_peek
|
|
(GES_TYPE_URI_CLIP_ASSET))->discoverer);
|
|
G_UNLOCK (discoverers_lock);
|
|
}
|
|
|
|
gboolean
|
|
_ges_uri_asset_ensure_setup (gpointer uriasset_class)
|
|
{
|
|
GESUriClipAssetClass *klass;
|
|
GError *err;
|
|
GstClockTime timeout;
|
|
const gchar *timeout_str;
|
|
GstDiscoverer *discoverer = NULL;
|
|
|
|
g_return_val_if_fail (GES_IS_URI_CLIP_ASSET_CLASS (uriasset_class), FALSE);
|
|
|
|
klass = GES_URI_CLIP_ASSET_CLASS (uriasset_class);
|
|
|
|
timeout = DEFAULT_DISCOVERY_TIMEOUT;
|
|
errno = 0;
|
|
timeout_str = g_getenv ("GES_DISCOVERY_TIMEOUT");
|
|
if (timeout_str)
|
|
timeout = g_ascii_strtod (timeout_str, NULL) * GST_SECOND;
|
|
else
|
|
errno = 10;
|
|
|
|
if (errno)
|
|
timeout = DEFAULT_DISCOVERY_TIMEOUT;
|
|
|
|
if (!klass->discoverer) {
|
|
discoverer = gst_discoverer_new (timeout, &err);
|
|
if (!discoverer) {
|
|
GST_ERROR ("Could not create discoverer: %s", err->message);
|
|
g_error_free (err);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* The class structure keeps weak pointers on the discoverers so they
|
|
* can be properly cleaned up in _ges_uri_asset_cleanup(). */
|
|
if (!klass->discoverer) {
|
|
klass->discoverer = klass->sync_discoverer = discoverer;
|
|
g_object_add_weak_pointer (G_OBJECT (discoverer),
|
|
(gpointer *) & klass->discoverer);
|
|
g_object_add_weak_pointer (G_OBJECT (discoverer),
|
|
(gpointer *) & klass->sync_discoverer);
|
|
|
|
g_signal_connect (klass->discoverer, "discovered",
|
|
G_CALLBACK (klass->discovered), NULL);
|
|
gst_discoverer_start (klass->discoverer);
|
|
}
|
|
|
|
G_LOCK (discoverers_lock);
|
|
if (discoverers == NULL) {
|
|
discoverers = g_hash_table_new_full (g_direct_hash,
|
|
(GEqualFunc) g_direct_equal, NULL, g_object_unref);
|
|
}
|
|
G_UNLOCK (discoverers_lock);
|
|
|
|
/* We just start the discoverer and let it live */
|
|
if (parent_newparent_table == NULL) {
|
|
parent_newparent_table = g_hash_table_new_full (g_file_hash,
|
|
(GEqualFunc) g_file_equal, g_object_unref, g_object_unref);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|