„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>
90 lines
3.9 KiB
C#
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);
|
|
}
|
|
}
|