using Flaro.Core.Library; using Flaro.Core.Metadata; using Flaro.Data; using Microsoft.EntityFrameworkCore; namespace Flaro.Api; /// Schreibt eine korrigierte Zuordnung überallhin, wo sie hingehört. /// /// /// An drei Stellen, und alle drei sind nötig. In die Datenbank, damit /// es sofort stimmt. In die NFO-Datei, damit es einen Neuaufbau und einen /// Umzug übersteht. Und als Sperre, damit kein automatischer Lauf die /// Entscheidung wieder überstimmt. /// public sealed class MatchApplier( FlaroContext db, IMetadataProvider anbieter, IImageStore bilder, IImageReader bildLeser, ISidecarStore beiwerk, ILogger log) { public async Task ApplyAsync(Movie film, MovieMetadata gefunden, CancellationToken ct = default) { film.TmdbId = gefunden.ProviderId; film.ImdbId = gefunden.ImdbId; film.Title = gefunden.Title; film.SortTitleValue = SortTitle.For(gefunden.Title); film.Year = gefunden.Year ?? film.Year; film.Overview = gefunden.Overview; film.PosterImage = await Ablegen(gefunden.Poster, ct); film.BackdropImage = await Ablegen(gefunden.Backdrop, ct); film.MatchLocked = true; await db.SaveChangesAsync(ct); var datei = await db.MediaFiles.AsNoTracking().FirstOrDefaultAsync(f => f.MovieId == film.Id, ct); if (datei is null || !beiwerk.Enabled) return; var allein = beiwerk.IsAloneInFolder(datei.Path); await BildNeben(datei.Path, Name(allein, datei.Path, SidecarNames.MoviePoster), film.PosterImage, ct); await BildNeben(datei.Path, Name(allein, datei.Path, SidecarNames.MovieBackdrop), film.BackdropImage, ct); await beiwerk.WriteTextAsync(datei.Path, SidecarNames.MovieNfo, Nfo.Write(new MovieNfo( film.Title, film.Year, film.Overview, new ProviderIds(film.TmdbId, film.ImdbId), true)), ct); log.LogInformation("Zuordnung von Hand gesetzt: „{Titel}“ → TMDB {Kennung}", film.Title, film.TmdbId); } public async Task ApplyAsync(Series serie, SeriesMetadata gefunden, CancellationToken ct = default) { serie.TmdbId = gefunden.ProviderId; serie.ImdbId = gefunden.ImdbId; serie.Title = gefunden.Title; serie.SortTitleValue = SortTitle.For(gefunden.Title); serie.Year = gefunden.Year ?? serie.Year; serie.Overview = gefunden.Overview; serie.PosterImage = await Ablegen(gefunden.Poster, ct); serie.BackdropImage = await Ablegen(gefunden.Backdrop, ct); serie.MatchLocked = true; // **Die Folgen gehörten zur alten Serie.** Ihre Beschreibungen und // Standbilder sind jetzt falsch; sie bleiben stehen, bis sie durch die // der richtigen Serie ersetzt sind. var folgen = await db.Episodes.Where(f => f.SeriesId == serie.Id) .OrderBy(f => f.SeasonNumber).ThenBy(f => f.EpisodeNumber).ToListAsync(ct); foreach (var folge in folgen) { ct.ThrowIfCancellationRequested(); var neu = await anbieter.FindEpisodeAsync(gefunden.ProviderId, folge.SeasonNumber, folge.EpisodeNumber, ct); folge.Title = neu?.Title; folge.Overview = neu?.Overview; folge.StillImage = await Ablegen(neu?.Still, ct); } await db.SaveChangesAsync(ct); if (!beiwerk.Enabled) return; await SerienBeiwerk(serie, folgen, ct); log.LogInformation("Zuordnung von Hand gesetzt: „{Titel}“ → TMDB {Kennung}, {Anzahl} Folgen neu geholt", serie.Title, serie.TmdbId, folgen.Count); } private async Task SerienBeiwerk(Series serie, List folgen, CancellationToken ct) { var folgenKennungen = folgen.Select(f => f.Id).ToList(); var dateien = await db.MediaFiles.AsNoTracking() .Where(d => d.EpisodeId != null && folgenKennungen.Contains(d.EpisodeId!.Value)) .ToListAsync(ct); if (dateien.Count == 0) return; var staffel = Path.GetDirectoryName(dateien[0].Path); var serienOrdner = staffel is null ? null : Path.GetDirectoryName(staffel); if (serienOrdner is not null) { var anker = Path.Combine(serienOrdner, "egal.mkv"); await BildNeben(anker, SidecarNames.SeriesPoster, serie.PosterImage, ct); await BildNeben(anker, SidecarNames.SeriesBackdrop, serie.BackdropImage, ct); await beiwerk.WriteTextAsync(anker, SidecarNames.SeriesNfo, Nfo.Write(new SeriesNfo( serie.Title, serie.Year, serie.Overview, new ProviderIds(serie.TmdbId, serie.ImdbId), true)), ct); } foreach (var datei in dateien) { var folge = folgen.First(f => f.Id == datei.EpisodeId); await BildNeben(datei.Path, SidecarNames.EpisodeThumb(datei.Path), folge.StillImage, ct); await beiwerk.WriteTextAsync(datei.Path, SidecarNames.EpisodeNfo(datei.Path), Nfo.Write( new EpisodeNfo(folge.Title, folge.SeasonNumber, folge.EpisodeNumber, folge.Overview)), ct); } } private async Task Ablegen(RemoteImage? bild, CancellationToken ct) => bild is null ? null : await bilder.StoreAsync(bild, ct); private async Task BildNeben(string anker, string name, string? schlüssel, CancellationToken ct) { if (schlüssel is null) return; await using var strom = await bildLeser.OpenAsync(schlüssel, ct); if (strom is null) return; await beiwerk.WriteStreamAsync(anker, name, strom, ct); } private static string Name(bool allein, string mediendatei, string beiwerkName) => allein ? beiwerkName : SidecarNames.Prefixed(mediendatei, beiwerkName); }