Jump to content

Aperture - AI-Powered Recommendations for Emby


Recommended Posts

TheGru
Posted

@GoldSpacer @ebr @TeamB @JdieselI've decided to stop worrying about trying to manage deduplication. The juice just isn't worth the squeeze and tech debt I pick up trying to solve for it is not something I want to deal with long term. 

I am hopeful that the Emby devs may decide to decouple continue watching from latest on the home screen in a near term beta release which solves not just this but other related configurations that create duplicates in continue watching. 

Please switch back to :latest tagged builds as nothing new will be added to :continuewatching.

Thank you

  • Like 1
Posted
1 minute ago, TheGru said:

I've decided to stop worrying about trying to manage deduplication

I think that is prudent.

2 minutes ago, TheGru said:

I am hopeful that the Emby devs may decide to decouple continue watching from latest on the home screen in a near term beta release which solves not just this but other related configurations that create duplicates in continue watching.

I think it is more likely that future features will make it unnecessary for integrations like Aperture to create duplicates.  IOW - use proper constructs like playlists once we have the ability to show these on the home screen.

  • Like 1
Jdiesel
Posted
2 minutes ago, TheGru said:

@GoldSpacer @ebr @TeamB @JdieselI've decided to stop worrying about trying to manage deduplication. The juice just isn't worth the squeeze and tech debt I pick up trying to solve for it is not something I want to deal with long term. 

I am hopeful that the Emby devs may decide to decouple continue watching from latest on the home screen in a near term beta release which solves not just this but other related configurations that create duplicates in continue watching. 

Please switch back to :latest tagged builds as nothing new will be added to :continuewatching.

Thank you

That's understandable and from a maintenance standpoint, much easier for you in the long term. As @TeamB mentioned the true solution is to have the ability to "pin" playlists to the home row. Aperture would be able to create playlists rather that libraries with symlinks or strm files.

TheGru
Posted (edited)
35 minutes ago, ebr said:

IOW - use proper constructs like playlists once we have the ability to show these on the home screen.

I would love to see it happen, and would be more than happy to adapt Aperture to that. I don't think it would be much effort at all, as along as playlists can be associated on a per user basis!

Edited by TheGru
TheGru
Posted

Aperture v0.5.4 Release Notes

So with all the duplicate distractions out of the way for now I have added Hugging Face as a new AI provider and introduced configurable embedding dimensions for custom models.


🤗 Hugging Face Provider Support

You can now use Hugging Face Inference API as your AI provider! Access thousands of models from Meta, DeepSeek, Qwen, and more.

How to Use

  • Go to Admin → Settings → AI / LLM
  • Select Hugging Face from the provider dropdown
  • Enter your API key from huggingface.co/settings/tokens
  • Click Add Custom Model... to enter any model name
  • Test the connection and save

Custom Models Only

Like OpenRouter, Hugging Face uses custom model IDs — you enter the exact model path:

  • meta-llama/Llama-3.3-70B-Instruct
  • deepseek-ai/DeepSeek-V3-0324
  • Qwen/Qwen3-235B-A22B-Instruct-2507
  • google/gemma-3-27b-it
  • moonshotai/Kimi-K2-Instruct

Find available models at huggingface.co/models or browse inference models at huggingface.co/inference/models.


📐 Configurable Embedding Dimensions

Custom embedding models now require you to specify the vector dimension size. This ensures embeddings are stored correctly and similarity searches work properly.

Why This Matters

Different embedding models output different dimension sizes. If the dimension doesn't match what the model outputs, embedding generation will fail or produce incorrect results.

How to Use

When adding a custom embedding model:

  • Select your provider (Ollama, OpenRouter, HuggingFace, or OpenAI-Compatible)
  • Choose Embeddings as the function
  • Click Add Custom Model...
  • Enter the model name
  • Select the correct dimension from the dropdown - You will need to do your own research here.
  • Test and save

Supported Dimensions

Dimensions Example Models
384 granite-embedding-30m-english, all-MiniLM-L6-v2
768 nomic-embed-text, snowflake-arctic-embed-m, e5-base-v2
1024 mxbai-embed-large, snowflake-arctic-embed-l, voyage-multilingual-2
1536 OpenAI text-embedding-3-small, text-embedding-ada-002
3072 OpenAI text-embedding-3-large
4096 nv-embed-v2, larger custom models

4096 Dimension Support

Added support for 4096-dimension embeddings using binary quantized HNSW indexes (pgvector's standard indexes have a 4000-dimension limit).


🚀 Update Instructions

For Docker Users

# Pull the latest image
docker compose pull

# Restart with new version
docker compose up -d

Database Migrations

This update includes two migrations that run automatically on startup:

  • 0091_embedding_dimensions_4096.sql — Adds embedding dimension support and 4096-dim tables
  • 0092_huggingface_provider.sql — Adds HuggingFace to provider constraint

Post-Update Steps

  • Clear browser cache — Or hard refresh (Cmd+Shift+R / Ctrl+Shift+R)
  • Re-add custom embedding models — Existing custom embedding models may need to be re-added with the correct dimension selected

Enjoy Hugging Face support and better embedding control! 🚀

  • Thanks 1
GoldSpacer
Posted (edited)

I tried the custom embedding model with the dimension setting of 4096 but it still fails with:

11:27:24 AM❌ Job failed: No embedding model configured or dimensions unknown

image.png.051d82d53f12a293acdafa1b5849c7f8.png

I ran this against the ollama docker and it returned successfully if it helps:

curl http://localhost:7869/api/embeddings   -H "Content-Type: application/json"   -d '{
    "model": "qwen3-embedding:8b",
    "prompt": "This is a test sentence",
    "dimensions": 4096
  }'

output: {"embedding":[0.01668785...  ...]}

edit: I tested on 0.5.5 and with 3072 dims with the same results.

Edited by GoldSpacer
TheGru
Posted

Aperture v0.5.5 Release Notes

This is a bug fix release that addresses two issues affecting new users during initial setup.


🔐 SESSION_SECRET YAML Parsing Fix

Fixed an issue where random session secrets containing special characters (like #, $, !) would cause the container to fail with:

SESSION_SECRET: { _errors: [ 'String must contain at least 32 character(s)' ] }

What Was Happening

YAML interprets # as the start of a comment. If your generated key contained #, everything after it was ignored:

# Before: YAML sees only "abc123xyz" (9 chars) because #... is a comment
SESSION_SECRET: abc123xyz#789qwerty456asdf123zzzz

The Fix

All docker-compose files now quote the SESSION_SECRET placeholder and include a warning comment:

# ⚠️  IMPORTANT: Keep the quotes! Special characters like # break YAML without them.
SESSION_SECRET: 'PASTE_YOUR_RANDOM_KEY_HERE'

If you're hitting this issue: Just wrap your SESSION_SECRET value in quotes.


🛡️ Session Validation Error Handling

Fixed an issue where changing SESSION_SECRET (or other session-related issues) would cause a completely blank page with 500 errors.

What Was Happening

The auth hook runs on every request — including static files like JavaScript bundles. If session validation failed (due to a changed secret, database issue, or corrupted cookie), it crashed the entire request, preventing the page from loading at all.

The Fix

  • Auth errors no longer crash requests — Static files load normally even when session validation fails

  • Helpful error message — The login page now shows a warning explaining what happened:

    "Your session was invalid. This can happen if the server was reconfigured. Please log in again."

  • Automatic cookie cleanup — Invalid session cookies are automatically cleared


🚀 Update Instructions

For Docker Users

# Pull the latest image
docker compose pull

# Restart with new version
docker compose up -d

No database migrations in this release.

Post-Update Steps

  • Clear browser cache — Or hard refresh (Cmd+Shift+R / Ctrl+Shift+R)

Thanks to @ebr from the Emby team for reporting these issues! 🙏

TheGru
Posted (edited)
6 minutes ago, GoldSpacer said:

I tried the custom embedding model with the dimension setting of 4096 but it still fails with:

11:27:24 AM❌ Job failed: No embedding model configured or dimensions unknown

I ran this against the ollama docker and it returned successfully if it helps:

curl http://localhost:7869/api/embeddings   -H "Content-Type: application/json"   -d '{
    "model": "qwen3-embedding:8b",
    "prompt": "This is a test sentence",
    "dimensions": 4096
  }'

output: {"embedding":[0.01668785...  ...]}

edit: I tested on 0.5.5 and with 3072 dims with the same results.

can you check the aperture log in the docker container? 

i added the model and am testing this locally, i hit the error so I can debug this, standby

Edited by TheGru
  • Like 1
TheGru
Posted
4 minutes ago, TheGru said:

can you check the aperture log in the docker container? 

i added the model and am testing this locally, i hit the error so I can debug this, standby

Lol, i forgot to connect the embedding functions to the custom DB values, it was only looking at the hardcoded options...

  • Haha 1
TheGru
Posted

Aperture v0.5.6 Release Notes

This is a bug fix release that resolves an issue with custom embedding models failing to run embedding jobs.


🤖 Custom Embedding Model Dimensions Fix

Fixed an issue where custom embedding models (like Ollama's qwen3-embedding:8b) would fail with:

❌ Job failed: No embedding model configured or dimensions unknown

What Was Happening

When you add a custom embedding model, the embedding dimensions are stored in the database's custom_ai_models table. However, when running embedding jobs, the system only checked the hardcoded model registry for known models — it never looked up custom models from the database.

This meant custom embedding models would:

  • ✅ Pass the connection test
  • ✅ Show as configured in settings
  • ❌ Fail when actually running any embedding job

The Fix

The getCurrentEmbeddingDimensions() function now:

  • First checks built-in models from the hardcoded registry
  • If not found, queries the custom_ai_models table
  • Returns the embedding dimensions you specified when adding the model

What's Now Working

All embedding-related functionality now works with custom models:

  • ✅ Generate Movie Embeddings
  • ✅ Generate Series Embeddings
  • ✅ Generate Episode Embeddings
  • ✅ Semantic Search
  • ✅ Similarity Graphs
  • ✅ Recommendations
  • ✅ Taste Profiles
  • ✅ AI Assistant queries

🚀 Update Instructions

For Docker Users

# Pull the latest image
docker compose pull

# Restart with new version
docker compose up -d

No database migrations in this release.


📋 Full Changelog

Bug Fixes:

  • fix: custom embedding models now correctly resolve dimensions from database

If you were affected by this issue, your custom embedding model should now work. Thanks @GoldSpacerfor testing all of this — just update and re-run your embedding jobs! 🎉

  • Like 1
Neminem
Posted
9 minutes ago, TheGru said:

Lol, i forgot to connect the embedding functions to the custom DB values, it was only looking at the hardcoded options...

It happens 😂🤣

TheGru
Posted (edited)

Aperture v0.5.7 Release Notes

This release fixes a significant performance issue with the Watcher Identity tabs in User Settings accessed via the logged in user's Avatar in the upper right top bar.
If you've noticed slow load times when switching between Movies and Series tabs, this one's for you!


🐛 Fixed: Watcher Identity Auto-Regeneration Bug

The Problem

The Watcher Identity tabs were triggering a full AI regeneration of your taste synopsis on every page load after 24 hours — completely ignoring your configured refresh interval setting (e.g., 1 month).

This meant:

  • ❌ 5-10+ second load times every day
  • ❌ Unnecessary AI API calls
  • ❌ Wasted compute on multiple database queries + embedding calculations
  • ❌ Your "Refresh Interval" setting in Identity Settings was being ignored

The Fix

The getTasteSynopsis() and getSeriesTasteSynopsis() functions no longer auto-regenerate on page load. They now:

  • ✅ Return cached synopsis immediately (if exists)
  • ✅ Let the background job handle periodic refresh based on your settings
  • ✅ Only regenerate when you explicitly click "Generate Identity"

⚡ Database Query Optimizations

Even with the caching fix, we also optimized the "quick stats" queries that display alongside your identity synopsis.

Before

  • Movie stats: 4 sequential database queries
  • Series stats: 5 sequential database queries

After

  • Movie stats: 1 combined CTE query
  • Series stats: 1 combined CTE query

This reduces database round trips by ~75-80% for the stats that always need to load.


📊 New Database Index

Added a composite index to improve series watch history query performance:

CREATE INDEX idx_watch_history_user_episode_composite 
ON watch_history(user_id, episode_id) 
WHERE episode_id IS NOT NULL;

This optimizes the 3-way joins (watch_history → episodes → series) that were slower than movie queries.


🚀 Update Instructions

For Docker Users

# Pull the latest image
docker compose pull

# Restart with new version
docker compose up -d

The new database index will be applied automatically on startup.

Expected Performance Impact

Scenario Before After
Page load (synopsis >24h old) 5-10+ seconds ~100-200ms
Database queries for stats 4-5 sequential 1 combined

Your Watcher Identity tabs should now load almost instantly! 🎉

Edited by TheGru
GoldSpacer
Posted

Can confirm generating embeddings work now. Got a new error when trying to generate recommendations:

12:35:41 PM❌ user: different halfvec dimensions 4096 and 1024

TheGru
Posted
6 minutes ago, GoldSpacer said:

Can confirm generating embeddings work now. Got a new error when trying to generate recommendations:

12:35:41 PM❌ user: different halfvec dimensions 4096 and 1024

fix: prevent halfvec dimension mismatch when embedding model changes

When users switch embedding models (e.g., from 4096-dim to 1024-dim),
the stored taste profile would have the old dimensions, causing
PostgreSQL to throw 'different halfvec dimensions' errors when
comparing against the new embedding table.

Changes:

  • getUserTasteProfile now checks if the stored profile's embedding
    model matches the currently active model
  • If model changed or model info is missing, profile is rebuilt
  • Movie and series pipelines now store embedding model ID when
    creating profiles via legacy fallback path

Fixes dimension mismatch errors for both movie and series recommendations.

@GoldSpacercan you pull from :dev and test this before I merge it to latest?

TheGru
Posted
26 minutes ago, TheGru said:

fix: prevent halfvec dimension mismatch when embedding model changes

When users switch embedding models (e.g., from 4096-dim to 1024-dim),
the stored taste profile would have the old dimensions, causing
PostgreSQL to throw 'different halfvec dimensions' errors when
comparing against the new embedding table.

Changes:

  • getUserTasteProfile now checks if the stored profile's embedding
    model matches the currently active model
  • If model changed or model info is missing, profile is rebuilt
  • Movie and series pipelines now store embedding model ID when
    creating profiles via legacy fallback path

Fixes dimension mismatch errors for both movie and series recommendations.

@GoldSpacercan you pull from :dev and test this before I merge it to latest?

I am trying to test this myself but my most powerful computer is on the struggle bus with this model!

image.png.e4bc281363c252de454006c029c2a922.png 

image.png.c2f55e4aa1070a006776dd59d6b18822.png

GoldSpacer
Posted

I'm testing now, generating recommendations doesn't immediately fail, but it takes awhile for my server to generate them.

akacharos
Posted
2 hours ago, ebr said:

I think that is prudent.

I think it is more likely that future features will make it unnecessary for integrations like Aperture to create duplicates.  IOW - use proper constructs like playlists once we have the ability to show these on the home screen.

Not everyone likes how Playlists are listing items in terms of displaying details (or the lack of customization). Friends and family really dislike them in terms of usability, and nobody is using them. They just add stuff in their "Favorites" -which technically becomes a " to-watch list" -simply becomes playlists are not fulfilling their purpose.
For years we rely on Collections that is more aligned to a library on how items are listed and used and that's how I recommend stuff to them. Years ago, you guys "revamped" Collections feature by stripping down features like the option of non-admins adding items to Collections ruining any social aspect of Collections (wtf!). 
So no, a playlist cannot replace a virtual library in many ways.
Top Picks addon -which became very popular- run into the same issue and similar discussions were raised. 
I personally feel that Emby team is not listening to their users -neither is very transparent on how they prioritize new features and changes.
Don't take this as a rant, but a long-time-user feedback.

TheGru
Posted
7 minutes ago, GoldSpacer said:

I'm testing now, generating recommendations doesn't immediately fail, but it takes awhile for my server to generate them.

Same, I think it is fixed though.

GoldSpacer
Posted (edited)

Finally got 1 user's recommendations generated and written to the library successfully so it appears to be good! 

Edited by GoldSpacer
TheGru
Posted

Whoa, I beat you? Mine ran 7 recommendation on Llama 3.2 on my m4 max mac studio with 36gb of ram

TheGru
Posted

Aperture v0.5.8 Release Notes

This release brings powerful new filtering capabilities to Discovery and fixes a critical bug that affected users switching between embedding models.


✨ New Feature: Advanced Discovery Filters - Thanks @Jdieselfor the inspiration

The Discovery page now includes a comprehensive filtering panel that gives you fine-grained control over what content appears in your recommendations:

Filter Options

  • Language Filter — Filter by original language (English, Korean, Japanese, Spanish, etc.) using ISO 639-1 language codes
  • Genre Filter — Select specific genres to focus your discovery
  • Year Range Slider — Set minimum and maximum release year to narrow down results
  • Minimum Similarity Score — Adjust the similarity threshold (0-1) to control how closely matches align with your taste

Real-Time Library Exclusion

Discovery now intelligently excludes:

  • ✅ Content already in your media server library
  • ✅ Movies/series you've already watched

This means you'll only see genuinely new content to discover — no more "I already have this!" moments.

Technical Details

  • Added original_language column to discovery candidates with indexing
  • Original language is now extracted from TMDb API during enrichment
  • Filtering happens at query time using efficient LEFT JOINs

✨ New Feature: Full Reset Jobs

Two new manual-only jobs for completely rebuilding recommendations from scratch:

Job Description
full-reset-movie-recommendations Delete ALL movie recommendations, then rebuild
full-reset-series-recommendations Delete ALL series recommendations, then rebuild

When to Use

These destructive jobs are useful after:

  • Major algorithm changes
  • Switching embedding models
  • Recommendations appearing corrupted
  • Significant library changes

Manual-Only UI

Manual-only jobs now display a warning banner in the Jobs configuration dialog explaining their destructive nature. Scheduling controls are disabled — these jobs can only be triggered manually.

⚠️ Warning: These jobs delete ALL existing recommendations before rebuilding. Users will have no recommendations until the job completes.


🐛 Fixed: Embedding Model Dimension Mismatch - Thank you @GoldSpacer

The Problem

When switching between embedding models with different dimensions (e.g., from a 4096-dimension model to a 1024-dimension model), users would encounter this error when generating recommendations:

different halfvec dimensions 4096 and 1024

This occurred because the stored taste profile had embeddings from the old model, but the system was trying to compare them against the new model's embedding table.

The Fix

The system now automatically detects when the embedding model has changed and rebuilds the taste profile with the correct dimensions:

  • ✅ getUserTasteProfile validates that the stored profile's model matches the active model
  • ✅ Profiles missing model info (older profiles) are automatically rebuilt
  • ✅ Both movie and series recommendation pipelines now store the embedding model ID

This fix applies to both Movies and Series recommendations.


🚀 Update Instructions

For Docker Users

# Pull the latest image
docker compose pull

# Restart with new version
docker compose up -d

The new database migration for original_language will be applied automatically on startup.


Migration Notes

If you've previously switched embedding models and encountered dimension mismatch errors, your taste profiles will automatically rebuild on the next recommendation generation. No manual action required!

  • Thanks 1
TheGru
Posted

For some reason my v0.5.7 release notes are stuck in content moderation hell on the form... 

The cliff's notes from that one:

Aperture v0.5.7 Release Notes
This release fixes a significant performance issue with the Watcher Identity tabs in User Settings accessed via the logged in user's Avatar in the upper right top bar.
If you've noticed slow load times when switching between Movies and Series tabs, this one's for you!

 Fixed: Watcher Identity Auto-Regeneration Bug
The Problem
The Watcher Identity tabs were triggering a full AI regeneration of your taste synopsis on every page load after 24 hours — completely ignoring your configured refresh interval setting (e.g., 1 month).

This meant:

 5-10+ second load times every day
 Unnecessary AI API calls
 Wasted compute on multiple database queries + embedding calculations
 Your "Refresh Interval" setting in Identity Settings was being ignored
The Fix
The getTasteSynopsis() and getSeriesTasteSynopsis() functions no longer auto-regenerate on page load. They now:

 Return cached synopsis immediately (if exists)
 Let the background job handle periodic refresh based on your settings
 Only regenerate when you explicitly click "Generate Identity"
 

akacharos
Posted

@TheGruwe thought you were taking a one-week break from the project 😅

TheGru
Posted (edited)
2 minutes ago, akacharos said:

@TheGruwe thought you were taking a one-week break from the project 😅

I didn't say what week... did I?

Edited by TheGru
  • Haha 1
TheGru
Posted

ADULT ADHD IS REAL!!!

  • Haha 1

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...