Code Monkey home page Code Monkey logo

cascap.apis.googlephotos's Introduction

CasCap.Apis.GooglePhotos

Unofficial Google Photos Library API wrapper library for .NET applications

CI Coverage Status SonarCloud Coverage Nuget

Want to save yourself some coding? See the preview release of GooglePhotosCli using this library...

This is an unofficial Google Photos REST API library targeting .NET 8.0.

Note: Older projects that require .NET Standard 2.0 please use version 1.x of this library.

If you find this library of use then please give it a thumbs-up by giving this repository a ⭐ ... 😉

If you wish to interact with your Google Photos media items/albums then there are official PHP and Java Client Libraries. However if you're looking for a comprehensive .NET library then you were out of luck... until now :)

The CasCap.Apis.GooglePhotos library wraps up all the available functionality of the Google Photos REST API in easy to use methods.

Note: Before you jump in and use this library you should be aware that the Google Photos Library API has some key limitations. The biggest of these is that the API only allows the upload/addition of images/videos to the library, no edits or deletion are possible and have to be done manually via https://photos.google.com.

Google Photos API Set-up

When you create your photos application, you must first create an OAuth login details using the Google API Console and retrieve a Client ID and a Client Secret.

Using your Google Account the steps are*;

  1. Visit Google API Console
  2. Select 'Library' on the main menu;
    • Search for 'Photos Library API', select it from the results and hit the Enable button.
  3. Select 'Credentials' on the main menu;
    • Select 'Create Credentials' on the sub menu and pick 'OAuth client ID'
    • Select 'Desktop' as the application type.
    • Enter a suitable application name and hit the Create button.
    • Copy/save the Client ID and Client Secret which are then displayed you will use these to authenticate with the GooglePhotosService.

*Note: the above instructions are correct as of 2022-04-06.

Library Configuration/Usage

Install the package into your project using NuGet (see details here).

For .NET Core applications using dependency injection the primary API usage is to call IServiceCollection.AddGooglePhotos in the Startup.cs ConfigureServices method.

//Startup.cs
using Microsoft.Extensions.DependencyInjection;

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddGooglePhotos();
    }
}

There are 4 mandatory configuration options that must be passed;

  • User (your email address)
  • Google Client ID
  • Google Client Secret
  • Authorisation Scopes

There are 5 possible authorisation scopes which designate the level of access you wish to give, these scopes can be combined if required;

  • ReadOnly
  • AppendOnly
  • AppCreatedData
  • Access
  • Sharing

Best practise is to assign the lowest access level possible to meet your requirements. If you wish to go against best practise and give your application unfettered access to your media collection then use Access and Sharing scopes combined.

The recommended method of setting these mandatory options is via the appsettings.json file;

// appsettings.json
{
    ...
    "CasCap": {
        "GooglePhotosOptions": {
            // This is the email address of the Google Account.
            "User": "[email protected]",

            // There are 5 security scopes, which can be combined.
            "Scopes": [
                "ReadOnly"
                //"AppendOnly",
                //"AppCreatedData",
                //"Access",
                //"Sharing"
                ],

            // The ClientId and ClientSecret are provided by the Google Console after you register your own application.
            "ClientId": "012345678901-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.apps.googleusercontent.com",

            "ClientSecret": "abcabcabcabcabcabcabcabc",
        }
    }
    ...
}

Alternatively you can pass the options into the AddGooglePhotos method;

//Startup.cs
using Microsoft.Extensions.DependencyInjection;

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddGooglePhotos(options =>
        {
            options.User = "[email protected]";//replace with **your** info
            options.ClientId = "012345678901-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.apps.googleusercontent.com";//replace with **your** info
            options.ClientSecret = "abcabcabcabcabcabcabcabc";//replace with **your** info
            options.Scopes = new[] { GooglePhotosScope.ReadOnly };
        });
    }
}

Using appsettings.json is generally the best option however remember the Client ID & Client Secret should be stored securely outside of source control via Azure KeyVault (or .NET Secret Manager as shown below).

dotnet user-secrets init
dotnet user-secrets set "CasCap:GooglePhotosOptions:User" "[email protected]" #replace with **your** info
dotnet user-secrets set "CasCap:GooglePhotosOptions:ClientId" "012345678901-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.apps.googleusercontent.com" #replace with **your** info
dotnet user-secrets set "CasCap:GooglePhotosOptions:ClientSecret" "abcabcabcabcabcabcabcabc" #replace with **your** info

After calling AddGooglePhotos in the ConfigureServices method of Startup.cs you can then call upon the GooglePhotosService within your own services shown below.

using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;
namespace CasCap.Services
{
    public class MyPhotoService
    {
        readonly ILogger _logger;
        readonly GooglePhotosService _googlePhotosSvc;

        public MyPhotoService(ILogger<MyPhotoService> logger, GooglePhotosService googlePhotosSvc)
        {
            _logger = logger;
            _googlePhotosSvc = googlePhotosSvc;
        }

        public async Task Login_And_List_Albums()
        {
            if (!await _googlePhotosSvc.LoginAsync())
                throw new Exception($"login failed");

            var albums = await _googlePhotosSvc.GetAlbums();
            foreach (var album in albums)
            {
                _logger.LogInfo($"{album.id}\t{album.title}");
            }
        }
    }
}

If you don't use dependency injection you can new-up the GooglePhotosService manually and pass the mandatory configuration options, logger and HttpClient via the service constructor;

//MyPhotosClass.cs
using CasCap.Models;
using CasCap.Services;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;

public class MyPhotosClass
{
    public async Task Login_And_List_Albums()
    {
        //new-up logging
        var logger = new LoggerFactory().CreateLogger<GooglePhotosService>();

        //new-up configuration options
        var options = new GooglePhotosOptions
        {
            User = "[email protected]",//replace with **your** info
            ClientId = "012345678901-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.apps.googleusercontent.com",//replace with **your** info
            ClientSecret = "abcabcabcabcabcabcabcabc",//replace with **your** info
            Scopes = new[] { GooglePhotosScope.ReadOnly },
        };

        //new-up a single HttpClient
        var handler = new HttpClientHandler { AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate };
        var client = new HttpClient(handler) { BaseAddress = new Uri(options.BaseAddress) };

        //new-up the GooglePhotosService and pass in the logger, options and HttpClient
        var googlePhotosSvc = new GooglePhotosService(logger, Options.Create(options), client);

        //attempt to log-in
        if (!await googlePhotosSvc.LoginAsync())
            throw new Exception($"login failed!");

        //get and list all albums
        var albums = await googlePhotosSvc.GetAlbums();
        foreach (var album in albums)
        {
            Console.WriteLine(album.title);
        }
    }
}

Misc

Changing Users/Scopes

The Google.Apis.Auth library will cache the OAuth 2.0 login information in a local JSON file which it will then read tokens from (and renew if necessary) on subsequent logins. The JSON file(s) are stored on a per-User basis in the Environment.SpecialFolder.ApplicationData folder. On Windows 10 this folder is located at;

  • %UserProfile%\AppData\Roaming\Google.Apis.Auth

If you change the authentication scopes for a User you must delete the JSON file and allow the Google.Apis.Auth library to re-auth and re-create a new JSON file with the new scopes.

You can change the location where these JSON token files are stored at using the FileDataStoreFullPathOverride property in the configuration options;

//Startup.cs
using Microsoft.Extensions.DependencyInjection;

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddGooglePhotos(options =>
        {
            options.User = "[email protected]";
            options.Scopes = new[] { GooglePhotosScope.ReadOnly };
            options.ClientId = "012345678901-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.apps.googleusercontent.com";
            options.ClientSecret = "abcabcabcabcabcabcabcabc";
            //change FileDataStoreFullPathOverride
            options.FileDataStoreFullPathOverride = "c:/temp/GooglePhotos/"
        });
    }
}

Sample Projects

All API functions are exposed by the GooglePhotosService class. There are several sample .NET Core applications which show the basics on how to set-up/config/use the library;

Core Dependencies

Misc Tips

  • The NuGet package includes SourceLink which enables you to jump inside the library and debug the API yourself. By default Visual Studio 2017/2019 does not allow this and will pop up an message "You are debugging a Release build of...", to disable this message go into the Visual Studio debugging options and un-check the 'Just My Code' option (menu path, Tools > Options > Debugging).

Resources

Feedback/Issues

Please post any issues or feedback here.

License

CasCap.Apis.GooglePhotos is Copyright © 2020 Alex Vincent under the MIT license.

cascap.apis.googlephotos's People

Contributors

dependabot-preview[bot] avatar f2calv avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

cascap.apis.googlephotos's Issues

Feature Request: Limit Pages to Get

Hi, would be great to add optional parameter MaxPages to GetMediaItemsAsync. So get stops requesting after defined number of pages. The same could be reached by using the cancel token. But Cancel token would be for "surprising" user cancellation while max pages would be for deterministic stop.

Use case would be if you pull the whole MediaList you might get thousands of pages a) this can take very long and b) if you want to show them until pictures are downloaded download link has expired.

You are already tracking page numbers. So just add it to condition a la
var pageNumber = 1;
while (pageToken is object && !cancellationToken.IsCancellationRequested && maxPageNumber <= pageNumber)

How to find the owner of each media item? Otherwise is it possible to load only media items owned by user in shared album?

Is it possible to know the owner of each media item, e.g. loaded by GetMediaItemsByAlbumAsync method, in Google Photos?

I mean, is there a property in some class?

I've found the property contributorInfo in class MediaItem, but, as written in summary, that field is only populated if the media item is in a shared album created by the app that use the library.

Do you confirm that is present only that property? Doesn't Google provide this useful information?

Otherwise, is it possible in method GetMediaItemsByAlbumAsync for a shared album to load only media items owned by the user used in the login?

I hope you can help me and thanks in advance.

How to find the lifetime of a login token.

I want to add a huge amount of photos (let's say 1000) which will takes a long time. After a while I get Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential error when calling any method on the library.

How do I find out when the OAuth access token is expiring?

P.S. My current workaround is to basically new up GooglePhotosService. Is there a better way?

Unable to upload files bigger than 2 GB in ResumableMultipart mode

Uploading files with sizes more than 2 GB fails in ResumableMultipart mode with JsonReaderException:

Error parsing Infinity value. Path '', line 1, position 2.

The reason is that variable offset in the method GooglePhotosServiceBase.UploadMediaAsync is Int32. As a result when loading files with sizes greater than Int32.MaxValue, offset overflows to a negative value passed to the HTTP header.

The solution would be to change the type of offset variable to Int64:

var offset = 0L;

DownloadBytes does not preserve exif data

It would be nice if the DownloadBytes would preserve the exif data, it currently does not, This can be accomplished by adding the "ip" parameter to the url you are creating to download the media item. (Found the info here https://gist.github.com/Sauerstoffdioxid/2a0206da9f44dde1fdfce290f38d2703)

async Task<byte[]?> DownloadBytes(string baseUrl, int? maxWidth = null, int? maxHeight = null, bool crop = false, bool downloadPhoto = false, bool downloadVideo = false)

library testing

@k0qed I'm looking for testers for this new Google Photos .NET Library, are you interested?

MimeType support

Hello,

Thanks for the excellent .net library.

Try to upload the RW2 file which is supported by google photo but get System.NotSupportedException.

System.NotSupportedException: 'Cannot match file extension '.RW2' from 'd:/temp/GooglePhotos/P1000195.RW2' to a known image or video mime type.'

The AcceptedMimeTypesImage needs to include a more supported format.

    static readonly HashSet<string> AcceptedMimeTypesImage = new(StringComparer.OrdinalIgnoreCase)
    {
        { "image/bmp" },
        { "image/gif" },
        { "image/heic" },
        { "image/vnd.microsoft.icon" },
        { "image/jpeg" },
        { "image/jpeg" },
        { "image/png" },
        { "image/tiff" },
        { "image/webp" },
    };

No permission to add media items to this album - When trying to upload to existing Album

Hello,

Thanks a lot for the .Net Wrapper :) .

I recently came across an issue when trying to upload photos to existing google albums(created from Google Photos web UI/elsewhere) . I keep getting the following error

No permission to add media items to this album

Even when the following permissions are granted in the Google security section

  • Add to your Google Photos library
  • View your Google Photos library

I'm using version 1.1.2 of CasCap.Apis.GooglePhotos

Please advice

If Photos Library API is not enabled, some requests succeed with zero results.

If you have not enabled the Photos Library API permission on your google app, the request fails with a 403, but the library ignores the error and just returns zero results.

Sample result from Fiddler.

HTTP/1.1 403 Forbidden
Content-Type: application/json; charset=UTF-8
Date: Mon, 27 Jul 2020 00:41:39 GMT

{
  "error": {
    "code": 403,
    "message": "Photos Library API has not been used in project XXX before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/photoslibrary.googleapis.com/overview?project=XXX then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.",
    "status": "PERMISSION_DENIED",
    "details": [
      {
        "@type": "type.googleapis.com/google.rpc.Help",
        "links": [
          {
            "description": "Google developers console API activation",
            "url": "https://console.developers.google.com/apis/api/photoslibrary.googleapis.com/overview?project=XXX"
          }
        ]
      }
    ]
  }
}

Note: I only verified this behavior with GetAlbumsAsync(...).

Non Compatibilty with .Net Framework 4.5

Thanks a lot for your effort in creating a proper library for C#. Google API Documentation does not have any examples on consuming the their REST API in C#.

I'm trying to use your library in one of my Winforms project that targets .net Framework 4.5. Installing from nuget does not seem to work since there is some compatibility issues with .Net 5. I have tried manually adding the DLLs but still there are some reference issues. Is there an official way to make this work with .Net Framework 4.5 ?

Filter by "Camera" or "Partner"

I would like to be able to use the API to filter my media, but haven't figured out the appropriate filters. The two filters I'm looking for are:

  • media from my phone/camera (essentially what's in my phone's "Gallery" app)
  • media from my partner's phone/camera (a special shared folder that, for some reason, doesn't show up on the list of shared folders)

Those are easy enough to do in the Google Photos app or web site, but I can't figure out how to do that via the API. Any/all help appreciated.

(Note: I've asked this question on Stack Overflow as well. I'm not 100% certain which is the better place for it.)

Uploading of files with names containing non-English characters is not supported

Sometimes when uploading files with names that contain non-English characters fails.
For example, uploading of file a "D:\2003.02.06 - (Открытый) Урок английского.jpg" fails and the server returns an HTML page

<title>Error 400 (Bad Request)!!1</title>

which eventually results in Newtonsoft.Json.JsonReaderException

Unexpected character encountered while parsing value: <. Path '', line 0, position 0.

The reason for this is the HTTP standard that doesn't support non-ISO-8859-1 characters directly in an HTTP header. Thus characters should be encoded to be passed in the header.

Suggested fix: use HttpUtility.UrlPathEncode in GooglePhotosServiceBase.UploadMediaAsync:

headers.Add((X_Goog_Upload_File_Name, HttpUtility.UrlPathEncode(Path.GetFileName(path))));

Paging

I have a big library with 10k+ items. When I run _googlePhotosSvc.GetMediaItemsByAlbumAsync(album.id, 25); It take long time...hangs. What am I doing wrong since paging is not working for me. Could you please help? Also, is there a easy way to get filenames? Thanks.

Getting "Request contains an invalid media item id" error when adding photo to an album.

I am trying to do the following:

  1. Create an album.
  2. Add existing photos to that album.

So I do the following:

// boilerplate to connect to google api

List<MediaItem> mediaItems = await googlePhotosSvc.GetMediaItemsAsync();
Album album= await googlePhotosSvc.CreateAlbumAsync(albumName);

foreach (var item in mediaItems) {
    bool success = await googlePhotosSvc.AddMediaItemsToAlbumAsync(album.id, new string[] { item.id });
}

It blows up on googlePhotosSvc.AddMediaItemsToAlbumAsync with Request contains an invalid media item id.

I've played around with adding scopes and at this point I have GooglePhotosScope.AppendOnly, GooglePhotosScope.ReadOnly, GooglePhotosScope.AppCreatedData, GooglePhotosScope.Sharing. But I am not sure what exactly is the problem. I've also tried adding photos in batches, but I get the same error.

The API call looks like this in Fiddler:

POST /v1/albums/ALvc__nJoKD2Rfh9rav5sQ7U9lm5ClQ3F_tVQ-mBANpi0pjGMfs5wn8eJ1gD2ibjQNbLsD3SHeI3:batchAddMediaItems HTTP/1.1

with the body of:

{"mediaItemIds":["ALvc__kuVUBPmBfk8AkGiXLrvxLj1tOdkGaoMtp0AmPqEbcP-hI13wILoiFpCzUXc9xe6zk2eMnsTGry-3SaS4znQZa_qFQwxQ"]}

And it returns

{
  "error": {
    "code": 400,
    "message": "Request contains an invalid media item id.",
    "status": "INVALID_ARGUMENT"
  }
}

What am I missing?

GetMediaItemsAsync JSON Error: 'Could not convert string to double: 0.050s' for 'mediaItems[0].mediaMetadata.photo.exposureTime'

Could not convert string to double: 0.050s. Path 'mediaItems[0].mediaMetadata.photo.exposureTime', line 18, position 34. | Newtonsoft.Json.JsonReaderException: Could not convert string to double: 0.050s. Path 'mediaItems[0].mediaMetadata.photo.exposureTime', line 18, position 34.
at Newtonsoft.Json.JsonReader.ReadDoubleString(String s)
at Newtonsoft.Json.JsonTextReader.FinishReadQuotedNumber(ReadType readType)
at Newtonsoft.Json.JsonTextReader.ReadAsDouble()
at Newtonsoft.Json.JsonReader.ReadForType(JsonContract contract, Boolean hasConverter)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.SetPropertyValue(JsonProperty property, JsonConverter propertyConverter, JsonContainerContract containerContract, JsonProperty containerProperty, JsonReader reader, Object target)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.SetPropertyValue(JsonProperty property, JsonConverter propertyConverter, JsonContainerContract containerContract, JsonProperty containerProperty, JsonReader reader, Object target)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateList(IList list, JsonReader reader, JsonArrayContract contract, JsonProperty containerProperty, String id)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateList(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, Object existingValue, String id)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.SetPropertyValue(JsonProperty property, JsonConverter propertyConverter, JsonContainerContract containerContract, JsonProperty containerProperty, JsonReader reader, Object target)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent)
at Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType)
at Newtonsoft.Json.JsonSerializer.Deserialize(JsonReader reader, Type objectType)
at Newtonsoft.Json.JsonConvert.DeserializeObject(String value, Type type, JsonSerializerSettings settings)
at Newtonsoft.Json.JsonConvert.DeserializeObject[T](String value, JsonSerializerSettings settings)
at Newtonsoft.Json.JsonConvert.DeserializeObject[T](String value)
at CasCap.Common.Extensions.Helpers.FromJSON[T](String json)
at CasCap.Services.HttpClientBase.HandleResult[TResult,TError](HttpResponseMessage response)
at CasCap.Services.HttpClientBase.Get[TResult,TError](String requestUri, Nullable1 timeout, List1 headers)
at CasCap.Services.GooglePhotosServiceBase._GetMediaItemsAsync(Int32 pageSize, Int32 maxPageCount, Boolean excludeNonAppCreatedData, String requestUri, CancellationToken cancellationToken)

File size by day and month

Hi

would it be possible to built an app with this library, that would calculate storage usage per Month and possibly per day for all the photos stored in the account?

something like 'du -sh *' in bash

How to upload a "live" photo?

"Live Photo" is an iPhone construct where the camera records 2 seconds of video and using some algorithm picks the best frame and that is what displays on phone's Photos app. When you long tap the photo, the entire 2 second video plays.

When Google Photos app backs up this "Live Photo", it shows up as a photo on the website, however, it has a "Turn on motion" button, that allows you to play the entire video, just like on the phone.

image

I have this "Live Photo" file on my desktop and Google Photos exports it as an mp4. However, when I upload the photo using _googlePhotosSvc.UploadMediaAsync and _googlePhotosSvc.AddMediaItemAsync combo, it just shows up as another video.

Is there a way to tell Google Photos that it's a "Live Photo"?

Doesn't work in UWP

ConsoleApp sample code works fine as a .NET Core 3.1 console app.
Same code in an UWP app crashes deep in LoginAsync, seems async-related trying to access a disposed Socket object.
I could post a project to reproduce but you already have the code, it's a pretty hard crash and burn.
Let me know if you have questions.
Lars

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.