[shotwell/wip/libsoup3] plugins: Add back Youtube publishing
- From: Jens Georg <jensgeorg src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [shotwell/wip/libsoup3] plugins: Add back Youtube publishing
- Date: Mon, 18 Jul 2022 19:03:08 +0000 (UTC)
commit e80358bf0b8d613489e522208373463ba6e160db
Author: Jens Georg <mail jensge org>
Date: Mon Jul 18 21:02:48 2022 +0200
plugins: Add back Youtube publishing
plugins/shotwell-publishing/YouTubePublishing.vala | 143 +++------------------
plugins/shotwell-publishing/YoutubeUploader.vala | 133 +++++++++++++++++++
plugins/shotwell-publishing/meson.build | 3 +-
.../shotwell-publishing/shotwell-publishing.vala | 3 +-
4 files changed, 156 insertions(+), 126 deletions(-)
---
diff --git a/plugins/shotwell-publishing/YouTubePublishing.vala
b/plugins/shotwell-publishing/YouTubePublishing.vala
index 91927bd0..4b5908a4 100644
--- a/plugins/shotwell-publishing/YouTubePublishing.vala
+++ b/plugins/shotwell-publishing/YouTubePublishing.vala
@@ -60,10 +60,23 @@ private const string DEVELOPER_KEY =
private enum PrivacySetting {
PUBLIC,
UNLISTED,
- PRIVATE
+ PRIVATE;
+
+ public string to_string() {
+ switch (this) {
+ case PUBLIC:
+ return "public";
+ case UNLISTED:
+ return "unlisted";
+ case PRIVATE:
+ return "private";
+ default:
+ assert_not_reached();
+ }
+ }
}
-private class PublishingParameters {
+internal class PublishingParameters {
private PrivacySetting privacy;
private string? user_name;
@@ -89,44 +102,14 @@ private class PublishingParameters {
}
}
-internal class YouTubeAuthorizer : GData.Authorizer, Object {
- private RESTSupport.GoogleSession session;
- private Spit.Publishing.Authenticator authenticator;
-
- public YouTubeAuthorizer(RESTSupport.GoogleSession session, Spit.Publishing.Authenticator authenticator)
{
- this.session = session;
- this.authenticator = authenticator;
- }
-
- public bool is_authorized_for_domain(GData.AuthorizationDomain domain) {
- return domain.scope.has_suffix ("auth/youtube");
- }
-
- public void process_request(GData.AuthorizationDomain? domain,
- Soup.Message message) {
- if (domain == null) {
- return;
- }
-
- var header = "Bearer %s".printf(session.get_access_token());
- message.request_headers.replace("Authorization", header);
- }
-
- public bool refresh_authorization (GLib.Cancellable? cancellable = null) throws GLib.Error {
- this.authenticator.refresh();
- return true;
- }
-}
-
public class YouTubePublisher : Publishing.RESTSupport.GooglePublisher {
private bool running;
private PublishingParameters publishing_parameters;
private Spit.Publishing.ProgressCallback? progress_reporter;
private Spit.Publishing.Authenticator authenticator;
- private GData.YouTubeService youtube_service;
public YouTubePublisher(Spit.Publishing.Service service, Spit.Publishing.PluginHost host) {
- base(service, host, "https://gdata.youtube.com/");
+ base(service, host, "https://www.googleapis.com/upload/youtube/v3/videos");
this.running = false;
this.publishing_parameters = new PublishingParameters();
@@ -161,8 +144,6 @@ public class YouTubePublisher : Publishing.RESTSupport.GooglePublisher {
publishing_parameters.set_user_name(get_session().get_user_name());
- this.youtube_service = new GData.YouTubeService(DEVELOPER_KEY,
- new YouTubeAuthorizer(get_session(), this.authenticator));
do_show_publishing_options_pane();
}
@@ -261,7 +242,7 @@ public class YouTubePublisher : Publishing.RESTSupport.GooglePublisher {
return;
Spit.Publishing.Publishable[] publishables = get_host().get_publishables();
- Uploader uploader = new Uploader(this.youtube_service, get_session(), publishables,
publishing_parameters);
+ Uploader uploader = new Uploader(get_session(), publishables, publishing_parameters);
uploader.upload_complete.connect(on_upload_complete);
uploader.upload_error.connect(on_upload_error);
@@ -397,104 +378,20 @@ internal class PublishingOptionsPane : Spit.Publishing.DialogPane, GLib.Object {
}
}
-internal class UploadTransaction : Publishing.RESTSupport.GooglePublisher.AuthenticatedTransaction {
- private const string ENDPOINT_URL = "https://uploads.gdata.youtube.com/feeds/api/users/default/uploads";
- private PublishingParameters parameters;
- private Publishing.RESTSupport.GoogleSession session;
- private Spit.Publishing.Publishable publishable;
- private GData.YouTubeService youtube_service;
-
- public UploadTransaction(GData.YouTubeService youtube_service, Publishing.RESTSupport.GoogleSession
session,
- PublishingParameters parameters, Spit.Publishing.Publishable publishable) {
- base(session, ENDPOINT_URL, Publishing.RESTSupport.HttpMethod.POST);
- assert(session.is_authenticated());
- this.session = session;
- this.parameters = parameters;
- this.publishable = publishable;
- this.youtube_service = youtube_service;
- }
-
- public override void execute() throws Spit.Publishing.PublishingError {
- var video = new GData.YouTubeVideo(null);
-
- var slug = publishable.get_param_string(Spit.Publishing.Publishable.PARAM_STRING_BASENAME);
- // Set title to publishing name, but if that's empty default to filename.
- string title = publishable.get_publishing_name();
- if (title == "") {
- title = publishable.get_param_string(Spit.Publishing.Publishable.PARAM_STRING_BASENAME);
- }
- video.title = title;
-
- video.is_private = (parameters.get_privacy() == PrivacySetting.PRIVATE);
-
- if (parameters.get_privacy() == PrivacySetting.UNLISTED) {
- video.set_access_control("list", GData.YouTubePermission.DENIED);
- } else if (!video.is_private) {
- video.set_access_control("list", GData.YouTubePermission.ALLOWED);
- }
-
- var file = publishable.get_serialized_file();
-
- try {
- var info = file.query_info(FileAttribute.STANDARD_CONTENT_TYPE + "," +
- FileAttribute.STANDARD_SIZE, FileQueryInfoFlags.NONE);
- var upload_stream = this.youtube_service.upload_video(video, slug,
- info.get_content_type());
- var input_stream = file.read();
-
- // Yuck...
- var loop = new MainLoop(null, false);
- this.splice_with_progress.begin(info, input_stream, upload_stream, (obj, res) => {
- try {
- this.splice_with_progress.end(res);
- } catch (Error error) {
- critical("Failed to upload: %s", error.message);
- }
- loop.quit();
- });
- loop.run();
- video = this.youtube_service.finish_video_upload(upload_stream);
- } catch (Error error) {
- critical("Upload failed: %s", error.message);
- }
- }
-
- private async void splice_with_progress(GLib.FileInfo info, GLib.InputStream input, GLib.OutputStream
output) throws Error {
- var total_bytes = info.get_size();
- var bytes_to_write = total_bytes;
- uint8 buffer[8192];
-
- while (bytes_to_write > 0) {
- var bytes_read = yield input.read_async(buffer);
- if (bytes_read == 0)
- break;
-
- var bytes_written = yield output.write_async(buffer[0:bytes_read]);
- bytes_to_write -= bytes_written;
- chunk_transmitted((int)(total_bytes - bytes_to_write), (int) total_bytes);
- }
-
- yield output.close_async();
- yield input.close_async();
- }
-}
-
internal class Uploader : Publishing.RESTSupport.BatchUploader {
private PublishingParameters parameters;
- private GData.YouTubeService youtube_service;
- public Uploader(GData.YouTubeService youtube_service, Publishing.RESTSupport.GoogleSession session,
+ public Uploader(Publishing.RESTSupport.GoogleSession session,
Spit.Publishing.Publishable[] publishables, PublishingParameters parameters) {
base(session, publishables);
this.parameters = parameters;
- this.youtube_service = youtube_service;
}
protected override Publishing.RESTSupport.Transaction create_transaction(
Spit.Publishing.Publishable publishable) {
- return new UploadTransaction(this.youtube_service, (Publishing.RESTSupport.GoogleSession)
get_session(),
- parameters, get_current_publishable());
+ return new UploadTransaction((Publishing.RESTSupport.GoogleSession) get_session(),
+ parameters, get_current_publishable());
}
}
diff --git a/plugins/shotwell-publishing/YoutubeUploader.vala
b/plugins/shotwell-publishing/YoutubeUploader.vala
new file mode 100644
index 00000000..f98bf15c
--- /dev/null
+++ b/plugins/shotwell-publishing/YoutubeUploader.vala
@@ -0,0 +1,133 @@
+using Spit;
+
+namespace Publishing.YouTube {
+internal class UploadTransaction : Publishing.RESTSupport.GooglePublisher.AuthenticatedTransaction {
+ private PublishingParameters parameters;
+ private Publishing.RESTSupport.GoogleSession session;
+ private Spit.Publishing.Publishable publishable;
+ private MappedFile mapped_file;
+
+ public UploadTransaction(Publishing.RESTSupport.GoogleSession session,
+ PublishingParameters parameters, Spit.Publishing.Publishable publishable) {
+ base(session, "https://www.googleapis.com/upload/youtube/v3/videos",
+ Publishing.RESTSupport.HttpMethod.POST);
+ assert(session.is_authenticated());
+
+ this.session = session;
+ this.parameters = parameters;
+ this.publishable = publishable;
+ }
+
+ public Spit.Publishing.Publishable get_publishable() {
+ return this.publishable;
+ }
+
+ public override void execute() throws Spit.Publishing.PublishingError {
+ // Collect parameters
+
+ var slug = publishable.get_param_string(Spit.Publishing.Publishable.PARAM_STRING_BASENAME);
+ // Set title to publishing name, but if that's empty default to filename.
+ string title = publishable.get_publishing_name();
+ if (title == "") {
+ title = publishable.get_param_string(Spit.Publishing.Publishable.PARAM_STRING_BASENAME);
+ }
+
+ var builder = new Json.Builder();
+ builder.begin_object();
+ builder.set_member_name("snippet");
+ builder.begin_object();
+ builder.set_member_name("description");
+ builder.add_string_value(slug);
+ builder.set_member_name("title");
+ builder.add_string_value(title);
+ builder.end_object();
+ builder.set_member_name("status");
+ builder.begin_object();
+ builder.set_member_name("privacyStatus");
+ builder.add_string_value(parameters.get_privacy().to_string());
+ builder.end_object();
+ builder.end_object();
+
+ var meta_data = Json.to_string (builder.get_root(), false);
+ debug ("Parameters: %s", meta_data);
+ var message_parts = new Soup.Multipart("multipart/related");
+ var headers = new Soup.MessageHeaders(Soup.MessageHeadersType.MULTIPART);
+ var encoding = new GLib.HashTable<string, string>(str_hash, str_equal);
+ encoding.insert("encoding", "UTF-8");
+ headers.set_content_type ("application/json", encoding);
+
+ message_parts.append_part (headers, new Bytes (meta_data.data));
+ headers = new Soup.MessageHeaders(Soup.MessageHeadersType.MULTIPART);
+ headers.set_content_type ("application/octet-stream", null);
+ headers.append("Content-Transfer-Encoding", "binary");
+
+ MappedFile? mapped_file = null;
+ try {
+ mapped_file = new MappedFile(publishable.get_serialized_file().get_path(), false);
+ } catch (Error e) {
+ throw new Spit.Publishing.PublishingError.LOCAL_FILE_ERROR(
+ _("A temporary file needed for publishing is unavailable"));
+ }
+
+
+ message_parts.append_part (headers, mapped_file.get_bytes());
+
+ var outbound_message = new Soup.Message.from_multipart (get_endpoint_url() + "?part=" +
GLib.Uri.escape_string ("snippet,status"), message_parts);
+ outbound_message.get_request_headers().append("Authorization", "Bearer " +
+ session.get_access_token());
+
+ set_message(outbound_message, mapped_file.get_length() + meta_data.length);
+ set_is_executed(true);
+ send();
+
+#if 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ var basename = publishable.get_param_string(Spit.Publishing.Publishable.PARAM_STRING_BASENAME);
+ int64 mapped_file_size = -1;
+
+ // attempt to map the binary image data from disk into memory
+ try {
+ mapped_file = publishable.get_serialized_file().read(null);
+ var info = ((FileInputStream)mapped_file).query_info("standard::size", null);
+ mapped_file_size = info.get_size();
+ } catch (Error e) {
+ string msg = "Google Photos: couldn't read data from %s: %s".printf(
+ publishable.get_serialized_file().get_path(), e.message);
+ warning("%s", msg);
+
+ throw new Spit.Publishing.PublishingError.LOCAL_FILE_ERROR(msg);
+ }
+
+ // create a message that can be sent over the wire whose payload is the multipart container
+ // that we've been building up
+ var outbound_message = new Soup.Message ("POST", get_endpoint_url());
+ outbound_message.request_headers.append("Authorization", "Bearer " +
+ session.get_access_token());
+ outbound_message.request_headers.append("X-Goog-Upload-File-Name", basename);
+ outbound_message.request_headers.append("X-Goog-Upload-Protocol", "raw");
+ outbound_message.request_headers.set_content_type("application/octet-stream", null);
+ outbound_message.set_request_body(null, mapped_file, (ssize_t)mapped_file_size);
+ set_message(outbound_message, (ulong)mapped_file_size);
+
+ // send the message and get its response
+ set_is_executed(true);
+ send();
+ #endif
+ }
+
+}
+}
diff --git a/plugins/shotwell-publishing/meson.build b/plugins/shotwell-publishing/meson.build
index 4d495438..3096ee56 100644
--- a/plugins/shotwell-publishing/meson.build
+++ b/plugins/shotwell-publishing/meson.build
@@ -2,7 +2,8 @@ shotwell_publishing_sources = [
'shotwell-publishing.vala',
'FlickrPublishing.vala',
'TumblrPublishing.vala',
-# 'YouTubePublishing.vala',
+ 'YouTubePublishing.vala',
+ 'YoutubeUploader.vala',
'PiwigoPublishing.vala',
'PhotosPublisher.vala',
'PhotosService.vala',
diff --git a/plugins/shotwell-publishing/shotwell-publishing.vala
b/plugins/shotwell-publishing/shotwell-publishing.vala
index da6c87d5..5cebf0e3 100644
--- a/plugins/shotwell-publishing/shotwell-publishing.vala
+++ b/plugins/shotwell-publishing/shotwell-publishing.vala
@@ -34,8 +34,7 @@ private class ShotwellPublishingCoreServices : Object, Spit.Module {
}
#endif
-#if 0
-//HAVE_YOUTUBE
+#if HAVE_YOUTUBE
if (authenicators.contains("youtube")) {
pluggables += new YouTubeService(resource_directory);
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]