Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ data class ProviderSeriesMetadata(
val id: ProviderSeriesId,
val metadata: SeriesMetadata,
val books: List<SeriesBook> = emptyList(),
val matchedAltTitle: String? = null,
)

@Serializable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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}" }
}

Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
}
}
Expand All @@ -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 ->
Expand All @@ -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 -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ import snd.komf.model.SeriesMetadata
data class SeriesAndBookMetadata(
val seriesMetadata: SeriesMetadata,
val bookMetadata: Map<MediaServerBook, BookMetadata?>,
val matchedAltTitle: String? = null,
)