Ein Unterstrich ist ein Wortzeichen, und das kostete 3445 Dateien
In `The Good Doctor_S02E18_Trampolin.mkv` steht vor dem S ein Unterstrich. Das Muster verlangte dort `\b` — eine Wortgrenze sitzt aber zwischen Wortzeichen und Nicht-Wortzeichen, und der Unterstrich ist selbst eines. Zwischen `r` und `S` gab es damit keine Grenze, und der gesamte Bestand aus einem Mediathek-Download fiel durch die Erkennung. Die Rückschau verlangt jetzt ausdrücklich: kein Buchstabe, keine Ziffer davor — ein Unterstrich schon. Zwei weitere Muster kamen dabei ans Licht. Mediatheken nummerieren nach Jahr (`Die Anstalt_S2024E04`); drei Ziffern für die Staffel waren zu wenig. Und `# Folge 12 #` ist eine Folgennummer, nur auf Deutsch — die Staffel kommt dann vom Ordner darüber, und nur wenn auch der keine nennt, gilt die Übereinkunft Staffel 1. Gemessen an den 3559 Pfaden aus dem Protokoll: 3554 werden jetzt erkannt. Die fünf übrigen tragen wirklich keine Nummer — ein Titel in einem Staffelordner, sonst nichts. Die Zahl der Serien steigt damit von 58 auf 95, und 95 ist die Zahl, die von Anfang an richtig war. Die neuen Tests arbeiten mit genau diesen Namen. Die alten hatten den Fehler nicht gefunden, weil in jedem ausgedachten Namen ein Leerzeichen vor der Folgennummer stand. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
cfb1e71762
commit
193a23a904
@@ -63,8 +63,27 @@ public static partial class FileNames
|
|||||||
[GeneratedRegex(@"^\d{3,4}[pi]$|^[48]k$", RegexOptions.IgnoreCase)]
|
[GeneratedRegex(@"^\d{3,4}[pi]$|^[48]k$", RegexOptions.IgnoreCase)]
|
||||||
private static partial Regex Auflösung();
|
private static partial Regex Auflösung();
|
||||||
|
|
||||||
/// <summary>S01E02, s1e2, S01.E02, S01E02E03 — die erste Folge zählt.</summary>
|
/// <summary>S01E02, s1e2, S01.E02, Serie_S02E18, S2024E04 — die erste Folge zählt.</summary>
|
||||||
[GeneratedRegex(@"\bS(?<staffel>\d{1,3})[\s._-]*E(?<folge>\d{1,4})", RegexOptions.IgnoreCase)]
|
///
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// <b>Kein <c>\b</c> vor dem S, und das hat 3445 Dateien gekostet.</b> Eine
|
||||||
|
/// Wortgrenze sitzt zwischen einem Wortzeichen und einem Nicht-Wortzeichen
|
||||||
|
/// — der Unterstrich ist aber selbst ein Wortzeichen. In
|
||||||
|
/// <c>The Good Doctor_S02E18_Trampolin.mkv</c> steht zwischen <c>r</c> und
|
||||||
|
/// <c>S</c> damit <i>keine</i> Grenze, und der ganze Bestand aus einem
|
||||||
|
/// Mediathek-Download fiel durch. Die Rückschau verlangt stattdessen
|
||||||
|
/// ausdrücklich: kein Buchstabe, keine Ziffer davor — ein Unterstrich
|
||||||
|
/// schon.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// <b>Vier Ziffern für die Staffel.</b> Sendungen aus Mediatheken
|
||||||
|
/// nummerieren nach Jahr: <c>Die Anstalt_S2024E04</c>. Mit drei Ziffern
|
||||||
|
/// fiel jede davon durch.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
[GeneratedRegex(@"(?<![A-Za-z0-9])S(?<staffel>\d{1,4})[\s._-]*E(?<folge>\d{1,4})",
|
||||||
|
RegexOptions.IgnoreCase)]
|
||||||
private static partial Regex StaffelUndFolge();
|
private static partial Regex StaffelUndFolge();
|
||||||
|
|
||||||
/// <summary>1x02, 01x02 — die ältere Schreibweise.</summary>
|
/// <summary>1x02, 01x02 — die ältere Schreibweise.</summary>
|
||||||
@@ -202,10 +221,30 @@ public static partial class FileNames
|
|||||||
|
|
||||||
var treffer = StaffelUndFolge().Match(roh);
|
var treffer = StaffelUndFolge().Match(roh);
|
||||||
if (!treffer.Success) treffer = KreuzSchreibweise().Match(roh);
|
if (!treffer.Success) treffer = KreuzSchreibweise().Match(roh);
|
||||||
if (!treffer.Success) return null;
|
|
||||||
|
|
||||||
if (!int.TryParse(treffer.Groups["staffel"].Value, out var staffel) ||
|
int staffel, folge;
|
||||||
!int.TryParse(treffer.Groups["folge"].Value, out var folge)) return null;
|
|
||||||
|
if (treffer.Success)
|
||||||
|
{
|
||||||
|
if (!int.TryParse(treffer.Groups["staffel"].Value, out staffel) ||
|
||||||
|
!int.TryParse(treffer.Groups["folge"].Value, out folge)) return null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// **„Folge 12" ist eine Folgennummer, nur auf Deutsch.**
|
||||||
|
//
|
||||||
|
// Mediatheken und Mitschnitte benennen so:
|
||||||
|
// „Weihnachtsmann & Co. KG # Folge 12 # Strafe muss sein".
|
||||||
|
// Eine Staffel steht dort nie — die kommt, wenn überhaupt, vom
|
||||||
|
// Ordner darüber. Fehlt auch der, gilt Staffel 1: das ist die
|
||||||
|
// Übereinkunft für eine Serie ohne Staffeln, nicht geraten.
|
||||||
|
var nurFolge = NurFolgennummer().Match(roh);
|
||||||
|
if (!nurFolge.Success || !int.TryParse(nurFolge.Groups["folge"].Value, out folge))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
staffel = StaffelAusOrdner(dateiname) ?? 1;
|
||||||
|
treffer = nurFolge;
|
||||||
|
}
|
||||||
|
|
||||||
var davor = roh[..treffer.Index];
|
var davor = roh[..treffer.Index];
|
||||||
var danach = roh[(treffer.Index + treffer.Length)..];
|
var danach = roh[(treffer.Index + treffer.Length)..];
|
||||||
@@ -352,6 +391,32 @@ public static partial class FileNames
|
|||||||
return titel.Length > 0 ? titel : null;
|
return titel.Length > 0 ? titel : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>„Folge 12", „Episode 3", „Teil 7" — eine Nummer ohne Staffel.</summary>
|
||||||
|
///
|
||||||
|
/// <remarks>
|
||||||
|
/// Mit Wortgrenze dahinter, damit „Folge 1" nicht aus „Folge 12" wird, und
|
||||||
|
/// nur bis drei Ziffern: „Folge 2024" wäre eher ein Datum.
|
||||||
|
/// </remarks>
|
||||||
|
[GeneratedRegex(@"(?<![A-Za-z])(?:folge|episode|teil|ep)[\s._#-]*(?<folge>\d{1,3})(?!\d)",
|
||||||
|
RegexOptions.IgnoreCase)]
|
||||||
|
private static partial Regex NurFolgennummer();
|
||||||
|
|
||||||
|
/// <summary>Die Staffelnummer aus einem Ordner „Season 4" über der Datei.</summary>
|
||||||
|
///
|
||||||
|
/// <remarks>
|
||||||
|
/// <b>Nur aus dem unmittelbaren Ordner.</b> Steht dort keine Zahl, wird
|
||||||
|
/// keine erfunden — der Aufrufer entscheidet, was dann gilt.
|
||||||
|
/// </remarks>
|
||||||
|
private static int? StaffelAusOrdner(string pfad)
|
||||||
|
{
|
||||||
|
var ordner = Path.GetFileName(Path.GetDirectoryName(pfad.Replace('\\', '/')) ?? "");
|
||||||
|
var treffer = StaffelZahl().Match(ordner.Trim());
|
||||||
|
return treffer.Success && int.TryParse(treffer.Groups["nr"].Value, out var nr) ? nr : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
[GeneratedRegex(@"^(?:staffel|season|series|s)[\s._-]*(?<nr>\d{1,3})$", RegexOptions.IgnoreCase)]
|
||||||
|
private static partial Regex StaffelZahl();
|
||||||
|
|
||||||
/// <summary>„Staffel 1", „Season 02", „S3" — Ordner, die keine Serie benennen.</summary>
|
/// <summary>„Staffel 1", „Season 02", „S3" — Ordner, die keine Serie benennen.</summary>
|
||||||
private static bool IstStaffelordner(string name) =>
|
private static bool IstStaffelordner(string name) =>
|
||||||
StaffelOrdnerName().IsMatch(name.Trim());
|
StaffelOrdnerName().IsMatch(name.Trim());
|
||||||
|
|||||||
@@ -255,3 +255,100 @@ public class SerieAusDerWurzelTests
|
|||||||
Assert.That(FileNames.ParseEpisode("/media/Serien/Dark - S01E01.mkv", Wurzel)?.SeriesTitle,
|
Assert.That(FileNames.ParseEpisode("/media/Serien/Dark - S01E01.mkv", Wurzel)?.SeriesTitle,
|
||||||
Is.EqualTo("Dark"));
|
Is.EqualTo("Dark"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Benennungen, die im echten Bestand vorkamen und durchfielen.</summary>
|
||||||
|
///
|
||||||
|
/// <remarks>
|
||||||
|
/// <b>Diese Namen sind nicht ausgedacht.</b> Jeder davon stammt aus einem
|
||||||
|
/// Protokoll, in dem 3559 von 5884 Dateien als „nicht zugeordnet" standen —
|
||||||
|
/// und aus 95 Serien wurden dadurch 58. Erfundene Testnamen hatten den Fehler
|
||||||
|
/// nicht gefunden: sie trugen alle ein Leerzeichen vor der Folgennummer.
|
||||||
|
/// </remarks>
|
||||||
|
public class EchteBenennungen
|
||||||
|
{
|
||||||
|
private const string Wurzel = "/media/Serien";
|
||||||
|
|
||||||
|
/// <summary>Der Unterstrich — 3445 Dateien, und die teuerste Zeile des Tages.</summary>
|
||||||
|
///
|
||||||
|
/// <remarks>
|
||||||
|
/// <c>\b</c> sitzt zwischen Wortzeichen und Nicht-Wortzeichen. Der
|
||||||
|
/// Unterstrich ist selbst ein Wortzeichen, also gibt es zwischen <c>r</c>
|
||||||
|
/// und <c>S</c> keine Grenze — und der ganze Mediathek-Bestand fiel durch.
|
||||||
|
/// </remarks>
|
||||||
|
[Test]
|
||||||
|
public void Unterstrich_vor_der_Folgennummer()
|
||||||
|
{
|
||||||
|
var name = FileNames.ParseEpisode(
|
||||||
|
$"{Wurzel}/The Good Doctor/S02/The Good Doctor_S02E18_Trampolin.mkv", Wurzel);
|
||||||
|
|
||||||
|
Assert.That(name, Is.Not.Null);
|
||||||
|
Assert.That(name!.SeriesTitle, Is.EqualTo("The Good Doctor"));
|
||||||
|
Assert.That(name.Season, Is.EqualTo(2));
|
||||||
|
Assert.That(name.Episode, Is.EqualTo(18));
|
||||||
|
Assert.That(name.EpisodeTitle, Is.EqualTo("Trampolin"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Mediatheken nummerieren nach Jahr: S2024E04.</summary>
|
||||||
|
[Test]
|
||||||
|
public void Jahreszahl_als_Staffel()
|
||||||
|
{
|
||||||
|
var name = FileNames.ParseEpisode(
|
||||||
|
$"{Wurzel}/Die Anstalt/Die Anstalt_S2024E04_Die Anstalt vom 11_ Juni 2024.mkv", Wurzel);
|
||||||
|
|
||||||
|
Assert.That(name, Is.Not.Null);
|
||||||
|
Assert.That(name!.SeriesTitle, Is.EqualTo("Die Anstalt"));
|
||||||
|
Assert.That(name.Season, Is.EqualTo(2024));
|
||||||
|
Assert.That(name.Episode, Is.EqualTo(4));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>„Folge 12" ist eine Folgennummer, nur auf Deutsch.</summary>
|
||||||
|
[Test]
|
||||||
|
public void Folge_auf_Deutsch()
|
||||||
|
{
|
||||||
|
var name = FileNames.ParseEpisode(
|
||||||
|
$"{Wurzel}/Weihnachtsmann & Co. KG (1997)/"
|
||||||
|
+ "Weihnachtsmann & Co. KG # Folge 12 # Strafe muss sein # Ganze Folge # TOGGO Serien.mp4",
|
||||||
|
Wurzel);
|
||||||
|
|
||||||
|
Assert.That(name, Is.Not.Null);
|
||||||
|
Assert.That(name!.SeriesTitle, Is.EqualTo("Weihnachtsmann & Co. KG"));
|
||||||
|
Assert.That(name.Season, Is.EqualTo(1), "Ohne Staffelordner gilt die Übereinkunft: Staffel 1.");
|
||||||
|
Assert.That(name.Episode, Is.EqualTo(12));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Steht die Staffel am Ordner, wird sie nicht erfunden.</summary>
|
||||||
|
[Test]
|
||||||
|
public void Folge_mit_Staffel_aus_dem_Ordner()
|
||||||
|
{
|
||||||
|
var name = FileNames.ParseEpisode(
|
||||||
|
$"{Wurzel}/Irgendeine Serie/Season 4/Irgendeine Serie - Folge 7.mp4", Wurzel);
|
||||||
|
|
||||||
|
Assert.That(name, Is.Not.Null);
|
||||||
|
Assert.That(name!.Season, Is.EqualTo(4));
|
||||||
|
Assert.That(name.Episode, Is.EqualTo(7));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Was wirklich keine Nummer trägt, bleibt liegen.</summary>
|
||||||
|
///
|
||||||
|
/// <remarks>
|
||||||
|
/// Fünf Dateien im echten Bestand sind so: ein Titel in einem
|
||||||
|
/// Staffelordner, sonst nichts. Eine Nummer dafür zu erfinden hiesse, sie
|
||||||
|
/// an eine beliebige Stelle zu setzen — dann lieber sichtbar liegen lassen.
|
||||||
|
/// </remarks>
|
||||||
|
[Test]
|
||||||
|
public void Ein_blosser_Titel_bleibt_unerkannt()
|
||||||
|
{
|
||||||
|
Assert.That(
|
||||||
|
FileNames.ParseEpisode($"{Wurzel}/Nachtstreife (2020)/Season 4/Endstation Zelle.mp4", Wurzel),
|
||||||
|
Is.Null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Die Gründe müssen auseinanderhalten, was Verschiedenes verlangt.</summary>
|
||||||
|
[Test]
|
||||||
|
public void Der_Grund_benennt_das_Richtige()
|
||||||
|
{
|
||||||
|
Assert.That(
|
||||||
|
FileNames.WarumNicht($"{Wurzel}/Nachtstreife (2020)/Season 4/Endstation Zelle.mp4", Wurzel),
|
||||||
|
Does.Contain("keine Folgennummer"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user