Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Google Photos integration #124835

Merged
merged 9 commits into from
Aug 30, 2024
Merged

Conversation

allenporter
Copy link
Contributor

@allenporter allenporter commented Aug 29, 2024

Proposed change

Add Google Photos integration with an initial media source platform. The main parts of this integration have been copied from Google Tasks (config flow, client library, etc) with minor modernization updates.

Milestones for future PRs include:

  • Display recent photos in a media source
  • Config flow reauth
  • Uploading content
  • Display favorites as a folder in the media source
  • Display albums as folders in the media source
  • Display shared albums in the media source

Currently this requests scopes for read access to the entire library. As more features are added above, users can select which scopes they want for giving access to home assistant and the integration will respect those choices.

In the future we can consider expanding media sources to support paging and search to make them more useful for this integration.

---------- coverage: platform linux, python 3.12.2-final-0 -----------
Name                                                                Stmts   Miss  Cover   Missing
-------------------------------------------------------------------------------------------------
homeassistant/components/google_photos/__init__.py                     27      0   100%
homeassistant/components/google_photos/api.py                          64      0   100%
homeassistant/components/google_photos/application_credentials.py       7      1    86%   19
homeassistant/components/google_photos/config_flow.py                  30      0   100%
homeassistant/components/google_photos/const.py                         4      0   100%
homeassistant/components/google_photos/exceptions.py                    2      0   100%
homeassistant/components/google_photos/media_source.py                107      0   100%
-------------------------------------------------------------------------------------------------
TOTAL                                                                 241      1    99%


Results (2.68s):
      18 passed

Screenshot 2024-08-28 at 5 31 30 PM

Type of change

  • Dependency upgrade
  • Bugfix (non-breaking change which fixes an issue)
  • New integration (thank you!)
  • New feature (which adds functionality to an existing integration)
  • Deprecation (breaking change to happen in the future)
  • Breaking change (fix/feature causing existing functionality to break)
  • Code quality improvements to existing code or addition of tests

Additional information

Checklist

  • The code change is tested and works locally.
  • Local tests pass. Your PR cannot be merged unless tests pass
  • There is no commented out code in this PR.
  • I have followed the development checklist
  • I have followed the perfect PR recommendations
  • The code has been formatted using Ruff (ruff format homeassistant tests)
  • Tests have been added to verify that the new code works.

If user exposed functionality or configuration variables are added/changed:

If the code communicates with devices, web services, or third-party tools:

  • The manifest file has all fields filled out correctly.
    Updated and included derived files by running: python3 -m script.hassfest.
  • New or updated dependencies have been added to requirements_all.txt.
    Updated by running python3 -m script.gen_requirements_all.
  • For the updated dependencies - a link to the changelog, or at minimum a diff between library versions is added to the PR description.

To help with the load of incoming pull requests:

homeassistant/components/google_photos/__init__.py Outdated Show resolved Hide resolved
homeassistant/components/google_photos/__init__.py Outdated Show resolved Hide resolved
homeassistant/components/google_photos/manifest.json Outdated Show resolved Hide resolved
homeassistant/components/google_photos/media_source.py Outdated Show resolved Hide resolved
homeassistant/components/google_photos/media_source.py Outdated Show resolved Hide resolved
Comment on lines +72 to +74
with patch(
"homeassistant.components.google_photos.async_setup_entry", return_value=True
) as mock_setup:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Create a generic fixture for this because you have more places where you will use this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only currently used once so I avoided adding a fixture. How about adding when it's needed in future PRs? Unless you're saying it's needed in more cases in this PR.

tests/components/google_photos/test_config_flow.py Outdated Show resolved Hide resolved
tests/components/google_photos/test_config_flow.py Outdated Show resolved Hide resolved
tests/components/google_photos/test_init.py Outdated Show resolved Hide resolved
@home-assistant
Copy link

Please take a look at the requested changes, and use the Ready for review button when you are done, thanks 👍

Learn more about our pull request process.

@home-assistant home-assistant bot marked this pull request as draft August 30, 2024 09:19
@allenporter allenporter marked this pull request as ready for review August 30, 2024 14:23
@home-assistant home-assistant bot requested a review from joostlek August 30, 2024 14:23
@allenporter allenporter merged commit c01bb44 into home-assistant:dev Aug 30, 2024
36 checks passed
@allenporter
Copy link
Contributor Author

Thank you for the speedy high quality review

homeassistant/components/google_photos/manifest.json Outdated Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What use cases do you have in mind for media source?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The use cases I am thinking would be useful are:

  • display "favorites" or a shared album on a display
  • use as a generic way to read media content for automations e.g. for an automation like sending to google generative ai vision service

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there any actions that give access to media sources? AFAIK, they are only used in the frontend for manually browsing so the only use case right now that I can think of is to browse your library for casting a single item to a media player.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is discussion here home-assistant/architecture#1079 so -- fair -- a future use case.

The upload service can return a media id in the service response, though, which could be used to form a media source identifier. The first half of this is a real use case, but the second half is not really:

  • "Ok nabu, take a photo" (based on context of the device i'm speaking from)
  • Automation calls camera snapshot
  • Automation calls google_photos.upload with the snapshot filename, which returns a media id
  • Automation calls a display automation with the media id to show it somewhere

I may play around in a custom component as well, possibly exposing something like the service mentioned above, or code doing generally processing on media sources. I was thinking if there were useful integrations to call google_generative_ai_conversation.generate_content with a media source, but maybe it doesn't make sense in core until there are more actions/hooks.


# Currently supports a single album of recent photos
RECENT_PHOTOS_ALBUM = "recent"
RECENT_PHOTOS_TITLE = "Recent Photos"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this to strings.json for localization?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can see how to do a one-off for google photos translations, however that doesn't appear to be a common pattern. I think if we want to support this it would make sense to do for all media sources. I spent about 45 minutes looking at what it might take to do this and am pausing as it looks quite involved given the current media player APIs. If there is a simple common pattern here i'm missing happy follow it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just looked at it too. It doesn't seem it's possible without a hack such as adding the string as an exception. Maybe introduce a generic strings section in strings.json and add a function similar to async_get_exception_message in helpers.translation? The functions could share most of the code. Do you think you could open a design discussion?

# Media Sources do not support paging, so we only show a subset of recent
# photos when displaying the users library. We fetch a minimum of 50 photos
# unless we run out, but in pages of 100 at a time given sometimes responses
# may only contain a handful of items Fetches at least 50 photos.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment and constant name are confusing. I had to look at the code below to understand. Can you rename the constant and rephrase the comment? Or change the code below to truly have max 50 photos?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What i'm trying to hack around is if we set PAGE_SIZE = 50 you can get back like 10 photos (presumably due to server side filtering) and as a result spend a bunch of time on server side round trips. Given that context, have a suggestion for naming?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I understand the potential server side filtering that might return less photos than requested in the page.
But there is a punctuation missing in the comment, I think a period before "Fetches at least 50 photos", and it's confusing having MAX_ in the constant (and now max_photos in SpecialAlbumDetails) when it's actually MIN_, but only if you have that many photos in your library, otherwise it's a MAX_. Unfortunately I don't have a good suggestion.

"""
width = media_item["mediaMetadata"]["width"]
height = media_item["mediaMetadata"]["height"]
key = "h" if height > width else "w"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make more sense to always have "h" here? If the image is shown fullscreen in a 4k screen we want max h2160. Similarly I assume the HA frontend when showing the grid of thumbnails caps the height? Depending on what resolution of thumbnails the HA frontend uses, maybe use both w and h? And maybe c too?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did have a version setting h and w and couldn't convince myself what the right behavior is. I've updated to "h" since I agree the height is often what matters. For the full image, just setting it to be largeish in case someone wants a large image outside of the media player.

I can't convince myself what the right behavior here is or that c should be used.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think just h is better than longest edge, at least for the full screen case. I haven't tried this integration yet but HA frontend in the grid for other images seems to already do some cropping by centering the image. That's why I think ideally for thumbnails we would specify w, h, and c that match what HA frontend does.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I trust your judgement here from your past experience optimizing photos on displays :) I'll experiment with it. I didn't notice the cropping until i saw some recent photos and they are cropped poorly (faces at top of the photo cut off -- didn't see if this is smart crop or not but it if is that would be useful)

media_class=MediaClass.IMAGE if not is_video else MediaClass.VIDEO,
media_content_type=MediaType.IMAGE if not is_video else MediaType.VIDEO,
title=media_item["filename"],
can_play=is_video,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be True for images too? I just tried the Media tab in Home Assistant and I was able to "play" an image entity (FullyKiosk screenshot image entity) on the browser by displaying it and also casting it by connecting it to a Nest Hub. Have you tried that playing images and videos works on media players?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll give it a try. I may not understand what 'can_play' means.

@github-actions github-actions bot locked and limited conversation to collaborators Sep 2, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants