I'm facing the same issue described by #Lamprizzle and ?solved by #jlindema -- trying to upload JPGs to Google Photos including the photo's caption:
Programmatically set a Google Photos caption
Instead of Google Apps Script (GAS), we've adapted eshmu's Python script:
https://github.com/eshmu/gphotos-upload
The basic interface (line 160 from eshmu's upload.py) uses the same newMediaItems interface:
create_body = json.dumps({
"albumId":album_id,
"newMediaItems":[
{"description":"THIS TEXT IS IGNORED",
"simpleMediaItem":
{"uploadToken":upload_token.content.decode()}
}
]
}, indent=4)
I can successfully create albums and upload JPGs to Google Photos using this Python script, but Google photos entirely ignores the "description" field. I'm at a loss for how #Lamprizzle found success with the equivalent GAS command:
{
description: item.description,
simpleMediaItem: {
fileName: item.filename,
uploadToken: _getUploadToken(item)
}
Any suggestions or tips on how to coax Google Photos to accepting/displaying my photo captions would be hugely appreciated.
Related
I have two Revit model files, A and B, where B is linked into A. I want to upload the files to BIM360 Docs via the Autodesk.Forge API and keep them linked, so I can see the combined model in the Forge Model viewer when I subsequently view model A.
I have the two files in a zip file, but from what I understand, I shouldn't upload the zip file, but rather upload A and B separately, then create a relationship between them.
I can upload the files without problems, and I've then tried to link them via this code (using the NON-encoded version ids for A and B):
public async Task SetLinkedFileRelationship(string projectId, string versionId, string linkedVersionId)
{
BaseAttributesExtensionObject baseAttribute = new BaseAttributesExtensionObject("auxiliary:autodesk.core:Attachment", "1.0");
CreateRefDataMeta meta = new CreateRefDataMeta(baseAttribute);
CreateRefData createRefData = new CreateRefData(CreateRefData.TypeEnum.Versions, linkedVersionId, meta);
CreateRef createRef = new CreateRef(new JsonApiVersionJsonapi(JsonApiVersionJsonapi.VersionEnum._0), createRefData);
VersionsApi versionsApi = new VersionsApi { Configuration = { AccessToken = _token.AccessToken } };
await versionsApi.PostVersionRelationshipsRefAsync(projectId, versionId, createRef);
}
...which produces this response:
status: 400
code: FUNCTION_NOT_SUPPORTED
detail: BIM360 currently does not support the creation of refs.
So apparently I can't create the link between A and B like this. Is there another way to accomplish what I want, or is this currently just not possible in BIM360? I know you can do it via the BIM360 Docs web page (using the Upload file -> Linked Files button), but is it possible when I upload the model files via the API? If so, what is the recipe?
Please keep in mind that my question is for uploading to BIM360 Docs - using the Autodesk.Forge API (v2). I'm aware of this post: BIM360 Docs: Setting up external references between files (Upload Linked Files), but that is targeted at manually composing requests. I'd like to be able to use the v2 API.
I believe this post should help https://forge.autodesk.com/blog/bim360-docs-setting-external-references-between-files-upload-linked-files.
I am trying to insert an image (PNG) in a Google Slide presentation using the Slides API. I do this by first uploading the image to the user's Drive, obtaining the url, passing that along to the Slide API via the correct request and then deleting the image file.
What used to work as of a few weeks ago:
image_url = '%s&access_token=%s' % (
drive_service.files().get_media(fileId=image_file_id).uri,
creds.token)
However, there have been changes to the Drive API, such that URLS constructed this way no longer work.
I am having difficulty figuring out the new correct URL to use here. The options as per the doc that describes the change are:
Use webContentLink -- Downloads
Use webViewLink -- View
Use exportLinks -- Export
I use code that looks like this to get these links:
upload = drive_service.files().create(
body={'name': 'My Image File'},
media_body=media_body,
fields='webContentLink, id, webViewLink').execute()
image_url = upload.get('webContentLink')
I have tried both #1 and #2 and get the following error:
"Invalid requests[0].createImage: The provided image is in an unsupported format."
I have also been receiving the following error intermittently:
"Invalid requests[0].createImage: Access to the provided image was forbidden."
I verified that I am able to download / view the image from the URLs generated in #1 and #2. I didn't try #3 since I am not trying to export to a different format.
What would be the best way to go about figuring out the correct URL to use?
From your script, I think that the reason of your issue is due to this. By this, the query parameter of access_token cannot be used. Under this situation, when image_url = '%s&access_token=%s' % (drive_service.files().get_media(fileId=image_file_id).uri,creds.token) is used, the login page is returned. By this, such error occurs. So as a workaround, how about the following flow?
Flow:
Upload a PNG file.
Publicly share the PNG file by creating a permission.
Insert the PNG file to Slides.
Close the shared PNG file by deleting the permission.
When the image file is put to the Slides, even when the permission of file is deleted, the image is not removed from the Slides. This workaround uses this.
Sample script:
For above flow, the sample script of python is as follows. Please set the variables of uploadFilename, presentation_id and pageObjectId
uploadFilename = './sample.png' # Please set the filename with the path.
presentation_id = '###' # Please set the Google Slides ID.
pageObjectId = '###' # Please set the page ID of the Slides.
drive = build('drive', 'v3', credentials=creds)
slides = build('slides', 'v1', credentials=creds)
# 1. Upload a PNG file from local PC
file_metadata = {'name': uploadFilename}
media = MediaFileUpload(uploadFilename, mimetype='image/png')
upload = drive.files().create(body=file_metadata, media_body=media, fields='webContentLink, id, webViewLink').execute()
fileId = upload.get('id')
url = upload.get('webContentLink')
# 2. Share publicly the uploaded PNG file by creating permissions.
drive.permissions().create(fileId=fileId, body={'type': 'anyone', 'role': 'reader'}).execute()
# 3. Insert the PNG file to the Slides.
body = {
"requests": [
{
"createImage": {
"url": url,
"elementProperties": {
"pageObjectId": pageObjectId
}
}
}
]
}
slides.presentations().batchUpdate(presentationId=presentation_id, body=body).execute()
# 4. Delete the permissions. By this, the shared PNG file is closed.
drive.permissions().delete(fileId=fileId, permissionId='anyoneWithLink').execute()
Note:
I thought that from your script, you might be using google-api-python-client with python. So I proposed the sample script for python.
In this case, the scopes for using Slides API and Drive API are required. Please be careful this.
In the case of Google Apps Script, you can see the sample script at here.
References:
Upcoming changes to the Google Drive API and Google Picker API
Permissions: create
Permissions: delete
If I misunderstood your question and this was not the direction you want, I apologize.
I was running into the same error even when using the flow involving granting temporary permissions access then removing the permissions after calling .createImage() or .replaceAllShapesWithImage()
I also ran into this error when creating permissions for a folder containing those images: "Invalid requests[0].replaceAllShapesWithImage: Access to the provided image was forbidden." Not sure why the permissions are not propagating to the images...
Following Kos' comment, switching to jpg file type worked for me.
Edit:
It appears I am also required to set the scope to 'https://www.googleapis.com/auth/drive' in order for it to work, which isn't ideal, but is sufficient for now.
Edit 2:
Nevermind it appears to be inconsistent. I am running into the permissions access error again. Deleting my token.pickle does not seem to fix either
I am trying to import a list of files from Google Drive to YouTube. The meta-data and the URL to the file are in a Google spreadsheet, so I wrote some code using Google Apps Script that does the following
Get the selected rows
Retrieve title, description, Google Drive URL
Load the file from Google Drive via DriveApp.getFileById
Upload the blob to YouTube using the title and description via YouTube.Videos.insert
Update the selected row with the YouTube video id from the response
The upload looks something like this
var blob = DriveApp.getFileById(id).getBlob();
var resource = {
snippet: {
title: 'The title',
description: 'A long description ...',
defaultLanguage: 'de',
categoryId: 17,
tags: [ 'Sport', 'Fitness' ],
},
status: {
privacyStatus: 'unlisted'
}
}
try {
var result = YouTube.Videos.insert(resource, "snippet,status", blob);
return result.id;
} catch (err) {
console.log({message: 'Error ' + err.message, error: err});
}
This code has already worked about a year ago. I have adapted it slightly, but now I do not get a response from the YouTube.Videos.insert call. The following is logged inside the catch:
message: Error Empty response
error: Exception: Empty response
Not very helpful.
Before uploading, I do a YouTube.Channels.list
to get a target channel in case there are multiple channels available. For this request, I have to permit access to my data and I am only asked on the first invocation. I also see the script in the list of applications for my Google account. I assume permissions are ok.
Any suggestions on how I can get more information on the issue, or is there something I should do differently?
Regarding the target channel (and this might be a different question), I cannot really use this, as it seems I can only upload to a specific channel, if I am a YouTube content partner (see parameters onBehalfOfContentOwner and onBehalfOfContentOwnerChannel):
Note: This parameter is intended exclusively for YouTube content partners.
I had same problem in my project and here's what I have figured out: if your video file size is more than 10 Mb, you will get Empty response error.
Probably (can't say officialy because no documentation mentions it) this is happening because Google Apps Script's YouTube.Videos.insert (and all other available built-in services) uses UrlFetchApp under the hood, which have restriction of 10 Mb per call: https://developers.google.com/apps-script/guides/services/quotas#current_limitations. You can check it yourself using your sample code: if file is under 10 Mb, it will be uploaded successfully.
As possible workaround, you can use idea from this answer: https://stackoverflow.com/a/44853845/555121
Basically, you will need to open modal window using SpreadsheetApp.getUi().showModalDialog and then perform upload to YouTube via plain JavaScript inside modal dialog, which have no restrictions on transferred data size. Here's good example of YouTube resumable upload implementation: https://github.com/sangnvus/2015SUMJS01/blob/master/WIP/Sources/FlyAwayPlus/FlyAwayPlus/Scripts/youtube-upload.js
I am trying to import a list of files from Google Drive to YouTube. The meta-data and the URL to the file are in a Google spreadsheet, so I wrote some code using Google Apps Script that does the following
Get the selected rows
Retrieve title, description, Google Drive URL
Load the file from Google Drive via DriveApp.getFileById
Upload the blob to YouTube using the title and description via YouTube.Videos.insert
Update the selected row with the YouTube video id from the response
The upload looks something like this
var blob = DriveApp.getFileById(id).getBlob();
var resource = {
snippet: {
title: 'The title',
description: 'A long description ...',
defaultLanguage: 'de',
categoryId: 17,
tags: [ 'Sport', 'Fitness' ],
},
status: {
privacyStatus: 'unlisted'
}
}
try {
var result = YouTube.Videos.insert(resource, "snippet,status", blob);
return result.id;
} catch (err) {
console.log({message: 'Error ' + err.message, error: err});
}
This code has already worked about a year ago. I have adapted it slightly, but now I do not get a response from the YouTube.Videos.insert call. The following is logged inside the catch:
message: Error Empty response
error: Exception: Empty response
Not very helpful.
Before uploading, I do a YouTube.Channels.list
to get a target channel in case there are multiple channels available. For this request, I have to permit access to my data and I am only asked on the first invocation. I also see the script in the list of applications for my Google account. I assume permissions are ok.
Any suggestions on how I can get more information on the issue, or is there something I should do differently?
Regarding the target channel (and this might be a different question), I cannot really use this, as it seems I can only upload to a specific channel, if I am a YouTube content partner (see parameters onBehalfOfContentOwner and onBehalfOfContentOwnerChannel):
Note: This parameter is intended exclusively for YouTube content partners.
I had same problem in my project and here's what I have figured out: if your video file size is more than 10 Mb, you will get Empty response error.
Probably (can't say officialy because no documentation mentions it) this is happening because Google Apps Script's YouTube.Videos.insert (and all other available built-in services) uses UrlFetchApp under the hood, which have restriction of 10 Mb per call: https://developers.google.com/apps-script/guides/services/quotas#current_limitations. You can check it yourself using your sample code: if file is under 10 Mb, it will be uploaded successfully.
As possible workaround, you can use idea from this answer: https://stackoverflow.com/a/44853845/555121
Basically, you will need to open modal window using SpreadsheetApp.getUi().showModalDialog and then perform upload to YouTube via plain JavaScript inside modal dialog, which have no restrictions on transferred data size. Here's good example of YouTube resumable upload implementation: https://github.com/sangnvus/2015SUMJS01/blob/master/WIP/Sources/FlyAwayPlus/FlyAwayPlus/Scripts/youtube-upload.js
We have a published Google Docs Add-on. Where we need 5 different languages. We are reading the Internationalizing Your App documentation. It's mainly describing how it can be done if you are publishing an Chrome Extension (uploading .zip). However an Docs Add-on is written in Google Apps Script.
As per documentation we need to add:
manifest.json
messages.json
"and providing a _locales directory in your app's ZIP file"
Here is where we get trouble. How can we add folders in Google Apps Script & how can we add .json files? The only options are script-files & html-files.
That documentation is NOT for Google Apps Script at all, but for Chrome Apps. So it does not directly apply.
Briefly looking through Google Apps Script docs, i18n is not mentioned. It does not seem like there is any supporting library for it.
Note that according to an earlier question on the topic you can get the user's locale with Session.getActiveUserLocale()) and implement your own i18n.
However, that probably won't enable item i18n in the Web Store itself. There's a bug report describing this situation.
The bug report mentions that you could potentially throw in the locale fields/folders in the generated Chrome app by editing it directly using this procedure. That much is not tested though.
Here is how we did it at Vocal. It's fairly simple and easy to implement (Our example supports english and spanish).
1. Create a dictionary with keys for the different strings in your app:
const LANGUAGE_MAP = {
"RECORD_MESSAGE": {
"en": "Record message",
"es": "Grabar mensaje"
},
"RECORD_MESSAGE_TEXT": {
"en": "1. Click here to record your voice",
"es": "1. Haga clic aquí para grabar su voz"
}
}
2. Create a function that takes a key as input and returns the string in the proper language based on the user's locale
This is done using Session.getActiveUserLocale()
function translate(key) {
locale = Session.getActiveUserLocale();
if (['en', 'es'].includes(locale)) {
return LANGUAGE_MAP[key][locale]
}
else {
return LANGUAGE_MAP[key]['en']
}
}
Replace all your bare strings with the function created above and the string as key.
CardService.newTextParagraph().setText(translate("RECORD_MESSAGE_TEXT"));