summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPhilip Withnall <philip@tecnocode.co.uk>2015-04-20 00:06:33 +0100
committerPhilip Withnall <philip@tecnocode.co.uk>2015-04-20 00:18:42 +0100
commit30a1aa76dce13e854dae0825cf981665254ebf40 (patch)
treef4bbcf904ec3606f000934f5dbdd76dc2041ca4e
parenta581ba449e82593cc418f4cc415e2aadaa13593a (diff)
downloadlibgdata-wip/pwithnall/youtube-v3.tar.gz
youtube: WIP v3 portwip/pwithnall/youtube-v3
Pending documentation: standard feeds are deprecated might have to re-register developer key (https://developers.google.com/youtube/registering_an_application) some properties are now only retrieved for single queries which properties are supported for uploads
-rw-r--r--Makefile.am5
-rw-r--r--docs/reference/Makefile.am2
-rw-r--r--gdata/app/gdata-app-categories.c1
-rw-r--r--gdata/media/gdata-media-thumbnail.c1
-rw-r--r--gdata/services/youtube/gdata-youtube-control.c183
-rw-r--r--gdata/services/youtube/gdata-youtube-control.h75
-rw-r--r--gdata/services/youtube/gdata-youtube-credit.c12
-rw-r--r--gdata/services/youtube/gdata-youtube-credit.h28
-rw-r--r--gdata/services/youtube/gdata-youtube-group.c265
-rw-r--r--gdata/services/youtube/gdata-youtube-group.h80
-rw-r--r--gdata/services/youtube/gdata-youtube-service.c513
-rw-r--r--gdata/services/youtube/gdata-youtube-service.h59
-rw-r--r--gdata/services/youtube/gdata-youtube-state.c50
-rw-r--r--gdata/services/youtube/gdata-youtube-video.c1343
-rw-r--r--gdata/services/youtube/gdata-youtube-video.h8
-rw-r--r--gdata/tests/youtube.c2
16 files changed, 1456 insertions, 1171 deletions
diff --git a/Makefile.am b/Makefile.am
index 1e1e0096..34c9539c 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -323,9 +323,6 @@ gdata_youtube_headers = \
gdata/services/youtube/gdata-youtube-category.h \
gdata/services/youtube/gdata-youtube-comment.h \
gdata/services/youtube/gdata-youtube-feed.h
-private_headers += \
- gdata/services/youtube/gdata-youtube-group.h \
- gdata/services/youtube/gdata-youtube-control.h
gdatayoutubeinclude_HEADERS = \
$(gdata_youtube_headers) \
gdata/services/youtube/gdata-youtube-enums.h
@@ -449,9 +446,7 @@ gdata_sources = \
gdata/services/youtube/gdata-youtube-content.c \
gdata/services/youtube/gdata-youtube-credit.c \
gdata/services/youtube/gdata-youtube-query.c \
- gdata/services/youtube/gdata-youtube-group.c \
gdata/services/youtube/gdata-youtube-state.c \
- gdata/services/youtube/gdata-youtube-control.c \
gdata/services/youtube/gdata-youtube-category.c \
gdata/services/youtube/gdata-youtube-comment.c \
gdata/services/youtube/gdata-youtube-feed.c \
diff --git a/docs/reference/Makefile.am b/docs/reference/Makefile.am
index d5b020bb..0eff9646 100644
--- a/docs/reference/Makefile.am
+++ b/docs/reference/Makefile.am
@@ -56,8 +56,6 @@ IGNORE_HFILES = \
gdata-enums.h \
gdata-media-enums.h \
gdata-media-group.h \
- gdata-youtube-group.h \
- gdata-youtube-control.h \
gdata-youtube-enums.h \
gdata-picasaweb-enums.h \
gdata-documents-enums.h \
diff --git a/gdata/app/gdata-app-categories.c b/gdata/app/gdata-app-categories.c
index aaf7bfa1..3c04e1d8 100644
--- a/gdata/app/gdata-app-categories.c
+++ b/gdata/app/gdata-app-categories.c
@@ -74,6 +74,7 @@ gdata_app_categories_class_init (GDataAPPCategoriesClass *klass)
gobject_class->dispose = gdata_app_categories_dispose;
gobject_class->finalize = gdata_app_categories_finalize;
+ /* TODO: eliminate */
parsable_class->pre_parse_xml = pre_parse_xml;
parsable_class->parse_xml = parse_xml;
parsable_class->post_parse_xml = post_parse_xml;
diff --git a/gdata/media/gdata-media-thumbnail.c b/gdata/media/gdata-media-thumbnail.c
index be55c127..75b5ae38 100644
--- a/gdata/media/gdata-media-thumbnail.c
+++ b/gdata/media/gdata-media-thumbnail.c
@@ -41,6 +41,7 @@
static void gdata_media_thumbnail_finalize (GObject *object);
static void gdata_media_thumbnail_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
+/* TODO: eliminate XML parsing */
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
diff --git a/gdata/services/youtube/gdata-youtube-control.c b/gdata/services/youtube/gdata-youtube-control.c
deleted file mode 100644
index 3436e1b2..00000000
--- a/gdata/services/youtube/gdata-youtube-control.c
+++ /dev/null
@@ -1,183 +0,0 @@
-/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
-/*
- * GData Client
- * Copyright (C) Philip Withnall 2009 <philip@tecnocode.co.uk>
- *
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-/*
- * SECTION:gdata-youtube-control
- * @short_description: Atom Publishing Protocol control element with YouTube-specific modifications
- * @stability: Stable
- * @include: gdata/services/youtube/gdata-youtube-control.h
- *
- * #GDataYouTubeControl represents a "control" element from the
- * <ulink type="http" url="http://www.atomenabled.org/developers/protocol/#appControl">Atom Publishing Protocol specification</ulink>,
- * with support for the YouTube-specific
- * <ulink type="http" url="http://code.google.com/apis/youtube/2.0/reference.html#youtube_data_api_tag_yt:state">"state" element</ulink>.
- *
- * It is private API, since it would be unnecessary and confusing to expose #GDataYouTubeControl itself. There is the option
- * of breaking this up into #GDataAPPControl and extending #GDataYouTubeControl classes in the future, if more entry types implement
- * APP support.
- *
- * For these reasons, properties have not been implemented on #GDataYouTubeControl (yet).
- */
-
-#include <glib.h>
-#include <libxml/parser.h>
-
-#include "gdata-youtube-control.h"
-#include "gdata-parsable.h"
-#include "gdata-parser.h"
-#include "gdata-private.h"
-#include "gdata-youtube-state.h"
-
-static void gdata_youtube_control_dispose (GObject *object);
-static gboolean parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error);
-static void get_xml (GDataParsable *parsable, GString *xml_string);
-static void get_namespaces (GDataParsable *parsable, GHashTable *namespaces);
-
-struct _GDataYouTubeControlPrivate {
- gboolean is_draft;
- GDataYouTubeState *state;
-};
-
-G_DEFINE_TYPE (GDataYouTubeControl, gdata_youtube_control, GDATA_TYPE_PARSABLE)
-
-static void
-gdata_youtube_control_class_init (GDataYouTubeControlClass *klass)
-{
- GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
- GDataParsableClass *parsable_class = GDATA_PARSABLE_CLASS (klass);
-
- g_type_class_add_private (klass, sizeof (GDataYouTubeControlPrivate));
-
- gobject_class->dispose = gdata_youtube_control_dispose;
-
- parsable_class->parse_xml = parse_xml;
- parsable_class->get_xml = get_xml;
- parsable_class->get_namespaces = get_namespaces;
- parsable_class->element_name = "control";
- parsable_class->element_namespace = "app";
-}
-
-static void
-gdata_youtube_control_init (GDataYouTubeControl *self)
-{
- self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GDATA_TYPE_YOUTUBE_CONTROL, GDataYouTubeControlPrivate);
-}
-
-static void
-gdata_youtube_control_dispose (GObject *object)
-{
- GDataYouTubeControlPrivate *priv = GDATA_YOUTUBE_CONTROL (object)->priv;
-
- if (priv->state != NULL)
- g_object_unref (priv->state);
- priv->state = NULL;
-
- /* Chain up to the parent class */
- G_OBJECT_CLASS (gdata_youtube_control_parent_class)->dispose (object);
-}
-
-static gboolean
-parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error)
-{
- gboolean success;
- GDataYouTubeControl *self = GDATA_YOUTUBE_CONTROL (parsable);
-
- if (gdata_parser_is_namespace (node, "http://www.w3.org/2007/app") == TRUE &&
- xmlStrcmp (node->name, (xmlChar*) "draft") == 0) {
- /* app:draft */
- xmlChar *draft = xmlNodeListGetString (doc, node, TRUE);
- if (xmlStrcmp (draft, (xmlChar*) "no") == 0)
- self->priv->is_draft = FALSE;
- else
- self->priv->is_draft = TRUE;
- xmlFree (draft);
- } else if (gdata_parser_is_namespace (node, "http://gdata.youtube.com/schemas/2007") == TRUE &&
- gdata_parser_object_from_element (node, "state", P_REQUIRED | P_NO_DUPES, GDATA_TYPE_YOUTUBE_STATE,
- &(self->priv->state), &success, error) == TRUE) {
- return success;
- } else {
- return GDATA_PARSABLE_CLASS (gdata_youtube_control_parent_class)->parse_xml (parsable, doc, node, user_data, error);
- }
-
- return TRUE;
-}
-
-static void
-get_xml (GDataParsable *parsable, GString *xml_string)
-{
- GDataYouTubeControlPrivate *priv = GDATA_YOUTUBE_CONTROL (parsable)->priv;
-
- if (priv->is_draft == TRUE)
- g_string_append (xml_string, "<app:draft>yes</app:draft>");
- else
- g_string_append (xml_string, "<app:draft>no</app:draft>");
-}
-
-static void
-get_namespaces (GDataParsable *parsable, GHashTable *namespaces)
-{
- g_hash_table_insert (namespaces, (gchar*) "app", (gchar*) "http://www.w3.org/2007/app");
-}
-
-/**
- * gdata_youtube_control_is_draft:
- * @self: a #GDataYouTubeControl
- *
- * Gets the #GDataYouTubeControl:is-draft property.
- *
- * Return value: %TRUE if the object is a draft, %FALSE otherwise
- **/
-gboolean
-gdata_youtube_control_is_draft (GDataYouTubeControl *self)
-{
- g_return_val_if_fail (GDATA_IS_YOUTUBE_CONTROL (self), FALSE);
- return self->priv->is_draft;
-}
-
-/**
- * gdata_youtube_control_set_is_draft:
- * @self: a #GDataYouTubeControl
- * @is_draft: whether the object is a draft
- *
- * Sets the #GDataYouTubeControl:is-draft property to decide whether the object is a draft.
- **/
-void
-gdata_youtube_control_set_is_draft (GDataYouTubeControl *self, gboolean is_draft)
-{
- g_return_if_fail (GDATA_IS_YOUTUBE_CONTROL (self));
- self->priv->is_draft = is_draft;
-}
-
-/**
- * gdata_youtube_control_get_state:
- * @self: a #GDataYouTubeControl
- *
- * Gets the #GDataYouTubeControl:state property.
- *
- * For more information, see the <ulink type="http"
- * url="http://code.google.com/apis/youtube/2.0/reference.html#youtube_data_api_tag_yt:state">online documentation</ulink>.
- *
- * Return value: (transfer none): a #GDataYouTubeState showing the state of the video, or %NULL
- **/
-GDataYouTubeState *
-gdata_youtube_control_get_state (GDataYouTubeControl *self)
-{
- g_return_val_if_fail (GDATA_IS_YOUTUBE_CONTROL (self), NULL);
- return self->priv->state;
-}
diff --git a/gdata/services/youtube/gdata-youtube-control.h b/gdata/services/youtube/gdata-youtube-control.h
deleted file mode 100644
index bfc3fbb6..00000000
--- a/gdata/services/youtube/gdata-youtube-control.h
+++ /dev/null
@@ -1,75 +0,0 @@
-/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
-/*
- * GData Client
- * Copyright (C) Philip Withnall 2009 <philip@tecnocode.co.uk>
- *
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-#ifndef GDATA_YOUTUBE_CONTROL_H
-#define GDATA_YOUTUBE_CONTROL_H
-
-#include <glib.h>
-#include <glib-object.h>
-
-#include <gdata/gdata-parsable.h>
-#include <gdata/services/youtube/gdata-youtube-state.h>
-
-G_BEGIN_DECLS
-
-#define GDATA_TYPE_YOUTUBE_CONTROL (gdata_youtube_control_get_type ())
-#define GDATA_YOUTUBE_CONTROL(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GDATA_TYPE_YOUTUBE_CONTROL, GDataYouTubeControl))
-#define GDATA_YOUTUBE_CONTROL_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GDATA_TYPE_YOUTUBE_CONTROL, GDataYouTubeControlClass))
-#define GDATA_IS_YOUTUBE_CONTROL(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDATA_TYPE_YOUTUBE_CONTROL))
-#define GDATA_IS_YOUTUBE_CONTROL_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GDATA_TYPE_YOUTUBE_CONTROL))
-#define GDATA_YOUTUBE_CONTROL_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDATA_TYPE_YOUTUBE_CONTROL, GDataYouTubeControlClass))
-
-typedef struct _GDataYouTubeControlPrivate GDataYouTubeControlPrivate;
-
-/**
- * GDataYouTubeControl:
- *
- * All the fields in the #GDataYouTubeControl structure are private and should never be accessed directly.
- **/
-typedef struct {
- GDataParsable parent;
- GDataYouTubeControlPrivate *priv;
-} GDataYouTubeControl;
-
-/**
- * GDataYouTubeControlClass:
- *
- * All the fields in the #GDataYouTubeControlClass structure are private and should never be accessed directly.
- *
- * Since: 0.4.0
- **/
-typedef struct {
- /*< private >*/
- GDataParsableClass parent;
-
- /*< private >*/
- /* Padding for future expansion */
- void (*_g_reserved0) (void);
- void (*_g_reserved1) (void);
-} GDataYouTubeControlClass;
-
-GType gdata_youtube_control_get_type (void) G_GNUC_CONST;
-
-gboolean gdata_youtube_control_is_draft (GDataYouTubeControl *self) G_GNUC_PURE;
-void gdata_youtube_control_set_is_draft (GDataYouTubeControl *self, gboolean is_draft);
-GDataYouTubeState *gdata_youtube_control_get_state (GDataYouTubeControl *self) G_GNUC_PURE;
-
-G_END_DECLS
-
-#endif /* !GDATA_YOUTUBE_CONTROL_H */
diff --git a/gdata/services/youtube/gdata-youtube-credit.c b/gdata/services/youtube/gdata-youtube-credit.c
index bfaf7edc..9ce5cb45 100644
--- a/gdata/services/youtube/gdata-youtube-credit.c
+++ b/gdata/services/youtube/gdata-youtube-credit.c
@@ -1,7 +1,7 @@
/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/*
* GData Client
- * Copyright (C) Philip Withnall 2009–2010 <philip@tecnocode.co.uk>
+ * Copyright (C) Philip Withnall 2009–2010, 2015 <philip@tecnocode.co.uk>
*
* GData Client is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -28,6 +28,8 @@
* online documentation</ulink>.
*
* Since: 0.4.0
+ * Deprecated: UNRELEASED: This is no longer supported by Google. There is no
+ * replacement.
**/
#include <glib.h>
@@ -37,6 +39,8 @@
#include "gdata-parsable.h"
#include "gdata-parser.h"
+G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+
static void gdata_youtube_credit_finalize (GObject *object);
static void gdata_youtube_credit_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);
@@ -78,6 +82,8 @@ gdata_youtube_credit_class_init (GDataYouTubeCreditClass *klass)
* YouTube documentation</ulink>.
*
* Since: 0.4.0
+ * Deprecated: UNRELEASED: This is no longer supported by Google. There
+ * is no replacement.
**/
g_object_class_install_property (gobject_class, PROP_ENTITY_TYPE,
g_param_spec_string ("entity-type",
@@ -160,6 +166,8 @@ get_namespaces (GDataParsable *parsable, GHashTable *namespaces)
* Return value: the type of the credited user (e.g. %GDATA_YOUTUBE_CREDIT_ENTITY_PARTNER), or %NULL
*
* Since: 0.4.0
+ * Deprecated: UNRELEASED: This is no longer supported by Google. There is no
+ * replacement.
**/
const gchar *
gdata_youtube_credit_get_entity_type (GDataYouTubeCredit *self)
@@ -167,3 +175,5 @@ gdata_youtube_credit_get_entity_type (GDataYouTubeCredit *self)
g_return_val_if_fail (GDATA_IS_YOUTUBE_CREDIT (self), NULL);
return self->priv->entity_type;
}
+
+G_GNUC_END_IGNORE_DEPRECATIONS
diff --git a/gdata/services/youtube/gdata-youtube-credit.h b/gdata/services/youtube/gdata-youtube-credit.h
index b140ff11..4462d957 100644
--- a/gdata/services/youtube/gdata-youtube-credit.h
+++ b/gdata/services/youtube/gdata-youtube-credit.h
@@ -1,7 +1,7 @@
/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/*
* GData Client
- * Copyright (C) Philip Withnall 2009–2010 <philip@tecnocode.co.uk>
+ * Copyright (C) Philip Withnall 2009–2010, 2015 <philip@tecnocode.co.uk>
*
* GData Client is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -28,12 +28,16 @@
G_BEGIN_DECLS
+#ifndef LIBGDATA_DISABLE_DEPRECATED
+
/**
* GDATA_YOUTUBE_CREDIT_ENTITY_PARTNER:
*
* The credited entity is a YouTube partner.
*
* Since: 0.7.0
+ * Deprecated: UNRELEASED: This is no longer supported by Google. There is no
+ * replacement.
**/
#define GDATA_YOUTUBE_CREDIT_ENTITY_PARTNER "partner"
@@ -44,7 +48,7 @@ G_BEGIN_DECLS
#define GDATA_IS_YOUTUBE_CREDIT_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GDATA_TYPE_YOUTUBE_CREDIT))
#define GDATA_YOUTUBE_CREDIT_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDATA_TYPE_YOUTUBE_CREDIT, GDataYouTubeCreditClass))
-typedef struct _GDataYouTubeCreditPrivate GDataYouTubeCreditPrivate;
+typedef struct _GDataYouTubeCreditPrivate GDataYouTubeCreditPrivate G_GNUC_DEPRECATED;
/**
* GDataYouTubeCredit:
@@ -52,11 +56,15 @@ typedef struct _GDataYouTubeCreditPrivate GDataYouTubeCreditPrivate;
* All the fields in the #GDataYouTubeCredit structure are private and should never be accessed directly.
*
* Since: 0.4.0
+ * Deprecated: UNRELEASED: This is no longer supported by Google. There is no
+ * replacement.
**/
typedef struct {
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS
GDataMediaCredit parent;
GDataYouTubeCreditPrivate *priv;
-} GDataYouTubeCredit;
+ G_GNUC_END_IGNORE_DEPRECATIONS
+} GDataYouTubeCredit G_GNUC_DEPRECATED;
/**
* GDataYouTubeCreditClass:
@@ -64,19 +72,27 @@ typedef struct {
* All the fields in the #GDataYouTubeCreditClass structure are private and should never be accessed directly.
*
* Since: 0.4.0
+ * Deprecated: UNRELEASED: This is no longer supported by Google. There is no
+ * replacement.
**/
typedef struct {
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS
/*< private >*/
GDataMediaCreditClass parent;
+ G_GNUC_END_IGNORE_DEPRECATIONS
/*< private >*/
/* Padding for future expansion */
void (*_g_reserved0) (void);
void (*_g_reserved1) (void);
-} GDataYouTubeCreditClass;
+} GDataYouTubeCreditClass G_GNUC_DEPRECATED;
+
+G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+GType gdata_youtube_credit_get_type (void) G_GNUC_CONST G_GNUC_DEPRECATED;
+const gchar *gdata_youtube_credit_get_entity_type (GDataYouTubeCredit *self) G_GNUC_PURE G_GNUC_DEPRECATED;
+G_GNUC_END_IGNORE_DEPRECATIONS
-GType gdata_youtube_credit_get_type (void) G_GNUC_CONST;
-const gchar *gdata_youtube_credit_get_entity_type (GDataYouTubeCredit *self) G_GNUC_PURE;
+#endif /* !LIBGDATA_DISABLE_DEPRECATED */
G_END_DECLS
diff --git a/gdata/services/youtube/gdata-youtube-group.c b/gdata/services/youtube/gdata-youtube-group.c
deleted file mode 100644
index 921fbdd0..00000000
--- a/gdata/services/youtube/gdata-youtube-group.c
+++ /dev/null
@@ -1,265 +0,0 @@
-/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
-/*
- * GData Client
- * Copyright (C) Philip Withnall 2009–2010 <philip@tecnocode.co.uk>
- *
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-/*
- * SECTION:gdata-youtube-group
- * @short_description: YouTube group element
- * @stability: Stable
- * @include: gdata/services/youtube/gdata-youtube-group.h
- *
- * #GDataYouTubeGroup represents the YouTube-specific customizations to #GDataMediaGroup. For more information,
- * see the <ulink type="http" url="http://code.google.com/apis/youtube/2.0/reference.html#youtube_data_api_tag_media:group">
- * online documentation</ulink>.
- */
-
-#include <glib.h>
-#include <libxml/parser.h>
-
-#include "gdata-youtube-group.h"
-#include "gdata-parsable.h"
-#include "gdata-parser.h"
-#include "gdata-private.h"
-#include "gdata-youtube-enums.h"
-#include "gdata-youtube-content.h"
-#include "gdata-youtube-credit.h"
-
-static void gdata_youtube_group_finalize (GObject *object);
-static gboolean parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error);
-static void get_xml (GDataParsable *parsable, GString *xml_string);
-static void get_namespaces (GDataParsable *parsable, GHashTable *namespaces);
-
-struct _GDataYouTubeGroupPrivate {
- guint duration;
- gboolean is_private;
- gint64 uploaded;
- gchar *video_id;
- gchar *aspect_ratio;
-};
-
-G_DEFINE_TYPE (GDataYouTubeGroup, gdata_youtube_group, GDATA_TYPE_MEDIA_GROUP)
-
-static void
-gdata_youtube_group_class_init (GDataYouTubeGroupClass *klass)
-{
- GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
- GDataParsableClass *parsable_class = GDATA_PARSABLE_CLASS (klass);
-
- g_type_class_add_private (klass, sizeof (GDataYouTubeGroupPrivate));
-
- gobject_class->finalize = gdata_youtube_group_finalize;
-
- parsable_class->parse_xml = parse_xml;
- parsable_class->get_xml = get_xml;
- parsable_class->get_namespaces = get_namespaces;
-}
-
-static void
-gdata_youtube_group_init (GDataYouTubeGroup *self)
-{
- self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GDATA_TYPE_YOUTUBE_GROUP, GDataYouTubeGroupPrivate);
- self->priv->uploaded = -1;
-}
-
-static void
-gdata_youtube_group_finalize (GObject *object)
-{
- GDataYouTubeGroupPrivate *priv = GDATA_YOUTUBE_GROUP (object)->priv;
-
- g_free (priv->video_id);
-
- /* Chain up to the parent class */
- G_OBJECT_CLASS (gdata_youtube_group_parent_class)->finalize (object);
-}
-
-static gboolean
-parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error)
-{
- gboolean success;
- GDataYouTubeGroup *self = GDATA_YOUTUBE_GROUP (parsable);
-
- G_GNUC_BEGIN_IGNORE_DEPRECATIONS
-
- if (gdata_parser_is_namespace (node, "http://search.yahoo.com/mrss/") == TRUE &&
- (gdata_parser_object_from_element_setter (node, "content", P_REQUIRED, GDATA_TYPE_YOUTUBE_CONTENT,
- _gdata_media_group_add_content, self, &success, error) == TRUE ||
- gdata_parser_object_from_element_setter (node, "credit", P_REQUIRED, GDATA_TYPE_YOUTUBE_CREDIT,
- _gdata_media_group_set_credit, self, &success, error) == TRUE)) {
- return success;
- } else if (gdata_parser_is_namespace (node, "http://gdata.youtube.com/schemas/2007") == TRUE) {
- if (gdata_parser_string_from_element (node, "videoid", P_NO_DUPES, &(self->priv->video_id), &success, error) == TRUE ||
- gdata_parser_string_from_element (node, "aspectRatio", P_REQUIRED | P_NO_DUPES,
- &(self->priv->aspect_ratio), &success, error) == TRUE ||
- gdata_parser_int64_time_from_element (node, "uploaded", P_REQUIRED | P_NO_DUPES,
- &(self->priv->uploaded), &success, error) == TRUE) {
- return success;
- } else if (xmlStrcmp (node->name, (xmlChar*) "duration") == 0) {
- /* yt:duration */
- xmlChar *duration = xmlGetProp (node, (xmlChar*) "seconds");
- if (duration == NULL)
- return gdata_parser_error_required_property_missing (node, "seconds", error);
-
- self->priv->duration = g_ascii_strtoull ((gchar*) duration, NULL, 10);
- xmlFree (duration);
- } else if (xmlStrcmp (node->name, (xmlChar*) "private") == 0) {
- /* yt:private */
- gdata_youtube_group_set_is_private (self, TRUE);
- } else {
- return GDATA_PARSABLE_CLASS (gdata_youtube_group_parent_class)->parse_xml (parsable, doc, node, user_data, error);
- }
- } else {
- return GDATA_PARSABLE_CLASS (gdata_youtube_group_parent_class)->parse_xml (parsable, doc, node, user_data, error);
- }
-
- G_GNUC_END_IGNORE_DEPRECATIONS
-
- return TRUE;
-}
-
-static void
-get_xml (GDataParsable *parsable, GString *xml_string)
-{
- GDataYouTubeGroupPrivate *priv = GDATA_YOUTUBE_GROUP (parsable)->priv;
-
- /* Chain up to the parent class */
- GDATA_PARSABLE_CLASS (gdata_youtube_group_parent_class)->get_xml (parsable, xml_string);
-
- if (priv->is_private == TRUE)
- g_string_append (xml_string, "<yt:private/>");
- if (priv->aspect_ratio != NULL)
- gdata_parser_string_append_escaped (xml_string, "<yt:aspectratio>", priv->aspect_ratio, "</yt:aspectratio>");
-}
-
-static void
-get_namespaces (GDataParsable *parsable, GHashTable *namespaces)
-{
- /* Chain up to the parent class */
- GDATA_PARSABLE_CLASS (gdata_youtube_group_parent_class)->get_namespaces (parsable, namespaces);
-
- g_hash_table_insert (namespaces, (gchar*) "yt", (gchar*) "http://gdata.youtube.com/schemas/2007");
-}
-
-/**
- * gdata_youtube_group_get_duration:
- * @self: a #GDataYouTubeGroup
- *
- * Gets the #GDataYouTubeGroup:duration property.
- *
- * Return value: the video duration in seconds, or <code class="literal">0</code> if unknown
- **/
-guint
-gdata_youtube_group_get_duration (GDataYouTubeGroup *self)
-{
- g_return_val_if_fail (GDATA_IS_YOUTUBE_GROUP (self), 0);
- return self->priv->duration;
-}
-
-/**
- * gdata_youtube_group_is_private:
- * @self: a #GDataYouTubeGroup
- *
- * Gets the #GDataYouTubeGroup:is-private property.
- *
- * Return value: %TRUE if the video is private, %FALSE otherwise
- **/
-gboolean
-gdata_youtube_group_is_private (GDataYouTubeGroup *self)
-{
- g_return_val_if_fail (GDATA_IS_YOUTUBE_GROUP (self), FALSE);
- return self->priv->is_private;
-}
-
-/**
- * gdata_youtube_group_set_is_private:
- * @self: a #GDataYouTubeGroup
- * @is_private: whether the video is private
- *
- * Sets the #GDataYouTubeGroup:is-private property to decide whether the video is publicly viewable.
- **/
-void
-gdata_youtube_group_set_is_private (GDataYouTubeGroup *self, gboolean is_private)
-{
- g_return_if_fail (GDATA_IS_YOUTUBE_GROUP (self));
- self->priv->is_private = is_private;
-}
-
-/**
- * gdata_youtube_group_get_uploaded:
- * @self: a #GDataYouTubeGroup
- *
- * Gets the #GDataYouTubeGroup:uploaded property. If the property is unset, <code class="literal">-1</code> will be returned.
- *
- * Return value: the UNIX timestamp for the time the group was uploaded, or <code class="literal">-1</code>
- **/
-gint64
-gdata_youtube_group_get_uploaded (GDataYouTubeGroup *self)
-{
- g_return_val_if_fail (GDATA_IS_YOUTUBE_GROUP (self), -1);
- return self->priv->uploaded;
-}
-
-/**
- * gdata_youtube_group_get_video_id:
- * @self: a #GDataYouTubeGroup
- *
- * Gets the #GDataYouTubeGroup:video-id property.
- *
- * Return value: the video's unique and permanent ID
- **/
-const gchar *
-gdata_youtube_group_get_video_id (GDataYouTubeGroup *self)
-{
- g_return_val_if_fail (GDATA_IS_YOUTUBE_GROUP (self), NULL);
- return self->priv->video_id;
-}
-
-/**
- * gdata_youtube_group_get_aspect_ratio:
- * @self: a #GDataYouTubeGroup
- *
- * Gets the #GDataYouTubeGroup:aspect-ratio property.
- *
- * Return value: the aspect ratio property, or %NULL
- *
- * Since: 0.4.0
- **/
-const gchar *
-gdata_youtube_group_get_aspect_ratio (GDataYouTubeGroup *self)
-{
- g_return_val_if_fail (GDATA_IS_YOUTUBE_GROUP (self), NULL);
- return self->priv->aspect_ratio;
-}
-
-/**
- * gdata_youtube_group_set_aspect_ratio:
- * @self: a #GDataYouTubeGroup
- * @aspect_ratio: (allow-none): the aspect ratio property, or %NULL
- *
- * Sets the #GDataYouTubeGroup:aspect-ratio property to decide the video aspect ratio.
- * If @aspect_ratio is %NULL, the property will be unset.
- *
- * Since: 0.4.0
- **/
-void
-gdata_youtube_group_set_aspect_ratio (GDataYouTubeGroup *self, const gchar *aspect_ratio)
-{
- g_return_if_fail (GDATA_IS_YOUTUBE_GROUP (self));
-
- g_free (self->priv->aspect_ratio);
- self->priv->aspect_ratio = g_strdup (aspect_ratio);
-}
diff --git a/gdata/services/youtube/gdata-youtube-group.h b/gdata/services/youtube/gdata-youtube-group.h
deleted file mode 100644
index c48f33c9..00000000
--- a/gdata/services/youtube/gdata-youtube-group.h
+++ /dev/null
@@ -1,80 +0,0 @@
-/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
-/*
- * GData Client
- * Copyright (C) Philip Withnall 2009 <philip@tecnocode.co.uk>
- *
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-#ifndef GDATA_YOUTUBE_GROUP_H
-#define GDATA_YOUTUBE_GROUP_H
-
-#include <glib.h>
-#include <glib-object.h>
-
-#include <gdata/gdata-parsable.h>
-#include <gdata/media/gdata-media-group.h>
-#include <gdata/services/youtube/gdata-youtube-video.h>
-
-G_BEGIN_DECLS
-
-#define GDATA_TYPE_YOUTUBE_GROUP (gdata_youtube_group_get_type ())
-#define GDATA_YOUTUBE_GROUP(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GDATA_TYPE_YOUTUBE_GROUP, GDataYouTubeGroup))
-#define GDATA_YOUTUBE_GROUP_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GDATA_TYPE_YOUTUBE_GROUP, GDataYouTubeGroupClass))
-#define GDATA_IS_YOUTUBE_GROUP(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDATA_TYPE_YOUTUBE_GROUP))
-#define GDATA_IS_YOUTUBE_GROUP_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GDATA_TYPE_YOUTUBE_GROUP))
-#define GDATA_YOUTUBE_GROUP_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDATA_TYPE_YOUTUBE_GROUP, GDataYouTubeGroupClass))
-
-typedef struct _GDataYouTubeGroupPrivate GDataYouTubeGroupPrivate;
-
-/**
- * GDataYouTubeGroup:
- *
- * All the fields in the #GDataYouTubeGroup structure are private and should never be accessed directly.
- **/
-typedef struct {
- GDataMediaGroup parent;
- GDataYouTubeGroupPrivate *priv;
-} GDataYouTubeGroup;
-
-/**
- * GDataYouTubeGroupClass:
- *
- * All the fields in the #GDataYouTubeGroupClass structure are private and should never be accessed directly.
- *
- * Since: 0.4.0
- **/
-typedef struct {
- /*< private >*/
- GDataMediaGroupClass parent;
-
- /*< private >*/
- /* Padding for future expansion */
- void (*_g_reserved0) (void);
- void (*_g_reserved1) (void);
-} GDataYouTubeGroupClass;
-
-GType gdata_youtube_group_get_type (void) G_GNUC_CONST;
-
-guint gdata_youtube_group_get_duration (GDataYouTubeGroup *self) G_GNUC_PURE;
-gboolean gdata_youtube_group_is_private (GDataYouTubeGroup *self) G_GNUC_PURE;
-void gdata_youtube_group_set_is_private (GDataYouTubeGroup *self, gboolean is_private);
-gint64 gdata_youtube_group_get_uploaded (GDataYouTubeGroup *self);
-const gchar *gdata_youtube_group_get_video_id (GDataYouTubeGroup *self) G_GNUC_PURE;
-const gchar *gdata_youtube_group_get_aspect_ratio (GDataYouTubeGroup *self) G_GNUC_PURE;
-void gdata_youtube_group_set_aspect_ratio (GDataYouTubeGroup *self, const gchar *aspect_ratio);
-
-G_END_DECLS
-
-#endif /* !GDATA_YOUTUBE_GROUP_H */
diff --git a/gdata/services/youtube/gdata-youtube-service.c b/gdata/services/youtube/gdata-youtube-service.c
index 7e54fafa..06ee9b97 100644
--- a/gdata/services/youtube/gdata-youtube-service.c
+++ b/gdata/services/youtube/gdata-youtube-service.c
@@ -1,7 +1,7 @@
/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/*
* GData Client
- * Copyright (C) Philip Withnall 2008–2010 <philip@tecnocode.co.uk>
+ * Copyright (C) Philip Withnall 2008–2010, 2015 <philip@tecnocode.co.uk>
*
* GData Client is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -24,9 +24,16 @@
* @include: gdata/services/youtube/gdata-youtube-service.h
*
* #GDataYouTubeService is a subclass of #GDataService for communicating with the GData API of YouTube. It supports querying for and
- * uploading videos.
+ * uploading videos using version 3 of the API.
*
- * For more details of YouTube's GData API, see the <ulink type="http" url="http://code.google.com/apis/youtube/2.0/reference.html">
+ * The YouTube API supports returning different sets of properties for
+ * #GDataYouTubeVideos depending on the specific query. For search results, only
+ * ‘snippet’ properties are returned (including #GDataEntry:title,
+ * #GDataEntry:summary and the set of thumbnails). For querying single videos,
+ * a more complete set of properties are returned — so use
+ * gdata_service_query_single_entry_async() to get further details on a video.
+ *
+ * For more details of YouTube's GData API, see the <ulink type="http" url="https://developers.google.com/youtube/v3/docs/">
* online documentation</ulink>.
*
* <example>
@@ -253,7 +260,7 @@
#include "gdata-youtube-category.h"
#include "gdata-batchable.h"
-/* Standards reference here: http://code.google.com/apis/youtube/2.0/reference.html */
+/* Standards reference here: https://developers.google.com/youtube/v3/docs/ */
GQuark
gdata_youtube_service_error_quark (void)
@@ -278,7 +285,9 @@ enum {
PROP_DEVELOPER_KEY = 1
};
-_GDATA_DEFINE_AUTHORIZATION_DOMAIN (youtube, "youtube", "http://gdata.youtube.com")
+/* Reference: https://developers.google.com/youtube/v3/guides/authentication */
+_GDATA_DEFINE_AUTHORIZATION_DOMAIN (youtube, "youtube",
+ "https://www.googleapis.com/auth/youtube")
G_DEFINE_TYPE_WITH_CODE (GDataYouTubeService, gdata_youtube_service, GDATA_TYPE_SERVICE, G_IMPLEMENT_INTERFACE (GDATA_TYPE_BATCHABLE, NULL))
static void
@@ -302,7 +311,11 @@ gdata_youtube_service_class_init (GDataYouTubeServiceClass *klass)
* GDataYouTubeService:developer-key:
*
* The developer key your application has registered with the YouTube API. For more information, see the <ulink type="http"
- * url="http://code.google.com/apis/youtube/2.0/developers_guide_protocol.html#Developer_Key">online documentation</ulink>.
+ * url="https://developers.google.com/youtube/registering_an_application">online documentation</ulink>.
+ *
+ * With the port from v2 to v3 of the YouTube API in libgdata
+ * UNRELEASED, it might be necessary to update your application’s
+ * developer key.
**/
g_object_class_install_property (gobject_class, PROP_DEVELOPER_KEY,
g_param_spec_string ("developer-key",
@@ -361,161 +374,220 @@ gdata_youtube_service_set_property (GObject *object, guint property_id, const GV
}
static void
-append_query_headers (GDataService *self, GDataAuthorizationDomain *domain, SoupMessage *message)
+append_query_headers (GDataService *self, GDataAuthorizationDomain *domain,
+ SoupMessage *message)
{
GDataYouTubeServicePrivate *priv = GDATA_YOUTUBE_SERVICE (self)->priv;
- gchar *key_header;
g_assert (message != NULL);
- /* Dev key and client headers */
- key_header = g_strdup_printf ("key=%s", priv->developer_key);
- soup_message_headers_append (message->request_headers, "X-GData-Key", key_header);
- g_free (key_header);
+ if (priv->developer_key != NULL &&
+ !gdata_authorizer_is_authorized_for_domain (gdata_service_get_authorizer (GDATA_SERVICE (self)),
+ get_youtube_authorization_domain ())) {
+ const gchar *query;
+ SoupURI *uri;
+
+ uri = soup_message_get_uri (message);
+ query = soup_uri_get_query (uri);
+
+ /* Set the key on every unauthorised request:
+ * https://developers.google.com/youtube/v3/docs/standard_parameters#key */
+ if (query != NULL) {
+ GString *new_query;
+
+ new_query = g_string_new (query);
+
+ g_string_append (new_query, "&key=");
+ g_string_append_uri_escaped (new_query,
+ priv->developer_key, NULL,
+ FALSE);
+
+ soup_uri_set_query (uri, new_query->str);
+ g_string_free (new_query, TRUE);
+ }
+ }
/* Chain up to the parent class */
GDATA_SERVICE_CLASS (gdata_youtube_service_parent_class)->append_query_headers (self, domain, message);
}
+/* Reference: https://developers.google.com/youtube/v3/docs/errors
+ *
+ * Example response:
+ * {
+ * "error": {
+ * "errors": [
+ * {
+ * "domain": "youtube.parameter",
+ * "reason": "missingRequiredParameter",
+ * "message": "No filter selected.",
+ * "locationType": "parameter",
+ * "location": ""
+ * }
+ * ],
+ * "code": 400,
+ * "message": "No filter selected."
+ * }
+ * }
+ */
+/* FIXME: Factor this out into a common JSON error parser helper which simply
+ * takes a map of expected error codes. */
static void
-parse_error_response (GDataService *self, GDataOperationType operation_type, guint status, const gchar *reason_phrase, const gchar *response_body,
- gint length, GError **error)
+parse_error_response (GDataService *self, GDataOperationType operation_type,
+ guint status, const gchar *reason_phrase,
+ const gchar *response_body, gint length, GError **error)
{
- xmlDoc *doc;
- xmlNode *node;
+ JsonParser *parser = NULL; /* owned */
+ JsonReader *reader = NULL; /* owned */
+ gint i;
+ GError *child_error = NULL;
- if (response_body == NULL)
+ if (response_body == NULL) {
goto parent;
+ }
- if (length == -1)
+ if (length == -1) {
length = strlen (response_body);
+ }
- /* Parse the XML */
- doc = xmlReadMemory (response_body, length, "/dev/null", NULL, 0);
- if (doc == NULL)
+ parser = json_parser_new ();
+ if (!json_parser_load_from_data (parser, response_body, length,
+ &child_error)) {
goto parent;
+ }
+
+ reader = json_reader_new (json_parser_get_root (parser));
- /* Get the root element */
- node = xmlDocGetRootElement (doc);
- if (node == NULL) {
- /* XML document's empty; chain up to the parent class */
- xmlFreeDoc (doc);
+ /* Check that the outermost node is an object. */
+ if (!json_reader_is_object (reader)) {
goto parent;
}
- if (xmlStrcmp (node->name, (xmlChar*) "errors") != 0) {
- /* No <errors> element (required); chain up to the parent class */
- xmlFreeDoc (doc);
+ /* Grab the ‘error’ member, then its ‘errors’ member. */
+ if (!json_reader_read_member (reader, "error") ||
+ !json_reader_is_object (reader) ||
+ !json_reader_read_member (reader, "errors") ||
+ !json_reader_is_array (reader)) {
goto parent;
}
- /* Parse the actual errors */
- node = node->children;
- while (node != NULL) {
- xmlChar *domain = NULL, *code = NULL, *location = NULL;
- xmlNode *child_node = node->children;
+ /* Parse each of the errors. Return the first one, and print out any
+ * others. */
+ for (i = 0; i < json_reader_count_elements (reader); i++) {
+ const gchar *domain, *reason, *message, *extended_help;
+ const gchar *location_type, *location;
- if (node->type == XML_TEXT_NODE) {
- /* Skip text nodes; they're all whitespace */
- node = node->next;
- continue;
+ /* Parse the error. */
+ if (!json_reader_read_element (reader, i) ||
+ !json_reader_is_object (reader)) {
+ goto parent;
}
- /* Get the error data */
- while (child_node != NULL) {
- if (child_node->type == XML_TEXT_NODE) {
- /* Skip text nodes; they're all whitespace */
- child_node = child_node->next;
- continue;
- }
+ json_reader_read_member (reader, "domain");
+ domain = json_reader_get_string_value (reader);
+ json_reader_end_member (reader);
- if (xmlStrcmp (child_node->name, (xmlChar*) "domain") == 0)
- domain = xmlNodeListGetString (doc, child_node->children, TRUE);
- else if (xmlStrcmp (child_node->name, (xmlChar*) "code") == 0)
- code = xmlNodeListGetString (doc, child_node->children, TRUE);
- else if (xmlStrcmp (child_node->name, (xmlChar*) "location") == 0)
- location = xmlNodeListGetString (doc, child_node->children, TRUE);
- else if (xmlStrcmp (child_node->name, (xmlChar*) "internalReason") != 0) {
- /* Unknown element (ignore internalReason) */
- g_message ("Unhandled <error/%s> element.", child_node->name);
-
- xmlFree (domain);
- xmlFree (code);
- xmlFree (location);
- xmlFreeDoc (doc);
- goto check_error;
- }
+ json_reader_read_member (reader, "reason");
+ reason = json_reader_get_string_value (reader);
+ json_reader_end_member (reader);
- child_node = child_node->next;
- }
+ json_reader_read_member (reader, "message");
+ message = json_reader_get_string_value (reader);
+ json_reader_end_member (reader);
+
+ json_reader_read_member (reader, "extendedHelp");
+ extended_help = json_reader_get_string_value (reader);
+ json_reader_end_member (reader);
+
+ json_reader_read_member (reader, "locationType");
+ location_type = json_reader_get_string_value (reader);
+ json_reader_end_member (reader);
+
+ json_reader_read_member (reader, "location");
+ location = json_reader_get_string_value (reader);
+ json_reader_end_member (reader);
+
+ /* End the error element. */
+ json_reader_end_element (reader);
/* Create an error message, but only for the first error */
if (error == NULL || *error == NULL) {
- /* See http://code.google.com/apis/youtube/2.0/developers_guide_protocol.html#Error_responses */
- if (xmlStrcmp (domain, (xmlChar*) "yt:service") == 0) {
- if (xmlStrcmp (code, (xmlChar*) "disabled_in_maintenance_mode") == 0) {
- /* Service disabled */
- g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_UNAVAILABLE,
- _("This service is not available at the moment."));
- } else if (xmlStrcmp (code, (xmlChar*) "youtube_signup_required") == 0) {
- /* Tried to authenticate with a Google Account which hasn't yet had a YouTube channel created for it. */
- g_set_error (error, GDATA_YOUTUBE_SERVICE_ERROR, GDATA_YOUTUBE_SERVICE_ERROR_CHANNEL_REQUIRED,
- /* Translators: the parameter is a URI. */
- _("Your Google Account must be associated with a YouTube channel to do this. Visit %s to create one."),
- "https://www.youtube.com/create_channel");
- } else {
- /* Protocol error */
- g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR,
- _("Unknown error code \"%s\" in domain \"%s\" received with location \"%s\"."),
- code, domain, location);
- }
- } else if (xmlStrcmp (domain, (xmlChar*) "yt:authentication") == 0) {
+ if (g_strcmp0 (domain, "usageLimits") == 0 &&
+ g_strcmp0 (reason,
+ "dailyLimitExceededUnreg") == 0) {
+ /* Daily Limit for Unauthenticated Use
+ * Exceeded. */
+ g_set_error (error, GDATA_SERVICE_ERROR,
+ GDATA_SERVICE_ERROR_API_QUOTA_EXCEEDED,
+ _("You have made too many API "
+ "calls recently. Please wait a "
+ "few minutes and try again."));
+ } else if (g_strcmp0 (reason,
+ "rateLimitExceeded") == 0) {
+ g_set_error (error, GDATA_YOUTUBE_SERVICE_ERROR,
+ GDATA_YOUTUBE_SERVICE_ERROR_ENTRY_QUOTA_EXCEEDED,
+ _("You have exceeded your entry "
+ "quota. Please delete some "
+ "entries and try again."));
+ } else if (g_strcmp0 (domain, "global") == 0 &&
+ (g_strcmp0 (reason, "authError") == 0 ||
+ g_strcmp0 (reason, "required") == 0)) {
/* Authentication problem */
- g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED,
- _("You must be authenticated to do this."));
- } else if (xmlStrcmp (domain, (xmlChar*) "yt:quota") == 0) {
- /* Quota errors */
- if (xmlStrcmp (code, (xmlChar*) "too_many_recent_calls") == 0) {
- g_set_error (error, GDATA_YOUTUBE_SERVICE_ERROR, GDATA_YOUTUBE_SERVICE_ERROR_API_QUOTA_EXCEEDED,
- _("You have made too many API calls recently. Please wait a few minutes and try again."));
- } else if (xmlStrcmp (code, (xmlChar*) "too_many_entries") == 0) {
- g_set_error (error, GDATA_YOUTUBE_SERVICE_ERROR, GDATA_YOUTUBE_SERVICE_ERROR_ENTRY_QUOTA_EXCEEDED,
- _("You have exceeded your entry quota. Please delete some entries and try again."));
- } else {
- /* Protocol error */
- g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR,
- /* Translators: the first parameter is an error code, which is a coded string.
- * The second parameter is an error domain, which is another coded string.
- * The third parameter is the location of the error, which is either a URI or an XPath. */
- _("Unknown error code \"%s\" in domain \"%s\" received with location \"%s\"."),
- code, domain, location);
- }
+ g_set_error (error, GDATA_SERVICE_ERROR,
+ GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED,
+ _("You must be authenticated to "
+ "do this."));
+ } else if (g_strcmp0 (reason,
+ "youtubeSignupRequired") == 0) {
+ /* Tried to authenticate with a Google Account which hasn't yet had a YouTube channel created for it. */
+ g_set_error (error, GDATA_YOUTUBE_SERVICE_ERROR,
+ GDATA_YOUTUBE_SERVICE_ERROR_CHANNEL_REQUIRED,
+ /* Translators: the parameter is a URI. */
+ _("Your Google Account must be "
+ "associated with a YouTube "
+ "channel to do this. Visit %s "
+ "to create one."),
+ "https://www.youtube.com/create_channel");
} else {
- /* Unknown or validation (protocol) error */
- g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR,
- _("Unknown error code \"%s\" in domain \"%s\" received with location \"%s\"."),
- code, domain, location);
+ /* Unknown or validation (protocol) error. Fall
+ * back to working off the HTTP status code. */
+ g_warning ("Unknown error code ‘%s’ in domain "
+ "‘%s’ received with location type "
+ "‘%s’, location ‘%s’, extended help "
+ "‘%s’ and message ‘%s’.",
+ reason, domain, location_type,
+ location, extended_help, message);
+
+ goto parent;
}
} else {
- /* For all errors after the first, log the error in the terminal */
- g_debug ("Error message received in response: code \"%s\", domain \"%s\", location \"%s\".", code, domain, location);
+ /* For all errors after the first, log the error in the
+ * terminal. */
+ g_debug ("Error message received in response: domain "
+ "‘%s’, reason ‘%s’, extended help ‘%s’, "
+ "message ‘%s’, location type ‘%s’, location "
+ "‘%s’.",
+ domain, reason, extended_help, message,
+ location_type, location);
}
+ }
- xmlFree (domain);
- xmlFree (code);
- xmlFree (location);
+ /* End the ‘errors’ and ‘error’ members. */
+ json_reader_end_element (reader);
+ json_reader_end_element (reader);
- node = node->next;
- }
+ g_clear_object (&reader);
+ g_clear_object (&parser);
-check_error:
- /* Ensure we're actually set an error message */
- if (error != NULL && *error == NULL)
- g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR, _("Unknown and unparsable error received."));
+ /* Ensure we’ve actually set an error message. */
+ g_assert (error == NULL || *error != NULL);
return;
parent:
+ g_clear_object (&reader);
+ g_clear_object (&parser);
+
/* Chain up to the parent class */
GDATA_SERVICE_CLASS (gdata_youtube_service_parent_class)->parse_error_response (self, operation_type, status, reason_phrase,
response_body, length, error);
@@ -534,7 +606,7 @@ get_authorization_domains (void)
*
* Creates a new #GDataYouTubeService using the given #GDataAuthorizer. If @authorizer is %NULL, all requests are made as an unauthenticated user.
* The @developer_key must be unique for your application, and as
- * <ulink type="http" url="http://code.google.com/apis/youtube/2.0/developers_guide_protocol.html#Developer_Key">registered with Google</ulink>.
+ * <ulink type="http" url="https://developers.google.com/youtube/registering_an_application">registered with Google</ulink>.
*
* Return value: a new #GDataYouTubeService, or %NULL; unref with g_object_unref()
*
@@ -571,30 +643,42 @@ gdata_youtube_service_get_primary_authorization_domain (void)
return get_youtube_authorization_domain ();
}
-static const gchar *
+static gchar *
standard_feed_type_to_feed_uri (GDataYouTubeStandardFeedType feed_type)
{
switch (feed_type) {
+ case GDATA_YOUTUBE_MOST_POPULAR_FEED:
+ return g_strdup ("https://www.googleapis.com/youtube/v3/videos"
+ "?part=snippet"
+ "&chart=mostPopular");
case GDATA_YOUTUBE_TOP_RATED_FEED:
- return "https://gdata.youtube.com/feeds/api/standardfeeds/top_rated";
case GDATA_YOUTUBE_TOP_FAVORITES_FEED:
- return "https://gdata.youtube.com/feeds/api/standardfeeds/top_favorites";
case GDATA_YOUTUBE_MOST_VIEWED_FEED:
- return "https://gdata.youtube.com/feeds/api/standardfeeds/most_viewed";
- case GDATA_YOUTUBE_MOST_POPULAR_FEED:
- return "https://gdata.youtube.com/feeds/api/standardfeeds/most_popular";
case GDATA_YOUTUBE_MOST_RECENT_FEED:
- return "https://gdata.youtube.com/feeds/api/standardfeeds/most_recent";
case GDATA_YOUTUBE_MOST_DISCUSSED_FEED:
- return "https://gdata.youtube.com/feeds/api/standardfeeds/most_discussed";
case GDATA_YOUTUBE_MOST_LINKED_FEED:
- return "https://gdata.youtube.com/feeds/api/standardfeeds/most_linked";
case GDATA_YOUTUBE_MOST_RESPONDED_FEED:
- return "https://gdata.youtube.com/feeds/api/standardfeeds/most_responded";
case GDATA_YOUTUBE_RECENTLY_FEATURED_FEED:
- return "https://gdata.youtube.com/feeds/api/standardfeeds/recently_featured";
- case GDATA_YOUTUBE_WATCH_ON_MOBILE_FEED:
- return "https://gdata.youtube.com/feeds/api/standardfeeds/watch_on_mobile";
+ case GDATA_YOUTUBE_WATCH_ON_MOBILE_FEED: {
+ gchar *date, *out;
+ GTimeVal tv;
+
+ /* All feed types except MOST_POPULAR have been deprecated for
+ * a while, and fall back to MOST_POPULAR on the server anyway.
+ * See: https://developers.google.com/youtube/2.0/developers_guide_protocol_video_feeds#Standard_feeds */
+ g_get_current_time (&tv);
+ tv.tv_sec -= 24 * 60 * 60; /* 1 day ago */
+ date = g_time_val_to_iso8601 (&tv);
+ out = g_strconcat ("https://www.googleapis.com/youtube/v3/videos"
+ "?part=snippet"
+ "&chart=mostPopular"
+ "&publishedAfter=",
+ date,
+ NULL);
+ g_free (date);
+
+ return out;
+ }
default:
g_assert_not_reached ();
}
@@ -612,6 +696,11 @@ standard_feed_type_to_feed_uri (GDataYouTubeStandardFeedType feed_type)
*
* Queries the service's standard @feed_type feed to build a #GDataFeed.
*
+ * Note that with the port from v2 to v3 of the YouTube API in libgdata
+ * UNRELEASED, all feed types except %GDATA_YOUTUBE_MOST_POPULAR_FEED have been
+ * deprecated. Other feed types will now transparently return
+ * %GDATA_YOUTUBE_MOST_POPULAR_FEED, limited to the past 24 hours.
+ *
* Parameters and errors are as for gdata_service_query().
*
* Return value: (transfer full): a #GDataFeed of query results, or %NULL; unref with g_object_unref()
@@ -621,14 +710,24 @@ gdata_youtube_service_query_standard_feed (GDataYouTubeService *self, GDataYouTu
GCancellable *cancellable, GDataQueryProgressCallback progress_callback, gpointer progress_user_data,
GError **error)
{
+ gchar *query_uri;
+ GDataFeed *feed;
+
g_return_val_if_fail (GDATA_IS_YOUTUBE_SERVICE (self), NULL);
g_return_val_if_fail (query == NULL || GDATA_IS_QUERY (query), NULL);
g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
/* TODO: Support the "time" parameter, as well as category- and region-specific feeds */
- return gdata_service_query (GDATA_SERVICE (self), get_youtube_authorization_domain (), standard_feed_type_to_feed_uri (feed_type), query,
- GDATA_TYPE_YOUTUBE_VIDEO, cancellable, progress_callback, progress_user_data, error);
+ query_uri = standard_feed_type_to_feed_uri (feed_type);
+ feed = gdata_service_query (GDATA_SERVICE (self),
+ get_youtube_authorization_domain (),
+ query_uri, query, GDATA_TYPE_YOUTUBE_VIDEO,
+ cancellable, progress_callback,
+ progress_user_data, error);
+ g_free (query_uri);
+
+ return feed;
}
/**
@@ -660,14 +759,22 @@ gdata_youtube_service_query_standard_feed_async (GDataYouTubeService *self, GDat
GDestroyNotify destroy_progress_user_data,
GAsyncReadyCallback callback, gpointer user_data)
{
+ gchar *query_uri;
+
g_return_if_fail (GDATA_IS_YOUTUBE_SERVICE (self));
g_return_if_fail (query == NULL || GDATA_IS_QUERY (query));
g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
g_return_if_fail (callback != NULL);
- gdata_service_query_async (GDATA_SERVICE (self), get_youtube_authorization_domain (), standard_feed_type_to_feed_uri (feed_type), query,
- GDATA_TYPE_YOUTUBE_VIDEO, cancellable, progress_callback, progress_user_data, destroy_progress_user_data,
- callback, user_data);
+ query_uri = standard_feed_type_to_feed_uri (feed_type);
+ gdata_service_query_async (GDATA_SERVICE (self),
+ get_youtube_authorization_domain (),
+ query_uri, query, GDATA_TYPE_YOUTUBE_VIDEO,
+ cancellable, progress_callback,
+ progress_user_data,
+ destroy_progress_user_data, callback,
+ user_data);
+ g_free (query_uri);
}
/**
@@ -696,8 +803,14 @@ gdata_youtube_service_query_videos (GDataYouTubeService *self, GDataQuery *query
g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
- return gdata_service_query (GDATA_SERVICE (self), get_youtube_authorization_domain (), "https://gdata.youtube.com/feeds/api/videos", query,
- GDATA_TYPE_YOUTUBE_VIDEO, cancellable, progress_callback, progress_user_data, error);
+ return gdata_service_query (GDATA_SERVICE (self),
+ get_youtube_authorization_domain (),
+ "https://www.googleapis.com/youtube/v3/search"
+ "?part=snippet"
+ "&type=video",
+ query, GDATA_TYPE_YOUTUBE_VIDEO,
+ cancellable, progress_callback,
+ progress_user_data, error);
}
/**
@@ -733,9 +846,15 @@ gdata_youtube_service_query_videos_async (GDataYouTubeService *self, GDataQuery
g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
g_return_if_fail (callback != NULL);
- gdata_service_query_async (GDATA_SERVICE (self), get_youtube_authorization_domain (), "https://gdata.youtube.com/feeds/api/videos", query,
- GDATA_TYPE_YOUTUBE_VIDEO, cancellable, progress_callback, progress_user_data, destroy_progress_user_data,
- callback, user_data);
+ gdata_service_query_async (GDATA_SERVICE (self),
+ get_youtube_authorization_domain (),
+ "https://www.googleapis.com/youtube/v3/search"
+ "?part=snippet"
+ "&type=video",
+ query, GDATA_TYPE_YOUTUBE_VIDEO, cancellable,
+ progress_callback, progress_user_data,
+ destroy_progress_user_data, callback,
+ user_data);
}
/**
@@ -750,8 +869,7 @@ gdata_youtube_service_query_videos_async (GDataYouTubeService *self, GDataQuery
*
* Queries the service for videos related to @video. The algorithm determining which videos are related is on the server side.
*
- * If @video does not have a link with rel value <literal>http://gdata.youtube.com/schemas/2007#video.related</literal>, a
- * %GDATA_SERVICE_ERROR_PROTOCOL_ERROR error will be thrown. Parameters and other errors are as for gdata_service_query().
+ * Parameters and other errors are as for gdata_service_query().
*
* Return value: (transfer full): a #GDataFeed of query results; unref with g_object_unref()
**/
@@ -760,7 +878,6 @@ gdata_youtube_service_query_related (GDataYouTubeService *self, GDataYouTubeVide
GCancellable *cancellable, GDataQueryProgressCallback progress_callback, gpointer progress_user_data,
GError **error)
{
- GDataLink *related_link;
GDataFeed *feed;
gchar *uri;
@@ -770,19 +887,17 @@ gdata_youtube_service_query_related (GDataYouTubeService *self, GDataYouTubeVide
g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
- /* See if the video already has a rel="http://gdata.youtube.com/schemas/2007#video.related" link */
- related_link = gdata_entry_look_up_link (GDATA_ENTRY (video), "http://gdata.youtube.com/schemas/2007#video.related");
- if (related_link == NULL) {
- /* Erroring out is probably the safest thing to do */
- g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR,
- _("The video did not have a related videos <link>."));
- return NULL;
- }
-
/* Execute the query */
- uri = _gdata_service_fix_uri_scheme (gdata_link_get_uri (related_link));
- feed = gdata_service_query (GDATA_SERVICE (self), get_youtube_authorization_domain (), uri, query,
- GDATA_TYPE_YOUTUBE_VIDEO, cancellable, progress_callback, progress_user_data, error);
+ uri = g_strdup_printf ("https://www.googleapis.com/youtube/v3/search"
+ "?part=snippet"
+ "&type=video"
+ "&relatedToVideoId=%s",
+ gdata_entry_get_id (GDATA_ENTRY (video)));
+ feed = gdata_service_query (GDATA_SERVICE (self),
+ get_youtube_authorization_domain (), uri,
+ query, GDATA_TYPE_YOUTUBE_VIDEO,
+ cancellable, progress_callback,
+ progress_user_data, error);
g_free (uri);
return feed;
@@ -817,7 +932,6 @@ gdata_youtube_service_query_related_async (GDataYouTubeService *self, GDataYouTu
GDestroyNotify destroy_progress_user_data,
GAsyncReadyCallback callback, gpointer user_data)
{
- GDataLink *related_link;
gchar *uri;
g_return_if_fail (GDATA_IS_YOUTUBE_SERVICE (self));
@@ -826,23 +940,17 @@ gdata_youtube_service_query_related_async (GDataYouTubeService *self, GDataYouTu
g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
g_return_if_fail (callback != NULL);
- /* See if the video already has a rel="http://gdata.youtube.com/schemas/2007#video.related" link */
- related_link = gdata_entry_look_up_link (GDATA_ENTRY (video), "http://gdata.youtube.com/schemas/2007#video.related");
- if (related_link == NULL) {
- /* Erroring out is probably the safest thing to do */
- GSimpleAsyncResult *result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, gdata_service_query_async);
- g_simple_async_result_set_error (result, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR, "%s",
- _("The video did not have a related videos <link>."));
- g_simple_async_result_complete_in_idle (result);
- g_object_unref (result);
-
- return;
- }
-
- uri = _gdata_service_fix_uri_scheme (gdata_link_get_uri (related_link));
- gdata_service_query_async (GDATA_SERVICE (self), get_youtube_authorization_domain (), uri, query,
- GDATA_TYPE_YOUTUBE_VIDEO, cancellable, progress_callback, progress_user_data,
- destroy_progress_user_data, callback, user_data);
+ uri = g_strdup_printf ("https://www.googleapis.com/youtube/v3/search"
+ "?part=snippet"
+ "&type=video"
+ "&relatedToVideoId=%s",
+ gdata_entry_get_id (GDATA_ENTRY (video)));
+ gdata_service_query_async (GDATA_SERVICE (self),
+ get_youtube_authorization_domain (), uri,
+ query, GDATA_TYPE_YOUTUBE_VIDEO, cancellable,
+ progress_callback, progress_user_data,
+ destroy_progress_user_data, callback,
+ user_data);
g_free (uri);
}
@@ -878,6 +986,8 @@ GDataUploadStream *
gdata_youtube_service_upload_video (GDataYouTubeService *self, GDataYouTubeVideo *video, const gchar *slug, const gchar *content_type,
GCancellable *cancellable, GError **error)
{
+ GOutputStream *stream = NULL; /* owned */
+
g_return_val_if_fail (GDATA_IS_YOUTUBE_SERVICE (self), NULL);
g_return_val_if_fail (GDATA_IS_YOUTUBE_VIDEO (video), NULL);
g_return_val_if_fail (slug != NULL && *slug != '\0', NULL);
@@ -891,6 +1001,8 @@ gdata_youtube_service_upload_video (GDataYouTubeService *self, GDataYouTubeVideo
return NULL;
}
+ /* FIXME: Could be more cunning about domains here; see scope on
+ * https://developers.google.com/youtube/v3/guides/authentication */
if (gdata_authorizer_is_authorized_for_domain (gdata_service_get_authorizer (GDATA_SERVICE (self)),
get_youtube_authorization_domain ()) == FALSE) {
g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED,
@@ -898,10 +1010,23 @@ gdata_youtube_service_upload_video (GDataYouTubeService *self, GDataYouTubeVideo
return NULL;
}
- /* Streaming upload support using GDataUploadStream; automatically handles the XML and multipart stuff for us */
- return GDATA_UPLOAD_STREAM (gdata_upload_stream_new (GDATA_SERVICE (self), get_youtube_authorization_domain (), SOUP_METHOD_POST,
- "https://uploads.gdata.youtube.com/feeds/api/users/default/uploads",
- GDATA_ENTRY (video), slug, content_type, cancellable));
+ /* FIXME: Add support for resumable uploads. That means a new
+ * gdata_youtube_service_upload_video_resumable() method a la
+ * Documents. */
+ stream = gdata_upload_stream_new (GDATA_SERVICE (self),
+ get_youtube_authorization_domain (),
+ SOUP_METHOD_POST,
+ "https://www.googleapis.com/upload/youtube/v3/videos"
+ "?part=snippet,status,"
+ "recordingDetails,"
+ "contentDetails,id,"
+ "statistics,"
+ "processingDetails",
+ GDATA_ENTRY (video), slug,
+ content_type,
+ cancellable);
+
+ return GDATA_UPLOAD_STREAM (stream);
}
/**
@@ -936,7 +1061,10 @@ gdata_youtube_service_finish_video_upload (GDataYouTubeService *self, GDataUploa
return NULL;
/* Parse the response to produce a GDataYouTubeVideo */
- return GDATA_YOUTUBE_VIDEO (gdata_parsable_new_from_xml (GDATA_TYPE_YOUTUBE_VIDEO, response_body, (gint) response_length, error));
+ return GDATA_YOUTUBE_VIDEO (gdata_parsable_new_from_json (GDATA_TYPE_YOUTUBE_VIDEO,
+ response_body,
+ (gint) response_length,
+ error));
}
/**
@@ -972,6 +1100,8 @@ gdata_youtube_service_get_developer_key (GDataYouTubeService *self)
GDataAPPCategories *
gdata_youtube_service_get_categories (GDataYouTubeService *self, GCancellable *cancellable, GError **error)
{
+ const gchar *locale;
+ gchar *uri;
SoupMessage *message;
GDataAPPCategories *categories;
@@ -979,16 +1109,31 @@ gdata_youtube_service_get_categories (GDataYouTubeService *self, GCancellable *c
g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
- /* Download the category list. Note that this is (service) locale-dependent. */
- message = _gdata_service_query (GDATA_SERVICE (self), get_youtube_authorization_domain (),
- "https://gdata.youtube.com/schemas/2007/categories.cat", NULL, cancellable, error);
+ /* Download the category list. Note that this is (service)
+ * locale-dependent, and a locale must always be specified. */
+ locale = gdata_service_get_locale (GDATA_SERVICE (self));
+ if (locale == NULL) {
+ locale = "US";
+ }
+
+ uri = g_strdup_printf ("https://www.googleapis.com/youtube/v3/videoCategories"
+ "?part=snippet"
+ "&regionCode=%s",
+ locale);
+ message = _gdata_service_query (GDATA_SERVICE (self),
+ get_youtube_authorization_domain (),
+ uri, NULL, cancellable, error);
+ g_free (uri);
+
if (message == NULL)
return NULL;
g_assert (message->response_body->data != NULL);
- categories = GDATA_APP_CATEGORIES (_gdata_parsable_new_from_xml (GDATA_TYPE_APP_CATEGORIES, message->response_body->data,
- message->response_body->length,
- GSIZE_TO_POINTER (GDATA_TYPE_YOUTUBE_CATEGORY), error));
+ categories = GDATA_APP_CATEGORIES (_gdata_parsable_new_from_json (GDATA_TYPE_APP_CATEGORIES,
+ message->response_body->data,
+ message->response_body->length,
+ GSIZE_TO_POINTER (GDATA_TYPE_YOUTUBE_CATEGORY),
+ error));
g_object_unref (message);
return categories;
diff --git a/gdata/services/youtube/gdata-youtube-service.h b/gdata/services/youtube/gdata-youtube-service.h
index 999fa4c7..23e6ef5e 100644
--- a/gdata/services/youtube/gdata-youtube-service.h
+++ b/gdata/services/youtube/gdata-youtube-service.h
@@ -1,7 +1,7 @@
/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/*
* GData Client
- * Copyright (C) Philip Withnall 2008-2009 <philip@tecnocode.co.uk>
+ * Copyright (C) Philip Withnall 2008-2009, 2015 <philip@tecnocode.co.uk>
*
* GData Client is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -33,20 +33,51 @@ G_BEGIN_DECLS
/**
* GDataYouTubeStandardFeedType:
- * @GDATA_YOUTUBE_TOP_RATED_FEED: This feed contains the most highly rated YouTube videos.
- * @GDATA_YOUTUBE_TOP_FAVORITES_FEED: This feed contains videos most frequently flagged as favorite videos.
- * @GDATA_YOUTUBE_MOST_VIEWED_FEED: This feed contains the most frequently watched YouTube videos.
- * @GDATA_YOUTUBE_MOST_POPULAR_FEED: This feed contains the most popular YouTube videos, selected using an algorithm that combines many
- * different signals to determine overall popularity.
- * @GDATA_YOUTUBE_MOST_RECENT_FEED: This feed contains the videos most recently submitted to YouTube.
- * @GDATA_YOUTUBE_MOST_DISCUSSED_FEED: This feed contains the YouTube videos that have received the most comments.
- * @GDATA_YOUTUBE_MOST_LINKED_FEED: This feed contains the YouTube videos that receive the most links from other websites.
- * @GDATA_YOUTUBE_MOST_RESPONDED_FEED: This feed contains YouTube videos that receive the most video responses.
- * @GDATA_YOUTUBE_RECENTLY_FEATURED_FEED: This feed contains videos recently featured on the YouTube home page or featured videos tab.
- * @GDATA_YOUTUBE_WATCH_ON_MOBILE_FEED: This feed contains videos suitable for playback on mobile devices.
+ * @GDATA_YOUTUBE_TOP_RATED_FEED: This feed contains the most highly rated
+ * YouTube videos. Deprecated: UNRELEASED: Google no longer supports this feed
+ * type, and it will return results equivalent to
+ * %GDATA_YOUTUBE_MOST_POPULAR_FEED.
+ * @GDATA_YOUTUBE_TOP_FAVORITES_FEED: This feed contains videos most frequently
+ * flagged as favorite videos. Deprecated: UNRELEASED: Google no longer
+ * supports this feed type, and it will return results equivalent to
+ * %GDATA_YOUTUBE_MOST_POPULAR_FEED.
+ * @GDATA_YOUTUBE_MOST_VIEWED_FEED: This feed contains the most frequently
+ * watched YouTube videos. Deprecated: UNRELEASED: Google no longer supports
+ * this feed type, and it will return results equivalent to
+ * %GDATA_YOUTUBE_MOST_POPULAR_FEED.
+ * @GDATA_YOUTUBE_MOST_POPULAR_FEED: This feed contains the most popular YouTube
+ * videos, selected using an algorithm that combines many different signals to
+ * determine overall popularity. As of version UNRELEASED, this is the only
+ * supported feed type.
+ * @GDATA_YOUTUBE_MOST_RECENT_FEED: This feed contains the videos most recently
+ * submitted to YouTube. Deprecated: UNRELEASED: Google no longer supports
+ * this feed type, and it will return results equivalent to
+ * %GDATA_YOUTUBE_MOST_POPULAR_FEED.
+ * @GDATA_YOUTUBE_MOST_DISCUSSED_FEED: This feed contains the YouTube videos
+ * that have received the most comments. Deprecated: UNRELEASED: Google no
+ * longer supports this feed type, and it will return results equivalent to
+ * %GDATA_YOUTUBE_MOST_POPULAR_FEED.
+ * @GDATA_YOUTUBE_MOST_LINKED_FEED: This feed contains the YouTube videos that
+ * receive the most links from other websites. Deprecated: UNRELEASED: Google
+ * no longer supports this feed type, and it will return results equivalent to
+ * %GDATA_YOUTUBE_MOST_POPULAR_FEED.
+ * @GDATA_YOUTUBE_MOST_RESPONDED_FEED: This feed contains YouTube videos that
+ * receive the most video responses. Deprecated: UNRELEASED: Google no longer
+ * supports this feed type, and it will return results equivalent to
+ * %GDATA_YOUTUBE_MOST_POPULAR_FEED.
+ * @GDATA_YOUTUBE_RECENTLY_FEATURED_FEED: This feed contains videos recently
+ * featured on the YouTube home page or featured videos tab. Deprecated:
+ * UNRELEASED: Google no longer supports this feed type, and it will return
+ * results equivalent to %GDATA_YOUTUBE_MOST_POPULAR_FEED.
+ * @GDATA_YOUTUBE_WATCH_ON_MOBILE_FEED: This feed contains videos suitable for
+ * playback on mobile devices. Deprecated: UNRELEASED: Google no longer
+ * supports this feed type, and it will return results equivalent to
+ * %GDATA_YOUTUBE_MOST_POPULAR_FEED.
*
- * Standard feed types for standard feed queries with gdata_youtube_service_query_standard_feed(). For more information, see
- * the <ulink type="http" url="http://code.google.com/apis/youtube/2.0/developers_guide_protocol.html#Standard_feeds">online documentation</ulink>.
+ * Standard feed types for standard feed queries with
+ * gdata_youtube_service_query_standard_feed(). For more information, see the
+ * <ulink type="http" url="https://developers.google.com/youtube/2.0/developers_guide_protocol_video_feeds#Standard_feeds">online
+ * documentation</ulink>.
**/
typedef enum {
GDATA_YOUTUBE_TOP_RATED_FEED,
diff --git a/gdata/services/youtube/gdata-youtube-state.c b/gdata/services/youtube/gdata-youtube-state.c
index e2da9bb5..4b0b3542 100644
--- a/gdata/services/youtube/gdata-youtube-state.c
+++ b/gdata/services/youtube/gdata-youtube-state.c
@@ -1,7 +1,7 @@
/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/*
* GData Client
- * Copyright (C) Philip Withnall 2009–2010 <philip@tecnocode.co.uk>
+ * Copyright (C) Philip Withnall 2009–2010, 2015 <philip@tecnocode.co.uk>
*
* GData Client is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -30,17 +30,12 @@
**/
#include <glib.h>
-#include <libxml/parser.h>
#include "gdata-youtube-state.h"
#include "gdata-parsable.h"
-#include "gdata-parser.h"
static void gdata_youtube_state_finalize (GObject *object);
static void gdata_youtube_state_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 gboolean parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error);
-static void get_namespaces (GDataParsable *parsable, GHashTable *namespaces);
struct _GDataYouTubeStatePrivate {
gchar *name;
@@ -62,19 +57,12 @@ static void
gdata_youtube_state_class_init (GDataYouTubeStateClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
- GDataParsableClass *parsable_class = GDATA_PARSABLE_CLASS (klass);
g_type_class_add_private (klass, sizeof (GDataYouTubeStatePrivate));
gobject_class->get_property = gdata_youtube_state_get_property;
gobject_class->finalize = gdata_youtube_state_finalize;
- parsable_class->pre_parse_xml = pre_parse_xml;
- parsable_class->parse_xml = parse_xml;
- parsable_class->get_namespaces = get_namespaces;
- parsable_class->element_name = "state";
- parsable_class->element_namespace = "yt";
-
/**
* GDataYouTubeState:name:
*
@@ -186,42 +174,6 @@ gdata_youtube_state_get_property (GObject *object, guint property_id, GValue *va
}
}
-static gboolean
-pre_parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *root_node, gpointer user_data, GError **error)
-{
- GDataYouTubeStatePrivate *priv = GDATA_YOUTUBE_STATE (parsable)->priv;
- xmlChar *name;
-
- name = xmlGetProp (root_node, (xmlChar*) "name");
- if (name == NULL || *name == '\0') {
- g_free (name);
- return gdata_parser_error_required_property_missing (root_node, "name", error);
- }
-
- priv->name = (gchar*) name;
- priv->reason_code = (gchar*) xmlGetProp (root_node, (xmlChar*) "reasonCode");
- priv->help_uri = (gchar*) xmlGetProp (root_node, (xmlChar*) "helpUrl");
- priv->message = (gchar*) xmlNodeListGetString (doc, root_node->children, TRUE);
-
- return TRUE;
-}
-
-static gboolean
-parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error)
-{
- /* Textual content's handled in pre_parse_xml */
- if (node->type != XML_ELEMENT_NODE)
- return TRUE;
-
- return GDATA_PARSABLE_CLASS (gdata_youtube_state_parent_class)->parse_xml (parsable, doc, node, user_data, error);
-}
-
-static void
-get_namespaces (GDataParsable *parsable, GHashTable *namespaces)
-{
- g_hash_table_insert (namespaces, (gchar*) "yt", (gchar*) "http://gdata.youtube.com/schemas/2007");
-}
-
/**
* gdata_youtube_state_get_name:
* @self: a #GDataYouTubeState
diff --git a/gdata/services/youtube/gdata-youtube-video.c b/gdata/services/youtube/gdata-youtube-video.c
index b491a6dc..a679395b 100644
--- a/gdata/services/youtube/gdata-youtube-video.c
+++ b/gdata/services/youtube/gdata-youtube-video.c
@@ -25,11 +25,14 @@
*
* #GDataYouTubeVideo is a subclass of #GDataEntry to represent a single video on YouTube, either when uploading or querying.
*
- * #GDataYouTubeVideo implements #GDataCommentable, allowing comments on videos to be queried using gdata_commentable_query_comments(), and new
- * comments to be added to videos using gdata_commentable_insert_comment(). Note that deletion of comments on any #GDataYouTubeVideo is not permitted;
- * gdata_commentable_delete_comment() will always fail if called on a #GDataYouTubeVideo.
- *
- * For more details of YouTube's GData API, see the <ulink type="http" url="http://code.google.com/apis/youtube/2.0/reference.html">
+ * #GDataYouTubeVideo implements #GDataCommentable, allowing comments on videos
+ * to be queried and added. However, the initial version of the v3 YouTube API
+ * does not currently support comments, so all #GDataCommentable calls will
+ * fail. It is hoped that the YouTube API will regain support for comments in
+ * future.
+ *
+ * For more details of YouTube’s GData API, see the
+ * <ulink type="http" url="https://developers.google.com/youtube/v3/docs/">
* online documentation</ulink>.
*
* <example>
@@ -62,7 +65,7 @@
#include <config.h>
#include <glib.h>
#include <glib/gi18n-lib.h>
-#include <libxml/parser.h>
+#include <json-glib/json-glib.h>
#include <string.h>
#include "gdata-youtube-video.h"
@@ -71,25 +74,19 @@
#include "gdata-parser.h"
#include "media/gdata-media-category.h"
#include "media/gdata-media-thumbnail.h"
-#include "gdata-youtube-group.h"
#include "gdata-types.h"
-#include "gdata-youtube-control.h"
#include "gdata-youtube-enums.h"
-#include "georss/gdata-georss-where.h"
-#include "gd/gdata-gd-feed-link.h"
#include "gdata-commentable.h"
#include "gdata-youtube-comment.h"
#include "gdata-youtube-service.h"
-static GObject *gdata_youtube_video_constructor (GType type, guint n_construct_params, GObjectConstructParam *construct_params);
static void gdata_youtube_video_dispose (GObject *object);
static void gdata_youtube_video_finalize (GObject *object);
static void gdata_youtube_video_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
static void gdata_youtube_video_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec);
-static gboolean parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error);
-static gboolean post_parse_xml (GDataParsable *parsable, gpointer user_data, GError **error);
-static void get_xml (GDataParsable *parsable, GString *xml_string);
-static void get_namespaces (GDataParsable *parsable, GHashTable *namespaces);
+static gboolean parse_json (GDataParsable *parsable, JsonReader *reader, gpointer user_data, GError **error);
+static void get_json (GDataParsable *parsable, JsonBuilder *builder);
+static const gchar *get_content_type (void);
static gchar *get_entry_uri (const gchar *id) G_GNUC_WARN_UNUSED_RESULT;
static void gdata_youtube_video_commentable_init (GDataCommentableInterface *iface);
static GDataAuthorizationDomain *get_authorization_domain (GDataCommentable *self) G_GNUC_CONST;
@@ -111,18 +108,31 @@ struct _GDataYouTubeVideoPrivate {
gdouble average;
} rating;
- /* media:group */
- GDataMediaGroup *media_group; /* is actually a GDataYouTubeGroup */
+ gchar **keywords;
+ gchar *player_uri;
+ gchar **region_restriction_allowed;
+ gchar **region_restriction_blocked;
+ GHashTable *content_ratings; /* owned string → owned string */
+ GList *thumbnails; /* GDataMediaThumbnail */
+ GDataMediaCategory *category;
+ guint duration;
+ gboolean is_private;
- /* georss:where */
- GDataGeoRSSWhere *georss_where;
+ /* Location. */
+ gdouble latitude;
+ gdouble longitude;
/* Other properties */
- GDataYouTubeControl *youtube_control;
+ gchar *rejection_reason;
+ gchar *processing_status;
+ gchar *upload_status;
+ gchar *failure_reason;
+ GDataYouTubeState *upload_state; /* owned */
+
gint64 recorded;
- /* Comments */
- GDataGDFeedLink *comments_feed_link;
+ /* State for parse_json(). */
+ gboolean parsing_in_video_list_response;
};
enum {
@@ -162,19 +172,17 @@ gdata_youtube_video_class_init (GDataYouTubeVideoClass *klass)
g_type_class_add_private (klass, sizeof (GDataYouTubeVideoPrivate));
- gobject_class->constructor = gdata_youtube_video_constructor;
gobject_class->get_property = gdata_youtube_video_get_property;
gobject_class->set_property = gdata_youtube_video_set_property;
gobject_class->dispose = gdata_youtube_video_dispose;
gobject_class->finalize = gdata_youtube_video_finalize;
- parsable_class->parse_xml = parse_xml;
- parsable_class->post_parse_xml = post_parse_xml;
- parsable_class->get_xml = get_xml;
- parsable_class->get_namespaces = get_namespaces;
+ parsable_class->parse_json = parse_json;
+ parsable_class->get_json = get_json;
+ parsable_class->get_content_type = get_content_type;
entry_class->get_entry_uri = get_entry_uri;
- entry_class->kind_term = "http://gdata.youtube.com/schemas/2007#video";
+ entry_class->kind_term = "youtube#video"; /* also: youtube#searchResult */
/**
* GDataYouTubeVideo:view-count:
@@ -280,7 +288,7 @@ gdata_youtube_video_class_init (GDataYouTubeVideoClass *klass)
* A %NULL-terminated array of words associated with the video.
*
* For more information, see the <ulink type="http"
- * url="http://code.google.com/apis/youtube/2.0/reference.html#youtube_data_api_tag_media:keywords">online documentation</ulink>.
+ * url="https://developers.google.com/youtube/v3/docs/videos#snippet.tags[]">online documentation</ulink>.
**/
g_object_class_install_property (gobject_class, PROP_KEYWORDS,
g_param_spec_boxed ("keywords",
@@ -305,7 +313,7 @@ gdata_youtube_video_class_init (GDataYouTubeVideoClass *klass)
* Specifies a genre or developer tag that describes the video.
*
* For more information, see the <ulink type="http"
- * url="http://code.google.com/apis/youtube/2.0/reference.html#youtube_data_api_tag_media:category">online documentation</ulink>.
+ * url="https://developers.google.com/youtube/v3/docs/videos#snippet.categoryId">online documentation</ulink>.
**/
g_object_class_install_property (gobject_class, PROP_CATEGORY,
g_param_spec_object ("category",
@@ -313,19 +321,24 @@ gdata_youtube_video_class_init (GDataYouTubeVideoClass *klass)
GDATA_TYPE_MEDIA_CATEGORY,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+
/**
* GDataYouTubeVideo:credit:
*
* Identifies the owner of the video.
*
- * For more information, see the <ulink type="http"
- * url="http://code.google.com/apis/youtube/2.0/reference.html#youtube_data_api_tag_media:credit">online documentation</ulink>.
+ * Deprecated: UNRELEASED: This is no longer supported by Google, and
+ * will always be %NULL. There is no replacement.
**/
g_object_class_install_property (gobject_class, PROP_CREDIT,
g_param_spec_object ("credit",
"Credit", "Identifies the owner of the video.",
GDATA_TYPE_YOUTUBE_CREDIT,
- G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS |
+ G_PARAM_DEPRECATED));
+
+ G_GNUC_END_IGNORE_DEPRECATIONS
/**
* GDataYouTubeVideo:description:
@@ -405,14 +418,15 @@ gdata_youtube_video_class_init (GDataYouTubeVideoClass *klass)
*
* Indicates whether the video is in draft, or unpublished, status.
*
- * For more information, see the <ulink type="http"
- * url="http://code.google.com/apis/youtube/2.0/reference.html#youtube_data_api_tag_app:draft">online documentation</ulink>.
+ * Deprecated: UNRELEASED: This is now equal to
+ * #GDataYouTubeVideo:is-private.
**/
g_object_class_install_property (gobject_class, PROP_IS_DRAFT,
g_param_spec_boolean ("is-draft",
"Draft?", "Indicates whether the video is in draft, or unpublished, status.",
FALSE,
- G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
+ G_PARAM_DEPRECATED));
/**
* GDataYouTubeVideo:state:
@@ -451,9 +465,6 @@ gdata_youtube_video_class_init (GDataYouTubeVideoClass *klass)
* The aspect ratio of the video. A %NULL value means the aspect ratio is unknown (it could still be a widescreen video). A value of
* %GDATA_YOUTUBE_ASPECT_RATIO_WIDESCREEN means the video is definitely widescreen.
*
- * For more information see the <ulink type="http"
- * url="http://code.google.com/apis/youtube/2.0/reference.html#youtube_data_api_tag_yt:aspectratio">online documentation</ulink>.
- *
* Since: 0.4.0
**/
g_object_class_install_property (gobject_class, PROP_ASPECT_RATIO,
@@ -515,25 +526,6 @@ gdata_youtube_video_init (GDataYouTubeVideo *self)
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GDATA_TYPE_YOUTUBE_VIDEO, GDataYouTubeVideoPrivate);
self->priv->recorded = -1;
self->priv->access_controls = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) g_free, NULL);
- self->priv->georss_where = g_object_new (GDATA_TYPE_GEORSS_WHERE, NULL);
-}
-
-static GObject *
-gdata_youtube_video_constructor (GType type, guint n_construct_params, GObjectConstructParam *construct_params)
-{
- GObject *object;
-
- /* Chain up to the parent class */
- object = G_OBJECT_CLASS (gdata_youtube_video_parent_class)->constructor (type, n_construct_params, construct_params);
-
- /* We can't create these in init, or they would collide with the group and control created when parsing the XML */
- if (_gdata_parsable_is_constructed_from_xml (GDATA_PARSABLE (object)) == FALSE) {
- GDataYouTubeVideoPrivate *priv = GDATA_YOUTUBE_VIDEO (object)->priv;
- priv->media_group = g_object_new (GDATA_TYPE_YOUTUBE_GROUP, NULL);
- priv->youtube_control = g_object_new (GDATA_TYPE_YOUTUBE_CONTROL, NULL);
- }
-
- return object;
}
static void
@@ -541,21 +533,7 @@ gdata_youtube_video_dispose (GObject *object)
{
GDataYouTubeVideoPrivate *priv = GDATA_YOUTUBE_VIDEO (object)->priv;
- if (priv->media_group != NULL)
- g_object_unref (priv->media_group);
- priv->media_group = NULL;
-
- if (priv->georss_where != NULL)
- g_object_unref (priv->georss_where);
- priv->georss_where = NULL;
-
- if (priv->youtube_control != NULL)
- g_object_unref (priv->youtube_control);
- priv->youtube_control = NULL;
-
- if (priv->comments_feed_link != NULL)
- g_object_unref (priv->comments_feed_link);
- priv->comments_feed_link = NULL;
+ g_clear_object (&priv->upload_state);
/* Chain up to the parent class */
G_OBJECT_CLASS (gdata_youtube_video_parent_class)->dispose (object);
@@ -568,6 +546,17 @@ gdata_youtube_video_finalize (GObject *object)
g_free (priv->location);
g_hash_table_destroy (priv->access_controls);
+ g_strfreev (priv->keywords);
+ g_free (priv->player_uri);
+ g_strfreev (priv->region_restriction_allowed);
+ g_strfreev (priv->region_restriction_blocked);
+ g_clear_pointer (&priv->content_ratings, (GDestroyNotify) g_hash_table_unref);
+ g_list_free_full (priv->thumbnails, (GDestroyNotify) g_object_unref);
+ g_clear_object (&priv->category);
+ g_free (priv->rejection_reason);
+ g_free (priv->processing_status);
+ g_free (priv->upload_status);
+ g_free (priv->failure_reason);
/* Chain up to the parent class */
G_OBJECT_CLASS (gdata_youtube_video_parent_class)->finalize (object);
@@ -601,49 +590,49 @@ gdata_youtube_video_get_property (GObject *object, guint property_id, GValue *va
g_value_set_double (value, priv->rating.average);
break;
case PROP_KEYWORDS:
- g_value_set_boxed (value, gdata_media_group_get_keywords (priv->media_group));
+ g_value_set_boxed (value, priv->keywords);
break;
case PROP_PLAYER_URI:
- g_value_set_string (value, gdata_media_group_get_player_uri (priv->media_group));
+ g_value_set_string (value, gdata_youtube_video_get_player_uri (GDATA_YOUTUBE_VIDEO (object)));
break;
case PROP_CATEGORY:
- g_value_set_object (value, gdata_media_group_get_category (priv->media_group));
+ g_value_set_object (value, priv->category);
break;
case PROP_CREDIT:
- g_value_set_object (value, gdata_media_group_get_credit (priv->media_group));
+ g_value_set_object (value, NULL);
break;
case PROP_DESCRIPTION:
- g_value_set_string (value, gdata_media_group_get_description (priv->media_group));
+ g_value_set_string (value, gdata_entry_get_summary (GDATA_ENTRY (object)));
break;
case PROP_DURATION:
- g_value_set_uint (value, gdata_youtube_group_get_duration (GDATA_YOUTUBE_GROUP (priv->media_group)));
+ g_value_set_uint (value, priv->duration);
break;
case PROP_IS_PRIVATE:
- g_value_set_boolean (value, gdata_youtube_group_is_private (GDATA_YOUTUBE_GROUP (priv->media_group)));
+ g_value_set_boolean (value, priv->is_private);
break;
case PROP_UPLOADED:
- g_value_set_int64 (value, gdata_youtube_group_get_uploaded (GDATA_YOUTUBE_GROUP (priv->media_group)));
+ g_value_set_int64 (value, gdata_entry_get_published (GDATA_ENTRY (object)));
break;
case PROP_VIDEO_ID:
- g_value_set_string (value, gdata_youtube_group_get_video_id (GDATA_YOUTUBE_GROUP (priv->media_group)));
+ g_value_set_string (value, gdata_entry_get_id (GDATA_ENTRY (object)));
break;
case PROP_IS_DRAFT:
- g_value_set_boolean (value, gdata_youtube_control_is_draft (priv->youtube_control));
+ g_value_set_boolean (value, gdata_youtube_video_is_private (GDATA_YOUTUBE_VIDEO (object)));
break;
case PROP_STATE:
- g_value_set_object (value, gdata_youtube_control_get_state (priv->youtube_control));
+ g_value_set_object (value, gdata_youtube_video_get_state (GDATA_YOUTUBE_VIDEO (object)));
break;
case PROP_RECORDED:
g_value_set_int64 (value, priv->recorded);
break;
case PROP_ASPECT_RATIO:
- g_value_set_string (value, gdata_youtube_group_get_aspect_ratio (GDATA_YOUTUBE_GROUP (priv->media_group)));
+ g_value_set_string (value, gdata_youtube_video_get_aspect_ratio (GDATA_YOUTUBE_VIDEO (object)));
break;
case PROP_LATITUDE:
- g_value_set_double (value, gdata_georss_where_get_latitude (priv->georss_where));
+ g_value_set_double (value, priv->latitude);
break;
case PROP_LONGITUDE:
- g_value_set_double (value, gdata_georss_where_get_longitude (priv->georss_where));
+ g_value_set_double (value, priv->longitude);
break;
default:
/* We don't have any other property... */
@@ -674,7 +663,7 @@ gdata_youtube_video_set_property (GObject *object, guint property_id, const GVal
gdata_youtube_video_set_is_private (self, g_value_get_boolean (value));
break;
case PROP_IS_DRAFT:
- gdata_youtube_video_set_is_draft (self, g_value_get_boolean (value));
+ gdata_youtube_video_set_is_private (self, g_value_get_boolean (value));
break;
case PROP_RECORDED:
gdata_youtube_video_set_recorded (self, g_value_get_int64 (value));
@@ -683,11 +672,13 @@ gdata_youtube_video_set_property (GObject *object, guint property_id, const GVal
gdata_youtube_video_set_aspect_ratio (self, g_value_get_string (value));
break;
case PROP_LATITUDE:
- gdata_youtube_video_set_coordinates (self, g_value_get_double (value),
- gdata_georss_where_get_longitude (self->priv->georss_where));
+ gdata_youtube_video_set_coordinates (self,
+ g_value_get_double (value),
+ self->priv->longitude);
break;
case PROP_LONGITUDE:
- gdata_youtube_video_set_coordinates (self, gdata_georss_where_get_latitude (self->priv->georss_where),
+ gdata_youtube_video_set_coordinates (self,
+ self->priv->latitude,
g_value_get_double (value));
break;
default:
@@ -697,46 +688,630 @@ gdata_youtube_video_set_property (GObject *object, guint property_id, const GVal
}
}
+/* https://developers.google.com/youtube/v3/docs/videos#contentDetails.duration
+ *
+ * Note that it can also include an ‘hours’ component, as specified in ISO 8601,
+ * but not in the Google documentation. */
+static gboolean
+duration_from_json_member (JsonReader *reader, const gchar *member_name,
+ GDataParserOptions options, guint *output,
+ gboolean *success, GError **error)
+{
+ gchar *duration_str = NULL, *i = NULL, *new_i = NULL;
+ guint64 seconds;
+ gboolean child_success = FALSE;
+
+ if (!gdata_parser_string_from_json_member (reader, member_name, options,
+ &duration_str,
+ &child_success, error)) {
+ return FALSE;
+ }
+
+ *success = child_success;
+ *output = 0;
+
+ if (!child_success) {
+ return TRUE;
+ }
+
+ /* Parse the string. Format: ‘PT(hH)?(mM)?(sS)?’, where ‘h’, ‘m’ and ‘s’
+ * are integer numbers of hours, minutes and seconds. Each element may
+ * not be present. */
+ i = duration_str;
+ if (strncmp (duration_str, "PT", 2) != 0) {
+ goto error;
+ }
+
+ i += 2; /* PT */
+
+ seconds = 0;
+
+ while (*i != '\0') {
+ guint64 element;
+ gchar designator;
+
+ element = g_ascii_strtoull (i, &new_i, 10);
+ if (new_i == i) {
+ goto error;
+ }
+
+ i = new_i;
+
+ designator = i[0];
+ if (designator == 'H') {
+ seconds += 60 * 60 * element;
+ } else if (designator == 'M') {
+ seconds += 60 * element;
+ } else if (designator == 'S') {
+ seconds += element;
+ } else {
+ goto error;
+ }
+
+ i += 1;
+ }
+
+ *output = seconds;
+ *success = child_success;
+
+ g_free (duration_str);
+
+ return TRUE;
+
+error:
+ gdata_parser_error_not_iso8601_format_json (reader, duration_str,
+ error);
+ g_free (duration_str);
+
+ return TRUE;
+}
+
+/* https://developers.google.com/youtube/v3/docs/videos#snippet.thumbnails */
+static gboolean
+thumbnails_from_json_member (JsonReader *reader, const gchar *member_name,
+ GDataParserOptions options, GList **output,
+ gboolean *success, GError **error)
+{
+ guint i, len;
+ GList *thumbnails = NULL;
+ const GError *child_error = NULL;
+
+ /* Check if there's such element */
+ if (g_strcmp0 (json_reader_get_member_name (reader),
+ member_name) != 0) {
+ return FALSE;
+ }
+
+ /* Check if the output string has already been set. The JSON parser
+ * guarantees this can't happen. */
+ g_assert (!(options & P_NO_DUPES) || *output == NULL);
+
+ len = json_reader_count_members (reader);
+ child_error = json_reader_get_error (reader);
+
+ if (child_error != NULL) {
+ *success = gdata_parser_error_from_json_error (reader,
+ child_error,
+ error);
+ goto done;
+ }
+
+ for (i = 0; i < len; i++) {
+ GDataParsable *thumbnail = NULL; /* GDataMediaThumbnail */
+
+ json_reader_read_element (reader, i);
+ thumbnail = _gdata_parsable_new_from_json_node (GDATA_TYPE_MEDIA_THUMBNAIL,
+ reader, NULL,
+ error);
+ json_reader_end_element (reader);
+
+ if (thumbnail == NULL) {
+ *success = FALSE;
+ goto done;
+ }
+
+ thumbnails = g_list_prepend (thumbnails, thumbnail);
+ }
+
+ /* Success! */
+ *output = thumbnails;
+ thumbnails = NULL;
+ *success = TRUE;
+
+done:
+ g_list_free_full (thumbnails, (GDestroyNotify) g_object_unref);
+
+ return TRUE;
+}
+
+/* https://developers.google.com/youtube/v3/docs/videos#contentDetails.regionRestriction */
+static gboolean
+restricted_countries_from_json_member (JsonReader *reader,
+ const gchar *member_name,
+ GDataParserOptions options,
+ gchar ***output_allowed,
+ gchar ***output_blocked,
+ gboolean *success, GError **error)
+{
+ guint i, len;
+ const GError *child_error = NULL;
+
+ /* Check if there's such element */
+ if (g_strcmp0 (json_reader_get_member_name (reader),
+ member_name) != 0) {
+ return FALSE;
+ }
+
+ /* Check if the output string has already been set. The JSON parser guarantees this can't happen. */
+ g_assert (!(options & P_NO_DUPES) ||
+ (*output_allowed == NULL && *output_blocked == NULL));
+
+ len = json_reader_count_members (reader);
+ child_error = json_reader_get_error (reader);
+
+ if (child_error != NULL) {
+ *success = gdata_parser_error_from_json_error (reader,
+ child_error,
+ error);
+ return TRUE;
+ }
+
+ for (i = 0; i < len; i++) {
+ json_reader_read_element (reader, i);
+
+ if (gdata_parser_strv_from_json_member (reader, "allowed",
+ P_DEFAULT,
+ output_allowed, success,
+ error) ||
+ gdata_parser_strv_from_json_member (reader, "blocked",
+ P_DEFAULT,
+ output_blocked, success,
+ error)) {
+ /* Nothing to do. */
+ }
+
+ json_reader_end_element (reader);
+ }
+
+ /* Success! */
+ *success = TRUE;
+
+ return TRUE;
+}
+
+/* https://developers.google.com/youtube/v3/docs/videos#contentDetails.contentRating */
+static gboolean
+content_rating_from_json_member (JsonReader *reader,
+ const gchar *member_name,
+ GDataParserOptions options,
+ GHashTable **output,
+ gboolean *success, GError **error)
+{
+ guint i, len;
+ const GError *child_error = NULL;
+
+ /* Check if there's such element */
+ if (g_strcmp0 (json_reader_get_member_name (reader),
+ member_name) != 0) {
+ return FALSE;
+ }
+
+ /* Check if the output string has already been set. The JSON parser
+ * guarantees this can't happen. */
+ g_assert (!(options & P_NO_DUPES) || *output == NULL);
+
+ len = json_reader_count_members (reader);
+ child_error = json_reader_get_error (reader);
+
+ if (child_error != NULL) {
+ *success = gdata_parser_error_from_json_error (reader,
+ child_error,
+ error);
+ return TRUE;
+ }
+
+ *output = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, g_free);
+
+ for (i = 0; i < len; i++) {
+ const gchar *scheme, *rating;
+
+ json_reader_read_element (reader, i);
+
+ scheme = json_reader_get_member_name (reader);
+ rating = json_reader_get_string_value (reader);
+
+ /* Ignore errors. */
+ if (rating != NULL) {
+ g_hash_table_insert (*output, g_strdup (scheme),
+ g_strdup (rating));
+ }
+
+ json_reader_end_element (reader);
+ }
+
+ /* Success! */
+ *success = TRUE;
+
+ return TRUE;
+}
+
+static guint64
+parse_uint64_from_json_string_member (JsonReader *reader,
+ const gchar *member_name,
+ GError **error)
+{
+ const gchar *str_val, *end_ptr;
+ guint64 out;
+ const GError *child_error = NULL;
+
+ /* Grab the string. */
+ json_reader_read_member (reader, member_name);
+
+ str_val = json_reader_get_string_value (reader);
+ child_error = json_reader_get_error (reader);
+
+ if (child_error != NULL) {
+ gdata_parser_error_from_json_error (reader, child_error, error);
+ out = 0;
+ goto done;
+ }
+
+ /* Try and parse it as an integer. */
+ out = g_ascii_strtoull (str_val, (gchar **) &end_ptr, 10);
+
+ if (*end_ptr != '\0') {
+ gdata_parser_error_required_json_content_missing (reader,
+ error);
+ out = 0;
+ goto done;
+ }
+
+done:
+ json_reader_end_member (reader);
+
+ return out;
+}
+
static gboolean
-parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error)
+parse_json (GDataParsable *parsable, JsonReader *reader, gpointer user_data, GError **error)
{
gboolean success;
GDataYouTubeVideo *self = GDATA_YOUTUBE_VIDEO (parsable);
+ GDataYouTubeVideoPrivate *priv = self->priv;
+
+ /* HACK: When called with gdata_service_query_single_entry(), the video
+ * list endpoint returns a 0–1 item list of results as a normal feed.
+ * (See: https://developers.google.com/youtube/v3/docs/videos/list)
+ * This differs from the v2 API, which returned just the entry.
+ *
+ * So, we need a hack to extract the single entry from the feed without
+ * being able to invoke the parsing machinery in GDataFeed, because
+ * gdata_service_query_single_entry() can’t do that. Do that by checking
+ * the kind, and then ignoring all subsequent members until we reach the
+ * items member. Recursively parse in there, then break out again.
+ * This all assumes that we see the kind member before items. */
+ if (g_strcmp0 (json_reader_get_member_name (reader), "kind") == 0 &&
+ g_strcmp0 (json_reader_get_string_value (reader),
+ "youtube#videoListResponse") == 0) {
+ priv->parsing_in_video_list_response = TRUE;
+ return TRUE;
+ } else if (g_strcmp0 (json_reader_get_member_name (reader), "items") == 0 &&
+ priv->parsing_in_video_list_response) {
+ guint i;
+
+ /* Instead of a 404 when searching for an invalid ID, the server
+ * returns an empty results list. */
+ if (json_reader_count_elements (reader) != 1) {
+ g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_NOT_FOUND,
+ /* Translators: the parameter is an error message returned by the server. */
+ _("The requested resource was not found: %s"),
+ "items");
+ return TRUE;
+ }
+
+ /* Parse the first (and only) array element. */
+ json_reader_read_element (reader, 0);
+ priv->parsing_in_video_list_response = FALSE;
+
+ /* Parse all its properties. */
+ for (i = 0; i < (guint) json_reader_count_members (reader); i++) {
+ g_return_val_if_fail (json_reader_read_element (reader, i), NULL);
+
+ if (GDATA_PARSABLE_GET_CLASS (self)->parse_json (GDATA_PARSABLE (self), reader, user_data, error) == FALSE) {
+ json_reader_end_element (reader);
+ g_object_unref (parsable);
+ break;
+ }
+
+ json_reader_end_element (reader);
+ }
+
+ priv->parsing_in_video_list_response = TRUE;
+ json_reader_end_element (reader);
+
+ return TRUE; /* handled */
+ } else if (priv->parsing_in_video_list_response) {
+ /* Ignore the member. */
+ return TRUE;
+ }
+
+ /* Actual video property parsing. */
+ if (g_strcmp0 (json_reader_get_member_name (reader), "id") == 0) {
+ const gchar *id = NULL;
+
+ /* If this is a youtube#searchResult, the id will be an object:
+ * https://developers.google.com/youtube/v3/docs/search#resource
+ * If it is a youtube#video, the id will be a string:
+ * https://developers.google.com/youtube/v3/docs/videos#resource
+ */
+
+ if (json_reader_is_value (reader)) {
+ id = json_reader_get_string_value (reader);
+ } else if (json_reader_is_object (reader)) {
+ json_reader_read_member (reader, "videoId");
+ id = json_reader_get_string_value (reader);
+ json_reader_end_member (reader);
+ }
+
+ /* Empty ID? */
+ if (id == NULL || *id == '\0') {
+ return gdata_parser_error_required_json_content_missing (reader, error);
+ }
+
+ _gdata_entry_set_id (GDATA_ENTRY (parsable), id);
+
+ return TRUE;
+ } else if (g_strcmp0 (json_reader_get_member_name (reader),
+ "snippet") == 0) {
+ guint i;
+
+ /* Check this is an object. */
+ if (!json_reader_is_object (reader)) {
+ return gdata_parser_error_required_json_content_missing (reader, error);
+ }
+
+ for (i = 0; i < (guint) json_reader_count_members (reader); i++) {
+ gint64 published_at;
+ gchar *title = NULL, *description = NULL;
+ gchar *category_id = NULL;
+
+ json_reader_read_element (reader, i);
+
+ if (gdata_parser_int64_time_from_json_member (reader, "publishedAt", P_DEFAULT, &published_at, &success, error)) {
+ if (success) {
+ _gdata_entry_set_published (GDATA_ENTRY (parsable),
+ published_at);
+ }
+ } else if (gdata_parser_string_from_json_member (reader, "title", P_DEFAULT, &title, &success, error)) {
+ if (success) {
+ gdata_entry_set_title (GDATA_ENTRY (parsable),
+ title);
+ }
+
+ g_free (title);
+ } else if (gdata_parser_string_from_json_member (reader, "description", P_DEFAULT, &description, &success, error)) {
+ if (success) {
+ gdata_entry_set_summary (GDATA_ENTRY (parsable),
+ description);
+ }
+
+ g_free (description);
+ } else if (gdata_parser_strv_from_json_member (reader, "tags", P_DEFAULT, &priv->keywords, &success, error) ||
+ thumbnails_from_json_member (reader, "thumbnails", P_DEFAULT, &priv->thumbnails, &success, error)) {
+ /* Fall through. */
+ } else if (gdata_parser_string_from_json_member (reader, "categoryId", P_DEFAULT, &category_id, &success, error)) {
+ if (success) {
+ priv->category = gdata_media_category_new (category_id,
+ NULL,
+ NULL);
+ }
+
+ g_free (category_id);
+ }
+
+ json_reader_end_element (reader);
+
+ if (!success) {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+ } else if (g_strcmp0 (json_reader_get_member_name (reader),
+ "contentDetails") == 0) {
+ guint i;
+
+ /* Check this is an object. */
+ if (!json_reader_is_object (reader)) {
+ return gdata_parser_error_required_json_content_missing (reader, error);
+ }
+
+ for (i = 0; i < (guint) json_reader_count_members (reader); i++) {
+ json_reader_read_element (reader, i);
+
+ if (duration_from_json_member (reader, "duration", P_DEFAULT, &priv->duration, &success, error) ||
+ restricted_countries_from_json_member (reader, "regionRestriction", P_DEFAULT, &priv->region_restriction_allowed, &priv->region_restriction_blocked, &success, error) ||
+ content_rating_from_json_member (reader, "contentRating", P_DEFAULT, &priv->content_ratings, &success, error)) {
+ /* Fall through. */
+ }
+
+ json_reader_end_element (reader);
+
+ if (!success) {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+ } else if (g_strcmp0 (json_reader_get_member_name (reader),
+ "status") == 0) {
+ const gchar *privacy_status;
+
+ /* Check this is an object. */
+ if (!json_reader_is_object (reader)) {
+ return gdata_parser_error_required_json_content_missing (reader, error);
+ }
+
+ json_reader_read_member (reader, "privacyStatus");
+ privacy_status = json_reader_get_string_value (reader);
+ json_reader_end_member (reader);
+
+ if (g_strcmp0 (privacy_status, "private") == 0) {
+ priv->is_private = TRUE;
+ } else if (g_strcmp0 (privacy_status, "public") == 0) {
+ priv->is_private = FALSE;
+ g_hash_table_insert (priv->access_controls,
+ (gpointer) "list",
+ GINT_TO_POINTER (GDATA_YOUTUBE_PERMISSION_ALLOWED));
+ } else if (g_strcmp0 (privacy_status, "unlisted") == 0) {
+ /* See: ‘list’ on
+ * https://developers.google.com/youtube/2.0/reference?csw=1#youtube_data_api_tag_yt:accessControl */
+ priv->is_private = FALSE;
+ g_hash_table_insert (priv->access_controls,
+ (gpointer) "list",
+ GINT_TO_POINTER (GDATA_YOUTUBE_PERMISSION_DENIED));
+ }
+
+ json_reader_read_member (reader, "embeddable");
+ g_hash_table_insert (priv->access_controls,
+ (gpointer) GDATA_YOUTUBE_ACTION_EMBED,
+ GINT_TO_POINTER (json_reader_get_boolean_value (reader) ?
+ GDATA_YOUTUBE_PERMISSION_ALLOWED :
+ GDATA_YOUTUBE_PERMISSION_DENIED));
+ json_reader_end_member (reader);
+
+ json_reader_read_member (reader, "uploadStatus");
+ priv->upload_status = g_strdup (json_reader_get_string_value (reader));
+ json_reader_end_member (reader);
+
+ json_reader_read_member (reader, "failureReason");
+ priv->rejection_reason = g_strdup (json_reader_get_string_value (reader));
+ json_reader_end_member (reader);
+
+ json_reader_read_member (reader, "rejectionReason");
+ priv->rejection_reason = g_strdup (json_reader_get_string_value (reader));
+ json_reader_end_member (reader);
+
+ return TRUE;
+ } else if (g_strcmp0 (json_reader_get_member_name (reader),
+ "statistics") == 0) {
+ gint64 likes, dislikes;
+ GError *child_error = NULL;
+
+ /* Check this is an object. */
+ if (!json_reader_is_object (reader)) {
+ return gdata_parser_error_required_json_content_missing (reader, error);
+ }
+
+ /* Views and favourites. For some unknown reason, the feed
+ * returns them as a string, even though they’re documented as
+ * being unsigned longs.
+ *
+ * Reference: https://developers.google.com/youtube/v3/docs/videos#statistics */
+ priv->view_count = parse_uint64_from_json_string_member (reader, "viewCount", &child_error);
+ if (child_error != NULL) {
+ g_propagate_error (error, child_error);
+ return FALSE;
+ }
+
+ priv->favorite_count = parse_uint64_from_json_string_member (reader, "favoriteCount", &child_error);
+ if (child_error != NULL) {
+ g_propagate_error (error, child_error);
+ return FALSE;
+ }
+
+ /* The new ratings API (total likes, total dislikes) doesn’t
+ * really match with the old API (collection of integer ratings
+ * between 1 and 5). Try and return something appropriate. */
+ likes = parse_uint64_from_json_string_member (reader, "likeCount", &child_error);
+ if (child_error != NULL) {
+ g_propagate_error (error, child_error);
+ return FALSE;
+ }
+
+ dislikes = parse_uint64_from_json_string_member (reader, "dislikeCount", &child_error);
+ if (child_error != NULL) {
+ g_propagate_error (error, child_error);
+ return FALSE;
+ }
+
+ priv->rating.min = 0;
+ priv->rating.max = 1;
+ priv->rating.count = likes + dislikes;
+ if (likes + dislikes == 0) {
+ priv->rating.average = 0.0; /* basically undefined */
+ } else {
+ priv->rating.average = (gdouble) likes / (gdouble) (likes + dislikes);
+ }
+
+ return TRUE;
+ } else if (g_strcmp0 (json_reader_get_member_name (reader),
+ "processingDetails") == 0) {
+ /* Check this is an object. */
+ if (!json_reader_is_object (reader)) {
+ return gdata_parser_error_required_json_content_missing (reader, error);
+ }
+
+ json_reader_read_member (reader, "processingStatus");
+ priv->processing_status = g_strdup (json_reader_get_string_value (reader));
+ json_reader_end_member (reader);
+
+ return TRUE;
+ } else if (g_strcmp0 (json_reader_get_member_name (reader),
+ "recordingDetails") == 0) {
+ const gchar *recording_date;
+ const GError *child_error = NULL;
+
+ /* Check this is an object. */
+ if (!json_reader_is_object (reader)) {
+ return gdata_parser_error_required_json_content_missing (reader, error);
+ }
+
+ json_reader_read_member (reader, "recordingDate");
+ recording_date = json_reader_get_string_value (reader);
+ json_reader_end_member (reader);
+
+ if (!gdata_parser_int64_from_date (recording_date,
+ &priv->recorded)) {
+ /* Error */
+ gdata_parser_error_not_iso8601_format_json (reader,
+ recording_date,
+ error);
+ return FALSE;
+ }
+
+ json_reader_read_member (reader, "locationDescription");
+ priv->location = g_strdup (json_reader_get_string_value (reader));
+ json_reader_end_member (reader);
- if (gdata_parser_is_namespace (node, "http://search.yahoo.com/mrss/") == TRUE &&
- gdata_parser_object_from_element (node, "group", P_REQUIRED | P_NO_DUPES, GDATA_TYPE_YOUTUBE_GROUP,
- &(self->priv->media_group), &success, error) == TRUE) {
- return success;
- } else if (gdata_parser_is_namespace (node, "http://www.w3.org/2007/app") == TRUE &&
- gdata_parser_object_from_element (node, "control", P_REQUIRED | P_NO_DUPES, GDATA_TYPE_YOUTUBE_CONTROL,
- &(self->priv->youtube_control), &success, error) == TRUE) {
- return success;
- } else if (gdata_parser_is_namespace (node, "http://www.georss.org/georss") == TRUE &&
- gdata_parser_object_from_element (node, "where", P_REQUIRED, GDATA_TYPE_GEORSS_WHERE,
- &(self->priv->georss_where), &success, error) == TRUE) {
- return success;
+ if (json_reader_read_member (reader, "location")) {
+ json_reader_read_member (reader, "latitude");
+ priv->latitude = json_reader_get_double_value (reader);
+ json_reader_end_member (reader);
+
+ json_reader_read_member (reader, "longitude");
+ priv->longitude = json_reader_get_double_value (reader);
+ json_reader_end_member (reader);
+ }
+
+ json_reader_end_member (reader);
+
+ child_error = json_reader_get_error (reader);
+ if (child_error != NULL) {
+ return gdata_parser_error_from_json_error (reader,
+ child_error,
+ error);
+ }
+
+ return TRUE;
+ } else {
+ return GDATA_PARSABLE_CLASS (gdata_youtube_video_parent_class)->parse_json (parsable, reader, user_data, error);
+ }
+
+#if 0
+TODO
} else if (gdata_parser_is_namespace (node, "http://gdata.youtube.com/schemas/2007") == TRUE) {
- if (gdata_parser_string_from_element (node, "location", P_NONE, &(self->priv->location), &success, error) == TRUE) {
- return success;
- } else if (xmlStrcmp (node->name, (xmlChar*) "statistics") == 0) {
- /* yt:statistics */
- xmlChar *view_count, *favorite_count;
-
- /* View count */
- view_count = xmlGetProp (node, (xmlChar*) "viewCount");
- if (view_count == NULL)
- return gdata_parser_error_required_property_missing (node, "viewCount", error);
- self->priv->view_count = g_ascii_strtoull ((gchar*) view_count, NULL, 10);
- xmlFree (view_count);
-
- /* Favourite count */
- favorite_count = xmlGetProp (node, (xmlChar*) "favoriteCount");
- self->priv->favorite_count = (favorite_count != NULL) ? g_ascii_strtoull ((gchar*) favorite_count, NULL, 10) : 0;
- xmlFree (favorite_count);
- } else if (xmlStrcmp (node->name, (xmlChar*) "noembed") == 0) {
- /* yt:noembed */
- /* Ignore this now; it's been superceded by yt:accessControl.
- * See http://apiblog.youtube.com/2010/02/extended-access-controls-available-via.html */
} else if (xmlStrcmp (node->name, (xmlChar*) "accessControl") == 0) {
/* yt:accessControl */
xmlChar *action, *permission;
@@ -766,195 +1341,169 @@ parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_da
/* Store the access control */
g_hash_table_insert (self->priv->access_controls, (gchar*) action, GINT_TO_POINTER (permission_enum));
- } else if (xmlStrcmp (node->name, (xmlChar*) "recorded") == 0) {
- /* yt:recorded */
- xmlChar *recorded;
- gint64 recorded_int64;
-
- recorded = xmlNodeListGetString (doc, node->children, TRUE);
- if (gdata_parser_int64_from_date ((gchar*) recorded, &recorded_int64) == FALSE) {
- /* Error */
- gdata_parser_error_not_iso8601_format (node, (gchar*) recorded, error);
- xmlFree (recorded);
- return FALSE;
- }
- xmlFree (recorded);
- gdata_youtube_video_set_recorded (self, recorded_int64);
- } else {
- return GDATA_PARSABLE_CLASS (gdata_youtube_video_parent_class)->parse_xml (parsable, doc, node, user_data, error);
- }
- } else if (gdata_parser_is_namespace (node, "http://schemas.google.com/g/2005") == TRUE) {
- if (xmlStrcmp (node->name, (xmlChar*) "rating") == 0) {
- /* gd:rating */
- xmlChar *min, *max, *num_raters, *average;
- guint num_raters_uint;
- gdouble average_double;
-
- min = xmlGetProp (node, (xmlChar*) "min");
- if (min == NULL)
- return gdata_parser_error_required_property_missing (node, "min", error);
-
- max = xmlGetProp (node, (xmlChar*) "max");
- if (max == NULL) {
- gdata_parser_error_required_property_missing (node, "max", error);
- xmlFree (min);
- return FALSE;
- }
-
- num_raters = xmlGetProp (node, (xmlChar*) "numRaters");
- if (num_raters == NULL)
- num_raters_uint = 0;
- else
- num_raters_uint = g_ascii_strtoull ((gchar*) num_raters, NULL, 10);
- xmlFree (num_raters);
-
- average = xmlGetProp (node, (xmlChar*) "average");
- if (average == NULL)
- average_double = 0;
- else
- average_double = g_ascii_strtod ((gchar*) average, NULL);
- xmlFree (average);
-
- self->priv->rating.min = g_ascii_strtoull ((gchar*) min, NULL, 10);
- self->priv->rating.max = g_ascii_strtoull ((gchar*) max, NULL, 10);
- self->priv->rating.count = num_raters_uint;
- self->priv->rating.average = average_double;
- } else if (xmlStrcmp (node->name, (xmlChar*) "comments") == 0) {
- /* gd:comments */
- xmlNode *child_node;
-
- /* This is actually the child of the <comments> element */
- child_node = node->children;
- if (child_node == NULL) {
- return gdata_parser_error_required_element_missing ("gd:feedLink", "gd:comments", error);
- }
-
- if (gdata_parser_object_from_element (child_node, "feedLink", P_REQUIRED | P_NO_DUPES, GDATA_TYPE_GD_FEED_LINK,
- &(self->priv->comments_feed_link), &success, error) == TRUE) {
- return success;
- }
- } else {
- return GDATA_PARSABLE_CLASS (gdata_youtube_video_parent_class)->parse_xml (parsable, doc, node, user_data, error);
- }
- } else {
- return GDATA_PARSABLE_CLASS (gdata_youtube_video_parent_class)->parse_xml (parsable, doc, node, user_data, error);
- }
+#endif
return TRUE;
}
-static gboolean
-post_parse_xml (GDataParsable *parsable, gpointer user_data, GError **error)
+static void
+get_json (GDataParsable *parsable, JsonBuilder *builder)
{
+ GDataEntry *entry = GDATA_ENTRY (parsable);
GDataYouTubeVideoPrivate *priv = GDATA_YOUTUBE_VIDEO (parsable)->priv;
+ guint i;
+ gpointer permission_ptr;
/* Chain up to the parent class */
- GDATA_PARSABLE_CLASS (gdata_youtube_video_parent_class)->post_parse_xml (parsable, user_data, error);
+ GDATA_PARSABLE_CLASS (gdata_youtube_video_parent_class)->get_json (parsable, builder);
+
+ /* Add the video-specific JSON.
+ * Reference:
+ * https://developers.google.com/youtube/v3/docs/videos/insert#request_body */
+ /* snippet object. */
+ json_builder_set_member_name (builder, "snippet");
+ json_builder_begin_object (builder);
+
+ if (gdata_entry_get_title (entry) != NULL) {
+ json_builder_set_member_name (builder, "title");
+ json_builder_add_string_value (builder,
+ gdata_entry_get_title (entry));
+ }
- /* This must always exist, so is_draft can be set on it */
- if (priv->youtube_control == NULL)
- priv->youtube_control = g_object_new (GDATA_TYPE_YOUTUBE_CONTROL, NULL);
+ if (gdata_entry_get_summary (entry) != NULL) {
+ json_builder_set_member_name (builder, "description");
+ json_builder_add_string_value (builder,
+ gdata_entry_get_summary (entry));
+ }
- return TRUE;
-}
+ if (priv->keywords != NULL) {
+ json_builder_set_member_name (builder, "tags");
+ json_builder_begin_array (builder);
-static void
-access_control_cb (const gchar *action, gpointer value, GString *xml_string)
-{
- GDataYouTubePermission permission = GPOINTER_TO_INT (value);
- const gchar *permission_string;
+ for (i = 0; priv->keywords[i] != NULL; i++) {
+ json_builder_add_string_value (builder,
+ priv->keywords[i]);
+ }
- switch (permission) {
- case GDATA_YOUTUBE_PERMISSION_ALLOWED:
- permission_string = "allowed";
- break;
- case GDATA_YOUTUBE_PERMISSION_DENIED:
- permission_string = "denied";
- break;
- case GDATA_YOUTUBE_PERMISSION_MODERATED:
- permission_string = "moderated";
- break;
- default:
- g_assert_not_reached ();
+ json_builder_end_array (builder);
}
- gdata_parser_string_append_escaped (xml_string, "<yt:accessControl action='", action, "'");
- g_string_append_printf (xml_string, " permission='%s'/>", permission_string);
-}
+ if (priv->category != NULL) {
+ json_builder_set_member_name (builder, "categoryId");
+ json_builder_add_string_value (builder,
+ gdata_media_category_get_category (priv->category));
+ }
-static void
-get_xml (GDataParsable *parsable, GString *xml_string)
-{
- GDataYouTubeVideoPrivate *priv = GDATA_YOUTUBE_VIDEO (parsable)->priv;
+ json_builder_end_object (builder);
- /* Chain up to the parent class */
- GDATA_PARSABLE_CLASS (gdata_youtube_video_parent_class)->get_xml (parsable, xml_string);
+ /* status object. */
+ json_builder_set_member_name (builder, "status");
+ json_builder_begin_object (builder);
+
+ json_builder_set_member_name (builder, "privacyStatus");
+
+ if (!priv->is_private &&
+ g_hash_table_lookup_extended (priv->access_controls,
+ "list", NULL,
+ &permission_ptr)) {
+ GDataYouTubePermission perm;
+
+ perm = GPOINTER_TO_INT (permission_ptr);
+
+ /* See the ‘list’ documentation on:
+ * https://developers.google.com/youtube/2.0/reference?csw=1#youtube_data_api_tag_yt:accessControl */
+ json_builder_add_string_value (builder,
+ (perm == GDATA_YOUTUBE_PERMISSION_ALLOWED) ? "public" : "unlisted");
+ } else {
+ json_builder_add_string_value (builder,
+ priv->is_private ? "private" : "public");
+ }
+
+ if (g_hash_table_lookup_extended (priv->access_controls,
+ GDATA_YOUTUBE_ACTION_EMBED, NULL,
+ &permission_ptr)) {
+ GDataYouTubePermission perm;
+
+ perm = GPOINTER_TO_INT (permission_ptr);
+
+ json_builder_set_member_name (builder, "embeddable");
+ json_builder_add_boolean_value (builder,
+ perm == GDATA_YOUTUBE_PERMISSION_ALLOWED);
+ }
+
+ /* FIXME: add support for:
+ * publicStatsViewable
+ * publishAt
+ * license
+ */
+
+ json_builder_end_object (builder);
+
+ /* recordingDetails object. */
+ json_builder_set_member_name (builder, "recordingDetails");
+ json_builder_begin_object (builder);
+
+ if (priv->location != NULL) {
+ json_builder_set_member_name (builder, "locationDescription");
+ json_builder_add_string_value (builder, priv->location);
+ }
+
+ if (priv->latitude != G_MAXDOUBLE && priv->longitude != G_MAXDOUBLE) {
+ json_builder_set_member_name (builder, "location");
+ json_builder_begin_object (builder);
- /* media:group */
- _gdata_parsable_get_xml (GDATA_PARSABLE (priv->media_group), xml_string, FALSE);
+ json_builder_set_member_name (builder, "latitude");
+ json_builder_add_double_value (builder, priv->latitude);
- if (priv->location != NULL)
- gdata_parser_string_append_escaped (xml_string, "<yt:location>", priv->location, "</yt:location>");
+ json_builder_set_member_name (builder, "longitude");
+ json_builder_add_double_value (builder, priv->longitude);
+
+ json_builder_end_object (builder);
+ }
if (priv->recorded != -1) {
gchar *recorded = gdata_parser_date_from_int64 (priv->recorded);
- g_string_append_printf (xml_string, "<yt:recorded>%s</yt:recorded>", recorded);
+ json_builder_set_member_name (builder, "recordingDate");
+ json_builder_add_string_value (builder, recorded);
g_free (recorded);
}
+ json_builder_end_object (builder);
+
+#if 0
/* yt:accessControl */
g_hash_table_foreach (priv->access_controls, (GHFunc) access_control_cb, xml_string);
-
- /* app:control */
- _gdata_parsable_get_xml (GDATA_PARSABLE (priv->youtube_control), xml_string, FALSE);
-
- /* georss:where */
- if (priv->georss_where != NULL && gdata_georss_where_get_latitude (priv->georss_where) != G_MAXDOUBLE &&
- gdata_georss_where_get_longitude (priv->georss_where) != G_MAXDOUBLE) {
- _gdata_parsable_get_xml (GDATA_PARSABLE (priv->georss_where), xml_string, FALSE);
- }
+#endif
}
-static void
-get_namespaces (GDataParsable *parsable, GHashTable *namespaces)
+static const gchar *
+get_content_type (void)
{
- GDataYouTubeVideoPrivate *priv = GDATA_YOUTUBE_VIDEO (parsable)->priv;
-
- /* Chain up to the parent class */
- GDATA_PARSABLE_CLASS (gdata_youtube_video_parent_class)->get_namespaces (parsable, namespaces);
-
- g_hash_table_insert (namespaces, (gchar*) "yt", (gchar*) "http://gdata.youtube.com/schemas/2007");
-
- /* Add the media:group, app:control and georss:where namespaces */
- GDATA_PARSABLE_GET_CLASS (priv->media_group)->get_namespaces (GDATA_PARSABLE (priv->media_group), namespaces);
- GDATA_PARSABLE_GET_CLASS (priv->youtube_control)->get_namespaces (GDATA_PARSABLE (priv->youtube_control), namespaces);
- GDATA_PARSABLE_GET_CLASS (priv->georss_where)->get_namespaces (GDATA_PARSABLE (priv->georss_where), namespaces);
+ return "application/json";
}
static gchar *
get_entry_uri (const gchar *id)
{
- /* The entry ID is in the format: "tag:youtube.com,2008:video:QjA5faZF1A8"; we want the bit after "video" */
- const gchar *video_id = NULL;
- gchar **parts, *uri;
- guint i;
-
- parts = g_strsplit (id, ":", -1);
+ const gchar *old_prefix = "tag:youtube.com,2008:video:";
- for (i = 0; parts[i] != NULL && parts[i + 1] != NULL; i += 2) {
- if (strcmp (parts[i], "video") == 0) {
- video_id = parts[i + 1];
- break;
- }
+ /* For compatibility with previous video ID formats, strip off the v2
+ * ID prefix. */
+ if (g_str_has_prefix (id, old_prefix)) {
+ id += strlen (old_prefix);
}
- g_assert (video_id != NULL);
-
- /* Build the URI using the video ID */
- uri = g_strconcat ("https://gdata.youtube.com/feeds/api/videos/", video_id, NULL);
- g_strfreev (parts);
-
- return uri;
+ /* Build the query URI for a single video. This is a bit of a pain,
+ * because it actually returns a list containing a single video, but
+ * there seems no other way to do it. See parsing_in_video_list_response
+ * in parse_json() for the fallout.
+ *
+ * Reference: https://developers.google.com/youtube/v3/docs/videos/list#part */
+ return g_strdup_printf ("https://www.googleapis.com/youtube/v3/videos"
+ "?part=contentDetails,id,recordingDetails,"
+ "snippet,status,statistics,"
+ "processingDetails"
+ "&id=%s", id);
}
static GDataAuthorizationDomain *
@@ -1121,6 +1670,10 @@ gdata_youtube_video_set_access_control (GDataYouTubeVideo *self, const gchar *ac
* @average: (out caller-allocates) (allow-none): return location for the average rating value, or %NULL
*
* Gets various properties of the ratings on the video.
+ *
+ * Note that this property may not be retrieved when querying for multiple
+ * videos at once, but is guaranteed to be retrieved when querying with
+ * gdata_service_query_single_entry_async().
**/
void
gdata_youtube_video_get_rating (GDataYouTubeVideo *self, guint *min, guint *max, guint *count, gdouble *average)
@@ -1148,7 +1701,7 @@ const gchar * const *
gdata_youtube_video_get_keywords (GDataYouTubeVideo *self)
{
g_return_val_if_fail (GDATA_IS_YOUTUBE_VIDEO (self), NULL);
- return gdata_media_group_get_keywords (self->priv->media_group);
+ return (const gchar * const *) self->priv->keywords;
}
/**
@@ -1159,7 +1712,7 @@ gdata_youtube_video_get_keywords (GDataYouTubeVideo *self)
* Sets the #GDataYouTubeVideo:keywords property to the new keyword list, @keywords.
*
* @keywords must not be %NULL. For more information, see the <ulink type="http"
- * url="http://code.google.com/apis/youtube/2.0/reference.html#youtube_data_api_tag_media:keywords">online documentation</ulink>.
+ * url="https://developers.google.com/youtube/v3/docs/videos#snippet.tags[]">online documentation</ulink>.
**/
void
gdata_youtube_video_set_keywords (GDataYouTubeVideo *self, const gchar * const *keywords)
@@ -1167,7 +1720,8 @@ gdata_youtube_video_set_keywords (GDataYouTubeVideo *self, const gchar * const *
g_return_if_fail (GDATA_IS_YOUTUBE_VIDEO (self));
g_return_if_fail (keywords != NULL);
- gdata_media_group_set_keywords (self->priv->media_group, keywords);
+ g_strfreev (self->priv->keywords);
+ self->priv->keywords = g_strdupv ((gchar **) keywords);
g_object_notify (G_OBJECT (self), "keywords");
}
@@ -1198,6 +1752,20 @@ gdata_youtube_video_get_player_uri (GDataYouTubeVideo *self)
return priv->player_uri;
}
+static gboolean
+strv_contains (const gchar * const *strv, const gchar *key)
+{
+ guint i;
+
+ for (i = 0; strv[i] != NULL; i++) {
+ if (g_strcmp0 (strv[i], key) == 0) {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
/**
* gdata_youtube_video_is_restricted_in_country:
* @self: a #GDataYouTubeVideo
@@ -1213,10 +1781,57 @@ gdata_youtube_video_get_player_uri (GDataYouTubeVideo *self)
gboolean
gdata_youtube_video_is_restricted_in_country (GDataYouTubeVideo *self, const gchar *country)
{
+ GDataYouTubeVideoPrivate *priv;
+
g_return_val_if_fail (GDATA_IS_YOUTUBE_VIDEO (self), FALSE);
g_return_val_if_fail (country != NULL && *country != '\0', FALSE);
- return gdata_media_group_is_restricted_in_country (self->priv->media_group, country);
+ priv = self->priv;
+
+ return (!strv_contains ((const gchar * const *) priv->region_restriction_allowed, country) &&
+ (strv_contains ((const gchar * const *) priv->region_restriction_blocked, country) ||
+ priv->region_restriction_allowed == NULL ||
+ priv->region_restriction_allowed[0] == NULL));
+}
+
+static const gchar *
+convert_mpaa_rating (const gchar *v3_rating)
+{
+ if (g_strcmp0 (v3_rating, "mpaaG") == 0) {
+ return "g";
+ } else if (g_strcmp0 (v3_rating, "mpaaNc17") == 0) {
+ return "nc-17";
+ } else if (g_strcmp0 (v3_rating, "mpaaPg") == 0) {
+ return "pg";
+ } else if (g_strcmp0 (v3_rating, "mpaaPg13") == 0) {
+ return "pg-13";
+ } else if (g_strcmp0 (v3_rating, "mpaaR") == 0) {
+ return "r";
+ } else {
+ return NULL;
+ }
+}
+
+static const gchar *
+convert_tvpg_rating (const gchar *v3_rating)
+{
+ if (g_strcmp0 (v3_rating, "pg14") == 0) {
+ return "tv-14";
+ } else if (g_strcmp0 (v3_rating, "tvpgG") == 0) {
+ return "tv-g";
+ } else if (g_strcmp0 (v3_rating, "tvpgMa") == 0) {
+ return "tv-ma";
+ } else if (g_strcmp0 (v3_rating, "tvpgPg") == 0) {
+ return "tv-pg";
+ } else if (g_strcmp0 (v3_rating, "tvpgY") == 0) {
+ return "tv-y";
+ } else if (g_strcmp0 (v3_rating, "tvpgY7") == 0) {
+ return "tv-y7";
+ } else if (g_strcmp0 (v3_rating, "tvpgY7Fv") == 0) {
+ return "tv-y7-fv";
+ } else {
+ return NULL;
+ }
}
/**
@@ -1279,7 +1894,7 @@ GDataMediaCategory *
gdata_youtube_video_get_category (GDataYouTubeVideo *self)
{
g_return_val_if_fail (GDATA_IS_YOUTUBE_VIDEO (self), NULL);
- return gdata_media_group_get_category (self->priv->media_group);
+ return self->priv->category;
}
/**
@@ -1298,10 +1913,14 @@ gdata_youtube_video_set_category (GDataYouTubeVideo *self, GDataMediaCategory *c
g_return_if_fail (GDATA_IS_YOUTUBE_VIDEO (self));
g_return_if_fail (GDATA_IS_MEDIA_CATEGORY (category));
- gdata_media_group_set_category (self->priv->media_group, category);
+ g_object_ref (category);
+ g_object_unref (self->priv->category);
+ self->priv->category = category;
g_object_notify (G_OBJECT (self), "category");
}
+G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+
/**
* gdata_youtube_video_get_credit:
* @self: a #GDataYouTubeVideo
@@ -1309,14 +1928,18 @@ gdata_youtube_video_set_category (GDataYouTubeVideo *self, GDataMediaCategory *c
* Gets the #GDataYouTubeVideo:credit property.
*
* Return value: (transfer none): a #GDataMediaCredit giving information on whom to credit for the video, or %NULL
+ * Deprecated: UNRELEASED: This is no longer supported by Google, and will
+ * always return %NULL. There is no replacement.
**/
GDataYouTubeCredit *
gdata_youtube_video_get_credit (GDataYouTubeVideo *self)
{
g_return_val_if_fail (GDATA_IS_YOUTUBE_VIDEO (self), NULL);
- return GDATA_YOUTUBE_CREDIT (gdata_media_group_get_credit (self->priv->media_group));
+ return NULL;
}
+G_GNUC_END_IGNORE_DEPRECATIONS
+
/**
* gdata_youtube_video_get_description:
* @self: a #GDataYouTubeVideo
@@ -1329,7 +1952,7 @@ const gchar *
gdata_youtube_video_get_description (GDataYouTubeVideo *self)
{
g_return_val_if_fail (GDATA_IS_YOUTUBE_VIDEO (self), NULL);
- return gdata_media_group_get_description (self->priv->media_group);
+ return gdata_entry_get_summary (GDATA_ENTRY (self));
}
/**
@@ -1345,8 +1968,7 @@ void
gdata_youtube_video_set_description (GDataYouTubeVideo *self, const gchar *description)
{
g_return_if_fail (GDATA_IS_YOUTUBE_VIDEO (self));
-
- gdata_media_group_set_description (self->priv->media_group, description);
+ gdata_entry_set_summary (GDATA_ENTRY (self), description);
g_object_notify (G_OBJECT (self), "description");
}
@@ -1389,7 +2011,7 @@ GList *
gdata_youtube_video_get_thumbnails (GDataYouTubeVideo *self)
{
g_return_val_if_fail (GDATA_IS_YOUTUBE_VIDEO (self), NULL);
- return gdata_media_group_get_thumbnails (self->priv->media_group);
+ return self->priv->thumbnails;
}
/**
@@ -1404,7 +2026,7 @@ guint
gdata_youtube_video_get_duration (GDataYouTubeVideo *self)
{
g_return_val_if_fail (GDATA_IS_YOUTUBE_VIDEO (self), 0);
- return gdata_youtube_group_get_duration (GDATA_YOUTUBE_GROUP (self->priv->media_group));
+ return self->priv->duration;
}
/**
@@ -1419,7 +2041,7 @@ gboolean
gdata_youtube_video_is_private (GDataYouTubeVideo *self)
{
g_return_val_if_fail (GDATA_IS_YOUTUBE_VIDEO (self), FALSE);
- return gdata_youtube_group_is_private (GDATA_YOUTUBE_GROUP (self->priv->media_group));
+ return self->priv->is_private;
}
/**
@@ -1433,7 +2055,7 @@ void
gdata_youtube_video_set_is_private (GDataYouTubeVideo *self, gboolean is_private)
{
g_return_if_fail (GDATA_IS_YOUTUBE_VIDEO (self));
- gdata_youtube_group_set_is_private (GDATA_YOUTUBE_GROUP (self->priv->media_group), is_private);
+ self->priv->is_private = is_private;
g_object_notify (G_OBJECT (self), "is-private");
}
@@ -1449,7 +2071,7 @@ gint64
gdata_youtube_video_get_uploaded (GDataYouTubeVideo *self)
{
g_return_val_if_fail (GDATA_IS_YOUTUBE_VIDEO (self), -1);
- return gdata_youtube_group_get_uploaded (GDATA_YOUTUBE_GROUP (self->priv->media_group));
+ return gdata_entry_get_published (GDATA_ENTRY (self));
}
/**
@@ -1475,12 +2097,14 @@ gdata_youtube_video_get_video_id (GDataYouTubeVideo *self)
* Gets the #GDataYouTubeVideo:is-draft property.
*
* Return value: %TRUE if the video is a draft, %FALSE otherwise
+ * Deprecated: UNRELEASED: This is now equal to
+ * gdata_youtube_video_is_private().
**/
gboolean
gdata_youtube_video_is_draft (GDataYouTubeVideo *self)
{
g_return_val_if_fail (GDATA_IS_YOUTUBE_VIDEO (self), FALSE);
- return gdata_youtube_control_is_draft (self->priv->youtube_control);
+ return gdata_youtube_video_is_private (self);
}
/**
@@ -1489,15 +2113,97 @@ gdata_youtube_video_is_draft (GDataYouTubeVideo *self)
* @is_draft: whether the video is a draft
*
* Sets the #GDataYouTubeVideo:is-draft property to decide whether the video is a draft.
+ *
+ * Deprecated: UNRELEASED: This is now equivalent to
+ * gdata_youtube_video_set_is_private().
**/
void
gdata_youtube_video_set_is_draft (GDataYouTubeVideo *self, gboolean is_draft)
{
g_return_if_fail (GDATA_IS_YOUTUBE_VIDEO (self));
- gdata_youtube_control_set_is_draft (self->priv->youtube_control, is_draft);
+ gdata_youtube_video_set_is_private (self, is_draft);
g_object_notify (G_OBJECT (self), "is-draft");
}
+/* Convert from v3 to v2 API video upload state. References:
+ * v2: https://developers.google.com/youtube/2.0/reference?csw=1#youtube_data_api_tag_yt:state
+ * v3: https://developers.google.com/youtube/v3/docs/videos#processingDetails.processingStatus
+ * https://developers.google.com/youtube/v3/docs/videos#status.uploadStatus
+ */
+static const gchar *
+convert_state_name (const gchar *v3_processing_status,
+ const gchar *v3_upload_status)
+{
+ if (g_strcmp0 (v3_upload_status, "deleted") == 0 ||
+ g_strcmp0 (v3_upload_status, "failed") == 0 ||
+ g_strcmp0 (v3_upload_status, "rejected") == 0) {
+ return v3_upload_status;
+ } else if (g_strcmp0 (v3_processing_status, "processing") == 0) {
+ return v3_processing_status;
+ }
+
+ return NULL;
+}
+
+/* References:
+ * v2: https://developers.google.com/youtube/2.0/reference?csw=1#youtube_data_api_tag_yt:state
+ * v3: https://developers.google.com/youtube/v3/docs/videos#status.failureReason
+ * https://developers.google.com/youtube/v3/docs/videos#status.rejectionReason
+ */
+static const gchar *
+convert_state_reason_code (const gchar *v2_name,
+ const gchar *v3_failure_reason,
+ const gchar *v3_rejection_reason)
+{
+ if (v2_name == NULL ||
+ g_strcmp0 (v2_name, "processing") == 0 ||
+ g_strcmp0 (v2_name, "deleted") == 0) {
+ /* Explicitly unset if unknown, processing or deleted. */
+ return NULL;
+ } else if (g_strcmp0 (v2_name, "restricted") == 0) {
+ /* Unsupported conversion; convert_state_name() can never return
+ * ‘restricted’ anyway. */
+ return NULL;
+ } else if (g_strcmp0 (v2_name, "rejected") == 0) {
+ if (g_strcmp0 (v3_rejection_reason, "claim") == 0 ||
+ g_strcmp0 (v3_rejection_reason, "copyright") == 0 ||
+ g_strcmp0 (v3_rejection_reason, "trademark") == 0) {
+ return "copyright";
+ } else if (g_strcmp0 (v3_rejection_reason, "duplicate") == 0) {
+ return "duplicate";
+ } else if (g_strcmp0 (v3_rejection_reason,
+ "inappropriate") == 0) {
+ return "inappropriate";
+ } else if (g_strcmp0 (v3_rejection_reason, "length") == 0) {
+ return "tooLong";
+ } else if (g_strcmp0 (v3_rejection_reason, "termsOfUse") == 0) {
+ return "termsOfUse";
+ } else if (g_strcmp0 (v3_rejection_reason,
+ "uploaderAccountClosed") == 0 ||
+ g_strcmp0 (v3_rejection_reason,
+ "uploaderAccountSuspended") == 0) {
+ return "duplicate";
+ } else {
+ /* Generic fallback. */
+ return "termsOfUse";
+ }
+ } else if (g_strcmp0 (v2_name, "failed") == 0) {
+ if (g_strcmp0 (v3_failure_reason, "codec") == 0) {
+ return "unsupportedCodec";
+ } else if (g_strcmp0 (v3_failure_reason, "conversion") == 0) {
+ return "invalidFormat";
+ } else if (g_strcmp0 (v3_failure_reason, "emptyFile") == 0) {
+ return "empty";
+ } else if (g_strcmp0 (v3_failure_reason, "tooSmall") == 0) {
+ return "tooSmall";
+ } else {
+ return "cantProcess";
+ }
+ }
+
+ return NULL;
+}
+
/**
* gdata_youtube_video_get_state:
* @self: a #GDataYouTubeVideo
@@ -1512,8 +2218,31 @@ gdata_youtube_video_set_is_draft (GDataYouTubeVideo *self, gboolean is_draft)
GDataYouTubeState *
gdata_youtube_video_get_state (GDataYouTubeVideo *self)
{
+ GDataYouTubeVideoPrivate *priv;
+
g_return_val_if_fail (GDATA_IS_YOUTUBE_VIDEO (self), NULL);
- return gdata_youtube_control_get_state (self->priv->youtube_control);
+
+ priv = self->priv;
+
+ /* Lazily create the state object. */
+ if (priv->upload_state == NULL) {
+ const gchar *name, *reason_code;
+
+ name = convert_state_name (priv->processing_status,
+ priv->upload_status);
+ reason_code = convert_state_reason_code (name,
+ priv->failure_reason,
+ priv->rejection_reason);
+
+ priv->upload_state = g_object_new (GDATA_TYPE_YOUTUBE_STATE,
+ "name", name,
+ "reason-code", reason_code,
+ "help-uri", NULL,
+ "message", NULL,
+ NULL);
+ }
+
+ return priv->upload_state;
}
/**
@@ -1635,7 +2364,10 @@ const gchar *
gdata_youtube_video_get_aspect_ratio (GDataYouTubeVideo *self)
{
g_return_val_if_fail (GDATA_IS_YOUTUBE_VIDEO (self), NULL);
- return gdata_youtube_group_get_aspect_ratio (GDATA_YOUTUBE_GROUP (self->priv->media_group));
+
+ /* Permanently NULL for the moment, but let’s not deprecate the property
+ * because it looks like it might come in useful in future. */
+ return NULL;
}
/**
@@ -1652,8 +2384,9 @@ void
gdata_youtube_video_set_aspect_ratio (GDataYouTubeVideo *self, const gchar *aspect_ratio)
{
g_return_if_fail (GDATA_IS_YOUTUBE_VIDEO (self));
- gdata_youtube_group_set_aspect_ratio (GDATA_YOUTUBE_GROUP (self->priv->media_group), aspect_ratio);
- g_object_notify (G_OBJECT (self), "aspect-ratio");
+
+ /* Ignore it. See note in gdata_youtube_video_get_aspect_ratio(),
+ * above. */
}
/**
@@ -1672,10 +2405,12 @@ gdata_youtube_video_get_coordinates (GDataYouTubeVideo *self, gdouble *latitude,
{
g_return_if_fail (GDATA_IS_YOUTUBE_VIDEO (self));
- if (latitude != NULL)
- *latitude = gdata_georss_where_get_latitude (self->priv->georss_where);
- if (longitude != NULL)
- *longitude = gdata_georss_where_get_longitude (self->priv->georss_where);
+ if (latitude != NULL) {
+ *latitude = self->priv->latitude;
+ }
+ if (longitude != NULL) {
+ *longitude = self->priv->longitude;
+ }
}
/**
@@ -1693,8 +2428,8 @@ gdata_youtube_video_set_coordinates (GDataYouTubeVideo *self, gdouble latitude,
{
g_return_if_fail (GDATA_IS_YOUTUBE_VIDEO (self));
- gdata_georss_where_set_latitude (self->priv->georss_where, latitude);
- gdata_georss_where_set_longitude (self->priv->georss_where, longitude);
+ self->priv->latitude = latitude;
+ self->priv->longitude = longitude;
g_object_freeze_notify (G_OBJECT (self));
g_object_notify (G_OBJECT (self), "latitude");
diff --git a/gdata/services/youtube/gdata-youtube-video.h b/gdata/services/youtube/gdata-youtube-video.h
index bb2c8299..2312cf45 100644
--- a/gdata/services/youtube/gdata-youtube-video.h
+++ b/gdata/services/youtube/gdata-youtube-video.h
@@ -206,7 +206,6 @@ gboolean gdata_youtube_video_is_restricted_in_country (GDataYouTubeVideo *self,
const gchar *gdata_youtube_video_get_media_rating (GDataYouTubeVideo *self, const gchar *rating_type) G_GNUC_PURE;
GDataMediaCategory *gdata_youtube_video_get_category (GDataYouTubeVideo *self) G_GNUC_PURE;
void gdata_youtube_video_set_category (GDataYouTubeVideo *self, GDataMediaCategory *category);
-GDataYouTubeCredit *gdata_youtube_video_get_credit (GDataYouTubeVideo *self) G_GNUC_PURE;
const gchar *gdata_youtube_video_get_description (GDataYouTubeVideo *self) G_GNUC_PURE;
void gdata_youtube_video_set_description (GDataYouTubeVideo *self, const gchar *description);
GList *gdata_youtube_video_get_thumbnails (GDataYouTubeVideo *self) G_GNUC_PURE;
@@ -214,8 +213,6 @@ guint gdata_youtube_video_get_duration (GDataYouTubeVideo *self) G_GNUC_PURE;
gboolean gdata_youtube_video_is_private (GDataYouTubeVideo *self) G_GNUC_PURE;
void gdata_youtube_video_set_is_private (GDataYouTubeVideo *self, gboolean is_private);
gint64 gdata_youtube_video_get_uploaded (GDataYouTubeVideo *self);
-gboolean gdata_youtube_video_is_draft (GDataYouTubeVideo *self) G_GNUC_PURE;
-void gdata_youtube_video_set_is_draft (GDataYouTubeVideo *self, gboolean is_draft);
GDataYouTubeState *gdata_youtube_video_get_state (GDataYouTubeVideo *self) G_GNUC_PURE;
gint64 gdata_youtube_video_get_recorded (GDataYouTubeVideo *self);
void gdata_youtube_video_set_recorded (GDataYouTubeVideo *self, gint64 recorded);
@@ -229,6 +226,11 @@ gchar *gdata_youtube_video_get_video_id_from_uri (const gchar *video_uri) G_GNUC
#ifndef LIBGDATA_DISABLE_DEPRECATED
const gchar *gdata_youtube_video_get_video_id (GDataYouTubeVideo *self) G_GNUC_PURE G_GNUC_DEPRECATED_FOR(gdata_entry_get_id);
GDataYouTubeContent *gdata_youtube_video_look_up_content (GDataYouTubeVideo *self, const gchar *type) G_GNUC_PURE G_GNUC_DEPRECATED_FOR(gdata_youtube_video_get_player_uri);
+
+gboolean gdata_youtube_video_is_draft (GDataYouTubeVideo *self) G_GNUC_PURE G_GNUC_DEPRECATED_FOR(gdata_youtube_video_is_private);
+void gdata_youtube_video_set_is_draft (GDataYouTubeVideo *self, gboolean is_draft) G_GNUC_DEPRECATED_FOR(gdata_youtube_video_set_is_private);
+
+GDataYouTubeCredit *gdata_youtube_video_get_credit (GDataYouTubeVideo *self) G_GNUC_PURE G_GNUC_DEPRECATED;
#endif /* !LIBGDATA_DISABLE_DEPRECATED */
G_END_DECLS
diff --git a/gdata/tests/youtube.c b/gdata/tests/youtube.c
index 545ca8b9..9ced7b42 100644
--- a/gdata/tests/youtube.c
+++ b/gdata/tests/youtube.c
@@ -902,7 +902,9 @@ test_parsing_app_control (void)
g_clear_error (&error);
/* Test the app:control values */
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS
g_assert (gdata_youtube_video_is_draft (video) == TRUE);
+ G_GNUC_END_IGNORE_DEPRECATIONS
state = gdata_youtube_video_get_state (video);
g_assert_cmpstr (gdata_youtube_state_get_name (state), ==, "blacklisted");