Files
Flaro/server/src/Flaro.Core/Library/SidecarNames.cs
T
ScarriffleandClaude Opus 5 ed13d5fa43 Kratzplatz, Markenerkennung, Tonabdruck — erfundene Wörter für Fachbegriffe
„Scratch disk" wörtlich zu übersetzen ist derselbe Fehler wie „Wachhund"
für Watchdog und „Keks" für Cookie. Und „Marke" ist kein Fachbegriff für
das, was gemeint ist: Jellyfin nennt diese Abschnitte Media Segments, und
das ist auch das Wort, in dem sich darüber reden lässt.

    Kratzplatz*       → Transcode*          (was es ist, steht in FLARO_TRANSCODE)
    Marke, Marken     → Segment, Segmente
    Markenerkennung   → Segmenterkennung
    Markenart/-suche  → Segmentart/-suche
    Tonabdruck        → Fingerprint         (Chromaprint nennt es so)
    Tonabgleich       → Fingerprintabgleich
    Schlüsselbild     → Keyframe
    Bildabtaster      → Frameabtaster

Gewöhnliches Deutsch bleibt gewöhnliches Deutsch: Wartung, Freigabe,
Meldung, Abspann, Vorspann. Das war nie das Problem.

**Was sich nicht ändert, und zwar mit Absicht.** Die drei
Einstellungsschlüssel `wartung.kratzplatz.paket.*` bleiben, wie sie sind —
sie stehen in der Datenbank, und ein neuer Name hiesse, dass die Werte
des Nutzers stillschweigend auf die Vorgabe zurückfallen. Dasselbe für
die Endpunktpfade `/marken`: sie sind nicht sichtbar, und ein Umbenennen
wäre Diff ohne Gewinn.

**Die Migration ist von Hand geschrieben.** `dotnet ef` hat ein Gerüst
erzeugt, das beide Tabellen löscht und neu anlegt — es sieht eine
verschwundene und eine neue Tabelle, nicht eine umbenannte. Damit wäre
jedes erkannte Segment und jeder Vermerk „hier wurde schon gesucht" weg
gewesen. `RenameTable` und `RenameIndex` tun dasselbe, ohne eine Zeile
anzufassen; nachgemessen an einer echten SQLite-Datei: Zeilen vorher 1
und 1, nachher 1 und 1, Inhalt unverändert.

Nachgezogen: in `bilder.ts` und `main.ts` heisst „marke" ein Häkchen und
das Logo — die blieben unberührt. Und „Segment" ist sächlich, was eine
Blindersetzung nicht weiss: „eine Segment" stand danach an vierzehn
Stellen.

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

90 lines
3.9 KiB
C#

namespace Flaro.Core.Library;
/// <summary>Welche Dateien Flaro neben eine Mediendatei legen darf — und sonst keine.</summary>
///
/// <remarks>
/// <para>
/// <b>Eine Erlaubnisliste, und sie ist der ganze Schutz.</b> Damit Plakate und
/// Metadaten im Filmordner landen können, muss die Einhängung schreibbar
/// sein — und damit kann der Server im Grundsatz alles dort anstellen. Dass
/// er es nicht tut, hängt an dieser Liste und an der Prüfung darunter.
/// </para>
/// <para>
/// <b>Mediendateien werden nie angefasst.</b> Nicht geändert, nicht
/// umbenannt, nicht gelöscht. Es gibt im ganzen Projekt keine Stelle, die das
/// könnte, und diese Liste ist der Grund: was nicht daraufsteht, wird
/// abgelehnt — auch wenn jemand es ausdrücklich verlangt.
/// </para>
/// <para>
/// <b>Transcode-Zwischenstände gehören ausdrücklich nicht hierher.</b> Die
/// liegen unter <c>FLARO_TRANSCODE</c>, auf einem eigenen Volume. Ein
/// Medienordner ist kein Transcode-Cache.
/// </para>
/// </remarks>
public static class SidecarNames
{
public const string MoviePoster = "poster.jpg";
public const string MovieBackdrop = "fanart.jpg";
public const string MovieLogo = "logo.png";
public const string MovieNfo = "movie.nfo";
public const string SeriesPoster = "poster.jpg";
public const string SeriesBackdrop = "fanart.jpg";
public const string SeriesNfo = "tvshow.nfo";
/// <summary>Was zu einer Folge gehört, hängt am Namen ihrer Datei.</summary>
///
/// <remarks>
/// Aus <c>Dark - S01E01.mkv</c> werden <c>Dark - S01E01.nfo</c> und
/// <c>Dark - S01E01-thumb.jpg</c>. So erwarten es Kodi, Jellyfin und Plex
/// — und so findet auch ein Mensch sie wieder.
/// </remarks>
public static string EpisodeNfo(string mediendatei) =>
Path.GetFileNameWithoutExtension(mediendatei) + ".nfo";
public static string EpisodeThumb(string mediendatei) =>
Path.GetFileNameWithoutExtension(mediendatei) + "-thumb.jpg";
/// <summary>Beiwerk für einen Film, der sich den Ordner mit anderen teilt.</summary>
///
/// <remarks>
/// <b>Sonst überschreiben sich zwei Filme gegenseitig das Plakat.</b> Wer
/// je Film einen Ordner anlegt, bekommt die schlichten Namen; wer zehn
/// Filme nebeneinanderlegt, bekommt sie mit dem Dateinamen davor. Kodi und
/// Jellyfin lesen beide Formen.
/// </remarks>
public static string Prefixed(string mediendatei, string beiwerk) =>
Path.GetFileNameWithoutExtension(mediendatei) + "-" + beiwerk;
private static readonly HashSet<string> Feste = new(StringComparer.OrdinalIgnoreCase)
{ MoviePoster, MovieBackdrop, MovieLogo, MovieNfo, SeriesNfo };
private static readonly string[] Endungen =
[".nfo", "-thumb.jpg", "-poster.jpg", "-fanart.jpg", "-logo.png"];
/// <summary>Ob dieser Dateiname geschrieben werden darf.</summary>
///
/// <remarks>
/// <b>Geprüft wird der Name, nicht die Absicht.</b> Ein Pfadbestandteil,
/// ein Verzeichniswechsel oder ein führender Punkt kommen hier nicht
/// durch — und „film.mkv" erst recht nicht, auch wenn es auf <c>.nfo</c>
/// endete.
/// </remarks>
public static bool MayWrite(string dateiname)
{
if (string.IsNullOrWhiteSpace(dateiname)) return false;
// Kein Pfad, nur ein Name. Alles andere könnte aus dem Ordner führen.
if (dateiname.Contains('/') || dateiname.Contains('\\')) return false;
if (dateiname != Path.GetFileName(dateiname)) return false;
if (dateiname.StartsWith('.')) return false;
if (dateiname.Contains("..", StringComparison.Ordinal)) return false;
if (Feste.Contains(dateiname)) return true;
// Je Folge: beliebiger Rumpf, aber nur mit diesen Endungen.
return Endungen.Any(e => dateiname.EndsWith(e, StringComparison.OrdinalIgnoreCase)
&& dateiname.Length > e.Length);
}
}