mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2024-12-20 19:06:06 +01:00
Use local image as cover if found, adding release-it version control
This commit is contained in:
parent
30700c1eb0
commit
eab8edce8d
@ -1,3 +1,4 @@
|
||||
.env
|
||||
node_modules
|
||||
npm-debug.log
|
||||
.git
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
.env
|
||||
dev.js
|
||||
node_modules/
|
||||
/config/
|
||||
|
5
.release-it.json
Normal file
5
.release-it.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"github": {
|
||||
"release": true
|
||||
}
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
### STAGE 0: FFMPEG ###
|
||||
FROM jrottenberg/ffmpeg:4.1-alpine AS ffmpeg
|
||||
# FROM alfg/ffmpeg AS ffmpeg
|
||||
|
||||
### STAGE 1: Build client ###
|
||||
FROM node:12-alpine AS build
|
||||
@ -11,8 +10,6 @@ RUN npm run generate
|
||||
|
||||
### STAGE 2: Build server ###
|
||||
FROM node:12-alpine
|
||||
# RUN apk add --no-cache ffmpeg
|
||||
# RUN apt-get install -y ffmpeg
|
||||
ENV NODE_ENV=production
|
||||
ENV LOG_LEVEL=INFO
|
||||
COPY --from=build /client/dist /client/dist
|
||||
@ -22,5 +19,4 @@ COPY package.json package.json
|
||||
COPY server server
|
||||
RUN npm install --production
|
||||
EXPOSE 80
|
||||
# CMD ["node", "index.js"]
|
||||
CMD ["npm", "start"]
|
||||
|
@ -1,7 +1,13 @@
|
||||
<template>
|
||||
<div class="relative rounded-sm overflow-hidden" :style="{ height: width * 1.6 + 'px', width: width + 'px', maxWidth: width + 'px', minWidth: width + 'px' }">
|
||||
<img ref="cover" :src="cover" class="w-full h-full object-cover" />
|
||||
<img ref="cover" :src="cover" @error="imageError" class="w-full h-full object-cover" />
|
||||
|
||||
<div v-if="imageFailed" class="absolute top-0 left-0 right-0 bottom-0 w-full h-full bg-red-100" :style="{ padding: placeholderCoverPadding + 'rem' }">
|
||||
<div class="w-full h-full border-2 border-error flex flex-col items-center justify-center">
|
||||
<img src="/LogoTransparent.png" class="mb-2" :style="{ height: 64 * sizeMultiplier + 'px' }" />
|
||||
<p class="text-center font-book text-error" :style="{ fontSize: titleFontSize + 'rem' }">Invalid Cover</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!hasCover" class="absolute top-0 left-0 right-0 bottom-0 w-full h-full flex items-center justify-center" :style="{ padding: placeholderCoverPadding + 'rem' }">
|
||||
<div>
|
||||
<p class="text-center font-book" style="color: rgb(247 223 187)" :style="{ fontSize: titleFontSize + 'rem' }">{{ titleCleaned }}</p>
|
||||
@ -26,7 +32,9 @@ export default {
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
return {
|
||||
imageFailed: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
book() {
|
||||
@ -56,23 +64,28 @@ export default {
|
||||
hasCover() {
|
||||
return !!this.book.cover
|
||||
},
|
||||
fontSizeMultiplier() {
|
||||
sizeMultiplier() {
|
||||
return this.width / 120
|
||||
},
|
||||
titleFontSize() {
|
||||
return 0.75 * this.fontSizeMultiplier
|
||||
return 0.75 * this.sizeMultiplier
|
||||
},
|
||||
authorFontSize() {
|
||||
return 0.6 * this.fontSizeMultiplier
|
||||
return 0.6 * this.sizeMultiplier
|
||||
},
|
||||
placeholderCoverPadding() {
|
||||
return 0.8 * this.fontSizeMultiplier
|
||||
return 0.8 * this.sizeMultiplier
|
||||
},
|
||||
authorBottom() {
|
||||
return 0.75 * this.fontSizeMultiplier
|
||||
return 0.75 * this.sizeMultiplier
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
imageError(err) {
|
||||
console.error('ImgError', err)
|
||||
this.imageFailed = true
|
||||
}
|
||||
},
|
||||
methods: {},
|
||||
mounted() {}
|
||||
}
|
||||
</script>
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="w-full">
|
||||
<p class="px-1 text-sm">{{ label }}</p>
|
||||
<p class="px-1 text-sm font-semibold">{{ label }}</p>
|
||||
<div ref="wrapper" class="relative">
|
||||
<form @submit.prevent="submitForm">
|
||||
<div ref="inputWrapper" style="min-height: 40px" class="flex-wrap relative w-full shadow-sm flex items-center bg-primary border border-gray-600 rounded-md px-2 py-1 cursor-text" @click.stop.prevent="clickWrapper" @mouseup.stop.prevent @mousedown.prevent>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="w-full">
|
||||
<p class="px-1 text-sm">{{ label }}</p>
|
||||
<p class="px-1 text-sm font-semibold">{{ label }}</p>
|
||||
<ui-text-input v-model="inputValue" :disabled="disabled" :type="type" class="w-full" />
|
||||
</div>
|
||||
</template>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="w-full">
|
||||
<p class="px-1 text-sm">{{ label }}</p>
|
||||
<p class="px-1 text-sm font-semibold">{{ label }}</p>
|
||||
<ui-textarea-input v-model="inputValue" :rows="rows" class="w-full" />
|
||||
</div>
|
||||
</template>
|
||||
|
@ -136,6 +136,11 @@ export default {
|
||||
this.socket.on('scan_start', this.scanStart)
|
||||
this.socket.on('scan_complete', this.scanComplete)
|
||||
this.socket.on('scan_progress', this.scanProgress)
|
||||
},
|
||||
checkVersion() {
|
||||
this.$axios.$get('http://github.com/advplyr/audiobookshelf/raw/master/package.json').then((data) => {
|
||||
console.log('GOT DATA', data)
|
||||
})
|
||||
}
|
||||
},
|
||||
beforeMount() {
|
||||
@ -145,6 +150,7 @@ export default {
|
||||
},
|
||||
mounted() {
|
||||
this.initializeSocket()
|
||||
this.checkVersion()
|
||||
}
|
||||
}
|
||||
</script>
|
@ -35,7 +35,7 @@ module.exports = {
|
||||
],
|
||||
link: [
|
||||
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
|
||||
{ rel: 'stylesheet', href: 'https://fonts.googleapis.com/css2?family=Fira+Mono&family=Ubuntu+Mono&family=Open+Sans:wght@600&family=Gentium+Book+Basic' },
|
||||
{ rel: 'stylesheet', href: 'https://fonts.googleapis.com/css2?family=Fira+Mono&family=Ubuntu+Mono&family=Open+Sans:wght@400;600&family=Gentium+Book+Basic' },
|
||||
{ rel: 'stylesheet', href: 'https://fonts.googleapis.com/icon?family=Material+Icons' }
|
||||
]
|
||||
},
|
||||
@ -69,7 +69,8 @@ module.exports = {
|
||||
],
|
||||
|
||||
proxy: {
|
||||
'/dev/': { target: 'http://localhost:3333', pathRewrite: { '^/dev/': '' } }
|
||||
'/dev/': { target: 'http://localhost:3333', pathRewrite: { '^/dev/': '' } },
|
||||
'/local/': { target: process.env.NODE_ENV !== 'production' ? 'http://localhost:3333' : '/', pathRewrite: { '^/local/': '' } }
|
||||
},
|
||||
|
||||
io: {
|
||||
|
@ -14,7 +14,7 @@
|
||||
</div>
|
||||
<div class="h-0.5 bg-primary bg-opacity-50 w-full" />
|
||||
<div class="flex items-center py-4">
|
||||
<p class="font-mono">Beta v{{ $config.version }}</p>
|
||||
<p class="font-mono">v{{ $config.version }}</p>
|
||||
<div class="flex-grow" />
|
||||
<p class="pr-2 text-sm font-book text-yellow-400">Report bugs, request features, provide feedback, and contribute on <a class="underline" href="https://github.com/advplyr/audiobookshelf" target="_blank">github</a>.</p>
|
||||
<a href="https://github.com/advplyr/audiobookshelf" target="_blank" class="text-white hover:text-gray-200 hover:scale-150 hover:rotate-6 transform duration-500">
|
||||
|
@ -1,6 +1,9 @@
|
||||
export default function ({ $axios, store }) {
|
||||
$axios.onRequest(config => {
|
||||
console.log('Making request to ' + config.url)
|
||||
if (config.url.startsWith('http:') || config.url.startsWith('https:')) {
|
||||
return
|
||||
}
|
||||
var bearerToken = store.state.user ? store.state.user.token : null
|
||||
// console.log('Bearer token', bearerToken)
|
||||
if (bearerToken) {
|
||||
|
2137
package-lock.json
generated
2137
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -5,7 +5,9 @@
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"dev": "node index.js",
|
||||
"start": "node index.js"
|
||||
"start": "node index.js",
|
||||
"release": "dotenv release-it --disable-metrics --no-npm --npm.skipChecks",
|
||||
"release-dry": "dotenv release-it --disable-metrics --no-npm --npm.skipChecks --dry-run"
|
||||
},
|
||||
"author": "advplyr",
|
||||
"license": "ISC",
|
||||
@ -22,5 +24,9 @@
|
||||
"njodb": "^0.4.20",
|
||||
"node-dir": "^0.1.17",
|
||||
"socket.io": "^4.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"dotenv-cli": "^4.0.0",
|
||||
"release-it": "^14.11.5"
|
||||
}
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
const Path = require('path')
|
||||
class Book {
|
||||
constructor(book = null) {
|
||||
this.olid = null
|
||||
@ -42,7 +43,6 @@ class Book {
|
||||
}
|
||||
|
||||
setData(data) {
|
||||
console.log('SET DATA', data)
|
||||
this.olid = data.olid || null
|
||||
this.title = data.title || null
|
||||
this.author = data.author || null
|
||||
@ -51,6 +51,14 @@ class Book {
|
||||
this.description = data.description || null
|
||||
this.cover = data.cover || null
|
||||
this.genres = data.genres || []
|
||||
|
||||
// Use first image file as cover
|
||||
if (data.otherFiles && data.otherFiles.length) {
|
||||
var imageFile = data.otherFiles.find(f => f.filetype === 'image')
|
||||
if (imageFile) {
|
||||
this.cover = Path.join('/local', imageFile.path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
update(payload) {
|
||||
|
@ -13,7 +13,6 @@ const ApiController = require('./ApiController')
|
||||
const HlsController = require('./HlsController')
|
||||
const StreamManager = require('./StreamManager')
|
||||
const Logger = require('./Logger')
|
||||
const streamTest = require('./streamTest')
|
||||
|
||||
class Server {
|
||||
constructor(PORT, CONFIG_PATH, METADATA_PATH, AUDIOBOOK_PATH) {
|
||||
@ -110,7 +109,6 @@ class Server {
|
||||
const distPath = Path.join(global.appRoot, '/client/dist')
|
||||
app.use(express.static(distPath))
|
||||
}
|
||||
|
||||
app.use(express.static(this.AudiobookPath))
|
||||
app.use(express.static(this.MetadataPath))
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
@ -122,13 +120,6 @@ class Server {
|
||||
app.get('/', (req, res) => {
|
||||
res.sendFile('/index.html')
|
||||
})
|
||||
app.get('/test/:id', (req, res) => {
|
||||
var audiobook = this.audiobooks.find(a => a.id === req.params.id)
|
||||
var startTime = !isNaN(req.query.start) ? Number(req.query.start) : 0
|
||||
Logger.info('/test with audiobook', audiobook.title)
|
||||
streamTest.start(audiobook, startTime)
|
||||
res.sendStatus(200)
|
||||
})
|
||||
|
||||
app.post('/login', (req, res) => this.auth.login(req, res))
|
||||
app.post('/logout', this.logout.bind(this))
|
||||
|
@ -1,112 +0,0 @@
|
||||
const Ffmpeg = require('fluent-ffmpeg')
|
||||
const Path = require('path')
|
||||
const fs = require('fs-extra')
|
||||
const Logger = require('./Logger')
|
||||
const { secondsToTimestamp } = require('./utils/fileUtils')
|
||||
|
||||
function escapeSingleQuotes(path) {
|
||||
return path.replace(/\\/g, '/').replace(/ /g, '\\ ').replace(/'/g, '\\\'')
|
||||
}
|
||||
|
||||
function getNumSegments(audiobook, segmentLength) {
|
||||
var numSegments = Math.floor(audiobook.totalDuration / segmentLength)
|
||||
var remainingTime = audiobook.totalDuration - (numSegments * segmentLength)
|
||||
if (remainingTime > 0) numSegments++
|
||||
return numSegments
|
||||
}
|
||||
|
||||
async function start(audiobook, startTime = 0, segmentLength = 6) {
|
||||
var testDir = Path.join(global.appRoot, 'test', audiobook.id)
|
||||
var existsAlready = await fs.pathExists(testDir)
|
||||
if (existsAlready) {
|
||||
await fs.remove(testDir).then(() => {
|
||||
Logger.info('Deleted test dir data', testDir)
|
||||
}).catch((err) => {
|
||||
Logger.error('Failed to delete test dir', err)
|
||||
})
|
||||
}
|
||||
|
||||
fs.ensureDirSync(testDir)
|
||||
var concatFilePath = Path.join(testDir, 'concat.txt')
|
||||
var playlistPath = Path.join(testDir, 'output.m3u8')
|
||||
|
||||
|
||||
const numSegments = getNumSegments(audiobook, segmentLength)
|
||||
const segmentStartNumber = Math.floor(startTime / segmentLength)
|
||||
Logger.info(`[STREAM] START STREAM - Num Segments: ${numSegments} - Segment Start: ${segmentStartNumber}`)
|
||||
|
||||
const tracks = audiobook.tracks
|
||||
|
||||
const ffmpeg = Ffmpeg()
|
||||
|
||||
var currTrackEnd = 0
|
||||
|
||||
var startingTrack = tracks.find(t => {
|
||||
currTrackEnd += t.duration
|
||||
return startTime < currTrackEnd
|
||||
})
|
||||
var trackStartTime = currTrackEnd - startingTrack.duration
|
||||
var currInpoint = startTime - trackStartTime
|
||||
Logger.info('Starting Track Index', startingTrack.index)
|
||||
|
||||
var tracksToInclude = tracks.filter(t => t.index >= startingTrack.index)
|
||||
var trackPaths = tracksToInclude.map(t => {
|
||||
var line = 'file ' + escapeSingleQuotes(t.fullPath) + '\n' + `duration ${t.duration}`
|
||||
// if (t.index === startingTrack.index) {
|
||||
// currInpoint = 60 * 5 + 4
|
||||
// line += `\ninpoint ${currInpoint}`
|
||||
// }
|
||||
return line
|
||||
})
|
||||
|
||||
var inputstr = trackPaths.join('\n\n')
|
||||
await fs.writeFile(concatFilePath, inputstr)
|
||||
|
||||
ffmpeg.addInput(concatFilePath)
|
||||
ffmpeg.inputFormat('concat')
|
||||
ffmpeg.inputOption('-safe 0')
|
||||
|
||||
var shiftedStartTime = startTime - trackStartTime
|
||||
if (startTime > 0) {
|
||||
Logger.info(`[STREAM] Starting Stream at startTime ${secondsToTimestamp(startTime)} and Segment #${segmentStartNumber}`)
|
||||
ffmpeg.inputOption(`-ss ${shiftedStartTime}`)
|
||||
ffmpeg.inputOption('-noaccurate_seek')
|
||||
}
|
||||
|
||||
ffmpeg.addOption([
|
||||
'-loglevel warning',
|
||||
'-map 0:a',
|
||||
'-c:a copy'
|
||||
])
|
||||
ffmpeg.addOption([
|
||||
'-f hls',
|
||||
"-copyts",
|
||||
"-avoid_negative_ts disabled",
|
||||
"-max_delay 5000000",
|
||||
"-max_muxing_queue_size 2048",
|
||||
`-hls_time 6`,
|
||||
"-hls_segment_type mpegts",
|
||||
`-start_number ${segmentStartNumber}`,
|
||||
"-hls_playlist_type vod",
|
||||
"-hls_list_size 0",
|
||||
"-hls_allow_cache 0"
|
||||
])
|
||||
var segmentFilename = Path.join(testDir, 'output-%d.ts')
|
||||
ffmpeg.addOption(`-hls_segment_filename ${segmentFilename}`)
|
||||
ffmpeg.output(playlistPath)
|
||||
|
||||
ffmpeg.on('start', (command) => {
|
||||
Logger.info('[FFMPEG-START] FFMPEG transcoding started with command: ' + command)
|
||||
})
|
||||
ffmpeg.on('stderr', (stdErrline) => {
|
||||
Logger.info('[FFMPEG-STDERR]', stdErrline)
|
||||
})
|
||||
ffmpeg.on('error', (err, stdout, stderr) => {
|
||||
Logger.info('[FFMPEG-ERROR]', err)
|
||||
})
|
||||
ffmpeg.on('end', (stdout, stderr) => {
|
||||
Logger.info('[FFMPEG] Transcode ended')
|
||||
})
|
||||
ffmpeg.run()
|
||||
}
|
||||
module.exports.start = start
|
@ -40,6 +40,10 @@ async function getAllAudiobookFiles(abRootPath) {
|
||||
|
||||
// If relative file directory has 3 folders, then the middle folder will be series
|
||||
var splitDir = pathformat.dir.split(Path.sep)
|
||||
if (splitDir.length === 1) {
|
||||
Logger.error('Invalid file in root dir', filepath)
|
||||
return
|
||||
}
|
||||
var author = splitDir.shift()
|
||||
var series = null
|
||||
if (splitDir.length > 1) series = splitDir.shift()
|
||||
|
Loading…
Reference in New Issue
Block a user