Jump to content

Aperture - AI-Powered Recommendations for Emby


Recommended Posts

akacharos
Posted
1 hour ago, TheGru said:

📺 Shows You Watch: Your Personal DVR-Style Home Row (Finally, No More Noise!)

Let me explain a feature that's a possible game-changer for multi-user households: Shows You Watch.

The Problem

If you're like me, you've got multiple users on your system—family members, roommates, whoever—and they all request content. That's great! But here's what happens:

Latest Shows becomes a complete mess.

My wife is watching three different reality shows. My kids are into anime. My buddy who I gave access to is binging some crime documentary series. And me? I'm just trying to keep up with the two or three shows I actually care about.

The result? The shows I'm actively following get buried under a mountain of content I'll never watch. I'm scrolling through 30+ items just to find the one show I want to continue. It's exhausting.

The Solution: Shows You Watch

Think of Shows You Watch as a pseudo-DVR home row — but smarter.

Here's the concept: You mark the shows YOU actually care about in Aperture, and Aperture creates a custom library that displays ONLY those items. No noise. No clutter. Just your shows.

What You Get:

  • A dedicated "Shows You Watch" section on your home screen with only the series you're following
  • Next episode tracking in Aperture — see exactly what's coming up and when
  • Progress indicators In Aperture — know where you left off at a glance
  • "Days until" countdowns In Aperture — "Tomorrow", "In 3 days", etc.
  • Behind count — if you're 5 episodes behind, you'll know

How It Works:

  • The system tracks what you've been watching recently
  • Only continuing (not ended) series qualify
  • Your personal "Shows You Watch" library gets created automatically
  • This library appears in both Aperture AND your media server (Emby)

The best part? It's per-user. My wife has her Shows You Watch, I have mine. We're not stepping on each other's toes.

The Virtual Library Bonus

Here's where it gets really nice: Shows You Watch creates an actual library in your media server called something like "Shows You Watch - YourName".

So even when you're browsing directly in Emby (not through Aperture), you've got quick one-click access to continue your shows. It shows up right on your home screen. No more hunting.

If you're running a multi-user setup and haven't explored this feature yet, give it a shot. It's the difference between your media server feeling like a chaotic shared Netflix account vs. your own personalized DVR.

OK, now it makes sense as a function is such setup. But why create the mess in the first place with all family watching from the same Emby user? Why not create different users and sign-in to all 3 accounts from the same Emby client and simply switch profiles?
What am I missing here?

akacharos
Posted

Minor UX suggestion: If "Shows You Watch" feature is disabled, hide the relevant sidebar selection
image.thumb.png.6af35cf14fc11b5a4e70c007f01ae0dc.png

  • Agree 1
TheGru
Posted
4 minutes ago, akacharos said:

OK, now it makes sense as a function is such setup. But why create the mess in the first place with all family watching from the same Emby user? Why not create different users and sign-in to all 3 accounts from the same Emby client and simply switch profiles?
What am I missing here?

Do you have children? They do not always switch to their profiles.

But the issue is really this:

Emby default latest TV Shows. I have thousands of series, requested by many users. Of that list I watch 1 thing. I use the latest row to be reminded of new episodes of shows I watch, so by default I have to scroll through a ton of "noise"

image.thumb.png.0c0ce69e96459069ea3421fe1bd3c128.png

Conversely using Shows You Watch, I built a list of what I care about, and on my home screen I get a row of my shows. And when there is a new unwatched episode I get the green counter overlay. 

image.thumb.png.39f149f7d13b2f9b175a2762382652e9.png

The only limitation is that in order for TOP PICKS i have created to always show #1 through #10 or whatever, I have to turn off hiding watched episodes. Another feature that would be nice to be able to control by Homescreen row.

IE:
For Top Picks Library on homescreen, show all series even watched one
For Latest Shows Gru Watches Library, hide series with no unwatched

TheGru
Posted
2 minutes ago, akacharos said:

Minor UX suggestion: If "Shows You Watch" feature is disabled, hide the relevant sidebar selection

Adding this fix to v0.6.0 about to release

akacharos
Posted
3 minutes ago, TheGru said:

Do you have children? They do not always switch to their profiles.
 

She's only 10 months old, so I have plenty of time to worry about messing with my Emby profile 😀
Even so, I think you can still work around this by setting the kids user profile as the default upon startup and adults can switch to their own. I don't think my borderline adhd will cope with this "continue watching" mayhem!
Plus, they are ruining your own recommendation algorithm 🤣

TheGru
Posted

Aperture v0.6.0 Release Notes

This is a major release packed with new features including API key authentication, hybrid discovery with lazy enrichment, API architecture improvements, and an important license change. We've completely reorganized the API for better maintainability and added comprehensive OpenAPI documentation.


📜 License Change: MIT → AGPL-3.0

Aperture is now licensed under the GNU Affero General Public License v3.0.

What This Means

  • ✅ You can still use Aperture freely for personal use
  • ✅ You can modify and self-host Aperture
  • ✅ Source code remains open and auditable
  • ⚠️ If you modify Aperture and provide it as a network service, you must release your source code under AGPL-3.0

This change protects the project from proprietary forks while keeping it fully open source. The AGPL-3.0 is OSI-approved and widely used by projects like Nextcloud and Grafana.


🔑 API Key Authentication

New feature: Programmatic API access with API keys!

You can now create API keys for external integrations, scripts, and automation. This enables building custom tools that interact with Aperture.

Features

  • Admin UI — Manage keys in Settings → System → API Keys
  • Secure storage — Keys are SHA-256 hashed in the database
  • Flexible expiration — Never expire, or 7/30/60/90/180/365 days
  • Usage trackinglast_used_at timestamp for security monitoring
  • Easy identification — Keys use apt_ prefix (e.g., apt_abc123...)
  • Revocation — Soft delete with audit trail

Usage

Include the API key in your requests:

curl -H "X-API-Key: apt_your_key_here" https://your-aperture/api/movies

Creating API Keys

  • Navigate to Settings → System
  • Scroll to API Keys section
  • Click Create API Key
  • Choose a name and expiration period
  • Copy the key (shown only once!)

⚡ Discovery: Hybrid Mode with Lazy Enrichment

Discovery has been completely overhauled for dramatically better performance and filter coverage.

Lazy Enrichment (60-80% Faster Generation)

Not all candidates need full metadata upfront:

Enrichment Level What's Fetched When
Basic (all candidates) Poster, language, genres Always
Full (top 75-150) Cast, crew, runtime, keywords On-demand

This reduces TMDb API calls by 60-80% during discovery generation.

Dynamic Expansion

When you apply restrictive filters (language, genre, year), Discovery now automatically fetches more content to maintain ~50 results:

  • Filter reduces results below 20 candidates
  • Aperture queries TMDb with your active filters
  • New candidates are merged and re-ranked
  • Results expand seamlessly in the UI

Scaled Candidate Pools

Parameter Before After
Candidates per source 50 200
Total candidates 200 1,000
Enriched candidates 75 150

This ensures you always have plenty of recommendations, even with strict filters.


📺 Jellyseerr: Season Selection for TV Series

When requesting TV series through Jellyseerr, you can now select specific seasons instead of requesting the entire show.

A new modal appears when requesting a series, letting you:

  • View all available seasons
  • Select one or more seasons to request
  • See which seasons are already available

image.thumb.png.5edf902e3dd137221b5d4847bffd45b5.png


🏗️ API Architecture Overhaul

The entire API has been reorganized from monolithic route files into a modular, maintainable structure:

New Structure

routes/
├── movies/
│   ├── index.ts        # Route registration
│   ├── schemas.ts      # OpenAPI schemas
│   ├── types.ts        # TypeScript interfaces
│   └── handlers/       # Individual endpoint handlers
│       ├── list.ts
│       ├── detail.ts
│       ├── filters.ts
│       └── ...
├── series/
├── settings/
├── recommendations/
└── ...

Benefits

  • Better maintainability — Each domain is self-contained
  • Comprehensive OpenAPI docs — Visit /openapi for full API documentation
  • Type safety — Shared types prevent inconsistencies
  • Easier testing — Handlers can be tested in isolation

✨ Swagger UI Branding

The OpenAPI documentation at /openapi now features:

  • Aperture logo in the header
  • "Aperture API" branding
  • Correct GitHub links to documentation and repository
  • AGPL-3.0 license information
  • Collapsed tag sections by default for easier navigation

image.png.3b36a09a218e906e28cfda43eb50c844.png

image.png.cf150966d8dcc03193b1433f33abd795.png

image.png.27e1dbd4e47d659d08ab044eb94d8088.png

 


⚡ Discovery: Shared Candidate Pool

Multi-user Discovery now uses a shared candidate pool for dramatically improved efficiency:

How It Works

  • Shared enrichment — TMDb data is fetched once and stored in a shared pool
  • User-specific scoring — Each user's candidates are scored against their taste profile
  • Reduced API calls — Eliminates duplicate TMDb requests across users

Performance Gains

Scenario Before After
5 users, 100 candidates each 500 TMDb calls ~100 TMDb calls
API rate limit risk High Low

Database Schema

New discovery_candidate_pool table stores shared candidate data with full TMDb metadata.


🐛 Fixed: Playlist Navigation Using Wrong ID

The Problem

Clicking a poster in the similarity playlist modal navigated to the wrong page (e.g., /movies/12345 instead of the correct Aperture UUID).

The Cause

The getGraphPlaylistItems function was returning the media server item ID (Jellyfin/Emby ID) instead of the Aperture UUID.

The Fix

Enhanced getGraphPlaylistItems to:

  • Fetch items from media server
  • Look up Aperture UUIDs by matching provider_item_id
  • Return items with correct Aperture UUIDs and media type

🚀 Update Instructions

For Docker Users

# Pull the latest image
docker compose pull

# Restart with new version
docker compose up -d

Database migrations will be applied automatically on startup.

Migration Notes

License Change

If you have forked Aperture or are providing it as a service, please review the AGPL-3.0 license terms.

API Changes

The API endpoints themselves have not changed — only the internal organization. All existing integrations should continue to work without modification.

New endpoints:

  • GET /api/api-keys — List API keys (admin only)
  • POST /api/api-keys — Create API key (admin only)
  • DELETE /api/api-keys/:id — Delete API key (admin only)
  • POST /api/api-keys/:id/revoke — Revoke API key (admin only)
  • POST /api/discovery/:mediaType/expand — Dynamically expand discovery results

Database Migrations

Three new migrations run automatically on startup:

  • 0096_discovery_enrichment.sql — Adds is_enriched column to discovery candidates
  • 0097_api_keys.sql — Creates API keys table
  • 0098_discovery_pool.sql — Creates shared discovery pool table
  • Thanks 1
TheGru
Posted
5 minutes ago, akacharos said:

She's only 10 months old, so I have plenty of time to worry about messing with my Emby profile 😀
Even so, I think you can still work around this by setting the kids user profile as the default upon startup and adults can switch to their own. I don't think my borderline adhd will cope with this "continue watching" mayhem!
Plus, they are ruining your own recommendation algorithm 🤣

That's part of why I made it easy to mark shows unwatched from Aperture so I can undo their wreckage... and my wife is not innocent in any of this either!!!

  • Haha 1
GoldSpacer
Posted

I'm running into the error 'Bad Request' when trying to add new custom models on 0.6.0. The test completes successfully, but when I click add model it errors. This is the docker log I think may be associated with the error:

{"level":30,"time":1768865827706,"pid":1,"hostname":"7834fe657d60","name":"api","requestId":"req-v9","res":{"statusCode":400},"err":{"type":"Error","message":"body/function must be equal to one of the allowed values","stack":"Error: body/function must be equal to one of the allowed values\n    at defaultSchemaErrorFormatter (/app/node_modules/.pnpm/fastify@5.6.2/node_modules/fastify/lib/context.js:92:10)\n    at wrapValidationError (/app/node_modules/.pnpm/fastify@5.6.2/node_modules/fastify/lib/validation.js:257:17)\n    at validate (/app/node_modules/.pnpm/fastify@5.6.2/node_modules/fastify/lib/validation.js:175:16)\n    at preValidationCallback (/app/node_modules/.pnpm/fastify@5.6.2/node_modules/fastify/lib/handle-request.js:85:25)\n    at handler (/app/node_modules/.pnpm/fastify@5.6.2/node_modules/fastify/lib/handle-request.js:69:7)\n    at onDone (/app/node_modules/.pnpm/fastify@5.6.2/node_modules/fastify/lib/content-type-parser.js:219:5)\n    at AsyncResource.runInAsyncScope (node:async_hooks:206:9)\n    at bound (node:async_hooks:238:16)\n    at Parser.defaultJsonParser [as fn] (/app/node_modules/.pnpm/fastify@5.6.2/node_modules/fastify/lib/content-type-parser.js:307:7)\n    at IncomingMessage.onEnd (/app/node_modules/.pnpm/fastify@5.6.2/node_modules/fastify/lib/content-type-parser.js:289:27)","statusCode":400,"code":"FST_ERR_VALIDATION","validation":[{"instancePath":"/function","schemaPath":"#/properties/function/enum","keyword":"enum","params":{"allowedValues":["embedding","text_generation","chat_assistant"]},"message":"must be equal to one of the allowed values"}],"validationContext":"body"},"msg":"body/function must be equal to one of the allowed values"}

TheGru
Posted
1 minute ago, GoldSpacer said:

I'm running into the error 'Bad Request' when trying to add new custom models on 0.6.0. The test completes successfully, but when I click add model it errors. This is the docker log I think may be associated with the error:

{"level":30,"time":1768865827706,"pid":1,"hostname":"7834fe657d60","name":"api","requestId":"req-v9","res":{"statusCode":400},"err":{"type":"Error","message":"body/function must be equal to one of the allowed values","stack":"Error: body/function must be equal to one of the allowed values\n    at defaultSchemaErrorFormatter (/app/node_modules/.pnpm/fastify@5.6.2/node_modules/fastify/lib/context.js:92:10)\n    at wrapValidationError (/app/node_modules/.pnpm/fastify@5.6.2/node_modules/fastify/lib/validation.js:257:17)\n    at validate (/app/node_modules/.pnpm/fastify@5.6.2/node_modules/fastify/lib/validation.js:175:16)\n    at preValidationCallback (/app/node_modules/.pnpm/fastify@5.6.2/node_modules/fastify/lib/handle-request.js:85:25)\n    at handler (/app/node_modules/.pnpm/fastify@5.6.2/node_modules/fastify/lib/handle-request.js:69:7)\n    at onDone (/app/node_modules/.pnpm/fastify@5.6.2/node_modules/fastify/lib/content-type-parser.js:219:5)\n    at AsyncResource.runInAsyncScope (node:async_hooks:206:9)\n    at bound (node:async_hooks:238:16)\n    at Parser.defaultJsonParser [as fn] (/app/node_modules/.pnpm/fastify@5.6.2/node_modules/fastify/lib/content-type-parser.js:307:7)\n    at IncomingMessage.onEnd (/app/node_modules/.pnpm/fastify@5.6.2/node_modules/fastify/lib/content-type-parser.js:289:27)","statusCode":400,"code":"FST_ERR_VALIDATION","validation":[{"instancePath":"/function","schemaPath":"#/properties/function/enum","keyword":"enum","params":{"allowedValues":["embedding","text_generation","chat_assistant"]},"message":"must be equal to one of the allowed values"}],"validationContext":"body"},"msg":"body/function must be equal to one of the allowed values"}

let me check I refactored so much code building out the API and associated docs it is very possible I broke some stuff

TheGru
Posted
2 minutes ago, TheGru said:

let me check I refactored so much code building out the API and associated docs it is very possible I broke some stuff

Or a guy just makes a typo.... embeddings with an S... SMH 🤦

graytinc
Posted

Media server not configured
I am getting the above error, but I didn't see the setup page (it's being skipped somehow)

TheGru
Posted
3 minutes ago, graytinc said:

Media server not configured
I am getting the above error, but I didn't see the setup page (it's being skipped somehow)

I must have broken that in the refactor too. I am working through some other fixes. I will get to that as well. Thanks for reporting it.

TheGru
Posted
Just now, graytinc said:

noted thank you @TheGru

Found the bug, just fixed it. I am going to roll out 0.6.1 in a few minutes

  • Like 1
Jdiesel
Posted

Is anyone else getting a completely blank page when clicking on the recommendations tab on 0.6.0?

graytinc
Posted

i am ready for version 0.6.1

 

TheGru
Posted

Aperture v0.6.1 Release Notes

This release brings expanded Top Picks sources, TMDB integration for popularity rankings, language filtering, auto-request support for Jellyseerr, and several important bug fixes. Thank you to everyone for testing and reporting issues!


🎯 Top Picks: Multi-Source Popularity

Top Picks now supports multiple popularity sources beyond just local watch history and MDBList. You can now choose from:

New Popularity Sources

Source Description
Local Watch History Based on what your users are watching (renamed from "Local")
TMDB Popular Most popular movies/series on TMDB
TMDB Trending (Today) What's trending today on TMDB
TMDB Trending (Week) What's trending this week on TMDB
TMDB Top Rated Highest rated content on TMDB
MDBList Custom curated list from MDBList
Hybrid Blend local watch history with any external source

Hybrid Mode Improvements

Hybrid mode now lets you choose which external source to blend with your local watch history:

  • TMDB Popular
  • TMDB Trending (Today/Week)
  • TMDB Top Rated
  • MDBList

This gives you fine-grained control over how your Top Picks are curated.

Preview Modal

A new preview modal lets you see exactly what items would be included in your Top Picks before saving:

  • View matched items (already in your library)
  • See which items are missing
  • Test different source configurations

🌍 Language Filtering for Top Picks

Filter Top Picks results by original language:

  • Multi-select language filter - choose which languages to include (e.g., English, Spanish, Korean)
  • Include unknown languages option - toggle whether to include content without language metadata
  • Separate settings for movies and series
  • Dynamic filtering in preview modal - filter results on-the-fly to see counts
  • Applied everywhere - filters work for popularity lists, preview, and auto-requests

This is especially useful when using TMDB sources, which may include content from many countries.


📺 Series Season Selection for Requests

When requesting missing series through Jellyseerr, you now get season-level control:

  • Season selector modal opens when requesting a series
  • Choose specific seasons to request instead of the entire series
  • View season details including episode counts and air dates
  • Integrated with preview modal - request seasons directly from Top Picks preview

🤖 Auto-Request Missing Top Picks

New Jellyseerr integration for Top Picks!

When enabled, Aperture can automatically request missing Top Picks items through Jellyseerr:

Configuration

  • Enable/disable auto-requests separately for movies and series
  • Set limits on how many items to request per job run (default: 10)
  • Custom schedule via cron expression (default: weekly on Sundays)

How It Works

  • Top Picks job identifies popular content not in your library
  • Auto-request job checks which items are missing
  • Requests are submitted to Jellyseerr automatically
  • Your library grows with trending content your users want

🐛 Bug Fixes

Fixed: Setup Wizard Not Displaying

The setup wizard wasn't appearing for new installs or when admins tried to re-run it. This was caused by OpenAPI response schemas not matching the actual API responses, causing Fastify to strip required fields.

Fixed: Poster Images Not Loading

Poster images on the Browse page and movie/series detail pages weren't loading. The response schemas used camelCase property names (posterUrl) while the API returned snake_case (poster_url), causing the fields to be filtered out.

Fixed: Custom Model Creation

Adding custom AI models (for Ollama, OpenAI-compatible, OpenRouter, or HuggingFace) failed with "Bad Request" error. The API schema validation used incorrect enum values for the function field.

Fixed: Top Picks Preview

The Top Picks preview modal wasn't showing results for external sources like TMDB Popular, and the "Missing" tab was disabled. Completely rewrote the preview functionality to properly fetch and display both matched and missing items.

Fixed: Various API Response Issues

Multiple endpoints were returning empty objects or missing fields due to schema mismatches:

  • Backup list/create/restore endpoints
  • Media server configuration
  • Library settings
  • Job scheduler status
  • Trakt configuration

🚀 Update Instructions

For Docker Users

# Pull the latest image
docker compose pull

# Restart with new version
docker compose up -d

Database migrations will be applied automatically on startup.


Auto-Request

Auto-request is disabled by default. To enable:

  • Navigate to Settings → Top Picks
  • Enable auto-request for movies and/or series
  • Configure request limits
  • Ensure Jellyseerr is configured in Settings → Integrations
  • Like 1
GoldSpacer
Posted

Looks like I'm getting bad request when trying to save changes to the AI recommendation algorithm section.

Jdiesel
Posted

What is the maximum number of items in an Emby home row? Is it app dependent? I counted 12 on my Android.

TheGru
Posted
Just now, Jdiesel said:

What is the maximum number of items in an Emby home row? Is it app dependent? I counted 12 on my Android.

I believe 12, but since it also builds a library you can adjust how many recommendations to you liking. @GoldSpacermore Schema issues relative to the swagger implementation. Fixing now.

TheGru
Posted (edited)

No version bump, 0.6.1 may just be a triage build, just pull it again in like 3 minutes @GoldSpacer and let me know what else you run across. Sorry everyone, in order to make things more maintainable long term I was refactoring stuff while at my son's hockey tournament. Apparently one should not code in a cold ice rink!

Edited by TheGru
Jdiesel
Posted (edited)

Would it be possible to randomize, and periodically re-randomize, the date added field of entries in the Top Picks library so the 12 latest entries that show up on the home screen rotate with libraries greater than 12 items?

 

Even though the Top Picks task runs daily I find I am still seeing the same few on my home screen. 

Edited by Jdiesel
TheGru
Posted
1 minute ago, Jdiesel said:

Would it be possible to randomize, and periodically re-randomize, the date added field of entries in the Top Picks library so the 12 latest entries that show up on the home screen rotate with libraries greater than 12 items?

I could add a randomization job I suppose. Make it optional and you could define in the job scheduler how often it ran. I would need to implement a Fisher–Yates shuffle and map it to the current calendar in order to make it work but sure. Once I get all these bugs I created sorted out, i'll add it to the "roadmap"

  • Thanks 1
TheGru
Posted

I think we are back in business, anyone with a bad 0.6.1 just pull :latest again

TheGru
Posted

If any of you run in to more trouble I’ll tackle it tomorrow. 
 

For now I am going to go play some pinball with my kids before they go to sleep. 

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...