mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-06-02 00:40:39 +02:00
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6d2482a98e | |||
| 4b23b842bb | |||
| 07bebc8808 | |||
| 027d7f7a5b | |||
| 6baa0fa047 | |||
| 8425fac543 | |||
| 7b2ac7b9e9 | |||
| bf071be247 | |||
| 6c05a0af8a | |||
| 0e292c64c4 | |||
| 725f8eecdb | |||
| 521a673094 | |||
| d917f0e37d | |||
| 7ed5b1744f | |||
| 64a7cfac3b | |||
| 1ee7ba54f8 | |||
| 6bb18f8800 | |||
| b26b854963 | |||
| 7d58361ced | |||
| a3723f3d06 | |||
| 78d1cd0cfb | |||
| d41366a417 | |||
| a2347150a2 | |||
| d33f23dede | |||
| cfca2be1b2 | |||
| 73f07c1392 | |||
| 4541e9ddc3 | |||
| 972271a1a9 | |||
| e97d92a8ac |
@@ -45,10 +45,10 @@
|
|||||||
</span>
|
</span>
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
</div>
|
</div>
|
||||||
<div v-show="numLibraryItemsSelected" class="absolute top-0 left-0 w-full h-full px-4 bg-primary flex items-center">
|
<div v-show="numMediaItemsSelected" class="absolute top-0 left-0 w-full h-full px-4 bg-primary flex items-center">
|
||||||
<h1 class="text-lg md:text-2xl px-4">{{ $getString('MessageItemsSelected', [numLibraryItemsSelected]) }}</h1>
|
<h1 class="text-lg md:text-2xl px-4">{{ $getString('MessageItemsSelected', [numMediaItemsSelected]) }}</h1>
|
||||||
<div class="flex-grow" />
|
<div class="flex-grow" />
|
||||||
<ui-btn v-if="!isPodcastLibrary" color="success" :padding-x="4" small class="flex items-center h-9 mr-2" @click="playSelectedItems">
|
<ui-btn v-if="!isPodcastLibrary && selectedMediaItemsArePlayable" color="success" :padding-x="4" small class="flex items-center h-9 mr-2" @click="playSelectedItems">
|
||||||
<span class="material-icons text-2xl -ml-2 pr-1 text-white">play_arrow</span>
|
<span class="material-icons text-2xl -ml-2 pr-1 text-white">play_arrow</span>
|
||||||
{{ $strings.ButtonPlay }}
|
{{ $strings.ButtonPlay }}
|
||||||
</ui-btn>
|
</ui-btn>
|
||||||
@@ -109,11 +109,14 @@ export default {
|
|||||||
username() {
|
username() {
|
||||||
return this.user ? this.user.username : 'err'
|
return this.user ? this.user.username : 'err'
|
||||||
},
|
},
|
||||||
numLibraryItemsSelected() {
|
numMediaItemsSelected() {
|
||||||
return this.selectedLibraryItems.length
|
return this.selectedMediaItems.length
|
||||||
},
|
},
|
||||||
selectedLibraryItems() {
|
selectedMediaItems() {
|
||||||
return this.$store.state.selectedLibraryItems
|
return this.$store.state.globals.selectedMediaItems
|
||||||
|
},
|
||||||
|
selectedMediaItemsArePlayable() {
|
||||||
|
return !this.selectedMediaItems.some(i => !i.hasTracks)
|
||||||
},
|
},
|
||||||
userMediaProgress() {
|
userMediaProgress() {
|
||||||
return this.$store.state.user.user.mediaProgress || []
|
return this.$store.state.user.user.mediaProgress || []
|
||||||
@@ -129,8 +132,8 @@ export default {
|
|||||||
},
|
},
|
||||||
selectedIsFinished() {
|
selectedIsFinished() {
|
||||||
// Find an item that is not finished, if none then all items finished
|
// Find an item that is not finished, if none then all items finished
|
||||||
return !this.selectedLibraryItems.find((libraryItemId) => {
|
return !this.selectedMediaItems.find((item) => {
|
||||||
var itemProgress = this.userMediaProgress.find((lip) => lip.libraryItemId === libraryItemId)
|
const itemProgress = this.userMediaProgress.find((lip) => lip.libraryItemId === item.id)
|
||||||
return !itemProgress || !itemProgress.isFinished
|
return !itemProgress || !itemProgress.isFinished
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@@ -154,8 +157,9 @@ export default {
|
|||||||
async playSelectedItems() {
|
async playSelectedItems() {
|
||||||
this.$store.commit('setProcessingBatch', true)
|
this.$store.commit('setProcessingBatch', true)
|
||||||
|
|
||||||
var libraryItems = await this.$axios.$post(`/api/items/batch/get`, { libraryItemIds: this.selectedLibraryItems }).catch((error) => {
|
const libraryItemIds = this.selectedMediaItems.map((i) => i.id)
|
||||||
var errorMsg = error.response.data || 'Failed to get items'
|
const libraryItems = await this.$axios.$post(`/api/items/batch/get`, { libraryItemIds }).catch((error) => {
|
||||||
|
const errorMsg = error.response.data || 'Failed to get items'
|
||||||
console.error(errorMsg, error)
|
console.error(errorMsg, error)
|
||||||
this.$toast.error(errorMsg)
|
this.$toast.error(errorMsg)
|
||||||
return []
|
return []
|
||||||
@@ -185,20 +189,20 @@ export default {
|
|||||||
queueItems
|
queueItems
|
||||||
})
|
})
|
||||||
this.$store.commit('setProcessingBatch', false)
|
this.$store.commit('setProcessingBatch', false)
|
||||||
this.$store.commit('setSelectedLibraryItems', [])
|
this.$store.commit('globals/resetSelectedMediaItems', [])
|
||||||
this.$eventBus.$emit('bookshelf_clear_selection')
|
this.$eventBus.$emit('bookshelf_clear_selection')
|
||||||
},
|
},
|
||||||
cancelSelectionMode() {
|
cancelSelectionMode() {
|
||||||
if (this.processingBatch) return
|
if (this.processingBatch) return
|
||||||
this.$store.commit('setSelectedLibraryItems', [])
|
this.$store.commit('globals/resetSelectedMediaItems', [])
|
||||||
this.$eventBus.$emit('bookshelf_clear_selection')
|
this.$eventBus.$emit('bookshelf_clear_selection')
|
||||||
},
|
},
|
||||||
toggleBatchRead() {
|
toggleBatchRead() {
|
||||||
this.$store.commit('setProcessingBatch', true)
|
this.$store.commit('setProcessingBatch', true)
|
||||||
var newIsFinished = !this.selectedIsFinished
|
const newIsFinished = !this.selectedIsFinished
|
||||||
var updateProgressPayloads = this.selectedLibraryItems.map((lid) => {
|
const updateProgressPayloads = this.selectedMediaItems.map((item) => {
|
||||||
return {
|
return {
|
||||||
libraryItemId: lid,
|
libraryItemId: item.id,
|
||||||
isFinished: newIsFinished
|
isFinished: newIsFinished
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -208,7 +212,7 @@ export default {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
this.$toast.success('Batch update success!')
|
this.$toast.success('Batch update success!')
|
||||||
this.$store.commit('setProcessingBatch', false)
|
this.$store.commit('setProcessingBatch', false)
|
||||||
this.$store.commit('setSelectedLibraryItems', [])
|
this.$store.commit('globals/resetSelectedMediaItems', [])
|
||||||
this.$eventBus.$emit('bookshelf_clear_selection')
|
this.$eventBus.$emit('bookshelf_clear_selection')
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@@ -218,18 +222,18 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
batchDeleteClick() {
|
batchDeleteClick() {
|
||||||
var audiobookText = this.numLibraryItemsSelected > 1 ? `these ${this.numLibraryItemsSelected} items` : 'this item'
|
const audiobookText = this.numMediaItemsSelected > 1 ? `these ${this.numMediaItemsSelected} items` : 'this item'
|
||||||
var confirmMsg = `Are you sure you want to remove ${audiobookText}?\n\n*Does not delete your files, only removes the items from Audiobookshelf`
|
const confirmMsg = `Are you sure you want to remove ${audiobookText}?\n\n*Does not delete your files, only removes the items from Audiobookshelf`
|
||||||
if (confirm(confirmMsg)) {
|
if (confirm(confirmMsg)) {
|
||||||
this.$store.commit('setProcessingBatch', true)
|
this.$store.commit('setProcessingBatch', true)
|
||||||
this.$axios
|
this.$axios
|
||||||
.$post(`/api/items/batch/delete`, {
|
.$post(`/api/items/batch/delete`, {
|
||||||
libraryItemIds: this.selectedLibraryItems
|
libraryItemIds: this.selectedMediaItems.map((i) => i.id)
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.$toast.success('Batch delete success!')
|
this.$toast.success('Batch delete success!')
|
||||||
this.$store.commit('setProcessingBatch', false)
|
this.$store.commit('setProcessingBatch', false)
|
||||||
this.$store.commit('setSelectedLibraryItems', [])
|
this.$store.commit('globals/resetSelectedMediaItems', [])
|
||||||
this.$eventBus.$emit('bookshelf_clear_selection')
|
this.$eventBus.$emit('bookshelf_clear_selection')
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
|||||||
@@ -89,8 +89,8 @@ export default {
|
|||||||
var baseSize = this.isCoverSquareAspectRatio ? 192 : 120
|
var baseSize = this.isCoverSquareAspectRatio ? 192 : 120
|
||||||
return this.bookCoverWidth / baseSize
|
return this.bookCoverWidth / baseSize
|
||||||
},
|
},
|
||||||
selectedLibraryItems() {
|
selectedMediaItems() {
|
||||||
return this.$store.state.selectedLibraryItems || []
|
return this.$store.state.globals.selectedMediaItems || []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -100,15 +100,15 @@ export default {
|
|||||||
const indexOf = shelf.shelfStartIndex + entityShelfIndex
|
const indexOf = shelf.shelfStartIndex + entityShelfIndex
|
||||||
|
|
||||||
const lastLastItemIndexSelected = this.lastItemIndexSelected
|
const lastLastItemIndexSelected = this.lastItemIndexSelected
|
||||||
if (!this.selectedLibraryItems.includes(entity.id)) {
|
if (!this.selectedMediaItems.some((i) => i.id === entity.id)) {
|
||||||
this.lastItemIndexSelected = indexOf
|
this.lastItemIndexSelected = indexOf
|
||||||
} else {
|
} else {
|
||||||
this.lastItemIndexSelected = -1
|
this.lastItemIndexSelected = -1
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shiftKey && lastLastItemIndexSelected >= 0) {
|
if (shiftKey && lastLastItemIndexSelected >= 0) {
|
||||||
var loopStart = indexOf
|
let loopStart = indexOf
|
||||||
var loopEnd = lastLastItemIndexSelected
|
let loopEnd = lastLastItemIndexSelected
|
||||||
if (indexOf > lastLastItemIndexSelected) {
|
if (indexOf > lastLastItemIndexSelected) {
|
||||||
loopStart = lastLastItemIndexSelected
|
loopStart = lastLastItemIndexSelected
|
||||||
loopEnd = indexOf
|
loopEnd = indexOf
|
||||||
@@ -117,12 +117,12 @@ export default {
|
|||||||
const flattenedEntitiesArray = []
|
const flattenedEntitiesArray = []
|
||||||
this.shelves.map((s) => flattenedEntitiesArray.push(...s.entities))
|
this.shelves.map((s) => flattenedEntitiesArray.push(...s.entities))
|
||||||
|
|
||||||
var isSelecting = false
|
let isSelecting = false
|
||||||
// If any items in this range is not selected then select all otherwise unselect all
|
// If any items in this range is not selected then select all otherwise unselect all
|
||||||
for (let i = loopStart; i <= loopEnd; i++) {
|
for (let i = loopStart; i <= loopEnd; i++) {
|
||||||
const thisEntity = flattenedEntitiesArray[i]
|
const thisEntity = flattenedEntitiesArray[i]
|
||||||
if (thisEntity) {
|
if (thisEntity) {
|
||||||
if (!this.selectedLibraryItems.includes(thisEntity.id)) {
|
if (!this.selectedMediaItems.some((i) => i.id === thisEntity.id)) {
|
||||||
isSelecting = true
|
isSelecting = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -133,13 +133,23 @@ export default {
|
|||||||
for (let i = loopStart; i <= loopEnd; i++) {
|
for (let i = loopStart; i <= loopEnd; i++) {
|
||||||
const thisEntity = flattenedEntitiesArray[i]
|
const thisEntity = flattenedEntitiesArray[i]
|
||||||
if (thisEntity) {
|
if (thisEntity) {
|
||||||
this.$store.commit('setLibraryItemSelected', { libraryItemId: thisEntity.id, selected: isSelecting })
|
const mediaItem = {
|
||||||
|
id: thisEntity.id,
|
||||||
|
mediaType: thisEntity.mediaType,
|
||||||
|
hasTracks: thisEntity.mediaType === 'podcast' || thisEntity.media.numTracks || (thisEntity.media.tracks && thisEntity.media.tracks.length)
|
||||||
|
}
|
||||||
|
this.$store.commit('globals/setMediaItemSelected', { item: mediaItem, selected: isSelecting })
|
||||||
} else {
|
} else {
|
||||||
console.error('Invalid entity index', i)
|
console.error('Invalid entity index', i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.$store.commit('toggleLibraryItemSelected', entity.id)
|
const mediaItem = {
|
||||||
|
id: entity.id,
|
||||||
|
mediaType: entity.mediaType,
|
||||||
|
hasTracks: entity.mediaType === 'podcast' || entity.media.numTracks || (entity.media.tracks && entity.media.tracks.length)
|
||||||
|
}
|
||||||
|
this.$store.commit('globals/toggleMediaItemSelected', mediaItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ export default {
|
|||||||
return this.$store.state.libraries.currentLibraryId
|
return this.$store.state.libraries.currentLibraryId
|
||||||
},
|
},
|
||||||
isSelectionMode() {
|
isSelectionMode() {
|
||||||
return this.$store.getters['getNumLibraryItemsSelected'] > 0
|
return this.$store.getters['globals/getIsBatchSelectingMediaItems']
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -119,14 +119,14 @@ export default {
|
|||||||
this.$store.commit('globals/setShowEditPodcastEpisodeModal', true)
|
this.$store.commit('globals/setShowEditPodcastEpisodeModal', true)
|
||||||
},
|
},
|
||||||
updateSelectionMode(val) {
|
updateSelectionMode(val) {
|
||||||
var selectedLibraryItems = this.$store.state.selectedLibraryItems
|
const selectedMediaItems = this.$store.state.globals.selectedMediaItems
|
||||||
if (this.shelf.type === 'book' || this.shelf.type === 'podcast') {
|
if (this.shelf.type === 'book' || this.shelf.type === 'podcast') {
|
||||||
this.shelf.entities.forEach((ent) => {
|
this.shelf.entities.forEach((ent) => {
|
||||||
var component = this.$refs[`shelf-book-${ent.id}`]
|
var component = this.$refs[`shelf-book-${ent.id}`]
|
||||||
if (!component || !component.length) return
|
if (!component || !component.length) return
|
||||||
component = component[0]
|
component = component[0]
|
||||||
component.setSelectionMode(val)
|
component.setSelectionMode(val)
|
||||||
component.selected = selectedLibraryItems.includes(ent.id)
|
component.selected = selectedMediaItems.some((i) => i.id === ent.id)
|
||||||
})
|
})
|
||||||
} else if (this.shelf.type === 'episode') {
|
} else if (this.shelf.type === 'episode') {
|
||||||
this.shelf.entities.forEach((ent) => {
|
this.shelf.entities.forEach((ent) => {
|
||||||
@@ -134,7 +134,7 @@ export default {
|
|||||||
if (!component || !component.length) return
|
if (!component || !component.length) return
|
||||||
component = component[0]
|
component = component[0]
|
||||||
component.setSelectionMode(val)
|
component.setSelectionMode(val)
|
||||||
component.selected = selectedLibraryItems.includes(ent.id)
|
component.selected = selectedMediaItems.some((i) => i.id === ent.id)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -205,7 +205,7 @@ export default {
|
|||||||
return this.seriesProgress.libraryItemIds || []
|
return this.seriesProgress.libraryItemIds || []
|
||||||
},
|
},
|
||||||
isBatchSelecting() {
|
isBatchSelecting() {
|
||||||
return this.$store.state.selectedLibraryItems.length
|
return this.$store.getters['globals/getIsBatchSelectingMediaItems']
|
||||||
},
|
},
|
||||||
isSeriesFinished() {
|
isSeriesFinished() {
|
||||||
return this.seriesProgress && !!this.seriesProgress.isFinished
|
return this.seriesProgress && !!this.seriesProgress.isFinished
|
||||||
|
|||||||
@@ -201,8 +201,8 @@ export default {
|
|||||||
// Includes margin
|
// Includes margin
|
||||||
return this.entityWidth + 24
|
return this.entityWidth + 24
|
||||||
},
|
},
|
||||||
selectedLibraryItems() {
|
selectedMediaItems() {
|
||||||
return this.$store.state.selectedLibraryItems || []
|
return this.$store.state.globals.selectedMediaItems || []
|
||||||
},
|
},
|
||||||
sizeMultiplier() {
|
sizeMultiplier() {
|
||||||
var baseSize = this.isCoverSquareAspectRatio ? 192 : 120
|
var baseSize = this.isCoverSquareAspectRatio ? 192 : 120
|
||||||
@@ -230,28 +230,28 @@ export default {
|
|||||||
},
|
},
|
||||||
selectEntity(entity, shiftKey) {
|
selectEntity(entity, shiftKey) {
|
||||||
if (this.entityName === 'books' || this.entityName === 'series-books') {
|
if (this.entityName === 'books' || this.entityName === 'series-books') {
|
||||||
var indexOf = this.entities.findIndex((ent) => ent && ent.id === entity.id)
|
const indexOf = this.entities.findIndex((ent) => ent && ent.id === entity.id)
|
||||||
const lastLastItemIndexSelected = this.lastItemIndexSelected
|
const lastLastItemIndexSelected = this.lastItemIndexSelected
|
||||||
if (!this.selectedLibraryItems.includes(entity.id)) {
|
if (!this.selectedMediaItems.some((i) => i.id === entity.id)) {
|
||||||
this.lastItemIndexSelected = indexOf
|
this.lastItemIndexSelected = indexOf
|
||||||
} else {
|
} else {
|
||||||
this.lastItemIndexSelected = -1
|
this.lastItemIndexSelected = -1
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shiftKey && lastLastItemIndexSelected >= 0) {
|
if (shiftKey && lastLastItemIndexSelected >= 0) {
|
||||||
var loopStart = indexOf
|
let loopStart = indexOf
|
||||||
var loopEnd = lastLastItemIndexSelected
|
let loopEnd = lastLastItemIndexSelected
|
||||||
if (indexOf > lastLastItemIndexSelected) {
|
if (indexOf > lastLastItemIndexSelected) {
|
||||||
loopStart = lastLastItemIndexSelected
|
loopStart = lastLastItemIndexSelected
|
||||||
loopEnd = indexOf
|
loopEnd = indexOf
|
||||||
}
|
}
|
||||||
|
|
||||||
var isSelecting = false
|
let isSelecting = false
|
||||||
// If any items in this range is not selected then select all otherwise unselect all
|
// If any items in this range is not selected then select all otherwise unselect all
|
||||||
for (let i = loopStart; i <= loopEnd; i++) {
|
for (let i = loopStart; i <= loopEnd; i++) {
|
||||||
const thisEntity = this.entities[i]
|
const thisEntity = this.entities[i]
|
||||||
if (thisEntity && !thisEntity.collapsedSeries) {
|
if (thisEntity && !thisEntity.collapsedSeries) {
|
||||||
if (!this.selectedLibraryItems.includes(thisEntity.id)) {
|
if (!this.selectedMediaItems.some((i) => i.id === thisEntity.id)) {
|
||||||
isSelecting = true
|
isSelecting = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -269,16 +269,28 @@ export default {
|
|||||||
const entityComponentRef = this.entityComponentRefs[i]
|
const entityComponentRef = this.entityComponentRefs[i]
|
||||||
if (thisEntity && entityComponentRef) {
|
if (thisEntity && entityComponentRef) {
|
||||||
entityComponentRef.selected = isSelecting
|
entityComponentRef.selected = isSelecting
|
||||||
this.$store.commit('setLibraryItemSelected', { libraryItemId: thisEntity.id, selected: isSelecting })
|
|
||||||
|
const mediaItem = {
|
||||||
|
id: thisEntity.id,
|
||||||
|
mediaType: thisEntity.mediaType,
|
||||||
|
hasTracks: thisEntity.mediaType === 'podcast' || thisEntity.media.numTracks || (thisEntity.media.tracks && thisEntity.media.tracks.length)
|
||||||
|
}
|
||||||
|
console.log('Setting media item selected', mediaItem, 'Num Selected=', this.selectedMediaItems.length)
|
||||||
|
this.$store.commit('globals/setMediaItemSelected', { item: mediaItem, selected: isSelecting })
|
||||||
} else {
|
} else {
|
||||||
console.error('Invalid entity index', i)
|
console.error('Invalid entity index', i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.$store.commit('toggleLibraryItemSelected', entity.id)
|
const mediaItem = {
|
||||||
|
id: entity.id,
|
||||||
|
mediaType: entity.mediaType,
|
||||||
|
hasTracks: entity.mediaType === 'podcast' || entity.media.numTracks || (entity.media.tracks && entity.media.tracks.length)
|
||||||
|
}
|
||||||
|
this.$store.commit('globals/toggleMediaItemSelected', mediaItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
var newIsSelectionMode = !!this.selectedLibraryItems.length
|
const newIsSelectionMode = !!this.selectedMediaItems.length
|
||||||
if (this.isSelectionMode !== newIsSelectionMode) {
|
if (this.isSelectionMode !== newIsSelectionMode) {
|
||||||
this.isSelectionMode = newIsSelectionMode
|
this.isSelectionMode = newIsSelectionMode
|
||||||
this.updateBookSelectionMode(newIsSelectionMode)
|
this.updateBookSelectionMode(newIsSelectionMode)
|
||||||
|
|||||||
@@ -41,9 +41,9 @@
|
|||||||
<span class="font-normal block truncate py-2">No {{ sublist }}</span>
|
<span class="font-normal block truncate py-2">No {{ sublist }}</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li v-else-if="sublist === 'series'" class="text-gray-50 select-none relative px-2 cursor-pointer hover:bg-black-400" role="option" @click="clickedSublistOption($encode('No Series'))">
|
<li v-else-if="sublist === 'series'" class="text-gray-50 select-none relative px-2 cursor-pointer hover:bg-black-400" role="option" @click="clickedSublistOption($encode('no-series'))">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<span class="font-normal block truncate py-2 text-xs text-white text-opacity-80">No Series</span>
|
<span class="font-normal block truncate py-2 text-xs text-white text-opacity-80">{{ $strings.MessageNoSeries }}</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<template v-for="item in sublistItems">
|
<template v-for="item in sublistItems">
|
||||||
@@ -174,6 +174,11 @@ export default {
|
|||||||
value: 'missing',
|
value: 'missing',
|
||||||
sublist: true
|
sublist: true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
text: this.$strings.LabelTracks,
|
||||||
|
value: 'tracks',
|
||||||
|
sublist: true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
text: this.$strings.ButtonIssues,
|
text: this.$strings.ButtonIssues,
|
||||||
value: 'issues',
|
value: 'issues',
|
||||||
@@ -263,10 +268,88 @@ export default {
|
|||||||
return this.filterData.languages || []
|
return this.filterData.languages || []
|
||||||
},
|
},
|
||||||
progress() {
|
progress() {
|
||||||
return [this.$strings.LabelFinished, this.$strings.LabelInProgress, this.$strings.LabelNotStarted, this.$strings.LabelNotFinished]
|
return [
|
||||||
|
{
|
||||||
|
id: 'finished',
|
||||||
|
name: this.$strings.LabelFinished
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'in-progress',
|
||||||
|
name: this.$strings.LabelInProgress
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'not-started',
|
||||||
|
name: this.$strings.LabelNotStarted
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'not-finished',
|
||||||
|
name: this.$strings.LabelNotFinished
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
tracks() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id: 'single',
|
||||||
|
name: this.$strings.LabelTracksSingleTrack
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'multi',
|
||||||
|
name: this.$strings.LabelTracksMultiTrack
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
missing() {
|
missing() {
|
||||||
return ['ASIN', 'ISBN', this.$strings.LabelSubtitle, this.$strings.LabelAuthor, this.$strings.LabelPublishYear, this.$strings.LabelSeries, this.$strings.LabelDescription, this.$strings.LabelGenres, this.$strings.LabelTags, this.$strings.LabelNarrator, this.$strings.LabelPublisher, this.$strings.LabelLanguage]
|
return [
|
||||||
|
{
|
||||||
|
id: 'asin',
|
||||||
|
name: 'ASIN'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'isbn',
|
||||||
|
name: 'ISBN'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'subtitle',
|
||||||
|
name: this.$strings.LabelSubtitle
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'authors',
|
||||||
|
name: this.$strings.LabelAuthor
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'publishedYear',
|
||||||
|
name: this.$strings.LabelPublishYear
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'series',
|
||||||
|
name: this.$strings.LabelSeries
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'description',
|
||||||
|
name: this.$strings.LabelDescription
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'genres',
|
||||||
|
name: this.$strings.LabelGenres
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'tags',
|
||||||
|
name: this.$strings.LabelTags
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'narrators',
|
||||||
|
name: this.$strings.LabelNarrator
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'publisher',
|
||||||
|
name: this.$strings.LabelPublisher
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'language',
|
||||||
|
name: this.$strings.LabelLanguage
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
sublistItems() {
|
sublistItems() {
|
||||||
return (this[this.sublist] || []).map((item) => {
|
return (this[this.sublist] || []).map((item) => {
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ export default {
|
|||||||
return this.$store.state.globals.showBatchQuickMatchModal
|
return this.$store.state.globals.showBatchQuickMatchModal
|
||||||
},
|
},
|
||||||
selectedBookIds() {
|
selectedBookIds() {
|
||||||
return this.$store.state.selectedLibraryItems || []
|
return (this.$store.state.globals.selectedMediaItems || []).map((i) => i.id)
|
||||||
},
|
},
|
||||||
currentLibraryId() {
|
currentLibraryId() {
|
||||||
return this.$store.state.libraries.currentLibraryId
|
return this.$store.state.libraries.currentLibraryId
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ export default {
|
|||||||
return this.$store.state.globals.showBatchCollectionModal
|
return this.$store.state.globals.showBatchCollectionModal
|
||||||
},
|
},
|
||||||
selectedBookIds() {
|
selectedBookIds() {
|
||||||
return this.$store.state.selectedLibraryItems || []
|
return (this.$store.state.globals.selectedMediaItems || []).map((i) => i.id)
|
||||||
},
|
},
|
||||||
currentLibraryId() {
|
currentLibraryId() {
|
||||||
return this.$store.state.libraries.currentLibraryId
|
return this.$store.state.libraries.currentLibraryId
|
||||||
|
|||||||
@@ -92,13 +92,18 @@ export default {
|
|||||||
},
|
},
|
||||||
ebookUrl() {
|
ebookUrl() {
|
||||||
if (!this.ebookFile) return null
|
if (!this.ebookFile) return null
|
||||||
var itemRelPath = this.selectedLibraryItem.relPath
|
let filepath = ''
|
||||||
if (itemRelPath.startsWith('/')) itemRelPath = itemRelPath.slice(1)
|
if (this.selectedLibraryItem.isFile) {
|
||||||
var relPath = this.ebookFile.metadata.relPath
|
filepath = this.$encodeUriPath(this.ebookFile.metadata.filename)
|
||||||
if (relPath.startsWith('/')) relPath = relPath.slice(1)
|
} else {
|
||||||
|
const itemRelPath = this.selectedLibraryItem.relPath
|
||||||
|
if (itemRelPath.startsWith('/')) itemRelPath = itemRelPath.slice(1)
|
||||||
|
const relPath = this.ebookFile.metadata.relPath
|
||||||
|
if (relPath.startsWith('/')) relPath = relPath.slice(1)
|
||||||
|
|
||||||
const relRelPath = this.$encodeUriPath(`${itemRelPath}/${relPath}`)
|
filepath = this.$encodeUriPath(`${itemRelPath}/${relPath}`)
|
||||||
return `/ebook/${this.libraryId}/${this.folderId}/${relRelPath}`
|
}
|
||||||
|
return `/ebook/${this.libraryId}/${this.folderId}/${filepath}`
|
||||||
},
|
},
|
||||||
userToken() {
|
userToken() {
|
||||||
return this.$store.getters['user/getToken']
|
return this.$store.getters['user/getToken']
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export default {
|
|||||||
return Math.floor(this.clientWidth / (this.cardWidth + 16))
|
return Math.floor(this.clientWidth / (this.cardWidth + 16))
|
||||||
},
|
},
|
||||||
isSelectionMode() {
|
isSelectionMode() {
|
||||||
return this.$store.getters['getNumLibraryItemsSelected'] > 0
|
return this.$store.getters['globals/getIsBatchSelectingMediaItems']
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ export default {
|
|||||||
return Math.floor(this.clientWidth / (this.cardWidth + 16))
|
return Math.floor(this.clientWidth / (this.cardWidth + 16))
|
||||||
},
|
},
|
||||||
isSelectionMode() {
|
isSelectionMode() {
|
||||||
return this.$store.getters['getNumLibraryItemsSelected'] > 0
|
return this.$store.getters['globals/getIsBatchSelectingMediaItems']
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -101,14 +101,14 @@ export default {
|
|||||||
this.updateSelectionMode(this.isSelectionMode)
|
this.updateSelectionMode(this.isSelectionMode)
|
||||||
},
|
},
|
||||||
updateSelectionMode(val) {
|
updateSelectionMode(val) {
|
||||||
var selectedLibraryItems = this.$store.state.selectedLibraryItems
|
const selectedMediaItems = this.$store.state.globals.selectedMediaItems
|
||||||
|
|
||||||
this.items.forEach((ent) => {
|
this.items.forEach((ent) => {
|
||||||
var component = this.$refs[`slider-episode-${ent.recentEpisode.id}`]
|
let component = this.$refs[`slider-episode-${ent.recentEpisode.id}`]
|
||||||
if (!component || !component.length) return
|
if (!component || !component.length) return
|
||||||
component = component[0]
|
component = component[0]
|
||||||
component.setSelectionMode(val)
|
component.setSelectionMode(val)
|
||||||
component.selected = selectedLibraryItems.includes(ent.id)
|
component.selected = selectedMediaItems.some((i) => i.id === ent.id)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
scrolled() {
|
scrolled() {
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ export default {
|
|||||||
return Math.floor(this.clientWidth / (this.cardWidth + 16))
|
return Math.floor(this.clientWidth / (this.cardWidth + 16))
|
||||||
},
|
},
|
||||||
isSelectionMode() {
|
isSelectionMode() {
|
||||||
return this.$store.getters['getNumLibraryItemsSelected'] > 0
|
return this.$store.getters['globals/getIsBatchSelectingMediaItems']
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -82,14 +82,14 @@ export default {
|
|||||||
this.updateSelectionMode(this.isSelectionMode)
|
this.updateSelectionMode(this.isSelectionMode)
|
||||||
},
|
},
|
||||||
updateSelectionMode(val) {
|
updateSelectionMode(val) {
|
||||||
var selectedLibraryItems = this.$store.state.selectedLibraryItems
|
const selectedMediaItems = this.$store.state.globals.selectedMediaItems
|
||||||
|
|
||||||
this.items.forEach((item) => {
|
this.items.forEach((item) => {
|
||||||
var component = this.$refs[`slider-item-${item.id}`]
|
let component = this.$refs[`slider-item-${item.id}`]
|
||||||
if (!component || !component.length) return
|
if (!component || !component.length) return
|
||||||
component = component[0]
|
component = component[0]
|
||||||
component.setSelectionMode(val)
|
component.setSelectionMode(val)
|
||||||
component.selected = selectedLibraryItems.includes(item.id)
|
component.selected = selectedMediaItems.some((i) => i.id === item.id)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
scrolled() {
|
scrolled() {
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export default {
|
|||||||
return Math.floor(this.clientWidth / (this.cardWidth + 16))
|
return Math.floor(this.clientWidth / (this.cardWidth + 16))
|
||||||
},
|
},
|
||||||
isSelectionMode() {
|
isSelectionMode() {
|
||||||
return this.$store.getters['getNumLibraryItemsSelected'] > 0
|
return this.$store.getters['globals/getIsBatchSelectingMediaItems']
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|||||||
@@ -42,9 +42,8 @@ export default {
|
|||||||
if (this.$store.state.showEditModal) {
|
if (this.$store.state.showEditModal) {
|
||||||
this.$store.commit('setShowEditModal', false)
|
this.$store.commit('setShowEditModal', false)
|
||||||
}
|
}
|
||||||
if (this.$store.state.selectedLibraryItems) {
|
|
||||||
this.$store.commit('setSelectedLibraryItems', [])
|
this.$store.commit('globals/resetSelectedMediaItems', [])
|
||||||
}
|
|
||||||
this.updateBodyClass()
|
this.updateBodyClass()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -504,9 +503,9 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Batch selecting
|
// Batch selecting
|
||||||
if (this.$store.getters['getNumLibraryItemsSelected'] && name === 'Escape') {
|
if (this.$store.getters['globals/getIsBatchSelectingMediaItems'] && name === 'Escape') {
|
||||||
// ESCAPE key cancels batch selection
|
// ESCAPE key cancels batch selection
|
||||||
this.$store.commit('setSelectedLibraryItems', [])
|
this.$store.commit('globals/resetSelectedMediaItems', [])
|
||||||
this.$eventBus.$emit('bookshelf_clear_selection')
|
this.$eventBus.$emit('bookshelf_clear_selection')
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export default {
|
|||||||
shelfEl.appendChild(bookComponent.$el)
|
shelfEl.appendChild(bookComponent.$el)
|
||||||
if (this.isSelectionMode) {
|
if (this.isSelectionMode) {
|
||||||
bookComponent.setSelectionMode(true)
|
bookComponent.setSelectionMode(true)
|
||||||
if (this.selectedLibraryItems.includes(bookComponent.libraryItemId) || this.isSelectAll) {
|
if (this.selectedMediaItems.some(i => i.id === bookComponent.libraryItemId) || this.isSelectAll) {
|
||||||
bookComponent.selected = true
|
bookComponent.selected = true
|
||||||
} else {
|
} else {
|
||||||
bookComponent.selected = false
|
bookComponent.selected = false
|
||||||
@@ -89,7 +89,7 @@ export default {
|
|||||||
}
|
}
|
||||||
if (this.isSelectionMode) {
|
if (this.isSelectionMode) {
|
||||||
instance.setSelectionMode(true)
|
instance.setSelectionMode(true)
|
||||||
if (instance.libraryItemId && this.selectedLibraryItems.includes(instance.libraryItemId) || this.isSelectAll) {
|
if (instance.libraryItemId && this.selectedMediaItems.some(i => i.id === instance.libraryItemId) || this.isSelectAll) {
|
||||||
instance.selected = true
|
instance.selected = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,7 +110,6 @@ module.exports = {
|
|||||||
short_name: 'Audiobookshelf',
|
short_name: 'Audiobookshelf',
|
||||||
display: 'standalone',
|
display: 'standalone',
|
||||||
background_color: '#373838',
|
background_color: '#373838',
|
||||||
start_url: '',
|
|
||||||
icons: [
|
icons: [
|
||||||
{
|
{
|
||||||
src: (process.env.ROUTER_BASE_PATH || '') + '/icon.svg',
|
src: (process.env.ROUTER_BASE_PATH || '') + '/icon.svg',
|
||||||
@@ -119,6 +118,8 @@ module.exports = {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
workbox: {
|
workbox: {
|
||||||
|
offline: false,
|
||||||
|
cacheAssets: false,
|
||||||
preCaching: [],
|
preCaching: [],
|
||||||
runtimeCaching: []
|
runtimeCaching: []
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "audiobookshelf-client",
|
"name": "audiobookshelf-client",
|
||||||
"version": "2.2.6",
|
"version": "2.2.8",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "audiobookshelf-client",
|
"name": "audiobookshelf-client",
|
||||||
"version": "2.2.6",
|
"version": "2.2.8",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxtjs/axios": "^5.13.6",
|
"@nuxtjs/axios": "^5.13.6",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "audiobookshelf-client",
|
"name": "audiobookshelf-client",
|
||||||
"version": "2.2.6",
|
"version": "2.2.8",
|
||||||
"description": "Self-hosted audiobook and podcast client",
|
"description": "Self-hosted audiobook and podcast client",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,37 +1,40 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="page-wrapper" class="bg-bg page overflow-y-auto relative" :class="streamLibraryItem ? 'streaming' : ''">
|
<div id="page-wrapper" class="bg-bg page overflow-y-auto relative" :class="streamLibraryItem ? 'streaming' : ''">
|
||||||
<div class="flex items-center py-4 max-w-7xl mx-auto">
|
<div class="flex items-center py-4 px-2 md:px-0 max-w-7xl mx-auto">
|
||||||
<nuxt-link :to="`/item/${libraryItem.id}`" class="hover:underline">
|
<nuxt-link :to="`/item/${libraryItem.id}`" class="hover:underline">
|
||||||
<h1 class="text-xl">{{ title }}</h1>
|
<h1 class="text-lg lg:text-xl">{{ title }}</h1>
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
<button class="w-7 h-7 flex items-center justify-center mx-4 hover:scale-110 duration-100 transform text-gray-200 hover:text-white" @click="editItem">
|
<button class="w-7 h-7 flex items-center justify-center mx-4 hover:scale-110 duration-100 transform text-gray-200 hover:text-white" @click="editItem">
|
||||||
<span class="material-icons text-base">edit</span>
|
<span class="material-icons text-base">edit</span>
|
||||||
</button>
|
</button>
|
||||||
<div class="flex-grow" />
|
<div class="flex-grow hidden md:block" />
|
||||||
<p class="text-base">{{ $strings.LabelDuration }}:</p>
|
<p class="text-base hidden md:block">{{ $strings.LabelDuration }}:</p>
|
||||||
<p class="text-base font-mono ml-8">{{ $secondsToTimestamp(mediaDurationRounded) }}</p>
|
<p class="text-base font-mono ml-4 hidden md:block">{{ $secondsToTimestamp(mediaDurationRounded) }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-wrap-reverse justify-center py-4">
|
<div class="flex flex-wrap-reverse justify-center py-4 px-2">
|
||||||
<div class="w-full max-w-3xl py-4">
|
<div class="w-full max-w-3xl py-4">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
|
<div class="w-12 hidden lg:block" />
|
||||||
<p class="text-lg mb-4 font-semibold">{{ $strings.HeaderChapters }}</p>
|
<p class="text-lg mb-4 font-semibold">{{ $strings.HeaderChapters }}</p>
|
||||||
<div class="flex-grow" />
|
<div class="flex-grow" />
|
||||||
<ui-checkbox v-model="showSecondInputs" checkbox-bg="primary" small label-class="text-sm text-gray-200 pl-1" label="Show seconds" class="mx-2" />
|
<ui-checkbox v-model="showSecondInputs" checkbox-bg="primary" small label-class="text-sm text-gray-200 pl-1" label="Show seconds" class="mx-2" />
|
||||||
<div class="w-40" />
|
<div class="w-32 hidden lg:block" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center mb-3 py-1">
|
<div class="flex items-center mb-3 py-1">
|
||||||
<div class="flex-grow" />
|
<div class="w-12 hidden lg:block" />
|
||||||
<ui-btn v-if="newChapters.length > 1" :color="showShiftTimes ? 'bg' : 'primary'" small @click="showShiftTimes = !showShiftTimes">{{ $strings.ButtonShiftTimes }}</ui-btn>
|
<ui-btn v-if="newChapters.length > 1" :color="showShiftTimes ? 'bg' : 'primary'" small @click="showShiftTimes = !showShiftTimes">{{ $strings.ButtonShiftTimes }}</ui-btn>
|
||||||
<ui-btn color="primary" small class="mx-2" @click="showFindChaptersModal = true">{{ $strings.ButtonLookup }}</ui-btn>
|
<ui-btn color="primary" small class="mx-2" @click="showFindChaptersModal = true">{{ $strings.ButtonLookup }}</ui-btn>
|
||||||
<ui-btn color="success" small @click="saveChapters">{{ $strings.ButtonSave }}</ui-btn>
|
<div class="flex-grow" />
|
||||||
<div class="w-40" />
|
<ui-btn v-if="hasChanges" small class="mx-2" @click.stop="resetChapters">{{ $strings.ButtonReset }}</ui-btn>
|
||||||
|
<ui-btn v-if="hasChanges" color="success" :disabled="!hasChanges" small @click="saveChapters">{{ $strings.ButtonSave }}</ui-btn>
|
||||||
|
<div class="w-32 hidden lg:block" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="overflow-hidden">
|
<div class="overflow-hidden">
|
||||||
<transition name="slide">
|
<transition name="slide">
|
||||||
<div v-if="showShiftTimes" class="flex mb-4">
|
<div v-if="showShiftTimes" class="flex mb-4">
|
||||||
<div class="w-12"></div>
|
<div class="w-12 hidden lg:block" />
|
||||||
<div class="flex-grow">
|
<div class="flex-grow">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<p class="text-sm mb-1 font-semibold pr-2">{{ $strings.LabelTimeToShift }}</p>
|
<p class="text-sm mb-1 font-semibold pr-2">{{ $strings.LabelTimeToShift }}</p>
|
||||||
@@ -42,28 +45,28 @@
|
|||||||
</div>
|
</div>
|
||||||
<p class="text-xs py-1.5 text-gray-300 max-w-md">{{ $strings.NoteChapterEditorTimes }}</p>
|
<p class="text-xs py-1.5 text-gray-300 max-w-md">{{ $strings.NoteChapterEditorTimes }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-40"></div>
|
<div class="w-32 hidden lg:block" />
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex text-xs uppercase text-gray-300 font-semibold mb-2">
|
<div class="flex text-xs uppercase text-gray-300 font-semibold mb-2">
|
||||||
<div class="w-12"></div>
|
<div class="w-8 min-w-8 md:w-12 md:min-w-12"></div>
|
||||||
<div class="w-32 px-2">{{ $strings.LabelStart }}</div>
|
<div class="w-24 min-w-24 md:w-32 md:min-w-32 px-2">{{ $strings.LabelStart }}</div>
|
||||||
<div class="flex-grow px-2">{{ $strings.LabelTitle }}</div>
|
<div class="flex-grow px-2">{{ $strings.LabelTitle }}</div>
|
||||||
<div class="w-40"></div>
|
<div class="w-32"></div>
|
||||||
</div>
|
</div>
|
||||||
<template v-for="chapter in newChapters">
|
<template v-for="chapter in newChapters">
|
||||||
<div :key="chapter.id" class="flex py-1">
|
<div :key="chapter.id" class="flex py-1">
|
||||||
<div class="w-12">#{{ chapter.id + 1 }}</div>
|
<div class="w-8 min-w-8 md:w-12 md:min-w-12">#{{ chapter.id + 1 }}</div>
|
||||||
<div class="w-32 px-1">
|
<div class="w-24 min-w-24 md:w-32 md:min-w-32 px-1">
|
||||||
<ui-text-input v-if="showSecondInputs" v-model="chapter.start" type="number" class="text-xs" @change="checkChapters" />
|
<ui-text-input v-if="showSecondInputs" v-model="chapter.start" type="number" class="text-xs" @change="checkChapters" />
|
||||||
<ui-time-picker v-else class="text-xs" v-model="chapter.start" :show-three-digit-hour="mediaDuration >= 360000" @change="checkChapters" />
|
<ui-time-picker v-else class="text-xs" v-model="chapter.start" :show-three-digit-hour="mediaDuration >= 360000" @change="checkChapters" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-grow px-1">
|
<div class="flex-grow px-1">
|
||||||
<ui-text-input v-model="chapter.title" class="text-xs" />
|
<ui-text-input v-model="chapter.title" @change="checkChapters" class="text-xs" />
|
||||||
</div>
|
</div>
|
||||||
<div class="w-40 px-2 py-1">
|
<div class="w-32 min-w-32 px-2 py-1">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<ui-tooltip :text="$strings.MessageRemoveChapter" direction="bottom">
|
<ui-tooltip :text="$strings.MessageRemoveChapter" direction="bottom">
|
||||||
<button v-if="newChapters.length > 1" class="w-7 h-7 rounded-full flex items-center justify-center text-gray-300 hover:text-error transform hover:scale-110 duration-150" @click="removeChapter(chapter)">
|
<button v-if="newChapters.length > 1" class="w-7 h-7 rounded-full flex items-center justify-center text-gray-300 hover:text-error transform hover:scale-110 duration-150" @click="removeChapter(chapter)">
|
||||||
@@ -96,8 +99,15 @@
|
|||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="w-full max-w-xl py-4">
|
<div class="w-full max-w-xl py-4 px-2">
|
||||||
<p class="text-lg mb-4 font-semibold py-1">{{ $strings.HeaderAudioTracks }}</p>
|
<div class="flex items-center mb-4 py-1">
|
||||||
|
<p class="text-lg font-semibold">{{ $strings.HeaderAudioTracks }}</p>
|
||||||
|
<div class="flex-grow" />
|
||||||
|
<ui-btn small @click="setChaptersFromTracks">{{ $strings.ButtonSetChaptersFromTracks }}</ui-btn>
|
||||||
|
<ui-tooltip :text="$strings.MessageSetChaptersFromTracksDescription" direction="top" class="flex items-center mx-1 cursor-default">
|
||||||
|
<span class="material-icons-outlined text-xl text-gray-200">info</span>
|
||||||
|
</ui-tooltip>
|
||||||
|
</div>
|
||||||
<div class="flex text-xs uppercase text-gray-300 font-semibold mb-2">
|
<div class="flex text-xs uppercase text-gray-300 font-semibold mb-2">
|
||||||
<div class="flex-grow">{{ $strings.LabelFilename }}</div>
|
<div class="flex-grow">{{ $strings.LabelFilename }}</div>
|
||||||
<div class="w-20">{{ $strings.LabelDuration }}</div>
|
<div class="w-20">{{ $strings.LabelDuration }}</div>
|
||||||
@@ -177,8 +187,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex items-center pt-2">
|
<div class="flex items-center pt-2">
|
||||||
<ui-btn small color="primary" class="mr-1" @click="applyChapterNamesOnly">{{ $strings.ButtonMapChapterTitles }}</ui-btn>
|
<ui-btn small color="primary" class="mr-1" @click="applyChapterNamesOnly">{{ $strings.ButtonMapChapterTitles }}</ui-btn>
|
||||||
<ui-tooltip :text="$strings.MessageMapChapterTitles" direction="top">
|
<ui-tooltip :text="$strings.MessageMapChapterTitles" direction="top" class="flex items-center">
|
||||||
<span class="material-icons-outlined">info</span>
|
<span class="material-icons-outlined text-xl text-gray-200">info</span>
|
||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
<div class="flex-grow" />
|
<div class="flex-grow" />
|
||||||
<ui-btn small color="success" @click="applyChapterData">{{ $strings.ButtonApplyChapters }}</ui-btn>
|
<ui-btn small color="success" @click="applyChapterData">{{ $strings.ButtonApplyChapters }}</ui-btn>
|
||||||
@@ -190,6 +200,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
async asyncData({ store, params, app, redirect, from }) {
|
async asyncData({ store, params, app, redirect, from }) {
|
||||||
if (!store.getters['user/getUserCanUpdate']) {
|
if (!store.getters['user/getUserCanUpdate']) {
|
||||||
@@ -232,7 +244,8 @@ export default {
|
|||||||
showFindChaptersModal: false,
|
showFindChaptersModal: false,
|
||||||
chapterData: null,
|
chapterData: null,
|
||||||
showSecondInputs: false,
|
showSecondInputs: false,
|
||||||
audibleRegions: ['US', 'CA', 'UK', 'AU', 'FR', 'DE', 'JP', 'IT', 'IN', 'ES']
|
audibleRegions: ['US', 'CA', 'UK', 'AU', 'FR', 'DE', 'JP', 'IT', 'IN', 'ES'],
|
||||||
|
hasChanges: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -274,6 +287,23 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
setChaptersFromTracks() {
|
||||||
|
let currentStartTime = 0
|
||||||
|
let index = 0
|
||||||
|
const chapters = []
|
||||||
|
for (const track of this.audioTracks) {
|
||||||
|
chapters.push({
|
||||||
|
id: index++,
|
||||||
|
title: path.basename(track.metadata.filename, path.extname(track.metadata.filename)),
|
||||||
|
start: currentStartTime,
|
||||||
|
end: currentStartTime + track.duration
|
||||||
|
})
|
||||||
|
currentStartTime += track.duration
|
||||||
|
}
|
||||||
|
this.newChapters = chapters
|
||||||
|
|
||||||
|
this.checkChapters()
|
||||||
|
},
|
||||||
shiftChapterTimes() {
|
shiftChapterTimes() {
|
||||||
if (!this.shiftAmount || isNaN(this.shiftAmount) || this.newChapters.length <= 1) {
|
if (!this.shiftAmount || isNaN(this.shiftAmount) || this.newChapters.length <= 1) {
|
||||||
return
|
return
|
||||||
@@ -304,7 +334,6 @@ export default {
|
|||||||
this.$store.commit('showEditModal', this.libraryItem)
|
this.$store.commit('showEditModal', this.libraryItem)
|
||||||
},
|
},
|
||||||
addChapter(chapter) {
|
addChapter(chapter) {
|
||||||
console.log('Add chapter', chapter)
|
|
||||||
const newChapter = {
|
const newChapter = {
|
||||||
id: chapter.id + 1,
|
id: chapter.id + 1,
|
||||||
start: chapter.start,
|
start: chapter.start,
|
||||||
@@ -319,22 +348,40 @@ export default {
|
|||||||
this.checkChapters()
|
this.checkChapters()
|
||||||
},
|
},
|
||||||
checkChapters() {
|
checkChapters() {
|
||||||
var previousStart = 0
|
let previousStart = 0
|
||||||
|
let hasChanges = this.newChapters.length !== this.chapters.length
|
||||||
|
|
||||||
for (let i = 0; i < this.newChapters.length; i++) {
|
for (let i = 0; i < this.newChapters.length; i++) {
|
||||||
this.newChapters[i].id = i
|
this.newChapters[i].id = i
|
||||||
this.newChapters[i].start = Number(this.newChapters[i].start)
|
this.newChapters[i].start = Number(this.newChapters[i].start)
|
||||||
|
|
||||||
if (i === 0 && this.newChapters[i].start !== 0) {
|
if (i === 0 && this.newChapters[i].start !== 0) {
|
||||||
this.newChapters[i].error = 'First chapter must start at 0'
|
this.newChapters[i].error = this.$strings.MessageChapterErrorFirstNotZero
|
||||||
} else if (this.newChapters[i].start <= previousStart && i > 0) {
|
} else if (this.newChapters[i].start <= previousStart && i > 0) {
|
||||||
this.newChapters[i].error = 'Invalid start time must be >= previous chapter start time'
|
this.newChapters[i].error = this.$strings.MessageChapterErrorStartLtPrev
|
||||||
} else if (this.newChapters[i].start >= this.mediaDuration) {
|
} else if (this.newChapters[i].start >= this.mediaDuration) {
|
||||||
this.newChapters[i].error = 'Invalid start time must be < duration'
|
this.newChapters[i].error = this.$strings.MessageChapterErrorStartGteDuration
|
||||||
} else {
|
} else {
|
||||||
this.newChapters[i].error = null
|
this.newChapters[i].error = null
|
||||||
}
|
}
|
||||||
previousStart = this.newChapters[i].start
|
previousStart = this.newChapters[i].start
|
||||||
|
|
||||||
|
if (hasChanges) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingChapter = this.chapters[i]
|
||||||
|
if (existingChapter) {
|
||||||
|
const { start, end, title } = this.newChapters[i]
|
||||||
|
if (start !== existingChapter.start || end !== existingChapter.end || title !== existingChapter.title) {
|
||||||
|
hasChanges = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
hasChanges = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.hasChanges = hasChanges
|
||||||
},
|
},
|
||||||
playChapter(chapter) {
|
playChapter(chapter) {
|
||||||
console.log('Play Chapter', chapter.id)
|
console.log('Play Chapter', chapter.id)
|
||||||
@@ -353,8 +400,6 @@ export default {
|
|||||||
const audioTrack = this.tracks.find((at) => {
|
const audioTrack = this.tracks.find((at) => {
|
||||||
return chapter.start >= at.startOffset && chapter.start < at.startOffset + at.duration
|
return chapter.start >= at.startOffset && chapter.start < at.startOffset + at.duration
|
||||||
})
|
})
|
||||||
console.log('audio track', audioTrack)
|
|
||||||
|
|
||||||
this.selectedChapter = chapter
|
this.selectedChapter = chapter
|
||||||
this.isLoadingChapter = true
|
this.isLoadingChapter = true
|
||||||
|
|
||||||
@@ -369,7 +414,6 @@ export default {
|
|||||||
if (this.$isDev) {
|
if (this.$isDev) {
|
||||||
src = `http://localhost:3333${this.$config.routerBasePath}${src}`
|
src = `http://localhost:3333${this.$config.routerBasePath}${src}`
|
||||||
}
|
}
|
||||||
console.log('src', src)
|
|
||||||
|
|
||||||
audioEl.src = src
|
audioEl.src = src
|
||||||
audioEl.id = 'chapter-audio'
|
audioEl.id = 'chapter-audio'
|
||||||
@@ -413,11 +457,11 @@ export default {
|
|||||||
|
|
||||||
for (let i = 0; i < this.newChapters.length; i++) {
|
for (let i = 0; i < this.newChapters.length; i++) {
|
||||||
if (this.newChapters[i].error) {
|
if (this.newChapters[i].error) {
|
||||||
this.$toast.error('Chapters have errors')
|
this.$toast.error(this.$strings.ToastChaptersHaveErrors)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!this.newChapters[i].title) {
|
if (!this.newChapters[i].title) {
|
||||||
this.$toast.error('Chapters must have titles')
|
this.$toast.error(this.$strings.ToastChaptersMustHaveTitles)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -464,6 +508,8 @@ export default {
|
|||||||
|
|
||||||
this.showFindChaptersModal = false
|
this.showFindChaptersModal = false
|
||||||
this.chapterData = null
|
this.chapterData = null
|
||||||
|
|
||||||
|
this.checkChapters()
|
||||||
},
|
},
|
||||||
applyChapterData() {
|
applyChapterData() {
|
||||||
var index = 0
|
var index = 0
|
||||||
@@ -480,6 +526,8 @@ export default {
|
|||||||
})
|
})
|
||||||
this.showFindChaptersModal = false
|
this.showFindChaptersModal = false
|
||||||
this.chapterData = null
|
this.chapterData = null
|
||||||
|
|
||||||
|
this.checkChapters()
|
||||||
},
|
},
|
||||||
findChapters() {
|
findChapters() {
|
||||||
if (!this.asinInput) {
|
if (!this.asinInput) {
|
||||||
@@ -513,22 +561,38 @@ export default {
|
|||||||
this.$toast.error('Failed to find chapters')
|
this.$toast.error('Failed to find chapters')
|
||||||
this.showFindChaptersModal = false
|
this.showFindChaptersModal = false
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
resetChapters() {
|
||||||
|
const payload = {
|
||||||
|
message: this.$strings.MessageResetChaptersConfirm,
|
||||||
|
callback: (confirmed) => {
|
||||||
|
if (confirmed) {
|
||||||
|
this.initChapters()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
type: 'yesNo'
|
||||||
|
}
|
||||||
|
this.$store.commit('globals/setConfirmPrompt', payload)
|
||||||
|
},
|
||||||
|
initChapters() {
|
||||||
|
this.newChapters = this.chapters.map((c) => ({ ...c }))
|
||||||
|
if (!this.newChapters.length) {
|
||||||
|
this.newChapters = [
|
||||||
|
{
|
||||||
|
id: 0,
|
||||||
|
start: 0,
|
||||||
|
end: this.mediaDuration,
|
||||||
|
title: ''
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
this.checkChapters()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.regionInput = localStorage.getItem('audibleRegion') || 'US'
|
this.regionInput = localStorage.getItem('audibleRegion') || 'US'
|
||||||
this.asinInput = this.mediaMetadata.asin || null
|
this.asinInput = this.mediaMetadata.asin || null
|
||||||
this.newChapters = this.chapters.map((c) => ({ ...c }))
|
this.initChapters()
|
||||||
if (!this.newChapters.length) {
|
|
||||||
this.newChapters = [
|
|
||||||
{
|
|
||||||
id: 0,
|
|
||||||
start: 0,
|
|
||||||
end: this.mediaDuration,
|
|
||||||
title: ''
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.destroyAudioEl()
|
this.destroyAudioEl()
|
||||||
|
|||||||
@@ -91,11 +91,13 @@
|
|||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
async asyncData({ store, redirect, app }) {
|
async asyncData({ store, redirect, app }) {
|
||||||
if (!store.state.selectedLibraryItems.length) {
|
if (!store.state.globals.selectedMediaItems.length) {
|
||||||
return redirect('/')
|
return redirect('/')
|
||||||
}
|
}
|
||||||
var libraryItems = await app.$axios.$post(`/api/items/batch/get`, { libraryItemIds: store.state.selectedLibraryItems }).catch((error) => {
|
|
||||||
var errorMsg = error.response.data || 'Failed to get items'
|
const libraryItemIds = store.state.globals.selectedMediaItems.map((i) => i.id)
|
||||||
|
const libraryItems = await app.$axios.$post(`/api/items/batch/get`, { libraryItemIds }).catch((error) => {
|
||||||
|
const errorMsg = error.response.data || 'Failed to get items'
|
||||||
console.error(errorMsg, error)
|
console.error(errorMsg, error)
|
||||||
return []
|
return []
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -201,9 +201,9 @@
|
|||||||
|
|
||||||
<div class="flex items-center py-4">
|
<div class="flex items-center py-4">
|
||||||
<div class="flex-grow" />
|
<div class="flex-grow" />
|
||||||
<ui-btn color="bg" small :padding-x="4" class="hidden lg:block mr-2" :loading="isPurgingCache" @click.stop="purgeCache">{{ $strings.ButtonPurgeAllCache }}</ui-btn>
|
<ui-btn color="bg" small :padding-x="4" class="mr-2 text-xs md:text-sm" :loading="isPurgingCache" @click.stop="purgeCache">{{ $strings.ButtonPurgeAllCache }}</ui-btn>
|
||||||
<ui-btn color="bg" small :padding-x="4" class="hidden lg:block mr-2" :loading="isPurgingCache" @click.stop="purgeItemsCache">{{ $strings.ButtonPurgeItemsCache }}</ui-btn>
|
<ui-btn color="bg" small :padding-x="4" class="mr-2 text-xs md:text-sm" :loading="isPurgingCache" @click.stop="purgeItemsCache">{{ $strings.ButtonPurgeItemsCache }}</ui-btn>
|
||||||
<ui-btn color="bg" small :padding-x="4" class="hidden lg:block mr-2" :loading="isResettingLibraryItems" @click="resetLibraryItems">{{ $strings.ButtonRemoveAllLibraryItems }}</ui-btn>
|
<ui-btn color="bg" small :padding-x="4" class="mr-2 text-xs md:text-sm" :loading="isResettingLibraryItems" @click="resetLibraryItems">{{ $strings.ButtonRemoveAllLibraryItems }}</ui-btn>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center py-4">
|
<div class="flex items-center py-4">
|
||||||
|
|||||||
@@ -94,13 +94,11 @@ Vue.prototype.$sanitizeSlug = (str) => {
|
|||||||
|
|
||||||
Vue.prototype.$copyToClipboard = (str, ctx) => {
|
Vue.prototype.$copyToClipboard = (str, ctx) => {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
if (!navigator.clipboard) {
|
if (navigator.clipboard) {
|
||||||
navigator.clipboard.writeText(str).then(() => {
|
navigator.clipboard.writeText(str).then(() => {
|
||||||
if (ctx) ctx.$toast.success('Copied to clipboard')
|
if (ctx) ctx.$toast.success('Copied to clipboard')
|
||||||
resolve(true)
|
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
console.error('Clipboard copy failed', str, err)
|
console.error('Clipboard copy failed', str, err)
|
||||||
resolve(false)
|
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
const el = document.createElement('textarea')
|
const el = document.createElement('textarea')
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export const state = () => ({
|
|||||||
selectedPlaylist: null,
|
selectedPlaylist: null,
|
||||||
selectedCollection: null,
|
selectedCollection: null,
|
||||||
selectedAuthor: null,
|
selectedAuthor: null,
|
||||||
|
selectedMediaItems: [],
|
||||||
isCasting: false, // Actively casting
|
isCasting: false, // Actively casting
|
||||||
isChromecastInitialized: false, // Script loadeds
|
isChromecastInitialized: false, // Script loadeds
|
||||||
showBatchQuickMatchModal: false,
|
showBatchQuickMatchModal: false,
|
||||||
@@ -64,6 +65,9 @@ export const getters = {
|
|||||||
return `http://localhost:3333${rootState.routerBasePath}/api/items/${libraryItemId}/cover?token=${userToken}`
|
return `http://localhost:3333${rootState.routerBasePath}/api/items/${libraryItemId}/cover?token=${userToken}`
|
||||||
}
|
}
|
||||||
return `${rootState.routerBasePath}/api/items/${libraryItemId}/cover?token=${userToken}`
|
return `${rootState.routerBasePath}/api/items/${libraryItemId}/cover?token=${userToken}`
|
||||||
|
},
|
||||||
|
getIsBatchSelectingMediaItems: (state) => {
|
||||||
|
return state.selectedMediaItems.length
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,5 +138,24 @@ export const mutations = {
|
|||||||
},
|
},
|
||||||
setShowBatchQuickMatchModal(state, val) {
|
setShowBatchQuickMatchModal(state, val) {
|
||||||
state.showBatchQuickMatchModal = val
|
state.showBatchQuickMatchModal = val
|
||||||
|
},
|
||||||
|
resetSelectedMediaItems(state) {
|
||||||
|
state.selectedMediaItems = []
|
||||||
|
},
|
||||||
|
toggleMediaItemSelected(state, item) {
|
||||||
|
if (state.selectedMediaItems.some(i => i.id === item.id)) {
|
||||||
|
state.selectedMediaItems = state.selectedMediaItems.filter(i => i.id !== item.id)
|
||||||
|
} else {
|
||||||
|
state.selectedMediaItems.push(item)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setMediaItemSelected(state, { item, selected }) {
|
||||||
|
const isAlreadySelected = state.selectedMediaItems.some(i => i.id === item.id)
|
||||||
|
if (isAlreadySelected && !selected) {
|
||||||
|
state.selectedMediaItems = state.selectedMediaItems.filter(i => i.id !== item.id)
|
||||||
|
|
||||||
|
} else if (selected && !isAlreadySelected) {
|
||||||
|
state.selectedMediaItems.push(item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -17,7 +17,6 @@ export const state = () => ({
|
|||||||
showEReader: false,
|
showEReader: false,
|
||||||
selectedLibraryItem: null,
|
selectedLibraryItem: null,
|
||||||
developerMode: false,
|
developerMode: false,
|
||||||
selectedLibraryItems: [],
|
|
||||||
processingBatch: false,
|
processingBatch: false,
|
||||||
previousPath: '/',
|
previousPath: '/',
|
||||||
showExperimentalFeatures: false,
|
showExperimentalFeatures: false,
|
||||||
@@ -29,14 +28,10 @@ export const state = () => ({
|
|||||||
})
|
})
|
||||||
|
|
||||||
export const getters = {
|
export const getters = {
|
||||||
getIsLibraryItemSelected: state => libraryItemId => {
|
|
||||||
return !!state.selectedLibraryItems.includes(libraryItemId)
|
|
||||||
},
|
|
||||||
getServerSetting: state => key => {
|
getServerSetting: state => key => {
|
||||||
if (!state.serverSettings) return null
|
if (!state.serverSettings) return null
|
||||||
return state.serverSettings[key]
|
return state.serverSettings[key]
|
||||||
},
|
},
|
||||||
getNumLibraryItemsSelected: state => state.selectedLibraryItems.length,
|
|
||||||
getLibraryItemIdStreaming: state => {
|
getLibraryItemIdStreaming: state => {
|
||||||
return state.streamLibraryItem ? state.streamLibraryItem.id : null
|
return state.streamLibraryItem ? state.streamLibraryItem.id : null
|
||||||
},
|
},
|
||||||
@@ -217,26 +212,6 @@ export const mutations = {
|
|||||||
setSelectedLibraryItem(state, val) {
|
setSelectedLibraryItem(state, val) {
|
||||||
Vue.set(state, 'selectedLibraryItem', val)
|
Vue.set(state, 'selectedLibraryItem', val)
|
||||||
},
|
},
|
||||||
setSelectedLibraryItems(state, items) {
|
|
||||||
Vue.set(state, 'selectedLibraryItems', items)
|
|
||||||
},
|
|
||||||
toggleLibraryItemSelected(state, itemId) {
|
|
||||||
if (state.selectedLibraryItems.includes(itemId)) {
|
|
||||||
state.selectedLibraryItems = state.selectedLibraryItems.filter(a => a !== itemId)
|
|
||||||
} else {
|
|
||||||
var newSel = state.selectedLibraryItems.concat([itemId])
|
|
||||||
Vue.set(state, 'selectedLibraryItems', newSel)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setLibraryItemSelected(state, { libraryItemId, selected }) {
|
|
||||||
var isThere = state.selectedLibraryItems.includes(libraryItemId)
|
|
||||||
if (isThere && !selected) {
|
|
||||||
state.selectedLibraryItems = state.selectedLibraryItems.filter(a => a !== libraryItemId)
|
|
||||||
} else if (selected && !isThere) {
|
|
||||||
var newSel = state.selectedLibraryItems.concat([libraryItemId])
|
|
||||||
Vue.set(state, 'selectedLibraryItems', newSel)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setProcessingBatch(state, val) {
|
setProcessingBatch(state, val) {
|
||||||
state.processingBatch = val
|
state.processingBatch = val
|
||||||
},
|
},
|
||||||
|
|||||||
+32
-20
@@ -30,11 +30,11 @@
|
|||||||
"ButtonLatest": "Neuste",
|
"ButtonLatest": "Neuste",
|
||||||
"ButtonLibrary": "Bibliothek",
|
"ButtonLibrary": "Bibliothek",
|
||||||
"ButtonLogout": "Abmelden",
|
"ButtonLogout": "Abmelden",
|
||||||
"ButtonLookup": "Nachschlagen",
|
"ButtonLookup": "Online-Suche",
|
||||||
"ButtonManageTracks": "Tracks verwalten",
|
"ButtonManageTracks": "Tracks verwalten",
|
||||||
"ButtonMapChapterTitles": "Kapitelüberschriften zuordnen",
|
"ButtonMapChapterTitles": "Kapitelüberschriften zuordnen",
|
||||||
"ButtonMatchAllAuthors": "Online-Abgleich aller Autoren",
|
"ButtonMatchAllAuthors": "Online-Suche aller Autoren",
|
||||||
"ButtonMatchBooks": "Online-Abgleich aller Hörbücher",
|
"ButtonMatchBooks": "Online-Suche aller Hörbücher",
|
||||||
"ButtonNevermind": "Vergiss es",
|
"ButtonNevermind": "Vergiss es",
|
||||||
"ButtonOk": "Ok",
|
"ButtonOk": "Ok",
|
||||||
"ButtonOpenFeed": "Feed öffnen",
|
"ButtonOpenFeed": "Feed öffnen",
|
||||||
@@ -65,7 +65,8 @@
|
|||||||
"ButtonSearch": "Suchen",
|
"ButtonSearch": "Suchen",
|
||||||
"ButtonSelectFolderPath": "Auswahl Ordnerpfad",
|
"ButtonSelectFolderPath": "Auswahl Ordnerpfad",
|
||||||
"ButtonSeries": "Serien",
|
"ButtonSeries": "Serien",
|
||||||
"ButtonShiftTimes": "Arbeitszeiten",
|
"ButtonSetChaptersFromTracks": "Kapitelerstellung aus Audiodateien",
|
||||||
|
"ButtonShiftTimes": "Zeitverschiebung",
|
||||||
"ButtonShow": "Anzeigen",
|
"ButtonShow": "Anzeigen",
|
||||||
"ButtonStartM4BEncode": "M4B-Kodierung starten",
|
"ButtonStartM4BEncode": "M4B-Kodierung starten",
|
||||||
"ButtonStartMetadataEmbed": "Metadateneinbettung starten",
|
"ButtonStartMetadataEmbed": "Metadateneinbettung starten",
|
||||||
@@ -80,7 +81,7 @@
|
|||||||
"HeaderAdvanced": "Erweitert",
|
"HeaderAdvanced": "Erweitert",
|
||||||
"HeaderAppriseNotificationSettings": "Apprise Benachrichtigungseinstellungen",
|
"HeaderAppriseNotificationSettings": "Apprise Benachrichtigungseinstellungen",
|
||||||
"HeaderAudiobookTools": "Hörbuch-Dateiverwaltungstools",
|
"HeaderAudiobookTools": "Hörbuch-Dateiverwaltungstools",
|
||||||
"HeaderAudioTracks": "Audio-Tracks",
|
"HeaderAudioTracks": "Audiodateien",
|
||||||
"HeaderBackups": "Sicherungen",
|
"HeaderBackups": "Sicherungen",
|
||||||
"HeaderChangePassword": "Passwort ändern",
|
"HeaderChangePassword": "Passwort ändern",
|
||||||
"HeaderChapters": "Kapitel",
|
"HeaderChapters": "Kapitel",
|
||||||
@@ -103,7 +104,7 @@
|
|||||||
"HeaderListeningStats": "Hörstatistiken",
|
"HeaderListeningStats": "Hörstatistiken",
|
||||||
"HeaderLogin": "Anmeldung",
|
"HeaderLogin": "Anmeldung",
|
||||||
"HeaderLogs": "Protokolle",
|
"HeaderLogs": "Protokolle",
|
||||||
"HeaderMatch": "Online-Abgleich",
|
"HeaderMatch": "Online-Suche",
|
||||||
"HeaderMetadataToEmbed": "Einzubettende Metadaten",
|
"HeaderMetadataToEmbed": "Einzubettende Metadaten",
|
||||||
"HeaderNewAccount": "Neues Konto",
|
"HeaderNewAccount": "Neues Konto",
|
||||||
"HeaderNewLibrary": "Neue Bibliothek",
|
"HeaderNewLibrary": "Neue Bibliothek",
|
||||||
@@ -200,7 +201,7 @@
|
|||||||
"LabelEpisode": "Episode",
|
"LabelEpisode": "Episode",
|
||||||
"LabelEpisodeTitle": "Episodentitel",
|
"LabelEpisodeTitle": "Episodentitel",
|
||||||
"LabelEpisodeType": "Episodentyp",
|
"LabelEpisodeType": "Episodentyp",
|
||||||
"LabelExplicit": "Explizit <br />(Altersbeschränkung)",
|
"LabelExplicit": "Explizit (Altersbeschränkung)",
|
||||||
"LabelFeedURL": "Feed URL",
|
"LabelFeedURL": "Feed URL",
|
||||||
"LabelFile": "Datei",
|
"LabelFile": "Datei",
|
||||||
"LabelFileBirthtime": "Datei Geburtsdatum",
|
"LabelFileBirthtime": "Datei Geburtsdatum",
|
||||||
@@ -344,9 +345,9 @@
|
|||||||
"LabelSettingsSquareBookCovers": "Benutze quadratische Titelbilder",
|
"LabelSettingsSquareBookCovers": "Benutze quadratische Titelbilder",
|
||||||
"LabelSettingsSquareBookCoversHelp": "Bevorzugen quadratische Titelbilder gegenüber den Standardtielbildern im Verhältnis 1,6:1",
|
"LabelSettingsSquareBookCoversHelp": "Bevorzugen quadratische Titelbilder gegenüber den Standardtielbildern im Verhältnis 1,6:1",
|
||||||
"LabelSettingsStoreCoversWithItem": "Titelbilder im Hörbuchordner speichern",
|
"LabelSettingsStoreCoversWithItem": "Titelbilder im Hörbuchordner speichern",
|
||||||
"LabelSettingsStoreCoversWithItemHelp": "Standardmäßig werden die Titelbilder in /metadata/items gespeichert. Wenn diese Option aktiviert wird, werden die Titelbilder in dem selben Ordner, in welchem auch das zugehörige Hörbuch gespeichert ist, gespeichert. Es wird nur eine Datei mit dem Namen \"cover\" gespeichert.",
|
"LabelSettingsStoreCoversWithItemHelp": "Standardmäßig werden die Titelbilder in /metadata/items gespeichert. Wenn diese Option aktiviert ist, werden die Titelbilder als jpg Datei in dem gleichen Ordner gespeichert in welchem sich auch das Hörbuch befindet. Es wird immer nur eine Datei mit dem Namen \"cover.jpg\" gespeichert.",
|
||||||
"LabelSettingsStoreMetadataWithItem": "Metadaten als OPF-Datei im Hörbuchordner speichern",
|
"LabelSettingsStoreMetadataWithItem": "Metadaten (OPF-Datei) im Hörbuchordner speichern",
|
||||||
"LabelSettingsStoreMetadataWithItemHelp": "Standardmäßig werden die Metadaten in /metadata/items gespeichert. Wenn diese Option aktiviert wird, werden die Metadaten in dem selben Ordner, in welchem auch das zugehörige Hörbuch gespeichert ist, gespeichert. Es wird eine Datei mit der Endung \".abs\" gespeichert.",
|
"LabelSettingsStoreMetadataWithItemHelp": "Standardmäßig werden die Metadaten in /metadata/items gespeichert. Wenn diese Option aktiviert ist, werden die Metadaten als OPF-Datei in dem gleichen Ordner gespeichert in welchem sich auch das Hörbuch befindet. Es wird immer nur eine Datei mit dem Namen \"matadata.abs\" gespeichert.",
|
||||||
"LabelShowAll": "Alles anzeigen",
|
"LabelShowAll": "Alles anzeigen",
|
||||||
"LabelSize": "Größe",
|
"LabelSize": "Größe",
|
||||||
"LabelSleepTimer": "Einschlaf-Timer",
|
"LabelSleepTimer": "Einschlaf-Timer",
|
||||||
@@ -389,6 +390,9 @@
|
|||||||
"LabelTotalTimeListened": "Gehörte Gesamtzeit",
|
"LabelTotalTimeListened": "Gehörte Gesamtzeit",
|
||||||
"LabelTrackFromFilename": "Titel von Dateiname",
|
"LabelTrackFromFilename": "Titel von Dateiname",
|
||||||
"LabelTrackFromMetadata": "Titel aus Metadaten",
|
"LabelTrackFromMetadata": "Titel aus Metadaten",
|
||||||
|
"LabelTracks": "Dateien",
|
||||||
|
"LabelTracksMultiTrack": "Mehrfachdatei",
|
||||||
|
"LabelTracksSingleTrack": "Einzeldatei",
|
||||||
"LabelType": "Typ",
|
"LabelType": "Typ",
|
||||||
"LabelUnknown": "Unbekannt",
|
"LabelUnknown": "Unbekannt",
|
||||||
"LabelUpdateCover": "Titelbild aktualisieren",
|
"LabelUpdateCover": "Titelbild aktualisieren",
|
||||||
@@ -398,8 +402,8 @@
|
|||||||
"LabelUpdateDetailsHelp": "Erlaube das Überschreiben bestehender Details für die ausgewählten Hörbücher wenn eine Übereinstimmung gefunden wird",
|
"LabelUpdateDetailsHelp": "Erlaube das Überschreiben bestehender Details für die ausgewählten Hörbücher wenn eine Übereinstimmung gefunden wird",
|
||||||
"LabelUploaderDragAndDrop": "Ziehen und Ablegen von Dateien oder Ordnern",
|
"LabelUploaderDragAndDrop": "Ziehen und Ablegen von Dateien oder Ordnern",
|
||||||
"LabelUploaderDropFiles": "Dateien löschen",
|
"LabelUploaderDropFiles": "Dateien löschen",
|
||||||
"LabelUseChapterTrack": "Kapitelverfolgung verwenden",
|
"LabelUseChapterTrack": "Kapiteldatei verwenden",
|
||||||
"LabelUseFullTrack": "Gesamten Track verwenden",
|
"LabelUseFullTrack": "Gesamte Datei verwenden",
|
||||||
"LabelUser": "Benutzer",
|
"LabelUser": "Benutzer",
|
||||||
"LabelUsername": "Benutzername",
|
"LabelUsername": "Benutzername",
|
||||||
"LabelValue": "Wert",
|
"LabelValue": "Wert",
|
||||||
@@ -411,17 +415,20 @@
|
|||||||
"LabelWeekdaysToRun": "Wochentage für die Ausführung",
|
"LabelWeekdaysToRun": "Wochentage für die Ausführung",
|
||||||
"LabelYourAudiobookDuration": "Laufzeit Ihres Hörbuchs",
|
"LabelYourAudiobookDuration": "Laufzeit Ihres Hörbuchs",
|
||||||
"LabelYourBookmarks": "Lesezeichen",
|
"LabelYourBookmarks": "Lesezeichen",
|
||||||
"LabelYourPlaylists": "Your Playlists",
|
"LabelYourPlaylists": "Eigene Playlists",
|
||||||
"LabelYourProgress": "Fortschritt",
|
"LabelYourProgress": "Fortschritt",
|
||||||
"MessageAddToPlayerQueue": "Add to player queue",
|
"MessageAddToPlayerQueue": "Zur Abspielwarteliste hinzufügen",
|
||||||
"MessageAppriseDescription": "Um diese Funktion nutzen zu können, müssen Sie eine Instanz von <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> laufen haben oder eine API verwenden welche dieselbe Anfragen bearbeiten kann. <br />Die Apprise API Url muss der vollständige URL-Pfad sein, an den die Benachrichtigung gesendet werden soll, z.B. wenn Ihre API-Instanz unter <code>http://192.168.1.1:8337</code> läuft, würden Sie <code>http://192.168.1.1:8337/notify</code> eingeben.",
|
"MessageAppriseDescription": "Um diese Funktion nutzen zu können, müssen Sie eine Instanz von <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> laufen haben oder eine API verwenden welche dieselbe Anfragen bearbeiten kann. <br />Die Apprise API Url muss der vollständige URL-Pfad sein, an den die Benachrichtigung gesendet werden soll, z.B. wenn Ihre API-Instanz unter <code>http://192.168.1.1:8337</code> läuft, würden Sie <code>http://192.168.1.1:8337/notify</code> eingeben.",
|
||||||
"MessageBackupsDescription": "In Sicherungen werden Benutzer, Benutzerfortschritte, Details zu den Bibliotheksobjekten, Servereinstellungen und Bilder gespeichert <code>/metadata/items</code> & <code>/metadata/authors</code>. Die Sicherungen enthalten keine Dateien welche in Ihren Bibliotheksordnern gespeichert sind.",
|
"MessageBackupsDescription": "In einer Sicherung werden Benutzer, Benutzerfortschritte, Details zu den Bibliotheksobjekten, Servereinstellungen und Bilder welche in <code>/metadata/items</code> & <code>/metadata/authors</code> gespeichert sind gespeichert. Sicherungen enthalten keine Dateien welche in den einzelnen Bibliotheksordnern (Hörbuch-/Podcastordnern) gespeichert sind.",
|
||||||
"MessageBatchQuickMatchDescription": "Der Schnellabgleich versucht, fehlende Titelbilder und Metadaten für die ausgewählten Artikel hinzuzufügen. Aktivieren Sie die nachstehenden Optionen, damit der Schnellabgleich vorhandene Titelbilder und/oder Metadaten überschreiben kann.",
|
"MessageBatchQuickMatchDescription": "Der Schnellabgleich versucht, fehlende Titelbilder und Metadaten für die ausgewählten Artikel hinzuzufügen. Aktivieren Sie die nachstehenden Optionen, damit der Schnellabgleich vorhandene Titelbilder und/oder Metadaten überschreiben kann.",
|
||||||
"MessageBookshelfNoCollections": "Es wurden noch keine Sammlungen erstellt",
|
"MessageBookshelfNoCollections": "Es wurden noch keine Sammlungen erstellt",
|
||||||
"MessageBookshelfNoResultsForFilter": "Keine Ergebnisse für filter \"{0}: {1}\"",
|
"MessageBookshelfNoResultsForFilter": "Keine Ergebnisse für filter \"{0}: {1}\"",
|
||||||
"MessageBookshelfNoRSSFeeds": "Keine RSS-Feeds geöffnet",
|
"MessageBookshelfNoRSSFeeds": "Keine RSS-Feeds geöffnet",
|
||||||
"MessageBookshelfNoSeries": "Keine Serien vorhanden",
|
"MessageBookshelfNoSeries": "Keine Serien vorhanden",
|
||||||
"MessageChapterEndIsAfter": "Das Kapitelende liegt nach dem Ende Ihres Hörbuchs",
|
"MessageChapterEndIsAfter": "Das Kapitelende liegt nach dem Ende Ihres Hörbuchs",
|
||||||
|
"MessageChapterErrorFirstNotZero": "Das erste Kapitel muss bei 0 beginnen",
|
||||||
|
"MessageChapterErrorStartGteDuration": "Die ungültige Startzeit darf nicht größer als die gesamte Hörbuchdauer sein",
|
||||||
|
"MessageChapterErrorStartLtPrev": "Die ungültige Startzeit darf nicht größer oder gleich der Startzeit des vorherigen Kapitels sein",
|
||||||
"MessageChapterStartIsAfter": "Der Kapitelanfang liegt nach dem Ende Ihres Hörbuchs",
|
"MessageChapterStartIsAfter": "Der Kapitelanfang liegt nach dem Ende Ihres Hörbuchs",
|
||||||
"MessageCheckingCron": "Überprüfe cron...",
|
"MessageCheckingCron": "Überprüfe cron...",
|
||||||
"MessageConfirmDeleteBackup": "Sind Sie sicher, dass Sie die Sicherung für {0} löschen wollen?",
|
"MessageConfirmDeleteBackup": "Sind Sie sicher, dass Sie die Sicherung für {0} löschen wollen?",
|
||||||
@@ -452,7 +459,7 @@
|
|||||||
"MessageMarkAsFinished": "Als beendet markieren",
|
"MessageMarkAsFinished": "Als beendet markieren",
|
||||||
"MessageMarkAsNotFinished": "Als nicht abgeschlossen markieren",
|
"MessageMarkAsNotFinished": "Als nicht abgeschlossen markieren",
|
||||||
"MessageMatchBooksDescription": "versucht, Bücher in der Bibliothek mit einem Buch des ausgewählten Suchanbieters abzugleichen und leere Details und das Titelbild auszufüllen. Details werden nicht überschrieben.",
|
"MessageMatchBooksDescription": "versucht, Bücher in der Bibliothek mit einem Buch des ausgewählten Suchanbieters abzugleichen und leere Details und das Titelbild auszufüllen. Details werden nicht überschrieben.",
|
||||||
"MessageNoAudioTracks": "Keine Audiotracks",
|
"MessageNoAudioTracks": "Keine Audiodateien",
|
||||||
"MessageNoAuthors": "Keine Autoren",
|
"MessageNoAuthors": "Keine Autoren",
|
||||||
"MessageNoBackups": "Keine Sicherungen",
|
"MessageNoBackups": "Keine Sicherungen",
|
||||||
"MessageNoBookmarks": "Keine Lesezeichen",
|
"MessageNoBookmarks": "Keine Lesezeichen",
|
||||||
@@ -474,10 +481,11 @@
|
|||||||
"MessageNoPodcastsFound": "Keine Podcasts gefunden",
|
"MessageNoPodcastsFound": "Keine Podcasts gefunden",
|
||||||
"MessageNoResults": "Keine Ergebnisse",
|
"MessageNoResults": "Keine Ergebnisse",
|
||||||
"MessageNoSearchResultsFor": "Keine Suchergebnisse für \"{0}\"",
|
"MessageNoSearchResultsFor": "Keine Suchergebnisse für \"{0}\"",
|
||||||
|
"MessageNoSeries": "Keine Serien",
|
||||||
"MessageNotYetImplemented": "Noch nicht implementiert",
|
"MessageNotYetImplemented": "Noch nicht implementiert",
|
||||||
"MessageNoUpdateNecessary": "Keine Aktualisierung erforderlich",
|
"MessageNoUpdateNecessary": "Keine Aktualisierung erforderlich",
|
||||||
"MessageNoUpdatesWereNecessary": "Keine Aktualisierungen waren notwendig",
|
"MessageNoUpdatesWereNecessary": "Keine Aktualisierungen waren notwendig",
|
||||||
"MessageNoUserPlaylists": "You have no playlists",
|
"MessageNoUserPlaylists": "Keine Wiedergabelisten vorhanden",
|
||||||
"MessageOr": "oder",
|
"MessageOr": "oder",
|
||||||
"MessagePauseChapter": "Kapitelwiedergabe pausieren",
|
"MessagePauseChapter": "Kapitelwiedergabe pausieren",
|
||||||
"MessagePlayChapter": "Kapitelanfang anhören",
|
"MessagePlayChapter": "Kapitelanfang anhören",
|
||||||
@@ -486,13 +494,15 @@
|
|||||||
"MessageRemoveAllItemsWarning": "WARNUNG! Bei dieser Aktion werden alle Bibliotheksobjekte aus der Datenbank entfernt, einschließlich aller Aktualisierungen oder Online-Abgleichs, die Sie vorgenommen haben. Ihre eigentlichen Dateien bleiben davon unberührt. Sind Sie sicher?",
|
"MessageRemoveAllItemsWarning": "WARNUNG! Bei dieser Aktion werden alle Bibliotheksobjekte aus der Datenbank entfernt, einschließlich aller Aktualisierungen oder Online-Abgleichs, die Sie vorgenommen haben. Ihre eigentlichen Dateien bleiben davon unberührt. Sind Sie sicher?",
|
||||||
"MessageRemoveChapter": "Kapitel löschen",
|
"MessageRemoveChapter": "Kapitel löschen",
|
||||||
"MessageRemoveEpisodes": "Entferne {0} Episode(n)",
|
"MessageRemoveEpisodes": "Entferne {0} Episode(n)",
|
||||||
"MessageRemoveFromPlayerQueue": "Remove from player queue",
|
"MessageRemoveFromPlayerQueue": "Aus der Abspielwarteliste löschen Remove from player queue",
|
||||||
"MessageRemoveUserWarning": "Sind Sie sicher, dass Sie den Benutzer \"{0}\" dauerhaft löschen wollen?",
|
"MessageRemoveUserWarning": "Sind Sie sicher, dass Sie den Benutzer \"{0}\" dauerhaft löschen wollen?",
|
||||||
"MessageReportBugsAndContribute": "Fehler melden, Funktionen anfordern und Beiträge leisten auf",
|
"MessageReportBugsAndContribute": "Fehler melden, Funktionen anfordern und Beiträge leisten auf",
|
||||||
|
"MessageResetChaptersConfirm": "Sind Sie sicher, dass Sie die Kapitel zurücksetzen und die vorgenommenen Änderungen rückgängig machen wollen?",
|
||||||
"MessageRestoreBackupConfirm": "Sind Sie sicher, dass Sie die Sicherung wiederherstellen wollen, welche am",
|
"MessageRestoreBackupConfirm": "Sind Sie sicher, dass Sie die Sicherung wiederherstellen wollen, welche am",
|
||||||
"MessageRestoreBackupWarning": "Bei der Wiederherstellung einer Sicherung wird die gesamte Datenbank unter /config und die Titelbilder in /metadata/items und /metadata/authors überschrieben.<br /><br />Bei der Sicherung werden keine Dateien in Ihren Bibliotheksordnern verändert. Wenn Sie die Servereinstellungen aktiviert haben, um Cover und Metadaten in Ihren Bibliotheksordnern zu speichern, werden diese nicht gesichert oder überschrieben.<br /><br />Alle Clients, die Ihren Server nutzen, werden automatisch aktualisiert.",
|
"MessageRestoreBackupWarning": "Bei der Wiederherstellung einer Sicherung wird die gesamte Datenbank unter /config und die Titelbilder in /metadata/items und /metadata/authors überschrieben.<br /><br />Bei der Sicherung werden keine Dateien in Ihren Bibliotheksordnern verändert. Wenn Sie die Servereinstellungen aktiviert haben, um Cover und Metadaten in Ihren Bibliotheksordnern zu speichern, werden diese nicht gesichert oder überschrieben.<br /><br />Alle Clients, die Ihren Server nutzen, werden automatisch aktualisiert.",
|
||||||
"MessageSearchResultsFor": "Suchergebnisse für",
|
"MessageSearchResultsFor": "Suchergebnisse für",
|
||||||
"MessageServerCouldNotBeReached": "Server kann nicht erreicht werden",
|
"MessageServerCouldNotBeReached": "Server kann nicht erreicht werden",
|
||||||
|
"MessageSetChaptersFromTracksDescription": "Kaitelerstellung basiert auf den existierenden einzelnen Audiodateien. Pro existierende Audiodatei wird 1 Kapitel erstellt, wobei deren Kapitelname aus dem Audiodateinamen extrahiert wird",
|
||||||
"MessageStartPlaybackAtTime": "Start der Wiedergabe für \"{0}\" bei {1}?",
|
"MessageStartPlaybackAtTime": "Start der Wiedergabe für \"{0}\" bei {1}?",
|
||||||
"MessageThinking": "Nachdenken...",
|
"MessageThinking": "Nachdenken...",
|
||||||
"MessageUploaderItemFailed": "Hochladen fehlgeschlagen",
|
"MessageUploaderItemFailed": "Hochladen fehlgeschlagen",
|
||||||
@@ -539,6 +549,8 @@
|
|||||||
"ToastBookmarkRemoveSuccess": "Lesezeichen gelöscht",
|
"ToastBookmarkRemoveSuccess": "Lesezeichen gelöscht",
|
||||||
"ToastBookmarkUpdateFailed": "Lesezeichenaktualisierung fehlgeschlagen",
|
"ToastBookmarkUpdateFailed": "Lesezeichenaktualisierung fehlgeschlagen",
|
||||||
"ToastBookmarkUpdateSuccess": "Lesezeichen aktualisiert",
|
"ToastBookmarkUpdateSuccess": "Lesezeichen aktualisiert",
|
||||||
|
"ToastChaptersHaveErrors": "Kapitel sind fehlerhaft",
|
||||||
|
"ToastChaptersMustHaveTitles": "Kapitel benötigen eindeutige Namen",
|
||||||
"ToastCollectionItemsRemoveFailed": "Element(e) konnte(n) nicht aus der Sammlung entfernt werden",
|
"ToastCollectionItemsRemoveFailed": "Element(e) konnte(n) nicht aus der Sammlung entfernt werden",
|
||||||
"ToastCollectionItemsRemoveSuccess": "Element(e) wurde(n) aus der Sammlung entfernt",
|
"ToastCollectionItemsRemoveSuccess": "Element(e) wurde(n) aus der Sammlung entfernt",
|
||||||
"ToastCollectionRemoveFailed": "Sammlung konnte nicht entfernt werden",
|
"ToastCollectionRemoveFailed": "Sammlung konnte nicht entfernt werden",
|
||||||
@@ -565,7 +577,7 @@
|
|||||||
"ToastPlaylistRemoveFailed": "Failed to remove playlist",
|
"ToastPlaylistRemoveFailed": "Failed to remove playlist",
|
||||||
"ToastPlaylistRemoveSuccess": "Playlist removed",
|
"ToastPlaylistRemoveSuccess": "Playlist removed",
|
||||||
"ToastPlaylistUpdateFailed": "Failed to update playlist",
|
"ToastPlaylistUpdateFailed": "Failed to update playlist",
|
||||||
"ToastPlaylistUpdateSuccess": "Playlist updated",
|
"ToastPlaylistUpdateSuccess": "Playlist aktualisieren",
|
||||||
"ToastPodcastCreateFailed": "Podcast konnte nicht erstellt werden",
|
"ToastPodcastCreateFailed": "Podcast konnte nicht erstellt werden",
|
||||||
"ToastPodcastCreateSuccess": "Podcast erfolgreich erstellt",
|
"ToastPodcastCreateSuccess": "Podcast erfolgreich erstellt",
|
||||||
"ToastRemoveItemFromCollectionFailed": "Element/Eintrag konnte nicht aus der Sammlung entfernt werden",
|
"ToastRemoveItemFromCollectionFailed": "Element/Eintrag konnte nicht aus der Sammlung entfernt werden",
|
||||||
@@ -586,4 +598,4 @@
|
|||||||
"WeekdayThursday": "Donnerstag",
|
"WeekdayThursday": "Donnerstag",
|
||||||
"WeekdayTuesday": "Dienstag",
|
"WeekdayTuesday": "Dienstag",
|
||||||
"WeekdayWednesday": "Mittwoch"
|
"WeekdayWednesday": "Mittwoch"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,6 +65,7 @@
|
|||||||
"ButtonSearch": "Search",
|
"ButtonSearch": "Search",
|
||||||
"ButtonSelectFolderPath": "Select Folder Path",
|
"ButtonSelectFolderPath": "Select Folder Path",
|
||||||
"ButtonSeries": "Series",
|
"ButtonSeries": "Series",
|
||||||
|
"ButtonSetChaptersFromTracks": "Set chapters from tracks",
|
||||||
"ButtonShiftTimes": "Shift Times",
|
"ButtonShiftTimes": "Shift Times",
|
||||||
"ButtonShow": "Show",
|
"ButtonShow": "Show",
|
||||||
"ButtonStartM4BEncode": "Start M4B Encode",
|
"ButtonStartM4BEncode": "Start M4B Encode",
|
||||||
@@ -389,6 +390,9 @@
|
|||||||
"LabelTotalTimeListened": "Total Time Listened",
|
"LabelTotalTimeListened": "Total Time Listened",
|
||||||
"LabelTrackFromFilename": "Track from Filename",
|
"LabelTrackFromFilename": "Track from Filename",
|
||||||
"LabelTrackFromMetadata": "Track from Metadata",
|
"LabelTrackFromMetadata": "Track from Metadata",
|
||||||
|
"LabelTracks": "Tracks",
|
||||||
|
"LabelTracksMultiTrack": "Multi-track",
|
||||||
|
"LabelTracksSingleTrack": "Single-track",
|
||||||
"LabelType": "Type",
|
"LabelType": "Type",
|
||||||
"LabelUnknown": "Unknown",
|
"LabelUnknown": "Unknown",
|
||||||
"LabelUpdateCover": "Update Cover",
|
"LabelUpdateCover": "Update Cover",
|
||||||
@@ -422,6 +426,9 @@
|
|||||||
"MessageBookshelfNoRSSFeeds": "No RSS feeds are open",
|
"MessageBookshelfNoRSSFeeds": "No RSS feeds are open",
|
||||||
"MessageBookshelfNoSeries": "You have no series",
|
"MessageBookshelfNoSeries": "You have no series",
|
||||||
"MessageChapterEndIsAfter": "Chapter end is after the end of your audiobook",
|
"MessageChapterEndIsAfter": "Chapter end is after the end of your audiobook",
|
||||||
|
"MessageChapterErrorFirstNotZero": "First chapter must start at 0",
|
||||||
|
"MessageChapterErrorStartGteDuration": "Invalid start time must be less than audiobook duration",
|
||||||
|
"MessageChapterErrorStartLtPrev": "Invalid start time must be greater than or equal to previous chapter start time",
|
||||||
"MessageChapterStartIsAfter": "Chapter start is after the end of your audiobook",
|
"MessageChapterStartIsAfter": "Chapter start is after the end of your audiobook",
|
||||||
"MessageCheckingCron": "Checking cron...",
|
"MessageCheckingCron": "Checking cron...",
|
||||||
"MessageConfirmDeleteBackup": "Are you sure you want to delete backup for {0}?",
|
"MessageConfirmDeleteBackup": "Are you sure you want to delete backup for {0}?",
|
||||||
@@ -474,6 +481,7 @@
|
|||||||
"MessageNoPodcastsFound": "No podcasts found",
|
"MessageNoPodcastsFound": "No podcasts found",
|
||||||
"MessageNoResults": "No Results",
|
"MessageNoResults": "No Results",
|
||||||
"MessageNoSearchResultsFor": "No search results for \"{0}\"",
|
"MessageNoSearchResultsFor": "No search results for \"{0}\"",
|
||||||
|
"MessageNoSeries": "No Series",
|
||||||
"MessageNotYetImplemented": "Not yet implemented",
|
"MessageNotYetImplemented": "Not yet implemented",
|
||||||
"MessageNoUpdateNecessary": "No update necessary",
|
"MessageNoUpdateNecessary": "No update necessary",
|
||||||
"MessageNoUpdatesWereNecessary": "No updates were necessary",
|
"MessageNoUpdatesWereNecessary": "No updates were necessary",
|
||||||
@@ -489,10 +497,12 @@
|
|||||||
"MessageRemoveFromPlayerQueue": "Remove from player queue",
|
"MessageRemoveFromPlayerQueue": "Remove from player queue",
|
||||||
"MessageRemoveUserWarning": "Are you sure you want to permanently delete user \"{0}\"?",
|
"MessageRemoveUserWarning": "Are you sure you want to permanently delete user \"{0}\"?",
|
||||||
"MessageReportBugsAndContribute": "Report bugs, request features, and contribute on",
|
"MessageReportBugsAndContribute": "Report bugs, request features, and contribute on",
|
||||||
|
"MessageResetChaptersConfirm": "Are you sure you want to reset chapters and undo the changes you made?",
|
||||||
"MessageRestoreBackupConfirm": "Are you sure you want to restore the backup created on",
|
"MessageRestoreBackupConfirm": "Are you sure you want to restore the backup created on",
|
||||||
"MessageRestoreBackupWarning": "Restoring a backup will overwrite the entire database located at /config and cover images in /metadata/items & /metadata/authors.<br /><br />Backups do not modify any files in your library folders. If you have enabled server settings to store cover art and metadata in your library folders then those are not backed up or overwritten.<br /><br />All clients using your server will be automatically refreshed.",
|
"MessageRestoreBackupWarning": "Restoring a backup will overwrite the entire database located at /config and cover images in /metadata/items & /metadata/authors.<br /><br />Backups do not modify any files in your library folders. If you have enabled server settings to store cover art and metadata in your library folders then those are not backed up or overwritten.<br /><br />All clients using your server will be automatically refreshed.",
|
||||||
"MessageSearchResultsFor": "Search results for",
|
"MessageSearchResultsFor": "Search results for",
|
||||||
"MessageServerCouldNotBeReached": "Server could not be reached",
|
"MessageServerCouldNotBeReached": "Server could not be reached",
|
||||||
|
"MessageSetChaptersFromTracksDescription": "Set chapters using each audio file as a chapter and chapter title as the audio file name",
|
||||||
"MessageStartPlaybackAtTime": "Start playback for \"{0}\" at {1}?",
|
"MessageStartPlaybackAtTime": "Start playback for \"{0}\" at {1}?",
|
||||||
"MessageThinking": "Thinking...",
|
"MessageThinking": "Thinking...",
|
||||||
"MessageUploaderItemFailed": "Failed to upload",
|
"MessageUploaderItemFailed": "Failed to upload",
|
||||||
@@ -539,6 +549,8 @@
|
|||||||
"ToastBookmarkRemoveSuccess": "Bookmark removed",
|
"ToastBookmarkRemoveSuccess": "Bookmark removed",
|
||||||
"ToastBookmarkUpdateFailed": "Failed to update bookmark",
|
"ToastBookmarkUpdateFailed": "Failed to update bookmark",
|
||||||
"ToastBookmarkUpdateSuccess": "Bookmark updated",
|
"ToastBookmarkUpdateSuccess": "Bookmark updated",
|
||||||
|
"ToastChaptersHaveErrors": "Chapters have errors",
|
||||||
|
"ToastChaptersMustHaveTitles": "Chapters must have titles",
|
||||||
"ToastCollectionItemsRemoveFailed": "Failed to remove item(s) from collection",
|
"ToastCollectionItemsRemoveFailed": "Failed to remove item(s) from collection",
|
||||||
"ToastCollectionItemsRemoveSuccess": "Item(s) removed from collection",
|
"ToastCollectionItemsRemoveSuccess": "Item(s) removed from collection",
|
||||||
"ToastCollectionRemoveFailed": "Failed to remove collection",
|
"ToastCollectionRemoveFailed": "Failed to remove collection",
|
||||||
|
|||||||
@@ -65,6 +65,7 @@
|
|||||||
"ButtonSearch": "Search",
|
"ButtonSearch": "Search",
|
||||||
"ButtonSelectFolderPath": "Select Folder Path",
|
"ButtonSelectFolderPath": "Select Folder Path",
|
||||||
"ButtonSeries": "Series",
|
"ButtonSeries": "Series",
|
||||||
|
"ButtonSetChaptersFromTracks": "Set chapters from tracks",
|
||||||
"ButtonShiftTimes": "Shift Times",
|
"ButtonShiftTimes": "Shift Times",
|
||||||
"ButtonShow": "Show",
|
"ButtonShow": "Show",
|
||||||
"ButtonStartM4BEncode": "Start M4B Encode",
|
"ButtonStartM4BEncode": "Start M4B Encode",
|
||||||
@@ -389,6 +390,9 @@
|
|||||||
"LabelTotalTimeListened": "Total Time Listened",
|
"LabelTotalTimeListened": "Total Time Listened",
|
||||||
"LabelTrackFromFilename": "Track from Filename",
|
"LabelTrackFromFilename": "Track from Filename",
|
||||||
"LabelTrackFromMetadata": "Track from Metadata",
|
"LabelTrackFromMetadata": "Track from Metadata",
|
||||||
|
"LabelTracks": "Tracks",
|
||||||
|
"LabelTracksMultiTrack": "Multi-track",
|
||||||
|
"LabelTracksSingleTrack": "Single-track",
|
||||||
"LabelType": "Type",
|
"LabelType": "Type",
|
||||||
"LabelUnknown": "Unknown",
|
"LabelUnknown": "Unknown",
|
||||||
"LabelUpdateCover": "Update Cover",
|
"LabelUpdateCover": "Update Cover",
|
||||||
@@ -422,6 +426,9 @@
|
|||||||
"MessageBookshelfNoRSSFeeds": "No RSS feeds are open",
|
"MessageBookshelfNoRSSFeeds": "No RSS feeds are open",
|
||||||
"MessageBookshelfNoSeries": "You have no series",
|
"MessageBookshelfNoSeries": "You have no series",
|
||||||
"MessageChapterEndIsAfter": "Chapter end is after the end of your audiobook",
|
"MessageChapterEndIsAfter": "Chapter end is after the end of your audiobook",
|
||||||
|
"MessageChapterErrorFirstNotZero": "First chapter must start at 0",
|
||||||
|
"MessageChapterErrorStartGteDuration": "Invalid start time must be less than audiobook duration",
|
||||||
|
"MessageChapterErrorStartLtPrev": "Invalid start time must be greater than or equal to previous chapter start time",
|
||||||
"MessageChapterStartIsAfter": "Chapter start is after the end of your audiobook",
|
"MessageChapterStartIsAfter": "Chapter start is after the end of your audiobook",
|
||||||
"MessageCheckingCron": "Checking cron...",
|
"MessageCheckingCron": "Checking cron...",
|
||||||
"MessageConfirmDeleteBackup": "Are you sure you want to delete backup for {0}?",
|
"MessageConfirmDeleteBackup": "Are you sure you want to delete backup for {0}?",
|
||||||
@@ -474,6 +481,7 @@
|
|||||||
"MessageNoPodcastsFound": "No podcasts found",
|
"MessageNoPodcastsFound": "No podcasts found",
|
||||||
"MessageNoResults": "No Results",
|
"MessageNoResults": "No Results",
|
||||||
"MessageNoSearchResultsFor": "No search results for \"{0}\"",
|
"MessageNoSearchResultsFor": "No search results for \"{0}\"",
|
||||||
|
"MessageNoSeries": "No Series",
|
||||||
"MessageNotYetImplemented": "Not yet implemented",
|
"MessageNotYetImplemented": "Not yet implemented",
|
||||||
"MessageNoUpdateNecessary": "No update necessary",
|
"MessageNoUpdateNecessary": "No update necessary",
|
||||||
"MessageNoUpdatesWereNecessary": "No updates were necessary",
|
"MessageNoUpdatesWereNecessary": "No updates were necessary",
|
||||||
@@ -489,10 +497,12 @@
|
|||||||
"MessageRemoveFromPlayerQueue": "Remove from player queue",
|
"MessageRemoveFromPlayerQueue": "Remove from player queue",
|
||||||
"MessageRemoveUserWarning": "Are you sure you want to permanently delete user \"{0}\"?",
|
"MessageRemoveUserWarning": "Are you sure you want to permanently delete user \"{0}\"?",
|
||||||
"MessageReportBugsAndContribute": "Report bugs, request features, and contribute on",
|
"MessageReportBugsAndContribute": "Report bugs, request features, and contribute on",
|
||||||
|
"MessageResetChaptersConfirm": "Are you sure you want to reset chapters and undo the changes you made?",
|
||||||
"MessageRestoreBackupConfirm": "Are you sure you want to restore the backup created on",
|
"MessageRestoreBackupConfirm": "Are you sure you want to restore the backup created on",
|
||||||
"MessageRestoreBackupWarning": "Restoring a backup will overwrite the entire database located at /config and cover images in /metadata/items & /metadata/authors.<br /><br />Backups do not modify any files in your library folders. If you have enabled server settings to store cover art and metadata in your library folders then those are not backed up or overwritten.<br /><br />All clients using your server will be automatically refreshed.",
|
"MessageRestoreBackupWarning": "Restoring a backup will overwrite the entire database located at /config and cover images in /metadata/items & /metadata/authors.<br /><br />Backups do not modify any files in your library folders. If you have enabled server settings to store cover art and metadata in your library folders then those are not backed up or overwritten.<br /><br />All clients using your server will be automatically refreshed.",
|
||||||
"MessageSearchResultsFor": "Search results for",
|
"MessageSearchResultsFor": "Search results for",
|
||||||
"MessageServerCouldNotBeReached": "Server could not be reached",
|
"MessageServerCouldNotBeReached": "Server could not be reached",
|
||||||
|
"MessageSetChaptersFromTracksDescription": "Set chapters using each audio file as a chapter and chapter title as the audio file name",
|
||||||
"MessageStartPlaybackAtTime": "Start playback for \"{0}\" at {1}?",
|
"MessageStartPlaybackAtTime": "Start playback for \"{0}\" at {1}?",
|
||||||
"MessageThinking": "Thinking...",
|
"MessageThinking": "Thinking...",
|
||||||
"MessageUploaderItemFailed": "Failed to upload",
|
"MessageUploaderItemFailed": "Failed to upload",
|
||||||
@@ -539,6 +549,8 @@
|
|||||||
"ToastBookmarkRemoveSuccess": "Bookmark removed",
|
"ToastBookmarkRemoveSuccess": "Bookmark removed",
|
||||||
"ToastBookmarkUpdateFailed": "Failed to update bookmark",
|
"ToastBookmarkUpdateFailed": "Failed to update bookmark",
|
||||||
"ToastBookmarkUpdateSuccess": "Bookmark updated",
|
"ToastBookmarkUpdateSuccess": "Bookmark updated",
|
||||||
|
"ToastChaptersHaveErrors": "Chapters have errors",
|
||||||
|
"ToastChaptersMustHaveTitles": "Chapters must have titles",
|
||||||
"ToastCollectionItemsRemoveFailed": "Failed to remove item(s) from collection",
|
"ToastCollectionItemsRemoveFailed": "Failed to remove item(s) from collection",
|
||||||
"ToastCollectionItemsRemoveSuccess": "Item(s) removed from collection",
|
"ToastCollectionItemsRemoveSuccess": "Item(s) removed from collection",
|
||||||
"ToastCollectionRemoveFailed": "Failed to remove collection",
|
"ToastCollectionRemoveFailed": "Failed to remove collection",
|
||||||
|
|||||||
+29
-17
@@ -41,7 +41,7 @@
|
|||||||
"ButtonOpenManager": "Ouvrir le Gestionnaire",
|
"ButtonOpenManager": "Ouvrir le Gestionnaire",
|
||||||
"ButtonPlay": "Ecouter",
|
"ButtonPlay": "Ecouter",
|
||||||
"ButtonPlaying": "En Lecture",
|
"ButtonPlaying": "En Lecture",
|
||||||
"ButtonPlaylists": "Playlists",
|
"ButtonPlaylists": "Listes de Lecture",
|
||||||
"ButtonPurgeAllCache": "Purger Tout le Cache",
|
"ButtonPurgeAllCache": "Purger Tout le Cache",
|
||||||
"ButtonPurgeItemsCache": "Purger le Cache des Articles",
|
"ButtonPurgeItemsCache": "Purger le Cache des Articles",
|
||||||
"ButtonPurgeMediaProgress": "Purger la Progression des Médias",
|
"ButtonPurgeMediaProgress": "Purger la Progression des Médias",
|
||||||
@@ -65,6 +65,7 @@
|
|||||||
"ButtonSearch": "Rechercher",
|
"ButtonSearch": "Rechercher",
|
||||||
"ButtonSelectFolderPath": "Sélectionner le Chemin du Dossier",
|
"ButtonSelectFolderPath": "Sélectionner le Chemin du Dossier",
|
||||||
"ButtonSeries": "Séries",
|
"ButtonSeries": "Séries",
|
||||||
|
"ButtonSetChaptersFromTracks": "Positionner les Chapitre par rapports aux Pistes",
|
||||||
"ButtonShiftTimes": "Décaler le Temps",
|
"ButtonShiftTimes": "Décaler le Temps",
|
||||||
"ButtonShow": "Montrer",
|
"ButtonShow": "Montrer",
|
||||||
"ButtonStartM4BEncode": "Démarrer l'Encodage M4B",
|
"ButtonStartM4BEncode": "Démarrer l'Encodage M4B",
|
||||||
@@ -112,8 +113,8 @@
|
|||||||
"HeaderOtherFiles": "Autres Fichiers",
|
"HeaderOtherFiles": "Autres Fichiers",
|
||||||
"HeaderPermissions": "Permissions",
|
"HeaderPermissions": "Permissions",
|
||||||
"HeaderPlayerQueue": "Liste d'Ecoute",
|
"HeaderPlayerQueue": "Liste d'Ecoute",
|
||||||
"HeaderPlaylist": "Playlist",
|
"HeaderPlaylist": "Liste de Lecture",
|
||||||
"HeaderPlaylistItems": "Playlist Items",
|
"HeaderPlaylistItems": "Elements de la Liste de Lecture",
|
||||||
"HeaderPodcastsToAdd": "Podcasts à Ajouter",
|
"HeaderPodcastsToAdd": "Podcasts à Ajouter",
|
||||||
"HeaderPreviewCover": "Prévisualiser la Couverture",
|
"HeaderPreviewCover": "Prévisualiser la Couverture",
|
||||||
"HeaderRemoveEpisode": "Supprimer l'Episode",
|
"HeaderRemoveEpisode": "Supprimer l'Episode",
|
||||||
@@ -150,8 +151,8 @@
|
|||||||
"LabelAddedAt": "Date d'Ajout",
|
"LabelAddedAt": "Date d'Ajout",
|
||||||
"LabelAddToCollection": "Ajouter à la Collection",
|
"LabelAddToCollection": "Ajouter à la Collection",
|
||||||
"LabelAddToCollectionBatch": "Ajout de {0} Livres à la Collection",
|
"LabelAddToCollectionBatch": "Ajout de {0} Livres à la Collection",
|
||||||
"LabelAddToPlaylist": "Add to Playlist",
|
"LabelAddToPlaylist": "Ajouter à la Liste de Lecture",
|
||||||
"LabelAddToPlaylistBatch": "Add {0} Items to Playlist",
|
"LabelAddToPlaylistBatch": "{0} Elements Ajoutés à la Liste de Lecture",
|
||||||
"LabelAll": "Tout",
|
"LabelAll": "Tout",
|
||||||
"LabelAllUsers": "Tous les Utilisateurs",
|
"LabelAllUsers": "Tous les Utilisateurs",
|
||||||
"LabelAuthor": "Auteur",
|
"LabelAuthor": "Auteur",
|
||||||
@@ -287,7 +288,7 @@
|
|||||||
"LabelPermissionsUpdate": "Peut Mettre à Jour",
|
"LabelPermissionsUpdate": "Peut Mettre à Jour",
|
||||||
"LabelPermissionsUpload": "Peut Téléverser",
|
"LabelPermissionsUpload": "Peut Téléverser",
|
||||||
"LabelPhotoPathURL": "Chemin/URL des photos",
|
"LabelPhotoPathURL": "Chemin/URL des photos",
|
||||||
"LabelPlaylists": "Playlists",
|
"LabelPlaylists": "Listes de Lecture",
|
||||||
"LabelPlayMethod": "Méthode d'Ecoute",
|
"LabelPlayMethod": "Méthode d'Ecoute",
|
||||||
"LabelPodcast": "Podcast",
|
"LabelPodcast": "Podcast",
|
||||||
"LabelPodcasts": "Podcasts",
|
"LabelPodcasts": "Podcasts",
|
||||||
@@ -389,6 +390,9 @@
|
|||||||
"LabelTotalTimeListened": "Temps d'Ecoute Total",
|
"LabelTotalTimeListened": "Temps d'Ecoute Total",
|
||||||
"LabelTrackFromFilename": "Piste depuis le Fichier",
|
"LabelTrackFromFilename": "Piste depuis le Fichier",
|
||||||
"LabelTrackFromMetadata": "Piste depuis les Métadonnées",
|
"LabelTrackFromMetadata": "Piste depuis les Métadonnées",
|
||||||
|
"LabelTracks": "Pistes",
|
||||||
|
"LabelTracksMultiTrack": "Piste Multiple",
|
||||||
|
"LabelTracksSingleTrack": "Piste Simple",
|
||||||
"LabelType": "Type",
|
"LabelType": "Type",
|
||||||
"LabelUnknown": "Inconnu",
|
"LabelUnknown": "Inconnu",
|
||||||
"LabelUpdateCover": "Mettre à jour la Couverture",
|
"LabelUpdateCover": "Mettre à jour la Couverture",
|
||||||
@@ -411,9 +415,9 @@
|
|||||||
"LabelWeekdaysToRun": "Jours de la semaine à exécuter",
|
"LabelWeekdaysToRun": "Jours de la semaine à exécuter",
|
||||||
"LabelYourAudiobookDuration": "Durée de vos Livres Audios",
|
"LabelYourAudiobookDuration": "Durée de vos Livres Audios",
|
||||||
"LabelYourBookmarks": "Vos Signets",
|
"LabelYourBookmarks": "Vos Signets",
|
||||||
"LabelYourPlaylists": "Your Playlists",
|
"LabelYourPlaylists": "Vos Listes de Lecture",
|
||||||
"LabelYourProgress": "Votre Progression",
|
"LabelYourProgress": "Votre Progression",
|
||||||
"MessageAddToPlayerQueue": "Add to player queue",
|
"MessageAddToPlayerQueue": "Ajouter en Queue d'Ecoute",
|
||||||
"MessageAppriseDescription": "Nécessite une instance d'<a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">API Apprise</a> pour utiliser cette fonctionnalité ou une api qui prend en charge les mêmes requêtes. <br />L'URL de l'API Apprise doit comprendre le chemin complet pour envoyer la notification. Par exemple, si votre instance écoute sur <code>http://192.168.1.1:8337</code> alors vous devez mettre <code>http://192.168.1.1:8337/notify</code>.",
|
"MessageAppriseDescription": "Nécessite une instance d'<a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">API Apprise</a> pour utiliser cette fonctionnalité ou une api qui prend en charge les mêmes requêtes. <br />L'URL de l'API Apprise doit comprendre le chemin complet pour envoyer la notification. Par exemple, si votre instance écoute sur <code>http://192.168.1.1:8337</code> alors vous devez mettre <code>http://192.168.1.1:8337/notify</code>.",
|
||||||
"MessageBackupsDescription": "Les Sauvegardes incluent les utilisateurs, la progression de lecture par utilisateur, les détails des articles des bibliothèques, les paramètres du serveur et les images sauvegardées. Les Sauvegardes n'incluent pas les fichiers de votre bibliothèque.",
|
"MessageBackupsDescription": "Les Sauvegardes incluent les utilisateurs, la progression de lecture par utilisateur, les détails des articles des bibliothèques, les paramètres du serveur et les images sauvegardées. Les Sauvegardes n'incluent pas les fichiers de votre bibliothèque.",
|
||||||
"MessageBatchQuickMatchDescription": "La Recherche par Correspondance Rapide tentera d'ajouter les couvertures et les métadonnées manquantes pour les articles sélectionnés. Activer l'option suivante pour autoriser la Recherche par Correspondance à écraser les données existantes.",
|
"MessageBatchQuickMatchDescription": "La Recherche par Correspondance Rapide tentera d'ajouter les couvertures et les métadonnées manquantes pour les articles sélectionnés. Activer l'option suivante pour autoriser la Recherche par Correspondance à écraser les données existantes.",
|
||||||
@@ -422,6 +426,9 @@
|
|||||||
"MessageBookshelfNoRSSFeeds": "Aucun flux RSS n'est ouvert",
|
"MessageBookshelfNoRSSFeeds": "Aucun flux RSS n'est ouvert",
|
||||||
"MessageBookshelfNoSeries": "Vous n'avez aucune séries",
|
"MessageBookshelfNoSeries": "Vous n'avez aucune séries",
|
||||||
"MessageChapterEndIsAfter": "Le Chapitre Fin est situé à la fin de votre Livre Audio",
|
"MessageChapterEndIsAfter": "Le Chapitre Fin est situé à la fin de votre Livre Audio",
|
||||||
|
"MessageChapterErrorFirstNotZero": "Le premier capitre doit débuter à 0",
|
||||||
|
"MessageChapterErrorStartGteDuration": "Horodatage invalide car il doit débuter avant la fin du livre",
|
||||||
|
"MessageChapterErrorStartLtPrev": "Horodatage invalide car il doit débuter au moins après le précédent chapitre",
|
||||||
"MessageChapterStartIsAfter": "Le Chapitre Début est situé au début de votre Livre Audio",
|
"MessageChapterStartIsAfter": "Le Chapitre Début est situé au début de votre Livre Audio",
|
||||||
"MessageCheckingCron": "Vérification du cron...",
|
"MessageCheckingCron": "Vérification du cron...",
|
||||||
"MessageConfirmDeleteBackup": "Etes vous certain de vouloir supprimer la Sauvegarde de {0}?",
|
"MessageConfirmDeleteBackup": "Etes vous certain de vouloir supprimer la Sauvegarde de {0}?",
|
||||||
@@ -431,7 +438,7 @@
|
|||||||
"MessageConfirmRemoveCollection": "Etes vous certain de vouloir supprimer la collection \"{0}\"?",
|
"MessageConfirmRemoveCollection": "Etes vous certain de vouloir supprimer la collection \"{0}\"?",
|
||||||
"MessageConfirmRemoveEpisode": "Etes vous certain de vouloir supprimer l'épisode \"{0}\"?",
|
"MessageConfirmRemoveEpisode": "Etes vous certain de vouloir supprimer l'épisode \"{0}\"?",
|
||||||
"MessageConfirmRemoveEpisodes": "Etes vous certain de vouloir supprimer {0} épisodes?",
|
"MessageConfirmRemoveEpisodes": "Etes vous certain de vouloir supprimer {0} épisodes?",
|
||||||
"MessageConfirmRemovePlaylist": "Are you sure you want to remove your playlist \"{0}\"?",
|
"MessageConfirmRemovePlaylist": "Etes vous certain de vouloir supprimer la liste de lecture \"{0}\"?",
|
||||||
"MessageDownloadingEpisode": "Téléchargement de l'épisode",
|
"MessageDownloadingEpisode": "Téléchargement de l'épisode",
|
||||||
"MessageDragFilesIntoTrackOrder": "Faire glisser les fichiers dans l'ordre correct",
|
"MessageDragFilesIntoTrackOrder": "Faire glisser les fichiers dans l'ordre correct",
|
||||||
"MessageEmbedFinished": "Intégration Terminée!",
|
"MessageEmbedFinished": "Intégration Terminée!",
|
||||||
@@ -474,10 +481,11 @@
|
|||||||
"MessageNoPodcastsFound": "Pas de podcasts trouvés",
|
"MessageNoPodcastsFound": "Pas de podcasts trouvés",
|
||||||
"MessageNoResults": "Pas de Résultats",
|
"MessageNoResults": "Pas de Résultats",
|
||||||
"MessageNoSearchResultsFor": "Pas de résultats de recherche pour \"{0}\"",
|
"MessageNoSearchResultsFor": "Pas de résultats de recherche pour \"{0}\"",
|
||||||
|
"MessageNoSeries": "Pas de Séries",
|
||||||
"MessageNotYetImplemented": "Non implémenté",
|
"MessageNotYetImplemented": "Non implémenté",
|
||||||
"MessageNoUpdateNecessary": "Pas de mise à jour nécessaire",
|
"MessageNoUpdateNecessary": "Pas de mise à jour nécessaire",
|
||||||
"MessageNoUpdatesWereNecessary": "Aucune mise à jour n'était nécessaire",
|
"MessageNoUpdatesWereNecessary": "Aucune mise à jour n'était nécessaire",
|
||||||
"MessageNoUserPlaylists": "You have no playlists",
|
"MessageNoUserPlaylists": "Vous n'avez aucune liste de lecture",
|
||||||
"MessageOr": "ou",
|
"MessageOr": "ou",
|
||||||
"MessagePauseChapter": "Suspendre la lecture du chapitre",
|
"MessagePauseChapter": "Suspendre la lecture du chapitre",
|
||||||
"MessagePlayChapter": "Ecouter depuis le début du chapitre",
|
"MessagePlayChapter": "Ecouter depuis le début du chapitre",
|
||||||
@@ -486,13 +494,15 @@
|
|||||||
"MessageRemoveAllItemsWarning": "ATTENTION! Cette action supprimera toute la base de données de la bibliothèque ainsi que les mises à jour ou correspondances qui auraient été effectuées. Cela n'a aucune incidence sur les fichiers de la bibliothèque. Voulez-vous continuer?",
|
"MessageRemoveAllItemsWarning": "ATTENTION! Cette action supprimera toute la base de données de la bibliothèque ainsi que les mises à jour ou correspondances qui auraient été effectuées. Cela n'a aucune incidence sur les fichiers de la bibliothèque. Voulez-vous continuer?",
|
||||||
"MessageRemoveChapter": "Supprimer le chapitre",
|
"MessageRemoveChapter": "Supprimer le chapitre",
|
||||||
"MessageRemoveEpisodes": "Suppression de {0} épisode(s)",
|
"MessageRemoveEpisodes": "Suppression de {0} épisode(s)",
|
||||||
"MessageRemoveFromPlayerQueue": "Remove from player queue",
|
"MessageRemoveFromPlayerQueue": "Supprimer de la liste d'écoute",
|
||||||
"MessageRemoveUserWarning": "Etes-vous certain de vouloir supprimer définitivement l'utilisateur \"{0}\"?",
|
"MessageRemoveUserWarning": "Etes-vous certain de vouloir supprimer définitivement l'utilisateur \"{0}\"?",
|
||||||
"MessageReportBugsAndContribute": "Remonter des anomalies, demander des fonctionnalités et contribuer sur",
|
"MessageReportBugsAndContribute": "Remonter des anomalies, demander des fonctionnalités et contribuer sur",
|
||||||
|
"MessageResetChaptersConfirm": "Etes-vous certain de vouloir réinitialiser les chapitres et annuler les changements effectués?",
|
||||||
"MessageRestoreBackupConfirm": "Etes-vous certain de vouloir restaurer la sauvegarde créée le",
|
"MessageRestoreBackupConfirm": "Etes-vous certain de vouloir restaurer la sauvegarde créée le",
|
||||||
"MessageRestoreBackupWarning": "Restaurer la sauvegarde écrasera la base de donnée située dans le dossier /config ainsi que les images sur /metadata/items & /metadata/authors.<br /><br />Les sauvegardes ne touchent pas aux fichiers de la bibliothèque. Si vous avez activé le paramètre pour sauvegarder les métadonnées et les images de couverture dans le même dossier que les fichiers, ceux-ci ne ni sauvegardés, ni écrasés lors de la restauration.<br /><br />Tous les clients utilisant votre serveur seront automatiquement mis à jour.",
|
"MessageRestoreBackupWarning": "Restaurer la sauvegarde écrasera la base de donnée située dans le dossier /config ainsi que les images sur /metadata/items & /metadata/authors.<br /><br />Les sauvegardes ne touchent pas aux fichiers de la bibliothèque. Si vous avez activé le paramètre pour sauvegarder les métadonnées et les images de couverture dans le même dossier que les fichiers, ceux-ci ne ni sauvegardés, ni écrasés lors de la restauration.<br /><br />Tous les clients utilisant votre serveur seront automatiquement mis à jour.",
|
||||||
"MessageSearchResultsFor": "Résultats de recherche pour",
|
"MessageSearchResultsFor": "Résultats de recherche pour",
|
||||||
"MessageServerCouldNotBeReached": "Serveur inaccessible",
|
"MessageServerCouldNotBeReached": "Serveur inaccessible",
|
||||||
|
"MessageSetChaptersFromTracksDescription": "Positionne un chapitre par fichier audio, avec le titre du fichier comme titre de chapitre",
|
||||||
"MessageStartPlaybackAtTime": "Démarrer la lecture pour \"{0}\" à {1}?",
|
"MessageStartPlaybackAtTime": "Démarrer la lecture pour \"{0}\" à {1}?",
|
||||||
"MessageThinking": "On Réfléchit...",
|
"MessageThinking": "On Réfléchit...",
|
||||||
"MessageUploaderItemFailed": "Echec du téléversement",
|
"MessageUploaderItemFailed": "Echec du téléversement",
|
||||||
@@ -514,7 +524,7 @@
|
|||||||
"NoteUploaderUnsupportedFiles": "Les fichiers non-supportés seront ignorés. En sélectionnant ou déponsant un dossier, les autres fichiers qui ne sont pas un dossier contenant un article seront ignorés.",
|
"NoteUploaderUnsupportedFiles": "Les fichiers non-supportés seront ignorés. En sélectionnant ou déponsant un dossier, les autres fichiers qui ne sont pas un dossier contenant un article seront ignorés.",
|
||||||
"PlaceholderNewCollection": "Nom de la nouvelle collection",
|
"PlaceholderNewCollection": "Nom de la nouvelle collection",
|
||||||
"PlaceholderNewFolderPath": "Nouveau chemin de dossier",
|
"PlaceholderNewFolderPath": "Nouveau chemin de dossier",
|
||||||
"PlaceholderNewPlaylist": "New playlist name",
|
"PlaceholderNewPlaylist": "Nouveau nom de liste de lecture",
|
||||||
"PlaceholderSearch": "Recherche...",
|
"PlaceholderSearch": "Recherche...",
|
||||||
"ToastAccountUpdateFailed": "Echec de la mise à jour du compte",
|
"ToastAccountUpdateFailed": "Echec de la mise à jour du compte",
|
||||||
"ToastAccountUpdateSuccess": "Compte mis à jour",
|
"ToastAccountUpdateSuccess": "Compte mis à jour",
|
||||||
@@ -539,6 +549,8 @@
|
|||||||
"ToastBookmarkRemoveSuccess": "Signet supprimé",
|
"ToastBookmarkRemoveSuccess": "Signet supprimé",
|
||||||
"ToastBookmarkUpdateFailed": "Echec de la mise à jour de signet",
|
"ToastBookmarkUpdateFailed": "Echec de la mise à jour de signet",
|
||||||
"ToastBookmarkUpdateSuccess": "Signet mis à jour",
|
"ToastBookmarkUpdateSuccess": "Signet mis à jour",
|
||||||
|
"ToastChaptersHaveErrors": "Les chapitres contiennent des erreurs",
|
||||||
|
"ToastChaptersMustHaveTitles": "Les chapitre doivent avoir un titre",
|
||||||
"ToastCollectionItemsRemoveFailed": "Echec de la suppression de(s) article(s) de la collection",
|
"ToastCollectionItemsRemoveFailed": "Echec de la suppression de(s) article(s) de la collection",
|
||||||
"ToastCollectionItemsRemoveSuccess": "Article(s) supprimé(s) de la collection",
|
"ToastCollectionItemsRemoveSuccess": "Article(s) supprimé(s) de la collection",
|
||||||
"ToastCollectionRemoveFailed": "Echec de la suppression de la collection",
|
"ToastCollectionRemoveFailed": "Echec de la suppression de la collection",
|
||||||
@@ -562,10 +574,10 @@
|
|||||||
"ToastLibraryScanStarted": "Analyse de la bibliothèque démarrée",
|
"ToastLibraryScanStarted": "Analyse de la bibliothèque démarrée",
|
||||||
"ToastLibraryUpdateFailed": "Echec de la mise à jour de la bibliothèque",
|
"ToastLibraryUpdateFailed": "Echec de la mise à jour de la bibliothèque",
|
||||||
"ToastLibraryUpdateSuccess": "Bibliothèque \"{0}\" mise à jour",
|
"ToastLibraryUpdateSuccess": "Bibliothèque \"{0}\" mise à jour",
|
||||||
"ToastPlaylistRemoveFailed": "Failed to remove playlist",
|
"ToastPlaylistRemoveFailed": "Echec de la suppression de la liste de lecture",
|
||||||
"ToastPlaylistRemoveSuccess": "Playlist removed",
|
"ToastPlaylistRemoveSuccess": "Liste de lecture supprimée",
|
||||||
"ToastPlaylistUpdateFailed": "Failed to update playlist",
|
"ToastPlaylistUpdateFailed": "Echec de la mise à jour de la liste de lecture",
|
||||||
"ToastPlaylistUpdateSuccess": "Playlist updated",
|
"ToastPlaylistUpdateSuccess": "Liste de lecture mise à jour",
|
||||||
"ToastPodcastCreateFailed": "Echec de la création du Podcast",
|
"ToastPodcastCreateFailed": "Echec de la création du Podcast",
|
||||||
"ToastPodcastCreateSuccess": "Podcast créé",
|
"ToastPodcastCreateSuccess": "Podcast créé",
|
||||||
"ToastRemoveItemFromCollectionFailed": "Echec de la suppression de l'article de la collection",
|
"ToastRemoveItemFromCollectionFailed": "Echec de la suppression de l'article de la collection",
|
||||||
@@ -586,4 +598,4 @@
|
|||||||
"WeekdayThursday": "Jeudi",
|
"WeekdayThursday": "Jeudi",
|
||||||
"WeekdayTuesday": "Mardi",
|
"WeekdayTuesday": "Mardi",
|
||||||
"WeekdayWednesday": "Mercredi"
|
"WeekdayWednesday": "Mercredi"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,6 +65,7 @@
|
|||||||
"ButtonSearch": "Traži",
|
"ButtonSearch": "Traži",
|
||||||
"ButtonSelectFolderPath": "Odaberi putanju do folder",
|
"ButtonSelectFolderPath": "Odaberi putanju do folder",
|
||||||
"ButtonSeries": "Serije",
|
"ButtonSeries": "Serije",
|
||||||
|
"ButtonSetChaptersFromTracks": "Set chapters from tracks",
|
||||||
"ButtonShiftTimes": "Pomakni vremena",
|
"ButtonShiftTimes": "Pomakni vremena",
|
||||||
"ButtonShow": "Prikaži",
|
"ButtonShow": "Prikaži",
|
||||||
"ButtonStartM4BEncode": "Pokreni M4B kodiranje",
|
"ButtonStartM4BEncode": "Pokreni M4B kodiranje",
|
||||||
@@ -389,6 +390,9 @@
|
|||||||
"LabelTotalTimeListened": "Sveukupno vrijeme slušanja",
|
"LabelTotalTimeListened": "Sveukupno vrijeme slušanja",
|
||||||
"LabelTrackFromFilename": "Track iz imena datoteke",
|
"LabelTrackFromFilename": "Track iz imena datoteke",
|
||||||
"LabelTrackFromMetadata": "Track iz metapodataka",
|
"LabelTrackFromMetadata": "Track iz metapodataka",
|
||||||
|
"LabelTracks": "Tracks",
|
||||||
|
"LabelTracksMultiTrack": "Multi-track",
|
||||||
|
"LabelTracksSingleTrack": "Single-track",
|
||||||
"LabelType": "Tip",
|
"LabelType": "Tip",
|
||||||
"LabelUnknown": "Nepoznato",
|
"LabelUnknown": "Nepoznato",
|
||||||
"LabelUpdateCover": "Aktualiziraj Cover",
|
"LabelUpdateCover": "Aktualiziraj Cover",
|
||||||
@@ -422,6 +426,9 @@
|
|||||||
"MessageBookshelfNoRSSFeeds": "No RSS feeds are open",
|
"MessageBookshelfNoRSSFeeds": "No RSS feeds are open",
|
||||||
"MessageBookshelfNoSeries": "You have no series",
|
"MessageBookshelfNoSeries": "You have no series",
|
||||||
"MessageChapterEndIsAfter": "Kraj poglavlja je nakon kraja audioknjige.",
|
"MessageChapterEndIsAfter": "Kraj poglavlja je nakon kraja audioknjige.",
|
||||||
|
"MessageChapterErrorFirstNotZero": "First chapter must start at 0",
|
||||||
|
"MessageChapterErrorStartGteDuration": "Invalid start time must be less than audiobook duration",
|
||||||
|
"MessageChapterErrorStartLtPrev": "Invalid start time must be greater than or equal to previous chapter start time",
|
||||||
"MessageChapterStartIsAfter": "Početak poglavlja je nakon kraja audioknjige.",
|
"MessageChapterStartIsAfter": "Početak poglavlja je nakon kraja audioknjige.",
|
||||||
"MessageCheckingCron": "Provjeravam cron...",
|
"MessageCheckingCron": "Provjeravam cron...",
|
||||||
"MessageConfirmDeleteBackup": "Jeste li sigurni da želite obrisati backup za {0}?",
|
"MessageConfirmDeleteBackup": "Jeste li sigurni da želite obrisati backup za {0}?",
|
||||||
@@ -474,6 +481,7 @@
|
|||||||
"MessageNoPodcastsFound": "Nijedan podcast pronađen",
|
"MessageNoPodcastsFound": "Nijedan podcast pronađen",
|
||||||
"MessageNoResults": "Nema rezultata",
|
"MessageNoResults": "Nema rezultata",
|
||||||
"MessageNoSearchResultsFor": "Nema rezultata pretragee za \"{0}\"",
|
"MessageNoSearchResultsFor": "Nema rezultata pretragee za \"{0}\"",
|
||||||
|
"MessageNoSeries": "No Series",
|
||||||
"MessageNotYetImplemented": "Not yet implemented",
|
"MessageNotYetImplemented": "Not yet implemented",
|
||||||
"MessageNoUpdateNecessary": "Aktualiziranje nije potrebno",
|
"MessageNoUpdateNecessary": "Aktualiziranje nije potrebno",
|
||||||
"MessageNoUpdatesWereNecessary": "Aktualiziranje nije bilo potrebno",
|
"MessageNoUpdatesWereNecessary": "Aktualiziranje nije bilo potrebno",
|
||||||
@@ -489,10 +497,12 @@
|
|||||||
"MessageRemoveFromPlayerQueue": "Remove from player queue",
|
"MessageRemoveFromPlayerQueue": "Remove from player queue",
|
||||||
"MessageRemoveUserWarning": "Jeste li sigurni da želite trajno obrisati korisnika \"{0}\"?",
|
"MessageRemoveUserWarning": "Jeste li sigurni da želite trajno obrisati korisnika \"{0}\"?",
|
||||||
"MessageReportBugsAndContribute": "Prijavte bugove, zatržite featurese i doprinosite na",
|
"MessageReportBugsAndContribute": "Prijavte bugove, zatržite featurese i doprinosite na",
|
||||||
|
"MessageResetChaptersConfirm": "Are you sure you want to reset chapters and undo the changes you made?",
|
||||||
"MessageRestoreBackupConfirm": "Jeste li sigurni da želite povratiti backup kreiran",
|
"MessageRestoreBackupConfirm": "Jeste li sigurni da želite povratiti backup kreiran",
|
||||||
"MessageRestoreBackupWarning": "Povračanje backupa će zamijeniti postoječu bazu podataka u /config i slike covera u /metadata/items i /metadata/authors.<br /><br />Backups ne modificiraju nikakve datoteke u folderu od biblioteke. Ako imate uključene server postavke da spremate cover i metapodtake u folderu od biblioteke, onda oni neće biti backupani ili overwritten.<br /><br />Svi klijenti koji koriste tvoj server će biti automatski osvježeni.",
|
"MessageRestoreBackupWarning": "Povračanje backupa će zamijeniti postoječu bazu podataka u /config i slike covera u /metadata/items i /metadata/authors.<br /><br />Backups ne modificiraju nikakve datoteke u folderu od biblioteke. Ako imate uključene server postavke da spremate cover i metapodtake u folderu od biblioteke, onda oni neće biti backupani ili overwritten.<br /><br />Svi klijenti koji koriste tvoj server će biti automatski osvježeni.",
|
||||||
"MessageSearchResultsFor": "Traži rezultate za",
|
"MessageSearchResultsFor": "Traži rezultate za",
|
||||||
"MessageServerCouldNotBeReached": "Server ne može biti kontaktiran",
|
"MessageServerCouldNotBeReached": "Server ne može biti kontaktiran",
|
||||||
|
"MessageSetChaptersFromTracksDescription": "Set chapters using each audio file as a chapter and chapter title as the audio file name",
|
||||||
"MessageStartPlaybackAtTime": "Pokreni reprodukciju za \"{0}\" na {1}?",
|
"MessageStartPlaybackAtTime": "Pokreni reprodukciju za \"{0}\" na {1}?",
|
||||||
"MessageThinking": "Razmišljam...",
|
"MessageThinking": "Razmišljam...",
|
||||||
"MessageUploaderItemFailed": "Upload neuspješan",
|
"MessageUploaderItemFailed": "Upload neuspješan",
|
||||||
@@ -539,6 +549,8 @@
|
|||||||
"ToastBookmarkRemoveSuccess": "Knjižnja bilješka uklonjena",
|
"ToastBookmarkRemoveSuccess": "Knjižnja bilješka uklonjena",
|
||||||
"ToastBookmarkUpdateFailed": "Aktualizacija knjižne bilješke neuspješna",
|
"ToastBookmarkUpdateFailed": "Aktualizacija knjižne bilješke neuspješna",
|
||||||
"ToastBookmarkUpdateSuccess": "Knjižna bilješka aktualizirana",
|
"ToastBookmarkUpdateSuccess": "Knjižna bilješka aktualizirana",
|
||||||
|
"ToastChaptersHaveErrors": "Chapters have errors",
|
||||||
|
"ToastChaptersMustHaveTitles": "Chapters must have titles",
|
||||||
"ToastCollectionItemsRemoveFailed": "Neuspješno brisanje stavke/-i iz kolekcije",
|
"ToastCollectionItemsRemoveFailed": "Neuspješno brisanje stavke/-i iz kolekcije",
|
||||||
"ToastCollectionItemsRemoveSuccess": "Stavka/-e obrisane iz kolekcije",
|
"ToastCollectionItemsRemoveSuccess": "Stavka/-e obrisane iz kolekcije",
|
||||||
"ToastCollectionRemoveFailed": "Brisanje kolekcije neuspješno",
|
"ToastCollectionRemoveFailed": "Brisanje kolekcije neuspješno",
|
||||||
|
|||||||
@@ -65,6 +65,7 @@
|
|||||||
"ButtonSearch": "Cerca",
|
"ButtonSearch": "Cerca",
|
||||||
"ButtonSelectFolderPath": "Seleziona percorso cartella",
|
"ButtonSelectFolderPath": "Seleziona percorso cartella",
|
||||||
"ButtonSeries": "Serie",
|
"ButtonSeries": "Serie",
|
||||||
|
"ButtonSetChaptersFromTracks": "Set chapters from tracks",
|
||||||
"ButtonShiftTimes": "Ricerca veloce",
|
"ButtonShiftTimes": "Ricerca veloce",
|
||||||
"ButtonShow": "Mostra",
|
"ButtonShow": "Mostra",
|
||||||
"ButtonStartM4BEncode": "Inizia L'Encoda del M4B",
|
"ButtonStartM4BEncode": "Inizia L'Encoda del M4B",
|
||||||
@@ -389,6 +390,9 @@
|
|||||||
"LabelTotalTimeListened": "Tempo totale di Ascolto",
|
"LabelTotalTimeListened": "Tempo totale di Ascolto",
|
||||||
"LabelTrackFromFilename": "Traccia da nome file",
|
"LabelTrackFromFilename": "Traccia da nome file",
|
||||||
"LabelTrackFromMetadata": "Traccia da Metadata",
|
"LabelTrackFromMetadata": "Traccia da Metadata",
|
||||||
|
"LabelTracks": "Tracks",
|
||||||
|
"LabelTracksMultiTrack": "Multi-track",
|
||||||
|
"LabelTracksSingleTrack": "Single-track",
|
||||||
"LabelType": "Tipo",
|
"LabelType": "Tipo",
|
||||||
"LabelUnknown": "Sconosciuto",
|
"LabelUnknown": "Sconosciuto",
|
||||||
"LabelUpdateCover": "Aggiornamento Cover",
|
"LabelUpdateCover": "Aggiornamento Cover",
|
||||||
@@ -422,6 +426,9 @@
|
|||||||
"MessageBookshelfNoRSSFeeds": "Nessun RSS feeds aperto",
|
"MessageBookshelfNoRSSFeeds": "Nessun RSS feeds aperto",
|
||||||
"MessageBookshelfNoSeries": "You have no series",
|
"MessageBookshelfNoSeries": "You have no series",
|
||||||
"MessageChapterEndIsAfter": "La fine del capitolo è dopo la fine del tuo audiolibro",
|
"MessageChapterEndIsAfter": "La fine del capitolo è dopo la fine del tuo audiolibro",
|
||||||
|
"MessageChapterErrorFirstNotZero": "First chapter must start at 0",
|
||||||
|
"MessageChapterErrorStartGteDuration": "Invalid start time must be less than audiobook duration",
|
||||||
|
"MessageChapterErrorStartLtPrev": "Invalid start time must be greater than or equal to previous chapter start time",
|
||||||
"MessageChapterStartIsAfter": "L'inizio del capitolo è dopo la fine del tuo audiolibro",
|
"MessageChapterStartIsAfter": "L'inizio del capitolo è dopo la fine del tuo audiolibro",
|
||||||
"MessageCheckingCron": "Controllo cron...",
|
"MessageCheckingCron": "Controllo cron...",
|
||||||
"MessageConfirmDeleteBackup": "Sei sicuro di voler eliminare il backup {0}?",
|
"MessageConfirmDeleteBackup": "Sei sicuro di voler eliminare il backup {0}?",
|
||||||
@@ -474,6 +481,7 @@
|
|||||||
"MessageNoPodcastsFound": "Nessun podcasts trovato",
|
"MessageNoPodcastsFound": "Nessun podcasts trovato",
|
||||||
"MessageNoResults": "Nessun Risultato",
|
"MessageNoResults": "Nessun Risultato",
|
||||||
"MessageNoSearchResultsFor": "Nessun risultato per \"{0}\"",
|
"MessageNoSearchResultsFor": "Nessun risultato per \"{0}\"",
|
||||||
|
"MessageNoSeries": "No Series",
|
||||||
"MessageNotYetImplemented": "Non Ancora Implementato",
|
"MessageNotYetImplemented": "Non Ancora Implementato",
|
||||||
"MessageNoUpdateNecessary": "Nessun aggiornamento necessario",
|
"MessageNoUpdateNecessary": "Nessun aggiornamento necessario",
|
||||||
"MessageNoUpdatesWereNecessary": "Nessun aggiornamento necessario",
|
"MessageNoUpdatesWereNecessary": "Nessun aggiornamento necessario",
|
||||||
@@ -489,10 +497,12 @@
|
|||||||
"MessageRemoveFromPlayerQueue": "Remove from player queue",
|
"MessageRemoveFromPlayerQueue": "Remove from player queue",
|
||||||
"MessageRemoveUserWarning": "Sei sicuro di voler eliminare definitivamente l'utente \"{0}\"?",
|
"MessageRemoveUserWarning": "Sei sicuro di voler eliminare definitivamente l'utente \"{0}\"?",
|
||||||
"MessageReportBugsAndContribute": "Segnala bug, richiedi funzionalità e contribuisci",
|
"MessageReportBugsAndContribute": "Segnala bug, richiedi funzionalità e contribuisci",
|
||||||
|
"MessageResetChaptersConfirm": "Are you sure you want to reset chapters and undo the changes you made?",
|
||||||
"MessageRestoreBackupConfirm": "Sei sicuro di voler ripristinare il backup creato su",
|
"MessageRestoreBackupConfirm": "Sei sicuro di voler ripristinare il backup creato su",
|
||||||
"MessageRestoreBackupWarning": "Il ripristino di un backup sovrascriverà l'intero database situato in /config e sovrascrive le immagini in /metadata/items & /metadata/authors.<br /><br />I backup non modificano alcun file nelle cartelle della libreria. Se hai abilitato le impostazioni del server per archiviare copertine e metadati nelle cartelle della libreria, questi non vengono sottoposti a backup o sovrascritti.<br /><br />Tutti i client che utilizzano il tuo server verranno aggiornati automaticamente.",
|
"MessageRestoreBackupWarning": "Il ripristino di un backup sovrascriverà l'intero database situato in /config e sovrascrive le immagini in /metadata/items & /metadata/authors.<br /><br />I backup non modificano alcun file nelle cartelle della libreria. Se hai abilitato le impostazioni del server per archiviare copertine e metadati nelle cartelle della libreria, questi non vengono sottoposti a backup o sovrascritti.<br /><br />Tutti i client che utilizzano il tuo server verranno aggiornati automaticamente.",
|
||||||
"MessageSearchResultsFor": "cerca risultati per",
|
"MessageSearchResultsFor": "cerca risultati per",
|
||||||
"MessageServerCouldNotBeReached": "Impossibile raggiungere il server",
|
"MessageServerCouldNotBeReached": "Impossibile raggiungere il server",
|
||||||
|
"MessageSetChaptersFromTracksDescription": "Set chapters using each audio file as a chapter and chapter title as the audio file name",
|
||||||
"MessageStartPlaybackAtTime": "Avvia la riproduzione per \"{0}\" a {1}?",
|
"MessageStartPlaybackAtTime": "Avvia la riproduzione per \"{0}\" a {1}?",
|
||||||
"MessageThinking": "Elaborazione...",
|
"MessageThinking": "Elaborazione...",
|
||||||
"MessageUploaderItemFailed": "Caricamento Fallito",
|
"MessageUploaderItemFailed": "Caricamento Fallito",
|
||||||
@@ -539,6 +549,8 @@
|
|||||||
"ToastBookmarkRemoveSuccess": "Segnalibro Rimosso",
|
"ToastBookmarkRemoveSuccess": "Segnalibro Rimosso",
|
||||||
"ToastBookmarkUpdateFailed": "Aggiornamento Segnalibro fallito",
|
"ToastBookmarkUpdateFailed": "Aggiornamento Segnalibro fallito",
|
||||||
"ToastBookmarkUpdateSuccess": "Segnalibro aggiornato",
|
"ToastBookmarkUpdateSuccess": "Segnalibro aggiornato",
|
||||||
|
"ToastChaptersHaveErrors": "Chapters have errors",
|
||||||
|
"ToastChaptersMustHaveTitles": "Chapters must have titles",
|
||||||
"ToastCollectionItemsRemoveFailed": "Rimozione oggetti dalla Raccolta fallita",
|
"ToastCollectionItemsRemoveFailed": "Rimozione oggetti dalla Raccolta fallita",
|
||||||
"ToastCollectionItemsRemoveSuccess": "Oggetto(i) rimossi dalla Raccolta",
|
"ToastCollectionItemsRemoveSuccess": "Oggetto(i) rimossi dalla Raccolta",
|
||||||
"ToastCollectionRemoveFailed": "Rimozione Raccolta fallita",
|
"ToastCollectionRemoveFailed": "Rimozione Raccolta fallita",
|
||||||
|
|||||||
@@ -65,6 +65,7 @@
|
|||||||
"ButtonSearch": "Szukaj",
|
"ButtonSearch": "Szukaj",
|
||||||
"ButtonSelectFolderPath": "Wybierz ścieżkę folderu",
|
"ButtonSelectFolderPath": "Wybierz ścieżkę folderu",
|
||||||
"ButtonSeries": "Seria",
|
"ButtonSeries": "Seria",
|
||||||
|
"ButtonSetChaptersFromTracks": "Set chapters from tracks",
|
||||||
"ButtonShiftTimes": "Przesunięcie czasowe",
|
"ButtonShiftTimes": "Przesunięcie czasowe",
|
||||||
"ButtonShow": "Pokaż",
|
"ButtonShow": "Pokaż",
|
||||||
"ButtonStartM4BEncode": "Eksportuj jako plik M4B",
|
"ButtonStartM4BEncode": "Eksportuj jako plik M4B",
|
||||||
@@ -389,6 +390,9 @@
|
|||||||
"LabelTotalTimeListened": "Całkowity czas odtwarzania",
|
"LabelTotalTimeListened": "Całkowity czas odtwarzania",
|
||||||
"LabelTrackFromFilename": "Ścieżka z nazwy pliku",
|
"LabelTrackFromFilename": "Ścieżka z nazwy pliku",
|
||||||
"LabelTrackFromMetadata": "Ścieżka z metadanych",
|
"LabelTrackFromMetadata": "Ścieżka z metadanych",
|
||||||
|
"LabelTracks": "Tracks",
|
||||||
|
"LabelTracksMultiTrack": "Multi-track",
|
||||||
|
"LabelTracksSingleTrack": "Single-track",
|
||||||
"LabelType": "Typ",
|
"LabelType": "Typ",
|
||||||
"LabelUnknown": "Nieznany",
|
"LabelUnknown": "Nieznany",
|
||||||
"LabelUpdateCover": "Zaktalizuj odkładkę",
|
"LabelUpdateCover": "Zaktalizuj odkładkę",
|
||||||
@@ -422,6 +426,9 @@
|
|||||||
"MessageBookshelfNoRSSFeeds": "Nie posiadasz żadnych otwartych feedów RSS",
|
"MessageBookshelfNoRSSFeeds": "Nie posiadasz żadnych otwartych feedów RSS",
|
||||||
"MessageBookshelfNoSeries": "Nie masz jeszcze żadnych serii",
|
"MessageBookshelfNoSeries": "Nie masz jeszcze żadnych serii",
|
||||||
"MessageChapterEndIsAfter": "Koniec rozdziału następuje po zakończeniu audiobooka",
|
"MessageChapterEndIsAfter": "Koniec rozdziału następuje po zakończeniu audiobooka",
|
||||||
|
"MessageChapterErrorFirstNotZero": "First chapter must start at 0",
|
||||||
|
"MessageChapterErrorStartGteDuration": "Invalid start time must be less than audiobook duration",
|
||||||
|
"MessageChapterErrorStartLtPrev": "Invalid start time must be greater than or equal to previous chapter start time",
|
||||||
"MessageChapterStartIsAfter": "Początek rozdziału następuje po zakończeniu audiobooka",
|
"MessageChapterStartIsAfter": "Początek rozdziału następuje po zakończeniu audiobooka",
|
||||||
"MessageCheckingCron": "Sprawdzanie cron...",
|
"MessageCheckingCron": "Sprawdzanie cron...",
|
||||||
"MessageConfirmDeleteBackup": "Czy na pewno chcesz usunąć kopię zapasową dla {0}?",
|
"MessageConfirmDeleteBackup": "Czy na pewno chcesz usunąć kopię zapasową dla {0}?",
|
||||||
@@ -474,6 +481,7 @@
|
|||||||
"MessageNoPodcastsFound": "Nie znaleziono podcastów",
|
"MessageNoPodcastsFound": "Nie znaleziono podcastów",
|
||||||
"MessageNoResults": "Brak wyników",
|
"MessageNoResults": "Brak wyników",
|
||||||
"MessageNoSearchResultsFor": "Brak wyników wyszukiwania dla \"{0}\"",
|
"MessageNoSearchResultsFor": "Brak wyników wyszukiwania dla \"{0}\"",
|
||||||
|
"MessageNoSeries": "No Series",
|
||||||
"MessageNotYetImplemented": "Jeszcze nie zaimplementowane",
|
"MessageNotYetImplemented": "Jeszcze nie zaimplementowane",
|
||||||
"MessageNoUpdateNecessary": "Brak konieczności aktualizacji",
|
"MessageNoUpdateNecessary": "Brak konieczności aktualizacji",
|
||||||
"MessageNoUpdatesWereNecessary": "Brak aktualizacji",
|
"MessageNoUpdatesWereNecessary": "Brak aktualizacji",
|
||||||
@@ -489,10 +497,12 @@
|
|||||||
"MessageRemoveFromPlayerQueue": "Remove from player queue",
|
"MessageRemoveFromPlayerQueue": "Remove from player queue",
|
||||||
"MessageRemoveUserWarning": "Czy na pewno chcesz trwale usunąć użytkownika \"{0}\"?",
|
"MessageRemoveUserWarning": "Czy na pewno chcesz trwale usunąć użytkownika \"{0}\"?",
|
||||||
"MessageReportBugsAndContribute": "Zgłoś błędy, pomysły i pomóż rozwijać aplikację na",
|
"MessageReportBugsAndContribute": "Zgłoś błędy, pomysły i pomóż rozwijać aplikację na",
|
||||||
|
"MessageResetChaptersConfirm": "Are you sure you want to reset chapters and undo the changes you made?",
|
||||||
"MessageRestoreBackupConfirm": "Czy na pewno chcesz przywrócić kopię zapasową utworzoną w dniu",
|
"MessageRestoreBackupConfirm": "Czy na pewno chcesz przywrócić kopię zapasową utworzoną w dniu",
|
||||||
"MessageRestoreBackupWarning": "Przywrócenie kopii zapasowej spowoduje nadpisane bazy danych w folderze /config oraz okładke w folderze /metadata/items & /metadata/authors.<br /><br />Kopie zapasowe nie modyfikują żadnego pliku w folderach z plikami audio. Jeśli włączyłeś ustawienia serwera, aby przechowywać okładki i metadane w folderach biblioteki, to nie są one zapisywane w kopii zapasowej lub nadpisywane<br /><br />Wszyscy klienci korzystający z Twojego serwera będą automatycznie odświeżani",
|
"MessageRestoreBackupWarning": "Przywrócenie kopii zapasowej spowoduje nadpisane bazy danych w folderze /config oraz okładke w folderze /metadata/items & /metadata/authors.<br /><br />Kopie zapasowe nie modyfikują żadnego pliku w folderach z plikami audio. Jeśli włączyłeś ustawienia serwera, aby przechowywać okładki i metadane w folderach biblioteki, to nie są one zapisywane w kopii zapasowej lub nadpisywane<br /><br />Wszyscy klienci korzystający z Twojego serwera będą automatycznie odświeżani",
|
||||||
"MessageSearchResultsFor": "Wyniki wyszukiwania dla",
|
"MessageSearchResultsFor": "Wyniki wyszukiwania dla",
|
||||||
"MessageServerCouldNotBeReached": "Nie udało się uzyskać połączenia z serwerem",
|
"MessageServerCouldNotBeReached": "Nie udało się uzyskać połączenia z serwerem",
|
||||||
|
"MessageSetChaptersFromTracksDescription": "Set chapters using each audio file as a chapter and chapter title as the audio file name",
|
||||||
"MessageStartPlaybackAtTime": "Rozpoczęcie odtwarzania \"{0}\" od {1}?",
|
"MessageStartPlaybackAtTime": "Rozpoczęcie odtwarzania \"{0}\" od {1}?",
|
||||||
"MessageThinking": "Myślę...",
|
"MessageThinking": "Myślę...",
|
||||||
"MessageUploaderItemFailed": "Nie udało się przesłać",
|
"MessageUploaderItemFailed": "Nie udało się przesłać",
|
||||||
@@ -539,6 +549,8 @@
|
|||||||
"ToastBookmarkRemoveSuccess": "Zakładka została usunięta",
|
"ToastBookmarkRemoveSuccess": "Zakładka została usunięta",
|
||||||
"ToastBookmarkUpdateFailed": "Nie udało się zaktualizować zakładki",
|
"ToastBookmarkUpdateFailed": "Nie udało się zaktualizować zakładki",
|
||||||
"ToastBookmarkUpdateSuccess": "Zaktualizowano zakładkę",
|
"ToastBookmarkUpdateSuccess": "Zaktualizowano zakładkę",
|
||||||
|
"ToastChaptersHaveErrors": "Chapters have errors",
|
||||||
|
"ToastChaptersMustHaveTitles": "Chapters must have titles",
|
||||||
"ToastCollectionItemsRemoveFailed": "Nie udało się usunąć pozycji z kolekcji",
|
"ToastCollectionItemsRemoveFailed": "Nie udało się usunąć pozycji z kolekcji",
|
||||||
"ToastCollectionItemsRemoveSuccess": "Przedmiot(y) zostały usunięte z kolekcji",
|
"ToastCollectionItemsRemoveSuccess": "Przedmiot(y) zostały usunięte z kolekcji",
|
||||||
"ToastCollectionRemoveFailed": "Nie udało się usunąć kolekcji",
|
"ToastCollectionRemoveFailed": "Nie udało się usunąć kolekcji",
|
||||||
|
|||||||
+34
-22
@@ -41,7 +41,7 @@
|
|||||||
"ButtonOpenManager": "打开管理器",
|
"ButtonOpenManager": "打开管理器",
|
||||||
"ButtonPlay": "播放",
|
"ButtonPlay": "播放",
|
||||||
"ButtonPlaying": "正在播放",
|
"ButtonPlaying": "正在播放",
|
||||||
"ButtonPlaylists": "Playlists",
|
"ButtonPlaylists": "播放列表",
|
||||||
"ButtonPurgeAllCache": "清理所有缓存",
|
"ButtonPurgeAllCache": "清理所有缓存",
|
||||||
"ButtonPurgeItemsCache": "清理项目缓存",
|
"ButtonPurgeItemsCache": "清理项目缓存",
|
||||||
"ButtonPurgeMediaProgress": "清理媒体进度",
|
"ButtonPurgeMediaProgress": "清理媒体进度",
|
||||||
@@ -65,6 +65,7 @@
|
|||||||
"ButtonSearch": "查找",
|
"ButtonSearch": "查找",
|
||||||
"ButtonSelectFolderPath": "选择文件夹路径",
|
"ButtonSelectFolderPath": "选择文件夹路径",
|
||||||
"ButtonSeries": "系列",
|
"ButtonSeries": "系列",
|
||||||
|
"ButtonSetChaptersFromTracks": "将音轨设置为章节",
|
||||||
"ButtonShiftTimes": "快速移动时间",
|
"ButtonShiftTimes": "快速移动时间",
|
||||||
"ButtonShow": "显示",
|
"ButtonShow": "显示",
|
||||||
"ButtonStartM4BEncode": "开始 M4B 编码",
|
"ButtonStartM4BEncode": "开始 M4B 编码",
|
||||||
@@ -111,9 +112,9 @@
|
|||||||
"HeaderOpenRSSFeed": "打开 RSS 源",
|
"HeaderOpenRSSFeed": "打开 RSS 源",
|
||||||
"HeaderOtherFiles": "其他文件",
|
"HeaderOtherFiles": "其他文件",
|
||||||
"HeaderPermissions": "权限",
|
"HeaderPermissions": "权限",
|
||||||
"HeaderPlayerQueue": "播放列表",
|
"HeaderPlayerQueue": "播放队列",
|
||||||
"HeaderPlaylist": "Playlist",
|
"HeaderPlaylist": "播放列表",
|
||||||
"HeaderPlaylistItems": "Playlist Items",
|
"HeaderPlaylistItems": "播放列表项目",
|
||||||
"HeaderPodcastsToAdd": "要添加的播客",
|
"HeaderPodcastsToAdd": "要添加的播客",
|
||||||
"HeaderPreviewCover": "预览封面",
|
"HeaderPreviewCover": "预览封面",
|
||||||
"HeaderRemoveEpisode": "移除剧集",
|
"HeaderRemoveEpisode": "移除剧集",
|
||||||
@@ -150,8 +151,8 @@
|
|||||||
"LabelAddedAt": "添加于",
|
"LabelAddedAt": "添加于",
|
||||||
"LabelAddToCollection": "添加到收藏",
|
"LabelAddToCollection": "添加到收藏",
|
||||||
"LabelAddToCollectionBatch": "批量添加 {0} 个媒体到收藏",
|
"LabelAddToCollectionBatch": "批量添加 {0} 个媒体到收藏",
|
||||||
"LabelAddToPlaylist": "Add to Playlist",
|
"LabelAddToPlaylist": "添加到播放列表",
|
||||||
"LabelAddToPlaylistBatch": "Add {0} Items to Playlist",
|
"LabelAddToPlaylistBatch": "添加 {0} 个项目到播放列表",
|
||||||
"LabelAll": "全部",
|
"LabelAll": "全部",
|
||||||
"LabelAllUsers": "所有用户",
|
"LabelAllUsers": "所有用户",
|
||||||
"LabelAuthor": "作者",
|
"LabelAuthor": "作者",
|
||||||
@@ -166,7 +167,7 @@
|
|||||||
"LabelBackupsMaxBackupSizeHelp": "为了防止错误配置, 如果备份超过配置的大小, 备份将失败.",
|
"LabelBackupsMaxBackupSizeHelp": "为了防止错误配置, 如果备份超过配置的大小, 备份将失败.",
|
||||||
"LabelBackupsNumberToKeep": "要保留的备份个数",
|
"LabelBackupsNumberToKeep": "要保留的备份个数",
|
||||||
"LabelBackupsNumberToKeepHelp": "一次只能删除一个备份, 因此如果你已经有超过此数量的备份, 则应手动删除它们.",
|
"LabelBackupsNumberToKeepHelp": "一次只能删除一个备份, 因此如果你已经有超过此数量的备份, 则应手动删除它们.",
|
||||||
"LabelBooks": "媒体",
|
"LabelBooks": "图书",
|
||||||
"LabelChangePassword": "修改密码",
|
"LabelChangePassword": "修改密码",
|
||||||
"LabelChaptersFound": "找到的章节",
|
"LabelChaptersFound": "找到的章节",
|
||||||
"LabelChapterTitle": "章节标题",
|
"LabelChapterTitle": "章节标题",
|
||||||
@@ -200,7 +201,7 @@
|
|||||||
"LabelEpisode": "剧集",
|
"LabelEpisode": "剧集",
|
||||||
"LabelEpisodeTitle": "剧集标题",
|
"LabelEpisodeTitle": "剧集标题",
|
||||||
"LabelEpisodeType": "剧集类型",
|
"LabelEpisodeType": "剧集类型",
|
||||||
"LabelExplicit": "显式",
|
"LabelExplicit": "信息明确",
|
||||||
"LabelFeedURL": "源 URL",
|
"LabelFeedURL": "源 URL",
|
||||||
"LabelFile": "文件",
|
"LabelFile": "文件",
|
||||||
"LabelFileBirthtime": "文件创建时间",
|
"LabelFileBirthtime": "文件创建时间",
|
||||||
@@ -287,7 +288,7 @@
|
|||||||
"LabelPermissionsUpdate": "可以更新",
|
"LabelPermissionsUpdate": "可以更新",
|
||||||
"LabelPermissionsUpload": "可以上传",
|
"LabelPermissionsUpload": "可以上传",
|
||||||
"LabelPhotoPathURL": "图片路径或 URL",
|
"LabelPhotoPathURL": "图片路径或 URL",
|
||||||
"LabelPlaylists": "Playlists",
|
"LabelPlaylists": "播放列表",
|
||||||
"LabelPlayMethod": "播放方法",
|
"LabelPlayMethod": "播放方法",
|
||||||
"LabelPodcast": "播客",
|
"LabelPodcast": "播客",
|
||||||
"LabelPodcasts": "播客",
|
"LabelPodcasts": "播客",
|
||||||
@@ -389,6 +390,9 @@
|
|||||||
"LabelTotalTimeListened": "总收听时间",
|
"LabelTotalTimeListened": "总收听时间",
|
||||||
"LabelTrackFromFilename": "从文件名获取音轨",
|
"LabelTrackFromFilename": "从文件名获取音轨",
|
||||||
"LabelTrackFromMetadata": "从源数据获取音轨",
|
"LabelTrackFromMetadata": "从源数据获取音轨",
|
||||||
|
"LabelTracks": "音轨",
|
||||||
|
"LabelTracksMultiTrack": "多轨",
|
||||||
|
"LabelTracksSingleTrack": "单轨",
|
||||||
"LabelType": "类型",
|
"LabelType": "类型",
|
||||||
"LabelUnknown": "未知",
|
"LabelUnknown": "未知",
|
||||||
"LabelUpdateCover": "更新封面",
|
"LabelUpdateCover": "更新封面",
|
||||||
@@ -411,9 +415,9 @@
|
|||||||
"LabelWeekdaysToRun": "工作日运行",
|
"LabelWeekdaysToRun": "工作日运行",
|
||||||
"LabelYourAudiobookDuration": "你的有声读物持续时间",
|
"LabelYourAudiobookDuration": "你的有声读物持续时间",
|
||||||
"LabelYourBookmarks": "你的书签",
|
"LabelYourBookmarks": "你的书签",
|
||||||
"LabelYourPlaylists": "Your Playlists",
|
"LabelYourPlaylists": "你的播放列表",
|
||||||
"LabelYourProgress": "你的进度",
|
"LabelYourProgress": "你的进度",
|
||||||
"MessageAddToPlayerQueue": "Add to player queue",
|
"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>.",
|
"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>.",
|
||||||
"MessageBackupsDescription": "备份包括用户, 用户进度, 媒体库项目详细信息, 服务器设置和图像, 存储在 <code>/metadata/items</code> & <code>/metadata/authors</code>. 备份不包括存储在您的媒体库文件夹中的任何文件.",
|
"MessageBackupsDescription": "备份包括用户, 用户进度, 媒体库项目详细信息, 服务器设置和图像, 存储在 <code>/metadata/items</code> & <code>/metadata/authors</code>. 备份不包括存储在您的媒体库文件夹中的任何文件.",
|
||||||
"MessageBatchQuickMatchDescription": "快速匹配将尝试为所选项目添加缺少的封面和元数据. 启用以下选项以允许快速匹配覆盖现有封面和或元数据.",
|
"MessageBatchQuickMatchDescription": "快速匹配将尝试为所选项目添加缺少的封面和元数据. 启用以下选项以允许快速匹配覆盖现有封面和或元数据.",
|
||||||
@@ -422,6 +426,9 @@
|
|||||||
"MessageBookshelfNoRSSFeeds": "没有打开的 RSS 源",
|
"MessageBookshelfNoRSSFeeds": "没有打开的 RSS 源",
|
||||||
"MessageBookshelfNoSeries": "你没有系列",
|
"MessageBookshelfNoSeries": "你没有系列",
|
||||||
"MessageChapterEndIsAfter": "章节结束是在有声读物结束之后",
|
"MessageChapterEndIsAfter": "章节结束是在有声读物结束之后",
|
||||||
|
"MessageChapterErrorFirstNotZero": "第一章节必须从 0 开始",
|
||||||
|
"MessageChapterErrorStartGteDuration": "无效的开始时间, 必须小于有声读物持续时间",
|
||||||
|
"MessageChapterErrorStartLtPrev": "无效的开始时间, 必须大于或等于上一章节的开始时间",
|
||||||
"MessageChapterStartIsAfter": "章节开始是在有声读物结束之后",
|
"MessageChapterStartIsAfter": "章节开始是在有声读物结束之后",
|
||||||
"MessageCheckingCron": "检查计划任务...",
|
"MessageCheckingCron": "检查计划任务...",
|
||||||
"MessageConfirmDeleteBackup": "你确定要删除备份 {0}?",
|
"MessageConfirmDeleteBackup": "你确定要删除备份 {0}?",
|
||||||
@@ -431,7 +438,7 @@
|
|||||||
"MessageConfirmRemoveCollection": "您确定要移除收藏 \"{0}\"?",
|
"MessageConfirmRemoveCollection": "您确定要移除收藏 \"{0}\"?",
|
||||||
"MessageConfirmRemoveEpisode": "您确定要移除剧集 \"{0}\"?",
|
"MessageConfirmRemoveEpisode": "您确定要移除剧集 \"{0}\"?",
|
||||||
"MessageConfirmRemoveEpisodes": "你确定要移除 {0} 剧集?",
|
"MessageConfirmRemoveEpisodes": "你确定要移除 {0} 剧集?",
|
||||||
"MessageConfirmRemovePlaylist": "Are you sure you want to remove your playlist \"{0}\"?",
|
"MessageConfirmRemovePlaylist": "你确定要移除播放列表 \"{0}\"?",
|
||||||
"MessageDownloadingEpisode": "正在下载剧集",
|
"MessageDownloadingEpisode": "正在下载剧集",
|
||||||
"MessageDragFilesIntoTrackOrder": "将文件拖动到正确的音轨顺序",
|
"MessageDragFilesIntoTrackOrder": "将文件拖动到正确的音轨顺序",
|
||||||
"MessageEmbedFinished": "嵌入完成!",
|
"MessageEmbedFinished": "嵌入完成!",
|
||||||
@@ -474,10 +481,11 @@
|
|||||||
"MessageNoPodcastsFound": "未找到播客",
|
"MessageNoPodcastsFound": "未找到播客",
|
||||||
"MessageNoResults": "无结果",
|
"MessageNoResults": "无结果",
|
||||||
"MessageNoSearchResultsFor": "没有搜索到结果 \"{0}\"",
|
"MessageNoSearchResultsFor": "没有搜索到结果 \"{0}\"",
|
||||||
|
"MessageNoSeries": "无系列",
|
||||||
"MessageNotYetImplemented": "尚未实施",
|
"MessageNotYetImplemented": "尚未实施",
|
||||||
"MessageNoUpdateNecessary": "无需更新",
|
"MessageNoUpdateNecessary": "无需更新",
|
||||||
"MessageNoUpdatesWereNecessary": "无需更新",
|
"MessageNoUpdatesWereNecessary": "无需更新",
|
||||||
"MessageNoUserPlaylists": "You have no playlists",
|
"MessageNoUserPlaylists": "你没有播放列表",
|
||||||
"MessageOr": "或",
|
"MessageOr": "或",
|
||||||
"MessagePauseChapter": "暂停章节播放",
|
"MessagePauseChapter": "暂停章节播放",
|
||||||
"MessagePlayChapter": "开始章节播放",
|
"MessagePlayChapter": "开始章节播放",
|
||||||
@@ -486,13 +494,15 @@
|
|||||||
"MessageRemoveAllItemsWarning": "警告! 此操作将从数据库中删除所有的媒体库项, 包括您所做的任何更新或匹配. 这不会对实际文件产生任何影响. 你确定吗?",
|
"MessageRemoveAllItemsWarning": "警告! 此操作将从数据库中删除所有的媒体库项, 包括您所做的任何更新或匹配. 这不会对实际文件产生任何影响. 你确定吗?",
|
||||||
"MessageRemoveChapter": "移除章节",
|
"MessageRemoveChapter": "移除章节",
|
||||||
"MessageRemoveEpisodes": "移除 {0} 剧集",
|
"MessageRemoveEpisodes": "移除 {0} 剧集",
|
||||||
"MessageRemoveFromPlayerQueue": "Remove from player queue",
|
"MessageRemoveFromPlayerQueue": "从播放队列中移除",
|
||||||
"MessageRemoveUserWarning": "是否确实要永久删除用户 \"{0}\"?",
|
"MessageRemoveUserWarning": "是否确实要永久删除用户 \"{0}\"?",
|
||||||
"MessageReportBugsAndContribute": "报告错误、请求功能和贡献在",
|
"MessageReportBugsAndContribute": "报告错误、请求功能和贡献在",
|
||||||
"MessageRestoreBackupConfirm": "您确定要恢复创建的这个备份",
|
"MessageResetChaptersConfirm": "你确定要重置章节并撤消你所做的更改吗?",
|
||||||
|
"MessageRestoreBackupConfirm": "你确定要恢复创建的这个备份",
|
||||||
"MessageRestoreBackupWarning": "恢复备份将覆盖位于 /config 的整个数据库并覆盖 /metadata/items & /metadata/authors 中的图像.<br /><br />备份不会修改媒体库文件夹中的任何文件. 如果您已启用服务器设置将封面和元数据存储在库文件夹中,则不会备份或覆盖这些内容.<br /><br />将自动刷新使用服务器的所有客户端.",
|
"MessageRestoreBackupWarning": "恢复备份将覆盖位于 /config 的整个数据库并覆盖 /metadata/items & /metadata/authors 中的图像.<br /><br />备份不会修改媒体库文件夹中的任何文件. 如果您已启用服务器设置将封面和元数据存储在库文件夹中,则不会备份或覆盖这些内容.<br /><br />将自动刷新使用服务器的所有客户端.",
|
||||||
"MessageSearchResultsFor": "搜索结果",
|
"MessageSearchResultsFor": "搜索结果",
|
||||||
"MessageServerCouldNotBeReached": "无法访问服务器",
|
"MessageServerCouldNotBeReached": "无法访问服务器",
|
||||||
|
"MessageSetChaptersFromTracksDescription": "把每个音频文件设置为章节并将章节标题设置为音频文件名",
|
||||||
"MessageStartPlaybackAtTime": "开始播放 \"{0}\" 在 {1}?",
|
"MessageStartPlaybackAtTime": "开始播放 \"{0}\" 在 {1}?",
|
||||||
"MessageThinking": "正在查找...",
|
"MessageThinking": "正在查找...",
|
||||||
"MessageUploaderItemFailed": "上传失败",
|
"MessageUploaderItemFailed": "上传失败",
|
||||||
@@ -512,9 +522,9 @@
|
|||||||
"NoteUploaderFoldersWithMediaFiles": "包含媒体文件的文件夹将作为单独的媒体库项目处理.",
|
"NoteUploaderFoldersWithMediaFiles": "包含媒体文件的文件夹将作为单独的媒体库项目处理.",
|
||||||
"NoteUploaderOnlyAudioFiles": "如果只上传音频文件, 则每个音频文件将作为单独的有声读物处理.",
|
"NoteUploaderOnlyAudioFiles": "如果只上传音频文件, 则每个音频文件将作为单独的有声读物处理.",
|
||||||
"NoteUploaderUnsupportedFiles": "不支持的文件将被忽略. 选择或删除文件夹时, 将忽略不在项目文件夹中的其他文件.",
|
"NoteUploaderUnsupportedFiles": "不支持的文件将被忽略. 选择或删除文件夹时, 将忽略不在项目文件夹中的其他文件.",
|
||||||
"PlaceholderNewCollection": "新建收藏夹名称",
|
"PlaceholderNewCollection": "输入收藏夹名称",
|
||||||
"PlaceholderNewFolderPath": "输入文件夹路径",
|
"PlaceholderNewFolderPath": "输入文件夹路径",
|
||||||
"PlaceholderNewPlaylist": "New playlist name",
|
"PlaceholderNewPlaylist": "输入播放列表名称",
|
||||||
"PlaceholderSearch": "查找..",
|
"PlaceholderSearch": "查找..",
|
||||||
"ToastAccountUpdateFailed": "账户更新失败",
|
"ToastAccountUpdateFailed": "账户更新失败",
|
||||||
"ToastAccountUpdateSuccess": "帐户已更新",
|
"ToastAccountUpdateSuccess": "帐户已更新",
|
||||||
@@ -539,6 +549,8 @@
|
|||||||
"ToastBookmarkRemoveSuccess": "书签已删除",
|
"ToastBookmarkRemoveSuccess": "书签已删除",
|
||||||
"ToastBookmarkUpdateFailed": "书签更新失败",
|
"ToastBookmarkUpdateFailed": "书签更新失败",
|
||||||
"ToastBookmarkUpdateSuccess": "书签已更新",
|
"ToastBookmarkUpdateSuccess": "书签已更新",
|
||||||
|
"ToastChaptersHaveErrors": "章节有错误",
|
||||||
|
"ToastChaptersMustHaveTitles": "章节必须有标题",
|
||||||
"ToastCollectionItemsRemoveFailed": "从收藏夹移除项目失败",
|
"ToastCollectionItemsRemoveFailed": "从收藏夹移除项目失败",
|
||||||
"ToastCollectionItemsRemoveSuccess": "项目从收藏夹移除",
|
"ToastCollectionItemsRemoveSuccess": "项目从收藏夹移除",
|
||||||
"ToastCollectionRemoveFailed": "删除收藏夹失败",
|
"ToastCollectionRemoveFailed": "删除收藏夹失败",
|
||||||
@@ -562,10 +574,10 @@
|
|||||||
"ToastLibraryScanStarted": "媒体库扫描已启动",
|
"ToastLibraryScanStarted": "媒体库扫描已启动",
|
||||||
"ToastLibraryUpdateFailed": "更新图书库失败",
|
"ToastLibraryUpdateFailed": "更新图书库失败",
|
||||||
"ToastLibraryUpdateSuccess": "媒体库 \"{0}\" 已更新",
|
"ToastLibraryUpdateSuccess": "媒体库 \"{0}\" 已更新",
|
||||||
"ToastPlaylistRemoveFailed": "Failed to remove playlist",
|
"ToastPlaylistRemoveFailed": "删除播放列表失败",
|
||||||
"ToastPlaylistRemoveSuccess": "Playlist removed",
|
"ToastPlaylistRemoveSuccess": "播放列表已删除",
|
||||||
"ToastPlaylistUpdateFailed": "Failed to update playlist",
|
"ToastPlaylistUpdateFailed": "更新播放列表失败",
|
||||||
"ToastPlaylistUpdateSuccess": "Playlist updated",
|
"ToastPlaylistUpdateSuccess": "播放列表已更新",
|
||||||
"ToastPodcastCreateFailed": "创建播客失败",
|
"ToastPodcastCreateFailed": "创建播客失败",
|
||||||
"ToastPodcastCreateSuccess": "已成功创建播客",
|
"ToastPodcastCreateSuccess": "已成功创建播客",
|
||||||
"ToastRemoveItemFromCollectionFailed": "从收藏中删除项目失败",
|
"ToastRemoveItemFromCollectionFailed": "从收藏中删除项目失败",
|
||||||
@@ -586,4 +598,4 @@
|
|||||||
"WeekdayThursday": "星期四",
|
"WeekdayThursday": "星期四",
|
||||||
"WeekdayTuesday": "星期二",
|
"WeekdayTuesday": "星期二",
|
||||||
"WeekdayWednesday": "星期三"
|
"WeekdayWednesday": "星期三"
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "audiobookshelf",
|
"name": "audiobookshelf",
|
||||||
"version": "2.2.6",
|
"version": "2.2.8",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "audiobookshelf",
|
"name": "audiobookshelf",
|
||||||
"version": "2.2.6",
|
"version": "2.2.8",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^0.26.1",
|
"axios": "^0.26.1",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "audiobookshelf",
|
"name": "audiobookshelf",
|
||||||
"version": "2.2.6",
|
"version": "2.2.8",
|
||||||
"description": "Self-hosted audiobook and podcast server",
|
"description": "Self-hosted audiobook and podcast server",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
+1
-1
@@ -109,7 +109,7 @@ class Auth {
|
|||||||
Logger.error('JWT Verify Token Failed', err)
|
Logger.error('JWT Verify Token Failed', err)
|
||||||
return resolve(null)
|
return resolve(null)
|
||||||
}
|
}
|
||||||
var user = this.users.find(u => u.id === payload.userId && u.username === payload.username)
|
const user = this.users.find(u => u.id === payload.userId && u.username === payload.username)
|
||||||
resolve(user || null)
|
resolve(user || null)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -31,9 +31,13 @@ class SocketAuthority {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Emits event to all authorized clients
|
// Emits event to all authorized clients
|
||||||
emitter(evt, data) {
|
// optional filter function to only send event to specific users
|
||||||
|
// TODO: validate that filter is actually a function
|
||||||
|
emitter(evt, data, filter = null) {
|
||||||
for (const socketId in this.clients) {
|
for (const socketId in this.clients) {
|
||||||
if (this.clients[socketId].user) {
|
if (this.clients[socketId].user) {
|
||||||
|
if (filter && !filter(this.clients[socketId].user)) continue
|
||||||
|
|
||||||
this.clients[socketId].socket.emit(evt, data)
|
this.clients[socketId].socket.emit(evt, data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class LibraryController {
|
|||||||
constructor() { }
|
constructor() { }
|
||||||
|
|
||||||
async create(req, res) {
|
async create(req, res) {
|
||||||
var newLibraryPayload = {
|
const newLibraryPayload = {
|
||||||
...req.body
|
...req.body
|
||||||
}
|
}
|
||||||
if (!newLibraryPayload.name || !newLibraryPayload.folders || !newLibraryPayload.folders.length) {
|
if (!newLibraryPayload.name || !newLibraryPayload.folders || !newLibraryPayload.folders.length) {
|
||||||
@@ -26,9 +26,9 @@ class LibraryController {
|
|||||||
f.fullPath = Path.resolve(f.fullPath)
|
f.fullPath = Path.resolve(f.fullPath)
|
||||||
return f
|
return f
|
||||||
})
|
})
|
||||||
for (var folder of newLibraryPayload.folders) {
|
for (const folder of newLibraryPayload.folders) {
|
||||||
try {
|
try {
|
||||||
var direxists = await fs.pathExists(folder.fullPath)
|
const direxists = await fs.pathExists(folder.fullPath)
|
||||||
if (!direxists) { // If folder does not exist try to make it and set file permissions/owner
|
if (!direxists) { // If folder does not exist try to make it and set file permissions/owner
|
||||||
await fs.mkdir(folder.fullPath)
|
await fs.mkdir(folder.fullPath)
|
||||||
await filePerms.setDefault(folder.fullPath)
|
await filePerms.setDefault(folder.fullPath)
|
||||||
@@ -39,12 +39,16 @@ class LibraryController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var library = new Library()
|
const library = new Library()
|
||||||
newLibraryPayload.displayOrder = this.db.libraries.length + 1
|
newLibraryPayload.displayOrder = this.db.libraries.length + 1
|
||||||
library.setData(newLibraryPayload)
|
library.setData(newLibraryPayload)
|
||||||
await this.db.insertEntity('library', library)
|
await this.db.insertEntity('library', library)
|
||||||
// TODO: Only emit to users that have access
|
|
||||||
SocketAuthority.emitter('library_added', library.toJSON())
|
// Only emit to users with access to library
|
||||||
|
const userFilter = (user) => {
|
||||||
|
return user.checkCanAccessLibrary && user.checkCanAccessLibrary(library.id)
|
||||||
|
}
|
||||||
|
SocketAuthority.emitter('library_added', library.toJSON(), userFilter)
|
||||||
|
|
||||||
// Add library watcher
|
// Add library watcher
|
||||||
this.watcher.addLibrary(library)
|
this.watcher.addLibrary(library)
|
||||||
@@ -53,7 +57,7 @@ class LibraryController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
findAll(req, res) {
|
findAll(req, res) {
|
||||||
var librariesAccessible = req.user.librariesAccessible || []
|
const librariesAccessible = req.user.librariesAccessible || []
|
||||||
if (librariesAccessible && librariesAccessible.length) {
|
if (librariesAccessible && librariesAccessible.length) {
|
||||||
return res.json(this.db.libraries.filter(lib => librariesAccessible.includes(lib.id)).map(lib => lib.toJSON()))
|
return res.json(this.db.libraries.filter(lib => librariesAccessible.includes(lib.id)).map(lib => lib.toJSON()))
|
||||||
}
|
}
|
||||||
@@ -75,12 +79,12 @@ class LibraryController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async update(req, res) {
|
async update(req, res) {
|
||||||
var library = req.library
|
const library = req.library
|
||||||
|
|
||||||
// Validate new folder paths exist or can be created & resolve rel paths
|
// Validate new folder paths exist or can be created & resolve rel paths
|
||||||
// returns 400 if a new folder fails to access
|
// returns 400 if a new folder fails to access
|
||||||
if (req.body.folders) {
|
if (req.body.folders) {
|
||||||
var newFolderPaths = []
|
const newFolderPaths = []
|
||||||
req.body.folders = req.body.folders.map(f => {
|
req.body.folders = req.body.folders.map(f => {
|
||||||
if (!f.id) {
|
if (!f.id) {
|
||||||
f.fullPath = Path.resolve(f.fullPath)
|
f.fullPath = Path.resolve(f.fullPath)
|
||||||
@@ -88,11 +92,11 @@ class LibraryController {
|
|||||||
}
|
}
|
||||||
return f
|
return f
|
||||||
})
|
})
|
||||||
for (var path of newFolderPaths) {
|
for (const path of newFolderPaths) {
|
||||||
var pathExists = await fs.pathExists(path)
|
const pathExists = await fs.pathExists(path)
|
||||||
if (!pathExists) {
|
if (!pathExists) {
|
||||||
// Ensure dir will recursively create directories which might be preferred over mkdir
|
// Ensure dir will recursively create directories which might be preferred over mkdir
|
||||||
var success = await fs.ensureDir(path).then(() => true).catch((error) => {
|
const success = await fs.ensureDir(path).then(() => true).catch((error) => {
|
||||||
Logger.error(`[LibraryController] Failed to ensure folder dir "${path}"`, error)
|
Logger.error(`[LibraryController] Failed to ensure folder dir "${path}"`, error)
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
@@ -105,7 +109,7 @@ class LibraryController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var hasUpdates = library.update(req.body)
|
const hasUpdates = library.update(req.body)
|
||||||
// TODO: Should check if this is an update to folder paths or name only
|
// TODO: Should check if this is an update to folder paths or name only
|
||||||
if (hasUpdates) {
|
if (hasUpdates) {
|
||||||
// Update watcher
|
// Update watcher
|
||||||
@@ -115,7 +119,7 @@ class LibraryController {
|
|||||||
this.cronManager.updateLibraryScanCron(library)
|
this.cronManager.updateLibraryScanCron(library)
|
||||||
|
|
||||||
// Remove libraryItems no longer in library
|
// Remove libraryItems no longer in library
|
||||||
var itemsToRemove = this.db.libraryItems.filter(li => li.libraryId === library.id && !library.checkFullPathInLibrary(li.path))
|
const itemsToRemove = this.db.libraryItems.filter(li => li.libraryId === library.id && !library.checkFullPathInLibrary(li.path))
|
||||||
if (itemsToRemove.length) {
|
if (itemsToRemove.length) {
|
||||||
Logger.info(`[Scanner] Updating library, removing ${itemsToRemove.length} items`)
|
Logger.info(`[Scanner] Updating library, removing ${itemsToRemove.length} items`)
|
||||||
for (let i = 0; i < itemsToRemove.length; i++) {
|
for (let i = 0; i < itemsToRemove.length; i++) {
|
||||||
@@ -123,32 +127,37 @@ class LibraryController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
await this.db.updateEntity('library', library)
|
await this.db.updateEntity('library', library)
|
||||||
SocketAuthority.emitter('library_updated', library.toJSON())
|
|
||||||
|
// Only emit to users with access to library
|
||||||
|
const userFilter = (user) => {
|
||||||
|
return user.checkCanAccessLibrary && user.checkCanAccessLibrary(library.id)
|
||||||
|
}
|
||||||
|
SocketAuthority.emitter('library_updated', library.toJSON(), userFilter)
|
||||||
}
|
}
|
||||||
return res.json(library.toJSON())
|
return res.json(library.toJSON())
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(req, res) {
|
async delete(req, res) {
|
||||||
var library = req.library
|
const library = req.library
|
||||||
|
|
||||||
// Remove library watcher
|
// Remove library watcher
|
||||||
this.watcher.removeLibrary(library)
|
this.watcher.removeLibrary(library)
|
||||||
|
|
||||||
// Remove collections for library
|
// Remove collections for library
|
||||||
var collections = this.db.collections.filter(c => c.libraryId === library.id)
|
const collections = this.db.collections.filter(c => c.libraryId === library.id)
|
||||||
for (const collection of collections) {
|
for (const collection of collections) {
|
||||||
Logger.info(`[Server] deleting collection "${collection.name}" for library "${library.name}"`)
|
Logger.info(`[Server] deleting collection "${collection.name}" for library "${library.name}"`)
|
||||||
await this.db.removeEntity('collection', collection.id)
|
await this.db.removeEntity('collection', collection.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove items in this library
|
// Remove items in this library
|
||||||
var libraryItems = this.db.libraryItems.filter(li => li.libraryId === library.id)
|
const libraryItems = this.db.libraryItems.filter(li => li.libraryId === library.id)
|
||||||
Logger.info(`[Server] deleting library "${library.name}" with ${libraryItems.length} items"`)
|
Logger.info(`[Server] deleting library "${library.name}" with ${libraryItems.length} items"`)
|
||||||
for (let i = 0; i < libraryItems.length; i++) {
|
for (let i = 0; i < libraryItems.length; i++) {
|
||||||
await this.handleDeleteLibraryItem(libraryItems[i])
|
await this.handleDeleteLibraryItem(libraryItems[i])
|
||||||
}
|
}
|
||||||
|
|
||||||
var libraryJson = library.toJSON()
|
const libraryJson = library.toJSON()
|
||||||
await this.db.removeEntity('library', library.id)
|
await this.db.removeEntity('library', library.id)
|
||||||
SocketAuthority.emitter('library_removed', libraryJson)
|
SocketAuthority.emitter('library_removed', libraryJson)
|
||||||
return res.json(libraryJson)
|
return res.json(libraryJson)
|
||||||
@@ -170,17 +179,17 @@ class LibraryController {
|
|||||||
minified: req.query.minified === '1',
|
minified: req.query.minified === '1',
|
||||||
collapseseries: req.query.collapseseries === '1'
|
collapseseries: req.query.collapseseries === '1'
|
||||||
}
|
}
|
||||||
var mediaIsBook = payload.mediaType === 'book'
|
const mediaIsBook = payload.mediaType === 'book'
|
||||||
|
|
||||||
// Step 1 - Filter the retrieved library items
|
// Step 1 - Filter the retrieved library items
|
||||||
var filterSeries = null
|
let filterSeries = null
|
||||||
if (payload.filterBy) {
|
if (payload.filterBy) {
|
||||||
libraryItems = libraryHelpers.getFilteredLibraryItems(libraryItems, payload.filterBy, req.user, this.rssFeedManager.feedsArray)
|
libraryItems = libraryHelpers.getFilteredLibraryItems(libraryItems, payload.filterBy, req.user, this.rssFeedManager.feedsArray)
|
||||||
payload.total = libraryItems.length
|
payload.total = libraryItems.length
|
||||||
|
|
||||||
// Determining if we are filtering titles by a series, and if so, which series
|
// Determining if we are filtering titles by a series, and if so, which series
|
||||||
filterSeries = (mediaIsBook && payload.filterBy.startsWith('series.')) ? libraryHelpers.decode(payload.filterBy.replace('series.', '')) : null
|
filterSeries = (mediaIsBook && payload.filterBy.startsWith('series.')) ? libraryHelpers.decode(payload.filterBy.replace('series.', '')) : null
|
||||||
if (filterSeries === 'No Series') filterSeries = null
|
if (filterSeries === 'no-series') filterSeries = null
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 2 - If selected, collapse library items by the series they belong to.
|
// Step 2 - If selected, collapse library items by the series they belong to.
|
||||||
@@ -216,7 +225,7 @@ class LibraryController {
|
|||||||
|
|
||||||
if (payload.sortBy) {
|
if (payload.sortBy) {
|
||||||
// old sort key TODO: should be mutated in dbMigration
|
// old sort key TODO: should be mutated in dbMigration
|
||||||
var sortKey = payload.sortBy
|
let sortKey = payload.sortBy
|
||||||
if (sortKey.startsWith('book.')) {
|
if (sortKey.startsWith('book.')) {
|
||||||
sortKey = sortKey.replace('book.', 'media.metadata.')
|
sortKey = sortKey.replace('book.', 'media.metadata.')
|
||||||
}
|
}
|
||||||
@@ -246,7 +255,7 @@ class LibraryController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sort series based on the sortBy attribute
|
// Sort series based on the sortBy attribute
|
||||||
var direction = payload.sortDesc ? 'desc' : 'asc'
|
const direction = payload.sortDesc ? 'desc' : 'asc'
|
||||||
sortArray.push({
|
sortArray.push({
|
||||||
[direction]: (li) => {
|
[direction]: (li) => {
|
||||||
if (mediaIsBook && sortBySequence) {
|
if (mediaIsBook && sortBySequence) {
|
||||||
@@ -332,7 +341,7 @@ class LibraryController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async removeLibraryItemsWithIssues(req, res) {
|
async removeLibraryItemsWithIssues(req, res) {
|
||||||
var libraryItemsWithIssues = req.libraryItems.filter(li => li.hasIssues)
|
const libraryItemsWithIssues = req.libraryItems.filter(li => li.hasIssues)
|
||||||
if (!libraryItemsWithIssues.length) {
|
if (!libraryItemsWithIssues.length) {
|
||||||
Logger.warn(`[LibraryController] No library items have issues`)
|
Logger.warn(`[LibraryController] No library items have issues`)
|
||||||
return res.sendStatus(200)
|
return res.sendStatus(200)
|
||||||
@@ -349,8 +358,8 @@ class LibraryController {
|
|||||||
|
|
||||||
// api/libraries/:id/series
|
// api/libraries/:id/series
|
||||||
async getAllSeriesForLibrary(req, res) {
|
async getAllSeriesForLibrary(req, res) {
|
||||||
var libraryItems = req.libraryItems
|
const libraryItems = req.libraryItems
|
||||||
var payload = {
|
const payload = {
|
||||||
results: [],
|
results: [],
|
||||||
total: 0,
|
total: 0,
|
||||||
limit: req.query.limit && !isNaN(req.query.limit) ? Number(req.query.limit) : 0,
|
limit: req.query.limit && !isNaN(req.query.limit) ? Number(req.query.limit) : 0,
|
||||||
@@ -361,7 +370,7 @@ class LibraryController {
|
|||||||
minified: req.query.minified === '1'
|
minified: req.query.minified === '1'
|
||||||
}
|
}
|
||||||
|
|
||||||
var series = libraryHelpers.getSeriesFromBooks(libraryItems, this.db.series, null, payload.filterBy, req.user, payload.minified)
|
let series = libraryHelpers.getSeriesFromBooks(libraryItems, this.db.series, null, payload.filterBy, req.user, payload.minified)
|
||||||
|
|
||||||
const direction = payload.sortDesc ? 'desc' : 'asc'
|
const direction = payload.sortDesc ? 'desc' : 'asc'
|
||||||
series = naturalSort(series).by([
|
series = naturalSort(series).by([
|
||||||
|
|||||||
@@ -187,14 +187,6 @@ class PlaylistController {
|
|||||||
req.playlist = playlist
|
req.playlist = playlist
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.method == 'DELETE' && !req.user.canDelete) {
|
|
||||||
Logger.warn(`[PlaylistController] User attempted to delete without permission`, req.user.username)
|
|
||||||
return res.sendStatus(403)
|
|
||||||
} else if ((req.method == 'PATCH' || req.method == 'POST') && !req.user.canUpdate) {
|
|
||||||
Logger.warn('[PlaylistController] User attempted to update without permission', req.user.username)
|
|
||||||
return res.sendStatus(403)
|
|
||||||
}
|
|
||||||
|
|
||||||
next()
|
next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class MediaFileScanner {
|
|||||||
getTrackAndDiscNumberFromFilename(mediaMetadataFromScan, audioLibraryFile) {
|
getTrackAndDiscNumberFromFilename(mediaMetadataFromScan, audioLibraryFile) {
|
||||||
const { title, author, series, publishedYear } = mediaMetadataFromScan
|
const { title, author, series, publishedYear } = mediaMetadataFromScan
|
||||||
const { filename, path } = audioLibraryFile.metadata
|
const { filename, path } = audioLibraryFile.metadata
|
||||||
var partbasename = Path.basename(filename, Path.extname(filename))
|
let partbasename = Path.basename(filename, Path.extname(filename))
|
||||||
|
|
||||||
// Remove title, author, series, and publishedYear from filename if there
|
// Remove title, author, series, and publishedYear from filename if there
|
||||||
if (title) partbasename = partbasename.replace(title, '')
|
if (title) partbasename = partbasename.replace(title, '')
|
||||||
@@ -23,8 +23,8 @@ class MediaFileScanner {
|
|||||||
if (publishedYear) partbasename = partbasename.replace(publishedYear)
|
if (publishedYear) partbasename = partbasename.replace(publishedYear)
|
||||||
|
|
||||||
// Look for disc number
|
// Look for disc number
|
||||||
var discNumber = null
|
let discNumber = null
|
||||||
var discMatch = partbasename.match(/\b(disc|cd) ?(\d\d?)\b/i)
|
const discMatch = partbasename.match(/\b(disc|cd) ?(\d\d?)\b/i)
|
||||||
if (discMatch && discMatch.length > 2 && discMatch[2]) {
|
if (discMatch && discMatch.length > 2 && discMatch[2]) {
|
||||||
if (!isNaN(discMatch[2])) {
|
if (!isNaN(discMatch[2])) {
|
||||||
discNumber = Number(discMatch[2])
|
discNumber = Number(discMatch[2])
|
||||||
@@ -35,14 +35,14 @@ class MediaFileScanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Look for disc number in folder path e.g. /Book Title/CD01/audiofile.mp3
|
// Look for disc number in folder path e.g. /Book Title/CD01/audiofile.mp3
|
||||||
var pathdir = Path.dirname(path).split('/').pop()
|
const pathdir = Path.dirname(path).split('/').pop()
|
||||||
if (pathdir && /^cd\d{1,3}$/i.test(pathdir)) {
|
if (pathdir && /^cd\d{1,3}$/i.test(pathdir)) {
|
||||||
var discFromFolder = Number(pathdir.replace(/cd/i, ''))
|
const discFromFolder = Number(pathdir.replace(/cd/i, ''))
|
||||||
if (!isNaN(discFromFolder) && discFromFolder !== null) discNumber = discFromFolder
|
if (!isNaN(discFromFolder) && discFromFolder !== null) discNumber = discFromFolder
|
||||||
}
|
}
|
||||||
|
|
||||||
var numbersinpath = partbasename.match(/\d{1,4}/g)
|
const numbersinpath = partbasename.match(/\d{1,4}/g)
|
||||||
var trackNumber = numbersinpath && numbersinpath.length ? parseInt(numbersinpath[0]) : null
|
const trackNumber = numbersinpath && numbersinpath.length ? parseInt(numbersinpath[0]) : null
|
||||||
return {
|
return {
|
||||||
trackNumber,
|
trackNumber,
|
||||||
discNumber
|
discNumber
|
||||||
@@ -51,7 +51,7 @@ class MediaFileScanner {
|
|||||||
|
|
||||||
getAverageScanDurationMs(results) {
|
getAverageScanDurationMs(results) {
|
||||||
if (!results.length) return 0
|
if (!results.length) return 0
|
||||||
var total = 0
|
let total = 0
|
||||||
for (let i = 0; i < results.length; i++) total += results[i].elapsed
|
for (let i = 0; i < results.length; i++) total += results[i].elapsed
|
||||||
return Math.floor(total / results.length)
|
return Math.floor(total / results.length)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,53 +10,56 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
getFilteredLibraryItems(libraryItems, filterBy, user, feedsArray) {
|
getFilteredLibraryItems(libraryItems, filterBy, user, feedsArray) {
|
||||||
var filtered = libraryItems
|
let filtered = libraryItems
|
||||||
|
|
||||||
var searchGroups = ['genres', 'tags', 'series', 'authors', 'progress', 'narrators', 'missing', 'languages']
|
const searchGroups = ['genres', 'tags', 'series', 'authors', 'progress', 'narrators', 'missing', 'languages', 'tracks']
|
||||||
var group = searchGroups.find(_group => filterBy.startsWith(_group + '.'))
|
const group = searchGroups.find(_group => filterBy.startsWith(_group + '.'))
|
||||||
if (group) {
|
if (group) {
|
||||||
var filterVal = filterBy.replace(`${group}.`, '')
|
const filterVal = filterBy.replace(`${group}.`, '')
|
||||||
var filter = this.decode(filterVal)
|
const filter = this.decode(filterVal)
|
||||||
if (group === 'genres') filtered = filtered.filter(li => li.media.metadata && li.media.metadata.genres.includes(filter))
|
if (group === 'genres') filtered = filtered.filter(li => li.media.metadata && li.media.metadata.genres.includes(filter))
|
||||||
else if (group === 'tags') filtered = filtered.filter(li => li.media.tags.includes(filter))
|
else if (group === 'tags') filtered = filtered.filter(li => li.media.tags.includes(filter))
|
||||||
else if (group === 'series') {
|
else if (group === 'series') {
|
||||||
if (filter === 'No Series') filtered = filtered.filter(li => li.mediaType === 'book' && !li.media.metadata.series.length)
|
if (filter === 'no-series') filtered = filtered.filter(li => li.isBook && !li.media.metadata.series.length)
|
||||||
else {
|
else {
|
||||||
filtered = filtered.filter(li => li.mediaType === 'book' && li.media.metadata.hasSeries(filter))
|
filtered = filtered.filter(li => li.isBook && li.media.metadata.hasSeries(filter))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (group === 'authors') filtered = filtered.filter(li => li.mediaType === 'book' && li.media.metadata.hasAuthor(filter))
|
else if (group === 'authors') filtered = filtered.filter(li => li.isBook && li.media.metadata.hasAuthor(filter))
|
||||||
else if (group === 'narrators') filtered = filtered.filter(li => li.mediaType === 'book' && li.media.metadata.hasNarrator(filter))
|
else if (group === 'narrators') filtered = filtered.filter(li => li.isBook && li.media.metadata.hasNarrator(filter))
|
||||||
else if (group === 'progress') {
|
else if (group === 'progress') {
|
||||||
filtered = filtered.filter(li => {
|
filtered = filtered.filter(li => {
|
||||||
var itemProgress = user.getMediaProgress(li.id)
|
const itemProgress = user.getMediaProgress(li.id)
|
||||||
if (filter === 'Finished' && (itemProgress && itemProgress.isFinished)) return true
|
if (filter === 'finished' && (itemProgress && itemProgress.isFinished)) return true
|
||||||
if (filter === 'Not Started' && !itemProgress) return true
|
if (filter === 'not-started' && !itemProgress) return true
|
||||||
if (filter === 'Not Finished' && (!itemProgress || !itemProgress.isFinished)) return true
|
if (filter === 'not-finished' && (!itemProgress || !itemProgress.isFinished)) return true
|
||||||
if (filter === 'In Progress' && (itemProgress && itemProgress.inProgress)) return true
|
if (filter === 'in-progress' && (itemProgress && itemProgress.inProgress)) return true
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
} else if (group == 'missing') {
|
} else if (group == 'missing') {
|
||||||
filtered = filtered.filter(li => {
|
filtered = filtered.filter(li => {
|
||||||
if (li.mediaType === 'book') {
|
if (li.isBook) {
|
||||||
if (filter === 'ASIN' && li.media.metadata.asin === null) return true;
|
if (filter === 'asin' && li.media.metadata.asin === null) return true
|
||||||
if (filter === 'ISBN' && li.media.metadata.isbn === null) return true;
|
if (filter === 'isbn' && li.media.metadata.isbn === null) return true
|
||||||
if (filter === 'Subtitle' && li.media.metadata.subtitle === null) return true;
|
if (filter === 'subtitle' && li.media.metadata.subtitle === null) return true
|
||||||
if (filter === 'Author' && li.media.metadata.authors.length === 0) return true;
|
if (filter === 'authors' && li.media.metadata.authors.length === 0) return true
|
||||||
if (filter === 'Publish Year' && li.media.metadata.publishedYear === null) return true;
|
if (filter === 'publishedYear' && li.media.metadata.publishedYear === null) return true
|
||||||
if (filter === 'Series' && li.media.metadata.series.length === 0) return true;
|
if (filter === 'series' && li.media.metadata.series.length === 0) return true
|
||||||
if (filter === 'Description' && li.media.metadata.description === null) return true;
|
if (filter === 'description' && li.media.metadata.description === null) return true
|
||||||
if (filter === 'Genres' && li.media.metadata.genres.length === 0) return true;
|
if (filter === 'genres' && li.media.metadata.genres.length === 0) return true
|
||||||
if (filter === 'Tags' && li.media.tags.length === 0) return true;
|
if (filter === 'tags' && li.media.tags.length === 0) return true
|
||||||
if (filter === 'Narrator' && li.media.metadata.narrators.length === 0) return true;
|
if (filter === 'narrators' && li.media.metadata.narrators.length === 0) return true
|
||||||
if (filter === 'Publisher' && li.media.metadata.publisher === null) return true;
|
if (filter === 'publisher' && li.media.metadata.publisher === null) return true
|
||||||
if (filter === 'Language' && li.media.metadata.language === null) return true;
|
if (filter === 'language' && li.media.metadata.language === null) return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else if (group === 'languages') {
|
} else if (group === 'languages') {
|
||||||
filtered = filtered.filter(li => li.media.metadata && li.media.metadata.language === filter)
|
filtered = filtered.filter(li => li.media.metadata && li.media.metadata.language === filter)
|
||||||
|
} else if (group === 'tracks') {
|
||||||
|
if (filter === 'single') filtered = filtered.filter(li => li.isBook && li.media.numTracks === 1)
|
||||||
|
else if (filter === 'multi') filtered = filtered.filter(li => li.isBook && li.media.numTracks > 1)
|
||||||
}
|
}
|
||||||
} else if (filterBy === 'issues') {
|
} else if (filterBy === 'issues') {
|
||||||
filtered = filtered.filter(li => li.hasIssues)
|
filtered = filtered.filter(li => li.hasIssues)
|
||||||
|
|||||||
Reference in New Issue
Block a user