Re: [PATCH] local-metadata: add series support
- From: Guillaume Emont <gemont igalia com>
- To: grilo-list gnome org
- Subject: Re: [PATCH] local-metadata: add series support
- Date: Mon, 21 Mar 2011 14:38:18 +0100
Thanks for all the changes!
Pushed (as 46f707538bd5bfb0d36b9c2701f7bccafdc63dfd).
For the record, video_uri_to_metadata() was left named as is to keep the
code identical to the one in the original patch in tracker, so that we
can sync with its evolutions in the future.
Cheers,
Guij
On 20/03/2011 04:39, lionel g landwerlin linux intel com wrote:
> From: Lionel Landwerlin <lionel g landwerlin linux intel com>
>
> Based on a patch for Tracker by Iain Holmes :
>
> http://build.meego.com/package/view_file?file=0002-Tracker-extract-Parse-the-video-filename-to-obtain-e.patch&package=tracker&project=devel%3Acontentfw&srcmd5=c91b724488ee9ae476a9ce65755d8152
>
> Signed-off-by: Lionel Landwerlin <lionel g landwerlin linux intel com>
> ---
> configure.ac | 3 +
> src/metadata/local-metadata/grl-local-metadata.c | 489 ++++++++++++++++++++--
> src/metadata/local-metadata/grl-local-metadata.h | 6 +-
> 3 files changed, 471 insertions(+), 27 deletions(-)
>
> diff --git a/configure.ac b/configure.ac
> index 5d9d9a9..15304f8 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -709,6 +709,9 @@ AC_ARG_ENABLE(localmetadata,
> if test "x$HAVE_GIO" = "xno"; then
> AC_MSG_ERROR([gio-2.0 not found, install it or use --disable-localmetadata])
> fi
> + if test "x$HAVE_GLIB_2_26" = "xno"; then
> + AC_MSG_ERROR([glib-2.26.0 or above not found, install it or use --disable-localmetadata])
> + fi
> ;;
> esac
> ],
> diff --git a/src/metadata/local-metadata/grl-local-metadata.c b/src/metadata/local-metadata/grl-local-metadata.c
> index 62737ba..9bba09f 100644
> --- a/src/metadata/local-metadata/grl-local-metadata.c
> +++ b/src/metadata/local-metadata/grl-local-metadata.c
> @@ -24,6 +24,9 @@
> #include "config.h"
> #endif
>
> +#include <string.h>
> +#include <stdlib.h>
> +
> #include <grilo.h>
> #include <gio/gio.h>
>
> @@ -42,8 +45,65 @@ GRL_LOG_DOMAIN_STATIC(local_metadata_log_domain);
> #define LICENSE "LGPL"
> #define SITE "http://www.igalia.com"
>
> -
> -static GrlLocalMetadataSource *grl_local_metadata_source_new (void);
> +/**/
> +
> +#define TV_REGEX \
> + "(?<showname>.*)\\." \
> + "(?<season>(?:\\d{1,2})|(?:[sS]\\K\\d{1,2}))" \
> + "(?<episode>(?:\\d{2})|(?:[eE]\\K\\d{1,2}))" \
> + "\\.?(?<name>.*)?"
> +#define MOVIE_REGEX \
> + "(?<name>.*)" \
> + "\\.?[\\(\\[](?<year>[12][90]\\d{2})[\\)\\]]"
> +
> +/**/
> +
> +#define GRL_LOCAL_METADATA_SOURCE_GET_PRIVATE(object) \
> + (G_TYPE_INSTANCE_GET_PRIVATE((object), \
> + GRL_LOCAL_METADATA_SOURCE_TYPE, \
> + GrlLocalMetadataSourcePriv))
> +
> +enum {
> + PROP_0,
> + PROP_GUESS_VIDEO,
> +};
> +
> +struct _GrlLocalMetadataSourcePriv {
> + gboolean guess_video;
> +};
> +
> +/**/
> +
> +typedef enum {
> + FLAG_VIDEO_TITLE = 0x1,
> + FLAG_VIDEO_SHOWNAME = 0x2,
> + FLAG_VIDEO_DATE = 0x4,
> + FLAG_VIDEO_SEASON = 0x8,
> + FLAG_VIDEO_EPISODE = 0x10,
> + FLAG_THUMBNAIL = 0x20,
> +} resolution_flags_t;
> +
> +const gchar *video_blacklisted_prefix[] = {
> + "tpz-", NULL
> +};
> +
> +const char *video_blacklisted_words[] = {
> + "720p", "1080p",
> + "ws", "WS", "proper", "PROPER",
> + "repack", "real.repack",
> + "hdtv", "HDTV", "pdtv", "PDTV", "notv", "NOTV",
> + "dsr", "DSR", "DVDRip", "divx", "DIVX", "xvid", "Xvid",
> + NULL
> +};
> +
> +/**/
> +
> +static void grl_local_metadata_source_set_property (GObject *object,
> + guint propid,
> + const GValue *value,
> + GParamSpec *pspec);
> +
> +static GrlLocalMetadataSource *grl_local_metadata_source_new (gboolean guess_video);
>
> static void grl_local_metadata_source_resolve (GrlMetadataSource *source,
> GrlMetadataSourceResolveSpec *rs);
> @@ -59,6 +119,7 @@ gboolean grl_local_metadata_source_plugin_init (GrlPluginRegistry *registry,
> const GrlPluginInfo *plugin,
> GList *configs);
>
> +/**/
>
> /* =================== GrlLocalMetadata Plugin =============== */
>
> @@ -67,11 +128,28 @@ grl_local_metadata_source_plugin_init (GrlPluginRegistry *registry,
> const GrlPluginInfo *plugin,
> GList *configs)
> {
> + guint config_count;
> + gboolean guess_video = TRUE;
> + GrlConfig *config;
> +
> GRL_LOG_DOMAIN_INIT (local_metadata_log_domain, "local-metadata");
>
> GRL_DEBUG ("grl_local_metadata_source_plugin_init");
>
> - GrlLocalMetadataSource *source = grl_local_metadata_source_new ();
> + if (!configs) {
> + GRL_WARNING ("\tConfiguration not provided! Using default configuration.");
> + } else {
> + config_count = g_list_length (configs);
> + if (config_count > 1) {
> + GRL_WARNING ("\tProvided %i configs, but will only use one", config_count);
> + }
> +
> + config = GRL_CONFIG (configs->data);
> +
> + guess_video = grl_config_get_boolean (config, "guess-video");
> + }
> +
> + GrlLocalMetadataSource *source = grl_local_metadata_source_new (guess_video);
> grl_plugin_registry_register_source (registry,
> plugin,
> GRL_MEDIA_PLUGIN (source),
> @@ -86,23 +164,40 @@ GRL_PLUGIN_REGISTER (grl_local_metadata_source_plugin_init,
> /* ================== GrlLocalMetadata GObject ================ */
>
> static GrlLocalMetadataSource *
> -grl_local_metadata_source_new (void)
> +grl_local_metadata_source_new (gboolean guess_video)
> {
> GRL_DEBUG ("grl_local_metadata_source_new");
> return g_object_new (GRL_LOCAL_METADATA_SOURCE_TYPE,
> "source-id", SOURCE_ID,
> "source-name", SOURCE_NAME,
> "source-desc", SOURCE_DESC,
> + "guess-video", guess_video,
> NULL);
> }
>
> static void
> grl_local_metadata_source_class_init (GrlLocalMetadataSourceClass * klass)
> {
> + GObjectClass *g_class = G_OBJECT_CLASS (klass);
> GrlMetadataSourceClass *metadata_class = GRL_METADATA_SOURCE_CLASS (klass);
> +
> metadata_class->supported_keys = grl_local_metadata_source_supported_keys;
> metadata_class->may_resolve = grl_local_metadata_source_may_resolve;
> metadata_class->resolve = grl_local_metadata_source_resolve;
> +
> + g_class->set_property = grl_local_metadata_source_set_property;
> +
> + g_object_class_install_property (g_class,
> + PROP_GUESS_VIDEO,
> + g_param_spec_boolean ("guess-video",
> + "Guess video",
> + "Guess video metadata "
> + "from filename",
> + TRUE,
> + G_PARAM_WRITABLE |
> + G_PARAM_CONSTRUCT_ONLY));
> +
> + g_type_class_add_private (klass, sizeof (GrlLocalMetadataSourcePriv));
> }
>
> static void
> @@ -114,7 +209,211 @@ G_DEFINE_TYPE (GrlLocalMetadataSource,
> grl_local_metadata_source,
> GRL_TYPE_METADATA_SOURCE);
>
> +static void
> +grl_local_metadata_source_set_property (GObject *object,
> + guint propid,
> + const GValue *value,
> + GParamSpec *pspec)
> +{
> + GrlLocalMetadataSourcePriv *priv =
> + GRL_LOCAL_METADATA_SOURCE_GET_PRIVATE (object);
> +
> + switch (propid) {
> + case PROP_GUESS_VIDEO:
> + priv->guess_video = g_value_get_boolean (value);
> + break;
> +
> + default:
> + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
> + }
> +}
> +
> /* ======================= Utilities ==================== */
> +
> +static gchar *
> +video_sanitise_string (const gchar *str)
> +{
> + int i;
> + gchar *line;
> +
> + line = (gchar *) str;
> + for (i = 0; video_blacklisted_prefix[i]; i++) {
> + if (g_str_has_prefix (str, video_blacklisted_prefix[i])) {
> + int len = strlen (video_blacklisted_prefix[i]);
> +
> + line = (gchar *) str + len;
> + }
> + }
> +
> + for (i = 0; video_blacklisted_words[i]; i++) {
> + gchar *end;
> +
> + end = strstr (line, video_blacklisted_words[i]);
> + if (end) {
> + return g_strndup (line, end - line);
> + }
> + }
> +
> + return g_strdup (line);
> +}
> +
> +/* tidies strings before we run them through the regexes */
> +static gchar *
> +video_uri_to_metadata (const gchar *uri)
> +{
> + gchar *ext, *basename, *name, *whitelisted;
> +
> + basename = g_path_get_basename (uri);
> + ext = strrchr (basename, '.');
> + if (ext) {
> + name = g_strndup (basename, ext - basename);
> + g_free (basename);
> + } else {
> + name = basename;
> + }
> +
> + /* Replace _ <space> with . */
> + g_strdelimit (name, "_ ", '.');
> + whitelisted = video_sanitise_string (name);
> + g_free (name);
> +
> + return whitelisted;
> +}
> +
> +static void
> +video_guess_values_from_uri (const gchar *uri,
> + gchar **title,
> + gchar **showname,
> + GDateTime **date,
> + gint *season,
> + gint *episode)
> +{
> + gchar *metadata;
> + GRegex *regex;
> + GMatchInfo *info;
> +
> + metadata = video_uri_to_metadata (uri);
> +
> + regex = g_regex_new (MOVIE_REGEX, 0, 0, NULL);
> + g_regex_match (regex, metadata, 0, &info);
> +
> + if (g_match_info_matches (info)) {
> + if (title) {
> + *title = g_match_info_fetch_named (info, "name");
> + /* Replace "." with <space> */
> + g_strdelimit (*title, ".", ' ');
> + }
> +
> + if (date) {
> + gchar *year = g_match_info_fetch_named (info, "year");
> +
> + *date = g_date_time_new_utc (atoi (year), 1, 1, 0, 0, 0.0);
> + g_free (year);
> + }
> +
> + if (showname) {
> + *showname = NULL;
> + }
> +
> + if (season) {
> + *season = 0;
> + }
> +
> + if (episode) {
> + *episode = 0;
> + }
> +
> + g_regex_unref (regex);
> + g_match_info_free (info);
> + g_free (metadata);
> +
> + return;
> + }
> +
> + g_regex_unref (regex);
> + g_match_info_free (info);
> +
> + regex = g_regex_new (TV_REGEX, 0, 0, NULL);
> + g_regex_match (regex, metadata, 0, &info);
> +
> + if (g_match_info_matches (info)) {
> + if (title) {
> + *title = g_match_info_fetch_named (info, "name");
> + g_strdelimit (*title, ".", ' ');
> + }
> +
> + if (showname) {
> + *showname = g_match_info_fetch_named (info, "showname");
> + g_strdelimit (*showname, ".", ' ');
> + }
> +
> + if (season) {
> + gchar *s = g_match_info_fetch_named (info, "season");
> + if (s) {
> + if (*s == 's' || *s == 'S') {
> + *season = atoi (s + 1);
> + } else {
> + *season = atoi (s);
> + }
> + } else {
> + *season = 0;
> + }
> +
> + g_free (s);
> + }
> +
> + if (episode) {
> + gchar *e = g_match_info_fetch_named (info, "episode");
> + if (e) {
> + if (*e == 'e' || *e == 'E') {
> + *episode = atoi (e + 1);
> + } else {
> + *episode = atoi (e);
> + }
> + } else {
> + *episode = 0;
> + }
> +
> + g_free (e);
> + }
> +
> + if (date) {
> + *date = NULL;
> + }
> +
> + g_regex_unref (regex);
> + g_match_info_free (info);
> + g_free (metadata);
> +
> + return;
> + }
> +
> + g_regex_unref (regex);
> + g_match_info_free (info);
> +
> + /* The filename doesn't look like a movie or a TV show, just use the
> + filename without extension as the title */
> + if (title) {
> + *title = g_strdelimit (metadata, ".", ' ');
> + }
> +
> + if (showname) {
> + *showname = NULL;
> + }
> +
> + if (date) {
> + *date = NULL;
> + }
> +
> + if (season) {
> + *season = 0;
> + }
> +
> + if (episode) {
> + *episode = 0;
> + }
> +}
> +
> static void
> got_file_info (GFile *file, GAsyncResult *result,
> GrlMetadataSourceResolveSpec *rs)
> @@ -168,21 +467,99 @@ exit:
> }
>
> static void
> -resolve_image (GrlMetadataSourceResolveSpec *rs)
> +resolve_video (GrlMetadataSourceResolveSpec *rs, resolution_flags_t flags)
> +{
> + gchar *title, *showname;
> + GDateTime *date;
> + gint season, episode;
> + GrlData *data = GRL_DATA (rs->media);
> + resolution_flags_t miss_flags = 0, fill_flags;
> +
> + GRL_DEBUG ("%s",__FUNCTION__);
> +
> + if (!(flags & (FLAG_VIDEO_TITLE |
> + FLAG_VIDEO_SHOWNAME |
> + FLAG_VIDEO_DATE |
> + FLAG_VIDEO_SEASON |
> + FLAG_VIDEO_EPISODE)))
> + return;
> +
> + miss_flags |= grl_data_key_is_known (data, GRL_METADATA_KEY_TITLE) ?
> + 0 : FLAG_VIDEO_TITLE;
> + miss_flags |= grl_data_key_is_known (data, GRL_METADATA_KEY_SHOW) ?
> + 0 : FLAG_VIDEO_SHOWNAME;
> + miss_flags |= grl_data_key_is_known (data, GRL_METADATA_KEY_DATE) ?
> + 0 : FLAG_VIDEO_DATE;
> + miss_flags |= grl_data_key_is_known (data, GRL_METADATA_KEY_SEASON) ?
> + 0 : FLAG_VIDEO_SEASON;
> + miss_flags |= grl_data_key_is_known (data, GRL_METADATA_KEY_EPISODE) ?
> + 0 : FLAG_VIDEO_EPISODE;
> +
> + fill_flags = flags & miss_flags;
> +
> + if (!fill_flags)
> + return;
> +
> + video_guess_values_from_uri (grl_media_get_url (rs->media),
> + &title, &showname, &date,
> + &season, &episode);
> +
> + GRL_DEBUG ("\tfound title=%s/showname=%s/year=%i/season=%i/episode=%i",
> + title, showname,
> + date != NULL ? g_date_time_get_year (date) : 0,
> + season, episode);
> +
> + /* As this is just a guess, don't erase already provided values. */
> + if (title) {
> + if (fill_flags & FLAG_VIDEO_TITLE) {
> + grl_data_set_string (data, GRL_METADATA_KEY_TITLE, title);
> + }
> + g_free (title);
> + }
> +
> + if (showname) {
> + if (fill_flags & FLAG_VIDEO_SHOWNAME) {
> + grl_data_set_string (data, GRL_METADATA_KEY_SHOW, showname);
> + }
> + g_free (showname);
> + }
> +
> + if (date) {
> + if (fill_flags & FLAG_VIDEO_DATE) {
> + gchar *str_date = g_date_time_format (date, "%F");
> + grl_data_set_string (data, GRL_METADATA_KEY_DATE, str_date);
> + g_free (str_date);
> + }
> + g_date_time_unref (date);
> + }
> +
> + if (season && (fill_flags & FLAG_VIDEO_SEASON)) {
> + grl_data_set_int (data, GRL_METADATA_KEY_SEASON, season);
> + }
> +
> + if (episode && (fill_flags & FLAG_VIDEO_EPISODE)) {
> + grl_data_set_int (data, GRL_METADATA_KEY_EPISODE, episode);
> + }
> +}
> +
> +static void
> +resolve_image (GrlMetadataSourceResolveSpec *rs, resolution_flags_t flags)
> {
> GFile *file;
>
> GRL_DEBUG ("resolve_image");
>
> - file = g_file_new_for_uri (grl_media_get_url (rs->media));
> + if (flags & FLAG_THUMBNAIL) {
> + file = g_file_new_for_uri (grl_media_get_url (rs->media));
>
> - g_file_query_info_async (file, G_FILE_ATTRIBUTE_THUMBNAIL_PATH,
> - G_FILE_QUERY_INFO_NONE, G_PRIORITY_DEFAULT, NULL,
> - (GAsyncReadyCallback)got_file_info, rs);
> + g_file_query_info_async (file, G_FILE_ATTRIBUTE_THUMBNAIL_PATH,
> + G_FILE_QUERY_INFO_NONE, G_PRIORITY_DEFAULT, NULL,
> + (GAsyncReadyCallback)got_file_info, rs);
> + }
> }
>
> static void
> -resolve_album_art (GrlMetadataSourceResolveSpec *rs)
> +resolve_album_art (GrlMetadataSourceResolveSpec *rs, resolution_flags_t flags)
> {
> /* FIXME: implement this, according to
> * http://live.gnome.org/MediaArtStorageSpec
> @@ -214,6 +591,33 @@ has_compatible_media_url (GrlMedia *media)
>
> return ret;
> }
> +
> +static resolution_flags_t
> +get_resolution_flags (GList *keys)
> +{
> + GList *key = keys;
> + resolution_flags_t flags = 0;
> +
> + while (key != NULL) {
> + if (key->data == GRL_METADATA_KEY_TITLE)
> + flags |= FLAG_VIDEO_TITLE;
> + else if (key->data == GRL_METADATA_KEY_SHOW)
> + flags |= FLAG_VIDEO_SHOWNAME;
> + else if (key->data == GRL_METADATA_KEY_DATE)
> + flags |= FLAG_VIDEO_DATE;
> + else if (key->data == GRL_METADATA_KEY_SEASON)
> + flags |= FLAG_VIDEO_SEASON;
> + else if (key->data == GRL_METADATA_KEY_EPISODE)
> + flags |= FLAG_VIDEO_EPISODE;
> + else if (key->data == GRL_METADATA_KEY_THUMBNAIL)
> + flags |= FLAG_THUMBNAIL;
> +
> + key = key->next;
> + }
> +
> + return flags;
> +}
> +
> /* ================== API Implementation ================ */
>
> static const GList *
> @@ -222,6 +626,11 @@ grl_local_metadata_source_supported_keys (GrlMetadataSource *source)
> static GList *keys = NULL;
> if (!keys) {
> keys = grl_metadata_key_list_new (GRL_METADATA_KEY_THUMBNAIL,
> + GRL_METADATA_KEY_TITLE,
> + GRL_METADATA_KEY_SHOW,
> + GRL_METADATA_KEY_DATE,
> + GRL_METADATA_KEY_SEASON,
> + GRL_METADATA_KEY_EPISODE,
> NULL);
> }
> return keys;
> @@ -233,13 +642,31 @@ grl_local_metadata_source_may_resolve (GrlMetadataSource *source,
> GrlKeyID key_id,
> GList **missing_keys)
> {
> - if (key_id != GRL_METADATA_KEY_THUMBNAIL
> - || GRL_IS_MEDIA_AUDIO (media)
> - || GRL_IS_MEDIA_BOX (media))
> - return FALSE;
> + GrlLocalMetadataSourcePriv *priv =
> + GRL_LOCAL_METADATA_SOURCE_GET_PRIVATE (source);
> +
> + if (media && grl_data_key_is_known (GRL_DATA (media),
> + GRL_METADATA_KEY_URL)) {
> + if (GRL_IS_MEDIA_IMAGE (media)) {
> + if (has_compatible_media_url (media) &&
> + (key_id == GRL_METADATA_KEY_THUMBNAIL))
> + return TRUE;
> + }
>
> - if (media && grl_data_key_is_known (GRL_DATA (media), GRL_METADATA_KEY_URL))
> - return has_compatible_media_url (media);
> + if (GRL_IS_MEDIA_VIDEO (media)) {
> + if (priv->guess_video &&
> + (key_id == GRL_METADATA_KEY_TITLE ||
> + key_id == GRL_METADATA_KEY_SHOW ||
> + key_id == GRL_METADATA_KEY_DATE ||
> + key_id == GRL_METADATA_KEY_SEASON ||
> + key_id == GRL_METADATA_KEY_EPISODE))
> + return TRUE;
> + if (has_compatible_media_url (media)) {
> + if (key_id == GRL_METADATA_KEY_THUMBNAIL)
> + return TRUE;
> + }
> + }
> + }
>
> if (missing_keys)
> *missing_keys = grl_metadata_key_list_new (GRL_METADATA_KEY_URL, NULL);
> @@ -252,15 +679,20 @@ grl_local_metadata_source_resolve (GrlMetadataSource *source,
> GrlMetadataSourceResolveSpec *rs)
> {
> GError *error = NULL;
> + resolution_flags_t flags;
> + GrlLocalMetadataSourcePriv *priv =
> + GRL_LOCAL_METADATA_SOURCE_GET_PRIVATE (source);
>
> GRL_DEBUG ("grl_local_metadata_source_resolve");
>
> - if (!has_compatible_media_url (rs->media))
> - error = g_error_new (GRL_CORE_ERROR, GRL_CORE_ERROR_RESOLVE_FAILED,
> - "local-metadata needs a url in the file:// scheme");
> - else if (!g_list_find (rs->keys, GRL_METADATA_KEY_THUMBNAIL))
> - error = g_error_new (GRL_CORE_ERROR, GRL_CORE_ERROR_RESOLVE_FAILED,
> - "local-metadata can only resolve the thumbnail key");
> + flags = get_resolution_flags (rs->keys);
> +
> + if (!flags)
> + error = g_error_new (GRL_CORE_ERROR, GRL_CORE_ERROR_RESOLVE_FAILED,
> + "local-metadata cannot resolve any of the given keys");
> + else if (!has_compatible_media_url (rs->media))
> + error = g_error_new (GRL_CORE_ERROR, GRL_CORE_ERROR_RESOLVE_FAILED,
> + "local-metadata needs a url in the file:// scheme");
>
> if (error) {
> /* No can do! */
> @@ -269,11 +701,16 @@ grl_local_metadata_source_resolve (GrlMetadataSource *source,
> return;
> }
>
> - if (GRL_IS_MEDIA_VIDEO (rs->media)
> - || GRL_IS_MEDIA_IMAGE (rs->media)) {
> - resolve_image (rs);
> + GRL_DEBUG ("\ttrying to resolve for: %s", grl_media_get_url (rs->media));
> +
> + if (GRL_IS_MEDIA_VIDEO (rs->media)) {
> + if (priv->guess_video)
> + resolve_video (rs, flags);
> + resolve_image (rs, flags);
> + } else if (GRL_IS_MEDIA_IMAGE (rs->media)) {
> + resolve_image (rs, flags);
> } else if (GRL_IS_MEDIA_AUDIO (rs->media)) {
> - resolve_album_art (rs);
> + resolve_album_art (rs, flags);
> } else {
> /* What's that media type? */
> rs->callback (source, rs->media, rs->user_data, NULL);
> diff --git a/src/metadata/local-metadata/grl-local-metadata.h b/src/metadata/local-metadata/grl-local-metadata.h
> index 448051e..f680061 100644
> --- a/src/metadata/local-metadata/grl-local-metadata.h
> +++ b/src/metadata/local-metadata/grl-local-metadata.h
> @@ -51,12 +51,16 @@
> GRL_LOCAL_METADATA_SOURCE_TYPE, \
> GrlLocalMetadataSourceClass))
>
> -typedef struct _GrlLocalMetadataSource GrlLocalMetadataSource;
> +typedef struct _GrlLocalMetadataSource GrlLocalMetadataSource;
> +typedef struct _GrlLocalMetadataSourcePriv GrlLocalMetadataSourcePriv;
>
> struct _GrlLocalMetadataSource {
>
> GrlMetadataSource parent;
>
> + /*< private >*/
> + GrlLocalMetadataSourcePriv *priv;
> +
> };
>
> typedef struct _GrlLocalMetadataSourceClass GrlLocalMetadataSourceClass;
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]