diff --git a/meson.build b/meson.build index e63689c00..377fe254c 100644 --- a/meson.build +++ b/meson.build @@ -85,6 +85,11 @@ libadwaita_dep = dependency('libadwaita-1', required: false, ) +# Optional +peas_dep = dependency('libpeas-2', + required: false, +) + cc = meson.get_compiler('c') libm = cc.find_library('m', required: false) @@ -147,6 +152,9 @@ summary('vapi', build_vapi ? 'Yes' : 'No', section: 'Build') summary('doc', build_doc ? 'Yes' : 'No', section: 'Build') if build_clapper + foreach name : clapper_possible_functionalities + summary(name, clapper_available_functionalities.contains(name) ? 'Yes' : 'No', section: 'Functionalities') + endforeach foreach name : clapper_possible_features summary(name, clapper_available_features.contains(name) ? 'Yes' : 'No', section: 'Features') endforeach diff --git a/meson_options.txt b/meson_options.txt index ec9cce168..09a2c12c6 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -35,6 +35,13 @@ option('doc', description: 'Build documentation' ) +# Functionalities +option('enhancers-loader', + type: 'feature', + value: 'enabled', + description: 'Ability to load libpeas based plugins that enhance functionalities' +) + # Features option('discoverer', type: 'feature', diff --git a/src/lib/clapper/clapper-enhancers-loader-private.h b/src/lib/clapper/clapper-enhancers-loader-private.h new file mode 100644 index 000000000..c2a50d9c9 --- /dev/null +++ b/src/lib/clapper/clapper-enhancers-loader-private.h @@ -0,0 +1,42 @@ +/* Clapper Playback Library + * Copyright (C) 2024 Rafał Dzięgiel + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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. + */ + +#pragma once + +#include +#include + +G_BEGIN_DECLS + +G_GNUC_INTERNAL +void clapper_enhancers_loader_initialize (void); + +G_GNUC_INTERNAL +gboolean clapper_enhancers_loader_has_enhancers (GType iface_type); + +G_GNUC_INTERNAL +gchar ** clapper_enhancers_loader_get_schemes (GType iface_type); + +G_GNUC_INTERNAL +gboolean clapper_enhancers_loader_check (GType iface_type, const gchar *scheme, const gchar *host, const gchar **name); + +G_GNUC_INTERNAL +GObject * clapper_enhancers_loader_create_enhancer_for_uri (GType iface_type, GUri *uri); + +G_END_DECLS diff --git a/src/lib/clapper/clapper-enhancers-loader.c b/src/lib/clapper/clapper-enhancers-loader.c new file mode 100644 index 000000000..b59b56638 --- /dev/null +++ b/src/lib/clapper/clapper-enhancers-loader.c @@ -0,0 +1,343 @@ +/* Clapper Playback Library + * Copyright (C) 2024 Rafał Dzięgiel + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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. + */ + +#include "config.h" + +#include +#include + +#include "clapper-enhancers-loader-private.h" + +#define ENHANCER_INTERFACES "X-Interfaces" +#define ENHANCER_SCHEMES "X-Schemes" +#define ENHANCER_HOSTS "X-Hosts" +#define ENHANCER_IFACE_NAME_FROM_TYPE(type) (g_type_name (type) + 7) // strip "Clapper" prefix + +#define GST_CAT_DEFAULT clapper_enhancers_loader_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +static PeasEngine *_engine = NULL; + +/* + * clapper_enhancers_loader_initialize: + * + * Initializes #PeasEngine with directories that store enhancers. + */ +void +clapper_enhancers_loader_initialize (void) +{ + const gchar *enhancers_path; + gchar **dir_paths; + guint i; + + GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperenhancersloader", 0, + "Clapper Enhancer Loader"); + + enhancers_path = g_getenv ("CLAPPER_ENHANCERS_PATH"); + if (!enhancers_path || *enhancers_path == '\0') + enhancers_path = CLAPPER_ENHANCERS_PATH; + + GST_DEBUG ("Initializing Clapper enhancers with path: \"%s\"", enhancers_path); + + _engine = peas_engine_new (); + + /* Peas loaders are loaded lazily, so it should be fine + * to just enable them all here (even if not installed) */ + peas_engine_enable_loader (_engine, "python"); + peas_engine_enable_loader (_engine, "gjs"); + + dir_paths = g_strsplit (enhancers_path, G_SEARCHPATH_SEPARATOR_S, 0); + + for (i = 0; dir_paths[i]; ++i) + peas_engine_add_search_path (_engine, dir_paths[i], NULL); + + g_strfreev (dir_paths); + + GST_DEBUG ("Clapper enhancers initialized, found: %u", + g_list_model_get_n_items ((GListModel *) _engine)); +} + +static inline gboolean +_is_name_listed (const gchar *name, const gchar *list_str) +{ + gsize name_len = strlen (name); + guint i = 0; + + while (list_str[i] != '\0') { + guint end = i; + + while (list_str[end] != ';' && list_str[end] != '\0') + ++end; + + /* Compare letters count until separator and prefix of whole string */ + if (end - i == name_len && g_str_has_prefix (list_str + i, name)) + return TRUE; + + i = end; + + /* Move to the next letter after ';' */ + if (list_str[i] != '\0') + ++i; + } + + return FALSE; +} + +/* + * clapper_enhancers_loader_get_info: + * @iface_type: an interface #GType + * @scheme: an URI scheme + * @host: (nullable): an URI host + * + * Returns: (transfer none) (nullable): available #PeasPluginInfo or %NULL. + */ +static PeasPluginInfo * +clapper_enhancers_loader_get_info (GType iface_type, const gchar *scheme, const gchar *host) +{ + GListModel *list = (GListModel *) _engine; + PeasPluginInfo *found_info = NULL; + guint i, n_plugins = g_list_model_get_n_items (list); + const gchar *iface_name; + gboolean is_https; + + if (n_plugins == 0) { + GST_INFO ("No Clapper enhancers found"); + return NULL; + } + + iface_name = ENHANCER_IFACE_NAME_FROM_TYPE (iface_type); + + /* Strip common subdomains, so plugins do not + * have to list all combinations */ + if (host) { + if (g_str_has_prefix (host, "www.")) + host += 4; + else if (g_str_has_prefix (host, "m.")) + host += 2; + } + + GST_INFO ("Enhancer check, iface: %s, scheme: %s, host: %s", + iface_name, scheme, GST_STR_NULL (host)); + + is_https = (g_str_has_prefix (scheme, "http") + && (scheme[4] == '\0' || (scheme[4] == 's' && scheme[5] == '\0'))); + + if (!host && is_https) + return NULL; + + for (i = 0; i < n_plugins; ++i) { + PeasPluginInfo *info = (PeasPluginInfo *) g_list_model_get_item (list, i); + const gchar *iface_names, *schemes, *hosts; + + if (!(iface_names = peas_plugin_info_get_external_data (info, ENHANCER_INTERFACES))) { + GST_DEBUG ("Skipping enhancer without interfaces: %s", peas_plugin_info_get_name (info)); + continue; + } + if (!_is_name_listed (iface_name, iface_names)) + continue; + if (!(schemes = peas_plugin_info_get_external_data (info, ENHANCER_SCHEMES))) { + GST_DEBUG ("Skipping enhancer without schemes: %s", peas_plugin_info_get_name (info)); + continue; + } + if (!_is_name_listed (scheme, schemes)) + continue; + if (is_https) { + if (!(hosts = peas_plugin_info_get_external_data (info, ENHANCER_HOSTS))) { + GST_DEBUG ("Skipping enhancer without hosts: %s", peas_plugin_info_get_name (info)); + continue; + } + if (!_is_name_listed (host, hosts)) + continue; + } + + found_info = info; + break; + } + + return found_info; +} + +/* + * clapper_enhancers_loader_has_enhancers: + * @iface_type: an interface #GType + * + * Check if any enhancer implementing given interface type is available. + * + * Returns: whether any valid enhancer was found. + */ +gboolean +clapper_enhancers_loader_has_enhancers (GType iface_type) +{ + GListModel *list = (GListModel *) _engine; + const gchar *iface_name = ENHANCER_IFACE_NAME_FROM_TYPE (iface_type); + guint i, n_plugins; + + GST_DEBUG ("Checking for any enhancers of type: \"%s\"", iface_name); + + n_plugins = g_list_model_get_n_items (list); + + for (i = 0; i < n_plugins; ++i) { + PeasPluginInfo *info = (PeasPluginInfo *) g_list_model_get_item (list, i); + const gchar *iface_names; + + if (!(iface_names = peas_plugin_info_get_external_data (info, ENHANCER_INTERFACES))) + continue; + if (!_is_name_listed (iface_name, iface_names)) + continue; + + /* Additional validation */ + if (!peas_plugin_info_get_external_data (info, ENHANCER_SCHEMES) + || !peas_plugin_info_get_external_data (info, ENHANCER_HOSTS)) + continue; + + GST_DEBUG ("Found valid enhancers of type: \"%s\"", iface_name); + + return TRUE; + } + + GST_DEBUG ("No available enhancers of type: \"%s\"", iface_name); + + return FALSE; +} + +/* + * clapper_enhancers_loader_get_schemes: + * @iface_type: an interface #GType + * + * Get all supported schemes for a given interface type. + * The returned array consists of unique strings (no duplicates). + * + * Returns: (transfer full): all supported schemes by enhancers of type. + */ +gchar ** +clapper_enhancers_loader_get_schemes (GType iface_type) +{ + GListModel *list = (GListModel *) _engine; + GSList *found_schemes = NULL; + const gchar *iface_name = ENHANCER_IFACE_NAME_FROM_TYPE (iface_type); + gchar **schemes_strv; + guint i, n_plugins, n_schemes; + + GST_DEBUG ("Checking supported URI schemes for \"%s\"", iface_name); + + n_plugins = g_list_model_get_n_items (list); + + for (i = 0; i < n_plugins; ++i) { + PeasPluginInfo *info = (PeasPluginInfo *) g_list_model_get_item (list, i); + const gchar *iface_names, *schemes; + gchar **tmp_strv; + gint j; + + if (!(iface_names = peas_plugin_info_get_external_data (info, ENHANCER_INTERFACES))) + continue; + if (!_is_name_listed (iface_name, iface_names)) + continue; + if (!(schemes = peas_plugin_info_get_external_data (info, ENHANCER_SCHEMES))) + continue; + + tmp_strv = g_strsplit (schemes, ";", 0); + + for (j = 0; tmp_strv[j]; ++j) { + const gchar *scheme = tmp_strv[j]; + + if (!found_schemes || !g_slist_find_custom (found_schemes, + scheme, (GCompareFunc) strcmp)) { + found_schemes = g_slist_append (found_schemes, g_strdup (scheme)); + GST_INFO ("Found supported URI scheme: %s", scheme); + } + } + + g_strfreev (tmp_strv); + } + + n_schemes = g_slist_length (found_schemes); + schemes_strv = g_new0 (gchar *, n_schemes + 1); + + for (i = 0; i < n_schemes; ++i) + schemes_strv[i] = g_slist_nth_data (found_schemes, i); + + GST_DEBUG ("Total found URI schemes: %u", n_schemes); + + /* Since string pointers were taken, + * free list without content */ + g_slist_free (found_schemes); + + return schemes_strv; +} + +/* + * clapper_enhancers_loader_check: + * @iface_type: a requested #GType + * @scheme: an URI scheme + * @host: (nullable): an URI host + * @name: (out) (optional) (transfer none): return location for found enhancer name + * + * Checks if any enhancer can handle @uri without initializing loader + * or creating enhancer instance, thus this can be used freely from any thread. + * + * Returns: whether enhancer for given scheme and host is available. + */ +gboolean +clapper_enhancers_loader_check (GType iface_type, + const gchar *scheme, const gchar *host, const gchar **name) +{ + const PeasPluginInfo *info; + + if ((info = clapper_enhancers_loader_get_info (iface_type, scheme, host))) { + if (name) + *name = peas_plugin_info_get_name (info); + + return TRUE; + } + + return FALSE; +} + +/* + * clapper_enhancers_loader_create_enhancer_for_uri: + * @iface_type: a requested #GType + * @uri: a #GUri + * + * Creates a new enhancer object for given URI. + * + * Enhancer should only be created and used within single thread. + * + * Returns: (transfer full) (nullable): a new enhancer instance. + */ +GObject * +clapper_enhancers_loader_create_enhancer_for_uri (GType iface_type, GUri *uri) +{ + GObject *enhancer = NULL; + PeasPluginInfo *info; + const gchar *scheme = g_uri_get_scheme (uri); + const gchar *host = g_uri_get_host (uri); + + if ((info = clapper_enhancers_loader_get_info (iface_type, scheme, host))) { + if (!peas_plugin_info_is_loaded (info) && !peas_engine_load_plugin (_engine, info)) { + GST_ERROR ("Could not load enhancer: %s", peas_plugin_info_get_name (info)); + } else if (!peas_engine_provides_extension (_engine, info, iface_type)) { + GST_ERROR ("No \"%s\" enhancer in plugin: %s", ENHANCER_IFACE_NAME_FROM_TYPE (iface_type), + peas_plugin_info_get_name (info)); + } else { + enhancer = peas_engine_create_extension (_engine, info, iface_type, NULL); + } + } + + return enhancer; +} diff --git a/src/lib/clapper/clapper-extractable-private.h b/src/lib/clapper/clapper-extractable-private.h new file mode 100644 index 000000000..85d6a470f --- /dev/null +++ b/src/lib/clapper/clapper-extractable-private.h @@ -0,0 +1,33 @@ +/* Clapper Playback Library + * Copyright (C) 2024 Rafał Dzięgiel + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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. + */ + +#pragma once + +#include +#include + +#include "clapper-extractable.h" +#include "clapper-harvest.h" + +G_BEGIN_DECLS + +G_GNUC_INTERNAL +gboolean clapper_extractable_extract (ClapperExtractable *extractable, GUri *uri, ClapperHarvest *harvest, GCancellable *cancellable, GError **error); + +G_END_DECLS diff --git a/src/lib/clapper/clapper-extractable.c b/src/lib/clapper/clapper-extractable.c new file mode 100644 index 000000000..64fe178b1 --- /dev/null +++ b/src/lib/clapper/clapper-extractable.c @@ -0,0 +1,61 @@ +/* Clapper Playback Library + * Copyright (C) 2024 Rafał Dzięgiel + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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. + */ + +/** + * ClapperExtractable: + * + * An interface for creating enhancers that resolve given URI into something playable. + * + * Since: 0.8 + */ + +#include + +#include "clapper-extractable-private.h" +#include "clapper-harvest-private.h" + +G_DEFINE_INTERFACE (ClapperExtractable, clapper_extractable, G_TYPE_OBJECT); + +static gboolean +clapper_extractable_default_extract (ClapperExtractable *self, GUri *uri, + ClapperHarvest *harvest, GCancellable *cancellable, GError **error) +{ + if (*error == NULL) { + g_set_error (error, GST_CORE_ERROR, + GST_CORE_ERROR_NOT_IMPLEMENTED, + "Extractable object did not implement extract function"); + } + + return FALSE; +} + +static void +clapper_extractable_default_init (ClapperExtractableInterface *iface) +{ + iface->extract = clapper_extractable_default_extract; +} + +gboolean +clapper_extractable_extract (ClapperExtractable *self, GUri *uri, + ClapperHarvest *harvest, GCancellable *cancellable, GError **error) +{ + ClapperExtractableInterface *iface = CLAPPER_EXTRACTABLE_GET_IFACE (self); + + return iface->extract (self, uri, harvest, cancellable, error); +} diff --git a/src/lib/clapper/clapper-extractable.h b/src/lib/clapper/clapper-extractable.h new file mode 100644 index 000000000..80c3e5975 --- /dev/null +++ b/src/lib/clapper/clapper-extractable.h @@ -0,0 +1,69 @@ +/* Clapper Playback Library + * Copyright (C) 2024 Rafał Dzięgiel + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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. + */ + +#pragma once + +#if !defined(__CLAPPER_INSIDE__) && !defined(CLAPPER_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include +#include + +#include +#include + +G_BEGIN_DECLS + +#define CLAPPER_TYPE_EXTRACTABLE (clapper_extractable_get_type()) +#define CLAPPER_EXTRACTABLE_CAST(obj) ((ClapperExtractable *)(obj)) + +CLAPPER_API +G_DECLARE_INTERFACE (ClapperExtractable, clapper_extractable, CLAPPER, EXTRACTABLE, GObject) + +/** + * ClapperExtractableInterface: + * @parent_iface: The parent interface structure. + * @extract: Extract data and fill harvest. + */ +struct _ClapperExtractableInterface +{ + GTypeInterface parent_iface; + + /** + * ClapperExtractableInterface::extract: + * @extractable: a #ClapperExtractable + * @uri: a #GUri + * @cancellable: a #GCancellable object + * @error: a #GError + * + * Extract data and fill harvest. + * + * Returns: whether extraction was successful. + * + * Since: 0.8 + */ + gboolean (* extract) (ClapperExtractable *extractable, GUri *uri, ClapperHarvest *harvest, GCancellable *cancellable, GError **error); + + /*< private >*/ + gpointer padding[8]; +}; + +G_END_DECLS diff --git a/src/lib/clapper/clapper-functionalities-availability.h.in b/src/lib/clapper/clapper-functionalities-availability.h.in new file mode 100644 index 000000000..bc32d9617 --- /dev/null +++ b/src/lib/clapper/clapper-functionalities-availability.h.in @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2024 Rafał Dzięgiel + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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. + */ + +#pragma once + +#if !defined(__CLAPPER_INSIDE__) && !defined(CLAPPER_COMPILATION) +#error "Only can be included directly." +#endif + +/** + * CLAPPER_WITH_ENHANCERS_LOADER: + * + * Check if Clapper was compiled with Enhancers Loader functionality. + * + * Alternatively, apps before compiling can also check whether `pkgconfig` + * variable named `functionalities` contains `enhancers-loader` string. + * + * Since: 0.8 + */ +#define CLAPPER_WITH_ENHANCERS_LOADER (@CLAPPER_WITH_ENHANCERS_LOADER@) diff --git a/src/lib/clapper/clapper-harvest-private.h b/src/lib/clapper/clapper-harvest-private.h new file mode 100644 index 000000000..e363b69f8 --- /dev/null +++ b/src/lib/clapper/clapper-harvest-private.h @@ -0,0 +1,35 @@ +/* Clapper Playback Library + * Copyright (C) 2024 Rafał Dzięgiel + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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. + */ + +#pragma once + +#include +#include + +#include "clapper-harvest.h" + +G_BEGIN_DECLS + +G_GNUC_INTERNAL +ClapperHarvest * clapper_harvest_new (void); + +G_GNUC_INTERNAL +gboolean clapper_harvest_unpack (ClapperHarvest *harvest, GstBuffer **buffer, gsize *buf_size, GstCaps **caps, GstTagList **tags, GstToc **toc, GstStructure **headers); + +G_END_DECLS diff --git a/src/lib/clapper/clapper-harvest.c b/src/lib/clapper/clapper-harvest.c new file mode 100644 index 000000000..ffd1dd7b4 --- /dev/null +++ b/src/lib/clapper/clapper-harvest.c @@ -0,0 +1,373 @@ +/* Clapper Playback Library + * Copyright (C) 2024 Rafał Dzięgiel + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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. + */ + +/** + * ClapperHarvest: + * + * An object storing data from enhancers implementing [iface@Clapper.Extractable]. + * + * Since: 0.8 + */ + +/* + * NOTE: We cannot simply expose GstMiniObjects for + * implementations to assemble TagList/Toc themselves, see: + * https://gitlab.freedesktop.org/gstreamer/gstreamer/-/issues/2867 + */ + +#include "clapper-harvest-private.h" + +#define REQUEST_HEADERS_STRUCTURE_NAME "request-headers" +#define UA_FIELD_NAME "User-Agent" +#define DEFAULT_UA "Mozilla/5.0 (Windows NT 10.0; rv:102.0) Gecko/20100101 Firefox/102.0" // Tor browser default + +#define GST_CAT_DEFAULT clapper_harvest_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +struct _ClapperHarvest +{ + GstObject parent; + + GstCaps *caps; + GstBuffer *buffer; + gsize buf_size; + + GstTagList *tags; + GstToc *toc; + GstStructure *headers; + + guint16 n_chapters; + guint16 n_tracks; +}; + +#define parent_class clapper_harvest_parent_class +G_DEFINE_TYPE (ClapperHarvest, clapper_harvest, GST_TYPE_OBJECT); + +static inline void +_ensure_tags (ClapperHarvest *self) +{ + if (!self->tags) { + self->tags = gst_tag_list_new_empty (); + gst_tag_list_set_scope (self->tags, GST_TAG_SCOPE_GLOBAL); + } +} + +static inline void +_ensure_toc (ClapperHarvest *self) +{ + if (!self->toc) + self->toc = gst_toc_new (GST_TOC_SCOPE_GLOBAL); +} + +static inline void +_ensure_headers (ClapperHarvest *self) +{ + if (!self->headers) + self->headers = gst_structure_new_empty (REQUEST_HEADERS_STRUCTURE_NAME); +} + +ClapperHarvest * +clapper_harvest_new (void) +{ + ClapperHarvest *harvest; + + harvest = g_object_new (CLAPPER_TYPE_HARVEST, NULL); + gst_object_ref_sink (harvest); + + return harvest; +} + +gboolean +clapper_harvest_unpack (ClapperHarvest *self, + GstBuffer **buffer, gsize *buf_size, GstCaps **caps, + GstTagList **tags, GstToc **toc, GstStructure **headers) +{ + /* Not filled or already unpacked */ + if (!self->buffer) + return FALSE; + + *buffer = self->buffer; + self->buffer = NULL; + + *buf_size = self->buf_size; + self->buf_size = 0; + + *caps = self->caps; + self->caps = NULL; + + *tags = self->tags; + self->tags = NULL; + + *toc = self->toc; + self->toc = NULL; + + *headers = self->headers; + self->headers = NULL; + + /* If extractable did not set "User-Agent", use default */ + if (*headers && !gst_structure_has_field (*headers, UA_FIELD_NAME)) + gst_structure_set (*headers, UA_FIELD_NAME, G_TYPE_STRING, DEFAULT_UA, NULL); + + return TRUE; +} + +/** + * clapper_harvest_fill: + * @harvest: a #ClapperHarvest + * @media_type: media mime type + * @data: (array length=size) (element-type guint8) (transfer full): data to fill @harvest + * @size: allocated size of @data + * + * Fill harvest with extracted data. It can be anything that GStreamer + * can parse and play such as single URI or a streaming manifest. + * + * Calling again this function will replace previously filled content. + * + * Commonly used media types are: + * + * * `application/dash+xml` + * + * * `application/x-hls` + * + * * `text/uri-list` + * + * Returns: %TRUE if taken data was not empty, %FALSE otherwise. + * + * Since: 0.8 + */ +gboolean +clapper_harvest_fill (ClapperHarvest *self, const gchar *media_type, gpointer data, gsize size) +{ + g_return_val_if_fail (CLAPPER_IS_HARVEST (self), FALSE); + g_return_val_if_fail (media_type != NULL, FALSE); + g_return_val_if_fail (data != NULL, FALSE); + + if (!data || size == 0) { + g_free (data); + return FALSE; + } + + if (gst_debug_category_get_threshold (GST_CAT_DEFAULT) >= GST_LEVEL_DEBUG) { + gboolean is_printable = (strcmp (media_type, "application/dash+xml") == 0) + || (strcmp (media_type, "application/x-hls") == 0) + || (strcmp (media_type, "text/uri-list") == 0); + + if (is_printable) { + gchar *data_str; + + data_str = g_new0 (gchar, size + 1); + memcpy (data_str, data, size); + + GST_DEBUG_OBJECT (self, "Filled with data:\n%s", data_str); + + g_free (data_str); + } + } + + gst_clear_buffer (&self->buffer); + gst_clear_caps (&self->caps); + + self->buffer = gst_buffer_new_wrapped (data, size); + self->buf_size = size; + self->caps = gst_caps_new_simple (media_type, + "source", G_TYPE_STRING, "clapper-extractor", NULL); + + return TRUE; +} + +/** + * clapper_harvest_tags_add: + * @harvest: a #ClapperHarvest + * @tag: a name of tag to set + * @...: %NULL-terminated list of arguments + * + * Append one or more tags into the tag list. + * + * Variable arguments should be in the form of tag and value pairs. + * + * Since: 0.8 + */ +void +clapper_harvest_tags_add (ClapperHarvest *self, const gchar *tag, ...) +{ + va_list args; + + g_return_if_fail (CLAPPER_IS_HARVEST (self)); + g_return_if_fail (tag != NULL); + + _ensure_tags (self); + + va_start (args, tag); + gst_tag_list_add_valist (self->tags, GST_TAG_MERGE_APPEND, tag, args); + va_end (args); +} + +/** + * clapper_harvest_tags_add_value: (rename-to clapper_harvest_tags_add) + * @harvest: a #ClapperHarvest + * @tag: a name of tag to set + * @value: a #GValue of tag + * + * Append another tag into the tag list using #GValue. + * + * Since: 0.8 + */ +void +clapper_harvest_tags_add_value (ClapperHarvest *self, const gchar *tag, const GValue *value) +{ + g_return_if_fail (CLAPPER_IS_HARVEST (self)); + g_return_if_fail (tag != NULL); + g_return_if_fail (G_IS_VALUE (value)); + + _ensure_tags (self); + gst_tag_list_add_value (self->tags, GST_TAG_MERGE_APPEND, tag, value); +} + +/** + * clapper_harvest_toc_add: + * @harvest: a #ClapperHarvest + * @type: a #GstTocEntryType + * @title: an entry title + * @start: entry start time in seconds + * @end: entry end time in seconds or -1 if none + * + * Append a chapter or track name into table of contents. + * + * Since: 0.8 + */ +void +clapper_harvest_toc_add (ClapperHarvest *self, GstTocEntryType type, + const gchar *title, gdouble start, gdouble end) +{ + GstTocEntry *entry, *subentry; + GstClockTime start_time, end_time; + gchar edition[3]; // 2 + 1 + gchar id[14]; // 7 + 1 + 5 + 1 + const gchar *id_prefix; + guint16 nth_entry; + + g_return_if_fail (CLAPPER_IS_HARVEST (self)); + g_return_if_fail (type == GST_TOC_ENTRY_TYPE_CHAPTER || type == GST_TOC_ENTRY_TYPE_TRACK); + g_return_if_fail (title != NULL); + g_return_if_fail (start >= 0 && end >= start); + + switch (type) { + case GST_TOC_ENTRY_TYPE_CHAPTER: + id_prefix = "chapter"; + nth_entry = ++(self->n_chapters); + break; + case GST_TOC_ENTRY_TYPE_TRACK: + id_prefix = "track"; + nth_entry = ++(self->n_tracks); + break; + default: + g_assert_not_reached (); + return; + } + + start_time = start * GST_SECOND; + end_time = (end >= 0) ? end * GST_SECOND : GST_CLOCK_TIME_NONE; + + g_snprintf (edition, sizeof (edition), "0%i", type); + g_snprintf (id, sizeof (id), "%s.%" G_GUINT16_FORMAT, id_prefix, nth_entry); + + GST_DEBUG_OBJECT (self, "Inserting TOC %s: \"%s\"" + " (%" G_GUINT64_FORMAT "-%" G_GUINT64_FORMAT ")", + id, title, start_time, end_time); + + subentry = gst_toc_entry_new (type, id); + gst_toc_entry_set_tags (subentry, gst_tag_list_new (GST_TAG_TITLE, title, NULL)); + gst_toc_entry_set_start_stop_times (subentry, start_time, end_time); + + _ensure_toc (self); + +find_entry: + if (!(entry = gst_toc_find_entry (self->toc, edition))) { + GstTocEntry *toc_entry; + + toc_entry = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_EDITION, edition); + gst_toc_entry_set_start_stop_times (toc_entry, 0, GST_CLOCK_TIME_NONE); + gst_toc_append_entry (self->toc, toc_entry); // transfer full and must be writable + + goto find_entry; + } + + gst_toc_entry_append_sub_entry (entry, subentry); +} + +/** + * clapper_harvest_headers_set: + * @harvest: a #ClapperHarvest + * @key: a header name + * @val: a header value + * + * Set the request header named with @key to specified @val. + * + * Calling this again on the same key will update the value to the new one. + * + * Since: 0.8 + */ +void +clapper_harvest_headers_set (ClapperHarvest *self, const gchar *key, const gchar *val) +{ + g_return_if_fail (CLAPPER_IS_HARVEST (self)); + g_return_if_fail (key != NULL); + g_return_if_fail (val != NULL); + + GST_DEBUG_OBJECT (self, "Set header: \"%s\": \"%s\"", key, val); + + _ensure_headers (self); + gst_structure_set (self->headers, key, G_TYPE_STRING, val, NULL); +} + +static void +clapper_harvest_init (ClapperHarvest *self) +{ +} + +static void +clapper_harvest_finalize (GObject *object) +{ + ClapperHarvest *self = CLAPPER_HARVEST_CAST (object); + + GST_TRACE_OBJECT (self, "Finalize"); + + gst_clear_caps (&self->caps); + gst_clear_buffer (&self->buffer); + + if (self->tags) + gst_tag_list_unref (self->tags); + if (self->toc) + gst_toc_unref (self->toc); + if (self->headers) + gst_structure_free (self->headers); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +clapper_harvest_class_init (ClapperHarvestClass *klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + + GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperharvest", 0, + "Clapper Harvest"); + + gobject_class->finalize = clapper_harvest_finalize; +} diff --git a/src/lib/clapper/clapper-harvest.h b/src/lib/clapper/clapper-harvest.h new file mode 100644 index 000000000..5bd2bc954 --- /dev/null +++ b/src/lib/clapper/clapper-harvest.h @@ -0,0 +1,55 @@ +/* Clapper Playback Library + * Copyright (C) 2024 Rafał Dzięgiel + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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. + */ + +#pragma once + +#if !defined(__CLAPPER_INSIDE__) && !defined(CLAPPER_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include +#include +#include + +#include + +G_BEGIN_DECLS + +#define CLAPPER_TYPE_HARVEST (clapper_harvest_get_type()) +#define CLAPPER_HARVEST_CAST(obj) ((ClapperHarvest *)(obj)) + +G_DECLARE_FINAL_TYPE (ClapperHarvest, clapper_harvest, CLAPPER, HARVEST, GstObject) + +CLAPPER_API +gboolean clapper_harvest_fill (ClapperHarvest *harvest, const gchar *media_type, gpointer data, gsize size); + +CLAPPER_API +void clapper_harvest_tags_add (ClapperHarvest *harvest, const gchar *tag, ...) G_GNUC_NULL_TERMINATED; + +CLAPPER_API +void clapper_harvest_tags_add_value (ClapperHarvest *harvest, const gchar *tag, const GValue *value); + +CLAPPER_API +void clapper_harvest_toc_add (ClapperHarvest *harvest, GstTocEntryType type, const gchar *title, gdouble start, gdouble end); + +CLAPPER_API +void clapper_harvest_headers_set (ClapperHarvest *harvest, const gchar *key, const gchar *val); + +G_END_DECLS diff --git a/src/lib/clapper/clapper.c b/src/lib/clapper/clapper.c index 9b14474a9..dfc50955e 100644 --- a/src/lib/clapper/clapper.c +++ b/src/lib/clapper/clapper.c @@ -17,6 +17,8 @@ * Boston, MA 02110-1301, USA. */ +#include "config.h" + #include #include @@ -25,6 +27,11 @@ #include "clapper-playbin-bus-private.h" #include "clapper-app-bus-private.h" #include "clapper-features-bus-private.h" +#include "gst/clapper-plugin-private.h" + +#if CLAPPER_WITH_ENHANCERS_LOADER +#include "clapper-enhancers-loader-private.h" +#endif static gboolean is_initialized = FALSE; static GMutex init_lock; @@ -44,6 +51,22 @@ clapper_init_check_internal (int *argc, char **argv[]) clapper_app_bus_initialize (); clapper_features_bus_initialize (); +#if CLAPPER_WITH_ENHANCERS_LOADER + clapper_enhancers_loader_initialize (); +#endif + + gst_plugin_register_static ( + GST_VERSION_MAJOR, + GST_VERSION_MINOR, + PACKAGE "internal", + PLUGIN_DESC, + (GstPluginInitFunc) clapper_gst_plugin_init, + PACKAGE_VERSION, + PLUGIN_LICENSE, + PACKAGE, + PACKAGE, + PACKAGE_ORIGIN); + is_initialized = TRUE; finish: @@ -92,3 +115,52 @@ clapper_init_check (int *argc, char **argv[]) { return clapper_init_check_internal (argc, argv); } + +/** + * clapper_enhancer_check: + * @iface_type: an interface #GType + * @scheme: an URI scheme + * @host: (nullable): an URI host + * @name: (out) (optional) (transfer none): return location for found enhancer name + * + * Check if an enhancer of @type is available for given @scheme and @host. + * + * A check that compares requested capabilites of all available Clapper enhancers, + * thus it is fast but does not guarantee that the found one will succeed. Please note + * that this function will always return %FALSE if Clapper was built without enhancers + * loader functionality. To check that, use [const@Clapper.WITH_ENHANCERS_LOADER]. + * + * This function can be used to quickly determine early if Clapper will at least try to + * handle URI and with one of its enhancers and which one. + * + * Example: + * + * ```c + * gboolean supported = clapper_enhancer_check (CLAPPER_TYPE_EXTRACTABLE, "https", "example.com", NULL); + * ``` + * + * For self hosted services a custom URI @scheme without @host can be used. Enhancers should announce + * support for such schemes by defining them in their plugin info files. + * + * ```c + * gboolean supported = clapper_enhancer_check (CLAPPER_TYPE_EXTRACTABLE, "example", NULL, NULL); + * ``` + * + * Returns: whether a plausible enhancer was found. + * + * Since: 0.8 + */ +gboolean +clapper_enhancer_check (GType iface_type, const gchar *scheme, const gchar *host, const gchar **name) +{ + gboolean success = FALSE; + + g_return_val_if_fail (G_TYPE_IS_INTERFACE (iface_type), FALSE); + g_return_val_if_fail (scheme != NULL, FALSE); + +#if CLAPPER_WITH_ENHANCERS_LOADER + success = clapper_enhancers_loader_check (iface_type, scheme, host, name); +#endif + + return success; +} diff --git a/src/lib/clapper/clapper.h b/src/lib/clapper/clapper.h index 62c9e5246..dd5491e5d 100644 --- a/src/lib/clapper/clapper.h +++ b/src/lib/clapper/clapper.h @@ -19,6 +19,9 @@ #pragma once +#include +#include + #define __CLAPPER_INSIDE__ #include @@ -28,6 +31,7 @@ #include #include +#include #include #include #include @@ -40,6 +44,9 @@ #include #include +#include + +#include #include #if CLAPPER_HAVE_DISCOVERER @@ -60,6 +67,9 @@ void clapper_init (int *argc, char **argv[]); CLAPPER_API gboolean clapper_init_check (int *argc, char **argv[]); +CLAPPER_API +gboolean clapper_enhancer_check (GType iface_type, const gchar *scheme, const gchar *host, const gchar **name); + G_END_DECLS #undef __CLAPPER_INSIDE__ diff --git a/src/lib/clapper/features/meson.build b/src/lib/clapper/features/meson.build index 72a9348ee..984df51d6 100644 --- a/src/lib/clapper/features/meson.build +++ b/src/lib/clapper/features/meson.build @@ -14,7 +14,7 @@ clapper_possible_features = [ foreach feature_name : clapper_possible_features subdir(feature_name) features_availability_conf.set( - 'CLAPPER_HAVE_@0@'.format(feature_name.to_upper()), + 'CLAPPER_HAVE_@0@'.format(feature_name.replace('-', '_').to_upper()), clapper_available_features.contains(feature_name) ? 'TRUE' : 'FALSE' ) endforeach diff --git a/src/lib/clapper/gst/clapper-extractor-director-private.h b/src/lib/clapper/gst/clapper-extractor-director-private.h new file mode 100644 index 000000000..c9e595ba6 --- /dev/null +++ b/src/lib/clapper/gst/clapper-extractor-director-private.h @@ -0,0 +1,43 @@ +/* Clapper Playback Library + * Copyright (C) 2024 Rafał Dzięgiel + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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. + */ + +#pragma once + +#include +#include +#include + +#include "../clapper-threaded-object.h" +#include "../clapper-harvest.h" + +G_BEGIN_DECLS + +#define CLAPPER_TYPE_EXTRACTOR_DIRECTOR (clapper_extractor_director_get_type()) +#define CLAPPER_EXTRACTOR_DIRECTOR_CAST(obj) ((ClapperExtractorDirector *)(obj)) + +G_GNUC_INTERNAL +G_DECLARE_FINAL_TYPE (ClapperExtractorDirector, clapper_extractor_director, CLAPPER, EXTRACTOR_DIRECTOR, ClapperThreadedObject) + +G_GNUC_INTERNAL +ClapperExtractorDirector * clapper_extractor_director_new (void); + +G_GNUC_INTERNAL +ClapperHarvest * clapper_extractor_director_extract (ClapperExtractorDirector *director, GUri *uri, GCancellable *cancellable, GError **error); + +G_END_DECLS diff --git a/src/lib/clapper/gst/clapper-extractor-director.c b/src/lib/clapper/gst/clapper-extractor-director.c new file mode 100644 index 000000000..e798c8ea8 --- /dev/null +++ b/src/lib/clapper/gst/clapper-extractor-director.c @@ -0,0 +1,181 @@ +/* Clapper Playback Library + * Copyright (C) 2024 Rafał Dzięgiel + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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. + */ + +#include + +#include "clapper-extractor-director-private.h" +#include "../clapper-enhancers-loader-private.h" +#include "../clapper-extractable-private.h" +#include "../clapper-harvest-private.h" +#include "../../shared/clapper-shared-utils-private.h" + +#define GST_CAT_DEFAULT clapper_extractor_director_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +struct _ClapperExtractorDirector +{ + ClapperThreadedObject parent; +}; + +#define parent_class clapper_extractor_director_parent_class +G_DEFINE_TYPE (ClapperExtractorDirector, clapper_extractor_director, CLAPPER_TYPE_THREADED_OBJECT); + +typedef struct +{ + ClapperExtractorDirector *director; + GUri *uri; + GCancellable *cancellable; + GError **error; +} ClapperExtractorDirectorData; + +static gpointer +clapper_extractor_director_extract_in_thread (ClapperExtractorDirectorData *data) +{ + //ClapperExtractorDirector *self = data->director; + ClapperExtractable *extractable = NULL; + ClapperHarvest *harvest = clapper_harvest_new (); + gboolean success = FALSE, cached = FALSE; + + /* Cancelled during thread switching */ + if (g_cancellable_is_cancelled (data->cancellable)) + goto finish; + + /* TODO: Cache lookup */ + if (cached) { + // success = fill harvest from cache + goto finish; + } + + extractable = CLAPPER_EXTRACTABLE_CAST (clapper_enhancers_loader_create_enhancer_for_uri ( + CLAPPER_TYPE_EXTRACTABLE, data->uri)); + + /* Check just before extract */ + if (g_cancellable_is_cancelled (data->cancellable)) + goto finish; + + success = clapper_extractable_extract (extractable, data->uri, + harvest, data->cancellable, data->error); + + /* Cancelled during extract */ + if (g_cancellable_is_cancelled (data->cancellable)) { + success = FALSE; + goto finish; + } + +finish: + if (success) { + if (!cached) { + /* TODO: Store in cache */ + } + } else { + gst_clear_object (&harvest); + + /* Ensure we have some error set on failure */ + if (*data->error == NULL) { + const gchar *err_msg = (g_cancellable_is_cancelled (data->cancellable)) + ? "Extraction was cancelled" + : "Extraction failed"; + g_set_error (data->error, GST_RESOURCE_ERROR, + GST_RESOURCE_ERROR_FAILED, "%s", err_msg); + } + } + + gst_clear_object (&extractable); + + return harvest; +} + +/* + * clapper_extractor_director_new: + * + * Returns: (transfer full): a new #ClapperExtractorDirector instance. + */ +ClapperExtractorDirector * +clapper_extractor_director_new (void) +{ + ClapperExtractorDirector *director; + + director = g_object_new (CLAPPER_TYPE_EXTRACTOR_DIRECTOR, NULL); + gst_object_ref_sink (director); + + return director; +} + +ClapperHarvest * +clapper_extractor_director_extract (ClapperExtractorDirector *self, GUri *uri, + GCancellable *cancellable, GError **error) +{ + ClapperExtractorDirectorData *data = g_new (ClapperExtractorDirectorData, 1); + + data->director = self; + data->uri = uri; + data->cancellable = cancellable; + data->error = error; + + return CLAPPER_HARVEST_CAST (clapper_shared_utils_context_invoke_sync_full ( + clapper_threaded_object_get_context (CLAPPER_THREADED_OBJECT_CAST (self)), + (GThreadFunc) clapper_extractor_director_extract_in_thread, + data, (GDestroyNotify) g_free)); +} + +static void +clapper_extractor_director_thread_start (ClapperThreadedObject *threaded_object) +{ + ClapperExtractorDirector *self = CLAPPER_EXTRACTOR_DIRECTOR_CAST (threaded_object); + + GST_TRACE_OBJECT (self, "Extractor director thread start"); +} + +static void +clapper_extractor_director_thread_stop (ClapperThreadedObject *threaded_object) +{ + ClapperExtractorDirector *self = CLAPPER_EXTRACTOR_DIRECTOR_CAST (threaded_object); + + GST_TRACE_OBJECT (self, "Extractor director thread stop"); +} + +static void +clapper_extractor_director_init (ClapperExtractorDirector *self) +{ +} + +static void +clapper_extractor_director_finalize (GObject *object) +{ + ClapperExtractorDirector *self = CLAPPER_EXTRACTOR_DIRECTOR_CAST (object); + + GST_TRACE_OBJECT (self, "Finalize"); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +clapper_extractor_director_class_init (ClapperExtractorDirectorClass *klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + ClapperThreadedObjectClass *threaded_object = (ClapperThreadedObjectClass *) klass; + + GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperextractordirector", 0, + "Clapper Extractor Director"); + + gobject_class->finalize = clapper_extractor_director_finalize; + + threaded_object->thread_start = clapper_extractor_director_thread_start; + threaded_object->thread_stop = clapper_extractor_director_thread_stop; +} diff --git a/src/lib/clapper/gst/clapper-extractor-src-private.h b/src/lib/clapper/gst/clapper-extractor-src-private.h new file mode 100644 index 000000000..501e42c29 --- /dev/null +++ b/src/lib/clapper/gst/clapper-extractor-src-private.h @@ -0,0 +1,36 @@ +/* Clapper Playback Library + * Copyright (C) 2024 Rafał Dzięgiel + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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. + */ + +#pragma once + +#include +#include +#include + +G_BEGIN_DECLS + +#define CLAPPER_TYPE_EXTRACTOR_SRC (clapper_extractor_src_get_type()) +#define CLAPPER_EXTRACTOR_SRC_CAST(obj) ((ClapperExtractorSrc *)(obj)) + +G_GNUC_INTERNAL +G_DECLARE_FINAL_TYPE (ClapperExtractorSrc, clapper_extractor_src, CLAPPER, EXTRACTOR_SRC, GstPushSrc) + +GST_ELEMENT_REGISTER_DECLARE (clapperextractorsrc) + +G_END_DECLS diff --git a/src/lib/clapper/gst/clapper-extractor-src.c b/src/lib/clapper/gst/clapper-extractor-src.c new file mode 100644 index 000000000..990862e0c --- /dev/null +++ b/src/lib/clapper/gst/clapper-extractor-src.c @@ -0,0 +1,491 @@ +/* Clapper Playback Library + * Copyright (C) 2024 Rafał Dzięgiel + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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. + */ + +#include "config.h" + +#include "clapper-extractor-src-private.h" +#include "clapper-extractor-director-private.h" + +#include "../clapper-extractable-private.h" +#include "../clapper-harvest-private.h" +#include "../clapper-enhancers-loader-private.h" + +#define GST_CAT_DEFAULT clapper_extractor_src_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +struct _ClapperExtractorSrc +{ + GstPushSrc parent; + + GCancellable *cancellable; + gsize buf_size; + + ClapperExtractorDirector *director; + + gchar *uri; + GUri *guri; +}; + +enum +{ + PROP_0, + PROP_URI, + PROP_LAST +}; + +static GParamSpec *param_specs[PROP_LAST] = { NULL, }; + +static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +static GstURIType +clapper_extractor_src_uri_handler_get_type (GType type) +{ + return GST_URI_SRC; +} + +static gpointer +_get_schemes_once (gpointer data) +{ + return clapper_enhancers_loader_get_schemes (GPOINTER_TO_SIZE (data)); +} + +static const gchar *const * +clapper_extractor_src_uri_handler_get_protocols (GType type) +{ + static GOnce schemes_once = G_ONCE_INIT; + + g_once (&schemes_once, _get_schemes_once, GSIZE_TO_POINTER (CLAPPER_TYPE_EXTRACTABLE)); + return (const gchar *const *) schemes_once.retval; +} + +static gchar * +clapper_extractor_src_uri_handler_get_uri (GstURIHandler *handler) +{ + ClapperExtractorSrc *self = CLAPPER_EXTRACTOR_SRC_CAST (handler); + gchar *uri; + + GST_OBJECT_LOCK (self); + uri = g_strdup (self->uri); + GST_OBJECT_UNLOCK (self); + + return uri; +} + +static gboolean +clapper_extractor_src_uri_handler_set_uri (GstURIHandler *handler, + const gchar *uri, GError **error) +{ + ClapperExtractorSrc *self = CLAPPER_EXTRACTOR_SRC_CAST (handler); + GUri *guri; + const gchar *const *protocols; + gboolean supported = FALSE; + guint i; + + GST_DEBUG_OBJECT (self, "Changing URI to: %s", uri); + + if (!uri) { + g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI, + "URI property cannot be NULL"); + return FALSE; + } + if (GST_STATE (GST_ELEMENT_CAST (self)) >= GST_STATE_PAUSED) { + g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_STATE, + "Cannot change URI property while element is running"); + return FALSE; + } + + protocols = gst_uri_handler_get_protocols (handler); + for (i = 0; protocols[i]; ++i) { + if ((supported = gst_uri_has_protocol (uri, protocols[i]))) + break; + } + if (!supported) { + g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_UNSUPPORTED_PROTOCOL, + "URI protocol is not supported"); + return FALSE; + } + + if (!(guri = g_uri_parse (uri, G_URI_FLAGS_ENCODED, NULL))) { + g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI, + "URI is invalid"); + return FALSE; + } + + if (!clapper_enhancers_loader_check (CLAPPER_TYPE_EXTRACTABLE, + g_uri_get_scheme (guri), g_uri_get_host (guri), NULL)) { + g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI, + "None of the available enhancers can handle this URI"); + g_uri_unref (guri); + + return FALSE; + } + + GST_OBJECT_LOCK (self); + + g_set_str (&self->uri, uri); + g_clear_pointer (&self->guri, g_uri_unref); + self->guri = guri; + + GST_INFO_OBJECT (self, "URI changed to: \"%s\"", self->uri); + + GST_OBJECT_UNLOCK (self); + + return TRUE; +} + +static void +_uri_handler_iface_init (GstURIHandlerInterface *iface) +{ + iface->get_type = clapper_extractor_src_uri_handler_get_type; + iface->get_protocols = clapper_extractor_src_uri_handler_get_protocols; + iface->get_uri = clapper_extractor_src_uri_handler_get_uri; + iface->set_uri = clapper_extractor_src_uri_handler_set_uri; +} + +#define parent_class clapper_extractor_src_parent_class +G_DEFINE_TYPE_WITH_CODE (ClapperExtractorSrc, clapper_extractor_src, GST_TYPE_PUSH_SRC, + G_IMPLEMENT_INTERFACE (GST_TYPE_URI_HANDLER, _uri_handler_iface_init)); +GST_ELEMENT_REGISTER_DEFINE (clapperextractorsrc, "clapperextractorsrc", + GST_RANK_PRIMARY + 20, CLAPPER_TYPE_EXTRACTOR_SRC); + +static gboolean +clapper_extractor_src_start (GstBaseSrc *base_src) +{ + ClapperExtractorSrc *self = CLAPPER_EXTRACTOR_SRC_CAST (base_src); + gboolean can_start; + + GST_DEBUG_OBJECT (self, "Start"); + + GST_OBJECT_LOCK (self); + can_start = (self->guri != NULL); + GST_OBJECT_UNLOCK (self); + + if (G_UNLIKELY (!can_start)) { + GST_ELEMENT_ERROR (self, RESOURCE, OPEN_READ, + ("No media URI"), (NULL)); + return FALSE; + } + + return TRUE; +} + +static gboolean +clapper_extractor_src_stop (GstBaseSrc *base_src) +{ + ClapperExtractorSrc *self = CLAPPER_EXTRACTOR_SRC_CAST (base_src); + + GST_DEBUG_OBJECT (self, "Stop"); + + self->buf_size = 0; + + return TRUE; +} + +static gboolean +clapper_extractor_src_get_size (GstBaseSrc *base_src, guint64 *size) +{ + ClapperExtractorSrc *self = CLAPPER_EXTRACTOR_SRC_CAST (base_src); + + if (self->buf_size > 0) { + *size = self->buf_size; + return TRUE; + } + + return FALSE; +} + +static gboolean +clapper_extractor_src_is_seekable (GstBaseSrc *base_src) +{ + return FALSE; +} + +static gboolean +clapper_extractor_src_unlock (GstBaseSrc *base_src) +{ + ClapperExtractorSrc *self = CLAPPER_EXTRACTOR_SRC_CAST (base_src); + + GST_LOG_OBJECT (self, "Cancel triggered"); + g_cancellable_cancel (self->cancellable); + + return TRUE; +} + +static gboolean +clapper_extractor_src_unlock_stop (GstBaseSrc *base_src) +{ + ClapperExtractorSrc *self = CLAPPER_EXTRACTOR_SRC_CAST (base_src); + + GST_LOG_OBJECT (self, "Resetting cancellable"); + + g_object_unref (self->cancellable); + self->cancellable = g_cancellable_new (); + + return TRUE; +} + +/* Pushes extracted tags, toc and request headers downstream (all transfer full) */ +static void +_push_events (ClapperExtractorSrc *self, GstTagList *tags, GstToc *toc, + GstStructure *headers, gboolean updated) +{ + GstEvent *event; + + if (tags) { + if (!gst_tag_list_is_empty (tags)) { + GST_DEBUG_OBJECT (self, "Pushing %" GST_PTR_FORMAT, tags); + event = gst_event_new_tag (gst_tag_list_ref (tags)); + gst_pad_push_event (GST_BASE_SRC_PAD (self), event); + + /* FIXME: We should only be posting event to make it reach app + * after stream start, but currently it is lost that way */ + gst_element_post_message (GST_ELEMENT (self), + gst_message_new_tag (NULL, tags)); + } else { + gst_tag_list_unref (tags); + } + } + + if (toc) { + if (g_list_length (gst_toc_get_entries (toc)) > 0) { + GST_DEBUG_OBJECT (self, "Pushing TOC"); // TOC is not printable + event = gst_event_new_toc (toc, updated); + gst_pad_push_event (GST_BASE_SRC_PAD (self), event); + + /* FIXME: We should only be posting event to make it reach app + * after stream start, but currently it is lost that way */ + gst_element_post_message (GST_ELEMENT (self), + gst_message_new_toc (NULL, toc, updated)); + } + gst_toc_unref (toc); + } + + if (headers) { + GST_DEBUG_OBJECT (self, "Pushing %" GST_PTR_FORMAT, headers); + event = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM_STICKY, headers); + gst_pad_push_event (GST_BASE_SRC_PAD (self), event); + } + + GST_DEBUG_OBJECT (self, "Pushed all events"); +} + +static GstFlowReturn +clapper_extractor_src_create (GstPushSrc *push_src, GstBuffer **outbuf) +{ + ClapperExtractorSrc *self = CLAPPER_EXTRACTOR_SRC_CAST (push_src); + GUri *guri; + GCancellable *cancellable; + ClapperHarvest *harvest; + GstCaps *caps = NULL; + GstTagList *tags = NULL; + GstToc *toc = NULL; + GstStructure *headers = NULL; + GError *error = NULL; + gboolean unpacked; + + /* When non-zero, we already returned complete data */ + if (self->buf_size > 0) + return GST_FLOW_EOS; + + /* Ensure director is created. Since it spins up its own + * thread, create it here as we know that it will be used. */ + if (!self->director) + self->director = clapper_extractor_director_new (); + + GST_OBJECT_LOCK (self); + guri = g_uri_ref (self->guri); + cancellable = g_object_ref (self->cancellable); + GST_OBJECT_UNLOCK (self); + + harvest = clapper_extractor_director_extract (self->director, guri, cancellable, &error); + + g_uri_unref (guri); + g_object_unref (cancellable); + + if (!harvest) { + GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND, + ("%s", error->message), (NULL)); + g_clear_error (&error); + + return GST_FLOW_ERROR; + } + + unpacked = clapper_harvest_unpack (harvest, outbuf, &self->buf_size, + &caps, &tags, &toc, &headers); + gst_object_unref (harvest); + + if (!unpacked) { + GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND, + ("Extraction harvest is empty"), (NULL)); + return GST_FLOW_ERROR; + } + + if (gst_base_src_set_caps (GST_BASE_SRC (self), caps)) + GST_INFO_OBJECT (self, "Using caps: %" GST_PTR_FORMAT, caps); + else + GST_ERROR_OBJECT (self, "Current caps could not be set"); + + gst_clear_caps (&caps); + + /* Now push all events before buffer */ + _push_events (self, tags, toc, headers, FALSE); + + return GST_FLOW_OK; +} + +static inline gboolean +_handle_uri_query (GstQuery *query) +{ + /* Since our URI does not actually lead to manifest data, we answer + * with "nodata" equivalent, so upstream will not try to fetch it */ + gst_query_set_uri (query, "data:,"); + + return TRUE; +} + +static gboolean +clapper_extractor_src_query (GstBaseSrc *base_src, GstQuery *query) +{ + gboolean ret = FALSE; + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_URI: + ret = _handle_uri_query (query); + break; + default: + break; + } + + if (!ret) + ret = GST_BASE_SRC_CLASS (parent_class)->query (base_src, query); + + return ret; +} + +static void +clapper_extractor_src_init (ClapperExtractorSrc *self) +{ + self->cancellable = g_cancellable_new (); +} + +static void +clapper_extractor_src_dispose (GObject *object) +{ + ClapperExtractorSrc *self = CLAPPER_EXTRACTOR_SRC_CAST (object); + + GST_OBJECT_LOCK (self); + g_clear_object (&self->director); + GST_OBJECT_UNLOCK (self); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +clapper_extractor_src_finalize (GObject *object) +{ + ClapperExtractorSrc *self = CLAPPER_EXTRACTOR_SRC_CAST (object); + + GST_TRACE_OBJECT (self, "Finalize"); + + g_clear_object (&self->cancellable); + g_free (self->uri); + g_clear_pointer (&self->guri, g_uri_unref); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +clapper_extractor_src_set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + ClapperExtractorSrc *self = CLAPPER_EXTRACTOR_SRC_CAST (object); + + switch (prop_id) { + case PROP_URI:{ + GError *error = NULL; + if (!gst_uri_handler_set_uri (GST_URI_HANDLER (self), + g_value_get_string (value), &error)) { + GST_ERROR_OBJECT (self, "%s", error->message); + g_error_free (error); + } + break; + } + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +clapper_extractor_src_get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + ClapperExtractorSrc *self = CLAPPER_EXTRACTOR_SRC_CAST (object); + + switch (prop_id) { + case PROP_URI: + g_value_take_string (value, gst_uri_handler_get_uri (GST_URI_HANDLER (self))); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +clapper_extractor_src_class_init (ClapperExtractorSrcClass *klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + GstElementClass *gstelement_class = (GstElementClass *) klass; + GstBaseSrcClass *gstbasesrc_class = (GstBaseSrcClass *) klass; + GstPushSrcClass *gstpushsrc_class = (GstPushSrcClass *) klass; + + GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperextractorsrc", 0, + "Clapper Extractor Source"); + + gobject_class->set_property = clapper_extractor_src_set_property; + gobject_class->get_property = clapper_extractor_src_get_property; + gobject_class->dispose = clapper_extractor_src_dispose; + gobject_class->finalize = clapper_extractor_src_finalize; + + gstbasesrc_class->start = GST_DEBUG_FUNCPTR (clapper_extractor_src_start); + gstbasesrc_class->stop = GST_DEBUG_FUNCPTR (clapper_extractor_src_stop); + gstbasesrc_class->get_size = GST_DEBUG_FUNCPTR (clapper_extractor_src_get_size); + gstbasesrc_class->is_seekable = GST_DEBUG_FUNCPTR (clapper_extractor_src_is_seekable); + gstbasesrc_class->unlock = GST_DEBUG_FUNCPTR (clapper_extractor_src_unlock); + gstbasesrc_class->unlock_stop = GST_DEBUG_FUNCPTR (clapper_extractor_src_unlock_stop); + gstbasesrc_class->query = GST_DEBUG_FUNCPTR (clapper_extractor_src_query); + + gstpushsrc_class->create = GST_DEBUG_FUNCPTR (clapper_extractor_src_create); + + param_specs[PROP_URI] = g_param_spec_string ("uri", + "URI", "Media URI", NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, PROP_LAST, param_specs); + + gst_element_class_add_static_pad_template (gstelement_class, &src_template); + + gst_element_class_set_static_metadata (gstelement_class, "Clapper Extractor Source", + "Source", "A source element that uses extractables to gain data", + "Rafał Dzięgiel "); +} diff --git a/src/lib/clapper/gst/clapper-plugin-private.h b/src/lib/clapper/gst/clapper-plugin-private.h new file mode 100644 index 000000000..eca578ed7 --- /dev/null +++ b/src/lib/clapper/gst/clapper-plugin-private.h @@ -0,0 +1,30 @@ +/* Clapper Playback Library + * Copyright (C) 2024 Rafał Dzięgiel + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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. + */ + +#pragma once + +#include +#include + +G_BEGIN_DECLS + +G_GNUC_INTERNAL +gboolean clapper_gst_plugin_init (GstPlugin *plugin); + +G_END_DECLS diff --git a/src/lib/clapper/gst/clapper-plugin.c b/src/lib/clapper/gst/clapper-plugin.c new file mode 100644 index 000000000..57bf80629 --- /dev/null +++ b/src/lib/clapper/gst/clapper-plugin.c @@ -0,0 +1,49 @@ +/* Clapper Playback Library + * Copyright (C) 2024 Rafał Dzięgiel + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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. + */ + +#include "config.h" + +#include + +#include "clapper-plugin-private.h" +#include "../clapper-functionalities-availability.h" + +#if CLAPPER_WITH_ENHANCERS_LOADER +#include "clapper-extractor-src-private.h" +#include "../clapper-extractable-private.h" +#include "../clapper-enhancers-loader-private.h" +#endif + +gboolean +clapper_gst_plugin_init (GstPlugin *plugin) +{ + gboolean res = FALSE; + +#if CLAPPER_WITH_ENHANCERS_LOADER + gst_plugin_add_dependency_simple (plugin, + "CLAPPER_ENHANCERS_PATH", CLAPPER_ENHANCERS_PATH, NULL, + GST_PLUGIN_DEPENDENCY_FLAG_PATHS_ARE_DEFAULT_ONLY); + + /* Avoid registering an URI handler without schemes */ + if (clapper_enhancers_loader_has_enhancers (CLAPPER_TYPE_EXTRACTABLE)) + res |= GST_ELEMENT_REGISTER (clapperextractorsrc, plugin); +#endif + + return res; +} diff --git a/src/lib/clapper/meson.build b/src/lib/clapper/meson.build index 87f053cd1..8995dbea7 100644 --- a/src/lib/clapper/meson.build +++ b/src/lib/clapper/meson.build @@ -1,6 +1,8 @@ clapper_dep = dependency('', required: false) clapper_option = get_option('clapper') clapper_headers_dir = join_paths(includedir, clapper_api_name, 'clapper') +clapper_enhancers_dir = join_paths(clapper_libdir, 'enhancers') +clapper_with_enhancers_loader = false build_clapper = false clapper_pkg_reqs = [ @@ -38,6 +40,27 @@ foreach dep : clapper_deps endif endforeach +# libpeas is an optional dependency +enhancers_option = get_option('enhancers-loader') +clapper_with_enhancers_loader = (not enhancers_option.disabled() and peas_dep.found()) + +if not clapper_with_enhancers_loader and enhancers_option.enabled() + error('enhancers-loader option was enabled, but required dependencies were not found') +endif + +config_h = configuration_data() +config_h.set_quoted('PACKAGE', meson.project_name()) +config_h.set_quoted('PACKAGE_VERSION', meson.project_version()) +config_h.set_quoted('PACKAGE_ORIGIN', 'https://github.com/Rafostar/clapper') +config_h.set_quoted('PLUGIN_DESC', 'Clapper elements') +config_h.set_quoted('PLUGIN_LICENSE', 'LGPL') +config_h.set_quoted('CLAPPER_ENHANCERS_PATH', clapper_enhancers_dir) + +configure_file( + output: 'config.h', + configuration: config_h, +) + visibility_conf = configuration_data() visibility_conf.set( @@ -86,7 +109,9 @@ clapper_headers = [ 'clapper.h', 'clapper-enums.h', 'clapper-audio-stream.h', + 'clapper-extractable.h', 'clapper-feature.h', + 'clapper-harvest.h', 'clapper-marker.h', 'clapper-media-item.h', 'clapper-player.h', @@ -105,9 +130,11 @@ clapper_sources = [ 'clapper.c', 'clapper-app-bus.c', 'clapper-audio-stream.c', + 'clapper-extractable.c', 'clapper-feature.c', 'clapper-features-bus.c', 'clapper-features-manager.c', + 'clapper-harvest.c', 'clapper-marker.c', 'clapper-media-item.c', 'clapper-playbin-bus.c', @@ -120,6 +147,7 @@ clapper_sources = [ 'clapper-timeline.c', 'clapper-utils.c', 'clapper-video-stream.c', + 'gst/clapper-plugin.c', '../shared/clapper-shared-utils.c', ] clapper_c_args = [ @@ -132,6 +160,36 @@ if get_option('default_library') == 'static' clapper_c_args += ['-DCLAPPER_STATIC_COMPILATION'] endif +clapper_possible_functionalities = [ + 'enhancers-loader', +] +clapper_available_functionalities = [] + +if clapper_with_enhancers_loader + clapper_deps += peas_dep + clapper_sources += [ + 'clapper-enhancers-loader.c', + 'gst/clapper-extractor-src.c', + 'gst/clapper-extractor-director.c', + ] + clapper_available_functionalities += 'enhancers-loader' +endif + +functionalities_availability_conf = configuration_data() + +foreach functionality_name : clapper_possible_functionalities + functionalities_availability_conf.set( + 'CLAPPER_WITH_@0@'.format(functionality_name.replace('-', '_').to_upper()), + clapper_available_functionalities.contains(functionality_name) ? 'TRUE' : 'FALSE' + ) +endforeach + +clapper_headers += configure_file( + input: 'clapper-functionalities-availability.h.in', + output: 'clapper-functionalities-availability.h', + configuration: functionalities_availability_conf, +) + subdir('features') clapper_enums = gnome.mkenums_simple( @@ -205,7 +263,7 @@ if build_vapi sources: clapper_gir[0], packages: clapper_pkg_reqs, metadata_dirs: [ - join_paths (meson.current_source_dir(), 'metadata') + join_paths(meson.current_source_dir(), 'metadata') ], install: true, ) @@ -213,7 +271,9 @@ if build_vapi endif clapper_pkgconfig_variables = [ + 'enhancersdir=${libdir}/' + clapper_api_name + '/enhancers', 'features=' + ' '.join(clapper_available_features), + 'functionalities=' + ' '.join(clapper_available_functionalities), ] pkgconfig.generate(clapper_lib,