mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-02-16 02:09:33 +01:00
Merge branch 'advplyr:master' into bookfinder-improvements
This commit is contained in:
commit
3c6d6bf688
@ -338,9 +338,15 @@ export default {
|
|||||||
libraryItemsAdded(libraryItems) {
|
libraryItemsAdded(libraryItems) {
|
||||||
console.log('libraryItems added', libraryItems)
|
console.log('libraryItems added', libraryItems)
|
||||||
|
|
||||||
const isThisLibrary = !libraryItems.some((li) => li.libraryId !== this.currentLibraryId)
|
const recentlyAddedShelf = this.shelves.find((shelf) => shelf.id === 'recently-added')
|
||||||
if (!this.search && isThisLibrary) {
|
if (!recentlyAddedShelf) return
|
||||||
this.fetchCategories()
|
|
||||||
|
// Add new library item to the recently added shelf
|
||||||
|
for (const libraryItem of libraryItems) {
|
||||||
|
if (libraryItem.libraryId === this.currentLibraryId && !recentlyAddedShelf.entities.some((ent) => ent.id === libraryItem.id)) {
|
||||||
|
// Add to front of array
|
||||||
|
recentlyAddedShelf.entities.unshift(libraryItem)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
libraryItemsUpdated(items) {
|
libraryItemsUpdated(items) {
|
||||||
|
@ -127,7 +127,7 @@ export default {
|
|||||||
skipMatchingMediaWithIsbn: false,
|
skipMatchingMediaWithIsbn: false,
|
||||||
autoScanCronExpression: null,
|
autoScanCronExpression: null,
|
||||||
hideSingleBookSeries: false,
|
hideSingleBookSeries: false,
|
||||||
metadataPrecedence: ['folderStructure', 'audioMetatags', 'txtFiles', 'opfFile', 'absMetadata']
|
metadataPrecedence: ['folderStructure', 'audioMetatags', 'nfoFile', 'txtFiles', 'opfFile', 'absMetadata']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -19,9 +19,11 @@
|
|||||||
<li v-for="(source, index) in metadataSourceMapped" :key="source.id" :class="source.include ? 'item' : 'opacity-50'" class="w-full px-2 flex items-center relative border border-white/10">
|
<li v-for="(source, index) in metadataSourceMapped" :key="source.id" :class="source.include ? 'item' : 'opacity-50'" class="w-full px-2 flex items-center relative border border-white/10">
|
||||||
<span class="material-icons drag-handle text-xl text-gray-400 hover:text-gray-50 mr-2 md:mr-4">reorder</span>
|
<span class="material-icons drag-handle text-xl text-gray-400 hover:text-gray-50 mr-2 md:mr-4">reorder</span>
|
||||||
<div class="text-center py-1 w-8 min-w-8">
|
<div class="text-center py-1 w-8 min-w-8">
|
||||||
{{ source.include ? index + 1 : '' }}
|
{{ source.include ? getSourceIndex(source.id) : '' }}
|
||||||
|
</div>
|
||||||
|
<div class="flex-grow inline-flex justify-between px-4 py-3">
|
||||||
|
{{ source.name }} <span v-if="source.include && (index === firstActiveSourceIndex || index === lastActiveSourceIndex)" class="px-2 italic font-semibold text-xs text-gray-400">{{ index === firstActiveSourceIndex ? $strings.LabelHighestPriority : $strings.LabelLowestPriority }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-grow px-4 py-3">{{ source.name }}</div>
|
|
||||||
<div class="px-2 opacity-100">
|
<div class="px-2 opacity-100">
|
||||||
<ui-toggle-switch v-model="source.include" :off-color="'error'" @input="includeToggled(source)" />
|
<ui-toggle-switch v-model="source.include" :off-color="'error'" @input="includeToggled(source)" />
|
||||||
</div>
|
</div>
|
||||||
@ -64,6 +66,11 @@ export default {
|
|||||||
name: 'Audio file meta tags',
|
name: 'Audio file meta tags',
|
||||||
include: true
|
include: true
|
||||||
},
|
},
|
||||||
|
nfoFile: {
|
||||||
|
id: 'nfoFile',
|
||||||
|
name: 'NFO file',
|
||||||
|
include: true
|
||||||
|
},
|
||||||
txtFiles: {
|
txtFiles: {
|
||||||
id: 'txtFiles',
|
id: 'txtFiles',
|
||||||
name: 'desc.txt & reader.txt files',
|
name: 'desc.txt & reader.txt files',
|
||||||
@ -92,20 +99,34 @@ export default {
|
|||||||
},
|
},
|
||||||
isBookLibrary() {
|
isBookLibrary() {
|
||||||
return this.mediaType === 'book'
|
return this.mediaType === 'book'
|
||||||
|
},
|
||||||
|
firstActiveSourceIndex() {
|
||||||
|
return this.metadataSourceMapped.findIndex((source) => source.include)
|
||||||
|
},
|
||||||
|
lastActiveSourceIndex() {
|
||||||
|
return this.metadataSourceMapped.findLastIndex((source) => source.include)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
getSourceIndex(source) {
|
||||||
|
const activeSources = (this.librarySettings.metadataPrecedence || []).map((s) => s).reverse()
|
||||||
|
return activeSources.findIndex((s) => s === source) + 1
|
||||||
|
},
|
||||||
resetToDefault() {
|
resetToDefault() {
|
||||||
this.metadataSourceMapped = []
|
this.metadataSourceMapped = []
|
||||||
for (const key in this.metadataSourceData) {
|
for (const key in this.metadataSourceData) {
|
||||||
this.metadataSourceMapped.push({ ...this.metadataSourceData[key] })
|
this.metadataSourceMapped.push({ ...this.metadataSourceData[key] })
|
||||||
}
|
}
|
||||||
|
this.metadataSourceMapped.reverse()
|
||||||
|
|
||||||
this.$emit('update', this.getLibraryData())
|
this.$emit('update', this.getLibraryData())
|
||||||
},
|
},
|
||||||
getLibraryData() {
|
getLibraryData() {
|
||||||
|
const metadataSourceIds = this.metadataSourceMapped.map((source) => (source.include ? source.id : null)).filter((s) => s)
|
||||||
|
metadataSourceIds.reverse()
|
||||||
return {
|
return {
|
||||||
settings: {
|
settings: {
|
||||||
metadataPrecedence: this.metadataSourceMapped.map((source) => (source.include ? source.id : null)).filter((s) => s)
|
metadataPrecedence: metadataSourceIds
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -120,15 +141,16 @@ export default {
|
|||||||
},
|
},
|
||||||
init() {
|
init() {
|
||||||
const metadataPrecedence = this.librarySettings.metadataPrecedence || []
|
const metadataPrecedence = this.librarySettings.metadataPrecedence || []
|
||||||
|
|
||||||
this.metadataSourceMapped = metadataPrecedence.map((source) => this.metadataSourceData[source]).filter((s) => s)
|
this.metadataSourceMapped = metadataPrecedence.map((source) => this.metadataSourceData[source]).filter((s) => s)
|
||||||
|
|
||||||
for (const sourceKey in this.metadataSourceData) {
|
for (const sourceKey in this.metadataSourceData) {
|
||||||
if (!metadataPrecedence.includes(sourceKey)) {
|
if (!metadataPrecedence.includes(sourceKey)) {
|
||||||
const unusedSourceData = { ...this.metadataSourceData[sourceKey], include: false }
|
const unusedSourceData = { ...this.metadataSourceData[sourceKey], include: false }
|
||||||
this.metadataSourceMapped.push(unusedSourceData)
|
this.metadataSourceMapped.unshift(unusedSourceData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.metadataSourceMapped.reverse()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
4
client/package-lock.json
generated
4
client/package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "audiobookshelf-client",
|
"name": "audiobookshelf-client",
|
||||||
"version": "2.5.0",
|
"version": "2.6.0",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "audiobookshelf-client",
|
"name": "audiobookshelf-client",
|
||||||
"version": "2.5.0",
|
"version": "2.6.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxtjs/axios": "^5.13.6",
|
"@nuxtjs/axios": "^5.13.6",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "audiobookshelf-client",
|
"name": "audiobookshelf-client",
|
||||||
"version": "2.5.0",
|
"version": "2.6.0",
|
||||||
"buildNumber": 1,
|
"buildNumber": 1,
|
||||||
"description": "Self-hosted audiobook and podcast client",
|
"description": "Self-hosted audiobook and podcast client",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
|
@ -11,6 +11,11 @@
|
|||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<ui-checkbox v-model="enableOpenIDAuth" checkbox-bg="bg" />
|
<ui-checkbox v-model="enableOpenIDAuth" checkbox-bg="bg" />
|
||||||
<p class="text-lg pl-4">{{ $strings.HeaderOpenIDConnectAuthentication }}</p>
|
<p class="text-lg pl-4">{{ $strings.HeaderOpenIDConnectAuthentication }}</p>
|
||||||
|
<ui-tooltip :text="$strings.LabelClickForMoreInfo" class="inline-flex ml-2">
|
||||||
|
<a href="https://www.audiobookshelf.org/guides/oidc_authentication" target="_blank" class="inline-flex">
|
||||||
|
<span class="material-icons text-xl w-5 text-gray-200">help_outline</span>
|
||||||
|
</a>
|
||||||
|
</ui-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<transition name="slide">
|
<transition name="slide">
|
||||||
|
@ -283,6 +283,7 @@
|
|||||||
"LabelHardDeleteFile": "Trvale smazat soubor",
|
"LabelHardDeleteFile": "Trvale smazat soubor",
|
||||||
"LabelHasEbook": "Obsahuje elektronickou knihu",
|
"LabelHasEbook": "Obsahuje elektronickou knihu",
|
||||||
"LabelHasSupplementaryEbook": "Obsahuje doplňkovou elektronickou knihu",
|
"LabelHasSupplementaryEbook": "Obsahuje doplňkovou elektronickou knihu",
|
||||||
|
"LabelHighestPriority": "Highest priority",
|
||||||
"LabelHost": "Hostitel",
|
"LabelHost": "Hostitel",
|
||||||
"LabelHour": "Hodina",
|
"LabelHour": "Hodina",
|
||||||
"LabelIcon": "Ikona",
|
"LabelIcon": "Ikona",
|
||||||
@ -324,11 +325,12 @@
|
|||||||
"LabelLogLevelInfo": "Informace",
|
"LabelLogLevelInfo": "Informace",
|
||||||
"LabelLogLevelWarn": "Varovat",
|
"LabelLogLevelWarn": "Varovat",
|
||||||
"LabelLookForNewEpisodesAfterDate": "Hledat nové epizody po tomto datu",
|
"LabelLookForNewEpisodesAfterDate": "Hledat nové epizody po tomto datu",
|
||||||
|
"LabelLowestPriority": "Lowest Priority",
|
||||||
"LabelMatchExistingUsersBy": "Match existing users by",
|
"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",
|
"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í",
|
"LabelMediaPlayer": "Přehrávač médií",
|
||||||
"LabelMediaType": "Typ média",
|
"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",
|
"LabelMetadataProvider": "Poskytovatel metadat",
|
||||||
"LabelMetaTag": "Metaznačka",
|
"LabelMetaTag": "Metaznačka",
|
||||||
"LabelMetaTags": "Metaznačky",
|
"LabelMetaTags": "Metaznačky",
|
||||||
|
@ -283,6 +283,7 @@
|
|||||||
"LabelHardDeleteFile": "Permanent slet fil",
|
"LabelHardDeleteFile": "Permanent slet fil",
|
||||||
"LabelHasEbook": "Har e-bog",
|
"LabelHasEbook": "Har e-bog",
|
||||||
"LabelHasSupplementaryEbook": "Har supplerende e-bog",
|
"LabelHasSupplementaryEbook": "Har supplerende e-bog",
|
||||||
|
"LabelHighestPriority": "Highest priority",
|
||||||
"LabelHost": "Vært",
|
"LabelHost": "Vært",
|
||||||
"LabelHour": "Time",
|
"LabelHour": "Time",
|
||||||
"LabelIcon": "Ikon",
|
"LabelIcon": "Ikon",
|
||||||
@ -324,11 +325,12 @@
|
|||||||
"LabelLogLevelInfo": "Information",
|
"LabelLogLevelInfo": "Information",
|
||||||
"LabelLogLevelWarn": "Advarsel",
|
"LabelLogLevelWarn": "Advarsel",
|
||||||
"LabelLookForNewEpisodesAfterDate": "Søg efter nye episoder efter denne dato",
|
"LabelLookForNewEpisodesAfterDate": "Søg efter nye episoder efter denne dato",
|
||||||
|
"LabelLowestPriority": "Lowest Priority",
|
||||||
"LabelMatchExistingUsersBy": "Match existing users by",
|
"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",
|
"LabelMatchExistingUsersByDescription": "Used for connecting existing users. Once connected, users will be matched by a unique id from your SSO provider",
|
||||||
"LabelMediaPlayer": "Medieafspiller",
|
"LabelMediaPlayer": "Medieafspiller",
|
||||||
"LabelMediaType": "Medietype",
|
"LabelMediaType": "Medietype",
|
||||||
"LabelMetadataOrderOfPrecedenceDescription": "1 is lowest priority, 5 is highest priority",
|
"LabelMetadataOrderOfPrecedenceDescription": "Higher priority metadata sources will override lower priority metadata sources",
|
||||||
"LabelMetadataProvider": "Metadataudbyder",
|
"LabelMetadataProvider": "Metadataudbyder",
|
||||||
"LabelMetaTag": "Meta-tag",
|
"LabelMetaTag": "Meta-tag",
|
||||||
"LabelMetaTags": "Meta-tags",
|
"LabelMetaTags": "Meta-tags",
|
||||||
|
@ -92,7 +92,7 @@
|
|||||||
"HeaderAppriseNotificationSettings": "Apprise Benachrichtigungseinstellungen",
|
"HeaderAppriseNotificationSettings": "Apprise Benachrichtigungseinstellungen",
|
||||||
"HeaderAudiobookTools": "Hörbuch-Dateiverwaltungstools",
|
"HeaderAudiobookTools": "Hörbuch-Dateiverwaltungstools",
|
||||||
"HeaderAudioTracks": "Audiodateien",
|
"HeaderAudioTracks": "Audiodateien",
|
||||||
"HeaderAuthentication": "Authentication",
|
"HeaderAuthentication": "Authentifizierung",
|
||||||
"HeaderBackups": "Sicherungen",
|
"HeaderBackups": "Sicherungen",
|
||||||
"HeaderChangePassword": "Passwort ändern",
|
"HeaderChangePassword": "Passwort ändern",
|
||||||
"HeaderChapters": "Kapitel",
|
"HeaderChapters": "Kapitel",
|
||||||
@ -132,10 +132,10 @@
|
|||||||
"HeaderNewAccount": "Neues Konto",
|
"HeaderNewAccount": "Neues Konto",
|
||||||
"HeaderNewLibrary": "Neue Bibliothek",
|
"HeaderNewLibrary": "Neue Bibliothek",
|
||||||
"HeaderNotifications": "Benachrichtigungen",
|
"HeaderNotifications": "Benachrichtigungen",
|
||||||
"HeaderOpenIDConnectAuthentication": "OpenID Connect Authentication",
|
"HeaderOpenIDConnectAuthentication": "OpenID Connect Authentifizierung",
|
||||||
"HeaderOpenRSSFeed": "RSS-Feed öffnen",
|
"HeaderOpenRSSFeed": "RSS-Feed öffnen",
|
||||||
"HeaderOtherFiles": "Sonstige Dateien",
|
"HeaderOtherFiles": "Sonstige Dateien",
|
||||||
"HeaderPasswordAuthentication": "Password Authentication",
|
"HeaderPasswordAuthentication": "Password Authentifizierung",
|
||||||
"HeaderPermissions": "Berechtigungen",
|
"HeaderPermissions": "Berechtigungen",
|
||||||
"HeaderPlayerQueue": "Spieler Warteschlange",
|
"HeaderPlayerQueue": "Spieler Warteschlange",
|
||||||
"HeaderPlaylist": "Wiedergabeliste",
|
"HeaderPlaylist": "Wiedergabeliste",
|
||||||
@ -184,11 +184,11 @@
|
|||||||
"LabelAddToCollectionBatch": "Füge {0} Hörbüch(er)/Podcast(s) der Sammlung hinzu",
|
"LabelAddToCollectionBatch": "Füge {0} Hörbüch(er)/Podcast(s) der Sammlung hinzu",
|
||||||
"LabelAddToPlaylist": "Zur Wiedergabeliste hinzufügen",
|
"LabelAddToPlaylist": "Zur Wiedergabeliste hinzufügen",
|
||||||
"LabelAddToPlaylistBatch": "Füge {0} Hörbüch(er)/Podcast(s) der Wiedergabeliste hinzu",
|
"LabelAddToPlaylistBatch": "Füge {0} Hörbüch(er)/Podcast(s) der Wiedergabeliste hinzu",
|
||||||
"LabelAdminUsersOnly": "Admin users only",
|
"LabelAdminUsersOnly": "Nur Admin Benutzer",
|
||||||
"LabelAll": "Alle",
|
"LabelAll": "Alle",
|
||||||
"LabelAllUsers": "Alle Benutzer",
|
"LabelAllUsers": "Alle Benutzer",
|
||||||
"LabelAllUsersExcludingGuests": "All users excluding guests",
|
"LabelAllUsersExcludingGuests": "Alle Benutzer außer Gästen",
|
||||||
"LabelAllUsersIncludingGuests": "All users including guests",
|
"LabelAllUsersIncludingGuests": "All Benutzer und Gäste",
|
||||||
"LabelAlreadyInYourLibrary": "In der Bibliothek vorhanden",
|
"LabelAlreadyInYourLibrary": "In der Bibliothek vorhanden",
|
||||||
"LabelAppend": "Anhängen",
|
"LabelAppend": "Anhängen",
|
||||||
"LabelAuthor": "Autor",
|
"LabelAuthor": "Autor",
|
||||||
@ -196,10 +196,10 @@
|
|||||||
"LabelAuthorLastFirst": "Autor (Nachname, Vorname)",
|
"LabelAuthorLastFirst": "Autor (Nachname, Vorname)",
|
||||||
"LabelAuthors": "Autoren",
|
"LabelAuthors": "Autoren",
|
||||||
"LabelAutoDownloadEpisodes": "Episoden automatisch herunterladen",
|
"LabelAutoDownloadEpisodes": "Episoden automatisch herunterladen",
|
||||||
"LabelAutoLaunch": "Auto Launch",
|
"LabelAutoLaunch": "Automatischer Start",
|
||||||
"LabelAutoLaunchDescription": "Redirect to the auth provider automatically when navigating to the login page (manual override path <code>/login?autoLaunch=0</code>)",
|
"LabelAutoLaunchDescription": "Automatische Weiterleitung zum Authentifizierungsanbieter beim Navigieren zur Anmeldeseite (manueller Überschreibungspfad <code>/login?autoLaunch=0</code>)",
|
||||||
"LabelAutoRegister": "Auto Register",
|
"LabelAutoRegister": "Automatische Registrierung",
|
||||||
"LabelAutoRegisterDescription": "Automatically create new users after logging in",
|
"LabelAutoRegisterDescription": "Automatische neue Neutzer anlegen nach dem Einloggen",
|
||||||
"LabelBackToUser": "Zurück zum Benutzer",
|
"LabelBackToUser": "Zurück zum Benutzer",
|
||||||
"LabelBackupLocation": "Backup-Ort",
|
"LabelBackupLocation": "Backup-Ort",
|
||||||
"LabelBackupsEnableAutomaticBackups": "Automatische Sicherung aktivieren",
|
"LabelBackupsEnableAutomaticBackups": "Automatische Sicherung aktivieren",
|
||||||
@ -240,7 +240,7 @@
|
|||||||
"LabelDeselectAll": "Alles abwählen",
|
"LabelDeselectAll": "Alles abwählen",
|
||||||
"LabelDevice": "Gerät",
|
"LabelDevice": "Gerät",
|
||||||
"LabelDeviceInfo": "Geräteinformationen",
|
"LabelDeviceInfo": "Geräteinformationen",
|
||||||
"LabelDeviceIsAvailableTo": "Device is available to...",
|
"LabelDeviceIsAvailableTo": "Dem Geärt ist es möglich zu ...",
|
||||||
"LabelDirectory": "Verzeichnis",
|
"LabelDirectory": "Verzeichnis",
|
||||||
"LabelDiscFromFilename": "CD aus dem Dateinamen",
|
"LabelDiscFromFilename": "CD aus dem Dateinamen",
|
||||||
"LabelDiscFromMetadata": "CD aus den Metadaten",
|
"LabelDiscFromMetadata": "CD aus den Metadaten",
|
||||||
@ -283,6 +283,7 @@
|
|||||||
"LabelHardDeleteFile": "Datei dauerhaft löschen",
|
"LabelHardDeleteFile": "Datei dauerhaft löschen",
|
||||||
"LabelHasEbook": "mit E-Book",
|
"LabelHasEbook": "mit E-Book",
|
||||||
"LabelHasSupplementaryEbook": "mit zusätlichem E-Book",
|
"LabelHasSupplementaryEbook": "mit zusätlichem E-Book",
|
||||||
|
"LabelHighestPriority": "Highest priority",
|
||||||
"LabelHost": "Host",
|
"LabelHost": "Host",
|
||||||
"LabelHour": "Stunde",
|
"LabelHour": "Stunde",
|
||||||
"LabelIcon": "Symbol",
|
"LabelIcon": "Symbol",
|
||||||
@ -324,11 +325,12 @@
|
|||||||
"LabelLogLevelInfo": "Informationen",
|
"LabelLogLevelInfo": "Informationen",
|
||||||
"LabelLogLevelWarn": "Warnungen",
|
"LabelLogLevelWarn": "Warnungen",
|
||||||
"LabelLookForNewEpisodesAfterDate": "Suchen nach neuen Episoden nach diesem Datum",
|
"LabelLookForNewEpisodesAfterDate": "Suchen nach neuen Episoden nach diesem Datum",
|
||||||
"LabelMatchExistingUsersBy": "Match existing users by",
|
"LabelLowestPriority": "Lowest Priority",
|
||||||
"LabelMatchExistingUsersByDescription": "Used for connecting existing users. Once connected, users will be matched by a unique id from your SSO provider",
|
"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",
|
"LabelMediaPlayer": "Mediaplayer",
|
||||||
"LabelMediaType": "Medientyp",
|
"LabelMediaType": "Medientyp",
|
||||||
"LabelMetadataOrderOfPrecedenceDescription": "1 is lowest priority, 5 is highest priority",
|
"LabelMetadataOrderOfPrecedenceDescription": "Higher priority metadata sources will override lower priority metadata sources",
|
||||||
"LabelMetadataProvider": "Metadatenanbieter",
|
"LabelMetadataProvider": "Metadatenanbieter",
|
||||||
"LabelMetaTag": "Meta Schlagwort",
|
"LabelMetaTag": "Meta Schlagwort",
|
||||||
"LabelMetaTags": "Meta Tags",
|
"LabelMetaTags": "Meta Tags",
|
||||||
@ -408,7 +410,7 @@
|
|||||||
"LabelSeason": "Staffel",
|
"LabelSeason": "Staffel",
|
||||||
"LabelSelectAllEpisodes": "Alle Episoden auswählen",
|
"LabelSelectAllEpisodes": "Alle Episoden auswählen",
|
||||||
"LabelSelectEpisodesShowing": "{0} ausgewählte Episoden werden angezeigt",
|
"LabelSelectEpisodesShowing": "{0} ausgewählte Episoden werden angezeigt",
|
||||||
"LabelSelectUsers": "Select users",
|
"LabelSelectUsers": "Benutzer auswählen",
|
||||||
"LabelSendEbookToDevice": "E-Book senden an...",
|
"LabelSendEbookToDevice": "E-Book senden an...",
|
||||||
"LabelSequence": "Reihenfolge",
|
"LabelSequence": "Reihenfolge",
|
||||||
"LabelSeries": "Serien",
|
"LabelSeries": "Serien",
|
||||||
|
@ -283,6 +283,7 @@
|
|||||||
"LabelHardDeleteFile": "Hard delete file",
|
"LabelHardDeleteFile": "Hard delete file",
|
||||||
"LabelHasEbook": "Has ebook",
|
"LabelHasEbook": "Has ebook",
|
||||||
"LabelHasSupplementaryEbook": "Has supplementary ebook",
|
"LabelHasSupplementaryEbook": "Has supplementary ebook",
|
||||||
|
"LabelHighestPriority": "Highest priority",
|
||||||
"LabelHost": "Host",
|
"LabelHost": "Host",
|
||||||
"LabelHour": "Hour",
|
"LabelHour": "Hour",
|
||||||
"LabelIcon": "Icon",
|
"LabelIcon": "Icon",
|
||||||
@ -324,11 +325,12 @@
|
|||||||
"LabelLogLevelInfo": "Info",
|
"LabelLogLevelInfo": "Info",
|
||||||
"LabelLogLevelWarn": "Warn",
|
"LabelLogLevelWarn": "Warn",
|
||||||
"LabelLookForNewEpisodesAfterDate": "Look for new episodes after this date",
|
"LabelLookForNewEpisodesAfterDate": "Look for new episodes after this date",
|
||||||
|
"LabelLowestPriority": "Lowest Priority",
|
||||||
"LabelMatchExistingUsersBy": "Match existing users by",
|
"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",
|
"LabelMatchExistingUsersByDescription": "Used for connecting existing users. Once connected, users will be matched by a unique id from your SSO provider",
|
||||||
"LabelMediaPlayer": "Media Player",
|
"LabelMediaPlayer": "Media Player",
|
||||||
"LabelMediaType": "Media Type",
|
"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",
|
"LabelMetadataProvider": "Metadata Provider",
|
||||||
"LabelMetaTag": "Meta Tag",
|
"LabelMetaTag": "Meta Tag",
|
||||||
"LabelMetaTags": "Meta Tags",
|
"LabelMetaTags": "Meta Tags",
|
||||||
|
@ -283,6 +283,7 @@
|
|||||||
"LabelHardDeleteFile": "Eliminar Definitivamente",
|
"LabelHardDeleteFile": "Eliminar Definitivamente",
|
||||||
"LabelHasEbook": "Tiene Ebook",
|
"LabelHasEbook": "Tiene Ebook",
|
||||||
"LabelHasSupplementaryEbook": "Tiene Ebook Suplementario",
|
"LabelHasSupplementaryEbook": "Tiene Ebook Suplementario",
|
||||||
|
"LabelHighestPriority": "Highest priority",
|
||||||
"LabelHost": "Host",
|
"LabelHost": "Host",
|
||||||
"LabelHour": "Hora",
|
"LabelHour": "Hora",
|
||||||
"LabelIcon": "Icono",
|
"LabelIcon": "Icono",
|
||||||
@ -324,11 +325,12 @@
|
|||||||
"LabelLogLevelInfo": "Información",
|
"LabelLogLevelInfo": "Información",
|
||||||
"LabelLogLevelWarn": "Advertencia",
|
"LabelLogLevelWarn": "Advertencia",
|
||||||
"LabelLookForNewEpisodesAfterDate": "Buscar Nuevos Episodios a partir de esta Fecha",
|
"LabelLookForNewEpisodesAfterDate": "Buscar Nuevos Episodios a partir de esta Fecha",
|
||||||
|
"LabelLowestPriority": "Lowest Priority",
|
||||||
"LabelMatchExistingUsersBy": "Match existing users by",
|
"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",
|
"LabelMatchExistingUsersByDescription": "Used for connecting existing users. Once connected, users will be matched by a unique id from your SSO provider",
|
||||||
"LabelMediaPlayer": "Reproductor de Medios",
|
"LabelMediaPlayer": "Reproductor de Medios",
|
||||||
"LabelMediaType": "Tipo de Multimedia",
|
"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",
|
"LabelMetadataProvider": "Proveedor de Metadata",
|
||||||
"LabelMetaTag": "Meta Tag",
|
"LabelMetaTag": "Meta Tag",
|
||||||
"LabelMetaTags": "Meta Tags",
|
"LabelMetaTags": "Meta Tags",
|
||||||
|
@ -283,6 +283,7 @@
|
|||||||
"LabelHardDeleteFile": "Suppression du fichier",
|
"LabelHardDeleteFile": "Suppression du fichier",
|
||||||
"LabelHasEbook": "Dispose d’un livre numérique",
|
"LabelHasEbook": "Dispose d’un livre numérique",
|
||||||
"LabelHasSupplementaryEbook": "Dispose d’un livre numérique supplémentaire",
|
"LabelHasSupplementaryEbook": "Dispose d’un livre numérique supplémentaire",
|
||||||
|
"LabelHighestPriority": "Highest priority",
|
||||||
"LabelHost": "Hôte",
|
"LabelHost": "Hôte",
|
||||||
"LabelHour": "Heure",
|
"LabelHour": "Heure",
|
||||||
"LabelIcon": "Icone",
|
"LabelIcon": "Icone",
|
||||||
@ -324,11 +325,12 @@
|
|||||||
"LabelLogLevelInfo": "Info",
|
"LabelLogLevelInfo": "Info",
|
||||||
"LabelLogLevelWarn": "Warn",
|
"LabelLogLevelWarn": "Warn",
|
||||||
"LabelLookForNewEpisodesAfterDate": "Chercher de nouveaux épisode après cette date",
|
"LabelLookForNewEpisodesAfterDate": "Chercher de nouveaux épisode après cette date",
|
||||||
|
"LabelLowestPriority": "Lowest Priority",
|
||||||
"LabelMatchExistingUsersBy": "Match existing users by",
|
"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",
|
"LabelMatchExistingUsersByDescription": "Used for connecting existing users. Once connected, users will be matched by a unique id from your SSO provider",
|
||||||
"LabelMediaPlayer": "Lecteur multimédia",
|
"LabelMediaPlayer": "Lecteur multimédia",
|
||||||
"LabelMediaType": "Type de mé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",
|
"LabelMetadataProvider": "Fournisseur de métadonnées",
|
||||||
"LabelMetaTag": "Etiquette de métadonnée",
|
"LabelMetaTag": "Etiquette de métadonnée",
|
||||||
"LabelMetaTags": "Etiquettes de métadonnée",
|
"LabelMetaTags": "Etiquettes de métadonnée",
|
||||||
|
@ -283,6 +283,7 @@
|
|||||||
"LabelHardDeleteFile": "Hard delete file",
|
"LabelHardDeleteFile": "Hard delete file",
|
||||||
"LabelHasEbook": "Has ebook",
|
"LabelHasEbook": "Has ebook",
|
||||||
"LabelHasSupplementaryEbook": "Has supplementary ebook",
|
"LabelHasSupplementaryEbook": "Has supplementary ebook",
|
||||||
|
"LabelHighestPriority": "Highest priority",
|
||||||
"LabelHost": "Host",
|
"LabelHost": "Host",
|
||||||
"LabelHour": "Hour",
|
"LabelHour": "Hour",
|
||||||
"LabelIcon": "Icon",
|
"LabelIcon": "Icon",
|
||||||
@ -324,11 +325,12 @@
|
|||||||
"LabelLogLevelInfo": "Info",
|
"LabelLogLevelInfo": "Info",
|
||||||
"LabelLogLevelWarn": "Warn",
|
"LabelLogLevelWarn": "Warn",
|
||||||
"LabelLookForNewEpisodesAfterDate": "Look for new episodes after this date",
|
"LabelLookForNewEpisodesAfterDate": "Look for new episodes after this date",
|
||||||
|
"LabelLowestPriority": "Lowest Priority",
|
||||||
"LabelMatchExistingUsersBy": "Match existing users by",
|
"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",
|
"LabelMatchExistingUsersByDescription": "Used for connecting existing users. Once connected, users will be matched by a unique id from your SSO provider",
|
||||||
"LabelMediaPlayer": "Media Player",
|
"LabelMediaPlayer": "Media Player",
|
||||||
"LabelMediaType": "Media Type",
|
"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",
|
"LabelMetadataProvider": "Metadata Provider",
|
||||||
"LabelMetaTag": "Meta Tag",
|
"LabelMetaTag": "Meta Tag",
|
||||||
"LabelMetaTags": "Meta Tags",
|
"LabelMetaTags": "Meta Tags",
|
||||||
|
@ -283,6 +283,7 @@
|
|||||||
"LabelHardDeleteFile": "Hard delete file",
|
"LabelHardDeleteFile": "Hard delete file",
|
||||||
"LabelHasEbook": "Has ebook",
|
"LabelHasEbook": "Has ebook",
|
||||||
"LabelHasSupplementaryEbook": "Has supplementary ebook",
|
"LabelHasSupplementaryEbook": "Has supplementary ebook",
|
||||||
|
"LabelHighestPriority": "Highest priority",
|
||||||
"LabelHost": "Host",
|
"LabelHost": "Host",
|
||||||
"LabelHour": "Hour",
|
"LabelHour": "Hour",
|
||||||
"LabelIcon": "Icon",
|
"LabelIcon": "Icon",
|
||||||
@ -324,11 +325,12 @@
|
|||||||
"LabelLogLevelInfo": "Info",
|
"LabelLogLevelInfo": "Info",
|
||||||
"LabelLogLevelWarn": "Warn",
|
"LabelLogLevelWarn": "Warn",
|
||||||
"LabelLookForNewEpisodesAfterDate": "Look for new episodes after this date",
|
"LabelLookForNewEpisodesAfterDate": "Look for new episodes after this date",
|
||||||
|
"LabelLowestPriority": "Lowest Priority",
|
||||||
"LabelMatchExistingUsersBy": "Match existing users by",
|
"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",
|
"LabelMatchExistingUsersByDescription": "Used for connecting existing users. Once connected, users will be matched by a unique id from your SSO provider",
|
||||||
"LabelMediaPlayer": "Media Player",
|
"LabelMediaPlayer": "Media Player",
|
||||||
"LabelMediaType": "Media Type",
|
"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",
|
"LabelMetadataProvider": "Metadata Provider",
|
||||||
"LabelMetaTag": "Meta Tag",
|
"LabelMetaTag": "Meta Tag",
|
||||||
"LabelMetaTags": "Meta Tags",
|
"LabelMetaTags": "Meta Tags",
|
||||||
|
@ -283,6 +283,7 @@
|
|||||||
"LabelHardDeleteFile": "Obriši datoteku zauvijek",
|
"LabelHardDeleteFile": "Obriši datoteku zauvijek",
|
||||||
"LabelHasEbook": "Has ebook",
|
"LabelHasEbook": "Has ebook",
|
||||||
"LabelHasSupplementaryEbook": "Has supplementary ebook",
|
"LabelHasSupplementaryEbook": "Has supplementary ebook",
|
||||||
|
"LabelHighestPriority": "Highest priority",
|
||||||
"LabelHost": "Host",
|
"LabelHost": "Host",
|
||||||
"LabelHour": "Sat",
|
"LabelHour": "Sat",
|
||||||
"LabelIcon": "Ikona",
|
"LabelIcon": "Ikona",
|
||||||
@ -324,11 +325,12 @@
|
|||||||
"LabelLogLevelInfo": "Info",
|
"LabelLogLevelInfo": "Info",
|
||||||
"LabelLogLevelWarn": "Warn",
|
"LabelLogLevelWarn": "Warn",
|
||||||
"LabelLookForNewEpisodesAfterDate": "Traži nove epizode nakon ovog datuma",
|
"LabelLookForNewEpisodesAfterDate": "Traži nove epizode nakon ovog datuma",
|
||||||
|
"LabelLowestPriority": "Lowest Priority",
|
||||||
"LabelMatchExistingUsersBy": "Match existing users by",
|
"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",
|
"LabelMatchExistingUsersByDescription": "Used for connecting existing users. Once connected, users will be matched by a unique id from your SSO provider",
|
||||||
"LabelMediaPlayer": "Media Player",
|
"LabelMediaPlayer": "Media Player",
|
||||||
"LabelMediaType": "Media Type",
|
"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 ",
|
"LabelMetadataProvider": "Poslužitelj metapodataka ",
|
||||||
"LabelMetaTag": "Meta Tag",
|
"LabelMetaTag": "Meta Tag",
|
||||||
"LabelMetaTags": "Meta Tags",
|
"LabelMetaTags": "Meta Tags",
|
||||||
|
@ -283,6 +283,7 @@
|
|||||||
"LabelHardDeleteFile": "Elimina Definitivamente",
|
"LabelHardDeleteFile": "Elimina Definitivamente",
|
||||||
"LabelHasEbook": "Un ebook",
|
"LabelHasEbook": "Un ebook",
|
||||||
"LabelHasSupplementaryEbook": "Un ebook Supplementare",
|
"LabelHasSupplementaryEbook": "Un ebook Supplementare",
|
||||||
|
"LabelHighestPriority": "Highest priority",
|
||||||
"LabelHost": "Host",
|
"LabelHost": "Host",
|
||||||
"LabelHour": "Ora",
|
"LabelHour": "Ora",
|
||||||
"LabelIcon": "Icona",
|
"LabelIcon": "Icona",
|
||||||
@ -324,11 +325,12 @@
|
|||||||
"LabelLogLevelInfo": "Info",
|
"LabelLogLevelInfo": "Info",
|
||||||
"LabelLogLevelWarn": "Allarme",
|
"LabelLogLevelWarn": "Allarme",
|
||||||
"LabelLookForNewEpisodesAfterDate": "Cerca nuovi episodi dopo questa data",
|
"LabelLookForNewEpisodesAfterDate": "Cerca nuovi episodi dopo questa data",
|
||||||
|
"LabelLowestPriority": "Lowest Priority",
|
||||||
"LabelMatchExistingUsersBy": "Match existing users by",
|
"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",
|
"LabelMatchExistingUsersByDescription": "Used for connecting existing users. Once connected, users will be matched by a unique id from your SSO provider",
|
||||||
"LabelMediaPlayer": "Media Player",
|
"LabelMediaPlayer": "Media Player",
|
||||||
"LabelMediaType": "Tipo Media",
|
"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",
|
"LabelMetadataProvider": "Metadata Provider",
|
||||||
"LabelMetaTag": "Meta Tag",
|
"LabelMetaTag": "Meta Tag",
|
||||||
"LabelMetaTags": "Meta Tags",
|
"LabelMetaTags": "Meta Tags",
|
||||||
|
@ -283,6 +283,7 @@
|
|||||||
"LabelHardDeleteFile": "Galutinai ištrinti failą",
|
"LabelHardDeleteFile": "Galutinai ištrinti failą",
|
||||||
"LabelHasEbook": "Turi e-knygą",
|
"LabelHasEbook": "Turi e-knygą",
|
||||||
"LabelHasSupplementaryEbook": "Turi papildomą e-knygą",
|
"LabelHasSupplementaryEbook": "Turi papildomą e-knygą",
|
||||||
|
"LabelHighestPriority": "Highest priority",
|
||||||
"LabelHost": "Serveris",
|
"LabelHost": "Serveris",
|
||||||
"LabelHour": "Valanda",
|
"LabelHour": "Valanda",
|
||||||
"LabelIcon": "Piktograma",
|
"LabelIcon": "Piktograma",
|
||||||
@ -324,11 +325,12 @@
|
|||||||
"LabelLogLevelInfo": "Info",
|
"LabelLogLevelInfo": "Info",
|
||||||
"LabelLogLevelWarn": "Warn",
|
"LabelLogLevelWarn": "Warn",
|
||||||
"LabelLookForNewEpisodesAfterDate": "Ieškoti naujų epizodų po šios datos",
|
"LabelLookForNewEpisodesAfterDate": "Ieškoti naujų epizodų po šios datos",
|
||||||
|
"LabelLowestPriority": "Lowest Priority",
|
||||||
"LabelMatchExistingUsersBy": "Match existing users by",
|
"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",
|
"LabelMatchExistingUsersByDescription": "Used for connecting existing users. Once connected, users will be matched by a unique id from your SSO provider",
|
||||||
"LabelMediaPlayer": "Grotuvas",
|
"LabelMediaPlayer": "Grotuvas",
|
||||||
"LabelMediaType": "Medijos tipas",
|
"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",
|
"LabelMetadataProvider": "Metaduomenų tiekėjas",
|
||||||
"LabelMetaTag": "Meta žymė",
|
"LabelMetaTag": "Meta žymė",
|
||||||
"LabelMetaTags": "Meta žymos",
|
"LabelMetaTags": "Meta žymos",
|
||||||
|
@ -283,6 +283,7 @@
|
|||||||
"LabelHardDeleteFile": "Hard-delete bestand",
|
"LabelHardDeleteFile": "Hard-delete bestand",
|
||||||
"LabelHasEbook": "Heeft ebook",
|
"LabelHasEbook": "Heeft ebook",
|
||||||
"LabelHasSupplementaryEbook": "Heeft supplementair ebook",
|
"LabelHasSupplementaryEbook": "Heeft supplementair ebook",
|
||||||
|
"LabelHighestPriority": "Highest priority",
|
||||||
"LabelHost": "Host",
|
"LabelHost": "Host",
|
||||||
"LabelHour": "Uur",
|
"LabelHour": "Uur",
|
||||||
"LabelIcon": "Icoon",
|
"LabelIcon": "Icoon",
|
||||||
@ -324,11 +325,12 @@
|
|||||||
"LabelLogLevelInfo": "Info",
|
"LabelLogLevelInfo": "Info",
|
||||||
"LabelLogLevelWarn": "Waarschuwing",
|
"LabelLogLevelWarn": "Waarschuwing",
|
||||||
"LabelLookForNewEpisodesAfterDate": "Zoek naar nieuwe afleveringen na deze datum",
|
"LabelLookForNewEpisodesAfterDate": "Zoek naar nieuwe afleveringen na deze datum",
|
||||||
|
"LabelLowestPriority": "Lowest Priority",
|
||||||
"LabelMatchExistingUsersBy": "Match existing users by",
|
"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",
|
"LabelMatchExistingUsersByDescription": "Used for connecting existing users. Once connected, users will be matched by a unique id from your SSO provider",
|
||||||
"LabelMediaPlayer": "Mediaspeler",
|
"LabelMediaPlayer": "Mediaspeler",
|
||||||
"LabelMediaType": "Mediatype",
|
"LabelMediaType": "Mediatype",
|
||||||
"LabelMetadataOrderOfPrecedenceDescription": "1 is lowest priority, 5 is highest priority",
|
"LabelMetadataOrderOfPrecedenceDescription": "Higher priority metadata sources will override lower priority metadata sources",
|
||||||
"LabelMetadataProvider": "Metadatabron",
|
"LabelMetadataProvider": "Metadatabron",
|
||||||
"LabelMetaTag": "Meta-tag",
|
"LabelMetaTag": "Meta-tag",
|
||||||
"LabelMetaTags": "Meta-tags",
|
"LabelMetaTags": "Meta-tags",
|
||||||
|
@ -283,6 +283,7 @@
|
|||||||
"LabelHardDeleteFile": "Tving sletting av fil",
|
"LabelHardDeleteFile": "Tving sletting av fil",
|
||||||
"LabelHasEbook": "Har ebok",
|
"LabelHasEbook": "Har ebok",
|
||||||
"LabelHasSupplementaryEbook": "Har supplerende ebok",
|
"LabelHasSupplementaryEbook": "Har supplerende ebok",
|
||||||
|
"LabelHighestPriority": "Highest priority",
|
||||||
"LabelHost": "Tjener",
|
"LabelHost": "Tjener",
|
||||||
"LabelHour": "Time",
|
"LabelHour": "Time",
|
||||||
"LabelIcon": "Ikon",
|
"LabelIcon": "Ikon",
|
||||||
@ -324,11 +325,12 @@
|
|||||||
"LabelLogLevelInfo": "Info",
|
"LabelLogLevelInfo": "Info",
|
||||||
"LabelLogLevelWarn": "Warn",
|
"LabelLogLevelWarn": "Warn",
|
||||||
"LabelLookForNewEpisodesAfterDate": "Se etter nye episoder etter denne datoen",
|
"LabelLookForNewEpisodesAfterDate": "Se etter nye episoder etter denne datoen",
|
||||||
|
"LabelLowestPriority": "Lowest Priority",
|
||||||
"LabelMatchExistingUsersBy": "Match existing users by",
|
"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",
|
"LabelMatchExistingUsersByDescription": "Used for connecting existing users. Once connected, users will be matched by a unique id from your SSO provider",
|
||||||
"LabelMediaPlayer": "Mediespiller",
|
"LabelMediaPlayer": "Mediespiller",
|
||||||
"LabelMediaType": "Medie type",
|
"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",
|
"LabelMetadataProvider": "Metadata Leverandør",
|
||||||
"LabelMetaTag": "Meta Tag",
|
"LabelMetaTag": "Meta Tag",
|
||||||
"LabelMetaTags": "Meta Tags",
|
"LabelMetaTags": "Meta Tags",
|
||||||
|
@ -283,6 +283,7 @@
|
|||||||
"LabelHardDeleteFile": "Usuń trwale plik",
|
"LabelHardDeleteFile": "Usuń trwale plik",
|
||||||
"LabelHasEbook": "Has ebook",
|
"LabelHasEbook": "Has ebook",
|
||||||
"LabelHasSupplementaryEbook": "Has supplementary ebook",
|
"LabelHasSupplementaryEbook": "Has supplementary ebook",
|
||||||
|
"LabelHighestPriority": "Highest priority",
|
||||||
"LabelHost": "Host",
|
"LabelHost": "Host",
|
||||||
"LabelHour": "Godzina",
|
"LabelHour": "Godzina",
|
||||||
"LabelIcon": "Ikona",
|
"LabelIcon": "Ikona",
|
||||||
@ -324,11 +325,12 @@
|
|||||||
"LabelLogLevelInfo": "Informacja",
|
"LabelLogLevelInfo": "Informacja",
|
||||||
"LabelLogLevelWarn": "Ostrzeżenie",
|
"LabelLogLevelWarn": "Ostrzeżenie",
|
||||||
"LabelLookForNewEpisodesAfterDate": "Szukaj nowych odcinków po dacie",
|
"LabelLookForNewEpisodesAfterDate": "Szukaj nowych odcinków po dacie",
|
||||||
|
"LabelLowestPriority": "Lowest Priority",
|
||||||
"LabelMatchExistingUsersBy": "Match existing users by",
|
"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",
|
"LabelMatchExistingUsersByDescription": "Used for connecting existing users. Once connected, users will be matched by a unique id from your SSO provider",
|
||||||
"LabelMediaPlayer": "Odtwarzacz",
|
"LabelMediaPlayer": "Odtwarzacz",
|
||||||
"LabelMediaType": "Typ mediów",
|
"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",
|
"LabelMetadataProvider": "Dostawca metadanych",
|
||||||
"LabelMetaTag": "Tag",
|
"LabelMetaTag": "Tag",
|
||||||
"LabelMetaTags": "Meta Tags",
|
"LabelMetaTags": "Meta Tags",
|
||||||
|
@ -283,6 +283,7 @@
|
|||||||
"LabelHardDeleteFile": "Жесткое удаление файла",
|
"LabelHardDeleteFile": "Жесткое удаление файла",
|
||||||
"LabelHasEbook": "Есть e-книга",
|
"LabelHasEbook": "Есть e-книга",
|
||||||
"LabelHasSupplementaryEbook": "Есть дополнительная e-книга",
|
"LabelHasSupplementaryEbook": "Есть дополнительная e-книга",
|
||||||
|
"LabelHighestPriority": "Highest priority",
|
||||||
"LabelHost": "Хост",
|
"LabelHost": "Хост",
|
||||||
"LabelHour": "Часы",
|
"LabelHour": "Часы",
|
||||||
"LabelIcon": "Иконка",
|
"LabelIcon": "Иконка",
|
||||||
@ -324,11 +325,12 @@
|
|||||||
"LabelLogLevelInfo": "Info",
|
"LabelLogLevelInfo": "Info",
|
||||||
"LabelLogLevelWarn": "Warn",
|
"LabelLogLevelWarn": "Warn",
|
||||||
"LabelLookForNewEpisodesAfterDate": "Искать новые эпизоды после этой даты",
|
"LabelLookForNewEpisodesAfterDate": "Искать новые эпизоды после этой даты",
|
||||||
|
"LabelLowestPriority": "Lowest Priority",
|
||||||
"LabelMatchExistingUsersBy": "Match existing users by",
|
"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",
|
"LabelMatchExistingUsersByDescription": "Used for connecting existing users. Once connected, users will be matched by a unique id from your SSO provider",
|
||||||
"LabelMediaPlayer": "Медиа проигрыватель",
|
"LabelMediaPlayer": "Медиа проигрыватель",
|
||||||
"LabelMediaType": "Тип медиа",
|
"LabelMediaType": "Тип медиа",
|
||||||
"LabelMetadataOrderOfPrecedenceDescription": "1 is lowest priority, 5 is highest priority",
|
"LabelMetadataOrderOfPrecedenceDescription": "Higher priority metadata sources will override lower priority metadata sources",
|
||||||
"LabelMetadataProvider": "Провайдер",
|
"LabelMetadataProvider": "Провайдер",
|
||||||
"LabelMetaTag": "Мета тег",
|
"LabelMetaTag": "Мета тег",
|
||||||
"LabelMetaTags": "Мета теги",
|
"LabelMetaTags": "Мета теги",
|
||||||
|
@ -283,6 +283,7 @@
|
|||||||
"LabelHardDeleteFile": "Hård radering av fil",
|
"LabelHardDeleteFile": "Hård radering av fil",
|
||||||
"LabelHasEbook": "Har e-bok",
|
"LabelHasEbook": "Har e-bok",
|
||||||
"LabelHasSupplementaryEbook": "Har kompletterande e-bok",
|
"LabelHasSupplementaryEbook": "Har kompletterande e-bok",
|
||||||
|
"LabelHighestPriority": "Highest priority",
|
||||||
"LabelHost": "Värd",
|
"LabelHost": "Värd",
|
||||||
"LabelHour": "Timme",
|
"LabelHour": "Timme",
|
||||||
"LabelIcon": "Ikon",
|
"LabelIcon": "Ikon",
|
||||||
@ -324,11 +325,12 @@
|
|||||||
"LabelLogLevelInfo": "Felsökningsnivå: Information",
|
"LabelLogLevelInfo": "Felsökningsnivå: Information",
|
||||||
"LabelLogLevelWarn": "Felsökningsnivå: Varning",
|
"LabelLogLevelWarn": "Felsökningsnivå: Varning",
|
||||||
"LabelLookForNewEpisodesAfterDate": "Sök efter nya avsnitt efter detta datum",
|
"LabelLookForNewEpisodesAfterDate": "Sök efter nya avsnitt efter detta datum",
|
||||||
|
"LabelLowestPriority": "Lowest Priority",
|
||||||
"LabelMatchExistingUsersBy": "Match existing users by",
|
"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",
|
"LabelMatchExistingUsersByDescription": "Used for connecting existing users. Once connected, users will be matched by a unique id from your SSO provider",
|
||||||
"LabelMediaPlayer": "Mediaspelare",
|
"LabelMediaPlayer": "Mediaspelare",
|
||||||
"LabelMediaType": "Mediatyp",
|
"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",
|
"LabelMetadataProvider": "Metadataleverantör",
|
||||||
"LabelMetaTag": "Metamärke",
|
"LabelMetaTag": "Metamärke",
|
||||||
"LabelMetaTags": "Metamärken",
|
"LabelMetaTags": "Metamärken",
|
||||||
|
@ -283,6 +283,7 @@
|
|||||||
"LabelHardDeleteFile": "完全删除文件",
|
"LabelHardDeleteFile": "完全删除文件",
|
||||||
"LabelHasEbook": "有电子书",
|
"LabelHasEbook": "有电子书",
|
||||||
"LabelHasSupplementaryEbook": "有补充电子书",
|
"LabelHasSupplementaryEbook": "有补充电子书",
|
||||||
|
"LabelHighestPriority": "Highest priority",
|
||||||
"LabelHost": "主机",
|
"LabelHost": "主机",
|
||||||
"LabelHour": "小时",
|
"LabelHour": "小时",
|
||||||
"LabelIcon": "图标",
|
"LabelIcon": "图标",
|
||||||
@ -324,11 +325,12 @@
|
|||||||
"LabelLogLevelInfo": "信息",
|
"LabelLogLevelInfo": "信息",
|
||||||
"LabelLogLevelWarn": "警告",
|
"LabelLogLevelWarn": "警告",
|
||||||
"LabelLookForNewEpisodesAfterDate": "在此日期后查找新剧集",
|
"LabelLookForNewEpisodesAfterDate": "在此日期后查找新剧集",
|
||||||
|
"LabelLowestPriority": "Lowest Priority",
|
||||||
"LabelMatchExistingUsersBy": "Match existing users by",
|
"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",
|
"LabelMatchExistingUsersByDescription": "Used for connecting existing users. Once connected, users will be matched by a unique id from your SSO provider",
|
||||||
"LabelMediaPlayer": "媒体播放器",
|
"LabelMediaPlayer": "媒体播放器",
|
||||||
"LabelMediaType": "媒体类型",
|
"LabelMediaType": "媒体类型",
|
||||||
"LabelMetadataOrderOfPrecedenceDescription": "1 is lowest priority, 5 is highest priority",
|
"LabelMetadataOrderOfPrecedenceDescription": "Higher priority metadata sources will override lower priority metadata sources",
|
||||||
"LabelMetadataProvider": "元数据提供者",
|
"LabelMetadataProvider": "元数据提供者",
|
||||||
"LabelMetaTag": "元数据标签",
|
"LabelMetaTag": "元数据标签",
|
||||||
"LabelMetaTags": "元标签",
|
"LabelMetaTags": "元标签",
|
||||||
|
6
package-lock.json
generated
6
package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "audiobookshelf",
|
"name": "audiobookshelf",
|
||||||
"version": "2.5.0",
|
"version": "2.6.0",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "audiobookshelf",
|
"name": "audiobookshelf",
|
||||||
"version": "2.5.0",
|
"version": "2.6.0",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^0.27.2",
|
"axios": "^0.27.2",
|
||||||
@ -9487,4 +9487,4 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "audiobookshelf",
|
"name": "audiobookshelf",
|
||||||
"version": "2.5.0",
|
"version": "2.6.0",
|
||||||
"buildNumber": 1,
|
"buildNumber": 1,
|
||||||
"description": "Self-hosted audiobook and podcast server",
|
"description": "Self-hosted audiobook and podcast server",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
@ -61,4 +61,4 @@
|
|||||||
"nyc": "^15.1.0",
|
"nyc": "^15.1.0",
|
||||||
"sinon": "^17.0.1"
|
"sinon": "^17.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -363,12 +363,50 @@ class Auth {
|
|||||||
req.session[sessionKey].code_verifier = req.query.code_verifier
|
req.session[sessionKey].code_verifier = req.query.code_verifier
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleAuthError(isMobile, errorCode, errorMessage, logMessage, response) {
|
||||||
|
Logger.error(logMessage)
|
||||||
|
if (response) {
|
||||||
|
// Depending on the error, it can also have a body
|
||||||
|
// We also log the request header the passport plugin sents for the URL
|
||||||
|
const header = response.req?._header.replace(/Authorization: [^\r\n]*/i, 'Authorization: REDACTED')
|
||||||
|
Logger.debug(header + '\n' + response.body?.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isMobile) {
|
||||||
|
return res.status(errorCode).send(errorMessage)
|
||||||
|
} else {
|
||||||
|
return res.redirect(`/login?error=${encodeURIComponent(errorMessage)}&autoLaunch=0`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function passportCallback(req, res, next) {
|
||||||
|
return (err, user, info) => {
|
||||||
|
const isMobile = req.session[sessionKey]?.mobile === true
|
||||||
|
if (err) {
|
||||||
|
return handleAuthError(isMobile, 500, 'Error in callback', `[Auth] Error in openid callback - ${err}`, err?.response)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
// Info usually contains the error message from the SSO provider
|
||||||
|
return handleAuthError(isMobile, 401, 'Unauthorized', `[Auth] No data in openid callback - ${info}`, info?.response)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.logIn(user, (loginError) => {
|
||||||
|
if (loginError) {
|
||||||
|
return handleAuthError(isMobile, 500, 'Error during login', `[Auth] Error in openid callback: ${loginError}`)
|
||||||
|
}
|
||||||
|
next()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// While not required by the standard, the passport plugin re-sends the original redirect_uri in the token request
|
// While not required by the standard, the passport plugin re-sends the original redirect_uri in the token request
|
||||||
// We need to set it correctly, as some SSO providers (e.g. keycloak) check that parameter when it is provided
|
// We need to set it correctly, as some SSO providers (e.g. keycloak) check that parameter when it is provided
|
||||||
if (req.session[sessionKey].mobile) {
|
if (req.session[sessionKey].mobile) {
|
||||||
return passport.authenticate('openid-client', { redirect_uri: 'audiobookshelf://oauth' })(req, res, next)
|
return passport.authenticate('openid-client', { redirect_uri: 'audiobookshelf://oauth' }, passportCallback(req, res, next))(req, res, next)
|
||||||
} else {
|
} else {
|
||||||
return passport.authenticate('openid-client', { failureRedirect: '/login?error=Unauthorized&autoLaunch=0' })(req, res, next)
|
return passport.authenticate('openid-client', passportCallback(req, res, next))(req, res, next)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// on a successfull login: read the cookies and react like the client requested (callback or json)
|
// on a successfull login: read the cookies and react like the client requested (callback or json)
|
||||||
|
@ -5,6 +5,7 @@ class Logger {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this.isDev = process.env.NODE_ENV !== 'production'
|
this.isDev = process.env.NODE_ENV !== 'production'
|
||||||
this.logLevel = !this.isDev ? LogLevel.INFO : LogLevel.TRACE
|
this.logLevel = !this.isDev ? LogLevel.INFO : LogLevel.TRACE
|
||||||
|
this.hideDevLogs = process.env.HIDE_DEV_LOGS === undefined ? !this.isDev : process.env.HIDE_DEV_LOGS === '1'
|
||||||
this.socketListeners = []
|
this.socketListeners = []
|
||||||
|
|
||||||
this.logManager = null
|
this.logManager = null
|
||||||
@ -92,7 +93,7 @@ class Logger {
|
|||||||
* @param {...any} args
|
* @param {...any} args
|
||||||
*/
|
*/
|
||||||
dev(...args) {
|
dev(...args) {
|
||||||
if (!this.isDev || process.env.HIDE_DEV_LOGS === '1') return
|
if (this.hideDevLogs) return
|
||||||
console.log(`[${this.timestamp}] DEV:`, ...args)
|
console.log(`[${this.timestamp}] DEV:`, ...args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,6 +231,7 @@ class Server {
|
|||||||
'/library/:library/search',
|
'/library/:library/search',
|
||||||
'/library/:library/bookshelf/:id?',
|
'/library/:library/bookshelf/:id?',
|
||||||
'/library/:library/authors',
|
'/library/:library/authors',
|
||||||
|
'/library/:library/narrators',
|
||||||
'/library/:library/series/:id?',
|
'/library/:library/series/:id?',
|
||||||
'/library/:library/podcast/search',
|
'/library/:library/podcast/search',
|
||||||
'/library/:library/podcast/latest',
|
'/library/:library/podcast/latest',
|
||||||
|
@ -9,7 +9,7 @@ class LibrarySettings {
|
|||||||
this.autoScanCronExpression = null
|
this.autoScanCronExpression = null
|
||||||
this.audiobooksOnly = false
|
this.audiobooksOnly = false
|
||||||
this.hideSingleBookSeries = false // Do not show series that only have 1 book
|
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) {
|
if (settings) {
|
||||||
this.construct(settings)
|
this.construct(settings)
|
||||||
@ -28,7 +28,7 @@ class LibrarySettings {
|
|||||||
this.metadataPrecedence = [...settings.metadataPrecedence]
|
this.metadataPrecedence = [...settings.metadataPrecedence]
|
||||||
} else {
|
} else {
|
||||||
// Added in v2.4.5
|
// Added in v2.4.5
|
||||||
this.metadataPrecedence = ['folderStructure', 'audioMetatags', 'txtFiles', 'opfFile', 'absMetadata']
|
this.metadataPrecedence = ['folderStructure', 'audioMetatags', 'nfoFile', 'txtFiles', 'opfFile', 'absMetadata']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ const BookFinder = require('../finders/BookFinder')
|
|||||||
|
|
||||||
const LibraryScan = require("./LibraryScan")
|
const LibraryScan = require("./LibraryScan")
|
||||||
const OpfFileScanner = require('./OpfFileScanner')
|
const OpfFileScanner = require('./OpfFileScanner')
|
||||||
|
const NfoFileScanner = require('./NfoFileScanner')
|
||||||
const AbsMetadataFileScanner = require('./AbsMetadataFileScanner')
|
const AbsMetadataFileScanner = require('./AbsMetadataFileScanner')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -593,7 +594,7 @@ class BookScanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const bookMetadataSourceHandler = new BookScanner.BookMetadataSourceHandler(bookMetadata, audioFiles, libraryItemData, libraryScan, existingLibraryItemId)
|
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(', ')}]`)
|
libraryScan.addLog(LogLevel.DEBUG, `"${bookMetadata.title}" Getting metadata with precedence [${metadataPrecedence.join(', ')}]`)
|
||||||
for (const metadataSource of metadataPrecedence) {
|
for (const metadataSource of metadataPrecedence) {
|
||||||
if (bookMetadataSourceHandler[metadataSource]) {
|
if (bookMetadataSourceHandler[metadataSource]) {
|
||||||
@ -649,6 +650,14 @@ class BookScanner {
|
|||||||
AudioFileScanner.setBookMetadataFromAudioMetaTags(bookTitle, this.audioFiles, this.bookMetadata, this.libraryScan)
|
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
|
* Description from desc.txt and narrator from reader.txt
|
||||||
*/
|
*/
|
||||||
|
@ -132,6 +132,11 @@ class LibraryItemScanData {
|
|||||||
return this.libraryFiles.find(lf => lf.metadata.ext.toLowerCase() === '.opf')
|
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
|
* @param {LibraryItem} existingLibraryItem
|
||||||
|
48
server/scanner/NfoFileScanner.js
Normal file
48
server/scanner/NfoFileScanner.js
Normal file
@ -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()
|
100
server/utils/parsers/parseNfoMetadata.js
Normal file
100
server/utils/parsers/parseNfoMetadata.js
Normal file
@ -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
|
||||||
|
}
|
123
test/server/utils/parsers/parseNfoMetadata.test.js
Normal file
123
test/server/utils/parsers/parseNfoMetadata.test.js
Normal file
@ -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
|
||||||
|
})
|
||||||
|
})
|
Loading…
Reference in New Issue
Block a user