mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-12-31 11:38:47 -05:00
Compare commits
111 Commits
new_stats_
...
new_sessio
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d9531166b6 | ||
|
|
336de49d8d | ||
|
|
45987ffd63 | ||
|
|
1a1ef9c378 | ||
|
|
342d100f3e | ||
|
|
e0b90c6813 | ||
|
|
2706a9c4aa | ||
|
|
2cc9d1b7f8 | ||
|
|
2b7268c952 | ||
|
|
e097fe1e88 | ||
|
|
6819c0b108 | ||
|
|
58cd751b43 | ||
|
|
9f834a5345 | ||
|
|
5eaf9c69ad | ||
|
|
fd84cd0d7f | ||
|
|
db7744eb84 | ||
|
|
af513a2fb6 | ||
|
|
4cb5c934d5 | ||
|
|
37f84a0f62 | ||
|
|
70595181f1 | ||
|
|
b357bbed60 | ||
|
|
f7a720c6ac | ||
|
|
6549605efd | ||
|
|
33952fb1fd | ||
|
|
7b207dc5d8 | ||
|
|
cb24a9c1ec | ||
|
|
3b42af5213 | ||
|
|
b56691f1a2 | ||
|
|
ac3154093c | ||
|
|
01ef24f5e6 | ||
|
|
3fb73c7426 | ||
|
|
bf3bc06322 | ||
|
|
2733c28784 | ||
|
|
b3dac831e6 | ||
|
|
35702aa770 | ||
|
|
b2ffb3b7b9 | ||
|
|
c52fe4b583 | ||
|
|
af8ace7d1f | ||
|
|
de37e40a1e | ||
|
|
56f5df91dc | ||
|
|
fc590abb09 | ||
|
|
bc7bbc1b7d | ||
|
|
4345973213 | ||
|
|
b4ff9f5944 | ||
|
|
a9a253f769 | ||
|
|
a9783efa34 | ||
|
|
a380ee080f | ||
|
|
eabefd099c | ||
|
|
97799919e6 | ||
|
|
35870a0158 | ||
|
|
ec05bd36e4 | ||
|
|
be041f93c2 | ||
|
|
a156d3595b | ||
|
|
a1d549a2b1 | ||
|
|
812cb5a160 | ||
|
|
e6264540af | ||
|
|
79fe064c4a | ||
|
|
7e69713683 | ||
|
|
3bbeb8f27a | ||
|
|
04fb8fa61d | ||
|
|
2caa861b8a | ||
|
|
d7f0815fb3 | ||
|
|
e6ab05e177 | ||
|
|
c2ecfd428b | ||
|
|
9f26274ca8 | ||
|
|
7764f1cf75 | ||
|
|
bc1b99efd6 | ||
|
|
26309019e7 | ||
|
|
b47d7b734d | ||
|
|
62194b8781 | ||
|
|
7c114a051a | ||
|
|
26c0c89b94 | ||
|
|
c81071a7b3 | ||
|
|
31f48edcc3 | ||
|
|
f7109a055c | ||
|
|
9d0e7759e0 | ||
|
|
d15ccbd2fc | ||
|
|
cfeb1743df | ||
|
|
a7e0330b06 | ||
|
|
5f69e83d46 | ||
|
|
fdde62896f | ||
|
|
f1909d0fc7 | ||
|
|
28225618fd | ||
|
|
1a562a5f23 | ||
|
|
bfbbcba160 | ||
|
|
21e4d17ef3 | ||
|
|
87faebc7d9 | ||
|
|
5d868d1355 | ||
|
|
ac0fd41740 | ||
|
|
1202e95b66 | ||
|
|
c05cf9718b | ||
|
|
d8f1e43e85 | ||
|
|
ed7e87b168 | ||
|
|
4ca2c9e97f | ||
|
|
01dd1c0615 | ||
|
|
cd387b8fed | ||
|
|
c85cd69152 | ||
|
|
9e9b52a252 | ||
|
|
292d5783a9 | ||
|
|
c7faafd0f3 | ||
|
|
ca7388b14e | ||
|
|
ddf2ca3670 | ||
|
|
96825c3c2b | ||
|
|
6ed66fea16 | ||
|
|
ddcda197b4 | ||
|
|
8bea5d83f5 | ||
|
|
121805ba39 | ||
|
|
f9bbd71174 | ||
|
|
2fbb31e0ea | ||
|
|
89167543fa | ||
|
|
33e0987d73 |
2
.github/workflows/docker-build.yml
vendored
2
.github/workflows/docker-build.yml
vendored
@@ -23,7 +23,7 @@ on:
|
||||
jobs:
|
||||
build:
|
||||
if: ${{ !contains(github.event.head_commit.message, 'skip ci') && github.repository == 'advplyr/audiobookshelf' }}
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
steps:
|
||||
- name: Check out
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -23,3 +23,4 @@ sw.*
|
||||
.DS_STORE
|
||||
.idea/*
|
||||
tailwind.compiled.css
|
||||
tailwind.config.js
|
||||
|
||||
@@ -156,7 +156,7 @@ export default {
|
||||
return this.mediaMetadata.authors || []
|
||||
},
|
||||
libraryId() {
|
||||
return this.streamLibraryItem ? this.streamLibraryItem.libraryId : null
|
||||
return this.streamLibraryItem?.libraryId || null
|
||||
},
|
||||
totalDurationPretty() {
|
||||
// Adjusted by playback rate
|
||||
|
||||
@@ -20,10 +20,10 @@
|
||||
<div class="w-1/2 px-2">
|
||||
<div v-if="!isPodcast" class="flex items-end">
|
||||
<ui-text-input-with-label v-model.trim="itemData.author" :disabled="processing" :label="$strings.LabelAuthor" />
|
||||
<ui-tooltip :text="$strings.LabelUploaderItemFetchMetadataHelp">
|
||||
<div class="ml-2 mb-1 w-8 h-8 bg-bg border border-white/10 flex items-center justify-center rounded-full hover:bg-primary cursor-pointer" @click="fetchMetadata">
|
||||
<ui-tooltip direction="top" :text="$strings.LabelUploaderItemFetchMetadataHelp">
|
||||
<button type="button" class="ml-2 mb-1 w-8 h-8 bg-bg border border-white/10 flex items-center justify-center rounded-full hover:bg-primary cursor-pointer" @click="fetchMetadata">
|
||||
<span class="text-base text-white/80 font-mono material-symbols">sync</span>
|
||||
</div>
|
||||
</button>
|
||||
</ui-tooltip>
|
||||
</div>
|
||||
<div v-else class="w-full">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="narrators?.length" class="flex py-0.5 mt-4">
|
||||
<div class="w-24 min-w-24 sm:w-32 sm:min-w-32">
|
||||
<div class="w-34 min-w-34 sm:w-34 sm:min-w-34 break-words">
|
||||
<span class="text-white/60 uppercase text-sm">{{ $strings.LabelNarrators }}</span>
|
||||
</div>
|
||||
<div class="max-w-[calc(100vw-10rem)] overflow-hidden text-ellipsis">
|
||||
@@ -12,7 +12,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="publishedYear" role="paragraph" class="flex py-0.5">
|
||||
<div class="w-24 min-w-24 sm:w-32 sm:min-w-32">
|
||||
<div class="w-34 min-w-34 sm:w-34 sm:min-w-34 break-words">
|
||||
<span class="text-white/60 uppercase text-sm">{{ $strings.LabelPublishYear }}</span>
|
||||
</div>
|
||||
<div>
|
||||
@@ -20,7 +20,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="publisher" role="paragraph" class="flex py-0.5">
|
||||
<div class="w-24 min-w-24 sm:w-32 sm:min-w-32">
|
||||
<div class="w-34 min-w-34 sm:w-34 sm:min-w-34 break-words">
|
||||
<span class="text-white/60 uppercase text-sm">{{ $strings.LabelPublisher }}</span>
|
||||
</div>
|
||||
<div>
|
||||
@@ -28,7 +28,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="podcastType" role="paragraph" class="flex py-0.5">
|
||||
<div class="w-24 min-w-24 sm:w-32 sm:min-w-32">
|
||||
<div class="w-34 min-w-34 sm:w-34 sm:min-w-34 break-words">
|
||||
<span class="text-white/60 uppercase text-sm">{{ $strings.LabelPodcastType }}</span>
|
||||
</div>
|
||||
<div class="capitalize">
|
||||
@@ -36,7 +36,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex py-0.5" v-if="genres.length">
|
||||
<div class="w-24 min-w-24 sm:w-32 sm:min-w-32">
|
||||
<div class="w-34 min-w-34 sm:w-34 sm:min-w-34 break-words">
|
||||
<span class="text-white/60 uppercase text-sm">{{ $strings.LabelGenres }}</span>
|
||||
</div>
|
||||
<div class="max-w-[calc(100vw-10rem)] overflow-hidden text-ellipsis">
|
||||
@@ -47,7 +47,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex py-0.5" v-if="tags.length">
|
||||
<div class="w-24 min-w-24 sm:w-32 sm:min-w-32">
|
||||
<div class="w-34 min-w-34 sm:w-34 sm:min-w-34 break-words">
|
||||
<span class="text-white/60 uppercase text-sm">{{ $strings.LabelTags }}</span>
|
||||
</div>
|
||||
<div class="max-w-[calc(100vw-10rem)] overflow-hidden text-ellipsis">
|
||||
@@ -58,7 +58,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="language" class="flex py-0.5">
|
||||
<div class="w-24 min-w-24 sm:w-32 sm:min-w-32">
|
||||
<div class="w-34 min-w-34 sm:w-34 sm:min-w-34 break-words">
|
||||
<span class="text-white/60 uppercase text-sm">{{ $strings.LabelLanguage }}</span>
|
||||
</div>
|
||||
<div>
|
||||
@@ -66,7 +66,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="tracks.length || (isPodcast && totalPodcastDuration)" role="paragraph" class="flex py-0.5">
|
||||
<div class="w-24 min-w-24 sm:w-32 sm:min-w-32">
|
||||
<div class="w-34 min-w-34 sm:w-34 sm:min-w-34 break-words">
|
||||
<span class="text-white/60 uppercase text-sm">{{ $strings.LabelDuration }}</span>
|
||||
</div>
|
||||
<div>
|
||||
@@ -74,7 +74,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div role="paragraph" class="flex py-0.5">
|
||||
<div class="w-24 min-w-24 sm:w-32 sm:min-w-32">
|
||||
<div class="w-34 min-w-34 sm:w-34 sm:min-w-34 break-words">
|
||||
<span class="text-white/60 uppercase text-sm">{{ $strings.LabelSize }}</span>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@@ -10,6 +10,12 @@
|
||||
<form @submit.prevent="submit" class="flex grow">
|
||||
<ui-text-input v-model="search" @input="inputUpdate" type="search" :placeholder="$strings.PlaceholderSearchEpisode" class="grow mr-2 text-sm md:text-base" />
|
||||
</form>
|
||||
<ui-btn :padding-x="4" @click="toggleSort">
|
||||
<span class="pr-4">{{ $strings.LabelSortPubDate }}</span>
|
||||
<span class="text-yellow-400 absolute inset-y-0 right-0 flex items-center pr-2">
|
||||
<span class="material-symbols text-xl" :aria-label="sortDescending ? $strings.LabelSortDescending : $strings.LabelSortAscending">{{ sortDescending ? 'expand_more' : 'expand_less' }}</span>
|
||||
</span>
|
||||
</ui-btn>
|
||||
</div>
|
||||
<div ref="episodeContainer" id="episodes-scroll" class="w-full overflow-x-hidden overflow-y-auto">
|
||||
<div v-for="(episode, index) in episodesList" :key="index" class="relative" :class="episode.isDownloaded || episode.isDownloading ? 'bg-primary/40' : selectedEpisodes[episode.cleanUrl] ? 'cursor-pointer bg-success/10' : index % 2 == 0 ? 'cursor-pointer bg-primary/25 hover:bg-primary/40' : 'cursor-pointer bg-primary/5 hover:bg-primary/25'" @click="toggleSelectEpisode(episode)">
|
||||
@@ -73,7 +79,8 @@ export default {
|
||||
searchTimeout: null,
|
||||
searchText: null,
|
||||
downloadedEpisodeGuidMap: {},
|
||||
downloadedEpisodeUrlMap: {}
|
||||
downloadedEpisodeUrlMap: {},
|
||||
sortDescending: true
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -141,6 +148,17 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggleSort() {
|
||||
this.sortDescending = !this.sortDescending
|
||||
this.episodesCleaned = this.episodesCleaned.toSorted((a, b) => {
|
||||
if (this.sortDescending) {
|
||||
return a.publishedAt < b.publishedAt ? 1 : -1
|
||||
}
|
||||
return a.publishedAt > b.publishedAt ? 1 : -1
|
||||
})
|
||||
this.selectedEpisodes = {}
|
||||
this.selectAll = false
|
||||
},
|
||||
getIsEpisodeDownloaded(episode) {
|
||||
if (episode.guid && !!this.downloadedEpisodeGuidMap[episode.guid]) {
|
||||
return true
|
||||
|
||||
@@ -74,6 +74,9 @@ export default {
|
||||
currentChapterStart() {
|
||||
if (!this.currentChapter) return 0
|
||||
return this.currentChapter.start
|
||||
},
|
||||
isMobile() {
|
||||
return this.$store.state.globals.isMobile
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -145,6 +148,9 @@ export default {
|
||||
})
|
||||
},
|
||||
mousemoveTrack(e) {
|
||||
if (this.isMobile) {
|
||||
return
|
||||
}
|
||||
const offsetX = e.offsetX
|
||||
|
||||
const baseTime = this.useChapterTrack ? this.currentChapterStart : 0
|
||||
@@ -198,6 +204,7 @@ export default {
|
||||
setTrackWidth() {
|
||||
if (this.$refs.track) {
|
||||
this.trackWidth = this.$refs.track.clientWidth
|
||||
this.trackOffsetLeft = this.$refs.track.getBoundingClientRect().left
|
||||
} else {
|
||||
console.error('Track not loaded', this.$refs)
|
||||
}
|
||||
|
||||
@@ -164,14 +164,15 @@ export default {
|
||||
beforeMount() {
|
||||
this.yearInReviewYear = new Date().getFullYear()
|
||||
|
||||
// When not December show previous year
|
||||
if (new Date().getMonth() < 11) {
|
||||
this.availableYears = this.getAvailableYears()
|
||||
const availableYearValues = this.availableYears.map((y) => y.value)
|
||||
|
||||
// When not December show previous year if data is available
|
||||
if (new Date().getMonth() < 11 && availableYearValues.includes(this.yearInReviewYear - 1)) {
|
||||
this.yearInReviewYear--
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.availableYears = this.getAvailableYears()
|
||||
|
||||
if (typeof navigator.share !== 'undefined' && navigator.share) {
|
||||
this.showShareButton = true
|
||||
} else {
|
||||
|
||||
@@ -26,9 +26,9 @@
|
||||
<span class="material-symbols text-2xl text-error">error_outline</span>
|
||||
</ui-tooltip>
|
||||
|
||||
<button aria-label="Download Backup" class="inline-flex material-symbols text-xl mx-1 mt-1 text-white/70 hover:text-white/100" @click.stop="downloadBackup(backup)">download</button>
|
||||
<button aria-label="Download backup" class="inline-flex material-symbols text-xl mx-1 mt-1 text-white/70 hover:text-white/100" @click.stop="downloadBackup(backup)">download</button>
|
||||
|
||||
<button aria-label="Delete Backup" class="inline-flex material-symbols text-xl mx-1 text-white/70 hover:text-error" @click.stop="deleteBackupClick(backup)">delete</button>
|
||||
<button aria-label="Delete backup" class="inline-flex material-symbols text-xl mx-1 text-white/70 hover:text-error" @click.stop="deleteBackupClick(backup)">delete</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="inline-flex toggle-btn-wrapper shadow-md">
|
||||
<button v-for="item in items" :key="item.value" type="button" class="toggle-btn outline-hidden relative border border-gray-600 px-4 py-1" :class="{ selected: item.value === value }" @click.stop="clickBtn(item.value)">
|
||||
<button v-for="item in items" :key="item.value" type="button" :disabled="disabled" class="toggle-btn outline-hidden relative border border-gray-600 px-4 py-1" :class="{ selected: item.value === value }" @click.stop="clickBtn(item.value)">
|
||||
{{ item.text }}
|
||||
</button>
|
||||
</div>
|
||||
@@ -9,13 +9,17 @@
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
value: String,
|
||||
value: [String, Number],
|
||||
/**
|
||||
* [{ "text", "", "value": "" }]
|
||||
*/
|
||||
items: {
|
||||
type: Array,
|
||||
default: Object
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -76,10 +80,19 @@ export default {
|
||||
.toggle-btn.selected {
|
||||
color: white;
|
||||
}
|
||||
.toggle-btn.selected:disabled {
|
||||
color: white;
|
||||
}
|
||||
.toggle-btn.selected::before {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
button.toggle-btn.selected:disabled::before {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
button.toggle-btn:disabled::before {
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
button.toggle-btn:disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
||||
211
client/components/widgets/EncoderOptionsCard.vue
Normal file
211
client/components/widgets/EncoderOptionsCard.vue
Normal file
@@ -0,0 +1,211 @@
|
||||
<template>
|
||||
<div class="w-full py-2">
|
||||
<div class="flex -mb-px">
|
||||
<button type="button" :disabled="disabled" class="w-1/2 h-8 rounded-tl-md relative border border-black-200 flex items-center justify-center disabled:cursor-not-allowed" :class="!showAdvancedView ? 'text-white bg-bg hover:bg-bg/60 border-b-bg' : 'text-gray-400 hover:text-gray-300 bg-primary/70 hover:bg-primary/60'" @click="showAdvancedView = false">
|
||||
<p class="text-sm">{{ $strings.HeaderPresets }}</p>
|
||||
</button>
|
||||
<button type="button" :disabled="disabled" class="w-1/2 h-8 rounded-tr-md relative border border-black-200 flex items-center justify-center -ml-px disabled:cursor-not-allowed" :class="showAdvancedView ? 'text-white bg-bg hover:bg-bg/60 border-b-bg' : 'text-gray-400 hover:text-gray-300 bg-primary/70 hover:bg-primary/60'" @click="showAdvancedView = true">
|
||||
<p class="text-sm">{{ $strings.HeaderAdvanced }}</p>
|
||||
</button>
|
||||
</div>
|
||||
<div class="p-4 md:p-8 border border-black-200 rounded-b-md mr-px bg-bg">
|
||||
<template v-if="!showAdvancedView">
|
||||
<div class="flex flex-wrap gap-4 sm:gap-8 justify-start sm:justify-center">
|
||||
<div class="flex flex-col items-start gap-2">
|
||||
<p class="text-sm w-40">{{ $strings.LabelCodec }}</p>
|
||||
<ui-toggle-btns v-model="selectedCodec" :items="codecItems" :disabled="disabled" />
|
||||
<p class="text-xs text-gray-300">
|
||||
{{ $strings.LabelCurrently }} <span class="text-white">{{ currentCodec }}</span> <span v-if="isCodecsDifferent" class="text-warning">(mixed)</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-col items-start gap-2">
|
||||
<p class="text-sm w-40">{{ $strings.LabelBitrate }}</p>
|
||||
<ui-toggle-btns v-model="selectedBitrate" :items="bitrateItems" :disabled="disabled" />
|
||||
<p class="text-xs text-gray-300">
|
||||
{{ $strings.LabelCurrently }} <span class="text-white">{{ currentBitrate }} KB/s</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-col items-start gap-2">
|
||||
<p class="text-sm w-40">{{ $strings.LabelChannels }}</p>
|
||||
<ui-toggle-btns v-model="selectedChannels" :items="channelsItems" :disabled="disabled" />
|
||||
<p class="text-xs text-gray-300">
|
||||
{{ $strings.LabelCurrently }} <span class="text-white">{{ currentChannels }} ({{ currentChanelLayout }})</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div>
|
||||
<div class="flex flex-wrap gap-4 sm:gap-8 justify-start sm:justify-center mb-4">
|
||||
<div class="w-40">
|
||||
<ui-text-input-with-label v-model="customCodec" :label="$strings.LabelAudioCodec" :disabled="disabled" @input="customCodecChanged" />
|
||||
</div>
|
||||
<div class="w-40">
|
||||
<ui-text-input-with-label v-model="customBitrate" :label="$strings.LabelAudioBitrate" :disabled="disabled" @input="customBitrateChanged" />
|
||||
</div>
|
||||
<div class="w-40">
|
||||
<ui-text-input-with-label v-model="customChannels" :label="$strings.LabelAudioChannels" type="number" :disabled="disabled" @input="customChannelsChanged" />
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-xs sm:text-sm text-warning sm:text-center">{{ $strings.LabelEncodingWarningAdvancedSettings }}</p>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
audioTracks: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showAdvancedView: false,
|
||||
selectedCodec: 'aac',
|
||||
selectedBitrate: '128k',
|
||||
selectedChannels: 2,
|
||||
customCodec: 'aac',
|
||||
customBitrate: '128k',
|
||||
customChannels: 2,
|
||||
currentCodec: '',
|
||||
currentBitrate: '',
|
||||
currentChannels: '',
|
||||
currentChanelLayout: '',
|
||||
isCodecsDifferent: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
codecItems() {
|
||||
return [
|
||||
{
|
||||
text: 'Copy',
|
||||
value: 'copy'
|
||||
},
|
||||
{
|
||||
text: 'AAC',
|
||||
value: 'aac'
|
||||
},
|
||||
{
|
||||
text: 'OPUS',
|
||||
value: 'opus'
|
||||
}
|
||||
]
|
||||
},
|
||||
bitrateItems() {
|
||||
return [
|
||||
{
|
||||
text: '32k',
|
||||
value: '32k'
|
||||
},
|
||||
{
|
||||
text: '64k',
|
||||
value: '64k'
|
||||
},
|
||||
{
|
||||
text: '128k',
|
||||
value: '128k'
|
||||
},
|
||||
{
|
||||
text: '192k',
|
||||
value: '192k'
|
||||
}
|
||||
]
|
||||
},
|
||||
channelsItems() {
|
||||
return [
|
||||
{
|
||||
text: '1 (mono)',
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
text: '2 (stereo)',
|
||||
value: 2
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
customBitrateChanged(val) {
|
||||
localStorage.setItem('embedMetadataBitrate', val)
|
||||
},
|
||||
customChannelsChanged(val) {
|
||||
localStorage.setItem('embedMetadataChannels', val)
|
||||
},
|
||||
customCodecChanged(val) {
|
||||
localStorage.setItem('embedMetadataCodec', val)
|
||||
},
|
||||
getEncodingOptions() {
|
||||
return {
|
||||
codec: this.selectedCodec || 'aac',
|
||||
bitrate: this.selectedBitrate || '128k',
|
||||
channels: this.selectedChannels || 2
|
||||
}
|
||||
},
|
||||
setPreset() {
|
||||
// If already AAC and not mixed, set copy
|
||||
if (this.currentCodec === 'aac' && !this.isCodecsDifferent) {
|
||||
this.selectedCodec = 'copy'
|
||||
} else {
|
||||
this.selectedCodec = 'aac'
|
||||
}
|
||||
|
||||
if (!this.currentBitrate) {
|
||||
this.selectedBitrate = '128k'
|
||||
} else {
|
||||
// Find closest bitrate rounding up
|
||||
const bitratesToMatch = [32, 64, 128, 192]
|
||||
const closestBitrate = bitratesToMatch.find((bitrate) => bitrate >= this.currentBitrate)
|
||||
this.selectedBitrate = closestBitrate + 'k'
|
||||
}
|
||||
|
||||
if (!this.currentChannels || isNaN(this.currentChannels)) {
|
||||
this.selectedChannels = 2
|
||||
} else {
|
||||
// Either 1 or 2
|
||||
this.selectedChannels = Math.max(Math.min(Number(this.currentChannels), 2), 1)
|
||||
}
|
||||
},
|
||||
setCurrentValues() {
|
||||
if (this.audioTracks.length === 0) return
|
||||
|
||||
this.currentChannels = this.audioTracks[0].channels
|
||||
this.currentChanelLayout = this.audioTracks[0].channelLayout
|
||||
this.currentCodec = this.audioTracks[0].codec
|
||||
|
||||
let totalBitrate = 0
|
||||
for (const track of this.audioTracks) {
|
||||
const trackBitrate = !isNaN(track.bitRate) ? track.bitRate : 0
|
||||
totalBitrate += trackBitrate
|
||||
|
||||
if (track.channels > this.currentChannels) this.currentChannels = track.channels
|
||||
if (track.codec !== this.currentCodec) {
|
||||
console.warn('Audio track codec is different from the first track', track.codec)
|
||||
this.isCodecsDifferent = true
|
||||
}
|
||||
}
|
||||
|
||||
this.currentBitrate = Math.round(totalBitrate / this.audioTracks.length / 1000)
|
||||
},
|
||||
init() {
|
||||
this.customBitrate = localStorage.getItem('embedMetadataBitrate') || '128k'
|
||||
this.customChannels = localStorage.getItem('embedMetadataChannels') || 2
|
||||
this.customCodec = localStorage.getItem('embedMetadataCodec') || 'aac'
|
||||
|
||||
this.setCurrentValues()
|
||||
|
||||
this.setPreset()
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.init()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -183,7 +183,7 @@ export default {
|
||||
this.$store.commit('libraries/updateFilterDataWithItem', libraryItem)
|
||||
},
|
||||
libraryItemUpdated(libraryItem) {
|
||||
if (this.$store.state.selectedLibraryItem && this.$store.state.selectedLibraryItem.id === libraryItem.id) {
|
||||
if (this.$store.state.selectedLibraryItem?.id === libraryItem.id) {
|
||||
this.$store.commit('setSelectedLibraryItem', libraryItem)
|
||||
if (this.$store.state.globals.selectedEpisode && libraryItem.mediaType === 'podcast') {
|
||||
const episode = libraryItem.media.episodes.find((ep) => ep.id === this.$store.state.globals.selectedEpisode.id)
|
||||
@@ -192,6 +192,9 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.$store.state.streamLibraryItem?.id === libraryItem.id) {
|
||||
this.$store.commit('updateStreamLibraryItem', libraryItem)
|
||||
}
|
||||
this.$eventBus.$emit(`${libraryItem.id}_updated`, libraryItem)
|
||||
this.$store.commit('libraries/updateFilterDataWithItem', libraryItem)
|
||||
},
|
||||
|
||||
4
client/package-lock.json
generated
4
client/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "audiobookshelf-client",
|
||||
"version": "2.20.0",
|
||||
"version": "2.21.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "audiobookshelf-client",
|
||||
"version": "2.20.0",
|
||||
"version": "2.21.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@nuxtjs/axios": "^5.13.6",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "audiobookshelf-client",
|
||||
"version": "2.20.0",
|
||||
"version": "2.21.0",
|
||||
"buildNumber": 1,
|
||||
"description": "Self-hosted audiobook and podcast client",
|
||||
"main": "index.js",
|
||||
|
||||
@@ -141,10 +141,21 @@
|
||||
</div>
|
||||
</template>
|
||||
<div class="w-full h-full max-h-full text-sm rounded-lg bg-bg shadow-lg border border-black-300 relative">
|
||||
<div v-if="!chapterData" class="flex p-20">
|
||||
<ui-text-input-with-label v-model.trim="asinInput" label="ASIN" />
|
||||
<ui-dropdown v-model="regionInput" :label="$strings.LabelRegion" small :items="audibleRegions" class="w-32 mx-1" />
|
||||
<ui-btn small color="bg-primary" class="mt-5" @click="findChapters">{{ $strings.ButtonSearch }}</ui-btn>
|
||||
<div v-if="!chapterData" class="flex flex-col items-center justify-center p-20">
|
||||
<div class="relative">
|
||||
<div class="flex items-end space-x-2">
|
||||
<ui-text-input-with-label v-model.trim="asinInput" label="ASIN" />
|
||||
<ui-dropdown v-model="regionInput" :label="$strings.LabelRegion" small :items="audibleRegions" class="w-32" />
|
||||
<ui-btn small color="bg-primary" @click="findChapters">{{ $strings.ButtonSearch }}</ui-btn>
|
||||
</div>
|
||||
|
||||
<div class="absolute left-0 mt-1.5 text-error text-s h-5">
|
||||
<p v-if="asinError">{{ asinError }}</p>
|
||||
<p v-if="asinError">{{ $strings.MessageAsinCheck }}</p>
|
||||
</div>
|
||||
|
||||
<div class="invisible h-5 mt-1 text-xs"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="w-full p-4">
|
||||
<div class="flex justify-between mb-4">
|
||||
@@ -221,6 +232,11 @@ export default {
|
||||
return redirect('/')
|
||||
}
|
||||
|
||||
// Fetch and set library if this items library does not match the current
|
||||
if (store.state.libraries.currentLibraryId !== libraryItem.libraryId || !store.state.libraries.filterData) {
|
||||
await store.dispatch('libraries/fetch', libraryItem.libraryId)
|
||||
}
|
||||
|
||||
var previousRoute = from ? from.fullPath : null
|
||||
if (from && from.path === '/login') previousRoute = null
|
||||
return {
|
||||
@@ -244,6 +260,7 @@ export default {
|
||||
findingChapters: false,
|
||||
showFindChaptersModal: false,
|
||||
chapterData: null,
|
||||
asinError: null,
|
||||
showSecondInputs: false,
|
||||
audibleRegions: ['US', 'CA', 'UK', 'AU', 'FR', 'DE', 'JP', 'IT', 'IN', 'ES'],
|
||||
hasChanges: false
|
||||
@@ -541,14 +558,14 @@ export default {
|
||||
|
||||
this.findingChapters = true
|
||||
this.chapterData = null
|
||||
this.asinError = null // used to show warning about audible vs amazon ASIN
|
||||
this.$axios
|
||||
.$get(`/api/search/chapters?asin=${this.asinInput}®ion=${this.regionInput}`)
|
||||
.then((data) => {
|
||||
this.findingChapters = false
|
||||
|
||||
if (data.error) {
|
||||
this.$toast.error(data.error)
|
||||
this.showFindChaptersModal = false
|
||||
this.asinError = this.$getString(data.stringKey)
|
||||
} else {
|
||||
console.log('Chapter data', data)
|
||||
this.chapterData = data
|
||||
|
||||
@@ -103,6 +103,12 @@ export default {
|
||||
console.error('No need to edit library item that is 1 file...')
|
||||
return redirect('/')
|
||||
}
|
||||
|
||||
// Fetch and set library if this items library does not match the current
|
||||
if (store.state.libraries.currentLibraryId !== libraryItem.libraryId || !store.state.libraries.filterData) {
|
||||
await store.dispatch('libraries/fetch', libraryItem.libraryId)
|
||||
}
|
||||
|
||||
return {
|
||||
libraryItem,
|
||||
files: libraryItem.media.audioFiles ? libraryItem.media.audioFiles.map((af) => ({ ...af, include: !af.exclude })) : []
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
<div class="w-full max-w-2xl"></div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-center flex-wrap">
|
||||
<div class="w-full max-w-2xl border border-white/10 bg-bg mx-2">
|
||||
<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>
|
||||
@@ -35,7 +35,7 @@
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full max-w-2xl border border-white/10 bg-bg mx-2">
|
||||
<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>
|
||||
@@ -77,10 +77,6 @@
|
||||
</div>
|
||||
<!-- m4b embed action buttons -->
|
||||
<div v-else class="w-full flex items-center mb-4">
|
||||
<button :disabled="processing" class="text-sm uppercase text-gray-200 flex items-center pt-px pl-1 pr-2 hover:bg-white/5 rounded-md" @click="showEncodeOptions = !showEncodeOptions">
|
||||
<span class="material-symbols text-xl">{{ showEncodeOptions || usingCustomEncodeOptions ? 'check_box' : 'check_box_outline_blank' }}</span> <span class="pl-1">{{ $strings.LabelUseAdvancedOptions }}</span>
|
||||
</button>
|
||||
|
||||
<div class="grow" />
|
||||
|
||||
<ui-btn v-if="!isTaskFinished && processing" color="bg-error" :loading="isCancelingEncode" class="mr-2" @click.stop="cancelEncodeClick">{{ $strings.ButtonCancelEncode }}</ui-btn>
|
||||
@@ -89,18 +85,16 @@
|
||||
<p v-else class="text-success text-lg font-semibold">{{ $strings.MessageM4BFinished }}</p>
|
||||
</div>
|
||||
|
||||
<!-- advanced encoding options -->
|
||||
<div v-if="isM4BTool" class="overflow-hidden">
|
||||
<transition name="slide">
|
||||
<div v-if="showEncodeOptions || usingCustomEncodeOptions" class="mb-4 pb-4 border-b border-white/10">
|
||||
<div class="flex flex-wrap -mx-2">
|
||||
<ui-text-input-with-label ref="bitrateInput" v-model="encodingOptions.bitrate" :disabled="processing || isTaskFinished" :label="$strings.LabelAudioBitrate" class="m-2 max-w-40" @input="bitrateChanged" />
|
||||
<ui-text-input-with-label ref="channelsInput" v-model="encodingOptions.channels" :disabled="processing || isTaskFinished" :label="$strings.LabelAudioChannels" class="m-2 max-w-40" @input="channelsChanged" />
|
||||
<ui-text-input-with-label ref="codecInput" v-model="encodingOptions.codec" :disabled="processing || isTaskFinished" :label="$strings.LabelAudioCodec" class="m-2 max-w-40" @input="codecChanged" />
|
||||
</div>
|
||||
<p class="text-sm text-warning">{{ $strings.LabelEncodingWarningAdvancedSettings }}</p>
|
||||
</div>
|
||||
</transition>
|
||||
<!-- show encoding options for running task -->
|
||||
<div v-if="encodeTaskHasEncodingOptions" class="mb-4 pb-4 border-b border-white/10">
|
||||
<div class="flex flex-wrap -mx-2">
|
||||
<ui-text-input-with-label ref="bitrateInput" v-model="encodingOptions.bitrate" readonly :label="$strings.LabelAudioBitrate" class="m-2 max-w-40" @input="bitrateChanged" />
|
||||
<ui-text-input-with-label ref="channelsInput" v-model="encodingOptions.channels" readonly :label="$strings.LabelAudioChannels" class="m-2 max-w-40" @input="channelsChanged" />
|
||||
<ui-text-input-with-label ref="codecInput" v-model="encodingOptions.codec" readonly :label="$strings.LabelAudioCodec" class="m-2 max-w-40" @input="codecChanged" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="isM4BTool" class="mb-4">
|
||||
<widgets-encoder-options-card ref="encoderOptionsCard" :audio-tracks="audioFiles" :disabled="processing || isTaskFinished" />
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
@@ -146,19 +140,29 @@
|
||||
<div class="flex py-2 px-4 bg-primary/25">
|
||||
<div class="w-10 text-xs font-semibold text-gray-200">#</div>
|
||||
<div class="grow text-xs font-semibold uppercase text-gray-200">{{ $strings.LabelFilename }}</div>
|
||||
<div class="w-20 text-xs font-semibold uppercase text-gray-200 hidden lg:block">{{ $strings.LabelChannels }}</div>
|
||||
<div class="w-16 text-xs font-semibold uppercase text-gray-200 hidden md:block">{{ $strings.LabelCodec }}</div>
|
||||
<div class="w-16 text-xs font-semibold uppercase text-gray-200 hidden md:block">{{ $strings.LabelBitrate }}</div>
|
||||
<div class="w-16 text-xs font-semibold uppercase text-gray-200">{{ $strings.LabelSize }}</div>
|
||||
<div class="w-24"></div>
|
||||
</div>
|
||||
<template v-for="file in audioFiles">
|
||||
<div :key="file.index" class="flex py-2 px-4 text-sm" :class="file.index % 2 === 0 ? 'bg-primary/25' : ''">
|
||||
<div class="w-10">{{ file.index }}</div>
|
||||
<div :key="file.index" class="flex py-2 px-4 text-xs sm:text-sm" :class="file.index % 2 === 0 ? 'bg-primary/25' : ''">
|
||||
<div class="w-10 min-w-10">{{ file.index }}</div>
|
||||
<div class="grow">
|
||||
{{ file.metadata.filename }}
|
||||
</div>
|
||||
<div class="w-16 font-mono text-gray-200">
|
||||
<div class="w-20 min-w-20 text-gray-200 hidden lg:block">{{ file.channels || 'unknown' }} ({{ file.channelLayout || 'unknown' }})</div>
|
||||
<div class="w-16 min-w-16 text-gray-200 hidden md:block">
|
||||
{{ file.codec || 'unknown' }}
|
||||
</div>
|
||||
<div class="w-16 min-w-16 text-gray-200 hidden md:block">
|
||||
{{ $bytesPretty(file.bitRate || 0, 0) }}
|
||||
</div>
|
||||
<div class="w-16 min-w-16 text-gray-200">
|
||||
{{ $bytesPretty(file.metadata.size) }}
|
||||
</div>
|
||||
<div class="w-24">
|
||||
<div class="w-24 min-w-24">
|
||||
<div class="flex justify-center">
|
||||
<span v-if="audioFilesFinished[file.ino]" class="material-symbols text-xl text-success leading-none">check_circle</span>
|
||||
<div v-else-if="audioFilesEncoding[file.ino]">
|
||||
@@ -195,10 +199,15 @@ export default {
|
||||
return redirect('/?error=invalid media type')
|
||||
}
|
||||
if (!libraryItem.media.audioFiles.length) {
|
||||
cnosole.error('No audio files')
|
||||
console.error('No audio files')
|
||||
return redirect('/?error=no audio files')
|
||||
}
|
||||
|
||||
// Fetch and set library if this items library does not match the current
|
||||
if (store.state.libraries.currentLibraryId !== libraryItem.libraryId || !store.state.libraries.filterData) {
|
||||
await store.dispatch('libraries/fetch', libraryItem.libraryId)
|
||||
}
|
||||
|
||||
return {
|
||||
libraryItem
|
||||
}
|
||||
@@ -209,7 +218,6 @@ export default {
|
||||
metadataObject: null,
|
||||
selectedTool: 'embed',
|
||||
isCancelingEncode: false,
|
||||
showEncodeOptions: false,
|
||||
shouldBackupAudioFiles: true,
|
||||
encodingOptions: {
|
||||
bitrate: '128k',
|
||||
@@ -309,8 +317,8 @@ export default {
|
||||
isMetadataEmbedQueued() {
|
||||
return this.queuedEmbedLIds.some((lid) => lid === this.libraryItemId)
|
||||
},
|
||||
usingCustomEncodeOptions() {
|
||||
return this.isM4BTool && this.encodeTask && this.encodeTask.data.encodeOptions && Object.keys(this.encodeTask.data.encodeOptions).length > 0
|
||||
encodeTaskHasEncodingOptions() {
|
||||
return this.isM4BTool && !!this.encodeTask?.data.encodeOptions && Object.keys(this.encodeTask.data.encodeOptions).length > 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -346,19 +354,13 @@ export default {
|
||||
if (this.$refs.channelsInput) this.$refs.channelsInput.blur()
|
||||
if (this.$refs.codecInput) this.$refs.codecInput.blur()
|
||||
|
||||
let queryStr = ''
|
||||
if (this.showEncodeOptions) {
|
||||
const options = []
|
||||
if (this.encodingOptions.bitrate) options.push(`bitrate=${this.encodingOptions.bitrate}`)
|
||||
if (this.encodingOptions.channels) options.push(`channels=${this.encodingOptions.channels}`)
|
||||
if (this.encodingOptions.codec) options.push(`codec=${this.encodingOptions.codec}`)
|
||||
if (options.length) {
|
||||
queryStr = `?${options.join('&')}`
|
||||
}
|
||||
}
|
||||
const encodeOptions = this.$refs.encoderOptionsCard.getEncodingOptions()
|
||||
|
||||
const queryParams = new URLSearchParams(encodeOptions)
|
||||
|
||||
this.processing = true
|
||||
this.$axios
|
||||
.$post(`/api/tools/item/${this.libraryItemId}/encode-m4b${queryStr}`)
|
||||
.$post(`/api/tools/item/${this.libraryItemId}/encode-m4b?${queryParams.toString()}`)
|
||||
.then(() => {
|
||||
console.log('Ab m4b merge started')
|
||||
})
|
||||
@@ -411,14 +413,10 @@ export default {
|
||||
const shouldBackupAudioFiles = localStorage.getItem('embedMetadataShouldBackup')
|
||||
this.shouldBackupAudioFiles = shouldBackupAudioFiles != 0
|
||||
|
||||
if (this.usingCustomEncodeOptions) {
|
||||
if (this.encodeTaskHasEncodingOptions) {
|
||||
if (this.encodeTask.data.encodeOptions.bitrate) this.encodingOptions.bitrate = this.encodeTask.data.encodeOptions.bitrate
|
||||
if (this.encodeTask.data.encodeOptions.channels) this.encodingOptions.channels = this.encodeTask.data.encodeOptions.channels
|
||||
if (this.encodeTask.data.encodeOptions.codec) this.encodingOptions.codec = this.encodeTask.data.encodeOptions.codec
|
||||
} else {
|
||||
this.encodingOptions.bitrate = localStorage.getItem('embedMetadataBitrate') || '128k'
|
||||
this.encodingOptions.channels = localStorage.getItem('embedMetadataChannels') || '2'
|
||||
this.encodingOptions.codec = localStorage.getItem('embedMetadataCodec') || 'aac'
|
||||
}
|
||||
},
|
||||
fetchMetadataEmbedObject() {
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
<p class="truncate">{{ feed.meta.title }}</p>
|
||||
</td>
|
||||
<!-- -->
|
||||
<td class="hidden xl:table-cell">
|
||||
<td class="hidden xl:table-cell max-w-48">
|
||||
<p class="truncate">{{ feed.slug }}</p>
|
||||
</td>
|
||||
<!-- -->
|
||||
@@ -57,7 +57,7 @@
|
||||
</td>
|
||||
<!-- -->
|
||||
<td class="text-center">
|
||||
<ui-icon-btn icon="delete" class="mx-0.5" :size="7" bg-color="bg-error" outlined @click.stop="deleteFeedClick(feed)" />
|
||||
<ui-icon-btn icon="delete" class="mx-0.5 text-white/70" borderless :size="7" iconFontSize="1.25rem" outlined @click.stop="deleteFeedClick(feed)" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -91,15 +91,15 @@
|
||||
{{ isMissing ? $strings.LabelMissing : $strings.LabelIncomplete }}
|
||||
</ui-btn>
|
||||
|
||||
<ui-tooltip v-if="showQueueBtn" :text="isQueued ? $strings.ButtonQueueRemoveItem : $strings.ButtonQueueAddItem" direction="top">
|
||||
<ui-icon-btn :icon="isQueued ? 'playlist_add_check' : 'playlist_play'" :bg-color="isQueued ? 'bg-primary' : 'bg-success/60'" class="mx-0.5" :class="isQueued ? 'text-success' : ''" @click="queueBtnClick" />
|
||||
</ui-tooltip>
|
||||
|
||||
<ui-btn v-if="showReadButton" color="bg-info" :padding-x="4" small class="flex items-center h-9 mr-2" @click="openEbook">
|
||||
<span class="material-symbols text-2xl -ml-2 pr-2 text-white" aria-hidden="true">auto_stories</span>
|
||||
{{ $strings.ButtonRead }}
|
||||
</ui-btn>
|
||||
|
||||
<ui-tooltip v-if="showQueueBtn" :text="isQueued ? $strings.ButtonQueueRemoveItem : $strings.ButtonQueueAddItem" direction="top">
|
||||
<ui-icon-btn :icon="isQueued ? 'playlist_add_check' : 'playlist_play'" :bg-color="isQueued ? 'bg-primary' : 'bg-success/60'" class="mx-0.5" :class="isQueued ? 'text-success' : ''" @click="queueBtnClick" />
|
||||
</ui-tooltip>
|
||||
|
||||
<ui-tooltip v-if="userCanUpdate" :text="$strings.LabelEdit" direction="top">
|
||||
<ui-icon-btn icon="" outlined class="mx-0.5" :aria-label="$strings.LabelEdit" @click="editClick" />
|
||||
</ui-tooltip>
|
||||
|
||||
@@ -249,7 +249,7 @@ export default {
|
||||
},
|
||||
async loadRecentEpisodes(page = 0) {
|
||||
this.processing = true
|
||||
const episodePayload = await this.$axios.$get(`/api/libraries/${this.libraryId}/recent-episodes?limit=25&page=${page}`).catch((error) => {
|
||||
const episodePayload = await this.$axios.$get(`/api/libraries/${this.libraryId}/recent-episodes?limit=50&page=${page}`).catch((error) => {
|
||||
console.error('Failed to get recent episodes', error)
|
||||
this.$toast.error(this.$strings.ToastFailedToLoadData)
|
||||
return null
|
||||
|
||||
@@ -89,14 +89,16 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
asyncData({ redirect, store }) {
|
||||
async asyncData({ redirect, store, params }) {
|
||||
if (!store.getters['user/getIsAdminOrUp']) {
|
||||
redirect('/')
|
||||
return
|
||||
}
|
||||
|
||||
if (!store.state.libraries.currentLibraryId) {
|
||||
return redirect('/config')
|
||||
const libraryId = params.library
|
||||
const library = await store.dispatch('libraries/fetch', libraryId)
|
||||
if (!library) {
|
||||
return redirect(`/oops?message=Library "${libraryId}" not found`)
|
||||
}
|
||||
return {}
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="w-full h-dvh max-h-dvh overflow-hidden" :style="{ backgroundColor: coverRgb }">
|
||||
<div class="w-full max-w-full h-dvh max-h-dvh overflow-hidden" :style="{ backgroundColor: coverRgb }">
|
||||
<div class="w-screen h-screen absolute inset-0 pointer-events-none" style="background: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, rgba(38, 38, 38, 1) 80%)"></div>
|
||||
<div class="absolute inset-0 w-screen h-dvh flex items-center justify-center z-10">
|
||||
<div class="w-full p-2 sm:p-4 md:p-8">
|
||||
@@ -335,8 +335,11 @@ export default {
|
||||
}
|
||||
},
|
||||
resize() {
|
||||
this.windowWidth = window.innerWidth
|
||||
this.windowHeight = window.innerHeight
|
||||
setTimeout(() => {
|
||||
this.windowWidth = window.innerWidth
|
||||
this.windowHeight = window.innerHeight
|
||||
this.$store.commit('globals/updateWindowSize', { width: window.innerWidth, height: window.innerHeight })
|
||||
}, 100)
|
||||
},
|
||||
playerError(error) {
|
||||
console.error('Player error', error)
|
||||
|
||||
@@ -316,9 +316,8 @@ export default {
|
||||
.$post('/api/upload', form)
|
||||
.then(() => true)
|
||||
.catch((error) => {
|
||||
console.error('Failed', error)
|
||||
var errorMessage = error.response && error.response.data ? error.response.data : 'Oops, something went wrong...'
|
||||
this.$toast.error(errorMessage)
|
||||
console.error('Failed to upload item', error)
|
||||
this.$toast.error(error.response?.data || 'Oops, something went wrong...')
|
||||
return false
|
||||
})
|
||||
},
|
||||
@@ -382,13 +381,9 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
let itemsUploaded = 0
|
||||
let itemsFailed = 0
|
||||
for (const item of itemsToUpload) {
|
||||
this.updateItemCardStatus(item.index, 'uploading')
|
||||
const result = await this.uploadItem(item)
|
||||
if (result) itemsUploaded++
|
||||
else itemsFailed++
|
||||
this.updateItemCardStatus(item.index, result ? 'success' : 'failed')
|
||||
}
|
||||
this.processing = false
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export default class AudioTrack {
|
||||
constructor(track, userToken, routerBasePath) {
|
||||
constructor(track, sessionId, userToken, routerBasePath) {
|
||||
this.index = track.index || 0
|
||||
this.startOffset = track.startOffset || 0 // Total time of all previous tracks
|
||||
this.duration = track.duration || 0
|
||||
@@ -8,28 +8,29 @@ export default class AudioTrack {
|
||||
this.mimeType = track.mimeType
|
||||
this.metadata = track.metadata || {}
|
||||
|
||||
this.userToken = userToken
|
||||
this.sessionId = sessionId
|
||||
this.routerBasePath = routerBasePath || ''
|
||||
if (this.contentUrl?.startsWith('/hls')) {
|
||||
this.sessionTrackUrl = `${this.contentUrl}?token=${userToken}`
|
||||
} else {
|
||||
this.sessionTrackUrl = `/public/session/${sessionId}/track/${this.index}`
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for CastPlayer
|
||||
*/
|
||||
get fullContentUrl() {
|
||||
if (!this.contentUrl || this.contentUrl.startsWith('http')) return this.contentUrl
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
return `${process.env.serverUrl}${this.contentUrl}?token=${this.userToken}`
|
||||
return `${process.env.serverUrl}${this.sessionTrackUrl}`
|
||||
}
|
||||
return `${window.location.origin}${this.routerBasePath}${this.contentUrl}?token=${this.userToken}`
|
||||
return `${window.location.origin}${this.routerBasePath}${this.sessionTrackUrl}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for LocalPlayer
|
||||
*/
|
||||
get relativeContentUrl() {
|
||||
if (!this.contentUrl || this.contentUrl.startsWith('http')) return this.contentUrl
|
||||
|
||||
return `${this.routerBasePath}${this.contentUrl}?token=${this.userToken}`
|
||||
return `${this.routerBasePath}${this.sessionTrackUrl}`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,7 +226,7 @@ export default class PlayerHandler {
|
||||
|
||||
console.log('[PlayerHandler] Preparing Session', session)
|
||||
|
||||
var audioTracks = session.audioTracks.map((at) => new AudioTrack(at, this.userToken, this.ctx.$config.routerBasePath))
|
||||
var audioTracks = session.audioTracks.map((at) => new AudioTrack(at, session.id, this.userToken, this.ctx.$config.routerBasePath))
|
||||
|
||||
this.ctx.playerLoading = true
|
||||
this.isHlsTranscode = true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const SupportedFileTypes = {
|
||||
image: ['png', 'jpg', 'jpeg', 'webp'],
|
||||
audio: ['m4b', 'mp3', 'm4a', 'flac', 'opus', 'ogg', 'oga', 'mp4', 'aac', 'wma', 'aiff', 'wav', 'webm', 'webma', 'mka', 'awb', 'caf', 'mpeg', 'mpg'],
|
||||
audio: ['m4b', 'mp3', 'm4a', 'flac', 'opus', 'ogg', 'oga', 'mp4', 'aac', 'wma', 'aiff', 'aif','wav', 'webm', 'webma', 'mka', 'awb', 'caf', 'mpeg', 'mpg'],
|
||||
ebook: ['epub', 'pdf', 'mobi', 'azw3', 'cbr', 'cbz'],
|
||||
info: ['nfo'],
|
||||
text: ['txt'],
|
||||
|
||||
@@ -171,6 +171,10 @@ export const mutations = {
|
||||
state.playerQueueItems = payload.queueItems || []
|
||||
}
|
||||
},
|
||||
updateStreamLibraryItem(state, libraryItem) {
|
||||
if (!libraryItem) return
|
||||
state.streamLibraryItem = libraryItem
|
||||
},
|
||||
setIsPlaying(state, isPlaying) {
|
||||
state.streamIsPlaying = isPlaying
|
||||
},
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
"ButtonIssues": "مشاكل",
|
||||
"ButtonJumpBackward": "اقفز للخلف",
|
||||
"ButtonJumpForward": "اقفز للأمام",
|
||||
"ButtonLatest": "أحدث",
|
||||
"ButtonLatest": "الأحدث",
|
||||
"ButtonLibrary": "المكتبة",
|
||||
"ButtonLogout": "تسجيل الخروج",
|
||||
"ButtonLookup": "البحث",
|
||||
@@ -51,10 +51,10 @@
|
||||
"ButtonNext": "التالي",
|
||||
"ButtonNextChapter": "الفصل التالي",
|
||||
"ButtonNextItemInQueue": "العنصر التالي في قائمة الانتظار",
|
||||
"ButtonOk": "نعم",
|
||||
"ButtonOk": "موافق",
|
||||
"ButtonOpenFeed": "فتح التغذية",
|
||||
"ButtonOpenManager": "فتح الإدارة",
|
||||
"ButtonPause": "تَوَقَّف",
|
||||
"ButtonPause": "إيقاف مؤقت",
|
||||
"ButtonPlay": "تشغيل",
|
||||
"ButtonPlayAll": "تشغيل الكل",
|
||||
"ButtonPlaying": "مشغل الآن",
|
||||
@@ -71,8 +71,8 @@
|
||||
"ButtonQuickMatch": "مطابقة سريعة",
|
||||
"ButtonReScan": "إعادة البحث",
|
||||
"ButtonRead": "اقرأ",
|
||||
"ButtonReadLess": "قلص",
|
||||
"ButtonReadMore": "المزيد",
|
||||
"ButtonReadLess": "اقرأ أقل",
|
||||
"ButtonReadMore": "اقرأ أكثر",
|
||||
"ButtonRefresh": "تحديث",
|
||||
"ButtonRemove": "إزالة",
|
||||
"ButtonRemoveAll": "إزالة الكل",
|
||||
@@ -98,7 +98,7 @@
|
||||
"ButtonStartM4BEncode": "ابدأ ترميز M4B",
|
||||
"ButtonStartMetadataEmbed": "ابدأ تضمين البيانات الوصفية",
|
||||
"ButtonStats": "الإحصائيات",
|
||||
"ButtonSubmit": "تقديم",
|
||||
"ButtonSubmit": "إرسال",
|
||||
"ButtonTest": "اختبار",
|
||||
"ButtonUnlinkOpenId": "إلغاء ربط المعرف",
|
||||
"ButtonUpload": "رفع",
|
||||
@@ -116,7 +116,7 @@
|
||||
"HeaderAddCustomMetadataProvider": "إضافة موفر بيانات تعريفية مخصص",
|
||||
"HeaderAdvanced": "متقدم",
|
||||
"HeaderAppriseNotificationSettings": "إعدادات الإشعارات",
|
||||
"HeaderAudioTracks": "المسارات الصوتية",
|
||||
"HeaderAudioTracks": "المقاطع الصوتية",
|
||||
"HeaderAudiobookTools": "أدوات إدارة ملفات الكتب الصوتية",
|
||||
"HeaderAuthentication": "المصادقة",
|
||||
"HeaderBackups": "النسخ الاحتياطية",
|
||||
@@ -152,5 +152,65 @@
|
||||
"HeaderLogin": "تسجيل الدخول",
|
||||
"HeaderLogs": "السجلات",
|
||||
"HeaderManageGenres": "إدارة الانواع",
|
||||
"HeaderManageTags": "إدارة العلامات"
|
||||
"HeaderManageTags": "إدارة العلامات",
|
||||
"HeaderOpenRSSFeed": "فتح تغذية RSS",
|
||||
"HeaderPlaylist": "قائمة تشغيل",
|
||||
"HeaderPlaylistItems": "عناصر قائمة التشغيل",
|
||||
"HeaderRSSFeedGeneral": "تفاصيل RSS",
|
||||
"HeaderRSSFeedIsOpen": "مغذي RSS مفتوح",
|
||||
"HeaderSettings": "إعدادات",
|
||||
"HeaderSleepTimer": "مؤقت النوم",
|
||||
"HeaderStatsMinutesListeningChart": "الدقائق المسموعة (آخر 7 أيام)",
|
||||
"HeaderStatsRecentSessions": "الجلسات الأخيرة",
|
||||
"HeaderTableOfContents": "جدول المحتويات",
|
||||
"HeaderYourStats": "إحصائياتك",
|
||||
"LabelAddToPlaylist": "أضف إلى قائمة التشغيل",
|
||||
"LabelAddedAt": "أضيفت على",
|
||||
"LabelAddedDate": "تمت الإضافة",
|
||||
"LabelAll": "الكل",
|
||||
"LabelAuthor": "المؤلف",
|
||||
"LabelAuthorFirstLast": "المؤلف (الاسم الأول الأخير)",
|
||||
"LabelAuthorLastFirst": "المؤلف (الاسم الأخير، الأول)",
|
||||
"LabelAuthors": "المؤلفون",
|
||||
"LabelAutoDownloadEpisodes": "تنزيل الحلقات تلقائيًا",
|
||||
"LabelBooks": "الكتب",
|
||||
"LabelChapters": "الفصول",
|
||||
"LabelClosePlayer": "إغلاق المشغل",
|
||||
"LabelCollapseSeries": "إخفاء المسلسلات",
|
||||
"LabelComplete": "مكتمل",
|
||||
"LabelContinueListening": "استمرار الاستماع",
|
||||
"LabelContinueReading": "استمرار القراءة",
|
||||
"LabelContinueSeries": "استمرار المسلسلات",
|
||||
"LabelDescription": "الوصف",
|
||||
"LabelDiscover": "استكشف",
|
||||
"LabelDownload": "تنزيل",
|
||||
"LabelDuration": "المدة",
|
||||
"LabelEbook": "الكتاب الإلكتروني",
|
||||
"LabelEbooks": "الكتب الإلكترونية",
|
||||
"LabelEnable": "تمكين",
|
||||
"LabelEnd": "انهاء",
|
||||
"LabelEndOfChapter": "نهاية الفصل",
|
||||
"LabelEpisode": "الحلقة",
|
||||
"LabelFeedURL": "عنوان التغذية",
|
||||
"LabelFile": "الملف",
|
||||
"LabelFileBirthtime": "وقت انشاء الملف",
|
||||
"LabelFileModified": "تم تعديل الملف",
|
||||
"LabelFilename": "اسم الملف",
|
||||
"LabelFinished": "المنجزة",
|
||||
"LabelFolder": "المجلد",
|
||||
"LabelFontBoldness": "تعريض الخط",
|
||||
"LabelFontScale": "نطاق الخط",
|
||||
"LabelGenre": "التصنيف",
|
||||
"LabelGenres": "التصانيف",
|
||||
"LabelHasEbook": "يحتوي كتاب إلكتروني",
|
||||
"LabelHasSupplementaryEbook": "يحتوي كتاب إلكتروني تكميلي",
|
||||
"LabelHost": "المضيف",
|
||||
"LabelInProgress": "تحت التنفيذ",
|
||||
"LabelIncomplete": "غير مكتمل",
|
||||
"LabelLanguage": "اللغة",
|
||||
"LabelLayout": "التنسيق",
|
||||
"LabelLayoutSinglePage": "صفحة واحدة",
|
||||
"LabelLineSpacing": "تباعد الأسطر",
|
||||
"LabelListenAgain": "الاستماع مجدداً",
|
||||
"LabelMediaType": "نوع الوسائط"
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
"ButtonApplyChapters": "Aplica Capítols",
|
||||
"ButtonAuthors": "Autors",
|
||||
"ButtonBack": "Enrere",
|
||||
"ButtonBatchEditPopulateFromExisting": "Omplir des d'existent",
|
||||
"ButtonBatchEditPopulateMapDetails": "Omplir detalls del mapa",
|
||||
"ButtonBrowseForFolder": "Cerca Carpeta",
|
||||
"ButtonCancel": "Cancel·la",
|
||||
"ButtonCancelEncode": "Cancel·la Codificador",
|
||||
@@ -117,7 +119,7 @@
|
||||
"HeaderAccount": "Compte",
|
||||
"HeaderAddCustomMetadataProvider": "Afegeix un proveïdor de metadades personalitzat",
|
||||
"HeaderAdvanced": "Avançat",
|
||||
"HeaderAppriseNotificationSettings": "Configuració de Notificacions Apprise",
|
||||
"HeaderAppriseNotificationSettings": "Paràmetres de notificacions Apprise",
|
||||
"HeaderAudioTracks": "Pistes d'àudio",
|
||||
"HeaderAudiobookTools": "Eines de gestió de fitxers de l'audiollibre",
|
||||
"HeaderAuthentication": "Autenticació",
|
||||
@@ -133,9 +135,9 @@
|
||||
"HeaderCustomMetadataProviders": "Proveïdors de metadades personalitzats",
|
||||
"HeaderDetails": "Detalls",
|
||||
"HeaderDownloadQueue": "Cua de baixades",
|
||||
"HeaderEbookFiles": "Fitxers de Llibres Digitals",
|
||||
"HeaderEbookFiles": "Fitxers de llibres digitals",
|
||||
"HeaderEmail": "Correu electrònic",
|
||||
"HeaderEmailSettings": "Configuració de Correu Electrònic",
|
||||
"HeaderEmailSettings": "Paràmetres de correu electrònic",
|
||||
"HeaderEpisodes": "Episodis",
|
||||
"HeaderEreaderDevices": "Dispositius Ereader",
|
||||
"HeaderEreaderSettings": "Paràmetres del lector",
|
||||
@@ -171,7 +173,7 @@
|
||||
"HeaderPasswordAuthentication": "Autenticació per Contrasenya",
|
||||
"HeaderPermissions": "Permisos",
|
||||
"HeaderPlayerQueue": "Cua del Reproductor",
|
||||
"HeaderPlayerSettings": "Configuració del Reproductor",
|
||||
"HeaderPlayerSettings": "Paràmetres del reproductor",
|
||||
"HeaderPlaylist": "Llista de Reproducció",
|
||||
"HeaderPlaylistItems": "Elements de la Llista de Reproducció",
|
||||
"HeaderPodcastsToAdd": "Pòdcasts a afegir",
|
||||
@@ -725,7 +727,8 @@
|
||||
"MessageConfirmResetProgress": "Estàs segur que vols reiniciar el teu progrés?",
|
||||
"MessageConfirmSendEbookToDevice": "Estàs segur que vols enviar {0} ebook(s) \"{1}\" al dispositiu \"{2}\"?",
|
||||
"MessageConfirmUnlinkOpenId": "Estàs segur que vols desvincular aquest usuari d'OpenID?",
|
||||
"MessageDownloadingEpisode": "Descarregant capítol",
|
||||
"MessageDaysListenedInTheLastYear": "{0} dies escoltats l'any passat",
|
||||
"MessageDownloadingEpisode": "S'està baixant l'episodi",
|
||||
"MessageDragFilesIntoTrackOrder": "Arrossega els fitxers en l'ordre correcte de les pistes",
|
||||
"MessageEmbedFailed": "Error en incrustar!",
|
||||
"MessageEmbedFinished": "Incrustació acabada!",
|
||||
@@ -1026,6 +1029,8 @@
|
||||
"ToastUnknownError": "Error desconegut",
|
||||
"ToastUnlinkOpenIdFailed": "Error en desvincular l'usuari d'OpenID",
|
||||
"ToastUnlinkOpenIdSuccess": "Usuari desvinculat d'OpenID",
|
||||
"ToastUploaderFilepathExistsError": "El camí del fitxer «{0}» ja existeix al servidor",
|
||||
"ToastUploaderItemExistsInSubdirectoryError": "L'element «{0}» usa un subdirectori del camí de pujada.",
|
||||
"ToastUserDeleteFailed": "Error en eliminar l'usuari",
|
||||
"ToastUserDeleteSuccess": "Usuari eliminat",
|
||||
"ToastUserPasswordChangeSuccess": "Contrasenya canviada correctament",
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
"ButtonApplyChapters": "Aplikovat kapitoly",
|
||||
"ButtonAuthors": "Autoři",
|
||||
"ButtonBack": "Zpět",
|
||||
"ButtonBatchEditPopulateFromExisting": "Vytvořit z existujících",
|
||||
"ButtonBatchEditPopulateMapDetails": "Předvyplnit podrobnosti mapování",
|
||||
"ButtonBrowseForFolder": "Vyhledat složku",
|
||||
"ButtonCancel": "Zrušit",
|
||||
"ButtonCancelEncode": "Zrušit kódování",
|
||||
@@ -145,7 +147,7 @@
|
||||
"HeaderItemFiles": "Soubory položek",
|
||||
"HeaderItemMetadataUtils": "Nástroje metadat položek",
|
||||
"HeaderLastListeningSession": "Poslední poslechová relace",
|
||||
"HeaderLatestEpisodes": "Nejnovější epizody",
|
||||
"HeaderLatestEpisodes": "Nové epizody",
|
||||
"HeaderLibraries": "Knihovny",
|
||||
"HeaderLibraryFiles": "Soubory knihovny",
|
||||
"HeaderLibraryStats": "Statistiky knihovny",
|
||||
@@ -227,6 +229,7 @@
|
||||
"LabelAddedDate": "Přidáno {0}",
|
||||
"LabelAdminUsersOnly": "Pouze administrátoři",
|
||||
"LabelAll": "Vše",
|
||||
"LabelAllEpisodesDownloaded": "Všechny epizody staženy",
|
||||
"LabelAllUsers": "Všichni uživatelé",
|
||||
"LabelAllUsersExcludingGuests": "Všichni uživatelé kromě hostů",
|
||||
"LabelAllUsersIncludingGuests": "Všichni uživatelé včetně hostů",
|
||||
@@ -250,7 +253,7 @@
|
||||
"LabelBackToUser": "Zpět k uživateli",
|
||||
"LabelBackupAudioFiles": "Zálohovat zvukové soubory",
|
||||
"LabelBackupLocation": "Umístění zálohy",
|
||||
"LabelBackupsEnableAutomaticBackups": "Povolit automatické zálohování",
|
||||
"LabelBackupsEnableAutomaticBackups": "Automatické zálohování",
|
||||
"LabelBackupsEnableAutomaticBackupsHelp": "Zálohy uložené v /metadata/backups",
|
||||
"LabelBackupsMaxBackupSize": "Maximální velikost zálohy (v GB) (0 bez omezení)",
|
||||
"LabelBackupsMaxBackupSizeHelp": "Ochrana proti chybné konfiguraci: Zálohování se nezdaří, pokud překročí nastavenou velikost.",
|
||||
@@ -282,6 +285,7 @@
|
||||
"LabelContinueSeries": "Pokračovat v sérii",
|
||||
"LabelCover": "Obálka",
|
||||
"LabelCoverImageURL": "URL obrázku obálky",
|
||||
"LabelCoverProvider": "Poskytovatel obálky",
|
||||
"LabelCreatedAt": "Vytvořeno v",
|
||||
"LabelCronExpression": "Výraz Cronu",
|
||||
"LabelCurrent": "Aktuální",
|
||||
@@ -421,7 +425,7 @@
|
||||
"LabelLookForNewEpisodesAfterDate": "Hledat nové epizody po tomto datu",
|
||||
"LabelLowestPriority": "Nejnižší priorita",
|
||||
"LabelMatchExistingUsersBy": "Přiřadit stávající uživatele podle",
|
||||
"LabelMatchExistingUsersByDescription": "Slouží k propojení stávajících uživatelů. Po propojení budou uživatelé přiřazeni k jedinečnému ID od poskytovatele SSO.",
|
||||
"LabelMatchExistingUsersByDescription": "Slouží k propojení stávajících uživatelů. Po propojení budou uživatelé přiřazeni k jedinečnému ID od poskytovatele SSO",
|
||||
"LabelMaxEpisodesToDownload": "Maximální # epizod pro stažení. Použijte 0 pro bez omezení.",
|
||||
"LabelMaxEpisodesToDownloadPerCheck": "Maximální počet nových epizod ke stažení při jedné kontrole",
|
||||
"LabelMaxEpisodesToKeep": "Maximální počet epizod k zachování",
|
||||
@@ -430,7 +434,7 @@
|
||||
"LabelMediaType": "Typ média",
|
||||
"LabelMetaTag": "Metaznačka",
|
||||
"LabelMetaTags": "Metaznačky",
|
||||
"LabelMetadataOrderOfPrecedenceDescription": "Zdroje metadat s vyšší prioritou budou mít přednost před zdroji metadat s nižší prioritou.",
|
||||
"LabelMetadataOrderOfPrecedenceDescription": "Zdroje metadat s vyšší prioritou budou mít přednost před zdroji metadat s nižší prioritou",
|
||||
"LabelMetadataProvider": "Poskytovatel metadat",
|
||||
"LabelMinute": "Minuta",
|
||||
"LabelMinutes": "Minuty",
|
||||
@@ -555,6 +559,8 @@
|
||||
"LabelSettingsBookshelfViewHelp": "Skeumorfní design s dřevěnými policemi",
|
||||
"LabelSettingsChromecastSupport": "Podpora Chromecastu",
|
||||
"LabelSettingsDateFormat": "Formát data",
|
||||
"LabelSettingsEnableWatcher": "Automaticky skenovat změny v knihovnách",
|
||||
"LabelSettingsEnableWatcherForLibrary": "Automaticky skenovat změny v knihovně",
|
||||
"LabelSettingsEnableWatcherHelp": "Povoluje automatické přidávání/aktualizaci položek, když jsou zjištěny změny souborů. *Vyžaduje restart serveru",
|
||||
"LabelSettingsEpubsAllowScriptedContent": "Povolení skriptovaného obsahu v epubu",
|
||||
"LabelSettingsEpubsAllowScriptedContentHelp": "Povolení spouštění skriptů v souborech epub. Doporučujeme toto nastavení vypnout, pokud nedůvěřujete zdroji souborů epub.",
|
||||
@@ -598,6 +604,7 @@
|
||||
"LabelSlug": "URL název",
|
||||
"LabelSortAscending": "Vzestupně",
|
||||
"LabelSortDescending": "Sestupně",
|
||||
"LabelSortPubDate": "Seřadit podle datumu publikování",
|
||||
"LabelStart": "Spustit",
|
||||
"LabelStartTime": "Čas Spuštění",
|
||||
"LabelStarted": "Spuštěno",
|
||||
@@ -702,6 +709,8 @@
|
||||
"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.",
|
||||
"MessageBackupsLocationPathEmpty": "Umístění záloh nemůže být prázdné",
|
||||
"MessageBatchEditPopulateMapDetailsAllHelp": "Předvyplnit vybraná pole datami ze všech položek. Pole s více hodnotami budou sloučena",
|
||||
"MessageBatchEditPopulateMapDetailsItemHelp": "Předvyplnit povolená pole mapování daty z této položky",
|
||||
"MessageBatchQuickMatchDescription": "Rychlá párování se pokusí přidat chybějící obálky a metadata pro vybrané položky. Povolením níže uvedených možností umožníte funkci Rychlé párování přepsat stávající obálky a/nebo metadata.",
|
||||
"MessageBookshelfNoCollections": "Ještě jste nevytvořili žádnou sbírku",
|
||||
"MessageBookshelfNoCollectionsHelp": "Kolekce jsou veřejné. Mohou je zobrazit všichni uživatelé s přístupem do knihovny.",
|
||||
@@ -838,6 +847,7 @@
|
||||
"MessageRestoreBackupConfirm": "Opravdu chcete obnovit zálohu vytvořenou dne",
|
||||
"MessageRestoreBackupWarning": "Obnovení zálohy přepíše celou databázi umístěnou v /config a obálku obrázků v /metadata/items & /metadata/authors.<br /><br />Backups nezmění žádné soubory ve složkách knihovny. Pokud jste povolili nastavení serveru pro ukládání obrázků obalu a metadat do složek knihovny, nebudou zálohovány ani přepsány.<br /><br />Všichni klienti používající váš server budou automaticky obnoveni.",
|
||||
"MessageScheduleLibraryScanNote": "Většině uživatelů se doporučuje ponechat tuto funkci vypnutou a ponechat zapnuté nastavení sledování složek. Sledování složek automaticky zjistí změny ve složkách vaší knihovny. Sledování složek nefunguje pro každý souborový systém (jako je NFS), takže místo toho lze použít plánované skenování knihoven.",
|
||||
"MessageScheduleRunEveryWeekdayAtTime": "Spusť každý {0} v {1}",
|
||||
"MessageSearchResultsFor": "Výsledky hledání pro",
|
||||
"MessageSelected": "{0} vybráno",
|
||||
"MessageServerCouldNotBeReached": "Server je nedostupný",
|
||||
@@ -945,6 +955,7 @@
|
||||
"ToastBackupRestoreFailed": "Nepodařilo se obnovit zálohu",
|
||||
"ToastBackupUploadFailed": "Nepodařilo se nahrát zálohu",
|
||||
"ToastBackupUploadSuccess": "Záloha nahrána",
|
||||
"ToastBatchApplyDetailsToItemsSuccess": "Detaily aplikované na položky",
|
||||
"ToastBatchDeleteFailed": "Hromadné smazání selhalo",
|
||||
"ToastBatchDeleteSuccess": "Hromadné smazání proběhlo úspěšně",
|
||||
"ToastBatchQuickMatchFailed": "Rychlá schoda dávky se nezdařila!",
|
||||
@@ -1057,6 +1068,7 @@
|
||||
"ToastSelectAtLeastOneUser": "Vyberte alespoň jednoho uživatele",
|
||||
"ToastSendEbookToDeviceFailed": "Odeslání e-knihy do zařízení se nezdařilo",
|
||||
"ToastSendEbookToDeviceSuccess": "E-kniha odeslána do zařízení \"{0}\"",
|
||||
"ToastSeriesSubmitFailedSameName": "Nelze přidat dvě série se stejným názvem",
|
||||
"ToastSeriesUpdateFailed": "Aktualizace série se nezdařila",
|
||||
"ToastSeriesUpdateSuccess": "Aktualizace série byla úspěšná",
|
||||
"ToastServerSettingsUpdateSuccess": "Nastavení serveru aktualizováno",
|
||||
@@ -1075,6 +1087,8 @@
|
||||
"ToastUnknownError": "Neznámý error",
|
||||
"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.",
|
||||
"ToastUserDeleteFailed": "Nepodařilo se smazat uživatele",
|
||||
"ToastUserDeleteSuccess": "Uživatel smazán",
|
||||
"ToastUserPasswordChangeSuccess": "Heslo bylo změněno úspěšně",
|
||||
|
||||
@@ -229,6 +229,7 @@
|
||||
"LabelAddedDate": "Tilføjet {0}",
|
||||
"LabelAdminUsersOnly": "Kun Administratorer",
|
||||
"LabelAll": "Alle",
|
||||
"LabelAllEpisodesDownloaded": "Alle episoder hentet",
|
||||
"LabelAllUsers": "Alle Brugere",
|
||||
"LabelAllUsersExcludingGuests": "Alle bruger eksklusiv gæster",
|
||||
"LabelAllUsersIncludingGuests": "Alle bruger inklusiv gæster",
|
||||
@@ -252,7 +253,7 @@
|
||||
"LabelBackToUser": "Tilbage til Bruger",
|
||||
"LabelBackupAudioFiles": "Sikkerhedskopier lydfiler",
|
||||
"LabelBackupLocation": "Backup Placering",
|
||||
"LabelBackupsEnableAutomaticBackups": "Aktivér automatisk sikkerhedskopiering",
|
||||
"LabelBackupsEnableAutomaticBackups": "Automatisk sikkerhedskopiering",
|
||||
"LabelBackupsEnableAutomaticBackupsHelp": "Sikkerhedskopier gemt i /metadata/backups",
|
||||
"LabelBackupsMaxBackupSize": "Maksimal sikkerhedskopistørrelse (i GB) (0 for ubegrænset)",
|
||||
"LabelBackupsMaxBackupSizeHelp": "Som en beskyttelse mod fejlkonfiguration fejler sikkerhedskopier, hvis de overstiger den konfigurerede størrelse.",
|
||||
@@ -558,6 +559,8 @@
|
||||
"LabelSettingsBookshelfViewHelp": "Skeumorfisk design med træhylder",
|
||||
"LabelSettingsChromecastSupport": "Chromecast-understøttelse",
|
||||
"LabelSettingsDateFormat": "Datoformat",
|
||||
"LabelSettingsEnableWatcher": "Scan automatisk bibliotek for ændringer",
|
||||
"LabelSettingsEnableWatcherForLibrary": "Scan automatisk bibliotek for ændringer",
|
||||
"LabelSettingsEnableWatcherHelp": "Aktiverer automatisk tilføjelse/opdatering af elementer, når filændringer registreres. *Kræver servergenstart",
|
||||
"LabelSettingsEpubsAllowScriptedContent": "Tillad scriptet indhold i epub",
|
||||
"LabelSettingsEpubsAllowScriptedContentHelp": "Tillad epub filer at køre scripts. Det anbefales at holde denne indstilling deaktiveret med mindre du stoler på kilderne af epub filerne.",
|
||||
@@ -1063,6 +1066,7 @@
|
||||
"ToastSelectAtLeastOneUser": "Vælg mindst en bruger",
|
||||
"ToastSendEbookToDeviceFailed": "Mislykkedes afsendelse af e-bog til enhed",
|
||||
"ToastSendEbookToDeviceSuccess": "E-bog afsendt til enhed \"{0}\"",
|
||||
"ToastSeriesSubmitFailedSameName": "Kan ikke tilføje to serier med samme navn",
|
||||
"ToastSeriesUpdateFailed": "Mislykkedes opdatering af serie",
|
||||
"ToastSeriesUpdateSuccess": "Serieopdatering lykkedes",
|
||||
"ToastServerSettingsUpdateSuccess": "Server indstillinger opdateret",
|
||||
@@ -1081,6 +1085,8 @@
|
||||
"ToastUnknownError": "Ukendt fejl",
|
||||
"ToastUnlinkOpenIdFailed": "Fejlede i af afkoble bruger fra OpenID",
|
||||
"ToastUnlinkOpenIdSuccess": "Bruger afkoblet fra OpenID",
|
||||
"ToastUploaderFilepathExistsError": "Filsti \"{0}\" findes allerede på serveren",
|
||||
"ToastUploaderItemExistsInSubdirectoryError": "Genstand \"{0}\" benytter en undermappe af upload stien",
|
||||
"ToastUserDeleteFailed": "Mislykkedes sletning af bruger",
|
||||
"ToastUserDeleteSuccess": "Bruger slettet",
|
||||
"ToastUserPasswordChangeSuccess": "Password ændret",
|
||||
|
||||
@@ -229,6 +229,7 @@
|
||||
"LabelAddedDate": "{0} Hinzugefügt",
|
||||
"LabelAdminUsersOnly": "Nur Admin Benutzer",
|
||||
"LabelAll": "Alle",
|
||||
"LabelAllEpisodesDownloaded": "Alle Episoden heruntergeladen",
|
||||
"LabelAllUsers": "Alle Benutzer",
|
||||
"LabelAllUsersExcludingGuests": "Alle Benutzer außer Gästen",
|
||||
"LabelAllUsersIncludingGuests": "Alle Benutzer und Gäste",
|
||||
@@ -397,7 +398,7 @@
|
||||
"LabelInvert": "Umkehren",
|
||||
"LabelItem": "Medium",
|
||||
"LabelJumpBackwardAmount": "Zurückspringen Zeit",
|
||||
"LabelJumpForwardAmount": "Vorwärtsspringn Zeit",
|
||||
"LabelJumpForwardAmount": "Vorwärtsspringen Zeit",
|
||||
"LabelLanguage": "Sprache",
|
||||
"LabelLanguageDefaultServer": "Standard-Server-Sprache",
|
||||
"LabelLanguages": "Sprachen",
|
||||
@@ -603,6 +604,7 @@
|
||||
"LabelSlug": "URL Teil",
|
||||
"LabelSortAscending": "Aufsteigend",
|
||||
"LabelSortDescending": "Absteigend",
|
||||
"LabelSortPubDate": "Erscheinungsdatum",
|
||||
"LabelStart": "Start",
|
||||
"LabelStartTime": "Startzeit",
|
||||
"LabelStarted": "Gestartet",
|
||||
@@ -953,6 +955,7 @@
|
||||
"ToastBackupRestoreFailed": "Sicherung konnte nicht wiederhergestellt werden",
|
||||
"ToastBackupUploadFailed": "Sicherung konnte nicht hochgeladen werden",
|
||||
"ToastBackupUploadSuccess": "Sicherung hochgeladen",
|
||||
"ToastBatchApplyDetailsToItemsSuccess": "Details auf Medien anwenden",
|
||||
"ToastBatchDeleteFailed": "Batch-Löschen fehlgeschlagen",
|
||||
"ToastBatchDeleteSuccess": "Batch-Löschung erfolgreich",
|
||||
"ToastBatchQuickMatchFailed": "Batch-Schnellabgleich fehlgeschlagen!",
|
||||
@@ -1065,6 +1068,7 @@
|
||||
"ToastSelectAtLeastOneUser": "Wähle mindestens einen Benutzer aus",
|
||||
"ToastSendEbookToDeviceFailed": "E-Buch konnte nicht auf Gerät übertragen werden",
|
||||
"ToastSendEbookToDeviceSuccess": "E-Buch an Gerät „{0}“ gesendet",
|
||||
"ToastSeriesSubmitFailedSameName": "Serien mit dem selben Namen können nicht hinzugefügt werden",
|
||||
"ToastSeriesUpdateFailed": "Aktualisierung der Serien fehlgeschlagen",
|
||||
"ToastSeriesUpdateSuccess": "Serien aktualisiert",
|
||||
"ToastServerSettingsUpdateSuccess": "Die Server-Einstellungen wurden geupdated",
|
||||
@@ -1083,6 +1087,8 @@
|
||||
"ToastUnknownError": "Unbekannter Fehler",
|
||||
"ToastUnlinkOpenIdFailed": "Fehler beim entkoppeln des Benutzers von OpenID",
|
||||
"ToastUnlinkOpenIdSuccess": "Benutzer entkoppelt von OpenID",
|
||||
"ToastUploaderFilepathExistsError": "Dateipfad \"{0}\" ist bereits auf dem Server horhanden",
|
||||
"ToastUploaderItemExistsInSubdirectoryError": "Element \"{0}\" verwendet ein Unterverzeichnis des Upload-Pfads.",
|
||||
"ToastUserDeleteFailed": "Benutzer konnte nicht gelöscht werden",
|
||||
"ToastUserDeleteSuccess": "Benutzer gelöscht",
|
||||
"ToastUserPasswordChangeSuccess": "Passwort erfolgreich verändert",
|
||||
|
||||
@@ -177,6 +177,7 @@
|
||||
"HeaderPlaylist": "Playlist",
|
||||
"HeaderPlaylistItems": "Playlist Items",
|
||||
"HeaderPodcastsToAdd": "Podcasts to Add",
|
||||
"HeaderPresets": "Presets",
|
||||
"HeaderPreviewCover": "Preview Cover",
|
||||
"HeaderRSSFeedGeneral": "RSS Details",
|
||||
"HeaderRSSFeedIsOpen": "RSS Feed is Open",
|
||||
@@ -604,6 +605,7 @@
|
||||
"LabelSlug": "Slug",
|
||||
"LabelSortAscending": "Ascending",
|
||||
"LabelSortDescending": "Descending",
|
||||
"LabelSortPubDate": "Sort Pub Date",
|
||||
"LabelStart": "Start",
|
||||
"LabelStartTime": "Start Time",
|
||||
"LabelStarted": "Started",
|
||||
@@ -704,6 +706,7 @@
|
||||
"LabelYourProgress": "Your Progress",
|
||||
"MessageAddToPlayerQueue": "Add to player queue",
|
||||
"MessageAppriseDescription": "To use this feature you will need to have an instance of <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> running or an api that will handle those same requests. <br />The Apprise API Url should be the full URL path to send the notification, e.g., if your API instance is served at <code>http://192.168.1.1:8337</code> then you would put <code>http://192.168.1.1:8337/notify</code>.",
|
||||
"MessageAsinCheck": "Ensure you are using the ASIN from the correct Audible region, not Amazon.",
|
||||
"MessageBackupsDescription": "Backups include users, user progress, library item details, server settings, and images stored in <code>/metadata/items</code> & <code>/metadata/authors</code>. Backups <strong>do not</strong> include any files stored in your library folders.",
|
||||
"MessageBackupsLocationEditNote": "Note: Updating the backup location will not move or modify existing backups",
|
||||
"MessageBackupsLocationNoEditNote": "Note: The backup location is set through an environment variable and cannot be changed here.",
|
||||
@@ -722,6 +725,7 @@
|
||||
"MessageChapterErrorStartGteDuration": "Invalid start time must be less than audiobook duration",
|
||||
"MessageChapterErrorStartLtPrev": "Invalid start time must be greater than or equal to previous chapter start time",
|
||||
"MessageChapterStartIsAfter": "Chapter start is after the end of your audiobook",
|
||||
"MessageChaptersNotFound": "Chapters not found",
|
||||
"MessageCheckingCron": "Checking cron...",
|
||||
"MessageConfirmCloseFeed": "Are you sure you want to close this feed?",
|
||||
"MessageConfirmDeleteBackup": "Are you sure you want to delete backup for {0}?",
|
||||
@@ -778,6 +782,7 @@
|
||||
"MessageForceReScanDescription": "will scan all files again like a fresh scan. Audio file ID3 tags, OPF files, and text files will be scanned as new.",
|
||||
"MessageImportantNotice": "Important Notice!",
|
||||
"MessageInsertChapterBelow": "Insert chapter below",
|
||||
"MessageInvalidAsin": "Invalid ASIN",
|
||||
"MessageItemsSelected": "{0} items selected",
|
||||
"MessageItemsUpdated": "{0} items updated",
|
||||
"MessageJoinUsOn": "Join us on",
|
||||
|
||||
@@ -77,9 +77,9 @@
|
||||
"ButtonRemove": "Quitar",
|
||||
"ButtonRemoveAll": "Quitar todo",
|
||||
"ButtonRemoveAllLibraryItems": "Quitar todos los elementos de la biblioteca",
|
||||
"ButtonRemoveFromContinueListening": "Remover de Continuar Escuchando",
|
||||
"ButtonRemoveFromContinueReading": "Remover de Continuar Leyendo",
|
||||
"ButtonRemoveSeriesFromContinueSeries": "Remover Serie de Continuar Series",
|
||||
"ButtonRemoveFromContinueListening": "Quitar de Continuar escuchando",
|
||||
"ButtonRemoveFromContinueReading": "Quitar de Continuar leyendo",
|
||||
"ButtonRemoveSeriesFromContinueSeries": "Quitar serie de Continuar serie",
|
||||
"ButtonReset": "Restablecer",
|
||||
"ButtonResetToDefault": "Restaurar valores predeterminados",
|
||||
"ButtonRestore": "Restaurar",
|
||||
@@ -227,6 +227,7 @@
|
||||
"LabelAddedDate": "{0} Añadido",
|
||||
"LabelAdminUsersOnly": "Solamente usuarios administradores",
|
||||
"LabelAll": "Todos",
|
||||
"LabelAllEpisodesDownloaded": "Todos los episodios descargados",
|
||||
"LabelAllUsers": "Todos los usuarios",
|
||||
"LabelAllUsersExcludingGuests": "Todos los usuarios excepto invitados",
|
||||
"LabelAllUsersIncludingGuests": "Todos los usuarios e invitados",
|
||||
@@ -436,8 +437,8 @@
|
||||
"LabelMinute": "Minuto",
|
||||
"LabelMinutes": "Minutos",
|
||||
"LabelMissing": "Falta",
|
||||
"LabelMissingEbook": "No tiene ebook",
|
||||
"LabelMissingSupplementaryEbook": "No tiene ebook suplementario",
|
||||
"LabelMissingEbook": "No tiene libro electrónico",
|
||||
"LabelMissingSupplementaryEbook": "No tiene libro electrónico suplementario",
|
||||
"LabelMobileRedirectURIs": "URIs de redirección a móviles permitidos",
|
||||
"LabelMobileRedirectURIsDescription": "Esta es una lista blanca de URI de redireccionamiento válidos para aplicaciones móviles. El predeterminado es <code> audiobookshelf</code> , que puede eliminar o complementar con URI adicionales para la integración de aplicaciones de terceros. Usando un asterisco (<code> *</code> ) como única entrada que permite cualquier URI.",
|
||||
"LabelMore": "Más",
|
||||
@@ -497,7 +498,7 @@
|
||||
"LabelPort": "Puerto",
|
||||
"LabelPrefixesToIgnore": "Prefijos para Ignorar (no distingue entre mayúsculas y minúsculas.)",
|
||||
"LabelPreventIndexing": "Evite que los directorios de pódcast de iTunes y Google indicen su suministro",
|
||||
"LabelPrimaryEbook": "Ebook principal",
|
||||
"LabelPrimaryEbook": "Libro electrónico principal",
|
||||
"LabelProgress": "Progreso",
|
||||
"LabelProvider": "Proveedor",
|
||||
"LabelProviderAuthorizationValue": "Valor del encabezado de autorización",
|
||||
@@ -713,20 +714,20 @@
|
||||
"MessageBookshelfNoResultsForQuery": "No hay resultados para la consulta",
|
||||
"MessageBookshelfNoSeries": "No tiene ninguna serie",
|
||||
"MessageChapterEndIsAfter": "El final del capítulo es después del final de tu audiolibro",
|
||||
"MessageChapterErrorFirstNotZero": "El primer capitulo debe iniciar en 0",
|
||||
"MessageChapterErrorFirstNotZero": "El primer capítulo debe iniciar en 0",
|
||||
"MessageChapterErrorStartGteDuration": "El tiempo de inicio no es válido: debe ser inferior a la duración del audiolibro",
|
||||
"MessageChapterErrorStartLtPrev": "El tiempo de inicio no es válido: debe ser mayor o igual que el tiempo de inicio del capítulo anterior",
|
||||
"MessageChapterStartIsAfter": "El comienzo del capítulo es después del final de su audiolibro",
|
||||
"MessageCheckingCron": "Revisando cron...",
|
||||
"MessageConfirmCloseFeed": "¿Confirma que quiere cerrar este suministro?",
|
||||
"MessageConfirmDeleteBackup": "¿Está seguro de que desea eliminar el respaldo {0}?",
|
||||
"MessageConfirmDeleteDevice": "¿Estás seguro de que deseas eliminar el lector electrónico \"{0}\"?",
|
||||
"MessageConfirmDeleteDevice": "¿Confirma que quiere eliminar el lector electrónico «{0}»?",
|
||||
"MessageConfirmDeleteFile": "Esto eliminará el archivo de su sistema de archivos. ¿Está seguro?",
|
||||
"MessageConfirmDeleteLibrary": "¿Está seguro de que desea eliminar permanentemente la biblioteca \"{0}\"?",
|
||||
"MessageConfirmDeleteLibrary": "¿Confirma que quiere eliminar permanentemente la biblioteca «{0}»?",
|
||||
"MessageConfirmDeleteLibraryItem": "Esto eliminará el elemento de la biblioteca de la base de datos y del sistema de archivos. ¿Confirma que quiere hacerlo?",
|
||||
"MessageConfirmDeleteLibraryItems": "Esto eliminará {0} elementos de la biblioteca de la base de datos y del sistema de archivos. ¿Confirma que quiere hacerlo?",
|
||||
"MessageConfirmDeleteMetadataProvider": "¿Estás seguro de que deseas eliminar el proveedor de metadatos personalizado \"{0}\"?",
|
||||
"MessageConfirmDeleteNotification": "¿Estás seguro de que deseas eliminar esta notificación?",
|
||||
"MessageConfirmDeleteMetadataProvider": "¿Confirma que quiere eliminar el proveedor de metadatos personalizado «{0}»?",
|
||||
"MessageConfirmDeleteNotification": "¿Confirma que quiere eliminar esta notificación?",
|
||||
"MessageConfirmDeleteSession": "¿Está seguro de que desea eliminar esta sesión?",
|
||||
"MessageConfirmEmbedMetadataInAudioFiles": "¿Está seguro de que desea incrustar metadatos en {0} archivos de audio?",
|
||||
"MessageConfirmForceReScan": "¿Está seguro de que desea forzar un re-escaneo?",
|
||||
@@ -741,27 +742,27 @@
|
||||
"MessageConfirmPurgeItemsCache": "Purgar la caché de los elementos eliminará todo el directorio <code>/metadata/cache/items</code>.<br />¿Estás seguro?",
|
||||
"MessageConfirmQuickEmbed": "¡Advertencia! La integración rápida no realiza copias de seguridad a ninguno de tus archivos de audio. Asegúrate de haber realizado una copia de los mismos previamente. <br><br>¿Deseas continuar?",
|
||||
"MessageConfirmQuickMatchEpisodes": "El reconocimiento rápido de extensiones sobrescribirá los detalles si se encuentra una coincidencia. Se actualizarán las extensiones no reconocidas. ¿Está seguro?",
|
||||
"MessageConfirmReScanLibraryItems": "¿Estás seguro de querer re escanear {0} elemento(s)?",
|
||||
"MessageConfirmRemoveAllChapters": "¿Está seguro de que desea remover todos los capitulos?",
|
||||
"MessageConfirmRemoveAuthor": "¿Está seguro de que desea remover el autor \"{0}\"?",
|
||||
"MessageConfirmRemoveCollection": "¿Está seguro de que desea remover la colección \"{0}\"?",
|
||||
"MessageConfirmRemoveEpisode": "¿Está seguro de que desea remover el episodio \"{0}\"?",
|
||||
"MessageConfirmRemoveEpisodes": "¿Está seguro de que desea remover {0} episodios?",
|
||||
"MessageConfirmRemoveListeningSessions": "¿Está seguro que desea remover {0} sesiones de escuchar?",
|
||||
"MessageConfirmReScanLibraryItems": "¿Confirma que quiere volver a analizar {0} elementos?",
|
||||
"MessageConfirmRemoveAllChapters": "¿Confirma que quiere quitar todos los capítulos?",
|
||||
"MessageConfirmRemoveAuthor": "¿Confirma que quiere quitar el autor «{0}»?",
|
||||
"MessageConfirmRemoveCollection": "¿Confirma que quiere quitar la colección «{0}»?",
|
||||
"MessageConfirmRemoveEpisode": "¿Confirma que quiere quitar el episodio «{0}»?",
|
||||
"MessageConfirmRemoveEpisodes": "¿Confirma que quiere quitar {0} episodios?",
|
||||
"MessageConfirmRemoveListeningSessions": "¿Confirma que quiere quitar {0} sesiones de escucha?",
|
||||
"MessageConfirmRemoveMetadataFiles": "¿Está seguro de que desea eliminar todos los archivos de metadatos.{0} en las carpetas de elementos de su biblioteca?",
|
||||
"MessageConfirmRemoveNarrator": "¿Está seguro de que desea remover el narrador \"{0}\"?",
|
||||
"MessageConfirmRemovePlaylist": "¿Está seguro de que desea remover la lista de reproducción \"{0}\"?",
|
||||
"MessageConfirmRemoveNarrator": "¿Confirma que quiere quitar el narrador «{0}»?",
|
||||
"MessageConfirmRemovePlaylist": "¿Confirma que quiere quitar la lista de reproducción «{0}»?",
|
||||
"MessageConfirmRenameGenre": "¿Está seguro de que desea renombrar el genero \"{0}\" a \"{1}\" de todos los elementos?",
|
||||
"MessageConfirmRenameGenreMergeNote": "Nota: Este género ya existe, por lo que se fusionarán.",
|
||||
"MessageConfirmRenameGenreWarning": "Advertencia! Un genero similar ya existe \"{0}\".",
|
||||
"MessageConfirmRenameTag": "¿Está seguro de que desea renombrar la etiqueta \"{0}\" a \"{1}\" de todos los elementos?",
|
||||
"MessageConfirmRenameTagMergeNote": "Nota: Esta etiqueta ya existe, por lo que se fusionarán.",
|
||||
"MessageConfirmRenameTagWarning": "Advertencia! Una etiqueta similar ya existe \"{0}\".",
|
||||
"MessageConfirmResetProgress": "¿Estás seguro de que quieres reiniciar tu progreso?",
|
||||
"MessageConfirmSendEbookToDevice": "¿Está seguro de que enviar {0} ebook(s) \"{1}\" al dispositivo \"{2}\"?",
|
||||
"MessageConfirmResetProgress": "¿Confirma que quiere restablecer su progreso?",
|
||||
"MessageConfirmSendEbookToDevice": "¿Confirma que quiere enviar el libro electrónico {0} «{1}» al dispositivo «{2}»?",
|
||||
"MessageConfirmUnlinkOpenId": "¿Estás seguro de que deseas desvincular este usuario de OpenID?",
|
||||
"MessageDaysListenedInTheLastYear": "{0} dies escoltats en l'últim any",
|
||||
"MessageDownloadingEpisode": "Descargando Capitulo",
|
||||
"MessageDaysListenedInTheLastYear": "{0} días escuchados el año pasado",
|
||||
"MessageDownloadingEpisode": "Descargando episodio",
|
||||
"MessageDragFilesIntoTrackOrder": "Arrastra los archivos al orden correcto de las pistas",
|
||||
"MessageEmbedFailed": "¡Error al insertar!",
|
||||
"MessageEmbedFinished": "Incrustación Terminada!",
|
||||
@@ -810,7 +811,7 @@
|
||||
"MessageNoMediaProgress": "Multimedia sin Progreso",
|
||||
"MessageNoNotifications": "Ninguna notificación",
|
||||
"MessageNoPodcastFeed": "Podcast no válido: Sin feed",
|
||||
"MessageNoPodcastsFound": "Ningún podcast encontrado",
|
||||
"MessageNoPodcastsFound": "No se encontró ningún pódcast",
|
||||
"MessageNoResults": "Sin Resultados",
|
||||
"MessageNoSearchResultsFor": "La búsqueda «{0}» no produjo ningún resultado",
|
||||
"MessageNoSeries": "Ninguna serie",
|
||||
@@ -832,10 +833,10 @@
|
||||
"MessageQuickEmbedQueue": "En cola para inserción rápida ({0} en cola)",
|
||||
"MessageQuickMatchAllEpisodes": "Combina rápidamente todos los episodios",
|
||||
"MessageQuickMatchDescription": "Rellenar detalles de elementos vacíos y portada con los primeros resultados de '{0}'. No sobrescribe los detalles a menos que la opción \"Preferir Metadatos Encontrados\" del servidor esté habilitada.",
|
||||
"MessageRemoveChapter": "Remover capítulos",
|
||||
"MessageRemoveEpisodes": "Remover {0} episodio(s)",
|
||||
"MessageRemoveFromPlayerQueue": "Remover la cola de reproducción",
|
||||
"MessageRemoveUserWarning": "¿Está seguro de que desea eliminar el usuario \"{0}\"?",
|
||||
"MessageRemoveChapter": "Quitar capítulo",
|
||||
"MessageRemoveEpisodes": "Quitar {0} episodio(s)",
|
||||
"MessageRemoveFromPlayerQueue": "Quitar de la cola de reproducción",
|
||||
"MessageRemoveUserWarning": "¿Confirma que quiere eliminar permanentemente el usuario «{0}»?",
|
||||
"MessageReportBugsAndContribute": "Reporte erres, solicite funciones y contribuya en",
|
||||
"MessageResetChaptersConfirm": "¿Está seguro de que desea deshacer los cambios y revertir los capítulos a su estado original?",
|
||||
"MessageRestoreBackupConfirm": "¿Está seguro de que desea para restaurar del respaldo creado en",
|
||||
@@ -851,7 +852,7 @@
|
||||
"MessageStartPlaybackAtTime": "Iniciar reproducción para \"{0}\" en {1}?",
|
||||
"MessageTaskAudioFileNotWritable": "El archivo de audio \"{0}\" no se puede grabar",
|
||||
"MessageTaskCanceledByUser": "Tarea cancelada por el usuario",
|
||||
"MessageTaskDownloadingEpisodeDescription": "Descargando el episodio \"{0}\"",
|
||||
"MessageTaskDownloadingEpisodeDescription": "Descargando el episodio «{0}»",
|
||||
"MessageTaskEmbeddingMetadata": "Inserción de metadatos",
|
||||
"MessageTaskEmbeddingMetadataDescription": "Inserción de metadatos en el audiolibro \"{0}\"",
|
||||
"MessageTaskEncodingM4b": "Codificación M4B",
|
||||
@@ -863,16 +864,16 @@
|
||||
"MessageTaskFailedToMergeAudioFiles": "Error al fusionar archivos de audio",
|
||||
"MessageTaskFailedToMoveM4bFile": "Error al mover el archivo m4b",
|
||||
"MessageTaskFailedToWriteMetadataFile": "Error al escribir el archivo de metadatos",
|
||||
"MessageTaskMatchingBooksInLibrary": "Libros coincidentes en la biblioteca \"{0}\"",
|
||||
"MessageTaskMatchingBooksInLibrary": "Libros coincidentes en la biblioteca «{0}»",
|
||||
"MessageTaskNoFilesToScan": "Sin archivos para escanear",
|
||||
"MessageTaskOpmlImport": "Importar OPML",
|
||||
"MessageTaskOpmlImportDescription": "Creando pódcast a partir de {0} suministros RSS",
|
||||
"MessageTaskOpmlImportFeed": "Feed de importación OPML",
|
||||
"MessageTaskOpmlImportFeedDescription": "Importando el feed RSS \"{0}\"",
|
||||
"MessageTaskOpmlImportFeedDescription": "Importando el suministro RSS «{0}»",
|
||||
"MessageTaskOpmlImportFeedFailed": "No se puede obtener el podcast",
|
||||
"MessageTaskOpmlImportFeedPodcastDescription": "Creando podcast \"{0}\"",
|
||||
"MessageTaskOpmlImportFeedPodcastDescription": "Creando pódcast «{0}»",
|
||||
"MessageTaskOpmlImportFeedPodcastExists": "Podcast ya existe en la ruta",
|
||||
"MessageTaskOpmlImportFeedPodcastFailed": "Error al crear podcast",
|
||||
"MessageTaskOpmlImportFeedPodcastFailed": "No se pudo crear el pódcast",
|
||||
"MessageTaskOpmlImportFinished": "Añadido {0} podcasts",
|
||||
"MessageTaskOpmlParseFailed": "No se pudo analizar el archivo OPML",
|
||||
"MessageTaskOpmlParseFastFail": "No se encontró la etiqueta <opml> del archivo OPML no válido O no se encontró la etiqueta <outline>",
|
||||
@@ -948,6 +949,7 @@
|
||||
"ToastBackupRestoreFailed": "Error al restaurar el respaldo",
|
||||
"ToastBackupUploadFailed": "Error al subir el respaldo",
|
||||
"ToastBackupUploadSuccess": "Respaldo cargado",
|
||||
"ToastBatchApplyDetailsToItemsSuccess": "Detalles aplicados a los elementos",
|
||||
"ToastBatchDeleteFailed": "Error al eliminar por lotes",
|
||||
"ToastBatchDeleteSuccess": "Borrado por lotes correcto",
|
||||
"ToastBatchQuickMatchFailed": "¡Error en la sincronización rápida por lotes!",
|
||||
@@ -964,7 +966,7 @@
|
||||
"ToastChaptersRemoved": "Capítulos eliminados",
|
||||
"ToastChaptersUpdated": "Capítulos actualizados",
|
||||
"ToastCollectionItemsAddFailed": "Artículo(s) añadido(s) a la colección fallido(s)",
|
||||
"ToastCollectionRemoveSuccess": "Colección removida",
|
||||
"ToastCollectionRemoveSuccess": "Colección quitada",
|
||||
"ToastCollectionUpdateSuccess": "Colección actualizada",
|
||||
"ToastCoverUpdateFailed": "Error al actualizar la cubierta",
|
||||
"ToastDateTimeInvalidOrIncomplete": "Fecha y hora inválidas o incompletas",
|
||||
@@ -998,12 +1000,12 @@
|
||||
"ToastItemMarkedAsNotFinishedSuccess": "Elemento marcado como No Terminado",
|
||||
"ToastItemUpdateSuccess": "Elemento actualizado",
|
||||
"ToastLibraryCreateFailed": "Error al crear biblioteca",
|
||||
"ToastLibraryCreateSuccess": "Biblioteca \"{0}\" creada",
|
||||
"ToastLibraryCreateSuccess": "Se creó la biblioteca «{0}»",
|
||||
"ToastLibraryDeleteFailed": "Error al eliminar biblioteca",
|
||||
"ToastLibraryDeleteSuccess": "Biblioteca eliminada",
|
||||
"ToastLibraryScanFailedToStart": "Error al iniciar el escaneo",
|
||||
"ToastLibraryScanStarted": "Se inició el escaneo de la biblioteca",
|
||||
"ToastLibraryUpdateSuccess": "Biblioteca \"{0}\" actualizada",
|
||||
"ToastLibraryUpdateSuccess": "Se actualizó la biblioteca «{0}»",
|
||||
"ToastMatchAllAuthorsFailed": "No se pudo encontrar a todos los autores",
|
||||
"ToastMetadataFilesRemovedError": "Error al eliminar metadatos de {0} archivo(s)",
|
||||
"ToastMetadataFilesRemovedNoneFound": "No hay metadatos.{0} archivo(s) encontrado(s) en la biblioteca",
|
||||
@@ -1013,7 +1015,7 @@
|
||||
"ToastNameEmailRequired": "Son obligatorios el nombre y el correo electrónico",
|
||||
"ToastNameRequired": "Nombre obligatorio",
|
||||
"ToastNewEpisodesFound": "{0} nuevo(s) episodio(s) encontrado(s)",
|
||||
"ToastNewUserCreatedFailed": "Error al crear la cuenta: \"{0}\"",
|
||||
"ToastNewUserCreatedFailed": "No se pudo crear la cuenta: «{0}»",
|
||||
"ToastNewUserCreatedSuccess": "Nueva cuenta creada",
|
||||
"ToastNewUserLibraryError": "Debes seleccionar al menos una biblioteca",
|
||||
"ToastNewUserPasswordError": "Debes tener una contraseña, solo el usuario root puede estar sin contraseña",
|
||||
@@ -1034,8 +1036,8 @@
|
||||
"ToastPlaylistCreateSuccess": "Lista de reproducción creada",
|
||||
"ToastPlaylistRemoveSuccess": "Lista de reproducción eliminada",
|
||||
"ToastPlaylistUpdateSuccess": "Lista de reproducción actualizada",
|
||||
"ToastPodcastCreateFailed": "Error al crear podcast",
|
||||
"ToastPodcastCreateSuccess": "Podcast creado",
|
||||
"ToastPodcastCreateFailed": "No se pudo crear el pódcast",
|
||||
"ToastPodcastCreateSuccess": "Se creó el pódcast correctamente",
|
||||
"ToastPodcastGetFeedFailed": "No se puede obtener el podcast",
|
||||
"ToastPodcastNoEpisodesInFeed": "No se han encontrado episodios en el feed del RSS",
|
||||
"ToastPodcastNoRssFeed": "El podcast no tiene feed RSS",
|
||||
@@ -1057,9 +1059,9 @@
|
||||
"ToastRescanUpToDate": "Reescaneado del artículo completo, estaba actualizado",
|
||||
"ToastRescanUpdated": "Reescaneado completado, el artículo ha sido actualizado",
|
||||
"ToastScanFailed": "No se pudo escanear el elemento de la biblioteca",
|
||||
"ToastSelectAtLeastOneUser": "Selecciona al menos un usuario",
|
||||
"ToastSendEbookToDeviceFailed": "Error al enviar el ebook al dispositivo",
|
||||
"ToastSendEbookToDeviceSuccess": "Ebook enviado al dispositivo \"{0}\"",
|
||||
"ToastSelectAtLeastOneUser": "Seleccione al menos un usuario",
|
||||
"ToastSendEbookToDeviceFailed": "No se pudo enviar el libro electrónico al dispositivo",
|
||||
"ToastSendEbookToDeviceSuccess": "Libro electrónico enviado al dispositivo «{0}»",
|
||||
"ToastSeriesSubmitFailedSameName": "No se puede añadir dos series con el mismo nombre",
|
||||
"ToastSeriesUpdateFailed": "Error al actualizar la serie",
|
||||
"ToastSeriesUpdateSuccess": "Serie actualizada",
|
||||
@@ -1079,10 +1081,12 @@
|
||||
"ToastUnknownError": "Error desconocido",
|
||||
"ToastUnlinkOpenIdFailed": "Error al desvincular el usuario de OpenID",
|
||||
"ToastUnlinkOpenIdSuccess": "Usuario desvinculado de OpenID",
|
||||
"ToastUploaderFilepathExistsError": "La ruta de archivo «{0}» ya existe en el servidor",
|
||||
"ToastUploaderItemExistsInSubdirectoryError": "El elemento «{0}» usa un subdirectorio de la ruta de carga.",
|
||||
"ToastUserDeleteFailed": "Error al eliminar el usuario",
|
||||
"ToastUserDeleteSuccess": "Usuario eliminado",
|
||||
"ToastUserPasswordChangeSuccess": "Contraseña modificada correctamente",
|
||||
"ToastUserPasswordMismatch": "No coinciden las contraseñas",
|
||||
"ToastUserPasswordMustChange": "La nueva contraseña no puede ser igual que la anterior",
|
||||
"ToastUserRootRequireName": "Debes introducir un nombre de usuario root"
|
||||
"ToastUserRootRequireName": "Debe introducir un nombre de usuario administrativo"
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
"ButtonQueueAddItem": "Lisää jonoon",
|
||||
"ButtonQueueRemoveItem": "Poista jonosta",
|
||||
"ButtonQuickEmbed": "Pikaupota",
|
||||
"ButtonQuickEmbedMetadata": "Upota kuvailutiedot nopeasti",
|
||||
"ButtonQuickEmbedMetadata": "Upota metatiedot pikaisesti",
|
||||
"ButtonQuickMatch": "Pikatäsmäys",
|
||||
"ButtonReScan": "Uudelleenskannaa",
|
||||
"ButtonRead": "Lue",
|
||||
@@ -229,6 +229,7 @@
|
||||
"LabelAddedDate": "Lisätty {0}",
|
||||
"LabelAdminUsersOnly": "Vain järjestelmänvalvojat",
|
||||
"LabelAll": "Kaikki",
|
||||
"LabelAllEpisodesDownloaded": "Kaikki jaksot ladattu",
|
||||
"LabelAllUsers": "Kaikki käyttäjät",
|
||||
"LabelAllUsersExcludingGuests": "Kaikki käyttäjät vieraita lukuun ottamatta",
|
||||
"LabelAllUsersIncludingGuests": "Kaikki käyttäjät mukaan lukien vieraat",
|
||||
@@ -252,7 +253,7 @@
|
||||
"LabelBackToUser": "Takaisin käyttäjään",
|
||||
"LabelBackupAudioFiles": "Varmuuskopioi äänitiedostot",
|
||||
"LabelBackupLocation": "Varmuuskopiointipaikka",
|
||||
"LabelBackupsEnableAutomaticBackups": "Ota automaattinen varmuuskopiointi käyttöön",
|
||||
"LabelBackupsEnableAutomaticBackups": "Automaattiset varmuuskopiot",
|
||||
"LabelBackupsEnableAutomaticBackupsHelp": "Varmuuskopiot tallennettu kansioon /metadata/backups",
|
||||
"LabelBackupsMaxBackupSize": "Varmuuskopion enimmäiskoko (Gt) (0 rajaton)",
|
||||
"LabelBackupsMaxBackupSizeHelp": "Virheellisten asetusten estämiseksi varmuuskopiot epäonnistuvat, jos ne ovat asetettua kokoa suurempia.",
|
||||
@@ -344,6 +345,9 @@
|
||||
"LabelExample": "Esimerkki",
|
||||
"LabelExpandSeries": "Laajenna sarja",
|
||||
"LabelExpandSubSeries": "Laajenna alisarja",
|
||||
"LabelExplicit": "Yksiselitteinen",
|
||||
"LabelExplicitChecked": "Yksiselitteinen (valittu)",
|
||||
"LabelExplicitUnchecked": "Ei yksiselitteinen (ei valittu)",
|
||||
"LabelExportOPML": "Vie OPML",
|
||||
"LabelFeedURL": "Syötteen URL",
|
||||
"LabelFetchingMetadata": "Noudetaan kuvailutietoja",
|
||||
@@ -372,7 +376,7 @@
|
||||
"LabelHasEbook": "Sillä on s-kirja",
|
||||
"LabelHasSupplementaryEbook": "Sillä on täydentävän s-kirjan",
|
||||
"LabelHideSubtitles": "Piilota tekstitykset",
|
||||
"LabelHighestPriority": "Tärkein",
|
||||
"LabelHighestPriority": "Korkein etusija",
|
||||
"LabelHost": "Isäntä",
|
||||
"LabelHour": "Tunti",
|
||||
"LabelHours": "Tunnit",
|
||||
@@ -393,6 +397,8 @@
|
||||
"LabelIntervalEveryMinute": "Joka minuutti",
|
||||
"LabelInvert": "Saa käänteiseksi",
|
||||
"LabelItem": "Kohde",
|
||||
"LabelJumpBackwardAmount": "Taaksepäin-hyppyjen määrä",
|
||||
"LabelJumpForwardAmount": "Eteenpäin-hyppyjen määrä",
|
||||
"LabelLanguage": "Kieli",
|
||||
"LabelLanguageDefaultServer": "Palvelimen oletuskieli",
|
||||
"LabelLanguages": "Kielet",
|
||||
@@ -484,6 +490,7 @@
|
||||
"LabelPersonalYearReview": "Vuotesi katsauksessa ({0})",
|
||||
"LabelPhotoPathURL": "Valokuvan polku/URL-osoite",
|
||||
"LabelPlayMethod": "Toistotapa",
|
||||
"LabelPlaybackRateIncrementDecrement": "Toistonopeuden lisäys-/vähennysmäärä",
|
||||
"LabelPlayerChapterNumberMarker": "{0}/{1}",
|
||||
"LabelPlaylists": "Soittolistat",
|
||||
"LabelPodcast": "Podcast",
|
||||
@@ -525,7 +532,7 @@
|
||||
"LabelRemoveAllMetadataJson": "Poista kaikki metadata.json-tiedostot",
|
||||
"LabelRemoveCover": "Poista kansikuva",
|
||||
"LabelRemoveMetadataFile": "Poista metatietotiedostot kirjaston kohdekansioista",
|
||||
"LabelRemoveMetadataFileHelp": "Poista kaikki metadata.json- ja metadata.abs-tiedostot {0} kansiostasi.",
|
||||
"LabelRemoveMetadataFileHelp": "Poista kaikki metadata.json- ja metadata.abs-tiedostot {0} kansioistasi.",
|
||||
"LabelRowsPerPage": "Rivejä sivulla",
|
||||
"LabelSearchTerm": "Hakusana",
|
||||
"LabelSearchTitle": "Etsi otsikko",
|
||||
@@ -552,6 +559,8 @@
|
||||
"LabelSettingsBookshelfViewHelp": "Skeuomorfinen muotoilu puisilla hyllyillä",
|
||||
"LabelSettingsChromecastSupport": "Chromecast-tuki",
|
||||
"LabelSettingsDateFormat": "Päivämäärän muoto",
|
||||
"LabelSettingsEnableWatcher": "Skannaa kirjastot automaattisesti muutoksien varalta",
|
||||
"LabelSettingsEnableWatcherForLibrary": "Skannaa kirjastot automaattisesti muutoksien varalta",
|
||||
"LabelSettingsEnableWatcherHelp": "Ottaa käyttöön kohteiden automaattisen lisäämisen ja päivityksen kun tiedostomuutoksia havaitaan. *Tarvitsee palvelimen uudelleenkäynnistyksen",
|
||||
"LabelSettingsEpubsAllowScriptedContent": "Salli komentosarjamuotoinen sisältö epubissa",
|
||||
"LabelSettingsEpubsAllowScriptedContentHelp": "Salli epub-tiedostojen suorittaa komentosarjoja. On suositeltavaa pitää tämä asetus pois käytöstä, ellet luota epub-tiedostojen lähteeseen.",
|
||||
@@ -777,70 +786,308 @@
|
||||
"MessageLogsDescription": "Lokitiedot tallennetaan kansioon <code>/metadata/logs</code> JSON-tiedostoina. Kaatumislokit tallennetaan kansioon <code>/metadata/logs/crash_logs.txt</code>.",
|
||||
"MessageM4BFailed": "M4B epäonnistui!",
|
||||
"MessageM4BFinished": "M4B valmis!",
|
||||
"MessageMapChapterTitles": "Kartoita lukujen otsikot olemassa oleviin äänikirjan lukuihin muuttamatta aikaleimoja",
|
||||
"MessageMarkAllEpisodesFinished": "Merkitse kaikki jaksot päättyneiksi",
|
||||
"MessageMarkAllEpisodesNotFinished": "Merkitse kaikki jaksot ei-päättyneiksi",
|
||||
"MessageMarkAsFinished": "Merkitse valmiiksi",
|
||||
"MessageMarkAsNotFinished": "Merkitse Ei-päättyneiksi",
|
||||
"MessageMatchBooksDescription": "yrittää yhdistää kirjaston kirjoja valitun hakupalvelun kirjaan ja täyttää tyhjät tiedot ja kansikuvan. Ei korvaa yksityiskohtia.",
|
||||
"MessageNoAudioTracks": "Ei ääniraitoja",
|
||||
"MessageNoAuthors": "Ei tekijöitä",
|
||||
"MessageNoBackups": "Ei varmuuskopioita",
|
||||
"MessageNoBookmarks": "Ei kirjanmerkkejä",
|
||||
"MessageNoChapters": "Ei kappaleita",
|
||||
"MessageNoCollections": "Ei kokoelmia",
|
||||
"MessageNoCoversFound": "Kansikuvia ei löydetty",
|
||||
"MessageNoDescription": "Ei kuvausta",
|
||||
"MessageNoDevices": "Ei laitteita",
|
||||
"MessageNoDownloadsInProgress": "Ei latauksia tällä hetkellä meneillään",
|
||||
"MessageNoDownloadsQueued": "Ei latauksia jonossa",
|
||||
"MessageNoEpisodeMatchesFound": "Jaksoosumia ei löytynyt",
|
||||
"MessageNoEpisodes": "Ei jaksoja",
|
||||
"MessageNoFoldersAvailable": "Ei kansioita saatavilla",
|
||||
"MessageNoGenres": "Ei lajityyppejä",
|
||||
"MessageNoIssues": "Ei vikoja",
|
||||
"MessageNoItems": "Ei kohteita",
|
||||
"MessageNoItemsFound": "Kohteita ei löytynyt",
|
||||
"MessageNoListeningSessions": "Ei kuunteluistuntoja",
|
||||
"MessageNoLogs": "Ei lokeja",
|
||||
"MessageNoMediaProgress": "Ei median edistymistä",
|
||||
"MessageNoNotifications": "Ei ilmoituksia",
|
||||
"MessageNoPodcastFeed": "Epäkelvollinen podcast: Ei syötettä",
|
||||
"MessageNoPodcastsFound": "Podcasteja ei löytynyt",
|
||||
"MessageNoResults": "Ei tuloksia",
|
||||
"MessageNoSearchResultsFor": "Ei hakutuloksia \"{0}\":lle",
|
||||
"MessageNoSeries": "Ei sarjaa",
|
||||
"MessageNoTags": "Ei tunnisteita",
|
||||
"MessageNoTasksRunning": "Ei käynnissä olevia tehtäviä",
|
||||
"MessageNoUpdatesWereNecessary": "Päivityksiä ei tarvittu",
|
||||
"MessageNoUserPlaylists": "Sinulla ei ole soittolistoja",
|
||||
"MessageNoUserPlaylistsHelp": "Soittolistat ovat yksityisiä. Vain ne luonut käyttäjä näkee ne.",
|
||||
"MessageNotYetImplemented": "Ei vielä toteutettu",
|
||||
"MessageOpmlPreviewNote": "Huomautus: Tämä on esikatselu jäsennetystä OPML-tiedostosta. Varsinainen podcastin nimi tullaan ottamaan RSS-syötteestä.",
|
||||
"MessageOr": "tai",
|
||||
"MessagePauseChapter": "Keskeytä luvun toisto",
|
||||
"MessagePlayChapter": "Kuuntele luvun alku",
|
||||
"MessagePlaylistCreateFromCollection": "Luo soittolista kokoelmasta",
|
||||
"MessagePleaseWait": "Ole hyvä ja odota...",
|
||||
"MessagePodcastHasNoRSSFeedForMatching": "Podcastilla ei ole RSS-syötteen URL-osoitetta, jota voitaisiin käyttää täsmäämiseen",
|
||||
"MessagePodcastSearchField": "Syötä hakutermi tai RSS-syötteen URL-osoite",
|
||||
"MessageQuickEmbedInProgress": "Pikaupottaminen meneillään",
|
||||
"MessageQuickEmbedQueue": "Jonotettu pikaupotusta varten ({0} jonossa)",
|
||||
"MessageQuickMatchAllEpisodes": "Pikatäsmää kaikki jaksot",
|
||||
"MessageQuickMatchDescription": "Täytä tyhjän tuotteen tiedot ja kansi ensimmäisellä täsmäävällä tuloksella {0}:sta. Ei korvaa tietoja, ellei 'Pidä mieluummin täsmäävät metatiedot'-palvelinasetus on otettu käyttöön.",
|
||||
"MessageRemoveChapter": "Poista luku",
|
||||
"MessageRemoveEpisodes": "Poista {0} jakso(a)",
|
||||
"MessageRemoveFromPlayerQueue": "Poista soittimen jonosta",
|
||||
"MessageRemoveUserWarning": "Oletko varma, että haluat poistaa käyttäjän \"{0}\" pysyvästi?",
|
||||
"MessageReportBugsAndContribute": "Ilmoita virheistä, toivo ominaisuuksia ja osallistu",
|
||||
"MessageResetChaptersConfirm": "Oletko varma, että haluat nollata luvut ja kumota tekemäsi muutokset?",
|
||||
"MessageRestoreBackupConfirm": "Oletko varma, että haluat palauttaa varmuuskopion, joka on luotu",
|
||||
"MessageRestoreBackupWarning": "Varmuuskopion palauttaminen korvaa koko /config:ssa sijaitsevan tietokannan, ja kansikuvat /metadata/items & /metadata/authors:ssa.<br /><br />Varmuuskopiot eivät muuta kirjastokansioissasi olevia tiedostoja. Jos olet ottanut käyttöön palvelinasetuksissa kansikuvien ja metatietojen tallentamisen kirjaston kansioihin, niitä ei varmuuskopioida tai korvata.<br /><br />Kaikki palvelintasi käyttävät asiakkaat virkistetään automaattisesti.",
|
||||
"MessageScheduleLibraryScanNote": "Suurimmalle osaa käyttäjistä on suositeltavaa jättää tämä ominaisuus pois päältä ja säilyttää kansiotarkkailu päällä. Kansiotarkkailu havaitsee automaattisesti tiedostomuutokset kirjaston kansioissa. Kansiotarkkailu ei toimi kaikille tiedostojärjestelmille (kuten NFS), jolloin voidaan käyttää ajastettuja kirjastoskannauksia.",
|
||||
"MessageScheduleRunEveryWeekdayAtTime": "Suorita joka {0} klo {1}",
|
||||
"MessageSearchResultsFor": "Hakutulokset haulle",
|
||||
"MessageSelected": "{0} valittuna",
|
||||
"MessageServerCouldNotBeReached": "Palvelimelle ei saatu yhteyttä",
|
||||
"MessageSetChaptersFromTracksDescription": "Aseta luvut käyttämällä kutakin äänitiedostoa lukuna ja luvun otsikkoa äänitiedoston nimenä",
|
||||
"MessageShareExpirationWillBe": "Umpeutuminen on <strong>{0}</strong>",
|
||||
"MessageShareExpiresIn": "Umpeutuu {0}:n kuluttua",
|
||||
"MessageShareURLWillBe": "Jaa URL-osoite on <strong>{0}</strong>",
|
||||
"MessageStartPlaybackAtTime": "Aloitetaanko \"{0}\":n toisto klo {1}?",
|
||||
"MessageTaskAudioFileNotWritable": "Äänitiedosto \"{0}\" ei ole kirjoitettava",
|
||||
"MessageTaskCanceledByUser": "Tehtävä peruttu käyttäjän toimesta",
|
||||
"MessageTaskDownloadingEpisodeDescription": "Ladataan jaksoa \"{0}\"",
|
||||
"MessageTaskEmbeddingMetadata": "Upotetaan metatiedot",
|
||||
"MessageTaskEmbeddingMetadataDescription": "Upotetaan metatiedot äänikirjaan \"{0}\"",
|
||||
"MessageTaskEncodingM4b": "Koodaus M4B",
|
||||
"MessageTaskEncodingM4bDescription": "Koodataan äänikirjaa \"{0}\" yhdeksi m4b-tiedostoksi",
|
||||
"MessageTaskFailed": "Epäonnistunut",
|
||||
"MessageTaskFailedToBackupAudioFile": "Äänitiedoston \"{0}\" varmuuskopiointi epäonnistui",
|
||||
"MessageTaskFailedToCreateCacheDirectory": "Välimuistihakemiston luominen epäonnistui",
|
||||
"MessageTaskFailedToEmbedMetadataInFile": "Metatietojen upottaminen tiedostoon \"{0}\" epäonnistui",
|
||||
"MessageTaskFailedToMergeAudioFiles": "Äänitiedostojen yhdistäminen epäonnistui",
|
||||
"MessageTaskFailedToMoveM4bFile": "m4b-tiedoston siirtäminen epäonnistui",
|
||||
"MessageTaskFailedToWriteMetadataFile": "Metatietotiedoston kirjoittaminen epäonnistui",
|
||||
"MessageTaskMatchingBooksInLibrary": "Vastaavat kirjat kirjastossa \"{0}\"",
|
||||
"MessageTaskNoFilesToScan": "Ei skannattavia tiedostoja",
|
||||
"MessageTaskOpmlImport": "OPML-tuonti",
|
||||
"MessageTaskOpmlImportDescription": "Luodaan podcasteja {0} RSS-syötteistä",
|
||||
"MessageTaskOpmlImportFeed": "OPML-tuontisyöte",
|
||||
"MessageTaskOpmlImportFeedDescription": "Tuodaan RSS-syötettä \"{0}\"",
|
||||
"MessageTaskOpmlImportFeedFailed": "Podcast-syötteen saaminen epäonnistui",
|
||||
"MessageTaskOpmlImportFeedPodcastDescription": "Luodaan podcastia \"{0}\"",
|
||||
"MessageTaskOpmlImportFeedPodcastExists": "Podcast on jo olemassa polulla",
|
||||
"MessageTaskOpmlImportFeedPodcastFailed": "Podcastin luominen epäonnistui",
|
||||
"MessageTaskOpmlImportFinished": "Lisätty {0} podcastia",
|
||||
"MessageTaskOpmlParseFailed": "OPML-tiedoston jäsentäminen epäonnistui",
|
||||
"MessageTaskOpmlParseFastFail": "Epäkelvollinen OPML-tiedoston <opml>-tunnistetta ei löytynyt tai <outline>-tunnistetta ei löytynyt",
|
||||
"MessageTaskOpmlParseNoneFound": "Syötteitä ei löytynyt OPML-tiedostosta",
|
||||
"MessageTaskScanItemsAdded": "{0} lisätty",
|
||||
"MessageTaskScanItemsMissing": "{0} puuttuu",
|
||||
"MessageTaskScanItemsUpdated": "{0} päivitetty",
|
||||
"MessageTaskScanNoChangesNeeded": "Muutoksia ei tarvita",
|
||||
"MessageTaskScanningFileChanges": "Tarkastetaan tiedoston muutoksia \"{0}\":sta",
|
||||
"MessageTaskScanningLibrary": "Tarkastetaan kirjastoa \"{0}\"",
|
||||
"MessageTaskTargetDirectoryNotWritable": "Kohdehakemisto ei ole kirjoitettava",
|
||||
"MessageThinking": "Ajattellaan...",
|
||||
"MessageUploaderItemFailed": "Lataaminen ulospäin epäonnistui",
|
||||
"MessageUploaderItemSuccess": "Onnistuneesti ladattu! ulospäin!",
|
||||
"MessageUploading": "Ladataan! ulospäin...",
|
||||
"MessageValidCronExpression": "Kelvollinen cron-lauseke",
|
||||
"MessageWatcherIsDisabledGlobally": "Kansiotarkkailu on poistettu käytöstä kaikkialla palvelimen asetuksissa",
|
||||
"MessageXLibraryIsEmpty": "{0} Kirjasto on tyhjä!",
|
||||
"MessageYourAudiobookDurationIsLonger": "Äänikirjasi kesto on pidempi kuin löydetty kesto",
|
||||
"MessageYourAudiobookDurationIsShorter": "Äänikirjasi kesto on lyhyempi kuin löydetty kesto",
|
||||
"NoteChangeRootPassword": "Käyttäjä root on ainoa käyttäjä, jolla voi olla tyhjä salasana",
|
||||
"NoteChapterEditorTimes": "Huomautus: Ensimmäisen luvun aloitusajan on oltava 0:00 ja viimeisen luvun aloitusaika ei saa ylittää tätä äänikirjan kestoa.",
|
||||
"NoteFolderPicker": "Huomautus: jo kartoitettuja kansioita ei näytetä",
|
||||
"NoteRSSFeedPodcastAppsHttps": "Varoitus: Useimmat podcast-sovellukset edellyttävät, että RSS-syötteen URL-osoite käyttää HTTPS:a",
|
||||
"NoteRSSFeedPodcastAppsPubDate": "Varoitus: yhdellä tai useammalla jaksollasi ei ole julkaisupäivämäärää. Jotkut podcast-sovellukset vaativat tämän.",
|
||||
"NoteUploaderFoldersWithMediaFiles": "Mediatiedostoja sisältävät kansiot käsitellään erillisinä kirjastokohteina.",
|
||||
"NoteUploaderOnlyAudioFiles": "Jos ladataan luospäin vain äänitiedostoja, silloin jokainen äänitiedosto käsitellään erillisenä äänikirjana.",
|
||||
"NoteUploaderUnsupportedFiles": "Ei-tuetut tiedostot ohitetaan. Kansiota valittaessa tai pudottaessa, muut tiedostot, jotka eivät ole kohdekansiossa, ohitetaan.",
|
||||
"NotificationOnBackupCompletedDescription": "Laukaistu, kun varmuuskopiointi on valmis",
|
||||
"NotificationOnBackupFailedDescription": "Laukaistu, kun varmuuskopiointi epäonnistuu",
|
||||
"NotificationOnEpisodeDownloadedDescription": "Laukaistu, kun podcast-jakso ladataan automaattisesti",
|
||||
"NotificationOnTestDescription": "Tapahtuma ilmoitusjärjestelmän testaamista varten",
|
||||
"PlaceholderNewCollection": "Uusi kokoelman nimi",
|
||||
"PlaceholderNewFolderPath": "Uusi kansion polku",
|
||||
"PlaceholderNewPlaylist": "Uusi soittolistan nimi",
|
||||
"PlaceholderSearch": "Haku...",
|
||||
"PlaceholderSearchEpisode": "Haku jaksosta..",
|
||||
"StatsAuthorsAdded": "tekijät lisätty",
|
||||
"StatsBooksAdded": "kirjat lisätty",
|
||||
"StatsBooksAdditional": "Jotkut lisäykset sisältävät…",
|
||||
"StatsBooksFinished": "kirjat päättyneet",
|
||||
"StatsBooksFinishedThisYear": "Jotkut kirjat päättyneet tänä vuonna…",
|
||||
"StatsBooksListenedTo": "kuunnellut kirjat",
|
||||
"StatsCollectionGrewTo": "Kirjakokoelmasi kasvoi asti…",
|
||||
"StatsSessions": "istunnot",
|
||||
"StatsSpentListening": "kuunteluun käytetty",
|
||||
"StatsTopAuthor": "HUIPPUTEKIJÄ",
|
||||
"StatsTopAuthors": "HUIPPUTEKIJÄT",
|
||||
"StatsTopGenre": "HUIPPUTYYLILAJI",
|
||||
"StatsTopGenres": "HUIPPUTYYLILAJIT",
|
||||
"StatsTopMonth": "HUIPPUKUUKAUSI",
|
||||
"StatsTopNarrator": "HUIPPUKERTOJA",
|
||||
"StatsTopNarrators": "HUIPPUKERTOJAT",
|
||||
"StatsTotalDuration": "Kokonaiskestolla…",
|
||||
"StatsYearInReview": "VUOSI KATSAUKSESSA",
|
||||
"ToastAccountUpdateSuccess": "Tili päivitetty",
|
||||
"ToastAppriseUrlRequired": "Arvon tulee olla Apprise URL",
|
||||
"ToastAsinRequired": "ASIN vaaditaan",
|
||||
"ToastAuthorImageRemoveSuccess": "Tekijän kuva poistettu",
|
||||
"ToastAuthorNotFound": "Tekijää \"{0}\" ei löydy",
|
||||
"ToastAuthorRemoveSuccess": "Tekijä poistettu",
|
||||
"ToastAuthorSearchNotFound": "Tekijää ei löydy",
|
||||
"ToastAuthorUpdateMerged": "Tekijä yhdistetty",
|
||||
"ToastAuthorUpdateSuccess": "Tekijä päivitetty",
|
||||
"ToastAuthorUpdateSuccessNoImageFound": "Tekijä päivitetty (kuvaa ei löytynyt)",
|
||||
"ToastBackupAppliedSuccess": "Varmuuskopiointi sovellettu",
|
||||
"ToastBackupCreateFailed": "Varmuuskopion luominen epäonnistui",
|
||||
"ToastBackupCreateSuccess": "Varmuuskopio luotu",
|
||||
"ToastBackupDeleteFailed": "Varmuuskopion poistaminen epäonnistui",
|
||||
"ToastBackupDeleteSuccess": "Varmuuskopio poistettu",
|
||||
"ToastBackupInvalidMaxKeep": "Epäkelvollinen määrä säilytettäviä varmuuskopioita",
|
||||
"ToastBackupInvalidMaxSize": "Epäkelvollinen varmuuskopion enimmäiskoko",
|
||||
"ToastBackupRestoreFailed": "Varmuuskopion palauttaminen epäonnistui",
|
||||
"ToastBackupUploadFailed": "Varmuuskopion lataaminen ulospäin epäonnistui",
|
||||
"ToastBackupUploadSuccess": "Varmuuskopio ladattu ulospäin",
|
||||
"ToastBatchApplyDetailsToItemsSuccess": "Kohteisiin sovelletut yksityiskohdat",
|
||||
"ToastBatchDeleteFailed": "Eräpoisto epäonnistui",
|
||||
"ToastBatchDeleteSuccess": "Eräpoisto onnistui",
|
||||
"ToastBatchQuickMatchFailed": "Erän pikatäsmäys epäonnistui!",
|
||||
"ToastBatchQuickMatchStarted": "{0} kirjan erän pikatäsmäys aloitettu!",
|
||||
"ToastBatchUpdateFailed": "Eräpäivitys epäonnistui",
|
||||
"ToastBatchUpdateSuccess": "Eräpäivitys onnistui",
|
||||
"ToastBookmarkCreateFailed": "Kirjanmerkin luominen epäonnistui",
|
||||
"ToastBookmarkCreateSuccess": "Kirjanmerkki lisätty",
|
||||
"ToastBookmarkRemoveSuccess": "Kirjanmerkki poistettu",
|
||||
"ToastCachePurgeFailed": "Välimuistin tyhjentäminen epäonnistui",
|
||||
"ToastCachePurgeSuccess": "Välimuisti tyhjennetty onnistuneesti",
|
||||
"ToastChaptersHaveErrors": "Luvuissa on virheitä",
|
||||
"ToastChaptersMustHaveTitles": "Lukuilla on oltava otsikot",
|
||||
"ToastChaptersRemoved": "Luvut poistettu",
|
||||
"ToastChaptersUpdated": "Luvut päivitetty",
|
||||
"ToastCollectionItemsAddFailed": "Kohteen/kohteiden lisääminen kokoelmaan epäonnistui",
|
||||
"ToastCollectionRemoveSuccess": "Kokoelma poistettu",
|
||||
"ToastCollectionUpdateSuccess": "Kokoelma päivitetty",
|
||||
"ToastCoverUpdateFailed": "Kansikuvan päivitys epäonnistui",
|
||||
"ToastDateTimeInvalidOrIncomplete": "Päivämäärä ja aika ovat epäkelvolliset tai puutteelliset",
|
||||
"ToastDeleteFileFailed": "Tiedoston poistaminen epäonnistui",
|
||||
"ToastDeleteFileSuccess": "Tiedosto poistettu",
|
||||
"ToastDeviceAddFailed": "Laitteen lisääminen epäonnistui",
|
||||
"ToastDeviceNameAlreadyExists": "Tämän niminen sähköinen lukulaite on jo olemassa",
|
||||
"ToastDeviceTestEmailFailed": "Testisähköpostin lähettäminen epäonnistui",
|
||||
"ToastDeviceTestEmailSuccess": "Testisähköposti lähetetty",
|
||||
"ToastEmailSettingsUpdateSuccess": "Sähköpostiasetukset päivitetty",
|
||||
"ToastEncodeCancelFailed": "Koodauksen peruuttaminen epäonnistui",
|
||||
"ToastEncodeCancelSucces": "Koodaaminen peruutettu",
|
||||
"ToastEpisodeDownloadQueueClearFailed": "Jonon tyhjentäminen epäonnistui",
|
||||
"ToastEpisodeDownloadQueueClearSuccess": "Jakson latausjono tyhjennetty",
|
||||
"ToastEpisodeUpdateSuccess": "{0} jaksoa päivitetty",
|
||||
"ToastErrorCannotShare": "Ei voi jakaa alkuperäisesti tällä laitteella",
|
||||
"ToastFailedToLoadData": "Tietojen lataaminen epäonnistui",
|
||||
"ToastFailedToMatch": "Vastaaminen epäonnistui",
|
||||
"ToastFailedToShare": "Jakaminen epäonnistui",
|
||||
"ToastFailedToUpdate": "Päivittäminen epäonnistui",
|
||||
"ToastInvalidImageUrl": "Epäkelvollinen kuvan URL-osoite",
|
||||
"ToastInvalidMaxEpisodesToDownload": "Ladattavien jaksojen enimmäismäärä on epäkelvollinen",
|
||||
"ToastInvalidUrl": "Epäkelvollinen URL-osoite",
|
||||
"ToastItemCoverUpdateSuccess": "Kohteen kansikuva päivitetty",
|
||||
"ToastItemDeletedFailed": "Kohteen poistaminen epäonnistui",
|
||||
"ToastItemDeletedSuccess": "Poistettu kohde",
|
||||
"ToastItemDetailsUpdateSuccess": "Tuotteen yksityiskohdat päivitetty",
|
||||
"ToastItemMarkedAsFinishedFailed": "Valmiiksi merkitseminen epäonnistui",
|
||||
"ToastItemMarkedAsFinishedSuccess": "Kohde merkitty Päättyneeksi",
|
||||
"ToastItemMarkedAsNotFinishedFailed": "Valmiiksi merkitsemisen poisto epäonnistui",
|
||||
"ToastItemMarkedAsNotFinishedSuccess": "Kohde merkitty Ei-päättyneeksi",
|
||||
"ToastItemUpdateSuccess": "Kohde päivitetty",
|
||||
"ToastLibraryCreateFailed": "Kirjaston luominen epäonnistui",
|
||||
"ToastLibraryCreateSuccess": "Kirjasto \"{0}\" luotu",
|
||||
"ToastLibraryDeleteFailed": "Kirjaston poistaminen epäonnistui",
|
||||
"ToastLibraryDeleteSuccess": "Kirjasto poistettu",
|
||||
"ToastLibraryScanFailedToStart": "Skannauksen käynnistäminen epäonnistui",
|
||||
"ToastLibraryScanStarted": "Kirjaston skannaus käynnistetty",
|
||||
"ToastLibraryUpdateSuccess": "Kirjasto \"{0}\" päivitetty",
|
||||
"ToastMatchAllAuthorsFailed": "Kaikkia tekijöitä ei voitu vaastattaa",
|
||||
"ToastMetadataFilesRemovedError": "Virhe poistettaessa metadata.{0}-tiedostot",
|
||||
"ToastMetadataFilesRemovedNoneFound": "metadata.{0}-tiedostoja ei löytynyt kirjastosta",
|
||||
"ToastMetadataFilesRemovedNoneRemoved": "Ei metadata.{0}-tiedostoja poistettu",
|
||||
"ToastMetadataFilesRemovedSuccess": "{0} metadata.{1}-tiedostoa poistettu",
|
||||
"ToastMustHaveAtLeastOnePath": "On oltava vähintään yksi polku",
|
||||
"ToastNameEmailRequired": "Nimi ja sähköpostiosoite vaaditaan",
|
||||
"ToastNameRequired": "Nimi vaaditaan",
|
||||
"ToastNewEpisodesFound": "{0} uutta jaksoa löydetty",
|
||||
"ToastNewUserCreatedFailed": "Tilin \"{0}\" luominen epäonnistui",
|
||||
"ToastNewUserCreatedSuccess": "Uusi tili luotu",
|
||||
"ToastNewUserLibraryError": "On valittava vähintään yksi kirjasto",
|
||||
"ToastNewUserPasswordError": "On oltava salasana; vain käyttäjällä root voi olla tyhjä salasana",
|
||||
"ToastNewUserTagError": "On valittava vähintään yksi tunniste",
|
||||
"ToastNewUserUsernameError": "Syötä käyttäjänimi",
|
||||
"ToastNoNewEpisodesFound": "Uusia jaksoja ei löytynyt",
|
||||
"ToastNoRSSFeed": "Podcastilla ei ole RSS-syötettä",
|
||||
"ToastNoUpdatesNecessary": "Päivityksiä ei tarvita",
|
||||
"ToastNotificationCreateFailed": "Ilmoituksen luominen epäonnistui",
|
||||
"ToastNotificationDeleteFailed": "Ilmoituksen poistaminen epäonnistui",
|
||||
"ToastNotificationFailedMaximum": "Epäonnistuneiden yritysten enimmäismäärän on oltava >= 0",
|
||||
"ToastNotificationQueueMaximum": "Ilmoitusjonon enimmäismäärä on oltava >= 0",
|
||||
"ToastNotificationSettingsUpdateSuccess": "Ilmoitusasetukset päivitetty",
|
||||
"ToastNotificationTestTriggerFailed": "Testiilmoituksen laukaiseminen epäonnistui",
|
||||
"ToastNotificationTestTriggerSuccess": "Laukaistiin testiilmoitus",
|
||||
"ToastNotificationUpdateSuccess": "Ilmoitus päivitetty",
|
||||
"ToastPlaylistCreateFailed": "Soittolistan luominen epäonnistui",
|
||||
"ToastPlaylistCreateSuccess": "Soittolista luotu",
|
||||
"ToastPlaylistRemoveSuccess": "Soittolista poistettu",
|
||||
"ToastPlaylistUpdateSuccess": "Soittolista päivitetty",
|
||||
"ToastPodcastCreateFailed": "Podcastin luominen epäonnistui",
|
||||
"ToastPodcastCreateSuccess": "Podcastin luominen onnistui",
|
||||
"ToastPodcastGetFeedFailed": "Podcast-syötteen saaminen epäonnistui",
|
||||
"ToastPodcastNoEpisodesInFeed": "RSS-syötteestä ei löytynyt jaksoja",
|
||||
"ToastPodcastNoRssFeed": "Podcastilla ei ole RSS-syötettä",
|
||||
"ToastProgressIsNotBeingSynced": "Edistystä ei synkronoida, aloita toisto uudelleen",
|
||||
"ToastProviderCreatedFailed": "Palveluntarjoajan lisääminen epäonnistui",
|
||||
"ToastProviderCreatedSuccess": "Uusi palveluntarjoaja lisätty",
|
||||
"ToastProviderNameAndUrlRequired": "Nimi ja URL-osoite vaaditaan",
|
||||
"ToastProviderRemoveSuccess": "Palveluntarjoaja poistettu",
|
||||
"ToastRSSFeedCloseFailed": "RSS syötteen sulkeminen epäonnistui",
|
||||
"ToastRSSFeedCloseSuccess": "RSS syöte suljettu",
|
||||
"ToastRemoveFailed": "Poistaminen epäonnistui",
|
||||
"ToastRemoveItemFromCollectionFailed": "Kohteen poistaminen kokoelmasta epäonnistui",
|
||||
"ToastRemoveItemFromCollectionSuccess": "Kohde poistettu kokoelmasta",
|
||||
"ToastRemoveItemsWithIssuesFailed": "Vikoja sisältävien kirjastokohteiden poistaminen epäonnistui",
|
||||
"ToastRemoveItemsWithIssuesSuccess": "Vikoja sisältäviä kirjastokohteita poistettu",
|
||||
"ToastRenameFailed": "Uudelleennimeäminen epäonnistui",
|
||||
"ToastRescanFailed": "Uudelleenskannaus {0}n kohdalla epäonnistui",
|
||||
"ToastRescanRemoved": "Uudelleenskannauksen täydellinen kohde poistettiin",
|
||||
"ToastRescanUpToDate": "Uudelleenskannauksen täydellinen kohde oli ajan tasalla",
|
||||
"ToastRescanUpdated": "Uudelleenskannauksen täydellinen kohde päivitettiin",
|
||||
"ToastScanFailed": "Kirjastokohteen skannaaminen epäonnistui",
|
||||
"ToastSelectAtLeastOneUser": "Valitse ainakin yksi käyttäjä",
|
||||
"ToastSendEbookToDeviceFailed": "S-kirjan lähettäminen laitteeseen epäonnistui",
|
||||
"ToastSendEbookToDeviceSuccess": "S-kirja lähetetty laitteeseen \"{0}\"",
|
||||
"ToastSeriesSubmitFailedSameName": "Ei voi lisätä kahta samannimistä sarjaa",
|
||||
"ToastSeriesUpdateFailed": "Sarjan päivittäminen epäonnistui",
|
||||
"ToastSeriesUpdateSuccess": "Sarjan päivittäminen onnistui",
|
||||
"ToastServerSettingsUpdateSuccess": "Palvelimen asetukset päivitetty",
|
||||
"ToastSessionCloseFailed": "Istunnon sulkeminen epäonnistui",
|
||||
"ToastSessionDeleteFailed": "Istunnon poistaminen epäonnistui",
|
||||
"ToastSessionDeleteSuccess": "Istunto poistettu",
|
||||
"ToastSleepTimerDone": "Uniajastin tehty... zZzzZz",
|
||||
"ToastSlugMustChange": "Slug sisältää epäkelvollisia merkkejä",
|
||||
"ToastSlugRequired": "Slug vaaditaan",
|
||||
"ToastSocketConnected": "Yhteys saatu",
|
||||
"ToastSocketDisconnected": "Yhteys katkaistu",
|
||||
"ToastSocketFailedToConnect": "Yhteyden muodostus epäonnistui",
|
||||
"ToastSortingPrefixesEmptyError": "On oltava vähintään yksi lajitteluetuliite",
|
||||
"ToastSortingPrefixesUpdateSuccess": "Lajitteluetuliitteet päivitetty ({0} kohdetta)",
|
||||
"ToastTitleRequired": "Otsikko on pakollinen",
|
||||
"ToastUnknownError": "Tuntematon virhe",
|
||||
"ToastUnlinkOpenIdFailed": "Käyttäjän linkityksen poistaminen OpenID:sta epäonnistui",
|
||||
"ToastUnlinkOpenIdSuccess": "Käyttäjän linkitys poistettu OpenID:sta",
|
||||
"ToastUploaderFilepathExistsError": "Tiedostopolku \"{0}\" on jo olemassa palvelimella",
|
||||
"ToastUploaderItemExistsInSubdirectoryError": "Kohde {0} käyttää ulospäinlatauspolun alihakemistoa.",
|
||||
"ToastUserDeleteFailed": "Käyttäjän poisto epäonnistui",
|
||||
"ToastUserDeleteSuccess": "Käyttäjä poistettu",
|
||||
"ToastUserPasswordChangeSuccess": "Salasana vaihdettu onnistuneesti",
|
||||
|
||||
@@ -229,6 +229,7 @@
|
||||
"LabelAddedDate": "Ajouté le {0}",
|
||||
"LabelAdminUsersOnly": "Administrateurs uniquement",
|
||||
"LabelAll": "Tout",
|
||||
"LabelAllEpisodesDownloaded": "Tous les épisodes ont été téléchargés",
|
||||
"LabelAllUsers": "Tous les utilisateurs",
|
||||
"LabelAllUsersExcludingGuests": "Tous les utilisateurs à l’exception des invités",
|
||||
"LabelAllUsersIncludingGuests": "Tous les utilisateurs, y compris les invités",
|
||||
@@ -252,7 +253,7 @@
|
||||
"LabelBackToUser": "Retour à l’utilisateur",
|
||||
"LabelBackupAudioFiles": "Sauvegarder les fichiers audio",
|
||||
"LabelBackupLocation": "Emplacement de la sauvegarde",
|
||||
"LabelBackupsEnableAutomaticBackups": "Activer les sauvegardes automatiques",
|
||||
"LabelBackupsEnableAutomaticBackups": "Sauvegardes automatiques",
|
||||
"LabelBackupsEnableAutomaticBackupsHelp": "Sauvegardes enregistrées dans /metadata/backups",
|
||||
"LabelBackupsMaxBackupSize": "Taille maximale de la sauvegarde (en Go) (0 pour illimité)",
|
||||
"LabelBackupsMaxBackupSizeHelp": "Afin de prévenir les mauvaises configuration, la sauvegarde échouera si elle excède la taille limite.",
|
||||
@@ -558,6 +559,8 @@
|
||||
"LabelSettingsBookshelfViewHelp": "Interface skeumorphique avec étagères en bois",
|
||||
"LabelSettingsChromecastSupport": "Support du Chromecast",
|
||||
"LabelSettingsDateFormat": "Format de date",
|
||||
"LabelSettingsEnableWatcher": "Analyser automatiquement les bibliothèques pour détecter les modifications",
|
||||
"LabelSettingsEnableWatcherForLibrary": "Analyser automatiquement la bibliothèque pour détecter les modifications",
|
||||
"LabelSettingsEnableWatcherHelp": "Active la mise à jour automatique d'éléments lorsque des modifications de fichiers sont détectées. * Nécessite le redémarrage du serveur",
|
||||
"LabelSettingsEpubsAllowScriptedContent": "Autoriser le contenu scénarisé pour les fichiers EPUB",
|
||||
"LabelSettingsEpubsAllowScriptedContentHelp": "Autoriser les fichiers EPUB à exécuter des scripts. Il est recommandé de laisser ce paramètre désactivé, sauf si vous faites confiance à la source des fichiers EPUB.",
|
||||
@@ -575,7 +578,7 @@
|
||||
"LabelSettingsOnlyShowLaterBooksInContinueSeries": "Sauter les livres précédents dans « Continuer la série »",
|
||||
"LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp": "L’étagère de la page d’accueil « Continuer la série » affiche le premier livre non commencé dans les séries dont au moins un livre est terminé et aucun livre n’est en cours. L’activation de ce paramètre permet de poursuivre la série à partir du dernier livre terminé au lieu du premier livre non commencé.",
|
||||
"LabelSettingsParseSubtitles": "Analyser les sous-titres",
|
||||
"LabelSettingsParseSubtitlesHelp": "Extrait les sous-titres depuis le dossier du livre audio.<br>Les sous-titres doivent être séparés par des « - »<br>c’est-à-dire : « Titre du livre - Ceci est un sous-titre » aura le sous-titre « Ceci est un sous-titre »",
|
||||
"LabelSettingsParseSubtitlesHelp": "Extraire les sous-titres des noms de dossiers de livres audio.<br>Les sous-titres doivent être séparés par « - »<br>Par exemple, « Titre du livre - Un sous-titre » a pour sous-titre « Un sous-titre »",
|
||||
"LabelSettingsPreferMatchedMetadata": "Préférer les métadonnées par correspondance",
|
||||
"LabelSettingsPreferMatchedMetadataHelp": "Les métadonnées mises en correspondance remplaceront les détails de l’élément lors de l’utilisation de la correspondance rapide. Par défaut, la correspondance rapide ne remplira que les détails manquants.",
|
||||
"LabelSettingsSkipMatchingBooksWithASIN": "Ignorer la recherche par correspondance pour les livres ayant déjà un ASIN",
|
||||
@@ -601,6 +604,7 @@
|
||||
"LabelSlug": "Identifiant d’URL",
|
||||
"LabelSortAscending": "Croissant",
|
||||
"LabelSortDescending": "Décroissant",
|
||||
"LabelSortPubDate": "Trier par date de publication",
|
||||
"LabelStart": "Démarrer",
|
||||
"LabelStartTime": "Heure de démarrage",
|
||||
"LabelStarted": "Démarré",
|
||||
@@ -706,6 +710,7 @@
|
||||
"MessageBackupsLocationNoEditNote": "Remarque : l’emplacement de sauvegarde est défini via une variable d’environnement et ne peut pas être modifié ici.",
|
||||
"MessageBackupsLocationPathEmpty": "L'emplacement de secours ne peut pas être vide",
|
||||
"MessageBatchEditPopulateMapDetailsAllHelp": "Remplir les champs disponibles avec les données de tous les éléments. Les champs avec des valeurs multiples seront fusionnés.",
|
||||
"MessageBatchEditPopulateMapDetailsItemHelp": "Renseigner les champs de la carte active avec les informations de cet élément",
|
||||
"MessageBatchQuickMatchDescription": "La recherche par correspondance rapide tentera d’ajouter les couvertures et métadonnées manquantes pour les éléments sélectionnés. Activez les options ci-dessous pour permettre la Recherche par correspondance d’écraser les couvertures et/ou métadonnées existantes.",
|
||||
"MessageBookshelfNoCollections": "Vous n’avez pas encore de collections",
|
||||
"MessageBookshelfNoCollectionsHelp": "Les collections sont publiques. Tous les utilisateurs ayant accès à la bibliothèque pourront les voir.",
|
||||
@@ -819,7 +824,7 @@
|
||||
"MessageNoTasksRunning": "Aucune tâche en cours",
|
||||
"MessageNoUpdatesWereNecessary": "Aucune mise à jour n’était nécessaire",
|
||||
"MessageNoUserPlaylists": "Vous n’avez aucune liste de lecture",
|
||||
"MessageNoUserPlaylistsHelp": "Les playlists sont privées. Seul l'utilisateur qui les a créées peut les voir.",
|
||||
"MessageNoUserPlaylistsHelp": "Les playlists sont privées. Seul l’utilisateur qui les crée peut les voir.",
|
||||
"MessageNotYetImplemented": "Non implémenté",
|
||||
"MessageOpmlPreviewNote": "Remarque : Il s’agit d’un aperçu du fichier OPML analysé. Le titre réel du podcast provient du flux RSS.",
|
||||
"MessageOr": "ou",
|
||||
@@ -950,6 +955,7 @@
|
||||
"ToastBackupRestoreFailed": "Échec de la restauration de sauvegarde",
|
||||
"ToastBackupUploadFailed": "Échec du téléversement de sauvegarde",
|
||||
"ToastBackupUploadSuccess": "Sauvegarde téléversée",
|
||||
"ToastBatchApplyDetailsToItemsSuccess": "Détails appliqués aux articles",
|
||||
"ToastBatchDeleteFailed": "Échec de la suppression par lot",
|
||||
"ToastBatchDeleteSuccess": "Suppression par lot réussie",
|
||||
"ToastBatchQuickMatchFailed": "Échec de la correspondance rapide par lot !",
|
||||
@@ -1062,6 +1068,7 @@
|
||||
"ToastSelectAtLeastOneUser": "Sélectionnez au moins un utilisateur",
|
||||
"ToastSendEbookToDeviceFailed": "Échec de l’envoi du livre numérique à l’appareil",
|
||||
"ToastSendEbookToDeviceSuccess": "Livre numérique envoyé à l’appareil : {0}",
|
||||
"ToastSeriesSubmitFailedSameName": "Impossible d’ajouter deux séries ayant le même nom",
|
||||
"ToastSeriesUpdateFailed": "Échec de la mise à jour de la série",
|
||||
"ToastSeriesUpdateSuccess": "Mise à jour de la série réussie",
|
||||
"ToastServerSettingsUpdateSuccess": "Mise à jour des paramètres du serveur",
|
||||
@@ -1080,6 +1087,8 @@
|
||||
"ToastUnknownError": "Erreur inconnue",
|
||||
"ToastUnlinkOpenIdFailed": "Échec de la dissociation de l’utilisateur l’OpenID",
|
||||
"ToastUnlinkOpenIdSuccess": "Utilisateur dissocié de OpenID",
|
||||
"ToastUploaderFilepathExistsError": "Le chemin de fichier « {0} » existe déjà sur le serveur",
|
||||
"ToastUploaderItemExistsInSubdirectoryError": "L’élément « {0} » utilise un sous-répertoire du chemin de téléchargement.",
|
||||
"ToastUserDeleteFailed": "Échec de la suppression de l’utilisateur",
|
||||
"ToastUserDeleteSuccess": "Utilisateur supprimé",
|
||||
"ToastUserPasswordChangeSuccess": "Mot de passe modifié avec succès",
|
||||
|
||||
@@ -604,6 +604,7 @@
|
||||
"LabelSlug": "Slug",
|
||||
"LabelSortAscending": "Uzlazno",
|
||||
"LabelSortDescending": "Silazno",
|
||||
"LabelSortPubDate": "Sortiranje po datumu objave",
|
||||
"LabelStart": "Početak",
|
||||
"LabelStartTime": "Vrijeme početka",
|
||||
"LabelStarted": "Započeto",
|
||||
@@ -704,6 +705,7 @@
|
||||
"LabelYourProgress": "Vaš napredak",
|
||||
"MessageAddToPlayerQueue": "Dodaj u redoslijed izvođenja",
|
||||
"MessageAppriseDescription": "Da biste se koristili ovom značajkom, treba vam instanca <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API-ja</a> ili API koji može rukovati istom vrstom zahtjeva.<br />The Adresa Apprise API-ja treba biti puna URL putanja za slanje obavijesti, npr. ako vam se API instanca poslužuje na adresi <code>http://192.168.1.1:8337</code> trebate upisati <code>http://192.168.1.1:8337/notify</code>.",
|
||||
"MessageAsinCheck": "Upišite ASIN iz odgovarajuće Audibleove regije, ne s Amazonov.",
|
||||
"MessageBackupsDescription": "Sigurnosne kopije sadrže korisnike, korisnikov napredak medija, pojedinosti knjižničke građe, postavke poslužitelja i slike koje se spremaju u <code>/metadata/items</code> & <code>/metadata/authors</code>. Sigurnosne kopije ne sadrže niti jednu datoteku iz mapa knjižnice.",
|
||||
"MessageBackupsLocationEditNote": "Napomena: Uređivanje lokacije za sigurnosne kopije ne premješta ili mijenja postojeće sigurnosne kopije",
|
||||
"MessageBackupsLocationNoEditNote": "Napomena: Lokacija za sigurnosne kopije zadana je kroz varijablu okoline i ovdje se ne može izmijeniti.",
|
||||
@@ -722,6 +724,7 @@
|
||||
"MessageChapterErrorStartGteDuration": "Netočno vrijeme početka, mora biti manje od trajanja zvučne knjige",
|
||||
"MessageChapterErrorStartLtPrev": "Netočno vrijeme početka, mora biti veće ili jednako vremenu početka prethodnog poglavlja",
|
||||
"MessageChapterStartIsAfter": "Početak poglavlja je nakon kraja zvučne knjige",
|
||||
"MessageChaptersNotFound": "Poglavlja nisu pronađena",
|
||||
"MessageCheckingCron": "Provjeravam cron...",
|
||||
"MessageConfirmCloseFeed": "Sigurno želite zatvoriti ovaj izvor?",
|
||||
"MessageConfirmDeleteBackup": "Sigurno želite izbrisati sigurnosnu kopiju za {0}?",
|
||||
@@ -778,6 +781,7 @@
|
||||
"MessageForceReScanDescription": "će ponovno skenirati sve datoteke kao nove datoteke. ID3 tagovi zvučnih datoteka, OPF datoteke i tekstualne datoteke skenirat će se kao da su nove.",
|
||||
"MessageImportantNotice": "Važna obavijest!",
|
||||
"MessageInsertChapterBelow": "Unesi poglavlje ispod",
|
||||
"MessageInvalidAsin": "Nevažeći ASIN",
|
||||
"MessageItemsSelected": "{0} odabranih stavki",
|
||||
"MessageItemsUpdated": "{0} stavki ažurirano",
|
||||
"MessageJoinUsOn": "Pridruži nam se na",
|
||||
@@ -1086,6 +1090,8 @@
|
||||
"ToastUnknownError": "Nepoznata pogreška",
|
||||
"ToastUnlinkOpenIdFailed": "Uklanjanje OpenID veze korisnika nije uspjelo",
|
||||
"ToastUnlinkOpenIdSuccess": "Korisnik odspojen od OpenID-ja",
|
||||
"ToastUploaderFilepathExistsError": "Putanja \"{0}\" već postoji na poslužitelju",
|
||||
"ToastUploaderItemExistsInSubdirectoryError": "Stavka \"{0}\" koristi se podmapom u putanje za učitavanje.",
|
||||
"ToastUserDeleteFailed": "Brisanje korisnika nije uspjelo",
|
||||
"ToastUserDeleteSuccess": "Korisnik izbrisan",
|
||||
"ToastUserPasswordChangeSuccess": "Zaporka je uspješno promijenjena",
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
"ButtonApplyChapters": "Fejezetek alkalmazása",
|
||||
"ButtonAuthors": "Szerzők",
|
||||
"ButtonBack": "Vissza",
|
||||
"ButtonBatchEditPopulateFromExisting": "Létezőből feltöltés",
|
||||
"ButtonBatchEditPopulateMapDetails": "",
|
||||
"ButtonBrowseForFolder": "Mappa keresése",
|
||||
"ButtonCancel": "Mégse",
|
||||
"ButtonCancelEncode": "Kódolás megszakítása",
|
||||
@@ -432,7 +434,7 @@
|
||||
"LabelMetadataProvider": "Metaadat-szolgáltató",
|
||||
"LabelMinute": "Perc",
|
||||
"LabelMinutes": "Perc",
|
||||
"LabelMissing": "Hiányzó",
|
||||
"LabelMissing": "Hiányzik",
|
||||
"LabelMissingEbook": "Nincs e-könyve",
|
||||
"LabelMissingSupplementaryEbook": "Nincs kiegészítő e-könyve",
|
||||
"LabelMobileRedirectURIs": "Engedélyezett mobil átirányítási URI-k",
|
||||
@@ -604,7 +606,7 @@
|
||||
"LabelStatsBestDay": "Legjobb nap",
|
||||
"LabelStatsDailyAverage": "Napi átlag",
|
||||
"LabelStatsDays": "Napok",
|
||||
"LabelStatsDaysListened": "Napon hallgatva",
|
||||
"LabelStatsDaysListened": "Hallgatással töltött napok",
|
||||
"LabelStatsHours": "Órák",
|
||||
"LabelStatsInARow": "egymás után",
|
||||
"LabelStatsItemsFinished": "Befejezett elem",
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"ButtonApplyChapters": "Hoofdstukken toepassen",
|
||||
"ButtonAuthors": "Auteurs",
|
||||
"ButtonBack": "Terug",
|
||||
"ButtonBatchEditPopulateFromExisting": "Vullen vanuit bestaande",
|
||||
"ButtonBatchEditPopulateMapDetails": "Kaartgegevens invullen",
|
||||
"ButtonBrowseForFolder": "Bladeren naar map",
|
||||
"ButtonCancel": "Annuleren",
|
||||
@@ -52,7 +53,7 @@
|
||||
"ButtonNext": "Volgende",
|
||||
"ButtonNextChapter": "Volgend hoofdstuk",
|
||||
"ButtonNextItemInQueue": "Volgend Item in Wachtrij",
|
||||
"ButtonOk": "Ok",
|
||||
"ButtonOk": "Akkoord",
|
||||
"ButtonOpenFeed": "Feed openen",
|
||||
"ButtonOpenManager": "Manager openen",
|
||||
"ButtonPause": "Pauze",
|
||||
@@ -218,6 +219,7 @@
|
||||
"LabelAccountTypeAdmin": "Beheerder",
|
||||
"LabelAccountTypeGuest": "Gast",
|
||||
"LabelAccountTypeUser": "Gebruiker",
|
||||
"LabelActivities": "Activiteiten",
|
||||
"LabelActivity": "Activiteit",
|
||||
"LabelAddToCollection": "Toevoegen aan collectie",
|
||||
"LabelAddToCollectionBatch": "{0} boeken toevoegen aan collectie",
|
||||
@@ -227,6 +229,7 @@
|
||||
"LabelAddedDate": "Toegevoegd {0}",
|
||||
"LabelAdminUsersOnly": "Enkel Admin gebruikers",
|
||||
"LabelAll": "Alle",
|
||||
"LabelAllEpisodesDownloaded": "Alle afleveringen gedownload",
|
||||
"LabelAllUsers": "Alle gebruikers",
|
||||
"LabelAllUsersExcludingGuests": "Alle gebruikers exclusief gasten",
|
||||
"LabelAllUsersIncludingGuests": "Alle gebruikers inclusief gasten",
|
||||
@@ -282,6 +285,7 @@
|
||||
"LabelContinueSeries": "Doorgaan met Serie",
|
||||
"LabelCover": "Omslag",
|
||||
"LabelCoverImageURL": "Coverafbeelding URL",
|
||||
"LabelCoverProvider": "Omslag bron",
|
||||
"LabelCreatedAt": "Gecreëerd op",
|
||||
"LabelCronExpression": "Cron-uitdrukking",
|
||||
"LabelCurrent": "Huidig",
|
||||
@@ -433,7 +437,7 @@
|
||||
"LabelMetadataProvider": "Metadatabron",
|
||||
"LabelMinute": "Minuut",
|
||||
"LabelMinutes": "Minuten",
|
||||
"LabelMissing": "Ontbrekend",
|
||||
"LabelMissing": "Missende",
|
||||
"LabelMissingEbook": "Heeft geen ebook",
|
||||
"LabelMissingSupplementaryEbook": "Heeft geen supplementair ebook",
|
||||
"LabelMobileRedirectURIs": "Toegestane mobiele omleidings-URL's",
|
||||
@@ -464,7 +468,7 @@
|
||||
"LabelNotificationsMaxQueueSize": "Max rijgrootte voor notificatie gebeurtenissen",
|
||||
"LabelNotificationsMaxQueueSizeHelp": "Gebeurtenissen zijn beperkt tot 1 aftrap per seconde. Gebeurtenissen zullen genegeerd worden als de rij aan de maximale grootte zit. Dit voorkomt notificatie-spamming.",
|
||||
"LabelNumberOfBooks": "Aantal Boeken",
|
||||
"LabelNumberOfEpisodes": "# afleveringen",
|
||||
"LabelNumberOfEpisodes": "# Afleveringen",
|
||||
"LabelOpenIDAdvancedPermsClaimDescription": "Naam van de OpenID-claim die geavanceerde machtigingen bevat voor gebruikersacties binnen de applicatie die van toepassing zijn op niet-beheerdersrollen (<b>indien geconfigureerd</b>). Als de claim ontbreekt in het antwoord, wordt toegang tot ABS geweigerd. Als er één optie ontbreekt, wordt deze behandeld als <code>false</code>. Zorg ervoor dat de claim van de identiteitsprovider overeenkomt met de verwachte structuur:",
|
||||
"LabelOpenIDClaims": "Laat de volgende opties leeg om geavanceerde groeps- en machtigingstoewijzing uit te schakelen en de groep 'Gebruiker' automatisch toe te wijzen.",
|
||||
"LabelOpenIDGroupClaimDescription": "Naam van de OpenID-claim die een lijst met de groepen van de gebruiker bevat. Vaak aangeduid als <code>groepen</code>. <b>Indien geconfigureerd</b>, zal de applicatie automatisch rollen toewijzen op basis van de groepslidmaatschappen van de gebruiker, op voorwaarde dat deze groepen hoofdlettergevoelig 'admin', 'gebruiker' of 'gast' worden genoemd in de claim. De claim moet een lijst bevatten en als een gebruiker tot meerdere groepen behoort, zal de applicatie de rol toewijzen die overeenkomt met het hoogste toegangsniveau. Als er geen groep overeenkomt, wordt de toegang geweigerd.",
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"ButtonCheckAndDownloadNewEpisodes": "Sjekk og last ned nye episoder",
|
||||
"ButtonChooseAFolder": "Velg mappe",
|
||||
"ButtonChooseFiles": "Velg filer",
|
||||
"ButtonClearFilter": "Bytt filter",
|
||||
"ButtonClearFilter": "Fjern filter",
|
||||
"ButtonCloseFeed": "Lukk Feed",
|
||||
"ButtonCloseSession": "Lukk åpen økt",
|
||||
"ButtonCollections": "Samlinger",
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
"ButtonApplyChapters": "Aplicar Capítulos",
|
||||
"ButtonAuthors": "Autores",
|
||||
"ButtonBack": "Voltar",
|
||||
"ButtonBatchEditPopulateFromExisting": "Popular de um existente",
|
||||
"ButtonBatchEditPopulateMapDetails": "Popular mapeamento de detalhes",
|
||||
"ButtonBrowseForFolder": "Procurar por Pasta",
|
||||
"ButtonCancel": "Cancelar",
|
||||
"ButtonCancelEncode": "Cancelar Codificação",
|
||||
@@ -19,6 +21,7 @@
|
||||
"ButtonChooseFiles": "Escolha arquivos",
|
||||
"ButtonClearFilter": "Limpar Filtro",
|
||||
"ButtonCloseFeed": "Fechar Feed",
|
||||
"ButtonCloseSession": "Fechar Sessão Aberta",
|
||||
"ButtonCollections": "Coleções",
|
||||
"ButtonConfigureScanner": "Configurar Verificador",
|
||||
"ButtonCreate": "Criar",
|
||||
@@ -28,6 +31,9 @@
|
||||
"ButtonEdit": "Editar",
|
||||
"ButtonEditChapters": "Editar Capítulos",
|
||||
"ButtonEditPodcast": "Editar Podcast",
|
||||
"ButtonEnable": "Ativar",
|
||||
"ButtonFireAndFail": "Disparar e Falhar",
|
||||
"ButtonFireOnTest": "Disparar evento onTest",
|
||||
"ButtonForceReScan": "Forcar Nova Verificação",
|
||||
"ButtonFullPath": "Caminho Completo",
|
||||
"ButtonHide": "Ocultar",
|
||||
@@ -37,6 +43,7 @@
|
||||
"ButtonJumpForward": "Adiantar",
|
||||
"ButtonLatest": "Mais Recentes",
|
||||
"ButtonLibrary": "Biblioteca",
|
||||
"ButtonLogout": "Logout",
|
||||
"ButtonLookup": "Procurar",
|
||||
"ButtonManageTracks": "Gerenciar Faixas",
|
||||
"ButtonMapChapterTitles": "Designar Títulos de Capítulos",
|
||||
@@ -45,18 +52,24 @@
|
||||
"ButtonNevermind": "Cancelar",
|
||||
"ButtonNext": "Próximo",
|
||||
"ButtonNextChapter": "Próximo Capítulo",
|
||||
"ButtonNextItemInQueue": "Próximo Item da Fila",
|
||||
"ButtonOk": "Ok",
|
||||
"ButtonOpenFeed": "Abrir Feed",
|
||||
"ButtonOpenManager": "Abrir Gerenciador",
|
||||
"ButtonPause": "Pausar",
|
||||
"ButtonPlay": "Reproduzir",
|
||||
"ButtonPlayAll": "Reproduzir Tudo",
|
||||
"ButtonPlaying": "Reproduzindo",
|
||||
"ButtonPlaylists": "Lista de Reprodução",
|
||||
"ButtonPrevious": "Anterior",
|
||||
"ButtonPreviousChapter": "Capítulo Anterior",
|
||||
"ButtonProbeAudioFile": "Sondar Arquivo de Áudio",
|
||||
"ButtonPurgeAllCache": "Apagar Todo o Cache",
|
||||
"ButtonPurgeItemsCache": "Apagar o Cache de Itens",
|
||||
"ButtonQueueAddItem": "Adicionar à Lista",
|
||||
"ButtonQueueRemoveItem": "Remover da Lista",
|
||||
"ButtonQuickEmbed": "Incorporação Rápida",
|
||||
"ButtonQuickEmbedMetadata": "Incorporação Rápida de Metadata",
|
||||
"ButtonQuickMatch": "Consulta rápida",
|
||||
"ButtonReScan": "Nova Verificação",
|
||||
"ButtonRead": "Ler",
|
||||
@@ -77,6 +90,8 @@
|
||||
"ButtonSaveTracklist": "Salvar Lista de Faixas",
|
||||
"ButtonScan": "Verificar",
|
||||
"ButtonScanLibrary": "Verificar Biblioteca",
|
||||
"ButtonScrollLeft": "Arrastar para Esquerda",
|
||||
"ButtonScrollRight": "Arrastar para Direita",
|
||||
"ButtonSearch": "Pesquisar",
|
||||
"ButtonSelectFolderPath": "Selecionar Caminho da Pasta",
|
||||
"ButtonSeries": "Séries",
|
||||
@@ -86,8 +101,11 @@
|
||||
"ButtonShow": "Exibir",
|
||||
"ButtonStartM4BEncode": "Iniciar Codificação M4B",
|
||||
"ButtonStartMetadataEmbed": "Iniciar Inclusão de Metadados",
|
||||
"ButtonStats": "Estatísticas",
|
||||
"ButtonSubmit": "Enviar",
|
||||
"ButtonTest": "Testar",
|
||||
"ButtonUnlinkOpenId": "Desvincular OpenID",
|
||||
"ButtonUpload": "Fazer Upload",
|
||||
"ButtonUploadBackup": "Upload de Backup",
|
||||
"ButtonUploadCover": "Upload de Capa",
|
||||
"ButtonUploadOPMLFile": "Upload Arquivo OPML",
|
||||
@@ -99,6 +117,7 @@
|
||||
"ErrorUploadFetchMetadataNoResults": "Não foi possível buscar metadados - tente atualizar o título e/ou autor",
|
||||
"ErrorUploadLacksTitle": "É preciso ter um título",
|
||||
"HeaderAccount": "Conta",
|
||||
"HeaderAddCustomMetadataProvider": "Adicionar Provedor de Metadados Personalizado",
|
||||
"HeaderAdvanced": "Avançado",
|
||||
"HeaderAppriseNotificationSettings": "Configuração de notificações Apprise",
|
||||
"HeaderAudioTracks": "Trilhas de áudio",
|
||||
@@ -196,6 +215,7 @@
|
||||
"LabelAddToPlaylist": "Adicionar à Lista de Reprodução",
|
||||
"LabelAddToPlaylistBatch": "Adicionar {0} itens à Lista de Reprodução",
|
||||
"LabelAddedAt": "Acrescentado em",
|
||||
"LabelAddedDate": "Adicionado {0}",
|
||||
"LabelAdminUsersOnly": "Apenas usuários administradores",
|
||||
"LabelAll": "Todos",
|
||||
"LabelAllUsers": "Todos Usuários",
|
||||
|
||||
@@ -229,6 +229,7 @@
|
||||
"LabelAddedDate": "Добавлено {0}",
|
||||
"LabelAdminUsersOnly": "Только для пользователей с правами администратора",
|
||||
"LabelAll": "Все",
|
||||
"LabelAllEpisodesDownloaded": "Все эпизоды загружены",
|
||||
"LabelAllUsers": "Все пользователи",
|
||||
"LabelAllUsersExcludingGuests": "Все пользователи, кроме гостей",
|
||||
"LabelAllUsersIncludingGuests": "Все пользователи, включая гостей",
|
||||
@@ -603,6 +604,7 @@
|
||||
"LabelSlug": "Слизень",
|
||||
"LabelSortAscending": "По возрастанию",
|
||||
"LabelSortDescending": "По убыванию",
|
||||
"LabelSortPubDate": "Отсортировать по дате публикации",
|
||||
"LabelStart": "Начало",
|
||||
"LabelStartTime": "Время начала",
|
||||
"LabelStarted": "Начат",
|
||||
@@ -703,6 +705,7 @@
|
||||
"LabelYourProgress": "Ваш прогресс",
|
||||
"MessageAddToPlayerQueue": "Добавить в очередь проигрывателя",
|
||||
"MessageAppriseDescription": "Для использования этой функции необходимо иметь запущенный экземпляр <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> или api которое обрабатывает те же самые запросы. <br />URL-адрес API Apprise должен быть полным URL-адресом для отправки уведомления, т.е., если API запущено по адресу <code>http://192.168.1.1:8337</code> тогда нужно указать <code>http://192.168.1.1:8337/notify</code>.",
|
||||
"MessageAsinCheck": "Убедитесь, что вы используете ASIN из правильной региональной зоны Audible, а не из Amazon.",
|
||||
"MessageBackupsDescription": "Бэкап включает пользователей, прогресс пользователей, данные элементов библиотеки, настройки сервера и изображения хранящиеся в <code>/metadata/items</code> и <code>/metadata/authors</code>. Бэкапы <strong>НЕ</strong> сохраняют файлы из папок библиотек.",
|
||||
"MessageBackupsLocationEditNote": "Примечание: Обновление местоположения резервной копии не приведет к перемещению или изменению существующих резервных копий",
|
||||
"MessageBackupsLocationNoEditNote": "Примечание: Местоположение резервного копирования задается с помощью переменной среды и не может быть изменено здесь.",
|
||||
@@ -721,6 +724,7 @@
|
||||
"MessageChapterErrorStartGteDuration": "Неверное время начала, должно быть меньше продолжительности аудиокниги",
|
||||
"MessageChapterErrorStartLtPrev": "Неверное время начала, должно быть больше или равно времени начала предыдущей главы",
|
||||
"MessageChapterStartIsAfter": "Глава начинается после окончания аудиокниги",
|
||||
"MessageChaptersNotFound": "Главы не найденны",
|
||||
"MessageCheckingCron": "Проверка cron...",
|
||||
"MessageConfirmCloseFeed": "Вы уверены, что хотите закрыть этот канал?",
|
||||
"MessageConfirmDeleteBackup": "Вы уверены, что хотите удалить бэкап для {0}?",
|
||||
@@ -777,8 +781,9 @@
|
||||
"MessageForceReScanDescription": "будет сканировать все файлы снова, как свежее сканирование. Теги ID3 аудиофайлов, OPF-файлы и текстовые файлы будут сканироваться как новые.",
|
||||
"MessageImportantNotice": "Важное замечание!",
|
||||
"MessageInsertChapterBelow": "Вставить главу ниже",
|
||||
"MessageItemsSelected": "{0} Элементов выделено",
|
||||
"MessageItemsUpdated": "{0} Элементов обновлено",
|
||||
"MessageInvalidAsin": "Неправильный ASIN",
|
||||
"MessageItemsSelected": "{0} элементов выделено",
|
||||
"MessageItemsUpdated": "{0} элементов обновлено",
|
||||
"MessageJoinUsOn": "Присоединяйтесь к нам в",
|
||||
"MessageLoading": "Загрузка...",
|
||||
"MessageLoadingFolders": "Загрузка каталогов...",
|
||||
@@ -810,7 +815,7 @@
|
||||
"MessageNoItems": "Нет элементов",
|
||||
"MessageNoItemsFound": "Элементы не найдены",
|
||||
"MessageNoListeningSessions": "Нет сеансов прослушивания",
|
||||
"MessageNoLogs": "Нет логов",
|
||||
"MessageNoLogs": "Нет записей",
|
||||
"MessageNoMediaProgress": "Нет прогресса медиа",
|
||||
"MessageNoNotifications": "Нет уведомлений",
|
||||
"MessageNoPodcastFeed": "Недопустимый подкаст: Нет канала",
|
||||
@@ -953,6 +958,7 @@
|
||||
"ToastBackupRestoreFailed": "Не удалось восстановить из бэкапа",
|
||||
"ToastBackupUploadFailed": "Не удалось загрузить бэкап",
|
||||
"ToastBackupUploadSuccess": "Бэкап загружен",
|
||||
"ToastBatchApplyDetailsToItemsSuccess": "Подробности, применяемые к элементам",
|
||||
"ToastBatchDeleteFailed": "Не удалось выполнить пакетное удаление",
|
||||
"ToastBatchDeleteSuccess": "Успешное пакетное удаление",
|
||||
"ToastBatchQuickMatchFailed": "Не удалось выполнить пакетное быстрое сопоставление!",
|
||||
@@ -1065,6 +1071,7 @@
|
||||
"ToastSelectAtLeastOneUser": "Выберите хотя бы одного пользователя",
|
||||
"ToastSendEbookToDeviceFailed": "Не удалось отправить e-книгу на устройство",
|
||||
"ToastSendEbookToDeviceSuccess": "E-книга отправлена на устройство \"{0}\"",
|
||||
"ToastSeriesSubmitFailedSameName": "Невозможно добавить две серии с одинаковым названием",
|
||||
"ToastSeriesUpdateFailed": "Не удалось обновить серию",
|
||||
"ToastSeriesUpdateSuccess": "Успешное обновление серии",
|
||||
"ToastServerSettingsUpdateSuccess": "Обновлены настройки сервера",
|
||||
@@ -1083,6 +1090,8 @@
|
||||
"ToastUnknownError": "Неизвестная ошибка",
|
||||
"ToastUnlinkOpenIdFailed": "Не удалось отвязать пользователя от OpenID",
|
||||
"ToastUnlinkOpenIdSuccess": "Пользователь отвязан от OpenID",
|
||||
"ToastUploaderFilepathExistsError": "Путь к файлу \"{0}\" уже существует на сервере",
|
||||
"ToastUploaderItemExistsInSubdirectoryError": "Элемент «{0}» использует подкаталог пути загрузки.",
|
||||
"ToastUserDeleteFailed": "Не удалось удалить пользователя",
|
||||
"ToastUserDeleteSuccess": "Пользователь удален",
|
||||
"ToastUserPasswordChangeSuccess": "Пароль успешно изменен",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"ButtonEditChapters": "Upraviť kapitoly",
|
||||
"ButtonEditPodcast": "Upraviť podcast",
|
||||
"ButtonEnable": "Povoliť",
|
||||
"ButtonFireAndFail": "Fire and Fail",
|
||||
"ButtonFireAndFail": "Spustiť a zlyhať",
|
||||
"ButtonFireOnTest": "Fire onTest udalosť",
|
||||
"ButtonForceReScan": "Vynútiť preskenovanie",
|
||||
"ButtonFullPath": "Zobraziť cestu",
|
||||
@@ -87,7 +87,7 @@
|
||||
"ButtonRestore": "Obnoviť zo zálohy",
|
||||
"ButtonSave": "Uložiť",
|
||||
"ButtonSaveAndClose": "Uložiť a zavrieť",
|
||||
"ButtonSaveTracklist": "Uložiť zoznam",
|
||||
"ButtonSaveTracklist": "Uložiť tracklist",
|
||||
"ButtonScan": "Skenovať",
|
||||
"ButtonScanLibrary": "Skenovať knižnicu",
|
||||
"ButtonScrollLeft": "Doľava",
|
||||
@@ -107,7 +107,7 @@
|
||||
"ButtonUnlinkOpenId": "Odhlásiť OpenID",
|
||||
"ButtonUpload": "Nahrať",
|
||||
"ButtonUploadBackup": "Nahrať zálohu",
|
||||
"ButtonUploadCover": "Nahrať obal",
|
||||
"ButtonUploadCover": "Nahrať prebal",
|
||||
"ButtonUploadOPMLFile": "Nahrať súbor OPML",
|
||||
"ButtonUserDelete": "Odstrániť užívateľa {0}",
|
||||
"ButtonUserEdit": "Upraviť užívateľa {0}",
|
||||
@@ -129,7 +129,7 @@
|
||||
"HeaderChooseAFolder": "Vybrať priečinok",
|
||||
"HeaderCollection": "Zbierka",
|
||||
"HeaderCollectionItems": "Položky zbierky",
|
||||
"HeaderCover": "Obal",
|
||||
"HeaderCover": "Prebal",
|
||||
"HeaderCurrentDownloads": "Aktuálne sťahovanie",
|
||||
"HeaderCustomMessageOnLogin": "Vlastné privítanie pri prihlásení",
|
||||
"HeaderCustomMetadataProviders": "Vlastné zdroje metadát",
|
||||
@@ -147,7 +147,7 @@
|
||||
"HeaderItemFiles": "Položka Súbory",
|
||||
"HeaderItemMetadataUtils": "Položka Nástroje metadát",
|
||||
"HeaderLastListeningSession": "Posledné pripojenie",
|
||||
"HeaderLatestEpisodes": "Najnovšie epizódy",
|
||||
"HeaderLatestEpisodes": "Posledné epizódy",
|
||||
"HeaderLibraries": "Knižnice",
|
||||
"HeaderLibraryFiles": "Súbory knižnice",
|
||||
"HeaderLibraryStats": "Štatistiky knižnice",
|
||||
@@ -157,6 +157,7 @@
|
||||
"HeaderLogs": "Záznamy udalostí",
|
||||
"HeaderManageGenres": "Spravovať žánre",
|
||||
"HeaderManageTags": "Spravovať štítky",
|
||||
"HeaderMapDetails": "Podrobnosti mapovania",
|
||||
"HeaderMatch": "Spárovať",
|
||||
"HeaderMetadataOrderOfPrecedence": "Metadáta pravidiel poradia",
|
||||
"HeaderMetadataToEmbed": "Metadáta na vloženie",
|
||||
@@ -176,11 +177,554 @@
|
||||
"HeaderPlaylist": "Playlist",
|
||||
"HeaderPlaylistItems": "Položky playlistu",
|
||||
"HeaderPodcastsToAdd": "Podcasty na pridanie",
|
||||
"HeaderPreviewCover": "Ukážka obalu",
|
||||
"HeaderPreviewCover": "Ukážka prebalu",
|
||||
"HeaderRSSFeedGeneral": "Detaily RSS",
|
||||
"HeaderRSSFeedIsOpen": "RSS zdroj je otvorený",
|
||||
"HeaderRSSFeeds": "RSS zdroje",
|
||||
"HeaderRemoveEpisode": "Odstrániť epizódu",
|
||||
"HeaderRemoveEpisodes": "Odstrániť {0} epizód",
|
||||
"LabelBackupsNumberToKeepHelp": "Týmto spôsobom odstránite vždy iba jednu zálohu. V prípade, ak chcete odtrániť viacero záloh, mali by ste ich odstrániť manuálne."
|
||||
"HeaderSavedMediaProgress": "Priebeh uložených médií",
|
||||
"HeaderSchedule": "Plán",
|
||||
"HeaderScheduleEpisodeDownloads": "Naplánovať automatické sťahovanie epizód",
|
||||
"HeaderScheduleLibraryScans": "Naplánovanovať automatické skenovanie knižnice",
|
||||
"HeaderSession": "Relácia",
|
||||
"HeaderSetBackupSchedule": "Naplánovať zálohovanie",
|
||||
"HeaderSettings": "Nastavenia",
|
||||
"HeaderSettingsDisplay": "Zobraziť",
|
||||
"HeaderSettingsExperimental": "Experimentálne funkcie",
|
||||
"HeaderSettingsGeneral": "Hlavné",
|
||||
"HeaderSettingsScanner": "Skener",
|
||||
"HeaderSettingsWebClient": "Webový klient",
|
||||
"HeaderSleepTimer": "Časovač spánku",
|
||||
"HeaderStatsLargestItems": "Najväčšie položky",
|
||||
"HeaderStatsLongestItems": "Najdlhšie položky (v hodinách)",
|
||||
"HeaderStatsMinutesListeningChart": "Vypočutých minút (za posledných 7 dní)",
|
||||
"HeaderStatsRecentSessions": "Nedávne relácie",
|
||||
"HeaderStatsTop10Authors": "Top 10 autorov",
|
||||
"HeaderStatsTop5Genres": "Top 5 žánrov",
|
||||
"HeaderTableOfContents": "Obsah",
|
||||
"HeaderTools": "Nástroje",
|
||||
"HeaderUpdateAccount": "Aktualizovať účet",
|
||||
"HeaderUpdateAuthor": "Aktualizovať autora",
|
||||
"HeaderUpdateDetails": "Aktualizovať detaily",
|
||||
"HeaderUpdateLibrary": "Aktualizovať knižnicu",
|
||||
"HeaderUsers": "Užívatelia",
|
||||
"HeaderYearReview": "Prehľad roka {0}",
|
||||
"HeaderYourStats": "Tvoje štatistiky",
|
||||
"LabelAbridged": "Skrátená verzia",
|
||||
"LabelAbridgedChecked": "Skrátená verzia (zaškrtnuté)",
|
||||
"LabelAbridgedUnchecked": "Neskrátená verzia (nezaškrtnuté)",
|
||||
"LabelAccessibleBy": "Prístupné pre",
|
||||
"LabelAccountType": "Typ účtu",
|
||||
"LabelAccountTypeAdmin": "Administrátor",
|
||||
"LabelAccountTypeGuest": "Hosť",
|
||||
"LabelAccountTypeUser": "Užívateľ",
|
||||
"LabelActivities": "Aktivity",
|
||||
"LabelActivity": "Aktivita",
|
||||
"LabelAddToCollection": "Pridať do kolekcie",
|
||||
"LabelAddToCollectionBatch": "Pridať {0} kníh do kolekcie",
|
||||
"LabelAddToPlaylist": "Pridať do playlistu",
|
||||
"LabelAddToPlaylistBatch": "Pridať {0} položie do playlistu",
|
||||
"LabelAddedAt": "Pridané",
|
||||
"LabelAddedDate": "Pridané {0}",
|
||||
"LabelAdminUsersOnly": "Iba administrátory",
|
||||
"LabelAll": "Všetko",
|
||||
"LabelAllEpisodesDownloaded": "Všetky epizódy stiahnuté",
|
||||
"LabelAllUsers": "Všetci užívatelia",
|
||||
"LabelAllUsersExcludingGuests": "Všetci užívatelia okrem hostí",
|
||||
"LabelAllUsersIncludingGuests": "Všetci užívatelia vrátane hostí",
|
||||
"LabelAlreadyInYourLibrary": "Už v tvojej knižnici",
|
||||
"LabelApiToken": "API Token",
|
||||
"LabelAppend": "Pridať",
|
||||
"LabelAudioBitrate": "Bitrate audio stopy (napr. 128k)",
|
||||
"LabelAudioChannels": "Počet kanálov audio stopy (1 alebo 2)",
|
||||
"LabelAudioCodec": "Kodek audio stopy",
|
||||
"LabelAuthor": "Autor",
|
||||
"LabelAuthorFirstLast": "Autor (Meno, Priezvisko)",
|
||||
"LabelAuthorLastFirst": "Autor (Priezvisko, Meno)",
|
||||
"LabelAuthors": "Autori",
|
||||
"LabelAutoDownloadEpisodes": "Automaticky sťahovať epizódy",
|
||||
"LabelAutoFetchMetadata": "Automaticky načítať metadáta",
|
||||
"LabelAutoFetchMetadataHelp": "Načíta metadáta pre názov, autra a sériu pre optimalizované nahranie. Dodatočné metadáta môžu byť priradené po nahraní.",
|
||||
"LabelAutoLaunch": "Automaticky spustiť",
|
||||
"LabelAutoLaunchDescription": "Presmerovať na poskytovateľa authentifikácie pri otvorení prihlasovacej stránky (manuálny prepis cesty <code>/login?autoLaunch=0</code>)",
|
||||
"LabelAutoRegister": "Automatická registrácia",
|
||||
"LabelAutoRegisterDescription": "Automaticky vytvoriť nových užívateľov po prihlásení",
|
||||
"LabelBackToUser": "Späť na užívateľa",
|
||||
"LabelBackupAudioFiles": "Zálohovať audio súbory",
|
||||
"LabelBackupLocation": "Zálohovať lokáciu",
|
||||
"LabelBackupsEnableAutomaticBackups": "Automatické zálohy",
|
||||
"LabelBackupsEnableAutomaticBackupsHelp": "Zálohy uložené v /metadata/backups",
|
||||
"LabelBackupsMaxBackupSize": "Maximálna veľkosť zálohy (v GB) (0 pre neobmedzenú)",
|
||||
"LabelBackupsMaxBackupSizeHelp": "Ako poistka proti miskonfigurácii, zálohy zlyhajú ak prekročia konfigurovanú veľkosť.",
|
||||
"LabelBackupsNumberToKeep": "Počet uložených záloh",
|
||||
"LabelBackupsNumberToKeepHelp": "Týmto spôsobom odstránite vždy iba jednu zálohu. V prípade, ak chcete odtrániť viacero záloh, mali by ste ich odstrániť manuálne.",
|
||||
"LabelBitrate": "Bitrate",
|
||||
"LabelBonus": "Bonus",
|
||||
"LabelBooks": "Knihy",
|
||||
"LabelButtonText": "Text tlačidla",
|
||||
"LabelByAuthor": "od {0}",
|
||||
"LabelChangePassword": "Zmeniť heslo",
|
||||
"LabelChannels": "Kanály",
|
||||
"LabelChapterCount": "{0} kapitol",
|
||||
"LabelChapterTitle": "Názov kapitoly",
|
||||
"LabelChapters": "Kapitoly",
|
||||
"LabelChaptersFound": "nájdených kapitol",
|
||||
"LabelClickForMoreInfo": "Klikni pre viac informácií",
|
||||
"LabelClickToUseCurrentValue": "Klikni pre použitie aktuálnej hodnoty",
|
||||
"LabelClosePlayer": "Zavrieť prehrávač",
|
||||
"LabelCodec": "Kodek",
|
||||
"LabelCollapseSeries": "Zbaliť série",
|
||||
"LabelCollapseSubSeries": "Zbaliť podsérie",
|
||||
"LabelCollection": "Kolekcia",
|
||||
"LabelCollections": "Kolekcie",
|
||||
"LabelComplete": "Hotovo",
|
||||
"LabelConfirmPassword": "Potvrdiť heslo",
|
||||
"LabelContinueListening": "Pokračovať v počúvaní",
|
||||
"LabelContinueReading": "Pokračovať v čítaní",
|
||||
"LabelContinueSeries": "Pokračovať v sérii",
|
||||
"LabelCover": "Prebal",
|
||||
"LabelCoverImageURL": "URL obrázku prebalu",
|
||||
"LabelCoverProvider": "Poskytovateľ prebalu",
|
||||
"LabelCreatedAt": "Vytvorené",
|
||||
"LabelCronExpression": "Cron príkaz",
|
||||
"LabelCurrent": "Aktuálny",
|
||||
"LabelCurrently": "Aktuálne:",
|
||||
"LabelCustomCronExpression": "Vlastný Cron príkaz:",
|
||||
"LabelDatetime": "Dátum a čas",
|
||||
"LabelDays": "Dni",
|
||||
"LabelDeleteFromFileSystemCheckbox": "Zmazať zo systému (odškrtni len pre odstránenie z databázy)",
|
||||
"LabelDescription": "Popis",
|
||||
"LabelDeselectAll": "Odznačiť všetko",
|
||||
"LabelDevice": "Zariadenie",
|
||||
"LabelDeviceInfo": "Informácie o zariadení",
|
||||
"LabelDeviceIsAvailableTo": "Zariadenie je k dispozícii...",
|
||||
"LabelDirectory": "Priečinok",
|
||||
"LabelDiscFromFilename": "Disk z názvu súboru",
|
||||
"LabelDiscFromMetadata": "Disk z metadát",
|
||||
"LabelDiscover": "Objaviť",
|
||||
"LabelDownload": "Stiahnuť",
|
||||
"LabelDownloadNEpisodes": "Stiahnuť {0} epizód",
|
||||
"LabelDownloadable": "Dostupné na stiahnutie",
|
||||
"LabelDuration": "Dĺžka",
|
||||
"LabelDurationComparisonExactMatch": "(presná zhoda)",
|
||||
"LabelDurationComparisonLonger": "({0} dlhšie)",
|
||||
"LabelDurationComparisonShorter": "({0} kratšie)",
|
||||
"LabelDurationFound": "Nájdená dlžka:",
|
||||
"LabelEbook": "E-kniha",
|
||||
"LabelEbooks": "E-knihy",
|
||||
"LabelEdit": "Upraviť",
|
||||
"LabelEmail": "E-mail",
|
||||
"LabelEmailSettingsFromAddress": "Z e-mailu",
|
||||
"LabelEmailSettingsRejectUnauthorized": "Odmietnuť neautorizované certifikáty",
|
||||
"LabelEmailSettingsRejectUnauthorizedHelp": "Vypnutie validácie SSL certifikátu môže tvoje pripojenie vystaviť bezpečnostným rizikám, ako napríklad MitM útokom. Vypni túto možnosť len v prípade, že rozumieš dôsledkom a dôveruješ e-mailovému serveru, ku ktorému sa pripájaš.",
|
||||
"LabelEmailSettingsSecure": "Bezpečné",
|
||||
"LabelEmailSettingsSecureHelp": "Pri povolení bude na pripojenie k serveru použité TLS. V opačnom prípade je TLS použité iba v prípade, ak server podporuje rozšírenie STARTTLS. Vo väčšine prípadov povoľte túto možnosť, ak sa pripájate cez port 465. V prípadoch, ak používate port 587 alebo 25, túto voľbu nepovoľujte. (prevzaté z nodemailer.com/smtp/#authentification)",
|
||||
"LabelEmailSettingsTestAddress": "Testovacia adresa",
|
||||
"LabelEmbeddedCover": "Vložený prebal",
|
||||
"LabelEnable": "Povoliť",
|
||||
"LabelEncodingBackupLocation": "Záloha vašich pôvodných zvukových súborov bude uložená v:",
|
||||
"LabelEncodingChaptersNotEmbedded": "Kapitoly nie sú vkladané do viacstopových audiokníh.",
|
||||
"LabelEncodingClearItemCache": "Nezabudnite pravidelne vyčistiť vyrovnávaciu pamäť jednotlivých položiek.",
|
||||
"LabelEncodingFinishedM4B": "Dokončený súbor M4B bude uložený do priečinka audioknihy v:",
|
||||
"LabelEncodingInfoEmbedded": "Metadáta budú vložené do zvukových stôp v priečinku audioknihy.",
|
||||
"LabelEncodingStartedNavigation": "Po spustení úlohy môžete túto stránku opustiť.",
|
||||
"LabelEncodingTimeWarning": "Prekódovanie môže trvať aj 30 minút.",
|
||||
"LabelEncodingWarningAdvancedSettings": "Pozor: Nemeňte uvedené nastavenia, pokiaľ nie ste dostatočne oboznámený s nastaveniami ffmpeg prekódovania.",
|
||||
"LabelEncodingWatcherDisabled": "V prípade, ak nemáte povolené automatické sledovanie zmien, bude na konci potrebné audioknihu opätovne preskenovať.",
|
||||
"LabelEnd": "Ukončiť",
|
||||
"LabelEndOfChapter": "Koniec kapitoly",
|
||||
"LabelEpisode": "Epizóda",
|
||||
"LabelEpisodeNotLinkedToRssFeed": "Epizóda bez RSS zdroja",
|
||||
"LabelEpisodeNumber": "Epizóda #{0}",
|
||||
"LabelEpisodeTitle": "Názov epizódy",
|
||||
"LabelEpisodeType": "Typ epizódy",
|
||||
"LabelEpisodeUrlFromRssFeed": "URL epizódy z RSS",
|
||||
"LabelEpisodes": "Epizódy",
|
||||
"LabelEpisodic": "Epizódny",
|
||||
"LabelExample": "Príklad",
|
||||
"LabelExpandSeries": "Rozbaliť série",
|
||||
"LabelExpandSubSeries": "Rozbaliť podsérie",
|
||||
"LabelExplicit": "Explicitné",
|
||||
"LabelExplicitChecked": "Explicitné (zaškrtnuté)",
|
||||
"LabelExplicitUnchecked": "Ne-explicitné (nezaškrtnuté)",
|
||||
"LabelExportOPML": "Exportovať OPML",
|
||||
"LabelFeedURL": "URL zdroja",
|
||||
"LabelFetchingMetadata": "Sťahovanie metadát",
|
||||
"LabelFile": "Súbor",
|
||||
"LabelFileBirthtime": "Čas vytvorenia súboru",
|
||||
"LabelFileBornDate": "Vytvorené {0}",
|
||||
"LabelFileModified": "Súbor zmenený",
|
||||
"LabelFileModifiedDate": "Zmenený {0}",
|
||||
"LabelFilename": "Názov súboru",
|
||||
"LabelFilterByUser": "Užívateľský filter",
|
||||
"LabelFindEpisodes": "Nájsť epizódy",
|
||||
"LabelFinished": "Ukončené",
|
||||
"LabelFolder": "Priečinok",
|
||||
"LabelFolders": "Priečinky",
|
||||
"LabelFontBold": "Tučné",
|
||||
"LabelFontBoldness": "Hrúbka písma",
|
||||
"LabelFontFamily": "Rodina písiem",
|
||||
"LabelFontItalic": "Kurzíva",
|
||||
"LabelFontScale": "Veľkosť písma",
|
||||
"LabelFontStrikethrough": "Preškrtnuté",
|
||||
"LabelFormat": "Formát",
|
||||
"LabelFull": "Plné",
|
||||
"LabelGenre": "Žáner",
|
||||
"LabelGenres": "Žánre",
|
||||
"LabelHardDeleteFile": "Nezvratné zmazanie súborov",
|
||||
"LabelHasEbook": "Má e-knihu",
|
||||
"LabelHasSupplementaryEbook": "Má doplnkovú e-knihu",
|
||||
"LabelHideSubtitles": "Skryť titulky",
|
||||
"LabelHighestPriority": "Najvyššia priorita",
|
||||
"LabelHost": "Host",
|
||||
"LabelHour": "Hodina",
|
||||
"LabelHours": "Hodiny",
|
||||
"LabelIcon": "Ikona",
|
||||
"LabelImageURLFromTheWeb": "URL obrázku",
|
||||
"LabelInProgress": "Prebieha",
|
||||
"LabelIncludeInTracklist": "Vložiť do tracklistu",
|
||||
"LabelIncomplete": "Nekompletné",
|
||||
"LabelInterval": "Interval",
|
||||
"LabelIntervalCustomDailyWeekly": "Vlastný",
|
||||
"LabelIntervalEvery12Hours": "Každých 12 hodín",
|
||||
"LabelIntervalEvery15Minutes": "Každých 15 minút",
|
||||
"LabelIntervalEvery2Hours": "Každé 2 hodiny",
|
||||
"LabelIntervalEvery30Minutes": "Každých 30 minút",
|
||||
"LabelIntervalEvery6Hours": "Každých 6 hodín",
|
||||
"LabelIntervalEveryDay": "Denne",
|
||||
"LabelIntervalEveryHour": "Každú hodinu",
|
||||
"LabelIntervalEveryMinute": "Každú minútu",
|
||||
"LabelInvert": "Invertne",
|
||||
"LabelItem": "Položka",
|
||||
"LabelJumpBackwardAmount": "Posunúť vpred o",
|
||||
"LabelJumpForwardAmount": "Posunúť dozadu o",
|
||||
"LabelLanguage": "Jazyk",
|
||||
"LabelLanguageDefaultServer": "Prednastavený jazyk servera",
|
||||
"LabelLanguages": "Jazyky",
|
||||
"LabelLastBookAdded": "Posledná pridaná kniha",
|
||||
"LabelLastBookUpdated": "Posledná aktualizovaná kniha",
|
||||
"LabelLastSeen": "Posledne videné",
|
||||
"LabelLastTime": "Posledný čas",
|
||||
"LabelLastUpdate": "Posledná aktualizácia",
|
||||
"LabelLayout": "Rozloženie",
|
||||
"LabelLayoutSinglePage": "Jedna stránka",
|
||||
"LabelLayoutSplitPage": "Rozdelená stránka",
|
||||
"LabelLess": "Menej",
|
||||
"LabelLibrariesAccessibleToUser": "Knižnice dostupné užívateľovi",
|
||||
"LabelLibrary": "Knižnica",
|
||||
"LabelLibraryFilterSublistEmpty": "Žiadne {0}",
|
||||
"LabelLibraryItem": "Položka knižnice",
|
||||
"LabelLibraryName": "Názov knižnice",
|
||||
"LabelLimit": "Limit",
|
||||
"LabelLineSpacing": "Riadkovanie",
|
||||
"LabelListenAgain": "Počúvať znova",
|
||||
"LabelLogLevelDebug": "Ladenie",
|
||||
"LabelLogLevelInfo": "Informácia",
|
||||
"LabelLogLevelWarn": "Varovanie",
|
||||
"LabelLookForNewEpisodesAfterDate": "Hľadať nové epizódy od uvedeného dátumu",
|
||||
"LabelLowestPriority": "Najnižšia priorita",
|
||||
"LabelMatchExistingUsersBy": "Vyhľadaj vytvorených užívateľov podľa",
|
||||
"LabelMatchExistingUsersByDescription": "Používané na pripájanie vytvorených užívateľov. Po pripojení budú užívatelia vyhľadaní na základe jedinečného id poskytnutého Vaším poskytovateľom SSO",
|
||||
"LabelMaxEpisodesToDownload": "Stiahnuť maximálne # epizód. Pre neobmedzené sťahovanie zadajte 0.",
|
||||
"LabelMaxEpisodesToDownloadPerCheck": "Pri kontrole stiahnuť maximálne # epizód",
|
||||
"LabelMaxEpisodesToKeep": "Uchovávať maximálne # epizód",
|
||||
"LabelMaxEpisodesToKeepHelp": "Hodnota 0 znamená bez limitu. Po automatickom stiahnutí novej epizódy bude najstaršia epizóda zmazaná a ponechaných zostane X epizód. Pri každom stiahnutí 1 epizódy bude vždy zmazaná iba 1 najstaršia epizóda.",
|
||||
"LabelMediaPlayer": "Prehrávač",
|
||||
"LabelMediaType": "Typ média",
|
||||
"LabelMetaTag": "Meta štítok",
|
||||
"LabelMetaTags": "Meta štítky",
|
||||
"LabelMetadataOrderOfPrecedenceDescription": "Zdroje metadát s vyššou prioritou prepíšu zdroje metadát s nižšou prioritou",
|
||||
"LabelMetadataProvider": "Poskytovateľ metadát",
|
||||
"LabelMinute": "Minúta",
|
||||
"LabelMinutes": "Minúty",
|
||||
"LabelMissing": "Chýbajúce",
|
||||
"LabelMissingEbook": "Nemá e-knihu",
|
||||
"LabelMissingSupplementaryEbook": "Nemá doplnkovú e-knihu",
|
||||
"LabelMobileRedirectURIs": "Povolené Mobile Redirect URI",
|
||||
"LabelMobileRedirectURIsDescription": "Toto je zoznam povolených URI pre mobilné aplikácie. Prednastavená je <code>audiobookshelf://oauth</code>, ktorú však môžete odstrániť alebo nahradiť inou URI pre integráciu aplikácií tretích strán. Použitím hviezdičky (<code>*</code>) povolíte všetky URI.",
|
||||
"LabelMore": "Viac",
|
||||
"LabelMoreInfo": "Viac informácií",
|
||||
"LabelName": "Meno",
|
||||
"LabelNarrator": "Interpret",
|
||||
"LabelNarrators": "Interpreti",
|
||||
"LabelNew": "Nový",
|
||||
"LabelNewPassword": "Nové heslo",
|
||||
"LabelNewestAuthors": "Najnovší autori",
|
||||
"LabelNewestEpisodes": "Najnovšie epizódy",
|
||||
"LabelNextBackupDate": "Ďalší dátum zálohovania",
|
||||
"LabelNextScheduledRun": "Ďalší plánovaný beh",
|
||||
"LabelNoCustomMetadataProviders": "Žiadne vlastné zdroje metadát",
|
||||
"LabelNoEpisodesSelected": "Neboli vybrané žiadne epizódy",
|
||||
"LabelNotFinished": "Nedokončené",
|
||||
"LabelNotStarted": "Nezačaté",
|
||||
"LabelNotes": "Poznámky",
|
||||
"LabelNotificationAppriseURL": "URL odkaz(-y) Apprise",
|
||||
"LabelNotificationAvailableVariables": "Dostupné premenné",
|
||||
"LabelNotificationBodyTemplate": "Šablóna obsahu",
|
||||
"LabelNotificationEvent": "Udalosť oznámení",
|
||||
"LabelNotificationTitleTemplate": "Šablóna názvu",
|
||||
"LabelNotificationsMaxFailedAttempts": "Maximálny počet neúspešných pokusov",
|
||||
"LabelNotificationsMaxFailedAttemptsHelp": "Notifikácie sa automaticky vypnú, ak ich odoslanie zlyhá nasledovný počet krát",
|
||||
"LabelNotificationsMaxQueueSize": "Maximálna dĺžka fronty oznámení",
|
||||
"LabelNotificationsMaxQueueSizeHelp": "Odosielanie udalostí je ohraničené na jedno oznámenie za sekundu. Novovzniknuté udalosti budú ignorované, ak bude fronta oznámení naplnená. Toto nastavenie zabraňuje nevyžiadanému zahlteniu oznámeniami.",
|
||||
"LabelNumberOfBooks": "Počet kníh",
|
||||
"LabelNumberOfEpisodes": "# epizód",
|
||||
"LabelOpenIDAdvancedPermsClaimDescription": "Názov OpenID predpokladá prítomnosť pokročilých povolení pre užívateľské akcie v rámci aplikácie, ktoré sú aplikovateľné na ne-administrátorské role (<b>ak sú nakonfigurované</b>). Ak potvrdenie takýchto pokročilých povolení nie je v odozve prítomné, prístup do ABS bude automaticky zamietnutý. Ak v odozve chýba len niektoré z očakávaných nastavení, tak bude jeho hodnota automaticky nastavená na <code>false</code>. Uistite sa prosím, že forma odozvy poskytovateľa identity má nasledovnú štruktúru:",
|
||||
"LabelOpenIDClaims": "Ak ponecháte nasledujúce nastavenia prázdne, pokročilé nastavenia skupín a povolení nebudú aktivované a automaticky bude nastavená skupina 'Užívateľ'.",
|
||||
"LabelOpenIDGroupClaimDescription": "Pri názve požiadavky OpenID sa predpokladá, že obsahuje zoznam užívateľských skupín. Bežne označovaný ako <code>groups</code>. <b>Ak je správne nakonfigurovaný</b>, aplikácia automaticky pridelí role podľa príslušnosti k užívateľským skupinám pod podmienkou, že sú tieto skupiny v požiadavke nazvané (bez ohľadu na veľkosť písmen) ako 'admin', 'user' alebo 'guest'. Požiadavka musí obsahovať zoznam skupín a ak užívateľ patrí do viacerých skupín, aplikácia mu priradí rolu, ktorá zodpovedá skupine s najvyššími prístupovými právami. Ak sa žiadna z poskytnutých skupín nezhoduje, prístup bude zamietnutý.",
|
||||
"LabelOpenRSSFeed": "Otvor RSS zdroj",
|
||||
"LabelOverwrite": "Prepísať",
|
||||
"LabelPaginationPageXOfY": "Stránka {0} z {1}",
|
||||
"LabelPassword": "Heslo",
|
||||
"LabelPath": "Cesta",
|
||||
"LabelPermanent": "Trvalé",
|
||||
"LabelPermissionsAccessAllLibraries": "Má prístup do všetkých knižníc",
|
||||
"LabelPermissionsAccessAllTags": "Má prístup ku všetkým štítkom",
|
||||
"LabelPermissionsAccessExplicitContent": "Má prístup k explicitnému obsahu",
|
||||
"LabelPermissionsCreateEreader": "Môže vytvoriť čítačku e-kníh",
|
||||
"LabelPermissionsDelete": "Môže mazať",
|
||||
"LabelPermissionsDownload": "Môže sťahovať",
|
||||
"LabelPermissionsUpdate": "Môže aktualizovať",
|
||||
"LabelPermissionsUpload": "Môže nahrávať",
|
||||
"LabelPersonalYearReview": "Váš rok v prehľade ({0})",
|
||||
"LabelPhotoPathURL": "Cesta/URL fotky",
|
||||
"LabelPlayMethod": "Metóda prehrávania",
|
||||
"LabelPlaybackRateIncrementDecrement": "Veľkosť kroku zrýchlenia/spomalenia rýchlosti prehávania",
|
||||
"LabelPlayerChapterNumberMarker": "{0} z {1}",
|
||||
"LabelPlaylists": "Playlisty",
|
||||
"LabelPodcast": "Podcast",
|
||||
"LabelPodcastSearchRegion": "Región vyhľadávania podcastu",
|
||||
"LabelPodcastType": "Typ podcastu",
|
||||
"LabelPodcasts": "Podcasty",
|
||||
"LabelPort": "Prístav",
|
||||
"LabelPrefixesToIgnore": "Ignorované predpony (bez ohľadu na veľkosť písmen)",
|
||||
"LabelPreventIndexing": "Zabráňte indexovaniu Vášho zdroja službami iTunes a Google podcasts directories",
|
||||
"LabelPrimaryEbook": "Primárny e-book",
|
||||
"LabelProgress": "Pokrok",
|
||||
"LabelProvider": "Poskytovateľ",
|
||||
"LabelProviderAuthorizationValue": "Obsah hlavičky autorizácie",
|
||||
"LabelPubDate": "Dátum vydania",
|
||||
"LabelPublishYear": "Rok vydania",
|
||||
"LabelPublishedDate": "Vydané {0}",
|
||||
"LabelPublishedDecade": "Dekáda vydania",
|
||||
"LabelPublishedDecades": "Dekády vydania",
|
||||
"LabelPublisher": "Vydavateľ",
|
||||
"LabelPublishers": "Vydavatelia",
|
||||
"LabelRSSFeedCustomOwnerEmail": "Vlastný e-mail vlastníka",
|
||||
"LabelRSSFeedCustomOwnerName": "Vlastné meno vlastníka",
|
||||
"LabelRSSFeedOpen": "RSS zdroj otvorený",
|
||||
"LabelRSSFeedPreventIndexing": "Zakázať indexovanie",
|
||||
"LabelRSSFeedSlug": "Slug RSS zdroja",
|
||||
"LabelRSSFeedURL": "URL RSS zdroja",
|
||||
"LabelRandomly": "Náhodne",
|
||||
"LabelReAddSeriesToContinueListening": "Znova pridať série do pokračujúceho počúvania",
|
||||
"LabelRead": "Čítať",
|
||||
"LabelReadAgain": "Čítať znova",
|
||||
"LabelReadEbookWithoutProgress": "Čítať e-knihu bez sledovania pokroku",
|
||||
"LabelRecentSeries": "Posledné série",
|
||||
"LabelRecentlyAdded": "Posledné pridané",
|
||||
"LabelRecommended": "Odporúčané",
|
||||
"LabelRedo": "Zopakovať",
|
||||
"LabelRegion": "Región",
|
||||
"LabelReleaseDate": "Dátum vydania",
|
||||
"LabelRemoveAllMetadataAbs": "Odstrániť všetky súbory metadata.abs",
|
||||
"LabelRemoveAllMetadataJson": "Odstrániť všetky súbory metadata.json",
|
||||
"LabelRemoveCover": "Odstrániť prebal",
|
||||
"LabelRemoveMetadataFile": "Odstrániť súbory metadát z priečinkov položiek v knižnici",
|
||||
"LabelRemoveMetadataFileHelp": "Odstrániť všetky súbory metadata.json a metadata.abs vo Vašich {0} priečinkoch.",
|
||||
"LabelRowsPerPage": "Počet riadkov na stránku",
|
||||
"LabelSearchTerm": "Hľadaj výraz",
|
||||
"LabelSearchTitle": "Hľadaj názov",
|
||||
"LabelSearchTitleOrASIN": "Hľadaj názov alebo ASIN",
|
||||
"LabelSeason": "Sezóna",
|
||||
"LabelSeasonNumber": "Sezóna #{0}",
|
||||
"LabelSelectAll": "Vybrať všetko",
|
||||
"LabelSelectAllEpisodes": "Vybrať všetky epizódy",
|
||||
"LabelSelectEpisodesShowing": "Vybrať {0} zobrazených epizód",
|
||||
"LabelSelectUsers": "Vybrať užívateľov",
|
||||
"LabelSendEbookToDevice": "Poslať e-knihu do...",
|
||||
"LabelSequence": "Postupnosť",
|
||||
"LabelSerial": "Na pokračovanie",
|
||||
"LabelSeries": "Séria",
|
||||
"LabelSeriesName": "Názov série",
|
||||
"LabelSeriesProgress": "Pokrok série",
|
||||
"LabelServerLogLevel": "Úroveň logovania servera",
|
||||
"LabelServerYearReview": "Rok servera v prehľade ({0})",
|
||||
"LabelSetEbookAsPrimary": "Nastaviť ako primárny",
|
||||
"LabelSetEbookAsSupplementary": "Nastaviť ako doplnkový",
|
||||
"LabelSettingsAllowIframe": "Povoliť vkladanie do iframe",
|
||||
"LabelSettingsAudiobooksOnly": "Len audioknihy",
|
||||
"LabelSettingsAudiobooksOnlyHelp": "Pri povolení tejto voľby budú všetky e-knihy ignorované okrem tých, ktoré sa nachádzajú v priečinkoch audiokníh. Tie budú zároveň automaticky nastavené ako doplnkové e-knihy",
|
||||
"LabelSettingsBookshelfViewHelp": "Skeuomorfný dizajn s drevenými poličkami",
|
||||
"LabelSettingsChromecastSupport": "Podpora chromecastu",
|
||||
"LabelSettingsDateFormat": "Formát dátumu",
|
||||
"LabelSettingsEnableWatcher": "Automatické skenovanie knižníc pre zmeny",
|
||||
"LabelSettingsEnableWatcherForLibrary": "Automaticky skenovať knižnicu pre zmeny",
|
||||
"LabelSettingsEnableWatcherHelp": "Povoliť automatické pridávanie/aktualizácie položiek pri zmene súborov. *Vyžaduje reštart servera",
|
||||
"LabelSettingsEpubsAllowScriptedContent": "Povoliť v e-knihách skriptovaný obsah",
|
||||
"LabelSettingsEpubsAllowScriptedContentHelp": "Povoliť e-knihám spúšťanie skriptov. Odporúča sa túto voľbu nepovolovať, pokiaľ plne nedôverujete zdrojom súborov e-kníh.",
|
||||
"LabelSettingsExperimentalFeatures": "Experimentálne funkcie",
|
||||
"LabelSettingsExperimentalFeaturesHelp": "Funkcie vo vývoji, ktoré ocenia Vašu spätnú väzbu a pomoc s testovaním. Kliknite pre otvorenie diskusie na GitHub-e.",
|
||||
"LabelSettingsFindCovers": "Nájdi prebaly",
|
||||
"LabelSettingsFindCoversHelp": "Ak Vaša audiokniha neobsahuje vložený prebal alebo jeho obrázok v priečinku audioknihy, skener sa ho pokúsi automaticky vyhľadať.<br>Poznámka: Táto voľba predĺži čas skenovania",
|
||||
"LabelSettingsHideSingleBookSeries": "Skryť série obsahujúce len jednu knihu",
|
||||
"LabelSettingsHideSingleBookSeriesHelp": "Série obsahujúce len jednu knihu budú skryté na stránke sérií a na poličkách domácej stránky.",
|
||||
"LabelSettingsHomePageBookshelfView": "Domáca stránka používa poličkový náhľad",
|
||||
"LabelSettingsLibraryBookshelfView": "Knižnica používa poličkový náhľad",
|
||||
"LabelSettingsLibraryMarkAsFinishedPercentComplete": "Percento dokončenia je väčšie ako",
|
||||
"LabelSettingsLibraryMarkAsFinishedTimeRemaining": "Zostávajúci čas je menší ako (sekúnd)",
|
||||
"LabelSettingsLibraryMarkAsFinishedWhen": "Označiť položku média ako dokončenú",
|
||||
"LabelSettingsOnlyShowLaterBooksInContinueSeries": "Preskočiť predchádzajúce knihy v pokračujúcej sérii",
|
||||
"LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp": "Polička pokračujúcich sérií na domácej stránke zobrazuje prvú nezačatú knihu série, ktorá má ukončenú aspoň jednu z kníh série a žiadne započaté knihy. Povolením tohto nastavenia bude pokračujúca séria začínať poslednou ukončenou knihou miesto prvej nepočúvanej.",
|
||||
"LabelSettingsParseSubtitles": "Parsovať podtituly",
|
||||
"LabelSettingsParseSubtitlesHelp": "Extrahovať podtituly z názvov priečinkov audiokníh.<br>Podtitul musí byť oddelený \" - \"<br>tj. \"Názov knihy - Podtitul knihy\" má podtitul \"Podtitul knihy\"",
|
||||
"LabelSettingsPreferMatchedMetadata": "Preferovať vyhľadané metadáta",
|
||||
"LabelSettingsPreferMatchedMetadataHelp": "Pri použití funkcie Rýchle vyhľadávanie, vyhľadané údaje prepíšu príslušné údaje položky. Defaultne funkcia Rýchle vyhľadávanie vyplní iba chýbajúce údaje.",
|
||||
"LabelSettingsSkipMatchingBooksWithASIN": "Preskočiť vyhľadané knihy, ktoré už majú vyplnené ASIN",
|
||||
"LabelSettingsSkipMatchingBooksWithISBN": "Preskočiť vyhľadané knihy, ktoré už majú vyplnené ISBN",
|
||||
"LabelSettingsSortingIgnorePrefixes": "Pri triedení ignorovať prefixy",
|
||||
"LabelSettingsSortingIgnorePrefixesHelp": "tj. v prípade prefixu \"the\" bude názov knihy \"The Book Title\" usporiadaný podľa \"Book Title, The\"",
|
||||
"LabelSettingsSquareBookCovers": "Použiť štvorcové prebaly kníh",
|
||||
"LabelSettingsSquareBookCoversHelp": "Preferovať štvorcové prebaly pred štandardnými prebalmi kníh formátu 1.6:1",
|
||||
"LabelSettingsStoreCoversWithItem": "Okladať prebaly k položkám",
|
||||
"LabelSettingsStoreCoversWithItemHelp": "Defaultne sú prebaly uložené v priečinku /metadata/items, povolením tejto voľby budú prebaly umiestnené do priečinkov jednotlivých položiek Vašej knižnice, pričom bude vždy použitý len jeden súbor s názvom \"cover\"",
|
||||
"LabelSettingsStoreMetadataWithItem": "Uložiť metadáta spolu s položkou",
|
||||
"LabelSettingsStoreMetadataWithItemHelp": "Defaultne sú súbory metadát ukladané do priečinka /metadata/items, pri povolení tejto voľby budú súbory metadát uložené do priečinkov položiek Vašej knižnice",
|
||||
"LabelSettingsTimeFormat": "Formát času",
|
||||
"LabelShare": "Zdieľať",
|
||||
"LabelShareDownloadableHelp": "Umožňuje užívateľom s linkom na zdieľanie sťahovať zip súbor položky knižnice.",
|
||||
"LabelShareOpen": "Zdieľať Otvoriť",
|
||||
"LabelShareURL": "Zdieľať URL",
|
||||
"LabelShowAll": "Zobraziť všetko",
|
||||
"LabelShowSeconds": "Zobraziť sekundy",
|
||||
"LabelShowSubtitles": "Zobraziť podnázvy",
|
||||
"LabelSize": "Veľkosť",
|
||||
"LabelSleepTimer": "Časovač spánku",
|
||||
"LabelSlug": "Slug",
|
||||
"LabelSortAscending": "Vzostupne",
|
||||
"LabelSortDescending": "Zostupne",
|
||||
"LabelSortPubDate": "Zoradiť podľa dátumu vydania",
|
||||
"LabelStart": "Začiatok",
|
||||
"LabelStartTime": "Čas spustenia",
|
||||
"LabelStarted": "Začaté",
|
||||
"LabelStartedAt": "Začaté v",
|
||||
"LabelStatsAudioTracks": "Zvukové stopy",
|
||||
"LabelStatsAuthors": "Autori",
|
||||
"LabelStatsBestDay": "Najlepší deň",
|
||||
"LabelStatsDailyAverage": "Denný priemer",
|
||||
"LabelStatsDays": "Dni",
|
||||
"LabelStatsDaysListened": "Dní počúvania",
|
||||
"LabelStatsHours": "Hodiny",
|
||||
"LabelStatsInARow": "v rade",
|
||||
"LabelStatsItemsFinished": "Dokončených položiek",
|
||||
"LabelStatsItemsInLibrary": "Položky v knižnici",
|
||||
"LabelStatsMinutes": "minút",
|
||||
"LabelStatsMinutesListening": "Minút počúvania",
|
||||
"LabelStatsOverallDays": "Celkovo dní",
|
||||
"LabelStatsOverallHours": "Celkovo hodín",
|
||||
"LabelStatsWeekListening": "Týždňov počúvania",
|
||||
"LabelSubtitle": "Podnázov",
|
||||
"LabelSupportedFileTypes": "Podporované typy súborov",
|
||||
"LabelTag": "Štítok",
|
||||
"LabelTags": "Štítky",
|
||||
"LabelTagsAccessibleToUser": "Štítky dostupné užívateľovi",
|
||||
"LabelTagsNotAccessibleToUser": "Štítky nedostupné užívateľovi",
|
||||
"LabelTasks": "Bežiace úlohy",
|
||||
"LabelTextEditorBulletedList": "Zoznam s odrážkami",
|
||||
"LabelTextEditorLink": "Pripojiť",
|
||||
"LabelTextEditorNumberedList": "Očíslovaný zoznam",
|
||||
"LabelTextEditorUnlink": "Odpojiť",
|
||||
"LabelTheme": "Téma",
|
||||
"LabelThemeDark": "Tmavá",
|
||||
"LabelThemeLight": "Svetlá",
|
||||
"LabelTimeBase": "Časová základňa",
|
||||
"LabelTimeDurationXHours": "{0} hodín",
|
||||
"LabelTimeDurationXMinutes": "{0} minút",
|
||||
"LabelTimeDurationXSeconds": "{0} sekúnd",
|
||||
"LabelTimeInMinutes": "Čas v minútach",
|
||||
"LabelTimeLeft": "{0} ponechaných",
|
||||
"LabelTimeListened": "Čas počúvania",
|
||||
"LabelTimeListenedToday": "Dnešný čas počúvania",
|
||||
"LabelTimeRemaining": "{0} zostávajúcich",
|
||||
"LabelTimeToShift": "Čas posunutia v sekundách",
|
||||
"LabelTitle": "Názov",
|
||||
"LabelToolsEmbedMetadata": "Vlož metadáta",
|
||||
"LabelToolsEmbedMetadataDescription": "Vloždo zvukových súborov metadáta obsahujúce obrázok prebalu a kapitoly.",
|
||||
"LabelToolsM4bEncoder": "M4B Encoder",
|
||||
"LabelToolsMakeM4b": "Vytvoriť M4B súbor audioknihy",
|
||||
"LabelToolsMakeM4bDescription": "Generovať .M4B súbor audioknihy obsahujúci vložené metadáta, obrázok prebalu a kapitoly.",
|
||||
"LabelToolsSplitM4b": "Rozdeliť M4B do MP3",
|
||||
"LabelToolsSplitM4bDescription": "Vytvoriť MP3 súbory rozdelením M4B podľa kapitol a vložiť do nich metadáta, obrázok prebalu a kapitoly.",
|
||||
"LabelTotalDuration": "Celkové trvanie",
|
||||
"LabelTotalTimeListened": "Celkový čas počúvania",
|
||||
"LabelTrackFromFilename": "Stopa podľa názvu súboru",
|
||||
"LabelTrackFromMetadata": "Stopa podľ metadát",
|
||||
"LabelTracks": "Stopy",
|
||||
"LabelTracksMultiTrack": "Viacstopová",
|
||||
"LabelTracksNone": "Žiadne stopy",
|
||||
"LabelTracksSingleTrack": "Jednostopová",
|
||||
"LabelTrailer": "Trailer",
|
||||
"LabelType": "Typ",
|
||||
"LabelUnabridged": "Neskrátená",
|
||||
"LabelUndo": "Naspäť",
|
||||
"LabelUnknown": "Neznámy",
|
||||
"LabelUnknownPublishDate": "Neznámy dátum vydania",
|
||||
"LabelUpdateCover": "Aktualizácia prebalu",
|
||||
"LabelUpdateCoverHelp": "Povoliť nahradenie existujúcich prebalov pre vybrané knihy, ak bolo vyhľadanie úspešné",
|
||||
"LabelUpdateDetails": "Aktualizovať detaily",
|
||||
"LabelUpdateDetailsHelp": "Povoliť nahradenie existujúcich detailov pre vybrané knihy, ak bolo vyhľadanie úspešné",
|
||||
"LabelUpdatedAt": "Aktualizované",
|
||||
"LabelUploaderDragAndDrop": "Potiahni a vlož súbory alebo priečinky",
|
||||
"LabelUploaderDragAndDropFilesOnly": "Potiahni a vlož súbory",
|
||||
"LabelUploaderDropFiles": "Vlož súbory",
|
||||
"LabelUploaderItemFetchMetadataHelp": "Automaticky vyhľadá názov, autora a sériu",
|
||||
"LabelUseAdvancedOptions": "Použiť pokročilé nastavenia",
|
||||
"LabelUseChapterTrack": "Použiť stopu kapitoly",
|
||||
"LabelUseFullTrack": "Použiť celú stopu",
|
||||
"LabelUseZeroForUnlimited": "Použito 0 pre neobmedzené",
|
||||
"LabelUser": "Užívateľ",
|
||||
"LabelUsername": "Prihlasovacie meno",
|
||||
"LabelValue": "Hodnota",
|
||||
"LabelVersion": "Verzia",
|
||||
"LabelViewBookmarks": "Zobraziť záložky",
|
||||
"LabelViewChapters": "Zobraziť kapitoly",
|
||||
"LabelViewPlayerSettings": "Zobraziť nastavenie prehrávania",
|
||||
"LabelViewQueue": "Zobraziť zoznam na prehratie",
|
||||
"LabelVolume": "Hlasitosť",
|
||||
"LabelWebRedirectURLsDescription": "Autorizovať nasledovné URL linky pomocou Vášho OAuth poskytovateľa a povoliť presmerovanie späť na webovú aplikáciu po prihlásení:",
|
||||
"LabelWebRedirectURLsSubfolder": "Podpriečinok pre presmerované URL",
|
||||
"LabelWeekdaysToRun": "Povolené dni v týždni",
|
||||
"LabelXBooks": "{0} kníh",
|
||||
"LabelXItems": "{0} položiek",
|
||||
"LabelYearReviewHide": "Skryť rok v prehľade",
|
||||
"LabelYearReviewShow": "Zobraziť rok v prehľade",
|
||||
"LabelYourAudiobookDuration": "Dĺžka Vašej audioknihy",
|
||||
"LabelYourBookmarks": "Vaše záložky",
|
||||
"LabelYourPlaylists": "Vaše playlisty",
|
||||
"LabelYourProgress": "Váš pokrok",
|
||||
"MessageAddToPlayerQueue": "Pridať do zoznamu prehrávania",
|
||||
"MessageAppriseDescription": "Aby ste mohli používať túto funkciumusíte mať k dispozícii inštanciu <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> alebo inú, ktorá dokáže spracovávať rovnaké požiadavky/requesty.<br/>Apprise URL musí byť úplná URL určená na zasielanie notifikácií, tj. ak napr. vaša APi beží na <code>http://192.168.1.1:8337</code>, vložte do daného poľa <code>http://192.168.1.1:8337/notify</code>.",
|
||||
"MessageBackupsDescription": "Zálohy pokrývajú používateľov, ich aktuálne stavy počúvania, detaily položiek knižnice, nastavenia servera a obrázky uložené v <code>/metadata/items</code> a <code>/metadata/authors</code>. Zálohy <strong>neobsahujú</strong> súbory v priečinkoch vašich knižníc.",
|
||||
"MessageBackupsLocationEditNote": "Poznámka: Zmena umiestnenia záloh nepresunie ani nezmení existujúce zálohy",
|
||||
"MessageBackupsLocationNoEditNote": "Poznámka: Umietnenie záloh je nastavené prostredníctvom premennej prostredia a nie je ho možné zmeniť z tohto miesta.",
|
||||
"MessageBackupsLocationPathEmpty": "Cesta umiestnenia záloh nemôže byť prázdna",
|
||||
"MessageBatchEditPopulateMapDetailsAllHelp": "Vyplniť povolené polia dátami zo všetkých položiek. Viacpočetné hodnoty v poliach budú zlúčené",
|
||||
"MessageBatchEditPopulateMapDetailsItemHelp": "Vyplniť polia povolených detailov z údajov tejto položky",
|
||||
"MessageBatchQuickMatchDescription": "Rýchle vyhľadanie sa pokúsi vložiť chýbajúci prebal a metadáta pre vybrané položky. Ak povolíte voľbu nižšie Rýchle vyhľadanie sa pokúsi vyhľadať a prepísať aj existujúce prebaly a/alebo metadáta.",
|
||||
"MessageBookshelfNoCollections": "Zatiaľ ste nevytvorili žiadnu zbierku",
|
||||
"MessageBookshelfNoCollectionsHelp": "Zbierky sú verejné. Všetci používatelia s prístupom do knižnice ich môžu vidieť.",
|
||||
"MessageBookshelfNoRSSFeeds": "Žiadne RSS zdroje nie sú otvorené",
|
||||
"MessageBookshelfNoResultsForFilter": "Žiadny výsledok filtrovania \"{0}: {1}\"",
|
||||
"MessageBookshelfNoResultsForQuery": "Žiadne výsledky dopytu",
|
||||
"MessageBookshelfNoSeries": "Nemáte žiadne série",
|
||||
"MessageChapterEndIsAfter": "Koniec kapitoly je až za koncom vašej audioknihy",
|
||||
"MessageChapterErrorFirstNotZero": "Prvá kapitola musí začínať na 0",
|
||||
"MessageChapterErrorStartGteDuration": "Neplatný čas začiatku musí byť menší ako celkové trvanie audioknihy",
|
||||
"MessageChapterErrorStartLtPrev": "Neplatný čas začiatku musí byť väčší alebo rovný času začiatku predchádzajúcej kapitoly",
|
||||
"MessageChapterStartIsAfter": "Začiatok kapitoly je až za koncom vašej audioknihy",
|
||||
"MessageCheckingCron": "Kontrola cron-u...",
|
||||
"MessageConfirmCloseFeed": "Ste si istý, že chcete zavrieť tento zdroj?",
|
||||
"MessageConfirmDeleteBackup": "Ste si istý, že chcete zmazať zálohu {0}?",
|
||||
"MessageConfirmDeleteDevice": "Ste si istý, že chcete zmazať zariadenie čítačky e-kníh \"{0}\"?"
|
||||
}
|
||||
|
||||
@@ -604,6 +604,7 @@
|
||||
"LabelSlug": "Slug",
|
||||
"LabelSortAscending": "Naraščajoče",
|
||||
"LabelSortDescending": "Padajoče",
|
||||
"LabelSortPubDate": "Razvrsti po datumu objave",
|
||||
"LabelStart": "Začetek",
|
||||
"LabelStartTime": "Čas začetka",
|
||||
"LabelStarted": "Začeto",
|
||||
@@ -1086,6 +1087,8 @@
|
||||
"ToastUnknownError": "Neznana napaka",
|
||||
"ToastUnlinkOpenIdFailed": "Prekinitev povezave uporabnika z OpenID ni uspela",
|
||||
"ToastUnlinkOpenIdSuccess": "Uporabnik je prekinil povezavo z OpenID",
|
||||
"ToastUploaderFilepathExistsError": "Datoteka s potjo \"{0}\" že obstaja na strežniku",
|
||||
"ToastUploaderItemExistsInSubdirectoryError": "Element \"{0}\" uporablja podmapo v poti za nalaganje.",
|
||||
"ToastUserDeleteFailed": "Brisanje uporabnika ni uspelo",
|
||||
"ToastUserDeleteSuccess": "Uporabnik je bil izbrisan",
|
||||
"ToastUserPasswordChangeSuccess": "Geslo je bilo uspešno spremenjeno",
|
||||
|
||||
@@ -32,6 +32,8 @@
|
||||
"ButtonEditChapters": "Redigera kapitel",
|
||||
"ButtonEditPodcast": "Redigera podcast",
|
||||
"ButtonEnable": "Aktivera",
|
||||
"ButtonFireAndFail": "Starta och Misslyckas",
|
||||
"ButtonFireOnTest": "Starta onTest händelse",
|
||||
"ButtonForceReScan": "Starta ny skanning",
|
||||
"ButtonFullPath": "Fullständig sökväg",
|
||||
"ButtonHide": "Dölj",
|
||||
@@ -102,6 +104,7 @@
|
||||
"ButtonStats": "Statistik",
|
||||
"ButtonSubmit": "Skicka",
|
||||
"ButtonTest": "Testa",
|
||||
"ButtonUnlinkOpenId": "Koppla från OpenID",
|
||||
"ButtonUpload": "Ladda upp",
|
||||
"ButtonUploadBackup": "Läs in säkerhetskopia",
|
||||
"ButtonUploadCover": "Ladda upp omslag",
|
||||
@@ -209,6 +212,7 @@
|
||||
"HeaderYearReview": "Sammanställning av {0}",
|
||||
"HeaderYourStats": "Din statistik",
|
||||
"LabelAbridged": "Förkortad version",
|
||||
"LabelAbridgedChecked": "Förkortad (kontrollerad)",
|
||||
"LabelAbridgedUnchecked": "Oavkortad (okontrollerad)",
|
||||
"LabelAccessibleBy": "Tillgänglig för",
|
||||
"LabelAccountType": "Kontotyp",
|
||||
@@ -249,7 +253,7 @@
|
||||
"LabelBackToUser": "Tillbaka till användaren",
|
||||
"LabelBackupAudioFiles": "Säkerhetskopiera ljudfiler",
|
||||
"LabelBackupLocation": "Plats för säkerhetskopia",
|
||||
"LabelBackupsEnableAutomaticBackups": "Aktivera automatisk säkerhetskopiering",
|
||||
"LabelBackupsEnableAutomaticBackups": "Automatisk säkerhetskopiering",
|
||||
"LabelBackupsEnableAutomaticBackupsHelp": "Säkerhetskopior sparas i \"/metadata/backups\"",
|
||||
"LabelBackupsMaxBackupSize": "Maximal storlek på säkerhetskopia i GigaByte (0 = obegränsad)",
|
||||
"LabelBackupsMaxBackupSizeHelp": "Som ett skydd mot en felaktig konfiguration kommer säkerhetskopior inte att genomföras om de överskrider den konfigurerade storleken.",
|
||||
@@ -271,6 +275,7 @@
|
||||
"LabelClosePlayer": "Stäng spelaren",
|
||||
"LabelCodec": "Codec",
|
||||
"LabelCollapseSeries": "Komprimera serier",
|
||||
"LabelCollapseSubSeries": "Komprimera underserier",
|
||||
"LabelCollection": "Samling",
|
||||
"LabelCollections": "Samlingar",
|
||||
"LabelComplete": "Komplett",
|
||||
@@ -339,6 +344,7 @@
|
||||
"LabelEpisodic": "Uppdelad i avsnitt",
|
||||
"LabelExample": "Exempel",
|
||||
"LabelExpandSeries": "Expandera serier",
|
||||
"LabelExpandSubSeries": "Expandera Underserier",
|
||||
"LabelExplicit": "Explicit version",
|
||||
"LabelExplicitChecked": "Explicit version (markerad)",
|
||||
"LabelExplicitUnchecked": "Ej Explicit version (ej markerad)",
|
||||
@@ -362,6 +368,7 @@
|
||||
"LabelFontItalic": "Kursiv",
|
||||
"LabelFontScale": "Skala på typsnitt",
|
||||
"LabelFontStrikethrough": "Genomstruken",
|
||||
"LabelFormat": "Format",
|
||||
"LabelFull": "Komplett",
|
||||
"LabelGenre": "Kategori",
|
||||
"LabelGenres": "Kategorier",
|
||||
@@ -434,6 +441,8 @@
|
||||
"LabelMissing": "Saknar",
|
||||
"LabelMissingEbook": "Saknar e-bok",
|
||||
"LabelMissingSupplementaryEbook": "Saknar kompletterande e-bok",
|
||||
"LabelMobileRedirectURIs": "Tillåtna mobila omdirigerings-URI:er",
|
||||
"LabelMobileRedirectURIsDescription": "Detta är en vitlista över giltiga omdirigerings-URI:er för mobila appar. Standard är <code>audiobookshelf://oauth</code>, som du kan radera eller komplettera med ytterligare URI:er för integrering av tredje-parts appar. Används ett asterisk (<code>*</code>) som enda inmatning tillåts alla URI:er.",
|
||||
"LabelMore": "Mer",
|
||||
"LabelMoreInfo": "Mer information",
|
||||
"LabelName": "Namn",
|
||||
@@ -544,7 +553,8 @@
|
||||
"LabelSettingsBookshelfViewHelp": "Bakgrund med ett utseende liknande en bokhylla i trä",
|
||||
"LabelSettingsChromecastSupport": "Stöd för Chromecast",
|
||||
"LabelSettingsDateFormat": "Datumformat",
|
||||
"LabelSettingsEnableWatcher": "Automatiskt upptäcka förändringar i biblioteket",
|
||||
"LabelSettingsEnableWatcher": "Upptäck automatiskt förändringar i biblioteket",
|
||||
"LabelSettingsEnableWatcherForLibrary": "Upptäck automatiskt förändringar i biblioteket",
|
||||
"LabelSettingsEnableWatcherHelp": "Aktiverar automatik att upptäcka när objekt<br>adderas, uppdateras eller raderas.<br>OBS: Kräver en omstart av servern",
|
||||
"LabelSettingsEpubsAllowScriptedContent": "Tillåt e-böcker i epubs-format som innehåller script",
|
||||
"LabelSettingsEpubsAllowScriptedContentHelp": "Tillåt att epub-filer får innehålla script.<br>Det rekommenderas att denna inställning är<br>avstängd när du inte litar på källan för epub-filerna.",
|
||||
@@ -819,12 +829,13 @@
|
||||
"MessageResetChaptersConfirm": "Är du säker på att du vill återställa alla kapitel och ångra de ändringarna du gjort?",
|
||||
"MessageRestoreBackupConfirm": "Är du säker på att du vill läsa in säkerhetskopian som skapades den",
|
||||
"MessageRestoreBackupWarning": "Att återställa en säkerhetskopia kommer att skriva över hela databasen som finns i /config och omslagsbilder i /metadata/items & /metadata/authors.<br /><br />Säkerhetskopior ändrar inte några filer i dina biblioteksmappar. Om du har aktiverat serverinställningar för att lagra omslagskonst och metadata i dina biblioteksmappar säkerhetskopieras eller skrivs de inte över.<br /><br />Alla klienter som använder din server kommer att uppdateras automatiskt.",
|
||||
"MessageScheduleLibraryScanNote": "För de flesta användare rekommenderas att denna funktion ej aktiveras. Istället bör funktionen som automatisk upptäcker ändringar av filerna vara aktiverad. För vissa filsystem (som t.ex. NFS) fungerar inte denna funktion. Då kan schemalagda skanningar av biblioteken användas istället.",
|
||||
"MessageScheduleLibraryScanNote": "För de flesta användare rekommenderas att denna funktion ej aktiveras. Istället bör funktionen som automatisk upptäcker ändringar i biblioteket vara aktiverad. För vissa filsystem (som t.ex. NFS) fungerar inte denna funktion. Då kan schemalagda skanningar av biblioteken användas istället.",
|
||||
"MessageScheduleRunEveryWeekdayAtTime": "Startar varje {0} klockan {1}",
|
||||
"MessageSearchResultsFor": "Sökresultat för",
|
||||
"MessageSelected": "{0} valda",
|
||||
"MessageServerCouldNotBeReached": "Servern kunde inte nås",
|
||||
"MessageSetChaptersFromTracksDescription": "Använd varje ljudfil som ett kapitel och ljudfilens namn som kapitlets rubrik",
|
||||
"MessageShareExpiresIn": "Upphör om {0}",
|
||||
"MessageStartPlaybackAtTime": "Starta uppspelning av \"{0}\" vid tidpunkt {1}?",
|
||||
"MessageTaskAudioFileNotWritable": "Det går inte att skriva till ljudfilen \"{0}\"",
|
||||
"MessageTaskCanceledByUser": "Uppgiften avslutades av användaren",
|
||||
@@ -909,6 +920,7 @@
|
||||
"ToastAuthorUpdateMerged": "Författaren sammanslagen",
|
||||
"ToastAuthorUpdateSuccess": "Författaren uppdaterad",
|
||||
"ToastAuthorUpdateSuccessNoImageFound": "Författaren uppdaterad (ingen bild hittad)",
|
||||
"ToastBackupAppliedSuccess": "Säkerhetskopian är importerad",
|
||||
"ToastBackupCreateFailed": "Det gick inte att skapa en säkerhetskopia",
|
||||
"ToastBackupCreateSuccess": "Säkerhetskopian har skapats",
|
||||
"ToastBackupDeleteFailed": "Det gick inte att radera säkerhetskopian",
|
||||
@@ -918,6 +930,7 @@
|
||||
"ToastBackupRestoreFailed": "Det gick inte att återställa säkerhetskopian",
|
||||
"ToastBackupUploadFailed": "Det gick inte att ladda upp säkerhetskopian",
|
||||
"ToastBackupUploadSuccess": "Säkerhetskopian uppladdad",
|
||||
"ToastBatchApplyDetailsToItemsSuccess": "Informationen har adderats till alla objekt",
|
||||
"ToastBatchQuickMatchStarted": "Snabbmatchning av {0} böcker har påbörjats!",
|
||||
"ToastBatchUpdateFailed": "Batchuppdateringen misslyckades",
|
||||
"ToastBatchUpdateSuccess": "Batchuppdateringen lyckades",
|
||||
@@ -948,6 +961,8 @@
|
||||
"ToastEpisodeDownloadQueueClearSuccess": "Kö för nedladdning av avsnitt har tömts",
|
||||
"ToastEpisodeUpdateSuccess": "{0} avsnitt uppdaterades",
|
||||
"ToastFailedToLoadData": "Misslyckades med att ladda data",
|
||||
"ToastFailedToMatch": "Misslyckades med att matcha",
|
||||
"ToastFailedToShare": "Misslyckades med att dela",
|
||||
"ToastFailedToUpdate": "Misslyckades med att uppdatera",
|
||||
"ToastInvalidImageUrl": "Felaktig URL-adress till omslagsbilden",
|
||||
"ToastInvalidMaxEpisodesToDownload": "Ogiltigt maximalt antal avsnitt att ladda ner",
|
||||
@@ -1030,6 +1045,7 @@
|
||||
"ToastSortingPrefixesUpdateSuccess": "{0} begrepp för sortering har uppdateras",
|
||||
"ToastTitleRequired": "En titel måste anges",
|
||||
"ToastUnknownError": "Ett okänt fel inträffade",
|
||||
"ToastUploaderFilepathExistsError": "En fil med namnet \"{0}\" finns redan på servern",
|
||||
"ToastUserDeleteFailed": "Misslyckades med att ta bort användaren",
|
||||
"ToastUserDeleteSuccess": "Användaren borttagen",
|
||||
"ToastUserPasswordChangeSuccess": "Lösenordet har ändrats",
|
||||
|
||||
@@ -47,6 +47,8 @@
|
||||
"ButtonLookup": "Sorgula",
|
||||
"ButtonManageTracks": "Parçaları Yönet",
|
||||
"ButtonMapChapterTitles": "Bölüm Başlıklarını Haritalandır",
|
||||
"ButtonMatchAllAuthors": "Bütün Yazarlarla Eşleştir",
|
||||
"ButtonMatchBooks": "Kitapları Eşleştir",
|
||||
"ButtonNevermind": "Vazgeç",
|
||||
"ButtonNext": "Sonraki",
|
||||
"ButtonNextChapter": "Sonraki Bölüm",
|
||||
@@ -66,6 +68,9 @@
|
||||
"ButtonPurgeItemsCache": "Öğenin Önbelleğini Temizle",
|
||||
"ButtonQueueAddItem": "Sıraya ekle",
|
||||
"ButtonQueueRemoveItem": "Sıradan çıkar",
|
||||
"ButtonQuickEmbed": "Hızlı Embed",
|
||||
"ButtonQuickEmbedMetadata": "Hızlı Embed Üstverisi",
|
||||
"ButtonQuickMatch": "Hızlı Eşleştirme",
|
||||
"ButtonReScan": "Yeniden Tara",
|
||||
"ButtonRead": "Oku",
|
||||
"ButtonReadLess": "Daha az göster",
|
||||
@@ -74,14 +79,37 @@
|
||||
"ButtonRemove": "Kaldır",
|
||||
"ButtonRemoveAll": "Hepsini Sil",
|
||||
"ButtonRemoveAllLibraryItems": "Bütün Kütüphane Öğelerini Sil",
|
||||
"ButtonRemoveFromContinueListening": "Dinlemeye Devam Etmekten Çıkar",
|
||||
"ButtonRemoveFromContinueReading": "Okumaya Devam Etmekten Çıkar",
|
||||
"ButtonRemoveSeriesFromContinueSeries": "Seriye Devam Et'ten Seriyi Çıkar",
|
||||
"ButtonReset": "Sıfırla",
|
||||
"ButtonResetToDefault": "Varsayılana sıfırla",
|
||||
"ButtonRestore": "Geri getir",
|
||||
"ButtonSave": "Kaydet",
|
||||
"ButtonSaveAndClose": "Kaydet & Kapat",
|
||||
"ButtonSaveTracklist": "Parça Listesini Kaydet",
|
||||
"ButtonScan": "Tara",
|
||||
"ButtonScanLibrary": "Kütüphaneyi Tara",
|
||||
"ButtonScrollLeft": "Sola Kaydır",
|
||||
"ButtonScrollRight": "Sağa Kaydır",
|
||||
"ButtonSearch": "Ara",
|
||||
"ButtonSeries": "Dizi",
|
||||
"ButtonSelectFolderPath": "Klasör Yolunu Seç",
|
||||
"ButtonSeries": "Seriler",
|
||||
"ButtonSubmit": "Gönder",
|
||||
"ButtonViewAll": "Tümünü Görüntüle",
|
||||
"ButtonYes": "Evet",
|
||||
"ErrorUploadFetchMetadataAPI": "Üst veriyi almakta hata",
|
||||
"ErrorUploadFetchMetadataNoResults": "Üstveri alınamadı - başlık ve(ya) yazarı güncellemeyi deneyin",
|
||||
"ErrorUploadLacksTitle": "Başlığa sahip olmalı",
|
||||
"HeaderAccount": "Hesap",
|
||||
"HeaderAddCustomMetadataProvider": "Özel Üstveri Sağlayıcısı Ekle",
|
||||
"HeaderAdvanced": "Gelişmiş",
|
||||
"HeaderAppriseNotificationSettings": "Bildirim Ayarlarının Haberini Ver",
|
||||
"HeaderAudioTracks": "Ses Kanalları",
|
||||
"HeaderAudiobookTools": "Sesli Kitap Dosya Yönetim Araçları",
|
||||
"HeaderAuthentication": "Kimlik Doğrulama",
|
||||
"HeaderBackups": "Yedeklemeler",
|
||||
"HeaderChangePassword": "Parolayı Değiştir",
|
||||
"HeaderChapters": "Bölümler",
|
||||
"HeaderCollection": "Koleksiyon",
|
||||
"HeaderCollectionItems": "Koleksiyon Öğeleri",
|
||||
@@ -89,37 +117,99 @@
|
||||
"HeaderEbookFiles": "Ebook Dosyaları",
|
||||
"HeaderEpisodes": "Bölümler",
|
||||
"HeaderEreaderSettings": "Ereader Ayarları",
|
||||
"HeaderFiles": "Dosyalar",
|
||||
"HeaderIgnoredFiles": "Görmezden Gelinen Dosyalar",
|
||||
"HeaderItemFiles": "Öğe Dosyaları",
|
||||
"HeaderItemMetadataUtils": "Öğe Üstveri Araçları",
|
||||
"HeaderLastListeningSession": "Son Dinleme Oturumu",
|
||||
"HeaderLatestEpisodes": "En son bölümler",
|
||||
"HeaderLibraries": "Kütüphaneler",
|
||||
"HeaderLibraryFiles": "Kütüphane Dosyaları",
|
||||
"HeaderLibraryStats": "Kütüphane İstatistikleri",
|
||||
"HeaderListeningSessions": "Dinleme Oturumları",
|
||||
"HeaderListeningStats": "Dinleme İstatistikleri",
|
||||
"HeaderLogin": "Giriş Yap",
|
||||
"HeaderLogs": "Günlükler",
|
||||
"HeaderManageGenres": "Türleri Yönet",
|
||||
"HeaderManageTags": "Etiketleri Yönet",
|
||||
"HeaderMapDetails": "Detayları haritalandır",
|
||||
"HeaderMatch": "Eşleştir",
|
||||
"HeaderMetadataOrderOfPrecedence": "Üstveri öncelik sırası",
|
||||
"HeaderMetadataToEmbed": "Gömülecek üstveri",
|
||||
"HeaderNewAccount": "Yeni Hesap",
|
||||
"HeaderNewLibrary": "Yeni Kütüphane",
|
||||
"HeaderNotificationCreate": "Bildirim Oluştur",
|
||||
"HeaderNotificationUpdate": "Güncelleme Bildirimi",
|
||||
"HeaderNotifications": "Bildirimler",
|
||||
"HeaderOpenRSSFeed": "RSS Akışını Aç",
|
||||
"HeaderOtherFiles": "Diğer Dosyalar",
|
||||
"HeaderPasswordAuthentication": "Parola Doğrulaması",
|
||||
"HeaderPermissions": "İzinler",
|
||||
"HeaderPlayerQueue": "Oynatıcı Sırası",
|
||||
"HeaderPlayerSettings": "Oynatıcı Ayarları",
|
||||
"HeaderPlaylist": "Oynatma listesi",
|
||||
"HeaderPlaylistItems": "Oynatma Listesi Öğeleri",
|
||||
"HeaderPodcastsToAdd": "Eklenecek Podcastler",
|
||||
"HeaderPreviewCover": "Kapak Önizlemesi",
|
||||
"HeaderRSSFeedGeneral": "RSS Detayları",
|
||||
"HeaderRSSFeedIsOpen": "RSS Akışı Açık",
|
||||
"HeaderRSSFeeds": "RSS Bültenleri",
|
||||
"HeaderRemoveEpisode": "Bölümü Kaldır",
|
||||
"HeaderRemoveEpisodes": "{0} Bölümü Kaldır",
|
||||
"HeaderSavedMediaProgress": "Kaydedilen Medya İlerleyişi",
|
||||
"HeaderSchedule": "Program",
|
||||
"HeaderSettings": "Ayarlar",
|
||||
"HeaderSleepTimer": "Uyku Zamanlayıcısı",
|
||||
"HeaderStatsMinutesListeningChart": "Dinlenilen Dakika (son 7 gün)",
|
||||
"HeaderStatsRecentSessions": "Geçmiş Oturumlar",
|
||||
"HeaderTableOfContents": "İçindekiler",
|
||||
"HeaderUpdateAuthor": "Yazarı Güncelle",
|
||||
"HeaderUpdateDetails": "Detayları Güncelle",
|
||||
"HeaderUpdateLibrary": "Kütüphaneyi Güncelle",
|
||||
"HeaderUsers": "Kullanıcılar",
|
||||
"HeaderYearReview": "{0} Yılına Bir Bakış",
|
||||
"HeaderYourStats": "İstatistiklerin",
|
||||
"LabelAccountType": "Hesap Türü",
|
||||
"LabelAccountTypeAdmin": "Yönetici",
|
||||
"LabelAccountTypeGuest": "Ziyaretçi",
|
||||
"LabelAccountTypeUser": "Kullanıcı",
|
||||
"LabelActivities": "Etkinlikler",
|
||||
"LabelActivity": "Etkinlik",
|
||||
"LabelAddToCollection": "Koleksiyona Ekle",
|
||||
"LabelAddToCollectionBatch": "Koleksiyona {0} Kitap Ekle",
|
||||
"LabelAddToPlaylist": "Oynatma Listesine Ekle",
|
||||
"LabelAddToPlaylistBatch": "Çalma Listesine {0} Öğe Ekle",
|
||||
"LabelAddedAt": "Eklenme Zamanı",
|
||||
"LabelAddedDate": "Eklendi {0}",
|
||||
"LabelAdminUsersOnly": "Yalnızca yönetici kullanıcılar",
|
||||
"LabelAll": "Hepsi",
|
||||
"LabelAllEpisodesDownloaded": "Tüm bölümler indirildi",
|
||||
"LabelAllUsers": "Tüm Kullanıcılar",
|
||||
"LabelAllUsersExcludingGuests": "Ziyaretçiler dışında tüm kullanıcılar",
|
||||
"LabelAllUsersIncludingGuests": "Ziyaretçiler dahil tüm kullanıcılar",
|
||||
"LabelAlreadyInYourLibrary": "Zaten kütüphanenizde var",
|
||||
"LabelApiToken": "API Token",
|
||||
"LabelAuthor": "Yazar",
|
||||
"LabelAuthorFirstLast": "Yazar (İlk Son)",
|
||||
"LabelAuthorLastFirst": "Yazar (Son, İlk)",
|
||||
"LabelAuthors": "Yazarlar",
|
||||
"LabelAutoDownloadEpisodes": "Bölümleri Otomatik Olarak İndir",
|
||||
"LabelBooks": "Kitaplar",
|
||||
"LabelChangePassword": "Şifreyi Değiştir",
|
||||
"LabelChapters": "Bölümler",
|
||||
"LabelClickForMoreInfo": "Daha fazla bilgi için tıklayın",
|
||||
"LabelClosePlayer": "Oynatıcıyı kapat",
|
||||
"LabelCollapseSeries": "Seriyi Daralt",
|
||||
"LabelComplete": "Tamamlandı",
|
||||
"LabelContinueListening": "Dinlemeye Devam Et",
|
||||
"LabelContinueReading": "Okumaya Devam Et",
|
||||
"LabelContinueSeries": "Seriye Devam Et",
|
||||
"LabelDeleteFromFileSystemCheckbox": "Dosya sisteminden sil (sadece veritabanından silmek için işareti kaldır)",
|
||||
"LabelDescription": "Açıklama",
|
||||
"LabelDeselectAll": "Tümünün Seçimini Kaldır",
|
||||
"LabelDevice": "Cihaz",
|
||||
"LabelDeviceInfo": "Cihaz Bilgisi",
|
||||
"LabelDirectory": "Dizin",
|
||||
"LabelDiscover": "Keşfet",
|
||||
"LabelDownload": "İndir",
|
||||
"LabelDuration": "Süre",
|
||||
@@ -179,6 +269,7 @@
|
||||
"LabelReadAgain": "Tekrar Oku",
|
||||
"LabelRecentlyAdded": "Yakınlarda Eklenmiş",
|
||||
"LabelSeason": "Sezon",
|
||||
"LabelSeries": "Seriler",
|
||||
"LabelSetEbookAsPrimary": "Birincil olarak ayarla",
|
||||
"LabelSetEbookAsSupplementary": "Yedek olarak ayarla",
|
||||
"LabelShowAll": "Hepsini Göster",
|
||||
@@ -205,5 +296,8 @@
|
||||
"LabelUnknown": "Bilinmeyen",
|
||||
"LabelUser": "Kullanıcı",
|
||||
"LabelUsername": "Kullanıcı Adı",
|
||||
"LabelYourBookmarks": "Yer İşaretleriniz"
|
||||
"LabelYearReviewHide": "Yıla Bakışı Sakla",
|
||||
"LabelYearReviewShow": "Yıla Bakışı Göster",
|
||||
"LabelYourBookmarks": "Yer İşaretleriniz",
|
||||
"LabelYourProgress": "Gelişiminiz"
|
||||
}
|
||||
|
||||
@@ -604,6 +604,7 @@
|
||||
"LabelSlug": "Назва",
|
||||
"LabelSortAscending": "По зростанню",
|
||||
"LabelSortDescending": "По спаданню",
|
||||
"LabelSortPubDate": "Сортувати дату публікації",
|
||||
"LabelStart": "Початок",
|
||||
"LabelStartTime": "Час початку",
|
||||
"LabelStarted": "Почато",
|
||||
@@ -704,6 +705,7 @@
|
||||
"LabelYourProgress": "Ваш прогрес",
|
||||
"MessageAddToPlayerQueue": "Додати до черги відтворення",
|
||||
"MessageAppriseDescription": "Щоб скористатися цією функцією, вам потрібно мати запущену <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> або API, що оброблятиме ті ж запити. <br />Аби надсилати сповіщення, URL-адреса API Apprise мусить бути повною, наприклад, якщо ваш API розміщено за адресою <code>http://192.168.1.1:8337</code>, то необхідно вказати адресу <code>http://192.168.1.1:8337/notify</code>.",
|
||||
"MessageAsinCheck": "Переконайтесь, що ви використовуєте ASIN з правильної регіональної Audible зони, а не з Amazon.",
|
||||
"MessageBackupsDescription": "Резервні копії містять користувачів, прогрес, подробиці елементів бібліотеки, налаштування сервера та зображення з <code>/metadata/items</code> та <code>/metadata/authors</code>. Резервні копії <strong>не</strong> містять жодних файлів з тек бібліотеки.",
|
||||
"MessageBackupsLocationEditNote": "Примітка: оновлення розташування резервної копії не переносить та не змінює існуючих копій",
|
||||
"MessageBackupsLocationNoEditNote": "Примітка: розташування резервної копії встановлюється за допомогою змінної середовища та не може бути змінене тут.",
|
||||
@@ -722,6 +724,7 @@
|
||||
"MessageChapterErrorStartGteDuration": "Час початку мусить бути меншим за тривалість аудіокниги",
|
||||
"MessageChapterErrorStartLtPrev": "Неприпустимий час початку, має бути більшим за час початку попередньої глави",
|
||||
"MessageChapterStartIsAfter": "Початок глави знаходиться після закінчення книги",
|
||||
"MessageChaptersNotFound": "Розділи не знайдені",
|
||||
"MessageCheckingCron": "Перевірка планувальника...",
|
||||
"MessageConfirmCloseFeed": "Ви дійсно бажаєте закрити цей канал?",
|
||||
"MessageConfirmDeleteBackup": "Ви дійсно бажаєте видалити резервну копію за {0}?",
|
||||
@@ -778,6 +781,7 @@
|
||||
"MessageForceReScanDescription": "Просканує усі файли заново, неначе вперше. ID3-мітки, файли OPF та текстові файли будуть проскановані як нові.",
|
||||
"MessageImportantNotice": "Важливе повідомлення!",
|
||||
"MessageInsertChapterBelow": "Введіть главу нижче",
|
||||
"MessageInvalidAsin": "Невірний ASIN",
|
||||
"MessageItemsSelected": "Вибрано елементів: {0}",
|
||||
"MessageItemsUpdated": "Оновлено елементів: {0}",
|
||||
"MessageJoinUsOn": "Приєднуйтесь до",
|
||||
@@ -811,7 +815,7 @@
|
||||
"MessageNoItems": "Елементи відсутні",
|
||||
"MessageNoItemsFound": "Елементів не знайдено",
|
||||
"MessageNoListeningSessions": "Сеанси прослуховування відсутні",
|
||||
"MessageNoLogs": "Журнал порожній",
|
||||
"MessageNoLogs": "Немає журналів",
|
||||
"MessageNoMediaProgress": "Прогрес відсутній",
|
||||
"MessageNoNotifications": "Сповіщення відсутні",
|
||||
"MessageNoPodcastFeed": "Невірний подкаст: Немає каналу",
|
||||
@@ -1086,6 +1090,8 @@
|
||||
"ToastUnknownError": "Невідома помилка",
|
||||
"ToastUnlinkOpenIdFailed": "Не вдалося відв'язати користувача від OpenID",
|
||||
"ToastUnlinkOpenIdSuccess": "Користувача відв'язано від OpenID",
|
||||
"ToastUploaderFilepathExistsError": "Шлях до файлу \"{0}\" уже існує на сервері",
|
||||
"ToastUploaderItemExistsInSubdirectoryError": "Елемент \"{0}\" використовує підкаталог шляху завантаження.",
|
||||
"ToastUserDeleteFailed": "Не вдалося видалити користувача",
|
||||
"ToastUserDeleteSuccess": "Користувача видалено",
|
||||
"ToastUserPasswordChangeSuccess": "Пароль успішно змінено",
|
||||
|
||||
@@ -229,6 +229,7 @@
|
||||
"LabelAddedDate": "已添加 {0}",
|
||||
"LabelAdminUsersOnly": "仅限管理员用户",
|
||||
"LabelAll": "全部",
|
||||
"LabelAllEpisodesDownloaded": "所有剧集已下载",
|
||||
"LabelAllUsers": "所有用户",
|
||||
"LabelAllUsersExcludingGuests": "除访客外的所有用户",
|
||||
"LabelAllUsersIncludingGuests": "包括访客的所有用户",
|
||||
@@ -603,6 +604,7 @@
|
||||
"LabelSlug": "Slug",
|
||||
"LabelSortAscending": "升序",
|
||||
"LabelSortDescending": "降序",
|
||||
"LabelSortPubDate": "按出版日期排序",
|
||||
"LabelStart": "开始",
|
||||
"LabelStartTime": "开始时间",
|
||||
"LabelStarted": "开始于",
|
||||
@@ -703,6 +705,7 @@
|
||||
"LabelYourProgress": "你的进度",
|
||||
"MessageAddToPlayerQueue": "添加到播放队列",
|
||||
"MessageAppriseDescription": "要使用此功能,你需要运行一个 <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> 实例或一个可以处理这些相同请求的 API. <br />Apprise API Url 应该是发送通知的完整 URL 路径, 例如: 如果你的 API 实例运行在 <code>http://192.168.1.1:8337</code>, 那么你可以输入 <code>http://192.168.1.1:8337/notify</code>.",
|
||||
"MessageAsinCheck": "确保你使用的 ASIN 来自正确的 Audible 地区, 而不是亚马逊.",
|
||||
"MessageBackupsDescription": "备份包括用户, 用户进度, 媒体库项目详细信息, 服务器设置和图像, 存储在 <code>/metadata/items</code> & <code>/metadata/authors</code>. 备份不包括存储在你的媒体库文件夹中的任何文件.",
|
||||
"MessageBackupsLocationEditNote": "注意: 更新备份位置不会移动或修改现有备份",
|
||||
"MessageBackupsLocationNoEditNote": "注意: 备份位置是通过环境变量设置的, 不能在此处更改.",
|
||||
@@ -721,6 +724,7 @@
|
||||
"MessageChapterErrorStartGteDuration": "无效的开始时间, 必须小于有声读物持续时间",
|
||||
"MessageChapterErrorStartLtPrev": "无效的开始时间, 必须大于或等于上一章节的开始时间",
|
||||
"MessageChapterStartIsAfter": "章节开始是在有声读物结束之后",
|
||||
"MessageChaptersNotFound": "未找到章节",
|
||||
"MessageCheckingCron": "检查计划任务...",
|
||||
"MessageConfirmCloseFeed": "你确定要关闭此订阅源吗?",
|
||||
"MessageConfirmDeleteBackup": "你确定要删除备份 {0}?",
|
||||
@@ -777,6 +781,7 @@
|
||||
"MessageForceReScanDescription": "将像重新扫描一样再次扫描所有文件. 音频文件 ID3 标签, OPF 文件和文本文件将被扫描为新文件.",
|
||||
"MessageImportantNotice": "重要通知!",
|
||||
"MessageInsertChapterBelow": "在下面插入章节",
|
||||
"MessageInvalidAsin": "无效的 ASIN",
|
||||
"MessageItemsSelected": "已选定 {0} 个项目",
|
||||
"MessageItemsUpdated": "已更新 {0} 个项目",
|
||||
"MessageJoinUsOn": "加入我们",
|
||||
@@ -953,6 +958,7 @@
|
||||
"ToastBackupRestoreFailed": "备份还原失败",
|
||||
"ToastBackupUploadFailed": "上传备份失败",
|
||||
"ToastBackupUploadSuccess": "备份已上传",
|
||||
"ToastBatchApplyDetailsToItemsSuccess": "应用于项目的详细信息",
|
||||
"ToastBatchDeleteFailed": "批量删除失败",
|
||||
"ToastBatchDeleteSuccess": "批量删除成功",
|
||||
"ToastBatchQuickMatchFailed": "批量快速匹配失败!",
|
||||
@@ -1065,6 +1071,7 @@
|
||||
"ToastSelectAtLeastOneUser": "至少选择一位用户",
|
||||
"ToastSendEbookToDeviceFailed": "发送电子书到设备失败",
|
||||
"ToastSendEbookToDeviceSuccess": "电子书已经发送到设备 \"{0}\"",
|
||||
"ToastSeriesSubmitFailedSameName": "无法添加两个同名系列",
|
||||
"ToastSeriesUpdateFailed": "更新系列失败",
|
||||
"ToastSeriesUpdateSuccess": "系列已更新",
|
||||
"ToastServerSettingsUpdateSuccess": "服务器设置已更新",
|
||||
@@ -1083,6 +1090,8 @@
|
||||
"ToastUnknownError": "未知错误",
|
||||
"ToastUnlinkOpenIdFailed": "无法取消用户与 OpenID 的关联",
|
||||
"ToastUnlinkOpenIdSuccess": "用户已取消与 OpenID 的关联",
|
||||
"ToastUploaderFilepathExistsError": "文件路径 \"{0}\" 在服务器上已存在",
|
||||
"ToastUploaderItemExistsInSubdirectoryError": "项目 \"{0}\" 正在使用上传路径的子目录。",
|
||||
"ToastUserDeleteFailed": "删除用户失败",
|
||||
"ToastUserDeleteSuccess": "用户已删除",
|
||||
"ToastUserPasswordChangeSuccess": "密码修改成功",
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "audiobookshelf",
|
||||
"version": "2.20.0",
|
||||
"version": "2.21.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "audiobookshelf",
|
||||
"version": "2.20.0",
|
||||
"version": "2.21.0",
|
||||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
"axios": "^0.27.2",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "audiobookshelf",
|
||||
"version": "2.20.0",
|
||||
"version": "2.21.0",
|
||||
"buildNumber": 1,
|
||||
"description": "Self-hosted audiobook and podcast server",
|
||||
"main": "index.js",
|
||||
|
||||
@@ -84,6 +84,42 @@ class SocketAuthority {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits event with library item to all clients that can access the library item
|
||||
* Note: Emits toOldJSONExpanded()
|
||||
*
|
||||
* @param {string} evt
|
||||
* @param {import('./models/LibraryItem')} libraryItem
|
||||
*/
|
||||
libraryItemEmitter(evt, libraryItem) {
|
||||
for (const socketId in this.clients) {
|
||||
if (this.clients[socketId].user?.checkCanAccessLibraryItem(libraryItem)) {
|
||||
this.clients[socketId].socket.emit(evt, libraryItem.toOldJSONExpanded())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits event with library items to all clients that can access the library items
|
||||
* Note: Emits toOldJSONExpanded()
|
||||
*
|
||||
* @param {string} evt
|
||||
* @param {import('./models/LibraryItem')[]} libraryItems
|
||||
*/
|
||||
libraryItemsEmitter(evt, libraryItems) {
|
||||
for (const socketId in this.clients) {
|
||||
if (this.clients[socketId].user) {
|
||||
const libraryItemsAccessibleToUser = libraryItems.filter((li) => this.clients[socketId].user.checkCanAccessLibraryItem(li))
|
||||
if (libraryItemsAccessibleToUser.length) {
|
||||
this.clients[socketId].socket.emit(
|
||||
evt,
|
||||
libraryItemsAccessibleToUser.map((li) => li.toOldJSONExpanded())
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the Socket.IO server and disconnect all clients
|
||||
*
|
||||
|
||||
@@ -152,10 +152,7 @@ class AuthorController {
|
||||
for (const libraryItem of libraryItems) {
|
||||
await libraryItem.saveMetadataFile()
|
||||
}
|
||||
SocketAuthority.emitter(
|
||||
'items_updated',
|
||||
libraryItems.map((li) => li.toOldJSONExpanded())
|
||||
)
|
||||
SocketAuthority.libraryItemsEmitter('items_updated', libraryItems)
|
||||
}
|
||||
|
||||
// Remove old author
|
||||
@@ -210,10 +207,7 @@ class AuthorController {
|
||||
}
|
||||
|
||||
if (libraryItems.length) {
|
||||
SocketAuthority.emitter(
|
||||
'items_updated',
|
||||
libraryItems.map((li) => li.toOldJSONExpanded())
|
||||
)
|
||||
SocketAuthority.libraryItemsEmitter('items_updated', libraryItems)
|
||||
}
|
||||
} else {
|
||||
numBooksForAuthor = await Database.bookAuthorModel.getCountForAuthor(req.author.id)
|
||||
|
||||
@@ -1188,10 +1188,7 @@ class LibraryController {
|
||||
}
|
||||
|
||||
if (itemsUpdated.length) {
|
||||
SocketAuthority.emitter(
|
||||
'items_updated',
|
||||
itemsUpdated.map((li) => li.toOldJSONExpanded())
|
||||
)
|
||||
SocketAuthority.libraryItemsEmitter('items_updated', itemsUpdated)
|
||||
}
|
||||
|
||||
res.json({
|
||||
@@ -1232,10 +1229,7 @@ class LibraryController {
|
||||
}
|
||||
|
||||
if (itemsUpdated.length) {
|
||||
SocketAuthority.emitter(
|
||||
'items_updated',
|
||||
itemsUpdated.map((li) => li.toOldJSONExpanded())
|
||||
)
|
||||
SocketAuthority.libraryItemsEmitter('items_updated', itemsUpdated)
|
||||
}
|
||||
|
||||
res.json({
|
||||
|
||||
@@ -253,7 +253,7 @@ class LibraryItemController {
|
||||
}
|
||||
|
||||
Logger.debug(`[LibraryItemController] Updated library item media ${req.libraryItem.media.title}`)
|
||||
SocketAuthority.emitter('item_updated', req.libraryItem.toOldJSONExpanded())
|
||||
SocketAuthority.libraryItemEmitter('item_updated', req.libraryItem)
|
||||
}
|
||||
res.json({
|
||||
updated: hasUpdates,
|
||||
@@ -300,7 +300,7 @@ class LibraryItemController {
|
||||
req.libraryItem.changed('updatedAt', true)
|
||||
await req.libraryItem.save()
|
||||
|
||||
SocketAuthority.emitter('item_updated', req.libraryItem.toOldJSONExpanded())
|
||||
SocketAuthority.libraryItemEmitter('item_updated', req.libraryItem)
|
||||
res.json({
|
||||
success: true,
|
||||
cover: result.cover
|
||||
@@ -332,7 +332,7 @@ class LibraryItemController {
|
||||
req.libraryItem.changed('updatedAt', true)
|
||||
await req.libraryItem.save()
|
||||
|
||||
SocketAuthority.emitter('item_updated', req.libraryItem.toOldJSONExpanded())
|
||||
SocketAuthority.libraryItemEmitter('item_updated', req.libraryItem)
|
||||
}
|
||||
res.json({
|
||||
success: true,
|
||||
@@ -358,7 +358,7 @@ class LibraryItemController {
|
||||
|
||||
await CacheManager.purgeCoverCache(req.libraryItem.id)
|
||||
|
||||
SocketAuthority.emitter('item_updated', req.libraryItem.toOldJSONExpanded())
|
||||
SocketAuthority.libraryItemEmitter('item_updated', req.libraryItem)
|
||||
}
|
||||
|
||||
res.sendStatus(200)
|
||||
@@ -485,7 +485,7 @@ class LibraryItemController {
|
||||
req.libraryItem.media.changed('audioFiles', true)
|
||||
await req.libraryItem.media.save()
|
||||
|
||||
SocketAuthority.emitter('item_updated', req.libraryItem.toOldJSONExpanded())
|
||||
SocketAuthority.libraryItemEmitter('item_updated', req.libraryItem)
|
||||
res.json(req.libraryItem.toOldJSON())
|
||||
}
|
||||
|
||||
@@ -663,7 +663,7 @@ class LibraryItemController {
|
||||
await libraryItem.saveMetadataFile()
|
||||
|
||||
Logger.debug(`[LibraryItemController] Updated library item media "${libraryItem.media.title}"`)
|
||||
SocketAuthority.emitter('item_updated', libraryItem.toOldJSONExpanded())
|
||||
SocketAuthority.libraryItemEmitter('item_updated', libraryItem)
|
||||
itemsUpdated++
|
||||
}
|
||||
}
|
||||
@@ -834,8 +834,8 @@ class LibraryItemController {
|
||||
}
|
||||
|
||||
if (req.libraryItem.isMissing || !req.libraryItem.isBook || !req.libraryItem.media.includedAudioFiles.length) {
|
||||
Logger.error(`[LibraryItemController] Invalid library item`)
|
||||
return res.sendStatus(500)
|
||||
Logger.error(`[LibraryItemController] getMetadataObject: Invalid library item "${req.libraryItem.media.title}"`)
|
||||
return res.sendStatus(400)
|
||||
}
|
||||
|
||||
res.json(this.audioMetadataManager.getMetadataObjectForApi(req.libraryItem))
|
||||
@@ -894,7 +894,7 @@ class LibraryItemController {
|
||||
|
||||
await req.libraryItem.saveMetadataFile()
|
||||
|
||||
SocketAuthority.emitter('item_updated', req.libraryItem.toOldJSONExpanded())
|
||||
SocketAuthority.libraryItemEmitter('item_updated', req.libraryItem)
|
||||
}
|
||||
|
||||
res.json({
|
||||
@@ -1005,7 +1005,7 @@ class LibraryItemController {
|
||||
|
||||
await req.libraryItem.save()
|
||||
|
||||
SocketAuthority.emitter('item_updated', req.libraryItem.toOldJSONExpanded())
|
||||
SocketAuthority.libraryItemEmitter('item_updated', req.libraryItem)
|
||||
res.sendStatus(200)
|
||||
}
|
||||
|
||||
@@ -1153,7 +1153,7 @@ class LibraryItemController {
|
||||
|
||||
await req.libraryItem.save()
|
||||
|
||||
SocketAuthority.emitter('item_updated', req.libraryItem.toOldJSONExpanded())
|
||||
SocketAuthority.libraryItemEmitter('item_updated', req.libraryItem)
|
||||
res.sendStatus(200)
|
||||
}
|
||||
|
||||
|
||||
@@ -37,25 +37,31 @@ class MiscController {
|
||||
Logger.warn(`User "${req.user.username}" attempted to upload without permission`)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
if (!req.files) {
|
||||
if (!req.files || !Object.values(req.files).length) {
|
||||
Logger.error('Invalid request, no files')
|
||||
return res.sendStatus(400)
|
||||
}
|
||||
|
||||
const files = Object.values(req.files)
|
||||
const { title, author, series, folder: folderId, library: libraryId } = req.body
|
||||
let { title, author, series, folder: folderId, library: libraryId } = req.body
|
||||
// Validate request body
|
||||
if (!libraryId || !folderId || typeof libraryId !== 'string' || typeof folderId !== 'string' || !title || typeof title !== 'string') {
|
||||
return res.status(400).send('Invalid request body')
|
||||
}
|
||||
if (!series || typeof series !== 'string') {
|
||||
series = null
|
||||
}
|
||||
if (!author || typeof author !== 'string') {
|
||||
author = null
|
||||
}
|
||||
|
||||
const library = await Database.libraryModel.findByIdWithFolders(libraryId)
|
||||
if (!library) {
|
||||
return res.status(404).send(`Library not found with id ${libraryId}`)
|
||||
return res.status(404).send('Library not found')
|
||||
}
|
||||
const folder = library.libraryFolders.find((fold) => fold.id === folderId)
|
||||
if (!folder) {
|
||||
return res.status(404).send(`Folder not found with id ${folderId} in library ${library.name}`)
|
||||
}
|
||||
|
||||
if (!files.length || !title) {
|
||||
return res.status(500).send(`Invalid post data`)
|
||||
return res.status(404).send('Folder not found')
|
||||
}
|
||||
|
||||
// Podcasts should only be one folder deep
|
||||
@@ -216,7 +222,7 @@ class MiscController {
|
||||
|
||||
// Update nameIgnorePrefix column on series
|
||||
const allSeries = await Database.seriesModel.findAll({
|
||||
attributes: ['id', 'name', 'nameIgnorePrefix']
|
||||
attributes: ['id', 'name', 'nameIgnorePrefix', 'libraryId']
|
||||
})
|
||||
const bulkUpdateSeries = []
|
||||
allSeries.forEach((series) => {
|
||||
@@ -224,6 +230,8 @@ class MiscController {
|
||||
if (nameIgnorePrefix !== series.nameIgnorePrefix) {
|
||||
bulkUpdateSeries.push({
|
||||
id: series.id,
|
||||
name: series.name,
|
||||
libraryId: series.libraryId,
|
||||
nameIgnorePrefix
|
||||
})
|
||||
}
|
||||
@@ -343,7 +351,7 @@ class MiscController {
|
||||
})
|
||||
await libraryItem.saveMetadataFile()
|
||||
|
||||
SocketAuthority.emitter('item_updated', libraryItem.toOldJSONExpanded())
|
||||
SocketAuthority.libraryItemEmitter('item_updated', libraryItem)
|
||||
numItemsUpdated++
|
||||
}
|
||||
}
|
||||
@@ -386,7 +394,7 @@ class MiscController {
|
||||
})
|
||||
await libraryItem.saveMetadataFile()
|
||||
|
||||
SocketAuthority.emitter('item_updated', libraryItem.toOldJSONExpanded())
|
||||
SocketAuthority.libraryItemEmitter('item_updated', libraryItem)
|
||||
numItemsUpdated++
|
||||
}
|
||||
|
||||
@@ -481,7 +489,7 @@ class MiscController {
|
||||
})
|
||||
await libraryItem.saveMetadataFile()
|
||||
|
||||
SocketAuthority.emitter('item_updated', libraryItem.toOldJSONExpanded())
|
||||
SocketAuthority.libraryItemEmitter('item_updated', libraryItem)
|
||||
numItemsUpdated++
|
||||
}
|
||||
}
|
||||
@@ -524,7 +532,7 @@ class MiscController {
|
||||
})
|
||||
await libraryItem.saveMetadataFile()
|
||||
|
||||
SocketAuthority.emitter('item_updated', libraryItem.toOldJSONExpanded())
|
||||
SocketAuthority.libraryItemEmitter('item_updated', libraryItem)
|
||||
numItemsUpdated++
|
||||
}
|
||||
|
||||
|
||||
@@ -161,7 +161,7 @@ class PodcastController {
|
||||
}
|
||||
}
|
||||
|
||||
SocketAuthority.emitter('item_added', newLibraryItem.toOldJSONExpanded())
|
||||
SocketAuthority.libraryItemEmitter('item_added', newLibraryItem)
|
||||
|
||||
res.json(newLibraryItem.toOldJSONExpanded())
|
||||
|
||||
@@ -379,7 +379,7 @@ class PodcastController {
|
||||
const overrideDetails = req.query.override === '1'
|
||||
const episodesUpdated = await Scanner.quickMatchPodcastEpisodes(req.libraryItem, { overrideDetails })
|
||||
if (episodesUpdated) {
|
||||
SocketAuthority.emitter('item_updated', req.libraryItem.toOldJSONExpanded())
|
||||
SocketAuthority.libraryItemEmitter('item_updated', req.libraryItem)
|
||||
}
|
||||
|
||||
res.json({
|
||||
@@ -418,7 +418,7 @@ class PodcastController {
|
||||
Logger.info(`[PodcastController] Updated episode "${episode.title}" keys`, episode.changed())
|
||||
await episode.save()
|
||||
|
||||
SocketAuthority.emitter('item_updated', req.libraryItem.toOldJSONExpanded())
|
||||
SocketAuthority.libraryItemEmitter('item_updated', req.libraryItem)
|
||||
} else {
|
||||
Logger.info(`[PodcastController] No changes to episode "${episode.title}"`)
|
||||
}
|
||||
@@ -504,7 +504,7 @@ class PodcastController {
|
||||
req.libraryItem.media.numEpisodes = req.libraryItem.media.podcastEpisodes.length
|
||||
await req.libraryItem.media.save()
|
||||
|
||||
SocketAuthority.emitter('item_updated', req.libraryItem.toOldJSONExpanded())
|
||||
SocketAuthority.libraryItemEmitter('item_updated', req.libraryItem)
|
||||
res.json(req.libraryItem.toOldJSON())
|
||||
}
|
||||
|
||||
|
||||
@@ -108,12 +108,12 @@ class SearchController {
|
||||
async findChapters(req, res) {
|
||||
const asin = req.query.asin
|
||||
if (!isValidASIN(asin.toUpperCase())) {
|
||||
return res.json({ error: 'Invalid ASIN' })
|
||||
return res.json({ error: 'Invalid ASIN', stringKey: 'MessageInvalidAsin' })
|
||||
}
|
||||
const region = (req.query.region || 'us').toLowerCase()
|
||||
const chapterData = await BookFinder.findChapters(asin, region)
|
||||
if (!chapterData) {
|
||||
return res.json({ error: 'Chapters not found' })
|
||||
return res.json({ error: 'Chapters not found', stringKey: 'MessageChaptersNotFound' })
|
||||
}
|
||||
res.json(chapterData)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
const Path = require('path')
|
||||
const { Request, Response, NextFunction } = require('express')
|
||||
const Logger = require('../Logger')
|
||||
const Database = require('../Database')
|
||||
const { toNumber, isUUID } = require('../utils/index')
|
||||
const { getAudioMimeTypeFromExtname, encodeUriPath } = require('../utils/fileUtils')
|
||||
|
||||
const ShareManager = require('../managers/ShareManager')
|
||||
|
||||
@@ -266,6 +268,51 @@ class SessionController {
|
||||
this.playbackSessionManager.syncLocalSessionsRequest(req, res)
|
||||
}
|
||||
|
||||
/**
|
||||
* GET: /public/session/:id/track/:index
|
||||
* While a session is open, this endpoint can be used to stream the audio track
|
||||
*
|
||||
* @this {import('../routers/PublicRouter')}
|
||||
*
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async getTrack(req, res) {
|
||||
const audioTrackIndex = toNumber(req.params.index, null)
|
||||
if (audioTrackIndex === null) {
|
||||
Logger.error(`[SessionController] Invalid audio track index "${req.params.index}"`)
|
||||
return res.sendStatus(400)
|
||||
}
|
||||
|
||||
const playbackSession = this.playbackSessionManager.getSession(req.params.id)
|
||||
if (!playbackSession) {
|
||||
Logger.error(`[SessionController] Unable to find playback session with id=${req.params.id}`)
|
||||
return res.sendStatus(404)
|
||||
}
|
||||
|
||||
const audioTrack = playbackSession.audioTracks.find((t) => t.index === audioTrackIndex)
|
||||
if (!audioTrack) {
|
||||
Logger.error(`[SessionController] Unable to find audio track with index=${audioTrackIndex}`)
|
||||
return res.sendStatus(404)
|
||||
}
|
||||
|
||||
const user = await Database.userModel.getUserById(playbackSession.userId)
|
||||
Logger.debug(`[SessionController] Serving audio track ${audioTrack.index} for session "${req.params.id}" belonging to user "${user.username}"`)
|
||||
|
||||
if (global.XAccel) {
|
||||
const encodedURI = encodeUriPath(global.XAccel + audioTrack.metadata.path)
|
||||
Logger.debug(`Use X-Accel to serve static file ${encodedURI}`)
|
||||
return res.status(204).header({ 'X-Accel-Redirect': encodedURI }).send()
|
||||
}
|
||||
|
||||
// Express does not set the correct mimetype for m4b files so use our defined mimetypes if available
|
||||
const audioMimeType = getAudioMimeTypeFromExtname(Path.extname(audioTrack.metadata.path))
|
||||
if (audioMimeType) {
|
||||
res.setHeader('Content-Type', audioMimeType)
|
||||
}
|
||||
res.sendFile(audioTrack.metadata.path)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
|
||||
@@ -48,6 +48,7 @@ class ToolsController {
|
||||
}
|
||||
|
||||
const options = req.query || {}
|
||||
Logger.info(`[ToolsController] encodeM4b: Starting audiobook merge for "${req.libraryItem.media.title}" with options: ${JSON.stringify(options)}`)
|
||||
this.abMergeManager.startAudiobookMerge(req.user.id, req.libraryItem, options)
|
||||
|
||||
res.sendStatus(200)
|
||||
|
||||
@@ -26,6 +26,12 @@ class PlaybackSessionManager {
|
||||
this.sessions = []
|
||||
}
|
||||
|
||||
/**
|
||||
* Get open session by id
|
||||
*
|
||||
* @param {string} sessionId
|
||||
* @returns {PlaybackSession}
|
||||
*/
|
||||
getSession(sessionId) {
|
||||
return this.sessions.find((s) => s.id === sessionId)
|
||||
}
|
||||
@@ -175,6 +181,25 @@ class PlaybackSessionManager {
|
||||
// New session from local
|
||||
session = new PlaybackSession(sessionJson)
|
||||
session.deviceInfo = deviceInfo
|
||||
|
||||
if (session.mediaMetadata == null) {
|
||||
session.mediaMetadata = {}
|
||||
}
|
||||
|
||||
// Populate mediaMetadata with the current library items metadata for any keys not set by client
|
||||
const libraryItemMediaMetadata = libraryItem.media.oldMetadataToJSON()
|
||||
for (const key in libraryItemMediaMetadata) {
|
||||
if (session.mediaMetadata[key] === undefined) {
|
||||
session.mediaMetadata[key] = libraryItemMediaMetadata[key]
|
||||
}
|
||||
}
|
||||
|
||||
if (session.displayTitle == null || session.displayTitle === '') {
|
||||
session.displayTitle = libraryItem.title
|
||||
}
|
||||
if (session.displayAuthor == null || session.displayAuthor === '') {
|
||||
session.displayAuthor = libraryItem.authorNamesFirstLast
|
||||
}
|
||||
session.duration = libraryItem.media.getPlaybackDuration(sessionJson.episodeId)
|
||||
|
||||
Logger.debug(`[PlaybackSessionManager] Inserting new session for "${session.displayTitle}" (${session.id})`)
|
||||
|
||||
@@ -211,6 +211,14 @@ class PodcastManager {
|
||||
const podcastEpisode = await Database.podcastEpisodeModel.createFromRssPodcastEpisode(this.currentDownload.rssPodcastEpisode, libraryItem.media.id, audioFile)
|
||||
|
||||
libraryItem.libraryFiles.push(libraryFile.toJSON())
|
||||
// Re-calculating library item size because this wasnt being updated properly for podcasts in v2.20.0 and below
|
||||
let libraryItemSize = 0
|
||||
libraryItem.libraryFiles.forEach((lf) => {
|
||||
if (lf.metadata.size && !isNaN(lf.metadata.size)) {
|
||||
libraryItemSize += Number(lf.metadata.size)
|
||||
}
|
||||
})
|
||||
libraryItem.size = libraryItemSize
|
||||
libraryItem.changed('libraryFiles', true)
|
||||
|
||||
libraryItem.media.podcastEpisodes.push(podcastEpisode)
|
||||
@@ -246,7 +254,7 @@ class PodcastManager {
|
||||
await libraryItem.media.save()
|
||||
}
|
||||
|
||||
SocketAuthority.emitter('item_updated', libraryItem.toOldJSONExpanded())
|
||||
SocketAuthority.libraryItemEmitter('item_updated', libraryItem)
|
||||
const podcastEpisodeExpanded = podcastEpisode.toOldJSONExpanded(libraryItem.id)
|
||||
podcastEpisodeExpanded.libraryItem = libraryItem.toOldJSONExpanded()
|
||||
SocketAuthority.emitter('episode_added', podcastEpisodeExpanded)
|
||||
@@ -359,7 +367,7 @@ class PodcastManager {
|
||||
libraryItem.changed('updatedAt', true)
|
||||
await libraryItem.save()
|
||||
|
||||
SocketAuthority.emitter('item_updated', libraryItem.toOldJSONExpanded())
|
||||
SocketAuthority.libraryItemEmitter('item_updated', libraryItem)
|
||||
|
||||
return libraryItem.media.autoDownloadEpisodes
|
||||
}
|
||||
@@ -417,7 +425,7 @@ class PodcastManager {
|
||||
libraryItem.changed('updatedAt', true)
|
||||
await libraryItem.save()
|
||||
|
||||
SocketAuthority.emitter('item_updated', libraryItem.toOldJSONExpanded())
|
||||
SocketAuthority.libraryItemEmitter('item_updated', libraryItem)
|
||||
|
||||
return newEpisodes || []
|
||||
}
|
||||
@@ -704,7 +712,7 @@ class PodcastManager {
|
||||
}
|
||||
}
|
||||
|
||||
SocketAuthority.emitter('item_added', newLibraryItem.toOldJSONExpanded())
|
||||
SocketAuthority.libraryItemEmitter('item_added', newLibraryItem)
|
||||
|
||||
// Turn on podcast auto download cron if not already on
|
||||
if (newLibraryItem.media.autoDownloadEpisodes) {
|
||||
|
||||
@@ -215,9 +215,13 @@ class FeedEpisode extends Model {
|
||||
* @returns {Promise<FeedEpisode[]>}
|
||||
*/
|
||||
static async createFromBooks(books, feed, slug, transaction) {
|
||||
const earliestLibraryItemCreatedAt = books.reduce((earliest, book) => {
|
||||
return book.libraryItem.createdAt < earliest.libraryItem.createdAt ? book : earliest
|
||||
}).libraryItem.createdAt
|
||||
// This is never null unless the books array is empty, as this method is not invoked when no books. Reduce needs an initial item
|
||||
const earliestLibraryItemCreatedAt =
|
||||
books.length > 0
|
||||
? books.reduce((earliest, book) => {
|
||||
return book.libraryItem.createdAt < earliest.libraryItem.createdAt ? book : earliest
|
||||
}).libraryItem.createdAt
|
||||
: null
|
||||
|
||||
const feedEpisodeObjs = []
|
||||
let numExisting = 0
|
||||
|
||||
@@ -246,7 +246,6 @@ class LibraryItem extends Model {
|
||||
include
|
||||
})
|
||||
if (!libraryItem) {
|
||||
Logger.error(`[LibraryItem] Library item not found`)
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
@@ -80,9 +80,13 @@ class PodcastEpisode extends Model {
|
||||
if (rssPodcastEpisode.guid) {
|
||||
podcastEpisode.extraData.guid = rssPodcastEpisode.guid
|
||||
}
|
||||
|
||||
if (audioFile.chapters?.length) {
|
||||
podcastEpisode.chapters = audioFile.chapters.map((ch) => ({ ...ch }))
|
||||
} else if (rssPodcastEpisode.chapters?.length) {
|
||||
podcastEpisode.chapters = rssPodcastEpisode.chapters.map((ch) => ({ ...ch }))
|
||||
}
|
||||
|
||||
return this.create(podcastEpisode)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const express = require('express')
|
||||
const ShareController = require('../controllers/ShareController')
|
||||
const SessionController = require('../controllers/SessionController')
|
||||
|
||||
class PublicRouter {
|
||||
constructor(playbackSessionManager) {
|
||||
@@ -17,6 +18,7 @@ class PublicRouter {
|
||||
this.router.get('/share/:slug/cover', ShareController.getMediaItemShareCoverImage.bind(this))
|
||||
this.router.get('/share/:slug/download', ShareController.downloadMediaItemShare.bind(this))
|
||||
this.router.patch('/share/:slug/progress', ShareController.updateMediaItemShareProgress.bind(this))
|
||||
this.router.get('/session/:id/track/:index', SessionController.getTrack.bind(this))
|
||||
}
|
||||
}
|
||||
module.exports = PublicRouter
|
||||
|
||||
@@ -475,6 +475,8 @@ class BookScanner {
|
||||
bookAuthors: [],
|
||||
bookSeries: []
|
||||
}
|
||||
|
||||
const createdAtTimestamp = new Date().getTime()
|
||||
if (bookMetadata.authors.length) {
|
||||
for (const authorName of bookMetadata.authors) {
|
||||
const matchingAuthorId = await Database.getAuthorIdByName(libraryItemData.libraryId, authorName)
|
||||
@@ -485,6 +487,8 @@ class BookScanner {
|
||||
} else {
|
||||
// New author
|
||||
bookObject.bookAuthors.push({
|
||||
// Ensures authors are in a set order
|
||||
createdAt: createdAtTimestamp + bookObject.bookAuthors.length,
|
||||
author: {
|
||||
libraryId: libraryItemData.libraryId,
|
||||
name: authorName,
|
||||
|
||||
@@ -64,7 +64,7 @@ class LibraryItemScanner {
|
||||
|
||||
const { libraryItem: expandedLibraryItem, wasUpdated } = await this.rescanLibraryItemMedia(libraryItem, libraryItemScanData, library.settings, scanLogger)
|
||||
if (libraryItemDataUpdated || wasUpdated) {
|
||||
SocketAuthority.emitter('item_updated', expandedLibraryItem.toOldJSONExpanded())
|
||||
SocketAuthority.libraryItemEmitter('item_updated', expandedLibraryItem)
|
||||
|
||||
await this.checkAuthorsAndSeriesRemovedFromBooks(library.id, scanLogger)
|
||||
|
||||
|
||||
@@ -223,11 +223,7 @@ class LibraryScanner {
|
||||
|
||||
// Emit item updates in chunks of 10 to client
|
||||
if (libraryItemsUpdated.length === 10) {
|
||||
// TODO: Should only emit to clients where library item is accessible
|
||||
SocketAuthority.emitter(
|
||||
'items_updated',
|
||||
libraryItemsUpdated.map((li) => li.toOldJSONExpanded())
|
||||
)
|
||||
SocketAuthority.libraryItemsEmitter('items_updated', libraryItemsUpdated)
|
||||
libraryItemsUpdated = []
|
||||
}
|
||||
|
||||
@@ -235,11 +231,7 @@ class LibraryScanner {
|
||||
}
|
||||
// Emit item updates to client
|
||||
if (libraryItemsUpdated.length) {
|
||||
// TODO: Should only emit to clients where library item is accessible
|
||||
SocketAuthority.emitter(
|
||||
'items_updated',
|
||||
libraryItemsUpdated.map((li) => li.toOldJSONExpanded())
|
||||
)
|
||||
SocketAuthority.libraryItemsEmitter('items_updated', libraryItemsUpdated)
|
||||
}
|
||||
|
||||
// Authors and series that were removed from books should be removed if they are now empty
|
||||
@@ -277,11 +269,7 @@ class LibraryScanner {
|
||||
|
||||
// Emit new items in chunks of 10 to client
|
||||
if (newLibraryItems.length === 10) {
|
||||
// TODO: Should only emit to clients where library item is accessible
|
||||
SocketAuthority.emitter(
|
||||
'items_added',
|
||||
newLibraryItems.map((li) => li.toOldJSONExpanded())
|
||||
)
|
||||
SocketAuthority.libraryItemsEmitter('items_added', newLibraryItems)
|
||||
newLibraryItems = []
|
||||
}
|
||||
|
||||
@@ -289,11 +277,7 @@ class LibraryScanner {
|
||||
}
|
||||
// Emit new items to client
|
||||
if (newLibraryItems.length) {
|
||||
// TODO: Should only emit to clients where library item is accessible
|
||||
SocketAuthority.emitter(
|
||||
'items_added',
|
||||
newLibraryItems.map((li) => li.toOldJSONExpanded())
|
||||
)
|
||||
SocketAuthority.libraryItemsEmitter('items_added', newLibraryItems)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -423,7 +407,7 @@ class LibraryScanner {
|
||||
const folder = library.libraryFolders[0]
|
||||
|
||||
const filePathItems = folderGroups[folderId].fileUpdates.map((fileUpdate) => fileUtils.getFilePathItemFromFileUpdate(fileUpdate))
|
||||
const fileUpdateGroup = scanUtils.groupFileItemsIntoLibraryItemDirs(library.mediaType, filePathItems, !!library.settings?.audiobooksOnly)
|
||||
const fileUpdateGroup = scanUtils.groupFileItemsIntoLibraryItemDirs(library.mediaType, filePathItems, !!library.settings?.audiobooksOnly, true)
|
||||
|
||||
if (!Object.keys(fileUpdateGroup).length) {
|
||||
Logger.info(`[LibraryScanner] No important changes to scan for in folder "${folderId}"`)
|
||||
@@ -609,7 +593,7 @@ class LibraryScanner {
|
||||
Logger.info(`[LibraryScanner] Scanning file update group and library item was deleted "${existingLibraryItem.media.title}" - marking as missing`)
|
||||
existingLibraryItem.isMissing = true
|
||||
await existingLibraryItem.save()
|
||||
SocketAuthority.emitter('item_updated', existingLibraryItem.toOldJSONExpanded())
|
||||
SocketAuthority.libraryItemEmitter('item_updated', existingLibraryItem)
|
||||
|
||||
itemGroupingResults[itemDir] = ScanResult.REMOVED
|
||||
continue
|
||||
@@ -643,7 +627,7 @@ class LibraryScanner {
|
||||
const isSingleMediaItem = isSingleMediaFile(fileUpdateGroup, itemDir)
|
||||
const newLibraryItem = await LibraryItemScanner.scanPotentialNewLibraryItem(fullPath, library, folder, isSingleMediaItem)
|
||||
if (newLibraryItem) {
|
||||
SocketAuthority.emitter('item_added', newLibraryItem.toOldJSONExpanded())
|
||||
SocketAuthority.libraryItemEmitter('item_added', newLibraryItem)
|
||||
}
|
||||
itemGroupingResults[itemDir] = newLibraryItem ? ScanResult.ADDED : ScanResult.NOTHING
|
||||
}
|
||||
|
||||
@@ -59,17 +59,36 @@ class PodcastScanner {
|
||||
|
||||
if (libraryItemData.hasAudioFileChanges || libraryItemData.audioLibraryFiles.length !== existingPodcastEpisodes.length) {
|
||||
// Filter out and destroy episodes that were removed
|
||||
existingPodcastEpisodes = await Promise.all(
|
||||
existingPodcastEpisodes.filter(async (ep) => {
|
||||
if (libraryItemData.checkAudioFileRemoved(ep.audioFile)) {
|
||||
libraryScan.addLog(LogLevel.INFO, `Podcast episode "${ep.title}" audio file was removed`)
|
||||
// TODO: Should clean up other data linked to this episode
|
||||
await ep.destroy()
|
||||
return false
|
||||
const episodesToRemove = []
|
||||
existingPodcastEpisodes = existingPodcastEpisodes.filter((ep) => {
|
||||
if (libraryItemData.checkAudioFileRemoved(ep.audioFile)) {
|
||||
episodesToRemove.push(ep)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
if (episodesToRemove.length) {
|
||||
// Remove episodes from playlists and media progress
|
||||
const episodeIds = episodesToRemove.map((ep) => ep.id)
|
||||
await Database.playlistModel.removeMediaItemsFromPlaylists(episodeIds)
|
||||
const mediaProgressRemoved = await Database.mediaProgressModel.destroy({
|
||||
where: {
|
||||
mediaItemId: episodeIds
|
||||
}
|
||||
return true
|
||||
})
|
||||
)
|
||||
if (mediaProgressRemoved) {
|
||||
libraryScan.addLog(LogLevel.INFO, `Removed ${mediaProgressRemoved} media progress for episodes`)
|
||||
}
|
||||
|
||||
// Remove episodes
|
||||
await Promise.all(
|
||||
episodesToRemove.map(async (ep) => {
|
||||
await ep.destroy()
|
||||
libraryScan.addLog(LogLevel.INFO, `Podcast episode "${ep.title}" audio file was removed`)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
// Update audio files that were modified
|
||||
if (libraryItemData.audioLibraryFilesModified.length) {
|
||||
@@ -113,6 +132,9 @@ class PodcastScanner {
|
||||
|
||||
// Create new podcast episodes from new found audio files
|
||||
for (const newAudioFile of newAudioFiles) {
|
||||
// Podcast episode audio files always have index 1
|
||||
newAudioFile.index = 1
|
||||
|
||||
const newEpisode = {
|
||||
title: newAudioFile.metaTags.tagTitle || newAudioFile.metadata.filenameNoExt,
|
||||
subtitle: null,
|
||||
@@ -136,7 +158,6 @@ class PodcastScanner {
|
||||
}
|
||||
|
||||
let hasMediaChanges = false
|
||||
|
||||
if (existingPodcastEpisodes.length !== media.numEpisodes) {
|
||||
media.numEpisodes = existingPodcastEpisodes.length
|
||||
hasMediaChanges = true
|
||||
@@ -253,6 +274,9 @@ class PodcastScanner {
|
||||
|
||||
// Create podcast episodes from audio files
|
||||
for (const audioFile of scannedAudioFiles) {
|
||||
// Podcast episode audio files always have index 1
|
||||
audioFile.index = 1
|
||||
|
||||
const newEpisode = {
|
||||
title: audioFile.metaTags.tagTitle || audioFile.metadata.filenameNoExt,
|
||||
subtitle: null,
|
||||
|
||||
@@ -126,7 +126,7 @@ class Scanner {
|
||||
|
||||
await libraryItem.saveMetadataFile()
|
||||
|
||||
SocketAuthority.emitter('item_updated', libraryItem.toOldJSONExpanded())
|
||||
SocketAuthority.libraryItemEmitter('item_updated', libraryItem)
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -45,6 +45,7 @@ module.exports.AudioMimeType = {
|
||||
FLAC: 'audio/flac',
|
||||
WMA: 'audio/x-ms-wma',
|
||||
AIFF: 'audio/x-aiff',
|
||||
AIF: 'audio/x-aiff',
|
||||
WEBM: 'audio/webm',
|
||||
WEBMA: 'audio/webm',
|
||||
MKA: 'audio/x-matroska',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const globals = {
|
||||
SupportedImageTypes: ['png', 'jpg', 'jpeg', 'webp'],
|
||||
SupportedAudioTypes: ['m4b', 'mp3', 'm4a', 'flac', 'opus', 'ogg', 'oga', 'mp4', 'aac', 'wma', 'aiff', 'wav', 'webm', 'webma', 'mka', 'awb', 'caf', 'mpg', 'mpeg'],
|
||||
SupportedAudioTypes: ['m4b', 'mp3', 'm4a', 'flac', 'opus', 'ogg', 'oga', 'mp4', 'aac', 'wma', 'aiff', 'aif', 'wav', 'webm', 'webma', 'mka', 'awb', 'caf', 'mpg', 'mpeg'],
|
||||
SupportedEbookTypes: ['epub', 'pdf', 'mobi', 'azw3', 'cbr', 'cbz'],
|
||||
TextFileTypes: ['txt', 'nfo'],
|
||||
MetadataFileTypes: ['opf', 'abs', 'xml', 'json']
|
||||
|
||||
@@ -243,3 +243,29 @@ module.exports.isValidASIN = (str) => {
|
||||
if (!str || typeof str !== 'string') return false
|
||||
return /^[A-Z0-9]{10}$/.test(str)
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert timestamp to seconds
|
||||
* @example "01:00:00" => 3600
|
||||
* @example "01:00" => 60
|
||||
* @example "01" => 1
|
||||
*
|
||||
* @param {string} timestamp
|
||||
* @returns {number}
|
||||
*/
|
||||
module.exports.timestampToSeconds = (timestamp) => {
|
||||
if (typeof timestamp !== 'string') {
|
||||
return null
|
||||
}
|
||||
const parts = timestamp.split(':').map(Number)
|
||||
if (parts.some(isNaN)) {
|
||||
return null
|
||||
} else if (parts.length === 1) {
|
||||
return parts[0]
|
||||
} else if (parts.length === 2) {
|
||||
return parts[0] * 60 + parts[1]
|
||||
} else if (parts.length === 3) {
|
||||
return parts[0] * 3600 + parts[1] * 60 + parts[2]
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
const axios = require('axios')
|
||||
const ssrfFilter = require('ssrf-req-filter')
|
||||
const Logger = require('../Logger')
|
||||
const { xmlToJSON, levenshteinDistance } = require('./index')
|
||||
const { xmlToJSON, levenshteinDistance, timestampToSeconds } = require('./index')
|
||||
const htmlSanitizer = require('../utils/htmlSanitizer')
|
||||
|
||||
/**
|
||||
* @typedef RssPodcastChapter
|
||||
* @property {number} id
|
||||
* @property {string} title
|
||||
* @property {number} start
|
||||
* @property {number} end
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef RssPodcastEpisode
|
||||
* @property {string} title
|
||||
@@ -22,6 +30,7 @@ const htmlSanitizer = require('../utils/htmlSanitizer')
|
||||
* @property {string} guid
|
||||
* @property {string} chaptersUrl
|
||||
* @property {string} chaptersType
|
||||
* @property {RssPodcastChapter[]} chapters
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -205,12 +214,53 @@ function extractEpisodeData(item) {
|
||||
const cleanKey = key.split(':').pop()
|
||||
episode[cleanKey] = extractFirstArrayItemString(item, key)
|
||||
})
|
||||
|
||||
// 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) {
|
||||
// Example chapter:
|
||||
// {"id":0,"start":0,"end":43.004286,"title":"chapter 1"}
|
||||
|
||||
const cleanedChapters = item['psc:chapters'][0]['psc:chapter'].map((chapter, index) => {
|
||||
if (!chapter['$']?.title || !chapter['$']?.start || typeof chapter['$']?.start !== 'string' || typeof chapter['$']?.title !== 'string') {
|
||||
return null
|
||||
}
|
||||
|
||||
const start = timestampToSeconds(chapter['$'].start)
|
||||
if (start === null) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
id: index,
|
||||
title: chapter['$'].title,
|
||||
start
|
||||
}
|
||||
})
|
||||
|
||||
if (cleanedChapters.some((chapter) => !chapter)) {
|
||||
Logger.warn(`[podcastUtils] Invalid chapter data for ${episode.enclosure.url}`)
|
||||
} else {
|
||||
episode.chapters = cleanedChapters.map((chapter, index) => {
|
||||
const nextChapter = cleanedChapters[index + 1]
|
||||
const end = nextChapter ? nextChapter.start : episodeDuration
|
||||
return {
|
||||
id: chapter.id,
|
||||
title: chapter.title,
|
||||
start: chapter.start,
|
||||
end
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return episode
|
||||
}
|
||||
|
||||
function cleanEpisodeData(data) {
|
||||
const pubJsDate = data.pubDate ? new Date(data.pubDate) : null
|
||||
const publishedAt = pubJsDate && !isNaN(pubJsDate) ? pubJsDate.valueOf() : null
|
||||
|
||||
return {
|
||||
title: data.title,
|
||||
subtitle: data.subtitle || '',
|
||||
@@ -227,7 +277,8 @@ function cleanEpisodeData(data) {
|
||||
enclosure: data.enclosure,
|
||||
guid: data.guid || null,
|
||||
chaptersUrl: data.chaptersUrl || null,
|
||||
chaptersType: data.chaptersType || null
|
||||
chaptersType: data.chaptersType || null,
|
||||
chapters: data.chapters || []
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1247,7 +1247,12 @@ module.exports = {
|
||||
libraryId
|
||||
}
|
||||
})
|
||||
return statResults[0]
|
||||
return {
|
||||
totalSize: statResults?.[0]?.totalSize || 0,
|
||||
totalDuration: statResults?.[0]?.totalDuration || 0,
|
||||
numAudioFiles: statResults?.[0]?.numAudioFiles || 0,
|
||||
totalItems: statResults?.[0]?.totalItems || 0
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -533,8 +533,10 @@ module.exports = {
|
||||
}
|
||||
})
|
||||
return {
|
||||
...statResults[0],
|
||||
totalSize: sizeResults[0].totalSize || 0
|
||||
totalDuration: statResults?.[0]?.totalDuration || 0,
|
||||
numAudioFiles: statResults?.[0]?.numAudioFiles || 0,
|
||||
totalItems: statResults?.[0]?.totalItems || 0,
|
||||
totalSize: sizeResults?.[0]?.totalSize || 0
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -24,6 +24,12 @@ function isMediaFile(mediaType, ext, audiobooksOnly = false) {
|
||||
return globals.SupportedAudioTypes.includes(extclean) || globals.SupportedEbookTypes.includes(extclean)
|
||||
}
|
||||
|
||||
function isScannableNonMediaFile(ext) {
|
||||
if (!ext) return false
|
||||
const extclean = ext.slice(1).toLowerCase()
|
||||
return globals.TextFileTypes.includes(extclean) || globals.MetadataFileTypes.includes(extclean) || globals.SupportedImageTypes.includes(extclean)
|
||||
}
|
||||
|
||||
function checkFilepathIsAudioFile(filepath) {
|
||||
const ext = Path.extname(filepath)
|
||||
if (!ext) return false
|
||||
@@ -35,27 +41,31 @@ module.exports.checkFilepathIsAudioFile = checkFilepathIsAudioFile
|
||||
/**
|
||||
* @param {string} mediaType
|
||||
* @param {import('./fileUtils').FilePathItem[]} fileItems
|
||||
* @param {boolean} [audiobooksOnly=false]
|
||||
* @param {boolean} audiobooksOnly
|
||||
* @param {boolean} [includeNonMediaFiles=false] - Used by the watcher to re-scan when covers/metadata files are added/removed
|
||||
* @returns {Record<string,string[]>} map of files grouped into potential libarary item dirs
|
||||
*/
|
||||
function groupFileItemsIntoLibraryItemDirs(mediaType, fileItems, audiobooksOnly = false) {
|
||||
function groupFileItemsIntoLibraryItemDirs(mediaType, fileItems, audiobooksOnly, includeNonMediaFiles = false) {
|
||||
// Step 1: Filter out non-book-media files in root dir (with depth of 0)
|
||||
const itemsFiltered = fileItems.filter((i) => {
|
||||
return i.deep > 0 || (mediaType === 'book' && isMediaFile(mediaType, i.extension, audiobooksOnly))
|
||||
})
|
||||
|
||||
// Step 2: Separate media files and other files
|
||||
// - Directories without a media file will not be included
|
||||
// - Directories without a media file will not be included (unless includeNonMediaFiles is true)
|
||||
/** @type {import('./fileUtils').FilePathItem[]} */
|
||||
const mediaFileItems = []
|
||||
/** @type {import('./fileUtils').FilePathItem[]} */
|
||||
const otherFileItems = []
|
||||
itemsFiltered.forEach((item) => {
|
||||
if (isMediaFile(mediaType, item.extension, audiobooksOnly)) mediaFileItems.push(item)
|
||||
else otherFileItems.push(item)
|
||||
if (isMediaFile(mediaType, item.extension, audiobooksOnly) || (includeNonMediaFiles && isScannableNonMediaFile(item.extension))) {
|
||||
mediaFileItems.push(item)
|
||||
} else {
|
||||
otherFileItems.push(item)
|
||||
}
|
||||
})
|
||||
|
||||
// Step 3: Group audio files in library items
|
||||
// Step 3: Group media files (or non-media files if includeNonMediaFiles is true) in library items
|
||||
const libraryItemGroup = {}
|
||||
mediaFileItems.forEach((item) => {
|
||||
const dirparts = item.reldirpath.split('/').filter((p) => !!p)
|
||||
|
||||
Reference in New Issue
Block a user