Files
Flaro/server/tests/Flaro.Media.Tests/VorschaubilderTests.cs
T
ScarriffleandClaude Opus 5 68abd71074 Ein Bild, wo bisher nur eine Zahl stand
Wer zur Zeitleiste greift, sieht jetzt das Standbild der Stelle — im Web
beim Überfahren, in der App beim Ziehen.

**Erzeugt wird beim Abspielen, nicht auf Vorrat.** Ein Lauf über 453
Filme und 4213 Folgen wäre ein Abend ffmpeg und mehrere Gigabyte für
Titel, die niemand anfasst. Startet jemand einen Titel, stellt sich die
Datei hinten an; bis zum ersten Griff zur Leiste ist der Lauf durch.
Gemessen an einer erzeugten Datei: 191 ms für 250 Sekunden Film, ein
Zweistünder liegt damit bei etwa fünf Sekunden.

**Ein ffmpeg-Aufruf, nicht siebenhundert.** `fps=1/10,scale=320:-2,
tile=10x10` macht aus einem Durchlauf fertige Blätter zu hundert
Kacheln. Bezahlbar ist das durch `-skip_frame nokey` — dieselbe
Abkürzung wie beim Abtasten der Abspannhelligkeit: nur Keyframes
dekodieren. Dass die Vorschau dadurch um zwei Sekunden danebenliegen
kann, sieht niemand.

**Der Spieler lädt ein Blatt und verschiebt danach nur den Ausschnitt.**
Beim Fahren über die Leiste wird kein einziger Abruf mehr fällig. Die
Rechnung Sekunde → Kachel steht an drei Stellen — Server, Web, App —
und deshalb auch dreimal dieselbe; weicht eine ab, hält man die Vorschau
für ein unscharfes Bild statt für einen Fehler. Neun Prüfungen decken
die Ränder ab: Sekunde null, letzte Kachel eines Blattes, erste des
nächsten, und hinter dem Ende.

**Die Kachelmasse werden gemessen, nicht gerechnet.** `scale=320:-2`
hält das Seitenverhältnis, ein Breitbildfilm ergibt also flachere
Kacheln. Geraten wäre jede Kachel um ein paar Bildpunkte verschoben.

Aufgeräumt wird mit dem Transcode-Cache: der Namensanfang trägt Kennung
und Änderungszeit der Mediendatei, verwaiste Blätter sind daran ohne
Tabelle erkennbar. Der Ordner taucht unter „Speicher" auf.

**Und die Pfeiltasten spulen wieder.** Hatte die Zeitleiste den Fokus,
überliess der Spieler die Pfeile dem Browser — der verschiebt `value`
und löst `input` aus, aber `change` kommt bei Tastatureingabe nicht
verlässlich, also wurde nie gesprungen. Die Ausnahme fällt weg; Pfeil
heisst überall ±10 Sekunden. Nativ wären es ohnehin 100 Millisekunden
gewesen, weil `step` auf 0,1 steht.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-16 17:05:55 +02:00

110 lines
3.8 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Diagnostics;
using Flaro.Media;
using Microsoft.Extensions.Logging.Abstractions;
namespace Flaro.Media.Tests;
/// <summary>Die Blätter der Vorschau — Zahl, Masse, Namen.</summary>
///
/// <remarks>
/// <para>
/// <b>Die einzige Prüfung hier, die ffmpeg braucht</b>, und sie ist es
/// wert: zwischen „der Filtergraph ist richtig geschrieben" und „die
/// Kacheln liegen da, wo der Spieler sie sucht" liegt alles. Eine um eine
/// Kachel verschobene Vorschau sieht niemand als Fehler — man hält sie
/// für ein ungenaues Bild.
/// </para>
/// <para>
/// Erzeugt wird ein Film von 250 Sekunden. Bei einem Bild alle zehn
/// Sekunden sind das 25 Kacheln, und die passen auf ein Blatt von zehn
/// mal zehn.
/// </para>
/// </remarks>
public class VorschaubilderTests
{
private string _ordner = "";
private string _datei = "";
[OneTimeSetUp]
public void Erzeugen()
{
_ordner = Directory.CreateDirectory(Path.Combine(
Path.GetTempPath(), "flaro-vorschau-" + Guid.CreateVersion7().ToString("N"))).FullName;
_datei = Path.Combine(_ordner, "quelle.mp4");
var start = new ProcessStartInfo("ffmpeg")
{
ArgumentList =
{
"-hide_banner", "-loglevel", "error", "-y",
"-f", "lavfi", "-i", "testsrc2=size=640x360:rate=25:duration=250",
"-c:v", "libx264", "-preset", "ultrafast",
"-g", "50", "-pix_fmt", "yuv420p", _datei,
},
RedirectStandardError = true,
UseShellExecute = false,
};
try
{
using var prozess = Process.Start(start);
prozess!.WaitForExit(120_000);
if (prozess.ExitCode != 0 || !File.Exists(_datei))
Assert.Ignore("ffmpeg konnte die Prüfdatei nicht erzeugen.");
}
catch (Exception)
{
Assert.Ignore("Ohne ffmpeg lässt sich das nicht prüfen.");
}
}
[OneTimeTearDown]
public void Aufraeumen()
{
try { Directory.Delete(_ordner, recursive: true); } catch (IOException) { }
}
[Test]
public async Task Ein_Lauf_ergibt_ein_Blatt_mit_richtigen_Massen()
{
var ziel = Path.Combine(_ordner, "blaetter");
var bilder = new Vorschaubilder(
FfmpegOptions.FromEnvironment(), NullLogger<Vorschaubilder>.Instance);
var plan = await bilder.ErzeugenAsync(_datei, ziel, "pruef-123");
Assert.That(plan, Is.Not.Null, "der Lauf muss Blätter ergeben");
Assert.Multiple(() =>
{
Assert.That(plan!.Value.Blaetter, Is.EqualTo(1),
"25 Kacheln passen auf ein Blatt von zehn mal zehn");
Assert.That(plan.Value.KachelBreite, Is.EqualTo(Vorschaubilder.Breite));
// 640×360 ist 16:9 — bei 320 Breite also 180 hoch.
Assert.That(plan.Value.KachelHoehe, Is.EqualTo(180).Within(2));
Assert.That(plan.Value.Intervall, Is.EqualTo(Vorschaubilder.Intervall));
});
Assert.Multiple(() =>
{
Assert.That(File.Exists(Path.Combine(ziel, "pruef-123-000.jpg")),
"die Blätter heissen nach dem Präfix und ihrer Nummer");
Assert.That(Directory.GetFiles(ziel, "*.teil-*"), Is.Empty,
"nichts Halbes bleibt liegen");
});
}
/// <summary>Eine Datei, die es nicht gibt, ergibt nichts — und wirft nicht.</summary>
[Test]
public async Task Ohne_Quelle_kein_Plan()
{
var bilder = new Vorschaubilder(
FfmpegOptions.FromEnvironment(), NullLogger<Vorschaubilder>.Instance);
var plan = await bilder.ErzeugenAsync(
Path.Combine(_ordner, "gibtsnicht.mkv"), Path.Combine(_ordner, "leer"), "x");
Assert.That(plan, Is.Null);
}
}