diff --git a/app/src/main/frontend/components/administration/LibraryManagement.tsx b/app/src/main/frontend/components/administration/LibraryManagement.tsx
index 14da06f..ee6485d 100644
--- a/app/src/main/frontend/components/administration/LibraryManagement.tsx
+++ b/app/src/main/frontend/components/administration/LibraryManagement.tsx
@@ -45,8 +45,11 @@ function LibraryManagementLayout({getConfig, formik}: any) {
-
+
+
+
@@ -95,6 +98,14 @@ const validationSchema = Yup.object({
// @ts-ignore
schedule: Yup.string().cron()
})
+ }),
+ scan: Yup.object({
+ "extract-title-using-regex": Yup.boolean(),
+ "title-extraction-regex": Yup.string().when("extract-title-using-regex", {
+ is: true,
+ then: (schema) => schema.trim().required("Title extraction regex is required when enabled")
+ }),
+ "title-match-min-ratio": Yup.number().min(1, "Must be between 1-100").max(100, "Must be between 1-100")
})
})
});
diff --git a/app/src/main/kotlin/org/gameyfin/app/config/ConfigProperties.kt b/app/src/main/kotlin/org/gameyfin/app/config/ConfigProperties.kt
index 3f90350..c5caccb 100644
--- a/app/src/main/kotlin/org/gameyfin/app/config/ConfigProperties.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/config/ConfigProperties.kt
@@ -36,6 +36,20 @@ sealed class ConfigProperties(
false
)
+ data object ExtractTitleUsingRegex : ConfigProperties(
+ Boolean::class,
+ "library.scan.extract-title-using-regex",
+ "Extract title from file names using regex",
+ false
+ )
+
+ data object TitleExtractionRegex : ConfigProperties(
+ String::class,
+ "library.scan.title-extraction-regex",
+ "Regex to extract title from file names",
+ "^[^\\[]+"
+ )
+
data object TitleMatchMinRatio : ConfigProperties(
Int::class,
"library.scan.title-match-min-ratio",
diff --git a/app/src/main/kotlin/org/gameyfin/app/games/GameService.kt b/app/src/main/kotlin/org/gameyfin/app/games/GameService.kt
index 88cbef1..067628b 100644
--- a/app/src/main/kotlin/org/gameyfin/app/games/GameService.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/games/GameService.kt
@@ -603,7 +603,26 @@ class GameService(
}
fun matchFromFile(path: Path, library: Library): Game? {
- val query = FilenameUtils.removeExtension(path.fileName.toString())
+ var query = FilenameUtils.removeExtension(path.fileName.toString())
+
+ // (Optional) Step -1: Extract title from filename using regex
+ if (config.get(ConfigProperties.Libraries.Scan.ExtractTitleUsingRegex) == true) {
+ val regexString = config.get(ConfigProperties.Libraries.Scan.TitleExtractionRegex)
+ if (regexString != null && regexString.isNotEmpty()) {
+ try {
+ val regex = Regex(regexString)
+ val originalQuery = query
+ query = regex.find(query)?.value?.trim() ?: query.also {
+ log.warn { "No match found for regex '$regexString' in filename '$query'. Using full filename." }
+ }
+ log.debug { "Extracted title '$query' from filename '$originalQuery'" }
+ } catch (_: Exception) {
+ log.error { "Title extraction regex ($regexString) is invalid, using fill filename." }
+ }
+ } else {
+ log.warn { "No regex configured for title extraction, using full filename '$query'" }
+ }
+ }
// Step 0: Query all metadata plugins for metadata on the provided game title
val metadataResults = queryPlugins(query)