{"id":51539458,"url":"https://github.com/daniel-alexander4/hespera","last_synced_at":"2026-07-09T12:01:19.303Z","repository":{"id":369332288,"uuid":"1289395990","full_name":"daniel-alexander4/hespera","owner":"daniel-alexander4","description":"Local-first media app for your music, TV, movies, and photos — automatic metadata matching, seekable ffmpeg transcoding, and a remote-friendly UI in one Go binary","archived":false,"fork":false,"pushed_at":"2026-07-04T17:56:46.000Z","size":17063,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-04T19:14:47.038Z","etag":null,"topics":["ffmpeg","golang","hls","media-library","media-server","movies","music-player","musicbrainz","photos","self-hosted","sqlite","tmdb","tv-shows"],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/daniel-alexander4.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-07-04T17:38:32.000Z","updated_at":"2026-07-04T17:54:57.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/daniel-alexander4/hespera","commit_stats":null,"previous_names":["daniel-alexander4/hespera"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/daniel-alexander4/hespera","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/daniel-alexander4%2Fhespera","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/daniel-alexander4%2Fhespera/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/daniel-alexander4%2Fhespera/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/daniel-alexander4%2Fhespera/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/daniel-alexander4","download_url":"https://codeload.github.com/daniel-alexander4/hespera/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/daniel-alexander4%2Fhespera/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35298222,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-09T02:00:07.329Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ffmpeg","golang","hls","media-library","media-server","movies","music-player","musicbrainz","photos","self-hosted","sqlite","tmdb","tv-shows"],"created_at":"2026-07-09T12:01:18.466Z","updated_at":"2026-07-09T12:01:19.279Z","avatar_url":"https://github.com/daniel-alexander4.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Hespera\n\nA local app for your **Music, TV, Movies, and Photos**, with automatic metadata\nmatching. Written in Go: a single static binary that opens a chromeless app\nwindow on your machine (loopback-only — a single-machine app, not a network\nserver). SQLite storage, server-rendered HTML, no external services required to\nrun. A headless server mode is also available if you want to reach it from\nother devices.\n\nLicensed under the [GNU AGPL v3](LICENSE); third-party attributions in\n[`THIRD_PARTY_LICENSES.md`](THIRD_PARTY_LICENSES.md).\n\n## Features\n\n- **Music** — MusicBrainz matching with Cover Art Archive covers, artist bios\n  and images, synced karaoke-style lyrics (LRCLIB), persistent playlists and\n  one-click instant mixes, shuffle by era or popularity, duplicate detection,\n  and a per-track tag editor that writes back to your files.\n- **TV \u0026 Movies** — TMDB matching (posters, backdrops, cast, collections,\n  related titles), direct play when your browser can handle the file and\n  **seekable on-demand HLS transcoding** when it can't, embedded +\n  on-demand OpenSubtitles subtitles, **skip-intro detected by audio\n  fingerprinting**, scrub-preview thumbnails, per-episode screen-capture\n  thumbnails, Up Next auto-advance, and watched/resume tracking.\n- **Photos** — a capture-date timeline built from EXIF (with a folders view\n  and year filters); home-video clips play through the same engine as TV.\n- **Library care** — a filesystem watcher auto-scans new media, corruption\n  detection with lossless container auto-repair, loudness leveling, and jobs\n  interrupted by a shutdown resume automatically on the next launch.\n- **Couch-friendly** — the whole UI drives with arrow keys or a TV remote,\n  scales itself to the physical display size, and honors hardware media keys.\n- **Local-first** — one binary, SQLite, your files stay yours: no accounts,\n  no telemetry, and external services are used only to *fetch* metadata.\n\n## Install\n\nHespera ships as a self-contained binary — the web UI assets are embedded, so\nthere is no directory to deploy alongside it. Builds are available for **Linux,\nmacOS, and Windows** (amd64 + arm64).\n\n### Debian / Ubuntu (recommended)\n\n```sh\n./install.sh\n```\n\nBuilds Hespera, packages a `.deb`, and installs it — placing `hespera` and\n`hescli` in `/usr/bin`, an app-menu entry + icon, and pulling the runtime\ndependencies (**ffmpeg**, **openssh-client**) via apt. No background service is\ninstalled; launch **Hespera** from your app menu (or run `hespera`) and it opens\nan app window:\n\n```sh\nhespera\n```\n\n### Other platforms (macOS, Windows, other Linux)\n\nBuild the binaries (or grab one from `dist/` after `./build.sh`) and run the\n`hespera` binary directly — it's fully self-contained. **ffmpeg must be on your\nPATH** for TV/movie playback (transcoding and tag recovery); music and metadata\nwork without it.\n\n- macOS: `brew install ffmpeg`\n- Windows: `winget install ffmpeg` (or `choco install ffmpeg`)\n\n```sh\n./hespera-\u003cversion\u003e-\u003cos\u003e-\u003carch\u003e\n```\n\n## Build from source\n\nRequires Go 1.23+.\n\n```sh\nmake build        # local ./bin/hespera + ./bin/hescli (quick dev build)\nmake dist         # cross-compile all platforms + .deb packages into dist/\nmake install      # build, package, and install on this Debian/Ubuntu machine\nmake test         # go test ./...\nmake release      # build + publish dist/ as GitHub release v\u003cVERSION\u003e (needs gh + nfpm)\n```\n\n`build.sh` produces one cgo-free static `hespera` binary per OS/arch in `dist/`,\nplus `.deb` packages for Linux amd64/arm64 (needs\n[`nfpm`](https://github.com/goreleaser/nfpm): `go install\ngithub.com/goreleaser/nfpm/v2/cmd/nfpm@latest`).\n\n## Run\n\n```sh\nhespera\n```\n\nThat's it — Hespera opens an app window automatically, bound to a random\nloopback port (so it never collides with anything else). The window is a\nChromium-family app-mode window (Chrome, Chromium, Edge, or Brave — on Linux\none of these must be installed; Hespera deliberately won't hand the app\nwindow to a non-Chromium default browser). On first run, the\nwindow walks you through pointing it at your media folder and adding a library;\nyou can also set the media folder under **Libraries** or with\n`HESPERA_MEDIA_ROOT`. It stores its database, caches, and downloaded artwork in\na per-user data directory (`~/.config/hespera` on Linux, the equivalent on\nmacOS/Windows).\n\n`HESPERA_NO_BROWSER=1` runs **server mode** instead: no window, binds\n`HESPERA_LISTEN` (default `127.0.0.1:8080` — loopback only). To reach it from\nother devices, opt in explicitly with `HESPERA_LISTEN=:8080`.\n\n### Serving your household\n\nTo let other devices in the house use Hespera (phones, laptops, a TV\nbrowser), run server mode on the machine that holds the media, started at\nboot by a systemd **user** unit:\n\n```ini\n# ~/.config/systemd/user/hespera.service\n[Unit]\nDescription=Hespera media server\n\n[Service]\nEnvironment=HESPERA_NO_BROWSER=1\nEnvironment=HESPERA_LISTEN=:8080\nExecStart=/usr/bin/hespera\nRestart=on-failure\n\n[Install]\nWantedBy=default.target\n```\n\n```sh\nsystemctl --user daemon-reload \u0026\u0026 systemctl --user enable --now hespera\nloginctl enable-linger $USER          # keep it running when logged out\nsudo ufw allow from 192.168.1.0/24 to any port 8080 proto tcp   # your LAN subnet\n```\n\nDevices then browse `http://\u003chostname\u003e:8080`. Phones and laptops get the\nright UI scale automatically; a TV browser can pin the 10-foot scale once\nwith `?scale=tv` (it persists per browser). On the server's own screen,\n**just launch Hespera from the app menu as usual** — when a running instance\nis detected (the service), the launcher *attaches*: it opens the same\nchromeless app window onto it instead of starting a second copy, and exits.\nStop the service and the icon goes back to launching a standalone app.\n\nNotes for shared use: Hespera has one household-wide state — watched marks,\nresume positions, and playlists are shared by everyone (there are no user\nprofiles); the security posture below applies (trusted network only); and\nthe topbar power button only works from the server machine itself, so a\nphone can't shut the family server down.\n\n### Security posture\n\nHespera has **no authentication layer, by design** — it is a single-machine\nmedia app, and in app mode it is only reachable from your own computer.\nThat means anyone who can reach the port in server mode has *full* access:\nnot just playback, but the tag editor (writes into your music files), the\nintegrity auto-repair (the one path that rewrites media files), settings,\nand shutdown. The built-in CSRF guard stops hostile web pages, not direct\nnetwork peers. Hence the loopback default: exposing Hespera to a network is\nan explicit choice, and should only be made on a network you trust end to\nend. For anything beyond that (shared LAN, remote access), put a reverse\nproxy with authentication in front (Caddy `basic_auth`, nginx `auth_basic`,\nTailscale, etc.) — that is the supported pattern, not an app-level login.\n\n### Performance: sharing a disk with another media server\n\nHespera runs all of its background work — library scans, integrity checks,\nloudness analysis, thumbnail and preview generation — at **idle I/O priority**\n(and nice 19), so a long scan yields the disk to anything that needs it right\nnow, like Plex or Jellyfin streaming a movie from the same drive.\n\nOne catch: the kernel only enforces I/O priorities when the disk's scheduler\nsupports them, and the default on most distros (`mq-deadline`) ignores them.\nCheck yours (replace `sdb` with your media disk):\n\n```sh\ncat /sys/block/sdb/queue/scheduler   # [mq-deadline] → priorities are ignored\n```\n\nFor a spinning disk (external USB drives especially), switch it to `bfq`:\n\n```sh\n# apply now\nsudo modprobe bfq\necho bfq | sudo tee /sys/block/sdb/queue/scheduler\n\n# make it stick across reboots\necho bfq | sudo tee /etc/modules-load.d/bfq.conf\necho 'ACTION==\"add|change\", KERNEL==\"sdb\", ATTR{queue/scheduler}=\"bfq\"' \\\n  | sudo tee /etc/udev/rules.d/60-media-disk.rules\n```\n\nWith `bfq`, Hespera's background jobs still use the disk's full speed when it\nis otherwise idle — they only step aside under contention. On NVMe/SSD media\ndisks this tuning rarely matters; it is for rotational disks shared with\nplayback.\n\n### Configuration\n\nDay-to-day settings (media folder, API keys, feature toggles, subtitle\ndefaults) live in the in-app **Settings** page. Environment variables cover\nthe rest — all `HESPERA_`-prefixed:\n\n| Variable | Default | Purpose |\n|----------|---------|---------|\n| `HESPERA_NO_BROWSER` | (unset) | Set → **server mode**: no app window, honors `HESPERA_LISTEN`. Unset → app mode (chromeless window on a random loopback port) |\n| `HESPERA_LISTEN` | `127.0.0.1:8080` | Server-mode listen address — loopback by default; LAN exposure is an explicit opt-in (`:8080`) |\n| `HESPERA_DATA_DIR` | per-user config dir | Database, caches, artwork |\n| `HESPERA_DB_PATH` | `\u003cdata dir\u003e/hespera.sqlite` | Database path |\n| `HESPERA_MEDIA_ROOT` | home dir | Media root (the path-containment boundary; also settable in Settings → Libraries) |\n| `HESPERA_TMDB_API_KEY` | | TMDB key for TV/movie matching (also settable in Settings) |\n| `HESPERA_FANARTTV_API_KEY` | | Optional fanart.tv key — artist image backfill |\n| `HESPERA_THEAUDIODB_API_KEY` | | Optional TheAudioDB key — artist bio/image backfill |\n| `HESPERA_LASTFM_API_KEY` | | Optional Last.fm key — popularity blend for shuffles |\n| `HESPERA_OPENSUBTITLES_API_KEY` | | Optional OpenSubtitles key — on-demand subtitle search |\n| `HESPERA_OPENSUBTITLES_USER_AGENT` | `Hespera v1.0` | OpenSubtitles registered consumer UA |\n| `HESPERA_LOG_LEVEL` | `info` | Log level (`debug`/`info`/`warn`/`error`). Per-request access logs are emitted at `debug`; the default `info` keeps request serving off the log path. Set `debug` to see per-request logs |\n| `HESPERA_FFMPEG_CONCURRENCY` | 4 | Max concurrent ffmpeg/ffprobe processes |\n| `HESPERA_HLS_ENCODER` | `software` | HLS video encoder: `software` (libx264) or `vaapi` (opt-in hardware encode) |\n| `HESPERA_HLS_SEGMENT_CONCURRENCY` | 1 | Max concurrent HLS segment transcodes (keeps prefetch bursts off every core) |\n| `HESPERA_FFMPEG_ACQUIRE_TIMEOUT` | 2s | How long foreground ffmpeg work waits for a slot |\n| `HESPERA_TV_HLS_CACHE_MAX_BYTES` | 20GiB | HLS transcode cache budget |\n| `HESPERA_TV_CACHE_MAX_AGE` | 72h | HLS cache entry max age |\n| `HESPERA_TRICKPLAY_CACHE_MAX_BYTES` | 10GiB | Scrub-preview sprite cache budget |\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdaniel-alexander4%2Fhespera","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdaniel-alexander4%2Fhespera","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdaniel-alexander4%2Fhespera/lists"}