Die Detailseite trägt wieder
Drei Dinge, eins davon war mein Fehlbefund: Regie und Drehbuch werden sehr wohl angezeigt — nur standen sie irgendwo zwischen dreissig Gesichtern. Jetzt kommt der Stab zuerst, weil man beim Suchen eher die Regie im Kopf hat als den zwölften Darsteller. Bei Zeichentrick hängt TMDB "(voice)" an den Rollennamen, und so stand es auch da — englisch, mitten im deutschen Text. Daraus wird "Stimme: Gru". Ähnliche Titel als eigene Reihe, nachgeladen, damit die Seite sofort steht. Aus dem eigenen Bestand gerechnet, nicht aus TMDBs Empfehlungsliste: ein Vorschlag, den man nicht abspielen kann, ist Werbung. Gemeinsame Menschen wiegen dreifach gegenüber gemeinsamen Genres — dass zwei Filme "Drama" sind, sagt wenig; dieselbe Regie sagt viel. Kostet keine einzige fremde Anfrage, der Katalog steht ohnehin in der Datenbank. Findet sich nichts, erscheint die Reihe gar nicht. Und der Hintergrund: er begann bei left: 240px, also hinter der Seitenleiste, und war mit opacity .28 und blur(2px) weder Bild noch Fläche. Jetzt über die volle Breite, kräftiger unscharf — er taucht die Seite in die Farbe des Films, so wie bei Plex. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
11a44f6a90
commit
e1399c808f
@@ -189,6 +189,52 @@ public static class LibraryContentEndpoints
|
||||
}).WithName("Scanstand").RequireAuthorization();
|
||||
}
|
||||
|
||||
/// <summary>Ähnliche Titel — aus dem eigenen Bestand, nicht aus dem Netz.</summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// <b>Nur, was man besitzt.</b> Ein Vorschlag, den man nicht abspielen
|
||||
/// kann, ist Werbung. TMDB hätte eine Empfehlungsliste, aber die besteht
|
||||
/// zur Hälfte aus Filmen, die hier nicht liegen.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <b>Aus Genres und Menschen, ohne eine einzige fremde Anfrage.</b> Der
|
||||
/// Katalog steht ohnehin in der Datenbank. Ein gemeinsamer Mensch wiegt
|
||||
/// schwerer als ein gemeinsames Genre: dass zwei Filme „Drama" sind, sagt
|
||||
/// wenig; dass beide von derselben Regie stammen, sagt viel.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
private static async Task<List<Guid>> AehnlicheAsync(
|
||||
FlaroContext db, Guid filmId, int wieViele)
|
||||
{
|
||||
var genres = await db.GenreLinks.Where(g => g.MovieId == filmId)
|
||||
.Select(g => g.GenreId).ToListAsync();
|
||||
var leute = await db.Credits.Where(c => c.MovieId == filmId)
|
||||
.Select(c => c.PersonId).ToListAsync();
|
||||
|
||||
if (genres.Count == 0 && leute.Count == 0) return [];
|
||||
|
||||
var bibliothek = await db.Movies.Where(m => m.Id == filmId)
|
||||
.Select(m => m.LibraryId).FirstOrDefaultAsync();
|
||||
|
||||
var punkte = await db.Movies
|
||||
.Where(m => m.Id != filmId && m.LibraryId == bibliothek)
|
||||
.Select(m => new
|
||||
{
|
||||
m.Id,
|
||||
Punkte =
|
||||
db.GenreLinks.Count(g => g.MovieId == m.Id && genres.Contains(g.GenreId))
|
||||
+ db.Credits.Count(c => c.MovieId == m.Id && leute.Contains(c.PersonId)) * 3,
|
||||
})
|
||||
.Where(x => x.Punkte > 0)
|
||||
.OrderByDescending(x => x.Punkte)
|
||||
.Take(wieViele)
|
||||
.Select(x => x.Id)
|
||||
.ToListAsync();
|
||||
|
||||
return punkte;
|
||||
}
|
||||
|
||||
/// <summary>Die Sprache dieses Benutzers — oder <c>null</c> für die des Servers.</summary>
|
||||
///
|
||||
/// <remarks>
|
||||
@@ -386,6 +432,41 @@ public static class LibraryContentEndpoints
|
||||
// **Einzelabruf, weil ein Client nicht immer aus einer Liste kommt.**
|
||||
// Ein Link, ein Wiederaufnehmen nach Neustart, ein Eintrag aus
|
||||
// „Weiterschauen" — in all diesen Fällen hat er nur die Kennung.
|
||||
// Ähnliche Titel als eigener Abruf: die Detailseite soll stehen,
|
||||
// bevor diese Rechnung durch ist.
|
||||
app.MapGet("/api/v1/movies/{id:guid}/similar", async (
|
||||
Guid id, ClaimsPrincipal wer, FlaroContext db, int take = 12) =>
|
||||
{
|
||||
var kennungen = await AehnlicheAsync(db, id, Math.Clamp(take, 1, 24));
|
||||
if (kennungen.Count == 0) return Results.Ok(new Page<MovieResponse>([], 0));
|
||||
|
||||
var sprache = await SpracheVon(wer, db);
|
||||
var filme = await db.Movies.AsNoTracking()
|
||||
.Where(m => kennungen.Contains(m.Id))
|
||||
.Select(m => new
|
||||
{
|
||||
m.Id,
|
||||
Title = sprache == null ? m.Title
|
||||
: db.Translations.Where(t => t.MovieId == m.Id && t.Language == sprache)
|
||||
.Select(t => t.Title).FirstOrDefault() ?? m.Title,
|
||||
m.OriginalTitle, m.Year, m.PosterImage, m.BackdropImage, m.LogoImage,
|
||||
m.RuntimeMinutes, m.Rating, m.ContentRating,
|
||||
})
|
||||
.ToListAsync();
|
||||
|
||||
// In der Reihenfolge der Punkte, nicht in der der Datenbank.
|
||||
var items = kennungen
|
||||
.Select(k => filme.FirstOrDefault(f => f.Id == k))
|
||||
.Where(f => f != null)
|
||||
.Select(f => new MovieResponse(
|
||||
f!.Id, f.Title, f.Year, null, f.PosterImage, f.BackdropImage,
|
||||
[], null, f.LogoImage, f.RuntimeMinutes, f.Rating, null, f.ContentRating,
|
||||
OriginalTitle: f.OriginalTitle))
|
||||
.ToList();
|
||||
|
||||
return Results.Ok(new Page<MovieResponse>(items, items.Count));
|
||||
}).RequireAuthorization();
|
||||
|
||||
app.MapGet("/api/v1/movies/{id:guid}", async (
|
||||
Guid id, ClaimsPrincipal wer, FlaroContext db) =>
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user