diff --git a/README.md b/README.md index 5013313..90fd113 100644 --- a/README.md +++ b/README.md @@ -1,57 +1,165 @@ +
+ # Jellybloom -A desktop media client for Jellyfin. i built this because i think nobody should have to rent back their own culture from a server they already run. +**A desktop media client for Jellyfin that actually feels good to use.** -This code is released under CC0 — take it, change it, run your own fork, or ignore it entirely. No attribution needed, no permission required. The culture belongs to everyone. +Built for people who own their own servers. No accounts, no subscriptions, no telemetry, no gatekeepers between you and your library. -## What Jellybloom does +
-Jellybloom is a complete alternative to the default Jellyfin web client, not a reskin. It's a React + TypeScript + Vite app wrapped in Tauri, and it talks directly to your Jellyfin server over the same APIs the official client uses. The difference is in the details — everything is denser, faster, and built around the idea that your library is yours to explore however you want. +Version +Platform +React +Tauri +License -### Browsing and search +
-The home page is built from shelves you can curate yourself, or you can let the app suggest rows based on what you actually watch. There's a unified search that fuzzy-matches across titles, people, and collections, with saved searches if you find yourself looking for the same things repeatedly. The poster grid uses virtualized scrolling so it stays smooth even with thousands of items, and a right-click on any poster opens a quick-look modal so you can peek at details without losing your scroll position. A notification bell in the header tracks new episodes and movies, deduplicated by series so a full-season drop doesn't flood the list. +
-### The player +## Why Jellybloom? -Most of the work went into the player. It's built on Vidstack with hls.js for HLS playback, and the entire chrome layer is custom. +You already run a Jellyfin server. You already own the media. So why does using it still feel like navigating a web app designed by committee? -Subtitles get special attention. The app renders ASS and SSA natively through libass-wasm, which means styled fansubs look exactly as intended. SRT, WebVTT, and plain text work too. You can search OpenSubtitles from inside the player, adjust size, font, outline, color, background opacity, and positioning. If the encode is bad, there's a picture filter for brightness, contrast, saturation, hue, and gamma. An audio graph gives you a compressor and parametric EQ for when the mix is too quiet or too loud, and you can pass through audio to your receiver when it's capable. +Jellybloom is a complete reimagining of the media client experience. It is not a reskin of jellyfin-web and it is not a fork. It is a ground-up desktop application that talks directly to your Jellyfin server and makes browsing, discovering, and watching actually pleasant. Fast, dense, responsive, and fully under your control. -Playback features include bookmarks with notes (shown as dots on the scrubber), A-B looping, chapter navigation, trickplay thumbnails on hover, speed control from 0.5x to 3x, and a sleep timer. There's a "still watching?" prompt that gently checks in after a few episodes so your stats don't run away from you. The resume prompt picks up where you left off but asks first, so you don't spoil a cold open. The end card shows the next episode or a related title as the credits roll. You can pop out a mini player to keep watching while doing other things, cast to another Jellyfin session on your network, or join a SyncPlay group to watch together with friends. +No cloud dependencies. No data collection. No feature gates. No one profits from your watch history except you. -Downloads work via blob URLs in the session, so you can save a movie or episode for offline playback. Trakt.tv scrobbling is optional and uses device-code auth — no browser redirects, no OAuth callbacks. Every keyboard action in the player and app can be rebound from the settings. +
-### Detail pages +## What it does -Every title gets a rich detail page that pulls from multiple sources. The hero shows a backdrop, poster, and logo overlay from Jellyfin or TMDB, plus rating badges and action buttons. Cast and crew are clickable, with in-library matches highlighted in a person's filmography. The composer gets their own block because the score matters. Awards come from Wikidata. Filming locations are mapped with Leaflet. Reception aggregates every available rating source — IMDb, TMDB, Rotten Tomatoes, Metacritic, plus your own. Reviews, trailers, production trivia, and tech specs (codec, container, resolution, HDR format) are all there. There's a collection strip for franchises, a series status block for air days and upcoming episodes, and anime filler detection based on a bundled dataset. If you have multiple versions of a file, a selector lets you pick. The watch timeline draws from your diary, and the personal section keeps your ratings, rewatch counts, and notes local. +### Browse your library -### Discover +The home page is built from shelves you can curate yourself, or you can let the app suggest rows based on what you actually watch. The poster grid uses virtualized scrolling so it stays smooth even with thousands of items. A right-click on any poster opens a quick-look modal so you can peek at details without losing your scroll position. A notification bell in the header tracks new episodes and movies, deduplicated by series so a full-season drop does not flood the list. -The Discover page is for finding things you don't already own. It needs a free TMDB API key and respects your region and adult-content preference. There's a personalized spotlight based on your top genre, a trending pick, mood chips that resolve to filtered rows, a roulette for random highly-rated titles, decade strips, canonical lists, an "on this day" feature, and a library gap finder that shows the highest-rated missing titles in genres you already watch. You can browse by genre, language, studio, or network, with inline expansion on each tile, or use advanced filters for year range, rating, vote count, and sort order. +Search is fuzzy and unified across titles, people, and collections. Saved searches stick around if you find yourself looking for the same things repeatedly. + +### Watch in a player that cares + +The player is built on Vidstack with hls.js, and the entire chrome layer is custom. Subtitles get special attention: ASS and SSA render natively through libass-wasm so styled fansubs look exactly as intended. SRT, WebVTT, and plain text work too. You can search OpenSubtitles from inside the player, adjust size, font, outline, color, background opacity, and positioning. + +Playback features include bookmarks with notes, A-B looping, chapter navigation, trickplay thumbnails on the scrubber, speed control from 0.5x to 3x, a picture filter for badly-encoded sources, and an audio graph with a compressor and parametric EQ. Audio passthrough lets your receiver handle decoding when it can. A quality menu switches streams on the fly. The resume prompt picks up where you left off but asks first so you do not spoil a cold open. + +The end card shows the next episode or a related title as the credits roll. The mini player pops out to a small floating window. A sleep timer fades out after your chosen duration. A "still watching?" prompt gently checks in after a few episodes so your stats do not run away from you. Downloads save movies and episodes for offline playback via blob URLs. Cast sends playback to another Jellyfin session on your network. SyncPlay lets you watch together with friends by joining or creating a group that locks playback state. + +Trakt.tv scrobbling is optional and uses device-code auth — no browser redirects, no OAuth callbacks. Every keyboard action can be rebound from settings. + +### Rich detail pages + +Every title gets a detail page that pulls from multiple sources. The hero shows a backdrop, poster, and logo overlay from Jellyfin or TMDB, plus rating badges and action buttons. Cast and crew are clickable, with in-library matches highlighted in a person's filmography. The composer gets their own block. Awards come from Wikidata. Filming locations are mapped with Leaflet. + +Reception aggregates every available rating source: IMDb, TMDB, Rotten Tomatoes, Metacritic, plus your own. Reviews come from TMDB. Videos include trailers and clips from YouTube. Trivia comes from Wikipedia. Tech specs show codec, container, resolution, HDR format, and audio channels, with badge icons for Dolby Vision, Atmos, DTS:X, and others. The collection strip shows franchise entries. Series status shows air days, next episode, season count, and production state. + +Anime filler detection flags filler and mixed-canon episodes based on a bundled dataset. If you have multiple versions of a file, a selector lets you pick. The watch timeline draws from your diary. The personal section keeps your ratings, rewatch counts, and notes local. The diary lets you log watches with a date, rating, and rewatch flag, and export to Markdown, JSON, or Letterboxd CSV. + +### Discover what you do not have + +The Discover page finds things you do not already own. It needs a free TMDB API key and respects your region and adult-content preference. There is a personalized spotlight based on your top genre, a trending pick, mood chips that resolve to filtered rows, a roulette for random highly-rated titles, decade strips, canonical lists, an "on this day" feature, and a library gap finder that shows the highest-rated missing titles in genres you already watch. + +You can browse by genre, language, studio, or network with inline expansion on each tile, or use advanced filters for year range, rating, vote count, and sort order. ### Profile and stats -The profile page tracks your watch streak, genre breakdown, year-in-review summary, personally top-rated titles, and recent diary entries. The diary itself lets you log watches with a date, rating, and rewatch flag, and export to Markdown, JSON, or Letterboxd CSV. +The profile page tracks your watch streak, genre breakdown, year-in-review summary, personally top-rated titles, and recent diary entries. ### Settings -The settings page is split into sections: server connection (with support for multiple servers and a live dashboard), playback defaults, audio and subtitle preferences, display options (density, accent color, UI scale, reduced motion), home page row toggles, detail page section toggles, episode defaults, discovery preferences, TMDB and Fanart.tv keys, Trakt.tv connection, Sonarr and Radarr configuration (including multiple instances and override rules), personal data management, privacy controls, and keyboard shortcut rebinding. +The settings page covers server connection with support for multiple servers and a live dashboard, playback defaults, audio and subtitle preferences, display options including density, accent color, UI scale, and reduced motion, home page row toggles, detail page section toggles, episode defaults, discovery preferences, TMDB and Fanart.tv keys, Trakt.tv connection, Sonarr and Radarr configuration with multiple instances and override rules, personal data management, privacy controls, and full keyboard shortcut rebinding. -## Stack +
-React 19 and TypeScript, Vite and Tailwind CSS, Tauri 2 for the desktop shell, Zustand for state, TanStack Query for data fetching, TanStack Virtual for lists, Vidstack and hls.js for playback, libass-wasm for subtitles, Framer Motion for transitions, Leaflet for maps, Fuse.js for fuzzy search, and Radix UI for accessible primitives. +## Accessibility -## Dev +Jellybloom targets WCAG 2.2 AAA conformance because a media client that only works for some people is not really a media client. -``` +| Area | Details | +|:-----|:--------| +| **Semantic HTML** | Proper landmarks, ARIA roles for menus, dialogs, tabs, and status regions | +| **Full keyboard navigation** | Every interactive element reachable and operable without a mouse | +| **Focus management** | Visible focus indicators on all controls, focus traps in modals with restore on close | +| **High contrast** | All themes meet AAA contrast ratios (7:1+ for normal text) | +| **Reduced motion** | Respects prefers-reduced-motion — all animations disabled when active | +| **Screen readers** | ARIA live regions for dynamic content, proper labels on icon-only buttons | +| **Touch targets** | All interactive controls meet 24x24px minimum | + +
+ +## Getting started + +### Prerequisites + +- Node.js 18+ +- Rust (latest stable) +- Tauri v2 prerequisites for your platform + +### Development + +```bash +git clone https://git.lashman.live/lashman/jellybloom.git +cd jellybloom npm install -npm run dev # browser dev -npm run tauri:dev # desktop dev -npm run build # production web build -npm run tauri:build # production desktop build ``` +```bash +# Browser dev server +npm run dev + +# Desktop dev +npm run tauri:dev + +# Production build +npm run tauri:build +``` + +
+ +## Technology stack + +| | Layer | Technology | +|:--|:--|:--| +| **Runtime** | Desktop shell | [Tauri v2](https://tauri.app/) | +| **Frontend** | UI framework | React 19, TypeScript, Vite | +| **Styling** | CSS | Tailwind CSS 4 | +| **State** | Management | Zustand | +| **Data** | Fetching and caching | TanStack Query | +| **Lists** | Virtualization | TanStack Virtual | +| **Playback** | Media engine | Vidstack, hls.js | +| **Subtitles** | Rendering | libass-wasm | +| **Animation** | Transitions | Framer Motion | +| **Maps** | Locations | Leaflet | +| **Search** | Fuzzy matching | Fuse.js | +| **Primitives** | Accessibility | Radix UI | + +
+ +## Project structure + +``` +jellybloom/ +├── src/ +│ ├── api/ API clients (Jellyfin, TMDB, Fanart, etc.) +│ ├── components/ UI components (player, detail, discover, layout) +│ ├── hooks/ Data hooks and player logic +│ ├── lib/ Utilities, formatters, device profile +│ ├── pages/ Route pages +│ ├── stores/ Zustand stores +│ └── types/ TypeScript declarations +├── src-tauri/ Rust backend +│ ├── src/ +│ ├── capabilities/ +│ └── icons/ +├── public/ Static assets +├── package.json +├── vite.config.ts +└── tsconfig.json +``` + +
+ ## License -CC0. This code is a common resource. The culture belongs to everyone. +Dedicated to the public domain under [CC0 1.0 Universal](LICENSE). + +No copyright, no restrictions, no permission needed. Take it, use it, change it, share it. The culture belongs to everyone.