/* GStreamer Editing Services * Copyright (C) 2010 Brandon Lewis * 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 #include #include #include #include "ges-formatter.h" #include "ges-internal.h" #include "ges.h" #ifndef DISABLE_XPTV #include "ges-pitivi-formatter.h" #endif #ifdef HAS_PYTHON #include #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 (); }