mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-06-03 01:10:38 +02:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 06554811e2 | |||
| 9935bd2ffa | |||
| 4260903bbe | |||
| 64ae8ef849 | |||
| be15f2f5a0 | |||
| 341de6a196 | |||
| 740f6966ba | |||
| 2e889ff9fe | |||
| d123abd4cd | |||
| cc84349d6d | |||
| b5e83d8866 | |||
| ca9521ac9a | |||
| 078f404fe4 | |||
| bc47dfa343 | |||
| 03f39d71e3 | |||
| 69cd6aa4d0 | |||
| e161f70710 | |||
| 3ef0173226 | |||
| 87a308f749 | |||
| f45a1c4161 | |||
| 5e15695f64 | |||
| b35997e8be |
+2
-1
@@ -10,4 +10,5 @@ npm-debug.log
|
||||
dev.js
|
||||
test/
|
||||
/client/.nuxt/
|
||||
/client/dist/
|
||||
/client/dist/
|
||||
/dist/
|
||||
+2
-1
@@ -7,4 +7,5 @@ node_modules/
|
||||
/metadata/
|
||||
test/
|
||||
/client/.nuxt/
|
||||
/client/dist/
|
||||
/client/dist/
|
||||
/dist/
|
||||
@@ -0,0 +1,8 @@
|
||||
Package: audiobookshelf
|
||||
Version: 1.2.1
|
||||
Section: base
|
||||
Priority: optional
|
||||
Architecture: amd64
|
||||
Depends:
|
||||
Maintainer: advplyr
|
||||
Description: Self-hosted audiobook server for managing and playing audiobooks
|
||||
@@ -0,0 +1,85 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
set -o pipefail
|
||||
|
||||
declare -r init_type='auto'
|
||||
declare -ri no_rebuild='0'
|
||||
|
||||
add_user() {
|
||||
: "${1:?'User was not defined'}"
|
||||
declare -r user="$1"
|
||||
declare -r uid="$2"
|
||||
|
||||
if [ -z "$uid" ]; then
|
||||
declare -r uid_flags=""
|
||||
else
|
||||
declare -r uid_flags="--uid $uid"
|
||||
fi
|
||||
|
||||
declare -r group="${3:-$user}"
|
||||
declare -r descr="${4:-No description}"
|
||||
declare -r shell="${5:-/bin/false}"
|
||||
|
||||
if ! getent passwd | grep -q "^$user:"; then
|
||||
echo "Creating system user: $user in $group with $descr and shell $shell"
|
||||
useradd $uid_flags --gid $group --no-create-home --system --shell $shell -c "$descr" $user
|
||||
fi
|
||||
}
|
||||
|
||||
add_group() {
|
||||
: "${1:?'Group was not defined'}"
|
||||
declare -r group="$1"
|
||||
declare -r gid="$2"
|
||||
|
||||
if [ -z "$gid" ]; then
|
||||
declare -r gid_flags=""
|
||||
else
|
||||
declare -r gid_flags="--gid $gid"
|
||||
fi
|
||||
|
||||
if ! getent group | grep -q "^$group:" ; then
|
||||
echo "Creating system group: $group"
|
||||
groupadd $gid_flags --system $group
|
||||
fi
|
||||
}
|
||||
|
||||
start_service () {
|
||||
: "${1:?'Service name was not defined'}"
|
||||
declare -r service_name="$1"
|
||||
|
||||
if hash systemctl 2> /dev/null; then
|
||||
if [[ "$init_type" == 'auto' || "$init_type" == 'systemd' ]]; then
|
||||
{
|
||||
systemctl enable "$service_name.service" && \
|
||||
systemctl start "$service_name.service"
|
||||
} || echo "$service_name could not be registered or started"
|
||||
fi
|
||||
elif hash service 2> /dev/null; then
|
||||
if [[ "$init_type" == 'auto' || "$init_type" == 'upstart' || "$init_type" == 'sysv' ]]; then
|
||||
service "$service_name" start || echo "$service_name could not be registered or started"
|
||||
fi
|
||||
elif hash start 2> /dev/null; then
|
||||
if [[ "$init_type" == 'auto' || "$init_type" == 'upstart' ]]; then
|
||||
start "$service_name" || echo "$service_name could not be registered or started"
|
||||
fi
|
||||
elif hash update-rc.d 2> /dev/null; then
|
||||
if [[ "$init_type" == 'auto' || "$init_type" == 'sysv' ]]; then
|
||||
{
|
||||
update-rc.d "$service_name" defaults && \
|
||||
"/etc/init.d/$service_name" start
|
||||
} || echo "$service_name could not be registered or started"
|
||||
fi
|
||||
else
|
||||
echo 'Your system does not appear to use systemd, Upstart, or System V, so the service could not be started'
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
add_group 'audiobookshelf' ''
|
||||
add_user 'audiobookshelf' '' 'audiobookshelf' 'audiobookshelf user-daemon' '/bin/false'
|
||||
|
||||
mkdir -p '/var/log/audiobookshelf'
|
||||
chown -R 'audiobookshelf:audiobookshelf' '/var/log/audiobookshelf'
|
||||
chown -R 'audiobookshelf:audiobookshelf' '/usr/share/audiobookshelf'
|
||||
|
||||
start_service 'audiobookshelf'
|
||||
@@ -0,0 +1,99 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
set -o pipefail
|
||||
|
||||
DEFAULT_AUDIOBOOK_PATH="/usr/share/audiobookshelf/audiobooks"
|
||||
DEFAULT_DATA_PATH="/usr/share/audiobookshelf"
|
||||
DEFAULT_PORT=7331
|
||||
|
||||
CONFIG_PATH="/etc/default/audiobookshelf"
|
||||
|
||||
install_ffmpeg() {
|
||||
echo "Starting FFMPEG Install"
|
||||
|
||||
WGET="wget https://johnvansickle.com/ffmpeg/builds/ffmpeg-git-amd64-static.tar.xz"
|
||||
TARGET_DIR="/usr/lib/audiobookshelf-ffmpeg/"
|
||||
|
||||
if ! cd "$TARGET_DIR"; then
|
||||
echo "WARNING: can't access working directory ($TARGET_DIR) creating it" >&2
|
||||
mkdir "$TARGET_DIR"
|
||||
cd "$TARGET_DIR"
|
||||
fi
|
||||
|
||||
$WGET
|
||||
tar xvf ffmpeg-git-amd64-static.tar.xz --strip-components=1
|
||||
rm ffmpeg-git-amd64-static.tar.xz
|
||||
|
||||
chown -R 'audiobookshelf:audiobookshelf' "$TARGET_DIR"
|
||||
|
||||
echo "Good to go on Ffmpeg... hopefully"
|
||||
}
|
||||
|
||||
should_build_config() {
|
||||
if [ -f "$CONFIG_PATH" ]; then
|
||||
echo "You already have a config file. Do you want to use it?"
|
||||
|
||||
options=("Yes" "No")
|
||||
select yn in "${options[@]}"
|
||||
do
|
||||
case $yn in
|
||||
"Yes")
|
||||
false; return
|
||||
;;
|
||||
"No")
|
||||
true; return
|
||||
;;
|
||||
esac
|
||||
done
|
||||
else
|
||||
echo "No existing config found in $CONFIG_PATH"
|
||||
true; return
|
||||
fi
|
||||
}
|
||||
|
||||
setup_config() {
|
||||
if should_build_config; then
|
||||
echo "Okay, let's setup a new config."
|
||||
|
||||
AUDIOBOOK_PATH=""
|
||||
read -p "
|
||||
Enter path for your audiobooks [Default: $DEFAULT_AUDIOBOOK_PATH]:" AUDIOBOOK_PATH
|
||||
|
||||
if [[ -z "$AUDIOBOOK_PATH" ]]; then
|
||||
AUDIOBOOK_PATH="$DEFAULT_AUDIOBOOK_PATH"
|
||||
fi
|
||||
|
||||
DATA_PATH=""
|
||||
read -p "
|
||||
Enter path for data files, i.e. streams, downloads, database [Default: $DEFAULT_DATA_PATH]:" DATA_PATH
|
||||
|
||||
if [[ -z "$DATA_PATH" ]]; then
|
||||
DATA_PATH="$DEFAULT_DATA_PATH"
|
||||
fi
|
||||
|
||||
PORT=""
|
||||
read -p "
|
||||
Port for the web ui [Default: $DEFAULT_PORT]:" PORT
|
||||
|
||||
if [[ -z "$PORT" ]]; then
|
||||
PORT="$DEFAULT_PORT"
|
||||
fi
|
||||
|
||||
config_text="AUDIOBOOK_PATH=$AUDIOBOOK_PATH
|
||||
METADATA_PATH=$DATA_PATH/metadata
|
||||
CONFIG_PATH=$DATA_PATH/config
|
||||
FFMPEG_PATH=/usr/lib/audiobookshelf-ffmpeg/ffmpeg
|
||||
PORT=$PORT"
|
||||
|
||||
echo "$config_text"
|
||||
|
||||
echo "$config_text" > /etc/default/audiobookshelf;
|
||||
|
||||
echo "Config created"
|
||||
|
||||
fi
|
||||
}
|
||||
|
||||
setup_config
|
||||
|
||||
install_ffmpeg
|
||||
@@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
set -o pipefail
|
||||
|
||||
declare -r init_type='auto'
|
||||
declare -r service_name='audiobookshelf'
|
||||
|
||||
if [[ "$init_type" == 'auto' || "$init_type" == 'systemd' || "$init_type" == 'upstart' || "$init_type" == 'sysv' ]]; then
|
||||
if hash systemctl 2> /dev/null; then
|
||||
systemctl disable "$service_name.service" && \
|
||||
systemctl stop "$service_name.service" || \
|
||||
echo "$service_name wasn't even running!"
|
||||
elif hash service 2> /dev/null; then
|
||||
service "$service_name" stop || echo "$service_name wasn't even running!"
|
||||
elif hash stop 2> /dev/null; then
|
||||
stop "$service_name" || echo "$service_name wasn't even running!"
|
||||
elif hash update-rc.d 2> /dev/null; then
|
||||
{
|
||||
update-rc.d "$service_name" remove && \
|
||||
"/etc/init.d/$service_name" stop
|
||||
} || "$service_name wasn't even running!"
|
||||
else
|
||||
echo "Your system does not appear to use upstart, systemd or sysv, so $service_name could not be stopped"
|
||||
echo 'Unless these systems were removed since install, no processes have been left running'
|
||||
fi
|
||||
fi
|
||||
@@ -0,0 +1,16 @@
|
||||
[Unit]
|
||||
Description=Self-hosted audiobook server for managing and playing audiobooks
|
||||
Requires=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
EnvironmentFile=/etc/default/audiobookshelf
|
||||
WorkingDirectory=/usr/share/audiobookshelf
|
||||
ExecStart=/usr/share/audiobookshelf/audiobookshelf
|
||||
ExecReload=/bin/kill -HUP $MAINPID
|
||||
Restart=always
|
||||
User=audiobookshelf
|
||||
PermissionsStartOnly=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Executable
+57
@@ -0,0 +1,57 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
set -o pipefail
|
||||
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
|
||||
|
||||
cd "$SCRIPT_DIR/.."
|
||||
|
||||
# Get package version without double quotes
|
||||
VERSION="$( eval echo $( jq '.version' package.json) )"
|
||||
DESCRIPTION="$( eval echo $( jq '.description' package.json) )"
|
||||
OUTPUT_FILE="audiobookshelf_${VERSION}_amd64.deb"
|
||||
|
||||
echo ">>> Building Client"
|
||||
echo "--------------------"
|
||||
|
||||
cd client
|
||||
rm -rf node_modules
|
||||
npm ci --unsafe-perm=true --allow-root
|
||||
npm run generate
|
||||
cd ..
|
||||
|
||||
echo ">>> Building Server"
|
||||
echo "--------------------"
|
||||
|
||||
rm -rf node_modules
|
||||
npm ci --unsafe-perm=true --allow-root
|
||||
|
||||
echo ">>> Packaging"
|
||||
echo "--------------------"
|
||||
|
||||
# Create debian control file
|
||||
|
||||
mkdir -p dist
|
||||
cp -R build/debian dist/debian
|
||||
chmod -R 775 dist/debian
|
||||
|
||||
controlfile="Package: audiobookshelf
|
||||
Version: $VERSION
|
||||
Section: base
|
||||
Priority: optional
|
||||
Architecture: amd64
|
||||
Depends:
|
||||
Maintainer: advplyr
|
||||
Description: $DESCRIPTION"
|
||||
|
||||
echo "$controlfile" > dist/debian/DEBIAN/control;
|
||||
|
||||
# Package debian
|
||||
pkg -t node12-linux-x64 -o dist/debian/usr/share/audiobookshelf/audiobookshelf .
|
||||
|
||||
fakeroot dpkg-deb --build dist/debian
|
||||
|
||||
mv dist/debian.deb "dist/$OUTPUT_FILE"
|
||||
chmod +x "dist/$OUTPUT_FILE"
|
||||
|
||||
echo "Finished! Filename: $OUTPUT_FILE"
|
||||
@@ -108,5 +108,5 @@
|
||||
}
|
||||
|
||||
.box-shadow-side {
|
||||
box-shadow: 4px 0px 4px #11111166;
|
||||
box-shadow: 5px 0px 5px #11111166;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div id="bookshelf" ref="wrapper" class="w-full h-full overflow-y-auto relative">
|
||||
<div id="bookshelf" ref="wrapper" class="w-full h-full overflow-y-scroll relative">
|
||||
<!-- Cover size widget -->
|
||||
<div v-show="!isSelectionMode" class="fixed bottom-2 right-4 z-20">
|
||||
<div class="rounded-full py-1 bg-primary px-2 border border-black-100 text-center flex items-center box-shadow-md" @mousedown.prevent @mouseup.prevent>
|
||||
@@ -23,15 +23,17 @@
|
||||
<template v-for="entity in shelf">
|
||||
<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-card v-else :key="entity.id" :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>
|
||||
</div>
|
||||
<div class="bookshelfDivider h-4 w-full absolute bottom-0 left-0 right-0 z-10" />
|
||||
</div>
|
||||
</template>
|
||||
<div v-show="!shelves.length" class="w-full py-16 text-center text-xl">
|
||||
<div class="py-4">No {{ showGroups ? 'Series' : 'Audiobooks' }}</div>
|
||||
<div v-if="page === 'search'" class="py-4 mb-6"><p class="text-2xl">No Results</p></div>
|
||||
<div v-else class="py-4">No {{ showGroups ? 'Series' : 'Audiobooks' }}</div>
|
||||
<ui-btn v-if="!showGroups && (filterBy !== 'all' || keywordFilter)" @click="clearFilter">Clear Filter</ui-btn>
|
||||
<ui-btn v-else-if="page === 'search'" to="/library">Back to Library</ui-btn>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -41,7 +43,12 @@
|
||||
export default {
|
||||
props: {
|
||||
page: String,
|
||||
selectedSeries: String
|
||||
selectedSeries: String,
|
||||
searchResults: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
searchQuery: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -51,7 +58,8 @@ export default {
|
||||
selectedSizeIndex: 3,
|
||||
rowPaddingX: 40,
|
||||
keywordFilterTimeout: null,
|
||||
scannerParseSubtitle: false
|
||||
scannerParseSubtitle: false,
|
||||
wrapperClientWidth: 0
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -62,6 +70,11 @@ export default {
|
||||
this.$nextTick(() => {
|
||||
this.setBookshelfEntities()
|
||||
})
|
||||
},
|
||||
searchResults() {
|
||||
this.$nextTick(() => {
|
||||
this.setBookshelfEntities()
|
||||
})
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -96,11 +109,13 @@ export default {
|
||||
return this.$store.getters['user/getUserSetting']('filterBy')
|
||||
},
|
||||
showGroups() {
|
||||
return this.page !== '' && !this.selectedSeries
|
||||
return this.page !== '' && this.page !== 'search' && !this.selectedSeries
|
||||
},
|
||||
entities() {
|
||||
if (this.page === '') {
|
||||
return this.$store.getters['audiobooks/getFilteredAndSorted']()
|
||||
} else if (this.page === 'search') {
|
||||
return this.searchResults || []
|
||||
} else {
|
||||
var seriesGroups = this.$store.getters['audiobooks/getSeriesGroups']()
|
||||
if (this.selectedSeries) {
|
||||
@@ -145,7 +160,8 @@ export default {
|
||||
this.$store.dispatch('user/updateUserSettings', { bookshelfCoverSize: this.bookCoverWidth })
|
||||
},
|
||||
setBookshelfEntities() {
|
||||
var width = Math.max(0, this.$refs.wrapper.clientWidth - this.rowPaddingX * 2)
|
||||
this.wrapperClientWidth = this.$refs.wrapper.clientWidth
|
||||
var width = Math.max(0, this.wrapperClientWidth - this.rowPaddingX * 2)
|
||||
var booksPerRow = Math.floor(width / this.bookWidth)
|
||||
|
||||
var entities = this.entities
|
||||
@@ -168,6 +184,8 @@ export default {
|
||||
this.shelves = groups
|
||||
},
|
||||
async init() {
|
||||
this.wrapperClientWidth = this.$refs.wrapper ? this.$refs.wrapper.clientWidth : 0
|
||||
|
||||
var bookshelfCoverSize = this.$store.getters['user/getUserSetting']('bookshelfCoverSize')
|
||||
var sizeIndex = this.availableSizes.findIndex((s) => s === bookshelfCoverSize)
|
||||
if (!isNaN(sizeIndex)) this.selectedSizeIndex = sizeIndex
|
||||
@@ -178,9 +196,7 @@ export default {
|
||||
}
|
||||
},
|
||||
resize() {
|
||||
this.$nextTick(() => {
|
||||
this.setBookshelfEntities()
|
||||
})
|
||||
this.$nextTick(this.setBookshelfEntities)
|
||||
},
|
||||
audiobooksUpdated() {
|
||||
console.log('[AudioBookshelf] Audiobooks Updated')
|
||||
@@ -202,10 +218,18 @@ export default {
|
||||
this.$root.socket.emit('scan')
|
||||
}
|
||||
},
|
||||
updated() {
|
||||
if (this.$refs.wrapper) {
|
||||
if (this.wrapperClientWidth !== this.$refs.wrapper.clientWidth) {
|
||||
this.$nextTick(this.setBookshelfEntities)
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
window.addEventListener('resize', this.resize)
|
||||
this.$store.commit('audiobooks/addListener', { id: 'bookshelf', meth: this.audiobooksUpdated })
|
||||
this.$store.commit('user/addSettingsListener', { id: 'bookshelf', meth: this.settingsUpdated })
|
||||
|
||||
this.init()
|
||||
},
|
||||
beforeDestroy() {
|
||||
|
||||
@@ -1,21 +1,32 @@
|
||||
<template>
|
||||
<div class="w-full h-10 relative">
|
||||
<div id="toolbar" class="absolute top-0 left-0 w-full h-full z-20 flex items-center px-8">
|
||||
<p v-if="!selectedSeries" class="font-book">{{ numShowing }} {{ entityName }}</p>
|
||||
<div v-else class="flex items-center">
|
||||
<div @click="seriesBackArrow" class="rounded-full h-10 w-10 flex items-center justify-center hover:bg-white hover:bg-opacity-10 cursor-pointer">
|
||||
<template v-if="page !== 'search'">
|
||||
<p v-if="!selectedSeries" class="font-book">{{ numShowing }} {{ entityName }}</p>
|
||||
<div v-else class="flex items-center">
|
||||
<div @click="seriesBackArrow" class="rounded-full h-10 w-10 flex items-center justify-center hover:bg-white hover:bg-opacity-10 cursor-pointer">
|
||||
<span class="material-icons text-3xl text-white">west</span>
|
||||
</div>
|
||||
<!-- <span class="material-icons text-2xl cursor-pointer" @click="seriesBackArrow">west</span> -->
|
||||
<p class="pl-4 font-book text-lg">
|
||||
{{ selectedSeries }} <span class="ml-3 font-mono text-lg bg-black bg-opacity-30 rounded-lg px-1 py-0.5">{{ numShowing }}</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex-grow" />
|
||||
|
||||
<ui-text-input v-show="!selectedSeries" v-model="_keywordFilter" placeholder="Keyword Filter" :padding-y="1.5" class="text-xs w-40" />
|
||||
<controls-filter-select v-show="showSortFilters" v-model="settings.filterBy" class="w-48 h-7.5 ml-4" @change="updateFilter" />
|
||||
<controls-order-select v-show="showSortFilters" v-model="settings.orderBy" :descending.sync="settings.orderDesc" class="w-48 h-7.5 ml-4" @change="updateOrder" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<div @click="searchBackArrow" class="rounded-full h-10 w-10 flex items-center justify-center hover:bg-white hover:bg-opacity-10 cursor-pointer">
|
||||
<span class="material-icons text-3xl text-white">west</span>
|
||||
</div>
|
||||
<!-- <span class="material-icons text-2xl cursor-pointer" @click="seriesBackArrow">west</span> -->
|
||||
<p class="pl-4 font-book text-lg">
|
||||
{{ selectedSeries }} <span class="ml-3 font-mono text-lg bg-black bg-opacity-30 rounded-lg px-1 py-0.5">{{ numShowing }}</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex-grow" />
|
||||
|
||||
<ui-text-input v-show="showSortFilters" v-model="_keywordFilter" placeholder="Keyword Filter" :padding-y="1.5" class="text-xs w-40" />
|
||||
<controls-filter-select v-show="showSortFilters" v-model="settings.filterBy" class="w-48 h-7.5 ml-4" @change="updateFilter" />
|
||||
<controls-order-select v-show="showSortFilters" v-model="settings.orderBy" :descending.sync="settings.orderDesc" class="w-48 h-7.5 ml-4" @change="updateOrder" />
|
||||
<!-- <p class="font-book pl-4">{{ numShowing }} showing</p> -->
|
||||
<div class="flex-grow" />
|
||||
<p>Search results for "{{ searchQuery }}"</p>
|
||||
<div class="flex-grow" />
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -24,7 +35,12 @@
|
||||
export default {
|
||||
props: {
|
||||
page: String,
|
||||
selectedSeries: String
|
||||
selectedSeries: String,
|
||||
searchResults: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
searchQuery: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -39,6 +55,8 @@ export default {
|
||||
numShowing() {
|
||||
if (this.page === '') {
|
||||
return this.$store.getters['audiobooks/getFiltered']().length
|
||||
} else if (this.page === 'search') {
|
||||
return (this.searchResults || []).length
|
||||
} else {
|
||||
var groups = this.$store.getters['audiobooks/getSeriesGroups']()
|
||||
if (this.selectedSeries) {
|
||||
@@ -65,6 +83,9 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
searchBackArrow() {
|
||||
this.$router.replace('/library')
|
||||
},
|
||||
seriesBackArrow() {
|
||||
this.$router.replace('/library/series')
|
||||
this.$emit('update:selectedSeries', null)
|
||||
@@ -100,6 +121,6 @@ export default {
|
||||
|
||||
<style>
|
||||
#toolbar {
|
||||
box-shadow: 0px 8px 8px #111111aa;
|
||||
box-shadow: 0px 8px 6px #111111aa;
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<div class="w-20 border-r border-primary bg-bg h-full relative box-shadow-side z-40" style="min-width: 80px">
|
||||
<div class="w-20 bg-bg h-full relative box-shadow-side z-20" style="min-width: 80px">
|
||||
<div class="absolute top-0 -right-4 w-4 bg-bg h-10 pointer-events-none" />
|
||||
<nuxt-link to="/library" class="w-full h-20 flex flex-col items-center justify-center text-white border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="paramId === '' ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" />
|
||||
|
||||
@@ -29,7 +29,11 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-show="!isSelectionMode" class="absolute bottom-0 left-0 h-1 shadow-sm" :class="userIsRead ? 'bg-success' : 'bg-yellow-400'" :style="{ width: width * userProgressPercent + 'px' }"></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` }">
|
||||
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">#{{ volumeNumber }}</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>
|
||||
|
||||
<ui-tooltip v-if="showError" :text="errorText" class="absolute bottom-4 left-0">
|
||||
<div :style="{ height: 1.5 * sizeMultiplier + 'rem', width: 2.5 * sizeMultiplier + 'rem' }" class="bg-error rounded-r-full shadow-md flex items-center justify-end border-r border-b border-red-300">
|
||||
@@ -56,7 +60,8 @@ export default {
|
||||
width: {
|
||||
type: Number,
|
||||
default: 120
|
||||
}
|
||||
},
|
||||
showVolumeNumber: Boolean
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
@@ -5,13 +5,16 @@
|
||||
<div class="w-full relative" :class="isHovering ? 'bg-black-400' : 'bg-primary'" :style="{ height: height + 'px', width: height + 'px' }">
|
||||
<cards-group-cover ref="groupcover" :name="groupName" :book-items="bookItems" :width="height" :height="height" />
|
||||
|
||||
<div v-if="hasValidCovers" class="bg-black bg-opacity-60 absolute top-0 left-0 w-full h-full flex items-center justify-center text-center transition-opacity" :class="isHovering ? '' : 'opacity-0'">
|
||||
<p class="truncate font-book" :style="{ fontSize: sizeMultiplier + 'rem' }">{{ groupName }}</p>
|
||||
<div v-if="hasValidCovers" class="bg-black bg-opacity-60 absolute top-0 left-0 w-full h-full flex items-center justify-center text-center transition-opacity" :class="isHovering ? '' : 'opacity-0'" :style="{ padding: `${sizeMultiplier}rem` }">
|
||||
<p class="font-book" :style="{ fontSize: sizeMultiplier + 'rem' }">{{ groupName }}</p>
|
||||
</div>
|
||||
|
||||
<div class="absolute top-2 right-2 w-7 h-7 rounded-lg bg-black bg-opacity-90 text-gray-300 box-shadow-book flex items-center justify-center border border-white border-opacity-25 pointer-events-none">
|
||||
<p class="font-book text-xl">{{ bookItems.length }}</p>
|
||||
</div>
|
||||
<div class="absolute bottom-0 left-0 w-full h-1 flex flex-nowrap">
|
||||
<div v-for="userProgress in userProgressItems" :key="userProgress.audiobookId" class="h-full w-full" :class="userProgress.isRead ? 'bg-success' : userProgress.progress > 0 ? 'bg-yellow-400' : ''" />
|
||||
</div>
|
||||
</div>
|
||||
</nuxt-link>
|
||||
</div>
|
||||
@@ -60,6 +63,15 @@ export default {
|
||||
bookItems() {
|
||||
return this._group.books || []
|
||||
},
|
||||
userAudiobooks() {
|
||||
return Object.values(this.$store.state.user.user ? this.$store.state.user.user.audiobooks || {} : {})
|
||||
},
|
||||
userProgressItems() {
|
||||
return this.bookItems.map((item) => {
|
||||
var userAudiobook = this.userAudiobooks.find((ab) => ab.audiobookId === item.id)
|
||||
return userAudiobook || {}
|
||||
})
|
||||
},
|
||||
groupName() {
|
||||
return this._group.name || 'No Name'
|
||||
},
|
||||
@@ -81,7 +93,6 @@ export default {
|
||||
clickCard() {
|
||||
this.$emit('click', this.group)
|
||||
}
|
||||
},
|
||||
mounted() {}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div ref="wrapper" :style="{ height: height + 'px', width: width + 'px' }" class="relative">
|
||||
<div v-if="noValidCovers" class="absolute top-0 left-0 w-full h-full flex items-center justify-center box-shadow-book">
|
||||
<div v-if="noValidCovers" class="absolute top-0 left-0 w-full h-full flex items-center justify-center box-shadow-book" :style="{ padding: `${sizeMultiplier}rem` }">
|
||||
<p :style="{ fontSize: sizeMultiplier + 'rem' }">{{ name }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<template>
|
||||
<div class="w-64 ml-8 relative">
|
||||
<ui-text-input v-model="search" placeholder="Search.." @input="inputUpdate" @focus="focussed" @blur="blurred" class="w-full h-8 text-sm" />
|
||||
<form @submit.prevent="submitSearch">
|
||||
<ui-text-input ref="input" v-model="search" placeholder="Search.." @input="inputUpdate" @focus="focussed" @blur="blurred" class="w-full h-8 text-sm" />
|
||||
</form>
|
||||
<div class="absolute top-0 right-0 bottom-0 h-full flex items-center px-2 text-gray-400 cursor-pointer" @click="clickClear">
|
||||
<span v-if="!search" class="material-icons" style="font-size: 1.2rem">search</span>
|
||||
<span v-else class="material-icons" style="font-size: 1.2rem">close</span>
|
||||
@@ -51,6 +53,19 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
submitSearch() {
|
||||
if (!this.search) return
|
||||
this.$router.push(`/library/search?query=${this.search}`)
|
||||
|
||||
this.search = null
|
||||
this.items = []
|
||||
this.showMenu = false
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.input) {
|
||||
this.$refs.input.blur()
|
||||
}
|
||||
})
|
||||
},
|
||||
focussed() {
|
||||
this.isFocused = true
|
||||
this.showMenu = true
|
||||
@@ -73,6 +88,10 @@ export default {
|
||||
return []
|
||||
})
|
||||
this.isFetching = false
|
||||
if (!this.showMenu) {
|
||||
return
|
||||
}
|
||||
|
||||
this.items = results.map((res) => {
|
||||
return {
|
||||
id: res.id,
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
<template>
|
||||
<modals-modal v-model="show" :width="500" :height="'unset'">
|
||||
<div class="w-full rounded-lg bg-primary box-shadow-md overflow-y-auto overflow-x-hidden" style="max-height: 500px">
|
||||
<div ref="container" class="w-full rounded-lg bg-primary box-shadow-md overflow-y-auto overflow-x-hidden" style="max-height: 80vh">
|
||||
<template v-for="chap in chapters">
|
||||
<div :key="chap.id" class="flex items-center px-6 py-3 justify-start cursor-pointer hover:bg-bg relative" :class="chap.id === currentChapterId ? 'bg-bg bg-opacity-80' : 'bg-opacity-20'" @click="clickChapter(chap)">
|
||||
<div :key="chap.id" :id="`chapter-row-${chap.id}`" class="flex items-center px-6 py-3 justify-start cursor-pointer hover:bg-bg relative" :class="chap.id === currentChapterId ? 'bg-bg bg-opacity-80' : 'bg-opacity-20'" @click="clickChapter(chap)">
|
||||
{{ chap.title }}
|
||||
<span class="flex-grow" />
|
||||
<span class="font-mono text-sm text-gray-300">{{ $secondsToTimestamp(chap.start) }}</span>
|
||||
|
||||
<div v-show="chap.id === currentChapterId" class="w-0.5 h-full absolute top-0 left-0 bg-yellow-400" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
@@ -28,6 +30,11 @@ export default {
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
watch: {
|
||||
value(newVal) {
|
||||
this.$nextTick(this.scrollToChapter)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
show: {
|
||||
get() {
|
||||
@@ -44,8 +51,20 @@ export default {
|
||||
methods: {
|
||||
clickChapter(chap) {
|
||||
this.$emit('select', chap)
|
||||
},
|
||||
scrollToChapter() {
|
||||
if (!this.currentChapterId) return
|
||||
|
||||
var container = this.$refs.container
|
||||
if (container) {
|
||||
var currChapterEl = document.getElementById(`chapter-row-${this.currentChapterId}`)
|
||||
if (currChapterEl) {
|
||||
var offsetTop = currChapterEl.offsetTop
|
||||
var containerHeight = container.clientHeight
|
||||
container.scrollTo({ top: offsetTop - containerHeight / 2 })
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<input v-model="inputValue" :type="type" :readonly="readonly" :disabled="disabled" :placeholder="placeholder" class="rounded bg-primary text-gray-200 focus:border-gray-500 focus:outline-none border border-gray-600" :class="classList" @keyup="keyup" @change="change" @focus="focused" @blur="blurred" />
|
||||
<input ref="input" v-model="inputValue" :type="type" :readonly="readonly" :disabled="disabled" :placeholder="placeholder" class="rounded bg-primary text-gray-200 focus:border-gray-500 focus:outline-none border border-gray-600" :class="classList" @keyup="keyup" @change="change" @focus="focused" @blur="blurred" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -53,6 +53,9 @@ export default {
|
||||
},
|
||||
keyup(e) {
|
||||
this.$emit('keyup', e)
|
||||
},
|
||||
blur() {
|
||||
if (this.$refs.input) this.$refs.input.blur()
|
||||
}
|
||||
},
|
||||
mounted() {}
|
||||
|
||||
@@ -26,6 +26,9 @@ export default {
|
||||
if (this.$store.state.selectedAudiobooks) {
|
||||
this.$store.commit('setSelectedAudiobooks', [])
|
||||
}
|
||||
if (this.$store.state.audiobooks.keywordFilter) {
|
||||
this.$store.commit('audiobooks/setKeywordFilter', '')
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "audiobookshelf-client",
|
||||
"version": "1.2.0",
|
||||
"version": "1.2.4",
|
||||
"description": "Audiobook manager and player",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
<div class="flex h-full">
|
||||
<app-side-rail />
|
||||
<div class="flex-grow">
|
||||
<app-book-shelf-toolbar :page="id || ''" :selected-series.sync="selectedSeries" />
|
||||
<app-book-shelf :page="id || ''" :selected-series.sync="selectedSeries" />
|
||||
<app-book-shelf-toolbar :page="id || ''" :search-results="searchResults" :search-query="searchQuery" :selected-series.sync="selectedSeries" />
|
||||
<app-book-shelf :page="id || ''" :search-results="searchResults" :search-query="searchQuery" :selected-series.sync="selectedSeries" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -12,24 +12,53 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
asyncData({ params, query, store, app }) {
|
||||
async asyncData({ params, query, store, app }) {
|
||||
if (query.filter) {
|
||||
store.dispatch('user/updateUserSettings', { filterBy: query.filter })
|
||||
}
|
||||
var searchResults = []
|
||||
var searchQuery = null
|
||||
if (params.id === 'search' && query.query) {
|
||||
searchQuery = query.query
|
||||
searchResults = await app.$axios.$get(`/api/audiobooks?q=${query.query}`).catch((error) => {
|
||||
console.error('Search error', error)
|
||||
return []
|
||||
})
|
||||
}
|
||||
return {
|
||||
id: params.id,
|
||||
searchQuery,
|
||||
searchResults,
|
||||
selectedSeries: query.series ? app.$decode(query.series) : null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
watch: {
|
||||
'$route.query'(newVal) {
|
||||
if (this.id === 'search' && this.$route.query.query) {
|
||||
if (this.$route.query.query !== this.searchQuery) {
|
||||
this.newQuery()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
streamAudiobook() {
|
||||
return this.$store.state.streamAudiobook
|
||||
}
|
||||
},
|
||||
methods: {},
|
||||
methods: {
|
||||
async newQuery() {
|
||||
var query = this.$route.query.query
|
||||
this.searchResults = await this.$axios.$get(`/api/audiobooks?q=${query}`).catch((error) => {
|
||||
console.error('Search error', error)
|
||||
return []
|
||||
})
|
||||
this.searchQuery = query
|
||||
}
|
||||
},
|
||||
mounted() {}
|
||||
}
|
||||
</script>
|
||||
@@ -5,7 +5,7 @@ function parseSemver(ver) {
|
||||
if (!ver) return null
|
||||
var groups = ver.match(/^v((([0-9]+)\.([0-9]+)\.([0-9]+)(?:-([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)$/)
|
||||
if (groups && groups.length > 6) {
|
||||
var total = Number(groups[3]) * 100 + Number(groups[4]) * 10 + Number(groups[5])
|
||||
var total = Number(groups[3]) * 10000 + Number(groups[4]) * 100 + Number(groups[5])
|
||||
if (isNaN(total)) {
|
||||
console.warn('Invalid version total', groups[3], groups[4], groups[5])
|
||||
return null
|
||||
|
||||
@@ -73,13 +73,23 @@ export const getters = {
|
||||
} else {
|
||||
series[audiobook.book.series] = {
|
||||
type: 'series',
|
||||
name: audiobook.book.series,
|
||||
name: audiobook.book.series || '',
|
||||
books: [audiobook]
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
return Object.values(series)
|
||||
var seriesArray = Object.values(series).map((_series) => {
|
||||
_series.books = sort(_series.books)['asc']((ab) => {
|
||||
return ab.book && ab.book.volumeNumber && !isNaN(ab.book.volumeNumber) ? Number(ab.book.volumeNumber) : null
|
||||
})
|
||||
return _series
|
||||
})
|
||||
if (state.keywordFilter) {
|
||||
const keywordFilter = state.keywordFilter.toLowerCase()
|
||||
return seriesArray.filter((_series) => _series.name.toLowerCase().includes(keywordFilter))
|
||||
}
|
||||
return seriesArray
|
||||
},
|
||||
getUniqueAuthors: (state) => {
|
||||
var _authors = state.audiobooks.filter(ab => !!(ab.book && ab.book.author)).map(ab => ab.book.author)
|
||||
|
||||
+4
-6
@@ -1,17 +1,15 @@
|
||||
{
|
||||
"name": "audiobookshelf",
|
||||
"version": "1.2.0",
|
||||
"description": "Self-hosted audiobook server for managing and playing audiobooks.",
|
||||
"version": "1.2.4",
|
||||
"description": "Self-hosted audiobook server for managing and playing audiobooks",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"dev": "node index.js",
|
||||
"start": "node index.js",
|
||||
"client": "cd client && npm install && npm run generate",
|
||||
"prod": "npm run client && npm install && node prod.js",
|
||||
"generate": "cd client && npm run generate",
|
||||
"build-win": "pkg -t node12-win-x64 -o ./dist/win/audiobookshelf .",
|
||||
"build-linuxarm": "pkg -t node12-linux-arm64 -o ./dist/linuxarm/audiobookshelf .",
|
||||
"build-linuxamd": "pkg -t node12-linux-amd64 -o ./dist/linuxamd/audiobookshelf ."
|
||||
"build-win": "npm run build-prep && pkg -t node12-win-x64 -o ./dist/win/audiobookshelf .",
|
||||
"build-linux": "build/linuxpackager"
|
||||
},
|
||||
"bin": "prod.js",
|
||||
"pkg": {
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
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)
|
||||
|
||||
Android app is in beta, try it out on the [Google Play Store](https://play.google.com/store/apps/details?id=com.audiobookshelf.app)
|
||||
|
||||
**Free & open source Android/iOS app is in development**
|
||||
@@ -66,13 +68,71 @@ Will save the title as `Hackers` and the subtitle as `Heroes of the Computer Rev
|
||||
|
||||
## Installation
|
||||
|
||||
Built to run in Docker for now (also on Unraid server Community Apps)
|
||||
### Docker Install
|
||||
Available in Unraid Community Apps
|
||||
|
||||
```bash
|
||||
docker run -d -p 1337:80 -v /audiobooks:/audiobooks -v /config:/config -v /metadata:/metadata --name audiobookshelf --rm advplyr/audiobookshelf
|
||||
docker pull advplyr/audiobookshelf
|
||||
|
||||
docker run -d \
|
||||
-p 1337:80 \
|
||||
-v </path/to/audiobooks>:/audiobooks \
|
||||
-v </path/to/config>:/config \
|
||||
-v </path/to/metadata>:/metadata \
|
||||
--name audiobookshelf \
|
||||
--rm advplyr/audiobookshelf
|
||||
```
|
||||
|
||||
## Running on your local
|
||||
### Linux (amd64) Install
|
||||
|
||||
A simple installer is added to setup the initial config. If you already have audiobooks, you can enter the path to your audiobooks during the install. The installer will create a user and group named `audiobookshelf`.
|
||||
|
||||
|
||||
|
||||
### Ubuntu Install via PPA
|
||||
|
||||
A PPA is hosted on [github](https://github.com/advplyr/audiobookshelf-ppa), add and install:
|
||||
|
||||
```bash
|
||||
curl -s --compressed "https://advplyr.github.io/audiobookshelf-ppa/KEY.gpg" | sudo apt-key add -
|
||||
|
||||
sudo curl -s --compressed -o /etc/apt/sources.list.d/audiobookshelf.list "https://advplyr.github.io/audiobookshelf-ppa/audiobookshelf.list"
|
||||
|
||||
sudo apt update
|
||||
|
||||
sudo apt install audiobookshelf
|
||||
```
|
||||
|
||||
or use a single command
|
||||
|
||||
```bash
|
||||
curl -s --compressed "https://advplyr.github.io/audiobookshelf-ppa/KEY.gpg" | sudo apt-key add - && sudo curl -s --compressed -o /etc/apt/sources.list.d/audiobookshelf.list "https://advplyr.github.io/audiobookshelf-ppa/audiobookshelf.list" && sudo apt update && sudo apt install audiobookshelf
|
||||
```
|
||||
|
||||
### Install via debian package
|
||||
|
||||
Get the `deb` file from the [github repo](https://github.com/advplyr/audiobookshelf-ppa).
|
||||
|
||||
```bash
|
||||
wget https://advplyr.github.io/audiobookshelf-ppa/audiobookshelf_1.2.3_amd64.deb
|
||||
|
||||
sudo apt install ./audiobookshelf_1.2.3_amd64.deb
|
||||
```
|
||||
|
||||
|
||||
#### File locations
|
||||
|
||||
Project directory: `/usr/share/audiobookshelf/`
|
||||
|
||||
Config file: `/etc/default/audiobookshelf`
|
||||
|
||||
System Service: `/lib/systemd/system/audiobookshelf.service`
|
||||
|
||||
Ffmpeg static build: `/usr/lib/audiobookshelf-ffmpeg/`
|
||||
|
||||
## Run from source
|
||||
|
||||
Note: you will need `npm`, `node12`, and `ffmpeg` to run this project locally
|
||||
|
||||
```bash
|
||||
git clone https://github.com/advplyr/audiobookshelf.git
|
||||
|
||||
@@ -7,7 +7,7 @@ const Logger = require('./Logger')
|
||||
const Download = require('./objects/Download')
|
||||
const { writeConcatFile, writeMetadataFile } = require('./utils/ffmpegHelpers')
|
||||
const { getFileSize } = require('./utils/fileUtils')
|
||||
|
||||
const TAG = 'DownloadManager'
|
||||
class DownloadManager {
|
||||
constructor(db, MetadataPath, AudiobookPath, emitter) {
|
||||
this.db = db
|
||||
@@ -260,7 +260,21 @@ class DownloadManager {
|
||||
output: download.fullPath,
|
||||
}
|
||||
|
||||
var worker = new workerThreads.Worker('./server/utils/downloadWorker.js', { workerData })
|
||||
var worker = null
|
||||
try {
|
||||
var workerPath = Path.join(global.appRoot, 'server/utils/downloadWorker.js')
|
||||
worker = new workerThreads.Worker(workerPath, { workerData })
|
||||
} catch (error) {
|
||||
Logger.error(`[${TAG}] Start worker thread failed`, error)
|
||||
if (download.socket) {
|
||||
var downloadJson = download.toJSON()
|
||||
download.socket.emit('download_failed', downloadJson)
|
||||
}
|
||||
this.removeDownload(download)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
worker.on('message', (message) => {
|
||||
if (message != null && typeof message === 'object') {
|
||||
if (message.type === 'RESULT') {
|
||||
|
||||
+6
-1
@@ -100,7 +100,12 @@ class Scanner {
|
||||
hasUpdatedAudioFiles = true
|
||||
}
|
||||
} else {
|
||||
newAudioFiles.push(file)
|
||||
var audioFileWithMatchingPath = existingAudiobook.getAudioFileByPath(file.fullPath)
|
||||
if (audioFileWithMatchingPath) {
|
||||
Logger.warn(`[Scanner] Audio file with path already exists with different inode, New: "${file.filename}" (${file.ino}) | Existing: ${audioFileWithMatchingPath.filename} (${audioFileWithMatchingPath.ino})`)
|
||||
} else {
|
||||
newAudioFiles.push(file)
|
||||
}
|
||||
}
|
||||
})
|
||||
if (newAudioFiles.length) {
|
||||
|
||||
@@ -65,12 +65,13 @@ class StreamManager {
|
||||
if (!dirs || !dirs.length) return true
|
||||
|
||||
await Promise.all(dirs.map(async (dirname) => {
|
||||
if (dirname !== 'streams' && dirname !== 'books') {
|
||||
if (dirname !== 'streams' && dirname !== 'books' && dirname !== 'downloads') {
|
||||
var fullPath = Path.join(this.MetadataPath, dirname)
|
||||
Logger.warn(`Removing OLD Orphan Stream ${dirname}`)
|
||||
return fs.remove(fullPath)
|
||||
}
|
||||
}))
|
||||
|
||||
return true
|
||||
} catch (error) {
|
||||
Logger.debug('No old orphan streams', error)
|
||||
|
||||
@@ -425,6 +425,10 @@ class Audiobook {
|
||||
return this.audioFiles.find(af => af.ino === ino)
|
||||
}
|
||||
|
||||
getAudioFileByPath(fullPath) {
|
||||
return this.audioFiles.find(af => af.fullPath === fullPath)
|
||||
}
|
||||
|
||||
setChapters() {
|
||||
// If 1 audio file without chapters, then no chapters will be set
|
||||
|
||||
|
||||
Reference in New Issue
Block a user