diff --git a/scratchpad/neu/data/flaro.db b/scratchpad/neu/data/flaro.db
index 719a7a9..7ad9942 100644
Binary files a/scratchpad/neu/data/flaro.db and b/scratchpad/neu/data/flaro.db differ
diff --git a/scratchpad/neu/data/logs/flaro-2026-10-07.log b/scratchpad/neu/data/logs/flaro-2026-10-07.log
index 816b658..98157d7 100644
--- a/scratchpad/neu/data/logs/flaro-2026-10-07.log
+++ b/scratchpad/neu/data/logs/flaro-2026-10-07.log
@@ -1,2 +1,5 @@
21:35:00 INFO Server gestartet — 1.0.0 · DEV
21:55:29 INFO Server fährt herunter.
+22:00:35 INFO Server gestartet — 1.0.0 · DEV
+22:11:06 INFO Server fährt herunter.
+22:14:53 INFO Server gestartet — 1.0.0 · DEV
diff --git a/scratchpad/neu/data/logs/flaro-2026-10-08.log b/scratchpad/neu/data/logs/flaro-2026-10-08.log
new file mode 100644
index 0000000..e9bfa51
--- /dev/null
+++ b/scratchpad/neu/data/logs/flaro-2026-10-08.log
@@ -0,0 +1,17 @@
+06:38:47 INFO Server fährt herunter.
+06:44:02 INFO Server gestartet — 1.0.0 · DEV
+06:57:23 INFO Server fährt herunter.
+06:59:45 INFO Server gestartet — 1.0.0 · DEV
+17:38:34 INFO Server fährt herunter.
+17:57:11 INFO Server gestartet — 1.0.0 · DEV
+19:05:40 INFO Server fährt herunter.
+19:08:22 INFO Server gestartet — 1.0.0 · DEV
+21:03:32 INFO Server fährt herunter.
+21:08:04 INFO Server gestartet — 1.0.0 · DEV
+21:10:04 INFO Wartung — Sicherung: flaro-2026-10-08-1910.db, 0 MB
+21:10:04 INFO Wartung — Optimieren: nichts zusammenzupacken
+21:10:04 INFO Wartung — Bilder: kein Bildspeicher da
+21:45:40 INFO Server fährt herunter.
+21:45:44 INFO Server gestartet — 1.0.0 · DEV
+22:06:03 INFO Server fährt herunter.
+22:12:27 INFO Server gestartet — 1.0.0 · DEV
diff --git a/scratchpad/neu/data/logs/flaro-2026-10-11.log b/scratchpad/neu/data/logs/flaro-2026-10-11.log
new file mode 100644
index 0000000..4999dc0
--- /dev/null
+++ b/scratchpad/neu/data/logs/flaro-2026-10-11.log
@@ -0,0 +1 @@
+21:17:00 INFO Wartung — Sicherung: flaro-2026-10-11-1917.db, 0 MB
diff --git a/scratchpad/neu/data/logs/flaro-2026-10-14.log b/scratchpad/neu/data/logs/flaro-2026-10-14.log
new file mode 100644
index 0000000..9add8a7
--- /dev/null
+++ b/scratchpad/neu/data/logs/flaro-2026-10-14.log
@@ -0,0 +1 @@
+21:26:01 INFO Wartung — Sicherung: flaro-2026-10-14-1926.db, 0 MB
diff --git a/scratchpad/neu/data/logs/flaro-2026-10-15.log b/scratchpad/neu/data/logs/flaro-2026-10-15.log
new file mode 100644
index 0000000..a33e816
--- /dev/null
+++ b/scratchpad/neu/data/logs/flaro-2026-10-15.log
@@ -0,0 +1,2 @@
+21:10:21 INFO Wartung — Optimieren: nichts zusammenzupacken
+21:10:21 INFO Wartung — Bilder: kein Bildspeicher da
diff --git a/scratchpad/neu/data/sicherungen/flaro-2026-10-08-1910.db b/scratchpad/neu/data/sicherungen/flaro-2026-10-08-1910.db
new file mode 100644
index 0000000..22cf373
Binary files /dev/null and b/scratchpad/neu/data/sicherungen/flaro-2026-10-08-1910.db differ
diff --git a/scratchpad/neu/data/sicherungen/flaro-2026-10-11-1917.db b/scratchpad/neu/data/sicherungen/flaro-2026-10-11-1917.db
new file mode 100644
index 0000000..6be24d0
Binary files /dev/null and b/scratchpad/neu/data/sicherungen/flaro-2026-10-11-1917.db differ
diff --git a/scratchpad/neu/data/sicherungen/flaro-2026-10-14-1926.db b/scratchpad/neu/data/sicherungen/flaro-2026-10-14-1926.db
new file mode 100644
index 0000000..2432423
Binary files /dev/null and b/scratchpad/neu/data/sicherungen/flaro-2026-10-14-1926.db differ
diff --git a/server/src/Flaro.Api/Auth/TokenAuthentication.cs b/server/src/Flaro.Api/Auth/TokenAuthentication.cs
index 3eb8ea6..d8bdc4e 100644
--- a/server/src/Flaro.Api/Auth/TokenAuthentication.cs
+++ b/server/src/Flaro.Api/Auth/TokenAuthentication.cs
@@ -13,7 +13,7 @@ namespace Flaro.Api.Auth;
///
///
/// Authorization: Bearer <token>. Der Server hasht das Vorgezeigte
-/// und sucht den Fingerabdruck — das Token selbst steht nirgends in der
+/// und sucht den Fingerfingerprint — das Token selbst steht nirgends in der
/// Datenbank und ist aus ihr auch nicht zu gewinnen.
///
///
@@ -69,8 +69,8 @@ public sealed class TokenAuthenticationHandler(
var token = AusKopfzeile() ?? Request.Cookies[CookieName];
if (string.IsNullOrEmpty(token)) return AuthenticateResult.NoResult();
- var fingerabdruck = Secrets.Fingerprint(token);
- var eintrag = await db.DeviceTokens.FirstOrDefaultAsync(t => t.TokenFingerprint == fingerabdruck);
+ var fingerfingerprint = Secrets.Fingerprint(token);
+ var eintrag = await db.DeviceTokens.FirstOrDefaultAsync(t => t.TokenFingerprint == fingerfingerprint);
var jetzt = clock.GetUtcNow();
if (eintrag is null || !eintrag.IsValid(jetzt)) return AuthenticateResult.Fail("Token gilt nicht.");
diff --git a/server/src/Flaro.Api/Endpoints/AuthEndpoints.cs b/server/src/Flaro.Api/Endpoints/AuthEndpoints.cs
index 5c10712..a00e679 100644
--- a/server/src/Flaro.Api/Endpoints/AuthEndpoints.cs
+++ b/server/src/Flaro.Api/Endpoints/AuthEndpoints.cs
@@ -128,8 +128,8 @@ public static class AuthEndpoints
string handle, FlaroContext db, TimeProvider clock) =>
{
var jetzt = clock.GetUtcNow();
- var fingerabdruck = Secrets.Fingerprint(handle);
- var code = await db.AuthCodes.FirstOrDefaultAsync(c => c.HandleFingerprint == fingerabdruck);
+ var fingerfingerprint = Secrets.Fingerprint(handle);
+ var code = await db.AuthCodes.FirstOrDefaultAsync(c => c.HandleFingerprint == fingerfingerprint);
if (code is null || code.IsExpired(jetzt) || code.ConsumedAt is not null)
return Results.Ok(new CodeState("expired", null));
diff --git a/server/src/Flaro.Api/Endpoints/Contracts.cs b/server/src/Flaro.Api/Endpoints/Contracts.cs
index 1d6b379..e36d76f 100644
--- a/server/src/Flaro.Api/Endpoints/Contracts.cs
+++ b/server/src/Flaro.Api/Endpoints/Contracts.cs
@@ -26,7 +26,7 @@ public sealed record SystemInfo(
/// lägen die Dateien woanders als die Datenbank sie vermutet.
///
/// Wie viel davon auf den Bildspeicher entfällt; sonst null.
-/// Wie viel auf dem Kratzplatz liegen bleiben darf.
+/// Wie viel auf dem Transcode-Cache liegen bleiben darf.
///
/// Obergrenze in Gigabyte; 0 heisst unbegrenzt.
/// Ab wann Unbenutztes auch unterhalb der Grenze geht; 0 heisst nie.
@@ -42,7 +42,7 @@ public sealed record MaintenanceSettingsResponse(
int OptimizeDays, int ImageDays);
///
-/// Die Unterordner, wo es welche gibt — heute nur beim Kratzplatz.
+/// Die Unterordner, wo es welche gibt — heute nur beim Transcode-Cache.
/// Mit Dateizahl, denn bei den Untertiteln ist die Anzahl das
/// Problem und nicht die Grösse, und das sieht man an Bytes nicht.
/// Additiv: Bilder bleibt stehen, damit kein Client bricht.
@@ -323,15 +323,15 @@ public sealed record ScanStatus(
int? Missing = null,
/// Woran der Lauf gerade ist, in einem Satz.
string? Step = null,
- /// Woran die Markensuche gerade ist — null, wenn keine läuft.
+ /// Woran die Segmentsuche gerade ist — null, wenn keine läuft.
///
///
- /// Ein eigenes Feld, nicht . Die Markensuche
+ /// Ein eigenes Feld, nicht . Die Segmentsuche
/// hängt sich hinter den Scan und läuft weiter, wenn der längst
/// „fertig" meldet. In dasselbe Feld geschrieben sähe der Scan wieder
/// laufend aus, und die Verwaltung käme nie zur Ruhe.
///
- string? Marken = null);
+ string? Segmente = null);
/// Was das Nachschlagen bei der Metadatenquelle ergeben hat.
public sealed record MetadataProgress(
diff --git a/server/src/Flaro.Api/Endpoints/EinladungEndpoints.cs b/server/src/Flaro.Api/Endpoints/EinladungEndpoints.cs
index c77cc2c..95ffb70 100644
--- a/server/src/Flaro.Api/Endpoints/EinladungEndpoints.cs
+++ b/server/src/Flaro.Api/Endpoints/EinladungEndpoints.cs
@@ -43,7 +43,7 @@ public static class EinladungEndpoints
return Einwand.Bitte("Ein Link, der nullmal gilt, ist keiner.");
// **Das Geheimnis verlässt den Server genau hier.** Gespeichert
- // wird nur sein Fingerabdruck; aus der Datenbank lässt sich kein
+ // wird nur sein Fingerfingerprint; aus der Datenbank lässt sich kein
// Link mehr bauen, und eine verlorene Einladung wird deshalb nicht
// wiedergefunden, sondern neu gemacht.
var token = Secrets.Create();
diff --git a/server/src/Flaro.Api/Endpoints/LibraryContentEndpoints.cs b/server/src/Flaro.Api/Endpoints/LibraryContentEndpoints.cs
index 19aed8e..a7f7b2a 100644
--- a/server/src/Flaro.Api/Endpoints/LibraryContentEndpoints.cs
+++ b/server/src/Flaro.Api/Endpoints/LibraryContentEndpoints.cs
@@ -121,7 +121,7 @@ public static class LibraryContentEndpoints
.ToListAsync();
var spuren = await SpurenZu(db, dateien.Select(d => d.Id).ToList());
var stände = await ZuständeZu(db, wer, folgen: [nachbar.Id]);
- var marken = await MarkenZu(db, [nachbar.Id]);
+ var segmente = await SegmenteZu(db, [nachbar.Id]);
// **Ohne eigenes Bild das der Serie.** Eine graue Fläche sagt
// nichts; das Plakat der Serie sagt wenigstens, worum es geht.
@@ -138,24 +138,24 @@ public static class LibraryContentEndpoints
stände.GetValueOrDefault(nachbar.Id),
nachbar.PublishAt,
SeriesId: diese.SeriesId,
- Markers: marken[nachbar.Id].ToList()));
+ Markers: segmente[nachbar.Id].ToList()));
}
- /// Die Marken zu einer Reihe von Folgen.
+ /// Die Segmente zu einer Reihe von Folgen.
///
///
/// Als Nachschlagewerk und nicht je Folge einzeln: eine Staffel mit
/// zwanzig Folgen wären sonst zwanzig Abfragen für zwanzig kurze Zeilen.
///
- private static async Task> MarkenZu(
+ private static async Task> SegmenteZu(
FlaroContext db, IReadOnlyCollection folgen)
{
if (folgen.Count == 0)
return Array.Empty<(Guid, MarkerResponse)>().ToLookup(x => x.Item1, x => x.Item2);
- var roh = await db.Marken.AsNoTracking()
+ var roh = await db.Segmente.AsNoTracking()
.Where(m => m.EpisodeId != null && folgen.Contains(m.EpisodeId!.Value))
- // In Zeitfolge: ein geteilter Abspann kommt als zwei Marken, und
+ // In Zeitfolge: ein geteilter Abspann kommt als zwei Segmente, und
// die Spieler nehmen die erste passende.
.OrderBy(m => m.VonSekunden)
.Select(m => new { Id = m.EpisodeId!.Value, m.Art, m.VonSekunden, m.BisSekunden })
@@ -164,24 +164,24 @@ public static class LibraryContentEndpoints
return roh.ToLookup(
m => m.Id,
m => new MarkerResponse(
- m.Art == Markenart.Intro ? "intro" : "credits", m.VonSekunden, m.BisSekunden));
+ m.Art == Segmentart.Intro ? "intro" : "credits", m.VonSekunden, m.BisSekunden));
}
/// Dasselbe für Filme.
///
///
- /// Getrennt und nicht über eine gemeinsame Kennung: eine Marke gehört
+ /// Getrennt und nicht über eine gemeinsame Kennung: ein Segment gehört
/// entweder zu einer Folge oder zu einem Film, und eine Abfrage, die
/// beides zugleich sucht, fände zu einer Filmkennung auch eine Folge
/// mit derselben — unmöglich heute, aber nichts hält es fest.
///
- private static async Task> FilmmarkenZu(
+ private static async Task> FilmsegmenteZu(
FlaroContext db, IReadOnlyCollection filme)
{
if (filme.Count == 0)
return Array.Empty<(Guid, MarkerResponse)>().ToLookup(x => x.Item1, x => x.Item2);
- var roh = await db.Marken.AsNoTracking()
+ var roh = await db.Segmente.AsNoTracking()
.Where(m => m.MovieId != null && filme.Contains(m.MovieId!.Value))
.OrderBy(m => m.VonSekunden)
.Select(m => new { Id = m.MovieId!.Value, m.Art, m.VonSekunden, m.BisSekunden })
@@ -190,7 +190,7 @@ public static class LibraryContentEndpoints
return roh.ToLookup(
m => m.Id,
m => new MarkerResponse(
- m.Art == Markenart.Intro ? "intro" : "credits", m.VonSekunden, m.BisSekunden));
+ m.Art == Segmentart.Intro ? "intro" : "credits", m.VonSekunden, m.BisSekunden));
}
private static async Task> SpurenZu(FlaroContext db, List dateien)
@@ -424,20 +424,20 @@ public static class LibraryContentEndpoints
}).RequireAuthorization(FlaroPolicies.Administrator);
app.MapGet("/api/v1/libraries/{id:guid}/scan",
- (Guid id, ScanCoordinator koordinator, Markenerkennung erkennung) =>
+ (Guid id, ScanCoordinator koordinator, Segmenterkennung erkennung) =>
{
- // **Auch ohne Scan kann etwas laufen.** Die Markensuche lässt
+ // **Auch ohne Scan kann etwas laufen.** Die Segmentsuche lässt
// sich von Hand anstossen; ohne diese Zeile stünde die
// Verwaltung dann auf „idle", während der Server stundenlang
// arbeitet.
- var marken = erkennung.Stand(id)?.Text();
+ var segmente = erkennung.Stand(id)?.Text();
var zustand = koordinator.State(id);
if (zustand is null)
return Results.Ok(new ScanStatus(
- marken is null ? "idle" : "marken",
+ segmente is null ? "idle" : "marken",
null, null, null, null, null, null, null, null, null, null, null,
- Marken: marken));
+ Segmente: segmente));
var bericht = zustand.Report;
var metadaten = zustand.Enrichment;
@@ -452,7 +452,7 @@ public static class LibraryContentEndpoints
metadaten.Skipped, metadaten.Error),
bericht?.Missing,
zustand.Schritt?.Text(),
- marken));
+ segmente));
}).WithName("Scanstand").RequireAuthorization();
}
@@ -592,7 +592,7 @@ public static class LibraryContentEndpoints
.ToListAsync();
var spuren = await SpurenZu(db, dateien.Select(f => f.Id).ToList());
var stände = await ZuständeZu(db, wer, filme: ids);
- var marken = await FilmmarkenZu(db, ids);
+ var segmente = await FilmsegmenteZu(db, ids);
var items = kennungen.Select(m => new MovieResponse(
m.Id, m.Title, m.Year, m.Overview, m.PosterImage, m.BackdropImage,
@@ -606,7 +606,7 @@ public static class LibraryContentEndpoints
SortTitle: m.SortTitleValue,
SortTitlePinned: m.SortTitlePinned,
HeroImage: Aufmacherwahl.Jetziges(m.Aufmacherbilder, m.Id, DateTimeOffset.UtcNow),
- Markers: marken[m.Id].ToList()))
+ Markers: segmente[m.Id].ToList()))
.ToList();
return Results.Ok(new Page(items, gesamt));
@@ -802,7 +802,7 @@ public static class LibraryContentEndpoints
ExternalRatings: await FremdwertungenZu(db, film: id, serie: null),
LibraryId: film.LibraryId,
HeroImage: Aufmacherwahl.Jetziges(film.Aufmacherbilder, id, DateTimeOffset.UtcNow),
- Markers: (await FilmmarkenZu(db, [id]))[id].ToList()));
+ Markers: (await FilmsegmenteZu(db, [id]))[id].ToList()));
}).RequireAuthorization();
app.MapGet("/api/v1/series/{id:guid}", async (
@@ -870,7 +870,7 @@ public static class LibraryContentEndpoints
.ToListAsync();
var spuren = await SpurenZu(db, dateien.Select(d => d.Id).ToList());
var stände = await ZuständeZu(db, wer, folgen: [id]);
- var marken = await MarkenZu(db, [id]);
+ var segmente = await SegmenteZu(db, [id]);
var wurzel = await MediathekspfadFuer(db, wer,
await db.Series.AsNoTracking().Where(x => x.Id == folge.SeriesId)
@@ -883,7 +883,7 @@ public static class LibraryContentEndpoints
stände.GetValueOrDefault(id),
folge.PublishAt,
SeriesId: folge.SeriesId,
- Markers: marken[id].ToList()));
+ Markers: segmente[id].ToList()));
}).RequireAuthorization();
// **Die Nachbarfolge — vorwärts wie rückwärts.** Reihenfolge ist
@@ -900,8 +900,8 @@ public static class LibraryContentEndpoints
// Hintergrundlauf statt einer Antwort, auf die jemand wartet.
// Dasselbe für einen Film — dort sucht der Server im Bild.
app.MapPost("/api/v1/movies/{id:guid}/marken", async (
- Guid id, FlaroContext db, Markenerkennung erkennung,
- Markenumfang umfang = Markenumfang.Alles) =>
+ Guid id, FlaroContext db, Segmenterkennung erkennung,
+ Segmentumfang umfang = Segmentumfang.Alles) =>
{
if (!await db.Movies.AsNoTracking().AnyAsync(x => x.Id == id)) return Results.NotFound();
@@ -917,7 +917,7 @@ public static class LibraryContentEndpoints
// Frage, ob bei Outtakes im Abspann wenigstens die Ränder dunkel
// bleiben.
app.MapGet("/api/v1/movies/{id:guid}/markenkurve", async (
- Guid id, FlaroContext db, Bildabtaster abtaster, CancellationToken ct) =>
+ Guid id, FlaroContext db, Frameabtaster abtaster, CancellationToken ct) =>
{
var film = await db.Movies.AsNoTracking()
.Where(m => m.Id == id)
@@ -928,7 +928,7 @@ public static class LibraryContentEndpoints
if (film?.Duration is not { TotalSeconds: > 0 } laufzeit)
return Einwand.Bitte("Zu diesem Film gibt es keine Datei mit Laufzeit.");
- var ab = Math.Max(0, laufzeit.TotalSeconds - Markenerkennung.Suchbereich);
+ var ab = Math.Max(0, laufzeit.TotalSeconds - Segmenterkennung.Suchbereich);
var kurve = await abtaster.KurveAsync(film.Path, ab, laufzeit.TotalSeconds - ab, ct);
var text = new StringBuilder("sekunde;gesamt;links;rechts\n");
@@ -945,16 +945,16 @@ public static class LibraryContentEndpoints
"text/csv", $"markenkurve-{name}.csv");
}).RequireAuthorization(FlaroPolicies.Administrator);
- // **Eine Marke wieder wegnehmen.**
+ // **Ein Segment wieder wegnehmen.**
//
// Ohne diesen Weg wäre die Erkennung eine Blackbox, die manchmal
// recht hat: ein Fehlgriff bliebe stehen, und niemand könnte etwas
// dagegen tun ausser die Datenbank anzufassen.
app.MapDelete("/api/v1/movies/{id:guid}/marken", async (Guid id, FlaroContext db) =>
{
- var weg = await db.Marken.Where(m => m.MovieId == id).ExecuteDeleteAsync();
+ var weg = await db.Segmente.Where(m => m.MovieId == id).ExecuteDeleteAsync();
// Der Vermerk bleibt: „gesucht" stimmt weiterhin, und ohne ihn
- // legte der nächste Scan dieselbe falsche Marke wieder an.
+ // legte der nächste Scan dieselbe falsches Segment wieder an.
return weg > 0 ? Results.NoContent() : Results.NotFound();
}).RequireAuthorization(FlaroPolicies.Administrator);
@@ -963,14 +963,14 @@ public static class LibraryContentEndpoints
// unzufrieden; ihn dann nach Lücken suchen zu lassen hiesse, ihm
// die Antwort zu verweigern, die er haben will.
app.MapPost("/api/v1/series/{id:guid}/marken", async (
- Guid id, FlaroContext db, Markenerkennung erkennung,
- Markenumfang umfang = Markenumfang.Alles) =>
+ Guid id, FlaroContext db, Segmenterkennung erkennung,
+ Segmentumfang umfang = Segmentumfang.Alles) =>
{
if (!await db.Series.AsNoTracking().AnyAsync(x => x.Id == id)) return Results.NotFound();
return erkennung.Starten(id, umfang)
? Results.Accepted()
- : Einwand.Belegt("Für diese Serie werden gerade Marken gesucht.");
+ : Einwand.Belegt("Für diese Serie werden gerade Segmente gesucht.");
}).RequireAuthorization(FlaroPolicies.Administrator);
// **Eine ganze Mediathek auf einmal.** Je Serie einen Knopf zu
@@ -979,8 +979,8 @@ public static class LibraryContentEndpoints
// einmal. Der Lauf kann Stunden dauern und läuft deshalb im
// Hintergrund weiter; die Antwort kommt sofort.
app.MapPost("/api/v1/libraries/{id:guid}/marken", async (
- Guid id, FlaroContext db, Markenerkennung erkennung,
- Markenumfang umfang = Markenumfang.Neue) =>
+ Guid id, FlaroContext db, Segmenterkennung erkennung,
+ Segmentumfang umfang = Segmentumfang.Neue) =>
{
if (!await db.Libraries.AsNoTracking().AnyAsync(x => x.Id == id))
return Results.NotFound();
@@ -1077,7 +1077,7 @@ public static class LibraryContentEndpoints
.ToListAsync();
var spuren = await SpurenZu(db, dateien.Select(d => d.Id).ToList());
var stände = await ZuständeZu(db, wer, folgen: ids);
- var marken = await MarkenZu(db, ids);
+ var segmente = await SegmenteZu(db, ids);
var serienBild = await db.Series.AsNoTracking()
.Where(x => x.Id == id)
@@ -1091,7 +1091,7 @@ public static class LibraryContentEndpoints
stände.GetValueOrDefault(f.Id),
f.PublishAt,
SeriesId: id,
- Markers: marken[f.Id].ToList())).ToList();
+ Markers: segmente[f.Id].ToList())).ToList();
return Results.Ok(new Page(items, items.Count));
}).RequireAuthorization();
diff --git a/server/src/Flaro.Api/Endpoints/PlaybackEndpoints.cs b/server/src/Flaro.Api/Endpoints/PlaybackEndpoints.cs
index 51cf585..260c6ac 100644
--- a/server/src/Flaro.Api/Endpoints/PlaybackEndpoints.cs
+++ b/server/src/Flaro.Api/Endpoints/PlaybackEndpoints.cs
@@ -106,14 +106,14 @@ public static class PlaybackEndpoints
// `OrderBy` entschied die Datenbank, welche gilt. Gemeint ist
// immer die erste — dort ist die Handlung vorbei.
var abspannAb = zustand.EpisodeId is { } folge
- ? await db.Marken.AsNoTracking()
- .Where(m => m.EpisodeId == folge && m.Art == Markenart.Abspann)
+ ? await db.Segmente.AsNoTracking()
+ .Where(m => m.EpisodeId == folge && m.Art == Segmentart.Abspann)
.OrderBy(m => m.VonSekunden)
.Select(m => (double?)m.VonSekunden)
.FirstOrDefaultAsync()
: zustand.MovieId is { } film
- ? await db.Marken.AsNoTracking()
- .Where(m => m.MovieId == film && m.Art == Markenart.Abspann)
+ ? await db.Segmente.AsNoTracking()
+ .Where(m => m.MovieId == film && m.Art == Segmentart.Abspann)
.OrderBy(m => m.VonSekunden)
.Select(m => (double?)m.VonSekunden)
.FirstOrDefaultAsync()
diff --git a/server/src/Flaro.Api/Endpoints/SettingsEndpoints.cs b/server/src/Flaro.Api/Endpoints/SettingsEndpoints.cs
index 9a19462..f15e91d 100644
--- a/server/src/Flaro.Api/Endpoints/SettingsEndpoints.cs
+++ b/server/src/Flaro.Api/Endpoints/SettingsEndpoints.cs
@@ -213,10 +213,10 @@ public static class SettingsEndpoints
return Results.Ok(anlaesse);
}).RequireAuthorization(FlaroPolicies.Administrator);
- /* ---------- Wartung: was auf dem Kratzplatz liegen bleiben darf ---------- */
+ /* ---------- Wartung: was auf dem Transcode-Cache liegen bleiben darf ---------- */
app.MapGet("/api/v1/settings/maintenance", async (
- Kratzplatzeinstellungen kratz, Wartungsplaneinstellungen plan) =>
+ Transcodeeinstellungen kratz, Wartungsplaneinstellungen plan) =>
{
var regeln = await kratz.RegelnAsync();
var w = await plan.RegelnAsync();
@@ -230,23 +230,23 @@ public static class SettingsEndpoints
app.MapPut("/api/v1/settings/maintenance", async (
MaintenanceSettingsRequest anfrage, FlaroContext db,
- Kratzplatzeinstellungen einstellungen, Wartungsplaneinstellungen plan) =>
+ Transcodeeinstellungen einstellungen, Wartungsplaneinstellungen plan) =>
{
var vorher = await einstellungen.RegelnAsync();
// Was nicht geschickt wird, bleibt — ein Client, der nur ein Feld
// kennt, soll die anderen nicht zurücksetzen.
- var nachher = Kratzplatzregeln.Geprueft(
+ var nachher = Transcoderegeln.Geprueft(
anfrage.CapGb ?? vorher.ObergrenzeBytes / 1024d / 1024 / 1024,
anfrage.UnusedDays ?? (int)vorher.Unbenutzt.TotalDays,
anfrage.GraceMinutes ?? (int)vorher.Schonfrist.TotalMinutes);
var zahl = System.Globalization.CultureInfo.InvariantCulture;
- await Setzen(db, Kratzplatzeinstellungen.ObergrenzeSchluessel,
+ await Setzen(db, Transcodeeinstellungen.ObergrenzeSchluessel,
(nachher.ObergrenzeBytes / 1024d / 1024 / 1024).ToString("0.###", zahl));
- await Setzen(db, Kratzplatzeinstellungen.UnbenutztSchluessel,
+ await Setzen(db, Transcodeeinstellungen.UnbenutztSchluessel,
((int)nachher.Unbenutzt.TotalDays).ToString(zahl));
- await Setzen(db, Kratzplatzeinstellungen.SchonfristSchluessel,
+ await Setzen(db, Transcodeeinstellungen.SchonfristSchluessel,
((int)nachher.Schonfrist.TotalMinutes).ToString(zahl));
var wVorher = await plan.RegelnAsync();
var wNachher = Wartungsregeln.Geprueft(
diff --git a/server/src/Flaro.Api/Endpoints/StreamEndpoints.cs b/server/src/Flaro.Api/Endpoints/StreamEndpoints.cs
index 7b3a847..cd81424 100644
--- a/server/src/Flaro.Api/Endpoints/StreamEndpoints.cs
+++ b/server/src/Flaro.Api/Endpoints/StreamEndpoints.cs
@@ -93,7 +93,7 @@ public static class StreamEndpoints
// **Was der Browser ohnehin öffnet, geht unverändert hinaus.**
//
// Eine MP4 mit H.264 und AAC durch ffmpeg zu schicken wäre ein
- // Prozess und ein Kratzplatzordner für nichts — und der
+ // Prozess und ein Transcodeordner für nichts — und der
// Browser verlöre dabei das, was er von sich aus am besten
// kann: springen über Bereichsanfragen, ohne dass irgendwo
// etwas neu gerechnet wird.
@@ -368,7 +368,7 @@ public static class StreamEndpoints
// **Schliessen heisst aufräumen.** Ohne das liefe ffmpeg weiter, bis
// die Geduld der Sitzung abgelaufen ist — und schriebe solange den
- // ganzen Film auf den Kratzplatz.
+ // ganzen Film auf den Transcode-Cache.
app.MapDelete("/api/v1/stream/{sitzung:guid}", (Guid sitzung, Streamkoordinator koordinator) =>
{
koordinator.Beenden(sitzung);
diff --git a/server/src/Flaro.Api/Endpoints/SystemEndpoints.cs b/server/src/Flaro.Api/Endpoints/SystemEndpoints.cs
index c95d6b7..497a560 100644
--- a/server/src/Flaro.Api/Endpoints/SystemEndpoints.cs
+++ b/server/src/Flaro.Api/Endpoints/SystemEndpoints.cs
@@ -251,12 +251,12 @@ public static class SystemEndpoints
// wohin die Bilder wandern, musste die Compose-Datei lesen. Und wenn
// das `cache`-Volume vollläuft, ist das der erste Ort, an dem man
// nachsieht — nicht der letzte.
- app.MapGet("/api/v1/system/storage", (FlaroPaths pfade, Kratzplatzpflege pflege) =>
+ app.MapGet("/api/v1/system/storage", (FlaroPaths pfade, Transcodepflege pflege) =>
{
Speicherort Messen(string name, string variable, string pfad) =>
new(name, variable, pfad, Belegt(pfad), Frei(pfad),
name == "Bilder" ? Belegt(Path.Combine(pfad, "images")) : null,
- // **Beim Kratzplatz aufgeschlüsselt.** „39 GB belegt" sagt
+ // **Beim Transcode-Cache aufgeschlüsselt.** „39 GB belegt" sagt
// nicht, ob das Segmente, umgepackte Filme oder Untertitel
// sind — und davon hängt ab, ob es ein Problem ist.
name == "Umpacken" ? pflege.Belegung() : null);
@@ -269,7 +269,7 @@ public static class SystemEndpoints
});
}).RequireAuthorization(FlaroPolicies.Administrator);
- // **Den Kratzplatz aufräumen.**
+ // **Den Transcode-Cache aufräumen.**
//
// POST und nicht DELETE wie beim Bildspeicher darüber: dort wird ein
// Ordner geleert, hier eine Regel angewandt. Etwas, das Verwaistes
@@ -301,8 +301,8 @@ public static class SystemEndpoints
}).RequireAuthorization(FlaroPolicies.Administrator);
app.MapPost("/api/v1/system/storage/transcode/cleanup", async (
- Kratzplatzpflege pflege, CancellationToken ct) =>
- Results.Ok(await pflege.LaufenAsync(Kratzplatzpflege.Anlass.VonHand, ct)))
+ Transcodepflege pflege, CancellationToken ct) =>
+ Results.Ok(await pflege.LaufenAsync(Transcodepflege.Anlass.VonHand, ct)))
.RequireAuthorization(FlaroPolicies.Administrator);
// **Den Bildspeicher leeren.**
diff --git a/server/src/Flaro.Api/PackageCoordinator.cs b/server/src/Flaro.Api/PackageCoordinator.cs
index 0c43330..381e82d 100644
--- a/server/src/Flaro.Api/PackageCoordinator.cs
+++ b/server/src/Flaro.Api/PackageCoordinator.cs
@@ -177,8 +177,8 @@ public sealed class PackageCoordinator(
// Umpacken ist der eine, in dem der Platz wirklich gebraucht
// wird — und `PlatzPruefen` sagt danach die Wahrheit statt
// „kein Platz", obwohl zwanzig Gigabyte Altlast herumliegen.
- await bereich.ServiceProvider.GetRequiredService()
- .LaufenAsync(Kratzplatzpflege.Anlass.VorDemUmpacken);
+ await bereich.ServiceProvider.GetRequiredService()
+ .LaufenAsync(Transcodepflege.Anlass.VorDemUmpacken);
PlatzPruefen(datei.SizeBytes);
@@ -237,7 +237,7 @@ public sealed class PackageCoordinator(
if (platte.AvailableFreeSpace >= gebraucht) return;
throw new InvalidOperationException(
- $"Auf dem Kratzplatz ({pfade.Transcode}) sind noch " +
+ $"Auf dem Transcode-Cache ({pfade.Transcode}) sind noch " +
$"{platte.AvailableFreeSpace / (1024 * 1024)} MB frei, gebraucht werden etwa " +
$"{gebraucht / (1024 * 1024)} MB. Räume dort auf oder hänge ein grösseres Volume ein.");
}
diff --git a/server/src/Flaro.Api/Program.cs b/server/src/Flaro.Api/Program.cs
index 379f9d6..975e9b6 100644
--- a/server/src/Flaro.Api/Program.cs
+++ b/server/src/Flaro.Api/Program.cs
@@ -75,7 +75,7 @@ builder.Services.AddRateLimiter(o =>
// und lässt einen Absturz dort den Server nicht mitnehmen.
builder.Services.AddSingleton(FfmpegOptions.FromEnvironment());
builder.Services.AddSingleton();
-builder.Services.AddSingleton();
+builder.Services.AddSingleton();
builder.Services.AddSingleton();
// Beiwerk im Medienordner — standardmässig aus. In fremde Medienordner zu
@@ -172,11 +172,11 @@ builder.Services.AddScoped();
builder.Services.AddSingleton();
// Vorspann und Abspann: der Leser ist zustandslos, die Erkennung hält den
// Vermerk, welche Serie gerade abgetastet wird.
-builder.Services.AddSingleton();
+builder.Services.AddSingleton();
// Bei Filmen führt kein Ton zum Abspann — nur das Bild.
-builder.Services.AddSingleton();
+builder.Services.AddSingleton();
builder.Services.AddSingleton();
-builder.Services.AddSingleton();
+builder.Services.AddSingleton();
builder.Services.AddSingleton();
// **Neben dem Umpacker, nicht an seiner Stelle.** Der baut eine fertige Datei
@@ -184,9 +184,9 @@ builder.Services.AddSingleton();
builder.Services.AddSingleton();
builder.Services.AddSingleton();
builder.Services.AddSingleton();
-// Der Kratzplatz räumt sich auf — kein Dienst, kein Takt, vier Anlässe.
-builder.Services.AddScoped();
-builder.Services.AddSingleton();
+// Der Transcode-Cache räumt sich auf — kein Dienst, kein Takt, vier Anlässe.
+builder.Services.AddScoped();
+builder.Services.AddSingleton();
// **Der einzige Hintergrunddienst, und mit Bedacht.** Eine Sicherung hängt
// an keinem Aufruf: sie soll alle drei Tage geschehen, auch wenn niemand
@@ -449,8 +449,8 @@ if (Directory.Exists(oberfläche))
// Stromordner noch da und jedes Teilstück eines abgebrochenen
// Umpackens — und beim Start ist sicher, dass niemand davon noch
// gebraucht wird.
- _ = app.Services.GetRequiredService()
- .LaufenAsync(Kratzplatzpflege.Anlass.Start, leben.ApplicationStopping);
+ _ = app.Services.GetRequiredService()
+ .LaufenAsync(Transcodepflege.Anlass.Start, leben.ApplicationStopping);
_ = app.Services.GetRequiredService()
.NachUpdateAsync(leben.ApplicationStopping);
diff --git a/server/src/Flaro.Api/ScanCoordinator.cs b/server/src/Flaro.Api/ScanCoordinator.cs
index ee5c946..4dc3e0d 100644
--- a/server/src/Flaro.Api/ScanCoordinator.cs
+++ b/server/src/Flaro.Api/ScanCoordinator.cs
@@ -172,15 +172,15 @@ public sealed class ScanCoordinator(
// Verwaiste entstehen: Dateien verschwinden, Änderungszeiten
// wandern, und die dazugehörigen Pakete und Untertitel gehören
// zu einem Stand, den es nicht mehr gibt.
- _ = bereich.ServiceProvider.GetRequiredService()
- .LaufenAsync(Kratzplatzpflege.Anlass.NachDemScan);
+ _ = bereich.ServiceProvider.GetRequiredService()
+ .LaufenAsync(Transcodepflege.Anlass.NachDemScan);
- // **Und zuletzt die Marken.** Ein Scan ist der Augenblick, in
+ // **Und zuletzt die Segmente.** 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
+ // Segment 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()
+ _ = bereich.ServiceProvider.GetRequiredService()
.NachScanAsync(libraryId);
_zustand[libraryId] = _zustand[libraryId] with
diff --git a/server/src/Flaro.Api/Markenerkennung.cs b/server/src/Flaro.Api/Segmenterkennung.cs
similarity index 81%
rename from server/src/Flaro.Api/Markenerkennung.cs
rename to server/src/Flaro.Api/Segmenterkennung.cs
index efc6e63..98a85ca 100644
--- a/server/src/Flaro.Api/Markenerkennung.cs
+++ b/server/src/Flaro.Api/Segmenterkennung.cs
@@ -7,14 +7,14 @@ using Microsoft.EntityFrameworkCore;
namespace Flaro.Api;
-/// Wie weit ein Markenlauf greift.
+/// Wie weit ein Segmentlauf greift.
///
///
///
-/// „Schon durchsucht" und „hat eine Marke" sind zwei verschiedene
+/// „Schon durchsucht" und „hat ein Segment" sind zwei verschiedene
/// Dinge, und genau dazwischen fehlte bisher die Unterscheidung. Ein
/// Film, bei dem die Suche einmal nichts fand, trägt einen Vermerk und
-/// keine Marke: der nächste Lauf übersprang ihn, obwohl er der
+/// kein Segment: der nächste Lauf übersprang ihn, obwohl er der
/// interessanteste Fall ist.
///
///
@@ -23,18 +23,18 @@ namespace Flaro.Api;
/// steht.
///
///
-public enum Markenumfang
+public enum Segmentumfang
{
/// Nur, wo noch nie gesucht wurde. Der Lauf nach einem Scan.
Neue,
///
/// Auch, wo gesucht und nichts gefunden wurde — aber nicht, wo schon
- /// eine Marke steht.
+ /// ein Segment steht.
///
Luecken,
- /// Alles, auch was längst eine Marke hat.
+ /// Alles, auch was längst ein Segment hat.
Alles,
}
@@ -43,7 +43,7 @@ public enum Markenumfang
///
///
/// Beides mit derselben Rechnung, nur in einem anderen Fenster.
-/// sucht das längste Stück, das zwei Tonabdrücke
+/// sucht das längste Stück, das zwei Tonabdrücke
/// teilen — was dabei herauskommt, entscheidet allein, welchen Ausschnitt
/// der Folge man ihm gibt. Vorn ist es der Vorspann, hinten der Abspann.
/// Eine zweite Erkennung dafür zu schreiben hiesse, dieselbe Suche zweimal
@@ -60,10 +60,10 @@ public enum Markenumfang
/// Zeitlimit zu laufen und am Ende nichts zu haben.
///
///
-public sealed class Markenerkennung(
+public sealed class Segmenterkennung(
IServiceScopeFactory scopes,
TimeProvider clock,
- ILogger log)
+ ILogger log)
{
/// Wie viel vom Anfang nach einem Vorspann abgesucht wird.
///
@@ -105,7 +105,7 @@ public sealed class Markenerkennung(
/// gleich wieder weggeworfen — gefunden, verworfen, und am Ende stand
/// kein Knopf da, obwohl die Erkennung gearbeitet hatte.
///
- /// Weiter als das Suchfenster kann eine Marke ohnehin nicht reichen;
+ /// Weiter als das Suchfenster kann ein Segment ohnehin nicht reichen;
/// mehr als dieses Fenster zu erlauben hiesse nichts zu erlauben.
///
public const double Hoechstabspann = Abspannfenster;
@@ -121,7 +121,7 @@ public sealed class Markenerkennung(
///
/// Ein eigener Zustand, nicht der des Scans. Der
/// ScanCoordinator setzt seinen Lauf auf „fertig", sobald er die
- /// Markenerkennung angestossen hat — in dasselbe Feld zu schreiben
+ /// Segmenterkennung angestossen hat — in dasselbe Feld zu schreiben
/// liesse einen beendeten Scan wieder laufend aussehen.
///
///
@@ -136,7 +136,7 @@ public sealed class Markenerkennung(
_stand.TryGetValue(mediathek, out var schritt) ? schritt : null;
/// Startet den Lauf für eine Serie — oder sagt, dass schon einer läuft.
- public bool Starten(Guid serieId, Markenumfang umfang = Markenumfang.Neue)
+ public bool Starten(Guid serieId, Segmentumfang umfang = Segmentumfang.Neue)
{
if (!_laufend.TryAdd(serieId, true)) return false;
@@ -148,7 +148,7 @@ public sealed class Markenerkennung(
}
catch (Exception fehler)
{
- log.LogWarning(fehler, "Markensuche für {Serie} abgebrochen: {Grund}",
+ log.LogWarning(fehler, "Segmentsuche für {Serie} abgebrochen: {Grund}",
serieId, Fehlertext.FuerMenschen(fehler));
}
finally
@@ -159,12 +159,12 @@ public sealed class Markenerkennung(
return true;
}
- /// Nach einem Scan: jede Serie der Mediathek, der noch Marken fehlen.
+ /// Nach einem Scan: jede Serie der Mediathek, der noch Segmente fehlen.
///
///
///
/// Hier, weil es sonst nie passiert. Eine neue Folge bekommt
- /// keine Marke, wenn niemand danach fragt — und niemand fragt, weil man
+ /// kein Segment, 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.
///
@@ -177,12 +177,12 @@ public sealed class Markenerkennung(
///
///
public async Task NachScanAsync(
- Guid mediathek, Markenumfang umfang = Markenumfang.Neue,
+ Guid mediathek, Segmentumfang umfang = Segmentumfang.Neue,
CancellationToken ct = default)
{
using var raum = scopes.CreateScope();
var db = raum.ServiceProvider.GetRequiredService();
- var leser = raum.ServiceProvider.GetRequiredService();
+ var leser = raum.ServiceProvider.GetRequiredService();
var mediathekname = await db.Libraries.AsNoTracking()
.Where(x => x.Id == mediathek).Select(x => x.Name).FirstOrDefaultAsync(ct)
@@ -228,16 +228,16 @@ public sealed class Markenerkennung(
/// Alle Serien einer Mediathek, eine nach der anderen.
private async Task SerienAsync(
FlaroContext db, string mediathekname, IReadOnlyList<(Guid Id, string Titel)> serien,
- Guid mediathek, Markenumfang umfang, CancellationToken ct)
+ Guid mediathek, Segmentumfang umfang, CancellationToken ct)
{
var folgen = await db.Episodes.AsNoTracking()
.CountAsync(f => db.Series.Any(s => s.Id == f.SeriesId && s.LibraryId == mediathek), ct);
log.LogInformation(
- "Markensuche in „{Mediathek}“: {Serien} Serie(n), {Folgen} Folge(n). "
+ "Segmentsuche in „{Mediathek}“: {Serien} Serie(n), {Folgen} Folge(n). "
+ "Das kann Stunden dauern.", mediathekname, serien.Count, folgen);
- var marken = 0;
+ var segmente = 0;
var ohneFund = 0;
var angefasst = 0;
@@ -253,16 +253,16 @@ public sealed class Markenerkennung(
void Melden(Scanschritt schritt) =>
_stand[mediathek] = schritt with { Erledigt = nummer, Gesamt = serien.Count };
- Melden(new Scanschritt(Scanphase.Marken, titel));
+ Melden(new Scanschritt(Scanphase.Segmente, titel));
if (!_laufend.TryAdd(id, true)) continue;
try
{
var stand = await LaufenAsync(id, umfang, ct, Melden);
- marken += stand.Marken;
+ segmente += stand.Segmente;
if (!stand.Angefasst) continue;
angefasst++;
- if (stand.Marken == 0) ohneFund++;
+ if (stand.Segmente == 0) ohneFund++;
}
catch (Exception fehler)
{
@@ -270,16 +270,16 @@ public sealed class Markenerkennung(
// `Message` an, und bei EF Core steht dort „See the inner
// exception for details." — eine Zeile, die ausdrücklich
// sagt, dass sie die Auskunft nicht enthält.
- log.LogWarning(fehler, "Markensuche für „{Serie}“ abgebrochen: {Grund}",
+ log.LogWarning(fehler, "Segmentsuche für „{Serie}“ abgebrochen: {Grund}",
titel, Fehlertext.FuerMenschen(fehler));
}
finally { _laufend.TryRemove(id, out _); }
}
log.LogInformation(
- "Markensuche in „{Mediathek}“ durch: {Marken} Marke(n) in {Angefasst} von "
+ "Segmentsuche in „{Mediathek}“ durch: {Segmente} Segment(n) in {Angefasst} von "
+ "{Serien} Serie(n) gesucht, {Ohne} ohne Fund.",
- mediathekname, marken, angefasst, serien.Count, ohneFund);
+ mediathekname, segmente, angefasst, serien.Count, ohneFund);
}
/// Startet den Lauf für eine ganze Mediathek — im Hintergrund.
@@ -289,16 +289,16 @@ public sealed class Markenerkennung(
/// dauert das Stunden; eine Anfrage so lange offen zu halten hiesse,
/// sie in ein Zeitlimit laufen zu lassen. Wie es steht, sieht man im
/// Tagesprotokoll — und an den Folgen selbst, bei denen danach eine
- /// Marke steht.
+ /// Segment steht.
///
- public void MediathekStarten(Guid mediathek, Markenumfang umfang)
+ public void MediathekStarten(Guid mediathek, Segmentumfang umfang)
{
_ = Task.Run(async () =>
{
try { await NachScanAsync(mediathek, umfang, CancellationToken.None); }
catch (Exception fehler)
{
- log.LogWarning(fehler, "Markenlauf für Mediathek {Kennung} abgebrochen: {Grund}",
+ log.LogWarning(fehler, "Segmentlauf für Mediathek {Kennung} abgebrochen: {Grund}",
mediathek, Fehlertext.FuerMenschen(fehler));
}
});
@@ -312,7 +312,7 @@ public sealed class Markenerkennung(
/// läuft dann ins Leere, und das kostet eine Abfrage.
///
private async Task FilmeAsync(
- FlaroContext db, string mediathekname, Guid mediathek, Markenumfang umfang,
+ FlaroContext db, string mediathekname, Guid mediathek, Segmentumfang umfang,
CancellationToken ct, Action melden)
{
// **In C# verzweigt, nicht im Ausdruck.** Drei Bedingungen in einem
@@ -322,12 +322,12 @@ public sealed class Markenerkennung(
var abfrage = db.Movies.AsNoTracking().Where(m => m.LibraryId == mediathek);
abfrage = umfang switch
{
- Markenumfang.Alles => abfrage,
+ Segmentumfang.Alles => abfrage,
// Die Lücke: gesucht wurde vielleicht, gefunden nichts.
- Markenumfang.Luecken => abfrage.Where(m =>
- !db.Marken.Any(x => x.MovieId == m.Id && x.Art == Markenart.Abspann)),
+ Segmentumfang.Luecken => abfrage.Where(m =>
+ !db.Segmente.Any(x => x.MovieId == m.Id && x.Art == Segmentart.Abspann)),
_ => abfrage.Where(m =>
- !db.Markensuchen.Any(x => x.MovieId == m.Id && x.Art == Markenart.Abspann)),
+ !db.Segmentsuchen.Any(x => x.MovieId == m.Id && x.Art == Segmentart.Abspann)),
};
var offen = await abfrage
@@ -346,7 +346,7 @@ public sealed class Markenerkennung(
var film = offen[i];
var titel = Filmname(film.Title, film.Year);
- melden(new Scanschritt(Scanphase.Marken, titel, i + 1, offen.Count));
+ melden(new Scanschritt(Scanphase.Segmente, titel, i + 1, offen.Count));
try { if (await FilmAsync(film.Id, umfang, ct)) gefunden++; }
catch (Exception fehler)
@@ -380,16 +380,16 @@ public sealed class Markenerkennung(
///
/// Erst grob, dann zielen. Ein Überblick über die letzten zwanzig
/// Minuten sagt, ob es eine dunkle Strecke gibt; er kommt aus
- /// Schlüsselbildern und ist deshalb billig, aber auf fünf bis zehn
+ /// Keyframesn und ist deshalb billig, aber auf fünf bis zehn
/// Sekunden ungenau. Die Kante holt danach eine Handvoll kurzer Sonden.
///
///
public async Task FilmAsync(
- Guid filmId, Markenumfang umfang, CancellationToken ct = default)
+ Guid filmId, Segmentumfang umfang, CancellationToken ct = default)
{
using var raum = scopes.CreateScope();
var db = raum.ServiceProvider.GetRequiredService();
- var abtaster = raum.ServiceProvider.GetRequiredService();
+ var abtaster = raum.ServiceProvider.GetRequiredService();
var kapitelleser = raum.ServiceProvider.GetRequiredService();
var film = await db.Movies.AsNoTracking()
@@ -416,21 +416,21 @@ public sealed class Markenerkennung(
return false;
}
- if (umfang == Markenumfang.Luecken && await db.Marken.AsNoTracking()
- .AnyAsync(m => m.MovieId == filmId && m.Art == Markenart.Abspann, ct))
+ if (umfang == Segmentumfang.Luecken && await db.Segmente.AsNoTracking()
+ .AnyAsync(m => m.MovieId == filmId && m.Art == Segmentart.Abspann, ct))
{
log.LogInformation("„{Film}“: hat schon einen Abspann — übersprungen.", titel);
return false;
}
- if (umfang == Markenumfang.Neue && await db.Markensuchen.AsNoTracking()
- .AnyAsync(x => x.MovieId == filmId && x.Art == Markenart.Abspann, ct))
+ if (umfang == Segmentumfang.Neue && await db.Segmentsuchen.AsNoTracking()
+ .AnyAsync(x => x.MovieId == filmId && x.Art == Segmentart.Abspann, ct))
{
log.LogInformation("„{Film}“: schon durchsucht — übersprungen.", titel);
return false;
}
- await VermerkenAsync(db, filmId, Markenart.Abspann, ct);
+ await VermerkenAsync(db, filmId, Segmentart.Abspann, ct);
var gefunden = await BildabspannAsync(
db, abtaster, kapitelleser, filmId, istFilm: true, titel, film.Path,
@@ -438,12 +438,12 @@ public sealed class Markenerkennung(
if (!gefunden)
{
- // Nichts gefunden ist ein Ergebnis. Eine alte Marke, die jetzt
+ // Nichts gefunden ist ein Ergebnis. Eine altes Segment, die jetzt
// nicht mehr bestätigt wird, gehört weg — sonst bliebe ein
// Fehlgriff für immer stehen.
- var alt = await db.Marken
- .FirstOrDefaultAsync(m => m.MovieId == filmId && m.Art == Markenart.Abspann, ct);
- if (alt is not null) db.Marken.Remove(alt);
+ var alt = await db.Segmente
+ .FirstOrDefaultAsync(m => m.MovieId == filmId && m.Art == Segmentart.Abspann, ct);
+ if (alt is not null) db.Segmente.Remove(alt);
}
await db.SaveChangesAsync(ct);
@@ -456,12 +456,12 @@ public sealed class Markenerkennung(
///
/// Dunkel und bis zum Ende durchhalten — mehr braucht es nicht,
/// und mehr hat ein Abspann auch nicht gemeinsam. Erst ein grober
- /// Überblick über die letzten zwanzig Minuten aus Schlüsselbildern
+ /// Überblick über die letzten zwanzig Minuten aus Keyframesn
/// (billig, aber auf fünf bis zehn Sekunden ungenau), dann eine Handvoll
/// kurzer Sonden für die Kante.
///
///
- /// Warum das auch bei Folgen gebraucht wird. Der Tonabgleich lebt
+ /// Warum das auch bei Folgen gebraucht wird. Der Fingerprintabgleich lebt
/// davon, dass der Abspann in jeder Folge derselbe ist — und genau das
/// stimmt beim Staffelfinale oft nicht: anderer Schluss, andere Musik,
/// kein Treffer. Das Bild weiss davon nichts; dort sieht das Finale aus
@@ -474,7 +474,7 @@ public sealed class Markenerkennung(
/// ein Pfad beantwortet die Frage „welche Folge war das?" nicht.
///
private async Task BildabspannAsync(
- FlaroContext db, Bildabtaster abtaster, Kapitelleser kapitelleser,
+ FlaroContext db, Frameabtaster abtaster, Kapitelleser kapitelleser,
Guid titelId, bool istFilm, string name, string pfad, double laufzeit,
CancellationToken ct)
{
@@ -482,7 +482,7 @@ public sealed class Markenerkennung(
// **Erst fragen, ob die Datei es selbst weiss.** Ein Kapitel kostet
// Millisekunden — ffprobe liest nur den Kopf —, die Helligkeit
- // zwanzig Minuten Schlüsselbilder. Und es findet, woran die
+ // zwanzig Minuten Keyframes. Und es findet, woran die
// Helligkeit scheitert: einen Abspann über laufendem Bild, wie bei
// „Alice im Wunderland" oder den Outtakes in „(T)Raumschiff
// Surprise".
@@ -492,7 +492,7 @@ public sealed class Markenerkennung(
log.LogInformation("„{Titel}“: Abspann laut Kapitel ab {Stelle:h\\:mm\\:ss}.",
name, TimeSpan.FromSeconds(ausKapitel));
- return await SchreibenAsync(db, titelId, istFilm, Markenart.Abspann,
+ return await SchreibenAsync(db, titelId, istFilm, Segmentart.Abspann,
ausKapitel, laufzeit, ct);
}
@@ -500,7 +500,7 @@ public sealed class Markenerkennung(
var proben = await abtaster.UeberblickAsync(pfad, ab, laufzeit - ab, ct);
// **Ein Lauf ohne Fund soll sagen, warum.** „Kein Abspann erkannt"
- // allein ist keine Auskunft: ob die Datei zu wenige Schlüsselbilder
+ // allein ist keine Auskunft: ob die Datei zu wenige Keyframes
// hat, ob der Abspann zu hell war oder ob am Schluss eine Tafel
// steht, sieht man nur an diesen drei Zahlen — und sonst gar nicht.
var stücke = Abspannkante.Suchen(proben, regeln, out var grund);
@@ -520,7 +520,7 @@ public sealed class Markenerkennung(
// **Nur die erste Kante wird nachgemessen.** Sie ist die, die der
// Zuschauer zuerst sieht, und die, an der „gilt als gesehen" hängt.
- // Die übrigen Grenzen liegen an Schlüsselbildern und sind damit auf
+ // Die übrigen Grenzen liegen an Keyframesn und sind damit auf
// fünf bis zehn Sekunden genau — dafür lohnt keine Sonde.
var hell = Abspannkante.HellDavor(proben, stücke[0].Von, regeln) ?? ab;
var kante = await Abspannkante.VerfeinernAsync(
@@ -545,28 +545,28 @@ public sealed class Markenerkennung(
(a, b) => $"{Zeit(a.Bis)}–{Zeit(b.Von)}")));
}
- return await SchreibenAsync(db, titelId, istFilm, Markenart.Abspann, fein, ct);
+ return await SchreibenAsync(db, titelId, istFilm, Segmentart.Abspann, fein, ct);
}
/// Was ein Serienlauf ergeben hat — für die Zusammenfassung.
///
- /// Wie viele Marken geschrieben wurden.
+ /// Wie viele Segmente geschrieben wurden.
///
/// Ob überhaupt gesucht wurde. Eine Serie, deren Staffeln alle schon
/// durchsucht sind, zählt nicht als „ohne Fund" — sie wurde gar nicht
/// erst gefragt, und beides in einen Topf zu werfen machte die
/// Zusammenfassung wertlos.
///
- private readonly record struct Serienstand(int Marken, bool Angefasst);
+ private readonly record struct Serienstand(int Segmente, bool Angefasst);
private async Task LaufenAsync(
- Guid serieId, Markenumfang umfang, CancellationToken ct,
+ Guid serieId, Segmentumfang umfang, CancellationToken ct,
Action? melden = null)
{
using var raum = scopes.CreateScope();
var db = raum.ServiceProvider.GetRequiredService();
- var leser = raum.ServiceProvider.GetRequiredService();
- var abtaster = raum.ServiceProvider.GetRequiredService();
+ var leser = raum.ServiceProvider.GetRequiredService();
+ var abtaster = raum.ServiceProvider.GetRequiredService();
var kapitelleser = raum.ServiceProvider.GetRequiredService();
var serienname = await db.Series.AsNoTracking()
@@ -595,14 +595,14 @@ public sealed class Markenerkennung(
// **Was schon einmal gesucht wurde, und mit wie vielen Folgen.**
// Nicht „was gefunden wurde": eine Staffel ohne Abspann sähe sonst
// bei jedem Scan aus wie eine, in der noch niemand nachgesehen hat.
- var gesucht = await db.Markensuchen.AsNoTracking()
+ var gesucht = await db.Segmentsuchen.AsNoTracking()
.Where(x => x.SeriesId == serieId)
.ToListAsync(ct);
// **Und was tatsächlich an den Folgen hängt.** Für „nur die
// Lücken" reicht der Vermerk nicht: er sagt, dass gesucht wurde,
// nicht ob etwas herauskam.
- var vorhanden = await db.Marken.AsNoTracking()
+ var vorhanden = await db.Segmente.AsNoTracking()
.Where(m => m.EpisodeId != null
&& db.Episodes.Any(f => f.Id == m.EpisodeId && f.SeriesId == serieId))
.Select(m => new { Folge = m.EpisodeId!.Value, m.Art })
@@ -613,7 +613,7 @@ public sealed class Markenerkennung(
log.LogInformation("„{Serie}“: {Staffeln} Staffel(n), {Folgen} Folge(n).",
serienname, staffeln.Count, folgen.Count);
- // Getrennt gezählt: „14 Marken" beantwortet nicht, ob diese Serie
+ // Getrennt gezählt: „14 Segmente" beantwortet nicht, ob diese Serie
// ihre Vorspänne bekommen hat — und das ist die häufigere Frage.
var gefundenIntro = 0;
var gefundenAbspann = 0;
@@ -629,25 +629,25 @@ public sealed class Markenerkennung(
// **Einmal lesen, zweimal schneiden.** fpcalc kann nicht
// springen — es liest immer von vorn. Also die ganze Spur je
// Folge, und Vorspann wie Abspann sind danach zwei Ausschnitte
- // aus demselben Abdruck. Das ist nicht nur richtig, sondern
+ // aus demselben Fingerprint. Das ist nicht nur richtig, sondern
// billiger als zweimal lesen.
// Eine Art fehlt dieser Staffel, sobald irgendeine ihrer Folgen
- // sie nicht hat. Der Tonabgleich ist Sache der ganzen Staffel,
+ // sie nicht hat. Der Fingerprintabgleich ist Sache der ganzen Staffel,
// nicht der einzelnen Folge — für eine Lücke läuft sie ganz.
- bool Luecke(Markenart art) => geordnet.Exists(
+ bool Luecke(Segmentart art) => geordnet.Exists(
f => !vorhanden.Exists(v => v.Folge == f.Id && v.Art == art));
- var fehlt = new[] { Markenart.Intro, Markenart.Abspann }
- .Where(art => umfang == Markenumfang.Alles
+ var fehlt = new[] { Segmentart.Intro, Segmentart.Abspann }
+ .Where(art => umfang == Segmentumfang.Alles
|| gesucht.FirstOrDefault(
x => x.Staffel == staffel.Key && x.Art == art) is not { } lauf
|| lauf.Folgen != geordnet.Count
- || (umfang == Markenumfang.Luecken && Luecke(art)))
+ || (umfang == Segmentumfang.Luecken && Luecke(art)))
.ToList();
if (fehlt.Count == 0) continue;
angefasst = true;
- // **Der Tonabgleich braucht etwas zum Vergleichen.** Eine
+ // **Der Fingerprintabgleich braucht etwas zum Vergleichen.** Eine
// Staffel mit einer einzigen Folge — eine laufende Serie, von
// der erst die Premiere da ist — hat das nicht. Früher wurde
// sie hier übersprungen, und zwar ganz; seit es den Bildweg
@@ -657,15 +657,15 @@ public sealed class Markenerkennung(
{
for (var i = 0; i < geordnet.Count; i++)
{
- melden?.Invoke(new Scanschritt(Scanphase.Marken, Name(i)));
- var abdruck = await leser.LesenAsync(geordnet[i].Path, ct);
- if (abdruck.Count > 0) ganze[geordnet[i].Id] = abdruck;
+ melden?.Invoke(new Scanschritt(Scanphase.Segmente, Name(i)));
+ var fingerprint = await leser.LesenAsync(geordnet[i].Path, ct);
+ if (fingerprint.Count > 0) ganze[geordnet[i].Id] = fingerprint;
}
}
else
{
log.LogInformation(
- "„{Serie}“, Staffel {Staffel}: nur eine Folge — Tonabgleich entfällt, "
+ "„{Serie}“, Staffel {Staffel}: nur eine Folge — Fingerprintabgleich entfällt, "
+ "es bleibt das Bild.", serienname, staffel.Key);
}
@@ -675,7 +675,7 @@ public sealed class Markenerkennung(
foreach (var art in tonarten)
{
- var abdruecke = new List<(Guid Schlüssel, IReadOnlyList Abdruck)>();
+ var fingerprints = new List<(Guid Schlüssel, IReadOnlyList Fingerprint)>();
var fensteranfang = new Dictionary();
foreach (var folge in geordnet)
@@ -685,21 +685,21 @@ public sealed class Markenerkennung(
var (vonIndex, anzahl) = Ausschnitt(art, ganz.Count);
if (anzahl < 2) continue;
- abdruecke.Add((folge.Id, ganz.Skip(vonIndex).Take(anzahl).ToList()));
- fensteranfang[folge.Id] = vonIndex * Tonabdruckleser.SekundenJeAbdruck;
+ fingerprints.Add((folge.Id, ganz.Skip(vonIndex).Take(anzahl).ToList()));
+ fensteranfang[folge.Id] = vonIndex * Fingerprintleser.SekundenJeFingerprint;
}
- if (abdruecke.Count < 2) continue;
+ if (fingerprints.Count < 2) continue;
- var treffer = Tonabgleich.ReiheAbgleichen(
- abdruecke, Tonabdruckleser.SekundenJeAbdruck);
+ var treffer = Fingerprintabgleich.ReiheAbgleichen(
+ fingerprints, Fingerprintleser.SekundenJeFingerprint);
// **Vermerken, dass gesucht wurde — vor dem Ergebnis.**
// Auch ein Lauf ohne Fund ist ein Lauf; ihn nicht zu
// vermerken hiesse, ihn beim nächsten Scan zu wiederholen.
await VermerkenAsync(db, serieId, staffel.Key, art, geordnet.Count, ct);
- var grenze = art == Markenart.Intro ? Hoechstlaenge : Hoechstabspann;
+ var grenze = art == Segmentart.Intro ? Hoechstlaenge : Hoechstabspann;
foreach (var (folgenId, stelle) in treffer)
{
@@ -713,18 +713,18 @@ public sealed class Markenerkennung(
if (!await SchreibenAsync(db, folgenId, istFilm: false, art,
stelle.Von + versatz, stelle.Bis + versatz, ct)) continue;
- if (art == Markenart.Intro) gefundenIntro++; else gefundenAbspann++;
+ if (art == Segmentart.Intro) gefundenIntro++; else gefundenAbspann++;
}
}
var nummern = geordnet.Select(f => f.Id).ToList();
var ueberBild = new HashSet();
- if (fehlt.Contains(Markenart.Abspann))
+ if (fehlt.Contains(Segmentart.Abspann))
{
- // Auch wenn der Tonabgleich gar nicht erst lief: gesucht wurde,
+ // Auch wenn der Fingerprintabgleich gar nicht erst lief: gesucht wurde,
// und ohne Vermerk liefe der Bildweg bei jedem Scan von Neuem.
- await VermerkenAsync(db, serieId, staffel.Key, Markenart.Abspann,
+ await VermerkenAsync(db, serieId, staffel.Key, Segmentart.Abspann,
geordnet.Count, ct);
// **Was der Ton nicht fand, sucht das Bild.**
@@ -737,13 +737,13 @@ public sealed class Markenerkennung(
// andere Folge.
//
// **Nur für die Lücken.** Der Überblick dekodiert zwanzig
- // Minuten Schlüsselbilder; bei einer Staffel ohne Lücke läuft
+ // Minuten Keyframes; bei einer Staffel ohne Lücke läuft
// er gar nicht, mit Lücke ein- bis zweimal. Erst speichern,
- // sonst sieht die Abfrage die eben geschriebenen Marken nicht.
+ // sonst sieht die Abfrage die eben geschriebenen Segmente nicht.
await db.SaveChangesAsync(ct);
- var mitAbspann = await db.Marken.AsNoTracking()
- .Where(m => m.Art == Markenart.Abspann
+ var mitAbspann = await db.Segmente.AsNoTracking()
+ .Where(m => m.Art == Segmentart.Abspann
&& m.EpisodeId != null && nummern.Contains(m.EpisodeId.Value))
.Select(m => m.EpisodeId!.Value)
.ToListAsync(ct);
@@ -754,7 +754,7 @@ public sealed class Markenerkennung(
if (mitAbspann.Contains(folge.Id)) continue;
if (folge.Duration is not { TotalSeconds: > 0 } dauer) continue;
- melden?.Invoke(new Scanschritt(Scanphase.Marken, Name(i)));
+ melden?.Invoke(new Scanschritt(Scanphase.Segmente, Name(i)));
if (await BildabspannAsync(db, abtaster, kapitelleser, folge.Id,
istFilm: false, Name(i), folge.Path, dauer.TotalSeconds, ct))
@@ -805,13 +805,13 @@ public sealed class Markenerkennung(
CancellationToken ct)
{
var nummern = folgen.Select(f => f.Id).ToList();
- var marken = await db.Marken.AsNoTracking()
+ var segmente = await db.Segmente.AsNoTracking()
.Where(m => m.EpisodeId != null && nummern.Contains(m.EpisodeId.Value))
.Select(m => new { Folge = m.EpisodeId!.Value, m.Art, m.VonSekunden, m.BisSekunden })
.ToListAsync(ct);
- var mitIntro = marken.Count(m => m.Art == Markenart.Intro);
- var mitAbspann = marken.Count(m => m.Art == Markenart.Abspann);
+ var mitIntro = segmente.Count(m => m.Art == Segmentart.Intro);
+ var mitAbspann = segmente.Count(m => m.Art == Segmentart.Abspann);
log.LogInformation(
// **Jeder Platzhalter verbraucht ein Argument, auch ein
@@ -825,8 +825,8 @@ public sealed class Markenerkennung(
foreach (var (id, name) in folgen)
{
- var intro = marken.FirstOrDefault(m => m.Folge == id && m.Art == Markenart.Intro);
- var abspann = marken.FirstOrDefault(m => m.Folge == id && m.Art == Markenart.Abspann);
+ var intro = segmente.FirstOrDefault(m => m.Folge == id && m.Art == Segmentart.Intro);
+ var abspann = segmente.FirstOrDefault(m => m.Folge == id && m.Art == Segmentart.Abspann);
log.LogInformation("{Folge}: {Vorspann}, {Abspann}.",
name,
@@ -863,35 +863,35 @@ public sealed class Markenerkennung(
return string.IsNullOrWhiteSpace(titel) ? kopf : $"{kopf} · {titel}";
}
- /// Welcher Ausschnitt des Abdrucks abgetastet wird.
+ /// Welcher Ausschnitt des Fingerprints abgetastet wird.
///
///
- /// In Werten, nicht in Sekunden: der Abdruck ist eine Reihe, und
+ /// In Werten, nicht in Sekunden: der Fingerprint ist eine Reihe, und
/// geschnitten wird an einer Stelle darin. Die Laufzeit steht nicht mehr
- /// in der Rechnung — die Länge des Abdrucks ist sie.
+ /// in der Rechnung — die Länge des Fingerprints ist sie.
///
- private static (int Von, int Anzahl) Ausschnitt(Markenart art, int werte)
+ private static (int Von, int Anzahl) Ausschnitt(Segmentart art, int werte)
{
var fenster = (int)Math.Round(
- (art == Markenart.Intro ? Introfenster : Abspannfenster)
- / Tonabdruckleser.SekundenJeAbdruck);
+ (art == Segmentart.Intro ? Introfenster : Abspannfenster)
+ / Fingerprintleser.SekundenJeFingerprint);
if (fenster >= werte) return (0, werte);
- return art == Markenart.Intro ? (0, fenster) : (werte - fenster, fenster);
+ return art == Segmentart.Intro ? (0, fenster) : (werte - fenster, fenster);
}
/// Hält fest, dass bei diesem Film gesucht wurde.
private async Task VermerkenAsync(
- FlaroContext db, Guid filmId, Markenart art, CancellationToken ct)
+ FlaroContext db, Guid filmId, Segmentart art, CancellationToken ct)
{
- var alt = db.Markensuchen.Local
+ var alt = db.Segmentsuchen.Local
.FirstOrDefault(x => x.MovieId == filmId && x.Art == art)
- ?? await db.Markensuchen
+ ?? await db.Segmentsuchen
.FirstOrDefaultAsync(x => x.MovieId == filmId && x.Art == art, ct);
if (alt is not null) { alt.GelaufenAm = clock.GetUtcNow(); return; }
- db.Markensuchen.Add(new Markensuche
+ db.Segmentsuchen.Add(new Segmentsuche
{
MovieId = filmId,
Art = art,
@@ -902,20 +902,20 @@ public sealed class Markenerkennung(
/// Hält fest, dass in dieser Staffel gesucht wurde.
private async Task VermerkenAsync(
- FlaroContext db, Guid serieId, int staffel, Markenart art, int folgen, CancellationToken ct)
+ FlaroContext db, Guid serieId, int staffel, Segmentart art, int folgen, CancellationToken ct)
{
// **Erst im Änderungsspeicher nachsehen, dann in der Datenbank.**
//
// Eine Abfrage sieht nicht, was derselbe Lauf eben erst hinzugefügt
// und noch nicht gespeichert hat. Seit der Bildweg dazukam, wird
// für dieselbe Staffel zweimal vermerkt — einmal nach dem
- // Tonabgleich, einmal vor dem Bild —, und die zweite Abfrage fand
+ // Fingerprintabgleich, einmal vor dem Bild —, und die zweite Abfrage fand
// nichts, legte eine zweite Zeile an und brach beim Speichern am
// eindeutigen Schlüssel ab. Die Serie verlor dabei alles, was in
// diesem Lauf gefunden worden war.
- var alt = db.Markensuchen.Local
+ var alt = db.Segmentsuchen.Local
.FirstOrDefault(x => x.SeriesId == serieId && x.Staffel == staffel && x.Art == art)
- ?? await db.Markensuchen
+ ?? await db.Segmentsuchen
.FirstOrDefaultAsync(
x => x.SeriesId == serieId && x.Staffel == staffel && x.Art == art, ct);
@@ -926,7 +926,7 @@ public sealed class Markenerkennung(
return;
}
- db.Markensuchen.Add(new Markensuche
+ db.Segmentsuchen.Add(new Segmentsuche
{
SeriesId = serieId,
Staffel = staffel,
@@ -937,11 +937,11 @@ public sealed class Markenerkennung(
}
private Task SchreibenAsync(
- FlaroContext db, Guid titelId, bool istFilm, Markenart art,
+ FlaroContext db, Guid titelId, bool istFilm, Segmentart art,
double von, double bis, CancellationToken ct) =>
SchreibenAsync(db, titelId, istFilm, art, [new Abspannstück(von, bis)], ct);
- /// Setzt die Marken einer Art auf genau diese Stücke.
+ /// Setzt die Segmente einer Art auf genau diese Stücke.
///
///
///
@@ -955,24 +955,24 @@ public sealed class Markenerkennung(
///
/// In derselben Transaktion — gespeichert wird weiter oben, und
/// bis dahin stehen Löschung und Anlage zusammen. Dazwischen gibt es
- /// keinen Augenblick, in dem der Titel gar keine Marke hätte.
+ /// keinen Augenblick, in dem der Titel gar kein Segment hätte.
///
///
private async Task SchreibenAsync(
- FlaroContext db, Guid titelId, bool istFilm, Markenart art,
+ FlaroContext db, Guid titelId, bool istFilm, Segmentart art,
IReadOnlyList stücke, CancellationToken ct)
{
if (stücke.Count == 0) return false;
var alte = istFilm
- ? await db.Marken.Where(m => m.MovieId == titelId && m.Art == art).ToListAsync(ct)
- : await db.Marken.Where(m => m.EpisodeId == titelId && m.Art == art).ToListAsync(ct);
+ ? await db.Segmente.Where(m => m.MovieId == titelId && m.Art == art).ToListAsync(ct)
+ : await db.Segmente.Where(m => m.EpisodeId == titelId && m.Art == art).ToListAsync(ct);
- db.Marken.RemoveRange(alte);
+ db.Segmente.RemoveRange(alte);
foreach (var stück in stücke)
{
- db.Marken.Add(new Marke
+ db.Segmente.Add(new Segment
{
EpisodeId = istFilm ? null : titelId,
MovieId = istFilm ? titelId : null,
diff --git a/server/src/Flaro.Api/Streamkoordinator.cs b/server/src/Flaro.Api/Streamkoordinator.cs
index 32438a5..3340013 100644
--- a/server/src/Flaro.Api/Streamkoordinator.cs
+++ b/server/src/Flaro.Api/Streamkoordinator.cs
@@ -32,7 +32,7 @@ public sealed record Stromsitzung(
///
/// Daran hängt das Aufräumen. Ein geschlossener Browser sagt
/// niemandem Bescheid; bliebe die Sitzung stehen, liefe ffmpeg weiter und
- /// schriebe den ganzen Film auf den Kratzplatz.
+ /// schriebe den ganzen Film auf den Transcode-Cache.
///
public DateTimeOffset Gesehen { get; set; } = Begonnen;
@@ -61,7 +61,7 @@ public sealed class Streamkoordinator(
IServiceScopeFactory scopes,
FlaroPaths pfade,
FfmpegOptions ffmpeg,
- Schluesselbilder schluesselbilder,
+ Keyframes schluesselbilder,
TimeProvider clock,
ILogger log) : IDisposable
{
@@ -140,22 +140,22 @@ public sealed class Streamkoordinator(
var stufe = await bereich.ServiceProvider
.GetRequiredService().StufeAsync(ct);
- // **Beim Kopieren wird auf das Schlüsselbild gerundet.**
+ // **Beim Kopieren wird auf das Keyframe gerundet.**
//
// Ein kopierter Bildstrom lässt sich nicht mitten in einer Bildgruppe
- // aufschneiden; ffmpeg beginnt beim Schlüsselbild davor, während der
+ // aufschneiden; ffmpeg beginnt beim Keyframe davor, während der
// Ton neu gerechnet wird und genau beim Wunsch anfängt. Gemessen an
- // einer Quelle mit Schlüsselbildern alle fünf Sekunden, Sprung auf
+ // einer Quelle mit Keyframesn alle fünf Sekunden, Sprung auf
// Sekunde 12: Bild ab 0, Ton erst ab 1,98 — zwei Sekunden stummes
// Bild, und die Uhr des Spielers zwei Sekunden daneben.
//
// Beim Umkodieren entfällt das: dort wird das Bild neu gerechnet und
// schneidet auf die Sekunde genau. Dann vorzurunden hiesse, ohne Not
// ein Stück zurückzuspringen.
- // **Gesprungen wird auf den Wunsch, gemeldet wird das Schlüsselbild.**
+ // **Gesprungen wird auf den Wunsch, gemeldet wird das Keyframe.**
//
// Beim Kopieren springt ffmpeg ungenau — siehe `HlsArgumente` — und
- // landet damit auf dem Schlüsselbild vor dem Wunsch. Bild und Ton
+ // landet damit auf dem Keyframe vor dem Wunsch. Bild und Ton
// fangen dort gemeinsam an; das ist die Zusage, und sie hängt an
// keiner Rechnung hier.
//
@@ -188,14 +188,14 @@ public sealed class Streamkoordinator(
// **Mit Zeiten, und auch im Tagesprotokoll.**
//
// „Es dauert fünfzehn Sekunden" liess sich nicht beantworten: zwischen
- // dem Antippen und dem ersten Bild liegen die Schlüsselbildsuche, der
+ // dem Antippen und dem ersten Bild liegen die Keyframesuche, der
// Start von ffmpeg und das erste Segment, und keine dieser Strecken
// war irgendwo abzulesen. Eine Zeile je Wiedergabe ist kein Lärm —
// sie ist genau das, wonach man im Protokoll sucht.
var meldung =
$"Wiedergabe: {plan.Art} von {Path.GetFileName(datei.Path)} ab {sitzung.Ab:0.#}s"
+ (plan.Hoehe is { } h ? $", auf {h}p" : "")
- + $" — Schlüsselbildsuche {suche} ms, Vorbereitung {uhr.ElapsedMilliseconds} ms.";
+ + $" — Keyframesuche {suche} ms, Vorbereitung {uhr.ElapsedMilliseconds} ms.";
log.LogInformation("{Meldung}", meldung);
@@ -472,7 +472,7 @@ public sealed class Streamkoordinator(
/// Löschen in Abraeumen an einem IOException, ist die
/// Sitzung aus dem Verzeichnis heraus und der Ordner bleibt für immer
/// liegen. Die Altersprüfung nimmt dabei den Fall heraus, dass ein
- /// zweiter Prozess denselben Kratzplatz benutzt: ein aktiver Ordner
+ /// zweiter Prozess denselben Transcode-Cache benutzt: ein aktiver Ordner
/// bekommt alle paar Sekunden ein Segment geschrieben.
///
public long VerwaisteOrdnerWegraeumen(DateTimeOffset jetzt)
@@ -521,7 +521,7 @@ public sealed class Streamkoordinator(
// **Auch, was ein Absturz hinterlassen hat.** Nach einem harten Ende
// steht der Ordner noch da; ihn beim nächsten Herunterfahren zu
- // übersehen hiesse, dass der Kratzplatz nur wächst.
+ // übersehen hiesse, dass der Transcode-Cache nur wächst.
WurzelLeeren();
}
}
diff --git a/server/src/Flaro.Api/Kratzplatzeinstellungen.cs b/server/src/Flaro.Api/Transcodeeinstellungen.cs
similarity index 80%
rename from server/src/Flaro.Api/Kratzplatzeinstellungen.cs
rename to server/src/Flaro.Api/Transcodeeinstellungen.cs
index b318cb9..9d1c902 100644
--- a/server/src/Flaro.Api/Kratzplatzeinstellungen.cs
+++ b/server/src/Flaro.Api/Transcodeeinstellungen.cs
@@ -4,22 +4,22 @@ using Microsoft.EntityFrameworkCore;
namespace Flaro.Api;
-/// Die Regeln fürs Aufräumen des Kratzplatzes, aus den Einstellungen.
+/// Die Regeln fürs Aufräumen des Transcodes, aus den Einstellungen.
///
///
/// Nach dem Vorbild von : einmal je
/// Anfrage gelesen und gemerkt. Die Tabelle ist generisch, es braucht keine
/// Migration.
///
-public sealed class Kratzplatzeinstellungen(FlaroContext db)
+public sealed class Transcodeeinstellungen(FlaroContext db)
{
public const string ObergrenzeSchluessel = "wartung.kratzplatz.paket.obergrenzeGb";
public const string UnbenutztSchluessel = "wartung.kratzplatz.paket.unbenutztTage";
public const string SchonfristSchluessel = "wartung.kratzplatz.paket.schonfristMinuten";
- private Kratzplatzregeln? _gemerkt;
+ private Transcoderegeln? _gemerkt;
- public async Task RegelnAsync(CancellationToken ct = default)
+ public async Task RegelnAsync(CancellationToken ct = default)
{
if (_gemerkt is { } fertig) return fertig;
@@ -29,9 +29,9 @@ public sealed class Kratzplatzeinstellungen(FlaroContext db)
|| e.Key == SchonfristSchluessel)
.ToDictionaryAsync(e => e.Key, e => e.Value, ct);
- var vorgabe = Kratzplatzregeln.Vorgabe;
+ var vorgabe = Transcoderegeln.Vorgabe;
- _gemerkt = Kratzplatzregeln.Geprueft(
+ _gemerkt = Transcoderegeln.Geprueft(
Zahl(werte, ObergrenzeSchluessel, vorgabe.ObergrenzeBytes / 1024d / 1024 / 1024),
(int)Zahl(werte, UnbenutztSchluessel, vorgabe.Unbenutzt.TotalDays),
(int)Zahl(werte, SchonfristSchluessel, vorgabe.Schonfrist.TotalMinutes));
diff --git a/server/src/Flaro.Api/Kratzplatzpflege.cs b/server/src/Flaro.Api/Transcodepflege.cs
similarity index 96%
rename from server/src/Flaro.Api/Kratzplatzpflege.cs
rename to server/src/Flaro.Api/Transcodepflege.cs
index 0ff931b..cb2c6be 100644
--- a/server/src/Flaro.Api/Kratzplatzpflege.cs
+++ b/server/src/Flaro.Api/Transcodepflege.cs
@@ -11,7 +11,7 @@ public sealed record Aufraeumbericht(
/// Der Unterordner — stroeme, packages, untertitel.
public sealed record Teilbericht(string Name, long Frei, int Dateien);
-/// Räumt den Kratzplatz auf.
+/// Räumt den Transcode-Cache auf.
///
///
///
@@ -31,13 +31,13 @@ public sealed record Teilbericht(string Name, long Frei, int Dateien);
/// nebeneinander löschten einander die Dateien unter den Händen weg.
///
///
-public sealed class Kratzplatzpflege(
+public sealed class Transcodepflege(
IServiceScopeFactory scopes,
FlaroPaths pfade,
PackageCoordinator pakete,
Streamkoordinator stroeme,
TimeProvider uhr,
- ILogger log)
+ ILogger log)
{
private readonly SemaphoreSlim _einerZurZeit = new(1, 1);
@@ -78,7 +78,7 @@ public sealed class Kratzplatzpflege(
if (bericht.Frei > 0)
{
log.LogInformation(
- "Kratzplatz aufgeräumt ({Anlass}): {Mb} MB frei, {Dateien} Datei(en).",
+ "Transcode-Cache aufgeräumt ({Anlass}): {Mb} MB frei, {Dateien} Datei(en).",
anlass, bericht.Frei / (1024 * 1024), bericht.Dateien);
}
@@ -100,7 +100,7 @@ public sealed class Kratzplatzpflege(
using var raum = scopes.CreateScope();
var db = raum.ServiceProvider.GetRequiredService();
var regeln = await raum.ServiceProvider
- .GetRequiredService().RegelnAsync(ct);
+ .GetRequiredService().RegelnAsync(ct);
// **Teilstücke gehen bedingungslos.** Sie entstehen nur beim Bauen,
// und wer gerade baut, hat die Datei noch offen — eine liegengebliebene
@@ -137,7 +137,7 @@ public sealed class Kratzplatzpflege(
.Select(f => new { f.Id, f.ModifiedAt })
.ToDictionaryAsync(f => f.Id, f => f.ModifiedAt.ToUnixTimeMilliseconds(), ct);
- foreach (var a in Kratzplatzauswahl.Wegzuwerfen(
+ foreach (var a in Transcodeauswahl.Wegzuwerfen(
akten, bekannt, PackageCoordinator.Bauart, regeln, jetzt, pakete.IstInBenutzung))
{
frei += Loeschen(a.Pfad);
diff --git a/server/src/Flaro.Core/Auth/AuthCode.cs b/server/src/Flaro.Core/Auth/AuthCode.cs
index d2744dd..88e5353 100644
--- a/server/src/Flaro.Core/Auth/AuthCode.cs
+++ b/server/src/Flaro.Core/Auth/AuthCode.cs
@@ -12,7 +12,7 @@ namespace Flaro.Core.Auth;
///
///
/// Der Code allein holt nie ein Token ab. Dafür gibt es den Abholschein,
-/// den nur das anfragende Gerät kennt und der hier nur als Fingerabdruck
+/// den nur das anfragende Gerät kennt und der hier nur als Fingerfingerprint
/// liegt. Ohne ihn könnte jemand sechsstellige Zahlen durchprobieren — eine
/// Million Versuche sind nichts, und am Ende stünde ein fremdes Gerät in der
/// Mediathek.
diff --git a/server/src/Flaro.Core/Auth/Einladung.cs b/server/src/Flaro.Core/Auth/Einladung.cs
index f75939a..3e37b60 100644
--- a/server/src/Flaro.Core/Auth/Einladung.cs
+++ b/server/src/Flaro.Core/Auth/Einladung.cs
@@ -14,7 +14,7 @@ namespace Flaro.Core.Auth;
///
///
/// Übernommen ist die Geheimnisdisziplin. Wie bei
-/// liegt hier nur der Fingerabdruck; den Link
+/// liegt hier nur der Fingerfingerprint; den Link
/// selbst bekommt man genau einmal zu sehen, beim Erstellen. Wer die
/// Datenbank liest, kann daraus keine Einladung bauen.
///
diff --git a/server/src/Flaro.Core/Auth/Secrets.cs b/server/src/Flaro.Core/Auth/Secrets.cs
index ddecaf3..a4209c2 100644
--- a/server/src/Flaro.Core/Auth/Secrets.cs
+++ b/server/src/Flaro.Core/Auth/Secrets.cs
@@ -6,7 +6,7 @@ namespace Flaro.Core.Auth;
/// Erzeugt und verfingert Geheimnisse, die Clients vorzeigen.
///
///
-/// Abgelegt wird nur der Fingerabdruck, nie das Geheimnis. Wer die
+/// Abgelegt wird nur der Fingerfingerprint, nie das Geheimnis. Wer die
/// Datenbank erbeutet, hat damit Hashes und keine Zugänge. Dass das überhaupt
/// erwähnt werden muss: bei Medienservern liegen Token regelmässig im
/// Klartext, und ein einziges Datenbankleck gibt dann jedem alles.
@@ -23,7 +23,7 @@ public static class Secrets
public static string Create() =>
Base64UrlEncode(RandomNumberGenerator.GetBytes(Bytes));
- /// Der Fingerabdruck, wie er in der Datenbank steht.
+ /// Der Fingerfingerprint, wie er in der Datenbank steht.
public static string Fingerprint(string secret) =>
Convert.ToHexStringLower(SHA256.HashData(Encoding.UTF8.GetBytes(secret)));
diff --git a/server/src/Flaro.Core/Delivery/Paketname.cs b/server/src/Flaro.Core/Delivery/Paketname.cs
index a744d5f..75d6ded 100644
--- a/server/src/Flaro.Core/Delivery/Paketname.cs
+++ b/server/src/Flaro.Core/Delivery/Paketname.cs
@@ -6,7 +6,7 @@ namespace Flaro.Core.Delivery;
///
///
///
-/// Warum der Name alles trägt. Im Kratzplatz liegen Dateien, zu denen
+/// Warum der Name alles trägt. Im Transcode-Cache liegen Dateien, zu denen
/// es keine Tabelle gibt. Ein Aufräumer muss allein am Namen erkennen können,
/// wozu eine Datei gehört und ob sie noch gilt — sonst bliebe ihm nur,
/// alles zu behalten oder alles wegzuwerfen.
diff --git a/server/src/Flaro.Core/Delivery/Kratzplatzregeln.cs b/server/src/Flaro.Core/Delivery/Transcoderegeln.cs
similarity index 93%
rename from server/src/Flaro.Core/Delivery/Kratzplatzregeln.cs
rename to server/src/Flaro.Core/Delivery/Transcoderegeln.cs
index ddb97fb..4e73355 100644
--- a/server/src/Flaro.Core/Delivery/Kratzplatzregeln.cs
+++ b/server/src/Flaro.Core/Delivery/Transcoderegeln.cs
@@ -21,7 +21,7 @@ namespace Flaro.Core.Delivery;
/// Wie viel insgesamt liegen bleiben darf; 0 heisst unbegrenzt.
/// Ab wann eine unbenutzte Datei auch unterhalb der Grenze geht; Zero heisst nie.
/// Wie lange eine frisch gebrauchte Datei in jedem Fall bleibt.
-public sealed record Kratzplatzregeln(
+public sealed record Transcoderegeln(
long ObergrenzeBytes,
TimeSpan Unbenutzt,
TimeSpan Schonfrist)
@@ -34,7 +34,7 @@ public sealed record Kratzplatzregeln(
/// Schonfrist decken die Lücke zwischen zwei Teilanfragen und einen
/// Neustart, der den Benutzungszähler verliert.
///
- public static Kratzplatzregeln Vorgabe { get; } =
+ public static Transcoderegeln Vorgabe { get; } =
new(20L * 1024 * 1024 * 1024, TimeSpan.FromDays(7), TimeSpan.FromMinutes(30));
/// Nimmt entgegen, was eingestellt wurde, und biegt Unsinn zurecht.
@@ -45,13 +45,13 @@ public sealed record Kratzplatzregeln(
/// Datei weg — deshalb eine Untergrenze von fünf Minuten. Negative Werte
/// gelten als „aus".
///
- public static Kratzplatzregeln Geprueft(double obergrenzeGb, int unbenutztTage, int schonfristMinuten) =>
+ public static Transcoderegeln Geprueft(double obergrenzeGb, int unbenutztTage, int schonfristMinuten) =>
new(obergrenzeGb <= 0 ? 0 : (long)(obergrenzeGb * 1024 * 1024 * 1024),
unbenutztTage <= 0 ? TimeSpan.Zero : TimeSpan.FromDays(Math.Min(unbenutztTage, 3650)),
TimeSpan.FromMinutes(Math.Clamp(schonfristMinuten, 5, 1440)));
}
-/// Eine Datei im Kratzplatz, so wie sie dort liegt.
+/// Eine Datei im Transcode-Cache, so wie sie dort liegt.
///
///
/// Wann sie zuletzt gebraucht wurde — die Änderungszeit der Datei selbst.
@@ -66,7 +66,7 @@ public readonly record struct Paketakte(
Paketname.Teile? Name);
/// Welche Dateien weggeräumt werden — reine Rechnung, ohne Platte.
-public static class Kratzplatzauswahl
+public static class Transcodeauswahl
{
/// Kennung → Änderungszeitpunkt der Quelle, aus der Mediathek.
/// Wie der Server heute baut.
@@ -75,7 +75,7 @@ public static class Kratzplatzauswahl
IReadOnlyList akten,
IReadOnlyDictionary bekannt,
int bauart,
- Kratzplatzregeln regeln,
+ Transcoderegeln regeln,
DateTimeOffset jetzt,
Func inBenutzung)
{
diff --git a/server/src/Flaro.Core/Library/CatalogEntities.cs b/server/src/Flaro.Core/Library/CatalogEntities.cs
index 77c07f0..7119acf 100644
--- a/server/src/Flaro.Core/Library/CatalogEntities.cs
+++ b/server/src/Flaro.Core/Library/CatalogEntities.cs
@@ -124,8 +124,8 @@ public sealed class Stichwortlink
public Guid? SeriesId { get; init; }
}
-/// Was für ein Abschnitt eine Marke bezeichnet.
-public enum Markenart
+/// Was für ein Abschnitt ein Segment bezeichnet.
+public enum Segmentart
{
/// Der Vorspann.
Intro,
@@ -148,7 +148,7 @@ public enum Markenart
/// im Film ist dieselbe.
///
///
-public sealed class Marke
+public sealed class Segment
{
public Guid Id { get; init; } = Guid.CreateVersion7();
/// Zu welcher Folge sie gehört — oder zu welchem Film.
@@ -162,7 +162,7 @@ public sealed class Marke
///
public Guid? EpisodeId { get; init; }
public Guid? MovieId { get; init; }
- public required Markenart Art { get; init; }
+ public required Segmentart Art { get; init; }
public required double VonSekunden { get; set; }
public required double BisSekunden { get; set; }
@@ -195,21 +195,21 @@ public sealed class Marke
/// Lauf.
///
///
-public sealed class Markensuche
+public sealed class Segmentsuche
{
public Guid Id { get; init; } = Guid.CreateVersion7();
/// Eine Staffel einer Serie — oder ein Film.
///
///
- /// Genau eines von beiden, wie bei . Bei einer
+ /// Genau eines von beiden, wie bei . Bei einer
/// Staffel steht die Nummer daneben; ein Film hat keine.
///
public Guid? SeriesId { get; init; }
public int? Staffel { get; init; }
public Guid? MovieId { get; init; }
- public required Markenart Art { get; init; }
+ public required Segmentart Art { get; init; }
/// Wie viele Folgen die Staffel hatte, als gesucht wurde.
///
diff --git a/server/src/Flaro.Core/Library/Scanschritt.cs b/server/src/Flaro.Core/Library/Scanschritt.cs
index 6a9b481..a09e710 100644
--- a/server/src/Flaro.Core/Library/Scanschritt.cs
+++ b/server/src/Flaro.Core/Library/Scanschritt.cs
@@ -28,7 +28,7 @@ public enum Scanphase
/// über Stunden, und ohne eine Zeile, die mitzählt, sieht die
/// Verwaltung minutenlang so aus, als täte sich nichts.
///
- Marken,
+ Segmente,
/// Eine Notiz fürs Protokoll — nicht für die Fortschrittszeile.
///
@@ -79,7 +79,7 @@ public readonly record struct Scanschritt(Scanphase Phase, string Was, int Erled
Scanphase.Holen => $"Hole Metadaten zu {Was}{zaehler}",
Scanphase.Bilder => $"Lade Bilder zu {Was}{zaehler}",
Scanphase.Aufraeumen => "Räume auf und lege Doppeltes zusammen",
- Scanphase.Marken => $"Suche Vorspann und Abspann: {Was}{zaehler}",
+ Scanphase.Segmente => $"Suche Vorspann und Abspann: {Was}{zaehler}",
Scanphase.Vermerk => Was,
_ => Was,
};
diff --git a/server/src/Flaro.Core/Library/SidecarNames.cs b/server/src/Flaro.Core/Library/SidecarNames.cs
index 54fdf35..c512fde 100644
--- a/server/src/Flaro.Core/Library/SidecarNames.cs
+++ b/server/src/Flaro.Core/Library/SidecarNames.cs
@@ -18,7 +18,7 @@ namespace Flaro.Core.Library;
///
/// Transcode-Zwischenstände gehören ausdrücklich nicht hierher. Die
/// liegen unter FLARO_TRANSCODE, auf einem eigenen Volume. Ein
-/// Medienordner ist kein Kratzplatz.
+/// Medienordner ist kein Transcode-Cache.
///
///
public static class SidecarNames
diff --git a/server/src/Flaro.Core/Media/Abspannkante.cs b/server/src/Flaro.Core/Media/Abspannkante.cs
index d6d0de3..4d6f16b 100644
--- a/server/src/Flaro.Core/Media/Abspannkante.cs
+++ b/server/src/Flaro.Core/Media/Abspannkante.cs
@@ -20,7 +20,7 @@ namespace Flaro.Core.Media;
///
/// Die Stelle im Film.
/// Wie viel Prozent des Bildes dunkel sind, 0 bis 100.
-public readonly record struct Bildprobe(double Sekunde, double Dunkelanteil);
+public readonly record struct Frameprobe(double Sekunde, double Dunkelanteil);
/// Ein Stück Abspann: von wann bis wann.
///
@@ -80,10 +80,10 @@ public readonly record struct Abspannregeln
/// In Sekunden, nicht in Proben — dieselbe Lehre wie bei
/// , hier eine Zeile höher übersehen. Als
/// Probenzahl hing die Nachsicht daran, wie dicht die Datei ihre
- /// Schlüsselbilder setzt: gemessen an „(T)Raumschiff Surprise" kommen
+ /// Keyframes setzt: gemessen an „(T)Raumschiff Surprise" kommen
/// 1318 Proben auf zwanzig Minuten, also eine alle 0,9 Sekunden —
/// zwei Proben waren dort 1,8 Sekunden Nachsicht. Bei einer Datei mit
- /// einem Schlüsselbild alle zehn Sekunden waren es zwanzig. Dasselbe
+ /// einem Keyframe alle zehn Sekunden waren es zwanzig. Dasselbe
/// Zugeständnis, zehnfach verschieden.
///
///
@@ -94,7 +94,7 @@ public readonly record struct Abspannregeln
///
/// In Sekunden, nicht in Proben. Eine Logotafel dauert, was sie
/// dauert; wie viele Messpunkte darauf fallen, hängt allein daran, wie
- /// dicht die Datei ihre Schlüsselbilder setzt. In Proben gezählt wäre
+ /// dicht die Datei ihre Keyframes setzt. In Proben gezählt wäre
/// dieselbe Tafel bei der einen Datei verziehen und bei der nächsten
/// ein Abbruch.
///
@@ -156,7 +156,7 @@ public readonly record struct Abspannregeln
/// Reine Rechnung, ohne Datei. Das Abtasten braucht ffmpeg; das
/// Beurteilen braucht nichts. Getrennt lässt sich das Schwierige prüfen,
/// ohne einen Film vorzuhalten — dieselbe Naht wie bei
-/// .
+/// .
///
///
public static class Abspannkante
@@ -165,10 +165,10 @@ public static class Abspannkante
///
///
/// Messpunkte über das Ende des Films, aufsteigend nach Zeit. Sie dürfen
- /// ungleichmässig liegen: Schlüsselbilder tun das.
+ /// ungleichmässig liegen: Keyframes tun das.
///
public static IReadOnlyList Suchen(
- IReadOnlyList proben, Abspannregeln regeln) =>
+ IReadOnlyList proben, Abspannregeln regeln) =>
Suchen(proben, regeln, out _);
///
@@ -178,7 +178,7 @@ public static class Abspannkante
/// und ohne diese Zeile beginnt jede Nachfrage wieder beim Raten.
///
public static IReadOnlyList Suchen(
- IReadOnlyList proben, Abspannregeln regeln, out string grund)
+ IReadOnlyList proben, Abspannregeln regeln, out string grund)
{
grund = "gefunden";
if (proben.Count < 2)
@@ -244,11 +244,11 @@ public static class Abspannkante
///
/// Die Nachsicht zählt Sekunden, nicht Proben. Eine Schrifttafel
/// blitzt auf, ein Logo steht zwei Sekunden im Bild; wie viele
- /// Messpunkte darauf fallen, hängt allein an der Schlüsselbilddichte
+ /// Messpunkte darauf fallen, hängt allein an der Keyframedichte
/// der Datei und schwankt um eine Zehnerpotenz.
///
private static List Läufe(
- IReadOnlyList proben, Abspannregeln regeln)
+ IReadOnlyList proben, Abspannregeln regeln)
{
var läufe = new List();
double? von = null;
@@ -281,7 +281,7 @@ public static class Abspannkante
/// Die letzte helle Probe vor dem Fund — der Suchbereich fürs Nachmessen.
public static double? HellDavor(
- IReadOnlyList proben, double dunkelAb, Abspannregeln regeln)
+ IReadOnlyList proben, double dunkelAb, Abspannregeln regeln)
{
double? letzteHelle = null;
foreach (var p in proben)
@@ -297,7 +297,7 @@ public static class Abspannkante
///
///
/// Binäre Suche mit kurzen Sonden, nicht noch ein Durchlauf. Die
- /// grobe Reihe stammt aus Schlüsselbildern, und die liegen je nach
+ /// grobe Reihe stammt aus Keyframesn, und die liegen je nach
/// Aufnahme fünf bis zehn Sekunden auseinander. Den ganzen Bereich fein
/// abzutasten hiesse, ihn zu dekodieren; zehn Sonden von je einer
/// Sekunde kosten einen Bruchteil davon. Denselben Weg geht Jellyfins
diff --git a/server/src/Flaro.Core/Media/Tonabgleich.cs b/server/src/Flaro.Core/Media/Fingerprintabgleich.cs
similarity index 88%
rename from server/src/Flaro.Core/Media/Tonabgleich.cs
rename to server/src/Flaro.Core/Media/Fingerprintabgleich.cs
index f879c68..f56f121 100644
--- a/server/src/Flaro.Core/Media/Tonabgleich.cs
+++ b/server/src/Flaro.Core/Media/Fingerprintabgleich.cs
@@ -24,7 +24,7 @@ public sealed record Gemeinsamkeit(double AVon, double ABis, double BVon, double
///
///
/// Verglichen werden Tonabdrücke, keine Töne. Was hereinkommt, ist je
-/// Folge eine Reihe von 32-Bit-Zahlen, eine je Zeitscheibe — der Abdruck,
+/// Folge eine Reihe von 32-Bit-Zahlen, eine je Zeitscheibe — der Fingerprint,
/// den Chromaprint aus dem Ton rechnet. Zwei Aufnahmen desselben Stücks
/// ergeben darin fast dieselben Zahlen, auch bei anderer Lautstärke, anderem
/// Codec und anderer Bitrate. Deshalb wird nicht auf Gleichheit geprüft,
@@ -36,7 +36,7 @@ public sealed record Gemeinsamkeit(double AVon, double ABis, double BVon, double
/// sich das Schwierige prüfen, ohne eine Mediendatei vorzuhalten.
///
///
-public static class Tonabgleich
+public static class Fingerprintabgleich
{
/// Wie viele Bits zweier Abdrücke sich unterscheiden dürfen.
///
@@ -67,7 +67,7 @@ public static class Tonabgleich
/// Sucht das längste Stück, das und teilen.
///
- ///
+ ///
/// Wie viel Zeit eine Zeitscheibe abdeckt. Chromaprint rechnet mit
/// 11025 Hz und 4096 Abtastwerten bei zwei Dritteln Überlappung — das
/// sind rund 0,1238 Sekunden.
@@ -75,14 +75,14 @@ public static class Tonabgleich
public static Gemeinsamkeit? Finden(
IReadOnlyList a,
IReadOnlyList b,
- double sekundenJeAbdruck,
+ double sekundenJeFingerprint,
double mindestlänge = StandardMindestlänge,
int bitabstand = StandardBitabstand,
int lücke = StandardLücke)
{
- if (a.Count == 0 || b.Count == 0 || sekundenJeAbdruck <= 0) return null;
+ if (a.Count == 0 || b.Count == 0 || sekundenJeFingerprint <= 0) return null;
- var mindestens = (int)Math.Ceiling(mindestlänge / sekundenJeAbdruck);
+ var mindestens = (int)Math.Ceiling(mindestlänge / sekundenJeFingerprint);
if (mindestens <= 0) mindestens = 1;
Gemeinsamkeit? beste = null;
@@ -90,7 +90,7 @@ public static class Tonabgleich
// **Jede Verschiebung durchprobieren.** Das Intro steht in beiden
// Folgen an einer anderen Stelle; gesucht ist die Verschiebung, bei
// der die meisten Zeitscheiben aufeinanderpassen. Bei zwei Minuten
- // Abdruck sind das ein paar Millionen Vergleiche — ein Wimpernschlag
+ // Fingerprint sind das ein paar Millionen Vergleiche — ein Wimpernschlag
// gegen das Lesen der Datei, aus der sie stammen.
for (var versatz = -(b.Count - 1); versatz < a.Count; versatz++)
{
@@ -102,13 +102,13 @@ public static class Tonabgleich
var lauf = LängsterLauf(a, b, vonA, vonB, länge, bitabstand, lücke);
if (lauf is not var (start, dauer) || dauer < mindestens) continue;
- if (beste is not null && dauer * sekundenJeAbdruck <= beste.Laenge) continue;
+ if (beste is not null && dauer * sekundenJeFingerprint <= beste.Laenge) continue;
beste = new Gemeinsamkeit(
- (vonA + start) * sekundenJeAbdruck,
- (vonA + start + dauer) * sekundenJeAbdruck,
- (vonB + start) * sekundenJeAbdruck,
- (vonB + start + dauer) * sekundenJeAbdruck);
+ (vonA + start) * sekundenJeFingerprint,
+ (vonA + start + dauer) * sekundenJeFingerprint,
+ (vonB + start) * sekundenJeFingerprint,
+ (vonB + start + dauer) * sekundenJeFingerprint);
}
return beste is not null && beste.Laenge >= mindestlänge ? beste : null;
@@ -176,8 +176,8 @@ public static class Tonabgleich
///
///
public static IReadOnlyDictionary ReiheAbgleichen(
- IReadOnlyList<(TSchlüssel Schlüssel, IReadOnlyList Abdruck)> folgen,
- double sekundenJeAbdruck,
+ IReadOnlyList<(TSchlüssel Schlüssel, IReadOnlyList Fingerprint)> folgen,
+ double sekundenJeFingerprint,
double mindestlänge = StandardMindestlänge)
where TSchlüssel : notnull
{
@@ -186,10 +186,10 @@ public static class Tonabgleich
for (var i = 0; i + 1 < folgen.Count; i++)
{
- var (schlüsselA, abdruckA) = folgen[i];
- var (schlüsselB, abdruckB) = folgen[i + 1];
+ var (schlüsselA, fingerprintA) = folgen[i];
+ var (schlüsselB, fingerprintB) = folgen[i + 1];
- var treffer = Finden(abdruckA, abdruckB, sekundenJeAbdruck, mindestlänge);
+ var treffer = Finden(fingerprintA, fingerprintB, sekundenJeFingerprint, mindestlänge);
if (treffer is null) continue;
// Der erste Fund je Folge gewinnt: er stammt vom Vergleich mit
diff --git a/server/src/Flaro.Core/Playback/ProgressRule.cs b/server/src/Flaro.Core/Playback/ProgressRule.cs
index 97270fc..372e89a 100644
--- a/server/src/Flaro.Core/Playback/ProgressRule.cs
+++ b/server/src/Flaro.Core/Playback/ProgressRule.cs
@@ -79,8 +79,8 @@ public static class ProgressRule
var schwelle = laenge * regeln.GesehenAnteil;
// Der Abspann zieht die Schwelle nach vorn, nie nach hinten: eine
- // falsch erkannte Marke bei Minute vierzig darf nicht dazu führen,
- // dass eine Folge erst nach der Marke als gesehen gilt.
+ // falsch erkannte Segment bei Minute vierzig darf nicht dazu führen,
+ // dass eine Folge erst nach dem Segment als gesehen gilt.
if (abspannAb is { } ab && ab > TimeSpan.Zero && ab < schwelle) schwelle = ab;
return new Outcome(stelle, Watched: stelle >= schwelle);
diff --git a/server/src/Flaro.Data/FlaroContext.cs b/server/src/Flaro.Data/FlaroContext.cs
index bc11140..06ca0ef 100644
--- a/server/src/Flaro.Data/FlaroContext.cs
+++ b/server/src/Flaro.Data/FlaroContext.cs
@@ -76,8 +76,8 @@ public sealed class FlaroContext(DbContextOptions options) : DbCon
public DbSet Genres => Set();
public DbSet GenreLinks => Set();
public DbSet Fremdbewertungen => Set();
- public DbSet Marken => Set();
- public DbSet Markensuchen => Set();
+ public DbSet Segmente => Set();
+ public DbSet Segmentsuchen => Set();
public DbSet Stichworte => Set();
public DbSet Stichwortlinks => Set();
public DbSet People => Set();
@@ -141,7 +141,7 @@ public sealed class FlaroContext(DbContextOptions options) : DbCon
{
e.HasKey(x => x.Id);
- // Gesucht wird nur über den Fingerabdruck — und eindeutig, damit
+ // Gesucht wird nur über den Fingerfingerprint — und eindeutig, damit
// zwei Links niemals dasselbe Konto füttern können.
e.HasIndex(x => x.TokenFingerprint).IsUnique();
e.Property(x => x.TokenFingerprint).HasMaxLength(64);
@@ -308,7 +308,7 @@ public sealed class FlaroContext(DbContextOptions options) : DbCon
e.Property(x => x.Anzeige).HasMaxLength(32);
});
- b.Entity(e =>
+ b.Entity(e =>
{
e.HasKey(x => x.Id);
// **Mehrere Stücke je Art, und das ist der ganze Zweck.** Hier
@@ -326,7 +326,7 @@ public sealed class FlaroContext(DbContextOptions options) : DbCon
e.Property(x => x.Art).HasConversion();
});
- b.Entity(e =>
+ b.Entity(e =>
{
e.HasKey(x => x.Id);
e.HasIndex(x => new { x.SeriesId, x.Staffel, x.Art }).IsUnique()
diff --git a/server/src/Flaro.Data/Migrations/20261016141454_Segmente.Designer.cs b/server/src/Flaro.Data/Migrations/20261016141454_Segmente.Designer.cs
new file mode 100644
index 0000000..143507e
--- /dev/null
+++ b/server/src/Flaro.Data/Migrations/20261016141454_Segmente.Designer.cs
@@ -0,0 +1,1357 @@
+//
+using System;
+using Flaro.Data;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace Flaro.Data.Migrations
+{
+ [DbContext(typeof(FlaroContext))]
+ [Migration("20261016141454_Segmente")]
+ partial class Segmente
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "10.0.12");
+
+ modelBuilder.Entity("Flaro.Core.Auth.AuthCode", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("ApprovedByUserId")
+ .HasColumnType("TEXT");
+
+ b.Property("Code")
+ .IsRequired()
+ .HasMaxLength(16)
+ .HasColumnType("TEXT");
+
+ b.Property("ConsumedAt")
+ .HasColumnType("INTEGER");
+
+ b.Property("CreatedAt")
+ .HasColumnType("INTEGER");
+
+ b.Property("DeviceId")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("TEXT");
+
+ b.Property("DeviceName")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("TEXT");
+
+ b.Property("ExpiresAt")
+ .HasColumnType("INTEGER");
+
+ b.Property("HandleFingerprint")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Code");
+
+ b.HasIndex("HandleFingerprint")
+ .IsUnique();
+
+ b.ToTable("AuthCodes");
+ });
+
+ modelBuilder.Entity("Flaro.Core.Auth.DeviceToken", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("CreatedAt")
+ .HasColumnType("INTEGER");
+
+ b.Property("DeviceId")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("TEXT");
+
+ b.Property("DeviceName")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("TEXT");
+
+ b.Property("LastSeenAt")
+ .HasColumnType("INTEGER");
+
+ b.Property("RevokedAt")
+ .HasColumnType("INTEGER");
+
+ b.Property("TokenFingerprint")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("TEXT");
+
+ b.Property("UserId")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("TokenFingerprint")
+ .IsUnique();
+
+ b.HasIndex("UserId");
+
+ b.ToTable("DeviceTokens");
+ });
+
+ modelBuilder.Entity("Flaro.Core.Auth.Einladung", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("CreatedAt")
+ .HasColumnType("INTEGER");
+
+ b.Property("CreatedByUserId")
+ .HasColumnType("TEXT");
+
+ b.Property("ExpiresAt")
+ .HasColumnType("INTEGER");
+
+ b.Property("MaxUses")
+ .HasColumnType("INTEGER");
+
+ b.Property("RevokedAt")
+ .HasColumnType("INTEGER");
+
+ b.Property("TokenFingerprint")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("TEXT");
+
+ b.Property("UsedCount")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CreatedAt");
+
+ b.HasIndex("CreatedByUserId");
+
+ b.HasIndex("TokenFingerprint")
+ .IsUnique();
+
+ b.ToTable("Einladungen");
+ });
+
+ modelBuilder.Entity("Flaro.Core.Libraries.Library", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("CreatedAt")
+ .HasColumnType("INTEGER");
+
+ b.Property("Kind")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("TEXT");
+
+ b.Property("Path")
+ .IsRequired()
+ .HasMaxLength(1024)
+ .HasColumnType("TEXT");
+
+ b.Property("ReviewNewItems")
+ .HasColumnType("INTEGER");
+
+ b.Property("SortOrder")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.ToTable("Libraries");
+ });
+
+ modelBuilder.Entity("Flaro.Core.Library.Credit", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("Character")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("MovieId")
+ .HasColumnType("TEXT");
+
+ b.Property("Order")
+ .HasColumnType("INTEGER");
+
+ b.Property("PersonId")
+ .HasColumnType("TEXT");
+
+ b.Property("Role")
+ .IsRequired()
+ .HasMaxLength(16)
+ .HasColumnType("TEXT");
+
+ b.Property("SeriesId")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("MovieId");
+
+ b.HasIndex("PersonId");
+
+ b.HasIndex("SeriesId");
+
+ b.ToTable("Credits");
+ });
+
+ modelBuilder.Entity("Flaro.Core.Library.Episode", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("AddedAt")
+ .HasColumnType("INTEGER");
+
+ b.Property("EpisodeNumber")
+ .HasColumnType("INTEGER");
+
+ b.Property("MetadataLanguage")
+ .HasMaxLength(16)
+ .HasColumnType("TEXT");
+
+ b.Property("Overview")
+ .HasColumnType("TEXT");
+
+ b.Property("PublishAt")
+ .HasColumnType("INTEGER");
+
+ b.Property("SeasonNumber")
+ .HasColumnType("INTEGER");
+
+ b.Property("SeriesId")
+ .HasColumnType("TEXT");
+
+ b.Property("StillImage")
+ .HasColumnType("TEXT");
+
+ b.Property("Title")
+ .HasMaxLength(512)
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("SeriesId", "SeasonNumber", "EpisodeNumber")
+ .IsUnique();
+
+ b.ToTable("Episodes");
+ });
+
+ modelBuilder.Entity("Flaro.Core.Library.Fremdbewertung", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("Anzeige")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("TEXT");
+
+ b.Property("MovieId")
+ .HasColumnType("TEXT");
+
+ b.Property("Quelle")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("TEXT");
+
+ b.Property("SeriesId")
+ .HasColumnType("TEXT");
+
+ b.Property("Wert")
+ .HasColumnType("REAL");
+
+ b.HasKey("Id");
+
+ b.HasIndex("MovieId", "Quelle")
+ .IsUnique()
+ .HasFilter("\"MovieId\" IS NOT NULL");
+
+ b.HasIndex("SeriesId", "Quelle")
+ .IsUnique()
+ .HasFilter("\"SeriesId\" IS NOT NULL");
+
+ b.ToTable("Fremdbewertungen");
+ });
+
+ modelBuilder.Entity("Flaro.Core.Library.Genre", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("TEXT")
+ .UseCollation("NOCASE");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Name")
+ .IsUnique();
+
+ b.ToTable("Genres");
+ });
+
+ modelBuilder.Entity("Flaro.Core.Library.GenreLink", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("GenreId")
+ .HasColumnType("TEXT");
+
+ b.Property("MovieId")
+ .HasColumnType("TEXT");
+
+ b.Property("SeriesId")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("MovieId");
+
+ b.HasIndex("SeriesId");
+
+ b.HasIndex("GenreId", "MovieId")
+ .IsUnique()
+ .HasFilter("\"MovieId\" IS NOT NULL");
+
+ b.HasIndex("GenreId", "SeriesId")
+ .IsUnique()
+ .HasFilter("\"SeriesId\" IS NOT NULL");
+
+ b.ToTable("GenreLinks");
+ });
+
+ modelBuilder.Entity("Flaro.Core.Library.ItemTranslation", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("EpisodeId")
+ .HasColumnType("TEXT");
+
+ b.Property("FetchedAt")
+ .HasColumnType("INTEGER");
+
+ b.Property("Language")
+ .IsRequired()
+ .HasMaxLength(16)
+ .HasColumnType("TEXT");
+
+ b.Property("MovieId")
+ .HasColumnType("TEXT");
+
+ b.Property("Overview")
+ .HasColumnType("TEXT");
+
+ b.Property("SeriesId")
+ .HasColumnType("TEXT");
+
+ b.Property("Tagline")
+ .HasMaxLength(512)
+ .HasColumnType("TEXT");
+
+ b.Property("Title")
+ .HasMaxLength(512)
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("EpisodeId", "Language")
+ .IsUnique()
+ .HasFilter("\"EpisodeId\" IS NOT NULL");
+
+ b.HasIndex("MovieId", "Language")
+ .IsUnique()
+ .HasFilter("\"MovieId\" IS NOT NULL");
+
+ b.HasIndex("SeriesId", "Language")
+ .IsUnique()
+ .HasFilter("\"SeriesId\" IS NOT NULL");
+
+ b.ToTable("Translations");
+ });
+
+ modelBuilder.Entity("Flaro.Core.Library.MediaFile", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("BitRate")
+ .HasColumnType("INTEGER");
+
+ b.Property("Container")
+ .HasMaxLength(32)
+ .HasColumnType("TEXT");
+
+ b.Property("Duration")
+ .HasColumnType("INTEGER");
+
+ b.Property("EpisodeId")
+ .HasColumnType("TEXT");
+
+ b.Property("LibraryId")
+ .HasColumnType("TEXT");
+
+ b.Property("MissingSince")
+ .HasColumnType("INTEGER");
+
+ b.Property("ModifiedAt")
+ .HasColumnType("INTEGER");
+
+ b.Property("MovieId")
+ .HasColumnType("TEXT");
+
+ b.Property("Path")
+ .IsRequired()
+ .HasMaxLength(1024)
+ .HasColumnType("TEXT");
+
+ b.Property("ProbedAt")
+ .HasColumnType("INTEGER");
+
+ b.Property("SeenAt")
+ .HasColumnType("INTEGER");
+
+ b.Property("SizeBytes")
+ .HasColumnType("INTEGER");
+
+ b.Property("VersionLabel")
+ .HasMaxLength(64)
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("EpisodeId");
+
+ b.HasIndex("MovieId");
+
+ b.HasIndex("LibraryId", "Path")
+ .IsUnique();
+
+ b.ToTable("MediaFiles");
+ });
+
+ modelBuilder.Entity("Flaro.Core.Library.MediaStream", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("Channels")
+ .HasColumnType("INTEGER");
+
+ b.Property("Codec")
+ .HasMaxLength(64)
+ .HasColumnType("TEXT");
+
+ b.Property("Height")
+ .HasColumnType("INTEGER");
+
+ b.Property("Index")
+ .HasColumnType("INTEGER");
+
+ b.Property("IsDefault")
+ .HasColumnType("INTEGER");
+
+ b.Property("IsForced")
+ .HasColumnType("INTEGER");
+
+ b.Property("Kind")
+ .IsRequired()
+ .HasMaxLength(16)
+ .HasColumnType("TEXT");
+
+ b.Property("Language")
+ .HasMaxLength(16)
+ .HasColumnType("TEXT");
+
+ b.Property("MediaFileId")
+ .HasColumnType("TEXT");
+
+ b.Property("Title")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("Width")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("MediaFileId", "Index")
+ .IsUnique();
+
+ b.ToTable("MediaStreams");
+ });
+
+ modelBuilder.Entity("Flaro.Core.Library.Meldung", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("Art")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("TEXT");
+
+ b.Property("EpisodeId")
+ .HasColumnType("TEXT");
+
+ b.Property("ErledigtAm")
+ .HasColumnType("INTEGER");
+
+ b.Property("ErledigtVon")
+ .HasColumnType("TEXT");
+
+ b.Property("GemeldetAm")
+ .HasColumnType("INTEGER");
+
+ b.Property("Kommentar")
+ .IsRequired()
+ .HasMaxLength(2000)
+ .HasColumnType("TEXT");
+
+ b.Property("MovieId")
+ .HasColumnType("TEXT");
+
+ b.Property("SeasonNumber")
+ .HasColumnType("INTEGER");
+
+ b.Property("SeriesId")
+ .HasColumnType("TEXT");
+
+ b.Property("Stelle")
+ .HasColumnType("INTEGER");
+
+ b.Property("UserId")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("EpisodeId");
+
+ b.HasIndex("MovieId");
+
+ b.HasIndex("SeriesId");
+
+ b.HasIndex("ErledigtAm", "GemeldetAm");
+
+ b.HasIndex("UserId", "GemeldetAm");
+
+ b.ToTable("Meldungen");
+ });
+
+ modelBuilder.Entity("Flaro.Core.Library.Movie", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("AddedAt")
+ .HasColumnType("INTEGER");
+
+ b.Property("Aufmacherbilder")
+ .HasColumnType("TEXT");
+
+ b.Property("BackdropImage")
+ .HasColumnType("TEXT");
+
+ b.Property("BackdropPinned")
+ .HasColumnType("INTEGER");
+
+ b.Property("ChangedSince")
+ .HasColumnType("INTEGER");
+
+ b.Property("ContentRating")
+ .HasMaxLength(16)
+ .HasColumnType("TEXT");
+
+ b.Property("FassungenGeprueftBei")
+ .HasColumnType("INTEGER");
+
+ b.Property("ImdbId")
+ .HasMaxLength(32)
+ .HasColumnType("TEXT");
+
+ b.Property("LibraryId")
+ .HasColumnType("TEXT");
+
+ b.Property("LogoImage")
+ .HasColumnType("TEXT");
+
+ b.Property("LogoPinned")
+ .HasColumnType("INTEGER");
+
+ b.Property("MatchLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("MetadataLanguage")
+ .HasMaxLength(16)
+ .HasColumnType("TEXT");
+
+ b.Property("OriginalTitle")
+ .HasMaxLength(512)
+ .HasColumnType("TEXT");
+
+ b.Property("Overview")
+ .HasColumnType("TEXT");
+
+ b.Property("ParsedTitle")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("ParsedYear")
+ .HasColumnType("INTEGER");
+
+ b.Property("PosterImage")
+ .HasColumnType("TEXT");
+
+ b.Property("PosterPinned")
+ .HasColumnType("INTEGER");
+
+ b.Property("PublishAt")
+ .HasColumnType("INTEGER");
+
+ b.Property("Rating")
+ .HasColumnType("REAL");
+
+ b.Property("RuntimeMinutes")
+ .HasColumnType("INTEGER");
+
+ b.Property("SortTitlePinned")
+ .HasColumnType("INTEGER");
+
+ b.Property("SortTitleValue")
+ .IsRequired()
+ .HasMaxLength(512)
+ .HasColumnType("TEXT");
+
+ b.Property("Tagline")
+ .HasMaxLength(512)
+ .HasColumnType("TEXT");
+
+ b.Property("Title")
+ .IsRequired()
+ .HasMaxLength(512)
+ .HasColumnType("TEXT");
+
+ b.Property("TitlePinned")
+ .HasColumnType("INTEGER");
+
+ b.Property("TmdbId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Year")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("LibraryId", "MetadataLanguage");
+
+ b.HasIndex("LibraryId", "SortTitleValue");
+
+ b.HasIndex("LibraryId", "TmdbId")
+ .IsUnique()
+ .HasFilter("\"TmdbId\" IS NOT NULL");
+
+ b.ToTable("Movies");
+ });
+
+ modelBuilder.Entity("Flaro.Core.Library.Person", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("Image")
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("TmdbId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("TmdbId")
+ .IsUnique();
+
+ b.ToTable("People");
+ });
+
+ modelBuilder.Entity("Flaro.Core.Library.Segment", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("Art")
+ .HasColumnType("INTEGER");
+
+ b.Property("BisSekunden")
+ .HasColumnType("REAL");
+
+ b.Property("EpisodeId")
+ .HasColumnType("TEXT");
+
+ b.Property("ErkanntAm")
+ .HasColumnType("INTEGER");
+
+ b.Property("MovieId")
+ .HasColumnType("TEXT");
+
+ b.Property("VonSekunden")
+ .HasColumnType("REAL");
+
+ b.HasKey("Id");
+
+ b.HasIndex("EpisodeId", "Art");
+
+ b.HasIndex("MovieId", "Art");
+
+ b.ToTable("Segmente");
+ });
+
+ modelBuilder.Entity("Flaro.Core.Library.Segmentsuche", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("Art")
+ .HasColumnType("INTEGER");
+
+ b.Property("Folgen")
+ .HasColumnType("INTEGER");
+
+ b.Property("GelaufenAm")
+ .HasColumnType("INTEGER");
+
+ b.Property("MovieId")
+ .HasColumnType("TEXT");
+
+ b.Property("SeriesId")
+ .HasColumnType("TEXT");
+
+ b.Property("Staffel")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("MovieId", "Art")
+ .IsUnique()
+ .HasFilter("\"MovieId\" IS NOT NULL");
+
+ b.HasIndex("SeriesId", "Staffel", "Art")
+ .IsUnique()
+ .HasFilter("\"SeriesId\" IS NOT NULL");
+
+ b.ToTable("Segmentsuchen");
+ });
+
+ modelBuilder.Entity("Flaro.Core.Library.Series", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("AddedAt")
+ .HasColumnType("INTEGER");
+
+ b.Property("Aufmacherbilder")
+ .HasColumnType("TEXT");
+
+ b.Property("BackdropImage")
+ .HasColumnType("TEXT");
+
+ b.Property("BackdropPinned")
+ .HasColumnType("INTEGER");
+
+ b.Property("ChangedSince")
+ .HasColumnType("INTEGER");
+
+ b.Property("ContentRating")
+ .HasMaxLength(16)
+ .HasColumnType("TEXT");
+
+ b.Property("ImdbId")
+ .HasMaxLength(32)
+ .HasColumnType("TEXT");
+
+ b.Property("LibraryId")
+ .HasColumnType("TEXT");
+
+ b.Property("LogoImage")
+ .HasColumnType("TEXT");
+
+ b.Property("LogoPinned")
+ .HasColumnType("INTEGER");
+
+ b.Property("MatchLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("MetadataLanguage")
+ .HasMaxLength(16)
+ .HasColumnType("TEXT");
+
+ b.Property("OriginalTitle")
+ .HasMaxLength(512)
+ .HasColumnType("TEXT");
+
+ b.Property("Overview")
+ .HasColumnType("TEXT");
+
+ b.Property("ParsedTitle")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("ParsedYear")
+ .HasColumnType("INTEGER");
+
+ b.Property("PosterImage")
+ .HasColumnType("TEXT");
+
+ b.Property("PosterPinned")
+ .HasColumnType("INTEGER");
+
+ b.Property("PublishAt")
+ .HasColumnType("INTEGER");
+
+ b.Property("Rating")
+ .HasColumnType("REAL");
+
+ b.Property("RuntimeMinutes")
+ .HasColumnType("INTEGER");
+
+ b.Property("SortTitlePinned")
+ .HasColumnType("INTEGER");
+
+ b.Property("SortTitleValue")
+ .IsRequired()
+ .HasMaxLength(512)
+ .HasColumnType("TEXT");
+
+ b.Property("Tagline")
+ .HasMaxLength(512)
+ .HasColumnType("TEXT");
+
+ b.Property("Title")
+ .IsRequired()
+ .HasMaxLength(512)
+ .HasColumnType("TEXT");
+
+ b.Property("TitlePinned")
+ .HasColumnType("INTEGER");
+
+ b.Property("TmdbId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Year")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("LibraryId", "MetadataLanguage");
+
+ b.HasIndex("LibraryId", "SortTitleValue");
+
+ b.HasIndex("LibraryId", "TmdbId")
+ .IsUnique()
+ .HasFilter("\"TmdbId\" IS NOT NULL");
+
+ b.ToTable("Series");
+ });
+
+ modelBuilder.Entity("Flaro.Core.Library.Stichwort", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Name")
+ .IsUnique();
+
+ b.ToTable("Stichworte");
+ });
+
+ modelBuilder.Entity("Flaro.Core.Library.Stichwortlink", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("MovieId")
+ .HasColumnType("TEXT");
+
+ b.Property("SeriesId")
+ .HasColumnType("TEXT");
+
+ b.Property("StichwortId")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("MovieId");
+
+ b.HasIndex("SeriesId");
+
+ b.HasIndex("StichwortId", "MovieId")
+ .IsUnique()
+ .HasFilter("\"MovieId\" IS NOT NULL");
+
+ b.HasIndex("StichwortId", "SeriesId")
+ .IsUnique()
+ .HasFilter("\"SeriesId\" IS NOT NULL");
+
+ b.ToTable("Stichwortlinks");
+ });
+
+ modelBuilder.Entity("Flaro.Core.Playback.Sehtag", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("EpisodeId")
+ .HasColumnType("TEXT");
+
+ b.Property("MovieId")
+ .HasColumnType("TEXT");
+
+ b.Property("Sekunden")
+ .HasColumnType("REAL");
+
+ b.Property