From b8ea8eb1505a68354b2fc0732e10920c71480f1e Mon Sep 17 00:00:00 2001 From: jessikitty Date: Mon, 25 May 2026 16:30:58 +1000 Subject: [PATCH] docs: update README with v1.4.0 auth and REST API documentation --- README.md | 141 +----------------------------------------------------- 1 file changed, 1 insertion(+), 140 deletions(-) diff --git a/README.md b/README.md index d8c5dfe..fe58dc4 100644 --- a/README.md +++ b/README.md @@ -1,140 +1 @@ -# Frambe - -

- Frambe -

- -A lightweight, self-contained Docker web application that connects to your [Immich](https://immich.app/) server and displays photos in a beautiful full-screen slideshow — perfect for turning old tablets, spare screens, and Raspberry Pis into digital photo frames. - -## ✨ Features - -- **Immich API Integration** — Connects securely via API key (kept server-side) -- **Album Browser** — Select any album, random photos, or favorites only -- **Person / Face Support** — Display photos of a specific person via Immich's face recognition -- **URL-Based Zero-Touch Launch** — Skip the setup screen entirely with query parameters -- **Auto-Refresh** — Periodically checks for new photos added to the source album/person -- **Smooth Crossfade** — Double-buffered image transitions with configurable duration -- **Background Blur** — Blurred backdrop fills the space behind non-covering images -- **Clock & Date Overlay** — Always know the time at a glance -- **EXIF Info** — Shows photo location, date, and camera info -- **Progress Bar** — Subtle indicator of time until next photo -- **Touch Controls** — Tap left/right edges to navigate, centre to toggle overlay -- **Keyboard Controls** — Arrow keys, Space, F (fullscreen), I (info), Esc (exit) -- **Screen Wake Lock** — Prevents screen sleep on supported devices -- **Responsive** — Works on any screen size from phone to TV -- **Older Device Friendly** — Vanilla HTML/CSS/JS, no heavy frameworks -- **Docker Containerised** — Single container, minimal footprint - -## 🚀 Quick Start - -### 1. Get your Immich API Key - -1. Open your Immich web interface -2. Click your profile picture → **Account Settings** → **API Keys** -3. Create a new key with `asset.read` and `album.read` permissions - -### 2. Run with Docker Compose - -```bash -git clone https://gitea.hideawaygaming.com.au/jessikitty/frambe.git -cd frambe -``` - -Edit `docker-compose.yml` and set your `IMMICH_URL` and `IMMICH_API_KEY`, then: - -```bash -docker compose up -d -``` - -Open `http://your-server:3030` in a browser on your tablet/screen. - -### 3. Run with Docker directly - -```bash -docker build -t frambe . -docker run -d \ - --name frambe \ - -p 3030:3000 \ - -e IMMICH_URL=http://your-immich-server:2283 \ - -e IMMICH_API_KEY=your-api-key \ - --restart unless-stopped \ - frambe -``` - -## 🔗 Zero-Touch URL Parameters - -Skip the setup screen entirely by passing query parameters. This is ideal for dedicated frames — just bookmark the URL on each tablet: - -| URL | What it shows | -|---|---| -| `http://server:3030/?album=ALBUM_UUID` | Photos from a specific album | -| `http://server:3030/?person=PERSON_UUID` | Photos of a specific person (face recognition) | -| `http://server:3030/?favorites` | Favorite photos only | -| `http://server:3030/?random` | Random photos from the library | - -You can find album and person UUIDs in Immich's web interface URL bar when viewing an album or person. - -## ⚙️ Configuration - -All settings are via environment variables: - -| Variable | Default | Description | -|---|---|---| -| `IMMICH_URL` | *(required)* | Your Immich server URL | -| `IMMICH_API_KEY` | *(required)* | Immich API key | -| `SLIDESHOW_INTERVAL` | `30` | Seconds between photos | -| `TRANSITION_DURATION` | `2` | Crossfade duration in seconds | -| `IMAGE_FIT` | `contain` | `contain` or `cover` | -| `SHUFFLE` | `true` | Randomise photo order | -| `BACKGROUND_BLUR` | `true` | Show blurred backdrop | -| `SHOW_CLOCK` | `true` | Display clock overlay | -| `SHOW_DATE` | `true` | Display date overlay | -| `SHOW_EXIF` | `true` | Display photo metadata | -| `SHOW_PROGRESS` | `true` | Display progress bar | -| `REFRESH_INTERVAL` | `300` | Seconds between source refresh checks (new photos) | -| `ALBUM_ID` | *(empty)* | Auto-start with specific album (env-based) | -| `SHOW_FAVORITES_ONLY` | `false` | Auto-start with favorites (env-based) | -| `PORT` | `3000` | Internal server port (Docker maps externally via compose) | - -## 🎮 Controls - -### Touch / Mouse -- **Left 20%** of screen — Previous photo -- **Centre 60%** — Toggle overlay (clock, info, close button) -- **Right 20%** — Next photo - -### Keyboard -- `←` / `→` — Previous / Next photo -- `Space` — Next photo -- `F` — Toggle fullscreen -- `I` — Toggle info overlay -- `Esc` — Exit to album selection - -## 📱 Tablet Setup Tips - -1. Open the frame URL in your tablet's browser (use a `?album=` or `?person=` URL for zero-touch) -2. Add to Home Screen for a full-screen app experience -3. Enable kiosk mode or guided access to lock to the app -4. Disable screen timeout in your device settings - -## 🏗️ Architecture - -``` -┌──────────────┐ ┌──────────────┐ ┌──────────────┐ -│ Browser │ HTTP │ Frambe │ API │ Immich │ -│ (Tablet) │◄───────►│ (Node.js) │◄───────►│ Server │ -└──────────────┘ :3030 └──────────────┘ :2283 └──────────────┘ -``` - -The Node.js backend acts as a secure proxy — your Immich API key never reaches the browser. The frontend periodically polls the backend for new photos so albums stay up to date without restarting. - -## 📋 Version History - -- **1.2.1** — Fix port mapping (3030:3000 external:internal), fix URL param auto-launch not starting slideshow -- **1.2.0** — URL params (`?album=`, `?person=`, `?favorites`, `?random`), person/face support, periodic auto-refresh, app icon, default port changed to 3030 -- **1.1.0** — Rebrand to Frambe -- **1.0.0** — Initial release: album browser, slideshow with crossfade, clock/date/EXIF overlays, touch & keyboard controls, Docker deployment - -## 📄 License - -MIT +IyBGcmFtYmUKCjxwIGFsaWduPSJjZW50ZXIiPgogIDxpbWcgc3JjPSJwdWJsaWMvaW1nL2ljb24ucG5nIiBhbHQ9IkZyYW1iZSIgd2lkdGg9IjE4MCI+CjwvcD4KCkEgbGlnaHR3ZWlnaHQsIHNlbGYtY29udGFpbmVkIERvY2tlciB3ZWIgYXBwbGljYXRpb24gdGhhdCBjb25uZWN0cyB0byB5b3VyIFtJbW1pY2hdKGh0dHBzOi8vaW1taWNoLmFwcC8pIHNlcnZlciBhbmQgZGlzcGxheXMgcGhvdG9zIGluIGEgYmVhdXRpZnVsIGZ1bGwtc2NyZWVuIHNsaWRlc2hvdyDigJQgcGVyZmVjdCBmb3IgdHVybmluZyBvbGQgdGFibGV0cywgc3BhcmUgc2NyZWVucywgYW5kIFJhc3BiZXJyeSBQaXMgaW50byBkaWdpdGFsIHBob3RvIGZyYW1lcy4KCiMjIOKcqCBGZWF0dXJlcwoKLSAqKkltbWljaCBBUEkgSW50ZWdyYXRpb24qKiDigJQgQ29ubmVjdHMgc2VjdXJlbHkgdmlhIEFQSSBrZXkgKGtlcHQgc2VydmVyLXNpZGUpCi0gKipBbGJ1bSBCcm93c2VyKiog4oCUIFNlbGVjdCBhbnkgYWxidW0sIHJhbmRvbSBwaG90b3MsIG9yIGZhdm9yaXRlcyBvbmx5Ci0gKipQZXJzb24gLyBGYWNlIFN1cHBvcnQqKiDigJQgRGlzcGxheSBwaG90b3Mgb2YgYSBzcGVjaWZpYyBwZXJzb24gdmlhIEltbWljaCdzIGZhY2UgcmVjb2duaXRpb24KLSAqKkFkbWluIERhc2hib2FyZCoqIOKAlCBSZWFsLXRpbWUgV2ViU29ja2V0LWJhc2VkIGNvbnRyb2wgcGFuZWwgZm9yIGFsbCBjb25uZWN0ZWQgZnJhbWVzCi0gKipBZG1pbiBBdXRoZW50aWNhdGlvbioqIOKAlCBPcHRpb25hbCB1c2VybmFtZS9wYXNzd29yZCBsb2dpbiB0byBwcm90ZWN0IHRoZSBhZG1pbiBkYXNoYm9hcmQKLSAqKlJFU1QgQVBJIHdpdGggVG9rZW4gQXV0aCoqIOKAlCBDb250cm9sIGZyYW1lcyBmcm9tIEhvbWUgQXNzaXN0YW50LCBzY3JpcHRzLCBvciBleHRlcm5hbCB0b29scwotICoqVVJMLUJhc2VkIFplcm8tVG91Y2ggTGF1bmNoKiog4oCUIFNraXAgdGhlIHNldHVwIHNjcmVlbiBlbnRpcmVseSB3aXRoIHF1ZXJ5IHBhcmFtZXRlcnMKLSAqKkF1dG8tUmVmcmVzaCoqIOKAlCBQZXJpb2RpY2FsbHkgY2hlY2tzIGZvciBuZXcgcGhvdG9zIGFkZGVkIHRvIHRoZSBzb3VyY2UgYWxidW0vcGVyc29uCi0gKipTbW9vdGggQ3Jvc3NmYWRlKiog4oCUIERvdWJsZS1idWZmZXJlZCBpbWFnZSB0cmFuc2l0aW9ucyB3aXRoIGNvbmZpZ3VyYWJsZSBkdXJhdGlvbgotICoqQmFja2dyb3VuZCBCbHVyKiog4oCUIEJsdXJyZWQgYmFja2Ryb3AgZmlsbHMgdGhlIHNwYWNlIGJlaGluZCBub24tY292ZXJpbmcgaW1hZ2VzCi0gKipDbG9jayAmIERhdGUgT3ZlcmxheSoqIOKAlCBBbHdheXMga25vdyB0aGUgdGltZSBhdCBhIGdsYW5jZQotICoqRVhJRiBJbmZvKiog4oCUIFNob3dzIHBob3RvIGxvY2F0aW9uLCBkYXRlLCBhbmQgY2FtZXJhIGluZm8KLSAqKlByb2dyZXNzIEJhcioqIOKAlCBTdWJ0bGUgaW5kaWNhdG9yIG9mIHRpbWUgdW50aWwgbmV4dCBwaG90bwotICoqVG91Y2ggQ29udHJvbHMqKiDigJQgVGFwIGxlZnQvcmlnaHQgZWRnZXMgdG8gbmF2aWdhdGUsIGNlbnRyZSB0byB0b2dnbGUgb3ZlcmxheQotICoqS2V5Ym9hcmQgQ29udHJvbHMqKiDigJQgQXJyb3cga2V5cywgU3BhY2UsIEYgKGZ1bGxzY3JlZW4pLCBJIChpbmZvKSwgRXNjIChleGl0KQotICoqU2NyZWVuIFdha2UgTG9jayoqIOKAlCBQcmV2ZW50cyBzY3JlZW4gc2xlZXAgb24gc3VwcG9ydGVkIGRldmljZXMKLSAqKlJlc3BvbnNpdmUqKiDigJQgV29ya3Mgb24gYW55IHNjcmVlbiBzaXplIGZyb20gcGhvbmUgdG8gVFYKLSAqKk9sZGVyIERldmljZSBGcmllbmRseSoqIOKAlCBWYW5pbGxhIEhUTUwvQ1NTL0pTLCBubyBoZWF2eSBmcmFtZXdvcmtzCi0gKipEb2NrZXIgQ29udGFpbmVyaXNlZCoqIOKAlCBTaW5nbGUgY29udGFpbmVyLCBtaW5pbWFsIGZvb3RwcmludAoKIyMg8J+agCBRdWljayBTdGFydAoKIyMjIDEuIEdldCB5b3VyIEltbWljaCBBUEkgS2V5CgoxLiBPcGVuIHlvdXIgSW1taWNoIHdlYiBpbnRlcmZhY2UKMi4gQ2xpY2sgeW91ciBwcm9maWxlIHBpY3R1cmUg4oaSICoqQWNjb3VudCBTZXR0aW5ncyoqIOKGkiAqKkFQSSBLZXlzKioKMy4gQ3JlYXRlIGEgbmV3IGtleSB3aXRoIGBhc3NldC5yZWFkYCBhbmQgYGFsYnVtLnJlYWRgIHBlcm1pc3Npb25zCgojIyMgMi4gUnVuIHdpdGggRG9ja2VyIENvbXBvc2UKCmBgYGJhc2gKZ2l0IGNsb25lIGh0dHBzOi8vZ2l0ZWEuaGlkZWF3YXlnYW1pbmcuY29tLmF1L2plc3Npa2l0dHkvZnJhbWJlLmdpdApjZCBmcmFtYmUKYGBgCgpFZGl0IGBkb2NrZXItY29tcG9zZS55bWxgIGFuZCBzZXQgeW91ciBgSU1NSUNIX1VSTGAgYW5kIGBJTU1JQ0hfQVBJX0tFWWAsIHRoZW46CgpgYGBiYXNoCmRvY2tlciBjb21wb3NlIHVwIC1kCmBgYAoKT3BlbiBgaHR0cDovL3lvdXItc2VydmVyOjMwMzBgIGluIGEgYnJvd3NlciBvbiB5b3VyIHRhYmxldC9zY3JlZW4uCgojIyMgMy4gUnVuIHdpdGggRG9ja2VyIGRpcmVjdGx5CgpgYGBiYXNoCmRvY2tlciBidWlsZCAtdCBmcmFtYmUgLgpkb2NrZXIgcnVuIC1kIFwKICAtLW5hbWUgZnJhbWJlIFwKICAtcCAzMDMwOjMwMDAgXAogIC1lIElNTUlDSF9VUkw9aHR0cDovL3lvdXItaW1taWNoLXNlcnZlcjoyMjgzIFwKICAtZSBJTU1JQ0hfQVBJX0tFWT15b3VyLWFwaS1rZXkgXAogIC0tcmVzdGFydCB1bmxlc3Mtc3RvcHBlZCBcCiAgZnJhbWJlCmBgYAoKIyMg8J+UkSBBdXRoZW50aWNhdGlvbgoKIyMjIEFkbWluIERhc2hib2FyZCBMb2dpbgoKUHJvdGVjdCB0aGUgYWRtaW4gZGFzaGJvYXJkIHdpdGggYSB1c2VybmFtZSBhbmQgcGFzc3dvcmQ6CgpgYGB5YW1sCmVudmlyb25tZW50OgogIC0gQURNSU5fVVNFUk5BTUU9YWRtaW4KICAtIEFETUlOX1BBU1NXT1JEPXlvdXItc2VjdXJlLXBhc3N3b3JkCmBgYAoKV2hlbiBgQURNSU5fUEFTU1dPUkRgIGlzIHNldCwgYWNjZXNzaW5nIGAvYWRtaW5gIHJlcXVpcmVzIHNpZ25pbmcgaW4uIFdoZW4gbm90IHNldCwgdGhlIGRhc2hib2FyZCBpcyBvcGVuICh1c2VmdWwgZm9yIHRydXN0ZWQgbG9jYWwgbmV0d29ya3MpLgoKIyMjIEFQSSBUb2tlbiBmb3IgRXh0ZXJuYWwgQWNjZXNzCgpFbmFibGUgdG9rZW4tYXV0aGVudGljYXRlZCBSRVNUIEFQSSBhY2Nlc3MgZm9yIEhvbWUgQXNzaXN0YW50LCBzY3JpcHRzLCBvciBvdGhlciBleHRlcm5hbCB0b29sczoKCmBgYHlhbWwKZW52aXJvbm1lbnQ6CiAgLSBGUkFNQkVfQVBJX1RPS0VOPXlvdXItc2VjcmV0LXRva2VuLWhlcmUKYGBgCgojIyDwn5SMIFJFU1QgQVBJCgpXaGVuIGBGUkFNQkVfQVBJX1RPS0VOYCBpcyBjb25maWd1cmVkLCB0aGUgZm9sbG93aW5nIGVuZHBvaW50cyBhcmUgYXZhaWxhYmxlOgoKIyMjIExpc3QgQ29ubmVjdGVkIEZyYW1lcwoKYGBgCkdFVCAvYXBpL2NsaWVudHMKQXV0aG9yaXphdGlvbjogQmVhcmVyIHlvdXItc2VjcmV0LXRva2VuLWhlcmUKYGBgCgpSZXR1cm5zIGFsbCBjb25uZWN0ZWQgZnJhbWUgY2xpZW50cyB3aXRoIHRoZWlyIHN0YXR1cywgSVAsIG5hbWUsIGFuZCBjb25maWcuCgojIyMgU2VuZCBDb21tYW5kIHRvIGEgRnJhbWUKCmBgYApQT1NUIC9hcGkvY2xpZW50cy86aWQvY29tbWFuZApBdXRob3JpemF0aW9uOiBCZWFyZXIgeW91ci1zZWNyZXQtdG9rZW4taGVyZQpDb250ZW50LVR5cGU6IGFwcGxpY2F0aW9uL2pzb24KCnsKICAiYWN0aW9uIjogIm5leHQiLAogICJwYXlsb2FkIjoge30KfQpgYGAKCkF2YWlsYWJsZSBhY3Rpb25zOiBgc3RhcnRgLCBgc3RvcGAsIGBuZXh0YCwgYHByZXZgLCBgc2xlZXBgLCBgd2FrZWAsIGByZWZyZXNoYCwgYHNldFNvdXJjZWAsIGBzZXRDb25maWdgCgojIyMgSG9tZSBBc3Npc3RhbnQgRXhhbXBsZQoKYGBgeWFtbApyZXN0X2NvbW1hbmQ6CiAgZnJhbWJlX25leHRfcGhvdG86CiAgICB1cmw6ICJodHRwOi8vZnJhbWJlLXNlcnZlcjozMDMwL2FwaS9jbGllbnRzL3t7IGNsaWVudF9pZCB9fS9jb21tYW5kIgogICAgbWV0aG9kOiBQT1NUCiAgICBoZWFkZXJzOgogICAgICBBdXRob3JpemF0aW9uOiAiQmVhcmVyIHlvdXItc2VjcmV0LXRva2VuLWhlcmUiCiAgICAgIENvbnRlbnQtVHlwZTogImFwcGxpY2F0aW9uL2pzb24iCiAgICBwYXlsb2FkOiAneyJhY3Rpb24iOiAibmV4dCJ9JwpgYGAKCkF1dGhlbnRpY2F0aW9uIGNhbiBhbHNvIGJlIHByb3ZpZGVkIHZpYSBgeC1hcGktdG9rZW5gIGhlYWRlciBvciBgP3Rva2VuPWAgcXVlcnkgcGFyYW1ldGVyLgoKIyMg8J+UlyBaZXJvLVRvdWNoIFVSTCBQYXJhbWV0ZXJzCgpTa2lwIHRoZSBzZXR1cCBzY3JlZW4gZW50aXJlbHkgYnkgcGFzc2luZyBxdWVyeSBwYXJhbWV0ZXJzLiBUaGlzIGlzIGlkZWFsIGZvciBkZWRpY2F0ZWQgZnJhbWVzIOKAlCBqdXN0IGJvb2ttYXJrIHRoZSBVUkwgb24gZWFjaCB0YWJsZXQ6Cgp8IFVSTCB8IFdoYXQgaXQgc2hvd3MgfAp8LS0tfC0tLXwKfCBgaHR0cDovL3NlcnZlcjozMDMwLz9hbGJ1bT1BTEJVTV9VVUlEYCB8IFBob3RvcyBmcm9tIGEgc3BlY2lmaWMgYWxidW0gfAp8IGBodHRwOi8vc2VydmVyOjMwMzAvP3BlcnNvbj1QRVJTT05fVVVJRGAgfCBQaG90b3Mgb2YgYSBzcGVjaWZpYyBwZXJzb24gKGZhY2UgcmVjb2duaXRpb24pIHwKfCBgaHR0cDovL3NlcnZlcjozMDMwLz9mYXZvcml0ZXNgIHwgRmF2b3JpdGUgcGhvdG9zIG9ubHkgfAp8IGBodHRwOi8vc2VydmVyOjMwMzAvP3JhbmRvbWAgfCBSYW5kb20gcGhvdG9zIGZyb20gdGhlIGxpYnJhcnkgfAoKWW91IGNhbiBmaW5kIGFsYnVtIGFuZCBwZXJzb24gVVVJRHMgaW4gSW1taWNoJ3Mgd2ViIGludGVyZmFjZSBVUkwgYmFyIHdoZW4gdmlld2luZyBhbiBhbGJ1bSBvciBwZXJzb24uCgojIyDimpnvuI8gQ29uZmlndXJhdGlvbgoKQWxsIHNldHRpbmdzIGFyZSB2aWEgZW52aXJvbm1lbnQgdmFyaWFibGVzOgoKfCBWYXJpYWJsZSB8IERlZmF1bHQgfCBEZXNjcmlwdGlvbiB8CnwtLS18LS0tfC0tLXwKfCBgSU1NSUNIX1VSTGAgfCAqKHJlcXVpcmVkKSogfCBZb3VyIEltbWljaCBzZXJ2ZXIgVVJMIHwKfCBgSU1NSUNIX0FQSV9LRVlgIHwgKihyZXF1aXJlZCkqIHwgSW1taWNoIEFQSSBrZXkgfAp8IGBTTElERVNIT1dfSU5URVJWQUxgIHwgYDMwYCB8IFNlY29uZHMgYmV0d2VlbiBwaG90b3MgfAp8IGBUUkFOU0lUSU9OX0RVUkFUSU9OYCB8IGAyYCB8IENyb3NzZmFkZSBkdXJhdGlvbiBpbiBzZWNvbmRzIHwKfCBgSU1BR0VfRklUYCB8IGBjb250YWluYCB8IGBjb250YWluYCBvciBgY292ZXJgIHwKfCBgU0hVRkZMRWAgfCBgdHJ1ZWAgfCBSYW5kb21pc2UgcGhvdG8gb3JkZXIgfAp8IGBCQUNLR1JPVU5EX0JMVVJgIHwgYHRydWVgIHwgU2hvdyBibHVycmVkIGJhY2tkcm9wIHwKfCBgU0hPV19DTE9DS2AgfCBgdHJ1ZWAgfCBEaXNwbGF5IGNsb2NrIG92ZXJsYXkgfAp8IGBTSE9XX0RBVEVgIHwgYHRydWVgIHwgRGlzcGxheSBkYXRlIG92ZXJsYXkgfAp8IGBTSE9XX0VYSUZgIHwgYHRydWVgIHwgRGlzcGxheSBwaG90byBtZXRhZGF0YSB8CnwgYFNIT1dfUFJPR1JFU1NgIHwgYHRydWVgIHwgRGlzcGxheSBwcm9ncmVzcyBiYXIgfAp8IGBSRUZSRVNIX0lOVEVSVkFMYCB8IGAzMDBgIHwgU2Vjb25kcyBiZXR3ZWVuIHNvdXJjZSByZWZyZXNoIGNoZWNrcyAobmV3IHBob3RvcykgfAp8IGBBTEJVTV9JRGAgfCAqKGVtcHR5KSogfCBBdXRvLXN0YXJ0IHdpdGggc3BlY2lmaWMgYWxidW0gKGVudi1iYXNlZCkgfAp8IGBTSE9XX0ZBVk9SSVRFU19PTkxZYCB8IGBmYWxzZWAgfCBBdXRvLXN0YXJ0IHdpdGggZmF2b3JpdGVzIChlbnYtYmFzZWQpIHwKfCBgQURNSU5fVVNFUk5BTUVgIHwgYGFkbWluYCB8IEFkbWluIGRhc2hib2FyZCBsb2dpbiB1c2VybmFtZSB8CnwgYEFETUlOX1BBU1NXT1JEYCB8ICooZW1wdHkpKiB8IEFkbWluIGRhc2hib2FyZCBwYXNzd29yZCAobGVhdmUgZW1wdHkgdG8gZGlzYWJsZSBhdXRoKSB8CnwgYEZSQU1CRV9BUElfVE9LRU5gIHwgKihlbXB0eSkqIHwgQVBJIHRva2VuIGZvciBSRVNUIGVuZHBvaW50IGFjY2VzcyAobGVhdmUgZW1wdHkgZm9yIG9wZW4gYWNjZXNzKSB8CnwgYFBPUlRgIHwgYDMwMDBgIHwgSW50ZXJuYWwgc2VydmVyIHBvcnQgKERvY2tlciBtYXBzIGV4dGVybmFsbHkgdmlhIGNvbXBvc2UpIHwKCiMjIPCfjq4gQ29udHJvbHMKCiMjIyBUb3VjaCAvIE1vdXNlCi0gKipMZWZ0IDIwJSoqIG9mIHNjcmVlbiDigJQgUHJldmlvdXMgcGhvdG8KLSAqKkNlbnRyZSA2MCUqKiDigJQgVG9nZ2xlIG92ZXJsYXkgKGNsb2NrLCBpbmZvLCBjbG9zZSBidXR0b24pCi0gKipSaWdodCAyMCUqKiDigJQgTmV4dCBwaG90bwoKIyMjIEtleWJvYXJkCi0gYOKGkGAgLyBg4oaSYCDigJQgUHJldmlvdXMgLyBOZXh0IHBob3RvCi0gYFNwYWNlYCDigJQgTmV4dCBwaG90bwotIGBGYCDigJQgVG9nZ2xlIGZ1bGxzY3JlZW4KLSBgSWAg4oCUIFRvZ2dsZSBpbmZvIG92ZXJsYXkKLSBgRXNjYCDigJQgRXhpdCB0byBhbGJ1bSBzZWxlY3Rpb24KCiMjIPCfk7EgVGFibGV0IFNldHVwIFRpcHMKCjEuIE9wZW4gdGhlIGZyYW1lIFVSTCBpbiB5b3VyIHRhYmxldCdzIGJyb3dzZXIgKHVzZSBhIGA/YWxidW09YCBvciBgP3BlcnNvbj1gIFVSTCBmb3IgemVyby10b3VjaCkKMi4gQWRkIHRvIEhvbWUgU2NyZWVuIGZvciBhIGZ1bGwtc2NyZWVuIGFwcCBleHBlcmllbmNlCjMuIEVuYWJsZSBraW9zayBtb2RlIG9yIGd1aWRlZCBhY2Nlc3MgdG8gbG9jayB0byB0aGUgYXBwCjQuIERpc2FibGUgc2NyZWVuIHRpbWVvdXQgaW4geW91ciBkZXZpY2Ugc2V0dGluZ3MKCiMjIPCfj5fvuI8gQXJjaGl0ZWN0dXJlCgpgYGAK4pSM4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSQICAgICAgICAg4pSM4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSQICAgICAgICAg4pSM4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSQCuKUgiAgIEJyb3dzZXIgICAg4pSCICBIVFRQICAg4pSCICAgIEZyYW1iZSAgICDilIIgICBBUEkgICDilIIgICAgSW1taWNoICAgICDilIIK4pSCICAoVGFibGV0KSAgICDilILil4TilIDilIDilIDilIDilIDilIDilIDilIDilrrilIIgIChOb2RlLmpzKSAg4pSC4peE4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pa64pSCICAgU2VydmVyICAgICDilIIK4pSU4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSYICA6MzAzMCAg4pSU4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSYICA6MjI4MyAg4pSU4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSYCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIOKWsgogICAgICAgICAgICAgICAgICAgIOKUjOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUtOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUkAogICAgICAgICAgICAgICAgICAgIOKUgiAgUkVTVCBBUEkgLyBXUyAgICDilIIKICAgICAgICAgICAgICAgICAgICDilIIgIChIb21lIEFzc2lzdGFudCkg4pSCCiAgICAgICAgICAgICAgICAgICAg4pSU4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSYCmBgYAoKVGhlIE5vZGUuanMgYmFja2VuZCBhY3RzIGFzIGEgc2VjdXJlIHByb3h5IOKAlCB5b3VyIEltbWljaCBBUEkga2V5IG5ldmVyIHJlYWNoZXMgdGhlIGJyb3dzZXIuIFRoZSBmcm9udGVuZCBwZXJpb2RpY2FsbHkgcG9sbHMgdGhlIGJhY2tlbmQgZm9yIG5ldyBwaG90b3Mgc28gYWxidW1zIHN0YXkgdXAgdG8gZGF0ZSB3aXRob3V0IHJlc3RhcnRpbmcuCgojIyDwn5OLIFZlcnNpb24gSGlzdG9yeQoKLSAqKjEuNC4wKiog4oCUIEFkbWluIGxvZ2luICh1c2VybmFtZS9wYXNzd29yZCksIEFQSSB0b2tlbiBhdXRoIGZvciBleHRlcm5hbCBhY2Nlc3MgKEhvbWUgQXNzaXN0YW50KSwgUkVTVCBlbmRwb2ludHMgKGBHRVQgL2FwaS9jbGllbnRzYCwgYFBPU1QgL2FwaS9jbGllbnRzLzppZC9jb21tYW5kYCkKLSAqKjEuMy4wKiog4oCUIEFkbWluIGRhc2hib2FyZCB3aXRoIHJlYWwtdGltZSBXZWJTb2NrZXQgZnJhbWUgbWFuYWdlbWVudCwgdmlkZW8gc3VwcG9ydCwgcGVyc29uL2ZhY2Ugc3VwcG9ydAotICoqMS4yLjEqKiDigJQgRml4IHBvcnQgbWFwcGluZyAoMzAzMDozMDAwIGV4dGVybmFsOmludGVybmFsKSwgZml4IFVSTCBwYXJhbSBhdXRvLWxhdW5jaCBub3Qgc3RhcnRpbmcgc2xpZGVzaG93Ci0gKioxLjIuMCoqIOKAlCBVUkwgcGFyYW1zIChgP2FsYnVtPWAsIGA/cGVyc29uPWAsIGA/ZmF2b3JpdGVzYCwgYD9yYW5kb21gKSwgcGVyc29uL2ZhY2Ugc3VwcG9ydCwgcGVyaW9kaWMgYXV0by1yZWZyZXNoLCBhcHAgaWNvbiwgZGVmYXVsdCBwb3J0IGNoYW5nZWQgdG8gMzAzMAotICoqMS4xLjAqKiDigJQgUmVicmFuZCB0byBGcmFtYmUKLSAqKjEuMC4wKiog4oCUIEluaXRpYWwgcmVsZWFzZTogYWxidW0gYnJvd3Nlciwgc2xpZGVzaG93IHdpdGggY3Jvc3NmYWRlLCBjbG9jay9kYXRlL0VYSUYgb3ZlcmxheXMsIHRvdWNoICYga2V5Ym9hcmQgY29udHJvbHMsIERvY2tlciBkZXBsb3ltZW50CgojIyDwn5OEIExpY2Vuc2UKCk1JVAo= \ No newline at end of file