mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-03-05 00:18:30 +01:00
Merge branch 'master' into server/respond-with-objects
This commit is contained in:
commit
5c31687a0f
@ -25,15 +25,21 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<nuxt-link v-if="currentLibrary" to="/config/stats" class="outline-none hover:text-gray-200 cursor-pointer w-8 h-8 hidden sm:flex items-center justify-center mx-1">
|
<nuxt-link v-if="currentLibrary" to="/config/stats" class="outline-none hover:text-gray-200 cursor-pointer w-8 h-8 hidden sm:flex items-center justify-center mx-1">
|
||||||
<span class="material-icons text-2xl" aria-label="User Stats" role="button">equalizer</span>
|
<ui-tooltip :text="$strings.HeaderYourStats" direction="bottom" class="flex items-center">
|
||||||
|
<span class="material-icons text-2xl" aria-label="User Stats" role="button">equalizer</span>
|
||||||
|
</ui-tooltip>
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
|
|
||||||
<nuxt-link v-if="userCanUpload && currentLibrary" to="/upload" class="outline-none hover:text-gray-200 cursor-pointer w-8 h-8 flex items-center justify-center mx-1">
|
<nuxt-link v-if="userCanUpload && currentLibrary" to="/upload" class="outline-none hover:text-gray-200 cursor-pointer w-8 h-8 flex items-center justify-center mx-1">
|
||||||
<span class="material-icons text-2xl" aria-label="Upload Media" role="button">upload</span>
|
<ui-tooltip :text="$strings.ButtonUpload" direction="bottom" class="flex items-center">
|
||||||
|
<span class="material-icons text-2xl" aria-label="Upload Media" role="button">upload</span>
|
||||||
|
</ui-tooltip>
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
|
|
||||||
<nuxt-link v-if="userIsAdminOrUp" to="/config" class="outline-none hover:text-gray-200 cursor-pointer w-8 h-8 flex items-center justify-center mx-1">
|
<nuxt-link v-if="userIsAdminOrUp" to="/config" class="outline-none hover:text-gray-200 cursor-pointer w-8 h-8 flex items-center justify-center mx-1">
|
||||||
<span class="material-icons text-2xl" aria-label="System Settings" role="button">settings</span>
|
<ui-tooltip :text="$strings.HeaderSettings" direction="bottom" class="flex items-center">
|
||||||
|
<span class="material-icons text-2xl" aria-label="System Settings" role="button">settings</span>
|
||||||
|
</ui-tooltip>
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
|
|
||||||
<nuxt-link to="/account" class="relative w-9 h-9 md:w-32 bg-fg border border-gray-500 rounded shadow-sm ml-1.5 sm:ml-3 md:ml-5 md:pl-3 md:pr-10 py-2 text-left focus:outline-none sm:text-sm cursor-pointer hover:bg-bg hover:bg-opacity-40" aria-haspopup="listbox" aria-expanded="true">
|
<nuxt-link to="/account" class="relative w-9 h-9 md:w-32 bg-fg border border-gray-500 rounded shadow-sm ml-1.5 sm:ml-3 md:ml-5 md:pl-3 md:pr-10 py-2 text-left focus:outline-none sm:text-sm cursor-pointer hover:bg-bg hover:bg-opacity-40" aria-haspopup="listbox" aria-expanded="true">
|
||||||
@ -45,10 +51,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 +115,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 +138,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 +163,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 +195,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 +218,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 +228,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
|
||||||
|
@ -163,7 +163,7 @@ export default {
|
|||||||
},
|
},
|
||||||
bookWidth() {
|
bookWidth() {
|
||||||
var coverSize = this.$store.getters['user/getUserSetting']('bookshelfCoverSize')
|
var coverSize = this.$store.getters['user/getUserSetting']('bookshelfCoverSize')
|
||||||
if (this.isCoverSquareAspectRatio) return coverSize * 1.6
|
if (this.isCoverSquareAspectRatio || this.entityName === 'playlists') return coverSize * 1.6
|
||||||
return coverSize
|
return coverSize
|
||||||
},
|
},
|
||||||
bookHeight() {
|
bookHeight() {
|
||||||
@ -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)
|
||||||
|
@ -13,10 +13,14 @@
|
|||||||
|
|
||||||
<!-- Search icon btn -->
|
<!-- Search icon btn -->
|
||||||
<div v-show="!searching && isHovering && userCanUpdate" class="absolute top-0 left-0 p-2 cursor-pointer hover:text-white text-gray-200 transform hover:scale-125 duration-150" @click.prevent.stop="searchAuthor">
|
<div v-show="!searching && isHovering && userCanUpdate" class="absolute top-0 left-0 p-2 cursor-pointer hover:text-white text-gray-200 transform hover:scale-125 duration-150" @click.prevent.stop="searchAuthor">
|
||||||
<span class="material-icons text-lg">search</span>
|
<ui-tooltip :text="$strings.ButtonQuickMatch" direction="bottom">
|
||||||
|
<span class="material-icons text-lg">search</span>
|
||||||
|
</ui-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div v-show="isHovering && !searching && userCanUpdate" class="absolute top-0 right-0 p-2 cursor-pointer hover:text-white text-gray-200 transform hover:scale-125 duration-150" @click.prevent.stop="$emit('edit', author)">
|
<div v-show="isHovering && !searching && userCanUpdate" class="absolute top-0 right-0 p-2 cursor-pointer hover:text-white text-gray-200 transform hover:scale-125 duration-150" @click.prevent.stop="$emit('edit', author)">
|
||||||
<span class="material-icons text-lg">edit</span>
|
<ui-tooltip :text="$strings.LabelEdit" direction="bottom">
|
||||||
|
<span class="material-icons text-lg">edit</span>
|
||||||
|
</ui-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Loading spinner -->
|
<!-- Loading spinner -->
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -118,6 +118,8 @@ module.exports = {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
workbox: {
|
workbox: {
|
||||||
|
offline: false,
|
||||||
|
cacheAssets: false,
|
||||||
preCaching: [],
|
preCaching: [],
|
||||||
runtimeCaching: []
|
runtimeCaching: []
|
||||||
}
|
}
|
||||||
|
4
client/package-lock.json
generated
4
client/package-lock.json
generated
@ -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,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": {
|
||||||
|
@ -508,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
|
||||||
@ -524,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) {
|
||||||
|
@ -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 []
|
||||||
})
|
})
|
||||||
|
@ -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
|
||||||
},
|
},
|
||||||
|
@ -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,8 +65,8 @@
|
|||||||
"ButtonSearch": "Suchen",
|
"ButtonSearch": "Suchen",
|
||||||
"ButtonSelectFolderPath": "Auswahl Ordnerpfad",
|
"ButtonSelectFolderPath": "Auswahl Ordnerpfad",
|
||||||
"ButtonSeries": "Serien",
|
"ButtonSeries": "Serien",
|
||||||
"ButtonSetChaptersFromTracks": "Set chapters from tracks",
|
"ButtonSetChaptersFromTracks": "Kapitelerstellung aus Audiodateien",
|
||||||
"ButtonShiftTimes": "Arbeitszeiten",
|
"ButtonShiftTimes": "Zeitverschiebung",
|
||||||
"ButtonShow": "Anzeigen",
|
"ButtonShow": "Anzeigen",
|
||||||
"ButtonStartM4BEncode": "M4B-Kodierung starten",
|
"ButtonStartM4BEncode": "M4B-Kodierung starten",
|
||||||
"ButtonStartMetadataEmbed": "Metadateneinbettung starten",
|
"ButtonStartMetadataEmbed": "Metadateneinbettung starten",
|
||||||
@ -81,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",
|
||||||
@ -104,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",
|
||||||
@ -201,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",
|
||||||
@ -345,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",
|
||||||
@ -390,9 +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": "Tracks",
|
"LabelTracks": "Dateien",
|
||||||
"LabelTracksMultiTrack": "Multi-track",
|
"LabelTracksMultiTrack": "Mehrfachdatei",
|
||||||
"LabelTracksSingleTrack": "Single-track",
|
"LabelTracksSingleTrack": "Einzeldatei",
|
||||||
"LabelType": "Typ",
|
"LabelType": "Typ",
|
||||||
"LabelUnknown": "Unbekannt",
|
"LabelUnknown": "Unbekannt",
|
||||||
"LabelUpdateCover": "Titelbild aktualisieren",
|
"LabelUpdateCover": "Titelbild aktualisieren",
|
||||||
@ -402,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",
|
||||||
@ -415,20 +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": "First chapter must start at 0",
|
"MessageChapterErrorFirstNotZero": "Das erste Kapitel muss bei 0 beginnen",
|
||||||
"MessageChapterErrorStartGteDuration": "Invalid start time must be less than audiobook duration",
|
"MessageChapterErrorStartGteDuration": "Die ungültige Startzeit darf nicht größer als die gesamte Hörbuchdauer sein",
|
||||||
"MessageChapterErrorStartLtPrev": "Invalid start time must be greater than or equal to previous chapter start time",
|
"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?",
|
||||||
@ -459,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",
|
||||||
@ -481,11 +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": "No Series",
|
"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",
|
||||||
@ -494,15 +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": "Are you sure you want to reset chapters and undo the changes you made?",
|
"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": "Set chapters using each audio file as a chapter and chapter title as the audio file name",
|
"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",
|
||||||
@ -549,8 +549,8 @@
|
|||||||
"ToastBookmarkRemoveSuccess": "Lesezeichen gelöscht",
|
"ToastBookmarkRemoveSuccess": "Lesezeichen gelöscht",
|
||||||
"ToastBookmarkUpdateFailed": "Lesezeichenaktualisierung fehlgeschlagen",
|
"ToastBookmarkUpdateFailed": "Lesezeichenaktualisierung fehlgeschlagen",
|
||||||
"ToastBookmarkUpdateSuccess": "Lesezeichen aktualisiert",
|
"ToastBookmarkUpdateSuccess": "Lesezeichen aktualisiert",
|
||||||
"ToastChaptersHaveErrors": "Chapters have errors",
|
"ToastChaptersHaveErrors": "Kapitel sind fehlerhaft",
|
||||||
"ToastChaptersMustHaveTitles": "Chapters must have titles",
|
"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",
|
||||||
@ -577,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",
|
||||||
@ -598,4 +598,4 @@
|
|||||||
"WeekdayThursday": "Donnerstag",
|
"WeekdayThursday": "Donnerstag",
|
||||||
"WeekdayTuesday": "Dienstag",
|
"WeekdayTuesday": "Dienstag",
|
||||||
"WeekdayWednesday": "Mittwoch"
|
"WeekdayWednesday": "Mittwoch"
|
||||||
}
|
}
|
||||||
|
@ -342,7 +342,7 @@
|
|||||||
"LabelSettingsSkipMatchingBooksWithISBN": "Skip matching books that already have an ISBN",
|
"LabelSettingsSkipMatchingBooksWithISBN": "Skip matching books that already have an ISBN",
|
||||||
"LabelSettingsSortingIgnorePrefixes": "Ignore prefixes when sorting",
|
"LabelSettingsSortingIgnorePrefixes": "Ignore prefixes when sorting",
|
||||||
"LabelSettingsSortingIgnorePrefixesHelp": "i.e. for prefix \"the\" book title \"The Book Title\" would sort as \"Book Title, The\"",
|
"LabelSettingsSortingIgnorePrefixesHelp": "i.e. for prefix \"the\" book title \"The Book Title\" would sort as \"Book Title, The\"",
|
||||||
"LabelSettingsSquareBookCovers": "User square book covers",
|
"LabelSettingsSquareBookCovers": "Use square book covers",
|
||||||
"LabelSettingsSquareBookCoversHelp": "Prefer to use square covers over standard 1.6:1 book covers",
|
"LabelSettingsSquareBookCoversHelp": "Prefer to use square covers over standard 1.6:1 book covers",
|
||||||
"LabelSettingsStoreCoversWithItem": "Store covers with item",
|
"LabelSettingsStoreCoversWithItem": "Store covers with item",
|
||||||
"LabelSettingsStoreCoversWithItemHelp": "By default covers are stored in /metadata/items, enabling this setting will store covers in your library item folder. Only one file named \"cover\" will be kept",
|
"LabelSettingsStoreCoversWithItemHelp": "By default covers are stored in /metadata/items, enabling this setting will store covers in your library item folder. Only one file named \"cover\" will be kept",
|
||||||
|
@ -342,7 +342,7 @@
|
|||||||
"LabelSettingsSkipMatchingBooksWithISBN": "Skip matching books that already have an ISBN",
|
"LabelSettingsSkipMatchingBooksWithISBN": "Skip matching books that already have an ISBN",
|
||||||
"LabelSettingsSortingIgnorePrefixes": "Ignore prefixes when sorting",
|
"LabelSettingsSortingIgnorePrefixes": "Ignore prefixes when sorting",
|
||||||
"LabelSettingsSortingIgnorePrefixesHelp": "i.e. for prefix \"the\" book title \"The Book Title\" would sort as \"Book Title, The\"",
|
"LabelSettingsSortingIgnorePrefixesHelp": "i.e. for prefix \"the\" book title \"The Book Title\" would sort as \"Book Title, The\"",
|
||||||
"LabelSettingsSquareBookCovers": "User square book covers",
|
"LabelSettingsSquareBookCovers": "Use square book covers",
|
||||||
"LabelSettingsSquareBookCoversHelp": "Prefer to use square covers over standard 1.6:1 book covers",
|
"LabelSettingsSquareBookCoversHelp": "Prefer to use square covers over standard 1.6:1 book covers",
|
||||||
"LabelSettingsStoreCoversWithItem": "Store covers with item",
|
"LabelSettingsStoreCoversWithItem": "Store covers with item",
|
||||||
"LabelSettingsStoreCoversWithItemHelp": "By default covers are stored in /metadata/items, enabling this setting will store covers in your library item folder. Only one file named \"cover\" will be kept",
|
"LabelSettingsStoreCoversWithItemHelp": "By default covers are stored in /metadata/items, enabling this setting will store covers in your library item folder. Only one file named \"cover\" will be kept",
|
||||||
|
@ -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,7 +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": "Set chapters from tracks",
|
"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",
|
||||||
@ -113,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",
|
||||||
@ -151,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",
|
||||||
@ -288,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",
|
||||||
@ -390,9 +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": "Tracks",
|
"LabelTracks": "Pistes",
|
||||||
"LabelTracksMultiTrack": "Multi-track",
|
"LabelTracksMultiTrack": "Piste Multiple",
|
||||||
"LabelTracksSingleTrack": "Single-track",
|
"LabelTracksSingleTrack": "Piste Simple",
|
||||||
"LabelType": "Type",
|
"LabelType": "Type",
|
||||||
"LabelUnknown": "Inconnu",
|
"LabelUnknown": "Inconnu",
|
||||||
"LabelUpdateCover": "Mettre à jour la Couverture",
|
"LabelUpdateCover": "Mettre à jour la Couverture",
|
||||||
@ -415,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.",
|
||||||
@ -426,9 +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": "First chapter must start at 0",
|
"MessageChapterErrorFirstNotZero": "Le premier capitre doit débuter à 0",
|
||||||
"MessageChapterErrorStartGteDuration": "Invalid start time must be less than audiobook duration",
|
"MessageChapterErrorStartGteDuration": "Horodatage invalide car il doit débuter avant la fin du livre",
|
||||||
"MessageChapterErrorStartLtPrev": "Invalid start time must be greater than or equal to previous chapter start time",
|
"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}?",
|
||||||
@ -438,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!",
|
||||||
@ -481,11 +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": "No Series",
|
"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",
|
||||||
@ -494,15 +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": "Are you sure you want to reset chapters and undo the changes you made?",
|
"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": "Set chapters using each audio file as a chapter and chapter title as the audio file name",
|
"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",
|
||||||
@ -524,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",
|
||||||
@ -549,8 +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": "Chapters have errors",
|
"ToastChaptersHaveErrors": "Les chapitres contiennent des erreurs",
|
||||||
"ToastChaptersMustHaveTitles": "Chapters must have titles",
|
"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",
|
||||||
@ -574,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",
|
||||||
@ -598,4 +598,4 @@
|
|||||||
"WeekdayThursday": "Jeudi",
|
"WeekdayThursday": "Jeudi",
|
||||||
"WeekdayTuesday": "Mardi",
|
"WeekdayTuesday": "Mardi",
|
||||||
"WeekdayWednesday": "Mercredi"
|
"WeekdayWednesday": "Mercredi"
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,7 @@
|
|||||||
"ButtonRemoveAllLibraryItems": "Rimuovi tutto il contenuto della libreria",
|
"ButtonRemoveAllLibraryItems": "Rimuovi tutto il contenuto della libreria",
|
||||||
"ButtonRemoveFromContinueListening": "Rimuovi per proseguire l'ascolto",
|
"ButtonRemoveFromContinueListening": "Rimuovi per proseguire l'ascolto",
|
||||||
"ButtonRemoveSeriesFromContinueSeries": "Rimuovi la Serie per Continuarla",
|
"ButtonRemoveSeriesFromContinueSeries": "Rimuovi la Serie per Continuarla",
|
||||||
"ButtonReScan": "Riscansiona",
|
"ButtonReScan": "Ri-scansiona",
|
||||||
"ButtonReset": "Reset",
|
"ButtonReset": "Reset",
|
||||||
"ButtonRestore": "Ripristina",
|
"ButtonRestore": "Ripristina",
|
||||||
"ButtonSave": "Salva",
|
"ButtonSave": "Salva",
|
||||||
@ -65,7 +65,7 @@
|
|||||||
"ButtonSearch": "Cerca",
|
"ButtonSearch": "Cerca",
|
||||||
"ButtonSelectFolderPath": "Seleziona percorso cartella",
|
"ButtonSelectFolderPath": "Seleziona percorso cartella",
|
||||||
"ButtonSeries": "Serie",
|
"ButtonSeries": "Serie",
|
||||||
"ButtonSetChaptersFromTracks": "Set chapters from tracks",
|
"ButtonSetChaptersFromTracks": "Impostare i capitoli dalle tracce",
|
||||||
"ButtonShiftTimes": "Ricerca veloce",
|
"ButtonShiftTimes": "Ricerca veloce",
|
||||||
"ButtonShow": "Mostra",
|
"ButtonShow": "Mostra",
|
||||||
"ButtonStartM4BEncode": "Inizia L'Encoda del M4B",
|
"ButtonStartM4BEncode": "Inizia L'Encoda del M4B",
|
||||||
@ -114,7 +114,7 @@
|
|||||||
"HeaderPermissions": "Permessi",
|
"HeaderPermissions": "Permessi",
|
||||||
"HeaderPlayerQueue": "Coda Riproduzione",
|
"HeaderPlayerQueue": "Coda Riproduzione",
|
||||||
"HeaderPlaylist": "Playlist",
|
"HeaderPlaylist": "Playlist",
|
||||||
"HeaderPlaylistItems": "Playlist Items",
|
"HeaderPlaylistItems": "Elementi della playlist",
|
||||||
"HeaderPodcastsToAdd": "Podcasts da Aggiungere",
|
"HeaderPodcastsToAdd": "Podcasts da Aggiungere",
|
||||||
"HeaderPreviewCover": "Anteprima Cover",
|
"HeaderPreviewCover": "Anteprima Cover",
|
||||||
"HeaderRemoveEpisode": "Rimuovi Episodi",
|
"HeaderRemoveEpisode": "Rimuovi Episodi",
|
||||||
@ -151,8 +151,8 @@
|
|||||||
"LabelAddedAt": "Aggiunto il",
|
"LabelAddedAt": "Aggiunto il",
|
||||||
"LabelAddToCollection": "Aggiungi alla Raccolta",
|
"LabelAddToCollection": "Aggiungi alla Raccolta",
|
||||||
"LabelAddToCollectionBatch": "Aggiungi {0} Libri alla Raccolta",
|
"LabelAddToCollectionBatch": "Aggiungi {0} Libri alla Raccolta",
|
||||||
"LabelAddToPlaylist": "Add to Playlist",
|
"LabelAddToPlaylist": "aggiungi alla Playlist",
|
||||||
"LabelAddToPlaylistBatch": "Add {0} Items to Playlist",
|
"LabelAddToPlaylistBatch": "Aggiungi {0} file alla Playlist",
|
||||||
"LabelAll": "All",
|
"LabelAll": "All",
|
||||||
"LabelAllUsers": "Tutti gli Utenti",
|
"LabelAllUsers": "Tutti gli Utenti",
|
||||||
"LabelAuthor": "Autore",
|
"LabelAuthor": "Autore",
|
||||||
@ -390,9 +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",
|
"LabelTracks": "Traccia",
|
||||||
"LabelTracksMultiTrack": "Multi-track",
|
"LabelTracksMultiTrack": "Multi-traccia",
|
||||||
"LabelTracksSingleTrack": "Single-track",
|
"LabelTracksSingleTrack": "Traccia-singola",
|
||||||
"LabelType": "Tipo",
|
"LabelType": "Tipo",
|
||||||
"LabelUnknown": "Sconosciuto",
|
"LabelUnknown": "Sconosciuto",
|
||||||
"LabelUpdateCover": "Aggiornamento Cover",
|
"LabelUpdateCover": "Aggiornamento Cover",
|
||||||
@ -415,20 +415,20 @@
|
|||||||
"LabelWeekdaysToRun": "Giorni feriali da eseguire",
|
"LabelWeekdaysToRun": "Giorni feriali da eseguire",
|
||||||
"LabelYourAudiobookDuration": "La durata dell'audiolibro",
|
"LabelYourAudiobookDuration": "La durata dell'audiolibro",
|
||||||
"LabelYourBookmarks": "I tuoi Preferiti",
|
"LabelYourBookmarks": "I tuoi Preferiti",
|
||||||
"LabelYourPlaylists": "Your Playlists",
|
"LabelYourPlaylists": "le tue Playlist",
|
||||||
"LabelYourProgress": "Completato al",
|
"LabelYourProgress": "Completato al",
|
||||||
"MessageAddToPlayerQueue": "Add to player queue",
|
"MessageAddToPlayerQueue": "Aggiungi alla coda di riproduzione",
|
||||||
"MessageAppriseDescription": "Per utilizzare questa funzione è necessario disporre di un'istanza di <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> in esecuzione o un'API che gestirà quelle stesse richieste. <br />L'API Url dovrebbe essere il percorso URL completo per inviare la notifica, ad esempio se la tua istanza API è servita cosi .<code>http://192.168.1.1:8337</code> Allora dovrai mettere <code>http://192.168.1.1:8337/notify</code>.",
|
"MessageAppriseDescription": "Per utilizzare questa funzione è necessario disporre di un'istanza di <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> in esecuzione o un'API che gestirà quelle stesse richieste. <br />L'API Url dovrebbe essere il percorso URL completo per inviare la notifica, ad esempio se la tua istanza API è servita cosi .<code>http://192.168.1.1:8337</code> Allora dovrai mettere <code>http://192.168.1.1:8337/notify</code>.",
|
||||||
"MessageBackupsDescription": "I backup includono utenti, progressi degli utenti, dettagli sugli elementi della libreria, impostazioni del server e immagini archiviate in <code>/metadata/items</code> & <code>/metadata/authors</code>. I backup non includono i file archiviati nelle cartelle della libreria.",
|
"MessageBackupsDescription": "I backup includono utenti, progressi degli utenti, dettagli sugli elementi della libreria, impostazioni del server e immagini archiviate in <code>/metadata/items</code> & <code>/metadata/authors</code>. I backup non includono i file archiviati nelle cartelle della libreria.",
|
||||||
"MessageBatchQuickMatchDescription": "Quick Match tenterà di aggiungere copertine e metadati mancanti per gli elementi selezionati. Attiva l'opzione per consentire a Quick Match di sovrascrivere copertine e/o metadati esistenti.",
|
"MessageBatchQuickMatchDescription": "Quick Match tenterà di aggiungere copertine e metadati mancanti per gli elementi selezionati. Attiva l'opzione per consentire a Quick Match di sovrascrivere copertine e/o metadati esistenti.",
|
||||||
"MessageBookshelfNoCollections": "Non hai ancora creato nessuna raccolta ",
|
"MessageBookshelfNoCollections": "Non hai ancora creato nessuna raccolta ",
|
||||||
"MessageBookshelfNoResultsForFilter": "Nessul risultato per il filtro \"{0}: {1}\"",
|
"MessageBookshelfNoResultsForFilter": "Nessul risultato per il filtro \"{0}: {1}\"",
|
||||||
"MessageBookshelfNoRSSFeeds": "Nessun RSS feeds aperto",
|
"MessageBookshelfNoRSSFeeds": "Nessun RSS feeds aperto",
|
||||||
"MessageBookshelfNoSeries": "You have no series",
|
"MessageBookshelfNoSeries": "Non c'è nessuna Serie",
|
||||||
"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",
|
"MessageChapterErrorFirstNotZero": "Il primo capitolo deve iniziare da 0",
|
||||||
"MessageChapterErrorStartGteDuration": "Invalid start time must be less than audiobook duration",
|
"MessageChapterErrorStartGteDuration": "L'ora di inizio non valida deve essere inferiore alla durata dell'audiolibro",
|
||||||
"MessageChapterErrorStartLtPrev": "Invalid start time must be greater than or equal to previous chapter start time",
|
"MessageChapterErrorStartLtPrev": "L'ora di inizio non valida deve essere maggiore o uguale all'ora di inizio del capitolo precedente",
|
||||||
"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}?",
|
||||||
@ -438,7 +438,7 @@
|
|||||||
"MessageConfirmRemoveCollection": "Sei sicuro di voler rimuovere la Raccolta \"{0}\"?",
|
"MessageConfirmRemoveCollection": "Sei sicuro di voler rimuovere la Raccolta \"{0}\"?",
|
||||||
"MessageConfirmRemoveEpisode": "Sei sicuro di voler rimuovere l'episodio \"{0}\"?",
|
"MessageConfirmRemoveEpisode": "Sei sicuro di voler rimuovere l'episodio \"{0}\"?",
|
||||||
"MessageConfirmRemoveEpisodes": "Sei sicuro di voler rimuovere {0} episodi?",
|
"MessageConfirmRemoveEpisodes": "Sei sicuro di voler rimuovere {0} episodi?",
|
||||||
"MessageConfirmRemovePlaylist": "Are you sure you want to remove your playlist \"{0}\"?",
|
"MessageConfirmRemovePlaylist": "Sei sicuro di voler rimuovere la tua playlist \"{0}\"?",
|
||||||
"MessageDownloadingEpisode": "Download episodio in corso",
|
"MessageDownloadingEpisode": "Download episodio in corso",
|
||||||
"MessageDragFilesIntoTrackOrder": "Trascina i file nell'ordine di traccia corretto",
|
"MessageDragFilesIntoTrackOrder": "Trascina i file nell'ordine di traccia corretto",
|
||||||
"MessageEmbedFinished": "Incorporamento finito!",
|
"MessageEmbedFinished": "Incorporamento finito!",
|
||||||
@ -481,11 +481,11 @@
|
|||||||
"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",
|
"MessageNoSeries": "Nessuna Serie",
|
||||||
"MessageNotYetImplemented": "Non Ancora Implementato",
|
"MessageNotYetImplemented": "Non Ancora Implementato",
|
||||||
"MessageNoUpdateNecessary": "Nessun aggiornamento necessario",
|
"MessageNoUpdateNecessary": "Nessun aggiornamento necessario",
|
||||||
"MessageNoUpdatesWereNecessary": "Nessun aggiornamento necessario",
|
"MessageNoUpdatesWereNecessary": "Nessun aggiornamento necessario",
|
||||||
"MessageNoUserPlaylists": "You have no playlists",
|
"MessageNoUserPlaylists": "non hai nessuna Playlist",
|
||||||
"MessageOr": "o",
|
"MessageOr": "o",
|
||||||
"MessagePauseChapter": "Metti in Pausa Capitolo",
|
"MessagePauseChapter": "Metti in Pausa Capitolo",
|
||||||
"MessagePlayChapter": "Ascolta dall'inizio del capitolo",
|
"MessagePlayChapter": "Ascolta dall'inizio del capitolo",
|
||||||
@ -494,15 +494,15 @@
|
|||||||
"MessageRemoveAllItemsWarning": "AVVERTIMENTO! Questa azione rimuoverà tutti gli elementi della libreria dal database, inclusi eventuali aggiornamenti o corrispondenze apportate. Questo non fa nulla ai tuoi file effettivi. Sei sicuro?",
|
"MessageRemoveAllItemsWarning": "AVVERTIMENTO! Questa azione rimuoverà tutti gli elementi della libreria dal database, inclusi eventuali aggiornamenti o corrispondenze apportate. Questo non fa nulla ai tuoi file effettivi. Sei sicuro?",
|
||||||
"MessageRemoveChapter": "Rimuovi Capitolo",
|
"MessageRemoveChapter": "Rimuovi Capitolo",
|
||||||
"MessageRemoveEpisodes": "rimuovi {0} episodio(i)",
|
"MessageRemoveEpisodes": "rimuovi {0} episodio(i)",
|
||||||
"MessageRemoveFromPlayerQueue": "Remove from player queue",
|
"MessageRemoveFromPlayerQueue": "Rimuovi dalla coda di riproduzione",
|
||||||
"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?",
|
"MessageResetChaptersConfirm": "Sei sicuro di voler reimpostare i capitoli e annullare le modifiche ?",
|
||||||
"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",
|
"MessageSetChaptersFromTracksDescription": "Impostare i capitoli utilizzando ciascun file audio come capitolo e il titolo del capitolo come nome del file audio",
|
||||||
"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",
|
||||||
@ -524,7 +524,7 @@
|
|||||||
"NoteUploaderUnsupportedFiles": "I file non supportati vengono ignorati. Quando si sceglie o si elimina una cartella, gli altri file che non si trovano in una cartella di elementi vengono ignorati.",
|
"NoteUploaderUnsupportedFiles": "I file non supportati vengono ignorati. Quando si sceglie o si elimina una cartella, gli altri file che non si trovano in una cartella di elementi vengono ignorati.",
|
||||||
"PlaceholderNewCollection": "Nome Nuova Raccolta",
|
"PlaceholderNewCollection": "Nome Nuova Raccolta",
|
||||||
"PlaceholderNewFolderPath": "Nuovo percorso Cartella",
|
"PlaceholderNewFolderPath": "Nuovo percorso Cartella",
|
||||||
"PlaceholderNewPlaylist": "New playlist name",
|
"PlaceholderNewPlaylist": "Nome nuova playlist",
|
||||||
"PlaceholderSearch": "Cerca..",
|
"PlaceholderSearch": "Cerca..",
|
||||||
"ToastAccountUpdateFailed": "Aggiornamento Account Fallito",
|
"ToastAccountUpdateFailed": "Aggiornamento Account Fallito",
|
||||||
"ToastAccountUpdateSuccess": "Account Aggiornato",
|
"ToastAccountUpdateSuccess": "Account Aggiornato",
|
||||||
@ -549,8 +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",
|
"ToastChaptersHaveErrors": "I capitoli contengono errori",
|
||||||
"ToastChaptersMustHaveTitles": "Chapters must have titles",
|
"ToastChaptersMustHaveTitles": "I capitoli devono avere titoli",
|
||||||
"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",
|
||||||
@ -574,10 +574,10 @@
|
|||||||
"ToastLibraryScanStarted": "Scansione Libreria iniziata",
|
"ToastLibraryScanStarted": "Scansione Libreria iniziata",
|
||||||
"ToastLibraryUpdateFailed": "Errore Aggiornamento libreria",
|
"ToastLibraryUpdateFailed": "Errore Aggiornamento libreria",
|
||||||
"ToastLibraryUpdateSuccess": "Libreria \"{0}\" aggiornata",
|
"ToastLibraryUpdateSuccess": "Libreria \"{0}\" aggiornata",
|
||||||
"ToastPlaylistRemoveFailed": "Failed to remove playlist",
|
"ToastPlaylistRemoveFailed": "Rimozione Playlist Fallita",
|
||||||
"ToastPlaylistRemoveSuccess": "Playlist removed",
|
"ToastPlaylistRemoveSuccess": "Playlist rimossa",
|
||||||
"ToastPlaylistUpdateFailed": "Failed to update playlist",
|
"ToastPlaylistUpdateFailed": "Aggiornamento Playlist Fallita",
|
||||||
"ToastPlaylistUpdateSuccess": "Playlist updated",
|
"ToastPlaylistUpdateSuccess": "Playlist Aggiornata",
|
||||||
"ToastPodcastCreateFailed": "Errore Creazione podcast",
|
"ToastPodcastCreateFailed": "Errore Creazione podcast",
|
||||||
"ToastPodcastCreateSuccess": "Podcast creato Correttamwnte",
|
"ToastPodcastCreateSuccess": "Podcast creato Correttamwnte",
|
||||||
"ToastRemoveItemFromCollectionFailed": "Errore rimozione file dalla Raccolta",
|
"ToastRemoveItemFromCollectionFailed": "Errore rimozione file dalla Raccolta",
|
||||||
@ -598,4 +598,4 @@
|
|||||||
"WeekdayThursday": "Giovedi",
|
"WeekdayThursday": "Giovedi",
|
||||||
"WeekdayTuesday": "Martedì",
|
"WeekdayTuesday": "Martedì",
|
||||||
"WeekdayWednesday": "Mercoledì"
|
"WeekdayWednesday": "Mercoledì"
|
||||||
}
|
}
|
||||||
|
4
package-lock.json
generated
4
package-lock.json
generated
@ -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,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": {
|
||||||
|
@ -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()))
|
||||||
}
|
}
|
||||||
@ -77,12 +81,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)
|
||||||
@ -90,11 +94,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
|
||||||
})
|
})
|
||||||
@ -107,7 +111,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
|
||||||
@ -117,7 +121,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++) {
|
||||||
@ -125,32 +129,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)
|
||||||
@ -172,10 +181,10 @@ 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
|
||||||
@ -218,7 +227,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.')
|
||||||
}
|
}
|
||||||
@ -248,7 +257,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) {
|
||||||
@ -334,7 +343,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)
|
||||||
@ -351,8 +360,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,
|
||||||
@ -363,7 +372,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([
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
const fs = require('../libs/fsExtra')
|
||||||
const Logger = require('../Logger')
|
const Logger = require('../Logger')
|
||||||
const SocketAuthority = require('../SocketAuthority')
|
const SocketAuthority = require('../SocketAuthority')
|
||||||
|
|
||||||
@ -178,7 +179,15 @@ class LibraryItemController {
|
|||||||
|
|
||||||
// GET api/items/:id/cover
|
// GET api/items/:id/cover
|
||||||
async getCover(req, res) {
|
async getCover(req, res) {
|
||||||
let { query: { width, height, format }, libraryItem } = req
|
const { query: { width, height, format, raw }, libraryItem } = req
|
||||||
|
|
||||||
|
if (raw) { // any value
|
||||||
|
if (!libraryItem.media.coverPath || !await fs.pathExists(libraryItem.media.coverPath)) {
|
||||||
|
return res.sendStatus(404)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.sendFile(libraryItem.media.coverPath)
|
||||||
|
}
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
format: format || (reqSupportsWebp(req) ? 'webp' : 'jpeg'),
|
format: format || (reqSupportsWebp(req) ? 'webp' : 'jpeg'),
|
||||||
|
@ -47,7 +47,7 @@ class CacheManager {
|
|||||||
|
|
||||||
res.type(`image/${format}`)
|
res.type(`image/${format}`)
|
||||||
|
|
||||||
var path = Path.join(this.CoverCachePath, `${libraryItem.id}_${width}${height ? `x${height}` : ''}`) + '.' + format
|
const path = Path.join(this.CoverCachePath, `${libraryItem.id}_${width}${height ? `x${height}` : ''}`) + '.' + format
|
||||||
|
|
||||||
// Cache exists
|
// Cache exists
|
||||||
if (await fs.pathExists(path)) {
|
if (await fs.pathExists(path)) {
|
||||||
@ -66,7 +66,7 @@ class CacheManager {
|
|||||||
return res.sendStatus(500)
|
return res.sendStatus(500)
|
||||||
}
|
}
|
||||||
|
|
||||||
let writtenFile = await resizeImage(libraryItem.media.coverPath, path, width, height)
|
const writtenFile = await resizeImage(libraryItem.media.coverPath, path, width, height)
|
||||||
if (!writtenFile) return res.sendStatus(500)
|
if (!writtenFile) return res.sendStatus(500)
|
||||||
|
|
||||||
// Set owner and permissions of cache image
|
// Set owner and permissions of cache image
|
||||||
|
@ -118,18 +118,25 @@ class PlaybackSessionManager {
|
|||||||
|
|
||||||
async startSession(user, deviceInfo, libraryItem, episodeId, options) {
|
async startSession(user, deviceInfo, libraryItem, episodeId, options) {
|
||||||
// Close any sessions already open for user
|
// Close any sessions already open for user
|
||||||
var userSessions = this.sessions.filter(playbackSession => playbackSession.userId === user.id)
|
const userSessions = this.sessions.filter(playbackSession => playbackSession.userId === user.id)
|
||||||
for (const session of userSessions) {
|
for (const session of userSessions) {
|
||||||
Logger.info(`[PlaybackSessionManager] startSession: Closing open session "${session.displayTitle}" for user "${user.username}"`)
|
Logger.info(`[PlaybackSessionManager] startSession: Closing open session "${session.displayTitle}" for user "${user.username}"`)
|
||||||
await this.closeSession(user, session, null)
|
await this.closeSession(user, session, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
var shouldDirectPlay = options.forceDirectPlay || (!options.forceTranscode && libraryItem.media.checkCanDirectPlay(options, episodeId))
|
const shouldDirectPlay = options.forceDirectPlay || (!options.forceTranscode && libraryItem.media.checkCanDirectPlay(options, episodeId))
|
||||||
var mediaPlayer = options.mediaPlayer || 'unknown'
|
const mediaPlayer = options.mediaPlayer || 'unknown'
|
||||||
|
|
||||||
const userProgress = user.getMediaProgress(libraryItem.id, episodeId)
|
const userProgress = user.getMediaProgress(libraryItem.id, episodeId)
|
||||||
var userStartTime = 0
|
let userStartTime = 0
|
||||||
if (userProgress) userStartTime = Number.parseFloat(userProgress.currentTime) || 0
|
if (userProgress) {
|
||||||
|
if (userProgress.isFinished) {
|
||||||
|
Logger.info(`[PlaybackSessionManager] Starting session for user "${user.username}" and resetting progress for finished item "${libraryItem.media.metadata.title}"`)
|
||||||
|
// Keep userStartTime as 0 so the client restarts the media
|
||||||
|
} else {
|
||||||
|
userStartTime = Number.parseFloat(userProgress.currentTime) || 0
|
||||||
|
}
|
||||||
|
}
|
||||||
const newPlaybackSession = new PlaybackSession()
|
const newPlaybackSession = new PlaybackSession()
|
||||||
newPlaybackSession.setData(libraryItem, user, mediaPlayer, deviceInfo, userStartTime, episodeId)
|
newPlaybackSession.setData(libraryItem, user, mediaPlayer, deviceInfo, userStartTime, episodeId)
|
||||||
|
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user