From 040f43f85091c3e1f3fe24a09bd2ba8b8dc9d35e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 16 Aug 2026 18:21:19 +0200 Subject: [PATCH] =?UTF-8?q?perf(domain):=20pr=C3=A9compiler=20et=20m=C3=A9?= =?UTF-8?q?mo=C3=AFser=20les=20Regex=20dans=20TitleCleaner,=20Formatters?= =?UTF-8?q?=20et=20VideoNameParser=20(fixes=20#147)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/localstream/app/domain/Formatters.kt | 30 +++++++++++-------- .../app/domain/VideoFilterSorter.kt | 4 ++- .../localstream/app/domain/VideoGrouper.kt | 7 +++-- .../localstream/app/domain/VideoNameParser.kt | 14 +++++---- 4 files changed, 35 insertions(+), 20 deletions(-) diff --git a/native/app/src/main/java/com/localstream/app/domain/Formatters.kt b/native/app/src/main/java/com/localstream/app/domain/Formatters.kt index a3b2c29..26ed8d4 100644 --- a/native/app/src/main/java/com/localstream/app/domain/Formatters.kt +++ b/native/app/src/main/java/com/localstream/app/domain/Formatters.kt @@ -8,10 +8,17 @@ import kotlin.math.pow object TitleCleaner { private val YEAR_IN_PAREN_REGEX = Regex("[\\(\\[]((?:19|20)\\d{2})[\\)\\]]") private val YEAR_REGEX = Regex("(?:^|[\\s\\._\\-\\(\\[])((?:19|20)\\d{2})(?=[\\s\\._\\-\\)\\]]|$)") + private val EXTENSION_REGEX = Regex("\\.[^/.]+$") + private val SEASON_EPISODE_CLEAN_REGEX = Regex("[sS]\\d+(\\s*)?([eE]\\d+)?|(\\d+)(\\s*)?x(\\d+).*", RegexOption.IGNORE_CASE) + private val YEAR_SUFFIX_REGEX = Regex("(?<=\\w|\\s|\\.|\\-|_|\\()\\s*[\\(\\.\\[\\-_]?(19|20)\\d{2}.*") + private val SEPARATORS_REGEX = Regex("[\\.\\-_]") + private val TAGS_REGEX = Regex("1080p|720p|2160p|4k|bluray|webrip|hdtv|x264|x265|hevc|vostfr|french|truefrench", RegexOption.IGNORE_CASE) + private val OPEN_BRACKET_END_REGEX = Regex("[\\(\\[\\{]\\s*$") + private val TRAILING_SYMBOLS_REGEX = Regex("[\\s\\-\\.\\(\\)\\[\\]\\{\\}]+$") @Suppress("ReturnCount") fun extractYear(filename: String): Int? { - val nameWithoutExt = filename.replace(Regex("\\.[^/.]+$"), "") + val nameWithoutExt = filename.replace(EXTENSION_REGEX, "") val parenMatch = YEAR_IN_PAREN_REGEX.find(nameWithoutExt) if (parenMatch != null) { return parenMatch.groupValues[1].toIntOrNull() @@ -21,22 +28,21 @@ object TitleCleaner { } fun getCleanTitle(filename: String): String { - var title = filename.replace(Regex("\\.[^/.]+$"), "") - title = title.replace(Regex("[sS]\\d+(\\s*)?([eE]\\d+)?|(\\d+)(\\s*)?x(\\d+).*", RegexOption.IGNORE_CASE), "") - title = title.replace(Regex("(?<=\\w|\\s|\\.|\\-|_|\\()\\s*[\\(\\.\\[\\-_]?(19|20)\\d{2}.*"), "") - title = title.replace(Regex("[\\.\\-_]"), " ") - title = title.replace( - Regex("1080p|720p|2160p|4k|bluray|webrip|hdtv|x264|x265|hevc|vostfr|french|truefrench", RegexOption.IGNORE_CASE), - "" - ) + var title = filename.replace(EXTENSION_REGEX, "") + title = title.replace(SEASON_EPISODE_CLEAN_REGEX, "") + title = title.replace(YEAR_SUFFIX_REGEX, "") + title = title.replace(SEPARATORS_REGEX, " ") + title = title.replace(TAGS_REGEX, "") return title.trim() - .replace(Regex("[\\(\\[\\{]\\s*$"), "") - .replace(Regex("[\\s\\-\\.\\(\\)\\[\\]\\{\\}]+$"), "") + .replace(OPEN_BRACKET_END_REGEX, "") + .replace(TRAILING_SYMBOLS_REGEX, "") .trim() } } object Formatters { + private val TRAILING_ZEROS_REGEX = Regex("\\.?0+$") + private val suspectPaths = listOf( "/dcim/", "/camera/", "/whatsapp/", "/snapchat/", "/instagram/", "/telegram/", "/signal/", "/viber/", "/messenger/", "/tiktok/", @@ -65,7 +71,7 @@ object Formatters { val sizes = arrayOf("B", "KB", "MB", "GB", "TB") val i = floor(ln(bytes.toDouble()) / ln(k)).toInt() val value = bytes / k.pow(i.toDouble()) - val formatted = String.format(Locale.US, "%.2f", value).replace(Regex("\\.?0+$"), "") + val formatted = String.format(Locale.US, "%.2f", value).replace(TRAILING_ZEROS_REGEX, "") return "$formatted ${sizes[i]}" } diff --git a/native/app/src/main/java/com/localstream/app/domain/VideoFilterSorter.kt b/native/app/src/main/java/com/localstream/app/domain/VideoFilterSorter.kt index 2138603..66b41fd 100644 --- a/native/app/src/main/java/com/localstream/app/domain/VideoFilterSorter.kt +++ b/native/app/src/main/java/com/localstream/app/domain/VideoFilterSorter.kt @@ -23,6 +23,8 @@ object VideoFilterSorter { } } + private val HD_OR_4K_REGEX = Regex("1080p|720p|2160p|4k", RegexOption.IGNORE_CASE) + private fun filterByResolution(videos: List, opts: FilterSortOptions): List { if (opts.filterResolution == ResolutionFilter.ALL) return videos return videos.filter { v -> @@ -33,7 +35,7 @@ object VideoFilterSorter { ResolutionFilter.TWO_K -> n.contains("1440p") ResolutionFilter.ONE_THOUSAND_EIGHTY_P -> n.contains("1080p") ResolutionFilter.SEVEN_HUNDRED_TWENTY_P -> n.contains("720p") - ResolutionFilter.SD -> !Regex("1080p|720p|2160p|4k", RegexOption.IGNORE_CASE).containsMatchIn(n) + ResolutionFilter.SD -> !HD_OR_4K_REGEX.containsMatchIn(n) ResolutionFilter.ALL -> true } } diff --git a/native/app/src/main/java/com/localstream/app/domain/VideoGrouper.kt b/native/app/src/main/java/com/localstream/app/domain/VideoGrouper.kt index c157e2b..f4c35a5 100644 --- a/native/app/src/main/java/com/localstream/app/domain/VideoGrouper.kt +++ b/native/app/src/main/java/com/localstream/app/domain/VideoGrouper.kt @@ -53,12 +53,15 @@ object VideoGrouper { return video.copy(season = sStr.toIntOrNull(), episode = eStr.toIntOrNull()) } + private val SERIES_SEPARATORS_REGEX = Regex("[\\.\\-_/\\\\\\[\\]\\(\\)]") + private val SERIES_TRAILING_DASH_SPACE_REGEX = Regex("[\\s\\-]+$") + private fun resolveSeriesName(video: VideoItem, match: MatchResult?): String { val sName = video.seriesName ?: if (match != null) { video.name.substring(0, match.range.first) - .replace(Regex("[\\.\\-_/\\\\\\[\\]\\(\\)]"), " ") + .replace(SERIES_SEPARATORS_REGEX, " ") .trim() - .replace(Regex("[\\s\\-]+$"), "") + .replace(SERIES_TRAILING_DASH_SPACE_REGEX, "") } else { TitleCleaner.getCleanTitle(video.name) } diff --git a/native/app/src/main/java/com/localstream/app/domain/VideoNameParser.kt b/native/app/src/main/java/com/localstream/app/domain/VideoNameParser.kt index 9b7188c..3c4ef67 100644 --- a/native/app/src/main/java/com/localstream/app/domain/VideoNameParser.kt +++ b/native/app/src/main/java/com/localstream/app/domain/VideoNameParser.kt @@ -14,12 +14,16 @@ object VideoNameParser { val SEASON_EPISODE_REGEX = Regex("[sS](\\d+)(\\s*)[eE](\\d+)|(\\d+)(\\s*)x(\\d+)") private val SEASON_FOLDER_REGEX = Regex("Saison\\s*(\\d+)|Season\\s*(\\d+)|S(\\d+)", RegexOption.IGNORE_CASE) + private val EXTENSION_DOT_WORD_REGEX = Regex("\\.\\w+$") + private val SERIES_SEPARATORS_REGEX = Regex("[\\.\\-_/\\\\\\[\\]\\(\\)]") + private val SERIES_TRAILING_DASH_SPACE_REGEX = Regex("[\\s\\-]+$") + private val LEADING_DIGITS_REGEX = Regex("^(\\d+)") fun videoBaseName(fileName: String): String = - fileName.replace(Regex("\\.\\w+$"), "").lowercase() + fileName.replace(EXTENSION_DOT_WORD_REGEX, "").lowercase() fun subtitleBaseName(fileName: String): String = - fileName.replace(Regex("\\.\\w+$"), "").replace(LANG_SUFFIX_REGEX, "").lowercase() + fileName.replace(EXTENSION_DOT_WORD_REGEX, "").replace(LANG_SUFFIX_REGEX, "").lowercase() fun parentFolder(relativePath: String): String { val normalized = relativePath.replace('\\', '/').trimEnd('/') @@ -34,9 +38,9 @@ object VideoNameParser { val sStr = if (groupValues[1].isNotEmpty()) groupValues[1] else groupValues[4] val eStr = if (groupValues[3].isNotEmpty()) groupValues[3] else groupValues[6] var seriesName = fileName.substring(0, match.range.first) - .replace(Regex("[\\.\\-_/\\\\\\[\\]\\(\\)]"), " ") + .replace(SERIES_SEPARATORS_REGEX, " ") .trim() - .replace(Regex("[\\s\\-]+$"), "") + .replace(SERIES_TRAILING_DASH_SPACE_REGEX, "") if (seriesName.isEmpty()) seriesName = "Série Inconnue" return SeriesInfo(seriesName = seriesName, season = sStr.toIntOrNull(), episode = eStr.toIntOrNull()) } @@ -53,7 +57,7 @@ object VideoNameParser { sMatch.groupValues[2].ifEmpty { sMatch.groupValues[3] } } val seriesName = if (pathParts.size >= 3) pathParts[pathParts.size - 3] else "Série Inconnue" - val epMatch = Regex("^(\\d+)").find(fileName) + val epMatch = LEADING_DIGITS_REGEX.find(fileName) return SeriesInfo( seriesName = seriesName, season = sVal.toIntOrNull(),