From d94482827a3093482919587fc902e3ab0187ba51 Mon Sep 17 00:00:00 2001
From: Dr-Blank <64108942+Dr-Blank@users.noreply.github.com>
Date: Fri, 7 Apr 2023 04:41:38 -0400
Subject: [PATCH 01/41] Kicked of Hindi Translation.
---
client/strings/hi.json | 641 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 641 insertions(+)
create mode 100644 client/strings/hi.json
diff --git a/client/strings/hi.json b/client/strings/hi.json
new file mode 100644
index 00000000..b146df45
--- /dev/null
+++ b/client/strings/hi.json
@@ -0,0 +1,641 @@
+{
+ "ButtonAdd": "जोड़ें",
+ "ButtonAddChapters": "अध्याय जोड़ें",
+ "ButtonAddPodcasts": "पॉडकास्ट जोड़ें",
+ "ButtonAddYourFirstLibrary": "अपनी पहली पुस्तकालय जोड़ें",
+ "ButtonApply": "लागू करें",
+ "ButtonApplyChapters": "अध्यायों में परिवर्तन लागू करें",
+ "ButtonAuthors": "लेखक",
+ "ButtonBrowseForFolder": "फ़ोल्डर खोजें",
+ "ButtonCancel": "रद्द करें",
+ "ButtonCancelEncode": "एनकोड रद्द करें",
+ "ButtonChangeRootPassword": "रूट का पासवर्ड बदलें",
+ "ButtonCheckAndDownloadNewEpisodes": "नए एपिसोड खोजें और डाउनलोड करें",
+ "ButtonChooseAFolder": "एक फ़ोल्डर चुनें",
+ "ButtonChooseFiles": "फ़ाइलें चुनें",
+ "ButtonClearFilter": "लागू फ़िल्टर साफ़ करें",
+ "ButtonCloseFeed": "फ़ीड बंद करें",
+ "ButtonCollections": "संग्रह",
+ "ButtonConfigureScanner": "स्कैनर सेटिंग्स बदलें",
+ "ButtonCreate": "बनाएं",
+ "ButtonCreateBackup": "बैकअप लें",
+ "ButtonDelete": "हटाएं",
+ "ButtonDownloadQueue": "कतार डाउनलोड करें",
+ "ButtonEdit": "संपादित करें",
+ "ButtonEditChapters": "अध्याय संपादित करें",
+ "ButtonEditPodcast": "पॉडकास्ट संपादित करें",
+ "ButtonForceReScan": "बलपूर्वक पुन: स्कैन करें",
+ "ButtonFullPath": "पूर्ण पथ",
+ "ButtonHide": "छुपाएं",
+ "ButtonHome": "घर",
+ "ButtonIssues": "समस्याएं",
+ "ButtonLatest": "नवीनतम",
+ "ButtonLibrary": "पुस्तकालय",
+ "ButtonLogout": "लॉग आउट",
+ "ButtonLookup": "तलाश करें",
+ "ButtonManageTracks": "ट्रैक्स मैनेज करें",
+ "ButtonMapChapterTitles": "अध्यायों का मिलान करें",
+ "ButtonMatchAllAuthors": "सभी लेखकों को तलाश करें",
+ "ButtonMatchBooks": "संबंधित पुस्तकों का मिलान करें",
+ "ButtonNevermind": "कोई बात नहीं",
+ "ButtonOk": "ठीक है",
+ "ButtonOpenFeed": "फ़ीड खोलें",
+ "ButtonOpenManager": "मैनेजर खोलें",
+ "ButtonPlay": "चलाएँ",
+ "ButtonPlaying": "चल रही है",
+ "ButtonPlaylists": "प्लेलिस्ट्स",
+ "ButtonPurgeAllCache": "सभी Cache मिटाएं",
+ "ButtonPurgeItemsCache": "आइटम Cache मिटाएं",
+ "ButtonPurgeMediaProgress": "अभी तक सुना हुआ सब हटा दे",
+ "ButtonQueueAddItem": "क़तार में जोड़ें",
+ "ButtonQueueRemoveItem": "कतार से हटाएं",
+ "ButtonQuickMatch": "जल्दी से समानता की तलाश करें",
+ "ButtonRead": "पढ़ लिया",
+ "ButtonRemove": "हटाएं",
+ "ButtonRemoveAll": "सभी हटाएं",
+ "ButtonRemoveAllLibraryItems": "पुस्तकालय की सभी आइटम हटाएं",
+ "ButtonRemoveFromContinueListening": "सुनना जारी रखें से हटाएं",
+ "ButtonRemoveSeriesFromContinueSeries": "इस सीरीज को कंटिन्यू सीरीज से हटा दें",
+ "ButtonReScan": "पुन: स्कैन करें",
+ "ButtonReset": "रीसेट करें",
+ "ButtonRestore": "पुनर्स्थापित करें",
+ "ButtonSave": "सहेजें",
+ "ButtonSaveAndClose": "सहेजें और बंद करें",
+ "ButtonSaveTracklist": "ट्रैक सूची सहेजें",
+ "ButtonScan": "स्कैन करें",
+ "ButtonScanLibrary": "पुस्तकालय स्कैन करें",
+ "ButtonSearch": "खोजें",
+ "ButtonSelectFolderPath": "फ़ोल्डर का पथ चुनें",
+ "ButtonSeries": "सीरीज",
+ "ButtonSetChaptersFromTracks": "ट्रैक्स से अध्याय बनाएं",
+ "ButtonShiftTimes": "समय खिसकाए",
+ "ButtonShow": "दिखाएं",
+ "ButtonStartM4BEncode": "M4B एन्कोडिंग शुरू करें",
+ "ButtonStartMetadataEmbed": "मेटाडेटा एम्बेडिंग शुरू करें",
+ "ButtonSubmit": "जमा करें",
+ "ButtonUpload": "अपलोड करें",
+ "ButtonUploadBackup": "बैकअप अपलोड करें",
+ "ButtonUploadCover": "कवर अपलोड करें",
+ "ButtonUploadOPMLFile": "OPML फ़ाइल अपलोड करें",
+ "ButtonUserDelete": "उपयोगकर्ता {0} को हटाएं",
+ "ButtonUserEdit": "उपयोगकर्ता {0} को संपादित करें",
+ "ButtonViewAll": "सभी को देखें",
+ "ButtonYes": "हाँ",
+ "HeaderAccount": "खाता",
+ "HeaderAdvanced": "विकसित",
+ "HeaderAppriseNotificationSettings": "Apprise अधिसूचना सेटिंग्स",
+ "HeaderAudiobookTools": "Audiobook File Management Tools",
+ "HeaderAudioTracks": "Audio Tracks",
+ "HeaderBackups": "Backups",
+ "HeaderChangePassword": "Change Password",
+ "HeaderChapters": "Chapters",
+ "HeaderChooseAFolder": "Choose a Folder",
+ "HeaderCollection": "Collection",
+ "HeaderCollectionItems": "Collection Items",
+ "HeaderCover": "Cover",
+ "HeaderCurrentDownloads": "Current Downloads",
+ "HeaderDetails": "Details",
+ "HeaderDownloadQueue": "Download Queue",
+ "HeaderEpisodes": "Episodes",
+ "HeaderFiles": "Files",
+ "HeaderFindChapters": "Find Chapters",
+ "HeaderIgnoredFiles": "Ignored Files",
+ "HeaderItemFiles": "Item Files",
+ "HeaderItemMetadataUtils": "Item Metadata Utils",
+ "HeaderLastListeningSession": "Last Listening Session",
+ "HeaderLatestEpisodes": "Latest episodes",
+ "HeaderLibraries": "Libraries",
+ "HeaderLibraryFiles": "Library Files",
+ "HeaderLibraryStats": "Library Stats",
+ "HeaderListeningSessions": "Listening Sessions",
+ "HeaderListeningStats": "Listening Stats",
+ "HeaderLogin": "Login",
+ "HeaderLogs": "Logs",
+ "HeaderManageGenres": "Manage Genres",
+ "HeaderManageTags": "Manage Tags",
+ "HeaderMapDetails": "Map details",
+ "HeaderMatch": "Match",
+ "HeaderMetadataToEmbed": "Metadata to embed",
+ "HeaderNewAccount": "New Account",
+ "HeaderNewLibrary": "New Library",
+ "HeaderNotifications": "Notifications",
+ "HeaderOpenRSSFeed": "Open RSS Feed",
+ "HeaderOtherFiles": "Other Files",
+ "HeaderPermissions": "Permissions",
+ "HeaderPlayerQueue": "Player Queue",
+ "HeaderPlaylist": "Playlist",
+ "HeaderPlaylistItems": "Playlist Items",
+ "HeaderPodcastsToAdd": "Podcasts to Add",
+ "HeaderPreviewCover": "Preview Cover",
+ "HeaderRemoveEpisode": "Remove Episode",
+ "HeaderRemoveEpisodes": "Remove {0} Episodes",
+ "HeaderRSSFeedGeneral": "RSS Details",
+ "HeaderRSSFeedIsOpen": "RSS Feed is Open",
+ "HeaderSavedMediaProgress": "Saved Media Progress",
+ "HeaderSchedule": "Schedule",
+ "HeaderScheduleLibraryScans": "Schedule Automatic Library Scans",
+ "HeaderSession": "Session",
+ "HeaderSetBackupSchedule": "Set Backup Schedule",
+ "HeaderSettings": "Settings",
+ "HeaderSettingsDisplay": "Display",
+ "HeaderSettingsExperimental": "Experimental Features",
+ "HeaderSettingsGeneral": "General",
+ "HeaderSettingsScanner": "Scanner",
+ "HeaderSleepTimer": "Sleep Timer",
+ "HeaderStatsLargestItems": "Largest Items",
+ "HeaderStatsLongestItems": "Longest Items (hrs)",
+ "HeaderStatsMinutesListeningChart": "Minutes Listening (last 7 days)",
+ "HeaderStatsRecentSessions": "Recent Sessions",
+ "HeaderStatsTop10Authors": "Top 10 Authors",
+ "HeaderStatsTop5Genres": "Top 5 Genres",
+ "HeaderTools": "Tools",
+ "HeaderUpdateAccount": "Update Account",
+ "HeaderUpdateAuthor": "Update Author",
+ "HeaderUpdateDetails": "Update Details",
+ "HeaderUpdateLibrary": "Update Library",
+ "HeaderUsers": "Users",
+ "HeaderYourStats": "Your Stats",
+ "LabelAbridged": "Abridged",
+ "LabelAccountType": "Account Type",
+ "LabelAccountTypeAdmin": "Admin",
+ "LabelAccountTypeGuest": "Guest",
+ "LabelAccountTypeUser": "User",
+ "LabelActivity": "Activity",
+ "LabelAddedAt": "Added At",
+ "LabelAddToCollection": "Add to Collection",
+ "LabelAddToCollectionBatch": "Add {0} Books to Collection",
+ "LabelAddToPlaylist": "Add to Playlist",
+ "LabelAddToPlaylistBatch": "Add {0} Items to Playlist",
+ "LabelAll": "All",
+ "LabelAllUsers": "All Users",
+ "LabelAlreadyInYourLibrary": "Already in your library",
+ "LabelAppend": "Append",
+ "LabelAuthor": "Author",
+ "LabelAuthorFirstLast": "Author (First Last)",
+ "LabelAuthorLastFirst": "Author (Last, First)",
+ "LabelAuthors": "Authors",
+ "LabelAutoDownloadEpisodes": "Auto Download Episodes",
+ "LabelBackToUser": "Back to User",
+ "LabelBackupsEnableAutomaticBackups": "Enable automatic backups",
+ "LabelBackupsEnableAutomaticBackupsHelp": "Backups saved in /metadata/backups",
+ "LabelBackupsMaxBackupSize": "Maximum backup size (in GB)",
+ "LabelBackupsMaxBackupSizeHelp": "As a safeguard against misconfiguration, backups will fail if they exceed the configured size.",
+ "LabelBackupsNumberToKeep": "Number of backups to keep",
+ "LabelBackupsNumberToKeepHelp": "Only 1 backup will be removed at a time so if you already have more backups than this you should manually remove them.",
+ "LabelBooks": "Books",
+ "LabelChangePassword": "Change Password",
+ "LabelChaptersFound": "chapters found",
+ "LabelChapterTitle": "Chapter Title",
+ "LabelClosePlayer": "Close player",
+ "LabelCollapseSeries": "Collapse Series",
+ "LabelCollections": "Collections",
+ "LabelComplete": "Complete",
+ "LabelConfirmPassword": "Confirm Password",
+ "LabelContinueListening": "Continue Listening",
+ "LabelContinueSeries": "Continue Series",
+ "LabelCover": "Cover",
+ "LabelCoverImageURL": "Cover Image URL",
+ "LabelCreatedAt": "Created At",
+ "LabelCronExpression": "Cron Expression",
+ "LabelCurrent": "Current",
+ "LabelCurrently": "Currently:",
+ "LabelCustomCronExpression": "Custom Cron Expression:",
+ "LabelDatetime": "Datetime",
+ "LabelDescription": "Description",
+ "LabelDeselectAll": "Deselect All",
+ "LabelDevice": "Device",
+ "LabelDeviceInfo": "Device Info",
+ "LabelDirectory": "Directory",
+ "LabelDiscFromFilename": "Disc from Filename",
+ "LabelDiscFromMetadata": "Disc from Metadata",
+ "LabelDownload": "Download",
+ "LabelDuration": "Duration",
+ "LabelDurationFound": "Duration found:",
+ "LabelEdit": "Edit",
+ "LabelEnable": "Enable",
+ "LabelEnd": "End",
+ "LabelEpisode": "Episode",
+ "LabelEpisodeTitle": "Episode Title",
+ "LabelEpisodeType": "Episode Type",
+ "LabelExample": "Example",
+ "LabelExplicit": "Explicit",
+ "LabelFeedURL": "Feed URL",
+ "LabelFile": "File",
+ "LabelFileBirthtime": "File Birthtime",
+ "LabelFileModified": "File Modified",
+ "LabelFilename": "Filename",
+ "LabelFilterByUser": "Filter by User",
+ "LabelFindEpisodes": "Find Episodes",
+ "LabelFinished": "Finished",
+ "LabelFolder": "Folder",
+ "LabelFolders": "Folders",
+ "LabelGenre": "Genre",
+ "LabelGenres": "Genres",
+ "LabelHardDeleteFile": "Hard delete file",
+ "LabelHour": "Hour",
+ "LabelIcon": "Icon",
+ "LabelIncludeInTracklist": "Include in Tracklist",
+ "LabelIncomplete": "Incomplete",
+ "LabelInProgress": "In Progress",
+ "LabelInterval": "Interval",
+ "LabelIntervalCustomDailyWeekly": "Custom daily/weekly",
+ "LabelIntervalEvery12Hours": "Every 12 hours",
+ "LabelIntervalEvery15Minutes": "Every 15 minutes",
+ "LabelIntervalEvery2Hours": "Every 2 hours",
+ "LabelIntervalEvery30Minutes": "Every 30 minutes",
+ "LabelIntervalEvery6Hours": "Every 6 hours",
+ "LabelIntervalEveryDay": "Every day",
+ "LabelIntervalEveryHour": "Every hour",
+ "LabelInvalidParts": "Invalid Parts",
+ "LabelItem": "Item",
+ "LabelLanguage": "Language",
+ "LabelLanguageDefaultServer": "Default Server Language",
+ "LabelLastSeen": "Last Seen",
+ "LabelLastTime": "Last Time",
+ "LabelLastUpdate": "Last Update",
+ "LabelLess": "Less",
+ "LabelLibrariesAccessibleToUser": "Libraries Accessible to User",
+ "LabelLibrary": "Library",
+ "LabelLibraryItem": "Library Item",
+ "LabelLibraryName": "Library Name",
+ "LabelLimit": "Limit",
+ "LabelListenAgain": "Listen Again",
+ "LabelLogLevelDebug": "Debug",
+ "LabelLogLevelInfo": "Info",
+ "LabelLogLevelWarn": "Warn",
+ "LabelLookForNewEpisodesAfterDate": "Look for new episodes after this date",
+ "LabelMediaPlayer": "Media Player",
+ "LabelMediaType": "Media Type",
+ "LabelMetadataProvider": "Metadata Provider",
+ "LabelMetaTag": "Meta Tag",
+ "LabelMinute": "Minute",
+ "LabelMissing": "Missing",
+ "LabelMissingParts": "Missing Parts",
+ "LabelMore": "More",
+ "LabelName": "Name",
+ "LabelNarrator": "Narrator",
+ "LabelNarrators": "Narrators",
+ "LabelNew": "New",
+ "LabelNewestAuthors": "Newest Authors",
+ "LabelNewestEpisodes": "Newest Episodes",
+ "LabelNewPassword": "New Password",
+ "LabelNextBackupDate": "Next backup date",
+ "LabelNextScheduledRun": "Next scheduled run",
+ "LabelNotes": "Notes",
+ "LabelNotFinished": "Not Finished",
+ "LabelNotificationAppriseURL": "Apprise URL(s)",
+ "LabelNotificationAvailableVariables": "Available variables",
+ "LabelNotificationBodyTemplate": "Body Template",
+ "LabelNotificationEvent": "Notification Event",
+ "LabelNotificationsMaxFailedAttempts": "Max failed attempts",
+ "LabelNotificationsMaxFailedAttemptsHelp": "Notifications are disabled once they fail to send this many times",
+ "LabelNotificationsMaxQueueSize": "Max queue size for notification events",
+ "LabelNotificationsMaxQueueSizeHelp": "Events are limited to firing 1 per second. Events will be ignored if the queue is at max size. This prevents notification spamming.",
+ "LabelNotificationTitleTemplate": "Title Template",
+ "LabelNotStarted": "Not Started",
+ "LabelNumberOfBooks": "Number of Books",
+ "LabelNumberOfEpisodes": "# of Episodes",
+ "LabelOpenRSSFeed": "Open RSS Feed",
+ "LabelOverwrite": "Overwrite",
+ "LabelPassword": "Password",
+ "LabelPath": "Path",
+ "LabelPermissionsAccessAllLibraries": "Can Access All Libraries",
+ "LabelPermissionsAccessAllTags": "Can Access All Tags",
+ "LabelPermissionsAccessExplicitContent": "Can Access Explicit Content",
+ "LabelPermissionsDelete": "Can Delete",
+ "LabelPermissionsDownload": "Can Download",
+ "LabelPermissionsUpdate": "Can Update",
+ "LabelPermissionsUpload": "Can Upload",
+ "LabelPhotoPathURL": "Photo Path/URL",
+ "LabelPlaylists": "Playlists",
+ "LabelPlayMethod": "Play Method",
+ "LabelPodcast": "Podcast",
+ "LabelPodcasts": "Podcasts",
+ "LabelPodcastType": "Podcast Type",
+ "LabelPrefixesToIgnore": "Prefixes to Ignore (case insensitive)",
+ "LabelPreventIndexing": "Prevent your feed from being indexed by iTunes and Google podcast directories",
+ "LabelProgress": "Progress",
+ "LabelProvider": "Provider",
+ "LabelPubDate": "Pub Date",
+ "LabelPublisher": "Publisher",
+ "LabelPublishYear": "Publish Year",
+ "LabelRecentlyAdded": "Recently Added",
+ "LabelRecentSeries": "Recent Series",
+ "LabelRecommended": "Recommended",
+ "LabelRegion": "Region",
+ "LabelReleaseDate": "Release Date",
+ "LabelRemoveCover": "Remove cover",
+ "LabelRSSFeedCustomOwnerEmail": "Custom owner Email",
+ "LabelRSSFeedCustomOwnerName": "Custom owner Name",
+ "LabelRSSFeedOpen": "RSS Feed Open",
+ "LabelRSSFeedPreventIndexing": "Prevent Indexing",
+ "LabelRSSFeedSlug": "RSS Feed Slug",
+ "LabelRSSFeedURL": "RSS Feed URL",
+ "LabelSearchTerm": "Search Term",
+ "LabelSearchTitle": "Search Title",
+ "LabelSearchTitleOrASIN": "Search Title or ASIN",
+ "LabelSeason": "Season",
+ "LabelSequence": "Sequence",
+ "LabelSeries": "Series",
+ "LabelSeriesName": "Series Name",
+ "LabelSeriesProgress": "Series Progress",
+ "LabelSettingsBookshelfViewHelp": "Skeumorphic design with wooden shelves",
+ "LabelSettingsChromecastSupport": "Chromecast support",
+ "LabelSettingsDateFormat": "Date Format",
+ "LabelSettingsDisableWatcher": "Disable Watcher",
+ "LabelSettingsDisableWatcherForLibrary": "Disable folder watcher for library",
+ "LabelSettingsDisableWatcherHelp": "Disables the automatic adding/updating of items when file changes are detected. *Requires server restart",
+ "LabelSettingsEnableEReader": "Enable e-reader for all users",
+ "LabelSettingsEnableEReaderHelp": "E-reader is still a work in progress, but use this setting to open it up to all your users (or use the \"Experimental Features\" toggle just for use by you)",
+ "LabelSettingsExperimentalFeatures": "Experimental features",
+ "LabelSettingsExperimentalFeaturesHelp": "Features in development that could use your feedback and help testing. Click to open github discussion.",
+ "LabelSettingsFindCovers": "Find covers",
+ "LabelSettingsFindCoversHelp": "If your audiobook does not have an embedded cover or a cover image inside the folder, the scanner will attempt to find a cover. Note: This will extend scan time",
+ "LabelSettingsHomePageBookshelfView": "Home page use bookshelf view",
+ "LabelSettingsLibraryBookshelfView": "Library use bookshelf view",
+ "LabelSettingsOverdriveMediaMarkers": "Use Overdrive Media Markers for chapters",
+ "LabelSettingsOverdriveMediaMarkersHelp": "MP3 files from Overdrive come with chapter timings embedded as custom metadata. Enabling this will use these tags for chapter timings automatically",
+ "LabelSettingsParseSubtitles": "Parse subtitles",
+ "LabelSettingsParseSubtitlesHelp": "Extract subtitles from audiobook folder names. Subtitle must be seperated by \" - \" i.e. \"Book Title - A Subtitle Here\" has the subtitle \"A Subtitle Here\"",
+ "LabelSettingsPreferAudioMetadata": "Prefer audio metadata",
+ "LabelSettingsPreferAudioMetadataHelp": "Audio file ID3 meta tags will be used for book details over folder names",
+ "LabelSettingsPreferMatchedMetadata": "Prefer matched metadata",
+ "LabelSettingsPreferMatchedMetadataHelp": "Matched data will overide item details when using Quick Match. By default Quick Match will only fill in missing details.",
+ "LabelSettingsPreferOPFMetadata": "Prefer OPF metadata",
+ "LabelSettingsPreferOPFMetadataHelp": "OPF file metadata will be used for book details over folder names",
+ "LabelSettingsSkipMatchingBooksWithASIN": "Skip matching books that already have an ASIN",
+ "LabelSettingsSkipMatchingBooksWithISBN": "Skip matching books that already have an ISBN",
+ "LabelSettingsSortingIgnorePrefixes": "Ignore prefixes when sorting",
+ "LabelSettingsSortingIgnorePrefixesHelp": "i.e. for prefix \"the\" book title \"The Book Title\" would sort as \"Book Title, The\"",
+ "LabelSettingsSquareBookCovers": "Use square book covers",
+ "LabelSettingsSquareBookCoversHelp": "Prefer to use square covers over standard 1.6:1 book covers",
+ "LabelSettingsStoreCoversWithItem": "Store covers with item",
+ "LabelSettingsStoreCoversWithItemHelp": "By default covers are stored in /metadata/items, enabling this setting will store covers in your library item folder. Only one file named \"cover\" will be kept",
+ "LabelSettingsStoreMetadataWithItem": "Store metadata with item",
+ "LabelSettingsStoreMetadataWithItemHelp": "By default metadata files are stored in /metadata/items, enabling this setting will store metadata files in your library item folders. Uses .abs file extension",
+ "LabelSettingsTimeFormat": "Time Format",
+ "LabelShowAll": "Show All",
+ "LabelSize": "Size",
+ "LabelSleepTimer": "Sleep timer",
+ "LabelStart": "Start",
+ "LabelStarted": "Started",
+ "LabelStartedAt": "Started At",
+ "LabelStartTime": "Start Time",
+ "LabelStatsAudioTracks": "Audio Tracks",
+ "LabelStatsAuthors": "Authors",
+ "LabelStatsBestDay": "Best Day",
+ "LabelStatsDailyAverage": "Daily Average",
+ "LabelStatsDays": "Days",
+ "LabelStatsDaysListened": "Days Listened",
+ "LabelStatsHours": "Hours",
+ "LabelStatsInARow": "in a row",
+ "LabelStatsItemsFinished": "Items Finished",
+ "LabelStatsItemsInLibrary": "Items in Library",
+ "LabelStatsMinutes": "minutes",
+ "LabelStatsMinutesListening": "Minutes Listening",
+ "LabelStatsOverallDays": "Overall Days",
+ "LabelStatsOverallHours": "Overall Hours",
+ "LabelStatsWeekListening": "Week Listening",
+ "LabelSubtitle": "Subtitle",
+ "LabelSupportedFileTypes": "Supported File Types",
+ "LabelTag": "Tag",
+ "LabelTags": "Tags",
+ "LabelTagsAccessibleToUser": "Tags Accessible to User",
+ "LabelTasks": "Tasks Running",
+ "LabelTimeListened": "Time Listened",
+ "LabelTimeListenedToday": "Time Listened Today",
+ "LabelTimeRemaining": "{0} remaining",
+ "LabelTimeToShift": "Time to shift in seconds",
+ "LabelTitle": "Title",
+ "LabelToolsEmbedMetadata": "Embed Metadata",
+ "LabelToolsEmbedMetadataDescription": "Embed metadata into audio files including cover image and chapters.",
+ "LabelToolsMakeM4b": "Make M4B Audiobook File",
+ "LabelToolsMakeM4bDescription": "Generate a .M4B audiobook file with embedded metadata, cover image, and chapters.",
+ "LabelToolsSplitM4b": "Split M4B to MP3's",
+ "LabelToolsSplitM4bDescription": "Create MP3's from an M4B split by chapters with embedded metadata, cover image, and chapters.",
+ "LabelTotalDuration": "Total Duration",
+ "LabelTotalTimeListened": "Total Time Listened",
+ "LabelTrackFromFilename": "Track from Filename",
+ "LabelTrackFromMetadata": "Track from Metadata",
+ "LabelTracks": "Tracks",
+ "LabelTracksMultiTrack": "Multi-track",
+ "LabelTracksSingleTrack": "Single-track",
+ "LabelType": "Type",
+ "LabelUnabridged": "Unabridged",
+ "LabelUnknown": "Unknown",
+ "LabelUpdateCover": "Update Cover",
+ "LabelUpdateCoverHelp": "Allow overwriting of existing covers for the selected books when a match is located",
+ "LabelUpdatedAt": "Updated At",
+ "LabelUpdateDetails": "Update Details",
+ "LabelUpdateDetailsHelp": "Allow overwriting of existing details for the selected books when a match is located",
+ "LabelUploaderDragAndDrop": "Drag & drop files or folders",
+ "LabelUploaderDropFiles": "Drop files",
+ "LabelUseChapterTrack": "Use chapter track",
+ "LabelUseFullTrack": "Use full track",
+ "LabelUser": "User",
+ "LabelUsername": "Username",
+ "LabelValue": "Value",
+ "LabelVersion": "Version",
+ "LabelViewBookmarks": "View bookmarks",
+ "LabelViewChapters": "View chapters",
+ "LabelViewQueue": "View player queue",
+ "LabelVolume": "Volume",
+ "LabelWeekdaysToRun": "Weekdays to run",
+ "LabelYourAudiobookDuration": "Your audiobook duration",
+ "LabelYourBookmarks": "Your Bookmarks",
+ "LabelYourPlaylists": "Your Playlists",
+ "LabelYourProgress": "Your Progress",
+ "MessageAddToPlayerQueue": "Add to player queue",
+ "MessageAppriseDescription": "To use this feature you will need to have an instance of Apprise API running or an api that will handle those same requests. The Apprise API Url should be the full URL path to send the notification, e.g., if your API instance is served at http://192.168.1.1:8337 then you would put http://192.168.1.1:8337/notify.",
+ "MessageBackupsDescription": "Backups include users, user progress, library item details, server settings, and images stored in /metadata/items & /metadata/authors. Backups do not include any files stored in your library folders.",
+ "MessageBatchQuickMatchDescription": "Quick Match will attempt to add missing covers and metadata for the selected items. Enable the options below to allow Quick Match to overwrite existing covers and/or metadata.",
+ "MessageBookshelfNoCollections": "You haven't made any collections yet",
+ "MessageBookshelfNoResultsForFilter": "No Results for filter \"{0}: {1}\"",
+ "MessageBookshelfNoRSSFeeds": "No RSS feeds are open",
+ "MessageBookshelfNoSeries": "You have no series",
+ "MessageChapterEndIsAfter": "Chapter end is after the end of your audiobook",
+ "MessageChapterErrorFirstNotZero": "First chapter must start at 0",
+ "MessageChapterErrorStartGteDuration": "Invalid start time must be less than audiobook duration",
+ "MessageChapterErrorStartLtPrev": "Invalid start time must be greater than or equal to previous chapter start time",
+ "MessageChapterStartIsAfter": "Chapter start is after the end of your audiobook",
+ "MessageCheckingCron": "Checking cron...",
+ "MessageConfirmDeleteBackup": "Are you sure you want to delete backup for {0}?",
+ "MessageConfirmDeleteLibrary": "Are you sure you want to permanently delete library \"{0}\"?",
+ "MessageConfirmDeleteSession": "Are you sure you want to delete this session?",
+ "MessageConfirmForceReScan": "Are you sure you want to force re-scan?",
+ "MessageConfirmMarkSeriesFinished": "Are you sure you want to mark all books in this series as finished?",
+ "MessageConfirmMarkSeriesNotFinished": "Are you sure you want to mark all books in this series as not finished?",
+ "MessageConfirmRemoveCollection": "Are you sure you want to remove collection \"{0}\"?",
+ "MessageConfirmRemoveEpisode": "Are you sure you want to remove episode \"{0}\"?",
+ "MessageConfirmRemoveEpisodes": "Are you sure you want to remove {0} episodes?",
+ "MessageConfirmRemovePlaylist": "Are you sure you want to remove your playlist \"{0}\"?",
+ "MessageConfirmRenameGenre": "Are you sure you want to rename genre \"{0}\" to \"{1}\" for all items?",
+ "MessageConfirmRenameGenreMergeNote": "Note: This genre already exists so they will be merged.",
+ "MessageConfirmRenameGenreWarning": "Warning! A similar genre with a different casing already exists \"{0}\".",
+ "MessageConfirmRenameTag": "Are you sure you want to rename tag \"{0}\" to \"{1}\" for all items?",
+ "MessageConfirmRenameTagMergeNote": "Note: This tag already exists so they will be merged.",
+ "MessageConfirmRenameTagWarning": "Warning! A similar tag with a different casing already exists \"{0}\".",
+ "MessageDownloadingEpisode": "Downloading episode",
+ "MessageDragFilesIntoTrackOrder": "Drag files into correct track order",
+ "MessageEmbedFinished": "Embed Finished!",
+ "MessageEpisodesQueuedForDownload": "{0} Episode(s) queued for download",
+ "MessageFeedURLWillBe": "Feed URL will be {0}",
+ "MessageFetching": "Fetching...",
+ "MessageForceReScanDescription": "will scan all files again like a fresh scan. Audio file ID3 tags, OPF files, and text files will be scanned as new.",
+ "MessageImportantNotice": "Important Notice!",
+ "MessageInsertChapterBelow": "Insert chapter below",
+ "MessageItemsSelected": "{0} Items Selected",
+ "MessageItemsUpdated": "{0} Items Updated",
+ "MessageJoinUsOn": "Join us on",
+ "MessageListeningSessionsInTheLastYear": "{0} listening sessions in the last year",
+ "MessageLoading": "Loading...",
+ "MessageLoadingFolders": "Loading folders...",
+ "MessageM4BFailed": "M4B Failed!",
+ "MessageM4BFinished": "M4B Finished!",
+ "MessageMapChapterTitles": "Map chapter titles to your existing audiobook chapters without adjusting timestamps",
+ "MessageMarkAsFinished": "Mark as Finished",
+ "MessageMarkAsNotFinished": "Mark as Not Finished",
+ "MessageMatchBooksDescription": "will attempt to match books in the library with a book from the selected search provider and fill in empty details and cover art. Does not overwrite details.",
+ "MessageNoAudioTracks": "No audio tracks",
+ "MessageNoAuthors": "No Authors",
+ "MessageNoBackups": "No Backups",
+ "MessageNoBookmarks": "No Bookmarks",
+ "MessageNoChapters": "No Chapters",
+ "MessageNoCollections": "No Collections",
+ "MessageNoCoversFound": "No Covers Found",
+ "MessageNoDescription": "No description",
+ "MessageNoDownloadsInProgress": "No downloads currently in progress",
+ "MessageNoDownloadsQueued": "No downloads queued",
+ "MessageNoEpisodeMatchesFound": "No episode matches found",
+ "MessageNoEpisodes": "No Episodes",
+ "MessageNoFoldersAvailable": "No Folders Available",
+ "MessageNoGenres": "No Genres",
+ "MessageNoIssues": "No Issues",
+ "MessageNoItems": "No Items",
+ "MessageNoItemsFound": "No items found",
+ "MessageNoListeningSessions": "No Listening Sessions",
+ "MessageNoLogs": "No Logs",
+ "MessageNoMediaProgress": "No Media Progress",
+ "MessageNoNotifications": "No Notifications",
+ "MessageNoPodcastsFound": "No podcasts found",
+ "MessageNoResults": "No Results",
+ "MessageNoSearchResultsFor": "No search results for \"{0}\"",
+ "MessageNoSeries": "No Series",
+ "MessageNoTags": "No Tags",
+ "MessageNoTasksRunning": "No Tasks Running",
+ "MessageNotYetImplemented": "Not yet implemented",
+ "MessageNoUpdateNecessary": "No update necessary",
+ "MessageNoUpdatesWereNecessary": "No updates were necessary",
+ "MessageNoUserPlaylists": "You have no playlists",
+ "MessageOr": "or",
+ "MessagePauseChapter": "Pause chapter playback",
+ "MessagePlayChapter": "Listen to beginning of chapter",
+ "MessagePlaylistCreateFromCollection": "Create playlist from collection",
+ "MessagePodcastHasNoRSSFeedForMatching": "Podcast has no RSS feed url to use for matching",
+ "MessageQuickMatchDescription": "Populate empty item details & cover with first match result from '{0}'. Does not overwrite details unless 'Prefer matched metadata' server setting is enabled.",
+ "MessageRemoveAllItemsWarning": "WARNING! This action will remove all library items from the database including any updates or matches you have made. This does not do anything to your actual files. Are you sure?",
+ "MessageRemoveChapter": "Remove chapter",
+ "MessageRemoveEpisodes": "Remove {0} episode(s)",
+ "MessageRemoveFromPlayerQueue": "Remove from player queue",
+ "MessageRemoveUserWarning": "Are you sure you want to permanently delete user \"{0}\"?",
+ "MessageReportBugsAndContribute": "Report bugs, request features, and contribute on",
+ "MessageResetChaptersConfirm": "Are you sure you want to reset chapters and undo the changes you made?",
+ "MessageRestoreBackupConfirm": "Are you sure you want to restore the backup created on",
+ "MessageRestoreBackupWarning": "Restoring a backup will overwrite the entire database located at /config and cover images in /metadata/items & /metadata/authors.
Backups do not modify any files in your library folders. If you have enabled server settings to store cover art and metadata in your library folders then those are not backed up or overwritten.
All clients using your server will be automatically refreshed.",
+ "MessageSearchResultsFor": "Search results for",
+ "MessageServerCouldNotBeReached": "Server could not be reached",
+ "MessageSetChaptersFromTracksDescription": "Set chapters using each audio file as a chapter and chapter title as the audio file name",
+ "MessageStartPlaybackAtTime": "Start playback for \"{0}\" at {1}?",
+ "MessageThinking": "Thinking...",
+ "MessageUploaderItemFailed": "Failed to upload",
+ "MessageUploaderItemSuccess": "Successfully Uploaded!",
+ "MessageUploading": "Uploading...",
+ "MessageValidCronExpression": "Valid cron expression",
+ "MessageWatcherIsDisabledGlobally": "Watcher is disabled globally in server settings",
+ "MessageXLibraryIsEmpty": "{0} Library is empty!",
+ "MessageYourAudiobookDurationIsLonger": "Your audiobook duration is longer than the duration found",
+ "MessageYourAudiobookDurationIsShorter": "Your audiobook duration is shorter than duration found",
+ "NoteChangeRootPassword": "रूट user is the only user that can have an empty password",
+ "NoteChapterEditorTimes": "Note: First chapter start time must remain at 0:00 and the last chapter start time cannot exceed this audiobooks duration.",
+ "NoteFolderPicker": "Note: folders already mapped will not be shown",
+ "NoteFolderPickerDebian": "Note: Folder picker for the debian install is not fully implemented. You should enter the path to your library directly.",
+ "NoteRSSFeedPodcastAppsHttps": "Warning: Most podcast apps will require the RSS feed URL is using HTTPS",
+ "NoteRSSFeedPodcastAppsPubDate": "Warning: 1 or more of your episodes do not have a Pub Date. Some podcast apps require this.",
+ "NoteUploaderFoldersWithMediaFiles": "Folders with media files will be handled as separate library items.",
+ "NoteUploaderOnlyAudioFiles": "If uploading only audio files then each audio file will be handled as a separate audiobook.",
+ "NoteUploaderUnsupportedFiles": "Unsupported files are ignored. When choosing or dropping a folder, other files that are not in an item folder are ignored.",
+ "PlaceholderNewCollection": "New collection name",
+ "PlaceholderNewFolderPath": "New folder path",
+ "PlaceholderNewPlaylist": "New playlist name",
+ "PlaceholderSearch": "Search..",
+ "PlaceholderSearchEpisode": "Search episode..",
+ "ToastAccountUpdateFailed": "Failed to update account",
+ "ToastAccountUpdateSuccess": "Account updated",
+ "ToastAuthorImageRemoveFailed": "Failed to remove image",
+ "ToastAuthorImageRemoveSuccess": "Author image removed",
+ "ToastAuthorUpdateFailed": "Failed to update author",
+ "ToastAuthorUpdateMerged": "Author merged",
+ "ToastAuthorUpdateSuccess": "Author updated",
+ "ToastAuthorUpdateSuccessNoImageFound": "Author updated (no image found)",
+ "ToastBackupCreateFailed": "Failed to create backup",
+ "ToastBackupCreateSuccess": "Backup created",
+ "ToastBackupDeleteFailed": "Failed to delete backup",
+ "ToastBackupDeleteSuccess": "Backup deleted",
+ "ToastBackupRestoreFailed": "Failed to restore backup",
+ "ToastBackupUploadFailed": "Failed to upload backup",
+ "ToastBackupUploadSuccess": "Backup uploaded",
+ "ToastBatchUpdateFailed": "Batch update failed",
+ "ToastBatchUpdateSuccess": "Batch update success",
+ "ToastBookmarkCreateFailed": "Failed to create bookmark",
+ "ToastBookmarkCreateSuccess": "Bookmark added",
+ "ToastBookmarkRemoveFailed": "Failed to remove bookmark",
+ "ToastBookmarkRemoveSuccess": "Bookmark removed",
+ "ToastBookmarkUpdateFailed": "Failed to update bookmark",
+ "ToastBookmarkUpdateSuccess": "Bookmark updated",
+ "ToastChaptersHaveErrors": "Chapters have errors",
+ "ToastChaptersMustHaveTitles": "Chapters must have titles",
+ "ToastCollectionItemsRemoveFailed": "Failed to remove item(s) from collection",
+ "ToastCollectionItemsRemoveSuccess": "Item(s) removed from collection",
+ "ToastCollectionRemoveFailed": "Failed to remove collection",
+ "ToastCollectionRemoveSuccess": "Collection removed",
+ "ToastCollectionUpdateFailed": "Failed to update collection",
+ "ToastCollectionUpdateSuccess": "Collection updated",
+ "ToastItemCoverUpdateFailed": "Failed to update item cover",
+ "ToastItemCoverUpdateSuccess": "Item cover updated",
+ "ToastItemDetailsUpdateFailed": "Failed to update item details",
+ "ToastItemDetailsUpdateSuccess": "Item details updated",
+ "ToastItemDetailsUpdateUnneeded": "No updates needed for item details",
+ "ToastItemMarkedAsFinishedFailed": "Failed to mark as Finished",
+ "ToastItemMarkedAsFinishedSuccess": "Item marked as Finished",
+ "ToastItemMarkedAsNotFinishedFailed": "Failed to mark as Not Finished",
+ "ToastItemMarkedAsNotFinishedSuccess": "Item marked as Not Finished",
+ "ToastLibraryCreateFailed": "Failed to create library",
+ "ToastLibraryCreateSuccess": "Library \"{0}\" created",
+ "ToastLibraryDeleteFailed": "Failed to delete library",
+ "ToastLibraryDeleteSuccess": "Library deleted",
+ "ToastLibraryScanFailedToStart": "Failed to start scan",
+ "ToastLibraryScanStarted": "Library scan started",
+ "ToastLibraryUpdateFailed": "Failed to update library",
+ "ToastLibraryUpdateSuccess": "Library \"{0}\" updated",
+ "ToastPlaylistCreateFailed": "Failed to create playlist",
+ "ToastPlaylistCreateSuccess": "Playlist created",
+ "ToastPlaylistRemoveFailed": "Failed to remove playlist",
+ "ToastPlaylistRemoveSuccess": "Playlist removed",
+ "ToastPlaylistUpdateFailed": "Failed to update playlist",
+ "ToastPlaylistUpdateSuccess": "Playlist updated",
+ "ToastPodcastCreateFailed": "Failed to create podcast",
+ "ToastPodcastCreateSuccess": "Podcast created successfully",
+ "ToastRemoveItemFromCollectionFailed": "Failed to remove item from collection",
+ "ToastRemoveItemFromCollectionSuccess": "Item removed from collection",
+ "ToastRSSFeedCloseFailed": "Failed to close RSS feed",
+ "ToastRSSFeedCloseSuccess": "RSS feed closed",
+ "ToastSeriesUpdateFailed": "Series update failed",
+ "ToastSeriesUpdateSuccess": "Series update success",
+ "ToastSessionDeleteFailed": "Failed to delete session",
+ "ToastSessionDeleteSuccess": "Session deleted",
+ "ToastSocketConnected": "Socket connected",
+ "ToastSocketDisconnected": "Socket disconnected",
+ "ToastSocketFailedToConnect": "Socket failed to connect",
+ "ToastUserDeleteFailed": "Failed to delete user",
+ "ToastUserDeleteSuccess": "User deleted"
+}
From a59311f795236195c21763d1673d892e231f38d6 Mon Sep 17 00:00:00 2001
From: advplyr
Date: Fri, 7 Apr 2023 18:05:23 -0500
Subject: [PATCH 02/41] Update:Adjust timestamps in player for playback speed
#1647
---
client/components/app/StreamContainer.vue | 11 ++++-----
client/components/modals/ChaptersModal.vue | 25 ++++++++++++---------
client/components/player/PlayerTrackBar.vue | 21 ++++++++++-------
client/components/player/PlayerUi.vue | 14 +++++++-----
4 files changed, 42 insertions(+), 29 deletions(-)
diff --git a/client/components/app/StreamContainer.vue b/client/components/app/StreamContainer.vue
index 470106ba..fa41b528 100644
--- a/client/components/app/StreamContainer.vue
+++ b/client/components/app/StreamContainer.vue
@@ -81,7 +81,7 @@ export default {
sleepTimerRemaining: 0,
sleepTimer: null,
displayTitle: null,
- initialPlaybackRate: 1,
+ currentPlaybackRate: 1,
syncFailedToast: null
}
},
@@ -152,7 +152,8 @@ export default {
return this.streamLibraryItem ? this.streamLibraryItem.libraryId : null
},
totalDurationPretty() {
- return this.$secondsToTimestamp(this.totalDuration)
+ // Adjusted by playback rate
+ return this.$secondsToTimestamp(this.totalDuration / this.currentPlaybackRate)
},
podcastAuthor() {
if (!this.isPodcast) return null
@@ -255,7 +256,7 @@ export default {
this.playerHandler.setVolume(volume)
},
setPlaybackRate(playbackRate) {
- this.initialPlaybackRate = playbackRate
+ this.currentPlaybackRate = playbackRate
this.playerHandler.setPlaybackRate(playbackRate)
},
seek(time) {
@@ -384,7 +385,7 @@ export default {
libraryItem: session.libraryItem,
episodeId: session.episodeId
})
- this.playerHandler.prepareOpenSession(session, this.initialPlaybackRate)
+ this.playerHandler.prepareOpenSession(session, this.currentPlaybackRate)
},
streamOpen(session) {
console.log(`[StreamContainer] Stream session open`, session)
@@ -451,7 +452,7 @@ export default {
if (this.$refs.audioPlayer) this.$refs.audioPlayer.checkUpdateChapterTrack()
})
- this.playerHandler.load(libraryItem, episodeId, true, this.initialPlaybackRate, payload.startTime)
+ this.playerHandler.load(libraryItem, episodeId, true, this.currentPlaybackRate, payload.startTime)
},
pauseItem() {
this.playerHandler.pause()
diff --git a/client/components/modals/ChaptersModal.vue b/client/components/modals/ChaptersModal.vue
index 2a3631db..2ace9891 100644
--- a/client/components/modals/ChaptersModal.vue
+++ b/client/components/modals/ChaptersModal.vue
@@ -2,13 +2,13 @@
@@ -588,6 +589,45 @@ export default {
]
}
this.checkChapters()
+ },
+ removeAllChaptersClick() {
+ const payload = {
+ message: this.$strings.MessageConfirmRemoveAllChapters,
+ callback: (confirmed) => {
+ if (confirmed) {
+ this.removeAllChapters()
+ }
+ },
+ type: 'yesNo'
+ }
+ this.$store.commit('globals/setConfirmPrompt', payload)
+ },
+ removeAllChapters() {
+ this.saving = true
+ const payload = {
+ chapters: []
+ }
+ this.$axios
+ .$post(`/api/items/${this.libraryItem.id}/chapters`, payload)
+ .then((data) => {
+ if (data.updated) {
+ this.$toast.success('Chapters removed')
+ if (this.previousRoute) {
+ this.$router.push(this.previousRoute)
+ } else {
+ this.$router.push(`/item/${this.libraryItem.id}`)
+ }
+ } else {
+ this.$toast.info(this.$strings.MessageNoUpdatesWereNecessary)
+ }
+ })
+ .catch((error) => {
+ console.error('Failed to remove chapters', error)
+ this.$toast.error('Failed to remove chapters')
+ })
+ .finally(() => {
+ this.saving = false
+ })
}
},
mounted() {
diff --git a/client/strings/de.json b/client/strings/de.json
index 28b859d6..4cac3511 100644
--- a/client/strings/de.json
+++ b/client/strings/de.json
@@ -465,6 +465,7 @@
"MessageConfirmForceReScan": "Sind Sie sicher, dass Sie einen erneuten Scanvorgang erzwingen wollen?",
"MessageConfirmMarkSeriesFinished": "Sind Sie sicher, dass Sie alle Medien dieser Reihe als abgeschlossen markieren wollen?",
"MessageConfirmMarkSeriesNotFinished": "Sind Sie sicher, dass Sie alle Medien dieser Reihe als nicht abgeschlossen markieren wollen?",
+ "MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?",
"MessageConfirmRemoveCollection": "Sind Sie sicher, dass Sie die Sammlung \"{0}\" löschen wollen?",
"MessageConfirmRemoveEpisode": "Sind Sie sicher, dass Sie die Episode \"{0}\" löschen möchten?",
"MessageConfirmRemoveEpisodes": "Sind Sie sicher, dass Sie {0} Episoden löschen wollen?",
diff --git a/client/strings/en-us.json b/client/strings/en-us.json
index 6550a503..1ea2fa27 100644
--- a/client/strings/en-us.json
+++ b/client/strings/en-us.json
@@ -465,6 +465,7 @@
"MessageConfirmForceReScan": "Are you sure you want to force re-scan?",
"MessageConfirmMarkSeriesFinished": "Are you sure you want to mark all books in this series as finished?",
"MessageConfirmMarkSeriesNotFinished": "Are you sure you want to mark all books in this series as not finished?",
+ "MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?",
"MessageConfirmRemoveCollection": "Are you sure you want to remove collection \"{0}\"?",
"MessageConfirmRemoveEpisode": "Are you sure you want to remove episode \"{0}\"?",
"MessageConfirmRemoveEpisodes": "Are you sure you want to remove {0} episodes?",
diff --git a/client/strings/es.json b/client/strings/es.json
index bb2a224d..17c13698 100644
--- a/client/strings/es.json
+++ b/client/strings/es.json
@@ -465,6 +465,7 @@
"MessageConfirmForceReScan": "Esta seguro que desea forzar re-escanear?",
"MessageConfirmMarkSeriesFinished": "Esta seguro que desea marcar todos los libros en esta serie como terminados?",
"MessageConfirmMarkSeriesNotFinished": "Esta seguro que desea marcar todos los libros en esta serie como no terminados?",
+ "MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?",
"MessageConfirmRemoveCollection": "Esta seguro que desea remover la colección \"{0}\"?",
"MessageConfirmRemoveEpisode": "Esta seguro que desea remover el episodio \"{0}\"?",
"MessageConfirmRemoveEpisodes": "Esta seguro que desea remover {0} episodios?",
diff --git a/client/strings/fr.json b/client/strings/fr.json
index 04b6b075..0b03f174 100644
--- a/client/strings/fr.json
+++ b/client/strings/fr.json
@@ -465,6 +465,7 @@
"MessageConfirmForceReScan": "Êtes-vous sûr de vouloir lancer une Analyse Forcée ?",
"MessageConfirmMarkSeriesFinished": "Êtes-vous sûr de vouloir marquer comme terminé tous les livres de cette série ?",
"MessageConfirmMarkSeriesNotFinished": "Êtes-vous sûr de vouloir marquer comme non terminé tous les livres de cette série ?",
+ "MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?",
"MessageConfirmRemoveCollection": "Êtes-vous sûr de vouloir supprimer la collection « {0} » ?",
"MessageConfirmRemoveEpisode": "Êtes-vous sûr de vouloir supprimer l’épisode « {0} » ?",
"MessageConfirmRemoveEpisodes": "Êtes-vous sûr de vouloir supprimer {0} épisodes ?",
diff --git a/client/strings/hi.json b/client/strings/hi.json
index b146df45..d1dcd1d7 100644
--- a/client/strings/hi.json
+++ b/client/strings/hi.json
@@ -465,6 +465,7 @@
"MessageConfirmForceReScan": "Are you sure you want to force re-scan?",
"MessageConfirmMarkSeriesFinished": "Are you sure you want to mark all books in this series as finished?",
"MessageConfirmMarkSeriesNotFinished": "Are you sure you want to mark all books in this series as not finished?",
+ "MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?",
"MessageConfirmRemoveCollection": "Are you sure you want to remove collection \"{0}\"?",
"MessageConfirmRemoveEpisode": "Are you sure you want to remove episode \"{0}\"?",
"MessageConfirmRemoveEpisodes": "Are you sure you want to remove {0} episodes?",
@@ -638,4 +639,4 @@
"ToastSocketFailedToConnect": "Socket failed to connect",
"ToastUserDeleteFailed": "Failed to delete user",
"ToastUserDeleteSuccess": "User deleted"
-}
+}
\ No newline at end of file
diff --git a/client/strings/hr.json b/client/strings/hr.json
index 3b9a5d75..69dcef74 100644
--- a/client/strings/hr.json
+++ b/client/strings/hr.json
@@ -465,6 +465,7 @@
"MessageConfirmForceReScan": "Jeste li sigurni da želite ponovno skenirati?",
"MessageConfirmMarkSeriesFinished": "Are you sure you want to mark all books in this series as finished?",
"MessageConfirmMarkSeriesNotFinished": "Are you sure you want to mark all books in this series as not finished?",
+ "MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?",
"MessageConfirmRemoveCollection": "AJeste li sigurni da želite obrisati kolekciju \"{0}\"?",
"MessageConfirmRemoveEpisode": "Jeste li sigurni da želite obrisati epizodu \"{0}\"?",
"MessageConfirmRemoveEpisodes": "Jeste li sigurni da želite obrisati {0} epizoda/-u?",
diff --git a/client/strings/it.json b/client/strings/it.json
index c44dc48a..1eecc3fc 100644
--- a/client/strings/it.json
+++ b/client/strings/it.json
@@ -465,6 +465,7 @@
"MessageConfirmForceReScan": "Sei sicuro di voler forzare una nuova scansione?",
"MessageConfirmMarkSeriesFinished": "Sei sicuro di voler contrassegnare tutti i libri di questa serie come completati?",
"MessageConfirmMarkSeriesNotFinished": "Sei sicuro di voler contrassegnare tutti i libri di questa serie come non completati?",
+ "MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?",
"MessageConfirmRemoveCollection": "Sei sicuro di voler rimuovere la Raccolta \"{0}\"?",
"MessageConfirmRemoveEpisode": "Sei sicuro di voler rimuovere l'episodio \"{0}\"?",
"MessageConfirmRemoveEpisodes": "Sei sicuro di voler rimuovere {0} episodi?",
diff --git a/client/strings/pl.json b/client/strings/pl.json
index 8d07bd64..ed0ba345 100644
--- a/client/strings/pl.json
+++ b/client/strings/pl.json
@@ -465,6 +465,7 @@
"MessageConfirmForceReScan": "Czy na pewno chcesz wymusić ponowne skanowanie?",
"MessageConfirmMarkSeriesFinished": "Are you sure you want to mark all books in this series as finished?",
"MessageConfirmMarkSeriesNotFinished": "Are you sure you want to mark all books in this series as not finished?",
+ "MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?",
"MessageConfirmRemoveCollection": "Czy na pewno chcesz usunąć kolekcję \"{0}\"?",
"MessageConfirmRemoveEpisode": "Czy na pewno chcesz usunąć odcinek \"{0}\"?",
"MessageConfirmRemoveEpisodes": "Czy na pewno chcesz usunąć {0} odcinki?",
diff --git a/client/strings/ru.json b/client/strings/ru.json
index c172df99..0e0871eb 100644
--- a/client/strings/ru.json
+++ b/client/strings/ru.json
@@ -465,6 +465,7 @@
"MessageConfirmForceReScan": "Вы уверены, что хотите принудительно выполнить повторное сканирование?",
"MessageConfirmMarkSeriesFinished": "Вы уверены, что хотите отметить все книги этой серии как законченные?",
"MessageConfirmMarkSeriesNotFinished": "Вы уверены, что хотите отметить все книги этой серии как незаконченные?",
+ "MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?",
"MessageConfirmRemoveCollection": "Вы уверены, что хотите удалить коллекцию \"{0}\"?",
"MessageConfirmRemoveEpisode": "Вы уверены, что хотите удалить эпизод \"{0}\"?",
"MessageConfirmRemoveEpisodes": "Вы уверены, что хотите удалить {0} эпизодов?",
diff --git a/client/strings/zh-cn.json b/client/strings/zh-cn.json
index afc21c05..3a9b0c9c 100644
--- a/client/strings/zh-cn.json
+++ b/client/strings/zh-cn.json
@@ -465,6 +465,7 @@
"MessageConfirmForceReScan": "你确定要强制重新扫描吗?",
"MessageConfirmMarkSeriesFinished": "你确定要将此系列中的所有书籍都标记为已听完吗?",
"MessageConfirmMarkSeriesNotFinished": "你确定要将此系列中的所有书籍都标记为未听完吗?",
+ "MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?",
"MessageConfirmRemoveCollection": "您确定要移除收藏 \"{0}\"?",
"MessageConfirmRemoveEpisode": "您确定要移除剧集 \"{0}\"?",
"MessageConfirmRemoveEpisodes": "你确定要移除 {0} 剧集?",
diff --git a/server/controllers/LibraryItemController.js b/server/controllers/LibraryItemController.js
index 05aee92d..d26f57f2 100644
--- a/server/controllers/LibraryItemController.js
+++ b/server/controllers/LibraryItemController.js
@@ -436,12 +436,12 @@ class LibraryItemController {
return res.sendStatus(500)
}
- const chapters = req.body.chapters || []
- if (!chapters.length) {
+ if (!req.body.chapters) {
Logger.error(`[LibraryItemController] Invalid payload`)
return res.sendStatus(400)
}
+ const chapters = req.body.chapters || []
const wasUpdated = req.libraryItem.media.updateChapters(chapters)
if (wasUpdated) {
await this.db.updateLibraryItem(req.libraryItem)
From 3dc9416da64d3f51f84b035aae877660e142a465 Mon Sep 17 00:00:00 2001
From: advplyr
Date: Sun, 9 Apr 2023 14:32:51 -0500
Subject: [PATCH 08/41] Add:Chapters to podcast episodes #1646
---
client/components/app/StreamContainer.vue | 14 ++++++++----
.../tables/podcast/EpisodeTableRow.vue | 1 +
client/players/PlayerHandler.js | 5 ++++-
server/managers/PodcastManager.js | 22 +++++++++++--------
server/objects/entities/PodcastEpisode.js | 11 +++++++---
server/utils/podcastUtils.js | 18 ++++++++++-----
6 files changed, 49 insertions(+), 22 deletions(-)
diff --git a/client/components/app/StreamContainer.vue b/client/components/app/StreamContainer.vue
index baecbde4..cd2bd1cf 100644
--- a/client/components/app/StreamContainer.vue
+++ b/client/components/app/StreamContainer.vue
@@ -120,17 +120,22 @@ export default {
streamLibraryItem() {
return this.$store.state.streamLibraryItem
},
+ streamEpisode() {
+ if (!this.$store.state.streamEpisodeId) return null
+ const episodes = this.streamLibraryItem.media.episodes || []
+ return episodes.find((ep) => ep.id === this.$store.state.streamEpisodeId)
+ },
libraryItemId() {
- return this.streamLibraryItem ? this.streamLibraryItem.id : null
+ return this.streamLibraryItem?.id || null
},
media() {
- return this.streamLibraryItem ? this.streamLibraryItem.media || {} : {}
+ return this.streamLibraryItem?.media || {}
},
isPodcast() {
- return this.streamLibraryItem ? this.streamLibraryItem.mediaType === 'podcast' : false
+ return this.streamLibraryItem?.mediaType === 'podcast'
},
isMusic() {
- return this.streamLibraryItem ? this.streamLibraryItem.mediaType === 'music' : false
+ return this.streamLibraryItem?.mediaType === 'music'
},
isExplicit() {
return this.mediaMetadata.explicit || false
@@ -139,6 +144,7 @@ export default {
return this.media.metadata || {}
},
chapters() {
+ if (this.streamEpisode) return this.streamEpisode.chapters || []
return this.media.chapters || []
},
title() {
diff --git a/client/components/tables/podcast/EpisodeTableRow.vue b/client/components/tables/podcast/EpisodeTableRow.vue
index 3012faf4..a5b85e6e 100644
--- a/client/components/tables/podcast/EpisodeTableRow.vue
+++ b/client/components/tables/podcast/EpisodeTableRow.vue
@@ -12,6 +12,7 @@
Season #{{ episode.season }}
Episode #{{ episode.episode }}
+
{{ episode.chapters.length }} Chapters
Published {{ $formatDate(publishedAt, dateFormat) }}
diff --git a/client/players/PlayerHandler.js b/client/players/PlayerHandler.js
index 1e591069..2fb188e8 100644
--- a/client/players/PlayerHandler.js
+++ b/client/players/PlayerHandler.js
@@ -123,7 +123,7 @@ export default class PlayerHandler {
playerError() {
// Switch to HLS stream on error
- if (!this.isCasting && !this.currentStreamId && (this.player instanceof LocalAudioPlayer)) {
+ if (!this.isCasting && (this.player instanceof LocalAudioPlayer)) {
console.log(`[PlayerHandler] Audio player error switching to HLS stream`)
this.prepare(true)
}
@@ -183,6 +183,8 @@ export default class PlayerHandler {
}
async prepare(forceTranscode = false) {
+ this.currentSessionId = null // Reset session
+
const payload = {
deviceInfo: {
deviceId: this.getDeviceId()
@@ -260,6 +262,7 @@ export default class PlayerHandler {
this.player = null
this.playerState = 'IDLE'
this.libraryItem = null
+ this.currentSessionId = null
this.startTime = 0
this.stopPlayInterval()
}
diff --git a/server/managers/PodcastManager.js b/server/managers/PodcastManager.js
index e60238cc..d50bc7bb 100644
--- a/server/managers/PodcastManager.js
+++ b/server/managers/PodcastManager.js
@@ -53,15 +53,15 @@ class PodcastManager {
}
async downloadPodcastEpisodes(libraryItem, episodesToDownload, isAutoDownload) {
- var index = libraryItem.media.episodes.length + 1
- episodesToDownload.forEach((ep) => {
- var newPe = new PodcastEpisode()
+ let index = libraryItem.media.episodes.length + 1
+ for (const ep of episodesToDownload) {
+ const newPe = new PodcastEpisode()
newPe.setData(ep, index++)
newPe.libraryItemId = libraryItem.id
- var newPeDl = new PodcastEpisodeDownload()
+ const newPeDl = new PodcastEpisodeDownload()
newPeDl.setData(newPe, libraryItem, isAutoDownload, libraryItem.libraryId)
this.startPodcastEpisodeDownload(newPeDl)
- })
+ }
}
async startPodcastEpisodeDownload(podcastEpisodeDownload) {
@@ -94,7 +94,6 @@ class PodcastManager {
await filePerms.setDefault(this.currentDownload.libraryItem.path)
}
-
let success = false
if (this.currentDownload.urlFileExtension === 'mp3') {
// Download episode and tag it
@@ -156,6 +155,11 @@ class PodcastManager {
const podcastEpisode = this.currentDownload.podcastEpisode
podcastEpisode.audioFile = audioFile
+
+ if (audioFile.chapters?.length) {
+ podcastEpisode.chapters = audioFile.chapters.map(ch => ({ ...ch }))
+ }
+
libraryItem.media.addPodcastEpisode(podcastEpisode)
if (libraryItem.isInvalid) {
// First episode added to an empty podcast
@@ -214,13 +218,13 @@ class PodcastManager {
}
async probeAudioFile(libraryFile) {
- var path = libraryFile.metadata.path
- var mediaProbeData = await prober.probe(path)
+ const path = libraryFile.metadata.path
+ const mediaProbeData = await prober.probe(path)
if (mediaProbeData.error) {
Logger.error(`[PodcastManager] Podcast Episode downloaded but failed to probe "${path}"`, mediaProbeData.error)
return false
}
- var newAudioFile = new AudioFile()
+ const newAudioFile = new AudioFile()
newAudioFile.setDataFromProbe(libraryFile, mediaProbeData)
return newAudioFile
}
diff --git a/server/objects/entities/PodcastEpisode.js b/server/objects/entities/PodcastEpisode.js
index 80e66611..394a0bea 100644
--- a/server/objects/entities/PodcastEpisode.js
+++ b/server/objects/entities/PodcastEpisode.js
@@ -1,6 +1,6 @@
const Path = require('path')
const Logger = require('../../Logger')
-const { getId, cleanStringForSearch } = require('../../utils/index')
+const { getId, cleanStringForSearch, areEquivalent, copyValue } = require('../../utils/index')
const AudioFile = require('../files/AudioFile')
const AudioTrack = require('../files/AudioTrack')
@@ -18,6 +18,7 @@ class PodcastEpisode {
this.description = null
this.enclosure = null
this.pubDate = null
+ this.chapters = []
this.audioFile = null
this.publishedAt = null
@@ -41,6 +42,7 @@ class PodcastEpisode {
this.description = episode.description
this.enclosure = episode.enclosure ? { ...episode.enclosure } : null
this.pubDate = episode.pubDate
+ this.chapters = episode.chapters?.map(ch => ({ ...ch })) || []
this.audioFile = new AudioFile(episode.audioFile)
this.publishedAt = episode.publishedAt
this.addedAt = episode.addedAt
@@ -62,6 +64,7 @@ class PodcastEpisode {
description: this.description,
enclosure: this.enclosure ? { ...this.enclosure } : null,
pubDate: this.pubDate,
+ chapters: this.chapters.map(ch => ({ ...ch })),
audioFile: this.audioFile.toJSON(),
publishedAt: this.publishedAt,
addedAt: this.addedAt,
@@ -82,6 +85,7 @@ class PodcastEpisode {
description: this.description,
enclosure: this.enclosure ? { ...this.enclosure } : null,
pubDate: this.pubDate,
+ chapters: this.chapters.map(ch => ({ ...ch })),
audioFile: this.audioFile.toJSON(),
audioTrack: this.audioTrack.toJSON(),
publishedAt: this.publishedAt,
@@ -136,6 +140,7 @@ class PodcastEpisode {
this.setDataFromAudioMetaTags(audioFile.metaTags, true)
+ this.chapters = audioFile.chapters?.map((c) => ({ ...c }))
this.addedAt = Date.now()
this.updatedAt = Date.now()
}
@@ -143,8 +148,8 @@ class PodcastEpisode {
update(payload) {
let hasUpdates = false
for (const key in this.toJSON()) {
- if (payload[key] != undefined && payload[key] != this[key]) {
- this[key] = payload[key]
+ if (payload[key] != undefined && !areEquivalent(payload[key], this[key])) {
+ this[key] = copyValue(payload[key])
hasUpdates = true
}
}
diff --git a/server/utils/podcastUtils.js b/server/utils/podcastUtils.js
index d59a3ec4..553ad7d1 100644
--- a/server/utils/podcastUtils.js
+++ b/server/utils/podcastUtils.js
@@ -74,12 +74,12 @@ function extractPodcastMetadata(channel) {
function extractEpisodeData(item) {
// Episode must have url
- if (!item.enclosure || !item.enclosure.length || !item.enclosure[0]['$'] || !item.enclosure[0]['$'].url) {
+ if (!item.enclosure?.[0]?.['$']?.url) {
Logger.error(`[podcastUtils] Invalid podcast episode data`)
return null
}
- var episode = {
+ const episode = {
enclosure: {
...item.enclosure[0]['$']
}
@@ -91,6 +91,12 @@ function extractEpisodeData(item) {
episode.description = htmlSanitizer.sanitize(rawDescription)
}
+ // Extract chapters
+ if (item['podcast:chapters']?.[0]?.['$']?.url) {
+ episode.chaptersUrl = item['podcast:chapters'][0]['$'].url
+ episode.chaptersType = item['podcast:chapters'][0]['$'].type || 'application/json'
+ }
+
// Supposed to be the plaintext description but not always followed
if (item['description']) {
const rawDescription = extractFirstArrayItem(item, 'description') || ''
@@ -133,14 +139,16 @@ function cleanEpisodeData(data) {
duration: data.duration || '',
explicit: data.explicit || '',
publishedAt,
- enclosure: data.enclosure
+ enclosure: data.enclosure,
+ chaptersUrl: data.chaptersUrl || null,
+ chaptersType: data.chaptersType || null
}
}
function extractPodcastEpisodes(items) {
- var episodes = []
+ const episodes = []
items.forEach((item) => {
- var extracted = extractEpisodeData(item)
+ const extracted = extractEpisodeData(item)
if (extracted) {
episodes.push(cleanEpisodeData(extracted))
}
From 22b8622c677db777f1a6c1ac88c811dafbf87b77 Mon Sep 17 00:00:00 2001
From: advplyr
Date: Sun, 9 Apr 2023 15:01:14 -0500
Subject: [PATCH 09/41] Fix:Crash for invalid payload to update cover endpoint
#1644
---
server/controllers/LibraryItemController.js | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/server/controllers/LibraryItemController.js b/server/controllers/LibraryItemController.js
index d26f57f2..55bbe72b 100644
--- a/server/controllers/LibraryItemController.js
+++ b/server/controllers/LibraryItemController.js
@@ -162,12 +162,12 @@ class LibraryItemController {
// PATCH: api/items/:id/cover
async updateCover(req, res) {
- var libraryItem = req.libraryItem
+ const libraryItem = req.libraryItem
if (!req.body.cover) {
- return res.status(400).error('Invalid request no cover path')
+ return res.status(400).send('Invalid request no cover path')
}
- var validationResult = await this.coverManager.validateCoverPath(req.body.cover, libraryItem)
+ const validationResult = await this.coverManager.validateCoverPath(req.body.cover, libraryItem)
if (validationResult.error) {
return res.status(500).send(validationResult.error)
}
From b96f878d6962b50be51a3402c5c710b40648d487 Mon Sep 17 00:00:00 2001
From: advplyr
Date: Sun, 9 Apr 2023 15:37:49 -0500
Subject: [PATCH 10/41] Update:Sleep timer presets and add custom time input
#1357
---
client/components/modals/SleepTimerModal.vue | 40 ++++++++++++++------
client/components/ui/TextInput.vue | 6 ++-
2 files changed, 33 insertions(+), 13 deletions(-)
diff --git a/client/components/modals/SleepTimerModal.vue b/client/components/modals/SleepTimerModal.vue
index 84e1d7bb..60193050 100644
--- a/client/components/modals/SleepTimerModal.vue
+++ b/client/components/modals/SleepTimerModal.vue
@@ -9,10 +9,14 @@
@@ -284,6 +291,12 @@ export default {
}
},
computed: {
+ userToken() {
+ return this.$store.getters['user/getToken']
+ },
+ downloadPath() {
+ return `${process.env.serverUrl}/api/items/${this.libraryItemId}/download?token=${this.userToken}`
+ },
dateFormat() {
return this.$store.state.serverSettings.dateFormat
},
diff --git a/server/controllers/LibraryItemController.js b/server/controllers/LibraryItemController.js
index 55bbe72b..b1b8cc6a 100644
--- a/server/controllers/LibraryItemController.js
+++ b/server/controllers/LibraryItemController.js
@@ -2,6 +2,7 @@ const fs = require('../libs/fsExtra')
const Logger = require('../Logger')
const SocketAuthority = require('../SocketAuthority')
+const zipHelpers = require('../utils/zipHelpers')
const { reqSupportsWebp, isNullOrNaN } = require('../utils/index')
const { ScanResult } = require('../utils/constants')
@@ -69,6 +70,17 @@ class LibraryItemController {
res.sendStatus(200)
}
+ download(req, res) {
+ if (!req.user.canDownload) {
+ Logger.warn('User attempted to download without permission', req.user)
+ return res.sendStatus(403)
+ }
+
+ const libraryItemPath = req.libraryItem.path
+ const filename = `${req.libraryItem.media.metadata.title}.zip`
+ zipHelpers.zipDirectoryPipe(libraryItemPath, filename, res)
+ }
+
//
// PATCH: will create new authors & series if in payload
//
diff --git a/server/routers/ApiRouter.js b/server/routers/ApiRouter.js
index 2264e175..7850adcb 100644
--- a/server/routers/ApiRouter.js
+++ b/server/routers/ApiRouter.js
@@ -98,6 +98,7 @@ class ApiRouter {
this.router.get('/items/:id', LibraryItemController.middleware.bind(this), LibraryItemController.findOne.bind(this))
this.router.patch('/items/:id', LibraryItemController.middleware.bind(this), LibraryItemController.update.bind(this))
this.router.delete('/items/:id', LibraryItemController.middleware.bind(this), LibraryItemController.delete.bind(this))
+ this.router.get('/items/:id/download', LibraryItemController.middleware.bind(this), LibraryItemController.download.bind(this))
this.router.patch('/items/:id/media', LibraryItemController.middleware.bind(this), LibraryItemController.updateMedia.bind(this))
this.router.get('/items/:id/cover', LibraryItemController.middleware.bind(this), LibraryItemController.getCover.bind(this))
this.router.post('/items/:id/cover', LibraryItemController.middleware.bind(this), LibraryItemController.uploadCover.bind(this))
diff --git a/server/utils/zipHelpers.js b/server/utils/zipHelpers.js
new file mode 100644
index 00000000..c1617272
--- /dev/null
+++ b/server/utils/zipHelpers.js
@@ -0,0 +1,52 @@
+const Logger = require('../Logger')
+const archiver = require('../libs/archiver')
+
+module.exports.zipDirectoryPipe = (path, filename, res) => {
+ return new Promise((resolve, reject) => {
+ // create a file to stream archive data to
+ res.attachment(filename)
+
+ const archive = archiver('zip', {
+ zlib: { level: 9 } // Sets the compression level.
+ })
+
+ // listen for all archive data to be written
+ // 'close' event is fired only when a file descriptor is involved
+ res.on('close', () => {
+ Logger.info(archive.pointer() + ' total bytes')
+ Logger.debug('archiver has been finalized and the output file descriptor has closed.')
+ resolve()
+ })
+
+ // This event is fired when the data source is drained no matter what was the data source.
+ // It is not part of this library but rather from the NodeJS Stream API.
+ // @see: https://nodejs.org/api/stream.html#stream_event_end
+ res.on('end', () => {
+ Logger.debug('Data has been drained')
+ })
+
+ // good practice to catch warnings (ie stat failures and other non-blocking errors)
+ archive.on('warning', function (err) {
+ if (err.code === 'ENOENT') {
+ // log warning
+ Logger.warn(`[DownloadManager] Archiver warning: ${err.message}`)
+ } else {
+ // throw error
+ Logger.error(`[DownloadManager] Archiver error: ${err.message}`)
+ // throw err
+ reject(err)
+ }
+ })
+ archive.on('error', function (err) {
+ Logger.error(`[DownloadManager] Archiver error: ${err.message}`)
+ reject(err)
+ })
+
+ // pipe archive data to the file
+ archive.pipe(res)
+
+ archive.directory(path, false)
+
+ archive.finalize()
+ })
+}
\ No newline at end of file
From 77cc0934be44d652aa83cfeff6c50f9798a0593b Mon Sep 17 00:00:00 2001
From: advplyr
Date: Sun, 9 Apr 2023 17:20:56 -0500
Subject: [PATCH 13/41] Update:Episodes table sort by pub date treats episodes
with no pub date as the oldest #1454
---
.../tables/podcast/EpisodesTable.vue | 22 ++++++++++++-------
1 file changed, 14 insertions(+), 8 deletions(-)
diff --git a/client/components/tables/podcast/EpisodesTable.vue b/client/components/tables/podcast/EpisodesTable.vue
index a5af04cd..feec71eb 100644
--- a/client/components/tables/podcast/EpisodesTable.vue
+++ b/client/components/tables/podcast/EpisodesTable.vue
@@ -54,7 +54,7 @@ export default {
quickMatchingEpisodes: false,
search: null,
searchTimeout: null,
- searchText: null,
+ searchText: null
}
},
watch: {
@@ -139,19 +139,25 @@ export default {
return episodeProgress && !episodeProgress.isFinished
})
.sort((a, b) => {
- if (this.sortDesc) {
- return String(b[this.sortKey]).localeCompare(String(a[this.sortKey]), undefined, { numeric: true, sensitivity: 'base' })
+ let aValue = a[this.sortKey]
+ let bValue = b[this.sortKey]
+
+ // Sort episodes with no pub date as the oldest
+ if (this.sortKey === 'publishedAt') {
+ if (!aValue) aValue = Number.MAX_VALUE
+ if (!bValue) bValue = Number.MAX_VALUE
}
- return String(a[this.sortKey]).localeCompare(String(b[this.sortKey]), undefined, { numeric: true, sensitivity: 'base' })
+
+ if (this.sortDesc) {
+ return String(bValue).localeCompare(String(aValue), undefined, { numeric: true, sensitivity: 'base' })
+ }
+ return String(aValue).localeCompare(String(bValue), undefined, { numeric: true, sensitivity: 'base' })
})
},
episodesList() {
return this.episodesSorted.filter((episode) => {
if (!this.searchText) return true
- return (
- (episode.title && episode.title.toLowerCase().includes(this.searchText)) ||
- (episode.subtitle && episode.subtitle.toLowerCase().includes(this.searchText))
- )
+ return (episode.title && episode.title.toLowerCase().includes(this.searchText)) || (episode.subtitle && episode.subtitle.toLowerCase().includes(this.searchText))
})
},
selectedIsFinished() {
From b0a8f3d207aac82431c95aee82dd857700470d90 Mon Sep 17 00:00:00 2001
From: Dr-Blank <64108942+Dr-Blank@users.noreply.github.com>
Date: Sun, 9 Apr 2023 23:58:51 -0400
Subject: [PATCH 14/41] Kick off Gujarati translation.
---
client/strings/gu.json | 642 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 642 insertions(+)
create mode 100644 client/strings/gu.json
diff --git a/client/strings/gu.json b/client/strings/gu.json
new file mode 100644
index 00000000..ebc3d2df
--- /dev/null
+++ b/client/strings/gu.json
@@ -0,0 +1,642 @@
+{
+ "ButtonAdd": "ઉમેરો",
+ "ButtonAddChapters": "પ્રકરણો ઉમેરો",
+ "ButtonAddPodcasts": "પોડકાસ્ટ ઉમેરો",
+ "ButtonAddYourFirstLibrary": "તમારી પ્રથમ પુસ્તકાલય ઉમેરો",
+ "ButtonApply": "લાગુ કરો",
+ "ButtonApplyChapters": "પ્રકરણો લાગુ કરો",
+ "ButtonAuthors": "લેખકો",
+ "ButtonBrowseForFolder": "ફોલ્ડર માટે જુઓ",
+ "ButtonCancel": "રદ કરો",
+ "ButtonCancelEncode": "એન્કોડ રદ કરો",
+ "ButtonChangeRootPassword": "રૂટ પાસવર્ડ બદલો",
+ "ButtonCheckAndDownloadNewEpisodes": "નવા એપિસોડ્સ ચેક કરો અને ડાઉનલોડ કરો",
+ "ButtonChooseAFolder": "ફોલ્ડર પસંદ કરો",
+ "ButtonChooseFiles": "ફાઇલો પસંદ કરો",
+ "ButtonClearFilter": "ફિલ્ટર જતુ કરો ",
+ "ButtonCloseFeed": "ફીડ બંધ કરો",
+ "ButtonCollections": "સંગ્રહ",
+ "ButtonConfigureScanner": "સ્કેનર સેટિંગ બદલો",
+ "ButtonCreate": "બનાવો",
+ "ButtonCreateBackup": "બેકઅપ બનાવો",
+ "ButtonDelete": "કાઢી નાખો",
+ "ButtonDownloadQueue": "કતાર ડાઉનલોડ કરો",
+ "ButtonEdit": "સંપાદિત કરો",
+ "ButtonEditChapters": "પ્રકરણો સંપાદિત કરો",
+ "ButtonEditPodcast": "પોડકાસ્ટ સંપાદિત કરો",
+ "ButtonForceReScan": "બળપૂર્વક ફરીથી સ્કેન કરો",
+ "ButtonFullPath": "સંપૂર્ણ પથ",
+ "ButtonHide": "છુપાવો",
+ "ButtonHome": "ઘર",
+ "ButtonIssues": "સમસ્યાઓ",
+ "ButtonLatest": "નવીનતમ",
+ "ButtonLibrary": "પુસ્તકાલય",
+ "ButtonLogout": "લૉગ આઉટ",
+ "ButtonLookup": "શોધો",
+ "ButtonManageTracks": "ટ્રેક્સ મેનેજ કરો",
+ "ButtonMapChapterTitles": "પ્રકરણ શીર્ષકો મેપ કરો",
+ "ButtonMatchAllAuthors": "બધા મેળ ખાતા લેખકો શોધો",
+ "ButtonMatchBooks": "મેળ ખાતી પુસ્તકો શોધો",
+ "ButtonNevermind": "કંઈ વાંધો નહીં",
+ "ButtonOk": "ઓકે",
+ "ButtonOpenFeed": "ફીડ ખોલો",
+ "ButtonOpenManager": "મેનેજર ખોલો",
+ "ButtonPlay": "ચલાવો",
+ "ButtonPlaying": "ચલાવી રહ્યું છે",
+ "ButtonPlaylists": "પ્લેલિસ્ટ",
+ "ButtonPurgeAllCache": "બધો Cache કાઢી નાખો",
+ "ButtonPurgeItemsCache": "વસ્તુઓનો Cache કાઢી નાખો",
+ "ButtonPurgeMediaProgress": "બધું સાંભળ્યું કાઢી નાખો",
+ "ButtonQueueAddItem": "કતારમાં ઉમેરો",
+ "ButtonQueueRemoveItem": "કતારથી કાઢી નાખો",
+ "ButtonQuickMatch": "ઝડપી મેળ ખવડાવો",
+ "ButtonRead": "વાંચો",
+ "ButtonRemove": "કાઢી નાખો",
+ "ButtonRemoveAll": "બધું કાઢી નાખો",
+ "ButtonRemoveAllLibraryItems": "બધું પુસ્તકાલય વસ્તુઓ કાઢી નાખો",
+ "ButtonRemoveFromContinueListening": "સાંભળતી પુસ્તકો માંથી કાઢી નાખો",
+ "ButtonRemoveSeriesFromContinueSeries": "સાંભળતી સિરીઝ માંથી કાઢી નાખો",
+ "ButtonReScan": "ફરીથી સ્કેન કરો",
+ "ButtonReset": "રીસેટ કરો",
+ "ButtonRestore": "પુનઃસ્થાપિત કરો",
+ "ButtonSave": "સાચવો",
+ "ButtonSaveAndClose": "સાચવો અને બંધ કરો",
+ "ButtonSaveTracklist": "ટ્રેક યાદી સાચવો",
+ "ButtonScan": "સ્કેન કરો",
+ "ButtonScanLibrary": "પુસ્તકાલય સ્કેન કરો",
+ "ButtonSearch": "શોધો",
+ "ButtonSelectFolderPath": "ફોલ્ડર પથ પસંદ કરો",
+ "ButtonSeries": "સિરીઝ",
+ "ButtonSetChaptersFromTracks": "ટ્રેક્સથી પ્રકરણો સેટ કરો",
+ "ButtonShiftTimes": "સમય શિફ્ટ કરો",
+ "ButtonShow": "બતાવો",
+ "ButtonStartM4BEncode": "M4B એન્કોડ શરૂ કરો",
+ "ButtonStartMetadataEmbed": "મેટાડેટા એમ્બેડ શરૂ કરો",
+ "ButtonSubmit": "સબમિટ કરો",
+ "ButtonUpload": "અપલોડ કરો",
+ "ButtonUploadBackup": "બેકઅપ અપલોડ કરો",
+ "ButtonUploadCover": "કવર અપલોડ કરો",
+ "ButtonUploadOPMLFile": "OPML ફાઇલ અપલોડ કરો",
+ "ButtonUserDelete": "વપરાશકર્તા {0} કાઢી નાખો",
+ "ButtonUserEdit": "વપરાશકર્તા {0} સંપાદિત કરો",
+ "ButtonViewAll": "બધું જુઓ",
+ "ButtonYes": "હા",
+ "HeaderAccount": "એકાઉન્ટ",
+ "HeaderAdvanced": "અડ્વાન્સડ",
+ "HeaderAppriseNotificationSettings": "Apprise સૂચના સેટિંગ્સ",
+ "HeaderAudiobookTools": "Audiobook File Management Tools",
+ "HeaderAudioTracks": "Audio Tracks",
+ "HeaderBackups": "Backups",
+ "HeaderChangePassword": "Change Password",
+ "HeaderChapters": "Chapters",
+ "HeaderChooseAFolder": "Choose a Folder",
+ "HeaderCollection": "Collection",
+ "HeaderCollectionItems": "Collection Items",
+ "HeaderCover": "Cover",
+ "HeaderCurrentDownloads": "Current Downloads",
+ "HeaderDetails": "Details",
+ "HeaderDownloadQueue": "Download Queue",
+ "HeaderEpisodes": "Episodes",
+ "HeaderFiles": "Files",
+ "HeaderFindChapters": "Find Chapters",
+ "HeaderIgnoredFiles": "Ignored Files",
+ "HeaderItemFiles": "Item Files",
+ "HeaderItemMetadataUtils": "Item Metadata Utils",
+ "HeaderLastListeningSession": "Last Listening Session",
+ "HeaderLatestEpisodes": "Latest episodes",
+ "HeaderLibraries": "Libraries",
+ "HeaderLibraryFiles": "Library Files",
+ "HeaderLibraryStats": "Library Stats",
+ "HeaderListeningSessions": "Listening Sessions",
+ "HeaderListeningStats": "Listening Stats",
+ "HeaderLogin": "Login",
+ "HeaderLogs": "Logs",
+ "HeaderManageGenres": "Manage Genres",
+ "HeaderManageTags": "Manage Tags",
+ "HeaderMapDetails": "Map details",
+ "HeaderMatch": "Match",
+ "HeaderMetadataToEmbed": "Metadata to embed",
+ "HeaderNewAccount": "New Account",
+ "HeaderNewLibrary": "New Library",
+ "HeaderNotifications": "Notifications",
+ "HeaderOpenRSSFeed": "Open RSS Feed",
+ "HeaderOtherFiles": "Other Files",
+ "HeaderPermissions": "Permissions",
+ "HeaderPlayerQueue": "Player Queue",
+ "HeaderPlaylist": "Playlist",
+ "HeaderPlaylistItems": "Playlist Items",
+ "HeaderPodcastsToAdd": "Podcasts to Add",
+ "HeaderPreviewCover": "Preview Cover",
+ "HeaderRemoveEpisode": "Remove Episode",
+ "HeaderRemoveEpisodes": "Remove {0} Episodes",
+ "HeaderRSSFeedGeneral": "RSS Details",
+ "HeaderRSSFeedIsOpen": "RSS Feed is Open",
+ "HeaderSavedMediaProgress": "Saved Media Progress",
+ "HeaderSchedule": "Schedule",
+ "HeaderScheduleLibraryScans": "Schedule Automatic Library Scans",
+ "HeaderSession": "Session",
+ "HeaderSetBackupSchedule": "Set Backup Schedule",
+ "HeaderSettings": "Settings",
+ "HeaderSettingsDisplay": "Display",
+ "HeaderSettingsExperimental": "Experimental Features",
+ "HeaderSettingsGeneral": "General",
+ "HeaderSettingsScanner": "Scanner",
+ "HeaderSleepTimer": "Sleep Timer",
+ "HeaderStatsLargestItems": "Largest Items",
+ "HeaderStatsLongestItems": "Longest Items (hrs)",
+ "HeaderStatsMinutesListeningChart": "Minutes Listening (last 7 days)",
+ "HeaderStatsRecentSessions": "Recent Sessions",
+ "HeaderStatsTop10Authors": "Top 10 Authors",
+ "HeaderStatsTop5Genres": "Top 5 Genres",
+ "HeaderTools": "Tools",
+ "HeaderUpdateAccount": "Update Account",
+ "HeaderUpdateAuthor": "Update Author",
+ "HeaderUpdateDetails": "Update Details",
+ "HeaderUpdateLibrary": "Update Library",
+ "HeaderUsers": "Users",
+ "HeaderYourStats": "Your Stats",
+ "LabelAbridged": "Abridged",
+ "LabelAccountType": "Account Type",
+ "LabelAccountTypeAdmin": "Admin",
+ "LabelAccountTypeGuest": "Guest",
+ "LabelAccountTypeUser": "User",
+ "LabelActivity": "Activity",
+ "LabelAddedAt": "Added At",
+ "LabelAddToCollection": "Add to Collection",
+ "LabelAddToCollectionBatch": "Add {0} Books to Collection",
+ "LabelAddToPlaylist": "Add to Playlist",
+ "LabelAddToPlaylistBatch": "Add {0} Items to Playlist",
+ "LabelAll": "All",
+ "LabelAllUsers": "All Users",
+ "LabelAlreadyInYourLibrary": "Already in your library",
+ "LabelAppend": "Append",
+ "LabelAuthor": "Author",
+ "LabelAuthorFirstLast": "Author (First Last)",
+ "LabelAuthorLastFirst": "Author (Last, First)",
+ "LabelAuthors": "Authors",
+ "LabelAutoDownloadEpisodes": "Auto Download Episodes",
+ "LabelBackToUser": "Back to User",
+ "LabelBackupsEnableAutomaticBackups": "Enable automatic backups",
+ "LabelBackupsEnableAutomaticBackupsHelp": "Backups saved in /metadata/backups",
+ "LabelBackupsMaxBackupSize": "Maximum backup size (in GB)",
+ "LabelBackupsMaxBackupSizeHelp": "As a safeguard against misconfiguration, backups will fail if they exceed the configured size.",
+ "LabelBackupsNumberToKeep": "Number of backups to keep",
+ "LabelBackupsNumberToKeepHelp": "Only 1 backup will be removed at a time so if you already have more backups than this you should manually remove them.",
+ "LabelBooks": "Books",
+ "LabelChangePassword": "Change Password",
+ "LabelChaptersFound": "chapters found",
+ "LabelChapterTitle": "Chapter Title",
+ "LabelClosePlayer": "Close player",
+ "LabelCollapseSeries": "Collapse Series",
+ "LabelCollections": "Collections",
+ "LabelComplete": "Complete",
+ "LabelConfirmPassword": "Confirm Password",
+ "LabelContinueListening": "Continue Listening",
+ "LabelContinueSeries": "Continue Series",
+ "LabelCover": "Cover",
+ "LabelCoverImageURL": "Cover Image URL",
+ "LabelCreatedAt": "Created At",
+ "LabelCronExpression": "Cron Expression",
+ "LabelCurrent": "Current",
+ "LabelCurrently": "Currently:",
+ "LabelCustomCronExpression": "Custom Cron Expression:",
+ "LabelDatetime": "Datetime",
+ "LabelDescription": "Description",
+ "LabelDeselectAll": "Deselect All",
+ "LabelDevice": "Device",
+ "LabelDeviceInfo": "Device Info",
+ "LabelDirectory": "Directory",
+ "LabelDiscFromFilename": "Disc from Filename",
+ "LabelDiscFromMetadata": "Disc from Metadata",
+ "LabelDownload": "Download",
+ "LabelDuration": "Duration",
+ "LabelDurationFound": "Duration found:",
+ "LabelEdit": "Edit",
+ "LabelEnable": "Enable",
+ "LabelEnd": "End",
+ "LabelEpisode": "Episode",
+ "LabelEpisodeTitle": "Episode Title",
+ "LabelEpisodeType": "Episode Type",
+ "LabelExample": "Example",
+ "LabelExplicit": "Explicit",
+ "LabelFeedURL": "Feed URL",
+ "LabelFile": "File",
+ "LabelFileBirthtime": "File Birthtime",
+ "LabelFileModified": "File Modified",
+ "LabelFilename": "Filename",
+ "LabelFilterByUser": "Filter by User",
+ "LabelFindEpisodes": "Find Episodes",
+ "LabelFinished": "Finished",
+ "LabelFolder": "Folder",
+ "LabelFolders": "Folders",
+ "LabelGenre": "Genre",
+ "LabelGenres": "Genres",
+ "LabelHardDeleteFile": "Hard delete file",
+ "LabelHour": "Hour",
+ "LabelIcon": "Icon",
+ "LabelIncludeInTracklist": "Include in Tracklist",
+ "LabelIncomplete": "Incomplete",
+ "LabelInProgress": "In Progress",
+ "LabelInterval": "Interval",
+ "LabelIntervalCustomDailyWeekly": "Custom daily/weekly",
+ "LabelIntervalEvery12Hours": "Every 12 hours",
+ "LabelIntervalEvery15Minutes": "Every 15 minutes",
+ "LabelIntervalEvery2Hours": "Every 2 hours",
+ "LabelIntervalEvery30Minutes": "Every 30 minutes",
+ "LabelIntervalEvery6Hours": "Every 6 hours",
+ "LabelIntervalEveryDay": "Every day",
+ "LabelIntervalEveryHour": "Every hour",
+ "LabelInvalidParts": "Invalid Parts",
+ "LabelItem": "Item",
+ "LabelLanguage": "Language",
+ "LabelLanguageDefaultServer": "Default Server Language",
+ "LabelLastSeen": "Last Seen",
+ "LabelLastTime": "Last Time",
+ "LabelLastUpdate": "Last Update",
+ "LabelLess": "Less",
+ "LabelLibrariesAccessibleToUser": "Libraries Accessible to User",
+ "LabelLibrary": "Library",
+ "LabelLibraryItem": "Library Item",
+ "LabelLibraryName": "Library Name",
+ "LabelLimit": "Limit",
+ "LabelListenAgain": "Listen Again",
+ "LabelLogLevelDebug": "Debug",
+ "LabelLogLevelInfo": "Info",
+ "LabelLogLevelWarn": "Warn",
+ "LabelLookForNewEpisodesAfterDate": "Look for new episodes after this date",
+ "LabelMediaPlayer": "Media Player",
+ "LabelMediaType": "Media Type",
+ "LabelMetadataProvider": "Metadata Provider",
+ "LabelMetaTag": "Meta Tag",
+ "LabelMinute": "Minute",
+ "LabelMissing": "Missing",
+ "LabelMissingParts": "Missing Parts",
+ "LabelMore": "More",
+ "LabelName": "Name",
+ "LabelNarrator": "Narrator",
+ "LabelNarrators": "Narrators",
+ "LabelNew": "New",
+ "LabelNewestAuthors": "Newest Authors",
+ "LabelNewestEpisodes": "Newest Episodes",
+ "LabelNewPassword": "New Password",
+ "LabelNextBackupDate": "Next backup date",
+ "LabelNextScheduledRun": "Next scheduled run",
+ "LabelNotes": "Notes",
+ "LabelNotFinished": "Not Finished",
+ "LabelNotificationAppriseURL": "Apprise URL(s)",
+ "LabelNotificationAvailableVariables": "Available variables",
+ "LabelNotificationBodyTemplate": "Body Template",
+ "LabelNotificationEvent": "Notification Event",
+ "LabelNotificationsMaxFailedAttempts": "Max failed attempts",
+ "LabelNotificationsMaxFailedAttemptsHelp": "Notifications are disabled once they fail to send this many times",
+ "LabelNotificationsMaxQueueSize": "Max queue size for notification events",
+ "LabelNotificationsMaxQueueSizeHelp": "Events are limited to firing 1 per second. Events will be ignored if the queue is at max size. This prevents notification spamming.",
+ "LabelNotificationTitleTemplate": "Title Template",
+ "LabelNotStarted": "Not Started",
+ "LabelNumberOfBooks": "Number of Books",
+ "LabelNumberOfEpisodes": "# of Episodes",
+ "LabelOpenRSSFeed": "Open RSS Feed",
+ "LabelOverwrite": "Overwrite",
+ "LabelPassword": "Password",
+ "LabelPath": "Path",
+ "LabelPermissionsAccessAllLibraries": "Can Access All Libraries",
+ "LabelPermissionsAccessAllTags": "Can Access All Tags",
+ "LabelPermissionsAccessExplicitContent": "Can Access Explicit Content",
+ "LabelPermissionsDelete": "Can Delete",
+ "LabelPermissionsDownload": "Can Download",
+ "LabelPermissionsUpdate": "Can Update",
+ "LabelPermissionsUpload": "Can Upload",
+ "LabelPhotoPathURL": "Photo Path/URL",
+ "LabelPlaylists": "Playlists",
+ "LabelPlayMethod": "Play Method",
+ "LabelPodcast": "Podcast",
+ "LabelPodcasts": "Podcasts",
+ "LabelPodcastType": "Podcast Type",
+ "LabelPrefixesToIgnore": "Prefixes to Ignore (case insensitive)",
+ "LabelPreventIndexing": "Prevent your feed from being indexed by iTunes and Google podcast directories",
+ "LabelProgress": "Progress",
+ "LabelProvider": "Provider",
+ "LabelPubDate": "Pub Date",
+ "LabelPublisher": "Publisher",
+ "LabelPublishYear": "Publish Year",
+ "LabelRecentlyAdded": "Recently Added",
+ "LabelRecentSeries": "Recent Series",
+ "LabelRecommended": "Recommended",
+ "LabelRegion": "Region",
+ "LabelReleaseDate": "Release Date",
+ "LabelRemoveCover": "Remove cover",
+ "LabelRSSFeedCustomOwnerEmail": "Custom owner Email",
+ "LabelRSSFeedCustomOwnerName": "Custom owner Name",
+ "LabelRSSFeedOpen": "RSS Feed Open",
+ "LabelRSSFeedPreventIndexing": "Prevent Indexing",
+ "LabelRSSFeedSlug": "RSS Feed Slug",
+ "LabelRSSFeedURL": "RSS Feed URL",
+ "LabelSearchTerm": "Search Term",
+ "LabelSearchTitle": "Search Title",
+ "LabelSearchTitleOrASIN": "Search Title or ASIN",
+ "LabelSeason": "Season",
+ "LabelSequence": "Sequence",
+ "LabelSeries": "Series",
+ "LabelSeriesName": "Series Name",
+ "LabelSeriesProgress": "Series Progress",
+ "LabelSettingsBookshelfViewHelp": "Skeumorphic design with wooden shelves",
+ "LabelSettingsChromecastSupport": "Chromecast support",
+ "LabelSettingsDateFormat": "Date Format",
+ "LabelSettingsDisableWatcher": "Disable Watcher",
+ "LabelSettingsDisableWatcherForLibrary": "Disable folder watcher for library",
+ "LabelSettingsDisableWatcherHelp": "Disables the automatic adding/updating of items when file changes are detected. *Requires server restart",
+ "LabelSettingsEnableEReader": "Enable e-reader for all users",
+ "LabelSettingsEnableEReaderHelp": "E-reader is still a work in progress, but use this setting to open it up to all your users (or use the \"Experimental Features\" toggle just for use by you)",
+ "LabelSettingsExperimentalFeatures": "Experimental features",
+ "LabelSettingsExperimentalFeaturesHelp": "Features in development that could use your feedback and help testing. Click to open github discussion.",
+ "LabelSettingsFindCovers": "Find covers",
+ "LabelSettingsFindCoversHelp": "If your audiobook does not have an embedded cover or a cover image inside the folder, the scanner will attempt to find a cover. Note: This will extend scan time",
+ "LabelSettingsHomePageBookshelfView": "Home page use bookshelf view",
+ "LabelSettingsLibraryBookshelfView": "Library use bookshelf view",
+ "LabelSettingsOverdriveMediaMarkers": "Use Overdrive Media Markers for chapters",
+ "LabelSettingsOverdriveMediaMarkersHelp": "MP3 files from Overdrive come with chapter timings embedded as custom metadata. Enabling this will use these tags for chapter timings automatically",
+ "LabelSettingsParseSubtitles": "Parse subtitles",
+ "LabelSettingsParseSubtitlesHelp": "Extract subtitles from audiobook folder names. Subtitle must be seperated by \" - \" i.e. \"Book Title - A Subtitle Here\" has the subtitle \"A Subtitle Here\"",
+ "LabelSettingsPreferAudioMetadata": "Prefer audio metadata",
+ "LabelSettingsPreferAudioMetadataHelp": "Audio file ID3 meta tags will be used for book details over folder names",
+ "LabelSettingsPreferMatchedMetadata": "Prefer matched metadata",
+ "LabelSettingsPreferMatchedMetadataHelp": "Matched data will overide item details when using Quick Match. By default Quick Match will only fill in missing details.",
+ "LabelSettingsPreferOPFMetadata": "Prefer OPF metadata",
+ "LabelSettingsPreferOPFMetadataHelp": "OPF file metadata will be used for book details over folder names",
+ "LabelSettingsSkipMatchingBooksWithASIN": "Skip matching books that already have an ASIN",
+ "LabelSettingsSkipMatchingBooksWithISBN": "Skip matching books that already have an ISBN",
+ "LabelSettingsSortingIgnorePrefixes": "Ignore prefixes when sorting",
+ "LabelSettingsSortingIgnorePrefixesHelp": "i.e. for prefix \"the\" book title \"The Book Title\" would sort as \"Book Title, The\"",
+ "LabelSettingsSquareBookCovers": "Use square book covers",
+ "LabelSettingsSquareBookCoversHelp": "Prefer to use square covers over standard 1.6:1 book covers",
+ "LabelSettingsStoreCoversWithItem": "Store covers with item",
+ "LabelSettingsStoreCoversWithItemHelp": "By default covers are stored in /metadata/items, enabling this setting will store covers in your library item folder. Only one file named \"cover\" will be kept",
+ "LabelSettingsStoreMetadataWithItem": "Store metadata with item",
+ "LabelSettingsStoreMetadataWithItemHelp": "By default metadata files are stored in /metadata/items, enabling this setting will store metadata files in your library item folders. Uses .abs file extension",
+ "LabelSettingsTimeFormat": "Time Format",
+ "LabelShowAll": "Show All",
+ "LabelSize": "Size",
+ "LabelSleepTimer": "Sleep timer",
+ "LabelStart": "Start",
+ "LabelStarted": "Started",
+ "LabelStartedAt": "Started At",
+ "LabelStartTime": "Start Time",
+ "LabelStatsAudioTracks": "Audio Tracks",
+ "LabelStatsAuthors": "Authors",
+ "LabelStatsBestDay": "Best Day",
+ "LabelStatsDailyAverage": "Daily Average",
+ "LabelStatsDays": "Days",
+ "LabelStatsDaysListened": "Days Listened",
+ "LabelStatsHours": "Hours",
+ "LabelStatsInARow": "in a row",
+ "LabelStatsItemsFinished": "Items Finished",
+ "LabelStatsItemsInLibrary": "Items in Library",
+ "LabelStatsMinutes": "minutes",
+ "LabelStatsMinutesListening": "Minutes Listening",
+ "LabelStatsOverallDays": "Overall Days",
+ "LabelStatsOverallHours": "Overall Hours",
+ "LabelStatsWeekListening": "Week Listening",
+ "LabelSubtitle": "Subtitle",
+ "LabelSupportedFileTypes": "Supported File Types",
+ "LabelTag": "Tag",
+ "LabelTags": "Tags",
+ "LabelTagsAccessibleToUser": "Tags Accessible to User",
+ "LabelTasks": "Tasks Running",
+ "LabelTimeListened": "Time Listened",
+ "LabelTimeListenedToday": "Time Listened Today",
+ "LabelTimeRemaining": "{0} remaining",
+ "LabelTimeToShift": "Time to shift in seconds",
+ "LabelTitle": "Title",
+ "LabelToolsEmbedMetadata": "Embed Metadata",
+ "LabelToolsEmbedMetadataDescription": "Embed metadata into audio files including cover image and chapters.",
+ "LabelToolsMakeM4b": "Make M4B Audiobook File",
+ "LabelToolsMakeM4bDescription": "Generate a .M4B audiobook file with embedded metadata, cover image, and chapters.",
+ "LabelToolsSplitM4b": "Split M4B to MP3's",
+ "LabelToolsSplitM4bDescription": "Create MP3's from an M4B split by chapters with embedded metadata, cover image, and chapters.",
+ "LabelTotalDuration": "Total Duration",
+ "LabelTotalTimeListened": "Total Time Listened",
+ "LabelTrackFromFilename": "Track from Filename",
+ "LabelTrackFromMetadata": "Track from Metadata",
+ "LabelTracks": "Tracks",
+ "LabelTracksMultiTrack": "Multi-track",
+ "LabelTracksSingleTrack": "Single-track",
+ "LabelType": "Type",
+ "LabelUnabridged": "Unabridged",
+ "LabelUnknown": "Unknown",
+ "LabelUpdateCover": "Update Cover",
+ "LabelUpdateCoverHelp": "Allow overwriting of existing covers for the selected books when a match is located",
+ "LabelUpdatedAt": "Updated At",
+ "LabelUpdateDetails": "Update Details",
+ "LabelUpdateDetailsHelp": "Allow overwriting of existing details for the selected books when a match is located",
+ "LabelUploaderDragAndDrop": "Drag & drop files or folders",
+ "LabelUploaderDropFiles": "Drop files",
+ "LabelUseChapterTrack": "Use chapter track",
+ "LabelUseFullTrack": "Use full track",
+ "LabelUser": "User",
+ "LabelUsername": "Username",
+ "LabelValue": "Value",
+ "LabelVersion": "Version",
+ "LabelViewBookmarks": "View bookmarks",
+ "LabelViewChapters": "View chapters",
+ "LabelViewQueue": "View player queue",
+ "LabelVolume": "Volume",
+ "LabelWeekdaysToRun": "Weekdays to run",
+ "LabelYourAudiobookDuration": "Your audiobook duration",
+ "LabelYourBookmarks": "Your Bookmarks",
+ "LabelYourPlaylists": "Your Playlists",
+ "LabelYourProgress": "Your Progress",
+ "MessageAddToPlayerQueue": "Add to player queue",
+ "MessageAppriseDescription": "To use this feature you will need to have an instance of Apprise API running or an api that will handle those same requests. The Apprise API Url should be the full URL path to send the notification, e.g., if your API instance is served at http://192.168.1.1:8337 then you would put http://192.168.1.1:8337/notify.",
+ "MessageBackupsDescription": "Backups include users, user progress, library item details, server settings, and images stored in /metadata/items & /metadata/authors. Backups do not include any files stored in your library folders.",
+ "MessageBatchQuickMatchDescription": "Quick Match will attempt to add missing covers and metadata for the selected items. Enable the options below to allow Quick Match to overwrite existing covers and/or metadata.",
+ "MessageBookshelfNoCollections": "You haven't made any collections yet",
+ "MessageBookshelfNoResultsForFilter": "No Results for filter \"{0}: {1}\"",
+ "MessageBookshelfNoRSSFeeds": "No RSS feeds are open",
+ "MessageBookshelfNoSeries": "You have no series",
+ "MessageChapterEndIsAfter": "Chapter end is after the end of your audiobook",
+ "MessageChapterErrorFirstNotZero": "First chapter must start at 0",
+ "MessageChapterErrorStartGteDuration": "Invalid start time must be less than audiobook duration",
+ "MessageChapterErrorStartLtPrev": "Invalid start time must be greater than or equal to previous chapter start time",
+ "MessageChapterStartIsAfter": "Chapter start is after the end of your audiobook",
+ "MessageCheckingCron": "Checking cron...",
+ "MessageConfirmDeleteBackup": "Are you sure you want to delete backup for {0}?",
+ "MessageConfirmDeleteLibrary": "Are you sure you want to permanently delete library \"{0}\"?",
+ "MessageConfirmDeleteSession": "Are you sure you want to delete this session?",
+ "MessageConfirmForceReScan": "Are you sure you want to force re-scan?",
+ "MessageConfirmMarkSeriesFinished": "Are you sure you want to mark all books in this series as finished?",
+ "MessageConfirmMarkSeriesNotFinished": "Are you sure you want to mark all books in this series as not finished?",
+ "MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?",
+ "MessageConfirmRemoveCollection": "Are you sure you want to remove collection \"{0}\"?",
+ "MessageConfirmRemoveEpisode": "Are you sure you want to remove episode \"{0}\"?",
+ "MessageConfirmRemoveEpisodes": "Are you sure you want to remove {0} episodes?",
+ "MessageConfirmRemovePlaylist": "Are you sure you want to remove your playlist \"{0}\"?",
+ "MessageConfirmRenameGenre": "Are you sure you want to rename genre \"{0}\" to \"{1}\" for all items?",
+ "MessageConfirmRenameGenreMergeNote": "Note: This genre already exists so they will be merged.",
+ "MessageConfirmRenameGenreWarning": "Warning! A similar genre with a different casing already exists \"{0}\".",
+ "MessageConfirmRenameTag": "Are you sure you want to rename tag \"{0}\" to \"{1}\" for all items?",
+ "MessageConfirmRenameTagMergeNote": "Note: This tag already exists so they will be merged.",
+ "MessageConfirmRenameTagWarning": "Warning! A similar tag with a different casing already exists \"{0}\".",
+ "MessageDownloadingEpisode": "Downloading episode",
+ "MessageDragFilesIntoTrackOrder": "Drag files into correct track order",
+ "MessageEmbedFinished": "Embed Finished!",
+ "MessageEpisodesQueuedForDownload": "{0} Episode(s) queued for download",
+ "MessageFeedURLWillBe": "Feed URL will be {0}",
+ "MessageFetching": "Fetching...",
+ "MessageForceReScanDescription": "will scan all files again like a fresh scan. Audio file ID3 tags, OPF files, and text files will be scanned as new.",
+ "MessageImportantNotice": "Important Notice!",
+ "MessageInsertChapterBelow": "Insert chapter below",
+ "MessageItemsSelected": "{0} Items Selected",
+ "MessageItemsUpdated": "{0} Items Updated",
+ "MessageJoinUsOn": "Join us on",
+ "MessageListeningSessionsInTheLastYear": "{0} listening sessions in the last year",
+ "MessageLoading": "Loading...",
+ "MessageLoadingFolders": "Loading folders...",
+ "MessageM4BFailed": "M4B Failed!",
+ "MessageM4BFinished": "M4B Finished!",
+ "MessageMapChapterTitles": "Map chapter titles to your existing audiobook chapters without adjusting timestamps",
+ "MessageMarkAsFinished": "Mark as Finished",
+ "MessageMarkAsNotFinished": "Mark as Not Finished",
+ "MessageMatchBooksDescription": "will attempt to match books in the library with a book from the selected search provider and fill in empty details and cover art. Does not overwrite details.",
+ "MessageNoAudioTracks": "No audio tracks",
+ "MessageNoAuthors": "No Authors",
+ "MessageNoBackups": "No Backups",
+ "MessageNoBookmarks": "No Bookmarks",
+ "MessageNoChapters": "No Chapters",
+ "MessageNoCollections": "No Collections",
+ "MessageNoCoversFound": "No Covers Found",
+ "MessageNoDescription": "No description",
+ "MessageNoDownloadsInProgress": "No downloads currently in progress",
+ "MessageNoDownloadsQueued": "No downloads queued",
+ "MessageNoEpisodeMatchesFound": "No episode matches found",
+ "MessageNoEpisodes": "No Episodes",
+ "MessageNoFoldersAvailable": "No Folders Available",
+ "MessageNoGenres": "No Genres",
+ "MessageNoIssues": "No Issues",
+ "MessageNoItems": "No Items",
+ "MessageNoItemsFound": "No items found",
+ "MessageNoListeningSessions": "No Listening Sessions",
+ "MessageNoLogs": "No Logs",
+ "MessageNoMediaProgress": "No Media Progress",
+ "MessageNoNotifications": "No Notifications",
+ "MessageNoPodcastsFound": "No podcasts found",
+ "MessageNoResults": "No Results",
+ "MessageNoSearchResultsFor": "No search results for \"{0}\"",
+ "MessageNoSeries": "No Series",
+ "MessageNoTags": "No Tags",
+ "MessageNoTasksRunning": "No Tasks Running",
+ "MessageNotYetImplemented": "Not yet implemented",
+ "MessageNoUpdateNecessary": "No update necessary",
+ "MessageNoUpdatesWereNecessary": "No updates were necessary",
+ "MessageNoUserPlaylists": "You have no playlists",
+ "MessageOr": "or",
+ "MessagePauseChapter": "Pause chapter playback",
+ "MessagePlayChapter": "Listen to beginning of chapter",
+ "MessagePlaylistCreateFromCollection": "Create playlist from collection",
+ "MessagePodcastHasNoRSSFeedForMatching": "Podcast has no RSS feed url to use for matching",
+ "MessageQuickMatchDescription": "Populate empty item details & cover with first match result from '{0}'. Does not overwrite details unless 'Prefer matched metadata' server setting is enabled.",
+ "MessageRemoveAllItemsWarning": "WARNING! This action will remove all library items from the database including any updates or matches you have made. This does not do anything to your actual files. Are you sure?",
+ "MessageRemoveChapter": "Remove chapter",
+ "MessageRemoveEpisodes": "Remove {0} episode(s)",
+ "MessageRemoveFromPlayerQueue": "Remove from player queue",
+ "MessageRemoveUserWarning": "Are you sure you want to permanently delete user \"{0}\"?",
+ "MessageReportBugsAndContribute": "Report bugs, request features, and contribute on",
+ "MessageResetChaptersConfirm": "Are you sure you want to reset chapters and undo the changes you made?",
+ "MessageRestoreBackupConfirm": "Are you sure you want to restore the backup created on",
+ "MessageRestoreBackupWarning": "Restoring a backup will overwrite the entire database located at /config and cover images in /metadata/items & /metadata/authors.
Backups do not modify any files in your library folders. If you have enabled server settings to store cover art and metadata in your library folders then those are not backed up or overwritten.
All clients using your server will be automatically refreshed.",
+ "MessageSearchResultsFor": "Search results for",
+ "MessageServerCouldNotBeReached": "Server could not be reached",
+ "MessageSetChaptersFromTracksDescription": "Set chapters using each audio file as a chapter and chapter title as the audio file name",
+ "MessageStartPlaybackAtTime": "Start playback for \"{0}\" at {1}?",
+ "MessageThinking": "Thinking...",
+ "MessageUploaderItemFailed": "Failed to upload",
+ "MessageUploaderItemSuccess": "Successfully Uploaded!",
+ "MessageUploading": "Uploading...",
+ "MessageValidCronExpression": "Valid cron expression",
+ "MessageWatcherIsDisabledGlobally": "Watcher is disabled globally in server settings",
+ "MessageXLibraryIsEmpty": "{0} Library is empty!",
+ "MessageYourAudiobookDurationIsLonger": "Your audiobook duration is longer than the duration found",
+ "MessageYourAudiobookDurationIsShorter": "Your audiobook duration is shorter than duration found",
+ "NoteChangeRootPassword": "Root user is the only user that can have an empty password",
+ "NoteChapterEditorTimes": "Note: First chapter start time must remain at 0:00 and the last chapter start time cannot exceed this audiobooks duration.",
+ "NoteFolderPicker": "Note: folders already mapped will not be shown",
+ "NoteFolderPickerDebian": "Note: Folder picker for the debian install is not fully implemented. You should enter the path to your library directly.",
+ "NoteRSSFeedPodcastAppsHttps": "Warning: Most podcast apps will require the RSS feed URL is using HTTPS",
+ "NoteRSSFeedPodcastAppsPubDate": "Warning: 1 or more of your episodes do not have a Pub Date. Some podcast apps require this.",
+ "NoteUploaderFoldersWithMediaFiles": "Folders with media files will be handled as separate library items.",
+ "NoteUploaderOnlyAudioFiles": "If uploading only audio files then each audio file will be handled as a separate audiobook.",
+ "NoteUploaderUnsupportedFiles": "Unsupported files are ignored. When choosing or dropping a folder, other files that are not in an item folder are ignored.",
+ "PlaceholderNewCollection": "New collection name",
+ "PlaceholderNewFolderPath": "New folder path",
+ "PlaceholderNewPlaylist": "New playlist name",
+ "PlaceholderSearch": "Search..",
+ "PlaceholderSearchEpisode": "Search episode..",
+ "ToastAccountUpdateFailed": "Failed to update account",
+ "ToastAccountUpdateSuccess": "Account updated",
+ "ToastAuthorImageRemoveFailed": "Failed to remove image",
+ "ToastAuthorImageRemoveSuccess": "Author image removed",
+ "ToastAuthorUpdateFailed": "Failed to update author",
+ "ToastAuthorUpdateMerged": "Author merged",
+ "ToastAuthorUpdateSuccess": "Author updated",
+ "ToastAuthorUpdateSuccessNoImageFound": "Author updated (no image found)",
+ "ToastBackupCreateFailed": "Failed to create backup",
+ "ToastBackupCreateSuccess": "Backup created",
+ "ToastBackupDeleteFailed": "Failed to delete backup",
+ "ToastBackupDeleteSuccess": "Backup deleted",
+ "ToastBackupRestoreFailed": "Failed to restore backup",
+ "ToastBackupUploadFailed": "Failed to upload backup",
+ "ToastBackupUploadSuccess": "Backup uploaded",
+ "ToastBatchUpdateFailed": "Batch update failed",
+ "ToastBatchUpdateSuccess": "Batch update success",
+ "ToastBookmarkCreateFailed": "Failed to create bookmark",
+ "ToastBookmarkCreateSuccess": "Bookmark added",
+ "ToastBookmarkRemoveFailed": "Failed to remove bookmark",
+ "ToastBookmarkRemoveSuccess": "Bookmark removed",
+ "ToastBookmarkUpdateFailed": "Failed to update bookmark",
+ "ToastBookmarkUpdateSuccess": "Bookmark updated",
+ "ToastChaptersHaveErrors": "Chapters have errors",
+ "ToastChaptersMustHaveTitles": "Chapters must have titles",
+ "ToastCollectionItemsRemoveFailed": "Failed to remove item(s) from collection",
+ "ToastCollectionItemsRemoveSuccess": "Item(s) removed from collection",
+ "ToastCollectionRemoveFailed": "Failed to remove collection",
+ "ToastCollectionRemoveSuccess": "Collection removed",
+ "ToastCollectionUpdateFailed": "Failed to update collection",
+ "ToastCollectionUpdateSuccess": "Collection updated",
+ "ToastItemCoverUpdateFailed": "Failed to update item cover",
+ "ToastItemCoverUpdateSuccess": "Item cover updated",
+ "ToastItemDetailsUpdateFailed": "Failed to update item details",
+ "ToastItemDetailsUpdateSuccess": "Item details updated",
+ "ToastItemDetailsUpdateUnneeded": "No updates needed for item details",
+ "ToastItemMarkedAsFinishedFailed": "Failed to mark as Finished",
+ "ToastItemMarkedAsFinishedSuccess": "Item marked as Finished",
+ "ToastItemMarkedAsNotFinishedFailed": "Failed to mark as Not Finished",
+ "ToastItemMarkedAsNotFinishedSuccess": "Item marked as Not Finished",
+ "ToastLibraryCreateFailed": "Failed to create library",
+ "ToastLibraryCreateSuccess": "Library \"{0}\" created",
+ "ToastLibraryDeleteFailed": "Failed to delete library",
+ "ToastLibraryDeleteSuccess": "Library deleted",
+ "ToastLibraryScanFailedToStart": "Failed to start scan",
+ "ToastLibraryScanStarted": "Library scan started",
+ "ToastLibraryUpdateFailed": "Failed to update library",
+ "ToastLibraryUpdateSuccess": "Library \"{0}\" updated",
+ "ToastPlaylistCreateFailed": "Failed to create playlist",
+ "ToastPlaylistCreateSuccess": "Playlist created",
+ "ToastPlaylistRemoveFailed": "Failed to remove playlist",
+ "ToastPlaylistRemoveSuccess": "Playlist removed",
+ "ToastPlaylistUpdateFailed": "Failed to update playlist",
+ "ToastPlaylistUpdateSuccess": "Playlist updated",
+ "ToastPodcastCreateFailed": "Failed to create podcast",
+ "ToastPodcastCreateSuccess": "Podcast created successfully",
+ "ToastRemoveItemFromCollectionFailed": "Failed to remove item from collection",
+ "ToastRemoveItemFromCollectionSuccess": "Item removed from collection",
+ "ToastRSSFeedCloseFailed": "Failed to close RSS feed",
+ "ToastRSSFeedCloseSuccess": "RSS feed closed",
+ "ToastSeriesUpdateFailed": "Series update failed",
+ "ToastSeriesUpdateSuccess": "Series update success",
+ "ToastSessionDeleteFailed": "Failed to delete session",
+ "ToastSessionDeleteSuccess": "Session deleted",
+ "ToastSocketConnected": "Socket connected",
+ "ToastSocketDisconnected": "Socket disconnected",
+ "ToastSocketFailedToConnect": "Socket failed to connect",
+ "ToastUserDeleteFailed": "Failed to delete user",
+ "ToastUserDeleteSuccess": "User deleted"
+}
From a77c3aae9313f33a46b64a0155f60b534132126e Mon Sep 17 00:00:00 2001
From: Tomazed
Date: Tue, 11 Apr 2023 09:41:38 +0200
Subject: [PATCH 15/41] MessageConfirmRemoveAllChapters translation fr
---
client/strings/fr.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/client/strings/fr.json b/client/strings/fr.json
index 0b03f174..98e527f6 100644
--- a/client/strings/fr.json
+++ b/client/strings/fr.json
@@ -465,7 +465,7 @@
"MessageConfirmForceReScan": "Êtes-vous sûr de vouloir lancer une Analyse Forcée ?",
"MessageConfirmMarkSeriesFinished": "Êtes-vous sûr de vouloir marquer comme terminé tous les livres de cette série ?",
"MessageConfirmMarkSeriesNotFinished": "Êtes-vous sûr de vouloir marquer comme non terminé tous les livres de cette série ?",
- "MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?",
+ "MessageConfirmRemoveAllChapters": "Êtes-vous sûr de vouloir supprimer tous les chapitres ?",
"MessageConfirmRemoveCollection": "Êtes-vous sûr de vouloir supprimer la collection « {0} » ?",
"MessageConfirmRemoveEpisode": "Êtes-vous sûr de vouloir supprimer l’épisode « {0} » ?",
"MessageConfirmRemoveEpisodes": "Êtes-vous sûr de vouloir supprimer {0} épisodes ?",
From 9b67fbe8d9012558367554b6454e4fb261903476 Mon Sep 17 00:00:00 2001
From: Dmitry Naboychenko
Date: Tue, 11 Apr 2023 21:09:18 +0300
Subject: [PATCH 16/41] Fix Russian localization
---
client/strings/ru.json | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/client/strings/ru.json b/client/strings/ru.json
index 0e0871eb..1227d516 100644
--- a/client/strings/ru.json
+++ b/client/strings/ru.json
@@ -155,7 +155,7 @@
"HeaderUpdateLibrary": "Обновить библиотеку",
"HeaderUsers": "Пользователи",
"HeaderYourStats": "Ваша статистика",
- "LabelAbridged": "Abridged",
+ "LabelAbridged": "Сокращенное издание",
"LabelAccountType": "Тип учетной записи",
"LabelAccountTypeAdmin": "Администратор",
"LabelAccountTypeGuest": "Гость",
@@ -394,7 +394,7 @@
"LabelStatsMinutes": "минут",
"LabelStatsMinutesListening": "Минут прослушано",
"LabelStatsOverallDays": "Всего дней",
- "LabelStatsOverallHours": "Всего сасов",
+ "LabelStatsOverallHours": "Всего часов",
"LabelStatsWeekListening": "Недель прослушано",
"LabelSubtitle": "Подзаголовок",
"LabelSupportedFileTypes": "Поддерживаемые типы файлов",
@@ -421,7 +421,7 @@
"LabelTracksMultiTrack": "Мультитрек",
"LabelTracksSingleTrack": "Один трек",
"LabelType": "Тип",
- "LabelUnabridged": "Unabridged",
+ "LabelUnabridged": "Полное издание",
"LabelUnknown": "Неизвестно",
"LabelUpdateCover": "Обновить обложку",
"LabelUpdateCoverHelp": "Позволяет перезаписывать существующие обложки для выбранных книг если будут найдены",
@@ -569,7 +569,7 @@
"PlaceholderNewFolderPath": "Путь к новой папке",
"PlaceholderNewPlaylist": "Новое название плейлиста",
"PlaceholderSearch": "Поиск...",
- "PlaceholderSearchEpisode": "Search episode...",
+ "PlaceholderSearchEpisode": "Поиск эпизода...",
"ToastAccountUpdateFailed": "Не удалось обновить учетную запись",
"ToastAccountUpdateSuccess": "Учетная запись обновлена",
"ToastAuthorImageRemoveFailed": "Не удалось удалить изображение",
From c1b2aaec9fd02035d5f960d32c32eda50fb5acd2 Mon Sep 17 00:00:00 2001
From: advplyr
Date: Tue, 11 Apr 2023 16:55:22 -0500
Subject: [PATCH 17/41] Fix:Set tone path for debian tone usage #1643
---
server/scanner/MediaFileScanner.js | 1 -
server/utils/toneHelpers.js | 4 ++++
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/server/scanner/MediaFileScanner.js b/server/scanner/MediaFileScanner.js
index adf2098b..b0c00b20 100644
--- a/server/scanner/MediaFileScanner.js
+++ b/server/scanner/MediaFileScanner.js
@@ -221,7 +221,6 @@ class MediaFileScanner {
*/
async scanMediaFiles(mediaLibraryFiles, libraryItem, libraryScan = null) {
const preferAudioMetadata = libraryScan ? !!libraryScan.preferAudioMetadata : !!global.ServerSettings.scannerPreferAudioMetadata
- const preferOverdriveMediaMarker = !!global.ServerSettings.scannerPreferOverdriveMediaMarker
let hasUpdated = false
diff --git a/server/utils/toneHelpers.js b/server/utils/toneHelpers.js
index d1a2b166..384b4c5d 100644
--- a/server/utils/toneHelpers.js
+++ b/server/utils/toneHelpers.js
@@ -73,6 +73,10 @@ module.exports.writeToneMetadataJsonFile = (libraryItem, chapters, filePath, tra
}
module.exports.tagAudioFile = (filePath, payload) => {
+ if (process.env.TONE_PATH) {
+ tone.TONE_PATH = process.env.TONE_PATH
+ }
+
return tone.tag(filePath, payload).then((data) => {
return true
}).catch((error) => {
From 122ec140e88382177c3123d100da37848f07a212 Mon Sep 17 00:00:00 2001
From: Divyang Joshi
Date: Tue, 11 Apr 2023 23:18:25 -0400
Subject: [PATCH 18/41] Add sortBy Last Book Added and Updated to series
---
client/components/app/BookShelfToolbar.vue | 8 ++++++++
client/strings/en-us.json | 2 ++
server/controllers/LibraryController.js | 4 ++++
3 files changed, 14 insertions(+)
diff --git a/client/components/app/BookShelfToolbar.vue b/client/components/app/BookShelfToolbar.vue
index d512bc1b..a3879279 100644
--- a/client/components/app/BookShelfToolbar.vue
+++ b/client/components/app/BookShelfToolbar.vue
@@ -163,6 +163,14 @@ export default {
text: this.$strings.LabelAddedAt,
value: 'addedAt'
},
+ {
+ text: this.$strings.LabelLastBookAdded,
+ value: 'lastBookAdded'
+ },
+ {
+ text: this.$strings.LabelLastBookUpdated,
+ value: 'lastBookUpdated'
+ },
{
text: this.$strings.LabelTotalDuration,
value: 'totalDuration'
diff --git a/client/strings/en-us.json b/client/strings/en-us.json
index 1ea2fa27..5cbef6d3 100644
--- a/client/strings/en-us.json
+++ b/client/strings/en-us.json
@@ -250,6 +250,8 @@
"LabelItem": "Item",
"LabelLanguage": "Language",
"LabelLanguageDefaultServer": "Default Server Language",
+ "LabelLastBookAdded": "Last Book Added",
+ "LabelLastBookUpdated": "Last Book Updated",
"LabelLastSeen": "Last Seen",
"LabelLastTime": "Last Time",
"LabelLastUpdate": "Last Update",
diff --git a/server/controllers/LibraryController.js b/server/controllers/LibraryController.js
index bbc70f31..154f6f17 100644
--- a/server/controllers/LibraryController.js
+++ b/server/controllers/LibraryController.js
@@ -417,6 +417,10 @@ class LibraryController {
return se.totalDuration
} else if (payload.sortBy === 'addedAt') {
return se.addedAt
+ } else if (payload.sortBy === 'lastBookUpdated') {
+ return Math.max(...(se.books).map(x => x.updatedAt), 0)
+ } else if (payload.sortBy === 'lastBookAdded') {
+ return Math.max(...(se.books).map(x => x.addedAt), 0)
} else { // sort by name
return this.db.serverSettings.sortingIgnorePrefix ? se.nameIgnorePrefixSort : se.name
}
From 69bac2ec1e44f87df86c1b1ef2c2b8778ed8d06a Mon Sep 17 00:00:00 2001
From: Divyang Joshi
Date: Wed, 12 Apr 2023 12:55:59 -0400
Subject: [PATCH 19/41] Add sorted by value to the series card
---
client/components/cards/LazySeriesCard.vue | 19 +++++++++++++------
client/strings/en-us.json | 1 +
2 files changed, 14 insertions(+), 6 deletions(-)
diff --git a/client/components/cards/LazySeriesCard.vue b/client/components/cards/LazySeriesCard.vue
index db5e3ec5..313530b6 100644
--- a/client/components/cards/LazySeriesCard.vue
+++ b/client/components/cards/LazySeriesCard.vue
@@ -81,13 +81,20 @@ export default {
return this.title
},
displaySortLine() {
- if (this.orderBy === 'addedAt') {
- // return this.addedAt
- return 'Added ' + this.$formatDate(this.addedAt, this.dateFormat)
- } else if (this.orderBy === 'totalDuration') {
- return 'Duration: ' + this.$elapsedPrettyExtended(this.totalDuration, false)
+ switch (this.orderBy) {
+ case 'addedAt':
+ return `${this.$strings.LabelAdded} ${this.$formatDate(this.addedAt, this.dateFormat)}`
+ case 'totalDuration':
+ return `${this.$strings.LabelDuration} ${this.$elapsedPrettyExtended(this.totalDuration, false)}`
+ case 'lastBookUpdated':
+ const lastUpdated = Math.max(...(this.books).map(x => x.updatedAt), 0)
+ return `${this.$strings.LabelLastBookUpdated} ${this.$formatDate(lastUpdated, this.dateFormat)}`
+ case 'lastBookAdded':
+ const lastBookAdded = Math.max(...(this.books).map(x => x.addedAt), 0)
+ return `${this.$strings.LabelLastBookAdded} ${this.$formatDate(lastBookAdded, this.dateFormat)}`
+ default:
+ return null
}
- return null
},
books() {
return this.series ? this.series.books || [] : []
diff --git a/client/strings/en-us.json b/client/strings/en-us.json
index 5cbef6d3..029c0422 100644
--- a/client/strings/en-us.json
+++ b/client/strings/en-us.json
@@ -161,6 +161,7 @@
"LabelAccountTypeGuest": "Guest",
"LabelAccountTypeUser": "User",
"LabelActivity": "Activity",
+ "LabelAdded": "Added",
"LabelAddedAt": "Added At",
"LabelAddToCollection": "Add to Collection",
"LabelAddToCollectionBatch": "Add {0} Books to Collection",
From 4d45a902bb7ef63d8adca7f25690ee8e24e27d98 Mon Sep 17 00:00:00 2001
From: advplyr
Date: Wed, 12 Apr 2023 16:25:02 -0500
Subject: [PATCH 20/41] Add translations to other languages
---
client/strings/de.json | 3 +++
client/strings/es.json | 3 +++
client/strings/fr.json | 3 +++
client/strings/gu.json | 5 ++++-
client/strings/hi.json | 3 +++
client/strings/hr.json | 3 +++
client/strings/it.json | 3 +++
client/strings/pl.json | 3 +++
client/strings/ru.json | 3 +++
client/strings/zh-cn.json | 3 +++
10 files changed, 31 insertions(+), 1 deletion(-)
diff --git a/client/strings/de.json b/client/strings/de.json
index 4cac3511..5f09a73d 100644
--- a/client/strings/de.json
+++ b/client/strings/de.json
@@ -161,6 +161,7 @@
"LabelAccountTypeGuest": "Gast",
"LabelAccountTypeUser": "Benutzer",
"LabelActivity": "Aktivitäten",
+ "LabelAdded": "Added",
"LabelAddedAt": "Hinzugefügt am",
"LabelAddToCollection": "Zur Sammlung hinzufügen",
"LabelAddToCollectionBatch": "Füge {0} Hörbüch(er)/Podcast(s) der Sammlung hinzu",
@@ -250,6 +251,8 @@
"LabelItem": "Medium",
"LabelLanguage": "Sprache",
"LabelLanguageDefaultServer": "Standard-Server-Sprache",
+ "LabelLastBookAdded": "Last Book Added",
+ "LabelLastBookUpdated": "Last Book Updated",
"LabelLastSeen": "Zuletzt angesehen",
"LabelLastTime": "Letztes Mal",
"LabelLastUpdate": "Letzte Aktualisierung",
diff --git a/client/strings/es.json b/client/strings/es.json
index 17c13698..b74d8f27 100644
--- a/client/strings/es.json
+++ b/client/strings/es.json
@@ -161,6 +161,7 @@
"LabelAccountTypeGuest": "Invitado",
"LabelAccountTypeUser": "Usuario",
"LabelActivity": "Actividad",
+ "LabelAdded": "Added",
"LabelAddedAt": "Añadido",
"LabelAddToCollection": "Añadido a la Colección",
"LabelAddToCollectionBatch": "Se Añadieron {0} Libros a la Colección",
@@ -250,6 +251,8 @@
"LabelItem": "Elemento",
"LabelLanguage": "Lenguaje",
"LabelLanguageDefaultServer": "Lenguaje Predeterminado del Servidor",
+ "LabelLastBookAdded": "Last Book Added",
+ "LabelLastBookUpdated": "Last Book Updated",
"LabelLastSeen": "Ultima Vez Visto",
"LabelLastTime": "Ultima Vez",
"LabelLastUpdate": "Ultima Actualización",
diff --git a/client/strings/fr.json b/client/strings/fr.json
index 98e527f6..397b5958 100644
--- a/client/strings/fr.json
+++ b/client/strings/fr.json
@@ -161,6 +161,7 @@
"LabelAccountTypeGuest": "Invité",
"LabelAccountTypeUser": "Utilisateur",
"LabelActivity": "Activité",
+ "LabelAdded": "Added",
"LabelAddedAt": "Date d’ajout",
"LabelAddToCollection": "Ajouter à la collection",
"LabelAddToCollectionBatch": "Ajout de {0} livres à la lollection",
@@ -250,6 +251,8 @@
"LabelItem": "Article",
"LabelLanguage": "Langue",
"LabelLanguageDefaultServer": "Langue par défaut",
+ "LabelLastBookAdded": "Last Book Added",
+ "LabelLastBookUpdated": "Last Book Updated",
"LabelLastSeen": "Vu dernièrement",
"LabelLastTime": "Progression",
"LabelLastUpdate": "Dernière mise à jour",
diff --git a/client/strings/gu.json b/client/strings/gu.json
index ebc3d2df..ca8be820 100644
--- a/client/strings/gu.json
+++ b/client/strings/gu.json
@@ -161,6 +161,7 @@
"LabelAccountTypeGuest": "Guest",
"LabelAccountTypeUser": "User",
"LabelActivity": "Activity",
+ "LabelAdded": "Added",
"LabelAddedAt": "Added At",
"LabelAddToCollection": "Add to Collection",
"LabelAddToCollectionBatch": "Add {0} Books to Collection",
@@ -250,6 +251,8 @@
"LabelItem": "Item",
"LabelLanguage": "Language",
"LabelLanguageDefaultServer": "Default Server Language",
+ "LabelLastBookAdded": "Last Book Added",
+ "LabelLastBookUpdated": "Last Book Updated",
"LabelLastSeen": "Last Seen",
"LabelLastTime": "Last Time",
"LabelLastUpdate": "Last Update",
@@ -639,4 +642,4 @@
"ToastSocketFailedToConnect": "Socket failed to connect",
"ToastUserDeleteFailed": "Failed to delete user",
"ToastUserDeleteSuccess": "User deleted"
-}
+}
\ No newline at end of file
diff --git a/client/strings/hi.json b/client/strings/hi.json
index d1dcd1d7..29092ed7 100644
--- a/client/strings/hi.json
+++ b/client/strings/hi.json
@@ -161,6 +161,7 @@
"LabelAccountTypeGuest": "Guest",
"LabelAccountTypeUser": "User",
"LabelActivity": "Activity",
+ "LabelAdded": "Added",
"LabelAddedAt": "Added At",
"LabelAddToCollection": "Add to Collection",
"LabelAddToCollectionBatch": "Add {0} Books to Collection",
@@ -250,6 +251,8 @@
"LabelItem": "Item",
"LabelLanguage": "Language",
"LabelLanguageDefaultServer": "Default Server Language",
+ "LabelLastBookAdded": "Last Book Added",
+ "LabelLastBookUpdated": "Last Book Updated",
"LabelLastSeen": "Last Seen",
"LabelLastTime": "Last Time",
"LabelLastUpdate": "Last Update",
diff --git a/client/strings/hr.json b/client/strings/hr.json
index 69dcef74..97c740ba 100644
--- a/client/strings/hr.json
+++ b/client/strings/hr.json
@@ -161,6 +161,7 @@
"LabelAccountTypeGuest": "Gost",
"LabelAccountTypeUser": "Korisnik",
"LabelActivity": "Aktivnost",
+ "LabelAdded": "Added",
"LabelAddedAt": "Added At",
"LabelAddToCollection": "Dodaj u kolekciju",
"LabelAddToCollectionBatch": "Add {0} Books to Collection",
@@ -250,6 +251,8 @@
"LabelItem": "Stavka",
"LabelLanguage": "Jezik",
"LabelLanguageDefaultServer": "Default jezik servera",
+ "LabelLastBookAdded": "Last Book Added",
+ "LabelLastBookUpdated": "Last Book Updated",
"LabelLastSeen": "Zadnje pogledano",
"LabelLastTime": "Prošli put",
"LabelLastUpdate": "Zadnja aktualizacija",
diff --git a/client/strings/it.json b/client/strings/it.json
index 1eecc3fc..b81ca9e5 100644
--- a/client/strings/it.json
+++ b/client/strings/it.json
@@ -161,6 +161,7 @@
"LabelAccountTypeGuest": "Ospite",
"LabelAccountTypeUser": "Utente",
"LabelActivity": "Attività",
+ "LabelAdded": "Added",
"LabelAddedAt": "Aggiunto il",
"LabelAddToCollection": "Aggiungi alla Raccolta",
"LabelAddToCollectionBatch": "Aggiungi {0} Libri alla Raccolta",
@@ -250,6 +251,8 @@
"LabelItem": "Oggetti",
"LabelLanguage": "Lingua",
"LabelLanguageDefaultServer": "Lingua di Default",
+ "LabelLastBookAdded": "Last Book Added",
+ "LabelLastBookUpdated": "Last Book Updated",
"LabelLastSeen": "Ultimi Visti",
"LabelLastTime": "Ultima Volta",
"LabelLastUpdate": "Ultimo Aggiornamento",
diff --git a/client/strings/pl.json b/client/strings/pl.json
index ed0ba345..12402df4 100644
--- a/client/strings/pl.json
+++ b/client/strings/pl.json
@@ -161,6 +161,7 @@
"LabelAccountTypeGuest": "Gość",
"LabelAccountTypeUser": "Użytkownik",
"LabelActivity": "Aktywność",
+ "LabelAdded": "Added",
"LabelAddedAt": "Dodano",
"LabelAddToCollection": "Dodaj do kolekcji",
"LabelAddToCollectionBatch": "Dodaj {0} książki do kolekcji",
@@ -250,6 +251,8 @@
"LabelItem": "Pozycja",
"LabelLanguage": "Język",
"LabelLanguageDefaultServer": "Domyślny język serwera",
+ "LabelLastBookAdded": "Last Book Added",
+ "LabelLastBookUpdated": "Last Book Updated",
"LabelLastSeen": "Ostatnio widziany",
"LabelLastTime": "Ostatni czas",
"LabelLastUpdate": "Ostatnia aktualizacja",
diff --git a/client/strings/ru.json b/client/strings/ru.json
index 1227d516..45c64498 100644
--- a/client/strings/ru.json
+++ b/client/strings/ru.json
@@ -161,6 +161,7 @@
"LabelAccountTypeGuest": "Гость",
"LabelAccountTypeUser": "Пользователь",
"LabelActivity": "Активность",
+ "LabelAdded": "Added",
"LabelAddedAt": "Дата добавления",
"LabelAddToCollection": "Добавить в коллекцию",
"LabelAddToCollectionBatch": "Добавить {0} книг в коллекцию",
@@ -250,6 +251,8 @@
"LabelItem": "Элемент",
"LabelLanguage": "Язык",
"LabelLanguageDefaultServer": "Язык сервера по умолчанию",
+ "LabelLastBookAdded": "Last Book Added",
+ "LabelLastBookUpdated": "Last Book Updated",
"LabelLastSeen": "Последнее сканирование",
"LabelLastTime": "Последний по времени",
"LabelLastUpdate": "Последний обновленный",
diff --git a/client/strings/zh-cn.json b/client/strings/zh-cn.json
index 3a9b0c9c..bf84b937 100644
--- a/client/strings/zh-cn.json
+++ b/client/strings/zh-cn.json
@@ -161,6 +161,7 @@
"LabelAccountTypeGuest": "来宾",
"LabelAccountTypeUser": "用户",
"LabelActivity": "活动",
+ "LabelAdded": "Added",
"LabelAddedAt": "添加于",
"LabelAddToCollection": "添加到收藏",
"LabelAddToCollectionBatch": "批量添加 {0} 个媒体到收藏",
@@ -250,6 +251,8 @@
"LabelItem": "项目",
"LabelLanguage": "语言",
"LabelLanguageDefaultServer": "默认服务器语言",
+ "LabelLastBookAdded": "Last Book Added",
+ "LabelLastBookUpdated": "Last Book Updated",
"LabelLastSeen": "上次查看时间",
"LabelLastTime": "最近一次",
"LabelLastUpdate": "最近更新",
From 589c4f73d23924bc18bf3265062b1a8a90cd41be Mon Sep 17 00:00:00 2001
From: advplyr
Date: Wed, 12 Apr 2023 16:45:52 -0500
Subject: [PATCH 21/41] Cleanup scanner
---
server/scanner/Scanner.js | 6 +++---
server/utils/scandir.js | 14 ++++++--------
2 files changed, 9 insertions(+), 11 deletions(-)
diff --git a/server/scanner/Scanner.js b/server/scanner/Scanner.js
index 682700b5..2280f0e3 100644
--- a/server/scanner/Scanner.js
+++ b/server/scanner/Scanner.js
@@ -68,7 +68,7 @@ class Scanner {
async scanLibraryItem(libraryMediaType, folder, libraryItem) {
// TODO: Support for single media item
- const libraryItemData = await getLibraryItemFileData(libraryMediaType, folder, libraryItem.path, false, this.db.serverSettings)
+ const libraryItemData = await getLibraryItemFileData(libraryMediaType, folder, libraryItem.path, false)
if (!libraryItemData) {
return ScanResult.NOTHING
}
@@ -173,7 +173,7 @@ class Scanner {
// Scan each library
for (let i = 0; i < libraryScan.folders.length; i++) {
const folder = libraryScan.folders[i]
- const itemDataFoundInFolder = await scanFolder(libraryScan.libraryMediaType, folder, this.db.serverSettings)
+ const itemDataFoundInFolder = await scanFolder(libraryScan.libraryMediaType, folder)
libraryScan.addLog(LogLevel.INFO, `${itemDataFoundInFolder.length} item data found in folder "${folder.fullPath}"`)
libraryItemDataFound = libraryItemDataFound.concat(itemDataFoundInFolder)
}
@@ -632,7 +632,7 @@ class Scanner {
}
async scanPotentialNewLibraryItem(libraryMediaType, folder, fullPath, isSingleMediaItem = false) {
- const libraryItemData = await getLibraryItemFileData(libraryMediaType, folder, fullPath, isSingleMediaItem, this.db.serverSettings)
+ const libraryItemData = await getLibraryItemFileData(libraryMediaType, folder, fullPath, isSingleMediaItem)
if (!libraryItemData) return null
return this.scanNewLibraryItem(libraryItemData, libraryMediaType)
}
diff --git a/server/utils/scandir.js b/server/utils/scandir.js
index 9df5254b..7f5b9855 100644
--- a/server/utils/scandir.js
+++ b/server/utils/scandir.js
@@ -175,7 +175,7 @@ function cleanFileObjects(libraryItemPath, files) {
}
// Scan folder
-async function scanFolder(libraryMediaType, folder, serverSettings = {}) {
+async function scanFolder(libraryMediaType, folder) {
const folderPath = filePathToPOSIX(folder.fullPath)
const pathExists = await fs.pathExists(folderPath)
@@ -216,7 +216,7 @@ async function scanFolder(libraryMediaType, folder, serverSettings = {}) {
fileObjs = await cleanFileObjects(folderPath, [libraryItemPath])
isFile = true
} else {
- libraryItemData = getDataFromMediaDir(libraryMediaType, folderPath, libraryItemPath, serverSettings, libraryItemGrouping[libraryItemPath])
+ libraryItemData = getDataFromMediaDir(libraryMediaType, folderPath, libraryItemPath)
fileObjs = await cleanFileObjects(libraryItemData.path, libraryItemGrouping[libraryItemPath])
}
@@ -347,19 +347,18 @@ function getPodcastDataFromDir(folderPath, relPath) {
}
}
-function getDataFromMediaDir(libraryMediaType, folderPath, relPath, serverSettings, fileNames) {
+function getDataFromMediaDir(libraryMediaType, folderPath, relPath) {
if (libraryMediaType === 'podcast') {
return getPodcastDataFromDir(folderPath, relPath)
} else if (libraryMediaType === 'book') {
- var parseSubtitle = !!serverSettings.scannerParseSubtitle
- return getBookDataFromDir(folderPath, relPath, parseSubtitle)
+ return getBookDataFromDir(folderPath, relPath, !!global.ServerSettings.scannerParseSubtitle)
} else {
return getPodcastDataFromDir(folderPath, relPath)
}
}
// Called from Scanner.js
-async function getLibraryItemFileData(libraryMediaType, folder, libraryItemPath, isSingleMediaItem, serverSettings = {}) {
+async function getLibraryItemFileData(libraryMediaType, folder, libraryItemPath, isSingleMediaItem) {
libraryItemPath = filePathToPOSIX(libraryItemPath)
const folderFullPath = filePathToPOSIX(folder.fullPath)
@@ -384,8 +383,7 @@ async function getLibraryItemFileData(libraryMediaType, folder, libraryItemPath,
}
} else {
fileItems = await recurseFiles(libraryItemPath)
- const fileNames = fileItems.map(i => i.name)
- libraryItemData = getDataFromMediaDir(libraryMediaType, folderFullPath, libraryItemDir, serverSettings, fileNames)
+ libraryItemData = getDataFromMediaDir(libraryMediaType, folderFullPath, libraryItemDir)
}
const libraryItemDirStats = await getFileTimestampsWithIno(libraryItemData.path)
From 24ef1057328a7ef3636b764c8b4ef7b6d8757493 Mon Sep 17 00:00:00 2001
From: advplyr
Date: Wed, 12 Apr 2023 17:20:11 -0500
Subject: [PATCH 22/41] Fix:Empty podcasts marked as missing & removing
episodes when deleted in folder #1671
---
server/objects/mediaTypes/Podcast.js | 2 +-
server/scanner/Scanner.js | 21 ++++++++++++++++-----
2 files changed, 17 insertions(+), 6 deletions(-)
diff --git a/server/objects/mediaTypes/Podcast.js b/server/objects/mediaTypes/Podcast.js
index 743512e8..efce8bb2 100644
--- a/server/objects/mediaTypes/Podcast.js
+++ b/server/objects/mediaTypes/Podcast.js
@@ -166,7 +166,7 @@ class Podcast {
}
removeFileWithInode(inode) {
- this.episodes = this.episodes.filter(ep => ep.ino !== inode)
+ this.episodes = this.episodes.filter(ep => ep.audioFile.ino !== inode)
}
findFileWithInode(inode) {
diff --git a/server/scanner/Scanner.js b/server/scanner/Scanner.js
index 2280f0e3..d6f20930 100644
--- a/server/scanner/Scanner.js
+++ b/server/scanner/Scanner.js
@@ -200,11 +200,22 @@ class Scanner {
// Find library item folder with matching inode or matching path
const dataFound = libraryItemDataFound.find(lid => lid.ino === libraryItem.ino || comparePaths(lid.relPath, libraryItem.relPath))
if (!dataFound) {
- libraryScan.addLog(LogLevel.WARN, `Library Item "${libraryItem.media.metadata.title}" is missing`)
- Logger.warn(`[Scanner] Library item "${libraryItem.media.metadata.title}" is missing (inode "${libraryItem.ino}")`)
- libraryScan.resultsMissing++
- libraryItem.setMissing()
- itemsToUpdate.push(libraryItem)
+ // Podcast folder can have no episodes and still be valid
+ if (libraryScan.libraryMediaType === 'podcast' && await fs.pathExists(libraryItem.path)) {
+ Logger.info(`[Scanner] Library item "${libraryItem.media.metadata.title}" folder exists but has no episodes`)
+ if (libraryItem.isMissing) {
+ libraryScan.resultsUpdated++
+ libraryItem.isMissing = false
+ libraryItem.setLastScan()
+ itemsToUpdate.push(libraryItem)
+ }
+ } else {
+ libraryScan.addLog(LogLevel.WARN, `Library Item "${libraryItem.media.metadata.title}" is missing`)
+ Logger.warn(`[Scanner] Library item "${libraryItem.media.metadata.title}" is missing (inode "${libraryItem.ino}")`)
+ libraryScan.resultsMissing++
+ libraryItem.setMissing()
+ itemsToUpdate.push(libraryItem)
+ }
} else {
const checkRes = libraryItem.checkScanData(dataFound)
if (checkRes.newLibraryFiles.length || libraryScan.scanOptions.forceRescan) { // Item has new files
From 5a21e63d0b952002a542b7633f199651a4e39f70 Mon Sep 17 00:00:00 2001
From: advplyr
Date: Thu, 13 Apr 2023 18:03:39 -0500
Subject: [PATCH 23/41] Add:Delete library files, condense item options in more
menu #1439
---
.../components/tables/LibraryFilesTable.vue | 22 +--
.../tables/LibraryFilesTableRow.vue | 105 +++++++++++++
client/components/ui/ContextMenuDropdown.vue | 14 +-
client/pages/item/_id/index.vue | 144 +++++++++++++-----
server/controllers/LibraryItemController.js | 22 +++
server/objects/mediaTypes/Podcast.js | 6 +-
server/routers/ApiRouter.js | 1 +
7 files changed, 254 insertions(+), 60 deletions(-)
create mode 100644 client/components/tables/LibraryFilesTableRow.vue
diff --git a/client/components/tables/LibraryFilesTable.vue b/client/components/tables/LibraryFilesTable.vue
index d941e608..b73c96b0 100644
--- a/client/components/tables/LibraryFilesTable.vue
+++ b/client/components/tables/LibraryFilesTable.vue
@@ -18,25 +18,10 @@
+
+
+
\ No newline at end of file
diff --git a/client/components/ui/ContextMenuDropdown.vue b/client/components/ui/ContextMenuDropdown.vue
index 018bf34b..6f486d5d 100644
--- a/client/components/ui/ContextMenuDropdown.vue
+++ b/client/components/ui/ContextMenuDropdown.vue
@@ -1,11 +1,13 @@
+
+
+
\ No newline at end of file
diff --git a/client/components/tables/LibraryFilesTable.vue b/client/components/tables/LibraryFilesTable.vue
index b73c96b0..12ad86e5 100644
--- a/client/components/tables/LibraryFilesTable.vue
+++ b/client/components/tables/LibraryFilesTable.vue
@@ -18,35 +18,42 @@