diff --git a/apps/coordinator/src/main/kotlin/no/iktdev/mediaprocessing/coordinator/listeners/events/MediaEventParsedInfoListener.kt b/apps/coordinator/src/main/kotlin/no/iktdev/mediaprocessing/coordinator/listeners/events/MediaEventParsedInfoListener.kt new file mode 100644 index 00000000..583118d1 --- /dev/null +++ b/apps/coordinator/src/main/kotlin/no/iktdev/mediaprocessing/coordinator/listeners/events/MediaEventParsedInfoListener.kt @@ -0,0 +1,194 @@ +package no.iktdev.mediaprocessing.coordinator.listeners.events + +import no.iktdev.eventi.events.EventListener +import no.iktdev.eventi.models.Event +import no.iktdev.mediaprocessing.shared.common.event_task_contract.events.StartProcessingEvent +import org.springframework.stereotype.Component +import java.io.File + +@Component +class MediaEventParsedInfoListener : EventListener() { + override fun onEvent( + event: Event, + history: List + ): Event? { + val started = event as? StartProcessingEvent ?: return null + val fileName = File(started.data.fileUri).nameWithoutExtension + val cleanedTitle = fileName.getCleanedTitle() + + return null + } + + fun String.getCleanedTitle(): String { + return this + .noBrackets() + .noParens() + .noResolutionAndAfter() + .noSourceTags() + .noYear() + .noDots() + .noTrailingOrLeading() + .noUnderscores() + .noExtraSpaces() + .trim() + } + + + fun String.noBrackets() = Regex("\\[.*?]").replace(this, " ") + fun String.noParens() = Regex("\\(.*?\\)").replace(this, " ") + fun String.noTrailingOrLeading() = Regex("^[^a-zA-Z0-9!,]+|[^a-zA-Z0-9!~,]+\$").replace(this, " ") + fun String.noResolutionAndAfter() = Regex("[0-9]+[pk].*", RegexOption.IGNORE_CASE).replace(this, "") + fun String.noSourceTags() = + Regex("(?i)(bluray|laserdisc|dvd|web|uhd|hd|htds|imax).*", RegexOption.IGNORE_CASE).replace(this, " ") + fun String.noUnderscores() = this.replace("_", " ") + fun String.noYear() = Regex("\\b\\d{4}\\b").replace(this.takeIf { !it.matches(Regex("^\\d{4}")) } ?: this, "") + fun String.noDots() = Regex("(? this.guessDesiredMovieTitle() + MediaType.Serie -> this.guessDesiredSerieTitle() + } + } + + fun File.getDesiredCollection(): String { + val collection = when (this.guessMovieOrSeries()) { + MediaType.Movie -> this.guessDesiredMovieTitle() + MediaType.Serie -> this.guessDesiredSerieTitle() + } + return collection.noParens().noYear().split(" - ").first().trim() + } + + /** + * @return A fully cleaned title suitable to use for collection + */ + fun File.guessDesiredMovieTitle(): String { + val cleaned = this.nameWithoutExtension.getCleanedTitle() + val yearRegex = Regex("\\b(19|20)\\d{2}\\b") + val yearMatch = yearRegex.find(cleaned) + + return if (yearMatch != null) { + val title = cleaned.replace(yearRegex, "").trim() + "${title} (${yearMatch.value})" + } else { + cleaned + } + } + + + /** + * @return A fully cleaned title including season and episode with possible episode title + */ + fun File.guessDesiredSerieTitle(): String { + val raw = this.nameWithoutExtension + + val seasonRegex = Regex("""(?i)(?:S|Season|Series)\s*(\d{1,2})""") + val episodeRegex = Regex("""(?i)(?:E|Episode|Ep)\s*(\d{1,3})""") + val revisionRegex = Regex("""(?i)\bv(\d+)\b""") + + val seasonMatch = seasonRegex.find(raw) + val episodeMatch = episodeRegex.find(raw) + val revisionMatch = revisionRegex.find(raw) + + val season = seasonMatch?.groupValues?.get(1)?.toIntOrNull() + val episode = episodeMatch?.groupValues?.get(1)?.toIntOrNull() + val revision = revisionMatch?.groupValues?.get(1)?.toIntOrNull() + + val baseTitle = if (seasonMatch != null) { + raw.substring(0, seasonMatch.range.first).getCleanedTitle() + } else raw.getCleanedTitle() + + val episodeTitle = if (episodeMatch != null) { + raw.substring(episodeMatch.range.last + 1).getCleanedTitle() + } else "" + + val tag = buildString { + append("S${(season ?: 1).toString().padStart(2, '0')}") + append("E${(episode ?: 1).toString().padStart(2, '0')}") + if (revision != null) append(" (v$revision)") + } + + return buildString { + append(baseTitle) + append(" - ") + append(tag) + if (episodeTitle.isNotEmpty()) { + append(" - ") + append(episodeTitle) + } + }.trim() + } + + + + fun File.guessSearchableTitle(): List { + val cleaned = this.guessDesiredFileName().noParens() + .let { + val regex = "\\((?!\\d{4}\\))(?>[^()]+|\\b)\\)" + Regex(regex).replace(it, "") + } + .noResolutionAndAfter() + .noSourceTags() + .noDots() + .noExtraSpaces() + .trim('.', ',', ' ') + + val titles = mutableListOf() + + // 1. Første del før bindestrek + val firstPart = cleaned.split(" - ").firstOrNull()?.trim() ?: cleaned + titles.add(firstPart) + + // 2. Hele cleaned + titles.add(cleaned) + + // 3. Fjern årstall hvis det finnes + val yearRegex = Regex("""\b(19|20)\d{2}\b""") + val noYear = yearRegex.replace(cleaned, "").trim() + if (noYear.isNotEmpty() && noYear != cleaned) { + titles.add(noYear) + } + + return titles.distinct() + } + + + +} \ No newline at end of file