diff --git a/client/components/modals/libraries/EditModal.vue b/client/components/modals/libraries/EditModal.vue
index 5bcdabed..2a68dd63 100644
--- a/client/components/modals/libraries/EditModal.vue
+++ b/client/components/modals/libraries/EditModal.vue
@@ -127,7 +127,7 @@ export default {
skipMatchingMediaWithIsbn: false,
autoScanCronExpression: null,
hideSingleBookSeries: false,
- metadataPrecedence: ['folderStructure', 'audioMetatags', 'txtFiles', 'opfFile', 'absMetadata']
+ metadataPrecedence: ['folderStructure', 'audioMetatags', 'nfoFile', 'txtFiles', 'opfFile', 'absMetadata']
}
}
},
diff --git a/client/components/modals/libraries/LibraryScannerSettings.vue b/client/components/modals/libraries/LibraryScannerSettings.vue
index 215f79b5..8ec73dd0 100644
--- a/client/components/modals/libraries/LibraryScannerSettings.vue
+++ b/client/components/modals/libraries/LibraryScannerSettings.vue
@@ -19,9 +19,11 @@
reorder
- {{ source.include ? index + 1 : '' }}
+ {{ source.include ? getSourceIndex(source.id) : '' }}
+
+
+ {{ source.name }} {{ index === firstActiveSourceIndex ? $strings.LabelHighestPriority : $strings.LabelLowestPriority }}
- {{ source.name }}
@@ -64,6 +66,11 @@ export default {
name: 'Audio file meta tags',
include: true
},
+ nfoFile: {
+ id: 'nfoFile',
+ name: 'NFO file',
+ include: true
+ },
txtFiles: {
id: 'txtFiles',
name: 'desc.txt & reader.txt files',
@@ -92,20 +99,34 @@ export default {
},
isBookLibrary() {
return this.mediaType === 'book'
+ },
+ firstActiveSourceIndex() {
+ return this.metadataSourceMapped.findIndex((source) => source.include)
+ },
+ lastActiveSourceIndex() {
+ return this.metadataSourceMapped.findLastIndex((source) => source.include)
}
},
methods: {
+ getSourceIndex(source) {
+ const activeSources = (this.librarySettings.metadataPrecedence || []).map((s) => s).reverse()
+ return activeSources.findIndex((s) => s === source) + 1
+ },
resetToDefault() {
this.metadataSourceMapped = []
for (const key in this.metadataSourceData) {
this.metadataSourceMapped.push({ ...this.metadataSourceData[key] })
}
+ this.metadataSourceMapped.reverse()
+
this.$emit('update', this.getLibraryData())
},
getLibraryData() {
+ const metadataSourceIds = this.metadataSourceMapped.map((source) => (source.include ? source.id : null)).filter((s) => s)
+ metadataSourceIds.reverse()
return {
settings: {
- metadataPrecedence: this.metadataSourceMapped.map((source) => (source.include ? source.id : null)).filter((s) => s)
+ metadataPrecedence: metadataSourceIds
}
}
},
@@ -120,15 +141,16 @@ export default {
},
init() {
const metadataPrecedence = this.librarySettings.metadataPrecedence || []
-
this.metadataSourceMapped = metadataPrecedence.map((source) => this.metadataSourceData[source]).filter((s) => s)
for (const sourceKey in this.metadataSourceData) {
if (!metadataPrecedence.includes(sourceKey)) {
const unusedSourceData = { ...this.metadataSourceData[sourceKey], include: false }
- this.metadataSourceMapped.push(unusedSourceData)
+ this.metadataSourceMapped.unshift(unusedSourceData)
}
}
+
+ this.metadataSourceMapped.reverse()
}
},
mounted() {
diff --git a/client/strings/cs.json b/client/strings/cs.json
index 26e7bcc9..ba027b92 100644
--- a/client/strings/cs.json
+++ b/client/strings/cs.json
@@ -283,6 +283,7 @@
"LabelHardDeleteFile": "Trvale smazat soubor",
"LabelHasEbook": "Obsahuje elektronickou knihu",
"LabelHasSupplementaryEbook": "Obsahuje doplňkovou elektronickou knihu",
+ "LabelHighestPriority": "Highest priority",
"LabelHost": "Hostitel",
"LabelHour": "Hodina",
"LabelIcon": "Ikona",
@@ -324,11 +325,12 @@
"LabelLogLevelInfo": "Informace",
"LabelLogLevelWarn": "Varovat",
"LabelLookForNewEpisodesAfterDate": "Hledat nové epizody po tomto datu",
+ "LabelLowestPriority": "Lowest Priority",
"LabelMatchExistingUsersBy": "Match existing users by",
"LabelMatchExistingUsersByDescription": "Used for connecting existing users. Once connected, users will be matched by a unique id from your SSO provider",
"LabelMediaPlayer": "Přehrávač médií",
"LabelMediaType": "Typ média",
- "LabelMetadataOrderOfPrecedenceDescription": "1 je nejnižší priorita, 5 je nejvyšší priorita",
+ "LabelMetadataOrderOfPrecedenceDescription": "Higher priority metadata sources will override lower priority metadata sources",
"LabelMetadataProvider": "Poskytovatel metadat",
"LabelMetaTag": "Metaznačka",
"LabelMetaTags": "Metaznačky",
diff --git a/client/strings/da.json b/client/strings/da.json
index d950be16..7ddc787e 100644
--- a/client/strings/da.json
+++ b/client/strings/da.json
@@ -283,6 +283,7 @@
"LabelHardDeleteFile": "Permanent slet fil",
"LabelHasEbook": "Har e-bog",
"LabelHasSupplementaryEbook": "Har supplerende e-bog",
+ "LabelHighestPriority": "Highest priority",
"LabelHost": "Vært",
"LabelHour": "Time",
"LabelIcon": "Ikon",
@@ -324,11 +325,12 @@
"LabelLogLevelInfo": "Information",
"LabelLogLevelWarn": "Advarsel",
"LabelLookForNewEpisodesAfterDate": "Søg efter nye episoder efter denne dato",
+ "LabelLowestPriority": "Lowest Priority",
"LabelMatchExistingUsersBy": "Match existing users by",
"LabelMatchExistingUsersByDescription": "Used for connecting existing users. Once connected, users will be matched by a unique id from your SSO provider",
"LabelMediaPlayer": "Medieafspiller",
"LabelMediaType": "Medietype",
- "LabelMetadataOrderOfPrecedenceDescription": "1 is lowest priority, 5 is highest priority",
+ "LabelMetadataOrderOfPrecedenceDescription": "Higher priority metadata sources will override lower priority metadata sources",
"LabelMetadataProvider": "Metadataudbyder",
"LabelMetaTag": "Meta-tag",
"LabelMetaTags": "Meta-tags",
diff --git a/client/strings/de.json b/client/strings/de.json
index 6e86b3ac..78e64804 100644
--- a/client/strings/de.json
+++ b/client/strings/de.json
@@ -283,6 +283,7 @@
"LabelHardDeleteFile": "Datei dauerhaft löschen",
"LabelHasEbook": "mit E-Book",
"LabelHasSupplementaryEbook": "mit zusätlichem E-Book",
+ "LabelHighestPriority": "Highest priority",
"LabelHost": "Host",
"LabelHour": "Stunde",
"LabelIcon": "Symbol",
@@ -324,11 +325,12 @@
"LabelLogLevelInfo": "Informationen",
"LabelLogLevelWarn": "Warnungen",
"LabelLookForNewEpisodesAfterDate": "Suchen nach neuen Episoden nach diesem Datum",
+ "LabelLowestPriority": "Lowest Priority",
"LabelMatchExistingUsersBy": "Zuordnen existierender Benutzer mit",
"LabelMatchExistingUsersByDescription": "Wird zum Verbinden vorhandener Benutzer verwendet. Sobald die Verbindung hergestellt ist, wird den Benutzern eine eindeutige ID von Ihrem SSO-Anbieter zugeordnet",
"LabelMediaPlayer": "Mediaplayer",
"LabelMediaType": "Medientyp",
- "LabelMetadataOrderOfPrecedenceDescription": "1 ist die niedrigste Priorität, 5 ist die höhste Priorität",
+ "LabelMetadataOrderOfPrecedenceDescription": "Higher priority metadata sources will override lower priority metadata sources",
"LabelMetadataProvider": "Metadatenanbieter",
"LabelMetaTag": "Meta Schlagwort",
"LabelMetaTags": "Meta Tags",
diff --git a/client/strings/en-us.json b/client/strings/en-us.json
index b7896281..8e1f6ce6 100644
--- a/client/strings/en-us.json
+++ b/client/strings/en-us.json
@@ -283,6 +283,7 @@
"LabelHardDeleteFile": "Hard delete file",
"LabelHasEbook": "Has ebook",
"LabelHasSupplementaryEbook": "Has supplementary ebook",
+ "LabelHighestPriority": "Highest priority",
"LabelHost": "Host",
"LabelHour": "Hour",
"LabelIcon": "Icon",
@@ -324,11 +325,12 @@
"LabelLogLevelInfo": "Info",
"LabelLogLevelWarn": "Warn",
"LabelLookForNewEpisodesAfterDate": "Look for new episodes after this date",
+ "LabelLowestPriority": "Lowest Priority",
"LabelMatchExistingUsersBy": "Match existing users by",
"LabelMatchExistingUsersByDescription": "Used for connecting existing users. Once connected, users will be matched by a unique id from your SSO provider",
"LabelMediaPlayer": "Media Player",
"LabelMediaType": "Media Type",
- "LabelMetadataOrderOfPrecedenceDescription": "1 is lowest priority, 5 is highest priority",
+ "LabelMetadataOrderOfPrecedenceDescription": "Higher priority metadata sources will override lower priority metadata sources",
"LabelMetadataProvider": "Metadata Provider",
"LabelMetaTag": "Meta Tag",
"LabelMetaTags": "Meta Tags",
diff --git a/client/strings/es.json b/client/strings/es.json
index fde3782e..bbfbf267 100644
--- a/client/strings/es.json
+++ b/client/strings/es.json
@@ -283,6 +283,7 @@
"LabelHardDeleteFile": "Eliminar Definitivamente",
"LabelHasEbook": "Tiene Ebook",
"LabelHasSupplementaryEbook": "Tiene Ebook Suplementario",
+ "LabelHighestPriority": "Highest priority",
"LabelHost": "Host",
"LabelHour": "Hora",
"LabelIcon": "Icono",
@@ -324,11 +325,12 @@
"LabelLogLevelInfo": "Información",
"LabelLogLevelWarn": "Advertencia",
"LabelLookForNewEpisodesAfterDate": "Buscar Nuevos Episodios a partir de esta Fecha",
+ "LabelLowestPriority": "Lowest Priority",
"LabelMatchExistingUsersBy": "Match existing users by",
"LabelMatchExistingUsersByDescription": "Used for connecting existing users. Once connected, users will be matched by a unique id from your SSO provider",
"LabelMediaPlayer": "Reproductor de Medios",
"LabelMediaType": "Tipo de Multimedia",
- "LabelMetadataOrderOfPrecedenceDescription": "1 is lowest priority, 5 is highest priority",
+ "LabelMetadataOrderOfPrecedenceDescription": "Higher priority metadata sources will override lower priority metadata sources",
"LabelMetadataProvider": "Proveedor de Metadata",
"LabelMetaTag": "Meta Tag",
"LabelMetaTags": "Meta Tags",
diff --git a/client/strings/fr.json b/client/strings/fr.json
index 97d2766e..8999222b 100644
--- a/client/strings/fr.json
+++ b/client/strings/fr.json
@@ -283,6 +283,7 @@
"LabelHardDeleteFile": "Suppression du fichier",
"LabelHasEbook": "Dispose d’un livre numérique",
"LabelHasSupplementaryEbook": "Dispose d’un livre numérique supplémentaire",
+ "LabelHighestPriority": "Highest priority",
"LabelHost": "Hôte",
"LabelHour": "Heure",
"LabelIcon": "Icone",
@@ -324,11 +325,12 @@
"LabelLogLevelInfo": "Info",
"LabelLogLevelWarn": "Warn",
"LabelLookForNewEpisodesAfterDate": "Chercher de nouveaux épisode après cette date",
+ "LabelLowestPriority": "Lowest Priority",
"LabelMatchExistingUsersBy": "Match existing users by",
"LabelMatchExistingUsersByDescription": "Used for connecting existing users. Once connected, users will be matched by a unique id from your SSO provider",
"LabelMediaPlayer": "Lecteur multimédia",
"LabelMediaType": "Type de média",
- "LabelMetadataOrderOfPrecedenceDescription": "1 is lowest priority, 5 is highest priority",
+ "LabelMetadataOrderOfPrecedenceDescription": "Higher priority metadata sources will override lower priority metadata sources",
"LabelMetadataProvider": "Fournisseur de métadonnées",
"LabelMetaTag": "Etiquette de métadonnée",
"LabelMetaTags": "Etiquettes de métadonnée",
diff --git a/client/strings/gu.json b/client/strings/gu.json
index 3fca0367..4318ad5a 100644
--- a/client/strings/gu.json
+++ b/client/strings/gu.json
@@ -283,6 +283,7 @@
"LabelHardDeleteFile": "Hard delete file",
"LabelHasEbook": "Has ebook",
"LabelHasSupplementaryEbook": "Has supplementary ebook",
+ "LabelHighestPriority": "Highest priority",
"LabelHost": "Host",
"LabelHour": "Hour",
"LabelIcon": "Icon",
@@ -324,11 +325,12 @@
"LabelLogLevelInfo": "Info",
"LabelLogLevelWarn": "Warn",
"LabelLookForNewEpisodesAfterDate": "Look for new episodes after this date",
+ "LabelLowestPriority": "Lowest Priority",
"LabelMatchExistingUsersBy": "Match existing users by",
"LabelMatchExistingUsersByDescription": "Used for connecting existing users. Once connected, users will be matched by a unique id from your SSO provider",
"LabelMediaPlayer": "Media Player",
"LabelMediaType": "Media Type",
- "LabelMetadataOrderOfPrecedenceDescription": "1 is lowest priority, 5 is highest priority",
+ "LabelMetadataOrderOfPrecedenceDescription": "Higher priority metadata sources will override lower priority metadata sources",
"LabelMetadataProvider": "Metadata Provider",
"LabelMetaTag": "Meta Tag",
"LabelMetaTags": "Meta Tags",
diff --git a/client/strings/hi.json b/client/strings/hi.json
index 1f35e11a..69244330 100644
--- a/client/strings/hi.json
+++ b/client/strings/hi.json
@@ -283,6 +283,7 @@
"LabelHardDeleteFile": "Hard delete file",
"LabelHasEbook": "Has ebook",
"LabelHasSupplementaryEbook": "Has supplementary ebook",
+ "LabelHighestPriority": "Highest priority",
"LabelHost": "Host",
"LabelHour": "Hour",
"LabelIcon": "Icon",
@@ -324,11 +325,12 @@
"LabelLogLevelInfo": "Info",
"LabelLogLevelWarn": "Warn",
"LabelLookForNewEpisodesAfterDate": "Look for new episodes after this date",
+ "LabelLowestPriority": "Lowest Priority",
"LabelMatchExistingUsersBy": "Match existing users by",
"LabelMatchExistingUsersByDescription": "Used for connecting existing users. Once connected, users will be matched by a unique id from your SSO provider",
"LabelMediaPlayer": "Media Player",
"LabelMediaType": "Media Type",
- "LabelMetadataOrderOfPrecedenceDescription": "1 is lowest priority, 5 is highest priority",
+ "LabelMetadataOrderOfPrecedenceDescription": "Higher priority metadata sources will override lower priority metadata sources",
"LabelMetadataProvider": "Metadata Provider",
"LabelMetaTag": "Meta Tag",
"LabelMetaTags": "Meta Tags",
diff --git a/client/strings/hr.json b/client/strings/hr.json
index 5fe09ab2..2370dc33 100644
--- a/client/strings/hr.json
+++ b/client/strings/hr.json
@@ -283,6 +283,7 @@
"LabelHardDeleteFile": "Obriši datoteku zauvijek",
"LabelHasEbook": "Has ebook",
"LabelHasSupplementaryEbook": "Has supplementary ebook",
+ "LabelHighestPriority": "Highest priority",
"LabelHost": "Host",
"LabelHour": "Sat",
"LabelIcon": "Ikona",
@@ -324,11 +325,12 @@
"LabelLogLevelInfo": "Info",
"LabelLogLevelWarn": "Warn",
"LabelLookForNewEpisodesAfterDate": "Traži nove epizode nakon ovog datuma",
+ "LabelLowestPriority": "Lowest Priority",
"LabelMatchExistingUsersBy": "Match existing users by",
"LabelMatchExistingUsersByDescription": "Used for connecting existing users. Once connected, users will be matched by a unique id from your SSO provider",
"LabelMediaPlayer": "Media Player",
"LabelMediaType": "Media Type",
- "LabelMetadataOrderOfPrecedenceDescription": "1 is lowest priority, 5 is highest priority",
+ "LabelMetadataOrderOfPrecedenceDescription": "Higher priority metadata sources will override lower priority metadata sources",
"LabelMetadataProvider": "Poslužitelj metapodataka ",
"LabelMetaTag": "Meta Tag",
"LabelMetaTags": "Meta Tags",
diff --git a/client/strings/it.json b/client/strings/it.json
index 881cc19b..3f86bf2e 100644
--- a/client/strings/it.json
+++ b/client/strings/it.json
@@ -283,6 +283,7 @@
"LabelHardDeleteFile": "Elimina Definitivamente",
"LabelHasEbook": "Un ebook",
"LabelHasSupplementaryEbook": "Un ebook Supplementare",
+ "LabelHighestPriority": "Highest priority",
"LabelHost": "Host",
"LabelHour": "Ora",
"LabelIcon": "Icona",
@@ -324,11 +325,12 @@
"LabelLogLevelInfo": "Info",
"LabelLogLevelWarn": "Allarme",
"LabelLookForNewEpisodesAfterDate": "Cerca nuovi episodi dopo questa data",
+ "LabelLowestPriority": "Lowest Priority",
"LabelMatchExistingUsersBy": "Match existing users by",
"LabelMatchExistingUsersByDescription": "Used for connecting existing users. Once connected, users will be matched by a unique id from your SSO provider",
"LabelMediaPlayer": "Media Player",
"LabelMediaType": "Tipo Media",
- "LabelMetadataOrderOfPrecedenceDescription": "1 e bassa priorità, 5 è alta priorità",
+ "LabelMetadataOrderOfPrecedenceDescription": "Higher priority metadata sources will override lower priority metadata sources",
"LabelMetadataProvider": "Metadata Provider",
"LabelMetaTag": "Meta Tag",
"LabelMetaTags": "Meta Tags",
diff --git a/client/strings/lt.json b/client/strings/lt.json
index 00d3aeed..ae54e8bf 100644
--- a/client/strings/lt.json
+++ b/client/strings/lt.json
@@ -283,6 +283,7 @@
"LabelHardDeleteFile": "Galutinai ištrinti failą",
"LabelHasEbook": "Turi e-knygą",
"LabelHasSupplementaryEbook": "Turi papildomą e-knygą",
+ "LabelHighestPriority": "Highest priority",
"LabelHost": "Serveris",
"LabelHour": "Valanda",
"LabelIcon": "Piktograma",
@@ -324,11 +325,12 @@
"LabelLogLevelInfo": "Info",
"LabelLogLevelWarn": "Warn",
"LabelLookForNewEpisodesAfterDate": "Ieškoti naujų epizodų po šios datos",
+ "LabelLowestPriority": "Lowest Priority",
"LabelMatchExistingUsersBy": "Match existing users by",
"LabelMatchExistingUsersByDescription": "Used for connecting existing users. Once connected, users will be matched by a unique id from your SSO provider",
"LabelMediaPlayer": "Grotuvas",
"LabelMediaType": "Medijos tipas",
- "LabelMetadataOrderOfPrecedenceDescription": "1 is lowest priority, 5 is highest priority",
+ "LabelMetadataOrderOfPrecedenceDescription": "Higher priority metadata sources will override lower priority metadata sources",
"LabelMetadataProvider": "Metaduomenų tiekėjas",
"LabelMetaTag": "Meta žymė",
"LabelMetaTags": "Meta žymos",
diff --git a/client/strings/nl.json b/client/strings/nl.json
index 1421368b..d9399455 100644
--- a/client/strings/nl.json
+++ b/client/strings/nl.json
@@ -283,6 +283,7 @@
"LabelHardDeleteFile": "Hard-delete bestand",
"LabelHasEbook": "Heeft ebook",
"LabelHasSupplementaryEbook": "Heeft supplementair ebook",
+ "LabelHighestPriority": "Highest priority",
"LabelHost": "Host",
"LabelHour": "Uur",
"LabelIcon": "Icoon",
@@ -324,11 +325,12 @@
"LabelLogLevelInfo": "Info",
"LabelLogLevelWarn": "Waarschuwing",
"LabelLookForNewEpisodesAfterDate": "Zoek naar nieuwe afleveringen na deze datum",
+ "LabelLowestPriority": "Lowest Priority",
"LabelMatchExistingUsersBy": "Match existing users by",
"LabelMatchExistingUsersByDescription": "Used for connecting existing users. Once connected, users will be matched by a unique id from your SSO provider",
"LabelMediaPlayer": "Mediaspeler",
"LabelMediaType": "Mediatype",
- "LabelMetadataOrderOfPrecedenceDescription": "1 is lowest priority, 5 is highest priority",
+ "LabelMetadataOrderOfPrecedenceDescription": "Higher priority metadata sources will override lower priority metadata sources",
"LabelMetadataProvider": "Metadatabron",
"LabelMetaTag": "Meta-tag",
"LabelMetaTags": "Meta-tags",
diff --git a/client/strings/no.json b/client/strings/no.json
index 7d2acf3b..b4541229 100644
--- a/client/strings/no.json
+++ b/client/strings/no.json
@@ -283,6 +283,7 @@
"LabelHardDeleteFile": "Tving sletting av fil",
"LabelHasEbook": "Har ebok",
"LabelHasSupplementaryEbook": "Har supplerende ebok",
+ "LabelHighestPriority": "Highest priority",
"LabelHost": "Tjener",
"LabelHour": "Time",
"LabelIcon": "Ikon",
@@ -324,11 +325,12 @@
"LabelLogLevelInfo": "Info",
"LabelLogLevelWarn": "Warn",
"LabelLookForNewEpisodesAfterDate": "Se etter nye episoder etter denne datoen",
+ "LabelLowestPriority": "Lowest Priority",
"LabelMatchExistingUsersBy": "Match existing users by",
"LabelMatchExistingUsersByDescription": "Used for connecting existing users. Once connected, users will be matched by a unique id from your SSO provider",
"LabelMediaPlayer": "Mediespiller",
"LabelMediaType": "Medie type",
- "LabelMetadataOrderOfPrecedenceDescription": "1 is lowest priority, 5 is highest priority",
+ "LabelMetadataOrderOfPrecedenceDescription": "Higher priority metadata sources will override lower priority metadata sources",
"LabelMetadataProvider": "Metadata Leverandør",
"LabelMetaTag": "Meta Tag",
"LabelMetaTags": "Meta Tags",
diff --git a/client/strings/pl.json b/client/strings/pl.json
index b0521f0a..51587fed 100644
--- a/client/strings/pl.json
+++ b/client/strings/pl.json
@@ -283,6 +283,7 @@
"LabelHardDeleteFile": "Usuń trwale plik",
"LabelHasEbook": "Has ebook",
"LabelHasSupplementaryEbook": "Has supplementary ebook",
+ "LabelHighestPriority": "Highest priority",
"LabelHost": "Host",
"LabelHour": "Godzina",
"LabelIcon": "Ikona",
@@ -324,11 +325,12 @@
"LabelLogLevelInfo": "Informacja",
"LabelLogLevelWarn": "Ostrzeżenie",
"LabelLookForNewEpisodesAfterDate": "Szukaj nowych odcinków po dacie",
+ "LabelLowestPriority": "Lowest Priority",
"LabelMatchExistingUsersBy": "Match existing users by",
"LabelMatchExistingUsersByDescription": "Used for connecting existing users. Once connected, users will be matched by a unique id from your SSO provider",
"LabelMediaPlayer": "Odtwarzacz",
"LabelMediaType": "Typ mediów",
- "LabelMetadataOrderOfPrecedenceDescription": "1 is lowest priority, 5 is highest priority",
+ "LabelMetadataOrderOfPrecedenceDescription": "Higher priority metadata sources will override lower priority metadata sources",
"LabelMetadataProvider": "Dostawca metadanych",
"LabelMetaTag": "Tag",
"LabelMetaTags": "Meta Tags",
diff --git a/client/strings/ru.json b/client/strings/ru.json
index 851d2ba3..c0fd0cf7 100644
--- a/client/strings/ru.json
+++ b/client/strings/ru.json
@@ -283,6 +283,7 @@
"LabelHardDeleteFile": "Жесткое удаление файла",
"LabelHasEbook": "Есть e-книга",
"LabelHasSupplementaryEbook": "Есть дополнительная e-книга",
+ "LabelHighestPriority": "Highest priority",
"LabelHost": "Хост",
"LabelHour": "Часы",
"LabelIcon": "Иконка",
@@ -324,11 +325,12 @@
"LabelLogLevelInfo": "Info",
"LabelLogLevelWarn": "Warn",
"LabelLookForNewEpisodesAfterDate": "Искать новые эпизоды после этой даты",
+ "LabelLowestPriority": "Lowest Priority",
"LabelMatchExistingUsersBy": "Match existing users by",
"LabelMatchExistingUsersByDescription": "Used for connecting existing users. Once connected, users will be matched by a unique id from your SSO provider",
"LabelMediaPlayer": "Медиа проигрыватель",
"LabelMediaType": "Тип медиа",
- "LabelMetadataOrderOfPrecedenceDescription": "1 is lowest priority, 5 is highest priority",
+ "LabelMetadataOrderOfPrecedenceDescription": "Higher priority metadata sources will override lower priority metadata sources",
"LabelMetadataProvider": "Провайдер",
"LabelMetaTag": "Мета тег",
"LabelMetaTags": "Мета теги",
diff --git a/client/strings/sv.json b/client/strings/sv.json
index eea30043..6bb0eec2 100644
--- a/client/strings/sv.json
+++ b/client/strings/sv.json
@@ -283,6 +283,7 @@
"LabelHardDeleteFile": "Hård radering av fil",
"LabelHasEbook": "Har e-bok",
"LabelHasSupplementaryEbook": "Har kompletterande e-bok",
+ "LabelHighestPriority": "Highest priority",
"LabelHost": "Värd",
"LabelHour": "Timme",
"LabelIcon": "Ikon",
@@ -324,11 +325,12 @@
"LabelLogLevelInfo": "Felsökningsnivå: Information",
"LabelLogLevelWarn": "Felsökningsnivå: Varning",
"LabelLookForNewEpisodesAfterDate": "Sök efter nya avsnitt efter detta datum",
+ "LabelLowestPriority": "Lowest Priority",
"LabelMatchExistingUsersBy": "Match existing users by",
"LabelMatchExistingUsersByDescription": "Used for connecting existing users. Once connected, users will be matched by a unique id from your SSO provider",
"LabelMediaPlayer": "Mediaspelare",
"LabelMediaType": "Mediatyp",
- "LabelMetadataOrderOfPrecedenceDescription": "1 är lägsta prioritet, 5 är högsta prioritet",
+ "LabelMetadataOrderOfPrecedenceDescription": "Higher priority metadata sources will override lower priority metadata sources",
"LabelMetadataProvider": "Metadataleverantör",
"LabelMetaTag": "Metamärke",
"LabelMetaTags": "Metamärken",
diff --git a/client/strings/zh-cn.json b/client/strings/zh-cn.json
index 8bb242a4..2f20a42e 100644
--- a/client/strings/zh-cn.json
+++ b/client/strings/zh-cn.json
@@ -283,6 +283,7 @@
"LabelHardDeleteFile": "完全删除文件",
"LabelHasEbook": "有电子书",
"LabelHasSupplementaryEbook": "有补充电子书",
+ "LabelHighestPriority": "Highest priority",
"LabelHost": "主机",
"LabelHour": "小时",
"LabelIcon": "图标",
@@ -324,11 +325,12 @@
"LabelLogLevelInfo": "信息",
"LabelLogLevelWarn": "警告",
"LabelLookForNewEpisodesAfterDate": "在此日期后查找新剧集",
+ "LabelLowestPriority": "Lowest Priority",
"LabelMatchExistingUsersBy": "Match existing users by",
"LabelMatchExistingUsersByDescription": "Used for connecting existing users. Once connected, users will be matched by a unique id from your SSO provider",
"LabelMediaPlayer": "媒体播放器",
"LabelMediaType": "媒体类型",
- "LabelMetadataOrderOfPrecedenceDescription": "1 is lowest priority, 5 is highest priority",
+ "LabelMetadataOrderOfPrecedenceDescription": "Higher priority metadata sources will override lower priority metadata sources",
"LabelMetadataProvider": "元数据提供者",
"LabelMetaTag": "元数据标签",
"LabelMetaTags": "元标签",
diff --git a/server/objects/settings/LibrarySettings.js b/server/objects/settings/LibrarySettings.js
index b734b6bf..10ee19e0 100644
--- a/server/objects/settings/LibrarySettings.js
+++ b/server/objects/settings/LibrarySettings.js
@@ -9,7 +9,7 @@ class LibrarySettings {
this.autoScanCronExpression = null
this.audiobooksOnly = false
this.hideSingleBookSeries = false // Do not show series that only have 1 book
- this.metadataPrecedence = ['folderStructure', 'audioMetatags', 'txtFiles', 'opfFile', 'absMetadata']
+ this.metadataPrecedence = ['folderStructure', 'audioMetatags', 'nfoFile', 'txtFiles', 'opfFile', 'absMetadata']
if (settings) {
this.construct(settings)
@@ -28,7 +28,7 @@ class LibrarySettings {
this.metadataPrecedence = [...settings.metadataPrecedence]
} else {
// Added in v2.4.5
- this.metadataPrecedence = ['folderStructure', 'audioMetatags', 'txtFiles', 'opfFile', 'absMetadata']
+ this.metadataPrecedence = ['folderStructure', 'audioMetatags', 'nfoFile', 'txtFiles', 'opfFile', 'absMetadata']
}
}
diff --git a/server/scanner/BookScanner.js b/server/scanner/BookScanner.js
index 282155f2..48e8529a 100644
--- a/server/scanner/BookScanner.js
+++ b/server/scanner/BookScanner.js
@@ -18,6 +18,7 @@ const BookFinder = require('../finders/BookFinder')
const LibraryScan = require("./LibraryScan")
const OpfFileScanner = require('./OpfFileScanner')
+const NfoFileScanner = require('./NfoFileScanner')
const AbsMetadataFileScanner = require('./AbsMetadataFileScanner')
/**
@@ -593,7 +594,7 @@ class BookScanner {
}
const bookMetadataSourceHandler = new BookScanner.BookMetadataSourceHandler(bookMetadata, audioFiles, libraryItemData, libraryScan, existingLibraryItemId)
- const metadataPrecedence = librarySettings.metadataPrecedence || ['folderStructure', 'audioMetatags', 'txtFiles', 'opfFile', 'absMetadata']
+ const metadataPrecedence = librarySettings.metadataPrecedence || ['folderStructure', 'audioMetatags', 'nfoFile', 'txtFiles', 'opfFile', 'absMetadata']
libraryScan.addLog(LogLevel.DEBUG, `"${bookMetadata.title}" Getting metadata with precedence [${metadataPrecedence.join(', ')}]`)
for (const metadataSource of metadataPrecedence) {
if (bookMetadataSourceHandler[metadataSource]) {
@@ -649,6 +650,14 @@ class BookScanner {
AudioFileScanner.setBookMetadataFromAudioMetaTags(bookTitle, this.audioFiles, this.bookMetadata, this.libraryScan)
}
+ /**
+ * Metadata from .nfo file
+ */
+ async nfoFile() {
+ if (!this.libraryItemData.metadataNfoLibraryFile) return
+ await NfoFileScanner.scanBookNfoFile(this.libraryItemData.metadataNfoLibraryFile, this.bookMetadata)
+ }
+
/**
* Description from desc.txt and narrator from reader.txt
*/
diff --git a/server/scanner/LibraryItemScanData.js b/server/scanner/LibraryItemScanData.js
index 576280c8..b604e4d7 100644
--- a/server/scanner/LibraryItemScanData.js
+++ b/server/scanner/LibraryItemScanData.js
@@ -132,6 +132,11 @@ class LibraryItemScanData {
return this.libraryFiles.find(lf => lf.metadata.ext.toLowerCase() === '.opf')
}
+ /** @type {LibraryItem.LibraryFileObject} */
+ get metadataNfoLibraryFile() {
+ return this.libraryFiles.find(lf => lf.metadata.ext.toLowerCase() === '.nfo')
+ }
+
/**
*
* @param {LibraryItem} existingLibraryItem
diff --git a/server/scanner/NfoFileScanner.js b/server/scanner/NfoFileScanner.js
new file mode 100644
index 00000000..e450b5c3
--- /dev/null
+++ b/server/scanner/NfoFileScanner.js
@@ -0,0 +1,48 @@
+const { parseNfoMetadata } = require('../utils/parsers/parseNfoMetadata')
+const { readTextFile } = require('../utils/fileUtils')
+
+class NfoFileScanner {
+ constructor() { }
+
+ /**
+ * Parse metadata from .nfo file found in library scan and update bookMetadata
+ *
+ * @param {import('../models/LibraryItem').LibraryFileObject} nfoLibraryFileObj
+ * @param {Object} bookMetadata
+ */
+ async scanBookNfoFile(nfoLibraryFileObj, bookMetadata) {
+ const nfoText = await readTextFile(nfoLibraryFileObj.metadata.path)
+ const nfoMetadata = nfoText ? await parseNfoMetadata(nfoText) : null
+ if (nfoMetadata) {
+ for (const key in nfoMetadata) {
+ if (key === 'tags') { // Add tags only if tags are empty
+ if (nfoMetadata.tags.length) {
+ bookMetadata.tags = nfoMetadata.tags
+ }
+ } else if (key === 'genres') { // Add genres only if genres are empty
+ if (nfoMetadata.genres.length) {
+ bookMetadata.genres = nfoMetadata.genres
+ }
+ } else if (key === 'authors') {
+ if (nfoMetadata.authors?.length) {
+ bookMetadata.authors = nfoMetadata.authors
+ }
+ } else if (key === 'narrators') {
+ if (nfoMetadata.narrators?.length) {
+ bookMetadata.narrators = nfoMetadata.narrators
+ }
+ } else if (key === 'series') {
+ if (nfoMetadata.series) {
+ bookMetadata.series = [{
+ name: nfoMetadata.series,
+ sequence: nfoMetadata.sequence || null
+ }]
+ }
+ } else if (nfoMetadata[key] && key !== 'sequence') {
+ bookMetadata[key] = nfoMetadata[key]
+ }
+ }
+ }
+ }
+}
+module.exports = new NfoFileScanner()
\ No newline at end of file
diff --git a/server/utils/parsers/parseNfoMetadata.js b/server/utils/parsers/parseNfoMetadata.js
new file mode 100644
index 00000000..56e9400a
--- /dev/null
+++ b/server/utils/parsers/parseNfoMetadata.js
@@ -0,0 +1,100 @@
+function parseNfoMetadata(nfoText) {
+ if (!nfoText) return null
+ const lines = nfoText.split(/\r?\n/)
+ const metadata = {}
+ let insideBookDescription = false
+ lines.forEach(line => {
+ if (line.search(/^\s*book description\s*$/i) !== -1) {
+ insideBookDescription = true
+ return
+ }
+ if (insideBookDescription) {
+ if (line.search(/^\s*=+\s*$/i) !== -1) return
+ metadata.description = metadata.description || ''
+ metadata.description += line + '\n'
+ return
+ }
+ const match = line.match(/^(.*?):(.*)$/)
+ if (match) {
+ const key = match[1].toLowerCase().trim()
+ const value = match[2].trim()
+ if (!value) return
+ switch (key) {
+ case 'title':
+ {
+ const titleMatch = value.match(/^(.*?):(.*)$/)
+ if (titleMatch) {
+ metadata.title = titleMatch[1].trim()
+ metadata.subtitle = titleMatch[2].trim()
+ } else {
+ metadata.title = value
+ }
+ }
+ break
+ case 'author':
+ metadata.authors = value.split(/\s*,\s*/).filter(v => v)
+ break
+ case 'narrator':
+ case 'read by':
+ metadata.narrators = value.split(/\s*,\s*/).filter(v => v)
+ break
+ case 'series name':
+ metadata.series = value
+ break
+ case 'genre':
+ metadata.genres = value.split(/\s*,\s*/).filter(v => v)
+ break
+ case 'tags':
+ metadata.tags = value.split(/\s*,\s*/).filter(v => v)
+ break
+ case 'copyright':
+ case 'audible.com release':
+ case 'audiobook copyright':
+ case 'book copyright':
+ case 'recording copyright':
+ case 'release date':
+ case 'date':
+ {
+ const year = extractYear(value)
+ if (year) {
+ metadata.publishedYear = year
+ }
+ }
+ break
+ case 'position in series':
+ metadata.sequence = value
+ break
+ case 'unabridged':
+ metadata.abridged = value.toLowerCase() === 'yes' ? false : true
+ break
+ case 'abridged':
+ metadata.abridged = value.toLowerCase() === 'no' ? false : true
+ break
+ case 'publisher':
+ metadata.publisher = value
+ break
+ case 'asin':
+ metadata.asin = value
+ break
+ case 'isbn':
+ case 'isbn-10':
+ case 'isbn-13':
+ metadata.isbn = value
+ break
+ }
+ }
+ })
+
+ // Trim leading/trailing whitespace for description
+ if (metadata.description) {
+ metadata.description = metadata.description.trim()
+ }
+
+ return metadata
+}
+module.exports = { parseNfoMetadata }
+
+function extractYear(str) {
+ const match = str.match(/\d{4}/g)
+ return match ? match[match.length - 1] : null
+}
\ No newline at end of file
diff --git a/test/server/utils/parsers/parseNfoMetadata.test.js b/test/server/utils/parsers/parseNfoMetadata.test.js
new file mode 100644
index 00000000..70e6a096
--- /dev/null
+++ b/test/server/utils/parsers/parseNfoMetadata.test.js
@@ -0,0 +1,123 @@
+const chai = require('chai')
+const expect = chai.expect
+const { parseNfoMetadata } = require('../../../../server/utils/parsers/parseNfoMetadata')
+
+describe('parseNfoMetadata', () => {
+ it('returns null if nfoText is empty', () => {
+ const result = parseNfoMetadata('')
+ expect(result).to.be.null
+ })
+
+ it('parses title', () => {
+ const nfoText = 'Title: The Great Gatsby'
+ const result = parseNfoMetadata(nfoText)
+ expect(result.title).to.equal('The Great Gatsby')
+ })
+
+ it('parses title with subtitle', () => {
+ const nfoText = 'Title: The Great Gatsby: A Novel'
+ const result = parseNfoMetadata(nfoText)
+ expect(result.title).to.equal('The Great Gatsby')
+ expect(result.subtitle).to.equal('A Novel')
+ })
+
+ it('parses authors', () => {
+ const nfoText = 'Author: F. Scott Fitzgerald'
+ const result = parseNfoMetadata(nfoText)
+ expect(result.authors).to.deep.equal(['F. Scott Fitzgerald'])
+ })
+
+ it('parses multiple authors', () => {
+ const nfoText = 'Author: John Steinbeck, Ernest Hemingway'
+ const result = parseNfoMetadata(nfoText)
+ expect(result.authors).to.deep.equal(['John Steinbeck', 'Ernest Hemingway'])
+ })
+
+ it('parses narrators', () => {
+ const nfoText = 'Read by: Jake Gyllenhaal'
+ const result = parseNfoMetadata(nfoText)
+ expect(result.narrators).to.deep.equal(['Jake Gyllenhaal'])
+ })
+
+ it('parses multiple narrators', () => {
+ const nfoText = 'Read by: Jake Gyllenhaal, Kate Winslet'
+ const result = parseNfoMetadata(nfoText)
+ expect(result.narrators).to.deep.equal(['Jake Gyllenhaal', 'Kate Winslet'])
+ })
+
+ it('parses series name', () => {
+ const nfoText = 'Series Name: Harry Potter'
+ const result = parseNfoMetadata(nfoText)
+ expect(result.series).to.equal('Harry Potter')
+ })
+
+ it('parses genre', () => {
+ const nfoText = 'Genre: Fiction'
+ const result = parseNfoMetadata(nfoText)
+ expect(result.genres).to.deep.equal(['Fiction'])
+ })
+
+ it('parses multiple genres', () => {
+ const nfoText = 'Genre: Fiction, Historical'
+ const result = parseNfoMetadata(nfoText)
+ expect(result.genres).to.deep.equal(['Fiction', 'Historical'])
+ })
+
+ it('parses tags', () => {
+ const nfoText = 'Tags: mystery, thriller'
+ const result = parseNfoMetadata(nfoText)
+ expect(result.tags).to.deep.equal(['mystery', 'thriller'])
+ })
+
+ it('parses year from various date fields', () => {
+ const nfoText = 'Release Date: 2021-05-01\nBook Copyright: 2021\nRecording Copyright: 2021'
+ const result = parseNfoMetadata(nfoText)
+ expect(result.publishedYear).to.equal('2021')
+ })
+
+ it('parses position in series', () => {
+ const nfoText = 'Position in Series: 2'
+ const result = parseNfoMetadata(nfoText)
+ expect(result.sequence).to.equal('2')
+ })
+
+ it('parses abridged flag', () => {
+ const nfoText = 'Abridged: No'
+ const result = parseNfoMetadata(nfoText)
+ expect(result.abridged).to.be.false
+
+ const nfoText2 = 'Unabridged: Yes'
+ const result2 = parseNfoMetadata(nfoText2)
+ expect(result2.abridged).to.be.false
+ })
+
+ it('parses publisher', () => {
+ const nfoText = 'Publisher: Penguin Random House'
+ const result = parseNfoMetadata(nfoText)
+ expect(result.publisher).to.equal('Penguin Random House')
+ })
+
+ it('parses ASIN', () => {
+ const nfoText = 'ASIN: B08X5JZJLH'
+ const result = parseNfoMetadata(nfoText)
+ expect(result.asin).to.equal('B08X5JZJLH')
+ })
+
+ it('parses description', () => {
+ const nfoText = 'Book Description\n=========\nThis is a book.\n It\'s good'
+ const result = parseNfoMetadata(nfoText)
+ expect(result.description).to.equal('This is a book.\n It\'s good')
+ })
+
+ it('no value', () => {
+ const nfoText = 'Title:'
+ const result = parseNfoMetadata(nfoText)
+ expect(result.title).to.be.undefined
+ })
+
+ it('no year value', () => {
+ const nfoText = "Date:0"
+ const result = parseNfoMetadata(nfoText)
+ expect(result.publishedYear).to.be.undefined
+ })
+})
\ No newline at end of file