diff --git a/server/src/Flaro.Api/Markenerkennung.cs b/server/src/Flaro.Api/Markenerkennung.cs
index a0c116a..dc92360 100644
--- a/server/src/Flaro.Api/Markenerkennung.cs
+++ b/server/src/Flaro.Api/Markenerkennung.cs
@@ -84,6 +84,55 @@ public sealed class Markenerkennung(
return true;
}
+ /// Nach einem Scan: jede Serie der Mediathek, der noch Marken fehlen.
+ ///
+ ///
+ ///
+ /// Hier, weil es sonst nie passiert. Eine neue Folge bekommt
+ /// keine Marke, wenn niemand danach fragt — und niemand fragt, weil man
+ /// nicht weiss, dass es etwas zu fragen gibt. Der Scan ist der
+ /// Augenblick, in dem neue Folgen entstehen.
+ ///
+ ///
+ /// Eine nach der anderen, nicht alle zugleich. Jede liest von
+ /// jeder ihrer Folgen ein Stück Ton; zwanzig Serien gleichzeitig hiesse
+ /// zwanzig Leseköpfe auf derselben Platte. Beim ersten Mal über einen
+ /// grossen Bestand dauert das entsprechend — danach findet der Lauf
+ /// nichts mehr zu tun und ist in Sekunden vorbei.
+ ///
+ ///
+ public async Task NachScanAsync(Guid mediathek, CancellationToken ct = default)
+ {
+ using var raum = scopes.CreateScope();
+ var db = raum.ServiceProvider.GetRequiredService();
+ var leser = raum.ServiceProvider.GetRequiredService();
+
+ if (!await leser.VorhandenAsync(ct)) return;
+
+ var serien = await db.Series.AsNoTracking()
+ .Where(x => x.LibraryId == mediathek)
+ .Select(x => x.Id)
+ .ToListAsync(ct);
+ if (serien.Count == 0) return;
+
+ log.LogInformation(
+ "Suche Vorspann und Abspann in {Anzahl} Serien — das kann dauern.", serien.Count);
+
+ foreach (var serie in serien)
+ {
+ if (ct.IsCancellationRequested) return;
+ if (!_laufend.TryAdd(serie, true)) continue;
+ try { await LaufenAsync(serie, erneut: false, ct); }
+ catch (Exception fehler)
+ {
+ log.LogWarning(fehler, "Markenerkennung für {Serie} abgebrochen.", serie);
+ }
+ finally { _laufend.TryRemove(serie, out _); }
+ }
+
+ log.LogInformation("Marken für Mediathek {Kennung} durch.", mediathek);
+ }
+
private async Task LaufenAsync(Guid serieId, bool erneut, CancellationToken ct)
{
using var raum = scopes.CreateScope();
diff --git a/server/src/Flaro.Api/ScanCoordinator.cs b/server/src/Flaro.Api/ScanCoordinator.cs
index 81b1bd4..ee5c946 100644
--- a/server/src/Flaro.Api/ScanCoordinator.cs
+++ b/server/src/Flaro.Api/ScanCoordinator.cs
@@ -175,6 +175,14 @@ public sealed class ScanCoordinator(
_ = bereich.ServiceProvider.GetRequiredService()
.LaufenAsync(Kratzplatzpflege.Anlass.NachDemScan);
+ // **Und zuletzt die Marken.** Ein Scan ist der Augenblick, in
+ // dem neue Folgen dazukommen — und eine Folge ohne Vorspann-
+ // Marke bekommt nie eine, wenn niemand danach fragt. Der Lauf
+ // hängt sich hinten an und nimmt sich Serie für Serie vor;
+ // gerechnet wird nur, wo noch nichts steht.
+ _ = bereich.ServiceProvider.GetRequiredService()
+ .NachScanAsync(libraryId);
+
_zustand[libraryId] = _zustand[libraryId] with
{
Status = "finished",