feat: smart video size warnings + Jinja2 template autocomplete
Video size warnings:
- Add file_size field to ImmichAssetInfo from exifInfo.fileSizeInByte
- Expose per-target max_video_size (50 MB for Telegram, none for others)
- Compute has_oversized_videos and per-asset oversized flag in template context
- Default templates show warning only when videos actually exceed the limit
- Templates no longer hardcode Telegram-specific logic
Template autocomplete:
- New jinja-autocomplete.ts engine with contextual completions
- Top-level variables ({{ }}), asset/album fields (dot access in loops),
Jinja2 filters (|), block tags ({% %}), and loop.* special vars
- JinjaEditor accepts optional variables prop via CodeMirror Compartment
- Wired into template-configs and command-template-configs pages
Also: fix template emoji (📷 → 📎) and sync sample_context with new vars.
This commit is contained in:
@@ -43,6 +43,7 @@ def _asset_to_media(asset: ImmichAssetInfo, external_url: str) -> MediaAsset:
|
||||
"state": asset.state,
|
||||
"country": asset.country,
|
||||
"thumbhash": asset.thumbhash,
|
||||
"file_size": asset.file_size,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -76,6 +76,7 @@ class ImmichAssetInfo:
|
||||
city: str | None = None
|
||||
state: str | None = None
|
||||
country: str | None = None
|
||||
file_size: int | None = None # bytes, from exifInfo.fileSizeInByte
|
||||
is_processed: bool = True
|
||||
thumbhash: str | None = None
|
||||
|
||||
@@ -105,6 +106,7 @@ class ImmichAssetInfo:
|
||||
city = exif_info.get("city") if exif_info else None
|
||||
state = exif_info.get("state") if exif_info else None
|
||||
country = exif_info.get("country") if exif_info else None
|
||||
file_size = exif_info.get("fileSizeInByte") if exif_info else None
|
||||
|
||||
asset_type = data.get("type", ASSET_TYPE_IMAGE)
|
||||
is_processed = cls._check_processing_status(data)
|
||||
@@ -126,6 +128,7 @@ class ImmichAssetInfo:
|
||||
city=city,
|
||||
state=state,
|
||||
country=country,
|
||||
file_size=file_size,
|
||||
is_processed=is_processed,
|
||||
thumbhash=thumbhash,
|
||||
)
|
||||
|
||||
@@ -7,6 +7,11 @@ from typing import Any
|
||||
|
||||
from notify_bridge_core.models.events import ServiceEvent
|
||||
|
||||
# Per-target maximum video size (bytes). None = no limit.
|
||||
_MAX_VIDEO_SIZE_BY_TARGET: dict[str, int] = {
|
||||
"telegram": 50 * 1024 * 1024, # 50 MB — Telegram Bot API hard limit
|
||||
}
|
||||
|
||||
|
||||
def build_template_context(
|
||||
event: ServiceEvent,
|
||||
@@ -57,6 +62,7 @@ def build_template_context(
|
||||
}
|
||||
# Flatten extras into asset dict for template access
|
||||
asset_dict.update(asset.extra)
|
||||
asset_dict.setdefault("oversized", False)
|
||||
assets.append(asset_dict)
|
||||
|
||||
# Enrich assets with per-asset public URLs if album has a public share link
|
||||
@@ -75,6 +81,22 @@ def build_template_context(
|
||||
ctx["has_videos"] = any(a.get("type") == "VIDEO" for a in assets)
|
||||
ctx["has_photos"] = any(a.get("type") == "IMAGE" for a in assets)
|
||||
|
||||
# Per-target video size limit and oversized-video detection
|
||||
max_video_bytes = _MAX_VIDEO_SIZE_BY_TARGET.get(target_type)
|
||||
ctx["max_video_size"] = max_video_bytes # bytes or None
|
||||
ctx["max_video_size_mb"] = max_video_bytes // (1024 * 1024) if max_video_bytes else None
|
||||
|
||||
has_oversized = False
|
||||
if max_video_bytes:
|
||||
for a in assets:
|
||||
if a.get("type") == "VIDEO":
|
||||
fs = a.get("file_size")
|
||||
oversized = fs is not None and fs > max_video_bytes
|
||||
a["oversized"] = oversized
|
||||
if oversized:
|
||||
has_oversized = True
|
||||
ctx["has_oversized_videos"] = has_oversized
|
||||
|
||||
# Date format strings (available to templates for custom formatting)
|
||||
ctx["date_format"] = date_format
|
||||
ctx["date_only_format"] = date_only_format
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
📷 {{ added_count }} new file(s) added to album {% if public_url %}<a href="{{ public_url }}">{{ album_name }}</a>{% else %}"{{ album_name }}"{% endif %}.
|
||||
📎 {{ added_count }} new file(s) added to album {% if public_url %}<a href="{{ public_url }}">{{ album_name }}</a>{% else %}"{{ album_name }}"{% endif %}.
|
||||
{%- if common_date %} 📅 {{ common_date }}{% endif %}
|
||||
{%- if common_location %} 📍 {{ common_location }}{% endif %}
|
||||
{%- if people %}
|
||||
@@ -9,9 +9,10 @@
|
||||
• {%- if asset.type == "VIDEO" %} 🎬{% else %} 🖼️{% endif %} {% if asset.public_url %}<a href="{{ asset.public_url }}">{{ asset.filename }}</a>{% else %}{{ asset.filename }}{% endif %}
|
||||
{%- if not common_location and asset.city %} 📍 {{ asset.city }}{% if asset.country %}, {{ asset.country }}{% endif %}{% endif %}
|
||||
{%- if asset.is_favorite %} ❤️{% endif %}
|
||||
{%- if asset.oversized %} ⚠️{% endif %}
|
||||
{%- endfor %}
|
||||
{%- endif %}
|
||||
{%- if target_type == "telegram" and has_videos %}
|
||||
{%- if has_oversized_videos %}
|
||||
|
||||
⚠️ Videos may not be sent due to Telegram's 50 MB file size limit.
|
||||
⚠️ Some videos exceed the {{ max_video_size_mb }} MB file size limit and may not be sent.
|
||||
{%- endif %}
|
||||
@@ -1,4 +1,4 @@
|
||||
📷 {{ added_count }} новых файл(ов) добавлено в альбом {% if public_url %}<a href="{{ public_url }}">{{ album_name }}</a>{% else %}"{{ album_name }}"{% endif %}.
|
||||
📎 {{ added_count }} новых файл(ов) добавлено в альбом {% if public_url %}<a href="{{ public_url }}">{{ album_name }}</a>{% else %}"{{ album_name }}"{% endif %}.
|
||||
{%- if common_date %} 📅 {{ common_date }}{% endif %}
|
||||
{%- if common_location %} 📍 {{ common_location }}{% endif %}
|
||||
{%- if people %}
|
||||
@@ -9,9 +9,10 @@
|
||||
• {%- if asset.type == "VIDEO" %} 🎬{% else %} 🖼️{% endif %} {% if asset.public_url %}<a href="{{ asset.public_url }}">{{ asset.filename }}</a>{% else %}{{ asset.filename }}{% endif %}
|
||||
{%- if not common_location and asset.city %} 📍 {{ asset.city }}{% if asset.country %}, {{ asset.country }}{% endif %}{% endif %}
|
||||
{%- if asset.is_favorite %} ❤️{% endif %}
|
||||
{%- if asset.oversized %} ⚠️{% endif %}
|
||||
{%- endfor %}
|
||||
{%- endif %}
|
||||
{%- if target_type == "telegram" and has_videos %}
|
||||
{%- if has_oversized_videos %}
|
||||
|
||||
⚠️ Видео может не отправиться из-за ограничения Telegram в 50 МБ.
|
||||
⚠️ Некоторые видео превышают лимит {{ max_video_size_mb }} МБ и могут не отправиться.
|
||||
{%- endif %}
|
||||
@@ -24,6 +24,7 @@ def validate_template(
|
||||
# Also allow common runtime variables not in the registry
|
||||
runtime_vars = {
|
||||
"target_type", "has_videos", "has_photos",
|
||||
"has_oversized_videos", "max_video_size", "max_video_size_mb",
|
||||
"added_assets", "assets", "albums",
|
||||
}
|
||||
allowed = available | runtime_vars
|
||||
|
||||
@@ -150,6 +150,9 @@ async def get_template_variables():
|
||||
"target_type": "Target type: 'telegram' or 'webhook'",
|
||||
"has_videos": "Whether added assets contain videos (boolean)",
|
||||
"has_photos": "Whether added assets contain photos (boolean)",
|
||||
"has_oversized_videos": "Whether any video exceeds the target's size limit (boolean)",
|
||||
"max_video_size": "Target video size limit in bytes (null if no limit)",
|
||||
"max_video_size_mb": "Target video size limit in MB (null if no limit)",
|
||||
# Immich aliases
|
||||
"album_name": "Alias for collection_name",
|
||||
"album_id": "Alias for collection_id",
|
||||
@@ -178,6 +181,8 @@ async def get_template_variables():
|
||||
"city": "City name",
|
||||
"state": "State/region name",
|
||||
"country": "Country name",
|
||||
"file_size": "File size in bytes (null if unknown)",
|
||||
"oversized": "Whether video exceeds the target's size limit (boolean, videos only)",
|
||||
"public_url": "Per-asset public share URL (empty if no album link)",
|
||||
"url": "Public viewer URL (if shared)",
|
||||
"download_url": "Direct download URL (if shared)",
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
"""Sample template context for previews and test notifications."""
|
||||
"""Sample template context for previews and test notifications.
|
||||
|
||||
IMPORTANT: Keep sample assets and context in sync with:
|
||||
- ``notify_bridge_core.templates.context.build_template_context`` (runtime variables)
|
||||
- ``notify_bridge_server.api.template_configs`` (variable docs)
|
||||
- ``notify_bridge_core.templates.validator`` (runtime_vars whitelist)
|
||||
|
||||
When adding new template variables, update all four locations.
|
||||
"""
|
||||
|
||||
# Sample asset matching what build_asset_detail() actually returns
|
||||
_SAMPLE_ASSET = {
|
||||
@@ -21,6 +29,8 @@ _SAMPLE_ASSET = {
|
||||
"public_url": "https://immich.example.com/share/abc123/photos/a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
||||
"download_url": "https://immich.example.com/api/assets/abc123/original",
|
||||
"photo_url": "https://immich.example.com/api/assets/abc123/thumbnail",
|
||||
"file_size": 3_500_000, # 3.5 MB
|
||||
"oversized": False,
|
||||
}
|
||||
|
||||
_SAMPLE_VIDEO_ASSET = {
|
||||
@@ -33,6 +43,8 @@ _SAMPLE_VIDEO_ASSET = {
|
||||
"photo_url": None,
|
||||
"public_url": "https://immich.example.com/share/abc123/photos/d4e5f6a7-b8c9-0123-defg-456789abcdef",
|
||||
"playback_url": "https://immich.example.com/api/assets/def456/video",
|
||||
"file_size": 75_000_000, # 75 MB — exceeds Telegram's 50 MB limit
|
||||
"oversized": True,
|
||||
}
|
||||
|
||||
_SAMPLE_COLLECTION = {
|
||||
@@ -68,6 +80,9 @@ _SAMPLE_CONTEXT = {
|
||||
"target_type": "telegram",
|
||||
"has_videos": True,
|
||||
"has_photos": True,
|
||||
"has_oversized_videos": True,
|
||||
"max_video_size": 50 * 1024 * 1024, # 50 MB in bytes
|
||||
"max_video_size_mb": 50,
|
||||
# Rename fields (always present, empty for non-rename events)
|
||||
"old_name": "Old Album",
|
||||
"new_name": "New Album",
|
||||
|
||||
Reference in New Issue
Block a user