mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-06-03 13:44:36 -04:00
Support cbr and cbz comics and comic reader #109
This commit is contained in:
parent
18c1d8f1a3
commit
88e2bac3f5
219
client/components/app/ComicReader.vue
Normal file
219
client/components/app/ComicReader.vue
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
<template>
|
||||||
|
<div class="w-full">
|
||||||
|
<div v-show="showPageMenu" v-click-outside="clickOutside" class="pagemenu absolute top-9 right-20 rounded-md overflow-y-auto bg-white shadow-lg z-20 border border-gray-400">
|
||||||
|
<div v-for="(file, index) in pages" :key="file" class="w-full cursor-pointer hover:bg-gray-200 px-2 py-1" @click="setPage(index)">
|
||||||
|
<p class="text-sm">{{ file }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="absolute top-0 right-40 border-b border-l border-r border-gray-400 hover:bg-gray-200 cursor-pointer rounded-b-md bg-gray-50 w-10 h-9 flex items-center justify-center text-center z-20" @mousedown.prevent @click.stop.prevent="showPageMenu = !showPageMenu">
|
||||||
|
<span class="material-icons">menu</span>
|
||||||
|
</div>
|
||||||
|
<div class="absolute top-0 right-20 border-b border-l border-r border-gray-400 rounded-b-md bg-gray-50 px-2 h-9 flex items-center text-center">
|
||||||
|
<p class="font-mono">{{ page + 1 }} / {{ numPages }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="overflow-hidden m-auto comicwrapper relative">
|
||||||
|
<div class="flex items-center justify-center">
|
||||||
|
<div class="px-12">
|
||||||
|
<span v-show="loadedFirstPage" class="material-icons text-5xl text-black" :class="!canGoPrev ? 'text-opacity-10' : 'cursor-pointer text-opacity-30 hover:text-opacity-90'" @click.stop.prevent="goPrevPage" @mousedown.prevent>arrow_back_ios</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<img v-if="mainImg" :src="mainImg" class="object-contain comicimg" />
|
||||||
|
|
||||||
|
<div class="px-12">
|
||||||
|
<span v-show="loadedFirstPage" class="material-icons text-5xl text-black" :class="!canGoNext ? 'text-opacity-10' : 'cursor-pointer text-opacity-30 hover:text-opacity-90'" @click.stop.prevent="goNextPage" @mousedown.prevent>arrow_forward_ios</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-show="loading" class="w-full h-full absolute top-0 left-0 flex items-center justify-center z-10">
|
||||||
|
<ui-loading-indicator />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- <div v-show="loading" class="w-screen h-screen absolute top-0 left-0 bg-black bg-opacity-20 flex items-center justify-center">
|
||||||
|
<ui-loading-indicator />
|
||||||
|
</div> -->
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Path from 'path'
|
||||||
|
import { Archive } from 'libarchive.js/main.js'
|
||||||
|
|
||||||
|
Archive.init({
|
||||||
|
workerUrl: '/libarchive/worker-bundle.js'
|
||||||
|
})
|
||||||
|
// Archive.init()
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
src: String
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
pages: null,
|
||||||
|
filesObject: null,
|
||||||
|
mainImg: null,
|
||||||
|
page: 0,
|
||||||
|
numPages: 0,
|
||||||
|
showPageMenu: false,
|
||||||
|
loadTimeout: null,
|
||||||
|
loadedFirstPage: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
src: {
|
||||||
|
immediate: true,
|
||||||
|
handler(newVal) {
|
||||||
|
this.extract()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
canGoNext() {
|
||||||
|
return this.page < this.numPages - 1
|
||||||
|
},
|
||||||
|
canGoPrev() {
|
||||||
|
return this.page > 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
clickOutside() {
|
||||||
|
if (this.showPageMenu) this.showPageMenu = false
|
||||||
|
},
|
||||||
|
goNextPage() {
|
||||||
|
if (!this.canGoNext) return
|
||||||
|
this.setPage(this.page + 1)
|
||||||
|
},
|
||||||
|
goPrevPage() {
|
||||||
|
if (!this.canGoPrev) return
|
||||||
|
this.setPage(this.page - 1)
|
||||||
|
},
|
||||||
|
setPage(index) {
|
||||||
|
if (index < 0 || index > this.numPages - 1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var filename = this.pages[index]
|
||||||
|
this.page = index
|
||||||
|
return this.extractFile(filename)
|
||||||
|
},
|
||||||
|
setLoadTimeout() {
|
||||||
|
this.loadTimeout = setTimeout(() => {
|
||||||
|
this.loading = true
|
||||||
|
}, 150)
|
||||||
|
},
|
||||||
|
extractFile(filename) {
|
||||||
|
return new Promise(async (resolve) => {
|
||||||
|
this.setLoadTimeout()
|
||||||
|
var file = await this.filesObject[filename].extract()
|
||||||
|
var reader = new FileReader()
|
||||||
|
reader.onload = (e) => {
|
||||||
|
this.mainImg = e.target.result
|
||||||
|
this.loading = false
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
reader.onerror = (e) => {
|
||||||
|
console.error(e)
|
||||||
|
this.$toast.error('Read page file failed')
|
||||||
|
this.loading = false
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
reader.readAsDataURL(file)
|
||||||
|
clearTimeout(this.loadTimeout)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
async extract() {
|
||||||
|
this.loading = true
|
||||||
|
console.log('Extracting', this.src)
|
||||||
|
|
||||||
|
var buff = await this.$axios.$get(this.src, {
|
||||||
|
responseType: 'blob'
|
||||||
|
})
|
||||||
|
const archive = await Archive.open(buff)
|
||||||
|
this.filesObject = await archive.getFilesObject()
|
||||||
|
var filenames = Object.keys(this.filesObject)
|
||||||
|
this.parseFilenames(filenames)
|
||||||
|
|
||||||
|
this.numPages = this.pages.length
|
||||||
|
|
||||||
|
if (this.pages.length) {
|
||||||
|
this.loading = false
|
||||||
|
await this.setPage(0)
|
||||||
|
this.loadedFirstPage = true
|
||||||
|
} else {
|
||||||
|
this.$toast.error('Unable to extract pages')
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
parseImageFilename(filename) {
|
||||||
|
var basename = Path.basename(filename, Path.extname(filename))
|
||||||
|
var numbersinpath = basename.match(/\d{1,4}/g)
|
||||||
|
if (!numbersinpath || !numbersinpath.length) {
|
||||||
|
return {
|
||||||
|
index: -1,
|
||||||
|
filename
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
index: Number(numbersinpath[numbersinpath.length - 1]),
|
||||||
|
filename
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
parseFilenames(filenames) {
|
||||||
|
const acceptableImages = ['.jpeg', '.jpg', '.png']
|
||||||
|
var imageFiles = filenames.filter((f) => {
|
||||||
|
return acceptableImages.includes((Path.extname(f) || '').toLowerCase())
|
||||||
|
})
|
||||||
|
var imageFileObjs = imageFiles.map((img) => {
|
||||||
|
return this.parseImageFilename(img)
|
||||||
|
})
|
||||||
|
|
||||||
|
var imagesWithNum = imageFileObjs.filter((i) => i.index >= 0)
|
||||||
|
var orderedImages = imagesWithNum.sort((a, b) => a.index - b.index).map((i) => i.filename)
|
||||||
|
var noNumImages = imageFileObjs.filter((i) => i.index < 0)
|
||||||
|
orderedImages = orderedImages.concat(noNumImages.map((i) => i.filename))
|
||||||
|
|
||||||
|
this.pages = orderedImages
|
||||||
|
},
|
||||||
|
keyUp(e) {
|
||||||
|
if ((e.keyCode || e.which) == 37) {
|
||||||
|
this.goPrevPage()
|
||||||
|
} else if ((e.keyCode || e.which) == 39) {
|
||||||
|
this.goNextPage()
|
||||||
|
} else if ((e.keyCode || e.which) == 27) {
|
||||||
|
this.unregisterListeners()
|
||||||
|
this.$emit('close')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
registerListeners() {
|
||||||
|
document.addEventListener('keyup', this.keyUp)
|
||||||
|
},
|
||||||
|
unregisterListeners() {
|
||||||
|
document.removeEventListener('keyup', this.keyUp)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.registerListeners()
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
this.unregisterListeners()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.pagemenu {
|
||||||
|
max-height: calc(100vh - 60px);
|
||||||
|
}
|
||||||
|
.comicimg {
|
||||||
|
height: calc(100vh - 40px);
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
.comicwrapper {
|
||||||
|
width: calc(100vw - 300px);
|
||||||
|
height: calc(100vh - 40px);
|
||||||
|
}
|
||||||
|
</style>
|
@ -39,6 +39,10 @@
|
|||||||
<div v-else-if="ebookType === 'pdf'" class="h-full flex items-center">
|
<div v-else-if="ebookType === 'pdf'" class="h-full flex items-center">
|
||||||
<app-pdf-reader :src="ebookUrl" />
|
<app-pdf-reader :src="ebookUrl" />
|
||||||
</div>
|
</div>
|
||||||
|
<!-- COMIC -->
|
||||||
|
<div v-else-if="ebookType === 'comic'" class="h-full flex items-center">
|
||||||
|
<app-comic-reader :src="ebookUrl" @close="show = false" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="absolute bottom-2 left-2">{{ ebookType }}</div>
|
<div class="absolute bottom-2 left-2">{{ ebookType }}</div>
|
||||||
</div>
|
</div>
|
||||||
@ -111,6 +115,9 @@ export default {
|
|||||||
pdfEbook() {
|
pdfEbook() {
|
||||||
return this.ebooks.find((eb) => eb.ext === '.pdf')
|
return this.ebooks.find((eb) => eb.ext === '.pdf')
|
||||||
},
|
},
|
||||||
|
comicEbook() {
|
||||||
|
return this.ebooks.find((eb) => eb.ext === '.cbz' || eb.ext === '.cbr')
|
||||||
|
},
|
||||||
userToken() {
|
userToken() {
|
||||||
return this.$store.getters['user/getToken']
|
return this.$store.getters['user/getToken']
|
||||||
},
|
},
|
||||||
@ -158,7 +165,6 @@ export default {
|
|||||||
document.removeEventListener('keyup', this.keyUp)
|
document.removeEventListener('keyup', this.keyUp)
|
||||||
},
|
},
|
||||||
init() {
|
init() {
|
||||||
this.registerListeners()
|
|
||||||
if (this.selectedAudiobookFile) {
|
if (this.selectedAudiobookFile) {
|
||||||
this.ebookUrl = this.getEbookUrl(this.selectedAudiobookFile.path)
|
this.ebookUrl = this.getEbookUrl(this.selectedAudiobookFile.path)
|
||||||
if (this.selectedAudiobookFile.ext === '.pdf') {
|
if (this.selectedAudiobookFile.ext === '.pdf') {
|
||||||
@ -169,6 +175,8 @@ export default {
|
|||||||
} else if (this.selectedAudiobookFile.ext === '.epub') {
|
} else if (this.selectedAudiobookFile.ext === '.epub') {
|
||||||
this.ebookType = 'epub'
|
this.ebookType = 'epub'
|
||||||
this.initEpub()
|
this.initEpub()
|
||||||
|
} else if (this.selectedAudiobookFile.ext === '.cbr' || this.selectedAudiobookFile.ext === '.cbz') {
|
||||||
|
this.ebookType = 'comic'
|
||||||
}
|
}
|
||||||
} else if (this.epubEbook) {
|
} else if (this.epubEbook) {
|
||||||
this.ebookType = 'epub'
|
this.ebookType = 'epub'
|
||||||
@ -181,6 +189,9 @@ export default {
|
|||||||
} else if (this.pdfEbook) {
|
} else if (this.pdfEbook) {
|
||||||
this.ebookType = 'pdf'
|
this.ebookType = 'pdf'
|
||||||
this.ebookUrl = this.getEbookUrl(this.pdfEbook.path)
|
this.ebookUrl = this.getEbookUrl(this.pdfEbook.path)
|
||||||
|
} else if (this.comicEbook) {
|
||||||
|
this.ebookType = 'comic'
|
||||||
|
this.ebookUrl = this.getEbookUrl(this.comicEbook.path)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
addHtmlCss() {
|
addHtmlCss() {
|
||||||
@ -266,6 +277,7 @@ export default {
|
|||||||
reader.readAsArrayBuffer(buff)
|
reader.readAsArrayBuffer(buff)
|
||||||
},
|
},
|
||||||
initEpub() {
|
initEpub() {
|
||||||
|
this.registerListeners()
|
||||||
// var book = ePub(this.url, {
|
// var book = ePub(this.url, {
|
||||||
// requestHeaders: {
|
// requestHeaders: {
|
||||||
// Authorization: `Bearer ${this.userToken}`
|
// Authorization: `Bearer ${this.userToken}`
|
||||||
@ -327,14 +339,16 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
close() {
|
close() {
|
||||||
this.unregisterListeners()
|
if (this.ebookType === 'epub') {
|
||||||
|
this.unregisterListeners()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
if (this.show) this.init()
|
if (this.show) this.init()
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.unregisterListeners()
|
this.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -19,7 +19,7 @@ module.exports = {
|
|||||||
|
|
||||||
// Global page headers: https://go.nuxtjs.dev/config-head
|
// Global page headers: https://go.nuxtjs.dev/config-head
|
||||||
head: {
|
head: {
|
||||||
title: 'AudioBookshelf',
|
title: 'Audiobookshelf',
|
||||||
htmlAttrs: {
|
htmlAttrs: {
|
||||||
lang: 'en'
|
lang: 'en'
|
||||||
},
|
},
|
||||||
@ -98,8 +98,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// Build Configuration: https://go.nuxtjs.dev/config-build
|
// Build Configuration: https://go.nuxtjs.dev/config-build
|
||||||
build: {
|
build: {},
|
||||||
},
|
|
||||||
watchers: {
|
watchers: {
|
||||||
webpack: {
|
webpack: {
|
||||||
aggregateTimeout: 300,
|
aggregateTimeout: 300,
|
||||||
|
7
client/package-lock.json
generated
7
client/package-lock.json
generated
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "audiobookshelf-client",
|
"name": "audiobookshelf-client",
|
||||||
"version": "1.4.8",
|
"version": "1.4.10",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -7385,6 +7385,11 @@
|
|||||||
"launch-editor": "^2.2.1"
|
"launch-editor": "^2.2.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"libarchive.js": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/libarchive.js/-/libarchive.js-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-EkQfRXt9DhWwj6BnEA2TNpOf4jTnzSTUPGgE+iFxcdNqjktY8GitbDeHnx8qZA0/IukNyyBUR3oQKRdYkO+HFg=="
|
||||||
|
},
|
||||||
"lie": {
|
"lie": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "audiobookshelf-client",
|
"name": "audiobookshelf-client",
|
||||||
"version": "1.4.9",
|
"version": "1.4.10",
|
||||||
"description": "Audiobook manager and player",
|
"description": "Audiobook manager and player",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@ -19,6 +19,7 @@
|
|||||||
"date-fns": "^2.25.0",
|
"date-fns": "^2.25.0",
|
||||||
"epubjs": "^0.3.88",
|
"epubjs": "^0.3.88",
|
||||||
"hls.js": "^1.0.7",
|
"hls.js": "^1.0.7",
|
||||||
|
"libarchive.js": "^1.3.0",
|
||||||
"nuxt": "^2.15.7",
|
"nuxt": "^2.15.7",
|
||||||
"nuxt-socket-io": "^1.1.18",
|
"nuxt-socket-io": "^1.1.18",
|
||||||
"vue-pdf": "^4.3.0",
|
"vue-pdf": "^4.3.0",
|
||||||
@ -29,4 +30,4 @@
|
|||||||
"@nuxtjs/tailwindcss": "^4.2.1",
|
"@nuxtjs/tailwindcss": "^4.2.1",
|
||||||
"postcss": "^8.3.6"
|
"postcss": "^8.3.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -104,7 +104,7 @@
|
|||||||
{{ isMissing ? 'Missing' : 'Incomplete' }}
|
{{ isMissing ? 'Missing' : 'Incomplete' }}
|
||||||
</ui-btn>
|
</ui-btn>
|
||||||
|
|
||||||
<ui-btn v-if="showExperimentalFeatures && (epubEbook || mobiEbook)" color="info" :padding-x="4" small class="flex items-center h-9 mr-2" @click="openEbook">
|
<ui-btn v-if="showExperimentalFeatures && numEbooks" color="info" :padding-x="4" small class="flex items-center h-9 mr-2" @click="openEbook">
|
||||||
<span class="material-icons -ml-2 pr-2 text-white">auto_stories</span>
|
<span class="material-icons -ml-2 pr-2 text-white">auto_stories</span>
|
||||||
Read
|
Read
|
||||||
</ui-btn>
|
</ui-btn>
|
||||||
@ -322,16 +322,14 @@ export default {
|
|||||||
return this.audiobook.ebooks
|
return this.audiobook.ebooks
|
||||||
},
|
},
|
||||||
showEpubAlert() {
|
showEpubAlert() {
|
||||||
return this.ebooks.length && !this.epubEbook && !this.tracks.length
|
return this.ebooks.length && !this.numEbooks && !this.tracks.length
|
||||||
},
|
},
|
||||||
showExperimentalReadAlert() {
|
showExperimentalReadAlert() {
|
||||||
return !this.tracks.length && this.ebooks.length && !this.showExperimentalFeatures
|
return !this.tracks.length && this.ebooks.length && !this.showExperimentalFeatures
|
||||||
},
|
},
|
||||||
epubEbook() {
|
numEbooks() {
|
||||||
return this.ebooks.find((eb) => eb.ext === '.epub')
|
// Number of currently supported for reading ebooks, not all ebooks
|
||||||
},
|
return this.audiobook.numEbooks
|
||||||
mobiEbook() {
|
|
||||||
return this.ebooks.find((eb) => eb.ext === '.mobi' || eb.ext === '.azw3')
|
|
||||||
},
|
},
|
||||||
userToken() {
|
userToken() {
|
||||||
return this.$store.getters['user/getToken']
|
return this.$store.getters['user/getToken']
|
||||||
|
15
client/static/libarchive/wasm-gen/libarchive.js
Normal file
15
client/static/libarchive/wasm-gen/libarchive.js
Normal file
File diff suppressed because one or more lines are too long
BIN
client/static/libarchive/wasm-gen/libarchive.wasm
Normal file
BIN
client/static/libarchive/wasm-gen/libarchive.wasm
Normal file
Binary file not shown.
1
client/static/libarchive/worker-bundle.js
Normal file
1
client/static/libarchive/worker-bundle.js
Normal file
File diff suppressed because one or more lines are too long
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "audiobookshelf",
|
"name": "audiobookshelf",
|
||||||
"version": "1.4.9",
|
"version": "1.4.10",
|
||||||
"description": "Self-hosted audiobook server for managing and playing audiobooks",
|
"description": "Self-hosted audiobook server for managing and playing audiobooks",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -139,6 +139,10 @@ class Audiobook {
|
|||||||
return this.ebooks.find(file => file.ext === '.pdf')
|
return this.ebooks.find(file => file.ext === '.pdf')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get hasComic() {
|
||||||
|
return this.ebooks.find(file => file.ext === '.cbr' || file.ext === '.cbz')
|
||||||
|
}
|
||||||
|
|
||||||
get hasMissingIno() {
|
get hasMissingIno() {
|
||||||
return !this.ino || this._audioFiles.find(abf => !abf.ino) || this._otherFiles.find(f => !f.ino) || this._tracks.find(t => !t.ino)
|
return !this.ino || this._audioFiles.find(abf => !abf.ino) || this._otherFiles.find(f => !f.ino) || this._tracks.find(t => !t.ino)
|
||||||
}
|
}
|
||||||
@ -210,7 +214,7 @@ class Audiobook {
|
|||||||
hasInvalidParts: this.invalidParts ? this.invalidParts.length : 0,
|
hasInvalidParts: this.invalidParts ? this.invalidParts.length : 0,
|
||||||
// numEbooks: this.ebooks.length,
|
// numEbooks: this.ebooks.length,
|
||||||
ebooks: this.ebooks.map(ebook => ebook.toJSON()),
|
ebooks: this.ebooks.map(ebook => ebook.toJSON()),
|
||||||
numEbooks: (this.hasEpub || this.hasMobi || this.hasPdf) ? 1 : 0, // Only supporting epubs in the reader currently
|
numEbooks: (this.hasEpub || this.hasMobi || this.hasPdf || this.hasComic) ? 1 : 0,
|
||||||
numTracks: this.tracks.length,
|
numTracks: this.tracks.length,
|
||||||
chapters: this.chapters || [],
|
chapters: this.chapters || [],
|
||||||
isMissing: !!this.isMissing,
|
isMissing: !!this.isMissing,
|
||||||
@ -237,7 +241,7 @@ class Audiobook {
|
|||||||
audioFiles: this._audioFiles.map(audioFile => audioFile.toJSON()),
|
audioFiles: this._audioFiles.map(audioFile => audioFile.toJSON()),
|
||||||
otherFiles: this._otherFiles.map(otherFile => otherFile.toJSON()),
|
otherFiles: this._otherFiles.map(otherFile => otherFile.toJSON()),
|
||||||
ebooks: this.ebooks.map(ebook => ebook.toJSON()),
|
ebooks: this.ebooks.map(ebook => ebook.toJSON()),
|
||||||
numEbooks: (this.hasEpub || this.hasMobi || this.hasPdf) ? 1 : 0,
|
numEbooks: (this.hasEpub || this.hasMobi || this.hasPdf || this.hasComic) ? 1 : 0,
|
||||||
numTracks: this.tracks.length,
|
numTracks: this.tracks.length,
|
||||||
tags: this.tags,
|
tags: this.tags,
|
||||||
book: this.bookToJSON(),
|
book: this.bookToJSON(),
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
const globals = {
|
const globals = {
|
||||||
SupportedImageTypes: ['png', 'jpg', 'jpeg', 'webp'],
|
SupportedImageTypes: ['png', 'jpg', 'jpeg', 'webp'],
|
||||||
SupportedAudioTypes: ['m4b', 'mp3', 'm4a', 'flac', 'opus', 'mp4'],
|
SupportedAudioTypes: ['m4b', 'mp3', 'm4a', 'flac', 'opus', 'mp4'],
|
||||||
SupportedEbookTypes: ['epub', 'pdf', 'mobi', 'azw3']
|
SupportedEbookTypes: ['epub', 'pdf', 'mobi', 'azw3', 'cbr', 'cbz']
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = globals
|
module.exports = globals
|
||||||
|
Loading…
x
Reference in New Issue
Block a user