Spider-Man ist nicht 720p, er ist breit

Die Auflösung wurde allein aus der Höhe gebildet. Ein Kinoformat von 2.39:1
liegt als 1920×800 vor — nach der Höhe gerechnet kam „720p" heraus, und das
stand so in der Oberfläche. Jetzt zählt die grössere von Breite und Höhe;
Hochformat kippt damit nicht, und 4K-Breitbild heisst nicht mehr 1440p.

Die Detailansicht hat zwei Leserschaften, und bisher bekamen beide dasselbe.
Wer schauen will, sieht jetzt Laufzeit, Auflösung und die Sprachen — jede
einmal, statt „Englisch · Englisch · Englisch · Englisch · Deutsch". Wer die
Mediathek betreut, sieht darunter Dateiformat, Grösse und den Ordner. Der
Ordner verrät eine falsche Zuordnung in einer Sekunde: steht über der
Beschreibung „Hanni & Nanni 3" und der Ordner heisst „Hanni und Nanni", ist
die Sache klar. Er kommt nur für Administratoren überhaupt vom Server —
versteckt im Client wäre er weiterhin auslesbar.

Ein geplanter Titel war unsichtbar. Das ist die Hälfte, die nichts bringt:
dass er kommt, ist der Sinn des Planens. Er steht jetzt mit „Verfügbar ab …"
in der Mediathek, und die Datei geht trotzdem nicht heraus — Download wie
Strom lehnen bis zum Termin ab, auch für Administratoren. Wer vorziehen will,
hat „Jetzt veröffentlichen"; eine Hintertür für eine Rolle wäre eine Regel,
die manchmal gilt.

Was ein Auffrischen geändert hat, steht jetzt unter „Zu prüfen" — in einer
eigenen Gruppe mit „Zur Kenntnis" statt „Veröffentlichen". Diese Titel sind
sichtbar; es gibt nichts freizugeben, nur etwas abzuhaken.

Und das Protokoll sagt endlich, wo es knallt. „InvalidOperationException:
Nullable object must have a value." ohne eine einzige Zeile Herkunft ist für
niemanden eine Diagnose — weder für den Betreiber noch für mich.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-10-06 14:51:00 +02:00
co-authored by Claude Opus 5
parent f44779c899
commit 255eb7c2f0
19 changed files with 1569 additions and 64 deletions
@@ -25,11 +25,13 @@ public static class DeliveryEndpoints
// durch bis zum SPA-Rückfall und bekäme die Webseite mit Status 200.
// Genau das ist passiert: der Spieler bekam HTML statt Video.
app.MapMethods("/api/v1/files/{id:guid}/download", ["GET", "HEAD"], async (
Guid id, FlaroContext db, PackageCoordinator packer) =>
Guid id, FlaroContext db, PackageCoordinator packer, TimeProvider uhr) =>
{
var gefunden = await Laden(db, id);
if (gefunden is not var (datei, plan)) return Results.NotFound();
if (await FreiAb(db, datei, uhr.GetUtcNow()) is { } nochNicht) return NochNicht(nochNicht);
// Passt schon alles, geht die Datei unverändert hinaus — mit
// Längenangabe und Bereichsanfragen, also mit Fortschritt und
// Fortsetzen.
@@ -70,6 +72,41 @@ public static class DeliveryEndpoints
}).RequireAuthorization();
}
/// <summary>Ab wann diese Datei herausgehen darf — <c>null</c> heisst: sofort.</summary>
///
/// <remarks>
/// <para>
/// <b>Hier und nicht im Sichtbarkeitsfilter.</b> Ein geplanter Titel steht
/// mit „Verfügbar ab …" in der Mediathek — das ist der Sinn des Planens.
/// Sichtbar heisst aber nicht abspielbar: ohne diese Prüfung liesse sich
/// die Datei trotzdem herunterladen, und die Angabe wäre eine Zierde.
/// </para>
/// <para>
/// <b>Für alle, auch für Administratoren.</b> Wer die Freigabe vorziehen
/// will, hat dafür den Knopf „Jetzt veröffentlichen" — eine Hintertür für
/// eine Rolle wäre eine Regel, die manchmal gilt.
/// </para>
/// </remarks>
internal static async Task<DateTimeOffset?> FreiAb(
FlaroContext db, MediaFile datei, DateTimeOffset jetzt)
{
DateTimeOffset? wann = datei.MovieId is { } film
? await db.Movies.AsNoTracking().Where(m => m.Id == film)
.Select(m => m.PublishAt).FirstOrDefaultAsync()
: datei.EpisodeId is { } folge
? await db.Episodes.AsNoTracking().Where(f => f.Id == folge)
.Select(f => f.PublishAt).FirstOrDefaultAsync()
: null;
return wann is { } termin && termin > jetzt ? termin : null;
}
/// <summary>Der Satz, der statt der Datei kommt.</summary>
internal static IResult NochNicht(DateTimeOffset wann) =>
Results.Problem(
$"Dieser Titel ist ab {wann.ToLocalTime():dd.MM.yyyy, HH:mm} Uhr verfügbar.",
statusCode: StatusCodes.Status403Forbidden);
private static async Task<(MediaFile Datei, DeliveryPlan Plan)?> Laden(FlaroContext db, Guid id)
{
var datei = await db.MediaFiles.AsNoTracking().FirstOrDefaultAsync(f => f.Id == id);