/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ /* * GData Client * Copyright (C) Philip Withnall 2009–2010 * * GData Client 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. * * GData Client 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 GData Client. If not, see . */ /** * SECTION:gdata-media-thumbnail * @short_description: Media RSS thumbnail element * @stability: Stable * @include: gdata/media/gdata-media-thumbnail.h * * #GDataMediaThumbnail represents a "thumbnail" element from the * Media RSS specification. * * The class only implements parsing, not XML output, at the moment. */ #include #include #include #include "gdata-media-thumbnail.h" #include "gdata-download-stream.h" #include "gdata-parsable.h" #include "gdata-parser.h" #include "gdata-private.h" static void gdata_media_thumbnail_finalize (GObject *object); static void gdata_media_thumbnail_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec); static gboolean pre_parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *root_node, gpointer user_data, GError **error); static void get_namespaces (GDataParsable *parsable, GHashTable *namespaces); static gboolean parse_json (GDataParsable *parsable, JsonReader *reader, gpointer user_data, GError **error); struct _GDataMediaThumbnailPrivate { gchar *uri; guint height; guint width; gint64 time; }; enum { PROP_URI = 1, PROP_HEIGHT, PROP_WIDTH, PROP_TIME }; G_DEFINE_TYPE (GDataMediaThumbnail, gdata_media_thumbnail, GDATA_TYPE_PARSABLE) static void gdata_media_thumbnail_class_init (GDataMediaThumbnailClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); GDataParsableClass *parsable_class = GDATA_PARSABLE_CLASS (klass); g_type_class_add_private (klass, sizeof (GDataMediaThumbnailPrivate)); gobject_class->get_property = gdata_media_thumbnail_get_property; gobject_class->finalize = gdata_media_thumbnail_finalize; parsable_class->pre_parse_xml = pre_parse_xml; parsable_class->get_namespaces = get_namespaces; parsable_class->parse_json = parse_json; parsable_class->element_name = "thumbnail"; parsable_class->element_namespace = "media"; /** * GDataMediaThumbnail:uri: * * The URI of the thumbnail. * * For more information, see the Media RSS specification. * * Since: 0.4.0 */ g_object_class_install_property (gobject_class, PROP_URI, g_param_spec_string ("uri", "URI", "The URI of the thumbnail.", NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); /** * GDataMediaThumbnail:height: * * The height of the thumbnail, in pixels. * * For more information, see the Media RSS specification. * * Since: 0.4.0 */ g_object_class_install_property (gobject_class, PROP_HEIGHT, g_param_spec_uint ("height", "Height", "The height of the thumbnail, in pixels.", 0, G_MAXUINT, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); /** * GDataMediaThumbnail:width: * * The width of the thumbnail, in pixels. * * For more information, see the Media RSS specification. * * Since: 0.4.0 */ g_object_class_install_property (gobject_class, PROP_WIDTH, g_param_spec_uint ("width", "Width", "The width of the thumbnail, in pixels.", 0, G_MAXUINT, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); /** * GDataMediaThumbnail:time: * * The time offset of the thumbnail in relation to the media object, in milliseconds. * * For more information, see the Media RSS specification. * * Since: 0.4.0 */ g_object_class_install_property (gobject_class, PROP_TIME, g_param_spec_int64 ("time", "Time", "The time offset of the thumbnail in relation to the media object, in ms.", -1, G_MAXINT64, -1, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); } static void gdata_media_thumbnail_init (GDataMediaThumbnail *self) { self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GDATA_TYPE_MEDIA_THUMBNAIL, GDataMediaThumbnailPrivate); } static void gdata_media_thumbnail_finalize (GObject *object) { GDataMediaThumbnailPrivate *priv = GDATA_MEDIA_THUMBNAIL (object)->priv; g_free (priv->uri); /* Chain up to the parent class */ G_OBJECT_CLASS (gdata_media_thumbnail_parent_class)->finalize (object); } static void gdata_media_thumbnail_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { GDataMediaThumbnailPrivate *priv = GDATA_MEDIA_THUMBNAIL (object)->priv; switch (property_id) { case PROP_URI: g_value_set_string (value, priv->uri); break; case PROP_HEIGHT: g_value_set_uint (value, priv->height); break; case PROP_WIDTH: g_value_set_uint (value, priv->width); break; case PROP_TIME: g_value_set_int64 (value, priv->time); break; default: /* We don't have any other property... */ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } /* * gdata_media_thumbnail_parse_time: * @time_string: a time string to parse * * Parses a time string in (a subset of) NTP format into a number of milliseconds since the start of a media stream. * * For more information about NTP format, see RFC 2326 3.6 Normal Play Time. * * To build an NTP-format string, see gdata_media_thumbnail_build_time(). * * Return value: number of milliseconds since the start of a media stream */ static gint64 parse_time (const gchar *time_string) { guint hours, minutes; gdouble seconds; gchar *end_pointer; g_return_val_if_fail (time_string != NULL, 0); hours = g_ascii_strtoull (time_string, &end_pointer, 10); if (end_pointer != time_string + 2) return -1; minutes = g_ascii_strtoull (time_string + 3, &end_pointer, 10); if (end_pointer != time_string + 5) return -1; seconds = g_ascii_strtod (time_string + 6, &end_pointer); if (end_pointer != time_string + strlen (time_string)) return -1; return (gint64) ((seconds + minutes * 60 + hours * 3600) * 1000); } /* * gdata_media_thumbnail_build_time: * @_time: a number of milliseconds since the start of a media stream * * Builds an NTP-format time string describing @_time milliseconds since the start * of a media stream. * * Return value: an NTP-format string describing @_time; free with g_free() */ /*static gchar * build_time (gint64 _time) { guint hours, minutes; gfloat seconds; hours = _time % 3600000; _time -= hours * 3600000; minutes = _time % 60000; _time -= minutes * 60000; seconds = _time / 1000.0; return g_strdup_printf ("%02u:%02u:%02f", hours, minutes, seconds); }*/ static gboolean pre_parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *root_node, gpointer user_data, GError **error) { GDataMediaThumbnailPrivate *priv = GDATA_MEDIA_THUMBNAIL (parsable)->priv; xmlChar *uri, *width, *height, *_time; guint width_uint, height_uint; gint64 time_int64; /* Get the width and height */ width = xmlGetProp (root_node, (xmlChar*) "width"); width_uint = (width == NULL) ? 0 : g_ascii_strtoull ((gchar*) width, NULL, 10); xmlFree (width); height = xmlGetProp (root_node, (xmlChar*) "height"); height_uint = (height == NULL) ? 0 : g_ascii_strtoull ((gchar*) height, NULL, 10); xmlFree (height); /* Get and parse the time */ _time = xmlGetProp (root_node, (xmlChar*) "time"); if (_time == NULL) { time_int64 = -1; } else { time_int64 = parse_time ((gchar*) _time); if (time_int64 == -1) { gdata_parser_error_unknown_property_value (root_node, "time", (gchar*) _time, error); xmlFree (_time); return FALSE; } xmlFree (_time); } /* Get the URI */ uri = xmlGetProp (root_node, (xmlChar*) "url"); if (uri == NULL || *uri == '\0') { xmlFree (uri); return gdata_parser_error_required_property_missing (root_node, "url", error); } priv->uri = (gchar*) uri; priv->height = height_uint; priv->width = width_uint; priv->time = time_int64; return TRUE; } static void get_namespaces (GDataParsable *parsable, GHashTable *namespaces) { g_hash_table_insert (namespaces, (gchar*) "media", (gchar*) "http://search.yahoo.com/mrss/"); } /* Reference: * https://developers.google.com/youtube/v3/docs/videos#snippet.thumbnails */ static gboolean parse_json (GDataParsable *parsable, JsonReader *reader, gpointer user_data, GError **error) { gboolean success; gint64 width, height; GDataMediaThumbnail *self = GDATA_MEDIA_THUMBNAIL (parsable); GDataMediaThumbnailPrivate *priv = self->priv; if (gdata_parser_string_from_json_member (reader, "url", P_DEFAULT, &priv->uri, &success, error)) { return success; } else if (gdata_parser_int_from_json_member (reader, "width", P_DEFAULT, &width, &success, error)) { priv->width = width; return success; } else if (gdata_parser_int_from_json_member (reader, "height", P_DEFAULT, &height, &success, error)) { priv->height = height; return success; } else { return GDATA_PARSABLE_CLASS (gdata_media_thumbnail_parent_class)->parse_json (parsable, reader, user_data, error); } } /** * gdata_media_thumbnail_get_uri: * @self: a #GDataMediaThumbnail * * Gets the #GDataMediaThumbnail:uri property. * * Return value: the thumbnail's URI * * Since: 0.4.0 */ const gchar * gdata_media_thumbnail_get_uri (GDataMediaThumbnail *self) { g_return_val_if_fail (GDATA_IS_MEDIA_THUMBNAIL (self), NULL); return self->priv->uri; } /** * gdata_media_thumbnail_get_height: * @self: a #GDataMediaThumbnail * * Gets the #GDataMediaThumbnail:height property. * * Return value: the thumbnail's height in pixels, or 0 * * Since: 0.4.0 */ guint gdata_media_thumbnail_get_height (GDataMediaThumbnail *self) { g_return_val_if_fail (GDATA_IS_MEDIA_THUMBNAIL (self), 0); return self->priv->height; } /** * gdata_media_thumbnail_get_width: * @self: a #GDataMediaThumbnail * * Gets the #GDataMediaThumbnail:width property. * * Return value: the thumbnail's width in pixels, or 0 * * Since: 0.4.0 */ guint gdata_media_thumbnail_get_width (GDataMediaThumbnail *self) { g_return_val_if_fail (GDATA_IS_MEDIA_THUMBNAIL (self), 0); return self->priv->width; } /** * gdata_media_thumbnail_get_time: * @self: a #GDataMediaThumbnail * * Gets the #GDataMediaThumbnail:time property. * * Return value: the thumbnail's time offset in the media, or -1 * * Since: 0.4.0 */ gint64 gdata_media_thumbnail_get_time (GDataMediaThumbnail *self) { g_return_val_if_fail (GDATA_IS_MEDIA_THUMBNAIL (self), -1); return self->priv->time; } /** * gdata_media_thumbnail_download: * @self: a #GDataMediaThumbnail * @service: the #GDataService * @cancellable: (allow-none): a #GCancellable for the entire download stream, or %NULL * @error: a #GError, or %NULL * * Downloads and returns a #GDataDownloadStream allowing the thumbnail data represented by @self to be read. * * To get the content type of the downloaded data, gdata_download_stream_get_content_type() can be called on the returned #GDataDownloadStream. * Calling gdata_download_stream_get_content_length() on the stream will not return a meaningful result, however, as the stream is encoded in chunks, * rather than by content length. * * In order to cancel the download, a #GCancellable passed in to @cancellable must be cancelled using g_cancellable_cancel(). Cancelling the individual * #GInputStream operations on the #GDataDownloadStream will not cancel the entire download; merely the read or close operation in question. See the * #GDataDownloadStream:cancellable for more details. * * Return value: (transfer full): a #GDataDownloadStream to download the thumbnail with, or %NULL; unref with g_object_unref() * * Since: 0.8.0 */ GDataDownloadStream * gdata_media_thumbnail_download (GDataMediaThumbnail *self, GDataService *service, GCancellable *cancellable, GError **error) { const gchar *src_uri; g_return_val_if_fail (GDATA_IS_MEDIA_THUMBNAIL (self), NULL); g_return_val_if_fail (GDATA_IS_SERVICE (service), NULL); g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); /* We keep a GError in the argument list so that we can add authentication errors, etc., in future if necessary */ /* Get the download URI and create a stream for it */ src_uri = gdata_media_thumbnail_get_uri (self); return GDATA_DOWNLOAD_STREAM (gdata_download_stream_new (service, NULL, src_uri, cancellable)); }