Use local image as cover if found, adding release-it version control

This commit is contained in:
Mark Cooper 2021-08-21 13:02:24 -05:00
parent 30700c1eb0
commit eab8edce8d
18 changed files with 2200 additions and 142 deletions

View File

@ -1,3 +1,4 @@
.env
node_modules
npm-debug.log
.git

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
.env
dev.js
node_modules/
/config/

5
.release-it.json Normal file
View File

@ -0,0 +1,5 @@
{
"github": {
"release": true
}
}

View File

@ -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"]

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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: {

View File

@ -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">

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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"
}
}

View File

@ -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) {

View File

@ -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))

View File

@ -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

View File

@ -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()