mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-12-31 11:38:47 -05:00
Compare commits
87 Commits
sanitize_h
...
experiment
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a992400d6a | ||
|
|
108b2a60f5 | ||
|
|
af684e6a69 | ||
|
|
5336d0525e | ||
|
|
bb4eec9355 | ||
|
|
28404f37b8 | ||
|
|
7b92c15a46 | ||
|
|
c150ed4e98 | ||
|
|
cb7632b216 | ||
|
|
b8849677de | ||
|
|
9bf8d7de11 | ||
|
|
6634ce8fd4 | ||
|
|
9d4303ef7b | ||
|
|
1f7be58124 | ||
|
|
6b8b27b04f | ||
|
|
ba4061e5a4 | ||
|
|
693dc00fa3 | ||
|
|
f3f5f3b9bd | ||
|
|
b515c6c746 | ||
|
|
35e196238a | ||
|
|
2dc93258f1 | ||
|
|
5123f7d240 | ||
|
|
06d3bd76a8 | ||
|
|
52196afd99 | ||
|
|
3e44ee6f50 | ||
|
|
9841826e10 | ||
|
|
def93d18ec | ||
|
|
387a3d05b4 | ||
|
|
398d04fc08 | ||
|
|
c5e5e516af | ||
|
|
1c6f99b876 | ||
|
|
d0af82e71a | ||
|
|
76e7616439 | ||
|
|
fe99a269bc | ||
|
|
5315f65023 | ||
|
|
c2809808c3 | ||
|
|
204ac4f204 | ||
|
|
accd5d1096 | ||
|
|
5025c6a3ea | ||
|
|
6d0d1415e4 | ||
|
|
514f5c2409 | ||
|
|
2cc58b2c8a | ||
|
|
777a055fcd | ||
|
|
b45085d2d6 | ||
|
|
22f6e86a12 | ||
|
|
dc6783ea76 | ||
|
|
a6f10ca48e | ||
|
|
aac01d6d9a | ||
|
|
a617994207 | ||
|
|
7a33a412fc | ||
|
|
0135b3560c | ||
|
|
6968a5c02a | ||
|
|
5e2bb0b12c | ||
|
|
7122756e58 | ||
|
|
8ecc912c2d | ||
|
|
c8cea4e6af | ||
|
|
0c5d05d319 | ||
|
|
4a3eb7727b | ||
|
|
81640464ba | ||
|
|
eda7036f70 | ||
|
|
e669a8d378 | ||
|
|
8e01859075 | ||
|
|
f0525d4f0d | ||
|
|
84c9c6cb50 | ||
|
|
346df3680c | ||
|
|
6aa7c8a3d8 | ||
|
|
704c6f7bde | ||
|
|
f01055f6e6 | ||
|
|
759c58d3f7 | ||
|
|
357176b301 | ||
|
|
9bb4dc3ab0 | ||
|
|
709c33f27a | ||
|
|
4d846e225a | ||
|
|
5dc6d613bd | ||
|
|
63ccdb68f0 | ||
|
|
424ef1aec3 | ||
|
|
b6995ba5d1 | ||
|
|
9968743a93 | ||
|
|
c377b57601 | ||
|
|
262d0b46e3 | ||
|
|
32fc4f6555 | ||
|
|
81572adab6 | ||
|
|
1ad2e71fd5 | ||
|
|
db66b9eaeb | ||
|
|
28c2e62e61 | ||
|
|
96401c377c | ||
|
|
9d45880b37 |
@@ -57,7 +57,7 @@ WORKDIR /app
|
||||
# Copy compiled frontend and server from build stages
|
||||
COPY --from=build-client /client/dist /app/client/dist
|
||||
COPY --from=build-server /server /app
|
||||
COPY --from=build-server /usr/local/lib/nusqlite3 /usr/local/lib/nusqlite3
|
||||
COPY --from=build-server ${NUSQLITE3_PATH} ${NUSQLITE3_PATH}
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
|
||||
@@ -94,6 +94,9 @@ export default {
|
||||
userIsAdminOrUp() {
|
||||
return this.$store.getters['user/getIsAdminOrUp']
|
||||
},
|
||||
userCanAccessExplicitContent() {
|
||||
return this.$store.getters['user/getUserCanAccessExplicitContent']
|
||||
},
|
||||
libraryMediaType() {
|
||||
return this.$store.getters['libraries/getCurrentLibraryMediaType']
|
||||
},
|
||||
@@ -239,6 +242,15 @@ export default {
|
||||
sublist: false
|
||||
}
|
||||
]
|
||||
|
||||
if (this.userCanAccessExplicitContent) {
|
||||
items.push({
|
||||
text: this.$strings.LabelExplicit,
|
||||
value: 'explicit',
|
||||
sublist: false
|
||||
})
|
||||
}
|
||||
|
||||
if (this.userIsAdminOrUp) {
|
||||
items.push({
|
||||
text: this.$strings.LabelShareOpen,
|
||||
@@ -249,7 +261,7 @@ export default {
|
||||
return items
|
||||
},
|
||||
podcastItems() {
|
||||
return [
|
||||
const items = [
|
||||
{
|
||||
text: this.$strings.LabelAll,
|
||||
value: 'all'
|
||||
@@ -283,6 +295,16 @@ export default {
|
||||
sublist: false
|
||||
}
|
||||
]
|
||||
|
||||
if (this.userCanAccessExplicitContent) {
|
||||
items.push({
|
||||
text: this.$strings.LabelExplicit,
|
||||
value: 'explicit',
|
||||
sublist: false
|
||||
})
|
||||
}
|
||||
|
||||
return items
|
||||
},
|
||||
selectItems() {
|
||||
if (this.isSeries) return this.seriesItems
|
||||
|
||||
@@ -35,7 +35,14 @@
|
||||
<widgets-podcast-type-indicator :type="episode.episodeType" />
|
||||
</div>
|
||||
<p v-if="episode.subtitle" class="mb-1 text-sm text-gray-300 line-clamp-2">{{ episode.subtitle }}</p>
|
||||
<p class="text-xs text-gray-300">Published {{ episode.publishedAt ? $dateDistanceFromNow(episode.publishedAt) : 'Unknown' }}</p>
|
||||
<div class="flex items-center space-x-2">
|
||||
<!-- published -->
|
||||
<p class="text-xs text-gray-300 w-40">Published {{ episode.publishedAt ? $dateDistanceFromNow(episode.publishedAt) : 'Unknown' }}</p>
|
||||
<!-- duration -->
|
||||
<p v-if="episode.durationSeconds && !isNaN(episode.durationSeconds)" class="text-xs text-gray-300 min-w-28">{{ $strings.LabelDuration }}: {{ $elapsedPretty(episode.durationSeconds) }}</p>
|
||||
<!-- size -->
|
||||
<p v-if="episode.enclosure?.length && !isNaN(episode.enclosure.length) && Number(episode.enclosure.length) > 0" class="text-xs text-gray-300">{{ $strings.LabelSize }}: {{ $bytesPretty(Number(episode.enclosure.length)) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<p dir="auto" class="text-lg font-semibold mb-6">{{ title }}</p>
|
||||
<div v-if="description" dir="auto" class="default-style less-spacing" v-html="description" />
|
||||
<div v-if="description" dir="auto" class="default-style less-spacing" @click="handleDescriptionClick" v-html="description" />
|
||||
<p v-else class="mb-2">{{ $strings.MessageNoDescription }}</p>
|
||||
|
||||
<div class="w-full h-px bg-white/5 my-4" />
|
||||
@@ -34,6 +34,12 @@
|
||||
{{ audioFileSize }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="grow">
|
||||
<p class="font-semibold text-xs mb-1">{{ $strings.LabelDuration }}</p>
|
||||
<p class="mb-2 text-xs">
|
||||
{{ audioFileDuration }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</modals-modal>
|
||||
@@ -68,7 +74,7 @@ export default {
|
||||
return this.episode.title || 'No Episode Title'
|
||||
},
|
||||
description() {
|
||||
return this.episode.description || ''
|
||||
return this.parseDescription(this.episode.description || '')
|
||||
},
|
||||
media() {
|
||||
return this.libraryItem?.media || {}
|
||||
@@ -90,11 +96,49 @@ export default {
|
||||
|
||||
return this.$bytesPretty(size)
|
||||
},
|
||||
audioFileDuration() {
|
||||
const duration = this.episode.duration || 0
|
||||
return this.$elapsedPretty(duration)
|
||||
},
|
||||
bookCoverAspectRatio() {
|
||||
return this.$store.getters['libraries/getBookCoverAspectRatio']
|
||||
}
|
||||
},
|
||||
methods: {},
|
||||
methods: {
|
||||
handleDescriptionClick(e) {
|
||||
if (e.target.matches('span.time-marker')) {
|
||||
const time = parseInt(e.target.dataset.time)
|
||||
if (!isNaN(time)) {
|
||||
this.$eventBus.$emit('play-item', {
|
||||
episodeId: this.episodeId,
|
||||
libraryItemId: this.libraryItem.id,
|
||||
startTime: time
|
||||
})
|
||||
}
|
||||
e.preventDefault()
|
||||
}
|
||||
},
|
||||
parseDescription(description) {
|
||||
const timeMarkerLinkRegex = /<a href="#([^"]*?\b\d{1,2}:\d{1,2}(?::\d{1,2})?)">(.*?)<\/a>/g
|
||||
const timeMarkerRegex = /\b\d{1,2}:\d{1,2}(?::\d{1,2})?\b/g
|
||||
|
||||
function convertToSeconds(time) {
|
||||
const timeParts = time.split(':').map(Number)
|
||||
return timeParts.reduce((acc, part, index) => acc * 60 + part, 0)
|
||||
}
|
||||
|
||||
return description
|
||||
.replace(timeMarkerLinkRegex, (match, href, displayTime) => {
|
||||
const time = displayTime.match(timeMarkerRegex)[0]
|
||||
const seekTimeInSeconds = convertToSeconds(time)
|
||||
return `<span class="time-marker cursor-pointer text-blue-400 hover:text-blue-300" data-time="${seekTimeInSeconds}">${displayTime}</span>`
|
||||
})
|
||||
.replace(timeMarkerRegex, (match) => {
|
||||
const seekTimeInSeconds = convertToSeconds(match)
|
||||
return `<span class="time-marker cursor-pointer text-blue-400 hover:text-blue-300" data-time="${seekTimeInSeconds}">${match}</span>`
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -318,10 +318,8 @@ export default {
|
||||
}
|
||||
},
|
||||
handleAttachmentAdd(event) {
|
||||
// Prevent pasting in images from the browser
|
||||
if (!event.attachment.file) {
|
||||
event.attachment.remove()
|
||||
}
|
||||
// Prevent pasting in images/any files from the browser
|
||||
event.attachment.remove()
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
||||
@@ -143,10 +143,18 @@ export default {
|
||||
localStorage.setItem('embedMetadataCodec', val)
|
||||
},
|
||||
getEncodingOptions() {
|
||||
return {
|
||||
codec: this.selectedCodec || 'aac',
|
||||
bitrate: this.selectedBitrate || '128k',
|
||||
channels: this.selectedChannels || 2
|
||||
if (this.showAdvancedView) {
|
||||
return {
|
||||
codec: this.customCodec || this.selectedCodec || 'aac',
|
||||
bitrate: this.customBitrate || this.selectedBitrate || '128k',
|
||||
channels: this.customChannels || this.selectedChannels || 2
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
codec: this.selectedCodec || 'aac',
|
||||
bitrate: this.selectedBitrate || '128k',
|
||||
channels: this.selectedChannels || 2
|
||||
}
|
||||
}
|
||||
},
|
||||
setPreset() {
|
||||
|
||||
@@ -248,4 +248,4 @@ export default {
|
||||
transform: scale(0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -109,4 +109,4 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
4
client/package-lock.json
generated
4
client/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "audiobookshelf-client",
|
||||
"version": "2.23.0",
|
||||
"version": "2.25.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "audiobookshelf-client",
|
||||
"version": "2.23.0",
|
||||
"version": "2.25.1",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@nuxtjs/axios": "^5.13.6",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "audiobookshelf-client",
|
||||
"version": "2.23.0",
|
||||
"version": "2.25.1",
|
||||
"buildNumber": 1,
|
||||
"description": "Self-hosted audiobook and podcast client",
|
||||
"main": "index.js",
|
||||
|
||||
@@ -28,14 +28,14 @@
|
||||
<div class="flex justify-center flex-wrap lg:flex-nowrap gap-4">
|
||||
<div class="w-full max-w-2xl border border-white/10 bg-bg">
|
||||
<div class="flex py-2 px-4">
|
||||
<div class="w-1/3 text-xs font-semibold uppercase text-gray-200">{{ $strings.LabelMetaTag }}</div>
|
||||
<div class="w-2/3 text-xs font-semibold uppercase text-gray-200">{{ $strings.LabelValue }}</div>
|
||||
<div class="w-28 min-w-28 text-xs font-semibold uppercase text-gray-200">{{ $strings.LabelMetaTag }}</div>
|
||||
<div class="grow text-xs font-semibold uppercase text-gray-200">{{ $strings.LabelValue }}</div>
|
||||
</div>
|
||||
<div class="w-full max-h-72 overflow-auto">
|
||||
<template v-for="(value, key, index) in metadataObject">
|
||||
<div :key="key" class="flex py-1 px-4 text-sm" :class="index % 2 === 0 ? 'bg-primary/25' : ''">
|
||||
<div class="w-1/3 font-semibold">{{ key }}</div>
|
||||
<div class="w-2/3">
|
||||
<div class="w-28 min-w-28 font-semibold">{{ key }}</div>
|
||||
<div class="grow">
|
||||
{{ value }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -45,18 +45,18 @@
|
||||
<div class="w-full max-w-2xl border border-white/10 bg-bg">
|
||||
<div class="flex py-2 px-4 bg-primary/25">
|
||||
<div class="grow text-xs font-semibold uppercase text-gray-200">{{ $strings.LabelChapterTitle }}</div>
|
||||
<div class="w-24 text-xs font-semibold uppercase text-gray-200">{{ $strings.LabelStart }}</div>
|
||||
<div class="w-24 text-xs font-semibold uppercase text-gray-200">{{ $strings.LabelEnd }}</div>
|
||||
<div class="w-16 min-w-16 text-xs font-semibold uppercase text-gray-200">{{ $strings.LabelStart }}</div>
|
||||
<div class="w-16 min-w-16 text-xs font-semibold uppercase text-gray-200">{{ $strings.LabelEnd }}</div>
|
||||
</div>
|
||||
<div class="w-full max-h-72 overflow-auto">
|
||||
<p v-if="!metadataChapters.length" class="py-5 text-center text-gray-200">{{ $strings.MessageNoChapters }}</p>
|
||||
<template v-for="(chapter, index) in metadataChapters">
|
||||
<div :key="index" class="flex py-1 px-4 text-sm" :class="index % 2 === 1 ? 'bg-primary/25' : ''">
|
||||
<div class="grow font-semibold">{{ chapter.title }}</div>
|
||||
<div class="w-24">
|
||||
<div class="w-16 min-w-16">
|
||||
{{ $secondsToTimestamp(chapter.start) }}
|
||||
</div>
|
||||
<div class="w-24">
|
||||
<div class="w-16 min-w-16">
|
||||
{{ $secondsToTimestamp(chapter.end) }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -356,6 +356,8 @@ export default {
|
||||
|
||||
const encodeOptions = this.$refs.encoderOptionsCard.getEncodingOptions()
|
||||
|
||||
this.encodingOptions = encodeOptions
|
||||
|
||||
const queryParams = new URLSearchParams(encodeOptions)
|
||||
|
||||
this.processing = true
|
||||
|
||||
@@ -58,6 +58,9 @@ export const getters = {
|
||||
getUserCanAccessAllLibraries: (state) => {
|
||||
return !!state.user?.permissions?.accessAllLibraries
|
||||
},
|
||||
getUserCanAccessExplicitContent: (state) => {
|
||||
return !!state.user?.permissions?.accessExplicitContent
|
||||
},
|
||||
getLibrariesAccessible: (state, getters) => {
|
||||
if (!state.user) return []
|
||||
if (getters.getUserCanAccessAllLibraries) return []
|
||||
|
||||
@@ -514,7 +514,7 @@
|
||||
"LabelPublishers": "الناشرون",
|
||||
"LabelRSSFeedCustomOwnerEmail": "البريد الالكتروني المخصص للمالك",
|
||||
"LabelRSSFeedCustomOwnerName": "الاسم المخصص للمالك",
|
||||
"LabelRSSFeedOpen": "فتح تغذية RSS",
|
||||
"LabelRSSFeedOpen": "موجز RSS مفتوح",
|
||||
"LabelRSSFeedPreventIndexing": "منع الفهرسة",
|
||||
"LabelRSSFeedSlug": "اسم تعريف تغذية RSS",
|
||||
"LabelRSSFeedURL": "رابط تغذية RSS",
|
||||
@@ -918,6 +918,8 @@
|
||||
"NotificationOnBackupCompletedDescription": "يتم تشغيله عند اكتمال النسخ الاحتياطي",
|
||||
"NotificationOnBackupFailedDescription": "يتم تشغيله عند فشل النسخ الاحتياطي",
|
||||
"NotificationOnEpisodeDownloadedDescription": "يتم تشغيله عند تنزيل حلقة بودكاست تلقائيًا",
|
||||
"NotificationOnRSSFeedDisabledDescription": "يتم تشغيله عندما يتم تعطيل تنزيلات الحلقة التلقائية بسبب الكثير من المحاولات الفاشلة",
|
||||
"NotificationOnRSSFeedFailedDescription": "يتم تشغيله عند فشل طلب تغذية RSS في تنزيل حلقة تلقائية",
|
||||
"NotificationOnTestDescription": "حدث لاختبار نظام الإشعارات",
|
||||
"PlaceholderNewCollection": "اسم المجموعة الجديدة",
|
||||
"PlaceholderNewFolderPath": "مسار المجلد الجديد",
|
||||
|
||||
@@ -154,7 +154,7 @@
|
||||
"HeaderListeningSessions": "Poslechové relace",
|
||||
"HeaderListeningStats": "Statistiky poslechu",
|
||||
"HeaderLogin": "Přihlásit",
|
||||
"HeaderLogs": "Záznamy",
|
||||
"HeaderLogs": "Logy",
|
||||
"HeaderManageGenres": "Spravovat žánry",
|
||||
"HeaderManageTags": "Spravovat štítky",
|
||||
"HeaderMapDetails": "Podrobnosti mapování",
|
||||
@@ -177,6 +177,7 @@
|
||||
"HeaderPlaylist": "Seznam skladeb",
|
||||
"HeaderPlaylistItems": "Položky seznamu přehrávání",
|
||||
"HeaderPodcastsToAdd": "Podcasty k přidání",
|
||||
"HeaderPresets": "Předvolba",
|
||||
"HeaderPreviewCover": "Náhled obálky",
|
||||
"HeaderRSSFeedGeneral": "Podrobnosti o RSS",
|
||||
"HeaderRSSFeedIsOpen": "Informační kanál RSS je otevřený",
|
||||
@@ -513,9 +514,9 @@
|
||||
"LabelPublishers": "Vydavatelé",
|
||||
"LabelRSSFeedCustomOwnerEmail": "Vlastní e-mail vlastníka",
|
||||
"LabelRSSFeedCustomOwnerName": "Vlastní jméno vlastníka",
|
||||
"LabelRSSFeedOpen": "Otevření RSS kanálu",
|
||||
"LabelRSSFeedOpen": "RSS kanál otevřen",
|
||||
"LabelRSSFeedPreventIndexing": "Zabránit indexování",
|
||||
"LabelRSSFeedSlug": "RSS kanál Slug",
|
||||
"LabelRSSFeedSlug": "Klíčové slovo kanálu RSS",
|
||||
"LabelRSSFeedURL": "URL RSS kanálu",
|
||||
"LabelRandomly": "Náhodně",
|
||||
"LabelReAddSeriesToContinueListening": "Znovu přidat sérii k pokračování poslechu",
|
||||
@@ -530,6 +531,7 @@
|
||||
"LabelReleaseDate": "Datum vydání",
|
||||
"LabelRemoveAllMetadataAbs": "Odebrat všechny soubory metadata.abs",
|
||||
"LabelRemoveAllMetadataJson": "Smazat všechny soubory metadata.json",
|
||||
"LabelRemoveAudibleBranding": "Odebrat úvod a závěr Audible z kapitol",
|
||||
"LabelRemoveCover": "Odstranit obálku",
|
||||
"LabelRemoveMetadataFile": "Odstranit soubory metadat ve složkách položek knihovny",
|
||||
"LabelRemoveMetadataFileHelp": "Odstraníte všechny soubory metadata.json a metadata.abs ve svých složkách {0}.",
|
||||
@@ -549,7 +551,7 @@
|
||||
"LabelSeries": "Série",
|
||||
"LabelSeriesName": "Název série",
|
||||
"LabelSeriesProgress": "Průběh série",
|
||||
"LabelServerLogLevel": "Úroveň protokolu serveru",
|
||||
"LabelServerLogLevel": "Úroveň Logování serveru",
|
||||
"LabelServerYearReview": "Přehled roku na serveru ({0})",
|
||||
"LabelSetEbookAsPrimary": "Nastavit jako primární",
|
||||
"LabelSetEbookAsSupplementary": "Nastavit jako doplňkové",
|
||||
@@ -705,6 +707,8 @@
|
||||
"LabelYourProgress": "Váš pokrok",
|
||||
"MessageAddToPlayerQueue": "Přidat do fronty přehrávače",
|
||||
"MessageAppriseDescription": "Abyste mohli používat tuto funkci, musíte mít spuštěnou instanci <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> nebo API, které bude zpracovávat stejné požadavky. <br />Adresa URL API Apprise by měla být úplná URL cesta pro odeslání oznámení, např. pokud je vaše instance API obsluhována na adrese <code>http://192.168.1.1:8337</code> pak byste měli zadat <code>http://192.168.1.1:8337/notify</code>.",
|
||||
"MessageAsinCheck": "Ujistěte se, že používáte ASIN ze správného regionu Audible a ne z Amazonu.",
|
||||
"MessageAuthenticationOIDCChangesRestart": "Po uložení restartujte server, aby se změny OIDC použily.",
|
||||
"MessageBackupsDescription": "Zálohy zahrnují uživatele, průběh uživatele, podrobnosti o položkách knihovny, nastavení serveru a obrázky uložené v <code>/metadata/items</code> a <code>/metadata/authors</code>. Zálohy <strong>ne</strong> zahrnují všechny soubory uložené ve složkách knihovny.",
|
||||
"MessageBackupsLocationEditNote": "Poznámka: Změna umístění záloh nepřesune ani nezmění existující zálohy",
|
||||
"MessageBackupsLocationNoEditNote": "Poznámka: Umístění záloh je nastavené z proměnných prostředí a nelze zde změnit.",
|
||||
@@ -723,6 +727,7 @@
|
||||
"MessageChapterErrorStartGteDuration": "Neplatný čas začátku, musí být kratší než doba trvání audioknihy",
|
||||
"MessageChapterErrorStartLtPrev": "Neplatný čas začátku, musí být větší nebo roven času začátku předchozí kapitoly",
|
||||
"MessageChapterStartIsAfter": "Začátek kapitoly přesahuje konec audioknihy",
|
||||
"MessageChaptersNotFound": "Kapitoly nenalezeny",
|
||||
"MessageCheckingCron": "Kontrola cronu...",
|
||||
"MessageConfirmCloseFeed": "Opravdu chcete zavřít tento kanál?",
|
||||
"MessageConfirmDeleteBackup": "Opravdu chcete smazat zálohu pro {0}?",
|
||||
@@ -779,12 +784,13 @@
|
||||
"MessageForceReScanDescription": "znovu prohledá všechny soubory jako při novém skenování. ID3 tagy zvukových souborů OPF soubory a textové soubory budou skenovány jako nové.",
|
||||
"MessageImportantNotice": "Důležité upozornění!",
|
||||
"MessageInsertChapterBelow": "Vložit kapitolu níže",
|
||||
"MessageInvalidAsin": "Neplatný ASIN",
|
||||
"MessageItemsSelected": "{0} vybraných položek",
|
||||
"MessageItemsUpdated": "{0} položky byly aktualizovány",
|
||||
"MessageJoinUsOn": "Přidejte se k nám",
|
||||
"MessageLoading": "Načítá se...",
|
||||
"MessageLoadingFolders": "Načítám složky...",
|
||||
"MessageLogsDescription": "Protokoly se ukládají do souborů JSON v <code>/metadata/logs</code>. Protokoly o pádech jsou uloženy v <code>/metadata/logs/crash_logs.txt</code>.",
|
||||
"MessageLogsDescription": "Logy se ukládají do souborů JSON v <code>/metadata/logs</code>. Logy o pádech jsou uloženy v <code>/metadata/logs/crash_logs.txt</code>.",
|
||||
"MessageM4BFailed": "M4B se nezdařil!",
|
||||
"MessageM4BFinished": "M4B dokončen!",
|
||||
"MessageMapChapterTitles": "Mapování názvů kapitol ke stávajícím kapitolám audioknihy bez úpravy časových razítek",
|
||||
@@ -808,11 +814,11 @@
|
||||
"MessageNoEpisodes": "Žádné epizody",
|
||||
"MessageNoFoldersAvailable": "Nejsou k dispozici žádné složky",
|
||||
"MessageNoGenres": "Žádné žánry",
|
||||
"MessageNoIssues": "Žádné výtisk",
|
||||
"MessageNoIssues": "Žádné problémy",
|
||||
"MessageNoItems": "Žádné položky",
|
||||
"MessageNoItemsFound": "Nebyly nalezeny žádné položky",
|
||||
"MessageNoListeningSessions": "Žádné poslechové relace",
|
||||
"MessageNoLogs": "Žádné protokoly",
|
||||
"MessageNoLogs": "Žádné logy",
|
||||
"MessageNoMediaProgress": "Žádný průběh médií",
|
||||
"MessageNoNotifications": "Žádná oznámení",
|
||||
"MessageNoPodcastFeed": "Neplatný podcast: Žádný kanál",
|
||||
@@ -850,6 +856,7 @@
|
||||
"MessageScheduleRunEveryWeekdayAtTime": "Spusť každý {0} v {1}",
|
||||
"MessageSearchResultsFor": "Výsledky hledání pro",
|
||||
"MessageSelected": "{0} vybráno",
|
||||
"MessageSeriesSequenceCannotContainSpaces": "Sekvence série nesmí obsahovat mezery",
|
||||
"MessageServerCouldNotBeReached": "Server je nedostupný",
|
||||
"MessageSetChaptersFromTracksDescription": "Nastavit kapitoly jako kapitolu a název kapitoly jako název zvukového souboru",
|
||||
"MessageShareExpirationWillBe": "Expiruje <strong>{0}</strong>",
|
||||
@@ -968,6 +975,8 @@
|
||||
"ToastCachePurgeFailed": "Nepodařilo se vyčistit mezipaměť",
|
||||
"ToastCachePurgeSuccess": "Vyrovnávací paměť úspěšně vyčištěna",
|
||||
"ToastChaptersHaveErrors": "Kapitoly obsahují chyby",
|
||||
"ToastChaptersInvalidShiftAmountLast": "Nesprávná délka posunu. Čas začátku poslední kapitoly by přesáhl dobu trvání této audioknihy.",
|
||||
"ToastChaptersInvalidShiftAmountStart": "Nesprávná délka posunu. První kapitola by měla nulovou nebo zápornou délku a byla by přepsána druhou kapitolou. Zvětšete počáteční délku druhé kapitoly.",
|
||||
"ToastChaptersMustHaveTitles": "Kapitoly musí mít názvy",
|
||||
"ToastChaptersRemoved": "Kapitoly odstraněny",
|
||||
"ToastChaptersUpdated": "Kapitola aktualizována",
|
||||
@@ -1088,7 +1097,7 @@
|
||||
"ToastUnlinkOpenIdFailed": "Chyba při odpárování uživatele z OpenID",
|
||||
"ToastUnlinkOpenIdSuccess": "Uživatel odpárován z uživatele z OpenID",
|
||||
"ToastUploaderFilepathExistsError": "Soubor \"{0}\" na serveru již existuje",
|
||||
"ToastUploaderItemExistsInSubdirectoryError": "Položka \"{0}\" používá podsložku nahrávané cesty.",
|
||||
"ToastUploaderItemExistsInSubdirectoryError": "Položka \"{0}\" používá podadresář cesty pro nahrání.",
|
||||
"ToastUserDeleteFailed": "Nepodařilo se smazat uživatele",
|
||||
"ToastUserDeleteSuccess": "Uživatel smazán",
|
||||
"ToastUserPasswordChangeSuccess": "Heslo bylo změněno úspěšně",
|
||||
|
||||
@@ -513,7 +513,7 @@
|
||||
"LabelPublishers": "Forlag",
|
||||
"LabelRSSFeedCustomOwnerEmail": "Brugerdefineret ejerens e-mail",
|
||||
"LabelRSSFeedCustomOwnerName": "Brugerdefineret ejerens navn",
|
||||
"LabelRSSFeedOpen": "Åben RSS-feed",
|
||||
"LabelRSSFeedOpen": "RSS-feed åbent",
|
||||
"LabelRSSFeedPreventIndexing": "Forhindrer indeksering",
|
||||
"LabelRSSFeedSlug": "RSS-feed-slug",
|
||||
"LabelRSSFeedURL": "RSS-feed-URL",
|
||||
|
||||
@@ -514,7 +514,7 @@
|
||||
"LabelPublishers": "Herausgeber",
|
||||
"LabelRSSFeedCustomOwnerEmail": "Benutzerdefinierte Eigentümer-E-Mail",
|
||||
"LabelRSSFeedCustomOwnerName": "Benutzerdefinierter Name des Eigentümers",
|
||||
"LabelRSSFeedOpen": "RSS Feed offen",
|
||||
"LabelRSSFeedOpen": "RSS Feed öffnen",
|
||||
"LabelRSSFeedPreventIndexing": "Indizierung verhindern",
|
||||
"LabelRSSFeedSlug": "RSS-Feed-Schlagwort",
|
||||
"LabelRSSFeedURL": "RSS-Feed-URL",
|
||||
@@ -858,7 +858,7 @@
|
||||
"MessageSelected": "{0} ausgewählt",
|
||||
"MessageSeriesSequenceCannotContainSpaces": "Serie Abfolge kann keine Leerzeichen enthalten",
|
||||
"MessageServerCouldNotBeReached": "Server kann nicht erreicht werden",
|
||||
"MessageSetChaptersFromTracksDescription": "Kaitelerstellung basiert auf den existierenden einzelnen Audiodateien. Pro existierende Audiodatei wird 1 Kapitel erstellt, wobei deren Kapitelname aus dem Audiodateinamen extrahiert wird",
|
||||
"MessageSetChaptersFromTracksDescription": "Kapitelerstellung basiert auf den existierenden einzelnen Audiodateien. Pro existierende Audiodatei wird 1 Kapitel erstellt, wobei deren Kapitelname aus dem Audiodateinamen extrahiert wird",
|
||||
"MessageShareExpirationWillBe": "Läuft am <strong>{0}</strong> ab",
|
||||
"MessageShareExpiresIn": "Läuft in {0} ab",
|
||||
"MessageShareURLWillBe": "Der Freigabe Link wird <strong>{0}</strong> sein",
|
||||
|
||||
@@ -918,6 +918,8 @@
|
||||
"NotificationOnBackupCompletedDescription": "Triggered when a backup is completed",
|
||||
"NotificationOnBackupFailedDescription": "Triggered when a backup fails",
|
||||
"NotificationOnEpisodeDownloadedDescription": "Triggered when a podcast episode is auto-downloaded",
|
||||
"NotificationOnRSSFeedDisabledDescription": "Triggered when automatic episode downloads are disabled due to too many failed attempts",
|
||||
"NotificationOnRSSFeedFailedDescription": "Triggered when the RSS feed request fails for an automatic episode download",
|
||||
"NotificationOnTestDescription": "Event for testing the notification system",
|
||||
"PlaceholderNewCollection": "New collection name",
|
||||
"PlaceholderNewFolderPath": "New folder path",
|
||||
|
||||
@@ -530,6 +530,7 @@
|
||||
"LabelReleaseDate": "Date de parution",
|
||||
"LabelRemoveAllMetadataAbs": "Supprimer tous les fichiers metadata.abs",
|
||||
"LabelRemoveAllMetadataJson": "Supprimer tous les fichiers metadata.json",
|
||||
"LabelRemoveAudibleBranding": "Supprimer l’intro et la fin Audible des chapitres",
|
||||
"LabelRemoveCover": "Supprimer la couverture",
|
||||
"LabelRemoveMetadataFile": "Supprimer les fichiers de métadonnées dans les dossiers des éléments de la bibliothèque",
|
||||
"LabelRemoveMetadataFileHelp": "Supprimer tous les fichiers metadata.json et metadata.abs de vos dossiers {0}.",
|
||||
@@ -705,6 +706,8 @@
|
||||
"LabelYourProgress": "Votre progression",
|
||||
"MessageAddToPlayerQueue": "Ajouter en file d’attente",
|
||||
"MessageAppriseDescription": "Nécessite une instance d’<a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">API Apprise</a> pour utiliser cette fonctionnalité ou une api qui prend en charge les mêmes requêtes.<br />L’URL de l’API Apprise doit comprendre le chemin complet pour envoyer la notification. Par exemple, si votre instance écoute sur <code>http://192.168.1.1:8337</code> alors vous devez mettre <code>http://192.168.1.1:8337/notify</code>.",
|
||||
"MessageAsinCheck": "Assurez-vous d’utiliser l’ASIN de la bonne région Audible, et non d’Amazon.",
|
||||
"MessageAuthenticationOIDCChangesRestart": "Redémarrez votre serveur après avoir enregistré pour appliquer les modifications OIDC.",
|
||||
"MessageBackupsDescription": "Les sauvegardes incluent les utilisateurs, la progression des utilisateurs, les détails des éléments de la bibliothèque, les paramètres du serveur et les images stockées dans <code>/metadata/items</code> & <code>/metadata/authors</code>. Les sauvegardes <strong>n’incluent pas</strong> les fichiers stockés dans les dossiers de votre bibliothèque.",
|
||||
"MessageBackupsLocationEditNote": "Remarque : Mettre à jour l'emplacement de sauvegarde ne déplacera pas ou ne modifiera pas les sauvegardes existantes",
|
||||
"MessageBackupsLocationNoEditNote": "Remarque : l’emplacement de sauvegarde est défini via une variable d’environnement et ne peut pas être modifié ici.",
|
||||
@@ -723,6 +726,7 @@
|
||||
"MessageChapterErrorStartGteDuration": "Horodatage invalide car il doit débuter avant la fin du livre",
|
||||
"MessageChapterErrorStartLtPrev": "Horodatage invalide car il doit débuter au moins après le précédent chapitre",
|
||||
"MessageChapterStartIsAfter": "Le premier chapitre est situé au début de votre livre audio",
|
||||
"MessageChaptersNotFound": "Chapitres non trouvés",
|
||||
"MessageCheckingCron": "Vérification du cron…",
|
||||
"MessageConfirmCloseFeed": "Êtes-vous sûr·e de vouloir fermer ce flux ?",
|
||||
"MessageConfirmDeleteBackup": "Êtes-vous sûr·e de vouloir supprimer la sauvegarde de « {0} » ?",
|
||||
@@ -779,6 +783,7 @@
|
||||
"MessageForceReScanDescription": "analysera de nouveau tous les fichiers. Les étiquettes ID3 des fichiers audio, les fichiers OPF et les fichiers texte seront analysés comme s’ils étaient nouveaux.",
|
||||
"MessageImportantNotice": "Information importante !",
|
||||
"MessageInsertChapterBelow": "Insérer le chapitre ci-dessous",
|
||||
"MessageInvalidAsin": "ASIN invalide",
|
||||
"MessageItemsSelected": "{0} éléments sélectionnés",
|
||||
"MessageItemsUpdated": "{0} éléments mis à jour",
|
||||
"MessageJoinUsOn": "Rejoignez-nous sur",
|
||||
@@ -850,6 +855,7 @@
|
||||
"MessageScheduleRunEveryWeekdayAtTime": "Exécuté tous les {0} à {1}",
|
||||
"MessageSearchResultsFor": "Résultats de recherche pour",
|
||||
"MessageSelected": "{0} sélectionnés",
|
||||
"MessageSeriesSequenceCannotContainSpaces": "La séquence de séries ne peut pas contenir d’espaces",
|
||||
"MessageServerCouldNotBeReached": "Serveur inaccessible",
|
||||
"MessageSetChaptersFromTracksDescription": "Positionne un chapitre par fichier audio, avec le titre du fichier comme titre de chapitre",
|
||||
"MessageShareExpirationWillBe": "Expire le <strong>{0}</strong>",
|
||||
@@ -968,6 +974,8 @@
|
||||
"ToastCachePurgeFailed": "Échec de la purge du cache",
|
||||
"ToastCachePurgeSuccess": "Cache purgé avec succès",
|
||||
"ToastChaptersHaveErrors": "Les chapitres contiennent des erreurs",
|
||||
"ToastChaptersInvalidShiftAmountLast": "Durée de décalage non valide. L’heure de début du dernier chapitre pourrait dépasser la durée de ce livre audio.",
|
||||
"ToastChaptersInvalidShiftAmountStart": "Durée de décalage non valide. Le premier chapitre aurait une longueur nulle ou négative et serait écrasé par le second. Augmentez la durée de début du second chapitre.",
|
||||
"ToastChaptersMustHaveTitles": "Les chapitre doivent avoir un titre",
|
||||
"ToastChaptersRemoved": "Chapitres supprimés",
|
||||
"ToastChaptersUpdated": "Chapitres mis à jour",
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
"ButtonApplyChapters": "החל פרקים",
|
||||
"ButtonAuthors": "סופרים",
|
||||
"ButtonBack": "חזור",
|
||||
"ButtonBatchEditPopulateFromExisting": "מלא משדות קיימים",
|
||||
"ButtonBatchEditPopulateMapDetails": "מלא פרטי מפה",
|
||||
"ButtonBrowseForFolder": "עיין בתיקייה",
|
||||
"ButtonCancel": "ביטול",
|
||||
"ButtonCancelEncode": "בטל קידוד",
|
||||
@@ -29,7 +31,9 @@
|
||||
"ButtonEdit": "ערוך",
|
||||
"ButtonEditChapters": "ערוך פרקים",
|
||||
"ButtonEditPodcast": "ערוך פודקאסט",
|
||||
"ButtonEnable": "הפעל",
|
||||
"ButtonEnable": "אפשר",
|
||||
"ButtonFireAndFail": "שלח בכישלון",
|
||||
"ButtonFireOnTest": "שלח באירוע בדיקה",
|
||||
"ButtonForceReScan": "סרוק מחדש בכוח",
|
||||
"ButtonFullPath": "נתיב מלא",
|
||||
"ButtonHide": "הסתר",
|
||||
@@ -37,7 +41,7 @@
|
||||
"ButtonIssues": "תקלות",
|
||||
"ButtonJumpBackward": "דלג אחורה",
|
||||
"ButtonJumpForward": "דלג קדימה",
|
||||
"ButtonLatest": "חדש ביותר",
|
||||
"ButtonLatest": "אחרון",
|
||||
"ButtonLibrary": "ספרייה",
|
||||
"ButtonLogout": "התנתק",
|
||||
"ButtonLookup": "חפש",
|
||||
@@ -70,7 +74,7 @@
|
||||
"ButtonReScan": "סרוק מחדש",
|
||||
"ButtonRead": "קרא",
|
||||
"ButtonReadLess": "קרא פחות",
|
||||
"ButtonReadMore": "קרא יותר",
|
||||
"ButtonReadMore": "קרא עוד",
|
||||
"ButtonRefresh": "רענן",
|
||||
"ButtonRemove": "הסר",
|
||||
"ButtonRemoveAll": "הסר הכל",
|
||||
@@ -86,7 +90,9 @@
|
||||
"ButtonSaveTracklist": "שמור רשימת רצועות",
|
||||
"ButtonScan": "סרוק",
|
||||
"ButtonScanLibrary": "סרוק ספרייה",
|
||||
"ButtonSearch": "חפש",
|
||||
"ButtonScrollLeft": "גלול שמאלה",
|
||||
"ButtonScrollRight": "גלול ימינה",
|
||||
"ButtonSearch": "חיפוש",
|
||||
"ButtonSelectFolderPath": "בחר נתיב לתיקייה",
|
||||
"ButtonSeries": "סדרה",
|
||||
"ButtonSetChaptersFromTracks": "קבע פרקים לפי הרצועות",
|
||||
@@ -96,7 +102,7 @@
|
||||
"ButtonStartM4BEncode": "התחל קידוד M4B",
|
||||
"ButtonStartMetadataEmbed": "התחל הטמעת מטא-נתונים",
|
||||
"ButtonStats": "סטטיסטיקות",
|
||||
"ButtonSubmit": "שלח",
|
||||
"ButtonSubmit": "שליחה",
|
||||
"ButtonTest": "בדיקה",
|
||||
"ButtonUnlinkOpenId": "נתק OpenID",
|
||||
"ButtonUpload": "העלה",
|
||||
@@ -122,26 +128,26 @@
|
||||
"HeaderChapters": "פרקים",
|
||||
"HeaderChooseAFolder": "בחר תיקייה",
|
||||
"HeaderCollection": "אוסף",
|
||||
"HeaderCollectionItems": "פריטי אוסף",
|
||||
"HeaderCollectionItems": "פרטי אוסף",
|
||||
"HeaderCover": "כריכה",
|
||||
"HeaderCurrentDownloads": "הורדות נוכחיות",
|
||||
"HeaderCustomMessageOnLogin": "הודעה מותאמת אישית בהתחברות",
|
||||
"HeaderCustomMetadataProviders": "ספקי מטא-נתונים מותאמים אישית",
|
||||
"HeaderDetails": "פרטים",
|
||||
"HeaderDownloadQueue": "תור הורדה",
|
||||
"HeaderEbookFiles": "קבצי ספר אלקטרוני",
|
||||
"HeaderEbookFiles": "קבצי Ebook",
|
||||
"HeaderEmail": "אימייל",
|
||||
"HeaderEmailSettings": "הגדרות אימייל",
|
||||
"HeaderEpisodes": "פרקים",
|
||||
"HeaderEreaderDevices": "התקני קריאה דיגיטליים",
|
||||
"HeaderEreaderSettings": "הגדרות התקני קריאה דיגיטליים",
|
||||
"HeaderEreaderSettings": "הגדרות קורא אלקטרוני",
|
||||
"HeaderFiles": "קבצים",
|
||||
"HeaderFindChapters": "מצא פרקים",
|
||||
"HeaderIgnoredFiles": "קבצים שנתעלמו",
|
||||
"HeaderItemFiles": "קבצי פריט",
|
||||
"HeaderItemMetadataUtils": "כלי מטא-נתונים",
|
||||
"HeaderLastListeningSession": "הפעלת האזנה אחרונה",
|
||||
"HeaderLatestEpisodes": "הפרקים העדכניים ביותר",
|
||||
"HeaderLatestEpisodes": "פרקים אחרונים",
|
||||
"HeaderLibraries": "ספריות",
|
||||
"HeaderLibraryFiles": "קבצי ספרייה",
|
||||
"HeaderLibraryStats": "סטטיסטיקות ספרייה",
|
||||
@@ -171,8 +177,9 @@
|
||||
"HeaderPlaylist": "רשימת השמעה",
|
||||
"HeaderPlaylistItems": "פריטי רשימת השמעה",
|
||||
"HeaderPodcastsToAdd": "פודקאסטים להוספה",
|
||||
"HeaderPresets": "קביעות מוגדרות מראש",
|
||||
"HeaderPreviewCover": "תצוגה מקדימה של כריכה",
|
||||
"HeaderRSSFeedGeneral": "פרטי ערוץ RSS",
|
||||
"HeaderRSSFeedGeneral": "פרטי RSS",
|
||||
"HeaderRSSFeedIsOpen": "ערוץ RSS פתוח",
|
||||
"HeaderRSSFeeds": "ערוצי RSS",
|
||||
"HeaderRemoveEpisode": "הסר פרק",
|
||||
@@ -188,14 +195,15 @@
|
||||
"HeaderSettingsExperimental": "תכונות ניסיוניות",
|
||||
"HeaderSettingsGeneral": "כללי",
|
||||
"HeaderSettingsScanner": "סורק",
|
||||
"HeaderSettingsWebClient": "מערך",
|
||||
"HeaderSleepTimer": "טיימר שינה",
|
||||
"HeaderStatsLargestItems": "הפריטים הגדולים ביותר",
|
||||
"HeaderStatsLongestItems": "הפריטים הארוכים ביותר (בשעות)",
|
||||
"HeaderStatsMinutesListeningChart": "דקות האזנה (בימים האחרונים)",
|
||||
"HeaderStatsRecentSessions": "הפעלות אחרונות",
|
||||
"HeaderStatsMinutesListeningChart": "דקות האזנה (7 ימים אחרונים)",
|
||||
"HeaderStatsRecentSessions": "האזנות אחרונות",
|
||||
"HeaderStatsTop10Authors": "10 היוצרים המובילים",
|
||||
"HeaderStatsTop5Genres": "הז'אנרים המובילים 5",
|
||||
"HeaderTableOfContents": "תוכן העניינים",
|
||||
"HeaderTableOfContents": "תוכן עניינים",
|
||||
"HeaderTools": "כלים",
|
||||
"HeaderUpdateAccount": "עדכן חשבון",
|
||||
"HeaderUpdateAuthor": "עדכן יוצר",
|
||||
@@ -212,15 +220,17 @@
|
||||
"LabelAccountTypeAdmin": "מנהל",
|
||||
"LabelAccountTypeGuest": "אורח",
|
||||
"LabelAccountTypeUser": "משתמש",
|
||||
"LabelActivities": "פעילויות",
|
||||
"LabelActivity": "פעילות",
|
||||
"LabelAddToCollection": "הוסף לאוסף",
|
||||
"LabelAddToCollectionBatch": "הוסף {0} ספרים לאוסף",
|
||||
"LabelAddToPlaylist": "הוסף לרשימת השמעה",
|
||||
"LabelAddToPlaylistBatch": "הוסף {0} פריטים לרשימת השמעה",
|
||||
"LabelAddedAt": "נוסף בתאריך",
|
||||
"LabelAddedAt": "נוסף ב-",
|
||||
"LabelAddedDate": "נוסף ב-{0}",
|
||||
"LabelAdminUsersOnly": "רק מנהלים",
|
||||
"LabelAll": "הכל",
|
||||
"LabelAllEpisodesDownloaded": "כל הפרקים הורדו",
|
||||
"LabelAllUsers": "כל המשתמשים",
|
||||
"LabelAllUsersExcludingGuests": "כל המשתמשים, ללא אורחים",
|
||||
"LabelAllUsersIncludingGuests": "כל המשתמשים כולל אורחים",
|
||||
@@ -230,10 +240,10 @@
|
||||
"LabelAudioBitrate": "קצב סיביות (לדוגמא 128k)",
|
||||
"LabelAudioChannels": "ערוצי קול (1 או 2)",
|
||||
"LabelAudioCodec": "קידוד קול",
|
||||
"LabelAuthor": "יוצר",
|
||||
"LabelAuthorFirstLast": "יוצר (שם פרטי שם משפחה)",
|
||||
"LabelAuthorLastFirst": "יוצר (שם משפחה, שם פרטי)",
|
||||
"LabelAuthors": "יוצרים",
|
||||
"LabelAuthor": "סופר",
|
||||
"LabelAuthorFirstLast": "סופר (שם, משפחה)",
|
||||
"LabelAuthorLastFirst": "סופר (משפחה, שם)",
|
||||
"LabelAuthors": "סופרים",
|
||||
"LabelAutoDownloadEpisodes": "הורד פרקים באופן אוטומטי",
|
||||
"LabelAutoFetchMetadata": "חפש והורד מטא-נתונים באופן אוטומטי",
|
||||
"LabelAutoFetchMetadataHelp": "מחפש ומוריד מטא-נתונים לשדות כותרת, יוצר וסדרה כדי לשפר את תהליך ההעלאה. ייתכן שיהיה צורך להתאים מטא-נתונים נוסף לאחר ההעלאה.",
|
||||
@@ -242,36 +252,48 @@
|
||||
"LabelAutoRegister": "הרשמה אוטומטית",
|
||||
"LabelAutoRegisterDescription": "יצירת משתמשים חדשים אוטומטית לאחר התחברות",
|
||||
"LabelBackToUser": "חזרה למשתמש",
|
||||
"LabelBackupAudioFiles": "גיבוי קבצי שמע",
|
||||
"LabelBackupLocation": "מיקום גיבוי",
|
||||
"LabelBackupsEnableAutomaticBackups": "הפעל גיבויים אוטומטיים",
|
||||
"LabelBackupsEnableAutomaticBackups": "גיבויים אוטומטיים",
|
||||
"LabelBackupsEnableAutomaticBackupsHelp": "גיבויים שמורים ב /metadata/backups",
|
||||
"LabelBackupsMaxBackupSize": "גודל הגיבוי המרבי (בג'יגה-בייט)",
|
||||
"LabelBackupsMaxBackupSize": "גודל הגיבוי המרבי (בג'יגה-בייט) (0 - ללא הגבלה)",
|
||||
"LabelBackupsMaxBackupSizeHelp": "כהגנה על עצמך מפני תצורה שגויה, הגיבויים ייכשלו אם הם יעברו את הגודל שהוגדר.",
|
||||
"LabelBackupsNumberToKeep": "מספר הגיבויים לשמירה",
|
||||
"LabelBackupsNumberToKeepHelp": "רק גיבוי אחד יוסר בכל פעם, לכן אם יש לך כבר יותר מגיבוי אחד יש להסיר אותם באופן ידני.",
|
||||
"LabelBitrate": "קצב סיביות",
|
||||
"LabelBonus": "בונוס",
|
||||
"LabelBooks": "ספרים",
|
||||
"LabelButtonText": "טקסט לחצן",
|
||||
"LabelByAuthor": "על ידי {0}",
|
||||
"LabelChangePassword": "שינוי סיסמה",
|
||||
"LabelChannels": "ערוצים",
|
||||
"LabelChapterCount": "{0} פרקים",
|
||||
"LabelChapterTitle": "כותרת הפרק",
|
||||
"LabelChapters": "פרקים",
|
||||
"LabelChaptersFound": "פרקים שנמצאו",
|
||||
"LabelClickForMoreInfo": "לחץ למידע נוסף",
|
||||
"LabelClickToUseCurrentValue": "לחץ לשימוש בערך הנוכחי",
|
||||
"LabelClosePlayer": "סגור נגן",
|
||||
"LabelCollapseSeries": "צמצום סדרה",
|
||||
"LabelCodec": "Coded",
|
||||
"LabelCollapseSeries": "הסתר סדרה",
|
||||
"LabelCollapseSubSeries": "הסתר תת סדרה",
|
||||
"LabelCollection": "אוסף",
|
||||
"LabelCollections": "אוספים",
|
||||
"LabelComplete": "מלא",
|
||||
"LabelComplete": "הושלם",
|
||||
"LabelConfirmPassword": "אישור סיסמה",
|
||||
"LabelContinueListening": "המשך האזנה",
|
||||
"LabelContinueReading": "המשך קריאה",
|
||||
"LabelContinueSeries": "המשך סדרה",
|
||||
"LabelCover": "כריכה",
|
||||
"LabelCoverImageURL": "כתובת התמונה ברשת",
|
||||
"LabelCoverProvider": "ספק כריכה",
|
||||
"LabelCreatedAt": "נוצר בתאריך",
|
||||
"LabelCronExpression": "ביטוי cron",
|
||||
"LabelCurrent": "נוכחי",
|
||||
"LabelCurrently": "כעת:",
|
||||
"LabelCustomCronExpression": "ביטוי cron מותאם אישית:",
|
||||
"LabelDatetime": "Datetime",
|
||||
"LabelDays": "ימים",
|
||||
"LabelDeleteFromFileSystemCheckbox": "מחיקה מהמערכת הקבצים (הסר סימון למחיקה רק ממסד הנתונים)",
|
||||
"LabelDescription": "תיאור",
|
||||
"LabelDeselectAll": "הסר בחירת כל הפריטים",
|
||||
@@ -282,51 +304,83 @@
|
||||
"LabelDiscFromFilename": "דיסק משם הקובץ",
|
||||
"LabelDiscFromMetadata": "דיסק מהמטא-נתונים",
|
||||
"LabelDiscover": "גלה",
|
||||
"LabelDownload": "הורד",
|
||||
"LabelDownload": "הורדה",
|
||||
"LabelDownloadNEpisodes": "הורד {0} פרקים",
|
||||
"LabelDownloadable": "ניתן להורדה",
|
||||
"LabelDuration": "משך",
|
||||
"LabelDurationComparisonExactMatch": "(התאמה מדוייקת)",
|
||||
"LabelDurationComparisonLonger": "({0} ארוך יותר)",
|
||||
"LabelDurationComparisonShorter": "({0} קצר יותר)",
|
||||
"LabelDurationFound": "משך נמצא:",
|
||||
"LabelEbook": "ספר אלקטרוני",
|
||||
"LabelEbooks": "ספרים אלקטרוניים",
|
||||
"LabelEdit": "עריכה",
|
||||
"LabelEmail": "דואר אלקטרוני",
|
||||
"LabelEmailSettingsFromAddress": "מאת",
|
||||
"LabelEmailSettingsRejectUnauthorized": "דחה תעודות לא מאושרות",
|
||||
"LabelEmailSettingsRejectUnauthorizedHelp": "השבתת אימות תעודת SSL עלולה לחשוף את החיבור שלך לסיכוני אבטחה, כגון התקפות \"אדם באמצע\". השבת אפשרות זו רק אם אתה מבין את ההשלכות ובוטח בשרת הדואר שאליו אתה מתחבר.",
|
||||
"LabelEmailSettingsSecure": "מאובטח",
|
||||
"LabelEmailSettingsSecureHelp": "אם מופעל, החיבור ישתמש ב-TLS בעת ההתחברות לשרת. אם לא, אז TLS יהיה בשימוש אם השרת תומך בהרחבת STARTTLS. ברוב המקרים מומלץ להפעיל את הגדרה זו אם אתה מתחבר לפורט 465. לפורט 587 או 25, השאר כבוי. (from nodemailer.com/smtp/#authentication)",
|
||||
"LabelEmailSettingsTestAddress": "כתובת לבדיקה",
|
||||
"LabelEmbeddedCover": "כריכה מוטמעת",
|
||||
"LabelEnable": "הפעל",
|
||||
"LabelEnd": "סיום",
|
||||
"LabelEnable": "אפשר",
|
||||
"LabelEncodingBackupLocation": "גיבוי של קבצי אודיו מקוריים יישמר ב:",
|
||||
"LabelEncodingChaptersNotEmbedded": "פרקים אינם מוטבעים בספרי אודיו מרובי רצועות.",
|
||||
"LabelEncodingClearItemCache": "הקפד לנקות מטמון פריטים מעת לעת.",
|
||||
"LabelEncodingFinishedM4B": "קובץ M4B סופי יישמר בתיקייה ה-audiobook ב:",
|
||||
"LabelEncodingInfoEmbedded": "מטה דאטה יוטמע ברצועות השמע בתוך תיקיית ה-audiobook.",
|
||||
"LabelEncodingStartedNavigation": "לאחר שהמשימה תתחיל אפשר לנווט לדף אחר.",
|
||||
"LabelEncodingTimeWarning": "קידוד יכול להימשך עד 30 דקות.",
|
||||
"LabelEncodingWarningAdvancedSettings": "אזהרה: אל תעדכן את ההגדרות האלה אלא אם כן אתה מכיר את אפשרויות קידוד ffmpeg.",
|
||||
"LabelEncodingWatcherDisabled": "אם ה-watcher כבוי, יש לסרוק את הספר מחדש לאחר מכן.",
|
||||
"LabelEnd": "סוף",
|
||||
"LabelEndOfChapter": "סוף הפרק",
|
||||
"LabelEpisode": "פרק",
|
||||
"LabelEpisodeNotLinkedToRssFeed": "פרק לא מקושר לערוץ RSS",
|
||||
"LabelEpisodeNumber": "פרק #{0}",
|
||||
"LabelEpisodeTitle": "כותרת הפרק",
|
||||
"LabelEpisodeType": "סוג הפרק",
|
||||
"LabelEpisodeUrlFromRssFeed": "קישור פרק מערוץ RSS",
|
||||
"LabelEpisodes": "פרקים",
|
||||
"LabelEpisodic": "ארעי",
|
||||
"LabelExample": "דוגמה",
|
||||
"LabelExpandSeries": "הרחב סדרה",
|
||||
"LabelExpandSubSeries": "הרחב תת סדרה",
|
||||
"LabelExplicit": "בוטה",
|
||||
"LabelExplicitChecked": "בוטה (מסומן)",
|
||||
"LabelExplicitUnchecked": "לא בוטה (לא מסומן)",
|
||||
"LabelExportOPML": "ייצוא OPML",
|
||||
"LabelFeedURL": "כתובת ערוץ",
|
||||
"LabelFetchingMetadata": "מושך מטא-נתונים",
|
||||
"LabelFile": "קובץ",
|
||||
"LabelFileBirthtime": "זמן יצירת הקובץ",
|
||||
"LabelFileModified": "הקובץ שונה",
|
||||
"LabelFilename": "שם הקובץ",
|
||||
"LabelFileBornDate": "נוצר {0}",
|
||||
"LabelFileModified": "קובץ נערך",
|
||||
"LabelFileModifiedDate": "שונה {0}",
|
||||
"LabelFilename": "שם קובץ",
|
||||
"LabelFilterByUser": "סינון לפי משתמש",
|
||||
"LabelFindEpisodes": "מצא פרקים",
|
||||
"LabelFinished": "הושלם",
|
||||
"LabelFolder": "תיקייה",
|
||||
"LabelFolders": "תיקיות",
|
||||
"LabelFontBold": "מודגש",
|
||||
"LabelFontBoldness": "עובי פונט",
|
||||
"LabelFontFamily": "משפחת הפונטים",
|
||||
"LabelFontItalic": "נטוי",
|
||||
"LabelFontScale": "קנה מידה של הפונט",
|
||||
"LabelFontScale": "גודל פונט",
|
||||
"LabelFontStrikethrough": "קו חוצה",
|
||||
"LabelFormat": "תבנית",
|
||||
"LabelGenre": "ז'אנר",
|
||||
"LabelGenres": "ז'אנרים",
|
||||
"LabelFull": "מלא",
|
||||
"LabelGenre": "סגנון",
|
||||
"LabelGenres": "סגנונות",
|
||||
"LabelHardDeleteFile": "מחיקה חזקה של הקובץ",
|
||||
"LabelHasEbook": "ספר אלקטרוני קיים",
|
||||
"LabelHasSupplementaryEbook": "קיים ספר אלקטרוני נלווה",
|
||||
"LabelHasEbook": "קיים ספר אלקטרוני",
|
||||
"LabelHasSupplementaryEbook": "קיים ספר אלקטרוני משלים",
|
||||
"LabelHideSubtitles": "הסתר תת כותרות",
|
||||
"LabelHighestPriority": "העדיפות הגבוהה ביותר",
|
||||
"LabelHost": "מארח",
|
||||
"LabelHour": "שעה",
|
||||
"LabelHours": "שעות",
|
||||
"LabelIcon": "סמל",
|
||||
"LabelImageURLFromTheWeb": "כתובת התמונה מהרשת",
|
||||
"LabelInProgress": "בתהליך",
|
||||
@@ -341,25 +395,30 @@
|
||||
"LabelIntervalEvery6Hours": "כל 6 שעות",
|
||||
"LabelIntervalEveryDay": "כל יום",
|
||||
"LabelIntervalEveryHour": "כל שעה",
|
||||
"LabelIntervalEveryMinute": "כל דקה",
|
||||
"LabelInvert": "הפוך",
|
||||
"LabelItem": "פריט",
|
||||
"LabelJumpBackwardAmount": "כמות הרצה לאחור",
|
||||
"LabelJumpForwardAmount": "כמות הרצה קדימה",
|
||||
"LabelLanguage": "שפה",
|
||||
"LabelLanguageDefaultServer": "שפת ברירת המחדל של השרת",
|
||||
"LabelLanguages": "שפות",
|
||||
"LabelLastBookAdded": "הספר האחרון שנוסף",
|
||||
"LabelLastBookUpdated": "הספר האחרון שעודכן",
|
||||
"LabelLastSeen": "נראה לאחרונה",
|
||||
"LabelLastTime": "הזמן האחרון",
|
||||
"LabelLastUpdate": "עדכון אחרון",
|
||||
"LabelLayout": "פריסה",
|
||||
"LabelLayoutSinglePage": "דף בודד",
|
||||
"LabelLayout": "Layout",
|
||||
"LabelLayoutSinglePage": "עמוד יחיד",
|
||||
"LabelLayoutSplitPage": "פיצול הדף",
|
||||
"LabelLess": "פחות",
|
||||
"LabelLibrariesAccessibleToUser": "ספריות נגישות למשתמש",
|
||||
"LabelLibrary": "ספרייה",
|
||||
"LabelLibraryFilterSublistEmpty": "לא {0}",
|
||||
"LabelLibraryItem": "פריט ספרייה",
|
||||
"LabelLibraryName": "שם הספרייה",
|
||||
"LabelLimit": "מגבלה",
|
||||
"LabelLineSpacing": "ריווח שורות",
|
||||
"LabelLineSpacing": "מרווח שורה",
|
||||
"LabelListenAgain": "האזן שוב",
|
||||
"LabelLogLevelDebug": "דיבוג",
|
||||
"LabelLogLevelInfo": "מידע",
|
||||
@@ -368,6 +427,10 @@
|
||||
"LabelLowestPriority": "העדיפות הנמוכה ביותר",
|
||||
"LabelMatchExistingUsersBy": "התאם משתמשים קיימים לפי",
|
||||
"LabelMatchExistingUsersByDescription": "משמש לחיבור משתמשים קיימים. לאחר החיבור, המשתמשים יותאמו לפי זיהוי ייחודי מספק ה-SSO שלך",
|
||||
"LabelMaxEpisodesToDownload": "מספר פרקים מקסימלי להורדה. 0 - ללא הגבלה.",
|
||||
"LabelMaxEpisodesToDownloadPerCheck": "מספר פרקים חדשים מקסימלי להורדה בכל בדיקה",
|
||||
"LabelMaxEpisodesToKeep": "מספר פרקים מקסימלי לשמור",
|
||||
"LabelMaxEpisodesToKeepHelp": "ערך של 0 קובע ללא מגבלה. לאחר הורדה אוטומטית של פרק חדש יימחק את הפרק הישן ביותר אם יש לך יותר מ-X פרקים. פעולה זו תמחק רק פרק אחד לכל הורדה חדשה.",
|
||||
"LabelMediaPlayer": "נגן מדיה",
|
||||
"LabelMediaType": "סוג מדיה",
|
||||
"LabelMetaTag": "תג מטא",
|
||||
@@ -375,6 +438,7 @@
|
||||
"LabelMetadataOrderOfPrecedenceDescription": "מקורות המטא-נתונים עם עדיפות גבוהה יחליפו מקורות עם עדיפות נמוכה יותר",
|
||||
"LabelMetadataProvider": "ספק מטא-נתונים",
|
||||
"LabelMinute": "דקה",
|
||||
"LabelMinutes": "דקות",
|
||||
"LabelMissing": "חסר",
|
||||
"LabelMissingEbook": "אין ספר אלקטרוני",
|
||||
"LabelMissingSupplementaryEbook": "אין ספר אלקטרוני נלווה",
|
||||
@@ -387,10 +451,11 @@
|
||||
"LabelNarrators": "מספרים",
|
||||
"LabelNew": "חדש",
|
||||
"LabelNewPassword": "סיסמה חדשה",
|
||||
"LabelNewestAuthors": "הסופרים החדשים ביותר",
|
||||
"LabelNewestAuthors": "הסופרים האחרונים",
|
||||
"LabelNewestEpisodes": "הפרקים החדשים ביותר",
|
||||
"LabelNextBackupDate": "תאריך הגיבוי הבא",
|
||||
"LabelNextScheduledRun": "הרצה מתוזמנת הבאה",
|
||||
"LabelNoCustomMetadataProviders": "אין ספקי מטא-נתונים מותאמים אישית",
|
||||
"LabelNoEpisodesSelected": "לא נבחרו פרקים",
|
||||
"LabelNotFinished": "לא הושלם",
|
||||
"LabelNotStarted": "לא התחיל",
|
||||
@@ -405,7 +470,9 @@
|
||||
"LabelNotificationsMaxQueueSize": "גודל התור המרבי לאירועי התראה",
|
||||
"LabelNotificationsMaxQueueSizeHelp": "האירועים מוגבלים לשליחה אחת לשנייה. האירועים יתעלמו אם התור מלא. הגדרה זו נועדה למנוע ספאם התראות.",
|
||||
"LabelNumberOfBooks": "מספר הספרים",
|
||||
"LabelNumberOfEpisodes": "מספר הפרקים",
|
||||
"LabelNumberOfEpisodes": "# פרקים",
|
||||
"LabelOpenIDAdvancedPermsClaimDescription": "שם OpenID claim המכילה הרשאות מתקדמות לפעולות משתמש בתוך האפליקציה, אשר יחולו על תפקידים שאינם מנהלי מערכת (<b>אם הוגדרה</b>). אם התביעה חסרה בתגובה, הגישה ל-ABS תידחה. אם אפשרות אחת חסרה, היא תטופל כ-<code>false</code> יש לוודא שטענת ספק הזהויות תואמת את המבנה הצפוי:",
|
||||
"LabelOpenIDClaims": "השאר את האפשרויות הבאות ריקות כדי להשבית הקצאת קבוצות והרשאות מתקדמת, ולאחר מכן להקצות אוטומטית את קבוצת 'משתמש'.",
|
||||
"LabelOpenRSSFeed": "פתח ערוץ RSS",
|
||||
"LabelOverwrite": "לשכפל",
|
||||
"LabelPassword": "סיסמה",
|
||||
@@ -433,13 +500,15 @@
|
||||
"LabelProvider": "ספק",
|
||||
"LabelPubDate": "תאריך פרסום",
|
||||
"LabelPublishYear": "שנת הפרסום",
|
||||
"LabelPublishedDate": "פורסם {0}",
|
||||
"LabelPublisher": "מוציא לאור",
|
||||
"LabelRSSFeedCustomOwnerEmail": "אימייל בעלים מותאם אישית",
|
||||
"LabelRSSFeedCustomOwnerName": "שם בעלים מותאם אישית",
|
||||
"LabelRSSFeedOpen": "פתח ערוץ RSS",
|
||||
"LabelRSSFeedOpen": "ערוץ RSS פתוח",
|
||||
"LabelRSSFeedPreventIndexing": "מנע רישום",
|
||||
"LabelRSSFeedSlug": "Slug של ערוץ ה-RSS",
|
||||
"LabelRSSFeedURL": "כתובת ערוץ ה-RSS",
|
||||
"LabelRandomly": "באופן אקראי",
|
||||
"LabelRead": "קריאה",
|
||||
"LabelReadAgain": "קרא שוב",
|
||||
"LabelReadEbookWithoutProgress": "קרא/י ספר אלקטרוני ללא שמירת התקדמות",
|
||||
@@ -465,7 +534,7 @@
|
||||
"LabelSeriesProgress": "התקדמות בסדרה",
|
||||
"LabelServerYearReview": "השנה בסקירה של השרת ({0})",
|
||||
"LabelSetEbookAsPrimary": "קבע כראשי",
|
||||
"LabelSetEbookAsSupplementary": "קבע כספר אלקטרוני נלווה",
|
||||
"LabelSetEbookAsSupplementary": "קבע כמשלים",
|
||||
"LabelSettingsAudiobooksOnly": "רק ספרי קול",
|
||||
"LabelSettingsAudiobooksOnlyHelp": "הפעלת ההגדרה הזו תתעלם מקבצי ספרים אלקטרוניים אלא אם כן הם נמצאים בתיקיית ספרי קול, שבמקרה זה יקבעו כספרים אלקטרוניים נלווים",
|
||||
"LabelSettingsBookshelfViewHelp": "עיצוב סקאומורפי עם מדפי עץ",
|
||||
@@ -500,7 +569,7 @@
|
||||
"LabelShowAll": "הצג הכל",
|
||||
"LabelSize": "גודל",
|
||||
"LabelSleepTimer": "טיימר שינה",
|
||||
"LabelStart": "התחלה",
|
||||
"LabelStart": "התחל",
|
||||
"LabelStartTime": "זמן התחלה",
|
||||
"LabelStarted": "התחיל",
|
||||
"LabelStartedAt": "התחיל ב",
|
||||
@@ -576,8 +645,8 @@
|
||||
"LabelViewQueue": "הצג תור נגן",
|
||||
"LabelVolume": "עוצמת קול",
|
||||
"LabelWeekdaysToRun": "ימי השבוע להרצה",
|
||||
"LabelYearReviewHide": "הסתר שנת סקירה",
|
||||
"LabelYearReviewShow": "הצג שנת סקירה",
|
||||
"LabelYearReviewHide": "הסתר סקירת שנה",
|
||||
"LabelYearReviewShow": "הצג סקירת שנה",
|
||||
"LabelYourAudiobookDuration": "משך הספר הקולי שלך",
|
||||
"LabelYourBookmarks": "הסימניות שלך",
|
||||
"LabelYourPlaylists": "הפלייליסטים שלך",
|
||||
@@ -628,8 +697,8 @@
|
||||
"MessageDownloadingEpisode": "מוריד פרק",
|
||||
"MessageDragFilesIntoTrackOrder": "גרור קבצים לסדר ההשמעה נכון",
|
||||
"MessageEmbedFinished": "ההטמעה הושלמה!",
|
||||
"MessageEpisodesQueuedForDownload": "{0} פרקים בתור להורדה",
|
||||
"MessageFeedURLWillBe": "כתובת URL של העדכון תהיה {0}",
|
||||
"MessageEpisodesQueuedForDownload": "{0} פרק/ים בתור להורדה",
|
||||
"MessageFeedURLWillBe": "כתובת ה- URL של הערוץ תהיה {0}",
|
||||
"MessageFetching": "מושך...",
|
||||
"MessageForceReScanDescription": "תבוצע סריקה מחדש כמו סריקה חדש מאפס, תגי ID3 של קבצי קול, קבצי OPF, וקבצי טקסט ייסרקו כחדשים.",
|
||||
"MessageImportantNotice": "הודעה חשובה!",
|
||||
@@ -644,7 +713,7 @@
|
||||
"MessageMapChapterTitles": "מפה שמות פרקים לפרקי הספר השמורים שלך ללא שינוי תגי זמן",
|
||||
"MessageMarkAllEpisodesFinished": "סמן את כל הפרקים כהסתיימו",
|
||||
"MessageMarkAllEpisodesNotFinished": "סמן את כל הפרקים כלא הסתיימו",
|
||||
"MessageMarkAsFinished": "סמן כהסתיים",
|
||||
"MessageMarkAsFinished": "סמן כהושלם",
|
||||
"MessageMarkAsNotFinished": "סמן כלא הסתיים",
|
||||
"MessageMatchBooksDescription": "ינסה להתאים ספרים בספריית הספרים שלך עם ספר מספק החיפוש הנבחר וימלא פרטים ריקים ותמונות כריכה. לא יחליף פרטים קיימים.",
|
||||
"MessageNoAudioTracks": "אין רצועות שמע",
|
||||
@@ -674,7 +743,7 @@
|
||||
"MessageNoSeries": "אין סדרות",
|
||||
"MessageNoTags": "אין תגיות",
|
||||
"MessageNoTasksRunning": "אין משימות פעילות",
|
||||
"MessageNoUpdatesWereNecessary": "לא היה צורך בעדכונים",
|
||||
"MessageNoUpdatesWereNecessary": "לא נדרש עדכון",
|
||||
"MessageNoUserPlaylists": "אין לך רשימות השמעה",
|
||||
"MessageNotYetImplemented": "עדיין לא מיושם",
|
||||
"MessageOr": "או",
|
||||
@@ -682,6 +751,7 @@
|
||||
"MessagePlayChapter": "הקשב לתחילת הפרק",
|
||||
"MessagePlaylistCreateFromCollection": "צור רשימת השמעה מאוסף",
|
||||
"MessagePodcastHasNoRSSFeedForMatching": "לפודקאסט אין כתובת URL של ערוץ RSS להתאמה",
|
||||
"MessagePodcastSearchField": "הזן מונח חיפוש או כתובת URL של ערוץ RSS",
|
||||
"MessageQuickMatchDescription": "ממלא פרטים ריקים וכריכות עם התוצאה הראשונה מ '{0}'. לא ימחק פרטים אלא אם הגדרת השרת 'העדף מטה-נתונים מותאמים' מופעלת.",
|
||||
"MessageRemoveChapter": "הסר פרק",
|
||||
"MessageRemoveEpisodes": "הסר {0} פרקים",
|
||||
@@ -708,7 +778,7 @@
|
||||
"NoteChangeRootPassword": "המשתמש root הוא המשתמש היחיד שיכולה להיות לו סיסמה ריקה",
|
||||
"NoteChapterEditorTimes": "הערה: זמן ההתחלה של הפרק הראשון חייב להישאר 0:00 וזמן ההתחלה של הפרק האחרון לא יכול לחרוג מהזמן של ספר השמע.",
|
||||
"NoteFolderPicker": "הערה: תיקיות שכבר מופו לא יוצגו",
|
||||
"NoteRSSFeedPodcastAppsHttps": "אזהרה: רוב יישומי הפודקאסט דורשים שכתובת ה-URL ערוץ ה-RSS תשתמש ב-HTTPS",
|
||||
"NoteRSSFeedPodcastAppsHttps": "אזהרה: רוב אפליקציות הפודקאסטים ידרשו שכתובת האתר של ערוץ ה-RSS תשתמש ב-HTTPS",
|
||||
"NoteRSSFeedPodcastAppsPubDate": "אזהרה: פרק אחד או יותר לא מכילים תאריך פרסום. חלק מיישומי הפודקאסט דורשים זאת.",
|
||||
"NoteUploaderFoldersWithMediaFiles": "תיקיות עם קבצי מדיה יעובדו כפריטי ספריה נפרדים.",
|
||||
"NoteUploaderOnlyAudioFiles": "אם מועלים רק קבצי שמע, כל קובץ שמע יעובד כספר שמע נפרד.",
|
||||
@@ -741,7 +811,7 @@
|
||||
"ToastCollectionUpdateSuccess": "האוסף עודכן בהצלחה",
|
||||
"ToastItemCoverUpdateSuccess": "כריכת הפריט עודכנה בהצלחה",
|
||||
"ToastItemDetailsUpdateSuccess": "פרטי הפריט עודכנו בהצלחה",
|
||||
"ToastItemMarkedAsFinishedFailed": "סימון כפריט כהושלם נכשל",
|
||||
"ToastItemMarkedAsFinishedFailed": "סימון כפריט שהושלם נכשל",
|
||||
"ToastItemMarkedAsFinishedSuccess": "הפריט סומן כהושלם בהצלחה",
|
||||
"ToastItemMarkedAsNotFinishedFailed": "סימון כפריט שלא הושלם נכשל",
|
||||
"ToastItemMarkedAsNotFinishedSuccess": "הפריט סומן כלא הושלם בהצלחה",
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"ButtonAuthors": "Szerzők",
|
||||
"ButtonBack": "Vissza",
|
||||
"ButtonBatchEditPopulateFromExisting": "Létezőből feltöltés",
|
||||
"ButtonBatchEditPopulateMapDetails": "",
|
||||
"ButtonBatchEditPopulateMapDetails": "A térkép részleteinek feltöltése",
|
||||
"ButtonBrowseForFolder": "Mappa keresése",
|
||||
"ButtonCancel": "Mégse",
|
||||
"ButtonCancelEncode": "Kódolás megszakítása",
|
||||
@@ -177,6 +177,7 @@
|
||||
"HeaderPlaylist": "Lejátszási lista",
|
||||
"HeaderPlaylistItems": "Lejátszási lista elemek",
|
||||
"HeaderPodcastsToAdd": "Hozzáadandó podcastok",
|
||||
"HeaderPresets": "Alapbeállítások",
|
||||
"HeaderPreviewCover": "Borító előnézete",
|
||||
"HeaderRSSFeedGeneral": "RSS részletek",
|
||||
"HeaderRSSFeedIsOpen": "RSS hírcsatorna nyitva van",
|
||||
@@ -219,6 +220,7 @@
|
||||
"LabelAccountTypeAdmin": "Adminisztrátor",
|
||||
"LabelAccountTypeGuest": "Vendég",
|
||||
"LabelAccountTypeUser": "Felhasználó",
|
||||
"LabelActivities": "Tevékenységek",
|
||||
"LabelActivity": "Tevékenység",
|
||||
"LabelAddToCollection": "Hozzáadás a gyűjteményhez",
|
||||
"LabelAddToCollectionBatch": "{0} könyv hozzáadása a gyűjteményhez",
|
||||
@@ -228,6 +230,7 @@
|
||||
"LabelAddedDate": "{0} Hozzáadva",
|
||||
"LabelAdminUsersOnly": "Csak admin felhasználók",
|
||||
"LabelAll": "Összes",
|
||||
"LabelAllEpisodesDownloaded": "Minden epizód letöltve",
|
||||
"LabelAllUsers": "Minden felhasználó",
|
||||
"LabelAllUsersExcludingGuests": "Minden felhasználó, vendégek kivételével",
|
||||
"LabelAllUsersIncludingGuests": "Minden felhasználó, beleértve a vendégeket is",
|
||||
@@ -251,7 +254,7 @@
|
||||
"LabelBackToUser": "Vissza a felhasználóhoz",
|
||||
"LabelBackupAudioFiles": "Audiófájlok biztonsági mentése",
|
||||
"LabelBackupLocation": "Biztonsági másolat helye",
|
||||
"LabelBackupsEnableAutomaticBackups": "Automatikus biztonsági másolatok engedélyezése",
|
||||
"LabelBackupsEnableAutomaticBackups": "Automatikus biztonsági másolatok",
|
||||
"LabelBackupsEnableAutomaticBackupsHelp": "Biztonsági másolatok mentése a /metadata/backups mappába",
|
||||
"LabelBackupsMaxBackupSize": "Maximális biztonsági másolat méret (GB-ban) (0-tól végtelenig)",
|
||||
"LabelBackupsMaxBackupSizeHelp": "A rossz konfiguráció elleni védelem érdekében a biztonsági másolatok meghiúsulnak, ha meghaladják a beállított méretet.",
|
||||
@@ -283,6 +286,7 @@
|
||||
"LabelContinueSeries": "Sorozat folytatása",
|
||||
"LabelCover": "Borító",
|
||||
"LabelCoverImageURL": "Borítókép URL",
|
||||
"LabelCoverProvider": "Borító Szolgáltató",
|
||||
"LabelCreatedAt": "Létrehozás ideje",
|
||||
"LabelCronExpression": "Cron kifejezés",
|
||||
"LabelCurrent": "Jelenlegi",
|
||||
@@ -391,7 +395,8 @@
|
||||
"LabelIntervalEvery6Hours": "Minden 6 órában",
|
||||
"LabelIntervalEveryDay": "Minden nap",
|
||||
"LabelIntervalEveryHour": "Minden órában",
|
||||
"LabelInvert": "Megfordítás",
|
||||
"LabelIntervalEveryMinute": "Minden percben",
|
||||
"LabelInvert": "Inverz",
|
||||
"LabelItem": "Elem",
|
||||
"LabelJumpBackwardAmount": "Visszafelé ugrás mennyisége",
|
||||
"LabelJumpForwardAmount": "Előre ugrás mennyisége",
|
||||
@@ -486,6 +491,7 @@
|
||||
"LabelPersonalYearReview": "Az éved összefoglalása ({0})",
|
||||
"LabelPhotoPathURL": "Fénykép útvonal/URL",
|
||||
"LabelPlayMethod": "Lejátszási módszer",
|
||||
"LabelPlaybackRateIncrementDecrement": "Lejátszási sebesség növelés/csökkentés értéke",
|
||||
"LabelPlayerChapterNumberMarker": "{0} a {1} -ből",
|
||||
"LabelPlaylists": "Lejátszási listák",
|
||||
"LabelPodcast": "Podcast",
|
||||
@@ -508,7 +514,7 @@
|
||||
"LabelPublishers": "Kiadók",
|
||||
"LabelRSSFeedCustomOwnerEmail": "Egyéni tulajdonos e-mail",
|
||||
"LabelRSSFeedCustomOwnerName": "Egyéni tulajdonos neve",
|
||||
"LabelRSSFeedOpen": "RSS hírcsatorna nyitva",
|
||||
"LabelRSSFeedOpen": "RSS-hírcsatorna nyitva",
|
||||
"LabelRSSFeedPreventIndexing": "Indexelés megakadályozása",
|
||||
"LabelRSSFeedSlug": "RSS hírcsatorna slug",
|
||||
"LabelRSSFeedURL": "RSS hírcsatorna URL",
|
||||
@@ -525,6 +531,7 @@
|
||||
"LabelReleaseDate": "Megjelenés dátuma",
|
||||
"LabelRemoveAllMetadataAbs": "Az összes metadata.abs fájl eltávolítása",
|
||||
"LabelRemoveAllMetadataJson": "Az összes metadata.json fájl eltávolítása",
|
||||
"LabelRemoveAudibleBranding": "Audible intro és outro eltávolítása a fejezetekből",
|
||||
"LabelRemoveCover": "Borító eltávolítása",
|
||||
"LabelRemoveMetadataFile": "Metaadatfájlok eltávolítása a könyvtár elemek mappáiból",
|
||||
"LabelRemoveMetadataFileHelp": "A metadata.json és metadata.abs fájlokat eltávolítása a {0} mappáidból.",
|
||||
@@ -554,6 +561,8 @@
|
||||
"LabelSettingsBookshelfViewHelp": "Skeuomorfikus dizájn fa polcokkal",
|
||||
"LabelSettingsChromecastSupport": "Chromecast támogatás",
|
||||
"LabelSettingsDateFormat": "Dátumformátum",
|
||||
"LabelSettingsEnableWatcher": "Változások automatikus vizsgálata a könyvtárakban",
|
||||
"LabelSettingsEnableWatcherForLibrary": "Változások automatikus vizsgálata a könyvtárban",
|
||||
"LabelSettingsEnableWatcherHelp": "Engedélyezi az automatikus elem hozzáadás/frissítés funkciót, amikor fájlváltozásokat észlel. *Szerver újraindítása szükséges",
|
||||
"LabelSettingsEpubsAllowScriptedContent": "Szkriptelt tartalmak engedélyezése epub-okban",
|
||||
"LabelSettingsEpubsAllowScriptedContentHelp": "Megengedi, hogy az epub fájlok szkripteket hajtsanak végre. Ezt a beállítást kikapcsolva ajánlott tartani, kivéve, ha megbízik az epub fájlok forrásában.",
|
||||
@@ -597,6 +606,7 @@
|
||||
"LabelSlug": "Rövid cím",
|
||||
"LabelSortAscending": "Emelkedő",
|
||||
"LabelSortDescending": "Csökkenő",
|
||||
"LabelSortPubDate": "Rendezés megjelenés dátuma szerint",
|
||||
"LabelStart": "Kezdés",
|
||||
"LabelStartTime": "Kezdési idő",
|
||||
"LabelStarted": "Elkezdődött",
|
||||
@@ -697,12 +707,17 @@
|
||||
"LabelYourProgress": "Haladásod",
|
||||
"MessageAddToPlayerQueue": "Hozzáadás a lejátszó sorhoz",
|
||||
"MessageAppriseDescription": "Ennek a funkció használatához futtatnia kell egy <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> példányt vagy egy olyan API-t, amely kezeli ezeket a kéréseket. <br />Az Apprise API URL-nek a teljes URL útvonalat kell tartalmaznia az értesítés elküldéséhez, például, ha az API példánya a <code>http://192.168.1.1:8337</code> címen szolgáltatva, akkor <code>http://192.168.1.1:8337/notify</code> értéket kell megadnia.",
|
||||
"MessageAsinCheck": "Győződjön meg róla, hogy az ASIN-t a megfelelő Audible régióból használja, nem az Amazonból.",
|
||||
"MessageAuthenticationOIDCChangesRestart": "A mentés után indítsa újra a szervert az OIDC módosítások alkalmazásához.",
|
||||
"MessageBackupsDescription": "A biztonsági másolatok tartalmazzák a felhasználókat, a felhasználói haladást, a könyvtári elem részleteit, a szerver beállításait és a képeket, amelyek a <code>/metadata/items</code> és <code>/metadata/authors</code> mappákban vannak tárolva. A biztonsági másolatok <strong>nem</strong> tartalmazzák a könyvtári mappákban tárolt fájlokat.",
|
||||
"MessageBackupsLocationEditNote": "Megjegyzés: A biztonsági mentés helyének frissítése nem mozgatja vagy módosítja a meglévő biztonsági mentéseket",
|
||||
"MessageBackupsLocationNoEditNote": "Megjegyzés: A biztonsági mentés helye egy környezeti változóval van beállítva, és itt nem módosítható.",
|
||||
"MessageBackupsLocationPathEmpty": "A biztonsági mentés helyének elérési útvonala nem lehet üres",
|
||||
"MessageBatchEditPopulateMapDetailsAllHelp": "Az engedélyezett mezők feltöltése az összes elem adatával. A több értéket tartalmazó mezők összevonásra kerülnek",
|
||||
"MessageBatchEditPopulateMapDetailsItemHelp": "A térkép engedélyezett adatmezőinek feltöltése ezen elem adataival",
|
||||
"MessageBatchQuickMatchDescription": "A Gyors egyeztetés megpróbálja hozzáadni a hiányzó borítókat és metaadatokat a kiválasztott elemekhez. Engedélyezze az alábbi opciókat, hogy a Gyors egyeztetés felülírhassa a meglévő borítókat és/vagy metaadatokat.",
|
||||
"MessageBookshelfNoCollections": "Még nem készített gyűjteményeket",
|
||||
"MessageBookshelfNoCollectionsHelp": "A gyűjtemények nyilvánosak. Minden, a könyvtárhoz hozzáféréssel rendelkező felhasználó láthatja őket.",
|
||||
"MessageBookshelfNoRSSFeeds": "Nincsenek nyitott RSS hírcsatornák",
|
||||
"MessageBookshelfNoResultsForFilter": "Nincs eredmény a \"{0}: {1}\" szűrőre",
|
||||
"MessageBookshelfNoResultsForQuery": "Nincs eredmény a lekérdezéshez",
|
||||
@@ -712,6 +727,7 @@
|
||||
"MessageChapterErrorStartGteDuration": "Érvénytelen kezdési idő, kevesebbnek kell lennie, mint a hangoskönyv időtartama",
|
||||
"MessageChapterErrorStartLtPrev": "Érvénytelen kezdési idő, nagyobbnak kell lennie, mint az előző fejezet kezdési ideje",
|
||||
"MessageChapterStartIsAfter": "A fejezet kezdete a hangoskönyv végét követi",
|
||||
"MessageChaptersNotFound": "Fejezetek nem találhatók",
|
||||
"MessageCheckingCron": "Cron ellenőrzése...",
|
||||
"MessageConfirmCloseFeed": "Biztosan be szeretné zárni ezt a hírcsatornát?",
|
||||
"MessageConfirmDeleteBackup": "Biztosan törölni szeretné a(z) {0} biztonsági másolatot?",
|
||||
@@ -768,6 +784,7 @@
|
||||
"MessageForceReScanDescription": "minden fájlt újra szkennel, mint egy friss szkennelés. Az audiofájlok ID3 címkéi, OPF fájlok és szövegfájlok újként lesznek szkennelve.",
|
||||
"MessageImportantNotice": "Fontos közlemény!",
|
||||
"MessageInsertChapterBelow": "Fejezet beszúrása alulra",
|
||||
"MessageInvalidAsin": "Érvénytelen ASIN",
|
||||
"MessageItemsSelected": "{0} kiválasztott elem",
|
||||
"MessageItemsUpdated": "{0} frissített elem",
|
||||
"MessageJoinUsOn": "Csatlakozzon hozzánk a",
|
||||
@@ -813,6 +830,7 @@
|
||||
"MessageNoTasksRunning": "Nincsenek futó feladatok",
|
||||
"MessageNoUpdatesWereNecessary": "Nem volt szükség frissítésekre",
|
||||
"MessageNoUserPlaylists": "Nincsenek felhasználói lejátszási listák",
|
||||
"MessageNoUserPlaylistsHelp": "A lejátszási listák személyesek. Csak az a felhasználó láthatja őket, aki létrehozta őket.",
|
||||
"MessageNotYetImplemented": "Még nem implementált",
|
||||
"MessageOpmlPreviewNote": "Megjegyzés: Ez egy előnézeti kép az elemzett OPML fájlról. A podcast tényleges címe az RSS hírcsatornából származik.",
|
||||
"MessageOr": "vagy",
|
||||
@@ -835,8 +853,10 @@
|
||||
"MessageRestoreBackupConfirm": "Biztosan vissza szeretné állítani a biztonsági másolatot, amely ekkor készült:",
|
||||
"MessageRestoreBackupWarning": "A biztonsági mentés visszaállítása felülírja az egész adatbázist, amely a /config mappában található, valamint a borítóképeket a /metadata/items és /metadata/authors mappákban.<br /><br />A biztonsági mentések nem módosítják a könyvtár mappáiban található fájlokat. Ha engedélyezte a szerverbeállításokat a borítóképek és a metaadatok könyvtármappákban való tárolására, akkor ezek nem kerülnek biztonsági mentésre vagy felülírásra.<br /><br />A szerver használó összes kliens automatikusan frissül.",
|
||||
"MessageScheduleLibraryScanNote": "A legtöbb felhasználó számára ajánlott ezt a funkciót kikapcsolva hagyni, és engedélyezni a mappafigyelő beállítást. A mappafigyelő automatikusan észleli a könyvtári mappák változásait. A mappafigyelő nem működik minden fájlrendszernél (mint például az NFS), ezért helyette ütemezett könyvtárellenőrzéseket lehet használni.",
|
||||
"MessageScheduleRunEveryWeekdayAtTime": "Futás minden {1} óra {0}-kor",
|
||||
"MessageSearchResultsFor": "Keresési eredmények",
|
||||
"MessageSelected": "{0} kiválasztva",
|
||||
"MessageSeriesSequenceCannotContainSpaces": "Sorozat sorrend nem tartalmazhat szóközt",
|
||||
"MessageServerCouldNotBeReached": "A szervert nem lehet elérni",
|
||||
"MessageSetChaptersFromTracksDescription": "Fejezetek beállítása minden egyes hangfájlt egy fejezetként használva, és a fejezet címét a hangfájl neveként",
|
||||
"MessageShareExpirationWillBe": "A lejárat: <strong>{0}</strong>",
|
||||
@@ -861,6 +881,7 @@
|
||||
"MessageTaskNoFilesToScan": "Nincs beolvasandó fájl",
|
||||
"MessageTaskOpmlImport": "OPML import",
|
||||
"MessageTaskOpmlImportDescription": "Podcastok létrehozása {0} RSS hírcsatornából",
|
||||
"MessageTaskOpmlImportFeed": "OPML import hírcsatorna",
|
||||
"MessageTaskOpmlImportFeedDescription": "RSS feed „{0}” importálása",
|
||||
"MessageTaskOpmlImportFeedFailed": "Nem sikerült letölteni a podcast feedet",
|
||||
"MessageTaskOpmlImportFeedPodcastDescription": "„{0}” podcast létrehozása",
|
||||
@@ -869,6 +890,7 @@
|
||||
"MessageTaskOpmlImportFinished": "{0} podcast hozzáadva",
|
||||
"MessageTaskOpmlParseFailed": "Az OPML fájl elemzése nem sikerült",
|
||||
"MessageTaskOpmlParseFastFail": "Érvénytelen OPML fájl: <opml> tag nem található VAGY nem találtak <outline> taget",
|
||||
"MessageTaskOpmlParseNoneFound": "Nem található feed az OPML fájlban",
|
||||
"MessageTaskScanItemsAdded": "{0} hozzáadva",
|
||||
"MessageTaskScanItemsMissing": "{0} hiányzik",
|
||||
"MessageTaskScanItemsUpdated": "{0} frissítve",
|
||||
@@ -896,6 +918,8 @@
|
||||
"NotificationOnBackupCompletedDescription": "A biztonsági mentés befejezésekor aktiválódik",
|
||||
"NotificationOnBackupFailedDescription": "A biztonsági mentés sikertelensége esetén aktiválódik",
|
||||
"NotificationOnEpisodeDownloadedDescription": "Egy podcast epizód automatikus letöltésekor aktiválódik",
|
||||
"NotificationOnRSSFeedDisabledDescription": "Akkor lép működésbe, ha az automatikus epizódletöltés a túl sok sikertelen próbálkozás miatt letiltásra kerül",
|
||||
"NotificationOnRSSFeedFailedDescription": "Akkor aktiválódik, ha az RSS feed kérés sikertelen az automatikus epizódletöltésnél",
|
||||
"NotificationOnTestDescription": "Esemény az értesítési rendszer teszteléséhez",
|
||||
"PlaceholderNewCollection": "Új gyűjtemény neve",
|
||||
"PlaceholderNewFolderPath": "Új mappa útvonala",
|
||||
@@ -940,8 +964,11 @@
|
||||
"ToastBackupRestoreFailed": "A biztonsági mentés visszaállítása sikertelen",
|
||||
"ToastBackupUploadFailed": "A biztonsági mentés feltöltése sikertelen",
|
||||
"ToastBackupUploadSuccess": "Biztonsági mentés feltöltve",
|
||||
"ToastBatchApplyDetailsToItemsSuccess": "Tételekre alkalmazott részletek",
|
||||
"ToastBatchDeleteFailed": "A tömeges törlés nem sikerült",
|
||||
"ToastBatchDeleteSuccess": "Sikeres tömeges törlés",
|
||||
"ToastBatchQuickMatchFailed": "Tömeges Gyors Egyeztetés sikertelen!",
|
||||
"ToastBatchQuickMatchStarted": "{0} könyv Tömeges Gyors Egyeztetése elkezdődött!",
|
||||
"ToastBatchUpdateFailed": "Kötegelt frissítés sikertelen",
|
||||
"ToastBatchUpdateSuccess": "Kötegelt frissítés sikeres",
|
||||
"ToastBookmarkCreateFailed": "Könyvjelző létrehozása sikertelen",
|
||||
@@ -950,9 +977,12 @@
|
||||
"ToastCachePurgeFailed": "A gyorsítótár törlése sikertelen",
|
||||
"ToastCachePurgeSuccess": "A gyorsítótár sikeresen törölve",
|
||||
"ToastChaptersHaveErrors": "A fejezetek hibákat tartalmaznak",
|
||||
"ToastChaptersInvalidShiftAmountLast": "Érvénytelen eltolási érték. Az utolsó fejezet kezdési időpontja túlnyúlna a hangoskönyv időtartamán.",
|
||||
"ToastChaptersInvalidShiftAmountStart": "Érvénytelen eltolási érték. Az első fejezet hossza nulla vagy negatív lenne, és a második fejezet felülírná. Növelje a második fejezet kezdő időtartamát.",
|
||||
"ToastChaptersMustHaveTitles": "A fejezeteknek címekkel kell rendelkezniük",
|
||||
"ToastChaptersRemoved": "Fejezetek eltávolítva",
|
||||
"ToastChaptersUpdated": "Fejezetek frissítve",
|
||||
"ToastCollectionItemsAddFailed": "A tétel(ek) hozzáadása gyűjteményhez sikertelen",
|
||||
"ToastCollectionRemoveSuccess": "Gyűjtemény eltávolítva",
|
||||
"ToastCollectionUpdateSuccess": "Gyűjtemény frissítve",
|
||||
"ToastCoverUpdateFailed": "A borító frissítése nem sikerült",
|
||||
@@ -967,6 +997,7 @@
|
||||
"ToastEncodeCancelFailed": "A kódolás törlése sikertelen volt",
|
||||
"ToastEncodeCancelSucces": "Kódolás törölve",
|
||||
"ToastEpisodeDownloadQueueClearFailed": "Nem sikerült törölni a várólistát",
|
||||
"ToastEpisodeDownloadQueueClearSuccess": "Epizód letöltési várólista törölve",
|
||||
"ToastEpisodeUpdateSuccess": "{0} epizód frissítve",
|
||||
"ToastErrorCannotShare": "Ezen az eszközön nem lehet natívan megosztani",
|
||||
"ToastFailedToLoadData": "Sikertelen adatbetöltés",
|
||||
@@ -974,6 +1005,7 @@
|
||||
"ToastFailedToShare": "Nem sikerült megosztani",
|
||||
"ToastFailedToUpdate": "Nem sikerült frissíteni",
|
||||
"ToastInvalidImageUrl": "Érvénytelen a kép URL címe",
|
||||
"ToastInvalidMaxEpisodesToDownload": "A letölthető epizódok száma érvénytelen",
|
||||
"ToastInvalidUrl": "Érvénytelen URL",
|
||||
"ToastItemCoverUpdateSuccess": "Elem borítója frissítve",
|
||||
"ToastItemDeletedFailed": "Nem sikerült törölni az elemet",
|
||||
@@ -1011,8 +1043,11 @@
|
||||
"ToastNoUpdatesNecessary": "Nincs szükség frissítésre",
|
||||
"ToastNotificationCreateFailed": "Értesítés létrehozása sikertelen",
|
||||
"ToastNotificationDeleteFailed": "Értesítés törlése sikertelen",
|
||||
"ToastNotificationFailedMaximum": "A sikertelen kísérletek maximális száma >= 0 kell, hogy legyen",
|
||||
"ToastNotificationQueueMaximum": "Az értesítési sor maximális száma >= 0 kell, hogy legyen",
|
||||
"ToastNotificationSettingsUpdateSuccess": "Értesítési beállítások frissítve",
|
||||
"ToastNotificationTestTriggerFailed": "Nem sikerült a tesztértesítést elindítani",
|
||||
"ToastNotificationTestTriggerSuccess": "Kiváltott tesztértesítés",
|
||||
"ToastNotificationUpdateSuccess": "Értesítés frissítve",
|
||||
"ToastPlaylistCreateFailed": "Lejátszási lista létrehozása sikertelen",
|
||||
"ToastPlaylistCreateSuccess": "Lejátszási lista létrehozva",
|
||||
@@ -1020,6 +1055,7 @@
|
||||
"ToastPlaylistUpdateSuccess": "Lejátszási lista frissítve",
|
||||
"ToastPodcastCreateFailed": "Podcast létrehozása sikertelen",
|
||||
"ToastPodcastCreateSuccess": "A podcast sikeresen létrehozva",
|
||||
"ToastPodcastGetFeedFailed": "Nem sikerült podcast feedet kapni",
|
||||
"ToastPodcastNoEpisodesInFeed": "Nincsenek epizódok az RSS hírcsatornában",
|
||||
"ToastPodcastNoRssFeed": "A podcastnak nincs RSS-hírcsatornája",
|
||||
"ToastProgressIsNotBeingSynced": "Az előrehaladás nem szinkronizálódik, a lejátszás újraindul",
|
||||
@@ -1032,10 +1068,18 @@
|
||||
"ToastRemoveFailed": "Sikertelen eltávolítás",
|
||||
"ToastRemoveItemFromCollectionFailed": "Tétel eltávolítása a gyűjteményből sikertelen",
|
||||
"ToastRemoveItemFromCollectionSuccess": "Tétel eltávolítva a gyűjteményből",
|
||||
"ToastRemoveItemsWithIssuesFailed": "Nem sikerült eltávolítani a hibás könyvtárelemeket",
|
||||
"ToastRemoveItemsWithIssuesSuccess": "Hibás könyvtárelemek eltávolítva",
|
||||
"ToastRenameFailed": "Sikertelen átnevezés",
|
||||
"ToastRescanFailed": "Sikertelen újrakeresés a következőnél: {0}",
|
||||
"ToastRescanRemoved": "A teljes újrabeolvasás befejezve, elem eltávolítva",
|
||||
"ToastRescanUpToDate": "A teljes újrabeolvasás befejezve, elem naprakész volt",
|
||||
"ToastRescanUpdated": "A teljes újrabeolvasás befejezve, elem frissítve",
|
||||
"ToastScanFailed": "Nem sikerült beolvasni a könyvtárelemet",
|
||||
"ToastSelectAtLeastOneUser": "Válasszon legalább egy felhasználót",
|
||||
"ToastSendEbookToDeviceFailed": "E-könyv küldése az eszközre sikertelen",
|
||||
"ToastSendEbookToDeviceSuccess": "E-könyv elküldve az eszközre \"{0}\"",
|
||||
"ToastSeriesSubmitFailedSameName": "Nem lehet két azonos nevű sorozatot hozzáadni",
|
||||
"ToastSeriesUpdateFailed": "Sorozat frissítése sikertelen",
|
||||
"ToastSeriesUpdateSuccess": "Sorozat frissítése sikeres",
|
||||
"ToastServerSettingsUpdateSuccess": "Szerver beállítások frissítve",
|
||||
@@ -1043,6 +1087,8 @@
|
||||
"ToastSessionDeleteFailed": "Munkamenet törlése sikertelen",
|
||||
"ToastSessionDeleteSuccess": "Munkamenet törölve",
|
||||
"ToastSleepTimerDone": "Alvásidőzítő kész... zZzzZZz",
|
||||
"ToastSlugMustChange": "A Slug érvénytelen karaktereket tartalmaz",
|
||||
"ToastSlugRequired": "Slug szükséges",
|
||||
"ToastSocketConnected": "Socket csatlakoztatva",
|
||||
"ToastSocketDisconnected": "Socket lecsatlakoztatva",
|
||||
"ToastSocketFailedToConnect": "A Socket csatlakoztatása sikertelen",
|
||||
@@ -1050,9 +1096,14 @@
|
||||
"ToastSortingPrefixesUpdateSuccess": "Rendezési előtagok frissítése ({0} elem)",
|
||||
"ToastTitleRequired": "A cím kötelező",
|
||||
"ToastUnknownError": "Ismeretlen hiba",
|
||||
"ToastUnlinkOpenIdFailed": "Nem sikerült leválasztani a felhasználót az OpenID-ről",
|
||||
"ToastUnlinkOpenIdSuccess": "Felhasználó leválasztva az OpenID-ről",
|
||||
"ToastUploaderFilepathExistsError": "A \"{0}\" fájl elérési útja már létezik a szerveren",
|
||||
"ToastUploaderItemExistsInSubdirectoryError": "A „{0}” elem a feltöltési útvonal egy alkönyvtárát használja.",
|
||||
"ToastUserDeleteFailed": "Felhasználó törlése sikertelen",
|
||||
"ToastUserDeleteSuccess": "Felhasználó törölve",
|
||||
"ToastUserPasswordChangeSuccess": "Jelszó sikeresen megváltoztatva",
|
||||
"ToastUserPasswordMismatch": "A jelszavak nem egyeznek",
|
||||
"ToastUserPasswordMustChange": "Az új jelszó nem egyezik a régi jelszóval",
|
||||
"ToastUserRootRequireName": "Egy root felhasználónevet kell megadnia"
|
||||
}
|
||||
|
||||
@@ -708,6 +708,7 @@
|
||||
"MessageAddToPlayerQueue": "Aggiungi alla coda di riproduzione",
|
||||
"MessageAppriseDescription": "Per utilizzare questa funzione è necessario disporre di un'istanza di <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> in esecuzione o un'API che gestirà quelle stesse richieste. <br />L'API Url dovrebbe essere il percorso URL completo per inviare la notifica, ad esempio se la tua istanza API è servita cosi .<code>http://192.168.1.1:8337</code> Allora dovrai mettere <code>http://192.168.1.1:8337/notify</code>.",
|
||||
"MessageAsinCheck": "Assicurati di utilizzare l'ASIN della regione Audible corretta, non di Amazon.",
|
||||
"MessageAuthenticationOIDCChangesRestart": "Riavvia il tuo server dopo aver salvato per applicare le modifiche OIDC.",
|
||||
"MessageBackupsDescription": "I backup includono utenti, progressi degli utenti, dettagli sugli elementi della libreria, impostazioni del server e immagini archiviate in <code>/metadata/items</code> & <code>/metadata/authors</code>. I backup non includono i file archiviati nelle cartelle della libreria.",
|
||||
"MessageBackupsLocationEditNote": "Nota: l'aggiornamento della posizione di backup non sposterà o modificherà i backup esistenti",
|
||||
"MessageBackupsLocationNoEditNote": "Nota: la posizione del backup viene impostata tramite una variabile di ambiente e non può essere modificata qui.",
|
||||
@@ -855,6 +856,7 @@
|
||||
"MessageScheduleRunEveryWeekdayAtTime": "Esegui ogni {0} alle {1}",
|
||||
"MessageSearchResultsFor": "cerca risultati per",
|
||||
"MessageSelected": "{0} selezionati",
|
||||
"MessageSeriesSequenceCannotContainSpaces": "La sequenza della serie non può contenere spazi",
|
||||
"MessageServerCouldNotBeReached": "Impossibile raggiungere il server",
|
||||
"MessageSetChaptersFromTracksDescription": "Impostare i capitoli utilizzando ciascun file audio come capitolo e il titolo del capitolo come nome del file audio",
|
||||
"MessageShareExpirationWillBe": "Scadrà tra <strong>{0}</strong>",
|
||||
|
||||
@@ -394,6 +394,7 @@
|
||||
"LabelIntervalEvery6Hours": "Iedere 6 uur",
|
||||
"LabelIntervalEveryDay": "Iedere dag",
|
||||
"LabelIntervalEveryHour": "Ieder uur",
|
||||
"LabelIntervalEveryMinute": "Elke minuut",
|
||||
"LabelInvert": "Omdraaien",
|
||||
"LabelItem": "Onderdeel",
|
||||
"LabelJumpBackwardAmount": "Terugspoelen hoeveelheid",
|
||||
@@ -424,7 +425,7 @@
|
||||
"LabelLookForNewEpisodesAfterDate": "Zoek naar nieuwe afleveringen na deze datum",
|
||||
"LabelLowestPriority": "Laagste Prioriteit",
|
||||
"LabelMatchExistingUsersBy": "Bestaande gebruikers matchen op",
|
||||
"LabelMatchExistingUsersByDescription": "Wordt gebruikt om bestaande gebruikers te verbinden. Zodra ze verbonden zijn, worden gebruikers gekoppeld aan een unieke id van uw SSO-provider.",
|
||||
"LabelMatchExistingUsersByDescription": "Wordt gebruikt om bestaande gebruikers te verbinden. Zodra ze verbonden zijn, worden gebruikers gekoppeld aan een unieke id van uw SSO-provider",
|
||||
"LabelMaxEpisodesToDownload": "Maximale # afleveringen om te downloaden. Gebruik 0 voor ongelimiteerd.",
|
||||
"LabelMaxEpisodesToDownloadPerCheck": "Maximale # nieuwe afleveringen om te downloaden per check",
|
||||
"LabelMaxEpisodesToKeep": "Maximale # afleveringen om te houden",
|
||||
@@ -529,6 +530,7 @@
|
||||
"LabelReleaseDate": "Verschijningsdatum",
|
||||
"LabelRemoveAllMetadataAbs": "Verwijder alle metadata.abs bestanden",
|
||||
"LabelRemoveAllMetadataJson": "Verwijder alle metadata.json bestanden",
|
||||
"LabelRemoveAudibleBranding": "Verwijder Audible intro en outro uit hoofdstukken",
|
||||
"LabelRemoveCover": "Verwijder cover",
|
||||
"LabelRemoveMetadataFile": "Verwijder metadata bestanden in bibliotheek item folders",
|
||||
"LabelRemoveMetadataFileHelp": "Verwijder alle metadata.json en metadata.abs bestanden in uw {0} folders.",
|
||||
@@ -558,6 +560,8 @@
|
||||
"LabelSettingsBookshelfViewHelp": "Skeumorphisch design met houten planken",
|
||||
"LabelSettingsChromecastSupport": "Chromecast ondersteuning",
|
||||
"LabelSettingsDateFormat": "Datum format",
|
||||
"LabelSettingsEnableWatcher": "Bibliotheken automatisch scannen op wijzigingen",
|
||||
"LabelSettingsEnableWatcherForLibrary": "Bibliotheek automatisch scannen op wijzigingen",
|
||||
"LabelSettingsEnableWatcherHelp": "Zorgt voor het automatisch toevoegen/bijwerken van onderdelen als bestandswijzigingen worden gedetecteerd. *Vereist herstarten van server",
|
||||
"LabelSettingsEpubsAllowScriptedContent": "Sta scripted content toe in epubs",
|
||||
"LabelSettingsEpubsAllowScriptedContentHelp": "Sta toe dat epub-bestanden scripts uitvoeren. Het wordt aanbevolen om deze instelling uitgeschakeld te houden, tenzij u de bron van de epub-bestanden vertrouwt.",
|
||||
@@ -719,6 +723,7 @@
|
||||
"MessageChapterErrorStartGteDuration": "Ongeldig: starttijd moet kleiner zijn dan duur van audioboek",
|
||||
"MessageChapterErrorStartLtPrev": "Ongeldig: starttijd moet be groter zijn dan of equal aan starttijd van vorig hoofdstuk",
|
||||
"MessageChapterStartIsAfter": "Start van hoofdstuk is na het einde van je audioboek",
|
||||
"MessageChaptersNotFound": "Hoofdstukken niet gevonden",
|
||||
"MessageCheckingCron": "Cron aan het checken...",
|
||||
"MessageConfirmCloseFeed": "Ben je zeker dat je deze feed wil sluiten?",
|
||||
"MessageConfirmDeleteBackup": "Weet je zeker dat je de backup voor {0} wil verwijderen?",
|
||||
@@ -775,6 +780,7 @@
|
||||
"MessageForceReScanDescription": "zal alle bestanden opnieuw scannen als een verse scan. Audiobestanden ID3-tags, OPF-bestanden en textbestanden zullen als nieuw worden gescand.",
|
||||
"MessageImportantNotice": "Belangrijke opmerking!",
|
||||
"MessageInsertChapterBelow": "Hoofdstuk hieronder invoegen",
|
||||
"MessageInvalidAsin": "Ongeldige ASIN",
|
||||
"MessageItemsSelected": "{0} onderdelen geselecteerd",
|
||||
"MessageItemsUpdated": "{0} onderdelen bijgewerkt",
|
||||
"MessageJoinUsOn": "Doe mee op",
|
||||
@@ -808,7 +814,7 @@
|
||||
"MessageNoItems": "Geen onderdelen",
|
||||
"MessageNoItemsFound": "Geen onderdelen gevonden",
|
||||
"MessageNoListeningSessions": "Geen luistersessies",
|
||||
"MessageNoLogs": "Geen logs",
|
||||
"MessageNoLogs": "Geen logbestanden",
|
||||
"MessageNoMediaProgress": "Geen mediavoortgang",
|
||||
"MessageNoNotifications": "Geen notificaties",
|
||||
"MessageNoPodcastFeed": "Ongeldige podcast: Geen Feed",
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"ButtonAddYourFirstLibrary": "Legg til ditt første bibliotek",
|
||||
"ButtonApply": "Bruk",
|
||||
"ButtonApplyChapters": "Bruk kapittel",
|
||||
"ButtonAuthors": "Forfatter",
|
||||
"ButtonAuthors": "Forfattere",
|
||||
"ButtonBack": "Tilbake",
|
||||
"ButtonBrowseForFolder": "Bla gjennom mappe",
|
||||
"ButtonCancel": "Avbryt",
|
||||
@@ -175,6 +175,7 @@
|
||||
"HeaderPlaylist": "Spilleliste",
|
||||
"HeaderPlaylistItems": "Spillelisteelement",
|
||||
"HeaderPodcastsToAdd": "Podcaster å legge til",
|
||||
"HeaderPresets": "Forhåndsinnstillinger",
|
||||
"HeaderPreviewCover": "Forhåndsvis omslag",
|
||||
"HeaderRSSFeedGeneral": "RSS Detailer",
|
||||
"HeaderRSSFeedIsOpen": "RSS Feed er åpen",
|
||||
@@ -217,6 +218,7 @@
|
||||
"LabelAccountTypeAdmin": "Administrator",
|
||||
"LabelAccountTypeGuest": "Gjest",
|
||||
"LabelAccountTypeUser": "Bruker",
|
||||
"LabelActivities": "Aktiviteter",
|
||||
"LabelActivity": "Aktivitet",
|
||||
"LabelAddToCollection": "Legg til i samling",
|
||||
"LabelAddToCollectionBatch": "Legg {0} bøker til samling",
|
||||
@@ -226,6 +228,7 @@
|
||||
"LabelAddedDate": "La til {0}",
|
||||
"LabelAdminUsersOnly": "Kun administratorer",
|
||||
"LabelAll": "Alle",
|
||||
"LabelAllEpisodesDownloaded": "Alle nedlastede episoder",
|
||||
"LabelAllUsers": "Alle brukere",
|
||||
"LabelAllUsersExcludingGuests": "Alle brukere bortsett fra gjester",
|
||||
"LabelAllUsersIncludingGuests": "Alle brukere inkludert gjester",
|
||||
@@ -281,6 +284,7 @@
|
||||
"LabelContinueSeries": "Fortsett serier",
|
||||
"LabelCover": "Omslag",
|
||||
"LabelCoverImageURL": "Omslagsbilde URL",
|
||||
"LabelCoverProvider": "Tilbyder av omslagsbilde",
|
||||
"LabelCreatedAt": "Dato opprettet",
|
||||
"LabelCronExpression": "Cron uttrykk",
|
||||
"LabelCurrent": "Nåværende",
|
||||
@@ -389,6 +393,7 @@
|
||||
"LabelIntervalEvery6Hours": "Hver 6. timer",
|
||||
"LabelIntervalEveryDay": "Hver dag",
|
||||
"LabelIntervalEveryHour": "Hver time",
|
||||
"LabelIntervalEveryMinute": "Hvert minutt",
|
||||
"LabelInvert": "Inverter",
|
||||
"LabelItem": "Enhet",
|
||||
"LabelJumpBackwardAmount": "Hopp bakover med",
|
||||
@@ -464,6 +469,7 @@
|
||||
"LabelNotificationsMaxQueueSizeHelp": "Hendelser er begrenset til avfyre én gang per sekund. Hendelser blir ignorert om køen er full. Dette forhindrer overflod av varslinger.",
|
||||
"LabelNumberOfBooks": "Antall bøker",
|
||||
"LabelNumberOfEpisodes": "Antall episoder",
|
||||
"LabelOpenIDAdvancedPermsClaimDescription": "Navnet på OpenID claim'et som inneholder avanserte tilganger for brukerhandlinger i applikasjonen som vil brukes for ikke-administratorroller (<b>hvis konfigurert</b>). Hvis claim'et mangler fra responsen, nektes tilgang til ABS. Hvis en enkelt opsjon mangler, blir behandlet som <code>false</code>. Påse at identitetstilbyderens claim stemmer overens med den forventede strukturen:",
|
||||
"LabelOpenIDClaims": "La følge valg være tomme for å slå av avanserte gruppe og tillatelser. Gruppen \"Bruker\" vil da også automatisk legges til.",
|
||||
"LabelOpenRSSFeed": "Åpne RSS Feed",
|
||||
"LabelOverwrite": "Overskriv",
|
||||
@@ -521,6 +527,7 @@
|
||||
"LabelReleaseDate": "Utgivelsesdato",
|
||||
"LabelRemoveAllMetadataAbs": "Fjern alle metadata.abs filer",
|
||||
"LabelRemoveAllMetadataJson": "Fjern alle metadata.json filer",
|
||||
"LabelRemoveAudibleBranding": "Fjern Audible inn- og utledning fra kapitler",
|
||||
"LabelRemoveCover": "Fjern omslag",
|
||||
"LabelRemoveMetadataFile": "Fjern metadata-filer fra biblioteks-mapper",
|
||||
"LabelRemoveMetadataFileHelp": "Fjern alle metadata.json og metadata.abs i alle {0} mappene.",
|
||||
@@ -550,6 +557,8 @@
|
||||
"LabelSettingsBookshelfViewHelp": "Skeuomorf design med hyller av ved",
|
||||
"LabelSettingsChromecastSupport": "Chromecast støtte",
|
||||
"LabelSettingsDateFormat": "Dato Format",
|
||||
"LabelSettingsEnableWatcher": "Skann biblioteker automatisk for endringer",
|
||||
"LabelSettingsEnableWatcherForLibrary": "Skann bibliotek automatisk for endringer",
|
||||
"LabelSettingsEnableWatcherHelp": "Aktiverer automatisk opprettelse/oppdatering av enheter når filendringer er oppdaget. *Krever restart av server*",
|
||||
"LabelSettingsEpubsAllowScriptedContent": "Tillat scripting i innholdet i ebub-bøker",
|
||||
"LabelSettingsEpubsAllowScriptedContentHelp": "Tillat epub-filer å kjøre script. Det er anbefalt å slå av denne innstillingen med mindre du stoler på kilden til epub-filene.",
|
||||
@@ -593,6 +602,7 @@
|
||||
"LabelSlug": "Slug",
|
||||
"LabelSortAscending": "Stigende",
|
||||
"LabelSortDescending": "Synkende",
|
||||
"LabelSortPubDate": "Sorter etter publiseringsdato",
|
||||
"LabelStart": "Start",
|
||||
"LabelStartTime": "Start Tid",
|
||||
"LabelStarted": "Startet",
|
||||
@@ -693,6 +703,8 @@
|
||||
"LabelYourProgress": "Din fremgang",
|
||||
"MessageAddToPlayerQueue": "Legg til i kø",
|
||||
"MessageAppriseDescription": "For å bruke denne funksjonen trenger du en instans av <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> kjørende eller et API som håndterer disse forespørslene. <br />Apprise API URL skal være hele URL-en til varslingen, f.eks., hvis din API-instans er på <code>http://192.168.1.1:8337</code> så skal du bruke <code>http://192.168.1.1:8337/notify</code>.",
|
||||
"MessageAsinCheck": "Påse at du bruker ASIN fra den riktige Audible-regionen, ikke Amazon.",
|
||||
"MessageAuthenticationOIDCChangesRestart": "Etter å ha lagret, start serveren din på nytt for at OIDC-endringene skal tre i kraft.",
|
||||
"MessageBackupsDescription": "Sikkerhetskopier inkluderer, brukerfremgang, detaljer om bibliotekgjenstander, tjener instillinger og bilder lagret under <code>/metadata/items</code> og <code>/metadata/authors</code>. Sikkerhetskopier <strong>vil ikke</strong> inkludere filer som er lagret i bibliotek mappene.",
|
||||
"MessageBackupsLocationEditNote": "Viktig: Endring av mappen for sikkerhetskopi hverken endrer eller flytter eksisterende sikkerhetskopier!",
|
||||
"MessageBackupsLocationNoEditNote": "NB: Mappen for sikkerhetskopi settes i en miljøvariabel og kan ikke endres her.",
|
||||
|
||||
@@ -329,7 +329,9 @@
|
||||
"LabelEpisode": "Odcinek",
|
||||
"LabelEpisodeTitle": "Tytuł odcinka",
|
||||
"LabelEpisodeType": "Typ odcinka",
|
||||
"LabelEpisodeUrlFromRssFeed": "Adres URL odcinka z kanału RSS",
|
||||
"LabelEpisodes": "Epizody",
|
||||
"LabelEpisodic": "Epizodyczny",
|
||||
"LabelExample": "Przykład",
|
||||
"LabelExpandSeries": "Rozwiń serie",
|
||||
"LabelExpandSubSeries": "Rozwiń podserie",
|
||||
@@ -357,6 +359,7 @@
|
||||
"LabelFontScale": "Rozmiar czcionki",
|
||||
"LabelFontStrikethrough": "Przekreślony",
|
||||
"LabelFormat": "Format",
|
||||
"LabelFull": "Pełny",
|
||||
"LabelGenre": "Gatunek",
|
||||
"LabelGenres": "Gatunki",
|
||||
"LabelHardDeleteFile": "Usuń trwale plik",
|
||||
@@ -381,6 +384,7 @@
|
||||
"LabelIntervalEvery6Hours": "Co 6 godzin",
|
||||
"LabelIntervalEveryDay": "Każdego dnia",
|
||||
"LabelIntervalEveryHour": "Każdej godziny",
|
||||
"LabelIntervalEveryMinute": "Co minutę",
|
||||
"LabelInvert": "Inversja",
|
||||
"LabelItem": "Pozycja",
|
||||
"LabelJumpBackwardAmount": "Przeskocz do tyłu o:",
|
||||
@@ -412,6 +416,9 @@
|
||||
"LabelLowestPriority": "Najniższy priorytet",
|
||||
"LabelMatchExistingUsersBy": "Dopasuje istniejących użytkowników poprzez",
|
||||
"LabelMatchExistingUsersByDescription": "Służy do łączenia istniejących użytkowników. Po połączeniu użytkownicy zostaną dopasowani za pomocą unikalnego identyfikatora od dostawcy SSO",
|
||||
"LabelMaxEpisodesToDownload": "Maksymalna liczba odcinków do pobrania. Użyj 0, aby wyłączyć ograniczenie.",
|
||||
"LabelMaxEpisodesToKeep": "Maksymalna liczba odcinków do zachowania",
|
||||
"LabelMaxEpisodesToKeepHelp": "Wartość 0 wyłącza maksymalny limit. Po automatycznym pobraniu nowego odcinka, najstarszy odcinek zostanie usunięty, jeśli masz ich więcej niż X. Spowoduje to usunięcie tylko 1 odcinka na nowe pobieranie.",
|
||||
"LabelMediaPlayer": "Odtwarzacz",
|
||||
"LabelMediaType": "Typ mediów",
|
||||
"LabelMetaTag": "Tag",
|
||||
@@ -424,6 +431,7 @@
|
||||
"LabelMissingEbook": "Nie posiada ebooka",
|
||||
"LabelMissingSupplementaryEbook": "Nie posiada dodatkowego ebooka",
|
||||
"LabelMobileRedirectURIs": "Dozwolone URI przekierowań mobilnych",
|
||||
"LabelMobileRedirectURIsDescription": "To jest biała lista prawidłowych adresów URI przekierowań dla aplikacji mobilnych. Domyślny adres to <code>audiobookshelf://oauth</code>, który można usunąć lub dodać inne adresy URI w celu integracji z aplikacjami innych firm. Użycie gwiazdki (<code>*</code>) jako jedynego wpisu zezwala na dowolny URI.",
|
||||
"LabelMore": "Więcej",
|
||||
"LabelMoreInfo": "Więcej informacji",
|
||||
"LabelName": "Nazwa",
|
||||
@@ -453,12 +461,14 @@
|
||||
"LabelNumberOfEpisodes": "# Odcinków",
|
||||
"LabelOpenRSSFeed": "Otwórz kanał RSS",
|
||||
"LabelOverwrite": "Nadpisz",
|
||||
"LabelPaginationPageXOfY": "Strona {0} z {1}",
|
||||
"LabelPassword": "Hasło",
|
||||
"LabelPath": "Ścieżka",
|
||||
"LabelPermanent": "Stałe",
|
||||
"LabelPermissionsAccessAllLibraries": "Ma dostęp do wszystkich bibliotek",
|
||||
"LabelPermissionsAccessAllTags": "Ma dostęp do wszystkich tagów",
|
||||
"LabelPermissionsAccessExplicitContent": "Ma dostęp do treści oznacznych jako nieprzyzwoite",
|
||||
"LabelPermissionsCreateEreader": "Możliwość stworzenia czytnika e-booków",
|
||||
"LabelPermissionsDelete": "Ma możliwość usuwania",
|
||||
"LabelPermissionsDownload": "Ma możliwość pobierania",
|
||||
"LabelPermissionsUpdate": "Ma możliwość aktualizowania",
|
||||
@@ -466,19 +476,25 @@
|
||||
"LabelPersonalYearReview": "Podsumowanie twojego roku ({0})",
|
||||
"LabelPhotoPathURL": "Scieżka/URL do zdjęcia",
|
||||
"LabelPlayMethod": "Metoda odtwarzania",
|
||||
"LabelPlayerChapterNumberMarker": "{0} z {1}",
|
||||
"LabelPlaylists": "Listy odtwarzania",
|
||||
"LabelPodcast": "Podcast",
|
||||
"LabelPodcastSearchRegion": "Obszar wyszukiwania podcastów",
|
||||
"LabelPodcastType": "Typ podcastu",
|
||||
"LabelPodcasts": "Podcasty",
|
||||
"LabelPort": "Port",
|
||||
"LabelPrefixesToIgnore": "Ignorowane prefiksy (wielkość liter nie ma znaczenia)",
|
||||
"LabelPreventIndexing": "Zapobiega indeksowaniu przez iTunes i Google",
|
||||
"LabelPrimaryEbook": "Główny ebook",
|
||||
"LabelProgress": "Postęp",
|
||||
"LabelProvider": "Dostawca",
|
||||
"LabelProviderAuthorizationValue": "Wartość nagłówka autoryzacji",
|
||||
"LabelPubDate": "Data publikacji",
|
||||
"LabelPublishYear": "Rok publikacji",
|
||||
"LabelPublishedDate": "Opublikowano {0}",
|
||||
"LabelPublisher": "Wydawca",
|
||||
"LabelPublishers": "Wydawcy",
|
||||
"LabelRSSFeedOpen": "RSS Feed otwarty",
|
||||
"LabelRSSFeedOpen": "Otwarty Kanał RSS",
|
||||
"LabelRSSFeedPreventIndexing": "Zapobiegaj indeksowaniu",
|
||||
"LabelRSSFeedURL": "URL kanały RSS",
|
||||
"LabelRandomly": "Losowo",
|
||||
@@ -490,15 +506,22 @@
|
||||
"LabelRecentlyAdded": "Niedawno dodane",
|
||||
"LabelRecommended": "Polecane",
|
||||
"LabelRedo": "Wycofaj",
|
||||
"LabelRegion": "Region",
|
||||
"LabelReleaseDate": "Data wydania",
|
||||
"LabelRemoveAllMetadataAbs": "Usuń wszystkie pliki metadata.abs",
|
||||
"LabelRemoveAllMetadataJson": "Usuń wszystkie pliki metadata.json",
|
||||
"LabelRemoveCover": "Usuń okładkę",
|
||||
"LabelRemoveMetadataFile": "Usuń pliki metadanych z folderów biblioteki",
|
||||
"LabelRemoveMetadataFileHelp": "Usuń wszystkie pliki metadata.json i metadata.abs z {0} folderów.",
|
||||
"LabelRowsPerPage": "Wierszy na stronę",
|
||||
"LabelSearchTerm": "Wyszukiwanie frazy",
|
||||
"LabelSearchTitle": "Wyszukaj tytuł",
|
||||
"LabelSearchTitleOrASIN": "Szukaj tytuł lub ASIN",
|
||||
"LabelSeason": "Sezon",
|
||||
"LabelSeasonNumber": "Sezon #{0}",
|
||||
"LabelSelectAll": "Wybierz wszystko",
|
||||
"LabelSelectAllEpisodes": "Wybierz wszystkie odcinki",
|
||||
"LabelSelectEpisodesShowing": "Wybierz {0} wyświetlanych odcinków",
|
||||
"LabelSelectUsers": "Wybór użytkowników",
|
||||
"LabelSendEbookToDevice": "Wyślij ebook do...",
|
||||
"LabelSequence": "Kolejność",
|
||||
@@ -513,6 +536,8 @@
|
||||
"LabelSettingsBookshelfViewHelp": "Widok półki z książkami",
|
||||
"LabelSettingsChromecastSupport": "Wsparcie Chromecast",
|
||||
"LabelSettingsDateFormat": "Format daty",
|
||||
"LabelSettingsEnableWatcher": "Automatyczne skanowanie bibliotek w poszukiwaniu zmian",
|
||||
"LabelSettingsEnableWatcherForLibrary": "Automatyczne skanowanie biblioteki w poszukiwaniu zmian",
|
||||
"LabelSettingsEnableWatcherHelp": "Włącza automatyczne dodawanie/aktualizację pozycji gdy wykryte zostaną zmiany w plikach. Wymaga restartu serwera",
|
||||
"LabelSettingsEpubsAllowScriptedContent": "Zezwalanie na skrypty w plikach epub",
|
||||
"LabelSettingsEpubsAllowScriptedContentHelp": "Zezwala plikom epub na wykonywanie skryptów. Zaleca się mieć to ustawienie wyłączone, chyba że ma się zaufanie do źródła plików epub.",
|
||||
@@ -524,6 +549,8 @@
|
||||
"LabelSettingsHideSingleBookSeriesHelp": "Serie, które posiadają tylko jedną książkę, nie będą pokazywane na stronie z seriami i na stronie domowej z półkami.",
|
||||
"LabelSettingsHomePageBookshelfView": "Widok półki z książkami na stronie głównej",
|
||||
"LabelSettingsLibraryBookshelfView": "Widok półki z książkami na stronie biblioteki",
|
||||
"LabelSettingsLibraryMarkAsFinishedWhen": "Oznacz element multimedialny jako ukończony, gdy",
|
||||
"LabelSettingsOnlyShowLaterBooksInContinueSeries": "Pomiń poprzednie książki przy kontynuacji serii",
|
||||
"LabelSettingsParseSubtitles": "Przetwarzaj podtytuły",
|
||||
"LabelSettingsParseSubtitlesHelp": "Opcja pozwala na pobranie podtytułu z nazwy folderu z audiobookiem. <br>Podtytuł musi być rozdzielony za pomocą separatora \" - \"<br>Przykład: \"Book Title - A Subtitle Here\" podtytuł \"A Subtitle Here\"",
|
||||
"LabelSettingsPreferMatchedMetadata": "Preferowanie dopasowanych metadanych",
|
||||
@@ -547,6 +574,9 @@
|
||||
"LabelShowSubtitles": "Pokaż Napisy",
|
||||
"LabelSize": "Rozmiar",
|
||||
"LabelSleepTimer": "Wyłącznik czasowy",
|
||||
"LabelSortAscending": "Rosnąco",
|
||||
"LabelSortDescending": "Malejąco",
|
||||
"LabelSortPubDate": "Sortuj według daty publikacji",
|
||||
"LabelStart": "Rozpocznij",
|
||||
"LabelStartTime": "Czas rozpoczęcia",
|
||||
"LabelStarted": "Rozpoczęty",
|
||||
@@ -568,14 +598,21 @@
|
||||
"LabelStatsWeekListening": "Tydzień słuchania",
|
||||
"LabelSubtitle": "Podtytuł",
|
||||
"LabelSupportedFileTypes": "Obsługiwane typy plików",
|
||||
"LabelTag": "Znacznik",
|
||||
"LabelTags": "Tagi",
|
||||
"LabelTagsAccessibleToUser": "Tagi dostępne dla użytkownika",
|
||||
"LabelTagsNotAccessibleToUser": "Znaczniki niedostępne dla użytkownika",
|
||||
"LabelTasks": "Uruchomione zadania",
|
||||
"LabelTextEditorLink": "Link",
|
||||
"LabelTextEditorNumberedList": "Lista numerowana",
|
||||
"LabelTextEditorUnlink": "Usuń link",
|
||||
"LabelThemeDark": "Ciemny",
|
||||
"LabelThemeLight": "Jasny",
|
||||
"LabelTimeDurationXHours": "{0} godzin",
|
||||
"LabelTimeDurationXMinutes": "{0} minuty",
|
||||
"LabelTimeDurationXSeconds": "{0} sekundy",
|
||||
"LabelTimeInMinutes": "Czas w minutach",
|
||||
"LabelTimeLeft": "pozostało {0}",
|
||||
"LabelTimeListened": "Czas odtwarzania",
|
||||
"LabelTimeListenedToday": "Czas odtwarzania dzisiaj",
|
||||
"LabelTimeRemaining": "Pozostało {0}",
|
||||
@@ -583,6 +620,7 @@
|
||||
"LabelTitle": "Tytuł",
|
||||
"LabelToolsEmbedMetadata": "Załącz metadane",
|
||||
"LabelToolsEmbedMetadataDescription": "Załącz metadane do plików audio (okładkę oraz znaczniki rozdziałów).",
|
||||
"LabelToolsM4bEncoder": "Enkoder M4B",
|
||||
"LabelToolsMakeM4b": "Generuj plik M4B",
|
||||
"LabelToolsMakeM4bDescription": "Tworzy plik w formacie .M4B, który zawiera metadane, okładkę oraz rozdziały.",
|
||||
"LabelToolsSplitM4b": "Podziel plik .M4B na pliki .MP3",
|
||||
@@ -595,12 +633,14 @@
|
||||
"LabelType": "Typ",
|
||||
"LabelUndo": "Wycofaj",
|
||||
"LabelUnknown": "Nieznany",
|
||||
"LabelUnknownPublishDate": "Nieznana data publikacji",
|
||||
"LabelUpdateCover": "Zaktalizuj odkładkę",
|
||||
"LabelUpdateCoverHelp": "Umożliwienie nadpisania istniejących okładek dla wybranych książek w przypadku znalezienia dopasowania",
|
||||
"LabelUpdateDetails": "Zaktualizuj szczegóły",
|
||||
"LabelUpdateDetailsHelp": "Umożliwienie nadpisania istniejących szczegółów dla wybranych książek w przypadku znalezienia dopasowania",
|
||||
"LabelUpdatedAt": "Zaktualizowano",
|
||||
"LabelUploaderDragAndDrop": "Przeciągnij i puść foldery lub pliki",
|
||||
"LabelUploaderDragAndDropFilesOnly": "Przeciągnij i upuść pliki",
|
||||
"LabelUploaderDropFiles": "Puść pliki",
|
||||
"LabelUploaderItemFetchMetadataHelp": "Automatycznie pobierz tytuł, autora i serie",
|
||||
"LabelUseChapterTrack": "Użyj ścieżki rozdziału",
|
||||
|
||||
@@ -212,9 +212,9 @@
|
||||
"HeaderUsers": "Пользователи",
|
||||
"HeaderYearReview": "Итоги {0} года",
|
||||
"HeaderYourStats": "Ваша статистика",
|
||||
"LabelAbridged": "Сокращенное издание",
|
||||
"LabelAbridged": "Сокращенная форма",
|
||||
"LabelAbridgedChecked": "Сокращено (отмечено)",
|
||||
"LabelAbridgedUnchecked": "Без сокращений (не отмечено)",
|
||||
"LabelAbridgedUnchecked": "Полное издание (не отмечено)",
|
||||
"LabelAccessibleBy": "Доступ",
|
||||
"LabelAccountType": "Тип учетной записи",
|
||||
"LabelAccountTypeAdmin": "Администратор",
|
||||
@@ -346,9 +346,9 @@
|
||||
"LabelExample": "Пример",
|
||||
"LabelExpandSeries": "Развернуть серию",
|
||||
"LabelExpandSubSeries": "Развернуть подсерию",
|
||||
"LabelExplicit": "Явный",
|
||||
"LabelExplicitChecked": "Явный (отмечено)",
|
||||
"LabelExplicitUnchecked": "Не явно (не отмечено)",
|
||||
"LabelExplicit": "18+",
|
||||
"LabelExplicitChecked": "18+ (отмечено)",
|
||||
"LabelExplicitUnchecked": "+18 (не отмечено)",
|
||||
"LabelExportOPML": "Экспорт OPML",
|
||||
"LabelFeedURL": "URL канала",
|
||||
"LabelFetchingMetadata": "Извлечение метаданных",
|
||||
@@ -514,7 +514,7 @@
|
||||
"LabelPublishers": "Издатели",
|
||||
"LabelRSSFeedCustomOwnerEmail": "Пользовательский Email владельца",
|
||||
"LabelRSSFeedCustomOwnerName": "Пользовательское Имя владельца",
|
||||
"LabelRSSFeedOpen": "Открыть RSS-канал",
|
||||
"LabelRSSFeedOpen": "Открыть RSS-ленту",
|
||||
"LabelRSSFeedPreventIndexing": "Запретить индексирование",
|
||||
"LabelRSSFeedSlug": "Встроить RSS-канал",
|
||||
"LabelRSSFeedURL": "URL RSS-канала",
|
||||
@@ -856,6 +856,7 @@
|
||||
"MessageScheduleRunEveryWeekdayAtTime": "Запуск каждые {0} по {1}",
|
||||
"MessageSearchResultsFor": "Результаты поиска для",
|
||||
"MessageSelected": "{0} выбрано",
|
||||
"MessageSeriesSequenceCannotContainSpaces": "Последовательность серии должна быть без пропусков",
|
||||
"MessageServerCouldNotBeReached": "Не удалось связаться с сервером",
|
||||
"MessageSetChaptersFromTracksDescription": "Установка глав с использованием каждого аудиофайла в качестве главы и заголовка главы в качестве имени аудиофайла",
|
||||
"MessageShareExpirationWillBe": "Срок действия истекает <strong>{0}</strong>",
|
||||
@@ -917,6 +918,8 @@
|
||||
"NotificationOnBackupCompletedDescription": "Запускается при завершении резервного копирования",
|
||||
"NotificationOnBackupFailedDescription": "Срабатывает при сбое резервного копирования",
|
||||
"NotificationOnEpisodeDownloadedDescription": "Запускается при автоматической загрузке эпизода подкаста",
|
||||
"NotificationOnRSSFeedDisabledDescription": "Срабатывает, когда автоматическая загрузка эпизодов отключена из-за слишком большого количества неудачных попыток",
|
||||
"NotificationOnRSSFeedFailedDescription": "Срабатывает при сбое запроса RSS-канала на автоматическую загрузку эпизода",
|
||||
"NotificationOnTestDescription": "Событие для тестирования системы оповещения",
|
||||
"PlaceholderNewCollection": "Новое имя коллекции",
|
||||
"PlaceholderNewFolderPath": "Путь к новой папке",
|
||||
|
||||
@@ -514,7 +514,7 @@
|
||||
"LabelPublishers": "Izdajatelji",
|
||||
"LabelRSSFeedCustomOwnerEmail": "E-pošta lastnika po meri",
|
||||
"LabelRSSFeedCustomOwnerName": "Ime lastnika po meri",
|
||||
"LabelRSSFeedOpen": "Odprt vir RSS",
|
||||
"LabelRSSFeedOpen": "RSS vir je odprt",
|
||||
"LabelRSSFeedPreventIndexing": "Prepreči indeksiranje",
|
||||
"LabelRSSFeedSlug": "Slug RSS vira",
|
||||
"LabelRSSFeedURL": "URL vira RSS",
|
||||
@@ -918,6 +918,8 @@
|
||||
"NotificationOnBackupCompletedDescription": "Sproži se, ko je varnostno kopiranje končano",
|
||||
"NotificationOnBackupFailedDescription": "Sproži se, ko varnostno kopiranje ne uspe",
|
||||
"NotificationOnEpisodeDownloadedDescription": "Sproži se, ko se epizoda podcasta samodejno prenese",
|
||||
"NotificationOnRSSFeedDisabledDescription": "Sproži se, ko so samodejni prenosi epizod onemogočeni zaradi preveč neuspelih poskusov",
|
||||
"NotificationOnRSSFeedFailedDescription": "Sproži se, ko zahteva za vir RSS za samodejni prenos epizode ne uspe",
|
||||
"NotificationOnTestDescription": "Dogodek za testiranje sistema obveščanja",
|
||||
"PlaceholderNewCollection": "Novo ime zbirke",
|
||||
"PlaceholderNewFolderPath": "Pot nove mape",
|
||||
|
||||
@@ -514,7 +514,7 @@
|
||||
"LabelPublishers": "Видавці",
|
||||
"LabelRSSFeedCustomOwnerEmail": "Користувацька електронна адреса власника",
|
||||
"LabelRSSFeedCustomOwnerName": "Користувацьке ім'я власника",
|
||||
"LabelRSSFeedOpen": "RSS-канал відкрито",
|
||||
"LabelRSSFeedOpen": "RSS-канал відкритий",
|
||||
"LabelRSSFeedPreventIndexing": "Запобігати індексації",
|
||||
"LabelRSSFeedSlug": "Назва RSS-каналу",
|
||||
"LabelRSSFeedURL": "Адреса RSS-каналу",
|
||||
@@ -918,6 +918,8 @@
|
||||
"NotificationOnBackupCompletedDescription": "Запускається після завершення резервного копіювання",
|
||||
"NotificationOnBackupFailedDescription": "Срабатывает при збої резервного копіювання",
|
||||
"NotificationOnEpisodeDownloadedDescription": "Запускається при автоматичному завантаженні епізоду подкасту",
|
||||
"NotificationOnRSSFeedDisabledDescription": "Активується, коли автоматичне завантаження епізодів вимкнено через занадто багато невдалих спроб",
|
||||
"NotificationOnRSSFeedFailedDescription": "Активується, коли запит RSS-каналу не вдається виконати для автоматичного завантаження епізоду",
|
||||
"NotificationOnTestDescription": "Подія для тестування системи сповіщень",
|
||||
"PlaceholderNewCollection": "Нова назва добірки",
|
||||
"PlaceholderNewFolderPath": "Новий шлях до теки",
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"ButtonChooseFiles": "选择文件",
|
||||
"ButtonClearFilter": "清除过滤器",
|
||||
"ButtonCloseFeed": "关闭源",
|
||||
"ButtonCloseSession": "关闭开放会话",
|
||||
"ButtonCloseSession": "关闭活动会话",
|
||||
"ButtonCollections": "收藏",
|
||||
"ButtonConfigureScanner": "配置扫描",
|
||||
"ButtonCreate": "创建",
|
||||
|
||||
1
index.js
1
index.js
@@ -28,6 +28,7 @@ if (isDev) {
|
||||
if (devEnv.SkipBinariesCheck) process.env.SKIP_BINARIES_CHECK = '1'
|
||||
if (devEnv.AllowIframe) process.env.ALLOW_IFRAME = '1'
|
||||
if (devEnv.BackupPath) process.env.BACKUP_PATH = devEnv.BackupPath
|
||||
if (devEnv.ReactClientPath) process.env.REACT_CLIENT_PATH = devEnv.ReactClientPath
|
||||
process.env.SOURCE = 'local'
|
||||
process.env.ROUTER_BASE_PATH = devEnv.RouterBasePath ?? '/audiobookshelf'
|
||||
}
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "audiobookshelf",
|
||||
"version": "2.23.0",
|
||||
"version": "2.25.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "audiobookshelf",
|
||||
"version": "2.23.0",
|
||||
"version": "2.25.1",
|
||||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
"axios": "^0.27.2",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "audiobookshelf",
|
||||
"version": "2.23.0",
|
||||
"version": "2.25.1",
|
||||
"buildNumber": 1,
|
||||
"description": "Self-hosted audiobook and podcast server",
|
||||
"main": "index.js",
|
||||
|
||||
@@ -442,7 +442,17 @@ class Auth {
|
||||
// Local strategy login route (takes username and password)
|
||||
router.post('/login', passport.authenticate('local'), async (req, res) => {
|
||||
// return the user login response json if the login was successfull
|
||||
res.json(await this.getUserLoginResponsePayload(req.user))
|
||||
const userResponse = await this.getUserLoginResponsePayload(req.user)
|
||||
|
||||
// Experimental Next.js client uses bearer token in cookies
|
||||
res.cookie('auth_token', userResponse.user.token, {
|
||||
httpOnly: true,
|
||||
secure: req.secure || req.get('x-forwarded-proto') === 'https',
|
||||
sameSite: 'strict',
|
||||
maxAge: 1000 * 60 * 60 * 24 * 7 // 7 days
|
||||
})
|
||||
|
||||
res.json(userResponse)
|
||||
})
|
||||
|
||||
// openid strategy login route (this redirects to the configured openid login provider)
|
||||
@@ -718,6 +728,7 @@ class Auth {
|
||||
const authMethod = req.cookies.auth_method
|
||||
|
||||
res.clearCookie('auth_method')
|
||||
res.clearCookie('auth_token')
|
||||
|
||||
let logoutUrl = null
|
||||
|
||||
|
||||
@@ -765,6 +765,26 @@ class Database {
|
||||
if (badSessionsRemoved > 0) {
|
||||
Logger.warn(`Removed ${badSessionsRemoved} sessions that were 3 seconds or less`)
|
||||
}
|
||||
|
||||
// Remove mediaProgresses with duplicate mediaItemId (remove the oldest updatedAt or if updatedAt is the same, remove arbitrary one)
|
||||
const [duplicateMediaProgresses] = await this.sequelize.query(`SELECT mp1.id, mp1.mediaItemId
|
||||
FROM mediaProgresses mp1
|
||||
WHERE EXISTS (
|
||||
SELECT 1
|
||||
FROM mediaProgresses mp2
|
||||
WHERE mp2.mediaItemId = mp1.mediaItemId
|
||||
AND mp2.userId = mp1.userId
|
||||
AND (
|
||||
mp2.updatedAt > mp1.updatedAt
|
||||
OR (mp2.updatedAt = mp1.updatedAt AND mp2.id < mp1.id)
|
||||
)
|
||||
)`)
|
||||
for (const duplicateMediaProgress of duplicateMediaProgresses) {
|
||||
Logger.warn(`Found duplicate mediaProgress for mediaItem "${duplicateMediaProgress.mediaItemId}" - removing it`)
|
||||
await this.mediaProgressModel.destroy({
|
||||
where: { id: duplicateMediaProgress.id }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async createTextSearchQuery(query) {
|
||||
|
||||
@@ -12,6 +12,7 @@ const { version } = require('../package.json')
|
||||
|
||||
// Utils
|
||||
const fileUtils = require('./utils/fileUtils')
|
||||
const { toNumber } = require('./utils/index')
|
||||
const Logger = require('./Logger')
|
||||
|
||||
const Auth = require('./Auth')
|
||||
@@ -84,12 +85,8 @@ class Server {
|
||||
global.DisableSsrfRequestFilter = (url) => whitelistedUrls.includes(new URL(url).hostname)
|
||||
}
|
||||
}
|
||||
|
||||
if (process.env.PODCAST_DOWNLOAD_TIMEOUT) {
|
||||
global.PodcastDownloadTimeout = process.env.PODCAST_DOWNLOAD_TIMEOUT
|
||||
} else {
|
||||
global.PodcastDownloadTimeout = 30000
|
||||
}
|
||||
global.PodcastDownloadTimeout = toNumber(process.env.PODCAST_DOWNLOAD_TIMEOUT, 30000)
|
||||
global.MaxFailedEpisodeChecks = toNumber(process.env.MAX_FAILED_EPISODE_CHECKS, 24)
|
||||
|
||||
if (!fs.pathExistsSync(global.ConfigPath)) {
|
||||
fs.mkdirSync(global.ConfigPath)
|
||||
@@ -223,6 +220,7 @@ class Server {
|
||||
|
||||
async start() {
|
||||
Logger.info('=== Starting Server ===')
|
||||
|
||||
this.initProcessEventListeners()
|
||||
await this.init()
|
||||
|
||||
@@ -284,6 +282,7 @@ class Server {
|
||||
await this.auth.initPassportJs()
|
||||
|
||||
const router = express.Router()
|
||||
|
||||
// if RouterBasePath is set, modify all requests to include the base path
|
||||
app.use((req, res, next) => {
|
||||
const urlStartsWithRouterBasePath = req.url.startsWith(global.RouterBasePath)
|
||||
@@ -316,10 +315,6 @@ class Server {
|
||||
router.use('/hls', this.hlsRouter.router)
|
||||
router.use('/public', this.publicRouter.router)
|
||||
|
||||
// Static path to generated nuxt
|
||||
const distPath = Path.join(global.appRoot, '/client/dist')
|
||||
router.use(express.static(distPath))
|
||||
|
||||
// Static folder
|
||||
router.use(express.static(Path.join(global.appRoot, 'static')))
|
||||
|
||||
@@ -339,32 +334,6 @@ class Server {
|
||||
// Auth routes
|
||||
await this.auth.initAuthRoutes(router)
|
||||
|
||||
// Client dynamic routes
|
||||
const dynamicRoutes = [
|
||||
'/item/:id',
|
||||
'/author/:id',
|
||||
'/audiobook/:id/chapters',
|
||||
'/audiobook/:id/edit',
|
||||
'/audiobook/:id/manage',
|
||||
'/library/:library',
|
||||
'/library/:library/search',
|
||||
'/library/:library/bookshelf/:id?',
|
||||
'/library/:library/authors',
|
||||
'/library/:library/narrators',
|
||||
'/library/:library/stats',
|
||||
'/library/:library/series/:id?',
|
||||
'/library/:library/podcast/search',
|
||||
'/library/:library/podcast/latest',
|
||||
'/library/:library/podcast/download-queue',
|
||||
'/config/users/:id',
|
||||
'/config/users/:id/sessions',
|
||||
'/config/item-metadata-utils/:id',
|
||||
'/collection/:id',
|
||||
'/playlist/:id',
|
||||
'/share/:slug'
|
||||
]
|
||||
dynamicRoutes.forEach((route) => router.get(route, (req, res) => res.sendFile(Path.join(distPath, 'index.html'))))
|
||||
|
||||
router.post('/init', (req, res) => {
|
||||
if (Database.hasRootUser) {
|
||||
Logger.error(`[Server] attempt to init server when server already has a root user`)
|
||||
@@ -395,6 +364,48 @@ class Server {
|
||||
})
|
||||
router.get('/healthcheck', (req, res) => res.sendStatus(200))
|
||||
|
||||
const ReactClientPath = process.env.REACT_CLIENT_PATH
|
||||
if (!ReactClientPath) {
|
||||
// Static path to generated nuxt
|
||||
const distPath = Path.join(global.appRoot, '/client/dist')
|
||||
router.use(express.static(distPath))
|
||||
|
||||
// Client dynamic routes
|
||||
const dynamicRoutes = [
|
||||
'/item/:id',
|
||||
'/author/:id',
|
||||
'/audiobook/:id/chapters',
|
||||
'/audiobook/:id/edit',
|
||||
'/audiobook/:id/manage',
|
||||
'/library/:library',
|
||||
'/library/:library/search',
|
||||
'/library/:library/bookshelf/:id?',
|
||||
'/library/:library/authors',
|
||||
'/library/:library/narrators',
|
||||
'/library/:library/stats',
|
||||
'/library/:library/series/:id?',
|
||||
'/library/:library/podcast/search',
|
||||
'/library/:library/podcast/latest',
|
||||
'/library/:library/podcast/download-queue',
|
||||
'/config/users/:id',
|
||||
'/config/users/:id/sessions',
|
||||
'/config/item-metadata-utils/:id',
|
||||
'/collection/:id',
|
||||
'/playlist/:id',
|
||||
'/share/:slug'
|
||||
]
|
||||
dynamicRoutes.forEach((route) => router.get(route, (req, res) => res.sendFile(Path.join(distPath, 'index.html'))))
|
||||
} else {
|
||||
// This is for using the experimental Next.js client
|
||||
Logger.info(`Using React client at ${ReactClientPath}`)
|
||||
const nextPath = Path.join(ReactClientPath, 'node_modules/next')
|
||||
const next = require(nextPath)
|
||||
const nextApp = next({ dev: Logger.isDev, dir: ReactClientPath })
|
||||
const handle = nextApp.getRequestHandler()
|
||||
await nextApp.prepare()
|
||||
router.get('*', (req, res) => handle(req, res))
|
||||
}
|
||||
|
||||
const unixSocketPrefix = 'unix/'
|
||||
if (this.Host?.startsWith(unixSocketPrefix)) {
|
||||
const sockPath = this.Host.slice(unixSocketPrefix.length)
|
||||
|
||||
@@ -89,7 +89,6 @@ class FileSystemController {
|
||||
}
|
||||
|
||||
const { directory, folderPath } = req.body
|
||||
|
||||
if (!directory?.length || typeof directory !== 'string' || !folderPath?.length || typeof folderPath !== 'string') {
|
||||
Logger.error(`[FileSystemController] Invalid request body: ${JSON.stringify(req.body)}`)
|
||||
return res.status(400).json({
|
||||
@@ -109,7 +108,14 @@ class FileSystemController {
|
||||
return res.sendStatus(404)
|
||||
}
|
||||
|
||||
const filepath = Path.posix.join(libraryFolder.path, directory)
|
||||
if (!req.user.checkCanAccessLibrary(libraryFolder.libraryId)) {
|
||||
Logger.error(`[FileSystemController] User "${req.user.username}" attempting to check path exists for library "${libraryFolder.libraryId}" without access`)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
let filepath = Path.join(libraryFolder.path, directory)
|
||||
filepath = fileUtils.filePathToPOSIX(filepath)
|
||||
|
||||
// Ensure filepath is inside library folder (prevents directory traversal)
|
||||
if (!filepath.startsWith(libraryFolder.path)) {
|
||||
Logger.error(`[FileSystemController] Filepath is not inside library folder: ${filepath}`)
|
||||
|
||||
@@ -59,6 +59,12 @@ class MiscController {
|
||||
if (!library) {
|
||||
return res.status(404).send('Library not found')
|
||||
}
|
||||
|
||||
if (!req.user.checkCanAccessLibrary(library.id)) {
|
||||
Logger.error(`[MiscController] User "${req.user.username}" attempting to upload to library "${library.id}" without access`)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
const folder = library.libraryFolders.find((fold) => fold.id === folderId)
|
||||
if (!folder) {
|
||||
return res.status(404).send('Folder not found')
|
||||
|
||||
13
server/libs/fusejs/index.js
Normal file
13
server/libs/fusejs/index.js
Normal file
File diff suppressed because one or more lines are too long
@@ -203,7 +203,15 @@ class AbMergeManager {
|
||||
// Move library item tracks to cache
|
||||
for (const [index, trackPath] of task.data.originalTrackPaths.entries()) {
|
||||
const trackFilename = Path.basename(trackPath)
|
||||
const moveToPath = Path.join(task.data.itemCachePath, trackFilename)
|
||||
let moveToPath = Path.join(task.data.itemCachePath, trackFilename)
|
||||
|
||||
// If the track is the same as the temp file, we need to rename it to avoid overwriting it
|
||||
if (task.data.tempFilepath === moveToPath) {
|
||||
const trackExtname = Path.extname(task.data.tempFilepath)
|
||||
const newTrackFilename = Path.basename(task.data.tempFilepath, trackExtname) + '.backup' + trackExtname
|
||||
moveToPath = Path.join(task.data.itemCachePath, newTrackFilename)
|
||||
}
|
||||
|
||||
Logger.debug(`[AbMergeManager] Backing up original track "${trackPath}" to ${moveToPath}`)
|
||||
if (index === 0) {
|
||||
// copy the first track to the cache directory
|
||||
|
||||
@@ -71,6 +71,54 @@ class NotificationManager {
|
||||
this.triggerNotification('onBackupCompleted', eventData)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles scheduled episode download RSS feed request failed
|
||||
*
|
||||
* @param {string} feedUrl
|
||||
* @param {number} numFailed
|
||||
* @param {string} title
|
||||
*/
|
||||
async onRSSFeedFailed(feedUrl, numFailed, title) {
|
||||
if (!Database.notificationSettings.isUseable) return
|
||||
|
||||
if (!Database.notificationSettings.getHasActiveNotificationsForEvent('onRSSFeedFailed')) {
|
||||
Logger.debug(`[NotificationManager] onRSSFeedFailed: No active notifications`)
|
||||
return
|
||||
}
|
||||
|
||||
Logger.debug(`[NotificationManager] onRSSFeedFailed: RSS feed request failed for ${feedUrl}`)
|
||||
const eventData = {
|
||||
feedUrl: feedUrl,
|
||||
numFailed: numFailed || 0,
|
||||
title: title || 'Unknown Title'
|
||||
}
|
||||
this.triggerNotification('onRSSFeedFailed', eventData)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles scheduled episode downloads disabled due to too many failed attempts
|
||||
*
|
||||
* @param {string} feedUrl
|
||||
* @param {number} numFailed
|
||||
* @param {string} title
|
||||
*/
|
||||
async onRSSFeedDisabled(feedUrl, numFailed, title) {
|
||||
if (!Database.notificationSettings.isUseable) return
|
||||
|
||||
if (!Database.notificationSettings.getHasActiveNotificationsForEvent('onRSSFeedDisabled')) {
|
||||
Logger.debug(`[NotificationManager] onRSSFeedDisabled: No active notifications`)
|
||||
return
|
||||
}
|
||||
|
||||
Logger.debug(`[NotificationManager] onRSSFeedDisabled: Podcast scheduled episode download disabled due to ${numFailed} failed requests for ${feedUrl}`)
|
||||
const eventData = {
|
||||
feedUrl: feedUrl,
|
||||
numFailed: numFailed || 0,
|
||||
title: title || 'Unknown Title'
|
||||
}
|
||||
this.triggerNotification('onRSSFeedDisabled', eventData)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} errorMsg
|
||||
|
||||
@@ -107,7 +107,7 @@ class PlaybackSessionManager {
|
||||
|
||||
const syncResults = []
|
||||
for (const sessionJson of sessions) {
|
||||
Logger.info(`[PlaybackSessionManager] Syncing local session "${sessionJson.displayTitle}" (${sessionJson.id})`)
|
||||
Logger.info(`[PlaybackSessionManager] Syncing local session "${sessionJson.displayTitle}" (${sessionJson.id}) (updatedAt: ${sessionJson.updatedAt})`)
|
||||
const result = await this.syncLocalSession(user, sessionJson, deviceInfo)
|
||||
syncResults.push(result)
|
||||
}
|
||||
@@ -230,9 +230,9 @@ class PlaybackSessionManager {
|
||||
let userProgressForItem = user.getMediaProgress(mediaItemId)
|
||||
if (userProgressForItem) {
|
||||
if (userProgressForItem.updatedAt.valueOf() > session.updatedAt) {
|
||||
Logger.debug(`[PlaybackSessionManager] Not updating progress for "${session.displayTitle}" because it has been updated more recently`)
|
||||
Logger.info(`[PlaybackSessionManager] Not updating progress for "${session.displayTitle}" because it has been updated more recently (${userProgressForItem.updatedAt.valueOf()} > ${session.updatedAt}) (incoming currentTime: ${session.currentTime}) (current currentTime: ${userProgressForItem.currentTime})`)
|
||||
} else {
|
||||
Logger.debug(`[PlaybackSessionManager] Updating progress for "${session.displayTitle}" with current time ${session.currentTime} (previously ${userProgressForItem.currentTime})`)
|
||||
Logger.info(`[PlaybackSessionManager] Updating progress for "${session.displayTitle}" with current time ${session.currentTime} (previously ${userProgressForItem.currentTime})`)
|
||||
const updateResponse = await user.createUpdateMediaProgressFromPayload({
|
||||
libraryItemId: libraryItem.id,
|
||||
episodeId: session.episodeId,
|
||||
@@ -246,7 +246,7 @@ class PlaybackSessionManager {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Logger.debug(`[PlaybackSessionManager] Creating new media progress for media item "${session.displayTitle}"`)
|
||||
Logger.info(`[PlaybackSessionManager] Creating new media progress for media item "${session.displayTitle}"`)
|
||||
const updateResponse = await user.createUpdateMediaProgressFromPayload({
|
||||
libraryItemId: libraryItem.id,
|
||||
episodeId: session.episodeId,
|
||||
|
||||
@@ -30,7 +30,7 @@ class PodcastManager {
|
||||
this.currentDownload = null
|
||||
|
||||
this.failedCheckMap = {}
|
||||
this.MaxFailedEpisodeChecks = 24
|
||||
this.MaxFailedEpisodeChecks = global.MaxFailedEpisodeChecks
|
||||
}
|
||||
|
||||
getEpisodeDownloadsInQueue(libraryItemId) {
|
||||
@@ -345,12 +345,14 @@ class PodcastManager {
|
||||
// Allow up to MaxFailedEpisodeChecks failed attempts before disabling auto download
|
||||
if (!this.failedCheckMap[libraryItem.id]) this.failedCheckMap[libraryItem.id] = 0
|
||||
this.failedCheckMap[libraryItem.id]++
|
||||
if (this.failedCheckMap[libraryItem.id] >= this.MaxFailedEpisodeChecks) {
|
||||
if (this.MaxFailedEpisodeChecks !== 0 && this.failedCheckMap[libraryItem.id] >= this.MaxFailedEpisodeChecks) {
|
||||
Logger.error(`[PodcastManager] runEpisodeCheck ${this.failedCheckMap[libraryItem.id]} failed attempts at checking episodes for "${libraryItem.media.title}" - disabling auto download`)
|
||||
void NotificationManager.onRSSFeedDisabled(libraryItem.media.feedURL, this.failedCheckMap[libraryItem.id], libraryItem.media.title)
|
||||
libraryItem.media.autoDownloadEpisodes = false
|
||||
delete this.failedCheckMap[libraryItem.id]
|
||||
} else {
|
||||
Logger.warn(`[PodcastManager] runEpisodeCheck ${this.failedCheckMap[libraryItem.id]} failed attempts at checking episodes for "${libraryItem.media.title}"`)
|
||||
void NotificationManager.onRSSFeedFailed(libraryItem.media.feedURL, this.failedCheckMap[libraryItem.id], libraryItem.media.title)
|
||||
}
|
||||
} else if (newEpisodes.length) {
|
||||
delete this.failedCheckMap[libraryItem.id]
|
||||
@@ -384,7 +386,17 @@ class PodcastManager {
|
||||
Logger.error(`[PodcastManager] checkPodcastForNewEpisodes no feed url for ${podcastLibraryItem.media.title} (ID: ${podcastLibraryItem.id})`)
|
||||
return null
|
||||
}
|
||||
const feed = await getPodcastFeed(podcastLibraryItem.media.feedURL)
|
||||
const feed = await Promise.race([
|
||||
getPodcastFeed(podcastLibraryItem.media.feedURL),
|
||||
new Promise((_, reject) =>
|
||||
// The added second is to make sure that axios can fail first and only falls back later
|
||||
setTimeout(() => reject(new Error('Timeout. getPodcastFeed seemed to timeout but not triggering the timeout.')), global.PodcastDownloadTimeout + 1000)
|
||||
)
|
||||
]).catch((error) => {
|
||||
Logger.error(`[PodcastManager] checkPodcastForNewEpisodes failed to fetch feed for ${podcastLibraryItem.media.title} (ID: ${podcastLibraryItem.id}):`, error)
|
||||
return null
|
||||
})
|
||||
|
||||
if (!feed?.episodes) {
|
||||
Logger.error(`[PodcastManager] checkPodcastForNewEpisodes invalid feed payload for ${podcastLibraryItem.media.title} (ID: ${podcastLibraryItem.id})`, feed)
|
||||
return null
|
||||
|
||||
@@ -222,13 +222,13 @@ class MediaProgress extends Model {
|
||||
const markAsFinishedPercentComplete = Number(progressPayload.markAsFinishedPercentComplete) / 100
|
||||
shouldMarkAsFinished = markAsFinishedPercentComplete < this.progress
|
||||
if (shouldMarkAsFinished) {
|
||||
Logger.debug(`[MediaProgress] Marking media progress as finished because progress (${this.progress}) is greater than ${markAsFinishedPercentComplete}`)
|
||||
Logger.info(`[MediaProgress] Marking media progress as finished because progress (${this.progress}) is greater than ${markAsFinishedPercentComplete} (media item ${this.mediaItemId})`)
|
||||
}
|
||||
} else {
|
||||
const markAsFinishedTimeRemaining = isNullOrNaN(progressPayload.markAsFinishedTimeRemaining) ? 10 : Number(progressPayload.markAsFinishedTimeRemaining)
|
||||
shouldMarkAsFinished = timeRemaining < markAsFinishedTimeRemaining
|
||||
if (shouldMarkAsFinished) {
|
||||
Logger.debug(`[MediaProgress] Marking media progress as finished because time remaining (${timeRemaining}) is less than ${markAsFinishedTimeRemaining} seconds`)
|
||||
Logger.info(`[MediaProgress] Marking media progress as finished because time remaining (${timeRemaining}) is less than ${markAsFinishedTimeRemaining} seconds (media item ${this.mediaItemId})`)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -246,6 +246,7 @@ class MediaProgress extends Model {
|
||||
// For local sync
|
||||
if (progressPayload.lastUpdate) {
|
||||
this.updatedAt = progressPayload.lastUpdate
|
||||
Logger.info(`[MediaProgress] Manually setting updatedAt to ${this.updatedAt} (media item ${this.mediaItemId})`)
|
||||
this.changed('updatedAt', true)
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ class Audible {
|
||||
}
|
||||
|
||||
cleanResult(item) {
|
||||
const { title, subtitle, asin, authors, narrators, publisherName, summary, releaseDate, image, genres, seriesPrimary, seriesSecondary, language, runtimeLengthMin, formatType } = item
|
||||
const { title, subtitle, asin, authors, narrators, publisherName, summary, releaseDate, image, genres, seriesPrimary, seriesSecondary, language, runtimeLengthMin, formatType, isbn } = item
|
||||
|
||||
const series = []
|
||||
if (seriesPrimary) {
|
||||
@@ -70,6 +70,7 @@ class Audible {
|
||||
description: summary || null,
|
||||
cover: image,
|
||||
asin,
|
||||
isbn,
|
||||
genres: genresFiltered.length ? genresFiltered : null,
|
||||
tags: tagsFiltered.length ? tagsFiltered.join(', ') : null,
|
||||
series: series.length ? series : null,
|
||||
|
||||
@@ -52,9 +52,7 @@ class FantLab {
|
||||
return []
|
||||
})
|
||||
|
||||
return Promise.all(items.map(async (item) => await this.getWork(item, timeout))).then((resArray) => {
|
||||
return resArray.filter((res) => res)
|
||||
})
|
||||
return Promise.all(items.map(async (item) => await this.getWork(item, timeout))).then((resArray) => resArray.filter(Boolean))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -83,6 +81,10 @@ class FantLab {
|
||||
return null
|
||||
})
|
||||
|
||||
if (!bookData) {
|
||||
return null
|
||||
}
|
||||
|
||||
return this.cleanBookData(bookData, timeout)
|
||||
}
|
||||
|
||||
|
||||
@@ -370,7 +370,7 @@ class Scanner {
|
||||
|
||||
let numEpisodesUpdated = 0
|
||||
for (const episode of episodesToQuickMatch) {
|
||||
const episodeMatches = findMatchingEpisodesInFeed(feed, episode.title)
|
||||
const episodeMatches = findMatchingEpisodesInFeed(feed, episode.title, 0.1)
|
||||
if (episodeMatches?.length) {
|
||||
const wasUpdated = await this.updateEpisodeWithMatch(episode, episodeMatches[0].episode, options)
|
||||
if (wasUpdated) numEpisodesUpdated++
|
||||
|
||||
@@ -103,18 +103,39 @@ module.exports.resizeImage = resizeImage
|
||||
*/
|
||||
module.exports.downloadPodcastEpisode = (podcastEpisodeDownload) => {
|
||||
return new Promise(async (resolve) => {
|
||||
const response = await axios({
|
||||
url: podcastEpisodeDownload.url,
|
||||
method: 'GET',
|
||||
responseType: 'stream',
|
||||
headers: {
|
||||
'User-Agent': 'audiobookshelf (+https://audiobookshelf.org)'
|
||||
},
|
||||
timeout: global.PodcastDownloadTimeout
|
||||
}).catch((error) => {
|
||||
Logger.error(`[ffmpegHelpers] Failed to download podcast episode with url "${podcastEpisodeDownload.url}"`, error)
|
||||
return null
|
||||
})
|
||||
// Some podcasts fail due to user agent strings
|
||||
// See: https://github.com/advplyr/audiobookshelf/issues/3246 (requires iTMS user agent)
|
||||
// See: https://github.com/advplyr/audiobookshelf/issues/4401 (requires no iTMS user agent)
|
||||
const userAgents = ['audiobookshelf (+https://audiobookshelf.org; like iTMS)', 'audiobookshelf (+https://audiobookshelf.org)']
|
||||
|
||||
let response = null
|
||||
let lastError = null
|
||||
|
||||
for (const userAgent of userAgents) {
|
||||
try {
|
||||
response = await axios({
|
||||
url: podcastEpisodeDownload.url,
|
||||
method: 'GET',
|
||||
responseType: 'stream',
|
||||
headers: {
|
||||
'User-Agent': userAgent
|
||||
},
|
||||
timeout: global.PodcastDownloadTimeout
|
||||
})
|
||||
|
||||
Logger.debug(`[ffmpegHelpers] Successfully connected with User-Agent: ${userAgent}`)
|
||||
break
|
||||
} catch (error) {
|
||||
lastError = error
|
||||
Logger.warn(`[ffmpegHelpers] Failed to download podcast episode with User-Agent "${userAgent}" for url "${podcastEpisodeDownload.url}"`, error.message)
|
||||
|
||||
// If this is the last attempt, log the full error
|
||||
if (userAgent === userAgents[userAgents.length - 1]) {
|
||||
Logger.error(`[ffmpegHelpers] All User-Agent attempts failed for url "${podcastEpisodeDownload.url}"`, lastError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
return resolve({
|
||||
success: false
|
||||
|
||||
@@ -60,6 +60,38 @@ module.exports.notificationData = {
|
||||
errorMsg: 'Example error message'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'onRSSFeedFailed',
|
||||
requiresLibrary: true,
|
||||
description: 'Triggered when the RSS feed request fails for an automatic episode download',
|
||||
descriptionKey: 'NotificationOnRSSFeedFailedDescription',
|
||||
variables: ['feedUrl', 'numFailed', 'title'],
|
||||
defaults: {
|
||||
title: 'RSS Feed Request Failed',
|
||||
body: 'Failed to request RSS feed for {{title}}.\nFeed URL: {{feedUrl}}\nNumber of failed attempts: {{numFailed}}'
|
||||
},
|
||||
testData: {
|
||||
title: 'Test RSS Feed',
|
||||
feedUrl: 'https://example.com/rss',
|
||||
numFailed: 3
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'onRSSFeedDisabled',
|
||||
requiresLibrary: true,
|
||||
description: 'Triggered when automatic episode downloads are disabled due to too many failed attempts',
|
||||
descriptionKey: 'NotificationOnRSSFeedDisabledDescription',
|
||||
variables: ['feedUrl', 'numFailed', 'title'],
|
||||
defaults: {
|
||||
title: 'Podcast Episode Download Schedule Disabled',
|
||||
body: 'Automatic episode downloads for {{title}} have been disabled due to too many failed RSS feed requests.\nFeed URL: {{feedUrl}}\nNumber of failed attempts: {{numFailed}}'
|
||||
},
|
||||
testData: {
|
||||
title: 'Test RSS Feed',
|
||||
feedUrl: 'https://example.com/rss',
|
||||
numFailed: 5
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'onTest',
|
||||
requiresLibrary: false,
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
const axios = require('axios')
|
||||
const ssrfFilter = require('ssrf-req-filter')
|
||||
const Logger = require('../Logger')
|
||||
const { xmlToJSON, levenshteinDistance, timestampToSeconds } = require('./index')
|
||||
const { xmlToJSON, timestampToSeconds } = require('./index')
|
||||
const htmlSanitizer = require('../utils/htmlSanitizer')
|
||||
const Fuse = require('../libs/fusejs')
|
||||
|
||||
/**
|
||||
* @typedef RssPodcastChapter
|
||||
@@ -24,6 +25,7 @@ const htmlSanitizer = require('../utils/htmlSanitizer')
|
||||
* @property {string} episode
|
||||
* @property {string} author
|
||||
* @property {string} duration
|
||||
* @property {number|null} durationSeconds - Parsed from duration string if duration is valid
|
||||
* @property {string} explicit
|
||||
* @property {number} publishedAt - Unix timestamp
|
||||
* @property {{ url: string, type?: string, length?: string }} enclosure
|
||||
@@ -216,8 +218,9 @@ function extractEpisodeData(item) {
|
||||
})
|
||||
|
||||
// Extract psc:chapters if duration is set
|
||||
let episodeDuration = !isNaN(episode.duration) ? timestampToSeconds(episode.duration) : null
|
||||
if (item['psc:chapters']?.[0]?.['psc:chapter']?.length && episodeDuration) {
|
||||
episode.durationSeconds = episode.duration ? timestampToSeconds(episode.duration) : null
|
||||
|
||||
if (item['psc:chapters']?.[0]?.['psc:chapter']?.length && episode.durationSeconds) {
|
||||
// Example chapter:
|
||||
// {"id":0,"start":0,"end":43.004286,"title":"chapter 1"}
|
||||
|
||||
@@ -243,7 +246,7 @@ function extractEpisodeData(item) {
|
||||
} else {
|
||||
episode.chapters = cleanedChapters.map((chapter, index) => {
|
||||
const nextChapter = cleanedChapters[index + 1]
|
||||
const end = nextChapter ? nextChapter.start : episodeDuration
|
||||
const end = nextChapter ? nextChapter.start : episode.durationSeconds
|
||||
return {
|
||||
id: chapter.id,
|
||||
title: chapter.title,
|
||||
@@ -272,6 +275,7 @@ function cleanEpisodeData(data) {
|
||||
episode: data.episode || '',
|
||||
author: data.author || '',
|
||||
duration: data.duration || '',
|
||||
durationSeconds: data.durationSeconds || null,
|
||||
explicit: data.explicit || '',
|
||||
publishedAt,
|
||||
enclosure: data.enclosure,
|
||||
@@ -407,7 +411,7 @@ module.exports.getPodcastFeed = (feedUrl, excludeEpisodeMetadata = false) => {
|
||||
})
|
||||
}
|
||||
|
||||
// Return array of episodes ordered by closest match (Levenshtein distance of 6 or less)
|
||||
// Return array of episodes ordered by closest match using fuse.js
|
||||
module.exports.findMatchingEpisodes = async (feedUrl, searchTitle) => {
|
||||
const feed = await this.getPodcastFeed(feedUrl).catch(() => {
|
||||
return null
|
||||
@@ -420,32 +424,29 @@ module.exports.findMatchingEpisodes = async (feedUrl, searchTitle) => {
|
||||
*
|
||||
* @param {RssPodcast} feed
|
||||
* @param {string} searchTitle
|
||||
* @returns {Array<{ episode: RssPodcastEpisode, levenshtein: number }>}
|
||||
* @param {number} [threshold=0.4] - 0.0 for perfect match, 1.0 for match anything
|
||||
* @returns {Array<{ episode: RssPodcastEpisode }>}
|
||||
*/
|
||||
module.exports.findMatchingEpisodesInFeed = (feed, searchTitle) => {
|
||||
searchTitle = searchTitle.toLowerCase().trim()
|
||||
module.exports.findMatchingEpisodesInFeed = (feed, searchTitle, threshold = 0.4) => {
|
||||
if (!feed?.episodes) {
|
||||
return null
|
||||
}
|
||||
|
||||
const fuseOptions = {
|
||||
ignoreDiacritics: true,
|
||||
threshold,
|
||||
keys: [
|
||||
{ name: 'title', weight: 0.7 }, // prefer match in title
|
||||
{ name: 'subtitle', weight: 0.3 }
|
||||
]
|
||||
}
|
||||
const fuse = new Fuse(feed.episodes, fuseOptions)
|
||||
|
||||
const matches = []
|
||||
feed.episodes.forEach((ep) => {
|
||||
if (!ep.title) return
|
||||
const epTitle = ep.title.toLowerCase().trim()
|
||||
if (epTitle === searchTitle) {
|
||||
matches.push({
|
||||
episode: ep,
|
||||
levenshtein: 0
|
||||
})
|
||||
} else {
|
||||
const levenshtein = levenshteinDistance(searchTitle, epTitle, true)
|
||||
if (levenshtein <= 6 && epTitle.length > levenshtein) {
|
||||
matches.push({
|
||||
episode: ep,
|
||||
levenshtein
|
||||
})
|
||||
}
|
||||
}
|
||||
fuse.search(searchTitle).forEach((match) => {
|
||||
matches.push({
|
||||
episode: match.item
|
||||
})
|
||||
})
|
||||
return matches.sort((a, b) => a.levenshtein - b.levenshtein)
|
||||
return matches
|
||||
}
|
||||
|
||||
@@ -186,6 +186,8 @@ module.exports = {
|
||||
mediaWhere['$series.id$'] = null
|
||||
} else if (group === 'abridged') {
|
||||
mediaWhere['abridged'] = true
|
||||
} else if (group === 'explicit') {
|
||||
mediaWhere['explicit'] = true
|
||||
} else if (['genres', 'tags', 'narrators'].includes(group)) {
|
||||
mediaWhere[group] = Sequelize.where(Sequelize.literal(`(SELECT count(*) FROM json_each(${group}) WHERE json_valid(${group}) AND json_each.value = :filterValue)`), {
|
||||
[Sequelize.Op.gte]: 1
|
||||
@@ -251,6 +253,15 @@ module.exports = {
|
||||
*/
|
||||
getOrder(sortBy, sortDesc, collapseseries) {
|
||||
const dir = sortDesc ? 'DESC' : 'ASC'
|
||||
|
||||
const getTitleOrder = () => {
|
||||
if (global.ServerSettings.sortingIgnorePrefix) {
|
||||
return [Sequelize.literal('`libraryItem`.`titleIgnorePrefix` COLLATE NOCASE'), dir]
|
||||
} else {
|
||||
return [Sequelize.literal('`libraryItem`.`title` COLLATE NOCASE'), dir]
|
||||
}
|
||||
}
|
||||
|
||||
if (sortBy === 'addedAt') {
|
||||
return [[Sequelize.literal('libraryItem.createdAt'), dir]]
|
||||
} else if (sortBy === 'size') {
|
||||
@@ -264,19 +275,16 @@ module.exports = {
|
||||
} else if (sortBy === 'media.metadata.publishedYear') {
|
||||
return [[Sequelize.literal(`CAST(\`book\`.\`publishedYear\` AS INTEGER)`), dir]]
|
||||
} else if (sortBy === 'media.metadata.authorNameLF') {
|
||||
return [[Sequelize.literal('`libraryItem`.`authorNamesLastFirst` COLLATE NOCASE'), dir]]
|
||||
// Sort by author name last first, secondary sort by title
|
||||
return [[Sequelize.literal('`libraryItem`.`authorNamesLastFirst` COLLATE NOCASE'), dir], getTitleOrder()]
|
||||
} else if (sortBy === 'media.metadata.authorName') {
|
||||
return [[Sequelize.literal('`libraryItem`.`authorNamesFirstLast` COLLATE NOCASE'), dir]]
|
||||
// Sort by author name first last, secondary sort by title
|
||||
return [[Sequelize.literal('`libraryItem`.`authorNamesFirstLast` COLLATE NOCASE'), dir], getTitleOrder()]
|
||||
} else if (sortBy === 'media.metadata.title') {
|
||||
if (collapseseries) {
|
||||
return [[Sequelize.literal('display_title COLLATE NOCASE'), dir]]
|
||||
}
|
||||
|
||||
if (global.ServerSettings.sortingIgnorePrefix) {
|
||||
return [[Sequelize.literal('`libraryItem`.`titleIgnorePrefix` COLLATE NOCASE'), dir]]
|
||||
} else {
|
||||
return [[Sequelize.literal('`libraryItem`.`title` COLLATE NOCASE'), dir]]
|
||||
}
|
||||
return [getTitleOrder()]
|
||||
} else if (sortBy === 'sequence') {
|
||||
const nullDir = sortDesc ? 'DESC NULLS FIRST' : 'ASC NULLS LAST'
|
||||
return [[Sequelize.literal(`CAST(\`series.bookSeries.sequence\` AS FLOAT) ${nullDir}`)]]
|
||||
|
||||
@@ -59,6 +59,8 @@ module.exports = {
|
||||
replacements.filterValue = value
|
||||
} else if (group === 'languages') {
|
||||
mediaWhere['language'] = value
|
||||
} else if (group === 'explicit') {
|
||||
mediaWhere['explicit'] = true
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user