diff --git a/komf-core/src/commonMain/kotlin/snd/komf/model/SeriesMetadata.kt b/komf-core/src/commonMain/kotlin/snd/komf/model/SeriesMetadata.kt index 1ffd6ff9..cf152f5c 100644 --- a/komf-core/src/commonMain/kotlin/snd/komf/model/SeriesMetadata.kt +++ b/komf-core/src/commonMain/kotlin/snd/komf/model/SeriesMetadata.kt @@ -45,6 +45,7 @@ data class ProviderSeriesMetadata( val id: ProviderSeriesId, val metadata: SeriesMetadata, val books: List = emptyList(), + val matchedAltTitle: String? = null, ) @Serializable diff --git a/komf-core/src/commonMain/kotlin/snd/komf/providers/MetadataConfigApplier.kt b/komf-core/src/commonMain/kotlin/snd/komf/providers/MetadataConfigApplier.kt index d5399b6f..18eb04d4 100644 --- a/komf-core/src/commonMain/kotlin/snd/komf/providers/MetadataConfigApplier.kt +++ b/komf-core/src/commonMain/kotlin/snd/komf/providers/MetadataConfigApplier.kt @@ -13,6 +13,7 @@ object MetadataConfigApplier { ProviderSeriesMetadata( id = id, books = getIfEnabled(books, config.books) ?: emptyList(), + matchedAltTitle = matchedAltTitle, metadata = SeriesMetadata( status = getIfEnabled(metadata.status, config.status), title = getIfEnabled(metadata.title, config.title), diff --git a/komf-core/src/commonMain/kotlin/snd/komf/providers/mangaupdates/MangaUpdatesMetadataProvider.kt b/komf-core/src/commonMain/kotlin/snd/komf/providers/mangaupdates/MangaUpdatesMetadataProvider.kt index bf04def3..ed6c940f 100644 --- a/komf-core/src/commonMain/kotlin/snd/komf/providers/mangaupdates/MangaUpdatesMetadataProvider.kt +++ b/komf-core/src/commonMain/kotlin/snd/komf/providers/mangaupdates/MangaUpdatesMetadataProvider.kt @@ -83,12 +83,26 @@ class MangaUpdatesMetadataProvider( val seriesName = matchQuery.seriesName val searchResults = client.searchSeries(seriesName.take(400), seriesTypes).results.map { it.record } - return searchResults + // Tier 1: match against primary title + val primaryMatch = searchResults .firstOrNull { nameMatcher.matches(seriesName, it.title.removeSuffix(" (Novel)")) } - ?.let { - val series = client.getSeries(it.id) + if (primaryMatch != null) { + val series = client.getSeries(primaryMatch.id) + val thumbnail = if (fetchSeriesCovers) client.getThumbnail(series) else null + return metadataMapper.toSeriesMetadata(series, thumbnail) + } + + // Tier 3: fetch full records and match against associated names + for (result in searchResults) { + val series = client.getSeries(result.id) + val associatedMatch = series.associated.firstOrNull { nameMatcher.matches(seriesName, it.title) } + if (associatedMatch != null) { val thumbnail = if (fetchSeriesCovers) client.getThumbnail(series) else null - metadataMapper.toSeriesMetadata(series, thumbnail) + return metadataMapper.toSeriesMetadata(series, thumbnail) + .copy(matchedAltTitle = series.title) } + } + + return null } } diff --git a/komf-mediaserver/src/commonMain/kotlin/snd/komf/mediaserver/metadata/MetadataPostProcessor.kt b/komf-mediaserver/src/commonMain/kotlin/snd/komf/mediaserver/metadata/MetadataPostProcessor.kt index 9268166d..bd135c4c 100644 --- a/komf-mediaserver/src/commonMain/kotlin/snd/komf/mediaserver/metadata/MetadataPostProcessor.kt +++ b/komf-mediaserver/src/commonMain/kotlin/snd/komf/mediaserver/metadata/MetadataPostProcessor.kt @@ -36,6 +36,7 @@ class MetadataPostProcessor( val bookMetadata = postProcessBooks(metadata.bookMetadata) return handleKomgaOneshot(seriesMetadata, bookMetadata) + .copy(matchedAltTitle = metadata.matchedAltTitle) } private fun postProcessSeries(series: SeriesMetadata): SeriesMetadata { diff --git a/komf-mediaserver/src/commonMain/kotlin/snd/komf/mediaserver/metadata/MetadataService.kt b/komf-mediaserver/src/commonMain/kotlin/snd/komf/mediaserver/metadata/MetadataService.kt index 129f31ab..9b8bdd71 100644 --- a/komf-mediaserver/src/commonMain/kotlin/snd/komf/mediaserver/metadata/MetadataService.kt +++ b/komf-mediaserver/src/commonMain/kotlin/snd/komf/mediaserver/metadata/MetadataService.kt @@ -29,6 +29,7 @@ import snd.komf.mediaserver.model.MediaServerBook import snd.komf.mediaserver.model.MediaServerLibraryId import snd.komf.mediaserver.model.MediaServerSeries import snd.komf.mediaserver.model.MediaServerSeriesId +import snd.komf.mediaserver.model.MediaServerSeriesMetadataUpdate import snd.komf.mediaserver.model.SeriesAndBookMetadata import snd.komf.model.BookMetadata import snd.komf.model.BookQualifier @@ -41,6 +42,7 @@ import snd.komf.model.ProviderSeriesId import snd.komf.model.ProviderSeriesMetadata import snd.komf.model.SeriesBook import snd.komf.model.SeriesSearchResult +import snd.komf.model.SeriesTitle import snd.komf.providers.CoreProviders import snd.komf.providers.MetadataProvider import snd.komf.providers.ProvidersModule @@ -211,6 +213,17 @@ class MetadataService( eventFlow.emit(PostProcessingStartEvent) metadataUpdateService.updateMetadata(series, metadata) + + matchResult.second.matchedAltTitle?.let { altTitle -> + logger.info { "writing matched provider title \"$altTitle\" as localized name for series \"$seriesTitle\" ${series.id}" } + mediaServerClient.updateSeriesMetadata( + series.id, + MediaServerSeriesMetadataUpdate( + alternativeTitles = listOf(SeriesTitle(name = altTitle, type = null, language = "ja-ro")) + ) + ) + } + logger.info { "finished metadata update of series \"${seriesTitle}\" ${series.id}" } } @@ -238,7 +251,7 @@ class MetadataService( if (result != null) { logger.info { "found match: \"${result.metadata.titles.firstOrNull()?.name}\" from ${provider.providerName()} ${result.id}" } val bookMetadata = getBookMetadata(books, result, provider, bookEdition, eventFlow) - return SeriesAndBookMetadata(result.metadata, bookMetadata) + return SeriesAndBookMetadata(result.metadata, bookMetadata, result.matchedAltTitle) } } return null diff --git a/komf-mediaserver/src/commonMain/kotlin/snd/komf/mediaserver/metadata/MetadataUpdater.kt b/komf-mediaserver/src/commonMain/kotlin/snd/komf/mediaserver/metadata/MetadataUpdater.kt index c3cbb349..3f392d6e 100644 --- a/komf-mediaserver/src/commonMain/kotlin/snd/komf/mediaserver/metadata/MetadataUpdater.kt +++ b/komf-mediaserver/src/commonMain/kotlin/snd/komf/mediaserver/metadata/MetadataUpdater.kt @@ -93,13 +93,20 @@ class MetadataUpdater( processedMetadata: SeriesAndBookMetadata ) { val bookIdToWriteSeriesMetadata = bookToWriteSeriesMetadata(unprocessedMetadata.bookMetadata) + // Prefer the explicit matched alt title (Tier 3 search result); fall back to any + // language-tagged title from the unprocessed metadata (e.g. the ja-ro primary title + // from MangaUpdates). Post-processing strips titles when alternativeSeriesTitles=false, + // so we must source this from the unprocessed metadata. + val localizedSeriesName = unprocessedMetadata.matchedAltTitle + ?: unprocessedMetadata.seriesMetadata.titles.find { it.language != null }?.name processedMetadata.bookMetadata.forEach { (book, metadata) -> updateBookMetadata( book, metadata, processedMetadata.seriesMetadata, - book.id == bookIdToWriteSeriesMetadata + book.id == bookIdToWriteSeriesMetadata, + localizedSeriesName, ) } } @@ -108,7 +115,8 @@ class MetadataUpdater( book: MediaServerBook, metadata: BookMetadata?, seriesMeta: SeriesMetadata, - writeSeriesMetadata: Boolean + writeSeriesMetadata: Boolean, + localizedSeriesName: String? = null, ) { logger.info { "updating book ${book.name}" } updateModes.forEach { mode -> @@ -124,7 +132,15 @@ class MetadataUpdater( if (writeSeriesMetadata) metadataUpdateMapper.toSeriesComicInfo(seriesMeta, metadata) else metadataUpdateMapper.toComicInfo(metadata, seriesMeta) - comicInfo?.let { comicInfoWriter.writeMetadata(book.url, it) } + // The post-processor strips titles when alternativeSeriesTitles=false, so + // the mapper produces localizedSeries=null. Patch it from the value we + // computed from the unprocessed metadata so Kavita's scanner never resets + // series.LocalizedName to empty on rescan. + val patchedComicInfo = if (localizedSeriesName != null) + comicInfo?.copy(localizedSeries = comicInfo.localizedSeries ?: localizedSeriesName) + else comicInfo + + patchedComicInfo?.let { comicInfoWriter.writeMetadata(book.url, it) } } // UpdateMode.OPF -> { diff --git a/komf-mediaserver/src/commonMain/kotlin/snd/komf/mediaserver/model/SeriesAndBookMetadata.kt b/komf-mediaserver/src/commonMain/kotlin/snd/komf/mediaserver/model/SeriesAndBookMetadata.kt index 78f3ba08..3e099880 100644 --- a/komf-mediaserver/src/commonMain/kotlin/snd/komf/mediaserver/model/SeriesAndBookMetadata.kt +++ b/komf-mediaserver/src/commonMain/kotlin/snd/komf/mediaserver/model/SeriesAndBookMetadata.kt @@ -6,4 +6,5 @@ import snd.komf.model.SeriesMetadata data class SeriesAndBookMetadata( val seriesMetadata: SeriesMetadata, val bookMetadata: Map, + val matchedAltTitle: String? = null, )