-
Notifications
You must be signed in to change notification settings - Fork 10
External app name + icon metadata #60
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
Merged
Merged
Changes from all commits
Commits
Show all changes
53 commits
Select commit
Hold shift + click to select a range
5aabe05
adding in first draft of external metadata + cache, updating tv.py to…
thomasm789 fdf1209
replacing UC_CONFIG_HOME with UC_DATA_HOME
thomasm789 1134247
test - updating setupflow and pass param through at device level
thomasm789 16c75bf
update - removing if condition allowing the the external metadata and…
thomasm789 35a4f0d
continued attempt to get use_external_metadata working - now have it …
thomasm789 7a68253
removing unused method
thomasm789 c9f61db
updating logic to conformto albinators chromecast repo
thomasm789 dda7fc1
syntax correction
thomasm789 5367f7e
syntax correction
thomasm789 de18f0a
syntax corrections and debugging
thomasm789 484ce8f
debugging
thomasm789 36951e5
adding in additional app reference
thomasm789 e7af5e0
debugging
thomasm789 8cbabaf
debugging
thomasm789 7e4cc21
updates
thomasm789 c38ed4e
adding support for new external metadata json object and also support…
thomasm789 6884138
adding icon media_image_url support
thomasm789 fdf5b6a
big one! added in update to support all existing apps with icons and …
thomasm789 34688c1
isort
thomasm789 088e48f
Update README.md
thomasm789 dfa672f
updating priority logic
thomasm789 5338f8b
Update setup_flow.py
thomasm789 5d583da
linting
thomasm789 fac19d6
Update requirements.txt
thomasm789 bb473b1
applying feedback from PR
thomasm789 37a09a7
reverting back to files
thomasm789 488dea7
updates
thomasm789 a5d29ff
Merge branch 'main' into external-metadata
thomasm789 03edc0b
bug fix
thomasm789 a5840f9
updating android home media title
thomasm789 f9974ac
clean up
thomasm789 61d412a
linting
thomasm789 6469c3f
updating logic to support http urls and file uris and covert to base6…
thomasm789 9ceb657
updating logic to support http urls and file uris and covert to base6…
thomasm789 db431fb
adding in Marcus' additions
thomasm789 e6e3d9b
updating encode to base64
thomasm789 1b4a166
updating encode to base64
thomasm789 a978171
updating encode to base64
thomasm789 64360c4
filter media_image_url property in log messages
zehnm 5feddc0
file open issue
thomasm789 46ed72d
tweaks
thomasm789 dcdc7c5
setting some external defaults to show nice android tv logo
thomasm789 7900776
enable base64 img log filter
thomasm789 628a66a
updating metadata logic
thomasm789 3c8926a
updating text to setupflow
thomasm789 ccf272e
DRY
thomasm789 5f3b4a8
tweaks
thomasm789 9d28ed1
tweaks
thomasm789 df960c5
update
thomasm789 8d4f4c0
Merge branch 'main' into external-metadata
thomasm789 91dfadd
syntax fixes
thomasm789 d95ca33
Fix linting, remove unused variables
zehnm d4bda79
clean up
zehnm File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| { | ||
| "com.google.android.tvlauncher": { | ||
| "name": "Android TV Home", | ||
| "icon": "data/external_cache/icons/androidtv.png" | ||
| }, | ||
| "com.google.android.backdrop": { | ||
| "name": "Backdrop Daydream", | ||
| "icon": "data/external_cache/icons/androidtv.png" | ||
| } | ||
| } |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,182 @@ | ||
| """ | ||
| External metadata retrieval from Google Play. | ||
|
|
||
| :copyright: (c) 2025 by thomasm789, www.tmason.uk | ||
| :license: MPL-2.0, see LICENSE for more details. | ||
| """ | ||
|
|
||
| import base64 | ||
| import json | ||
| import logging | ||
| import os | ||
| from io import BytesIO | ||
| from pathlib import Path | ||
| from typing import Dict | ||
| from urllib.parse import urlparse | ||
|
|
||
| import google_play_scraper | ||
| import requests | ||
| from PIL import Image | ||
| from PIL.Image import Resampling | ||
| from pychromecast.controllers.media import MediaImage | ||
|
|
||
| _LOG = logging.getLogger(__name__) | ||
|
|
||
| CACHE_ROOT = "external_cache" | ||
| ICON_SUBDIR = "icons" | ||
| ICON_SIZE = (120, 120) | ||
|
|
||
|
|
||
| # Paths | ||
| def _get_cache_root() -> Path: | ||
| config_home = Path(os.environ.get("UC_DATA_HOME", "./data")) | ||
| cache_root = config_home / CACHE_ROOT | ||
| cache_root.mkdir(parents=True, exist_ok=True) | ||
| return cache_root | ||
|
|
||
|
|
||
| def _get_metadata_dir() -> Path: | ||
| metadata_dir = _get_cache_root() | ||
| metadata_dir.mkdir(parents=True, exist_ok=True) | ||
| return metadata_dir | ||
|
|
||
|
|
||
| def _get_icon_dir() -> Path: | ||
| icon_dir = _get_cache_root() / ICON_SUBDIR | ||
| icon_dir.mkdir(parents=True, exist_ok=True) | ||
| return icon_dir | ||
|
|
||
|
|
||
| def _get_metadata_file_path() -> Path: | ||
| return _get_metadata_dir() / "app_metadata.json" | ||
|
|
||
|
|
||
| def _get_icon_path(package_id: str) -> Path: | ||
| return _get_icon_dir() / f"{package_id}.png" | ||
|
|
||
|
|
||
| # Cache Management | ||
| def _load_cache() -> Dict[str, Dict[str, str]]: | ||
| path = _get_metadata_file_path() | ||
| if path.exists(): | ||
| try: | ||
| with open(path, "r", encoding="utf-8") as f: | ||
| return json.load(f) | ||
| except Exception: | ||
| return {} | ||
| return {} | ||
|
|
||
|
|
||
| def _save_cache(cache: Dict[str, Dict[str, str]]) -> None: | ||
| path = _get_metadata_file_path() | ||
| with open(path, "w", encoding="utf-8") as f: | ||
| json.dump(cache, f, indent=2) | ||
|
|
||
|
|
||
| # Metadata Fetch | ||
| def _download_and_resize_icon(url: str, package_id: str) -> str | None: | ||
| try: | ||
| response = requests.get(url, timeout=10) | ||
| response.raise_for_status() | ||
|
|
||
| img = Image.open(BytesIO(response.content)) | ||
| img = img.resize(ICON_SIZE, Resampling.LANCZOS) | ||
|
|
||
| icon_path = _get_icon_path(package_id) | ||
| img.save(icon_path, format="PNG") | ||
| return str(icon_path) | ||
| except Exception as e: | ||
| _LOG.warning("Failed to fetch icon for %s: %s", package_id, e) | ||
| return None | ||
|
|
||
|
|
||
| def encode_icon_to_data_uri(icon_path: str) -> str: | ||
| """ | ||
| Encode an image from a local file path or remote URL. | ||
|
|
||
| Returns a base64-encoded PNG data URI. | ||
| """ | ||
| if isinstance(icon_path, MediaImage): | ||
| icon_path = icon_path.url | ||
|
|
||
| # Already a base64 data URI | ||
| if isinstance(icon_path, str) and icon_path.startswith("data:image"): | ||
| return icon_path | ||
|
|
||
| try: | ||
| if _is_url(icon_path): | ||
| response = requests.get(icon_path, timeout=10) | ||
| response.raise_for_status() | ||
| img = Image.open(BytesIO(response.content)) | ||
| else: | ||
| with open(icon_path, "rb") as f: | ||
| img = Image.open(f) | ||
| img.load() # Ensure the image is fully loaded before the file is closed | ||
|
|
||
| img = img.convert("RGBA") | ||
|
|
||
| buffer = BytesIO() | ||
| img.save(buffer, format="PNG") | ||
| encoded = base64.b64encode(buffer.getvalue()).decode("utf-8") | ||
| return f"data:image/png;base64,{encoded}" | ||
|
|
||
| except Exception as e: | ||
| _LOG.warning("Failed to encode icon to base64 for %s: %s", icon_path, e) | ||
| return "" | ||
|
|
||
|
|
||
| def _is_url(path: str) -> bool: | ||
| parsed = urlparse(path) | ||
| return parsed.scheme in ("http", "https") | ||
|
|
||
|
|
||
| def _fetch_google_play_metadata(package_id: str) -> Dict[str, str] | None: | ||
| try: | ||
| app = google_play_scraper.app(package_id) | ||
|
|
||
| name = app["title"] | ||
| icon_url = app["icon"] | ||
| icon_path = _download_and_resize_icon(icon_url, package_id) | ||
|
|
||
| return {"name": name, "icon": icon_path or ""} | ||
|
|
||
| except Exception as e: | ||
| _LOG.warning("Google Play metadata fetch failed for %s: %s", package_id, e) | ||
| return None | ||
|
|
||
|
|
||
| def get_app_metadata(package_id: str) -> Dict[str, str]: | ||
| """ | ||
| Fetch metadata for a mobile application specified by the package ID. | ||
|
|
||
| The metadata includes the application name and its icon encoded as a data URI. | ||
| If metadata is found in a locally cached source, it is fetched from the cache. | ||
| Otherwise, metadata is retrieved from external sources such as Google Play. | ||
|
|
||
| :param package_id: The unique package identifier for the application. | ||
| :type package_id: str | ||
| :return: A dictionary containing the application's metadata. The dictionary | ||
| includes the 'name' of the application and the 'icon', which is the | ||
| application's icon encoded as a data URI. If no metadata is found, | ||
| it returns the package ID as the name and an empty string as the icon. | ||
| :rtype: Dict[str, str] | ||
| """ | ||
| cache = _load_cache() | ||
| if package_id in cache: | ||
| icon_path = cache[package_id].get("icon") | ||
| icon_data_uri = encode_icon_to_data_uri(icon_path) if icon_path else "" | ||
| return {"name": cache[package_id]["name"], "icon": icon_data_uri} | ||
|
|
||
| # Try Google Play | ||
| metadata = _fetch_google_play_metadata(package_id) | ||
| # if not metadata: | ||
| # Additional Fallback option for the future maybe APKPure or another source | ||
| # metadata = fetch_fallback_metadata(package_id) | ||
|
|
||
| if metadata: | ||
| cache[package_id] = metadata | ||
| _save_cache(cache) | ||
| icon_data_uri = encode_icon_to_data_uri(metadata["icon"]) if metadata["icon"] else "" | ||
| return {"name": metadata["name"], "icon": icon_data_uri} | ||
|
|
||
| return {"name": package_id, "icon": ""} | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.