private-assistant-picture-display-skill 0.4.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- private_assistant_picture_display_skill/__init__.py +15 -0
- private_assistant_picture_display_skill/config.py +61 -0
- private_assistant_picture_display_skill/immich/__init__.py +20 -0
- private_assistant_picture_display_skill/immich/client.py +250 -0
- private_assistant_picture_display_skill/immich/config.py +94 -0
- private_assistant_picture_display_skill/immich/models.py +109 -0
- private_assistant_picture_display_skill/immich/payloads.py +55 -0
- private_assistant_picture_display_skill/immich/storage.py +127 -0
- private_assistant_picture_display_skill/immich/sync_service.py +501 -0
- private_assistant_picture_display_skill/main.py +152 -0
- private_assistant_picture_display_skill/models/__init__.py +24 -0
- private_assistant_picture_display_skill/models/commands.py +63 -0
- private_assistant_picture_display_skill/models/device.py +30 -0
- private_assistant_picture_display_skill/models/image.py +62 -0
- private_assistant_picture_display_skill/models/immich_sync_job.py +109 -0
- private_assistant_picture_display_skill/picture_skill.py +575 -0
- private_assistant_picture_display_skill/py.typed +0 -0
- private_assistant_picture_display_skill/services/__init__.py +9 -0
- private_assistant_picture_display_skill/services/device_mqtt_client.py +163 -0
- private_assistant_picture_display_skill/services/image_manager.py +175 -0
- private_assistant_picture_display_skill/templates/describe_image.j2 +11 -0
- private_assistant_picture_display_skill/templates/help.j2 +1 -0
- private_assistant_picture_display_skill/templates/next_picture.j2 +9 -0
- private_assistant_picture_display_skill/utils/__init__.py +15 -0
- private_assistant_picture_display_skill/utils/color_analysis.py +78 -0
- private_assistant_picture_display_skill/utils/image_processing.py +104 -0
- private_assistant_picture_display_skill/utils/metadata_builder.py +135 -0
- private_assistant_picture_display_skill-0.4.1.dist-info/METADATA +47 -0
- private_assistant_picture_display_skill-0.4.1.dist-info/RECORD +32 -0
- private_assistant_picture_display_skill-0.4.1.dist-info/WHEEL +4 -0
- private_assistant_picture_display_skill-0.4.1.dist-info/entry_points.txt +3 -0
- private_assistant_picture_display_skill-0.4.1.dist-info/licenses/LICENSE +0 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""Natural language metadata builder for images."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
# Constants for name formatting
|
|
6
|
+
_PAIR_COUNT = 2
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class MetadataBuilder:
|
|
10
|
+
"""Build natural language metadata from image data.
|
|
11
|
+
|
|
12
|
+
Generates human-readable title and description strings from structured
|
|
13
|
+
metadata like people names, location, date, and album information.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
@staticmethod
|
|
17
|
+
def build_title(
|
|
18
|
+
people: list[str] | None = None,
|
|
19
|
+
city: str | None = None,
|
|
20
|
+
country: str | None = None,
|
|
21
|
+
date: datetime | None = None,
|
|
22
|
+
) -> str | None:
|
|
23
|
+
"""Build short title for voice responses.
|
|
24
|
+
|
|
25
|
+
Priority: People > Location > Date
|
|
26
|
+
Examples: "John and Sarah, Berlin" or "Berlin, August 2023"
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
people: List of recognized person names
|
|
30
|
+
city: City name from EXIF
|
|
31
|
+
country: Country name from EXIF
|
|
32
|
+
date: Date photo was taken
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Short title string or None if no data available
|
|
36
|
+
"""
|
|
37
|
+
parts: list[str] = []
|
|
38
|
+
|
|
39
|
+
# People names (max 2 shown)
|
|
40
|
+
if people:
|
|
41
|
+
parts.append(_format_names_short(people))
|
|
42
|
+
|
|
43
|
+
# Location (city preferred, country as fallback if no people)
|
|
44
|
+
if city:
|
|
45
|
+
parts.append(city)
|
|
46
|
+
elif country and not people:
|
|
47
|
+
parts.append(country)
|
|
48
|
+
|
|
49
|
+
# Date (only if no people, to keep title short)
|
|
50
|
+
if date and not people:
|
|
51
|
+
parts.append(date.strftime("%B %Y"))
|
|
52
|
+
|
|
53
|
+
return ", ".join(parts) if parts else None
|
|
54
|
+
|
|
55
|
+
@staticmethod
|
|
56
|
+
def build_description( # noqa: PLR0913
|
|
57
|
+
people: list[str] | None = None,
|
|
58
|
+
city: str | None = None,
|
|
59
|
+
state: str | None = None,
|
|
60
|
+
country: str | None = None,
|
|
61
|
+
date: datetime | None = None,
|
|
62
|
+
album_names: list[str] | None = None,
|
|
63
|
+
) -> str | None:
|
|
64
|
+
"""Build full description combining all metadata.
|
|
65
|
+
|
|
66
|
+
Examples:
|
|
67
|
+
- "John and Sarah in Berlin, Germany - August 2023. From album: Summer Vacation"
|
|
68
|
+
- "Photo from San Francisco, California - March 2024"
|
|
69
|
+
- "Photo of Emma and Michael. From album: Family"
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
people: List of recognized person names
|
|
73
|
+
city: City name from EXIF
|
|
74
|
+
state: State/region from EXIF
|
|
75
|
+
country: Country name from EXIF
|
|
76
|
+
date: Date photo was taken
|
|
77
|
+
album_names: List of album names containing this image
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
Full description string or None if no data available
|
|
81
|
+
"""
|
|
82
|
+
sentences: list[str] = []
|
|
83
|
+
|
|
84
|
+
# Main sentence: people + location
|
|
85
|
+
location = _build_location(city, state, country)
|
|
86
|
+
if people and location:
|
|
87
|
+
names = _format_names_full(people)
|
|
88
|
+
sentences.append(f"{names} in {location}")
|
|
89
|
+
elif people:
|
|
90
|
+
sentences.append(f"Photo of {_format_names_full(people)}")
|
|
91
|
+
elif location:
|
|
92
|
+
sentences.append(f"Photo from {location}")
|
|
93
|
+
|
|
94
|
+
# Append date to last sentence
|
|
95
|
+
if date:
|
|
96
|
+
date_str = date.strftime("%B %Y")
|
|
97
|
+
if sentences:
|
|
98
|
+
sentences[-1] += f" - {date_str}"
|
|
99
|
+
else:
|
|
100
|
+
sentences.append(f"Photo from {date_str}")
|
|
101
|
+
|
|
102
|
+
# Album info as separate sentence
|
|
103
|
+
if album_names:
|
|
104
|
+
albums = ", ".join(album_names[:2]) # Max 2 albums
|
|
105
|
+
sentences.append(f"From album: {albums}")
|
|
106
|
+
|
|
107
|
+
return ". ".join(sentences) if sentences else None
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _format_names_short(names: list[str]) -> str:
|
|
111
|
+
"""Format names for short title (max 2 shown)."""
|
|
112
|
+
if len(names) == 1:
|
|
113
|
+
return names[0]
|
|
114
|
+
if len(names) == _PAIR_COUNT:
|
|
115
|
+
return f"{names[0]} and {names[1]}"
|
|
116
|
+
return f"{names[0]} and others"
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _format_names_full(names: list[str]) -> str:
|
|
120
|
+
"""Format names for full description."""
|
|
121
|
+
if len(names) == 1:
|
|
122
|
+
return names[0]
|
|
123
|
+
if len(names) == _PAIR_COUNT:
|
|
124
|
+
return f"{names[0]} and {names[1]}"
|
|
125
|
+
return f"{names[0]}, {names[1]}, and {len(names) - _PAIR_COUNT} others"
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _build_location(
|
|
129
|
+
city: str | None,
|
|
130
|
+
state: str | None,
|
|
131
|
+
country: str | None,
|
|
132
|
+
) -> str | None:
|
|
133
|
+
"""Build location string from components."""
|
|
134
|
+
parts = [p for p in [city, state, country] if p]
|
|
135
|
+
return ", ".join(parts) if parts else None
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: private-assistant-picture-display-skill
|
|
3
|
+
Version: 0.4.1
|
|
4
|
+
Summary: Skill to dynamically display images from various sources via external agents.
|
|
5
|
+
Keywords:
|
|
6
|
+
Author: stkr22
|
|
7
|
+
Author-email: stkr22 <stkr22@github.com>
|
|
8
|
+
License-Expression: GPL-3.0-only
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Programming Language :: Python
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Requires-Dist: pydantic
|
|
16
|
+
Requires-Dist: jinja2~=3.1.3
|
|
17
|
+
Requires-Dist: private-assistant-commons~=5.4.0
|
|
18
|
+
Requires-Dist: typer~=0.21.0
|
|
19
|
+
Requires-Dist: httpx~=0.28.0
|
|
20
|
+
Requires-Dist: minio~=7.2.0
|
|
21
|
+
Requires-Dist: pyyaml~=6.0.0
|
|
22
|
+
Requires-Dist: pillow~=12.1.0
|
|
23
|
+
Requires-Dist: pillow-heif~=1.1.1
|
|
24
|
+
Requires-Dist: coloraide~=6.2.1
|
|
25
|
+
Requires-Python: >=3.12, <3.14
|
|
26
|
+
Project-URL: Changelog, https://github.com/stkr22/private-assistant-picture-display-skill-py/blob/main/CHANGELOG.md
|
|
27
|
+
Project-URL: Documentation, https://github.com/stkr22/private-assistant-picture-display-skill-py/tree/main/docs
|
|
28
|
+
Project-URL: Homepage, https://github.com/stkr22/private-assistant-picture-display-skill-py
|
|
29
|
+
Project-URL: Issues, https://github.com/stkr22/private-assistant-picture-display-skill-py/issues
|
|
30
|
+
Project-URL: Repository, https://github.com/stkr22/private-assistant-picture-display-skill-py
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# private-assistant-picture-display-skill
|
|
34
|
+
|
|
35
|
+
[](https://github.com/copier-org/copier)
|
|
36
|
+
[](https://www.python.org)
|
|
37
|
+
[](https://github.com/astral-sh/uv)
|
|
38
|
+
[](https://github.com/charliermarsh/ruff)
|
|
39
|
+
[](https://mypy-lang.org/)
|
|
40
|
+
[](https://github.com/psf/black)
|
|
41
|
+
[](https://github.com/pre-commit/pre-commit)
|
|
42
|
+
|
|
43
|
+
Owner: stkr22
|
|
44
|
+
|
|
45
|
+
**READ THE AGENTS.md**
|
|
46
|
+
|
|
47
|
+
Skill to dynamically display images from various sources via external agents.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
private_assistant_picture_display_skill/__init__.py,sha256=QCisYgmEO6tX47aB00xviNK3saJCmishWJ984Ohvmxw,394
|
|
2
|
+
private_assistant_picture_display_skill/config.py,sha256=DjjJxD7uPZWgxS1Xh8w4gdsdJd63NIQ8QyT5aTVmrRI,2235
|
|
3
|
+
private_assistant_picture_display_skill/immich/__init__.py,sha256=n-u-jUMu6SoUnu_lsn9HvrQ2w10XSDdWpJ3LIjl6B1U,612
|
|
4
|
+
private_assistant_picture_display_skill/immich/client.py,sha256=WLcsJE-UECKb0Oi0mbOCKsqON_sw04q-Dsp4r4zcKYg,8632
|
|
5
|
+
private_assistant_picture_display_skill/immich/config.py,sha256=C2uHEgzTuHueD71FQfrrIsRwKyn864Kd9QzGOaHgOVI,3545
|
|
6
|
+
private_assistant_picture_display_skill/immich/models.py,sha256=hrkSvZ3Gw4niRarIjDXbYYBqK1J1iUJfgY7L0n9rf6E,3089
|
|
7
|
+
private_assistant_picture_display_skill/immich/payloads.py,sha256=Ch69ah5mu_zo926um41jLTnbsdlddJNqqrDao6PxRKg,1634
|
|
8
|
+
private_assistant_picture_display_skill/immich/storage.py,sha256=dVF6Fh01h30L6WrfOTJWpiHWcuqtxFC3MPJNYO2HzbE,3535
|
|
9
|
+
private_assistant_picture_display_skill/immich/sync_service.py,sha256=sGsshx-PP_Xv2yDfs7RqdDvKLvwyjaa8GQekLt_ZxLw,19088
|
|
10
|
+
private_assistant_picture_display_skill/main.py,sha256=fHtZQNq17wfiCVmBVIbKxDlPeoNXUCTOI8akHg1JsUE,5295
|
|
11
|
+
private_assistant_picture_display_skill/models/__init__.py,sha256=-gRXRrAhItZFKfBDDPR1owciLsj5KwKpOp4RDoYiIQM,546
|
|
12
|
+
private_assistant_picture_display_skill/models/commands.py,sha256=bJ7qz9Q0qjpVbxKKH8yFepoAiA32oqVa6TShUQRIJ3I,2470
|
|
13
|
+
private_assistant_picture_display_skill/models/device.py,sha256=w30kQw1m-Pe6uoDNWqwurWHAr6HsrB1UXNa_-0PPQII,1263
|
|
14
|
+
private_assistant_picture_display_skill/models/image.py,sha256=yQeM31uFhY4lURTr4ZFfCTRvsYW_P72SiJWLZJff8Pk,3056
|
|
15
|
+
private_assistant_picture_display_skill/models/immich_sync_job.py,sha256=KuXRs2GIH7vGacQrBPTlyNxQOlJtayVEw664GJo1VN8,4439
|
|
16
|
+
private_assistant_picture_display_skill/picture_skill.py,sha256=RmFw1pjUcNyDRDxXuUcSYmZJUzw2D8ZvikaaugfV1x0,21726
|
|
17
|
+
private_assistant_picture_display_skill/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
|
+
private_assistant_picture_display_skill/services/__init__.py,sha256=WYEEnCJglZTllt5VpDCzikugTHir8vMoqoTyl2JyKJM,195
|
|
19
|
+
private_assistant_picture_display_skill/services/device_mqtt_client.py,sha256=gu02Y4zxyCe7fmEuAWExXTET1iz2WS9xCIpjDq1ax7k,5744
|
|
20
|
+
private_assistant_picture_display_skill/services/image_manager.py,sha256=UMhlQtfNzpbr3bMm_tFNFrhvoWQthIVZz8dvnFCZOxs,6797
|
|
21
|
+
private_assistant_picture_display_skill/templates/describe_image.j2,sha256=sZ6mZLP_mnQAVXEzBkIoh7tHA82tzvXJ2rbc3WrBBjA,359
|
|
22
|
+
private_assistant_picture_display_skill/templates/help.j2,sha256=8NxNXV3uafSNhMhmxoqEV_f4_V_jlRORHNh7IpmovdA,168
|
|
23
|
+
private_assistant_picture_display_skill/templates/next_picture.j2,sha256=jeKg6ABni_WMW-Qy62G-sx_aSYpkS2jcZmNdH8hpQcQ,310
|
|
24
|
+
private_assistant_picture_display_skill/utils/__init__.py,sha256=B-u7QNTPnE7J-7lBfjcyKeULih0voJKuYzBxg79DIJ8,499
|
|
25
|
+
private_assistant_picture_display_skill/utils/color_analysis.py,sha256=4ZhnnxMWxS1ndV3IWg1Av0kXsJzZZm25Yxs2Xh76hWM,3210
|
|
26
|
+
private_assistant_picture_display_skill/utils/image_processing.py,sha256=BHLAg1yBBszgVR-NB-2kA7FPi8gvmpj4E-3WxPwFidA,3926
|
|
27
|
+
private_assistant_picture_display_skill/utils/metadata_builder.py,sha256=h3Qi4Rvh1A2n0gnAt_UL7-N4s-81uI9TfT7IssmYMgI,4293
|
|
28
|
+
private_assistant_picture_display_skill-0.4.1.dist-info/licenses/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
29
|
+
private_assistant_picture_display_skill-0.4.1.dist-info/WHEEL,sha256=eycQt0QpYmJMLKpE3X9iDk8R04v2ZF0x82ogq-zP6bQ,79
|
|
30
|
+
private_assistant_picture_display_skill-0.4.1.dist-info/entry_points.txt,sha256=dbqQbHKXHoKD8Fex3RgYPNlbumICWM4OUwlnlVfVK74,110
|
|
31
|
+
private_assistant_picture_display_skill-0.4.1.dist-info/METADATA,sha256=2mJjWtVZFyeqT3REOxMlglwc0j87_Bs_7vqNdx1GptU,2566
|
|
32
|
+
private_assistant_picture_display_skill-0.4.1.dist-info/RECORD,,
|
|
File without changes
|