diff --git a/docs/libs/ges-sections.txt b/docs/libs/ges-sections.txt index 65f6fffc1d..4ddb424e66 100644 --- a/docs/libs/ges-sections.txt +++ b/docs/libs/ges-sections.txt @@ -821,8 +821,6 @@ GESFormatterPrivate GESPitiviFormatter GESPitiviFormatter ges_pitivi_formatter_new -ges_pitivi_formatter_set_sources -ges_pitivi_formatter_get_sources GESPitiviFormatterClass GESPitiviFormatterPrivate diff --git a/docs/libs/ges.types b/docs/libs/ges.types index 99559468f8..e6c5e51ae7 100644 --- a/docs/libs/ges.types +++ b/docs/libs/ges.types @@ -37,6 +37,7 @@ ges_track_transition_get_type %ges_track_type_get_type ges_track_video_test_source_get_type ges_track_video_transition_get_type +ges_project_get_type %ges_video_test_pattern_get_type %ges_video_standard_transition_type_get_type ges_meta_container_get_type diff --git a/ges/Makefile.am b/ges/Makefile.am index a9b45acefc..03ba400ca8 100644 --- a/ges/Makefile.am +++ b/ges/Makefile.am @@ -50,6 +50,7 @@ libges_@GST_API_VERSION@_la_SOURCES = \ ges-asset.c \ ges-asset-file-source.c \ ges-extractable.c \ + ges-project.c \ ges-utils.c libges_@GST_API_VERSION@includedir = $(includedir)/gstreamer-@GST_API_VERSION@/ges/ @@ -97,6 +98,7 @@ libges_@GST_API_VERSION@include_HEADERS = \ ges-asset.h \ ges-asset-file-source.h \ ges-extractable.h \ + ges-project.h \ ges-utils.h noinst_HEADERS = \ diff --git a/ges/ges-asset.c b/ges/ges-asset.c index 452ba45717..4e327a19db 100644 --- a/ges/ges-asset.c +++ b/ges/ges-asset.c @@ -549,6 +549,8 @@ ges_asset_cache_init (void) g_mutex_init (&asset_cache_lock); type_entries_table = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify) g_hash_table_unref); + + _init_formatter_assets (); } gboolean diff --git a/ges/ges-formatter.c b/ges/ges-formatter.c index 78e0bb72e3..59d5e73976 100644 --- a/ges/ges-formatter.c +++ b/ges/ges-formatter.c @@ -22,25 +22,6 @@ * SECTION:ges-formatter * @short_description: Timeline saving and loading. * - * The #GESFormatter is the object responsible for loading and/or saving the contents - * of a #GESTimeline to/from various formats. - * - * In order to save a #GESTimeline, you can either let GES pick a default formatter by - * using ges_timeline_save_to_uri(), or pick your own formatter and use - * ges_formatter_save_to_uri(). - * - * To load a #GESTimeline, you might want to be able to track the progress of the loading, - * in which case you should create an empty #GESTimeline, connect to the relevant signals - * and call ges_formatter_load_from_uri(). - * - * If you do not care about tracking the loading progress, you can use the convenience - * ges_timeline_new_from_uri() method. - * - * Support for saving or loading new formats can be added by creating a subclass of - * #GESFormatter and implement the various vmethods of #GESFormatterClass. - * - * Note that subclasses should call ges_formatter_project_loaded when they are done - * loading a project. **/ #include @@ -51,6 +32,7 @@ #include "ges-internal.h" #include "ges.h" +/* TODO Add a GCancellable somewhere in the API */ static void ges_extractable_interface_init (GESExtractableInterface * iface); G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GESFormatter, ges_formatter, @@ -63,50 +45,22 @@ struct _GESFormatterPrivate }; static void ges_formatter_dispose (GObject * object); -static gboolean load_from_uri (GESFormatter * formatter, GESTimeline * - timeline, const gchar * uri, GError ** error); -static gboolean save_to_uri (GESFormatter * formatter, GESTimeline * - timeline, const gchar * uri, GError ** error); -static gboolean default_can_load_uri (const gchar * uri, GError ** error); -static gboolean default_can_save_uri (const gchar * uri, GError ** error); - -enum -{ - LAST_SIGNAL -}; - -/* Utils */ -static GESFormatterClass * -ges_formatter_find_for_uri (const gchar * uri) -{ - GType *formatters; - guint n_formatters, i; - GESFormatterClass *class, *ret = NULL; - - formatters = g_type_children (GES_TYPE_FORMATTER, &n_formatters); - for (i = 0; i < n_formatters; i++) { - class = g_type_class_ref (formatters[i]); - - if (class->can_load_uri (uri, NULL)) { - ret = class; - break; - } - g_type_class_unref (class); - } - - g_free (formatters); - - return ret; -} +static gboolean default_can_load_uri (GESFormatterClass * class, + const gchar * uri, GError ** error); +static gboolean default_can_save_uri (GESFormatterClass * class, + const gchar * uri, GError ** error); /* GESExtractable implementation */ static gchar * extractable_check_id (GType type, const gchar * id) { - if (gst_uri_is_valid (id)) + GESFormatterClass *class; + + if (id) return g_strdup (id); - return NULL; + class = g_type_class_peek (type); + return g_strdup (class->name); } static gchar * @@ -120,19 +74,27 @@ extractable_get_id (GESExtractable * self) return g_strdup (ges_asset_get_id (asset)); } -static GType -extractable_get_real_extractable_type (GType type, const gchar * id) +static gboolean +_register_metas (GESExtractableInterface * iface, GObjectClass * class, + GESAsset * asset) { - GType real_type = G_TYPE_NONE; - GESFormatterClass *class; + GESFormatterClass *fclass = GES_FORMATTER_CLASS (class); + GESMetaContainer *container = GES_META_CONTAINER (asset); - class = ges_formatter_find_for_uri (id); - if (class) { - real_type = G_OBJECT_CLASS_TYPE (class); - g_type_class_unref (class); - } + 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); - return real_type; + return TRUE; } static void @@ -140,7 +102,8 @@ ges_extractable_interface_init (GESExtractableInterface * iface) { iface->check_id = (GESExtractableCheckId) extractable_check_id; iface->get_id = extractable_get_id; - iface->get_real_extractable_type = extractable_get_real_extractable_type; + iface->asset_type = GES_TYPE_ASSET; + iface->register_metas = _register_metas; } static void @@ -154,33 +117,48 @@ ges_formatter_class_init (GESFormatterClass * klass) klass->can_load_uri = default_can_load_uri; klass->can_save_uri = default_can_save_uri; - klass->load_from_uri = load_from_uri; - klass->save_to_uri = save_to_uri; + klass->load_from_uri = NULL; + klass->save_to_uri = NULL; + + /* We set dummy metas */ + klass->name = "base-formatter"; + klass->extension = "noextension"; + klass->description = "Formatter base class, you should give" + " a name to your formatter"; + klass->mimetype = "No mimetype"; + klass->version = 0.0; + klass->rank = GST_RANK_NONE; } static void ges_formatter_init (GESFormatter * object) { + object->priv = G_TYPE_INSTANCE_GET_PRIVATE (object, + GES_TYPE_FORMATTER, GESFormatterPrivate); + object->project = NULL; } static void ges_formatter_dispose (GObject * object) { - - + ges_formatter_set_project (GES_FORMATTER (object), NULL); } static gboolean -default_can_load_uri (const gchar * uri, GError ** error) +default_can_load_uri (GESFormatterClass * class, const gchar * uri, + GError ** error) { - GST_ERROR ("No 'can_load_uri' vmethod implementation"); + GST_DEBUG ("%s: no 'can_load_uri' vmethod implementation", + g_type_name (G_OBJECT_CLASS_TYPE (class))); return FALSE; } static gboolean -default_can_save_uri (const gchar * uri, GError ** error) +default_can_save_uri (GESFormatterClass * class, + const gchar * uri, GError ** error) { - GST_ERROR ("No 'can_save_uri' vmethod implementation"); + GST_DEBUG ("%s: no 'can_save_uri' vmethod implementation", + g_type_name (G_OBJECT_CLASS_TYPE (class))); return FALSE; } @@ -199,6 +177,10 @@ default_can_save_uri (const gchar * uri, GError ** error) gboolean ges_formatter_can_load_uri (const gchar * uri, GError ** error) { + gboolean ret = FALSE; + GList *formatter_assets, *tmp; + GESFormatterClass *class = NULL; + if (!(gst_uri_is_valid (uri))) { GST_ERROR ("Invalid uri!"); return FALSE; @@ -211,10 +193,21 @@ ges_formatter_can_load_uri (const gchar * uri, GError ** error) return FALSE; } - /* FIXME Reimplement */ - GST_FIXME ("This should be reimplemented"); + formatter_assets = ges_list_assets (GES_TYPE_FORMATTER); + for (tmp = formatter_assets; tmp; tmp = tmp->next) { + GESAsset *asset = GES_ASSET (tmp->data); - return FALSE; + class = g_type_class_ref (ges_asset_get_extractable_type (asset)); + if (class->can_load_uri (class, uri, error)) { + g_type_class_unref (class); + ret = TRUE; + break; + } + g_type_class_unref (class); + } + + g_list_free (formatter_assets); + return ret; } /** @@ -283,19 +276,12 @@ ges_formatter_load_from_uri (GESFormatter * formatter, GESTimeline * timeline, return ret; } -static gboolean -load_from_uri (GESFormatter * formatter, GESTimeline * timeline, - const gchar * uri, GError ** error) -{ - GST_FIXME ("This should be reimplemented"); - return FALSE; -} - /** * 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. @@ -306,22 +292,133 @@ load_from_uri (GESFormatter * formatter, GESTimeline * timeline, gboolean ges_formatter_save_to_uri (GESFormatter * formatter, GESTimeline * - timeline, const gchar * uri, GError ** error) + timeline, const gchar * uri, gboolean overwrite, GError ** error) { GESFormatterClass *klass = GES_FORMATTER_GET_CLASS (formatter); if (klass->save_to_uri) - return klass->save_to_uri (formatter, timeline, uri, error); + return klass->save_to_uri (formatter, timeline, uri, overwrite, error); GST_ERROR ("not implemented!"); return FALSE; } -static gboolean -save_to_uri (GESFormatter * formatter, GESTimeline * timeline, - const gchar * uri, GError ** error) +/** + * 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) { - GST_FIXME ("This should be reimplemented"); - return FALSE; + GList *assets, *tmp; + GESAsset *ret = NULL; + GstRank tmprank, rank = GST_RANK_NONE; + + assets = ges_list_assets (GES_TYPE_FORMATTER); + for (tmp = assets; tmp; tmp = tmp->next) { + tmprank = GST_RANK_NONE; + ges_meta_container_get_uint (GES_META_CONTAINER (tmp->data), + GES_META_FORMATTER_RANK, &tmprank); + + if (tmprank > rank) { + rank = tmprank; + ret = GES_ASSET (tmp->data); + } + } + g_list_free (assets); + + return ret; +} + +void +ges_formatter_class_register_metas (GESFormatterClass * class, + const gchar * name, const gchar * description, const gchar * extension, + const gchar * mimetype, gdouble version, GstRank rank) +{ + class->name = name; + class->description = description; + class->extension = extension; + class->mimetype = mimetype; + class->version = version; + class->rank = rank; +} + +/* Main Formatter methods */ + +/*< protected >*/ +void +ges_formatter_set_project (GESFormatter * formatter, GESProject * project) +{ + formatter->project = project; +} + +GESProject * +ges_formatter_get_project (GESFormatter * formatter) +{ + return formatter->project; +} + +static void +_list_formatters (GType * formatters, guint n_formatters) +{ + GType *tmptypes, type; + guint tmp_n_types, i; + + for (i = 0; i < n_formatters; i++) { + type = formatters[i]; + tmptypes = g_type_children (type, &tmp_n_types); + if (tmp_n_types) { + /* Recurse as g_type_children does not */ + _list_formatters (tmptypes, tmp_n_types); + g_free (tmptypes); + } + + if (G_TYPE_IS_ABSTRACT (type)) { + GST_DEBUG ("%s is abstract, not using", g_type_name (type)); + } else { + gst_object_unref (ges_asset_request (type, NULL, NULL)); + } + } +} + +void +_init_formatter_assets (void) +{ + GType *formatters; + guint n_formatters; + + formatters = g_type_children (GES_TYPE_FORMATTER, &n_formatters); + _list_formatters (formatters, n_formatters); + g_free (formatters); +} + +GESAsset * +_find_formatter_asset_for_uri (const gchar * uri) +{ + GESFormatterClass *class = NULL; + GList *formatter_assets, *tmp; + GESAsset *asset = NULL; + + formatter_assets = ges_list_assets (GES_TYPE_FORMATTER); + for (tmp = formatter_assets; tmp; tmp = tmp->next) { + asset = GES_ASSET (tmp->data); + class = g_type_class_ref (ges_asset_get_extractable_type (asset)); + if (class->can_load_uri (class, uri, NULL)) { + g_type_class_unref (class); + asset = gst_object_ref (asset); + break; + } + + asset = NULL; + g_type_class_unref (class); + } + + g_list_free (formatter_assets); + + return asset; } diff --git a/ges/ges-formatter.h b/ges/ges-formatter.h index b4159f584d..da64927406 100644 --- a/ges/ges-formatter.h +++ b/ges/ges-formatter.h @@ -56,14 +56,15 @@ struct _GESFormatter { GESFormatterPrivate *priv; /*< protected >*/ + GESProject *project; GESTimeline *timeline; /* Padding for API extension */ gpointer _ges_reserved[GES_PADDING]; }; -typedef gboolean (*GESFormatterCanLoadURIMethod) (const gchar * uri, GError **error); -typedef gboolean (*GESFormatterCanSaveURIMethod) (const gchar * uri, GError **error); +typedef gboolean (*GESFormatterCanLoadURIMethod) (GESFormatterClass *class, const gchar * uri, GError **error); +typedef gboolean (*GESFormatterCanSaveURIMethod) (GESFormatterClass *class, const gchar * uri, GError **error); /** * GESFormatterLoadFromURIMethod: @@ -88,6 +89,7 @@ typedef gboolean (*GESFormatterLoadFromURIMethod) (GESFormatter *formatter, * @formatter: a #GESFormatter * @timeline: a #GESTimeline * @uri: the URI to save to + * @overwrite: Whether the file should be overwritten in case it exists * * Virtual method for saving a timeline to a uri. * @@ -97,8 +99,8 @@ typedef gboolean (*GESFormatterLoadFromURIMethod) (GESFormatter *formatter, * else FALSE. */ typedef gboolean (*GESFormatterSaveToURIMethod) (GESFormatter *formatter, - GESTimeline *timeline, - const gchar * uri, GError **error); + GESTimeline *timeline, const gchar * uri, gboolean overwrite, + GError **error); /** * GESFormatterClass: @@ -121,10 +123,14 @@ struct _GESFormatterClass { GESFormatterLoadFromURIMethod load_from_uri; GESFormatterSaveToURIMethod save_to_uri; - /*< private >*/ - /* FIXME : formatter name */ - /* FIXME : formatter description */ - /* FIXME : format name/mime-type */ + /* < private > */ + const gchar *name; + const gchar *description; + const gchar *extension; + const gchar *mimetype; + gdouble version; + GstRank rank; + /* Padding for API extension */ gpointer _ges_reserved[GES_PADDING]; @@ -132,7 +138,14 @@ struct _GESFormatterClass { GType ges_formatter_get_type (void); -/* Main Formatter methods */ +void ges_formatter_class_register_metas (GESFormatterClass * class, + const gchar *name, + const gchar *description, + const gchar *extension, + const gchar *mimetype, + gdouble version, + GstRank rank); + gboolean ges_formatter_can_load_uri (const gchar * uri, GError **error); gboolean ges_formatter_can_save_uri (const gchar * uri, GError **error); @@ -144,6 +157,9 @@ gboolean ges_formatter_load_from_uri (GESFormatter * formatter, gboolean ges_formatter_save_to_uri (GESFormatter * formatter, GESTimeline *timeline, const gchar *uri, + gboolean overwrite, GError **error); +GESAsset *ges_formatter_get_default (void); + #endif /* _GES_FORMATTER */ diff --git a/ges/ges-internal.h b/ges/ges-internal.h index feb490379f..cdc570096c 100644 --- a/ges/ges-internal.h +++ b/ges/ges-internal.h @@ -103,12 +103,20 @@ ges_extractable_get_real_extractable_type_for_id (GType type, const gchar * id); gboolean ges_extractable_register_metas (GType extractable_type, GESAsset *asset); -#if 0 +/* GESFormatter internal methods */ +void +ges_formatter_set_project (GESFormatter *formatter, + GESProject *project); +GESProject * +ges_formatter_get_project (GESFormatter *formatter); +GESAsset * _find_formatter_asset_for_uri (const gchar *uri); + /* GESProject internal methods */ gboolean ges_project_set_loaded (GESProject * project, GESFormatter *formatter); gchar * ges_project_try_updating_id (GESProject *self, GESAsset *asset, GError *error); + +void _init_formatter_assets (void); #endif -#endif /* __GES_INTERNAL_H__ */ diff --git a/ges/ges-pitivi-formatter.c b/ges/ges-pitivi-formatter.c index 6aa9aee46b..453e93633d 100644 --- a/ges/ges-pitivi-formatter.c +++ b/ges/ges-pitivi-formatter.c @@ -36,16 +36,16 @@ #include #define GetCurrentDir getcwd +/* The PiTiVi etree formatter is 0.1 we set GES one to 0.2 */ +#define VERSION "0.2" +#define DOUBLE_VERSION 0.2 + G_DEFINE_TYPE (GESPitiviFormatter, ges_pitivi_formatter, GES_TYPE_FORMATTER); #undef GST_CAT_DEFAULT GST_DEBUG_CATEGORY_STATIC (ges_pitivi_formatter_debug); #define GST_CAT_DEFAULT ges_pitivi_formatter_debug -/* The PiTiVi etree formatter is 0.1 we set GES one to 0.2 */ -#define VERSION "0.2" - - typedef struct SrcMapping { gchar *id; @@ -110,7 +110,8 @@ list_table_destroyer (gpointer key, gpointer value, void *unused) } static gboolean -pitivi_can_load_uri (const gchar * uri, GError ** error) +pitivi_can_load_uri (GESFormatterClass * class, const gchar * uri, + GError ** error) { xmlDocPtr doc; gboolean ret = TRUE; @@ -431,7 +432,8 @@ save_timeline_objects (xmlTextWriterPtr writer, GList * list) static gboolean save_pitivi_timeline_to_uri (GESFormatter * formatter, - GESTimeline * timeline, const gchar * uri, GError ** error) + GESTimeline * timeline, const gchar * uri, gboolean overwrite, + GError ** error) { xmlTextWriterPtr writer; GList *list = NULL, *layers = NULL; @@ -741,9 +743,9 @@ track_object_added_cb (GESTimelineObject * object, g_hash_table_steal (props_table, "current-formatter"); priv->sources_to_load = g_list_remove (priv->sources_to_load, object); - if (!priv->sources_to_load) - /* ges_formatter_emit_loaded (GES_FORMATTER (formatter)); */ - GST_FIXME ("This should be reimplemented"); + if (!priv->sources_to_load && GES_FORMATTER (formatter)->project) + ges_project_set_loaded (GES_FORMATTER (formatter)->project, + GES_FORMATTER (formatter)); } if (lockedstr && !g_strcmp0 (lockedstr, "(bool)False")) @@ -1036,9 +1038,10 @@ load_pitivi_file_from_uri (GESFormatter * self, /* If there are no timeline objects to load we should emit * 'project-loaded' signal. */ - if (!g_hash_table_size (priv->timeline_objects_table)) { - /*ges_formatter_emit_loaded (self); */ - GST_FIXME ("This should be reimplemented"); + if (!g_hash_table_size (priv->timeline_objects_table) && + GES_FORMATTER (self)->project) { + ges_project_set_loaded (GES_FORMATTER (self)->project, + GES_FORMATTER (self)); } else { if (!make_timeline_objects (self)) { GST_ERROR ("Couldn't deserialise the project properly"); @@ -1096,6 +1099,10 @@ ges_pitivi_formatter_class_init (GESPitiviFormatterClass * klass) formatter_klass->save_to_uri = save_pitivi_timeline_to_uri; formatter_klass->load_from_uri = load_pitivi_file_from_uri; object_class->finalize = ges_pitivi_formatter_finalize; + + ges_formatter_class_register_metas (formatter_klass, "pitivi", + "The PiTiVi file format", "xptv", "text/x-xptv", + DOUBLE_VERSION, GST_RANK_MARGINAL); } static void @@ -1138,61 +1145,3 @@ ges_pitivi_formatter_new (void) { return g_object_new (GES_TYPE_PITIVI_FORMATTER, NULL); } - -/* API */ - -/** - * ges_pitivi_formatter_set_sources: - * @formatter: The #GESPitiviFormatter to set sources on - * @infos: (transfer none) (element-type GstDiscovererInfo): - * The #GstDiscovererInfo infos to add as sources. - * - * Add @infos as the formatter sources so we can save sources that are - * not in the timeline when saving. - * - * Returns: %TRUE if everything wen fine, %FALSE otherwise - */ -gboolean -ges_pitivi_formatter_set_sources (GESPitiviFormatter * formatter, GList * infos) -{ - GList *tmp; - gchar *strid; - GESPitiviFormatterPrivate *priv = formatter->priv; - - g_hash_table_remove_all (priv->saving_source_table); - priv->nb_sources = 1; - - for (tmp = infos; tmp; tmp = g_list_next (tmp)) { - GstDiscovererInfo *info = GST_DISCOVERER_INFO (tmp->data); - gchar *uri = g_strdup (gst_discoverer_info_get_uri (info)); - - strid = g_strdup_printf ("%i", priv->nb_sources); - - g_hash_table_insert (priv->saving_source_table, uri, strid); - priv->nb_sources++; - } - - return TRUE; -} - -/** - * ges_pitivi_formatter_get_sources: - * @formatter: The #GESPitiviFormatter to get sources from - * - * Returns: (transfer full) (element-type utf8): %TRUE if everything went - * fine, %FALSE otherwise - */ -GList * -ges_pitivi_formatter_get_sources (GESPitiviFormatter * formatter) -{ - GList *sources = NULL; - GHashTableIter iter; - gpointer key, value; - - g_hash_table_iter_init (&iter, formatter->priv->source_uris); - while (g_hash_table_iter_next (&iter, &key, &value)) { - sources = g_list_prepend (sources, g_strdup (value)); - } - - return sources; -} diff --git a/ges/ges-pitivi-formatter.h b/ges/ges-pitivi-formatter.h index ebdb33655c..e62f559a55 100644 --- a/ges/ges-pitivi-formatter.h +++ b/ges/ges-pitivi-formatter.h @@ -66,11 +66,6 @@ struct _GESPitiviFormatterClass }; GType ges_pitivi_formatter_get_type (void); - GESPitiviFormatter *ges_pitivi_formatter_new (void); -gboolean ges_pitivi_formatter_set_sources (GESPitiviFormatter * formatter, GList * infos); - -GList * ges_pitivi_formatter_get_sources(GESPitiviFormatter * formatter); - #endif /* _GES_PITIVI_FORMATTER */ diff --git a/ges/ges.h b/ges/ges.h index 2350cc3460..f4668f7c86 100644 --- a/ges/ges.h +++ b/ges/ges.h @@ -47,9 +47,7 @@ #include #include #include -#if 0 #include -#endif #include #include