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.
Files changed (32) hide show
  1. private_assistant_picture_display_skill/__init__.py +15 -0
  2. private_assistant_picture_display_skill/config.py +61 -0
  3. private_assistant_picture_display_skill/immich/__init__.py +20 -0
  4. private_assistant_picture_display_skill/immich/client.py +250 -0
  5. private_assistant_picture_display_skill/immich/config.py +94 -0
  6. private_assistant_picture_display_skill/immich/models.py +109 -0
  7. private_assistant_picture_display_skill/immich/payloads.py +55 -0
  8. private_assistant_picture_display_skill/immich/storage.py +127 -0
  9. private_assistant_picture_display_skill/immich/sync_service.py +501 -0
  10. private_assistant_picture_display_skill/main.py +152 -0
  11. private_assistant_picture_display_skill/models/__init__.py +24 -0
  12. private_assistant_picture_display_skill/models/commands.py +63 -0
  13. private_assistant_picture_display_skill/models/device.py +30 -0
  14. private_assistant_picture_display_skill/models/image.py +62 -0
  15. private_assistant_picture_display_skill/models/immich_sync_job.py +109 -0
  16. private_assistant_picture_display_skill/picture_skill.py +575 -0
  17. private_assistant_picture_display_skill/py.typed +0 -0
  18. private_assistant_picture_display_skill/services/__init__.py +9 -0
  19. private_assistant_picture_display_skill/services/device_mqtt_client.py +163 -0
  20. private_assistant_picture_display_skill/services/image_manager.py +175 -0
  21. private_assistant_picture_display_skill/templates/describe_image.j2 +11 -0
  22. private_assistant_picture_display_skill/templates/help.j2 +1 -0
  23. private_assistant_picture_display_skill/templates/next_picture.j2 +9 -0
  24. private_assistant_picture_display_skill/utils/__init__.py +15 -0
  25. private_assistant_picture_display_skill/utils/color_analysis.py +78 -0
  26. private_assistant_picture_display_skill/utils/image_processing.py +104 -0
  27. private_assistant_picture_display_skill/utils/metadata_builder.py +135 -0
  28. private_assistant_picture_display_skill-0.4.1.dist-info/METADATA +47 -0
  29. private_assistant_picture_display_skill-0.4.1.dist-info/RECORD +32 -0
  30. private_assistant_picture_display_skill-0.4.1.dist-info/WHEEL +4 -0
  31. private_assistant_picture_display_skill-0.4.1.dist-info/entry_points.txt +3 -0
  32. 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
+ [![Copier](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/copier-org/copier/master/img/badge/badge-grayscale-inverted-border-orange.json)](https://github.com/copier-org/copier)
36
+ [![python](https://img.shields.io/badge/Python-3.12-3776AB.svg?style=flat&logo=python&logoColor=white)](https://www.python.org)
37
+ [![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv)
38
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v0.json)](https://github.com/charliermarsh/ruff)
39
+ [![Checked with mypy](https://www.mypy-lang.org/static/mypy_badge.svg)](https://mypy-lang.org/)
40
+ [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
41
+ [![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white)](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,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.9.24
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ private-assistant-picture-display-skill = private_assistant_picture_display_skill.main:app
3
+