Track pending assets for delayed processing events
All checks were successful
Validate / Hassfest (push) Successful in 3s
All checks were successful
Validate / Hassfest (push) Successful in 3s
- Add _pending_asset_ids to track assets detected but not yet processed - Fire events when pending assets become processed (thumbhash available) - Fixes issue where videos added during transcoding never triggered events - Add debug logging for change detection and pending asset tracking - Document external domain feature in README Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
23
README.md
23
README.md
@@ -79,6 +79,29 @@ A Home Assistant custom integration that monitors [Immich](https://immich.app/)
|
|||||||
| Telegram Bot Token | Bot token for sending media to Telegram (optional) | - |
|
| Telegram Bot Token | Bot token for sending media to Telegram (optional) | - |
|
||||||
| Telegram Cache TTL | How long to cache uploaded file IDs (hours, 1-168) | 48 |
|
| Telegram Cache TTL | How long to cache uploaded file IDs (hours, 1-168) | 48 |
|
||||||
|
|
||||||
|
### External Domain Support
|
||||||
|
|
||||||
|
The integration supports connecting to a local Immich server while using an external domain for user-facing URLs. This is useful when:
|
||||||
|
|
||||||
|
- Your Home Assistant connects to Immich via local network (e.g., `http://192.168.1.100:2283`)
|
||||||
|
- But you want share links and asset URLs to use your public domain (e.g., `https://photos.example.com`)
|
||||||
|
|
||||||
|
**How it works:**
|
||||||
|
|
||||||
|
1. Configure "External domain" in Immich: **Administration → Settings → Server → External Domain**
|
||||||
|
2. The integration automatically fetches this setting on startup
|
||||||
|
3. All user-facing URLs (share links, asset URLs in events) use the external domain
|
||||||
|
4. API calls and file downloads still use the local connection URL for faster performance
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
- Server URL (in integration config): `http://192.168.1.100:2283`
|
||||||
|
- External Domain (in Immich settings): `https://photos.example.com`
|
||||||
|
- Share links in events: `https://photos.example.com/share/...`
|
||||||
|
- Telegram downloads: via `http://192.168.1.100:2283` (fast local network)
|
||||||
|
|
||||||
|
If no external domain is configured in Immich, all URLs will use the Server URL from the integration configuration.
|
||||||
|
|
||||||
## Entities Created (per album)
|
## Entities Created (per album)
|
||||||
|
|
||||||
| Entity Type | Name | Description |
|
| Entity Type | Name | Description |
|
||||||
|
|||||||
@@ -204,19 +204,39 @@ class AssetInfo:
|
|||||||
Returns:
|
Returns:
|
||||||
True if asset is fully processed and not trashed/offline, False otherwise
|
True if asset is fully processed and not trashed/offline, False otherwise
|
||||||
"""
|
"""
|
||||||
|
asset_id = data.get("id", "unknown")
|
||||||
|
asset_type = data.get("type", "unknown")
|
||||||
|
is_offline = data.get("isOffline", False)
|
||||||
|
is_trashed = data.get("isTrashed", False)
|
||||||
|
thumbhash = data.get("thumbhash")
|
||||||
|
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Asset %s (%s): isOffline=%s, isTrashed=%s, thumbhash=%s",
|
||||||
|
asset_id,
|
||||||
|
asset_type,
|
||||||
|
is_offline,
|
||||||
|
is_trashed,
|
||||||
|
bool(thumbhash),
|
||||||
|
)
|
||||||
|
|
||||||
# Exclude offline assets
|
# Exclude offline assets
|
||||||
if data.get("isOffline", False):
|
if is_offline:
|
||||||
|
_LOGGER.debug("Asset %s excluded: offline", asset_id)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Exclude trashed assets
|
# Exclude trashed assets
|
||||||
if data.get("isTrashed", False):
|
if is_trashed:
|
||||||
|
_LOGGER.debug("Asset %s excluded: trashed", asset_id)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Check if thumbnails have been generated
|
# Check if thumbnails have been generated
|
||||||
# This works for both photos and videos - Immich always generates thumbnails
|
# This works for both photos and videos - Immich always generates thumbnails
|
||||||
# Note: The API doesn't expose video transcoding status (encodedVideoPath),
|
# Note: The API doesn't expose video transcoding status (encodedVideoPath),
|
||||||
# but thumbhash is sufficient since Immich generates thumbnails for all assets
|
# but thumbhash is sufficient since Immich generates thumbnails for all assets
|
||||||
return bool(data.get("thumbhash"))
|
is_processed = bool(thumbhash)
|
||||||
|
if not is_processed:
|
||||||
|
_LOGGER.debug("Asset %s excluded: no thumbhash", asset_id)
|
||||||
|
return is_processed
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -331,6 +351,7 @@ class ImmichAlbumWatcherCoordinator(DataUpdateCoordinator[AlbumData | None]):
|
|||||||
self._telegram_cache = telegram_cache
|
self._telegram_cache = telegram_cache
|
||||||
self._persisted_asset_ids: set[str] | None = None
|
self._persisted_asset_ids: set[str] | None = None
|
||||||
self._external_domain: str | None = None # Fetched from server config
|
self._external_domain: str | None = None # Fetched from server config
|
||||||
|
self._pending_asset_ids: set[str] = set() # Assets detected but not yet processed
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def immich_url(self) -> str:
|
def immich_url(self) -> str:
|
||||||
@@ -824,11 +845,16 @@ class ImmichAlbumWatcherCoordinator(DataUpdateCoordinator[AlbumData | None]):
|
|||||||
elif removed_ids and not added_ids:
|
elif removed_ids and not added_ids:
|
||||||
change_type = "assets_removed"
|
change_type = "assets_removed"
|
||||||
|
|
||||||
added_assets = [
|
added_assets = []
|
||||||
album.assets[aid]
|
for aid in added_ids:
|
||||||
for aid in added_ids
|
if aid not in album.assets:
|
||||||
if aid in album.assets
|
continue
|
||||||
]
|
asset = album.assets[aid]
|
||||||
|
if asset.is_processed:
|
||||||
|
added_assets.append(asset)
|
||||||
|
else:
|
||||||
|
# Track unprocessed assets for later
|
||||||
|
self._pending_asset_ids.add(aid)
|
||||||
|
|
||||||
change = AlbumChange(
|
change = AlbumChange(
|
||||||
album_id=album.id,
|
album_id=album.id,
|
||||||
@@ -885,12 +911,54 @@ class ImmichAlbumWatcherCoordinator(DataUpdateCoordinator[AlbumData | None]):
|
|||||||
added_ids = new_state.asset_ids - old_state.asset_ids
|
added_ids = new_state.asset_ids - old_state.asset_ids
|
||||||
removed_ids = old_state.asset_ids - new_state.asset_ids
|
removed_ids = old_state.asset_ids - new_state.asset_ids
|
||||||
|
|
||||||
# Only include fully processed assets in added_assets
|
_LOGGER.debug(
|
||||||
added_assets = [
|
"Change detection: added_ids=%d, removed_ids=%d, pending=%d",
|
||||||
new_state.assets[aid]
|
len(added_ids),
|
||||||
for aid in added_ids
|
len(removed_ids),
|
||||||
if aid in new_state.assets and new_state.assets[aid].is_processed
|
len(self._pending_asset_ids),
|
||||||
]
|
)
|
||||||
|
|
||||||
|
# Track new unprocessed assets and collect processed ones
|
||||||
|
added_assets = []
|
||||||
|
for aid in added_ids:
|
||||||
|
if aid not in new_state.assets:
|
||||||
|
_LOGGER.debug("Asset %s: not in assets dict", aid)
|
||||||
|
continue
|
||||||
|
asset = new_state.assets[aid]
|
||||||
|
_LOGGER.debug(
|
||||||
|
"New asset %s (%s): is_processed=%s, filename=%s",
|
||||||
|
aid,
|
||||||
|
asset.type,
|
||||||
|
asset.is_processed,
|
||||||
|
asset.filename,
|
||||||
|
)
|
||||||
|
if asset.is_processed:
|
||||||
|
added_assets.append(asset)
|
||||||
|
else:
|
||||||
|
# Track unprocessed assets for later
|
||||||
|
self._pending_asset_ids.add(aid)
|
||||||
|
_LOGGER.debug("Asset %s added to pending (not yet processed)", aid)
|
||||||
|
|
||||||
|
# Check if any pending assets are now processed
|
||||||
|
newly_processed = []
|
||||||
|
for aid in list(self._pending_asset_ids):
|
||||||
|
if aid not in new_state.assets:
|
||||||
|
# Asset was removed, no longer pending
|
||||||
|
self._pending_asset_ids.discard(aid)
|
||||||
|
continue
|
||||||
|
asset = new_state.assets[aid]
|
||||||
|
if asset.is_processed:
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Pending asset %s (%s) is now processed: filename=%s",
|
||||||
|
aid,
|
||||||
|
asset.type,
|
||||||
|
asset.filename,
|
||||||
|
)
|
||||||
|
newly_processed.append(asset)
|
||||||
|
self._pending_asset_ids.discard(aid)
|
||||||
|
|
||||||
|
# Include newly processed pending assets
|
||||||
|
added_assets.extend(newly_processed)
|
||||||
|
|
||||||
# Detect metadata changes
|
# Detect metadata changes
|
||||||
name_changed = old_state.name != new_state.name
|
name_changed = old_state.name != new_state.name
|
||||||
|
|||||||
Reference in New Issue
Block a user