Compare commits

...

4 Commits

Author SHA1 Message Date
Mark Cooper 3e5338ec8e Fixing scanner inodes, select all fix, starting ebook reader 2021-09-27 06:52:21 -05:00
Mark Cooper 01fdca4bf9 Fix docs link url 2021-09-26 17:21:10 -05:00
Mark Cooper ed96dd7c81 Readme update docs 2021-09-26 17:20:41 -05:00
Mark Cooper 1ead5de9f5 Hide volume number on selection mode 2021-09-26 15:44:25 -05:00
22 changed files with 665 additions and 68 deletions
+5 -2
View File
@@ -37,7 +37,9 @@
<div v-show="numAudiobooksSelected" class="absolute top-0 left-0 w-full h-full px-4 bg-primary flex items-center"> <div v-show="numAudiobooksSelected" class="absolute top-0 left-0 w-full h-full px-4 bg-primary flex items-center">
<h1 class="text-2xl px-4">{{ numAudiobooksSelected }} Selected</h1> <h1 class="text-2xl px-4">{{ numAudiobooksSelected }} Selected</h1>
<ui-btn small class="text-sm mx-2" @click="toggleSelectAll">{{ isAllSelected ? 'Select None' : 'Select All' }}</ui-btn> <ui-btn small class="text-sm mx-2" @click="toggleSelectAll"
>{{ isAllSelected ? 'Select None' : 'Select All' }}<span class="pl-2">({{ audiobooksShowing.length }})</span></ui-btn
>
<div class="flex-grow" /> <div class="flex-grow" />
@@ -87,7 +89,8 @@ export default {
return this.$store.state.user.user.audiobooks || {} return this.$store.state.user.user.audiobooks || {}
}, },
audiobooksShowing() { audiobooksShowing() {
return this.$store.getters['audiobooks/getFiltered']() // return this.$store.getters['audiobooks/getFiltered']()
return this.$store.getters['audiobooks/getEntitiesShowing']()
}, },
userCanUpdate() { userCanUpdate() {
return this.$store.getters['user/getUserCanUpdate'] return this.$store.getters['user/getUserCanUpdate']
+1 -1
View File
@@ -23,7 +23,7 @@
<template v-for="entity in shelf"> <template v-for="entity in shelf">
<cards-group-card v-if="showGroups" :key="entity.id" :width="bookCoverWidth" :group="entity" @click="clickGroup" /> <cards-group-card v-if="showGroups" :key="entity.id" :width="bookCoverWidth" :group="entity" @click="clickGroup" />
<!-- <cards-book-3d :key="entity.id" v-else :width="100" :src="$store.getters['audiobooks/getBookCoverSrc'](entity.book)" /> --> <!-- <cards-book-3d :key="entity.id" v-else :width="100" :src="$store.getters['audiobooks/getBookCoverSrc'](entity.book)" /> -->
<cards-book-card v-else :key="entity.id" :show-volume-number="selectedSeries" :width="bookCoverWidth" :user-progress="userAudiobooks[entity.id]" :audiobook="entity" /> <cards-book-card v-else :key="entity.id" :show-volume-number="!!selectedSeries" :width="bookCoverWidth" :user-progress="userAudiobooks[entity.id]" :audiobook="entity" />
</template> </template>
</div> </div>
<div class="bookshelfDivider h-4 w-full absolute bottom-0 left-0 right-0 z-10" /> <div class="bookshelfDivider h-4 w-full absolute bottom-0 left-0 right-0 z-10" />
+8 -1
View File
@@ -29,10 +29,14 @@
</div> </div>
</div> </div>
<div v-if="volumeNumber && showVolumeNumber && !isHovering" class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', padding: `${0.1 * sizeMultiplier}rem ${0.25 * sizeMultiplier}rem` }"> <div v-if="volumeNumber && showVolumeNumber && !isHovering && !isSelectionMode" class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', padding: `${0.1 * sizeMultiplier}rem ${0.25 * sizeMultiplier}rem` }">
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">#{{ volumeNumber }}</p> <p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">#{{ volumeNumber }}</p>
</div> </div>
<!-- <div v-if="true && hasEbook" class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md" :style="{ bottom: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', padding: `${0.1 * sizeMultiplier}rem ${0.25 * sizeMultiplier}rem` }">
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">EBook</p>
</div> -->
<div v-show="!isSelectionMode" class="absolute bottom-0 left-0 h-1 shadow-sm max-w-full" :class="userIsRead ? 'bg-success' : 'bg-yellow-400'" :style="{ width: width * userProgressPercent + 'px' }"></div> <div v-show="!isSelectionMode" class="absolute bottom-0 left-0 h-1 shadow-sm max-w-full" :class="userIsRead ? 'bg-success' : 'bg-yellow-400'" :style="{ width: width * userProgressPercent + 'px' }"></div>
<ui-tooltip v-if="showError" :text="errorText" class="absolute bottom-4 left-0"> <ui-tooltip v-if="showError" :text="errorText" class="absolute bottom-4 left-0">
@@ -78,6 +82,9 @@ export default {
audiobookId() { audiobookId() {
return this.audiobook.id return this.audiobook.id
}, },
hasEbook() {
return this.audiobook.numEbooks
},
isSelectionMode() { isSelectionMode() {
return this.$store.getters['getNumAudiobooksSelected'] return this.$store.getters['getNumAudiobooksSelected']
}, },
+1 -1
View File
@@ -23,7 +23,7 @@
<template v-for="track in files"> <template v-for="track in files">
<tr :key="track.path"> <tr :key="track.path">
<td class="font-book pl-2"> <td class="font-book pl-2">
{{ track.filename }} {{ track.filename }}<span class="text-white text-opacity-50 pl-4">({{ track.ino }})</span>
</td> </td>
<td class="font-mono"> <td class="font-mono">
{{ $bytesPretty(track.size) }} {{ $bytesPretty(track.size) }}
+1 -1
View File
@@ -21,7 +21,7 @@
<template v-for="file in files"> <template v-for="file in files">
<tr :key="file.path"> <tr :key="file.path">
<td class="font-book pl-2"> <td class="font-book pl-2">
{{ file.path }} {{ file.path }}<span class="text-white text-opacity-50 pl-4">({{ file.ino }})</span>
</td> </td>
<td class="text-xs"> <td class="text-xs">
<p>{{ file.filetype }}</p> <p>{{ file.filetype }}</p>
+1 -1
View File
@@ -27,7 +27,7 @@
<p>{{ track.index }}</p> <p>{{ track.index }}</p>
</td> </td>
<td class="font-book"> <td class="font-book">
{{ track.filename }} {{ track.filename }}<span class="text-white text-opacity-50 pl-4">({{ track.ino }})</span>
</td> </td>
<td class="font-mono"> <td class="font-mono">
{{ $bytesPretty(track.size) }} {{ $bytesPretty(track.size) }}
+1 -1
View File
@@ -1,7 +1,7 @@
export default function ({ store, redirect, route, app }) { export default function ({ store, redirect, route, app }) {
// If the user is not authenticated // If the user is not authenticated
if (!store.state.user.user) { if (!store.state.user.user) {
if (route.name === 'batch') return redirect('/login') if (route.name === 'batch' || route.name === 'index') return redirect('/login')
return redirect(`/login?redirect=${route.fullPath}`) return redirect(`/login?redirect=${route.fullPath}`)
} }
} }
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "audiobookshelf-client", "name": "audiobookshelf-client",
"version": "1.1.13", "version": "1.2.4",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "audiobookshelf-client", "name": "audiobookshelf-client",
"version": "1.2.4", "version": "1.2.5",
"description": "Audiobook manager and player", "description": "Audiobook manager and player",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
+22
View File
@@ -42,6 +42,11 @@
Missing Missing
</ui-btn> </ui-btn>
<!-- <ui-btn v-if="ebooks.length" 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>
Read
</ui-btn> -->
<ui-tooltip v-if="userCanUpdate" text="Edit" direction="top"> <ui-tooltip v-if="userCanUpdate" text="Edit" direction="top">
<ui-icon-btn icon="edit" class="mx-0.5" @click="editClick" /> <ui-icon-btn icon="edit" class="mx-0.5" @click="editClick" />
</ui-tooltip> </ui-tooltip>
@@ -86,6 +91,8 @@
<tables-other-files-table v-if="otherFiles.length" :audiobook-id="audiobook.id" :files="otherFiles" class="mt-6" /> <tables-other-files-table v-if="otherFiles.length" :audiobook-id="audiobook.id" :files="otherFiles" class="mt-6" />
</div> </div>
</div> </div>
<div id="area"></div>
</div> </div>
</div> </div>
</template> </template>
@@ -223,6 +230,9 @@ export default {
audioFiles() { audioFiles() {
return this.audiobook.audioFiles || [] return this.audiobook.audioFiles || []
}, },
ebooks() {
return this.audiobook.ebooks
},
description() { description() {
return this.book.description || '' return this.book.description || ''
}, },
@@ -261,6 +271,18 @@ export default {
} }
}, },
methods: { methods: {
openEbook() {
var ebook = this.ebooks[0]
console.log('Ebook', ebook)
this.$axios
.$get(`/ebook/open/${this.audiobookId}/${ebook.ino}`)
.then(() => {
console.log('opened')
})
.catch((error) => {
console.error('failed', error)
})
},
toggleRead() { toggleRead() {
var updatePayload = { var updatePayload = {
isRead: !this.isRead isRead: !this.isRead
+8 -2
View File
@@ -24,12 +24,18 @@ export default {
console.error('Search error', error) console.error('Search error', error)
return [] return []
}) })
store.commit('audiobooks/setSearchResults', searchResults)
} }
var selectedSeries = query.series ? app.$decode(query.series) : null
store.commit('audiobooks/setSelectedSeries', selectedSeries)
var libraryPage = params.id || ''
store.commit('audiobooks/setLibraryPage', libraryPage)
return { return {
id: params.id, id: libraryPage,
searchQuery, searchQuery,
searchResults, searchResults,
selectedSeries: query.series ? app.$decode(query.series) : null selectedSeries
} }
}, },
data() { data() {
-1
View File
@@ -77,7 +77,6 @@ export default {
if (token) { if (token) {
this.processing = true this.processing = true
console.log('Authorize', token)
this.$axios this.$axios
.$post('/api/authorize', null, { .$post('/api/authorize', null, {
headers: { headers: {
+29 -1
View File
@@ -10,13 +10,32 @@ export const state = () => ({
genres: [...STANDARD_GENRES], genres: [...STANDARD_GENRES],
tags: [], tags: [],
series: [], series: [],
keywordFilter: null keywordFilter: null,
selectedSeries: null,
libraryPage: null,
searchResults: []
}) })
export const getters = { export const getters = {
getAudiobook: (state) => id => { getAudiobook: (state) => id => {
return state.audiobooks.find(ab => ab.id === id) return state.audiobooks.find(ab => ab.id === id)
}, },
getEntitiesShowing: (state, getters, rootState, rootGetters) => () => {
if (!state.libraryPage) {
return getters.getFiltered()
} else if (state.libraryPage === 'search') {
return state.searchResults
} else if (state.libraryPage === 'series') {
var series = getters.getSeriesGroups()
if (state.selectedSeries) {
var _series = series.find(__series => __series.name === state.selectedSeries)
if (!_series) return []
return _series.books || []
}
return series
}
return []
},
getFiltered: (state, getters, rootState, rootGetters) => () => { getFiltered: (state, getters, rootState, rootGetters) => () => {
var filtered = state.audiobooks var filtered = state.audiobooks
var settings = rootState.user.settings || {} var settings = rootState.user.settings || {}
@@ -159,6 +178,15 @@ export const mutations = {
setKeywordFilter(state, val) { setKeywordFilter(state, val) {
state.keywordFilter = val state.keywordFilter = val
}, },
setSelectedSeries(state, val) {
state.selectedSeries = val
},
setLibraryPage(state, val) {
state.libraryPage = val
},
setSearchResults(state, val) {
state.searchResults = val
},
set(state, audiobooks) { set(state, audiobooks) {
// GENRES // GENRES
var genres = [...state.genres] var genres = [...state.genres]
+425 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "audiobookshelf", "name": "audiobookshelf",
"version": "1.1.13", "version": "1.2.4",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@@ -69,6 +69,12 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
"optional": true
},
"aborter": { "aborter": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/aborter/-/aborter-1.1.0.tgz", "resolved": "https://registry.npmjs.org/aborter/-/aborter-1.1.0.tgz",
@@ -83,6 +89,23 @@
"negotiator": "0.6.2" "negotiator": "0.6.2"
} }
}, },
"adm-zip": {
"version": "0.4.16",
"resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.16.tgz",
"integrity": "sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg=="
},
"ansi-regex": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
"optional": true
},
"aproba": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
"integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
"optional": true
},
"archiver": { "archiver": {
"version": "5.3.0", "version": "5.3.0",
"resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.0.tgz", "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.0.tgz",
@@ -138,6 +161,33 @@
"is-primitive": "^3.0.1" "is-primitive": "^3.0.1"
} }
}, },
"are-we-there-yet": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz",
"integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==",
"optional": true,
"requires": {
"delegates": "^1.0.0",
"readable-stream": "^2.0.6"
},
"dependencies": {
"readable-stream": {
"version": "2.3.7",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
"integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
"optional": true,
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
}
}
}
},
"array-back": { "array-back": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz",
@@ -285,6 +335,12 @@
"responselike": "^2.0.0" "responselike": "^2.0.0"
} }
}, },
"chownr": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
"optional": true
},
"clone-response": { "clone-response": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz",
@@ -293,6 +349,12 @@
"mimic-response": "^1.0.0" "mimic-response": "^1.0.0"
} }
}, },
"code-point-at": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
"optional": true
},
"command-line-args": { "command-line-args": {
"version": "5.2.0", "version": "5.2.0",
"resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.2.0.tgz", "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.2.0.tgz",
@@ -325,6 +387,12 @@
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
}, },
"console-control-strings": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
"integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
"optional": true
},
"content-disposition": { "content-disposition": {
"version": "0.5.3", "version": "0.5.3",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
@@ -417,11 +485,23 @@
} }
} }
}, },
"deep-extend": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
"optional": true
},
"defer-to-connect": { "defer-to-connect": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz",
"integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==" "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg=="
}, },
"delegates": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
"integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=",
"optional": true
},
"depd": { "depd": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
@@ -432,6 +512,12 @@
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
}, },
"detect-libc": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
"integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=",
"optional": true
},
"dicer": { "dicer": {
"version": "0.3.0", "version": "0.3.0",
"resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.0.tgz", "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.0.tgz",
@@ -629,11 +715,36 @@
"universalify": "^2.0.0" "universalify": "^2.0.0"
} }
}, },
"fs-minipass": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz",
"integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==",
"optional": true,
"requires": {
"minipass": "^2.6.0"
}
},
"fs.realpath": { "fs.realpath": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
}, },
"gauge": {
"version": "2.7.4",
"resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
"integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
"optional": true,
"requires": {
"aproba": "^1.0.3",
"console-control-strings": "^1.0.0",
"has-unicode": "^2.0.0",
"object-assign": "^4.1.0",
"signal-exit": "^3.0.0",
"string-width": "^1.0.1",
"strip-ansi": "^3.0.1",
"wide-align": "^1.1.0"
}
},
"get-stream": { "get-stream": {
"version": "5.2.0", "version": "5.2.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
@@ -679,6 +790,12 @@
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz",
"integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==" "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ=="
}, },
"has-unicode": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
"integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=",
"optional": true
},
"http-cache-semantics": { "http-cache-semantics": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz",
@@ -718,6 +835,15 @@
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="
}, },
"ignore-walk": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.4.tgz",
"integrity": "sha512-PY6Ii8o1jMRA1z4F2hRkH/xN59ox43DavKvD3oDpfurRlOJyAHpifIwpbdv1n4jt4ov0jSpw3kQ4GhJnpBL6WQ==",
"optional": true,
"requires": {
"minimatch": "^3.0.4"
}
},
"inflight": { "inflight": {
"version": "1.0.6", "version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
@@ -732,6 +858,12 @@
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
}, },
"ini": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
"optional": true
},
"ip": { "ip": {
"version": "1.1.5", "version": "1.1.5",
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
@@ -742,6 +874,15 @@
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="
}, },
"is-fullwidth-code-point": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
},
"is-primitive": { "is-primitive": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-3.0.1.tgz", "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-3.0.1.tgz",
@@ -965,11 +1106,79 @@
"brace-expansion": "^1.1.7" "brace-expansion": "^1.1.7"
} }
}, },
"minimist": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
"optional": true
},
"minipass": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz",
"integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==",
"optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
}
},
"minizlib": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz",
"integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==",
"optional": true,
"requires": {
"minipass": "^2.9.0"
}
},
"mkdirp": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"optional": true,
"requires": {
"minimist": "^1.2.5"
}
},
"ms": { "ms": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
}, },
"nan": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz",
"integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==",
"optional": true
},
"needle": {
"version": "2.9.1",
"resolved": "https://registry.npmjs.org/needle/-/needle-2.9.1.tgz",
"integrity": "sha512-6R9fqJ5Zcmf+uYaFgdIHmLwNldn5HbK8L5ybn7Uz+ylX/rnOsSp1AHcvQSrCaFN+qNM1wpymHqD7mVasEOlHGQ==",
"optional": true,
"requires": {
"debug": "^3.2.6",
"iconv-lite": "^0.4.4",
"sax": "^1.2.4"
},
"dependencies": {
"debug": {
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
"optional": true,
"requires": {
"ms": "^2.1.1"
}
},
"ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"optional": true
}
}
},
"negotiator": { "negotiator": {
"version": "0.6.2", "version": "0.6.2",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
@@ -991,6 +1200,34 @@
"minimatch": "^3.0.2" "minimatch": "^3.0.2"
} }
}, },
"node-pre-gyp": {
"version": "0.10.3",
"resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.10.3.tgz",
"integrity": "sha512-d1xFs+C/IPS8Id0qPTZ4bUT8wWryfR/OzzAFxweG+uLN85oPzyo2Iw6bVlLQ/JOdgNonXLCoRyqDzDWq4iw72A==",
"optional": true,
"requires": {
"detect-libc": "^1.0.2",
"mkdirp": "^0.5.1",
"needle": "^2.2.1",
"nopt": "^4.0.1",
"npm-packlist": "^1.1.6",
"npmlog": "^4.0.2",
"rc": "^1.2.7",
"rimraf": "^2.6.1",
"semver": "^5.3.0",
"tar": "^4"
}
},
"nopt": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz",
"integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==",
"optional": true,
"requires": {
"abbrev": "1",
"osenv": "^0.1.4"
}
},
"normalize-path": { "normalize-path": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
@@ -1001,6 +1238,50 @@
"resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz",
"integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==" "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A=="
}, },
"npm-bundled": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.2.tgz",
"integrity": "sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ==",
"optional": true,
"requires": {
"npm-normalize-package-bin": "^1.0.1"
}
},
"npm-normalize-package-bin": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz",
"integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==",
"optional": true
},
"npm-packlist": {
"version": "1.4.8",
"resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz",
"integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==",
"optional": true,
"requires": {
"ignore-walk": "^3.0.1",
"npm-bundled": "^1.0.1",
"npm-normalize-package-bin": "^1.0.1"
}
},
"npmlog": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
"integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
"optional": true,
"requires": {
"are-we-there-yet": "~1.1.2",
"console-control-strings": "~1.1.0",
"gauge": "~2.7.3",
"set-blocking": "~2.0.0"
}
},
"number-is-nan": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
"integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
"optional": true
},
"object-assign": { "object-assign": {
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -1022,6 +1303,28 @@
"wrappy": "1" "wrappy": "1"
} }
}, },
"os-homedir": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
"integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=",
"optional": true
},
"os-tmpdir": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
"optional": true
},
"osenv": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz",
"integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==",
"optional": true,
"requires": {
"os-homedir": "^1.0.0",
"os-tmpdir": "^1.0.0"
}
},
"p-cancelable": { "p-cancelable": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz",
@@ -1119,6 +1422,18 @@
"unpipe": "1.0.0" "unpipe": "1.0.0"
} }
}, },
"rc": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
"optional": true,
"requires": {
"deep-extend": "^0.6.0",
"ini": "~1.3.0",
"minimist": "^1.2.0",
"strip-json-comments": "~2.0.1"
}
},
"readable-stream": { "readable-stream": {
"version": "3.6.0", "version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
@@ -1155,6 +1470,15 @@
"resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
"integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=" "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs="
}, },
"rimraf": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
"optional": true,
"requires": {
"glob": "^7.1.3"
}
},
"ripstat": { "ripstat": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/ripstat/-/ripstat-1.1.1.tgz", "resolved": "https://registry.npmjs.org/ripstat/-/ripstat-1.1.1.tgz",
@@ -1197,6 +1521,11 @@
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
}, },
"sax": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
},
"semver": { "semver": {
"version": "5.7.1", "version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
@@ -1240,6 +1569,12 @@
"send": "0.17.1" "send": "0.17.1"
} }
}, },
"set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
"optional": true
},
"setprototypeof": { "setprototypeof": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
@@ -1326,6 +1661,17 @@
"resolved": "https://registry.npmjs.org/string-indexes/-/string-indexes-1.0.0.tgz", "resolved": "https://registry.npmjs.org/string-indexes/-/string-indexes-1.0.0.tgz",
"integrity": "sha512-RUlx+2YydZJNlRAvoh1siPYWj/Xfk6t1sQLkA5n1tMGRCKkRLzkRtJhHk4qRmKergEBh8R3pWhsUsDqia/bolw==" "integrity": "sha512-RUlx+2YydZJNlRAvoh1siPYWj/Xfk6t1sQLkA5n1tMGRCKkRLzkRtJhHk4qRmKergEBh8R3pWhsUsDqia/bolw=="
}, },
"string-width": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
"strip-ansi": "^3.0.0"
}
},
"string_decoder": { "string_decoder": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
@@ -1334,6 +1680,44 @@
"safe-buffer": "~5.1.0" "safe-buffer": "~5.1.0"
} }
}, },
"strip-ansi": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
},
"strip-json-comments": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
"integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
"optional": true
},
"tar": {
"version": "4.4.19",
"resolved": "https://registry.npmjs.org/tar/-/tar-4.4.19.tgz",
"integrity": "sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA==",
"optional": true,
"requires": {
"chownr": "^1.1.4",
"fs-minipass": "^1.2.7",
"minipass": "^2.9.0",
"minizlib": "^1.3.3",
"mkdirp": "^0.5.5",
"safe-buffer": "^5.2.1",
"yallist": "^3.1.1"
},
"dependencies": {
"safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"optional": true
}
}
},
"tar-stream": { "tar-stream": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
@@ -1419,6 +1803,15 @@
"isexe": "^2.0.0" "isexe": "^2.0.0"
} }
}, },
"wide-align": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
"integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
"optional": true,
"requires": {
"string-width": "^1.0.2 || 2"
}
},
"wrappy": { "wrappy": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
@@ -1434,6 +1827,26 @@
"resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz",
"integrity": "sha1-eLpyAgApxbyHuKgaPPzXS0ovweU=" "integrity": "sha1-eLpyAgApxbyHuKgaPPzXS0ovweU="
}, },
"xml2js": {
"version": "0.4.23",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
"integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==",
"requires": {
"sax": ">=0.6.0",
"xmlbuilder": "~11.0.0"
}
},
"xmlbuilder": {
"version": "11.0.1",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="
},
"yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
"optional": true
},
"zip-stream": { "zip-stream": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.0.tgz", "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.0.tgz",
@@ -1443,6 +1856,16 @@
"compress-commons": "^4.1.0", "compress-commons": "^4.1.0",
"readable-stream": "^3.6.0" "readable-stream": "^3.6.0"
} }
},
"zipfile": {
"version": "0.5.12",
"resolved": "https://registry.npmjs.org/zipfile/-/zipfile-0.5.12.tgz",
"integrity": "sha512-zA60gW+XgQBu/Q4qV3BCXNIDRald6Xi5UOPj3jWGlnkjmBHaKDwIz7kyXWV3kq7VEsQN/2t/IWjdXdKeVNm6Eg==",
"optional": true,
"requires": {
"nan": "~2.10.0",
"node-pre-gyp": "~0.10.2"
}
} }
} }
} }
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "audiobookshelf", "name": "audiobookshelf",
"version": "1.2.4", "version": "1.2.5",
"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": {
+3 -1
View File
@@ -2,7 +2,7 @@
AudioBookshelf is a self-hosted audiobook server for managing and playing your audiobooks. AudioBookshelf is a self-hosted audiobook server for managing and playing your audiobooks.
See [Install guides](https://audiobookshelf.org/install) and docs coming soon to [audiobookshelf.org](https://audiobookshelf.org) See [Install guides](https://audiobookshelf.org/install) and [documentation](https://audiobookshelf.org/docs)
Android app is in beta, try it out on the [Google Play Store](https://play.google.com/store/apps/details?id=com.audiobookshelf.app) Android app is in beta, try it out on the [Google Play Store](https://play.google.com/store/apps/details?id=com.audiobookshelf.app)
@@ -13,6 +13,8 @@ Android app is in beta, try it out on the [Google Play Store](https://play.googl
## Directory Structure ## Directory Structure
See [documentation](https://audiobookshelf.org/docs) for directory structure and naming.
Author, Series, Volume Number, Title and Publish Year can all be parsed from your folder structure. Author, Series, Volume Number, Title and Publish Year can all be parsed from your folder structure.
**Note**: Files in the root directory `/audiobooks` will be ignored, all audiobooks should be in a directory **Note**: Files in the root directory `/audiobooks` will be ignored, all audiobooks should be in a directory
+42
View File
@@ -0,0 +1,42 @@
// const express = require('express')
// const EPub = require('epub')
// const Logger = require('./Logger')
// class EbookReader {
// constructor(db, MetadataPath, AudiobookPath) {
// this.db = db
// this.MetadataPath = MetadataPath
// this.AudiobookPath = AudiobookPath
// this.router = express()
// this.init()
// }
// init() {
// this.router.get('/open/:id/:ino', this.openRequest.bind(this))
// }
// openRequest(req, res) {
// Logger.info('Open request received', req.params)
// var audiobookId = req.params.id
// var fileIno = req.params.ino
// var audiobook = this.db.audiobooks.find(ab => ab.id === audiobookId)
// if (!audiobook) {
// return res.sendStatus(404)
// }
// var ebook = audiobook.ebooks.find(eb => eb.ino === fileIno)
// if (!ebook) {
// Logger.error('Ebook file not found', fileIno)
// return res.sendStatus(404)
// }
// Logger.info('Ebook found', ebook)
// this.open(ebook.fullPath)
// res.sendStatus(200)
// }
// open(path) {
// var epub = new EPub(path)
// console.log('epub', epub)
// }
// }
// module.exports = EbookReader
+35 -39
View File
@@ -25,25 +25,6 @@ class Scanner {
return this.db.audiobooks return this.db.audiobooks
} }
async setAudiobookDataInos(audiobookData) {
for (let i = 0; i < audiobookData.length; i++) {
var abd = audiobookData[i]
var matchingAB = this.db.audiobooks.find(_ab => comparePaths(_ab.path, abd.path))
if (matchingAB) {
if (!matchingAB.ino) {
matchingAB.ino = await getIno(matchingAB.fullPath)
}
abd.ino = matchingAB.ino
} else {
abd.ino = await getIno(abd.fullPath)
if (!abd.ino) {
Logger.error('[Scanner] Invalid ino - ignoring audiobook data', abd.path)
}
}
}
return audiobookData.filter(abd => !!abd.ino)
}
async setAudioFileInos(audiobookDataAudioFiles, audiobookAudioFiles) { async setAudioFileInos(audiobookDataAudioFiles, audiobookAudioFiles) {
for (let i = 0; i < audiobookDataAudioFiles.length; i++) { for (let i = 0; i < audiobookDataAudioFiles.length; i++) {
var abdFile = audiobookDataAudioFiles[i] var abdFile = audiobookDataAudioFiles[i]
@@ -68,7 +49,6 @@ class Scanner {
Logger.debug(`[Scanner] Scanning "${audiobookData.title}" (${audiobookData.ino}) - ${!!existingAudiobook ? 'Exists' : 'New'}`) Logger.debug(`[Scanner] Scanning "${audiobookData.title}" (${audiobookData.ino}) - ${!!existingAudiobook ? 'Exists' : 'New'}`)
if (existingAudiobook) { if (existingAudiobook) {
// REMOVE: No valid audio files // REMOVE: No valid audio files
// TODO: Label as incomplete, do not actually delete // TODO: Label as incomplete, do not actually delete
if (!audiobookData.audioFiles.length) { if (!audiobookData.audioFiles.length) {
@@ -80,7 +60,10 @@ class Scanner {
return ScanResult.REMOVED return ScanResult.REMOVED
} }
audiobookData.audioFiles = await this.setAudioFileInos(audiobookData.audioFiles, existingAudiobook.audioFiles) // ino is now set for every file in scandir
audiobookData.audioFiles = audiobookData.audioFiles.filter(af => af.ino)
// audiobookData.audioFiles = await this.setAudioFileInos(audiobookData.audioFiles, existingAudiobook.audioFiles)
// Check for audio files that were removed // Check for audio files that were removed
var abdAudioFileInos = audiobookData.audioFiles.map(af => af.ino) var abdAudioFileInos = audiobookData.audioFiles.map(af => af.ino)
@@ -90,6 +73,14 @@ class Scanner {
removedAudioFiles.forEach((af) => existingAudiobook.removeAudioFile(af)) removedAudioFiles.forEach((af) => existingAudiobook.removeAudioFile(af))
} }
// Check for mismatched audio tracks - tracks with no matching audio file
var removedAudioTracks = existingAudiobook.tracks.filter(track => !abdAudioFileInos.includes(track.ino))
if (removedAudioTracks.length) {
Logger.info(`[Scanner] ${removedAudioTracks.length} tracks removed no matching audio file for audiobook "${existingAudiobook.title}"`)
removedAudioTracks.forEach((at) => existingAudiobook.removeAudioTrack(at))
}
// Check for new audio files and sync existing audio files // Check for new audio files and sync existing audio files
var newAudioFiles = [] var newAudioFiles = []
var hasUpdatedAudioFiles = false var hasUpdatedAudioFiles = false
@@ -124,7 +115,7 @@ class Scanner {
return ScanResult.REMOVED return ScanResult.REMOVED
} }
var hasUpdates = removedAudioFiles.length || newAudioFiles.length || hasUpdatedAudioFiles var hasUpdates = removedAudioFiles.length || removedAudioTracks.length || newAudioFiles.length || hasUpdatedAudioFiles
if (existingAudiobook.checkUpdateMissingParts()) { if (existingAudiobook.checkUpdateMissingParts()) {
Logger.info(`[Scanner] "${existingAudiobook.title}" missing parts updated`) Logger.info(`[Scanner] "${existingAudiobook.title}" missing parts updated`)
@@ -187,27 +178,28 @@ class Scanner {
// TODO: This temporary fix from pre-release should be removed soon, including the "fixRelativePath" and "checkUpdateInos" // TODO: This temporary fix from pre-release should be removed soon, including the "fixRelativePath" and "checkUpdateInos"
// TEMP - fix relative file paths // TEMP - fix relative file paths
// TEMP - update ino for each audiobook // TEMP - update ino for each audiobook
// if (this.audiobooks.length) { if (this.audiobooks.length) {
// for (let i = 0; i < this.audiobooks.length; i++) { for (let i = 0; i < this.audiobooks.length; i++) {
// var ab = this.audiobooks[i] var ab = this.audiobooks[i]
// var shouldUpdate = ab.fixRelativePath(this.AudiobookPath) || !ab.ino // var shouldUpdate = ab.fixRelativePath(this.AudiobookPath) || !ab.ino
// // Update ino if an audio file has the same ino as the audiobook // Update ino if inos are not set
// var shouldUpdateIno = !ab.ino || (ab.audioFiles || []).find(abf => abf.ino === ab.ino) var shouldUpdateIno = ab.hasMissingIno
// if (shouldUpdateIno) { if (shouldUpdateIno) {
// await ab.checkUpdateInos() Logger.debug(`Updating inos for ${ab.title}`)
// } var hasUpdates = await ab.checkUpdateInos()
// if (shouldUpdate) { if (hasUpdates) {
// await this.db.updateAudiobook(ab) await this.db.updateAudiobook(ab)
// } }
// } }
// } }
}
const scanStart = Date.now() const scanStart = Date.now()
var audiobookDataFound = await scanRootDir(this.AudiobookPath, this.db.serverSettings) var audiobookDataFound = await scanRootDir(this.AudiobookPath, this.db.serverSettings)
// Set ino for each ab data as a string // Remove audiobooks with no inode
audiobookDataFound = await this.setAudiobookDataInos(audiobookDataFound) audiobookDataFound = audiobookDataFound.filter(abd => abd.ino)
if (this.cancelScan) { if (this.cancelScan) {
this.cancelScan = false this.cancelScan = false
@@ -323,7 +315,11 @@ class Scanner {
async filesChanged(filepaths) { async filesChanged(filepaths) {
if (!filepaths.length) return ScanResult.NOTHING if (!filepaths.length) return ScanResult.NOTHING
var relfilepaths = filepaths.map(path => path.replace(this.AudiobookPath, '')) var relfilepaths = filepaths.map(path => path.replace(this.AudiobookPath, ''))
var fileGroupings = groupFilesIntoAudiobookPaths(relfilepaths) var fileGroupings = groupFilesIntoAudiobookPaths(relfilepaths, true)
Logger.debug(`[Scanner] fileGroupings `, filepaths, fileGroupings)
var results = [] var results = []
for (const dir in fileGroupings) { for (const dir in fileGroupings) {
+3 -1
View File
@@ -14,6 +14,7 @@ const HlsController = require('./HlsController')
const StreamManager = require('./StreamManager') const StreamManager = require('./StreamManager')
const RssFeeds = require('./RssFeeds') const RssFeeds = require('./RssFeeds')
const DownloadManager = require('./DownloadManager') const DownloadManager = require('./DownloadManager')
// const EbookReader = require('./EbookReader')
const Logger = require('./Logger') const Logger = require('./Logger')
class Server { class Server {
@@ -37,6 +38,7 @@ class Server {
this.downloadManager = new DownloadManager(this.db, this.MetadataPath, this.AudiobookPath, this.emitter.bind(this)) this.downloadManager = new DownloadManager(this.db, this.MetadataPath, this.AudiobookPath, this.emitter.bind(this))
this.apiController = new ApiController(this.MetadataPath, this.db, this.scanner, this.auth, this.streamManager, this.rssFeeds, this.downloadManager, this.emitter.bind(this), this.clientEmitter.bind(this)) this.apiController = new ApiController(this.MetadataPath, this.db, this.scanner, this.auth, this.streamManager, this.rssFeeds, this.downloadManager, this.emitter.bind(this), this.clientEmitter.bind(this))
this.hlsController = new HlsController(this.db, this.scanner, this.auth, this.streamManager, this.emitter.bind(this), this.streamManager.StreamsPath) this.hlsController = new HlsController(this.db, this.scanner, this.auth, this.streamManager, this.emitter.bind(this), this.streamManager.StreamsPath)
// this.ebookReader = new EbookReader(this.db, this.MetadataPath, this.AudiobookPath)
this.server = null this.server = null
this.io = null this.io = null
@@ -204,7 +206,7 @@ class Server {
app.use('/api', this.authMiddleware.bind(this), this.apiController.router) app.use('/api', this.authMiddleware.bind(this), this.apiController.router)
app.use('/hls', this.authMiddleware.bind(this), this.hlsController.router) app.use('/hls', this.authMiddleware.bind(this), this.hlsController.router)
// app.use('/hls', this.hlsController.router) // app.use('/ebook', this.ebookReader.router)
app.use('/feeds', this.rssFeeds.router) app.use('/feeds', this.rssFeeds.router)
app.post('/upload', this.authMiddleware.bind(this), this.handleUpload.bind(this)) app.post('/upload', this.authMiddleware.bind(this), this.handleUpload.bind(this))
+61 -4
View File
@@ -106,6 +106,14 @@ class Audiobook {
return (this.audioFiles || []).filter(af => af.invalid).map(af => ({ filename: af.filename, error: af.error || 'Unknown Error' })) return (this.audioFiles || []).filter(af => af.invalid).map(af => ({ filename: af.filename, error: af.error || 'Unknown Error' }))
} }
get ebooks() {
return this.otherFiles.filter(file => file.filetype === 'ebook')
}
get hasMissingIno() {
return !this.ino || (this.audioFiles || []).find(abf => !abf.ino) || (this.otherFiles || []).find(f => !f.ino) || (this.tracks || []).find(t => !t.ino)
}
bookToJSON() { bookToJSON() {
return this.book ? this.book.toJSON() : null return this.book ? this.book.toJSON() : null
} }
@@ -152,6 +160,7 @@ class Audiobook {
hasBookMatch: !!this.book, hasBookMatch: !!this.book,
hasMissingParts: this.missingParts ? this.missingParts.length : 0, hasMissingParts: this.missingParts ? this.missingParts.length : 0,
hasInvalidParts: this.invalidParts ? this.invalidParts.length : 0, hasInvalidParts: this.invalidParts ? this.invalidParts.length : 0,
numEbooks: this.ebooks.length,
numTracks: this.tracks.length, numTracks: this.tracks.length,
chapters: this.chapters || [], chapters: this.chapters || [],
isMissing: !!this.isMissing isMissing: !!this.isMissing
@@ -173,6 +182,7 @@ class Audiobook {
invalidParts: this.invalidParts, invalidParts: this.invalidParts,
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()),
tags: this.tags, tags: this.tags,
book: this.bookToJSON(), book: this.bookToJSON(),
tracks: this.tracksToJSON(), tracks: this.tracksToJSON(),
@@ -195,7 +205,8 @@ class Audiobook {
return false return false
} }
// Update was made to add ino values, ensure they are set // Originally files did not store the inode value
// this function checks all files and sets the inode
async checkUpdateInos() { async checkUpdateInos() {
var hasUpdates = false var hasUpdates = false
if (!this.ino) { if (!this.ino) {
@@ -204,15 +215,55 @@ class Audiobook {
} }
for (let i = 0; i < this.audioFiles.length; i++) { for (let i = 0; i < this.audioFiles.length; i++) {
var af = this.audioFiles[i] var af = this.audioFiles[i]
var at = this.tracks.find(t => t.ino === af.ino)
if (!at) {
at = this.tracks.find(t => comparePaths(t.path, af.path))
}
if (!af.ino || af.ino === this.ino) { if (!af.ino || af.ino === this.ino) {
af.ino = await getIno(af.fullPath) af.ino = await getIno(af.fullPath)
if (!af.ino) { if (!af.ino) {
Logger.error('[Audiobook] checkUpdateInos: Failed to set ino for audio file', af.fullPath) Logger.error('[Audiobook] checkUpdateInos: Failed to set ino for audio file', af.fullPath)
} else { } else {
var track = this.tracks.find(t => comparePaths(t.path, af.path)) Logger.debug(`[Audiobook] Set INO For audio file ${af.path}`)
if (track) { if (at) at.ino = af.ino
track.ino = af.ino }
hasUpdates = true
} else if (at && at.ino !== af.ino) {
at.ino = af.ino
hasUpdates = true
}
}
for (let i = 0; i < this.tracks.length; i++) {
var at = this.tracks[i]
if (!at.ino) {
Logger.debug(`[Audiobook] Track ${at.filename} still does not have ino`)
var atino = await getIno(at.fullPath)
var af = this.audioFiles.find(_af => _af.ino === atino)
if (!af) {
Logger.debug(`[Audiobook] Track ${at.filename} no matching audio file with ino ${atino}`)
af = this.audioFiles.find(_af => _af.filename === at.filename)
if (!af) {
Logger.debug(`[Audiobook] Track ${at.filename} no matching audio file with filename`)
} else {
Logger.debug(`[Audiobook] Track ${at.filename} found matching filename but mismatch ino ${atino}/${af.ino}`)
// at.ino = af.ino
// at.path = af.path
// at.fullPath = af.fullPath
// hasUpdates = true
} }
} else {
Logger.debug(`[Audiobook] Track ${at.filename} found audio file with matching ino ${at.path}/${af.path}`)
}
}
}
for (let i = 0; i < this.otherFiles.length; i++) {
var file = this.otherFiles[i]
if (!file.ino || file.ino === this.ino) {
file.ino = await getIno(file.fullPath)
if (!file.ino) {
Logger.error('[Audiobook] checkUpdateInos: Failed to set ino for other file', file.fullPath)
} else {
Logger.debug(`[Audiobook] Set INO For other file ${file.path}`)
} }
hasUpdates = true hasUpdates = true
} }
@@ -329,6 +380,11 @@ class Audiobook {
this.audioFiles = this.audioFiles.filter(f => f.ino !== audioFile.ino) this.audioFiles = this.audioFiles.filter(f => f.ino !== audioFile.ino)
} }
removeAudioTrack(track) {
this.tracks = this.tracks.filter(t => t.ino !== track.ino)
this.audioFiles = this.audioFiles.filter(f => f.ino !== track.ino)
}
checkUpdateMissingParts() { checkUpdateMissingParts() {
var currMissingParts = (this.missingParts || []).join(',') || '' var currMissingParts = (this.missingParts || []).join(',') || ''
@@ -363,6 +419,7 @@ class Audiobook {
var newOtherFilePaths = newOtherFiles.map(f => f.path) var newOtherFilePaths = newOtherFiles.map(f => f.path)
this.otherFiles = this.otherFiles.filter(f => newOtherFilePaths.includes(f.path)) this.otherFiles = this.otherFiles.filter(f => newOtherFilePaths.includes(f.path))
// TODO: Should use inode
newOtherFiles.forEach((file) => { newOtherFiles.forEach((file) => {
var existingOtherFile = this.otherFiles.find(f => f.path === file.path) var existingOtherFile = this.otherFiles.find(f => f.path === file.path)
if (!existingOtherFile) { if (!existingOtherFile) {
+1 -1
View File
@@ -85,7 +85,7 @@ function getTrackNumberFromFilename(title, author, series, publishYear, filename
async function scanAudioFiles(audiobook, newAudioFiles) { async function scanAudioFiles(audiobook, newAudioFiles) {
if (!newAudioFiles || !newAudioFiles.length) { if (!newAudioFiles || !newAudioFiles.length) {
Logger.error('[AudioFileScanner] Scan Audio Files no files', audiobook.title) Logger.error('[AudioFileScanner] Scan Audio Files no new files', audiobook.title)
return return
} }
var tracks = [] var tracks = []
+15 -5
View File
@@ -1,6 +1,7 @@
const Path = require('path') const Path = require('path')
const dir = require('node-dir') const dir = require('node-dir')
const Logger = require('../Logger') const Logger = require('../Logger')
const { getIno } = require('./index')
const AUDIO_FORMATS = ['m4b', 'mp3', 'm4a'] const AUDIO_FORMATS = ['m4b', 'mp3', 'm4a']
const INFO_FORMATS = ['nfo'] const INFO_FORMATS = ['nfo']
@@ -26,7 +27,7 @@ function isAudioFile(path) {
return AUDIO_FORMATS.includes(ext.slice(1).toLowerCase()) return AUDIO_FORMATS.includes(ext.slice(1).toLowerCase())
} }
function groupFilesIntoAudiobookPaths(paths) { function groupFilesIntoAudiobookPaths(paths, useAllFileTypes = false) {
// Step 1: Normalize path, Remove leading "/", Filter out files in root dir // Step 1: Normalize path, Remove leading "/", Filter out files in root dir
var pathsFiltered = paths.map(path => Path.normalize(path.slice(1))).filter(path => Path.parse(path).dir) var pathsFiltered = paths.map(path => Path.normalize(path.slice(1))).filter(path => Path.parse(path).dir)
@@ -37,11 +38,11 @@ function groupFilesIntoAudiobookPaths(paths) {
return pathsA - pathsB return pathsA - pathsB
}) })
// Step 2.5: Seperate audio files and other files // Step 2.5: Seperate audio files and other files (optional)
var audioFilePaths = [] var audioFilePaths = []
var otherFilePaths = [] var otherFilePaths = []
pathsFiltered.forEach(path => { pathsFiltered.forEach(path => {
if (isAudioFile(path)) audioFilePaths.push(path) if (isAudioFile(path) || useAllFileTypes) audioFilePaths.push(path)
else otherFilePaths.push(path) else otherFilePaths.push(path)
}) })
@@ -134,7 +135,12 @@ async function scanRootDir(abRootPath, serverSettings = {}) {
var audiobookData = getAudiobookDataFromDir(abRootPath, audiobookPath, parseSubtitle) var audiobookData = getAudiobookDataFromDir(abRootPath, audiobookPath, parseSubtitle)
var fileObjs = cleanFileObjects(audiobookData.fullPath, audiobookPath, audiobookGrouping[audiobookPath]) var fileObjs = cleanFileObjects(audiobookData.fullPath, audiobookPath, audiobookGrouping[audiobookPath])
for (let i = 0; i < fileObjs.length; i++) {
fileObjs[i].ino = await getIno(fileObjs[i].fullPath)
}
var audiobookIno = await getIno(audiobookData.fullPath)
audiobooks.push({ audiobooks.push({
ino: audiobookIno,
...audiobookData, ...audiobookData,
audioFiles: fileObjs.filter(f => f.filetype === 'audio'), audioFiles: fileObjs.filter(f => f.filetype === 'audio'),
otherFiles: fileObjs.filter(f => f.filetype !== 'audio') otherFiles: fileObjs.filter(f => f.filetype !== 'audio')
@@ -241,11 +247,15 @@ async function getAudiobookFileData(abRootPath, audiobookPath, serverSettings =
otherFiles: [] otherFiles: []
} }
filepaths.forEach((filepath) => { for (let i = 0; i < filepaths.length; i++) {
var filepath = filepaths[i]
var relpath = Path.normalize(filepath).replace(abRootPath, '').slice(1) var relpath = Path.normalize(filepath).replace(abRootPath, '').slice(1)
var extname = Path.extname(filepath) var extname = Path.extname(filepath)
var basename = Path.basename(filepath) var basename = Path.basename(filepath)
var ino = await getIno(filepath)
var fileObj = { var fileObj = {
ino,
filetype: getFileType(extname), filetype: getFileType(extname),
filename: basename, filename: basename,
path: relpath, path: relpath,
@@ -257,7 +267,7 @@ async function getAudiobookFileData(abRootPath, audiobookPath, serverSettings =
} else { } else {
audiobook.otherFiles.push(fileObj) audiobook.otherFiles.push(fileObj)
} }
}) }
return audiobook return audiobook
} }
module.exports.getAudiobookFileData = getAudiobookFileData module.exports.getAudiobookFileData = getAudiobookFileData