text
).strip()
- def choose_photo(self):
- """Pick one of the cached URLs and build a page."""
- if len(self.candidate_photo_uuids) == 0:
- print("No photos!")
- return False
- selected_uuid = random.sample(list(self.candidate_photo_uuids), 1)[0]
- asset = self.immich_cli.get_full_asset(selected_uuid)
- if not asset:
- print(f"Asset {selected_uuid} is unknown?!")
- return False
+def choose_photo(self):
+ """Pick one of the cached URLs and build a page."""
+ if len(self.candidate_photo_uuids) == 0:
+ print("No photos!")
+ return False
+
+ selected_uuid = random.sample(list(self.candidate_photo_uuids), 1)[0]
+ asset = self.immich_cli.get_full_asset(selected_uuid)
+ if not asset:
+ print(f"Asset {selected_uuid} is unknown?!")
+ return False
+
+ # --- Fetch ALL network data first, before touching disk ---
+ try:
+ jpeg = self.immich_cli.get_asset_thumbnail(selected_uuid, size="fullsize")
+ except Exception:
+ print("Failed to get image!")
+ return False
- try:
- jpeg = self.immich_cli.get_asset_thumbnail(selected_uuid, size="fullsize")
- with open("/var/www/kiosk/pages/mfs/current.jpeg", "wb") as f:
- f.write(jpeg)
- f.flush()
- os.fsync(f.fileno())
- except Exception:
- print("Failed to get image!")
- return False
-
- # Simple aspect ratio check for layout decision.
- layout_class = get_layout_class(asset)
-
- # Albums list...
- try:
- albums = self.immich_cli.get_asset_albums(selected_uuid)
- except Exception:
- print("Failed to get albums!")
- albums = []
-
- # Location, description, etc...
- exif = asset.get('exifInfo', {})
- location = "Unknown location"
- descr = ''
- if exif:
- date = parse_exif_date(exif)
- city = exif.get("city", "")
- state = exif.get("state", "")
- country = exif.get("country", "")
- lat = exif.get("latitude", "")
- lon = exif.get("longitude", "")
- if city:
- location = city
- if state:
- if location:
- location += f", {state}"
- else:
- location = state
- if country:
- if location:
- location += f", {country}"
- else:
- location = country
- if lat and lon:
- if location:
- location += f" @ {format_coords(lat, lon)}"
- else:
- location = format_coords(lat, lon)
- descr = exif.get("description", "")
-
- if not descr:
- people = asset.get('people', [])
- if people:
- descr = "A photo of "
- descr += natural_join([p.get("name", "") for p in asset.get("people", [])])
- else:
- descr = "No description data available. #needs description"
- descr = self._highlight_hashtags(descr)
-
- album_html = ''
- for album in albums:
- album_name = album.get('albumName')
- album_cover_uuid = album.get('albumThumbnailAssetId')
- album_cover = self.immich_cli.get_asset_thumbnail(
- album_cover_uuid, size="thumbnail"
- )
- with open(f"/var/www/kiosk/pages/mfs/{album_cover_uuid}.jpeg", "wb") as f:
- f.write(album_cover)
+ try:
+ albums = self.immich_cli.get_asset_albums(selected_uuid)
+ except Exception:
+ print("Failed to get albums!")
+ albums = []
+
+ album_covers = {}
+ for album in albums:
+ album_cover_uuid = album.get('albumThumbnailAssetId')
+ if album_cover_uuid:
+ try:
+ album_covers[album_cover_uuid] = self.immich_cli.get_asset_thumbnail(
+ album_cover_uuid, size="thumbnail"
+ )
+ except Exception:
+ print(f"Failed to get album cover for {album_cover_uuid}, skipping.")
+
+ # --- All network calls done. Now compute derived values. ---
+ layout_class = get_layout_class(asset)
+ exif = asset.get('exifInfo', {})
+ date = None
+ location = "Unknown location"
+ descr = ''
+ if exif:
+ date = parse_exif_date(exif)
+ city = exif.get("city", "")
+ state = exif.get("state", "")
+ country = exif.get("country", "")
+ lat = exif.get("latitude", "")
+ lon = exif.get("longitude", "")
+
+ if city:
+ location = city
+ if state:
+ location = f"{location}, {state}" if location else state
+ if country:
+ location = f"{location}, {country}" if location else country
+ if lat and lon:
+ location = f"{location} @ {format_coords(lat, lon)}" if location else format_coords(lat, lon)
+ descr = exif.get("description", "")
+
+ if not descr:
+ people = asset.get('people', [])
+ if people:
+ descr = "A photo of " + natural_join([p.get("name", "") for p in people])
+ else:
+ descr = "No description data available. #needs description"
+
+ descr = self._highlight_hashtags(descr)
+ date_str = date.strftime("%B %-d, %Y") if date else "Unknown date"
+ album_html = ''
+ for album in albums:
+ album_name = album.get('albumName')
+ album_cover_uuid = album.get('albumThumbnailAssetId')
+ if album_cover_uuid and album_cover_uuid in album_covers:
album_html += f"""
<div class="album-card">
<img src="/kiosk/mfs/{album_cover_uuid}.jpeg" class="album-thumb" alt="Album Art">
<span class="album-name">{album_name}</span>
</div>
"""
- cache_buster = int(time.time())
- with file_writer.file_writer("photo_33_3600.html") as f:
- f.write(
- f"""
+
+ # --- All data ready. Now write to disk atomically. ---
+ with open("/var/www/kiosk/pages/mfs/current.jpeg", "wb") as f:
+ f.write(jpeg)
+ f.flush()
+ os.fsync(f.fileno())
+
+ for cover_uuid, cover_data in album_covers.items():
+ with open(f"/var/www/kiosk/pages/mfs/{cover_uuid}.jpeg", "wb") as f:
+ f.write(cover_data)
+
+ cache_buster = int(time.time())
+ with file_writer.file_writer("photo_33_3600.html") as f:
+ f.write(
+ f"""
<style>
div#time{{color:#dddddd;}}
div#date{{color:#dddddd;}}
height: 100vh;
width: 100vw;
}}
-
/* --- Photo Pane Logic --- */
.photo-pane {{
display: flex;
object-fit: contain;
box-shadow: 0 0 50px rgba(0,0,0,0.5);
}}
-
/* --- Info Pane Logic --- */
.info-pane {{
display: flex;
flex: 0 1 auto;
min-height: 20vh;
display: flex;
- /* flex-direction: row; */
align-items: center;
padding: 2vh 3vw;
}}
-
/* --- Typography & Elements --- */
.description {{
font-weight: 600;
overflow: visible;
margin: 0;
padding-right: 2vw;
- min-width: 0; /* Allows text to truncate/wrap properly */
+ min-width: 0;
}}
-
.divider {{
background: var(--accent);
}}
height: 50px;
margin: 0;
}}
-
- /* Metadata section: Constrained to 20% width */
.panorama_mode .meta-group {{
flex: 0 0 20%;
display: flex;
flex-direction: column;
justify-content: center;
padding: 0 1.5vw;
- min-width: 0; /* Crucial for internal text wrapping */
+ min-width: 0;
}}
-
.location {{
font-size: 0.9rem;
color: var(--text-dim);
font-size: 1rem;
margin-top: 4px;
}}
-
/* --- Album List --- */
.album-section-title {{
font-size: 0.7rem;
transform: rotate(180deg);
margin: 10px;
}}
-
.album-grid {{
display: flex;
gap: 15px;
margin-top: 5px;
text-align: center;
width: 100%;
-/* white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis; */
-
- /* Fix #1: Force long words like 'AdobeLightroom' to break */
word-break: break-word;
overflow-wrap: break-word;
-
- /* Fix #2: Multi-line truncation (The "Best of Both Worlds") */
display: -webkit-box;
- -webkit-line-clamp: 2; /* Show exactly 3 lines, then dots */
+ -webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}}
<div class="photo-pane">
<img src="/kiosk/mfs/current.jpeg?v={cache_buster}" alt="Current kiosk photo">
</div>
-
<!-- Metadata Section -->
<div class="info-pane">
<div class="description">
{location}
</div>
<div class="date">
- {date.strftime("%B %-d, %Y")}
+ {date_str}
</div>
</div>
- <!-- <div class="divider"></div> -->
<div class="album-section-title">Appears In</div>
<div class="album-grid">
{album_html}
</div>
</div>
</div>
- """)
- return True
-
+ """)
+ return True
# Test code
x = immich_photo_renderer({"Fetch Photos": (60 * 60 * 12),