Instead of spamming the terminal with a python traceback Fixes https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/issues/107 Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/173>
		
			
				
	
	
		
			802 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			802 lines
		
	
	
		
			21 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"
 | |
| #ifndef DISABLE_XPTV
 | |
| #include "ges-pitivi-formatter.h"
 | |
| #endif
 | |
| 
 | |
| #ifdef HAS_PYTHON
 | |
| #include <Python.h>
 | |
| #include "ges-resources.h"
 | |
| #endif
 | |
| 
 | |
| GST_DEBUG_CATEGORY_STATIC (ges_formatter_debug);
 | |
| #undef GST_CAT_DEFAULT
 | |
| #define GST_CAT_DEFAULT ges_formatter_debug
 | |
| static gboolean initialized = FALSE;
 | |
| 
 | |
| /* 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);
 | |
| 
 | |
|   /* We are leaking the metadata but we don't really have choice here
 | |
|    * as calling ges_init() after deinit() is allowed.
 | |
|    */
 | |
| 
 | |
|   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 = g_strdup ("base-formatter");
 | |
|   klass->extension = g_strdup ("noextension");
 | |
|   klass->description = g_strdup ("Formatter base class, you should give"
 | |
|       " a name to your formatter");
 | |
|   klass->mimetype = g_strdup ("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;
 | |
|     gchar **valid_exts =
 | |
|         g_strsplit (ges_meta_container_get_string (GES_META_CONTAINER (asset),
 | |
|             GES_META_FORMATTER_EXTENSION), ",", -1);
 | |
|     gint i;
 | |
| 
 | |
|     if (extension) {
 | |
|       gboolean found = FALSE;
 | |
| 
 | |
|       for (i = 0; valid_exts[i]; i++) {
 | |
|         if (!g_strcmp0 (extension, valid_exts[i])) {
 | |
|           found = TRUE;
 | |
|           break;
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       if (!found)
 | |
|         goto next;
 | |
|     }
 | |
| 
 | |
|     class = g_type_class_ref (ges_asset_get_extractable_type (asset));
 | |
|     dummy_instance =
 | |
|         g_object_ref_sink (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);
 | |
|   next:
 | |
|     g_strfreev (valid_exts);
 | |
|   }
 | |
|   g_free (extension);
 | |
| 
 | |
|   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 ("Unsupported 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.
 | |
|  *
 | |
|  * Deprecated: 1.18: Use @ges_timeline_load_from_uri
 | |
|  */
 | |
| 
 | |
| 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.
 | |
|  *
 | |
|  * Deprecated: 1.18: Use @ges_timeline_save_to_uri
 | |
|  */
 | |
| 
 | |
| 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;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * ges_formatter_class_register_metas:
 | |
|  * @klass: The class to register metas on
 | |
|  * @name: The name of the formatter
 | |
|  * @description: The formatter description
 | |
|  * @extensions: A list of coma separated file extensions handled
 | |
|  * by the formatter. The order of the extensions should match the
 | |
|  * list of the structures inside @caps
 | |
|  * @caps: The caps the formatter handled, they should match what
 | |
|  * gstreamer typefind mechanism will report for the files the formatter
 | |
|  * handles.
 | |
|  * @version: The version of the formatter
 | |
|  * @rank: The rank of the formatter
 | |
|  */
 | |
| void
 | |
| ges_formatter_class_register_metas (GESFormatterClass * klass,
 | |
|     const gchar * name, const gchar * description, const gchar * extensions,
 | |
|     const gchar * caps, gdouble version, GstRank rank)
 | |
| {
 | |
|   g_return_if_fail (klass->name);
 | |
|   klass->name = g_strdup (name);
 | |
|   klass->description = g_strdup (description);
 | |
|   klass->extension = g_strdup (extensions);
 | |
|   klass->mimetype = g_strdup (caps);
 | |
|   klass->version = version;
 | |
|   klass->rank = rank;
 | |
| 
 | |
|   if (g_atomic_int_get (&initialized)
 | |
|       && g_type_class_peek (G_OBJECT_CLASS_TYPE (klass)))
 | |
|     gst_object_unref (ges_asset_request (G_OBJECT_CLASS_TYPE (klass), NULL,
 | |
|             NULL));
 | |
| }
 | |
| 
 | |
| /* 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));
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void
 | |
| load_python_formatters (void)
 | |
| {
 | |
| #ifdef HAS_PYTHON
 | |
|   PyGILState_STATE state = 0;
 | |
|   PyObject *main_module, *main_locals;
 | |
|   GError *err = NULL;
 | |
|   GResource *resource = ges_get_resource ();
 | |
|   GBytes *bytes =
 | |
|       g_resource_lookup_data (resource, "/ges/python/gesotioformatter.py",
 | |
|       G_RESOURCE_LOOKUP_FLAGS_NONE, &err);
 | |
|   PyObject *code = NULL, *res = NULL;
 | |
|   gboolean we_initialized = FALSE;
 | |
|   GModule *libpython;
 | |
|   gpointer has_python = NULL;
 | |
| 
 | |
|   GST_LOG ("Checking to see if libpython is already loaded");
 | |
|   if (g_module_symbol (g_module_open (NULL, G_MODULE_BIND_LOCAL),
 | |
|           "_Py_NoneStruct", &has_python) && has_python) {
 | |
|     GST_LOG ("libpython is already loaded");
 | |
|   } else {
 | |
|     const gchar *libpython_path =
 | |
|         PY_LIB_LOC "/libpython" PYTHON_VERSION PY_ABI_FLAGS "." PY_LIB_SUFFIX;
 | |
|     GST_LOG ("loading libpython from '%s'", libpython_path);
 | |
|     libpython = g_module_open (libpython_path, 0);
 | |
|     if (!libpython) {
 | |
|       GST_ERROR ("Couldn't g_module_open libpython. Reason: %s",
 | |
|           g_module_error ());
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (!Py_IsInitialized ()) {
 | |
|     GST_LOG ("python wasn't initialized");
 | |
|     /* set the correct plugin for registering stuff */
 | |
|     Py_Initialize ();
 | |
|     we_initialized = TRUE;
 | |
|   } else {
 | |
|     GST_LOG ("python was already initialized");
 | |
|     state = PyGILState_Ensure ();
 | |
|   }
 | |
| 
 | |
|   if (!bytes) {
 | |
|     GST_DEBUG ("Could not load gesotioformatter: %s\n", err->message);
 | |
| 
 | |
|     g_clear_error (&err);
 | |
| 
 | |
|     goto done;
 | |
|   }
 | |
| 
 | |
|   main_module = PyImport_AddModule ("__main__");
 | |
|   if (main_module == NULL) {
 | |
|     GST_WARNING ("Could not add main module");
 | |
|     PyErr_Print ();
 | |
|     PyErr_Clear ();
 | |
|     goto done;
 | |
|   }
 | |
| 
 | |
|   main_locals = PyModule_GetDict (main_module);
 | |
|   /* Compiling the code ourself so it has a proper filename */
 | |
|   code =
 | |
|       Py_CompileString (g_bytes_get_data (bytes, NULL), "gesotioformatter.py",
 | |
|       Py_file_input);
 | |
|   if (PyErr_Occurred ()) {
 | |
|     PyErr_Print ();
 | |
|     PyErr_Clear ();
 | |
|     goto done;
 | |
|   }
 | |
|   res = PyEval_EvalCode ((gpointer) code, main_locals, main_locals);
 | |
|   Py_XDECREF (code);
 | |
|   Py_XDECREF (res);
 | |
|   if (PyErr_Occurred ()) {
 | |
|     PyObject *exception_backtrace;
 | |
|     PyObject *exception_type;
 | |
|     PyObject *exception_value, *exception_value_repr, *exception_value_str;
 | |
| 
 | |
|     PyErr_Fetch (&exception_type, &exception_value, &exception_backtrace);
 | |
|     PyErr_NormalizeException (&exception_type, &exception_value,
 | |
|         &exception_backtrace);
 | |
| 
 | |
|     exception_value_repr = PyObject_Repr (exception_value);
 | |
|     exception_value_str =
 | |
|         PyUnicode_AsEncodedString (exception_value_repr, "utf-8", "Error ~");
 | |
|     GST_INFO ("Could not load OpenTimelineIO formatter: %s",
 | |
|         PyBytes_AS_STRING (exception_value_str));
 | |
| 
 | |
|     Py_XDECREF (exception_type);
 | |
|     Py_XDECREF (exception_value);
 | |
|     Py_XDECREF (exception_backtrace);
 | |
| 
 | |
|     Py_XDECREF (exception_value_repr);
 | |
|     Py_XDECREF (exception_value_str);
 | |
|     PyErr_Clear ();
 | |
|   }
 | |
| 
 | |
| done:
 | |
|   if (bytes)
 | |
|     g_bytes_unref (bytes);
 | |
| 
 | |
|   if (we_initialized) {
 | |
|     PyEval_SaveThread ();
 | |
|   } else {
 | |
|     PyGILState_Release (state);
 | |
|   }
 | |
| #endif /* HAS_PYTHON */
 | |
| }
 | |
| 
 | |
| void
 | |
| _init_formatter_assets (void)
 | |
| {
 | |
|   GType *formatters;
 | |
|   guint n_formatters;
 | |
|   static gsize init_debug = 0;
 | |
| 
 | |
|   if (g_once_init_enter (&init_debug)) {
 | |
| 
 | |
|     GST_DEBUG_CATEGORY_INIT (ges_formatter_debug, "gesformatter",
 | |
|         GST_DEBUG_FG_YELLOW, "ges formatter");
 | |
|     g_once_init_leave (&init_debug, TRUE);
 | |
|   }
 | |
| 
 | |
|   if (g_atomic_int_compare_and_exchange (&initialized, FALSE, TRUE)) {
 | |
|     /* register formatter types with the system */
 | |
| #ifndef DISABLE_XPTV
 | |
|     g_type_class_ref (GES_TYPE_PITIVI_FORMATTER);
 | |
| #endif
 | |
|     g_type_class_ref (GES_TYPE_COMMAND_LINE_FORMATTER);
 | |
|     g_type_class_ref (GES_TYPE_XML_FORMATTER);
 | |
| 
 | |
|     load_python_formatters ();
 | |
| 
 | |
|     formatters = g_type_children (GES_TYPE_FORMATTER, &n_formatters);
 | |
|     _list_formatters (formatters, n_formatters);
 | |
|     g_free (formatters);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void
 | |
| _deinit_formatter_assets (void)
 | |
| {
 | |
|   if (g_atomic_int_compare_and_exchange (&initialized, TRUE, FALSE)) {
 | |
| 
 | |
| #ifndef DISABLE_XPTV
 | |
|     g_type_class_unref (g_type_class_peek (GES_TYPE_PITIVI_FORMATTER));
 | |
| #endif
 | |
| 
 | |
|     g_type_class_unref (g_type_class_peek (GES_TYPE_COMMAND_LINE_FORMATTER));
 | |
|     g_type_class_unref (g_type_class_peek (GES_TYPE_XML_FORMATTER));
 | |
|   }
 | |
| }
 | |
| 
 | |
| 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_ref_sink (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;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * ges_find_formatter_for_uri:
 | |
|  *
 | |
|  * Get the best formatter for @uri. It tries to find a formatter
 | |
|  * compatible with @uri extension, if none is found, it returns the default
 | |
|  * formatter asset.
 | |
|  *
 | |
|  * Returns: (transfer none): The #GESAsset for the best formatter to save to @uri
 | |
|  *
 | |
|  * Since: 1.18
 | |
|  */
 | |
| GESAsset *
 | |
| ges_find_formatter_for_uri (const gchar * uri)
 | |
| {
 | |
|   GList *formatter_assets, *tmp;
 | |
|   GESAsset *asset = NULL;
 | |
| 
 | |
|   gchar *extension = _get_extension (uri);
 | |
|   if (!extension)
 | |
|     return ges_formatter_get_default ();
 | |
| 
 | |
|   formatter_assets = g_list_sort (ges_list_assets (GES_TYPE_FORMATTER),
 | |
|       (GCompareFunc) _sort_formatters);
 | |
| 
 | |
|   for (tmp = formatter_assets; tmp; tmp = tmp->next) {
 | |
|     gint i;
 | |
|     gchar **valid_exts =
 | |
|         g_strsplit (ges_meta_container_get_string (GES_META_CONTAINER
 | |
|             (tmp->data),
 | |
|             GES_META_FORMATTER_EXTENSION), ",", -1);
 | |
| 
 | |
|     for (i = 0; valid_exts[i]; i++) {
 | |
|       if (!g_strcmp0 (extension, valid_exts[i])) {
 | |
|         asset = GES_ASSET (tmp->data);
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     g_strfreev (valid_exts);
 | |
|     if (asset)
 | |
|       break;
 | |
|   }
 | |
|   g_free (extension);
 | |
|   g_list_free (formatter_assets);
 | |
| 
 | |
|   if (asset) {
 | |
|     GST_INFO_OBJECT (asset, "Using for URI %s", uri);
 | |
|     return asset;
 | |
|   }
 | |
| 
 | |
|   return ges_formatter_get_default ();
 | |
| }
 |