Der Upload zu App Store Connect scheiterte am Namen. „Halo" ist eine eingetragene Marke von Microsoft; im App Store heisst zwar keine App exakt so, aber reservierte und markenrechtlich belegte Namen tauchen in keiner Suche auf. Der einzige verlässliche Test war die Eingabe dort, und die ging daneben. **Der Name wurde diesmal vorher geprüft.** Zwanzig Kandidaten gegen den deutschen und amerikanischen App Store, auf exakte Treffer und auf ähnliche Namen. „Kavo" wäre dieselbe Wand gewesen — exakt belegt in beiden Stores, in Deutschland von KaVo Dental GmbH. „Flare" ist mit 42 ähnlichen Apps je Store der am dichtesten besetzte Name der ganzen Runde. **Flaro** ist in beiden frei und liest sich als eigenes Wort, nicht als Flare mit Tippfehler. Das bleibt eine Store-Suche und keine Markenrecherche — reservierte Namen ohne veröffentlichte App sieht sie nicht. Aber es ist mehr Prüfung, als beim letzten Mal stattfand. --- 2160 Textvorkommen in 273 Dateien, 49 Pfade. Betroffen ist diesmal auch der Server: `Flaro.Api`, `Flaro.Core`, `Flaro.Data`, `Flaro.Media`, `Flaro.Metadata`, `FlaroContext`, `FlaroPaths`, `flaro.db`, die Variablen `FLARO_DATA`, `FLARO_CACHE`, `FLARO_TRANSCODE`, `FLARO_WRITE_SIDECARS`, und das Image. **Vor dem Ersetzen geprüft**, ob „halo" irgendwo als Teil eines anderen Wortes steckt — und ob es im TMDB-Token vorkommt. Tat es nicht; ein blindes Ersetzen hätte den sonst zerlegt. `docs/superpowers/specs/` bleibt wie beim letzten Mal unangetastet: datierte Entwurfsdokumente sind ein Protokoll getroffener Entscheidungen. Das Server-Dokument hat stattdessen einen Nachtrag bekommen, damit es niemanden in die Irre führt. **Die Bundle-ID wandert mit**, zum zweiten Mal an einem Tag: `com.scarriffleservices.flaro`. Wer die vorige Fassung installiert hat, hat jetzt zwei Karteileichen auf dem Gerät — beide löschen, dann ist Ruhe. 308 Server-Tests grün, 368 im Swift-Paket, null Warnungen. iOS und tvOS bauen, das Image baut, und ein frischer Container läuft durch Einrichtung und Anmeldung. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
204 lines
7.9 KiB
C#
204 lines
7.9 KiB
C#
namespace Flaro.Core.Library;
|
|
|
|
/// <summary>Eine Datei auf der Platte, so wie der Scan sie vorgefunden hat.</summary>
|
|
///
|
|
/// <remarks>
|
|
/// <para>
|
|
/// <b>Die Datei zeigt auf den Titel, nicht umgekehrt.</b> Das sieht
|
|
/// herumgedreht aus und ist Absicht: ein Film liegt oft zweimal vor, als 4K
|
|
/// und als 1080p. Trüge der Film den Verweis auf seine eine Datei, wäre die
|
|
/// zweite Fassung ein Umbau am Datenmodell — so ist sie eine Zeile mehr.
|
|
/// </para>
|
|
/// <para>
|
|
/// Genau dasselbe gilt für Folgen.
|
|
/// </para>
|
|
/// </remarks>
|
|
public sealed class MediaFile
|
|
{
|
|
public Guid Id { get; init; } = Guid.CreateVersion7();
|
|
public required Guid LibraryId { get; init; }
|
|
|
|
/// <summary>Der Pfad, wie der Server ihn sieht — im Container, nicht auf dem Wirt.</summary>
|
|
public required string Path { get; set; }
|
|
|
|
public long SizeBytes { get; set; }
|
|
public DateTimeOffset ModifiedAt { get; set; }
|
|
|
|
/// <summary>Wann der letzte Scan diese Datei noch vorgefunden hat.</summary>
|
|
///
|
|
/// <remarks>
|
|
/// Daran hängt das Aufräumen: Was ein Lauf nicht gesehen hat, liegt nicht
|
|
/// mehr da. Ohne diese Angabe müsste der Scan sich merken, was er gesehen
|
|
/// hat, und das wächst mit dem Bestand.
|
|
/// </remarks>
|
|
public DateTimeOffset SeenAt { get; set; }
|
|
|
|
public Guid? MovieId { get; set; }
|
|
public Guid? EpisodeId { get; set; }
|
|
|
|
// --- Was ffprobe gelesen hat -----------------------------------------
|
|
|
|
/// <summary>Die Schachtel: mkv, mp4, ts …</summary>
|
|
public string? Container { get; set; }
|
|
|
|
public TimeSpan? Duration { get; set; }
|
|
public long? BitRate { get; set; }
|
|
|
|
/// <summary>Wann zuletzt untersucht — <c>null</c>, solange nie.</summary>
|
|
///
|
|
/// <remarks>
|
|
/// Daran hängt, ob der nächste Scan die Datei erneut untersucht. Ein
|
|
/// Durchgang kostet rund fünfzig Millisekunden; bei vierzigtausend Dateien
|
|
/// sind das über eine halbe Stunde, und die will niemand bei jedem Lauf
|
|
/// erneut aufbringen.
|
|
/// </remarks>
|
|
public DateTimeOffset? ProbedAt { get; set; }
|
|
}
|
|
|
|
public sealed class Movie
|
|
{
|
|
public Guid Id { get; init; } = Guid.CreateVersion7();
|
|
public required Guid LibraryId { get; init; }
|
|
/// <summary>Der Titel, wie er aus dem Dateinamen gelesen wurde.</summary>
|
|
///
|
|
/// <remarks>
|
|
/// <b>Daran ordnet der Scan zu, nicht an <see cref="Title"/>.</b> Der
|
|
/// angezeigte Titel kommt nach dem ersten Lauf von der Metadatenquelle und
|
|
/// ändert sich dabei — aus „The Matrix" wird „Matrix". Würde der nächste
|
|
/// Scan darauf suchen, fände er nichts, legte den Film ein zweites Mal an,
|
|
/// und die Mediathek wüchse bei jedem Lauf. Dieses Feld ändert sich nie.
|
|
/// </remarks>
|
|
public required string ParsedTitle { get; init; }
|
|
|
|
/// <summary>Das Jahr, wie es im Dateinamen stand.</summary>
|
|
///
|
|
/// <remarks>
|
|
/// <b>Aus demselben Grund wie <see cref="ParsedTitle"/>, und er wurde
|
|
/// teuer gelernt.</b> Nach einer Korrektur von Hand stand im Ordner
|
|
/// „Dark (2017)", in der Datenbank aber 2004 — der nächste Scan suchte
|
|
/// nach (Dark, 2017), fand nichts, legte den Film neu an und löschte den
|
|
/// korrigierten als verwaist. Gerettet hat es nur die NFO-Datei. Dieses
|
|
/// Feld ändert sich nie.
|
|
/// </remarks>
|
|
public int? ParsedYear { get; init; }
|
|
|
|
public required string Title { get; set; }
|
|
|
|
/// <summary>Ohne führenden Artikel — siehe <see cref="SortTitle"/>.</summary>
|
|
public required string SortTitleValue { get; set; }
|
|
|
|
public int? Year { get; set; }
|
|
public string? Overview { get; set; }
|
|
|
|
/// <summary>Die Kennung bei TMDB, sobald zugeordnet.</summary>
|
|
public int? TmdbId { get; set; }
|
|
|
|
/// <summary>Die Kennung bei IMDb — für die NFO-Datei.</summary>
|
|
///
|
|
/// <remarks>
|
|
/// Damit ein anderes Werkzeug denselben Titel wiederfindet, auch wenn es
|
|
/// TMDB gar nicht kennt.
|
|
/// </remarks>
|
|
public string? ImdbId { get; set; }
|
|
|
|
/// <summary>Ob jemand diese Zuordnung von Hand festgelegt hat.</summary>
|
|
///
|
|
/// <remarks>
|
|
/// Dann sucht kein Lauf mehr danach. Steht auch als <c>lockdata</c> in der
|
|
/// NFO-Datei — so überlebt die Entscheidung einen Neuaufbau der Datenbank
|
|
/// und einen Umzug auf einen anderen Server.
|
|
/// </remarks>
|
|
public bool MatchLocked { get; set; }
|
|
|
|
/// <summary>Schlüssel des zwischengespeicherten Plakats — siehe Bildablage.</summary>
|
|
///
|
|
/// <remarks>
|
|
/// Der Schlüssel, nicht die Adresse bei TMDB. Würde die App dort unmittelbar
|
|
/// laden, bräuchte sie Internet für ein Plakat, das zehn Zentimeter weiter
|
|
/// auf der Platte liegt — und TMDB erführe, wer wann was ansieht.
|
|
/// </remarks>
|
|
public string? PosterImage { get; set; }
|
|
|
|
public string? BackdropImage { get; set; }
|
|
|
|
public DateTimeOffset AddedAt { get; init; } = DateTimeOffset.UtcNow;
|
|
}
|
|
|
|
public sealed class Series
|
|
{
|
|
public Guid Id { get; init; } = Guid.CreateVersion7();
|
|
public required Guid LibraryId { get; init; }
|
|
/// <summary>Der Titel, wie er aus dem Dateinamen gelesen wurde.</summary>
|
|
///
|
|
/// <remarks>
|
|
/// <b>Daran ordnet der Scan zu, nicht an <see cref="Title"/>.</b> Der
|
|
/// angezeigte Titel kommt nach dem ersten Lauf von der Metadatenquelle und
|
|
/// ändert sich dabei — aus „The Matrix" wird „Matrix". Würde der nächste
|
|
/// Scan darauf suchen, fände er nichts, legte den Film ein zweites Mal an,
|
|
/// und die Mediathek wüchse bei jedem Lauf. Dieses Feld ändert sich nie.
|
|
/// </remarks>
|
|
public required string ParsedTitle { get; init; }
|
|
|
|
/// <summary>Das Jahr, wie es im Dateinamen stand.</summary>
|
|
///
|
|
/// <remarks>
|
|
/// <b>Aus demselben Grund wie <see cref="ParsedTitle"/>, und er wurde
|
|
/// teuer gelernt.</b> Nach einer Korrektur von Hand stand im Ordner
|
|
/// „Dark (2017)", in der Datenbank aber 2004 — der nächste Scan suchte
|
|
/// nach (Dark, 2017), fand nichts, legte den Film neu an und löschte den
|
|
/// korrigierten als verwaist. Gerettet hat es nur die NFO-Datei. Dieses
|
|
/// Feld ändert sich nie.
|
|
/// </remarks>
|
|
public int? ParsedYear { get; init; }
|
|
|
|
public required string Title { get; set; }
|
|
public required string SortTitleValue { get; set; }
|
|
public int? Year { get; set; }
|
|
public string? Overview { get; set; }
|
|
public int? TmdbId { get; set; }
|
|
|
|
/// <summary>Die Kennung bei IMDb — für die NFO-Datei.</summary>
|
|
///
|
|
/// <remarks>
|
|
/// Damit ein anderes Werkzeug denselben Titel wiederfindet, auch wenn es
|
|
/// TMDB gar nicht kennt.
|
|
/// </remarks>
|
|
public string? ImdbId { get; set; }
|
|
|
|
/// <summary>Ob jemand diese Zuordnung von Hand festgelegt hat.</summary>
|
|
///
|
|
/// <remarks>
|
|
/// Dann sucht kein Lauf mehr danach. Steht auch als <c>lockdata</c> in der
|
|
/// NFO-Datei — so überlebt die Entscheidung einen Neuaufbau der Datenbank
|
|
/// und einen Umzug auf einen anderen Server.
|
|
/// </remarks>
|
|
public bool MatchLocked { get; set; }
|
|
/// <summary>Schlüssel des zwischengespeicherten Plakats — siehe Bildablage.</summary>
|
|
///
|
|
/// <remarks>
|
|
/// Der Schlüssel, nicht die Adresse bei TMDB. Würde die App dort unmittelbar
|
|
/// laden, bräuchte sie Internet für ein Plakat, das zehn Zentimeter weiter
|
|
/// auf der Platte liegt — und TMDB erführe, wer wann was ansieht.
|
|
/// </remarks>
|
|
public string? PosterImage { get; set; }
|
|
|
|
public string? BackdropImage { get; set; }
|
|
|
|
public DateTimeOffset AddedAt { get; init; } = DateTimeOffset.UtcNow;
|
|
}
|
|
|
|
public sealed class Episode
|
|
{
|
|
public Guid Id { get; init; } = Guid.CreateVersion7();
|
|
public required Guid SeriesId { get; init; }
|
|
public required int SeasonNumber { get; init; }
|
|
public required int EpisodeNumber { get; init; }
|
|
public string? Title { get; set; }
|
|
public string? Overview { get; set; }
|
|
|
|
/// <summary>Das Standbild der Folge.</summary>
|
|
public string? StillImage { get; set; }
|
|
|
|
public DateTimeOffset AddedAt { get; init; } = DateTimeOffset.UtcNow;
|
|
}
|