Metadata Fixes (#3533)

Co-authored-by: Midhun Sudhir <60651970+midhun3301@users.noreply.github.com>
This commit is contained in:
Joe Milazzo 2025-02-06 16:47:29 -06:00 committed by GitHub
parent 40bbdcb5f0
commit bb9621a588
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 151 additions and 56 deletions

View file

@ -195,6 +195,7 @@ public class ExternalMetadataService : IExternalMetadataService
var license = (await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.LicenseKey)).Value;
var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(dto.SeriesId,
SeriesIncludes.Metadata | SeriesIncludes.ExternalMetadata);
if (series == null) return [];
var potentialAnilistId = ScrobblingService.ExtractId<int?>(dto.Query, ScrobblingService.AniListWeblinkWebsite);
var potentialMalId = ScrobblingService.ExtractId<long?>(dto.Query, ScrobblingService.MalWeblinkWebsite);
@ -512,7 +513,7 @@ public class ExternalMetadataService : IExternalMetadataService
madeModification = true;
}
if (settings.EnableStartDate && externalMetadata.StartDate.HasValue)
if (settings.EnableStartDate && !series.Metadata.ReleaseYearLocked && externalMetadata.StartDate.HasValue)
{
series.Metadata.ReleaseYear = externalMetadata.StartDate.Value.Year;
madeModification = true;
@ -526,7 +527,7 @@ public class ExternalMetadataService : IExternalMetadataService
// Process Genres
if (externalMetadata.Genres != null)
{
foreach (var genre in externalMetadata.Genres.Where(g => !settings.Blacklist.Contains(g)))
foreach (var genre in externalMetadata.Genres)
{
// Apply field mappings
var mappedGenre = ApplyFieldMapping(genre, MetadataFieldType.Genre, settings.FieldMappings);
@ -537,9 +538,12 @@ public class ExternalMetadataService : IExternalMetadataService
}
// Strip blacklisted items from processedGenres
processedGenres = processedGenres.Distinct().Where(g => !settings.Blacklist.Contains(g)).ToList();
processedGenres = processedGenres
.Distinct()
.Where(g => !settings.Blacklist.Contains(g))
.ToList();
if (settings.EnableGenres && processedGenres.Count > 0)
if (settings.EnableGenres && !series.Metadata.GenresLocked && processedGenres.Count > 0)
{
_logger.LogDebug("Found {GenreCount} genres for {SeriesName}", processedGenres.Count, series.Name);
var allGenres = (await _unitOfWork.GenreRepository.GetAllGenresByNamesAsync(processedGenres.Select(Parser.Normalize))).ToList();
@ -567,13 +571,14 @@ public class ExternalMetadataService : IExternalMetadataService
}
// Strip blacklisted items from processedTags
processedTags = processedTags.Distinct()
processedTags = processedTags
.Distinct()
.Where(g => !settings.Blacklist.Contains(g))
.Where(g => settings.Whitelist.Count == 0 || settings.Whitelist.Contains(g))
.ToList();
// Set the tags for the series and ensure they are in the DB
if (settings.EnableTags && processedTags.Count > 0)
if (settings.EnableTags && !series.Metadata.TagsLocked && processedTags.Count > 0)
{
_logger.LogDebug("Found {TagCount} tags for {SeriesName}", processedTags.Count, series.Name);
var allTags = (await _unitOfWork.TagRepository.GetAllTagsByNameAsync(processedTags.Select(Parser.Normalize)))
@ -591,22 +596,36 @@ public class ExternalMetadataService : IExternalMetadataService
#region Age Rating
// Determine Age Rating
var ageRating = DetermineAgeRating(processedGenres.Concat(processedTags), settings.AgeRatingMappings);
if (!series.Metadata.AgeRatingLocked && series.Metadata.AgeRating <= ageRating)
if (!series.Metadata.AgeRatingLocked)
{
series.Metadata.AgeRating = ageRating;
_unitOfWork.SeriesRepository.Update(series);
madeModification = true;
}
try
{
// Determine Age Rating
var totalTags = processedGenres
.Concat(processedTags)
.Concat(series.Metadata.Genres.Select(g => g.Title))
.Concat(series.Metadata.Tags.Select(g => g.Title));
var ageRating = DetermineAgeRating(totalTags, settings.AgeRatingMappings);
if (!series.Metadata.AgeRatingLocked && series.Metadata.AgeRating <= ageRating)
{
series.Metadata.AgeRating = ageRating;
_unitOfWork.SeriesRepository.Update(series);
madeModification = true;
}
}
catch (Exception ex)
{
_logger.LogError(ex, "There was an issue determining Age Rating for Series {SeriesName} ({SeriesId})", series.Name, series.Id);
}
}
#endregion
#region People
if (settings.EnablePeople)
{
series.Metadata.People ??= new List<SeriesMetadataPeople>();
series.Metadata.People ??= [];
// Ensure all people are named correctly
externalMetadata.Staff = externalMetadata.Staff.Select(s =>
@ -635,7 +654,10 @@ public class ExternalMetadataService : IExternalMetadataService
Name = w.Name,
AniListId = ScrobblingService.ExtractId<int>(w.Url, ScrobblingService.AniListStaffWebsite),
Description = CleanSummary(w.Description),
}).ToList();
})
.Concat(series.Metadata.People.Where(p => p.Role == PersonRole.Writer).Select(p => _mapper.Map<PersonDto>(p)))
.DistinctBy(p => Parser.Normalize(p.Name))
.ToList();
// NOTE: PersonRoles can be a hashset
@ -661,7 +683,10 @@ public class ExternalMetadataService : IExternalMetadataService
Name = w.Name,
AniListId = ScrobblingService.ExtractId<int>(w.Url, ScrobblingService.AniListStaffWebsite),
Description = CleanSummary(w.Description),
}).ToList();
})
.Concat(series.Metadata.People.Where(p => p.Role == PersonRole.CoverArtist).Select(p => _mapper.Map<PersonDto>(p)))
.DistinctBy(p => Parser.Normalize(p.Name))
.ToList();
if (!series.Metadata.CoverArtistLocked && artists.Count > 0 && settings.PersonRoles.Contains(PersonRole.CoverArtist))
{
@ -684,7 +709,10 @@ public class ExternalMetadataService : IExternalMetadataService
Name = w.Name,
AniListId = ScrobblingService.ExtractId<int>(w.Url, ScrobblingService.AniListCharacterWebsite),
Description = CleanSummary(w.Description),
}).ToList();
})
.Concat(series.Metadata.People.Where(p => p.Role == PersonRole.Character).Select(p => _mapper.Map<PersonDto>(p)))
.DistinctBy(p => Parser.Normalize(p.Name))
.ToList();
if (!series.Metadata.CharacterLocked && characters.Count > 0)
@ -713,13 +741,27 @@ public class ExternalMetadataService : IExternalMetadataService
#endregion
#region Publication Status
if (!series.Metadata.PublicationStatusLocked && settings.EnablePublicationStatus)
{
var chapters = (await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(series.Id, SeriesIncludes.Chapters))!.Volumes.SelectMany(v => v.Chapters).ToList();
var wasChanged = DeterminePublicationStatus(series, chapters, externalMetadata);
_unitOfWork.SeriesRepository.Update(series);
madeModification = madeModification || wasChanged;
try
{
var chapters =
(await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(series.Id, SeriesIncludes.Chapters))!.Volumes
.SelectMany(v => v.Chapters).ToList();
var wasChanged = DeterminePublicationStatus(series, chapters, externalMetadata);
_unitOfWork.SeriesRepository.Update(series);
madeModification = madeModification || wasChanged;
}
catch (Exception ex)
{
_logger.LogError(ex, "There was an issue determining Publication Status for Series {SeriesName} ({SeriesId})", series.Name, series.Id);
}
}
#endregion
#region Relationships
if (settings.EnableRelationships && externalMetadata.Relations != null && defaultAdmin != null)
{
@ -773,6 +815,7 @@ public class ExternalMetadataService : IExternalMetadataService
madeModification = true;
}
}
#endregion
return madeModification;
}
@ -889,6 +932,8 @@ public class ExternalMetadataService : IExternalMetadataService
private static AgeRating DetermineAgeRating(IEnumerable<string> values, Dictionary<string, AgeRating> mappings)
{
// Find highest age rating from mappings
mappings ??= new Dictionary<string, AgeRating>();
return values
.Select(v => mappings.TryGetValue(v, out var mapping) ? mapping : AgeRating.Unknown)
.DefaultIfEmpty(AgeRating.Unknown)
@ -913,6 +958,7 @@ public class ExternalMetadataService : IExternalMetadataService
};
series.ExternalSeriesMetadata = externalSeriesMetadata;
_unitOfWork.ExternalSeriesMetadataRepository.Attach(externalSeriesMetadata);
return externalSeriesMetadata;
}

View file

@ -349,7 +349,8 @@ public class SeriesService : ISeriesService
var existingPeople = await unitOfWork.PersonRepository.GetPeopleByNames(normalizedNames);
// Use a dictionary for quick lookups
var existingPeopleDictionary = existingPeople.DistinctBy(p => p.NormalizedName).ToDictionary(p => p.NormalizedName, p => p);
var existingPeopleDictionary = existingPeople.DistinctBy(p => p.NormalizedName)
.ToDictionary(p => p.NormalizedName, p => p);
// List to track people that will be added to the metadata
var peopleToAdd = new List<Person>();

View file

@ -450,12 +450,12 @@ public class ScannerService : IScannerService
// That way logging and UI informing is all in one place with full context
_logger.LogError("[ScannerService] Some of the root folders for the library are empty. " +
"Either your mount has been disconnected or you are trying to delete all series in the library. " +
"Scan has be aborted. " +
"Scan has been aborted. " +
"Check that your mount is connected or change the library's root folder and rescan");
await _eventHub.SendMessageAsync(MessageFactory.Error, MessageFactory.ErrorEvent( $"Some of the root folders for the library, {libraryName}, are empty.",
"Either your mount has been disconnected or you are trying to delete all series in the library. " +
"Scan has be aborted. " +
"Scan has been aborted. " +
"Check that your mount is connected or change the library's root folder and rescan"));
return false;

View file

@ -114,6 +114,7 @@ public partial class VersionUpdaterService : IVersionUpdaterService
var nightlyDto = new UpdateNotificationDto
{
// TODO: I should pass Title to the FE so that Nightly Release can be localized
UpdateTitle = $"Nightly Release {nightly.Version} - {prInfo.Title}",
UpdateVersion = nightly.Version,
CurrentVersion = dto.CurrentVersion,
@ -446,7 +447,7 @@ public partial class VersionUpdaterService : IVersionUpdaterService
{
var sections = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
var lines = body.Split('\n');
string currentSection = null;
string? currentSection = null;
foreach (var line in lines)
{