Tag-Manager: Audio-Metadaten lesen, schreiben und nachschlagen (Teil 24)
Audiodateien tragen ihre Metadaten — Titel, Interpret, Album, Cover — direkt in der Datei. Diese Daten werden als Tags bezeichnet. Das gebräuchlichste Format bei MP3-Dateien ist ID3, bei FLAC-Dateien spricht man von Vorbis Comments. EchoPlay nutzt die Bibliothek TagLib# sowie die MusicBrainz-API, um Tags zu lesen, zu schreiben und online nachzuschlagen.
TagLib#: Audio-Metadaten ohne Format-Kenntnisse
TagLib# ist ein .NET-Port der bekannten C++-Bibliothek TagLib. Sie abstrahiert die technischen Unterschiede zwischen den verschiedenen Tag-Formaten vollständig. Ob MP3 mit ID3v2, FLAC mit Vorbis Comment oder OGG — der Code sieht immer gleich aus:
using TagLib.File file = TagLib.File.Create(filePath);
string? title = file.Tag.Title;
file.Tag.Title = "Neuer Titel";
file.Save();
Das using-Statement sorgt dafür, dass die Datei nach dem Lesen oder Schreiben ordentlich geschlossen wird. TagLib# öffnet die Datei synchron, weshalb der Aufruf in einen Task.Run-Block gehört, um den UI-Thread nicht zu blockieren. Der AudioTag-Datentyp in EchoPlay hat nullable Felder — beim Schreiben werden nur Felder übertragen, die nicht null sind. Das erlaubt es, gezielt einzelne Felder zu aktualisieren, ohne alle anderen zu überschreiben.
MusicBrainz: Kostenlose Musikdatenbank
MusicBrainz ist eine gemeinschaftlich gepflegte Musikdatenbank. Im Gegensatz zu Spotify oder der iTunes-API braucht man keinen API-Key — die REST-API ist öffentlich zugänglich. MusicBrainz erlaubt aber nur eine Anfrage pro Sekunde für anonyme Nutzer. Um mehrere parallele Anfragen zu verhindern, nutzt EchoPlay einen statischen SemaphoreSlim(1, 1). Ein Semaphor ist im Prinzip ein asynchrones Lock: nur ein Thread darf gleichzeitig weitermachen, alle anderen warten.
private static readonly SemaphoreSlim RateLimitSemaphore = new(1, 1);
await RateLimitSemaphore.WaitAsync(cancellationToken);
try
{
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
// API-Aufruf
}
finally
{
RateLimitSemaphore.Release();
}
Das Semaphor ist static, damit es auch bei mehreren MusicBrainzLookupService-Instanzen (z.B. durch DI) geteilt wird.
Das ViewModel-Event-Muster für ContentDialogs
Die Suche startet im ViewModel, aber der ContentDialog gehört zur UI. Ein ViewModel sollte keine WinUI-Dialoge direkt öffnen. Die Lösung: Das ViewModel feuert ein Event mit den Suchergebnissen, und die Page reagiert darauf mit einem Dialog:
// Im ViewModel:
public event EventHandler<IReadOnlyList<TagLookupResult>>? LookupResultsReady;
private async Task LookupOnlineAsync()
{
IReadOnlyList<TagLookupResult> results = await _lookupService.SearchAsync(query);
LookupResultsReady?.Invoke(this, results);
}
Wichtig: Das Event wird in OnNavigatedFrom wieder abgemeldet, um Memory-Leaks zu verhindern. Wenn die Page nicht mehr angezeigt wird, hält der Event-Handler sonst eine Referenz auf die Page, die nicht freigegeben werden kann.
Dateien umbenennen: Platzhaltermuster und Vorschau
Neben dem Bearbeiten von Tags kann EchoPlay Audiodateien auch nach einem konfigurierbaren Muster umbenennen. Der Nutzer gibt ein Muster wie {track:00} - {title} ein, sieht eine Vorschau und bestätigt die Umbenennung. Ein häufig übersehenes Problem bei String-Ersetzungen: Spezifischere Muster müssen immer zuerst verarbeitet werden:
// {track:000} und {track:00} vor {track} ersetzen,
// damit {track} nicht versehentlich den Anfang von {track:00} matcht
result = result.Replace("{track:000}", ...);
result = result.Replace("{track:00}", ...);
result = result.Replace("{track}", ...);
Die Umbenennung ist zweistufig: Zuerst wird eine Vorschau berechnet, dann — nach Bestätigung — die Umbenennung ausgeführt. File.Move mit overwrite: false ist auf demselben Laufwerk atomar: Die Datei erscheint entweder unter dem alten oder dem neuen Namen, nie in einem Zwischenzustand. Ungültige Zeichen im Dateinamen (, /, :, etc.) werden mit Path.GetInvalidFileNameChars() erkannt und durch Unterstriche ersetzt.
Auto-Lookup: Tags aus dem Ordnernamen ableiten
Hörspiele auf der Festplatte folgen meist einer vorhersagbaren Ordnerstruktur: Der übergeordnete Ordner ist der Serienname, der aktuelle Ordner ist der Folgentitel. Aus dieser Struktur lässt sich automatisch eine MusicBrainz-Suchanfrage bauen:
internal static string BuildAutoLookupQuery(string? folderPath)
{
string seriesName = Path.GetFileName(Path.GetDirectoryName(folderPath)) ?? string.Empty;
string episodeFolderName = Path.GetFileName(folderPath) ?? string.Empty;
// Laufnummer entfernen: "001 - Der Super-Papagei" → "Der Super-Papagei"
string episodeTitle = EpisodeFolderParser.StripLeadingSequenceNumber(episodeFolderName);
return $"{seriesName} {episodeTitle}";
}
MusicBrainz liefert bei Hörspiel-Folgen manchmal mehrere Treffer für verschiedene Auflagen. Das Auswahlkriterium ist einfach: Welche Version hat exakt so viele Tracks wie der geladene Ordner? Stimmt die Track-Anzahl überein, wird das Ergebnis direkt angewandt — ohne Rückfrage. Gibt es keinen genauen Treffer, öffnet sich ein Auswahl-Dialog.
Projektstruktur: EchoPlay.TagManager
Alle Tag-Funktionalitäten sind im separaten Projekt EchoPlay.TagManager gekapselt. Es enthält den ITagService für Lesen/Schreiben über TagLib#, den ITagLookupService für die Online-Suche, sowie die DTOs AudioTag und TagLookupResult. Das Projekt hat keine Abhängigkeit zu WinUI oder Entity Framework — es ist eine reine Logik-Schicht.
Die gezeigten Code-Beispiele dienen zur Veranschaulichung. Nutzung auf eigene Verantwortung. Mehr dazu