From 98ed2e01cc7f3e879563d62de60e5703beb5af40 Mon Sep 17 00:00:00 2001 From: advplyr Date: Thu, 28 Sep 2023 17:23:52 -0500 Subject: [PATCH 01/15] Fix:Scanner overwriting metadata when metadata file is not stored with items #2155 --- server/scanner/BookScanner.js | 33 +++++++++++++++++++++++++------- server/scanner/PodcastScanner.js | 32 +++++++++++++++++++++++++------ 2 files changed, 52 insertions(+), 13 deletions(-) diff --git a/server/scanner/BookScanner.js b/server/scanner/BookScanner.js index 1e51cf526..f8e959bde 100644 --- a/server/scanner/BookScanner.js +++ b/server/scanner/BookScanner.js @@ -168,9 +168,7 @@ class BookScanner { hasMediaChanges = true } - // TODO: When metadata file is stored in /metadata/items/{libraryItemId}.[abs|json] we should load this - // TODO: store an additional array of metadata keys that the user has changed manually so we know what not to override - const bookMetadata = await this.getBookMetadataFromScanData(media.audioFiles, libraryItemData, libraryScan) + const bookMetadata = await this.getBookMetadataFromScanData(media.audioFiles, libraryItemData, libraryScan, existingLibraryItem.id) let authorsUpdated = false const bookAuthorsRemoved = [] let seriesUpdated = false @@ -550,9 +548,10 @@ class BookScanner { * @param {import('../models/Book').AudioFileObject[]} audioFiles * @param {import('./LibraryItemScanData')} libraryItemData * @param {LibraryScan} libraryScan + * @param {string} [existingLibraryItemId] * @returns {Promise} */ - async getBookMetadataFromScanData(audioFiles, libraryItemData, libraryScan) { + async getBookMetadataFromScanData(audioFiles, libraryItemData, libraryScan, existingLibraryItemId = null) { // First set book metadata from folder/file names const bookMetadata = { title: libraryItemData.mediaMetadata.title, @@ -722,11 +721,31 @@ class BookScanner { // If metadata.json or metadata.abs use this for metadata const metadataLibraryFile = libraryItemData.metadataJsonLibraryFile || libraryItemData.metadataAbsLibraryFile - const metadataText = metadataLibraryFile ? await readTextFile(metadataLibraryFile.metadata.path) : null + let metadataText = metadataLibraryFile ? await readTextFile(metadataLibraryFile.metadata.path) : null + let metadataFilePath = metadataLibraryFile?.metadata.path + let metadataFileFormat = libraryItemData.metadataJsonLibraryFile ? 'json' : 'abs' + + // When metadata file is not stored with library item then check in the /metadata/items folder for it + if (!metadataText && existingLibraryItemId) { + let metadataPath = Path.join(global.MetadataPath, 'items', existingLibraryItemId) + + let altFormat = global.ServerSettings.metadataFileFormat === 'json' ? 'abs' : 'json' + // First check the metadata format set in server settings, fallback to the alternate + metadataFilePath = Path.join(metadataPath, `metadata.${global.ServerSettings.metadataFileFormat}`) + metadataFileFormat = global.ServerSettings.metadataFileFormat + if (await fsExtra.pathExists(metadataFilePath)) { + metadataText = await readTextFile(metadataFilePath) + } else if (await fsExtra.pathExists(Path.join(metadataPath, `metadata.${altFormat}`))) { + metadataFilePath = Path.join(metadataPath, `metadata.${altFormat}`) + metadataFileFormat = altFormat + metadataText = await readTextFile(metadataFilePath) + } + } + if (metadataText) { - libraryScan.addLog(LogLevel.INFO, `Found metadata file "${metadataLibraryFile.metadata.path}" - preferring`) + libraryScan.addLog(LogLevel.INFO, `Found metadata file "${metadataFilePath}" - preferring`) let abMetadata = null - if (!!libraryItemData.metadataJsonLibraryFile) { + if (metadataFileFormat === 'json') { abMetadata = abmetadataGenerator.parseJson(metadataText) } else { abMetadata = abmetadataGenerator.parse(metadataText, 'book') diff --git a/server/scanner/PodcastScanner.js b/server/scanner/PodcastScanner.js index d5a799cfb..aa4a91a5b 100644 --- a/server/scanner/PodcastScanner.js +++ b/server/scanner/PodcastScanner.js @@ -145,8 +145,7 @@ class PodcastScanner { hasMediaChanges = true } - // TODO: When metadata file is stored in /metadata/items/{libraryItemId}.[abs|json] we should load this - const podcastMetadata = await this.getPodcastMetadataFromScanData(existingPodcastEpisodes, libraryItemData, libraryScan) + const podcastMetadata = await this.getPodcastMetadataFromScanData(existingPodcastEpisodes, libraryItemData, libraryScan, existingLibraryItem.id) for (const key in podcastMetadata) { // Ignore unset metadata and empty arrays @@ -312,9 +311,10 @@ class PodcastScanner { * @param {PodcastEpisode[]} podcastEpisodes Not the models for new podcasts * @param {import('./LibraryItemScanData')} libraryItemData * @param {import('./LibraryScan')} libraryScan + * @param {string} [existingLibraryItemId] * @returns {Promise} */ - async getPodcastMetadataFromScanData(podcastEpisodes, libraryItemData, libraryScan) { + async getPodcastMetadataFromScanData(podcastEpisodes, libraryItemData, libraryScan, existingLibraryItemId = null) { const podcastMetadata = { title: libraryItemData.mediaMetadata.title, titleIgnorePrefix: getTitleIgnorePrefix(libraryItemData.mediaMetadata.title), @@ -389,11 +389,31 @@ class PodcastScanner { // If metadata.json or metadata.abs use this for metadata const metadataLibraryFile = libraryItemData.metadataJsonLibraryFile || libraryItemData.metadataAbsLibraryFile - const metadataText = metadataLibraryFile ? await readTextFile(metadataLibraryFile.metadata.path) : null + let metadataText = metadataLibraryFile ? await readTextFile(metadataLibraryFile.metadata.path) : null + let metadataFilePath = metadataLibraryFile?.metadata.path + let metadataFileFormat = libraryItemData.metadataJsonLibraryFile ? 'json' : 'abs' + + // When metadata file is not stored with library item then check in the /metadata/items folder for it + if (!metadataText && existingLibraryItemId) { + let metadataPath = Path.join(global.MetadataPath, 'items', existingLibraryItemId) + + let altFormat = global.ServerSettings.metadataFileFormat === 'json' ? 'abs' : 'json' + // First check the metadata format set in server settings, fallback to the alternate + metadataFilePath = Path.join(metadataPath, `metadata.${global.ServerSettings.metadataFileFormat}`) + metadataFileFormat = global.ServerSettings.metadataFileFormat + if (await fsExtra.pathExists(metadataFilePath)) { + metadataText = await readTextFile(metadataFilePath) + } else if (await fsExtra.pathExists(Path.join(metadataPath, `metadata.${altFormat}`))) { + metadataFilePath = Path.join(metadataPath, `metadata.${altFormat}`) + metadataFileFormat = altFormat + metadataText = await readTextFile(metadataFilePath) + } + } + if (metadataText) { - libraryScan.addLog(LogLevel.INFO, `Found metadata file "${metadataLibraryFile.metadata.path}" - preferring`) + libraryScan.addLog(LogLevel.INFO, `Found metadata file "${metadataFilePath}" - preferring`) let abMetadata = null - if (!!libraryItemData.metadataJsonLibraryFile) { + if (metadataFileFormat === 'json') { abMetadata = abmetadataGenerator.parseJson(metadataText) } else { abMetadata = abmetadataGenerator.parse(metadataText, 'podcast') From f0929729a305faeaa5cabc2062716adc6241e17c Mon Sep 17 00:00:00 2001 From: advplyr Date: Fri, 29 Sep 2023 14:52:04 -0500 Subject: [PATCH 02/15] Fix:Adding new podcast with auto download episodes not setting the schedule #2160 --- server/controllers/PodcastController.js | 2 +- server/objects/settings/ServerSettings.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/server/controllers/PodcastController.js b/server/controllers/PodcastController.js index 0e1ebcd3a..4f6df6afb 100644 --- a/server/controllers/PodcastController.js +++ b/server/controllers/PodcastController.js @@ -91,7 +91,7 @@ class PodcastController { res.json(libraryItem.toJSONExpanded()) - if (payload.episodesToDownload && payload.episodesToDownload.length) { + if (payload.episodesToDownload?.length) { Logger.info(`[PodcastController] Podcast created now starting ${payload.episodesToDownload.length} episode downloads`) this.podcastManager.downloadPodcastEpisodes(libraryItem, payload.episodesToDownload) } diff --git a/server/objects/settings/ServerSettings.js b/server/objects/settings/ServerSettings.js index 8daca57a2..418b80540 100644 --- a/server/objects/settings/ServerSettings.js +++ b/server/objects/settings/ServerSettings.js @@ -147,6 +147,7 @@ class ServerSettings { loggerScannerLogsToKeep: this.loggerScannerLogsToKeep, homeBookshelfView: this.homeBookshelfView, bookshelfView: this.bookshelfView, + podcastEpisodeSchedule: this.podcastEpisodeSchedule, sortingIgnorePrefix: this.sortingIgnorePrefix, sortingPrefixes: [...this.sortingPrefixes], chromecastEnabled: this.chromecastEnabled, From 826fee45903495e5e62994dce6165246192fa81c Mon Sep 17 00:00:00 2001 From: 92Kev <132282021+92Kev@users.noreply.github.com> Date: Sat, 30 Sep 2023 16:42:28 +0200 Subject: [PATCH 03/15] Update nl.json Added Dutch translations where they were missing --- client/strings/nl.json | 112 ++++++++++++++++++++--------------------- 1 file changed, 56 insertions(+), 56 deletions(-) diff --git a/client/strings/nl.json b/client/strings/nl.json index 9bd346359..e0b4ecd41 100644 --- a/client/strings/nl.json +++ b/client/strings/nl.json @@ -99,11 +99,11 @@ "HeaderDetails": "Details", "HeaderDownloadQueue": "Download-wachtrij", "HeaderEbookFiles": "Ebook Files", - "HeaderEmail": "Email", - "HeaderEmailSettings": "Email Settings", + "HeaderEmail": "E-mail", + "HeaderEmailSettings": "E-mail instellingen", "HeaderEpisodes": "Afleveringen", - "HeaderEreaderDevices": "Ereader Devices", - "HeaderEreaderSettings": "Ereader Settings", + "HeaderEreaderDevices": "Ereader-apparaten", + "HeaderEreaderSettings": "Ereader-instellingen", "HeaderFiles": "Bestanden", "HeaderFindChapters": "Zoek hoofdstukken", "HeaderIgnoredFiles": "Genegeerde bestanden", @@ -138,7 +138,7 @@ "HeaderRemoveEpisodes": "Verwijder {0} afleveringen", "HeaderRSSFeedGeneral": "RSS-details", "HeaderRSSFeedIsOpen": "RSS-feed is open", - "HeaderRSSFeeds": "RSS Feeds", + "HeaderRSSFeeds": "RSS-feeds", "HeaderSavedMediaProgress": "Opgeslagen mediavoortgang", "HeaderSchedule": "Schema", "HeaderScheduleLibraryScans": "Schema automatische bibliotheekscans", @@ -156,7 +156,7 @@ "HeaderStatsRecentSessions": "Recente sessies", "HeaderStatsTop10Authors": "Top 10 auteurs", "HeaderStatsTop5Genres": "Top 5 genres", - "HeaderTableOfContents": "Table of Contents", + "HeaderTableOfContents": "Inhoudsopgave", "HeaderTools": "Tools", "HeaderUpdateAccount": "Account bijwerken", "HeaderUpdateAuthor": "Auteur bijwerken", @@ -179,14 +179,14 @@ "LabelAll": "Alle", "LabelAllUsers": "Alle gebruikers", "LabelAlreadyInYourLibrary": "Reeds in je bibliotheek", - "LabelAppend": "Append", + "LabelAppend": "Achteraan toevoegen", "LabelAuthor": "Auteur", "LabelAuthorFirstLast": "Auteur (Voornaam Achternaam)", "LabelAuthorLastFirst": "Auteur (Achternaam, Voornaam)", "LabelAuthors": "Auteurs", "LabelAutoDownloadEpisodes": "Afleveringen automatisch downloaden", "LabelBackToUser": "Terug naar gebruiker", - "LabelBackupLocation": "Backup Location", + "LabelBackupLocation": "Back-up locatie", "LabelBackupsEnableAutomaticBackups": "Automatische back-ups inschakelen", "LabelBackupsEnableAutomaticBackupsHelp": "Back-ups opgeslagen in /metadata/backups", "LabelBackupsMaxBackupSize": "Maximale back-up-grootte (in GB)", @@ -208,7 +208,7 @@ "LabelComplete": "Compleet", "LabelConfirmPassword": "Bevestig wachtwoord", "LabelContinueListening": "Verder luisteren", - "LabelContinueReading": "Continue Reading", + "LabelContinueReading": "Verder luisteren", "LabelContinueSeries": "Ga verder met serie", "LabelCover": "Cover", "LabelCoverImageURL": "Coverafbeelding URL", @@ -225,7 +225,7 @@ "LabelDirectory": "Map", "LabelDiscFromFilename": "Schijf uit bestandsnaam", "LabelDiscFromMetadata": "Schijf uit metadata", - "LabelDiscover": "Discover", + "LabelDiscover": "Ontdek", "LabelDownload": "Download", "LabelDownloadNEpisodes": "Download {0} episodes", "LabelDuration": "Duur", @@ -234,10 +234,10 @@ "LabelEbooks": "Ebooks", "LabelEdit": "Wijzig", "LabelEmail": "Email", - "LabelEmailSettingsFromAddress": "From Address", - "LabelEmailSettingsSecure": "Secure", - "LabelEmailSettingsSecureHelp": "If true the connection will use TLS when connecting to server. If false then TLS is used if server supports the STARTTLS extension. In most cases set this value to true if you are connecting to port 465. For port 587 or 25 keep it false. (from nodemailer.com/smtp/#authentication)", - "LabelEmailSettingsTestAddress": "Test Address", + "LabelEmailSettingsFromAddress": "Van-adres", + "LabelEmailSettingsSecure": "Veilig", + "LabelEmailSettingsSecureHelp": "Als 'waar', dan gebruikt de verbinding TLS om met de server te verbinden. Als 'onwaar', dan wordt TLS gebruikt als de server de STARTTLS-extensie ondersteunt. In de meeste gevallen kies je voor 'waar' verbindt met poort 465. Voo poort 587 of 25, laat op 'onwaar'. (van nodemailer.com/smtp/#authentication)", + "LabelEmailSettingsTestAddress": "Test-adres", "LabelEmbeddedCover": "Ingesloten cover", "LabelEnable": "Inschakelen", "LabelEnd": "Einde", @@ -256,13 +256,13 @@ "LabelFinished": "Voltooid", "LabelFolder": "Map", "LabelFolders": "Mappen", - "LabelFontScale": "Font scale", - "LabelFormat": "Format", + "LabelFontScale": "Lettertype schaal", + "LabelFormat": "Formaat", "LabelGenre": "Genre", "LabelGenres": "Genres", "LabelHardDeleteFile": "Hard-delete bestand", - "LabelHasEbook": "Has ebook", - "LabelHasSupplementaryEbook": "Has supplementary ebook", + "LabelHasEbook": "Heeft ebook", + "LabelHasSupplementaryEbook": "Heeft supplementair ebook", "LabelHost": "Host", "LabelHour": "Uur", "LabelIcon": "Icoon", @@ -289,15 +289,15 @@ "LabelLastTime": "Laatste keer", "LabelLastUpdate": "Laatste update", "LabelLayout": "Layout", - "LabelLayoutSinglePage": "Single page", - "LabelLayoutSplitPage": "Split page", + "LabelLayoutSinglePage": "Enkele pagina", + "LabelLayoutSplitPage": "Gesplitste pagina", "LabelLess": "Minder", "LabelLibrariesAccessibleToUser": "Voor gebruiker toegankelijke bibliotheken", "LabelLibrary": "Bibliotheek", - "LabelLibraryItem": "Library Item", - "LabelLibraryName": "Library Name", + "LabelLibraryItem": "Bibliotheekonderdeel", + "LabelLibraryName": "Bibliotheeknaam", "LabelLimit": "Limiet", - "LabelLineSpacing": "Line spacing", + "LabelLineSpacing": "Regelruimte", "LabelListenAgain": "Luister opnieuw", "LabelLogLevelDebug": "Debug", "LabelLogLevelInfo": "Info", @@ -322,7 +322,7 @@ "LabelNewPassword": "Nieuw wachtwoord", "LabelNextBackupDate": "Volgende back-up datum", "LabelNextScheduledRun": "Volgende geplande run", - "LabelNoEpisodesSelected": "No episodes selected", + "LabelNoEpisodesSelected": "Geen afleveringen geselecteerd", "LabelNotes": "Notities", "LabelNotFinished": "Niet Voltooid", "LabelNotificationAppriseURL": "Apprise URL(s)", @@ -354,18 +354,18 @@ "LabelPodcast": "Podcast", "LabelPodcasts": "Podcasts", "LabelPodcastType": "Podcasttype", - "LabelPort": "Port", + "LabelPort": "Poort", "LabelPrefixesToIgnore": "Te negeren voorzetsels (ongeacht hoofdlettergebruik)", "LabelPreventIndexing": "Voorkom indexering van je feed door iTunes- en Google podcastmappen", - "LabelPrimaryEbook": "Primary ebook", + "LabelPrimaryEbook": "Primair ebook", "LabelProgress": "Voortgang", "LabelProvider": "Bron", "LabelPubDate": "Publicatiedatum", "LabelPublisher": "Uitgever", "LabelPublishYear": "Jaar van uitgave", - "LabelRead": "Read", - "LabelReadAgain": "Read Again", - "LabelReadEbookWithoutProgress": "Read ebook without keeping progress", + "LabelRead": "Lees", + "LabelReadAgain": "Lees opnieuw", + "LabelReadEbookWithoutProgress": "Lees ebook zonder voortgang bij te houden", "LabelRecentlyAdded": "Recent toegevoegd", "LabelRecentSeries": "Recente series", "LabelRecommended": "Aangeraden", @@ -382,32 +382,32 @@ "LabelSearchTitle": "Zoek titel", "LabelSearchTitleOrASIN": "Zoek titel of ASIN", "LabelSeason": "Seizoen", - "LabelSelectAllEpisodes": "Select all episodes", - "LabelSelectEpisodesShowing": "Select {0} episodes showing", - "LabelSendEbookToDevice": "Send Ebook to...", + "LabelSelectAllEpisodes": "Selecteer alle afleveringen", + "LabelSelectEpisodesShowing": "Selecteer {0} afleveringen laten zien", + "LabelSendEbookToDevice": "Stuur ebook naar...", "LabelSequence": "Sequentie", "LabelSeries": "Serie", "LabelSeriesName": "Naam serie", "LabelSeriesProgress": "Voortgang serie", - "LabelSetEbookAsPrimary": "Set as primary", - "LabelSetEbookAsSupplementary": "Set as supplementary", - "LabelSettingsAudiobooksOnly": "Audiobooks only", - "LabelSettingsAudiobooksOnlyHelp": "Enabling this setting will ignore ebook files unless they are inside an audiobook folder in which case they will be set as supplementary ebooks", + "LabelSetEbookAsPrimary": "Stel in als primair", + "LabelSetEbookAsSupplementary": "Stel in als supplementair", + "LabelSettingsAudiobooksOnly": "Alleen audiobooks", + "LabelSettingsAudiobooksOnlyHelp": "Deze instelling inschakelen zorgt ervoor dat ebook-bestanden genegeerd worden tenzij ze in een audiobook-map staan, in welk geval ze worden ingesteld als supplementaire ebooks", "LabelSettingsBookshelfViewHelp": "Skeumorphisch design met houten planken", "LabelSettingsChromecastSupport": "Chromecast support", "LabelSettingsDateFormat": "Datum format", "LabelSettingsDisableWatcher": "Watcher uitschakelen", "LabelSettingsDisableWatcherForLibrary": "Map-watcher voor bibliotheek uitschakelen", "LabelSettingsDisableWatcherHelp": "Schakelt het automatisch toevoegen/bijwerken van onderdelen wanneer bestandswijzigingen gedetecteerd zijn uit. *Vereist herstart server", - "LabelSettingsEnableWatcher": "Enable Watcher", - "LabelSettingsEnableWatcherForLibrary": "Enable folder watcher for library", - "LabelSettingsEnableWatcherHelp": "Enables the automatic adding/updating of items when file changes are detected. *Requires server restart", + "LabelSettingsEnableWatcher": "Watcher inschakelen", + "LabelSettingsEnableWatcherForLibrary": "Map-watcher voor bibliotheek inschakelen", + "LabelSettingsEnableWatcherHelp": "Zorgt voor het automatisch toevoegen/bijwerken van onderdelen als bestandswijzigingen worden gedetecteerd. *Vereist herstarten van server", "LabelSettingsExperimentalFeatures": "Experimentele functies", "LabelSettingsExperimentalFeaturesHelp": "Functies in ontwikkeling die je feedback en testing kunnen gebruiken. Klik om de Github-discussie te openen.", "LabelSettingsFindCovers": "Zoek covers", "LabelSettingsFindCoversHelp": "Als je audioboek geen ingesloten cover of cover in de map heeft, zal de scanner proberen een cover te vinden.
Opmerking: Dit zal de scan-duur verlengen", - "LabelSettingsHideSingleBookSeries": "Hide single book series", - "LabelSettingsHideSingleBookSeriesHelp": "Series that have a single book will be hidden from the series page and home page shelves.", + "LabelSettingsHideSingleBookSeries": "Verberg series met een enkel boek", + "LabelSettingsHideSingleBookSeriesHelp": "Series die slechts een enkel boek bevatten worden verborgen op de seriespagina en de homepagina-planken.", "LabelSettingsHomePageBookshelfView": "Boekenplank-view voor homepagina", "LabelSettingsLibraryBookshelfView": "Boekenplank-view voor bibliotheek", "LabelSettingsOverdriveMediaMarkers": "Gebruik Overdrive media markers voor hoofdstukken", @@ -461,9 +461,9 @@ "LabelTagsAccessibleToUser": "Tags toegankelijk voor de gebruiker", "LabelTagsNotAccessibleToUser": "Tags niet toegankelijk voor de gebruiker", "LabelTasks": "Lopende taken", - "LabelTheme": "Theme", - "LabelThemeDark": "Dark", - "LabelThemeLight": "Light", + "LabelTheme": "Thema", + "LabelThemeDark": "Donker", + "LabelThemeLight": "Licht", "LabelTimeBase": "Tijdsbasis", "LabelTimeListened": "Tijd geluisterd", "LabelTimeListenedToday": "Tijd geluisterd vandaag", @@ -482,8 +482,8 @@ "LabelTrackFromMetadata": "Track vanuit metadata", "LabelTracks": "Tracks", "LabelTracksMultiTrack": "Multi-track", - "LabelTracksNone": "No tracks", - "LabelTracksSingleTrack": "Single-track", + "LabelTracksNone": "Geen tracks", + "LabelTracksSingleTrack": "Enkele track", "LabelType": "Type", "LabelUnabridged": "Onverkort", "LabelUnknown": "Onbekend", @@ -525,16 +525,16 @@ "MessageCheckingCron": "Cron aan het checken...", "MessageConfirmCloseFeed": "Are you sure you want to close this feed?", "MessageConfirmDeleteBackup": "Weet je zeker dat je de backup voor {0} wil verwijderen?", - "MessageConfirmDeleteFile": "This will delete the file from your file system. Are you sure?", + "MessageConfirmDeleteFile": "Dit verwijdert het bestand uit het bestandssysteem. Weet je het zeker?", "MessageConfirmDeleteLibrary": "Weet je zeker dat je de bibliotheek \"{0}\" permanent wil verwijderen?", "MessageConfirmDeleteSession": "Weet je zeker dat je deze sessie wil verwijderen?", "MessageConfirmForceReScan": "Weet je zeker dat je geforceerd opnieuw wil scannen?", - "MessageConfirmMarkAllEpisodesFinished": "Are you sure you want to mark all episodes as finished?", - "MessageConfirmMarkAllEpisodesNotFinished": "Are you sure you want to mark all episodes as not finished?", + "MessageConfirmMarkAllEpisodesFinished": "Weet je zeker dat je alle afleveringen als voltooid wil markeren?", + "MessageConfirmMarkAllEpisodesNotFinished": "Weet je zeker dat je alle afleveringen als niet-voltooid wil markeren?", "MessageConfirmMarkSeriesFinished": "Weet je zeker dat je alle boeken in deze serie wil markeren als voltooid?", "MessageConfirmMarkSeriesNotFinished": "Weet je zeker dat je alle boeken in deze serie wil markeren als niet voltooid?", "MessageConfirmRemoveAllChapters": "Weet je zeker dat je alle hoofdstukken wil verwijderen?", - "MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?", + "MessageConfirmRemoveAuthor": "Weet je zeker dat je auteur \"{0}\" wil verwijderen?", "MessageConfirmRemoveCollection": "Weet je zeker dat je de collectie \"{0}\" wil verwijderen?", "MessageConfirmRemoveEpisode": "Weet je zeker dat je de aflevering \"{0}\" wil verwijderen?", "MessageConfirmRemoveEpisodes": "Weet je zeker dat je {0} afleveringen wil verwijderen?", @@ -546,7 +546,7 @@ "MessageConfirmRenameTag": "Weet je zeker dat je tag \"{0}\" wil hernoemen naar\"{1}\" voor alle onderdelen?", "MessageConfirmRenameTagMergeNote": "Opmerking: Deze tag bestaat al, dus zullen ze worden samengevoegd.", "MessageConfirmRenameTagWarning": "Waarschuwing! Een gelijknamige tag met ander hoofdlettergebruik bestaat al: \"{0}\".", - "MessageConfirmSendEbookToDevice": "Are you sure you want to send {0} ebook \"{1}\" to device \"{2}\"?", + "MessageConfirmSendEbookToDevice": "Weet je zeker dat je {0} ebook \"{1}\" naar apparaat \"{2}\" wil sturen?", "MessageDownloadingEpisode": "Aflevering aan het dowloaden", "MessageDragFilesIntoTrackOrder": "Sleep bestanden in de juiste trackvolgorde", "MessageEmbedFinished": "Insluiting voltooid!", @@ -565,8 +565,8 @@ "MessageM4BFailed": "M4B mislukt!", "MessageM4BFinished": "M4B voltooid!", "MessageMapChapterTitles": "Map hoofdstuktitels naar je bestaande audioboekhoofdstukken zonder aanpassing van tijden", - "MessageMarkAllEpisodesFinished": "Mark all episodes finished", - "MessageMarkAllEpisodesNotFinished": "Mark all episodes not finished", + "MessageMarkAllEpisodesFinished": "Markeer alle afleveringen als voltooid", + "MessageMarkAllEpisodesNotFinished": "Markeer alle afleveringen als niet voltooid", "MessageMarkAsFinished": "Markeer als Voltooid", "MessageMarkAsNotFinished": "Markeer als Niet Voltooid", "MessageMatchBooksDescription": "zal proberen boeken in de bibliotheek te matchen met een boek uit de geselecteerde bron en lege details en coverafbeelding te vullen. Overschrijft details niet.", @@ -702,8 +702,8 @@ "ToastRemoveItemFromCollectionSuccess": "Onderdeel verwijderd uit collectie", "ToastRSSFeedCloseFailed": "Sluiten RSS-feed mislukt", "ToastRSSFeedCloseSuccess": "RSS-feed gesloten", - "ToastSendEbookToDeviceFailed": "Failed to Send Ebook to device", - "ToastSendEbookToDeviceSuccess": "Ebook sent to device \"{0}\"", + "ToastSendEbookToDeviceFailed": "Ebook naar apparaat sturen mislukt", + "ToastSendEbookToDeviceSuccess": "Ebook verstuurd naar apparaat \"{0}\"", "ToastSeriesUpdateFailed": "Bijwerken serie mislukt", "ToastSeriesUpdateSuccess": "Bijwerken serie gelukt", "ToastSessionDeleteFailed": "Verwijderen sessie mislukt", @@ -713,4 +713,4 @@ "ToastSocketFailedToConnect": "Verbinding Socket mislukt", "ToastUserDeleteFailed": "Verwijderen gebruiker mislukt", "ToastUserDeleteSuccess": "Gebruiker verwijderd" -} \ No newline at end of file +} From 1b22205f74b7aa081e918284b4d19fe702caace7 Mon Sep 17 00:00:00 2001 From: advplyr Date: Sat, 30 Sep 2023 12:39:16 -0500 Subject: [PATCH 04/15] Update:Add libraryItems table index to improve performance #2073 --- server/models/LibraryItem.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/models/LibraryItem.js b/server/models/LibraryItem.js index 4ca599dd6..d17dbd153 100644 --- a/server/models/LibraryItem.js +++ b/server/models/LibraryItem.js @@ -794,6 +794,9 @@ class LibraryItem extends Model { { fields: ['libraryId', 'mediaType'] }, + { + fields: ['libraryId', 'mediaId', 'mediaType'] + }, { fields: ['birthtime'] }, From 8d3a326216022fa6da5dc6bc1f16fab54415a35b Mon Sep 17 00:00:00 2001 From: advplyr Date: Sat, 30 Sep 2023 14:19:10 -0500 Subject: [PATCH 05/15] Fix:Newest episodes home page shelf #2119 --- server/utils/queries/libraryItemsPodcastFilters.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/utils/queries/libraryItemsPodcastFilters.js b/server/utils/queries/libraryItemsPodcastFilters.js index 63dc915fa..27ac3fcd8 100644 --- a/server/utils/queries/libraryItemsPodcastFilters.js +++ b/server/utils/queries/libraryItemsPodcastFilters.js @@ -247,7 +247,7 @@ module.exports = { podcastEpisodeWhere['$mediaProgresses.isFinished$'] = true } } else if (filterGroup === 'recent') { - libraryItemWhere['createdAt'] = { + podcastEpisodeWhere['createdAt'] = { [Sequelize.Op.gte]: new Date(new Date() - (60 * 24 * 60 * 60 * 1000)) // 60 days ago } } From 1dc369180cf2005beec40275cc75ab245eb2b7e7 Mon Sep 17 00:00:00 2001 From: advplyr Date: Sat, 30 Sep 2023 14:32:40 -0500 Subject: [PATCH 06/15] Fix:Home page recent series shelf respect hide single book series library setting #2134 --- server/utils/queries/libraryFilters.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/server/utils/queries/libraryFilters.js b/server/utils/queries/libraryFilters.js index 2d93ebaf8..6ba6ec5e0 100644 --- a/server/utils/queries/libraryFilters.js +++ b/server/utils/queries/libraryFilters.js @@ -205,6 +205,15 @@ module.exports = { } } ] + + // Handle library setting to hide single book series + // TODO: Merge with existing query + if (library.settings.hideSingleBookSeries) { + seriesWhere.push(Sequelize.where(Sequelize.literal(`(SELECT count(*) FROM books b, bookSeries bs WHERE bs.seriesId = series.id AND bs.bookId = b.id)`), { + [Sequelize.Op.gt]: 1 + })) + } + // Handle user permissions to only include series with at least 1 book // TODO: Simplify to a single query if (userPermissionBookWhere.bookWhere.length) { From 3d96749d385aca1367fb48612f20a7450ed5db81 Mon Sep 17 00:00:00 2001 From: advplyr Date: Sat, 30 Sep 2023 15:12:37 -0500 Subject: [PATCH 07/15] Fix:Downloading podcasts with watcher causing duplicate episodes #2122 --- .../tables/podcast/EpisodesTable.vue | 2 +- server/Logger.js | 2 +- server/Watcher.js | 25 +++++++++++++------ 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/client/components/tables/podcast/EpisodesTable.vue b/client/components/tables/podcast/EpisodesTable.vue index 8b29198c9..9534b34e1 100644 --- a/client/components/tables/podcast/EpisodesTable.vue +++ b/client/components/tables/podcast/EpisodesTable.vue @@ -191,7 +191,7 @@ export default { } }, methods: { - search() {}, + submit() {}, inputUpdate() { clearTimeout(this.searchTimeout) this.searchTimeout = setTimeout(() => { diff --git a/server/Logger.js b/server/Logger.js index b49220dff..19e657b41 100644 --- a/server/Logger.js +++ b/server/Logger.js @@ -92,7 +92,7 @@ class Logger { * @param {...any} args */ dev(...args) { - if (!this.isDev) return + if (!this.isDev || process.env.HIDE_DEV_LOGS === '1') return console.log(`[${this.timestamp}] DEV:`, ...args) } diff --git a/server/Watcher.js b/server/Watcher.js index bc6b6094f..577460a4c 100644 --- a/server/Watcher.js +++ b/server/Watcher.js @@ -28,6 +28,8 @@ class FolderWatcher extends EventEmitter { this.ignoreDirs = [] /** @type {string[]} */ this.pendingDirsToRemoveFromIgnore = [] + /** @type {NodeJS.Timeout} */ + this.removeFromIgnoreTimer = null this.disabled = false } @@ -240,9 +242,12 @@ class FolderWatcher extends EventEmitter { */ addIgnoreDir(path) { path = this.cleanDirPath(path) - if (this.ignoreDirs.includes(path)) return this.pendingDirsToRemoveFromIgnore = this.pendingDirsToRemoveFromIgnore.filter(p => p !== path) - Logger.debug(`[Watcher] Ignoring directory "${path}"`) + if (this.ignoreDirs.includes(path)) { + // Already ignoring dir + return + } + Logger.debug(`[Watcher] addIgnoreDir: Ignoring directory "${path}"`) this.ignoreDirs.push(path) } @@ -255,18 +260,24 @@ class FolderWatcher extends EventEmitter { */ removeIgnoreDir(path) { path = this.cleanDirPath(path) - if (!this.ignoreDirs.includes(path) || this.pendingDirsToRemoveFromIgnore.includes(path)) return + if (!this.ignoreDirs.includes(path)) { + Logger.debug(`[Watcher] removeIgnoreDir: Path is not being ignored "${path}"`) + return + } // Add a 5 second delay before removing the ignore from this dir - this.pendingDirsToRemoveFromIgnore.push(path) - setTimeout(() => { + if (!this.pendingDirsToRemoveFromIgnore.includes(path)) { + this.pendingDirsToRemoveFromIgnore.push(path) + } + + clearTimeout(this.removeFromIgnoreTimer) + this.removeFromIgnoreTimer = setTimeout(() => { if (this.pendingDirsToRemoveFromIgnore.includes(path)) { this.pendingDirsToRemoveFromIgnore = this.pendingDirsToRemoveFromIgnore.filter(p => p !== path) - Logger.debug(`[Watcher] No longer ignoring directory "${path}"`) + Logger.debug(`[Watcher] removeIgnoreDir: No longer ignoring directory "${path}"`) this.ignoreDirs = this.ignoreDirs.filter(p => p !== path) } }, 5000) - } } module.exports = FolderWatcher \ No newline at end of file From c2c8cf919ed91821fe218068196aac6edfc1347a Mon Sep 17 00:00:00 2001 From: advplyr Date: Sat, 30 Sep 2023 16:01:10 -0500 Subject: [PATCH 08/15] Fix:Bad backup causing other backup files to not be displayed #1961 --- server/controllers/MeController.js | 2 +- server/managers/BackupManager.js | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/server/controllers/MeController.js b/server/controllers/MeController.js index b03868a07..d38c1c4a7 100644 --- a/server/controllers/MeController.js +++ b/server/controllers/MeController.js @@ -196,7 +196,7 @@ class MeController { const libraryItem = await Database.libraryItemModel.getOldById(localProgress.libraryItemId) if (!libraryItem) { - Logger.error(`[MeController] syncLocalMediaProgress invalid local media progress object no library item`, localProgress) + Logger.error(`[MeController] syncLocalMediaProgress invalid local media progress object no library item with id "${localProgress.libraryItemId}"`, localProgress) continue } diff --git a/server/managers/BackupManager.js b/server/managers/BackupManager.js index 50d6d19c5..ef8ed643d 100644 --- a/server/managers/BackupManager.js +++ b/server/managers/BackupManager.js @@ -100,7 +100,7 @@ class BackupManager { let entries try { entries = await zip.entries() - } catch(error){ + } catch (error) { // Not a valid zip file Logger.error('[BackupManager] Failed to read backup file - backup might not be a valid .zip file', tempPath, error) return res.status(400).send('Failed to read backup file - backup might not be a valid .zip file') @@ -182,7 +182,6 @@ class BackupManager { data = await zip.entryData('details') } catch (error) { Logger.error(`[BackupManager] Failed to unzip backup "${fullFilePath}"`, error) - await zip.close() continue } From f2aed08d51df82c2b95e18907966a724e49a0d8f Mon Sep 17 00:00:00 2001 From: advplyr Date: Sat, 30 Sep 2023 16:04:06 -0500 Subject: [PATCH 09/15] Version bump v2.4.4 --- client/package-lock.json | 4 ++-- client/package.json | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index cf9491cc4..25000ab00 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1,12 +1,12 @@ { "name": "audiobookshelf-client", - "version": "2.4.3", + "version": "2.4.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "audiobookshelf-client", - "version": "2.4.3", + "version": "2.4.4", "license": "ISC", "dependencies": { "@nuxtjs/axios": "^5.13.6", diff --git a/client/package.json b/client/package.json index 3c90c31f2..cc7859269 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "audiobookshelf-client", - "version": "2.4.3", + "version": "2.4.4", "description": "Self-hosted audiobook and podcast client", "main": "index.js", "scripts": { diff --git a/package-lock.json b/package-lock.json index 9870d246d..779480045 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "audiobookshelf", - "version": "2.4.3", + "version": "2.4.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "audiobookshelf", - "version": "2.4.3", + "version": "2.4.4", "license": "GPL-3.0", "dependencies": { "axios": "^0.27.2", diff --git a/package.json b/package.json index 88f02c2c0..4a00fa59c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "audiobookshelf", - "version": "2.4.3", + "version": "2.4.4", "description": "Self-hosted audiobook and podcast server", "main": "index.js", "scripts": { From e10b1785651b672b9fcd50dcc7e9018e614fbd36 Mon Sep 17 00:00:00 2001 From: advplyr Date: Sun, 1 Oct 2023 09:03:01 -0500 Subject: [PATCH 10/15] Fix:Crash on failed scanner find covers #2164 --- server/scanner/BookScanner.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/scanner/BookScanner.js b/server/scanner/BookScanner.js index f8e959bde..187ecac1d 100644 --- a/server/scanner/BookScanner.js +++ b/server/scanner/BookScanner.js @@ -1111,7 +1111,7 @@ class BookScanner { const result = await CoverManager.downloadCoverFromUrlNew(results[i], libraryItemId, libraryItemPath) if (result.error) { - Logger.error(`[Scanner] Failed to download cover from url "${results[i]}" | Attempt ${i + 1}`, result.error) + libraryScan.addLog(LogLevel.ERROR, `Failed to download cover from url "${results[i]}" | Attempt ${i + 1}`, result.error) } else if (result.cover) { return result.cover } From 20a1d40d990b1d14750ba061a0dd808a016cb0ec Mon Sep 17 00:00:00 2001 From: advplyr Date: Sun, 1 Oct 2023 12:44:52 -0500 Subject: [PATCH 11/15] Fix:Set date properly on local playback sessions #2168 --- server/controllers/LibraryItemController.js | 6 ------ server/objects/PlaybackSession.js | 8 +++++++- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/server/controllers/LibraryItemController.js b/server/controllers/LibraryItemController.js index 498398f72..4aff7a139 100644 --- a/server/controllers/LibraryItemController.js +++ b/server/controllers/LibraryItemController.js @@ -280,12 +280,6 @@ class LibraryItemController { return CacheManager.handleCoverCache(res, libraryItem.id, libraryItem.media.coverPath, options) } - // GET: api/items/:id/stream - openStream(req, res) { - // this.streamManager.openStreamApiRequest(res, req.user, req.libraryItem) - res.sendStatus(500) - } - // POST: api/items/:id/play startPlaybackSession(req, res) { if (!req.libraryItem.media.numTracks && req.libraryItem.mediaType !== 'video') { diff --git a/server/objects/PlaybackSession.js b/server/objects/PlaybackSession.js index 4fe3d8fd5..20b50009c 100644 --- a/server/objects/PlaybackSession.js +++ b/server/objects/PlaybackSession.js @@ -168,7 +168,13 @@ class PlaybackSession { this.currentTime = session.currentTime || 0 this.startedAt = session.startedAt - this.updatedAt = session.updatedAt || null + this.updatedAt = session.updatedAt || session.startedAt + + // Local playback sessions dont set this date field so set using updatedAt + if (!this.date && session.updatedAt) { + this.date = date.format(new Date(session.updatedAt), 'YYYY-MM-DD') + this.dayOfWeek = date.format(new Date(session.updatedAt), 'dddd') + } } get mediaItemId() { From 73bb73a04aa04fb123a9ff1ef8e96e3ce5c2201e Mon Sep 17 00:00:00 2001 From: Alistair Bahr Date: Mon, 2 Oct 2023 09:25:34 +0200 Subject: [PATCH 12/15] make force transcode apply to all "ffmpeg error 1" --- server/objects/Stream.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/objects/Stream.js b/server/objects/Stream.js index c8452ac39..1860c47f2 100644 --- a/server/objects/Stream.js +++ b/server/objects/Stream.js @@ -340,7 +340,7 @@ class Stream extends EventEmitter { Logger.error('Ffmpeg Err', '"' + err.message + '"') // Temporary workaround for https://github.com/advplyr/audiobookshelf/issues/172 - const aacErrorMsg = 'ffmpeg exited with code 1: Could not write header for output file #0 (incorrect codec parameters ?)' + const aacErrorMsg = 'ffmpeg exited with code 1:' if (audioCodec === 'copy' && this.isAACEncodable && err.message && err.message.startsWith(aacErrorMsg)) { Logger.info(`[Stream] Re-attempting stream with AAC encode`) this.transcodeOptions.forceAAC = true From 4352989242bf9a25b092deafb7c50b5e17a0eadc Mon Sep 17 00:00:00 2001 From: Alistair1231 Date: Mon, 2 Oct 2023 09:30:57 +0200 Subject: [PATCH 13/15] update comment to include second issue that is adressed by change --- server/objects/Stream.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/objects/Stream.js b/server/objects/Stream.js index 1860c47f2..9f5ff59b2 100644 --- a/server/objects/Stream.js +++ b/server/objects/Stream.js @@ -339,7 +339,7 @@ class Stream extends EventEmitter { } else { Logger.error('Ffmpeg Err', '"' + err.message + '"') - // Temporary workaround for https://github.com/advplyr/audiobookshelf/issues/172 + // Temporary workaround for https://github.com/advplyr/audiobookshelf/issues/172 and https://github.com/advplyr/audiobookshelf/issues/2157 const aacErrorMsg = 'ffmpeg exited with code 1:' if (audioCodec === 'copy' && this.isAACEncodable && err.message && err.message.startsWith(aacErrorMsg)) { Logger.info(`[Stream] Re-attempting stream with AAC encode`) @@ -435,4 +435,4 @@ class Stream extends EventEmitter { return newAudioTrack } } -module.exports = Stream \ No newline at end of file +module.exports = Stream From 7c9631c1b0dbff6e15a33c72945ef69449225e0d Mon Sep 17 00:00:00 2001 From: advplyr Date: Mon, 2 Oct 2023 08:34:56 -0500 Subject: [PATCH 14/15] Update server/objects/Stream.js --- server/objects/Stream.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/objects/Stream.js b/server/objects/Stream.js index 9f5ff59b2..115bb96e6 100644 --- a/server/objects/Stream.js +++ b/server/objects/Stream.js @@ -341,7 +341,7 @@ class Stream extends EventEmitter { // Temporary workaround for https://github.com/advplyr/audiobookshelf/issues/172 and https://github.com/advplyr/audiobookshelf/issues/2157 const aacErrorMsg = 'ffmpeg exited with code 1:' - if (audioCodec === 'copy' && this.isAACEncodable && err.message && err.message.startsWith(aacErrorMsg)) { + if (audioCodec === 'copy' && this.isAACEncodable && err.message?.startsWith(aacErrorMsg)) { Logger.info(`[Stream] Re-attempting stream with AAC encode`) this.transcodeOptions.forceAAC = true this.reset(this.startTime) From a3a8937ba37b439dd40c406005c4bb280af0b12c Mon Sep 17 00:00:00 2001 From: advplyr Date: Mon, 2 Oct 2023 17:09:12 -0500 Subject: [PATCH 15/15] Fix:Crash when searching for cover without an author #2174 --- server/controllers/LibraryItemController.js | 1 - server/controllers/SearchController.js | 2 +- server/finders/BookFinder.js | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/server/controllers/LibraryItemController.js b/server/controllers/LibraryItemController.js index 4aff7a139..2b25474e5 100644 --- a/server/controllers/LibraryItemController.js +++ b/server/controllers/LibraryItemController.js @@ -259,7 +259,6 @@ class LibraryItemController { // Check if library item media has a cover path if (!libraryItem.media.coverPath || !await fs.pathExists(libraryItem.media.coverPath)) { - Logger.debug(`[LibraryItemController] getCover: Library item "${req.params.id}" has no cover path`) return res.sendStatus(404) } diff --git a/server/controllers/SearchController.js b/server/controllers/SearchController.js index 2749016c3..93587bc4f 100644 --- a/server/controllers/SearchController.js +++ b/server/controllers/SearchController.js @@ -26,7 +26,7 @@ class SearchController { let results = null if (podcast) results = await PodcastFinder.findCovers(query.title) - else results = await BookFinder.findCovers(query.provider || 'google', query.title, query.author || null) + else results = await BookFinder.findCovers(query.provider || 'google', query.title, query.author || '') res.json({ results }) diff --git a/server/finders/BookFinder.js b/server/finders/BookFinder.js index 96735cc95..bcf8ea068 100644 --- a/server/finders/BookFinder.js +++ b/server/finders/BookFinder.js @@ -234,7 +234,7 @@ class BookFinder { if (!books.length && maxFuzzySearches > 0) { // normalize title and author title = title.trim().toLowerCase() - author = author.trim().toLowerCase() + author = author?.trim().toLowerCase() || '' // Now run up to maxFuzzySearches fuzzy searches let candidates = new Set()