mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-07-10 19:31:39 +00:00
Merge branch 'master' into mf/rssInboundManager
This commit is contained in:
commit
e9a1317306
12 changed files with 198 additions and 114 deletions
|
|
@ -332,6 +332,7 @@ export default {
|
||||||
if (this.isPodcast) return `term=${encodeURIComponent(this.searchTitle)}`
|
if (this.isPodcast) return `term=${encodeURIComponent(this.searchTitle)}`
|
||||||
var searchQuery = `provider=${this.provider}&fallbackTitleOnly=1&title=${encodeURIComponent(this.searchTitle)}`
|
var searchQuery = `provider=${this.provider}&fallbackTitleOnly=1&title=${encodeURIComponent(this.searchTitle)}`
|
||||||
if (this.searchAuthor) searchQuery += `&author=${encodeURIComponent(this.searchAuthor)}`
|
if (this.searchAuthor) searchQuery += `&author=${encodeURIComponent(this.searchAuthor)}`
|
||||||
|
if (this.libraryItemId) searchQuery += `&id=${this.libraryItemId}`
|
||||||
return searchQuery
|
return searchQuery
|
||||||
},
|
},
|
||||||
submitSearch() {
|
submitSearch() {
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
{
|
{
|
||||||
"ButtonAdd": "Hinzufügen",
|
"ButtonAdd": "Hinzufügen",
|
||||||
"ButtonAddChapters": "Kapitel hinzufügen",
|
"ButtonAddChapters": "Kapitel hinzufügen",
|
||||||
"ButtonAddDevice": "Add Device",
|
"ButtonAddDevice": "Gerät hinzufügen",
|
||||||
"ButtonAddLibrary": "Add Library",
|
"ButtonAddLibrary": "Bibliothek hinzufügen",
|
||||||
"ButtonAddPodcasts": "Podcasts hinzufügen",
|
"ButtonAddPodcasts": "Podcasts hinzufügen",
|
||||||
"ButtonAddUser": "Add User",
|
"ButtonAddUser": "Benutzer hinzufügen",
|
||||||
"ButtonAddYourFirstLibrary": "Erstelle deine erste Bibliothek",
|
"ButtonAddYourFirstLibrary": "Erstelle deine erste Bibliothek",
|
||||||
"ButtonApply": "Übernehmen",
|
"ButtonApply": "Übernehmen",
|
||||||
"ButtonApplyChapters": "Kapitel anwenden",
|
"ButtonApplyChapters": "Kapitel anwenden",
|
||||||
|
|
@ -58,11 +58,11 @@
|
||||||
"ButtonRemoveAll": "Alles löschen",
|
"ButtonRemoveAll": "Alles löschen",
|
||||||
"ButtonRemoveAllLibraryItems": "Lösche alle Bibliothekseinträge",
|
"ButtonRemoveAllLibraryItems": "Lösche alle Bibliothekseinträge",
|
||||||
"ButtonRemoveFromContinueListening": "Lösche den Eintrag aus der Fortsetzungsliste",
|
"ButtonRemoveFromContinueListening": "Lösche den Eintrag aus der Fortsetzungsliste",
|
||||||
"ButtonRemoveFromContinueReading": "Remove from Continue Reading",
|
"ButtonRemoveFromContinueReading": "Lösche die Serie aus der Lesefortsetzungsliste",
|
||||||
"ButtonRemoveSeriesFromContinueSeries": "Lösche die Serie aus der Serienfortsetzungsliste",
|
"ButtonRemoveSeriesFromContinueSeries": "Lösche die Serie aus der Serienfortsetzungsliste",
|
||||||
"ButtonReScan": "Neu scannen",
|
"ButtonReScan": "Neu scannen",
|
||||||
"ButtonReset": "Zurücksetzen",
|
"ButtonReset": "Zurücksetzen",
|
||||||
"ButtonResetToDefault": "Reset to default",
|
"ButtonResetToDefault": "Zurücksetzen auf Standard",
|
||||||
"ButtonRestore": "Wiederherstellen",
|
"ButtonRestore": "Wiederherstellen",
|
||||||
"ButtonSave": "Speichern",
|
"ButtonSave": "Speichern",
|
||||||
"ButtonSaveAndClose": "Speichern & Schließen",
|
"ButtonSaveAndClose": "Speichern & Schließen",
|
||||||
|
|
@ -221,7 +221,7 @@
|
||||||
"LabelChapters": "Kapitel",
|
"LabelChapters": "Kapitel",
|
||||||
"LabelChaptersFound": "gefundene Kapitel",
|
"LabelChaptersFound": "gefundene Kapitel",
|
||||||
"LabelChapterTitle": "Kapitelüberschrift",
|
"LabelChapterTitle": "Kapitelüberschrift",
|
||||||
"LabelClickForMoreInfo": "Click for more info",
|
"LabelClickForMoreInfo": "Klicken für mehr Informationen",
|
||||||
"LabelClosePlayer": "Player schließen",
|
"LabelClosePlayer": "Player schließen",
|
||||||
"LabelCodec": "Codec",
|
"LabelCodec": "Codec",
|
||||||
"LabelCollapseSeries": "Serien zusammenfassen",
|
"LabelCollapseSeries": "Serien zusammenfassen",
|
||||||
|
|
@ -251,7 +251,7 @@
|
||||||
"LabelDiscFromMetadata": "CD aus den Metadaten",
|
"LabelDiscFromMetadata": "CD aus den Metadaten",
|
||||||
"LabelDiscover": "Entdecken",
|
"LabelDiscover": "Entdecken",
|
||||||
"LabelDownload": "Herunterladen",
|
"LabelDownload": "Herunterladen",
|
||||||
"LabelDownloadNEpisodes": "Download {0} episodes",
|
"LabelDownloadNEpisodes": "Download {0} Episoden",
|
||||||
"LabelDuration": "Laufzeit",
|
"LabelDuration": "Laufzeit",
|
||||||
"LabelDurationFound": "Gefundene Laufzeit:",
|
"LabelDurationFound": "Gefundene Laufzeit:",
|
||||||
"LabelEbook": "E-Book",
|
"LabelEbook": "E-Book",
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
{
|
{
|
||||||
"ButtonAdd": "Добавить",
|
"ButtonAdd": "Добавить",
|
||||||
"ButtonAddChapters": "Добавить главы",
|
"ButtonAddChapters": "Добавить главы",
|
||||||
"ButtonAddDevice": "Add Device",
|
"ButtonAddDevice": "Добавить устройство",
|
||||||
"ButtonAddLibrary": "Add Library",
|
"ButtonAddLibrary": "Добавить библиотеку",
|
||||||
"ButtonAddPodcasts": "Добавить подкасты",
|
"ButtonAddPodcasts": "Добавить подкасты",
|
||||||
"ButtonAddUser": "Add User",
|
"ButtonAddUser": "Добавить пользователя",
|
||||||
"ButtonAddYourFirstLibrary": "Добавьте Вашу первую библиотеку",
|
"ButtonAddYourFirstLibrary": "Добавьте Вашу первую библиотеку",
|
||||||
"ButtonApply": "Применить",
|
"ButtonApply": "Применить",
|
||||||
"ButtonApplyChapters": "Применить главы",
|
"ButtonApplyChapters": "Применить главы",
|
||||||
|
|
@ -62,7 +62,7 @@
|
||||||
"ButtonRemoveSeriesFromContinueSeries": "Удалить серию из Продолжить серию",
|
"ButtonRemoveSeriesFromContinueSeries": "Удалить серию из Продолжить серию",
|
||||||
"ButtonReScan": "Пересканировать",
|
"ButtonReScan": "Пересканировать",
|
||||||
"ButtonReset": "Сбросить",
|
"ButtonReset": "Сбросить",
|
||||||
"ButtonResetToDefault": "Reset to default",
|
"ButtonResetToDefault": "Сборосить по умолчанию",
|
||||||
"ButtonRestore": "Восстановить",
|
"ButtonRestore": "Восстановить",
|
||||||
"ButtonSave": "Сохранить",
|
"ButtonSave": "Сохранить",
|
||||||
"ButtonSaveAndClose": "Сохранить и закрыть",
|
"ButtonSaveAndClose": "Сохранить и закрыть",
|
||||||
|
|
@ -78,7 +78,7 @@
|
||||||
"ButtonStartM4BEncode": "Начать кодирование M4B",
|
"ButtonStartM4BEncode": "Начать кодирование M4B",
|
||||||
"ButtonStartMetadataEmbed": "Начать встраивание метаданных",
|
"ButtonStartMetadataEmbed": "Начать встраивание метаданных",
|
||||||
"ButtonSubmit": "Применить",
|
"ButtonSubmit": "Применить",
|
||||||
"ButtonTest": "Test",
|
"ButtonTest": "Тест",
|
||||||
"ButtonUpload": "Загрузить",
|
"ButtonUpload": "Загрузить",
|
||||||
"ButtonUploadBackup": "Загрузить бэкап",
|
"ButtonUploadBackup": "Загрузить бэкап",
|
||||||
"ButtonUploadCover": "Загрузить обложку",
|
"ButtonUploadCover": "Загрузить обложку",
|
||||||
|
|
@ -87,15 +87,15 @@
|
||||||
"ButtonUserEdit": "Редактировать пользователя {0}",
|
"ButtonUserEdit": "Редактировать пользователя {0}",
|
||||||
"ButtonViewAll": "Посмотреть все",
|
"ButtonViewAll": "Посмотреть все",
|
||||||
"ButtonYes": "Да",
|
"ButtonYes": "Да",
|
||||||
"ErrorUploadFetchMetadataAPI": "Error fetching metadata",
|
"ErrorUploadFetchMetadataAPI": "Ошибка при получении метаданных",
|
||||||
"ErrorUploadFetchMetadataNoResults": "Could not fetch metadata - try updating title and/or author",
|
"ErrorUploadFetchMetadataNoResults": "Не удалось получить метаданные - попробуйте обновить название и/или автора",
|
||||||
"ErrorUploadLacksTitle": "Must have a title",
|
"ErrorUploadLacksTitle": "Название должно быть заполнено",
|
||||||
"HeaderAccount": "Учетная запись",
|
"HeaderAccount": "Учетная запись",
|
||||||
"HeaderAdvanced": "Дополнительно",
|
"HeaderAdvanced": "Дополнительно",
|
||||||
"HeaderAppriseNotificationSettings": "Настройки оповещений",
|
"HeaderAppriseNotificationSettings": "Настройки оповещений",
|
||||||
"HeaderAudiobookTools": "Инструменты файлов аудиокниг",
|
"HeaderAudiobookTools": "Инструменты файлов аудиокниг",
|
||||||
"HeaderAudioTracks": "Аудио треки",
|
"HeaderAudioTracks": "Аудио треки",
|
||||||
"HeaderAuthentication": "Authentication",
|
"HeaderAuthentication": "Аутентификация",
|
||||||
"HeaderBackups": "Бэкапы",
|
"HeaderBackups": "Бэкапы",
|
||||||
"HeaderChangePassword": "Изменить пароль",
|
"HeaderChangePassword": "Изменить пароль",
|
||||||
"HeaderChapters": "Главы",
|
"HeaderChapters": "Главы",
|
||||||
|
|
@ -130,15 +130,15 @@
|
||||||
"HeaderManageTags": "Редактировать теги",
|
"HeaderManageTags": "Редактировать теги",
|
||||||
"HeaderMapDetails": "Найти подробности",
|
"HeaderMapDetails": "Найти подробности",
|
||||||
"HeaderMatch": "Поиск",
|
"HeaderMatch": "Поиск",
|
||||||
"HeaderMetadataOrderOfPrecedence": "Metadata order of precedence",
|
"HeaderMetadataOrderOfPrecedence": "Порядок приоритета метаданных",
|
||||||
"HeaderMetadataToEmbed": "Метаинформация для встраивания",
|
"HeaderMetadataToEmbed": "Метаинформация для встраивания",
|
||||||
"HeaderNewAccount": "Новая учетная запись",
|
"HeaderNewAccount": "Новая учетная запись",
|
||||||
"HeaderNewLibrary": "Новая библиотека",
|
"HeaderNewLibrary": "Новая библиотека",
|
||||||
"HeaderNotifications": "Уведомления",
|
"HeaderNotifications": "Уведомления",
|
||||||
"HeaderOpenIDConnectAuthentication": "OpenID Connect Authentication",
|
"HeaderOpenIDConnectAuthentication": "Аутентификация OpenID Connect",
|
||||||
"HeaderOpenRSSFeed": "Открыть RSS-канал",
|
"HeaderOpenRSSFeed": "Открыть RSS-канал",
|
||||||
"HeaderOtherFiles": "Другие файлы",
|
"HeaderOtherFiles": "Другие файлы",
|
||||||
"HeaderPasswordAuthentication": "Password Authentication",
|
"HeaderPasswordAuthentication": "Аутентификация по паролю",
|
||||||
"HeaderPermissions": "Разрешения",
|
"HeaderPermissions": "Разрешения",
|
||||||
"HeaderPlayerQueue": "Очередь воспроизведения",
|
"HeaderPlayerQueue": "Очередь воспроизведения",
|
||||||
"HeaderPlaylist": "Плейлист",
|
"HeaderPlaylist": "Плейлист",
|
||||||
|
|
@ -187,11 +187,11 @@
|
||||||
"LabelAddToCollectionBatch": "Добавить {0} книг в коллекцию",
|
"LabelAddToCollectionBatch": "Добавить {0} книг в коллекцию",
|
||||||
"LabelAddToPlaylist": "Добавить в плейлист",
|
"LabelAddToPlaylist": "Добавить в плейлист",
|
||||||
"LabelAddToPlaylistBatch": "Добавить {0} элементов в плейлист",
|
"LabelAddToPlaylistBatch": "Добавить {0} элементов в плейлист",
|
||||||
"LabelAdminUsersOnly": "Admin users only",
|
"LabelAdminUsersOnly": "Только для пользователей с правами администратора",
|
||||||
"LabelAll": "Все",
|
"LabelAll": "Все",
|
||||||
"LabelAllUsers": "Все пользователи",
|
"LabelAllUsers": "Все пользователи",
|
||||||
"LabelAllUsersExcludingGuests": "All users excluding guests",
|
"LabelAllUsersExcludingGuests": "Все пользователи, кроме гостей",
|
||||||
"LabelAllUsersIncludingGuests": "All users including guests",
|
"LabelAllUsersIncludingGuests": "Все пользователи, включая гостей",
|
||||||
"LabelAlreadyInYourLibrary": "Уже в Вашей библиотеке",
|
"LabelAlreadyInYourLibrary": "Уже в Вашей библиотеке",
|
||||||
"LabelAppend": "Добавить",
|
"LabelAppend": "Добавить",
|
||||||
"LabelAuthor": "Автор",
|
"LabelAuthor": "Автор",
|
||||||
|
|
@ -199,14 +199,14 @@
|
||||||
"LabelAuthorLastFirst": "Автор (Фамилия, Имя)",
|
"LabelAuthorLastFirst": "Автор (Фамилия, Имя)",
|
||||||
"LabelAuthors": "Авторы",
|
"LabelAuthors": "Авторы",
|
||||||
"LabelAutoDownloadEpisodes": "Скачивать эпизоды автоматически",
|
"LabelAutoDownloadEpisodes": "Скачивать эпизоды автоматически",
|
||||||
"LabelAutoFetchMetadata": "Auto Fetch Metadata",
|
"LabelAutoFetchMetadata": "Автоматическое извлечение метаданных",
|
||||||
"LabelAutoFetchMetadataHelp": "Fetches metadata for title, author, and series to streamline uploading. Additional metadata may have to be matched after upload.",
|
"LabelAutoFetchMetadataHelp": "Извлекает метаданные для названия, автора и серии для упрощения загрузки. После загрузки может потребоваться сопоставление дополнительных метаданных.",
|
||||||
"LabelAutoLaunch": "Auto Launch",
|
"LabelAutoLaunch": "Автозапуск",
|
||||||
"LabelAutoLaunchDescription": "Redirect to the auth provider automatically when navigating to the login page (manual override path <code>/login?autoLaunch=0</code>)",
|
"LabelAutoLaunchDescription": "Редирект на провайдера аутентификации автоматически при переходе на страницу входа (путь ручного переопределения <code>/login?autoLaunch=0</code>)",
|
||||||
"LabelAutoRegister": "Auto Register",
|
"LabelAutoRegister": "Автоматическая регистрация",
|
||||||
"LabelAutoRegisterDescription": "Automatically create new users after logging in",
|
"LabelAutoRegisterDescription": "Автоматическое создание новых пользователей после входа в систему",
|
||||||
"LabelBackToUser": "Назад к пользователю",
|
"LabelBackToUser": "Назад к пользователю",
|
||||||
"LabelBackupLocation": "Backup Location",
|
"LabelBackupLocation": "Путь для бэкапов",
|
||||||
"LabelBackupsEnableAutomaticBackups": "Включить автоматическое бэкапирование",
|
"LabelBackupsEnableAutomaticBackups": "Включить автоматическое бэкапирование",
|
||||||
"LabelBackupsEnableAutomaticBackupsHelp": "Бэкапы сохраняются в /metadata/backups",
|
"LabelBackupsEnableAutomaticBackupsHelp": "Бэкапы сохраняются в /metadata/backups",
|
||||||
"LabelBackupsMaxBackupSize": "Максимальный размер бэкапа (в GB)",
|
"LabelBackupsMaxBackupSize": "Максимальный размер бэкапа (в GB)",
|
||||||
|
|
@ -215,13 +215,13 @@
|
||||||
"LabelBackupsNumberToKeepHelp": "За один раз только 1 бэкап будет удален, так что если у вас будет больше бэкапов, то их нужно удалить вручную.",
|
"LabelBackupsNumberToKeepHelp": "За один раз только 1 бэкап будет удален, так что если у вас будет больше бэкапов, то их нужно удалить вручную.",
|
||||||
"LabelBitrate": "Битрейт",
|
"LabelBitrate": "Битрейт",
|
||||||
"LabelBooks": "Книги",
|
"LabelBooks": "Книги",
|
||||||
"LabelButtonText": "Button Text",
|
"LabelButtonText": "Текст кнопки",
|
||||||
"LabelChangePassword": "Изменить пароль",
|
"LabelChangePassword": "Изменить пароль",
|
||||||
"LabelChannels": "Каналы",
|
"LabelChannels": "Каналы",
|
||||||
"LabelChapters": "Главы",
|
"LabelChapters": "Главы",
|
||||||
"LabelChaptersFound": "глав найдено",
|
"LabelChaptersFound": "глав найдено",
|
||||||
"LabelChapterTitle": "Название главы",
|
"LabelChapterTitle": "Название главы",
|
||||||
"LabelClickForMoreInfo": "Click for more info",
|
"LabelClickForMoreInfo": "Нажмите, чтобы узнать больше",
|
||||||
"LabelClosePlayer": "Закрыть проигрыватель",
|
"LabelClosePlayer": "Закрыть проигрыватель",
|
||||||
"LabelCodec": "Кодек",
|
"LabelCodec": "Кодек",
|
||||||
"LabelCollapseSeries": "Свернуть серии",
|
"LabelCollapseSeries": "Свернуть серии",
|
||||||
|
|
@ -240,12 +240,12 @@
|
||||||
"LabelCurrently": "Текущее:",
|
"LabelCurrently": "Текущее:",
|
||||||
"LabelCustomCronExpression": "Пользовательское выражение Cron:",
|
"LabelCustomCronExpression": "Пользовательское выражение Cron:",
|
||||||
"LabelDatetime": "Дата и время",
|
"LabelDatetime": "Дата и время",
|
||||||
"LabelDeleteFromFileSystemCheckbox": "Delete from file system (uncheck to only remove from database)",
|
"LabelDeleteFromFileSystemCheckbox": "Удалить из файловой системы (снимите флажок, чтобы удалить только из базы данных)",
|
||||||
"LabelDescription": "Описание",
|
"LabelDescription": "Описание",
|
||||||
"LabelDeselectAll": "Снять выделение",
|
"LabelDeselectAll": "Снять выделение",
|
||||||
"LabelDevice": "Устройство",
|
"LabelDevice": "Устройство",
|
||||||
"LabelDeviceInfo": "Информация об устройстве",
|
"LabelDeviceInfo": "Информация об устройстве",
|
||||||
"LabelDeviceIsAvailableTo": "Device is available to...",
|
"LabelDeviceIsAvailableTo": "Устройство доступно для...",
|
||||||
"LabelDirectory": "Каталог",
|
"LabelDirectory": "Каталог",
|
||||||
"LabelDiscFromFilename": "Диск из Имени файла",
|
"LabelDiscFromFilename": "Диск из Имени файла",
|
||||||
"LabelDiscFromMetadata": "Диск из Метаданных",
|
"LabelDiscFromMetadata": "Диск из Метаданных",
|
||||||
|
|
@ -271,7 +271,7 @@
|
||||||
"LabelExample": "Пример",
|
"LabelExample": "Пример",
|
||||||
"LabelExplicit": "Явный",
|
"LabelExplicit": "Явный",
|
||||||
"LabelFeedURL": "URL канала",
|
"LabelFeedURL": "URL канала",
|
||||||
"LabelFetchingMetadata": "Fetching Metadata",
|
"LabelFetchingMetadata": "Извлечение метаданных",
|
||||||
"LabelFile": "Файл",
|
"LabelFile": "Файл",
|
||||||
"LabelFileBirthtime": "Дата создания",
|
"LabelFileBirthtime": "Дата создания",
|
||||||
"LabelFileModified": "Дата модификации",
|
"LabelFileModified": "Дата модификации",
|
||||||
|
|
@ -289,11 +289,11 @@
|
||||||
"LabelHardDeleteFile": "Жесткое удаление файла",
|
"LabelHardDeleteFile": "Жесткое удаление файла",
|
||||||
"LabelHasEbook": "Есть e-книга",
|
"LabelHasEbook": "Есть e-книга",
|
||||||
"LabelHasSupplementaryEbook": "Есть дополнительная e-книга",
|
"LabelHasSupplementaryEbook": "Есть дополнительная e-книга",
|
||||||
"LabelHighestPriority": "Highest priority",
|
"LabelHighestPriority": "Наивысший приоритет",
|
||||||
"LabelHost": "Хост",
|
"LabelHost": "Хост",
|
||||||
"LabelHour": "Часы",
|
"LabelHour": "Часы",
|
||||||
"LabelIcon": "Иконка",
|
"LabelIcon": "Иконка",
|
||||||
"LabelImageURLFromTheWeb": "Image URL from the web",
|
"LabelImageURLFromTheWeb": "URL-адрес изображения из Интернета",
|
||||||
"LabelIncludeInTracklist": "Включать в список воспроизведения",
|
"LabelIncludeInTracklist": "Включать в список воспроизведения",
|
||||||
"LabelIncomplete": "Не завершен",
|
"LabelIncomplete": "Не завершен",
|
||||||
"LabelInProgress": "В процессе",
|
"LabelInProgress": "В процессе",
|
||||||
|
|
@ -331,20 +331,20 @@
|
||||||
"LabelLogLevelInfo": "Info",
|
"LabelLogLevelInfo": "Info",
|
||||||
"LabelLogLevelWarn": "Warn",
|
"LabelLogLevelWarn": "Warn",
|
||||||
"LabelLookForNewEpisodesAfterDate": "Искать новые эпизоды после этой даты",
|
"LabelLookForNewEpisodesAfterDate": "Искать новые эпизоды после этой даты",
|
||||||
"LabelLowestPriority": "Lowest Priority",
|
"LabelLowestPriority": "Самый низкий приоритет",
|
||||||
"LabelMatchExistingUsersBy": "Match existing users by",
|
"LabelMatchExistingUsersBy": "Сопоставление существующих пользователей по",
|
||||||
"LabelMatchExistingUsersByDescription": "Used for connecting existing users. Once connected, users will be matched by a unique id from your SSO provider",
|
"LabelMatchExistingUsersByDescription": "Используется для подключения существующих пользователей. После подключения пользователям будет присвоен уникальный идентификатор от поставщика единого входа",
|
||||||
"LabelMediaPlayer": "Медиа проигрыватель",
|
"LabelMediaPlayer": "Медиа проигрыватель",
|
||||||
"LabelMediaType": "Тип медиа",
|
"LabelMediaType": "Тип медиа",
|
||||||
"LabelMetadataOrderOfPrecedenceDescription": "Higher priority metadata sources will override lower priority metadata sources",
|
"LabelMetadataOrderOfPrecedenceDescription": "Источники метаданных с более высоким приоритетом будут переопределять источники метаданных с более низким приоритетом",
|
||||||
"LabelMetadataProvider": "Провайдер",
|
"LabelMetadataProvider": "Провайдер",
|
||||||
"LabelMetaTag": "Мета тег",
|
"LabelMetaTag": "Мета тег",
|
||||||
"LabelMetaTags": "Мета теги",
|
"LabelMetaTags": "Мета теги",
|
||||||
"LabelMinute": "Минуты",
|
"LabelMinute": "Минуты",
|
||||||
"LabelMissing": "Потеряно",
|
"LabelMissing": "Потеряно",
|
||||||
"LabelMissingParts": "Потерянные части",
|
"LabelMissingParts": "Потерянные части",
|
||||||
"LabelMobileRedirectURIs": "Allowed Mobile Redirect URIs",
|
"LabelMobileRedirectURIs": "Разрешенные URI перенаправления с мобильных устройств",
|
||||||
"LabelMobileRedirectURIsDescription": "This is a whitelist of valid redirect URIs for mobile apps. The default one is <code>audiobookshelf://oauth</code>, which you can remove or supplement with additional URIs for third-party app integration. Using an asterisk (<code>*</code>) as the sole entry permits any URI.",
|
"LabelMobileRedirectURIsDescription": "Это белый список допустимых URI перенаправления для мобильных приложений. По умолчанию используется <code>audiobookshelf://oauth</code>, который можно удалить или дополнить дополнительными URI для интеграции со сторонними приложениями. Использование звездочки (<code>*</code>) в качестве единственной записи разрешает любой URI.",
|
||||||
"LabelMore": "Еще",
|
"LabelMore": "Еще",
|
||||||
"LabelMoreInfo": "Больше информации",
|
"LabelMoreInfo": "Больше информации",
|
||||||
"LabelName": "Имя",
|
"LabelName": "Имя",
|
||||||
|
|
@ -523,7 +523,7 @@
|
||||||
"LabelUpdateDetailsHelp": "Позволяет перезаписывать текущие подробности для выбранных книг если будут найдены",
|
"LabelUpdateDetailsHelp": "Позволяет перезаписывать текущие подробности для выбранных книг если будут найдены",
|
||||||
"LabelUploaderDragAndDrop": "Перетащите файлы или каталоги",
|
"LabelUploaderDragAndDrop": "Перетащите файлы или каталоги",
|
||||||
"LabelUploaderDropFiles": "Перетащите файлы",
|
"LabelUploaderDropFiles": "Перетащите файлы",
|
||||||
"LabelUploaderItemFetchMetadataHelp": "Automatically fetch title, author, and series",
|
"LabelUploaderItemFetchMetadataHelp": "Автоматическое извлечение названия, автора и серии",
|
||||||
"LabelUseChapterTrack": "Показывать время главы",
|
"LabelUseChapterTrack": "Показывать время главы",
|
||||||
"LabelUseFullTrack": "Показывать время книги",
|
"LabelUseFullTrack": "Показывать время книги",
|
||||||
"LabelUser": "Пользователь",
|
"LabelUser": "Пользователь",
|
||||||
|
|
@ -557,17 +557,17 @@
|
||||||
"MessageConfirmDeleteBackup": "Вы уверены, что хотите удалить бэкап для {0}?",
|
"MessageConfirmDeleteBackup": "Вы уверены, что хотите удалить бэкап для {0}?",
|
||||||
"MessageConfirmDeleteFile": "Это удалит файл из Вашей файловой системы. Вы уверены?",
|
"MessageConfirmDeleteFile": "Это удалит файл из Вашей файловой системы. Вы уверены?",
|
||||||
"MessageConfirmDeleteLibrary": "Вы уверены, что хотите навсегда удалить библиотеку \"{0}\"?",
|
"MessageConfirmDeleteLibrary": "Вы уверены, что хотите навсегда удалить библиотеку \"{0}\"?",
|
||||||
"MessageConfirmDeleteLibraryItem": "This will delete the library item from the database and your file system. Are you sure?",
|
"MessageConfirmDeleteLibraryItem": "Это приведет к удалению элемента библиотеки из базы данных и файловой системы. Вы уверены?",
|
||||||
"MessageConfirmDeleteLibraryItems": "This will delete {0} library items from the database and your file system. Are you sure?",
|
"MessageConfirmDeleteLibraryItems": "Это приведет к удалению {0} элементов библиотеки из базы данных и файловой системы. Вы уверены?",
|
||||||
"MessageConfirmDeleteSession": "Вы уверены, что хотите удалить этот сеанс?",
|
"MessageConfirmDeleteSession": "Вы уверены, что хотите удалить этот сеанс?",
|
||||||
"MessageConfirmForceReScan": "Вы уверены, что хотите принудительно выполнить повторное сканирование?",
|
"MessageConfirmForceReScan": "Вы уверены, что хотите принудительно выполнить повторное сканирование?",
|
||||||
"MessageConfirmMarkAllEpisodesFinished": "Вы уверены, что хотите отметить все эпизоды как завершенные?",
|
"MessageConfirmMarkAllEpisodesFinished": "Вы уверены, что хотите отметить все эпизоды как завершенные?",
|
||||||
"MessageConfirmMarkAllEpisodesNotFinished": "Вы уверены, что хотите отметить все эпизоды как не завершенные?",
|
"MessageConfirmMarkAllEpisodesNotFinished": "Вы уверены, что хотите отметить все эпизоды как не завершенные?",
|
||||||
"MessageConfirmMarkSeriesFinished": "Вы уверены, что хотите отметить все книги этой серии как завершенные?",
|
"MessageConfirmMarkSeriesFinished": "Вы уверены, что хотите отметить все книги этой серии как завершенные?",
|
||||||
"MessageConfirmMarkSeriesNotFinished": "Вы уверены, что хотите отметить все книги этой серии как не завершенные?",
|
"MessageConfirmMarkSeriesNotFinished": "Вы уверены, что хотите отметить все книги этой серии как не завершенные?",
|
||||||
"MessageConfirmQuickEmbed": "Warning! Quick embed will not backup your audio files. Make sure that you have a backup of your audio files. <br><br>Would you like to continue?",
|
"MessageConfirmQuickEmbed": "Предупреждение! Быстрое встраивание не позволяет создавать резервные копии аудиофайлов. Убедитесь, что у вас есть резервная копия аудиофайлов. <br><br>Хотите продолжить?",
|
||||||
"MessageConfirmRemoveAllChapters": "Вы уверены, что хотите удалить все главы?",
|
"MessageConfirmRemoveAllChapters": "Вы уверены, что хотите удалить все главы?",
|
||||||
"MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?",
|
"MessageConfirmRemoveAuthor": "Вы уверены, что хотите удалить автора \"{0}\"?",
|
||||||
"MessageConfirmRemoveCollection": "Вы уверены, что хотите удалить коллекцию \"{0}\"?",
|
"MessageConfirmRemoveCollection": "Вы уверены, что хотите удалить коллекцию \"{0}\"?",
|
||||||
"MessageConfirmRemoveEpisode": "Вы уверены, что хотите удалить эпизод \"{0}\"?",
|
"MessageConfirmRemoveEpisode": "Вы уверены, что хотите удалить эпизод \"{0}\"?",
|
||||||
"MessageConfirmRemoveEpisodes": "Вы уверены, что хотите удалить {0} эпизодов?",
|
"MessageConfirmRemoveEpisodes": "Вы уверены, что хотите удалить {0} эпизодов?",
|
||||||
|
|
@ -579,7 +579,7 @@
|
||||||
"MessageConfirmRenameTag": "Вы уверены, что хотите переименовать тег \"{0}\" в \"{1}\" для всех элементов?",
|
"MessageConfirmRenameTag": "Вы уверены, что хотите переименовать тег \"{0}\" в \"{1}\" для всех элементов?",
|
||||||
"MessageConfirmRenameTagMergeNote": "Примечание: Этот тег уже существует, поэтому они будут объединены.",
|
"MessageConfirmRenameTagMergeNote": "Примечание: Этот тег уже существует, поэтому они будут объединены.",
|
||||||
"MessageConfirmRenameTagWarning": "Предупреждение! Похожий тег с другими начальными буквами уже существует \"{0}\".",
|
"MessageConfirmRenameTagWarning": "Предупреждение! Похожий тег с другими начальными буквами уже существует \"{0}\".",
|
||||||
"MessageConfirmReScanLibraryItems": "Are you sure you want to re-scan {0} items?",
|
"MessageConfirmReScanLibraryItems": "Вы уверены, что хотите пересканировать {0} элементов?",
|
||||||
"MessageConfirmSendEbookToDevice": "Вы уверены, что хотите отправить {0} e-книгу \"{1}\" на устройство \"{2}\"?",
|
"MessageConfirmSendEbookToDevice": "Вы уверены, что хотите отправить {0} e-книгу \"{1}\" на устройство \"{2}\"?",
|
||||||
"MessageDownloadingEpisode": "Эпизод скачивается",
|
"MessageDownloadingEpisode": "Эпизод скачивается",
|
||||||
"MessageDragFilesIntoTrackOrder": "Перетащите файлы для исправления порядка треков",
|
"MessageDragFilesIntoTrackOrder": "Перетащите файлы для исправления порядка треков",
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
{
|
{
|
||||||
"ButtonAdd": "增加",
|
"ButtonAdd": "增加",
|
||||||
"ButtonAddChapters": "添加章节",
|
"ButtonAddChapters": "添加章节",
|
||||||
"ButtonAddDevice": "Add Device",
|
"ButtonAddDevice": "添加设备",
|
||||||
"ButtonAddLibrary": "Add Library",
|
"ButtonAddLibrary": "添加库",
|
||||||
"ButtonAddPodcasts": "添加播客",
|
"ButtonAddPodcasts": "添加播客",
|
||||||
"ButtonAddUser": "Add User",
|
"ButtonAddUser": "添加用户",
|
||||||
"ButtonAddYourFirstLibrary": "添加第一个媒体库",
|
"ButtonAddYourFirstLibrary": "添加第一个媒体库",
|
||||||
"ButtonApply": "应用",
|
"ButtonApply": "应用",
|
||||||
"ButtonApplyChapters": "应用到章节",
|
"ButtonApplyChapters": "应用到章节",
|
||||||
|
|
@ -62,7 +62,7 @@
|
||||||
"ButtonRemoveSeriesFromContinueSeries": "从继续收听系列中删除",
|
"ButtonRemoveSeriesFromContinueSeries": "从继续收听系列中删除",
|
||||||
"ButtonReScan": "重新扫描",
|
"ButtonReScan": "重新扫描",
|
||||||
"ButtonReset": "重置",
|
"ButtonReset": "重置",
|
||||||
"ButtonResetToDefault": "Reset to default",
|
"ButtonResetToDefault": "重置为默认",
|
||||||
"ButtonRestore": "恢复",
|
"ButtonRestore": "恢复",
|
||||||
"ButtonSave": "保存",
|
"ButtonSave": "保存",
|
||||||
"ButtonSaveAndClose": "保存并关闭",
|
"ButtonSaveAndClose": "保存并关闭",
|
||||||
|
|
@ -87,15 +87,15 @@
|
||||||
"ButtonUserEdit": "编辑用户 {0}",
|
"ButtonUserEdit": "编辑用户 {0}",
|
||||||
"ButtonViewAll": "查看全部",
|
"ButtonViewAll": "查看全部",
|
||||||
"ButtonYes": "确定",
|
"ButtonYes": "确定",
|
||||||
"ErrorUploadFetchMetadataAPI": "Error fetching metadata",
|
"ErrorUploadFetchMetadataAPI": "获取元数据时出错",
|
||||||
"ErrorUploadFetchMetadataNoResults": "Could not fetch metadata - try updating title and/or author",
|
"ErrorUploadFetchMetadataNoResults": "无法获取元数据 - 尝试更新标题和/或作者",
|
||||||
"ErrorUploadLacksTitle": "Must have a title",
|
"ErrorUploadLacksTitle": "必须有标题",
|
||||||
"HeaderAccount": "帐户",
|
"HeaderAccount": "帐户",
|
||||||
"HeaderAdvanced": "高级",
|
"HeaderAdvanced": "高级",
|
||||||
"HeaderAppriseNotificationSettings": "测试通知设置",
|
"HeaderAppriseNotificationSettings": "测试通知设置",
|
||||||
"HeaderAudiobookTools": "有声读物文件管理工具",
|
"HeaderAudiobookTools": "有声读物文件管理工具",
|
||||||
"HeaderAudioTracks": "音轨",
|
"HeaderAudioTracks": "音轨",
|
||||||
"HeaderAuthentication": "Authentication",
|
"HeaderAuthentication": "身份验证",
|
||||||
"HeaderBackups": "备份",
|
"HeaderBackups": "备份",
|
||||||
"HeaderChangePassword": "更改密码",
|
"HeaderChangePassword": "更改密码",
|
||||||
"HeaderChapters": "章节",
|
"HeaderChapters": "章节",
|
||||||
|
|
@ -130,15 +130,15 @@
|
||||||
"HeaderManageTags": "管理标签",
|
"HeaderManageTags": "管理标签",
|
||||||
"HeaderMapDetails": "编辑详情",
|
"HeaderMapDetails": "编辑详情",
|
||||||
"HeaderMatch": "匹配",
|
"HeaderMatch": "匹配",
|
||||||
"HeaderMetadataOrderOfPrecedence": "Metadata order of precedence",
|
"HeaderMetadataOrderOfPrecedence": "元数据优先级",
|
||||||
"HeaderMetadataToEmbed": "嵌入元数据",
|
"HeaderMetadataToEmbed": "嵌入元数据",
|
||||||
"HeaderNewAccount": "新建帐户",
|
"HeaderNewAccount": "新建帐户",
|
||||||
"HeaderNewLibrary": "新建媒体库",
|
"HeaderNewLibrary": "新建媒体库",
|
||||||
"HeaderNotifications": "通知",
|
"HeaderNotifications": "通知",
|
||||||
"HeaderOpenIDConnectAuthentication": "OpenID Connect Authentication",
|
"HeaderOpenIDConnectAuthentication": "OpenID 连接身份验证",
|
||||||
"HeaderOpenRSSFeed": "打开 RSS 源",
|
"HeaderOpenRSSFeed": "打开 RSS 源",
|
||||||
"HeaderOtherFiles": "其他文件",
|
"HeaderOtherFiles": "其他文件",
|
||||||
"HeaderPasswordAuthentication": "Password Authentication",
|
"HeaderPasswordAuthentication": "密码认证",
|
||||||
"HeaderPermissions": "权限",
|
"HeaderPermissions": "权限",
|
||||||
"HeaderPlayerQueue": "播放队列",
|
"HeaderPlayerQueue": "播放队列",
|
||||||
"HeaderPlaylist": "播放列表",
|
"HeaderPlaylist": "播放列表",
|
||||||
|
|
@ -187,11 +187,11 @@
|
||||||
"LabelAddToCollectionBatch": "批量添加 {0} 个媒体到收藏",
|
"LabelAddToCollectionBatch": "批量添加 {0} 个媒体到收藏",
|
||||||
"LabelAddToPlaylist": "添加到播放列表",
|
"LabelAddToPlaylist": "添加到播放列表",
|
||||||
"LabelAddToPlaylistBatch": "添加 {0} 个项目到播放列表",
|
"LabelAddToPlaylistBatch": "添加 {0} 个项目到播放列表",
|
||||||
"LabelAdminUsersOnly": "Admin users only",
|
"LabelAdminUsersOnly": "仅限管理员用户",
|
||||||
"LabelAll": "全部",
|
"LabelAll": "全部",
|
||||||
"LabelAllUsers": "所有用户",
|
"LabelAllUsers": "所有用户",
|
||||||
"LabelAllUsersExcludingGuests": "All users excluding guests",
|
"LabelAllUsersExcludingGuests": "除访客外的所有用户",
|
||||||
"LabelAllUsersIncludingGuests": "All users including guests",
|
"LabelAllUsersIncludingGuests": "包括访客的所有用户",
|
||||||
"LabelAlreadyInYourLibrary": "已存在你的库中",
|
"LabelAlreadyInYourLibrary": "已存在你的库中",
|
||||||
"LabelAppend": "附加",
|
"LabelAppend": "附加",
|
||||||
"LabelAuthor": "作者",
|
"LabelAuthor": "作者",
|
||||||
|
|
@ -199,12 +199,12 @@
|
||||||
"LabelAuthorLastFirst": "作者 (名, 姓)",
|
"LabelAuthorLastFirst": "作者 (名, 姓)",
|
||||||
"LabelAuthors": "作者",
|
"LabelAuthors": "作者",
|
||||||
"LabelAutoDownloadEpisodes": "自动下载剧集",
|
"LabelAutoDownloadEpisodes": "自动下载剧集",
|
||||||
"LabelAutoFetchMetadata": "Auto Fetch Metadata",
|
"LabelAutoFetchMetadata": "自动获取元数据",
|
||||||
"LabelAutoFetchMetadataHelp": "Fetches metadata for title, author, and series to streamline uploading. Additional metadata may have to be matched after upload.",
|
"LabelAutoFetchMetadataHelp": "获取标题, 作者和系列的元数据以简化上传. 上传后可能需要匹配其他元数据.",
|
||||||
"LabelAutoLaunch": "Auto Launch",
|
"LabelAutoLaunch": "自动启动",
|
||||||
"LabelAutoLaunchDescription": "Redirect to the auth provider automatically when navigating to the login page (manual override path <code>/login?autoLaunch=0</code>)",
|
"LabelAutoLaunchDescription": "导航到登录页面时自动重定向到身份验证提供程序 (手动覆盖路径 <code>/login?autoLaunch=0</code>)",
|
||||||
"LabelAutoRegister": "Auto Register",
|
"LabelAutoRegister": "自动注册",
|
||||||
"LabelAutoRegisterDescription": "Automatically create new users after logging in",
|
"LabelAutoRegisterDescription": "登录后自动创建新用户",
|
||||||
"LabelBackToUser": "返回到用户",
|
"LabelBackToUser": "返回到用户",
|
||||||
"LabelBackupLocation": "备份位置",
|
"LabelBackupLocation": "备份位置",
|
||||||
"LabelBackupsEnableAutomaticBackups": "启用自动备份",
|
"LabelBackupsEnableAutomaticBackups": "启用自动备份",
|
||||||
|
|
@ -215,13 +215,13 @@
|
||||||
"LabelBackupsNumberToKeepHelp": "一次只能删除一个备份, 因此如果你已经有超过此数量的备份, 则应手动删除它们.",
|
"LabelBackupsNumberToKeepHelp": "一次只能删除一个备份, 因此如果你已经有超过此数量的备份, 则应手动删除它们.",
|
||||||
"LabelBitrate": "比特率",
|
"LabelBitrate": "比特率",
|
||||||
"LabelBooks": "图书",
|
"LabelBooks": "图书",
|
||||||
"LabelButtonText": "Button Text",
|
"LabelButtonText": "按钮文本",
|
||||||
"LabelChangePassword": "修改密码",
|
"LabelChangePassword": "修改密码",
|
||||||
"LabelChannels": "声道",
|
"LabelChannels": "声道",
|
||||||
"LabelChapters": "章节",
|
"LabelChapters": "章节",
|
||||||
"LabelChaptersFound": "找到的章节",
|
"LabelChaptersFound": "找到的章节",
|
||||||
"LabelChapterTitle": "章节标题",
|
"LabelChapterTitle": "章节标题",
|
||||||
"LabelClickForMoreInfo": "Click for more info",
|
"LabelClickForMoreInfo": "点击了解更多信息",
|
||||||
"LabelClosePlayer": "关闭播放器",
|
"LabelClosePlayer": "关闭播放器",
|
||||||
"LabelCodec": "编解码",
|
"LabelCodec": "编解码",
|
||||||
"LabelCollapseSeries": "折叠系列",
|
"LabelCollapseSeries": "折叠系列",
|
||||||
|
|
@ -240,12 +240,12 @@
|
||||||
"LabelCurrently": "当前:",
|
"LabelCurrently": "当前:",
|
||||||
"LabelCustomCronExpression": "自定义计划任务表达式:",
|
"LabelCustomCronExpression": "自定义计划任务表达式:",
|
||||||
"LabelDatetime": "日期时间",
|
"LabelDatetime": "日期时间",
|
||||||
"LabelDeleteFromFileSystemCheckbox": "Delete from file system (uncheck to only remove from database)",
|
"LabelDeleteFromFileSystemCheckbox": "从文件系统删除 (取消选中仅从数据库中删除)",
|
||||||
"LabelDescription": "描述",
|
"LabelDescription": "描述",
|
||||||
"LabelDeselectAll": "全部取消选择",
|
"LabelDeselectAll": "全部取消选择",
|
||||||
"LabelDevice": "设备",
|
"LabelDevice": "设备",
|
||||||
"LabelDeviceInfo": "设备信息",
|
"LabelDeviceInfo": "设备信息",
|
||||||
"LabelDeviceIsAvailableTo": "Device is available to...",
|
"LabelDeviceIsAvailableTo": "设备可用于...",
|
||||||
"LabelDirectory": "目录",
|
"LabelDirectory": "目录",
|
||||||
"LabelDiscFromFilename": "从文件名获取光盘",
|
"LabelDiscFromFilename": "从文件名获取光盘",
|
||||||
"LabelDiscFromMetadata": "从元数据获取光盘",
|
"LabelDiscFromMetadata": "从元数据获取光盘",
|
||||||
|
|
@ -271,7 +271,7 @@
|
||||||
"LabelExample": "示例",
|
"LabelExample": "示例",
|
||||||
"LabelExplicit": "信息准确",
|
"LabelExplicit": "信息准确",
|
||||||
"LabelFeedURL": "源 URL",
|
"LabelFeedURL": "源 URL",
|
||||||
"LabelFetchingMetadata": "Fetching Metadata",
|
"LabelFetchingMetadata": "正在获取元数据",
|
||||||
"LabelFile": "文件",
|
"LabelFile": "文件",
|
||||||
"LabelFileBirthtime": "文件创建时间",
|
"LabelFileBirthtime": "文件创建时间",
|
||||||
"LabelFileModified": "文件修改时间",
|
"LabelFileModified": "文件修改时间",
|
||||||
|
|
@ -289,7 +289,7 @@
|
||||||
"LabelHardDeleteFile": "完全删除文件",
|
"LabelHardDeleteFile": "完全删除文件",
|
||||||
"LabelHasEbook": "有电子书",
|
"LabelHasEbook": "有电子书",
|
||||||
"LabelHasSupplementaryEbook": "有补充电子书",
|
"LabelHasSupplementaryEbook": "有补充电子书",
|
||||||
"LabelHighestPriority": "Highest priority",
|
"LabelHighestPriority": "最高优先级",
|
||||||
"LabelHost": "主机",
|
"LabelHost": "主机",
|
||||||
"LabelHour": "小时",
|
"LabelHour": "小时",
|
||||||
"LabelIcon": "图标",
|
"LabelIcon": "图标",
|
||||||
|
|
@ -331,20 +331,20 @@
|
||||||
"LabelLogLevelInfo": "信息",
|
"LabelLogLevelInfo": "信息",
|
||||||
"LabelLogLevelWarn": "警告",
|
"LabelLogLevelWarn": "警告",
|
||||||
"LabelLookForNewEpisodesAfterDate": "在此日期后查找新剧集",
|
"LabelLookForNewEpisodesAfterDate": "在此日期后查找新剧集",
|
||||||
"LabelLowestPriority": "Lowest Priority",
|
"LabelLowestPriority": "最低优先级",
|
||||||
"LabelMatchExistingUsersBy": "Match existing users by",
|
"LabelMatchExistingUsersBy": "匹配现有用户",
|
||||||
"LabelMatchExistingUsersByDescription": "Used for connecting existing users. Once connected, users will be matched by a unique id from your SSO provider",
|
"LabelMatchExistingUsersByDescription": "用于连接现有用户. 连接后, 用户将通过SSO提供商提供的唯一 id 进行匹配",
|
||||||
"LabelMediaPlayer": "媒体播放器",
|
"LabelMediaPlayer": "媒体播放器",
|
||||||
"LabelMediaType": "媒体类型",
|
"LabelMediaType": "媒体类型",
|
||||||
"LabelMetadataOrderOfPrecedenceDescription": "Higher priority metadata sources will override lower priority metadata sources",
|
"LabelMetadataOrderOfPrecedenceDescription": "较高优先级的元数据源将覆盖较低优先级的元数据源",
|
||||||
"LabelMetadataProvider": "元数据提供者",
|
"LabelMetadataProvider": "元数据提供者",
|
||||||
"LabelMetaTag": "元数据标签",
|
"LabelMetaTag": "元数据标签",
|
||||||
"LabelMetaTags": "元标签",
|
"LabelMetaTags": "元标签",
|
||||||
"LabelMinute": "分钟",
|
"LabelMinute": "分钟",
|
||||||
"LabelMissing": "丢失",
|
"LabelMissing": "丢失",
|
||||||
"LabelMissingParts": "丢失的部分",
|
"LabelMissingParts": "丢失的部分",
|
||||||
"LabelMobileRedirectURIs": "Allowed Mobile Redirect URIs",
|
"LabelMobileRedirectURIs": "允许移动应用重定向 URI",
|
||||||
"LabelMobileRedirectURIsDescription": "This is a whitelist of valid redirect URIs for mobile apps. The default one is <code>audiobookshelf://oauth</code>, which you can remove or supplement with additional URIs for third-party app integration. Using an asterisk (<code>*</code>) as the sole entry permits any URI.",
|
"LabelMobileRedirectURIsDescription": "这是移动应用程序的有效重定向 URI 白名单. 默认值为 <code>audiobookshelf://oauth</code>,您可以删除它或添加其他 URI 以进行第三方应用集成. 使用星号 (<code>*</code>) 作为唯一条目允许任何 URI.",
|
||||||
"LabelMore": "更多",
|
"LabelMore": "更多",
|
||||||
"LabelMoreInfo": "更多..",
|
"LabelMoreInfo": "更多..",
|
||||||
"LabelName": "名称",
|
"LabelName": "名称",
|
||||||
|
|
@ -523,7 +523,7 @@
|
||||||
"LabelUpdateDetailsHelp": "找到匹配项时允许覆盖所选书籍存在的详细信息",
|
"LabelUpdateDetailsHelp": "找到匹配项时允许覆盖所选书籍存在的详细信息",
|
||||||
"LabelUploaderDragAndDrop": "拖放文件或文件夹",
|
"LabelUploaderDragAndDrop": "拖放文件或文件夹",
|
||||||
"LabelUploaderDropFiles": "删除文件",
|
"LabelUploaderDropFiles": "删除文件",
|
||||||
"LabelUploaderItemFetchMetadataHelp": "Automatically fetch title, author, and series",
|
"LabelUploaderItemFetchMetadataHelp": "自动获取标题, 作者和系列",
|
||||||
"LabelUseChapterTrack": "使用章节音轨",
|
"LabelUseChapterTrack": "使用章节音轨",
|
||||||
"LabelUseFullTrack": "使用完整音轨",
|
"LabelUseFullTrack": "使用完整音轨",
|
||||||
"LabelUser": "用户",
|
"LabelUser": "用户",
|
||||||
|
|
@ -557,15 +557,15 @@
|
||||||
"MessageConfirmDeleteBackup": "你确定要删除备份 {0}?",
|
"MessageConfirmDeleteBackup": "你确定要删除备份 {0}?",
|
||||||
"MessageConfirmDeleteFile": "这将从文件系统中删除该文件. 你确定吗?",
|
"MessageConfirmDeleteFile": "这将从文件系统中删除该文件. 你确定吗?",
|
||||||
"MessageConfirmDeleteLibrary": "你确定要永久删除媒体库 \"{0}\"?",
|
"MessageConfirmDeleteLibrary": "你确定要永久删除媒体库 \"{0}\"?",
|
||||||
"MessageConfirmDeleteLibraryItem": "This will delete the library item from the database and your file system. Are you sure?",
|
"MessageConfirmDeleteLibraryItem": "这将从数据库和文件系统中删除库项目. 你确定吗?",
|
||||||
"MessageConfirmDeleteLibraryItems": "This will delete {0} library items from the database and your file system. Are you sure?",
|
"MessageConfirmDeleteLibraryItems": "这将从数据库和文件系统中删除 {0} 个库项目. 你确定吗?",
|
||||||
"MessageConfirmDeleteSession": "你确定要删除此会话吗?",
|
"MessageConfirmDeleteSession": "你确定要删除此会话吗?",
|
||||||
"MessageConfirmForceReScan": "你确定要强制重新扫描吗?",
|
"MessageConfirmForceReScan": "你确定要强制重新扫描吗?",
|
||||||
"MessageConfirmMarkAllEpisodesFinished": "你确定要将所有剧集都标记为已完成吗?",
|
"MessageConfirmMarkAllEpisodesFinished": "你确定要将所有剧集都标记为已完成吗?",
|
||||||
"MessageConfirmMarkAllEpisodesNotFinished": "你确定要将所有剧集都标记为未完成吗?",
|
"MessageConfirmMarkAllEpisodesNotFinished": "你确定要将所有剧集都标记为未完成吗?",
|
||||||
"MessageConfirmMarkSeriesFinished": "你确定要将此系列中的所有书籍都标记为已听完吗?",
|
"MessageConfirmMarkSeriesFinished": "你确定要将此系列中的所有书籍都标记为已听完吗?",
|
||||||
"MessageConfirmMarkSeriesNotFinished": "你确定要将此系列中的所有书籍都标记为未听完吗?",
|
"MessageConfirmMarkSeriesNotFinished": "你确定要将此系列中的所有书籍都标记为未听完吗?",
|
||||||
"MessageConfirmQuickEmbed": "Warning! Quick embed will not backup your audio files. Make sure that you have a backup of your audio files. <br><br>Would you like to continue?",
|
"MessageConfirmQuickEmbed": "警告! 快速嵌入不会备份你的音频文件. 确保你有音频文件的备份. <br><br>你是否想继续吗?",
|
||||||
"MessageConfirmRemoveAllChapters": "你确定要移除所有章节吗?",
|
"MessageConfirmRemoveAllChapters": "你确定要移除所有章节吗?",
|
||||||
"MessageConfirmRemoveAuthor": "你确定要删除作者 \"{0}\"?",
|
"MessageConfirmRemoveAuthor": "你确定要删除作者 \"{0}\"?",
|
||||||
"MessageConfirmRemoveCollection": "你确定要移除收藏 \"{0}\"?",
|
"MessageConfirmRemoveCollection": "你确定要移除收藏 \"{0}\"?",
|
||||||
|
|
@ -579,7 +579,7 @@
|
||||||
"MessageConfirmRenameTag": "你确定要将所有项目标签 \"{0}\" 重命名到 \"{1}\"?",
|
"MessageConfirmRenameTag": "你确定要将所有项目标签 \"{0}\" 重命名到 \"{1}\"?",
|
||||||
"MessageConfirmRenameTagMergeNote": "注意: 该标签已经存在, 因此它们将被合并.",
|
"MessageConfirmRenameTagMergeNote": "注意: 该标签已经存在, 因此它们将被合并.",
|
||||||
"MessageConfirmRenameTagWarning": "警告! 已经存在有大小写不同的类似标签 \"{0}\".",
|
"MessageConfirmRenameTagWarning": "警告! 已经存在有大小写不同的类似标签 \"{0}\".",
|
||||||
"MessageConfirmReScanLibraryItems": "Are you sure you want to re-scan {0} items?",
|
"MessageConfirmReScanLibraryItems": "你确定要重新扫描 {0} 个项目吗?",
|
||||||
"MessageConfirmSendEbookToDevice": "你确定要发送 {0} 电子书 \"{1}\" 到设备 \"{2}\"?",
|
"MessageConfirmSendEbookToDevice": "你确定要发送 {0} 电子书 \"{1}\" 到设备 \"{2}\"?",
|
||||||
"MessageDownloadingEpisode": "正在下载剧集",
|
"MessageDownloadingEpisode": "正在下载剧集",
|
||||||
"MessageDragFilesIntoTrackOrder": "将文件拖动到正确的音轨顺序",
|
"MessageDragFilesIntoTrackOrder": "将文件拖动到正确的音轨顺序",
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,28 @@ version: "3.7"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
audiobookshelf:
|
audiobookshelf:
|
||||||
image: ghcr.io/advplyr/audiobookshelf
|
image: ghcr.io/advplyr/audiobookshelf:latest
|
||||||
|
# ABS runs on port 13378 by default. If you want to change
|
||||||
|
# the port, only change the external port, not the internal port
|
||||||
ports:
|
ports:
|
||||||
- 13378:80
|
- 13378:80
|
||||||
volumes:
|
volumes:
|
||||||
|
# These volumes are needed to keep your library persistent
|
||||||
|
# and allow media to be accessed by the ABS server.
|
||||||
|
# The path to the left of the colon is the path on your computer,
|
||||||
|
# and the path to the right of the colon is where the data is
|
||||||
|
# available to ABS in Docker.
|
||||||
|
# You can change these media directories or add as many as you want
|
||||||
- ./audiobooks:/audiobooks
|
- ./audiobooks:/audiobooks
|
||||||
- ./podcasts:/podcasts
|
- ./podcasts:/podcasts
|
||||||
|
# The metadata directory can be stored anywhere on your computer
|
||||||
- ./metadata:/metadata
|
- ./metadata:/metadata
|
||||||
|
# The config directory needs to be on the same physical machine
|
||||||
|
# you are running ABS on
|
||||||
- ./config:/config
|
- ./config:/config
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
# You can use the following environment variable to run the ABS
|
||||||
|
# docker container as a specific user. You will need to change
|
||||||
|
# the UID and GID to the correct values for your user.
|
||||||
|
#environment:
|
||||||
|
# - user=1000:1000
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,18 @@ const BookFinder = require('../finders/BookFinder')
|
||||||
const PodcastFinder = require('../finders/PodcastFinder')
|
const PodcastFinder = require('../finders/PodcastFinder')
|
||||||
const AuthorFinder = require('../finders/AuthorFinder')
|
const AuthorFinder = require('../finders/AuthorFinder')
|
||||||
const MusicFinder = require('../finders/MusicFinder')
|
const MusicFinder = require('../finders/MusicFinder')
|
||||||
|
const Database = require("../Database")
|
||||||
|
|
||||||
class SearchController {
|
class SearchController {
|
||||||
constructor() { }
|
constructor() { }
|
||||||
|
|
||||||
async findBooks(req, res) {
|
async findBooks(req, res) {
|
||||||
|
const id = req.query.id
|
||||||
|
const libraryItem = await Database.libraryItemModel.getOldById(id)
|
||||||
const provider = req.query.provider || 'google'
|
const provider = req.query.provider || 'google'
|
||||||
const title = req.query.title || ''
|
const title = req.query.title || ''
|
||||||
const author = req.query.author || ''
|
const author = req.query.author || ''
|
||||||
const results = await BookFinder.search(provider, title, author)
|
const results = await BookFinder.search(libraryItem, provider, title, author)
|
||||||
res.json(results)
|
res.json(results)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -167,6 +167,7 @@ class BookFinder {
|
||||||
[/ (2nd|3rd|\d+th)\s+ed(\.|ition)?/g, ''], // Remove edition
|
[/ (2nd|3rd|\d+th)\s+ed(\.|ition)?/g, ''], // Remove edition
|
||||||
[/(^| |\.)(m4b|m4a|mp3)( |$)/g, ''], // Remove file-type
|
[/(^| |\.)(m4b|m4a|mp3)( |$)/g, ''], // Remove file-type
|
||||||
[/ a novel.*$/g, ''], // Remove "a novel"
|
[/ a novel.*$/g, ''], // Remove "a novel"
|
||||||
|
[/(^| )(un)?abridged( |$)/g, ' '], // Remove "unabridged/abridged"
|
||||||
[/^\d+ | \d+$/g, ''], // Remove preceding/trailing numbers
|
[/^\d+ | \d+$/g, ''], // Remove preceding/trailing numbers
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -298,6 +299,7 @@ class BookFinder {
|
||||||
/**
|
/**
|
||||||
* Search for books including fuzzy searches
|
* Search for books including fuzzy searches
|
||||||
*
|
*
|
||||||
|
* @param {Object} libraryItem
|
||||||
* @param {string} provider
|
* @param {string} provider
|
||||||
* @param {string} title
|
* @param {string} title
|
||||||
* @param {string} author
|
* @param {string} author
|
||||||
|
|
@ -306,7 +308,7 @@ class BookFinder {
|
||||||
* @param {{titleDistance:number, authorDistance:number, maxFuzzySearches:number}} options
|
* @param {{titleDistance:number, authorDistance:number, maxFuzzySearches:number}} options
|
||||||
* @returns {Promise<Object[]>}
|
* @returns {Promise<Object[]>}
|
||||||
*/
|
*/
|
||||||
async search(provider, title, author, isbn, asin, options = {}) {
|
async search(libraryItem, provider, title, author, isbn, asin, options = {}) {
|
||||||
let books = []
|
let books = []
|
||||||
const maxTitleDistance = !isNaN(options.titleDistance) ? Number(options.titleDistance) : 4
|
const maxTitleDistance = !isNaN(options.titleDistance) ? Number(options.titleDistance) : 4
|
||||||
const maxAuthorDistance = !isNaN(options.authorDistance) ? Number(options.authorDistance) : 4
|
const maxAuthorDistance = !isNaN(options.authorDistance) ? Number(options.authorDistance) : 4
|
||||||
|
|
@ -335,6 +337,7 @@ class BookFinder {
|
||||||
for (const titlePart of titleParts)
|
for (const titlePart of titleParts)
|
||||||
authorCandidates.add(titlePart)
|
authorCandidates.add(titlePart)
|
||||||
authorCandidates = await authorCandidates.getCandidates()
|
authorCandidates = await authorCandidates.getCandidates()
|
||||||
|
loop_author:
|
||||||
for (const authorCandidate of authorCandidates) {
|
for (const authorCandidate of authorCandidates) {
|
||||||
let titleCandidates = new BookFinder.TitleCandidates(authorCandidate)
|
let titleCandidates = new BookFinder.TitleCandidates(authorCandidate)
|
||||||
for (const titlePart of titleParts)
|
for (const titlePart of titleParts)
|
||||||
|
|
@ -342,13 +345,27 @@ class BookFinder {
|
||||||
titleCandidates = titleCandidates.getCandidates()
|
titleCandidates = titleCandidates.getCandidates()
|
||||||
for (const titleCandidate of titleCandidates) {
|
for (const titleCandidate of titleCandidates) {
|
||||||
if (titleCandidate == title && authorCandidate == author) continue // We already tried this
|
if (titleCandidate == title && authorCandidate == author) continue // We already tried this
|
||||||
if (++numFuzzySearches > maxFuzzySearches) return books
|
if (++numFuzzySearches > maxFuzzySearches) break loop_author
|
||||||
books = await this.runSearch(titleCandidate, authorCandidate, provider, asin, maxTitleDistance, maxAuthorDistance)
|
books = await this.runSearch(titleCandidate, authorCandidate, provider, asin, maxTitleDistance, maxAuthorDistance)
|
||||||
if (books.length) return books
|
if (books.length) break loop_author
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (books.length) {
|
||||||
|
const resultsHaveDuration = provider.startsWith('audible')
|
||||||
|
if (resultsHaveDuration && libraryItem?.media?.duration) {
|
||||||
|
const libraryItemDurationMinutes = libraryItem.media.duration / 60
|
||||||
|
// If provider results have duration, sort by ascendinge duration difference from libraryItem
|
||||||
|
books.sort((a, b) => {
|
||||||
|
const aDuration = a.duration || Number.POSITIVE_INFINITY
|
||||||
|
const bDuration = b.duration || Number.POSITIVE_INFINITY
|
||||||
|
const aDurationDiff = Math.abs(aDuration - libraryItemDurationMinutes)
|
||||||
|
const bDurationDiff = Math.abs(bDuration - libraryItemDurationMinutes)
|
||||||
|
return aDurationDiff - bDurationDiff
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
return books
|
return books
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -392,12 +409,12 @@ class BookFinder {
|
||||||
|
|
||||||
if (provider === 'all') {
|
if (provider === 'all') {
|
||||||
for (const providerString of this.providers) {
|
for (const providerString of this.providers) {
|
||||||
const providerResults = await this.search(providerString, title, author, options)
|
const providerResults = await this.search(null, providerString, title, author, options)
|
||||||
Logger.debug(`[BookFinder] Found ${providerResults.length} covers from ${providerString}`)
|
Logger.debug(`[BookFinder] Found ${providerResults.length} covers from ${providerString}`)
|
||||||
searchResults.push(...providerResults)
|
searchResults.push(...providerResults)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
searchResults = await this.search(provider, title, author, options)
|
searchResults = await this.search(null, provider, title, author, options)
|
||||||
}
|
}
|
||||||
Logger.debug(`[BookFinder] FindCovers search results: ${searchResults.length}`)
|
Logger.debug(`[BookFinder] FindCovers search results: ${searchResults.length}`)
|
||||||
|
|
||||||
|
|
@ -461,6 +478,8 @@ function cleanAuthorForCompares(author) {
|
||||||
cleanAuthor = cleanAuthor.replace(/([a-z])\.([a-z])/g, '$1. $2')
|
cleanAuthor = cleanAuthor.replace(/([a-z])\.([a-z])/g, '$1. $2')
|
||||||
// remove middle initials
|
// remove middle initials
|
||||||
cleanAuthor = cleanAuthor.replace(/(?<=\w\w)(\s+[a-z]\.?)+(?=\s+\w\w)/g, '')
|
cleanAuthor = cleanAuthor.replace(/(?<=\w\w)(\s+[a-z]\.?)+(?=\s+\w\w)/g, '')
|
||||||
|
// remove et al.
|
||||||
|
cleanAuthor = cleanAuthor.replace(/ et al\.?(?= |$)/g, '')
|
||||||
return cleanAuthor
|
return cleanAuthor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -103,19 +103,29 @@ class RssFeedManager {
|
||||||
await Database.updateFeed(feed)
|
await Database.updateFeed(feed)
|
||||||
}
|
}
|
||||||
} else if (feed.entityType === 'collection') {
|
} else if (feed.entityType === 'collection') {
|
||||||
const collection = await Database.collectionModel.findByPk(feed.entityId)
|
const collection = await Database.collectionModel.findByPk(feed.entityId, {
|
||||||
|
include: Database.collectionBookModel
|
||||||
|
})
|
||||||
if (collection) {
|
if (collection) {
|
||||||
const collectionExpanded = await collection.getOldJsonExpanded()
|
const collectionExpanded = await collection.getOldJsonExpanded()
|
||||||
|
|
||||||
// Find most recently updated item in collection
|
// Find most recently updated item in collection
|
||||||
let mostRecentlyUpdatedAt = collectionExpanded.lastUpdate
|
let mostRecentlyUpdatedAt = collectionExpanded.lastUpdate
|
||||||
|
// Check for most recently updated book
|
||||||
collectionExpanded.books.forEach((libraryItem) => {
|
collectionExpanded.books.forEach((libraryItem) => {
|
||||||
if (libraryItem.media.tracks.length && libraryItem.updatedAt > mostRecentlyUpdatedAt) {
|
if (libraryItem.media.tracks.length && libraryItem.updatedAt > mostRecentlyUpdatedAt) {
|
||||||
mostRecentlyUpdatedAt = libraryItem.updatedAt
|
mostRecentlyUpdatedAt = libraryItem.updatedAt
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
// Check for most recently added collection book
|
||||||
|
collection.collectionBooks.forEach((collectionBook) => {
|
||||||
|
if (collectionBook.createdAt.valueOf() > mostRecentlyUpdatedAt) {
|
||||||
|
mostRecentlyUpdatedAt = collectionBook.createdAt.valueOf()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const hasBooksRemoved = collection.collectionBooks.length < feed.episodes.length
|
||||||
|
|
||||||
if (!feed.entityUpdatedAt || mostRecentlyUpdatedAt > feed.entityUpdatedAt) {
|
if (!feed.entityUpdatedAt || hasBooksRemoved || mostRecentlyUpdatedAt > feed.entityUpdatedAt) {
|
||||||
Logger.debug(`[RssFeedManager] Updating RSS feed for collection "${collection.name}"`)
|
Logger.debug(`[RssFeedManager] Updating RSS feed for collection "${collection.name}"`)
|
||||||
|
|
||||||
feed.updateFromCollection(collectionExpanded)
|
feed.updateFromCollection(collectionExpanded)
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,7 @@ class Feed extends Model {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find all library item ids that have an open feed (used in library filter)
|
* Find all library item ids that have an open feed (used in library filter)
|
||||||
* @returns {Promise<Array<String>>} array of library item ids
|
* @returns {Promise<string[]>} array of library item ids
|
||||||
*/
|
*/
|
||||||
static async findAllLibraryItemIds() {
|
static async findAllLibraryItemIds() {
|
||||||
const feeds = await this.findAll({
|
const feeds = await this.findAll({
|
||||||
|
|
@ -122,8 +122,8 @@ class Feed extends Model {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find feed where and return oldFeed
|
* Find feed where and return oldFeed
|
||||||
* @param {object} where sequelize where object
|
* @param {Object} where sequelize where object
|
||||||
* @returns {Promise<objects.Feed>} oldFeed
|
* @returns {Promise<oldFeed>} oldFeed
|
||||||
*/
|
*/
|
||||||
static async findOneOld(where) {
|
static async findOneOld(where) {
|
||||||
if (!where) return null
|
if (!where) return null
|
||||||
|
|
@ -140,7 +140,7 @@ class Feed extends Model {
|
||||||
/**
|
/**
|
||||||
* Find feed and return oldFeed
|
* Find feed and return oldFeed
|
||||||
* @param {string} id
|
* @param {string} id
|
||||||
* @returns {Promise<objects.Feed>} oldFeed
|
* @returns {Promise<oldFeed>} oldFeed
|
||||||
*/
|
*/
|
||||||
static async findByPkOld(id) {
|
static async findByPkOld(id) {
|
||||||
if (!id) return null
|
if (!id) return null
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ class Scanner {
|
||||||
var searchISBN = options.isbn || libraryItem.media.metadata.isbn
|
var searchISBN = options.isbn || libraryItem.media.metadata.isbn
|
||||||
var searchASIN = options.asin || libraryItem.media.metadata.asin
|
var searchASIN = options.asin || libraryItem.media.metadata.asin
|
||||||
|
|
||||||
var results = await BookFinder.search(provider, searchTitle, searchAuthor, searchISBN, searchASIN, { maxFuzzySearches: 2 })
|
var results = await BookFinder.search(libraryItem, provider, searchTitle, searchAuthor, searchISBN, searchASIN, { maxFuzzySearches: 2 })
|
||||||
if (!results.length) {
|
if (!results.length) {
|
||||||
return {
|
return {
|
||||||
warning: `No ${provider} match found`
|
warning: `No ${provider} match found`
|
||||||
|
|
|
||||||
|
|
@ -218,7 +218,7 @@ module.exports.parsePodcastRssFeedXml = async (xml, excludeEpisodeMetadata = fal
|
||||||
|
|
||||||
module.exports.getPodcastFeed = (feedUrl, excludeEpisodeMetadata = false) => {
|
module.exports.getPodcastFeed = (feedUrl, excludeEpisodeMetadata = false) => {
|
||||||
Logger.debug(`[podcastUtils] getPodcastFeed for "${feedUrl}"`)
|
Logger.debug(`[podcastUtils] getPodcastFeed for "${feedUrl}"`)
|
||||||
return axios.get(feedUrl, { timeout: 12000, responseType: 'arraybuffer' }).then(async (data) => {
|
return axios.get(feedUrl, { timeout: 12000, responseType: 'arraybuffer', headers: { Accept: 'application/rss+xml' } }).then(async (data) => {
|
||||||
|
|
||||||
// Adding support for ios-8859-1 encoded RSS feeds.
|
// Adding support for ios-8859-1 encoded RSS feeds.
|
||||||
// See: https://github.com/advplyr/audiobookshelf/issues/1489
|
// See: https://github.com/advplyr/audiobookshelf/issues/1489
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,8 @@ describe('TitleCandidates', () => {
|
||||||
['adds candidate + variant, removing edition 2', 'anna karenina 4th ed.', ['anna karenina', 'anna karenina 4th ed.']],
|
['adds candidate + variant, removing edition 2', 'anna karenina 4th ed.', ['anna karenina', 'anna karenina 4th ed.']],
|
||||||
['adds candidate + variant, removing fie type', 'anna karenina.mp3', ['anna karenina', 'anna karenina.mp3']],
|
['adds candidate + variant, removing fie type', 'anna karenina.mp3', ['anna karenina', 'anna karenina.mp3']],
|
||||||
['adds candidate + variant, removing "a novel"', 'anna karenina a novel', ['anna karenina', 'anna karenina a novel']],
|
['adds candidate + variant, removing "a novel"', 'anna karenina a novel', ['anna karenina', 'anna karenina a novel']],
|
||||||
|
['adds candidate + variant, removing "abridged"', 'abridged anna karenina', ['anna karenina', 'abridged anna karenina']],
|
||||||
|
['adds candidate + variant, removing "unabridged"', 'anna karenina unabridged', ['anna karenina', 'anna karenina unabridged']],
|
||||||
['adds candidate + variant, removing preceding/trailing numbers', '1 anna karenina 2', ['anna karenina', '1 anna karenina 2']],
|
['adds candidate + variant, removing preceding/trailing numbers', '1 anna karenina 2', ['anna karenina', '1 anna karenina 2']],
|
||||||
['does not add empty candidate', '', []],
|
['does not add empty candidate', '', []],
|
||||||
['does not add spaces-only candidate', ' ', []],
|
['does not add spaces-only candidate', ' ', []],
|
||||||
|
|
@ -109,6 +111,7 @@ describe('AuthorCandidates', () => {
|
||||||
['adds recognized author if edit distance from candidate is small', 'nicolai gogol', ['nikolai gogol']],
|
['adds recognized author if edit distance from candidate is small', 'nicolai gogol', ['nikolai gogol']],
|
||||||
['does not add candidate if edit distance from any recognized author is large', 'nikolai google', []],
|
['does not add candidate if edit distance from any recognized author is large', 'nikolai google', []],
|
||||||
['adds normalized recognized candidate (contains redundant spaces)', 'nikolai gogol', ['nikolai gogol']],
|
['adds normalized recognized candidate (contains redundant spaces)', 'nikolai gogol', ['nikolai gogol']],
|
||||||
|
['adds normalized recognized candidate (et al removed)', 'nikolai gogol et al.', ['nikolai gogol']],
|
||||||
['adds normalized recognized candidate (normalized initials)', 'j.k. rowling', ['j. k. rowling']],
|
['adds normalized recognized candidate (normalized initials)', 'j.k. rowling', ['j. k. rowling']],
|
||||||
].forEach(([name, author, expected]) => it(name, async () => {
|
].forEach(([name, author, expected]) => it(name, async () => {
|
||||||
authorCandidates.add(author)
|
authorCandidates.add(author)
|
||||||
|
|
@ -222,14 +225,14 @@ describe('search', () => {
|
||||||
|
|
||||||
describe('search title is empty', () => {
|
describe('search title is empty', () => {
|
||||||
it('returns empty result', async () => {
|
it('returns empty result', async () => {
|
||||||
expect(await bookFinder.search('', '', a)).to.deep.equal([])
|
expect(await bookFinder.search(null, '', '', a)).to.deep.equal([])
|
||||||
sinon.assert.callCount(bookFinder.runSearch, 0)
|
sinon.assert.callCount(bookFinder.runSearch, 0)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('search title is a recognized title and search author is a recognized author', () => {
|
describe('search title is a recognized title and search author is a recognized author', () => {
|
||||||
it('returns non-empty result (no fuzzy searches)', async () => {
|
it('returns non-empty result (no fuzzy searches)', async () => {
|
||||||
expect(await bookFinder.search('', t, a)).to.deep.equal(r)
|
expect(await bookFinder.search(null, '', t, a)).to.deep.equal(r)
|
||||||
sinon.assert.callCount(bookFinder.runSearch, 1)
|
sinon.assert.callCount(bookFinder.runSearch, 1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
@ -251,7 +254,7 @@ describe('search', () => {
|
||||||
[`2022_${t}_HQ`],
|
[`2022_${t}_HQ`],
|
||||||
].forEach(([searchTitle]) => {
|
].forEach(([searchTitle]) => {
|
||||||
it(`search('${searchTitle}', '${a}') returns non-empty result (with 1 fuzzy search)`, async () => {
|
it(`search('${searchTitle}', '${a}') returns non-empty result (with 1 fuzzy search)`, async () => {
|
||||||
expect(await bookFinder.search('', searchTitle, a)).to.deep.equal(r)
|
expect(await bookFinder.search(null, '', searchTitle, a)).to.deep.equal(r)
|
||||||
sinon.assert.callCount(bookFinder.runSearch, 2)
|
sinon.assert.callCount(bookFinder.runSearch, 2)
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
@ -261,7 +264,7 @@ describe('search', () => {
|
||||||
[`${a} - series 01 - ${t}`],
|
[`${a} - series 01 - ${t}`],
|
||||||
].forEach(([searchTitle]) => {
|
].forEach(([searchTitle]) => {
|
||||||
it(`search('${searchTitle}', '${a}') returns non-empty result (with 2 fuzzy searches)`, async () => {
|
it(`search('${searchTitle}', '${a}') returns non-empty result (with 2 fuzzy searches)`, async () => {
|
||||||
expect(await bookFinder.search('', searchTitle, a)).to.deep.equal(r)
|
expect(await bookFinder.search(null, '', searchTitle, a)).to.deep.equal(r)
|
||||||
sinon.assert.callCount(bookFinder.runSearch, 3)
|
sinon.assert.callCount(bookFinder.runSearch, 3)
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
@ -271,7 +274,7 @@ describe('search', () => {
|
||||||
[`${t} junk`],
|
[`${t} junk`],
|
||||||
].forEach(([searchTitle]) => {
|
].forEach(([searchTitle]) => {
|
||||||
it(`search('${searchTitle}', '${a}') returns an empty result`, async () => {
|
it(`search('${searchTitle}', '${a}') returns an empty result`, async () => {
|
||||||
expect(await bookFinder.search('', searchTitle, a)).to.deep.equal([])
|
expect(await bookFinder.search(null, '', searchTitle, a)).to.deep.equal([])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -280,7 +283,7 @@ describe('search', () => {
|
||||||
[`${t} - ${a}`],
|
[`${t} - ${a}`],
|
||||||
].forEach(([searchTitle]) => {
|
].forEach(([searchTitle]) => {
|
||||||
it(`search('${searchTitle}', '${a}') returns an empty result (with no fuzzy searches)`, async () => {
|
it(`search('${searchTitle}', '${a}') returns an empty result (with no fuzzy searches)`, async () => {
|
||||||
expect(await bookFinder.search('', searchTitle, a, null, null, { maxFuzzySearches: 0 })).to.deep.equal([])
|
expect(await bookFinder.search(null, '', searchTitle, a, null, null, { maxFuzzySearches: 0 })).to.deep.equal([])
|
||||||
sinon.assert.callCount(bookFinder.runSearch, 1)
|
sinon.assert.callCount(bookFinder.runSearch, 1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
@ -292,7 +295,7 @@ describe('search', () => {
|
||||||
[`${a} - series 01 - ${t}`],
|
[`${a} - series 01 - ${t}`],
|
||||||
].forEach(([searchTitle]) => {
|
].forEach(([searchTitle]) => {
|
||||||
it(`search('${searchTitle}', '${a}') returns an empty result (1 fuzzy search)`, async () => {
|
it(`search('${searchTitle}', '${a}') returns an empty result (1 fuzzy search)`, async () => {
|
||||||
expect(await bookFinder.search('', searchTitle, a, null, null, { maxFuzzySearches: 1 })).to.deep.equal([])
|
expect(await bookFinder.search(null, '', searchTitle, a, null, null, { maxFuzzySearches: 1 })).to.deep.equal([])
|
||||||
sinon.assert.callCount(bookFinder.runSearch, 2)
|
sinon.assert.callCount(bookFinder.runSearch, 2)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
@ -305,7 +308,7 @@ describe('search', () => {
|
||||||
[`${a} - ${t}`],
|
[`${a} - ${t}`],
|
||||||
].forEach(([searchTitle]) => {
|
].forEach(([searchTitle]) => {
|
||||||
it(`search('${searchTitle}', '') returns a non-empty result (1 fuzzy search)`, async () => {
|
it(`search('${searchTitle}', '') returns a non-empty result (1 fuzzy search)`, async () => {
|
||||||
expect(await bookFinder.search('', searchTitle, '')).to.deep.equal(r)
|
expect(await bookFinder.search(null, '', searchTitle, '')).to.deep.equal(r)
|
||||||
sinon.assert.callCount(bookFinder.runSearch, 2)
|
sinon.assert.callCount(bookFinder.runSearch, 2)
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
@ -316,7 +319,7 @@ describe('search', () => {
|
||||||
[`${u} - ${t}`]
|
[`${u} - ${t}`]
|
||||||
].forEach(([searchTitle]) => {
|
].forEach(([searchTitle]) => {
|
||||||
it(`search('${searchTitle}', '') returns an empty result`, async () => {
|
it(`search('${searchTitle}', '') returns an empty result`, async () => {
|
||||||
expect(await bookFinder.search('', searchTitle, '')).to.deep.equal([])
|
expect(await bookFinder.search(null, '', searchTitle, '')).to.deep.equal([])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
@ -327,7 +330,7 @@ describe('search', () => {
|
||||||
[`${u} - ${t}`]
|
[`${u} - ${t}`]
|
||||||
].forEach(([searchTitle]) => {
|
].forEach(([searchTitle]) => {
|
||||||
it(`search('${searchTitle}', '${u}') returns a non-empty result (1 fuzzy search)`, async () => {
|
it(`search('${searchTitle}', '${u}') returns a non-empty result (1 fuzzy search)`, async () => {
|
||||||
expect(await bookFinder.search('', searchTitle, u)).to.deep.equal(r)
|
expect(await bookFinder.search(null, '', searchTitle, u)).to.deep.equal(r)
|
||||||
sinon.assert.callCount(bookFinder.runSearch, 2)
|
sinon.assert.callCount(bookFinder.runSearch, 2)
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
@ -336,9 +339,41 @@ describe('search', () => {
|
||||||
[`${t}`]
|
[`${t}`]
|
||||||
].forEach(([searchTitle]) => {
|
].forEach(([searchTitle]) => {
|
||||||
it(`search('${searchTitle}', '${u}') returns a non-empty result (no fuzzy search)`, async () => {
|
it(`search('${searchTitle}', '${u}') returns a non-empty result (no fuzzy search)`, async () => {
|
||||||
expect(await bookFinder.search('', searchTitle, u)).to.deep.equal(r)
|
expect(await bookFinder.search(null, '', searchTitle, u)).to.deep.equal(r)
|
||||||
sinon.assert.callCount(bookFinder.runSearch, 1)
|
sinon.assert.callCount(bookFinder.runSearch, 1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('search provider results have duration', () => {
|
||||||
|
const libraryItem = { media: { duration: 60 * 1000 } }
|
||||||
|
const provider = 'audible'
|
||||||
|
const unsorted = [{ duration: 3000 }, { duration: 2000 }, { duration: 1000 }, { duration: 500 }]
|
||||||
|
const sorted = [{ duration: 1000 }, { duration: 500 }, { duration: 2000 }, { duration: 3000 }]
|
||||||
|
runSearchStub.withArgs(t, a, provider).resolves(unsorted)
|
||||||
|
|
||||||
|
it('returns results sorted by library item duration diff', async () => {
|
||||||
|
expect(await bookFinder.search(libraryItem, provider, t, a)).to.deep.equal(sorted)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns unsorted results if library item is null', async () => {
|
||||||
|
expect(await bookFinder.search(null, provider, t, a)).to.deep.equal(unsorted)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns unsorted results if library item duration is undefined', async () => {
|
||||||
|
expect(await bookFinder.search({ media: {} }, provider, t, a)).to.deep.equal(unsorted)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns unsorted results if library item media is undefined', async () => {
|
||||||
|
expect(await bookFinder.search({ }, provider, t, a)).to.deep.equal(unsorted)
|
||||||
|
})
|
||||||
|
|
||||||
|
it ('should return a result last if it has no duration', async () => {
|
||||||
|
const unsorted = [{}, { duration: 3000 }, { duration: 2000 }, { duration: 1000 }, { duration: 500 }]
|
||||||
|
const sorted = [{ duration: 1000 }, { duration: 500 }, { duration: 2000 }, { duration: 3000 }, {}]
|
||||||
|
runSearchStub.withArgs(t, a, provider).resolves(unsorted)
|
||||||
|
|
||||||
|
expect(await bookFinder.search(libraryItem, provider, t, a)).to.deep.equal(sorted)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue